summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/Action.php467
-rw-r--r--includes/AjaxDispatcher.php15
-rw-r--r--includes/AjaxFunctions.php101
-rw-r--r--includes/AjaxResponse.php4
-rw-r--r--includes/Article.php3667
-rw-r--r--includes/AuthPlugin.php13
-rw-r--r--includes/AutoLoader.php522
-rw-r--r--includes/Autopromote.php72
-rw-r--r--includes/BacklinkCache.php131
-rw-r--r--includes/BagOStuff.php906
-rw-r--r--includes/Block.php1156
-rw-r--r--includes/Category.php11
-rw-r--r--includes/CategoryPage.php197
-rw-r--r--includes/Categoryfinder.php7
-rw-r--r--includes/Cdb.php12
-rw-r--r--includes/Cdb_PHP.php76
-rw-r--r--includes/ChangeTags.php36
-rw-r--r--includes/ChangesFeed.php54
-rw-r--r--includes/ChangesList.php564
-rw-r--r--includes/Collation.php52
-rw-r--r--includes/ConfEditor.php78
-rw-r--r--includes/Cookie.php245
-rw-r--r--includes/DefaultSettings.php833
-rw-r--r--includes/Defines.php41
-rw-r--r--includes/DjVuImage.php12
-rw-r--r--includes/EditPage.php905
-rw-r--r--includes/Exception.php351
-rw-r--r--includes/Exif.php1150
-rw-r--r--includes/Export.php448
-rw-r--r--includes/ExternalEdit.php63
-rw-r--r--includes/ExternalStore.php16
-rw-r--r--includes/ExternalStoreDB.php16
-rw-r--r--includes/ExternalUser.php4
-rw-r--r--includes/FakeTitle.php6
-rw-r--r--includes/Fallback.php200
-rw-r--r--includes/Feed.php52
-rw-r--r--includes/FeedUtils.php2
-rw-r--r--includes/FileDeleteForm.php36
-rw-r--r--includes/FileRevertForm.php180
-rw-r--r--includes/ForkController.php6
-rw-r--r--includes/FormOptions.php121
-rw-r--r--includes/GenderCache.php135
-rw-r--r--includes/GlobalFunctions.php2092
-rw-r--r--includes/HTMLForm.php507
-rw-r--r--includes/HistoryBlob.php88
-rw-r--r--includes/HistoryPage.php50
-rw-r--r--includes/Hooks.php384
-rw-r--r--includes/Html.php102
-rw-r--r--includes/HttpFunctions.old.php1
-rw-r--r--includes/HttpFunctions.php306
-rw-r--r--includes/IP.php86
-rw-r--r--includes/ImageFunctions.php2
-rw-r--r--includes/ImageGallery.php147
-rw-r--r--includes/ImagePage.php660
-rw-r--r--includes/ImageQueryPage.php29
-rw-r--r--includes/Import.php290
-rw-r--r--includes/Init.php184
-rw-r--r--includes/Licenses.php49
-rw-r--r--includes/LinkFilter.php36
-rw-r--r--includes/Linker.php971
-rw-r--r--includes/LinksUpdate.php44
-rw-r--r--includes/LocalisationCache.php130
-rw-r--r--includes/LogEventsList.php277
-rw-r--r--includes/LogPage.php135
-rw-r--r--includes/MWFunction.php64
-rw-r--r--includes/MacBinary.php272
-rw-r--r--includes/MagicWord.php169
-rw-r--r--includes/Math.php341
-rw-r--r--includes/Message.php194
-rw-r--r--includes/MessageBlobStore.php56
-rw-r--r--includes/Metadata.php146
-rw-r--r--includes/MimeMagic.php475
-rw-r--r--includes/Namespace.php106
-rw-r--r--includes/ObjectCache.php123
-rw-r--r--includes/OutputHandler.php22
-rw-r--r--includes/OutputPage.php1537
-rw-r--r--includes/PHPVersionError.php91
-rw-r--r--includes/PageQueryPage.php2
-rw-r--r--includes/Pager.php40
-rw-r--r--includes/PatrolLog.php12
-rw-r--r--includes/PoolCounter.php115
-rw-r--r--includes/Preferences.php244
-rw-r--r--includes/PrefixSearch.php40
-rw-r--r--includes/ProfilerSimpleText.php39
-rw-r--r--includes/ProfilerStub.php52
-rw-r--r--includes/ProtectionForm.php80
-rw-r--r--includes/ProxyTools.php107
-rw-r--r--includes/QueryPage.php393
-rw-r--r--includes/RawPage.php38
-rw-r--r--includes/RecentChange.php157
-rw-r--r--includes/RequestContext.php399
-rw-r--r--includes/Revision.php124
-rw-r--r--includes/RevisionList.php370
-rw-r--r--includes/Sanitizer.php760
-rw-r--r--includes/SeleniumWebSettings.php197
-rw-r--r--includes/Setup.php463
-rw-r--r--includes/SiteConfiguration.php43
-rw-r--r--includes/SiteStats.php148
-rw-r--r--includes/Skin.php1761
-rw-r--r--includes/SkinLegacy.php942
-rw-r--r--includes/SkinTemplate.php1275
-rw-r--r--includes/SpecialPage.php1164
-rw-r--r--includes/SpecialPageFactory.php554
-rw-r--r--includes/SquidPurgeClient.php28
-rw-r--r--includes/Status.php54
-rw-r--r--includes/StreamFile.php15
-rw-r--r--includes/StringUtils.php22
-rw-r--r--includes/StubObject.php22
-rw-r--r--includes/Title.php1394
-rw-r--r--includes/TitleArray.php22
-rw-r--r--includes/User.php1740
-rw-r--r--includes/UserArray.php35
-rw-r--r--includes/UserMailer.php183
-rw-r--r--includes/ViewCountUpdate.php111
-rw-r--r--includes/WatchedItem.php5
-rw-r--r--includes/WatchlistEditor.php528
-rw-r--r--includes/WebRequest.php375
-rw-r--r--includes/WebResponse.php130
-rw-r--r--includes/WebStart.php82
-rw-r--r--includes/Wiki.php639
-rw-r--r--includes/WikiCategoryPage.php37
-rw-r--r--includes/WikiError.php12
-rw-r--r--includes/WikiFilePage.php139
-rw-r--r--includes/WikiMap.php21
-rw-r--r--includes/WikiPage.php2677
-rw-r--r--includes/Xml.php202
-rw-r--r--includes/XmlTypeCheck.php17
-rw-r--r--includes/ZhClient.php88
-rw-r--r--includes/ZhConversion.php230
-rw-r--r--includes/ZipDirectoryReader.php684
-rw-r--r--includes/actions/CreditsAction.php (renamed from includes/Credits.php)109
-rw-r--r--includes/actions/DeletetrackbackAction.php54
-rw-r--r--includes/actions/InfoAction.php151
-rw-r--r--includes/actions/MarkpatrolledAction.php86
-rw-r--r--includes/actions/PurgeAction.php100
-rw-r--r--includes/actions/RevertAction.php140
-rw-r--r--includes/actions/RevisiondeleteAction.php53
-rw-r--r--includes/actions/RollbackAction.php122
-rw-r--r--includes/actions/WatchAction.php183
-rw-r--r--includes/api/ApiBase.php276
-rw-r--r--includes/api/ApiBlock.php85
-rw-r--r--includes/api/ApiComparePages.php130
-rw-r--r--includes/api/ApiDelete.php42
-rw-r--r--includes/api/ApiDisabled.php6
-rw-r--r--includes/api/ApiEditPage.php105
-rw-r--r--includes/api/ApiEmailUser.php8
-rw-r--r--includes/api/ApiExpandTemplates.php21
-rw-r--r--includes/api/ApiFeedContributions.php207
-rw-r--r--includes/api/ApiFeedWatchlist.php41
-rw-r--r--includes/api/ApiFileRevert.php189
-rw-r--r--includes/api/ApiFormatBase.php22
-rw-r--r--includes/api/ApiFormatDbg.php6
-rw-r--r--includes/api/ApiFormatDump.php4
-rw-r--r--includes/api/ApiFormatJson.php4
-rw-r--r--includes/api/ApiFormatPhp.php4
-rw-r--r--includes/api/ApiFormatRaw.php8
-rw-r--r--includes/api/ApiFormatTxt.php6
-rw-r--r--includes/api/ApiFormatWddx.php4
-rw-r--r--includes/api/ApiFormatXml.php24
-rw-r--r--includes/api/ApiFormatYaml.php14
-rw-r--r--includes/api/ApiHelp.php17
-rw-r--r--includes/api/ApiImport.php38
-rw-r--r--includes/api/ApiLogin.php25
-rw-r--r--includes/api/ApiLogout.php10
-rw-r--r--includes/api/ApiMain.php256
-rw-r--r--includes/api/ApiMove.php49
-rw-r--r--includes/api/ApiOpenSearch.php12
-rw-r--r--includes/api/ApiPageSet.php127
-rw-r--r--includes/api/ApiParamInfo.php83
-rw-r--r--includes/api/ApiParse.php209
-rw-r--r--includes/api/ApiPatrol.php10
-rw-r--r--includes/api/ApiProtect.php24
-rw-r--r--includes/api/ApiPurge.php47
-rw-r--r--includes/api/ApiQuery.php74
-rw-r--r--includes/api/ApiQueryAllCategories.php28
-rw-r--r--includes/api/ApiQueryAllLinks.php31
-rw-r--r--includes/api/ApiQueryAllUsers.php189
-rw-r--r--includes/api/ApiQueryAllimages.php68
-rw-r--r--includes/api/ApiQueryAllmessages.php135
-rw-r--r--includes/api/ApiQueryAllpages.php74
-rw-r--r--includes/api/ApiQueryBacklinks.php80
-rw-r--r--includes/api/ApiQueryBase.php104
-rw-r--r--includes/api/ApiQueryBlocks.php86
-rw-r--r--includes/api/ApiQueryCategories.php14
-rw-r--r--includes/api/ApiQueryCategoryInfo.php8
-rw-r--r--includes/api/ApiQueryCategoryMembers.php88
-rw-r--r--includes/api/ApiQueryDeletedrevs.php117
-rw-r--r--includes/api/ApiQueryDisabled.php6
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php14
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php109
-rw-r--r--includes/api/ApiQueryExternalLinks.php52
-rw-r--r--includes/api/ApiQueryFilearchive.php113
-rw-r--r--includes/api/ApiQueryIWBacklinks.php13
-rw-r--r--includes/api/ApiQueryIWLinks.php33
-rw-r--r--includes/api/ApiQueryImageInfo.php227
-rw-r--r--includes/api/ApiQueryImages.php28
-rw-r--r--includes/api/ApiQueryInfo.php50
-rw-r--r--includes/api/ApiQueryLangBacklinks.php220
-rw-r--r--includes/api/ApiQueryLangLinks.php39
-rw-r--r--includes/api/ApiQueryLinks.php18
-rw-r--r--includes/api/ApiQueryLogEvents.php82
-rw-r--r--includes/api/ApiQueryPageProps.php62
-rw-r--r--includes/api/ApiQueryProtectedTitles.php32
-rw-r--r--includes/api/ApiQueryQueryPage.php198
-rw-r--r--includes/api/ApiQueryRandom.php22
-rw-r--r--includes/api/ApiQueryRecentChanges.php132
-rw-r--r--includes/api/ApiQueryRevisions.php57
-rw-r--r--includes/api/ApiQuerySearch.php38
-rw-r--r--includes/api/ApiQuerySiteinfo.php140
-rw-r--r--includes/api/ApiQueryStashImageInfo.php93
-rw-r--r--includes/api/ApiQueryTags.php20
-rw-r--r--includes/api/ApiQueryUserContributions.php30
-rw-r--r--includes/api/ApiQueryUserInfo.php41
-rw-r--r--includes/api/ApiQueryUsers.php97
-rw-r--r--includes/api/ApiQueryWatchlist.php94
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php14
-rw-r--r--includes/api/ApiResult.php35
-rw-r--r--includes/api/ApiRollback.php24
-rw-r--r--includes/api/ApiRsd.php15
-rw-r--r--includes/api/ApiUnblock.php46
-rw-r--r--includes/api/ApiUndelete.php19
-rw-r--r--includes/api/ApiUpload.php291
-rw-r--r--includes/api/ApiUserrights.php17
-rw-r--r--includes/api/ApiWatch.php33
-rw-r--r--includes/cache/CacheDependency.php (renamed from includes/CacheDependency.php)29
-rw-r--r--includes/cache/HTMLCacheUpdate.php (renamed from includes/HTMLCacheUpdate.php)28
-rw-r--r--includes/cache/HTMLFileCache.php (renamed from includes/HTMLFileCache.php)51
-rw-r--r--includes/cache/LinkBatch.php (renamed from includes/LinkBatch.php)37
-rw-r--r--includes/cache/LinkCache.php (renamed from includes/LinkCache.php)30
-rw-r--r--includes/cache/MemcachedSessions.php (renamed from includes/MemcachedSessions.php)5
-rw-r--r--includes/cache/MessageCache.php (renamed from includes/MessageCache.php)539
-rw-r--r--includes/cache/SquidUpdate.php (renamed from includes/SquidUpdate.php)50
-rw-r--r--includes/db/CloneDatabase.php158
-rw-r--r--includes/db/Database.php1763
-rw-r--r--includes/db/DatabaseError.php314
-rw-r--r--includes/db/DatabaseIbm_db2.php292
-rw-r--r--includes/db/DatabaseMssql.php484
-rw-r--r--includes/db/DatabaseMysql.php219
-rw-r--r--includes/db/DatabaseOracle.php290
-rw-r--r--includes/db/DatabasePostgres.php178
-rw-r--r--includes/db/DatabaseSqlite.php299
-rw-r--r--includes/db/DatabaseUtility.php268
-rw-r--r--includes/db/LBFactory.php47
-rw-r--r--includes/db/LBFactory_Multi.php22
-rw-r--r--includes/db/LBFactory_Single.php42
-rw-r--r--includes/db/LoadBalancer.php203
-rw-r--r--includes/db/LoadMonitor.php67
-rw-r--r--includes/diff/DairikiDiff.php (renamed from includes/diff/WikiDiff.php)450
-rw-r--r--includes/diff/DifferenceEngine.php259
-rw-r--r--includes/diff/WikiDiff3.php14
-rw-r--r--includes/extauth/MediaWiki.php35
-rw-r--r--includes/extauth/vB.php6
-rw-r--r--includes/filerepo/ArchivedFile.php69
-rw-r--r--includes/filerepo/FSRepo.php122
-rw-r--r--includes/filerepo/File.php541
-rw-r--r--includes/filerepo/FileRepo.php122
-rw-r--r--includes/filerepo/FileRepoStatus.php12
-rw-r--r--includes/filerepo/ForeignAPIFile.php29
-rw-r--r--includes/filerepo/ForeignAPIRepo.php51
-rw-r--r--includes/filerepo/ForeignDBFile.php23
-rw-r--r--includes/filerepo/ForeignDBRepo.php4
-rw-r--r--includes/filerepo/Image.php80
-rw-r--r--includes/filerepo/LocalFile.php273
-rw-r--r--includes/filerepo/LocalRepo.php37
-rw-r--r--includes/filerepo/NullRepo.php3
-rw-r--r--includes/filerepo/OldLocalFile.php100
-rw-r--r--includes/filerepo/README18
-rw-r--r--includes/filerepo/RepoGroup.php56
-rw-r--r--includes/filerepo/UnregisteredLocalFile.php24
-rw-r--r--includes/installer/CliInstaller.php21
-rw-r--r--includes/installer/DatabaseInstaller.php27
-rw-r--r--includes/installer/DatabaseUpdater.php82
-rw-r--r--includes/installer/Ibm_db2Installer.php251
-rw-r--r--includes/installer/Ibm_db2Updater.php69
-rw-r--r--includes/installer/InstallDocFormatter.php42
-rw-r--r--includes/installer/Installer.i18n.php6105
-rw-r--r--includes/installer/Installer.php115
-rw-r--r--includes/installer/LocalSettingsGenerator.php19
-rw-r--r--includes/installer/MysqlInstaller.php62
-rw-r--r--includes/installer/MysqlUpdater.php36
-rw-r--r--includes/installer/OracleInstaller.php6
-rw-r--r--includes/installer/OracleUpdater.php35
-rw-r--r--includes/installer/PhpBugTests.php2
-rw-r--r--includes/installer/PostgresInstaller.php54
-rw-r--r--includes/installer/PostgresUpdater.php2
-rw-r--r--includes/installer/SqliteInstaller.php43
-rw-r--r--includes/installer/SqliteUpdater.php7
-rw-r--r--includes/installer/WebInstaller.php89
-rw-r--r--includes/installer/WebInstallerOutput.php27
-rw-r--r--includes/installer/WebInstallerPage.php114
-rw-r--r--includes/interwiki/Interwiki.php (renamed from includes/Interwiki.php)32
-rw-r--r--includes/job/DoubleRedirectJob.php10
-rw-r--r--includes/job/JobQueue.php74
-rw-r--r--includes/job/RefreshLinksJob.php2
-rw-r--r--includes/job/UploadFromUrlJob.php61
-rw-r--r--includes/json/FormatJson.php7
-rw-r--r--includes/json/Services_JSON.php80
-rw-r--r--includes/libs/CSSMin.php52
-rw-r--r--includes/libs/HttpStatus.php68
-rw-r--r--includes/libs/jsminplus.php2094
-rw-r--r--includes/libs/spyc.php248
-rw-r--r--includes/media/BMP.php35
-rw-r--r--includes/media/Bitmap.php363
-rw-r--r--includes/media/BitmapMetadataHandler.php269
-rw-r--r--includes/media/Bitmap_ClientOnly.php14
-rw-r--r--includes/media/DjVu.php65
-rw-r--r--includes/media/Exif.php836
-rw-r--r--includes/media/ExifBitmap.php210
-rw-r--r--includes/media/FormatMetadata.php1354
-rw-r--r--includes/media/GIF.php103
-rw-r--r--includes/media/GIFMetadataExtractor.php236
-rw-r--r--includes/media/Generic.php302
-rw-r--r--includes/media/IPTC.php576
-rw-r--r--includes/media/Jpeg.php46
-rw-r--r--includes/media/JpegMetadataExtractor.php252
-rw-r--r--includes/media/MediaTransformOutput.php26
-rw-r--r--includes/media/PNG.php88
-rw-r--r--includes/media/PNGMetadataExtractor.php359
-rw-r--r--includes/media/SVG.php92
-rw-r--r--includes/media/SVGMetadataExtractor.php27
-rw-r--r--includes/media/Tiff.php51
-rw-r--r--includes/media/XMP.php1174
-rw-r--r--includes/media/XMPInfo.php1139
-rw-r--r--includes/media/XMPValidate.php323
-rw-r--r--includes/mime.info3
-rw-r--r--includes/mime.types2
-rw-r--r--includes/normal/CleanUpTest.php425
-rw-r--r--includes/normal/Makefile10
-rw-r--r--includes/normal/Utf8Case.php30
-rw-r--r--includes/normal/Utf8CaseGenerate.php1
-rw-r--r--includes/normal/Utf8Test.php2
-rw-r--r--includes/normal/UtfNormal.php42
-rw-r--r--includes/normal/UtfNormalBench.php1
-rw-r--r--includes/normal/UtfNormalData.inc10
-rw-r--r--includes/normal/UtfNormalDataK.inc2
-rw-r--r--includes/normal/UtfNormalDefines.php6
-rw-r--r--includes/normal/UtfNormalGenerate.php1
-rw-r--r--includes/normal/UtfNormalMemStress.php110
-rw-r--r--includes/normal/UtfNormalTest.php1
-rw-r--r--includes/normal/UtfNormalTest2.php6
-rw-r--r--includes/normal/UtfNormalUtil.php6
-rw-r--r--includes/objectcache/APCBagOStuff.php43
-rw-r--r--includes/objectcache/BagOStuff.php164
-rw-r--r--includes/objectcache/DBABagOStuff.php194
-rw-r--r--includes/objectcache/EhcacheBagOStuff.php230
-rw-r--r--includes/objectcache/EmptyBagOStuff.php27
-rw-r--r--includes/objectcache/HashBagOStuff.php58
-rw-r--r--includes/objectcache/MemcachedClient.php (renamed from includes/memcached-client.php)41
-rw-r--r--includes/objectcache/MemcachedPhpBagOStuff.php178
-rw-r--r--includes/objectcache/MultiWriteBagOStuff.php113
-rw-r--r--includes/objectcache/ObjectCache.php119
-rw-r--r--includes/objectcache/SqlBagOStuff.php432
-rw-r--r--includes/objectcache/WinCacheBagOStuff.php71
-rw-r--r--includes/objectcache/XCacheBagOStuff.php51
-rw-r--r--includes/objectcache/eAccelBagOStuff.php46
-rw-r--r--includes/parser/CoreLinkFunctions.php30
-rw-r--r--includes/parser/CoreParserFunctions.php173
-rw-r--r--includes/parser/CoreTagHooks.php65
-rw-r--r--includes/parser/DateFormatter.php8
-rw-r--r--includes/parser/LinkHolderArray.php252
-rw-r--r--includes/parser/Parser.php1224
-rw-r--r--includes/parser/ParserCache.php44
-rw-r--r--includes/parser/ParserOptions.php192
-rw-r--r--includes/parser/ParserOutput.php133
-rw-r--r--includes/parser/Parser_DiffTest.php4
-rw-r--r--includes/parser/Parser_LinkHooks.php21
-rw-r--r--includes/parser/Preprocessor.php48
-rw-r--r--includes/parser/Preprocessor_DOM.php249
-rw-r--r--includes/parser/Preprocessor_Hash.php214
-rw-r--r--includes/parser/Preprocessor_HipHop.hphp1941
-rw-r--r--includes/parser/StripState.php175
-rw-r--r--includes/parser/Tidy.php163
-rw-r--r--includes/profiler/Profiler.php (renamed from includes/Profiler.php)311
-rw-r--r--includes/profiler/ProfilerSimple.php (renamed from includes/ProfilerSimple.php)47
-rw-r--r--includes/profiler/ProfilerSimpleText.php54
-rw-r--r--includes/profiler/ProfilerSimpleTrace.php (renamed from includes/ProfilerSimpleTrace.php)10
-rw-r--r--includes/profiler/ProfilerSimpleUDP.php (renamed from includes/ProfilerSimpleUDP.php)8
-rw-r--r--includes/profiler/ProfilerStub.php15
-rw-r--r--includes/proxy_check.php54
-rw-r--r--includes/resourceloader/ResourceLoader.php297
-rw-r--r--includes/resourceloader/ResourceLoaderContext.php50
-rw-r--r--includes/resourceloader/ResourceLoaderFileModule.php270
-rw-r--r--includes/resourceloader/ResourceLoaderFilePageModule.php11
-rw-r--r--includes/resourceloader/ResourceLoaderModule.php189
-rw-r--r--includes/resourceloader/ResourceLoaderNoscriptModule.php52
-rw-r--r--includes/resourceloader/ResourceLoaderSiteModule.php4
-rw-r--r--includes/resourceloader/ResourceLoaderStartUpModule.php143
-rw-r--r--includes/resourceloader/ResourceLoaderUserGroupsModule.php59
-rw-r--r--includes/resourceloader/ResourceLoaderUserModule.php12
-rw-r--r--includes/resourceloader/ResourceLoaderUserOptionsModule.php29
-rw-r--r--includes/resourceloader/ResourceLoaderUserTokensModule.php63
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php44
-rw-r--r--includes/revisiondelete/RevisionDelete.php374
-rw-r--r--includes/revisiondelete/RevisionDeleteAbstracts.php235
-rw-r--r--includes/revisiondelete/RevisionDeleteUser.php130
-rw-r--r--includes/revisiondelete/RevisionDeleter.php191
-rw-r--r--includes/search/SearchEngine.php124
-rw-r--r--includes/search/SearchIBM_DB2.php4
-rw-r--r--includes/search/SearchMssql.php8
-rw-r--r--includes/search/SearchMySQL.php190
-rw-r--r--includes/search/SearchOracle.php14
-rw-r--r--includes/search/SearchPostgres.php36
-rw-r--r--includes/search/SearchSqlite.php12
-rw-r--r--includes/search/SearchUpdate.php4
-rw-r--r--includes/specials/SpecialActiveusers.php14
-rw-r--r--includes/specials/SpecialAllmessages.php214
-rw-r--r--includes/specials/SpecialAllpages.php80
-rw-r--r--includes/specials/SpecialAncientpages.php40
-rw-r--r--includes/specials/SpecialBlankpage.php3
-rw-r--r--includes/specials/SpecialBlock.php855
-rw-r--r--includes/specials/SpecialBlockList.php437
-rw-r--r--includes/specials/SpecialBlockip.php892
-rw-r--r--includes/specials/SpecialBlockme.php8
-rw-r--r--includes/specials/SpecialBooksources.php41
-rw-r--r--includes/specials/SpecialBrokenRedirects.php75
-rw-r--r--includes/specials/SpecialCategories.php19
-rw-r--r--includes/specials/SpecialChangePassword.php (renamed from includes/specials/SpecialResetpass.php)64
-rw-r--r--includes/specials/SpecialComparePages.php170
-rw-r--r--includes/specials/SpecialConfirmemail.php79
-rw-r--r--includes/specials/SpecialContributions.php322
-rw-r--r--includes/specials/SpecialDeadendpages.php58
-rw-r--r--includes/specials/SpecialDeletedContributions.php31
-rw-r--r--includes/specials/SpecialDisambiguations.php109
-rw-r--r--includes/specials/SpecialDoubleRedirects.php86
-rw-r--r--includes/specials/SpecialEditWatchlist.php596
-rw-r--r--includes/specials/SpecialEmailuser.php112
-rw-r--r--includes/specials/SpecialExport.php127
-rw-r--r--includes/specials/SpecialFewestrevisions.php41
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php226
-rw-r--r--includes/specials/SpecialFilepath.php11
-rw-r--r--includes/specials/SpecialImport.php50
-rw-r--r--includes/specials/SpecialIpblocklist.php581
-rw-r--r--includes/specials/SpecialLinkSearch.php202
-rw-r--r--includes/specials/SpecialListfiles.php148
-rw-r--r--includes/specials/SpecialListgrouprights.php62
-rw-r--r--includes/specials/SpecialListredirects.php96
-rw-r--r--includes/specials/SpecialListusers.php86
-rw-r--r--includes/specials/SpecialLockdb.php24
-rw-r--r--includes/specials/SpecialLog.php9
-rw-r--r--includes/specials/SpecialLonelypages.php64
-rw-r--r--includes/specials/SpecialLongpages.php15
-rw-r--r--includes/specials/SpecialMIMEsearch.php151
-rw-r--r--includes/specials/SpecialMergeHistory.php210
-rw-r--r--includes/specials/SpecialMostcategories.php49
-rw-r--r--includes/specials/SpecialMostimages.php39
-rw-r--r--includes/specials/SpecialMostlinked.php62
-rw-r--r--includes/specials/SpecialMostlinkedcategories.php57
-rw-r--r--includes/specials/SpecialMostlinkedtemplates.php50
-rw-r--r--includes/specials/SpecialMostrevisions.php62
-rw-r--r--includes/specials/SpecialMovepage.php36
-rw-r--r--includes/specials/SpecialNewimages.php308
-rw-r--r--includes/specials/SpecialNewpages.php286
-rw-r--r--includes/specials/SpecialPasswordReset.php273
-rw-r--r--includes/specials/SpecialPopularpages.php51
-rw-r--r--includes/specials/SpecialPreferences.php5
-rw-r--r--includes/specials/SpecialPrefixindex.php58
-rw-r--r--includes/specials/SpecialProtectedpages.php39
-rw-r--r--includes/specials/SpecialProtectedtitles.php26
-rw-r--r--includes/specials/SpecialRandompage.php90
-rw-r--r--includes/specials/SpecialRecentchanges.php417
-rw-r--r--includes/specials/SpecialRecentchangeslinked.php52
-rw-r--r--includes/specials/SpecialRevisiondelete.php238
-rw-r--r--includes/specials/SpecialSearch.php412
-rw-r--r--includes/specials/SpecialShortpages.php60
-rw-r--r--includes/specials/SpecialSpecialpages.php44
-rw-r--r--includes/specials/SpecialStatistics.php121
-rw-r--r--includes/specials/SpecialTags.php16
-rw-r--r--includes/specials/SpecialUnblock.php209
-rw-r--r--includes/specials/SpecialUncategorizedcategories.php18
-rw-r--r--includes/specials/SpecialUncategorizedimages.php32
-rw-r--r--includes/specials/SpecialUncategorizedpages.php54
-rw-r--r--includes/specials/SpecialUncategorizedtemplates.php18
-rw-r--r--includes/specials/SpecialUndelete.php703
-rw-r--r--includes/specials/SpecialUnlockdb.php16
-rw-r--r--includes/specials/SpecialUnusedcategories.php34
-rw-r--r--includes/specials/SpecialUnusedimages.php70
-rw-r--r--includes/specials/SpecialUnusedtemplates.php39
-rw-r--r--includes/specials/SpecialUnwatchedpages.php66
-rw-r--r--includes/specials/SpecialUpload.php150
-rw-r--r--includes/specials/SpecialUploadStash.php168
-rw-r--r--includes/specials/SpecialUserlogin.php405
-rw-r--r--includes/specials/SpecialUserrights.php29
-rw-r--r--includes/specials/SpecialVersion.php303
-rw-r--r--includes/specials/SpecialWantedcategories.php48
-rw-r--r--includes/specials/SpecialWantedfiles.php45
-rw-r--r--includes/specials/SpecialWantedpages.php102
-rw-r--r--includes/specials/SpecialWantedtemplates.php44
-rw-r--r--includes/specials/SpecialWatchlist.php825
-rw-r--r--includes/specials/SpecialWhatlinkshere.php86
-rw-r--r--includes/specials/SpecialWithoutinterwiki.php66
-rw-r--r--includes/templates/PHP4.php102
-rw-r--r--includes/templates/Userlogin.php48
-rw-r--r--includes/upload/UploadBase.php314
-rw-r--r--includes/upload/UploadFromFile.php33
-rw-r--r--includes/upload/UploadFromStash.php130
-rw-r--r--includes/upload/UploadFromUrl.php31
-rw-r--r--includes/upload/UploadStash.php615
-rw-r--r--includes/zhtable/Makefile.py14
-rw-r--r--includes/zhtable/simp2trad_noconvert.manual137
-rw-r--r--includes/zhtable/simpphrases.manual6
-rw-r--r--includes/zhtable/toCN.manual9
-rw-r--r--includes/zhtable/toHK.manual4
-rw-r--r--includes/zhtable/toSimp.manual5
-rw-r--r--includes/zhtable/toTW.manual8
-rw-r--r--includes/zhtable/toTrad.manual6
-rw-r--r--includes/zhtable/trad2simp.manual144
-rw-r--r--includes/zhtable/trad2simp_noconvert.manual1
-rw-r--r--includes/zhtable/tradphrases.manual50
-rw-r--r--includes/zhtable/tradphrases_exclude.manual1
509 files changed, 65551 insertions, 33821 deletions
diff --git a/includes/Action.php b/includes/Action.php
new file mode 100644
index 00000000..d5432b23
--- /dev/null
+++ b/includes/Action.php
@@ -0,0 +1,467 @@
+<?php
+/**
+ * Actions are things which can be done to pages (edit, delete, rollback, etc). They
+ * are distinct from Special Pages because an action must apply to exactly one page.
+ *
+ * To add an action in an extension, create a subclass of Action, and add the key to
+ * $wgActions. There is also the deprecated UnknownAction hook
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public 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
+ */
+abstract class Action {
+
+ /**
+ * Page on which we're performing the action
+ * @var Article
+ */
+ protected $page;
+
+ /**
+ * IContextSource if specified; otherwise we'll use the Context from the Page
+ * @var IContextSource
+ */
+ protected $context;
+
+ /**
+ * The fields used to create the HTMLForm
+ * @var Array
+ */
+ protected $fields;
+
+ /**
+ * Get the Action subclass which should be used to handle this action, false if
+ * the action is disabled, or null if it's not recognised
+ * @param $action String
+ * @param $overrides Array
+ * @return bool|null|string
+ */
+ private final static function getClass( $action, array $overrides ) {
+ global $wgActions;
+ $action = strtolower( $action );
+
+ if ( !isset( $wgActions[$action] ) ) {
+ return null;
+ }
+
+ if ( $wgActions[$action] === false ) {
+ return false;
+ } elseif ( $wgActions[$action] === true && isset( $overrides[$action] ) ) {
+ return $overrides[$action];
+ } elseif ( $wgActions[$action] === true ) {
+ return ucfirst( $action ) . 'Action';
+ } else {
+ return $wgActions[$action];
+ }
+ }
+
+ /**
+ * Get an appropriate Action subclass for the given action
+ * @param $action String
+ * @param $page Article
+ * @return Action|false|null false if the action is disabled, null
+ * if it is not recognised
+ */
+ public final static function factory( $action, Page $page ) {
+ $class = self::getClass( $action, $page->getActionOverrides() );
+ if ( $class ) {
+ $obj = new $class( $page );
+ return $obj;
+ }
+ return $class;
+ }
+
+ /**
+ * Check if a given action is recognised, even if it's disabled
+ *
+ * @param $name String: name of an action
+ * @return Bool
+ */
+ public final static function exists( $name ) {
+ return self::getClass( $name ) !== null;
+ }
+
+ /**
+ * Get the IContextSource in use here
+ * @return IContextSource
+ */
+ protected final function getContext() {
+ if ( $this->context instanceof IContextSource ) {
+ return $this->context;
+ }
+ return $this->page->getContext();
+ }
+
+ /**
+ * Get the WebRequest being used for this instance
+ *
+ * @return WebRequest
+ */
+ protected final function getRequest() {
+ return $this->getContext()->getRequest();
+ }
+
+ /**
+ * Get the OutputPage being used for this instance
+ *
+ * @return OutputPage
+ */
+ protected final function getOutput() {
+ return $this->getContext()->getOutput();
+ }
+
+ /**
+ * Shortcut to get the User being used for this instance
+ *
+ * @return User
+ */
+ protected final function getUser() {
+ return $this->getContext()->getUser();
+ }
+
+ /**
+ * Shortcut to get the Skin being used for this instance
+ *
+ * @return Skin
+ */
+ protected final function getSkin() {
+ return $this->getContext()->getSkin();
+ }
+
+ /**
+ * Shortcut to get the user Language being used for this instance
+ *
+ * @return Skin
+ */
+ protected final function getLang() {
+ return $this->getContext()->getLang();
+ }
+
+ /**
+ * Shortcut to get the Title object from the page
+ * @return Title
+ */
+ protected final function getTitle() {
+ return $this->page->getTitle();
+ }
+
+ /**
+ * Protected constructor: use Action::factory( $action, $page ) to actually build
+ * these things in the real world
+ * @param Page $page
+ */
+ protected function __construct( Page $page ) {
+ $this->page = $page;
+ }
+
+ /**
+ * Return the name of the action this object responds to
+ * @return String lowercase
+ */
+ public abstract function getName();
+
+ /**
+ * Get the permission required to perform this action. Often, but not always,
+ * the same as the action name
+ */
+ public abstract function getRestriction();
+
+ /**
+ * Checks if the given user (identified by an object) can perform this action. Can be
+ * overridden by sub-classes with more complicated permissions schemes. Failures here
+ * must throw subclasses of ErrorPageError
+ *
+ * @param $user User: the user to check, or null to use the context user
+ * @throws ErrorPageError
+ */
+ protected function checkCanExecute( User $user ) {
+ if ( $this->requiresWrite() && wfReadOnly() ) {
+ throw new ReadOnlyError();
+ }
+
+ if ( $this->getRestriction() !== null && !$user->isAllowed( $this->getRestriction() ) ) {
+ throw new PermissionsError( $this->getRestriction() );
+ }
+
+ if ( $this->requiresUnblock() && $user->isBlocked() ) {
+ $block = $user->mBlock;
+ throw new UserBlockedError( $block );
+ }
+ }
+
+ /**
+ * Whether this action requires the wiki not to be locked
+ * @return Bool
+ */
+ public function requiresWrite() {
+ return true;
+ }
+
+ /**
+ * Whether this action can still be executed by a blocked user
+ * @return Bool
+ */
+ public function requiresUnblock() {
+ return true;
+ }
+
+ /**
+ * Set output headers for noindexing etc. This function will not be called through
+ * the execute() entry point, so only put UI-related stuff in here.
+ */
+ protected function setHeaders() {
+ $out = $this->getOutput();
+ $out->setRobotPolicy( "noindex,nofollow" );
+ $out->setPageTitle( $this->getPageTitle() );
+ $this->getOutput()->setSubtitle( $this->getDescription() );
+ $out->setArticleRelated( true );
+ }
+
+ /**
+ * Returns the name that goes in the \<h1\> page title
+ *
+ * @return String
+ */
+ protected function getPageTitle() {
+ return $this->getTitle()->getPrefixedText();
+ }
+
+ /**
+ * Returns the description that goes below the \<h1\> tag
+ *
+ * @return String
+ */
+ protected function getDescription() {
+ return wfMsg( strtolower( $this->getName() ) );
+ }
+
+ /**
+ * The main action entry point. Do all output for display and send it to the context
+ * output. Do not use globals $wgOut, $wgRequest, etc, in implementations; use
+ * $this->getOutput(), etc.
+ * @throws ErrorPageError
+ */
+ public abstract function show();
+
+ /**
+ * Execute the action in a silent fashion: do not display anything or release any errors.
+ * @param $data Array values that would normally be in the POST request
+ * @param $captureErrors Bool whether to catch exceptions and just return false
+ * @return Bool whether execution was successful
+ */
+ public abstract function execute();
+}
+
+abstract class FormAction extends Action {
+
+ /**
+ * Get an HTMLForm descriptor array
+ * @return Array
+ */
+ protected abstract function getFormFields();
+
+ /**
+ * Add pre- or post-text to the form
+ * @return String HTML which will be sent to $form->addPreText()
+ */
+ protected function preText() { return ''; }
+ protected function postText() { return ''; }
+
+ /**
+ * Play with the HTMLForm if you need to more substantially
+ * @param $form HTMLForm
+ */
+ protected function alterForm( HTMLForm $form ) {}
+
+ /**
+ * Get the HTMLForm to control behaviour
+ * @return HTMLForm|null
+ */
+ protected function getForm() {
+ $this->fields = $this->getFormFields();
+
+ // Give hooks a chance to alter the form, adding extra fields or text etc
+ wfRunHooks( 'ActionModifyFormFields', array( $this->getName(), &$this->fields, $this->page ) );
+
+ $form = new HTMLForm( $this->fields, $this->getContext() );
+ $form->setSubmitCallback( array( $this, 'onSubmit' ) );
+
+ // Retain query parameters (uselang etc)
+ $form->addHiddenField( 'action', $this->getName() ); // Might not be the same as the query string
+ $params = array_diff_key(
+ $this->getRequest()->getQueryValues(),
+ array( 'action' => null, 'title' => null )
+ );
+ $form->addHiddenField( 'redirectparams', wfArrayToCGI( $params ) );
+
+ $form->addPreText( $this->preText() );
+ $form->addPostText( $this->postText() );
+ $this->alterForm( $form );
+
+ // Give hooks a chance to alter the form, adding extra fields or text etc
+ wfRunHooks( 'ActionBeforeFormDisplay', array( $this->getName(), &$form, $this->page ) );
+
+ return $form;
+ }
+
+ /**
+ * Process the form on POST submission. If you return false from getFormFields(),
+ * this will obviously never be reached. If you don't want to do anything with the
+ * form, just return false here
+ * @param $data Array
+ * @return Bool|Array true for success, false for didn't-try, array of errors on failure
+ */
+ public abstract function onSubmit( $data );
+
+ /**
+ * Do something exciting on successful processing of the form. This might be to show
+ * a confirmation message (watch, rollback, etc) or to redirect somewhere else (edit,
+ * protect, etc).
+ */
+ public abstract function onSuccess();
+
+ /**
+ * The basic pattern for actions is to display some sort of HTMLForm UI, maybe with
+ * some stuff underneath (history etc); to do some processing on submission of that
+ * form (delete, protect, etc) and to do something exciting on 'success', be that
+ * display something new or redirect to somewhere. Some actions have more exotic
+ * behaviour, but that's what subclassing is for :D
+ */
+ public function show() {
+ $this->setHeaders();
+
+ // This will throw exceptions if there's a problem
+ $this->checkCanExecute( $this->getUser() );
+
+ $form = $this->getForm();
+ if ( $form->show() ) {
+ $this->onSuccess();
+ }
+ }
+
+ /**
+ * @see Action::execute()
+ * @throws ErrorPageError
+ * @param array|null $data
+ * @param bool $captureErrors
+ * @return bool
+ */
+ public function execute( array $data = null, $captureErrors = true ) {
+ try {
+ // Set a new context so output doesn't leak.
+ $this->context = clone $this->page->getContext();
+
+ // This will throw exceptions if there's a problem
+ $this->checkCanExecute( $this->getUser() );
+
+ $fields = array();
+ foreach ( $this->fields as $key => $params ) {
+ if ( isset( $data[$key] ) ) {
+ $fields[$key] = $data[$key];
+ } elseif ( isset( $params['default'] ) ) {
+ $fields[$key] = $params['default'];
+ } else {
+ $fields[$key] = null;
+ }
+ }
+ $status = $this->onSubmit( $fields );
+ if ( $status === true ) {
+ // This might do permanent stuff
+ $this->onSuccess();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ catch ( ErrorPageError $e ) {
+ if ( $captureErrors ) {
+ return false;
+ } else {
+ throw $e;
+ }
+ }
+ }
+}
+
+/**
+ * Actions generally fall into two groups: the show-a-form-then-do-something-with-the-input
+ * format (protect, delete, move, etc), and the just-do-something format (watch, rollback,
+ * patrol, etc).
+ */
+abstract class FormlessAction extends Action {
+
+ /**
+ * Show something on GET request.
+ * @return String|null will be added to the HTMLForm if present, or just added to the
+ * output if not. Return null to not add anything
+ */
+ public abstract function onView();
+
+ /**
+ * We don't want an HTMLForm
+ */
+ protected function getFormFields() {
+ return false;
+ }
+
+ public function onSubmit( $data ) {
+ return false;
+ }
+
+ public function onSuccess() {
+ return false;
+ }
+
+ public function show() {
+ $this->setHeaders();
+
+ // This will throw exceptions if there's a problem
+ $this->checkCanExecute( $this->getUser() );
+
+ $this->getOutput()->addHTML( $this->onView() );
+ }
+
+ /**
+ * Execute the action silently, not giving any output. Since these actions don't have
+ * forms, they probably won't have any data, but some (eg rollback) may do
+ * @param $data Array values that would normally be in the GET request
+ * @param $captureErrors Bool whether to catch exceptions and just return false
+ * @return Bool whether execution was successful
+ */
+ public function execute( array $data = null, $captureErrors = true ) {
+ try {
+ // Set a new context so output doesn't leak.
+ $this->context = clone $this->page->getContext();
+ if ( is_array( $data ) ) {
+ $this->context->setRequest( new FauxRequest( $data, false ) );
+ }
+
+ // This will throw exceptions if there's a problem
+ $this->checkCanExecute( $this->getUser() );
+
+ $this->onView();
+ return true;
+ }
+ catch ( ErrorPageError $e ) {
+ if ( $captureErrors ) {
+ return false;
+ } else {
+ throw $e;
+ }
+ }
+ }
+}
diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php
index f7583188..17b154d6 100644
--- a/includes/AjaxDispatcher.php
+++ b/includes/AjaxDispatcher.php
@@ -7,12 +7,6 @@
* Handle ajax requests and send them to the proper handler.
*/
-if ( !( defined( 'MEDIAWIKI' ) && $wgUseAjax ) ) {
- die( 1 );
-}
-
-require_once( 'AjaxFunctions.php' );
-
/**
* Object-Oriented Ajax functions.
* @ingroup Ajax
@@ -74,7 +68,7 @@ class AjaxDispatcher {
* request.
*/
function performAction() {
- global $wgAjaxExportList, $wgOut, $wgUser;
+ global $wgAjaxExportList, $wgOut;
if ( empty( $this->mode ) ) {
return;
@@ -90,13 +84,6 @@ class AjaxDispatcher {
'Bad Request',
"unknown function " . (string) $this->func_name
);
- } elseif ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true )
- && !$wgUser->isAllowed( 'read' ) )
- {
- wfHttpError(
- 403,
- 'Forbidden',
- 'You must log in to view pages.' );
} else {
wfDebug( __METHOD__ . ' dispatching ' . $this->func_name . "\n" );
diff --git a/includes/AjaxFunctions.php b/includes/AjaxFunctions.php
deleted file mode 100644
index 8e5de31b..00000000
--- a/includes/AjaxFunctions.php
+++ /dev/null
@@ -1,101 +0,0 @@
-<?php
-/**
- * Handler functions for Ajax requests
- *
- * @file
- * @ingroup Ajax
- */
-
-if ( !defined( 'MEDIAWIKI' ) ) {
- die( 1 );
-}
-
-/**
- * Function converts an Javascript escaped string back into a string with
- * specified charset (default is UTF-8).
- * Modified function from http://pure-essence.net/stuff/code/utf8RawUrlDecode.phps
- *
- * @param $source String escaped with Javascript's escape() function
- * @param $iconv_to String destination character set will be used as second parameter
- * in the iconv function. Default is UTF-8.
- * @return string
- */
-function js_unescape( $source, $iconv_to = 'UTF-8' ) {
- $decodedStr = '';
- $pos = 0;
- $len = strlen ( $source );
-
- while ( $pos < $len ) {
- $charAt = substr ( $source, $pos, 1 );
- if ( $charAt == '%' ) {
- $pos++;
- $charAt = substr ( $source, $pos, 1 );
-
- if ( $charAt == 'u' ) {
- // we got a unicode character
- $pos++;
- $unicodeHexVal = substr ( $source, $pos, 4 );
- $unicode = hexdec ( $unicodeHexVal );
- $decodedStr .= code2utf( $unicode );
- $pos += 4;
- } else {
- // we have an escaped ascii character
- $hexVal = substr ( $source, $pos, 2 );
- $decodedStr .= chr ( hexdec ( $hexVal ) );
- $pos += 2;
- }
- } else {
- $decodedStr .= $charAt;
- $pos++;
- }
- }
-
- if ( $iconv_to != "UTF-8" ) {
- $decodedStr = iconv( "utf-8", $iconv_to, $decodedStr );
- }
-
- return $decodedStr;
-}
-
-/**
- * Function coverts number of utf char into that character.
- * Function taken from: http://www.php.net/manual/en/function.utf8-encode.php#49336
- *
- * @param $num Integer
- * @return utf8char
- */
-function code2utf( $num ) {
- 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 in some places (currently just extensions)
- * to get the URL for a given file.
- */
-function wfAjaxGetFileUrl( $file ) {
- $file = wfFindFile( $file );
-
- if ( !$file || !$file->exists() ) {
- return null;
- }
-
- $url = $file->getUrl();
-
- return $url;
-}
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php
index 014798f8..b9f80855 100644
--- a/includes/AjaxResponse.php
+++ b/includes/AjaxResponse.php
@@ -6,10 +6,6 @@
* @ingroup Ajax
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- die( 1 );
-}
-
/**
* Handle responses for Ajax requests (send headers, print
* content, that sort of thing)
diff --git a/includes/Article.php b/includes/Article.php
index 3e8cfd5e..a0cc6a95 100644
--- a/includes/Article.php
+++ b/includes/Article.php
@@ -5,7 +5,11 @@
*/
/**
- * Class representing a MediaWiki article and history.
+ * Class for viewing MediaWiki article and history.
+ *
+ * This maintains WikiPage functions for backwards compatibility.
+ *
+ * @TODO: move and rewrite code to an Action class
*
* See design.txt for an overview.
* Note: edit user interface and cache support functions have been
@@ -13,198 +17,125 @@
*
* @internal documentation reviewed 15 Mar 2010
*/
-class Article {
+class Article extends Page {
/**@{{
* @private
*/
- var $mComment = ''; // !<
+
+ /**
+ * @var IContextSource
+ */
+ protected $mContext;
+
+ /**
+ * @var WikiPage
+ */
+ protected $mPage;
+
var $mContent; // !<
var $mContentLoaded = false; // !<
- var $mCounter = -1; // !< Not loaded
- var $mCurID = -1; // !< Not loaded
- var $mDataLoaded = false; // !<
- var $mForUpdate = false; // !<
- var $mGoodAdjustment = 0; // !<
- var $mIsRedirect = false; // !<
- var $mLatest = false; // !<
- var $mMinorEdit; // !<
var $mOldId; // !<
- var $mPreparedEdit = false; // !< Title object if set
- var $mRedirectedFrom = null; // !< Title object if set
- var $mRedirectTarget = null; // !< Title object if set
+
+ /**
+ * @var Title
+ */
+ var $mRedirectedFrom = null;
+
+ /**
+ * @var mixed: boolean false or URL string
+ */
var $mRedirectUrl = false; // !<
var $mRevIdFetched = 0; // !<
- var $mRevision; // !< Revision object if set
- var $mTimestamp = ''; // !<
- var $mTitle; // !< Title object
- var $mTotalAdjustment = 0; // !<
- var $mTouched = '19700101000000'; // !<
- var $mUser = -1; // !< Not loaded
- var $mUserText = ''; // !< username from Revision if set
- var $mParserOptions; // !< ParserOptions object
- var $mParserOutput; // !< ParserCache object if set
+
+ /**
+ * @var Revision
+ */
+ var $mRevision = null;
+
+ /**
+ * @var ParserOutput
+ */
+ var $mParserOutput;
+
/**@}}*/
/**
* Constructor and clear the article
- * @param $title Reference to a Title object.
+ * @param $title Title Reference to a Title object.
* @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;
+ $this->mPage = $this->newPage( $title );
+ }
+
+ protected function newPage( Title $title ) {
+ return new WikiPage( $title );
}
/**
- * Constructor from an page id
- * @param $id The article ID to load
+ * Constructor from a page id
+ * @param $id Int article ID to load
*/
public static function newFromID( $id ) {
$t = Title::newFromID( $id );
- # FIXME: doesn't inherit right
+ # @todo FIXME: Doesn't inherit right
return $t == null ? null : new self( $t );
# return $t == null ? null : new static( $t ); // PHP 5.3
}
/**
- * Tell the page view functions that this view was redirected
- * from another page on the wiki.
- * @param $from Title object.
- */
- public function setRedirectedFrom( Title $from ) {
- $this->mRedirectedFrom = $from;
- }
-
- /**
- * If this page is a redirect, get its target
+ * Create an Article object of the appropriate class for the given page.
*
- * The target will be fetched from the redirect table if possible.
- * If this page doesn't have an entry there, call insertRedirect()
- * @return mixed Title object, or null if this page is not a redirect
+ * @param $title Title
+ * @param $context IContextSource
+ * @return Article object
*/
- public function getRedirectTarget() {
- if ( !$this->mTitle->isRedirect() ) {
- return null;
+ public static function newFromTitle( $title, IContextSource $context ) {
+ if ( NS_MEDIA == $title->getNamespace() ) {
+ // FIXME: where should this go?
+ $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
}
- if ( $this->mRedirectTarget !== null ) {
- return $this->mRedirectTarget;
- }
-
- # Query the redirect table
- $dbr = wfGetDB( DB_SLAVE );
- $row = $dbr->selectRow( 'redirect',
- array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
- array( 'rd_from' => $this->getID() ),
- __METHOD__
- );
-
- // 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 );
+ $page = null;
+ wfRunHooks( 'ArticleFromTitle', array( &$title, &$page ) );
+ if ( !$page ) {
+ switch( $title->getNamespace() ) {
+ case NS_FILE:
+ $page = new ImagePage( $title );
+ break;
+ case NS_CATEGORY:
+ $page = new CategoryPage( $title );
+ break;
+ default:
+ $page = new Article( $title );
+ }
}
+ $page->setContext( $context );
- # This page doesn't have an entry in the redirect table
- return $this->mRedirectTarget = $this->insertRedirect();
+ return $page;
}
/**
- * Insert an entry for this page into the redirect table.
+ * Create an Article object of the appropriate class for the given page.
*
- * Don't call this function directly unless you know what you're doing.
- * @return Title object or null if not a redirect
+ * @param $page WikiPage
+ * @param $context IContextSource
+ * @return Article object
*/
- public function insertRedirect() {
- // 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' => $rt->getNamespace(),
- 'rd_title' => $rt->getDBkey(),
- 'rd_fragment' => $rt->getFragment(),
- 'rd_interwiki' => $rt->getInterwiki(),
- ),
- __METHOD__
- );
+ public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
+ $article = self::newFromTitle( $page->getTitle(), $context );
+ $article->mPage = $page; // override to keep process cached vars
+ return $article;
}
/**
- * 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() {
- 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 ) {
- // 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
+ * Tell the page view functions that this view was redirected
+ * from another page on the wiki.
+ * @param $from Title object.
*/
- public function getRedirectURL( $rt ) {
- if ( $rt ) {
- if ( $rt->getInterwiki() != '' ) {
- if ( $rt->isLocal() ) {
- // Offsite wikis need an HTTP redirect.
- //
- // This can be hard to reverse and may produce loops,
- // so they may be disabled in the site configuration.
- $source = $this->mTitle->getFullURL( 'redirect=no' );
- return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
- }
- } else {
- if ( $rt->getNamespace() == NS_SPECIAL ) {
- // Gotta handle redirects to special pages differently:
- // Fill the HTTP response "Location" header and ignore
- // the rest of the page we're on.
- //
- // This can be hard to reverse, so they may be disabled.
- if ( $rt->isSpecial( 'Userlogout' ) ) {
- // rolleyes
- } else {
- return $rt->getFullURL();
- }
- }
-
- return $rt;
- }
- }
-
- // No or invalid redirect
- return false;
+ public function setRedirectedFrom( Title $from ) {
+ $this->mRedirectedFrom = $from;
}
/**
@@ -212,31 +143,22 @@ class Article {
* @return Title object of this page
*/
public function getTitle() {
- return $this->mTitle;
+ return $this->mPage->getTitle();
}
/**
* Clear the object
- * FIXME: shouldn't this be public?
+ * @todo FIXME: Shouldn't this be public?
* @private
*/
public function clear() {
- $this->mDataLoaded = false;
$this->mContentLoaded = false;
- $this->mCurID = $this->mUser = $this->mCounter = -1; # Not loaded
$this->mRedirectedFrom = null; # Title object if set
- $this->mRedirectTarget = null; # Title object if set
- $this->mUserText =
- $this->mTimestamp = $this->mComment = '';
- $this->mGoodAdjustment = $this->mTotalAdjustment = 0;
- $this->mTouched = '19700101000000';
- $this->mForUpdate = false;
- $this->mIsRedirect = false;
$this->mRevIdFetched = 0;
$this->mRedirectUrl = false;
- $this->mLatest = false;
- $this->mPreparedEdit = false;
+
+ $this->mPage->clear();
}
/**
@@ -250,20 +172,18 @@ class Article {
* @return Return the text of this revision
*/
public function getContent() {
- global $wgUser, $wgContLang, $wgMessageCache;
+ global $wgUser;
wfProfileIn( __METHOD__ );
- if ( $this->getID() === 0 ) {
+ if ( $this->mPage->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() ) );
- $text = wfMsgGetKey( $message, false, $lang, false );
-
- if ( wfEmptyMsg( $message, $text ) )
+ if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
+ $text = $this->getTitle()->getDefaultMessageText();
+ if ( $text === false ) {
$text = '';
+ }
} else {
$text = wfMsgExt( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', 'parsemag' );
}
@@ -279,71 +199,6 @@ class Article {
}
/**
- * Get the text of the current revision. No side-effects...
- *
- * @return Return the text of the current revision
- */
- public function getRawText() {
- // Check process cache for current revision
- if ( $this->mContentLoaded && $this->mOldId == 0 ) {
- return $this->mContent;
- }
-
- $rev = Revision::newFromTitle( $this->mTitle );
- $text = $rev ? $rev->getRawText() : false;
-
- return $text;
- }
-
- /**
- * This function returns the text of a section, specified by a number ($section).
- * A section is text under a heading like == Heading == or \<h1\>Heading\</h1\>, or
- * the first section before any such heading (section 0).
- *
- * If a section contains subsections, these are also returned.
- *
- * @param $text String: text to look in
- * @param $section Integer: section number
- * @return string text of the requested section
- * @deprecated
- */
- public function getSection( $text, $section ) {
- global $wgParser;
- return $wgParser->getSection( $text, $section );
- }
-
- /**
- * Get the text that needs to be saved in order to undo all revisions
- * between $undo and $undoafter. Revisions must belong to the same page,
- * must exist and must not be deleted
- * @param $undo Revision
- * @param $undoafter Revision Must be an earlier revision than $undo
- * @return mixed string on success, false on failure
- */
- 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 = $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 ) ) {
- return false;
- }
-
- return $undone_text;
- }
-
- /**
* @return int The oldid of the article that is to be shown, 0 for the
* current revision
*/
@@ -370,14 +225,14 @@ class Article {
if ( isset( $oldid ) ) {
$oldid = intval( $oldid );
if ( $wgRequest->getVal( 'direction' ) == 'next' ) {
- $nextid = $this->mTitle->getNextRevisionID( $oldid );
+ $nextid = $this->getTitle()->getNextRevisionID( $oldid );
if ( $nextid ) {
$oldid = $nextid;
} else {
- $this->mRedirectUrl = $this->mTitle->getFullURL( 'redirect=no' );
+ $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
}
} elseif ( $wgRequest->getVal( 'direction' ) == 'prev' ) {
- $previd = $this->mTitle->getPreviousRevisionID( $oldid );
+ $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
if ( $previd ) {
$oldid = $previd;
}
@@ -401,102 +256,12 @@ class Article {
wfProfileIn( __METHOD__ );
- $oldid = $this->getOldID();
- $this->mOldId = $oldid;
- $this->fetchContent( $oldid );
+ $this->fetchContent( $this->getOldID() );
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(
- 'page_id',
- 'page_namespace',
- 'page_title',
- 'page_restrictions',
- 'page_counter',
- 'page_is_redirect',
- 'page_is_new',
- 'page_random',
- 'page_touched',
- 'page_latest',
- 'page_len',
- );
-
- wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
-
- $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__ );
-
- wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$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(
- 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey() ) );
- }
-
- /**
- * Fetch a page record matching the requested ID
- *
- * @param $dbr Database
- * @param $id Integer
- */
- protected function pageDataFromId( $dbr, $id ) {
- return $this->pageData( $dbr, array( 'page_id' => $id ) );
- }
-
- /**
- * Set the general counter, title etc data loaded from
- * some source.
- *
- * @param $data Database row object or "fromdb"
- */
- public function loadPageData( $data = 'fromdb' ) {
- if ( $data === 'fromdb' ) {
- $dbr = wfGetDB( DB_MASTER );
- $data = $this->pageDataFromId( $dbr, $this->getId() );
- }
-
- $lc = LinkCache::singleton();
-
- if ( $data ) {
- $lc->addGoodLinkObj( $data->page_id, $this->mTitle, $data->page_len, $data->page_is_redirect, $data->page_latest );
-
- $this->mTitle->mArticleID = intval( $data->page_id );
-
- # Old-fashioned restrictions
- $this->mTitle->loadRestrictions( $data->page_restrictions );
-
- $this->mCounter = intval( $data->page_counter );
- $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
- $this->mIsRedirect = intval( $data->page_is_redirect );
- $this->mLatest = intval( $data->page_latest );
- } else {
- $lc->addBadLinkObj( $this->mTitle );
- $this->mTitle->mArticleID = 0;
- }
-
- $this->mDataLoaded = true;
- }
-
- /**
* Get text of an article from database
* Does *NOT* follow redirects.
*
@@ -508,57 +273,44 @@ class Article {
return $this->mContent;
}
- $dbr = wfGetDB( DB_MASTER );
-
# Pre-fill content with error message so that if something
# fails we'll have something telling us what we intended.
- $t = $this->mTitle->getPrefixedText();
+ $t = $this->getTitle()->getPrefixedText();
$d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : '';
$this->mContent = wfMsgNoTrans( 'missing-article', $t, $d ) ;
if ( $oldid ) {
$revision = Revision::newFromId( $oldid );
- if ( $revision === null ) {
+ if ( !$revision ) {
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" );
+ // Revision title doesn't match the page title given?
+ if ( $this->mPage->getID() != $revision->getPage() ) {
+ $function = array( get_class( $this->mPage ), 'newFromID' );
+ $this->mPage = call_user_func( $function, $revision->getPage() );
+ if ( !$this->mPage->getId() ) {
+ wfDebug( __METHOD__ . " failed to get page data linked to revision id $oldid\n" );
return false;
}
-
- $this->loadPageData( $data );
}
- $revision = Revision::newFromId( $this->mLatest );
- if ( $revision === null ) {
- wfDebug( __METHOD__ . " failed to retrieve current page, rev_id {$this->mLatest}\n" );
+ } else {
+ if ( !$this->mPage->getLatest() ) {
+ wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" );
+ return false;
+ }
+
+ $revision = $this->mPage->getRevision();
+ if ( !$revision ) {
+ wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
return false;
}
}
- // FIXME: Horrible, horrible! This content-loading interface just plain sucks.
+ // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
// We should instead work with the Revision object when we need it...
$this->mContent = $revision->getText( Revision::FOR_THIS_USER ); // Loads if user is allowed
- $this->mUser = $revision->getUser();
- $this->mUserText = $revision->getUserText();
- $this->mComment = $revision->getComment();
- $this->mTimestamp = wfTimestamp( TS_MW, $revision->getTimestamp() );
-
$this->mRevIdFetched = $revision->getId();
$this->mContentLoaded = true;
$this->mRevision =& $revision;
@@ -569,118 +321,11 @@ 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 options for all SELECT statements
- *
- * @param $options Array: an optional options array which'll be appended to
- * the default
- * @return Array: options
- */
- protected function getSelectOptions( $options = '' ) {
- if ( $this->mForUpdate ) {
- if ( is_array( $options ) ) {
- $options[] = 'FOR UPDATE';
- } else {
- $options = 'FOR UPDATE';
- }
- }
-
- return $options;
- }
-
- /**
- * @return int Page ID
- */
- public function getID() {
- return $this->mTitle->getArticleID();
- }
-
- /**
- * @return bool Whether or not the page exists in the database
+ * No-op
+ * @deprecated since 1.18
*/
- public function exists() {
- return $this->getId() > 0;
- }
-
- /**
- * Check if this page is something we're going to be showing
- * some sort of sensible content for. If we return false, page
- * views (plain action=view) will return an HTTP 404 response,
- * so spiders and robots can know they're following a bad link.
- *
- * @return bool
- */
- public function hasViewableContent() {
- return $this->exists() || $this->mTitle->isAlwaysKnown();
- }
-
- /**
- * @return int The view count for the page
- */
- public function getCount() {
- if ( -1 == $this->mCounter ) {
- $id = $this->getID();
-
- if ( $id == 0 ) {
- $this->mCounter = 0;
- } else {
- $dbr = wfGetDB( DB_SLAVE );
- $this->mCounter = $dbr->selectField( 'page',
- 'page_counter',
- array( 'page_id' => $id ),
- __METHOD__,
- $this->getSelectOptions()
- );
- }
- }
-
- return $this->mCounter;
- }
-
- /**
- * 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
- * @return bool
- */
- public function isCountable( $text ) {
- 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 mixed string containing article contents, or boolean
- * @return bool
- */
- public function isRedirect( $text = false ) {
- if ( $text === false ) {
- 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;
+ public function forUpdate() {
+ wfDeprecated( __METHOD__ );
}
/**
@@ -694,80 +339,7 @@ class Article {
return true;
}
- return $this->exists() && isset( $this->mRevision ) && $this->mRevision->isCurrent();
- }
-
- /**
- * Loads everything except the text
- * This isn't necessary for all uses, so it's only done if needed.
- */
- protected function loadLastEdit() {
- if ( -1 != $this->mUser ) {
- return;
- }
-
- # New or non-existent articles have no user information
- $id = $this->getID();
- if ( 0 == $id ) {
- return;
- }
-
- $this->mLastRevision = Revision::loadFromPageId( wfGetDB( DB_MASTER ), $id );
- if ( !is_null( $this->mLastRevision ) ) {
- $this->mUser = $this->mLastRevision->getUser();
- $this->mUserText = $this->mLastRevision->getUserText();
- $this->mTimestamp = $this->mLastRevision->getTimestamp();
- $this->mComment = $this->mLastRevision->getComment();
- $this->mMinorEdit = $this->mLastRevision->isMinor();
- $this->mRevIdFetched = $this->mLastRevision->getId();
- }
- }
-
- /**
- * @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;
+ return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
}
/**
@@ -776,52 +348,11 @@ class Article {
* @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 ) {
- # FIXME: this is expensive; cache this info somewhere.
-
- $dbr = wfGetDB( DB_SLAVE );
- $revTable = $dbr->tableName( 'revision' );
- $userTable = $dbr->tableName( 'user' );
-
- $pageId = $this->getId();
-
- $user = $this->getUser();
-
- if ( $user ) {
- $excludeCond = "AND rev_user != $user";
+ if ( $this->mRevIdFetched ) {
+ return $this->mRevIdFetched;
} else {
- $userText = $dbr->addQuotes( $this->getUserText() );
- $excludeCond = "AND rev_user_text != $userText";
+ return $this->mPage->getLatest();
}
-
- $deletedBit = $dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER ); // username hidden?
-
- $sql = "SELECT {$userTable}.*, rev_user_text as user_name, MAX(rev_timestamp) as timestamp
- FROM $revTable LEFT JOIN $userTable ON rev_user = user_id
- WHERE rev_page = $pageId
- $excludeCond
- AND $deletedBit = 0
- GROUP BY rev_user, rev_user_text
- ORDER BY timestamp DESC";
-
- if ( $limit > 0 ) {
- $sql = $dbr->limitResult( $sql, $limit, $offset );
- }
-
- $sql .= ' ' . $this->getSelectOptions();
- $res = $dbr->query( $sql, __METHOD__ );
-
- return new UserArrayFromResult( $res );
}
/**
@@ -836,67 +367,68 @@ class Article {
# Get variables from query string
$oldid = $this->getOldID();
+
+ # 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;
+ }
+
+ $wgOut->setArticleFlag( true );
+ # Set page title (may be overridden by DISPLAYTITLE)
+ $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
+
+ # If we got diff in the query, we want to see a diff page instead of the article.
+ if ( $wgRequest->getCheck( 'diff' ) ) {
+ wfDebug( __METHOD__ . ": showing diff page\n" );
+ $this->showDiffPage();
+ wfProfileOut( __METHOD__ );
+
+ return;
+ }
+
+ # Allow frames by default
+ $wgOut->allowClickjacking();
+
$parserCache = ParserCache::singleton();
- $parserOptions = $this->getParserOptions();
+ $parserOptions = $this->mPage->getParserOptions();
# Render printable version, use printable version cache
if ( $wgOut->isPrintable() ) {
$parserOptions->setIsPrintable( true );
$parserOptions->setEditSection( false );
- } else if ( $wgUseETag && !$this->mTitle->quickUserCan( 'edit' ) ) {
+ } elseif ( $wgUseETag && !$this->getTitle()->quickUserCan( 'edit' ) ) {
$parserOptions->setEditSection( false );
}
# Try client and file cache
- if ( $oldid === 0 && $this->checkTouched() ) {
+ if ( $oldid === 0 && $this->mPage->checkTouched() ) {
if ( $wgUseETag ) {
$wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) );
}
# Is it client cached?
- if ( $wgOut->checkLastModified( $this->getTouched() ) ) {
+ if ( $wgOut->checkLastModified( $this->mPage->getTouched() ) ) {
wfDebug( __METHOD__ . ": done 304\n" );
wfProfileOut( __METHOD__ );
return;
# Try file cache
- } else if ( $wgUseFileCache && $this->tryFileCache() ) {
+ } elseif ( $wgUseFileCache && $this->tryFileCache() ) {
wfDebug( __METHOD__ . ": done file cache\n" );
# tell wgOut that output is taken care of
$wgOut->disable();
- $this->viewUpdates();
+ $this->mPage->viewUpdates();
wfProfileOut( __METHOD__ );
return;
}
}
- # 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;
- }
-
- $wgOut->setArticleFlag( true );
- # Set page title (may be overridden by DISPLAYTITLE)
- $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
-
- # If we got diff in the query, we want to see a diff page instead of the article.
- 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' ) ) {
+ if ( !$wgUseETag && !$this->getTitle()->quickUserCan( 'edit' ) ) {
$parserOptions->setEditSection( false );
}
@@ -931,14 +463,18 @@ class Article {
$wgOut->addParserOutput( $this->mParserOutput );
# Ensure that UI elements requiring revision ID have
# the correct version information.
- $wgOut->setRevisionId( $this->mLatest );
+ $wgOut->setRevisionId( $this->mPage->getLatest() );
$outputDone = true;
+ # Preload timestamp to avoid a DB hit
+ if ( isset( $this->mParserOutput->mTimestamp ) ) {
+ $this->mPage->setTimestamp( $this->mParserOutput->mTimestamp );
+ }
}
}
break;
case 3:
$text = $this->getContent();
- if ( $text === false || $this->getID() == 0 ) {
+ if ( $text === false || $this->mPage->getID() == 0 ) {
wfDebug( __METHOD__ . ": showing missing article\n" );
$this->showMissingArticle();
wfProfileOut( __METHOD__ );
@@ -946,7 +482,7 @@ class Article {
}
# Another whitelist check in case oldid is altering the title
- if ( !$this->mTitle->userCanRead() ) {
+ if ( !$this->getTitle()->userCanRead() ) {
wfDebug( __METHOD__ . ": denied on secondary read check\n" );
$wgOut->loginToUse();
$wgOut->output();
@@ -966,14 +502,14 @@ class Article {
}
# If this "old" version is the current, then try the parser cache...
- if ( $oldid === $this->getLatest() && $this->useParserCache( false ) ) {
+ if ( $oldid === $this->mPage->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 );
+ $wgOut->setRevisionId( $this->mPage->getLatest() );
$outputDone = true;
- break;
+ break;
}
}
}
@@ -983,7 +519,7 @@ class Article {
$wgOut->setRevisionId( $this->getRevIdFetched() );
# Pages containing custom CSS or JavaScript get special treatment
- if ( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) {
+ if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
$this->showCssOrJsPage();
$outputDone = true;
@@ -995,7 +531,7 @@ class Article {
# 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 );
+ $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
$wgOut->addParserOutputNoText( $this->mParserOutput );
$outputDone = true;
}
@@ -1007,7 +543,7 @@ class Article {
$key = $parserCache->getKey( $this, $parserOptions );
$poolArticleView = new PoolWorkArticleView( $this, $key, $useParserCache, $parserOptions );
-
+
if ( !$poolArticleView->execute() ) {
# Connection or timeout error
wfProfileOut( __METHOD__ );
@@ -1035,10 +571,11 @@ class Article {
# 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' ) ) !== '' )
- {
- $wgOut->setHTMLTitle( $m );
+ if ( $this->getTitle()->equals( Title::newMainPage() ) ) {
+ $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
+ if ( !$msg->isDisabled() ) {
+ $wgOut->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
+ }
}
# Now that we've filled $this->mParserOutput, we know whether
@@ -1048,7 +585,7 @@ class Article {
$wgOut->setFollowPolicy( $policy['follow'] );
$this->showViewFooter();
- $this->viewUpdates();
+ $this->mPage->viewUpdates();
wfProfileOut( __METHOD__ );
}
@@ -1066,16 +603,14 @@ class Article {
$unhide = $wgRequest->getInt( 'unhide' ) == 1;
$oldid = $this->getOldID();
- $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $unhide );
+ $de = new DifferenceEngine( $this->getTitle(), $oldid, $diff, $rcid, $purge, $unhide );
// DifferenceEngine directly fetched the revision:
$this->mRevIdFetched = $de->mNewid;
$de->showDiffPage( $diffOnly );
- // Needed to get the page's current revision
- $this->loadPageData();
- if ( $diff == 0 || $diff == $this->mLatest ) {
+ if ( $diff == 0 || $diff == $this->mPage->getLatest() ) {
# Run view updates for current revision only
- $this->viewUpdates();
+ $this->mPage->viewUpdates();
}
}
@@ -1087,15 +622,19 @@ class Article {
* page views.
*/
protected function showCssOrJsPage() {
- global $wgOut;
+ global $wgOut, $wgLang;
+
+ $dir = $wgLang->getDir();
+ $lang = $wgLang->getCode();
- $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache'>\n$1\n</div>", 'clearyourcache' );
+ $wgOut->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
+ 'clearyourcache' );
// Give hooks a chance to customise the output
- if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) {
+ if ( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) {
// Wrap the whole lot in a <pre> and don't parse
$m = array();
- preg_match( '!\.(css|js)$!u', $this->mTitle->getText(), $m );
+ preg_match( '!\.(css|js)$!u', $this->getTitle()->getText(), $m );
$wgOut->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
$wgOut->addHTML( htmlspecialchars( $this->mContent ) );
$wgOut->addHTML( "\n</pre>\n" );
@@ -1112,13 +651,12 @@ class Article {
global $wgOut, $wgArticleRobotPolicies, $wgNamespaceRobotPolicies;
global $wgDefaultRobotPolicy, $wgRequest;
- $ns = $this->mTitle->getNamespace();
+ $ns = $this->getTitle()->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() ) ) {
+ if ( !$this->getTitle()->isSubpage() ) {
+ if ( Block::newFromTarget( null, $this->getTitle()->getText() ) instanceof Block ) {
return array(
'index' => 'noindex',
'follow' => 'nofollow'
@@ -1127,7 +665,7 @@ class Article {
}
}
- if ( $this->getID() === 0 || $this->getOldID() ) {
+ if ( $this->mPage->getID() === 0 || $this->getOldID() ) {
# Non-articles (special pages etc), and old revisions
return array(
'index' => 'noindex',
@@ -1157,7 +695,7 @@ class Article {
self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
);
}
- if ( $this->mTitle->canUseNoindex() && is_object( $this->mParserOutput ) && $this->mParserOutput->getIndexPolicy() ) {
+ if ( $this->getTitle()->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(
@@ -1166,11 +704,11 @@ class Article {
);
}
- if ( isset( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] ) ) {
+ if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
# (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
$policy = array_merge(
$policy,
- self::formatRobotPolicy( $wgArticleRobotPolicies[$this->mTitle->getPrefixedText()] )
+ self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
);
}
@@ -1182,7 +720,7 @@ class Article {
* merging of several policies using array_merge().
* @param $policy Mixed, returns empty array on null/false/'', transparent
* to already-converted arrays, converts String.
- * @return associative Array: 'index' => <indexpolicy>, 'follow' => <followpolicy>
+ * @return Array: 'index' => <indexpolicy>, 'follow' => <followpolicy>
*/
public static function formatRobotPolicy( $policy ) {
if ( is_array( $policy ) ) {
@@ -1214,16 +752,15 @@ class Article {
* @return boolean
*/
public function showRedirectedFromHeader() {
- global $wgOut, $wgUser, $wgRequest, $wgRedirectSources;
+ global $wgOut, $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.
if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) {
- $redir = $sk->link(
+ $redir = Linker::link(
$this->mRedirectedFrom,
null,
array(),
@@ -1235,14 +772,14 @@ class Article {
$wgOut->setSubtitle( $s );
// Set the fragment if one was specified in the redirect
- if ( strval( $this->mTitle->getFragment() ) != '' ) {
- $fragment = Xml::escapeJsString( $this->mTitle->getFragmentForURL() );
+ if ( strval( $this->getTitle()->getFragment() ) != '' ) {
+ $fragment = Xml::escapeJsString( $this->getTitle()->getFragmentForURL() );
$wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" );
}
// Add a <link rel="canonical"> tag
$wgOut->addLink( array( 'rel' => 'canonical',
- 'href' => $this->mTitle->getLocalURL() )
+ 'href' => $this->getTitle()->getLocalURL() )
);
return true;
@@ -1251,7 +788,7 @@ class Article {
// This is an externally redirected view, from some other wiki.
// If it was reported from a trusted site, supply a backlink.
if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
- $redir = $sk->makeExternalLink( $rdfrom, $rdfrom );
+ $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
$s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir );
$wgOut->setSubtitle( $s );
@@ -1269,9 +806,8 @@ class Article {
public function showNamespaceHeader() {
global $wgOut;
- if ( $this->mTitle->isTalkPage() ) {
- $msg = wfMsgNoTrans( 'talkpageheader' );
- if ( $msg !== '-' && !wfEmptyMsg( 'talkpageheader', $msg ) ) {
+ if ( $this->getTitle()->isTalkPage() ) {
+ if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
$wgOut->wrapWikiMsg( "<div class=\"mw-talkpageheader\">\n$1\n</div>", array( 'talkpageheader' ) );
}
}
@@ -1284,7 +820,7 @@ class Article {
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() ) ) {
+ if ( $this->getTitle()->getNamespace() == NS_USER_TALK && IP::isValid( $this->getTitle()->getText() ) ) {
$wgOut->addWikiMsg( 'anontalkpagetext' );
}
@@ -1296,6 +832,9 @@ class Article {
if ( $wgUseTrackbacks ) {
$this->addTrackbacks();
}
+
+ wfRunHooks( 'ArticleViewFooter', array( $this ) );
+
}
/**
@@ -1308,11 +847,10 @@ class Article {
$rcid = $wgRequest->getVal( 'rcid' );
- if ( !$rcid || !$this->mTitle->quickUserCan( 'patrol' ) ) {
+ if ( !$rcid || !$this->getTitle()->quickUserCan( 'patrol' ) ) {
return;
}
- $sk = $wgUser->getSkin();
$token = $wgUser->editToken( $rcid );
$wgOut->preventClickjacking();
@@ -1320,8 +858,8 @@ class Article {
"<div class='patrollink'>" .
wfMsgHtml(
'markaspatrolledlink',
- $sk->link(
- $this->mTitle,
+ Linker::link(
+ $this->getTitle(),
wfMsgHtml( 'markaspatrolledtext' ),
array(),
array(
@@ -1344,16 +882,16 @@ class Article {
global $wgOut, $wgRequest, $wgUser;
# Show info in user (talk) namespace. Does the user exist? Is he blocked?
- if ( $this->mTitle->getNamespace() == NS_USER || $this->mTitle->getNamespace() == NS_USER_TALK ) {
- $parts = explode( '/', $this->mTitle->getText() );
+ if ( $this->getTitle()->getNamespace() == NS_USER || $this->getTitle()->getNamespace() == NS_USER_TALK ) {
+ $parts = explode( '/', $this->getTitle()->getText() );
$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\n</div>",
- array( 'userpage-userdoesnotexist-view', $rootPart ) );
- } else if ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
+ array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) );
+ } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
LogEventsList::showLogExtract(
$wgOut,
'block',
@@ -1374,7 +912,7 @@ class Article {
wfRunHooks( 'ShowMissingArticle', array( $this ) );
# Show delete and move logs
- LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle->getPrefixedText(), '',
+ LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->getTitle()->getPrefixedText(), '',
array( 'lim' => 10,
'conds' => array( "log_action != 'revision'" ),
'showIfEmpty' => false,
@@ -1385,14 +923,14 @@ class Article {
$oldid = $this->getOldID();
if ( $oldid ) {
$text = wfMsgNoTrans( 'missing-article',
- $this->mTitle->getPrefixedText(),
+ $this->getTitle()->getPrefixedText(),
wfMsgNoTrans( 'missingarticle-rev', $oldid ) );
- } elseif ( $this->mTitle->getNamespace() === NS_MEDIAWIKI ) {
+ } elseif ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) {
// Use the default message text
- $text = $this->getContent();
+ $text = $this->getTitle()->getDefaultMessageText();
} else {
- $createErrors = $this->mTitle->getUserPermissionsErrors( 'create', $wgUser );
- $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
+ $createErrors = $this->getTitle()->getUserPermissionsErrors( 'create', $wgUser );
+ $editErrors = $this->getTitle()->getUserPermissionsErrors( 'edit', $wgUser );
$errors = array_merge( $createErrors, $editErrors );
if ( !count( $errors ) ) {
@@ -1403,7 +941,7 @@ class Article {
}
$text = "<div class='noarticletext'>\n$text\n</div>";
- if ( !$this->hasViewableContent() ) {
+ if ( !$this->mPage->hasViewableContent() ) {
// If there's no backing content, send a 404 Not Found
// for better machine handling of broken links.
$wgRequest->response()->header( "HTTP/1.1 404 Not Found" );
@@ -1433,10 +971,10 @@ class Article {
return false;
// If the user needs to confirm that they want to see it...
- } else if ( $wgRequest->getInt( 'unhide' ) != 1 ) {
+ } elseif ( $wgRequest->getInt( 'unhide' ) != 1 ) {
# Give explanation and add a link to view the revision...
$oldid = intval( $this->getOldID() );
- $link = $this->mTitle->getFullUrl( "oldid={$oldid}&unhide=1" );
+ $link = $this->getTitle()->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\n</div>\n",
@@ -1454,41 +992,25 @@ class Article {
}
/**
- * Should the parser cache be used?
- *
- * @return boolean
- */
- public function useParserCache( $oldid ) {
- global $wgUser, $wgEnableParserCache;
-
- return $wgEnableParserCache
- && $wgUser->getStubThreshold() == 0
- && $this->exists()
- && empty( $oldid )
- && !$this->mTitle->isCssOrJsPage()
- && !$this->mTitle->isCssJsSubpage();
- }
-
- /**
* Execute the uncached parse for action=view
*/
public function doViewParse() {
global $wgOut;
$oldid = $this->getOldID();
- $parserOptions = $this->getParserOptions();
+ $parserOptions = $this->mPage->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.
- if ( !$this->isCurrent() || $wgOut->isPrintable() || !$this->mTitle->quickUserCan( 'edit' ) ) {
+ if ( !$this->isCurrent() || $wgOut->isPrintable() || !$this->getTitle()->quickUserCan( 'edit' ) ) {
$parserOptions->setEditSection( false );
}
-
+
$useParserCache = $this->useParserCache( $oldid );
$this->outputWikiText( $this->getContent(), $useParserCache, $parserOptions );
-
+
return true;
}
@@ -1503,13 +1025,13 @@ class Article {
public function tryDirtyCache() {
global $wgOut;
$parserCache = ParserCache::singleton();
- $options = $this->getParserOptions();
-
+ $options = $this->mPage->getParserOptions();
+
if ( $wgOut->isPrintable() ) {
$options->setIsPrintable( true );
$options->setEditSection( false );
}
-
+
$output = $parserCache->getDirty( $this, $options );
if ( $output ) {
@@ -1532,48 +1054,46 @@ class Article {
/**
* View redirect
*
- * @param $target Title object or Array of destination(s) to redirect
+ * @param $target Title|Array of destination(s) to redirect
* @param $appendSubtitle Boolean [optional]
* @param $forceKnown Boolean: should the image be shown as a bluelink regardless of existence?
* @return string containing HMTL with redirect link
*/
public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
- global $wgOut, $wgContLang, $wgStylePath, $wgUser;
+ global $wgOut, $wgLang, $wgStylePath;
if ( !is_array( $target ) ) {
$target = array( $target );
}
- $imageDir = $wgContLang->getDir();
+ $imageDir = $wgLang->getDir();
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->linkKnown( $title, htmlspecialchars( $title->getFullText() ) );
+ $link = Linker::linkKnown( $title, htmlspecialchars( $title->getFullText() ) );
} else {
- $link = $sk->link( $title, htmlspecialchars( $title->getFullText() ) );
+ $link = Linker::link( $title, htmlspecialchars( $title->getFullText() ) );
}
$nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
- $alt = $wgContLang->isRTL() ? '←' : '→';
+ $alt = $wgLang->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 .= $sk->linkKnown( $rt, htmlspecialchars( $rt->getFullText() ) );
+ $link .= Linker::linkKnown( $rt, htmlspecialchars( $rt->getFullText(), array(), array( 'redirect' => 'no' ) ) );
} else {
- $link .= $sk->link( $rt, htmlspecialchars( $rt->getFullText() ) );
+ $link .= Linker::link( $rt, htmlspecialchars( $rt->getFullText() ), array(), array( 'redirect' => 'no' ) );
}
}
- $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
+ $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>';
@@ -1583,12 +1103,12 @@ class Article {
* Builds trackback links for article display if $wgUseTrackbacks is set to true
*/
public function addTrackbacks() {
- global $wgOut, $wgUser;
+ global $wgOut;
$dbr = wfGetDB( DB_SLAVE );
$tbs = $dbr->select( 'trackbacks',
array( 'tb_id', 'tb_title', 'tb_url', 'tb_ex', 'tb_name' ),
- array( 'tb_page' => $this->getID() )
+ array( 'tb_page' => $this->mPage->getID() )
);
if ( !$dbr->numRows( $tbs ) ) {
@@ -1601,9 +1121,9 @@ class Article {
foreach ( $tbs as $o ) {
$rmvtxt = "";
- if ( $wgUser->isAllowed( 'trackback' ) ) {
- $delurl = $this->mTitle->getFullURL( "action=deletetrackback&tbid=" .
- $o->tb_id . "&token=" . urlencode( $wgUser->editToken() ) );
+ if ( $this->getContext()->getUser()->isAllowed( 'trackback' ) ) {
+ $delurl = $this->getTitle()->getFullURL( "action=deletetrackback&tbid=" .
+ $o->tb_id . "&token=" . urlencode( $this->getContext()->getUser()->editToken() ) );
$rmvtxt = wfMsg( 'trackbackremove', htmlspecialchars( $delurl ) );
}
@@ -1621,29 +1141,10 @@ class Article {
/**
* Removes trackback record for current article from trackbacks table
+ * @deprecated since 1.18
*/
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;
- }
-
- $db = wfGetDB( DB_MASTER );
- $db->delete( 'trackbacks', array( 'tb_id' => $wgRequest->getInt( 'tbid' ) ) );
-
- $wgOut->addWikiMsg( 'trackbackdeleteok' );
- $this->mTitle->invalidateCache();
+ return Action::factory( 'deletetrackback', $this )->show();
}
/**
@@ -1661,766 +1162,24 @@ class Article {
* Handle action=purge
*/
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 {
- $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' );
- }
- }
-
- /**
- * Perform the actions of a page purging
- */
- public function doPurge() {
- global $wgUseSquid;
-
- // Invalidate the cache
- $this->mTitle->invalidateCache();
-
- if ( $wgUseSquid ) {
- // Commit the transaction before the purge is sent
- $dbw = wfGetDB( DB_MASTER );
- $dbw->commit();
-
- // Send purge
- $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 );
- }
- }
-
- /**
- * Insert a new empty page record for this article.
- * This *must* be followed up by creating a revision
- * and running $this->updateRevisionOn( ... );
- * or else the record will be left in a funky state.
- * Best if all done inside a transaction.
- *
- * @param $dbw Database
- * @return int The newly created page_id key, or false if the title already existed
- * @private
- */
- public function insertOn( $dbw ) {
- wfProfileIn( __METHOD__ );
-
- $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
- $dbw->insert( 'page', array(
- 'page_id' => $page_id,
- 'page_namespace' => $this->mTitle->getNamespace(),
- 'page_title' => $this->mTitle->getDBkey(),
- 'page_counter' => 0,
- 'page_restrictions' => '',
- 'page_is_redirect' => 0, # Will set this shortly...
- 'page_is_new' => 1,
- 'page_random' => wfRandom(),
- 'page_touched' => $dbw->timestamp(),
- 'page_latest' => 0, # Fill this in shortly...
- 'page_len' => 0, # Fill this in shortly...
- ), __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 DatabaseBase: object
- * @param $revision Revision: For ID number, and text used to set
- length and redirect status fields
- * @param $lastRevision Integer: if given, will not overwrite the page field
- * when different from the currently set value.
- * Giving 0 indicates the new page flag should be set
- * on.
- * @param $lastRevIsRedirect Boolean: if given, will optimize adding and
- * removing rows in redirect table.
- * @return bool true on success, false on failure
- * @private
- */
- public function updateRevisionOn( &$dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
- wfProfileIn( __METHOD__ );
-
- $text = $revision->getText();
- $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;
- }
-
- $dbw->update( 'page',
- array( /* SET */
- 'page_latest' => $revision->getId(),
- 'page_touched' => $dbw->timestamp(),
- 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
- 'page_is_redirect' => $rt !== null ? 1 : 0,
- 'page_len' => strlen( $text ),
- ),
- $conditions,
- __METHOD__ );
-
- $result = $dbw->affectedRows() != 0;
- if ( $result ) {
- $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
- }
-
- wfProfileOut( __METHOD__ );
- return $result;
- }
-
- /**
- * Add row to the redirect table if this is a redirect, remove otherwise.
- *
- * @param $dbw Database
- * @param $redirectTitle a title object pointing to the redirect target,
- * or NULL if this is not a redirect
- * @param $lastRevIsRedirect If given, will optimize adding and
- * removing rows in redirect table.
- * @return bool true on success, false on failure
- * @private
- */
- public function updateRedirectOn( &$dbw, $redirectTitle, $lastRevIsRedirect = null ) {
- // Always update redirects (target link might have changed)
- // 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->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;
- }
-
- /**
- * If the given revision is newer than the currently set page_latest,
- * update the page record. Otherwise, do nothing.
- *
- * @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' ),
- array(
- 'page_id' => $this->getId(),
- 'page_latest=rev_id' ),
- __METHOD__ );
-
- if ( $row ) {
- if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- $prev = $row->rev_id;
- $lastRevIsRedirect = (bool)$row->page_is_redirect;
- } else {
- # No or missing previous revision; mark the page as new
- $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 {
- if ( is_null( $edittime ) ) {
- $rev = Revision::newFromTitle( $this->mTitle );
- } else {
- $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' ) {
- # Inserting a new section
- $subject = $summary ? wfMsgForContent( 'newsectionheaderdefaultlevel', $summary ) . "\n\n" : '';
- $text = strlen( trim( $oldtext ) ) > 0
- ? "{$oldtext}\n\n{$subject}{$text}"
- : "{$subject}{$text}";
- } else {
- # Replacing an existing section; roll out the big guns
- global $wgParser;
-
- $text = $wgParser->replaceSection( $oldtext, $section, $text );
- }
- }
-
- wfProfileOut( __METHOD__ );
- return $text;
- }
-
- /**
- * This function is not deprecated until somebody fixes the core not to use
- * it. Nevertheless, use Article::doEdit() instead.
- */
- function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC = false, $comment = false, $bot = false ) {
- $flags = EDIT_NEW | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
- ( $isminor ? EDIT_MINOR : 0 ) |
- ( $suppressRC ? EDIT_SUPPRESS_RC : 0 ) |
- ( $bot ? EDIT_FORCE_BOT : 0 );
-
- # If this is a comment, add the summary as headline
- if ( $comment && $summary != "" ) {
- $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $summary ) . "\n\n" . $text;
- }
- $this->doEdit( $text, $summary, $flags );
-
- $dbw = wfGetDB( DB_MASTER );
- if ( $watchthis ) {
- if ( !$this->mTitle->userIsWatching() ) {
- $dbw->begin();
- $this->doWatch();
- $dbw->commit();
- }
- } else {
- if ( $this->mTitle->userIsWatching() ) {
- $dbw->begin();
- $this->doUnwatch();
- $dbw->commit();
- }
- }
- $this->doRedirect( $this->isRedirect( $text ) );
- }
-
- /**
- * @deprecated use Article::doEdit()
- */
- function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) {
- $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
- ( $minor ? EDIT_MINOR : 0 ) |
- ( $forceBot ? EDIT_FORCE_BOT : 0 );
-
- $status = $this->doEdit( $text, $summary, $flags );
-
- if ( !$status->isOK() ) {
- return false;
- }
-
- $dbw = wfGetDB( DB_MASTER );
- if ( $watchthis ) {
- if ( !$this->mTitle->userIsWatching() ) {
- $dbw->begin();
- $this->doWatch();
- $dbw->commit();
- }
- } else {
- if ( $this->mTitle->userIsWatching() ) {
- $dbw->begin();
- $this->doUnwatch();
- $dbw->commit();
- }
- }
-
- $extraQuery = ''; // Give extensions a chance to modify URL query on update
- wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this, &$sectionanchor, &$extraQuery ) );
-
- $this->doRedirect( $this->isRedirect( $text ), $sectionanchor, $extraQuery );
- return true;
- }
-
- /**
- * 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,
- * optionally via the deferred update array.
- *
- * $wgUser must be set before calling this function.
- *
- * @param $text String: new text
- * @param $summary String: edit summary
- * @param $flags Integer bitfield:
- * EDIT_NEW
- * Article is known or assumed to be non-existent, create a new one
- * EDIT_UPDATE
- * Article is known or assumed to be pre-existing, update it
- * EDIT_MINOR
- * Mark this edit minor, if the user is allowed to do so
- * EDIT_SUPPRESS_RC
- * Do not log the change in recentchanges
- * EDIT_FORCE_BOT
- * Mark the edit a "bot" edit regardless of user rights
- * EDIT_DEFER_UPDATES
- * Defer some of the updates until the end of index.php
- * EDIT_AUTOSUMMARY
- * Fill in blank summaries with generated text where possible
- *
- * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
- * If EDIT_UPDATE is specified and the article doesn't exist, the function will an
- * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
- * edit-already-exists error will be returned. These two conditions are also possible with
- * auto-detection due to MediaWiki's performance-optimised locking strategy.
- *
- * @param $baseRevId the revision ID this edit was based off, if any
- * @param $user Optional user object, $wgUser will be used if not passed
- *
- * @return Status object. Possible errors:
- * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
- * edit-gone-missing: In update mode, but the article didn't exist
- * edit-conflict: In update mode, the article changed unexpectedly
- * edit-no-change: Warning that the text was the same as before
- * edit-already-exists: In creation mode, but the article already exists
- *
- * Extensions may define additional errors.
- *
- * $return->value will contain an associative array with members as follows:
- * new: Boolean indicating if the function attempted to create a new article
- * revision: The revision object for the inserted revision, or null
- *
- * Compatibility note: this function previously returned a boolean value indicating success/failure
- */
- public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
- global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries;
-
- # Low-level sanity check
- if ( $this->mTitle->getText() === '' ) {
- throw new MWException( 'Something is trying to edit an article with an empty title' );
- }
-
- wfProfileIn( __METHOD__ );
-
- $user = is_null( $user ) ? $wgUser : $user;
- $status = Status::newGood( array() );
-
- # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already
- $this->loadPageData();
-
- $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" );
-
- if ( $status->isOK() ) {
- $status->fatal( 'edit-hook-aborted' );
- }
-
- wfProfileOut( __METHOD__ );
- return $status;
- }
-
- # Silently ignore EDIT_MINOR if not allowed
- $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
- $bot = $flags & EDIT_FORCE_BOT;
-
- $oldtext = $this->getRawText(); // current revision
- $oldsize = strlen( $oldtext );
-
- # Provide autosummaries if one is not provided and autosummaries are enabled.
- if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
- $summary = $this->getAutosummary( $oldtext, $text, $flags );
- }
-
- $editInfo = $this->prepareTextForEdit( $text );
- $text = $editInfo->pst;
- $newsize = strlen( $text );
-
- $dbw = wfGetDB( DB_MASTER );
- $now = wfTimestampNow();
- $this->mTimestamp = $now;
-
- 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 );
- }
-
- $changed = ( strcmp( $text, $oldtext ) != 0 );
-
- if ( $changed ) {
- $this->mGoodAdjustment = (int)$this->isCountable( $text )
- - (int)$this->isCountable( $oldtext );
- $this->mTotalAdjustment = 0;
-
- if ( !$this->mLatest ) {
- # Article gone missing
- wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
- $status->fatal( 'edit-gone-missing' );
-
- wfProfileOut( __METHOD__ );
- return $status;
- }
-
- $revision = new Revision( array(
- 'page' => $this->getId(),
- 'comment' => $summary,
- 'minor_edit' => $isminor,
- 'text' => $text,
- 'parent_id' => $this->mLatest,
- 'user' => $user->getId(),
- 'user_text' => $user->getName(),
- ) );
-
- $dbw->begin();
- $revisionId = $revision->insertOn( $dbw );
-
- # Update page
- #
- # Note that we use $this->mLatest instead of fetching a value from the master DB
- # during the course of this function. This makes sure that EditPage can detect
- # edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
- # before this function is called. A previous function used a separate query, this
- # creates a window where concurrent edits can cause an ignored edit conflict.
- $ok = $this->updateRevisionOn( $dbw, $revision, $this->mLatest );
-
- 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 {
- global $wgUseRCPatrol;
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
- # Update recentchanges
- if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
- # Mark as patrolled if the user can do so
- $patrolled = $wgUseRCPatrol && $this->mTitle->userCan( 'autopatrol' );
- # Add RC row to the DB
- $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
- $this->mLatest, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
- $revisionId, $patrolled
- );
-
- # Log auto-patrolled edits
- if ( $patrolled ) {
- PatrolLog::record( $rc, true );
- }
- }
- $user->incEditCount();
- $dbw->commit();
- }
- } else {
- $status->warning( 'edit-no-change' );
- $revision = null;
- // Keep the same revision ID, but do some updates on it
- $revisionId = $this->getRevIdFetched();
- // Update page_touched, this is usually implicit in the page update
- // Other cache updates are done in onArticleEdit()
- $this->mTitle->invalidateCache();
- }
-
- if ( !$wgDBtransactions ) {
- ignore_user_abort( $userAbort );
- }
-
- // Now that ignore_user_abort is restored, we can respond to fatal errors
- if ( !$status->isOK() ) {
- wfProfileOut( __METHOD__ );
- return $status;
- }
-
- # Invalidate cache of this article and all pages using this article
- # as a template. Partly deferred.
- Article::onArticleEdit( $this->mTitle );
- # Update links tables, site stats, etc.
- $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed );
- } else {
- # Create new article
- $status->value['new'] = true;
-
- # Set statistics members
- # We work out if it's countable after PST to avoid counter drift
- # when articles are created with {{subst:}}
- $this->mGoodAdjustment = (int)$this->isCountable( $text );
- $this->mTotalAdjustment = 1;
-
- $dbw->begin();
-
- # Add the page record; stake our claim on this title!
- # This will return false if the article already exists
- $newid = $this->insertOn( $dbw );
-
- if ( $newid === false ) {
- $dbw->rollback();
- $status->fatal( 'edit-already-exists' );
-
- wfProfileOut( __METHOD__ );
- return $status;
- }
-
- # Save the revision text...
- $revision = new Revision( array(
- 'page' => $newid,
- 'comment' => $summary,
- 'minor_edit' => $isminor,
- 'text' => $text,
- 'user' => $user->getId(),
- 'user_text' => $user->getName(),
- ) );
- $revisionId = $revision->insertOn( $dbw );
-
- $this->mTitle->resetArticleID( $newid );
-
- # Update the page record with revision data
- $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 );
- }
- }
- $user->incEditCount();
- $dbw->commit();
-
- # Update links, etc.
- $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, true );
-
- # Clear caches
- Article::onArticleCreate( $this->mTitle );
-
- wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
- }
-
- # Do updates right now unless deferral was requested
- if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
- wfDoUpdates();
- }
-
- // Return the new revision (or null) to the caller
- $status->value['revision'] = $revision;
-
- wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
-
- wfProfileOut( __METHOD__ );
- return $status;
- }
-
- /**
- * @deprecated wrapper for doRedirect
- */
- public function showArticle( $text, $subtitle , $sectionanchor = '', $me2, $now, $summary, $oldid ) {
- wfDeprecated( __METHOD__ );
- $this->doRedirect( $this->isRedirect( $text ), $sectionanchor );
- }
-
- /**
- * Output a redirect back to the article.
- * This is typically used after an edit.
- *
- * @param $noRedir Boolean: add redirect=no
- * @param $sectionAnchor String: section to redirect to, including "#"
- * @param $extraQuery String: extra query params
- */
- public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
- global $wgOut;
-
- if ( $noRedir ) {
- $query = 'redirect=no';
- if ( $extraQuery )
- $query .= "&$extraQuery";
- } else {
- $query = $extraQuery;
- }
-
- $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor );
+ return Action::factory( 'purge', $this )->show();
}
/**
* Mark this particular edit/page as patrolled
+ * @deprecated since 1.18
*/
public function markpatrolled() {
- 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;
- }
-
- # It would be nice to see where the user had actually come from, but for now just guess
- $returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges';
- $return = SpecialPage::getTitleFor( $returnto );
-
- $errors = $rc->doMarkPatrolled();
-
- if ( in_array( array( 'rcpatroldisabled' ), $errors ) ) {
- $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
-
- return;
- }
-
- if ( in_array( array( 'hookaborted' ), $errors ) ) {
- // The hook itself has handled any output
- return;
- }
-
- if ( in_array( array( 'markedaspatrollederror-noautopatrol' ), $errors ) ) {
- $wgOut->setPageTitle( wfMsg( 'markedaspatrollederror' ) );
- $wgOut->addWikiMsg( 'markedaspatrollederror-noautopatrol' );
- $wgOut->returnToMain( false, $return );
-
- return;
- }
-
- if ( !empty( $errors ) ) {
- $wgOut->showPermissionsErrorPage( $errors );
-
- return;
- }
-
- # Inform the user
- $wgOut->setPageTitle( wfMsg( 'markedaspatrolled' ) );
- $wgOut->addWikiMsg( 'markedaspatrolledtext', $rc->getTitle()->getPrefixedText() );
- $wgOut->returnToMain( false, $return );
+ Action::factory( 'markpatrolled', $this )->show();
}
/**
- * User-interface handler for the "watch" action
+ * User-interface handler for the "watch" action.
+ * Requires Request to pass a token as of 1.18.
+ * @deprecated since 1.18
*/
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() );
+ Action::factory( 'watch', $this )->show();
}
/**
@@ -2429,64 +1188,30 @@ class Article {
* This is safe to be called multiple times
*
* @return bool true on successful watch operation
+ * @deprecated since 1.18
*/
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;
+ return WatchAction::doWatch( $this->getTitle(), $wgUser );
}
/**
* User interface handler for the "unwatch" action.
+ * Requires Request to pass a token as of 1.18.
+ * @deprecated since 1.18
*/
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() );
+ Action::factory( 'unwatch', $this )->show();
}
/**
* Stop watching a page
* @return bool true on successful unwatch
+ * @deprecated since 1.18
*/
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;
+ return WatchAction::doUnwatch( $this->getTitle(), $wgUser );
}
/**
@@ -2505,316 +1230,60 @@ class Article {
}
/**
- * Update the article's restriction field, and leave a log entry.
- *
- * @param $limit Array: set of restriction keys
- * @param $reason String
- * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
- * @param $expiry Array: per restriction type expiration
- * @return bool true on success
+ * Info about this page
+ * Called for ?action=info when $wgAllowPageInfo is on.
*/
- public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
- global $wgUser, $wgContLang;
-
- $restrictionTypes = $this->mTitle->getRestrictionTypes();
-
- $id = $this->mTitle->getArticleID();
-
- if ( $id <= 0 ) {
- wfDebug( "updateRestrictions failed: article id $id <= 0\n" );
- return false;
- }
-
- if ( wfReadOnly() ) {
- wfDebug( "updateRestrictions failed: read-only\n" );
- return false;
- }
-
- if ( !$this->mTitle->userCan( 'protect' ) ) {
- wfDebug( "updateRestrictions failed: insufficient permissions\n" );
- return false;
- }
-
- if ( !$cascade ) {
- $cascade = false;
- }
-
- // Take this opportunity to purge out expired restrictions
- Title::purgeExpiredRestrictions();
-
- # FIXME: Same limitations as described in ProtectionForm.php (line 37);
- # we expect a single selection, but the schema allows otherwise.
- $current = array();
- $updated = Article::flattenRestrictions( $limit );
- $changed = false;
-
- foreach ( $restrictionTypes as $action ) {
- if ( isset( $expiry[$action] ) ) {
- # Get current restrictions on $action
- $aLimits = $this->mTitle->getRestrictions( $action );
- $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".
- if ( $aRChanged && $this->mTitle->mRestrictionsExpiry[$action] != $expiry[$action] ) {
- $changed = true;
- }
- }
- }
-
- $current = Article::flattenRestrictions( $current );
-
- $changed = ( $changed || $current != $updated );
- $changed = $changed || ( $updated && $this->mTitle->areRestrictionsCascading() != $cascade );
- $protect = ( $updated != '' );
-
- # 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 ) ) {
- $cascade = false;
- }
-
- $cascade_description = '';
-
- if ( $cascade ) {
- $cascade_description = ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']';
- }
-
- if ( $reason ) {
- $comment .= ": $reason";
- }
-
- $editComment = $comment;
- $encodedExpiry = array();
- $protect_description = '';
- foreach ( $limit as $action => $restrictions ) {
- if ( !isset( $expiry[$action] ) )
- $expiry[$action] = Block::infinity();
-
- $encodedExpiry[$action] = Block::encodeExpiry( $expiry[$action], $dbw );
- if ( $restrictions != '' ) {
- $protect_description .= "[$action=$restrictions] (";
- if ( $encodedExpiry[$action] != 'infinity' ) {
- $protect_description .= wfMsgForContent( 'protect-expiring',
- $wgContLang->timeanddate( $expiry[$action], false, false ) ,
- $wgContLang->date( $expiry[$action], false, false ) ,
- $wgContLang->time( $expiry[$action], false, false ) );
- } else {
- $protect_description .= wfMsgForContent( 'protect-expiry-indefinite' );
- }
-
- $protect_description .= ') ';
- }
- }
- $protect_description = trim( $protect_description );
-
- if ( $protect_description && $protect ) {
- $editComment .= " ($protect_description)";
- }
-
- if ( $cascade ) {
- $editComment .= "$cascade_description";
- }
-
- # Update restrictions table
- foreach ( $limit as $action => $restrictions ) {
- if ( $restrictions != '' ) {
- $dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ),
- array( 'pr_page' => $id,
- 'pr_type' => $action,
- 'pr_level' => $restrictions,
- 'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
- 'pr_expiry' => $encodedExpiry[$action]
- ),
- __METHOD__
- );
- } else {
- $dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
- 'pr_type' => $action ), __METHOD__ );
- }
- }
-
- # Insert a null revision
- $nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true );
- $nullRevId = $nullRevision->insertOn( $dbw );
-
- $latest = $this->getLatest();
- # Update page record
- $dbw->update( 'page',
- array( /* SET */
- 'page_touched' => $dbw->timestamp(),
- 'page_restrictions' => '',
- 'page_latest' => $nullRevId
- ), array( /* WHERE */
- 'page_id' => $id
- ), 'Article::protect'
- );
-
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $wgUser ) );
- wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) );
-
- # Update the protection log
- $log = new LogPage( 'protect' );
- if ( $protect ) {
- $params = array( $protect_description, $cascade ? 'cascade' : '' );
- $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason ), $params );
- } else {
- $log->addEntry( 'unprotect', $this->mTitle, $reason );
- }
- } # End hook
- } # End "changed" check
-
- return true;
+ public function info() {
+ Action::factory( 'info', $this )->show();
}
/**
- * Take an array of page restrictions and flatten it to a string
- * suitable for insertion into the page_restrictions field.
- * @param $limit Array
- * @return String
+ * Overriden by ImagePage class, only present here to avoid a fatal error
+ * Called for ?action=revert
*/
- protected static function flattenRestrictions( $limit ) {
- 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";
- }
- }
+ public function revert() {
+ Action::factory( 'revert', $this )->show();
+ }
- return implode( ':', $bits );
+ /**
+ * User interface for rollback operations
+ */
+ public function rollback() {
+ Action::factory( 'rollback', $this )->show();
}
/**
- * Auto-generates a deletion reason
+ * Output a redirect back to the article.
+ * This is typically used after an edit.
*
- * @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
+ * @deprecated in 1.18; call $wgOut->redirect() directly
+ * @param $noRedir Boolean: add redirect=no
+ * @param $sectionAnchor String: section to redirect to, including "#"
+ * @param $extraQuery String: extra query params
*/
- public function generateReason( &$hasHistory ) {
- global $wgContLang;
-
- $dbw = wfGetDB( DB_MASTER );
- // Get the last revision
- $rev = Revision::newFromTitle( $this->mTitle );
-
- 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;
- }
- }
-
- // Find out if there was only one contributor
- // Only scan the last 20 revisions
- $res = $dbw->select( 'revision', 'rev_user_text',
- array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
- __METHOD__,
- array( 'LIMIT' => 20 )
- );
-
- if ( $res === false ) {
- // This page has no revisions, which is very weird
- return false;
- }
-
- $hasHistory = ( $res->numRows() > 1 );
- $row = $dbw->fetchObject( $res );
-
- 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;
- }
+ public function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
+ wfDeprecated( __METHOD__ );
+ global $wgOut;
- // Generate the summary with a '$1' placeholder
- if ( $blank ) {
- // The current revision is blank and the one before is also
- // blank. It's just not our lucky day
- $reason = wfMsgForContent( 'exbeforeblank', '$1' );
+ if ( $noRedir ) {
+ $query = 'redirect=no';
+ if ( $extraQuery )
+ $query .= "&$extraQuery";
} else {
- if ( $onlyAuthor ) {
- $reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor );
- } else {
- $reason = wfMsgForContent( 'excontent', '$1' );
- }
- }
-
- if ( $reason == '-' ) {
- // Allow these UI messages to be blanked out cleanly
- return '';
+ $query = $extraQuery;
}
- // Replace newlines with spaces to prevent uglyness
- $contents = preg_replace( "/[\n\r]/", ' ', $contents );
- // Calculate the maximum amount of chars to get
- // Max content length = max comment length - length of the comment (excl. $1) - '...'
- $maxLength = 255 - ( strlen( $reason ) - 2 ) - 3;
- $contents = $wgContLang->truncate( $contents, $maxLength );
- // Remove possible unfinished links
- $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
- // Now replace the '$1' placeholder
- $reason = str_replace( '$1', $contents, $reason );
-
- return $reason;
+ $wgOut->redirect( $this->getTitle()->getFullURL( $query ) . $sectionAnchor );
}
-
- /*
+ /**
* UI entry point for page deletion
*/
public function delete() {
- global $wgUser, $wgOut, $wgRequest;
+ global $wgOut, $wgRequest;
$confirm = $wgRequest->wasPosted() &&
- $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
+ $this->getContext()->getUser()->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
$this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
$this->DeleteReason = $wgRequest->getText( 'wpReason' );
@@ -2829,7 +1298,7 @@ class Article {
}
# Flag to hide all contents of the archived revisions
- $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
+ $suppress = $wgRequest->getVal( 'wpSuppress' ) && $this->getContext()->getUser()->isAllowed( 'suppressrevision' );
# This code desperately needs to be totally rewritten
@@ -2841,7 +1310,7 @@ class Article {
}
# Check permissions
- $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser );
+ $permission_errors = $this->getTitle()->getUserPermissionsErrors( 'delete', $this->getContext()->getUser() );
if ( count( $permission_errors ) > 0 ) {
$wgOut->showPermissionsErrorPage( $permission_errors );
@@ -2849,33 +1318,34 @@ class Article {
return;
}
- $wgOut->setPagetitle( wfMsg( 'delete-confirm', $this->mTitle->getPrefixedText() ) );
+ $wgOut->setPagetitle( wfMsg( 'delete-confirm', $this->getTitle()->getPrefixedText() ) );
# Better double-check that it hasn't been deleted yet!
$dbw = wfGetDB( DB_MASTER );
- $conds = $this->mTitle->pageCond();
+ $conds = $this->getTitle()->pageCond();
$latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
if ( $latest === false ) {
$wgOut->showFatalError(
Html::rawElement(
'div',
array( 'class' => 'error mw-error-cannotdelete' ),
- wfMsgExt( 'cannotdelete', array( 'parse' ), $this->mTitle->getPrefixedText() )
+ wfMsgExt( 'cannotdelete', array( 'parse' ),
+ wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
)
);
$wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
LogEventsList::showLogExtract(
$wgOut,
'delete',
- $this->mTitle->getPrefixedText()
+ $this->getTitle()->getPrefixedText()
);
return;
}
# Hack for big sites
- $bigHistory = $this->isBigDeletion();
- if ( $bigHistory && !$this->mTitle->userCan( 'bigdelete' ) ) {
+ $bigHistory = $this->mPage->isBigDeletion();
+ if ( $bigHistory && !$this->getTitle()->userCan( 'bigdelete' ) ) {
global $wgLang, $wgDeleteRevisionsLimit;
$wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
@@ -2887,9 +1357,9 @@ class Article {
if ( $confirm ) {
$this->doDelete( $reason, $suppress );
- if ( $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn() ) {
+ if ( $wgRequest->getCheck( 'wpWatch' ) && $this->getContext()->getUser()->isLoggedIn() ) {
$this->doWatch();
- } elseif ( $this->mTitle->userIsWatching() ) {
+ } elseif ( $this->getTitle()->userIsWatching() ) {
$this->doUnwatch();
}
@@ -2906,12 +1376,14 @@ class Article {
if ( $hasHistory && !$confirm ) {
global $wgLang;
- $skin = $wgUser->getSkin();
- $revisions = $this->estimateRevisionCount();
- //FIXME: lego
+ $revisions = $this->mPage->estimateRevisionCount();
+ // @todo FIXME: i18n issue/patchwork message
$wgOut->addHTML( '<strong class="mw-delete-warning-revisions">' .
wfMsgExt( 'historywarning', array( 'parseinline' ), $wgLang->formatNum( $revisions ) ) .
- wfMsgHtml( 'word-separator' ) . $skin->historyLink() .
+ wfMsgHtml( 'word-separator' ) . Linker::link( $this->getTitle(),
+ wfMsgHtml( 'history' ),
+ array( 'rel' => 'archives' ),
+ array( 'action' => 'history' ) ) .
'</strong>'
);
@@ -2926,103 +1398,24 @@ class Article {
}
/**
- * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
- */
- public function isBigDeletion() {
- global $wgDeleteRevisionsLimit;
-
- if ( $wgDeleteRevisionsLimit ) {
- $revCount = $this->estimateRevisionCount();
-
- return $revCount > $wgDeleteRevisionsLimit;
- }
-
- return false;
- }
-
- /**
- * @return int approximate revision count
- */
- public function estimateRevisionCount() {
- $dbr = wfGetDB( DB_SLAVE );
-
- // For an exact count...
- // return $dbr->selectField( 'revision', 'COUNT(*)',
- // array( 'rev_page' => $this->getId() ), __METHOD__ );
- return $dbr->estimateRowCount( 'revision', '*',
- array( 'rev_page' => $this->getId() ), __METHOD__ );
- }
-
- /**
- * Get the last N authors
- * @param $num Integer: number of revisions to get
- * @param $revLatest String: the latest rev_id, selected from the master (optional)
- * @return array Array of authors, duplicates not removed
- */
- public function getLastNAuthors( $num, $revLatest = 0 ) {
- wfProfileIn( __METHOD__ );
- // First try the slave
- // If that doesn't have the latest revision, try the master
- $continue = 2;
- $db = wfGetDB( DB_SLAVE );
-
- do {
- $res = $db->select( array( 'page', 'revision' ),
- array( 'rev_id', 'rev_user_text' ),
- array(
- 'page_namespace' => $this->mTitle->getNamespace(),
- 'page_title' => $this->mTitle->getDBkey(),
- 'rev_page = page_id'
- ), __METHOD__, $this->getSelectOptions( array(
- 'ORDER BY' => 'rev_timestamp DESC',
- '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--;
- } else {
- $continue = 0;
- }
- } while ( $continue );
-
- $authors = array( $row->rev_user_text );
-
- foreach ( $res as $row ) {
- $authors[] = $row->rev_user_text;
- }
-
- wfProfileOut( __METHOD__ );
- return $authors;
- }
-
- /**
* Output deletion confirmation dialog
- * FIXME: Move to another file?
+ * @todo FIXME: Move to another file?
* @param $reason String: prefilled reason
*/
public function confirmDelete( $reason ) {
- global $wgOut, $wgUser;
+ global $wgOut;
wfDebug( "Article::confirmDelete\n" );
- $deleteBackLink = $wgUser->getSkin()->linkKnown( $this->mTitle );
+ $deleteBackLink = Linker::linkKnown( $this->getTitle() );
$wgOut->setSubtitle( wfMsgHtml( 'delete-backlink', $deleteBackLink ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->addWikiMsg( 'confirmdeletetext' );
wfRunHooks( 'ArticleConfirmDelete', array( $this, $wgOut, &$reason ) );
- if ( $wgUser->isAllowed( 'suppressrevision' ) ) {
- $suppress = "<tr id=\"wpDeleteSuppressRow\" name=\"wpDeleteSuppressRow\">
+ if ( $this->getContext()->getUser()->isAllowed( 'suppressrevision' ) ) {
+ $suppress = "<tr id=\"wpDeleteSuppressRow\">
<td></td>
<td class='mw-input'><strong>" .
Xml::checkLabel( wfMsg( 'revdelete-suppress' ),
@@ -3032,10 +1425,10 @@ class Article {
} else {
$suppress = '';
}
- $checkWatch = $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching();
+ $checkWatch = $this->getContext()->getUser()->getBoolOption( 'watchdeletion' ) || $this->getTitle()->userIsWatching();
$form = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->mTitle->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
+ 'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) .
Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
@@ -3065,7 +1458,7 @@ class Article {
</tr>";
# Disallow watching if user is not logged in
- if ( $wgUser->isLoggedIn() ) {
+ if ( $this->getContext()->getUser()->isLoggedIn() ) {
$form .= "
<tr>
<td></td>
@@ -3087,13 +1480,12 @@ class Article {
</tr>" .
Xml::closeElement( 'table' ) .
Xml::closeElement( 'fieldset' ) .
- Html::hidden( 'wpEditToken', $wgUser->editToken() ) .
+ Html::hidden( 'wpEditToken', $this->getContext()->getUser()->editToken() ) .
Xml::closeElement( 'form' );
- if ( $wgUser->isAllowed( 'editinterface' ) ) {
- $skin = $wgUser->getSkin();
+ if ( $this->getContext()->getUser()->isAllowed( 'editinterface' ) ) {
$title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
- $link = $skin->link(
+ $link = Linker::link(
$title,
wfMsgHtml( 'delete-edit-reasonlist' ),
array(),
@@ -3105,7 +1497,7 @@ class Article {
$wgOut->addHTML( $form );
$wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
LogEventsList::showLogExtract( $wgOut, 'delete',
- $this->mTitle->getPrefixedText()
+ $this->getTitle()->getPrefixedText()
);
}
@@ -3113,31 +1505,29 @@ class Article {
* Perform a deletion and output success or failure messages
*/
public function doDelete( $reason, $suppress = false ) {
- global $wgOut, $wgUser;
+ global $wgOut;
- $id = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
+ $id = $this->getTitle()->getArticleID( Title::GAID_FOR_UPDATE );
$error = '';
- if ( wfRunHooks( 'ArticleDelete', array( &$this, &$wgUser, &$reason, &$error ) ) ) {
- if ( $this->doDeleteArticle( $reason, $suppress, $id ) ) {
- $deleted = $this->mTitle->getPrefixedText();
+ if ( $this->mPage->doDeleteArticle( $reason, $suppress, $id, $error ) ) {
+ $deleted = $this->getTitle()->getPrefixedText();
- $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
+ $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $loglink = '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]';
+ $loglink = '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]';
- $wgOut->addWikiMsg( 'deletedtext', $deleted, $loglink );
- $wgOut->returnToMain( false );
- wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$wgUser, $reason, $id ) );
- }
+ $wgOut->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
+ $wgOut->returnToMain( false );
} else {
if ( $error == '' ) {
$wgOut->showFatalError(
Html::rawElement(
'div',
array( 'class' => 'error mw-error-cannotdelete' ),
- wfMsgExt( 'cannotdelete', array( 'parse' ), $this->mTitle->getPrefixedText() )
+ wfMsgExt( 'cannotdelete', array( 'parse' ),
+ wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
)
);
@@ -3146,7 +1536,7 @@ class Article {
LogEventsList::showLogExtract(
$wgOut,
'delete',
- $this->mTitle->getPrefixedText()
+ $this->getTitle()->getPrefixedText()
);
} else {
$wgOut->showFatalError( $error );
@@ -3155,574 +1545,6 @@ class Article {
}
/**
- * Back-end article deletion
- * Deletes the article with database consistency, writes logs, purges caches
- *
- * @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, $commit = true ) {
- global $wgDeferredUpdateList, $wgUseTrackbacks;
-
- wfDebug( __METHOD__ . "\n" );
-
- $dbw = wfGetDB( DB_MASTER );
- $t = $this->mTitle->getDBkey();
- $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
-
- if ( $t === '' || $id == 0 ) {
- return false;
- }
-
- $u = new SiteStatsUpdate( 0, 1, - (int)$this->isCountable( $this->getRawText() ), -1 );
- array_push( $wgDeferredUpdateList, $u );
-
- // Bitfields to further suppress the content
- if ( $suppress ) {
- $bitfield = 0;
- // This should be 15...
- $bitfield |= Revision::DELETED_TEXT;
- $bitfield |= Revision::DELETED_COMMENT;
- $bitfield |= Revision::DELETED_USER;
- $bitfield |= Revision::DELETED_RESTRICTED;
- } else {
- $bitfield = 'rev_deleted';
- }
-
- $dbw->begin();
- // For now, shunt the revision data into the archive table.
- // Text is *not* removed from the text table; bulk storage
- // is left intact to avoid breaking block-compression or
- // immutable storage schemes.
- //
- // For backwards compatibility, note that some older archive
- // table entries will have ar_text and ar_flags fields still.
- //
- // In the future, we may keep revisions and mark them with
- // the rev_deleted field, which is reserved for this purpose.
- $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
- array(
- 'ar_namespace' => 'page_namespace',
- 'ar_title' => 'page_title',
- 'ar_comment' => 'rev_comment',
- 'ar_user' => 'rev_user',
- 'ar_user_text' => 'rev_user_text',
- 'ar_timestamp' => 'rev_timestamp',
- 'ar_minor_edit' => 'rev_minor_edit',
- 'ar_rev_id' => 'rev_id',
- 'ar_text_id' => 'rev_text_id',
- 'ar_text' => '\'\'', // Be explicit to appease
- 'ar_flags' => '\'\'', // MySQL's "strict mode"...
- 'ar_len' => 'rev_len',
- 'ar_page_id' => 'page_id',
- 'ar_deleted' => $bitfield
- ), array(
- 'page_id' => $id,
- 'page_id = rev_page'
- ), __METHOD__
- );
-
- # Delete restrictions for it
- $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
-
- # 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;
- }
-
- # 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
- if ( !$dbw->cascadingDeletes() ) {
- $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
-
- if ( $wgUseTrackbacks )
- $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
-
- # Delete outgoing links
- $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
- $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
- $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
- $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 ) );
- }
-
- # If using cleanup triggers, we can skip some manual deletes
- if ( !$dbw->cleanupTriggers() ) {
- # Clean up recentchanges entries...
- $dbw->delete( 'recentchanges',
- array( 'rc_type != ' . RC_LOG,
- 'rc_namespace' => $this->mTitle->getNamespace(),
- 'rc_title' => $this->mTitle->getDBkey() ),
- __METHOD__ );
- $dbw->delete( 'recentchanges',
- array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
- __METHOD__ );
- }
-
- # Clear caches
- Article::onArticleDelete( $this->mTitle );
-
- # Clear the cached article id so the interface doesn't act like we exist
- $this->mTitle->resetArticleID( 0 );
-
- # Log the deletion, if the page was suppressed, log it at Oversight instead
- $logtype = $suppress ? 'suppress' : 'delete';
- $log = new LogPage( $logtype );
-
- # Make sure logging got through
- $log->addEntry( 'delete', $this->mTitle, $reason, array() );
-
- if ( $commit ) {
- $dbw->commit();
- }
-
- return true;
- }
-
- /**
- * Roll back the most recent consecutive set of edits to a page
- * from the same user; fails if there are no eligible edits to
- * roll back to, e.g. user is the sole contributor. This function
- * performs permissions checks on $wgUser, then calls commitRollback()
- * to do the dirty work
- *
- * @param $fromP String: Name of the user whose edits to rollback.
- * @param $summary String: Custom summary. Set to default summary if empty.
- * @param $token String: Rollback token.
- * @param $bot Boolean: If true, mark all reverted edits as bot.
- *
- * @param $resultDetails Array: contains result-specific array of additional values
- * 'alreadyrolled' : 'current' (rev)
- * success : 'summary' (str), 'current' (rev), 'target' (rev)
- *
- * @return array of errors, each error formatted as
- * array(messagekey, param1, param2, ...).
- * On success, the array is empty. This array can also be passed to
- * OutputPage::showPermissionsErrorPage().
- */
- public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails ) {
- global $wgUser;
-
- $resultDetails = null;
-
- # Check permissions
- $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
- $rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $wgUser );
- $errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
-
- 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 ) ) {
- return $errors;
- }
-
- return $this->commitRollback( $fromP, $summary, $bot, $resultDetails );
- }
-
- /**
- * Backend implementation of doRollback(), please refer there for parameter
- * and return value documentation
- *
- * NOTE: This function does NOT check ANY permissions, it just commits the
- * rollback to the DB Therefore, you should only call this function direct-
- * ly if you want to use custom permissions checks. If you don't, use
- * doRollback() instead.
- */
- public function commitRollback( $fromP, $summary, $bot, &$resultDetails ) {
- global $wgUseRCPatrol, $wgUser, $wgLang;
-
- $dbw = wfGetDB( DB_MASTER );
-
- if ( wfReadOnly() ) {
- return array( array( 'readonlytext' ) );
- }
-
- # Get the last editor
- $current = Revision::newFromTitle( $this->mTitle );
- if ( is_null( $current ) ) {
- # Something wrong... no page?
- return array( array( 'notanarticle' ) );
- }
-
- $from = str_replace( '_', ' ', $fromP );
- # User name given should match up with the top revision.
- # If the user was deleted then $from should be empty.
- if ( $from != $current->getUserText() ) {
- $resultDetails = array( 'current' => $current );
- return array( array( 'alreadyrolled',
- htmlspecialchars( $this->mTitle->getPrefixedText() ),
- htmlspecialchars( $fromP ),
- htmlspecialchars( $current->getUserText() )
- ) );
- }
-
- # Get the last edit not by this guy...
- # Note: these may not be public values
- $user = intval( $current->getRawUser() );
- $user_text = $dbw->addQuotes( $current->getRawUserText() );
- $s = $dbw->selectRow( 'revision',
- array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
- array( 'rev_page' => $current->getPage(),
- "rev_user != {$user} OR rev_user_text != {$user_text}"
- ), __METHOD__,
- array( 'USE INDEX' => 'page_timestamp',
- '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 ) {
- # Only admins can see this text
- return array( array( 'notvisiblerev' ) );
- }
-
- $set = array();
- if ( $bot && $wgUser->isAllowed( 'markbotedits' ) ) {
- # Mark all reverted edits as bot
- $set['rc_bot'] = 1;
- }
-
- if ( $wgUseRCPatrol ) {
- # Mark all reverted edits as patrolled
- $set['rc_patrolled'] = 1;
- }
-
- if ( count( $set ) ) {
- $dbw->update( 'recentchanges', $set,
- array( /* WHERE */
- 'rc_cur_id' => $current->getPage(),
- 'rc_user_text' => $current->getUserText(),
- "rc_timestamp > '{$s->rev_timestamp}'",
- ), __METHOD__
- );
- }
-
- # Generate the edit summary if necessary
- $target = Revision::newFromId( $s->rev_id );
- if ( empty( $summary ) ) {
- if ( $from == '' ) { // no public user name
- $summary = wfMsgForContent( 'revertpage-nouser' );
- } else {
- $summary = wfMsgForContent( 'revertpage' );
- }
- }
-
- # Allow the custom summary to use the same args as the default message
- $args = array(
- $target->getUserText(), $from, $s->rev_id,
- $wgLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ), true ),
- $current->getId(), $wgLang->timeanddate( $current->getTimestamp() )
- );
- $summary = wfMsgReplaceArgs( $summary, $args );
-
- # Save
- $flags = EDIT_UPDATE;
-
- if ( $wgUser->isAllowed( 'minoredit' ) ) {
- $flags |= EDIT_MINOR;
- }
-
- 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'] ) ) {
- $revId = $status->value['revision']->getId();
- } else {
- $revId = false;
- }
-
- wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target, $current ) );
-
- $resultDetails = array(
- 'summary' => $summary,
- 'current' => $current,
- 'target' => $target,
- 'newid' => $revId
- );
-
- return array();
- }
-
- /**
- * User interface for rollback operations
- */
- public function rollback() {
- global $wgUser, $wgOut, $wgRequest;
-
- $details = null;
-
- $result = $this->doRollback(
- $wgRequest->getVal( 'from' ),
- $wgRequest->getText( 'summary' ),
- $wgRequest->getVal( 'token' ),
- $wgRequest->getBool( 'bot' ),
- $details
- );
-
- if ( in_array( array( 'actionthrottledtext' ), $result ) ) {
- $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.
- # Remove any 'readonlytext' error manually.
- $out = array();
- foreach ( $result as $error ) {
- if ( $error != array( 'readonlytext' ) ) {
- $out [] = $error;
- }
- }
- $wgOut->showPermissionsErrorPage( $out );
-
- return;
- }
-
- if ( $result == array( array( 'readonlytext' ) ) ) {
- $wgOut->readOnlyPage();
-
- return;
- }
-
- $current = $details['current'];
- $target = $details['target'];
- $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 ) );
- $wgOut->returnToMain( false, $this->mTitle );
-
- if ( !$wgRequest->getBool( 'hidediff', false ) && !$wgUser->getBoolOption( 'norollbackdiff', false ) ) {
- $de = new DifferenceEngine( $this->mTitle, $current->getId(), $newId, false, true );
- $de->showDiff( '', '' );
- }
- }
-
- /**
- * Do standard deferred updates after page view
- */
- public function viewUpdates() {
- global $wgDeferredUpdateList, $wgDisableCounters, $wgUser;
- 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 );
- }
-
- /**
- * Prepare text which is about to be saved.
- * Returns a stdclass with source, pst and output members
- */
- public function prepareTextForEdit( $text, $revid = null ) {
- if ( $this->mPreparedEdit && $this->mPreparedEdit->newText == $text && $this->mPreparedEdit->revid == $revid ) {
- // Already prepared
- return $this->mPreparedEdit;
- }
-
- global $wgParser;
-
- $edit = (object)array();
- $edit->revid = $revid;
- $edit->newText = $text;
- $edit->pst = $this->preSaveTransform( $text );
- $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;
- }
-
- /**
- * Do standard deferred updates after page edit.
- * Update links tables, site stats, search index and message cache.
- * Purges pages that include this page if the text was changed here.
- * Every 100th edit, prune the recent changes table.
- *
- * @private
- * @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 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;
-
- wfProfileIn( __METHOD__ );
-
- # Parse the text
- # Be careful not to double-PST: $text is usually already PST-ed once
- if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
- wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
- $editInfo = $this->prepareTextForEdit( $text, $newid );
- } else {
- wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
- $editInfo = $this->mPreparedEdit;
- }
-
- # Save it to the parser cache
- if ( $wgEnableParserCache ) {
- $parserCache = ParserCache::singleton();
- $parserCache->save( $editInfo->output, $this, $editInfo->popts );
- }
-
- # Update the links tables
- $u = new LinksUpdate( $this->mTitle, $editInfo->output );
- $u->doUpdate();
-
- wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $changed ) );
-
- if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
- if ( 0 == mt_rand( 0, 99 ) ) {
- // Flush old entries from the `recentchanges` table; we do this on
- // random requests so as to avoid an increase in writes for no good reason
- 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 );
- }
- }
-
- $id = $this->getID();
- $title = $this->mTitle->getPrefixedDBkey();
- $shortTitle = $this->mTitle->getDBkey();
-
- if ( 0 == $id ) {
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $u = new SiteStatsUpdate( 0, 1, $this->mGoodAdjustment, $this->mTotalAdjustment );
- array_push( $wgDeferredUpdateList, $u );
- $u = new SearchUpdate( $id, $title, $text );
- array_push( $wgDeferredUpdateList, $u );
-
- # If this is another user's talk page, update newtalk
- # Don't do this if $changed = false otherwise some idiot can null-edit a
- # 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' ) )
- ) {
- if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
- $other = User::newFromName( $shortTitle, false );
- if ( !$other ) {
- wfDebug( __METHOD__ . ": invalid username\n" );
- } elseif ( User::isIP( $shortTitle ) ) {
- // An anonymous user
- $other->setNewtalk( true );
- } elseif ( $other->isLoggedIn() ) {
- $other->setNewtalk( true );
- } else {
- wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
- }
- }
- }
-
- if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- $wgMessageCache->replace( $shortTitle, $text );
- }
-
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Perform article updates on a special page creation.
- *
- * @param $rev Revision object
- *
- * @todo This is a shitty interface function. Kill it and replace the
- * other shitty functions like editUpdates and such so it's not needed
- * anymore.
- */
- public function createUpdates( $rev ) {
- $this->mGoodAdjustment = $this->isCountable( $rev->getText() );
- $this->mTotalAdjustment = 1;
- $this->editUpdates( $rev->getText(), $rev->getComment(),
- $rev->isMinor(), wfTimestamp(), $rev->getId(), true );
- }
-
- /**
* Generate the navigation links when browsing through an article revisions
* It shows the information as:
* Revision as of \<date\>; view current revision
@@ -3746,16 +1568,17 @@ class Article {
}
$revision = Revision::newFromId( $oldid );
+ $timestamp = $revision->getTimestamp();
+
+ $current = ( $oldid == $this->mPage->getLatest() );
+ $td = $wgLang->timeanddate( $timestamp, true );
+ $tddate = $wgLang->date( $timestamp, true );
+ $tdtime = $wgLang->time( $timestamp, true );
- $current = ( $oldid == $this->mLatest );
- $td = $wgLang->timeanddate( $this->mTimestamp, true );
- $tddate = $wgLang->date( $this->mTimestamp, true );
- $tdtime = $wgLang->time( $this->mTimestamp, true );
- $sk = $wgUser->getSkin();
$lnk = $current
? wfMsgHtml( 'currentrevisionlink' )
- : $sk->link(
- $this->mTitle,
+ : Linker::link(
+ $this->getTitle(),
wfMsgHtml( 'currentrevisionlink' ),
array(),
$extraParams,
@@ -3763,8 +1586,8 @@ class Article {
);
$curdiff = $current
? wfMsgHtml( 'diff' )
- : $sk->link(
- $this->mTitle,
+ : Linker::link(
+ $this->getTitle(),
wfMsgHtml( 'diff' ),
array(),
array(
@@ -3773,10 +1596,10 @@ class Article {
) + $extraParams,
array( 'known', 'noclasses' )
);
- $prev = $this->mTitle->getPreviousRevisionID( $oldid ) ;
+ $prev = $this->getTitle()->getPreviousRevisionID( $oldid ) ;
$prevlink = $prev
- ? $sk->link(
- $this->mTitle,
+ ? Linker::link(
+ $this->getTitle(),
wfMsgHtml( 'previousrevision' ),
array(),
array(
@@ -3787,8 +1610,8 @@ class Article {
)
: wfMsgHtml( 'previousrevision' );
$prevdiff = $prev
- ? $sk->link(
- $this->mTitle,
+ ? Linker::link(
+ $this->getTitle(),
wfMsgHtml( 'diff' ),
array(),
array(
@@ -3800,8 +1623,8 @@ class Article {
: wfMsgHtml( 'diff' );
$nextlink = $current
? wfMsgHtml( 'nextrevision' )
- : $sk->link(
- $this->mTitle,
+ : Linker::link(
+ $this->getTitle(),
wfMsgHtml( 'nextrevision' ),
array(),
array(
@@ -3812,8 +1635,8 @@ class Article {
);
$nextdiff = $current
? wfMsgHtml( 'diff' )
- : $sk->link(
- $this->mTitle,
+ : Linker::link(
+ $this->getTitle(),
wfMsgHtml( 'diff' ),
array(),
array(
@@ -3829,23 +1652,22 @@ class Article {
$canHide = $wgUser->isAllowed( 'deleterevision' );
if ( $canHide || ( $revision->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) ) {
if ( !$revision->userCan( Revision::DELETED_RESTRICTED ) ) {
- $cdel = $sk->revDeleteLinkDisabled( $canHide ); // rev was hidden from Sysops
+ $cdel = Linker::revDeleteLinkDisabled( $canHide ); // rev was hidden from Sysops
} else {
$query = array(
'type' => 'revision',
- 'target' => $this->mTitle->getPrefixedDbkey(),
+ 'target' => $this->getTitle()->getPrefixedDbkey(),
'ids' => $oldid
);
- $cdel = $sk->revDeleteLink( $query, $revision->isDeleted( File::DELETED_RESTRICTED ), $canHide );
+ $cdel = Linker::revDeleteLink( $query, $revision->isDeleted( File::DELETED_RESTRICTED ), $canHide );
}
$cdel .= ' ';
}
# Show user links if allowed to see them. If hidden, then show them only if requested...
- $userlinks = $sk->revUserTools( $revision, !$unhide );
+ $userlinks = Linker::revUserTools( $revision, !$unhide );
- $m = wfMsg( 'revision-info-current' );
- $infomsg = $current && !wfEmptyMsg( 'revision-info-current', $m ) && $m != '-'
+ $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
? 'revision-info-current'
: 'revision-info';
@@ -3867,20 +1689,6 @@ class Article {
$wgOut->setSubtitle( $r );
}
- /**
- * This function is called right before saving the wikitext,
- * so we can do things like signatures and links-in-context.
- *
- * @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 ) );
- }
-
/* Caching functions */
/**
@@ -3900,8 +1708,8 @@ class Article {
$called = true;
if ( $this->isFileCacheable() ) {
- $cache = new HTMLFileCache( $this->mTitle );
- if ( $cache->isFileCacheGood( $this->mTouched ) ) {
+ $cache = new HTMLFileCache( $this->getTitle() );
+ if ( $cache->isFileCacheGood( $this->mPage->getTouched() ) ) {
wfDebug( "Article::tryFileCache(): about to load file\n" );
$cache->loadFromFileCache();
return true;
@@ -3924,7 +1732,7 @@ class Article {
$cacheable = false;
if ( HTMLFileCache::useFileCache() ) {
- $cacheable = $this->getID() && !$this->mRedirectedFrom && !$this->mTitle->isRedirect();
+ $cacheable = $this->mPage->getID() && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
// Extension may have reason to disable file caching on some pages.
if ( $cacheable ) {
$cacheable = wfRunHooks( 'IsFileCacheable', array( &$this ) );
@@ -3934,462 +1742,77 @@ class Article {
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() {
- 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;
- }
-
- /**
- * Edit an article without doing all that other stuff
- * The article must already exist; link tables etc
- * are not updated, caches are not flushed.
- *
- * @param $text String: text submitted
- * @param $comment String: comment submitted
- * @param $minor Boolean: whereas it's a minor modification
- */
- public function quickEdit( $text, $comment = '', $minor = 0 ) {
- wfProfileIn( __METHOD__ );
-
- $dbw = wfGetDB( DB_MASTER );
- $revision = new Revision( array(
- 'page' => $this->getId(),
- 'text' => $text,
- 'comment' => $comment,
- 'minor_edit' => $minor ? 1 : 0,
- ) );
- $revision->insertOn( $dbw );
- $this->updateRevisionOn( $dbw, $revision );
-
- global $wgUser;
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $wgUser ) );
-
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Used to increment the view counter
- *
- * @param $id Integer: article id
- */
- public static function incViewCount( $id ) {
- $id = intval( $id );
-
- global $wgHitcounterUpdateFreq;
-
- $dbw = wfGetDB( DB_MASTER );
- $pageTable = $dbw->tableName( 'page' );
- $hitcounterTable = $dbw->tableName( 'hitcounter' );
- $acchitsTable = $dbw->tableName( 'acchits' );
- $dbType = $dbw->getType();
-
- if ( $wgHitcounterUpdateFreq <= 1 || $dbType == 'sqlite' ) {
- $dbw->query( "UPDATE $pageTable SET page_counter = page_counter + 1 WHERE page_id = $id" );
-
- return;
- }
-
- # Not important enough to warrant an error page in case of failure
- $oldignore = $dbw->ignoreErrors( true );
-
- $dbw->query( "INSERT INTO $hitcounterTable (hc_id) VALUES ({$id})" );
-
- $checkfreq = intval( $wgHitcounterUpdateFreq / 25 + 1 );
- 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 );
-
- $dbw->lockTables( array(), array( 'hitcounter' ), __METHOD__, false );
- $tabletype = $dbType == 'mysql' ? "ENGINE=HEAP " : '';
- $dbw->query( "CREATE TEMPORARY TABLE $acchitsTable $tabletype AS " .
- "SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable " .
- 'GROUP BY hc_id', __METHOD__ );
- $dbw->delete( 'hitcounter', '*', __METHOD__ );
- $dbw->unlockTables( __METHOD__ );
-
- if ( $dbType == 'mysql' ) {
- $dbw->query( "UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n " .
- 'WHERE page_id = hc_id', __METHOD__ );
- } else {
- $dbw->query( "UPDATE $pageTable SET page_counter=page_counter + hc_n " .
- "FROM $acchitsTable WHERE page_id = hc_id", __METHOD__ );
- }
- $dbw->query( "DROP TABLE $acchitsTable", __METHOD__ );
-
- ignore_user_abort( $old_user_abort );
- wfProfileOut( 'Article::incViewCount-collect' );
- }
-
- $dbw->ignoreErrors( $oldignore );
- }
-
- /**#@+
- * The onArticle*() functions are supposed to be a kind of hooks
- * which should be called whenever any of the specified actions
- * are done.
- *
- * This is a good place to put code to clear caches, for instance.
- *
- * This is called on page move and undelete, as well as edit
- *
- * @param $title a title object
- */
- public static function onArticleCreate( $title ) {
- # Update existence markers on article/talk tabs...
- if ( $title->isTalkPage() ) {
- $other = $title->getSubjectPage();
- } else {
- $other = $title->getTalkPage();
- }
-
- $other->invalidateCache();
- $other->purgeSquid();
-
- $title->touchLinks();
- $title->purgeSquid();
- $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();
-
- $title->touchLinks();
- $title->purgeSquid();
-
- # File cache
- HTMLFileCache::clearFileCache( $title );
-
- # Messages
- 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 ) {
- global $wgDeferredUpdateList;
-
- // Invalidate caches of articles which include this page
- $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
-
- // Invalidate the caches of all pages which redirect here
- $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
-
- # Purge squid for this page only
- $title->purgeSquid();
-
- # Clear file cache for this page only
- HTMLFileCache::clearFileCache( $title );
- }
-
/**#@-*/
/**
- * Overriden by ImagePage class, only present here to avoid a fatal error
- * Called for ?action=revert
+ * Add the primary page-view wikitext to the output buffer
+ * Saves the text into the parser cache if possible.
+ * Updates templatelinks if it is out of date.
+ *
+ * @param $text String
+ * @param $cache Boolean
+ * @param $parserOptions mixed ParserOptions object, or boolean false
*/
- public function revert() {
+ public function outputWikiText( $text, $cache = true, $parserOptions = false ) {
global $wgOut;
- $wgOut->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
- }
-
- /**
- * Info about this page
- * Called for ?action=info when $wgAllowPageInfo is on.
- */
- public function info() {
- global $wgLang, $wgOut, $wgAllowPageInfo, $wgUser;
-
- if ( !$wgAllowPageInfo ) {
- $wgOut->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
- return;
- }
-
- $page = $this->mTitle->getSubjectPage();
-
- $wgOut->setPagetitle( $page->getPrefixedText() );
- $wgOut->setPageTitleActionText( wfMsg( 'info_short' ) );
- $wgOut->setSubtitle( wfMsgHtml( 'infosubtitle' ) );
-
- if ( !$this->mTitle->exists() ) {
- $wgOut->addHTML( '<div class="noarticletext">' );
- if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- // This doesn't quite make sense; the user is asking for
- // information about the _page_, not the message... -- RC
- $wgOut->addHTML( htmlspecialchars( wfMsgWeirdKey( $this->mTitle->getText() ) ) );
- } else {
- $msg = $wgUser->isLoggedIn()
- ? 'noarticletext'
- : 'noarticletextanon';
- $wgOut->addHTML( wfMsgExt( $msg, 'parse' ) );
- }
- $wgOut->addHTML( '</div>' );
- } else {
- $dbr = wfGetDB( DB_SLAVE );
- $wl_clause = array(
- 'wl_title' => $page->getDBkey(),
- 'wl_namespace' => $page->getNamespace() );
- $numwatchers = $dbr->selectField(
- 'watchlist',
- 'COUNT(*)',
- $wl_clause,
- __METHOD__,
- $this->getSelectOptions() );
-
- $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>' );
+ $this->mParserOutput = $this->getOutputFromWikitext( $text, $cache, $parserOptions );
- if ( $talkInfo ) {
- $wgOut->addHTML( '<li>' . wfMsg( 'numtalkauthors', $wgLang->formatNum( $talkInfo['authors'] ) ) . '</li>' );
- }
+ $this->doCascadeProtectionUpdates( $this->mParserOutput );
- $wgOut->addHTML( '</ul>' );
- }
+ $wgOut->addParserOutput( $this->mParserOutput );
}
/**
- * Return the total number of edits and number of unique editors
- * on a given page. If page does not exist, returns false.
+ * 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 WikiPage::view is forced to
+ * consider, so it's not appropriate to use there.
*
- * @param $title Title object
- * @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(
- 'revision',
- 'COUNT(rev_page)',
- $rev_clause,
- __METHOD__,
- $this->getSelectOptions()
- );
- $authors = $dbr->selectField(
- 'revision',
- 'COUNT(DISTINCT rev_user_text)',
- $rev_clause,
- __METHOD__,
- $this->getSelectOptions()
- );
-
- return array( 'edits' => $edits, 'authors' => $authors );
- }
-
- /**
- * Return a list of templates used by this article.
- * Uses the templatelinks table
+ * @since 1.16 (r52326) for LiquidThreads
*
- * @return Array of Title objects
+ * @param $oldid mixed integer Revision ID or null
+ * @param $user User The relevant user
+ * @return ParserOutput or false if the given revsion ID is not found
*/
- 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 );
- }
- }
+ public function getParserOutput( $oldid = null, User $user = null ) {
+ global $wgEnableParserCache, $wgUser;
+ $user = is_null( $user ) ? $wgUser : $user;
- return $result;
- }
+ wfProfileIn( __METHOD__ );
+ // Should the parser cache be used?
+ $useParserCache = $wgEnableParserCache &&
+ $user->getStubThreshold() == 0 &&
+ $this->mPage->exists() &&
+ $oldid === null;
- /**
- * Returns a list of hidden categories this page is a member of.
- * Uses the page_props and categorylinks tables.
- *
- * @return Array of Title objects
- */
- public function getHiddenCategories() {
- $result = array();
- $id = $this->mTitle->getArticleID();
+ wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
- if ( $id == 0 ) {
- return array();
+ if ( $user->getStubThreshold() ) {
+ wfIncrStats( 'pcache_miss_stub' );
}
- $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 );
+ if ( $useParserCache ) {
+ $parserOutput = ParserCache::singleton()->get( $this, $this->mPage->getParserOptions() );
+ if ( $parserOutput !== false ) {
+ wfProfileOut( __METHOD__ );
+ return $parserOutput;
}
}
- return $result;
- }
-
- /**
- * Return an applicable autosummary if one exists for the given edit.
- * @param $oldtext String: the previous text of the page.
- * @param $newtext String: The submitted text of the page.
- * @param $flags Bitmask: a bitmask of flags submitted for the edit.
- * @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() );
- }
-
- # New page autosummaries
- if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
- # If they're making a new article, give its text, truncated, in the summary.
-
- $truncatedtext = $wgContLang->truncate(
- str_replace( "\n", ' ', $newtext ),
- max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) );
-
- return wfMsgForContent( 'autosumm-new', $truncatedtext );
- }
-
- # Blanking autosummaries
- if ( $oldtext != '' && $newtext == '' ) {
- return wfMsgForContent( 'autosumm-blank' );
- } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
- # Removing more than 90% of the article
-
- $truncatedtext = $wgContLang->truncate(
- $newtext,
- max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) );
-
- return wfMsgForContent( 'autosumm-replace', $truncatedtext );
+ // Cache miss; parse and output it.
+ if ( $oldid === null ) {
+ $text = $this->mPage->getRawText();
+ } else {
+ $rev = Revision::newFromTitle( $this->getTitle(), $oldid );
+ if ( $rev === null ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $text = $rev->getText();
}
- # If we reach this point, there's no applicable autosummary for our case, so our
- # autosummary is empty.
- return '';
- }
-
- /**
- * Add the primary page-view wikitext to the output buffer
- * Saves the text into the parser cache if possible.
- * Updates templatelinks if it is out of date.
- *
- * @param $text String
- * @param $cache Boolean
- * @param $parserOptions mixed ParserOptions object, or boolean false
- */
- public function outputWikiText( $text, $cache = true, $parserOptions = false ) {
- global $wgOut;
-
- $this->mParserOutput = $this->getOutputFromWikitext( $text, $cache, $parserOptions );
- $wgOut->addParserOutput( $this->mParserOutput );
+ wfProfileOut( __METHOD__ );
+ return $this->getOutputFromWikitext( $text, $useParserCache );
}
/**
@@ -4400,24 +1823,24 @@ class Article {
* @param $text string
* @param $cache boolean
* @param $parserOptions parsing options, defaults to false
- * @return string containing parsed output
+ * @return ParserOutput
*/
public function getOutputFromWikitext( $text, $cache = true, $parserOptions = false ) {
global $wgParser, $wgEnableParserCache, $wgUseFileCache;
if ( !$parserOptions ) {
- $parserOptions = $this->getParserOptions();
+ $parserOptions = $this->mPage->getParserOptions();
}
$time = - wfTime();
- $this->mParserOutput = $wgParser->parse( $text, $this->mTitle,
+ $this->mParserOutput = $wgParser->parse( $text, $this->getTitle(),
$parserOptions, true, true, $this->getRevIdFetched() );
$time += wfTime();
# Timing hack
if ( $time > 3 ) {
wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
- $this->mTitle->getPrefixedDBkey() ) );
+ $this->getTitle()->getPrefixedDBkey() ) );
}
if ( $wgEnableParserCache && $cache && $this->mParserOutput->isCacheable() ) {
@@ -4432,213 +1855,156 @@ class Article {
$wgUseFileCache = false;
}
- $this->doCascadeProtectionUpdates( $this->mParserOutput );
+ if ( $this->isCurrent() ) {
+ $this->mPage->doCascadeProtectionUpdates( $this->mParserOutput );
+ }
return $this->mParserOutput;
}
/**
- * Get parser options suitable for rendering the primary article wikitext
- * @return mixed ParserOptions object or boolean false
+ * Sets the context this Article is executed in
+ *
+ * @param $context IContextSource
+ * @since 1.18
*/
- public function getParserOptions() {
- global $wgUser;
-
- if ( !$this->mParserOptions ) {
- $this->mParserOptions = new ParserOptions( $wgUser );
- $this->mParserOptions->setTidy( true );
- $this->mParserOptions->enableLimitReport();
- }
-
- // Clone to allow modifications of the return value without affecting
- // the cache
- return clone $this->mParserOptions;
+ public function setContext( $context ) {
+ $this->mContext = $context;
}
/**
- * Updates cascading protections
+ * Gets the context this Article is executed in
*
- * @param $parserOutput mixed ParserOptions object, or boolean false
- **/
- protected function doCascadeProtectionUpdates( $parserOutput ) {
- if ( !$this->isCurrent() || wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
- return;
- }
-
- // templatelinks table may have become out of sync,
- // especially if using variable-based transclusions.
- // For paranoia, check if things have changed and if
- // so apply updates to the database. This will ensure
- // that cascaded protections apply as soon as the changes
- // are visible.
-
- # Get templates from templatelinks
- $id = $this->mTitle->getArticleID();
-
- $tlTemplates = array();
-
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( array( 'templatelinks' ),
- array( 'tl_namespace', 'tl_title' ),
- array( 'tl_from' => $id ),
- __METHOD__
- );
-
- foreach ( $res as $row ) {
- $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
- }
-
- # Get templates from parser output.
- $poTemplates = array();
- foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
- foreach ( $templates as $dbk => $id ) {
- $poTemplates["$ns:$dbk"] = true;
- }
- }
-
- # Get the diff
- $templates_diff = array_diff_key( $poTemplates, $tlTemplates );
-
- if ( count( $templates_diff ) > 0 ) {
- # Whee, link updates time.
- $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
- $u->doUpdate();
+ * @return IContextSource
+ * @since 1.18
+ */
+ public function getContext() {
+ if ( $this->mContext instanceof IContextSource ) {
+ return $this->mContext;
+ } else {
+ wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
+ return RequestContext::getMain();
}
}
/**
- * Update all the appropriate counts in the category table, given that
- * we've added the categories $added and deleted the categories $deleted.
+ * Use PHP's magic __get handler to handle accessing of
+ * raw WikiPage fields for backwards compatibility.
*
- * @param $added array The names of categories that were added
- * @param $deleted array The names of categories that were deleted
+ * @param $fname String Field name
*/
- public function updateCategoryCounts( $added, $deleted ) {
- $ns = $this->mTitle->getNamespace();
- $dbw = wfGetDB( DB_MASTER );
-
- # First make sure the rows exist. If one of the "deleted" ones didn't
- # exist, we might legitimately not create it, but it's simpler to just
- # create it and then give it a negative value, since the value is bogus
- # anyway.
- #
- # Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE.
- $insertCats = array_merge( $added, $deleted );
- if ( !$insertCats ) {
- # Okay, nothing to do
- return;
- }
-
- $insertRows = array();
-
- foreach ( $insertCats as $cat ) {
- $insertRows[] = array(
- 'cat_id' => $dbw->nextSequenceValue( 'category_cat_id_seq' ),
- 'cat_title' => $cat
- );
- }
- $dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' );
-
- $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';
- } elseif ( $ns == NS_FILE ) {
- $addFields[] = 'cat_files = cat_files + 1';
- $removeFields[] = 'cat_files = cat_files - 1';
- }
-
- if ( $added ) {
- $dbw->update(
- 'category',
- $addFields,
- array( 'cat_title' => $added ),
- __METHOD__
- );
+ public function __get( $fname ) {
+ if ( property_exists( $this->mPage, $fname ) ) {
+ #wfWarn( "Access to raw $fname field " . __CLASS__ );
+ return $this->mPage->$fname;
}
+ trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
+ }
- if ( $deleted ) {
- $dbw->update(
- 'category',
- $removeFields,
- array( 'cat_title' => $deleted ),
- __METHOD__
- );
+ /**
+ * Use PHP's magic __set handler to handle setting of
+ * raw WikiPage fields for backwards compatibility.
+ *
+ * @param $fname String Field name
+ * @param $fvalue mixed New value
+ * @param $args Array Arguments to the method
+ */
+ public function __set( $fname, $fvalue ) {
+ if ( property_exists( $this->mPage, $fname ) ) {
+ #wfWarn( "Access to raw $fname field of " . __CLASS__ );
+ $this->mPage->$fname = $fvalue;
+ // Note: extensions may want to toss on new fields
+ } elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) {
+ $this->mPage->$fname = $fvalue;
+ } else {
+ trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
}
}
/**
- * 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.
+ * Use PHP's magic __call handler to transform instance calls to
+ * WikiPage functions for backwards compatibility.
*
- * @since 1.16 (r52326) for LiquidThreads
- *
- * @param $oldid mixed integer Revision ID or null
+ * @param $fname String Name of called method
+ * @param $args Array Arguments to the method
*/
- public function getParserOutput( $oldid = null ) {
- global $wgEnableParserCache, $wgUser;
+ public function __call( $fname, $args ) {
+ if ( is_callable( array( $this->mPage, $fname ) ) ) {
+ #wfWarn( "Call to " . __CLASS__ . "::$fname; please use WikiPage instead" );
+ return call_user_func_array( array( $this->mPage, $fname ), $args );
+ }
+ trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR );
+ }
- // Should the parser cache be used?
- $useParserCache = $wgEnableParserCache &&
- $wgUser->getStubThreshold() == 0 &&
- $this->exists() &&
- $oldid === null;
+ // ****** B/C functions to work-around PHP silliness with __call and references ****** //
+ public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
+ return $this->mPage->updateRestrictions( $limit, $reason, $cascade, $expiry );
+ }
- wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
+ public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) {
+ return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error );
+ }
- if ( $wgUser->getStubThreshold() ) {
- wfIncrStats( 'pcache_miss_stub' );
- }
+ public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
+ global $wgUser;
+ $user = is_null( $user ) ? $wgUser : $user;
+ return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
+ }
- $parserOutput = false;
- if ( $useParserCache ) {
- $parserOutput = ParserCache::singleton()->get( $this, $this->getParserOptions() );
- }
+ public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
+ global $wgUser;
+ $guser = is_null( $guser ) ? $wgUser : $guser;
+ return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
+ }
- if ( $parserOutput === false ) {
- // Cache miss; parse and output it.
- $rev = Revision::newFromTitle( $this->getTitle(), $oldid );
+ public function generateReason( &$hasHistory ) {
+ return $this->mPage->getAutoDeleteReason( $hasHistory );
+ }
- return $this->getOutputFromWikitext( $rev->getText(), $useParserCache );
- } else {
- return $parserOutput;
- }
+ // ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
+ public static function selectFields() {
+ return WikiPage::selectFields();
}
- // 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 );
+ public static function onArticleCreate( $title ) {
+ return WikiPage::onArticleCreate( $title );
+ }
+
+ public static function onArticleDelete( $title ) {
+ return WikiPage::onArticleDelete( $title );
+ }
+
+ public static function onArticleEdit( $title ) {
+ return WikiPage::onArticleEdit( $title );
}
+ public static function getAutosummary( $oldtext, $newtext, $flags ) {
+ return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
+ }
+ // ******
}
class PoolWorkArticleView extends PoolCounterWork {
+
+ /**
+ * @var Article
+ */
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 );
@@ -4652,21 +2018,24 @@ class PoolWorkArticleView extends PoolCounterWork {
}
return false;
}
-
+
function fallback() {
return $this->mArticle->tryDirtyCache();
}
-
+
+ /**
+ * @param $status Status
+ */
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 7dc99259..eebb52d6 100644
--- a/includes/AuthPlugin.php
+++ b/includes/AuthPlugin.php
@@ -131,6 +131,8 @@ class AuthPlugin {
* and use the same keys. 'Realname' 'Emailaddress' and 'Nickname'
* all reference this.
*
+ * @param $prop string
+ *
* @return Boolean
*/
public function allowPropChange( $prop = '' ) {
@@ -254,10 +256,21 @@ class AuthPlugin {
* Get an instance of a User object
*
* @param $user User
+ *
+ * @return AuthPluginUser
*/
public function getUserInstance( User &$user ) {
return new AuthPluginUser( $user );
}
+
+ /**
+ * Get a list of domains (in HTMLForm options format) used.
+ *
+ * @return array
+ */
+ public function domainList() {
+ return array();
+ }
}
class AuthPluginUser {
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 347ed694..134e53ea 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -14,20 +14,18 @@ global $wgAutoloadLocalClasses;
$wgAutoloadLocalClasses = array(
# Includes
+ 'Action' => 'includes/Action.php',
'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
'AjaxResponse' => 'includes/AjaxResponse.php',
'AlphabeticPager' => 'includes/Pager.php',
- 'APCBagOStuff' => 'includes/BagOStuff.php',
'Article' => 'includes/Article.php',
'AtomFeed' => 'includes/Feed.php',
'AuthPlugin' => 'includes/AuthPlugin.php',
'AuthPluginUser' => 'includes/AuthPlugin.php',
'Autopromote' => 'includes/Autopromote.php',
'BacklinkCache' => 'includes/BacklinkCache.php',
- 'BagOStuff' => 'includes/BagOStuff.php',
+ 'BaseTemplate' => 'includes/SkinTemplate.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',
@@ -39,28 +37,22 @@ $wgAutoloadLocalClasses = array(
'CdbWriter' => 'includes/Cdb.php',
'CdbWriter_DBA' => 'includes/Cdb.php',
'CdbWriter_PHP' => 'includes/Cdb_PHP.php',
- 'ChangesList' => 'includes/ChangesList.php',
'ChangesFeed' => 'includes/ChangesFeed.php',
+ 'ChangesList' => 'includes/ChangesList.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',
'ConfEditor' => 'includes/ConfEditor.php',
'ConfEditorParseError' => 'includes/ConfEditor.php',
'ConfEditorToken' => 'includes/ConfEditor.php',
- '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',
+ 'ContextSource' => 'includes/RequestContext.php',
+ 'Cookie' => 'includes/Cookie.php',
+ 'CookieJar' => 'includes/Cookie.php',
'DiffHistoryBlob' => 'includes/HistoryBlob.php',
'DjVuImage' => 'includes/DjVuImage.php',
'DoubleReplacer' => 'includes/StringUtils.php',
- 'DublinCoreRdf' => 'includes/Metadata.php',
+ 'DummyLinker' => 'includes/Linker.php',
'Dump7ZipOutput' => 'includes/Export.php',
'DumpBZip2Output' => 'includes/Export.php',
'DumpFileOutput' => 'includes/Export.php',
@@ -72,145 +64,133 @@ $wgAutoloadLocalClasses = array(
'DumpNotalkFilter' => 'includes/Export.php',
'DumpOutput' => 'includes/Export.php',
'DumpPipeOutput' => 'includes/Export.php',
- 'eAccelBagOStuff' => 'includes/BagOStuff.php',
'EditPage' => 'includes/EditPage.php',
'EmailNotification' => 'includes/UserMailer.php',
'EnhancedChangesList' => 'includes/ChangesList.php',
'ErrorPageError' => 'includes/Exception.php',
- 'Exif' => 'includes/Exif.php',
'ExplodeIterator' => 'includes/StringUtils.php',
'ExternalEdit' => 'includes/ExternalEdit.php',
+ 'ExternalStore' => 'includes/ExternalStore.php',
'ExternalStoreDB' => 'includes/ExternalStoreDB.php',
'ExternalStoreHttp' => 'includes/ExternalStoreHttp.php',
- 'ExternalStore' => 'includes/ExternalStore.php',
'ExternalUser' => 'includes/ExternalUser.php',
- 'FatalError' => 'includes/Exception.php',
'FakeTitle' => 'includes/FakeTitle.php',
- 'FakeMemCachedClient' => 'includes/ObjectCache.php',
+ 'Fallback' => 'includes/Fallback.php',
+ 'FatalError' => 'includes/Exception.php',
'FauxRequest' => 'includes/WebRequest.php',
'FauxResponse' => 'includes/WebResponse.php',
'FeedItem' => 'includes/Feed.php',
'FeedUtils' => 'includes/FeedUtils.php',
'FileDeleteForm' => 'includes/FileDeleteForm.php',
- 'FileDependency' => 'includes/CacheDependency.php',
- 'FileRevertForm' => 'includes/FileRevertForm.php',
'ForkController' => 'includes/ForkController.php',
- 'FormatExif' => 'includes/Exif.php',
+ 'FormlessAction' => 'includes/Action.php',
+ 'FormAction' => 'includes/Action.php',
'FormOptions' => 'includes/FormOptions.php',
- 'GlobalDependency' => 'includes/CacheDependency.php',
- 'HashBagOStuff' => 'includes/BagOStuff.php',
+ 'FormSpecialPage' => 'includes/SpecialPage.php',
+ 'GenderCache' => 'includes/GenderCache.php',
'HashtableReplacer' => 'includes/StringUtils.php',
- 'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
'HistoryBlob' => 'includes/HistoryBlob.php',
+ 'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
'HistoryBlobStub' => 'includes/HistoryBlob.php',
'HistoryPage' => 'includes/HistoryPage.php',
'HistoryPager' => 'includes/HistoryPage.php',
+ 'Hooks' => 'includes/Hooks.php',
'Html' => 'includes/Html.php',
- 'HTMLCacheUpdate' => 'includes/HTMLCacheUpdate.php',
- 'HTMLCacheUpdateJob' => 'includes/HTMLCacheUpdate.php',
- 'HTMLFileCache' => 'includes/HTMLFileCache.php',
+ 'HTMLCheckField' => 'includes/HTMLForm.php',
+ 'HTMLEditTools' => 'includes/HTMLForm.php',
+ 'HTMLFloatField' => 'includes/HTMLForm.php',
'HTMLForm' => 'includes/HTMLForm.php',
'HTMLFormField' => 'includes/HTMLForm.php',
- 'HTMLTextField' => 'includes/HTMLForm.php',
- 'HTMLIntField' => 'includes/HTMLForm.php',
- 'HTMLTextAreaField' => 'includes/HTMLForm.php',
- 'HTMLFloatField' => 'includes/HTMLForm.php',
'HTMLHiddenField' => 'includes/HTMLForm.php',
- 'HTMLSubmitField' => 'includes/HTMLForm.php',
- 'HTMLEditTools' => 'includes/HTMLForm.php',
- 'HTMLCheckField' => 'includes/HTMLForm.php',
- 'HTMLSelectField' => 'includes/HTMLForm.php',
- 'HTMLSelectOrOtherField' => 'includes/HTMLForm.php',
+ 'HTMLInfoField' => 'includes/HTMLForm.php',
+ 'HTMLIntField' => 'includes/HTMLForm.php',
'HTMLMultiSelectField' => 'includes/HTMLForm.php',
'HTMLRadioField' => 'includes/HTMLForm.php',
- 'HTMLInfoField' => 'includes/HTMLForm.php',
+ 'HTMLSelectAndOtherField' => 'includes/HTMLForm.php',
+ 'HTMLSelectField' => 'includes/HTMLForm.php',
+ 'HTMLSelectOrOtherField' => 'includes/HTMLForm.php',
+ 'HTMLSubmitField' => 'includes/HTMLForm.php',
+ 'HTMLTextAreaField' => 'includes/HTMLForm.php',
+ 'HTMLTextField' => 'includes/HTMLForm.php',
'Http' => 'includes/HttpFunctions.php',
'HttpRequest' => 'includes/HttpFunctions.old.php',
+ 'IContextSource' => 'includes/RequestContext.php',
'IcuCollation' => 'includes/Collation.php',
+ 'IdentityCollation' => 'includes/Collation.php',
'ImageGallery' => 'includes/ImageGallery.php',
'ImageHistoryList' => 'includes/ImagePage.php',
'ImageHistoryPseudoPager' => 'includes/ImagePage.php',
'ImagePage' => 'includes/ImagePage.php',
'ImageQueryPage' => 'includes/ImageQueryPage.php',
+ 'ImportStreamSource' => 'includes/Import.php',
+ 'ImportStringSource' => 'includes/Import.php',
'IncludableSpecialPage' => 'includes/SpecialPage.php',
'IndexPager' => 'includes/Pager.php',
- 'Interwiki' => 'includes/Interwiki.php',
+ 'Interwiki' => 'includes/interwiki/Interwiki.php',
'IP' => 'includes/IP.php',
- 'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php',
- 'LCStore_DB' => 'includes/LocalisationCache.php',
'LCStore_CDB' => 'includes/LocalisationCache.php',
+ 'LCStore_DB' => 'includes/LocalisationCache.php',
'LCStore_Null' => 'includes/LocalisationCache.php',
+ 'LegacyTemplate' => 'includes/SkinLegacy.php',
'License' => 'includes/Licenses.php',
'Licenses' => 'includes/Licenses.php',
- 'LinkBatch' => 'includes/LinkBatch.php',
- 'LinkCache' => 'includes/LinkCache.php',
'Linker' => 'includes/Linker.php',
'LinkFilter' => 'includes/LinkFilter.php',
'LinksUpdate' => 'includes/LinksUpdate.php',
'LocalisationCache' => 'includes/LocalisationCache.php',
'LocalisationCache_BulkLoad' => 'includes/LocalisationCache.php',
+ 'LogEventsList' => 'includes/LogEventsList.php',
'LogPage' => 'includes/LogPage.php',
'LogPager' => 'includes/LogEventsList.php',
- 'LogEventsList' => 'includes/LogEventsList.php',
- 'LogReader' => 'includes/LogEventsList.php',
- 'LogViewer' => 'includes/LogEventsList.php',
- 'MacBinary' => 'includes/MacBinary.php',
- 'MagicWordArray' => 'includes/MagicWord.php',
'MagicWord' => 'includes/MagicWord.php',
+ 'MagicWordArray' => 'includes/MagicWord.php',
'MailAddress' => 'includes/UserMailer.php',
- 'MathRenderer' => 'includes/Math.php',
- 'MediaWikiBagOStuff' => 'includes/BagOStuff.php',
- 'MediaWiki_I18N' => 'includes/SkinTemplate.php',
'MediaWiki' => 'includes/Wiki.php',
- 'MemCachedClientforWiki' => 'includes/memcached-client.php',
+ 'MediaWiki_I18N' => 'includes/SkinTemplate.php',
'Message' => 'includes/Message.php',
'MessageBlobStore' => 'includes/MessageBlobStore.php',
- 'MessageCache' => 'includes/MessageCache.php',
'MimeMagic' => 'includes/MimeMagic.php',
'MWException' => 'includes/Exception.php',
+ 'MWExceptionHandler' => 'includes/Exception.php',
+ 'MWFunction' => 'includes/MWFunction.php',
'MWHttpRequest' => 'includes/HttpFunctions.php',
- 'MWMemcached' => 'includes/memcached-client.php',
+ 'MWInit' => 'includes/Init.php',
'MWNamespace' => 'includes/Namespace.php',
'OldChangesList' => 'includes/ChangesList.php',
'OutputPage' => 'includes/OutputPage.php',
- 'PageQueryPage' => 'includes/PageQueryPage.php',
+ 'Page' => 'includes/WikiPage.php',
'PageHistory' => 'includes/HistoryPage.php',
'PageHistoryPager' => 'includes/HistoryPage.php',
+ 'PageQueryPage' => 'includes/PageQueryPage.php',
'Pager' => 'includes/Pager.php',
'PasswordError' => 'includes/User.php',
'PatrolLog' => 'includes/PatrolLog.php',
+ 'PermissionsError' => 'includes/Exception.php',
'PhpHttpRequest' => 'includes/HttpFunctions.php',
'PoolCounter' => 'includes/PoolCounter.php',
'PoolCounter_Stub' => 'includes/PoolCounter.php',
'PoolCounterWork' => 'includes/PoolCounter.php',
'Preferences' => 'includes/Preferences.php',
+ 'PreferencesForm' => 'includes/Preferences.php',
'PrefixSearch' => 'includes/PrefixSearch.php',
- 'Profiler' => 'includes/Profiler.php',
- 'ProfilerSimple' => 'includes/ProfilerSimple.php',
- 'ProfilerSimpleText' => 'includes/ProfilerSimpleText.php',
- 'ProfilerSimpleUDP' => 'includes/ProfilerSimpleUDP.php',
'ProtectionForm' => 'includes/ProtectionForm.php',
'QueryPage' => 'includes/QueryPage.php',
'QuickTemplate' => 'includes/SkinTemplate.php',
'RawPage' => 'includes/RawPage.php',
'RCCacheEntry' => 'includes/ChangesList.php',
'RdfMetaData' => 'includes/Metadata.php',
+ 'ReadOnlyError' => 'includes/Exception.php',
'RecentChange' => 'includes/RecentChange.php',
+ 'RedirectSpecialPage' => 'includes/SpecialPage.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',
+ 'RequestContext' => 'includes/RequestContext.php',
'ReverseChronologicalPager' => 'includes/Pager.php',
+ 'Rev_Item' => 'includes/RevisionList.php',
+ 'Rev_List' => 'includes/RevisionList.php',
'Revision' => 'includes/Revision.php',
- 'RevisionDelete' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevisionList' => 'includes/RevisionList.php',
'RSSFeed' => 'includes/Feed.php',
'Sanitizer' => 'includes/Sanitizer.php',
'SiteConfiguration' => 'includes/SiteConfiguration.php',
@@ -218,64 +198,88 @@ $wgAutoloadLocalClasses = array(
'SiteStatsInit' => 'includes/SiteStats.php',
'SiteStatsUpdate' => 'includes/SiteStats.php',
'Skin' => 'includes/Skin.php',
+ 'SkinLegacy' => 'includes/SkinLegacy.php',
'SkinTemplate' => 'includes/SkinTemplate.php',
+ 'SpecialCreateAccount' => 'includes/SpecialPage.php',
+ 'SpecialListAdmins' => 'includes/SpecialPage.php',
+ 'SpecialListBots' => 'includes/SpecialPage.php',
'SpecialMycontributions' => 'includes/SpecialPage.php',
'SpecialMypage' => 'includes/SpecialPage.php',
'SpecialMytalk' => 'includes/SpecialPage.php',
+ 'SpecialMyuploads' => 'includes/SpecialPage.php',
'SpecialPage' => 'includes/SpecialPage.php',
+ 'SpecialPageFactory' => 'includes/SpecialPageFactory.php',
'SpecialRedirectToSpecial' => 'includes/SpecialPage.php',
- 'SqlBagOStuff' => 'includes/BagOStuff.php',
- 'SquidUpdate' => 'includes/SquidUpdate.php',
'SquidPurgeClient' => 'includes/SquidPurgeClient.php',
'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php',
'Status' => 'includes/Status.php',
+ 'StringUtils' => 'includes/StringUtils.php',
'StubContLang' => 'includes/StubObject.php',
- 'StubUserLang' => 'includes/StubObject.php',
'StubObject' => 'includes/StubObject.php',
- 'StringUtils' => 'includes/StringUtils.php',
+ 'StubUserLang' => 'includes/StubObject.php',
'TablePager' => 'includes/Pager.php',
- 'TitleDependency' => 'includes/CacheDependency.php',
'Title' => 'includes/Title.php',
'TitleArray' => 'includes/TitleArray.php',
'TitleArrayFromResult' => 'includes/TitleArray.php',
- 'TitleListDependency' => 'includes/CacheDependency.php',
+ 'ThrottledError' => 'includes/Exception.php',
'UnlistedSpecialPage' => 'includes/SpecialPage.php',
'UppercaseCollation' => 'includes/Collation.php',
'User' => 'includes/User.php',
'UserArray' => 'includes/UserArray.php',
'UserArrayFromResult' => 'includes/UserArray.php',
+ 'UserBlockedError' => 'includes/Exception.php',
'UserMailer' => 'includes/UserMailer.php',
'UserRightsProxy' => 'includes/UserRightsProxy.php',
+ 'ViewCountUpdate' => 'includes/ViewCountUpdate.php',
'WantedQueryPage' => 'includes/QueryPage.php',
'WatchedItem' => 'includes/WatchedItem.php',
- 'WatchlistEditor' => 'includes/WatchlistEditor.php',
'WebRequest' => 'includes/WebRequest.php',
'WebRequestUpload' => 'includes/WebRequest.php',
'WebResponse' => 'includes/WebResponse.php',
+ 'WikiCategoryPage' => 'includes/WikiCategoryPage.php',
'WikiError' => 'includes/WikiError.php',
'WikiErrorMsg' => 'includes/WikiError.php',
'WikiExporter' => 'includes/Export.php',
+ 'WikiFilePage' => 'includes/WikiFilePage.php',
+ 'WikiImporter' => 'includes/Import.php',
+ 'WikiPage' => 'includes/WikiPage.php',
+ 'WikiRevision' => 'includes/Import.php',
'WikiMap' => 'includes/WikiMap.php',
'WikiReference' => 'includes/WikiMap.php',
'WikiXmlError' => 'includes/WikiError.php',
- 'WinCacheBagOStuff' => 'includes/BagOStuff.php',
- 'XCacheBagOStuff' => 'includes/BagOStuff.php',
- 'XmlDumpWriter' => 'includes/Export.php',
'Xml' => 'includes/Xml.php',
+ 'XmlDumpWriter' => 'includes/Export.php',
'XmlJsCode' => 'includes/Xml.php',
'XmlSelect' => 'includes/Xml.php',
'XmlTypeCheck' => 'includes/XmlTypeCheck.php',
'ZhClient' => 'includes/ZhClient.php',
+ 'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
+
+ # includes/actions
+ 'CreditsAction' => 'includes/actions/CreditsAction.php',
+ 'DeletetrackbackAction' => 'includes/actions/DeletetrackbackAction.php',
+ 'InfoAction' => 'includes/actions/InfoAction.php',
+ 'MarkpatrolledAction' => 'includes/actions/MarkpatrolledAction.php',
+ 'PurgeAction' => 'includes/actions/PurgeAction.php',
+ 'RevertAction' => 'includes/actions/RevertAction.php',
+ 'RevertFileAction' => 'includes/actions/RevertAction.php',
+ 'RevisiondeleteAction' => 'includes/actions/RevisiondeleteAction.php',
+ 'RollbackAction' => 'includes/actions/RollbackAction.php',
+ 'UnwatchAction' => 'includes/actions/WatchAction.php',
+ 'WatchAction' => 'includes/actions/WatchAction.php',
# includes/api
'ApiBase' => 'includes/api/ApiBase.php',
'ApiBlock' => 'includes/api/ApiBlock.php',
+ 'ApiComparePages' => 'includes/api/ApiComparePages.php',
'ApiDelete' => 'includes/api/ApiDelete.php',
'ApiDisabled' => 'includes/api/ApiDisabled.php',
'ApiEditPage' => 'includes/api/ApiEditPage.php',
'ApiEmailUser' => 'includes/api/ApiEmailUser.php',
'ApiExpandTemplates' => 'includes/api/ApiExpandTemplates.php',
+ 'ApiFeedContributions' => 'includes/api/ApiFeedContributions.php',
'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php',
+ 'ApiFileRevert' => 'includes/api/ApiFileRevert.php',
'ApiFormatBase' => 'includes/api/ApiFormatBase.php',
'ApiFormatDbg' => 'includes/api/ApiFormatDbg.php',
'ApiFormatDump' => 'includes/api/ApiFormatDump.php',
@@ -286,6 +290,7 @@ $wgAutoloadLocalClasses = array(
'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php',
'ApiFormatWddx' => 'includes/api/ApiFormatWddx.php',
'ApiFormatXml' => 'includes/api/ApiFormatXml.php',
+ 'ApiFormatXmlRsd' => 'includes/api/ApiRsd.php',
'ApiFormatYaml' => 'includes/api/ApiFormatYaml.php',
'ApiHelp' => 'includes/api/ApiHelp.php',
'ApiImport' => 'includes/api/ApiImport.php',
@@ -301,14 +306,13 @@ $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',
'ApiQueryAllLinks' => 'includes/api/ApiQueryAllLinks.php',
- 'ApiQueryAllUsers' => 'includes/api/ApiQueryAllUsers.php',
'ApiQueryAllmessages' => 'includes/api/ApiQueryAllmessages.php',
'ApiQueryAllpages' => 'includes/api/ApiQueryAllpages.php',
+ 'ApiQueryAllUsers' => 'includes/api/ApiQueryAllUsers.php',
'ApiQueryBacklinks' => 'includes/api/ApiQueryBacklinks.php',
'ApiQueryBase' => 'includes/api/ApiQueryBase.php',
'ApiQueryBlocks' => 'includes/api/ApiQueryBlocks.php',
@@ -319,20 +323,22 @@ $wgAutoloadLocalClasses = array(
'ApiQueryDeletedrevs' => 'includes/api/ApiQueryDeletedrevs.php',
'ApiQueryDisabled' => 'includes/api/ApiQueryDisabled.php',
'ApiQueryDuplicateFiles' => 'includes/api/ApiQueryDuplicateFiles.php',
+ 'ApiQueryExternalLinks' => 'includes/api/ApiQueryExternalLinks.php',
'ApiQueryExtLinksUsage' => 'includes/api/ApiQueryExtLinksUsage.php',
'ApiQueryFilearchive' => 'includes/api/ApiQueryFilearchive.php',
- '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',
+ 'ApiQueryIWLinks' => 'includes/api/ApiQueryIWLinks.php',
+ 'ApiQueryLangBacklinks' => 'includes/api/ApiQueryLangBacklinks.php',
'ApiQueryLangLinks' => 'includes/api/ApiQueryLangLinks.php',
'ApiQueryLinks' => 'includes/api/ApiQueryLinks.php',
'ApiQueryLogEvents' => 'includes/api/ApiQueryLogEvents.php',
'ApiQueryPageProps' => 'includes/api/ApiQueryPageProps.php',
'ApiQueryProtectedTitles' => 'includes/api/ApiQueryProtectedTitles.php',
+ 'ApiQueryQueryPage' => 'includes/api/ApiQueryQueryPage.php',
'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php',
'ApiQueryRecentChanges' => 'includes/api/ApiQueryRecentChanges.php',
'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php',
@@ -346,29 +352,37 @@ $wgAutoloadLocalClasses = array(
'ApiQueryWatchlistRaw' => 'includes/api/ApiQueryWatchlistRaw.php',
'ApiResult' => 'includes/api/ApiResult.php',
'ApiRollback' => 'includes/api/ApiRollback.php',
+ 'ApiRsd' => 'includes/api/ApiRsd.php',
'ApiUnblock' => 'includes/api/ApiUnblock.php',
'ApiUndelete' => 'includes/api/ApiUndelete.php',
- 'ApiUserrights' => 'includes/api/ApiUserrights.php',
'ApiUpload' => 'includes/api/ApiUpload.php',
+ 'ApiUserrights' => 'includes/api/ApiUserrights.php',
'ApiWatch' => 'includes/api/ApiWatch.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',
- 'FormatJson' => 'includes/json/FormatJson.php',
+ # includes/cache
+ 'CacheDependency' => 'includes/cache/CacheDependency.php',
+ 'ConstantDependency' => 'includes/cache/CacheDependency.php',
+ 'DependencyWrapper' => 'includes/cache/CacheDependency.php',
+ 'FileDependency' => 'includes/cache/CacheDependency.php',
+ 'GlobalDependency' => 'includes/cache/CacheDependency.php',
+ 'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php',
+ 'HTMLCacheUpdateJob' => 'includes/cache/HTMLCacheUpdate.php',
+ 'HTMLFileCache' => 'includes/cache/HTMLFileCache.php',
+ 'LinkBatch' => 'includes/cache/LinkBatch.php',
+ 'LinkCache' => 'includes/cache/LinkCache.php',
+ 'MessageCache' => 'includes/cache/MessageCache.php',
+ 'SquidUpdate' => 'includes/cache/SquidUpdate.php',
+ 'TitleDependency' => 'includes/cache/CacheDependency.php',
+ 'TitleListDependency' => 'includes/cache/CacheDependency.php',
# includes/db
- 'Blob' => 'includes/db/Database.php',
+ 'Blob' => 'includes/db/DatabaseUtility.php',
'ChronologyProtector' => 'includes/db/LBFactory.php',
+ 'CloneDatabase' => 'includes/db/CloneDatabase.php',
'Database' => 'includes/db/DatabaseMysql.php',
'DatabaseBase' => 'includes/db/Database.php',
+ 'DatabaseIbm_db2' => 'includes/db/DatabaseIbm_db2.php',
'DatabaseMssql' => 'includes/db/DatabaseMssql.php',
'DatabaseMysql' => 'includes/db/DatabaseMysql.php',
'DatabaseOracle' => 'includes/db/DatabaseOracle.php',
@@ -376,51 +390,57 @@ $wgAutoloadLocalClasses = array(
'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',
+ 'DBConnectionError' => 'includes/db/DatabaseError.php',
+ 'DBError' => 'includes/db/DatabaseError.php',
+ 'DBObject' => 'includes/db/DatabaseUtility.php',
+ 'DBMasterPos' => 'includes/db/DatabaseUtility.php',
+ 'DBQueryError' => 'includes/db/DatabaseError.php',
+ 'DBUnexpectedError' => 'includes/db/DatabaseError.php',
+ 'FakeResultWrapper' => 'includes/db/DatabaseUtility.php',
+ 'Field' => 'includes/db/DatabaseUtility.php',
'IBM_DB2Blob' => 'includes/db/DatabaseIbm_db2.php',
+ 'IBM_DB2Field' => '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',
+ 'LikeMatch' => 'includes/db/DatabaseUtility.php',
'LoadBalancer' => 'includes/db/LoadBalancer.php',
'LoadBalancer_Single' => 'includes/db/LBFactory_Single.php',
'LoadMonitor' => 'includes/db/LoadMonitor.php',
'LoadMonitor_MySQL' => 'includes/db/LoadMonitor.php',
+ 'LoadMonitor_Null' => 'includes/db/LoadMonitor.php',
'MySQLField' => 'includes/db/DatabaseMysql.php',
'MySQLMasterPos' => 'includes/db/DatabaseMysql.php',
'ORAField' => 'includes/db/DatabaseOracle.php',
'ORAResult' => 'includes/db/DatabaseOracle.php',
'PostgresField' => 'includes/db/DatabasePostgres.php',
- 'ResultWrapper' => 'includes/db/Database.php',
+ 'ResultWrapper' => 'includes/db/DatabaseUtility.php',
'SQLiteField' => 'includes/db/DatabaseSqlite.php',
- 'DatabaseIbm_db2' => 'includes/db/DatabaseIbm_db2.php',
- 'IBM_DB2Field' => 'includes/db/DatabaseIbm_db2.php',
# includes/diff
- 'ArrayDiffFormatter' => 'includes/diff/WikiDiff.php',
- '_DiffEngine' => 'includes/diff/WikiDiff.php',
+ '_DiffEngine' => 'includes/diff/DairikiDiff.php',
+ '_DiffOp' => 'includes/diff/DairikiDiff.php',
+ '_DiffOp_Add' => 'includes/diff/DairikiDiff.php',
+ '_DiffOp_Change' => 'includes/diff/DairikiDiff.php',
+ '_DiffOp_Copy' => 'includes/diff/DairikiDiff.php',
+ '_DiffOp_Delete' => 'includes/diff/DairikiDiff.php',
+ '_HWLDF_WordAccumulator' => 'includes/diff/DairikiDiff.php',
+ 'ArrayDiffFormatter' => 'includes/diff/DairikiDiff.php',
+ 'Diff' => 'includes/diff/DairikiDiff.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',
+ 'DiffFormatter' => 'includes/diff/DairikiDiff.php',
+ 'MappedDiff' => 'includes/diff/DairikiDiff.php',
'RangeDifference' => 'includes/diff/WikiDiff3.php',
- 'TableDiffFormatter' => 'includes/diff/WikiDiff.php',
- 'UnifiedDiffFormatter' => 'includes/diff/WikiDiff.php',
+ 'TableDiffFormatter' => 'includes/diff/DairikiDiff.php',
+ 'UnifiedDiffFormatter' => 'includes/diff/DairikiDiff.php',
'WikiDiff3' => 'includes/diff/WikiDiff3.php',
- 'WordLevelDiff' => 'includes/diff/WikiDiff.php',
+ 'WordLevelDiff' => 'includes/diff/DairikiDiff.php',
+
+ # includes/extauth
+ 'ExternalUser_Hardcoded' => 'includes/extauth/Hardcoded.php',
+ 'ExternalUser_MediaWiki' => 'includes/extauth/MediaWiki.php',
+ 'ExternalUser_vB' => 'includes/extauth/vB.php',
# includes/filerepo
'ArchivedFile' => 'includes/filerepo/ArchivedFile.php',
@@ -433,7 +453,6 @@ $wgAutoloadLocalClasses = array(
'ForeignDBRepo' => 'includes/filerepo/ForeignDBRepo.php',
'ForeignDBViaLBRepo' => 'includes/filerepo/ForeignDBViaLBRepo.php',
'FSRepo' => 'includes/filerepo/FSRepo.php',
- 'Image' => 'includes/filerepo/Image.php',
'LocalFile' => 'includes/filerepo/LocalFile.php',
'LocalFileDeleteBatch' => 'includes/filerepo/LocalFile.php',
'LocalFileMoveBatch' => 'includes/filerepo/LocalFile.php',
@@ -445,24 +464,43 @@ $wgAutoloadLocalClasses = array(
# includes/installer
'CliInstaller' => 'includes/installer/CliInstaller.php',
- 'Installer' => 'includes/installer/Installer.php',
'DatabaseInstaller' => 'includes/installer/DatabaseInstaller.php',
'DatabaseUpdater' => 'includes/installer/DatabaseUpdater.php',
+ 'Ibm_db2Installer' => 'includes/installer/Ibm_db2Installer.php',
+ 'Ibm_db2Updater' => 'includes/installer/Ibm_db2Updater.php',
+ 'InstallDocFormatter' => 'includes/installer/InstallDocFormatter.php',
+ 'Installer' => 'includes/installer/Installer.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',
+ 'OracleInstaller' => 'includes/installer/OracleInstaller.php',
+ 'OracleUpdater' => 'includes/installer/OracleUpdater.php',
'PhpRefCallBugTester' => 'includes/installer/PhpBugTests.php',
+ 'PhpXmlBugTester' => 'includes/installer/PhpBugTests.php',
'PostgresInstaller' => 'includes/installer/PostgresInstaller.php',
'PostgresUpdater' => 'includes/installer/PostgresUpdater.php',
'SqliteInstaller' => 'includes/installer/SqliteInstaller.php',
'SqliteUpdater' => 'includes/installer/SqliteUpdater.php',
- 'OracleInstaller' => 'includes/installer/OracleInstaller.php',
- 'OracleUpdater' => 'includes/installer/OracleUpdater.php',
+ 'WebInstaller' => 'includes/installer/WebInstaller.php',
+ 'WebInstaller_Complete' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_Copying' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_DBConnect' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_DBSettings' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_Document' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_ExistingWiki' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_Install' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_Language' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_Name' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_Options' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_Readme' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_ReleaseNotes' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_Restart' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_Upgrade' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_UpgradeDoc' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstaller_Welcome' => 'includes/installer/WebInstallerPage.php',
+ 'WebInstallerOutput' => 'includes/installer/WebInstallerOutput.php',
+ 'WebInstallerPage' => 'includes/installer/WebInstallerPage.php',
# includes/job
'DoubleRedirectJob' => 'includes/job/DoubleRedirectJob.php',
@@ -473,19 +511,37 @@ $wgAutoloadLocalClasses = array(
'RefreshLinksJob2' => 'includes/job/RefreshLinksJob.php',
'UploadFromUrlJob' => 'includes/job/UploadFromUrlJob.php',
+ # includes/json
+ 'FormatJson' => 'includes/json/FormatJson.php',
+ 'Services_JSON' => 'includes/json/Services_JSON.php',
+ 'Services_JSON_Error' => 'includes/json/Services_JSON.php',
+
# includes/libs
+ 'CSSJanus' => 'includes/libs/CSSJanus.php',
+ 'CSSMin' => 'includes/libs/CSSMin.php',
+ 'HttpStatus' => 'includes/libs/HttpStatus.php',
'IEContentAnalyzer' => 'includes/libs/IEContentAnalyzer.php',
'IEUrlExtension' => 'includes/libs/IEUrlExtension.php',
- 'Spyc' => 'includes/libs/spyc.php',
+ 'JavaScriptMinifier' => 'includes/libs/JavaScriptMinifier.php',
+ 'JSMinPlus' => 'includes/libs/jsminplus.php',
+ 'JSParser' => 'includes/libs/jsminplus.php',
# includes/media
'BitmapHandler' => 'includes/media/Bitmap.php',
'BitmapHandler_ClientOnly' => 'includes/media/Bitmap_ClientOnly.php',
+ 'BitmapMetadataHandler' => 'includes/media/BitmapMetadataHandler.php',
'BmpHandler' => 'includes/media/BMP.php',
'DjVuHandler' => 'includes/media/DjVu.php',
+ 'Exif' => 'includes/media/Exif.php',
+ 'FormatExif' => 'includes/media/FormatMetadata.php',
+ 'FormatMetadata' => 'includes/media/FormatMetadata.php',
'GIFHandler' => 'includes/media/GIF.php',
'GIFMetadataExtractor' => 'includes/media/GIFMetadataExtractor.php',
'ImageHandler' => 'includes/media/Generic.php',
+ 'IPTC' => 'includes/media/IPTC.php',
+ 'JpegHandler' => 'includes/media/Jpeg.php',
+ 'JpegMetadataExtractor' => 'includes/media/JpegMetadataExtractor.php',
+ 'ExifBitmapHandler' => 'includes/media/ExifBitmap.php',
'MediaHandler' => 'includes/media/Generic.php',
'MediaTransformError' => 'includes/media/MediaTransformOutput.php',
'MediaTransformOutput' => 'includes/media/MediaTransformOutput.php',
@@ -496,38 +552,72 @@ $wgAutoloadLocalClasses = array(
'ThumbnailImage' => 'includes/media/MediaTransformOutput.php',
'TiffHandler' => 'includes/media/Tiff.php',
'TransformParameterError' => 'includes/media/MediaTransformOutput.php',
+ 'XMPInfo' => 'includes/media/XMPInfo.php',
+ 'XMPReader' => 'includes/media/XMP.php',
+ 'XMPValidate' => 'includes/media/XMPValidate.php',
# includes/normal
'UtfNormal' => 'includes/normal/UtfNormal.php',
+ # includes/objectcache
+ 'APCBagOStuff' => 'includes/objectcache/APCBagOStuff.php',
+ 'BagOStuff' => 'includes/objectcache/BagOStuff.php',
+ 'DBABagOStuff' => 'includes/objectcache/DBABagOStuff.php',
+ 'eAccelBagOStuff' => 'includes/objectcache/eAccelBagOStuff.php',
+ 'EhcacheBagOStuff' => 'includes/objectcache/EhcacheBagOStuff.php',
+ 'EmptyBagOStuff' => 'includes/objectcache/EmptyBagOStuff.php',
+ 'FakeMemCachedClient' => 'includes/objectcache/EmptyBagOStuff.php',
+ 'HashBagOStuff' => 'includes/objectcache/HashBagOStuff.php',
+ 'MediaWikiBagOStuff' => 'includes/objectcache/SqlBagOStuff.php',
+ 'MemCachedClientforWiki' => 'includes/objectcache/MemcachedClient.php',
+ 'MemcachedPhpBagOStuff' => 'includes/objectcache/MemcachedPhpBagOStuff.php',
+ 'MultiWriteBagOStuff' => 'includes/objectcache/MultiWriteBagOStuff.php',
+ 'MWMemcached' => 'includes/objectcache/MemcachedClient.php',
+ 'ObjectCache' => 'includes/objectcache/ObjectCache.php',
+ 'SqlBagOStuff' => 'includes/objectcache/SqlBagOStuff.php',
+ 'WinCacheBagOStuff' => 'includes/objectcache/WinCacheBagOStuff.php',
+ 'XCacheBagOStuff' => 'includes/objectcache/XCacheBagOStuff.php',
+
# includes/parser
+ 'CacheTime' => 'includes/parser/ParserOutput.php',
'CoreLinkFunctions' => 'includes/parser/CoreLinkFunctions.php',
'CoreParserFunctions' => 'includes/parser/CoreParserFunctions.php',
'CoreTagHooks' => 'includes/parser/CoreTagHooks.php',
'DateFormatter' => 'includes/parser/DateFormatter.php',
'LinkHolderArray' => 'includes/parser/LinkHolderArray.php',
'LinkMarkerReplacer' => 'includes/parser/Parser_LinkHooks.php',
- 'OnlyIncludeReplacer' => 'includes/parser/Parser.php',
- 'PPCustomFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
+ 'MWTidy' => 'includes/parser/Tidy.php',
'PPCustomFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
+ 'PPCustomFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
+ 'PPCustomFrame_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPDAccum_Hash' => 'includes/parser/Preprocessor_Hash.php',
+ 'PPDAccum_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPDPart' => 'includes/parser/Preprocessor_DOM.php',
'PPDPart_Hash' => 'includes/parser/Preprocessor_Hash.php',
+ 'PPDPart_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPDStack' => 'includes/parser/Preprocessor_DOM.php',
'PPDStackElement' => 'includes/parser/Preprocessor_DOM.php',
'PPDStackElement_Hash' => 'includes/parser/Preprocessor_Hash.php',
+ 'PPDStackElement_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPDStack_Hash' => 'includes/parser/Preprocessor_Hash.php',
+ 'PPDStack_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPFrame' => 'includes/parser/Preprocessor.php',
'PPFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
'PPFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
+ 'PPFrame_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPNode' => 'includes/parser/Preprocessor.php',
'PPNode_DOM' => 'includes/parser/Preprocessor_DOM.php',
'PPNode_Hash_Array' => 'includes/parser/Preprocessor_Hash.php',
'PPNode_Hash_Attr' => 'includes/parser/Preprocessor_Hash.php',
'PPNode_Hash_Text' => 'includes/parser/Preprocessor_Hash.php',
'PPNode_Hash_Tree' => 'includes/parser/Preprocessor_Hash.php',
+ 'PPNode_HipHop_Array' => 'includes/parser/Preprocessor_HipHop.hphp',
+ 'PPNode_HipHop_Attr' => 'includes/parser/Preprocessor_HipHop.hphp',
+ 'PPNode_HipHop_Text' => 'includes/parser/Preprocessor_HipHop.hphp',
+ 'PPNode_HipHop_Tree' => 'includes/parser/Preprocessor_HipHop.hphp',
'PPTemplateFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
'PPTemplateFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
+ 'PPTemplateFrame_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
'Parser' => 'includes/parser/Parser.php',
'ParserCache' => 'includes/parser/ParserCache.php',
'ParserOptions' => 'includes/parser/ParserOptions.php',
@@ -537,15 +627,55 @@ $wgAutoloadLocalClasses = array(
'Preprocessor' => 'includes/parser/Preprocessor.php',
'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php',
'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php',
- 'StripState' => 'includes/parser/Parser.php',
- 'MWTidy' => 'includes/parser/Tidy.php',
+ 'Preprocessor_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
+ 'StripState' => 'includes/parser/StripState.php',
+
+ # includes/profiler
+ 'Profiler' => 'includes/profiler/Profiler.php',
+ 'ProfilerSimple' => 'includes/profiler/ProfilerSimple.php',
+ 'ProfilerSimpleText' => 'includes/profiler/ProfilerSimpleText.php',
+ 'ProfilerSimpleTrace' => 'includes/profiler/ProfilerSimpleTrace.php',
+ 'ProfilerSimpleUDP' => 'includes/profiler/ProfilerSimpleUDP.php',
+ 'ProfilerStub' => 'includes/profiler/ProfilerStub.php',
+
+ # includes/resourceloader
+ 'ResourceLoader' => 'includes/resourceloader/ResourceLoader.php',
+ 'ResourceLoaderContext' => 'includes/resourceloader/ResourceLoaderContext.php',
+ 'ResourceLoaderFileModule' => 'includes/resourceloader/ResourceLoaderFileModule.php',
+ 'ResourceLoaderFilePageModule' => 'includes/resourceloader/ResourceLoaderFilePageModule.php',
+ 'ResourceLoaderModule' => 'includes/resourceloader/ResourceLoaderModule.php',
+ 'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php',
+ 'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php',
+ 'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php',
+ 'ResourceLoaderUserGroupsModule' => 'includes/resourceloader/ResourceLoaderUserGroupsModule.php',
+ 'ResourceLoaderUserModule' => 'includes/resourceloader/ResourceLoaderUserModule.php',
+ 'ResourceLoaderUserOptionsModule' => 'includes/resourceloader/ResourceLoaderUserOptionsModule.php',
+ 'ResourceLoaderUserTokensModule' => 'includes/resourceloader/ResourceLoaderUserTokensModule.php',
+ 'ResourceLoaderWikiModule' => 'includes/resourceloader/ResourceLoaderWikiModule.php',
+
+ # includes/revisiondelete
+ 'RevDel_ArchivedFileItem' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_ArchivedFileList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_ArchiveItem' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_ArchiveList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_FileItem' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_FileList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_Item' => 'includes/revisiondelete/RevisionDeleteAbstracts.php',
+ 'RevDel_List' => 'includes/revisiondelete/RevisionDeleteAbstracts.php',
+ 'RevDel_LogItem' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_LogList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_RevisionItem' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevDel_RevisionList' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevisionDelete' => 'includes/revisiondelete/RevisionDelete.php',
+ 'RevisionDeleter' => 'includes/revisiondelete/RevisionDeleter.php',
+ 'RevisionDeleteUser' => 'includes/revisiondelete/RevisionDeleteUser.php',
# includes/search
'MySQLSearchResultSet' => 'includes/search/SearchMySQL.php',
'PostgresSearchResult' => 'includes/search/SearchPostgres.php',
'PostgresSearchResultSet' => 'includes/search/SearchPostgres.php',
- 'SearchEngineDummy' => 'includes/search/SearchEngine.php',
'SearchEngine' => 'includes/search/SearchEngine.php',
+ 'SearchEngineDummy' => 'includes/search/SearchEngine.php',
'SearchHighlighter' => 'includes/search/SearchEngine.php',
'SearchIBM_DB2' => 'includes/search/SearchIBM_DB2.php',
'SearchMssql' => 'includes/search/SearchMssql.php',
@@ -562,30 +692,26 @@ $wgAutoloadLocalClasses = array(
'SqlSearchResultSet' => 'includes/search/SearchEngine.php',
# includes/specials
- 'SpecialAllmessages' => 'includes/specials/SpecialAllmessages.php',
'ActiveUsersPager' => 'includes/specials/SpecialActiveusers.php',
'AllmessagesTablePager' => 'includes/specials/SpecialAllmessages.php',
'AncientPagesPage' => 'includes/specials/SpecialAncientpages.php',
+ 'BlockListPager' => 'includes/specials/SpecialBlockList.php',
'BrokenRedirectsPage' => 'includes/specials/SpecialBrokenRedirects.php',
'ContribsPager' => 'includes/specials/SpecialContributions.php',
'DBLockForm' => 'includes/specials/SpecialLockdb.php',
'DBUnlockForm' => 'includes/specials/SpecialUnlockdb.php',
'DeadendPagesPage' => 'includes/specials/SpecialDeadendpages.php',
- 'DeletedContributionsPage' => 'includes/specials/SpecialDeletedContributions.php',
'DeletedContribsPager' => 'includes/specials/SpecialDeletedContributions.php',
+ 'DeletedContributionsPage' => 'includes/specials/SpecialDeletedContributions.php',
'DisambiguationsPage' => 'includes/specials/SpecialDisambiguations.php',
'DoubleRedirectsPage' => 'includes/specials/SpecialDoubleRedirects.php',
'EmailConfirmation' => 'includes/specials/SpecialConfirmemail.php',
'EmailInvalidation' => 'includes/specials/SpecialConfirmemail.php',
- 'SpecialEmailUser' => 'includes/specials/SpecialEmailuser.php',
'FewestrevisionsPage' => 'includes/specials/SpecialFewestrevisions.php',
'FileDuplicateSearchPage' => 'includes/specials/SpecialFileDuplicateSearch.php',
- 'IPBlockForm' => 'includes/specials/SpecialBlockip.php',
- 'IPBlocklistPager' => 'includes/specials/SpecialIpblocklist.php',
- 'IPUnblockForm' => 'includes/specials/SpecialIpblocklist.php',
+ 'HTMLBlockedUsersItemSelect' => 'includes/specials/SpecialBlockList.php',
'ImportReporter' => 'includes/specials/SpecialImport.php',
- 'ImportStreamSource' => 'includes/Import.php',
- 'ImportStringSource' => 'includes/Import.php',
+ 'IPBlockForm' => 'includes/specials/SpecialBlock.php',
'LinkSearchPage' => 'includes/specials/SpecialLinkSearch.php',
'ListredirectsPage' => 'includes/specials/SpecialListredirects.php',
'LoginForm' => 'includes/specials/SpecialUserlogin.php',
@@ -596,46 +722,42 @@ $wgAutoloadLocalClasses = array(
'MostimagesPage' => 'includes/specials/SpecialMostimages.php',
'MostlinkedCategoriesPage' => 'includes/specials/SpecialMostlinkedcategories.php',
'MostlinkedPage' => 'includes/specials/SpecialMostlinked.php',
+ 'MostlinkedTemplatesPage' => 'includes/specials/SpecialMostlinkedtemplates.php',
'MostrevisionsPage' => 'includes/specials/SpecialMostrevisions.php',
'MovePageForm' => 'includes/specials/SpecialMovepage.php',
- 'SpecialNewpages' => 'includes/specials/SpecialNewpages.php',
- 'SpecialContributions' => 'includes/specials/SpecialContributions.php',
'NewPagesPager' => 'includes/specials/SpecialNewpages.php',
'PageArchive' => 'includes/specials/SpecialUndelete.php',
- 'SpecialResetpass' => 'includes/specials/SpecialResetpass.php',
'PopularPagesPage' => 'includes/specials/SpecialPopularpages.php',
- 'PreferencesForm' => 'includes/Preferences.php',
'RandomPage' => 'includes/specials/SpecialRandompage.php',
- 'SpecialRevisionDelete' => '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',
+ 'SpecialAllmessages' => 'includes/specials/SpecialAllmessages.php',
'SpecialAllpages' => 'includes/specials/SpecialAllpages.php',
'SpecialBlankpage' => 'includes/specials/SpecialBlankpage.php',
+ 'SpecialBlock' => 'includes/specials/SpecialBlock.php',
+ 'SpecialBlockList' => 'includes/specials/SpecialBlockList.php',
'SpecialBlockme' => 'includes/specials/SpecialBlockme.php',
'SpecialBookSources' => 'includes/specials/SpecialBooksources.php',
'SpecialCategories' => 'includes/specials/SpecialCategories.php',
+ 'SpecialChangePassword' => 'includes/specials/SpecialChangePassword.php',
'SpecialComparePages' => 'includes/specials/SpecialComparePages.php',
+ 'SpecialContributions' => 'includes/specials/SpecialContributions.php',
+ 'SpecialEditWatchlist' => 'includes/specials/SpecialEditWatchlist.php',
+ 'SpecialEmailUser' => 'includes/specials/SpecialEmailuser.php',
'SpecialExport' => 'includes/specials/SpecialExport.php',
'SpecialFilepath' => 'includes/specials/SpecialFilepath.php',
'SpecialImport' => 'includes/specials/SpecialImport.php',
+ 'SpecialListFiles' => 'includes/specials/SpecialListfiles.php',
'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php',
+ 'SpecialListUsers' => 'includes/specials/SpecialListusers.php',
'SpecialLockdb' => 'includes/specials/SpecialLockdb.php',
'SpecialLog' => 'includes/specials/SpecialLog.php',
'SpecialMergeHistory' => 'includes/specials/SpecialMergeHistory.php',
'SpecialMostlinkedtemplates' => 'includes/specials/SpecialMostlinkedtemplates.php',
+ 'SpecialNewFiles' => 'includes/specials/SpecialNewimages.php',
+ 'SpecialNewpages' => 'includes/specials/SpecialNewpages.php',
+ 'SpecialPasswordReset' => 'includes/specials/SpecialPasswordReset.php',
+ 'SpecialPermanentLink' => 'includes/SpecialPage.php',
'SpecialPreferences' => 'includes/specials/SpecialPreferences.php',
'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php',
'SpecialProtectedpages' => 'includes/specials/SpecialProtectedpages.php',
@@ -643,21 +765,24 @@ $wgAutoloadLocalClasses = array(
'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php',
'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php',
'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php',
+ 'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.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',
+ 'SpecialUnblock' => 'includes/specials/SpecialUnblock.php',
+ 'SpecialUndelete' => 'includes/specials/SpecialUndelete.php',
'SpecialUnlockdb' => 'includes/specials/SpecialUnlockdb.php',
'SpecialUpload' => 'includes/specials/SpecialUpload.php',
+ 'SpecialUploadStash' => 'includes/specials/SpecialUploadStash.php',
'SpecialUserlogout' => 'includes/specials/SpecialUserlogout.php',
'SpecialVersion' => 'includes/specials/SpecialVersion.php',
+ 'SpecialWatchlist' => 'includes/specials/SpecialWatchlist.php',
'SpecialWhatlinkshere' => 'includes/specials/SpecialWhatlinkshere.php',
- 'SpecialWhatLinksHere' => 'includes/specials/SpecialWhatlinkshere.php',
'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php',
+ 'UncategorizedImagesPage' => 'includes/specials/SpecialUncategorizedimages.php',
'UncategorizedPagesPage' => 'includes/specials/SpecialUncategorizedpages.php',
'UncategorizedTemplatesPage' => 'includes/specials/SpecialUncategorizedtemplates.php',
- 'UndeleteForm' => 'includes/specials/SpecialUndelete.php',
'UnusedCategoriesPage' => 'includes/specials/SpecialUnusedcategories.php',
'UnusedimagesPage' => 'includes/specials/SpecialUnusedimages.php',
'UnusedtemplatesPage' => 'includes/specials/SpecialUnusedtemplates.php',
@@ -670,9 +795,7 @@ $wgAutoloadLocalClasses = array(
'WantedFilesPage' => 'includes/specials/SpecialWantedfiles.php',
'WantedPagesPage' => 'includes/specials/SpecialWantedpages.php',
'WantedTemplatesPage' => 'includes/specials/SpecialWantedtemplates.php',
- 'WhatLinksHerePage' => 'includes/specials/SpecialWhatlinkshere.php',
- 'WikiImporter' => 'includes/Import.php',
- 'WikiRevision' => 'includes/Import.php',
+ 'WatchlistEditor' => 'includes/specials/SpecialEditWatchlist.php',
'WithoutInterwikiPage' => 'includes/specials/SpecialWithoutinterwiki.php',
# includes/templates
@@ -681,66 +804,71 @@ $wgAutoloadLocalClasses = array(
# includes/upload
'UploadBase' => 'includes/upload/UploadBase.php',
- 'UploadFromStash' => 'includes/upload/UploadFromStash.php',
'UploadFromFile' => 'includes/upload/UploadFromFile.php',
+ 'UploadFromStash' => 'includes/upload/UploadFromStash.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',
+ 'UploadStashFile' => 'includes/upload/UploadStash.php',
'UploadStashFileException' => 'includes/upload/UploadStash.php',
+ 'UploadStashFileNotFoundException' => 'includes/upload/UploadStash.php',
+ 'UploadStashNotAvailableException' => 'includes/upload/UploadStash.php',
'UploadStashZeroLengthFileException' => 'includes/upload/UploadStash.php',
# languages
- 'Language' => 'languages/Language.php',
'FakeConverter' => 'languages/Language.php',
+ 'Language' => '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',
'FakeMaintenance' => 'maintenance/Maintenance.php',
+ 'LoggedUpdateMaintenance' => 'maintenance/Maintenance.php',
'Maintenance' => 'maintenance/Maintenance.php',
- 'ParserTest' => 'maintenance/tests/parser/parserTest.inc',
- 'ParserTestParserHook' => 'maintenance/tests/parser/parserTestsParserHook.php',
- 'ParserTestStaticParserHook' => 'maintenance/tests/parser/parserTestsStaticParserHook.php',
+ 'FixExtLinksProtocolRelative' => 'maintenance/fixExtLinksProtocolRelative.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',
'textStatsOutput' => 'maintenance/language/StatOutputs.php',
'wikiStatsOutput' => 'maintenance/language/StatOutputs.php',
+
+ # tests
+ 'AnsiTermColorer' => 'tests/testHelpers.inc',
+ 'DbTestPreviewer' => 'tests/testHelpers.inc',
+ 'DbTestRecorder' => 'tests/testHelpers.inc',
+ 'DummyTermColorer' => 'tests/testHelpers.inc',
+ 'TestFileIterator' => 'tests/testHelpers.inc',
+ 'TestRecorder' => 'tests/testHelpers.inc',
+
+ # tests/parser
+ 'ParserTest' => 'tests/parser/parserTest.inc',
+ 'ParserTestParserHook' => 'tests/parser/parserTestsParserHook.php',
+ 'ParserTestStaticParserHook' => 'tests/parser/parserTestsStaticParserHook.php',
+
+ # tests/selenium
+ 'Selenium' => 'tests/selenium/Selenium.php',
+ 'SeleniumLoader' => 'tests/selenium/SeleniumLoader.php',
+ 'SeleniumTestCase' => 'tests/selenium/SeleniumTestCase.php',
+ 'SeleniumTestConsoleLogger' => 'tests/selenium/SeleniumTestConsoleLogger.php',
+ 'SeleniumTestHTMLLogger' => 'tests/selenium/SeleniumTestHTMLLogger.php',
+ 'SeleniumTestListener' => 'tests/selenium/SeleniumTestListener.php',
+ 'SeleniumTestSuite' => 'tests/selenium/SeleniumTestSuite.php',
+ 'SeleniumConfig' => 'tests/selenium/SeleniumConfig.php',
);
class AutoLoader {
@@ -792,16 +920,6 @@ class AutoLoader {
return true;
}
- static function loadAllExtensions() {
- global $wgAutoloadClasses;
-
- foreach ( $wgAutoloadClasses as $class => $file ) {
- if ( !( class_exists( $class, false ) || interface_exists( $class, false ) ) ) {
- require( $file );
- }
- }
- }
-
/**
* Force a class to be run through the autoloader, helpful for things like
* Sanitizer that have define()s outside of their class definition. Of course
diff --git a/includes/Autopromote.php b/includes/Autopromote.php
index b4d89b24..83f3c20b 100644
--- a/includes/Autopromote.php
+++ b/includes/Autopromote.php
@@ -8,7 +8,7 @@ class Autopromote {
/**
* Get the groups for the given user based on $wgAutopromote.
*
- * @param $user The user to get the groups for
+ * @param $user User The user to get the groups for
* @return array Array of groups to promote to.
*/
public static function getAutopromoteGroups( User $user ) {
@@ -28,8 +28,47 @@ class Autopromote {
}
/**
+ * Get the groups for the given user based on the given criteria.
+ *
+ * Does not return groups the user already belongs to or has once belonged.
+ *
+ * @param $user The user to get the groups for
+ * @param $event String key in $wgAutopromoteOnce (each one has groups/criteria)
+ *
+ * @return array Groups the user should be promoted to.
+ *
+ * @see $wgAutopromoteOnce
+ */
+ public static function getAutopromoteOnceGroups( User $user, $event ) {
+ global $wgAutopromoteOnce;
+
+ $promote = array();
+
+ if ( isset( $wgAutopromoteOnce[$event] ) && count( $wgAutopromoteOnce[$event] ) ) {
+ $currentGroups = $user->getGroups();
+ $formerGroups = $user->getFormerGroups();
+ foreach ( $wgAutopromoteOnce[$event] as $group => $cond ) {
+ // Do not check if the user's already a member
+ if ( in_array( $group, $currentGroups ) ) {
+ continue;
+ }
+ // Do not autopromote if the user has belonged to the group
+ if ( in_array( $group, $formerGroups ) ) {
+ continue;
+ }
+ // Finally - check the conditions
+ if ( self::recCheckCondition( $cond, $user ) ) {
+ $promote[] = $group;
+ }
+ }
+ }
+
+ return $promote;
+ }
+
+ /**
* Recursively check a condition. Conditions are in the form
- * array( '&' or '|' or '^', cond1, cond2, ... )
+ * array( '&' or '|' or '^' or '!', cond1, cond2, ... )
* where cond1, cond2, ... are themselves conditions; *OR*
* APCOND_EMAILCONFIRMED, *OR*
* array( APCOND_EMAILCONFIRMED ), *OR*
@@ -40,7 +79,7 @@ class Autopromote {
* self::checkCondition for evaluation of the latter type.
*
* @param $cond Mixed: a condition, possibly containing other conditions
- * @param $user The user to check the conditions against
+ * @param $user User The user to check the conditions against
* @return bool Whether the condition is true
*/
private static function recCheckCondition( $cond, User $user ) {
@@ -48,7 +87,7 @@ class Autopromote {
if ( is_array( $cond ) && count( $cond ) >= 2 && in_array( $cond[0], $validOps ) ) {
# Recursive condition
- if ( $cond[0] == '&' ) {
+ if ( $cond[0] == '&' ) { // AND (all conds pass)
foreach ( array_slice( $cond, 1 ) as $subcond ) {
if ( !self::recCheckCondition( $subcond, $user ) ) {
return false;
@@ -56,7 +95,7 @@ class Autopromote {
}
return true;
- } elseif ( $cond[0] == '|' ) {
+ } elseif ( $cond[0] == '|' ) { // OR (at least one cond passes)
foreach ( array_slice( $cond, 1 ) as $subcond ) {
if ( self::recCheckCondition( $subcond, $user ) ) {
return true;
@@ -64,18 +103,13 @@ class Autopromote {
}
return false;
- } elseif ( $cond[0] == '^' ) {
- $res = null;
- foreach ( array_slice( $cond, 1 ) as $subcond ) {
- if ( is_null( $res ) ) {
- $res = self::recCheckCondition( $subcond, $user );
- } else {
- $res = ( $res xor self::recCheckCondition( $subcond, $user ) );
- }
+ } elseif ( $cond[0] == '^' ) { // XOR (exactly one cond passes)
+ if ( count( $cond ) > 3 ) {
+ wfWarn( 'recCheckCondition() given XOR ("^") condition on three or more conditions. Check your $wgAutopromote and $wgAutopromoteOnce settings.' );
}
-
- return $res;
- } elseif ( $cond[0] == '!' ) {
+ return self::recCheckCondition( $cond[1], $user )
+ xor self::recCheckCondition( $cond[2], $user );
+ } elseif ( $cond[0] == '!' ) { // NOT (no conds pass)
foreach ( array_slice( $cond, 1 ) as $subcond ) {
if ( self::recCheckCondition( $subcond, $user ) ) {
return false;
@@ -101,7 +135,7 @@ class Autopromote {
* ates them.
*
* @param $cond Array: A condition, which must not contain other conditions
- * @param $user The user to check the condition against
+ * @param $user User The user to check the condition against
* @return bool Whether the condition is true for the user
*/
private static function checkCondition( $cond, User $user ) {
@@ -112,7 +146,7 @@ class Autopromote {
switch( $cond[0] ) {
case APCOND_EMAILCONFIRMED:
- if ( User::isValidEmailAddr( $user->getEmail() ) ) {
+ if ( Sanitizer::validateEmail( $user->getEmail() ) ) {
if ( $wgEmailAuthentication ) {
return (bool)$user->getEmailAuthenticationTimestamp();
} else {
@@ -137,6 +171,8 @@ class Autopromote {
return IP::isInRange( wfGetIP(), $cond[1] );
case APCOND_BLOCKED:
return $user->isBlocked();
+ case APCOND_ISBOT:
+ return in_array( 'bot', User::getGroupPermissions( $user->getGroups() ) );
default:
$result = null;
wfRunHooks( 'AutopromoteCondition', array( $cond[0], array_slice( $cond, 1 ), $user, &$result ) );
diff --git a/includes/BacklinkCache.php b/includes/BacklinkCache.php
index 02b0f170..8d1571ec 100644
--- a/includes/BacklinkCache.php
+++ b/includes/BacklinkCache.php
@@ -1,21 +1,71 @@
<?php
/**
- * Class for fetching backlink lists, approximate backlink counts and partitions.
- * Instances of this class should typically be fetched with $title->getBacklinkCache().
+ * File for BacklinkCache class
+ * @file
+ */
+
+/**
+ * Class for fetching backlink lists, approximate backlink counts and
+ * partitions. This is a shared cache.
+ *
+ * Instances of this class should typically be fetched with the method
+ * $title->getBacklinkCache().
+ *
+ * Ideally you should only get your backlinks from here when you think
+ * there is some advantage in caching them. Otherwise it's just a waste
+ * of memory.
*
- * Ideally you should only get your backlinks from here when you think there is some
- * advantage in caching them. Otherwise it's just a waste of memory.
+ * Introduced by r47317
+ *
+ * @internal documentation reviewed on 18 Mar 2011 by hashar
+ *
+ * @author Tim Starling
+ * @copyright © 2009, Tim Starling, Domas Mituzas
+ * @copyright © 2010, Max Sem
+ * @copyright © 2011, Ashar Voultoiz
*/
class BacklinkCache {
- var $partitionCache = array();
- var $fullResultCache = array();
- var $title;
- var $db;
+
+ /**
+ * Multi dimensions array representing batches. Keys are:
+ * > (string) links table name
+ * > 'numRows' : Number of rows for this link table
+ * > 'batches' : array( $start, $end )
+ *
+ * @see BacklinkCache::partitionResult()
+ *
+ * Cleared with BacklinkCache::clear()
+ */
+ protected $partitionCache = array();
+
+ /**
+ * Contains the whole links from a database result.
+ * This is raw data that will be partitioned in $partitionCache
+ *
+ * Initialized with BacklinkCache::getLinks()
+ * Cleared with BacklinkCache::clear()
+ */
+ protected $fullResultCache = array();
+
+ /**
+ * Local copy of a database object.
+ *
+ * Accessor: BacklinkCache::getDB()
+ * Mutator : BacklinkCache::setDB()
+ * Cleared with BacklinkCache::clear()
+ */
+ protected $db;
+
+ /**
+ * Local copy of a Title object
+ */
+ protected $title;
const CACHE_EXPIRY = 3600;
/**
* Create a new BacklinkCache
+ * @param Title $title : Title object to create a backlink cache for.
*/
function __construct( $title ) {
$this->title = $title;
@@ -23,16 +73,17 @@ class BacklinkCache {
/**
* Serialization handler, diasallows to serialize the database to prevent
- * failures after this class is deserialized from cache with dead DB connection.
+ * failures after this class is deserialized from cache with dead DB
+ * connection.
*/
function __sleep() {
return array( 'partitionCache', 'fullResultCache', 'title' );
}
/**
- * Clear locally stored data
+ * Clear locally stored data and database object.
*/
- function clear() {
+ public function clear() {
$this->partitionCache = array();
$this->fullResultCache = array();
unset( $this->db );
@@ -40,11 +91,18 @@ class BacklinkCache {
/**
* Set the Database object to use
+ *
+ * @param $db DatabaseBase
*/
public function setDB( $db ) {
$this->db = $db;
}
+ /**
+ * Get the slave connection to the database
+ * When non existing, will initialize the connection.
+ * @return Database object
+ */
protected function getDB() {
if ( !isset( $this->db ) ) {
$this->db = wfGetDB( DB_SLAVE );
@@ -58,7 +116,7 @@ class BacklinkCache {
* @param $table String
* @param $startId Integer or false
* @param $endId Integer or false
- * @return TitleArray
+ * @return TitleArrayFromResult
*/
public function getLinks( $table, $startId = false, $endId = false ) {
wfProfileIn( __METHOD__ );
@@ -95,6 +153,7 @@ class BacklinkCache {
return $ta;
}
+ // @todo FIXME: Make this a function?
if ( !isset( $this->fullResultCache[$table] ) ) {
wfDebug( __METHOD__ . ": from DB\n" );
$res = $this->getDB()->select(
@@ -117,14 +176,15 @@ class BacklinkCache {
/**
* Get the field name prefix for a given table
+ * @param $table String
*/
protected function getPrefix( $table ) {
static $prefixes = array(
- 'pagelinks' => 'pl',
- 'imagelinks' => 'il',
+ 'pagelinks' => 'pl',
+ 'imagelinks' => 'il',
'categorylinks' => 'cl',
'templatelinks' => 'tl',
- 'redirect' => 'rd',
+ 'redirect' => 'rd',
);
if ( isset( $prefixes[$table] ) ) {
@@ -135,18 +195,32 @@ class BacklinkCache {
}
/**
- * Get the SQL condition array for selecting backlinks, with a join on the page table
+ * Get the SQL condition array for selecting backlinks, with a join
+ * on the page table.
+ * @param $table String
*/
protected function getConditions( $table ) {
$prefix = $this->getPrefix( $table );
+ // @todo FIXME: imagelinks and categorylinks do not rely on getNamespace,
+ // they could be moved up for nicer case statements
switch ( $table ) {
case 'pagelinks':
case 'templatelinks':
+ $conds = array(
+ "{$prefix}_namespace" => $this->title->getNamespace(),
+ "{$prefix}_title" => $this->title->getDBkey(),
+ "page_id={$prefix}_from"
+ );
+ break;
case 'redirect':
$conds = array(
"{$prefix}_namespace" => $this->title->getNamespace(),
- "{$prefix}_title" => $this->title->getDBkey(),
+ "{$prefix}_title" => $this->title->getDBkey(),
+ $this->getDb()->makeList( array(
+ "{$prefix}_interwiki = ''",
+ "{$prefix}_interwiki is null",
+ ), LIST_OR ),
"page_id={$prefix}_from"
);
break;
@@ -171,6 +245,8 @@ class BacklinkCache {
/**
* Get the approximate number of backlinks
+ * @param $table String
+ * @return integer
*/
public function getNumLinks( $table ) {
if ( isset( $this->fullResultCache[$table] ) ) {
@@ -189,15 +265,17 @@ class BacklinkCache {
/**
* Partition the backlinks into batches.
- * Returns an array giving the start and end of each range. The first batch has
- * a start of false, and the last batch has an end of false.
+ * Returns an array giving the start and end of each range. The first
+ * batch has a start of false, and the last batch has an end of false.
*
* @param $table String: the links table name
* @param $batchSize Integer
* @return Array
*/
public function partition( $table, $batchSize ) {
- // Try cache
+
+ // 1) try partition cache ...
+
if ( isset( $this->partitionCache[$table][$batchSize] ) ) {
wfDebug( __METHOD__ . ": got from partition cache\n" );
return $this->partitionCache[$table][$batchSize]['batches'];
@@ -206,7 +284,8 @@ class BacklinkCache {
$this->partitionCache[$table][$batchSize] = false;
$cacheEntry =& $this->partitionCache[$table][$batchSize];
- // Try full result cache
+ // 2) ... then try full result cache ...
+
if ( isset( $this->fullResultCache[$table] ) ) {
$cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
wfDebug( __METHOD__ . ": got from full result cache\n" );
@@ -214,7 +293,8 @@ class BacklinkCache {
return $cacheEntry['batches'];
}
- // Try memcached
+ // 3) ... fallback to memcached ...
+
global $wgMemc;
$memcKey = wfMemcKey(
@@ -233,7 +313,9 @@ class BacklinkCache {
return $cacheEntry['batches'];
}
- // Fetch from database
+
+ // 4) ... finally fetch from the slow database :(
+
$this->getLinks( $table );
$cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
// Save to memcached
@@ -245,6 +327,9 @@ class BacklinkCache {
/**
* Partition a DB result with backlinks in it into batches
+ * @param $res ResultWrapper database result
+ * @param $batchSize integer
+ * @return array @see
*/
protected function partitionResult( $res, $batchSize ) {
$batches = array();
diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php
deleted file mode 100644
index 63c96de7..00000000
--- a/includes/BagOStuff.php
+++ /dev/null
@@ -1,906 +0,0 @@
-<?php
-/**
- * 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.
- *
- * backends for local hash array and SQL table included:
- * <code>
- * $bag = new HashBagOStuff();
- * $bag = new SqlBagOStuff(); # connect to db first
- * </code>
- *
- * @ingroup Cache
- */
-abstract class BagOStuff {
- var $debugMode = false;
-
- public function set_debug( $bool ) {
- $this->debugMode = $bool;
- }
-
- /* *** THE GUTS OF THE OPERATION *** */
- /* Override these with functional things in subclasses */
-
- /**
- * Get an item with the given key. Returns false if it does not exist.
- * @param $key string
- */
- abstract public function get( $key );
-
- /**
- * Set an item.
- * @param $key string
- * @param $value mixed
- * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
- */
- abstract public function set( $key, $value, $exptime = 0 );
-
- /*
- * Delete an item.
- * @param $key string
- * @param $time int Amount of time to delay the operation (mostly memcached-specific)
- */
- abstract public function delete( $key, $time = 0 );
-
- public function lock( $key, $timeout = 0 ) {
- /* stub */
- return true;
- }
-
- public function unlock( $key ) {
- /* stub */
- return true;
- }
-
- public function keys() {
- /* stub */
- return array();
- }
-
- /* *** Emulated functions *** */
- /* Better performance can likely be got with custom written versions */
- public function get_multi( $keys ) {
- $out = array();
-
- foreach ( $keys as $key ) {
- $out[$key] = $this->get( $key );
- }
-
- return $out;
- }
-
- public function set_multi( $hash, $exptime = 0 ) {
- foreach ( $hash as $key => $value ) {
- $this->set( $key, $value, $exptime );
- }
- }
-
- public function add( $key, $value, $exptime = 0 ) {
- if ( !$this->get( $key ) ) {
- $this->set( $key, $value, $exptime );
-
- return true;
- }
- }
-
- public function add_multi( $hash, $exptime = 0 ) {
- foreach ( $hash as $key => $value ) {
- $this->add( $key, $value, $exptime );
- }
- }
-
- public function delete_multi( $keys, $time = 0 ) {
- foreach ( $keys as $key ) {
- $this->delete( $key, $time );
- }
- }
-
- public function replace( $key, $value, $exptime = 0 ) {
- if ( $this->get( $key ) !== false ) {
- $this->set( $key, $value, $exptime );
- }
- }
-
- /**
- * @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 null;
- }
-
- $value = intval( $value );
-
- if ( ( $n = $this->get( $key ) ) !== false ) {
- $n += $value;
- $this->set( $key, $n ); // exptime?
- }
- $this->unlock( $key );
-
- return $n;
- }
-
- public function decr( $key, $value = 1 ) {
- return $this->incr( $key, - $value );
- }
-
- public function debug( $text ) {
- 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 < 86400 * 3650 /* 10 years */ ) ) {
- return time() + $exptime;
- } else {
- return $exptime;
- }
- }
-}
-
-/**
- * Functional versions!
- * This is a test of the interface, mainly. It stores things in an associative
- * array, which is not going to persist between program runs.
- *
- * @ingroup Cache
- */
-class HashBagOStuff extends BagOStuff {
- var $bag;
-
- function __construct() {
- $this->bag = array();
- }
-
- protected function expire( $key ) {
- $et = $this->bag[$key][1];
-
- if ( ( $et == 0 ) || ( $et > time() ) ) {
- return false;
- }
-
- $this->delete( $key );
-
- return true;
- }
-
- function get( $key ) {
- if ( !isset( $this->bag[$key] ) ) {
- return false;
- }
-
- if ( $this->expire( $key ) ) {
- return false;
- }
-
- return $this->bag[$key][0];
- }
-
- function set( $key, $value, $exptime = 0 ) {
- $this->bag[$key] = array( $value, $this->convertExpiry( $exptime ) );
- }
-
- function delete( $key, $time = 0 ) {
- if ( !isset( $this->bag[$key] ) ) {
- return false;
- }
-
- unset( $this->bag[$key] );
-
- return true;
- }
-
- function keys() {
- return array_keys( $this->bag );
- }
-}
-
-/**
- * Class to store objects in the database
- *
- * @ingroup Cache
- */
-class SqlBagOStuff extends BagOStuff {
- var $lb, $db;
- var $lastExpireAll = 0;
-
- 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.
- * @todo Investigate behaviour for other databases
- */
- if ( $wgDBtype == 'sqlite' ) {
- $this->db = wfGetDB( DB_MASTER );
- } else {
- $this->lb = wfGetLBFactory()->newMainLB();
- $this->db = $this->lb->getConnection( DB_MASTER );
- $this->db->clearFlag( DBO_TRX );
- }
- }
-
- return $this->db;
- }
-
- public function get( $key ) {
- # expire old entries if any
- $this->garbageCollect();
- $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 {
- $db->begin();
- # Put the expiry time in the WHERE condition to avoid deleting a
- # newly-inserted value
- $db->delete( 'objectcache',
- array(
- 'keyname' => $key,
- 'exptime' => $row->exptime
- ), __METHOD__ );
- $db->commit();
- } 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 ) {
- $encExpiry = $this->getMaxDateTime();
- } else {
- if ( $exptime < 3.16e8 ) { # ~10 years
- $exptime += time();
- }
-
- $encExpiry = $db->timestamp( $exptime );
- }
- try {
- $db->begin();
- // (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 ) ),
- 'exptime' => $encExpiry
- ), __METHOD__ );
- $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;
- }
-
- public function incr( $key, $step = 1 ) {
- $db = $this->getDB();
- $step = intval( $step );
-
- try {
- $db->begin();
- $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ),
- array( 'keyname' => $key ), __METHOD__, array( 'FOR UPDATE' ) );
- if ( $row === false ) {
- // Missing
- $db->commit();
-
- return null;
- }
- $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ );
- if ( $this->isExpired( $row->exptime ) ) {
- // Expired, do not reinsert
- $db->commit();
-
- return null;
- }
-
- $oldValue = intval( $this->unserialize( $db->decodeBlob( $row->value ) ) );
- $newValue = $oldValue + $step;
- $db->insert( 'objectcache',
- array(
- 'keyname' => $key,
- 'value' => $db->encodeBlob( $this->serialize( $newValue ) ),
- 'exptime' => $row->exptime
- ), __METHOD__, 'IGNORE' );
-
- if ( $db->affectedRows() == 0 ) {
- // Race condition. See bug 28611
- $newValue = null;
- }
- $db->commit();
- } catch ( DBQueryError $e ) {
- $this->handleWriteError( $e );
-
- return null;
- }
-
- return $newValue;
- }
-
- public function keys() {
- $db = $this->getDB();
- $res = $db->select( 'objectcache', array( 'keyname' ), false, __METHOD__ );
- $result = array();
-
- foreach ( $res as $row ) {
- $result[] = $row->keyname;
- }
-
- return $result;
- }
-
- protected function isExpired( $exptime ) {
- return $exptime != $this->getMaxDateTime() && wfTimestamp( TS_UNIX, $exptime ) < time();
- }
-
- protected function getMaxDateTime() {
- if ( time() > 0x7fffffff ) {
- return $this->getDB()->timestamp( 1 << 62 );
- } else {
- return $this->getDB()->timestamp( 0x7fffffff );
- }
- }
-
- protected function garbageCollect() {
- /* Ignore 99% of requests */
- if ( !mt_rand( 0, 100 ) ) {
- $now = time();
- /* Avoid repeating the delete within a few seconds */
- if ( $now > ( $this->lastExpireAll + 1 ) ) {
- $this->lastExpireAll = $now;
- $this->expireAll();
- }
- }
- }
-
- public function expireAll() {
- $db = $this->getDB();
- $now = $db->timestamp();
-
- try {
- $db->begin();
- $db->delete( 'objectcache', array( 'exptime < ' . $db->addQuotes( $now ) ), __METHOD__ );
- $db->commit();
- } catch ( DBQueryError $e ) {
- $this->handleWriteError( $e );
- }
- }
-
- public function deleteAll() {
- $db = $this->getDB();
-
- try {
- $db->begin();
- $db->delete( 'objectcache', '*', __METHOD__ );
- $db->commit();
- } catch ( DBQueryError $e ) {
- $this->handleWriteError( $e );
- }
- }
-
- /**
- * Serialize an object and, if possible, compress the representation.
- * On typical message and page data, this can provide a 3X decrease
- * in storage requirements.
- *
- * @param $data mixed
- * @return string
- */
- protected function serialize( &$data ) {
- $serial = serialize( $data );
-
- if ( function_exists( 'gzdeflate' ) ) {
- return gzdeflate( $serial );
- } else {
- return $serial;
- }
- }
-
- /**
- * Unserialize and, if necessary, decompress an object.
- * @param $serial string
- * @return mixed
- */
- protected function unserialize( $serial ) {
- if ( function_exists( 'gzinflate' ) ) {
- $decomp = @gzinflate( $serial );
-
- if ( false !== $decomp ) {
- $serial = $decomp;
- }
- }
-
- $ret = unserialize( $serial );
-
- return $ret;
- }
-
- /**
- * Handle a DBQueryError which occurred during a write operation.
- * Ignore errors which are due to a read-only database, rethrow others.
- */
- 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 );
- }
-}
-
-/**
- * Backwards compatibility alias
- */
-class MediaWikiBagOStuff extends SqlBagOStuff { }
-
-/**
- * This is a wrapper for APC's shared memory functions
- *
- * @ingroup Cache
- */
-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;
- }
-
- public function keys() {
- $info = apc_cache_info( 'user' );
- $list = $info['cache_list'];
- $keys = array();
-
- foreach ( $list as $entry ) {
- $keys[] = $entry['info'];
- }
-
- return $keys;
- }
-}
-
-/**
- * This is a wrapper for eAccelerator's shared memory functions.
- *
- * This is basically identical to the deceased Turck MMCache version,
- * mostly because eAccelerator is based on Turck MMCache.
- *
- * @ingroup Cache
- */
-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;
- }
-}
-
-/**
- * Wrapper for XCache object caching functions; identical interface
- * to the APC wrapper
- *
- * @ingroup Cache
- */
-class XCacheBagOStuff extends BagOStuff {
- /**
- * Get a value from the XCache object cache
- *
- * @param $key String: cache key
- * @return mixed
- */
- public function get( $key ) {
- $val = xcache_get( $key );
-
- if ( is_string( $val ) ) {
- $val = unserialize( $val );
- }
-
- return $val;
- }
-
- /**
- * Store a value in the XCache 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 ) {
- xcache_set( $key, serialize( $value ), $expire );
-
- return true;
- }
-
- /**
- * Remove a value from the XCache object cache
- *
- * @param $key String: cache key
- * @param $time Int: not used in this implementation
- * @return bool
- */
- public function delete( $key, $time = 0 ) {
- xcache_unset( $key );
-
- return true;
- }
-}
-
-/**
- * Cache that uses DBA as a backend.
- * Slow due to the need to constantly open and close the file to avoid holding
- * writer locks. Intended for development use only, as a memcached workalike
- * for systems that don't have it.
- *
- * @ingroup Cache
- */
-class DBABagOStuff extends BagOStuff {
- var $mHandler, $mFile, $mReader, $mWriter, $mDisabled;
-
- 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" );
- $this->mHandler = $wgDBAhandler;
- }
-
- /**
- * Encode value and expiry for storage
- */
- function encode( $value, $expiry ) {
- # Convert to absolute time
- $expiry = $this->convertExpiry( $expiry );
-
- return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value );
- }
-
- /**
- * @return list containing value first and expiry second
- */
- function decode( $blob ) {
- if ( !is_string( $blob ) ) {
- return array( null, 0 );
- } else {
- return array(
- unserialize( substr( $blob, 11 ) ),
- intval( substr( $blob, 0, 10 ) )
- );
- }
- }
-
- function getReader() {
- if ( file_exists( $this->mFile ) ) {
- $handle = dba_open( $this->mFile, 'rl', $this->mHandler );
- } else {
- $handle = $this->getWriter();
- }
-
- if ( !$handle ) {
- wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
- }
-
- return $handle;
- }
-
- 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 );
-
- if ( !is_null( $val ) && $expiry && $expiry < time() ) {
- # Key is expired, delete it
- $handle = $this->getWriter();
- dba_delete( $key, $handle );
- dba_close( $handle );
- wfDebug( __METHOD__ . ": $key expired\n" );
- $val = null;
- }
-
- wfProfileOut( __METHOD__ );
- return $val;
- }
-
- 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;
- }
-
- 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 );
- $ret = dba_insert( $key, $blob, $handle );
- # This time if it failed then it will be handled by the caller like any other race
- }
- }
-
- dba_close( $handle );
-
- wfProfileOut( __METHOD__ );
- return $ret;
- }
-
- 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 7c5f0ddd..27181d86 100644
--- a/includes/Block.php
+++ b/includes/Block.php
@@ -1,53 +1,93 @@
<?php
/**
- * @file
* Blocks and bans object
- */
-
-/**
- * The block class
- * All the functions in this class assume the object is either explicitly
- * loaded or filled. It is not load-on-demand. There are no accessors.
*
- * Globals used: $wgAutoblockExpiry, $wgAntiLockFlags
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
*
- * @todo This could be used everywhere, but it isn't.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 Block {
- /* public*/ var $mAddress, $mUser, $mBy, $mReason, $mTimestamp, $mAuto, $mId, $mExpiry,
- $mRangeStart, $mRangeEnd, $mAnonOnly, $mEnableAutoblock, $mHideName,
- $mBlockEmail, $mByName, $mAngryAutoblock, $mAllowUsertalk;
- /* private */ var $mNetworkBits, $mIntegerAddr, $mForUpdate, $mFromMaster;
+ /* public*/ var $mReason, $mTimestamp, $mAuto, $mExpiry, $mHideName;
+
+ protected
+ $mId,
+ $mFromMaster,
- const EB_KEEP_EXPIRED = 1;
- const EB_FOR_UPDATE = 2;
- const EB_RANGE_ONLY = 4;
+ $mBlockEmail,
+ $mDisableUsertalk,
+ $mCreateAccount;
+ /// @var User|String
+ protected $target;
+
+ /// @var Block::TYPE_ constant. Can only be USER, IP or RANGE internally
+ protected $type;
+
+ /// @var User
+ protected $blocker;
+
+ /// @var Bool
+ protected $isHardblock = true;
+
+ /// @var Bool
+ protected $isAutoblocking = true;
+
+ # TYPE constants
+ const TYPE_USER = 1;
+ const TYPE_IP = 2;
+ const TYPE_RANGE = 3;
+ const TYPE_AUTO = 4;
+ const TYPE_ID = 5;
+
+ /**
+ * Constructor
+ * @todo FIXME: Don't know what the best format to have for this constructor is, but fourteen
+ * optional parameters certainly isn't it.
+ */
function __construct( $address = '', $user = 0, $by = 0, $reason = '',
$timestamp = 0, $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0,
- $hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byName = false )
+ $hideName = 0, $blockEmail = 0, $allowUsertalk = 0 )
{
- $this->mId = 0;
- # Expand valid IPv6 addresses
- $address = IP::sanitizeIP( $address );
- $this->mAddress = $address;
- $this->mUser = $user;
- $this->mBy = $by;
+ if( $timestamp === 0 ){
+ $timestamp = wfTimestampNow();
+ }
+
+ if( count( func_get_args() ) > 0 ){
+ # Soon... :D
+ # wfDeprecated( __METHOD__ . " with arguments" );
+ }
+
+ $this->setTarget( $address );
+ $this->setBlocker( User::newFromID( $by ) );
$this->mReason = $reason;
$this->mTimestamp = wfTimestamp( TS_MW, $timestamp );
$this->mAuto = $auto;
- $this->mAnonOnly = $anonOnly;
- $this->mCreateAccount = $createAccount;
- $this->mExpiry = self::decodeExpiry( $expiry );
- $this->mEnableAutoblock = $enableAutoblock;
+ $this->isHardblock( !$anonOnly );
+ $this->prevents( 'createaccount', $createAccount );
+ if ( $expiry == 'infinity' || $expiry == Block::infinity() ) {
+ $this->mExpiry = 'infinity';
+ } else {
+ $this->mExpiry = wfTimestamp( TS_MW, $expiry );
+ }
+ $this->isAutoblocking( $enableAutoblock );
$this->mHideName = $hideName;
- $this->mBlockEmail = $blockEmail;
- $this->mAllowUsertalk = $allowUsertalk;
- $this->mForUpdate = false;
+ $this->prevents( 'sendemail', $blockEmail );
+ $this->prevents( 'editownusertalk', !$allowUsertalk );
+
$this->mFromMaster = false;
- $this->mByName = $byName;
- $this->mAngryAutoblock = false;
- $this->initialiseRange();
}
/**
@@ -57,56 +97,54 @@ class Block {
*
* @param $address String: IP address of user/anon
* @param $user Integer: user id of user
- * @param $killExpired Boolean: delete expired blocks on load
* @return Block Object
+ * @deprecated since 1.18
*/
- public static function newFromDB( $address, $user = 0, $killExpired = true ) {
- $block = new Block;
- $block->load( $address, $user, $killExpired );
-
- if ( $block->isValid() ) {
- return $block;
- } else {
- return null;
- }
+ public static function newFromDB( $address, $user = 0 ) {
+ return self::newFromTarget( User::whoIs( $user ), $address );
}
/**
* Load a blocked user from their block id.
*
* @param $id Integer: Block id to search for
- * @return Block object
+ * @return Block object or null
*/
public static function newFromID( $id ) {
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->resultObject( $dbr->select( 'ipblocks', '*',
- array( 'ipb_id' => $id ), __METHOD__ ) );
- $block = new Block;
-
- if ( $block->loadFromResult( $res ) ) {
- return $block;
+ $res = $dbr->selectRow(
+ 'ipblocks',
+ '*',
+ array( 'ipb_id' => $id ),
+ __METHOD__
+ );
+ if ( $res ) {
+ return Block::newFromRow( $res );
} else {
return null;
}
}
/**
- * Check if two blocks are effectively equal
+ * Check if two blocks are effectively equal. Doesn't check irrelevant things like
+ * the blocking user or the block timestamp, only things which affect the blocked user *
*
- * @return Boolean
+ * @param $block Block
+ *
+ * @return bool
*/
public function equals( Block $block ) {
return (
- $this->mAddress == $block->mAddress
- && $this->mUser == $block->mUser
+ (string)$this->target == (string)$block->target
+ && $this->type == $block->type
&& $this->mAuto == $block->mAuto
- && $this->mAnonOnly == $block->mAnonOnly
- && $this->mCreateAccount == $block->mCreateAccount
+ && $this->isHardblock() == $block->isHardblock()
+ && $this->prevents( 'createaccount' ) == $block->prevents( 'createaccount' )
&& $this->mExpiry == $block->mExpiry
- && $this->mEnableAutoblock == $block->mEnableAutoblock
+ && $this->isAutoblocking() == $block->isAutoblocking()
&& $this->mHideName == $block->mHideName
- && $this->mBlockEmail == $block->mBlockEmail
- && $this->mAllowUsertalk == $block->mAllowUsertalk
+ && $this->prevents( 'sendemail' ) == $block->prevents( 'sendemail' )
+ && $this->prevents( 'editownusertalk' ) == $block->prevents( 'editownusertalk' )
&& $this->mReason == $block->mReason
);
}
@@ -114,251 +152,232 @@ class Block {
/**
* Clear all member variables in the current object. Does not clear
* the block from the DB.
+ * @deprecated since 1.18
*/
public function clear() {
- $this->mAddress = $this->mReason = $this->mTimestamp = '';
- $this->mId = $this->mAnonOnly = $this->mCreateAccount =
- $this->mEnableAutoblock = $this->mAuto = $this->mUser =
- $this->mBy = $this->mHideName = $this->mBlockEmail = $this->mAllowUsertalk = 0;
- $this->mByName = false;
+ # Noop
}
/**
- * Get the DB object and set the reference parameter to the select options.
- * The options array will contain FOR UPDATE if appropriate.
+ * Get a block from the DB, with either the given address or the given username
*
- * @param $options Array
- * @return Database
+ * @param $address string The IP address of the user, or blank to skip IP blocks
+ * @param $user int The user ID, or zero for anonymous users
+ * @return Boolean: the user is blocked from editing
+ * @deprecated since 1.18
*/
- protected function &getDBOptions( &$options ) {
- global $wgAntiLockFlags;
+ public function load( $address = '', $user = 0 ) {
+ wfDeprecated( __METHOD__ );
+ if( $user ){
+ $username = User::whoIs( $user );
+ $block = self::newFromTarget( $username, $address );
+ } else {
+ $block = self::newFromTarget( null, $address );
+ }
- if ( $this->mForUpdate || $this->mFromMaster ) {
- $db = wfGetDB( DB_MASTER );
- if ( !$this->mForUpdate || ( $wgAntiLockFlags & ALF_NO_BLOCK_LOCK ) ) {
- $options = array();
- } else {
- $options = array( 'FOR UPDATE' );
+ if( $block instanceof Block ){
+ # This is mildly evil, but hey, it's B/C :D
+ foreach( $block as $variable => $value ){
+ $this->$variable = $value;
}
+ return true;
} else {
- $db = wfGetDB( DB_SLAVE );
- $options = array();
+ return false;
}
-
- return $db;
}
/**
- * Get a block from the DB, with either the given address or the given username
- *
- * @param $address string The IP address of the user, or blank to skip IP blocks
- * @param $user int The user ID, or zero for anonymous users
- * @param $killExpired bool Whether to delete expired rows while loading
- * @return Boolean: the user is blocked from editing
- *
+ * Load a block from the database which affects the already-set $this->target:
+ * 1) A block directly on the given user or IP
+ * 2) A rangeblock encompasing the given IP (smallest first)
+ * 3) An autoblock on the given IP
+ * @param $vagueTarget User|String also search for blocks affecting this target. Doesn't
+ * make any sense to use TYPE_AUTO / TYPE_ID here. Leave blank to skip IP lookups.
+ * @return Bool whether a relevant block was found
*/
- public function load( $address = '', $user = 0, $killExpired = true ) {
- wfDebug( "Block::load: '$address', '$user', $killExpired\n" );
+ protected function newLoad( $vagueTarget = null ) {
+ $db = wfGetDB( $this->mFromMaster ? DB_MASTER : DB_SLAVE );
- $options = array();
- $db = $this->getDBOptions( $options );
-
- if ( 0 == $user && $address === '' ) {
- # Invalid user specification, not blocked
- $this->clear();
-
- return false;
+ if( $this->type !== null ){
+ $conds = array(
+ 'ipb_address' => array( (string)$this->target ),
+ );
+ } else {
+ $conds = array( 'ipb_address' => array() );
}
- # Try user block
- if ( $user ) {
- $res = $db->resultObject( $db->select( 'ipblocks', '*', array( 'ipb_user' => $user ),
- __METHOD__, $options ) );
-
- if ( $this->loadFromResult( $res, $killExpired ) ) {
- return true;
+ # Be aware that the != '' check is explicit, since empty values will be
+ # passed by some callers (bug 29116)
+ if( $vagueTarget != ''){
+ list( $target, $type ) = self::parseTarget( $vagueTarget );
+ switch( $type ) {
+ case self::TYPE_USER:
+ # Slightly wierd, but who are we to argue?
+ $conds['ipb_address'][] = (string)$target;
+ break;
+
+ case self::TYPE_IP:
+ $conds['ipb_address'][] = (string)$target;
+ $conds[] = self::getRangeCond( IP::toHex( $target ) );
+ $conds = $db->makeList( $conds, LIST_OR );
+ break;
+
+ case self::TYPE_RANGE:
+ list( $start, $end ) = IP::parseRange( $target );
+ $conds['ipb_address'][] = (string)$target;
+ $conds[] = self::getRangeCond( $start, $end );
+ $conds = $db->makeList( $conds, LIST_OR );
+ break;
+
+ default:
+ throw new MWException( "Tried to load block with invalid type" );
}
}
- # Try IP block
- # TODO: improve performance by merging this query with the autoblock one
- # Slightly tricky while handling killExpired as well
- if ( $address !== '' ) {
- $conds = array( 'ipb_address' => $address, 'ipb_auto' => 0 );
- $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
-
- if ( $this->loadFromResult( $res, $killExpired ) ) {
- if ( $user && $this->mAnonOnly ) {
- # Block is marked anon-only
- # Whitelist this IP address against autoblocks and range blocks
- # (but not account creation blocks -- bug 13611)
- if ( !$this->mCreateAccount ) {
- $this->clear();
- }
-
- return false;
- } else {
- return true;
- }
- }
- }
+ $res = $db->select( 'ipblocks', '*', $conds, __METHOD__ );
- # Try range block
- if ( $this->loadRange( $address, $killExpired, $user ) ) {
- if ( $user && $this->mAnonOnly ) {
- # Respect account creation blocks on logged-in users -- bug 13611
- if ( !$this->mCreateAccount ) {
- $this->clear();
- }
+ # This result could contain a block on the user, a block on the IP, and a russian-doll
+ # set of rangeblocks. We want to choose the most specific one, so keep a leader board.
+ $bestRow = null;
- return false;
- } else {
- return true;
- }
- }
+ # Lower will be better
+ $bestBlockScore = 100;
- # Try autoblock
- if ( $address ) {
- $conds = array( 'ipb_address' => $address, 'ipb_auto' => 1 );
+ # This is begging for $this = $bestBlock, but that's not allowed in PHP :(
+ $bestBlockPreventsEdit = null;
- if ( $user ) {
- $conds['ipb_anon_only'] = 0;
- }
+ foreach( $res as $row ){
+ $block = Block::newFromRow( $row );
- $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
+ # Don't use expired blocks
+ if( $block->deleteIfExpired() ){
+ continue;
+ }
- if ( $this->loadFromResult( $res, $killExpired ) ) {
- return true;
+ # Don't use anon only blocks on users
+ if( $this->type == self::TYPE_USER && !$block->isHardblock() ){
+ continue;
}
- }
- # Give up
- $this->clear();
- return false;
- }
+ if( $block->getType() == self::TYPE_RANGE ){
+ # This is the number of bits that are allowed to vary in the block, give
+ # or take some floating point errors
+ $end = wfBaseconvert( $block->getRangeEnd(), 16, 10 );
+ $start = wfBaseconvert( $block->getRangeStart(), 16, 10 );
+ $size = log( $end - $start + 1, 2 );
+
+ # This has the nice property that a /32 block is ranked equally with a
+ # single-IP block, which is exactly what it is...
+ $score = self::TYPE_RANGE - 1 + ( $size / 128 );
- /**
- * Fill in member variables from a result wrapper
- *
- * @param $res ResultWrapper: row from the ipblocks table
- * @param $killExpired Boolean: whether to delete expired rows while loading
- * @return Boolean
- */
- protected function loadFromResult( ResultWrapper $res, $killExpired = true ) {
- $ret = false;
-
- if ( 0 != $res->numRows() ) {
- # Get first block
- $row = $res->fetchObject();
- $this->initFromRow( $row );
-
- if ( $killExpired ) {
- # If requested, delete expired rows
- do {
- $killed = $this->deleteIfExpired();
- if ( $killed ) {
- $row = $res->fetchObject();
- if ( $row ) {
- $this->initFromRow( $row );
- }
- }
- } while ( $killed && $row );
-
- # If there were any left after the killing finished, return true
- if ( $row ) {
- $ret = true;
- }
} else {
- $ret = true;
+ $score = $block->getType();
+ }
+
+ if( $score < $bestBlockScore ){
+ $bestBlockScore = $score;
+ $bestRow = $row;
+ $bestBlockPreventsEdit = $block->prevents( 'edit' );
}
}
- $res->free();
- return $ret;
+ if( $bestRow !== null ){
+ $this->initFromRow( $bestRow );
+ $this->prevents( 'edit', $bestBlockPreventsEdit );
+ return true;
+ } else {
+ return false;
+ }
}
/**
- * Search the database for any range blocks matching the given address, and
- * load the row if one is found.
- *
- * @param $address String: IP address range
- * @param $killExpired Boolean: whether to delete expired rows while loading
- * @param $user Integer: if not 0, then sets ipb_anon_only
- * @return Boolean
+ * Get a set of SQL conditions which will select rangeblocks encompasing a given range
+ * @param $start String Hexadecimal IP representation
+ * @param $end String Hexadecimal IP represenation, or null to use $start = $end
+ * @return String
*/
- public function loadRange( $address, $killExpired = true, $user = 0 ) {
- $iaddr = IP::toHex( $address );
-
- if ( $iaddr === false ) {
- # Invalid address
- return false;
+ public static function getRangeCond( $start, $end = null ) {
+ if ( $end === null ) {
+ $end = $start;
}
+ # Per bug 14634, we want to include relevant active rangeblocks; for
+ # rangeblocks, we want to include larger ranges which enclose the given
+ # range. We know that all blocks must be smaller than $wgBlockCIDRLimit,
+ # so we can improve performance by filtering on a LIKE clause
+ $chunk = self::getIpFragment( $start );
+ $dbr = wfGetDB( DB_SLAVE );
+ $like = $dbr->buildLike( $chunk, $dbr->anyString() );
- # Only scan ranges which start in this /16, this improves search speed
- # Blocks should not cross a /16 boundary.
- $range = substr( $iaddr, 0, 4 );
+ # Fairly hard to make a malicious SQL statement out of hex characters,
+ # but stranger things have happened...
+ $safeStart = $dbr->addQuotes( $start );
+ $safeEnd = $dbr->addQuotes( $end );
- $options = array();
- $db = $this->getDBOptions( $options );
- $conds = array(
- 'ipb_range_start' . $db->buildLike( $range, $db->anyString() ),
- "ipb_range_start <= '$iaddr'",
- "ipb_range_end >= '$iaddr'"
+ return $dbr->makeList(
+ array(
+ "ipb_range_start $like",
+ "ipb_range_start <= $safeStart",
+ "ipb_range_end >= $safeEnd",
+ ),
+ LIST_AND
);
+ }
- if ( $user ) {
- $conds['ipb_anon_only'] = 0;
+ /**
+ * Get the component of an IP address which is certain to be the same between an IP
+ * address and a rangeblock containing that IP address.
+ * @param $hex String Hexadecimal IP representation
+ * @return String
+ */
+ protected static function getIpFragment( $hex ) {
+ global $wgBlockCIDRLimit;
+ if ( substr( $hex, 0, 3 ) == 'v6-' ) {
+ return 'v6-' . substr( substr( $hex, 3 ), 0, floor( $wgBlockCIDRLimit['IPv6'] / 4 ) );
+ } else {
+ return substr( $hex, 0, floor( $wgBlockCIDRLimit['IPv4'] / 4 ) );
}
-
- $res = $db->resultObject( $db->select( 'ipblocks', '*', $conds, __METHOD__, $options ) );
- $success = $this->loadFromResult( $res, $killExpired );
-
- return $success;
}
/**
* Given a database row from the ipblocks table, initialize
* member variables
- *
* @param $row ResultWrapper: a row from the ipblocks table
*/
- public function initFromRow( $row ) {
- $this->mAddress = $row->ipb_address;
+ protected function initFromRow( $row ) {
+ $this->setTarget( $row->ipb_address );
+ $this->setBlocker( User::newFromId( $row->ipb_by ) );
+
$this->mReason = $row->ipb_reason;
$this->mTimestamp = wfTimestamp( TS_MW, $row->ipb_timestamp );
- $this->mUser = $row->ipb_user;
- $this->mBy = $row->ipb_by;
$this->mAuto = $row->ipb_auto;
- $this->mAnonOnly = $row->ipb_anon_only;
- $this->mCreateAccount = $row->ipb_create_account;
- $this->mEnableAutoblock = $row->ipb_enable_autoblock;
- $this->mBlockEmail = $row->ipb_block_email;
- $this->mAllowUsertalk = $row->ipb_allow_usertalk;
$this->mHideName = $row->ipb_deleted;
$this->mId = $row->ipb_id;
- $this->mExpiry = self::decodeExpiry( $row->ipb_expiry );
- if ( isset( $row->user_name ) ) {
- $this->mByName = $row->user_name;
+ // I wish I didn't have to do this
+ $db = wfGetDB( DB_SLAVE );
+ if ( $row->ipb_expiry == $db->getInfinity() ) {
+ $this->mExpiry = 'infinity';
} else {
- $this->mByName = $row->ipb_by_text;
+ $this->mExpiry = wfTimestamp( TS_MW, $row->ipb_expiry );
}
- $this->mRangeStart = $row->ipb_range_start;
- $this->mRangeEnd = $row->ipb_range_end;
+ $this->isHardblock( !$row->ipb_anon_only );
+ $this->isAutoblocking( $row->ipb_enable_autoblock );
+
+ $this->prevents( 'createaccount', $row->ipb_create_account );
+ $this->prevents( 'sendemail', $row->ipb_block_email );
+ $this->prevents( 'editownusertalk', !$row->ipb_allow_usertalk );
}
/**
- * Once $mAddress has been set, get the range they came from.
- * Wrapper for IP::parseRange
+ * Create a new Block object from a database row
+ * @param $row ResultWrapper row from the ipblocks table
+ * @return Block
*/
- protected function initialiseRange() {
- $this->mRangeStart = '';
- $this->mRangeEnd = '';
-
- if ( $this->mUser == 0 ) {
- list( $this->mRangeStart, $this->mRangeEnd ) = IP::parseRange( $this->mAddress );
- }
+ public static function newFromRow( $row ){
+ $block = new Block;
+ $block->initFromRow( $row );
+ return $block;
}
/**
@@ -371,12 +390,12 @@ class Block {
return false;
}
- if ( !$this->mId ) {
- throw new MWException( "Block::delete() now requires that the mId member be filled\n" );
+ if ( !$this->getId() ) {
+ throw new MWException( "Block::delete() requires that the mId member be filled\n" );
}
$dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->mId ), __METHOD__ );
+ $dbw->delete( 'ipblocks', array( 'ipb_id' => $this->getId() ), __METHOD__ );
return $dbw->affectedRows() > 0;
}
@@ -385,16 +404,16 @@ class Block {
* Insert a block into the block table. Will fail if there is a conflicting
* block (same name and options) already in the database.
*
- * @return Boolean: whether or not the insertion was successful.
+ * @param $dbw DatabaseBase if you have one available
+ * @return mixed: false on failure, assoc array on success:
+ * ('id' => block ID, 'autoIds' => array of autoblock IDs)
*/
public function insert( $dbw = null ) {
wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
- if ( $dbw === null )
+ if ( $dbw === null ) {
$dbw = wfGetDB( DB_MASTER );
-
- $this->validateBlockParams();
- $this->initialiseRange();
+ }
# Don't collide with expired blocks
Block::purgeExpired();
@@ -402,139 +421,125 @@ class Block {
$ipb_id = $dbw->nextSequenceValue( 'ipblocks_ipb_id_seq' );
$dbw->insert(
'ipblocks',
- array(
- 'ipb_id' => $ipb_id,
- 'ipb_address' => $this->mAddress,
- 'ipb_user' => $this->mUser,
- 'ipb_by' => $this->mBy,
- 'ipb_by_text' => $this->mByName,
- 'ipb_reason' => $this->mReason,
- 'ipb_timestamp' => $dbw->timestamp( $this->mTimestamp ),
- 'ipb_auto' => $this->mAuto,
- 'ipb_anon_only' => $this->mAnonOnly,
- 'ipb_create_account' => $this->mCreateAccount,
- 'ipb_enable_autoblock' => $this->mEnableAutoblock,
- 'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
- 'ipb_range_start' => $this->mRangeStart,
- 'ipb_range_end' => $this->mRangeEnd,
- 'ipb_deleted' => intval( $this->mHideName ), // typecast required for SQLite
- 'ipb_block_email' => $this->mBlockEmail,
- 'ipb_allow_usertalk' => $this->mAllowUsertalk
- ),
- 'Block::insert',
+ $this->getDatabaseArray(),
+ __METHOD__,
array( 'IGNORE' )
);
$affected = $dbw->affectedRows();
+ $this->mId = $dbw->insertId();
- if ( $affected )
- $this->doRetroactiveAutoblock();
+ if ( $affected ) {
+ $auto_ipd_ids = $this->doRetroactiveAutoblock();
+ return array( 'id' => $this->mId, 'autoIds' => $auto_ipd_ids );
+ }
- return (bool)$affected;
+ return false;
}
/**
* Update a block in the DB with new parameters.
* The ID field needs to be loaded first.
+ *
+ * @return Int number of affected rows, which should probably be 1 or something's
+ * gone slightly awry
*/
public function update() {
wfDebug( "Block::update; timestamp {$this->mTimestamp}\n" );
$dbw = wfGetDB( DB_MASTER );
- $this->validateBlockParams();
-
$dbw->update(
'ipblocks',
- array(
- 'ipb_user' => $this->mUser,
- 'ipb_by' => $this->mBy,
- 'ipb_by_text' => $this->mByName,
- 'ipb_reason' => $this->mReason,
- 'ipb_timestamp' => $dbw->timestamp( $this->mTimestamp ),
- 'ipb_auto' => $this->mAuto,
- 'ipb_anon_only' => $this->mAnonOnly,
- 'ipb_create_account' => $this->mCreateAccount,
- 'ipb_enable_autoblock' => $this->mEnableAutoblock,
- 'ipb_expiry' => self::encodeExpiry( $this->mExpiry, $dbw ),
- 'ipb_range_start' => $this->mRangeStart,
- 'ipb_range_end' => $this->mRangeEnd,
- 'ipb_deleted' => $this->mHideName,
- 'ipb_block_email' => $this->mBlockEmail,
- 'ipb_allow_usertalk' => $this->mAllowUsertalk
- ),
- array( 'ipb_id' => $this->mId ),
- 'Block::update'
+ $this->getDatabaseArray( $dbw ),
+ array( 'ipb_id' => $this->getId() ),
+ __METHOD__
);
return $dbw->affectedRows();
}
/**
- * Make sure all the proper members are set to sane values
- * before adding/updating a block
+ * Get an array suitable for passing to $dbw->insert() or $dbw->update()
+ * @param $db DatabaseBase
+ * @return Array
*/
- protected function validateBlockParams() {
- # Unset ipb_anon_only for user blocks, makes no sense
- if ( $this->mUser ) {
- $this->mAnonOnly = 0;
+ protected function getDatabaseArray( $db = null ){
+ if( !$db ){
+ $db = wfGetDB( DB_SLAVE );
}
+ $expiry = $db->encodeExpiry( $this->mExpiry );
+
+ $a = array(
+ 'ipb_address' => (string)$this->target,
+ 'ipb_user' => $this->target instanceof User ? $this->target->getID() : 0,
+ 'ipb_by' => $this->getBlocker()->getId(),
+ 'ipb_by_text' => $this->getBlocker()->getName(),
+ 'ipb_reason' => $this->mReason,
+ 'ipb_timestamp' => $db->timestamp( $this->mTimestamp ),
+ 'ipb_auto' => $this->mAuto,
+ 'ipb_anon_only' => !$this->isHardblock(),
+ 'ipb_create_account' => $this->prevents( 'createaccount' ),
+ 'ipb_enable_autoblock' => $this->isAutoblocking(),
+ 'ipb_expiry' => $expiry,
+ 'ipb_range_start' => $this->getRangeStart(),
+ 'ipb_range_end' => $this->getRangeEnd(),
+ 'ipb_deleted' => intval( $this->mHideName ), // typecast required for SQLite
+ 'ipb_block_email' => $this->prevents( 'sendemail' ),
+ 'ipb_allow_usertalk' => !$this->prevents( 'editownusertalk' )
+ );
- # Unset ipb_enable_autoblock for IP blocks, makes no sense
- if ( !$this->mUser ) {
- $this->mEnableAutoblock = 0;
- }
+ return $a;
+ }
- # bug 18860: non-anon-only IP blocks should be allowed to block email
- if ( !$this->mUser && $this->mAnonOnly ) {
- $this->mBlockEmail = 0;
- }
+ /**
+ * Retroactively autoblocks the last IP used by the user (if it is a user)
+ * blocked by this Block.
+ *
+ * @return Array: block IDs of retroactive autoblocks made
+ */
+ protected function doRetroactiveAutoblock() {
+ $blockIds = array();
+ # If autoblock is enabled, autoblock the LAST IP(s) used
+ if ( $this->isAutoblocking() && $this->getType() == self::TYPE_USER ) {
+ wfDebug( "Doing retroactive autoblocks for " . $this->getTarget() . "\n" );
- if ( !$this->mByName ) {
- if ( $this->mBy ) {
- $this->mByName = User::whoIs( $this->mBy );
- } else {
- global $wgUser;
- $this->mByName = $wgUser->getName();
+ $continue = wfRunHooks(
+ 'PerformRetroactiveAutoblock', array( $this, &$blockIds ) );
+
+ if ( $continue ) {
+ self::defaultRetroactiveAutoblock( $this, $blockIds );
}
}
+ return $blockIds;
}
/**
* Retroactively autoblocks the last IP used by the user (if it is a user)
- * blocked by this Block.
+ * blocked by this Block. This will use the recentchanges table.
*
- * @return Boolean: whether or not a retroactive autoblock was made.
+ * @param Block $block
+ * @param Array &$blockIds
+ * @return Array: block IDs of retroactive autoblocks made
*/
- public function doRetroactiveAutoblock() {
+ protected static function defaultRetroactiveAutoblock( Block $block, array &$blockIds ) {
$dbr = wfGetDB( DB_SLAVE );
- # If autoblock is enabled, autoblock the LAST IP used
- # - stolen shamelessly from CheckUser_body.php
- if ( $this->mEnableAutoblock && $this->mUser ) {
- wfDebug( "Doing retroactive autoblocks for " . $this->mAddress . "\n" );
+ $options = array( 'ORDER BY' => 'rc_timestamp DESC' );
+ $conds = array( 'rc_user_text' => (string)$block->getTarget() );
- $options = array( 'ORDER BY' => 'rc_timestamp DESC' );
- $conds = array( 'rc_user_text' => $this->mAddress );
-
- if ( $this->mAngryAutoblock ) {
- // Block any IP used in the last 7 days. Up to five IPs.
- $conds[] = 'rc_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( time() - ( 7 * 86400 ) ) );
- $options['LIMIT'] = 5;
- } else {
- // Just the last IP used.
- $options['LIMIT'] = 1;
- }
+ // Just the last IP used.
+ $options['LIMIT'] = 1;
- $res = $dbr->select( 'recentchanges', array( 'rc_ip' ), $conds,
- __METHOD__ , $options );
+ $res = $dbr->select( 'recentchanges', array( 'rc_ip' ), $conds,
+ __METHOD__ , $options );
- if ( !$dbr->numRows( $res ) ) {
- # No results, don't autoblock anything
- wfDebug( "No IP found to retroactively autoblock\n" );
- } else {
- foreach ( $res as $row ) {
- if ( $row->rc_ip ) {
- $this->doAutoblock( $row->rc_ip );
- }
+ if ( !$dbr->numRows( $res ) ) {
+ # No results, don't autoblock anything
+ wfDebug( "No IP found to retroactively autoblock\n" );
+ } else {
+ foreach ( $res as $row ) {
+ if ( $row->rc_ip ) {
+ $id = $block->doAutoblock( $row->rc_ip );
+ if ( $id ) $blockIds[] = $id;
}
}
}
@@ -542,6 +547,7 @@ class Block {
/**
* Checks whether a given IP is on the autoblock whitelist.
+ * TODO: this probably belongs somewhere else, but not sure where...
*
* @param $ip String: The IP to check
* @return Boolean
@@ -587,73 +593,70 @@ class Block {
* Autoblocks the given IP, referring to this Block.
*
* @param $autoblockIP String: the IP to autoblock.
- * @param $justInserted Boolean: the main block was just inserted
- * @return Boolean: whether or not an autoblock was inserted.
+ * @return mixed: block ID if an autoblock was inserted, false if not.
*/
- public function doAutoblock( $autoblockIP, $justInserted = false ) {
+ public function doAutoblock( $autoblockIP ) {
# If autoblocks are disabled, go away.
- if ( !$this->mEnableAutoblock ) {
- return;
+ if ( !$this->isAutoblocking() ) {
+ return false;
}
- # Check for presence on the autoblock whitelist
- if ( Block::isWhitelistedFromAutoblocks( $autoblockIP ) ) {
- return;
+ # Check for presence on the autoblock whitelist.
+ if ( self::isWhitelistedFromAutoblocks( $autoblockIP ) ) {
+ return false;
}
- # # Allow hooks to cancel the autoblock.
+ # Allow hooks to cancel the autoblock.
if ( !wfRunHooks( 'AbortAutoblock', array( $autoblockIP, &$this ) ) ) {
wfDebug( "Autoblock aborted by hook.\n" );
return false;
}
- # It's okay to autoblock. Go ahead and create/insert the block.
+ # It's okay to autoblock. Go ahead and insert/update the block...
- $ipblock = Block::newFromDB( $autoblockIP );
+ # Do not add a *new* block if the IP is already blocked.
+ $ipblock = Block::newFromTarget( $autoblockIP );
if ( $ipblock ) {
- # If the user is already blocked. Then check if the autoblock would
- # exceed the user block. If it would exceed, then do nothing, else
- # prolong block time
- if ( $this->mExpiry &&
- ( $this->mExpiry < Block::getAutoblockExpiry( $ipblock->mTimestamp ) )
+ # Check if the block is an autoblock and would exceed the user block
+ # if renewed. If so, do nothing, otherwise prolong the block time...
+ if ( $ipblock->mAuto && // @TODO: why not compare $ipblock->mExpiry?
+ $this->mExpiry > Block::getAutoblockExpiry( $ipblock->mTimestamp )
) {
- return;
- }
-
- # Just update the timestamp
- if ( !$justInserted ) {
+ # Reset block timestamp to now and its expiry to
+ # $wgAutoblockExpiry in the future
$ipblock->updateTimestamp();
}
+ return false;
+ }
- return;
- } else {
- $ipblock = new Block;
- }
-
- # Make a new block object with the desired properties
- wfDebug( "Autoblocking {$this->mAddress}@" . $autoblockIP . "\n" );
- $ipblock->mAddress = $autoblockIP;
- $ipblock->mUser = 0;
- $ipblock->mBy = $this->mBy;
- $ipblock->mByName = $this->mByName;
- $ipblock->mReason = wfMsgForContent( 'autoblocker', $this->mAddress, $this->mReason );
- $ipblock->mTimestamp = wfTimestampNow();
- $ipblock->mAuto = 1;
- $ipblock->mCreateAccount = $this->mCreateAccount;
+ # Make a new block object with the desired properties.
+ $autoblock = new Block;
+ wfDebug( "Autoblocking {$this->getTarget()}@" . $autoblockIP . "\n" );
+ $autoblock->setTarget( $autoblockIP );
+ $autoblock->setBlocker( $this->getBlocker() );
+ $autoblock->mReason = wfMsgForContent( 'autoblocker', $this->getTarget(), $this->mReason );
+ $timestamp = wfTimestampNow();
+ $autoblock->mTimestamp = $timestamp;
+ $autoblock->mAuto = 1;
+ $autoblock->prevents( 'createaccount', $this->prevents( 'createaccount' ) );
# Continue suppressing the name if needed
- $ipblock->mHideName = $this->mHideName;
- $ipblock->mAllowUsertalk = $this->mAllowUsertalk;
+ $autoblock->mHideName = $this->mHideName;
+ $autoblock->prevents( 'editownusertalk', $this->prevents( 'editownusertalk' ) );
- # If the user is already blocked with an expiry date, we don't
- # want to pile on top of that!
- if ( $this->mExpiry ) {
- $ipblock->mExpiry = min( $this->mExpiry, Block::getAutoblockExpiry( $this->mTimestamp ) );
+ if ( $this->mExpiry == 'infinity' ) {
+ # Original block was indefinite, start an autoblock now
+ $autoblock->mExpiry = Block::getAutoblockExpiry( $timestamp );
} else {
- $ipblock->mExpiry = Block::getAutoblockExpiry( $this->mTimestamp );
+ # If the user is already blocked with an expiry date, we don't
+ # want to pile on top of that.
+ $autoblock->mExpiry = min( $this->mExpiry, Block::getAutoblockExpiry( $timestamp ) );
}
- # Insert it
- return $ipblock->insert();
+ # Insert the block...
+ $status = $autoblock->insert();
+ return $status
+ ? $status['id']
+ : false;
}
/**
@@ -681,12 +684,13 @@ class Block {
* @return Boolean
*/
public function isExpired() {
- wfDebug( "Block::isExpired() checking current " . wfTimestampNow() . " vs $this->mExpiry\n" );
+ $timestamp = wfTimestampNow();
+ wfDebug( "Block::isExpired() checking current " . $timestamp . " vs $this->mExpiry\n" );
if ( !$this->mExpiry ) {
return false;
} else {
- return wfTimestampNow() > $this->mExpiry;
+ return $timestamp > $this->mExpiry;
}
}
@@ -695,7 +699,7 @@ class Block {
* @return Boolean
*/
public function isValid() {
- return $this->mAddress != '';
+ return $this->getTarget() != null;
}
/**
@@ -711,20 +715,58 @@ class Block {
array( /* SET */
'ipb_timestamp' => $dbw->timestamp( $this->mTimestamp ),
'ipb_expiry' => $dbw->timestamp( $this->mExpiry ),
- ), array( /* WHERE */
- 'ipb_address' => $this->mAddress
- ), 'Block::updateTimestamp'
+ ),
+ array( /* WHERE */
+ 'ipb_address' => (string)$this->getTarget()
+ ),
+ __METHOD__
);
}
}
/**
+ * Get the IP address at the start of the range in Hex form
+ * @return String IP in Hex form
+ */
+ public function getRangeStart() {
+ switch( $this->type ) {
+ case self::TYPE_USER:
+ return '';
+ case self::TYPE_IP:
+ return IP::toHex( $this->target );
+ case self::TYPE_RANGE:
+ list( $start, /*...*/ ) = IP::parseRange( $this->target );
+ return $start;
+ default: throw new MWException( "Block with invalid type" );
+ }
+ }
+
+ /**
+ * Get the IP address at the start of the range in Hex form
+ * @return String IP in Hex form
+ */
+ public function getRangeEnd() {
+ switch( $this->type ) {
+ case self::TYPE_USER:
+ return '';
+ case self::TYPE_IP:
+ return IP::toHex( $this->target );
+ case self::TYPE_RANGE:
+ list( /*...*/, $end ) = IP::parseRange( $this->target );
+ return $end;
+ default: throw new MWException( "Block with invalid type" );
+ }
+ }
+
+ /**
* Get the user id of the blocking sysop
*
* @return Integer
*/
public function getBy() {
- return $this->mBy;
+ return $this->getBlocker() instanceof User
+ ? $this->getBlocker()->getId()
+ : 0;
}
/**
@@ -733,32 +775,102 @@ class Block {
* @return String
*/
public function getByName() {
- return $this->mByName;
+ return $this->getBlocker() instanceof User
+ ? $this->getBlocker()->getName()
+ : null;
+ }
+
+ /**
+ * Get the block ID
+ * @return int
+ */
+ public function getId() {
+ return $this->mId;
}
/**
* Get/set the SELECT ... FOR UPDATE flag
+ * @deprecated since 1.18
+ *
+ * @param $x Bool
*/
public function forUpdate( $x = null ) {
- return wfSetVar( $this->mForUpdate, $x );
+ # noop
}
/**
* Get/set a flag determining whether the master is used for reads
+ *
+ * @param $x Bool
+ * @return Bool
*/
public function fromMaster( $x = null ) {
return wfSetVar( $this->mFromMaster, $x );
}
/**
+ * Get/set whether the Block is a hardblock (affects logged-in users on a given IP/range
+ * @param $x Bool
+ * @return Bool
+ */
+ public function isHardblock( $x = null ) {
+ wfSetVar( $this->isHardblock, $x );
+
+ # You can't *not* hardblock a user
+ return $this->getType() == self::TYPE_USER
+ ? true
+ : $this->isHardblock;
+ }
+
+ public function isAutoblocking( $x = null ) {
+ wfSetVar( $this->isAutoblocking, $x );
+
+ # You can't put an autoblock on an IP or range as we don't have any history to
+ # look over to get more IPs from
+ return $this->getType() == self::TYPE_USER
+ ? $this->isAutoblocking
+ : false;
+ }
+
+ /**
+ * Get/set whether the Block prevents a given action
+ * @param $action String
+ * @param $x Bool
+ * @return Bool
+ */
+ public function prevents( $action, $x = null ) {
+ switch( $action ) {
+ case 'edit':
+ # For now... <evil laugh>
+ return true;
+
+ case 'createaccount':
+ return wfSetVar( $this->mCreateAccount, $x );
+
+ case 'sendemail':
+ return wfSetVar( $this->mBlockEmail, $x );
+
+ case 'editownusertalk':
+ return wfSetVar( $this->mDisableUsertalk, $x );
+
+ default:
+ return null;
+ }
+ }
+
+ /**
* Get the block name, but with autoblocked IPs hidden as per standard privacy policy
- * @return String
+ * @return String, text is escaped
*/
public function getRedactedName() {
if ( $this->mAuto ) {
- return '#' . $this->mId;
+ return Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-autoblockid' ),
+ wfMessage( 'autoblockid', $this->mId )
+ );
} else {
- return $this->mAddress;
+ return htmlspecialchars( $this->getTarget() );
}
}
@@ -768,33 +880,29 @@ class Block {
* @param $expiry String: timestamp for expiry, or
* @param $db Database object
* @return String
+ * @deprecated since 1.18; use $dbw->encodeExpiry() instead
*/
public static function encodeExpiry( $expiry, $db ) {
- if ( $expiry == '' || $expiry == Block::infinity() ) {
- return Block::infinity();
- } else {
- return $db->timestamp( $expiry );
- }
+ return $db->encodeExpiry( $expiry );
}
/**
* Decode expiry which has come from the DB
*
* @param $expiry String: Database expiry format
- * @param $timestampType Requested timestamp format
+ * @param $timestampType Int Requested timestamp format
* @return String
+ * @deprecated since 1.18; use $wgLang->decodeExpiry() instead
*/
public static function decodeExpiry( $expiry, $timestampType = TS_MW ) {
- if ( $expiry == '' || $expiry == Block::infinity() ) {
- return Block::infinity();
- } else {
- return wfTimestamp( $timestampType, $expiry );
- }
+ global $wgContLang;
+ return $wgContLang->formatExpiry( $expiry, $timestampType );
}
/**
* Get a timestamp of the expiry for autoblocks
*
+ * @param $timestamp String|Int
* @return String
*/
public static function getAutoblockExpiry( $timestamp ) {
@@ -808,35 +916,10 @@ class Block {
* For example, 127.111.113.151/24 -> 127.111.113.0/24
* @param $range String: IP address to normalize
* @return string
+ * @deprecated since 1.18, call IP::sanitizeRange() directly
*/
public static function normaliseRange( $range ) {
- $parts = explode( '/', $range );
- if ( count( $parts ) == 2 ) {
- // IPv6
- if ( IP::isIPv6( $range ) && $parts[1] >= 64 && $parts[1] <= 128 ) {
- $bits = $parts[1];
- $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 );
- # 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 );
- # Reform octet address
- $newip = IP::toOctet( $network );
- $range = "$newip/{$parts[1]}";
- } // IPv4
- elseif ( IP::isIPv4( $range ) && $parts[1] >= 16 && $parts[1] <= 32 ) {
- $shift = 32 - $parts[1];
- $ipint = IP::toUnsigned( $parts[0] );
- $ipint = $ipint >> $shift << $shift;
- $newip = long2ip( $ipint );
- $range = "$newip/{$parts[1]}";
- }
- }
-
- return $range;
+ return IP::sanitizeRange( $range );
}
/**
@@ -849,25 +932,12 @@ class Block {
/**
* Get a value to insert into expiry field of the database when infinite expiry
- * is desired. In principle this could be DBMS-dependant, but currently all
- * supported DBMS's support the string "infinity", so we just use that.
- *
+ * is desired
+ * @deprecated since 1.18, call $dbr->getInfinity() directly
* @return String
*/
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';
+ return wfGetDB( DB_SLAVE )->getInfinity();
}
/**
@@ -875,8 +945,10 @@ class Block {
*
* @param $encoded_expiry String: Database encoded expiry time
* @return Html-escaped String
+ * @deprecated since 1.18; use $wgLang->formatExpiry() instead
*/
public static function formatExpiry( $encoded_expiry ) {
+ global $wgContLang;
static $msg = null;
if ( is_null( $msg ) ) {
@@ -888,8 +960,8 @@ class Block {
}
}
- $expiry = Block::decodeExpiry( $encoded_expiry );
- if ( $expiry == 'infinity' ) {
+ $expiry = $wgContLang->formatExpiry( $encoded_expiry, TS_MW );
+ if ( $expiry == wfGetDB( DB_SLAVE )->getInfinity() ) {
$expirystr = $msg['infiniteblock'];
} else {
global $wgLang;
@@ -902,21 +974,179 @@ class Block {
}
/**
- * Convert a typed-in expiry time into something we can put into the database.
- * @param $expiry_input String: whatever was typed into the form
- * @return String: more database friendly
+ * Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute
+ * ("24 May 2034"), into an absolute timestamp we can put into the database.
+ * @param $expiry String: whatever was typed into the form
+ * @return String: timestamp or "infinity" string for th DB implementation
+ * @deprecated since 1.18 moved to SpecialBlock::parseExpiryInput()
*/
- public static function parseExpiryInput( $expiry_input ) {
- if ( $expiry_input == 'infinite' || $expiry_input == 'indefinite' ) {
- $expiry = 'infinity';
+ public static function parseExpiryInput( $expiry ) {
+ wfDeprecated( __METHOD__ );
+ return SpecialBlock::parseExpiryInput( $expiry );
+ }
+
+ /**
+ * Given a target and the target's type, get an existing Block object if possible.
+ * @param $specificTarget String|User|Int a block target, which may be one of several types:
+ * * A user to block, in which case $target will be a User
+ * * An IP to block, in which case $target will be a User generated by using
+ * User::newFromName( $ip, false ) to turn off name validation
+ * * An IP range, in which case $target will be a String "123.123.123.123/18" etc
+ * * The ID of an existing block, in the format "#12345" (since pure numbers are valid
+ * usernames
+ * Calling this with a user, IP address or range will not select autoblocks, and will
+ * only select a block where the targets match exactly (so looking for blocks on
+ * 1.2.3.4 will not select 1.2.0.0/16 or even 1.2.3.4/32)
+ * @param $vagueTarget String|User|Int as above, but we will search for *any* block which
+ * affects that target (so for an IP address, get ranges containing that IP; and also
+ * get any relevant autoblocks). Leave empty or blank to skip IP-based lookups.
+ * @param $fromMaster Bool whether to use the DB_MASTER database
+ * @return Block|null (null if no relevant block could be found). The target and type
+ * of the returned Block will refer to the actual block which was found, which might
+ * not be the same as the target you gave if you used $vagueTarget!
+ */
+ public static function newFromTarget( $specificTarget, $vagueTarget = null, $fromMaster = false ) {
+
+ list( $target, $type ) = self::parseTarget( $specificTarget );
+ if( $type == Block::TYPE_ID || $type == Block::TYPE_AUTO ){
+ return Block::newFromID( $target );
+
+ } elseif( $target === null && $vagueTarget == '' ){
+ # We're not going to find anything useful here
+ # Be aware that the == '' check is explicit, since empty values will be
+ # passed by some callers (bug 29116)
+ return null;
+
+ } elseif( in_array( $type, array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) ) {
+ $block = new Block();
+ $block->fromMaster( $fromMaster );
+
+ if( $type !== null ){
+ $block->setTarget( $target );
+ }
+
+ if( $block->newLoad( $vagueTarget ) ){
+ return $block;
+ } else {
+ return null;
+ }
} else {
- $expiry = strtotime( $expiry_input );
+ return null;
+ }
+ }
- if ( $expiry < 0 || $expiry === false ) {
- return false;
+ /**
+ * From an existing Block, get the target and the type of target. Note that it is
+ * always safe to treat the target as a string; for User objects this will return
+ * User::__toString() which in turn gives User::getName().
+ *
+ * @param $target String|Int|User
+ * @return array( User|String, Block::TYPE_ constant )
+ */
+ public static function parseTarget( $target ) {
+ $target = trim( $target );
+
+ # We may have been through this before
+ if( $target instanceof User ){
+ if( IP::isValid( $target->getName() ) ){
+ return array( $target, self::TYPE_IP );
+ } else {
+ return array( $target, self::TYPE_USER );
}
+ } elseif( $target === null ){
+ return array( null, null );
+ }
+
+ if ( IP::isValid( $target ) ) {
+ # We can still create a User if it's an IP address, but we need to turn
+ # off validation checking (which would exclude IP addresses)
+ return array(
+ User::newFromName( IP::sanitizeIP( $target ), false ),
+ Block::TYPE_IP
+ );
+
+ } elseif ( IP::isValidBlock( $target ) ) {
+ # Can't create a User from an IP range
+ return array( IP::sanitizeRange( $target ), Block::TYPE_RANGE );
}
- return $expiry;
+ # Consider the possibility that this is not a username at all
+ # but actually an old subpage (bug #29797)
+ if( strpos( $target, '/' ) !== false ){
+ # An old subpage, drill down to the user behind it
+ $parts = explode( '/', $target );
+ $target = $parts[0];
+ }
+
+ $userObj = User::newFromName( $target );
+ if ( $userObj instanceof User ) {
+ # Note that since numbers are valid usernames, a $target of "12345" will be
+ # considered a User. If you want to pass a block ID, prepend a hash "#12345",
+ # since hash characters are not valid in usernames or titles generally.
+ return array( $userObj, Block::TYPE_USER );
+
+ } elseif ( preg_match( '/^#\d+$/', $target ) ) {
+ # Autoblock reference in the form "#12345"
+ return array( substr( $target, 1 ), Block::TYPE_AUTO );
+
+ } else {
+ # WTF?
+ return array( null, null );
+ }
+ }
+
+ /**
+ * Get the type of target for this particular block
+ * @return Block::TYPE_ constant, will never be TYPE_ID
+ */
+ public function getType() {
+ return $this->mAuto
+ ? self::TYPE_AUTO
+ : $this->type;
+ }
+
+ /**
+ * Get the target and target type for this particular Block. Note that for autoblocks,
+ * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
+ * in this situation.
+ * @return array( User|String, Block::TYPE_ constant )
+ * @todo FIXME: This should be an integral part of the Block member variables
+ */
+ public function getTargetAndType() {
+ return array( $this->getTarget(), $this->getType() );
+ }
+
+ /**
+ * Get the target for this particular Block. Note that for autoblocks,
+ * this returns the unredacted name; frontend functions need to call $block->getRedactedName()
+ * in this situation.
+ * @return User|String
+ */
+ public function getTarget() {
+ return $this->target;
+ }
+
+ /**
+ * Set the target for this block, and update $this->type accordingly
+ * @param $target Mixed
+ */
+ public function setTarget( $target ){
+ list( $this->target, $this->type ) = self::parseTarget( $target );
+ }
+
+ /**
+ * Get the user who implemented this block
+ * @return User
+ */
+ public function getBlocker(){
+ return $this->blocker;
+ }
+
+ /**
+ * Set the user who implemented (or will implement) this block
+ * @param $user User
+ */
+ public function setBlocker( User $user ){
+ $this->blocker = $user;
}
}
diff --git a/includes/Category.php b/includes/Category.php
index 614933ff..9d9b5a67 100644
--- a/includes/Category.php
+++ b/includes/Category.php
@@ -13,7 +13,10 @@ class Category {
/** Name of the category, normalized to DB-key form */
private $mName = null;
private $mID = null;
- /** Category page title */
+ /**
+ * Category page title
+ * @var Title
+ */
private $mTitle = null;
/** Counts of membership (cat_pages, cat_subcats, cat_files) */
private $mPages = null, $mSubcats = null, $mFiles = null;
@@ -100,7 +103,7 @@ class Category {
* Factory function.
*
* @param $title Title for the category page
- * @return Mixed: category, or false on a totally invalid name
+ * @return category|false on a totally invalid name
*/
public static function newFromTitle( $title ) {
$cat = new self();
@@ -129,7 +132,7 @@ class Category {
* @param $row result set row, must contain the cat_xxx fields. If the fields are null,
* the resulting Category object will represent an empty category if a title object
* was given. If the fields are null and no title was given, this method fails and returns false.
- * @param $title optional title object for the category represented by the given row.
+ * @param Title $title optional title object for the category represented by the given row.
* May be provided if it is already known, to avoid having to re-create a title object later.
* @return Category
*/
@@ -182,7 +185,7 @@ class Category {
public function getFileCount() { return $this->getX( 'mFiles' ); }
/**
- * @return mixed The Title for this category, or false on failure.
+ * @return Title|false Title for this category, or false on failure.
*/
public function getTitle() {
if ( $this->mTitle ) return $this->mTitle;
diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php
index f990b79b..6a0f6132 100644
--- a/includes/CategoryPage.php
+++ b/includes/CategoryPage.php
@@ -1,6 +1,6 @@
<?php
/**
- * Special handling for category description pages.
+ * Class for viewing MediaWiki category description pages.
* Modelled after ImagePage.php.
*
* @file
@@ -17,6 +17,22 @@ class CategoryPage extends Article {
# Subclasses can change this to override the viewer class.
protected $mCategoryViewerClass = 'CategoryViewer';
+ protected function newPage( Title $title ) {
+ // Overload mPage with a category-specific page
+ return new WikiCategoryPage( $title );
+ }
+
+ /**
+ * Constructor from a page id
+ * @param $id Int article ID to load
+ */
+ public static function newFromID( $id ) {
+ $t = Title::newFromID( $id );
+ # @todo FIXME: Doesn't inherit right
+ return $t == null ? null : new self( $t );
+ # return $t == null ? null : new static( $t ); // PHP 5.3
+ }
+
function view() {
global $wgRequest, $wgUser;
@@ -42,27 +58,6 @@ 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 );
- // If any of these are not 0, then has members
- if ( $cat->getPageCount()
- || $cat->getSubcatCount()
- || $cat->getFileCount()
- ) {
- return true;
- }
- }
- return false;
- }
-
function openShowCategory() {
# For overloading
}
@@ -70,27 +65,77 @@ class CategoryPage extends Article {
function closeShowCategory() {
global $wgOut, $wgRequest;
+ // Use these as defaults for back compat --catrope
+ $oldFrom = $wgRequest->getVal( 'from' );
+ $oldUntil = $wgRequest->getVal( 'until' );
+
+ $reqArray = $wgRequest->getValues();
+
$from = $until = array();
foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
- $from[$type] = $wgRequest->getVal( "{$type}from" );
- $until[$type] = $wgRequest->getVal( "{$type}until" );
+ $from[$type] = $wgRequest->getVal( "{$type}from", $oldFrom );
+ $until[$type] = $wgRequest->getVal( "{$type}until", $oldUntil );
+
+ // Do not want old-style from/until propagating in nav links.
+ if ( !isset( $reqArray["{$type}from"] ) && isset( $reqArray["from"] ) ) {
+ $reqArray["{$type}from"] = $reqArray["from"];
+ }
+ if ( !isset( $reqArray["{$type}to"] ) && isset( $reqArray["to"] ) ) {
+ $reqArray["{$type}to"] = $reqArray["to"];
+ }
}
- $viewer = new $this->mCategoryViewerClass( $this->mTitle, $from, $until, $wgRequest->getValues() );
+ unset( $reqArray["from"] );
+ unset( $reqArray["to"] );
+
+ $viewer = new $this->mCategoryViewerClass( $this->mTitle, $from, $until, $reqArray );
$wgOut->addHTML( $viewer->getHTML() );
}
}
class CategoryViewer {
- var $title, $limit, $from, $until,
+ var $limit, $from, $until,
$articles, $articles_start_char,
$children, $children_start_char,
- $showGallery, $gallery,
- $imgsNoGalley, $imgsNoGallery_start_char,
- $skin, $collation;
- # Category object for this page
+ $showGallery, $imgsNoGalley,
+ $imgsNoGallery_start_char,
+ $imgsNoGallery;
+
+ /**
+ * @var
+ */
+ var $nextPage;
+
+ /**
+ * @var Array
+ */
+ var $flip;
+
+ /**
+ * @var Title
+ */
+ var $title;
+
+ /**
+ * @var Collation
+ */
+ var $collation;
+
+ /**
+ * @var ImageGallery
+ */
+ var $gallery;
+
+ /**
+ * Category object for this page
+ * @var Category
+ */
private $cat;
- # The original query array, to be used in generating paging links.
+
+ /**
+ * The original query array, to be used in generating paging links.
+ * @var array
+ */
private $query;
function __construct( $title, $from = '', $until = '', $query = array() ) {
@@ -111,7 +156,7 @@ class CategoryViewer {
* @return string HTML output
*/
public function getHTML() {
- global $wgOut, $wgCategoryMagicGallery, $wgContLang;
+ global $wgOut, $wgCategoryMagicGallery, $wgLang, $wgContLang;
wfProfileIn( __METHOD__ );
$this->showGallery = $wgCategoryMagicGallery && !$wgOut->mNoGallery;
@@ -127,7 +172,7 @@ class CategoryViewer {
if ( $r == '' ) {
// If there is no category content to display, only
// show the top part of the navigation links.
- // FIXME: cannot be completely suppressed because it
+ // @todo FIXME: Cannot be completely suppressed because it
// is unknown if 'until' or 'from' makes this
// give 0 results.
$r = $r . $this->getCategoryTop();
@@ -142,6 +187,12 @@ class CategoryViewer {
$r = wfMsgExt( 'category-empty', array( 'parse' ) );
}
+ $pageLang = $this->title->getPageLanguage();
+ $langAttribs = array( 'lang' => $wgLang->getCode(), 'dir' => $wgLang->getDir() );
+ # close the previous div, show the headings in user language,
+ # then open a new div with the page content language again
+ $r = Html::openElement( 'div', $langAttribs ) . $r . '</div>';
+
wfProfileOut( __METHOD__ );
return $wgContLang->convert( $r );
}
@@ -160,14 +211,6 @@ class CategoryViewer {
}
}
- function getSkin() {
- if ( !$this->skin ) {
- global $wgUser;
- $this->skin = $wgUser->getSkin();
- }
- return $this->skin;
- }
-
/**
* Add a subcategory to the internal lists, using a Category object
*/
@@ -175,7 +218,7 @@ class CategoryViewer {
// Subcategory; strip the 'Category' namespace from the link text.
$title = $cat->getTitle();
- $link = $this->getSkin()->link( $title, $title->getText() );
+ $link = Linker::link( $title, htmlspecialchars( $title->getText() ) );
if ( $title->isRedirect() ) {
// This didn't used to add redirect-in-category, but might
// as well be consistent with the rest of the sections
@@ -190,7 +233,7 @@ class CategoryViewer {
/**
* Add a subcategory to the internal lists, using a title object
- * @deprecated kept for compatibility, please use addSubcategoryObject instead
+ * @deprecated since 1.17 kept for compatibility, please use addSubcategoryObject instead
*/
function addSubcategory( Title $title, $sortkey, $pageLength ) {
$this->addSubcategoryObject( Category::newFromTitle( $title ), $sortkey, $pageLength );
@@ -233,7 +276,7 @@ class CategoryViewer {
$this->gallery->add( $title );
}
} else {
- $link = $this->getSkin()->link( $title );
+ $link = Linker::link( $title );
if ( $isRedirect ) {
// This seems kind of pointless given 'mw-redirect' class,
// but keeping for back-compatibility with user css.
@@ -252,7 +295,7 @@ class CategoryViewer {
function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) {
global $wgContLang;
- $link = $this->getSkin()->link( $title );
+ $link = Linker::link( $title );
if ( $isRedirect ) {
// This seems kind of pointless given 'mw-redirect' class,
// but keeping for back-compatiability with user css.
@@ -309,7 +352,7 @@ class CategoryViewer {
'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,
+ array_merge( array( 'cl_to' => $this->title->getDBkey() ), $extraConds ),
__METHOD__,
array(
'USE INDEX' => array( 'categorylinks' => 'cl_sortkey' ),
@@ -385,7 +428,7 @@ class CategoryViewer {
# Don't show articles section if there are none.
$r = '';
- # FIXME, here and in the other two sections: we don't need to bother
+ # @todo FIXME: Here and in the other two sections: we don't need to bother
# with this rigamarole if the entire category contents fit on one page
# and have already been retrieved. We can just use $rescnt in that
# case and save a query and some logic.
@@ -460,13 +503,20 @@ class CategoryViewer {
* @private
*/
function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
+ $list = '';
if ( count ( $articles ) > $cutoff ) {
- return self::columnList( $articles, $articles_start_char );
+ $list = self::columnList( $articles, $articles_start_char );
} elseif ( count( $articles ) > 0 ) {
// for short lists of articles in categories.
- return self::shortList( $articles, $articles_start_char );
+ $list = self::shortList( $articles, $articles_start_char );
}
- return '';
+
+ $pageLang = $this->title->getPageLanguage();
+ $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
+ 'class' => 'mw-content-'.$pageLang->getDir() );
+ $list = Html::rawElement( 'div', $attribs, $list );
+
+ return $list;
}
/**
@@ -539,10 +589,8 @@ class CategoryViewer {
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++ )
- {
- if ( $articles_start_char[$index] != $articles_start_char[$index - 1] )
- {
+ for ( $index = 1; $index < count( $articles ); $index++ ) {
+ if ( $articles_start_char[$index] != $articles_start_char[$index - 1] ) {
$r .= "</ul><h3>" . htmlspecialchars( $articles_start_char[$index] ) . "</h3>\n<ul>";
}
@@ -563,7 +611,7 @@ class CategoryViewer {
*/
private function pagingLinks( $first, $last, $type = '' ) {
global $wgLang;
- $sk = $this->getSkin();
+
$limitText = $wgLang->formatNum( $this->limit );
$prevLink = wfMsgExt( 'prevn', array( 'escape', 'parsemag' ), $limitText );
@@ -572,8 +620,8 @@ class CategoryViewer {
$prevQuery = $this->query;
$prevQuery["{$type}until"] = $first;
unset( $prevQuery["{$type}from"] );
- $prevLink = $sk->linkKnown(
- $this->title,
+ $prevLink = Linker::linkKnown(
+ $this->addFragmentToTitle( $this->title, $type ),
$prevLink,
array(),
$prevQuery
@@ -586,8 +634,8 @@ class CategoryViewer {
$lastQuery = $this->query;
$lastQuery["{$type}from"] = $last;
unset( $lastQuery["{$type}until"] );
- $nextLink = $sk->linkKnown(
- $this->title,
+ $nextLink = Linker::linkKnown(
+ $this->addFragmentToTitle( $this->title, $type ),
$nextLink,
array(),
$lastQuery
@@ -598,8 +646,34 @@ class CategoryViewer {
}
/**
+ * Takes a title, and adds the fragment identifier that
+ * corresponds to the correct segment of the category.
+ *
+ * @param Title $title: The title (usually $this->title)
+ * @param String $section: Which section
+ */
+ private function addFragmentToTitle( $title, $section ) {
+ switch ( $section ) {
+ case 'page':
+ $fragment = 'mw-pages';
+ break;
+ case 'subcat':
+ $fragment = 'mw-subcategories';
+ break;
+ case 'file':
+ $fragment = 'mw-category-media';
+ break;
+ default:
+ throw new MWException( __METHOD__ .
+ " Invalid section $section." );
+ }
+
+ return Title::makeTitle( $title->getNamespace(),
+ $title->getDBkey(), $fragment );
+ }
+ /**
* What to do if the category table conflicts with the number of results
- * returned? This function says what. Each type is considered independantly
+ * returned? This function says what. Each type is considered independently
* of the other types.
*
* Note for grepping: uses the messages category-article-count,
@@ -640,8 +714,7 @@ class CategoryViewer {
}
if ( $dbcnt == $rescnt || ( ( $rescnt == $this->limit || $fromOrUntil )
- && $dbcnt > $rescnt ) )
- {
+ && $dbcnt > $rescnt ) ) {
# Case 1: seems sane.
$totalcnt = $dbcnt;
} elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php
index 1f08b7f8..2567de0d 100644
--- a/includes/Categoryfinder.php
+++ b/includes/Categoryfinder.php
@@ -42,6 +42,7 @@ class Categoryfinder {
* @param $article_ids Array of article IDs
* @param $categories FIXME
* @param $mode String: FIXME, default 'AND'.
+ * @todo FIXME: $categories/$mode
*/
function seed( $article_ids, $categories, $mode = 'AND' ) {
$this->articles = $article_ids;
@@ -85,9 +86,9 @@ class Categoryfinder {
/**
* This functions recurses through the parent representation, trying to match the conditions
- * @param $id The article/category to check
- * @param $conds The array of categories to match
- * @param $path used to check for recursion loops
+ * @param $id int The article/category to check
+ * @param $conds array The array of categories to match
+ * @param $path array used to check for recursion loops
* @return bool Does this match the conditions?
*/
function check( $id, &$conds, $path = array() ) {
diff --git a/includes/Cdb.php b/includes/Cdb.php
index 60477485..d7a2bca5 100644
--- a/includes/Cdb.php
+++ b/includes/Cdb.php
@@ -13,6 +13,10 @@
abstract class CdbReader {
/**
* Open a file and return a subclass instance
+ *
+ * @param $fileName string
+ *
+ * @return CdbReader
*/
public static function open( $fileName ) {
if ( self::haveExtension() ) {
@@ -25,6 +29,8 @@ abstract class CdbReader {
/**
* Returns true if the native extension is available
+ *
+ * @return bool
*/
public static function haveExtension() {
if ( !function_exists( 'dba_handlers' ) ) {
@@ -49,6 +55,8 @@ abstract class CdbReader {
/**
* Get a value with a given key. Only string values are supported.
+ *
+ * @param $key string
*/
abstract public function get( $key );
}
@@ -61,6 +69,10 @@ abstract class CdbWriter {
/**
* Open a writer and return a subclass instance.
* The user must have write access to the directory, for temporary file creation.
+ *
+ * @param $fileName string
+ *
+ * @return bool
*/
public static function open( $fileName ) {
if ( CdbReader::haveExtension() ) {
diff --git a/includes/Cdb_PHP.php b/includes/Cdb_PHP.php
index 1485cc66..f4029ba5 100644
--- a/includes/Cdb_PHP.php
+++ b/includes/Cdb_PHP.php
@@ -16,6 +16,11 @@ class CdbFunctions {
/**
* Take a modulo of a signed integer as if it were an unsigned integer.
* $b must be less than 0x40000000 and greater than 0
+ *
+ * @param $a
+ * @param $b
+ *
+ * @return int
*/
public static function unsignedMod( $a, $b ) {
if ( $a & 0x80000000 ) {
@@ -25,9 +30,12 @@ class CdbFunctions {
return $a % $b;
}
}
-
+
/**
* Shift a signed integer right as if it were unsigned
+ * @param $a
+ * @param $b
+ * @return int
*/
public static function unsignedShiftRight( $a, $b ) {
if ( $b == 0 ) {
@@ -42,6 +50,10 @@ class CdbFunctions {
/**
* The CDB hash function.
+ *
+ * @param $s
+ *
+ * @return
*/
public static function hash( $s ) {
$h = 5381;
@@ -103,11 +115,16 @@ class CdbReader_PHP extends CdbReader {
}
function close() {
- if( isset($this->handle) )
+ if( isset( $this->handle ) ) {
fclose( $this->handle );
+ }
unset( $this->handle );
}
+ /**
+ * @param $key
+ * @return bool|string
+ */
public function get( $key ) {
// strval is required
if ( $this->find( strval( $key ) ) ) {
@@ -117,6 +134,11 @@ class CdbReader_PHP extends CdbReader {
}
}
+ /**
+ * @param $key
+ * @param $pos
+ * @return bool
+ */
protected function match( $key, $pos ) {
$buf = $this->read( strlen( $key ), $pos );
return $buf === $key;
@@ -126,6 +148,12 @@ class CdbReader_PHP extends CdbReader {
$this->loop = 0;
}
+ /**
+ * @throws MWException
+ * @param $length
+ * @param $pos
+ * @return string
+ */
protected function read( $length, $pos ) {
if ( fseek( $this->handle, $pos ) == -1 ) {
// This can easily happen if the internal pointers are incorrect
@@ -145,6 +173,8 @@ class CdbReader_PHP extends CdbReader {
/**
* Unpack an unsigned integer and throw an exception if it needs more than 31 bits
+ * @param $s
+ * @return
*/
protected function unpack31( $s ) {
$data = unpack( 'V', $s );
@@ -156,12 +186,18 @@ class CdbReader_PHP extends CdbReader {
/**
* Unpack a 32-bit signed integer
+ * @param $s
+ * @return int
*/
protected function unpackSigned( $s ) {
$data = unpack( 'va/vb', $s );
return $data['a'] | ( $data['b'] << 16 );
}
+ /**
+ * @param $key
+ * @return bool
+ */
protected function findNext( $key ) {
if ( !$this->loop ) {
$u = CdbFunctions::hash( $key );
@@ -204,6 +240,10 @@ class CdbReader_PHP extends CdbReader {
return false;
}
+ /**
+ * @param $key
+ * @return bool
+ */
protected function find( $key ) {
$this->findStart();
return $this->findNext( $key );
@@ -240,6 +280,11 @@ class CdbWriter_PHP extends CdbWriter {
}
}
+ /**
+ * @param $key
+ * @param $value
+ * @return
+ */
public function set( $key, $value ) {
if ( strval( $key ) === '' ) {
// DBA cross-check hack
@@ -251,10 +296,14 @@ class CdbWriter_PHP extends CdbWriter {
$this->addend( strlen( $key ), strlen( $value ), CdbFunctions::hash( $key ) );
}
+ /**
+ * @throws MWException
+ */
public function close() {
$this->finish();
- if( isset($this->handle) )
+ if( isset($this->handle) ) {
fclose( $this->handle );
+ }
if ( wfIsWindows() && file_exists($this->realFileName) ) {
unlink( $this->realFileName );
}
@@ -264,6 +313,10 @@ class CdbWriter_PHP extends CdbWriter {
unset( $this->handle );
}
+ /**
+ * @throws MWException
+ * @param $buf
+ */
protected function write( $buf ) {
$len = fwrite( $this->handle, $buf );
if ( $len !== strlen( $buf ) ) {
@@ -271,6 +324,10 @@ class CdbWriter_PHP extends CdbWriter {
}
}
+ /**
+ * @throws MWException
+ * @param $len
+ */
protected function posplus( $len ) {
$newpos = $this->pos + $len;
if ( $newpos > 0x7fffffff ) {
@@ -279,6 +336,11 @@ class CdbWriter_PHP extends CdbWriter {
$this->pos = $newpos;
}
+ /**
+ * @param $keylen
+ * @param $datalen
+ * @param $h
+ */
protected function addend( $keylen, $datalen, $h ) {
$this->hplist[] = array(
'h' => $h,
@@ -291,6 +353,11 @@ class CdbWriter_PHP extends CdbWriter {
$this->posplus( $datalen );
}
+ /**
+ * @throws MWException
+ * @param $keylen
+ * @param $datalen
+ */
protected function addbegin( $keylen, $datalen ) {
if ( $keylen > 0x7fffffff ) {
throw new MWException( __METHOD__.': key length too long' );
@@ -302,6 +369,9 @@ class CdbWriter_PHP extends CdbWriter {
$this->write( $buf );
}
+ /**
+ * @throws MWException
+ */
protected function finish() {
// Hack for DBA cross-check
$this->hplist = array_reverse( $this->hplist );
diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php
index 7f0fee21..c8e522df 100644
--- a/includes/ChangeTags.php
+++ b/includes/ChangeTags.php
@@ -1,8 +1,5 @@
<?php
-if( !defined( 'MEDIAWIKI' ) )
- die;
-
class ChangeTags {
static function formatSummaryRow( $tags, $page ) {
if( !$tags )
@@ -28,11 +25,8 @@ class ChangeTags {
}
static function tagDescription( $tag ) {
- $msg = wfMsgExt( "tag-$tag", 'parseinline' );
- if ( wfEmptyMsg( "tag-$tag", $msg ) ) {
- return htmlspecialchars( $tag );
- }
- return $msg;
+ $msg = wfMessage( "tag-$tag" );
+ return $msg->exists() ? $msg->parse() : htmlspecialchars( $tag );
}
## Basic utility method to add tags to a particular change, given its rc_id, rev_id and/or log_id.
@@ -150,18 +144,26 @@ class ChangeTags {
}
/**
- * If $fullForm is set to false, then it returns an array of (label, form).
- * If $fullForm is true, it returns an entire form.
+ * Build a text box to select a change tag
+ *
+ * @param $selected String: tag to select by default
+ * @param $fullForm Boolean:
+ * - if false, then it returns an array of (label, form).
+ * - if true, it returns an entire form around the selector.
+ * @param $title Title object to send the form to.
+ * Used when, and only when $fullForm is true.
+ * @return String or array:
+ * - if $fullForm is false: Array with
+ * - if $fullForm is true: String, html fragment
*/
- static function buildTagFilterSelector( $selected='', $fullForm = false /* used to put a full form around the selector */ ) {
+ public static function buildTagFilterSelector( $selected='', $fullForm = false, Title $title = null ) {
global $wgUseTagFilter;
if ( !$wgUseTagFilter || !count( self::listDefinedTags() ) )
return $fullForm ? '' : array();
- global $wgTitle;
-
- $data = array( wfMsgExt( 'tag-filter', 'parseinline' ), Xml::input( 'tagfilter', 20, $selected ) );
+ $data = array( Html::rawElement( 'label', array( 'for' => 'tagfilter' ), wfMsgExt( 'tag-filter', 'parseinline' ) ),
+ Xml::input( 'tagfilter', 20, $selected ) );
if ( !$fullForm ) {
return $data;
@@ -175,7 +177,11 @@ class ChangeTags {
return $html;
}
- /** Basically lists defined tags which count even if they aren't applied to anything */
+ /**
+ *Basically lists defined tags which count even if they aren't applied to anything
+ *
+ * @return array
+ */
static function listDefinedTags() {
// Caching...
global $wgMemc;
diff --git a/includes/ChangesFeed.php b/includes/ChangesFeed.php
index f07b6505..c4c4a8a1 100644
--- a/includes/ChangesFeed.php
+++ b/includes/ChangesFeed.php
@@ -24,15 +24,19 @@ class ChangesFeed {
*
* @param $title String: feed's title
* @param $description String: feed's description
+ * @param $url String: url of origin page
* @return ChannelFeed subclass or false on failure
*/
- public function getFeedObject( $title, $description ) {
- global $wgSitename, $wgLanguageCode, $wgFeedClasses, $wgTitle;
- $feedTitle = "$wgSitename - {$title} [$wgLanguageCode]";
- if( !isset($wgFeedClasses[$this->format] ) )
+ public function getFeedObject( $title, $description, $url ) {
+ global $wgSitename, $wgLanguageCode, $wgFeedClasses;
+
+ if ( !isset( $wgFeedClasses[$this->format] ) ) {
return false;
+ }
+
+ $feedTitle = "$wgSitename - {$title} [$wgLanguageCode]";
return new $wgFeedClasses[$this->format](
- $feedTitle, htmlspecialchars( $description ), $wgTitle->getFullUrl() );
+ $feedTitle, htmlspecialchars( $description ), $url );
}
/**
@@ -57,11 +61,11 @@ class ChangesFeed {
FeedUtils::checkPurge( $timekey, $key );
- /*
- * Bumping around loading up diffs can be pretty slow, so where
- * possible we want to cache the feed output so the next visitor
- * gets it quick too.
- */
+ /**
+ * Bumping around loading up diffs can be pretty slow, so where
+ * possible we want to cache the feed output so the next visitor
+ * gets it quick too.
+ */
$cachedFeed = $this->loadFromCache( $lastmod, $timekey, $key );
if( is_string( $cachedFeed ) ) {
wfDebug( "RC: Outputting cached feed\n" );
@@ -106,12 +110,12 @@ class ChangesFeed {
$feedLastmod = $messageMemc->get( $timekey );
if( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) {
- /*
- * If the cached feed was rendered very recently, we may
- * go ahead and use it even if there have been edits made
- * since it was rendered. This keeps a swarm of requests
- * from being too bad on a super-frequently edited wiki.
- */
+ /**
+ * If the cached feed was rendered very recently, we may
+ * go ahead and use it even if there have been edits made
+ * since it was rendered. This keeps a swarm of requests
+ * from being too bad on a super-frequently edited wiki.
+ */
$feedAge = time() - wfTimestamp( TS_UNIX, $feedLastmod );
$feedLastmodUnix = wfTimestamp( TS_UNIX, $feedLastmod );
@@ -145,6 +149,7 @@ class ChangesFeed {
$n = 0;
foreach( $rows as $obj ) {
if( $n > 0 &&
+ $obj->rc_type == RC_EDIT &&
$obj->rc_namespace >= 0 &&
$obj->rc_cur_id == $sorted[$n-1]->rc_cur_id &&
$obj->rc_user_text == $sorted[$n-1]->rc_user_text ) {
@@ -157,16 +162,27 @@ class ChangesFeed {
foreach( $sorted as $obj ) {
$title = Title::makeTitle( $obj->rc_namespace, $obj->rc_title );
- $talkpage = $title->getTalkPage();
+ $talkpage = MWNamespace::canTalk( $obj->rc_namespace ) ? $title->getTalkPage()->getFullUrl() : '';
// Skip items with deleted content (avoids partially complete/inconsistent output)
if( $obj->rc_deleted ) continue;
+
+ if ( $obj->rc_this_oldid ) {
+ $url = $title->getFullURL(
+ 'diff=' . $obj->rc_this_oldid .
+ '&oldid=' . $obj->rc_last_oldid
+ );
+ } else {
+ // log entry or something like that.
+ $url = $title->getFullURL();
+ }
+
$item = new FeedItem(
$title->getPrefixedText(),
FeedUtils::formatDiff( $obj ),
- $obj->rc_this_oldid ? $title->getFullURL( 'diff=' . $obj->rc_this_oldid . '&oldid=prev' ) : $title->getFullURL(),
+ $url,
$obj->rc_timestamp,
($obj->rc_deleted & Revision::DELETED_USER) ? wfMsgHtml('rev-deleted-user') : $obj->rc_user_text,
- $talkpage->getFullURL()
+ $talkpage
);
$feed->outItem( $item );
}
diff --git a/includes/ChangesList.php b/includes/ChangesList.php
index b8bc4f55..1858dc3a 100644
--- a/includes/ChangesList.php
+++ b/includes/ChangesList.php
@@ -16,6 +16,10 @@ class RCCacheEntry extends RecentChange {
var $curlink , $difflink, $lastlink, $usertalklink, $versionlink;
var $userlink, $timestamp, $watched;
+ /**
+ * @param $rc RecentChange
+ * @return RCCacheEntry
+ */
static function newFromParent( $rc ) {
$rc2 = new RCCacheEntry;
$rc2->mAttribs = $rc->mAttribs;
@@ -27,39 +31,64 @@ class RCCacheEntry extends RecentChange {
/**
* Base class for all changes lists
*/
-class ChangesList {
+class ChangesList extends ContextSource {
+
+ /**
+ * @var Skin
+ */
public $skin;
+
protected $watchlist = false;
+ protected $message;
+
/**
- * Changeslist contructor
- * @param $skin Skin
- */
- public function __construct( $skin ) {
- $this->skin = $skin;
+ * Changeslist contructor
+ *
+ * @param $obj Skin or IContextSource
+ */
+ public function __construct( $obj ) {
+ if ( $obj instanceof IContextSource ) {
+ $this->setContext( $obj );
+ $this->skin = $obj->getSkin();
+ } else {
+ $this->setContext( $obj->getContext() );
+ $this->skin = $obj;
+ }
$this->preCacheMessages();
}
/**
- * Fetch an appropriate changes list class for the specified user
- * Some users might want to use an enhanced list format, for instance
+ * Fetch an appropriate changes list class for the main context
+ * This first argument used to be an User object.
*
- * @param $user User to fetch the list class for
- * @return ChangesList derivative
+ * @deprecated in 1.18; use newFromContext() instead
+ * @param $unused Unused
+ * @return ChangesList|EnhancedChangesList|OldChangesList derivative
*/
- public static function newFromUser( &$user ) {
- global $wgRequest;
+ public static function newFromUser( $unused ) {
+ return self::newFromContext( RequestContext::getMain() );
+ }
- $sk = $user->getSkin();
+ /**
+ * Fetch an appropriate changes list class for the specified context
+ * Some users might want to use an enhanced list format, for instance
+ *
+ * @param $context IContextSource to use
+ * @return ChangesList|EnhancedChangesList|OldChangesList derivative
+ */
+ public static function newFromContext( IContextSource $context ) {
+ $user = $context->getUser();
+ $sk = $context->getSkin();
$list = null;
- if( wfRunHooks( 'FetchChangesList', array( &$user, &$sk, &$list ) ) ) {
- $new = $wgRequest->getBool( 'enhanced', $user->getOption( 'usenewrc' ) );
- return $new ? new EnhancedChangesList( $sk ) : new OldChangesList( $sk );
+ if( wfRunHooks( 'FetchChangesList', array( $user, &$sk, &$list ) ) ) {
+ $new = $context->getRequest()->getBool( 'enhanced', $user->getOption( 'usenewrc' ) );
+ return $new ? new EnhancedChangesList( $context ) : new OldChangesList( $context );
} else {
return $list;
}
}
-
+
/**
* Sets the list to use a <li class="watchlist-(namespace)-(page)"> tag
* @param $value Boolean
@@ -81,21 +110,19 @@ class ChangesList {
}
}
-
/**
* Returns the appropriate flags for new page, minor change and patrolling
- * @param $new Boolean
- * @param $minor Boolean
- * @param $patrolled Boolean
+ * @param $flags Array Associative array of 'flag' => Bool
* @param $nothing String to use for empty space
- * @param $bot Boolean
* @return String
*/
- 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;
- $f .= $patrolled ? self::flag( 'unpatrolled' ) : $nothing;
+ protected function recentChangesFlags( $flags, $nothing = '&#160;' ) {
+ $f = '';
+ foreach( array( 'newpage', 'minor', 'bot', 'unpatrolled' ) as $flag ){
+ $f .= isset( $flags[$flag] ) && $flags[$flag]
+ ? self::flag( $flag )
+ : $nothing;
+ }
return $f;
}
@@ -105,28 +132,36 @@ class ChangesList {
* unpatrolled edit. By default in English it will contain "N", "m", "b",
* "!" respectively, plus it will have an appropriate title and class.
*
- * @param $key String: 'newpage', 'unpatrolled', 'minor', or 'bot'
+ * @param $flag String: 'newpage', 'unpatrolled', 'minor', or 'bot'
* @return String: Raw HTML
*/
- public static function flag( $key ) {
+ public static function flag( $flag ) {
static $messages = null;
if ( is_null( $messages ) ) {
- foreach ( explode( ' ', 'minoreditletter boteditletter newpageletter ' .
- 'unpatrolledletter recentchanges-label-minor recentchanges-label-bot ' .
- 'recentchanges-label-newpage recentchanges-label-unpatrolled' ) as $msg ) {
- $messages[$msg] = wfMsgExt( $msg, 'escapenoentities' );
+ $messages = array(
+ 'newpage' => array( 'newpageletter', 'recentchanges-label-newpage' ),
+ 'minoredit' => array( 'minoreditletter', 'recentchanges-label-minor' ),
+ 'botedit' => array( 'boteditletter', 'recentchanges-label-bot' ),
+ 'unpatrolled' => array( 'unpatrolledletter', 'recentchanges-label-unpatrolled' ),
+ );
+ foreach( $messages as &$value ) {
+ $value[0] = wfMsgExt( $value[0], 'escapenoentities' );
+ $value[1] = wfMsgExt( $value[1], 'escapenoentities' );
}
}
+
# Inconsistent naming, bleh
- if ( $key == 'newpage' || $key == 'unpatrolled' ) {
- $key2 = $key;
- } else {
- $key2 = $key . 'edit';
- }
- return "<abbr class=\"$key\" title=\""
- . $messages["recentchanges-label-$key"] . "\">"
- . $messages["${key2}letter"]
- . '</abbr>';
+ $map = array(
+ 'newpage' => 'newpage',
+ 'minor' => 'minoredit',
+ 'bot' => 'botedit',
+ 'unpatrolled' => 'unpatrolled',
+ 'minoredit' => 'minoredit',
+ 'botedit' => 'botedit',
+ );
+ $flag = $map[$flag];
+
+ return "<abbr class='$flag' title='" . $messages[$flag][1] . "'>" . $messages[$flag][0] . '</abbr>';
}
/**
@@ -141,12 +176,12 @@ class ChangesList {
$this->rclistOpen = false;
return '';
}
-
+
/**
* Show formatted char difference
* @param $old Integer: bytes
* @param $new Integer: bytes
- * @returns String
+ * @return String
*/
public static function showCharacterDifference( $old, $new ) {
global $wgRCChangedSizeThreshold, $wgLang, $wgMiserMode;
@@ -157,29 +192,29 @@ class ChangesList {
if ( !isset($fastCharDiff[$code]) ) {
$fastCharDiff[$code] = $wgMiserMode || wfMsgNoTrans( 'rc-change-size' ) === '$1';
}
-
+
$formatedSize = $wgLang->formatNum($szdiff);
if ( !$fastCharDiff[$code] ) {
$formatedSize = wfMsgExt( 'rc-change-size', array( 'parsemag', 'escape' ), $formatedSize );
}
-
+
if( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) {
$tag = 'strong';
} else {
- $tag = 'span';
+ $tag = 'span';
}
if( $szdiff === 0 ) {
return "<$tag class='mw-plusminus-null'>($formatedSize)</$tag>";
} elseif( $szdiff > 0 ) {
return "<$tag class='mw-plusminus-pos'>(+$formatedSize)</$tag>";
- } else {
+ } else {
return "<$tag class='mw-plusminus-neg'>($formatedSize)</$tag>";
}
}
/**
- * Returns text for the end of RC
+ * Returns text for the end of RC
* @return String
*/
public function endRecentChangesList() {
@@ -190,42 +225,38 @@ class ChangesList {
}
}
+ /**
+ * @param $s
+ * @param $rc RecentChange
+ * @return void
+ */
public function insertMove( &$s, $rc ) {
# Diff
$s .= '(' . $this->message['diff'] . ') (';
# Hist
- $s .= $this->skin->link(
+ $s .= Linker::linkKnown(
$rc->getMovedToTitle(),
$this->message['hist'],
array(),
- array( 'action' => 'history' ),
- array( 'known', 'noclasses' )
+ array( 'action' => 'history' )
) . ') . . ';
# "[[x]] moved to [[y]]"
$msg = ( $rc->mAttribs['rc_type'] == RC_MOVE ) ? '1movedto2' : '1movedto2_redir';
- $s .= wfMsg(
+ $s .= wfMsgHtml(
$msg,
- $this->skin->link(
+ Linker::linkKnown(
$rc->getTitle(),
null,
array(),
- array( 'redirect' => 'no' ),
- array( 'known', 'noclasses' )
+ array( 'redirect' => 'no' )
),
- $this->skin->link(
- $rc->getMovedToTitle(),
- null,
- array(),
- array(),
- array( 'known', 'noclasses' )
- )
+ Linker::linkKnown( $rc->getMovedToTitle() )
);
}
public function insertDateHeader( &$s, $rc_timestamp ) {
- global $wgLang;
# Make date header if necessary
- $date = $wgLang->date( $rc_timestamp, true, true );
+ $date = $this->getLang()->date( $rc_timestamp, true, true );
if( $date != $this->lastdate ) {
if( $this->lastdate != '' ) {
$s .= "</ul>\n";
@@ -238,20 +269,20 @@ class ChangesList {
public function insertLog( &$s, $title, $logtype ) {
$logname = LogPage::logName( $logtype );
- $s .= '(' . $this->skin->link(
- $title,
- $logname,
- array(),
- array(),
- array( 'known', 'noclasses' )
- ) . ')';
+ $s .= '(' . Linker::linkKnown( $title, htmlspecialchars( $logname ) ) . ')';
}
+ /**
+ * @param $s
+ * @param $rc RecentChange
+ * @param $unpatrolled
+ * @return void
+ */
public function insertDiffHist( &$s, &$rc, $unpatrolled ) {
# Diff link
if( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) {
$diffLink = $this->message['diff'];
- } else if( !self::userCan($rc,Revision::DELETED_TEXT) ) {
+ } elseif( !self::userCan($rc,Revision::DELETED_TEXT) ) {
$diffLink = $this->message['diff'];
} else {
$query = array(
@@ -264,31 +295,35 @@ class ChangesList {
$query['rcid'] = $rc->mAttribs['rc_id'];
};
- $diffLink = $this->skin->link(
+ $diffLink = Linker::linkKnown(
$rc->getTitle(),
$this->message['diff'],
array( 'tabindex' => $rc->counter ),
- $query,
- array( 'known', 'noclasses' )
+ $query
);
}
$s .= '(' . $diffLink . $this->message['pipe-separator'];
# History link
- $s .= $this->skin->link(
+ $s .= Linker::linkKnown(
$rc->getTitle(),
$this->message['hist'],
array(),
array(
'curid' => $rc->mAttribs['rc_cur_id'],
'action' => 'history'
- ),
- array( 'known', 'noclasses' )
+ )
);
$s .= ') . . ';
}
+ /**
+ * @param $s
+ * @param $rc RecentChange
+ * @param $unpatrolled
+ * @param $watched
+ * @return void
+ */
public function insertArticleLink( &$s, &$rc, $unpatrolled, $watched ) {
- global $wgContLang;
# If it's a new article, there is no diff link, but if it hasn't been
# patrolled yet, we need to give users a way to do so
$params = array();
@@ -298,21 +333,19 @@ class ChangesList {
}
if( $this->isDeleted($rc,Revision::DELETED_TEXT) ) {
- $articlelink = $this->skin->link(
+ $articlelink = Linker::linkKnown(
$rc->getTitle(),
null,
array(),
- $params,
- array( 'known', 'noclasses' )
+ $params
);
$articlelink = '<span class="history-deleted">' . $articlelink . '</span>';
} else {
- $articlelink = ' '. $this->skin->link(
+ $articlelink = ' '. Linker::linkKnown(
$rc->getTitle(),
null,
array(),
- $params,
- array( 'known', 'noclasses' )
+ $params
);
}
# Bolden pages watched by this user
@@ -320,7 +353,7 @@ class ChangesList {
$articlelink = "<strong class=\"mw-watched\">{$articlelink}</strong>";
}
# RTL/LTR marker
- $articlelink .= $wgContLang->getDirMark();
+ $articlelink .= $this->getLang()->getDirMark();
wfRunHooks( 'ChangesListInsertArticleLink',
array(&$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched) );
@@ -328,41 +361,54 @@ class ChangesList {
$s .= " $articlelink";
}
+ /**
+ * @param $s
+ * @param $rc RecentChange
+ * @return void
+ */
public function insertTimestamp( &$s, $rc ) {
- global $wgLang;
- $s .= $this->message['semicolon-separator'] .
- $wgLang->time( $rc->mAttribs['rc_timestamp'], true, true ) . ' . . ';
+ $s .= $this->message['semicolon-separator'] .
+ $this->getLang()->time( $rc->mAttribs['rc_timestamp'], true, true ) . ' . . ';
}
- /** Insert links to user page, user talk page and eventually a blocking link */
+ /** Insert links to user page, user talk page and eventually a blocking link
+ *
+ * @param $rc RecentChange
+ */
public function insertUserRelatedLinks( &$s, &$rc ) {
if( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
- $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
+ $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
} else {
- $s .= $this->skin->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
- $s .= $this->skin->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ $s .= Linker::userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ $s .= Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
}
}
- /** insert a formatted action */
+ /** insert a formatted action
+ *
+ * @param $rc RecentChange
+ */
public function insertAction( &$s, &$rc ) {
if( $rc->mAttribs['rc_type'] == RC_LOG ) {
if( $this->isDeleted( $rc, LogPage::DELETED_ACTION ) ) {
$s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-event' ) . '</span>';
} else {
$s .= ' '.LogPage::actionText( $rc->mAttribs['rc_log_type'], $rc->mAttribs['rc_log_action'],
- $rc->getTitle(), $this->skin, LogPage::extractParams( $rc->mAttribs['rc_params'] ), true, true );
+ $rc->getTitle(), $this->getSkin(), LogPage::extractParams( $rc->mAttribs['rc_params'] ), true, true );
}
}
}
- /** insert a formatted comment */
+ /** insert a formatted comment
+ *
+ * @param $rc RecentChange
+ */
public function insertComment( &$s, &$rc ) {
if( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) {
if( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) {
$s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
} else {
- $s .= $this->skin->commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
+ $s .= Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
}
}
}
@@ -380,12 +426,11 @@ class ChangesList {
* Returns the string which indicates the number of watching users
*/
protected function numberofWatchingusers( $count ) {
- global $wgLang;
static $cache = array();
if( $count > 0 ) {
if( !isset( $cache[$count] ) ) {
$cache[$count] = wfMsgExt( 'number_of_watching_users_RCview',
- array('parsemag', 'escape' ), $wgLang->formatNum( $count ) );
+ array('parsemag', 'escape' ), $this->getLang()->formatNum( $count ) );
}
return $cache[$count];
} else {
@@ -425,15 +470,18 @@ class ChangesList {
return '<span class="mw-rc-unwatched">' . $link . '</span>';
}
}
-
- /** Inserts a rollback link */
+
+ /** Inserts a rollback link
+ *
+ * @param $s
+ * @param $rc RecentChange
+ */
public function insertRollback( &$s, &$rc ) {
- global $wgUser;
if( !$rc->mAttribs['rc_new'] && $rc->mAttribs['rc_this_oldid'] && $rc->mAttribs['rc_cur_id'] ) {
$page = $rc->getTitle();
/** Check for rollback and edit permissions, disallow special pages, and only
* show a link on the top-most revision */
- if ($wgUser->isAllowed('rollback') && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] )
+ if ( $this->getUser()->isAllowed('rollback') && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] )
{
$rev = new Revision( array(
'id' => $rc->mAttribs['rc_this_oldid'],
@@ -442,15 +490,21 @@ class ChangesList {
'deleted' => $rc->mAttribs['rc_deleted']
) );
$rev->setTitle( $page );
- $s .= ' '.$this->skin->generateRollback( $rev );
+ $s .= ' '.Linker::generateRollback( $rev, $this->getContext() );
}
}
}
+ /**
+ * @param $s
+ * @param $rc RecentChange
+ * @param $classes
+ * @return
+ */
public function insertTags( &$s, &$rc, &$classes ) {
if ( empty($rc->mAttribs['ts_tags']) )
return;
-
+
list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' );
$classes = array_merge( $classes, $newClasses );
$s .= ' ' . $tagSummary;
@@ -468,12 +522,14 @@ class ChangesList {
class OldChangesList extends ChangesList {
/**
* Format a line using the old system (aka without any javascript).
+ *
+ * @param $rc RecentChange
*/
public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
- global $wgLang, $wgRCShowChangedSize, $wgUser;
+ global $wgRCShowChangedSize;
wfProfileIn( __METHOD__ );
# Should patrol-related stuff be shown?
- $unpatrolled = $wgUser->useRCPatrol() && !$rc->mAttribs['rc_patrolled'];
+ $unpatrolled = $this->getUser()->useRCPatrol() && !$rc->mAttribs['rc_patrolled'];
$dateheader = ''; // $s now contains only <li>...</li>, for hooks' convenience.
$this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
@@ -499,7 +555,7 @@ class OldChangesList extends ChangesList {
$this->insertLog( $s, $logtitle, $rc->mAttribs['rc_log_type'] );
// Log entries (old format) or log targets, and special pages
} elseif( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
- list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $rc->mAttribs['rc_title'] );
+ list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] );
if( $name == 'Log' ) {
$this->insertLog( $s, $rc->getTitle(), $subpage );
}
@@ -507,8 +563,15 @@ class OldChangesList extends ChangesList {
} else {
$this->insertDiffHist( $s, $rc, $unpatrolled );
# M, N, b and ! (minor, new, bot and unpatrolled)
- $s .= $this->recentChangesFlags( $rc->mAttribs['rc_new'], $rc->mAttribs['rc_minor'],
- $unpatrolled, '', $rc->mAttribs['rc_bot'] );
+ $s .= $this->recentChangesFlags(
+ array(
+ 'newpage' => $rc->mAttribs['rc_new'],
+ 'minor' => $rc->mAttribs['rc_minor'],
+ 'unpatrolled' => $unpatrolled,
+ 'bot' => $rc->mAttribs['rc_bot']
+ ),
+ ''
+ );
$this->insertArticleLink( $s, $rc, $unpatrolled, $watched );
}
# Edit/log timestamp
@@ -522,6 +585,8 @@ class OldChangesList extends ChangesList {
}
# User tool links
$this->insertUserRelatedLinks( $s, $rc );
+ # LTR/RTL direction mark
+ $s .= $this->getLang()->getDirMark();
# Log action text (if any)
$this->insertAction( $s, $rc );
# Edit or log comment
@@ -532,17 +597,17 @@ class OldChangesList extends ChangesList {
$this->insertRollback( $s, $rc );
# For subclasses
$this->insertExtra( $s, $rc, $classes );
-
+
# How many users watch this page
if( $rc->numberofWatchingusers > 0 ) {
- $s .= ' ' . wfMsgExt( 'number_of_watching_users_RCview',
- array( 'parsemag', 'escape' ), $wgLang->formatNum( $rc->numberofWatchingusers ) );
+ $s .= ' ' . wfMsgExt( 'number_of_watching_users_RCview',
+ array( 'parsemag', 'escape' ), $this->getLang()->formatNum( $rc->numberofWatchingusers ) );
}
-
+
if( $this->watchlist ) {
$classes[] = Sanitizer::escapeClass( 'watchlist-'.$rc->mAttribs['rc_namespace'].'-'.$rc->mAttribs['rc_title'] );
}
-
+
wfRunHooks( 'OldChangesListRecentChangesLine', array(&$this, &$s, $rc) );
wfProfileOut( __METHOD__ );
@@ -560,34 +625,32 @@ class EnhancedChangesList extends ChangesList {
* @return String
*/
public function beginRecentChangesList() {
- global $wgOut;
$this->rc_cache = array();
$this->rcMoveIndex = 0;
$this->rcCacheIndex = 0;
$this->lastdate = '';
$this->rclistOpen = false;
- $wgOut->addModules( 'mediawiki.legacy.enhancedchanges' );
+ $this->getOutput()->addModuleStyles( 'mediawiki.special.changeslist' );
return '';
}
/**
* Format a line for enhanced recentchange (aka with javascript and block of lines).
+ *
+ * @param $baseRC RecentChange
+ * @param $watched bool
+ *
+ * @return string
*/
public function recentChangesLine( &$baseRC, $watched = false ) {
- global $wgLang, $wgUser;
-
wfProfileIn( __METHOD__ );
# Create a specialised object
$rc = RCCacheEntry::newFromParent( $baseRC );
- # 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.
- extract( $rc->mAttribs );
- $curIdEq = array( 'curid' => $rc_cur_id );
+ $curIdEq = array( 'curid' => $rc->mAttribs['rc_cur_id'] );
# If it's a new day, add the headline and flush the cache
- $date = $wgLang->date( $rc_timestamp, true );
+ $date = $this->getLang()->date( $rc->mAttribs['rc_timestamp'], true );
$ret = '';
if( $date != $this->lastdate ) {
# Process current cache
@@ -598,48 +661,50 @@ class EnhancedChangesList extends ChangesList {
}
# Should patrol-related stuff be shown?
- if( $wgUser->useRCPatrol() ) {
- $rc->unpatrolled = !$rc_patrolled;
+ if( $this->getUser()->useRCPatrol() ) {
+ $rc->unpatrolled = !$rc->mAttribs['rc_patrolled'];
} else {
$rc->unpatrolled = false;
}
$showdifflinks = true;
# Make article link
+ $type = $rc->mAttribs['rc_type'];
+ $logType = $rc->mAttribs['rc_log_type'];
// Page moves
- if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
- $msg = ( $rc_type == RC_MOVE ) ? "1movedto2" : "1movedto2_redir";
- $clink = wfMsg( $msg, $this->skin->linkKnown( $rc->getTitle(), null,
+ if( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
+ $msg = ( $type == RC_MOVE ) ? "1movedto2" : "1movedto2_redir";
+ $clink = wfMsg( $msg, Linker::linkKnown( $rc->getTitle(), null,
array(), array( 'redirect' => 'no' ) ),
- $this->skin->linkKnown( $rc->getMovedToTitle() ) );
+ Linker::linkKnown( $rc->getMovedToTitle() ) );
// New unpatrolled pages
- } else if( $rc->unpatrolled && $rc_type == RC_NEW ) {
- $clink = $this->skin->linkKnown( $rc->getTitle(), null, array(),
- array( 'rcid' => $rc_id ) );
+ } elseif( $rc->unpatrolled && $type == RC_NEW ) {
+ $clink = Linker::linkKnown( $rc->getTitle(), null, array(),
+ array( 'rcid' => $rc->mAttribs['rc_id'] ) );
// Log entries
- } else if( $rc_type == RC_LOG ) {
- if( $rc_log_type ) {
- $logtitle = SpecialPage::getTitleFor( 'Log', $rc_log_type );
- $clink = '(' . $this->skin->linkKnown( $logtitle,
- LogPage::logName($rc_log_type) ) . ')';
+ } elseif( $type == RC_LOG ) {
+ if( $logType ) {
+ $logtitle = SpecialPage::getTitleFor( 'Log', $logType );
+ $clink = '(' . Linker::linkKnown( $logtitle,
+ LogPage::logName( $logType ) ) . ')';
} else {
- $clink = $this->skin->link( $rc->getTitle() );
+ $clink = Linker::link( $rc->getTitle() );
}
$watched = false;
// Log entries (old format) and special pages
- } elseif( $rc_namespace == NS_SPECIAL ) {
- list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $rc_title );
+ } elseif( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
+ list( $specialName, $logtype ) = SpecialPageFactory::resolveAlias( $rc->mAttribs['rc_title'] );
if ( $specialName == 'Log' ) {
# Log updates, etc
$logname = LogPage::logName( $logtype );
- $clink = '(' . $this->skin->linkKnown( $rc->getTitle(), $logname ) . ')';
+ $clink = '(' . Linker::linkKnown( $rc->getTitle(), $logname ) . ')';
} else {
wfDebug( "Unexpected special page in recentchanges\n" );
$clink = '';
}
// Edits
} else {
- $clink = $this->skin->linkKnown( $rc->getTitle() );
+ $clink = Linker::linkKnown( $rc->getTitle() );
}
# Don't show unusable diff links
@@ -647,7 +712,7 @@ class EnhancedChangesList extends ChangesList {
$showdifflinks = false;
}
- $time = $wgLang->time( $rc_timestamp, true, true );
+ $time = $this->getLang()->time( $rc->mAttribs['rc_timestamp'], true, true );
$rc->watched = $watched;
$rc->link = $clink;
$rc->timestamp = $time;
@@ -655,20 +720,22 @@ class EnhancedChangesList extends ChangesList {
# Make "cur" and "diff" links. Do not use link(), it is too slow if
# called too many times (50% of CPU time on RecentChanges!).
+ $thisOldid = $rc->mAttribs['rc_this_oldid'];
+ $lastOldid = $rc->mAttribs['rc_last_oldid'];
if( $rc->unpatrolled ) {
- $rcIdQuery = array( 'rcid' => $rc_id );
+ $rcIdQuery = array( 'rcid' => $rc->mAttribs['rc_id'] );
} else {
$rcIdQuery = array();
}
- $querycur = $curIdEq + array( 'diff' => '0', 'oldid' => $rc_this_oldid );
- $querydiff = $curIdEq + array( 'diff' => $rc_this_oldid, 'oldid' =>
- $rc_last_oldid ) + $rcIdQuery;
+ $querycur = $curIdEq + array( 'diff' => '0', 'oldid' => $thisOldid );
+ $querydiff = $curIdEq + array( 'diff' => $thisOldid, 'oldid' =>
+ $lastOldid ) + $rcIdQuery;
if( !$showdifflinks ) {
$curLink = $this->message['cur'];
$diffLink = $this->message['diff'];
- } else if( in_array( $rc_type, array(RC_NEW,RC_LOG,RC_MOVE,RC_MOVE_OVER_REDIRECT) ) ) {
- if ( $rc_type != RC_NEW ) {
+ } elseif( in_array( $type, array( RC_NEW, RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) {
+ if ( $type != RC_NEW ) {
$curLink = $this->message['cur'];
} else {
$curUrl = htmlspecialchars( $rc->getTitle()->getLinkUrl( $querycur ) );
@@ -683,21 +750,21 @@ class EnhancedChangesList extends ChangesList {
}
# Make "last" link
- if( !$showdifflinks || !$rc_last_oldid ) {
+ if( !$showdifflinks || !$lastOldid ) {
$lastLink = $this->message['last'];
- } else if( $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
+ } elseif( in_array( $type, array( RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) {
$lastLink = $this->message['last'];
} else {
- $lastLink = $this->skin->linkKnown( $rc->getTitle(), $this->message['last'],
- array(), $curIdEq + array('diff' => $rc_this_oldid, 'oldid' => $rc_last_oldid) + $rcIdQuery );
+ $lastLink = Linker::linkKnown( $rc->getTitle(), $this->message['last'],
+ array(), $curIdEq + array('diff' => $thisOldid, 'oldid' => $lastOldid) + $rcIdQuery );
}
# Make user links
- if( $this->isDeleted($rc,Revision::DELETED_USER) ) {
- $rc->userlink = ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
+ if( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
+ $rc->userlink = ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
} else {
- $rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text );
- $rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text );
+ $rc->userlink = Linker::userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ $rc->usertalklink = Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
}
$rc->lastlink = $lastLink;
@@ -708,13 +775,13 @@ class EnhancedChangesList extends ChangesList {
# Page moves go on their own line
$title = $rc->getTitle();
$secureName = $title->getPrefixedDBkey();
- if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
+ if( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
# Use an @ character to prevent collision with page names
$this->rc_cache['@@' . ($this->rcMoveIndex++)] = array($rc);
} else {
# Logs are grouped by type
- if( $rc_type == RC_LOG ){
- $secureName = SpecialPage::getTitleFor( 'Log', $rc_log_type )->getPrefixedDBkey();
+ if( $type == RC_LOG ){
+ $secureName = SpecialPage::getTitleFor( 'Log', $logType )->getPrefixedDBkey();
}
if( !isset( $this->rc_cache[$secureName] ) ) {
$this->rc_cache[$secureName] = array();
@@ -732,16 +799,16 @@ class EnhancedChangesList extends ChangesList {
* Enhanced RC group
*/
protected function recentChangesBlockGroup( $block ) {
- global $wgLang, $wgContLang, $wgRCShowChangedSize;
+ global $wgRCShowChangedSize;
wfProfileIn( __METHOD__ );
# 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'] );
+ $classes = 'mw-collapsible mw-collapsed 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'] );
+ $classes = 'mw-collapsible mw-collapsed 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' );
@@ -794,51 +861,56 @@ class EnhancedChangesList extends ChangesList {
$users = array();
foreach( $userlinks as $userlink => $count) {
$text = $userlink;
- $text .= $wgContLang->getDirMark();
+ $text .= $this->getLang()->getDirMark();
if( $count > 1 ) {
- $text .= ' (' . $wgLang->formatNum( $count ) . '×)';
+ $text .= ' (' . $this->getLang()->formatNum( $count ) . '×)';
}
array_push( $users, $text );
}
- $users = ' <span class="changedby">[' .
+ $users = ' <span class="changedby">[' .
implode( $this->message['semicolon-separator'], $users ) . ']</span>';
- # ID for JS visibility toggle
- $jsid = $this->rcCacheIndex;
- # onclick handler to toggle hidden/expanded
- $toggleLink = "onclick='toggleVisibility($jsid); return false'";
# Title for <a> tags
$expandTitle = htmlspecialchars( wfMsg( 'rc-enhanced-expand' ) );
$closeTitle = htmlspecialchars( wfMsg( 'rc-enhanced-hide' ) );
- $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.'&#160;';
+ $tl = "<span class='mw-collapsible-toggle'>"
+ . "<span class='mw-rc-openarrow'>"
+ . "<a href='#' title='$expandTitle'>{$this->sideArrow()}</a>"
+ . "</span><span class='mw-rc-closearrow'>"
+ . "<a href='#' title='$closeTitle'>{$this->downArrow()}</a>"
+ . "</span></span>";
+ $r .= "<td>$tl</td>";
# Main line
- $r .= $this->recentChangesFlags( $isnew, false, $unpatrolled, '&#160;', $bot );
+ $r .= '<td class="mw-enhanced-rc">' . $this->recentChangesFlags( array(
+ 'newpage' => $isnew,
+ 'minor' => false,
+ 'unpatrolled' => $unpatrolled,
+ 'bot' => $bot ,
+ ) );
# Timestamp
- $r .= '&#160;'.$block[0]->timestamp.'&#160;</td><td style="padding:0px;">';
+ $r .= '&#160;'.$block[0]->timestamp.'&#160;</td><td>';
# Article link
if( $namehidden ) {
$r .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-event' ) . '</span>';
- } else if( $allLogs ) {
+ } elseif( $allLogs ) {
$r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
} else {
$this->insertArticleLink( $r, $block[0], $block[0]->unpatrolled, $block[0]->watched );
}
- $r .= $wgContLang->getDirMark();
+ $r .= $this->getLang()->getDirMark();
$queryParams['curid'] = $curId;
# Changes message
$n = count($block);
static $nchanges = array();
if ( !isset( $nchanges[$n] ) ) {
- $nchanges[$n] = wfMsgExt( 'nchanges', array( 'parsemag', 'escape' ), $wgLang->formatNum( $n ) );
+ $nchanges[$n] = wfMsgExt( 'nchanges', array( 'parsemag', 'escape' ), $this->getLang()->formatNum( $n ) );
}
# Total change link
$r .= ' ';
@@ -846,14 +918,14 @@ class EnhancedChangesList extends ChangesList {
$r .= '(';
if( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT ) ) {
$r .= $nchanges[$n];
- } else if( $isnew ) {
+ } elseif( $isnew ) {
$r .= $nchanges[$n];
} else {
$params = $queryParams;
$params['diff'] = $currentRevision;
$params['oldid'] = $oldid;
-
- $r .= $this->skin->link(
+
+ $r .= Linker::link(
$block[0]->getTitle(),
$nchanges[$n],
array(),
@@ -866,19 +938,18 @@ class EnhancedChangesList extends ChangesList {
# History
if( $allLogs ) {
// don't show history link for logs
- } else if( $namehidden || !$block[0]->getTitle()->exists() ) {
+ } elseif( $namehidden || !$block[0]->getTitle()->exists() ) {
$r .= $this->message['pipe-separator'] . $this->message['hist'] . ')';
} else {
$params = $queryParams;
$params['action'] = 'history';
$r .= $this->message['pipe-separator'] .
- $this->skin->link(
+ Linker::linkKnown(
$block[0]->getTitle(),
$this->message['hist'],
array(),
- $params,
- array( 'known', 'noclasses' )
+ $params
) . ')';
}
$r .= ' . . ';
@@ -908,55 +979,51 @@ class EnhancedChangesList extends ChangesList {
$r .= $users;
$r .= $this->numberofWatchingusers($block[0]->numberofWatchingusers);
- $r .= "</td></tr></table>\n";
-
# Sub-entries
- $r .= '<div id="mw-rc-subentries-'.$jsid.'" class="mw-changeslist-hidden">';
- $r .= '<table class="mw-enhanced-rc">';
foreach( $block as $rcObj ) {
- # 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 to apply -- TODO implement
$classes = array();
- extract( $rcObj->mAttribs );
+ $type = $rcObj->mAttribs['rc_type'];
#$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, '&#160;', $rc_bot );
- $r .= '&#160;</td><td style="vertical-align:top; padding:0px;"><span style="font-family:monospace">';
+ $r .= '<tr><td></td><td class="mw-enhanced-rc">';
+ $r .= $this->recentChangesFlags( array(
+ 'newpage' => $rcObj->mAttribs['rc_new'],
+ 'minor' => $rcObj->mAttribs['rc_minor'],
+ 'unpatrolled' => $rcObj->unpatrolled,
+ 'bot' => $rcObj->mAttribs['rc_bot'],
+ ) );
+ $r .= '&#160;</td><td class="mw-enhanced-rc-nested"><span class="mw-enhanced-rc-time">';
$params = $queryParams;
- if( $rc_this_oldid != 0 ) {
- $params['oldid'] = $rc_this_oldid;
+ if( $rcObj->mAttribs['rc_this_oldid'] != 0 ) {
+ $params['oldid'] = $rcObj->mAttribs['rc_this_oldid'];
}
# Log timestamp
- if( $rc_type == RC_LOG ) {
+ if( $type == RC_LOG ) {
$link = $rcObj->timestamp;
# Revision link
- } else if( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) {
+ } elseif( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) {
$link = '<span class="history-deleted">'.$rcObj->timestamp.'</span> ';
} else {
- if ( $rcObj->unpatrolled && $rc_type == RC_NEW) {
+ if ( $rcObj->unpatrolled && $type == RC_NEW) {
$params['rcid'] = $rcObj->mAttribs['rc_id'];
}
- $link = $this->skin->link(
+ $link = Linker::linkKnown(
$rcObj->getTitle(),
$rcObj->timestamp,
array(),
- $params,
- array( 'known', 'noclasses' )
+ $params
);
if( $this->isDeleted($rcObj,Revision::DELETED_TEXT) )
$link = '<span class="history-deleted">'.$link.'</span> ';
}
$r .= $link . '</span>';
- if ( !$rc_type == RC_LOG || $rc_type == RC_NEW ) {
+ if ( !$type == RC_LOG || $type == RC_NEW ) {
$r .= ' (';
$r .= $rcObj->curlink;
$r .= $this->message['pipe-separator'];
@@ -966,9 +1033,10 @@ class EnhancedChangesList extends ChangesList {
$r .= ' . . ';
# Character diff
- if( $wgRCShowChangedSize ) {
- $r .= ( $rcObj->getCharacterDifference() == '' ? '' : $rcObj->getCharacterDifference() . ' . . ' ) ;
+ if( $wgRCShowChangedSize && $rcObj->getCharacterDifference() ) {
+ $r .= $rcObj->getCharacterDifference() . ' . . ' ;
}
+
# User links
$r .= $rcObj->userlink;
$r .= $rcObj->usertalklink;
@@ -983,7 +1051,7 @@ class EnhancedChangesList extends ChangesList {
$r .= "</td></tr>\n";
}
- $r .= "</table></div>\n";
+ $r .= "</table>\n";
$this->rcCacheIndex++;
@@ -1013,8 +1081,8 @@ class EnhancedChangesList extends ChangesList {
* @return String: HTML <img> tag
*/
protected function sideArrow() {
- global $wgContLang;
- $dir = $wgContLang->isRTL() ? 'l' : 'r';
+ global $wgLang;
+ $dir = $wgLang->isRTL() ? 'l' : 'r';
return $this->arrow( $dir, '+', wfMsg( 'rc-enhanced-expand' ) );
}
@@ -1036,70 +1104,58 @@ class EnhancedChangesList extends ChangesList {
}
/**
- * Add a set of spaces
- * @return String: HTML <td> tag
- */
- protected function spacerIndent() {
- return '&#160;&#160;&#160;&#160;&#160;';
- }
-
- /**
* Enhanced RC ungrouped line.
+ *
+ * @param $rcObj RecentChange
* @return String: a HTML formated line (generated using $r)
*/
protected function recentChangesBlockLine( $rcObj ) {
global $wgRCShowChangedSize;
wfProfileIn( __METHOD__ );
+ $query['curid'] = $rcObj->mAttribs['rc_cur_id'];
- # 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.
- // TODO implement
- extract( $rcObj->mAttribs );
- $query['curid'] = $rc_cur_id;
-
- if( $rc_log_type ) {
+ $type = $rcObj->mAttribs['rc_type'];
+ $logType = $rcObj->mAttribs['rc_log_type'];
+ if( $logType ) {
# Log entry
- $classes = 'mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-log-' . $rc_log_type . '-' . $rcObj->mAttribs['rc_title'] );
+ $classes = 'mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType . '-' . $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;';
+ $r .= '<td class="mw-enhanced-rc">' . $this->spacerArrow();
# Flag and Timestamp
- if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
+ if( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
$r .= '&#160;&#160;&#160;&#160;'; // 4 flags -> 4 spaces
} else {
- $r .= $this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $rcObj->unpatrolled, '&#160;', $rc_bot );
+ $r .= $this->recentChangesFlags( array(
+ 'newpage' => $type == RC_NEW,
+ 'minor' => $rcObj->mAttribs['rc_minor'],
+ 'unpatrolled' => $rcObj->unpatrolled,
+ 'bot' => $rcObj->mAttribs['rc_bot'],
+ ) );
}
- $r .= '&#160;'.$rcObj->timestamp.'&#160;</td><td style="padding:0px;">';
+ $r .= '&#160;'.$rcObj->timestamp.'&#160;</td><td>';
# Article or log link
- if( $rc_log_type ) {
- $logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
- $logname = LogPage::logName( $rc_log_type );
- $r .= '(' . $this->skin->link(
- $logtitle,
- $logname,
- array(),
- array(),
- array( 'known', 'noclasses' )
- ) . ')';
+ if( $logType ) {
+ $logtitle = SpecialPage::getTitleFor( 'Log', $logType );
+ $logname = LogPage::logName( $logType );
+ $r .= '(' . Linker::linkKnown( $logtitle, htmlspecialchars( $logname ) ) . ')';
} else {
$this->insertArticleLink( $r, $rcObj, $rcObj->unpatrolled, $rcObj->watched );
}
# Diff and hist links
- if ( $rc_type != RC_LOG ) {
+ if ( $type != RC_LOG ) {
$r .= ' ('. $rcObj->difflink . $this->message['pipe-separator'];
$query['action'] = 'history';
- $r .= $this->skin->link(
+ $r .= Linker::linkKnown(
$rcObj->getTitle(),
$this->message['hist'],
array(),
- $query,
- array( 'known', 'noclasses' )
+ $query
) . ')';
}
$r .= ' . . ';
@@ -1110,12 +1166,12 @@ class EnhancedChangesList extends ChangesList {
# User/talk
$r .= ' '.$rcObj->userlink . $rcObj->usertalklink;
# Log action (if any)
- if( $rc_log_type ) {
+ if( $logType ) {
if( $this->isDeleted($rcObj,LogPage::DELETED_ACTION) ) {
$r .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
} else {
- $r .= ' ' . LogPage::actionText( $rc_log_type, $rc_log_action, $rcObj->getTitle(),
- $this->skin, LogPage::extractParams($rc_params), true, true );
+ $r .= ' ' . LogPage::actionText( $logType, $rcObj->mAttribs['rc_log_action'], $rcObj->getTitle(),
+ $this->getSkin(), LogPage::extractParams( $rcObj->mAttribs['rc_params'] ), true, true );
}
}
$this->insertComment( $r, $rcObj );
@@ -1136,6 +1192,8 @@ class EnhancedChangesList extends ChangesList {
/**
* If enhanced RC is in use, this function takes the previously cached
* RC lines, arranges them, and outputs the HTML
+ *
+ * @return string
*/
protected function recentChangesBlock() {
if( count ( $this->rc_cache ) == 0 ) {
@@ -1159,7 +1217,7 @@ class EnhancedChangesList extends ChangesList {
}
/**
- * Returns text for the end of RC
+ * Returns text for the end of RC
* If enhanced RC is in use, returns pretty much all the text
*/
public function endRecentChangesList() {
diff --git a/includes/Collation.php b/includes/Collation.php
index f00b568f..0c510b78 100644
--- a/includes/Collation.php
+++ b/includes/Collation.php
@@ -3,6 +3,9 @@
abstract class Collation {
static $instance;
+ /**
+ * @return Collation
+ */
static function singleton() {
if ( !self::$instance ) {
global $wgCategoryCollation;
@@ -11,13 +14,30 @@ abstract class Collation {
return self::$instance;
}
+ /**
+ * @throws MWException
+ * @param $collationName string
+ * @return Collation
+ */
static function factory( $collationName ) {
switch( $collationName ) {
case 'uppercase':
return new UppercaseCollation;
+ case 'identity':
+ return new IdentityCollation;
case 'uca-default':
return new IcuCollation( 'root' );
default:
+ # Provide a mechanism for extensions to hook in.
+
+ $collationObject = null;
+ wfRunHooks( 'Collation::factory', array( $collationName, &$collationObject ) );
+
+ if ( $collationObject instanceof Collation ) {
+ return $collationObject;
+ }
+
+ // If all else fails...
throw new MWException( __METHOD__.": unknown collation type \"$collationName\"" );
}
}
@@ -81,6 +101,30 @@ class UppercaseCollation extends Collation {
}
}
+/**
+ * Collation class that's essentially a no-op.
+ *
+ * Does sorting based on binary value of the string.
+ * Like how things were pre 1.17.
+ */
+class IdentityCollation extends Collation {
+
+ function getSortKey( $string ) {
+ return $string;
+ }
+
+ function getFirstLetter( $string ) {
+ global $wgContLang;
+ // Copied from UppercaseCollation.
+ // I'm kind of unclear on when this could happen...
+ if ( $string[0] == "\0" ) {
+ $string = substr( $string, 1 );
+ }
+ return $wgContLang->firstChar( $string );
+ }
+}
+
+
class IcuCollation extends Collation {
var $primaryCollator, $mainCollator, $locale;
var $firstLetterData;
@@ -259,13 +303,13 @@ class IcuCollation extends Collation {
* 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
+ * @param $valueCallback array A function to call to get the value with
* a given array index.
- * @param $valueCount The number of items accessible via $valueCallback,
+ * @param $valueCount int The number of items accessible via $valueCallback,
* indexed from 0 to $valueCount - 1
- * @param $comparisonCallback A callback to compare two values, returning
+ * @param $comparisonCallback array A callback to compare two values, returning
* -1, 0 or 1 in the style of strcmp().
- * @param $target The target value to find.
+ * @param $target string The target value to find.
*
* @return The item index of the lower bound, or false if the target value
* sorts before all items.
diff --git a/includes/ConfEditor.php b/includes/ConfEditor.php
index b08b77df..42a7173d 100644
--- a/includes/ConfEditor.php
+++ b/includes/ConfEditor.php
@@ -1,6 +1,6 @@
<?php
-/**
+/**
* This is a state machine style parser with two internal stacks:
* * A next state stack, which determines the state the machine will progress to next
* * A path stack, which keeps track of the logical location in the file.
@@ -40,8 +40,8 @@ class ConfEditor {
/** The previous ConfEditorToken object */
var $prevToken;
- /**
- * The state machine stack. This is an array of strings where the topmost
+ /**
+ * The state machine stack. This is an array of strings where the topmost
* element will be popped off and become the next parser state.
*/
var $stateStack;
@@ -66,7 +66,7 @@ class ConfEditor {
var $pathStack;
/**
- * The elements of the top of the pathStack for every path encountered, indexed
+ * The elements of the top of the pathStack for every path encountered, indexed
* by slash-separated path.
*/
var $pathInfo;
@@ -77,13 +77,17 @@ class ConfEditor {
var $serial;
/**
- * Editor state. This consists of the internal copy/insert operations which
+ * Editor state. This consists of the internal copy/insert operations which
* are applied to the source string to obtain the destination string.
*/
var $edits;
/**
* Simple entry point for command-line testing
+ *
+ * @param $text string
+ *
+ * @return string
*/
static function test( $text ) {
try {
@@ -103,7 +107,7 @@ class ConfEditor {
}
/**
- * Edit the text. Returns the edited text.
+ * Edit the text. Returns the edited text.
* @param $ops Array of operations.
*
* Operations are given as an associative array, with members:
@@ -114,20 +118,20 @@ class ConfEditor {
*
* delete
* Deletes an array element or statement with the specified path.
- * e.g.
+ * e.g.
* array('type' => 'delete', 'path' => '$foo/bar/baz' )
* is equivalent to the runtime PHP code:
* unset( $foo['bar']['baz'] );
*
* set
- * Sets the value of an array element. If the element doesn't exist, it
- * is appended to the array. If it does exist, the value is set, with
+ * Sets the value of an array element. If the element doesn't exist, it
+ * is appended to the array. If it does exist, the value is set, with
* comments and indenting preserved.
*
* append
* Appends a new element to the end of the array. Adds a trailing comma.
* e.g.
- * array( 'type' => 'append', 'path', '$foo/bar',
+ * array( 'type' => 'append', 'path', '$foo/bar',
* 'key' => 'baz', 'value' => "'x'" )
* is like the PHP code:
* $foo['bar']['baz'] = 'x';
@@ -187,7 +191,7 @@ class ConfEditor {
list( $indent, ) = $this->getIndent( $start );
$textToInsert = "$indent$value,";
} else {
- list( $indent, $arrowIndent ) =
+ list( $indent, $arrowIndent ) =
$this->getIndent( $start, $key, $lastEltInfo['arrowByte'] );
$textToInsert = "$indent$key$arrowIndent=> $value,";
}
@@ -210,7 +214,7 @@ class ConfEditor {
list( $indent, ) = $this->getIndent( $start );
$textToInsert = "$indent$value,";
} else {
- list( $indent, $arrowIndent ) =
+ list( $indent, $arrowIndent ) =
$this->getIndent( $start, $key, $info['arrowByte'] );
$textToInsert = "$indent$key$arrowIndent=> $value,";
}
@@ -239,13 +243,13 @@ class ConfEditor {
try {
$this->parse();
} catch ( ConfEditorParseError $e ) {
- throw new MWException(
+ throw new MWException(
"Sorry, ConfEditor broke the file during editing and it won't parse anymore: " .
$e->getMessage() );
}
return $out;
}
-
+
/**
* Get the variables defined in the text
* @return array( varname => value )
@@ -266,7 +270,7 @@ class ConfEditor {
strlen( $trimmedPath ) - strlen( $name ) );
if( substr( $parentPath, -1 ) == '/' )
$parentPath = substr( $parentPath, 0, -1 );
-
+
$value = substr( $this->text, $data['valueStartByte'],
$data['valueEndByte'] - $data['valueStartByte']
);
@@ -275,7 +279,7 @@ class ConfEditor {
}
return $vars;
}
-
+
/**
* Set a value in an array, unless it's set already. For instance,
* setVar( $arr, 'foo/bar', 'baz', 3 ); will set
@@ -298,7 +302,7 @@ class ConfEditor {
if ( !isset( $target[$key] ) )
$target[$key] = $value;
}
-
+
/**
* Parse a scalar value in PHP
* @return mixed Parsed value
@@ -306,14 +310,14 @@ class ConfEditor {
function parseScalar( $str ) {
if ( $str !== '' && $str[0] == '\'' )
// Single-quoted string
- // @todo Fixme: trim() call is due to mystery bug where whitespace gets
+ // @todo FIXME: trim() call is due to mystery bug where whitespace gets
// appended to the token; without it we ended up reading in the
// extra quote on the end!
return strtr( substr( trim( $str ), 1, -1 ),
array( '\\\'' => '\'', '\\\\' => '\\' ) );
- if ( $str !== '' && @$str[0] == '"' )
+ if ( $str !== '' && $str[0] == '"' )
// Double-quoted string
- // @todo Fixme: trim() call is due to mystery bug where whitespace gets
+ // @todo FIXME: trim() call is due to mystery bug where whitespace gets
// appended to the token; without it we ended up reading in the
// extra quote on the end!
return stripcslashes( substr( trim( $str ), 1, -1 ) );
@@ -364,8 +368,8 @@ class ConfEditor {
}
/**
- * Finds the source byte region which you would want to delete, if $pathName
- * was to be deleted. Includes the leading spaces and tabs, the trailing line
+ * Finds the source byte region which you would want to delete, if $pathName
+ * was to be deleted. Includes the leading spaces and tabs, the trailing line
* break, and any comments in between.
*/
function findDeletionRegion( $pathName ) {
@@ -419,9 +423,9 @@ class ConfEditor {
}
/**
- * Find the byte region in the source corresponding to the value part.
- * This includes the quotes, but does not include the trailing comma
- * or semicolon.
+ * Find the byte region in the source corresponding to the value part.
+ * This includes the quotes, but does not include the trailing comma
+ * or semicolon.
*
* The end position is the past-the-end (end + 1) value as per convention.
*/
@@ -472,7 +476,7 @@ class ConfEditor {
return $extraPath;
}
- /*
+ /**
* Find the path name of first element in the array.
* If the array is empty, this will return the \@extra interstitial element.
* If the specified path is not found or is not an array, it will return false.
@@ -519,7 +523,7 @@ class ConfEditor {
}
/**
- * Run the parser on the text. Throws an exception if the string does not
+ * Run the parser on the text. Throws an exception if the string does not
* match our defined subset of PHP syntax.
*/
public function parse() {
@@ -706,7 +710,7 @@ class ConfEditor {
}
/**
- * Set the parse position. Do not call this except from firstToken() and
+ * Set the parse position. Do not call this except from firstToken() and
* nextToken(), there is more to update than just the position.
*/
protected function setPos( $pos ) {
@@ -800,7 +804,7 @@ class ConfEditor {
if ( $this->currentToken && $this->currentToken->type == $type ) {
return $this->nextToken();
} else {
- $this->error( "expected " . $this->getTypeName( $type ) .
+ $this->error( "expected " . $this->getTypeName( $type ) .
", got " . $this->getTypeName( $this->currentToken->type ) );
}
}
@@ -875,7 +879,7 @@ class ConfEditor {
}
/**
- * Go to the next path on the same level. This ends the current path and
+ * Go to the next path on the same level. This ends the current path and
* starts a new one. If $path is \@next, the new path is set to the next
* numeric array element.
*/
@@ -889,7 +893,7 @@ class ConfEditor {
} else {
$this->pathStack[$i]['name'] = $path;
}
- $this->pathStack[$i] =
+ $this->pathStack[$i] =
array(
'startByte' => $this->byteNum,
'startToken' => $this->pos,
@@ -955,8 +959,8 @@ class ConfEditor {
}
/**
- * Looks ahead to see if the given type is the next token type, starting
- * from the current position plus the given offset. Skips any intervening
+ * Looks ahead to see if the given type is the next token type, starting
+ * from the current position plus the given offset. Skips any intervening
* whitespace.
*/
function isAhead( $type, $offset = 0 ) {
@@ -992,8 +996,8 @@ class ConfEditor {
$out = '';
foreach ( $this->tokens as $token ) {
$obj = $this->newTokenObj( $token );
- $out .= sprintf( "%-28s %s\n",
- $this->getTypeName( $obj->type ),
+ $out .= sprintf( "%-28s %s\n",
+ $this->getTypeName( $obj->type ),
addcslashes( $obj->text, "\0..\37" ) );
}
echo "<pre>" . htmlspecialchars( $out ) . "</pre>";
@@ -1008,7 +1012,7 @@ class ConfEditorParseError extends MWException {
function __construct( $editor, $msg ) {
$this->lineNum = $editor->lineNum;
$this->colNum = $editor->colNum;
- parent::__construct( "Parse error on line {$editor->lineNum} " .
+ parent::__construct( "Parse error on line {$editor->lineNum} " .
"col {$editor->colNum}: $msg" );
}
@@ -1028,7 +1032,7 @@ class ConfEditorParseError extends MWException {
*/
class ConfEditorToken {
var $type, $text;
-
+
static $scalarTypes = array( T_LNUMBER, T_DNUMBER, T_STRING, T_CONSTANT_ENCAPSED_STRING );
static $skipTypes = array( T_WHITESPACE, T_COMMENT, T_DOC_COMMENT );
diff --git a/includes/Cookie.php b/includes/Cookie.php
new file mode 100644
index 00000000..95a4599f
--- /dev/null
+++ b/includes/Cookie.php
@@ -0,0 +1,245 @@
+<?php
+/**
+ * @defgroup HTTP HTTP
+ */
+
+class Cookie {
+ protected $name;
+ protected $value;
+ protected $expires;
+ protected $path;
+ protected $domain;
+ protected $isSessionKey = true;
+ // TO IMPLEMENT protected $secure
+ // TO IMPLEMENT? protected $maxAge (add onto expires)
+ // TO IMPLEMENT? protected $version
+ // TO IMPLEMENT? protected $comment
+
+ function __construct( $name, $value, $attr ) {
+ $this->name = $name;
+ $this->set( $value, $attr );
+ }
+
+ /**
+ * 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 $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'] ) ) {
+ $this->isSessionKey = false;
+ $this->expires = strtotime( $attr['expires'] );
+ }
+
+ if ( isset( $attr['path'] ) ) {
+ $this->path = $attr['path'];
+ } else {
+ $this->path = '/';
+ }
+
+ if ( isset( $attr['domain'] ) ) {
+ if ( self::validateCookieDomain( $attr['domain'] ) ) {
+ $this->domain = $attr['domain'];
+ }
+ } else {
+ throw new MWException( 'You must specify a domain.' );
+ }
+ }
+
+ /**
+ * Return the true if the cookie is valid is valid. Otherwise,
+ * false. The uses a method similar to IE cookie security
+ * described here:
+ * http://kuza55.blogspot.com/2008/02/understanding-cookie-security.html
+ * A better method might be to use a blacklist like
+ * http://publicsuffix.org/
+ *
+ * @fixme fails to detect 3-letter top-level domains
+ * @fixme fails to detect 2-letter top-level domains for single-domain use (probably not a big problem in practice, but there are test cases)
+ *
+ * @param $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 ) {
+ // Don't allow a trailing dot
+ if ( substr( $domain, -1 ) == '.' ) {
+ return false;
+ }
+
+ $dc = explode( ".", $domain );
+
+ // Only allow full, valid IP addresses
+ if ( preg_match( '/^[0-9.]+$/', $domain ) ) {
+ if ( count( $dc ) != 4 ) {
+ return false;
+ }
+
+ if ( ip2long( $domain ) === false ) {
+ return false;
+ }
+
+ 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 ) ) {
+ return false;
+ }
+ 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 ) {
+ return false;
+ }
+
+ if ( substr( $domain, 0, 1 ) == '.'
+ && substr_compare( $originDomain, $domain, -strlen( $domain ),
+ strlen( $domain ), true ) != 0 ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 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
+ */
+ public function serializeToHttpRequest( $path, $domain ) {
+ $ret = '';
+
+ if ( $this->canServeDomain( $domain )
+ && $this->canServePath( $path )
+ && $this->isUnExpired() ) {
+ $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 ) == '.'
+ && 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 ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function isUnExpired() {
+ if ( $this->isSessionKey || $this->expires > time() ) {
+ return true;
+ }
+
+ return false;
+ }
+}
+
+class CookieJar {
+ private $cookie = array();
+
+ /**
+ * Set a cookie in the cookie jar. Make sure only one cookie per-name exists.
+ * @see Cookie::set()
+ */
+ 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] ) ) {
+ $this->cookie[$index]->set( $value, $attr );
+ } else {
+ $this->cookie[$index] = new Cookie( $name, $value, $attr );
+ }
+ }
+
+ /**
+ * @see Cookie::serializeToHttpRequest
+ */
+ public function serializeToHttpRequest( $path, $domain ) {
+ $cookies = array();
+
+ foreach ( $this->cookie as $c ) {
+ $serialized = $c->serializeToHttpRequest( $path, $domain );
+
+ if ( $serialized ) {
+ $cookies[] = $serialized;
+ }
+ }
+
+ return implode( '; ', $cookies );
+ }
+
+ /**
+ * Parse the content of an Set-Cookie HTTP Response header.
+ *
+ * @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 );
+ $attr = array();
+
+ foreach ( $bit as $piece ) {
+ $parts = explode( '=', $piece );
+ if ( count( $parts ) > 1 ) {
+ $attr[strtolower( $parts[0] )] = $parts[1];
+ } else {
+ $attr[strtolower( $parts[0] )] = true;
+ }
+ }
+
+ if ( !isset( $attr['domain'] ) ) {
+ $attr['domain'] = $domain;
+ } elseif ( !Cookie::validateCookieDomain( $attr['domain'], $domain ) ) {
+ return null;
+ }
+
+ $this->setCookie( $name, $value, $attr );
+ }
+ }
+}
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 0395633d..4248add7 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -26,58 +26,43 @@ if( !defined( 'MEDIAWIKI' ) ) {
die( 1 );
}
-# 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;
-}
+# Create a site configuration object. Not used for much in a default install.
+# Note: this (and other things) will break if the autoloader is not enabled.
+# Please include includes/AutoLoader.php before including this file.
+$wgConf = new SiteConfiguration;
/** @endcond */
/** MediaWiki version number */
-$wgVersion = '1.17.1';
+$wgVersion = '1.18.0';
/** Name of the site. It must be changed in LocalSettings.php */
$wgSitename = 'MediaWiki';
/**
- * URL of the server. It will be automatically built including https mode.
+ * URL of the server.
*
* Example:
* <code>
- * $wgServer = http://example.com
+ * $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.
+ *
+ * If you want to use protocol-relative URLs on your wiki, set this to a
+ * protocol-relative URL like '//example.com' and set $wgCanonicalServer
+ * to a fully qualified URL.
*/
-$wgServer = '';
-
-/** @cond file_level_code */
-if( isset( $_SERVER['SERVER_NAME'] ) ) {
- $serverName = $_SERVER['SERVER_NAME'];
-} elseif( isset( $_SERVER['HOSTNAME'] ) ) {
- $serverName = $_SERVER['HOSTNAME'];
-} elseif( isset( $_SERVER['HTTP_HOST'] ) ) {
- $serverName = $_SERVER['HTTP_HOST'];
-} elseif( isset( $_SERVER['SERVER_ADDR'] ) ) {
- $serverName = $_SERVER['SERVER_ADDR'];
-} else {
- $serverName = 'localhost';
-}
-
-$wgProto = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http';
+$wgServer = WebRequest::detectServer();
-$wgServer = $wgProto.'://' . $serverName;
-# If the port is a non-standard one, add it to the URL
-if( isset( $_SERVER['SERVER_PORT'] )
- && !strpos( $serverName, ':' )
- && ( ( $wgProto == 'http' && $_SERVER['SERVER_PORT'] != 80 )
- || ( $wgProto == 'https' && $_SERVER['SERVER_PORT'] != 443 ) ) ) {
-
- $wgServer .= ":" . $_SERVER['SERVER_PORT'];
-}
-/** @endcond */
+/**
+ * Canonical URL of the server, to use in IRC feeds and notification e-mails.
+ * Must be fully qualified, even if $wgServer is protocol-relative.
+ *
+ * Defaults to $wgServer, expanded to a fully qualified http:// URL if needed.
+ */
+$wgCanonicalServer = false;
/************************************************************************//**
* @name Script path settings
@@ -147,6 +132,7 @@ $wgRedirectScript = false; ///< defaults to
*/
$wgLoadScript = false;
+
/**@}*/
/************************************************************************//**
@@ -183,6 +169,7 @@ $wgLocalStylePath = false;
/**
* The URL path of the extensions directory.
* Defaults to "{$wgScriptPath}/extensions".
+ * @since 1.16
*/
$wgExtensionAssetsPath = false;
@@ -228,23 +215,6 @@ $wgFavicon = '/favicon.ico';
$wgAppleTouchIcon = false;
/**
- * The URL path of the math directory. Defaults to "{$wgUploadPath}/math".
- *
- * See http://www.mediawiki.org/wiki/Manual:Enable_TeX for details about how to
- * set up mathematical formula display.
- */
-$wgMathPath = false;
-
-/**
- * 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.
- */
-$wgMathDirectory = false;
-
-/**
* The local filesystem path to a temporary directory. This is not required to
* be web accessible.
*
@@ -295,7 +265,7 @@ $wgAllowImageMoving = true;
$wgIllegalFileChars = ":";
/**
- * @deprecated use $wgDeletedDirectory
+ * @deprecated since 1.17 use $wgDeletedDirectory
*/
$wgFileStore = array();
@@ -327,7 +297,7 @@ $wgImgAuthPublicTest = true;
* - 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 (but $wgLocalFileRepo should be 'local').
*
* For most core repos:
* - url Base public URL
@@ -373,8 +343,12 @@ $wgImgAuthPublicTest = true;
* - 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.
+ * If you leave $wgLocalFileRepo set to false, Setup will fill in appropriate values.
+ * Otherwise, set $wgLocalFileRepo to a repository structure as described above.
+ * If you set $wgUseInstantCommons to true, it will add an entry for Commons.
+ * If you set $wgForeignFileRepos to an array of repostory structures, those will
+ * be searched after the local file repo.
+ * Otherwise, you will only have access to local media files.
*/
$wgLocalFileRepo = false;
@@ -393,7 +367,7 @@ $wgUseInstantCommons = false;
* 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
+ * To enable EXIF functions, add the following lines to the
* "Windows extensions" section of php.ini:
*
* extension=extensions/php_mbstring.dll
@@ -402,6 +376,13 @@ $wgUseInstantCommons = false;
$wgShowEXIF = function_exists( 'exif_read_data' );
/**
+ * If to automatically update the img_metadata field
+ * if the metadata field is outdated but compatible with the current version.
+ * Defaults to false.
+ */
+$wgUpdateCompatibleMetadata = false;
+
+/**
* 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.
@@ -434,12 +415,24 @@ $wgCacheSharedUploads = true;
$wgAllowCopyUploads = false;
/**
* Allow asynchronous copy uploads.
- * This feature is experimental.
+ * This feature is experimental and broken as of r81612.
*/
$wgAllowAsyncCopyUploads = false;
/**
- * Max size for uploads, in bytes. Applies to all uploads.
+ * Max size for uploads, in bytes. If not set to an array, applies to all
+ * uploads. If set to an array, per upload type maximums can be set, using the
+ * file and url keys. If the * key is set this value will be used as maximum
+ * for non-specified types.
+ *
+ * For example:
+ * $wgMaxUploadSize = array(
+ * '*' => 250 * 1024,
+ * 'url' => 500 * 1024,
+ * );
+ * Sets the maximum for all uploads to 250 kB except for upload-by-url, which
+ * will have a maximum of 500 kB.
+ *
*/
$wgMaxUploadSize = 1024*1024*100; # 100MB
@@ -536,22 +529,16 @@ $wgMimeTypeBlacklist = array(
'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',
);
/**
+ * Allow Java archive uploads.
+ * This is not recommended for public wikis since a maliciously-constructed
+ * applet running on the same domain as the wiki can steal the user's cookies.
+ */
+$wgAllowJavaUploads = false;
+
+/**
* 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.
@@ -593,7 +580,7 @@ $wgTrustedMediaFormats = array(
* Each entry in the array maps a MIME type to a class name
*/
$wgMediaHandlers = array(
- 'image/jpeg' => 'BitmapHandler',
+ 'image/jpeg' => 'JpegHandler',
'image/png' => 'PNGHandler',
'image/gif' => 'GIFHandler',
'image/tiff' => 'TiffHandler',
@@ -645,11 +632,18 @@ $wgImageMagickTempDir = false;
$wgCustomConvertCommand = false;
/**
+ * Some tests and extensions use exiv2 to manipulate the EXIF metadata in some image formats.
+ */
+$wgExiv2Command = '/usr/bin/exiv2';
+
+/**
* 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.
+ * If set to an array, the first item is a PHP callable and any further items
+ * are passed as parameters after $srcPath, $dstPath, $width, $height
*/
$wgSVGConverters = array(
'ImageMagick' => '$path/convert -background white -thumbnail $widthx$height\! $input PNG:$output',
@@ -658,6 +652,7 @@ $wgSVGConverters = array(
'batik' => 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input',
'rsvg' => '$path/rsvg -w$width -h$height $input $output',
'imgserv' => '$path/imgserv-wrapper -i svg -o png -w$width $input $output',
+ 'ImagickExt' => array( 'SvgHandler::rasterizeImagickExt' ),
);
/** Pick a converter defined in $wgSVGConverters */
$wgSVGConverter = 'ImageMagick';
@@ -667,7 +662,7 @@ $wgSVGConverterPath = '';
$wgSVGMaxSize = 2048;
/** Don't read SVG metadata beyond this point.
* Default is 1024*256 bytes */
-$wgSVGMetadataCutoff = 262144;
+$wgSVGMetadataCutoff = 262144;
/**
* MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't
@@ -744,6 +739,12 @@ $wgShowArchiveThumbnails = true;
/** Obsolete, always true, kept for compatibility with extensions */
$wgUseImageResize = true;
+/**
+ * If set to true, images that contain certain the exif orientation tag will
+ * be rotated accordingly. If set to null, try to auto-detect whether a scaler
+ * is available that can rotate.
+ */
+$wgEnableAutoRotation = null;
/**
* Internal name of virus scanner. This servers as a key to the
@@ -907,7 +908,7 @@ $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)
+ 'captionLength' => 25, // Length of caption to truncate (in characters)
'showBytes' => true, // Show the filesize in bytes in categories
);
@@ -976,6 +977,8 @@ $wgDjvuOutputExtension = 'jpg';
* @{
*/
+$serverName = substr( $wgServer, strrpos( $wgServer, '/' ) + 1 );
+
/**
* Site admin email address.
*/
@@ -1037,6 +1040,11 @@ $wgPasswordReminderResendTime = 24;
$wgNewPasswordExpiry = 3600 * 24 * 7;
/**
+ * The time, in seconds, when an email confirmation email expires
+ */
+$wgUserEmailConfirmationTokenExpiry = 7 * 24 * 60 * 60;
+
+/**
* SMTP Mode
* For using a direct (authenticated) SMTP server connection.
* Default to false or fill an array :
@@ -1053,6 +1061,7 @@ $wgSMTP = false;
/**
* Additional email parameters, will be passed as the last argument to mail() call.
+ * If using safe_mode this has no effect
*/
$wgAdditionalMailParams = null;
@@ -1177,8 +1186,6 @@ $wgSQLMode = '';
/** Mediawiki schema */
$wgDBmwschema = 'mediawiki';
-/** Tsearch2 schema */
-$wgDBts2schema = 'public';
/** To override default SQLite data directory ($docroot/../data) */
$wgSQLiteDataDir = '';
@@ -1375,6 +1382,7 @@ $wgExternalServers = array();
*
* $wgDefaultExternalStore = array( 'DB://cluster1', 'DB://cluster2' );
*
+ * @var array
*/
$wgDefaultExternalStore = false;
@@ -1467,6 +1475,8 @@ $wgCacheDirectory = false;
* - CACHE_DBA: Use PHP's DBA extension to store in a DBM-style
* database. This is slow, and is not recommended for
* anything other than debugging.
+ * - (other): A string may be used which identifies a cache
+ * configuration in $wgObjectCaches.
*
* @see $wgMessageCacheType, $wgParserCacheType
*/
@@ -1489,6 +1499,37 @@ $wgMessageCacheType = CACHE_ANYTHING;
$wgParserCacheType = CACHE_ANYTHING;
/**
+ * Advanced object cache configuration.
+ *
+ * Use this to define the class names and constructor parameters which are used
+ * for the various cache types. Custom cache types may be defined here and
+ * referenced from $wgMainCacheType, $wgMessageCacheType or $wgParserCacheType.
+ *
+ * The format is an associative array where the key is a cache identifier, and
+ * the value is an associative array of parameters. The "class" parameter is the
+ * class name which will be used. Alternatively, a "factory" parameter may be
+ * given, giving a callable function which will generate a suitable cache object.
+ *
+ * The other parameters are dependent on the class used.
+ */
+$wgObjectCaches = array(
+ CACHE_NONE => array( 'class' => 'EmptyBagOStuff' ),
+ CACHE_DB => array( 'class' => 'SqlBagOStuff', 'table' => 'objectcache' ),
+ CACHE_DBA => array( 'class' => 'DBABagOStuff' ),
+
+ CACHE_ANYTHING => array( 'factory' => 'ObjectCache::newAnything' ),
+ CACHE_ACCEL => array( 'factory' => 'ObjectCache::newAccelerator' ),
+ CACHE_MEMCACHED => array( 'factory' => 'ObjectCache::newMemcached' ),
+
+ 'eaccelerator' => array( 'class' => 'eAccelBagOStuff' ),
+ 'apc' => array( 'class' => 'APCBagOStuff' ),
+ 'xcache' => array( 'class' => 'XCacheBagOStuff' ),
+ 'wincache' => array( 'class' => 'WinCacheBagOStuff' ),
+ 'memcached-php' => array( 'class' => 'MemcachedPhpBagOStuff' ),
+ 'hash' => array( 'class' => 'HashBagOStuff' ),
+);
+
+/**
* The expiry time for the parser cache, in seconds. The default is 86.4k
* seconds, otherwise known as a day.
*/
@@ -1512,7 +1553,7 @@ $wgSessionsInMemcached = false;
* 'session_mysql.' Setting to null skips setting this entirely (which might be
* useful if you're doing cross-application sessions, see bug 11381)
*/
-$wgSessionHandler = 'files';
+$wgSessionHandler = null;
/** If enabled, will send MemCached debugging information to $wgDebugLogFile */
$wgMemCachedDebug = false;
@@ -1545,6 +1586,13 @@ $wgUseLocalMessageCache = false;
$wgLocalMessageCacheSerialized = true;
/**
+ * Instead of caching everything, keep track which messages are requested and
+ * load only most used messages. This only makes sense if there is lots of
+ * interface messages customised in the wiki (like hundreds in many languages).
+ */
+$wgAdaptiveMessageCache = false;
+
+/**
* Localisation cache configuration. Associative array with keys:
* class: The class to use. May be overridden by extensions.
*
@@ -1589,7 +1637,7 @@ $wgCacheEpoch = '20030516000000';
* to ensure that client-side caches do not keep obsolete copies of global
* styles.
*/
-$wgStyleVersion = '301';
+$wgStyleVersion = '303';
/**
* This will cache static pages for non-logged-in users to reduce
@@ -1673,9 +1721,9 @@ $wgClockSkewFudge = 5;
* 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
+ * 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.
+ * unnecessary cache invalidations.
*/
$wgInvalidateCacheOnLocalSettingsChange = true;
@@ -1706,13 +1754,22 @@ $wgUseESI = false;
/** Send X-Vary-Options header for better caching (requires patched Squid) */
$wgUseXVO = false;
+/** Add X-Forwarded-Proto to the Vary and X-Vary-Options headers for API
+ * requests and RSS/Atom feeds. Use this if you have an SSL termination setup
+ * and need to split the cache between HTTP and HTTPS for API requests,
+ * feed requests and HTTP redirect responses in order to prevent cache
+ * pollution. This does not affect 'normal' requests to index.php other than
+ * HTTP redirects.
+ */
+$wgVaryOnXFP = false;
+
/**
* Internal server name as known to Squid, if different. Example:
* <code>
* $wgInternalServer = 'http://yourinternal.tld:8000';
* </code>
*/
-$wgInternalServer = $wgServer;
+$wgInternalServer = false;
/**
* Cache timeout for the squid, will be sent as s-maxage (without ESI) or
@@ -1807,20 +1864,15 @@ $wgDummyLanguageCodes = array(
'als',
'bat-smg',
'be-x-old',
- 'dk',
'fiu-vro',
'iu',
'nb',
'qqq',
+ 'qqx',
+ 'roa-rup',
'simple',
- 'tp',
);
-/** @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.
@@ -2060,17 +2112,7 @@ $wgLocaltimezone = null;
* 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).
+ * By default, this will be set to match $wgLocaltimezone.
*/
$wgLocalTZoffset = null;
@@ -2084,28 +2126,49 @@ $wgLocalTZoffset = null;
/** The default Content-Type header. */
$wgMimeType = 'text/html';
-/** The content type used in script tags. */
+/**
+ * The content type used in script tags. This is mostly going to be ignored if
+ * $wgHtml5 is true, at least for actual HTML output, since HTML5 doesn't
+ * require a MIME type for JavaScript or CSS (those are the default script and
+ * style languages).
+ */
$wgJsMimeType = 'text/javascript';
-/** The HTML document type. */
+/**
+ * The HTML document type. Ignored if $wgHtml5 is true, since <!DOCTYPE html>
+ * doesn't actually have a doctype part to put this variable's contents in.
+ */
$wgDocType = '-//W3C//DTD XHTML 1.0 Transitional//EN';
-/** The URL of the document type declaration. */
+/**
+ * The URL of the document type declaration. Ignored if $wgHtml5 is true,
+ * since HTML5 has no DTD, and <!DOCTYPE html> doesn't actually have a DTD part
+ * to put this variable's contents in.
+ */
$wgDTD = 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd';
-/** The default xmlns attribute. */
+/**
+ * The default xmlns attribute. Ignored if $wgHtml5 is true (or it's supposed
+ * to be), since we don't currently support XHTML5, and in HTML5 (i.e., served
+ * as text/html) the attribute has no effect, so why bother?
+ */
$wgXhtmlDefaultNamespace = 'http://www.w3.org/1999/xhtml';
/**
* 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.
+ * to always true. If it's true, a number of other settings will be irrelevant
+ * and have no effect.
*/
$wgHtml5 = true;
/**
* Defines the value of the version attribute in the &lt;html&gt; tag, if any.
- * Will be initialized later if not set explicitly.
+ * This is ignored if $wgHtml5 is false. If $wgAllowRdfaAttributes and
+ * $wgHtml5 are both true, and this evaluates to boolean false (like if it's
+ * left at the default null value), it will be auto-initialized to the correct
+ * value for RDFa+HTML5. As such, you should have no reason to ever actually
+ * set this to anything.
*/
$wgHtml5Version = null;
@@ -2145,6 +2208,9 @@ $wgWellFormedXml = true;
* $wgXhtmlNamespaces['svg'] = 'http://www.w3.org/2000/svg';
* Normally we wouldn't have to define this in the root <html>
* element, but IE needs it there in some circumstances.
+ *
+ * This is ignored if $wgHtml5 is true, for the same reason as
+ * $wgXhtmlDefaultNamespace.
*/
$wgXhtmlNamespaces = array();
@@ -2188,12 +2254,6 @@ $wgValidateAllHtml = false;
$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;
-
-/**
* 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
@@ -2269,7 +2329,7 @@ $wgEnableTooltipsAndAccesskeys = true;
$wgBreakFrames = false;
/**
- * The X-Frame-Options header to send on pages sensitive to clickjacking
+ * 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:
*
@@ -2279,9 +2339,9 @@ $wgBreakFrames = false;
* 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
+ * - 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,
@@ -2310,17 +2370,17 @@ $wgExperimentalHtmlIds = false;
* 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
+ * 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
+ * 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
@@ -2346,6 +2406,13 @@ $wgFooterIcons = array(
);
/**
+ * Login / create account link behavior when it's possible for anonymous users to create an account
+ * true = use a combined login / create account link
+ * false = split login and create account into two separate links
+ */
+$wgUseCombinedLoginLink = true;
+
+/**
* Search form behavior for Vector skin only
* true = use an icon search button
* false = use Go & Search buttons
@@ -2370,9 +2437,12 @@ $wgVectorShowVariantName = false;
$wgEdititis = false;
/**
- * Experimental better directionality support.
+ * Better directionality support (bug 6100 and related).
+ * Removed in 1.18, still kept here for LiquidThreads backwards compatibility.
+ *
+ * @deprecated since 1.18
*/
-$wgBetterDirectionality = false;
+$wgBetterDirectionality = true;
/** @} */ # End of output format settings }
@@ -2397,6 +2467,12 @@ $wgBetterDirectionality = false;
*/
$wgResourceModules = array();
+/*
+ * Default 'remoteBasePath' value for resource loader modules.
+ * If not set, then $wgScriptPath will be used as a fallback.
+ */
+$wgResourceBasePath = null;
+
/**
* Maximum time in seconds to cache resources served by the resource loader
*/
@@ -2453,6 +2529,19 @@ $wgResourceLoaderMinifierMaxLineLength = 1000;
$wgIncludeLegacyJavaScript = true;
/**
+ * Whether or not to assing configuration variables to the global window object.
+ * If this is set to false, old code using deprecated variables like:
+ * " if ( window.wgRestrictionEdit ) ..."
+ * or:
+ * " if ( wgIsArticle ) ..."
+ * will no longer work and needs to use mw.config instead. For example:
+ * " if ( mw.config.exists('wgRestrictionEdit') )"
+ * or
+ * " if ( mw.config.get('wgIsArticle') )".
+ */
+$wgLegacyJavaScriptGlobals = 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,
@@ -2465,6 +2554,25 @@ $wgIncludeLegacyJavaScript = true;
*/
$wgResourceLoaderMaxQueryLength = -1;
+/**
+ * If set to true, JavaScript modules loaded from wiki pages will be parsed prior
+ * to minification to validate it.
+ *
+ * Parse errors will result in a JS exception being thrown during module load,
+ * which avoids breaking other modules loaded in the same request.
+ */
+$wgResourceLoaderValidateJS = true;
+
+/**
+ * If set to true, statically-sourced (file-backed) JavaScript resources will
+ * be parsed for validity before being bundled up into ResourceLoader modules.
+ *
+ * This can be helpful for development by providing better error messages in
+ * default (non-debug) mode, but JavaScript parsing is slow and memory hungry
+ * and may fail on large pre-bundled frameworks.
+ */
+$wgResourceLoaderValidateStaticJS = false;
+
/** @} */ # End of resource loader settings }
@@ -2511,6 +2619,14 @@ $wgMetaNamespaceTalk = false;
$wgExtraNamespaces = array();
/**
+ * Same as above, but for namespaces with gender distinction.
+ * Note: the default form for the namespace should also be set
+ * using $wgExtraNamespaces for the same index.
+ * @since 1.18
+ */
+$wgExtraGenderNamespaces = 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
@@ -2720,6 +2836,7 @@ $wgUrlProtocols = array(
'https://',
'ftp://',
'irc://',
+ 'ircs://', // @bug 28503
'gopher://',
'telnet://', // Well if we're going to support the above.. -ævar
'nntp://', // @bug 3808 RFC 1738
@@ -2729,6 +2846,7 @@ $wgUrlProtocols = array(
'svn://',
'git://',
'mms://',
+ '//', // for protocol-relative URLs
);
/**
@@ -2781,8 +2899,9 @@ $wgAllowImageTag = false;
* - $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.
+ * - $wgTidyInternal controls the use of the PECL extension or the
+ * libtidy (PHP >= 5) 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.
@@ -2862,6 +2981,7 @@ $wgExpensiveParserFunctionLimit = 100;
/**
* Preprocessor caching threshold
+ * Setting it to 'false' will disable the preprocessor cache.
*/
$wgPreprocessorCacheThreshold = 1000;
@@ -2883,14 +3003,30 @@ $wgTranscludeCacheExpiry = 3600;
*/
/**
- * 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
+ * Method used to determine if a page in a content namespace should be counted
+ * as a valid article.
+ *
+ * Redirect pages will never be counted as valid articles.
*
- * Retroactively changing this variable will not affect
- * the existing count (cf. maintenance/recount.sql).
+ * This variable can have the following values:
+ * - 'any': all pages as considered as valid articles
+ * - 'comma': the page must contain a comma to be considered valid
+ * - 'link': the page must contain a [[wiki link]] to be considered valid
+ * - null: the value will be set at run time depending on $wgUseCommaCount:
+ * if $wgUseCommaCount is false, it will be 'link', if it is true
+ * it will be 'comma'
+ *
+ * See also See http://www.mediawiki.org/wiki/Manual:Article_count
+ *
+ * Retroactively changing this variable will not affect the existing count,
+ * to update it, you will need to run the maintenance/updateArticleCount.php
+ * script.
+ */
+$wgArticleCountMethod = null;
+
+/**
+ * Backward compatibility setting, will set $wgArticleCountMethod if it is null.
+ * @deprecated since 1.18; use $wgArticleCountMethod instead
*/
$wgUseCommaCount = false;
@@ -2928,6 +3064,17 @@ $wgPasswordSalt = true;
$wgMinimalPasswordLength = 1;
/**
+ * Whether to allow password resets ("enter some identifying data, and we'll send an email
+ * with a temporary password you can use to get back into the account") identified by
+ * various bits of data. Setting all of these to false (or the whole variable to false)
+ * has the effect of disabling password resets entirely
+ */
+$wgPasswordResetRoutes = array(
+ 'username' => true,
+ 'email' => false,
+);
+
+/**
* Maximum number of Unicode characters in signature
*/
$wgMaxSigChars = 255;
@@ -2962,8 +3109,6 @@ $wgReservedUsernames = array(
$wgDefaultUserOptions = array(
'ccmeonemails' => 0,
'cols' => 80,
- 'contextchars' => 50,
- 'contextlines' => 5,
'date' => 'default',
'diffonly' => 0,
'disablemail' => 0,
@@ -2996,7 +3141,7 @@ $wgDefaultUserOptions = array(
'numberheadings' => 0,
'previewonfirst' => 0,
'previewontop' => 1,
- 'quickbar' => 1,
+ 'quickbar' => 5,
'rcdays' => 7,
'rclimit' => 50,
'rememberpassword' => 0,
@@ -3029,7 +3174,7 @@ $wgDefaultUserOptions = array(
/**
* Whether or not to allow and use real name fields.
- * @deprecated in 1.16, use $wgHiddenPrefs[] = 'realname' below to disable real
+ * @deprecated since 1.16, use $wgHiddenPrefs[] = 'realname' below to disable real
* names
*/
$wgAllowRealName = true;
@@ -3128,18 +3273,6 @@ $wgSecureLogin = false;
*/
/**
- * 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;
@@ -3180,7 +3313,7 @@ $wgBlockDisablesLogin = false;
* $wgWhitelistRead = array ( "Main Page", "Wikipedia:Help");
* </code>
*
- * Special:Userlogin and Special:Resetpass are always whitelisted.
+ * Special:Userlogin and Special:ChangePassword are always whitelisted.
*
* NOTE: This will only work if $wgGroupPermissions['*']['read'] is false --
* see below. Otherwise, ALL pages are accessible, regardless of this setting.
@@ -3278,7 +3411,6 @@ $wgGroupPermissions['sysop']['autopatrol'] = true;
$wgGroupPermissions['sysop']['protect'] = true;
$wgGroupPermissions['sysop']['proxyunbannable'] = true;
$wgGroupPermissions['sysop']['rollback'] = true;
-$wgGroupPermissions['sysop']['trackback'] = true;
$wgGroupPermissions['sysop']['upload'] = true;
$wgGroupPermissions['sysop']['reupload'] = true;
$wgGroupPermissions['sysop']['reupload-shared'] = true;
@@ -3295,6 +3427,7 @@ $wgGroupPermissions['sysop']['movefile'] = true;
$wgGroupPermissions['sysop']['unblockself'] = true;
$wgGroupPermissions['sysop']['suppressredirect'] = true;
#$wgGroupPermissions['sysop']['mergehistory'] = true;
+#$wgGroupPermissions['sysop']['trackback'] = true;
// Permission to change users' group assignments
$wgGroupPermissions['bureaucrat']['userrights'] = true;
@@ -3364,7 +3497,7 @@ $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
+ * Title::getRestrictionTypes() will remove restrictions that are not
* applicable to a specific title (create and upload)
*/
$wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' );
@@ -3429,7 +3562,7 @@ $wgAutoConfirmCount = 0;
/**
* Automatically add a usergroup to any user who matches certain conditions.
* The format is
- * array( '&' or '|' or '^', cond1, cond2, ... )
+ * array( '&' or '|' or '^' or '!', cond1, cond2, ... )
* where cond1, cond2, ... are themselves conditions; *OR*
* APCOND_EMAILCONFIRMED, *OR*
* array( APCOND_EMAILCONFIRMED ), *OR*
@@ -3440,6 +3573,7 @@ $wgAutoConfirmCount = 0;
* array( APCOND_IPINRANGE, range ), *OR*
* array( APCOND_AGE_FROM_EDIT, seconds since first edit ), *OR*
* array( APCOND_BLOCKED ), *OR*
+ * array( APCOND_ISBOT ), *OR*
* similar constructs defined by extensions.
*
* If $wgEmailAuthentication is off, APCOND_EMAILCONFIRMED will be true for any
@@ -3453,6 +3587,31 @@ $wgAutopromote = array(
);
/**
+ * Automatically add a usergroup to any user who matches certain conditions.
+ * Does not add the user to the group again if it has been removed.
+ * Also, does not remove the group if the user no longer meets the criteria.
+ *
+ * The format is
+ * array( event => criteria, ... )
+ * where event is
+ * 'onEdit' (when user edits) or 'onView' (when user views the wiki)
+ * and criteria has the same format as $wgAutopromote
+ *
+ * @see $wgAutopromote
+ * @since 1.18
+ */
+$wgAutopromoteOnce = array(
+ 'onEdit' => array(),
+ 'onView' => array()
+);
+
+/*
+ * Put user rights log entries for autopromotion in recent changes?
+ * @since 1.18
+ */
+$wgAutopromoteOnceLogInRC = true;
+
+/**
* $wgAddGroups and $wgRemoveGroups can be used to give finer control over who
* can assign which groups at Special:Userrights. Example configuration:
*
@@ -3512,7 +3671,7 @@ $wgSummarySpamRegex = array();
* - true : block it
* - false : let it through
*
- * @deprecated Use hooks. See SpamBlacklist extension.
+ * @deprecated since 1.17 Use hooks. See SpamBlacklist extension.
*/
$wgFilterCallback = false;
@@ -3523,7 +3682,7 @@ $wgFilterCallback = false;
$wgEnableDnsBlacklist = false;
/**
- * @deprecated Use $wgEnableDnsBlacklist instead, only kept for backward
+ * @deprecated since 1.17 Use $wgEnableDnsBlacklist instead, only kept for backward
* compatibility
*/
$wgEnableSorbs = false;
@@ -3535,7 +3694,7 @@ $wgEnableSorbs = false;
$wgDnsBlacklistUrls = array( 'http.dnsbl.sorbs.net.' );
/**
- * @deprecated Use $wgDnsBlacklistUrls instead, only kept for backward
+ * @deprecated since 1.17 Use $wgDnsBlacklistUrls instead, only kept for backward
* compatibility
*/
$wgSorbsUrl = array();
@@ -3583,17 +3742,6 @@ $wgRateLimits = array(
$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.
*/
@@ -3631,7 +3779,7 @@ $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";
+$wgProxyScriptPath = "$IP/maintenance/proxy_check.php";
/** */
$wgProxyMemcExpiry = 86400;
/** This should always be customised in LocalSettings.php */
@@ -3658,13 +3806,34 @@ $wgCookieExpiration = 30*86400;
* or ".any.subdomain.net"
*/
$wgCookieDomain = '';
+
+
+/**
+ * Set this variable if you want to restrict cookies to a certain path within
+ * the domain specified by $wgCookieDomain.
+ */
$wgCookiePath = '/';
-$wgCookieSecure = ($wgProto == 'https');
+
+/**
+ * Whether the "secure" flag should be set on the cookie. This can be:
+ * - true: Set secure flag
+ * - false: Don't set secure flag
+ * - "detect": Set the secure flag if $wgServer is set to an HTTPS URL
+ */
+$wgCookieSecure = 'detect';
+
+/**
+ * By default, MediaWiki checks if the client supports cookies during the
+ * login process, so that it can display an informative error message if
+ * cookies are disabled. Set this to true if you want to disable this cookie
+ * check.
+ */
$wgDisableCookieCheck = false;
/**
- * Set $wgCookiePrefix to use a custom one. Setting to false sets the default of
- * using the database name.
+ * Cookies generated by MediaWiki have names starting with this prefix. Set it
+ * to a string to use a custom prefix. Setting it to false causes the database
+ * name to be used as a prefix.
*/
$wgCookiePrefix = false;
@@ -3672,10 +3841,8 @@ $wgCookiePrefix = false;
* Set authentication cookies to HttpOnly to prevent access by JavaScript,
* in browsers that support this feature. This can mitigates some classes of
* XSS attack.
- *
- * Only supported on PHP 5.2 or higher.
*/
-$wgCookieHttpOnly = version_compare("5.2", PHP_VERSION, "<");
+$wgCookieHttpOnly = true;
/**
* If the requesting browser matches a regex in this blacklist, we won't
@@ -3708,28 +3875,6 @@ $wgSessionName = false;
* Please see math/README for more information.
*/
$wgUseTeX = false;
-/** Location of the texvc binary */
-$wgTexvc = $IP . '/math/texvc';
-/**
- * Texvc background color
- * use LaTeX color format as used in \special function
- * for transparent background use value 'Transparent' for alpha transparency or
- * 'transparent' for binary transparency.
- */
-$wgTexvcBackgroundColor = 'transparent';
-
-/**
- * Normally when generating math images, we double-check that the
- * directories we want to write to exist, and that files that have
- * been generated still exist when we need to bring them up again.
- *
- * This lets us give useful error messages in case of permission
- * problems, and automatically rebuild images that have been lost.
- *
- * On a big site with heavy NFS traffic this can be slow and flaky,
- * so sometimes we want to short-circuit it by setting this to false.
- */
-$wgMathCheckFiles = true;
/* @} */ # end LaTeX }
@@ -3761,7 +3906,7 @@ $wgDebugLogPrefix = '';
$wgDebugRedirects = false;
/**
- * If true, log debugging data from action=raw.
+ * If true, log debugging data from action=raw and load.php.
* This is normally false to avoid overlapping debug entries due to gen=css and
* gen=js requests.
*/
@@ -3885,7 +4030,7 @@ $wgDebugProfiling = false;
/** Output debug message on every wfProfileIn/wfProfileOut */
$wgDebugFunctionEntry = 0;
-/*
+/**
* Destination for wfIncrStats() data...
* 'cache' to go into the system cache, if enabled (memcached)
* 'udp' to be sent to the UDP profiler (see $wgUDPProfilerHost)
@@ -3893,6 +4038,14 @@ $wgDebugFunctionEntry = 0;
*/
$wgStatsMethod = 'cache';
+/**
+ * When $wgStatsMethod is 'udp', setting this to a string allows statistics to
+ * be aggregated over more than one wiki. The string will be used in place of
+ * the DB name in outgoing UDP packets. If this is set to false, the DB name
+ * will be used.
+ */
+$wgAggregateStatsID = false;
+
/** Whereas to count the number of time an article is viewed.
* Does not work if pages are cached (for example with squid).
*/
@@ -3901,6 +4054,8 @@ $wgDisableCounters = false;
/**
* Support blog-style "trackbacks" for articles. See
* http://www.sixapart.com/pronet/docs/trackback_spec for details.
+ *
+ * If enabling this, you also need to grant the 'trackback' right to a group
*/
$wgUseTrackbacks = false;
@@ -3914,8 +4069,8 @@ $wgUseTrackbacks = false;
* Use full paths.
*/
$wgParserTestFiles = array(
- "$IP/maintenance/tests/parser/parserTests.txt",
- "$IP/maintenance/tests/parser/ExtraParserTests.txt"
+ "$IP/tests/parser/parserTests.txt",
+ "$IP/tests/parser/extraParserTests.txt"
);
/**
@@ -3954,11 +4109,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
+$wgSearchHighlightBoundaries = '[\p{Z}\p{P}\p{C}]';
/**
* Set to true to have the search engine count total
@@ -4176,12 +4328,12 @@ $wgReadOnly = null;
$wgReadOnlyFile = false;
/**
- * When you run the web-based upgrade utility, it will tell you what to set
+ * 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,
+ * 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;
@@ -4289,6 +4441,16 @@ $wgFeedDiffCutoff = 32768;
$wgOverrideSiteFeed = array();
/**
+ * Available feeds objects
+ * Should probably only be defined when a page is syndicated ie when
+ * $wgOut->isSyndicated() is true
+ */
+$wgFeedClasses = array(
+ 'rss' => 'RSSFeed',
+ 'atom' => 'AtomFeed',
+);
+
+/**
* Which feed types should we provide by default? This can include 'rss',
* 'atom', neither, or both.
*/
@@ -4337,16 +4499,31 @@ $wgUseTagFilter = true;
* @{
*/
-/** RDF metadata toggles */
-$wgEnableDublinCoreRdf = false;
-$wgEnableCreativeCommonsRdf = false;
-
-/** Override for copyright metadata.
- * TODO: these options need documentation
+/**
+ * Override for copyright metadata.
+ *
+ * This is the name of the page containing information about the wiki's copyright status,
+ * which will be added as a link in the footer if it is specified. It overrides
+ * $wgRightsUrl if both are specified.
*/
$wgRightsPage = null;
+
+/**
+ * Set this to specify an external URL containing details about the content license used on your wiki.
+ * If $wgRightsPage is set then this setting is ignored.
+ */
$wgRightsUrl = null;
+
+/**
+ * If either $wgRightsUrl or $wgRightsPage is specified then this variable gives the text for the link.
+ * If using $wgRightsUrl then this value must be specified. If using $wgRightsPage then the name of the
+ * page will also be used as the link if this variable is not set.
+ */
$wgRightsText = null;
+
+/**
+ * Override for copyright metadata.
+ */
$wgRightsIcon = null;
/**
@@ -4356,17 +4533,13 @@ $wgLicenseTerms = false;
/**
* Set this to some HTML to override the rights icon with an arbitrary logo
- * @deprecated Use $wgFooterIcons['copyright']['copyright']
+ * @deprecated since 1.18 Use $wgFooterIcons['copyright']['copyright']
*/
$wgCopyrightIcon = null;
/** Set this to true if you want detailed copyright information forms on Upload. */
$wgUseCopyrightUpload = false;
-/** Set this to false if you want to disable checking that detailed copyright
- * information values are not empty. */
-$wgCheckCopyrightUpload = true;
-
/**
* Set this to the number of authors that you want to be credited below an
* article text. Set it to zero to hide the attribution block, and a negative
@@ -4456,12 +4629,6 @@ $wgExportFromNamespaces = false;
$wgExtensionFunctions = array();
/**
- * Extension functions for initialisation of skins. This is called somewhat earlier
- * than $wgExtensionFunctions.
- */
-$wgSkinExtensionFunctions = array();
-
-/**
* Extension messages files.
*
* Associative array mapping extension name to the filename where messages can be
@@ -4480,7 +4647,7 @@ $wgExtensionMessagesFiles = array();
/**
* Aliases for special pages provided by extensions.
- * @deprecated Use $specialPageAliases in a file referred to by $wgExtensionMessagesFiles
+ * @deprecated since 1.16 Use $specialPageAliases in a file referred to by $wgExtensionMessagesFiles
*/
$wgExtensionAliasesFiles = array();
@@ -4500,7 +4667,10 @@ $wgParserOutputHooks = array();
/**
* List of valid skin names.
- * The key should be the name in all lower case, the value should be a display name.
+ * The key should be the name in all lower case, the value should be a properly
+ * cased name for the skin. This value will be prefixed with "Skin" to create the
+ * class name of the skin to load, and if the skin's class cannot be found through
+ * the autoloader it will be used to load a .php file by that name in the skins directory.
* The default skins will be added later, by Skin::getSkinNames(). Use
* Skin::getSkinNames() as an accessor if you wish to have access to the full list.
*/
@@ -4542,6 +4712,7 @@ $wgExtensionCredits = array();
/**
* Authentication plugin.
+ * @var AuthPlugin
*/
$wgAuth = null;
@@ -4572,6 +4743,17 @@ $wgJobClasses = array(
);
/**
+ * Jobs that must be explicitly requested, i.e. aren't run by job runners unless special flags are set.
+ *
+ * These can be:
+ * - Very long-running jobs.
+ * - Jobs that you would never want to run as part of a page rendering request.
+ * - Jobs that you want to run on specialized machines ( like transcoding, or a particular
+ * machine on your cluster has 'outside' web access you could restrict uploadFromUrl )
+ */
+$wgJobTypesExcludedFromDefaultQueue = array();
+
+/**
* Additional functions to be performed with updateSpecialPages.
* Expensive Querypages are already updated.
*/
@@ -4624,24 +4806,29 @@ $wgCategoryMagicGallery = true;
$wgCategoryPagingLimit = 200;
/**
- * Specify how category names should be sorted, when listed on a category page.
+ * Specify how category names should be sorted, when listed on a category page.
* A sorting scheme is also known as a collation.
*
* Available values are:
*
* - uppercase: Converts the category name to upper case, and sorts by that.
*
- * - uca-default: Provides access to the Unicode Collation Algorithm with
+ * - identity: Does no conversion. Sorts by binary value of the string.
+ *
+ * - 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".
*
- * 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
+ * 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.
*
* After you change this, you must run maintenance/updateCollation.php to fix
- * the sort keys in the database.
+ * the sort keys in the database.
+ *
+ * Extensions can define there own collations by subclassing Collation
+ * and using the Collation::factory hook.
*/
$wgCategoryCollation = 'uppercase';
@@ -4753,34 +4940,34 @@ $wgLogHeaders = array(
* Extensions with custom log types may add to this array.
*/
$wgLogActions = array(
- 'block/block' => 'blocklogentry',
- 'block/unblock' => 'unblocklogentry',
- 'block/reblock' => 'reblock-logentry',
- 'protect/protect' => 'protectedarticle',
- 'protect/modify' => 'modifiedarticleprotection',
- 'protect/unprotect' => 'unprotectedarticle',
- 'protect/move_prot' => 'movedarticleprotection',
- 'rights/rights' => 'rightslogentry',
- 'rights/disable' => 'disableaccount-logentry',
- 'delete/delete' => 'deletedarticle',
- 'delete/restore' => 'undeletedarticle',
- 'delete/revision' => 'revdelete-logentry',
- 'delete/event' => 'logdelete-logentry',
- 'upload/upload' => 'uploadedimage',
- 'upload/overwrite' => 'overwroteimage',
- 'upload/revert' => 'uploadedimage',
- 'move/move' => '1movedto2',
- 'move/move_redir' => '1movedto2_redir',
- 'import/upload' => 'import-logentry-upload',
- 'import/interwiki' => 'import-logentry-interwiki',
- 'merge/merge' => 'pagemerge-logentry',
- 'suppress/revision' => 'revdelete-logentry',
- 'suppress/file' => 'revdelete-logentry',
- 'suppress/event' => 'logdelete-logentry',
- 'suppress/delete' => 'suppressedarticle',
- 'suppress/block' => 'blocklogentry',
- 'suppress/reblock' => 'reblock-logentry',
- 'patrol/patrol' => 'patrol-log-line',
+ 'block/block' => 'blocklogentry',
+ 'block/unblock' => 'unblocklogentry',
+ 'block/reblock' => 'reblock-logentry',
+ 'protect/protect' => 'protectedarticle',
+ 'protect/modify' => 'modifiedarticleprotection',
+ 'protect/unprotect' => 'unprotectedarticle',
+ 'protect/move_prot' => 'movedarticleprotection',
+ 'rights/rights' => 'rightslogentry',
+ 'rights/autopromote' => 'rightslogentry-autopromote',
+ 'delete/delete' => 'deletedarticle',
+ 'delete/restore' => 'undeletedarticle',
+ 'delete/revision' => 'revdelete-logentry',
+ 'delete/event' => 'logdelete-logentry',
+ 'upload/upload' => 'uploadedimage',
+ 'upload/overwrite' => 'overwroteimage',
+ 'upload/revert' => 'uploadedimage',
+ 'move/move' => '1movedto2',
+ 'move/move_redir' => '1movedto2_redir',
+ 'import/upload' => 'import-logentry-upload',
+ 'import/interwiki' => 'import-logentry-interwiki',
+ 'merge/merge' => 'pagemerge-logentry',
+ 'suppress/revision' => 'revdelete-logentry',
+ 'suppress/file' => 'revdelete-logentry',
+ 'suppress/event' => 'logdelete-logentry',
+ 'suppress/delete' => 'suppressedarticle',
+ 'suppress/block' => 'blocklogentry',
+ 'suppress/reblock' => 'reblock-logentry',
+ 'patrol/patrol' => 'patrol-log-line',
);
/**
@@ -4795,11 +4982,6 @@ $wgLogActionsHandlers = array();
*/
$wgNewUserLog = true;
-/**
- * Log the automatic creations of new users accounts?
- */
-$wgLogAutocreatedAccounts = false;
-
/** @} */ # end logging }
/*************************************************************************//**
@@ -4868,16 +5050,18 @@ $wgSpecialPageGroups = array(
'Listusers' => 'users',
'Activeusers' => 'users',
'Listgrouprights' => 'users',
- 'Ipblocklist' => 'users',
+ 'BlockList' => 'users',
'Contributions' => 'users',
'Emailuser' => 'users',
'Listadmins' => 'users',
'Listbots' => 'users',
'Userrights' => 'users',
- 'Blockip' => 'users',
+ 'Block' => 'users',
+ 'Unblock' => 'users',
'Preferences' => 'users',
- 'Resetpass' => 'users',
+ 'ChangePassword' => 'users',
'DeletedContributions' => 'users',
+ 'PasswordReset' => 'users',
'Mostlinked' => 'highuse',
'Mostlinkedcategories' => 'highuse',
@@ -4926,12 +5110,6 @@ $wgSpecialPageGroups = array(
$wgSortSpecialPages = true;
/**
- * Filter for Special:Randompage. Part of a WHERE clause
- * @deprecated as of 1.16, use the SpecialRandomGetRandomTitle hook
- */
-$wgExtraRandompageSQL = false;
-
-/**
* On Special:Unusedimages, consider images "used", if they are put
* into a category. Default (false) is not to count those as used.
*/
@@ -4946,6 +5124,47 @@ $wgMaxRedirectLinksRetrieved = 500;
/** @} */ # end special pages }
/*************************************************************************//**
+ * @name Actions
+ * @{
+ */
+
+/**
+ * Array of allowed values for the title=foo&action=<action> parameter. Syntax is:
+ * 'foo' => 'ClassName' Load the specified class which subclasses Action
+ * 'foo' => true Load the class FooAction which subclasses Action
+ * If something is specified in the getActionOverrides()
+ * of the relevant Page object it will be used
+ * instead of the default class.
+ * 'foo' => false The action is disabled; show an error message
+ * Unsetting core actions will probably cause things to complain loudly.
+ */
+$wgActions = array(
+ 'credits' => true,
+ 'deletetrackback' => true,
+ 'info' => true,
+ 'markpatrolled' => true,
+ 'purge' => true,
+ 'revert' => true,
+ 'revisiondelete' => true,
+ 'rollback' => true,
+ 'unwatch' => true,
+ 'watch' => true,
+);
+
+/**
+ * Array of disabled article actions, e.g. view, edit, delete, etc.
+ * @deprecated since 1.18; just set $wgActions['action'] = false instead
+ */
+$wgDisabledActions = array();
+
+/**
+ * Allow the "info" action, very inefficient at the moment
+ */
+$wgAllowPageInfo = false;
+
+/** @} */ # end actions }
+
+/*************************************************************************//**
* @name Robot (search engine crawler) policy
* See also $wgNoFollowLinks.
* @{
@@ -5060,14 +5279,7 @@ $wgAPIMaxUncachedDiffs = 1;
$wgAPIRequestLog = false;
/**
- * Cache the API help text for up to an hour. Disable this during API
- * debugging and development
- */
-$wgAPICacheHelp = true;
-
-/**
- * Set the timeout for the API help text cache. Ignored if $wgAPICacheHelp
- * is false.
+ * Set the timeout for the API help text cache. If set to 0, caching disabled
*/
$wgAPICacheHelpTimeout = 60*60;
@@ -5080,12 +5292,11 @@ $wgUseAjax = true;
* List of Ajax-callable functions.
* Extensions acting as Ajax callbacks must register here
*/
-$wgAjaxExportList = array( 'wfAjaxGetFileUrl' );
+$wgAjaxExportList = array();
/**
* Enable watching/unwatching pages using AJAX.
* Requires $wgUseAjax to be true too.
- * Causes wfAjaxWatch to be added to $wgAjaxExportList
*/
$wgAjaxWatch = true;
@@ -5214,20 +5425,62 @@ $wgUpdateRowsPerQuery = 100;
/** @} */ # End job queue }
/************************************************************************//**
- * @name Miscellaneous
+ * @name HipHop compilation
* @{
*/
-/** Allow the "info" action, very inefficient at the moment */
-$wgAllowPageInfo = false;
+/**
+ * The build directory for HipHop compilation.
+ * Defaults to $IP/maintenance/hiphop/build.
+ */
+$wgHipHopBuildDirectory = false;
-/** Name of the external diff engine to use */
-$wgExternalDiffEngine = false;
+/**
+ * The HipHop build type. Can be either "Debug" or "Release".
+ */
+$wgHipHopBuildType = 'Debug';
/**
- * Array of disabled article actions, e.g. view, edit, dublincore, delete, etc.
+ * Number of parallel processes to use during HipHop compilation, or "detect"
+ * to guess from system properties.
*/
-$wgDisabledActions = array();
+$wgHipHopCompilerProcs = 'detect';
+
+/**
+ * Filesystem extensions directory. Defaults to $IP/../extensions.
+ *
+ * To compile extensions with HipHop, set $wgExtensionsDirectory correctly,
+ * and use code like:
+ *
+ * require( MWInit::extensionSetupPath( 'Extension/Extension.php' ) );
+ *
+ * to include the extension setup file from LocalSettings.php. It is not
+ * necessary to set this variable unless you use MWInit::extensionSetupPath().
+ */
+$wgExtensionsDirectory = false;
+
+/**
+ * A list of files that should be compiled into a HipHop build, in addition to
+ * those listed in $wgAutoloadClasses. Add to this array in an extension setup
+ * file in order to add files to the build.
+ *
+ * The files listed here must either be either absolute paths under $IP or
+ * under $wgExtensionsDirectory, or paths relative to the virtual source root
+ * "$IP/..", i.e. starting with "phase3" for core files, and "extensions" for
+ * extension files.
+ */
+$wgCompiledFiles = array();
+
+/** @} */ # End of HipHop compilation }
+
+
+/************************************************************************//**
+ * @name Miscellaneous
+ * @{
+ */
+
+/** Name of the external diff engine to use */
+$wgExternalDiffEngine = false;
/**
* Disable redirects to special pages and interwiki redirects, which use a 302
@@ -5296,6 +5549,8 @@ $wgUploadMaintenance = false;
$wgEnableSelenium = false;
$wgSeleniumTestConfigs = array();
$wgSeleniumConfigFile = null;
+$wgDBtestuser = ''; //db user that has permission to create and drop the test databases only
+$wgDBtestpassword = '';
/**
* For really cool vim folding this needs to be at the end:
diff --git a/includes/Defines.php b/includes/Defines.php
index 64197d9c..ff7d7980 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -1,6 +1,11 @@
<?php
/**
- * A few constants that might be needed during LocalSettings.php
+ * A few constants that might be needed during LocalSettings.php.
+ *
+ * Note: these constants must all be resolvable at compile time by HipHop,
+ * since this file will not be executed during request startup for a compiled
+ * MediaWiki.
+ *
* @file
*/
@@ -80,27 +85,6 @@ define( 'NS_IMAGE', NS_FILE );
define( 'NS_IMAGE_TALK', NS_FILE_TALK );
/**@}*/
-/**
- * Available feeds objects
- * Should probably only be defined when a page is syndicated ie when
- * $wgOut->isSyndicated() is true
- */
-$wgFeedClasses = array(
- 'rss' => 'RSSFeed',
- 'atom' => 'AtomFeed',
-);
-
-/**@{
- * Maths constants
- */
-define( 'MW_MATH_PNG', 0 );
-define( 'MW_MATH_SIMPLE', 1 );
-define( 'MW_MATH_HTML', 2 );
-define( 'MW_MATH_SOURCE', 3 );
-define( 'MW_MATH_MODERN', 4 );
-define( 'MW_MATH_MATHML', 5 );
-/**@}*/
-
/**@{
* Cache type
*/
@@ -114,7 +98,7 @@ define( 'CACHE_DBA', 4 ); // Use PHP's DBA extension to store in a DBM-st
/**@{
* Media types.
- * This defines constants for the value returned by Image::getMediaType()
+ * This defines constants for the value returned by File::getMediaType()
*/
define( 'MEDIATYPE_UNKNOWN', 'UNKNOWN' ); // unknown format
define( 'MEDIATYPE_BITMAP', 'BITMAP' ); // some bitmap image or image source (like psd, etc). Can't scale up.
@@ -254,4 +238,15 @@ define( 'APCOND_ISIP', 5 );
define( 'APCOND_IPINRANGE', 6 );
define( 'APCOND_AGE_FROM_EDIT', 7 );
define( 'APCOND_BLOCKED', 8 );
+define( 'APCOND_ISBOT', 9 );
/**@}*/
+
+/**
+ * Protocol constants for wfExpandUrl()
+ */
+define( 'PROTO_HTTP', 'http://' );
+define( 'PROTO_HTTPS', 'https://' );
+define( 'PROTO_RELATIVE', '//' );
+define( 'PROTO_CURRENT', null );
+define( 'PROTO_CANONICAL', 1 );
+define( 'PROTO_INTERNAL', 2 );
diff --git a/includes/DjVuImage.php b/includes/DjVuImage.php
index cccb070a..80b7408c 100644
--- a/includes/DjVuImage.php
+++ b/includes/DjVuImage.php
@@ -72,7 +72,7 @@ class DjVuImage {
function dump() {
$file = fopen( $this->mFilename, 'rb' );
$header = fread( $file, 12 );
- // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack( 'a4magic/a4chunk/NchunkLength', $header ) );
echo "$chunk $chunkLength\n";
$this->dumpForm( $file, $chunkLength, 1 );
@@ -88,7 +88,7 @@ class DjVuImage {
if( $chunkHeader == '' ) {
break;
}
- // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack( 'a4chunk/NchunkLength', $chunkHeader ) );
echo str_repeat( ' ', $indent * 4 ) . "$chunk $chunkLength\n";
@@ -119,7 +119,7 @@ class DjVuImage {
if( strlen( $header ) < 16 ) {
wfDebug( __METHOD__ . ": too short file header\n" );
} else {
- // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack( 'a4magic/a4form/NformLength/a4subtype', $header ) );
if( $magic != 'AT&T' ) {
@@ -143,7 +143,7 @@ class DjVuImage {
if( strlen( $header ) < 8 ) {
return array( false, 0 );
} else {
- // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack( 'a4chunk/Nlength', $header ) );
return array( $chunk, $length );
}
@@ -202,7 +202,7 @@ class DjVuImage {
return false;
}
- // FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
extract( unpack(
'nwidth/' .
'nheight/' .
@@ -269,7 +269,7 @@ class DjVuImage {
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 = preg_replace( "/<DjVuXML>/", "<mw-djvu><DjVuXML>", $xml, 1 );
$xml = $xml . $txt. '</mw-djvu>' ;
}
}
diff --git a/includes/EditPage.php b/includes/EditPage.php
index 3e85ad10..e6e7111d 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -11,7 +11,7 @@
* interfaces.
*
* EditPage cares about two distinct titles:
- * $wgTitle is the page that forms submit to, links point to,
+ * $this->mContextTitle is the page that forms submit to, links point to,
* redirects go to, etc. $this->mTitle (as well as $mArticle) is the
* page in the database that is actually being edited. These are
* usually the same, but they are now allowed to be different.
@@ -42,20 +42,31 @@ class EditPage {
const AS_IMAGE_REDIRECT_ANON = 233;
const AS_IMAGE_REDIRECT_LOGGED = 234;
+ /**
+ * @var Article
+ */
var $mArticle;
+
+ /**
+ * @var Title
+ */
var $mTitle;
+ private $mContextTitle = null;
var $action;
var $isConflict = false;
var $isCssJsSubpage = false;
var $isCssSubpage = false;
var $isJsSubpage = false;
- var $deletedSinceEdit = false;
+ var $isWrongCaseCssJsPage = false;
+ var $isNew = false; // new page or new section
+ var $deletedSinceEdit;
var $formtype;
var $firsttime;
var $lastDelete;
var $mTokenOk = false;
var $mTokenOkExceptSuffix = false;
var $mTriedSave = false;
+ var $incompleteForm = false;
var $tooBig = false;
var $kblength = false;
var $missingComment = false;
@@ -64,7 +75,12 @@ class EditPage {
var $autoSumm = '';
var $hookError = '';
#var $mPreviewTemplates;
+
+ /**
+ * @var ParserOutput
+ */
var $mParserOutput;
+
var $mBaseRevision = false;
var $mShowSummaryField = true;
@@ -85,6 +101,7 @@ class EditPage {
public $editFormTextBottom;
public $editFormTextAfterContent;
public $previewTextAfterContent;
+ public $mPreloadText;
/* $didSave should be set to true whenever an article was succesfully altered. */
public $didSave = false;
@@ -94,7 +111,7 @@ class EditPage {
/**
* @todo document
- * @param $article
+ * @param $article Article
*/
function __construct( $article ) {
$this->mArticle =& $article;
@@ -113,18 +130,47 @@ class EditPage {
$this->mPreloadText = "";
}
+ /**
+ * @return Article
+ */
function getArticle() {
return $this->mArticle;
}
+ /**
+ * Set the context Title object
+ *
+ * @param $title Title object or null
+ */
+ public function setContextTitle( $title ) {
+ $this->mContextTitle = $title;
+ }
+
+ /**
+ * Get the context title object.
+ * If not set, $wgTitle will be returned. This behavior might changed in
+ * the future to return $this->mTitle instead.
+ *
+ * @return Title object
+ */
+ public function getContextTitle() {
+ if ( is_null( $this->mContextTitle ) ) {
+ global $wgTitle;
+ return $wgTitle;
+ } else {
+ return $this->mContextTitle;
+ }
+ }
/**
* Fetch initial editing page content.
+ *
+ * @param $def_text string
* @returns mixed string on success, $def_text for invalid sections
* @private
*/
function getContent( $def_text = '' ) {
- global $wgOut, $wgRequest, $wgParser, $wgContLang, $wgMessageCache;
+ global $wgOut, $wgRequest, $wgParser;
wfProfileIn( __METHOD__ );
# Get variables from query string :P
@@ -141,10 +187,10 @@ class EditPage {
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() ) );
- $text = wfMsgGetKey( $message, false, $lang, false );
- if( wfEmptyMsg( $message, $text ) )
+ $text = $this->mTitle->getDefaultMessageText();
+ if( $text === false ) {
$text = $this->getPreloadedText( $preload );
+ }
} else {
# If requested, preload some text.
$text = $this->getPreloadedText( $preload );
@@ -198,7 +244,7 @@ class EditPage {
// was created, or we may simply have got bogus input.
$this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-norev">' . wfMsgNoTrans( 'undo-norev' ) . '</div>' );
}
- } else if ( $section != '' ) {
+ } elseif ( $section != '' ) {
if ( $section == 'new' ) {
$text = $this->getPreloadedText( $preload );
} else {
@@ -212,7 +258,11 @@ class EditPage {
return $text;
}
- /** Use this method before edit() to preload some text into the edit box */
+ /**
+ * Use this method before edit() to preload some text into the edit box
+ *
+ * @param $text string
+ */
public function setPreloadedText( $text ) {
$this->mPreloadText = $text;
}
@@ -253,15 +303,19 @@ class EditPage {
return '';
}
- /*
+ /**
* Check if a page was deleted while the user was editing it, before submit.
* Note that we rely on the logging table, which hasn't been always there,
* but that doesn't matter, because this only applies to brand new
* deletes.
*/
protected function wasDeletedSinceLastEdit() {
- if ( $this->deletedSinceEdit )
- return true;
+ if ( $this->deletedSinceEdit !== null ) {
+ return $this->deletedSinceEdit;
+ }
+
+ $this->deletedSinceEdit = false;
+
if ( $this->mTitle->isDeletedQuick() ) {
$this->lastDelete = $this->getLastDelete();
if ( $this->lastDelete ) {
@@ -271,12 +325,15 @@ 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"
+ *
+ * @return bool
*/
protected function isWrongCaseCssJsPage() {
if( $this->mTitle->isCssJsSubpage() ) {
@@ -335,7 +392,7 @@ class EditPage {
$this->preview = true;
}
- $wgOut->addModules( array( 'mediawiki.legacy.edit', 'mediawiki.action.edit' ) );
+ $wgOut->addModules( array( 'mediawiki.action.edit' ) );
if ( $wgUser->getOption( 'uselivepreview', false ) ) {
$wgOut->addModules( 'mediawiki.legacy.preview' );
@@ -345,6 +402,9 @@ class EditPage {
$permErrors = $this->getEditPermissionErrors();
if ( $permErrors ) {
+ // Auto-block user's IP if the account was "hard" blocked
+ $wgUser->spreadAnyEditBlock();
+
wfDebug( __METHOD__ . ": User can't edit\n" );
$content = $this->getContent( null );
$content = $content === '' ? null : $content;
@@ -354,9 +414,9 @@ class EditPage {
} else {
if ( $this->save ) {
$this->formtype = 'save';
- } else if ( $this->preview ) {
+ } elseif ( $this->preview ) {
$this->formtype = 'preview';
- } else if ( $this->diff ) {
+ } elseif ( $this->diff ) {
$this->formtype = 'diff';
} else { # First time through
$this->firsttime = true;
@@ -377,10 +437,11 @@ class EditPage {
$this->isConflict = false;
// css / js subpages of user pages get a special treatment
- $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
- $this->isCssSubpage = $this->mTitle->isCssSubpage();
- $this->isJsSubpage = $this->mTitle->isJsSubpage();
+ $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
+ $this->isCssSubpage = $this->mTitle->isCssSubpage();
+ $this->isJsSubpage = $this->mTitle->isJsSubpage();
$this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
+ $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
# Show applicable editing introductions
if ( $this->formtype == 'initial' || $this->firsttime )
@@ -392,16 +453,18 @@ class EditPage {
# Optional notices on a per-namespace and per-page basis
$editnotice_ns = 'editnotice-'.$this->mTitle->getNamespace();
- if ( !wfEmptyMsg( $editnotice_ns, wfMsgForContent( $editnotice_ns ) ) ) {
- $wgOut->addWikiText( wfMsgForContent( $editnotice_ns ) );
+ $editnotice_ns_message = wfMessage( $editnotice_ns )->inContentLanguage();
+ if ( $editnotice_ns_message->exists() ) {
+ $wgOut->addWikiText( $editnotice_ns_message->plain() );
}
if ( MWNamespace::hasSubpages( $this->mTitle->getNamespace() ) ) {
$parts = explode( '/', $this->mTitle->getDBkey() );
$editnotice_base = $editnotice_ns;
while ( count( $parts ) > 0 ) {
$editnotice_base .= '-'.array_shift( $parts );
- if ( !wfEmptyMsg( $editnotice_base, wfMsgForContent( $editnotice_base ) ) ) {
- $wgOut->addWikiText( wfMsgForContent( $editnotice_base ) );
+ $editnotice_base_msg = wfMessage( $editnotice_base )->inContentLanguage();
+ if ( $editnotice_base_msg->exists() ) {
+ $wgOut->addWikiText( $editnotice_base_msg->plain() );
}
}
}
@@ -439,6 +502,9 @@ class EditPage {
wfProfileOut( __METHOD__ );
}
+ /**
+ * @return array
+ */
protected function getEditPermissionErrors() {
global $wgUser;
$permErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser );
@@ -532,7 +598,7 @@ class EditPage {
/**
* @todo document
- * @param $request
+ * @param $request WebRequest
*/
function importFormData( &$request ) {
global $wgLang, $wgUser;
@@ -559,7 +625,7 @@ class EditPage {
}
# Truncate for whole multibyte characters. +5 bytes for ellipsis
- $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250, '' );
+ $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250 );
# Remove extra headings from summaries and new sections.
$this->summary = preg_replace('/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary);
@@ -569,7 +635,17 @@ class EditPage {
$this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
- if ( is_null( $this->edittime ) ) {
+ if ($this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null) {
+ // wpTextbox1 field is missing, possibly due to being "too big"
+ // according to some filter rules such as Suhosin's setting for
+ // suhosin.request.max_value_length (d'oh)
+ $this->incompleteForm = true;
+ } else {
+ // edittime should be one of our last fields; if it's missing,
+ // the submission probably broke somewhere in the middle.
+ $this->incompleteForm = is_null( $this->edittime );
+ }
+ if ( $this->incompleteForm ) {
# If the form is incomplete, force to preview.
wfDebug( __METHOD__ . ": Form data appears to be incomplete\n" );
wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
@@ -589,7 +665,7 @@ class EditPage {
# The unmarked state will be assumed to be a save,
# if the form seems otherwise complete.
wfDebug( __METHOD__ . ": Passed token check.\n" );
- } else if ( $this->diff ) {
+ } elseif ( $this->diff ) {
# Failed token check, but only requested "Show Changes".
wfDebug( __METHOD__ . ": Failed token check; Show Changes requested.\n" );
} else {
@@ -653,7 +729,7 @@ class EditPage {
$this->bot = $request->getBool( 'bot', true );
$this->nosummary = $request->getBool( 'nosummary' );
- // FIXME: unused variable?
+ // @todo FIXME: Unused variable?
$this->oldid = $request->getInt( 'oldid' );
$this->live = $request->getCheck( 'live' );
@@ -719,8 +795,8 @@ class EditPage {
$ip = User::isIP( $username );
if ( !$user->isLoggedIn() && !$ip ) { # User does not exist
$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
+ array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
+ } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
LogEventsList::showLogExtract(
$wgOut,
'block',
@@ -766,8 +842,8 @@ class EditPage {
$title = Title::newFromText( $this->editintro );
if ( $title instanceof Title && $title->exists() && $title->userCanRead() ) {
global $wgOut;
- $revision = Revision::newFromTitle( $title );
- $wgOut->addWikiTextTitleTidy( $revision->getText(), $this->mTitle );
+ // Added using template syntax, to take <noinclude>'s into account.
+ $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
return true;
} else {
return false;
@@ -779,32 +855,46 @@ class EditPage {
/**
* Attempt submission (no UI)
- * @return one of the constants describing the result
+ *
+ * @param $result
+ * @param $bot bool
+ *
+ * @return Status object, possibly with a message, but always with one of the AS_* constants in $status->value,
+ *
+ * FIXME: This interface is TERRIBLE, but hard to get rid of due to various error display idiosyncrasies. There are
+ * also lots of cases where error metadata is set in the object and retrieved later instead of being returned, e.g.
+ * AS_CONTENT_TOO_BIG and AS_BLOCKED_PAGE_FOR_USER. All that stuff needs to be cleaned up some time.
*/
function internalAttemptSave( &$result, $bot = false ) {
global $wgFilterCallback, $wgUser, $wgParser;
global $wgMaxArticleSize;
+
+ $status = Status::newGood();
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__ . '-checks' );
if ( !wfRunHooks( 'EditPage::attemptSave', array( $this ) ) ) {
wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR;
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_HOOK_ERROR;
+ return $status;
}
# Check image redirect
if ( $this->mTitle->getNamespace() == NS_FILE &&
Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
!$wgUser->isAllowed( 'upload' ) ) {
- $isAnon = $wgUser->isAnon();
+ $code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
+ $status->setResult( false, $code );
wfProfileOut( __METHOD__ . '-checks' );
+
wfProfileOut( __METHOD__ );
- return $isAnon ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
+ return $status;
}
# Check for spam
@@ -818,276 +908,350 @@ class EditPage {
$pdbk = $this->mTitle->getPrefixedDBkey();
$match = str_replace( "\n", '', $match );
wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
+ $status->fatal( 'spamprotectionmatch', $match );
+ $status->value = self::AS_SPAM_ERROR;
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_SPAM_ERROR;
+ return $status;
}
if ( $wgFilterCallback && $wgFilterCallback( $this->mTitle, $this->textbox1, $this->section, $this->hookError, $this->summary ) ) {
# Error messages or other handling should be performed by the filter function
+ $status->setResult( false, self::AS_FILTERING );
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_FILTERING;
+ return $status;
}
if ( !wfRunHooks( 'EditFilter', array( $this, $this->textbox1, $this->section, &$this->hookError, $this->summary ) ) ) {
# Error messages etc. could be handled within the hook...
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR;
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_HOOK_ERROR;
+ return $status;
} elseif ( $this->hookError != '' ) {
# ...or the hook could be expecting us to produce an error
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR_EXPECTED;
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_HOOK_ERROR_EXPECTED;
+ return $status;
}
if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
+ // Auto-block user's IP if the account was "hard" blocked
+ $wgUser->spreadAnyEditBlock();
# Check block state against master, thus 'false'.
+ $status->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER );
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_BLOCKED_PAGE_FOR_USER;
+ return $status;
}
$this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
if ( $this->kblength > $wgMaxArticleSize ) {
// Error will be displayed by showEditForm()
$this->tooBig = true;
+ $status->setResult( false, self::AS_CONTENT_TOO_BIG );
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_CONTENT_TOO_BIG;
+ return $status;
}
if ( !$wgUser->isAllowed( 'edit' ) ) {
if ( $wgUser->isAnon() ) {
+ $status->setResult( false, self::AS_READ_ONLY_PAGE_ANON );
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_READ_ONLY_PAGE_ANON;
+ return $status;
} else {
+ $status->fatal( 'readonlytext' );
+ $status->value = self::AS_READ_ONLY_PAGE_LOGGED;
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_READ_ONLY_PAGE_LOGGED;
+ return $status;
}
}
if ( wfReadOnly() ) {
+ $status->fatal( 'readonlytext' );
+ $status->value = self::AS_READ_ONLY_PAGE;
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_READ_ONLY_PAGE;
+ return $status;
}
if ( $wgUser->pingLimiter() ) {
+ $status->fatal( 'actionthrottledtext' );
+ $status->value = self::AS_RATE_LIMITED;
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_RATE_LIMITED;
+ return $status;
}
# If the article has been deleted while editing, don't save it without
# confirmation
if ( $this->wasDeletedSinceLastEdit() && !$this->recreate ) {
+ $status->setResult( false, self::AS_ARTICLE_WAS_DELETED );
wfProfileOut( __METHOD__ . '-checks' );
wfProfileOut( __METHOD__ );
- return self::AS_ARTICLE_WAS_DELETED;
+ return $status;
}
wfProfileOut( __METHOD__ . '-checks' );
# If article is new, insert it.
$aid = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
- if ( 0 == $aid ) {
+ $new = ( $aid == 0 );
+
+ if ( $new ) {
// Late check for create permission, just in case *PARANOIA*
if ( !$this->mTitle->userCan( 'create' ) ) {
+ $status->fatal( 'nocreatetext' );
+ $status->value = self::AS_NO_CREATE_PERMISSION;
wfDebug( __METHOD__ . ": no create permission\n" );
wfProfileOut( __METHOD__ );
- return self::AS_NO_CREATE_PERMISSION;
+ return $status;
}
# Don't save a new article if it's blank.
if ( $this->textbox1 == '' ) {
+ $status->setResult( false, self::AS_BLANK_ARTICLE );
wfProfileOut( __METHOD__ );
- return self::AS_BLANK_ARTICLE;
+ return $status;
}
// Run post-section-merge edit filter
if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
# Error messages etc. could be handled within the hook...
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR;
wfProfileOut( __METHOD__ );
- return self::AS_HOOK_ERROR;
+ return $status;
} elseif ( $this->hookError != '' ) {
# ...or the hook could be expecting us to produce an error
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR_EXPECTED;
wfProfileOut( __METHOD__ );
- return self::AS_HOOK_ERROR_EXPECTED;
+ return $status;
}
# Handle the user preference to force summaries here. Check if it's not a redirect.
if ( !$this->allowBlankSummary && !Title::newFromRedirect( $this->textbox1 ) ) {
if ( md5( $this->summary ) == $this->autoSumm ) {
$this->missingSummary = true;
+ $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
+ $status->value = self::AS_SUMMARY_NEEDED;
wfProfileOut( __METHOD__ );
- return self::AS_SUMMARY_NEEDED;
+ return $status;
}
}
- $isComment = ( $this->section == 'new' );
+ $text = $this->textbox1;
+ if ( $this->section == 'new' && $this->summary != '' ) {
+ $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
+ }
- $this->mArticle->insertNewArticle( $this->textbox1, $this->summary,
- $this->minoredit, $this->watchthis, false, $isComment, $bot );
+ $status->value = self::AS_SUCCESS_NEW_ARTICLE;
- wfProfileOut( __METHOD__ );
- return self::AS_SUCCESS_NEW_ARTICLE;
- }
+ } else {
- # Article exists. Check for edit conflict.
+ # Article exists. Check for edit conflict.
- $this->mArticle->clear(); # Force reload of dates, etc.
- $this->mArticle->forUpdate( true ); # Lock the article
+ $this->mArticle->clear(); # Force reload of dates, etc.
- wfDebug( "timestamp: {$this->mArticle->getTimestamp()}, edittime: {$this->edittime}\n" );
+ wfDebug( "timestamp: {$this->mArticle->getTimestamp()}, edittime: {$this->edittime}\n" );
- if ( $this->mArticle->getTimestamp() != $this->edittime ) {
- $this->isConflict = true;
- if ( $this->section == 'new' ) {
- if ( $this->mArticle->getUserText() == $wgUser->getName() &&
- $this->mArticle->getComment() == $this->summary ) {
- // Probably a duplicate submission of a new comment.
- // This can happen when squid resends a request after
- // a timeout but the first one actually went through.
- wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
- } else {
- // New comment; suppress conflict.
- $this->isConflict = false;
- wfDebug( __METHOD__ .": conflict suppressed; new section\n" );
+ if ( $this->mArticle->getTimestamp() != $this->edittime ) {
+ $this->isConflict = true;
+ if ( $this->section == 'new' ) {
+ if ( $this->mArticle->getUserText() == $wgUser->getName() &&
+ $this->mArticle->getComment() == $this->summary ) {
+ // Probably a duplicate submission of a new comment.
+ // This can happen when squid resends a request after
+ // a timeout but the first one actually went through.
+ wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
+ } else {
+ // New comment; suppress conflict.
+ $this->isConflict = false;
+ wfDebug( __METHOD__ .": conflict suppressed; new section\n" );
+ }
}
}
- }
- $userid = $wgUser->getId();
-
- # Suppress edit conflict with self, except for section edits where merging is required.
- if ( $this->isConflict && $this->section == '' && $this->userWasLastToEdit( $userid, $this->edittime ) ) {
- wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
- $this->isConflict = false;
- }
+ $userid = $wgUser->getId();
- if ( $this->isConflict ) {
- wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '" .
- $this->mArticle->getTimestamp() . "')\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary, $this->edittime );
- } else {
- wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary );
- }
- if ( is_null( $text ) ) {
- wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
- $this->isConflict = true;
- $text = $this->textbox1; // do not try to merge here!
- } else if ( $this->isConflict ) {
- # Attempt merge
- if ( $this->mergeChangesInto( $text ) ) {
- // Successful merge! Maybe we should tell the user the good news?
+ # Suppress edit conflict with self, except for section edits where merging is required.
+ if ( $this->isConflict && $this->section == '' && $this->userWasLastToEdit( $userid, $this->edittime ) ) {
+ wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
$this->isConflict = false;
- wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
- } else {
- $this->section = '';
- $this->textbox1 = $text;
- wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
}
- }
- if ( $this->isConflict ) {
- wfProfileOut( __METHOD__ );
- return self::AS_CONFLICT_DETECTED;
- }
-
- $oldtext = $this->mArticle->getContent();
-
- // Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
- # Error messages etc. could be handled within the hook...
- wfProfileOut( __METHOD__ );
- return self::AS_HOOK_ERROR;
- } elseif ( $this->hookError != '' ) {
- # ...or the hook could be expecting us to produce an error
- wfProfileOut( __METHOD__ );
- return self::AS_HOOK_ERROR_EXPECTED;
- }
+ if ( $this->isConflict ) {
+ wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '" .
+ $this->mArticle->getTimestamp() . "')\n" );
+ $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary, $this->edittime );
+ } else {
+ wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
+ $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary );
+ }
+ if ( is_null( $text ) ) {
+ wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
+ $this->isConflict = true;
+ $text = $this->textbox1; // do not try to merge here!
+ } elseif ( $this->isConflict ) {
+ # Attempt merge
+ if ( $this->mergeChangesInto( $text ) ) {
+ // Successful merge! Maybe we should tell the user the good news?
+ $this->isConflict = false;
+ wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
+ } else {
+ $this->section = '';
+ $this->textbox1 = $text;
+ wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
+ }
+ }
- # Handle the user preference to force summaries here, but not for null edits
- if ( $this->section != 'new' && !$this->allowBlankSummary && 0 != strcmp( $oldtext, $text )
- && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
- {
- if ( md5( $this->summary ) == $this->autoSumm ) {
- $this->missingSummary = true;
+ if ( $this->isConflict ) {
+ $status->setResult( false, self::AS_CONFLICT_DETECTED );
wfProfileOut( __METHOD__ );
- return self::AS_SUMMARY_NEEDED;
+ return $status;
}
- }
- # And a similar thing for new sections
- if ( $this->section == 'new' && !$this->allowBlankSummary ) {
- if ( trim( $this->summary ) == '' ) {
- $this->missingSummary = true;
+ $oldtext = $this->mArticle->getContent();
+
+ // Run post-section-merge edit filter
+ if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
+ # Error messages etc. could be handled within the hook...
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ } elseif ( $this->hookError != '' ) {
+ # ...or the hook could be expecting us to produce an error
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR_EXPECTED;
wfProfileOut( __METHOD__ );
- return self::AS_SUMMARY_NEEDED;
+ return $status;
}
- }
- # All's well
- wfProfileIn( __METHOD__ . '-sectionanchor' );
- $sectionanchor = '';
- if ( $this->section == 'new' ) {
- if ( $this->textbox1 == '' ) {
- $this->missingComment = true;
- wfProfileOut( __METHOD__ . '-sectionanchor' );
- wfProfileOut( __METHOD__ );
- return self::AS_TEXTBOX_EMPTY;
+ # Handle the user preference to force summaries here, but not for null edits
+ if ( $this->section != 'new' && !$this->allowBlankSummary && 0 != strcmp( $oldtext, $text )
+ && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
+ {
+ if ( md5( $this->summary ) == $this->autoSumm ) {
+ $this->missingSummary = true;
+ $status->fatal( 'missingsummary' );
+ $status->value = self::AS_SUMMARY_NEEDED;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
}
- if ( $this->summary != '' ) {
- $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
- # This is a new section, so create a link to the new section
- # in the revision summary.
- $cleanSummary = $wgParser->stripSectionName( $this->summary );
- $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
+
+ # And a similar thing for new sections
+ if ( $this->section == 'new' && !$this->allowBlankSummary ) {
+ if ( trim( $this->summary ) == '' ) {
+ $this->missingSummary = true;
+ $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
+ $status->value = self::AS_SUMMARY_NEEDED;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
}
- } elseif ( $this->section != '' ) {
- # Try to get a section anchor from the section source, redirect to edited section if header found
- # XXX: might be better to integrate this into Article::replaceSection
- # for duplicate heading checking and maybe parsing
- $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
- # we can't deal with anchors, includes, html etc in the header for now,
- # headline would need to be parsed to improve this
- if ( $hasmatch and strlen( $matches[2] ) > 0 ) {
- $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
+
+ # All's well
+ wfProfileIn( __METHOD__ . '-sectionanchor' );
+ $sectionanchor = '';
+ if ( $this->section == 'new' ) {
+ if ( $this->textbox1 == '' ) {
+ $this->missingComment = true;
+ $status->fatal( 'missingcommenttext' );
+ $status->value = self::AS_TEXTBOX_EMPTY;
+ wfProfileOut( __METHOD__ . '-sectionanchor' );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+ if ( $this->summary != '' ) {
+ $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
+ # This is a new section, so create a link to the new section
+ # in the revision summary.
+ $cleanSummary = $wgParser->stripSectionName( $this->summary );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
+ }
+ } elseif ( $this->section != '' ) {
+ # Try to get a section anchor from the section source, redirect to edited section if header found
+ # XXX: might be better to integrate this into Article::replaceSection
+ # for duplicate heading checking and maybe parsing
+ $hasmatch = preg_match( "/^ *([=]{1,6})(.*?)(\\1) *\\n/i", $this->textbox1, $matches );
+ # we can't deal with anchors, includes, html etc in the header for now,
+ # headline would need to be parsed to improve this
+ if ( $hasmatch && strlen( $matches[2] ) > 0 ) {
+ $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $matches[2] );
+ }
}
- }
- wfProfileOut( __METHOD__ . '-sectionanchor' );
+ $result['sectionanchor'] = $sectionanchor;
+ wfProfileOut( __METHOD__ . '-sectionanchor' );
+
+ // Save errors may fall down to the edit form, but we've now
+ // merged the section into full text. Clear the section field
+ // so that later submission of conflict forms won't try to
+ // replace that into a duplicated mess.
+ $this->textbox1 = $text;
+ $this->section = '';
- // Save errors may fall down to the edit form, but we've now
- // merged the section into full text. Clear the section field
- // so that later submission of conflict forms won't try to
- // replace that into a duplicated mess.
- $this->textbox1 = $text;
- $this->section = '';
+ $status->value = self::AS_SUCCESS_UPDATE;
+ }
// Check for length errors again now that the section is merged in
$this->kblength = (int)( strlen( $text ) / 1024 );
if ( $this->kblength > $wgMaxArticleSize ) {
$this->tooBig = true;
+ $status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
wfProfileOut( __METHOD__ );
- return self::AS_MAX_ARTICLE_SIZE_EXCEEDED;
+ return $status;
}
- # update the article here
- if ( $this->mArticle->updateArticle( $text, $this->summary, $this->minoredit,
- $this->watchthis, $bot, $sectionanchor ) )
- {
+ $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
+ ( $new ? EDIT_NEW : EDIT_UPDATE ) |
+ ( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
+ ( $bot ? EDIT_FORCE_BOT : 0 );
+
+ $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags );
+
+ if ( $doEditStatus->isOK() ) {
+ $result['redirect'] = Title::newFromRedirect( $text ) !== null;
+ $this->commitWatch();
wfProfileOut( __METHOD__ );
- return self::AS_SUCCESS_UPDATE;
+ return $status;
} else {
$this->isConflict = true;
+ $doEditStatus->value = self::AS_END; // Destroys data doEdit() put in $status->value but who cares
+ wfProfileOut( __METHOD__ );
+ return $doEditStatus;
+ }
+ }
+
+ /**
+ * Commit the change of watch status
+ */
+ protected function commitWatch() {
+ global $wgUser;
+ if ( $this->watchthis xor $this->mTitle->userIsWatching() ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+ if ( $this->watchthis ) {
+ WatchAction::doWatch( $this->mTitle, $wgUser );
+ } else {
+ WatchAction::doUnwatch( $this->mTitle, $wgUser );
+ }
+ $dbw->commit();
}
- wfProfileOut( __METHOD__ );
- return self::AS_END;
}
/**
* Check if no edits were made by other users since
* the time a user started editing the page. Limit to
* 50 revisions for the sake of performance.
+ *
+ * @param $id int
+ * @param $edittime string
+ *
+ * @return bool
*/
protected function userWasLastToEdit( $id, $edittime ) {
if( !$id ) return false;
@@ -1110,7 +1274,10 @@ class EditPage {
/**
* Check given input text against $wgSpamRegex, and return the text of the first match.
- * @return mixed -- matching string or false
+ *
+ * @param $text string
+ *
+ * @return string|false matching string or false
*/
public static function matchSpamRegex( $text ) {
global $wgSpamRegex;
@@ -1121,7 +1288,10 @@ class EditPage {
/**
* Check given input text against $wgSpamRegex, and return the text of the first match.
- * @return mixed -- matching string or false
+ *
+ * @parma $text string
+ *
+ * @return string|false matching string or false
*/
public static function matchSummarySpamRegex( $text ) {
global $wgSummarySpamRegex;
@@ -1129,6 +1299,11 @@ class EditPage {
return self::matchSpamRegexInternal( $text, $regexes );
}
+ /**
+ * @param $text string
+ * @param $regexes array
+ * @return bool|string
+ */
protected static function matchSpamRegexInternal( $text, $regexes ) {
foreach( $regexes as $regex ) {
$matches = array();
@@ -1142,7 +1317,7 @@ class EditPage {
/**
* Initialise form fields in the object
* Called on the first invocation, e.g. when a user clicks an edit link
- * @returns bool -- if the requested section is valid
+ * @return bool -- if the requested section is valid
*/
function initialiseForm() {
global $wgUser;
@@ -1160,30 +1335,34 @@ class EditPage {
# Already watched
$this->watchthis = true;
}
- if ( $wgUser->getOption( 'minordefault' ) ) $this->minoredit = true;
- if ( $this->textbox1 === false ) return false;
+ if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
+ $this->minoredit = true;
+ }
+ if ( $this->textbox1 === false ) {
+ return false;
+ }
wfProxyCheck();
return true;
}
function setHeaders() {
- global $wgOut, $wgTitle;
+ global $wgOut;
$wgOut->setRobotPolicy( 'noindex,nofollow' );
if ( $this->formtype == 'preview' ) {
$wgOut->setPageTitleActionText( wfMsg( 'preview' ) );
}
if ( $this->isConflict ) {
- $wgOut->setPageTitle( wfMsg( 'editconflict', $wgTitle->getPrefixedText() ) );
+ $wgOut->setPageTitle( wfMsg( 'editconflict', $this->getContextTitle()->getPrefixedText() ) );
} elseif ( $this->section != '' ) {
$msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
- $wgOut->setPageTitle( wfMsg( $msg, $wgTitle->getPrefixedText() ) );
+ $wgOut->setPageTitle( wfMsg( $msg, $this->getContextTitle()->getPrefixedText() ) );
} else {
# Use the title defined by DISPLAYTITLE magic word when present
if ( isset( $this->mParserOutput )
&& ( $dt = $this->mParserOutput->getDisplayTitle() ) !== false ) {
$title = $dt;
} else {
- $title = $wgTitle->getPrefixedText();
+ $title = $this->getContextTitle()->getPrefixedText();
}
$wgOut->setPageTitle( wfMsg( 'editing', $title ) );
}
@@ -1191,24 +1370,14 @@ class EditPage {
/**
* Send the edit form and related headers to $wgOut
- * @param $formCallback Optional callable that takes an OutputPage
- * parameter; will be called during form output
- * near the top, for captchas and the like.
+ * @param $formCallback Callback that takes an OutputPage parameter; will be called
+ * during form output near the top, for captchas and the like.
*/
function showEditForm( $formCallback = null ) {
- global $wgOut, $wgUser, $wgTitle;
-
- # If $wgTitle is null, that means we're in API mode.
- # Some hook probably called this function without checking
- # for is_null($wgTitle) first. Bail out right here so we don't
- # do lots of work just to discard it right after.
- if ( is_null( $wgTitle ) )
- return;
+ global $wgOut, $wgUser;
wfProfileIn( __METHOD__ );
- $sk = $wgUser->getSkin();
-
#need to parse the preview early so that we know which templates are used,
#otherwise users with "show preview after edit box" will get a blank list
#we parse this near the beginning so that setHeaders can do the title
@@ -1230,7 +1399,7 @@ class EditPage {
return;
}
- $action = htmlspecialchars( $this->getActionURL( $wgTitle ) );
+ $action = htmlspecialchars( $this->getActionURL( $this->getContextTitle() ) );
if ( $wgUser->getOption( 'showtoolbar' ) and !$this->isCssJsSubpage ) {
# prepare toolbar for edit buttons
@@ -1249,10 +1418,10 @@ class EditPage {
$wgOut->addHTML( $this->editFormTextTop );
$templates = $this->getTemplates();
- $formattedtemplates = $sk->formatTemplates( $templates, $this->preview, $this->section != '');
+ $formattedtemplates = Linker::formatTemplates( $templates, $this->preview, $this->section != '');
$hiddencats = $this->mArticle->getHiddenCategories();
- $formattedhiddencats = $sk->formatHiddenCategories( $hiddencats );
+ $formattedhiddencats = Linker::formatHiddenCategories( $hiddencats );
if ( $this->wasDeletedSinceLastEdit() && 'save' != $this->formtype ) {
$wgOut->wrapWikiMsg(
@@ -1265,7 +1434,6 @@ class EditPage {
// @todo move this to a cleaner conditional instead of blanking a variable
}
$wgOut->addHTML( <<<HTML
-{$toolbar}
<form id="editform" name="editform" method="post" action="$action" enctype="multipart/form-data">
HTML
);
@@ -1280,11 +1448,19 @@ HTML
$this->showFormBeforeText();
if ( $this->wasDeletedSinceLastEdit() && 'save' == $this->formtype ) {
+ $username = $this->lastDelete->user_name;
+ $comment = $this->lastDelete->log_comment;
+
+ // It is better to not parse the comment at all than to have templates expanded in the middle
+ // TODO: can the checkLabel be moved outside of the div so that wrapWikiMsg could be used?
+ $key = $comment === ''
+ ? 'confirmrecreate-noreason'
+ : 'confirmrecreate';
$wgOut->addHTML(
'<div class="mw-confirm-recreate">' .
- $wgOut->parse( wfMsg( 'confirmrecreate', $this->lastDelete->user_name , $this->lastDelete->log_comment ) ) .
+ wfMsgExt( $key, 'parseinline', $username, "<nowiki>$comment</nowiki>" ) .
Xml::checkLabel( wfMsg( 'recreate' ), 'wpRecreate', 'wpRecreate', false,
- array( 'title' => $sk->titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
+ array( 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
) .
'</div>'
);
@@ -1312,6 +1488,8 @@ HTML
$wgOut->addHTML( $this->editFormTextBeforeContent );
+ $wgOut->addHTML( $toolbar );
+
if ( $this->isConflict ) {
// In an edit conflict bypass the overrideable content form method
// and fallback to the raw wpTextbox1 since editconflicts can't be
@@ -1359,7 +1537,7 @@ HTML
}
protected function showHeader() {
- global $wgOut, $wgUser, $wgTitle, $wgMaxArticleSize, $wgLang;
+ global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
if ( $this->isConflict ) {
$wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
$this->edittime = $this->mArticle->getTimestamp();
@@ -1410,7 +1588,7 @@ HTML
if ( !$this->mArticle->mRevision->userCan( Revision::DELETED_TEXT ) ) {
$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 ) ) {
+ } elseif ( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
$wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
}
@@ -1433,7 +1611,7 @@ HTML
if ( $this->isCssJsSubpage ) {
# Check the skin exists
if ( $this->isWrongCaseCssJsPage ) {
- $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $wgTitle->getSkinFromCssJsSubpage() ) );
+ $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->getContextTitle()->getSkinFromCssJsSubpage() ) );
}
if ( $this->formtype !== 'preview' ) {
if ( $this->isCssSubpage )
@@ -1485,9 +1663,7 @@ HTML
$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 !== '-' ) {
+ if( !wfMessage('longpage-hint')->isDisabled() ) {
$wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
array( 'longpage-hint', $wgLang->formatSize( strlen( $this->textbox1 ) ), strlen( $this->textbox1 ) )
);
@@ -1502,15 +1678,14 @@ HTML
* 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 $inputAttrs An array of attrs to use on the input
- * @param $spanLabelAttrs An array of attrs to use on the span inside the label
+ * @param $summary string The value of the summary input
+ * @param $labelText string The html to place inside the label
+ * @param $inputAttrs array of attrs to use on the input
+ * @param $spanLabelAttrs array of attrs to use on the span inside the label
*
* @return array An array in the format array( $label, $input )
*/
function getSummaryInput($summary = "", $labelText = null, $inputAttrs = null, $spanLabelAttrs = null) {
- 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',
@@ -1518,7 +1693,7 @@ HTML
'tabindex' => '1',
'size' => 60,
'spellcheck' => 'true',
- ) + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'summary' );
+ ) + Linker::tooltipAndAccesskeyAttribs( 'summary' );
$spanLabelAttrs = ( is_array($spanLabelAttrs) ? $spanLabelAttrs : array() ) + array(
'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
@@ -1548,11 +1723,13 @@ HTML
# Add a class if 'missingsummary' is triggered to allow styling of the summary line
$summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
if ( $isSubjectPreview ) {
- if ( $this->nosummary )
+ if ( $this->nosummary ) {
return;
+ }
} else {
- if ( !$this->mShowSummaryField )
+ if ( !$this->mShowSummaryField ) {
return;
+ }
}
$summary = $wgContLang->recodeForEdit( $summary );
$labelText = wfMsgExt( $isSubjectPreview ? 'subject' : 'summary', 'parseinline' );
@@ -1571,15 +1748,14 @@ HTML
if ( !$summary || ( !$this->preview && !$this->diff ) )
return "";
- global $wgParser, $wgUser;
- $sk = $wgUser->getSkin();
+ global $wgParser;
if ( $isSubjectPreview )
$summary = wfMsgForContent( 'newsectionsummary', $wgParser->stripSectionName( $summary ) );
$message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
- $summary = wfMsgExt( $message, 'parseinline' ) . $sk->commentBlock( $summary, $this->mTitle, $isSubjectPreview );
+ $summary = wfMsgExt( $message, 'parseinline' ) . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
return Xml::tags( 'div', array( 'class' => 'mw-summary-preview' ), $summary );
}
@@ -1645,6 +1821,10 @@ HTML
# Then it must be protected based on static groups (regular)
$classes[] = 'mw-textarea-protected';
}
+ # Is the title cascade-protected?
+ if ( $this->mTitle->isCascadeProtected() ) {
+ $classes[] = 'mw-textarea-cprotected';
+ }
}
$attribs = array( 'tabindex' => 1 );
if ( is_array($customAttribs) )
@@ -1662,7 +1842,7 @@ HTML
}
protected function showTextbox2() {
- $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6 ) );
+ $this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
}
protected function showTextbox( $content, $name, $customAttribs = array() ) {
@@ -1685,6 +1865,10 @@ HTML
'style' => '' // avoid php notices when appending preferences (appending allows customAttribs['style'] to still work
);
+ $pageLang = $this->mTitle->getPageLanguage();
+ $attribs['lang'] = $pageLang->getCode();
+ $attribs['dir'] = $pageLang->getDir();
+
$wgOut->addHTML( Html::textarea( $name, $wikitext, $attribs ) );
}
@@ -1743,20 +1927,19 @@ HTML
protected function showTosSummary() {
$msg = 'editpage-tos-summary';
wfRunHooks( 'EditPageTosSummary', array( $this->mTitle, &$msg ) );
- $text = wfMsg( $msg );
- if( !wfEmptyMsg( $msg, $text ) && $text !== '-' ) {
+ if( !wfMessage( $msg )->isDisabled() ) {
global $wgOut;
$wgOut->addHTML( '<div class="mw-tos-summary">' );
- $wgOut->addWikiMsgArray( $msg, array() );
+ $wgOut->addWikiMsg( $msg );
$wgOut->addHTML( '</div>' );
}
}
protected function showEditTools() {
global $wgOut;
- $wgOut->addHTML( '<div class="mw-editTools">' );
- $wgOut->addWikiMsgArray( 'edittools', array(), array( 'content' ) );
- $wgOut->addHTML( '</div>' );
+ $wgOut->addHTML( '<div class="mw-editTools">' .
+ wfMessage( 'edittools' )->inContentLanguage()->parse() .
+ '</div>' );
}
protected function getCopywarn() {
@@ -1772,11 +1955,12 @@ HTML
// 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>";
+ return "<div id=\"editpage-copywarn\">\n" .
+ call_user_func_array("wfMsgNoTrans", $copywarnMsg) . "\n</div>";
}
protected function showStandardInputs( &$tabindex = 2 ) {
- global $wgOut, $wgUser;
+ global $wgOut;
$wgOut->addHTML( "<div class='editOptions'>\n" );
if ( $this->section != 'new' ) {
@@ -1784,23 +1968,25 @@ HTML
$wgOut->addHTML( $this->getSummaryPreview( false, $this->summary ) );
}
- $checkboxes = $this->getCheckboxes( $tabindex, $wgUser->getSkin(),
+ $checkboxes = $this->getCheckboxes( $tabindex,
array( 'minor' => $this->minoredit, 'watch' => $this->watchthis ) );
$wgOut->addHTML( "<div class='editCheckboxes'>" . implode( $checkboxes, "\n" ) . "</div>\n" );
$wgOut->addHTML( "<div class='editButtons'>\n" );
$wgOut->addHTML( implode( $this->getEditButtons( $tabindex ), "\n" ) . "\n" );
$cancel = $this->getCancelLink();
- $separator = wfMsgExt( 'pipe-separator' , 'escapenoentities' );
+ if ( $cancel !== '' ) {
+ $cancel .= wfMsgExt( 'pipe-separator' , 'escapenoentities' );
+ }
$edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ) );
$edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'.
htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '.
htmlspecialchars( wfMsg( 'newwindow' ) );
- $wgOut->addHTML( " <span class='editHelp'>{$cancel}{$separator}{$edithelp}</span>\n" );
+ $wgOut->addHTML( " <span class='editHelp'>{$cancel}{$edithelp}</span>\n" );
$wgOut->addHTML( "</div><!-- editButtons -->\n</div><!-- editOptions -->\n" );
}
- /*
+ /**
* Show an edit conflict. textbox1 is already shown in showEditForm().
* If you want to use another entry point to this function, be careful.
*/
@@ -1825,20 +2011,20 @@ HTML
$data = $dbr->selectRow(
array( 'logging', 'user' ),
array( 'log_type',
- 'log_action',
- 'log_timestamp',
- 'log_user',
- 'log_namespace',
- 'log_title',
- 'log_comment',
- 'log_params',
- 'log_deleted',
- 'user_name' ),
+ 'log_action',
+ 'log_timestamp',
+ 'log_user',
+ 'log_namespace',
+ 'log_title',
+ 'log_comment',
+ 'log_params',
+ 'log_deleted',
+ 'user_name' ),
array( 'log_namespace' => $this->mTitle->getNamespace(),
- 'log_title' => $this->mTitle->getDBkey(),
- 'log_type' => 'delete',
- 'log_action' => 'delete',
- 'user_id=log_user' ),
+ 'log_title' => $this->mTitle->getDBkey(),
+ 'log_type' => 'delete',
+ 'log_action' => 'delete',
+ 'user_id=log_user' ),
__METHOD__,
array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
);
@@ -1857,7 +2043,7 @@ HTML
* @return string
*/
function getPreviewText() {
- global $wgOut, $wgUser, $wgParser, $wgMessageCache;
+ global $wgOut, $wgUser, $wgParser;
wfProfileIn( __METHOD__ );
@@ -1867,6 +2053,8 @@ HTML
} else {
$note = wfMsg( 'session_fail_preview' );
}
+ } elseif ( $this->incompleteForm ) {
+ $note = wfMsg( 'edit_form_incomplete' );
} else {
$note = wfMsg( 'previewnote' );
}
@@ -1880,14 +2068,20 @@ HTML
if ( $wgRawHtml && !$this->mTokenOk ) {
// Could be an offsite preview attempt. This is very unsafe if
// HTML is enabled, as it could be an attack.
- $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
- wfMsg( 'session_fail_preview_html' ) . "</div>" );
+ $parsedNote = '';
+ if ( $this->textbox1 !== '' ) {
+ // Do not put big scary notice, if previewing the empty
+ // string, which happens when you initially edit
+ // a category page, due to automatic preview-on-open.
+ $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 -- This note has been there since r3530. Sure the bug was fixed time ago?
+ # XXX: stupid php bug won't let us use $this->getContextTitle()->isCssJsSubpage() here -- This note has been there since r3530. Sure the bug was fixed time ago?
if ( $this->isCssJsSubpage || $this->mTitle->isCssOrJsPage() ) {
$level = 'user';
@@ -1926,13 +2120,6 @@ HTML
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 ),
@@ -1958,14 +2145,24 @@ HTML
'<h2 id="mw-previewheader">' . htmlspecialchars( wfMsg( 'preview' ) ) . "</h2>" .
$wgOut->parse( $note ) . $conflict . "</div>\n";
+ $pageLang = $this->mTitle->getPageLanguage();
+ $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
+ 'class' => 'mw-content-'.$pageLang->getDir() );
+ $previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
+
wfProfileOut( __METHOD__ );
return $previewhead . $previewHTML . $this->previewTextAfterContent;
}
+ /**
+ * @return Array
+ */
function getTemplates() {
if ( $this->preview || $this->section != '' ) {
$templates = array();
- if ( !isset( $this->mParserOutput ) ) return $templates;
+ if ( !isset( $this->mParserOutput ) ) {
+ return $templates;
+ }
foreach( $this->mParserOutput->getTemplates() as $ns => $template) {
foreach( array_keys( $template ) as $dbk ) {
$templates[] = Title::makeTitle($ns, $dbk);
@@ -2005,24 +2202,22 @@ HTML
* Produce the stock "please login to edit pages" page
*/
function userNotLoggedInPage() {
- global $wgUser, $wgOut, $wgTitle;
- $skin = $wgUser->getSkin();
+ global $wgOut;
$loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
- $loginLink = $skin->link(
+ $loginLink = Linker::linkKnown(
$loginTitle,
wfMsgHtml( 'loginreqlink' ),
array(),
- array( 'returnto' => $wgTitle->getPrefixedText() ),
- array( 'known', 'noclasses' )
+ array( 'returnto' => $this->getContextTitle()->getPrefixedText() )
);
$wgOut->setPageTitle( wfMsg( 'whitelistedittitle' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setArticleRelated( false );
- $wgOut->addHTML( wfMsgWikiHtml( 'whitelistedittext', $loginLink ) );
- $wgOut->returnToMain( false, $wgTitle );
+ $wgOut->addHTML( wfMessage( 'whitelistedittext' )->rawParams( $loginLink )->parse() );
+ $wgOut->returnToMain( false, $this->getContextTitle() );
}
/**
@@ -2047,7 +2242,7 @@ HTML
* Produce the stock "your edit contains spam" page
*
* @param $match Text which triggered one or more filters
- * @deprecated Use method spamPageWithContent() instead
+ * @deprecated since 1.17 Use method spamPageWithContent() instead
*/
static function spamPage( $match = false ) {
global $wgOut, $wgTitle;
@@ -2072,7 +2267,7 @@ HTML
* @param $match Text which triggered one or more filters
*/
public function spamPageWithContent( $match = false ) {
- global $wgOut, $wgTitle;
+ global $wgOut;
$this->textbox2 = $this->textbox1;
$wgOut->setPageTitle( wfMsg( 'spamprotectiontitle' ) );
@@ -2094,13 +2289,17 @@ HTML
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
$this->showTextbox2();
- $wgOut->addReturnTo( $wgTitle, array( 'action' => 'edit' ) );
+ $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
}
/**
* @private
* @todo document
+ *
+ * @parma $editText string
+ *
+ * @return bool
*/
function mergeChangesInto( &$editText ){
wfProfileIn( __METHOD__ );
@@ -2157,14 +2356,6 @@ HTML
}
/**
- * @deprecated use $wgParser->stripSectionName()
- */
- function pseudoParseSectionAnchor( $text ) {
- global $wgParser;
- return $wgParser->stripSectionName( $text );
- }
-
- /**
* Format an anchor fragment as it would appear for a given section name
* @param $text String
* @return String
@@ -2189,16 +2380,19 @@ HTML
$imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
/**
-
- * toolarray an array of arrays which each include the filename of
- * the button image (without path), the opening tag, the closing tag,
- * and optionally a sample text that is inserted between the two when no
- * selection is highlighted.
- * The tip text is shown when the user moves the mouse over the button.
+ * $toolarray is an array of arrays each of which includes the
+ * filename of the button image (without path), the opening
+ * tag, the closing tag, optionally a sample text that is
+ * inserted between the two when no selection is highlighted
+ * and an option to select which switches the automatic
+ * selection of inserted text (default is true, see
+ * mw-editbutton-image). The tip text is shown when the user
+ * moves the mouse over the button.
*
- * Already here are accesskeys (key), which are not used yet until someone
- * can figure out a way to make them work in IE. However, we should make
- * sure these keys are not defined on the edit page.
+ * Also here: accesskeys (key), which are not used yet until
+ * someone can figure out a way to make them work in
+ * IE. However, we should make sure these keys are not defined
+ * on the edit page.
*/
$toolarray = array(
array(
@@ -2253,7 +2447,8 @@ HTML
'close' => ']]',
'sample' => wfMsg( 'image_sample' ),
'tip' => wfMsg( 'image_tip' ),
- 'key' => 'D'
+ 'key' => 'D',
+ 'select' => true
) : false,
$imagesAvailable ? array(
'image' => $wgLang->getImageFile( 'button-media' ),
@@ -2301,7 +2496,6 @@ HTML
'key' => 'R'
)
);
- $toolbar = "<div id='toolbar'>\n";
$script = '';
foreach ( $toolarray as $tool ) {
@@ -2309,6 +2503,10 @@ HTML
continue;
}
+ if( !isset( $tool['select'] ) ) {
+ $tool['select'] = true;
+ }
+
$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.
@@ -2322,16 +2520,11 @@ HTML
$cssId = $tool['id'],
);
- $paramList = implode( ',',
- array_map( array( 'Xml', 'encodeJsVar' ), $params ) );
- $script .= "addButton($paramList);\n";
+ $script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
}
-
- $wgOut->addScript( Html::inlineScript(
- "if ( window.mediaWiki ) { $script }"
- ) );
-
- $toolbar .= "\n</div>";
+ $wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) );
+
+ $toolbar = '<div id="toolbar"></div>';
wfRunHooks( 'EditPageBeforeEditToolbar', array( &$toolbar ) );
@@ -2343,30 +2536,32 @@ HTML
* minor and watch
*
* @param $tabindex Current tabindex
- * @param $skin Skin object
* @param $checked Array of checkbox => bool, where bool indicates the checked
* status of the checkbox
*
* @return array
*/
- public function getCheckboxes( &$tabindex, $skin, $checked ) {
+ public function getCheckboxes( &$tabindex, $checked ) {
global $wgUser;
$checkboxes = array();
- $checkboxes['minor'] = '';
- $minorLabel = wfMsgExt( 'minoredit', array( 'parseinline' ) );
- if ( $wgUser->isAllowed( 'minoredit' ) ) {
- $attribs = array(
- 'tabindex' => ++$tabindex,
- 'accesskey' => wfMsg( 'accesskey-minoredit' ),
- 'id' => 'wpMinoredit',
- );
- $checkboxes['minor'] =
- Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
- "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
- Xml::expandAttributes( array( 'title' => $skin->titleAttrib( 'minoredit', 'withaccess' ) ) ) .
- ">{$minorLabel}</label>";
+ // don't show the minor edit checkbox if it's a new page or section
+ if ( !$this->isNew ) {
+ $checkboxes['minor'] = '';
+ $minorLabel = wfMsgExt( 'minoredit', array( 'parseinline' ) );
+ if ( $wgUser->isAllowed( 'minoredit' ) ) {
+ $attribs = array(
+ 'tabindex' => ++$tabindex,
+ 'accesskey' => wfMsg( 'accesskey-minoredit' ),
+ 'id' => 'wpMinoredit',
+ );
+ $checkboxes['minor'] =
+ Xml::check( 'wpMinoredit', $checked['minor'], $attribs ) .
+ "&#160;<label for='wpMinoredit' id='mw-editpage-minoredit'" .
+ Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'minoredit', 'withaccess' ) ) ) .
+ ">{$minorLabel}</label>";
+ }
}
$watchLabel = wfMsgExt( 'watchthis', array( 'parseinline' ) );
@@ -2380,7 +2575,7 @@ HTML
$checkboxes['watch'] =
Xml::check( 'wpWatchthis', $checked['watch'], $attribs ) .
"&#160;<label for='wpWatchthis' id='mw-editpage-watch'" .
- Xml::expandAttributes( array( 'title' => $skin->titleAttrib( 'watch', 'withaccess' ) ) ) .
+ Xml::expandAttributes( array( 'title' => Linker::titleAttrib( 'watch', 'withaccess' ) ) ) .
">{$watchLabel}</label>";
}
wfRunHooks( 'EditPageBeforeEditChecks', array( &$this, &$checkboxes, &$tabindex ) );
@@ -2467,21 +2662,20 @@ HTML
echo $s;
}
-
+ /**
+ * @return string
+ */
public function getCancelLink() {
- global $wgUser, $wgTitle;
-
$cancelParams = array();
if ( !$this->isConflict && $this->mArticle->getOldID() > 0 ) {
$cancelParams['oldid'] = $this->mArticle->getOldID();
}
- return $wgUser->getSkin()->link(
- $wgTitle,
+ return Linker::linkKnown(
+ $this->getContextTitle(),
wfMsgExt( 'cancel', array( 'parseinline' ) ),
array( 'id' => 'mw-editform-cancel' ),
- $cancelParams,
- array( 'known', 'noclasses' )
+ $cancelParams
);
}
@@ -2531,6 +2725,11 @@ HTML
: $text;
}
+ /**
+ * @param $request WebRequest
+ * @param $text string
+ * @return string
+ */
function safeUnicodeText( $request, $text ) {
$text = rtrim( $text );
return $request->getBool( 'safemode' )
@@ -2575,7 +2774,7 @@ HTML
$result = "";
$working = 0;
for( $i = 0; $i < strlen( $invalue ); $i++ ) {
- $bytevalue = ord( $invalue{$i} );
+ $bytevalue = ord( $invalue[$i] );
if ( $bytevalue <= 0x7F ) { //0xxx xxxx
$result .= chr( $bytevalue );
$bytesleft = 0;
@@ -2612,13 +2811,13 @@ HTML
function unmakesafe( $invalue ) {
$result = "";
for( $i = 0; $i < strlen( $invalue ); $i++ ) {
- if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue{$i+3} != '0' ) ) {
+ if ( ( substr( $invalue, $i, 3 ) == "&#x" ) && ( $invalue[$i+3] != '0' ) ) {
$i += 3;
$hexstring = "";
do {
- $hexstring .= $invalue{$i};
+ $hexstring .= $invalue[$i];
$i++;
- } while( ctype_xdigit( $invalue{$i} ) && ( $i < strlen( $invalue ) ) );
+ } while( ctype_xdigit( $invalue[$i] ) && ( $i < strlen( $invalue ) ) );
// Do some sanity checks. These aren't needed for reversability,
// but should help keep the breakage down if the editor
@@ -2648,21 +2847,22 @@ HTML
* @return bool false if output is done, true if the rest of the form should be displayed
*/
function attemptSave() {
- global $wgUser, $wgOut, $wgTitle;
+ global $wgUser, $wgOut;
$resultDetails = false;
# Allow bots to exempt some edits from bot flagging
$bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
- $value = $this->internalAttemptSave( $resultDetails, $bot );
+ $status = $this->internalAttemptSave( $resultDetails, $bot );
+ // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
- if ( $value == self::AS_SUCCESS_UPDATE || $value == self::AS_SUCCESS_NEW_ARTICLE ) {
+ if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
$this->didSave = true;
}
- switch ( $value ) {
+ switch ( $status->value ) {
case self::AS_HOOK_ERROR_EXPECTED:
case self::AS_CONTENT_TOO_BIG:
- case self::AS_ARTICLE_WAS_DELETED:
+ case self::AS_ARTICLE_WAS_DELETED:
case self::AS_CONFLICT_DETECTED:
case self::AS_SUMMARY_NEEDED:
case self::AS_TEXTBOX_EMPTY:
@@ -2672,8 +2872,28 @@ HTML
case self::AS_HOOK_ERROR:
case self::AS_FILTERING:
+ return false;
+
case self::AS_SUCCESS_NEW_ARTICLE:
+ $query = $resultDetails['redirect'] ? 'redirect=no' : '';
+ $wgOut->redirect( $this->mTitle->getFullURL( $query ) );
+ return false;
+
case self::AS_SUCCESS_UPDATE:
+ $extraQuery = '';
+ $sectionanchor = $resultDetails['sectionanchor'];
+
+ // Give extensions a chance to modify URL query on update
+ wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this->mArticle, &$sectionanchor, &$extraQuery ) );
+
+ if ( $resultDetails['redirect'] ) {
+ if ( $extraQuery == '' ) {
+ $extraQuery = 'redirect=no';
+ } else {
+ $extraQuery = 'redirect=no&' . $extraQuery;
+ }
+ }
+ $wgOut->redirect( $this->mTitle->getFullURL( $extraQuery ) . $sectionanchor );
return false;
case self::AS_SPAM_ERROR:
@@ -2692,22 +2912,22 @@ HTML
$this->userNotLoggedInPage();
return false;
- case self::AS_READ_ONLY_PAGE_LOGGED:
- case self::AS_READ_ONLY_PAGE:
- $wgOut->readOnlyPage();
- return false;
+ case self::AS_READ_ONLY_PAGE_LOGGED:
+ case self::AS_READ_ONLY_PAGE:
+ $wgOut->readOnlyPage();
+ return false;
- case self::AS_RATE_LIMITED:
- $wgOut->rateLimited();
- return false;
+ case self::AS_RATE_LIMITED:
+ $wgOut->rateLimited();
+ return false;
- case self::AS_NO_CREATE_PERMISSION:
- $this->noCreatePermission();
- return;
+ case self::AS_NO_CREATE_PERMISSION:
+ $this->noCreatePermission();
+ return false;
case self::AS_BLANK_ARTICLE:
- $wgOut->redirect( $wgTitle->getFullURL() );
- return false;
+ $wgOut->redirect( $this->getContextTitle()->getFullURL() );
+ return false;
case self::AS_IMAGE_REDIRECT_LOGGED:
$wgOut->permissionRequired( 'upload' );
@@ -2715,6 +2935,9 @@ HTML
}
}
+ /**
+ * @return Revision
+ */
function getBaseRevision() {
if ( !$this->mBaseRevision ) {
$db = wfGetDB( DB_MASTER );
diff --git a/includes/Exception.php b/includes/Exception.php
index ff5d4b19..1f599d66 100644
--- a/includes/Exception.php
+++ b/includes/Exception.php
@@ -22,7 +22,7 @@ class MWException extends Exception {
function useOutputPage() {
return $this->useMessageCache() &&
!empty( $GLOBALS['wgFullyInitialised'] ) &&
- ( !empty( $GLOBALS['wgArticle'] ) || ( !empty( $GLOBALS['wgOut'] ) && !$GLOBALS['wgOut']->isArticle() ) ) &&
+ !empty( $GLOBALS['wgOut'] ) &&
!empty( $GLOBALS['wgTitle'] );
}
@@ -39,7 +39,7 @@ class MWException extends Exception {
}
}
- return is_object( $wgLang );
+ return $wgLang instanceof Language;
}
/**
@@ -88,7 +88,7 @@ class MWException extends Exception {
$args = array_slice( func_get_args(), 2 );
if ( $this->useMessageCache() ) {
- return wfMsgReal( $key, $args );
+ return wfMsgNoTrans( $key, $args );
} else {
return wfMsgReplaceArgs( $fallback, $args );
}
@@ -133,13 +133,8 @@ class MWException extends Exception {
/* Return titles of this error page */
function getPageTitle() {
- if ( $this->useMessageCache() ) {
- return wfMsg( 'internalerror' );
- } else {
- global $wgSitename;
-
- return "$wgSitename error";
- }
+ global $wgSitename;
+ return $this->msg( 'internalerror', "$wgSitename error" );
}
/**
@@ -155,7 +150,7 @@ class MWException extends Exception {
$line = $this->getLine();
$message = $this->getMessage();
- if ( isset( $wgRequest ) ) {
+ if ( isset( $wgRequest ) && !$wgRequest instanceof FauxRequest ) {
$url = $wgRequest->getRequestURL();
if ( !$url ) {
$url = '[no URL]';
@@ -170,7 +165,6 @@ 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" );
@@ -193,13 +187,8 @@ class MWException extends Exception {
die( $hookResult );
}
- if ( defined( 'MEDIAWIKI_INSTALL' ) || $this->htmlBodyOnly() ) {
- echo $this->getHTML();
- } else {
- echo $this->htmlHeader();
- echo $this->getHTML();
- echo $this->htmlFooter();
- }
+ echo $this->getHTML();
+ die(1);
}
}
@@ -215,55 +204,14 @@ class MWException extends Exception {
}
if ( self::isCommandLine() ) {
- wfPrintError( $this->getText() );
+ MWExceptionHandler::printError( $this->getText() );
} else {
$this->reportHTML();
}
}
- /**
- * Send headers and output the beginning of the html page if not using
- * $wgOut to output the exception.
- */
- function htmlHeader() {
- global $wgLogo, $wgOutputEncoding;
-
- if ( !headers_sent() ) {
- header( 'HTTP/1.0 500 Internal Server Error' );
- 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' );
- }
-
- $logo = htmlspecialchars( $wgLogo, ENT_QUOTES );
- $title = htmlspecialchars( $this->getPageTitle() );
-
- return "<html>
- <head>
- <title>$title</title>
- </head>
- <body>
- <h1><img src='$logo' style='float:left;margin-right:1em' alt=''/>$title</h1>
- ";
- }
-
- /**
- * print the end of the html page if not using $wgOut.
- */
- function htmlFooter() {
- return "</body></html>";
- }
-
- /**
- * headers handled by subclass?
- */
- function htmlBodyOnly() {
- return false;
- }
-
static function isCommandLine() {
- return !empty( $GLOBALS['wgCommandLineMode'] ) && !defined( 'MEDIAWIKI_INSTALL' );
+ return !empty( $GLOBALS['wgCommandLineMode'] );
}
}
@@ -283,122 +231,253 @@ class FatalError extends MWException {
}
/**
+ * An error page which can definitely be safely rendered using the OutputPage
* @ingroup Exception
*/
class ErrorPageError extends MWException {
- public $title, $msg;
+ public $title, $msg, $params;
/**
* Note: these arguments are keys into wfMsg(), not text!
*/
- function __construct( $title, $msg ) {
+ function __construct( $title, $msg, $params = null ) {
$this->title = $title;
$this->msg = $msg;
- parent::__construct( wfMsg( $msg ) );
+ $this->params = $params;
+
+ if( $msg instanceof Message ){
+ parent::__construct( $msg );
+ } else {
+ parent::__construct( wfMsg( $msg ) );
+ }
}
function report() {
global $wgOut;
- $wgOut->showErrorPage( $this->title, $this->msg );
+ $wgOut->showErrorPage( $this->title, $this->msg, $this->params );
$wgOut->output();
}
}
/**
- * Install an exception handler for MediaWiki exception types.
+ * Show an error when a user tries to do something they do not have the necessary
+ * permissions for.
+ * @ingroup Exception
*/
-function wfInstallExceptionHandler() {
- set_exception_handler( 'wfExceptionHandler' );
+class PermissionsError extends ErrorPageError {
+ public $permission;
+
+ function __construct( $permission ) {
+ global $wgLang;
+
+ $this->permission = $permission;
+
+ $groups = array_map(
+ array( 'User', 'makeGroupLinkWiki' ),
+ User::getGroupsWithPermission( $this->permission )
+ );
+
+ if( $groups ) {
+ parent::__construct(
+ 'badaccess',
+ 'badaccess-groups',
+ array(
+ $wgLang->commaList( $groups ),
+ count( $groups )
+ )
+ );
+ } else {
+ parent::__construct(
+ 'badaccess',
+ 'badaccess-group0'
+ );
+ }
+ }
}
/**
- * Report an exception to the user
+ * Show an error when the wiki is locked/read-only and the user tries to do
+ * something that requires write access
+ * @ingroup Exception
*/
-function wfReportException( Exception $e ) {
- global $wgShowExceptionDetails;
+class ReadOnlyError extends ErrorPageError {
+ public function __construct(){
+ parent::__construct(
+ 'readonly',
+ 'readonlytext',
+ wfReadOnlyReason()
+ );
+ }
+}
- $cmdLine = MWException::isCommandLine();
+/**
+ * Show an error when the user hits a rate limit
+ * @ingroup Exception
+ */
+class ThrottledError extends ErrorPageError {
+ public function __construct(){
+ parent::__construct(
+ 'actionthrottled',
+ 'actionthrottledtext'
+ );
+ }
+ public function report(){
+ global $wgOut;
+ $wgOut->setStatusCode( 503 );
+ return parent::report();
+ }
+}
- if ( $e instanceof MWException ) {
- try {
- $e->report();
- } catch ( Exception $e2 ) {
- // Exception occurred from within exception handler
- // Show a simpler error message for the original exception,
- // don't try to invoke report()
- $message = "MediaWiki internal error.\n\n";
+/**
+ * Show an error when the user tries to do something whilst blocked
+ * @ingroup Exception
+ */
+class UserBlockedError extends ErrorPageError {
+ public function __construct( Block $block ){
+ global $wgLang;
- 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.";
+ $blockerUserpage = $block->getBlocker()->getUserPage();
+ $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
+
+ $reason = $block->mReason;
+ if( $reason == '' ) {
+ $reason = wfMsg( 'blockednoreason' );
+ }
+
+ /* $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. */
+ $intended = $block->getTarget();
+
+ parent::__construct(
+ 'blockedtitle',
+ $block->mAuto ? 'autoblockedtext' : 'blockedtext',
+ array(
+ $link,
+ $reason,
+ wfGetIP(),
+ $block->getBlocker()->getName(),
+ $block->getId(),
+ $wgLang->formatExpiry( $block->mExpiry ),
+ $intended,
+ $wgLang->timeanddate( wfTimestamp( TS_MW, $block->mTimestamp ), true )
+ )
+ );
+ }
+}
+
+/**
+ * Handler class for MWExceptions
+ * @ingroup Exception
+ */
+class MWExceptionHandler {
+ /**
+ * Install an exception handler for MediaWiki exception types.
+ */
+ public static function installHandler() {
+ set_exception_handler( array( 'MWExceptionHandler', 'handle' ) );
+ }
+
+ /**
+ * Report an exception to the user
+ */
+ protected static function report( Exception $e ) {
+ global $wgShowExceptionDetails;
+
+ $cmdLine = MWException::isCommandLine();
+
+ if ( $e instanceof MWException ) {
+ try {
+ // Try and show the exception prettily, with the normal skin infrastructure
+ $e->report();
+ } catch ( Exception $e2 ) {
+ // Exception occurred from within exception handler
+ // Show a simpler error message for the original exception,
+ // don't try to invoke report()
+ $message = "MediaWiki internal error.\n\n";
+
+ if ( $wgShowExceptionDetails ) {
+ $message .= 'Original exception: ' . $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 ) {
+ self::printError( $message );
+ } else {
+ self::escapeEchoAndDie( $message );
+ }
}
+ } else {
+ $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
+ $e->__toString() . "\n";
- $message .= "\n";
+ if ( $wgShowExceptionDetails ) {
+ $message .= "\n" . $e->getTraceAsString() . "\n";
+ }
if ( $cmdLine ) {
- wfPrintError( $message );
+ self::printError( $message );
} else {
- echo nl2br( htmlspecialchars( $message ) ) . "\n";
+ self::escapeEchoAndDie( $message );
}
}
- } else {
- $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
- $e->__toString() . "\n";
-
- if ( $wgShowExceptionDetails ) {
- $message .= "\n" . $e->getTraceAsString() . "\n";
- }
+ }
- if ( $cmdLine ) {
- wfPrintError( $message );
+ /**
+ * Print a message, if possible to STDERR.
+ * Use this in command line mode only (see isCommandLine)
+ * @param $message String Failure text
+ */
+ public static function printError( $message ) {
+ # NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
+ # Try to produce meaningful output anyway. Using echo may corrupt output to STDOUT though.
+ if ( defined( 'STDERR' ) ) {
+ fwrite( STDERR, $message );
} else {
- echo nl2br( htmlspecialchars( $message ) ) . "\n";
+ echo( $message );
}
}
-}
-/**
- * Print a message, if possible to STDERR.
- * 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).
- # Try to produce meaningful output anyway. Using echo may corrupt output to STDOUT though.
- if ( defined( 'STDERR' ) ) {
- fwrite( STDERR, $message );
- } else {
- echo( $message );
+ /**
+ * Print a message after escaping it and converting newlines to <br>
+ * Use this for non-command line failures
+ * @param $message String Failure text
+ */
+ private static function escapeEchoAndDie( $message ) {
+ echo nl2br( htmlspecialchars( $message ) ) . "\n";
+ die(1);
}
-}
-/**
- * Exception handler which simulates the appropriate catch() handling:
- *
- * try {
- * ...
- * } catch ( MWException $e ) {
- * $e->report();
- * } catch ( Exception $e ) {
- * echo $e->__toString();
- * }
- */
-function wfExceptionHandler( $e ) {
- global $wgFullyInitialised;
+ /**
+ * Exception handler which simulates the appropriate catch() handling:
+ *
+ * try {
+ * ...
+ * } catch ( MWException $e ) {
+ * $e->report();
+ * } catch ( Exception $e ) {
+ * echo $e->__toString();
+ * }
+ */
+ public static function handle( $e ) {
+ global $wgFullyInitialised;
- wfReportException( $e );
+ self::report( $e );
- // Final cleanup
- if ( $wgFullyInitialised ) {
- try {
- wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
- } catch ( Exception $e ) {}
- }
+ // Final cleanup
+ if ( $wgFullyInitialised ) {
+ try {
+ wfLogProfilingData(); // uses $wgRequest, hence the $wgFullyInitialised condition
+ } catch ( Exception $e ) {}
+ }
- // Exit value should be nonzero for the benefit of shell jobs
- exit( 1 );
+ // Exit value should be nonzero for the benefit of shell jobs
+ exit( 1 );
+ }
}
diff --git a/includes/Exif.php b/includes/Exif.php
deleted file mode 100644
index 630821c3..00000000
--- a/includes/Exif.php
+++ /dev/null
@@ -1,1150 +0,0 @@
-<?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
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @ingroup Media
- * @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
- * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification
- * @file
- */
-
-/**
- * @todo document (e.g. one-sentence class-overview description)
- * @ingroup Media
- */
-class Exif {
-
- const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer.
- const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
- const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer.
- const LONG = 4; //!< A 32-bit (4-byte) unsigned integer.
- const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
- const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition
- const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
- const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
-
- //@{
- /* @var array
- * @private
- */
-
- /**
- * Exif tags grouped by category, the tagname itself is the key and the type
- * is the value, in the case of more than one possible value type they are
- * separated by commas.
- */
- var $mExifTags;
-
- /**
- * A one dimentional array of all Exif tags
- */
- var $mFlatExifTags;
-
- /**
- * The raw Exif data returned by exif_read_data()
- */
- var $mRawExifData;
-
- /**
- * A Filtered version of $mRawExifData that has been pruned of invalid
- * tags and tags that contain content they shouldn't contain according
- * to the Exif specification
- */
- var $mFilteredExifData;
-
- /**
- * Filtered and formatted Exif data, see FormatExif::getFormattedData()
- */
- var $mFormattedExifData;
-
- //@}
-
- //@{
- /* @var string
- * @private
- */
-
- /**
- * The file being processed
- */
- var $file;
-
- /**
- * The basename of the file being processed
- */
- var $basename;
-
- /**
- * The private log to log to, e.g. 'exif'
- */
- var $log = false;
-
- //@}
-
- /**
- * Constructor
- *
- * @param $file String: filename.
- */
- function __construct( $file ) {
- /**
- * Page numbers here refer to pages in the EXIF 2.2 standard
- *
- * @link http://exif.org/Exif2-2.PDF The Exif 2.2 specification
- */
- $this->mExifTags = array(
- # TIFF Rev. 6.0 Attribute Information (p22)
- 'tiff' => array(
- # Tags relating to image structure
- 'structure' => array(
- 'ImageWidth' => Exif::SHORT.','.Exif::LONG, # Image width
- 'ImageLength' => Exif::SHORT.','.Exif::LONG, # Image height
- 'BitsPerSample' => Exif::SHORT, # Number of bits per component
- # "When a primary image is JPEG compressed, this designation is not"
- # "necessary and is omitted." (p23)
- 'Compression' => Exif::SHORT, # Compression scheme #p23
- 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23
- 'Orientation' => Exif::SHORT, # Orientation of image #p24
- 'SamplesPerPixel' => Exif::SHORT, # Number of components
- 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
- 'YCbCrSubSampling' => Exif::SHORT, # Subsampling ratio of Y to C #p24
- 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
- 'XResolution' => Exif::RATIONAL, # Image resolution in width direction
- 'YResolution' => Exif::RATIONAL, # Image resolution in height direction
- 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
- ),
-
- # Tags relating to recording offset
- 'offset' => array(
- 'StripOffsets' => Exif::SHORT.','.Exif::LONG, # Image data location
- 'RowsPerStrip' => Exif::SHORT.','.Exif::LONG, # Number of rows per strip
- 'StripByteCounts' => Exif::SHORT.','.Exif::LONG, # Bytes per compressed strip
- 'JPEGInterchangeFormat' => Exif::SHORT.','.Exif::LONG, # Offset to JPEG SOI
- 'JPEGInterchangeFormatLength' => Exif::SHORT.','.Exif::LONG, # Bytes of JPEG data
- ),
-
- # Tags relating to image data characteristics
- 'characteristics' => array(
- 'TransferFunction' => Exif::SHORT, # Transfer function
- 'WhitePoint' => Exif::RATIONAL, # White point chromaticity
- 'PrimaryChromaticities' => Exif::RATIONAL, # Chromaticities of primarities
- 'YCbCrCoefficients' => Exif::RATIONAL, # Color space transformation matrix coefficients #p27
- 'ReferenceBlackWhite' => Exif::RATIONAL # Pair of black and white reference values
- ),
-
- # Other tags
- 'other' => array(
- 'DateTime' => Exif::ASCII, # File change date and time
- 'ImageDescription' => Exif::ASCII, # Image title
- 'Make' => Exif::ASCII, # Image input equipment manufacturer
- 'Model' => Exif::ASCII, # Image input equipment model
- 'Software' => Exif::ASCII, # Software used
- 'Artist' => Exif::ASCII, # Person who created the image
- 'Copyright' => Exif::ASCII, # Copyright holder
- ),
- ),
-
- # Exif IFD Attribute Information (p30-31)
- 'exif' => array(
- # Tags relating to version
- 'version' => array(
- # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
- # to the EXIF 2.1 AND 2.2 standards
- 'ExifVersion' => Exif::UNDEFINED, # Exif version
- 'FlashpixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
- ),
-
- # Tags relating to Image Data Characteristics
- 'characteristics' => array(
- 'ColorSpace' => Exif::SHORT, # Color space information #p32
- ),
-
- # Tags relating to image configuration
- 'configuration' => array(
- 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
- 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
- 'PixelYDimension' => Exif::SHORT.','.Exif::LONG, # Valid image width
- 'PixelXDimension' => Exif::SHORT.','.Exif::LONG, # Valind image height
- ),
-
- # Tags relating to related user information
- 'user' => array(
- 'MakerNote' => Exif::UNDEFINED, # Manufacturer notes
- 'UserComment' => Exif::UNDEFINED, # User comments #p34
- ),
-
- # Tags relating to related file information
- 'related' => array(
- 'RelatedSoundFile' => Exif::ASCII, # Related audio file
- ),
-
- # Tags relating to date and time
- 'dateandtime' => array(
- 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36
- 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation
- 'SubSecTime' => Exif::ASCII, # DateTime subseconds
- 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds
- 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds
- ),
-
- # Tags relating to picture-taking conditions (p31)
- 'conditions' => array(
- 'ExposureTime' => Exif::RATIONAL, # Exposure time
- 'FNumber' => Exif::RATIONAL, # F Number
- 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38
- 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity
- 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating
- 'OECF' => Exif::UNDEFINED, # Optoelectronic conversion factor
- 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed
- 'ApertureValue' => Exif::RATIONAL, # Aperture
- 'BrightnessValue' => Exif::SRATIONAL, # Brightness
- 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias
- 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture
- 'SubjectDistance' => Exif::RATIONAL, # Subject distance
- 'MeteringMode' => Exif::SHORT, # Metering mode #p40
- 'LightSource' => Exif::SHORT, # Light source #p40-41
- 'Flash' => Exif::SHORT, # Flash #p41-42
- 'FocalLength' => Exif::RATIONAL, # Lens focal length
- 'SubjectArea' => Exif::SHORT, # Subject area
- 'FlashEnergy' => Exif::RATIONAL, # Flash energy
- 'SpatialFrequencyResponse' => Exif::UNDEFINED, # Spatial frequency response
- 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
- 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
- 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
- 'SubjectLocation' => Exif::SHORT, # Subject location
- 'ExposureIndex' => Exif::RATIONAL, # Exposure index
- 'SensingMethod' => Exif::SHORT, # Sensing method #p46
- 'FileSource' => Exif::UNDEFINED, # File source #p47
- 'SceneType' => Exif::UNDEFINED, # Scene type #p47
- 'CFAPattern' => Exif::UNDEFINED, # CFA pattern
- 'CustomRendered' => Exif::SHORT, # Custom image processing #p48
- 'ExposureMode' => Exif::SHORT, # Exposure mode #p48
- 'WhiteBalance' => Exif::SHORT, # White Balance #p49
- 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
- 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
- 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
- 'GainControl' => Exif::RATIONAL, # Scene control #p49-50
- 'Contrast' => Exif::SHORT, # Contrast #p50
- 'Saturation' => Exif::SHORT, # Saturation #p50
- 'Sharpness' => Exif::SHORT, # Sharpness #p50
- 'DeviceSettingDescription' => Exif::UNDEFINED, # Desice settings description
- 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51
- ),
-
- 'other' => array(
- 'ImageUniqueID' => Exif::ASCII, # Unique image ID
- ),
- ),
-
- # GPS Attribute Information (p52)
- 'gps' => array(
- 'GPSVersionID' => Exif::BYTE, # GPS tag version
- 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
- 'GPSLatitude' => Exif::RATIONAL, # Latitude
- 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
- 'GPSLongitude' => Exif::RATIONAL, # Longitude
- 'GPSAltitudeRef' => Exif::BYTE, # Altitude reference
- 'GPSAltitude' => Exif::RATIONAL, # Altitude
- 'GPSTimeStamp' => Exif::RATIONAL, # GPS time (atomic clock)
- 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
- 'GPSStatus' => Exif::ASCII, # Receiver status #p54
- 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
- 'GPSDOP' => Exif::RATIONAL, # Measurement precision
- 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55
- 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver
- 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55
- 'GPSTrack' => Exif::RATIONAL, # Direction of movement
- 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56
- 'GPSImgDirection' => Exif::RATIONAL, # Direction of image
- 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used
- 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56
- 'GPSDestLatitude' => Exif::RATIONAL, # Latitude destination
- 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57
- 'GPSDestLongitude' => Exif::RATIONAL, # Longitude of destination
- 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57
- 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination
- 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58
- 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination
- 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method
- 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area
- 'GPSDateStamp' => Exif::ASCII, # GPS date
- 'GPSDifferential' => Exif::SHORT, # GPS differential correction
- ),
- );
-
- $this->file = $file;
- $this->basename = wfBaseName( $this->file );
-
- $this->makeFlatExifTags();
-
- $this->debugFile( $this->basename, __FUNCTION__, true );
- wfSuppressWarnings();
- $data = exif_read_data( $this->file );
- wfRestoreWarnings();
- /**
- * exif_read_data() will return false on invalid input, such as
- * when somebody uploads a file called something.jpeg
- * containing random gibberish.
- */
- $this->mRawExifData = $data ? $data : array();
-
- $this->makeFilteredData();
- $this->makeFormattedData();
-
- $this->debugFile( __FUNCTION__, false );
- }
-
- /**#@+
- * @private
- */
- /**
- * Generate a flat list of the exif tags
- */
- function makeFlatExifTags() {
- $this->extractTags( $this->mExifTags );
- }
-
- /**
- * A recursing extractor function used by makeFlatExifTags()
- *
- * Note: This used to use an array_walk function, but it made PHP5
- * segfault, see `cvs diff -u -r 1.4 -r 1.5 Exif.php`
- */
- function extractTags( &$tagset ) {
- foreach( $tagset as $key => $val ) {
- if( is_array( $val ) ) {
- $this->extractTags( $val );
- } else {
- $this->mFlatExifTags[$key] = $val;
- }
- }
- }
-
- /**
- * Make $this->mFilteredExifData
- */
- function makeFilteredData() {
- $this->mFilteredExifData = $this->mRawExifData;
-
- foreach( $this->mFilteredExifData as $k => $v ) {
- if ( !in_array( $k, array_keys( $this->mFlatExifTags ) ) ) {
- $this->debug( $v, __FUNCTION__, "'$k' is not a valid Exif tag" );
- unset( $this->mFilteredExifData[$k] );
- }
- }
-
- foreach( $this->mFilteredExifData as $k => $v ) {
- if ( !$this->validate($k, $v) ) {
- $this->debug( $v, __FUNCTION__, "'$k' contained invalid data" );
- unset( $this->mFilteredExifData[$k] );
- }
- }
- }
-
- /**
- * @todo document
- */
- function makeFormattedData( ) {
- $format = new FormatExif( $this->getFilteredData() );
- $this->mFormattedExifData = $format->getFormattedData();
- }
- /**#@-*/
-
- /**#@+
- * @return array
- */
- /**
- * Get $this->mRawExifData
- */
- function getData() {
- return $this->mRawExifData;
- }
-
- /**
- * Get $this->mFilteredExifData
- */
- function getFilteredData() {
- return $this->mFilteredExifData;
- }
-
- /**
- * Get $this->mFormattedExifData
- */
- function getFormattedData() {
- return $this->mFormattedExifData;
- }
- /**#@-*/
-
- /**
- * The version of the output format
- *
- * Before the actual metadata information is saved in the database we
- * strip some of it since we don't want to save things like thumbnails
- * which usually accompany Exif data. This value gets saved in the
- * database along with the actual Exif data, and if the version in the
- * database doesn't equal the value returned by this function the Exif
- * data is regenerated.
- *
- * @return int
- */
- public static function version() {
- return 1; // We don't need no bloddy constants!
- }
-
- /**#@+
- * Validates if a tag value is of the type it should be according to the Exif spec
- *
- * @private
- *
- * @param $in Mixed: the input value to check
- * @return bool
- */
- function isByte( $in ) {
- if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 255 ) {
- $this->debug( $in, __FUNCTION__, true );
- return true;
- } else {
- $this->debug( $in, __FUNCTION__, false );
- return false;
- }
- }
-
- function isASCII( $in ) {
- if ( is_array( $in ) ) {
- return false;
- }
-
- if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
- $this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
- return false;
- }
-
- if ( preg_match( '/^\s*$/', $in ) ) {
- $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
- return false;
- }
-
- return true;
- }
-
- function isShort( $in ) {
- if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 65536 ) {
- $this->debug( $in, __FUNCTION__, true );
- return true;
- } else {
- $this->debug( $in, __FUNCTION__, false );
- return false;
- }
- }
-
- function isLong( $in ) {
- if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 4294967296 ) {
- $this->debug( $in, __FUNCTION__, true );
- return true;
- } else {
- $this->debug( $in, __FUNCTION__, false );
- return false;
- }
- }
-
- function isRational( $in ) {
- $m = array();
- if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
- return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
- } else {
- $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
- return false;
- }
- }
-
- function isUndefined( $in ) {
- if ( !is_array( $in ) && preg_match( '/^\d{4}$/', $in ) ) { // Allow ExifVersion and FlashpixVersion
- $this->debug( $in, __FUNCTION__, true );
- return true;
- } else {
- $this->debug( $in, __FUNCTION__, false );
- return false;
- }
- }
-
- function isSlong( $in ) {
- if ( $this->isLong( abs( $in ) ) ) {
- $this->debug( $in, __FUNCTION__, true );
- return true;
- } else {
- $this->debug( $in, __FUNCTION__, false );
- return false;
- }
- }
-
- function isSrational( $in ) {
- $m = array();
- if ( !is_array( $in ) && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
- return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
- } else {
- $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
- return false;
- }
- }
- /**#@-*/
-
- /**
- * Validates if a tag has a legal value according to the Exif spec
- *
- * @private
- *
- * @param $tag String: the tag to check.
- * @param $val Mixed: the value of the tag.
- * @return bool
- */
- function validate( $tag, $val ) {
- $debug = "tag is '$tag'";
- // Does not work if not typecast
- switch( (string)$this->mFlatExifTags[$tag] ) {
- case (string)Exif::BYTE:
- $this->debug( $val, __FUNCTION__, $debug );
- return $this->isByte( $val );
- case (string)Exif::ASCII:
- $this->debug( $val, __FUNCTION__, $debug );
- return $this->isASCII( $val );
- case (string)Exif::SHORT:
- $this->debug( $val, __FUNCTION__, $debug );
- return $this->isShort( $val );
- case (string)Exif::LONG:
- $this->debug( $val, __FUNCTION__, $debug );
- return $this->isLong( $val );
- case (string)Exif::RATIONAL:
- $this->debug( $val, __FUNCTION__, $debug );
- return $this->isRational( $val );
- case (string)Exif::UNDEFINED:
- $this->debug( $val, __FUNCTION__, $debug );
- return $this->isUndefined( $val );
- case (string)Exif::SLONG:
- $this->debug( $val, __FUNCTION__, $debug );
- return $this->isSlong( $val );
- case (string)Exif::SRATIONAL:
- $this->debug( $val, __FUNCTION__, $debug );
- return $this->isSrational( $val );
- case (string)Exif::SHORT.','.Exif::LONG:
- $this->debug( $val, __FUNCTION__, $debug );
- return $this->isShort( $val ) || $this->isLong( $val );
- default:
- $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
- return false;
- }
- }
-
- /**
- * Convenience function for debugging output
- *
- * @private
- *
- * @param $in Mixed:
- * @param $fname String:
- * @param $action Mixed: , default NULL.
- */
- function debug( $in, $fname, $action = null ) {
- if ( !$this->log ) {
- return;
- }
- $type = gettype( $in );
- $class = ucfirst( __CLASS__ );
- if ( $type === 'array' )
- $in = print_r( $in, true );
-
- if ( $action === true )
- wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n");
- elseif ( $action === false )
- wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n");
- elseif ( $action === null )
- wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n");
- else
- wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n");
- }
-
- /**
- * Convenience function for debugging output
- *
- * @private
- *
- * @param $fname String: the name of the function calling this function
- * @param $io Boolean: Specify whether we're beginning or ending
- */
- function debugFile( $fname, $io ) {
- if ( !$this->log ) {
- return;
- }
- $class = ucfirst( __CLASS__ );
- if ( $io ) {
- wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
- } else {
- wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'\n" );
- }
- }
-
-}
-
-/**
- * @todo document (e.g. one-sentence class-overview description)
- * @ingroup Media
- */
-class FormatExif {
- /**
- * The Exif data to format
- *
- * @var array
- * @private
- */
- var $mExif;
-
- /**
- * Constructor
- *
- * @param $exif Array: the Exif data to format ( as returned by
- * Exif::getFilteredData() )
- */
- function __construct( $exif ) {
- $this->mExif = $exif;
- }
-
- /**
- * Numbers given by Exif user agents are often magical, that is they
- * should be replaced by a detailed explanation depending on their
- * value which most of the time are plain integers. This function
- * formats Exif values into human readable form.
- *
- * @return array
- */
- function getFormattedData() {
- global $wgLang;
-
- $tags =& $this->mExif;
-
- $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
- unset( $tags['ResolutionUnit'] );
-
- foreach( $tags as $tag => $val ) {
- switch( $tag ) {
- case 'Compression':
- switch( $val ) {
- case 1: case 6:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'PhotometricInterpretation':
- switch( $val ) {
- case 2: case 6:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'Orientation':
- switch( $val ) {
- case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'PlanarConfiguration':
- switch( $val ) {
- case 1: case 2:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- // TODO: YCbCrSubSampling
- // TODO: YCbCrPositioning
-
- case 'XResolution':
- case 'YResolution':
- switch( $resolutionunit ) {
- case 2:
- $tags[$tag] = $this->msg( 'XYResolution', 'i', $this->formatNum( $val ) );
- break;
- case 3:
- $this->msg( 'XYResolution', 'c', $this->formatNum( $val ) );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- // TODO: YCbCrCoefficients #p27 (see annex E)
- case 'ExifVersion': case 'FlashpixVersion':
- $tags[$tag] = "$val"/100;
- break;
-
- case 'ColorSpace':
- switch( $val ) {
- case 1: case 'FFFF.H':
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'ComponentsConfiguration':
- switch( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 5: case 6:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'DateTime':
- case 'DateTimeOriginal':
- case 'DateTimeDigitized':
- if( $val == '0000:00:00 00:00:00' ) {
- $tags[$tag] = wfMsg('exif-unknowndate');
- } elseif( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) {
- $tags[$tag] = $wgLang->timeanddate( wfTimestamp(TS_MW, $val) );
- }
- break;
-
- case 'ExposureProgram':
- switch( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'SubjectDistance':
- $tags[$tag] = $this->msg( $tag, '', $this->formatNum( $val ) );
- break;
-
- case 'MeteringMode':
- switch( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'LightSource':
- switch( $val ) {
- case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
- case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
- case 21: case 22: case 23: case 24: case 255:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'Flash':
- $flashDecode = array(
- 'fired' => $val & bindec( '00000001' ),
- 'return' => ($val & bindec( '00000110' )) >> 1,
- 'mode' => ($val & bindec( '00011000' )) >> 3,
- 'function' => ($val & bindec( '00100000' )) >> 5,
- 'redeye' => ($val & bindec( '01000000' )) >> 6,
-// 'reserved' => ($val & bindec( '10000000' )) >> 7,
- );
-
- # We do not need to handle unknown values since all are used.
- foreach( $flashDecode as $subTag => $subValue ) {
- # We do not need any message for zeroed values.
- if( $subTag != 'fired' && $subValue == 0) {
- continue;
- }
- $fullTag = $tag . '-' . $subTag ;
- $flashMsgs[] = $this->msg( $fullTag, $subValue );
- }
- $tags[$tag] = $wgLang->commaList( $flashMsgs );
- break;
-
- case 'FocalPlaneResolutionUnit':
- switch( $val ) {
- case 2:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'SensingMethod':
- switch( $val ) {
- case 1: case 2: case 3: case 4: case 5: case 7: case 8:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'FileSource':
- switch( $val ) {
- case 3:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'SceneType':
- switch( $val ) {
- case 1:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'CustomRendered':
- switch( $val ) {
- case 0: case 1:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'ExposureMode':
- switch( $val ) {
- case 0: case 1: case 2:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'WhiteBalance':
- switch( $val ) {
- case 0: case 1:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'SceneCaptureType':
- switch( $val ) {
- case 0: case 1: case 2: case 3:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'GainControl':
- switch( $val ) {
- case 0: case 1: case 2: case 3: case 4:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'Contrast':
- switch( $val ) {
- case 0: case 1: case 2:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'Saturation':
- switch( $val ) {
- case 0: case 1: case 2:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'Sharpness':
- switch( $val ) {
- case 0: case 1: case 2:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'SubjectDistanceRange':
- switch( $val ) {
- case 0: case 1: case 2: case 3:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'GPSLatitudeRef':
- case 'GPSDestLatitudeRef':
- switch( $val ) {
- case 'N': case 'S':
- $tags[$tag] = $this->msg( 'GPSLatitude', $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'GPSLongitudeRef':
- case 'GPSDestLongitudeRef':
- switch( $val ) {
- case 'E': case 'W':
- $tags[$tag] = $this->msg( 'GPSLongitude', $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'GPSStatus':
- switch( $val ) {
- case 'A': case 'V':
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'GPSMeasureMode':
- switch( $val ) {
- case 2: case 3:
- $tags[$tag] = $this->msg( $tag, $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'GPSSpeedRef':
- case 'GPSDestDistanceRef':
- switch( $val ) {
- case 'K': case 'M': case 'N':
- $tags[$tag] = $this->msg( 'GPSSpeed', $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'GPSTrackRef':
- case 'GPSImgDirectionRef':
- case 'GPSDestBearingRef':
- switch( $val ) {
- case 'T': case 'M':
- $tags[$tag] = $this->msg( 'GPSDirection', $val );
- break;
- default:
- $tags[$tag] = $val;
- break;
- }
- break;
-
- case 'GPSDateStamp':
- $tags[$tag] = $wgLang->date( substr( $val, 0, 4 ) . substr( $val, 5, 2 ) . substr( $val, 8, 2 ) . '000000' );
- break;
-
- // This is not in the Exif standard, just a special
- // case for our purposes which enables wikis to wikify
- // the make, model and software name to link to their articles.
- case 'Make':
- case 'Model':
- case 'Software':
- $tags[$tag] = $this->msg( $tag, '', $val );
- break;
-
- case 'ExposureTime':
- // Show the pretty fraction as well as decimal version
- $tags[$tag] = wfMsg( 'exif-exposuretime-format',
- $this->formatFraction( $val ), $this->formatNum( $val ) );
- break;
-
- case 'FNumber':
- $tags[$tag] = wfMsg( 'exif-fnumber-format',
- $this->formatNum( $val ) );
- break;
-
- case 'FocalLength':
- $tags[$tag] = wfMsg( 'exif-focallength-format',
- $this->formatNum( $val ) );
- break;
-
- // Do not transform fields with pure text.
- // For some languages the formatNum() conversion results to wrong output like
- // foo,bar@example,com or foo٫bar@example٫com
- case 'ImageDescription':
- case 'Artist':
- case 'Copyright':
- $tags[$tag] = htmlspecialchars( $val );
- break;
- default:
- $tags[$tag] = $this->formatNum( $val );
- break;
- }
- }
-
- return $tags;
- }
-
- /**
- * Convenience function for getFormattedData()
- *
- * @private
- *
- * @param $tag String: the tag name to pass on
- * @param $val String: the value of the tag
- * @param $arg String: an argument to pass ($1)
- * @return string A wfMsg of "exif-$tag-$val" in lower case
- */
- function msg( $tag, $val, $arg = null ) {
- global $wgContLang;
-
- if ($val === '')
- $val = 'value';
- return wfMsg( $wgContLang->lc( "exif-$tag-$val" ), $arg );
- }
-
- /**
- * Format a number, convert numbers from fractions into floating point
- * numbers
- *
- * @private
- *
- * @param $num Mixed: the value to format
- * @return mixed A floating point number or whatever we were fed
- */
- function formatNum( $num ) {
- global $wgLang;
-
- $m = array();
- if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) )
- return $wgLang->formatNum( $m[2] != 0 ? $m[1] / $m[2] : $num );
- else
- return $wgLang->formatNum( $num );
- }
-
- /**
- * Format a rational number, reducing fractions
- *
- * @private
- *
- * @param $num Mixed: the value to format
- * @return mixed A floating point number or whatever we were fed
- */
- function formatFraction( $num ) {
- $m = array();
- if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) ) {
- $numerator = intval( $m[1] );
- $denominator = intval( $m[2] );
- $gcd = $this->gcd( $numerator, $denominator );
- if( $gcd != 0 ) {
- // 0 shouldn't happen! ;)
- return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd );
- }
- }
- return $this->formatNum( $num );
- }
-
- /**
- * Calculate the greatest common divisor of two integers.
- *
- * @param $a Integer: FIXME
- * @param $b Integer: FIXME
- * @return int
- * @private
- */
- function gcd( $a, $b ) {
- /*
- // http://en.wikipedia.org/wiki/Euclidean_algorithm
- // Recursive form would be:
- if( $b == 0 )
- return $a;
- else
- return gcd( $b, $a % $b );
- */
- while( $b != 0 ) {
- $remainder = $a % $b;
-
- // tail recursion...
- $a = $b;
- $b = $remainder;
- }
- return $a;
- }
-}
diff --git a/includes/Export.php b/includes/Export.php
index e7cd4d3f..87c735c1 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -35,11 +35,13 @@ class WikiExporter {
var $author_list = "" ;
var $dumpUploads = false;
+ var $dumpUploadFileContents = false;
const FULL = 1;
const CURRENT = 2;
const STABLE = 4; // extension defined
const LOGS = 8;
+ const RANGE = 16;
const BUFFER = 0;
const STREAM = 1;
@@ -55,7 +57,8 @@ class WikiExporter {
* main query is still running.
*
* @param $db Database
- * @param $history Mixed: one of WikiExporter::FULL or WikiExporter::CURRENT,
+ * @param $history Mixed: one of WikiExporter::FULL, WikiExporter::CURRENT,
+ * WikiExporter::RANGE or WikiExporter::STABLE,
* or an associative array:
* offset: non-inclusive offset at which to start the query
* limit: maximum number of rows to return
@@ -112,13 +115,28 @@ class WikiExporter {
*/
public function pagesByRange( $start, $end ) {
$condition = 'page_id >= ' . intval( $start );
- if( $end ) {
+ if ( $end ) {
$condition .= ' AND page_id < ' . intval( $end );
}
return $this->dumpFrom( $condition );
}
/**
+ * Dumps a series of page and revision records for those pages
+ * in the database with revisions falling within the rev_id range given.
+ * @param $start Int: inclusive lower limit (this id is included)
+ * @param $end Int: Exclusive upper limit (this id is not included)
+ * If 0, no upper limit.
+ */
+ public function revsByRange( $start, $end ) {
+ $condition = 'rev_id >= ' . intval( $start );
+ if ( $end ) {
+ $condition .= ' AND rev_id < ' . intval( $end );
+ }
+ return $this->dumpFrom( $condition );
+ }
+
+ /**
* @param $title Title
*/
public function pageByTitle( $title ) {
@@ -129,7 +147,7 @@ class WikiExporter {
public function pageByName( $name ) {
$title = Title::newFromText( $name );
- if( is_null( $title ) ) {
+ if ( is_null( $title ) ) {
throw new MWException( "Can't export invalid title" );
} else {
return $this->pageByTitle( $title );
@@ -137,7 +155,7 @@ class WikiExporter {
}
public function pagesByName( $names ) {
- foreach( $names as $name ) {
+ foreach ( $names as $name ) {
$this->pageByName( $name );
}
}
@@ -148,7 +166,7 @@ class WikiExporter {
public function logsByRange( $start, $end ) {
$condition = 'log_id >= ' . intval( $start );
- if( $end ) {
+ if ( $end ) {
$condition .= ' AND log_id < ' . intval( $end );
}
return $this->dumpFrom( $condition );
@@ -157,17 +175,23 @@ class WikiExporter {
# Generates the distinct list of authors of an article
# 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 ) {
+ protected function do_list_authors( $cond ) {
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, __METHOD__ );
- $resultset = $this->db->resultObject( $result );
- foreach ( $resultset as $row ) {
+ // rev_deleted
+
+ $res = $this->db->select(
+ array( 'page', 'revision' ),
+ array( 'DISTINCT rev_user_text', 'rev_user' ),
+ array(
+ $this->db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0',
+ $cond,
+ 'page_id = rev_id',
+ ),
+ __METHOD__
+ );
+
+ foreach ( $res as $row ) {
$this->author_list .= "<contributor>" .
"<username>" .
htmlentities( $row->rev_user_text ) .
@@ -184,27 +208,27 @@ class WikiExporter {
protected function dumpFrom( $cond = '' ) {
wfProfileIn( __METHOD__ );
# For logging dumps...
- if( $this->history & self::LOGS ) {
- if( $this->buffer == WikiExporter::STREAM ) {
+ if ( $this->history & self::LOGS ) {
+ if ( $this->buffer == WikiExporter::STREAM ) {
$prev = $this->db->bufferResults( false );
}
$where = array( 'user_id = log_user' );
# Hide private logs
$hideLogs = LogEventsList::getExcludeClause( $this->db );
- if( $hideLogs ) $where[] = $hideLogs;
+ if ( $hideLogs ) $where[] = $hideLogs;
# Add on any caller specified conditions
- if( $cond ) $where[] = $cond;
+ if ( $cond ) $where[] = $cond;
# Get logging table name for logging.* clause
- $logging = $this->db->tableName('logging');
- $result = $this->db->select( array('logging','user'),
+ $logging = $this->db->tableName( 'logging' );
+ $result = $this->db->select( array( 'logging', 'user' ),
array( "{$logging}.*", 'user_name' ), // grab the user name
$where,
__METHOD__,
- array( 'ORDER BY' => 'log_id', 'USE INDEX' => array('logging' => 'PRIMARY') )
+ array( 'ORDER BY' => 'log_id', 'USE INDEX' => array( 'logging' => 'PRIMARY' ) )
);
$wrapper = $this->db->resultObject( $result );
$this->outputLogStream( $wrapper );
- if( $this->buffer == WikiExporter::STREAM ) {
+ if ( $this->buffer == WikiExporter::STREAM ) {
$this->db->bufferResults( $prev );
}
# For page dumps...
@@ -213,11 +237,11 @@ class WikiExporter {
$opts = array( 'ORDER BY' => 'page_id ASC' );
$opts['USE INDEX'] = array();
$join = array();
- if( is_array( $this->history ) ) {
+ if ( is_array( $this->history ) ) {
# Time offset/limit for all pages/history...
$revJoin = 'page_id=rev_page';
# Set time order
- if( $this->history['dir'] == 'asc' ) {
+ if ( $this->history['dir'] == 'asc' ) {
$op = '>';
$opts['ORDER BY'] = 'rev_timestamp ASC';
} else {
@@ -225,54 +249,57 @@ class WikiExporter {
$opts['ORDER BY'] = 'rev_timestamp DESC';
}
# Set offset
- if( !empty( $this->history['offset'] ) ) {
+ if ( !empty( $this->history['offset'] ) ) {
$revJoin .= " AND rev_timestamp $op " .
$this->db->addQuotes( $this->db->timestamp( $this->history['offset'] ) );
}
- $join['revision'] = array('INNER JOIN',$revJoin);
+ $join['revision'] = array( 'INNER JOIN', $revJoin );
# Set query limit
- if( !empty( $this->history['limit'] ) ) {
+ if ( !empty( $this->history['limit'] ) ) {
$opts['LIMIT'] = intval( $this->history['limit'] );
}
- } elseif( $this->history & WikiExporter::FULL ) {
+ } elseif ( $this->history & WikiExporter::FULL ) {
# Full history dumps...
- $join['revision'] = array('INNER JOIN','page_id=rev_page');
- } elseif( $this->history & WikiExporter::CURRENT ) {
+ $join['revision'] = array( 'INNER JOIN', 'page_id=rev_page' );
+ } elseif ( $this->history & WikiExporter::CURRENT ) {
# Latest revision dumps...
- if( $this->list_authors && $cond != '' ) { // List authors, if so desired
- list($page,$revision) = $this->db->tableNamesN('page','revision');
- $this->do_list_authors( $page, $revision, $cond );
+ if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
+ $this->do_list_authors( $cond );
}
- $join['revision'] = array('INNER JOIN','page_id=rev_page AND page_latest=rev_id');
- } elseif( $this->history & WikiExporter::STABLE ) {
+ $join['revision'] = array( 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' );
+ } elseif ( $this->history & WikiExporter::STABLE ) {
# "Stable" revision dumps...
# Default JOIN, to be overridden...
- $join['revision'] = array('INNER JOIN','page_id=rev_page AND page_latest=rev_id');
+ $join['revision'] = array( 'INNER JOIN', 'page_id=rev_page AND page_latest=rev_id' );
# One, and only one hook should set this, and return false
- if( wfRunHooks( 'WikiExporter::dumpStableQuery', array(&$tables,&$opts,&$join) ) ) {
+ if ( wfRunHooks( 'WikiExporter::dumpStableQuery', array( &$tables, &$opts, &$join ) ) ) {
wfProfileOut( __METHOD__ );
- throw new MWException( __METHOD__." given invalid history dump type." );
+ throw new MWException( __METHOD__ . " given invalid history dump type." );
}
+ } elseif ( $this->history & WikiExporter::RANGE ) {
+ # Dump of revisions within a specified range
+ $join['revision'] = array( 'INNER JOIN', 'page_id=rev_page' );
+ $opts['ORDER BY'] = 'rev_page ASC, rev_id ASC';
} else {
# Uknown history specification parameter?
wfProfileOut( __METHOD__ );
- throw new MWException( __METHOD__." given invalid history dump type." );
+ throw new MWException( __METHOD__ . " given invalid history dump type." );
}
# Query optimization hacks
- if( $cond == '' ) {
+ if ( $cond == '' ) {
$opts[] = 'STRAIGHT_JOIN';
$opts['USE INDEX']['page'] = 'PRIMARY';
}
# Build text join options
- if( $this->text != WikiExporter::STUB ) { // 1-pass
+ if ( $this->text != WikiExporter::STUB ) { // 1-pass
$tables[] = 'text';
- $join['text'] = array('INNER JOIN','rev_text_id=old_id');
+ $join['text'] = array( 'INNER JOIN', 'rev_text_id=old_id' );
}
- if( $this->buffer == WikiExporter::STREAM ) {
+ if ( $this->buffer == WikiExporter::STREAM ) {
$prev = $this->db->bufferResults( false );
}
-
+
wfRunHooks( 'ModifyExportQuery',
array( $this->db, &$tables, &$cond, &$opts, &$join ) );
@@ -281,11 +308,11 @@ class WikiExporter {
$wrapper = $this->db->resultObject( $result );
# Output dump results
$this->outputPageStream( $wrapper );
- if( $this->list_authors ) {
+ if ( $this->list_authors ) {
$this->outputPageStream( $wrapper );
}
- if( $this->buffer == WikiExporter::STREAM ) {
+ if ( $this->buffer == WikiExporter::STREAM ) {
$this->db->bufferResults( $prev );
}
}
@@ -307,13 +334,13 @@ class WikiExporter {
protected function outputPageStream( $resultset ) {
$last = null;
foreach ( $resultset as $row ) {
- if( is_null( $last ) ||
+ if ( is_null( $last ) ||
$last->page_namespace != $row->page_namespace ||
$last->page_title != $row->page_title ) {
- if( isset( $last ) ) {
+ if ( isset( $last ) ) {
$output = '';
- if( $this->dumpUploads ) {
- $output .= $this->writer->writeUploads( $last );
+ if ( $this->dumpUploads ) {
+ $output .= $this->writer->writeUploads( $last, $this->dumpUploadFileContents );
}
$output .= $this->writer->closePage();
$this->sink->writeClosePage( $output );
@@ -325,17 +352,17 @@ class WikiExporter {
$output = $this->writer->writeRevision( $row );
$this->sink->writeRevision( $row, $output );
}
- if( isset( $last ) ) {
+ if ( isset( $last ) ) {
$output = '';
- if( $this->dumpUploads ) {
- $output .= $this->writer->writeUploads( $last );
+ if ( $this->dumpUploads ) {
+ $output .= $this->writer->writeUploads( $last, $this->dumpUploadFileContents );
}
$output .= $this->author_list;
$output .= $this->writer->closePage();
$this->sink->writeClosePage( $output );
}
}
-
+
protected function outputLogStream( $resultset ) {
foreach ( $resultset as $row ) {
$output = $this->writer->writeLogItem( $row );
@@ -348,7 +375,6 @@ class WikiExporter {
* @ingroup Dump
*/
class XmlDumpWriter {
-
/**
* Returns the export schema version.
* @return string
@@ -405,7 +431,7 @@ class XmlDumpWriter {
}
function homelink() {
- return Xml::element( 'base', array(), Title::newMainPage()->getFullUrl() );
+ return Xml::element( 'base', array(), Title::newMainPage()->getCanonicalUrl() );
}
function caseSetting() {
@@ -418,9 +444,9 @@ class XmlDumpWriter {
function namespaces() {
global $wgContLang;
$spaces = "<namespaces>\n";
- foreach( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
- $spaces .= ' ' .
- Xml::element( 'namespace',
+ foreach ( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
+ $spaces .= ' ' .
+ Xml::element( 'namespace',
array( 'key' => $ns,
'case' => MWNamespace::isCapitalized( $ns ) ? 'first-letter' : 'case-sensitive',
), $title ) . "\n";
@@ -432,12 +458,13 @@ class XmlDumpWriter {
/**
* Closes the output stream with the closing root element.
* Call when finished dumping things.
+ *
+ * @return string
*/
function closeStream() {
return "</mediawiki>\n";
}
-
/**
* Opens a <page> section on the output stream, with data
* from the given database row.
@@ -449,18 +476,18 @@ class XmlDumpWriter {
function openPage( $row ) {
$out = " <page>\n";
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $out .= ' ' . Xml::elementClean( 'title', array(), $title->getPrefixedText() ) . "\n";
+ $out .= ' ' . Xml::elementClean( 'title', array(), self::canonicalTitle( $title ) ) . "\n";
$out .= ' ' . Xml::element( 'id', array(), strval( $row->page_id ) ) . "\n";
- if( $row->page_is_redirect ) {
+ if ( $row->page_is_redirect ) {
$out .= ' ' . Xml::element( 'redirect', array() ) . "\n";
}
- if( $row->page_restrictions != '' ) {
+ if ( $row->page_restrictions != '' ) {
$out .= ' ' . Xml::element( 'restrictions', array(),
strval( $row->page_restrictions ) ) . "\n";
}
-
+
wfRunHooks( 'XmlDumpWriterOpenPage', array( $this, &$out, $row, $title ) );
-
+
return $out;
}
@@ -489,25 +516,25 @@ class XmlDumpWriter {
$out .= $this->writeTimestamp( $row->rev_timestamp );
- if( $row->rev_deleted & Revision::DELETED_USER ) {
+ if ( $row->rev_deleted & Revision::DELETED_USER ) {
$out .= " " . Xml::element( 'contributor', array( 'deleted' => 'deleted' ) ) . "\n";
} else {
$out .= $this->writeContributor( $row->rev_user, $row->rev_user_text );
}
- if( $row->rev_minor_edit ) {
+ if ( $row->rev_minor_edit ) {
$out .= " <minor/>\n";
}
- if( $row->rev_deleted & Revision::DELETED_COMMENT ) {
+ if ( $row->rev_deleted & Revision::DELETED_COMMENT ) {
$out .= " " . Xml::element( 'comment', array( 'deleted' => 'deleted' ) ) . "\n";
- } elseif( $row->rev_comment != '' ) {
+ } elseif ( $row->rev_comment != '' ) {
$out .= " " . Xml::elementClean( 'comment', null, strval( $row->rev_comment ) ) . "\n";
}
$text = '';
- if( $row->rev_deleted & Revision::DELETED_TEXT ) {
+ if ( $row->rev_deleted & Revision::DELETED_TEXT ) {
$out .= " " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
- } elseif( isset( $row->old_text ) ) {
+ } elseif ( isset( $row->old_text ) ) {
// Raw text from the database may have invalid chars
$text = strval( Revision::getRevisionText( $row ) );
$out .= " " . Xml::elementClean( 'text',
@@ -527,7 +554,7 @@ class XmlDumpWriter {
wfProfileOut( __METHOD__ );
return $out;
}
-
+
/**
* Dumps a <logitem> section on the output stream, with
* data filled in from the given database row.
@@ -544,26 +571,26 @@ class XmlDumpWriter {
$out .= $this->writeTimestamp( $row->log_timestamp );
- if( $row->log_deleted & LogPage::DELETED_USER ) {
+ if ( $row->log_deleted & LogPage::DELETED_USER ) {
$out .= " " . Xml::element( 'contributor', array( 'deleted' => 'deleted' ) ) . "\n";
} else {
$out .= $this->writeContributor( $row->log_user, $row->user_name );
}
- if( $row->log_deleted & LogPage::DELETED_COMMENT ) {
+ if ( $row->log_deleted & LogPage::DELETED_COMMENT ) {
$out .= " " . Xml::element( 'comment', array( 'deleted' => 'deleted' ) ) . "\n";
- } elseif( $row->log_comment != '' ) {
+ } elseif ( $row->log_comment != '' ) {
$out .= " " . Xml::elementClean( 'comment', null, strval( $row->log_comment ) ) . "\n";
}
-
+
$out .= " " . Xml::element( 'type', null, strval( $row->log_type ) ) . "\n";
$out .= " " . Xml::element( 'action', null, strval( $row->log_action ) ) . "\n";
- if( $row->log_deleted & LogPage::DELETED_ACTION ) {
+ if ( $row->log_deleted & LogPage::DELETED_ACTION ) {
$out .= " " . Xml::element( 'text', array( 'deleted' => 'deleted' ) ) . "\n";
} else {
$title = Title::makeTitle( $row->log_namespace, $row->log_title );
- $out .= " " . Xml::elementClean( 'logtitle', null, $title->getPrefixedText() ) . "\n";
+ $out .= " " . Xml::elementClean( 'logtitle', null, self::canonicalTitle( $title ) ) . "\n";
$out .= " " . Xml::elementClean( 'params',
array( 'xml:space' => 'preserve' ),
strval( $row->log_params ) ) . "\n";
@@ -582,7 +609,7 @@ class XmlDumpWriter {
function writeContributor( $id, $text ) {
$out = " <contributor>\n";
- if( $id ) {
+ if ( $id ) {
$out .= " " . Xml::elementClean( 'username', null, strval( $text ) ) . "\n";
$out .= " " . Xml::element( 'id', null, strval( $id ) ) . "\n";
} else {
@@ -595,32 +622,79 @@ class XmlDumpWriter {
/**
* Warning! This data is potentially inconsistent. :(
*/
- function writeUploads( $row ) {
- if( $row->page_namespace == NS_IMAGE ) {
- $img = wfFindFile( $row->page_title );
- if( $img ) {
+ function writeUploads( $row, $dumpContents = false ) {
+ if ( $row->page_namespace == NS_IMAGE ) {
+ $img = wfLocalFile( $row->page_title );
+ if ( $img && $img->exists() ) {
$out = '';
- foreach( array_reverse( $img->getHistory() ) as $ver ) {
- $out .= $this->writeUpload( $ver );
+ foreach ( array_reverse( $img->getHistory() ) as $ver ) {
+ $out .= $this->writeUpload( $ver, $dumpContents );
}
- $out .= $this->writeUpload( $img );
+ $out .= $this->writeUpload( $img, $dumpContents );
return $out;
}
}
return '';
}
- function writeUpload( $file ) {
+ /**
+ * @param $file File
+ * @param $dumpContents bool
+ * @return string
+ */
+ function writeUpload( $file, $dumpContents = false ) {
+ if ( $file->isOld() ) {
+ $archiveName = " " .
+ Xml::element( 'archivename', null, $file->getArchiveName() ) . "\n";
+ } else {
+ $archiveName = '';
+ }
+ if ( $dumpContents ) {
+ # Dump file as base64
+ # Uses only XML-safe characters, so does not need escaping
+ $contents = ' <contents encoding="base64">' .
+ chunk_split( base64_encode( file_get_contents( $file->getPath() ) ) ) .
+ " </contents>\n";
+ } else {
+ $contents = '';
+ }
return " <upload>\n" .
$this->writeTimestamp( $file->getTimestamp() ) .
$this->writeContributor( $file->getUser( 'id' ), $file->getUser( 'text' ) ) .
" " . Xml::elementClean( 'comment', null, $file->getDescription() ) . "\n" .
" " . Xml::element( 'filename', null, $file->getName() ) . "\n" .
- " " . Xml::element( 'src', null, $file->getFullUrl() ) . "\n" .
+ $archiveName .
+ " " . Xml::element( 'src', null, $file->getCanonicalUrl() ) . "\n" .
" " . Xml::element( 'size', null, $file->getSize() ) . "\n" .
+ " " . Xml::element( 'sha1base36', null, $file->getSha1() ) . "\n" .
+ " " . Xml::element( 'rel', null, $file->getRel() ) . "\n" .
+ $contents .
" </upload>\n";
}
+ /**
+ * Return prefixed text form of title, but using the content language's
+ * canonical namespace. This skips any special-casing such as gendered
+ * user namespaces -- which while useful, are not yet listed in the
+ * XML <siteinfo> data so are unsafe in export.
+ *
+ * @param Title $title
+ * @return string
+ */
+ public static function canonicalTitle( Title $title ) {
+ if ( $title->getInterwiki() ) {
+ return $title->getPrefixedText();
+ }
+
+ global $wgContLang;
+ $prefix = str_replace( '_', ' ', $wgContLang->getNsText( $title->getNamespace() ) );
+
+ if ($prefix !== '') {
+ $prefix .= ':';
+ }
+
+ return $prefix . $title->getText();
+ }
}
@@ -648,7 +722,7 @@ class DumpOutput {
function writeRevision( $rev, $string ) {
$this->write( $string );
}
-
+
function writeLogItem( $rev, $string ) {
$this->write( $string );
}
@@ -660,6 +734,36 @@ class DumpOutput {
function write( $string ) {
print $string;
}
+
+ /**
+ * Close the old file, move it to a specified name,
+ * and reopen new file with the old name. Use this
+ * for writing out a file in multiple pieces
+ * at specified checkpoints (e.g. every n hours).
+ * @param $newname mixed File name. May be a string or an array with one element
+ */
+ function closeRenameAndReopen( $newname ) {
+ return;
+ }
+
+ /**
+ * Close the old file, and move it to a specified name.
+ * Use this for the last piece of a file written out
+ * at specified checkpoints (e.g. every n hours).
+ * @param $newname mixed File name. May be a string or an array with one element
+ * @param $open bool If true, a new file with the old filename will be opened again for writing (default: false)
+ */
+ function closeAndRename( $newname, $open = false ) {
+ return;
+ }
+
+ /**
+ * Returns the name of the file or files which are
+ * being written to, if there are any.
+ */
+ function getFilenames() {
+ return NULL;
+ }
}
/**
@@ -667,15 +771,52 @@ class DumpOutput {
* @ingroup Dump
*/
class DumpFileOutput extends DumpOutput {
- var $handle;
+ protected $handle, $filename;
function __construct( $file ) {
$this->handle = fopen( $file, "wt" );
+ $this->filename = $file;
}
function write( $string ) {
fputs( $this->handle, $string );
}
+
+ function closeRenameAndReopen( $newname ) {
+ $this->closeAndRename( $newname, true );
+ }
+
+ function renameOrException( $newname ) {
+ if (! rename( $this->filename, $newname ) ) {
+ throw new MWException( __METHOD__ . ": rename of file {$this->filename} to $newname failed\n" );
+ }
+ }
+
+ function checkRenameArgCount( $newname ) {
+ if ( is_array( $newname ) ) {
+ if ( count( $newname ) > 1 ) {
+ throw new MWException( __METHOD__ . ": passed multiple arguments for rename of single file\n" );
+ } else {
+ $newname = $newname[0];
+ }
+ }
+ return $newname;
+ }
+
+ function closeAndRename( $newname, $open = false ) {
+ $newname = $this->checkRenameArgCount( $newname );
+ if ( $newname ) {
+ fclose( $this->handle );
+ $this->renameOrException( $newname );
+ if ( $open ) {
+ $this->handle = fopen( $this->filename, "wt" );
+ }
+ }
+ }
+
+ function getFilenames() {
+ return $this->filename;
+ }
}
/**
@@ -685,12 +826,45 @@ class DumpFileOutput extends DumpOutput {
* @ingroup Dump
*/
class DumpPipeOutput extends DumpFileOutput {
+ protected $command, $filename;
+
function __construct( $command, $file = null ) {
- if( !is_null( $file ) ) {
+ if ( !is_null( $file ) ) {
$command .= " > " . wfEscapeShellArg( $file );
}
- $this->handle = popen( $command, "w" );
+
+ $this->startCommand( $command );
+ $this->command = $command;
+ $this->filename = $file;
+ }
+
+ function startCommand( $command ) {
+ $spec = array(
+ 0 => array( "pipe", "r" ),
+ );
+ $pipes = array();
+ $this->procOpenResource = proc_open( $command, $spec, $pipes );
+ $this->handle = $pipes[0];
}
+
+ function closeRenameAndReopen( $newname ) {
+ $this->closeAndRename( $newname, true );
+ }
+
+ function closeAndRename( $newname, $open = false ) {
+ $newname = $this->checkRenameArgCount( $newname );
+ if ( $newname ) {
+ fclose( $this->handle );
+ proc_close( $this->procOpenResource );
+ $this->renameOrException( $newname );
+ if ( $open ) {
+ $command = $this->command;
+ $command .= " > " . wfEscapeShellArg( $this->filename );
+ $this->startCommand( $command );
+ }
+ }
+ }
+
}
/**
@@ -718,12 +892,37 @@ class DumpBZip2Output extends DumpPipeOutput {
* @ingroup Dump
*/
class Dump7ZipOutput extends DumpPipeOutput {
+ protected $filename;
+
function __construct( $file ) {
+ $command = $this->setup7zCommand( $file );
+ parent::__construct( $command );
+ $this->filename = $file;
+ }
+
+ function setup7zCommand( $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::__construct( $command );
+ return( $command );
+ }
+
+ function closeRenameAndReopen( $newname ) {
+ $this->closeAndRename( $newname, true );
+ }
+
+ function closeAndRename( $newname, $open = false ) {
+ $newname = $this->checkRenameArgCount( $newname );
+ if ( $newname ) {
+ fclose( $this->handle );
+ proc_close( $this->procOpenResource );
+ $this->renameOrException( $newname );
+ if ( $open ) {
+ $command = $this->setup7zCommand( $file );
+ $this->startCommand( $command );
+ }
+ }
}
}
@@ -750,27 +949,39 @@ class DumpFilter {
function writeOpenPage( $page, $string ) {
$this->sendingThisPage = $this->pass( $page, $string );
- if( $this->sendingThisPage ) {
+ if ( $this->sendingThisPage ) {
$this->sink->writeOpenPage( $page, $string );
}
}
function writeClosePage( $string ) {
- if( $this->sendingThisPage ) {
+ if ( $this->sendingThisPage ) {
$this->sink->writeClosePage( $string );
$this->sendingThisPage = false;
}
}
function writeRevision( $rev, $string ) {
- if( $this->sendingThisPage ) {
+ if ( $this->sendingThisPage ) {
$this->sink->writeRevision( $rev, $string );
}
}
-
+
function writeLogItem( $rev, $string ) {
$this->sink->writeRevision( $rev, $string );
- }
+ }
+
+ function closeRenameAndReopen( $newname ) {
+ $this->sink->closeRenameAndReopen( $newname );
+ }
+
+ function closeAndRename( $newname, $open = false ) {
+ $this->sink->closeAndRename( $newname, $open );
+ }
+
+ function getFilenames() {
+ return $this->sink->getFilenames();
+ }
/**
* Override for page-based filter types.
@@ -822,17 +1033,17 @@ class DumpNamespaceFilter extends DumpFilter {
"NS_CATEGORY" => NS_CATEGORY,
"NS_CATEGORY_TALK" => NS_CATEGORY_TALK );
- if( $param{0} == '!' ) {
+ if ( $param { 0 } == '!' ) {
$this->invert = true;
$param = substr( $param, 1 );
}
- foreach( explode( ',', $param ) as $key ) {
+ foreach ( explode( ',', $param ) as $key ) {
$key = trim( $key );
- if( isset( $constants[$key] ) ) {
+ if ( isset( $constants[$key] ) ) {
$ns = $constants[$key];
$this->namespaces[$ns] = true;
- } elseif( is_numeric( $key ) ) {
+ } elseif ( is_numeric( $key ) ) {
$ns = intval( $key );
$this->namespaces[$ns] = true;
} else {
@@ -861,7 +1072,7 @@ class DumpLatestFilter extends DumpFilter {
}
function writeClosePage( $string ) {
- if( $this->rev ) {
+ if ( $this->rev ) {
$this->sink->writeOpenPage( $this->page, $this->pageString );
$this->sink->writeRevision( $this->rev, $this->revString );
$this->sink->writeClosePage( $string );
@@ -873,7 +1084,7 @@ class DumpLatestFilter extends DumpFilter {
}
function writeRevision( $rev, $string ) {
- if( $rev->rev_id == $this->page->page_latest ) {
+ if ( $rev->rev_id == $this->page->page_latest ) {
$this->rev = $rev;
$this->revString = $string;
}
@@ -891,34 +1102,53 @@ class DumpMultiWriter {
}
function writeOpenStream( $string ) {
- for( $i = 0; $i < $this->count; $i++ ) {
+ for ( $i = 0; $i < $this->count; $i++ ) {
$this->sinks[$i]->writeOpenStream( $string );
}
}
function writeCloseStream( $string ) {
- for( $i = 0; $i < $this->count; $i++ ) {
+ for ( $i = 0; $i < $this->count; $i++ ) {
$this->sinks[$i]->writeCloseStream( $string );
}
}
function writeOpenPage( $page, $string ) {
- for( $i = 0; $i < $this->count; $i++ ) {
+ for ( $i = 0; $i < $this->count; $i++ ) {
$this->sinks[$i]->writeOpenPage( $page, $string );
}
}
function writeClosePage( $string ) {
- for( $i = 0; $i < $this->count; $i++ ) {
+ for ( $i = 0; $i < $this->count; $i++ ) {
$this->sinks[$i]->writeClosePage( $string );
}
}
function writeRevision( $rev, $string ) {
- for( $i = 0; $i < $this->count; $i++ ) {
+ for ( $i = 0; $i < $this->count; $i++ ) {
$this->sinks[$i]->writeRevision( $rev, $string );
}
}
+
+ function closeRenameAndReopen( $newnames ) {
+ $this->closeAndRename( $newnames, true );
+ }
+
+ function closeAndRename( $newnames, $open = false ) {
+ for ( $i = 0; $i < $this->count; $i++ ) {
+ $this->sinks[$i]->closeAndRename( $newnames[$i], $open );
+ }
+ }
+
+ function getFilenames() {
+ $filenames = array();
+ for ( $i = 0; $i < $this->count; $i++ ) {
+ $filenames[] = $this->sinks[$i]->getFilenames();
+ }
+ return $filenames;
+ }
+
}
function xmlsafe( $string ) {
diff --git a/includes/ExternalEdit.php b/includes/ExternalEdit.php
index 7109c1ac..bf97c1a5 100644
--- a/includes/ExternalEdit.php
+++ b/includes/ExternalEdit.php
@@ -19,39 +19,54 @@
*
*/
class ExternalEdit {
+ /**
+ * Title to perform the edit on
+ * @var Title
+ */
+ private $title;
- function __construct( $article, $mode ) {
- global $wgInputEncoding;
- $this->mArticle =& $article;
- $this->mTitle =& $article->mTitle;
- $this->mCharset = $wgInputEncoding;
- $this->mMode = $mode;
+ /**
+ * Mode of editing
+ * @var String
+ */
+ private $mode;
+
+ /**
+ * Constructor
+ * @param $title Title object we're performing the edit on
+ * @param $mode String What mode we're using. Only 'file' has any effect
+ */
+ public function __construct( $title, $mode ) {
+ $this->title = $title;
+ $this->mode = $mode;
}
- function edit() {
- global $wgOut, $wgScript, $wgScriptPath, $wgServer, $wgLang;
+ /**
+ * Output the information for the external editor
+ */
+ public function edit() {
+ global $wgOut, $wgScript, $wgScriptPath, $wgCanonicalServer, $wgLang;
$wgOut->disable();
- $name=$this->mTitle->getText();
- $pos=strrpos($name,".")+1;
- header ( "Content-type: application/x-external-editor; charset=".$this->mCharset );
- header( "Cache-control: no-cache" );
+ header( 'Content-type: application/x-external-editor; charset=utf-8' );
+ header( 'Cache-control: no-cache' );
# $type can be "Edit text", "Edit file" or "Diff text" at the moment
# See the protocol specifications at [[m:Help:External editors/Tech]] for
# details.
- if(!isset($this->mMode)) {
- $type="Edit text";
- $url=$this->mTitle->getFullURL("action=edit&internaledit=true");
+ if( $this->mode == "file" ) {
+ $type = "Edit file";
+ $image = wfLocalFile( $this->title );
+ $url = $image->getCanonicalURL();
+ $extension = $image->getExtension();
+ } else {
+ $type = "Edit text";
+ $url = $this->title->getCanonicalURL(
+ array( 'action' => 'edit', 'internaledit' => 'true' ) );
# *.wiki file extension is used by some editors for syntax
# highlighting, so we follow that convention
- $extension="wiki";
- } elseif($this->mMode=="file") {
- $type="Edit file";
- $image = wfLocalFile( $this->mTitle );
- $url = $image->getFullURL();
- $extension=substr($name, $pos);
+ $extension = "wiki";
}
- $special=$wgLang->getNsText(NS_SPECIAL);
+ $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
@@ -61,8 +76,8 @@ class ExternalEdit {
[Process]
Type=$type
Engine=MediaWiki
-Script={$wgServer}{$wgScript}
-Server={$wgServer}
+Script={$wgCanonicalServer}{$wgScript}
+Server={$wgCanonicalServer}
Path={$wgScriptPath}
Special namespace=$special
diff --git a/includes/ExternalStore.php b/includes/ExternalStore.php
index ddb40c32..3bee6ed8 100644
--- a/includes/ExternalStore.php
+++ b/includes/ExternalStore.php
@@ -67,7 +67,7 @@ class ExternalStore {
$class = 'ExternalStore' . ucfirst( $proto );
/* Any custom modules should be added to $wgAutoLoadClasses for on-demand loading */
- if( !class_exists( $class ) ) {
+ if( !MWInit::classExists( $class ) ) {
return false;
}
@@ -78,7 +78,10 @@ class ExternalStore {
* Store a data item to an external store, identified by a partial URL
* The protocol part is used to identify the class, the rest is passed to the
* class itself as a parameter.
- * @return The URL of the stored data item, or false on error
+ * @param $url
+ * @param $data
+ * @param $params array
+ * @return string|false The URL of the stored data item, or false on error
*/
static function insert( $url, $data, $params = array() ) {
list( $proto, $params ) = explode( '://', $url, 2 );
@@ -97,7 +100,7 @@ class ExternalStore {
*
* @param $data String
* @param $storageParams Array: associative array of parameters for the ExternalStore object.
- * @return The URL of the stored data item, or false on error
+ * @return string The URL of the stored data item, or false on error
*/
public static function insertToDefault( $data, $storageParams = array() ) {
global $wgDefaultExternalStore;
@@ -136,7 +139,12 @@ class ExternalStore {
}
}
- /** Like insertToDefault, but inserts on another wiki */
+ /**
+ * @param $data
+ * @param $wiki
+ *
+ * @return string
+ */
public static function insertToForeignDefault( $data, $wiki ) {
return self::insertToDefault( $data, array( 'wiki' => $wiki ) );
}
diff --git a/includes/ExternalStoreDB.php b/includes/ExternalStoreDB.php
index 877277a2..552c3109 100644
--- a/includes/ExternalStoreDB.php
+++ b/includes/ExternalStoreDB.php
@@ -18,7 +18,7 @@ class ExternalStoreDB {
*/
function &getLoadBalancer( $cluster ) {
$wiki = isset($this->mParams['wiki']) ? $this->mParams['wiki'] : false;
-
+
return wfGetLBFactory()->getExternalLB( $cluster, $wiki );
}
@@ -29,8 +29,18 @@ class ExternalStoreDB {
* @return DatabaseBase object
*/
function &getSlave( $cluster ) {
+ global $wgDefaultExternalStore;
+
$wiki = isset($this->mParams['wiki']) ? $this->mParams['wiki'] : false;
$lb =& $this->getLoadBalancer( $cluster );
+
+ if ( !in_array( "DB://" . $cluster, $wgDefaultExternalStore ) ) {
+ wfDebug( "read only external store" );
+ $lb->allowLagged(true);
+ } else {
+ wfDebug( "writable external store" );
+ }
+
return $lb->getConnection( DB_SLAVE, array(), $wiki );
}
@@ -139,8 +149,8 @@ class ExternalStoreDB {
function store( $cluster, $data ) {
$dbw = $this->getMaster( $cluster );
$id = $dbw->nextSequenceValue( 'blob_blob_id_seq' );
- $dbw->insert( $this->getTable( $dbw ),
- array( 'blob_id' => $id, 'blob_text' => $data ),
+ $dbw->insert( $this->getTable( $dbw ),
+ array( 'blob_id' => $id, 'blob_text' => $data ),
__METHOD__ );
$id = $dbw->insertId();
if ( !$id ) {
diff --git a/includes/ExternalUser.php b/includes/ExternalUser.php
index d1eff916..37716390 100644
--- a/includes/ExternalUser.php
+++ b/includes/ExternalUser.php
@@ -98,7 +98,7 @@ abstract class ExternalUser {
* This is a wrapper around newFromId().
*
* @param $user User
- * @return mixed ExternalUser or false
+ * @return ExternalUser|false
*/
public static function newFromUser( $user ) {
global $wgExternalAuthType;
@@ -293,7 +293,7 @@ abstract class ExternalUser {
* @return Mixed User if the account is linked, Null otherwise.
*/
public final function getLocalUser(){
- $dbr = wfGetDb( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
'external_user',
'*',
diff --git a/includes/FakeTitle.php b/includes/FakeTitle.php
index 21b49bde..515ff387 100644
--- a/includes/FakeTitle.php
+++ b/includes/FakeTitle.php
@@ -6,10 +6,6 @@
class FakeTitle extends Title {
function error() { throw new MWException( "Attempt to call member function of FakeTitle\n" ); }
- // PHP 5.1 method overload
- function __call( $name, $args ) { $this->error(); }
-
- // PHP <5.1 compatibility
function isLocal() { $this->error(); }
function isTrans() { $this->error(); }
function getText() { $this->error(); }
@@ -33,7 +29,7 @@ class FakeTitle extends Title {
function getSubpageText() { $this->error(); }
function getSubpageUrlForm() { $this->error(); }
function getPrefixedURL() { $this->error(); }
- function getFullURL( $query = '', $variant = false ) {$this->error(); }
+ function getFullURL( $query = '', $variant = false ) { $this->error(); }
function getLocalURL( $query = '', $variant = false ) { $this->error(); }
function getLinkUrl( $query = array(), $variant = false ) { $this->error(); }
function escapeLocalURL( $query = '' ) { $this->error(); }
diff --git a/includes/Fallback.php b/includes/Fallback.php
new file mode 100644
index 00000000..2cca1e81
--- /dev/null
+++ b/includes/Fallback.php
@@ -0,0 +1,200 @@
+<?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
+ *
+ */
+
+/**
+ * Fallback functions for PHP installed without mbstring support
+ */
+class Fallback {
+
+ public static function iconv( $from, $to, $string ) {
+ if ( substr( $to, -8 ) == '//IGNORE' ) {
+ $to = substr( $to, 0, strlen( $to ) - 8 );
+ }
+ if( strcasecmp( $from, $to ) == 0 ) {
+ return $string;
+ }
+ if( strcasecmp( $from, 'utf-8' ) == 0 ) {
+ return utf8_decode( $string );
+ }
+ if( strcasecmp( $to, 'utf-8' ) == 0 ) {
+ return utf8_encode( $string );
+ }
+ return $string;
+ }
+
+ /**
+ * Fallback implementation for mb_substr, hardcoded to UTF-8.
+ * Attempts to be at least _moderately_ efficient; best optimized
+ * for relatively small offset and count values -- about 5x slower
+ * than native mb_string in my testing.
+ *
+ * Larger offsets are still fairly efficient for Latin text, but
+ * can be up to 100x slower than native if the text is heavily
+ * multibyte and we have to slog through a few hundred kb.
+ *
+ * @param $str
+ * @param $start
+ * @param $count string
+ *
+ * @return string
+ */
+ public static function mb_substr( $str, $start, $count = 'end' ) {
+ if( $start != 0 ) {
+ $split = self::mb_substr_split_unicode( $str, intval( $start ) );
+ $str = substr( $str, $split );
+ }
+
+ if( $count !== 'end' ) {
+ $split = self::mb_substr_split_unicode( $str, intval( $count ) );
+ $str = substr( $str, 0, $split );
+ }
+
+ return $str;
+ }
+
+ public static 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" ) {
+ ++$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" ) {
+ ++$bytePos;
+ }
+ }
+ } else {
+ $splitPosX = $splitPos + 1;
+ $charPos = 0; // relative to end of string; we don't care about the actual char position here
+ $bytePos = $byteLen;
+ while( $bytePos > 0 && $charPos-- >= $splitPosX ) {
+ --$bytePos;
+ // Move past any tail bytes
+ while ( $bytePos > 0 && $str[$bytePos] >= "\x80" && $str[$bytePos] < "\xc0" ) {
+ --$bytePos;
+ }
+ }
+ }
+
+ return $bytePos;
+ }
+
+ /**
+ * Fallback implementation of mb_strlen, hardcoded to UTF-8.
+ * @param string $str
+ * @param string $enc optional encoding; ignored
+ * @return int
+ */
+ public static function mb_strlen( $str, $enc = '' ) {
+ $counts = count_chars( $str );
+ $total = 0;
+
+ // Count ASCII bytes
+ for( $i = 0; $i < 0x80; $i++ ) {
+ $total += $counts[$i];
+ }
+
+ // Count multibyte sequence heads
+ for( $i = 0xc0; $i < 0xff; $i++ ) {
+ $total += $counts[$i];
+ }
+ return $total;
+ }
+
+
+ /**
+ * Fallback implementation of mb_strpos, hardcoded to UTF-8.
+ * @param $haystack String
+ * @param $needle String
+ * @param $offset String: optional start position
+ * @param $encoding String: optional encoding; ignored
+ * @return int
+ */
+ public static function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
+ $needle = preg_quote( $needle, '/' );
+
+ $ar = array();
+ preg_match( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
+
+ if( isset( $ar[0][1] ) ) {
+ return $ar[0][1];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Fallback implementation of mb_strrpos, hardcoded to UTF-8.
+ * @param $haystack String
+ * @param $needle String
+ * @param $offset String: optional start position
+ * @param $encoding String: optional encoding; ignored
+ * @return int
+ */
+ public static 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 );
+
+ 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;
+ }
+ }
+
+ /**
+ * Fallback implementation of stream_resolve_include_path()
+ * Native stream_resolve_include_path is available for PHP 5 >= 5.3.2
+ * @param $filename String
+ * @return String
+ */
+ public static function stream_resolve_include_path( $filename ) {
+ $pathArray = explode( PATH_SEPARATOR, get_include_path() );
+ foreach ( $pathArray as $path ) {
+ $fullFilename = $path . DIRECTORY_SEPARATOR . $filename;
+ if ( file_exists( $fullFilename ) ) {
+ return $fullFilename;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/includes/Feed.php b/includes/Feed.php
index bc20a9dc..ef33c78f 100644
--- a/includes/Feed.php
+++ b/includes/Feed.php
@@ -36,9 +36,8 @@
* @ingroup Feed
*/
class FeedItem {
- /**#@+
- * @var string
- * @private
+ /**
+ * @var Title
*/
var $Title = 'Wiki';
var $Description = '';
@@ -47,12 +46,11 @@ class FeedItem {
var $Author = '';
var $UniqueId = '';
var $RSSIsPermalink;
- /**#@-*/
/**
* Constructor
*
- * @param $Title String: Item's title
+ * @param $Title String|Title Item's title
* @param $Description String
* @param $Url String: URL uniquely designating the item.
* @param $Date String: Item's date
@@ -71,6 +69,15 @@ class FeedItem {
}
/**
+ * Get the last touched timestamp
+ *
+ * @return String last-touched timestamp
+ */
+ public function getLastMod() {
+ return $this->Title->getTouched();
+ }
+
+ /**
* Encode $string so that it can be safely embedded in a XML document
*
* @param $string String: string to encode
@@ -99,7 +106,7 @@ class FeedItem {
* @param $uniqueId String: unique id for the item
* @param $RSSisPermalink Boolean: set to true if the guid (unique id) is a permalink (RSS feeds only)
*/
- public function setUniqueId($uniqueId, $RSSisPermalink = False) {
+ public function setUniqueId($uniqueId, $RSSisPermalink = false) {
$this->UniqueId = $uniqueId;
$this->RSSIsPermalink = $RSSisPermalink;
}
@@ -114,6 +121,16 @@ class FeedItem {
}
/**
+ * Get the DB prefixed title
+ *
+ * @return String the prefixed title, with underscores and
+ * any interwiki and namespace prefixes
+ */
+ public function getDBPrefixedTitle() {
+ return $this->Title->getPrefixedDBKey();
+ }
+
+ /**
* Get the URL of this item; already xml-encoded
*
* @return String
@@ -222,12 +239,15 @@ class ChannelFeed extends FeedItem {
* but can also be called separately.
*/
public function httpHeaders() {
- global $wgOut;
+ global $wgOut, $wgVaryOnXFP;
# We take over from $wgOut, excepting its cache header info
$wgOut->disable();
$mimetype = $this->contentType();
header( "Content-type: $mimetype; charset=UTF-8" );
+ if ( $wgVaryOnXFP ) {
+ $wgOut->addVaryHeader( 'X-Forwarded-Proto' );
+ }
$wgOut->sendCacheControl();
}
@@ -256,7 +276,7 @@ class ChannelFeed extends FeedItem {
$this->httpHeaders();
echo '<?xml version="1.0"?>' . "\n";
echo '<?xml-stylesheet type="text/css" href="' .
- htmlspecialchars( wfExpandUrl( "$wgStylePath/common/feed.css?$wgStyleVersion" ) ) .
+ htmlspecialchars( wfExpandUrl( "$wgStylePath/common/feed.css?$wgStyleVersion", PROTO_CURRENT ) ) .
'"?' . ">\n";
}
}
@@ -288,7 +308,7 @@ class RSSFeed extends ChannelFeed {
?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title><?php print $this->getTitle() ?></title>
- <link><?php print $this->getUrl() ?></link>
+ <link><?php print wfExpandUrl( $this->getUrl(), PROTO_CURRENT ) ?></link>
<description><?php print $this->getDescription() ?></description>
<language><?php print $this->getLanguage() ?></language>
<generator>MediaWiki <?php print $wgVersion ?></generator>
@@ -304,12 +324,12 @@ class RSSFeed extends ChannelFeed {
?>
<item>
<title><?php print $item->getTitle() ?></title>
- <link><?php print $item->getUrl() ?></link>
+ <link><?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ) ?></link>
<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 }?>
- <?php if( $item->getComments() ) { ?><comments><?php print $item->getComments() ?></comments><?php }?>
+ <?php if( $item->getComments() ) { ?><comments><?php print wfExpandUrl( $item->getComments(), PROTO_CURRENT ) ?></comments><?php }?>
</item>
<?php
}
@@ -348,8 +368,8 @@ class AtomFeed extends ChannelFeed {
?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="<?php print $this->getLanguage() ?>">
<id><?php print $this->getFeedId() ?></id>
<title><?php print $this->getTitle() ?></title>
- <link rel="self" type="application/atom+xml" href="<?php print $this->getSelfUrl() ?>"/>
- <link rel="alternate" type="text/html" href="<?php print $this->getUrl() ?>"/>
+ <link rel="self" type="application/atom+xml" href="<?php print wfExpandUrl( $this->getSelfUrl(), PROTO_CURRENT ) ?>"/>
+ <link rel="alternate" type="text/html" href="<?php print wfExpandUrl( $this->getUrl(), PROTO_CURRENT ) ?>"/>
<updated><?php print $this->formatTime( wfTimestampNow() ) ?>Z</updated>
<subtitle><?php print $this->getDescription() ?></subtitle>
<generator>MediaWiki <?php print $wgVersion ?></generator>
@@ -390,7 +410,7 @@ class AtomFeed extends ChannelFeed {
<entry>
<id><?php print $item->getUniqueId() ?></id>
<title><?php print $item->getTitle() ?></title>
- <link rel="alternate" type="<?php print $wgMimeType ?>" href="<?php print $item->getUrl() ?>"/>
+ <link rel="alternate" type="<?php print $wgMimeType ?>" href="<?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ) ?>"/>
<?php if( $item->getDate() ) { ?>
<updated><?php print $this->formatTime( $item->getDate() ) ?>Z</updated>
<?php } ?>
@@ -399,9 +419,9 @@ class AtomFeed extends ChannelFeed {
<?php if( $item->getAuthor() ) { ?><author><name><?php print $item->getAuthor() ?></name></author><?php }?>
</entry>
-<?php /* FIXME need to add comments
+<?php /* @todo FIXME: Need to add comments
<?php if( $item->getComments() ) { ?><dc:comment><?php print $item->getComments() ?></dc:comment><?php }?>
- */
+ */
}
/**
diff --git a/includes/FeedUtils.php b/includes/FeedUtils.php
index 9daffc12..4502c3a8 100644
--- a/includes/FeedUtils.php
+++ b/includes/FeedUtils.php
@@ -112,7 +112,7 @@ class FeedUtils {
# $wgLang->date( $timestamp ),
# $wgLang->time( $timestamp ) ),
# wfMsg( 'currentrev' ) );
-
+
// Don't bother generating the diff if we won't be able to show it
if ( $wgFeedDiffCutoff > 0 ) {
$de = new DifferenceEngine( $title, $oldid, $newid );
diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php
index 030330bb..515768ff 100644
--- a/includes/FileDeleteForm.php
+++ b/includes/FileDeleteForm.php
@@ -118,25 +118,21 @@ class FileDeleteForm {
} else {
$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();
- }
- $status = $file->delete( $reason, $suppress );
- if( $status->ok ) {
- $dbw->commit();
- wfRunHooks( 'ArticleDeleteComplete', array( &$article, &$wgUser, $reason, $id ) );
- } else {
- $dbw->rollback();
- }
+ // delete the associated article first
+ if( $article->doDeleteArticle( $reason, $suppress, $id, false ) ) {
+ global $wgRequest;
+ if ( $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn() ) {
+ WatchAction::doWatch( $title, $wgUser );
+ } elseif ( $title->userIsWatching() ) {
+ WatchAction::doUnwatch( $title, $wgUser );
+ }
+ $status = $file->delete( $reason, $suppress );
+ if( $status->ok ) {
+ $dbw->commit();
+ } else {
+ $dbw->rollback();
}
}
} catch ( MWException $e ) {
@@ -257,15 +253,15 @@ class FileDeleteForm {
return wfMsgExt(
"{$message}-old", # To ensure grep will find them: 'filedelete-intro-old', 'filedelete-nofile-old', 'filedelete-success-old'
'parse',
- $this->title->getText(),
+ wfEscapeWikiText( $this->title->getText() ),
$wgLang->date( $this->getTimestamp(), true ),
$wgLang->time( $this->getTimestamp(), true ),
- wfExpandUrl( $this->file->getArchiveUrl( $this->oldimage ) ) );
+ wfExpandUrl( $this->file->getArchiveUrl( $this->oldimage ), PROTO_CURRENT ) );
} else {
return wfMsgExt(
$message,
'parse',
- $this->title->getText()
+ wfEscapeWikiText( $this->title->getText() )
);
}
}
diff --git a/includes/FileRevertForm.php b/includes/FileRevertForm.php
deleted file mode 100644
index 47084aad..00000000
--- a/includes/FileRevertForm.php
+++ /dev/null
@@ -1,180 +0,0 @@
-<?php
-
-/**
- * File reversion user interface
- *
- * @ingroup Media
- * @author Rob Church <robchur@gmail.com>
- */
-class FileRevertForm {
-
- protected $title = null;
- protected $file = null;
- protected $archiveName = '';
- protected $timestamp = false;
- protected $oldFile;
-
- /**
- * Constructor
- *
- * @param $file File we're reverting
- */
- public function __construct( $file ) {
- $this->title = $file->getTitle();
- $this->file = $file;
- }
-
- /**
- * Fulfil the request; shows the form or reverts the file,
- * pending authentication, confirmation, etc.
- */
- public function execute() {
- global $wgOut, $wgRequest, $wgUser, $wgLang;
- $this->setHeaders();
-
- if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- } elseif( !$wgUser->isLoggedIn() ) {
- $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
- return;
- } elseif( !$this->title->userCan( 'edit' ) || !$this->title->userCan( 'upload' ) ) {
- // The standard read-only thing doesn't make a whole lot of sense
- // here; surely it should show the image or something? -- RC
- $article = new Article( $this->title );
- $wgOut->readOnlyPage( $article->getContent(), true );
- return;
- } elseif( $wgUser->isBlocked() ) {
- $wgOut->blockedPage();
- return;
- }
-
- $this->archiveName = $wgRequest->getText( 'oldimage' );
- $token = $wgRequest->getText( 'wpEditToken' );
- if( !$this->isValidOldSpec() ) {
- $wgOut->showUnexpectedValueError( 'oldimage', htmlspecialchars( $this->archiveName ) );
- return;
- }
-
- if( !$this->haveOldVersion() ) {
- $wgOut->addHTML( wfMsgExt( 'filerevert-badversion', 'parse' ) );
- $wgOut->returnToMain( false, $this->title );
- return;
- }
-
- // Perform the reversion if appropriate
- if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $token, $this->archiveName ) ) {
- $source = $this->file->getArchiveVirtualUrl( $this->archiveName );
- $comment = $wgRequest->getText( 'wpComment' );
- // TODO: Preserve file properties from database instead of reloading from file
- $status = $this->file->upload( $source, $comment, $comment );
- if( $status->isGood() ) {
- $wgOut->addHTML( wfMsgExt( 'filerevert-success', 'parse', $this->title->getText(),
- $wgLang->date( $this->getTimestamp(), true ),
- $wgLang->time( $this->getTimestamp(), true ),
- wfExpandUrl( $this->file->getArchiveUrl( $this->archiveName ) ) ) );
- $wgOut->returnToMain( false, $this->title );
- } else {
- $wgOut->addWikiText( $status->getWikiText() );
- }
- return;
- }
-
- // Show the form
- $this->showForm();
- }
-
- /**
- * Show the confirmation form
- */
- protected function showForm() {
- global $wgOut, $wgUser, $wgLang, $wgContLang;
- $timestamp = $this->getTimestamp();
-
- $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getAction() ) );
- $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 ),
- wfExpandUrl( $this->file->getArchiveUrl( $this->archiveName ) ) );
- $form .= '<p>' . Xml::inputLabel( wfMsg( 'filerevert-comment' ), 'wpComment', 'wpComment',
- 60, wfMsgForContent( 'filerevert-defaultcomment',
- $wgContLang->date( $timestamp, false, false ), $wgContLang->time( $timestamp, false, false ) ) ) . '</p>';
- $form .= '<p>' . Xml::submitButton( wfMsg( 'filerevert-submit' ) ) . '</p>';
- $form .= '</fieldset>';
- $form .= '</form>';
-
- $wgOut->addHTML( $form );
- }
-
- /**
- * Set headers, titles and other bits
- */
- protected function setHeaders() {
- global $wgOut, $wgUser;
- $wgOut->setPageTitle( wfMsg( 'filerevert', $this->title->getText() ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setSubtitle( wfMsg(
- 'filerevert-backlink',
- $wgUser->getSkin()->link(
- $this->title,
- null,
- array(),
- array(),
- array( 'known', 'noclasses' )
- )
- ) );
- }
-
- /**
- * Is the provided `oldimage` value valid?
- *
- * @return bool
- */
- protected function isValidOldSpec() {
- return strlen( $this->archiveName ) >= 16
- && strpos( $this->archiveName, '/' ) === false
- && strpos( $this->archiveName, '\\' ) === false;
- }
-
- /**
- * Does the provided `oldimage` value correspond
- * to an existing, local, old version of this file?
- *
- * @return bool
- */
- protected function haveOldVersion() {
- return $this->getOldFile()->exists();
- }
-
- /**
- * Prepare the form action
- *
- * @return string
- */
- protected function getAction() {
- $q = array();
- $q[] = 'action=revert';
- $q[] = 'oldimage=' . urlencode( $this->archiveName );
- return $this->title->getLocalUrl( implode( '&', $q ) );
- }
-
- /**
- * Extract the timestamp of the old version
- *
- * @return string
- */
- protected function getTimestamp() {
- if( $this->timestamp === false ) {
- $this->timestamp = $this->getOldFile()->getTimestamp();
- }
- return $this->timestamp;
- }
-
- protected function getOldFile() {
- if ( !isset( $this->oldFile ) ) {
- $this->oldFile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $this->title, $this->archiveName );
- }
- return $this->oldFile;
- }
-}
diff --git a/includes/ForkController.php b/includes/ForkController.php
index e5b44c2b..d87dfb1e 100644
--- a/includes/ForkController.php
+++ b/includes/ForkController.php
@@ -115,15 +115,17 @@ class ForkController {
}
protected function prepareEnvironment() {
- global $wgCaches, $wgMemc;
+ global $wgMemc;
// Don't share DB or memcached connections
wfGetLBFactory()->destroyInstance();
- $wgCaches = array();
+ ObjectCache::clear();
unset( $wgMemc );
}
/**
* Fork a number of worker processes.
+ *
+ * return string
*/
protected function forkWorkers( $numProcs ) {
$this->prepareEnvironment();
diff --git a/includes/FormOptions.php b/includes/FormOptions.php
index 2442a330..b668ff46 100644
--- a/includes/FormOptions.php
+++ b/includes/FormOptions.php
@@ -1,19 +1,38 @@
<?php
/**
* Helper class to keep track of options when mixing links and form elements.
+ * @todo This badly need some examples and tests :-)
*
- * Copyright © 2008, Niklas Laxström
+ * Copyright © 2008, Niklas Laxstiröm
+ *
+ * Copyright © 2011, Ashar Voultoiz
*
* @author Niklas Laxström
+ * @author Ashar Voultoiz
*/
class FormOptions implements ArrayAccess {
- const AUTO = -1; // ! Automatically detects simple data types
+ /** @name Type constants
+ * Used internally to map an option value to a WebRequest accessor
+ */
+ /* @{ */
+ /** Mark value for automatic detection (for simple data types only) */
+ const AUTO = -1;
+ /** String type, maps guessType() to WebRequest::getText() */
const STRING = 0;
+ /** Integer type, maps guessType() to WebRequest::getInt() */
const INT = 1;
+ /** Boolean type, maps guessType() to WebRequest::getBool() */
const BOOL = 2;
- const INTNULL = 3; // ! Useful for namespace selector
-
+ /** Integer type or null, maps to WebRequest::getIntOrNull()
+ * This is useful for the namespace selector.
+ */
+ const INTNULL = 3;
+ /* @} */
+
+ /**
+ * @todo Document!
+ */
protected $options = array();
# Setting up
@@ -38,6 +57,16 @@ class FormOptions implements ArrayAccess {
unset( $this->options[$name] );
}
+ /**
+ * Used to find out which type the data is.
+ * All types are defined in the 'Type constants' section of this class
+ * Please note we do not support detection of INTNULL MediaWiki type
+ * which will be assumed as INT if the data is an integer.
+ *
+ * @param $data Mixed: value to guess type for
+ * @exception MWException Unsupported datatype
+ * @return Type constant
+ */
public static function guessType( $data ) {
if ( is_bool( $data ) ) {
return self::BOOL;
@@ -52,6 +81,13 @@ class FormOptions implements ArrayAccess {
# Handling values
+ /**
+ * Verify the given option name exist.
+ *
+ * @param $name String: option name
+ * @param $strict Boolean: throw an exception when the option does not exist (default false)
+ * @return Boolean: true if option exist, false otherwise
+ */
public function validateName( $name, $strict = false ) {
if ( !isset( $this->options[$name] ) ) {
if ( $strict ) {
@@ -63,6 +99,14 @@ class FormOptions implements ArrayAccess {
return true;
}
+ /**
+ * Use to set the value of an option.
+ *
+ * @param $name String: option name
+ * @param $value Mixed: value for the option
+ * @param $force Boolean: whether to set the value when it is equivalent to the default value for this option (default false).
+ * @return null
+ */
public function setValue( $name, $value, $force = false ) {
$this->validateName( $name, true );
@@ -74,12 +118,24 @@ class FormOptions implements ArrayAccess {
}
}
+ /**
+ * Get the value for the given option name.
+ * Internally use getValueReal()
+ *
+ * @param $name String: option name
+ * @return Mixed
+ */
public function getValue( $name ) {
$this->validateName( $name, true );
return $this->getValueReal( $this->options[$name] );
}
+ /**
+ * @todo Document
+ * @param $option Array: array structure describing the option
+ * @return Mixed. Value or the default value if it is null
+ */
protected function getValueReal( $option ) {
if ( $option['value'] !== null ) {
return $option['value'];
@@ -88,11 +144,22 @@ class FormOptions implements ArrayAccess {
}
}
+ /**
+ * Delete the option value.
+ * This will make future calls to getValue() return the default value.
+ * @param $name String: option name
+ * @return null
+ */
public function reset( $name ) {
$this->validateName( $name, true );
$this->options[$name]['value'] = null;
}
+ /**
+ * @todo Document
+ * @param $name String: option name
+ * @return null
+ */
public function consumeValue( $name ) {
$this->validateName( $name, true );
$this->options[$name]['consumed'] = true;
@@ -100,6 +167,11 @@ class FormOptions implements ArrayAccess {
return $this->getValueReal( $this->options[$name] );
}
+ /**
+ * @todo Document
+ * @param $names Array: array of option names
+ * @return null
+ */
public function consumeValues( /*Array*/ $names ) {
$out = array();
@@ -112,8 +184,16 @@ class FormOptions implements ArrayAccess {
return $out;
}
- # Validating values
-
+ /**
+ * Validate and set an option integer value
+ * The value will be altered to fit in the range.
+ *
+ * @param $name String: option name
+ * @param $min Int: minimum value
+ * @param $max Int: maximum value
+ * @exception MWException Option is not of type int
+ * @return null
+ */
public function validateIntBounds( $name, $min, $max ) {
$this->validateName( $name, true );
@@ -127,8 +207,11 @@ class FormOptions implements ArrayAccess {
$this->setValue( $name, $value );
}
- # Getting the data out for use
-
+ /**
+ * Getting the data out for use
+ * @param $all Boolean: whether to include unchanged options (default: false)
+ * @return Array
+ */
public function getUnconsumedValues( $all = false ) {
$values = array();
@@ -143,6 +226,10 @@ class FormOptions implements ArrayAccess {
return $values;
}
+ /**
+ * Return options modified as an array ( name => value )
+ * @return Array
+ */
public function getChangedValues() {
$values = array();
@@ -155,6 +242,10 @@ class FormOptions implements ArrayAccess {
return $values;
}
+ /**
+ * Format options to an array ( name => value)
+ * @return Array
+ */
public function getAllValues() {
$values = array();
@@ -195,20 +286,26 @@ class FormOptions implements ArrayAccess {
}
}
- /* ArrayAccess methods */
+ /** @name ArrayAccess functions
+ * Those function implements PHP ArrayAccess interface
+ * @see http://php.net/manual/en/class.arrayaccess.php
+ */
+ /* @{ */
+ /** Whether option exist*/
public function offsetExists( $name ) {
return isset( $this->options[$name] );
}
-
+ /** Retrieve an option value */
public function offsetGet( $name ) {
return $this->getValue( $name );
}
-
+ /** Set an option to given value */
public function offsetSet( $name, $value ) {
$this->setValue( $name, $value );
}
-
+ /** Delete the option */
public function offsetUnset( $name ) {
$this->delete( $name );
}
+ /* @} */
}
diff --git a/includes/GenderCache.php b/includes/GenderCache.php
new file mode 100644
index 00000000..a17ac024
--- /dev/null
+++ b/includes/GenderCache.php
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * Caches user genders when needed to use correct namespace aliases.
+ * @author Niklas Laxström
+ * @since 1.18
+ */
+class GenderCache {
+ protected $cache = array();
+ protected $default;
+ protected $misses = 0;
+ protected $missLimit = 1000;
+
+ /**
+ * @return GenderCache
+ */
+ public static function singleton() {
+ static $that = null;
+ if ( $that === null ) {
+ $that = new self();
+ }
+ return $that;
+ }
+
+ protected function __construct() {}
+
+ /**
+ * Returns the default gender option in this wiki.
+ * @return String
+ */
+ protected function getDefault() {
+ if ( $this->default === null ) {
+ $this->default = User::getDefaultOption( 'gender' );
+ }
+ return $this->default;
+ }
+
+ /**
+ * Returns the gender for given username.
+ * @param $username String: username
+ * @param $caller String: the calling method
+ * @return String
+ */
+ public function getGenderOf( $username, $caller = '' ) {
+ global $wgUser;
+
+ $username = strtr( $username, '_', ' ' );
+ if ( !isset( $this->cache[$username] ) ) {
+
+ if ( $this->misses >= $this->missLimit && $wgUser->getName() !== $username ) {
+ if( $this->misses === $this->missLimit ) {
+ $this->misses++;
+ wfDebug( __METHOD__ . ": too many misses, returning default onwards\n" );
+ }
+ return $this->getDefault();
+
+ } else {
+ $this->misses++;
+ if ( !User::isValidUserName( $username ) ) {
+ $this->cache[$username] = $this->getDefault();
+ } else {
+ $this->doQuery( $username, $caller );
+ }
+ }
+
+ }
+
+ /* Undefined if there is a valid username which for some reason doesn't
+ * exist in the database.
+ */
+ return isset( $this->cache[$username] ) ? $this->cache[$username] : $this->getDefault();
+ }
+
+ /**
+ * Wrapper for doQuery that processes raw LinkBatch data.
+ *
+ * @param $data
+ * @param $caller
+ */
+ public function doLinkBatch( $data, $caller = '' ) {
+ $users = array();
+ foreach ( $data as $ns => $pagenames ) {
+ if ( !MWNamespace::hasGenderDistinction( $ns ) ) continue;
+ foreach ( array_keys( $pagenames ) as $username ) {
+ if ( isset( $this->cache[$username] ) ) continue;
+ $users[$username] = true;
+ }
+ }
+
+ $this->doQuery( array_keys( $users ), $caller );
+ }
+
+ /**
+ * Preloads genders for given list of users.
+ * @param $users List|String: usernames
+ * @param $caller String: the calling method
+ */
+ public function doQuery( $users, $caller = '' ) {
+ $default = $this->getDefault();
+
+ foreach ( (array) $users as $index => $value ) {
+ $name = strtr( $value, '_', ' ' );
+ if ( isset( $this->cache[$name] ) ) {
+ // Skip users whose gender setting we already know
+ unset( $users[$index] );
+ } else {
+ $users[$index] = $name;
+ // For existing users, this value will be overwritten by the correct value
+ $this->cache[$name] = $default;
+ }
+ }
+
+ if ( count( $users ) === 0 ) {
+ return false;
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $table = array( 'user', 'user_properties' );
+ $fields = array( 'user_name', 'up_value' );
+ $conds = array( 'user_name' => $users );
+ $joins = array( 'user_properties' =>
+ array( 'LEFT JOIN', array( 'user_id = up_user', 'up_property' => 'gender' ) ) );
+
+ $comment = __METHOD__;
+ if ( strval( $caller ) !== '' ) {
+ $comment .= "/$caller";
+ }
+ $res = $dbr->select( $table, $fields, $conds, $comment, $joins, $joins );
+
+ foreach ( $res as $row ) {
+ $this->cache[$row->user_name] = $row->up_value ? $row->up_value : $default;
+ }
+ }
+
+}
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index 2c35568b..3424211f 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -8,188 +8,68 @@ if ( !defined( 'MEDIAWIKI' ) ) {
die( "This file is part of MediaWiki, it is not a valid entry point" );
}
-require_once dirname( __FILE__ ) . '/normal/UtfNormalUtil.php';
-
// Hide compatibility functions from Doxygen
/// @cond
/**
* Compatibility functions
*
- * We support PHP 5.1.x and up.
+ * We support PHP 5.2.3 and up.
* Re-implementations of newer functions or functions in non-standard
* PHP extensions may be included here.
*/
+
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.
+ /** @codeCoverageIgnore */
function iconv( $from, $to, $string ) {
- if ( substr( $to, -8 ) == '//IGNORE' ) {
- $to = substr( $to, 0, strlen( $to ) - 8 );
- }
- if( strcasecmp( $from, $to ) == 0 ) {
- return $string;
- }
- if( strcasecmp( $from, 'utf-8' ) == 0 ) {
- return utf8_decode( $string );
- }
- if( strcasecmp( $to, 'utf-8' ) == 0 ) {
- return utf8_encode( $string );
- }
- return $string;
+ return Fallback::iconv( $from, $to, $string );
}
}
if ( !function_exists( 'mb_substr' ) ) {
- /**
- * Fallback implementation for mb_substr, hardcoded to UTF-8.
- * Attempts to be at least _moderately_ efficient; best optimized
- * for relatively small offset and count values -- about 5x slower
- * than native mb_string in my testing.
- *
- * Larger offsets are still fairly efficient for Latin text, but
- * can be up to 100x slower than native if the text is heavily
- * multibyte and we have to slog through a few hundred kb.
- */
+ /** @codeCoverageIgnore */
function mb_substr( $str, $start, $count='end' ) {
- if( $start != 0 ) {
- $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;
+ return Fallback::mb_substr( $str, $start, $count );
}
+ /** @codeCoverageIgnore */
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" ) {
- ++$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" ) {
- ++$bytePos;
- }
- }
- } else {
- $splitPosX = $splitPos + 1;
- $charPos = 0; // relative to end of string; we don't care about the actual char position here
- $bytePos = $byteLen;
- while( $bytePos > 0 && $charPos-- >= $splitPosX ) {
- --$bytePos;
- // Move past any tail bytes
- while ( $bytePos > 0 && $str{$bytePos} >= "\x80" && $str{$bytePos} < "\xc0" ) {
- --$bytePos;
- }
- }
- }
-
- return $bytePos;
+ return Fallback::mb_substr_split_unicode( $str, $splitPos );
}
}
if ( !function_exists( 'mb_strlen' ) ) {
- /**
- * Fallback implementation of mb_strlen, hardcoded to UTF-8.
- * @param string $str
- * @param string $enc optional encoding; ignored
- * @return int
- */
+ /** @codeCoverageIgnore */
function mb_strlen( $str, $enc = '' ) {
- $counts = count_chars( $str );
- $total = 0;
-
- // Count ASCII bytes
- for( $i = 0; $i < 0x80; $i++ ) {
- $total += $counts[$i];
- }
-
- // Count multibyte sequence heads
- for( $i = 0xc0; $i < 0xff; $i++ ) {
- $total += $counts[$i];
- }
- return $total;
+ return Fallback::mb_strlen( $str, $enc );
}
}
-
if( !function_exists( 'mb_strpos' ) ) {
- /**
- * Fallback implementation of mb_strpos, hardcoded to UTF-8.
- * @param $haystack String
- * @param $needle String
- * @param $offset String: optional start position
- * @param $encoding String: optional encoding; ignored
- * @return int
- */
+ /** @codeCoverageIgnore */
function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
- $needle = preg_quote( $needle, '/' );
-
- $ar = array();
- preg_match( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
-
- if( isset( $ar[0][1] ) ) {
- return $ar[0][1];
- } else {
- return false;
- }
+ return Fallback::mb_strpos( $haystack, $needle, $offset, $encoding );
}
+
}
if( !function_exists( 'mb_strrpos' ) ) {
- /**
- * Fallback implementation of mb_strrpos, hardcoded to UTF-8.
- * @param $haystack String
- * @param $needle String
- * @param $offset String: optional start position
- * @param $encoding String: optional encoding; ignored
- * @return int
- */
+ /** @codeCoverageIgnore */
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 );
-
- 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;
- }
+ return Fallback::mb_strrpos( $haystack, $needle, $offset, $encoding );
}
}
+
// Support for Wietse Venema's taint feature
if ( !function_exists( 'istainted' ) ) {
+ /** @codeCoverageIgnore */
function istainted( $var ) {
return 0;
}
+ /** @codeCoverageIgnore */
function taint( $var, $level = 0 ) {}
+ /** @codeCoverageIgnore */
function untaint( $var, $level = 0 ) {}
define( 'TC_HTML', 1 );
define( 'TC_SHELL', 1 );
@@ -197,25 +77,20 @@ 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
-
/**
* Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
*/
function wfArrayDiff2( $a, $b ) {
return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
}
+
+/**
+ * @param $a
+ * @param $b
+ * @return int
+ */
function wfArrayDiff2_cmp( $a, $b ) {
if ( !is_array( $a ) ) {
return strcmp( $a, $b );
@@ -235,12 +110,148 @@ function wfArrayDiff2_cmp( $a, $b ) {
}
/**
- * Seed Mersenne Twister
- * No-op for compatibility; only necessary in PHP < 4.2.0
- * @deprecated. Remove in 1.18
+ * Array lookup
+ * Returns an array where the values in the first array are replaced by the
+ * values in the second array with the corresponding keys
+ *
+ * @param $a Array
+ * @param $b Array
+ * @return array
+ */
+function wfArrayLookup( $a, $b ) {
+ return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
+}
+
+/**
+ * Appends to second array if $value differs from that in $default
+ *
+ * @param $key String|Int
+ * @param $value Mixed
+ * @param $default Mixed
+ * @param $changed Array to alter
+ */
+function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
+ if ( is_null( $changed ) ) {
+ throw new MWException( 'GlobalFunctions::wfAppendToArrayIfNotDefault got null' );
+ }
+ if ( $default[$key] !== $value ) {
+ $changed[$key] = $value;
+ }
+}
+
+/**
+ * Backwards array plus for people who haven't bothered to read the PHP manual
+ * XXX: will not darn your socks for you.
+ *
+ * @param $array1 Array
+ * @param [$array2, [...]] Arrays
+ * @return Array
+ */
+function wfArrayMerge( $array1/* ... */ ) {
+ $args = func_get_args();
+ $args = array_reverse( $args, true );
+ $out = array();
+ foreach ( $args as $arg ) {
+ $out += $arg;
+ }
+ return $out;
+}
+
+/**
+ * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
+ * e.g.
+ * wfMergeErrorArrays(
+ * array( array( 'x' ) ),
+ * array( array( 'x', '2' ) ),
+ * array( array( 'x' ) ),
+ * array( array( 'y') )
+ * );
+ * returns:
+ * array(
+ * array( 'x', '2' ),
+ * array( 'x' ),
+ * array( 'y' )
+ * )
+ * @param varargs
+ * @return Array
+ */
+function wfMergeErrorArrays( /*...*/ ) {
+ $args = func_get_args();
+ $out = array();
+ foreach ( $args as $errors ) {
+ foreach ( $errors as $params ) {
+ # @todo FIXME: Sometimes get nested arrays for $params,
+ # which leads to E_NOTICEs
+ $spec = implode( "\t", $params );
+ $out[$spec] = $params;
+ }
+ }
+ return array_values( $out );
+}
+
+/**
+ * Insert array into another array after the specified *KEY*
+ *
+ * @param $array Array: The array.
+ * @param $insert Array: The array to insert.
+ * @param $after Mixed: The key to insert after
+ * @return Array
+ */
+function wfArrayInsertAfter( $array, $insert, $after ) {
+ // Find the offset of the element to insert after.
+ $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 );
+
+ $output = $before + $insert + $after;
+
+ return $output;
+}
+
+/**
+ * Recursively converts the parameter (an object) to an array with the same data
+ *
+ * @param $objOrArray Object|Array
+ * @param $recursive Bool
+ * @return Array
+ */
+function wfObjectToArray( $objOrArray, $recursive = true ) {
+ $array = array();
+ 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;
+}
+
+/**
+ * Wrapper around array_map() which also taints variables
+ *
+ * @param $function Callback
+ * @param $input Array
+ * @return Array
*/
-function wfSeedRandom() {
- wfDeprecated(__FUNCTION__);
+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;
}
/**
@@ -283,6 +294,11 @@ function wfRandom() {
*/
function wfUrlencode( $s ) {
static $needle;
+ if ( is_null( $s ) ) {
+ $needle = null;
+ return;
+ }
+
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 ) ) {
@@ -301,6 +317,365 @@ function wfUrlencode( $s ) {
}
/**
+ * This function takes two arrays as input, and returns a CGI-style string, e.g.
+ * "days=7&limit=100". Options in the first array override options in the second.
+ * Options set to "" will not be output.
+ *
+ * @param $array1 Array ( String|Array )
+ * @param $array2 Array ( String|Array )
+ * @param $prefix String
+ * @return String
+ */
+function wfArrayToCGI( $array1, $array2 = null, $prefix = '' ) {
+ if ( !is_null( $array2 ) ) {
+ $array1 = $array1 + $array2;
+ }
+
+ $cgi = '';
+ foreach ( $array1 as $key => $value ) {
+ if ( $value !== '' ) {
+ if ( $cgi != '' ) {
+ $cgi .= '&';
+ }
+ if ( $prefix !== '' ) {
+ $key = $prefix . "[$key]";
+ }
+ if ( is_array( $value ) ) {
+ $firstTime = true;
+ foreach ( $value as $k => $v ) {
+ $cgi .= $firstTime ? '' : '&';
+ if ( is_array( $v ) ) {
+ $cgi .= wfArrayToCGI( $v, null, $key . "[$k]" );
+ } else {
+ $cgi .= urlencode( $key . "[$k]" ) . '=' . urlencode( $v );
+ }
+ $firstTime = false;
+ }
+ } else {
+ if ( is_object( $value ) ) {
+ $value = $value->__toString();
+ }
+ $cgi .= urlencode( $key ) . '=' . urlencode( $value );
+ }
+ }
+ }
+ return $cgi;
+}
+
+/**
+ * This is the logical opposite of wfArrayToCGI(): it accepts a query string as
+ * its argument and returns the same string in array form. This allows compa-
+ * tibility with legacy functions that accept raw query strings instead of nice
+ * arrays. Of course, keys and values are urldecode()d. Don't try passing in-
+ * valid query strings, or it will explode.
+ *
+ * @param $query String: query string
+ * @return array Array version of input
+ */
+function wfCgiToArray( $query ) {
+ if ( isset( $query[0] ) && $query[0] == '?' ) {
+ $query = substr( $query, 1 );
+ }
+ $bits = explode( '&', $query );
+ $ret = array();
+ foreach ( $bits as $bit ) {
+ if ( $bit === '' ) {
+ continue;
+ }
+ list( $key, $value ) = explode( '=', $bit );
+ $key = urldecode( $key );
+ $value = urldecode( $value );
+ if ( strpos( $key, '[' ) !== false ) {
+ $keys = array_reverse( explode( '[', $key ) );
+ $key = array_pop( $keys );
+ $temp = $value;
+ foreach ( $keys as $k ) {
+ $k = substr( $k, 0, -1 );
+ $temp = array( $k => $temp );
+ }
+ if ( isset( $ret[$key] ) ) {
+ $ret[$key] = array_merge( $ret[$key], $temp );
+ } else {
+ $ret[$key] = $temp;
+ }
+ } else {
+ $ret[$key] = $value;
+ }
+ }
+ return $ret;
+}
+
+/**
+ * Append a query string to an existing URL, which may or may not already
+ * have query string parameters already. If so, they will be combined.
+ *
+ * @param $url String
+ * @param $query Mixed: string or associative array
+ * @return string
+ */
+function wfAppendQuery( $url, $query ) {
+ if ( is_array( $query ) ) {
+ $query = wfArrayToCGI( $query );
+ }
+ if( $query != '' ) {
+ if( false === strpos( $url, '?' ) ) {
+ $url .= '?';
+ } else {
+ $url .= '&';
+ }
+ $url .= $query;
+ }
+ return $url;
+}
+
+/**
+ * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
+ * is correct.
+ *
+ * The meaning of the PROTO_* constants is as follows:
+ * PROTO_HTTP: Output a URL starting with http://
+ * PROTO_HTTPS: Output a URL starting with https://
+ * PROTO_RELATIVE: Output a URL starting with // (protocol-relative URL)
+ * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending on which protocol was used for the current incoming request
+ * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer. For protocol-relative URLs, use the protocol of $wgCanonicalServer
+ * PROTO_INTERNAL: Like PROTO_CANONICAL, but uses $wgInternalServer instead of $wgCanonicalServer
+ *
+ * @todo this won't work with current-path-relative URLs
+ * like "subdir/foo.html", etc.
+ *
+ * @param $url String: either fully-qualified or a local path + query
+ * @param $defaultProto Mixed: one of the PROTO_* constants. Determines the protocol to use if $url or $wgServer is protocol-relative
+ * @return string Fully-qualified URL
+ */
+function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
+ global $wgServer, $wgCanonicalServer, $wgInternalServer;
+ $serverUrl = $wgServer;
+ if ( $defaultProto === PROTO_CANONICAL ) {
+ $serverUrl = $wgCanonicalServer;
+ }
+ // Make $wgInternalServer fall back to $wgServer if not set
+ if ( $defaultProto === PROTO_INTERNAL && $wgInternalServer !== false ) {
+ $serverUrl = $wgInternalServer;
+ }
+ if ( $defaultProto === PROTO_CURRENT ) {
+ $defaultProto = WebRequest::detectProtocol() . '://';
+ }
+
+ // Analyze $serverUrl to obtain its protocol
+ $bits = wfParseUrl( $serverUrl );
+ $serverHasProto = $bits && $bits['scheme'] != '';
+
+ if ( $defaultProto === PROTO_CANONICAL || $defaultProto === PROTO_INTERNAL ) {
+ if ( $serverHasProto ) {
+ $defaultProto = $bits['scheme'] . '://';
+ } else {
+ // $wgCanonicalServer or $wgInternalServer doesn't have a protocol. This really isn't supposed to happen
+ // Fall back to HTTP in this ridiculous case
+ $defaultProto = PROTO_HTTP;
+ }
+ }
+
+ $defaultProtoWithoutSlashes = substr( $defaultProto, 0, -2 );
+
+ if( substr( $url, 0, 2 ) == '//' ) {
+ return $defaultProtoWithoutSlashes . $url;
+ } elseif( substr( $url, 0, 1 ) == '/' ) {
+ // If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes, otherwise leave it alone
+ return ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url;
+ } else {
+ return $url;
+ }
+}
+
+/**
+ * Returns a regular expression of url protocols
+ *
+ * @param $includeProtocolRelative bool If false, remove '//' from the returned protocol list.
+ * DO NOT USE this directy, use wfUrlProtocolsWithoutProtRel() instead
+ * @return String
+ */
+function wfUrlProtocols( $includeProtocolRelative = true ) {
+ global $wgUrlProtocols;
+
+ // Cache return values separately based on $includeProtocolRelative
+ static $withProtRel = null, $withoutProtRel = null;
+ $cachedValue = $includeProtocolRelative ? $withProtRel : $withoutProtRel;
+ if ( !is_null( $cachedValue ) ) {
+ return $cachedValue;
+ }
+
+ // Support old-style $wgUrlProtocols strings, for backwards compatibility
+ // with LocalSettings files from 1.5
+ if ( is_array( $wgUrlProtocols ) ) {
+ $protocols = array();
+ foreach ( $wgUrlProtocols as $protocol ) {
+ // Filter out '//' if !$includeProtocolRelative
+ if ( $includeProtocolRelative || $protocol !== '//' ) {
+ $protocols[] = preg_quote( $protocol, '/' );
+ }
+ }
+
+ $retval = implode( '|', $protocols );
+ } else {
+ // Ignore $includeProtocolRelative in this case
+ // This case exists for pre-1.6 compatibility, and we can safely assume
+ // that '//' won't appear in a pre-1.6 config because protocol-relative
+ // URLs weren't supported until 1.18
+ $retval = $wgUrlProtocols;
+ }
+
+ // Cache return value
+ if ( $includeProtocolRelative ) {
+ $withProtRel = $retval;
+ } else {
+ $withoutProtRel = $retval;
+ }
+ return $retval;
+}
+
+/**
+ * Like wfUrlProtocols(), but excludes '//' from the protocol list. Use this if
+ * you need a regex that matches all URL protocols but does not match protocol-
+ * relative URLs
+ */
+function wfUrlProtocolsWithoutProtRel() {
+ return wfUrlProtocols( false );
+}
+
+/**
+ * parse_url() work-alike, but non-broken. Differences:
+ *
+ * 1) Does not raise warnings on bad URLs (just returns false)
+ * 2) Handles protocols that don't use :// (e.g., mailto: and news: , as well as protocol-relative URLs) correctly
+ * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2))
+ *
+ * @param $url String: a URL to parse
+ * @return Array: bits of the URL in an associative array, per PHP docs
+ */
+function wfParseUrl( $url ) {
+ global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
+
+ // Protocol-relative URLs are handled really badly by parse_url(). It's so bad that the easiest
+ // way to handle them is to just prepend 'http:' and strip the protocol out later
+ $wasRelative = substr( $url, 0, 2 ) == '//';
+ if ( $wasRelative ) {
+ $url = "http:$url";
+ }
+ wfSuppressWarnings();
+ $bits = parse_url( $url );
+ wfRestoreWarnings();
+ if ( !$bits ) {
+ return false;
+ }
+
+ // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
+ if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
+ $bits['delimiter'] = '://';
+ } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
+ $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'] ) ) {
+ $bits['host'] = $bits['path'];
+ $bits['path'] = '';
+ }
+ } else {
+ return false;
+ }
+
+ /* Provide an empty host for eg. file:/// urls (see bug 28627) */
+ if ( !isset( $bits['host'] ) ) {
+ $bits['host'] = '';
+
+ /* parse_url loses the third / for file:///c:/ urls (but not on variants) */
+ if ( substr( $bits['path'], 0, 1 ) !== '/' ) {
+ $bits['path'] = '/' . $bits['path'];
+ }
+ }
+
+ // If the URL was protocol-relative, fix scheme and delimiter
+ if ( $wasRelative ) {
+ $bits['scheme'] = '';
+ $bits['delimiter'] = '//';
+ }
+ return $bits;
+}
+
+/**
+ * Make URL indexes, appropriate for the el_index field of externallinks.
+ *
+ * @param $url String
+ * @return array
+ */
+function wfMakeUrlIndexes( $url ) {
+ $bits = wfParseUrl( $url );
+
+ // Reverse the labels in the hostname, convert to lower case
+ // For emails reverse domainpart only
+ if ( $bits['scheme'] == 'mailto' ) {
+ $mailparts = explode( '@', $bits['host'], 2 );
+ if ( count( $mailparts ) === 2 ) {
+ $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
+ } else {
+ // No domain specified, don't mangle it
+ $domainpart = '';
+ }
+ $reversedHost = $domainpart . '@' . $mailparts[0];
+ } else {
+ $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
+ }
+ // Add an extra dot to the end
+ // Why? Is it in wrong place in mailto links?
+ if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
+ $reversedHost .= '.';
+ }
+ // Reconstruct the pseudo-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['path'] ) ) {
+ $index .= $bits['path'];
+ } else {
+ $index .= '/';
+ }
+ if ( isset( $bits['query'] ) ) {
+ $index .= '?' . $bits['query'];
+ }
+ if ( isset( $bits['fragment'] ) ) {
+ $index .= '#' . $bits['fragment'];
+ }
+
+ if ( $prot == '' ) {
+ return array( "http:$index", "https:$index" );
+ } else {
+ return array( $index );
+ }
+}
+
+/**
+ * Check whether a given URL has a domain that occurs in a given set of domains
+ * @param $url string URL
+ * @param $domains array Array of domains (strings)
+ * @return bool True if the host part of $url ends in one of the strings in $domains
+ */
+function wfMatchesDomainList( $url, $domains ) {
+ $bits = wfParseUrl( $url );
+ if ( is_array( $bits ) && isset( $bits['host'] ) ) {
+ foreach ( (array)$domains as $domain ) {
+ // FIXME: This gives false positives. http://nds-nl.wikipedia.org will match nl.wikipedia.org
+ // We should use something that interprets dots instead
+ if ( substr( $bits['host'], -strlen( $domain ) ) === $domain ) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
* Sends a line to the debug log if enabled or, optionally, to a comment in output.
* In normal operation this is a NOP.
*
@@ -316,44 +691,61 @@ function wfUrlencode( $s ) {
function wfDebug( $text, $logonly = false ) {
global $wgOut, $wgDebugLogFile, $wgDebugComments, $wgProfileOnly, $wgDebugRawPage;
global $wgDebugLogPrefix, $wgShowDebug;
- static $recursion = 0;
static $cache = array(); // Cache of unoutputted messages
$text = wfDebugTimer() . $text;
- # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
- if ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' && !$wgDebugRawPage ) {
+ if ( !$wgDebugRawPage && wfIsDebugRawPage() ) {
return;
}
if ( ( $wgDebugComments || $wgShowDebug ) && !$logonly ) {
$cache[] = $text;
- if ( !isset( $wgOut ) ) {
- return;
+ if ( isset( $wgOut ) && is_object( $wgOut ) ) {
+ // add the message and any cached messages to the output
+ array_map( array( $wgOut, 'debug' ), $cache );
+ $cache = array();
}
- if ( !StubObject::isRealObject( $wgOut ) ) {
- if ( $recursion ) {
- return;
- }
- $recursion++;
- $wgOut->_unstub();
- $recursion--;
+ }
+ if ( wfRunHooks( 'Debug', array( $text, null /* no log group */ ) ) ) {
+ if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
+ # Strip unprintables; they can switch terminal modes when binary data
+ # gets dumped, which is pretty annoying.
+ $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
+ $text = $wgDebugLogPrefix . $text;
+ wfErrorLog( $text, $wgDebugLogFile );
}
+ }
+}
- // add the message and possible cached ones to the output
- array_map( array( $wgOut, 'debug' ), $cache );
- $cache = array();
+/**
+ * Returns true if debug logging should be suppressed if $wgDebugRawPage = false
+ */
+function wfIsDebugRawPage() {
+ static $cache;
+ if ( $cache !== null ) {
+ return $cache;
}
- if ( $wgDebugLogFile != '' && !$wgProfileOnly ) {
- # Strip unprintables; they can switch terminal modes when binary data
- # gets dumped, which is pretty annoying.
- $text = preg_replace( '![\x00-\x08\x0b\x0c\x0e-\x1f]!', ' ', $text );
- $text = $wgDebugLogPrefix . $text;
- wfErrorLog( $text, $wgDebugLogFile );
+ # Check for raw action using $_GET not $wgRequest, since the latter might not be initialised yet
+ if ( ( isset( $_GET['action'] ) && $_GET['action'] == 'raw' )
+ || (
+ isset( $_SERVER['SCRIPT_NAME'] )
+ && substr( $_SERVER['SCRIPT_NAME'], -8 ) == 'load.php'
+ ) )
+ {
+ $cache = true;
+ } else {
+ $cache = false;
}
+ return $cache;
}
+/**
+ * Get microsecond timestamps for debug logs
+ *
+ * @return string
+ */
function wfDebugTimer() {
global $wgDebugTimestamps;
if ( !$wgDebugTimestamps ) {
@@ -373,6 +765,7 @@ function wfDebugTimer() {
/**
* Send a line giving PHP memory usage.
+ *
* @param $exact Bool: print exact values instead of kilobytes (default: false)
*/
function wfDebugMem( $exact = false ) {
@@ -405,7 +798,9 @@ function wfDebugLog( $logGroup, $text, $public = true ) {
} else {
$host = '';
}
- wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
+ if ( wfRunHooks( 'Debug', array( $text, $logGroup ) ) ) {
+ wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
+ }
} elseif ( $public === true ) {
wfDebug( $text, true );
}
@@ -413,6 +808,7 @@ function wfDebugLog( $logGroup, $text, $public = true ) {
/**
* Log for database errors
+ *
* @param $text String: database error message.
*/
function wfLogDBError( $text ) {
@@ -429,6 +825,9 @@ function wfLogDBError( $text ) {
*
* Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
* send lines to the specified port, prefixed by the specified prefix and a space.
+ *
+ * @param $text String
+ * @param $file String filename
*/
function wfErrorLog( $text, $file ) {
if ( substr( $file, 0, 4 ) == 'udp:' ) {
@@ -450,12 +849,21 @@ function wfErrorLog( $text, $file ) {
} else {
throw new MWException( __METHOD__ . ': Invalid UDP specification' );
}
+
// Clean it up for the multiplexer
if ( strval( $prefix ) !== '' ) {
$text = preg_replace( '/^/m', $prefix . ' ', $text );
+
+ // Limit to 64KB
+ if ( strlen( $text ) > 65534 ) {
+ $text = substr( $text, 0, 65534 );
+ }
+
if ( substr( $text, -1 ) != "\n" ) {
$text .= "\n";
}
+ } elseif ( strlen( $text ) > 65535 ) {
+ $text = substr( $text, 0, 65535 );
}
$sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
@@ -469,7 +877,7 @@ function wfErrorLog( $text, $file ) {
$exists = file_exists( $file );
$size = $exists ? filesize( $file ) : false;
if ( !$exists || ( $size !== false && $size + strlen( $text ) < 0x7fffffff ) ) {
- error_log( $text, 3, $file );
+ file_put_contents( $file, $text, FILE_APPEND );
}
wfRestoreWarnings();
}
@@ -480,49 +888,60 @@ function wfErrorLog( $text, $file ) {
*/
function wfLogProfilingData() {
global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
- global $wgProfiler, $wgProfileLimit, $wgUser;
+ global $wgProfileLimit, $wgUser;
+
+ $profiler = Profiler::instance();
+
# Profiling must actually be enabled...
- if( is_null( $wgProfiler ) ) {
+ if ( $profiler->isStub() ) {
return;
}
- # Get total page request time
+
+ // Get total page request time and only show pages that longer than
+ // $wgProfileLimit time (default is 0)
$now = wfTime();
$elapsed = $now - $wgRequestTime;
- # Only show pages that longer than $wgProfileLimit time (default is 0)
- if( $elapsed <= $wgProfileLimit ) {
+ if ( $elapsed <= $wgProfileLimit ) {
+ return;
+ }
+
+ $profiler->logData();
+
+ // Check whether this should be logged in the debug file.
+ if ( $wgDebugLogFile == '' || ( !$wgDebugRawPage && wfIsDebugRawPage() ) ) {
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
- // FIXME: We can detect some anons even if it is not loaded. See User::getId()
- if( $wgUser->mDataLoaded && $wgUser->isAnon() ) {
+ // Don't load $wgUser at this late stage just for statistics purposes
+ // @todo FIXME: We can detect some anons even if it is not loaded. See User::getId()
+ if ( $wgUser->isItemLoaded( 'id' ) && $wgUser->isAnon() ) {
$forward .= ' anon';
}
$log = sprintf( "%s\t%04.3f\t%s\n",
gmdate( 'YmdHis' ), $elapsed,
urldecode( $wgRequest->getRequestURL() . $forward ) );
- if ( $wgDebugLogFile != '' && ( $wgRequest->getVal( 'action' ) != 'raw' || $wgDebugRawPage ) ) {
- wfErrorLog( $log . $prof, $wgDebugLogFile );
- }
+
+ wfErrorLog( $log . $profiler->getOutput(), $wgDebugLogFile );
}
/**
* Check if the wiki read-only lock file is present. This can be used to lock
* off editing functions, but doesn't guarantee that the database will not be
* modified.
+ *
* @return bool
*/
function wfReadOnly() {
@@ -551,6 +970,7 @@ function wfReadOnlyReason() {
/**
* Return a Language object from $langcode
+ *
* @param $langcode Mixed: either:
* - a Language object
* - code of the language to get the message for, if it is
@@ -596,15 +1016,15 @@ 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).
+ * Old function when $wgBetterDirectionality existed
+ * Removed in core, kept in extensions for backwards compat.
+ *
+ * @deprecated since 1.18
+ * @return Language
*/
function wfUILang() {
- global $wgBetterDirectionality;
- return wfGetLangObj( !$wgBetterDirectionality );
+ global $wgLang;
+ return $wgLang;
}
/**
@@ -613,7 +1033,7 @@ function wfUILang() {
* The intention is that this function replaces all old wfMsg* functions.
* @param $key \string Message key.
* Varargs: normal message parameters.
- * @return \type{Message}
+ * @return Message
* @since 1.17
*/
function wfMessage( $key /*...*/) {
@@ -626,6 +1046,19 @@ function wfMessage( $key /*...*/) {
}
/**
+ * This function accepts multiple message keys and returns a message instance
+ * for the first message which is non-empty. If all messages are empty then an
+ * instance of the first message key is returned.
+ * @param varargs: message keys
+ * @return Message
+ * @since 1.18
+ */
+function wfMessageFallback( /*...*/ ) {
+ $args = func_get_args();
+ return MWFunction::callArray( 'Message::newFallbackSequence', $args );
+}
+
+/**
* Get a message from anywhere, for the current user language.
*
* Use wfMsgForContent() instead if the message should NOT
@@ -634,18 +1067,25 @@ function wfMessage( $key /*...*/) {
* @param $key String: lookup key for the message, usually
* defined in languages/Language.php
*
- * This function also takes extra optional parameters (not
- * shown in the function definition), which can be used to
- * insert variable text into the predefined message.
+ * Parameters to the message, which can be used to insert variable text into
+ * it, can be passed to this function in the following formats:
+ * - One per argument, starting at the second parameter
+ * - As an array in the second parameter
+ * These are not shown in the function definition.
+ *
+ * @return String
*/
function wfMsg( $key ) {
$args = func_get_args();
array_shift( $args );
- return wfMsgReal( $key, $args, true );
+ return wfMsgReal( $key, $args );
}
/**
* Same as above except doesn't transform the message
+ *
+ * @param $key String
+ * @return String
*/
function wfMsgNoTrans( $key ) {
$args = func_get_args();
@@ -673,7 +1113,8 @@ function wfMsgNoTrans( $key ) {
* 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
+ * defined in languages/Language.php
+ * @return String
*/
function wfMsgForContent( $key ) {
global $wgForceUIMsgAsContentMsg;
@@ -690,6 +1131,9 @@ function wfMsgForContent( $key ) {
/**
* Same as above except doesn't transform the message
+ *
+ * @param $key String
+ * @return String
*/
function wfMsgForContentNoTrans( $key ) {
global $wgForceUIMsgAsContentMsg;
@@ -705,33 +1149,8 @@ function wfMsgForContentNoTrans( $key ) {
}
/**
- * Get a message from the language file, for the UI elements
- */
-function wfMsgNoDB( $key ) {
- $args = func_get_args();
- array_shift( $args );
- return wfMsgReal( $key, $args, false );
-}
-
-/**
- * Get a message from the language file, for the content
- */
-function wfMsgNoDBForContent( $key ) {
- global $wgForceUIMsgAsContentMsg;
- $args = func_get_args();
- array_shift( $args );
- $forcontent = true;
- if( is_array( $wgForceUIMsgAsContentMsg ) &&
- in_array( $key, $wgForceUIMsgAsContentMsg ) )
- {
- $forcontent = false;
- }
- return wfMsgReal( $key, $args, false, $forcontent );
-}
-
-
-/**
* Really get a message
+ *
* @param $key String: key to get.
* @param $args
* @param $useDB Boolean
@@ -748,20 +1167,8 @@ function wfMsgReal( $key, $args, $useDB = true, $forContent = false, $transform
}
/**
- * This function provides the message source for messages to be edited which are *not* stored in the database.
- * @param $key String:
- */
-function wfMsgWeirdKey( $key ) {
- $source = wfMsgGetKey( $key, false, true, false );
- if ( wfEmptyMsg( $key, $source ) ) {
- return '';
- } else {
- return $source;
- }
-}
-
-/**
* Fetch a message string value, but don't replace any keys yet.
+ *
* @param $key String
* @param $useDB Bool
* @param $langCode String: Code of the language to get the message for, or
@@ -769,20 +1176,15 @@ function wfMsgWeirdKey( $key ) {
* @param $transform Boolean: whether to parse magic words, etc.
* @return string
*/
-function wfMsgGetKey( $key, $useDB, $langCode = false, $transform = true ) {
- global $wgMessageCache;
-
+function wfMsgGetKey( $key, $useDB = true, $langCode = false, $transform = true ) {
wfRunHooks( 'NormalizeMessageKey', array( &$key, &$useDB, &$langCode, &$transform ) );
- if ( !is_object( $wgMessageCache ) ) {
- throw new MWException( 'Trying to get message before message cache is initialised' );
- }
-
- $message = $wgMessageCache->get( $key, $useDB, $langCode );
+ $cache = MessageCache::singleton();
+ $message = $cache->get( $key, $useDB, $langCode );
if( $message === false ) {
$message = '&lt;' . htmlspecialchars( $key ) . '&gt;';
} elseif ( $transform ) {
- $message = $wgMessageCache->transform( $message );
+ $message = $cache->transform( $message );
}
return $message;
}
@@ -829,7 +1231,7 @@ function wfMsgReplaceArgs( $message, $args ) {
function wfMsgHtml( $key ) {
$args = func_get_args();
array_shift( $args );
- return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key, true ) ), $args );
+ return wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $key ) ), $args );
}
/**
@@ -844,10 +1246,11 @@ function wfMsgHtml( $key ) {
* @return string
*/
function wfMsgWikiHtml( $key ) {
- global $wgOut;
$args = func_get_args();
array_shift( $args );
- return wfMsgReplaceArgs( $wgOut->parse( wfMsgGetKey( $key, true ), /* can't be set to false */ true ), $args );
+ return wfMsgReplaceArgs(
+ MessageCache::singleton()->parse( wfMsgGetKey( $key ), null, /* can't be set to false */ true )->getText(),
+ $args );
}
/**
@@ -864,13 +1267,12 @@ function wfMsgWikiHtml( $key ) {
* <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 parse, parseinline
- * and parsemag is undefined.
+ * (overriden by <i>content</i>).
* Behavior for conflicting options (e.g., parse+parseinline) is undefined.
+ *
+ * @return String
*/
function wfMsgExt( $key, $options ) {
- global $wgOut;
-
$args = func_get_args();
array_shift( $args );
array_shift( $args );
@@ -891,12 +1293,15 @@ function wfMsgExt( $key, $options ) {
if( in_array( 'content', $options, true ) ) {
$forContent = true;
$langCode = true;
+ $langCodeObj = null;
} elseif( array_key_exists( 'language', $options ) ) {
$forContent = false;
$langCode = wfGetLangObj( $options['language'] );
+ $langCodeObj = $langCode;
} else {
$forContent = false;
$langCode = false;
+ $langCodeObj = null;
}
$string = wfMsgGetKey( $key, /*DB*/true, $langCode, /*Transform*/false );
@@ -905,21 +1310,18 @@ function wfMsgExt( $key, $options ) {
$string = wfMsgReplaceArgs( $string, $args );
}
+ $messageCache = MessageCache::singleton();
if( in_array( 'parse', $options, true ) ) {
- $string = $wgOut->parse( $string, true, !$forContent );
+ $string = $messageCache->parse( $string, null, true, !$forContent, $langCodeObj )->getText();
} elseif ( in_array( 'parseinline', $options, true ) ) {
- $string = $wgOut->parse( $string, true, !$forContent );
+ $string = $messageCache->parse( $string, null, true, !$forContent, $langCodeObj )->getText();
$m = array();
if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
$string = $m[1];
}
} elseif ( in_array( 'parsemag', $options, true ) ) {
- global $wgMessageCache;
- if ( isset( $wgMessageCache ) ) {
- $string = $wgMessageCache->transform( $string,
- !$forContent,
- is_object( $langCode ) ? $langCode : null );
- }
+ $string = $messageCache->transform( $string,
+ !$forContent, $langCodeObj );
}
if ( in_array( 'escape', $options, true ) ) {
@@ -935,58 +1337,16 @@ function wfMsgExt( $key, $options ) {
return $string;
}
-
/**
- * Just like exit() but makes a note of it.
- * Commits open transactions except if the error parameter is set
+ * Since wfMsg() and co suck, they don't return false if the message key they
+ * looked up didn't exist but a XHTML string, this function checks for the
+ * nonexistance of messages by checking the MessageCache::get() result directly.
*
- * @deprecated Please return control to the caller or throw an exception. Will
- * be removed in 1.19.
- */
-function wfAbruptExit( $error = false ) {
- static $called = false;
- 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");
- }
- } else {
- wfDebug( "WARNING: Abrupt exit\n" );
- }
-
- wfLogProfilingData();
-
- if ( !$error ) {
- wfGetLB()->closeAll();
- }
- exit( -1 );
-}
-
-/**
- * @deprecated Please return control the caller or throw an exception. Will
- * be removed in 1.19.
- */
-function wfErrorExit() {
- wfDeprecated( __FUNCTION__ );
- wfAbruptExit( true );
-}
-
-/**
- * Print a simple message and die, returning nonzero to the shell if any.
- * Plain die() fails to return nonzero to the shell if you pass a string.
- * @param $msg String
+ * @param $key String: the message key looked up
+ * @return Boolean True if the message *doesn't* exist.
*/
-function wfDie( $msg = '' ) {
- echo $msg;
- die( 1 );
+function wfEmptyMsg( $key ) {
+ return MessageCache::singleton()->get( $key, /*useDB*/true, /*content*/false ) === false;
}
/**
@@ -1003,6 +1363,7 @@ function wfDebugDieBacktrace( $msg = '' ) {
* Fetch server name for use in error reporting etc.
* Use real server name if available, so we know which machine
* in a server farm generated the current page.
+ *
* @return string
*/
function wfHostname() {
@@ -1010,7 +1371,7 @@ function wfHostname() {
if ( is_null( $host ) ) {
if ( function_exists( 'posix_uname' ) ) {
// This function not present on Windows
- $uname = @posix_uname();
+ $uname = posix_uname();
} else {
$uname = false;
}
@@ -1030,6 +1391,7 @@ function wfHostname() {
/**
* Returns a HTML comment with the elapsed time since request.
* This method has no side effects.
+ *
* @return string
*/
function wfReportTime() {
@@ -1054,9 +1416,11 @@ function wfReportTime() {
* debug_backtrace is disabled, otherwise the output from
* debug_backtrace() (trimmed).
*
+ * @param $limit int This parameter can be used to limit the number of stack frames returned
+ *
* @return array of backtrace information
*/
-function wfDebugBacktrace() {
+function wfDebugBacktrace( $limit = 0 ) {
static $disabled = null;
if( extension_loaded( 'Zend Optimizer' ) ) {
@@ -1078,9 +1442,18 @@ function wfDebugBacktrace() {
return array();
}
- return array_slice( debug_backtrace(), 1 );
+ if ( $limit && version_compare( PHP_VERSION, '5.4.0', '>=' ) ) {
+ return array_slice( debug_backtrace( DEBUG_BACKTRACE_PROVIDE_OBJECT, $limit ), 1 );
+ } else {
+ return array_slice( debug_backtrace(), 1 );
+ }
}
+/**
+ * Get a debug backtrace as a string
+ *
+ * @return string
+ */
function wfBacktrace() {
global $wgCommandLineMode;
@@ -1108,7 +1481,7 @@ function wfBacktrace() {
$msg .= '<li>' . $file . ' line ' . $line . ' calls ';
}
if( !empty( $call['class'] ) ) {
- $msg .= $call['class'] . '::';
+ $msg .= $call['class'] . $call['type'];
}
$msg .= $call['function'] . '()';
@@ -1127,12 +1500,61 @@ function wfBacktrace() {
return $msg;
}
+/**
+ * Get the name of the function which called this function
+ *
+ * @param $level Int
+ * @return Bool|string
+ */
+function wfGetCaller( $level = 2 ) {
+ $backtrace = wfDebugBacktrace( $level );
+ if ( isset( $backtrace[$level] ) ) {
+ return wfFormatStackFrame( $backtrace[$level] );
+ } else {
+ $caller = 'unknown';
+ }
+ return $caller;
+}
+
+/**
+ * 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.
+ * @return String
+ */
+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
+ *
+ * @param $frame Array
+ * @return Bool
+ */
+function wfFormatStackFrame( $frame ) {
+ return isset( $frame['class'] ) ?
+ $frame['class'] . '::' . $frame['function'] :
+ $frame['function'];
+}
+
/* Some generic result counters, pulled out of SearchEngine */
/**
* @todo document
+ *
+ * @param $offset Int
+ * @param $limit Int
+ * @return String
*/
function wfShowingResults( $offset, $limit ) {
global $wgLang;
@@ -1145,31 +1567,19 @@ function wfShowingResults( $offset, $limit ) {
}
/**
- * @todo document
- */
-function wfShowingResultsNum( $offset, $limit, $num ) {
- global $wgLang;
- return wfMsgExt(
- 'showingresultsnum',
- array( 'parseinline' ),
- $wgLang->formatNum( $limit ),
- $wgLang->formatNum( $offset + 1 ),
- $wgLang->formatNum( $num )
- );
-}
-
-/**
* Generate (prev x| next x) (20|50|100...) type links for paging
+ *
* @param $offset String
* @param $limit Integer
* @param $link String
* @param $query String: optional URL query parameter string
* @param $atend Bool: optional param for specified if this is the last page
+ * @return String
*/
function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
global $wgLang;
$fmtLimit = $wgLang->formatNum( $limit );
- // FIXME: Why on earth this needs one message for the text and another one for tooltip??
+ // @todo 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 );
@@ -1221,6 +1631,7 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
/**
* Generate links for (20|50|100...) items-per-page links
+ *
* @param $offset String
* @param $limit Integer
* @param $title Title
@@ -1242,16 +1653,17 @@ function wfNumLink( $offset, $limit, $title, $query = '' ) {
/**
* @todo document
- * @todo FIXME: we may want to blacklist some broken browsers
+ * @todo FIXME: We may want to blacklist some broken browsers
*
+ * @param $force Bool
* @return bool Whereas client accept gzip compression
*/
-function wfClientAcceptsGzip() {
+function wfClientAcceptsGzip( $force = false ) {
static $result = null;
- if ( $result === null ) {
+ if ( $result === null || $force ) {
$result = false;
if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
- # FIXME: we may want to blacklist some broken browsers
+ # @todo FIXME: We may want to blacklist some broken browsers
$m = array();
if( preg_match(
'/\bgzip(?:;(q)=([0-9]+(?:\.[0-9]+)))?\b/',
@@ -1275,8 +1687,8 @@ function wfClientAcceptsGzip() {
* Obtain the offset and limit values from the request string;
* used in special pages
*
- * @param $deflimit Default limit if none supplied
- * @param $optionname Name of a user preference to check against
+ * @param $deflimit Int default limit if none supplied
+ * @param $optionname String Name of a user preference to check against
* @return array
*
*/
@@ -1289,50 +1701,26 @@ function wfCheckLimits( $deflimit = 50, $optionname = 'rclimit' ) {
* Escapes the given text so that it may be output using addWikiText()
* without any linking, formatting, etc. making its way through. This
* is achieved by substituting certain characters with HTML entities.
- * As required by the callers, <nowiki> is not used. It currently does
- * not filter out characters which have special meaning only at the
- * start of a line, such as "*".
+ * As required by the callers, <nowiki> is not used.
*
* @param $text String: text to be escaped
+ * @return String
*/
function wfEscapeWikiText( $text ) {
- $text = str_replace(
- array( '[', '|', ']', '\'', 'ISBN ',
- 'RFC ', '://', "\n=", '{{', '}}' ),
- array( '&#91;', '&#124;', '&#93;', '&#39;', 'ISBN&#32;',
- 'RFC&#32;', '&#58;//', "\n&#61;", '&#123;&#123;', '&#125;&#125;' ),
- htmlspecialchars( $text )
- );
- return $text;
-}
-
-/**
- * @todo document
- */
-function wfQuotedPrintable( $string, $charset = '' ) {
- # Probably incomplete; see RFC 2045
- if( empty( $charset ) ) {
- global $wgInputEncoding;
- $charset = $wgInputEncoding;
- }
- $charset = strtoupper( $charset );
- $charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
-
- $illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
- $replace = $illegal . '\t ?_';
- if( !preg_match( "/[$illegal]/", $string ) ) {
- return $string;
- }
- $out = "=?$charset?Q?";
- $out .= preg_replace( "/([$replace])/e", 'sprintf("=%02X",ord("$1"))', $string );
- $out .= '?=';
- return $out;
+ $text = strtr( "\n$text", array(
+ '"' => '&#34;', '&' => '&#38;', "'" => '&#39;', '<' => '&#60;',
+ '=' => '&#61;', '>' => '&#62;', '[' => '&#91;', ']' => '&#93;',
+ '{' => '&#123;', '|' => '&#124;', '}' => '&#125;',
+ "\n#" => "\n&#35;", "\n*" => "\n&#42;",
+ "\n:" => "\n&#58;", "\n;" => "\n&#59;",
+ '://' => '&#58;//', 'ISBN ' => 'ISBN&#32;', 'RFC ' => 'RFC&#32;',
+ ) );
+ return substr( $text, 1 );
}
-
/**
- * @todo document
- * @return float
+ * Get the current unix timetstamp with microseconds. Useful for profiling
+ * @return Float
*/
function wfTime() {
return microtime( true );
@@ -1342,6 +1730,11 @@ function wfTime() {
* Sets dest to source and returns the original value of dest
* If source is NULL, it just returns the value, it doesn't set the variable
* If force is true, it will set the value even if source is NULL
+ *
+ * @param $dest Mixed
+ * @param $source Mixed
+ * @param $force Bool
+ * @return Mixed
*/
function wfSetVar( &$dest, $source, $force = false ) {
$temp = $dest;
@@ -1353,6 +1746,10 @@ function wfSetVar( &$dest, $source, $force = false ) {
/**
* As for wfSetVar except setting a bit
+ *
+ * @param $dest Int
+ * @param $bit Int
+ * @param $state Bool
*/
function wfSetBit( &$dest, $bit, $state = true ) {
$temp = (bool)( $dest & $bit );
@@ -1367,121 +1764,15 @@ function wfSetBit( &$dest, $bit, $state = true ) {
}
/**
- * This function takes two arrays as input, and returns a CGI-style string, e.g.
- * "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 ) {
- if ( !is_null( $array2 ) ) {
- $array1 = $array1 + $array2;
- }
-
- $cgi = '';
- foreach ( $array1 as $key => $value ) {
- if ( $value !== '' ) {
- if ( $cgi != '' ) {
- $cgi .= '&';
- }
- if ( is_array( $value ) ) {
- $firstTime = true;
- foreach ( $value as $v ) {
- $cgi .= ( $firstTime ? '' : '&') .
- urlencode( $key . '[]' ) . '=' .
- urlencode( $v );
- $firstTime = false;
- }
- } else {
- if ( is_object( $value ) ) {
- $value = $value->__toString();
- }
- $cgi .= urlencode( $key ) . '=' .
- urlencode( $value );
- }
- }
- }
- return $cgi;
-}
-
-/**
- * This is the logical opposite of wfArrayToCGI(): it accepts a query string as
- * its argument and returns the same string in array form. This allows compa-
- * tibility with legacy functions that accept raw query strings instead of nice
- * arrays. Of course, keys and values are urldecode()d. Don't try passing in-
- * valid query strings, or it will explode.
- *
- * @param $query String: query string
- * @return array Array version of input
- */
-function wfCgiToArray( $query ) {
- if( isset( $query[0] ) && $query[0] == '?' ) {
- $query = substr( $query, 1 );
- }
- $bits = explode( '&', $query );
- $ret = array();
- foreach( $bits as $bit ) {
- if( $bit === '' ) {
- continue;
- }
- list( $key, $value ) = explode( '=', $bit );
- $key = urldecode( $key );
- $value = urldecode( $value );
- $ret[$key] = $value;
- }
- return $ret;
-}
-
-/**
- * Append a query string to an existing URL, which may or may not already
- * have query string parameters already. If so, they will be combined.
- *
- * @param $url String
- * @param $query Mixed: string or associative array
- * @return string
- */
-function wfAppendQuery( $url, $query ) {
- if ( is_array( $query ) ) {
- $query = wfArrayToCGI( $query );
- }
- if( $query != '' ) {
- if( false === strpos( $url, '?' ) ) {
- $url .= '?';
- } else {
- $url .= '&';
- }
- $url .= $query;
- }
- return $url;
-}
-
-/**
- * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
- * 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, 2 ) == '//' ) {
- global $wgProto;
- return $wgProto . ':' . $url;
- } elseif( substr( $url, 0, 1 ) == '/' ) {
- global $wgServer;
- return $wgServer . $url;
- } else {
- return $url;
- }
-}
-
-/**
* 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
* earlier distro releases of PHP)
+ *
+ * @param varargs
+ * @return String
*/
function wfEscapeShellArg( ) {
wfInitShellLocale();
@@ -1497,8 +1788,12 @@ function wfEscapeShellArg( ) {
}
if ( wfIsWindows() ) {
- // Escaping for an MSVC-style command line parser
- // Ref: http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
+ // Escaping for an MSVC-style command line parser and CMD.EXE
+ // Refs:
+ // * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
+ // * http://technet.microsoft.com/en-us/library/cc723564.aspx
+ // * Bug #13518
+ // * CR r63214
// Double the backslashes before any double quotes. Escape the double quotes.
$tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
$arg = '';
@@ -1535,6 +1830,12 @@ function wfEscapeShellArg( ) {
/**
* wfMerge attempts to merge differences between three texts.
* Returns true for a clean merge and false for failure or a conflict.
+ *
+ * @param $old String
+ * @param $mine String
+ * @param $yours String
+ * @param $result String
+ * @return Bool
*/
function wfMerge( $old, $mine, $yours, &$result ) {
global $wgDiff3;
@@ -1604,6 +1905,7 @@ function wfMerge( $old, $mine, $yours, &$result ) {
/**
* Returns unified plain-text diff of two texts.
* Useful for machine processing of diffs.
+ *
* @param $before String: the text before the changes.
* @param $after String: the text after the changes.
* @param $params String: command-line options for the diff command.
@@ -1681,7 +1983,7 @@ function wfDiff( $before, $after, $params = '-u' ) {
function wfVarDump( $var ) {
global $wgOut;
$s = str_replace( "\n", "<br />\n", var_export( $var, true ) . "\n" );
- if ( headers_sent() || !@is_object( $wgOut ) ) {
+ if ( headers_sent() || !isset( $wgOut ) || !is_object( $wgOut ) ) {
print $s;
} else {
$wgOut->addHTML( $s );
@@ -1690,6 +1992,10 @@ function wfVarDump( $var ) {
/**
* Provide a simple HTTP error.
+ *
+ * @param $code Int|String
+ * @param $label String
+ * @param $desc String
*/
function wfHttpError( $code, $label, $desc ) {
global $wgOut;
@@ -1783,6 +2089,10 @@ function wfClearOutputBuffers() {
/**
* Converts an Accept-* header into an array mapping string values to quality
* factors
+ *
+ * @param $accept String
+ * @param $def String default
+ * @return Array
*/
function wfAcceptToPrefs( $accept, $def = '*/*' ) {
# No arg means accept anything (per HTTP spec)
@@ -1795,13 +2105,13 @@ function wfAcceptToPrefs( $accept, $def = '*/*' ) {
$parts = explode( ',', $accept );
foreach( $parts as $part ) {
- # FIXME: doesn't deal with params like 'text/html; level=1'
- @list( $value, $qpart ) = explode( ';', trim( $part ) );
+ # @todo FIXME: Doesn't deal with params like 'text/html; level=1'
+ $values = explode( ';', trim( $part ) );
$match = array();
- if( !isset( $qpart ) ) {
- $prefs[$value] = 1.0;
- } elseif( preg_match( '/q\s*=\s*(\d*\.\d+)/', $qpart, $match ) ) {
- $prefs[$value] = floatval( $match[1] );
+ if ( count( $values ) == 1 ) {
+ $prefs[$values[0]] = 1.0;
+ } elseif ( preg_match( '/q\s*=\s*(\d*\.\d+)/', $values[1], $match ) ) {
+ $prefs[$values[0]] = floatval( $match[1] );
}
}
@@ -1845,7 +2155,7 @@ function mimeTypeMatch( $type, $avail ) {
* @param $sprefs Array: server's offered types
* @return string
*
- * @todo FIXME: doesn't handle params like 'text/plain; charset=UTF-8'
+ * @todo FIXME: Doesn't handle params like 'text/plain; charset=UTF-8'
* XXX: generalize to negotiate other stuff
*/
function wfNegotiateType( $cprefs, $sprefs ) {
@@ -1885,27 +2195,9 @@ function wfNegotiateType( $cprefs, $sprefs ) {
}
/**
- * Array lookup
- * Returns an array where the values in the first array are replaced by the
- * values in the second array with the corresponding keys
- *
- * @return array
- */
-function wfArrayLookup( $a, $b ) {
- return array_flip( array_intersect( array_flip( $a ), array_keys( $b ) ) );
-}
-
-/**
- * Convenience function; returns MediaWiki timestamp for the present time.
- * @return string
- */
-function wfTimestampNow() {
- # return NOW
- return wfTimestamp( TS_MW, time() );
-}
-
-/**
* Reference-counted warning suppression
+ *
+ * @param $end Bool
*/
function wfSuppressWarnings( $end = false ) {
static $suppressCount = 0;
@@ -1920,7 +2212,11 @@ function wfSuppressWarnings( $end = false ) {
}
} else {
if ( !$suppressCount ) {
- $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE ) );
+ // E_DEPRECATED is undefined in PHP 5.2
+ if( !defined( 'E_DEPRECATED' ) ){
+ define( 'E_DEPRECATED', 8192 );
+ }
+ $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED ) );
}
++$suppressCount;
}
@@ -1987,13 +2283,13 @@ define( 'TS_POSTGRES', 7 );
define( 'TS_DB2', 8 );
/**
- * ISO 8601 basic format with no timezone: 19860209T200000Z
- *
- * This is used by ResourceLoader
+ * ISO 8601 basic format with no timezone: 19860209T200000Z. This is used by ResourceLoader
*/
define( 'TS_ISO_8601_BASIC', 9 );
/**
+ * Get a timestamp string in one of various formats
+ *
* @param $outputtype Mixed: A timestamp in one of the supported formats, the
* function will autodetect which format is supplied and act
* accordingly.
@@ -2036,7 +2332,7 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
'\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
+ # 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
@@ -2046,13 +2342,11 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
$strtime = $ts;
} else {
# Bogus value...
- wfDebug("wfTimestamp() fed bogus time value: $outputtype; $ts\n");
-
+ wfDebug("wfTimestamp() fed bogus time value: TYPE=$outputtype; VALUE=$ts\n");
+
return false;
}
-
-
static $formats = array(
TS_UNIX => 'U',
TS_MW => 'YmdHis',
@@ -2082,12 +2376,12 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
} 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 ) ) {
@@ -2120,6 +2414,7 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
/**
* Return a formatted timestamp, or null if input is null.
* For dealing with nullable timestamp columns in the database.
+ *
* @param $outputtype Integer
* @param $ts String
* @return String
@@ -2133,6 +2428,16 @@ function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
}
/**
+ * Convenience function; returns MediaWiki timestamp for the present time.
+ *
+ * @return string
+ */
+function wfTimestampNow() {
+ # return NOW
+ return wfTimestamp( TS_MW, time() );
+}
+
+/**
* Check if the operating system is Windows
*
* @return Bool: true if it's Windows, False otherwise.
@@ -2146,7 +2451,19 @@ function wfIsWindows() {
}
/**
+ * Check if we are running under HipHop
+ *
+ * @return Bool
+ */
+function wfIsHipHop() {
+ return function_exists( 'hphp_thread_set_warmup_enabled' );
+}
+
+/**
* Swap two variables
+ *
+ * @param $x Mixed
+ * @param $y Mixed
*/
function swap( &$x, &$y ) {
$z = $x;
@@ -2154,116 +2471,6 @@ function swap( &$x, &$y ) {
$y = $z;
}
-function wfGetCachedNotice( $name ) {
- global $wgOut, $wgRenderHashAppend, $parserMemc;
- $fname = 'wfGetCachedNotice';
- wfProfileIn( $fname );
-
- $needParse = false;
-
- if( $name === 'default' ) {
- // special case
- global $wgSiteNotice;
- $notice = $wgSiteNotice;
- if( empty( $notice ) ) {
- wfProfileOut( $fname );
- return false;
- }
- } else {
- $notice = wfMsgForContentNoTrans( $name );
- if( wfEmptyMsg( $name, $notice ) || $notice == '-' ) {
- wfProfileOut( $fname );
- return( false );
- }
- }
-
- // Use the extra hash appender to let eg SSL variants separately cache.
- $key = wfMemcKey( $name . $wgRenderHashAppend );
- $cachedNotice = $parserMemc->get( $key );
- if( is_array( $cachedNotice ) ) {
- if( md5( $notice ) == $cachedNotice['hash'] ) {
- $notice = $cachedNotice['html'];
- } else {
- $needParse = true;
- }
- } else {
- $needParse = true;
- }
-
- if( $needParse ) {
- if( is_object( $wgOut ) ) {
- $parsed = $wgOut->parse( $notice );
- $parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
- $notice = $parsed;
- } else {
- wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' . "\n" );
- $notice = '';
- }
- }
- $notice = '<div id="localNotice">' .$notice . '</div>';
- wfProfileOut( $fname );
- return $notice;
-}
-
-function wfGetNamespaceNotice() {
- global $wgTitle;
-
- # Paranoia
- if ( !isset( $wgTitle ) || !is_object( $wgTitle ) ) {
- return '';
- }
-
- $fname = 'wfGetNamespaceNotice';
- wfProfileIn( $fname );
-
- $key = 'namespacenotice-' . $wgTitle->getNsText();
- $namespaceNotice = wfGetCachedNotice( $key );
- if ( $namespaceNotice && substr( $namespaceNotice, 0, 7 ) != '<p>&lt;' ) {
- $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . '</div>';
- } else {
- $namespaceNotice = '';
- }
-
- wfProfileOut( $fname );
- return $namespaceNotice;
-}
-
-function wfGetSiteNotice() {
- global $wgUser;
- $fname = 'wfGetSiteNotice';
- wfProfileIn( $fname );
- $siteNotice = '';
-
- if( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice ) ) ) {
- if( is_object( $wgUser ) && $wgUser->isLoggedIn() ) {
- $siteNotice = wfGetCachedNotice( 'sitenotice' );
- } else {
- $anonNotice = wfGetCachedNotice( 'anonnotice' );
- if( !$anonNotice ) {
- $siteNotice = wfGetCachedNotice( 'sitenotice' );
- } else {
- $siteNotice = $anonNotice;
- }
- }
- if( !$siteNotice ) {
- $siteNotice = wfGetCachedNotice( 'default' );
- }
- }
-
- wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice ) );
- wfProfileOut( $fname );
- return $siteNotice;
-}
-
-/**
- * BC wrapper for MimeMagic::singleton()
- * @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. The TMPDIR, TMP, and
* TEMP environment variables are then checked in sequence, and if none are set
@@ -2329,16 +2536,24 @@ function wfMkdirParents( $dir, $mode = null, $caller = null ) {
/**
* Increment a statistics counter
+ *
+ * @param $key String
+ * @param $count Int
*/
-function wfIncrStats( $key ) {
+function wfIncrStats( $key, $count = 1 ) {
global $wgStatsMethod;
+ $count = intval( $count );
+
if( $wgStatsMethod == 'udp' ) {
- global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname;
+ global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgDBname, $wgAggregateStatsID;
static $socket;
+
+ $id = $wgAggregateStatsID !== false ? $wgAggregateStatsID : $wgDBname;
+
if ( !$socket ) {
$socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
- $statline = "stats/{$wgDBname} - 1 1 1 1 1 -total\n";
+ $statline = "stats/{$id} - {$count} 1 1 1 1 -total\n";
socket_sendto(
$socket,
$statline,
@@ -2348,7 +2563,7 @@ function wfIncrStats( $key ) {
$wgUDPProfilerPort
);
}
- $statline = "stats/{$wgDBname} - 1 1 1 1 1 {$key}\n";
+ $statline = "stats/{$id} - {$count} 1 1 1 1 {$key}\n";
wfSuppressWarnings();
socket_sendto(
$socket,
@@ -2362,8 +2577,8 @@ function wfIncrStats( $key ) {
} elseif( $wgStatsMethod == 'cache' ) {
global $wgMemc;
$key = wfMemcKey( 'stats', $key );
- if ( is_null( $wgMemc->incr( $key ) ) ) {
- $wgMemc->add( $key, 1 );
+ if ( is_null( $wgMemc->incr( $key, $count ) ) ) {
+ $wgMemc->add( $key, $count );
}
} else {
// Disabled
@@ -2382,87 +2597,35 @@ function wfPercent( $nr, $acc = 2, $round = true ) {
}
/**
- * Encrypt a username/password.
- *
- * @param $userid Integer: ID of the user
- * @param $password String: password of the user
- * @return String: hashed password
- * @deprecated Use User::crypt() or User::oldCrypt() instead
- */
-function wfEncryptPassword( $userid, $password ) {
- wfDeprecated(__FUNCTION__);
- # Just wrap around User::oldCrypt()
- return User::oldCrypt( $password, $userid );
-}
-
-/**
- * Appends to second array if $value differs from that in $default
- */
-function wfAppendToArrayIfNotDefault( $key, $value, $default, &$changed ) {
- if ( is_null( $changed ) ) {
- throw new MWException( 'GlobalFunctions::wfAppendToArrayIfNotDefault got null' );
- }
- if ( $default[$key] !== $value ) {
- $changed[$key] = $value;
- }
-}
-
-/**
- * Since wfMsg() and co suck, they don't return false if the message key they
- * looked up didn't exist but a XHTML string, this function checks for the
- * nonexistance of messages by looking at wfMsg() output
- *
- * @param $key String: the message key looked up
- * @return Boolean True if the message *doesn't* exist.
- */
-function wfEmptyMsg( $key ) {
- global $wgMessageCache;
- return $wgMessageCache->get( $key, /*useDB*/true, /*content*/false ) === false;
-}
-
-/**
* Find out whether or not a mixed variable exists in a string
*
* @param $needle String
* @param $str String
+ * @param $insensitive Boolean
* @return Boolean
*/
-function in_string( $needle, $str ) {
- return strpos( $str, $needle ) !== false;
-}
+function in_string( $needle, $str, $insensitive = false ) {
+ $func = 'strpos';
+ if( $insensitive ) $func = 'stripos';
-function wfSpecialList( $page, $details ) {
- global $wgContLang;
- $details = $details ? ' ' . $wgContLang->getDirMark() . "($details)" : '';
- return $page . $details;
+ return $func( $str, $needle ) !== false;
}
/**
- * Returns a regular expression of url protocols
+ * Make a list item, used by various special pages
*
+ * @param $page String Page link
+ * @param $details String Text between brackets
+ * @param $oppositedm Boolean Add the direction mark opposite to your
+ * language, to display text properly
* @return String
*/
-function wfUrlProtocols() {
- global $wgUrlProtocols;
-
- static $retval = null;
- 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 ) {
- $protocols[] = preg_quote( $protocol, '/' );
- }
-
- $retval = implode( '|', $protocols );
- } else {
- $retval = $wgUrlProtocols;
- }
- return $retval;
+function wfSpecialList( $page, $details, $oppositedm = true ) {
+ global $wgLang;
+ $dirmark = ( $oppositedm ? $wgLang->getDirMark( true ) : '' ) .
+ $wgLang->getDirMark();
+ $details = $details ? $dirmark . " ($details)" : '';
+ return $page . $details;
}
/**
@@ -2503,19 +2666,30 @@ function wfIniGetBool( $setting ) {
*
* @param $extension String A PHP extension. The file suffix (.so or .dll)
* should be omitted
+ * @param $fileName String Name of the library, if not $extension.suffix
* @return Bool - Whether or not the extension is loaded
*/
-function wfDl( $extension ) {
+function wfDl( $extension, $fileName = null ) {
if( extension_loaded( $extension ) ) {
return true;
}
- $canDl = ( function_exists( 'dl' ) && is_callable( 'dl' )
+ $canDl = false;
+ $sapi = php_sapi_name();
+ if( version_compare( PHP_VERSION, '5.3.0', '<' ) ||
+ $sapi == 'cli' || $sapi == 'cgi' || $sapi == 'embed' )
+ {
+ $canDl = ( function_exists( 'dl' ) && is_callable( 'dl' )
&& wfIniGetBool( 'enable_dl' ) && !wfIniGetBool( 'safe_mode' ) );
+ }
if( $canDl ) {
+ $fileName = $fileName ? $fileName : $extension;
+ if( wfIsWindows() ) {
+ $fileName = 'php_' . $fileName;
+ }
wfSuppressWarnings();
- dl( $extension . '.' . PHP_SHLIB_SUFFIX );
+ dl( $fileName . '.' . PHP_SHLIB_SUFFIX );
wfRestoreWarnings();
}
return extension_loaded( $extension );
@@ -2744,142 +2918,12 @@ function wfRelativePath( $path, $from ) {
}
/**
- * Backwards array plus for people who haven't bothered to read the PHP manual
- * XXX: will not darn your socks for you.
- *
- * @param $array1 Array
- * @param [$array2, [...]] Arrays
- * @return Array
- */
-function wfArrayMerge( $array1/* ... */ ) {
- $args = func_get_args();
- $args = array_reverse( $args, true );
- $out = array();
- foreach ( $args as $arg ) {
- $out += $arg;
- }
- return $out;
-}
-
-/**
- * Merge arrays in the style of getUserPermissionsErrors, with duplicate removal
- * e.g.
- * wfMergeErrorArrays(
- * array( array( 'x' ) ),
- * array( array( 'x', '2' ) ),
- * array( array( 'x' ) ),
- * array( array( 'y') )
- * );
- * returns:
- * array(
- * array( 'x', '2' ),
- * array( 'x' ),
- * array( 'y' )
- * )
- */
-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;
- }
- }
- return array_values( $out );
-}
-
-/**
- * parse_url() work-alike, but non-broken. Differences:
- *
- * 1) Does not raise warnings on bad URLs (just returns false)
- * 2) Handles protocols that don't use :// (e.g., mailto: and news:) correctly
- * 3) Adds a "delimiter" element to the array, either '://' or ':' (see (2))
- *
- * @param $url String: a URL to parse
- * @return Array: bits of the URL in an associative array, per PHP docs
- */
-function wfParseUrl( $url ) {
- global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
- wfSuppressWarnings();
- $bits = parse_url( $url );
- wfRestoreWarnings();
- if ( !$bits ) {
- return false;
- }
-
- // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
- if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
- $bits['delimiter'] = '://';
- } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
- $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'] ) ) {
- $bits['host'] = $bits['path'];
- $bits['path'] = '';
- }
- } else {
- return false;
- }
-
- return $bits;
-}
-
-/**
- * Make a URL index, appropriate for the el_index field of externallinks.
- */
-function wfMakeUrlIndex( $url ) {
- $bits = wfParseUrl( $url );
-
- // Reverse the labels in the hostname, convert to lower case
- // For emails reverse domainpart only
- if ( $bits['scheme'] == 'mailto' ) {
- $mailparts = explode( '@', $bits['host'], 2 );
- if ( count( $mailparts ) === 2 ) {
- $domainpart = strtolower( implode( '.', array_reverse( explode( '.', $mailparts[1] ) ) ) );
- } else {
- // No domain specified, don't mangle it
- $domainpart = '';
- }
- $reversedHost = $domainpart . '@' . $mailparts[0];
- } else {
- $reversedHost = strtolower( implode( '.', array_reverse( explode( '.', $bits['host'] ) ) ) );
- }
- // Add an extra dot to the end
- // Why? Is it in wrong place in mailto links?
- if ( substr( $reversedHost, -1, 1 ) !== '.' ) {
- $reversedHost .= '.';
- }
- // Reconstruct the pseudo-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['path'] ) ) {
- $index .= $bits['path'];
- } else {
- $index .= '/';
- }
- if ( isset( $bits['query'] ) ) {
- $index .= '?' . $bits['query'];
- }
- if ( isset( $bits['fragment'] ) ) {
- $index .= '#' . $bits['fragment'];
- }
- return $index;
-}
-
-/**
* Do any deferred updates and clear the list
*
- * @param $commit Boolean: commit after every update to prevent lock contention
+ * @param $commit String: set to 'commit' to commit after every update to
+ * prevent lock contention
*/
-function wfDoUpdates( $commit = false ) {
+function wfDoUpdates( $commit = '' ) {
global $wgDeferredUpdateList;
wfProfileIn( __METHOD__ );
@@ -2890,14 +2934,15 @@ function wfDoUpdates( $commit = false ) {
return;
}
- if ( $commit ) {
+ $doCommit = $commit == 'commit';
+ if ( $doCommit ) {
$dbw = wfGetDB( DB_MASTER );
}
foreach ( $wgDeferredUpdateList as $update ) {
$update->doUpdate();
- if ( $commit && $dbw->trxLevel() ) {
+ if ( $doCommit && $dbw->trxLevel() ) {
$dbw->commit();
}
}
@@ -2941,7 +2986,7 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
// Decode and validate input string
$input = strtolower( $input );
for( $i = 0; $i < strlen( $input ); $i++ ) {
- $n = strpos( $digitChars, $input{$i} );
+ $n = strpos( $digitChars, $input[$i] );
if( $n === false || $n > $sourceBase ) {
return false;
}
@@ -2995,36 +3040,18 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
/**
* Create an object with a given name and an array of construct parameters
+ *
* @param $name String
* @param $p Array: parameters
+ * @deprecated since 1.18, warnings in 1.18, removal in 1.20
*/
function wfCreateObject( $name, $p ) {
- $p = array_values( $p );
- switch ( count( $p ) ) {
- case 0:
- return new $name;
- case 1:
- return new $name( $p[0] );
- case 2:
- return new $name( $p[0], $p[1] );
- case 3:
- return new $name( $p[0], $p[1], $p[2] );
- case 4:
- return new $name( $p[0], $p[1], $p[2], $p[3] );
- case 5:
- return new $name( $p[0], $p[1], $p[2], $p[3], $p[4] );
- 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' );
- }
+ wfDeprecated( __FUNCTION__ );
+ return MWFunction::newObj( $name, $p );
}
function wfHttpOnlySafe() {
global $wgHttpOnlyBlacklist;
- if( !version_compare( '5.2', PHP_VERSION, '<' ) ) {
- return false;
- }
if( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
foreach( $wgHttpOnlyBlacklist as $regex ) {
@@ -3039,18 +3066,31 @@ function wfHttpOnlySafe() {
/**
* Initialise php session
+ *
+ * @param $sessionId Bool
*/
function wfSetupSession( $sessionId = false ) {
global $wgSessionsInMemcached, $wgCookiePath, $wgCookieDomain,
$wgCookieSecure, $wgCookieHttpOnly, $wgSessionHandler;
if( $wgSessionsInMemcached ) {
- require_once( 'MemcachedSessions.php' );
+ if ( !defined( 'MW_COMPILED' ) ) {
+ global $IP;
+ require_once( "$IP/includes/cache/MemcachedSessions.php" );
+ }
+ session_set_save_handler( 'memsess_open', 'memsess_close', 'memsess_read',
+ 'memsess_write', 'memsess_destroy', 'memsess_gc' );
+
+ // It's necessary to register a shutdown function to call session_write_close(),
+ // because by the time the request shutdown function for the session module is
+ // called, $wgMemc has already been destroyed. Shutdown functions registered
+ // this way are called before object destruction.
+ register_shutdown_function( 'memsess_write_close' );
} 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 );
}
- $httpOnlySafe = wfHttpOnlySafe();
+ $httpOnlySafe = wfHttpOnlySafe() && $wgCookieHttpOnly;
wfDebugLog( 'cookie',
'session_set_cookie_params: "' . implode( '", "',
array(
@@ -3058,13 +3098,8 @@ function wfSetupSession( $sessionId = false ) {
$wgCookiePath,
$wgCookieDomain,
$wgCookieSecure,
- $httpOnlySafe && $wgCookieHttpOnly ) ) . '"' );
- if( $httpOnlySafe && $wgCookieHttpOnly ) {
- session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookieHttpOnly );
- } else {
- // PHP 5.1 throws warnings if you pass the HttpOnly parameter for 5.2.
- session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
- }
+ $httpOnlySafe ) ) . '"' );
+ session_set_cookie_params( 0, $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $httpOnlySafe );
session_cache_limiter( 'private, must-revalidate' );
if ( $sessionId ) {
session_id( $sessionId );
@@ -3077,6 +3112,7 @@ function wfSetupSession( $sessionId = false ) {
/**
* Get an object from the precompiled serialized directory
*
+ * @param $name String
* @return Mixed: the variable on success, false on failure
*/
function wfGetPrecompiledData( $name ) {
@@ -3092,43 +3128,11 @@ function wfGetPrecompiledData( $name ) {
return false;
}
-function wfGetCaller( $level = 2 ) {
- $backtrace = wfDebugBacktrace();
- if ( isset( $backtrace[$level] ) ) {
- return wfFormatStackFrame( $backtrace[$level] );
- } else {
- $caller = 'unknown';
- }
- return $caller;
-}
-
-/**
- * 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( $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'];
-}
-
/**
* Get a cache key
+ *
+ * @param varargs
+ * @return String
*/
function wfMemcKey( /*... */ ) {
$args = func_get_args();
@@ -3139,6 +3143,11 @@ function wfMemcKey( /*... */ ) {
/**
* Get a cache key for a foreign DB
+ *
+ * @param $db String
+ * @param $prefix String
+ * @param varargs String
+ * @return String
*/
function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
$args = array_slice( func_get_args(), 2 );
@@ -3153,6 +3162,8 @@ function wfForeignMemcKey( $db, $prefix /*, ... */ ) {
/**
* Get an ASCII string identifying this wiki
* This is used as a prefix in memcached keys
+ *
+ * @return String
*/
function wfWikiID() {
global $wgDBprefix, $wgDBname;
@@ -3165,6 +3176,9 @@ function wfWikiID() {
/**
* Split a wiki ID into DB name and table prefix
+ *
+ * @param $wiki String
+ * @param $bits String
*/
function wfSplitWikiID( $wiki ) {
$bits = explode( '-', $wiki, 2 );
@@ -3176,6 +3190,7 @@ function wfSplitWikiID( $wiki ) {
/**
* 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
* queries, or an integer >= 0 for a particular server.
@@ -3190,6 +3205,9 @@ function wfSplitWikiID( $wiki ) {
* will always return the same object, unless the underlying connection or load
* balancer is manually destroyed.
*
+ * Note 2: use $this->getDB() in maintenance scripts that may be invoked by
+ * updater to ensure that a proper database is being updated.
+ *
* @return DatabaseBase
*/
function &wfGetDB( $db, $groups = array(), $wiki = false ) {
@@ -3208,6 +3226,8 @@ function wfGetLB( $wiki = false ) {
/**
* Get the load balancer factory object
+ *
+ * @return LBFactory
*/
function &wfGetLBFactory() {
return LBFactory::singleton();
@@ -3216,6 +3236,7 @@ function &wfGetLBFactory() {
/**
* Find a file.
* Shortcut for RepoGroup::singleton()->findFile()
+ *
* @param $title String or Title object
* @param $options Associative array of options:
* time: requested time for an archived image, or false for the
@@ -3239,6 +3260,7 @@ function wfFindFile( $title, $options = array() ) {
/**
* Get an object referring to a locally registered file.
* Returns a valid placeholder object if the file does not exist.
+ *
* @param $title Title or String
* @return File, or null if passed an invalid Title
*/
@@ -3250,6 +3272,7 @@ function wfLocalFile( $title ) {
* Should low-performance queries be disabled?
*
* @return Boolean
+ * @codeCoverageIgnore
*/
function wfQueriesMustScale() {
global $wgMiserMode;
@@ -3307,14 +3330,16 @@ function wfBoolToStr( $value ) {
/**
* Load an extension messages file
- * @deprecated in 1.16 (warnings in 1.18, removed in ?)
+ *
+ * @deprecated since 1.16, warnings in 1.18, remove in 1.20
+ * @codeCoverageIgnore
*/
-function wfLoadExtensionMessages( $extensionName, $langcode = false ) {
+function wfLoadExtensionMessages() {
+ wfDeprecated( __FUNCTION__ );
}
/**
- * Get a platform-independent path to the null file, e.g.
- * /dev/null
+ * Get a platform-independent path to the null file, e.g. /dev/null
*
* @return string
*/
@@ -3325,27 +3350,8 @@ function wfGetNull() {
}
/**
- * Displays a maxlag error
- *
- * @param $host String: server that lags the most
- * @param $lag Integer: maxlag (actual)
- * @param $maxLag Integer: maxlag (requested)
- */
-function wfMaxlagError( $host, $lag, $maxLag ) {
- global $wgShowHostnames;
- header( 'HTTP/1.1 503 Service Unavailable' );
- header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
- header( 'X-Database-Lag: ' . intval( $lag ) );
- header( 'Content-Type: text/plain' );
- if( $wgShowHostnames ) {
- echo "Waiting for $host: $lag seconds lagged\n";
- } else {
- echo "Waiting for a database server: $lag seconds lagged\n";
- }
-}
-
-/**
* Throws a warning that $function is deprecated
+ *
* @param $function String
* @return null
*/
@@ -3362,32 +3368,33 @@ function wfDeprecated( $function ) {
* $wgDevelopmentWarnings
*
* @param $msg String: message to send
- * @param $callerOffset Integer: number of itmes to go back in the backtrace to
+ * @param $callerOffset Integer: number of items to go back in the backtrace to
* find the correct caller (1 = function calling wfWarn, ...)
* @param $level Integer: PHP error level; only used when $wgDevelopmentWarnings
* is true
*/
function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
+ global $wgDevelopmentWarnings;
+
$callers = wfDebugBacktrace();
- if( isset( $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'] . '::';
}
- if( isset( $callerfunc['function'] ) ) {
+ if ( isset( $callerfunc['function'] ) ) {
$func .= $callerfunc['function'];
}
$msg .= " [Called from $func in $file]";
}
- global $wgDevelopmentWarnings;
if ( $wgDevelopmentWarnings ) {
trigger_error( $msg, $level );
} else {
@@ -3396,44 +3403,35 @@ function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
}
/**
- * Sleep until the worst slave's replication lag is less than or equal to
- * $maxLag, in seconds. Use this when updating very large numbers of rows, as
+ * Modern version of wfWaitForSlaves(). Instead of looking at replication lag
+ * and waiting for it to go down, this waits for the slaves to catch up to the
+ * master position. Use this when updating very large numbers of rows, as
* in maintenance scripts, to avoid causing too much lag. Of course, this is
* a no-op if there are no slaves.
*
- * Every time the function has to wait for a slave, it will print a message to
- * that effect (and then sleep for a little while), so it's probably not best
- * to use this outside maintenance scripts in its present form.
- *
- * @param $maxLag Integer
+ * @param $maxLag Integer (deprecated)
* @param $wiki mixed Wiki identifier accepted by wfGetLB
* @return null
*/
-function wfWaitForSlaves( $maxLag, $wiki = false ) {
- if( $maxLag ) {
- $lb = wfGetLB( $wiki );
- list( $host, $lag ) = $lb->getMaxLag( $wiki );
- while( $lag > $maxLag ) {
- wfSuppressWarnings();
- $name = gethostbyaddr( $host );
- wfRestoreWarnings();
- if( $name !== false ) {
- $host = $name;
- }
- print "Waiting for $host (lagged $lag seconds)...\n";
- sleep( $maxLag );
- list( $host, $lag ) = $lb->getMaxLag();
- }
+function wfWaitForSlaves( $maxLag = false, $wiki = false ) {
+ $lb = wfGetLB( $wiki );
+ // bug 27975 - Don't try to wait for slaves if there are none
+ // Prevents permission error when getting master position
+ if ( $lb->getServerCount() > 1 ) {
+ $dbw = $lb->getConnection( DB_MASTER );
+ $pos = $dbw->getMasterPos();
+ $lb->waitForAll( $pos );
}
}
/**
* Used to be used for outputting text in the installer/updater
- * @deprecated Warnings in 1.19, removal in 1.20
+ * @deprecated since 1.18, warnings in 1.18, remove in 1.20
*/
function wfOut( $s ) {
+ wfDeprecated( __METHOD__ );
global $wgCommandLineMode;
- if ( $wgCommandLineMode && !defined( 'MEDIAWIKI_INSTALL' ) ) {
+ if ( $wgCommandLineMode ) {
echo $s;
} else {
echo htmlspecialchars( $s );
@@ -3444,6 +3442,7 @@ function wfOut( $s ) {
/**
* Count down from $n to zero on the terminal, with a one-second pause
* between showing each number. For use in command-line scripts.
+ * @codeCoverageIgnore
*/
function wfCountDown( $n ) {
for ( $i = $n; $i >= 0; $i-- ) {
@@ -3463,6 +3462,8 @@ function wfCountDown( $n ) {
* Generate a random 32-character hexadecimal token.
* @param $salt Mixed: some sort of salt, if necessary, to add to random
* characters before hashing.
+ * @return string
+ * @codeCoverageIgnore
*/
function wfGenerateToken( $salt = '' ) {
$salt = serialize( $salt );
@@ -3471,7 +3472,9 @@ function wfGenerateToken( $salt = '' ) {
/**
* Replace all invalid characters with -
+ *
* @param $name Mixed: filename to process
+ * @return String
*/
function wfStripIllegalFilenameChars( $name ) {
global $wgIllegalFileChars;
@@ -3487,46 +3490,8 @@ function wfStripIllegalFilenameChars( $name ) {
}
/**
- * Insert array into another array after the specified *KEY*
- * @param $array Array: The array.
- * @param $insert Array: The array to insert.
- * @param $after Mixed: The key to insert after
- */
-function wfArrayInsertAfter( $array, $insert, $after ) {
- // Find the offset of the element to insert after.
- $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 );
-
- $output = $before + $insert + $after;
-
- return $output;
-}
-
-/* Recursively converts the parameter (an object) to an array with the same data */
-function wfObjectToArray( $objOrArray, $recursive = true ) {
- $array = array();
- 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;
-}
-
-/**
* Set PHP's memory limit to the larger of php.ini or $wgMemoryLimit;
+ *
* @return Integer value memory was set to.
*/
function wfMemoryLimit() {
@@ -3553,6 +3518,7 @@ function wfMemoryLimit() {
/**
* Converts shorthand byte notation to integer form
+ *
* @param $string String
* @return Integer
*/
@@ -3582,19 +3548,25 @@ function wfShorthandToInteger( $string = '' ) {
/**
* Get the normalised IETF language tag
+ * See unit test for examples.
+ *
* @param $code String: The language code.
* @return $langCode String: The language code which complying with BCP 47 standards.
*/
function wfBCP47( $code ) {
$codeSegment = explode( '-', $code );
+ $codeBCP = array();
foreach ( $codeSegment as $segNo => $seg ) {
if ( count( $codeSegment ) > 0 ) {
+ // when previous segment is x, it is a private segment and should be lc
+ if( $segNo > 0 && strtolower( $codeSegment[($segNo - 1)] ) == 'x') {
+ $codeBCP[$segNo] = strtolower( $seg );
// ISO 3166 country code
- if ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
+ } elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
$codeBCP[$segNo] = strtoupper( $seg );
// ISO 15924 script code
} elseif ( ( strlen( $seg ) == 4 ) && ( $segNo > 0 ) ) {
- $codeBCP[$segNo] = ucfirst( $seg );
+ $codeBCP[$segNo] = ucfirst( strtolower( $seg ) );
// Use lowercase for other cases
} else {
$codeBCP[$segNo] = strtolower( $seg );
@@ -3608,13 +3580,89 @@ function wfBCP47( $code ) {
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 );
+/**
+ * Get a cache object.
+ *
+ * @param $inputType integer Cache type, one the the CACHE_* constants.
+ * @return BagOStuff
+ */
+function wfGetCache( $inputType ) {
+ return ObjectCache::getInstance( $inputType );
+}
+
+/**
+ * Get the main cache object
+ *
+ * @return BagOStuff
+ */
+function wfGetMainCache() {
+ global $wgMainCacheType;
+ return ObjectCache::getInstance( $wgMainCacheType );
+}
+
+/**
+ * Get the cache object used by the message cache
+ *
+ * @return BagOStuff
+ */
+function wfGetMessageCacheStorage() {
+ global $wgMessageCacheType;
+ return ObjectCache::getInstance( $wgMessageCacheType );
+}
+
+/**
+ * Get the cache object used by the parser cache
+ *
+ * @return BagOStuff
+ */
+function wfGetParserCacheStorage() {
+ global $wgParserCacheType;
+ return ObjectCache::getInstance( $wgParserCacheType );
+}
+
+/**
+ * Call hook functions defined in $wgHooks
+ *
+ * @param $event String: event name
+ * @param $args Array: parameters passed to hook functions
+ * @return Boolean
+ */
+function wfRunHooks( $event, $args = array() ) {
+ return Hooks::run( $event, $args );
+}
+
+/**
+ * Wrapper around php's unpack.
+ *
+ * @param $format String: The format string (See php's docs)
+ * @param $data: A binary string of binary data
+ * @param $length integer or false: The minimun length of $data. This is to
+ * prevent reading beyond the end of $data. false to disable the check.
+ *
+ * Also be careful when using this function to read unsigned 32 bit integer
+ * because php might make it negative.
+ *
+ * @throws MWException if $data not long enough, or if unpack fails
+ * @return Associative array of the extracted data
+ */
+function wfUnpack( $format, $data, $length=false ) {
+ if ( $length !== false ) {
+ $realLen = strlen( $data );
+ if ( $realLen < $length ) {
+ throw new MWException( "Tried to use wfUnpack on a "
+ . "string of length $realLen, but needed one "
+ . "of at least length $length."
+ );
}
}
- return $ret;
+
+ wfSuppressWarnings();
+ $result = unpack( $format, $data );
+ wfRestoreWarnings();
+
+ if ( $result === false ) {
+ // If it cannot extract the packed data.
+ throw new MWException( "unpack could not unpack binary data" );
+ }
+ return $result;
}
diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php
index be912daf..948de61f 100644
--- a/includes/HTMLForm.php
+++ b/includes/HTMLForm.php
@@ -33,6 +33,10 @@
* 'help-message' -- message key for a message to use as a help text.
* can be an array of msg key and then parameters to
* the message.
+ * Overwrites 'help-messages'.
+ * 'help-messages' -- array of message key. As above, each item can
+ * be an array of msg key and then parameters.
+ * Overwrites 'help-message'.
* 'required' -- passed through to the object, indicating that it
* is a required field.
* 'size' -- the length of text fields
@@ -50,7 +54,6 @@
* TODO: Document 'section' / 'subsection' stuff
*/
class HTMLForm {
- static $jsAdded = false;
# A mapping of 'type' inputs onto standard HTMLFormField subclasses
static $typeMappings = array(
@@ -65,6 +68,7 @@ class HTMLForm {
'float' => 'HTMLFloatField',
'info' => 'HTMLInfoField',
'selectorother' => 'HTMLSelectOrOtherField',
+ 'selectandother' => 'HTMLSelectAndOtherField',
'submit' => 'HTMLSubmitField',
'hidden' => 'HTMLHiddenField',
'edittools' => 'HTMLEditTools',
@@ -88,6 +92,8 @@ class HTMLForm {
protected $mPre = '';
protected $mHeader = '';
protected $mFooter = '';
+ protected $mSectionHeaders = array();
+ protected $mSectionFooters = array();
protected $mPost = '';
protected $mId;
@@ -95,6 +101,8 @@ class HTMLForm {
protected $mSubmitName;
protected $mSubmitText;
protected $mSubmitTooltip;
+
+ protected $mContext; // <! IContextSource
protected $mTitle;
protected $mMethod = 'post';
@@ -107,10 +115,22 @@ class HTMLForm {
/**
* Build a new HTMLForm from an array of field attributes
* @param $descriptor Array of Field constructs, as described above
+ * @param $context IContextSource available since 1.18, will become compulsory in 1.18.
+ * Obviates the need to call $form->setTitle()
* @param $messagePrefix String a prefix to go in front of default messages
*/
- public function __construct( $descriptor, $messagePrefix = '' ) {
- $this->mMessagePrefix = $messagePrefix;
+ public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) {
+ if( $context instanceof IContextSource ){
+ $this->mContext = $context;
+ $this->mTitle = false; // We don't need them to set a title
+ $this->mMessagePrefix = $messagePrefix;
+ } else {
+ // B/C since 1.18
+ if( is_string( $context ) && $messagePrefix === '' ){
+ // it's actually $messagePrefix
+ $this->mMessagePrefix = $context;
+ }
+ }
// Expand out into a tree.
$loadedDescriptor = array();
@@ -153,14 +173,9 @@ class HTMLForm {
/**
* Add the HTMLForm-specific JavaScript, if it hasn't been
* done already.
+ * @deprecated since 1.18 load modules with ResourceLoader instead
*/
- static function addJS() {
- if ( self::$jsAdded ) return;
-
- global $wgOut;
-
- $wgOut->addModules( 'mediawiki.legacy.htmlform' );
- }
+ static function addJS() { }
/**
* Initialise a new Object for the field
@@ -173,12 +188,14 @@ class HTMLForm {
} elseif ( isset( $descriptor['type'] ) ) {
$class = self::$typeMappings[$descriptor['type']];
$descriptor['class'] = $class;
+ } else {
+ $class = null;
}
if ( !$class ) {
throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) );
}
-
+
$descriptor['fieldname'] = $fieldname;
$obj = new $class( $descriptor );
@@ -191,27 +208,23 @@ class HTMLForm {
*/
function prepareForm() {
# Check if we have the info we need
- if ( ! $this->mTitle ) {
+ if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) {
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 submitting, with edit token check first
- * @return Status|boolean
+ * @return Status|boolean
*/
function tryAuthorizedSubmit() {
- global $wgUser, $wgRequest;
- $editToken = $wgRequest->getVal( 'wpEditToken' );
+ $editToken = $this->getRequest()->getVal( 'wpEditToken' );
$result = false;
- if ( $this->getMethod() != 'post' || $wgUser->matchEditToken( $editToken ) ) {
+ if ( $this->getMethod() != 'post' || $this->getUser()->matchEditToken( $editToken ) ) {
$result = $this->trySubmit();
}
return $result;
@@ -304,14 +317,34 @@ class HTMLForm {
/**
* Add header text, inside the form.
* @param $msg String complete text of message to display
+ * @param $section The section to add the header to
*/
- function addHeaderText( $msg ) { $this->mHeader .= $msg; }
+ function addHeaderText( $msg, $section = null ) {
+ if ( is_null( $section ) ) {
+ $this->mHeader .= $msg;
+ } else {
+ if ( !isset( $this->mSectionHeaders[$section] ) ) {
+ $this->mSectionHeaders[$section] = '';
+ }
+ $this->mSectionHeaders[$section] .= $msg;
+ }
+ }
/**
* Add footer text, inside the form.
* @param $msg String complete text of message to display
+ * @param $section string The section to add the footer text to
*/
- function addFooterText( $msg ) { $this->mFooter .= $msg; }
+ function addFooterText( $msg, $section = null ) {
+ if ( is_null( $section ) ) {
+ $this->mFooter .= $msg;
+ } else {
+ if ( !isset( $this->mSectionFooters[$section] ) ) {
+ $this->mSectionFooters[$section] = '';
+ }
+ $this->mSectionFooters[$section] .= $msg;
+ }
+ }
/**
* Add text to the end of the display.
@@ -340,10 +373,9 @@ class HTMLForm {
* @param $submitResult Mixed output from HTMLForm::trySubmit()
*/
function displayForm( $submitResult ) {
- global $wgOut;
-
# For good measure (it is the default)
- $wgOut->preventClickjacking();
+ $this->getOutput()->preventClickjacking();
+ $this->getOutput()->addModules( 'mediawiki.htmlform' );
$html = ''
. $this->getErrors( $submitResult )
@@ -356,7 +388,7 @@ class HTMLForm {
$html = $this->wrapForm( $html );
- $wgOut->addHTML( ''
+ $this->getOutput()->addHTML( ''
. $this->mPre
. $html
. $this->mPost
@@ -397,12 +429,15 @@ class HTMLForm {
* @return String HTML.
*/
function getHiddenFields() {
- global $wgUser;
+ global $wgUsePathInfo;
$html = '';
-
if( $this->getMethod() == 'post' ){
- $html .= Html::hidden( 'wpEditToken', $wgUser->editToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
+ $html .= Html::hidden( 'wpEditToken', $this->getUser()->editToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
+ $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
+ }
+
+ if ( !$wgUsePathInfo && $this->getMethod() == 'get' ) {
$html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
}
@@ -431,8 +466,7 @@ class HTMLForm {
}
if ( isset( $this->mSubmitTooltip ) ) {
- global $wgUser;
- $attribs += $wgUser->getSkin()->tooltipAndAccessKeyAttribs( $this->mSubmitTooltip );
+ $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
}
$attribs['class'] = 'mw-htmlform-submit';
@@ -479,16 +513,15 @@ class HTMLForm {
/**
* Format and display an error message stack.
- * @param $errors Mixed String or Array of message keys
+ * @param $errors String|Array|Status
* @return String
*/
function getErrors( $errors ) {
if ( $errors instanceof Status ) {
- global $wgOut;
if ( $errors->isOK() ) {
$errorstr = '';
} else {
- $errorstr = $wgOut->parse( $errors->getWikiText() );
+ $errorstr = $this->getOutput()->parse( $errors->getWikiText() );
}
} elseif ( is_array( $errors ) ) {
$errorstr = $this->formatErrors( $errors );
@@ -506,7 +539,7 @@ class HTMLForm {
* @param $errors Array of message keys/values
* @return String HTML, a <ul> list of errors
*/
- static function formatErrors( $errors ) {
+ public static function formatErrors( $errors ) {
$errorstr = '';
foreach ( $errors as $error ) {
@@ -519,7 +552,7 @@ class HTMLForm {
$errorstr .= Html::rawElement(
'li',
- null,
+ array(),
wfMsgExt( $msg, array( 'parseinline' ), $error )
);
}
@@ -557,7 +590,8 @@ class HTMLForm {
/**
* Set the id for the submit button.
- * @param $t String. FIXME: Integrity is *not* validated
+ * @param $t String.
+ * @todo FIXME: Integrity of $t is *not* validated
*/
function setSubmitID( $t ) {
$this->mSubmitID = $t;
@@ -597,9 +631,41 @@ class HTMLForm {
* @return Title
*/
function getTitle() {
- return $this->mTitle;
+ return $this->mTitle === false
+ ? $this->getContext()->getTitle()
+ : $this->mTitle;
+ }
+
+ /**
+ * @return IContextSource
+ */
+ public function getContext(){
+ return $this->mContext instanceof IContextSource
+ ? $this->mContext
+ : RequestContext::getMain();
+ }
+
+ /**
+ * @return OutputPage
+ */
+ public function getOutput(){
+ return $this->getContext()->getOutput();
+ }
+
+ /**
+ * @return WebRequest
+ */
+ public function getRequest(){
+ return $this->getContext()->getRequest();
}
-
+
+ /**
+ * @return User
+ */
+ public function getUser(){
+ return $this->getContext()->getUser();
+ }
+
/**
* Set the method used to submit the form
* @param $method String
@@ -607,16 +673,18 @@ class HTMLForm {
public function setMethod( $method='post' ){
$this->mMethod = $method;
}
-
+
public function getMethod(){
return $this->mMethod;
}
/**
* TODO: Document
- * @param $fields
+ * @param $fields array of fields (either arrays or objects)
+ * @param $sectionName string ID attribute of the <table> tag for this section, ignored if empty
+ * @param $fieldsetIDPrefix string ID prefix for the <fieldset> tag of each subsection, ignored if empty
*/
- function displaySection( $fields, $sectionName = '' ) {
+ function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) {
$tableHtml = '';
$subsectionHtml = '';
$hasLeftColumn = false;
@@ -632,8 +700,18 @@ class HTMLForm {
$hasLeftColumn = true;
} elseif ( is_array( $value ) ) {
$section = $this->displaySection( $value, $key );
- $legend = wfMsg( "{$this->mMessagePrefix}-$key" );
- $subsectionHtml .= Xml::fieldset( $legend, $section ) . "\n";
+ $legend = $this->getLegend( $key );
+ if ( isset( $this->mSectionHeaders[$key] ) ) {
+ $section = $this->mSectionHeaders[$key] . $section;
+ }
+ if ( isset( $this->mSectionFooters[$key] ) ) {
+ $section .= $this->mSectionFooters[$key];
+ }
+ $attributes = array();
+ if ( $fieldsetIDPrefix ) {
+ $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" );
+ }
+ $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n";
}
}
@@ -661,8 +739,6 @@ class HTMLForm {
* Construct the form fields from the Descriptor array
*/
function loadData() {
- global $wgRequest;
-
$fieldData = array();
foreach ( $this->mFlatFields as $fieldname => $field ) {
@@ -671,7 +747,7 @@ class HTMLForm {
} elseif ( !empty( $field->mParams['disabled'] ) ) {
$fieldData[$fieldname] = $field->getDefault();
} else {
- $fieldData[$fieldname] = $field->loadDataFromRequest( $wgRequest );
+ $fieldData[$fieldname] = $field->loadDataFromRequest( $this->getRequest() );
}
}
@@ -703,6 +779,16 @@ class HTMLForm {
function filterDataForSubmit( $data ) {
return $data;
}
+
+ /**
+ * Get a string to go in the <legend> of a section fieldset. Override this if you
+ * want something more complicated
+ * @param $key String
+ * @return String
+ */
+ public function getLegend( $key ) {
+ return wfMsg( "{$this->mMessagePrefix}-$key" );
+ }
}
/**
@@ -719,6 +805,10 @@ abstract class HTMLFormField {
protected $mID;
protected $mClass = '';
protected $mDefault;
+
+ /**
+ * @var HTMLForm
+ */
public $mParent;
/**
@@ -785,7 +875,7 @@ abstract class HTMLFormField {
/**
* Initialise the object
- * @param $params Associative Array. See HTMLForm doc for syntax.
+ * @param $params array Associative Array. See HTMLForm doc for syntax.
*/
function __construct( $params ) {
$this->mParams = $params;
@@ -810,12 +900,12 @@ abstract class HTMLFormField {
if ( isset( $params['name'] ) ) {
$this->mName = $params['name'];
}
-
+
$validName = Sanitizer::escapeId( $this->mName );
if ( $this->mName != $validName && !isset( $params['nodata'] ) ) {
throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ );
}
-
+
$this->mID = "mw-input-{$this->mName}";
if ( isset( $params['default'] ) ) {
@@ -854,22 +944,23 @@ abstract class HTMLFormField {
*/
function getTableRow( $value ) {
# Check for invalid data.
- global $wgRequest;
$errors = $this->validate( $value, $this->mParent->mFieldData );
-
+
$cellAttributes = array();
$verticalLabel = false;
-
+
if ( !empty($this->mParams['vertical-label']) ) {
$cellAttributes['colspan'] = 2;
$verticalLabel = true;
}
- if ( $errors === true || ( !$wgRequest->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) {
+ if ( $errors === true || ( !$this->mParent->getRequest()->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) {
$errors = '';
+ $errorClass = '';
} else {
- $errors = Html::rawElement( 'span', array( 'class' => 'error' ), $errors );
+ $errors = self::formatErrors( $errors );
+ $errorClass = 'mw-htmlform-invalid-input';
}
$label = $this->getLabelHtml( $cellAttributes );
@@ -878,29 +969,37 @@ abstract class HTMLFormField {
array( 'class' => 'mw-input' ) + $cellAttributes,
$this->getInputHTML( $value ) . "\n$errors"
);
-
+
$fieldType = get_class( $this );
-
- if ($verticalLabel) {
+
+ 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}" ),
+ array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
$field );
} else {
$html = Html::rawElement( 'tr',
- array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass}" ),
+ array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
$label . $field );
}
$helptext = null;
if ( isset( $this->mParams['help-message'] ) ) {
- $msg = $this->mParams['help-message'];
- $helptext = wfMsgExt( $msg, 'parseinline' );
- if ( wfEmptyMsg( $msg, $helptext ) ) {
- # Never mind
- $helptext = null;
+ $msg = wfMessage( $this->mParams['help-message'] );
+ if ( $msg->exists() ) {
+ $helptext = $msg->parse();
+ }
+ } elseif ( isset( $this->mParams['help-messages'] ) ) {
+ # help-message can be passed a message key (string) or an array containing
+ # a message key and additional parameters. This makes it impossible to pass
+ # an array of message key
+ foreach( $this->mParams['help-messages'] as $name ) {
+ $msg = wfMessage( $name );
+ if( $msg->exists() ) {
+ $helptext .= $msg->parse(); // append message
+ }
}
} elseif ( isset( $this->mParams['help'] ) ) {
$helptext = $this->mParams['help'];
@@ -950,10 +1049,7 @@ abstract class HTMLFormField {
if ( empty( $this->mParams['tooltip'] ) ) {
return array();
}
-
- global $wgUser;
-
- return $wgUser->getSkin()->tooltipAndAccessKeyAttribs( $this->mParams['tooltip'] );
+ return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
}
/**
@@ -976,6 +1072,35 @@ abstract class HTMLFormField {
return $flatOpts;
}
+
+ /**
+ * Formats one or more errors as accepted by field validation-callback.
+ * @param $errors String|Message|Array of strings or Message instances
+ * @return String html
+ * @since 1.18
+ */
+ protected static function formatErrors( $errors ) {
+ if ( is_array( $errors ) && count( $errors ) === 1 ) {
+ $errors = array_shift( $errors );
+ }
+
+ if ( is_array( $errors ) ) {
+ $lines = array();
+ foreach ( $errors as $error ) {
+ if ( $error instanceof Message ) {
+ $lines[] = Html::rawElement( 'li', array(), $error->parse() );
+ } else {
+ $lines[] = Html::rawElement( 'li', array(), $error );
+ }
+ }
+ return Html::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) );
+ } else {
+ if ( $errors instanceof Message ) {
+ $errors = $errors->parse();
+ }
+ return Html::rawElement( 'span', array( 'class' => 'error' ), $errors );
+ }
+ }
}
class HTMLTextField extends HTMLFormField {
@@ -1098,11 +1223,11 @@ class HTMLFloatField extends HTMLTextField {
if ( $p !== true ) {
return $p;
}
-
+
$value = trim( $value );
# http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers
- # with the addition that a leading '+' sign is ok.
+ # with the addition that a leading '+' sign is ok.
if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) {
return wfMsgExt( 'htmlform-float-invalid', 'parse' );
}
@@ -1141,8 +1266,8 @@ class HTMLIntField extends HTMLFloatField {
}
# 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
+ # 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().
@@ -1183,6 +1308,10 @@ class HTMLCheckField extends HTMLFormField {
return '&#160;';
}
+ /**
+ * @param $request WebRequest
+ * @return String
+ */
function loadDataFromRequest( $request ) {
$invert = false;
if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) {
@@ -1190,7 +1319,10 @@ class HTMLCheckField extends HTMLFormField {
}
// GetCheck won't work like we want for checks.
- if ( $request->getCheck( 'wpEditToken' ) ) {
+ // Fetch the value in either one of the two following case:
+ // - we have a valid token (form got posted or GET forged by the user)
+ // - checkbox name has a value (false or true), ie is not null
+ if ( $request->getCheck( 'wpEditToken' ) || $request->getVal( $this->mName )!== null ) {
// XOR has the following truth table, which is what we want
// INVERT VALUE | OUTPUT
// true true | false
@@ -1227,9 +1359,9 @@ class HTMLSelectField extends HTMLFormField {
$select = new XmlSelect( $this->mName, $this->mID, strval( $value ) );
# If one of the options' 'name' is int(0), it is automatically selected.
- # because PHP sucks and things int(0) == 'some string'.
+ # because PHP sucks and thinks int(0) == 'some string'.
# Working around this by forcing all of them to strings.
- foreach( $this->mParams['options'] as $key => &$opt ){
+ foreach( $this->mParams['options'] as &$opt ){
if( is_int( $opt ) ){
$opt = strval( $opt );
}
@@ -1254,7 +1386,8 @@ class HTMLSelectOrOtherField extends HTMLTextField {
function __construct( $params ) {
if ( !in_array( 'other', $params['options'], true ) ) {
- $params['options'][wfMsg( 'htmlform-selectorother-other' )] = 'other';
+ $msg = isset( $params['other'] ) ? $params['other'] : wfMsg( 'htmlform-selectorother-other' );
+ $params['options'][$msg] = 'other';
}
parent::__construct( $params );
@@ -1310,6 +1443,10 @@ class HTMLSelectOrOtherField extends HTMLTextField {
return "$select<br />\n$textbox";
}
+ /**
+ * @param $request WebRequest
+ * @return String
+ */
function loadDataFromRequest( $request ) {
if ( $request->getCheck( $this->mName ) ) {
$val = $request->getText( $this->mName );
@@ -1329,6 +1466,14 @@ class HTMLSelectOrOtherField extends HTMLTextField {
* Multi-select field
*/
class HTMLMultiSelectField extends HTMLFormField {
+
+ public function __construct( $params ){
+ parent::__construct( $params );
+ if( isset( $params['flatlist'] ) ){
+ $this->mClass .= ' mw-htmlform-multiselect-flatlist';
+ }
+ }
+
function validate( $value, $alldata ) {
$p = parent::validate( $value, $alldata );
@@ -1374,31 +1519,41 @@ class HTMLMultiSelectField extends HTMLFormField {
} else {
$thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info );
- $checkbox = Xml::check(
- $this->mName . '[]',
+ $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 />';
+ $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-multiselect-item' ), $checkbox );
}
}
return $html;
}
+ /**
+ * @param $request WebRequest
+ * @return String
+ */
function loadDataFromRequest( $request ) {
- # won't work with getCheck
- if ( $request->getCheck( 'wpEditToken' ) ) {
- $arr = $request->getArray( $this->mName );
-
- if ( !$arr ) {
- $arr = array();
+ if ( $this->mParent->getMethod() == 'post' ) {
+ if( $request->wasPosted() ){
+ # Checkboxes are just not added to the request arrays if they're not checked,
+ # so it's perfectly possible for there not to be an entry at all
+ return $request->getArray( $this->mName, array() );
+ } else {
+ # That's ok, the user has not yet submitted the form, so show the defaults
+ return $this->getDefault();
}
-
- return $arr;
} else {
- return $this->getDefault();
+ # This is the impossible case: if we look at $_GET and see no data for our
+ # field, is it because the user has not yet submitted the form, or that they
+ # have submitted it with all the options unchecked? We will have to assume the
+ # latter, which basically means that you can't specify 'positive' defaults
+ # for GET forms.
+ # @todo FIXME...
+ return $request->getArray( $this->mName, array() );
}
}
@@ -1416,6 +1571,161 @@ class HTMLMultiSelectField extends HTMLFormField {
}
/**
+ * Double field with a dropdown list constructed from a system message in the format
+ * * Optgroup header
+ * ** <option value>
+ * * New Optgroup header
+ * Plus a text field underneath for an additional reason. The 'value' of the field is
+ * ""<select>: <extra reason>"", or "<extra reason>" if nothing has been selected in the
+ * select dropdown.
+ * @todo FIXME: If made 'required', only the text field should be compulsory.
+ */
+class HTMLSelectAndOtherField extends HTMLSelectField {
+
+ function __construct( $params ) {
+ if ( array_key_exists( 'other', $params ) ) {
+ } elseif( array_key_exists( 'other-message', $params ) ){
+ $params['other'] = wfMessage( $params['other-message'] )->plain();
+ } else {
+ $params['other'] = null;
+ }
+
+ if ( array_key_exists( 'options', $params ) ) {
+ # Options array already specified
+ } elseif( array_key_exists( 'options-message', $params ) ){
+ # Generate options array from a system message
+ $params['options'] = self::parseMessage(
+ wfMessage( $params['options-message'] )->inContentLanguage()->plain(),
+ $params['other']
+ );
+ } else {
+ # Sulk
+ throw new MWException( 'HTMLSelectAndOtherField called without any options' );
+ }
+ $this->mFlatOptions = self::flattenOptions( $params['options'] );
+
+ parent::__construct( $params );
+ }
+
+ /**
+ * Build a drop-down box from a textual list.
+ * @param $string String message text
+ * @param $otherName String name of "other reason" option
+ * @return Array
+ * TODO: this is copied from Xml::listDropDown(), deprecate/avoid duplication?
+ */
+ public static function parseMessage( $string, $otherName=null ) {
+ if( $otherName === null ){
+ $otherName = wfMessage( 'htmlform-selectorother-other' )->plain();
+ }
+
+ $optgroup = false;
+ $options = array( $otherName => 'other' );
+
+ foreach ( explode( "\n", $string ) as $option ) {
+ $value = trim( $option );
+ if ( $value == '' ) {
+ continue;
+ } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
+ # A new group is starting...
+ $value = trim( substr( $value, 1 ) );
+ $optgroup = $value;
+ } elseif ( substr( $value, 0, 2) == '**' ) {
+ # groupmember
+ $opt = trim( substr( $value, 2 ) );
+ if( $optgroup === false ){
+ $options[$opt] = $opt;
+ } else {
+ $options[$optgroup][$opt] = $opt;
+ }
+ } else {
+ # groupless reason list
+ $optgroup = false;
+ $options[$option] = $option;
+ }
+ }
+
+ return $options;
+ }
+
+ function getInputHTML( $value ) {
+ $select = parent::getInputHTML( $value[1] );
+
+ $textAttribs = array(
+ 'id' => $this->mID . '-other',
+ 'size' => $this->getSize(),
+ );
+
+ foreach ( array( 'required', 'autofocus', 'multiple', 'disabled' ) as $param ) {
+ if ( isset( $this->mParams[$param] ) ) {
+ $textAttribs[$param] = '';
+ }
+ }
+
+ $textbox = Html::input(
+ $this->mName . '-other',
+ $value[2],
+ 'text',
+ $textAttribs
+ );
+
+ return "$select<br />\n$textbox";
+ }
+
+ /**
+ * @param $request WebRequest
+ * @return Array( <overall message>, <select value>, <text field value> )
+ */
+ function loadDataFromRequest( $request ) {
+ if ( $request->getCheck( $this->mName ) ) {
+
+ $list = $request->getText( $this->mName );
+ $text = $request->getText( $this->mName . '-other' );
+
+ if ( $list == 'other' ) {
+ $final = $text;
+ } elseif( !in_array( $list, $this->mFlatOptions ) ){
+ # User has spoofed the select form to give an option which wasn't
+ # in the original offer. Sulk...
+ $final = $text;
+ } elseif( $text == '' ) {
+ $final = $list;
+ } else {
+ $final = $list . wfMsgForContent( 'colon-separator' ) . $text;
+ }
+
+ } else {
+ $final = $this->getDefault();
+ $list = $text = '';
+ }
+ return array( $final, $list, $text );
+ }
+
+ function getSize() {
+ return isset( $this->mParams['size'] )
+ ? $this->mParams['size']
+ : 45;
+ }
+
+ function validate( $value, $alldata ) {
+ # HTMLSelectField forces $value to be one of the options in the select
+ # field, which is not useful here. But we do want the validation further up
+ # the chain
+ $p = parent::validate( $value[1], $alldata );
+
+ if ( $p !== true ) {
+ return $p;
+ }
+
+ if( isset( $this->mParams['required'] ) && $value[1] === '' ){
+ return wfMsgExt( 'htmlform-required', 'parseinline' );
+ }
+
+ return true;
+ }
+}
+
+/**
* Radio checkbox fields.
*/
class HTMLRadioField extends HTMLFormField {
@@ -1515,7 +1825,7 @@ class HTMLInfoField extends HTMLFormField {
class HTMLHiddenField extends HTMLFormField {
public function __construct( $params ) {
parent::__construct( $params );
-
+
# Per HTML5 spec, hidden fields cannot be 'required'
# http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#hidden-state
unset( $this->mParams['required'] );
@@ -1564,7 +1874,7 @@ class HTMLSubmitField extends HTMLFormField {
protected function needsLabel() {
return false;
}
-
+
/**
* Button cannot be invalid
*/
@@ -1579,11 +1889,20 @@ class HTMLEditTools extends HTMLFormField {
}
public function getTableRow( $value ) {
- return "<tr><td></td><td class=\"mw-input\">"
+ if ( empty( $this->mParams['message'] ) ) {
+ $msg = wfMessage( 'edittools' );
+ } else {
+ $msg = wfMessage( $this->mParams['message'] );
+ if ( $msg->isDisabled() ) {
+ $msg = wfMessage( 'edittools' );
+ }
+ }
+ $msg->inContentLanguage();
+
+
+ return '<tr><td></td><td class="mw-input">'
. '<div class="mw-editTools">'
- . wfMsgExt( empty( $this->mParams['message'] )
- ? 'edittools' : $this->mParams['message'],
- array( 'parse', 'content' ) )
+ . $msg->parseAsBlock()
. "</div></td></tr>\n";
}
}
diff --git a/includes/HistoryBlob.php b/includes/HistoryBlob.php
index fe2b48bf..f707b3f6 100644
--- a/includes/HistoryBlob.php
+++ b/includes/HistoryBlob.php
@@ -12,16 +12,20 @@ interface HistoryBlob
* You must call setLocation() on the stub object before storing it to the
* database
*
+ * @param $text string
+ *
* @return String: the key for getItem()
*/
- public function addItem( $text );
+ function addItem( $text );
/**
* Get item by key, or false if the key is not present
*
+ * @param $key string
+ *
* @return String or false
*/
- public function getItem( $key );
+ function getItem( $key );
/**
* Set the "default text"
@@ -30,8 +34,10 @@ interface HistoryBlob
* be other revisions in the same object.
*
* Default text is not required for two-part external storage URLs.
+ *
+ * @param $text string
*/
- public function setText( $text );
+ function setText( $text );
/**
* Get default text. This is called from Revision::getRevisionText()
@@ -53,12 +59,16 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
public $mMaxCount = 100;
/** Constructor */
- public function ConcatenatedGzipHistoryBlob() {
+ public function __construct() {
if ( !function_exists( 'gzdeflate' ) ) {
throw new MWException( "Need zlib support to read or write this kind of history object (ConcatenatedGzipHistoryBlob)\n" );
}
}
+ /**
+ * @param $text string
+ * @return string
+ */
public function addItem( $text ) {
$this->uncompress();
$hash = md5( $text );
@@ -69,6 +79,10 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
return $hash;
}
+ /**
+ * @param $hash string
+ * @return array|bool
+ */
public function getItem( $hash ) {
$this->uncompress();
if ( array_key_exists( $hash, $this->mItems ) ) {
@@ -78,11 +92,18 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
}
}
+ /**
+ * @param $text string
+ * @return void
+ */
public function setText( $text ) {
$this->uncompress();
$this->mDefaultHash = $this->addItem( $text );
}
+ /**
+ * @return array|bool
+ */
public function getText() {
$this->uncompress();
return $this->getItem( $this->mDefaultHash );
@@ -90,6 +111,8 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
/**
* Remove an item
+ *
+ * @param $hash string
*/
public function removeItem( $hash ) {
$this->mSize -= strlen( $this->mItems[$hash] );
@@ -116,7 +139,9 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
}
}
-
+ /**
+ * @return array
+ */
function __sleep() {
$this->compress();
return array( 'mVersion', 'mCompressed', 'mItems', 'mDefaultHash' );
@@ -129,6 +154,8 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
/**
* Helper function for compression jobs
* Returns true until the object is "full" and ready to be committed
+ *
+ * @return bool
*/
public function isHappy() {
return $this->mSize < $this->mMaxSize
@@ -137,8 +164,6 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob
}
-
-
/**
* Pointer object for an item within a CGZ blob stored in the text table.
*/
@@ -183,6 +208,9 @@ class HistoryBlobStub {
return $this->mRef;
}
+ /**
+ * @return string
+ */
function getText() {
$fname = 'HistoryBlobStub::getText';
@@ -197,12 +225,12 @@ class HistoryBlobStub {
$flags = explode( ',', $row->old_flags );
if( in_array( 'external', $flags ) ) {
$url=$row->old_text;
- @list( /* $proto */ ,$path)=explode('://',$url,2);
- if ($path=="") {
+ $parts = explode( '://', $url, 2 );
+ if ( !isset( $parts[1] ) || $parts[1] == '' ) {
wfProfileOut( $fname );
return false;
}
- $row->old_text=ExternalStore::fetchFromUrl($url);
+ $row->old_text = ExternalStore::fetchFromUrl($url);
}
if( !in_array( 'object', $flags ) ) {
@@ -232,6 +260,8 @@ class HistoryBlobStub {
/**
* Get the content hash
+ *
+ * @return string
*/
function getHash() {
return $this->mHash;
@@ -260,11 +290,16 @@ class HistoryBlobCurStub {
/**
* Sets the location (cur_id) of the main object to which this object
* points
+ *
+ * @param $id int
*/
function setLocation( $id ) {
$this->mCurId = $id;
}
+ /**
+ * @return string|false
+ */
function getText() {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow( 'cur', array( 'cur_text' ), array( 'cur_id' => $this->mCurId ) );
@@ -336,6 +371,11 @@ class DiffHistoryBlob implements HistoryBlob {
}
}
+ /**
+ * @throws MWException
+ * @param $text string
+ * @return int
+ */
function addItem( $text ) {
if ( $this->mFrozen ) {
throw new MWException( __METHOD__.": Cannot add more items after sleep/wakeup" );
@@ -347,18 +387,31 @@ class DiffHistoryBlob implements HistoryBlob {
return count( $this->mItems ) - 1;
}
+ /**
+ * @param $key string
+ * @return string
+ */
function getItem( $key ) {
return $this->mItems[$key];
}
+ /**
+ * @param $text string
+ */
function setText( $text ) {
$this->mDefaultKey = $this->addItem( $text );
}
+ /**
+ * @return string
+ */
function getText() {
return $this->getItem( $this->mDefaultKey );
}
+ /**
+ * @throws MWException
+ */
function compress() {
if ( !function_exists( 'xdiff_string_rabdiff' ) ){
throw new MWException( "Need xdiff 1.5+ support to write DiffHistoryBlob\n" );
@@ -431,6 +484,11 @@ class DiffHistoryBlob implements HistoryBlob {
}
}
+ /**
+ * @param $t1
+ * @param $t2
+ * @return string
+ */
function diff( $t1, $t2 ) {
# Need to do a null concatenation with warnings off, due to bugs in the current version of xdiff
# "String is not zero-terminated"
@@ -440,6 +498,11 @@ class DiffHistoryBlob implements HistoryBlob {
return $diff;
}
+ /**
+ * @param $base
+ * @param $diff
+ * @return bool|string
+ */
function patch( $base, $diff ) {
if ( function_exists( 'xdiff_string_bpatch' ) ) {
wfSuppressWarnings();
@@ -510,6 +573,9 @@ class DiffHistoryBlob implements HistoryBlob {
}
}
+ /**
+ * @return array
+ */
function __sleep() {
$this->compress();
if ( !count( $this->mItems ) ) {
@@ -575,6 +641,8 @@ class DiffHistoryBlob implements HistoryBlob {
/**
* Helper function for compression jobs
* Returns true until the object is "full" and ready to be committed
+ *
+ * @return bool
*/
function isHappy() {
return $this->mSize < $this->mMaxSize
diff --git a/includes/HistoryPage.php b/includes/HistoryPage.php
index b2cf044a..dd5ecd43 100644
--- a/includes/HistoryPage.php
+++ b/includes/HistoryPage.php
@@ -326,6 +326,10 @@ class HistoryPager extends ReverseChronologicalPager {
return $this->historyPage->getArticle();
}
+ function getTitle() {
+ return $this->title;
+ }
+
function getSqlComment() {
if ( $this->conds ) {
return 'history page filtered'; // potentially slow, see CR r58153
@@ -339,7 +343,7 @@ class HistoryPager extends ReverseChronologicalPager {
'tables' => array( 'revision' ),
'fields' => Revision::selectFields(),
'conds' => array_merge(
- array( 'rev_page' => $this->historyPage->getTitle()->getArticleID() ),
+ array( 'rev_page' => $this->title->getArticleID() ),
$this->conds ),
'options' => array( 'USE INDEX' => array( 'revision' => 'page_timestamp' ) ),
'join_conds' => array( 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ),
@@ -380,7 +384,7 @@ class HistoryPager extends ReverseChronologicalPager {
* @return string HTML output
*/
function getStartBody() {
- global $wgScript, $wgUser, $wgOut, $wgContLang;
+ global $wgScript, $wgUser, $wgOut;
$this->lastRow = false;
$this->counter = 1;
$this->oldIdChecked = 0;
@@ -397,31 +401,33 @@ class HistoryPager extends ReverseChronologicalPager {
$this->buttons = '<div>';
$this->buttons .= $this->submitButton( wfMsg( 'compareselectedversions' ),
array( 'class' => 'historysubmit' )
- + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'compareselectedversions' )
+ + Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' )
) . "\n";
if ( $wgUser->isAllowed( 'deleterevision' ) ) {
- $this->preventClickjacking();
- $float = $wgContLang->alignEnd();
- # Note bug #20966, <button> is non-standard in IE<8
- $element = Html::element( 'button',
- array(
- 'type' => 'submit',
- 'name' => 'revisiondelete',
- 'value' => '1',
- 'style' => "float: $float;",
- 'class' => 'mw-history-revisiondelete-button',
- ),
- wfMsg( 'showhideselectedversions' )
- ) . "\n";
- $s .= $element;
- $this->buttons .= $element;
+ $s .= $this->getRevisionButton( 'revisiondelete', 'showhideselectedversions' );
}
$this->buttons .= '</div>';
$s .= '</div><ul id="pagehistory">' . "\n";
return $s;
}
+ private function getRevisionButton( $name, $msg ) {
+ $this->preventClickjacking();
+ # Note bug #20966, <button> is non-standard in IE<8
+ $element = Html::element( 'button',
+ array(
+ 'type' => 'submit',
+ 'name' => $name,
+ 'value' => '1',
+ 'class' => "mw-history-$name-button",
+ ),
+ wfMsg( $msg )
+ ) . "\n";
+ $this->buttons .= $element;
+ return $element;
+ }
+
function getEndBody() {
if ( $this->lastRow ) {
$latest = $this->counter == 1 && $this->mIsFirst;
@@ -513,7 +519,7 @@ class HistoryPager extends ReverseChronologicalPager {
array( 'name' => 'ids[' . $rev->getId() . ']' ) );
}
// User can only view deleted revisions...
- } else if ( $rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) {
+ } elseif ( $rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) {
// If revision was hidden from sysops, disable the link
if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
$cdel = $this->getSkin()->revDeleteLinkDisabled( false );
@@ -529,9 +535,13 @@ class HistoryPager extends ReverseChronologicalPager {
$s .= " $del ";
}
+ $dirmark = $wgLang->getDirMark();
+
$s .= " $link";
+ $s .= $dirmark;
$s .= " <span class='history-user'>" .
$this->getSkin()->revUserTools( $rev, true ) . "</span>";
+ $s .= $dirmark;
if ( $rev->isMinor() ) {
$s .= ' ' . ChangesList::flag( 'minor' );
@@ -721,7 +731,7 @@ class HistoryPager extends ReverseChronologicalPager {
if ( !$rev->userCan( Revision::DELETED_TEXT ) ) {
$radio['disabled'] = 'disabled';
$checkmark = array(); // We will check the next possible one
- } else if ( !$this->oldIdChecked ) {
+ } elseif ( !$this->oldIdChecked ) {
$checkmark = array( 'checked' => 'checked' );
$this->oldIdChecked = $id;
} else {
diff --git a/includes/Hooks.php b/includes/Hooks.php
index 168f4bd9..dd08d03b 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -4,203 +4,271 @@
*
* Copyright 2004, 2005 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
*
* @author Evan Prodromou <evan@wikitravel.org>
* @see hooks.txt
* @file
*/
+class MWHookException extends MWException {}
/**
- * Call hook functions defined in $wgHooks
+ * Hooks class.
*
- * 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
+ * Used to supersede $wgHooks, because globals are EVIL.
*/
-function wfRunHooks($event, $args = array()) {
+class Hooks {
- global $wgHooks;
+ protected static $handlers = array();
- // Return quickly in the most common case
- if ( !isset( $wgHooks[$event] ) ) {
- return true;
+ /**
+ * Attach an event handler to a given hook
+ *
+ * @param $name Mixed: name of hook
+ * @param $callback Mixed: callback function to attach
+ * @return void
+ */
+ public static function register( $name, $callback ) {
+ if( !isset( self::$handlers[$name] ) ) {
+ self::$handlers[$name] = array();
+ }
+
+ self::$handlers[$name][] = $callback;
}
- if (!is_array($wgHooks)) {
- throw new MWException("Global hooks array is not an array!\n");
+ /**
+ * Returns true if a hook has a function registered to it.
+ *
+ * @param $name Mixed: name of hook
+ * @return Boolean: true if a hook has a function registered to it
+ */
+ public static function isRegistered( $name ) {
+ if( !isset( self::$handlers[$name] ) ) {
+ self::$handlers[$name] = array();
+ }
+
+ return ( count( self::$handlers[$name] ) != 0 );
}
- if (!is_array($wgHooks[$event])) {
- throw new MWException("Hooks array for event '$event' is not an array!\n");
+ /**
+ * Returns an array of all the event functions attached to a hook
+ *
+ * @param $name Mixed: name of the hook
+ * @return array
+ */
+ public static function getHandlers( $name ) {
+ if( !isset( self::$handlers[$name] ) ) {
+ return array();
+ }
+
+ return self::$handlers[$name];
}
- foreach ($wgHooks[$event] as $index => $hook) {
-
- $object = null;
- $method = null;
- $func = null;
- $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
- * array of object, method, and data.
- */
-
- if ( is_array( $hook ) ) {
- if ( count( $hook ) < 1 ) {
- throw new MWException("Empty array in hooks for " . $event . "\n");
- } else if ( is_object( $hook[0] ) ) {
- $object = $wgHooks[$event][$index][0];
- if ( $object instanceof Closure ) {
- $closure = true;
- if ( count( $hook ) > 1 ) {
+ /**
+ * Call hook functions defined in Hooks::register
+ *
+ * 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
+ */
+ public static function run( $event, $args = array() ) {
+ global $wgHooks;
+
+ // Return quickly in the most common case
+ if ( !isset( self::$handlers[$event] ) && !isset( $wgHooks[$event] ) ) {
+ return true;
+ }
+
+ if ( !is_array( self::$handlers ) ) {
+ throw new MWException( "Local hooks array is not an array!\n" );
+ }
+
+ if ( !is_array( $wgHooks ) ) {
+ throw new MWException( "Global hooks array is not an array!\n" );
+ }
+
+ $new_handlers = (array) self::$handlers;
+ $old_handlers = (array) $wgHooks;
+
+ $hook_array = array_merge( $new_handlers, $old_handlers );
+
+ if ( !is_array( $hook_array[$event] ) ) {
+ throw new MWException( "Hooks array for event '$event' is not an array!\n" );
+ }
+
+ foreach ( $hook_array[$event] as $index => $hook ) {
+ $object = null;
+ $method = null;
+ $func = null;
+ $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 array of object, method, and data.
+ */
+ if ( is_array( $hook ) ) {
+ if ( count( $hook ) < 1 ) {
+ throw new MWException( 'Empty array in hooks for ' . $event . "\n" );
+ } elseif ( is_object( $hook[0] ) ) {
+ $object = $hook_array[$event][$index][0];
+ if ( $object instanceof Closure ) {
+ $closure = true;
+ if ( count( $hook ) > 1 ) {
+ $data = $hook[1];
+ $have_data = true;
+ }
+ } else {
+ if ( count( $hook ) < 2 ) {
+ $method = 'on' . $event;
+ } else {
+ $method = $hook[1];
+ if ( count( $hook ) > 2 ) {
+ $data = $hook[2];
+ $have_data = true;
+ }
+ }
+ }
+ } elseif ( is_string( $hook[0] ) ) {
+ $func = $hook[0];
+ if ( count( $hook ) > 1) {
$data = $hook[1];
$have_data = true;
}
} else {
- if ( count( $hook ) < 2 ) {
- $method = "on" . $event;
- } else {
- $method = $hook[1];
- if ( count( $hook ) > 2 ) {
- $data = $hook[2];
- $have_data = true;
- }
- }
+ throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" );
}
- } else if ( is_string( $hook[0] ) ) {
- $func = $hook[0];
- if ( count( $hook ) > 1) {
- $data = $hook[1];
- $have_data = true;
+ } elseif ( is_string( $hook ) ) { # functions look like strings, too
+ $func = $hook;
+ } elseif ( is_object( $hook ) ) {
+ $object = $hook_array[$event][$index];
+ if ( $object instanceof Closure ) {
+ $closure = true;
+ } else {
+ $method = "on" . $event;
}
} else {
- throw new MWException( "Unknown datatype in hooks for " . $event . "\n" );
+ throw new MWException( 'Unknown datatype in hooks for ' . $event . "\n" );
}
- } else if ( is_string( $hook ) ) { # functions look like strings, too
- $func = $hook;
- } else if ( is_object( $hook ) ) {
- $object = $wgHooks[$event][$index];
- if ( $object instanceof Closure ) {
- $closure = true;
+
+ /* We put the first data element on, if needed. */
+ if ( $have_data ) {
+ $hook_args = array_merge( array( $data ), $args );
} else {
- $method = "on" . $event;
+ $hook_args = $args;
}
- } else {
- throw new MWException( "Unknown datatype in hooks for " . $event . "\n" );
- }
- /* We put the first data element on, if needed. */
+ if ( $closure ) {
+ $callback = $object;
+ $func = "hook-$event-closure";
+ } elseif ( isset( $object ) ) {
+ $func = get_class( $object ) . '::' . $method;
+ $callback = array( $object, $method );
+ } else {
+ $callback = $func;
+ }
- if ( $have_data ) {
- $hook_args = array_merge(array($data), $args);
- } else {
- $hook_args = $args;
- }
+ // Run autoloader (workaround for call_user_func_array bug)
+ is_callable( $callback );
- if ( $closure ) {
- $callback = $object;
- $func = "hook-$event-closure";
- } elseif ( isset( $object ) ) {
- $func = get_class( $object ) . '::' . $method;
- $callback = array( $object, $method );
- } elseif ( false !== ( $pos = strpos( $func, '::' ) ) ) {
- $callback = array( substr( $func, 0, $pos ), substr( $func, $pos + 2 ) );
- } else {
- $callback = $func;
- }
+ /**
+ * 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( 'Hooks::hookErrorHandler' );
+ wfProfileIn( $func );
+ try {
+ $retval = call_user_func_array( $callback, $hook_args );
+ } catch ( MWHookException $e ) {
+ $badhookmsg = $e->getMessage();
+ }
+ wfProfileOut( $func );
+ restore_error_handler();
- // Run autoloader (workaround for call_user_func_array bug)
- is_callable( $callback );
-
- /* 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 );
- 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 );
- return false;
- } elseif( $retval === null ) {
- if ( $closure ) {
- $prettyFunc = "$event closure";
- } elseif( is_array( $callback ) ) {
- if( is_object( $callback[0] ) ) {
- $prettyClass = get_class( $callback[0] );
+ /* String return is an error; false return means stop processing. */
+ if ( is_string( $retval ) ) {
+ global $wgOut;
+ $wgOut->showFatalError( $retval );
+ return false;
+ } elseif( $retval === null ) {
+ if ( $closure ) {
+ $prettyFunc = "$event closure";
+ } elseif( is_array( $callback ) ) {
+ if( is_object( $callback[0] ) ) {
+ $prettyClass = get_class( $callback[0] );
+ } else {
+ $prettyClass = strval( $callback[0] );
+ }
+ $prettyFunc = $prettyClass . '::' . strval( $callback[1] );
} else {
- $prettyClass = strval( $callback[0] );
+ $prettyFunc = strval( $callback );
}
- $prettyFunc = $prettyClass . '::' . strval( $callback[1] );
- } else {
- $prettyFunc = strval( $callback );
- }
- 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." );
+ 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.'
+ );
+ }
+ } elseif ( !$retval ) {
+ return false;
}
- } else if ( !$retval ) {
- return false;
}
- }
- return true;
-}
+ return true;
+ }
-function hookErrorHandler( $errno, $errstr ) {
- if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) {
- throw new MWHookException( $errstr );
+ /**
+ * This REALLY should be protected... but it's public for compatibility
+ *
+ * @param $errno Unused
+ * @param $errstr String: error message
+ * @return Boolean: false
+ */
+ public static function hookErrorHandler( $errno, $errstr ) {
+ if ( strpos( $errstr, 'expected to be a reference, value given' ) !== false ) {
+ throw new MWHookException( $errstr );
+ }
+ return false;
}
- return false;
}
-
-class MWHookException extends MWException {} \ No newline at end of file
diff --git a/includes/Html.php b/includes/Html.php
index 6c802ca3..be9a1e1b 100644
--- a/includes/Html.php
+++ b/includes/Html.php
@@ -107,8 +107,8 @@ class Html {
* features might be added, like allowing arrays for the values of
* attributes like class= and media=.
*
- * @param $element string The element's name, e.g., 'a'
- * @param $attribs array Associative array of attributes, e.g., array(
+ * @param $element string The element's name, e.g., 'a'
+ * @param $attribs array Associative array of attributes, e.g., array(
* 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
* further documentation.
* @param $contents string The raw HTML contents of the element: *not*
@@ -132,6 +132,12 @@ class Html {
/**
* Identical to rawElement(), but HTML-escapes $contents (like
* Xml::element()).
+ *
+ * @param $element string
+ * @param $attribs array
+ * @param $contents string
+ *
+ * @return string
*/
public static function element( $element, $attribs = array(), $contents = '' ) {
return self::rawElement( $element, $attribs, strtr( $contents, array(
@@ -145,6 +151,11 @@ class Html {
/**
* Identical to rawElement(), but has no third parameter and omits the end
* tag (and the self-closing '/' in XML mode for empty elements).
+ *
+ * @param $element string
+ * @param $attribs array
+ *
+ * @return string
*/
public static function openElement( $element, $attribs = array() ) {
global $wgHtml5, $wgWellFormedXml;
@@ -180,15 +191,18 @@ class Html {
'button',
'search',
);
+
if ( isset( $attribs['type'] )
&& !in_array( $attribs['type'], $validTypes ) ) {
unset( $attribs['type'] );
}
+
if ( isset( $attribs['type'] ) && $attribs['type'] == 'search'
&& !$wgHtml5 ) {
unset( $attribs['type'] );
}
}
+
if ( !$wgHtml5 && $element == 'textarea' && isset( $attribs['maxlength'] ) ) {
unset( $attribs['maxlength'] );
}
@@ -201,6 +215,7 @@ class Html {
* Returns "</$element>", except if $wgWellFormedXml is off, in which case
* it returns the empty string when that's guaranteed to be safe.
*
+ * @since 1.17
* @param $element string Name of the element, e.g., 'a'
* @return string A closing tag, if required
*/
@@ -351,7 +366,7 @@ class Html {
$ret = '';
$attribs = (array)$attribs;
foreach ( $attribs as $key => $value ) {
- if ( $value === false ) {
+ if ( $value === false || is_null( $value ) ) {
continue;
}
@@ -425,7 +440,8 @@ class Html {
# Apparently we need to entity-encode \n, \r, \t, although the
# spec doesn't mention that. Since we're doing strtr() anyway,
# and we don't need <> escaped here, we may as well not call
- # htmlspecialchars(). FIXME: verify that we actually need to
+ # htmlspecialchars().
+ # @todo FIXME: Verify that we actually need to
# escape \n\r\t here, and explain why, exactly.
#
# We could call Sanitizer::encodeAttribute() for this, but we
@@ -440,8 +456,8 @@ class Html {
);
if ( $wgWellFormedXml ) {
# This is allowed per spec: <http://www.w3.org/TR/xml/#NT-AttValue>
- # But reportedly it breaks some XML tools? FIXME: is this
- # really true?
+ # But reportedly it breaks some XML tools?
+ # @todo FIXME: Is this really true?
$map['<'] = '&lt;';
}
$ret .= " $key=$quote" . strtr( $value, $map ) . $quote;
@@ -462,12 +478,15 @@ class Html {
global $wgHtml5, $wgJsMimeType, $wgWellFormedXml;
$attrs = array();
+
if ( !$wgHtml5 ) {
$attrs['type'] = $wgJsMimeType;
}
+
if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
$contents = "/*<![CDATA[*/$contents/*]]>*/";
}
+
return self::rawElement( 'script', $attrs, $contents );
}
@@ -482,9 +501,11 @@ class Html {
global $wgHtml5, $wgJsMimeType;
$attrs = array( 'src' => $url );
+
if ( !$wgHtml5 ) {
$attrs['type'] = $wgJsMimeType;
}
+
return self::element( 'script', $attrs );
}
@@ -503,6 +524,7 @@ class Html {
if ( $wgWellFormedXml && preg_match( '/[<&]/', $contents ) ) {
$contents = "/*<![CDATA[*/$contents/*]]>*/";
}
+
return self::rawElement( 'style', array(
'type' => 'text/css',
'media' => $media,
@@ -574,16 +596,29 @@ class Html {
*/
public static function textarea( $name, $value = '', $attribs = array() ) {
global $wgHtml5;
+
$attribs['name'] = $name;
+
if ( !$wgHtml5 ) {
if ( !isset( $attribs['cols'] ) ) {
$attribs['cols'] = "";
}
+
if ( !isset( $attribs['rows'] ) ) {
$attribs['rows'] = "";
}
}
- return self::element( 'textarea', $attribs, $value );
+
+ if (substr($value, 0, 1) == "\n") {
+ // Workaround for bug 12130: browsers eat the initial newline
+ // assuming that it's just for show, but they do keep the later
+ // newlines, which we may want to preserve during editing.
+ // Prepending a single newline
+ $spacedValue = "\n" . $value;
+ } else {
+ $spacedValue = $value;
+ }
+ return self::element( 'textarea', $attribs, $spacedValue );
}
/**
@@ -597,30 +632,38 @@ class Html {
public static function htmlHeader( $attribs = array() ) {
$ret = '';
- global $wgMimeType, $wgOutputEncoding;
+ global $wgMimeType;
+
if ( self::isXmlMimeType( $wgMimeType ) ) {
- $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?" . ">\n";
+ $ret .= "<?xml version=\"1.0\" encoding=\"UTF-8\" ?" . ">\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;
}
@@ -640,4 +683,45 @@ class Html {
return false;
}
}
+
+ /**
+ * 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 $alt String: alternate text for the icon
+ * @param $class String: additional class name to add to the wrapper div
+ * @param $useStylePath
+ *
+ * @return string
+ */
+ static function infoBox( $text, $icon, $alt, $class = false, $useStylePath = true ) {
+ global $wgStylePath;
+
+ if ( $useStylePath ) {
+ $icon = $wgStylePath.'/common/images/'.$icon;
+ }
+
+ $s = Html::openElement( 'div', array( 'class' => "mw-infobox $class") );
+
+ $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-left' ) ).
+ Html::element( 'img',
+ array(
+ 'src' => $icon,
+ 'alt' => $alt,
+ )
+ ).
+ Html::closeElement( 'div' );
+
+ $s .= Html::openElement( 'div', array( 'class' => 'mw-infobox-right' ) ).
+ $text.
+ Html::closeElement( 'div' );
+ $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
+
+ $s .= Html::closeElement( 'div' );
+
+ $s .= Html::element( 'div', array( 'style' => 'clear: left;' ), ' ' );
+
+ return $s;
+ }
}
diff --git a/includes/HttpFunctions.old.php b/includes/HttpFunctions.old.php
index 6d28abc6..ddfa608e 100644
--- a/includes/HttpFunctions.old.php
+++ b/includes/HttpFunctions.old.php
@@ -7,6 +7,7 @@
* http://www.php.net/manual/en/class.httprequest.php
*
* This is for backwards compatibility.
+ * @since 1.17
*/
class HttpRequest extends MWHttpRequest { }
diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php
index 3d4d77a9..a80fec17 100644
--- a/includes/HttpFunctions.php
+++ b/includes/HttpFunctions.php
@@ -14,7 +14,7 @@ class Http {
* Perform an HTTP request
*
* @param $method String: HTTP method. Usually GET/POST
- * @param $url String: full URL to act on
+ * @param $url String: full URL to act on. If protocol-relative, will be expanded to an http:// URL
* @param $options Array: options to pass to MWHttpRequest object.
* Possible keys for the array:
* - timeout Timeout length in seconds
@@ -32,7 +32,6 @@ class Http {
* @return Mixed: (bool)false on failure or a string on success
*/
public static function request( $method, $url, $options = array() ) {
- $url = wfExpandUrl( $url );
wfDebug( "HTTP: $method: $url\n" );
$options['method'] = strtoupper( $method );
@@ -53,6 +52,8 @@ class Http {
/**
* Simple wrapper for Http::request( 'GET' )
* @see Http::request()
+ *
+ * @return string
*/
public static function get( $url, $timeout = 'default', $options = array() ) {
$options['timeout'] = $timeout;
@@ -62,6 +63,8 @@ class Http {
/**
* Simple wrapper for Http::request( 'POST' )
* @see Http::request()
+ *
+ * @return string
*/
public static function post( $url, $options = array() ) {
return Http::request( 'POST', $url, $options );
@@ -89,6 +92,7 @@ class Http {
// Check if this domain or any superdomain is listed in $wgConf as a local virtual host
$domainParts = array_reverse( $domainParts );
+ $domain = '';
for ( $i = 0; $i < count( $domainParts ); $i++ ) {
$domainPart = $domainParts[$i];
if ( $i == 0 ) {
@@ -137,10 +141,12 @@ 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.
+ * Renamed from HttpRequest to MWHttpRequest to avoid conflict with
+ * PHP's HTTP extension.
*/
class MWHttpRequest {
+ const SUPPORTS_FILE_POSTS = false;
+
protected $content;
protected $timeout = 'default';
protected $headersOnly = null;
@@ -158,6 +164,9 @@ class MWHttpRequest {
protected $maxRedirects = 5;
protected $followRedirects = false;
+ /**
+ * @var CookieJar
+ */
protected $cookieJar;
protected $headerList = array();
@@ -168,14 +177,14 @@ class MWHttpRequest {
public $status;
/**
- * @param $url String: url to use
+ * @param $url String: url to use. If protocol-relative, will be expanded to an http:// URL
* @param $options Array: (optional) extra params to pass (see Http::request())
*/
function __construct( $url, $options = array() ) {
global $wgHTTPTimeout;
- $this->url = $url;
- $this->parsedUrl = parse_url( $url );
+ $this->url = wfExpandUrl( $url, PROTO_HTTP );
+ $this->parsedUrl = parse_url( $this->url );
if ( !Http::isValidURI( $this->url ) ) {
$this->status = Status::newFatal( 'http-invalid-url' );
@@ -190,7 +199,7 @@ class MWHttpRequest {
}
$members = array( "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
- "method", "followRedirects", "maxRedirects", "sslVerifyCert" );
+ "method", "followRedirects", "maxRedirects", "sslVerifyCert", "callback" );
foreach ( $members as $o ) {
if ( isset( $options[$o] ) ) {
@@ -210,6 +219,8 @@ class MWHttpRequest {
/**
* Generate a new request object
+ * @param $url String: url to use
+ * @param $options Array: (optional) extra params to pass (see Http::request())
* @see MWHttpRequest::__construct
*/
public static function factory( $url, $options = null ) {
@@ -368,12 +379,8 @@ class MWHttpRequest {
$this->headersOnly = true;
}
- if ( is_array( $this->postData ) ) {
- $this->postData = wfArrayToCGI( $this->postData );
- }
-
if ( is_object( $wgTitle ) && !isset( $this->reqHeaders['Referer'] ) ) {
- $this->setReferer( $wgTitle->getFullURL() );
+ $this->setReferer( wfExpandUrl( $wgTitle->getFullURL(), PROTO_CURRENT ) );
}
if ( !$this->noProxy ) {
@@ -582,249 +589,12 @@ class MWHttpRequest {
}
}
-
-class Cookie {
- protected $name;
- protected $value;
- protected $expires;
- protected $path;
- protected $domain;
- protected $isSessionKey = true;
- // TO IMPLEMENT protected $secure
- // TO IMPLEMENT? protected $maxAge (add onto expires)
- // TO IMPLEMENT? protected $version
- // TO IMPLEMENT? protected $comment
-
- function __construct( $name, $value, $attr ) {
- $this->name = $name;
- $this->set( $value, $attr );
- }
-
- /**
- * 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 $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'] ) ) {
- $this->isSessionKey = false;
- $this->expires = strtotime( $attr['expires'] );
- }
-
- if ( isset( $attr['path'] ) ) {
- $this->path = $attr['path'];
- } else {
- $this->path = "/";
- }
-
- if ( isset( $attr['domain'] ) ) {
- if ( self::validateCookieDomain( $attr['domain'] ) ) {
- $this->domain = $attr['domain'];
- }
- } else {
- throw new MWException( "You must specify a domain." );
- }
- }
-
- /**
- * Return the true if the cookie is valid is valid. Otherwise,
- * false. The uses a method similar to IE cookie security
- * described here:
- * http://kuza55.blogspot.com/2008/02/understanding-cookie-security.html
- * 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 Boolean
- */
- public static function validateCookieDomain( $domain, $originDomain = null ) {
- // Don't allow a trailing dot
- if ( substr( $domain, -1 ) == "." ) {
- return false;
- }
-
- $dc = explode( ".", $domain );
-
- // Only allow full, valid IP addresses
- if ( preg_match( '/^[0-9.]+$/', $domain ) ) {
- if ( count( $dc ) != 4 ) {
- return false;
- }
-
- if ( ip2long( $domain ) === false ) {
- return false;
- }
-
- 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 ) ) {
- return false;
- }
- 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 ) {
- return false;
- }
-
- if ( substr( $domain, 0, 1 ) == "."
- && substr_compare( $originDomain, $domain, -strlen( $domain ),
- strlen( $domain ), TRUE ) != 0 ) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
- * 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
- */
- public function serializeToHttpRequest( $path, $domain ) {
- $ret = "";
-
- if ( $this->canServeDomain( $domain )
- && $this->canServePath( $path )
- && $this->isUnExpired() ) {
- $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 ) == "."
- && 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 ) {
- return true;
- }
-
- return false;
- }
-
- protected function isUnExpired() {
- if ( $this->isSessionKey || $this->expires > time() ) {
- return true;
- }
-
- return false;
- }
-}
-
-class CookieJar {
- private $cookie = array();
-
- /**
- * Set a cookie in the cookie jar. Make sure only one cookie per-name exists.
- * @see Cookie::set()
- */
- 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] ) ) {
- $this->cookie[$index]->set( $value, $attr );
- } else {
- $this->cookie[$index] = new Cookie( $name, $value, $attr );
- }
- }
-
- /**
- * @see Cookie::serializeToHttpRequest
- */
- public function serializeToHttpRequest( $path, $domain ) {
- $cookies = array();
-
- foreach ( $this->cookie as $c ) {
- $serialized = $c->serializeToHttpRequest( $path, $domain );
-
- if ( $serialized ) {
- $cookies[] = $serialized;
- }
- }
-
- return implode( "; ", $cookies );
- }
-
- /**
- * Parse the content of an Set-Cookie HTTP Response header.
- *
- * @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 );
- $attr = array();
-
- foreach ( $bit as $piece ) {
- $parts = explode( "=", $piece );
- if ( count( $parts ) > 1 ) {
- $attr[strtolower( $parts[0] )] = $parts[1];
- } else {
- $attr[strtolower( $parts[0] )] = true;
- }
- }
-
- if ( !isset( $attr['domain'] ) ) {
- $attr['domain'] = $domain;
- } elseif ( !Cookie::validateCookieDomain( $attr['domain'], $domain ) ) {
- return null;
- }
-
- $this->setCookie( $name, $value, $attr );
- }
- }
-}
-
/**
* MWHttpRequest implemented using internal curl compiled into PHP
*/
class CurlHttpRequest extends MWHttpRequest {
+ const SUPPORTS_FILE_POSTS = true;
+
static $curlMessageMap = array(
6 => 'http-host-unreachable',
28 => 'http-timed-out'
@@ -894,12 +664,14 @@ class CurlHttpRequest extends MWHttpRequest {
}
if ( $this->followRedirects && $this->canFollowRedirects() ) {
- if ( ! @curl_setopt( $curlHandle, CURLOPT_FOLLOWLOCATION, true ) ) {
+ wfSuppressWarnings();
+ 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
}
+ wfRestoreWarnings();
}
if ( false === curl_exec( $curlHandle ) ) {
@@ -947,9 +719,9 @@ class PhpHttpRequest extends MWHttpRequest {
public function execute() {
parent::execute();
- // At least on Centos 4.8 with PHP 5.1.6, using max_redirects to follow redirects
- // causes a segfault
- $manuallyRedirect = version_compare( phpversion(), '5.1.7', '<' );
+ if ( is_array( $this->postData ) ) {
+ $this->postData = wfArrayToCGI( $this->postData );
+ }
if ( $this->parsedUrl['scheme'] != 'http' &&
$this->parsedUrl['scheme'] != 'https' ) {
@@ -969,7 +741,7 @@ class PhpHttpRequest extends MWHttpRequest {
$options['request_fulluri'] = true;
}
- if ( !$this->followRedirects || $manuallyRedirect ) {
+ if ( !$this->followRedirects ) {
$options['max_redirects'] = 0;
} else {
$options['max_redirects'] = $this->maxRedirects;
@@ -990,12 +762,7 @@ class PhpHttpRequest extends MWHttpRequest {
$options['content'] = $this->postData;
}
- $oldTimeout = false;
- if ( version_compare( '5.2.1', phpversion(), '>' ) ) {
- $oldTimeout = ini_set( 'default_socket_timeout', $this->timeout );
- } else {
- $options['timeout'] = $this->timeout;
- }
+ $options['timeout'] = $this->timeout;
$context = stream_context_create( array( 'http' => $options ) );
@@ -1003,6 +770,8 @@ class PhpHttpRequest extends MWHttpRequest {
$reqCount = 0;
$url = $this->url;
+ $result = array();
+
do {
$reqCount++;
wfSuppressWarnings();
@@ -1017,7 +786,7 @@ class PhpHttpRequest extends MWHttpRequest {
$this->headerList = $result['wrapper_data'];
$this->parseHeader();
- if ( !$manuallyRedirect || !$this->followRedirects ) {
+ if ( !$this->followRedirects ) {
break;
}
@@ -1034,10 +803,6 @@ class PhpHttpRequest extends MWHttpRequest {
}
} while ( true );
- if ( $oldTimeout !== false ) {
- ini_set( 'default_socket_timeout', $oldTimeout );
- }
-
$this->setStatus();
if ( $fh === false ) {
@@ -1050,7 +815,10 @@ class PhpHttpRequest extends MWHttpRequest {
return $this->status;
}
- if ( $this->status->isOK() ) {
+ // If everything went OK, or we recieved some error code
+ // get the response body content.
+ if ( $this->status->isOK()
+ || (int)$this->respStatus >= 300) {
while ( !feof( $fh ) ) {
$buf = fread( $fh, 8192 );
diff --git a/includes/IP.php b/includes/IP.php
index 50d57583..1da7cd07 100644
--- a/includes/IP.php
+++ b/includes/IP.php
@@ -186,6 +186,76 @@ class IP {
}
/**
+ * Given a host/port string, like one might find in the host part of a URL
+ * per RFC 2732, split the hostname part and the port part and return an
+ * array with an element for each. If there is no port part, the array will
+ * have false in place of the port. If the string was invalid in some way,
+ * false is returned.
+ *
+ * This was easy with IPv4 and was generally done in an ad-hoc way, but
+ * with IPv6 it's somewhat more complicated due to the need to parse the
+ * square brackets and colons.
+ *
+ * A bare IPv6 address is accepted despite the lack of square brackets.
+ *
+ * @param $both The string with the host and port
+ * @return array
+ */
+ public static function splitHostAndPort( $both ) {
+ if ( substr( $both, 0, 1 ) === '[' ) {
+ if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P<port>\d+))?$/', $both, $m ) ) {
+ if ( isset( $m['port'] ) ) {
+ return array( $m[1], intval( $m['port'] ) );
+ } else {
+ return array( $m[1], false );
+ }
+ } else {
+ // Square bracket found but no IPv6
+ return false;
+ }
+ }
+ $numColons = substr_count( $both, ':' );
+ if ( $numColons >= 2 ) {
+ // Is it a bare IPv6 address?
+ if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) {
+ return array( $both, false );
+ } else {
+ // Not valid IPv6, but too many colons for anything else
+ return false;
+ }
+ }
+ if ( $numColons >= 1 ) {
+ // Host:port?
+ $bits = explode( ':', $both );
+ if ( preg_match( '/^\d+/', $bits[1] ) ) {
+ return array( $bits[0], intval( $bits[1] ) );
+ } else {
+ // Not a valid port
+ return false;
+ }
+ }
+ // Plain hostname
+ return array( $both, false );
+ }
+
+ /**
+ * Given a host name and a port, combine them into host/port string like
+ * you might find in a URL. If the host contains a colon, wrap it in square
+ * brackets like in RFC 2732. If the port matches the default port, omit
+ * the port specification
+ */
+ public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
+ if ( strpos( $host, ':' ) !== false ) {
+ $host = "[$host]";
+ }
+ if ( $defaultPort !== false && $port == $defaultPort ) {
+ return $host;
+ } else {
+ return "$host:$port";
+ }
+ }
+
+ /**
* Given an unsigned integer, returns an IPv6 address in octet notation
*
* @param $ip_int String: IP address.
@@ -614,4 +684,20 @@ class IP {
return null; // give up
}
+
+ /**
+ * Gets rid of uneeded numbers in quad-dotted/octet IP strings
+ * For example, 127.111.113.151/24 -> 127.111.113.0/24
+ * @param $range String: IP address to normalize
+ * @return string
+ */
+ public static function sanitizeRange( $range ) {
+ list( /*...*/, $bits ) = self::parseCIDR( $range );
+ list( $start, /*...*/ ) = self::parseRange( $range );
+ $start = self::formatHex( $start );
+ if ( $bits === false ) {
+ return $start; // wasn't actually a range
+ }
+ return "$start/$bits";
+ }
}
diff --git a/includes/ImageFunctions.php b/includes/ImageFunctions.php
index 8eaebd26..d048a9dd 100644
--- a/includes/ImageFunctions.php
+++ b/includes/ImageFunctions.php
@@ -15,7 +15,7 @@
* i.e. articles where the image may occur inline.
*
* @param $name string the image name to check
- * @param $contextTitle Title: the page on which the image occurs, if known
+ * @param $contextTitle Title|bool the page on which the image occurs, if known
* @return bool
*/
function wfIsBadImage( $name, $contextTitle = false ) {
diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php
index f7020d63..4d5f067c 100644
--- a/includes/ImageGallery.php
+++ b/includes/ImageGallery.php
@@ -9,12 +9,9 @@ if ( ! defined( 'MEDIAWIKI' ) )
*
* @ingroup Media
*/
-class ImageGallery
-{
+class ImageGallery {
var $mImages, $mShowBytes, $mShowFilename;
var $mCaption = false;
- var $mSkin = false;
- var $mRevisionId = 0;
/**
* Hide blacklisted images?
@@ -23,6 +20,7 @@ class ImageGallery
/**
* Registered parser object for output callbacks
+ * @var Parser
*/
var $mParser;
@@ -39,13 +37,13 @@ class ImageGallery
*/
const THUMB_PADDING = 30;
const GB_PADDING = 5;
- //2px borders on each side + 2px implied padding on each side
+ // 2px borders on each side + 2px implied padding on each side
const GB_BORDERS = 8;
/**
* Create a new image gallery object.
*/
- function __construct( ) {
+ function __construct() {
global $wgGalleryOptions;
$this->mImages = array();
$this->mShowBytes = $wgGalleryOptions['showBytes'];
@@ -60,6 +58,8 @@ class ImageGallery
/**
* Register a parser object
+ *
+ * @param $parser Parser
*/
function setParser( $parser ) {
$this->mParser = $parser;
@@ -97,7 +97,7 @@ class ImageGallery
* invalid numbers will be rejected
*/
public function setPerRow( $num ) {
- if ($num >= 0) {
+ if ( $num >= 0 ) {
$this->mPerRow = (int)$num;
}
}
@@ -108,7 +108,7 @@ class ImageGallery
* @param $num Integer > 0; invalid numbers will be ignored
*/
public function setWidths( $num ) {
- if ($num > 0) {
+ if ( $num > 0 ) {
$this->mWidths = (int)$num;
}
}
@@ -119,7 +119,7 @@ class ImageGallery
* @param $num Integer > 0; invalid numbers will be ignored
*/
public function setHeights( $num ) {
- if ($num > 0) {
+ if ( $num > 0 ) {
$this->mHeights = (int)$num;
}
}
@@ -128,56 +128,44 @@ class ImageGallery
* Instruct the class to use a specific skin for rendering
*
* @param $skin Skin object
+ * @deprecated since 1.18 Not used anymore
*/
function useSkin( $skin ) {
- $this->mSkin = $skin;
- }
-
- /**
- * Return the skin that should be used
- *
- * @return Skin object
- */
- function getSkin() {
- if( !$this->mSkin ) {
- global $wgUser;
- $skin = $wgUser->getSkin();
- } else {
- $skin = $this->mSkin;
- }
- return $skin;
+ wfDeprecated( __METHOD__ );
+ /* no op */
}
/**
* Add an image to the gallery.
*
* @param $title Title object of the image that is added to the gallery
- * @param $html String: additional HTML text to be shown. The name and size of the image are always shown.
+ * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
+ * @param $alt String: Alt text for the image
*/
- function add( $title, $html='' ) {
+ function add( $title, $html = '', $alt = '' ) {
if ( $title instanceof File ) {
// Old calling convention
$title = $title->getTitle();
}
- $this->mImages[] = array( $title, $html );
- wfDebug( "ImageGallery::add " . $title->getText() . "\n" );
+ $this->mImages[] = array( $title, $html, $alt );
+ wfDebug( 'ImageGallery::add ' . $title->getText() . "\n" );
}
/**
* Add an image at the beginning of the gallery.
*
* @param $title Title object of the image that is added to the gallery
- * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
+ * @param $html String: Additional HTML text to be shown. The name and size of the image are always shown.
+ * @param $alt String: Alt text for the image
*/
- function insert( $title, $html='' ) {
+ function insert( $title, $html = '', $alt = '' ) {
if ( $title instanceof File ) {
// Old calling convention
$title = $title->getTitle();
}
- array_unshift( $this->mImages, array( &$title, $html ) );
+ array_unshift( $this->mImages, array( &$title, $html, $alt ) );
}
-
/**
* isEmpty() returns true if the gallery contains no images
*/
@@ -231,47 +219,56 @@ class ImageGallery
function toHTML() {
global $wgLang;
- $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'] : "";
+ $oldStyle = isset( $this->mAttribs['style'] ) ? $this->mAttribs['style'] : '';
+ # _width is ignored by any sane browser. IE6 doesn't know max-width so it uses _width instead
$this->mAttribs['style'] = "max-width: {$maxwidth}px;_width: {$maxwidth}px;" . $oldStyle;
}
$attribs = Sanitizer::mergeAttributes(
- array(
- 'class' => 'gallery'),
- $this->mAttribs );
- $s = Xml::openElement( 'ul', $attribs );
+ array( 'class' => 'gallery' ), $this->mAttribs );
+
+ $output = Xml::openElement( 'ul', $attribs );
if ( $this->mCaption ) {
- $s .= "\n\t<li class='gallerycaption'>{$this->mCaption}</li>";
+ $output .= "\n\t<li class='gallerycaption'>{$this->mCaption}</li>";
}
- $params = array( 'width' => $this->mWidths, 'height' => $this->mHeights );
- $i = 0;
+ $params = array(
+ 'width' => $this->mWidths,
+ 'height' => $this->mHeights
+ );
+ # Output each image...
foreach ( $this->mImages as $pair ) {
$nt = $pair[0];
$text = $pair[1]; # "text" means "caption" here
+ $alt = $pair[2];
- # Give extensions a chance to select the file revision for us
- $time = $descQuery = false;
- wfRunHooks( 'BeforeGalleryFindFile', array( &$this, &$nt, &$time, &$descQuery ) );
-
+ $descQuery = false;
if ( $nt->getNamespace() == NS_FILE ) {
- $img = wfFindFile( $nt, array( 'time' => $time ) );
+ # Get the file...
+ if ( $this->mParser instanceof Parser ) {
+ # Give extensions a chance to select the file revision for us
+ $time = $sha1 = false;
+ wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ array( $this->mParser, $nt, &$time, &$sha1, &$descQuery ) );
+ # Fetch and register the file (file title may be different via hooks)
+ list( $img, $nt ) = $this->mParser->fetchFileAndTitle( $nt, $time, $sha1 );
+ } else {
+ $img = wfFindFile( $nt );
+ }
} else {
$img = false;
}
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: '.(self::THUMB_PADDING + $this->mHeights).'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: '.(self::THUMB_PADDING + $this->mHeights).'px;">' .
- $sk->link(
+ $thumbhtml = "\n\t\t\t" . '<div style="height: ' . ( self::THUMB_PADDING + $this->mHeights ) . 'px;">' .
+ Linker::link(
$nt,
htmlspecialchars( $nt->getText() ),
array(),
@@ -281,31 +278,28 @@ class ImageGallery
'</div>';
} elseif( !( $thumb = $img->transform( $params ) ) ) {
# Error generating thumbnail.
- $thumbhtml = "\n\t\t\t".'<div style="height: '.(self::THUMB_PADDING + $this->mHeights).'px;">'
+ $thumbhtml = "\n\t\t\t" . '<div style="height: ' . ( self::THUMB_PADDING + $this->mHeights ) . 'px;">'
. htmlspecialchars( $img->getLastError() ) . '</div>';
} else {
- //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);
-
-
+ $vpad = ( self::THUMB_PADDING + $this->mHeights - $thumb->height ) /2;
+
$imageParameters = array(
'desc-link' => true,
- 'desc-query' => $descQuery
+ 'desc-query' => $descQuery,
+ 'alt' => $alt,
);
- # In the absence of a caption, fall back on providing screen readers with the filename as alt text
- if ( $text == '' ) {
+ # In the absence of both alt text and caption, fall back on providing screen readers with the filename as alt text
+ if ( $alt == '' && $text == '' ) {
$imageParameters['alt'] = $nt->getText();
}
-
+
# Set both fixed width and min-height.
- $thumbhtml = "\n\t\t\t".
- '<div class="thumb" style="width: ' .($this->mWidths + self::THUMB_PADDING).'px;">'
+ $thumbhtml = "\n\t\t\t" .
+ '<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:'.$vpad.'px auto;">'
+ . '<div style="margin:' . $vpad . 'px auto;">'
. $thumb->toHtml( $imageParameters ) . '</div></div>';
// Call parser transform hook
@@ -316,22 +310,22 @@ class ImageGallery
//TODO
// $linkTarget = Title::newFromText( $wgContLang->getNsText( MWNamespace::getUser() ) . ":{$ut}" );
- // $ul = $sk->link( $linkTarget, $ut );
+ // $ul = Linker::link( $linkTarget, $ut );
if( $this->mShowBytes ) {
if( $img ) {
- $nb = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
+ $fileSize = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
$wgLang->formatNum( $img->getSize() ) );
} else {
- $nb = wfMsgHtml( 'filemissing' );
+ $fileSize = wfMsgHtml( 'filemissing' );
}
- $nb = "$nb<br />\n";
+ $fileSize = "$fileSize<br />\n";
} else {
- $nb = '';
+ $fileSize = '';
}
$textlink = $this->mShowFilename ?
- $sk->link(
+ Linker::link(
$nt,
htmlspecialchars( $wgLang->truncate( $nt->getText(), $this->mCaptionLength ) ),
array(),
@@ -344,21 +338,20 @@ class ImageGallery
# in version 4.8.6 generated crackpot html in its absence, see:
# http://bugzilla.wikimedia.org/show_bug.cgi?id=1765 -Ævar
- # Weird double wrapping in div needed due to FF2 bug
+ # Weird double wrapping (the extra div inside the li) needed due to FF2 bug
# Can be safely removed if FF2 falls completely out of existance
- $s .=
+ $output .=
"\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
+ . $textlink . $text . $fileSize
. "\n\t\t\t</div>"
. "\n\t\t</div></li>";
- ++$i;
}
- $s .= "\n</ul>";
+ $output .= "\n</ul>";
- return $s;
+ return $output;
}
/**
diff --git a/includes/ImagePage.php b/includes/ImagePage.php
index c018e647..956977e0 100644
--- a/includes/ImagePage.php
+++ b/includes/ImagePage.php
@@ -1,35 +1,46 @@
<?php
-
-if ( !defined( 'MEDIAWIKI' ) )
- die( 1 );
-
/**
- * Special handling for image description pages
+ * Class for viewing MediaWiki file description pages
*
* @ingroup Media
*/
class ImagePage extends Article {
- /* private */ var $img; // Image object
- /* private */ var $displayImg;
- /* private */ var $repo;
- /* private */ var $fileLoaded;
+ /**
+ * @var File
+ */
+ private $displayImg;
+ /**
+ * @var FileRepo
+ */
+ private $repo;
+ private $fileLoaded;
+
var $mExtraDescription = false;
- var $dupes;
- function __construct( $title ) {
- parent::__construct( $title );
- $this->dupes = null;
- $this->repo = null;
+ protected function newPage( Title $title ) {
+ // Overload mPage with a file-specific page
+ return new WikiFilePage( $title );
+ }
+
+ /**
+ * Constructor from a page id
+ * @param $id Int article ID to load
+ */
+ public static function newFromID( $id ) {
+ $t = Title::newFromID( $id );
+ # @todo FIXME: Doesn't inherit right
+ return $t == null ? null : new self( $t );
+ # return $t == null ? null : new static( $t ); // PHP 5.3
}
/**
- * @param $file File:
+ * @param $file File:
* @return void
*/
public function setFile( $file ) {
+ $this->mPage->setFile( $file );
$this->displayImg = $file;
- $this->img = $file;
$this->fileLoaded = true;
}
@@ -39,18 +50,19 @@ class ImagePage extends Article {
}
$this->fileLoaded = true;
- $this->displayImg = $this->img = false;
- wfRunHooks( 'ImagePageFindFile', array( $this, &$this->img, &$this->displayImg ) );
- if ( !$this->img ) {
- $this->img = wfFindFile( $this->mTitle );
- if ( !$this->img ) {
- $this->img = wfLocalFile( $this->mTitle );
+ $this->displayImg = $img = false;
+ wfRunHooks( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) );
+ if ( !$img ) { // not set by hook?
+ $img = wfFindFile( $this->getTitle() );
+ if ( !$img ) {
+ $img = wfLocalFile( $this->getTitle() );
}
}
- if ( !$this->displayImg ) {
- $this->displayImg = $this->img;
+ $this->mPage->setFile( $img );
+ if ( !$this->displayImg ) { // not set by hook?
+ $this->displayImg = $img;
}
- $this->repo = $this->img->getRepo();
+ $this->repo = $img->getRepo();
}
/**
@@ -69,25 +81,25 @@ class ImagePage extends Article {
$diff = $wgRequest->getVal( 'diff' );
$diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
- if ( $this->mTitle->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) ) {
+ if ( $this->getTitle()->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() || isset( $diff ) ) {
+ if ( $this->getTitle()->getNamespace() == NS_FILE && $this->mPage->getFile()->getRedirected() ) {
+ if ( $this->getTitle()->getDBkey() == $this->mPage->getFile()->getName() || isset( $diff ) ) {
// mTitle is the same as the redirect target so ask Article
// to perform the redirect for us.
$wgRequest->setVal( 'diffonly', 'true' );
return parent::view();
} else {
- // mTitle is not the same as the redirect target so it is
+ // mTitle is not the same as the redirect target so it is
// probably the redirect page itself. Fake the redirect symbol
- $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
- $wgOut->addHTML( $this->viewRedirect( Title::makeTitle( NS_FILE, $this->img->getName() ),
+ $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
+ $wgOut->addHTML( $this->viewRedirect( Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ),
/* $appendSubtitle */ true, /* $forceKnown */ true ) );
- $this->viewUpdates();
+ $this->mPage->viewUpdates();
return;
}
}
@@ -95,34 +107,43 @@ class ImagePage extends Article {
$this->showRedirectedFromHeader();
if ( $wgShowEXIF && $this->displayImg->exists() ) {
- // FIXME: bad interface, see note on MediaHandler::formatMetadata().
+ // @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
$formattedMetadata = $this->displayImg->formatMetadata();
$showmeta = $formattedMetadata !== false;
} else {
$showmeta = false;
}
- if ( !$diff && $this->displayImg->exists() )
+ 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() ) {
+ if ( $this->mPage->getID() ) {
+ # NS_FILE is in the user language, but this section (the actual wikitext)
+ # should be in page content language
+ $pageLang = $this->getTitle()->getPageLanguage();
+ $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
+ 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
+ 'class' => 'mw-content-'.$pageLang->getDir() ) ) );
parent::view();
+ $wgOut->addHTML( Xml::closeElement( 'div' ) );
} else {
# Just need to set the right headers
$wgOut->setArticleFlag( true );
- $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
- $this->viewUpdates();
+ $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
+ $this->mPage->viewUpdates();
}
# Show shared description, if needed
if ( $this->mExtraDescription ) {
- $fol = wfMsgNoTrans( 'shareddescriptionfollows' );
- if ( $fol != '-' && !wfEmptyMsg( 'shareddescriptionfollows', $fol ) ) {
- $wgOut->addWikiText( $fol );
+ $fol = wfMessage( 'shareddescriptionfollows' );
+ if ( !$fol->isDisabled() ) {
+ $wgOut->addWikiText( $fol->plain() );
}
$wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" );
}
@@ -135,100 +156,38 @@ class ImagePage extends Article {
array( 'id' => 'filelinks' ),
wfMsg( 'imagelinks' ) ) . "\n" );
$this->imageDupes();
- # TODO! FIXME! For some freaky reason, we can't redirect to foreign images.
+ # @todo FIXME: For some freaky reason, we can't redirect to foreign images.
# Yet we return metadata about the target. Definitely an issue in the FileRepo
- $this->imageRedirects();
$this->imageLinks();
-
+
# Allow extensions to add something after the image links
$html = '';
wfRunHooks( 'ImagePageAfterImageLinks', array( $this, &$html ) );
- if ( $html )
+ if ( $html ) {
$wgOut->addHTML( $html );
+ }
if ( $showmeta ) {
$wgOut->addHTML( Xml::element( 'h2', array( 'id' => 'metadata' ), wfMsg( 'metadata' ) ) . "\n" );
$wgOut->addWikiText( $this->makeMetadataTable( $formattedMetadata ) );
- $wgOut->addModules( array( 'mediawiki.legacy.metadata' ) );
- }
-
- $css = $this->repo->getDescriptionStylesheetUrl();
- if ( $css ) {
- $wgOut->addStyle( $css );
- }
- }
-
- public function getRedirectTarget() {
- $this->loadFile();
- if ( $this->img->isLocal() ) {
- return parent::getRedirectTarget();
- }
- // Foreign image page
- $from = $this->img->getRedirected();
- $to = $this->img->getName();
- if ( $from == $to ) {
- return null;
+ $wgOut->addModules( array( 'mediawiki.action.view.metadata' ) );
}
- return $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
- }
- public function followRedirect() {
- $this->loadFile();
- if ( $this->img->isLocal() ) {
- return parent::followRedirect();
- }
- $from = $this->img->getRedirected();
- $to = $this->img->getName();
- if ( $from == $to ) {
- return false;
+
+ // Add remote Filepage.css
+ if( !$this->repo->isLocal() ) {
+ $css = $this->repo->getDescriptionStylesheetUrl();
+ if ( $css ) {
+ $wgOut->addStyle( $css );
+ }
}
- return Title::makeTitle( NS_FILE, $to );
+ // always show the local local Filepage.css, bug 29277
+ $wgOut->addModuleStyles( 'filepage' );
}
- public function isRedirect( $text = false ) {
- $this->loadFile();
- if ( $this->img->isLocal() )
- return parent::isRedirect( $text );
-
- return (bool)$this->img->getRedirected();
- }
-
- public function isLocal() {
- $this->loadFile();
- return $this->img->isLocal();
- }
-
- public function getFile() {
- $this->loadFile();
- return $this->img;
- }
-
+
public function getDisplayedFile() {
$this->loadFile();
return $this->displayImg;
}
-
- public function getDuplicates() {
- $this->loadFile();
- if ( !is_null( $this->dupes ) ) {
- return $this->dupes;
- }
- 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();
- $size = $this->img->getSize();
- foreach ( $dupes as $index => $file ) {
- $key = $file->getRepoName() . ':' . $file->getName();
- if ( $key == $self )
- unset( $dupes[$index] );
- if ( $file->getSize() != $size )
- unset( $dupes[$index] );
- }
- return $this->dupes = $dupes;
-
- }
-
/**
* Create the TOC
@@ -238,26 +197,26 @@ class ImagePage extends Article {
*/
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="#file">' . wfMsgHtml( 'file-anchor-link' ) . '</a></li>',
+ '<li><a href="#filehistory">' . wfMsgHtml( 'filehist' ) . '</a></li>',
+ '<li><a href="#filelinks">' . wfMsgHtml( 'imagelinks' ) . '</a></li>',
);
if ( $metadata ) {
$r[] = '<li><a href="#metadata">' . wfMsgHtml( 'metadata' ) . '</a></li>';
}
-
+
wfRunHooks( 'ImagePageShowTOC', array( $this, &$r ) );
-
+
return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>';
}
/**
* Make a table with metadata to be shown in the output page.
*
- * FIXME: bad interface, see note on MediaHandler::formatMetadata().
+ * @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
*
* @param $metadata Array: the array containing the EXIF data
- * @return String
+ * @return String The metadata table. This is treated as Wikitext (!)
*/
protected function makeMetadataTable( $metadata ) {
$r = "<div class=\"mw-imagepage-section-metadata\">";
@@ -265,7 +224,7 @@ class ImagePage extends Article {
$r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n";
foreach ( $metadata as $type => $stuff ) {
foreach ( $stuff as $v ) {
- # FIXME, why is this using escapeId for a class?!
+ # @todo FIXME: Why is this using escapeId for a class?!
$class = Sanitizer::escapeId( $v['id'] );
if ( $type == 'collapsed' ) {
$class .= ' collapsable';
@@ -287,7 +246,7 @@ class ImagePage extends Article {
*/
public function getContent() {
$this->loadFile();
- if ( $this->img && !$this->img->isLocal() && 0 == $this->getID() ) {
+ if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
return '';
}
return parent::getContent();
@@ -295,7 +254,7 @@ class ImagePage extends Article {
protected function openShowImage() {
global $wgOut, $wgUser, $wgImageLimits, $wgRequest,
- $wgLang, $wgContLang, $wgEnableUploads;
+ $wgLang, $wgEnableUploads;
$this->loadFile();
@@ -313,8 +272,7 @@ class ImagePage extends Article {
$max = $wgImageLimits[$sizeSel];
$maxWidth = $max[0];
$maxHeight = $max[1];
- $sk = $wgUser->getSkin();
- $dirmark = $wgContLang->getDirMark();
+ $dirmark = $wgLang->getDirMark();
if ( $this->displayImg->exists() ) {
# image
@@ -338,7 +296,7 @@ class ImagePage extends Article {
# 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, Linker::formatSize( $this->displayImg->getSize() ), $mime );
# We'll show a thumbnail of this image
if ( $width > $maxWidth || $height > $maxHeight ) {
# Calculate the thumbnail size.
@@ -355,23 +313,29 @@ class ImagePage extends Article {
# because of rounding.
}
$msgbig = wfMsgHtml( 'show-big-image' );
- $msgsmall = wfMsgExt( 'show-big-image-thumb', 'parseinline',
- $wgLang->formatNum( $width ),
- $wgLang->formatNum( $height )
- );
+ $otherSizes = array();
+ foreach ( $wgImageLimits as $size ) {
+ if ( $size[0] < $width_orig && $size[1] < $height_orig &&
+ $size[0] != $width && $size[1] != $height ) {
+ $otherSizes[] = $this->makeSizeLink( $params, $size[0], $size[1] );
+ }
+ }
+ $msgsmall = wfMessage( 'show-big-image-preview' )->
+ rawParams( $this->makeSizeLink( $params, $width, $height ) )->
+ parse() . ' ' .
+ wfMessage( 'show-big-image-other' )->
+ rawParams( $wgLang->pipeList( $otherSizes ) )->parse();
} else {
# Image is small enough to show full size on image page
$msgsmall = wfMsgExt( 'file-nohires', array( 'parseinline' ) );
}
$params['width'] = $width;
+ $params['height'] = $height;
$thumbnail = $this->displayImg->transform( $params );
$showLink = true;
- $anchorclose = '';
- if ( !$this->displayImg->mustRender() ) {
- $anchorclose = "<br />" . $msgsmall;
- }
+ $anchorclose = '<br />' . $msgsmall;
$isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
if ( $isMulti ) {
@@ -393,14 +357,14 @@ class ImagePage extends Article {
if ( $page > 1 ) {
$label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false );
- $link = $sk->link(
- $this->mTitle,
+ $link = Linker::link(
+ $this->getTitle(),
$label,
array(),
array( 'page' => $page - 1 ),
array( 'known', 'noclasses' )
);
- $thumb1 = $sk->makeThumbLinkObj( $this->mTitle, $this->displayImg, $link, $label, 'none',
+ $thumb1 = Linker::makeThumbLinkObj( $this->getTitle(), $this->displayImg, $link, $label, 'none',
array( 'page' => $page - 1 ) );
} else {
$thumb1 = '';
@@ -408,14 +372,14 @@ class ImagePage extends Article {
if ( $page < $count ) {
$label = wfMsg( 'imgmultipagenext' );
- $link = $sk->link(
- $this->mTitle,
+ $link = Linker::link(
+ $this->getTitle(),
$label,
array(),
array( 'page' => $page + 1 ),
array( 'known', 'noclasses' )
);
- $thumb2 = $sk->makeThumbLinkObj( $this->mTitle, $this->displayImg, $link, $label, 'none',
+ $thumb2 = Linker::makeThumbLinkObj( $this->getTitle(), $this->displayImg, $link, $label, 'none',
array( 'page' => $page + 1 ) );
} else {
$thumb2 = '';
@@ -439,7 +403,7 @@ class ImagePage extends Article {
$wgOut->addHTML(
'</td><td><div class="multipageimagenavbox">' .
Xml::openElement( 'form', $formParams ) .
- Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
wfMsgExt( 'imgmultigoto', array( 'parseinline', 'replaceafter' ), $select ) .
Xml::submitButton( wfMsg( 'imgmultigo' ) ) .
Xml::closeElement( 'form' ) .
@@ -452,14 +416,13 @@ class ImagePage extends Article {
$icon = $this->displayImg->iconThumb();
$wgOut->addHTML( '<div class="fullImageLink" id="file">' .
- $icon->toHtml( array( 'file-link' => true ) ) .
- "</div>\n" );
+ $icon->toHtml( array( 'file-link' => true ) ) .
+ "</div>\n" );
}
$showLink = true;
}
-
if ( $showLink ) {
$filename = wfEscapeWikiText( $this->displayImg->getName() );
$linktext = $filename;
@@ -494,21 +457,45 @@ EOT
$uploadTitle = SpecialPage::getTitleFor( 'Upload' );
$nofile = array(
'filepage-nofile-link',
- $uploadTitle->getFullUrl( array( 'wpDestFile' => $this->img->getName() ) )
+ $uploadTitle->getFullURL( array( 'wpDestFile' => $this->mPage->getFile()->getName() ) )
);
- }
- else
- {
+ } else {
$nofile = 'filepage-nofile';
}
+ // Note, if there is an image description page, but
+ // no image, then this setRobotPolicy is overriden
+ // by Article::View().
$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" );
+ $wgRequest->response()->header( 'HTTP/1.1 404 Not Found' );
}
}
+ $wgOut->setFileVersion( $this->displayImg );
+ }
+
+ /**
+ * Creates an thumbnail of specified size and returns an HTML link to it
+ * @param array $params Scaler parameters
+ * @param int $width
+ * @param int $height
+ */
+ private function makeSizeLink( $params, $width, $height ) {
+ $params['width'] = $width;
+ $params['height'] = $height;
+ $thumbnail = $this->displayImg->transform( $params );
+ if ( $thumbnail && !$thumbnail->isError() ) {
+ return Html::rawElement( 'a', array(
+ 'href' => $thumbnail->getUrl(),
+ 'class' => 'mw-thumbnail-link'
+ ), wfMessage( 'show-big-image-size' )->numParams(
+ $thumbnail->getWidth(), $thumbnail->getHeight()
+ )->parse() );
+ } else {
+ return '';
+ }
}
/**
@@ -519,16 +506,16 @@ EOT
$this->loadFile();
- $descUrl = $this->img->getDescriptionUrl();
- $descText = $this->img->getDescriptionText();
+ $descUrl = $this->mPage->getFile()->getDescriptionUrl();
+ $descText = $this->mPage->getFile()->getDescriptionText();
/* Add canonical to head if there is no local page for this shared file */
- if( $descUrl && $this->getID() == 0 ) {
+ if( $descUrl && $this->mPage->getID() == 0 ) {
$wgOut->addLink( array( 'rel' => 'canonical', 'href' => $descUrl ) );
}
$wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
- $repo = $this->img->getRepo()->getDisplayName();
+ $repo = $this->mPage->getFile()->getRepo()->getDisplayName();
if ( $descUrl && $descText && wfMsgNoTrans( 'sharedupload-desc-here' ) !== '-' ) {
$wgOut->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) );
@@ -546,8 +533,8 @@ EOT
public function getUploadUrl() {
$this->loadFile();
$uploadTitle = SpecialPage::getTitleFor( 'Upload' );
- return $uploadTitle->getFullUrl( array(
- 'wpDestFile' => $this->img->getName(),
+ return $uploadTitle->getFullURL( array(
+ 'wpDestFile' => $this->mPage->getFile()->getName(),
'wpForReUpload' => 1
) );
}
@@ -559,26 +546,27 @@ EOT
protected function uploadLinksBox() {
global $wgUser, $wgOut, $wgEnableUploads, $wgUseExternalEditor;
- if ( !$wgEnableUploads ) { return; }
+ if ( !$wgEnableUploads ) {
+ return;
+ }
$this->loadFile();
- if ( !$this->img->isLocal() )
+ if ( !$this->mPage->getFile()->isLocal() ) {
return;
-
- $sk = $wgUser->getSkin();
+ }
$wgOut->addHTML( "<br /><ul>\n" );
# "Upload a new version of this file" link
- if ( UploadBase::userCanReUpload( $wgUser, $this->img->name ) ) {
- $ulink = $sk->makeExternalLink( $this->getUploadUrl(), wfMsg( 'uploadnewversion-linktext' ) );
+ if ( UploadBase::userCanReUpload( $wgUser, $this->mPage->getFile()->name ) ) {
+ $ulink = Linker::makeExternalLink( $this->getUploadUrl(), wfMsg( 'uploadnewversion-linktext' ) );
$wgOut->addHTML( "<li id=\"mw-imagepage-reupload-link\"><div class=\"plainlinks\">{$ulink}</div></li>\n" );
}
# External editing link
if ( $wgUseExternalEditor ) {
- $elink = $sk->link(
- $this->mTitle,
+ $elink = Linker::link(
+ $this->getTitle(),
wfMsgHtml( 'edit-externally' ),
array(),
array(
@@ -588,7 +576,11 @@ EOT
),
array( 'known', 'noclasses' )
);
- $wgOut->addHTML( '<li id="mw-imagepage-edit-external">' . $elink . ' <small>' . wfMsgExt( 'edit-externally-help', array( 'parseinline' ) ) . "</small></li>\n" );
+ $wgOut->addHTML(
+ '<li id="mw-imagepage-edit-external">' . $elink . ' <small>' .
+ wfMsgExt( 'edit-externally-help', array( 'parseinline' ) ) .
+ "</small></li>\n"
+ );
}
$wgOut->addHTML( "</ul>\n" );
@@ -608,69 +600,118 @@ EOT
$wgOut->addHTML( $pager->getBody() );
$wgOut->preventClickjacking( $pager->getPreventClickjacking() );
- $this->img->resetHistory(); // free db resources
+ $this->mPage->getFile()->resetHistory(); // free db resources
# 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->mPage->getFile()->exists() ) {
$this->uploadLinksBox();
}
}
- protected function imageLinks() {
- global $wgUser, $wgOut, $wgLang;
-
- $limit = 100;
-
+ protected function queryImageLinks( $target, $limit ) {
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select(
+ return $dbr->select(
array( 'imagelinks', 'page' ),
- array( 'page_namespace', 'page_title' ),
- array( 'il_to' => $this->mTitle->getDBkey(), 'il_from = page_id' ),
+ array( 'page_namespace', 'page_title', 'page_is_redirect', 'il_to' ),
+ array( 'il_to' => $target, 'il_from = page_id' ),
__METHOD__,
- array( 'LIMIT' => $limit + 1 )
+ array( 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', )
);
- $count = $dbr->numRows( $res );
+ }
+
+ protected function imageLinks() {
+ global $wgOut, $wgLang;
+
+ $limit = 100;
+
+ $res = $this->queryImageLinks( $this->getTitle()->getDbKey(), $limit + 1);
+ $rows = array();
+ $redirects = array();
+ foreach ( $res as $row ) {
+ if ( $row->page_is_redirect ) {
+ $redirects[$row->page_title] = array();
+ }
+ $rows[] = $row;
+ }
+ $count = count( $rows );
+
+ $hasMore = $count > $limit;
+ if ( !$hasMore && count( $redirects ) ) {
+ $res = $this->queryImageLinks( array_keys( $redirects ),
+ $limit - count( $rows ) + 1 );
+ foreach ( $res as $row ) {
+ $redirects[$row->il_to][] = $row;
+ $count++;
+ }
+ $hasMore = ( $res->numRows() + count( $rows ) ) > $limit;
+ }
+
if ( $count == 0 ) {
- $wgOut->wrapWikiMsg( Html::rawElement( 'div', array ( 'id' => 'mw-imagepage-nolinkstoimage' ), "\n$1\n" ), 'nolinkstoimage' );
+ $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 ( !$hasMore ) {
$wgOut->addWikiMsg( 'linkstoimage', $count );
} else {
// More links than the limit. Add a link to [[Special:Whatlinkshere]]
$wgOut->addWikiMsg( 'linkstoimage-more',
$wgLang->formatNum( $limit ),
- $this->mTitle->getPrefixedDBkey()
+ $this->getTitle()->getPrefixedDBkey()
);
}
- $wgOut->addHTML( Html::openElement( 'ul', array( 'class' => 'mw-imagepage-linkstoimage' ) ) . "\n" );
- $sk = $wgUser->getSkin();
+ $wgOut->addHTML(
+ Html::openElement( 'ul',
+ array( 'class' => 'mw-imagepage-linkstoimage' ) ) . "\n"
+ );
$count = 0;
- $elements = array();
- foreach ( $res as $s ) {
- $count++;
- if ( $count <= $limit ) {
- // We have not yet reached the extra one that tells us there is more to fetch
- $elements[] = $s;
- }
- }
// Sort the list by namespace:title
- usort ( $elements, array( $this, 'compare' ) );
+ usort( $rows, 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(
+ $currentCount = 0;
+ foreach( $rows as $element ) {
+ $currentCount++;
+ if ( $currentCount > $limit ) {
+ break;
+ }
+
+ $link = Linker::linkKnown( Title::makeTitle( $element->page_namespace, $element->page_title ) );
+ if ( !isset( $redirects[$element->page_title] ) ) {
+ $liContents = $link;
+ } else {
+ $ul = "<ul class='mw-imagepage-redirectstofile'>\n";
+ foreach ( $redirects[$element->page_title] as $row ) {
+ $currentCount++;
+ if ( $currentCount > $limit ) {
+ break;
+ }
+
+ $link2 = Linker::linkKnown( Title::makeTitle( $row->page_namespace, $row->page_title ) );
+ $ul .= Html::rawElement(
'li',
array( 'id' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
- $link
- ) . "\n"
+ $link2
+ ) . "\n";
+ }
+ $ul .= '</ul>';
+ $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams(
+ $link, $ul )->parse();
+ }
+ $wgOut->addHTML( Html::rawElement(
+ 'li',
+ array( 'id' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
+ $liContents
+ ) . "\n"
);
};
@@ -679,57 +720,31 @@ EOT
// Add a links to [[Special:Whatlinkshere]]
if ( $count > $limit ) {
- $wgOut->addWikiMsg( 'morelinkstoimage', $this->mTitle->getPrefixedDBkey() );
+ $wgOut->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() );
}
$wgOut->addHTML( Html::closeElement( 'div' ) . "\n" );
}
-
- protected function imageRedirects() {
- global $wgUser, $wgOut, $wgLang;
-
- $redirects = $this->getTitle()->getRedirectsHere( NS_FILE );
- if ( count( $redirects ) == 0 ) return;
-
- $wgOut->addHTML( "<div id='mw-imagepage-section-redirectstofile'>\n" );
- $wgOut->addWikiMsg( 'redirectstofile',
- $wgLang->formatNum( count( $redirects ) )
- );
- $wgOut->addHTML( "<ul class='mw-imagepage-redirectstofile'>\n" );
-
- $sk = $wgUser->getSkin();
- foreach ( $redirects as $title ) {
- $link = $sk->link(
- $title,
- null,
- array(),
- array( 'redirect' => 'no' ),
- array( 'known', 'noclasses' )
- );
- $wgOut->addHTML( "<li>{$link}</li>\n" );
- }
- $wgOut->addHTML( "</ul></div>\n" );
-
- }
protected function imageDupes() {
- global $wgOut, $wgUser, $wgLang;
+ global $wgOut, $wgLang;
$this->loadFile();
- $dupes = $this->getDuplicates();
- if ( count( $dupes ) == 0 ) return;
+ $dupes = $this->mPage->getDuplicates();
+ if ( count( $dupes ) == 0 ) {
+ return;
+ }
$wgOut->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
$wgOut->addWikiMsg( 'duplicatesoffile',
- $wgLang->formatNum( count( $dupes ) ), $this->mTitle->getDBkey()
+ $wgLang->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey()
);
$wgOut->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
- $sk = $wgUser->getSkin();
foreach ( $dupes as $file ) {
$fromSrc = '';
if ( $file->isLocal() ) {
- $link = $sk->link(
+ $link = Linker::link(
$file->getTitle(),
null,
array(),
@@ -737,7 +752,7 @@ EOT
array( 'known', 'noclasses' )
);
} else {
- $link = $sk->makeExternalLink( $file->getDescriptionUrl(),
+ $link = Linker::makeExternalLink( $file->getDescriptionUrl(),
$file->getTitle()->getPrefixedText() );
$fromSrc = wfMsg( 'shared-repo-from', $file->getRepo()->getDisplayName() );
}
@@ -751,68 +766,38 @@ EOT
*/
public function delete() {
global $wgUploadMaintenance;
- if ( $wgUploadMaintenance && $this->mTitle && $this->mTitle->getNamespace() == NS_FILE ) {
+ if ( $wgUploadMaintenance && $this->getTitle() && $this->getTitle()->getNamespace() == NS_FILE ) {
global $wgOut;
$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->mPage->getFile()->exists() || !$this->mPage->getFile()->isLocal() || $this->mPage->getFile()->getRedirected() ) {
// Standard article deletion
parent::delete();
return;
}
- $deleter = new FileDeleteForm( $this->img );
+ $deleter = new FileDeleteForm( $this->mPage->getFile() );
$deleter->execute();
}
/**
- * Revert the file to an earlier version
- */
- public function revert() {
- $this->loadFile();
- $reverter = new FileRevertForm( $this->img );
- $reverter->execute();
- }
-
- /**
- * Override handling of action=purge
- */
- public function doPurge() {
- $this->loadFile();
- if ( $this->img->exists() ) {
- wfDebug( "ImagePage::doPurge purging " . $this->img->getName() . "\n" );
- $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
- $update->doUpdate();
- $this->img->upgradeRow();
- $this->img->purgeCache();
- } else {
- wfDebug( "ImagePage::doPurge no image for " . $this->img->getName() . "; limiting purge to cache only\n" );
- // even if the file supposedly doesn't exist, force any cached information
- // to be updated (in case the cached information is wrong)
- $this->img->purgeCache();
- }
- parent::doPurge();
- }
-
- /**
* Display an error with a wikitext description
*/
function showError( $description ) {
global $wgOut;
- $wgOut->setPageTitle( wfMsg( "internalerror" ) );
- $wgOut->setRobotPolicy( "noindex,nofollow" );
+ $wgOut->setPageTitle( wfMsg( 'internalerror' ) );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setArticleRelated( false );
$wgOut->enableClientCache( false );
$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
@@ -833,12 +818,29 @@ EOT
*/
class ImageHistoryList {
- protected $imagePage, $img, $skin, $title, $repo, $showThumb;
+ /**
+ * @var Title
+ */
+ protected $title;
+
+ /**
+ * @var File
+ */
+ protected $img;
+
+ /**
+ * @var ImagePage
+ */
+ protected $imagePage;
+
+ protected $repo, $showThumb;
protected $preventClickjacking = false;
+ /**
+ * @param ImagePage $imagePage
+ */
public function __construct( $imagePage ) {
- global $wgUser, $wgShowArchiveThumbnails;
- $this->skin = $wgUser->getSkin();
+ global $wgShowArchiveThumbnails;
$this->current = $imagePage->getFile();
$this->img = $imagePage->getDisplayedFile();
$this->title = $imagePage->getTitle();
@@ -850,10 +852,6 @@ class ImageHistoryList {
return $this->imagePage;
}
- public function getSkin() {
- return $this->skin;
- }
-
public function getFile() {
return $this->img;
}
@@ -866,7 +864,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->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<td></td>' : '' )
. '<th>' . wfMsgHtml( 'filehist-datetime' ) . '</th>'
. ( $this->showThumb ? '<th>' . wfMsgHtml( 'filehist-thumb' ) . '</th>' : '' )
. '<th>' . wfMsgHtml( 'filehist-dimensions' ) . '</th>'
@@ -879,8 +877,13 @@ class ImageHistoryList {
return "</table>\n$navLinks\n</div>\n";
}
+ /**
+ * @param $iscur
+ * @param $file File
+ * @return string
+ */
public function imageHistoryLine( $iscur, $file ) {
- global $wgUser, $wgLang;
+ global $wgUser, $wgLang, $wgContLang;
$timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
$img = $iscur ? $file->getName() : $file->getArchiveName();
@@ -892,14 +895,15 @@ class ImageHistoryList {
$row = $selected = '';
// Deletion link
- if ( $local && ( $wgUser->isAllowed( 'delete' ) || $wgUser->isAllowed( 'deletedhistory' ) ) ) {
+ if ( $local && ( $wgUser->isAllowedAny( 'delete', 'deletedhistory' ) ) ) {
$row .= '<td>';
# Link to remove from history
if ( $wgUser->isAllowed( 'delete' ) ) {
$q = array( 'action' => 'delete' );
- if ( !$iscur )
+ if ( !$iscur ) {
$q['oldimage'] = $img;
- $row .= $this->skin->link(
+ }
+ $row .= Linker::link(
$this->title,
wfMsgHtml( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' ),
array(), $q, array( 'known' )
@@ -913,7 +917,7 @@ class ImageHistoryList {
}
// If file is top revision or locked from this user, don't link
if ( $iscur || !$file->userCan( File::DELETED_RESTRICTED ) ) {
- $del = $this->skin->revDeleteLinkDisabled( $canHide );
+ $del = Linker::revDeleteLinkDisabled( $canHide );
} else {
list( $ts, $name ) = explode( '!', $img, 2 );
$query = array(
@@ -921,7 +925,7 @@ class ImageHistoryList {
'target' => $this->title->getPrefixedText(),
'ids' => $ts,
);
- $del = $this->skin->revDeleteLink( $query,
+ $del = Linker::revDeleteLink( $query,
$file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
}
$row .= $del;
@@ -937,7 +941,7 @@ class ImageHistoryList {
if ( $file->isDeleted( File::DELETED_FILE ) ) {
$row .= wfMsgHtml( 'filehist-revert' );
} else {
- $row .= $this->skin->link(
+ $row .= Linker::link(
$this->title,
wfMsgHtml( 'filehist-revert' ),
array(),
@@ -961,20 +965,24 @@ class ImageHistoryList {
# Don't link to unviewable files
$row .= '<span class="history-deleted">' . $wgLang->timeAndDate( $timestamp, true ) . '</span>';
} elseif ( $file->isDeleted( File::DELETED_FILE ) ) {
- $this->preventClickjacking();
- $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
- # Make a link to review the image
- $url = $this->skin->link(
- $revdel,
- $wgLang->timeAndDate( $timestamp, true ),
- array(),
- array(
- 'target' => $this->title->getPrefixedText(),
- 'file' => $img,
- 'token' => $wgUser->editToken( $img )
- ),
- array( 'known', 'noclasses' )
- );
+ if ( $local ) {
+ $this->preventClickjacking();
+ $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
+ # Make a link to review the image
+ $url = Linker::link(
+ $revdel,
+ $wgLang->timeAndDate( $timestamp, true ),
+ array(),
+ array(
+ 'target' => $this->title->getPrefixedText(),
+ 'file' => $img,
+ 'token' => $wgUser->editToken( $img )
+ ),
+ array( 'known', 'noclasses' )
+ );
+ } else {
+ $url = $wgLang->timeAndDate( $timestamp, true );
+ }
$row .= '<span class="history-deleted">' . $url . '</span>';
} else {
$url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
@@ -990,7 +998,7 @@ class ImageHistoryList {
// Image dimensions + size
$row .= '<td>';
$row .= htmlspecialchars( $file->getDimensionsString() );
- $row .= " <span style='white-space: nowrap;'>(" . $this->skin->formatSize( $file->getSize() ) . ')</span>';
+ $row .= ' <span style="white-space: nowrap;">(' . Linker::formatSize( $file->getSize() ) . ')</span>';
$row .= '</td>';
// Uploading user
@@ -1000,29 +1008,32 @@ class ImageHistoryList {
$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>';
+ $row .= Linker::userLink( $user, $usertext ) . ' <span style="white-space: nowrap;">' .
+ Linker::userToolLinks( $user, $usertext ) . '</span>';
} else {
$row .= htmlspecialchars( $usertext );
}
}
- $row .= '</td><td>';
+ $row .= '</td>';
// Don't show deleted descriptions
if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
- $row .= '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
+ $row .= '<td><span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span></td>';
} else {
- $row .= $this->skin->commentBlock( $description, $this->title );
+ $row .= '<td dir="' . $wgContLang->getDir() . '">' . Linker::commentBlock( $description, $this->title ) . '</td>';
}
- $row .= '</td>';
$rowClass = null;
wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) );
- $classAttr = $rowClass ? " class='$rowClass'" : "";
+ $classAttr = $rowClass ? " class='$rowClass'" : '';
return "<tr{$classAttr}>{$row}</tr>\n";
}
+ /**
+ * @param $file File
+ * @return string
+ */
protected function getThumbForLine( $file ) {
global $wgLang;
@@ -1041,8 +1052,10 @@ class ImageHistoryList {
$wgLang->time( $timestamp, true ) ),
'file-link' => true,
);
-
- if ( !$thumbnail ) return wfMsgHtml( 'filehist-nothumb' );
+
+ if ( !$thumbnail ) {
+ return wfMsgHtml( 'filehist-nothumb' );
+ }
return $thumbnail->toHtml( $options );
} else {
@@ -1062,6 +1075,19 @@ class ImageHistoryList {
class ImageHistoryPseudoPager extends ReverseChronologicalPager {
protected $preventClickjacking = false;
+ /**
+ * @var File
+ */
+ protected $mImg;
+
+ /**
+ * @var Title
+ */
+ protected $mTitle;
+
+ /**
+ * @param ImagePage $imagePage
+ */
function __construct( $imagePage ) {
parent::__construct();
$this->mImagePage = $imagePage;
@@ -1071,7 +1097,7 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
$this->mHist = array();
$this->mRange = array( 0, 0 ); // display range
}
-
+
function getTitle() {
return $this->mTitle;
}
@@ -1087,7 +1113,7 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
function formatRow( $row ) {
return '';
}
-
+
function getBody() {
$s = '';
$this->doQuery();
@@ -1111,7 +1137,9 @@ 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() ) {
return;
@@ -1188,7 +1216,7 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
}
$this->mQueryDone = true;
}
-
+
protected function preventClickjacking( $enable = true ) {
$this->preventClickjacking = $enable;
}
diff --git a/includes/ImageQueryPage.php b/includes/ImageQueryPage.php
index 180201a2..f46974b2 100644
--- a/includes/ImageQueryPage.php
+++ b/includes/ImageQueryPage.php
@@ -7,15 +7,15 @@
* @ingroup SpecialPage
* @author Rob Church <robchur@gmail.com>
*/
-class ImageQueryPage extends QueryPage {
+abstract class ImageQueryPage extends QueryPage {
/**
* Format and output report results using the given information plus
* OutputPage
*
* @param $out OutputPage to print to
- * @param $skin Skin: user skin to use
- * @param $dbr Database (read) connection to use
+ * @param $skin Skin: user skin to use [unused]
+ * @param $dbr DatabaseBase (read) connection to use
* @param $res Integer: result pointer
* @param $num Integer: number of available result rows
* @param $offset Integer: paging offset
@@ -23,14 +23,14 @@ class ImageQueryPage extends QueryPage {
protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
if( $num > 0 ) {
$gallery = new ImageGallery();
- $gallery->useSkin( $skin );
# $res might contain the whole 1,000 rows, so we read up to
# $num [should update this to use a Pager]
for( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
- $image = $this->prepareImage( $row );
- if( $image ) {
- $gallery->add( $image->getTitle(), $this->getCellHtml( $row ) );
+ $namespace = isset( $row->namespace ) ? $row->namespace : NS_FILE;
+ $title = Title::makeTitleSafe( $namespace, $row->title );
+ if ( $title instanceof Title && $title->getNamespace() == NS_FILE ) {
+ $gallery->add( $title, $this->getCellHtml( $row ) );
}
}
@@ -38,19 +38,8 @@ class ImageQueryPage extends QueryPage {
}
}
- /**
- * Prepare an image object given a result row
- *
- * @param $row Object: result row
- * @return Image
- */
- private function prepareImage( $row ) {
- $namespace = isset( $row->namespace ) ? $row->namespace : NS_FILE;
- $title = Title::makeTitleSafe( $namespace, $row->title );
- return ( $title instanceof Title && $title->getNamespace() == NS_FILE )
- ? wfFindFile( $title )
- : null;
- }
+ // Gotta override this since it's abstract
+ function formatResult( $skin, $result ) { }
/**
* Get additional HTML to be shown in a results' cell
diff --git a/includes/Import.php b/includes/Import.php
index c76a6834..b874462e 100644
--- a/includes/Import.php
+++ b/includes/Import.php
@@ -35,16 +35,22 @@ class WikiImporter {
private $mLogItemCallback, $mUploadCallback, $mRevisionCallback, $mPageCallback;
private $mSiteInfoCallback, $mTargetNamespace, $mPageOutCallback;
private $mDebug;
+ private $mImportUploads, $mImageBasePath;
/**
* 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" );
+ if (defined( 'LIBXML_PARSEHUGE' ) ) {
+ $this->reader->open( "uploadsource://$id", null, LIBXML_PARSEHUGE );
+ }
+ else {
+ $this->reader->open( "uploadsource://$id" );
+ }
// Default callbacks
$this->setRevisionCallback( array( $this, "importRevision" ) );
@@ -163,12 +169,22 @@ class WikiImporter {
// Don't override namespaces
$this->mTargetNamespace = null;
} elseif( $namespace >= 0 ) {
- // FIXME: Check for validity
+ // @todo FIXME: Check for validity
$this->mTargetNamespace = intval( $namespace );
} else {
return false;
}
}
+
+ /**
+ *
+ */
+ public function setImageBasePath( $dir ) {
+ $this->mImageBasePath = $dir;
+ }
+ public function setImportUploads( $import ) {
+ $this->mImportUploads = $import;
+ }
/**
* Default per-revision callback, performs the import.
@@ -192,9 +208,8 @@ class WikiImporter {
* Dummy for now...
*/
public function importUpload( $revision ) {
- //$dbw = wfGetDB( DB_MASTER );
- //return $dbw->deadlockLoop( array( $revision, 'importUpload' ) );
- return false;
+ $dbw = wfGetDB( DB_MASTER );
+ return $dbw->deadlockLoop( array( $revision, 'importUpload' ) );
}
/**
@@ -295,7 +310,7 @@ class WikiImporter {
return $buffer;
}
}
-
+
$this->reader->close();
return '';
}
@@ -545,18 +560,27 @@ class WikiImporter {
private function processRevision( $pageInfo, $revisionInfo ) {
$revision = new WikiRevision;
- $revision->setID( $revisionInfo['id'] );
- $revision->setText( $revisionInfo['text'] );
+ if( isset( $revisionInfo['id'] ) ) {
+ $revision->setID( $revisionInfo['id'] );
+ }
+ if ( isset( $revisionInfo['text'] ) ) {
+ $revision->setText( $revisionInfo['text'] );
+ }
$revision->setTitle( $pageInfo['_title'] );
- $revision->setTimestamp( $revisionInfo['timestamp'] );
+
+ if ( isset( $revisionInfo['timestamp'] ) ) {
+ $revision->setTimestamp( $revisionInfo['timestamp'] );
+ } else {
+ $revision->setTimestamp( wfTimestampNow() );
+ }
if ( isset( $revisionInfo['comment'] ) ) {
$revision->setComment( $revisionInfo['comment'] );
}
- if ( isset( $revisionInfo['minor'] ) )
+ if ( isset( $revisionInfo['minor'] ) ) {
$revision->setMinor( true );
-
+ }
if ( isset( $revisionInfo['contributor']['ip'] ) ) {
$revision->setUserIP( $revisionInfo['contributor']['ip'] );
}
@@ -572,7 +596,7 @@ class WikiImporter {
$uploadInfo = array();
$normalFields = array( 'timestamp', 'comment', 'filename', 'text',
- 'src', 'size' );
+ 'src', 'size', 'sha1base36', 'archivename', 'rel' );
$skip = false;
@@ -591,24 +615,59 @@ class WikiImporter {
$uploadInfo[$tag] = $this->nodeContents();
} elseif ( $tag == 'contributor' ) {
$uploadInfo['contributor'] = $this->handleContributor();
+ } elseif ( $tag == 'contents' ) {
+ $contents = $this->nodeContents();
+ $encoding = $this->reader->getAttribute( 'encoding' );
+ if ( $encoding === 'base64' ) {
+ $uploadInfo['fileSrc'] = $this->dumpTemp( base64_decode( $contents ) );
+ $uploadInfo['isTempSrc'] = true;
+ }
} elseif ( $tag != '#text' ) {
$this->warn( "Unhandled upload XML tag $tag" );
$skip = true;
}
}
+
+ if ( $this->mImageBasePath && isset( $uploadInfo['rel'] ) ) {
+ $path = "{$this->mImageBasePath}/{$uploadInfo['rel']}";
+ if ( file_exists( $path ) ) {
+ $uploadInfo['fileSrc'] = $path;
+ $uploadInfo['isTempSrc'] = false;
+ }
+ }
- return $this->processUpload( $pageInfo, $uploadInfo );
+ if ( $this->mImportUploads ) {
+ return $this->processUpload( $pageInfo, $uploadInfo );
+ }
+ }
+
+ private function dumpTemp( $contents ) {
+ $filename = tempnam( wfTempDir(), 'importupload' );
+ file_put_contents( $filename, $contents );
+ return $filename;
}
+
private function processUpload( $pageInfo, $uploadInfo ) {
$revision = new WikiRevision;
+ $text = isset( $uploadInfo['text'] ) ? $uploadInfo['text'] : '';
$revision->setTitle( $pageInfo['_title'] );
- $revision->setID( $uploadInfo['id'] );
+ $revision->setID( $pageInfo['id'] );
$revision->setTimestamp( $uploadInfo['timestamp'] );
- $revision->setText( $uploadInfo['text'] );
+ $revision->setText( $text );
$revision->setFilename( $uploadInfo['filename'] );
+ if ( isset( $uploadInfo['archivename'] ) ) {
+ $revision->setArchiveName( $uploadInfo['archivename'] );
+ }
$revision->setSrc( $uploadInfo['src'] );
+ if ( isset( $uploadInfo['fileSrc'] ) ) {
+ $revision->setFileSrc( $uploadInfo['fileSrc'],
+ !empty( $uploadInfo['isTempSrc'] ) );
+ }
+ if ( isset( $uploadInfo['sha1base36'] ) ) {
+ $revision->setSha1Base36( $uploadInfo['sha1base36'] );
+ }
$revision->setSize( intval( $uploadInfo['size'] ) );
$revision->setComment( $uploadInfo['comment'] );
@@ -619,7 +678,7 @@ class WikiImporter {
$revision->setUserName( $uploadInfo['contributor']['username'] );
}
- return $this->uploadCallback( $revision );
+ return call_user_func( $this->mUploadCallback, $revision );
}
private function handleContributor() {
@@ -778,6 +837,7 @@ class XMLReader2 extends XMLReader {
* @ingroup SpecialPage
*/
class WikiRevision {
+ var $importer = null;
var $title = null;
var $id = 0;
var $timestamp = "20010115000000";
@@ -789,6 +849,10 @@ class WikiRevision {
var $type = "";
var $action = "";
var $params = "";
+ var $fileSrc = '';
+ var $sha1base36 = false;
+ var $isTemp = false;
+ var $archiveName = '';
function setTitle( $title ) {
if( is_object( $title ) ) {
@@ -832,27 +896,40 @@ class WikiRevision {
function setSrc( $src ) {
$this->src = $src;
}
+ function setFileSrc( $src, $isTemp ) {
+ $this->fileSrc = $src;
+ $this->fileIsTemp = $isTemp;
+ }
+ function setSha1Base36( $sha1base36 ) {
+ $this->sha1base36 = $sha1base36;
+ }
function setFilename( $filename ) {
$this->filename = $filename;
}
+ function setArchiveName( $archiveName ) {
+ $this->archiveName = $archiveName;
+ }
function setSize( $size ) {
$this->size = intval( $size );
}
-
+
function setType( $type ) {
$this->type = $type;
}
-
+
function setAction( $action ) {
$this->action = $action;
}
-
+
function setParams( $params ) {
$this->params = $params;
}
+ /**
+ * @return Title
+ */
function getTitle() {
return $this->title;
}
@@ -884,23 +961,38 @@ class WikiRevision {
function getSrc() {
return $this->src;
}
+ function getSha1() {
+ if ( $this->sha1base36 ) {
+ return wfBaseConvert( $this->sha1base36, 36, 16 );
+ }
+ return false;
+ }
+ function getFileSrc() {
+ return $this->fileSrc;
+ }
+ function isTempSrc() {
+ return $this->isTemp;
+ }
function getFilename() {
return $this->filename;
}
+ function getArchiveName() {
+ return $this->archiveName;
+ }
function getSize() {
return $this->size;
}
-
+
function getType() {
return $this->type;
}
-
+
function getAction() {
return $this->action;
}
-
+
function getParams() {
return $this->params;
}
@@ -913,9 +1005,11 @@ class WikiRevision {
if( $user ) {
$userId = intval( $user->getId() );
$userText = $user->getName();
+ $userObj = $user;
} else {
$userId = 0;
$userText = $this->getUser();
+ $userObj = new User;
}
// avoid memory leak...?
@@ -928,6 +1022,7 @@ class WikiRevision {
# must create the page...
$pageId = $article->insertOn( $dbw );
$created = true;
+ $oldcountable = null;
} else {
$created = false;
@@ -939,14 +1034,15 @@ class WikiRevision {
__METHOD__
);
if( $prior ) {
- // FIXME: this could fail slightly for multiple matches :P
+ // @todo FIXME: This could fail slightly for multiple matches :P
wfDebug( __METHOD__ . ": skipping existing revision for [[" .
$this->title->getPrefixedText() . "]], timestamp " . $this->timestamp . "\n" );
return false;
}
+ $oldcountable = $article->isCountable();
}
- # FIXME: Use original rev_id optionally (better for backups)
+ # @todo FIXME: Use original rev_id optionally (better for backups)
# Insert the row
$revision = new Revision( array(
'page' => $pageId,
@@ -957,47 +1053,27 @@ class WikiRevision {
'timestamp' => $this->timestamp,
'minor_edit' => $this->minor,
) );
- $revId = $revision->insertOn( $dbw );
+ $revision->insertOn( $dbw );
$changed = $article->updateIfNewerOn( $dbw, $revision );
-
- # To be on the safe side...
- $tempTitle = $GLOBALS['wgTitle'];
- $GLOBALS['wgTitle'] = $this->title;
- if( $created ) {
- wfDebug( __METHOD__ . ": running onArticleCreate\n" );
- Article::onArticleCreate( $this->title );
-
- wfDebug( __METHOD__ . ": running create updates\n" );
- $article->createUpdates( $revision );
-
- } elseif( $changed ) {
- wfDebug( __METHOD__ . ": running onArticleEdit\n" );
- Article::onArticleEdit( $this->title );
-
- wfDebug( __METHOD__ . ": running edit updates\n" );
- $article->editUpdates(
- $this->getText(),
- $this->getComment(),
- $this->minor,
- $this->timestamp,
- $revId );
+ if ( $changed !== false ) {
+ wfDebug( __METHOD__ . ": running updates\n" );
+ $article->doEditUpdates( $revision, $userObj, array( 'created' => $created, 'oldcountable' => $oldcountable ) );
}
- $GLOBALS['wgTitle'] = $tempTitle;
return true;
}
-
+
function importLogItem() {
$dbw = wfGetDB( DB_MASTER );
- # FIXME: this will not record autoblocks
+ # @todo FIXME: This will not record autoblocks
if( !$this->getTitle() ) {
- wfDebug( __METHOD__ . ": skipping invalid {$this->type}/{$this->action} log time, timestamp " .
+ wfDebug( __METHOD__ . ": skipping invalid {$this->type}/{$this->action} log time, timestamp " .
$this->timestamp . "\n" );
return;
}
# Check if it exists already
- // FIXME: use original log ID (better for backups)
+ // @todo FIXME: Use original log ID (better for backups)
$prior = $dbw->selectField( 'logging', '1',
array( 'log_type' => $this->getType(),
'log_action' => $this->getAction(),
@@ -1009,9 +1085,9 @@ class WikiRevision {
'log_params' => $this->params ),
__METHOD__
);
- // FIXME: this could fail slightly for multiple matches :P
+ // @todo FIXME: This could fail slightly for multiple matches :P
if( $prior ) {
- wfDebug( __METHOD__ . ": skipping existing item for Log:{$this->type}/{$this->action}, timestamp " .
+ wfDebug( __METHOD__ . ": skipping existing item for Log:{$this->type}/{$this->action}, timestamp " .
$this->timestamp . "\n" );
return false;
}
@@ -1032,60 +1108,66 @@ class WikiRevision {
}
function importUpload() {
- wfDebug( __METHOD__ . ": STUB\n" );
-
- /**
- // from file revert...
- $source = $this->file->getArchiveVirtualUrl( $this->oldimage );
- $comment = $wgRequest->getText( 'wpComment' );
- // TODO: Preserve file properties from database instead of reloading from file
- $status = $this->file->upload( $source, $comment, $comment );
- if( $status->isGood() ) {
- */
-
- /**
- // from file upload...
- $this->mLocalFile = wfLocalFile( $nt );
- $this->mDestName = $this->mLocalFile->getName();
- //....
- $status = $this->mLocalFile->upload( $this->mTempPath, $this->mComment, $pageText,
- File::DELETE_SOURCE, $this->mFileProps );
- if ( !$status->isGood() ) {
- $resultDetails = array( 'internal' => $status->getWikiText() );
- */
-
- // @todo Fixme: upload() uses $wgUser, which is wrong here
- // it may also create a page without our desire, also wrong potentially.
- // and, it will record a *current* upload, but we might want an archive version here
-
- $file = wfLocalFile( $this->getTitle() );
+ # Construct a file
+ $archiveName = $this->getArchiveName();
+ if ( $archiveName ) {
+ wfDebug( __METHOD__ . "Importing archived file as $archiveName\n" );
+ $file = OldLocalFile::newFromArchiveName( $this->getTitle(),
+ RepoGroup::singleton()->getLocalRepo(), $archiveName );
+ } else {
+ $file = wfLocalFile( $this->getTitle() );
+ wfDebug( __METHOD__ . 'Importing new file as ' . $file->getName() . "\n" );
+ if ( $file->exists() && $file->getTimestamp() > $this->getTimestamp() ) {
+ $archiveName = $file->getTimestamp() . '!' . $file->getName();
+ $file = OldLocalFile::newFromArchiveName( $this->getTitle(),
+ RepoGroup::singleton()->getLocalRepo(), $archiveName );
+ wfDebug( __METHOD__ . "File already exists; importing as $archiveName\n" );
+ }
+ }
if( !$file ) {
- wfDebug( "IMPORT: Bad file. :(\n" );
+ wfDebug( __METHOD__ . ': Bad file for ' . $this->getTitle() . "\n" );
return false;
}
-
- $source = $this->downloadSource();
+
+ # Get the file source or download if necessary
+ $source = $this->getFileSrc();
+ $flags = $this->isTempSrc() ? File::DELETE_SOURCE : 0;
+ if ( !$source ) {
+ $source = $this->downloadSource();
+ $flags |= File::DELETE_SOURCE;
+ }
if( !$source ) {
- wfDebug( "IMPORT: Could not fetch remote file. :(\n" );
+ wfDebug( __METHOD__ . ": Could not fetch remote file.\n" );
+ return false;
+ }
+ $sha1 = $this->getSha1();
+ if ( $sha1 && ( $sha1 !== sha1_file( $source ) ) ) {
+ if ( $flags & File::DELETE_SOURCE ) {
+ # Broken file; delete it if it is a temporary file
+ unlink( $source );
+ }
+ wfDebug( __METHOD__ . ": Corrupt file $source.\n" );
return false;
}
- $status = $file->upload( $source,
- $this->getComment(),
- $this->getComment(), // Initial page, if none present...
- File::DELETE_SOURCE,
- false, // props...
- $this->getTimestamp() );
-
- if( $status->isGood() ) {
- // yay?
- wfDebug( "IMPORT: is ok?\n" );
+ $user = User::newFromName( $this->user_text );
+
+ # Do the actual upload
+ if ( $archiveName ) {
+ $status = $file->uploadOld( $source, $archiveName,
+ $this->getTimestamp(), $this->getComment(), $user, $flags );
+ } else {
+ $status = $file->upload( $source, $this->getComment(), $this->getComment(),
+ $flags, false, $this->getTimestamp(), $user );
+ }
+
+ if ( $status->isGood() ) {
+ wfDebug( __METHOD__ . ": Succesful\n" );
return true;
+ } else {
+ wfDebug( __METHOD__ . ': failed: ' . $status->getXml() . "\n" );
+ return false;
}
-
- wfDebug( "IMPORT: is bad? " . $status->getXml() . "\n" );
- return false;
-
}
function downloadSource() {
@@ -1101,7 +1183,7 @@ class WikiRevision {
return false;
}
- // @todo Fixme!
+ // @todo FIXME!
$src = $this->getSrc();
$data = Http::get( $src );
if( !$data ) {
@@ -1161,7 +1243,9 @@ class ImportStreamSource {
}
static function newFromFile( $filename ) {
- $file = @fopen( $filename, 'rt' );
+ wfSuppressWarnings();
+ $file = fopen( $filename, 'rt' );
+ wfRestoreWarnings();
if( !$file ) {
return Status::newFatal( "importcantopen" );
}
@@ -1202,7 +1286,7 @@ class ImportStreamSource {
# quicker and sorts out user-agent problems which might
# otherwise prevent importing from large sites, such
# as the Wikimedia cluster, etc.
- $data = Http::request( $method, $url );
+ $data = Http::request( $method, $url, array( 'followRedirects' => true ) );
if( $data !== false ) {
$file = tmpfile();
fwrite( $file, $data );
diff --git a/includes/Init.php b/includes/Init.php
new file mode 100644
index 00000000..de867282
--- /dev/null
+++ b/includes/Init.php
@@ -0,0 +1,184 @@
+<?php
+
+/**
+ * Some functions that are useful during startup.
+ */
+class MWInit {
+ static $compilerVersion;
+
+ /**
+ * Get the version of HipHop used to compile, or false if MediaWiki was not
+ * compiled. This works by having our build script insert a special function
+ * into the compiled code.
+ */
+ static function getCompilerVersion() {
+ if ( self::$compilerVersion === null ) {
+ if ( self::functionExists( 'wfHipHopCompilerVersion' ) ) {
+ self::$compilerVersion = wfHipHopCompilerVersion();
+ } else {
+ self::$compilerVersion = false;
+ }
+ }
+ return self::$compilerVersion;
+ }
+
+ /**
+ * Returns true if we are running under HipHop, whether in compiled or
+ * interpreted mode.
+ *
+ * @return bool
+ */
+ static function isHipHop() {
+ return function_exists( 'hphp_thread_set_warmup_enabled' );
+ }
+
+ /**
+ * Get a fully-qualified path for a source file relative to $IP. Including
+ * such a path under HipHop will force the file to be interpreted. This is
+ * useful for configuration files.
+ *
+ * @param $file string
+ *
+ * @return string
+ */
+ static function interpretedPath( $file ) {
+ global $IP;
+ return "$IP/$file";
+ }
+
+ /**
+ * If we are running code compiled by HipHop, this will pass through the
+ * input path, assumed to be relative to $IP. If the code is interpreted,
+ * it will converted to a fully qualified path. It is necessary to use a
+ * path which is relative to $IP in order to make HipHop use its compiled
+ * code.
+ *
+ * @param $file string
+ *
+ * @return string
+ */
+ static function compiledPath( $file ) {
+ global $IP;
+
+ if ( defined( 'MW_COMPILED' ) ) {
+ return "phase3/$file";
+ } else {
+ return "$IP/$file";
+ }
+ }
+
+ /**
+ * The equivalent of MWInit::interpretedPath() but for files relative to the
+ * extensions directory.
+ *
+ * @param $file string
+ * @return string
+ */
+ static function extInterpretedPath( $file ) {
+ return self::getExtensionsDirectory() . '/' . $file;
+ }
+
+ /**
+ * The equivalent of MWInit::compiledPath() but for files relative to the
+ * extensions directory. Any files referenced in this way must be registered
+ * for compilation by including them in $wgCompiledFiles.
+ * @param $file string
+ * @return string
+ */
+ static function extCompiledPath( $file ) {
+ if ( defined( 'MW_COMPILED' ) ) {
+ return "extensions/$file";
+ } else {
+ return self::getExtensionsDirectory() . '/' . $file;
+ }
+ }
+
+ /**
+ * Register an extension setup file and return its path for compiled
+ * inclusion. Use this function in LocalSettings.php to add extensions
+ * to the build. For example:
+ *
+ * require( MWInit::extSetupPath( 'ParserFunctions/ParserFunctions.php' ) );
+ *
+ * @param $extRel string The path relative to the extensions directory, as defined by
+ * $wgExtensionsDirectory.
+ *
+ * @return string
+ */
+ static function extSetupPath( $extRel ) {
+ $baseRel = "extensions/$extRel";
+ if ( defined( 'MW_COMPILED' ) ) {
+ return $baseRel;
+ } else {
+ global $wgCompiledFiles;
+ $wgCompiledFiles[] = $baseRel;
+ return self::getExtensionsDirectory() . '/' . $extRel;
+ }
+ }
+
+ /**
+ * @return bool|string
+ */
+ static function getExtensionsDirectory() {
+ global $wgExtensionsDirectory, $IP;
+ if ( $wgExtensionsDirectory === false ) {
+ $wgExtensionsDirectory = "$IP/../extensions";
+ }
+ return $wgExtensionsDirectory;
+ }
+
+ /**
+ * Determine whether a class exists, using a method which works under HipHop.
+ *
+ * Note that it's not possible to implement this with any variant of
+ * class_exists(), because class_exists() returns false for classes which
+ * are compiled in.
+ *
+ * Calling class_exists() on a literal string causes the class to be made
+ * "volatile", which means (as of March 2011) that the class is broken and
+ * can't be used at all. So don't do that. See
+ * https://github.com/facebook/hiphop-php/issues/314
+ *
+ * @param $class string
+ *
+ * @return bool
+ */
+ static function classExists( $class ) {
+ try {
+ $r = new ReflectionClass( $class );
+ } catch( ReflectionException $r ) {
+ $r = false;
+ }
+ return $r !== false;
+ }
+
+ /**
+ * Determine whether a function exists, using a method which works under
+ * HipHop.
+ *
+ * @param $function string
+ *
+ * @return bool
+ */
+ static function functionExists( $function ) {
+ try {
+ $r = new ReflectionFunction( $function );
+ } catch( ReflectionException $r ) {
+ $r = false;
+ }
+ return $r !== false;
+ }
+
+ /**
+ * Call a static method of a class with variable arguments without causing
+ * it to become volatile.
+ * @param $className string
+ * @param $methodName string
+ * @param $args array
+ *
+ */
+ static function callStaticMethod( $className, $methodName, $args ) {
+ $r = new ReflectionMethod( $className, $methodName );
+ return $r->invokeArgs( null, $args );
+ }
+}
diff --git a/includes/Licenses.php b/includes/Licenses.php
index 45944c73..09fa8db3 100644
--- a/includes/Licenses.php
+++ b/includes/Licenses.php
@@ -28,6 +28,8 @@ class Licenses extends HTMLFormField {
/**
* Constructor
+ *
+ * @param $params array
*/
public function __construct( $params ) {
parent::__construct( $params );
@@ -38,7 +40,7 @@ class Licenses extends HTMLFormField {
$this->makeLicenses();
}
- /**#@+
+ /**
* @private
*/
protected function makeLicenses() {
@@ -46,9 +48,9 @@ class Licenses extends HTMLFormField {
$lines = explode( "\n", $this->msg );
foreach ( $lines as $line ) {
- if ( strpos( $line, '*' ) !== 0 )
+ if ( strpos( $line, '*' ) !== 0 ) {
continue;
- else {
+ } else {
list( $level, $line ) = $this->trimStars( $line );
if ( strpos( $line, '|' ) !== false ) {
@@ -60,7 +62,7 @@ class Licenses extends HTMLFormField {
}
if ( $level == count( $levels ) ) {
$levels[$level - 1] = $line;
- } else if ( $level > count( $levels ) ) {
+ } elseif ( $level > count( $levels ) ) {
$levels[] = $line;
}
}
@@ -68,19 +70,34 @@ class Licenses extends HTMLFormField {
}
}
+ /**
+ * @param $str
+ * @return array
+ */
protected function trimStars( $str ) {
$numStars = strspn( $str, '*' );
return array( $numStars, ltrim( substr( $str, $numStars ), ' ' ) );
}
+ /**
+ * @param $list
+ * @param $path
+ * @param $item
+ */
protected function stackItem( &$list, $path, $item ) {
$position =& $list;
- if ( $path )
- foreach( $path as $key )
+ if ( $path ) {
+ foreach( $path as $key ) {
$position =& $position[$key];
+ }
+ }
$position[] = $item;
}
+ /**
+ * @param $tagset
+ * @param $depth int
+ */
protected function makeHtml( $tagset, $depth = 0 ) {
foreach ( $tagset as $key => $val )
if ( is_array( $val ) ) {
@@ -102,6 +119,13 @@ class Licenses extends HTMLFormField {
}
}
+ /**
+ * @param $text
+ * @param $value
+ * @param $attribs null
+ * @param $depth int
+ * @return string
+ */
protected function outputOption( $text, $value, $attribs = null, $depth = 0 ) {
$attribs['value'] = $value;
if ( $value === $this->selected )
@@ -111,8 +135,8 @@ class Licenses extends HTMLFormField {
}
protected function msg( $str ) {
- $out = wfMsg( $str );
- return wfEmptyMsg( $str, $out ) ? $str : $out;
+ $msg = wfMessage( $str );
+ return $msg->exists() ? $msg->text() : $str;
}
/**#@-*/
@@ -122,11 +146,15 @@ class Licenses extends HTMLFormField {
*
* @return array
*/
- public function getLicenses() { return $this->licenses; }
+ public function getLicenses() {
+ return $this->licenses;
+ }
/**
* Accessor for $this->html
*
+ * @param $value bool
+ *
* @return string
*/
public function getInputHTML( $value ) {
@@ -140,8 +168,9 @@ class Licenses extends HTMLFormField {
'name' => $this->mName,
'id' => $this->mID
);
- if ( !empty( $this->mParams['disabled'] ) )
+ if ( !empty( $this->mParams['disabled'] ) ) {
$attibs['disabled'] = 'disabled';
+ }
return Html::rawElement( 'select', $attribs, $this->html );
}
diff --git a/includes/LinkFilter.php b/includes/LinkFilter.php
index 53841df1..af7680fb 100644
--- a/includes/LinkFilter.php
+++ b/includes/LinkFilter.php
@@ -42,42 +42,6 @@ class LinkFilter {
}
/**
- * Make a string to go after an SQL LIKE, which will match the specified
- * string. There are several kinds of filter entry:
- * *.domain.com - Produces http://com.domain.%, matches domain.com
- * and www.domain.com
- * domain.com - Produces http://com.domain./%, matches domain.com
- * or domain.com/ but not www.domain.com
- * *.domain.com/x - Produces http://com.domain.%/x%, matches
- * www.domain.com/xy
- * domain.com/x - Produces http://com.domain./x%, matches
- * domain.com/xy but not www.domain.com/xy
- *
- * Asterisks in any other location are considered invalid.
- *
- * @param $filterEntry String: domainparts
- * @param $prot String: protocol
- * @return String
- * @deprecated Use makeLikeArray() and pass result to Database::buildLike() instead
- */
- public static function makeLike( $filterEntry , $prot = 'http://' ) {
- wfDeprecated( __METHOD__ );
-
- $like = self::makeLikeArray( $filterEntry , $prot );
- if ( !$like ) {
- return false;
- }
- $dbw = wfGetDB( DB_MASTER );
- $s = $dbw->buildLike( $like );
- $m = false;
- if ( preg_match( "/^ *LIKE '(.*)' *$/", $s, $m ) ) {
- return $m[1];
- } else {
- throw new MWException( __METHOD__.': this DBMS is not supported by this function.' );
- }
- }
-
- /**
* Make an array to be used for calls to DatabaseBase::buildLike(), which
* will match the specified string. There are several kinds of filter entry:
* *.domain.com - Produces http://com.domain.%, matches domain.com
diff --git a/includes/Linker.php b/includes/Linker.php
index e2193f97..e1e554c3 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -1,10 +1,8 @@
<?php
/**
- * Split off some of the internal bits from Skin.php. These functions are used
+ * Some internal bits split of from Skin.php. These functions are used
* for primarily page content: links, embedded images, table of contents. Links
- * are also used in the skin. For the moment, Skin is a descendent class of
- * Linker. In the future, it should probably be further split so that every
- * other bit of the wiki doesn't have to go loading up Skin to get at it.
+ * are also used in the skin.
*
* @ingroup Skins
*/
@@ -15,17 +13,17 @@ class Linker {
*/
const TOOL_LINKS_NOBLOCK = 1;
- function __construct() {}
-
/**
* Get the appropriate HTML attributes to add to the "a" element of an ex-
* ternal link, as created by [wikisyntax].
*
* @param $class String: the contents of the class attribute; if an empty
* string is passed, which is the default value, defaults to 'external'.
+ * @deprecated since 1.18 Just pass the external class directly to something using Html::expandAttributes
*/
- function getExternalLinkAttributes( $class = 'external' ) {
- return $this->getLinkAttributesInternal( '', $class );
+ static function getExternalLinkAttributes( $class = 'external' ) {
+ wfDeprecated( __METHOD__ );
+ return self::getLinkAttributesInternal( '', $class );
}
/**
@@ -38,16 +36,16 @@ class Linker {
* @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' ) {
+ static function getInterwikiLinkAttributes( $title, $unused = null, $class = 'external' ) {
global $wgContLang;
- # FIXME: We have a whole bunch of handling here that doesn't happen in
+ # @todo FIXME: We have a whole bunch of handling here that doesn't happen in
# getExternalLinkAttributes, why?
$title = urldecode( $title );
$title = $wgContLang->checkTitleEncoding( $title );
$title = preg_replace( '/[\\x00-\\x1f]/', ' ', $title );
- return $this->getLinkAttributesInternal( $title, $class );
+ return self::getLinkAttributesInternal( $title, $class );
}
/**
@@ -59,33 +57,38 @@ class Linker {
* @param $unused String: unused
* @param $class String: the contents of the class attribute, default none
*/
- function getInternalLinkAttributes( $title, $unused = null, $class = '' ) {
+ static function getInternalLinkAttributes( $title, $unused = null, $class = '' ) {
$title = urldecode( $title );
$title = str_replace( '_', ' ', $title );
- return $this->getLinkAttributesInternal( $title, $class );
+ return self::getLinkAttributesInternal( $title, $class );
}
/**
* 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 $nt The Title object
+ * @param $nt Title
* @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 ) {
+ static function getInternalLinkAttributesObj( $nt, $unused = null, $class = '', $title = false ) {
if ( $title === false ) {
$title = $nt->getPrefixedText();
}
- return $this->getLinkAttributesInternal( $title, $class );
+ return self::getLinkAttributesInternal( $title, $class );
}
/**
* Common code for getLinkAttributesX functions
+ *
+ * @param $title string
+ * @param $class string
+ *
+ * @return string
*/
- private function getLinkAttributesInternal( $title, $class ) {
+ private static function getLinkAttributesInternal( $title, $class ) {
$title = htmlspecialchars( $title );
$class = htmlspecialchars( $class );
$r = '';
@@ -105,7 +108,7 @@ class Linker {
* @param $threshold Integer: user defined threshold
* @return String: CSS class
*/
- function getLinkColour( $t, $threshold ) {
+ static function getLinkColour( $t, $threshold ) {
$colour = '';
if ( $t->isRedirect() ) {
# Page is a redirect
@@ -145,7 +148,7 @@ class Linker {
* @param $query array The query string to append to the URL
* you're linking to, in key => value array form. Query keys and values
* will be URL-encoded.
- * @param $options mixed String or array of strings:
+ * @param $options string|array String or array of strings:
* 'known': Page is known to exist, so don't check if it does.
* 'broken': Page is known not to exist, so don't check if it does.
* 'noclasses': Don't add any classes automatically (includes "new",
@@ -156,23 +159,27 @@ class Linker {
* Has compatibility issues on some setups, so avoid wherever possible.
* @return string HTML <a> attribute
*/
- public function link( $target, $text = null, $customAttribs = array(), $query = array(), $options = array() ) {
+ public static function link(
+ $target, $html = null, $customAttribs = array(), $query = array(), $options = array()
+ ) {
wfProfileIn( __METHOD__ );
if ( !$target instanceof Title ) {
wfProfileOut( __METHOD__ );
- return "<!-- ERROR -->$text";
+ return "<!-- ERROR -->$html";
}
$options = (array)$options;
+ $dummy = new DummyLinker; // dummy linker instance for bc on the hooks
+
$ret = null;
- if ( !wfRunHooks( 'LinkBegin', array( $this, $target, &$text,
+ if ( !wfRunHooks( 'LinkBegin', array( $dummy, $target, &$html,
&$customAttribs, &$query, &$options, &$ret ) ) ) {
wfProfileOut( __METHOD__ );
return $ret;
}
# Normalize the Title if it's a special page
- $target = $this->normaliseSpecialPage( $target );
+ $target = self::normaliseSpecialPage( $target );
# If we don't know whether the page exists, let's find out.
wfProfileIn( __METHOD__ . '-checkPageExistence' );
@@ -192,22 +199,22 @@ class Linker {
}
# Note: we want the href attribute first, for prettiness.
- $attribs = array( 'href' => $this->linkUrl( $target, $query, $options ) );
+ $attribs = array( 'href' => self::linkUrl( $target, $query, $options ) );
if ( in_array( 'forcearticlepath', $options ) && $oldquery ) {
$attribs['href'] = wfAppendQuery( $attribs['href'], wfArrayToCgi( $oldquery ) );
}
$attribs = array_merge(
$attribs,
- $this->linkAttribs( $target, $customAttribs, $options )
+ self::linkAttribs( $target, $customAttribs, $options )
);
- if ( is_null( $text ) ) {
- $text = $this->linkText( $target );
+ if ( is_null( $html ) ) {
+ $html = self::linkText( $target );
}
$ret = null;
- if ( wfRunHooks( 'LinkEnd', array( $this, $target, $options, &$text, &$attribs, &$ret ) ) ) {
- $ret = Html::rawElement( 'a', $attribs, $text );
+ if ( wfRunHooks( 'LinkEnd', array( $dummy, $target, $options, &$html, &$attribs, &$ret ) ) ) {
+ $ret = Html::rawElement( 'a', $attribs, $html );
}
wfProfileOut( __METHOD__ );
@@ -217,18 +224,23 @@ class Linker {
/**
* Identical to link(), except $options defaults to 'known'.
*/
- public function linkKnown( $target, $text = null, $customAttribs = array(), $query = array(), $options = array( 'known', 'noclasses' ) ) {
- return $this->link( $target, $text, $customAttribs, $query, $options );
+ public static function linkKnown(
+ $target, $text = null, $customAttribs = array(),
+ $query = array(), $options = array( 'known', 'noclasses' ) )
+ {
+ return self::link( $target, $text, $customAttribs, $query, $options );
}
/**
* Returns the Url used to link to a Title
+ *
+ * @param $target Title
*/
- private function linkUrl( $target, $query, $options ) {
+ private static function linkUrl( $target, $query, $options ) {
wfProfileIn( __METHOD__ );
# We don't want to include fragments for broken links, because they
# generally make no sense.
- if ( in_array( 'broken', $options ) and $target->mFragment !== '' ) {
+ if ( in_array( 'broken', $options ) && $target->mFragment !== '' ) {
$target = clone $target;
$target->mFragment = '';
}
@@ -236,8 +248,8 @@ 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'] )
- and $target->getNamespace() != NS_SPECIAL ) {
+ if ( in_array( 'broken', $options ) && empty( $query['action'] )
+ && $target->getNamespace() != NS_SPECIAL ) {
$query['action'] = 'edit';
$query['redlink'] = '1';
}
@@ -248,8 +260,14 @@ class Linker {
/**
* Returns the array of attributes used when linking to the Title $target
+ *
+ * @param $target Title
+ * @param $attribs
+ * @param $options
+ *
+ * @return array
*/
- private function linkAttribs( $target, $attribs, $options ) {
+ private static function linkAttribs( $target, $attribs, $options ) {
wfProfileIn( __METHOD__ );
global $wgUser;
$defaults = array();
@@ -268,7 +286,7 @@ class Linker {
}
if ( !in_array( 'broken', $options ) ) { # Avoid useless calls to LinkCache (see r50387)
- $colour = $this->getLinkColour( $target, $wgUser->getStubThreshold() );
+ $colour = self::getLinkColour( $target, $wgUser->getStubThreshold() );
if ( $colour !== '' ) {
$classes[] = $colour; # mw-redirect or stub
}
@@ -306,8 +324,12 @@ class Linker {
/**
* Default text of the links to the Title $target
+ *
+ * @param $target Title
+ *
+ * @return string
*/
- private function linkText( $target ) {
+ private static function linkText( $target ) {
# We might be passed a non-Title by make*LinkObj(). Fail gracefully.
if ( !$target instanceof Title ) {
return '';
@@ -315,7 +337,7 @@ class Linker {
# 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() === '' && $target->getFragment() !== '' ) {
return htmlspecialchars( $target->getFragment() );
}
return htmlspecialchars( $target->getPrefixedText() );
@@ -332,34 +354,42 @@ class Linker {
* @param $trail String
* @param $prefix String
* @return string HTML of link
- * @deprecated
+ * @deprecated since 1.17
*/
- function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
+ static function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
global $wgUser;
wfDeprecated( __METHOD__ );
$threshold = $wgUser->getStubThreshold();
$colour = ( $size < $threshold ) ? 'stub' : '';
- // FIXME: replace deprecated makeColouredLinkObj by link()
- return $this->makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix );
+ // @todo FIXME: Replace deprecated makeColouredLinkObj by link()
+ return self::makeColouredLinkObj( $nt, $colour, $text, $query, $trail, $prefix );
}
/**
* Make appropriate markup for a link to the current article. This is currently rendered
- * as the bold link text. The calling sequence is the same as the other make*LinkObj functions,
+ * as the bold link text. The calling sequence is the same as the other make*LinkObj static functions,
* despite $query not being used.
+ *
+ * @param $nt Title
+ *
+ * @return string
*/
- function makeSelfLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
+ static function makeSelfLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
if ( $text == '' ) {
$text = htmlspecialchars( $nt->getPrefixedText() );
}
- list( $inside, $trail ) = Linker::splitTrail( $trail );
+ list( $inside, $trail ) = self::splitTrail( $trail );
return "<strong class=\"selflink\">{$prefix}{$text}{$inside}</strong>{$trail}";
}
- function normaliseSpecialPage( Title $title ) {
+ /**
+ * @param $title Title
+ * @return Title
+ */
+ static function normaliseSpecialPage( Title $title ) {
if ( $title->getNamespace() == NS_SPECIAL ) {
- list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() );
+ list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
if ( !$name ) {
return $title;
}
@@ -374,8 +404,12 @@ class Linker {
/**
* Returns the filename part of an url.
* Used as alternative text for external images.
+ *
+ * @param $url string
+ *
+ * @return string
*/
- function fnamePart( $url ) {
+ static function fnamePart( $url ) {
$basename = strrchr( $url, '/' );
if ( false === $basename ) {
$basename = $url;
@@ -388,10 +422,15 @@ class Linker {
/**
* Return the code for images which were added via external links,
* via Parser::maybeMakeExternalImage().
+ *
+ * @param $url
+ * @param $alt
+ *
+ * @return string
*/
- function makeExternalImage( $url, $alt = '' ) {
+ static function makeExternalImage( $url, $alt = '' ) {
if ( $alt == '' ) {
- $alt = $this->fnamePart( $url );
+ $alt = self::fnamePart( $url );
}
$img = '';
$success = wfRunHooks( 'LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
@@ -438,16 +477,19 @@ class Linker {
* @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 = "", $widthOption = null ) {
+ static function makeImageLink2( Title $title, $file, $frameParams = array(),
+ $handlerParams = array(), $time = false, $query = "", $widthOption = null )
+ {
$res = null;
- if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$this, &$title,
- &$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) {
+ $dummy = new DummyLinker;
+ if ( !wfRunHooks( 'ImageBeforeProduceHTML', array( &$dummy, &$title,
+ &$file, &$frameParams, &$handlerParams, &$time, &$res ) ) ) {
return $res;
}
if ( $file && !$file->allowInlineDisplay() ) {
wfDebug( __METHOD__ . ': ' . $title->getPrefixedDBkey() . " does not allow inline display\n" );
- return $this->link( $title );
+ return self::link( $title );
}
// Shortcuts
@@ -456,9 +498,15 @@ class Linker {
// Clean up parameters
$page = isset( $hp['page'] ) ? $hp['page'] : false;
- if ( !isset( $fp['align'] ) ) $fp['align'] = '';
- if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
- if ( !isset( $fp['title'] ) ) $fp['title'] = '';
+ if ( !isset( $fp['align'] ) ) {
+ $fp['align'] = '';
+ }
+ if ( !isset( $fp['alt'] ) ) {
+ $fp['alt'] = '';
+ }
+ if ( !isset( $fp['title'] ) ) {
+ $fp['title'] = '';
+ }
$prefix = $postfix = '';
@@ -468,7 +516,14 @@ class Linker {
$fp['align'] = 'none';
}
if ( $file && !isset( $hp['width'] ) ) {
- $hp['width'] = $file->getWidth( $page );
+ if ( isset( $hp['height'] ) && $file->isVectorized() ) {
+ // If its a vector image, and user only specifies height
+ // we don't want it to be limited by its "normal" width.
+ global $wgSVGMaxSize;
+ $hp['width'] = $wgSVGMaxSize;
+ } else {
+ $hp['width'] = $file->getWidth( $page );
+ }
if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
global $wgThumbLimits, $wgThumbUpright;
@@ -506,7 +561,7 @@ class Linker {
if ( $fp['align'] == '' ) {
$fp['align'] = $wgContLang->alignEnd();
}
- return $prefix . $this->makeThumbLink2( $title, $file, $fp, $hp, $time, $query ) . $postfix;
+ return $prefix . self::makeThumbLink2( $title, $file, $fp, $hp, $time, $query ) . $postfix;
}
if ( $file && isset( $fp['frameless'] ) ) {
@@ -526,14 +581,14 @@ class Linker {
}
if ( !$thumb ) {
- $s = $this->makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
+ $s = self::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 );
- $params = $this->getImageLinkMTOParams( $fp, $query ) + $params;
+ $params = self::getImageLinkMTOParams( $fp, $query ) + $params;
$s = $thumb->toHtml( $params );
}
@@ -549,7 +604,7 @@ class Linker {
* @param $frameParams The frame parameters
* @param $query An optional query string to add to description page links
*/
- function getImageLinkMTOParams( $frameParams, $query = '' ) {
+ static function getImageLinkMTOParams( $frameParams, $query = '' ) {
$mtoParams = array();
if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
$mtoParams['custom-url-link'] = $frameParams['link-url'];
@@ -557,7 +612,7 @@ class Linker {
$mtoParams['custom-target-link'] = $frameParams['link-target'];
}
} elseif ( isset( $frameParams['link-title'] ) && $frameParams['link-title'] !== '' ) {
- $mtoParams['custom-title-link'] = $this->normaliseSpecialPage( $frameParams['link-title'] );
+ $mtoParams['custom-title-link'] = self::normaliseSpecialPage( $frameParams['link-title'] );
} elseif ( !empty( $frameParams['no-link'] ) ) {
// No link
} else {
@@ -578,19 +633,36 @@ class Linker {
* @param $framed Boolean
* @param $manualthumb String
*/
- function makeThumbLinkObj( Title $title, $file, $label = '', $alt, $align = 'right', $params = array(), $framed = false , $manualthumb = "" ) {
+ static function makeThumbLinkObj( Title $title, $file, $label = '', $alt,
+ $align = 'right', $params = array(), $framed = false , $manualthumb = "" )
+ {
$frameParams = array(
'alt' => $alt,
'caption' => $label,
'align' => $align
);
- if ( $framed ) $frameParams['framed'] = true;
- if ( $manualthumb ) $frameParams['manualthumb'] = $manualthumb;
- return $this->makeThumbLink2( $title, $file, $frameParams, $params );
+ if ( $framed ) {
+ $frameParams['framed'] = true;
+ }
+ if ( $manualthumb ) {
+ $frameParams['manualthumb'] = $manualthumb;
+ }
+ return self::makeThumbLink2( $title, $file, $frameParams, $params );
}
- function makeThumbLink2( Title $title, $file, $frameParams = array(), $handlerParams = array(), $time = false, $query = "" ) {
- global $wgStylePath;
+ /**
+ * @param $title Title
+ * @param $file File
+ * @param array $frameParams
+ * @param array $handlerParams
+ * @param bool $time
+ * @param string $query
+ * @return mixed
+ */
+ static function makeThumbLink2( Title $title, $file, $frameParams = array(),
+ $handlerParams = array(), $time = false, $query = "" )
+ {
+ global $wgStylePath, $wgContLang;
$exists = $file && $file->exists();
# Shortcuts
@@ -653,7 +725,7 @@ class Linker {
$s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
if ( !$exists ) {
- $s .= $this->makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
+ $s .= self::makeBrokenImageLinkObj( $title, $fp['title'], '', '', '', $time == true );
$zoomIcon = '';
} elseif ( !$thumb ) {
$s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
@@ -663,16 +735,21 @@ class Linker {
'alt' => $fp['alt'],
'title' => $fp['title'],
'img-class' => 'thumbimage' );
- $params = $this->getImageLinkMTOParams( $fp, $query ) + $params;
+ $params = self::getImageLinkMTOParams( $fp, $query ) + $params;
$s .= $thumb->toHtml( $params );
if ( isset( $fp['framed'] ) ) {
$zoomIcon = "";
} else {
- $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>';
+ $zoomIcon = Html::rawElement( 'div', array( 'class' => 'magnify' ),
+ Html::rawElement( 'a', array(
+ 'href' => $url,
+ 'class' => 'internal',
+ 'title' => wfMsg( 'thumbnail-more' ) ),
+ Html::element( 'img', array(
+ 'src' => $wgStylePath . '/common/images/magnify-clip' . ( $wgContLang->isRTL() ? '-rtl' : '' ) . '.png',
+ 'width' => 15,
+ 'height' => 11,
+ 'alt' => "" ) ) ) );
}
}
$s .= ' <div class="thumbcaption">' . $zoomIcon . $fp['caption'] . "</div></div></div>";
@@ -683,44 +760,42 @@ class Linker {
* Make a "broken" link to an image
*
* @param $title Title object
- * @param $text String: link label
+ * @param $text String: link label in unescaped text form
* @param $query String: query string
- * @param $trail String: link trail
- * @param $prefix String: link prefix
+ * @param $trail String: link trail (HTML fragment)
+ * @param $prefix String: link prefix (HTML fragment)
* @param $time Boolean: a file of a certain timestamp was requested
* @return String
*/
- public function makeBrokenImageLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '', $time = false ) {
+ public static function makeBrokenImageLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '', $time = false ) {
global $wgEnableUploads, $wgUploadMissingFileUrl;
- if ( $title instanceof Title ) {
- wfProfileIn( __METHOD__ );
- $currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
-
- list( $inside, $trail ) = self::splitTrail( $trail );
- if ( $text == '' )
- $text = htmlspecialchars( $title->getPrefixedText() );
-
- if ( ( $wgUploadMissingFileUrl || $wgEnableUploads ) && !$currentExists ) {
- $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
+ if ( ! $title instanceof Title ) {
+ return "<!-- ERROR -->{$prefix}{$text}{$trail}";
+ }
+ wfProfileIn( __METHOD__ );
+ $currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
- if ( $redir ) {
- wfProfileOut( __METHOD__ );
- return $this->linkKnown( $title, "$prefix$text$inside", array(), $query ) . $trail;
- }
+ list( $inside, $trail ) = self::splitTrail( $trail );
+ if ( $text == '' )
+ $text = htmlspecialchars( $title->getPrefixedText() );
- $href = $this->getUploadUrl( $title, $query );
+ if ( ( $wgUploadMissingFileUrl || $wgEnableUploads ) && !$currentExists ) {
+ $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
+ if ( $redir ) {
wfProfileOut( __METHOD__ );
- 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->linkKnown( $title, "$prefix$text$inside", array(), $query ) . $trail;
+ return self::linkKnown( $title, "$prefix$text$inside", array(), $query ) . $trail;
}
+
+ $href = self::getUploadUrl( $title, $query );
+
+ wfProfileOut( __METHOD__ );
+ return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
+ htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
+ "$prefix$text$inside</a>$trail";
} else {
wfProfileOut( __METHOD__ );
- return "<!-- ERROR -->{$prefix}{$text}{$trail}";
+ return self::linkKnown( $title, "$prefix$text$inside", array(), $query ) . $trail;
}
}
@@ -731,7 +806,7 @@ class Linker {
* @param $query String: urlencoded query string to prepend
* @return String: urlencoded URL
*/
- protected function getUploadUrl( $destFile, $query = '' ) {
+ protected static function getUploadUrl( $destFile, $query = '' ) {
global $wgUploadMissingFileUrl;
$q = 'wpDestFile=' . $destFile->getPartialUrl();
if ( $query != '' )
@@ -750,42 +825,54 @@ class Linker {
*
* @param $title Title object.
* @param $text String: pre-sanitized HTML
- * @param $time string: time image was created
+ * @param $time string: MW timestamp of file creation time
+ * @return String: HTML
+ */
+ public static function makeMediaLinkObj( $title, $text = '', $time = false ) {
+ $img = wfFindFile( $title, array( 'time' => $time ) );
+ return self::makeMediaLinkFile( $title, $img, $text );
+ }
+
+ /**
+ * Create a direct link to a given uploaded file.
+ * This will make a broken link if $file is false.
+ *
+ * @param $title Title object.
+ * @param $file File|false mixed File object or false
+ * @param $text String: pre-sanitized HTML
* @return String: HTML
*
* @todo Handle invalid or missing images better.
*/
- public function makeMediaLinkObj( $title, $text = '', $time = false ) {
- if ( is_null( $title ) ) {
- # # # HOTFIX. Instead of breaking, return empty string.
- return $text;
+ public static function makeMediaLinkFile( Title $title, $file, $text = '' ) {
+ if ( $file && $file->exists() ) {
+ $url = $file->getURL();
+ $class = 'internal';
} else {
- $img = wfFindFile( $title, array( 'time' => $time ) );
- if ( $img ) {
- $url = $img->getURL();
- $class = 'internal';
- } else {
- $url = $this->getUploadUrl( $title );
- $class = 'new';
- }
- $alt = htmlspecialchars( $title->getText(), ENT_QUOTES );
- if ( $text == '' ) {
- $text = $alt;
- }
- $u = htmlspecialchars( $url );
- return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$text}</a>";
+ $url = self::getUploadUrl( $title );
+ $class = 'new';
+ }
+ $alt = htmlspecialchars( $title->getText(), ENT_QUOTES );
+ if ( $text == '' ) {
+ $text = $alt;
}
+ $u = htmlspecialchars( $url );
+ return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$text}</a>";
}
/**
- * Make a link to a special page given its name and, optionally,
+ * Make a link to a special page given its name and, optionally,
* a message key from the link text.
* Usage example: $skin->specialLink( 'recentchanges' )
+ *
+ * @return bool
*/
- function specialLink( $name, $key = '' ) {
- if ( $key == '' ) { $key = strtolower( $name ); }
+ static function specialLink( $name, $key = '' ) {
+ if ( $key == '' ) {
+ $key = strtolower( $name );
+ }
- return $this->linkKnown( SpecialPage::getTitleFor( $name ) , wfMsg( $key ) );
+ return self::linkKnown( SpecialPage::getTitleFor( $name ) , wfMsg( $key ) );
}
/**
@@ -795,38 +882,29 @@ class Linker {
* @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
- * into a string). Then, if we've got additional params in $attribs, we
- * add to it. People using this might want to change the classes (or other
- * default link attributes), but passing $attribsText is just messy. Would
- * make a lot more sense to make put the classes into $attribs, let the
- * hook play with them, *then* expand it all at once.
- */
- function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) {
- if ( isset( $attribs['class'] ) ) {
- # yet another hack :(
- $class = $attribs['class'];
- } else {
- $class = "external $linktype";
+ */
+ static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) {
+ $class = "external";
+ if ( isset($linktype) && $linktype ) {
+ $class .= " $linktype";
}
+ if ( isset($attribs['class']) && $attribs['class'] ) {
+ $class .= " {$attribs['class']}";
+ }
+ $attribs['class'] = $class;
- $attribsText = $this->getExternalLinkAttributes( $class );
- $url = htmlspecialchars( $url );
if ( $escape ) {
$text = htmlspecialchars( $text );
}
$link = '';
- $success = wfRunHooks( 'LinkerMakeExternalLink', array( &$url, &$text, &$link, &$attribs, $linktype ) );
+ $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>';
+ $attribs['href'] = $url;
+ return Html::rawElement( 'a', $attribs, $text );
}
/**
@@ -836,13 +914,13 @@ class Linker {
* @return String: HTML fragment
* @private
*/
- function userLink( $userId, $userText ) {
+ static function userLink( $userId, $userText ) {
if ( $userId == 0 ) {
$page = SpecialPage::getTitleFor( 'Contributions', $userText );
} else {
$page = Title::makeTitle( NS_USER, $userText );
}
- return $this->link( $page, htmlspecialchars( $userText ), array( 'class' => 'mw-userlink' ) );
+ return self::link( $page, htmlspecialchars( $userText ), array( 'class' => 'mw-userlink' ) );
}
/**
@@ -852,18 +930,20 @@ class Linker {
* @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 $flags Integer: customisation flags (e.g. Linker::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;
+ public static function userToolLinks(
+ $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits = null
+ ) {
+ global $wgUser, $wgDisableAnonTalk, $wgLang;
$talkable = !( $wgDisableAnonTalk && 0 == $userId );
- $blockable = ( $wgSysopUserBans || 0 == $userId ) && !$flags & self::TOOL_LINKS_NOBLOCK;
+ $blockable = !$flags & self::TOOL_LINKS_NOBLOCK;
$items = array();
if ( $talkable ) {
- $items[] = $this->userTalkLink( $userId, $userText );
+ $items[] = self::userTalkLink( $userId, $userText );
}
if ( $userId ) {
// check if the user has an edit
@@ -876,10 +956,10 @@ class Linker {
}
$contribsPage = SpecialPage::getTitleFor( 'Contributions', $userText );
- $items[] = $this->link( $contribsPage, wfMsgHtml( 'contribslink' ), $attribs );
+ $items[] = self::link( $contribsPage, wfMsgHtml( 'contribslink' ), $attribs );
}
if ( $blockable && $wgUser->isAllowed( 'block' ) ) {
- $items[] = $this->blockLink( $userId, $userText );
+ $items[] = self::blockLink( $userId, $userText );
}
if ( $items ) {
@@ -895,8 +975,8 @@ class Linker {
* @param $userText String: user name or IP address
* @param $edits Integer: user edit count (optional, for performance)
*/
- public function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
- return $this->userToolLinks( $userId, $userText, true, 0, $edits );
+ public static function userToolLinksRedContribs( $userId, $userText, $edits = null ) {
+ return self::userToolLinks( $userId, $userText, true, 0, $edits );
}
@@ -906,9 +986,9 @@ class Linker {
* @return String: HTML fragment with user talk link
* @private
*/
- function userTalkLink( $userId, $userText ) {
+ static function userTalkLink( $userId, $userText ) {
$userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
- $userTalkLink = $this->link( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) );
+ $userTalkLink = self::link( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) );
return $userTalkLink;
}
@@ -918,9 +998,9 @@ class Linker {
* @return String: HTML fragment with block link
* @private
*/
- function blockLink( $userId, $userText ) {
- $blockPage = SpecialPage::getTitleFor( 'Blockip', $userText );
- $blockLink = $this->link( $blockPage, wfMsgHtml( 'blocklink' ) );
+ static function blockLink( $userId, $userText ) {
+ $blockPage = SpecialPage::getTitleFor( 'Block', $userText );
+ $blockLink = self::link( $blockPage, wfMsgHtml( 'blocklink' ) );
return $blockLink;
}
@@ -930,11 +1010,11 @@ class Linker {
* @param $isPublic Boolean: show only if all users can see it
* @return String: HTML fragment
*/
- function revUserLink( $rev, $isPublic = false ) {
+ static function revUserLink( $rev, $isPublic = false ) {
if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
$link = wfMsgHtml( 'rev-deleted-user' );
- } else if ( $rev->userCan( Revision::DELETED_USER ) ) {
- $link = $this->userLink( $rev->getUser( Revision::FOR_THIS_USER ),
+ } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
+ $link = self::userLink( $rev->getUser( Revision::FOR_THIS_USER ),
$rev->getUserText( Revision::FOR_THIS_USER ) );
} else {
$link = wfMsgHtml( 'rev-deleted-user' );
@@ -951,14 +1031,14 @@ class Linker {
* @param $isPublic Boolean: show only if all users can see it
* @return string HTML
*/
- function revUserTools( $rev, $isPublic = false ) {
+ static function revUserTools( $rev, $isPublic = false ) {
if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
$link = wfMsgHtml( 'rev-deleted-user' );
- } else if ( $rev->userCan( Revision::DELETED_USER ) ) {
+ } elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
$userId = $rev->getUser( Revision::FOR_THIS_USER );
$userText = $rev->getUserText( Revision::FOR_THIS_USER );
- $link = $this->userLink( $userId, $userText ) .
- ' ' . $this->userToolLinks( $userId, $userText );
+ $link = self::userLink( $userId, $userText ) .
+ ' ' . self::userToolLinks( $userId, $userText );
} else {
$link = wfMsgHtml( 'rev-deleted-user' );
}
@@ -984,7 +1064,7 @@ class Linker {
* @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 ) {
+ static function formatComment( $comment, $title = null, $local = false ) {
wfProfileIn( __METHOD__ );
# Sanitize text a bit:
@@ -993,14 +1073,20 @@ class Linker {
$comment = Sanitizer::escapeHtmlAllowEntities( $comment );
# Render autocomments and make links:
- $comment = $this->formatAutocomments( $comment, $title, $local );
- $comment = $this->formatLinksInComment( $comment, $title, $local );
+ $comment = self::formatAutocomments( $comment, $title, $local );
+ $comment = self::formatLinksInComment( $comment, $title, $local );
wfProfileOut( __METHOD__ );
return $comment;
}
/**
+ * @var Title
+ */
+ static $autocommentTitle;
+ static $autocommentLocal;
+
+ /**
* The pattern for autogen comments is / * foo * /, which makes for
* some nasty regex.
* We look for all comments, match any text before and after the comment,
@@ -1012,22 +1098,26 @@ class Linker {
* @param $local Boolean: whether section links should refer to local page
* @return String: formatted comment
*/
- private function formatAutocomments( $comment, $title = null, $local = false ) {
+ private static function formatAutocomments( $comment, $title = null, $local = false ) {
// Bah!
- $this->autocommentTitle = $title;
- $this->autocommentLocal = $local;
+ self::$autocommentTitle = $title;
+ self::$autocommentLocal = $local;
$comment = preg_replace_callback(
'!(.*)/\*\s*(.*?)\s*\*/(.*)!',
- array( $this, 'formatAutocommentsCallback' ),
+ array( 'Linker', 'formatAutocommentsCallback' ),
$comment );
- unset( $this->autocommentTitle );
- unset( $this->autocommentLocal );
+ self::$autocommentTitle = null;
+ self::$autocommentLocal = null;
return $comment;
}
- private function formatAutocommentsCallback( $match ) {
- $title = $this->autocommentTitle;
- $local = $this->autocommentLocal;
+ /**
+ * @param $match
+ * @return string
+ */
+ private static function formatAutocommentsCallback( $match ) {
+ $title = self::$autocommentTitle;
+ $local = self::$autocommentLocal;
$pre = $match[1];
$auto = $match[2];
@@ -1050,7 +1140,7 @@ class Linker {
$title->getDBkey(), $section );
}
if ( $sectionTitle ) {
- $link = $this->link( $sectionTitle,
+ $link = self::link( $sectionTitle,
htmlspecialchars( wfMsgForContent( 'sectionlink' ) ), array(), array(),
'noclasses' );
} else {
@@ -1072,28 +1162,38 @@ class Linker {
}
/**
+ * @var Title
+ */
+ static $commentContextTitle;
+ static $commentLocal;
+
+ /**
* Formats wiki links and media links in text; all other wiki formatting
* is ignored
*
- * @todo Fixme: doesn't handle sub-links as in image thumb texts like the main parser
+ * @todo FIXME: Doesn't handle sub-links as in image thumb texts like the main parser
* @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;
- $this->commentLocal = $local;
+ public static function formatLinksInComment( $comment, $title = null, $local = false ) {
+ self::$commentContextTitle = $title;
+ self::$commentLocal = $local;
$html = preg_replace_callback(
'/\[\[:?(.*?)(\|(.*?))*\]\]([^[]*)/',
- array( $this, 'formatLinksInCommentCallback' ),
+ array( 'Linker', 'formatLinksInCommentCallback' ),
$comment );
- unset( $this->commentContextTitle );
- unset( $this->commentLocal );
+ self::$commentContextTitle = null;
+ self::$commentLocal = null;
return $html;
}
- protected function formatLinksInCommentCallback( $match ) {
+ /**
+ * @param $match
+ * @return mixed
+ */
+ protected static function formatLinksInCommentCallback( $match ) {
global $wgContLang;
$medians = '(?:' . preg_quote( MWNamespace::getCanonicalName( NS_MEDIA ), '/' ) . '|';
@@ -1103,7 +1203,7 @@ class Linker {
# 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] ) );
+ $match[1] = str_replace( array( '<', '>' ), array( '&lt;', '&gt;' ), rawurldecode( $match[1] ) );
}
# Handle link renaming [[foo|text]] will show link as "text"
@@ -1118,7 +1218,9 @@ class Linker {
# Media link; trail not supported.
$linkRegexp = '/\[\[(.*?)\]\]/';
$title = Title::makeTitleSafe( NS_FILE, $submatch[1] );
- $thelink = $this->makeMediaLinkObj( $title, $text );
+ if ( $title ) {
+ $thelink = self::makeMediaLinkObj( $title, $text );
+ }
} else {
# Other kind of link
if ( preg_match( $wgContLang->linkTrail(), $match[4], $submatch ) ) {
@@ -1129,22 +1231,22 @@ class Linker {
$linkRegexp = '/\[\[(.*?)\]\]' . preg_quote( $trail, '/' ) . '/';
if ( isset( $match[1][0] ) && $match[1][0] == ':' )
$match[1] = substr( $match[1], 1 );
- list( $inside, $trail ) = Linker::splitTrail( $trail );
+ list( $inside, $trail ) = self::splitTrail( $trail );
$linkText = $text;
- $linkTarget = Linker::normalizeSubpageLink( $this->commentContextTitle,
+ $linkTarget = self::normalizeSubpageLink( self::$commentContextTitle,
$match[1], $linkText );
$target = Title::newFromText( $linkTarget );
if ( $target ) {
if ( $target->getText() == '' && $target->getInterwiki() === ''
- && !$this->commentLocal && $this->commentContextTitle )
+ && !self::$commentLocal && self::$commentContextTitle )
{
- $newTarget = clone ( $this->commentContextTitle );
+ $newTarget = clone ( self::$commentContextTitle );
$newTarget->setFragment( '#' . $target->getFragment() );
$target = $newTarget;
}
- $thelink = $this->link(
+ $thelink = self::link(
$target,
$linkText . $inside
) . $trail;
@@ -1158,6 +1260,12 @@ class Linker {
return $comment;
}
+ /**
+ * @param $contextTitle Title
+ * @param $target
+ * @param $text
+ * @return string
+ */
static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
# Valid link forms:
# Foobar -- normal
@@ -1240,14 +1348,14 @@ class Linker {
*
* @return string
*/
- function commentBlock( $comment, $title = null, $local = false ) {
+ static function commentBlock( $comment, $title = null, $local = false ) {
// '*' 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 == '*' ) {
return '';
} else {
- $formatted = $this->formatComment( $comment, $title, $local );
+ $formatted = self::formatComment( $comment, $title, $local );
return " <span class=\"comment\">($formatted)</span>";
}
}
@@ -1261,12 +1369,14 @@ class Linker {
* @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 "";
+ static function revComment( Revision $rev, $local = false, $isPublic = false ) {
+ 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 ) ) {
- $block = $this->commentBlock( $rev->getComment( Revision::FOR_THIS_USER ),
+ } elseif ( $rev->userCan( Revision::DELETED_COMMENT ) ) {
+ $block = self::commentBlock( $rev->getComment( Revision::FOR_THIS_USER ),
$rev->getTitle(), $local );
} else {
$block = " <span class=\"comment\">" . wfMsgHtml( 'rev-deleted-comment' ) . "</span>";
@@ -1277,7 +1387,11 @@ class Linker {
return $block;
}
- public function formatRevisionSize( $size ) {
+ /**
+ * @param $size
+ * @return string
+ */
+ public static function formatRevisionSize( $size ) {
if ( $size == 0 ) {
$stxt = wfMsgExt( 'historyempty', 'parsemag' );
} else {
@@ -1291,25 +1405,32 @@ class Linker {
/**
* Add another level to the Table of Contents
+ *
+ * @return string
*/
- function tocIndent() {
+ static function tocIndent() {
return "\n<ul>";
}
/**
* Finish one or more sublevels on the Table of Contents
+ *
+ * @return string
*/
- function tocUnindent( $level ) {
+ static function tocUnindent( $level ) {
return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
}
/**
* parameter level defines if we are on an indentation level
+ *
+ * @return string
*/
- function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
+ static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
$classes = "toclevel-$level";
- if ( $sectionIndex !== false )
+ if ( $sectionIndex !== false ) {
$classes .= " tocsection-$sectionIndex";
+ }
return "\n<li class=\"$classes\"><a href=\"#" .
$anchor . '"><span class="tocnumber">' .
$tocnumber . '</span> <span class="toctext">' .
@@ -1321,7 +1442,7 @@ class Linker {
* tocUnindent() will be used instead if we're ending a line below
* the new level.
*/
- function tocLineEnd() {
+ static function tocLineEnd() {
return "</li>\n";
}
@@ -1332,7 +1453,7 @@ class Linker {
* @param $lang mixed: Language code for the toc title
* @return String: full html of the TOC
*/
- function tocList( $toc, $lang = false ) {
+ static function tocList( $toc, $lang = false ) {
$title = wfMsgExt( 'toc', array( 'language' => $lang, 'escape' ) );
return
'<table id="toc" class="toc"><tr><td>'
@@ -1348,80 +1469,25 @@ class Linker {
* @param $tree Return value of ParserOutput::getSections()
* @return String: HTML fragment
*/
- public function generateTOC( $tree ) {
+ public static function generateTOC( $tree ) {
$toc = '';
$lastLevel = 0;
foreach ( $tree as $section ) {
if ( $section['toclevel'] > $lastLevel )
- $toc .= $this->tocIndent();
- else if ( $section['toclevel'] < $lastLevel )
- $toc .= $this->tocUnindent(
+ $toc .= self::tocIndent();
+ elseif ( $section['toclevel'] < $lastLevel )
+ $toc .= self::tocUnindent(
$lastLevel - $section['toclevel'] );
else
- $toc .= $this->tocLineEnd();
+ $toc .= self::tocLineEnd();
- $toc .= $this->tocLine( $section['anchor'],
+ $toc .= self::tocLine( $section['anchor'],
$section['line'], $section['number'],
$section['toclevel'], $section['index'] );
$lastLevel = $section['toclevel'];
}
- $toc .= $this->tocLineEnd();
- return $this->tocList( $toc );
- }
-
- /**
- * Create a section edit link. This supersedes editSectionLink() and
- * editSectionLinkForOther().
- *
- * @param $nt Title The title being linked to (may not be the same as
- * $wgTitle, if the section is included from a template)
- * @param $section string The designation of the section being pointed to,
- * 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, $lang = false ) {
- // HTML generated here should probably have userlangattributes
- // added to it for LTR text on RTL pages
- $attribs = array();
- 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, wfMsgExt( 'editsection', array( 'language' => $lang ) ),
- $attribs,
- array( 'action' => 'edit', 'section' => $section ),
- array( 'noclasses', 'known' )
- );
-
- # Run the old hook. This takes up half of the function . . . hopefully
- # we can rid of it someday.
- $attribs = '';
- if ( $tooltip ) {
- $attribs = htmlspecialchars( wfMsgReal( 'editsectionhint', array( $tooltip ), true, $lang ) );
- $attribs = " title=\"$attribs\"";
- }
- $result = null;
- 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 = 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 = wfMsgExt( 'editsection-brackets', array( 'escape', 'replaceafter', 'language' => $lang ), $link );
- $result = "<span class=\"editsection\">$result</span>";
-
- wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result, $lang ) );
- return $result;
+ $toc .= self::tocLineEnd();
+ return self::tocList( $toc );
}
/**
@@ -1439,7 +1505,7 @@ class Linker {
*
* @return String: HTML headline
*/
- public function makeHeadline( $level, $attribs, $anchor, $text, $link, $legacyAnchor = false ) {
+ public static function makeHeadline( $level, $attribs, $anchor, $text, $link, $legacyAnchor = false ) {
$ret = "<h$level$attribs"
. $link
. " <span class=\"mw-headline\" id=\"$anchor\">$text</span>"
@@ -1455,11 +1521,8 @@ class Linker {
* as a two-element array
*/
static function splitTrail( $trail ) {
- static $regex = false;
- if ( $regex === false ) {
- global $wgContLang;
- $regex = $wgContLang->linkTrail();
- }
+ global $wgContLang;
+ $regex = $wgContLang->linkTrail();
$inside = '';
if ( $trail !== '' ) {
$m = array();
@@ -1484,9 +1547,9 @@ class Linker {
*
* @param $rev Revision object
*/
- function generateRollback( $rev ) {
+ static function generateRollback( $rev ) {
return '<span class="mw-rollback-link">['
- . $this->buildRollbackLink( $rev )
+ . self::buildRollbackLink( $rev )
. ']</span>';
}
@@ -1496,22 +1559,25 @@ class Linker {
* @param $rev Revision object
* @return String: HTML fragment
*/
- public function buildRollbackLink( $rev ) {
+ public static function buildRollbackLink( $rev ) {
global $wgRequest, $wgUser;
$title = $rev->getTitle();
$query = array(
'action' => 'rollback',
- 'from' => $rev->getUserText()
+ 'from' => $rev->getUserText(),
+ 'token' => $wgUser->editToken( array( $title->getPrefixedText(), $rev->getUserText() ) ),
);
if ( $wgRequest->getBool( 'bot' ) ) {
$query['bot'] = '1';
$query['hidediff'] = '1'; // bug 15999
}
- $query['token'] = $wgUser->editToken( array( $title->getPrefixedText(),
- $rev->getUserText() ) );
- return $this->link( $title, wfMsgHtml( 'rollbacklink' ),
+ return self::link(
+ $title,
+ wfMsgHtml( 'rollbacklink' ),
array( 'title' => wfMsg( 'tooltip-rollback' ) ),
- $query, array( 'known', 'noclasses' ) );
+ $query,
+ array( 'known', 'noclasses' )
+ );
}
/**
@@ -1523,7 +1589,7 @@ class Linker {
* @param $section Boolean: whether this is for a section edit
* @return String: HTML output
*/
- public function formatTemplates( $templates, $preview = false, $section = false ) {
+ public static function formatTemplates( $templates, $preview = false, $section = false ) {
wfProfileIn( __METHOD__ );
$outText = '';
@@ -1557,21 +1623,21 @@ class Linker {
$protected = '';
}
if ( $titleObj->quickUserCan( 'edit' ) ) {
- $editLink = $this->link(
+ $editLink = self::link(
$titleObj,
wfMsg( 'editlink' ),
array(),
array( 'action' => 'edit' )
);
} else {
- $editLink = $this->link(
+ $editLink = self::link(
$titleObj,
wfMsg( 'viewsourcelink' ),
array(),
array( 'action' => 'edit' )
);
}
- $outText .= '<li>' . $this->link( $titleObj ) . ' (' . $editLink . ') ' . $protected . '</li>';
+ $outText .= '<li>' . self::link( $titleObj ) . ' (' . $editLink . ') ' . $protected . '</li>';
}
$outText .= '</ul>';
}
@@ -1586,7 +1652,7 @@ class Linker {
* or similar
* @return String: HTML output
*/
- public function formatHiddenCategories( $hiddencats ) {
+ public static function formatHiddenCategories( $hiddencats ) {
global $wgLang;
wfProfileIn( __METHOD__ );
@@ -1598,7 +1664,7 @@ class Linker {
$outText .= "</div><ul>\n";
foreach ( $hiddencats as $titleObj ) {
- $outText .= '<li>' . $this->link( $titleObj, null, array(), array(), 'known' ) . "</li>\n"; # If it's hidden, it must exist - no need to check with a LinkBatch
+ $outText .= '<li>' . self::link( $titleObj, null, array(), array(), 'known' ) . "</li>\n"; # If it's hidden, it must exist - no need to check with a LinkBatch
}
$outText .= '</ul>';
}
@@ -1613,7 +1679,7 @@ class Linker {
* @param $size Size to format
* @return String
*/
- public function formatSize( $size ) {
+ public static function formatSize( $size ) {
global $wgLang;
return htmlspecialchars( $wgLang->formatSize( $size ) );
}
@@ -1630,13 +1696,19 @@ class Linker {
* @return String: contents of the title attribute (which you must HTML-
* escape), or false for no title attribute
*/
- public function titleAttrib( $name, $options = null ) {
+ public static function titleAttrib( $name, $options = null ) {
+ global $wgEnableTooltipsAndAccesskeys;
+ if ( !$wgEnableTooltipsAndAccesskeys )
+ return false;
+
wfProfileIn( __METHOD__ );
- if ( wfEmptyMsg( "tooltip-$name" ) ) {
+ $message = wfMessage( "tooltip-$name" );
+
+ if ( !$message->exists() ) {
$tooltip = false;
} else {
- $tooltip = wfMsg( "tooltip-$name" );
+ $tooltip = $message->text();
# Compatibility: formerly some tooltips had [alt-.] hardcoded
$tooltip = preg_replace( "/ ?\[alt-.\]$/", '', $tooltip );
# Message equal to '-' means suppress it.
@@ -1646,7 +1718,7 @@ class Linker {
}
if ( $options == 'withaccess' ) {
- $accesskey = $this->accesskey( $name );
+ $accesskey = self::accesskey( $name );
if ( $accesskey !== false ) {
if ( $tooltip === false || $tooltip === '' ) {
$tooltip = "[$accesskey]";
@@ -1660,6 +1732,8 @@ class Linker {
return $tooltip;
}
+ static $accesskeycache;
+
/**
* Given the id of an interface element, constructs the appropriate
* accesskey attribute from the system messages. (Note, this is usually
@@ -1670,9 +1744,9 @@ class Linker {
* @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];
+ public static function accesskey( $name ) {
+ if ( isset( self::$accesskeycache[$name] ) ) {
+ return self::$accesskeycache[$name];
}
wfProfileIn( __METHOD__ );
@@ -1683,7 +1757,7 @@ class Linker {
} else {
$accesskey = $message->plain();
if ( $accesskey === '' || $accesskey === '-' ) {
- # FIXME: Per standard MW behavior, a value of '-' means to suppress the
+ # @todo 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;
@@ -1691,7 +1765,7 @@ class Linker {
}
wfProfileOut( __METHOD__ );
- return $this->accesskeycache[$name] = $accesskey;
+ return self::$accesskeycache[$name] = $accesskey;
}
/**
@@ -1704,11 +1778,11 @@ class Linker {
* @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 ) {
+ public static function revDeleteLink( $query = array(), $restricted = false, $delete = true ) {
$sp = SpecialPage::getTitleFor( 'Revisiondelete' );
$text = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' );
$tag = $restricted ? 'strong' : 'span';
- $link = $this->link( $sp, $text, array(), $query, array( 'known', 'noclasses' ) );
+ $link = self::link( $sp, $text, array(), $query, array( 'known', 'noclasses' ) );
return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), "($link)" );
}
@@ -1720,7 +1794,7 @@ class Linker {
* @return string HTML text wrapped in a span to allow for customization
* of appearance with CSS
*/
- public function revDeleteLinkDisabled( $delete = true ) {
+ public static function revDeleteLinkDisabled( $delete = true ) {
$text = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' );
return Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), "($text)" );
}
@@ -1728,68 +1802,7 @@ class Linker {
/* Deprecated methods */
/**
- * @deprecated
- */
- function postParseLinkColour( $s = null ) {
- wfDeprecated( __METHOD__ );
- return null;
- }
-
-
- /**
- * @deprecated Use link()
- *
- * This function is a shortcut to makeLinkObj(Title::newFromText($title),...). Do not call
- * it if you already have a title object handy. See makeLinkObj for further documentation.
- *
- * @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 makeLink( $title, $text = '', $query = '', $trail = '' ) {
- wfProfileIn( __METHOD__ );
- $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" );
- $result = $text == "" ? $title : $text;
- }
-
- wfProfileOut( __METHOD__ );
- return $result;
- }
-
- /**
- * @deprecated Use link()
- *
- * This function is a shortcut to makeKnownLinkObj(Title::newFromText($title),...). Do not call
- * it if you already have a title object handy. See makeKnownLinkObj for further documentation.
- *
- * @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.
- * @param $prefix String: Optional prefix
- * @param $aprops String: extra attributes to the a-element
- */
- 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" );
- return $text == '' ? $title : $text;
- }
- }
-
- /**
- * @deprecated Use link()
+ * @deprecated since 1.16 Use link()
*
* This function is a shortcut to makeBrokenLinkObj(Title::newFromText($title),...). Do not call
* it if you already have a title object handy. See makeBrokenLinkObj for further documentation.
@@ -1801,42 +1814,18 @@ class Linker {
* be included in the link text. Other characters will be appended after
* the end of the link.
*/
- function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
+ static function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
$nt = Title::newFromText( $title );
if ( $nt instanceof Title ) {
- return $this->makeBrokenLinkObj( $nt, $text, $query, $trail );
+ return self::makeBrokenLinkObj( $nt, $text, $query, $trail );
} else {
- wfDebug( 'Invalid title passed to Linker::makeBrokenLink(): "' . $title . "\"\n" );
+ wfDebug( 'Invalid title passed to self::makeBrokenLink(): "' . $title . "\"\n" );
return $text == '' ? $title : $text;
}
}
/**
- * @deprecated Use link()
- *
- * This function is a shortcut to makeStubLinkObj(Title::newFromText($title),...). Do not call
- * it if you already have a title object handy. See makeStubLinkObj for further documentation.
- *
- * @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 makeStubLink( $title, $text = '', $query = '', $trail = '' ) {
- wfDeprecated( __METHOD__ );
- $nt = Title::newFromText( $title );
- if ( $nt instanceof Title ) {
- return $this->makeStubLinkObj( $nt, $text, $query, $trail );
- } else {
- wfDebug( 'Invalid title passed to Linker::makeStubLink(): "' . $title . "\"\n" );
- return $text == '' ? $title : $text;
- }
- }
-
- /**
- * @deprecated Use link()
+ * @deprecated since 1.16 Use link()
*
* Make a link for a title which may or may not be in the database. If you need to
* call this lots of times, pre-fill the link cache with a LinkBatch, otherwise each
@@ -1851,23 +1840,22 @@ 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 = '' ) {
+ static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
wfProfileIn( __METHOD__ );
-
$query = wfCgiToArray( $query );
- list( $inside, $trail ) = Linker::splitTrail( $trail );
+ list( $inside, $trail ) = self::splitTrail( $trail );
if ( $text === '' ) {
- $text = $this->linkText( $nt );
+ $text = self::linkText( $nt );
}
- $ret = $this->link( $nt, "$prefix$text$inside", array(), $query ) . $trail;
+ $ret = self::link( $nt, "$prefix$text$inside", array(), $query ) . $trail;
wfProfileOut( __METHOD__ );
return $ret;
}
/**
- * @deprecated Use link()
+ * @deprecated since 1.16 Use link()
*
* Make a link for a title which definitely exists. This is faster than makeLinkObj because
* it doesn't have to do a database query. It's also valid for interwiki titles and special
@@ -1882,20 +1870,22 @@ class Linker {
* @param $style String: style to apply - if empty, use getInternalLinkAttributesObj instead
* @return the a-element
*/
- function makeKnownLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = '' ) {
+ static function makeKnownLinkObj(
+ $title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = ''
+ ) {
wfProfileIn( __METHOD__ );
if ( $text == '' ) {
- $text = $this->linkText( $title );
+ $text = self::linkText( $title );
}
$attribs = Sanitizer::mergeAttributes(
Sanitizer::decodeTagAttributes( $aprops ),
Sanitizer::decodeTagAttributes( $style )
);
$query = wfCgiToArray( $query );
- list( $inside, $trail ) = Linker::splitTrail( $trail );
+ list( $inside, $trail ) = self::splitTrail( $trail );
- $ret = $this->link( $title, "$prefix$text$inside", $attribs, $query,
+ $ret = self::link( $title, "$prefix$text$inside", $attribs, $query,
array( 'known', 'noclasses' ) ) . $trail;
wfProfileOut( __METHOD__ );
@@ -1903,7 +1893,7 @@ class Linker {
}
/**
- * @deprecated Use link()
+ * @deprecated since 1.16 Use link()
*
* Make a red link to the edit page of a given title.
*
@@ -1915,15 +1905,15 @@ class Linker {
* the end of the link.
* @param $prefix String: Optional prefix
*/
- function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
+ static function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
wfProfileIn( __METHOD__ );
- list( $inside, $trail ) = Linker::splitTrail( $trail );
+ list( $inside, $trail ) = self::splitTrail( $trail );
if ( $text === '' ) {
- $text = $this->linkText( $title );
+ $text = self::linkText( $title );
}
- $ret = $this->link( $title, "$prefix$text$inside", array(),
+ $ret = self::link( $title, "$prefix$text$inside", array(),
wfCgiToArray( $query ), 'broken' ) . $trail;
wfProfileOut( __METHOD__ );
@@ -1931,25 +1921,7 @@ class Linker {
}
/**
- * @deprecated Use link()
- *
- * Make a brown link to a short article.
- *
- * @param $nt 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 makeStubLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
- // wfDeprecated( __METHOD__ );
- return $this->makeColouredLinkObj( $nt, 'stub', $text, $query, $trail, $prefix );
- }
-
- /**
- * @deprecated Use link()
+ * @deprecated since 1.16 Use link()
*
* Make a coloured link.
*
@@ -1962,108 +1934,28 @@ class Linker {
* the end of the link.
* @param $prefix String: Optional prefix
*/
- function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
- // wfDeprecated( __METHOD__ );
+ static function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
if ( $colour != '' ) {
- $style = $this->getInternalLinkAttributesObj( $nt, $text, $colour );
- } else $style = '';
- return $this->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
- }
-
- /** Obsolete alias */
- function makeImage( $url, $alt = '' ) {
- wfDeprecated( __METHOD__ );
- return $this->makeExternalImage( $url, $alt );
- }
-
- /**
- * Creates the HTML source for images
- * @deprecated use makeImageLink2
- *
- * @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 )
- {
- $frameParams = array( 'alt' => $alt, 'caption' => $label );
- if ( $align ) {
- $frameParams['align'] = $align;
- }
- if ( $framed ) {
- $frameParams['framed'] = true;
- }
- if ( $thumb ) {
- $frameParams['thumbnail'] = true;
- }
- if ( $manualthumb ) {
- $frameParams['manualthumb'] = $manualthumb;
- }
- if ( $valign ) {
- $frameParams['valign'] = $valign;
- }
- $file = wfFindFile( $title, array( 'time' => $time ) );
- return $this->makeImageLink2( $title, $file, $frameParams, $handlerParams, $time );
- }
-
- /** @deprecated use Linker::makeMediaLinkObj() */
- function makeMediaLink( $name, $unused = '', $text = '', $time = false ) {
- $nt = Title::makeTitleSafe( NS_FILE, $name );
- return $this->makeMediaLinkObj( $nt, $text, $time );
- }
-
- /**
- * Used to generate section edit links that point to "other" pages
- * (sections that are really part of included pages).
- *
- * @deprecated use Linker::doEditSectionLink()
- * @param $title Title string.
- * @param $section Integer: section number.
- */
- public function editSectionLinkForOther( $title, $section ) {
- wfDeprecated( __METHOD__ );
- $title = Title::newFromText( $title );
- return $this->doEditSectionLink( $title, $section );
- }
-
- /**
- * @deprecated use Linker::doEditSectionLink()
- * @param $nt Title object.
- * @param $section Integer: section number.
- * @param $hint Link String: title, or default if omitted or empty
- */
- public function editSectionLink( Title $nt, $section, $hint = '' ) {
- wfDeprecated( __METHOD__ );
- 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;
+ $style = self::getInternalLinkAttributesObj( $nt, $text, $colour );
+ } else {
+ $style = '';
}
- return $this->doEditSectionLink( $nt, $section, $hint );
+ return self::makeKnownLinkObj( $nt, $text, $query, $trail, $prefix, '', $style );
}
/**
* Returns the attributes for the tooltip and access key.
*/
- public function tooltipAndAccesskeyAttribs( $name ) {
+ public static function tooltipAndAccesskeyAttribs( $name ) {
global $wgEnableTooltipsAndAccesskeys;
if ( !$wgEnableTooltipsAndAccesskeys )
return array();
- # FIXME: If Sanitizer::expandAttributes() treated "false" as "output
+ # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
# no attribute" instead of "output '' as value for attribute", this
# would be three lines.
$attribs = array(
- 'title' => $this->titleAttrib( $name, 'withaccess' ),
- 'accesskey' => $this->accesskey( $name )
+ 'title' => self::titleAttrib( $name, 'withaccess' ),
+ 'accesskey' => self::accesskey( $name )
);
if ( $attribs['title'] === false ) {
unset( $attribs['title'] );
@@ -2073,27 +1965,42 @@ class Linker {
}
return $attribs;
}
+
/**
- * @deprecated Returns raw bits of HTML, use titleAttrib() and accesskey()
+ * @deprecated since 1.14
+ * Returns raw bits of HTML, use titleAttrib()
*/
- public function tooltipAndAccesskey( $name ) {
- return Xml::expandAttributes( $this->tooltipAndAccesskeyAttribs( $name ) );
- }
-
- /** @deprecated Returns raw bits of HTML, use titleAttrib() */
- public function tooltip( $name, $options = null ) {
+ public static function tooltip( $name, $options = null ) {
global $wgEnableTooltipsAndAccesskeys;
if ( !$wgEnableTooltipsAndAccesskeys )
return '';
- # FIXME: If Sanitizer::expandAttributes() treated "false" as "output
+ # @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
# no attribute" instead of "output '' as value for attribute", this
# would be two lines.
- $tooltip = $this->titleAttrib( $name, $options );
+ $tooltip = self::titleAttrib( $name, $options );
if ( $tooltip === false ) {
return '';
}
return Xml::expandAttributes( array(
- 'title' => $this->titleAttrib( $name, $options )
+ 'title' => $tooltip
) );
}
}
+
+/**
+ * @since 1.18
+ */
+class DummyLinker {
+
+ /**
+ * Use PHP's magic __call handler to transform instance calls to a dummy instance
+ * into static calls to the new Linker for backwards compatibility.
+ *
+ * @param $fname String Name of called method
+ * @param $args Array Arguments to the method
+ */
+ public function __call( $fname, $args ) {
+ return call_user_func_array( array( 'Linker', $fname ), $args );
+ }
+}
+
diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php
index b86ea565..1fe6118a 100644
--- a/includes/LinksUpdate.php
+++ b/includes/LinksUpdate.php
@@ -2,6 +2,21 @@
/**
* See docs/deferred.txt
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @todo document (e.g. one-sentence top-level class description).
*/
class LinksUpdate {
@@ -67,7 +82,7 @@ class LinksUpdate {
$this->mInterlangs[$key] = $title;
}
- foreach ( $this->mCategories as $cat => &$sortkey ) {
+ foreach ( $this->mCategories as &$sortkey ) {
# If the sortkey is longer then 255 bytes,
# it truncated by DB, and then doesn't get
# matched when comparing existing vs current
@@ -110,7 +125,8 @@ class LinksUpdate {
$existing = $this->getExistingImages();
$imageDeletes = $this->getImageDeletions( $existing );
- $this->incrTableUpdate( 'imagelinks', 'il', $imageDeletes, $this->getImageInsertions( $existing ) );
+ $this->incrTableUpdate( 'imagelinks', 'il', $imageDeletes,
+ $this->getImageInsertions( $existing ) );
# Invalidate all image description pages which had links added or removed
$imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existing );
@@ -141,7 +157,8 @@ class LinksUpdate {
$categoryDeletes = $this->getCategoryDeletions( $existing );
- $this->incrTableUpdate( 'categorylinks', 'cl', $categoryDeletes, $this->getCategoryInsertions( $existing ) );
+ $this->incrTableUpdate( 'categorylinks', 'cl', $categoryDeletes,
+ $this->getCategoryInsertions( $existing ) );
# Invalidate all categories which were added, deleted or changed (set symmetric difference)
$categoryInserts = array_diff_assoc( $this->mCategories, $existing );
@@ -154,7 +171,8 @@ class LinksUpdate {
$propertiesDeletes = $this->getPropertyDeletions( $existing );
- $this->incrTableUpdate( 'page_props', 'pp', $propertiesDeletes, $this->getPropertyInsertions( $existing ) );
+ $this->incrTableUpdate( 'page_props', 'pp', $propertiesDeletes,
+ $this->getPropertyInsertions( $existing ) );
# Invalidate the necessary pages
$changed = $propertiesDeletes + array_diff_assoc( $this->mProperties, $existing );
@@ -362,7 +380,9 @@ class LinksUpdate {
function getLinkInsertions( $existing = array() ) {
$arr = array();
foreach( $this->mLinks as $ns => $dbkeys ) {
- $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
+ $diffs = isset( $existing[$ns] )
+ ? array_diff_key( $dbkeys, $existing[$ns] )
+ : $dbkeys;
foreach ( $diffs as $dbk => $id ) {
$arr[] = array(
'pl_from' => $this->mId,
@@ -418,11 +438,13 @@ class LinksUpdate {
$arr = array();
$diffs = array_diff_key( $this->mExternals, $existing );
foreach( $diffs as $url => $dummy ) {
- $arr[] = array(
- 'el_from' => $this->mId,
- 'el_to' => $url,
- 'el_index' => wfMakeUrlIndex( $url ),
- );
+ foreach( wfMakeUrlIndexes( $url ) as $index ) {
+ $arr[] = array(
+ 'el_from' => $this->mId,
+ 'el_to' => $url,
+ 'el_index' => $index,
+ );
+ }
}
return $arr;
}
@@ -749,7 +771,7 @@ class LinksUpdate {
function getTitle() {
return $this->mTitle;
}
-
+
/**
* Return the list of images used as generated by the parser
*/
diff --git a/includes/LocalisationCache.php b/includes/LocalisationCache.php
index 9ead21f1..6d5882d9 100644
--- a/includes/LocalisationCache.php
+++ b/includes/LocalisationCache.php
@@ -8,8 +8,8 @@ define( 'MW_LC_VERSION', 1 );
*
* An instance of this class is available using Language::getLocalisationCache().
*
- * The values retrieved from here are merged, containing items from extension
- * files, core messages files and the language fallback sequence (e.g. zh-cn ->
+ * The values retrieved from here are merged, containing items from extension
+ * files, core messages files and the language fallback sequence (e.g. zh-cn ->
* zh-hans -> en ). Some common errors are corrected, for example namespace
* names with spaces instead of underscores, but heavyweight processing, such
* as grammatical transformation, is done by the caller.
@@ -78,31 +78,24 @@ class LocalisationCache {
var $recachedLangs = array();
/**
- * Data added by extensions using the deprecated $wgMessageCache->addMessages()
- * interface.
- */
- var $legacyData = array();
-
- /**
* All item keys
*/
static public $allKeys = array(
- 'fallback', 'namespaceNames', 'mathNames', 'bookstoreList',
+ 'fallback', 'namespaceNames', 'bookstoreList',
'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable',
'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
- 'defaultUserOptionOverrides', 'linkTrail', 'namespaceAliases',
+ 'linkTrail', 'namespaceAliases',
'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
- 'imageFiles', 'preloadedMessages',
+ 'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
);
/**
* Keys for items which consist of associative arrays, which may be merged
* by a fallback sequence.
*/
- static public $mergeableMapKeys = array( 'messages', 'namespaceNames', 'mathNames',
- 'dateFormats', 'defaultUserOptionOverrides', 'imageFiles',
- 'preloadedMessages',
+ static public $mergeableMapKeys = array( 'messages', 'namespaceNames',
+ 'dateFormats', 'imageFiles', 'preloadedMessages',
);
/**
@@ -122,7 +115,7 @@ class LocalisationCache {
* key is removed after the first merge.
*/
static public $optionalMergeKeys = array( 'bookstoreList' );
-
+
/**
* Keys for items that are formatted like $magicWords
*/
@@ -136,13 +129,14 @@ class LocalisationCache {
/**
* Keys which are loaded automatically by initLanguage()
*/
- static public $preloadedKeys = array( 'dateFormats', 'namespaceNames',
- 'defaultUserOptionOverrides' );
+ static public $preloadedKeys = array( 'dateFormats', 'namespaceNames' );
/**
* Constructor.
- * For constructor parameters, see the documentation in DefaultSettings.php
+ * For constructor parameters, see the documentation in DefaultSettings.php
* for $wgLocalisationCacheConf.
+ *
+ * @param $conf Array
*/
function __construct( $conf ) {
global $wgCacheDirectory;
@@ -164,7 +158,7 @@ class LocalisationCache {
$storeClass = $wgCacheDirectory ? 'LCStore_CDB' : 'LCStore_DB';
break;
default:
- throw new MWException(
+ throw new MWException(
'Please set $wgLocalisationCacheConf[\'store\'] to something sensible.' );
}
}
@@ -221,11 +215,8 @@ class LocalisationCache {
* Get a subitem, for instance a single message for a given language.
*/
public function getSubitem( $code, $key, $subkey ) {
- if ( isset( $this->legacyData[$code][$key][$subkey] ) ) {
- return $this->legacyData[$code][$key][$subkey];
- }
- if ( !isset( $this->loadedSubitems[$code][$key][$subkey] )
- && !isset( $this->loadedItems[$code][$key] ) )
+ if ( !isset( $this->loadedSubitems[$code][$key][$subkey] )
+ && !isset( $this->loadedItems[$code][$key] ) )
{
wfProfileIn( __METHOD__.'-load' );
$this->loadSubitem( $code, $key, $subkey );
@@ -241,10 +232,10 @@ class LocalisationCache {
/**
* Get the list of subitem keys for a given item.
*
- * This is faster than array_keys($lc->getItem(...)) for the items listed in
+ * This is faster than array_keys($lc->getItem(...)) for the items listed in
* self::$splitKeys.
*
- * Will return null if the item is not found, or false if the item is not an
+ * Will return null if the item is not found, or false if the item is not an
* array.
*/
public function getSubitemList( $code, $key ) {
@@ -335,7 +326,7 @@ class LocalisationCache {
// anymore (e.g. uninstalled extensions)
// When this happens, always expire the cache
if ( !$dep instanceof CacheDependency || $dep->isExpired() ) {
- wfDebug( __METHOD__."($code): cache for $code expired due to " .
+ wfDebug( __METHOD__."($code): cache for $code expired due to " .
get_class( $dep ) . "\n" );
return true;
}
@@ -352,6 +343,12 @@ class LocalisationCache {
}
$this->initialisedLangs[$code] = true;
+ # If the code is of the wrong form for a Messages*.php file, do a shallow fallback
+ if ( !Language::isValidBuiltInCode( $code ) ) {
+ $this->initShallowFallback( $code, 'en' );
+ return;
+ }
+
# Recache the data if necessary
if ( !$this->manualRecache && $this->isExpired( $code ) ) {
if ( file_exists( Language::getMessagesFileName( $code ) ) ) {
@@ -370,7 +367,7 @@ class LocalisationCache {
if ( $this->manualRecache ) {
// No Messages*.php file. Do shallow fallback to en.
if ( $code === 'en' ) {
- throw new MWException( 'No localisation cache found for English. ' .
+ throw new MWException( 'No localisation cache found for English. ' .
'Please run maintenance/rebuildLocalisationCache.php.' );
}
$this->initShallowFallback( $code, 'en' );
@@ -392,7 +389,7 @@ class LocalisationCache {
}
/**
- * Create a fallback from one language to another, without creating a
+ * Create a fallback from one language to another, without creating a
* complete persistent cache.
*/
public function initShallowFallback( $primaryCode, $fallbackCode ) {
@@ -422,7 +419,7 @@ class LocalisationCache {
}
/**
- * Merge two localisation values, a primary and a fallback, overwriting the
+ * Merge two localisation values, a primary and a fallback, overwriting the
* primary value in place.
*/
protected function mergeItem( $key, &$value, $fallbackValue ) {
@@ -457,7 +454,7 @@ class LocalisationCache {
} else {
$oldSynonyms = array_slice( $fallbackInfo, 1 );
$newSynonyms = array_slice( $value[$magicName], 1 );
- $synonyms = array_values( array_unique( array_merge(
+ $synonyms = array_values( array_unique( array_merge(
$newSynonyms, $oldSynonyms ) ) );
$value[$magicName] = array_merge( array( $fallbackInfo[0] ), $synonyms );
}
@@ -469,7 +466,7 @@ class LocalisationCache {
* found in extension *.i18n.php files, iterate through a fallback sequence
* to merge the given data with an existing primary value.
*
- * Returns true if any data from the extension array was used, false
+ * Returns true if any data from the extension array was used, false
* otherwise.
*/
protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
@@ -499,7 +496,7 @@ class LocalisationCache {
# Initial values
$initialData = array_combine(
- self::$allKeys,
+ self::$allKeys,
array_fill( 0, count( self::$allKeys ), null ) );
$coreData = $initialData;
$deps = array();
@@ -551,7 +548,7 @@ class LocalisationCache {
# Load the extension localisations
# This is done after the core because we know the fallback sequence now.
- # But it has a higher precedence for merging so that we can support things
+ # But it has a higher precedence for merging so that we can support things
# like site-specific message overrides.
$allData = $initialData;
foreach ( $wgExtensionMessagesFiles as $fileName ) {
@@ -602,11 +599,6 @@ class LocalisationCache {
}
# Decouple the reference to prevent accidental damage
unset($page);
-
- # Fix broken defaultUserOptionOverrides
- if ( !is_array( $allData['defaultUserOptionOverrides'] ) ) {
- $allData['defaultUserOptionOverrides'] = array();
- }
# Set the list keys
$allData['list'] = array();
@@ -617,8 +609,8 @@ class LocalisationCache {
# Run hooks
wfRunHooks( 'LocalisationCacheRecache', array( $this, $code, &$allData ) );
- if ( is_null( $allData['defaultUserOptionOverrides'] ) ) {
- throw new MWException( __METHOD__.': Localisation data failed sanity check! ' .
+ if ( is_null( $allData['namespaceNames'] ) ) {
+ throw new MWException( __METHOD__.': Localisation data failed sanity check! ' .
'Check that your languages/messages/MessagesEn.php file is intact.' );
}
@@ -643,7 +635,7 @@ 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
@@ -677,7 +669,7 @@ class LocalisationCache {
}
/**
- * Unload the data for a given language from the object cache.
+ * Unload the data for a given language from the object cache.
* Reduces memory usage.
*/
public function unload( $code ) {
@@ -685,8 +677,6 @@ class LocalisationCache {
unset( $this->loadedItems[$code] );
unset( $this->loadedSubitems[$code] );
unset( $this->initialisedLangs[$code] );
- // We don't unload legacyData because there's no way to get it back
- // again, it's not really a cache
foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
if ( $fbCode === $code ) {
$this->unload( $shallowCode );
@@ -704,22 +694,6 @@ class LocalisationCache {
}
/**
- * Add messages to the cache, from an extension that has not yet been
- * migrated to $wgExtensionMessages or the LocalisationCacheRecache hook.
- * Called by deprecated function $wgMessageCache->addMessages().
- */
- public function addLegacyMessages( $messages ) {
- foreach ( $messages as $lang => $langMessages ) {
- if ( isset( $this->legacyData[$lang]['messages'] ) ) {
- $this->legacyData[$lang]['messages'] =
- $langMessages + $this->legacyData[$lang]['messages'];
- } else {
- $this->legacyData[$lang]['messages'] = $langMessages;
- }
- }
- }
-
- /**
* Disable the storage backend
*/
public function disableBackend() {
@@ -734,15 +708,15 @@ class LocalisationCache {
* The persistence layer is two-level hierarchical cache. The first level
* is the language, the second level is the item or subitem.
*
- * Since the data for a whole language is rebuilt in one operation, it needs
- * to have a fast and atomic method for deleting or replacing all of the
+ * Since the data for a whole language is rebuilt in one operation, it needs
+ * to have a fast and atomic method for deleting or replacing all of the
* current data for a given language. The interface reflects this bulk update
- * operation. Callers writing to the cache must first call startWrite(), then
- * will call set() a couple of thousand times, then will call finishWrite()
- * to commit the operation. When finishWrite() is called, the cache is
+ * operation. Callers writing to the cache must first call startWrite(), then
+ * will call set() a couple of thousand times, then will call finishWrite()
+ * to commit the operation. When finishWrite() is called, the cache is
* expected to delete all data previously stored for that language.
*
- * The values stored are PHP variables suitable for serialize(). Implementations
+ * The values stored are PHP variables suitable for serialize(). Implementations
* of LCStore are responsible for serializing and unserializing.
*/
interface LCStore {
@@ -751,29 +725,29 @@ interface LCStore {
* @param $code Language code
* @param $key Cache key
*/
- public function get( $code, $key );
+ function get( $code, $key );
/**
* Start a write transaction.
* @param $code Language code
*/
- public function startWrite( $code );
+ function startWrite( $code );
/**
* Finish a write transaction.
*/
- public function finishWrite();
+ function finishWrite();
/**
* Set a key to a given value. startWrite() must be called before this
* is called, and finishWrite() must be called afterwards.
*/
- public function set( $key, $value );
+ function set( $key, $value );
}
/**
- * LCStore implementation which uses the standard DB functions to store data.
+ * LCStore implementation which uses the standard DB functions to store data.
* This will work on any MediaWiki installation.
*/
class LCStore_DB implements LCStore {
@@ -859,9 +833,9 @@ class LCStore_DB implements LCStore {
* directory given by $wgCacheDirectory. If $wgCacheDirectory is not set, this
* will throw an exception.
*
- * Profiling indicates that on Linux, this implementation outperforms MySQL if
- * the directory is on a local filesystem and there is ample kernel cache
- * space. The performance advantage is greater when the DBA extension is
+ * Profiling indicates that on Linux, this implementation outperforms MySQL if
+ * the directory is on a local filesystem and there is ample kernel cache
+ * space. The performance advantage is greater when the DBA extension is
* available than it is with the PHP port.
*
* See Cdb.php and http://cr.yp.to/cdb.html
@@ -901,7 +875,7 @@ class LCStore_CDB implements LCStore {
public function startWrite( $code ) {
if ( !file_exists( $this->directory ) ) {
if ( !wfMkdirParents( $this->directory ) ) {
- throw new MWException( "Unable to create the localisation store " .
+ throw new MWException( "Unable to create the localisation store " .
"directory \"{$this->directory}\"" );
}
}
@@ -950,7 +924,7 @@ class LCStore_Null implements LCStore {
}
/**
- * A localisation cache optimised for loading large amounts of data for many
+ * A localisation cache optimised for loading large amounts of data for many
* languages. Used by rebuildLocalisationCache.php.
*/
class LocalisationCache_BulkLoad extends LocalisationCache {
@@ -962,7 +936,7 @@ class LocalisationCache_BulkLoad extends LocalisationCache {
/**
* Most recently used languages. Uses the linked-list aspect of PHP hashtables
- * to keep the most recently used language codes at the end of the array, and
+ * to keep the most recently used language codes at the end of the array, and
* the language codes that are ready to be deleted at the beginning.
*/
var $mruLangs = array();
diff --git a/includes/LogEventsList.php b/includes/LogEventsList.php
index 128500c5..744a60ce 100644
--- a/includes/LogEventsList.php
+++ b/includes/LogEventsList.php
@@ -27,10 +27,27 @@ class LogEventsList {
const NO_ACTION_LINK = 1;
const NO_EXTRA_USER_LINKS = 2;
+ /**
+ * @var Skin
+ */
private $skin;
+
+ /**
+ * @var OutputPage
+ */
private $out;
public $flags;
+ /**
+ * @var Array
+ */
+ protected $message;
+
+ /**
+ * @var Array
+ */
+ protected $mDefaultQuery;
+
public function __construct( $skin, $out, $flags = 0 ) {
$this->skin = $skin;
$this->out = $out;
@@ -81,9 +98,8 @@ class LogEventsList {
* @param $filter: array
* @param $tagFilter: array?
*/
- public function showOptions( $types=array(), $user='', $page='', $pattern='', $year='',
- $month = '', $filter = null, $tagFilter='' )
- {
+ public function showOptions( $types=array(), $user='', $page='', $pattern='', $year='',
+ $month = '', $filter = null, $tagFilter='' ) {
global $wgScript, $wgMiserMode;
$action = $wgScript;
@@ -138,7 +154,7 @@ class LogEventsList {
* @return String: Formatted HTML
*/
private function getFilterLinks( $filter ) {
- global $wgTitle, $wgLang;
+ global $wgLang;
// show/hide links
$messages = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) );
// Option value -> message mapping
@@ -153,8 +169,8 @@ class LogEventsList {
$hideVal = 1 - intval($val);
$query[$queryKey] = $hideVal;
- $link = $this->skin->link(
- $wgTitle,
+ $link = Linker::link(
+ $this->getDisplayTitle(),
$messages[$hideVal],
array(),
$query,
@@ -169,8 +185,10 @@ class LogEventsList {
}
private function getDefaultQuery() {
+ global $wgRequest;
+
if ( !isset( $this->mDefaultQuery ) ) {
- $this->mDefaultQuery = $_GET;
+ $this->mDefaultQuery = $wgRequest->getQueryValues();
unset( $this->mDefaultQuery['title'] );
unset( $this->mDefaultQuery['dir'] );
unset( $this->mDefaultQuery['offset'] );
@@ -183,6 +201,16 @@ class LogEventsList {
}
/**
+ * Get the Title object of the page the links should point to.
+ * This is NOT the Title of the page the entries should be restricted to.
+ *
+ * @return Title object
+ */
+ public function getDisplayTitle() {
+ return $this->out->getTitle();
+ }
+
+ /**
* @param $queryTypes Array
* @return String: Formatted HTML
*/
@@ -312,7 +340,7 @@ class LogEventsList {
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 );
@@ -324,10 +352,10 @@ class LogEventsList {
$userLinks = '<span class="history-deleted">' .
wfMsgHtml( 'rev-deleted-user' ) . '</span>';
} else {
- $userLinks = $this->skin->userLink( $row->log_user, $row->user_name );
+ $userLinks = Linker::userLink( $row->log_user, $row->user_name );
// Talk|Contribs links...
if( !( $this->flags & self::NO_EXTRA_USER_LINKS ) ) {
- $userLinks .= $this->skin->userToolLinks(
+ $userLinks .= Linker::userToolLinks(
$row->log_user, $row->user_name, true, 0, $row->user_editcount );
}
}
@@ -344,20 +372,28 @@ class LogEventsList {
}
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>';
} else {
- $comment = $wgContLang->getDirMark() .
- $this->skin->commentBlock( $row->log_comment );
+ global $wgLang;
+ $comment = $wgLang->getDirMark() .
+ Linker::commentBlock( $row->log_comment );
}
return $comment;
}
- // @TODO: split up!
+ /**
+ * @TODO: split up!
+ *
+ * @param $row
+ * @param Title $title
+ * @param Array $paramArray
+ * @param $comment
+ * @return String
+ */
private function logActionLinks( $row, $title, $paramArray, &$comment ) {
global $wgUser;
if( ( $this->flags & self::NO_ACTION_LINK ) // we don't want to see the action
@@ -369,7 +405,7 @@ class LogEventsList {
if( self::typeAction( $row, 'move', 'move', 'move' ) && !empty( $paramArray[0] ) ) {
$destTitle = Title::newFromText( $paramArray[0] );
if( $destTitle ) {
- $revert = '(' . $this->skin->link(
+ $revert = '(' . Linker::link(
SpecialPage::getTitleFor( 'Movepage' ),
$this->message['revertmove'],
array(),
@@ -383,13 +419,13 @@ class LogEventsList {
) . ')';
}
// Show undelete link
- } else if( self::typeAction( $row, array( 'delete', 'suppress' ), 'delete', 'deletedhistory' ) ) {
+ } elseif( self::typeAction( $row, array( 'delete', 'suppress' ), 'delete', 'deletedhistory' ) ) {
if( !$wgUser->isAllowed( 'undelete' ) ) {
$viewdeleted = $this->message['undeleteviewlink'];
} else {
$viewdeleted = $this->message['undeletelink'];
}
- $revert = '(' . $this->skin->link(
+ $revert = '(' . Linker::link(
SpecialPage::getTitleFor( 'Undelete' ),
$viewdeleted,
array(),
@@ -397,21 +433,18 @@ class LogEventsList {
array( 'known', 'noclasses' )
) . ')';
// Show unblock/change block link
- } else if( self::typeAction( $row, array( 'block', 'suppress' ), array( 'block', 'reblock' ), 'block' ) ) {
+ } elseif( self::typeAction( $row, array( 'block', 'suppress' ), array( 'block', 'reblock' ), 'block' ) ) {
$revert = '(' .
- $this->skin->link(
- SpecialPage::getTitleFor( 'Ipblocklist' ),
+ Linker::link(
+ SpecialPage::getTitleFor( 'Unblock', $row->log_title ),
$this->message['unblocklink'],
array(),
- array(
- 'action' => 'unblock',
- 'ip' => $row->log_title
- ),
+ array(),
'known'
) .
$this->message['pipe-separator'] .
- $this->skin->link(
- SpecialPage::getTitleFor( 'Blockip', $row->log_title ),
+ Linker::link(
+ SpecialPage::getTitleFor( 'Block', $row->log_title ),
$this->message['change-blocklink'],
array(),
array(),
@@ -419,9 +452,9 @@ class LogEventsList {
) .
')';
// Show change protection link
- } else if( self::typeAction( $row, 'protect', array( 'modify', 'protect', 'unprotect' ) ) ) {
+ } elseif( self::typeAction( $row, 'protect', array( 'modify', 'protect', 'unprotect' ) ) ) {
$revert .= ' (' .
- $this->skin->link( $title,
+ Linker::link( $title,
$this->message['hist'],
array(),
array(
@@ -431,7 +464,7 @@ class LogEventsList {
);
if( $wgUser->isAllowed( 'protect' ) ) {
$revert .= $this->message['pipe-separator'] .
- $this->skin->link( $title,
+ Linker::link( $title,
$this->message['protect_change'],
array(),
array( 'action' => 'protect' ),
@@ -439,8 +472,8 @@ class LogEventsList {
}
$revert .= ')';
// Show unmerge link
- } else if( self::typeAction( $row, 'merge', 'merge', 'mergehistory' ) ) {
- $revert = '(' . $this->skin->link(
+ } elseif( self::typeAction( $row, 'merge', 'merge', 'mergehistory' ) ) {
+ $revert = '(' . Linker::link(
SpecialPage::getTitleFor( 'MergeHistory' ),
$this->message['revertmerge'],
array(),
@@ -452,19 +485,19 @@ class LogEventsList {
array( 'known', 'noclasses' )
) . ')';
// 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' ) ) {
+ } elseif( self::typeAction( $row, array( 'delete', 'suppress' ), 'revision', 'deletedhistory' ) ) {
$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' ) ) {
+ } elseif( self::typeAction( $row, array( 'delete', 'suppress' ), 'event', 'deletedhistory' ) ) {
if( count($paramArray) >= 1 ) {
$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
// $paramArray[1] is a CSV of the IDs
$query = $paramArray[0];
// Link to each hidden object ID, $paramArray[1] is the url param
- $revert = '(' . $this->skin->link(
+ $revert = '(' . Linker::link(
$revdel,
- $this->message['revdel-restore'],
+ $this->message['revdel-restore'],
array(),
array(
'target' => $title->getPrefixedText(),
@@ -475,12 +508,12 @@ class LogEventsList {
) . ')';
}
// Self-created users
- } else if( self::typeAction( $row, 'newusers', 'create2' ) ) {
+ } elseif( self::typeAction( $row, 'newusers', 'create2' ) ) {
if( isset( $paramArray[0] ) ) {
- $revert = $this->skin->userToolLinks( $paramArray[0], $title->getDBkey(), true );
+ $revert = Linker::userToolLinks( $paramArray[0], $title->getDBkey(), true );
} else {
# Fall back to a blue contributions link
- $revert = $this->skin->userToolLinks( 1, $title->getDBkey() );
+ $revert = Linker::userToolLinks( 1, $title->getDBkey() );
}
if( wfTimestamp( TS_MW, $row->log_timestamp ) < '20080129000000' ) {
# Suppress $comment from old entries (before 2008-01-29),
@@ -505,8 +538,7 @@ class LogEventsList {
private function getShowHideLinks( $row ) {
global $wgUser;
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
- {
+ || $row->log_type == 'suppress' ) { // no one can hide items from the suppress log
return '';
}
$del = '';
@@ -516,7 +548,7 @@ class LogEventsList {
$canHide = $wgUser->isAllowed( 'deleterevision' );
// If event was hidden from sysops
if( !self::userCan( $row, LogPage::DELETED_RESTRICTED ) ) {
- $del = $this->skin->revDeleteLinkDisabled( $canHide );
+ $del = Linker::revDeleteLinkDisabled( $canHide );
} else {
$target = SpecialPage::getTitleFor( 'Log', $row->log_type );
$query = array(
@@ -524,7 +556,7 @@ class LogEventsList {
'type' => 'logging',
'ids' => $row->log_id,
);
- $del = $this->skin->revDeleteLink( $query,
+ $del = Linker::revDeleteLink( $query,
self::isDeleted( $row, LogPage::DELETED_RESTRICTED ), $canHide );
}
}
@@ -601,7 +633,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 $out OutputPage|String-by-reference
* @param $types String or Array
* @param $page String The page title to show log entries for
* @param $user String The user who made the log entries
@@ -684,7 +716,7 @@ class LogEventsList {
# If there is exactly one log type, we can link to Special:Log?type=foo
if ( count( $types ) == 1 )
$urlParam['type'] = $types[0];
- $s .= $wgUser->getSkin()->link(
+ $s .= Linker::link(
SpecialPage::getTitleFor( 'Log' ),
wfMsgHtml( 'log-fulllog' ),
array(),
@@ -757,8 +789,7 @@ class LogPager extends ReverseChronologicalPager {
* @param $tagFilter String: tag
*/
public function __construct( $list, $types = array(), $user = '', $title = '', $pattern = '',
- $conds = array(), $year = false, $month = false, $tagFilter = '' )
- {
+ $conds = array(), $year = false, $month = false, $tagFilter = '' ) {
parent::__construct();
$this->mConds = $conds;
@@ -780,6 +811,10 @@ class LogPager extends ReverseChronologicalPager {
return $query;
}
+ function getTitle() {
+ return $this->mLogEventsList->getDisplayTitle();
+ }
+
// Call ONLY after calling $this->limitType() already!
public function getFilterParams() {
global $wgFilterLogTypes, $wgUser, $wgRequest;
@@ -858,7 +893,7 @@ class LogPager extends ReverseChronologicalPager {
// Paranoia: avoid brute force searches (bug 17342)
if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
$this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::DELETED_USER) . ' = 0';
- } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ } elseif( !$wgUser->isAllowed( 'suppressrevision' ) ) {
$this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::SUPPRESSED_USER) .
' != ' . LogPage::SUPPRESSED_USER;
}
@@ -877,8 +912,9 @@ class LogPager extends ReverseChronologicalPager {
global $wgMiserMode, $wgUser;
$title = Title::newFromText( $page );
- if( strlen( $page ) == 0 || !$title instanceof Title )
+ if( strlen( $page ) == 0 || !$title instanceof Title ) {
return false;
+ }
$this->title = $title->getPrefixedText();
$ns = $title->getNamespace();
@@ -906,7 +942,7 @@ class LogPager extends ReverseChronologicalPager {
// Paranoia: avoid brute force searches (bug 17342)
if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
$this->mConds[] = $db->bitAnd('log_deleted', LogPage::DELETED_ACTION) . ' = 0';
- } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ } elseif( !$wgUser->isAllowed( 'suppressrevision' ) ) {
$this->mConds[] = $db->bitAnd('log_deleted', LogPage::SUPPRESSED_ACTION) .
' != ' . LogPage::SUPPRESSED_ACTION;
}
@@ -917,21 +953,30 @@ class LogPager extends ReverseChronologicalPager {
$this->mConds[] = 'user_id = log_user';
$index = array();
$options = array();
- # Add log_search table if there are conditions on it
- if( array_key_exists('ls_field',$this->mConds) ) {
+ # Add log_search table if there are conditions on it.
+ # This filters the results to only include log rows that have
+ # log_search records with the specified ls_field and ls_value values.
+ if( array_key_exists( 'ls_field', $this->mConds ) ) {
$tables[] = 'log_search';
$index['log_search'] = 'ls_field_val';
$index['logging'] = 'PRIMARY';
- $options[] = 'DISTINCT';
+ if ( !$this->hasEqualsClause( 'ls_field' )
+ || !$this->hasEqualsClause( 'ls_value' ) )
+ {
+ # Since (ls_field,ls_value,ls_logid) is unique, if the condition is
+ # to match a specific (ls_field,ls_value) tuple, then there will be
+ # no duplicate log rows. Otherwise, we need to remove the duplicates.
+ $options[] = 'DISTINCT';
+ }
# Avoid usage of the wrong index by limiting
# the choices of available indexes. This mainly
# avoids site-breaking filesorts.
- } else if( $this->title || $this->pattern || $this->user ) {
+ } elseif( $this->title || $this->pattern || $this->user ) {
$index['logging'] = array( 'page_time', 'user_time' );
if( count($this->types) == 1 ) {
$index['logging'][] = 'log_user_type_time';
}
- } else if( count($this->types) == 1 ) {
+ } elseif( count($this->types) == 1 ) {
$index['logging'] = 'type_time';
} else {
$index['logging'] = 'times';
@@ -946,7 +991,7 @@ class LogPager extends ReverseChronologicalPager {
'conds' => $this->mConds,
'options' => $options,
'join_conds' => array(
- 'user' => array( 'INNER JOIN', 'user_id=log_user' ),
+ 'user' => array( 'INNER JOIN', 'user_id=log_user' ),
'log_search' => array( 'INNER JOIN', 'ls_log_id=log_id' )
)
);
@@ -956,6 +1001,14 @@ class LogPager extends ReverseChronologicalPager {
return $info;
}
+ // Checks if $this->mConds has $field matched to a *single* value
+ protected function hasEqualsClause( $field ) {
+ return (
+ array_key_exists( $field, $this->mConds ) &&
+ ( !is_array( $this->mConds[$field] ) || count( $this->mConds[$field] ) == 1 )
+ );
+ }
+
function getIndexField() {
return 'log_timestamp';
}
@@ -1017,115 +1070,3 @@ class LogPager extends ReverseChronologicalPager {
}
}
-/**
- * @deprecated
- * @ingroup SpecialPage
- */
-class LogReader {
- var $pager;
-
- /**
- * @param $request WebRequest: for internal use use a FauxRequest object to pass arbitrary parameters.
- */
- function __construct( $request ) {
- global $wgUser, $wgOut;
- wfDeprecated(__METHOD__);
- # Get parameters
- $type = $request->getVal( 'type' );
- $user = $request->getText( 'user' );
- $title = $request->getText( 'page' );
- $pattern = $request->getBool( 'pattern' );
- $year = $request->getIntOrNull( 'year' );
- $month = $request->getIntOrNull( 'month' );
- $tagFilter = $request->getVal( 'tagfilter' );
- # Don't let the user get stuck with a certain date
- $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev';
- if( $skip ) {
- $year = '';
- $month = '';
- }
- # Use new list class to output results
- $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
- $this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month, $tagFilter );
- }
-
- /**
- * Is there at least one row?
- * @return bool
- */
- public function hasRows() {
- return isset($this->pager) ? ($this->pager->getNumRows() > 0) : false;
- }
-}
-
-/**
- * @deprecated
- * @ingroup SpecialPage
- */
-class LogViewer {
- const NO_ACTION_LINK = 1;
-
- /**
- * LogReader object
- */
- var $reader;
-
- /**
- * @param &$reader LogReader: where to get our data from
- * @param $flags Integer: Bitwise combination of flags:
- * LogEventsList::NO_ACTION_LINK Don't show restore/unblock/block links
- */
- function __construct( &$reader, $flags = 0 ) {
- wfDeprecated(__METHOD__);
- $this->reader =& $reader;
- $this->reader->pager->mLogEventsList->flags = $flags;
- # Aliases for shorter code...
- $this->pager =& $this->reader->pager;
- $this->list =& $this->reader->pager->mLogEventsList;
- }
-
- /**
- * 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( $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() );
- # Insert list
- $logBody = $this->pager->getBody();
- if( $logBody ) {
- $wgOut->addHTML(
- $this->pager->getNavigationBar() .
- $this->list->beginLogEventsList() .
- $logBody .
- $this->list->endLogEventsList() .
- $this->pager->getNavigationBar()
- );
- } else {
- $wgOut->addWikiMsg( 'logempty' );
- }
- }
-
- /**
- * 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 ) {
- $logBody = $this->pager->getBody();
- if( $logBody ) {
- $out->addHTML(
- $this->list->beginLogEventsList() .
- $logBody .
- $this->list->endLogEventsList()
- );
- } else {
- $out->addWikiMsg( 'logempty' );
- }
- }
-}
diff --git a/includes/LogPage.php b/includes/LogPage.php
index 8bd08dc4..5155d9a5 100644
--- a/includes/LogPage.php
+++ b/includes/LogPage.php
@@ -38,7 +38,18 @@ class LogPage {
const SUPPRESSED_USER = 12;
const SUPPRESSED_ACTION = 9;
/* @access private */
- var $type, $action, $comment, $params, $target, $doer;
+ var $type, $action, $comment, $params;
+
+ /**
+ * @var User
+ */
+ var $doer;
+
+ /**
+ * @var Title
+ */
+ var $target;
+
/* @acess public */
var $updateRecentChanges, $sendToUDP;
@@ -56,6 +67,9 @@ class LogPage {
$this->sendToUDP = ( $udp == 'UDP' );
}
+ /**
+ * @return bool|int|null
+ */
protected function saveContent() {
global $wgLogRestrictions;
@@ -193,26 +207,32 @@ class LogPage {
{
global $wgLang, $wgContLang, $wgLogActions;
+ if ( is_null( $skin ) ) {
+ $langObj = $wgContLang;
+ $langObjOrNull = null;
+ } else {
+ $langObj = $wgLang;
+ $langObjOrNull = $wgLang;
+ }
+
$key = "$type/$action";
# Defer patrol log to PatrolLog class
if( $key == 'patrol/patrol' ) {
- return PatrolLog::makeActionText( $title, $params, $skin );
+ return PatrolLog::makeActionText( $title, $params, $langObjOrNull );
}
if( isset( $wgLogActions[$key] ) ) {
if( is_null( $title ) ) {
- $rv = wfMsgHtml( $wgLogActions[$key] );
+ $rv = wfMsgExt( $wgLogActions[$key], array( 'parsemag', 'escape', 'language' => $langObj ) );
} else {
- $titleLink = self::getTitleLink( $type, $skin, $title, $params );
- if( $key == 'rights/rights' ) {
+ $titleLink = self::getTitleLink( $type, $langObjOrNull, $title, $params );
+ if( preg_match( '/^rights\/(rights|autopromote)/', $key ) ) {
+ $rightsnone = wfMsgExt( 'rightsnone', array( 'parsemag', 'language' => $langObj ) );
if( $skin ) {
- $rightsnone = wfMsg( 'rightsnone' );
foreach ( $params as &$param ) {
$groupArray = array_map( 'trim', explode( ',', $param ) );
$groupArray = array_map( array( 'User', 'getGroupMember' ), $groupArray );
$param = $wgLang->listToText( $groupArray );
}
- } else {
- $rightsnone = wfMsgForContent( 'rightsnone' );
}
if( !isset( $params[0] ) || trim( $params[0] ) == '' ) {
$params[0] = $rightsnone;
@@ -222,11 +242,7 @@ class LogPage {
}
}
if( count( $params ) == 0 ) {
- if ( $skin ) {
- $rv = wfMsgHtml( $wgLogActions[$key], $titleLink );
- } else {
- $rv = wfMsgExt( $wgLogActions[$key], array( 'parsemag', 'escape', 'replaceafter', 'content' ), $titleLink );
- }
+ $rv = wfMsgExt( $wgLogActions[$key], array( 'parsemag', 'escape', 'replaceafter', 'language' => $langObj ), $titleLink );
} else {
$details = '';
array_unshift( $params, $titleLink );
@@ -239,33 +255,25 @@ class LogPage {
$params[1] = $wgContLang->translateBlockExpiry( $params[1] );
}
$params[2] = isset( $params[2] ) ?
- self::formatBlockFlags( $params[2], is_null( $skin ) ) : '';
+ self::formatBlockFlags( $params[2], $langObj ) : '';
// Page protections
} elseif ( $type == 'protect' && count($params) == 3 ) {
// Restrictions and expiries
if( $skin ) {
- $details .= htmlspecialchars( " {$params[1]}" );
+ $details .= $wgLang->getDirMark() . htmlspecialchars( " {$params[1]}" );
} else {
$details .= " {$params[1]}";
}
// Cascading flag...
if( $params[2] ) {
- if ( $skin ) {
- $details .= ' [' . wfMsg( 'protect-summary-cascade' ) . ']';
- } else {
- $details .= ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']';
- }
+ $details .= ' [' . wfMsgExt( 'protect-summary-cascade', array( 'parsemag', 'language' => $langObj ) ) . ']';
}
// Page moves
} elseif ( $type == 'move' && count( $params ) == 3 ) {
if( $params[2] ) {
- if ( $skin ) {
- $details .= ' [' . wfMsg( 'move-redirect-suppressed' ) . ']';
- } else {
- $details .= ' [' . wfMsgForContent( 'move-redirect-suppressed' ) . ']';
- }
+ $details .= ' [' . wfMsgExt( 'move-redirect-suppressed', array( 'parsemag', 'language' => $langObj ) ) . ']';
}
// Revision deletion
@@ -273,21 +281,17 @@ class LogPage {
$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, is_null( $skin ) );
+ $details .= ': ' . RevisionDeleter::getLogMessage( $count, $nfield, $ofield, $langObj, false );
// Log deletion
} 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, is_null( $skin ) );
+ $details .= ': ' . RevisionDeleter::getLogMessage( $count, $nfield, $ofield, $langObj, true );
}
- if ( $skin ) {
- $rv = wfMsgHtml( $wgLogActions[$key], $params ) . $details;
- } else {
- $rv = wfMsgExt( $wgLogActions[$key], array( 'parsemag', 'escape', 'replaceafter', 'content' ), $params ) . $details;
- }
+ $rv = wfMsgExt( $wgLogActions[$key], array( 'parsemag', 'escape', 'replaceafter', 'language' => $langObj ), $params ) . $details;
}
}
} else {
@@ -318,14 +322,22 @@ class LogPage {
return $rv;
}
- protected static function getTitleLink( $type, $skin, $title, &$params ) {
- global $wgLang, $wgContLang, $wgUserrightsInterwikiDelimiter;
- if( !$skin ) {
+ /**
+ * TODO document
+ * @param $type String
+ * @param $lang Language or null
+ * @param $title Title
+ * @param $params Array
+ * @return String
+ */
+ protected static function getTitleLink( $type, $lang, $title, &$params ) {
+ global $wgContLang, $wgUserrightsInterwikiDelimiter;
+ if( !$lang ) {
return $title->getPrefixedText();
}
switch( $type ) {
case 'move':
- $titleLink = $skin->link(
+ $titleLink = Linker::link(
$title,
htmlspecialchars( $title->getPrefixedText() ),
array(),
@@ -336,7 +348,7 @@ class LogPage {
# Workaround for broken database
$params[0] = htmlspecialchars( $params[0] );
} else {
- $params[0] = $skin->link(
+ $params[0] = Linker::link(
$targetTitle,
htmlspecialchars( $params[0] )
);
@@ -349,8 +361,8 @@ class LogPage {
// TODO: Store the user identifier in the parameters
// to make this faster for future log entries
$id = User::idFromName( $title->getText() );
- $titleLink = $skin->userLink( $id, $title->getText() )
- . $skin->userToolLinks( $id, $title->getText(), false, Linker::TOOL_LINKS_NOBLOCK );
+ $titleLink = Linker::userLink( $id, $title->getText() )
+ . Linker::userToolLinks( $id, $title->getText(), false, Linker::TOOL_LINKS_NOBLOCK );
}
break;
case 'rights':
@@ -363,32 +375,32 @@ class LogPage {
break;
}
}
- $titleLink = $skin->link( Title::makeTitle( NS_USER, $text ) );
+ $titleLink = Linker::link( Title::makeTitle( NS_USER, $text ) );
break;
case 'merge':
- $titleLink = $skin->link(
+ $titleLink = Linker::link(
$title,
$title->getPrefixedText(),
array(),
array( 'redirect' => 'no' )
);
- $params[0] = $skin->link(
+ $params[0] = Linker::link(
Title::newFromText( $params[0] ),
htmlspecialchars( $params[0] )
);
- $params[1] = $wgLang->timeanddate( $params[1] );
+ $params[1] = $lang->timeanddate( $params[1] );
break;
default:
if( $title->getNamespace() == NS_SPECIAL ) {
- list( $name, $par ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() );
+ list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
# Use the language name for log titles, rather than Log/X
if( $name == 'Log' ) {
- $titleLink = '(' . $skin->link( $title, LogPage::logName( $par ) ) . ')';
+ $titleLink = '(' . Linker::link( $title, LogPage::logName( $par ) ) . ')';
} else {
- $titleLink = $skin->link( $title );
+ $titleLink = Linker::link( $title );
}
} else {
- $titleLink = $skin->link( $title );
+ $titleLink = Linker::link( $title );
}
}
return $titleLink;
@@ -485,19 +497,16 @@ class LogPage {
* into a more readable (and translated) form
*
* @param $flags Flags to format
- * @param $forContent Whether to localize the message depending of the user
- * language
+ * @param $lang Language object to use
* @return String
*/
- public static function formatBlockFlags( $flags, $forContent = false ) {
- global $wgLang;
-
+ public static function formatBlockFlags( $flags, $lang ) {
$flags = explode( ',', trim( $flags ) );
if( count( $flags ) > 0 ) {
for( $i = 0; $i < count( $flags ); $i++ ) {
- $flags[$i] = self::formatBlockFlag( $flags[$i], $forContent );
+ $flags[$i] = self::formatBlockFlag( $flags[$i], $lang );
}
- return '(' . $wgLang->commaList( $flags ) . ')';
+ return '(' . $lang->commaList( $flags ) . ')';
} else {
return '';
}
@@ -506,21 +515,19 @@ class LogPage {
/**
* Translate a block log flag if possible
*
- * @param $flag Flag to translate
- * @param $forContent Whether to localize the message depending of the user
- * language
+ * @param $flag int Flag to translate
+ * @param $lang Language object to use
* @return String
*/
- public static function formatBlockFlag( $flag, $forContent = false ) {
+ public static function formatBlockFlag( $flag, $lang ) {
static $messages = array();
if( !isset( $messages[$flag] ) ) {
- $k = 'block-log-flags-' . $flag;
- if( $forContent ) {
- $msg = wfMsgForContent( $k );
- } else {
- $msg = wfMsg( $k );
+ $messages[$flag] = htmlspecialchars( $flag ); // Fallback
+
+ $msg = wfMessage( 'block-log-flags-' . $flag )->inLanguage( $lang );
+ if ( $msg->exists() ) {
+ $messages[$flag] = $msg->escaped();
}
- $messages[$flag] = htmlspecialchars( wfEmptyMsg( $k, $msg ) ? $flag : $msg );
}
return $messages[$flag];
}
diff --git a/includes/MWFunction.php b/includes/MWFunction.php
new file mode 100644
index 00000000..53ce446e
--- /dev/null
+++ b/includes/MWFunction.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+class MWFunction {
+
+ protected static function cleanCallback( $callback ) {
+ if( is_string( $callback ) ) {
+ if ( strpos( $callback, '::' ) !== false ) {
+ // PHP 5.1 cannot use call_user_func( 'Class::Method' )
+ // It can only handle only call_user_func( array( 'Class', 'Method' ) )
+ $callback = explode( '::', $callback, 2);
+ }
+ }
+
+ if( count( $callback ) == 2 && $callback[0] == 'self' || $callback[0] == 'parent' ) {
+ throw new MWException( 'MWFunction cannot call self::method() or parent::method()' );
+ }
+
+ // Run autoloader (workaround for call_user_func_array bug: http://bugs.php.net/bug.php?id=51329)
+ is_callable( $callback );
+
+ return $callback;
+ }
+
+ public static function call( $callback ) {
+ $callback = self::cleanCallback( $callback );
+
+ $args = func_get_args();
+
+ return call_user_func_array( 'call_user_func', $args );
+ }
+
+ public static function callArray( $callback, $argsarams ) {
+ $callback = self::cleanCallback( $callback );
+ return call_user_func_array( $callback, $argsarams );
+ }
+
+ public static function newObj( $class, $args = array() ) {
+ if( !count( $args ) ) {
+ return new $class;
+ }
+
+ $ref = new ReflectionClass( $class );
+ return $ref->newInstanceArgs( $args );
+ }
+
+}
diff --git a/includes/MacBinary.php b/includes/MacBinary.php
deleted file mode 100644
index 0c38a641..00000000
--- a/includes/MacBinary.php
+++ /dev/null
@@ -1,272 +0,0 @@
-<?php
-/**
- * MacBinary signature checker and data fork extractor, for files
- * uploaded from Internet Explorer for Mac.
- *
- * Copyright (C) 2005 Brion Vibber <brion@pobox.com>
- * Portions based on Convert::BinHex by Eryq et al
- * 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
- *
- * @ingroup SpecialPage
- */
-
-class MacBinary {
- function __construct( $filename ) {
- $this->open( $filename );
- $this->loadHeader();
- }
-
- /**
- * The file must be seekable, such as local filesystem.
- * Remote URLs probably won't work.
- *
- * @param $filename String
- */
- function open( $filename ) {
- $this->valid = false;
- $this->version = 0;
- $this->filename = '';
- $this->dataLength = 0;
- $this->resourceLength = 0;
- $this->handle = fopen( $filename, 'rb' );
- }
-
- /**
- * Does this appear to be a valid MacBinary archive?
- *
- * @return Boolean
- */
- function isValid() {
- return $this->valid;
- }
-
- /**
- * Get length of data fork
- *
- * @return Integer
- */
- function dataForkLength() {
- return $this->dataLength;
- }
-
- /**
- * Copy the data fork to an external file or resource.
- *
- * @param $destination Ressource
- * @return Boolean
- */
- function extractData( $destination ) {
- if( !$this->isValid() ) {
- return false;
- }
-
- // Data fork appears immediately after header
- fseek( $this->handle, 128 );
- return $this->copyBytesTo( $destination, $this->dataLength );
- }
-
- /**
- *
- */
- function close() {
- fclose( $this->handle );
- }
-
- // --------------------------------------------------------------
-
- /**
- * Check if the given file appears to be MacBinary-encoded,
- * as Internet Explorer on Mac OS may provide for unknown types.
- * http://www.lazerware.com/formats/macbinary/macbinary_iii.html
- * If ok, load header data.
- *
- * @return bool
- * @access private
- */
- function loadHeader() {
- $fname = 'MacBinary::loadHeader';
-
- fseek( $this->handle, 0 );
- $head = fread( $this->handle, 128 );
- #$this->hexdump( $head );
-
- if( strlen( $head ) < 128 ) {
- wfDebug( "$fname: couldn't read full MacBinary header\n" );
- return false;
- }
-
- if( $head{0} != "\x00" || $head{74} != "\x00" ) {
- wfDebug( "$fname: header bytes 0 and 74 not null\n" );
- return false;
- }
-
- $signature = substr( $head, 102, 4 );
- $a = unpack( "ncrc", substr( $head, 124, 2 ) );
- $storedCRC = $a['crc'];
- $calculatedCRC = $this->calcCRC( substr( $head, 0, 124 ) );
- if( $storedCRC == $calculatedCRC ) {
- if( $signature == 'mBIN' ) {
- $this->version = 3;
- } else {
- $this->version = 2;
- }
- } else {
- $crc = sprintf( "%x != %x", $storedCRC, $calculatedCRC );
- if( $storedCRC == 0 && $head{82} == "\x00" &&
- substr( $head, 101, 24 ) == str_repeat( "\x00", 24 ) ) {
- wfDebug( "$fname: no CRC, looks like MacBinary I\n" );
- $this->version = 1;
- } elseif( $signature == 'mBIN' && $storedCRC == 0x185 ) {
- // Mac IE 5.0 seems to insert this value in the CRC field.
- // 5.2.3 works correctly; don't know about other versions.
- wfDebug( "$fname: CRC doesn't match ($crc), looks like Mac IE 5.0\n" );
- $this->version = 3;
- } else {
- wfDebug( "$fname: CRC doesn't match ($crc) and not MacBinary I\n" );
- return false;
- }
- }
-
- $nameLength = ord( $head{1} );
- if( $nameLength < 1 || $nameLength > 63 ) {
- wfDebug( "$fname: invalid filename size $nameLength\n" );
- return false;
- }
- $this->filename = substr( $head, 2, $nameLength );
-
- $forks = unpack( "Ndata/Nresource", substr( $head, 83, 8 ) );
- $this->dataLength = $forks['data'];
- $this->resourceLength = $forks['resource'];
- $maxForkLength = 0x7fffff;
-
- if( $this->dataLength < 0 || $this->dataLength > $maxForkLength ) {
- wfDebug( "$fname: invalid data fork length $this->dataLength\n" );
- return false;
- }
-
- if( $this->resourceLength < 0 || $this->resourceLength > $maxForkLength ) {
- wfDebug( "$fname: invalid resource fork size $this->resourceLength\n" );
- return false;
- }
-
- wfDebug( "$fname: appears to be MacBinary $this->version, data length $this->dataLength\n" );
- $this->valid = true;
- return true;
- }
-
- /**
- * Calculate a 16-bit CRC value as for MacBinary headers.
- * Adapted from perl5 Convert::BinHex by Eryq,
- * based on the mcvert utility (Doug Moore, April '87),
- * with magic array thingy by Jim Van Verth.
- * http://search.cpan.org/~eryq/Convert-BinHex-1.119/lib/Convert/BinHex.pm
- *
- * @param $data String
- * @param $seed Integer
- * @return Integer
- * @access private
- */
- function calcCRC( $data, $seed = 0 ) {
- # An array useful for CRC calculations that use 0x1021 as the "seed":
- $MAGIC = array(
- 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
- 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
- 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
- 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
- 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
- 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
- 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
- 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
- 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
- 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
- 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
- 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
- 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
- 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
- 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
- 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
- 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
- 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
- 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
- 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
- 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
- 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
- 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
- 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
- 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
- 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
- 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
- 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
- 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
- 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
- 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
- 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
- );
- $len = strlen( $data );
- $crc = $seed;
- for( $i = 0; $i < $len; $i++ ) {
- $crc ^= ord( $data{$i} ) << 8;
- $crc &= 0xFFFF;
- $crc = ($crc << 8) ^ $MAGIC[$crc >> 8];
- $crc &= 0xFFFF;
- }
- return $crc;
- }
-
- /**
- * @param $destination Resource
- * @param $bytesToCopy Integer
- * @return Boolean
- * @access private
- */
- function copyBytesTo( $destination, $bytesToCopy ) {
- $bufferSize = 65536;
- for( $remaining = $bytesToCopy; $remaining > 0; $remaining -= $bufferSize ) {
- $thisChunkSize = min( $remaining, $bufferSize );
- $buffer = fread( $this->handle, $thisChunkSize );
- fwrite( $destination, $buffer );
- }
- }
-
- /**
- * Hex dump of the header for debugging
- * @access private
- */
- function hexdump( $data ) {
- global $wgDebugLogFile;
- if( !$wgDebugLogFile ) return;
-
- $width = 16;
- $at = 0;
- for( $remaining = strlen( $data ); $remaining > 0; $remaining -= $width ) {
- $line = sprintf( "%04x:", $at );
- $printable = '';
- for( $i = 0; $i < $width && $remaining - $i > 0; $i++ ) {
- $byte = ord( $data{$at++} );
- $line .= sprintf( " %02x", $byte );
- $printable .= ($byte >= 32 && $byte <= 126 )
- ? chr( $byte )
- : '.';
- }
- if( $i < $width ) {
- $line .= str_repeat( ' ', $width - $i );
- }
- wfDebug( "MacBinary: $line $printable\n" );
- }
- }
-}
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index 31d83332..d1579380 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -30,9 +30,14 @@ class MagicWord {
/**#@+
* @private
*/
- var $mId, $mSynonyms, $mCaseSensitive, $mRegex;
- var $mRegexStart, $mBaseRegex, $mVariableRegex;
- var $mModified, $mFound;
+ var $mId, $mSynonyms, $mCaseSensitive;
+ var $mRegex = '';
+ var $mRegexStart = '';
+ var $mBaseRegex = '';
+ var $mVariableRegex = '';
+ var $mVariableStartToEndRegex = '';
+ var $mModified = false;
+ var $mFound = false;
static public $mVariableIDsInitialised = false;
static public $mVariableIDs = array(
@@ -182,30 +187,28 @@ class MagicWord {
$this->mId = $id;
$this->mSynonyms = (array)$syn;
$this->mCaseSensitive = $cs;
- $this->mRegex = '';
- $this->mRegexStart = '';
- $this->mVariableRegex = '';
- $this->mVariableStartToEndRegex = '';
- $this->mModified = false;
}
/**
* Factory: creates an object representing an ID
- * @static
+ *
+ * @param $id
+ *
+ * @return MagicWord
*/
static function &get( $id ) {
- wfProfileIn( __METHOD__ );
if ( !isset( self::$mObjects[$id] ) ) {
$mw = new MagicWord();
$mw->load( $id );
self::$mObjects[$id] = $mw;
}
- wfProfileOut( __METHOD__ );
return self::$mObjects[$id];
}
/**
* Get an array of parser variable IDs
+ *
+ * @return array
*/
static function getVariableIDs() {
if ( !self::$mVariableIDsInitialised ) {
@@ -230,16 +233,24 @@ class MagicWord {
return self::$mSubstIDs;
}
- /* Allow external reads of TTL array */
+ /**
+ * Allow external reads of TTL array
+ *
+ * @return array
+ */
static function getCacheTTL($id) {
- if (array_key_exists($id,self::$mCacheTTLs)) {
+ if ( array_key_exists( $id, self::$mCacheTTLs ) ) {
return self::$mCacheTTLs[$id];
} else {
return -1;
}
}
- /** Get a MagicWordArray of double-underscore entities */
+ /**
+ * Get a MagicWordArray of double-underscore entities
+ *
+ * @return MagicWordArray
+ */
static function getDoubleUnderscoreArray() {
if ( is_null( self::$mDoubleUnderscoreArray ) ) {
self::$mDoubleUnderscoreArray = new MagicWordArray( self::$mDoubleUnderscoreIDs );
@@ -255,9 +266,14 @@ class MagicWord {
self::$mObjects = array();
}
- # Initialises this object with an ID
+ /**
+ * Initialises this object with an ID
+ *
+ * @param $id
+ */
function load( $id ) {
global $wgContLang;
+ wfProfileIn( __METHOD__ );
$this->mId = $id;
$wgContLang->getMagic( $this );
if ( !$this->mSynonyms ) {
@@ -265,6 +281,7 @@ class MagicWord {
#throw new MWException( "Error: invalid magic word '$id'" );
wfDebugLog( 'exception', "Error: invalid magic word '$id'\n" );
}
+ wfProfileOut( __METHOD__ );
}
/**
@@ -295,6 +312,11 @@ 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.
+ *
+ * @param $s1 string
+ * @param $s2 string
+ *
+ * @return int
*/
function compareStringLength( $s1, $s2 ) {
$l1 = strlen( $s1 );
@@ -310,6 +332,8 @@ class MagicWord {
/**
* Gets a regex representing matching the word
+ *
+ * @return string
*/
function getRegex() {
if ($this->mRegex == '' ) {
@@ -322,6 +346,8 @@ class MagicWord {
* Gets the regexp case modifier to use, i.e. i or nothing, to be used if
* one is using MagicWord::getBaseRegex(), otherwise it'll be included in
* the complete expression
+ *
+ * @return string
*/
function getRegexCase() {
if ( $this->mRegex === '' )
@@ -332,6 +358,8 @@ class MagicWord {
/**
* Gets a regex matching the word, if it is at the string start
+ *
+ * @return string
*/
function getRegexStart() {
if ($this->mRegex == '' ) {
@@ -342,6 +370,8 @@ class MagicWord {
/**
* regex without the slashes and what not
+ *
+ * @return string
*/
function getBaseRegex() {
if ($this->mRegex == '') {
@@ -352,6 +382,9 @@ class MagicWord {
/**
* Returns true if the text contains the word
+ *
+ * @paran $text string
+ *
* @return bool
*/
function match( $text ) {
@@ -360,6 +393,9 @@ class MagicWord {
/**
* Returns true if the text starts with the word
+ *
+ * @param $text string
+ *
* @return bool
*/
function matchStart( $text ) {
@@ -371,6 +407,10 @@ class MagicWord {
* The return code is the matched string, if there's no variable
* part in the regex and the matched variable part ($1) if there
* is one.
+ *
+ * @param $text string
+ *
+ * @return string
*/
function matchVariableStartToEnd( $text ) {
$matches = array();
@@ -385,8 +425,11 @@ class MagicWord {
$matches = array_values(array_filter($matches));
- if ( count($matches) == 1 ) { return $matches[0]; }
- else { return $matches[1]; }
+ if ( count($matches) == 1 ) {
+ return $matches[0];
+ } else {
+ return $matches[1];
+ }
}
}
@@ -394,6 +437,10 @@ class MagicWord {
/**
* Returns true if the text matches the word, and alters the
* input string, removing all instances of the word
+ *
+ * @param $text string
+ *
+ * @return bool
*/
function matchAndRemove( &$text ) {
$this->mFound = false;
@@ -401,6 +448,10 @@ class MagicWord {
return $this->mFound;
}
+ /**
+ * @param $text
+ * @return bool
+ */
function matchStartAndRemove( &$text ) {
$this->mFound = false;
$text = preg_replace_callback( $this->getRegexStart(), array( &$this, 'pregRemoveAndRecord' ), $text );
@@ -409,17 +460,24 @@ class MagicWord {
/**
* Used in matchAndRemove()
- * @private
- **/
- function pregRemoveAndRecord( ) {
+ *
+ * @return string
+ */
+ function pregRemoveAndRecord() {
$this->mFound = true;
return '';
}
/**
* Replaces the word with something else
- */
- function replace( $replacement, $subject, $limit=-1 ) {
+ *
+ * @param $replacement
+ * @param $subject
+ * @param $limit int
+ *
+ * @return string
+ */
+ function replace( $replacement, $subject, $limit = -1 ) {
$res = preg_replace( $this->getRegex(), StringUtils::escapeRegexReplacement( $replacement ), $subject, $limit );
$this->mModified = !($res === $subject);
return $res;
@@ -429,6 +487,11 @@ class MagicWord {
* Variable handling: {{SUBST:xxx}} style words
* Calls back a function to determine what to replace xxx with
* Input word must contain $1
+ *
+ * @param $text string
+ * @param $callback
+ *
+ * @return string
*/
function substituteCallback( $text, $callback ) {
$res = preg_replace_callback( $this->getVariableRegex(), $callback, $text );
@@ -438,6 +501,8 @@ class MagicWord {
/**
* Matches the word, where $1 is a wildcard
+ *
+ * @return string
*/
function getVariableRegex() {
if ( $this->mVariableRegex == '' ) {
@@ -448,6 +513,8 @@ class MagicWord {
/**
* Matches the entire string, where $1 is a wildcard
+ *
+ * @return string
*/
function getVariableStartToEndRegex() {
if ( $this->mVariableStartToEndRegex == '' ) {
@@ -458,11 +525,18 @@ class MagicWord {
/**
* Accesses the synonym list directly
+ *
+ * @param $i int
+ *
+ * @return string
*/
function getSynonym( $i ) {
return $this->mSynonyms[$i];
}
+ /**
+ * @return array
+ */
function getSynonyms() {
return $this->mSynonyms;
}
@@ -470,6 +544,8 @@ class MagicWord {
/**
* Returns true if the last call to replace() or substituteCallback()
* returned a modified text, otherwise false.
+ *
+ * @return bool
*/
function getWasModified(){
return $this->mModified;
@@ -480,8 +556,14 @@ class MagicWord {
* This method uses the php feature to do several replacements at the same time,
* thereby gaining some efficiency. The result is placed in the out variable
* $result. The return value is true if something was replaced.
- * @static
- **/
+ * @todo Should this be static? It doesn't seem to be used at all
+ *
+ * @param $magicarr
+ * @param $subject
+ * @param $result
+ *
+ * @return bool
+ */
function replaceMultiple( $magicarr, $subject, &$result ){
$search = array();
$replace = array();
@@ -498,6 +580,9 @@ class MagicWord {
/**
* Adds all the synonyms of this MagicWord to an array, to allow quick
* lookup in a list of magic words
+ *
+ * @param $array
+ * @param $value
*/
function addToArray( &$array, $value ) {
global $wgContLang;
@@ -506,10 +591,16 @@ class MagicWord {
}
}
+ /**
+ * @return bool
+ */
function isCaseSensitive() {
return $this->mCaseSensitive;
}
+ /**
+ * @return int
+ */
function getId() {
return $this->mId;
}
@@ -531,6 +622,8 @@ class MagicWordArray {
/**
* Add a magic word by name
+ *
+ * @param $name string
*/
public function add( $name ) {
$this->names[] = $name;
@@ -539,6 +632,8 @@ class MagicWordArray {
/**
* Add a number of magic words by name
+ *
+ * $param $names array
*/
public function addArray( $names ) {
$this->names = array_merge( $this->names, array_values( $names ) );
@@ -607,6 +702,8 @@ class MagicWordArray {
/**
* Get a regex for matching variables with parameters
+ *
+ * @return string
*/
function getVariableRegex() {
return str_replace( "\\$1", "(.*?)", $this->getRegex() );
@@ -614,6 +711,8 @@ class MagicWordArray {
/**
* Get a regex anchored to the start of the string that does not match parameters
+ *
+ * @return string
*/
function getRegexStart() {
$base = $this->getBaseRegex();
@@ -629,6 +728,8 @@ class MagicWordArray {
/**
* Get an anchored regex for matching variables with parameters
+ *
+ * @return string
*/
function getVariableStartToEndRegex() {
$base = $this->getBaseRegex();
@@ -646,6 +747,10 @@ class MagicWordArray {
* Parse a match array from preg_match
* Returns array(magic word ID, parameter value)
* If there is no parameter value, that element will be false.
+ *
+ * @param $m arrray
+ *
+ * @return array
*/
function parseMatch( $m ) {
reset( $m );
@@ -672,6 +777,10 @@ class MagicWordArray {
* Returns an array with the magic word name in the first element and the
* parameter in the second element.
* Both elements are false if there was no match.
+ *
+ * @param $text string
+ *
+ * @return array
*/
public function matchVariableStartToEnd( $text ) {
$regexes = $this->getVariableStartToEndRegex();
@@ -689,6 +798,10 @@ class MagicWordArray {
/**
* Match some text, without parameter capture
* Returns the magic word name, or false if there was no capture
+ *
+ * @param $text string
+ *
+ * @return string|false
*/
public function matchStartToEnd( $text ) {
$hash = $this->getHash();
@@ -706,6 +819,10 @@ class MagicWordArray {
/**
* Returns an associative array, ID => param value, for all items that match
* Removes the matched items from the input string (passed by reference)
+ *
+ * @param $text string
+ *
+ * @return array
*/
public function matchAndRemove( &$text ) {
$found = array();
@@ -729,6 +846,10 @@ class MagicWordArray {
* the prefix from $text.
* Return false if no match found and $text is not modified.
* Does not match parameters.
+ *
+ * @param $text string
+ *
+ * @return int|false
*/
public function matchStartAndRemove( &$text ) {
$regexes = $this->getRegexStart();
diff --git a/includes/Math.php b/includes/Math.php
deleted file mode 100644
index 0e136d5f..00000000
--- a/includes/Math.php
+++ /dev/null
@@ -1,341 +0,0 @@
-<?php
-/**
- * Contain everything related to <math> </math> parsing
- * @file
- * @ingroup Parser
- */
-
-/**
- * Takes LaTeX fragments, sends them to a helper program (texvc) for rendering
- * to rasterized PNG and HTML and MathML approximations. An appropriate
- * rendering form is picked and returned.
- *
- * @author Tomasz Wegrzanowski, with additions by Brion Vibber (2003, 2004)
- * @ingroup Parser
- */
-class MathRenderer {
- var $mode = MW_MATH_MODERN;
- var $tex = '';
- var $inputhash = '';
- var $hash = '';
- var $html = '';
- var $mathml = '';
- var $conservativeness = 0;
-
- function __construct( $tex, $params=array() ) {
- $this->tex = $tex;
- $this->params = $params;
- }
-
- function setOutputMode( $mode ) {
- $this->mode = $mode;
- }
-
- function render() {
- global $wgTmpDirectory, $wgInputEncoding;
- global $wgTexvc, $wgMathCheckFiles, $wgTexvcBackgroundColor;
-
- if( $this->mode == MW_MATH_SOURCE ) {
- # No need to render or parse anything more!
- # 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
- }
-
- if( !$this->_recall() ) {
- if( $wgMathCheckFiles ) {
- # Ensure that the temp and output directories are available before continuing...
- if( !file_exists( $wgTmpDirectory ) ) {
- if( !wfMkdirParents( $wgTmpDirectory ) ) {
- return $this->_error( 'math_bad_tmpdir' );
- }
- } elseif( !is_dir( $wgTmpDirectory ) || !is_writable( $wgTmpDirectory ) ) {
- return $this->_error( 'math_bad_tmpdir' );
- }
- }
-
- if( !is_executable( $wgTexvc ) ) {
- return $this->_error( 'math_notexvc' );
- }
- $cmd = $wgTexvc . ' ' .
- escapeshellarg( $wgTmpDirectory ).' '.
- escapeshellarg( $wgTmpDirectory ).' '.
- escapeshellarg( $this->tex ).' '.
- escapeshellarg( $wgInputEncoding ).' '.
- escapeshellarg( $wgTexvcBackgroundColor );
-
- if ( wfIsWindows() ) {
- # Invoke it within cygwin sh, because texvc expects sh features in its default shell
- $cmd = 'sh -c ' . wfEscapeShellArg( $cmd );
- }
-
- wfDebug( "TeX: $cmd\n" );
- $contents = wfShellExec( $cmd );
- wfDebug( "TeX output:\n $contents\n---\n" );
-
- if (strlen($contents) == 0) {
- return $this->_error( 'math_unknown_error' );
- }
-
- $retval = substr ($contents, 0, 1);
- $errmsg = '';
- if (($retval == 'C') || ($retval == 'M') || ($retval == 'L')) {
- if ($retval == 'C') {
- $this->conservativeness = 2;
- } else if ($retval == 'M') {
- $this->conservativeness = 1;
- } else {
- $this->conservativeness = 0;
- }
- $outdata = substr ($contents, 33);
-
- $i = strpos($outdata, "\000");
-
- $this->html = substr($outdata, 0, $i);
- $this->mathml = substr($outdata, $i+1);
- } else if (($retval == 'c') || ($retval == 'm') || ($retval == 'l')) {
- $this->html = substr ($contents, 33);
- if ($retval == 'c') {
- $this->conservativeness = 2;
- } else if ($retval == 'm') {
- $this->conservativeness = 1;
- } else {
- $this->conservativeness = 0;
- }
- $this->mathml = null;
- } else if ($retval == 'X') {
- $this->html = null;
- $this->mathml = substr ($contents, 33);
- $this->conservativeness = 0;
- } else if ($retval == '+') {
- $this->html = null;
- $this->mathml = null;
- $this->conservativeness = 0;
- } else {
- $errbit = htmlspecialchars( substr($contents, 1) );
- switch( $retval ) {
- case 'E':
- $errmsg = $this->_error( 'math_lexing_error', $errbit );
- break;
- case 'S':
- $errmsg = $this->_error( 'math_syntax_error', $errbit );
- break;
- case 'F':
- $errmsg = $this->_error( 'math_unknown_function', $errbit );
- break;
- default:
- $errmsg = $this->_error( 'math_unknown_error', $errbit );
- }
- }
-
- if ( !$errmsg ) {
- $this->hash = substr ($contents, 1, 32);
- }
-
- wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) );
-
- if ( $errmsg ) {
- return $errmsg;
- }
-
- if (!preg_match("/^[a-f0-9]{32}$/", $this->hash)) {
- return $this->_error( 'math_unknown_error' );
- }
-
- if( !file_exists( "$wgTmpDirectory/{$this->hash}.png" ) ) {
- return $this->_error( 'math_image_error' );
- }
-
- if( filesize( "$wgTmpDirectory/{$this->hash}.png" ) == 0 ) {
- return $this->_error( 'math_image_error' );
- }
-
- $hashpath = $this->_getHashPath();
- if( !file_exists( $hashpath ) ) {
- wfSuppressWarnings();
- $ret = wfMkdirParents( $hashpath, 0755 );
- wfRestoreWarnings();
- if( !$ret ) {
- return $this->_error( 'math_bad_output' );
- }
- } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
- return $this->_error( 'math_bad_output' );
- }
-
- if( !rename( "$wgTmpDirectory/{$this->hash}.png", "$hashpath/{$this->hash}.png" ) ) {
- return $this->_error( 'math_output_error' );
- }
-
- # Now save it back to the DB:
- if ( !wfReadOnly() ) {
- $outmd5_sql = pack('H32', $this->hash);
-
- $md5_sql = pack('H32', $this->md5); # Binary packed, not hex
-
- $dbw = wfGetDB( DB_MASTER );
- $dbw->replace( 'math', array( 'math_inputhash' ),
- array(
- 'math_inputhash' => $dbw->encodeBlob($md5_sql),
- 'math_outputhash' => $dbw->encodeBlob($outmd5_sql),
- 'math_html_conservativeness' => $this->conservativeness,
- 'math_html' => $this->html,
- 'math_mathml' => $this->mathml,
- ), __METHOD__
- );
- }
-
- // If we're replacing an older version of the image, make sure it's current.
- global $wgUseSquid;
- if ( $wgUseSquid ) {
- $urls = array( $this->_mathImageUrl() );
- $u = new SquidUpdate( $urls );
- $u->doUpdate();
- }
- }
-
- return $this->_doRender();
- }
-
- function _error( $msg, $append = '' ) {
- $mf = htmlspecialchars( wfMsg( 'math_failure' ) );
- $errmsg = htmlspecialchars( wfMsg( $msg ) );
- $source = htmlspecialchars( str_replace( "\n", ' ', $this->tex ) );
- return "<strong class='error'>$mf ($errmsg$append): $source</strong>\n";
- }
-
- function _recall() {
- global $wgMathDirectory, $wgMathCheckFiles;
-
- $this->md5 = md5( $this->tex );
- $dbr = wfGetDB( DB_SLAVE );
- $rpage = $dbr->selectRow( 'math',
- array( 'math_outputhash','math_html_conservativeness','math_html','math_mathml' ),
- array( 'math_inputhash' => $dbr->encodeBlob(pack("H32", $this->md5))), # Binary packed, not hex
- __METHOD__
- );
-
- if( $rpage !== false ) {
- # Tailing 0x20s can get dropped by the database, add it back on if necessary:
- $xhash = unpack( 'H32md5', $dbr->decodeBlob($rpage->math_outputhash) . " " );
- $this->hash = $xhash ['md5'];
-
- $this->conservativeness = $rpage->math_html_conservativeness;
- $this->html = $rpage->math_html;
- $this->mathml = $rpage->math_mathml;
-
- $filename = $this->_getHashPath() . "/{$this->hash}.png";
-
- if( !$wgMathCheckFiles ) {
- // Short-circuit the file existence & migration checks
- return true;
- }
-
- if( file_exists( $filename ) ) {
- if( filesize( $filename ) == 0 ) {
- // Some horrible error corrupted stuff :(
- wfSuppressWarnings();
- unlink( $filename );
- wfRestoreWarnings();
- } else {
- return true;
- }
- }
-
- if( file_exists( $wgMathDirectory . "/{$this->hash}.png" ) ) {
- $hashpath = $this->_getHashPath();
-
- if( !file_exists( $hashpath ) ) {
- wfSuppressWarnings();
- $ret = wfMkdirParents( $hashpath, 0755 );
- wfRestoreWarnings();
- if( !$ret ) {
- return false;
- }
- } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
- return false;
- }
- if ( function_exists( "link" ) ) {
- return link ( $wgMathDirectory . "/{$this->hash}.png",
- $hashpath . "/{$this->hash}.png" );
- } else {
- return rename ( $wgMathDirectory . "/{$this->hash}.png",
- $hashpath . "/{$this->hash}.png" );
- }
- }
-
- }
-
- # Missing from the database and/or the render cache
- return false;
- }
-
- /**
- * Select among PNG, HTML, or MathML output depending on
- */
- function _doRender() {
- if( $this->mode == MW_MATH_MATHML && $this->mathml != '' ) {
- return Xml::tags( 'math',
- $this->_attribs( 'math',
- array( 'xmlns' => 'http://www.w3.org/1998/Math/MathML' ) ),
- $this->mathml );
- }
- if (($this->mode == MW_MATH_PNG) || ($this->html == '') ||
- (($this->mode == MW_MATH_SIMPLE) && ($this->conservativeness != 2)) ||
- (($this->mode == MW_MATH_MODERN || $this->mode == MW_MATH_MATHML) && ($this->conservativeness == 0))) {
- return $this->_linkToMathImage();
- } else {
- return Xml::tags( 'span',
- $this->_attribs( 'span',
- array( 'class' => 'texhtml' ) ),
- $this->html );
- }
- }
-
- function _attribs( $tag, $defaults=array(), $overrides=array() ) {
- $attribs = Sanitizer::validateTagAttributes( $this->params, $tag );
- $attribs = Sanitizer::mergeAttributes( $defaults, $attribs );
- $attribs = Sanitizer::mergeAttributes( $attribs, $overrides );
- return $attribs;
- }
-
- function _linkToMathImage() {
- $url = $this->_mathImageUrl();
-
- return Xml::element( 'img',
- $this->_attribs(
- 'img',
- array(
- 'class' => 'tex',
- 'alt' => $this->tex ),
- array(
- 'src' => $url ) ) );
- }
-
- function _mathImageUrl() {
- global $wgMathPath;
- $dir = $this->_getHashSubPath();
- return "$wgMathPath/$dir/{$this->hash}.png";
- }
-
- function _getHashPath() {
- global $wgMathDirectory;
- $path = $wgMathDirectory .'/' . $this->_getHashSubPath();
- wfDebug( "TeX: getHashPath, hash is: $this->hash, path is: $path\n" );
- return $path;
- }
-
- function _getHashSubPath() {
- return substr($this->hash, 0, 1)
- .'/'. substr($this->hash, 1, 1)
- .'/'. substr($this->hash, 2, 1);
- }
-
- public static function renderMath( $tex, $params=array(), ParserOptions $parserOptions = null ) {
- $math = new MathRenderer( $tex, $params );
- if ( $parserOptions )
- $math->setOutputMode( $parserOptions->getMath() );
- return $math->render();
- }
-}
diff --git a/includes/Message.php b/includes/Message.php
index 72232ec9..87349ff5 100644
--- a/includes/Message.php
+++ b/includes/Message.php
@@ -48,9 +48,8 @@
* $escaped = wfMessage( 'key' )->rawParams( 'apple' )->escaped();
* </pre>
*
- * TODO:
+ * @todo
* - test, can we have tests?
- * - sort out the details marked with fixme
*
* @since 1.17
* @author Niklas Laxström
@@ -65,6 +64,8 @@ class Message {
/**
* In which language to get this message. Overrides the $interface
* variable.
+ *
+ * @var Language
*/
protected $language = null;
@@ -95,8 +96,13 @@ class Message {
protected $useDatabase = true;
/**
+ * Title object to use as context
+ */
+ protected $title = null;
+
+ /**
* Constructor.
- * @param $key String: message key
+ * @param $key: message key, or array of message keys to try and use the first non-empty message for
* @param $params Array message parameters
* @return Message: $this
*/
@@ -122,12 +128,37 @@ class Message {
}
/**
+ * Factory function accepting multiple message keys and returning a message instance
+ * for the first message which is non-empty. If all messages are empty then an
+ * instance of the first message key is returned.
+ * @param Varargs: message keys
+ * @return Message: $this
+ */
+ public static function newFallbackSequence( /*...*/ ) {
+ $keys = func_get_args();
+ if ( func_num_args() == 1 ) {
+ if ( is_array($keys[0]) ) {
+ // Allow an array to be passed as the first argument instead
+ $keys = array_values($keys[0]);
+ } else {
+ // Optimize a single string to not need special fallback handling
+ $keys = $keys[0];
+ }
+ }
+ return new self( $keys );
+ }
+
+ /**
* 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() );
+ $args = func_get_args();
+ if ( isset( $args[0] ) && is_array( $args[0] ) ) {
+ $args = $args[0];
+ }
+ $args_values = array_values( $args );
$this->parameters = array_merge( $this->parameters, $args_values );
return $this;
}
@@ -142,6 +173,9 @@ class Message {
*/
public function rawParams( /*...*/ ) {
$params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
foreach( $params as $param ) {
$this->parameters[] = self::rawParam( $param );
}
@@ -149,6 +183,23 @@ class Message {
}
/**
+ * Add parameters that are numeric and will be passed through
+ * Language::formatNum before substitution
+ * @param Varargs: numeric parameters
+ * @return Message: $this
+ */
+ public function numParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach( $params as $param ) {
+ $this->parameters[] = self::numParam( $param );
+ }
+ return $this;
+ }
+
+ /**
* Request the message in any language that is supported.
* As a side effect interface message status is unconditionally
* turned off.
@@ -156,7 +207,7 @@ class Message {
* @return Message: $this
*/
public function inLanguage( $lang ) {
- if( $lang instanceof Language ){
+ if ( $lang instanceof Language || $lang instanceof StubUserLang ) {
$this->language = $lang;
} elseif ( is_string( $lang ) ) {
if( $this->language->getCode() != $lang ) {
@@ -173,10 +224,17 @@ class Message {
}
/**
- * Request the message in the wiki's content language.
+ * Request the message in the wiki's content language,
+ * unless it is disabled for this message.
+ * @see $wgForceUIMsgAsContentMsg
* @return Message: $this
*/
public function inContentLanguage() {
+ global $wgForceUIMsgAsContentMsg;
+ if ( in_array( $this->key, (array)$wgForceUIMsgAsContentMsg ) ) {
+ return $this;
+ }
+
global $wgContLang;
$this->interface = false;
$this->language = $wgContLang;
@@ -194,13 +252,18 @@ class Message {
}
/**
+ * Set the Title object to use as context when transforming the message
+ *
+ * @param $title Title object
+ * @return Message: $this
+ */
+ public function title( $title ) {
+ $this->title = $title;
+ 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() {
@@ -221,9 +284,8 @@ class Message {
} elseif( $this->format === 'text' ){
$string = $this->transformText( $string );
} elseif( $this->format === 'escaped' ){
- # FIXME: Sanitizer method here?
$string = $this->transformText( $string );
- $string = htmlspecialchars( $string );
+ $string = htmlspecialchars( $string, ENT_QUOTES, 'UTF-8', false );
}
# Raw parameter replacement
@@ -231,6 +293,16 @@ class Message {
return $string;
}
+
+ /**
+ * Magic method implementation of the above (for PHP >= 5.2.0), so we can do, eg:
+ * $foo = Message::get($key);
+ * $string = "<abbr>$foo</abbr>";
+ * @return String
+ */
+ public function __toString() {
+ return $this->toString();
+ }
/**
* Fully parse the text from wikitext to HTML
@@ -286,23 +358,53 @@ class Message {
return $this->fetchMessage() !== false;
}
+ /**
+ * Check whether a message does not exist, or is an empty string
+ * @return Bool: true if is is and false if not
+ * @todo FIXME: Merge with isDisabled()?
+ */
+ public function isBlank() {
+ $message = $this->fetchMessage();
+ return $message === false || $message === '';
+ }
+
+ /**
+ * Check whether a message does not exist, is an empty string, or is "-"
+ * @return Bool: true if is is and false if not
+ */
+ public function isDisabled() {
+ $message = $this->fetchMessage();
+ return $message === false || $message === '' || $message === '-';
+ }
+
+ /**
+ * @param $value
+ * @return array
+ */
public static function rawParam( $value ) {
return array( 'raw' => $value );
}
/**
+ * @param $value
+ * @return array
+ */
+ public static function numParam( $value ) {
+ return array( 'num' => $value );
+ }
+
+ /**
* Substitutes any paramaters into the message text.
- * @param $message String, 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'];
+ list( $paramType, $value ) = $this->extractParam( $param );
+ if ( $type === $paramType ) {
+ $replacementKeys['$' . ($n + 1)] = $value;
}
}
$message = strtr( $message, $replacementKeys );
@@ -310,27 +412,41 @@ class Message {
}
/**
+ * Extracts the parameter type and preprocessed the value if needed.
+ * @param $param String|Array: Parameter as defined in this class.
+ * @return Tuple(type, value)
+ * @throws MWException
+ */
+ protected function extractParam( $param ) {
+ if ( is_array( $param ) && isset( $param['raw'] ) ) {
+ return array( 'after', $param['raw'] );
+ } elseif ( is_array( $param ) && isset( $param['num'] ) ) {
+ // Replace number params always in before step for now.
+ // No support for combined raw and num params
+ return array( 'before', $this->language->formatNum( $param['num'] ) );
+ } elseif ( !is_array( $param ) ) {
+ return array( 'before', $param );
+ } else {
+ throw new MWException( "Invalid message parameter" );
+ }
+ }
+
+ /**
* Wrapper for what ever method we use to parse wikitext.
* @param $string String: Wikitext message contents
- * @return Wikitext parsed into HTML
+ * @return string 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 );
+ return MessageCache::singleton()->parse( $string, $this->title, /*linestart*/true, $this->interface, $this->language )->getText();
}
/**
* Wrapper for what ever method we use to {{-transform wikitext.
* @param $string String: Wikitext message contents
- * @return Wikitext with {{-constructs replaced with their values.
+ * @return string Wikitext with {{-constructs replaced with their values.
*/
protected function transformText( $string ) {
- global $wgMessageCache;
- return $wgMessageCache->transform( $string, $this->interface, $this->language );
+ return MessageCache::singleton()->transform( $string, $this->interface, $this->language, $this->title );
}
/**
@@ -340,7 +456,7 @@ class Message {
protected function getMessageText() {
$message = $this->fetchMessage();
if ( $message === false ) {
- return '&lt;' . htmlspecialchars( $this->key ) . '&gt;';
+ return '&lt;' . htmlspecialchars( is_array($this->key) ? $this->key[0] : $this->key ) . '&gt;';
} else {
return $message;
}
@@ -348,13 +464,25 @@ class Message {
/**
* Wrapper for what ever method we use to get message contents
+ *
+ * @return string
*/
protected function fetchMessage() {
if ( !isset( $this->message ) ) {
- global $wgMessageCache;
- $this->message = $wgMessageCache->get( $this->key, $this->useDatabase, $this->language );
+ $cache = MessageCache::singleton();
+ if ( is_array($this->key) ) {
+ foreach ( $this->key as $key ) {
+ $message = $cache->get( $key, $this->useDatabase, $this->language );
+ if ( $message !== false && $message !== '' ) {
+ break;
+ }
+ }
+ $this->message = $message;
+ } else {
+ $this->message = $cache->get( $this->key, $this->useDatabase, $this->language );
+ }
}
return $this->message;
}
-} \ No newline at end of file
+}
diff --git a/includes/MessageBlobStore.php b/includes/MessageBlobStore.php
index 5e6c8e5e..be6b27c9 100644
--- a/includes/MessageBlobStore.php
+++ b/includes/MessageBlobStore.php
@@ -117,49 +117,37 @@ class MessageBlobStore {
}
/**
- * Update all message blobs for a given module.
+ * Update the message blob for a given module in a given language
*
* @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.
+ * @param $lang String: language code
+ * @return String Regenerated message blob, or null if there was no blob for the given module/language pair
*/
- public static function updateModule( $name, ResourceLoaderModule $module, $lang = null ) {
- $retval = null;
-
- // Find all existing blobs for this module
+ public static function updateModule( $name, ResourceLoaderModule $module, $lang ) {
$dbw = wfGetDB( DB_MASTER );
- $res = $dbw->select( 'msg_resource',
- array( 'mr_lang', 'mr_blob' ),
- array( 'mr_resource' => $name ),
+ $row = $dbw->selectRow( 'msg_resource', 'mr_blob',
+ array( 'mr_resource' => $name, 'mr_lang' => $lang ),
__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
- );
+ if ( !$row ) {
+ return null;
}
+ // Save the old and new blobs for later
+ $oldBlob = $row->mr_blob;
+ $newBlob = self::generateMessageBlob( $module, $lang );
+
+ $newRow = array(
+ 'mr_resource' => $name,
+ 'mr_lang' => $lang,
+ 'mr_blob' => $newBlob,
+ 'mr_timestamp' => $dbw->timestamp()
+ );
+
$dbw->replace( 'msg_resource',
array( array( 'mr_resource', 'mr_lang' ) ),
- $newRows, __METHOD__
+ $newRow, __METHOD__
);
// Figure out which messages were added and removed
@@ -192,7 +180,7 @@ class MessageBlobStore {
);
}
- return $retval;
+ return $newBlob;
}
/**
@@ -340,7 +328,7 @@ class MessageBlobStore {
}
// 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() ||
+ if ( array_keys( FormatJson::decode( $row->mr_blob, true ) ) !== array_values( array_unique( $module->getMessages() ) ) ||
wfTimestamp( TS_MW, $row->mr_timestamp ) <= $wgCacheEpoch ) {
$retval[$row->mr_resource] = self::updateModule( $row->mr_resource, $module, $lang );
} else {
diff --git a/includes/Metadata.php b/includes/Metadata.php
index 93ce4b27..2e4ab94c 100644
--- a/includes/Metadata.php
+++ b/includes/Metadata.php
@@ -1,6 +1,5 @@
<?php
/**
- * Provides DublinCore and CreativeCommons metadata
*
* Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
*
@@ -29,15 +28,12 @@ abstract class RdfMetaData {
* Constructor
* @param $article Article object
*/
- public function __construct( Article $article ) {
+ public function __construct( Page $article ) {
$this->mArticle = $article;
}
public abstract function show();
- /**
- *
- */
protected function setup() {
global $wgOut, $wgRequest;
@@ -55,9 +51,6 @@ abstract class RdfMetaData {
}
}
- /**
- *
- */
protected function reallyFullUrl() {
return $this->mArticle->getTitle()->getFullURL();
}
@@ -65,7 +58,7 @@ abstract class RdfMetaData {
protected function basics() {
global $wgLanguageCode, $wgSitename;
- $this->element( 'title', $this->mArticle->mTitle->getText() );
+ $this->element( 'title', $this->mArticle->getTitle()->getText() );
$this->pageOrString( 'publisher', wfMsg( 'aboutpage' ), $wgSitename );
$this->element( 'language', $wgLanguageCode );
$this->element( 'type', 'Text' );
@@ -95,10 +88,11 @@ abstract class RdfMetaData {
}
protected function pageOrString( $name, $page, $str ) {
- if( $page instanceof Title )
+ if( $page instanceof Title ) {
$nt = $page;
- else
+ } else {
$nt = Title::newFromText( $page );
+ }
if( !$nt || $nt->getArticleID() == 0 ){
$this->element( $name, $str );
@@ -107,6 +101,10 @@ abstract class RdfMetaData {
}
}
+ /**
+ * @param $name string
+ * @param $title Title
+ */
protected function page( $name, $title ) {
$this->url( $name, $title->getFullUrl() );
}
@@ -140,9 +138,9 @@ abstract class RdfMetaData {
if( $wgRightsPage && ( $nt = Title::newFromText( $wgRightsPage ) )
&& ($nt->getArticleID() != 0)) {
$this->page('rights', $nt);
- } else if( $wgRightsUrl ){
+ } elseif( $wgRightsUrl ){
$this->url('rights', $wgRightsUrl);
- } else if( $wgRightsText ){
+ } elseif( $wgRightsText ){
$this->element( 'rights', $wgRightsText );
}
}
@@ -198,125 +196,3 @@ abstract class RdfMetaData {
}
}
-class DublinCoreRdf extends RdfMetaData {
-
- public function show(){
- if( $this->setup() ){
- $this->prologue();
- $this->basics();
- $this->epilogue();
- }
- }
-
- /**
- * begin of the page
- */
- protected function prologue() {
- global $wgOutputEncoding;
-
- $url = htmlspecialchars( $this->reallyFullUrl() );
- print <<<PROLOGUE
-<?xml version="1.0" encoding="{$wgOutputEncoding}" ?>
-<!DOCTYPE rdf:RDF PUBLIC "-//DUBLIN CORE//DCMES DTD 2002/07/31//EN" "http://dublincore.org/documents/2002/07/31/dcmes-xml/dcmes-xml-dtd.dtd">
-<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:dc="http://purl.org/dc/elements/1.1/">
- <rdf:Description rdf:about="{$url}">
-
-PROLOGUE;
- }
-
- /**
- * end of the page
- */
- protected function epilogue() {
- print <<<EPILOGUE
- </rdf:Description>
-</rdf:RDF>
-EPILOGUE;
- }
-}
-
-class CreativeCommonsRdf extends RdfMetaData {
-
- public function show(){
- if( $this->setup() ){
- global $wgRightsUrl;
-
- $url = $this->reallyFullUrl();
-
- $this->prologue();
- $this->subPrologue('Work', $url);
-
- $this->basics();
- if( $wgRightsUrl ){
- $url = htmlspecialchars( $wgRightsUrl );
- print "\t\t<cc:license rdf:resource=\"$url\" />\n";
- }
-
- $this->subEpilogue('Work');
-
- if( $wgRightsUrl ){
- $terms = $this->getTerms( $wgRightsUrl );
- if( $terms ){
- $this->subPrologue( 'License', $wgRightsUrl );
- $this->license( $terms );
- $this->subEpilogue( 'License' );
- }
- }
- }
-
- $this->epilogue();
- }
-
- protected function prologue() {
- global $wgOutputEncoding;
- echo <<<PROLOGUE
-<?xml version='1.0' encoding="{$wgOutputEncoding}" ?>
-<rdf:RDF xmlns:cc="http://web.resource.org/cc/"
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
-
-PROLOGUE;
- }
-
- protected function subPrologue( $type, $url ){
- $url = htmlspecialchars( $url );
- echo "\t<cc:{$type} rdf:about=\"{$url}\">\n";
- }
-
- protected function subEpilogue($type) {
- echo "\t</cc:{$type}>\n";
- }
-
- protected function license($terms) {
-
- foreach( $terms as $term ){
- switch( $term ) {
- case 're':
- $this->term('permits', 'Reproduction'); break;
- case 'di':
- $this->term('permits', 'Distribution'); break;
- case 'de':
- $this->term('permits', 'DerivativeWorks'); break;
- case 'nc':
- $this->term('prohibits', 'CommercialUse'); break;
- case 'no':
- $this->term('requires', 'Notice'); break;
- case 'by':
- $this->term('requires', 'Attribution'); break;
- case 'sa':
- $this->term('requires', 'ShareAlike'); break;
- case 'sc':
- $this->term('requires', 'SourceCode'); break;
- }
- }
- }
-
- protected function term( $term, $name ){
- print "\t\t<cc:{$term} rdf:resource=\"http://web.resource.org/cc/{$name}\" />\n";
- }
-
- protected function epilogue() {
- echo "</rdf:RDF>\n";
- }
-}
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index 018f601d..62325238 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -117,14 +117,6 @@ unknown/unknown application/octet-stream application/x-empty [UNKNOWN]
END_STRING
);
-#note: because this file is possibly included by a function,
-#we need to access the global scope explicitely!
-global $wgLoadFileinfoExtension;
-
-if ($wgLoadFileinfoExtension) {
- wfDl( 'fileinfo' );
-}
-
/**
* Implements functions related to mime types such as detection and mapping to
* file extension.
@@ -138,19 +130,19 @@ class MimeMagic {
* Mapping of media types to arrays of mime types.
* This is used by findMediaType and getMediaType, respectively
*/
- var $mMediaTypes= null;
+ var $mMediaTypes = null;
/** Map of mime type aliases
*/
- var $mMimeTypeAliases= null;
+ var $mMimeTypeAliases = null;
/** map of mime types to file extensions (as a space seprarated list)
*/
- var $mMimeToExt= null;
+ var $mMimeToExt = null;
/** map of file extensions types to mime types (as a space seprarated list)
*/
- var $mExtToMime= null;
+ var $mExtToMime = null;
/** IEContentAnalyzer instance
*/
@@ -160,16 +152,20 @@ class MimeMagic {
*/
private static $instance;
+ /** True if the fileinfo extension has been loaded
+ */
+ private static $extensionLoaded = false;
+
/** Initializes the MimeMagic object. This is called by MimeMagic::singleton().
- *
- * This constructor parses the mime.types and mime.info files and build internal mappings.
- */
+ *
+ * This constructor parses the mime.types and mime.info files and build internal mappings.
+ */
function __construct() {
- /*
+ /**
* --- load mime.types ---
*/
- global $wgMimeTypeFile, $IP;
+ global $wgMimeTypeFile, $IP, $wgLoadFileinfoExtension;
$types = MM_WELL_KNOWN_MIME_TYPES;
@@ -177,6 +173,11 @@ class MimeMagic {
$wgMimeTypeFile = "$IP/$wgMimeTypeFile";
}
+ if ( $wgLoadFileinfoExtension && !self::$extensionLoaded ) {
+ self::$extensionLoaded = true;
+ wfDl( 'fileinfo' );
+ }
+
if ( $wgMimeTypeFile ) {
if ( is_file( $wgMimeTypeFile ) and is_readable( $wgMimeTypeFile ) ) {
wfDebug( __METHOD__.": loading mime types from $wgMimeTypeFile\n" );
@@ -198,20 +199,28 @@ class MimeMagic {
$lines = explode( "\n",$types );
foreach ( $lines as $s ) {
$s = trim( $s );
- if ( empty( $s ) ) continue;
- if ( strpos( $s, '#' ) === 0 ) continue;
+ if ( empty( $s ) ) {
+ continue;
+ }
+ if ( strpos( $s, '#' ) === 0 ) {
+ continue;
+ }
$s = strtolower( $s );
$i = strpos( $s, ' ' );
- if ( $i === false ) continue;
+ if ( $i === false ) {
+ continue;
+ }
#print "processing MIME line $s<br>";
$mime = substr( $s, 0, $i );
$ext = trim( substr($s, $i+1 ) );
- if ( empty( $ext ) ) continue;
+ if ( empty( $ext ) ) {
+ continue;
+ }
if ( !empty( $this->mMimeToExt[$mime] ) ) {
$this->mMimeToExt[$mime] .= ' ' . $ext;
@@ -223,7 +232,9 @@ class MimeMagic {
foreach ( $extensions as $e ) {
$e = trim( $e );
- if ( empty( $e ) ) continue;
+ if ( empty( $e ) ) {
+ continue;
+ }
if ( !empty( $this->mExtToMime[$e] ) ) {
$this->mExtToMime[$e] .= ' ' . $mime;
@@ -233,9 +244,9 @@ class MimeMagic {
}
}
- /*
- * --- load mime.info ---
- */
+ /**
+ * --- load mime.info ---
+ */
global $wgMimeInfoFile;
if ( $wgMimeInfoFile == 'includes/mime.info' ) {
@@ -265,13 +276,19 @@ class MimeMagic {
$lines = explode( "\n", $info );
foreach ( $lines as $s ) {
$s = trim( $s );
- if ( empty( $s ) ) continue;
- if ( strpos( $s, '#' ) === 0 ) continue;
+ if ( empty( $s ) ) {
+ continue;
+ }
+ if ( strpos( $s, '#' ) === 0 ) {
+ continue;
+ }
$s = strtolower( $s );
$i = strpos( $s, ' ' );
- if ( $i === false ) continue;
+ if ( $i === false ) {
+ continue;
+ }
#print "processing MIME INFO line $s<br>";
@@ -291,7 +308,9 @@ class MimeMagic {
foreach ( $m as $mime ) {
$mime = trim( $mime );
- if ( empty( $mime ) ) continue;
+ if ( empty( $mime ) ) {
+ continue;
+ }
$this->mMediaTypes[$mtype][] = $mime;
}
@@ -309,47 +328,70 @@ class MimeMagic {
/**
* Get an instance of this class
+ * @return MimeMagic
*/
- static function &singleton() {
+ public static function &singleton() {
if ( !isset( self::$instance ) ) {
self::$instance = new MimeMagic;
}
return self::$instance;
}
- /** returns a list of file extensions for a given mime type
- * as a space separated string.
- */
- function getExtensionsForType( $mime ) {
+ /**
+ * Returns a list of file extensions for a given mime type as a space
+ * separated string or null if the mime type was unrecognized. Resolves
+ * mime type aliases.
+ *
+ * @param $mime string
+ * @return string|null
+ */
+ public function getExtensionsForType( $mime ) {
$mime = strtolower( $mime );
- $r = @$this->mMimeToExt[$mime];
+ // Check the mime-to-ext map
+ if ( isset( $this->mMimeToExt[$mime] ) ) {
+ return $this->mMimeToExt[$mime];
+ }
- if ( @!$r and isset( $this->mMimeTypeAliases[$mime] ) ) {
+ // Resolve the mime type to the canonical type
+ if ( isset( $this->mMimeTypeAliases[$mime] ) ) {
$mime = $this->mMimeTypeAliases[$mime];
- $r = @$this->mMimeToExt[$mime];
+ if ( isset( $this->mMimeToExt[$mime] ) ) {
+ return $this->mMimeToExt[$mime];
+ }
}
- return $r;
+ return null;
}
- /** returns a list of mime types for a given file extension
- * as a space separated string.
- */
- function getTypesForExtension( $ext ) {
+ /**
+ * Returns a list of mime types for a given file extension as a space
+ * separated string or null if the extension was unrecognized.
+ *
+ * @param $ext string
+ * @return string|null
+ */
+ public function getTypesForExtension( $ext ) {
$ext = strtolower( $ext );
$r = isset( $this->mExtToMime[$ext] ) ? $this->mExtToMime[$ext] : null;
return $r;
}
- /** returns a single mime type for a given file extension.
- * This is always the first type from the list returned by getTypesForExtension($ext).
- */
- function guessTypesForExtension( $ext ) {
+ /**
+ * Returns a single mime type for a given file extension or null if unknown.
+ * This is always the first type from the list returned by getTypesForExtension($ext).
+ *
+ * @param $ext string
+ * @return string|null
+ */
+ public function guessTypesForExtension( $ext ) {
$m = $this->getTypesForExtension( $ext );
- if ( is_null( $m ) ) return null;
+ if ( is_null( $m ) ) {
+ return null;
+ }
+ // TODO: Check if this is needed; strtok( $m, ' ' ) should be sufficient
$m = trim( $m );
$m = preg_replace( '/\s.*$/', '', $m );
@@ -357,32 +399,38 @@ class MimeMagic {
}
- /** tests if the extension matches the given mime type.
- * returns true if a match was found, NULL if the mime type is unknown,
- * and false if the mime type is known but no matches where found.
- */
- function isMatchingExtension( $extension, $mime ) {
+ /**
+ * Tests if the extension matches the given mime type. Returns true if a
+ * match was found, null if the mime type is unknown, and false if the
+ * mime type is known but no matches where found.
+ *
+ * @param $extension string
+ * @param $mime string
+ * @return bool|null
+ */
+ public function isMatchingExtension( $extension, $mime ) {
$ext = $this->getExtensionsForType( $mime );
if ( !$ext ) {
- return null; //unknown
+ return null; // Unknown mime type
}
$ext = explode( ' ', $ext );
$extension = strtolower( $extension );
- if ( in_array( $extension, $ext ) ) {
- return true;
- }
-
- return false;
+ return in_array( $extension, $ext );
}
- /** returns true if the mime type is known to represent
- * an image format supported by the PHP GD library.
- */
- function isPHPImageType( $mime ) {
- #as defined by imagegetsize and image_type_to_mime
+ /**
+ * Returns true if the mime type is known to represent an image format
+ * supported by the PHP GD library.
+ *
+ * @param $mime string
+ *
+ * @return bool
+ */
+ public function isPHPImageType( $mime ) {
+ // As defined by imagegetsize and image_type_to_mime
static $types = array(
'image/gif', 'image/jpeg', 'image/png',
'image/x-bmp', 'image/xbm', 'image/tiff',
@@ -426,44 +474,45 @@ 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" );
+ /**
+ * 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
+ */
+ public 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 */
+ // Not something we can detect, so simply
+ // trust the file extension
$mime = $this->guessTypesForExtension( $ext );
}
}
- else if ( $mime === "application/x-opc+zip" ) {
+ elseif ( $mime === 'application/x-opc+zip' ) {
if ( $this->isMatchingExtension( $ext, $mime ) ) {
- /* A known file extension for an OPC file,
- * find the proper mime type for that file extension */
+ // 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";
+ $mime = 'application/zip';
}
}
@@ -475,20 +524,22 @@ class MimeMagic {
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,
- * as well as ZIP based formats like OPC/ODF files).
- *
- * @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.
+ /**
+ * 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, as well as ZIP
+ * based formats like OPC/ODF files).
+ *
+ * @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
+ */
+ public 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" );
}
@@ -508,12 +559,22 @@ class MimeMagic {
return $mime;
}
- private function doGuessMimeType( $file, $ext ) { # TODO: remove $ext param
+ /**
+ * Guess the mime type from the file contents.
+ *
+ * @param string $file
+ * @param mixed $ext
+ */
+ private function doGuessMimeType( $file, $ext ) { // TODO: remove $ext param
// Read a chunk of the file
wfSuppressWarnings();
- $f = fopen( $file, "rt" );
+ // @todo FIXME: Shouldn't this be rb?
+ $f = fopen( $file, 'rt' );
wfRestoreWarnings();
- if( !$f ) return "unknown/unknown";
+
+ if( !$f ) {
+ return 'unknown/unknown';
+ }
$head = fread( $f, 1024 );
fseek( $f, -65558, SEEK_END );
$tail = fread( $f, 65558 ); // 65558 = maximum size of a zip EOCDR
@@ -540,23 +601,23 @@ class MimeMagic {
"\x7fELF" => 'application/octet-stream', // ELF binary
);
- foreach( $headers as $magic => $candidate ) {
- if( strncmp( $head, $magic, strlen( $magic ) ) == 0 ) {
+ foreach ( $headers as $magic => $candidate ) {
+ if ( strncmp( $head, $magic, strlen( $magic ) ) == 0 ) {
wfDebug( __METHOD__ . ": magic header in $file recognized as $candidate\n" );
return $candidate;
}
}
/* Look for WebM and Matroska files */
- if( strncmp( $head, pack( "C4", 0x1a, 0x45, 0xdf, 0xa3 ), 4 ) == 0 ) {
+ if ( strncmp( $head, pack( "C4", 0x1a, 0x45, 0xdf, 0xa3 ), 4 ) == 0 ) {
$doctype = strpos( $head, "\x42\x82" );
- if( $doctype ) {
+ 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 ) {
+ 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 ) {
+ } elseif ( strncmp( $data, "webm", 4 ) == 0 ) {
wfDebug( __METHOD__ . ": recognized file as video/webm\n" );
return "video/webm";
}
@@ -566,24 +627,24 @@ class MimeMagic {
}
/* Look for WebP */
- if( strncmp( $head, "RIFF", 4 ) == 0 && strncmp( substr( $head, 8, 8), "WEBPVP8 ", 8 ) == 0 ) {
+ 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
* will also match text files which could be PHP. :)
*
- * FIXME: For this reason, the check is probably useless -- an attacker
+ * @todo FIXME: For this reason, the check is probably useless -- an attacker
* could almost certainly just pad the file with a lot of nonsense to
* circumvent the check in any case where it would be a security
* problem. On the other hand, it causes harmful false positives (bug
* 16583). The heuristic has been cut down to exclude three-character
* strings like "<? ", but should it be axed completely?
*/
- if( ( strpos( $head, '<?php' ) !== false ) ||
+ if ( ( strpos( $head, '<?php' ) !== false ) ||
( strpos( $head, "<\x00?\x00p\x00h\x00p" ) !== false ) ||
( strpos( $head, "<\x00?\x00 " ) !== false ) ||
@@ -592,23 +653,23 @@ class MimeMagic {
( strpos( $head, "<\x00?\x00=" ) !== false ) ) {
wfDebug( __METHOD__ . ": recognized $file as application/x-php\n" );
- return "application/x-php";
+ return 'application/x-php';
}
- /*
+ /**
* look for XML formats (XHTML and SVG)
*/
$xml = new XmlTypeCheck( $file );
- if( $xml->wellFormed ) {
+ if ( $xml->wellFormed ) {
global $wgXMLMimeTypes;
- if( isset( $wgXMLMimeTypes[$xml->getRootElement()] ) ) {
+ if ( isset( $wgXMLMimeTypes[$xml->getRootElement()] ) ) {
return $wgXMLMimeTypes[$xml->getRootElement()];
} else {
return 'application/xml';
}
}
- /*
+ /**
* look for shell scripts
*/
$script_type = null;
@@ -718,18 +779,18 @@ class MimeMagic {
$openxmlRegex = "/^\[Content_Types\].xml/";
- if( preg_match( $opendocRegex, substr( $header, 30 ), $matches ) ) {
+ if ( preg_match( $opendocRegex, substr( $header, 30 ), $matches ) ) {
$mime = $matches[1];
wfDebug( __METHOD__.": detected $mime from ZIP archive\n" );
- } elseif( preg_match( $openxmlRegex, substr( $header, 30 ) ) ) {
+ } 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 ) {
+ 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) ) {
+ 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 );
@@ -738,10 +799,10 @@ class MimeMagic {
}
}
wfDebug( __METHOD__.": detected an Open Packaging Conventions archive: $mime\n" );
- } else if( substr( $header, 0, 8 ) == "\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" &&
+ } elseif ( 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" ) {
+ if ( substr( $header, 512, 4) == "\xEC\xA5\xC1\x00" ) {
$mime = "application/msword";
}
switch( substr( $header, 512, 6) ) {
@@ -773,31 +834,34 @@ class MimeMagic {
return $mime;
}
- /** Internal mime type detection, please use guessMimeType() for application code instead.
- * Detection is done using an external program, if $wgMimeDetectorCommand is set.
- * Otherwise, the fileinfo extension and mime_content_type are tried (in this order), if they are available.
- * If the dections fails and $ext is not false, the mime type is guessed from the file extension, using
- * guessTypesForExtension.
- * If the mime type is still unknown, getimagesize is used to detect the mime type if the file is an image.
- * If no mime type can be determined, this function returns "unknown/unknown".
- *
- * @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
- */
+ /**
+ * Internal mime type detection. Detection is done using an external
+ * program, if $wgMimeDetectorCommand is set. Otherwise, the fileinfo
+ * extension and mime_content_type are tried (in this order), if they
+ * are available. If the dections fails and $ext is not false, the mime
+ * type is guessed from the file extension, using guessTypesForExtension.
+ *
+ * If the mime type is still unknown, getimagesize is used to detect the
+ * mime type if the file is an image. If no mime type can be determined,
+ * this function returns 'unknown/unknown'.
+ *
+ * @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
+ */
private function detectMimeType( $file, $ext = true ) {
global $wgMimeDetectorCommand;
- if( $ext ) { # TODO: make $ext default to false. Or better, remove it.
+ 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 ) {
+ // @todo FIXME: Use wfShellExec
$fn = wfEscapeShellArg( $file );
$m = `$wgMimeDetectorCommand $fn`;
} elseif ( function_exists( "finfo_open" ) && function_exists( "finfo_file" ) ) {
@@ -812,9 +876,9 @@ class MimeMagic {
# If you may need to load the fileinfo extension at runtime, set
# $wgLoadFileinfoExtension in LocalSettings.php
- $mime_magic_resource = finfo_open(FILEINFO_MIME); /* return mime type ala mimetype extension */
+ $mime_magic_resource = finfo_open( FILEINFO_MIME ); /* return mime type ala mimetype extension */
- if ($mime_magic_resource) {
+ if ( $mime_magic_resource ) {
$m = finfo_file( $mime_magic_resource, $file );
finfo_close( $mime_magic_resource );
} else {
@@ -850,7 +914,7 @@ class MimeMagic {
}
}
- # if desired, look at extension as a fallback.
+ // If desired, look at extension as a fallback.
if ( $ext === true ) {
$i = strrpos( $file, '.' );
$ext = strtolower( $i ? substr( $file, $i + 1 ) : '' );
@@ -867,36 +931,40 @@ class MimeMagic {
}
}
- #unknown type
- wfDebug( __METHOD__.": failed to guess mime type for $file!\n" );
- return "unknown/unknown";
+ // Unknown type
+ wfDebug( __METHOD__ . ": failed to guess mime type for $file!\n" );
+ return 'unknown/unknown';
}
/**
- * Determine the media type code for a file, using its mime type, name and possibly
- * its contents.
- *
- * This function relies on the findMediaType(), mapping extensions and mime
- * types to media types.
- *
- * @todo analyse file if need be
- * @todo look at multiple extension, separately and together.
- *
- * @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 $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.
- */
+ * Determine the media type code for a file, using its mime type, name and
+ * possibly its contents.
+ *
+ * This function relies on the findMediaType(), mapping extensions and mime
+ * types to media types.
+ *
+ * @todo analyse file if need be
+ * @todo look at multiple extension, separately and together.
+ *
+ * @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 $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.
+ */
function getMediaType( $path = null, $mime = null ) {
- if( !$mime && !$path ) return MEDIATYPE_UNKNOWN;
+ if( !$mime && !$path ) {
+ return MEDIATYPE_UNKNOWN;
+ }
- # If mime type is unknown, guess it
- if( !$mime ) $mime = $this->guessMimeType( $path, false );
+ // If mime type is unknown, guess it
+ if( !$mime ) {
+ $mime = $this->guessMimeType( $path, false );
+ }
- # Special code for ogg - detect if it's video (theora),
- # else label it as sound.
- if( $mime == "application/ogg" && file_exists( $path ) ) {
+ // Special code for ogg - detect if it's video (theora),
+ // else label it as sound.
+ if ( $mime == 'application/ogg' && file_exists( $path ) ) {
// Read a chunk of the file
$f = fopen( $path, "rt" );
@@ -906,7 +974,7 @@ class MimeMagic {
$head = strtolower( $head );
- # This is an UGLY HACK, file should be parsed correctly
+ // This is an UGLY HACK, file should be parsed correctly
if ( strpos( $head, 'theora' ) !== false ) return MEDIATYPE_VIDEO;
elseif ( strpos( $head, 'vorbis' ) !== false ) return MEDIATYPE_AUDIO;
elseif ( strpos( $head, 'flac' ) !== false ) return MEDIATYPE_AUDIO;
@@ -914,58 +982,69 @@ class MimeMagic {
else return MEDIATYPE_MULTIMEDIA;
}
- # check for entry for full mime type
+ // Check for entry for full mime type
if( $mime ) {
$type = $this->findMediaType( $mime );
- if( $type !== MEDIATYPE_UNKNOWN ) return $type;
+ if ( $type !== MEDIATYPE_UNKNOWN ) {
+ return $type;
+ }
}
- # Check for entry for file extension
+ // Check for entry for file extension
if ( $path ) {
$i = strrpos( $path, '.' );
$e = strtolower( $i ? substr( $path, $i + 1 ) : '' );
- # TODO: look at multi-extension if this fails, parse from full path
-
+ // TODO: look at multi-extension if this fails, parse from full path
$type = $this->findMediaType( '.' . $e );
- if ( $type !== MEDIATYPE_UNKNOWN ) return $type;
+ if ( $type !== MEDIATYPE_UNKNOWN ) {
+ return $type;
+ }
}
- # Check major mime type
- if( $mime ) {
+ // Check major mime type
+ if ( $mime ) {
$i = strpos( $mime, '/' );
- if( $i !== false ) {
+ if ( $i !== false ) {
$major = substr( $mime, 0, $i );
$type = $this->findMediaType( $major );
- if( $type !== MEDIATYPE_UNKNOWN ) return $type;
+ if ( $type !== MEDIATYPE_UNKNOWN ) {
+ return $type;
+ }
}
}
- if( !$type ) $type = MEDIATYPE_UNKNOWN;
+ if( !$type ) {
+ $type = MEDIATYPE_UNKNOWN;
+ }
return $type;
}
- /** returns a media code matching the given mime type or file extension.
- * File extensions are represented by a string starting with a dot (.) to
- * distinguish them from mime types.
- *
- * This funktion relies on the mapping defined by $this->mMediaTypes
- * @access private
- */
+ /**
+ * Returns a media code matching the given mime type or file extension.
+ * File extensions are represented by a string starting with a dot (.) to
+ * distinguish them from mime types.
+ *
+ * This funktion relies on the mapping defined by $this->mMediaTypes
+ * @access private
+ */
function findMediaType( $extMime ) {
- if ( strpos( $extMime, '.' ) === 0 ) { #if it's an extension, look up the mime types
+ if ( strpos( $extMime, '.' ) === 0 ) {
+ // If it's an extension, look up the mime types
$m = $this->getTypesForExtension( substr( $extMime, 1 ) );
- if ( !$m ) return MEDIATYPE_UNKNOWN;
+ if ( !$m ) {
+ return MEDIATYPE_UNKNOWN;
+ }
$m = explode( ' ', $m );
} else {
- # Normalize mime type
+ // Normalize mime type
if ( isset( $this->mMimeTypeAliases[$extMime] ) ) {
$extMime = $this->mMimeTypeAliases[$extMime];
}
- $m = array($extMime);
+ $m = array( $extMime );
}
foreach ( $m as $mime ) {
@@ -994,6 +1073,8 @@ class MimeMagic {
/**
* Get a cached instance of IEContentAnalyzer
+ *
+ * @return IEContentAnalyzer
*/
protected function getIEContentAnalyzer() {
if ( is_null( $this->mIEAnalyzer ) ) {
diff --git a/includes/Namespace.php b/includes/Namespace.php
index 47dc3c5f..95c167b2 100644
--- a/includes/Namespace.php
+++ b/includes/Namespace.php
@@ -5,35 +5,6 @@
*/
/**
- * Definitions of the NS_ constants are in Defines.php
- * @private
- */
-$wgCanonicalNamespaceNames = array(
- NS_MEDIA => 'Media',
- NS_SPECIAL => 'Special',
- NS_TALK => 'Talk',
- NS_USER => 'User',
- NS_USER_TALK => 'User_talk',
- NS_PROJECT => 'Project',
- NS_PROJECT_TALK => 'Project_talk',
- NS_FILE => 'File',
- NS_FILE_TALK => 'File_talk',
- NS_MEDIAWIKI => 'MediaWiki',
- NS_MEDIAWIKI_TALK => 'MediaWiki_talk',
- NS_TEMPLATE => 'Template',
- NS_TEMPLATE_TALK => 'Template_talk',
- NS_HELP => 'Help',
- NS_HELP_TALK => 'Help_talk',
- NS_CATEGORY => 'Category',
- NS_CATEGORY_TALK => 'Category_talk',
-);
-
-/// @todo UGLY UGLY
-if( is_array( $wgExtraNamespaces ) ) {
- $wgCanonicalNamespaceNames = $wgCanonicalNamespaceNames + $wgExtraNamespaces;
-}
-
-/**
* This is a utility class with only static functions
* for dealing with namespaces that encodes all the
* "magic" behaviors of them based on index. The textual
@@ -54,6 +25,24 @@ class MWNamespace {
private static $alwaysCapitalizedNamespaces = array( NS_SPECIAL, NS_USER, NS_MEDIAWIKI );
/**
+ * Throw an exception when trying to get the subject or talk page
+ * for a given namespace where it does not make sense.
+ * Special namespaces are defined in includes/define.php and have
+ * a value below 0 (ex: NS_SPECIAL = -1 , NS_MEDIA = -2)
+ *
+ * @param $index
+ * @param $method
+ *
+ * @return true
+ */
+ private static function isMethodValidFor( $index, $method ) {
+ if( $index < NS_MAIN ) {
+ throw new MWException( "$method does not make any sense for given namespace $index" );
+ }
+ return true;
+ }
+
+ /**
* Can pages in the given namespace be moved?
*
* @param $index Int: namespace index
@@ -92,6 +81,7 @@ class MWNamespace {
* @return int
*/
public static function getTalk( $index ) {
+ self::isMethodValidFor( $index, __METHOD__ );
return self::isTalk( $index )
? $index
: $index + 1;
@@ -99,18 +89,48 @@ class MWNamespace {
/**
* Get the subject namespace index for a given namespace
+ * Special namespaces (NS_MEDIA, NS_SPECIAL) are always the subject.
*
* @param $index Int: Namespace index
* @return int
*/
public static function getSubject( $index ) {
+ # Handle special namespaces
+ if( $index < NS_MAIN ) {
+ return $index;
+ }
+
return self::isTalk( $index )
? $index - 1
: $index;
}
/**
+ * Get the associated namespace.
+ * For talk namespaces, returns the subject (non-talk) namespace
+ * For subject (non-talk) namespaces, returns the talk namespace
+ *
+ * @param $index Int: namespace index
+ * @return int or null if no associated namespace could be found
+ */
+ public static function getAssociated( $index ) {
+ self::isMethodValidFor( $index, __METHOD__ );
+
+ if( self::isMain( $index ) ) {
+ return self::getTalk( $index );
+ } elseif( self::isTalk( $index ) ) {
+ return self::getSubject( $index );
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Returns whether the specified namespace exists
+ *
+ * @param $index
+ *
+ * @return bool
*/
public static function exists( $index ) {
$nslist = self::getCanonicalNamespaces();
@@ -237,6 +257,21 @@ class MWNamespace {
}
/**
+ * Get a list of all namespace indices which are considered to contain content
+ * @return array of namespace indices
+ */
+ public static function getContentNamespaces() {
+ global $wgContentNamespaces;
+ if( !is_array( $wgContentNamespaces ) || $wgContentNamespaces === array() ) {
+ return NS_MAIN;
+ } elseif ( !in_array( NS_MAIN, $wgContentNamespaces ) ) {
+ // always force NS_MAIN to be part of array (to match the algorithm used by isContent)
+ return array_merge( array( NS_MAIN ), $wgContentNamespaces );
+ } else {
+ return $wgContentNamespaces;
+ }
+ }
+ /**
* Is the namespace first-letter capitalized?
*
* @param $index int Index to check
@@ -261,4 +296,17 @@ class MWNamespace {
// Default to the global setting
return $wgCapitalLinks;
}
+
+ /**
+ * Does the namespace (potentially) have different aliases for different
+ * genders. Not all languages make a distinction here.
+ *
+ * @since 1.18
+ * @param $index int Index to check
+ * @return bool
+ */
+ public static function hasGenderDistinction( $index ) {
+ return $index == NS_USER || $index == NS_USER_TALK;
+ }
+
}
diff --git a/includes/ObjectCache.php b/includes/ObjectCache.php
deleted file mode 100644
index 05ae9c9c..00000000
--- a/includes/ObjectCache.php
+++ /dev/null
@@ -1,123 +0,0 @@
-<?php
-/**
- * Functions to get cache objects
- *
- * @file
- * @ingroup Cache
- */
-
-/**
- * FakeMemCachedClient imitates the API of memcached-client v. 0.1.2.
- * It acts as a memcached server with no RAM, that is, all objects are
- * cleared the moment they are set. All set operations succeed and all
- * get operations return null.
- * @ingroup Cache
- */
-class FakeMemCachedClient {
- function add ($key, $val, $exp = 0) { return true; }
- function decr ($key, $amt=1) { return null; }
- function delete ($key, $time = 0) { return false; }
- function disconnect_all () { }
- function enable_compress ($enable) { }
- function forget_dead_hosts () { }
- function get ($key) { return null; }
- function get_multi ($keys) { return array_pad(array(), count($keys), null); }
- function incr ($key, $amt=1) { return null; }
- function replace ($key, $value, $exp=0) { return false; }
- function run_command ($sock, $cmd) { return null; }
- function set ($key, $value, $exp=0){ return true; }
- function set_compress_threshold ($thresh){ }
- function set_debug ($dbg) { }
- function set_servers ($list) { }
-}
-
-global $wgCaches;
-$wgCaches = array();
-
-/**
- * Get a cache object.
- * @param $inputType Integer: cache type, one the the CACHE_* constants.
- */
-function &wfGetCache( $inputType ) {
- global $wgCaches, $wgMemCachedServers, $wgMemCachedDebug, $wgMemCachedPersistent;
- $cache = false;
-
- if ( $inputType == CACHE_ANYTHING ) {
- reset( $wgCaches );
- $type = key( $wgCaches );
- if ( $type === false || $type === CACHE_NONE ) {
- $type = CACHE_DB;
- }
- } else {
- $type = $inputType;
- }
-
- if ( $type == CACHE_MEMCACHED ) {
- if ( !array_key_exists( CACHE_MEMCACHED, $wgCaches ) ) {
- $wgCaches[CACHE_MEMCACHED] = new MemCachedClientforWiki(
- array('persistant' => $wgMemCachedPersistent, 'compress_threshold' => 1500 ) );
- $wgCaches[CACHE_MEMCACHED]->set_servers( $wgMemCachedServers );
- $wgCaches[CACHE_MEMCACHED]->set_debug( $wgMemCachedDebug );
- }
- $cache =& $wgCaches[CACHE_MEMCACHED];
- } elseif ( $type == CACHE_ACCEL ) {
- if ( !array_key_exists( CACHE_ACCEL, $wgCaches ) ) {
- if ( function_exists( 'eaccelerator_get' ) ) {
- $wgCaches[CACHE_ACCEL] = new eAccelBagOStuff;
- } elseif ( function_exists( 'apc_fetch') ) {
- $wgCaches[CACHE_ACCEL] = new APCBagOStuff;
- } elseif( function_exists( 'xcache_get' ) && wfIniGetBool( 'xcache.var_size' ) ) {
- $wgCaches[CACHE_ACCEL] = new XCacheBagOStuff();
- } elseif( function_exists( 'wincache_ucache_get' ) ) {
- $wgCaches[CACHE_ACCEL] = new WinCacheBagOStuff();
- } else {
- $wgCaches[CACHE_ACCEL] = false;
- }
- }
- if ( $wgCaches[CACHE_ACCEL] !== false ) {
- $cache =& $wgCaches[CACHE_ACCEL];
- }
- } elseif ( $type == CACHE_DBA ) {
- if ( !array_key_exists( CACHE_DBA, $wgCaches ) ) {
- $wgCaches[CACHE_DBA] = new DBABagOStuff;
- }
- $cache =& $wgCaches[CACHE_DBA];
- }
-
- if ( $type == CACHE_DB || ( $inputType == CACHE_ANYTHING && $cache === false ) ) {
- if ( !array_key_exists( CACHE_DB, $wgCaches ) ) {
- $wgCaches[CACHE_DB] = new SqlBagOStuff('objectcache');
- }
- $cache =& $wgCaches[CACHE_DB];
- }
-
- if ( $cache === false ) {
- if ( !array_key_exists( CACHE_NONE, $wgCaches ) ) {
- $wgCaches[CACHE_NONE] = new FakeMemCachedClient;
- }
- $cache =& $wgCaches[CACHE_NONE];
- }
-
- return $cache;
-}
-
-/** Get the main cache object */
-function &wfGetMainCache() {
- global $wgMainCacheType;
- $ret =& wfGetCache( $wgMainCacheType );
- return $ret;
-}
-
-/** Get the cache object used by the message cache */
-function &wfGetMessageCacheStorage() {
- global $wgMessageCacheType;
- $ret =& wfGetCache( $wgMessageCacheType );
- return $ret;
-}
-
-/** Get the cache object used by the parser cache */
-function &wfGetParserCacheStorage() {
- global $wgParserCacheType;
- $ret =& wfGetCache( $wgParserCacheType );
- return $ret;
-}
diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php
index 8f310da2..4112f8a2 100644
--- a/includes/OutputHandler.php
+++ b/includes/OutputHandler.php
@@ -7,6 +7,10 @@
/**
* Standard output handler for use with ob_start
+ *
+ * @param $s string
+ *
+ * @return string
*/
function wfOutputHandler( $s ) {
global $wgDisableOutputCompression, $wgValidateAllHtml;
@@ -40,9 +44,11 @@ function wfOutputHandler( $s ) {
* the currently-requested URL.
* This isn't on WebRequest because we need it when things aren't initialized
* @private
+ *
+ * @return string
*/
function wfRequestExtension() {
- /// @todo Fixme: this sort of dupes some code in WebRequest::getRequestUrl()
+ /// @todo FIXME: this sort of dupes some code in WebRequest::getRequestUrl()
if( isset( $_SERVER['REQUEST_URI'] ) ) {
// Strip the query string...
list( $path ) = explode( '?', $_SERVER['REQUEST_URI'], 2 );
@@ -64,6 +70,10 @@ function wfRequestExtension() {
/**
* Handler that compresses data with gzip if allowed by the Accept header.
* Unlike ob_gzhandler, it works for HEAD requests too.
+ *
+ * @param $s string
+ *
+ * @return string
*/
function wfGzipHandler( $s ) {
if( !function_exists( 'gzencode' ) || headers_sent() ) {
@@ -105,6 +115,10 @@ function wfGzipHandler( $s ) {
/**
* Mangle flash policy tags which open up the site to XSS attacks.
+ *
+ * @param $s string
+ *
+ * @return string
*/
function wfMangleFlashPolicy( $s ) {
# Avoid weird excessive memory usage in PCRE on big articles
@@ -117,6 +131,8 @@ function wfMangleFlashPolicy( $s ) {
/**
* Add a Content-Length header if possible. This makes it cooperate with squid better.
+ *
+ * @param $length int
*/
function wfDoContentLength( $length ) {
if ( !headers_sent() && $_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.0' ) {
@@ -126,6 +142,10 @@ function wfDoContentLength( $length ) {
/**
* Replace the output with an error if the HTML is not valid
+ *
+ * @param $s string
+ *
+ * @return string
*/
function wfHtmlValidationHandler( $s ) {
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 6ecc2754..419bbdf7 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -4,63 +4,211 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
/**
+ * This class should be covered by a general architecture document which does
+ * not exist as of January 2011. This is one of the Core classes and should
+ * be read at least once by any new developers.
+ *
+ * This class is used to prepare the final rendering. A skin is then
+ * applied to the output parameters (links, javascript, html, categories ...).
+ *
+ * @todo FIXME: Another class handles sending the whole page to the client.
+ *
+ * Some comments comes from a pairing session between Zak Greant and Ashar Voultoiz
+ * in November 2010.
+ *
* @todo document
*/
-class OutputPage {
- var $mMetatags = array(), $mKeywords = array(), $mLinktags = array();
+class OutputPage extends ContextSource {
+ /// Should be private. Used with addMeta() which adds <meta>
+ var $mMetatags = array();
+
+ /// <meta keyworkds="stuff"> most of the time the first 10 links to an article
+ var $mKeywords = array();
+
+ var $mLinktags = array();
+
+ /// Additional stylesheets. Looks like this is for extensions. Might be replaced by resource loader.
var $mExtStyles = array();
- var $mPagetitle = '', $mBodytext = '';
+
+ /// Should be private - has getter and setter. Contains the HTML title
+ var $mPagetitle = '';
+
+ /// Contains all of the <body> content. Should be private we got set/get accessors and the append() method.
+ var $mBodytext = '';
/**
- * Holds the debug lines that will be outputted as comments in page source if
+ * Holds the debug lines that will be output as comments in page source if
* $wgDebugComments is enabled. See also $wgShowDebug.
* TODO: make a getter method for this
*/
- public $mDebugtext = '';
+ public $mDebugtext = ''; // TODO: we might want to replace it by wfDebug() wfDebugLog()
- var $mHTMLtitle = '', $mIsarticle = true, $mPrintable = false;
- var $mSubtitle = '', $mRedirect = '', $mStatusCode;
- var $mLastModified = '', $mETag = false;
- var $mCategoryLinks = array(), $mCategories = array(), $mLanguageLinks = array();
+ /// Should be private. Stores contents of <title> tag
+ var $mHTMLtitle = '';
+
+ /// Should be private. Is the displayed content related to the source of the corresponding wiki article.
+ var $mIsarticle = true;
+
+ /**
+ * Should be private. We have to set isPrintable(). Some pages should
+ * never be printed (ex: redirections).
+ */
+ var $mPrintable = false;
+
+ /**
+ * Should be private. We have set/get/append methods.
+ *
+ * Contains the page subtitle. Special pages usually have some links here.
+ * Don't confuse with site subtitle added by skins.
+ */
+ var $mSubtitle = '';
+
+ var $mRedirect = '';
+ var $mStatusCode;
+
+ /**
+ * mLastModified and mEtag are used for sending cache control.
+ * The whole caching system should probably be moved into its own class.
+ */
+ var $mLastModified = '';
+
+ /**
+ * Should be private. No getter but used in sendCacheControl();
+ * Contains an HTTP Entity Tags (see RFC 2616 section 3.13) which is used
+ * as a unique identifier for the content. It is later used by the client
+ * to compare its cached version with the server version. Client sends
+ * headers If-Match and If-None-Match containing its locally cached ETAG value.
+ *
+ * To get more information, you will have to look at HTTP/1.1 protocol which
+ * is properly described in RFC 2616 : http://tools.ietf.org/html/rfc2616
+ */
+ var $mETag = false;
+
+ var $mCategoryLinks = array();
+ var $mCategories = array();
+
+ /// Should be private. Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page')
+ var $mLanguageLinks = array();
+
+ /**
+ * Should be private. Used for JavaScript (pre resource loader)
+ * We should split js / css.
+ * mScripts content is inserted as is in <head> by Skin. This might contains
+ * either a link to a stylesheet or inline css.
+ */
+ var $mScripts = '';
+
+ /**
+ * Inline CSS styles. Use addInlineStyle() sparsingly
+ */
+ var $mInlineStyles = '';
+
+ //
+ var $mLinkColours;
+
+ /**
+ * Used by skin template.
+ * Example: $tpl->set( 'displaytitle', $out->mPageLinkTitle );
+ */
+ var $mPageLinkTitle = '';
- var $mScripts = '', $mInlineStyles = '', $mLinkColours, $mPageLinkTitle = '', $mHeadItems = array();
+ /// Array of elements in <head>. Parser might add its own headers!
+ var $mHeadItems = array();
+
+ // @todo FIXME: Next variables probably comes from the resource loader
var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array();
var $mResourceLoader;
+ var $mJsConfigVars = array();
+
+ /** @todo FIXME: Is this still used ?*/
var $mInlineMsg = array();
var $mTemplateIds = array();
+ var $mImageTimeKeys = array();
+
+ var $mRedirectCode = '';
+
+ var $mFeedLinksAppendQuery = null;
- var $mAllowUserJs;
- var $mSuppressQuickbar = false;
+ # What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page?
+ # @see ResourceLoaderModule::$origin
+ # ResourceLoaderModule::ORIGIN_ALL is assumed unless overridden;
+ protected $mAllowedModules = array(
+ ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL,
+ );
+
+ /**
+ * @EasterEgg I just love the name for this self documenting variable.
+ * @todo document
+ */
var $mDoNothing = false;
+
+ // Parser related.
var $mContainsOldMagic = 0, $mContainsNewMagic = 0;
+
+ /**
+ * Should be private. Has get/set methods properly documented.
+ * Stores "article flag" toggle.
+ */
var $mIsArticleRelated = true;
- protected $mParserOptions = null; // lazy initialised, use parserOptions()
+ /// lazy initialised, use parserOptions()
+ protected $mParserOptions = null;
+
+ /**
+ * Handles the atom / rss links.
+ * We probably only support atom in 2011.
+ * Looks like a private variable.
+ * @see $wgAdvertisedFeedTypes
+ */
var $mFeedLinks = array();
+ // Gwicke work on squid caching? Roughly from 2003.
var $mEnableClientCache = true;
+
+ /**
+ * Flag if output should only contain the body of the article.
+ * Should be private.
+ */
var $mArticleBodyOnly = false;
var $mNewSectionLink = false;
var $mHideNewSectionLink = false;
+
+ /**
+ * Comes from the parser. This was probably made to load CSS/JS only
+ * if we had <gallery>. Used directly in CategoryPage.php
+ * Looks like resource loader can replace this.
+ */
var $mNoGallery = false;
+
+ // should be private.
var $mPageTitleActionText = '';
var $mParseWarnings = array();
+
+ // Cache stuff. Looks like mEnableClientCache
var $mSquidMaxage = 0;
+
+ // @todo document
var $mPreventClickjacking = true;
+
+ /// should be private. To include the variable {{REVISIONID}}
var $mRevisionId = null;
- protected $mTitle = null;
+
+ var $mFileVersion = null;
/**
* An array of stylesheet filenames (relative from skins path), with options
* for CSS media, IE conditions, and RTL/LTR direction.
* For internal use; add settings in the skin via $this->addStyle()
+ *
+ * Style again! This seems like a code duplication since we already have
+ * mStyles. This is what makes OpenSource amazing.
*/
var $styles = array();
/**
- * Whether to load jQuery core.
+ * Whether jQuery is already handled.
*/
protected $mJQueryDone = false;
@@ -72,12 +220,12 @@ class OutputPage {
);
/**
- * Constructor
- * Initialise private variables
+ * Constructor for OutputPage. This should not be called directly.
+ * Instead a new RequestContext should be created and it will implicitly create
+ * a OutputPage tied to that context.
*/
- function __construct() {
- global $wgAllowUserJs;
- $this->mAllowUserJs = $wgAllowUserJs;
+ function __construct( IContextSource $context = null ) {
+ $this->setContext( $context );
}
/**
@@ -105,7 +253,6 @@ class OutputPage {
* Set the HTTP status code to send with the output.
*
* @param $statusCode Integer
- * @return nothing
*/
public function setStatusCode( $statusCode ) {
$this->mStatusCode = $statusCode;
@@ -115,8 +262,8 @@ class OutputPage {
* Add a new <meta> tag
* To add an http-equiv meta tag, precede the name with "http:"
*
- * @param $name tag name
- * @param $val tag value
+ * @param $name String tag name
+ * @param $val String tag value
*/
function addMeta( $name, $val ) {
array_push( $this->mMetatags, array( $name, $val ) );
@@ -152,11 +299,24 @@ class OutputPage {
* "rel" attribute will be automatically added
*/
function addMetadataLink( $linkarr ) {
+ $linkarr['rel'] = $this->getMetadataAttribute();
+ $this->addLink( $linkarr );
+ }
+
+ /**
+ * Get the value of the "rel" attribute for metadata links
+ *
+ * @return String
+ */
+ public function getMetadataAttribute() {
# note: buggy CC software only reads first "meta" link
static $haveMeta = false;
- $linkarr['rel'] = $haveMeta ? 'alternate meta' : 'meta';
- $this->addLink( $linkarr );
- $haveMeta = true;
+ if ( $haveMeta ) {
+ return 'alternate meta';
+ } else {
+ $haveMeta = true;
+ return 'meta';
+ }
}
/**
@@ -181,7 +341,7 @@ class OutputPage {
}
/**
- * Get all links added by extensions
+ * Get all styles added by extensions
*
* @return Array
*/
@@ -228,12 +388,41 @@ class OutputPage {
}
/**
+ * Filter an array of modules to remove insufficiently trustworthy members, and modules
+ * which are no longer registered (eg a page is cached before an extension is disabled)
+ * @param $modules Array
+ * @param $position String if not null, only return modules with this position
+ * @param $type string
+ * @return Array
+ */
+ protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ){
+ $resourceLoader = $this->getResourceLoader();
+ $filteredModules = array();
+ foreach( $modules as $val ){
+ $module = $resourceLoader->getModule( $val );
+ if( $module instanceof ResourceLoaderModule
+ && $module->getOrigin() <= $this->getAllowedModules( $type )
+ && ( is_null( $position ) || $module->getPosition() == $position ) )
+ {
+ $filteredModules[] = $val;
+ }
+ }
+ return $filteredModules;
+ }
+
+ /**
* Get the list of modules to include on this page
*
+ * @param $filter Bool whether to filter out insufficiently trustworthy modules
+ * @param $position String if not null, only return modules with this position
+ * @param $param string
* @return Array of module names
*/
- public function getModules() {
- return array_values( array_unique( $this->mModules ) );
+ public function getModules( $filter = false, $position = null, $param = 'mModules' ) {
+ $modules = array_values( array_unique( $this->$param ) );
+ return $filter
+ ? $this->filterModules( $modules, $position )
+ : $modules;
}
/**
@@ -249,10 +438,14 @@ class OutputPage {
/**
* Get the list of module JS to include on this page
+ *
+ * @param $filter
+ * @param $position
+ *
* @return array of module names
*/
- public function getModuleScripts() {
- return array_values( array_unique( $this->mModuleScripts ) );
+ public function getModuleScripts( $filter = false, $position = null ) {
+ return $this->getModules( $filter, $position, 'mModuleScripts' );
}
/**
@@ -269,10 +462,13 @@ class OutputPage {
/**
* Get the list of module CSS to include on this page
*
+ * @param $filter
+ * @param $position
+ *
* @return Array of module names
*/
- public function getModuleStyles() {
- return array_values( array_unique( $this->mModuleStyles ) );
+ public function getModuleStyles( $filter = false, $position = null ) {
+ return $this->getModules( $filter, $position, 'mModuleStyles' );
}
/**
@@ -289,10 +485,13 @@ class OutputPage {
/**
* Get the list of module messages to include on this page
*
+ * @param $filter
+ * @param $position
+ *
* @return Array of module names
*/
- public function getModuleMessages() {
- return array_values( array_unique( $this->mModuleMessages ) );
+ public function getModuleMessages( $filter = false, $position = null ) {
+ return $this->getModules( $filter, $position, 'mModuleMessages' );
}
/**
@@ -375,10 +574,12 @@ class OutputPage {
*
* Side effect: sets mLastModified for Last-Modified header
*
+ * @param $timestamp string
+ *
* @return Boolean: true iff cache-ok headers was sent.
*/
public function checkLastModified( $timestamp ) {
- global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest;
+ global $wgCachePages, $wgCacheEpoch;
if ( !$timestamp || $timestamp == '19700101000000' ) {
wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
@@ -388,7 +589,7 @@ class OutputPage {
wfDebug( __METHOD__ . ": CACHE DISABLED\n", false );
return false;
}
- if( $wgUser->getOption( 'nocache' ) ) {
+ if( $this->getUser()->getOption( 'nocache' ) ) {
wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false );
return false;
}
@@ -396,7 +597,7 @@ class OutputPage {
$timestamp = wfTimestamp( TS_MW, $timestamp );
$modifiedTimes = array(
'page' => $timestamp,
- 'user' => $wgUser->getTouched(),
+ 'user' => $this->getUser()->getTouched(),
'epoch' => $wgCacheEpoch
);
wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
@@ -445,7 +646,7 @@ class OutputPage {
# Give a 304 response code and disable body output
wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
ini_set( 'zlib.output_compression', 0 );
- $wgRequest->response()->header( "HTTP/1.1 304 Not Modified" );
+ $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" );
$this->sendCacheControl();
$this->disable();
@@ -538,6 +739,8 @@ 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.
+ *
+ * @param $name string
*/
public function setHTMLTitle( $name ) {
$this->mHTMLtitle = $name;
@@ -557,6 +760,8 @@ class OutputPage {
* This function allows good tags like \<sup\> in the \<h1\> tag, but not bad tags like \<script\>.
* This function automatically sets \<title\> to the same content as \<h1\> but with all tags removed.
* Bad tags that were escaped in \<h1\> will still be escaped in \<title\>, and good tags like \<i\> will be dropped entirely.
+ *
+ * @param $name string
*/
public function setPageTitle( $name ) {
# change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
@@ -583,23 +788,9 @@ class OutputPage {
* @param $t Title object
*/
public function setTitle( $t ) {
- $this->mTitle = $t;
+ $this->getContext()->setTitle( $t );
}
- /**
- * Get the Title object used in this instance
- *
- * @return Title
- */
- public function getTitle() {
- if ( $this->mTitle instanceof Title ) {
- return $this->mTitle;
- } else {
- wfDebug( __METHOD__ . " called and \$mTitle is null. Return \$wgTitle for sanity\n" );
- global $wgTitle;
- return $wgTitle;
- }
- }
/**
* Replace the subtile with $str
@@ -826,7 +1017,7 @@ class OutputPage {
/**
* Get the list of language links
*
- * @return Associative array mapping language code to the page name
+ * @return Array of Interwiki Prefixed (non DB key) Titles (e.g. 'fr:Test page')
*/
public function getLanguageLinks() {
return $this->mLanguageLinks;
@@ -835,10 +1026,10 @@ class OutputPage {
/**
* Add an array of categories, with names in the keys
*
- * @param $categories Associative array mapping category name to its sort key
+ * @param $categories Array mapping category name => sort key
*/
public function addCategoryLinks( $categories ) {
- global $wgUser, $wgContLang;
+ global $wgContLang;
if ( !is_array( $categories ) || count( $categories ) == 0 ) {
return;
@@ -851,12 +1042,13 @@ class OutputPage {
# Fetch existence plus the hiddencat property
$dbr = wfGetDB( DB_SLAVE );
- $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, 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__ );
+ $res = $dbr->select( array( 'page', 'page_props' ),
+ array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ),
+ $lb->constructSet( 'page', $dbr ),
+ __METHOD__,
+ array(),
+ array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) )
+ );
# Add the results to the link cache
$lb->addResultToCache( LinkCache::singleton(), $res );
@@ -876,7 +1068,6 @@ class OutputPage {
# Add the remaining categories to the skin
if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) {
- $sk = $wgUser->getSkin();
foreach ( $categories as $category => $type ) {
$origcategory = $category;
$title = Title::makeTitleSafe( NS_CATEGORY, $category );
@@ -888,7 +1079,7 @@ class OutputPage {
}
$text = $wgContLang->convertHtml( $title->getText() );
$this->mCategories[] = $title->getText();
- $this->mCategoryLinks[$type][] = $sk->link( $title, $text );
+ $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
}
}
}
@@ -896,7 +1087,7 @@ class OutputPage {
/**
* Reset the category links (but not the category list) and add $categories
*
- * @param $categories Associative array mapping category name to its sort key
+ * @param $categories Array mapping category name => sort key
*/
public function setCategoryLinks( $categories ) {
$this->mCategoryLinks = array();
@@ -925,36 +1116,58 @@ class OutputPage {
}
/**
- * Suppress the quickbar from the output, only for skin supporting
- * the quickbar
+ * Do not allow scripts which can be modified by wiki users to load on this page;
+ * only allow scripts bundled with, or generated by, the software.
*/
- public function suppressQuickbar() {
- $this->mSuppressQuickbar = true;
+ public function disallowUserJs() {
+ $this->reduceAllowedModules(
+ ResourceLoaderModule::TYPE_SCRIPTS,
+ ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
+ );
}
/**
- * Return whether the quickbar should be suppressed from the output
- *
+ * Return whether user JavaScript is allowed for this page
+ * @deprecated since 1.18 Load modules with ResourceLoader, and origin and
+ * trustworthiness is identified and enforced automagically.
* @return Boolean
*/
- public function isQuickbarSuppressed() {
- return $this->mSuppressQuickbar;
+ public function isUserJsAllowed() {
+ return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL;
}
/**
- * Remove user JavaScript from scripts to load
+ * Show what level of JavaScript / CSS untrustworthiness is allowed on this page
+ * @see ResourceLoaderModule::$origin
+ * @param $type String ResourceLoaderModule TYPE_ constant
+ * @return Int ResourceLoaderModule ORIGIN_ class constant
*/
- public function disallowUserJs() {
- $this->mAllowUserJs = false;
+ public function getAllowedModules( $type ){
+ if( $type == ResourceLoaderModule::TYPE_COMBINED ){
+ return min( array_values( $this->mAllowedModules ) );
+ } else {
+ return isset( $this->mAllowedModules[$type] )
+ ? $this->mAllowedModules[$type]
+ : ResourceLoaderModule::ORIGIN_ALL;
+ }
}
/**
- * Return whether user JavaScript is allowed for this page
- *
- * @return Boolean
+ * Set the highest level of CSS/JS untrustworthiness allowed
+ * @param $type String ResourceLoaderModule TYPE_ constant
+ * @param $level Int ResourceLoaderModule class constant
*/
- public function isUserJsAllowed() {
- return $this->mAllowUserJs;
+ public function setAllowedModules( $type, $level ){
+ $this->mAllowedModules[$type] = $level;
+ }
+
+ /**
+ * As for setAllowedModules(), but don't inadvertantly make the page more accessible
+ * @param $type String
+ * @param $level Int ResourceLoaderModule class constant
+ */
+ public function reduceAllowedModules( $type, $level ){
+ $this->mAllowedModules[$type] = min( $this->getAllowedModules($type), $level );
}
/**
@@ -1001,19 +1214,11 @@ class OutputPage {
}
/**
- * @deprecated use parserOptions() instead
- */
- public function setParserOptions( $options ) {
- wfDeprecated( __METHOD__ );
- return $this->parserOptions( $options );
- }
-
- /**
* Get/set the ParserOptions object to use for wikitext parsing
*
* @param $options either the ParserOption to use or null to only get the
* current ParserOption object
- * @return current ParserOption object
+ * @return ParserOptions object
*/
public function parserOptions( $options = null ) {
if ( !$this->mParserOptions ) {
@@ -1035,7 +1240,7 @@ class OutputPage {
}
/**
- * Get the current revision ID
+ * Get the displayed revision ID
*
* @return Integer
*/
@@ -1044,6 +1249,49 @@ class OutputPage {
}
/**
+ * Set the displayed file version
+ *
+ * @param $file File|false
+ * @return Mixed: previous value
+ */
+ public function setFileVersion( $file ) {
+ $val = null;
+ if ( $file instanceof File && $file->exists() ) {
+ $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() );
+ }
+ return wfSetVar( $this->mFileVersion, $val, true );
+ }
+
+ /**
+ * Get the displayed file version
+ *
+ * @return Array|null ('time' => MW timestamp, 'sha1' => sha1)
+ */
+ public function getFileVersion() {
+ return $this->mFileVersion;
+ }
+
+ /**
+ * Get the templates used on this page
+ *
+ * @return Array (namespace => dbKey => revId)
+ * @since 1.18
+ */
+ public function getTemplateIds() {
+ return $this->mTemplateIds;
+ }
+
+ /**
+ * Get the files used on this page
+ *
+ * @return Array (dbKey => array('time' => MW timestamp or null, 'sha1' => sha1 or ''))
+ * @since 1.18
+ */
+ public function getImageTimeKeys() {
+ return $this->mImageTimeKeys;
+ }
+
+ /**
* Convert wikitext to HTML and add it to the buffer
* Default assumes that the current page title will be used.
*
@@ -1067,7 +1315,7 @@ class OutputPage {
}
/**
- * Add wikitext with a custom Title object and
+ * Add wikitext with a custom Title object and tidy enabled.
*
* @param $text String: wikitext
* @param $title Title object
@@ -1119,43 +1367,6 @@ class OutputPage {
}
/**
- * Add wikitext to the buffer, assuming that this is the primary text for a page view
- * Saves the text into the parser cache if possible.
- *
- * @param $text String: wikitext
- * @param $article Article object
- * @param $cache Boolean
- * @deprecated Use Article::outputWikitext
- */
- public function addPrimaryWikiText( $text, $article, $cache = true ) {
- global $wgParser;
-
- 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->isCacheable() ) {
- $parserCache = ParserCache::singleton();
- $parserCache->save( $parserOutput, $article, $popts );
- }
-
- $this->addParserOutput( $parserOutput );
- }
-
- /**
- * @deprecated use addWikiTextTidy()
- */
- public function addSecondaryWikiText( $text, $linestart = true ) {
- wfDeprecated( __METHOD__ );
- $this->addWikiTextTitleTidy( $text, $this->getTitle(), $linestart );
- }
-
- /**
* Add a ParserOutput object, but without Html
*
* @param $parserOutput ParserOutput object
@@ -1173,14 +1384,22 @@ class OutputPage {
$this->mNoGallery = $parserOutput->getNoGallery();
$this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
$this->addModules( $parserOutput->getModules() );
- // Versioning...
- foreach ( (array)$parserOutput->mTemplateIds as $ns => $dbks ) {
+ $this->addModuleScripts( $parserOutput->getModuleScripts() );
+ $this->addModuleStyles( $parserOutput->getModuleStyles() );
+ $this->addModuleMessages( $parserOutput->getModuleMessages() );
+
+ // Template versioning...
+ foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
if ( isset( $this->mTemplateIds[$ns] ) ) {
$this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
} else {
$this->mTemplateIds[$ns] = $dbks;
}
}
+ // File versioning...
+ foreach ( (array)$parserOutput->getImageTimeKeys() as $dbk => $data ) {
+ $this->mImageTimeKeys[$dbk] = $data;
+ }
// Hooks registered in the object
global $wgParserOutputHooks;
@@ -1227,24 +1446,37 @@ class OutputPage {
* @param $interface Boolean: use interface language ($wgLang instead of
* $wgContLang) while parsing language sensitive magic
* words like GRAMMAR and PLURAL
+ * @param $language Language object: target language object, will override
+ * $interface
* @return String: HTML
*/
- public function parse( $text, $linestart = true, $interface = false ) {
+ public function parse( $text, $linestart = true, $interface = false, $language = null ) {
global $wgParser;
+
if( is_null( $this->getTitle() ) ) {
throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
}
+
$popts = $this->parserOptions();
if ( $interface ) {
$popts->setInterfaceMessage( true );
}
+ if ( $language !== null ) {
+ $oldLang = $popts->setTargetLanguage( $language );
+ }
+
$parserOutput = $wgParser->parse(
$text, $this->getTitle(), $popts,
$linestart, true, $this->mRevisionId
);
+
if ( $interface ) {
$popts->setInterfaceMessage( false );
}
+ if ( $language !== null ) {
+ $popts->setTargetLanguage( $oldLang );
+ }
+
return $parserOutput->getText();
}
@@ -1270,24 +1502,6 @@ class OutputPage {
}
/**
- * @deprecated
- *
- * @param $article Article
- * @return Boolean: true if successful, else false.
- */
- public function tryParserCache( &$article ) {
- wfDeprecated( __METHOD__ );
- $parserOutput = ParserCache::singleton()->get( $article, $article->getParserOptions() );
-
- if ( $parserOutput !== false ) {
- $this->addParserOutput( $parserOutput );
- return true;
- } else {
- return false;
- }
- }
-
- /**
* Set the value of the "s-maxage" part of the "Cache-control" HTTP header
*
* @param $maxage Integer: maximum cache time on the Squid, in seconds.
@@ -1299,7 +1513,9 @@ class OutputPage {
/**
* Use enableClientCache(false) to force it to send nocache headers
*
- * @param $state ??
+ * @param $state bool
+ *
+ * @return bool
*/
public function enableClientCache( $state ) {
return wfSetVar( $this->mEnableClientCache, $state );
@@ -1334,9 +1550,9 @@ class OutputPage {
* @return Boolean
*/
function uncacheableBecauseRequestVars() {
- global $wgRequest;
- return $wgRequest->getText( 'useskin', false ) === false
- && $wgRequest->getText( 'uselang', false ) === false;
+ $request = $this->getRequest();
+ return $request->getText( 'useskin', false ) === false
+ && $request->getText( 'uselang', false ) === false;
}
/**
@@ -1346,8 +1562,7 @@ class OutputPage {
* @return Boolean
*/
function haveCacheVaryCookies() {
- global $wgRequest;
- $cookieHeader = $wgRequest->getHeader( 'cookie' );
+ $cookieHeader = $this->getRequest()->getHeader( 'cookie' );
if ( $cookieHeader === false ) {
return false;
}
@@ -1367,8 +1582,8 @@ class OutputPage {
* Add an HTTP header that will influence on the cache
*
* @param $header String: header name
- * @param $option either an Array or null
- * @fixme Document the $option parameter; it appears to be for
+ * @param $option Array|null
+ * @todo FIXME: Document the $option parameter; it appears to be for
* X-Vary-Options but what format is acceptable?
*/
public function addVaryHeader( $header, $option = null ) {
@@ -1381,7 +1596,7 @@ class OutputPage {
$this->mVaryHeader[$header] = $option;
}
}
- $this->mVaryHeader[$header] = array_unique( $this->mVaryHeader[$header] );
+ $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] );
}
/**
@@ -1420,8 +1635,8 @@ class OutputPage {
* /w/index.php?title=Main_page&variant=zh-cn should never be served.
*/
function addAcceptLanguage() {
- global $wgRequest, $wgContLang;
- if( !$wgRequest->getCheck( 'variant' ) && $wgContLang->hasVariants() ) {
+ global $wgContLang;
+ if( !$this->getRequest()->getCheck( 'variant' ) && $wgContLang->hasVariants() ) {
$variants = $wgContLang->getVariants();
$aloption = array();
foreach ( $variants as $variant ) {
@@ -1429,7 +1644,7 @@ class OutputPage {
continue;
} 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.
@@ -1446,12 +1661,14 @@ class OutputPage {
}
/**
- * Set a flag which will cause an X-Frame-Options header appropriate for
- * edit pages to be sent. The header value is controlled by
+ * Set a flag which will cause an X-Frame-Options header appropriate for
+ * edit pages to be sent. The header value is controlled by
* $wgEditPageFrameOptions.
*
- * This is the default for special pages. If you display a CSRF-protected
+ * This is the default for special pages. If you display a CSRF-protected
* form on an ordinary view page, then you need to call this function.
+ *
+ * @param $enable bool
*/
public function preventClickjacking( $enable = true ) {
$this->mPreventClickjacking = $enable;
@@ -1467,9 +1684,11 @@ class OutputPage {
}
/**
- * Get the X-Frame-Options header value (without the name part), or false
- * if there isn't one. This is used by Skin to determine whether to enable
+ * Get the X-Frame-Options header value (without the name part), or false
+ * if there isn't one. This is used by Skin to determine whether to enable
* JavaScript frame-breaking, for clients that don't support X-Frame-Options.
+ *
+ * @return string
*/
public function getFrameOptions() {
global $wgBreakFrames, $wgEditPageFrameOptions;
@@ -1484,9 +1703,9 @@ class OutputPage {
* Send cache control HTTP headers
*/
public function sendCacheControl() {
- global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest, $wgUseXVO;
+ global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgUseXVO;
- $response = $wgRequest->response();
+ $response = $this->getRequest()->response();
if ( $wgUseETag && $this->mETag ) {
$response->header( "ETag: $this->mETag" );
}
@@ -1553,58 +1772,13 @@ class OutputPage {
*
* @param $code Integer: status code
* @return String or null: message or null if $code is not in the list of
- * messages
+ * messages
+ *
+ * @deprecated since 1.18 Use HttpStatus::getMessage() instead.
*/
public static function getStatusMessage( $code ) {
- static $statusMessage = array(
- 100 => 'Continue',
- 101 => 'Switching Protocols',
- 102 => 'Processing',
- 200 => 'OK',
- 201 => 'Created',
- 202 => 'Accepted',
- 203 => 'Non-Authoritative Information',
- 204 => 'No Content',
- 205 => 'Reset Content',
- 206 => 'Partial Content',
- 207 => 'Multi-Status',
- 300 => 'Multiple Choices',
- 301 => 'Moved Permanently',
- 302 => 'Found',
- 303 => 'See Other',
- 304 => 'Not Modified',
- 305 => 'Use Proxy',
- 307 => 'Temporary Redirect',
- 400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Timeout',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Large',
- 415 => 'Unsupported Media Type',
- 416 => 'Request Range Not Satisfiable',
- 417 => 'Expectation Failed',
- 422 => 'Unprocessable Entity',
- 423 => 'Locked',
- 424 => 'Failed Dependency',
- 500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Timeout',
- 505 => 'HTTP Version Not Supported',
- 507 => 'Insufficient Storage'
- );
- return isset( $statusMessage[$code] ) ? $statusMessage[$code] : null;
+ wfDeprecated( __METHOD__ );
+ return HttpStatus::getMessage( $code );
}
/**
@@ -1612,110 +1786,68 @@ class OutputPage {
* the object, let's actually output it:
*/
public function output() {
- global $wgUser, $wgOutputEncoding, $wgRequest;
- global $wgLanguageCode, $wgDebugRedirects, $wgMimeType;
- global $wgUseAjax, $wgAjaxWatch;
- global $wgEnableMWSuggest, $wgUniversalEditButton;
- global $wgArticle;
+ global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP;
if( $this->mDoNothing ) {
return;
}
+
wfProfileIn( __METHOD__ );
+
+ $response = $this->getRequest()->response();
+
if ( $this->mRedirect != '' ) {
# Standards require redirect URLs to be absolute
- $this->mRedirect = wfExpandUrl( $this->mRedirect );
+ $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
if( $this->mRedirectCode == '301' || $this->mRedirectCode == '303' ) {
if( !$wgDebugRedirects ) {
- $message = self::getStatusMessage( $this->mRedirectCode );
- $wgRequest->response()->header( "HTTP/1.1 {$this->mRedirectCode} $message" );
+ $message = HttpStatus::getMessage( $this->mRedirectCode );
+ $response->header( "HTTP/1.1 {$this->mRedirectCode} $message" );
}
$this->mLastModified = wfTimestamp( TS_RFC2822 );
}
+ if ( $wgVaryOnXFP ) {
+ $this->addVaryHeader( 'X-Forwarded-Proto' );
+ }
$this->sendCacheControl();
- $wgRequest->response()->header( "Content-Type: text/html; charset=utf-8" );
+ $response->header( "Content-Type: text/html; charset=utf-8" );
if( $wgDebugRedirects ) {
$url = htmlspecialchars( $this->mRedirect );
print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
print "<p>Location: <a href=\"$url\">$url</a></p>\n";
print "</body>\n</html>\n";
} else {
- $wgRequest->response()->header( 'Location: ' . $this->mRedirect );
+ $response->header( 'Location: ' . $this->mRedirect );
}
wfProfileOut( __METHOD__ );
return;
} elseif ( $this->mStatusCode ) {
- $message = self::getStatusMessage( $this->mStatusCode );
+ $message = HttpStatus::getMessage( $this->mStatusCode );
if ( $message ) {
- $wgRequest->response()->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message );
+ $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->addModules( 'mediawiki.legacy.ajax' );
-
- wfRunHooks( 'AjaxAddScript', array( &$this ) );
-
- if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
- $this->addModules( 'mediawiki.legacy.ajaxwatch' );
- }
-
- if ( $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ) {
- $this->addModules( 'mediawiki.legacy.mwsuggest' );
- }
- }
-
- if( $wgUser->getBoolOption( 'editsectiononrightclick' ) ) {
- $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' );
- $this->addLink( array(
- 'rel' => 'alternate',
- 'type' => 'application/x-wiki',
- 'title' => $msg,
- 'href' => $this->getTitle()->getLocalURL( 'action=edit' )
- ) );
- // Alternate edit link
- $this->addLink( array(
- 'rel' => 'edit',
- 'title' => $msg,
- 'href' => $this->getTitle()->getLocalURL( 'action=edit' )
- ) );
- }
- }
-
-
# Buffer output; final headers may depend on later processing
ob_start();
- $wgRequest->response()->header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" );
- $wgRequest->response()->header( 'Content-language: ' . $wgLanguageCode );
+ $response->header( "Content-type: $wgMimeType; charset=UTF-8" );
+ $response->header( 'Content-language: ' . $wgLanguageCode );
// Prevent framing, if requested
$frameOptions = $this->getFrameOptions();
if ( $frameOptions ) {
- $wgRequest->response()->header( "X-Frame-Options: $frameOptions" );
+ $response->header( "X-Frame-Options: $frameOptions" );
}
if ( $this->mArticleBodyOnly ) {
$this->out( $this->mBodytext );
} else {
+ $this->addDefaultModules();
+
+ $sk = $this->getSkin();
+
// Hook that allows last minute changes to the output page, e.g.
// adding of CSS or Javascript by extensions.
wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) );
@@ -1731,127 +1863,31 @@ class OutputPage {
}
/**
- * Actually output something with print(). Performs an iconv to the
- * output encoding, if needed.
+ * Actually output something with print().
*
* @param $ins String: the string to output
*/
public function out( $ins ) {
- global $wgInputEncoding, $wgOutputEncoding, $wgContLang;
- if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
- $outs = $ins;
- } else {
- $outs = $wgContLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
- if ( false === $outs ) {
- $outs = $ins;
- }
- }
- print $outs;
- }
-
- /**
- * @todo document
- */
- public static function setEncodings() {
- global $wgInputEncoding, $wgOutputEncoding;
-
- $wgInputEncoding = strtolower( $wgInputEncoding );
-
- if ( empty( $_SERVER['HTTP_ACCEPT_CHARSET'] ) ) {
- $wgOutputEncoding = strtolower( $wgOutputEncoding );
- return;
- }
- $wgOutputEncoding = $wgInputEncoding;
- }
-
- /**
- * @deprecated use wfReportTime() instead.
- *
- * @return String
- */
- public function reportTime() {
- wfDeprecated( __METHOD__ );
- $time = wfReportTime();
- return $time;
+ print $ins;
}
/**
* Produce a "user is blocked" page.
- *
- * @param $return Boolean: whether to have a "return to $wgTitle" message or not.
- * @return nothing
+ * @deprecated since 1.18
*/
- function blockedPage( $return = true ) {
- global $wgUser, $wgContLang, $wgLang;
-
- $this->setPageTitle( wfMsg( 'blockedtitle' ) );
- $this->setRobotPolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
-
- $name = User::whoIs( $wgUser->blockedBy() );
- $reason = $wgUser->blockedFor();
- if( $reason == '' ) {
- $reason = wfMsg( 'blockednoreason' );
- }
- $blockTimestamp = $wgLang->timeanddate(
- wfTimestamp( TS_MW, $wgUser->mBlock->mTimestamp ), true
- );
- $ip = wfGetIP();
-
- $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
-
- $blockid = $wgUser->mBlock->mId;
-
- $blockExpiry = $wgUser->mBlock->mExpiry;
- if ( $blockExpiry == 'infinity' ) {
- // Entry in database (table ipblocks) is 'infinity' but 'ipboptions' uses 'infinite' or 'indefinite'
- // Search for localization in 'ipboptions'
- $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
- );
- }
-
- if ( $wgUser->mBlock->mAuto ) {
- $msg = 'autoblockedtext';
- } else {
- $msg = 'blockedtext';
- }
-
- /* $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. */
- $intended = $wgUser->mBlock->mAddress;
-
- $this->addWikiMsg(
- $msg, $link, $reason, $ip, $name, $blockid, $blockExpiry,
- $intended, $blockTimestamp
- );
-
- # Don't auto-return to special pages
- if( $return ) {
- $return = $this->getTitle()->getNamespace() > -1 ? $this->getTitle() : null;
- $this->returnToMain( null, $return );
- }
+ function blockedPage() {
+ throw new UserBlockedError( $this->getUser()->mBlock );
}
/**
* Output a standard error page
*
+ * showErrorPage( 'titlemsg', 'pagetextmsg', array( 'param1', 'param2' ) );
+ * showErrorPage( 'titlemsg', $messageObject );
+ *
* @param $title String: message key for page title
- * @param $msg String: message key for page text
- * @param $params Array: message parameters
+ * @param $msg Mixed: message key (string) for page text, or a Message object
+ * @param $params Array: message parameters; ignored if $msg is a Message object
*/
public function showErrorPage( $title, $msg, $params = array() ) {
if ( $this->getTitle() ) {
@@ -1865,9 +1901,11 @@ class OutputPage {
$this->mRedirect = '';
$this->mBodytext = '';
- array_unshift( $params, 'parse' );
- array_unshift( $params, $msg );
- $this->addHTML( call_user_func_array( 'wfMsgExt', $params ) );
+ if ( $msg instanceof Message ){
+ $this->addHTML( $msg->parse() );
+ } else {
+ $this->addWikiMsgArray( $msg, $params );
+ }
$this->returnToMain();
}
@@ -1910,60 +1948,35 @@ class OutputPage {
/**
* Display an error page noting that a given permission bit is required.
- *
+ * @deprecated since 1.18, just throw the exception directly
* @param $permission String: key required
*/
public function permissionRequired( $permission ) {
- global $wgLang;
-
- $this->setPageTitle( wfMsg( 'badaccess' ) );
- $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
- $this->setRobotPolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
- $this->mBodytext = '';
-
- $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
- User::getGroupsWithPermission( $permission ) );
- if( $groups ) {
- $this->addWikiMsg(
- 'badaccess-groups',
- $wgLang->commaList( $groups ),
- count( $groups )
- );
- } else {
- $this->addWikiMsg( 'badaccess-group0' );
- }
- $this->returnToMain();
+ throw new PermissionsError( $permission );
}
/**
* Produce the stock "please login to use the wiki" page
*/
public function loginToUse() {
- global $wgUser;
-
- if( $wgUser->isLoggedIn() ) {
- $this->permissionRequired( 'read' );
- return;
+ if( $this->getUser()->isLoggedIn() ) {
+ throw new PermissionsError( 'read' );
}
- $skin = $wgUser->getSkin();
-
$this->setPageTitle( wfMsg( 'loginreqtitle' ) );
$this->setHtmlTitle( wfMsg( 'errorpagetitle' ) );
$this->setRobotPolicy( 'noindex,nofollow' );
- $this->setArticleFlag( false );
+ $this->setArticleRelated( false );
$loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
- $loginLink = $skin->link(
+ $loginLink = Linker::linkKnown(
$loginTitle,
wfMsgHtml( 'loginreqlink' ),
array(),
- array( 'returnto' => $this->getTitle()->getPrefixedText() ),
- array( 'known', 'noclasses' )
+ array( 'returnto' => $this->getTitle()->getPrefixedText() )
);
- $this->addHTML( wfMsgWikiHtml( 'loginreqpagetext', $loginLink ) );
- $this->addHTML( "\n<!--" . $this->getTitle()->getPrefixedUrl() . '-->' );
+ $this->addHTML( wfMessage( 'loginreqpagetext' )->rawParams( $loginLink )->parse() .
+ "\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
@@ -1976,7 +1989,7 @@ class OutputPage {
/**
* Format a list of error messages
*
- * @param $errors An array of arrays returned by Title::getUserPermissionsErrors
+ * @param $errors Array of arrays returned by Title::getUserPermissionsErrors
* @param $action String: action that was denied or null if unknown
* @return String: the wikitext error-messages, formatted into a list.
*/
@@ -2031,9 +2044,6 @@ class OutputPage {
* @param $action String: action that was denied or null if unknown
*/
public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
- global $wgUser;
- $skin = $wgUser->getSkin();
-
$this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
@@ -2048,7 +2058,7 @@ class OutputPage {
if( $source ) {
$this->setPageTitle( wfMsg( 'viewsource' ) );
$this->setSubtitle(
- wfMsg( 'viewsourcefor', $skin->linkKnown( $this->getTitle() ) )
+ wfMsg( 'viewsourcefor', Linker::linkKnown( $this->getTitle() ) )
);
} else {
$this->setPageTitle( wfMsg( 'badaccess' ) );
@@ -2056,9 +2066,7 @@ class OutputPage {
$this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
} else {
// Wiki is read only
- $this->setPageTitle( wfMsg( 'readonly' ) );
- $reason = wfReadOnlyReason();
- $this->wrapWikiMsg( "<div class='mw-readonly-error'>\n$1\n</div>", array( 'readonlytext', $reason ) );
+ throw new ReadOnlyError;
}
// Show source, if supplied
@@ -2068,17 +2076,17 @@ class OutputPage {
$params = array(
'id' => 'wpTextbox1',
'name' => 'wpTextbox1',
- 'cols' => $wgUser->getOption( 'cols' ),
- 'rows' => $wgUser->getOption( 'rows' ),
+ 'cols' => $this->getUser()->getOption( 'cols' ),
+ 'rows' => $this->getUser()->getOption( 'rows' ),
'readonly' => 'readonly'
);
$this->addHTML( Html::element( 'textarea', $params, $source ) );
// Show templates used by this article
- $skin = $wgUser->getSkin();
$article = new Article( $this->getTitle() );
+ $templates = Linker::formatTemplates( $article->getUsedTemplates() );
$this->addHTML( "<div class='templatesUsed'>
-{$skin->formatTemplates( $article->getUsedTemplates() )}
+$templates
</div>
" );
}
@@ -2091,51 +2099,32 @@ class OutputPage {
}
}
- /** @deprecated */
- public function errorpage( $title, $msg ) {
- wfDeprecated( __METHOD__ );
- throw new ErrorPageError( $title, $msg );
- }
-
- /** @deprecated */
- public function databaseError( $fname, $sql, $error, $errno ) {
- throw new MWException( "OutputPage::databaseError is obsolete\n" );
- }
-
- /** @deprecated */
- public function fatalError( $message ) {
- wfDeprecated( __METHOD__ );
- throw new FatalError( $message );
- }
-
- /** @deprecated */
- public function unexpectedValueError( $name, $val ) {
- wfDeprecated( __METHOD__ );
- throw new FatalError( wfMsg( 'unexpected', $name, $val ) );
- }
-
- /** @deprecated */
- public function fileCopyError( $old, $new ) {
- wfDeprecated( __METHOD__ );
- throw new FatalError( wfMsg( 'filecopyerror', $old, $new ) );
- }
-
- /** @deprecated */
- public function fileRenameError( $old, $new ) {
- wfDeprecated( __METHOD__ );
- throw new FatalError( wfMsg( 'filerenameerror', $old, $new ) );
- }
-
- /** @deprecated */
- public function fileDeleteError( $name ) {
- wfDeprecated( __METHOD__ );
- throw new FatalError( wfMsg( 'filedeleteerror', $name ) );
+ /**
+ * Turn off regular page output and return an error reponse
+ * for when rate limiting has triggered.
+ */
+ public function rateLimited() {
+ throw new ThrottledError;
}
- /** @deprecated */
- public function fileNotFoundError( $name ) {
- wfDeprecated( __METHOD__ );
- throw new FatalError( wfMsg( 'filenotfound', $name ) );
+ /**
+ * Show a warning about slave lag
+ *
+ * If the lag is higher than $wgSlaveLagCritical seconds,
+ * then the warning is a bit more obvious. If the lag is
+ * lower than $wgSlaveLagWarning, then no warning is shown.
+ *
+ * @param $lag Integer: slave lag
+ */
+ public function showLagWarning( $lag ) {
+ global $wgSlaveLagWarning, $wgSlaveLagCritical;
+ if( $lag >= $wgSlaveLagWarning ) {
+ $message = $lag < $wgSlaveLagCritical
+ ? 'lag-warn-normal'
+ : 'lag-warn-high';
+ $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
+ $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getContext()->getLang()->formatNum( $lag ) ) );
+ }
}
public function showFatalError( $message ) {
@@ -2171,15 +2160,14 @@ class OutputPage {
* Add a "return to" link pointing to a specified title
*
* @param $title Title to link
- * @param $query String: query string
+ * @param $query String query string
* @param $text String text of the link (input is not escaped)
*/
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, $text, array(), $query )
+ Linker::link( $title, $text, array(), $query )
);
$this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
}
@@ -2193,14 +2181,12 @@ class OutputPage {
* @param $returntoquery String: query string for the return to link
*/
public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
- global $wgRequest;
-
if ( $returnto == null ) {
- $returnto = $wgRequest->getText( 'returnto' );
+ $returnto = $this->getRequest()->getText( 'returnto' );
}
if ( $returntoquery == null ) {
- $returntoquery = $wgRequest->getText( 'returntoquery' );
+ $returntoquery = $this->getRequest()->getText( 'returntoquery' );
}
if ( $returnto === '' ) {
@@ -2225,17 +2211,16 @@ class OutputPage {
* @return String: The doctype, opening <html>, and head element.
*/
public function headElement( Skin $sk, $includeStyle = true ) {
- global $wgOutputEncoding, $wgMimeType;
- global $wgUseTrackbacks, $wgHtml5;
- global $wgUser, $wgRequest, $wgLang;
+ global $wgContLang, $wgUseTrackbacks;
+ $userdir = $this->getLang()->getDir();
+ $sitedir = $wgContLang->getDir();
if ( $sk->commonPrintStylesheet() ) {
$this->addModuleStyles( 'mediawiki.legacy.wikiprintable' );
}
$sk->setupUserCss( $this );
- $lang = wfUILang();
- $ret = Html::htmlHeader( array( 'lang' => $lang->getCode(), 'dir' => $lang->getDir() ) );
+ $ret = Html::htmlHeader( array( 'lang' => $this->getLang()->getCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
if ( $this->getHTMLTitle() == '' ) {
$this->setHTMLTitle( wfMsg( 'pagetitle', $this->getPageTitle() ) );
@@ -2247,19 +2232,12 @@ class OutputPage {
$ret .= "$openHead\n";
}
- if ( $wgHtml5 ) {
- # More succinct than <meta http-equiv=Content-Type>, has the
- # same effect
- $ret .= Html::element( 'meta', array( 'charset' => $wgOutputEncoding ) ) . "\n";
- } else {
- $this->addMeta( 'http:Content-Type', "$wgMimeType; charset=$wgOutputEncoding" );
- }
-
$ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
$ret .= implode( "\n", array(
- $this->getHeadLinks( $sk ),
+ $this->getHeadLinks( $sk, true ),
$this->buildCssLinks( $sk ),
+ $this->getHeadScripts( $sk ),
$this->getHeadItems()
) );
@@ -2275,12 +2253,12 @@ class OutputPage {
$bodyAttrs = array();
# Crazy edit-on-double-click stuff
- $action = $wgRequest->getVal( 'action', 'view' );
+ $action = $this->getRequest()->getVal( 'action', 'view' );
if (
$this->getTitle()->getNamespace() != NS_SPECIAL &&
- !in_array( $action, array( 'edit', 'submit' ) ) &&
- $wgUser->getOption( 'editondblclick' )
+ in_array( $action, array( 'view', 'purge' ) ) &&
+ $this->getUser()->getOption( 'editondblclick' )
)
{
$editUrl = $this->getTitle()->getLocalUrl( $sk->editUrlOptions() );
@@ -2288,24 +2266,15 @@ class OutputPage {
Xml::escapeJsString( $editUrl ) . "'";
}
- # Class bloat
- $dir = wfUILang()->getDir();
- $bodyAttrs['class'] = "mediawiki $dir";
+ # Classes for LTR/RTL directionality support
+ $bodyAttrs['class'] = "mediawiki $userdir sitedir-$sitedir";
- if ( $wgLang->capitalizeAllNouns() ) {
+ if ( $this->getContext()->getLang()->capitalizeAllNouns() ) {
# A <body> class is probably not the best way to do this . . .
$bodyAttrs['class'] .= ' capitalize-all-nouns';
}
- $bodyAttrs['class'] .= ' ns-' . $this->getTitle()->getNamespace();
- if ( $this->getTitle()->getNamespace() == NS_SPECIAL ) {
- $bodyAttrs['class'] .= ' ns-special';
- } elseif ( $this->getTitle()->isTalkPage() ) {
- $bodyAttrs['class'] .= ' ns-talk';
- } else {
- $bodyAttrs['class'] .= ' ns-subject';
- }
- $bodyAttrs['class'] .= ' ' . Sanitizer::escapeClass( 'page-' . $this->getTitle()->getPrefixedText() );
- $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $wgUser->getSkin()->getSkinName() );
+ $bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() );
+ $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
$sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need
wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
@@ -2316,52 +2285,92 @@ class OutputPage {
}
/**
+ * Add the default ResourceLoader modules to this object
+ */
+ private function addDefaultModules() {
+ global $wgIncludeLegacyJavaScript, $wgUseAjax,
+ $wgAjaxWatch, $wgEnableMWSuggest;
+
+ // Add base resources
+ $this->addModules( array(
+ 'mediawiki.user',
+ 'mediawiki.util',
+ 'mediawiki.page.startup',
+ 'mediawiki.page.ready',
+ ) );
+ if ( $wgIncludeLegacyJavaScript ){
+ $this->addModules( 'mediawiki.legacy.wikibits' );
+ }
+
+ // Add various resources if required
+ if ( $wgUseAjax ) {
+ $this->addModules( 'mediawiki.legacy.ajax' );
+
+ wfRunHooks( 'AjaxAddScript', array( &$this ) );
+
+ if( $wgAjaxWatch && $this->getUser()->isLoggedIn() ) {
+ $this->addModules( 'mediawiki.action.watch.ajax' );
+ }
+
+ if ( $wgEnableMWSuggest && !$this->getUser()->getOption( 'disablesuggest', false ) ) {
+ $this->addModules( 'mediawiki.legacy.mwsuggest' );
+ }
+ }
+
+ if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) {
+ $this->addModules( 'mediawiki.action.view.rightClickEdit' );
+ }
+ }
+
+ /**
* Get a ResourceLoader object associated with this OutputPage
+ *
+ * @return ResourceLoader
*/
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 $only String ResourceLoaderModule TYPE_ class constant
* @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;
+ global $wgLoadScript, $wgResourceLoaderUseESI,
+ $wgResourceLoaderInlinePrivateModules;
// Lazy-load ResourceLoader
// TODO: Should this be a static function of ResourceLoader instead?
$baseQuery = array(
- 'lang' => $wgLang->getCode(),
+ 'lang' => $this->getContext()->getLang()->getCode(),
'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
'skin' => $skin->getSkinName(),
'only' => $only,
);
// Propagate printable and handheld parameters if present
- if ( $wgRequest->getBool( 'printable' ) ) {
+ if ( $this->isPrintable() ) {
$baseQuery['printable'] = 1;
}
- if ( $wgRequest->getBool( 'handheld' ) ) {
+ if ( $this->getRequest()->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 = '';
@@ -2371,12 +2380,21 @@ class OutputPage {
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 );
+ # Check that we're allowed to include this module on this page
+ if ( ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS )
+ && $only == ResourceLoaderModule::TYPE_SCRIPTS )
+ || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES )
+ && $only == ResourceLoaderModule::TYPE_STYLES )
+ )
+ {
+ continue;
+ }
$group = $module->getGroup();
if ( !isset( $groups[$group] ) ) {
@@ -2384,14 +2402,15 @@ class OutputPage {
}
$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();
+ if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
+ $query['user'] = $this->getUser()->getName();
}
-
+
// Create a fake request based on the one we are about to make so modules return
// correct timestamp and emptiness data
$context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
@@ -2405,12 +2424,12 @@ class OutputPage {
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' ) {
+ if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
$links .= Html::inlineStyle(
$resourceLoader->makeModuleResponse( $context, $modules )
);
@@ -2446,107 +2465,244 @@ class OutputPage {
$url .= '&*';
if ( $useESI && $wgResourceLoaderUseESI ) {
$esi = Xml::element( 'esi:include', array( 'src' => $url ) );
- if ( $only == 'styles' ) {
- $links .= Html::inlineStyle( $esi );
+ if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
+ $link = Html::inlineStyle( $esi );
} else {
- $links .= Html::inlineScript( $esi );
+ $link = Html::inlineScript( $esi );
}
} else {
// Automatically select style/script elements
- if ( $only === 'styles' ) {
- $links .= Html::linkedStyle( $url ) . "\n";
+ if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
+ $link = Html::linkedStyle( $url );
} else {
- $links .= Html::linkedScript( $url ) . "\n";
+ $link = Html::linkedScript( $url );
}
}
+
+ if( $group == 'noscript' ){
+ $links .= Html::rawElement( 'noscript', array(), $link ) . "\n";
+ } else {
+ $links .= $link . "\n";
+ }
}
return $links;
}
/**
- * Gets the global variables and mScripts; also adds userjs to the end if
- * enabled. Despite the name, these scripts are no longer put in the
- * <head> but at the bottom of the <body>
+ * JS stuff to put in the <head>. This is the startup module, config
+ * vars and modules marked with position 'top'
*
* @param $sk Skin object to use
* @return String: HTML fragment
*/
function getHeadScripts( Skin $sk ) {
- global $wgUser, $wgRequest, $wgUseSiteJs;
-
// Startup - this will immediately load jquery and mediawiki modules
- $scripts = $this->makeResourceLoaderLink( $sk, 'startup', 'scripts', true );
+ $scripts = $this->makeResourceLoaderLink( $sk, 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
- // 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";
+ // Load config before anything else
+ $scripts .= Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ ResourceLoader::makeConfigSetScript( $this->getJSVars() )
+ )
+ );
- // Script and Messages "only" requests
- $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleScripts(), 'scripts' );
- $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleMessages(), 'messages' );
+ // Script and Messages "only" requests marked for top inclusion
+ // Messages should go first
+ $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES );
+ $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS );
// Modules requests - let the client calculate dependencies and batch requests as it likes
- if ( $this->getModules() ) {
+ // Only load modules that have marked themselves for loading at the top
+ $modules = $this->getModules( true, 'top' );
+ if ( $modules ) {
$scripts .= Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mediaWiki.loader.load', array( $this->getModules() ) ) .
- Xml::encodeJsCall( 'mediaWiki.loader.go', array() )
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
)
- ) . "\n";
+ );
+ }
+
+ return $scripts;
+ }
+
+ /**
+ * JS stuff to put at the bottom of the <body>: modules marked with position 'bottom',
+ * legacy scripts ($this->mScripts), user preferences, site JS and user JS
+ *
+ * @param $sk Skin
+ *
+ * @return string
+ */
+ function getBottomScripts( Skin $sk ) {
+ global $wgUseSiteJs, $wgAllowUserJs;
+
+ // Script and Messages "only" requests marked for bottom inclusion
+ // Messages should go first
+ $scripts = $this->makeResourceLoaderLink( $sk, $this->getModuleMessages( true, 'bottom' ), ResourceLoaderModule::TYPE_MESSAGES );
+ $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleScripts( true, 'bottom' ), ResourceLoaderModule::TYPE_SCRIPTS );
+
+ // Modules requests - let the client calculate dependencies and batch requests as it likes
+ // Only load modules that have marked themselves for loading at the bottom
+ $modules = $this->getModules( true, 'bottom' );
+ if ( $modules ) {
+ $scripts .= Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
+ )
+ );
}
// Legacy Scripts
$scripts .= "\n" . $this->mScripts;
+ $userScripts = array( 'user.options', 'user.tokens' );
+
// Add site JS if enabled
if ( $wgUseSiteJs ) {
- $scripts .= $this->makeResourceLoaderLink( $sk, 'site', 'scripts' );
+ $scripts .= $this->makeResourceLoaderLink( $sk, 'site', ResourceLoaderModule::TYPE_SCRIPTS );
+ if( $this->getUser()->isLoggedIn() ){
+ $userScripts[] = 'user.groups';
+ }
}
- // 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() && $sk->userCanPreview( $action ) ) {
+ // Add user JS if enabled
+ if ( $wgAllowUserJs && $this->getUser()->isLoggedIn() ) {
+ $action = $this->getRequest()->getVal( 'action', 'view' );
+ if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $sk->userCanPreview( $action ) ) {
# XXX: additional security check/prompt?
- $scripts .= Html::inlineScript( "\n" . $wgRequest->getText( 'wpTextbox1' ) . "\n" ) . "\n";
+ $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
} else {
- $scripts .= $this->makeResourceLoaderLink(
- $sk, array( 'user', 'user.options' ), 'scripts'
- );
- $userOptionsAdded = true;
+ # @todo FIXME: This means that User:Me/Common.js doesn't load when previewing
+ # User:Me/Vector.js, and vice versa (bug26283)
+ $userScripts[] = 'user';
}
}
- if ( !$userOptionsAdded ) {
- $scripts .= $this->makeResourceLoaderLink( $sk, 'user.options', 'scripts' );
- }
-
+ $scripts .= $this->makeResourceLoaderLink( $sk, $userScripts, ResourceLoaderModule::TYPE_SCRIPTS );
+
return $scripts;
}
/**
- * Add default \<meta\> tags
+ * Add one or more variables to be set in mw.config in JavaScript.
+ *
+ * @param $key {String|Array} Key or array of key/value pars.
+ * @param $value {Mixed} Value of the configuration variable.
*/
- protected function addDefaultMeta() {
- global $wgVersion, $wgHtml5;
-
- static $called = false;
- if ( $called ) {
- # Don't run this twice
+ public function addJsConfigVars( $keys, $value ) {
+ if ( is_array( $keys ) ) {
+ foreach ( $keys as $key => $value ) {
+ $this->mJsConfigVars[$key] = $value;
+ }
return;
}
- $called = true;
- if ( !$wgHtml5 ) {
- $this->addMeta( 'http:Content-Style-Type', 'text/css' ); // bug 15835
+ $this->mJsConfigVars[$keys] = $value;
+ }
+
+
+ /**
+ * Get an array containing the variables to be set in mw.config in JavaScript.
+ *
+ * Do not add things here which can be evaluated in ResourceLoaderStartupScript
+ * - in other words, page-indendent/site-wide variables (without state).
+ * You will only be adding bloat to the html page and causing page caches to
+ * have to be purged on configuration changes.
+ */
+ protected function getJSVars() {
+ global $wgUseAjax, $wgEnableMWSuggest, $wgContLang;
+
+ $title = $this->getTitle();
+ $ns = $title->getNamespace();
+ $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
+ if ( $ns == NS_SPECIAL ) {
+ list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
+ } else {
+ $canonicalName = false; # bug 21115
+ }
+
+ $vars = array(
+ 'wgCanonicalNamespace' => $nsname,
+ 'wgCanonicalSpecialPageName' => $canonicalName,
+ 'wgNamespaceNumber' => $title->getNamespace(),
+ 'wgPageName' => $title->getPrefixedDBKey(),
+ 'wgTitle' => $title->getText(),
+ 'wgCurRevisionId' => $title->getLatestRevID(),
+ 'wgArticleId' => $title->getArticleId(),
+ 'wgIsArticle' => $this->isArticle(),
+ 'wgAction' => $this->getRequest()->getText( 'action', 'view' ),
+ 'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(),
+ 'wgUserGroups' => $this->getUser()->getEffectiveGroups(),
+ 'wgCategories' => $this->getCategories(),
+ 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
+ );
+ if ( $wgContLang->hasVariants() ) {
+ $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
}
- $this->addMeta( 'generator', "MediaWiki $wgVersion" );
+ foreach ( $title->getRestrictionTypes() as $type ) {
+ $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
+ }
+ if ( $wgUseAjax && $wgEnableMWSuggest && !$this->getUser()->getOption( 'disablesuggest', false ) ) {
+ $vars['wgSearchNamespaces'] = SearchEngine::userNamespaces( $this->getUser() );
+ }
+ if ( $title->isMainPage() ) {
+ $vars['wgIsMainPage'] = true;
+ }
+
+ // Allow extensions to add their custom variables to the mw.config map.
+ // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
+ // page-dependant but site-wide (without state).
+ // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
+ wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars ) );
+
+ // Merge in variables from addJsConfigVars last
+ return array_merge( $vars, $this->mJsConfigVars );
+ }
+
+ /**
+ * @param $sk Skin
+ * @param $addContentType bool
+ *
+ * @return string HTML tag links to be put in the header.
+ */
+ public function getHeadLinks( Skin $sk, $addContentType = false ) {
+ global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
+ $wgSitename, $wgVersion, $wgHtml5, $wgMimeType,
+ $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
+ $wgDisableLangConversion, $wgCanonicalLanguageLinks, $wgContLang,
+ $wgRightsPage, $wgRightsUrl;
+
+ $tags = array();
+
+ if ( $addContentType ) {
+ if ( $wgHtml5 ) {
+ # More succinct than <meta http-equiv=Content-Type>, has the
+ # same effect
+ $tags[] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
+ } else {
+ $tags[] = Html::element( 'meta', array(
+ 'http-equiv' => 'Content-Type',
+ 'content' => "$wgMimeType; charset=UTF-8"
+ ) );
+ $tags[] = Html::element( 'meta', array( // bug 15835
+ 'http-equiv' => 'Content-Style-Type',
+ 'content' => 'text/css'
+ ) );
+ }
+ }
+
+ $tags[] = Html::element( 'meta', array(
+ 'name' => 'generator',
+ 'content' => "MediaWiki $wgVersion",
+ ) );
$p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
if( $p !== 'index,follow' ) {
// http://www.robotstxt.org/wc/meta-user.html
// Only show if it's different from the default robots policy
- $this->addMeta( 'robots', $p );
+ $tags[] = Html::element( 'meta', array(
+ 'name' => 'robots',
+ 'content' => $p,
+ ) );
}
if ( count( $this->mKeywords ) > 0 ) {
@@ -2554,27 +2710,15 @@ class OutputPage {
"/<.*?" . ">/" => '',
"/_/" => ' '
);
- $this->addMeta(
- 'keywords',
- preg_replace(
+ $tags[] = Html::element( 'meta', array(
+ 'name' => 'keywords',
+ 'content' => 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( Skin $sk ) {
- global $wgFeed;
-
- // Ideally this should happen earlier, somewhere. :P
- $this->addDefaultMeta();
-
- $tags = array();
foreach ( $this->mMetatags as $tag ) {
if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
@@ -2590,16 +2734,117 @@ class OutputPage {
)
);
}
+
foreach ( $this->mLinktags as $tag ) {
$tags[] = Html::element( 'link', $tag );
}
- if( $wgFeed ) {
+ # Universal edit button
+ if ( $wgUniversalEditButton ) {
+ if ( $this->isArticleRelated() && $this->getTitle() && $this->getTitle()->quickUserCan( 'edit' )
+ && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create' ) ) ) {
+ // Original UniversalEditButton
+ $msg = wfMsg( 'edit' );
+ $tags[] = Html::element( 'link', array(
+ 'rel' => 'alternate',
+ 'type' => 'application/x-wiki',
+ 'title' => $msg,
+ 'href' => $this->getTitle()->getLocalURL( 'action=edit' )
+ ) );
+ // Alternate edit link
+ $tags[] = Html::element( 'link', array(
+ 'rel' => 'edit',
+ 'title' => $msg,
+ 'href' => $this->getTitle()->getLocalURL( 'action=edit' )
+ ) );
+ }
+ }
+
+ # Generally the order of the favicon and apple-touch-icon links
+ # should not matter, but Konqueror (3.5.9 at least) incorrectly
+ # uses whichever one appears later in the HTML source. Make sure
+ # apple-touch-icon is specified first to avoid this.
+ if ( $wgAppleTouchIcon !== false ) {
+ $tags[] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
+ }
+
+ if ( $wgFavicon !== false ) {
+ $tags[] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
+ }
+
+ # OpenSearch description link
+ $tags[] = Html::element( 'link', array(
+ 'rel' => 'search',
+ '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)
+ $tags[] = Html::element( 'link', array(
+ 'rel' => 'EditURI',
+ 'type' => 'application/rsd+xml',
+ // Output a protocol-relative URL here if $wgServer is protocol-relative
+ // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though
+ 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ),
+ ) );
+ }
+
+ # Language variants
+ if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks
+ && $wgContLang->hasVariants() ) {
+
+ $urlvar = $wgContLang->getURLVariant();
+
+ if ( !$urlvar ) {
+ $variants = $wgContLang->getVariants();
+ foreach ( $variants as $_v ) {
+ $tags[] = Html::element( 'link', array(
+ 'rel' => 'alternate',
+ 'hreflang' => $_v,
+ 'href' => $this->getTitle()->getLocalURL( '', $_v ) )
+ );
+ }
+ } else {
+ $tags[] = Html::element( 'link', array(
+ 'rel' => 'canonical',
+ 'href' => $this->getTitle()->getCanonicalUrl()
+ ) );
+ }
+ }
+
+ # Copyright
+ $copyright = '';
+ if ( $wgRightsPage ) {
+ $copy = Title::newFromText( $wgRightsPage );
+
+ if ( $copy ) {
+ $copyright = $copy->getLocalURL();
+ }
+ }
+
+ if ( !$copyright && $wgRightsUrl ) {
+ $copyright = $wgRightsUrl;
+ }
+
+ if ( $copyright ) {
+ $tags[] = Html::element( 'link', array(
+ 'rel' => 'copyright',
+ 'href' => $copyright )
+ );
+ }
+
+ # Feeds
+ if ( $wgFeed ) {
foreach( $this->getSyndicationLinks() as $format => $link ) {
- # Use the page name for the title (accessed through $wgTitle since
- # there's no other way). In principle, this could lead to issues
- # with having the same name for different feeds corresponding to
- # the same page, but we can't avoid that at this low a level.
+ # Use the page name for the title. In principle, this could
+ # lead to issues with having the same name for different feeds
+ # corresponding to the same page, but we can't avoid that at
+ # this low a level.
$tags[] = $this->feedLink(
$format,
@@ -2617,14 +2862,14 @@ class OutputPage {
# or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
# If so, use it instead.
- global $wgOverrideSiteFeed, $wgSitename, $wgAdvertisedFeedTypes;
$rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
if ( $wgOverrideSiteFeed ) {
foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
+ // Note, this->feedLink escapes the url.
$tags[] = $this->feedLink(
$type,
- htmlspecialchars( $feedUrl ),
+ $feedUrl,
wfMsg( "site-{$type}-feed", $wgSitename )
);
}
@@ -2638,7 +2883,6 @@ class OutputPage {
}
}
}
-
return implode( "\n", $tags );
}
@@ -2687,8 +2931,13 @@ class OutputPage {
/**
* Adds inline CSS styles
* @param $style_css Mixed: inline CSS
+ * @param $flip False or String: Set to 'flip' to flip the CSS if needed
*/
- public function addInlineStyle( $style_css ){
+ public function addInlineStyle( $style_css, $flip = false ) {
+ if( $flip === 'flip' && $this->getLang()->isRTL() ) {
+ # If wanted, and the interface is right-to-left, flip the CSS
+ $style_css = CSSJanus::transform( $style_css, true, false );
+ }
$this->mInlineStyles .= Html::inlineStyle( $style_css );
}
@@ -2696,12 +2945,14 @@ class OutputPage {
* 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
+ *
+ * @return string
*/
public function buildCssLinks( $sk ) {
$ret = '';
// Add ResourceLoader styles
// Split the styles into four groups
- $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array() );
+ $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() );
$resourceLoader = $this->getResourceLoader();
foreach ( $this->getModuleStyles() as $name ) {
$group = $resourceLoader->getModule( $name )->getGroup();
@@ -2714,19 +2965,19 @@ class OutputPage {
// 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' );
+ $ret .= $this->makeResourceLoaderLink( $sk, $styles['other'], ResourceLoaderModule::TYPE_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' => '' ) );
-
+ $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n";
+
// Add site, private and user styles
// 'private' at present only contains user.options, so put that before 'user'
// Any future private modules will likely have a similar user-specific character
- foreach ( array( 'site', 'private', 'user' ) as $group ) {
- $ret .= $this->makeResourceLoaderLink(
- $sk, array_merge( $styles['site'], $styles['user'] ), 'styles'
+ foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) {
+ $ret .= $this->makeResourceLoaderLink( $sk, $styles[$group],
+ ResourceLoaderModule::TYPE_STYLES
);
}
return $ret;
@@ -2753,8 +3004,7 @@ class OutputPage {
*/
protected function styleLink( $style, $options ) {
if( isset( $options['dir'] ) ) {
- $siteDir = wfUILang()->getDir();
- if( $siteDir != $options['dir'] ) {
+ if( $this->getLang()->getDir() != $options['dir'] ) {
return '';
}
}
@@ -2826,43 +3076,6 @@ class OutputPage {
}
/**
- * Turn off regular page output and return an error reponse
- * for when rate limiting has triggered.
- */
- public function rateLimited() {
- $this->setPageTitle( wfMsg( 'actionthrottled' ) );
- $this->setRobotPolicy( 'noindex,follow' );
- $this->setArticleRelated( false );
- $this->enableClientCache( false );
- $this->mRedirect = '';
- $this->clearHTML();
- $this->setStatusCode( 503 );
- $this->addWikiMsg( 'actionthrottledtext' );
-
- $this->returnToMain( null, $this->getTitle() );
- }
-
- /**
- * Show a warning about slave lag
- *
- * If the lag is higher than $wgSlaveLagCritical seconds,
- * then the warning is a bit more obvious. If the lag is
- * lower than $wgSlaveLagWarning, then no warning is shown.
- *
- * @param $lag Integer: slave lag
- */
- public function showLagWarning( $lag ) {
- global $wgSlaveLagWarning, $wgSlaveLagCritical, $wgLang;
- if( $lag >= $wgSlaveLagWarning ) {
- $message = $lag < $wgSlaveLagCritical
- ? 'lag-warn-normal'
- : 'lag-warn-high';
- $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
- $this->wrapWikiMsg( "$wrap\n", array( $message, $wgLang->formatNum( $lag ) ) );
- }
- }
-
- /**
* Add a wikitext-formatted message to the output.
* This is equivalent to:
*
@@ -2880,6 +3093,10 @@ class OutputPage {
* instead of a variable argument list.
*
* $options is passed through to wfMsgExt(), see that function for details.
+ *
+ * @param $name string
+ * @param $args array
+ * @param $options array
*/
public function addWikiMsgArray( $name, $args, $options = array() ) {
$options[] = 'parse';
@@ -2910,6 +3127,8 @@ class OutputPage {
* $wgOut->addWikiText( "<div class='error'>\n" . wfMsgNoTrans( 'some-error' ) . "\n</div>" );
*
* The newline after opening div is needed in some wikitext. See bug 19226.
+ *
+ * @param $wrap string
*/
public function wrapWikiMsg( $wrap /*, ...*/ ) {
$msgSpecs = func_get_args();
@@ -2931,7 +3150,7 @@ class OutputPage {
}
$s = str_replace( '$' . ( $n + 1 ), wfMsgExt( $name, $options, $args ), $s );
}
- $this->addHTML( $this->parse( $s, /*linestart*/true, /*uilang*/true ) );
+ $this->addWikiText( $s );
}
/**
@@ -2941,7 +3160,7 @@ class OutputPage {
* @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
+ * @deprecated since 1.17
*/
public function includeJQuery( $modules = array() ) {
return array();
diff --git a/includes/PHPVersionError.php b/includes/PHPVersionError.php
new file mode 100644
index 00000000..91502a4c
--- /dev/null
+++ b/includes/PHPVersionError.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Display something vaguely comprehensible in the event of a totally unrecoverable error.
+ * Does not assume access to *anything*; no globals, no autloader, no database, no localisation.
+ * Safe for PHP4 (and putting this here means that WebStart.php and GlobalSettings.php
+ * no longer need to be).
+ *
+ * Calling this function kills execution immediately.
+ *
+ * @param $entryPoint String Which entry point we're protecting. One of:
+ * - index.php
+ * - load.php
+ * - api.php
+ * - cli
+ *
+ * @note Since we can't rely on anything, the minimum PHP versions and MW current
+ * version are hardcoded here
+ */
+function wfPHPVersionError( $type ){
+ $mwVersion = '1.18';
+ $phpVersion = PHP_VERSION;
+ $message = "MediaWiki $mwVersion requires at least PHP version 5.2.3, you are using PHP $phpVersion.";
+ if( $type == 'index.php' ) {
+ $encLogo = htmlspecialchars(
+ str_replace( '//', '/', pathinfo( $_SERVER['SCRIPT_NAME'], PATHINFO_DIRNAME ) . '/'
+ ) . 'skins/common/images/mediawiki.png'
+ );
+
+ header( $_SERVER['SERVER_PROTOCOL'] . ' 500 MediaWiki configuration Error', true, 500 );
+ header( 'Content-type: text/html; charset=UTF-8' );
+ // Don't cache error pages! They cause no end of trouble...
+ header( 'Cache-control: none' );
+ header( 'Pragma: nocache' );
+
+$finalOutput = <<<HTML
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns='http://www.w3.org/1999/xhtml' lang='en'>
+ <head>
+ <title>MediaWiki {$mwVersion}</title>
+ <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
+ <style type='text/css' media='screen'>
+ body {
+ color: #000;
+ background-color: #fff;
+ font-family: sans-serif;
+ padding: 2em;
+ text-align: center;
+ }
+ p, img, h1 {
+ text-align: left;
+ margin: 0.5em 0;
+ }
+ h1 {
+ font-size: 120%;
+ }
+ </style>
+ </head>
+ <body>
+ <img src="{$encLogo}" alt='The MediaWiki logo' />
+ <h1>MediaWiki {$mwVersion} internal error</h1>
+ <div class='error'>
+ <p>
+ {$message}
+ </p>
+ <p>
+ Please consider <a href="http://www.php.net/downloads.php">upgrading your copy of PHP</a>.
+ PHP versions less than 5.3.0 are no longer supported by the PHP Group and will not receive
+ security or bugfix updates.
+ </p>
+ <p>
+ If for some reason you are unable to upgrade your PHP version, you will need to
+ <a href="http://www.mediawiki.org/wiki/Download">download</a> an older version
+ of MediaWiki from our website. See our
+ <a href="http://www.mediawiki.org/wiki/Compatibility#PHP">compatibility page</a>
+ for details of which versions are compatible with prior versions of PHP.
+ </p>
+ </div>
+ </body>
+</html>
+HTML;
+ // Handle everything that's not index.php
+ } else {
+ // So nothing thinks this is JS or CSS
+ $finalOutput = ( $type == 'load.php' ) ? "/* $message */" : $message;
+ if( $type != 'cli' ) {
+ header( $_SERVER['SERVER_PROTOCOL'] . ' 500 MediaWiki configuration Error', true, 500 );
+ }
+ }
+ echo( "$finalOutput\n" );
+ die( 1 );
+} \ No newline at end of file
diff --git a/includes/PageQueryPage.php b/includes/PageQueryPage.php
index 892ff259..8390241f 100644
--- a/includes/PageQueryPage.php
+++ b/includes/PageQueryPage.php
@@ -5,7 +5,7 @@
*
* @ingroup SpecialPage
*/
-class PageQueryPage extends QueryPage {
+abstract class PageQueryPage extends QueryPage {
/**
* Format the result as a simple link to the page
diff --git a/includes/Pager.php b/includes/Pager.php
index 19b61e8d..81d95593 100644
--- a/includes/Pager.php
+++ b/includes/Pager.php
@@ -51,7 +51,7 @@ interface Pager {
*
* Subclassing the pager to implement concrete functionality should be fairly
* simple, please see the examples in HistoryPage.php and
- * SpecialIpblocklist.php. You just need to override formatRow(),
+ * SpecialBlockList.php. You just need to override formatRow(),
* getQueryInfo() and getIndexField(). Don't forget to call the parent
* constructor if you override it.
*
@@ -93,6 +93,8 @@ abstract class IndexPager implements Pager {
/**
* Result object for the query. Warning: seek before use.
+ *
+ * @var ResultWrapper
*/
public $mResult;
@@ -140,7 +142,7 @@ abstract class IndexPager implements Pager {
* has been kept minimal to make it overridable if necessary, to allow for
* result sets formed from multiple DB queries.
*/
- function doQuery() {
+ public function doQuery() {
# Use the child class name for profiling
$fname = __METHOD__ . ' (' . get_class( $this ) . ')';
wfProfileIn( $fname );
@@ -197,7 +199,7 @@ abstract class IndexPager implements Pager {
# Remove any table prefix from index field
$parts = explode( '.', $this->mIndexField );
$indexColumn = end( $parts );
-
+
$row = $res->fetchRow();
$firstIndex = $row[$indexColumn];
@@ -415,8 +417,10 @@ abstract class IndexPager implements Pager {
* @return Associative array
*/
function getDefaultQuery() {
+ global $wgRequest;
+
if ( !isset( $this->mDefaultQuery ) ) {
- $this->mDefaultQuery = $_GET;
+ $this->mDefaultQuery = $wgRequest->getQueryValues();
unset( $this->mDefaultQuery['title'] );
unset( $this->mDefaultQuery['dir'] );
unset( $this->mDefaultQuery['offset'] );
@@ -703,7 +707,9 @@ abstract class ReverseChronologicalPager extends IndexPager {
function getNavigationBar() {
global $wgLang;
- if ( !$this->isNavigationBarShown() ) return '';
+ if ( !$this->isNavigationBarShown() ) {
+ return '';
+ }
if ( isset( $this->mNavigationBar ) ) {
return $this->mNavigationBar;
@@ -815,7 +821,7 @@ abstract class TablePager extends IndexPager {
$tableClass = htmlspecialchars( $this->getTableClass() );
$sortClass = htmlspecialchars( $this->getSortHeaderClass() );
- $s = "<table border='1' class=\"$tableClass\"><thead><tr>\n";
+ $s = "<table style='border:1;' class=\"$tableClass\"><thead><tr>\n";
$fields = $this->getFieldNames();
# Make table header
@@ -829,13 +835,13 @@ abstract class TablePager extends IndexPager {
# Prepare a link that goes in the other sort order
if ( $this->mDefaultDirection ) {
# Descending
- $image = 'Arr_u.png';
+ $image = 'Arr_d.png';
$query['asc'] = '1';
$query['desc'] = '';
$alt = htmlspecialchars( wfMsg( 'descending_abbrev' ) );
} else {
# Ascending
- $image = 'Arr_d.png';
+ $image = 'Arr_u.png';
$query['asc'] = '';
$query['desc'] = '1';
$alt = htmlspecialchars( wfMsg( 'ascending_abbrev' ) );
@@ -941,7 +947,7 @@ abstract class TablePager extends IndexPager {
* A navigation bar with images
*/
function getNavigationBar() {
- global $wgStylePath, $wgContLang;
+ global $wgStylePath, $wgLang;
if ( !$this->isNavigationBarShown() ) {
return '';
@@ -966,7 +972,7 @@ abstract class TablePager extends IndexPager {
'next' => 'arrow_disabled_right_25.png',
'last' => 'arrow_disabled_last_25.png',
);
- if( $wgContLang->isRTL() ) {
+ if( $wgLang->isRTL() ) {
$keys = array_keys( $labels );
$images = array_combine( $keys, array_reverse( $images ) );
$disabledImages = array_combine( $keys, array_reverse( $disabledImages ) );
@@ -982,10 +988,10 @@ abstract class TablePager extends IndexPager {
$links = $this->getPagingLinks( $linkTexts, $disabledTexts );
$navClass = htmlspecialchars( $this->getNavClass() );
- $s = "<table class=\"$navClass\" align=\"center\" cellpadding=\"3\"><tr>\n";
- $cellAttrs = 'valign="top" align="center" width="' . 100 / count( $links ) . '%"';
+ $s = "<table class=\"$navClass\"><tr>\n";
+ $width = 100 / count( $links ) . '%';
foreach ( $labels as $type => $label ) {
- $s .= "<td $cellAttrs>{$links[$type]}</td>\n";
+ $s .= "<td style='width:$width;'>{$links[$type]}</td>\n";
}
$s .= "</tr></table>\n";
return $s;
@@ -998,7 +1004,7 @@ abstract class TablePager extends IndexPager {
*/
function getLimitSelect() {
global $wgLang;
-
+
# 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 ) ) {
@@ -1025,14 +1031,16 @@ 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
+ * Resubmits all defined elements of the query string, except for a
* blacklist, passed in the $blacklist parameter.
*
* @return String: HTML fragment
*/
function getHiddenFields( $blacklist = array() ) {
+ global $wgRequest;
+
$blacklist = (array)$blacklist;
- $query = $_GET;
+ $query = $wgRequest->getQueryValues();
foreach ( $blacklist as $name ) {
unset( $query[$name] );
}
diff --git a/includes/PatrolLog.php b/includes/PatrolLog.php
index 5727853e..0df48a85 100644
--- a/includes/PatrolLog.php
+++ b/includes/PatrolLog.php
@@ -35,14 +35,14 @@ class PatrolLog {
*
* @param $title Title of the page that was patrolled
* @param $params Array: log parameters (from logging.log_params)
- * @param $skin Skin to use for building links, etc.
+ * @param $lang Language object to use, or false
* @return String
*/
- public static function makeActionText( $title, $params, $skin ) {
+ public static function makeActionText( $title, $params, $lang ) {
list( $cur, /* $prev */, $auto ) = $params;
- if( is_object( $skin ) ) {
+ if( is_object( $lang ) ) {
# Standard link to the page in question
- $link = $skin->link( $title );
+ $link = Linker::link( $title );
if( $title->exists() ) {
# Generate a diff link
$query = array(
@@ -50,9 +50,9 @@ class PatrolLog {
'diff' => 'prev'
);
- $diff = $skin->link(
+ $diff = Linker::link(
$title,
- htmlspecialchars( wfMsg( 'patrol-log-diff', $cur ) ),
+ htmlspecialchars( wfMsg( 'patrol-log-diff', $lang->formatNum( $cur, true ) ) ),
array(),
$query,
array( 'known', 'noclasses' )
diff --git a/includes/PoolCounter.php b/includes/PoolCounter.php
index 3851767f..182520e1 100644
--- a/includes/PoolCounter.php
+++ b/includes/PoolCounter.php
@@ -4,9 +4,14 @@
* 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'.
+ * of the cpu of the pool. This is also known as 'Michael Jackson effect'
+ * since this effect triggered on the english wikipedia on the day Michael
+ * Jackson died, the biographical article got hit with several edits per
+ * minutes and hundreds of read hits.
+ *
* The PoolCounter provides semaphore semantics for restricting the number
* of workers that may be concurrently performing such single task.
*
@@ -16,15 +21,15 @@
abstract class PoolCounter {
/* Return codes */
- const LOCKED = 1; /* Lock acquired */
+ const LOCKED = 1; /* Lock acquired */
const RELEASED = 2; /* Lock released */
- const DONE = 3; /* Another one did the work for you */
+ const DONE = 3; /* Another worker did the work for you */
- const ERROR = -1; /* Indeterminate error */
- const NOT_LOCKED = -2; /* Called release() with no lock held */
- const QUEUE_FULL = -3; /* There are already maxqueue workers on this lock */
- const TIMEOUT = -4; /* Timeout exceeded */
- const LOCK_HELD = -5; /* Cannot acquire another lock while you have one lock held */
+ 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.
@@ -62,6 +67,11 @@ abstract class PoolCounter {
/**
* Create a Pool counter. This should only be called from the PoolWorks.
+ *
+ * @param $type
+ * @param $key
+ *
+ * @return PoolCounter
*/
public static function factory( $type, $key ) {
global $wgPoolCounterConf;
@@ -83,18 +93,28 @@ abstract class PoolCounter {
}
class PoolCounter_Stub extends PoolCounter {
+
+ /**
+ * @return Status
+ */
function acquireForMe() {
return Status::newGood( PoolCounter::LOCKED );
}
+ /**
+ * @return Status
+ */
function acquireForAnyone() {
return Status::newGood( PoolCounter::LOCKED );
}
+ /**
+ * @return Status
+ */
function release() {
return Status::newGood( PoolCounter::RELEASED );
}
-
+
public function __construct() {
/* No parameters needed */
}
@@ -134,6 +154,13 @@ abstract class PoolCounterWork {
function error( $status ) {
return false;
}
+
+ /**
+ * Log an error
+ */
+ function logError( $status ) {
+ wfDebugLog( 'poolcounter', $status->getWikiText() );
+ }
/**
* Get the result of the work (whatever it is), or false.
@@ -144,43 +171,47 @@ abstract class PoolCounterWork {
} else {
$status = $this->poolCounter->acquireForMe();
}
-
- if ( $status->isOK() ) {
- switch ( $status->value ) {
- case PoolCounter::LOCKED:
- $result = $this->doWork();
- $this->poolCounter->release();
- return $result;
+
+ if ( !$status->isOK() ) {
+ // Respond gracefully to complete server breakage: just log it and do the work
+ $this->logError( $status );
+ return $this->doWork();
+ }
+
+ switch ( $status->value ) {
+ case PoolCounter::LOCKED:
+ $result = $this->doWork();
+ $this->poolCounter->release();
+ return $result;
+
+ case PoolCounter::DONE:
+ $result = $this->getCachedWork();
+ if ( $result === false ) {
+ /* That someone else work didn't serve us.
+ * Acquire the lock for me
+ */
+ return $this->execute( true );
+ }
+ return $result;
+
+ case PoolCounter::QUEUE_FULL:
+ case PoolCounter::TIMEOUT:
+ $result = $this->fallback();
- 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 );
- }
+ if ( $result !== false ) {
return $result;
-
- case PoolCounter::QUEUE_FULL:
- case PoolCounter::TIMEOUT:
- $result = $this->fallback();
-
- if ( $result !== false ) {
- return $result;
- }
- /* no break */
+ }
+ /* no break */
+
+ /* These two cases should never be hit... */
+ case PoolCounter::ERROR:
+ default:
+ $errors = array( PoolCounter::QUEUE_FULL => 'pool-queuefull', PoolCounter::TIMEOUT => 'pool-timeout' );
- /* 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 */
- }
+ $status = Status::newFatal( isset($errors[$status->value]) ? $errors[$status->value] : 'pool-errorunknown' );
+ $this->logError( $status );
+ return $this->error( $status );
}
- return $this->error( $status );
}
function __construct( $type, $key ) {
diff --git a/includes/Preferences.php b/includes/Preferences.php
index c8ea2cc6..91b3326b 100644
--- a/includes/Preferences.php
+++ b/includes/Preferences.php
@@ -36,16 +36,21 @@ class Preferences {
'searchlimit' => array( 'Preferences', 'filterIntval' ),
);
+ /**
+ * @throws MWException
+ * @param $user User
+ * @return array|null
+ */
static function getPreferences( $user ) {
- if ( self::$defaultPreferences )
+ if ( self::$defaultPreferences ) {
return self::$defaultPreferences;
+ }
$defaultPreferences = array();
self::profilePreferences( $user, $defaultPreferences );
self::skinPreferences( $user, $defaultPreferences );
self::filesPreferences( $user, $defaultPreferences );
- self::mathPreferences( $user, $defaultPreferences );
self::datetimePreferences( $user, $defaultPreferences );
self::renderingPreferences( $user, $defaultPreferences );
self::editingPreferences( $user, $defaultPreferences );
@@ -92,7 +97,14 @@ class Preferences {
return $defaultPreferences;
}
- // Pull option from a user account. Handles stuff like array-type preferences.
+ /**
+ * Pull option from a user account. Handles stuff like array-type preferences.
+ *
+ * @param $name
+ * @param $info
+ * @param $user User
+ * @return array|String
+ */
static function getOptionFromUser( $name, $info, $user ) {
$val = $user->getOption( $name );
@@ -113,6 +125,11 @@ class Preferences {
return $val;
}
+ /**
+ * @param $user User
+ * @param $defaultPreferences
+ * @return void
+ */
static function profilePreferences( $user, &$defaultPreferences ) {
global $wgLang, $wgUser;
## User info #####################################
@@ -207,7 +224,7 @@ class Preferences {
);
if ( $wgAuth->allowPasswordChange() ) {
- $link = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'Resetpass' ),
+ $link = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'ChangePassword' ),
wfMsgHtml( 'prefs-resetpass' ), array(),
array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' ) ) );
@@ -296,7 +313,7 @@ class Preferences {
global $wgMaxSigChars, $wgOut, $wgParser;
// show a preview of the old signature first
- $oldsigWikiText = $wgParser->preSaveTransform( "~~~", new Title , $user, new ParserOptions );
+ $oldsigWikiText = $wgParser->preSaveTransform( "~~~", new Title, $user, new ParserOptions );
$oldsigHTML = $wgOut->parseInline( $oldsigWikiText );
$defaultPreferences['oldsig'] = array(
'type' => 'info',
@@ -325,19 +342,27 @@ class Preferences {
global $wgEnableEmail;
if ( $wgEnableEmail ) {
global $wgEmailConfirmToEdit;
+ global $wgEnableUserEmail;
+
+ $helpMessages[] = $wgEmailConfirmToEdit
+ ? 'prefs-help-email-required'
+ : 'prefs-help-email' ;
+
+ if( $wgEnableUserEmail ) {
+ // additional messages when users can send email to each other
+ $helpMessages[] = 'prefs-help-email-others';
+ }
$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',
+ 'help-messages' => $helpMessages,
'validation-callback' => array( 'Preferences', 'validateEmail' ),
);
- global $wgEnableUserEmail, $wgEmailAuthentication;
+ global $wgEmailAuthentication;
$disableEmailPrefs = false;
@@ -437,6 +462,11 @@ class Preferences {
}
}
+ /**
+ * @param $user User
+ * @param $defaultPreferences
+ * @return void
+ */
static function skinPreferences( $user, &$defaultPreferences ) {
## Skin #####################################
global $wgLang, $wgAllowUserCss, $wgAllowUserJs;
@@ -487,19 +517,10 @@ class Preferences {
}
}
- 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' => '&#160;',
- 'section' => 'rendering/math',
- );
- }
- }
-
+ /**
+ * @param $user User
+ * @param $defaultPreferences Array
+ */
static function filesPreferences( $user, &$defaultPreferences ) {
## Files #####################################
$defaultPreferences['imagesize'] = array(
@@ -516,6 +537,11 @@ class Preferences {
);
}
+ /**
+ * @param $user User
+ * @param $defaultPreferences
+ * @return void
+ */
static function datetimePreferences( $user, &$defaultPreferences ) {
global $wgLang;
@@ -531,10 +557,11 @@ class Preferences {
}
// Info
+ $now = wfTimestampNow();
$nowlocal = Xml::element( 'span', array( 'id' => 'wpLocalTime' ),
- $wgLang->time( $now = wfTimestampNow(), true ) );
+ $wgLang->time( $now, true ) );
$nowserver = $wgLang->time( $now, false ) .
- Html::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) );
+ Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
$defaultPreferences['nowserver'] = array(
'type' => 'info',
@@ -572,6 +599,10 @@ class Preferences {
);
}
+ /**
+ * @param $user User
+ * @param $defaultPreferences Array
+ */
static function renderingPreferences( $user, &$defaultPreferences ) {
## Page Rendering ##############################
global $wgAllowUserCssPrefs;
@@ -645,6 +676,10 @@ class Preferences {
);
}
+ /**
+ * @param $user User
+ * @param $defaultPreferences Array
+ */
static function editingPreferences( $user, &$defaultPreferences ) {
global $wgUseExternalEditor, $wgAllowUserCssPrefs;
@@ -710,11 +745,14 @@ class Preferences {
'section' => 'editing/advancedediting',
'label-message' => 'tog-showtoolbar',
);
- $defaultPreferences['minordefault'] = array(
- 'type' => 'toggle',
- 'section' => 'editing/advancedediting',
- 'label-message' => 'tog-minordefault',
- );
+
+ if ( $user->isAllowed( 'minoredit' ) ) {
+ $defaultPreferences['minordefault'] = array(
+ 'type' => 'toggle',
+ 'section' => 'editing/advancedediting',
+ 'label-message' => 'tog-minordefault',
+ );
+ }
if ( $wgUseExternalEditor ) {
$defaultPreferences['externaleditor'] = array(
@@ -735,7 +773,7 @@ class Preferences {
'label-message' => 'tog-forceeditsummary',
);
-
+
$defaultPreferences['uselivepreview'] = array(
'type' => 'toggle',
'section' => 'editing/advancedediting',
@@ -743,8 +781,12 @@ class Preferences {
);
}
+ /**
+ * @param $user User
+ * @param $defaultPreferences Array
+ */
static function rcPreferences( $user, &$defaultPreferences ) {
- global $wgRCMaxAge, $wgUseRCPatrol, $wgLang;
+ global $wgRCMaxAge, $wgLang;
## RecentChanges #####################################
$defaultPreferences['rcdays'] = array(
@@ -776,7 +818,7 @@ class Preferences {
'section' => 'rc/advancedrc',
);
- if ( $wgUseRCPatrol ) {
+ if ( $user->useRCPatrol() ) {
$defaultPreferences['hidepatrolled'] = array(
'type' => 'toggle',
'section' => 'rc/advancedrc',
@@ -799,6 +841,10 @@ class Preferences {
}
}
+ /**
+ * @param $user User
+ * @param $defaultPreferences
+ */
static function watchlistPreferences( $user, &$defaultPreferences ) {
global $wgUseRCPatrol, $wgEnableAPI;
@@ -892,6 +938,10 @@ class Preferences {
}
}
+ /**
+ * @param $user User
+ * @param $defaultPreferences Array
+ */
static function searchPreferences( $user, &$defaultPreferences ) {
global $wgContLang;
@@ -902,18 +952,6 @@ class Preferences {
'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 ) {
@@ -923,7 +961,7 @@ class Preferences {
'section' => 'searchoptions/displaysearchoptions',
);
}
-
+
global $wgVectorUseSimpleSearch;
if ( $wgVectorUseSimpleSearch ) {
$defaultPreferences['vector-simplesearch'] = array(
@@ -939,9 +977,6 @@ class Preferences {
'section' => 'searchoptions/advancedsearchoptions',
);
- // Searchable namespaces back-compat with old format
- $searchableNamespaces = SearchEngine::searchableNamespaces();
-
$nsOptions = array();
foreach ( $wgContLang->getNamespaces() as $ns => $name ) {
@@ -968,6 +1003,10 @@ class Preferences {
);
}
+ /**
+ * @param $user User
+ * @param $defaultPreferences Array
+ */
static function miscPreferences( $user, &$defaultPreferences ) {
## Misc #####################################
$defaultPreferences['diffonly'] = array(
@@ -996,7 +1035,7 @@ class Preferences {
}
/**
- * @param $user The User object
+ * @param $user User The User object
* @return Array: text/links to display as key; $skinkey as value
*/
static function generateSkinOptions( $user ) {
@@ -1013,10 +1052,9 @@ class Preferences {
# 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 ) {
- $msgName = "skinname-{$skinkey}";
- $localisedSkinName = wfMsg( $msgName );
- if ( !wfEmptyMsg( $msgName, $localisedSkinName ) ) {
- $skinname = htmlspecialchars( $localisedSkinName );
+ $msg = wfMessage( "skinname-{$skinkey}" );
+ if ( $msg->exists() ) {
+ $skinname = htmlspecialchars( $msg->text() );
}
}
asort( $validSkinNames );
@@ -1052,6 +1090,9 @@ class Preferences {
return $ret;
}
+ /**
+ * @return array
+ */
static function getDateOptions() {
global $wgLang;
$dateopts = $wgLang->getDatePreferences();
@@ -1083,6 +1124,9 @@ class Preferences {
return $ret;
}
+ /**
+ * @return array
+ */
static function getImageSizes() {
global $wgImageLimits;
@@ -1096,6 +1140,9 @@ class Preferences {
return $ret;
}
+ /**
+ * @return array
+ */
static function getThumbSizes() {
global $wgThumbLimits;
@@ -1109,6 +1156,11 @@ class Preferences {
return $ret;
}
+ /**
+ * @param $signature
+ * @param $alldata
+ * @return bool|string
+ */
static function validateSignature( $signature, $alldata ) {
global $wgParser, $wgMaxSigChars, $wgLang;
if ( mb_strlen( $signature ) > $wgMaxSigChars ) {
@@ -1126,6 +1178,11 @@ class Preferences {
}
}
+ /**
+ * @param $signature string
+ * @param $alldata array
+ * @return string
+ */
static function cleanSignature( $signature, $alldata ) {
global $wgParser;
if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
@@ -1138,8 +1195,13 @@ class Preferences {
return $signature;
}
+ /**
+ * @param $email
+ * @param $alldata
+ * @return bool|String
+ */
static function validateEmail( $email, $alldata ) {
- if ( $email && !User::isValidEmailAddr( $email ) ) {
+ if ( $email && !Sanitizer::validateEmail( $email ) ) {
return wfMsgExt( 'invalidemailaddress', 'parseinline' );
}
@@ -1150,10 +1212,16 @@ class Preferences {
return true;
}
+ /**
+ * @param $user User
+ * @param $formClass string
+ * @return HtmlForm
+ */
static function getFormObject( $user, $formClass = 'PreferencesForm' ) {
$formDescriptor = Preferences::getPreferences( $user );
$htmlForm = new $formClass( $formDescriptor, 'prefs' );
+ $htmlForm->setId( 'mw-prefs-form' );
$htmlForm->setSubmitText( wfMsg( 'saveprefs' ) );
# Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
$htmlForm->setSubmitTooltip( 'preferences-save' );
@@ -1164,12 +1232,21 @@ class Preferences {
return $htmlForm;
}
+ /**
+ * @return array
+ */
static function getTimezoneOptions() {
$opt = array();
- global $wgLocalTZoffset;
-
- $opt[wfMsg( 'timezoneuseserverdefault' )] = "System|$wgLocalTZoffset";
+ global $wgLocalTZoffset, $wgLocaltimezone;
+ // Check that $wgLocalTZoffset is the same as $wgLocaltimezone
+ if ( $wgLocalTZoffset == date( 'Z' ) / 60 ) {
+ $server_tz_msg = wfMsg( 'timezoneuseserverdefault', $wgLocaltimezone );
+ } else {
+ $tzstring = sprintf( '%+03d:%02d', floor( $wgLocalTZoffset / 60 ), abs( $wgLocalTZoffset ) % 60 );
+ $server_tz_msg = wfMsg( 'timezoneuseserverdefault', $tzstring );
+ }
+ $opt[$server_tz_msg] = "System|$wgLocalTZoffset";
$opt[wfMsg( 'timezoneuseoffset' )] = 'other';
$opt[wfMsg( 'guesstimezone' )] = 'guess';
@@ -1219,11 +1296,21 @@ class Preferences {
}
return $opt;
}
-
+
+ /**
+ * @param $value
+ * @param $alldata
+ * @return int
+ */
static function filterIntval( $value, $alldata ){
return intval( $value );
}
+ /**
+ * @param $tz
+ * @param $alldata
+ * @return string
+ */
static function filterTimezoneInput( $tz, $alldata ) {
$data = explode( '|', $tz, 3 );
switch ( $data[0] ) {
@@ -1249,6 +1336,11 @@ class Preferences {
}
}
+ /**
+ * @param $formData
+ * @param $entryPoint string
+ * @return bool|Status|string
+ */
static function tryFormSubmit( $formData, $entryPoint = 'internal' ) {
global $wgUser, $wgEmailAuthentication, $wgEnableEmail;
@@ -1307,6 +1399,16 @@ class Preferences {
unset( $formData[$b] );
}
+ # If users have saved a value for a preference which has subsequently been disabled
+ # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference
+ # is subsequently re-enabled
+ # TODO: maintenance script to actually delete these
+ foreach( $wgHiddenPrefs as $pref ){
+ # If the user has not set a non-default value here, the default will be returned
+ # and subsequently discarded
+ $formData[$pref] = $wgUser->getOption( $pref, null, true );
+ }
+
// Keeps old preferences from interfering due to back-compat
// code, etc.
$wgUser->resetOptions();
@@ -1320,6 +1422,10 @@ class Preferences {
return $result;
}
+ /**
+ * @param $formData
+ * @return Status
+ */
public static function tryUISubmit( $formData ) {
$res = self::tryFormSubmit( $formData, 'ui' );
@@ -1341,6 +1447,10 @@ class Preferences {
return Status::newGood();
}
+ /**
+ * @param $user User
+ * @return array
+ */
public static function loadOldSearchNs( $user ) {
$searchableNamespaces = SearchEngine::searchableNamespaces();
// Back compat with old format
@@ -1358,12 +1468,20 @@ class Preferences {
/** Some tweaks to allow js prefs to work */
class PreferencesForm extends HTMLForm {
+
+ /**
+ * @param $html string
+ * @return String
+ */
function wrapForm( $html ) {
$html = Xml::tags( 'div', array( 'id' => 'preferences' ), $html );
return parent::wrapForm( $html );
}
+ /**
+ * @return String
+ */
function getButtons() {
$html = parent::getButtons();
@@ -1379,6 +1497,10 @@ class PreferencesForm extends HTMLForm {
return $html;
}
+ /**
+ * @param $data array
+ * @return array
+ */
function filterDataForSubmit( $data ) {
// Support for separating MultiSelect preferences into multiple preferences
// Due to lack of array support.
@@ -1398,4 +1520,10 @@ class PreferencesForm extends HTMLForm {
return $data;
}
+ /**
+ * Get the whole body of the form.
+ */
+ function getBody() {
+ return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
+ }
}
diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php
index 236e4370..0efe1bdd 100644
--- a/includes/PrefixSearch.php
+++ b/includes/PrefixSearch.php
@@ -15,7 +15,7 @@ class PrefixSearch {
* @param $namespaces Array: used if query is not explicitely prefixed
* @return Array of strings
*/
- public static function titleSearch( $search, $limit, $namespaces=array() ) {
+ public static function titleSearch( $search, $limit, $namespaces = array() ) {
$search = trim( $search );
if( $search == '' ) {
return array(); // Return empty result
@@ -26,8 +26,9 @@ class PrefixSearch {
$title = Title::newFromText( $search );
if( $title && $title->getInterwiki() == '' ) {
$ns = array($title->getNamespace());
- if($ns[0] == NS_MAIN)
+ if( $ns[0] == NS_MAIN ) {
$ns = $namespaces; // no explicit prefix, use default namespaces
+ }
return self::searchBackend(
$ns, $title->getText(), $limit );
}
@@ -38,13 +39,12 @@ class PrefixSearch {
&& $title->getNamespace() != NS_MAIN
&& $title->getInterwiki() == '' ) {
return self::searchBackend(
- array($title->getNamespace()), '', $limit );
+ array( $title->getNamespace() ), '', $limit );
}
return self::searchBackend( $namespaces, $search, $limit );
}
-
/**
* Do a prefix search of titles and return a list of matching page names.
* @param $namespaces Array
@@ -53,10 +53,10 @@ class PrefixSearch {
* @return Array of strings
*/
protected static function searchBackend( $namespaces, $search, $limit ) {
- if( count($namespaces) == 1 ){
+ if( count( $namespaces ) == 1 ) {
$ns = $namespaces[0];
if( $ns == NS_MEDIA ) {
- $namespaces = array(NS_FILE);
+ $namespaces = array( NS_FILE );
} elseif( $ns == NS_SPECIAL ) {
return self::specialSearch( $search, $limit );
}
@@ -85,15 +85,13 @@ class PrefixSearch {
// Unlike SpecialPage itself, we want the canonical forms of both
// canonical and alias title forms...
- SpecialPage::initList();
- SpecialPage::initAliasList();
$keys = array();
- foreach( array_keys( SpecialPage::$mList ) as $page ) {
+ foreach( SpecialPageFactory::getList() as $page => $class ) {
$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, SpecialPageFactory::getList() ) ) {# bug 20885
continue;
}
@@ -135,12 +133,13 @@ class PrefixSearch {
* @return Array of title strings
*/
protected static function defaultSearchBackend( $namespaces, $search, $limit ) {
- $ns = array_shift($namespaces); // support only one namespace
- if( in_array(NS_MAIN,$namespaces))
+ $ns = array_shift( $namespaces ); // support only one namespace
+ if( in_array( NS_MAIN, $namespaces ) ) {
$ns = NS_MAIN; // if searching on many always default to main
+ }
// Prepare nested request
- $req = new FauxRequest(array (
+ $req = new FauxRequest( array(
'action' => 'query',
'list' => 'allpages',
'apnamespace' => $ns,
@@ -149,7 +148,7 @@ class PrefixSearch {
));
// Execute
- $module = new ApiMain($req);
+ $module = new ApiMain( $req );
$module->execute();
// Get resulting data
@@ -157,7 +156,7 @@ class PrefixSearch {
// Reformat useful data for future printing by JSON engine
$srchres = array ();
- foreach ((array)$data['query']['allpages'] as $pageinfo) {
+ foreach ( (array)$data['query']['allpages'] as $pageinfo ) {
// Note: this data will no be printable by the xml engine
// because it does not support lists of unnamed items
$srchres[] = $pageinfo['title'];
@@ -172,18 +171,19 @@ class PrefixSearch {
* @param $namespaces Array
* @return Array (default: contains only NS_MAIN)
*/
- protected static function validateNamespaces($namespaces){
+ 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 ){
+ if( is_array( $namespaces ) && count( $namespaces ) > 0 ) {
$valid = array();
- foreach ($namespaces as $ns){
- if( is_numeric($ns) && array_key_exists($ns, $validNamespaces) )
+ foreach ( $namespaces as $ns ) {
+ if( is_numeric( $ns ) && array_key_exists( $ns, $validNamespaces ) ) {
$valid[] = $ns;
+ }
}
- if( count($valid) > 0 ) {
+ if( count( $valid ) > 0 ) {
return $valid;
}
}
diff --git a/includes/ProfilerSimpleText.php b/includes/ProfilerSimpleText.php
deleted file mode 100644
index db4b6053..00000000
--- a/includes/ProfilerSimpleText.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-/**
- * @file
- * @ingroup Profiler
- */
-
-require_once( dirname( __FILE__ ) . '/ProfilerSimple.php' );
-
-/**
- * The least sophisticated profiler output class possible, view your source! :)
- *
- * Put the following 3 lines in StartProfiler.php:
- *
- * require_once( dirname( __FILE__ ) . '/includes/ProfilerSimpleText.php' );
- * $wgProfiler = new ProfilerSimpleText;
- * $wgProfiler->visible=true;
- *
- * @ingroup Profiler
- */
-class ProfilerSimpleText extends ProfilerSimple {
- public $visible=false; /* Show as <PRE> or <!-- ? */
- static private $out;
-
- function getFunctionReport() {
- 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) { self::$out .= sprintf("%3.6f %6d - %s\n",$item['real'],$item['count'], $key); }
-}
diff --git a/includes/ProfilerStub.php b/includes/ProfilerStub.php
deleted file mode 100644
index e624e6f0..00000000
--- a/includes/ProfilerStub.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-/**
- * Stub profiling functions
- * @file
- * @ingroup Profiler
- */
-
-/** backward compatibility */
-$wgProfiling = false;
-$wgProfiler = null;
-
-/** is setproctitle function available ? */
-$haveProctitle = function_exists( 'setproctitle' );
-
-/**
- * Begin profiling of a function
- * @param $fn string
- */
-function wfProfileIn( $fn = '' ) {
- global $hackwhere, $wgDBname, $haveProctitle;
- if( $haveProctitle ){
- $hackwhere[] = $fn;
- setproctitle( $fn . " [$wgDBname]" );
- }
-}
-
-/**
- * Stop profiling of a function
- * @param $fn string
- */
-function wfProfileOut( $fn = '' ) {
- global $hackwhere, $wgDBname, $haveProctitle;
- if( !$haveProctitle ) {
- return;
- }
- if( count( $hackwhere ) ) {
- array_pop( $hackwhere );
- }
- if( count( $hackwhere ) ) {
- setproctitle( $hackwhere[count( $hackwhere )-1] . " [$wgDBname]" );
- }
-}
-
-/**
- * Does nothing, just for compatibility
- */
-function wfGetProfilingOutput( $s, $e ) {}
-
-/**
- * Does nothing, just for compatibility
- */
-function wfProfileClose() {}
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index 2d95d801..71703eb2 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -57,11 +57,11 @@ class ProtectionForm {
/** Map of action to the expiry time of the existing protection */
var $mExistingExpiry = array();
- function __construct( Article $article ) {
+ function __construct( Page $article ) {
global $wgUser;
// Set instance variables.
$this->mArticle = $article;
- $this->mTitle = $article->mTitle;
+ $this->mTitle = $article->getTitle();
$this->mApplicableTypes = $this->mTitle->getRestrictionTypes();
// Check if the form should be disabled.
@@ -89,7 +89,7 @@ class ProtectionForm {
$this->mCascade = $wgRequest->getBool( 'mwProtect-cascade', $this->mCascade );
foreach( $this->mApplicableTypes as $action ) {
- // Fixme: this form currently requires individual selections,
+ // @todo FIXME: This form currently requires individual selections,
// but the db allows multiples separated by commas.
// Pull the actual restriction from the DB
@@ -133,7 +133,7 @@ class ProtectionForm {
// Prevent users from setting levels that they cannot later unset
if( $val == 'sysop' ) {
// Special case, rewrite sysop to either protect and editprotected
- if( !$wgUser->isAllowed('protect') && !$wgUser->isAllowed('editprotected') )
+ if( !$wgUser->isAllowedAny( 'protect', 'editprotected' ) )
continue;
} else {
if( !$wgUser->isAllowed($val) )
@@ -147,7 +147,9 @@ class ProtectionForm {
/**
* Get the expiry time for a given action, by combining the relevant inputs.
*
- * @return 14-char timestamp or "infinity", or false if the input was invalid
+ * @param $action string
+ *
+ * @return string 14-char timestamp or "infinity", or false if the input was invalid
*/
function getExpiry( $action ) {
if ( $this->mExpirySelection[$action] == 'existing' ) {
@@ -158,7 +160,7 @@ class ProtectionForm {
$value = $this->mExpirySelection[$action];
}
if ( $value == 'infinite' || $value == 'indefinite' || $value == 'infinity' ) {
- $time = Block::infinity();
+ $time = wfGetDB( DB_SLAVE )->getInfinity();
} else {
$unix = strtotime( $value );
@@ -166,7 +168,7 @@ class ProtectionForm {
return false;
}
- // Fixme: non-qualified absolute times are not in users specified timezone
+ // @todo FIXME: Non-qualified absolute times are not in users specified timezone
// and there isn't notice about it in the ui
$time = wfTimestamp( TS_MW, $unix );
}
@@ -232,10 +234,11 @@ class ProtectionForm {
if( wfReadOnly() ) {
$wgOut->readOnlyPage();
} elseif( $this->mPermErrors ) {
- $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $this->mPermErrors ) );
+ $wgOut->showPermissionsErrorPage( $this->mPermErrors );
}
} else {
- $wgOut->addWikiMsg( 'protect-text', $this->mTitle->getPrefixedText() );
+ $wgOut->addWikiMsg( 'protect-text',
+ wfEscapeWikiText( $this->mTitle->getPrefixedText() ) );
}
$wgOut->addHTML( $this->buildForm() );
@@ -316,10 +319,10 @@ class ProtectionForm {
return false;
}
- if( $wgRequest->getCheck( 'mwProtectWatch' ) && $wgUser->isLoggedIn() ) {
- $this->mArticle->doWatch();
- } elseif( $this->mTitle->userIsWatching() ) {
- $this->mArticle->doUnwatch();
+ if ( $wgRequest->getCheck( 'mwProtectWatch' ) && $wgUser->isLoggedIn() ) {
+ WatchAction::doWatch( $this->mTitle, $wgUser );
+ } elseif ( $this->mTitle->userIsWatching() ) {
+ WatchAction::doUnwatch( $this->mTitle, $wgUser );
}
return $ok;
}
@@ -351,13 +354,10 @@ class ProtectionForm {
foreach( $this->mRestrictions as $action => $selected ) {
/* Not all languages have V_x <-> N_x relation */
- $msg = wfMsg( 'restriction-' . $action );
- if( wfEmptyMsg( 'restriction-' . $action, $msg ) ) {
- $msg = $action;
- }
+ $msg = wfMessage( 'restriction-' . $action );
$out .= "<tr><td>".
Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, $msg ) .
+ Xml::element( 'legend', null, $msg->exists() ? $msg->text() : $action ) .
Xml::openElement( 'table', array( 'id' => "mw-protect-table-$action" ) ) .
"<tr><td>" . $this->buildSelector( $action, $selected ) . "</td></tr><tr><td>";
@@ -470,7 +470,10 @@ class ProtectionForm {
</td>
<td class='mw-input'>" .
Xml::input( 'mwProtect-reason', 60, $this->mReason, array( 'type' => 'text',
- 'id' => 'mwProtect-reason', 'maxlength' => 255 ) ) .
+ 'id' => 'mwProtect-reason', 'maxlength' => 180 ) ) .
+ // Limited maxlength as the database trims at 255 bytes and other texts
+ // chosen by dropdown menus on this page are also included in this database field.
+ // The byte limit of 180 bytes is enforced in javascript
"</td>
</tr>";
# Disallow watching is user is not logged in
@@ -530,7 +533,7 @@ class ProtectionForm {
//don't let them choose levels above their own (aka so they can still unprotect and edit the page). but only when the form isn't disabled
if( $key == 'sysop' ) {
//special case, rewrite sysop to protect and editprotected
- if( !$wgUser->isAllowed('protect') && !$wgUser->isAllowed('editprotected') && !$this->disabled )
+ if( !$wgUser->isAllowedAny( 'protect', 'editprotected' ) && !$this->disabled )
continue;
} else {
if( !$wgUser->isAllowed($key) && !$this->disabled )
@@ -565,34 +568,35 @@ class ProtectionForm {
if( $permission == '' ) {
return wfMsg( 'protect-default' );
} else {
- $key = "protect-level-{$permission}";
- $msg = wfMsg( $key );
- if( wfEmptyMsg( $key, $msg ) )
- $msg = wfMsg( 'protect-fallback', $permission );
- return $msg;
+ $msg = wfMessage( "protect-level-{$permission}" );
+ if( $msg->exists() ) {
+ return $msg->text();
+ }
+ return wfMsg( 'protect-fallback', $permission );
}
}
function buildCleanupScript() {
- global $wgRestrictionLevels, $wgGroupPermissions;
- $script = 'var wgCascadeableLevels=';
- $CascadeableLevels = array();
+ global $wgRestrictionLevels, $wgGroupPermissions, $wgOut;
+
+ $cascadeableLevels = array();
foreach( $wgRestrictionLevels as $key ) {
- if ( (isset($wgGroupPermissions[$key]['protect']) && $wgGroupPermissions[$key]['protect']) || $key == 'protect' ) {
- $CascadeableLevels[] = "'" . Xml::escapeJsString( $key ) . "'";
+ if ( ( isset( $wgGroupPermissions[$key]['protect'] ) && $wgGroupPermissions[$key]['protect'] )
+ || $key == 'protect'
+ ) {
+ $cascadeableLevels[] = $key;
}
}
- $script .= "[" . implode(',',$CascadeableLevels) . "];\n";
- $options = (object)array(
+ $options = array(
'tableId' => 'mwProtectSet',
- 'labelText' => wfMsg( 'protect-unchain-permissions' ),
- 'numTypes' => count($this->mApplicableTypes),
- 'existingMatch' => 1 == count( array_unique( $this->mExistingExpiry ) ),
+ 'labelText' => wfMessage( 'protect-unchain-permissions' )->plain(),
+ 'numTypes' => count( $this->mApplicableTypes ),
+ 'existingMatch' => count( array_unique( $this->mExistingExpiry ) ) === 1,
);
- $encOptions = Xml::encodeJsVar( $options );
- $script .= "ProtectionForm.init($encOptions)";
- return Html::inlineScript( "if ( window.mediaWiki ) { $script }" );
+ $wgOut->addJsConfigVars( 'wgCascadeableLevels', $cascadeableLevels );
+ $script = Xml::encodeJsCall( 'ProtectionForm.init', array( $options ) );
+ return Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) );
}
/**
diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php
index 13c19965..99cd65c5 100644
--- a/includes/ProxyTools.php
+++ b/includes/ProxyTools.php
@@ -12,10 +12,11 @@
* @return string
*/
function wfGetForwardedFor() {
- if( function_exists( 'apache_request_headers' ) ) {
+ $apacheHeaders = function_exists( 'apache_request_headers' ) ? apache_request_headers() : null;
+ if( is_array( $apacheHeaders ) ) {
// More reliable than $_SERVER due to case and -/_ folding
- $set = array ();
- foreach ( apache_request_headers() as $tempName => $tempValue ) {
+ $set = array();
+ foreach ( $apacheHeaders as $tempName => $tempValue ) {
$set[ strtoupper( $tempName ) ] = $tempValue;
}
$index = strtoupper ( 'X-Forwarded-For' );
@@ -30,7 +31,7 @@ function wfGetForwardedFor() {
#Try a couple of headers
if( isset( $set[$index] ) ) {
return $set[$index];
- } else if( isset( $set[$index2] ) ) {
+ } elseif( isset( $set[$index2] ) ) {
return $set[$index2];
} else {
return null;
@@ -40,12 +41,15 @@ function wfGetForwardedFor() {
/**
* Returns the browser/OS data from the request header
* Note: headers are spoofable
+ *
+ * @deprecated in 1.18; use $wgRequest->getHeader( 'User-Agent' ) instead.
* @return string
*/
function wfGetAgent() {
+ wfDeprecated( __FUNCTION__ );
if( function_exists( 'apache_request_headers' ) ) {
// More reliable than $_SERVER due to case and -/_ folding
- $set = array ();
+ $set = array();
foreach ( apache_request_headers() as $tempName => $tempValue ) {
$set[ strtoupper( $tempName ) ] = $tempValue;
}
@@ -76,8 +80,6 @@ function wfGetIP() {
return $ip;
}
- $ipchain = array();
-
/* collect the originating ips */
# Client connecting to this webserver
if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
@@ -85,30 +87,29 @@ function wfGetIP() {
} elseif( $wgCommandLineMode ) {
$ip = '127.0.0.1';
}
- if( $ip ) {
- $ipchain[] = $ip;
- }
- # Append XFF on to $ipchain
+ # Append XFF
$forwardedFor = wfGetForwardedFor();
- if ( isset( $forwardedFor ) ) {
- $xff = array_map( 'trim', explode( ',', $forwardedFor ) );
- $xff = array_reverse( $xff );
- $ipchain = array_merge( $ipchain, $xff );
- }
+ if ( $forwardedFor !== null ) {
+ $ipchain = array_map( 'trim', explode( ',', $forwardedFor ) );
+ $ipchain = array_reverse( $ipchain );
+ if ( $ip ) {
+ array_unshift( $ipchain, $ip );
+ }
- # Step through XFF list and find the last address in the list which is a trusted server
- # Set $ip to the IP address given by that trusted server, unless the address is not sensible (e.g. private)
- foreach ( $ipchain as $i => $curIP ) {
- $curIP = IP::canonicalize( $curIP );
- if ( wfIsTrustedProxy( $curIP ) ) {
- if ( isset( $ipchain[$i + 1] ) ) {
- if( $wgUsePrivateIPs || IP::isPublic( $ipchain[$i + 1 ] ) ) {
- $ip = $ipchain[$i + 1];
+ # Step through XFF list and find the last address in the list which is a trusted server
+ # Set $ip to the IP address given by that trusted server, unless the address is not sensible (e.g. private)
+ foreach ( $ipchain as $i => $curIP ) {
+ $curIP = IP::canonicalize( $curIP );
+ if ( wfIsTrustedProxy( $curIP ) ) {
+ if ( isset( $ipchain[$i + 1] ) ) {
+ if( $wgUsePrivateIPs || IP::isPublic( $ipchain[$i + 1 ] ) ) {
+ $ip = $ipchain[$i + 1];
+ }
}
+ } else {
+ break;
}
- } else {
- break;
}
}
@@ -133,13 +134,8 @@ function wfGetIP() {
function wfIsTrustedProxy( $ip ) {
global $wgSquidServers, $wgSquidServersNoPurge;
- if ( in_array( $ip, $wgSquidServers ) ||
- in_array( $ip, $wgSquidServersNoPurge )
- ) {
- $trusted = true;
- } else {
- $trusted = false;
- }
+ $trusted = in_array( $ip, $wgSquidServers ) ||
+ in_array( $ip, $wgSquidServersNoPurge );
wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) );
return $trusted;
}
@@ -168,7 +164,7 @@ function wfProxyCheck() {
if ( !$skip ) {
$title = SpecialPage::getTitleFor( 'Blockme' );
$iphash = md5( $ip . $wgProxyKey );
- $url = $title->getFullURL( 'ip='.$iphash );
+ $url = wfExpandUrl( $title->getFullURL( 'ip='.$iphash ), PROTO_HTTP );
foreach ( $wgProxyPorts as $port ) {
$params = implode( ' ', array(
@@ -183,46 +179,3 @@ function wfProxyCheck() {
$wgMemc->set( $mcKey, 1, $wgProxyMemcExpiry );
}
}
-
-/**
- * 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 );
-}
-
-/**
- * Check if an IP address is in the local proxy list
- * @return bool
- */
-function wfIsLocallyBlockedProxy( $ip ) {
- global $wgProxyList;
-
- if ( !$wgProxyList ) {
- return false;
- }
- wfProfileIn( __METHOD__ );
-
- if ( !is_array( $wgProxyList ) ) {
- # Load from the specified file
- $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
- }
-
- if ( !is_array( $wgProxyList ) ) {
- $ret = false;
- } elseif ( array_search( $ip, $wgProxyList ) !== false ) {
- $ret = true;
- } elseif ( array_key_exists( $ip, $wgProxyList ) ) {
- # Old-style flipped proxy list
- $ret = true;
- } else {
- $ret = false;
- }
- wfProfileOut( __METHOD__ );
- return $ret;
-}
-
diff --git a/includes/QueryPage.php b/includes/QueryPage.php
index d9fb3bb2..b3bb974b 100644
--- a/includes/QueryPage.php
+++ b/includes/QueryPage.php
@@ -15,20 +15,22 @@
global $wgQueryPages; // not redundant
$wgQueryPages = array(
// QueryPage subclass Special page name Limit (false for none, none for the default)
-//----------------------------------------------------------------------------
+// ----------------------------------------------------------------------------
array( 'AncientPagesPage', 'Ancientpages' ),
array( 'BrokenRedirectsPage', 'BrokenRedirects' ),
array( 'DeadendPagesPage', 'Deadendpages' ),
array( 'DisambiguationsPage', 'Disambiguations' ),
array( 'DoubleRedirectsPage', 'DoubleRedirects' ),
+ array( 'FileDuplicateSearchPage', 'FileDuplicateSearch' ),
array( 'LinkSearchPage', 'LinkSearch' ),
array( 'ListredirectsPage', 'Listredirects' ),
array( 'LonelyPagesPage', 'Lonelypages' ),
array( 'LongPagesPage', 'Longpages' ),
+ array( 'MIMEsearchPage', 'MIMEsearch' ),
array( 'MostcategoriesPage', 'Mostcategories' ),
array( 'MostimagesPage', 'Mostimages' ),
array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ),
- array( 'SpecialMostlinkedtemplates', 'Mostlinkedtemplates' ),
+ array( 'MostlinkedtemplatesPage', 'Mostlinkedtemplates' ),
array( 'MostlinkedPage', 'Mostlinked' ),
array( 'MostrevisionsPage', 'Mostrevisions' ),
array( 'FewestrevisionsPage', 'Fewestrevisions' ),
@@ -51,7 +53,7 @@ wfRunHooks( 'wgQueryPages', array( &$wgQueryPages ) );
global $wgDisableCounters;
if ( !$wgDisableCounters )
- $wgQueryPages[] = array( 'PopularPagesPage', 'Popularpages' );
+ $wgQueryPages[] = array( 'PopularPagesPage', 'Popularpages' );
/**
@@ -60,7 +62,7 @@ if ( !$wgDisableCounters )
* subclasses derive from it.
* @ingroup SpecialPage
*/
-class QueryPage {
+abstract class QueryPage extends SpecialPage {
/**
* Whether or not we want plain listoutput rather than an ordered list
*
@@ -77,51 +79,91 @@ class QueryPage {
var $limit = 0;
/**
- * A mutator for $this->listoutput;
- *
- * @param $bool Boolean
+ * The number of rows returned by the query. Reading this variable
+ * only makes sense in functions that are run after the query has been
+ * done, such as preprocessResults() and formatRow().
*/
- function setListoutput( $bool ) {
- $this->listoutput = $bool;
- }
+ protected $numRows;
+
+ protected $cachedTimestamp = null;
/**
- * Subclasses return their name here. Make sure the name is also
- * specified in SpecialPage.php and in Language.php as a language message
- * param.
- *
- * @return String
+ * Wheter to show prev/next links
*/
- function getName() {
- return '';
- }
+ protected $shownavigation = true;
/**
- * Return title object representing this page
+ * A mutator for $this->listoutput;
*
- * @return Title
+ * @param $bool Boolean
*/
- function getTitle() {
- return SpecialPage::getTitleFor( $this->getName() );
+ function setListoutput( $bool ) {
+ $this->listoutput = $bool;
}
/**
- * Subclasses return an SQL query here.
+ * Subclasses return an SQL query here, formatted as an array with the
+ * following keys:
+ * tables => Table(s) for passing to Database::select()
+ * fields => Field(s) for passing to Database::select(), may be *
+ * conds => WHERE conditions
+ * options => options
+ * join_conds => JOIN conditions
*
- * Note that the query itself should return the following four columns:
- * 'type' (your special page's name), 'namespace', 'title', and 'value'
- * *in that order*. 'value' is used for sorting.
+ * Note that the query itself should return the following three columns:
+ * 'namespace', 'title', and 'value'. 'value' is used for sorting.
*
* These may be stored in the querycache table for expensive queries,
* and that cached data will be returned sometimes, so the presence of
* extra fields can't be relied upon. The cached 'value' column will be
- * an integer; non-numeric values are useful only for sorting the initial
- * query.
+ * an integer; non-numeric values are useful only for sorting the
+ * initial query (except if they're timestamps, see usesTimestamps()).
*
- * Don't include an ORDER or LIMIT clause, this will be added.
+ * Don't include an ORDER or LIMIT clause, they will be added.
+ *
+ * If this function is not overridden or returns something other than
+ * an array, getSQL() will be used instead. This is for backwards
+ * compatibility only and is strongly deprecated.
+ * @return array
+ * @since 1.18
+ */
+ function getQueryInfo() {
+ return null;
+ }
+
+ /**
+ * For back-compat, subclasses may return a raw SQL query here, as a string.
+ * This is stronly deprecated; getQueryInfo() should be overridden instead.
+ * @return string
*/
function getSQL() {
- return "SELECT 'sample' as type, 0 as namespace, 'Sample result' as title, 42 as value";
+ /* Implement getQueryInfo() instead */
+ throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor getQuery() properly" );
+ }
+
+ /**
+ * Subclasses return an array of fields to order by here. Don't append
+ * DESC to the field names, that'll be done automatically if
+ * sortDescending() returns true.
+ * @return array
+ * @since 1.18
+ */
+ function getOrderFields() {
+ return array( 'value' );
+ }
+
+ /**
+ * Does this query return timestamps rather than integers in its
+ * 'value' field? If true, this class will convert 'value' to a
+ * UNIX timestamp for caching.
+ * NOTE: formatRow() may get timestamps in TS_MW (mysql), TS_DB (pgsql)
+ * or TS_UNIX (querycache) format, so be sure to always run them
+ * through wfTimestamp()
+ * @return bool
+ * @since 1.18
+ */
+ function usesTimestamps() {
+ return false;
}
/**
@@ -133,11 +175,6 @@ class QueryPage {
return true;
}
- function getOrder() {
- return ' ORDER BY value ' .
- ($this->sortDescending() ? 'DESC' : '');
- }
-
/**
* Is this query expensive (for some definition of expensive)? Then we
* don't let it run in miser mode. $wgDisableQueryPages causes all query
@@ -151,7 +188,18 @@ class QueryPage {
}
/**
- * Whether or not the output of the page in question is retrived from
+ * Is the output of this query cacheable? Non-cacheable expensive pages
+ * will be disabled in miser mode and will not have their results written
+ * to the querycache table.
+ * @return Boolean
+ * @since 1.18
+ */
+ public function isCacheable() {
+ return true;
+ }
+
+ /**
+ * Whether or not the output of the page in question is retrieved from
* the database cache.
*
* @return Boolean
@@ -175,14 +223,15 @@ class QueryPage {
* Formats the results of the query for display. The skin is the current
* skin; you can use it for making links. The result is a single row of
* result data. You should be able to grab SQL results off of it.
- * If the function return "false", the line output will be skipped.
+ * If the function returns false, the line output will be skipped.
+ * @param $skin Skin
+ * @param $result object Result row
+ * @return mixed String or false to skip
*
* @param $skin Skin object
* @param $result Object: database row
*/
- function formatResult( $skin, $result ) {
- return '';
- }
+ abstract function formatResult( $skin, $result );
/**
* The content returned by this function will be output before any result
@@ -207,8 +256,9 @@ class QueryPage {
/**
* Some special pages (for example SpecialListusers) might not return the
* current object formatted, but return the previous one instead.
- * Setting this to return true, will call one more time wfFormatResult to
- * be sure that the very last result is formatted and shown.
+ * Setting this to return true will ensure formatResult() is called
+ * one more time to make sure that the very last result is formatted
+ * as well.
*/
function tryLastResult() {
return false;
@@ -221,6 +271,10 @@ class QueryPage {
* @param $ignoreErrors Boolean: whether to ignore database errors
*/
function recache( $limit, $ignoreErrors = true ) {
+ if ( !$this->isCacheable() ) {
+ return 0;
+ }
+
$fname = get_class( $this ) . '::recache';
$dbw = wfGetDB( DB_MASTER );
$dbr = wfGetDB( DB_SLAVE, array( $this->getName(), __METHOD__, 'vslow' ) );
@@ -236,10 +290,7 @@ class QueryPage {
# Clear out any old cached data
$dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname );
# Do query
- $sql = $this->getSQL() . $this->getOrder();
- if ( $limit !== false )
- $sql = $dbr->limitResult( $sql, $limit, 0 );
- $res = $dbr->query( $sql, $fname );
+ $res = $this->reallyDoQuery( $limit, false );
$num = false;
if ( $res ) {
$num = $dbr->numRows( $res );
@@ -247,22 +298,27 @@ class QueryPage {
$vals = array();
while ( $res && $row = $dbr->fetchObject( $res ) ) {
if ( isset( $row->value ) ) {
- $value = intval( $row->value ); // @bug 14414
+ if ( $this->usesTimestamps() ) {
+ $value = wfTimestamp( TS_UNIX,
+ $row->value );
+ } else {
+ $value = intval( $row->value ); // @bug 14414
+ }
} else {
$value = 0;
}
-
- $vals[] = array('qc_type' => $row->type,
+
+ $vals[] = array( 'qc_type' => $this->getName(),
'qc_namespace' => $row->namespace,
'qc_title' => $row->title,
- 'qc_value' => $value);
+ 'qc_value' => $value );
}
# Save results into the querycache table on the master
if ( count( $vals ) ) {
if ( !$dbw->insert( 'querycache', $vals, __METHOD__ ) ) {
// Set result to false to indicate error
- $res = false;
+ $num = false;
}
}
if ( $ignoreErrors ) {
@@ -279,47 +335,147 @@ class QueryPage {
}
/**
+ * Run the query and return the result
+ * @param $limit mixed Numerical limit or false for no limit
+ * @param $offset mixed Numerical offset or false for no offset
+ * @return ResultWrapper
+ * @since 1.18
+ */
+ function reallyDoQuery( $limit, $offset = false ) {
+ $fname = get_class( $this ) . "::reallyDoQuery";
+ $dbr = wfGetDB( DB_SLAVE );
+ $query = $this->getQueryInfo();
+ $order = $this->getOrderFields();
+ if ( $this->sortDescending() ) {
+ foreach ( $order as &$field ) {
+ $field .= ' DESC';
+ }
+ }
+ if ( is_array( $query ) ) {
+ $tables = isset( $query['tables'] ) ? (array)$query['tables'] : array();
+ $fields = isset( $query['fields'] ) ? (array)$query['fields'] : array();
+ $conds = isset( $query['conds'] ) ? (array)$query['conds'] : array();
+ $options = isset( $query['options'] ) ? (array)$query['options'] : array();
+ $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : array();
+ if ( count( $order ) ) {
+ $options['ORDER BY'] = implode( ', ', $order );
+ }
+ if ( $limit !== false ) {
+ $options['LIMIT'] = intval( $limit );
+ }
+ if ( $offset !== false ) {
+ $options['OFFSET'] = intval( $offset );
+ }
+
+ $res = $dbr->select( $tables, $fields, $conds, $fname,
+ $options, $join_conds
+ );
+ } else {
+ // Old-fashioned raw SQL style, deprecated
+ $sql = $this->getSQL();
+ $sql .= ' ORDER BY ' . implode( ', ', $order );
+ $sql = $dbr->limitResult( $sql, $limit, $offset );
+ $res = $dbr->query( $sql, $fname );
+ }
+ return $dbr->resultObject( $res );
+ }
+
+ /**
+ * Somewhat deprecated, you probably want to be using execute()
+ */
+ function doQuery( $offset = false, $limit = false ) {
+ if ( $this->isCached() && $this->isCacheable() ) {
+ return $this->fetchFromCache( $limit, $offset );
+ } else {
+ return $this->reallyDoQuery( $limit, $offset );
+ }
+ }
+
+ /**
+ * Fetch the query results from the query cache
+ * @param $limit mixed Numerical limit or false for no limit
+ * @param $offset mixed Numerical offset or false for no offset
+ * @return ResultWrapper
+ * @since 1.18
+ */
+ function fetchFromCache( $limit, $offset = false ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $options = array ();
+ if ( $limit !== false ) {
+ $options['LIMIT'] = intval( $limit );
+ }
+ if ( $offset !== false ) {
+ $options['OFFSET'] = intval( $offset );
+ }
+ if ( $this->sortDescending() ) {
+ $options['ORDER BY'] = 'qc_value DESC';
+ } else {
+ $options['ORDER BY'] = 'qc_value ASC';
+ }
+ $res = $dbr->select( 'querycache', array( 'qc_type',
+ 'qc_namespace AS namespace',
+ 'qc_title AS title',
+ 'qc_value AS value' ),
+ array( 'qc_type' => $this->getName() ),
+ __METHOD__, $options
+ );
+ return $dbr->resultObject( $res );
+ }
+
+ public function getCachedTimestamp() {
+ if ( is_null( $this->cachedTimestamp ) ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $fname = get_class( $this ) . '::getCachedTimestamp';
+ $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp',
+ array( 'qci_type' => $this->getName() ), $fname );
+ }
+ return $this->cachedTimestamp;
+ }
+
+ /**
* This is the actual workhorse. It does everything needed to make a
* real, honest-to-gosh query page.
- *
- * @param $offset database query offset
- * @param $limit database query limit
- * @param $shownavigation show navigation like "next 200"?
*/
- function doQuery( $offset, $limit, $shownavigation=true ) {
- global $wgUser, $wgOut, $wgLang, $wgContLang;
+ function execute( $par ) {
+ global $wgUser, $wgOut, $wgLang, $wgRequest;
- $this->offset = $offset;
- $this->limit = $limit;
+ if ( !$this->userCanExecute( $wgUser ) ) {
+ $this->displayRestrictionError();
+ return;
+ }
- $sname = $this->getName();
- $fname = get_class($this) . '::doQuery';
+ if ( $this->limit == 0 && $this->offset == 0 ) {
+ list( $this->limit, $this->offset ) = $wgRequest->getLimitOffset();
+ }
$dbr = wfGetDB( DB_SLAVE );
+ $this->setHeaders();
$wgOut->setSyndicated( $this->isSyndicated() );
+ if ( $this->isCached() && !$this->isCacheable() ) {
+ $wgOut->setSyndicated( false );
+ $wgOut->addWikiMsg( 'querypage-disabled' );
+ return 0;
+ }
+
+ // TODO: Use doQuery()
+ // $res = null;
if ( !$this->isCached() ) {
- $sql = $this->getSQL();
+ $res = $this->reallyDoQuery( $this->limit, $this->offset );
} else {
# Get the cached result
- $querycache = $dbr->tableName( 'querycache' );
- $type = $dbr->strencode( $sname );
- $sql =
- "SELECT qc_type as type, qc_namespace as namespace,qc_title as title, qc_value as value
- FROM $querycache WHERE qc_type='$type'";
-
- if( !$this->listoutput ) {
+ $res = $this->fetchFromCache( $this->limit, $this->offset );
+ if ( !$this->listoutput ) {
# Fetch the timestamp of this update
- $tRes = $dbr->select( 'querycache_info', array( 'qci_timestamp' ), array( 'qci_type' => $type ), $fname );
- $tRow = $dbr->fetchObject( $tRes );
-
- if( $tRow ) {
- $updated = $wgLang->timeanddate( $tRow->qci_timestamp, true, true );
- $updateddate = $wgLang->date( $tRow->qci_timestamp, true, true );
- $updatedtime = $wgLang->time( $tRow->qci_timestamp, true, true );
- $wgOut->addMeta( 'Data-Cache-Time', $tRow->qci_timestamp );
- $wgOut->addInlineScript( "var dataCacheTime = '{$tRow->qci_timestamp}';" );
+ $ts = $this->getCachedTimestamp();
+
+ if ( $ts ) {
+ $updated = $wgLang->timeanddate( $ts, true, true );
+ $updateddate = $wgLang->date( $ts, true, true );
+ $updatedtime = $wgLang->time( $ts, true, true );
+ $wgOut->addMeta( 'Data-Cache-Time', $ts );
+ $wgOut->addInlineScript( "var dataCacheTime = '$ts';" );
$wgOut->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime );
} else {
$wgOut->addWikiMsg( 'perfcached' );
@@ -328,7 +484,7 @@ class QueryPage {
# If updates on this page have been disabled, let the user know
# that the data set won't be refreshed for now
global $wgDisableQueryPageUpdate;
- if( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) {
+ if ( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) {
$wgOut->addWikiMsg( 'querypage-no-updates' );
}
@@ -336,23 +492,21 @@ class QueryPage {
}
- $sql .= $this->getOrder();
- $sql = $dbr->limitResult($sql, $limit, $offset);
- $res = $dbr->query( $sql );
- $num = $dbr->numRows($res);
+ $this->numRows = $dbr->numRows( $res );
$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 ) {
+ if ( $this->shownavigation ) {
$wgOut->addHTML( $this->getPageHeader() );
- if( $num > 0 ) {
- $wgOut->addHTML( '<p>' . wfShowingResults( $offset, $num ) . '</p>' );
+ if ( $this->numRows > 0 ) {
+ $wgOut->addHTML( '<p>' . wfShowingResults( $this->offset, $this->numRows ) . '</p>' );
# Disable the "next" link when we reach the end
- $paging = wfViewPrevNext( $offset, $limit, $wgContLang->specialPage( $sname ),
- wfArrayToCGI( $this->linkParameters() ), ( $num < $limit ) );
+ $paging = wfViewPrevNext( $this->offset, $this->limit,
+ $this->getTitle( $par ),
+ wfArrayToCGI( $this->linkParameters() ), ( $this->numRows < $this->limit ) );
$wgOut->addHTML( '<p>' . $paging . '</p>' );
} else {
# No results to show, so don't bother with "showing X of Y" etc.
@@ -367,20 +521,20 @@ class QueryPage {
# with more than a straight list, so we hand them the info, plus
# an OutputPage, and let them get on with it
$this->outputResults( $wgOut,
- $wgUser->getSkin(),
+ $this->getSkin(),
$dbr, # Should use a ResultWrapper for this
$res,
- $dbr->numRows( $res ),
- $offset );
+ $this->numRows,
+ $this->offset );
# Repeat the paging links at the bottom
- if( $shownavigation ) {
+ if ( $this->shownavigation ) {
$wgOut->addHTML( '<p>' . $paging . '</p>' );
}
$wgOut->addHTML( Xml::closeElement( 'div' ) );
- return $num;
+ return $this->numRows;
}
/**
@@ -397,16 +551,17 @@ class QueryPage {
protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
global $wgContLang;
- if( $num > 0 ) {
+ if ( $num > 0 ) {
$html = array();
- if( !$this->listoutput )
+ if ( !$this->listoutput ) {
$html[] = $this->openList( $offset );
+ }
# $res might contain the whole 1,000 rows, so we read up to
# $num [should update this to use a Pager]
- for( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
+ for ( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
$line = $this->formatResult( $skin, $row );
- if( $line ) {
+ if ( $line ) {
$attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 )
? ' class="not-patrolled"'
: '';
@@ -417,10 +572,10 @@ class QueryPage {
}
# Flush the final result
- if( $this->tryLastResult() ) {
+ if ( $this->tryLastResult() ) {
$row = null;
$line = $this->formatResult( $skin, $row );
- if( $line ) {
+ if ( $line ) {
$attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 )
? ' class="not-patrolled"'
: '';
@@ -430,8 +585,9 @@ class QueryPage {
}
}
- if( !$this->listoutput )
+ if ( !$this->listoutput ) {
$html[] = $this->closeList();
+ }
$html = $this->listoutput
? $wgContLang->listToText( $html )
@@ -465,26 +621,23 @@ class QueryPage {
$wgOut->addWikiMsg( 'feed-unavailable' );
return;
}
-
+
global $wgFeedLimit;
- if( $limit > $wgFeedLimit ) {
+ if ( $limit > $wgFeedLimit ) {
$limit = $wgFeedLimit;
}
- if( isset($wgFeedClasses[$class]) ) {
+ if ( isset( $wgFeedClasses[$class] ) ) {
$feed = new $wgFeedClasses[$class](
$this->feedTitle(),
$this->feedDesc(),
$this->feedUrl() );
$feed->outHeader();
- $dbr = wfGetDB( DB_SLAVE );
- $sql = $this->getSQL() . $this->getOrder();
- $sql = $dbr->limitResult( $sql, $limit, 0 );
- $res = $dbr->query( $sql, 'QueryPage::doFeed' );
+ $res = $this->reallyDoQuery( $limit, 0 );
foreach ( $res as $obj ) {
$item = $this->feedResult( $obj );
- if( $item ) {
+ if ( $item ) {
$feed->outItem( $item );
}
}
@@ -501,14 +654,14 @@ class QueryPage {
* feedItemDesc()
*/
function feedResult( $row ) {
- if( !isset( $row->title ) ) {
+ if ( !isset( $row->title ) ) {
return null;
}
$title = Title::MakeTitle( intval( $row->namespace ), $row->title );
- if( $title ) {
+ if ( $title ) {
$date = isset( $row->timestamp ) ? $row->timestamp : '';
$comments = '';
- if( $title ) {
+ if ( $title ) {
$talkpage = $title->getTalkPage();
$comments = $talkpage->getFullURL();
}
@@ -519,7 +672,7 @@ class QueryPage {
$title->getFullURL(),
$date,
$this->feedItemAuthor( $row ),
- $comments);
+ $comments );
} else {
return null;
}
@@ -535,8 +688,7 @@ class QueryPage {
function feedTitle() {
global $wgLanguageCode, $wgSitename;
- $page = SpecialPage::getPage( $this->getName() );
- $desc = $page->getDescription();
+ $desc = $this->getDescription();
return "$wgSitename - $desc [$wgLanguageCode]";
}
@@ -545,8 +697,7 @@ class QueryPage {
}
function feedUrl() {
- $title = SpecialPage::getTitleFor( $this->getName() );
- return $title->getFullURL();
+ return $this->getTitle()->getFullURL();
}
}
@@ -579,7 +730,7 @@ abstract class WantedQueryPage extends QueryPage {
// If there are no rows we get an error seeking.
$db->dataSeek( $res, 0 );
}
-
+
/**
* Should formatResult() always check page existence, even if
* the results are fresh? This is a (hopefully temporary)
@@ -600,8 +751,8 @@ abstract class WantedQueryPage extends QueryPage {
*/
public function formatResult( $skin, $result ) {
$title = Title::makeTitleSafe( $result->namespace, $result->title );
- if( $title instanceof Title ) {
- if( $this->isCached() || $this->forceExistenceCheck() ) {
+ if ( $title instanceof Title ) {
+ if ( $this->isCached() || $this->forceExistenceCheck() ) {
$pageLink = $title->isKnown()
? '<del>' . $skin->link( $title ) . '</del>'
: $skin->link(
@@ -626,7 +777,7 @@ abstract class WantedQueryPage extends QueryPage {
return wfMsgHtml( 'wantedpages-badtitle', $tsafe );
}
}
-
+
/**
* Make a "what links here" link for a given title
*
diff --git a/includes/RawPage.php b/includes/RawPage.php
index ff935c2a..6a552a50 100644
--- a/includes/RawPage.php
+++ b/includes/RawPage.php
@@ -23,12 +23,12 @@ class RawPage {
var $mSmaxage, $mMaxage;
var $mContentType, $mExpandTemplates;
- function __construct( Article $article, $request = false ) {
- global $wgRequest, $wgInputEncoding, $wgSquidMaxage, $wgJsMimeType, $wgGroupPermissions;
+ function __construct( Page $article, $request = false ) {
+ global $wgRequest, $wgSquidMaxage, $wgJsMimeType, $wgGroupPermissions;
$allowedCTypes = array( 'text/x-wiki', $wgJsMimeType, 'text/css', 'application/x-zope-edit' );
$this->mArticle = $article;
- $this->mTitle = $article->mTitle;
+ $this->mTitle = $article->getTitle();
if( $request === false ) {
$this->mRequest = $wgRequest;
@@ -60,7 +60,7 @@ class RawPage {
if( !$oldid ) {
# get the current revision so we can get the penultimate one
$this->mArticle->getTouched();
- $oldid = $this->mArticle->mLatest;
+ $oldid = $this->mArticle->getLatest();
}
$prev = $this->mTitle->getPreviousRevisionId( $oldid );
$oldid = $prev ? $prev : -1 ;
@@ -89,7 +89,7 @@ class RawPage {
} else {
$this->mGen = false;
}
- $this->mCharset = $wgInputEncoding;
+ $this->mCharset = 'UTF-8';
# Force caching for CSS and JS raw content, default: 5 minutes
if( is_null( $smaxage ) && ( $ctype == 'text/css' || $ctype == $wgJsMimeType ) ) {
@@ -151,7 +151,7 @@ class RawPage {
}
function getRawText() {
- global $wgUser, $wgOut;
+ global $wgOut, $wgUser;
if( $this->mGen ) {
$sk = $wgUser->getSkin();
if( !StubObject::isRealObject( $wgOut ) ) {
@@ -160,7 +160,7 @@ class RawPage {
$sk->initPage( $wgOut );
if( $this->mGen == 'css' ) {
return $sk->generateUserStylesheet();
- } else if( $this->mGen == 'js' ) {
+ } elseif( $this->mGen == 'js' ) {
return $sk->generateUserJs();
}
} else {
@@ -175,11 +175,9 @@ class RawPage {
// If it's a MediaWiki message we can just hit the message cache
if( $this->mUseMessageCache && $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
$key = $this->mTitle->getDBkey();
- $text = wfMsgForContentNoTrans( $key );
+ $msg = wfMessage( $key )->inContentLanguage();
# If the message doesn't exist, return a blank
- if( wfEmptyMsg( $key, $text ) ) {
- $text = '';
- }
+ $text = !$msg->exists() ? '' : $msg->plain();
$found = true;
} else {
// Get it from the DB
@@ -208,23 +206,13 @@ class RawPage {
header( 'HTTP/1.0 404 Not Found' );
}
- // Special-case for empty CSS/JS
- //
- // Internet Explorer for Mac handles empty files badly;
- // particularly so when keep-alive is active. It can lead
- // to long timeouts as it seems to sit there waiting for
- // more data that never comes.
- //
- // Give it a comment...
- if( strlen( $text ) == 0 &&
- ( $this->mContentType == 'text/css' ||
- $this->mContentType == 'text/javascript' ) ) {
- return '/* Empty */';
- }
-
return $this->parseArticleText( $text );
}
+ /**
+ * @param $text
+ * @return string
+ */
function parseArticleText( $text ) {
if( $text === '' ) {
return '';
diff --git a/includes/RecentChange.php b/includes/RecentChange.php
index 803420f6..955986e9 100644
--- a/includes/RecentChange.php
+++ b/includes/RecentChange.php
@@ -44,7 +44,16 @@
*/
class RecentChange {
var $mAttribs = array(), $mExtra = array();
- var $mTitle = false, $mMovedToTitle = false;
+
+ /**
+ * @var Title
+ */
+ var $mTitle = false;
+
+ /**
+ * @var Title
+ */
+ var $mMovedToTitle = false;
var $numberofWatchingusers = 0 ; # Dummy to prevent error message in SpecialRecentchangeslinked
# Factory methods
@@ -66,7 +75,7 @@ class RecentChange {
/**
* Obtain the recent change with a given rc_id value
*
- * @param $rcid rc_id value to retrieve
+ * @param $rcid Int rc_id value to retrieve
* @return RecentChange
*/
public static function newFromId( $rcid ) {
@@ -192,7 +201,7 @@ class RecentChange {
$editor = ($wgUser->getName() == $this->mAttribs['rc_user_text']) ?
$wgUser : User::newFromName( $this->mAttribs['rc_user_text'], false );
}
- # FIXME: this would be better as an extension hook
+ # @todo FIXME: This would be better as an extension hook
$enotif = new EmailNotification();
$title = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
$enotif->notifyOnPageChange( $editor, $title,
@@ -259,24 +268,28 @@ class RecentChange {
* @return Array See doMarkPatrolled(), or null if $change is not an existing rc_id
*/
public static function markPatrolled( $change, $auto = false ) {
+ global $wgUser;
+
$change = $change instanceof RecentChange
? $change
: RecentChange::newFromId($change);
+
if( !$change instanceof RecentChange ) {
return null;
}
- return $change->doMarkPatrolled( $auto );
+ return $change->doMarkPatrolled( $wgUser, $auto );
}
/**
* Mark this RecentChange as patrolled
*
* NOTE: Can also return 'rcpatroldisabled', 'hookaborted' and 'markedaspatrollederror-noautopatrol' as errors
+ * @param $user User object doing the action
* @param $auto Boolean: for automatic patrol
* @return array of permissions errors, see Title::getUserPermissionsErrors()
*/
- public function doMarkPatrolled( $auto = false ) {
- global $wgUser, $wgUseRCPatrol, $wgUseNPPatrol;
+ public function doMarkPatrolled( User $user, $auto = false ) {
+ global $wgUseRCPatrol, $wgUseNPPatrol;
$errors = array();
// If recentchanges patrol is disabled, only new pages
// can be patrolled
@@ -285,13 +298,13 @@ class RecentChange {
}
// Automatic patrol needs "autopatrol", ordinary patrol needs "patrol"
$right = $auto ? 'autopatrol' : 'patrol';
- $errors = array_merge( $errors, $this->getTitle()->getUserPermissionsErrors( $right, $wgUser ) );
- if( !wfRunHooks('MarkPatrolled', array($this->getAttribute('rc_id'), &$wgUser, false)) ) {
+ $errors = array_merge( $errors, $this->getTitle()->getUserPermissionsErrors( $right, $user ) );
+ if( !wfRunHooks('MarkPatrolled', array($this->getAttribute('rc_id'), &$user, false)) ) {
$errors[] = array('hookaborted');
}
// Users without the 'autopatrol' right can't patrol their
// own revisions
- if( $wgUser->getName() == $this->getAttribute('rc_user_text') && !$wgUser->isAllowed('autopatrol') ) {
+ if( $user->getName() == $this->getAttribute('rc_user_text') && !$user->isAllowed('autopatrol') ) {
$errors[] = array('markedaspatrollederror-noautopatrol');
}
if( $errors ) {
@@ -305,7 +318,7 @@ class RecentChange {
$this->reallyMarkPatrolled();
// Log this patrol event
PatrolLog::record( $this, $auto );
- wfRunHooks( 'MarkPatrolledComplete', array($this->getAttribute('rc_id'), &$wgUser, false) );
+ wfRunHooks( 'MarkPatrolledComplete', array($this->getAttribute('rc_id'), &$user, false) );
return array();
}
@@ -328,10 +341,26 @@ class RecentChange {
return $dbw->affectedRows();
}
- # Makes an entry in the database corresponding to an edit
+ /**
+ * Makes an entry in the database corresponding to an edit
+ *
+ * @param $timestamp
+ * @param $title Title
+ * @param $minor
+ * @param $user User
+ * @param $comment
+ * @param $oldId
+ * @param $lastTimestamp
+ * @param $bot
+ * @param $ip string
+ * @param $oldSize int
+ * @param $newSize int
+ * @param $newId int
+ * @param $patrol int
+ * @return RecentChange
+ */
public static function notifyEdit( $timestamp, &$title, $minor, &$user, $comment, $oldId,
- $lastTimestamp, $bot, $ip='', $oldSize=0, $newSize=0, $newId=0, $patrol=0 )
- {
+ $lastTimestamp, $bot, $ip='', $oldSize=0, $newSize=0, $newId=0, $patrol=0 ) {
if( !$ip ) {
$ip = wfGetIP();
if( !$ip ) $ip = '';
@@ -380,10 +409,21 @@ class RecentChange {
* Makes an entry in the database corresponding to page creation
* Note: the title object must be loaded with the new id using resetArticleID()
* @todo Document parameters and return
+ *
+ * @param $timestamp
+ * @param $title Title
+ * @param $minor
+ * @param $user User
+ * @param $comment
+ * @param $bot
+ * @param $ip string
+ * @param $size int
+ * @param $newId int
+ * @param $patrol int
+ * @return RecentChange
*/
public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot,
- $ip='', $size=0, $newId=0, $patrol=0 )
- {
+ $ip='', $size=0, $newId=0, $patrol=0 ) {
if( !$ip ) {
$ip = wfGetIP();
if( !$ip ) $ip = '';
@@ -429,8 +469,19 @@ class RecentChange {
}
# Makes an entry in the database corresponding to a rename
- public static function notifyMove( $timestamp, &$oldTitle, &$newTitle, &$user, $comment, $ip='', $overRedir = false )
- {
+
+ /**
+ * @param $timestamp
+ * @param $oldTitle Title
+ * @param $newTitle Title
+ * @param $user User
+ * @param $comment
+ * @param $ip string
+ * @param $overRedir bool
+ * @return void
+ */
+ public static function notifyMove( $timestamp, &$oldTitle, &$newTitle, &$user, $comment, $ip='',
+ $overRedir = false ) {
global $wgRequest;
if( !$ip ) {
$ip = wfGetIP();
@@ -496,13 +547,28 @@ class RecentChange {
return true;
}
+ /**
+ * @param $timestamp
+ * @param $title Title
+ * @param $user User
+ * @param $actionComment
+ * @param $ip string
+ * @param $type
+ * @param $action
+ * @param $target Title
+ * @param $logComment
+ * @param $params
+ * @param $newId int
+ * @return RecentChange
+ */
public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip='',
- $type, $action, $target, $logComment, $params, $newId=0 )
- {
+ $type, $action, $target, $logComment, $params, $newId=0 ) {
global $wgRequest;
if( !$ip ) {
$ip = wfGetIP();
- if( !$ip ) $ip = '';
+ if( !$ip ) {
+ $ip = '';
+ }
}
$rc = new RecentChange;
@@ -614,41 +680,36 @@ class RecentChange {
}
public function getIRCLine() {
- global $wgUseRCPatrol, $wgUseNPPatrol, $wgRC2UDPInterwikiPrefix, $wgLocalInterwiki;
-
- // FIXME: Would be good to replace these 2 extract() calls with something more explicit
- // e.g. list ($rc_type, $rc_id) = array_values ($this->mAttribs); [or something like that]
- extract($this->mAttribs);
- extract($this->mExtra);
+ global $wgUseRCPatrol, $wgUseNPPatrol, $wgRC2UDPInterwikiPrefix, $wgLocalInterwiki,
+ $wgCanonicalServer, $wgScript;
- if( $rc_type == RC_LOG ) {
- $titleObj = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL );
+ if( $this->mAttribs['rc_type'] == RC_LOG ) {
+ $titleObj = Title::newFromText( 'Log/' . $this->mAttribs['rc_log_type'], NS_SPECIAL );
} else {
$titleObj =& $this->getTitle();
}
$title = $titleObj->getPrefixedText();
$title = self::cleanupForIRC( $title );
- if( $rc_type == RC_LOG ) {
+ if( $this->mAttribs['rc_type'] == RC_LOG ) {
$url = '';
} else {
- if( $rc_type == RC_NEW ) {
- $url = "oldid=$rc_this_oldid";
+ $url = $wgCanonicalServer . $wgScript;
+ if( $this->mAttribs['rc_type'] == RC_NEW ) {
+ $query = '?oldid=' . $this->mAttribs['rc_this_oldid'];
} else {
- $url = "diff=$rc_this_oldid&oldid=$rc_last_oldid";
+ $query = '?diff=' . $this->mAttribs['rc_this_oldid'] . '&oldid=' . $this->mAttribs['rc_last_oldid'];
}
- if( $wgUseRCPatrol || ($rc_type == RC_NEW && $wgUseNPPatrol) ) {
- $url .= "&rcid=$rc_id";
+ if ( $wgUseRCPatrol || ( $this->mAttribs['rc_type'] == RC_NEW && $wgUseNPPatrol ) ) {
+ $query .= '&rcid=' . $this->mAttribs['rc_id'];
}
- // XXX: *HACK* this should use getFullURL(), hacked for SSL madness --brion 2005-12-26
- // XXX: *HACK^2* the preg_replace() undoes much of what getInternalURL() does, but we
- // XXX: need to call it so that URL paths on the Wikimedia secure server can be fixed
- // XXX: by a custom GetInternalURL hook --vyznev 2008-12-10
- $url = preg_replace( '/title=[^&]*&/', '', $titleObj->getInternalURL( $url ) );
+ // HACK: We need this hook for WMF's secure server setup
+ wfRunHooks( 'IRCLineURL', array( &$url, &$query ) );
+ $url .= $query;
}
- if( isset( $oldSize ) && isset( $newSize ) ) {
- $szdiff = $newSize - $oldSize;
+ if( isset( $this->mExtra['oldSize'] ) && isset( $this->mExtra['newSize'] ) ) {
+ $szdiff = $this->mExtra['newSize'] - $this->mExtra['oldSize'];
if($szdiff < -500) {
$szdiff = "\002$szdiff\002";
} elseif($szdiff >= 0) {
@@ -659,19 +720,19 @@ class RecentChange {
$szdiff = '';
}
- $user = self::cleanupForIRC( $rc_user_text );
+ $user = self::cleanupForIRC( $this->mAttribs['rc_user_text'] );
- if( $rc_type == RC_LOG ) {
+ if ( $this->mAttribs['rc_type'] == RC_LOG ) {
$targetText = $this->getTitle()->getPrefixedText();
- $comment = self::cleanupForIRC( str_replace("[[$targetText]]","[[\00302$targetText\00310]]",$actionComment) );
- $flag = $rc_log_action;
+ $comment = self::cleanupForIRC( str_replace( "[[$targetText]]", "[[\00302$targetText\00310]]", $this->mExtra['actionComment'] ) );
+ $flag = $this->mAttribs['rc_log_action'];
} else {
- $comment = self::cleanupForIRC( $rc_comment );
+ $comment = self::cleanupForIRC( $this->mAttribs['rc_comment'] );
$flag = '';
- if( !$rc_patrolled && ($wgUseRCPatrol || $rc_new && $wgUseNPPatrol) ) {
+ if ( !$this->mAttribs['rc_patrolled'] && ( $wgUseRCPatrol || $this->mAttribs['rc_new'] && $wgUseNPPatrol ) ) {
$flag .= '!';
}
- $flag .= ($rc_new ? "N" : "") . ($rc_minor ? "M" : "") . ($rc_bot ? "B" : "");
+ $flag .= ( $this->mAttribs['rc_new'] ? "N" : "" ) . ( $this->mAttribs['rc_minor'] ? "M" : "" ) . ( $this->mAttribs['rc_bot'] ? "B" : "" );
}
if ( $wgRC2UDPInterwikiPrefix === true && $wgLocalInterwiki !== false ) {
diff --git a/includes/RequestContext.php b/includes/RequestContext.php
new file mode 100644
index 00000000..37617457
--- /dev/null
+++ b/includes/RequestContext.php
@@ -0,0 +1,399 @@
+<?php
+/**
+ * Request-dependant objects containers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @since 1.18
+ *
+ * @author Alexandre Emsenhuber
+ * @author Daniel Friesen
+ * @file
+ */
+
+/**
+ * Interface for objects which can provide a context on request.
+ */
+interface IContextSource {
+
+ /**
+ * Get the WebRequest object
+ *
+ * @return WebRequest
+ */
+ public function getRequest();
+
+ /**
+ * Get the Title object
+ *
+ * @return Title
+ */
+ public function getTitle();
+
+ /**
+ * Get the OutputPage object
+ *
+ * @return OutputPage object
+ */
+ public function getOutput();
+
+ /**
+ * Get the User object
+ *
+ * @return User
+ */
+ public function getUser();
+
+ /**
+ * Get the Language object
+ *
+ * @return Language
+ */
+ public function getLang();
+
+ /**
+ * Get the Skin object
+ *
+ * @return Skin
+ */
+ public function getSkin();
+}
+
+/**
+ * Group all the pieces relevant to the context of a request into one instance
+ */
+class RequestContext implements IContextSource {
+
+ /**
+ * @var WebRequest
+ */
+ private $request;
+
+ /**
+ * @var Title
+ */
+ private $title;
+
+ /**
+ * @var OutputPage
+ */
+ private $output;
+
+ /**
+ * @var User
+ */
+ private $user;
+
+ /**
+ * @var Language
+ */
+ private $lang;
+
+ /**
+ * @var Skin
+ */
+ private $skin;
+
+ /**
+ * Set the WebRequest object
+ *
+ * @param $r WebRequest object
+ */
+ public function setRequest( WebRequest $r ) {
+ $this->request = $r;
+ }
+
+ /**
+ * Get the WebRequest object
+ *
+ * @return WebRequest
+ */
+ public function getRequest() {
+ if ( $this->request === null ) {
+ global $wgRequest; # fallback to $wg till we can improve this
+ $this->request = $wgRequest;
+ }
+ return $this->request;
+ }
+
+ /**
+ * Set the Title object
+ *
+ * @param $t Title object
+ */
+ public function setTitle( Title $t ) {
+ $this->title = $t;
+ }
+
+ /**
+ * Get the Title object
+ *
+ * @return Title
+ */
+ public function getTitle() {
+ if ( $this->title === null ) {
+ global $wgTitle; # fallback to $wg till we can improve this
+ $this->title = $wgTitle;
+ }
+ return $this->title;
+ }
+
+ /**
+ * @param $o OutputPage
+ */
+ public function setOutput( OutputPage $o ) {
+ $this->output = $o;
+ }
+
+ /**
+ * Get the OutputPage object
+ *
+ * @return OutputPage object
+ */
+ public function getOutput() {
+ if ( $this->output === null ) {
+ $this->output = new OutputPage( $this );
+ }
+ return $this->output;
+ }
+
+ /**
+ * Set the User object
+ *
+ * @param $u User
+ */
+ public function setUser( User $u ) {
+ $this->user = $u;
+ }
+
+ /**
+ * Get the User object
+ *
+ * @return User
+ */
+ public function getUser() {
+ if ( $this->user === null ) {
+ $this->user = User::newFromSession( $this->getRequest() );
+ }
+ return $this->user;
+ }
+
+ /**
+ * Set the Language object
+ *
+ * @param $l Language
+ */
+ public function setLang( Language $l ) {
+ $this->lang = $l;
+ }
+
+ /**
+ * Get the Language object
+ *
+ * @return Language
+ */
+ public function getLang() {
+ if ( $this->lang === null ) {
+ global $wgLanguageCode, $wgContLang;
+ $code = $this->getRequest()->getVal(
+ 'uselang',
+ $this->getUser()->getOption( 'language' )
+ );
+ // BCP 47 - letter case MUST NOT carry meaning
+ $code = strtolower( $code );
+
+ # Validate $code
+ if( empty( $code ) || !Language::isValidCode( $code ) || ( $code === 'qqq' ) ) {
+ wfDebug( "Invalid user language code\n" );
+ $code = $wgLanguageCode;
+ }
+
+ wfRunHooks( 'UserGetLanguageObject', array( $this->getUser(), &$code ) );
+
+ if( $code === $wgLanguageCode ) {
+ $this->lang = $wgContLang;
+ } else {
+ $obj = Language::factory( $code );
+ $this->lang = $obj;
+ }
+ }
+ return $this->lang;
+ }
+
+ /**
+ * Set the Skin object
+ *
+ * @param $s Skin
+ */
+ public function setSkin( Skin $s ) {
+ $this->skin = clone $s;
+ $this->skin->setContext( $this );
+ }
+
+ /**
+ * Get the Skin object
+ *
+ * @return Skin
+ */
+ public function getSkin() {
+ if ( $this->skin === null ) {
+ wfProfileIn( __METHOD__ . '-createskin' );
+
+ global $wgHiddenPrefs;
+ if( !in_array( 'skin', $wgHiddenPrefs ) ) {
+ # get the user skin
+ $userSkin = $this->getUser()->getOption( 'skin' );
+ $userSkin = $this->getRequest()->getVal( 'useskin', $userSkin );
+ } else {
+ # if we're not allowing users to override, then use the default
+ global $wgDefaultSkin;
+ $userSkin = $wgDefaultSkin;
+ }
+
+ $this->skin = Skin::newFromKey( $userSkin );
+ $this->skin->setContext( $this );
+ wfProfileOut( __METHOD__ . '-createskin' );
+ }
+ return $this->skin;
+ }
+
+ /** Helpful methods **/
+
+ /**
+ * Get a Message object with context set
+ * Parameters are the same as wfMessage()
+ *
+ * @return Message object
+ */
+ public function msg() {
+ $args = func_get_args();
+ return call_user_func_array( 'wfMessage', $args )->inLanguage( $this->getLang() )->title( $this->getTitle() );
+ }
+
+ /** Static methods **/
+
+ /**
+ * Get the RequestContext object associated with the main request
+ *
+ * @return RequestContext object
+ */
+ public static function getMain() {
+ static $instance = null;
+ if ( $instance === null ) {
+ $instance = new self;
+ }
+ return $instance;
+ }
+}
+
+/**
+ * The simplest way of implementing IContextSource is to hold a RequestContext as a
+ * member variable and provide accessors to it.
+ */
+abstract class ContextSource implements IContextSource {
+
+ /**
+ * @var IContextSource
+ */
+ private $context;
+
+ /**
+ * Get the IContextSource object
+ *
+ * @return IContextSource
+ */
+ public function getContext() {
+ if ( $this->context === null ) {
+ $class = get_class( $this );
+ wfDebug( __METHOD__ . " ($class): called and \$context is null. Using RequestContext::getMain() for sanity\n" );
+ $this->context = RequestContext::getMain();
+ }
+ return $this->context;
+ }
+
+ /**
+ * Set the IContextSource object
+ *
+ * @param $context IContextSource
+ */
+ public function setContext( IContextSource $context ) {
+ $this->context = $context;
+ }
+
+ /**
+ * Get the WebRequest object
+ *
+ * @return WebRequest
+ */
+ public function getRequest() {
+ return $this->getContext()->getRequest();
+ }
+
+ /**
+ * Get the Title object
+ *
+ * @return Title
+ */
+ public function getTitle() {
+ return $this->getContext()->getTitle();
+ }
+
+ /**
+ * Get the OutputPage object
+ *
+ * @return OutputPage object
+ */
+ public function getOutput() {
+ return $this->getContext()->getOutput();
+ }
+
+ /**
+ * Get the User object
+ *
+ * @return User
+ */
+ public function getUser() {
+ return $this->getContext()->getUser();
+ }
+
+ /**
+ * Get the Language object
+ *
+ * @return Language
+ */
+ public function getLang() {
+ return $this->getContext()->getLang();
+ }
+
+ /**
+ * Get the Skin object
+ *
+ * @return Skin
+ */
+ public function getSkin() {
+ return $this->getContext()->getSkin();
+ }
+
+ /**
+ * Get a Message object with context set
+ * Parameters are the same as wfMessage()
+ *
+ * @return Message object
+ */
+ public function msg( /* $args */ ) {
+ return call_user_func_array( array( $this->getContext(), 'msg' ), func_get_args() );
+ }
+}
diff --git a/includes/Revision.php b/includes/Revision.php
index 9cc49350..b5d776bd 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -25,7 +25,7 @@ class Revision {
public static function newFromId( $id ) {
return Revision::newFromConds(
array( 'page_id=rev_page',
- 'rev_id' => intval( $id ) ) );
+ 'rev_id' => intval( $id ) ) );
}
/**
@@ -34,13 +34,13 @@ class Revision {
* to that title, will return null.
*
* @param $title Title
- * @param $id Integer
+ * @param $id Integer (optional)
* @return Revision or null
*/
public static function newFromTitle( $title, $id = 0 ) {
- $conds = array(
- 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey()
+ $conds = array(
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey()
);
if ( $id ) {
// Use the specified ID
@@ -50,8 +50,7 @@ class Revision {
$dbw = wfGetDB( DB_MASTER );
$latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
if ( $latest === false ) {
- // Page does not exist
- return null;
+ return null; // page does not exist
}
$conds['rev_id'] = $latest;
} else {
@@ -63,9 +62,42 @@ class Revision {
}
/**
+ * Load either the current, or a specified, revision
+ * that's attached to a given page ID.
+ * Returns null if no such revision can be found.
+ *
+ * @param $revId Integer
+ * @param $pageId Integer (optional)
+ * @return Revision or null
+ */
+ public static function newFromPageId( $pageId, $revId = 0 ) {
+ $conds = array( 'page_id' => $pageId );
+ if ( $revId ) {
+ $conds['rev_id'] = $revId;
+ } elseif ( wfGetLB()->getServerCount() > 1 ) {
+ // Get the latest revision ID from the master
+ $dbw = wfGetDB( DB_MASTER );
+ $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
+ if ( $latest === false ) {
+ return null; // page does not exist
+ }
+ $conds['rev_id'] = $latest;
+ } else {
+ $conds[] = 'rev_id = page_latest';
+ }
+ $conds[] = 'page_id=rev_page';
+ return Revision::newFromConds( $conds );
+ }
+
+ /**
* Make a fake revision object from an archive table row. This is queried
* for permissions or even inserted (as in Special:Undelete)
- * @todo Fixme: should be a subclass for RevisionDelete. [TS]
+ * @todo FIXME: Should be a subclass for RevisionDelete. [TS]
+ *
+ * @param $row
+ * @param $overrides array
+ *
+ * @return Revision
*/
public static function newFromArchiveRow( $row, $overrides = array() ) {
$attribs = $overrides + array(
@@ -90,6 +122,14 @@ class Revision {
}
/**
+ * @param $row
+ * @return Revision
+ */
+ public static function newFromRow( $row ) {
+ return new self( $row );
+ }
+
+ /**
* Load a page revision from a given revision ID number.
* Returns null if no such revision can be found.
*
@@ -100,7 +140,7 @@ class Revision {
public static function loadFromId( $db, $id ) {
return Revision::loadFromConds( $db,
array( 'page_id=rev_page',
- 'rev_id' => intval( $id ) ) );
+ 'rev_id' => intval( $id ) ) );
}
/**
@@ -142,9 +182,9 @@ class Revision {
return Revision::loadFromConds(
$db,
array( "rev_id=$matchId",
- 'page_id=rev_page',
- 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey() ) );
+ 'page_id=rev_page',
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey() ) );
}
/**
@@ -152,7 +192,7 @@ class Revision {
* WARNING: Timestamps may in some circumstances not be unique,
* so this isn't the best key to use.
*
- * @param $db Database
+ * @param $db DatabaseBase
* @param $title Title
* @param $timestamp String
* @return Revision or null
@@ -161,9 +201,9 @@ class Revision {
return Revision::loadFromConds(
$db,
array( 'rev_timestamp' => $db->timestamp( $timestamp ),
- 'page_id=rev_page',
- 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey() ) );
+ 'page_id=rev_page',
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey() ) );
}
/**
@@ -186,7 +226,7 @@ class Revision {
* Given a set of conditions, fetch a revision from
* the given database connection.
*
- * @param $db Database
+ * @param $db DatabaseBase
* @param $conditions Array
* @return Revision or null
*/
@@ -216,9 +256,9 @@ class Revision {
return Revision::fetchFromConds(
wfGetDB( DB_SLAVE ),
array( 'rev_id=page_latest',
- 'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey(),
- 'page_id=rev_page' ) );
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey(),
+ 'page_id=rev_page' ) );
}
/**
@@ -226,7 +266,7 @@ class Revision {
* which will return matching database rows with the
* fields necessary to build Revision objects.
*
- * @param $db Database
+ * @param $db DatabaseBase
* @param $conditions Array
* @return ResultWrapper
*/
@@ -235,21 +275,19 @@ class Revision {
$fields[] = 'page_namespace';
$fields[] = 'page_title';
$fields[] = 'page_latest';
- $res = $db->select(
+ return $db->select(
array( 'page', 'revision' ),
$fields,
$conditions,
__METHOD__,
array( 'LIMIT' => 1 ) );
- $ret = $db->resultObject( $res );
- return $ret;
}
/**
* Return the list of revision fields that should be selected to create
* a new revision.
*/
- static function selectFields() {
+ public static function selectFields() {
return array(
'rev_id',
'rev_page',
@@ -264,9 +302,9 @@ class Revision {
'rev_parent_id'
);
}
-
+
/**
- * Return the list of text fields that should be selected to read the
+ * Return the list of text fields that should be selected to read the
* revision text
*/
static function selectTextFields() {
@@ -412,11 +450,11 @@ class Revision {
array( 'page', 'revision' ),
array( 'page_namespace', 'page_title' ),
array( 'page_id=rev_page',
- 'rev_id' => $this->mId ),
+ 'rev_id' => $this->mId ),
'Revision::getTitle' );
if( $row ) {
$this->mTitle = Title::makeTitle( $row->page_namespace,
- $row->page_title );
+ $row->page_title );
}
return $this->mTitle;
}
@@ -441,7 +479,7 @@ class Revision {
/**
* Fetch revision's user id if it's available to the specified audience.
- * If the specified audience does not have access to it, zero will be
+ * If the specified audience does not have access to it, zero will be
* returned.
*
* @param $audience Integer: one of:
@@ -473,7 +511,7 @@ class Revision {
/**
* Fetch revision's username if it's available to the specified audience.
- * If the specified audience does not have access to the username, an
+ * If the specified audience does not have access to the username, an
* empty string will be returned.
*
* @param $audience Integer: one of:
@@ -504,7 +542,7 @@ class Revision {
/**
* Fetch revision comment if it's available to the specified audience.
- * If the specified audience does not have access to the comment, an
+ * If the specified audience does not have access to the comment, an
* empty string will be returned.
*
* @param $audience Integer: one of:
@@ -539,7 +577,7 @@ class Revision {
public function isMinor() {
return (bool)$this->mMinorEdit;
}
-
+
/**
* @return Integer rcid of the unpatrolled row, zero if there isn't one
*/
@@ -579,7 +617,7 @@ class Revision {
/**
* Fetch revision text if it's available to the specified audience.
- * If the specified audience does not have the ability to view this
+ * If the specified audience does not have the ability to view this
* revision, an empty string will be returned.
*
* @param $audience Integer: one of:
@@ -587,7 +625,6 @@ class Revision {
* Revision::FOR_THIS_USER to be displayed to $wgUser
* Revision::RAW get the text regardless of permissions
*
- *
* @return String
*/
public function getText( $audience = self::FOR_PUBLIC ) {
@@ -603,10 +640,11 @@ class Revision {
/**
* Alias for getText(Revision::FOR_THIS_USER)
*
- * @deprecated
+ * @deprecated since 1.17
* @return String
*/
public function revText() {
+ wfDeprecated( __METHOD__ );
return $this->getText( self::FOR_THIS_USER );
}
@@ -724,8 +762,8 @@ class Revision {
# Use external methods for external objects, text in table is URL-only then
if ( in_array( 'external', $flags ) ) {
$url = $text;
- @list(/* $proto */, $path ) = explode( '://', $url, 2 );
- if( $path == '' ) {
+ $parts = explode( '://', $url, 2 );
+ if( count( $parts ) == 1 || $parts[1] == '' ) {
wfProfileOut( __METHOD__ );
return false;
}
@@ -753,15 +791,15 @@ class Revision {
}
global $wgLegacyEncoding;
- if( $text !== false && $wgLegacyEncoding
- && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags ) )
+ if( $text !== false && $wgLegacyEncoding
+ && !in_array( 'utf-8', $flags ) && !in_array( 'utf8', $flags ) )
{
# Old revisions kept around in a legacy encoding?
# Upconvert on demand.
# ("utf8" checked for compatibility with some broken
# conversion scripts 2008-12-30)
- global $wgInputEncoding, $wgContLang;
- $text = $wgContLang->iconv( $wgLegacyEncoding, $wgInputEncoding, $text );
+ global $wgContLang;
+ $text = $wgContLang->iconv( $wgLegacyEncoding, 'UTF-8', $text );
}
}
wfProfileOut( __METHOD__ );
@@ -941,7 +979,7 @@ class Revision {
* @param $pageId Integer: ID number of the page to read from
* @param $summary String: revision's summary
* @param $minor Boolean: whether the revision should be considered as minor
- * @return Mixed: Revision, or null on error
+ * @return Revision|null on error
*/
public static function newNullRevision( $dbw, $pageId, $summary, $minor ) {
wfProfileIn( __METHOD__ );
diff --git a/includes/RevisionList.php b/includes/RevisionList.php
new file mode 100644
index 00000000..ae067ead
--- /dev/null
+++ b/includes/RevisionList.php
@@ -0,0 +1,370 @@
+<?php
+/**
+ * List for revision table items for a single page
+ */
+abstract class Rev_List {
+ /**
+ * @var Title
+ */
+ var $title;
+ /**
+ * @var IContextSource
+ */
+ var $context;
+
+ var $ids, $res, $current;
+
+ /**
+ * Construct a revision list for a given title
+ * @param $context IContextSource
+ * @param $title Title
+ */
+ function __construct( IContextSource $context, Title $title ) {
+ $this->context = $context;
+ $this->title = $title;
+ }
+
+ /**
+ * Select items only where the ID is any of the specified values
+ * @param $ids Array
+ */
+ function filterByIds( array $ids ) {
+ $this->ids = $ids;
+ }
+
+ /**
+ * Get the internal type name of this list. Equal to the table name.
+ * Override this function.
+ */
+ public function getType() {
+ return null;
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+ /**
+ * Do the DB query to iterate through the objects.
+ * @param $db DatabaseBase object to use for the query
+ */
+ abstract public function doQuery( $db );
+
+ /**
+ * Create an item object from a DB result row
+ * @param $row stdclass
+ */
+ abstract public function newItem( $row );
+
+ /**
+ * Get the language of the user doing the action
+ *
+ * @return Language object
+ */
+ public function getLang() {
+ return $this->context->getLang();
+ }
+
+ /**
+ * Get the user doing the action
+ *
+ * @return User object
+ */
+ public function getUser() {
+ return $this->context->getUser();
+ }
+}
+
+/**
+ * Abstract base class for revision items
+ */
+abstract class Rev_Item {
+ /** The parent Rev_List */
+ var $list;
+
+ /** The DB result row */
+ var $row;
+
+ /**
+ * @param $list Rev_List
+ * @param $row DB result row
+ */
+ public function __construct( $list, $row ) {
+ $this->list = $list;
+ $this->row = $row;
+ }
+
+ /**
+ * Get the DB field name associated with the ID list.
+ * Override this function.
+ */
+ public function getIdField() {
+ return null;
+ }
+
+ /**
+ * Get the DB field name storing timestamps.
+ * Override this function.
+ */
+ public function getTimestampField() {
+ return false;
+ }
+
+ /**
+ * Get the DB field name storing user ids.
+ * Override this function.
+ */
+ public function getAuthorIdField() {
+ return false;
+ }
+
+ /**
+ * Get the DB field name storing user names.
+ * Override this function.
+ */
+ public function getAuthorNameField() {
+ return false;
+ }
+
+ /**
+ * Get the ID, as it would appear in the ids URL parameter
+ */
+ public function getId() {
+ $field = $this->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->getTimestampField();
+ return wfTimestamp( TS_MW, $this->row->$field );
+ }
+
+ /**
+ * Get the author user ID
+ */
+ public function getAuthorId() {
+ $field = $this->getAuthorIdField();
+ return intval( $this->row->$field );
+ }
+
+ /**
+ * Get the author user name
+ */
+ public function getAuthorName() {
+ $field = $this->getAuthorNameField();
+ return strval( $this->row->$field );
+ }
+
+ /**
+ * 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 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();
+}
+
+class RevisionList extends Rev_List {
+ public function getType() {
+ return 'revision';
+ }
+
+ /**
+ * @param $db DatabaseBase
+ * @return mixed
+ */
+ public function doQuery( $db ) {
+ $conds = array(
+ 'rev_page' => $this->title->getArticleID(),
+ 'rev_page = page_id'
+ );
+ if ( $this->ids !== null ) {
+ $conds['rev_id'] = array_map( 'intval', $this->ids );
+ }
+ return $db->select(
+ array( 'revision', 'page' ),
+ '*',
+ $conds,
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_id DESC' )
+ );
+ }
+
+ public function newItem( $row ) {
+ return new RevisionItem( $this, $row );
+ }
+}
+
+/**
+ * Item class for a live revision table row
+ */
+class RevisionItem extends Rev_Item {
+ var $revision, $context;
+
+ public function __construct( $list, $row ) {
+ parent::__construct( $list, $row );
+ $this->revision = new Revision( $row );
+ $this->context = $list->context;
+ }
+
+ public function getIdField() {
+ return 'rev_id';
+ }
+
+ public function getTimestampField() {
+ return 'rev_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'rev_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'rev_user_text';
+ }
+
+ public function canView() {
+ return $this->revision->userCan( Revision::DELETED_RESTRICTED );
+ }
+
+ public function canViewContent() {
+ return $this->revision->userCan( Revision::DELETED_TEXT );
+ }
+
+ public function isDeleted() {
+ return $this->revision->isDeleted( Revision::DELETED_TEXT );
+ }
+
+ /**
+ * Get the HTML link to the revision text.
+ * Overridden by RevDel_ArchiveItem.
+ */
+ protected function getRevisionLink() {
+ $date = $this->list->getLang()->timeanddate( $this->revision->getTimestamp(), true );
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $date;
+ }
+ return Linker::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
+ Linker::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 = Linker::revUserLink( $this->revision );
+ $comment = Linker::revComment( $this->revision );
+ if ( $this->isDeleted() ) {
+ $revlink = "<span class=\"history-deleted\">$revlink</span>";
+ }
+ return "<li>($difflink) $revlink $userlink $comment</li>";
+ }
+}
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index a6c64264..d0e46f91 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -25,323 +25,325 @@
*/
/**
- * Regular expression to match various types of character references in
- * Sanitizer::normalizeCharReferences and Sanitizer::decodeCharReferences
- */
-define( 'MW_CHAR_REFS_REGEX',
- '/&([A-Za-z0-9\x80-\xff]+);
- |&\#([0-9]+);
- |&\#x([0-9A-Za-z]+);
- |&\#X([0-9A-Za-z]+);
- |(&)/x' );
-
-/**
- * Regular expression to match HTML/XML attribute pairs within a tag.
- * Allows some... latitude.
- * Used in Sanitizer::fixTagAttributes and Sanitizer::decodeTagAttributes
+ * XHTML sanitizer for MediaWiki
+ * @ingroup Parser
*/
-$attribFirst = '[:A-Z_a-z0-9]';
-$attrib = '[:A-Z_a-z-.0-9]';
-$space = '[\x09\x0a\x0d\x20]';
-define( 'MW_ATTRIBS_REGEX',
- "/(?:^|$space)({$attribFirst}{$attrib}*)
- ($space*=$space*
- (?:
- # The attribute value: quoted or alone
- \"([^<\"]*)\"
- | '([^<']*)'
- | ([a-zA-Z0-9!#$%&()*,\\-.\\/:;<>?@[\\]^_`{|}~]+)
- | (\#[0-9a-fA-F]+) # Technically wrong, but lots of
- # colors are specified like this.
- # We'll be normalizing it.
- )
- )?(?=$space|\$)/sx" );
+class Sanitizer {
+ /**
+ * Regular expression to match various types of character references in
+ * Sanitizer::normalizeCharReferences and Sanitizer::decodeCharReferences
+ */
+ const CHAR_REFS_REGEX =
+ '/&([A-Za-z0-9\x80-\xff]+);
+ |&\#([0-9]+);
+ |&\#[xX]([0-9A-Fa-f]+);
+ |(&)/x';
-/**
- * Regular expression to match URIs that could trigger script execution
- */
-define( 'MW_EVIL_URI_PATTERN', '!(^|\s|\*/\s*)(javascript|vbscript)([^\w]|$)!i' );
+ const EVIL_URI_PATTERN = '!(^|\s|\*/\s*)(javascript|vbscript)([^\w]|$)!i';
+ const XMLNS_ATTRIBUTE_PATTERN = "/^xmlns:[:A-Z_a-z-.0-9]+$/";
-/**
- * Regular expression to match namespace attributes
- */
-define( 'MW_XMLNS_ATTRIBUTE_PATTRN', "/^xmlns:$attrib+$/" );
+ /**
+ * List of all named character entities defined in HTML 4.01
+ * http://www.w3.org/TR/html4/sgml/entities.html
+ * As well as &apos; which is only defined starting in XHTML1.
+ * @private
+ */
+ static $htmlEntities = array(
+ 'Aacute' => 193,
+ 'aacute' => 225,
+ 'Acirc' => 194,
+ 'acirc' => 226,
+ 'acute' => 180,
+ 'AElig' => 198,
+ 'aelig' => 230,
+ 'Agrave' => 192,
+ 'agrave' => 224,
+ 'alefsym' => 8501,
+ 'Alpha' => 913,
+ 'alpha' => 945,
+ 'amp' => 38,
+ 'and' => 8743,
+ 'ang' => 8736,
+ 'apos' => 39, // New in XHTML & HTML 5; avoid in output for compatibility with IE.
+ 'Aring' => 197,
+ 'aring' => 229,
+ 'asymp' => 8776,
+ 'Atilde' => 195,
+ 'atilde' => 227,
+ 'Auml' => 196,
+ 'auml' => 228,
+ 'bdquo' => 8222,
+ 'Beta' => 914,
+ 'beta' => 946,
+ 'brvbar' => 166,
+ 'bull' => 8226,
+ 'cap' => 8745,
+ 'Ccedil' => 199,
+ 'ccedil' => 231,
+ 'cedil' => 184,
+ 'cent' => 162,
+ 'Chi' => 935,
+ 'chi' => 967,
+ 'circ' => 710,
+ 'clubs' => 9827,
+ 'cong' => 8773,
+ 'copy' => 169,
+ 'crarr' => 8629,
+ 'cup' => 8746,
+ 'curren' => 164,
+ 'dagger' => 8224,
+ 'Dagger' => 8225,
+ 'darr' => 8595,
+ 'dArr' => 8659,
+ 'deg' => 176,
+ 'Delta' => 916,
+ 'delta' => 948,
+ 'diams' => 9830,
+ 'divide' => 247,
+ 'Eacute' => 201,
+ 'eacute' => 233,
+ 'Ecirc' => 202,
+ 'ecirc' => 234,
+ 'Egrave' => 200,
+ 'egrave' => 232,
+ 'empty' => 8709,
+ 'emsp' => 8195,
+ 'ensp' => 8194,
+ 'Epsilon' => 917,
+ 'epsilon' => 949,
+ 'equiv' => 8801,
+ 'Eta' => 919,
+ 'eta' => 951,
+ 'ETH' => 208,
+ 'eth' => 240,
+ 'Euml' => 203,
+ 'euml' => 235,
+ 'euro' => 8364,
+ 'exist' => 8707,
+ 'fnof' => 402,
+ 'forall' => 8704,
+ 'frac12' => 189,
+ 'frac14' => 188,
+ 'frac34' => 190,
+ 'frasl' => 8260,
+ 'Gamma' => 915,
+ 'gamma' => 947,
+ 'ge' => 8805,
+ 'gt' => 62,
+ 'harr' => 8596,
+ 'hArr' => 8660,
+ 'hearts' => 9829,
+ 'hellip' => 8230,
+ 'Iacute' => 205,
+ 'iacute' => 237,
+ 'Icirc' => 206,
+ 'icirc' => 238,
+ 'iexcl' => 161,
+ 'Igrave' => 204,
+ 'igrave' => 236,
+ 'image' => 8465,
+ 'infin' => 8734,
+ 'int' => 8747,
+ 'Iota' => 921,
+ 'iota' => 953,
+ 'iquest' => 191,
+ 'isin' => 8712,
+ 'Iuml' => 207,
+ 'iuml' => 239,
+ 'Kappa' => 922,
+ 'kappa' => 954,
+ 'Lambda' => 923,
+ 'lambda' => 955,
+ 'lang' => 9001,
+ 'laquo' => 171,
+ 'larr' => 8592,
+ 'lArr' => 8656,
+ 'lceil' => 8968,
+ 'ldquo' => 8220,
+ 'le' => 8804,
+ 'lfloor' => 8970,
+ 'lowast' => 8727,
+ 'loz' => 9674,
+ 'lrm' => 8206,
+ 'lsaquo' => 8249,
+ 'lsquo' => 8216,
+ 'lt' => 60,
+ 'macr' => 175,
+ 'mdash' => 8212,
+ 'micro' => 181,
+ 'middot' => 183,
+ 'minus' => 8722,
+ 'Mu' => 924,
+ 'mu' => 956,
+ 'nabla' => 8711,
+ 'nbsp' => 160,
+ 'ndash' => 8211,
+ 'ne' => 8800,
+ 'ni' => 8715,
+ 'not' => 172,
+ 'notin' => 8713,
+ 'nsub' => 8836,
+ 'Ntilde' => 209,
+ 'ntilde' => 241,
+ 'Nu' => 925,
+ 'nu' => 957,
+ 'Oacute' => 211,
+ 'oacute' => 243,
+ 'Ocirc' => 212,
+ 'ocirc' => 244,
+ 'OElig' => 338,
+ 'oelig' => 339,
+ 'Ograve' => 210,
+ 'ograve' => 242,
+ 'oline' => 8254,
+ 'Omega' => 937,
+ 'omega' => 969,
+ 'Omicron' => 927,
+ 'omicron' => 959,
+ 'oplus' => 8853,
+ 'or' => 8744,
+ 'ordf' => 170,
+ 'ordm' => 186,
+ 'Oslash' => 216,
+ 'oslash' => 248,
+ 'Otilde' => 213,
+ 'otilde' => 245,
+ 'otimes' => 8855,
+ 'Ouml' => 214,
+ 'ouml' => 246,
+ 'para' => 182,
+ 'part' => 8706,
+ 'permil' => 8240,
+ 'perp' => 8869,
+ 'Phi' => 934,
+ 'phi' => 966,
+ 'Pi' => 928,
+ 'pi' => 960,
+ 'piv' => 982,
+ 'plusmn' => 177,
+ 'pound' => 163,
+ 'prime' => 8242,
+ 'Prime' => 8243,
+ 'prod' => 8719,
+ 'prop' => 8733,
+ 'Psi' => 936,
+ 'psi' => 968,
+ 'quot' => 34,
+ 'radic' => 8730,
+ 'rang' => 9002,
+ 'raquo' => 187,
+ 'rarr' => 8594,
+ 'rArr' => 8658,
+ 'rceil' => 8969,
+ 'rdquo' => 8221,
+ 'real' => 8476,
+ 'reg' => 174,
+ 'rfloor' => 8971,
+ 'Rho' => 929,
+ 'rho' => 961,
+ 'rlm' => 8207,
+ 'rsaquo' => 8250,
+ 'rsquo' => 8217,
+ 'sbquo' => 8218,
+ 'Scaron' => 352,
+ 'scaron' => 353,
+ 'sdot' => 8901,
+ 'sect' => 167,
+ 'shy' => 173,
+ 'Sigma' => 931,
+ 'sigma' => 963,
+ 'sigmaf' => 962,
+ 'sim' => 8764,
+ 'spades' => 9824,
+ 'sub' => 8834,
+ 'sube' => 8838,
+ 'sum' => 8721,
+ 'sup' => 8835,
+ 'sup1' => 185,
+ 'sup2' => 178,
+ 'sup3' => 179,
+ 'supe' => 8839,
+ 'szlig' => 223,
+ 'Tau' => 932,
+ 'tau' => 964,
+ 'there4' => 8756,
+ 'Theta' => 920,
+ 'theta' => 952,
+ 'thetasym' => 977,
+ 'thinsp' => 8201,
+ 'THORN' => 222,
+ 'thorn' => 254,
+ 'tilde' => 732,
+ 'times' => 215,
+ 'trade' => 8482,
+ 'Uacute' => 218,
+ 'uacute' => 250,
+ 'uarr' => 8593,
+ 'uArr' => 8657,
+ 'Ucirc' => 219,
+ 'ucirc' => 251,
+ 'Ugrave' => 217,
+ 'ugrave' => 249,
+ 'uml' => 168,
+ 'upsih' => 978,
+ 'Upsilon' => 933,
+ 'upsilon' => 965,
+ 'Uuml' => 220,
+ 'uuml' => 252,
+ 'weierp' => 8472,
+ 'Xi' => 926,
+ 'xi' => 958,
+ 'Yacute' => 221,
+ 'yacute' => 253,
+ 'yen' => 165,
+ 'Yuml' => 376,
+ 'yuml' => 255,
+ 'Zeta' => 918,
+ 'zeta' => 950,
+ 'zwj' => 8205,
+ 'zwnj' => 8204
+ );
-/**
- * List of all named character entities defined in HTML 4.01
- * http://www.w3.org/TR/html4/sgml/entities.html
- * @private
- */
-global $wgHtmlEntities;
-$wgHtmlEntities = array(
- 'Aacute' => 193,
- 'aacute' => 225,
- 'Acirc' => 194,
- 'acirc' => 226,
- 'acute' => 180,
- 'AElig' => 198,
- 'aelig' => 230,
- 'Agrave' => 192,
- 'agrave' => 224,
- 'alefsym' => 8501,
- 'Alpha' => 913,
- 'alpha' => 945,
- 'amp' => 38,
- 'and' => 8743,
- 'ang' => 8736,
- 'Aring' => 197,
- 'aring' => 229,
- 'asymp' => 8776,
- 'Atilde' => 195,
- 'atilde' => 227,
- 'Auml' => 196,
- 'auml' => 228,
- 'bdquo' => 8222,
- 'Beta' => 914,
- 'beta' => 946,
- 'brvbar' => 166,
- 'bull' => 8226,
- 'cap' => 8745,
- 'Ccedil' => 199,
- 'ccedil' => 231,
- 'cedil' => 184,
- 'cent' => 162,
- 'Chi' => 935,
- 'chi' => 967,
- 'circ' => 710,
- 'clubs' => 9827,
- 'cong' => 8773,
- 'copy' => 169,
- 'crarr' => 8629,
- 'cup' => 8746,
- 'curren' => 164,
- 'dagger' => 8224,
- 'Dagger' => 8225,
- 'darr' => 8595,
- 'dArr' => 8659,
- 'deg' => 176,
- 'Delta' => 916,
- 'delta' => 948,
- 'diams' => 9830,
- 'divide' => 247,
- 'Eacute' => 201,
- 'eacute' => 233,
- 'Ecirc' => 202,
- 'ecirc' => 234,
- 'Egrave' => 200,
- 'egrave' => 232,
- 'empty' => 8709,
- 'emsp' => 8195,
- 'ensp' => 8194,
- 'Epsilon' => 917,
- 'epsilon' => 949,
- 'equiv' => 8801,
- 'Eta' => 919,
- 'eta' => 951,
- 'ETH' => 208,
- 'eth' => 240,
- 'Euml' => 203,
- 'euml' => 235,
- 'euro' => 8364,
- 'exist' => 8707,
- 'fnof' => 402,
- 'forall' => 8704,
- 'frac12' => 189,
- 'frac14' => 188,
- 'frac34' => 190,
- 'frasl' => 8260,
- 'Gamma' => 915,
- 'gamma' => 947,
- 'ge' => 8805,
- 'gt' => 62,
- 'harr' => 8596,
- 'hArr' => 8660,
- 'hearts' => 9829,
- 'hellip' => 8230,
- 'Iacute' => 205,
- 'iacute' => 237,
- 'Icirc' => 206,
- 'icirc' => 238,
- 'iexcl' => 161,
- 'Igrave' => 204,
- 'igrave' => 236,
- 'image' => 8465,
- 'infin' => 8734,
- 'int' => 8747,
- 'Iota' => 921,
- 'iota' => 953,
- 'iquest' => 191,
- 'isin' => 8712,
- 'Iuml' => 207,
- 'iuml' => 239,
- 'Kappa' => 922,
- 'kappa' => 954,
- 'Lambda' => 923,
- 'lambda' => 955,
- 'lang' => 9001,
- 'laquo' => 171,
- 'larr' => 8592,
- 'lArr' => 8656,
- 'lceil' => 8968,
- 'ldquo' => 8220,
- 'le' => 8804,
- 'lfloor' => 8970,
- 'lowast' => 8727,
- 'loz' => 9674,
- 'lrm' => 8206,
- 'lsaquo' => 8249,
- 'lsquo' => 8216,
- 'lt' => 60,
- 'macr' => 175,
- 'mdash' => 8212,
- 'micro' => 181,
- 'middot' => 183,
- 'minus' => 8722,
- 'Mu' => 924,
- 'mu' => 956,
- 'nabla' => 8711,
- 'nbsp' => 160,
- 'ndash' => 8211,
- 'ne' => 8800,
- 'ni' => 8715,
- 'not' => 172,
- 'notin' => 8713,
- 'nsub' => 8836,
- 'Ntilde' => 209,
- 'ntilde' => 241,
- 'Nu' => 925,
- 'nu' => 957,
- 'Oacute' => 211,
- 'oacute' => 243,
- 'Ocirc' => 212,
- 'ocirc' => 244,
- 'OElig' => 338,
- 'oelig' => 339,
- 'Ograve' => 210,
- 'ograve' => 242,
- 'oline' => 8254,
- 'Omega' => 937,
- 'omega' => 969,
- 'Omicron' => 927,
- 'omicron' => 959,
- 'oplus' => 8853,
- 'or' => 8744,
- 'ordf' => 170,
- 'ordm' => 186,
- 'Oslash' => 216,
- 'oslash' => 248,
- 'Otilde' => 213,
- 'otilde' => 245,
- 'otimes' => 8855,
- 'Ouml' => 214,
- 'ouml' => 246,
- 'para' => 182,
- 'part' => 8706,
- 'permil' => 8240,
- 'perp' => 8869,
- 'Phi' => 934,
- 'phi' => 966,
- 'Pi' => 928,
- 'pi' => 960,
- 'piv' => 982,
- 'plusmn' => 177,
- 'pound' => 163,
- 'prime' => 8242,
- 'Prime' => 8243,
- 'prod' => 8719,
- 'prop' => 8733,
- 'Psi' => 936,
- 'psi' => 968,
- 'quot' => 34,
- 'radic' => 8730,
- 'rang' => 9002,
- 'raquo' => 187,
- 'rarr' => 8594,
- 'rArr' => 8658,
- 'rceil' => 8969,
- 'rdquo' => 8221,
- 'real' => 8476,
- 'reg' => 174,
- 'rfloor' => 8971,
- 'Rho' => 929,
- 'rho' => 961,
- 'rlm' => 8207,
- 'rsaquo' => 8250,
- 'rsquo' => 8217,
- 'sbquo' => 8218,
- 'Scaron' => 352,
- 'scaron' => 353,
- 'sdot' => 8901,
- 'sect' => 167,
- 'shy' => 173,
- 'Sigma' => 931,
- 'sigma' => 963,
- 'sigmaf' => 962,
- 'sim' => 8764,
- 'spades' => 9824,
- 'sub' => 8834,
- 'sube' => 8838,
- 'sum' => 8721,
- 'sup' => 8835,
- 'sup1' => 185,
- 'sup2' => 178,
- 'sup3' => 179,
- 'supe' => 8839,
- 'szlig' => 223,
- 'Tau' => 932,
- 'tau' => 964,
- 'there4' => 8756,
- 'Theta' => 920,
- 'theta' => 952,
- 'thetasym' => 977,
- 'thinsp' => 8201,
- 'THORN' => 222,
- 'thorn' => 254,
- 'tilde' => 732,
- 'times' => 215,
- 'trade' => 8482,
- 'Uacute' => 218,
- 'uacute' => 250,
- 'uarr' => 8593,
- 'uArr' => 8657,
- 'Ucirc' => 219,
- 'ucirc' => 251,
- 'Ugrave' => 217,
- 'ugrave' => 249,
- 'uml' => 168,
- 'upsih' => 978,
- 'Upsilon' => 933,
- 'upsilon' => 965,
- 'Uuml' => 220,
- 'uuml' => 252,
- 'weierp' => 8472,
- 'Xi' => 926,
- 'xi' => 958,
- 'Yacute' => 221,
- 'yacute' => 253,
- 'yen' => 165,
- 'Yuml' => 376,
- 'yuml' => 255,
- 'Zeta' => 918,
- 'zeta' => 950,
- 'zwj' => 8205,
- 'zwnj' => 8204 );
+ /**
+ * Character entity aliases accepted by MediaWiki
+ */
+ static $htmlEntityAliases = array(
+ 'רלמ' => 'rlm',
+ 'رلم' => 'rlm',
+ );
-/**
- * Character entity aliases accepted by MediaWiki
- */
-global $wgHtmlEntityAliases;
-$wgHtmlEntityAliases = array(
- 'רלמ' => 'rlm',
- 'رلم' => 'rlm',
-);
+ /**
+ * Lazy-initialised attributes regex, see getAttribsRegex()
+ */
+ static $attribsRegex;
+ /**
+ * Regular expression to match HTML/XML attribute pairs within a tag.
+ * Allows some... latitude.
+ * Used in Sanitizer::fixTagAttributes and Sanitizer::decodeTagAttributes
+ */
+ static function getAttribsRegex() {
+ if ( self::$attribsRegex === null ) {
+ $attribFirst = '[:A-Z_a-z0-9]';
+ $attrib = '[:A-Z_a-z-.0-9]';
+ $space = '[\x09\x0a\x0d\x20]';
+ self::$attribsRegex =
+ "/(?:^|$space)({$attribFirst}{$attrib}*)
+ ($space*=$space*
+ (?:
+ # The attribute value: quoted or alone
+ \"([^<\"]*)\"
+ | '([^<']*)'
+ | ([a-zA-Z0-9!#$%&()*,\\-.\\/:;<>?@[\\]^_`{|}~]+)
+ | (\#[0-9a-fA-F]+) # Technically wrong, but lots of
+ # colors are specified like this.
+ # We'll be normalizing it.
+ )
+ )?(?=$space|\$)/sx";
+ }
+ return self::$attribsRegex;
+ }
-/**
- * XHTML sanitizer for MediaWiki
- * @ingroup Parser
- */
-class Sanitizer {
/**
* Cleans up HTML, removes dangerous tags and attributes, and
* removes HTML comments
@@ -636,8 +638,8 @@ class Sanitizer {
$out = array();
foreach( $attribs as $attribute => $value ) {
#allow XML namespace declaration if RDFa is enabled
- if ( $wgAllowRdfaAttributes && preg_match( MW_XMLNS_ATTRIBUTE_PATTRN, $attribute ) ) {
- if ( !preg_match( MW_EVIL_URI_PATTERN, $value ) ) {
+ if ( $wgAllowRdfaAttributes && preg_match( self::XMLNS_ATTRIBUTE_PATTERN, $attribute ) ) {
+ if ( !preg_match( self::EVIL_URI_PATTERN, $value ) ) {
$out[$attribute] = $value;
}
@@ -667,7 +669,7 @@ class Sanitizer {
$attribute === 'itemscope' || $attribute === 'itemtype' ) { #HTML5 microdata
//Paranoia. Allow "simple" values but suppress javascript
- if ( preg_match( MW_EVIL_URI_PATTERN, $value ) ) {
+ if ( preg_match( self::EVIL_URI_PATTERN, $value ) ) {
continue;
}
}
@@ -687,19 +689,6 @@ class Sanitizer {
}
if ( $wgAllowMicrodataAttributes ) {
- # There are some complicated validity constraints we need to
- # enforce here. First of all, we don't want to allow non-standard
- # itemtypes.
- $allowedTypes = array(
- 'http://microformats.org/profile/hcard',
- 'http://microformats.org/profile/hcalendar#vevent',
- 'http://n.whatwg.org/work',
- );
- if ( isset( $out['itemtype'] ) && !in_array( $out['itemtype'],
- $allowedTypes ) ) {
- # Kill everything
- unset( $out['itemscope'] );
- }
# itemtype, itemid, itemref don't make sense without itemscope
if ( !array_key_exists( 'itemscope', $out ) ) {
unset( $out['itemtype'] );
@@ -803,6 +792,10 @@ class Sanitizer {
return $value;
}
+ /**
+ * @param $matches array
+ * @return String
+ */
static function cssDecodeCallback( $matches ) {
if ( $matches[1] !== '' ) {
// Line continuation
@@ -1037,7 +1030,7 @@ class Sanitizer {
$attribs = array();
$pairs = array();
if( !preg_match_all(
- MW_ATTRIBS_REGEX,
+ self::getAttribsRegex(),
$text,
$pairs,
PREG_SET_ORDER ) ) {
@@ -1060,7 +1053,7 @@ class Sanitizer {
/**
* Pick the appropriate attribute value from a match set from the
- * MW_ATTRIBS_REGEX matches.
+ * attribs regex matches.
*
* @param $set Array
* @return String
@@ -1104,6 +1097,10 @@ class Sanitizer {
Sanitizer::normalizeCharReferences( $text ) ) );
}
+ /**
+ * @param $text string
+ * @return mixed
+ */
private static function normalizeWhitespace( $text ) {
return preg_replace(
'/\r\n|[\x20\x0d\x0a\x09]/',
@@ -1140,7 +1137,7 @@ class Sanitizer {
*/
static function normalizeCharReferences( $text ) {
return preg_replace_callback(
- MW_CHAR_REFS_REGEX,
+ self::CHAR_REFS_REGEX,
array( 'Sanitizer', 'normalizeCharReferencesCallback' ),
$text );
}
@@ -1156,8 +1153,6 @@ class Sanitizer {
$ret = Sanitizer::decCharReference( $matches[2] );
} elseif( $matches[3] != '' ) {
$ret = Sanitizer::hexCharReference( $matches[3] );
- } elseif( $matches[4] != '' ) {
- $ret = Sanitizer::hexCharReference( $matches[4] );
}
if( is_null( $ret ) ) {
return htmlspecialchars( $matches[0] );
@@ -1177,19 +1172,22 @@ class Sanitizer {
* @return String
*/
static function normalizeEntity( $name ) {
- global $wgHtmlEntities, $wgHtmlEntityAliases;
- if ( isset( $wgHtmlEntityAliases[$name] ) ) {
- return "&{$wgHtmlEntityAliases[$name]};";
+ if ( isset( self::$htmlEntityAliases[$name] ) ) {
+ return '&' . self::$htmlEntityAliases[$name] . ';';
} elseif ( in_array( $name,
array( 'lt', 'gt', 'amp', 'quot' ) ) ) {
return "&$name;";
- } elseif ( isset( $wgHtmlEntities[$name] ) ) {
- return "&#{$wgHtmlEntities[$name]};";
+ } elseif ( isset( self::$htmlEntities[$name] ) ) {
+ return '&#' . self::$htmlEntities[$name] . ';';
} else {
return "&amp;$name;";
}
}
+ /**
+ * @param $codepoint
+ * @return null|string
+ */
static function decCharReference( $codepoint ) {
$point = intval( $codepoint );
if( Sanitizer::validateCodepoint( $point ) ) {
@@ -1199,6 +1197,10 @@ class Sanitizer {
}
}
+ /**
+ * @param $codepoint
+ * @return null|string
+ */
static function hexCharReference( $codepoint ) {
$point = hexdec( $codepoint );
if( Sanitizer::validateCodepoint( $point ) ) {
@@ -1231,7 +1233,7 @@ class Sanitizer {
*/
public static function decodeCharReferences( $text ) {
return preg_replace_callback(
- MW_CHAR_REFS_REGEX,
+ self::CHAR_REFS_REGEX,
array( 'Sanitizer', 'decodeCharReferencesCallback' ),
$text );
}
@@ -1249,7 +1251,7 @@ class Sanitizer {
public static function decodeCharReferencesAndNormalize( $text ) {
global $wgContLang;
$text = preg_replace_callback(
- MW_CHAR_REFS_REGEX,
+ self::CHAR_REFS_REGEX,
array( 'Sanitizer', 'decodeCharReferencesCallback' ),
$text, /* limit */ -1, $count );
@@ -1271,8 +1273,6 @@ class Sanitizer {
return Sanitizer::decodeChar( intval( $matches[2] ) );
} elseif( $matches[3] != '' ) {
return Sanitizer::decodeChar( hexdec( $matches[3] ) );
- } elseif( $matches[4] != '' ) {
- return Sanitizer::decodeChar( hexdec( $matches[4] ) );
}
# Last case should be an ampersand by itself
return $matches[0];
@@ -1298,16 +1298,15 @@ class Sanitizer {
* return the UTF-8 encoding of that character. Otherwise, returns
* pseudo-entity source (eg &foo;)
*
- * @param $name Strings
+ * @param $name String
* @return String
*/
static function decodeEntity( $name ) {
- global $wgHtmlEntities, $wgHtmlEntityAliases;
- if ( isset( $wgHtmlEntityAliases[$name] ) ) {
- $name = $wgHtmlEntityAliases[$name];
+ if ( isset( self::$htmlEntityAliases[$name] ) ) {
+ $name = self::$htmlEntityAliases[$name];
}
- if( isset( $wgHtmlEntities[$name] ) ) {
- return codepointToUtf8( $wgHtmlEntities[$name] );
+ if( isset( self::$htmlEntities[$name] ) ) {
+ return codepointToUtf8( self::$htmlEntities[$name] );
} else {
return "&$name;";
}
@@ -1532,22 +1531,26 @@ class Sanitizer {
* @return String
*/
static function hackDocType() {
- global $wgHtmlEntities;
$out = "<!DOCTYPE html [\n";
- foreach( $wgHtmlEntities as $entity => $codepoint ) {
+ foreach( self::$htmlEntities as $entity => $codepoint ) {
$out .= "<!ENTITY $entity \"&#$codepoint;\">";
}
$out .= "]>\n";
return $out;
}
+ /**
+ * @param $url string
+ * @return mixed|string
+ */
static function cleanUrl( $url ) {
# Normalize any HTML entities in input. They will be
# re-escaped by makeExternalLink().
$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_callback( '/[\][<>"\\x00-\\x20\\x7F\|]/',
+ array( __CLASS__, 'cleanUrlCallback' ), $url );
# Validate hostname portion
$matches = array();
@@ -1575,7 +1578,7 @@ class Sanitizer {
$host = preg_replace( $strip, '', $host );
- // @todo Fixme: validate hostnames here
+ // @todo FIXME: Validate hostnames here
return $protocol . $host . $rest;
} else {
@@ -1583,4 +1586,61 @@ class Sanitizer {
}
}
+ /**
+ * @param $matches array
+ * @return string
+ */
+ static function cleanUrlCallback( $matches ) {
+ return urlencode( $matches[0] );
+ }
+
+ /**
+ * Does a string look like an e-mail 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:
+ *
+ * A valid e-mail address is a string that matches the ABNF production
+ * 1*( atext / "." ) "@" ldh-str *( "." ldh-str ) where atext is defined
+ * in RFC 5322 section 3.2.3, and ldh-str is defined in RFC 1034 section
+ * 3.5.
+ *
+ * This function is an implementation of the specification as requested in
+ * bug 22449.
+ *
+ * Client-side forms will use the same standard validation rules via JS or
+ * HTML 5 validation; additional restrictions can be enforced server-side
+ * by extensions via the 'isValidEmailAddr' hook.
+ *
+ * Note that this validation doesn't 100% match RFC 2822, but is believed
+ * to be liberal enough for wide use. Some invalid addresses will still
+ * pass validation here.
+ *
+ * @param $addr String E-mail address
+ * @return Bool
+ */
+ public static function validateEmail( $addr ) {
+ $result = null;
+ if( !wfRunHooks( 'isValidEmailAddr', array( $addr, &$result ) ) ) {
+ return $result;
+ }
+
+ // 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 );
+ }
}
diff --git a/includes/SeleniumWebSettings.php b/includes/SeleniumWebSettings.php
index 8afb26da..56afa929 100644
--- a/includes/SeleniumWebSettings.php
+++ b/includes/SeleniumWebSettings.php
@@ -1,5 +1,5 @@
<?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
@@ -8,53 +8,97 @@ if ( !defined( 'MEDIAWIKI' ) ) {
die( 1 );
}
+require_once( "$IP/includes/GlobalFunctions.php" );
+
$fname = 'SeleniumWebSettings.php';
wfProfileIn( $fname );
-$cookiePrefix = $wgSitename . "-";
-$cookieName = $cookiePrefix . "Selenium";
+$cookiePrefix = $wgSitename . '-';
+$cookieName = $cookiePrefix . 'Selenium';
-//if we find a request parameter containing the test name, set a cookie with the test name
+// this is a fallback SQL file
+$testSqlFile = false;
+$testImageZip = false;
+
+// 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] ) ) {
+
+ if (
+ preg_match( '/[^a-zA-Z0-9_-]/', $setupTestSuiteName ) ||
+ !isset( $wgSeleniumTestConfigs[$setupTestSuiteName] )
+ )
+ {
return;
}
- if ( strlen( $setupTestSuiteName) > 0 ) {
+ if ( strlen( $setupTestSuiteName ) > 0 ) {
$expire = time() + 600;
- setcookie( $cookieName,
+ setcookie(
+ $cookieName,
$setupTestSuiteName,
$expire,
$wgCookiePath,
$wgCookieDomain,
$wgCookieSecure,
- true );
+ true
+ );
+ }
+
+ $testIncludes = array(); // array containing all the includes needed for this test
+ $testGlobalConfigs = array(); // an array containg all the global configs needed for this test
+ $testResourceFiles = array(); // an array containing all the resource files needed for this test
+ $callback = $wgSeleniumTestConfigs[$setupTestSuiteName];
+ call_user_func_array( $callback, array( &$testIncludes, &$testGlobalConfigs, &$testResourceFiles));
+
+ if ( isset( $testResourceFiles['images'] ) ) {
+ $testImageZip = $testResourceFiles['images'];
+ }
+
+ if ( isset( $testResourceFiles['db'] ) ) {
+ $testSqlFile = $testResourceFiles['db'];
+ $testResourceName = getTestResourceNameFromTestSuiteName( $setupTestSuiteName );
+
+ switchToTestResources( $testResourceName, false ); // false means do not switch database yet
+ setupTestResources( $testResourceName, $testSqlFile, $testImageZip );
}
}
-//clear the cookie based on a request param
+
+// clear the cookie based on a request param
if ( isset( $_GET['clearTestSuite'] ) ) {
- $expire = time() - 600;
- setcookie( $cookieName,
- '',
- $expire,
- $wgCookiePath,
- $wgCookieDomain,
- $wgCookieSecure,
- true );
+ $testSuiteName = getTestSuiteNameFromCookie( $cookieName );
+
+ $expire = time() - 600;
+ setcookie(
+ $cookieName,
+ '',
+ $expire,
+ $wgCookiePath,
+ $wgCookieDomain,
+ $wgCookieSecure,
+ true
+ );
+
+ $testResourceName = getTestResourceNameFromTestSuiteName( $testSuiteName );
+ teardownTestResources( $testResourceName );
}
-//if a cookie is found, run the appropriate callback to get the config params.
+// if a cookie is found, run the appropriate callback to get the config params.
if ( isset( $_COOKIE[$cookieName] ) ) {
- $testSuiteName = $_COOKIE[$cookieName];
+ $testSuiteName = getTestSuiteNameFromCookie( $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
+
+ $testIncludes = array(); // array containing all the includes needed for this test
+ $testGlobalConfigs = array(); // an array containg all the global configs needed for this test
+ $testResourceFiles = array(); // an array containing all the resource files needed for this test
$callback = $wgSeleniumTestConfigs[$testSuiteName];
- call_user_func_array( $callback, array( &$testIncludes, &$testGlobalConfigs));
-
+ call_user_func_array( $callback, array( &$testIncludes, &$testGlobalConfigs, &$testResourceFiles));
+
+ if ( isset( $testResourceFiles['db'] ) ) {
+ $testResourceName = getTestResourceNameFromTestSuiteName( $testSuiteName );
+ switchToTestResources( $testResourceName );
+ }
foreach ( $testIncludes as $includeFile ) {
$file = $IP . '/' . $includeFile;
require_once( $file );
@@ -70,3 +114,108 @@ if ( isset( $_COOKIE[$cookieName] ) ) {
}
wfProfileOut( $fname );
+
+function getTestSuiteNameFromCookie( $cookieName ) {
+ $testSuiteName = null;
+ if ( isset( $_COOKIE[$cookieName] ) ) {
+ $testSuiteName = $_COOKIE[$cookieName];
+ }
+ return $testSuiteName;
+}
+
+function getTestResourceNameFromTestSuiteName( $testSuiteName ) {
+ $testResourceName = null;
+ if ( isset( $testSuiteName ) ) {
+ $testResourceName = $testSuiteName;
+ }
+ return $testResourceName;
+}
+
+function getTestUploadPathFromResourceName( $testResourceName ) {
+ global $IP;
+ $testUploadPath = "$IP/images/$testResourceName";
+ return $testUploadPath;
+}
+
+function setupTestResources( $testResourceName, $testSqlFile, $testImageZip ) {
+ global $wgDBname;
+
+ // Basic security. Do not allow to drop productive database.
+ if ( $testResourceName == $wgDBname ) {
+ die( 'Cannot override productive database.' );
+ }
+ if ( $testResourceName == '' ) {
+ die( 'Cannot identify a test the resources should be installed for.' );
+ }
+
+ // create tables
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->query( 'DROP DATABASE IF EXISTS ' . $testResourceName );
+ $dbw->query( 'CREATE DATABASE ' . $testResourceName );
+
+ // do not set the new DB name before database is setup
+ $wgDBname = $testResourceName;
+ $dbw->selectDB( $testResourceName );
+ // populate from SQL file
+ if ( $testSqlFile ) {
+ $dbw->sourceFile( $testSqlFile );
+ }
+
+ // create test image dir
+ $testUploadPath = getTestUploadPathFromResourceName( $testResourceName );
+ if ( !file_exists( $testUploadPath ) ) {
+ mkdir( $testUploadPath );
+ }
+
+ if ( $testImageZip ) {
+ $zip = new ZipArchive();
+ $zip->open( $testImageZip );
+ $zip->extractTo( $testUploadPath );
+ $zip->close();
+ }
+}
+
+function teardownTestResources( $testResourceName ) {
+ // remove test database
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->query( 'DROP DATABASE IF EXISTS ' . $testResourceName );
+
+ $testUploadPath = getTestUploadPathFromResourceName( $testResourceName );
+ // remove test image dir
+ if ( file_exists( $testUploadPath ) ) {
+ wfRecursiveRemoveDir( $testUploadPath );
+ }
+}
+
+function switchToTestResources( $testResourceName, $switchDB = true ) {
+ global $wgDBuser, $wgDBpassword, $wgDBname;
+ global $wgDBtestuser, $wgDBtestpassword;
+ global $wgUploadPath;
+
+ if ( $switchDB ) {
+ $wgDBname = $testResourceName;
+ }
+ $wgDBuser = $wgDBtestuser;
+ $wgDBpassword = $wgDBtestpassword;
+
+ $testUploadPath = getTestUploadPathFromResourceName( $testResourceName );
+ $wgUploadPath = $testUploadPath;
+}
+
+function wfRecursiveRemoveDir( $dir ) {
+ // taken from http://de3.php.net/manual/en/function.rmdir.php#98622
+ if ( is_dir( $dir ) ) {
+ $objects = scandir( $dir );
+ foreach ( $objects as $object ) {
+ if ( $object != "." && $object != ".." ) {
+ if ( filetype( $dir . '/' . $object ) == "dir" ) {
+ wfRecursiveRemoveDir( $dir . '/' . $object );
+ } else {
+ unlink( $dir . '/' . $object );
+ }
+ }
+ }
+ reset( $objects );
+ rmdir( $dir );
+ }
+} \ No newline at end of file
diff --git a/includes/Setup.php b/includes/Setup.php
index 5d348885..815d24eb 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -9,7 +9,7 @@
* This file is not a valid entry point, perform no further processing unless
* MEDIAWIKI is defined
*/
-if( !defined( 'MEDIAWIKI' ) ) {
+if ( !defined( 'MEDIAWIKI' ) ) {
exit( 1 );
}
@@ -29,60 +29,72 @@ 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 ( $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 ) {
+if ( $wgArticlePath === false ) {
+ if ( $wgUsePathInfo ) {
$wgArticlePath = "$wgScript/$1";
} else {
$wgArticlePath = "$wgScript?title=$1";
}
}
-if( $wgStylePath === false ) $wgStylePath = "$wgScriptPath/skins";
-if( $wgLocalStylePath === false ) $wgLocalStylePath = "$wgScriptPath/skins";
-if( $wgStyleDirectory === false) $wgStyleDirectory = "$IP/skins";
-if( $wgExtensionAssetsPath === false ) $wgExtensionAssetsPath = "$wgScriptPath/extensions";
+if ( !empty($wgActionPaths) && !isset($wgActionPaths['view']) ) {
+ # 'view' is assumed the default action path everywhere in the code
+ # but is rarely filled in $wgActionPaths
+ $wgActionPaths['view'] = $wgArticlePath;
+}
+
+if ( !empty($wgActionPaths) && !isset($wgActionPaths['view']) ) {
+ # 'view' is assumed the default action path everywhere in the code
+ # but is rarely filled in $wgActionPaths
+ $wgActionPaths['view'] = $wgArticlePath ;
+}
-if( $wgLogo === false ) $wgLogo = "$wgStylePath/common/images/wiki.png";
+if ( $wgStylePath === false ) $wgStylePath = "$wgScriptPath/skins";
+if ( $wgLocalStylePath === false ) $wgLocalStylePath = "$wgScriptPath/skins";
+if ( $wgStyleDirectory === false ) $wgStyleDirectory = "$IP/skins";
+if ( $wgExtensionAssetsPath === false ) $wgExtensionAssetsPath = "$wgScriptPath/extensions";
-if( $wgUploadPath === false ) $wgUploadPath = "$wgScriptPath/images";
-if( $wgUploadDirectory === false ) $wgUploadDirectory = "$IP/images";
+if ( $wgLogo === false ) $wgLogo = "$wgStylePath/common/images/wiki.png";
-if( $wgMathPath === false ) $wgMathPath = "{$wgUploadPath}/math";
-if( $wgMathDirectory === false ) $wgMathDirectory = "{$wgUploadDirectory}/math";
-if( $wgTmpDirectory === false ) $wgTmpDirectory = "{$wgUploadDirectory}/tmp";
+if ( $wgUploadPath === false ) $wgUploadPath = "$wgScriptPath/images";
+if ( $wgUploadDirectory === false ) $wgUploadDirectory = "$IP/images";
-if( $wgReadOnlyFile === false ) $wgReadOnlyFile = "{$wgUploadDirectory}/lock_yBgMBwiR";
-if( $wgFileCacheDirectory === false ) $wgFileCacheDirectory = "{$wgUploadDirectory}/cache";
-if( $wgDeletedDirectory === false ) $wgDeletedDirectory = "{$wgUploadDirectory}/deleted";
+if ( $wgTmpDirectory === false ) $wgTmpDirectory = "{$wgUploadDirectory}/tmp";
-if( isset( $wgFileStore['deleted']['directory'] ) ) {
+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( $wgFooterIcons['copyright'] ) &&
+ isset( $wgFooterIcons['copyright']['copyright'] ) &&
+ $wgFooterIcons['copyright']['copyright'] === array() )
+{
if ( isset( $wgCopyrightIcon ) && $wgCopyrightIcon ) {
- $wgFooterIcons["copyright"]["copyright"] = $wgCopyrightIcon;
+ $wgFooterIcons['copyright']['copyright'] = $wgCopyrightIcon;
} elseif ( $wgRightsIcon || $wgRightsText ) {
- $wgFooterIcons["copyright"]["copyright"] = array(
- "url" => $wgRightsUrl,
- "src" => $wgRightsIcon,
- "alt" => $wgRightsText,
+ $wgFooterIcons['copyright']['copyright'] = array(
+ 'url' => $wgRightsUrl,
+ 'src' => $wgRightsIcon,
+ 'alt' => $wgRightsText,
);
} else {
- unset($wgFooterIcons["copyright"]["copyright"]);
+ unset( $wgFooterIcons['copyright']['copyright'] );
}
}
-if( isset($wgFooterIcons["poweredby"]) &&
- isset($wgFooterIcons["poweredby"]["mediawiki"]) &&
- $wgFooterIcons["poweredby"]["mediawiki"]["src"] === null ) {
- $wgFooterIcons["poweredby"]["mediawiki"]["src"] = "$wgStylePath/common/images/poweredby_mediawiki_88x31.png";
+if ( isset( $wgFooterIcons['poweredby'] ) &&
+ isset( $wgFooterIcons['poweredby']['mediawiki'] ) &&
+ $wgFooterIcons['poweredby']['mediawiki']['src'] === null )
+{
+ $wgFooterIcons['poweredby']['mediawiki']['src'] = "$wgStylePath/common/images/poweredby_mediawiki_88x31.png";
}
/**
@@ -106,7 +118,7 @@ $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
* Initialise $wgLocalFileRepo from backwards-compatible settings
*/
if ( !$wgLocalFileRepo ) {
- if( isset( $wgFileStore['deleted']['hash'] ) ) {
+ if ( isset( $wgFileStore['deleted']['hash'] ) ) {
$deletedHashLevel = $wgFileStore['deleted']['hash'];
} else {
$deletedHashLevel = $wgHashedUploadDirectory ? 3 : 0;
@@ -143,7 +155,7 @@ if ( $wgUseSharedUploads ) {
'dbUser' => $wgDBuser,
'dbPassword' => $wgDBpassword,
'dbName' => $wgSharedUploadDBname,
- 'dbFlags' => ($wgDebugDumpSql ? DBO_DEBUG : 0) | DBO_DEFAULT,
+ 'dbFlags' => ( $wgDebugDumpSql ? DBO_DEBUG : 0 ) | DBO_DEFAULT,
'tablePrefix' => $wgSharedUploadDBprefix,
'hasSharedCache' => $wgCacheSharedUploads,
'descBaseUrl' => $wgRepositoryBaseUrl,
@@ -163,7 +175,7 @@ if ( $wgUseSharedUploads ) {
);
}
}
-if( $wgUseInstantCommons ) {
+if ( $wgUseInstantCommons ) {
$wgForeignFileRepos[] = array(
'class' => 'ForeignAPIRepo',
'name' => 'wikimediacommons',
@@ -175,81 +187,19 @@ if( $wgUseInstantCommons ) {
);
}
-if ( !class_exists( 'AutoLoader' ) ) {
- require_once( "$IP/includes/AutoLoader.php" );
+if ( is_null( $wgEnableAutoRotation ) ) {
+ // Only enable auto-rotation when the bitmap handler can rotate
+ $wgEnableAutoRotation = BitmapHandler::canRotate();
}
-wfProfileIn( $fname.'-exception' );
-require_once( "$IP/includes/Exception.php" );
-wfInstallExceptionHandler();
-wfProfileOut( $fname.'-exception' );
-
-wfProfileIn( $fname.'-includes' );
-require_once( "$IP/includes/GlobalFunctions.php" );
-require_once( "$IP/includes/Hooks.php" );
-require_once( "$IP/includes/Namespace.php" );
-require_once( "$IP/includes/ProxyTools.php" );
-require_once( "$IP/includes/ObjectCache.php" );
-require_once( "$IP/includes/ImageFunctions.php" );
-wfProfileOut( $fname.'-includes' );
-wfProfileIn( $fname.'-misc1' );
-
-# Raise the memory limit if it's too low
-wfMemoryLimit();
-
-/**
- * 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.
- */
-wfSuppressWarnings();
-date_default_timezone_set( date_default_timezone_get() );
-wfRestoreWarnings();
-
-# 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" );
- # 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";
-
- if ( function_exists( 'getallheaders' ) ) {
- $headers = getallheaders();
- foreach ( $headers as $name => $value ) {
- $headerOut .= "$name: $value\n";
- }
- } else {
- $headers = $_SERVER;
- foreach ( $headers as $name => $value ) {
- if ( substr( $name, 0, 5 ) !== 'HTTP_' ) continue;
- $name = substr( $name, 5 );
- $headerOut .= "$name: $value\n";
- }
- }
- wfDebug( "$headerOut\n" );
- }
-}
-
-if( $wgRCFilterByAge ) {
- ## Trim down $wgRCLinkDays so that it only lists links which are valid
- ## as determined by $wgRCMaxAge.
- ## Note that we allow 1 link higher than the max for things like 56 days but a 60 day link.
- sort($wgRCLinkDays);
- for( $i = 0; $i < count($wgRCLinkDays); $i++ ) {
- if( $wgRCLinkDays[$i] >= $wgRCMaxAge / ( 3600 * 24 ) ) {
- $wgRCLinkDays = array_slice( $wgRCLinkDays, 0, $i+1, false );
+if ( $wgRCFilterByAge ) {
+ # # Trim down $wgRCLinkDays so that it only lists links which are valid
+ # # as determined by $wgRCMaxAge.
+ # # Note that we allow 1 link higher than the max for things like 56 days but a 60 day link.
+ sort( $wgRCLinkDays );
+ for ( $i = 0; $i < count( $wgRCLinkDays ); $i++ ) {
+ if ( $wgRCLinkDays[$i] >= $wgRCMaxAge / ( 3600 * 24 ) ) {
+ $wgRCLinkDays = array_slice( $wgRCLinkDays, 0, $i + 1, false );
break;
}
}
@@ -259,84 +209,218 @@ if ( $wgSkipSkin ) {
$wgSkipSkins[] = $wgSkipSkin;
}
+# Set default shared prefix
+if ( $wgSharedPrefix === false ) {
+ $wgSharedPrefix = $wgDBprefix;
+}
+
+if ( !$wgCookiePrefix ) {
+ if ( $wgSharedDB && $wgSharedPrefix && in_array( 'user', $wgSharedTables ) ) {
+ $wgCookiePrefix = $wgSharedDB . '_' . $wgSharedPrefix;
+ } elseif ( $wgSharedDB && in_array( 'user', $wgSharedTables ) ) {
+ $wgCookiePrefix = $wgSharedDB;
+ } elseif ( $wgDBprefix ) {
+ $wgCookiePrefix = $wgDBname . '_' . $wgDBprefix;
+ } else {
+ $wgCookiePrefix = $wgDBname;
+ }
+}
+$wgCookiePrefix = strtr( $wgCookiePrefix, '=,; +."\'\\[', '__________' );
+
$wgUseEnotif = $wgEnotifUserTalk || $wgEnotifWatchlist;
-if($wgMetaNamespace === FALSE) {
+if ( $wgMetaNamespace === false ) {
$wgMetaNamespace = str_replace( ' ', '_', $wgSitename );
}
+/**
+ * Definitions of the NS_ constants are in Defines.php
+ * @private
+ */
+$wgCanonicalNamespaceNames = array(
+ NS_MEDIA => 'Media',
+ NS_SPECIAL => 'Special',
+ NS_TALK => 'Talk',
+ NS_USER => 'User',
+ NS_USER_TALK => 'User_talk',
+ NS_PROJECT => 'Project',
+ NS_PROJECT_TALK => 'Project_talk',
+ NS_FILE => 'File',
+ NS_FILE_TALK => 'File_talk',
+ NS_MEDIAWIKI => 'MediaWiki',
+ NS_MEDIAWIKI_TALK => 'MediaWiki_talk',
+ NS_TEMPLATE => 'Template',
+ NS_TEMPLATE_TALK => 'Template_talk',
+ NS_HELP => 'Help',
+ NS_HELP_TALK => 'Help_talk',
+ NS_CATEGORY => 'Category',
+ NS_CATEGORY_TALK => 'Category_talk',
+);
+
+/// @todo UGLY UGLY
+if( is_array( $wgExtraNamespaces ) ) {
+ $wgCanonicalNamespaceNames = $wgCanonicalNamespaceNames + $wgExtraNamespaces;
+}
+
# These are now the same, always
# To determine the user language, use $wgLang->getCode()
$wgContLanguageCode = $wgLanguageCode;
# Easy to forget to falsify $wgShowIPinHeader for static caches.
# If file cache or squid cache is on, just disable this (DWIMD).
-if( $wgUseFileCache || $wgUseSquid ) $wgShowIPinHeader = false;
+if ( $wgUseFileCache || $wgUseSquid ) {
+ $wgShowIPinHeader = false;
+}
# $wgAllowRealName and $wgAllowUserSkin were removed in 1.16
# in favor of $wgHiddenPrefs, handle b/c here
-if( !$wgAllowRealName ) {
+if ( !$wgAllowRealName ) {
$wgHiddenPrefs[] = 'realname';
}
-if( !$wgAllowUserSkin ) {
- $wgHiddenPrefs[] = 'skin';
+# Doesn't make sense to have if disabled.
+if ( !$wgEnotifMinorEdits ) {
+ $wgHiddenPrefs[] = 'enotifminoredits';
+}
+
+# $wgDisabledActions is deprecated as of 1.18
+foreach( $wgDisabledActions as $action ){
+ $wgActions[$action] = false;
+}
+if( !$wgAllowPageInfo ){
+ $wgActions['info'] = false;
}
if ( !$wgHtml5Version && $wgHtml5 && $wgAllowRdfaAttributes ) {
# see http://www.w3.org/TR/rdfa-in-html/#document-conformance
- if ( $wgMimeType == 'application/xhtml+xml' ) $wgHtml5Version = 'XHTML+RDFa 1.0';
- else $wgHtml5Version = 'HTML+RDFa 1.0';
+ if ( $wgMimeType == 'application/xhtml+xml' ) {
+ $wgHtml5Version = 'XHTML+RDFa 1.0';
+ } else {
+ $wgHtml5Version = 'HTML+RDFa 1.0';
+ }
+}
+
+# Blacklisted file extensions shouldn't appear on the "allowed" list
+$wgFileExtensions = array_diff ( $wgFileExtensions, $wgFileBlacklist );
+
+if ( $wgArticleCountMethod === null ) {
+ $wgArticleCountMethod = $wgUseCommaCount ? 'comma' : 'link';
}
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 );
+if ( $wgAjaxUploadDestCheck ) {
+ $wgAjaxExportList[] = 'SpecialUpload::ajaxGetExistsWarning';
+}
-wfProfileOut( $fname.'-misc1' );
-wfProfileIn( $fname.'-memcached' );
+if ( $wgNewUserLog ) {
+ # Add a new log type
+ $wgLogTypes[] = 'newusers';
+ $wgLogNames['newusers'] = 'newuserlogpage';
+ $wgLogHeaders['newusers'] = 'newuserlogpagetext';
+ $wgLogActions['newusers/newusers'] = 'newuserlogentry'; // For compatibility with older log entries
+ $wgLogActions['newusers/create'] = 'newuserlog-create-entry';
+ $wgLogActions['newusers/create2'] = 'newuserlog-create2-entry';
+ $wgLogActions['newusers/autocreate'] = 'newuserlog-autocreate-entry';
+}
-$wgMemc =& wfGetMainCache();
-$messageMemc =& wfGetMessageCacheStorage();
-$parserMemc =& wfGetParserCacheStorage();
+if ( $wgCookieSecure === 'detect' ) {
+ $wgCookieSecure = ( substr( $wgServer, 0, 6 ) === 'https:' );
+}
-wfDebug( 'CACHES: ' . get_class( $wgMemc ) . '[main] ' .
- get_class( $messageMemc ) . '[message] ' .
- get_class( $parserMemc ) . "[parser]\n" );
+if ( !defined( 'MW_COMPILED' ) ) {
+ if ( !MWInit::classExists( 'AutoLoader' ) ) {
+ require_once( "$IP/includes/AutoLoader.php" );
+ }
-wfProfileOut( $fname.'-memcached' );
+ wfProfileIn( $fname . '-exception' );
+ MWExceptionHandler::installHandler();
+ wfProfileOut( $fname . '-exception' );
+
+ wfProfileIn( $fname . '-includes' );
+ require_once( "$IP/includes/normal/UtfNormalUtil.php" );
+ require_once( "$IP/includes/GlobalFunctions.php" );
+ require_once( "$IP/includes/ProxyTools.php" );
+ require_once( "$IP/includes/ImageFunctions.php" );
+ require_once( "$IP/includes/normal/UtfNormalDefines.php" );
+ wfProfileOut( $fname . '-includes' );
+}
-## Most of the config is out, some might want to run hooks here.
-wfRunHooks( 'SetupAfterCache' );
+# Now that GlobalFunctions is loaded, set the default for $wgCanonicalServer
+if ( $wgCanonicalServer === false ) {
+ $wgCanonicalServer = wfExpandUrl( $wgServer, PROTO_HTTP );
+}
-wfProfileIn( $fname.'-SetupSession' );
+wfProfileIn( $fname . '-misc1' );
-# Set default shared prefix
-if( $wgSharedPrefix === false ) $wgSharedPrefix = $wgDBprefix;
+# Raise the memory limit if it's too low
+wfMemoryLimit();
-if( !$wgCookiePrefix ) {
- if ( $wgSharedDB && $wgSharedPrefix && in_array('user',$wgSharedTables) ) {
- $wgCookiePrefix = $wgSharedDB . '_' . $wgSharedPrefix;
- } elseif ( $wgSharedDB && in_array('user',$wgSharedTables) ) {
- $wgCookiePrefix = $wgSharedDB;
- } elseif ( $wgDBprefix ) {
- $wgCookiePrefix = $wgDBname . '_' . $wgDBprefix;
- } else {
- $wgCookiePrefix = $wgDBname;
+/**
+ * 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.
+ */
+if ( is_null( $wgLocaltimezone) ) {
+ wfSuppressWarnings();
+ $wgLocaltimezone = date_default_timezone_get();
+ wfRestoreWarnings();
+}
+
+date_default_timezone_set( $wgLocaltimezone );
+if( is_null( $wgLocalTZoffset ) ) {
+ $wgLocalTZoffset = date( 'Z' ) / 60;
+}
+
+# Useful debug output
+global $wgCommandLineMode;
+if ( $wgCommandLineMode ) {
+ $wgRequest = new FauxRequest( array() );
+
+ wfDebug( "\n\nStart command line script $self\n" );
+} else {
+ # Can't stub this one, it sets up $_GET and $_REQUEST in its constructor
+ $wgRequest = new WebRequest;
+
+ $debug = "Start request\n\n{$_SERVER['REQUEST_METHOD']} {$wgRequest->getRequestURL()}";
+
+ if ( $wgDebugPrintHttpHeaders ) {
+ $debug .= "\nHTTP HEADERS:\n";
+
+ foreach ( $wgRequest->getAllHeaders() as $name => $value ) {
+ $debug .= "$name: $value\n";
+ }
}
+ wfDebug( "$debug\n" );
}
-$wgCookiePrefix = strtr($wgCookiePrefix, "=,; +.\"'\\[", "__________");
+
+wfProfileOut( $fname . '-misc1' );
+wfProfileIn( $fname . '-memcached' );
+
+$wgMemc = wfGetMainCache();
+$messageMemc = wfGetMessageCacheStorage();
+$parserMemc = wfGetParserCacheStorage();
+
+wfDebug( 'CACHES: ' . get_class( $wgMemc ) . '[main] ' .
+ get_class( $messageMemc ) . '[message] ' .
+ get_class( $parserMemc ) . "[parser]\n" );
+
+wfProfileOut( $fname . '-memcached' );
+
+# # Most of the config is out, some might want to run hooks here.
+wfRunHooks( 'SetupAfterCache' );
+
+wfProfileIn( $fname . '-session' );
# If session.auto_start is there, we can't touch session name
-#
-if( !wfIniGetBool( 'session.auto_start' ) )
+if ( !wfIniGetBool( 'session.auto_start' ) ) {
session_name( $wgSessionName ? $wgSessionName : $wgCookiePrefix . '_session' );
+}
-if( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
- if( $wgRequest->checkSessionCookie() || isset( $_COOKIE[$wgCookiePrefix.'Token'] ) ) {
+if ( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
+ if ( $wgRequest->checkSessionCookie() || isset( $_COOKIE[$wgCookiePrefix . 'Token'] ) ) {
wfIncrStats( 'request_with_session' );
wfSetupSession();
$wgSessionStarted = true;
@@ -346,51 +430,50 @@ if( !defined( 'MW_NO_SESSION' ) && !$wgCommandLineMode ) {
}
}
-wfProfileOut( $fname.'-SetupSession' );
-wfProfileIn( $fname.'-globals' );
+wfProfileOut( $fname . '-session' );
+wfProfileIn( $fname . '-globals' );
-$wgContLang = new StubContLang;
+$wgContLang = Language::factory( $wgLanguageCode );
+$wgContLang->initEncoding();
+$wgContLang->initContLang();
// Now that variant lists may be available...
$wgRequest->interpolateTitle();
-$wgUser = $wgCommandLineMode ? new User : User::newFromSession();
-$wgLang = new StubUserLang;
-$wgOut = new StubObject( 'wgOut', 'OutputPage' );
-$wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
+$wgUser = RequestContext::getMain()->getUser(); # BackCompat
-$wgMessageCache = new StubObject( 'wgMessageCache', 'MessageCache',
- array( $messageMemc, $wgUseDatabaseMessages, $wgMsgCacheExpiry ) );
+/**
+ * @var Language
+ */
+$wgLang = new StubUserLang;
-wfProfileOut( $fname.'-globals' );
-wfProfileIn( $fname.'-User' );
+/**
+ * @var OutputPage
+ */
+$wgOut = RequestContext::getMain()->getOutput(); # BackCompat
-# Skin setup functions
-# Entries can be added to this variable during the inclusion
-# of the extension file. Skins can then perform any necessary initialisation.
-#
-foreach ( $wgSkinExtensionFunctions as $func ) {
- call_user_func( $func );
-}
+/**
+ * @var Parser
+ */
+$wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
-if( !is_object( $wgAuth ) ) {
+if ( !is_object( $wgAuth ) ) {
$wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' );
wfRunHooks( 'AuthPluginSetup', array( &$wgAuth ) );
}
-wfProfileOut( $fname.'-User' );
-
-wfProfileIn( $fname.'-misc2' );
+# Placeholders in case of DB error
+$wgTitle = null;
$wgDeferredUpdateList = array();
-if ( $wgAjaxUploadDestCheck ) $wgAjaxExportList[] = 'SpecialUpload::ajaxGetExistsWarning';
-
-# Placeholders in case of DB error
-$wgTitle = null;
-$wgArticle = null;
+// We need to check for safe_mode, because mail() will throw an E_NOTICE
+// on additional parameters
+if( !is_null($wgAdditionalMailParams) && wfIniGetBool('safe_mode') ) {
+ $wgAdditionalMailParams = null;
+}
-wfProfileOut( $fname.'-misc2' );
-wfProfileIn( $fname.'-extensions' );
+wfProfileOut( $fname . '-globals' );
+wfProfileIn( $fname . '-extensions' );
# Extension setup functions for extensions other than skins
# Entries should be added to this variable during the inclusion
@@ -399,14 +482,14 @@ wfProfileIn( $fname.'-extensions' );
foreach ( $wgExtensionFunctions as $func ) {
# Allow closures in PHP 5.3+
if ( is_object( $func ) && $func instanceof Closure ) {
- $profName = $fname.'-extensions-closure';
- } elseif( is_array( $func ) ) {
+ $profName = $fname . '-extensions-closure';
+ } elseif ( is_array( $func ) ) {
if ( is_object( $func[0] ) )
- $profName = $fname.'-extensions-'.get_class( $func[0] ).'::'.$func[1];
+ $profName = $fname . '-extensions-' . get_class( $func[0] ) . '::' . $func[1];
else
- $profName = $fname.'-extensions-'.implode( '::', $func );
+ $profName = $fname . '-extensions-' . implode( '::', $func );
} else {
- $profName = $fname.'-extensions-'.strval( $func );
+ $profName = $fname . '-extensions-' . strval( $func );
}
wfProfileIn( $profName );
@@ -414,24 +497,8 @@ foreach ( $wgExtensionFunctions as $func ) {
wfProfileOut( $profName );
}
-// For compatibility
-wfRunHooks( 'LogPageValidTypes', array( &$wgLogTypes ) );
-wfRunHooks( 'LogPageLogName', array( &$wgLogNames ) );
-wfRunHooks( 'LogPageLogHeader', array( &$wgLogHeaders ) );
-wfRunHooks( 'LogPageActionText', array( &$wgLogActions ) );
-
-if( !empty($wgNewUserLog) ) {
- # Add a new log type
- $wgLogTypes[] = 'newusers';
- $wgLogNames['newusers'] = 'newuserlogpage';
- $wgLogHeaders['newusers'] = 'newuserlogpagetext';
- $wgLogActions['newusers/newusers'] = 'newuserlogentry'; // For compatibility with older log entries
- $wgLogActions['newusers/create'] = 'newuserlog-create-entry';
- $wgLogActions['newusers/create2'] = 'newuserlog-create2-entry';
- $wgLogActions['newusers/autocreate'] = 'newuserlog-autocreate-entry';
-}
-
wfDebug( "Fully initialised\n" );
$wgFullyInitialised = true;
-wfProfileOut( $fname.'-extensions' );
+
+wfProfileOut( $fname . '-extensions' );
wfProfileOut( $fname );
diff --git a/includes/SiteConfiguration.php b/includes/SiteConfiguration.php
index f4a4576a..ebdb08fe 100644
--- a/includes/SiteConfiguration.php
+++ b/includes/SiteConfiguration.php
@@ -1,17 +1,4 @@
<?php
-
-/**
- * The include paths change after this file is included from commandLine.inc,
- * meaning that require_once() fails to detect that it is including the same
- * file again. We use DIY C-style protection as a workaround.
- */
-
-// Hide this pattern from Doxygen, which spazzes out at it
-/// @cond
-if( !defined( 'SITE_CONFIGURATION' ) ){
-define( 'SITE_CONFIGURATION', 1 );
-/// @endcond
-
/**
* This is a class used to hold configuration settings, particularly for multi-wiki sites.
*/
@@ -149,6 +136,11 @@ class SiteConfiguration {
/**
* Type-safe string replace; won't do replacements on non-strings
* private?
+ *
+ * @param $from
+ * @param $to
+ * @param $in
+ * @return string
*/
function doReplace( $from, $to, $in ) {
if( is_string( $in ) ) {
@@ -204,7 +196,11 @@ class SiteConfiguration {
return (bool)($this->get( $setting, $wiki, $suffix, array(), $wikiTags ) );
}
- /** Retrieves an array of local databases */
+ /**
+ * Retrieves an array of local databases
+ *
+ * @return array
+ */
function &getLocalDatabases() {
return $this->wikis;
}
@@ -242,6 +238,11 @@ class SiteConfiguration {
$this->extractGlobalSetting( $setting, $wiki, $params );
}
+ /**
+ * @param $setting string
+ * @param $wiki string
+ * @param $params array
+ */
public function extractGlobalSetting( $setting, $wiki, $params ) {
$value = $this->getSetting( $setting, $wiki, $params );
if ( !is_null( $value ) ) {
@@ -288,8 +289,9 @@ class SiteConfiguration {
'params' => array(),
);
- if( !is_callable( $this->siteParamsCallback ) )
+ if( !is_callable( $this->siteParamsCallback ) ) {
return $default;
+ }
$ret = call_user_func_array( $this->siteParamsCallback, array( $this, $wiki ) );
# Validate the returned value
@@ -339,6 +341,8 @@ class SiteConfiguration {
/**
* Work out the site and language name from a database name
* @param $db
+ *
+ * @return array
*/
public function siteFromDB( $db ) {
// Allow override
@@ -377,10 +381,14 @@ class SiteConfiguration {
* On encountering duplicate keys, merge the two, but ONLY if they're arrays.
* PHP's array_merge_recursive() merges ANY duplicate values into arrays,
* which is not fun
+ *
+ * @param $array1 array
+ *
+ * @return array
*/
static function arrayMerge( $array1/* ... */ ) {
$out = $array1;
- for( $i=1; $i < func_num_args(); $i++ ) {
+ for( $i = 1; $i < func_num_args(); $i++ ) {
foreach( func_get_arg( $i ) as $key => $value ) {
if ( isset($out[$key]) && is_array($out[$key]) && is_array($value) ) {
$out[$key] = self::arrayMerge( $out[$key], $value );
@@ -395,7 +403,7 @@ class SiteConfiguration {
return $out;
}
-
+
public function loadFullData() {
if ($this->fullLoadCallback && !$this->fullLoadDone) {
call_user_func( $this->fullLoadCallback, $this );
@@ -403,4 +411,3 @@ class SiteConfiguration {
}
}
}
-} // End of multiple inclusion guard
diff --git a/includes/SiteStats.php b/includes/SiteStats.php
index 11cef56f..d61d4fc7 100644
--- a/includes/SiteStats.php
+++ b/includes/SiteStats.php
@@ -13,6 +13,9 @@ class SiteStats {
self::load( true );
}
+ /**
+ * @param $recache bool
+ */
static function load( $recache = false ) {
if ( self::$loaded && !$recache ) {
return;
@@ -32,6 +35,9 @@ class SiteStats {
self::$loaded = true;
}
+ /**
+ * @return Bool|ResultWrapper
+ */
static function loadAndLazyInit() {
wfDebug( __METHOD__ . ": reading site_stats from slave\n" );
$row = self::doLoad( wfGetDB( DB_SLAVE ) );
@@ -49,7 +55,7 @@ class SiteStats {
// clean schema with mwdumper.
wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" );
- SiteStatsInit::doAllAndCommit( false );
+ SiteStatsInit::doAllAndCommit( wfGetDB( DB_SLAVE ) );
$row = self::doLoad( wfGetDB( DB_MASTER ) );
}
@@ -60,54 +66,71 @@ class SiteStats {
return $row;
}
+ /**
+ * @param $db DatabaseBase
+ * @return Bool|ResultWrapper
+ */
static function doLoad( $db ) {
return $db->selectRow( 'site_stats', '*', false, __METHOD__ );
}
+ /**
+ * @return int
+ */
static function views() {
self::load();
return self::$row->ss_total_views;
}
+ /**
+ * @return int
+ */
static function edits() {
self::load();
return self::$row->ss_total_edits;
}
+ /**
+ * @return int
+ */
static function articles() {
self::load();
return self::$row->ss_good_articles;
}
+ /**
+ * @return int
+ */
static function pages() {
self::load();
return self::$row->ss_total_pages;
}
+ /**
+ * @return int
+ */
static function users() {
self::load();
return self::$row->ss_users;
}
+ /**
+ * @return int
+ */
static function activeUsers() {
self::load();
return self::$row->ss_active_users;
}
+ /**
+ * @return int
+ */
static function images() {
self::load();
return self::$row->ss_images;
}
/**
- * @deprecated Use self::numberingroup('sysop') instead
- */
- static function admins() {
- wfDeprecated(__METHOD__);
- return self::numberingroup( 'sysop' );
- }
-
- /**
* Find the number of users in a given user group.
* @param $group String: name of group
* @return Integer
@@ -132,6 +155,9 @@ class SiteStats {
return self::$groupMemberCounts[$group];
}
+ /**
+ * @return int
+ */
static function jobs() {
if ( !isset( self::$jobs ) ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -144,11 +170,16 @@ class SiteStats {
return self::$jobs;
}
+ /**
+ * @param $ns int
+ *
+ * @return int
+ */
static function pagesInNs( $ns ) {
wfProfileIn( __METHOD__ );
if( !isset( self::$pageCount[$ns] ) ) {
$dbr = wfGetDB( DB_SLAVE );
- $pageCount[$ns] = (int)$dbr->selectField(
+ self::$pageCount[$ns] = (int)$dbr->selectField(
'page',
'COUNT(*)',
array( 'page_namespace' => $ns ),
@@ -156,15 +187,21 @@ class SiteStats {
);
}
wfProfileOut( __METHOD__ );
- return $pageCount[$ns];
+ return self::$pageCount[$ns];
}
- /** Is the provided row of site stats sane, or should it be regenerated? */
+ /**
+ * Is the provided row of site stats sane, or should it be regenerated?
+ *
+ * @param $row
+ *
+ * @return bool
+ */
private static function isSane( $row ) {
if(
$row === false
- or $row->ss_total_pages < $row->ss_good_articles
- or $row->ss_total_edits < $row->ss_total_pages
+ || $row->ss_total_pages < $row->ss_good_articles
+ || $row->ss_total_edits < $row->ss_total_pages
) {
return false;
}
@@ -173,7 +210,7 @@ class SiteStats {
'total_pages', 'users', 'admins', 'images' ) as $member ) {
if(
$row->{"ss_$member"} > 2000000000
- or $row->{"ss_$member"} < 0
+ || $row->{"ss_$member"} < 0
) {
return false;
}
@@ -182,7 +219,6 @@ class SiteStats {
}
}
-
/**
*
*/
@@ -198,6 +234,11 @@ class SiteStatsUpdate {
$this->mUsers = $users;
}
+ /**
+ * @param $sql
+ * @param $field
+ * @param $delta
+ */
function appendUpdate( &$sql, $field, $delta ) {
if ( $delta ) {
if ( $sql ) {
@@ -233,6 +274,10 @@ class SiteStatsUpdate {
}
}
+ /**
+ * @param $dbw DatabaseBase
+ * @return bool|mixed
+ */
public static function cacheUpdate( $dbw ) {
global $wgActiveUserDays;
$dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow' ) );
@@ -272,10 +317,16 @@ class SiteStatsInit {
/**
* Constructor
- * @param $useMaster Boolean: whether to use the master DB
+ * @param $database Boolean or DatabaseBase:
+ * - Boolean: whether to use the master DB
+ * - DatabaseBase: database connection to use
*/
- public function __construct( $useMaster = false ) {
- $this->db = wfGetDB( $useMaster ? DB_MASTER : DB_SLAVE );
+ public function __construct( $database = false ) {
+ if ( $database instanceof DatabaseBase ) {
+ $this->db = $database;
+ } else {
+ $this->db = wfGetDB( $database ? DB_MASTER : DB_SLAVE );
+ }
}
/**
@@ -293,17 +344,28 @@ class SiteStatsInit {
* @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__
+ global $wgArticleCountMethod;
+
+ $tables = array( 'page' );
+ $conds = array(
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0,
);
+
+ if ( $wgArticleCountMethod == 'link' ) {
+ $tables[] = 'pagelinks';
+ $conds[] = 'pl_from=page_id';
+ } elseif ( $wgArticleCountMethod == 'comma' ) {
+ // To make a correct check for this, we would need, for each page,
+ // to load the text, maybe uncompress it, maybe decode it and then
+ // check if there's one comma.
+ // But one thing we are sure is that if the page is empty, it can't
+ // contain a comma :)
+ $conds[] = 'page_len > 0';
+ }
+
+ $this->mArticles = $this->db->selectField( $tables, 'COUNT(DISTINCT page_id)',
+ $conds, __METHOD__ );
return $this->mArticles;
}
@@ -315,7 +377,7 @@ class SiteStatsInit {
$this->mPages = $this->db->selectField( 'page', 'COUNT(*)', '', __METHOD__ );
return $this->mPages;
}
-
+
/**
* Count total users
* @return Integer
@@ -324,7 +386,7 @@ class SiteStatsInit {
$this->mUsers = $this->db->selectField( 'user', 'COUNT(*)', '', __METHOD__ );
return $this->mUsers;
}
-
+
/**
* Count views
* @return Integer
@@ -346,13 +408,21 @@ 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 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
+ *
+ * @param $database Boolean or DatabaseBase:
+ * - Boolean: whether to use the master DB
+ * - DatabaseBase: database connection to use
+ * @param $options Array of options, may contain the following values
+ * - update Boolean: whether to update the current stats (true) or write fresh (false) (default: false)
+ * - views Boolean: when true, do not update the number of page views (default: true)
+ * - activeUsers Boolean: whether to update the number of active users (default: false)
*/
- public static function doAllAndCommit( $update, $noViews = false, $activeUsers = false ) {
+ public static function doAllAndCommit( $database, array $options = array() ) {
+ $options += array( 'update' => false, 'views' => true, 'activeUsers' => false );
+
// Grab the object and count everything
- $counter = new SiteStatsInit( false );
+ $counter = new SiteStatsInit( $database );
+
$counter->edits();
$counter->articles();
$counter->pages();
@@ -360,19 +430,19 @@ class SiteStatsInit {
$counter->files();
// Only do views if we don't want to not count them
- if( !$noViews ) {
+ if( $options['views'] ) {
$counter->views();
}
// Update/refresh
- if( $update ) {
+ if( $options['update'] ) {
$counter->update();
} else {
$counter->refresh();
}
// Count active users if need be
- if( $activeUsers ) {
+ if( $options['activeUsers'] ) {
SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) );
}
}
diff --git a/includes/Skin.php b/includes/Skin.php
index 01b3b4fe..a713660c 100644
--- a/includes/Skin.php
+++ b/includes/Skin.php
@@ -15,23 +15,10 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*
* @ingroup Skins
*/
-class Skin extends Linker {
- /**#@+
- * @private
- */
- var $mWatchLinkNum = 0; // Appended to end of watch link id's
- // How many search boxes have we made? Avoid duplicate id's.
- protected $searchboxes = '';
- /**#@-*/
- protected $mRevisionId; // The revision ID we're looking at, null if not applicable.
+abstract class Skin extends ContextSource {
protected $skinname = 'standard';
- // @todo Fixme: should be protected :-\
- var $mTitle = null;
-
- /** Constructor, call parent constructor */
- function __construct() {
- parent::__construct();
- }
+ protected $mRelevantTitle = null;
+ protected $mRelevantUser = null;
/**
* Fetch the set of available skins.
@@ -41,7 +28,7 @@ class Skin extends Linker {
global $wgValidSkinNames;
static $skinsInitialised = false;
- if ( !$skinsInitialised ) {
+ if ( !$skinsInitialised || !count( $wgValidSkinNames ) ) {
# Get a list of available skins
# Build using the regular expression '^(.*).php$'
# Array keys are all lower case, array value keep the case used by filename
@@ -123,7 +110,7 @@ class Skin extends Linker {
if ( isset( $skinNames[$key] ) ) {
return $key;
- } else if ( isset( $skinNames[$wgDefaultSkin] ) ) {
+ } elseif ( isset( $skinNames[$wgDefaultSkin] ) ) {
return $wgDefaultSkin;
} else {
return 'vector';
@@ -142,27 +129,31 @@ class Skin extends Linker {
$skinNames = Skin::getSkinNames();
$skinName = $skinNames[$key];
- $className = 'Skin' . ucfirst( $key );
+ $className = "Skin{$skinName}";
# Grab the skin class and initialise it.
- if ( !class_exists( $className ) ) {
- // Preload base classes to work around APC/PHP5 bug
- $deps = "{$wgStyleDirectory}/{$skinName}.deps.php";
+ if ( !MWInit::classExists( $className ) ) {
- if ( file_exists( $deps ) ) {
- include_once( $deps );
+ if ( !defined( 'MW_COMPILED' ) ) {
+ // Preload base classes to work around APC/PHP5 bug
+ $deps = "{$wgStyleDirectory}/{$skinName}.deps.php";
+ if ( file_exists( $deps ) ) {
+ include_once( $deps );
+ }
+ require_once( "{$wgStyleDirectory}/{$skinName}.php" );
}
- require_once( "{$wgStyleDirectory}/{$skinName}.php" );
# Check if we got if not failback to default skin
- if ( !class_exists( $className ) ) {
+ if ( !MWInit::classExists( $className ) ) {
# DO NOT die if the class isn't found. This breaks maintenance
# scripts and can cause a user account to be unrecoverable
# except by SQL manipulation if a previously valid skin name
# is no longer valid.
wfDebug( "Skin class does not exist: $className\n" );
$className = 'SkinVector';
- require_once( "{$wgStyleDirectory}/Vector.php" );
+ if ( !defined( 'MW_COMPILED' ) ) {
+ require_once( "{$wgStyleDirectory}/Vector.php" );
+ }
}
}
$skin = new $className;
@@ -179,60 +170,11 @@ class Skin extends Linker {
return $this->skinname;
}
- function qbSetting() {
- global $wgOut, $wgUser;
-
- if ( $wgOut->isQuickbarSuppressed() ) {
- return 0;
- }
-
- $q = $wgUser->getOption( 'quickbar', 0 );
-
- return $q;
- }
-
function initPage( OutputPage $out ) {
- global $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI;
-
wfProfileIn( __METHOD__ );
- # Generally the order of the favicon and apple-touch-icon links
- # 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 ) {
- $out->addLink( array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
- }
-
- if ( false !== $wgFavicon ) {
- $out->addLink( array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
- }
-
- # OpenSearch description link
- $out->addLink( array(
- 'rel' => 'search',
- '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 );
-
- $this->mRevisionId = $out->mRevisionId;
-
$this->preloadExistence();
+ $this->setMembers();
wfProfileOut( __METHOD__ );
}
@@ -241,18 +183,18 @@ class Skin extends Linker {
* Preload the existence of three commonly-requested pages in a single query
*/
function preloadExistence() {
- global $wgUser;
+ $user = $this->getUser();
// User/talk link
- $titles = array( $wgUser->getUserPage(), $wgUser->getTalkPage() );
+ $titles = array( $user->getUserPage(), $user->getTalkPage() );
// Other tab link
- if ( $this->mTitle->getNamespace() == NS_SPECIAL ) {
+ if ( $this->getTitle()->getNamespace() == NS_SPECIAL ) {
// nothing
- } elseif ( $this->mTitle->isTalkPage() ) {
- $titles[] = $this->mTitle->getSubjectPage();
+ } elseif ( $this->getTitle()->isTalkPage() ) {
+ $titles[] = $this->getTitle()->getSubjectPage();
} else {
- $titles[] = $this->mTitle->getTalkPage();
+ $titles[] = $this->getTitle()->getTalkPage();
}
$lb = new LinkBatch( $titles );
@@ -261,147 +203,100 @@ class Skin extends Linker {
}
/**
- * 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
+ * Set some local variables
*/
- function addMetadataLinks( OutputPage $out ) {
- global $wgEnableDublinCoreRdf, $wgEnableCreativeCommonsRdf;
- global $wgDisableLangConversion, $wgCanonicalLanguageLinks, $wgContLang;
- global $wgRightsPage, $wgRightsUrl;
-
- if ( $out->isArticleRelated() ) {
- # note: buggy CC software only reads first "meta" link
- if ( $wgEnableCreativeCommonsRdf ) {
- $out->addMetadataLink( array(
- 'title' => 'Creative Commons',
- 'type' => 'application/rdf+xml',
- 'href' => $this->mTitle->getLocalURL( 'action=creativecommons' ) )
- );
- }
-
- if ( $wgEnableDublinCoreRdf ) {
- $out->addMetadataLink( array(
- 'title' => 'Dublin Core',
- 'type' => 'application/rdf+xml',
- 'href' => $this->mTitle->getLocalURL( 'action=dublincore' ) )
- );
- }
- }
-
- 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 ) {
- $copy = Title::newFromText( $wgRightsPage );
-
- if ( $copy ) {
- $copyright = $copy->getLocalURL();
- }
- }
-
- if ( !$copyright && $wgRightsUrl ) {
- $copyright = $wgRightsUrl;
- }
+ protected function setMembers() {
+ $this->userpage = $this->getUser()->getUserPage()->getPrefixedText();
+ }
- if ( $copyright ) {
- $out->addLink( array(
- 'rel' => 'copyright',
- 'href' => $copyright )
- );
- }
+ /**
+ * Get the current revision ID
+ *
+ * @return Integer
+ */
+ public function getRevisionId() {
+ return $this->getOutput()->getRevisionId();
}
/**
- * Set some local variables
+ * Whether the revision displayed is the latest revision of the page
+ *
+ * @return Boolean
*/
- protected function setMembers() {
- global $wgUser;
- $this->mUser = $wgUser;
- $this->userpage = $wgUser->getUserPage()->getPrefixedText();
- $this->usercss = false;
+ public function isRevisionCurrent() {
+ $revID = $this->getRevisionId();
+ return $revID == 0 || $revID == $this->getTitle()->getLatestRevID();
}
/**
- * Set the title
+ * Set the "relevant" title
+ * @see self::getRelevantTitle()
* @param $t Title object to use
*/
- public function setTitle( $t ) {
- $this->mTitle = $t;
+ public function setRelevantTitle( $t ) {
+ $this->mRelevantTitle = $t;
}
- /** Get the title */
- public function getTitle() {
- return $this->mTitle;
+ /**
+ * Return the "relevant" title.
+ * A "relevant" title is not necessarily the actual title of the page.
+ * Special pages like Special:MovePage use set the page they are acting on
+ * as their "relevant" title, this allows the skin system to display things
+ * such as content tabs which belong to to that page instead of displaying
+ * a basic special page tab which has almost no meaning.
+ *
+ * @return Title
+ */
+ public function getRelevantTitle() {
+ if ( isset($this->mRelevantTitle) ) {
+ return $this->mRelevantTitle;
+ }
+ return $this->getTitle();
}
/**
- * Outputs the HTML generated by other functions.
- * @param $out Object: instance of OutputPage
+ * Set the "relevant" user
+ * @see self::getRelevantUser()
+ * @param $u User object to use
*/
- function outputPage( OutputPage $out ) {
- global $wgDebugComments;
- wfProfileIn( __METHOD__ );
-
- $this->setMembers();
- $this->initPage( $out );
-
- // See self::afterContentHook() for documentation
- $afterContent = $this->afterContentHook();
-
- $out->out( $out->headElement( $this ) );
+ public function setRelevantUser( $u ) {
+ $this->mRelevantUser = $u;
+ }
- if ( $wgDebugComments ) {
- $out->out( "<!-- Wiki debugging output:\n" .
- $out->mDebugtext . "-->\n" );
+ /**
+ * Return the "relevant" user.
+ * A "relevant" user is similar to a relevant title. Special pages like
+ * Special:Contributions mark the user which they are relevant to so that
+ * things like the toolbox can display the information they usually are only
+ * able to display on a user's userpage and talkpage.
+ * @return User
+ */
+ public function getRelevantUser() {
+ if ( isset($this->mRelevantUser) ) {
+ return $this->mRelevantUser;
+ }
+ $title = $this->getRelevantTitle();
+ if( $title->getNamespace() == NS_USER || $title->getNamespace() == NS_USER_TALK ) {
+ $rootUser = strtok( $title->getText(), '/' );
+ if ( User::isIP( $rootUser ) ) {
+ $this->mRelevantUser = User::newFromName( $rootUser, false );
+ } else {
+ $user = User::newFromName( $rootUser, false );
+ if ( $user->isLoggedIn() ) {
+ $this->mRelevantUser = $user;
+ }
+ }
+ return $this->mRelevantUser;
}
-
- $out->out( $this->beforeContent() );
-
- $out->out( $out->mBodytext . "\n" );
-
- $out->out( $this->afterContent() );
-
- $out->out( $afterContent );
-
- $out->out( $this->bottomScripts( $out ) );
-
- $out->out( wfReportTime() );
-
- $out->out( "\n</body></html>" );
- wfProfileOut( __METHOD__ );
+ return null;
}
+ /**
+ * Outputs the HTML generated by other functions.
+ * @param $out OutputPage
+ */
+ abstract function outputPage( OutputPage $out );
+
static function makeVariablesScript( $data ) {
if ( $data ) {
return Html::inlineScript(
@@ -409,50 +304,7 @@ class Skin extends Linker {
);
} 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!
- *
- * 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 ) {
- global $wgTitle, $wgUser, $wgRequest, $wgArticle, $wgOut, $wgUseAjax, $wgEnableMWSuggest;
-
- $ns = $wgTitle->getNamespace();
- $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $wgTitle->getNsText();
- $vars = array(
- 'wgCanonicalNamespace' => $nsname,
- 'wgCanonicalSpecialPageName' => $ns == NS_SPECIAL ?
- SpecialPage::resolveAlias( $wgTitle->getDBkey() ) : false, # bug 21115
- 'wgNamespaceNumber' => $wgTitle->getNamespace(),
- 'wgPageName' => $wgTitle->getPrefixedDBKey(),
- 'wgTitle' => $wgTitle->getText(),
- 'wgAction' => $wgRequest->getText( 'action', 'view' ),
- 'wgArticleId' => $wgTitle->getArticleId(),
- 'wgIsArticle' => $wgOut->isArticle(),
- 'wgUserName' => $wgUser->isAnon() ? null : $wgUser->getName(),
- 'wgUserGroups' => $wgUser->getEffectiveGroups(),
- 'wgCurRevisionId' => isset( $wgArticle ) ? $wgArticle->getLatest() : 0,
- 'wgCategories' => $wgOut->getCategories(),
- 'wgBreakFrames' => $wgOut->getFrameOptions() == 'DENY',
- );
- foreach ( $wgTitle->getRestrictionTypes() as $type ) {
- $vars['wgRestriction' . ucfirst( $type )] = $wgTitle->getRestrictions( $type );
}
- 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 );
}
/**
@@ -466,72 +318,54 @@ class Skin extends Linker {
* @return bool
*/
public function userCanPreview( $action ) {
- global $wgRequest, $wgUser;
-
if ( $action != 'submit' ) {
return false;
}
- if ( !$wgRequest->wasPosted() ) {
+ if ( !$this->getRequest()->wasPosted() ) {
return false;
}
- if ( !$this->mTitle->userCanEditCssSubpage() ) {
+ if ( !$this->getTitle()->userCanEditCssSubpage() ) {
return false;
}
- if ( !$this->mTitle->userCanEditJsSubpage() ) {
+ if ( !$this->getTitle()->userCanEditJsSubpage() ) {
return false;
}
- return $wgUser->matchEditToken(
- $wgRequest->getVal( 'wpEditToken' ) );
+ return $this->getUser()->matchEditToken(
+ $this->getRequest()->getVal( 'wpEditToken' ) );
}
/**
* Generated JavaScript action=raw&gen=js
- * This returns MediaWiki:Common.js and MediaWiki:[Skinname].js concate-
- * nated together. For some bizarre reason, it does *not* return any
- * custom user JS from subpages. Huh?
- *
- * There's absolutely no reason to have separate Monobook/Common JSes.
- * Any JS that cares can just check the skin variable generated at the
- * top. For now Monobook.js will be maintained, but it should be consi-
- * dered deprecated.
+ * This used to load MediaWiki:Common.js and the skin-specific style
+ * before the ResourceLoader.
*
+ * @deprecated since 1.18 Use the ResourceLoader instead. This may be removed at some
+ * point.
* @param $skinName String: If set, overrides the skin name
- * @return string
+ * @return String
*/
public function generateUserJs( $skinName = null ) {
-
- // Stub - see ResourceLoaderSiteModule, CologneBlue, Simple and Standard skins override this
-
return '';
}
/**
* Generate user stylesheet for action=raw&gen=css
+ *
+ * @deprecated since 1.18 Use the ResourceLoader instead. This may be removed at some
+ * point.
+ * @return String
*/
public function generateUserStylesheet() {
-
- // 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() {
-
- // Stub - see ResourceLoaderUserModule, CologneBlue, Simple and Standard skins override this
-
return '';
}
/**
* @private
+ * @todo document
+ * @param $out OutputPage
*/
function setupUserCss( OutputPage $out ) {
- global $wgRequest;
global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs;
wfProfileIn( __METHOD__ );
@@ -544,14 +378,17 @@ class Skin extends Linker {
// Per-site custom styles
if ( $wgUseSiteCss ) {
- $out->addModuleStyles( 'site' );
+ $out->addModuleStyles( array( 'site', 'noscript' ) );
+ if( $this->getUser()->isLoggedIn() ){
+ $out->addModuleStyles( 'user.groups' );
+ }
}
// 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' ) );
+ if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview( $this->getRequest()->getVal( 'action' ) ) ) {
+ // @todo FIXME: Properly escape the cdata!
+ $out->addInlineStyle( $this->getRequest()->getText( 'wpTextbox1' ) );
} else {
$out->addModuleStyles( 'user' );
}
@@ -584,21 +421,32 @@ class Skin extends Linker {
/**
* Add skin specific stylesheets
+ * Calling this method with an $out of anything but the same OutputPage
+ * inside ->getOutput() is deprecated. The $out arg is kept
+ * for compatibility purposes with skins.
* @param $out OutputPage
+ * @delete
*/
- function setupSkinUserCss( OutputPage $out ) {
- $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' );
- }
+ abstract function setupSkinUserCss( OutputPage $out );
+ /**
+ * TODO: document
+ * @param $title Title
+ * @return String
+ */
function getPageClasses( $title ) {
+ global $wgRequest;
$numeric = 'ns-' . $title->getNamespace();
if ( $title->getNamespace() == NS_SPECIAL ) {
$type = 'ns-special';
+ // bug 23315: provide a class based on the canonical special page name without subpages
+ list( $canonicalName ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
+ if ( $canonicalName ) {
+ $type .= ' ' . Sanitizer::escapeClass( "mw-special-$canonicalName" );
+ } else {
+ $type .= ' mw-invalidspecialpage';
+ }
} elseif ( $title->isTalkPage() ) {
$type = 'ns-talk';
} else {
@@ -606,14 +454,21 @@ class Skin extends Linker {
}
$name = Sanitizer::escapeClass( 'page-' . $title->getPrefixedText() );
-
- return "$numeric $type $name";
+
+ if ( $wgRequest->getVal('action') ) {
+ $action = 'action-' . $wgRequest->getVal('action');
+ } else {
+ $action = 'action-view';
+ }
+ return "$numeric $type $name $action";
}
/**
* This will be called by OutputPage::headElement when it is creating the
* <body> tag, skins can override it if they have a need to add in any
* body attributes or classes of their own.
+ * @param $out OutputPage
+ * @param $bodyAttrs Array
*/
function addToBodyAttributes( $out, &$bodyAttrs ) {
// does nothing by default
@@ -621,124 +476,43 @@ class Skin extends Linker {
/**
* URL to the logo
+ * @return String
*/
function getLogo() {
global $wgLogo;
return $wgLogo;
}
- /**
- * This will be called immediately after the <body> tag. Split into
- * two functions to make it easier to subclass.
- */
- function beforeContent() {
- return $this->doBeforeContent();
- }
-
- function doBeforeContent() {
- global $wgContLang;
- wfProfileIn( __METHOD__ );
-
- $s = '';
- $qb = $this->qbSetting();
-
- $langlinks = $this->otherLanguages();
- if ( $langlinks ) {
- $rows = 2;
- $borderhack = '';
- } else {
- $rows = 1;
- $langlinks = false;
- $borderhack = 'class="top"';
- }
-
- $s .= "\n<div id='content'>\n<div id='topbar'>\n" .
- "<table border='0' cellspacing='0' width='98%'>\n<tr>\n";
-
- $shove = ( $qb != 0 );
- $left = ( $qb == 1 || $qb == 3 );
-
- if ( $wgContLang->isRTL() ) {
- $left = !$left;
- }
-
- if ( !$shove ) {
- $s .= "<td class='top' align='left' valign='top' rowspan='{$rows}'>\n" .
- $this->logoText() . '</td>';
- } elseif ( $left ) {
- $s .= $this->getQuickbarCompensator( $rows );
- }
-
- $l = $wgContLang->alignStart();
- $s .= "<td {$borderhack} align='$l' valign='top'>\n";
-
- $s .= $this->topLinks();
- $s .= '<p class="subtitle">' . $this->pageTitleLinks() . "</p>\n";
-
- $r = $wgContLang->alignEnd();
- $s .= "</td>\n<td {$borderhack} valign='top' align='$r' nowrap='nowrap'>";
- $s .= $this->nameAndLogin();
- $s .= "\n<br />" . $this->searchForm() . '</td>';
-
- if ( $langlinks ) {
- $s .= "</tr>\n<tr>\n<td class='top' colspan=\"2\">$langlinks</td>\n";
- }
-
- 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 ) {
- $s .= "\n<div id='siteNotice'>$notice</div>\n";
- }
- $s .= $this->pageTitle();
- $s .= $this->pageSubtitle();
- $s .= $this->getCategories();
-
- wfProfileOut( __METHOD__ );
- return $s;
- }
-
function getCategoryLinks() {
- global $wgOut, $wgUseCategoryBrowser;
- global $wgContLang, $wgUser;
+ global $wgUseCategoryBrowser;
+
+ $out = $this->getOutput();
- if ( count( $wgOut->mCategoryLinks ) == 0 ) {
+ if ( count( $out->mCategoryLinks ) == 0 ) {
return '';
}
- # Separator
- $sep = wfMsgExt( 'catseparator', array( 'parsemag', 'escapenoentities' ) );
+ $embed = "<li>";
+ $pop = "</li>";
- // Use Unicode bidi embedding override characters,
- // to make sure links don't smash each other up in ugly ways.
- $dir = $wgContLang->getDir();
- $embed = "<span dir='$dir'>";
- $pop = '</span>';
-
- $allCats = $wgOut->getCategoryLinks();
+ $allCats = $out->getCategoryLinks();
$s = '';
$colon = wfMsgExt( 'colon-separator', 'escapenoentities' );
if ( !empty( $allCats['normal'] ) ) {
- $t = $embed . implode( "{$pop} {$sep} {$embed}" , $allCats['normal'] ) . $pop;
+ $t = $embed . implode( "{$pop}{$embed}" , $allCats['normal'] ) . $pop;
$msg = wfMsgExt( 'pagecategories', array( 'parsemag', 'escapenoentities' ), count( $allCats['normal'] ) );
$s .= '<div id="mw-normal-catlinks">' .
- $this->link( Title::newFromText( wfMsgForContent( 'pagecategorieslink' ) ), $msg )
- . $colon . $t . '</div>';
+ Linker::link( Title::newFromText( wfMsgForContent( 'pagecategorieslink' ) ), $msg )
+ . $colon . '<ul>' . $t . '</ul>' . '</div>';
}
# Hidden categories
if ( isset( $allCats['hidden'] ) ) {
- if ( $wgUser->getBoolOption( 'showhiddencats' ) ) {
+ if ( $this->getUser()->getBoolOption( 'showhiddencats' ) ) {
$class = 'mw-hidden-cats-user-shown';
- } elseif ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
+ } elseif ( $this->getTitle()->getNamespace() == NS_CATEGORY ) {
$class = 'mw-hidden-cats-ns-shown';
} else {
$class = 'mw-hidden-cats-hidden';
@@ -746,7 +520,7 @@ class Skin extends Linker {
$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 .
+ $colon . '<ul>' . $embed . implode( "{$pop}{$embed}" , $allCats['hidden'] ) . $pop . '</ul>' .
'</div>';
}
@@ -756,10 +530,10 @@ class Skin extends Linker {
$s .= '<br /><hr />';
# get a big array of the parents tree
- $parenttree = $this->mTitle->getParentCategoryTree();
+ $parenttree = $this->getTitle()->getParentCategoryTree();
# Skin object passed by reference cause it can not be
# accessed under the method subfunction drawCategoryBrowser
- $tempout = explode( "\n", $this->drawCategoryBrowser( $parenttree, $this ) );
+ $tempout = explode( "\n", $this->drawCategoryBrowser( $parenttree ) );
# Clean out bogus first entry and sort them
unset( $tempout[0] );
asort( $tempout );
@@ -773,10 +547,9 @@ class Skin extends Linker {
/**
* Render the array as a serie of links.
* @param $tree Array: categories tree returned by Title::getParentCategoryTree
- * @param &skin Object: skin passed by reference
* @return String separated by &gt;, terminate with "\n"
*/
- function drawCategoryBrowser( $tree, &$skin ) {
+ function drawCategoryBrowser( $tree ) {
$return = '';
foreach ( $tree as $element => $parent ) {
@@ -785,28 +558,28 @@ class Skin extends Linker {
$return .= "\n";
} else {
# grab the others elements
- $return .= $this->drawCategoryBrowser( $parent, $skin ) . ' &gt; ';
+ $return .= $this->drawCategoryBrowser( $parent ) . ' &gt; ';
}
# add our current element to the list
$eltitle = Title::newFromText( $element );
- $return .= $skin->link( $eltitle, $eltitle->getText() );
+ $return .= Linker::link( $eltitle, $eltitle->getText() );
}
return $return;
}
function getCategories() {
+ $out = $this->getOutput();
+
$catlinks = $this->getCategoryLinks();
$classes = 'catlinks';
- global $wgOut, $wgUser;
-
// Check what we're showing
- $allCats = $wgOut->getCategoryLinks();
- $showHidden = $wgUser->getBoolOption( 'showhiddencats' ) ||
- $this->mTitle->getNamespace() == NS_CATEGORY;
+ $allCats = $out->getCategoryLinks();
+ $showHidden = $this->getUser()->getBoolOption( 'showhiddencats' ) ||
+ $this->getTitle()->getNamespace() == NS_CATEGORY;
if ( empty( $allCats['normal'] ) && !( !empty( $allCats['hidden'] ) && $showHidden ) ) {
$classes .= ' catlinks-allhidden';
@@ -815,10 +588,6 @@ class Skin extends Linker {
return "<div id='catlinks' class='$classes'>{$catlinks}</div>";
}
- function getQuickbarCompensator( $rows = 1 ) {
- return "<td width='152' rowspan='{$rows}'>&#160;</td>";
- }
-
/**
* This runs a hook to allow extensions placing their stuff after content
* and article metadata (e.g. categories).
@@ -831,7 +600,7 @@ class Skin extends Linker {
* The output of this function gets processed in SkinTemplate::outputPage() for
* the SkinTemplate based skins, all other skins should directly echo it.
*
- * Returns an empty string by default, if not changed by any hook function.
+ * @return String, empty by default, if not changed by any hook function.
*/
protected function afterContentHook() {
$data = '';
@@ -860,11 +629,11 @@ class Skin extends Linker {
* @return String HTML containing debug data, if enabled (otherwise empty).
*/
protected function generateDebugHTML() {
- global $wgShowDebug, $wgOut;
+ global $wgShowDebug;
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 = $this->formatDebugHTML( $this->getOutput()->mDebugtext );
+ return "\n<hr />\n<strong>Debug data:</strong><ul id=\"mw-debug-html\">" .
$listInternals . "</ul>\n";
}
@@ -872,15 +641,26 @@ class Skin extends Linker {
}
private function formatDebugHTML( $debugText ) {
+ global $wgDebugTimestamps;
+
$lines = explode( "\n", $debugText );
$curIdent = 0;
$ret = '<li>';
foreach ( $lines as $line ) {
+ $pre = '';
+ if ( $wgDebugTimestamps ) {
+ $matches = array();
+ if ( preg_match( '/^(\d+\.\d+\s{2})/', $line, $matches ) ) {
+ $pre = $matches[1];
+ $line = substr( $line, strlen( $pre ) );
+ }
+ }
$display = ltrim( $line );
$ident = strlen( $line ) - strlen( $display );
$diff = $ident - $curIdent;
+ $display = $pre . $display;
if ( $display == '' ) {
$display = "\xc2\xa0";
}
@@ -900,7 +680,7 @@ class Skin extends Linker {
} else {
$ret .= str_repeat( "<ul><li>\n", $diff );
}
- $ret .= $display . "\n";
+ $ret .= "<tt>$display</tt>\n";
$curIdent = $ident;
}
@@ -912,130 +692,46 @@ class Skin extends Linker {
/**
* This gets called shortly before the </body> tag.
- * @return String HTML to be put before </body>
- */
- function afterContent() {
- $printfooter = "<div class=\"printfooter\">\n" . $this->printFooter() . "</div>\n";
- return $printfooter . $this->generateDebugHTML() . $this->doAfterContent();
- }
-
- /**
- * 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( $out ) {
- $bottomScriptText = "\n" . $out->getHeadScripts( $this );
+ // TODO and the suckage continues. This function is really just a wrapper around
+ // OutputPage::getBottomScripts() which takes a Skin param. This should be cleaned
+ // up at some point
+ $bottomScriptText = $out->getBottomScripts( $this );
wfRunHooks( 'SkinAfterBottomScripts', array( $this, &$bottomScriptText ) );
return $bottomScriptText;
}
- /** @return string Retrievied from HTML text */
+ /**
+ * Text with the permalink to the source page,
+ * usually shown on the footer of a printed page
+ *
+ * @return string HTML text with an URL
+ */
function printSource() {
- $url = htmlspecialchars( $this->mTitle->getFullURL() );
- return wfMsg( 'retrievedfrom', '<a href="' . $url . '">' . $url . '</a>' );
- }
-
- function printFooter() {
- return "<p>" . $this->printSource() .
- "</p>\n\n<p>" . $this->pageStats() . "</p>\n";
- }
-
- /** overloaded by derived classes */
- function doAfterContent() {
- return '</div></div>';
- }
-
- function pageTitleLinks() {
- global $wgOut, $wgUser, $wgRequest, $wgLang;
-
- $oldid = $wgRequest->getVal( 'oldid' );
- $diff = $wgRequest->getVal( 'diff' );
- $action = $wgRequest->getText( 'action' );
-
- $s[] = $this->printableLink();
- $disclaimer = $this->disclaimerLink(); # may be empty
-
- if ( $disclaimer ) {
- $s[] = $disclaimer;
- }
-
- $privacy = $this->privacyLink(); # may be empty too
-
- if ( $privacy ) {
- $s[] = $privacy;
- }
-
- if ( $wgOut->isArticleRelated() ) {
- if ( $this->mTitle->getNamespace() == NS_FILE ) {
- $name = $this->mTitle->getDBkey();
- $image = wfFindFile( $this->mTitle );
-
- 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,
- wfMsg( 'currentrev' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
- );
- }
-
- 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() ) ) {
- $tl = $this->link(
- $wgUser->getTalkPage(),
- wfMsgHtml( 'newmessageslink' ),
- array(),
- array( 'redirect' => 'no' ),
- array( 'known', 'noclasses' )
- );
-
- $dl = $this->link(
- $wgUser->getTalkPage(),
- wfMsgHtml( 'newmessagesdifflink' ),
- array(),
- array( 'diff' => 'cur' ),
- array( 'known', 'noclasses' )
- );
- $s[] = '<strong>' . wfMsg( 'youhavenewmessages', $tl, $dl ) . '</strong>';
- # disable caching
- $wgOut->setSquidMaxage( 0 );
- $wgOut->enableClientCache( false );
- }
- }
-
- $undelete = $this->getUndeleteLink();
-
- if ( !empty( $undelete ) ) {
- $s[] = $undelete;
+ $oldid = $this->getRevisionId();
+ if ( $oldid ) {
+ $url = htmlspecialchars( $this->getTitle()->getCanonicalURL( 'oldid=' . $oldid ) );
+ } else {
+ // oldid not available for non existing pages
+ $url = htmlspecialchars( $this->getTitle()->getCanonicalURL() );
}
-
- return $wgLang->pipeList( $s );
+ return wfMsg( 'retrievedfrom', '<a href="' . $url . '">' . $url . '</a>' );
}
function getUndeleteLink() {
- global $wgUser, $wgLang, $wgRequest;
+ $action = $this->getRequest()->getVal( 'action', 'view' );
- $action = $wgRequest->getVal( 'action', 'view' );
+ if ( $this->getUser()->isAllowed( 'deletedhistory' ) &&
+ ( $this->getTitle()->getArticleId() == 0 || $action == 'history' ) ) {
+ $n = $this->getTitle()->isDeleted();
- if ( $wgUser->isAllowed( 'deletedhistory' ) &&
- ( $this->mTitle->getArticleId() == 0 || $action == 'history' ) ) {
- $n = $this->mTitle->isDeleted();
if ( $n ) {
- if ( $wgUser->isAllowed( 'undelete' ) ) {
+ if ( $this->getUser()->isAllowed( 'undelete' ) ) {
$msg = 'thisisdeleted';
} else {
$msg = 'viewdeleted';
@@ -1043,9 +739,9 @@ class Skin extends Linker {
return wfMsg(
$msg,
- $this->link(
- SpecialPage::getTitleFor( 'Undelete', $this->mTitle->getPrefixedDBkey() ),
- wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $wgLang->formatNum( $n ) ),
+ Linker::link(
+ SpecialPage::getTitleFor( 'Undelete', $this->getTitle()->getPrefixedDBkey() ),
+ wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $this->getLang()->formatNum( $n ) ),
array(),
array(),
array( 'known', 'noclasses' )
@@ -1057,64 +753,16 @@ class Skin extends Linker {
return '';
}
- function printableLink() {
- global $wgOut, $wgFeedClasses, $wgRequest, $wgLang;
-
- $s = array();
-
- if ( !$wgOut->isPrintable() ) {
- $printurl = $wgRequest->escapeAppendQuery( 'printable=yes' );
- $s[] = "<a href=\"$printurl\" rel=\"alternate\">" . wfMsg( 'printableversion' ) . '</a>';
- }
-
- 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>";
- }
- }
- return $wgLang->pipeList( $s );
- }
-
- /**
- * Gets the h1 element with the page title.
- * @return string
- */
- function pageTitle() {
- global $wgOut;
- $s = '<h1 class="pagetitle">' . $wgOut->getPageTitle() . '</h1>';
- return $s;
- }
-
- function pageSubtitle() {
- 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() {
+ $out = $this->getOutput();
$subpages = '';
- if ( !wfRunHooks( 'SkinSubPageSubtitle', array( &$subpages, $this ) ) ) {
+ if ( !wfRunHooks( 'SkinSubPageSubtitle', array( &$subpages, $this, $out ) ) ) {
return $subpages;
}
- global $wgOut;
-
- if ( $wgOut->isArticle() && MWNamespace::hasSubpages( $this->mTitle->getNamespace() ) ) {
- $ptext = $this->mTitle->getPrefixedText();
+ if ( $out->isArticle() && MWNamespace::hasSubpages( $out->getTitle()->getNamespace() ) ) {
+ $ptext = $this->getTitle()->getPrefixedText();
if ( preg_match( '/\//', $ptext ) ) {
$links = explode( '/', $ptext );
array_pop( $links );
@@ -1159,73 +807,13 @@ class Skin extends Linker {
/**
* Returns true if the IP should be shown in the header
+ * @return Bool
*/
function showIPinHeader() {
global $wgShowIPinHeader;
return $wgShowIPinHeader && session_id() != '';
}
- function nameAndLogin() {
- global $wgUser, $wgLang, $wgContLang;
-
- $logoutPage = $wgContLang->specialPage( 'Userlogout' );
-
- $ret = '';
-
- if ( $wgUser->isAnon() ) {
- if ( $this->showIPinHeader() ) {
- $name = wfGetIP();
-
- $talkLink = $this->link( $wgUser->getTalkPage(),
- $wgLang->getNsText( NS_TALK ) );
-
- $ret .= "$name ($talkLink)";
- } else {
- $ret .= wfMsg( 'notloggedin' );
- }
-
- $returnTo = $this->mTitle->getPrefixedDBkey();
- $query = array();
-
- if ( $logoutPage != $returnTo ) {
- $query['returnto'] = $returnTo;
- }
-
- $loginlink = $wgUser->isAllowed( 'createaccount' )
- ? 'nav-login-createaccount'
- : 'login';
- $ret .= "\n<br />" . $this->link(
- SpecialPage::getTitleFor( 'Userlogin' ),
- wfMsg( $loginlink ), array(), $query
- );
- } else {
- $returnTo = $this->mTitle->getPrefixedDBkey();
- $talkLink = $this->link( $wgUser->getTalkPage(),
- $wgLang->getNsText( NS_TALK ) );
-
- $ret .= $this->link( $wgUser->getUserPage(),
- htmlspecialchars( $wgUser->getName() ) );
- $ret .= " ($talkLink)<br />";
- $ret .= $wgLang->pipeList( array(
- $this->link(
- SpecialPage::getTitleFor( 'Userlogout' ), wfMsg( 'logout' ),
- array(), array( 'returnto' => $returnTo )
- ),
- $this->specialLink( 'Preferences' ),
- ) );
- }
-
- $ret = $wgLang->pipeList( array(
- $ret,
- $this->link(
- Title::newFromText( wfMsgForContent( 'helppage' ) ),
- wfMsg( 'help' )
- ),
- ) );
-
- return $ret;
- }
-
function getSearchLink() {
$searchPage = SpecialPage::getTitleFor( 'Search' );
return $searchPage->getLocalURL();
@@ -1235,246 +823,13 @@ class Skin extends Linker {
return htmlspecialchars( $this->getSearchLink() );
}
- function searchForm() {
- global $wgRequest, $wgUseTwoButtonsSearchForm;
-
- $search = $wgRequest->getText( 'search' );
-
- $s = '<form id="searchform' . $this->searchboxes . '" name="search" class="inline" method="post" action="'
- . $this->escapeSearchLink() . "\">\n"
- . '<input type="text" id="searchInput' . $this->searchboxes . '" name="search" size="19" value="'
- . htmlspecialchars( substr( $search, 0, 256 ) ) . "\" />\n"
- . '<input type="submit" name="go" value="' . wfMsg( 'searcharticle' ) . '" />';
-
- 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";
- }
-
- $s .= '</form>';
-
- // Ensure unique id's for search boxes made after the first
- $this->searchboxes = $this->searchboxes == '' ? 2 : $this->searchboxes + 1;
-
- return $s;
- }
-
- function topLinks() {
- global $wgOut;
-
- $s = array(
- $this->mainPageLink(),
- $this->specialLink( 'Recentchanges' )
- );
-
- if ( $wgOut->isArticleRelated() ) {
- $s[] = $this->editThisPage();
- $s[] = $this->historyLink();
- }
-
- # Many people don't like this dropdown box
- # $s[] = $this->specialPagesList();
-
- if ( $this->variantLinks() ) {
- $s[] = $this->variantLinks();
- }
-
- if ( $this->extensionTabLinks() ) {
- $s[] = $this->extensionTabLinks();
- }
-
- // @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" );
- }
-
- /**
- * Compatibility for extensions adding functionality through tabs.
- * Eventually these old skins should be replaced with SkinTemplate-based
- * versions, sigh...
- * @return string
- */
- function extensionTabLinks() {
- $tabs = array();
- $out = '';
- $s = array();
- wfRunHooks( 'SkinTemplateTabs', array( $this, &$tabs ) );
- foreach ( $tabs as $tab ) {
- $s[] = Xml::element( 'a',
- array( 'href' => $tab['href'] ),
- $tab['text'] );
- }
-
- if ( count( $s ) ) {
- global $wgLang;
-
- $out = wfMsgExt( 'pipe-separator' , 'escapenoentities' );
- $out .= $wgLang->pipeList( $s );
- }
-
- return $out;
- }
-
- /**
- * Language/charset variant links for classic-style skins
- * @return string
- */
- 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 ) {
- $varname = $wgContLang->getVariantname( $code );
-
- if ( $varname == 'disable' ) {
- continue;
- }
- $s = $wgLang->pipeList( array(
- $s,
- '<a href="' . $this->mTitle->escapeLocalURL( 'variant=' . $code ) . '">' . htmlspecialchars( $varname ) . '</a>'
- ) );
- }
- }
-
- return $s;
- }
-
- function bottomLinks() {
- global $wgOut, $wgUser, $wgUseTrackbacks;
- $sep = wfMsgExt( 'pipe-separator', 'escapenoentities' ) . "\n";
-
- $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 ) {
- $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 ) {
- $element[] = $this->userContribsLink();
- }
-
- if ( $this->showEmailUser( $id ) ) {
- $element[] = $this->emailUserLink();
- }
- }
-
- $s = implode( $element, $sep );
-
- if ( $this->mTitle->getArticleId() ) {
- $s .= "\n<br />";
-
- // Delete/protect/move links for privileged users
- if ( $wgUser->isAllowed( 'delete' ) ) {
- $s .= $this->deleteThisPage();
- }
-
- if ( $wgUser->isAllowed( 'protect' ) ) {
- $s .= $sep . $this->protectThisPage();
- }
-
- if ( $wgUser->isAllowed( 'move' ) ) {
- $s .= $sep . $this->moveThisPage();
- }
- }
-
- $s .= "<br />\n" . $this->otherLanguages();
- }
-
- return $s;
- }
-
- function pageStats() {
- global $wgOut, $wgLang, $wgArticle, $wgRequest, $wgUser;
- global $wgDisableCounters, $wgMaxCredits, $wgShowCreditsIfMax, $wgPageShowWatchingUsers;
-
- $oldid = $wgRequest->getVal( 'oldid' );
- $diff = $wgRequest->getVal( 'diff' );
-
- if ( !$wgOut->isArticle() ) {
- return '';
- }
-
- 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 ) {
- $s .= ' ' . Credits::getCredits( $wgArticle, $wgMaxCredits, $wgShowCreditsIfMax );
- } else {
- $s .= $this->lastModified();
- }
-
- if ( $wgPageShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' ) ) {
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select(
- 'watchlist',
- array( 'COUNT(*) AS n' ),
- array(
- 'wl_title' => $dbr->strencode( $this->mTitle->getDBkey() ),
- 'wl_namespace' => $this->mTitle->getNamespace()
- ),
- __METHOD__
- );
- $x = $dbr->fetchObject( $res );
-
- $s .= ' ' . wfMsgExt( 'number_of_watching_users_pageview',
- array( 'parseinline' ), $wgLang->formatNum( $x->n )
- );
- }
-
- return $s . ' ' . $this->getCopyright();
- }
-
function getCopyright( $type = 'detect' ) {
- global $wgRightsPage, $wgRightsUrl, $wgRightsText, $wgRequest, $wgArticle;
+ global $wgRightsPage, $wgRightsUrl, $wgRightsText;
if ( $type == 'detect' ) {
- $diff = $wgRequest->getVal( 'diff' );
- $isCur = $wgArticle && $wgArticle->isCurrent();
+ $diff = $this->getRequest()->getVal( 'diff' );
- if ( is_null( $diff ) && !$isCur && wfMsgForContent( 'history_copyright' ) !== '-' ) {
+ if ( is_null( $diff ) && !$this->isRevisionCurrent() && wfMsgForContent( 'history_copyright' ) !== '-' ) {
$type = 'history';
} else {
$type = 'normal';
@@ -1491,9 +846,9 @@ class Skin extends Linker {
if ( $wgRightsPage ) {
$title = Title::newFromText( $wgRightsPage );
- $link = $this->linkKnown( $title, $wgRightsText );
+ $link = Linker::linkKnown( $title, $wgRightsText );
} elseif ( $wgRightsUrl ) {
- $link = $this->makeExternalLink( $wgRightsUrl, $wgRightsText );
+ $link = Linker::makeExternalLink( $wgRightsUrl, $wgRightsText );
} elseif ( $wgRightsText ) {
$link = $wgRightsText;
} else {
@@ -1504,9 +859,7 @@ class Skin extends Linker {
// Allow for site and per-namespace customization of copyright notice.
$forContent = true;
- if ( isset( $wgArticle ) ) {
- wfRunHooks( 'SkinCopyrightFooter', array( $wgArticle->getTitle(), $type, &$msg, &$link, &$forContent ) );
- }
+ wfRunHooks( 'SkinCopyrightFooter', array( $this->getTitle(), $type, &$msg, &$link, &$forContent ) );
if ( $forContent ) {
$out .= wfMsgForContent( $msg, $link );
@@ -1552,22 +905,26 @@ class Skin extends Linker {
$url = htmlspecialchars( "$wgStylePath/common/images/poweredby_mediawiki_88x31.png" );
$text = '<a href="http://www.mediawiki.org/"><img src="' . $url . '" height="31" width="88" alt="Powered by MediaWiki" /></a>';
- wfRunHooks( 'SkinGetPoweredBy', array( &$text, $this ) );
+ wfRunHooks( 'SkinGetPoweredBy', array( &$text, $this ) );
return $text;
}
- function lastModified() {
- global $wgLang, $wgArticle;
-
- if ( $this->mRevisionId && $this->mRevisionId != $wgArticle->getLatest() ) {
- $timestamp = Revision::getTimestampFromId( $wgArticle->getTitle(), $this->mRevisionId );
+ /**
+ * Get the timestamp of the latest revision, formatted in user language
+ *
+ * @param $article Article object. Used if we're working with the current revision
+ * @return String
+ */
+ protected function lastModified( $article ) {
+ if ( !$this->isRevisionCurrent() ) {
+ $timestamp = Revision::getTimestampFromId( $this->getTitle(), $this->getRevisionId() );
} else {
- $timestamp = $wgArticle->getTimestamp();
+ $timestamp = $article->getTimestamp();
}
if ( $timestamp ) {
- $d = $wgLang->date( $timestamp, true );
- $t = $wgLang->time( $timestamp, true );
+ $d = $this->getLang()->date( $timestamp, true );
+ $t = $this->getLang()->time( $timestamp, true );
$s = ' ' . wfMsg( 'lastmodifiedat', $d, $t );
} else {
$s = '';
@@ -1598,43 +955,10 @@ class Skin extends Linker {
}
/**
- * Show a drop-down box of special pages
- */
- function specialPagesList() {
- global $wgContLang, $wgServer, $wgRedirectScript;
-
- $pages = array_merge( SpecialPage::getRegularPages(), SpecialPage::getRestrictedPages() );
-
- foreach ( $pages as $name => $page ) {
- $pages[$name] = $page->getDescription();
- }
-
- $go = wfMsg( 'go' );
- $sp = wfMsg( 'specialpages' );
- $spp = $wgContLang->specialPage( 'Specialpages' );
-
- $s = '<form id="specialpages" method="get" ' .
- 'action="' . htmlspecialchars( "{$wgServer}{$wgRedirectScript}" ) . "\">\n";
- $s .= "<select name=\"wpDropdown\">\n";
- $s .= "<option value=\"{$spp}\">{$sp}</option>\n";
-
-
- foreach ( $pages as $name => $desc ) {
- $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
+ * @param $withImage Bool|String: Whether to use the icon's image or output a text-only footericon
+ * @return String HTML
*/
function makeFooterIcon( $icon, $withImage = 'withImage' ) {
if ( is_string( $icon ) ) {
@@ -1659,7 +983,7 @@ class Skin extends Linker {
* @return string
*/
function mainPageLink() {
- $s = $this->link(
+ $s = Linker::link(
Title::newMainPage(),
wfMsg( 'mainpage' ),
array(),
@@ -1681,7 +1005,7 @@ class Skin extends Linker {
// but we make the link target be the one site-wide page.
$title = Title::newFromText( wfMsgForContent( $page ) );
- return $this->linkKnown(
+ return Linker::linkKnown(
$title,
wfMsgExt( $desc, array( 'parsemag', 'escapenoentities' ) )
);
@@ -1690,6 +1014,7 @@ class Skin extends Linker {
/**
* Gets the link to the wiki's privacy policy page.
+ * @return String HTML
*/
function privacyLink() {
return $this->footerLink( 'privacy', 'privacypage' );
@@ -1697,6 +1022,7 @@ class Skin extends Linker {
/**
* Gets the link to the wiki's about page.
+ * @return String HTML
*/
function aboutLink() {
return $this->footerLink( 'aboutsite', 'aboutpage' );
@@ -1704,37 +1030,12 @@ class Skin extends Linker {
/**
* Gets the link to the wiki's general disclaimers page.
+ * @return String HTML
*/
function disclaimerLink() {
return $this->footerLink( 'disclaimers', 'disclaimerpage' );
}
- function editThisPage() {
- global $wgOut;
-
- if ( !$wgOut->isArticleRelated() ) {
- $s = wfMsg( 'protectedpage' );
- } else {
- if ( $this->mTitle->quickUserCan( 'edit' ) && $this->mTitle->exists() ) {
- $t = wfMsg( 'editthispage' );
- } elseif ( $this->mTitle->quickUserCan( 'create' ) && !$this->mTitle->exists() ) {
- $t = wfMsg( 'create-this-page' );
- } else {
- $t = wfMsg( 'viewsource' );
- }
-
- $s = $this->link(
- $this->mTitle,
- $t,
- array(),
- $this->editUrlOptions(),
- array( 'known', 'noclasses' )
- );
- }
-
- return $s;
- }
-
/**
* Return URL options for the 'edit page' link.
* This may include an 'oldid' specifier, if the current page view is such.
@@ -1743,345 +1044,43 @@ class Skin extends Linker {
* @private
*/
function editUrlOptions() {
- global $wgArticle;
-
$options = array( 'action' => 'edit' );
- if ( $this->mRevisionId && ! $wgArticle->isCurrent() ) {
- $options['oldid'] = intval( $this->mRevisionId );
+ if ( !$this->isRevisionCurrent() ) {
+ $options['oldid'] = intval( $this->getRevisionId() );
}
return $options;
}
- function deleteThisPage() {
- global $wgUser, $wgRequest;
-
- $diff = $wgRequest->getVal( 'diff' );
-
- if ( $this->mTitle->getArticleId() && ( !$diff ) && $wgUser->isAllowed( 'delete' ) ) {
- $t = wfMsg( 'deletethispage' );
-
- $s = $this->link(
- $this->mTitle,
- $t,
- array(),
- array( 'action' => 'delete' ),
- array( 'known', 'noclasses' )
- );
- } else {
- $s = '';
- }
-
- return $s;
- }
-
- function protectThisPage() {
- global $wgUser, $wgRequest;
-
- $diff = $wgRequest->getVal( 'diff' );
-
- if ( $this->mTitle->getArticleId() && ( ! $diff ) && $wgUser->isAllowed( 'protect' ) ) {
- if ( $this->mTitle->isProtected() ) {
- $text = wfMsg( 'unprotectthispage' );
- $query = array( 'action' => 'unprotect' );
- } else {
- $text = wfMsg( 'protectthispage' );
- $query = array( 'action' => 'protect' );
- }
-
- $s = $this->link(
- $this->mTitle,
- $text,
- array(),
- $query,
- array( 'known', 'noclasses' )
- );
- } else {
- $s = '';
- }
-
- return $s;
- }
-
- function watchThisPage() {
- global $wgOut;
- ++$this->mWatchLinkNum;
-
- if ( $wgOut->isArticleRelated() ) {
- if ( $this->mTitle->userIsWatching() ) {
- $text = wfMsg( 'unwatchthispage' );
- $query = array( 'action' => 'unwatch' );
- $id = 'mw-unwatch-link' . $this->mWatchLinkNum;
- } else {
- $text = wfMsg( 'watchthispage' );
- $query = array( 'action' => 'watch' );
- $id = 'mw-watch-link' . $this->mWatchLinkNum;
- }
-
- $s = $this->link(
- $this->mTitle,
- $text,
- array( 'id' => $id ),
- $query,
- array( 'known', 'noclasses' )
- );
- } else {
- $s = wfMsg( 'notanarticle' );
- }
-
- return $s;
- }
-
- function moveThisPage() {
- if ( $this->mTitle->quickUserCan( 'move' ) ) {
- return $this->link(
- SpecialPage::getTitleFor( 'Movepage' ),
- wfMsg( 'movethispage' ),
- array(),
- array( 'target' => $this->mTitle->getPrefixedDBkey() ),
- array( 'known', 'noclasses' )
- );
- } else {
- // no message if page is protected - would be redundant
- return '';
- }
- }
-
- function historyLink() {
- return $this->link(
- $this->mTitle,
- wfMsgHtml( 'history' ),
- array( 'rel' => 'archives' ),
- array( 'action' => 'history' )
- );
- }
-
- function whatLinksHere() {
- return $this->link(
- SpecialPage::getTitleFor( 'Whatlinkshere', $this->mTitle->getPrefixedDBkey() ),
- wfMsgHtml( 'whatlinkshere' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
- );
- }
-
- function userContribsLink() {
- return $this->link(
- SpecialPage::getTitleFor( 'Contributions', $this->mTitle->getDBkey() ),
- wfMsgHtml( 'contributions' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
- );
- }
-
function showEmailUser( $id ) {
- global $wgUser;
$targetUser = User::newFromId( $id );
- return $wgUser->canSendEmail() && # the sending user must have a confirmed email address
+ return $this->getUser()->canSendEmail() && # the sending user must have a confirmed email address
$targetUser->canReceiveEmail(); # the target user must have a confirmed email address and allow emails from users
}
- function emailUserLink() {
- return $this->link(
- SpecialPage::getTitleFor( 'Emailuser', $this->mTitle->getDBkey() ),
- wfMsg( 'emailuser' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
- );
- }
-
- function watchPageLinksLink() {
- global $wgOut;
-
- if ( !$wgOut->isArticleRelated() ) {
- return '(' . wfMsg( 'notanarticle' ) . ')';
- } else {
- return $this->link(
- SpecialPage::getTitleFor( 'Recentchangeslinked', $this->mTitle->getPrefixedDBkey() ),
- wfMsg( 'recentchangeslinked-toolbox' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
- );
- }
- }
-
- function trackbackLink() {
- return '<a href="' . $this->mTitle->trackbackURL() . '">'
- . wfMsg( 'trackbacklink' ) . '</a>';
- }
-
- function otherLanguages() {
- global $wgOut, $wgContLang, $wgHideInterlanguageLinks;
-
- if ( $wgHideInterlanguageLinks ) {
- return '';
- }
-
- $a = $wgOut->getLanguageLinks();
-
- if ( 0 == count( $a ) ) {
- return '';
- }
-
- $s = wfMsg( 'otherlanguages' ) . wfMsg( 'colon-separator' );
- $first = true;
-
- if ( $wgContLang->isRTL() ) {
- $s .= '<span dir="LTR">';
- }
-
- 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}\" title=\"{$title}\"{$style}>{$text}</a>";
- }
-
- if ( $wgContLang->isRTL() ) {
- $s .= '</span>';
- }
-
- return $s;
- }
-
- function talkLink() {
- if ( NS_SPECIAL == $this->mTitle->getNamespace() ) {
- # No discussion links for special pages
- return '';
- }
-
- $linkOptions = array();
-
- if ( $this->mTitle->isTalkPage() ) {
- $link = $this->mTitle->getSubjectPage();
- switch( $link->getNamespace() ) {
- case NS_MAIN:
- $text = wfMsg( 'articlepage' );
- break;
- case NS_USER:
- $text = wfMsg( 'userpage' );
- break;
- case NS_PROJECT:
- $text = wfMsg( 'projectpage' );
- break;
- case NS_FILE:
- $text = wfMsg( 'imagepage' );
- # Make link known if image exists, even if the desc. page doesn't.
- if ( wfFindFile( $link ) )
- $linkOptions[] = 'known';
- break;
- case NS_MEDIAWIKI:
- $text = wfMsg( 'mediawikipage' );
- break;
- case NS_TEMPLATE:
- $text = wfMsg( 'templatepage' );
- break;
- case NS_HELP:
- $text = wfMsg( 'viewhelppage' );
- break;
- case NS_CATEGORY:
- $text = wfMsg( 'categorypage' );
- break;
- default:
- $text = wfMsg( 'articlepage' );
- }
- } else {
- $link = $this->mTitle->getTalkPage();
- $text = wfMsg( 'talkpage' );
- }
-
- $s = $this->link( $link, $text, array(), array(), $linkOptions );
-
- return $s;
- }
-
- function commentLink() {
- global $wgOut;
-
- if ( $this->mTitle->getNamespace() == NS_SPECIAL ) {
- return '';
- }
-
- # __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() ) {
- $title = $this->mTitle;
- } elseif ( $wgOut->showNewSectionLink() ) {
- $title = $this->mTitle;
- } else {
- $title = $this->mTitle->getTalkPage();
- }
-
- return $this->link(
- $title,
- wfMsg( 'postcomment' ),
- array(),
- array(
- 'action' => 'edit',
- 'section' => 'new'
- ),
- array( 'known', 'noclasses' )
- );
- }
-
- 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.
+ * @param $name String: The name or path of a skin resource file
* @return String The fully resolved style path url including styleversion
*/
function getCommonStylePath( $name ) {
global $wgStylePath, $wgStyleVersion;
- return "{$wgStylePath}/common/$name?{$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.
+ * @param $name String: The name or path of a skin resource file
* @return String The fully resolved style path url including styleversion
*/
function getSkinStylePath( $name ) {
global $wgStylePath, $wgStyleVersion;
- return "{$wgStylePath}/{$this->stylename}/$name?{$wgStyleVersion}";
+ return "$wgStylePath/{$this->stylename}/$name?$wgStyleVersion";
}
/* these are used extensively in SkinTemplate, but also some other places */
@@ -2093,7 +1092,7 @@ class Skin extends Linker {
}
static function makeSpecialUrl( $name, $urlaction = '' ) {
- $title = SpecialPage::getTitleFor( $name );
+ $title = SpecialPage::getSafeTitleFor( $name );
return $title->getLocalURL( $urlaction );
}
@@ -2118,6 +1117,8 @@ class Skin extends Linker {
/**
* If url string starts with http, consider as external URL, else
* internal
+ * @param $name String
+ * @return String URL
*/
static function makeInternalOrExternalUrl( $name ) {
if ( preg_match( '/^(?:' . wfUrlProtocols() . ')/', $name ) ) {
@@ -2148,6 +1149,9 @@ class Skin extends Linker {
/**
* Make URL details where the article exists (or at least it's convenient to think so)
+ * @param $name String Article name
+ * @param $urlaction String
+ * @return Array
*/
static function makeKnownUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
@@ -2176,10 +1180,9 @@ class Skin extends Linker {
*/
function buildSidebar() {
global $parserMemc, $wgEnableSidebarCache, $wgSidebarCacheExpiry;
- global $wgLang;
wfProfileIn( __METHOD__ );
- $key = wfMemcKey( 'sidebar', $wgLang->getCode() );
+ $key = wfMemcKey( 'sidebar', $this->getLang()->getCode() );
if ( $wgEnableSidebarCache ) {
$cachedsidebar = $parserMemc->get( $key );
@@ -2210,7 +1213,7 @@ class Skin extends Linker {
* @param $message String
*/
function addToSidebar( &$bar, $message ) {
- $this->addToSidebarPlain( $bar, wfMsgForContent( $message ) );
+ $this->addToSidebarPlain( $bar, wfMsgForContentNoTrans( $message ) );
}
/**
@@ -2218,10 +1221,10 @@ class Skin extends Linker {
* @since 1.17
* @param &$bar array
* @param $text string
+ * @return Array
*/
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 = '';
@@ -2239,25 +1242,40 @@ class Skin extends Linker {
$line = trim( $line, '* ' );
if ( strpos( $line, '|' ) !== false ) { // sanity check
+ $line = MessageCache::singleton()->transform( $line, false, null, $this->getTitle() );
$line = array_map( 'trim', explode( '|', $line, 2 ) );
- $link = wfMsgForContent( $line[0] );
+ $extraAttribs = array();
- if ( $link == '-' ) {
- continue;
+ $msgLink = wfMessage( $line[0] )->inContentLanguage();
+ if ( $msgLink->exists() ) {
+ $link = $msgLink->text();
+ if ( $link == '-' ) {
+ continue;
+ }
+ } else {
+ $link = $line[0];
}
- $text = wfMsgExt( $line[1], 'parsemag' );
-
- if ( wfEmptyMsg( $line[1], $text ) ) {
+ $msgText = wfMessage( $line[1] );
+ if ( $msgText->exists() ) {
+ $text = $msgText->text();
+ } else {
$text = $line[1];
}
- if ( wfEmptyMsg( $line[0], $link ) ) {
- $link = $line[0];
- }
-
if ( preg_match( '/^(?:' . wfUrlProtocols() . ')/', $link ) ) {
$href = $link;
+
+ // Parser::getExternalLinkAttribs won't work here because of the Namespace things
+ global $wgNoFollowLinks, $wgNoFollowDomainExceptions;
+ if ( $wgNoFollowLinks && !wfMatchesDomainList( $href, $wgNoFollowDomainExceptions ) ) {
+ $extraAttribs['rel'] = 'nofollow';
+ }
+
+ global $wgExternalLinkTarget;
+ if ( $wgExternalLinkTarget) {
+ $extraAttribs['target'] = $wgExternalLinkTarget;
+ }
} else {
$title = Title::newFromText( $link );
@@ -2269,31 +1287,18 @@ class Skin extends Linker {
}
}
- $bar[$heading][] = array(
+ $bar[$heading][] = array_merge( array(
'text' => $text,
'href' => $href,
- 'id' => 'n-' . strtr( $line[1], ' ', '-' ),
+ 'id' => 'n-' . Sanitizer::escapeId( strtr( $line[1], ' ', '-' ), 'noninitial' ),
'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();
+ ), $extraAttribs );
} else {
continue;
}
}
}
- if ( count( $wikiBar ) > 0 ) {
- $bar = array_merge( $bar, $wikiBar );
- }
-
return $bar;
}
@@ -2314,16 +1319,16 @@ class Skin extends Linker {
* @return MediaWiki message or if no new talk page messages, nothing
*/
function getNewtalks() {
- global $wgUser, $wgOut;
+ $out = $this->getOutput();
- $newtalks = $wgUser->getNewMessageLinks();
+ $newtalks = $this->getUser()->getNewMessageLinks();
$ntl = '';
if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
- $userTitle = $this->mUser->getUserPage();
+ $userTitle = $this->getUser()->getUserPage();
$userTalkTitle = $userTitle->getTalkPage();
- if ( !$userTalkTitle->equals( $this->mTitle ) ) {
+ if ( !$userTalkTitle->equals( $out->getTitle() ) ) {
$newMessagesLink = $this->link(
$userTalkTitle,
wfMsgHtml( 'newmessageslink' ),
@@ -2346,7 +1351,7 @@ class Skin extends Linker {
$newMessagesDiffLink
);
# Disable Squid cache
- $wgOut->setSquidMaxage( 0 );
+ $out->setSquidMaxage( 0 );
}
} elseif ( count( $newtalks ) ) {
// _>" " for BC <= 1.16
@@ -2361,9 +1366,187 @@ class Skin extends Linker {
}
$parts = implode( $sep, $msgs );
$ntl = wfMsgHtml( 'youhavenewmessagesmulti', $parts );
- $wgOut->setSquidMaxage( 0 );
+ $out->setSquidMaxage( 0 );
}
return $ntl;
}
+
+ /**
+ * Get a cached notice
+ *
+ * @param $name String: message name, or 'default' for $wgSiteNotice
+ * @return String: HTML fragment
+ */
+ private function getCachedNotice( $name ) {
+ global $wgRenderHashAppend, $parserMemc, $wgContLang;
+
+ wfProfileIn( __METHOD__ );
+
+ $needParse = false;
+
+ if( $name === 'default' ) {
+ // special case
+ global $wgSiteNotice;
+ $notice = $wgSiteNotice;
+ if( empty( $notice ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ } else {
+ $msg = wfMessage( $name )->inContentLanguage();
+ if( $msg->isDisabled() ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $notice = $msg->plain();
+ }
+
+ // Use the extra hash appender to let eg SSL variants separately cache.
+ $key = wfMemcKey( $name . $wgRenderHashAppend );
+ $cachedNotice = $parserMemc->get( $key );
+ if( is_array( $cachedNotice ) ) {
+ if( md5( $notice ) == $cachedNotice['hash'] ) {
+ $notice = $cachedNotice['html'];
+ } else {
+ $needParse = true;
+ }
+ } else {
+ $needParse = true;
+ }
+
+ if ( $needParse ) {
+ $parsed = $this->getOutput()->parse( $notice );
+ $parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
+ $notice = $parsed;
+ }
+
+ $notice = Html::rawElement( 'div', array( 'id' => 'localNotice',
+ 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ), $notice );
+ wfProfileOut( __METHOD__ );
+ return $notice;
+ }
+
+ /**
+ * Get a notice based on page's namespace
+ *
+ * @return String: HTML fragment
+ */
+ function getNamespaceNotice() {
+ wfProfileIn( __METHOD__ );
+
+ $key = 'namespacenotice-' . $this->getTitle()->getNsText();
+ $namespaceNotice = $this->getCachedNotice( $key );
+ if ( $namespaceNotice && substr( $namespaceNotice, 0, 7 ) != '<p>&lt;' ) {
+ $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . '</div>';
+ } else {
+ $namespaceNotice = '';
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $namespaceNotice;
+ }
+
+ /**
+ * Get the site notice
+ *
+ * @return String: HTML fragment
+ */
+ function getSiteNotice() {
+ wfProfileIn( __METHOD__ );
+ $siteNotice = '';
+
+ if ( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice, $this ) ) ) {
+ if ( is_object( $this->getUser() ) && $this->getUser()->isLoggedIn() ) {
+ $siteNotice = $this->getCachedNotice( 'sitenotice' );
+ } else {
+ $anonNotice = $this->getCachedNotice( 'anonnotice' );
+ if ( !$anonNotice ) {
+ $siteNotice = $this->getCachedNotice( 'sitenotice' );
+ } else {
+ $siteNotice = $anonNotice;
+ }
+ }
+ if ( !$siteNotice ) {
+ $siteNotice = $this->getCachedNotice( 'default' );
+ }
+ }
+
+ wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice, $this ) );
+ wfProfileOut( __METHOD__ );
+ return $siteNotice;
+ }
+
+ /**
+ * Create a section edit link. This supersedes editSectionLink() and
+ * editSectionLinkForOther().
+ *
+ * @param $nt Title The title being linked to (may not be the same as
+ * $wgTitle, if the section is included from a template)
+ * @param $section string The designation of the section being pointed to,
+ * 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, $lang = false ) {
+ // HTML generated here should probably have userlangattributes
+ // added to it for LTR text on RTL pages
+ $attribs = array();
+ if ( !is_null( $tooltip ) ) {
+ # Bug 25462: undo double-escaping.
+ $tooltip = Sanitizer::decodeCharReferences( $tooltip );
+ $attribs['title'] = wfMsgExt( 'editsectionhint', array( 'language' => $lang, 'parsemag' ), $tooltip );
+ }
+ $link = Linker::link( $nt, wfMsgExt( 'editsection', array( 'language' => $lang ) ),
+ $attribs,
+ array( 'action' => 'edit', 'section' => $section ),
+ array( 'noclasses', 'known' )
+ );
+
+ # Run the old hook. This takes up half of the function . . . hopefully
+ # we can rid of it someday.
+ $attribs = '';
+ if ( $tooltip ) {
+ $attribs = wfMsgExt( 'editsectionhint', array( 'language' => $lang, 'parsemag', 'escape' ), $tooltip );
+ $attribs = " title=\"$attribs\"";
+ }
+ $result = null;
+ 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 = 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 = wfMsgExt( 'editsection-brackets', array( 'escape', 'replaceafter', 'language' => $lang ), $link );
+ $result = "<span class=\"editsection\">$result</span>";
+
+ wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result, $lang ) );
+ return $result;
+ }
+
+ /**
+ * Use PHP's magic __call handler to intercept legacy calls to the linker
+ * for backwards compatibility.
+ *
+ * @param $fname String Name of called method
+ * @param $args Array Arguments to the method
+ */
+ function __call( $fname, $args ) {
+ $realFunction = array( 'Linker', $fname );
+ if ( is_callable( $realFunction ) ) {
+ return call_user_func_array( $realFunction, $args );
+ } else {
+ $className = get_class( $this );
+ throw new MWException( "Call to undefined method $className::$fname" );
+ }
+ }
+
}
diff --git a/includes/SkinLegacy.php b/includes/SkinLegacy.php
new file mode 100644
index 00000000..53ce6741
--- /dev/null
+++ b/includes/SkinLegacy.php
@@ -0,0 +1,942 @@
+<?php
+/**
+ * @defgroup Skins Skins
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( 1 );
+}
+
+class SkinLegacy extends SkinTemplate {
+ var $useHeadElement = true;
+ protected $mWatchLinkNum = 0; // Appended to end of watch link id's
+
+ /**
+ * Add skin specific stylesheets
+ * @param $out OutputPage
+ */
+ function setupSkinUserCss( OutputPage $out ) {
+ $out->addModuleStyles( 'mediawiki.legacy.shared' );
+ $out->addModuleStyles( 'mediawiki.legacy.oldshared' );
+ }
+
+ public function commonPrintStylesheet() {
+ return true;
+ }
+
+ /**
+ * This was for the old skins and for users with 640x480 screen.
+ * Please note old skins are still used and might prove useful for
+ * users having old computers or visually impaired.
+ */
+ var $mSuppressQuickbar = false;
+
+ /**
+ * Suppress the quickbar from the output, only for skin supporting
+ * the quickbar
+ */
+ public function suppressQuickbar() {
+ $this->mSuppressQuickbar = true;
+ }
+
+ /**
+ * Return whether the quickbar should be suppressed from the output
+ *
+ * @return Boolean
+ */
+ public function isQuickbarSuppressed() {
+ return $this->mSuppressQuickbar;
+ }
+
+ function qbSetting() {
+ global $wgUser;
+ if ( $this->isQuickbarSuppressed() ) {
+ return 0;
+ }
+ $q = $wgUser->getOption( 'quickbar', 0 );
+ if( $q == 5 ) {
+ # 5 is the default, which chooses the setting
+ # depending on the directionality of your interface language
+ global $wgLang;
+ return $wgLang->isRTL() ? 2 : 1;
+ }
+ return $q;
+ }
+
+}
+
+class LegacyTemplate extends BaseTemplate {
+
+ // How many search boxes have we made? Avoid duplicate id's.
+ protected $searchboxes = '';
+
+ function execute() {
+ $this->html( 'headelement' );
+ echo $this->beforeContent();
+ $this->html( 'bodytext' );
+ echo "\n";
+ echo $this->afterContent();
+ $this->html( 'dataAfterContent' );
+ $this->printTrail();
+ echo "\n</body></html>";
+ }
+
+ /**
+ * This will be called immediately after the <body> tag. Split into
+ * two functions to make it easier to subclass.
+ */
+ function beforeContent() {
+ return $this->doBeforeContent();
+ }
+
+ function doBeforeContent() {
+ global $wgLang;
+ wfProfileIn( __METHOD__ );
+
+ $s = '';
+
+ $langlinks = $this->otherLanguages();
+ if ( $langlinks ) {
+ $rows = 2;
+ $borderhack = '';
+ } else {
+ $rows = 1;
+ $langlinks = false;
+ $borderhack = 'class="top"';
+ }
+
+ $s .= "\n<div id='content'>\n<div id='topbar'>\n" .
+ "<table border='0' cellspacing='0' width='100%'>\n<tr>\n";
+
+ if ( $this->getSkin()->qbSetting() == 0 ) {
+ $s .= "<td class='top' align='left' valign='top' rowspan='{$rows}'>\n" .
+ $this->getSkin()->logoText( $wgLang->alignStart() ) . '</td>';
+ }
+
+ $l = $wgLang->alignStart();
+ $s .= "<td {$borderhack} align='$l' valign='top'>\n";
+
+ $s .= $this->topLinks();
+ $s .= '<p class="subtitle">' . $this->pageTitleLinks() . "</p>\n";
+
+ $r = $wgLang->alignEnd();
+ $s .= "</td>\n<td {$borderhack} valign='top' align='$r' nowrap='nowrap'>";
+ $s .= $this->nameAndLogin();
+ $s .= "\n<br />" . $this->searchForm() . '</td>';
+
+ if ( $langlinks ) {
+ $s .= "</tr>\n<tr>\n<td class='top' colspan=\"2\">$langlinks</td>\n";
+ }
+
+ $s .= "</tr>\n</table>\n</div>\n";
+ $s .= "\n<div id='article'>\n";
+
+ $notice = $this->getSkin()->getSiteNotice();
+
+ if ( $notice ) {
+ $s .= "\n<div id='siteNotice'>$notice</div>\n";
+ }
+ $s .= $this->pageTitle();
+ $s .= $this->pageSubtitle();
+ $s .= $this->getSkin()->getCategories();
+
+ wfProfileOut( __METHOD__ );
+ return $s;
+ }
+
+ /**
+ * This gets called shortly before the </body> tag.
+ * @return String HTML to be put before </body>
+ */
+ function afterContent() {
+ return $this->doAfterContent();
+ }
+
+ /** overloaded by derived classes */
+ function doAfterContent() {
+ return '</div></div>';
+ }
+
+ function searchForm() {
+ global $wgRequest, $wgUseTwoButtonsSearchForm;
+
+ $search = $wgRequest->getText( 'search' );
+
+ $s = '<form id="searchform' . $this->searchboxes . '" name="search" class="inline" method="post" action="'
+ . $this->getSkin()->escapeSearchLink() . "\">\n"
+ . '<input type="text" id="searchInput' . $this->searchboxes . '" name="search" size="19" value="'
+ . htmlspecialchars( substr( $search, 0, 256 ) ) . "\" />\n"
+ . '<input type="submit" name="go" value="' . wfMsg( 'searcharticle' ) . '" />';
+
+ if ( $wgUseTwoButtonsSearchForm ) {
+ $s .= '&#160;<input type="submit" name="fulltext" value="' . wfMsg( 'searchbutton' ) . "\" />\n";
+ } else {
+ $s .= ' <a href="' . $this->getSkin()->escapeSearchLink() . '" rel="search">' . wfMsg( 'powersearch-legend' ) . "</a>\n";
+ }
+
+ $s .= '</form>';
+
+ // Ensure unique id's for search boxes made after the first
+ $this->searchboxes = $this->searchboxes == '' ? 2 : $this->searchboxes + 1;
+
+ return $s;
+ }
+
+ function pageStats() {
+ global $wgOut, $wgLang, $wgRequest, $wgUser;
+ global $wgDisableCounters, $wgMaxCredits, $wgShowCreditsIfMax, $wgPageShowWatchingUsers;
+
+ if ( !is_null( $wgRequest->getVal( 'oldid' ) ) || !is_null( $wgRequest->getVal( 'diff' ) ) ) {
+ return '';
+ }
+
+ if ( !$wgOut->isArticle() || !$this->getSkin()->getTitle()->exists() ) {
+ return '';
+ }
+
+ $article = new Article( $this->getSkin()->getTitle(), 0 );
+
+ $s = '';
+
+ if ( !$wgDisableCounters ) {
+ $count = $wgLang->formatNum( $article->getCount() );
+
+ if ( $count ) {
+ $s = wfMsgExt( 'viewcount', array( 'parseinline' ), $count );
+ }
+ }
+
+ if ( $wgMaxCredits != 0 ) {
+ $s .= ' ' . Action::factory( 'credits', $article )->getCredits( $wgMaxCredits, $wgShowCreditsIfMax );
+ } else {
+ $s .= $this->data['lastmod'];
+ }
+
+ if ( $wgPageShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' ) ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select(
+ 'watchlist',
+ array( 'COUNT(*) AS n' ),
+ array(
+ 'wl_title' => $dbr->strencode( $this->getSkin()->getTitle()->getDBkey() ),
+ 'wl_namespace' => $this->getSkin()->getTitle()->getNamespace()
+ ),
+ __METHOD__
+ );
+ $x = $dbr->fetchObject( $res );
+
+ $s .= ' ' . wfMsgExt( 'number_of_watching_users_pageview',
+ array( 'parseinline' ), $wgLang->formatNum( $x->n )
+ );
+ }
+
+ return $s . ' ' . $this->getSkin()->getCopyright();
+ }
+
+ function topLinks() {
+ global $wgOut;
+
+ $s = array(
+ $this->getSkin()->mainPageLink(),
+ Linker::specialLink( 'Recentchanges' )
+ );
+
+ if ( $wgOut->isArticleRelated() ) {
+ $s[] = $this->editThisPage();
+ $s[] = $this->historyLink();
+ }
+
+ # Many people don't like this dropdown box
+ # $s[] = $this->specialPagesList();
+
+ if ( $this->variantLinks() ) {
+ $s[] = $this->variantLinks();
+ }
+
+ if ( $this->extensionTabLinks() ) {
+ $s[] = $this->extensionTabLinks();
+ }
+
+ // @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" );
+ }
+
+ /**
+ * Language/charset variant links for classic-style skins
+ * @return string
+ */
+ 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 ) {
+ $varname = $wgContLang->getVariantname( $code );
+
+ if ( $varname == 'disable' ) {
+ continue;
+ }
+ $s = $wgLang->pipeList( array(
+ $s,
+ '<a href="' . $this->getSkin()->getTitle()->escapeLocalURL( 'variant=' . $code ) . '">' . htmlspecialchars( $varname ) . '</a>'
+ ) );
+ }
+ }
+
+ return $s;
+ }
+
+ /**
+ * Compatibility for extensions adding functionality through tabs.
+ * Eventually these old skins should be replaced with SkinTemplate-based
+ * versions, sigh...
+ * @return string
+ * @todo Exterminate! ...that, and replace it with normal SkinTemplate stuff
+ */
+ function extensionTabLinks() {
+ $tabs = array();
+ $out = '';
+ $s = array();
+ wfRunHooks( 'SkinTemplateTabs', array( $this->getSkin(), &$tabs ) );
+ foreach ( $tabs as $tab ) {
+ $s[] = Xml::element( 'a',
+ array( 'href' => $tab['href'] ),
+ $tab['text'] );
+ }
+
+ if ( count( $s ) ) {
+ global $wgLang;
+
+ $out = wfMsgExt( 'pipe-separator' , 'escapenoentities' );
+ $out .= $wgLang->pipeList( $s );
+ }
+
+ return $out;
+ }
+
+ function bottomLinks() {
+ global $wgOut, $wgUser, $wgUseTrackbacks;
+ $sep = wfMsgExt( 'pipe-separator', 'escapenoentities' ) . "\n";
+
+ $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 ) {
+ $element[] = $this->trackbackLink();
+ }
+
+ if (
+ $this->getSkin()->getTitle()->getNamespace() == NS_USER ||
+ $this->getSkin()->getTitle()->getNamespace() == NS_USER_TALK
+ ) {
+ $id = User::idFromName( $this->getSkin()->getTitle()->getText() );
+ $ip = User::isIP( $this->getSkin()->getTitle()->getText() );
+
+ # Both anons and non-anons have contributions list
+ if ( $id || $ip ) {
+ $element[] = $this->userContribsLink();
+ }
+
+ if ( $this->getSkin()->showEmailUser( $id ) ) {
+ $element[] = $this->emailUserLink();
+ }
+ }
+
+ $s = implode( $element, $sep );
+
+ if ( $this->getSkin()->getTitle()->getArticleId() ) {
+ $s .= "\n<br />";
+
+ // Delete/protect/move links for privileged users
+ if ( $wgUser->isAllowed( 'delete' ) ) {
+ $s .= $this->deleteThisPage();
+ }
+
+ if ( $wgUser->isAllowed( 'protect' ) ) {
+ $s .= $sep . $this->protectThisPage();
+ }
+
+ if ( $wgUser->isAllowed( 'move' ) ) {
+ $s .= $sep . $this->moveThisPage();
+ }
+ }
+
+ $s .= "<br />\n" . $this->otherLanguages();
+ }
+
+ return $s;
+ }
+
+ function otherLanguages() {
+ global $wgOut, $wgContLang, $wgHideInterlanguageLinks;
+
+ if ( $wgHideInterlanguageLinks ) {
+ return '';
+ }
+
+ $a = $wgOut->getLanguageLinks();
+
+ if ( 0 == count( $a ) ) {
+ return '';
+ }
+
+ $s = wfMsg( 'otherlanguages' ) . wfMsg( 'colon-separator' );
+ $first = true;
+
+ if ( $wgContLang->isRTL() ) {
+ $s .= '<span dir="LTR">';
+ }
+
+ foreach ( $a as $l ) {
+ if ( !$first ) {
+ $s .= wfMsgExt( 'pipe-separator', 'escapenoentities' );
+ }
+
+ $first = false;
+
+ $nt = Title::newFromText( $l );
+ $text = $wgContLang->getLanguageName( $nt->getInterwiki() );
+
+ $s .= Html::element( 'a',
+ array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => "external" ),
+ $text == '' ? $l : $text );
+ }
+
+ if ( $wgContLang->isRTL() ) {
+ $s .= '</span>';
+ }
+
+ return $s;
+ }
+
+ /**
+ * Show a drop-down box of special pages
+ */
+ function specialPagesList() {
+ global $wgContLang, $wgServer, $wgRedirectScript;
+
+ $pages = SpecialPageFactory::getUsablePages();
+
+ foreach ( $pages as $name => $page ) {
+ $pages[$name] = $page->getDescription();
+ }
+
+ $go = wfMsg( 'go' );
+ $sp = wfMsg( 'specialpages' );
+ $spp = $wgContLang->specialPage( 'Specialpages' );
+
+ $s = '<form id="specialpages" method="get" ' .
+ 'action="' . htmlspecialchars( "{$wgServer}{$wgRedirectScript}" ) . "\">\n";
+ $s .= "<select name=\"wpDropdown\">\n";
+ $s .= "<option value=\"{$spp}\">{$sp}</option>\n";
+
+
+ foreach ( $pages as $name => $desc ) {
+ $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;
+ }
+
+ function pageTitleLinks() {
+ global $wgOut, $wgUser, $wgRequest, $wgLang;
+
+ $oldid = $wgRequest->getVal( 'oldid' );
+ $diff = $wgRequest->getVal( 'diff' );
+ $action = $wgRequest->getText( 'action' );
+
+ $s[] = $this->printableLink();
+ $disclaimer = $this->getSkin()->disclaimerLink(); # may be empty
+
+ if ( $disclaimer ) {
+ $s[] = $disclaimer;
+ }
+
+ $privacy = $this->getSkin()->privacyLink(); # may be empty too
+
+ if ( $privacy ) {
+ $s[] = $privacy;
+ }
+
+ if ( $wgOut->isArticleRelated() ) {
+ if ( $this->getSkin()->getTitle()->getNamespace() == NS_FILE ) {
+ $name = $this->getSkin()->getTitle()->getDBkey();
+ $image = wfFindFile( $this->getSkin()->getTitle() );
+
+ if ( $image ) {
+ $link = htmlspecialchars( $image->getURL() );
+ $style = Linker::getInternalLinkAttributes( $link, $name );
+ $s[] = "<a href=\"{$link}\"{$style}>{$name}</a>";
+ }
+ }
+ }
+
+ if ( 'history' == $action || isset( $diff ) || isset( $oldid ) ) {
+ $s[] .= Linker::link(
+ $this->getSkin()->getTitle(),
+ wfMsg( 'currentrev' ),
+ array(),
+ array(),
+ array( 'known', 'noclasses' )
+ );
+ }
+
+ if ( $wgUser->getNewtalk() ) {
+ # do not show "You have new messages" text when we are viewing our
+ # own talk page
+ if ( !$this->getSkin()->getTitle()->equals( $wgUser->getTalkPage() ) ) {
+ $tl = Linker::link(
+ $wgUser->getTalkPage(),
+ wfMsgHtml( 'newmessageslink' ),
+ array(),
+ array( 'redirect' => 'no' ),
+ array( 'known', 'noclasses' )
+ );
+
+ $dl = Linker::link(
+ $wgUser->getTalkPage(),
+ wfMsgHtml( 'newmessagesdifflink' ),
+ array(),
+ array( 'diff' => 'cur' ),
+ array( 'known', 'noclasses' )
+ );
+ $s[] = '<strong>' . wfMsg( 'youhavenewmessages', $tl, $dl ) . '</strong>';
+ # disable caching
+ $wgOut->setSquidMaxage( 0 );
+ $wgOut->enableClientCache( false );
+ }
+ }
+
+ $undelete = $this->getSkin()->getUndeleteLink();
+
+ if ( !empty( $undelete ) ) {
+ $s[] = $undelete;
+ }
+
+ return $wgLang->pipeList( $s );
+ }
+
+ /**
+ * Gets the h1 element with the page title.
+ * @return string
+ */
+ function pageTitle() {
+ global $wgOut;
+ $s = '<h1 class="pagetitle">' . $wgOut->getPageTitle() . '</h1>';
+ return $s;
+ }
+
+ function pageSubtitle() {
+ global $wgOut;
+
+ $sub = $wgOut->getSubtitle();
+
+ if ( $sub == '' ) {
+ global $wgExtraSubtitle;
+ $sub = wfMsgExt( 'tagline', 'parsemag' ) . $wgExtraSubtitle;
+ }
+
+ $subpages = $this->getSkin()->subPageSubtitle();
+ $sub .= !empty( $subpages ) ? "</p><p class='subpages'>$subpages" : '';
+ $s = "<p class='subtitle'>{$sub}</p>\n";
+
+ return $s;
+ }
+
+ function printableLink() {
+ global $wgOut, $wgRequest, $wgLang;
+
+ $s = array();
+
+ if ( !$wgOut->isPrintable() ) {
+ $printurl = htmlspecialchars( $this->getSkin()->getTitle()->getLocalUrl(
+ $wgRequest->appendQueryValue( 'printable', 'yes', true ) ) );
+ $s[] = "<a href=\"$printurl\" rel=\"alternate\">" . wfMsg( 'printableversion' ) . '</a>';
+ }
+
+ if ( $wgOut->isSyndicated() ) {
+ foreach ( $wgOut->getSyndicationLinks() as $format => $link ) {
+ $feedurl = htmlspecialchars( $link );
+ $s[] = "<a href=\"$feedurl\" rel=\"alternate\" type=\"application/{$format}+xml\""
+ . " class=\"feedlink\">" . wfMsgHtml( "feed-$format" ) . "</a>";
+ }
+ }
+ return $wgLang->pipeList( $s );
+ }
+
+ /**
+ * @deprecated in 1.19
+ */
+ function getQuickbarCompensator( $rows = 1 ) {
+ return "<td width='152' rowspan='{$rows}'>&#160;</td>";
+ }
+
+ function editThisPage() {
+ global $wgOut;
+
+ if ( !$wgOut->isArticleRelated() ) {
+ $s = wfMsg( 'protectedpage' );
+ } else {
+ if ( $this->getSkin()->getTitle()->quickUserCan( 'edit' ) && $this->getSkin()->getTitle()->exists() ) {
+ $t = wfMsg( 'editthispage' );
+ } elseif ( $this->getSkin()->getTitle()->quickUserCan( 'create' ) && !$this->getSkin()->getTitle()->exists() ) {
+ $t = wfMsg( 'create-this-page' );
+ } else {
+ $t = wfMsg( 'viewsource' );
+ }
+
+ $s = Linker::link(
+ $this->getSkin()->getTitle(),
+ $t,
+ array(),
+ $this->getSkin()->editUrlOptions(),
+ array( 'known', 'noclasses' )
+ );
+ }
+
+ return $s;
+ }
+
+ function deleteThisPage() {
+ global $wgUser, $wgRequest;
+
+ $diff = $wgRequest->getVal( 'diff' );
+
+ if ( $this->getSkin()->getTitle()->getArticleId() && ( !$diff ) && $wgUser->isAllowed( 'delete' ) ) {
+ $t = wfMsg( 'deletethispage' );
+
+ $s = Linker::link(
+ $this->getSkin()->getTitle(),
+ $t,
+ array(),
+ array( 'action' => 'delete' ),
+ array( 'known', 'noclasses' )
+ );
+ } else {
+ $s = '';
+ }
+
+ return $s;
+ }
+
+ function protectThisPage() {
+ global $wgUser, $wgRequest;
+
+ $diff = $wgRequest->getVal( 'diff' );
+
+ if ( $this->getSkin()->getTitle()->getArticleId() && ( ! $diff ) && $wgUser->isAllowed( 'protect' ) ) {
+ if ( $this->getSkin()->getTitle()->isProtected() ) {
+ $text = wfMsg( 'unprotectthispage' );
+ $query = array( 'action' => 'unprotect' );
+ } else {
+ $text = wfMsg( 'protectthispage' );
+ $query = array( 'action' => 'protect' );
+ }
+
+ $s = Linker::link(
+ $this->getSkin()->getTitle(),
+ $text,
+ array(),
+ $query,
+ array( 'known', 'noclasses' )
+ );
+ } else {
+ $s = '';
+ }
+
+ return $s;
+ }
+
+ function watchThisPage() {
+ global $wgOut, $wgUser;
+ ++$this->mWatchLinkNum;
+
+ // Cache
+ $title = $this->getSkin()->getTitle();
+
+ if ( $wgOut->isArticleRelated() ) {
+ if ( $title->userIsWatching() ) {
+ $text = wfMsg( 'unwatchthispage' );
+ $query = array(
+ 'action' => 'unwatch',
+ 'token' => UnwatchAction::getUnwatchToken( $title, $wgUser ),
+ );
+ $id = 'mw-unwatch-link' . $this->mWatchLinkNum;
+ } else {
+ $text = wfMsg( 'watchthispage' );
+ $query = array(
+ 'action' => 'watch',
+ 'token' => WatchAction::getWatchToken( $title, $wgUser ),
+ );
+ $id = 'mw-watch-link' . $this->mWatchLinkNum;
+ }
+
+ $s = Linker::link(
+ $title,
+ $text,
+ array( 'id' => $id ),
+ $query,
+ array( 'known', 'noclasses' )
+ );
+ } else {
+ $s = wfMsg( 'notanarticle' );
+ }
+
+ return $s;
+ }
+
+ function moveThisPage() {
+ if ( $this->getSkin()->getTitle()->quickUserCan( 'move' ) ) {
+ return Linker::link(
+ SpecialPage::getTitleFor( 'Movepage' ),
+ wfMsg( 'movethispage' ),
+ array(),
+ array( 'target' => $this->getSkin()->getTitle()->getPrefixedDBkey() ),
+ array( 'known', 'noclasses' )
+ );
+ } else {
+ // no message if page is protected - would be redundant
+ return '';
+ }
+ }
+
+ function historyLink() {
+ return Linker::link(
+ $this->getSkin()->getTitle(),
+ wfMsgHtml( 'history' ),
+ array( 'rel' => 'archives' ),
+ array( 'action' => 'history' )
+ );
+ }
+
+ function whatLinksHere() {
+ return Linker::link(
+ SpecialPage::getTitleFor( 'Whatlinkshere', $this->getSkin()->getTitle()->getPrefixedDBkey() ),
+ wfMsgHtml( 'whatlinkshere' ),
+ array(),
+ array(),
+ array( 'known', 'noclasses' )
+ );
+ }
+
+ function userContribsLink() {
+ return Linker::link(
+ SpecialPage::getTitleFor( 'Contributions', $this->getSkin()->getTitle()->getDBkey() ),
+ wfMsgHtml( 'contributions' ),
+ array(),
+ array(),
+ array( 'known', 'noclasses' )
+ );
+ }
+
+ function emailUserLink() {
+ return Linker::link(
+ SpecialPage::getTitleFor( 'Emailuser', $this->getSkin()->getTitle()->getDBkey() ),
+ wfMsg( 'emailuser' ),
+ array(),
+ array(),
+ array( 'known', 'noclasses' )
+ );
+ }
+
+ function watchPageLinksLink() {
+ global $wgOut;
+
+ if ( !$wgOut->isArticleRelated() ) {
+ return '(' . wfMsg( 'notanarticle' ) . ')';
+ } else {
+ return Linker::link(
+ SpecialPage::getTitleFor( 'Recentchangeslinked', $this->getSkin()->getTitle()->getPrefixedDBkey() ),
+ wfMsg( 'recentchangeslinked-toolbox' ),
+ array(),
+ array(),
+ array( 'known', 'noclasses' )
+ );
+ }
+ }
+
+ function trackbackLink() {
+ return '<a href="' . $this->getSkin()->getTitle()->trackbackURL() . '">'
+ . wfMsg( 'trackbacklink' ) . '</a>';
+ }
+
+ function talkLink() {
+ if ( NS_SPECIAL == $this->getSkin()->getTitle()->getNamespace() ) {
+ # No discussion links for special pages
+ return '';
+ }
+
+ $linkOptions = array();
+
+ if ( $this->getSkin()->getTitle()->isTalkPage() ) {
+ $link = $this->getSkin()->getTitle()->getSubjectPage();
+ switch( $link->getNamespace() ) {
+ case NS_MAIN:
+ $text = wfMsg( 'articlepage' );
+ break;
+ case NS_USER:
+ $text = wfMsg( 'userpage' );
+ break;
+ case NS_PROJECT:
+ $text = wfMsg( 'projectpage' );
+ break;
+ case NS_FILE:
+ $text = wfMsg( 'imagepage' );
+ # Make link known if image exists, even if the desc. page doesn't.
+ if ( wfFindFile( $link ) )
+ $linkOptions[] = 'known';
+ break;
+ case NS_MEDIAWIKI:
+ $text = wfMsg( 'mediawikipage' );
+ break;
+ case NS_TEMPLATE:
+ $text = wfMsg( 'templatepage' );
+ break;
+ case NS_HELP:
+ $text = wfMsg( 'viewhelppage' );
+ break;
+ case NS_CATEGORY:
+ $text = wfMsg( 'categorypage' );
+ break;
+ default:
+ $text = wfMsg( 'articlepage' );
+ }
+ } else {
+ $link = $this->getSkin()->getTitle()->getTalkPage();
+ $text = wfMsg( 'talkpage' );
+ }
+
+ $s = Linker::link( $link, $text, array(), array(), $linkOptions );
+
+ return $s;
+ }
+
+ function commentLink() {
+ global $wgOut;
+
+ if ( $this->getSkin()->getTitle()->getNamespace() == NS_SPECIAL ) {
+ return '';
+ }
+
+ # __NEWSECTIONLINK___ changes behaviour here
+ # If it is present, the link points to this page, otherwise
+ # it points to the talk page
+ if ( $this->getSkin()->getTitle()->isTalkPage() ) {
+ $title = $this->getSkin()->getTitle();
+ } elseif ( $wgOut->showNewSectionLink() ) {
+ $title = $this->getSkin()->getTitle();
+ } else {
+ $title = $this->getSkin()->getTitle()->getTalkPage();
+ }
+
+ return Linker::link(
+ $title,
+ wfMsg( 'postcomment' ),
+ array(),
+ array(
+ 'action' => 'edit',
+ 'section' => 'new'
+ ),
+ array( 'known', 'noclasses' )
+ );
+ }
+
+ function getUploadLink() {
+ global $wgUploadNavigationUrl;
+
+ if ( $wgUploadNavigationUrl ) {
+ # Using an empty class attribute to avoid automatic setting of "external" class
+ return Linker::makeExternalLink( $wgUploadNavigationUrl, wfMsgHtml( 'upload' ), false, null, array( 'class' => '' ) );
+ } else {
+ return Linker::link(
+ SpecialPage::getTitleFor( 'Upload' ),
+ wfMsgHtml( 'upload' ),
+ array(),
+ array(),
+ array( 'known', 'noclasses' )
+ );
+ }
+ }
+
+ function nameAndLogin() {
+ global $wgUser, $wgLang, $wgContLang;
+
+ $logoutPage = $wgContLang->specialPage( 'Userlogout' );
+
+ $ret = '';
+
+ if ( $wgUser->isAnon() ) {
+ if ( $this->getSkin()->showIPinHeader() ) {
+ $name = wfGetIP();
+
+ $talkLink = Linker::link( $wgUser->getTalkPage(),
+ $wgLang->getNsText( NS_TALK ) );
+
+ $ret .= "$name ($talkLink)";
+ } else {
+ $ret .= wfMsg( 'notloggedin' );
+ }
+
+ $returnTo = $this->getSkin()->getTitle()->getPrefixedDBkey();
+ $query = array();
+
+ if ( $logoutPage != $returnTo ) {
+ $query['returnto'] = $returnTo;
+ }
+
+ $loginlink = $wgUser->isAllowed( 'createaccount' )
+ ? 'nav-login-createaccount'
+ : 'login';
+ $ret .= "\n<br />" . Linker::link(
+ SpecialPage::getTitleFor( 'Userlogin' ),
+ wfMsg( $loginlink ), array(), $query
+ );
+ } else {
+ $returnTo = $this->getSkin()->getTitle()->getPrefixedDBkey();
+ $talkLink = Linker::link( $wgUser->getTalkPage(),
+ $wgLang->getNsText( NS_TALK ) );
+
+ $ret .= Linker::link( $wgUser->getUserPage(),
+ htmlspecialchars( $wgUser->getName() ) );
+ $ret .= " ($talkLink)<br />";
+ $ret .= $wgLang->pipeList( array(
+ Linker::link(
+ SpecialPage::getTitleFor( 'Userlogout' ), wfMsg( 'logout' ),
+ array(), array( 'returnto' => $returnTo )
+ ),
+ Linker::specialLink( 'Preferences' ),
+ ) );
+ }
+
+ $ret = $wgLang->pipeList( array(
+ $ret,
+ Linker::link(
+ Title::newFromText( wfMsgForContent( 'helppage' ) ),
+ wfMsg( 'help' )
+ ),
+ ) );
+
+ return $ret;
+ }
+
+}
+
diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php
index 023afdd8..373daa9d 100644
--- a/includes/SkinTemplate.php
+++ b/includes/SkinTemplate.php
@@ -20,8 +20,9 @@
* @file
*/
-if ( ! defined( 'MEDIAWIKI' ) )
+if ( !defined( 'MEDIAWIKI' ) ) {
die( 1 );
+}
/**
* Wrapper object for MediaWiki's localization functions,
@@ -106,7 +107,7 @@ class SkinTemplate extends Skin {
*
* @param $out OutputPage
*/
- function setupSkinUserCss( OutputPage $out ){
+ function setupSkinUserCss( OutputPage $out ) {
$out->addModuleStyles( array( 'mediawiki.legacy.shared', 'mediawiki.legacy.commonPrint' ) );
}
@@ -115,10 +116,10 @@ class SkinTemplate extends Skin {
* and eventually it spits out some HTML. Should have interface
* roughly equivalent to PHPTAL 0.7.
*
- * @param $classname string (or file)
+ * @param $classname String
* @param $repository string: subdirectory where we keep template files
* @param $cache_dir string
- * @return object
+ * @return QuickTemplate
* @private
*/
function setupTemplate( $classname, $repository = false, $cache_dir = false ) {
@@ -131,20 +132,18 @@ class SkinTemplate extends Skin {
* @param $out OutputPage
*/
function outputPage( OutputPage $out ) {
- global $wgArticle, $wgUser, $wgLang, $wgContLang;
- global $wgScript, $wgStylePath, $wgLanguageCode;
- global $wgMimeType, $wgJsMimeType, $wgOutputEncoding, $wgRequest;
+ global $wgUser, $wgLang, $wgContLang;
+ global $wgScript, $wgStylePath;
+ global $wgMimeType, $wgJsMimeType, $wgRequest;
global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces, $wgHtml5Version;
global $wgDisableCounters, $wgLogo, $wgHideInterlanguageLinks;
global $wgMaxCredits, $wgShowCreditsIfMax;
global $wgPageShowWatchingUsers;
global $wgUseTrackbacks, $wgUseSiteJs, $wgDebugComments;
- global $wgArticlePath, $wgScriptPath, $wgServer, $wgProfiler;
+ global $wgArticlePath, $wgScriptPath, $wgServer;
wfProfileIn( __METHOD__ );
- if ( is_object( $wgProfiler ) ) {
- $wgProfiler->setTemplated( true );
- }
+ Profiler::instance()->setTemplated( true );
$oldid = $wgRequest->getVal( 'oldid' );
$diff = $wgRequest->getVal( 'diff' );
@@ -153,17 +152,13 @@ class SkinTemplate extends Skin {
wfProfileIn( __METHOD__ . '-init' );
$this->initPage( $out );
- $this->setMembers();
$tpl = $this->setupTemplate( $this->template, 'skins' );
-
- #if ( $wgUseDatabaseMessages ) { // uncomment this to fall back to GetText
- $tpl->setTranslator( new MediaWiki_I18N() );
- #}
wfProfileOut( __METHOD__ . '-init' );
wfProfileIn( __METHOD__ . '-stuff' );
- $this->thispage = $this->mTitle->getPrefixedDBkey();
- $this->thisurl = $this->mTitle->getPrefixedURL();
+ $this->thispage = $this->getTitle()->getPrefixedDBkey();
+ $this->userpage = $wgUser->getUserPage()->getPrefixedText();
+
$query = array();
if ( !$wgRequest->wasPosted() ) {
$query = $wgRequest->getValues();
@@ -171,13 +166,14 @@ class SkinTemplate extends Skin {
unset( $query['returnto'] );
unset( $query['returntoquery'] );
}
- $this->thisquery = wfUrlencode( wfArrayToCGI( $query ) );
+ $this->thisquery = wfArrayToCGI( $query );
$this->loggedin = $wgUser->isLoggedIn();
- $this->iscontent = ( $this->mTitle->getNamespace() != NS_SPECIAL );
+ $this->iscontent = ( $this->getTitle()->getNamespace() != NS_SPECIAL );
$this->iseditable = ( $this->iscontent and !( $action == 'edit' or $action == 'submit' ) );
$this->username = $wgUser->getName();
if ( $wgUser->isLoggedIn() || $this->showIPinHeader() ) {
+
$this->userpageUrlDetails = self::makeUrlDetails( $this->userpage );
} else {
# This won't be used in the standard skins, but we define it to preserve the interface
@@ -185,7 +181,7 @@ class SkinTemplate extends Skin {
$this->userpageUrlDetails = self::makeKnownUrlDetails( $this->userpage );
}
- $this->titletxt = $this->mTitle->getPrefixedText();
+ $this->titletxt = $this->getTitle()->getPrefixedText();
wfProfileOut( __METHOD__ . '-stuff' );
wfProfileIn( __METHOD__ . '-stuff-head' );
@@ -197,9 +193,11 @@ class SkinTemplate extends Skin {
$this->setupUserCss( $out );
$tpl->set( 'pagecss', $this->setupPageCss() );
- $tpl->setRef( 'usercss', $this->usercss );
+ $tpl->set( 'usercss', false );
$this->userjs = $this->userjsprev = false;
+ # @todo FIXME: This is the only use of OutputPage::isUserJsAllowed() anywhere; can we
+ # get rid of it? For that matter, why is any of this here at all?
$this->setupUserJs( $out->isUserJsAllowed() );
$tpl->setRef( 'userjs', $this->userjs );
$tpl->setRef( 'userjsprev', $this->userjsprev );
@@ -232,19 +230,19 @@ class SkinTemplate extends Skin {
$tpl->set( 'title', $out->getPageTitle() );
$tpl->set( 'pagetitle', $out->getHTMLTitle() );
$tpl->set( 'displaytitle', $out->mPageLinkTitle );
- $tpl->set( 'pageclass', $this->getPageClasses( $this->mTitle ) );
+ $tpl->set( 'pageclass', $this->getPageClasses( $this->getTitle() ) );
$tpl->set( 'skinnameclass', ( 'skin-' . Sanitizer::escapeClass( $this->getSkinName() ) ) );
- $nsname = MWNamespace::exists( $this->mTitle->getNamespace() ) ?
- MWNamespace::getCanonicalName( $this->mTitle->getNamespace() ) :
- $this->mTitle->getNsText();
+ $nsname = MWNamespace::exists( $this->getTitle()->getNamespace() ) ?
+ MWNamespace::getCanonicalName( $this->getTitle()->getNamespace() ) :
+ $this->getTitle()->getNsText();
$tpl->set( 'nscanonical', $nsname );
- $tpl->set( 'nsnumber', $this->mTitle->getNamespace() );
- $tpl->set( 'titleprefixeddbkey', $this->mTitle->getPrefixedDBKey() );
- $tpl->set( 'titletext', $this->mTitle->getText() );
- $tpl->set( 'articleid', $this->mTitle->getArticleId() );
- $tpl->set( 'currevisionid', isset( $wgArticle ) ? $wgArticle->getLatest() : 0 );
+ $tpl->set( 'nsnumber', $this->getTitle()->getNamespace() );
+ $tpl->set( 'titleprefixeddbkey', $this->getTitle()->getPrefixedDBKey() );
+ $tpl->set( 'titletext', $this->getTitle()->getText() );
+ $tpl->set( 'articleid', $this->getTitle()->getArticleId() );
+ $tpl->set( 'currevisionid', $this->getTitle()->getLatestRevID() );
$tpl->set( 'isarticle', $out->isArticle() );
@@ -252,13 +250,13 @@ class SkinTemplate extends Skin {
$subpagestr = $this->subPageSubtitle();
$tpl->set(
'subtitle', !empty( $subpagestr ) ?
- '<span class="subpages">'.$subpagestr.'</span>'.$out->getSubtitle() :
+ '<span class="subpages">' . $subpagestr . '</span>' . $out->getSubtitle() :
$out->getSubtitle()
);
$undelete = $this->getUndeleteLink();
$tpl->set(
'undelete', !empty( $undelete ) ?
- '<span class="subpages">'.$undelete.'</span>' :
+ '<span class="subpages">' . $undelete . '</span>' :
''
);
@@ -278,7 +276,7 @@ class SkinTemplate extends Skin {
$tpl->setRef( 'mimetype', $wgMimeType );
$tpl->setRef( 'jsmimetype', $wgJsMimeType );
- $tpl->setRef( 'charset', $wgOutputEncoding );
+ $tpl->set( 'charset', 'UTF-8' );
$tpl->setRef( 'wgScript', $wgScript );
$tpl->setRef( 'skinname', $this->skinname );
$tpl->set( 'skinclass', get_class( $this ) );
@@ -286,13 +284,13 @@ class SkinTemplate extends Skin {
$tpl->set( 'printable', $out->isPrintable() );
$tpl->set( 'handheld', $wgRequest->getBool( 'handheld' ) );
$tpl->setRef( 'loggedin', $this->loggedin );
- $tpl->set( 'notspecialpage', $this->mTitle->getNamespace() != NS_SPECIAL );
+ $tpl->set( 'notspecialpage', $this->getTitle()->getNamespace() != NS_SPECIAL );
/* XXX currently unused, might get useful later
- $tpl->set( "editable", ($this->mTitle->getNamespace() != NS_SPECIAL ) );
- $tpl->set( "exists", $this->mTitle->getArticleID() != 0 );
- $tpl->set( "watch", $this->mTitle->userIsWatching() ? "unwatch" : "watch" );
- $tpl->set( "protect", count($this->mTitle->isProtected()) ? "unprotect" : "protect" );
- $tpl->set( "helppage", wfMsg('helppage'));
+ $tpl->set( 'editable', ( $this->getTitle()->getNamespace() != NS_SPECIAL ) );
+ $tpl->set( 'exists', $this->getTitle()->getArticleID() != 0 );
+ $tpl->set( 'watch', $this->getTitle()->userIsWatching() ? 'unwatch' : 'watch' );
+ $tpl->set( 'protect', count( $this->getTitle()->isProtected() ) ? 'unprotect' : 'protect' );
+ $tpl->set( 'helppage', wfMsg( 'helppage' ) );
*/
$tpl->set( 'searchaction', $this->escapeSearchLink() );
$tpl->set( 'searchtitle', SpecialPage::getTitleFor( 'Search' )->getPrefixedDBKey() );
@@ -303,39 +301,34 @@ class SkinTemplate extends Skin {
$tpl->setRef( 'serverurl', $wgServer );
$tpl->setRef( 'logopath', $wgLogo );
- $lang = wfUILang();
- $tpl->set( 'lang', $lang->getCode() );
- $tpl->set( 'dir', $lang->getDir() );
- $tpl->set( 'rtl', $lang->isRTL() );
+ $contentlang = $wgContLang->getCode();
+ $contentdir = $wgContLang->getDir();
+ $userlang = $wgLang->getCode();
+ $userdir = $wgLang->getDir();
+
+ $tpl->set( 'lang', $userlang );
+ $tpl->set( 'dir', $userdir );
+ $tpl->set( 'rtl', $wgLang->isRTL() );
$tpl->set( 'capitalizeallnouns', $wgLang->capitalizeAllNouns() ? ' capitalize-all-nouns' : '' );
$tpl->set( 'showjumplinks', $wgUser->getOption( 'showjumplinks' ) );
$tpl->set( 'username', $wgUser->isAnon() ? null : $this->username );
$tpl->setRef( 'userpage', $this->userpage );
$tpl->setRef( 'userpageurl', $this->userpageUrlDetails['href'] );
- $tpl->set( 'userlang', $wgLang->getCode() );
+ $tpl->set( 'userlang', $userlang );
// 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', '' );
-
- $lang = $wgLang->getCode();
- $dir = $wgLang->getDir();
- if ( $lang !== $wgContLang->getCode() || $dir !== $wgContLang->getDir() ) {
- $attrs = " lang='$lang' dir='$dir'";
+ $tpl->set( 'specialpageattributes', '' ); # obsolete
+ if ( $userlang !== $contentlang || $userdir !== $contentdir ) {
+ $attrs = " lang='$userlang' dir='$userdir'";
$tpl->set( 'userlangattributes', $attrs );
-
- // The content of SpecialPages should be presented in the
- // user's language. Content of regular pages should not be touched.
- if( $this->mTitle->isSpecialPage() ) {
- $tpl->set( 'specialpageattributes', $attrs );
- }
}
- $newtalks = $this->getNewtalks();
+ $newtalks = $this->getNewtalks( $out );
wfProfileOut( __METHOD__ . '-stuff2' );
@@ -343,10 +336,12 @@ class SkinTemplate extends Skin {
$tpl->setRef( 'newtalk', $newtalks );
$tpl->setRef( 'skin', $this );
$tpl->set( 'logo', $this->logoText() );
- if ( $out->isArticle() and ( !isset( $oldid ) or isset( $diff ) ) and
- $wgArticle and 0 != $wgArticle->getID() ){
+ if ( $out->isArticle() && ( !isset( $oldid ) || isset( $diff ) ) &&
+ $this->getTitle()->exists() )
+ {
+ $article = new Article( $this->getTitle(), 0 );
if ( !$wgDisableCounters ) {
- $viewcount = $wgLang->formatNum( $wgArticle->getCount() );
+ $viewcount = $wgLang->formatNum( $article->getCount() );
if ( $viewcount ) {
$tpl->set( 'viewcount', wfMsgExt( 'viewcount', array( 'parseinline' ), $viewcount ) );
} else {
@@ -360,7 +355,7 @@ class SkinTemplate extends Skin {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'watchlist',
array( 'COUNT(*) AS n' ),
- array( 'wl_title' => $dbr->strencode( $this->mTitle->getDBkey() ), 'wl_namespace' => $this->mTitle->getNamespace() ),
+ array( 'wl_title' => $dbr->strencode( $this->getTitle()->getDBkey() ), 'wl_namespace' => $this->getTitle()->getNamespace() ),
__METHOD__
);
$x = $dbr->fetchObject( $res );
@@ -382,9 +377,9 @@ class SkinTemplate extends Skin {
$this->credits = false;
if( $wgMaxCredits != 0 ){
- $this->credits = Credits::getCredits( $wgArticle, $wgMaxCredits, $wgShowCreditsIfMax );
+ $this->credits = Action::factory( 'credits', $article )->getCredits( $wgMaxCredits, $wgShowCreditsIfMax );
} else {
- $tpl->set( 'lastmod', $this->lastModified() );
+ $tpl->set( 'lastmod', $this->lastModified( $article ) );
}
$tpl->setRef( 'credits', $this->credits );
@@ -425,19 +420,23 @@ class SkinTemplate extends Skin {
'disclaimer',
),
) );
-
+
global $wgFooterIcons;
$tpl->set( 'footericons', $wgFooterIcons );
- foreach ( $tpl->data["footericons"] as $footerIconsKey => &$footerIconsBlock ) {
- if ( count($footerIconsBlock) > 0 ) {
+ 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;
+ if ( isset( $footerIcon['src'] ) ) {
+ if ( !isset( $footerIcon['width'] ) ) {
+ $footerIcon['width'] = 88;
+ }
+ if ( !isset( $footerIcon['height'] ) ) {
+ $footerIcon['height'] = 31;
+ }
}
}
} else {
- unset($tpl->data["footericons"][$footerIconsKey]);
+ unset( $tpl->data['footericons'][$footerIconsKey] );
}
}
@@ -448,16 +447,22 @@ class SkinTemplate extends Skin {
}
$tpl->set( 'reporttime', wfReportTime() );
- $tpl->set( 'sitenotice', wfGetSiteNotice() );
+ $tpl->set( 'sitenotice', $this->getSiteNotice() );
$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() );
+ $tpl->set( 'printfooter', $this->printSource() );
+
+ # Add a <div class="mw-content-ltr/rtl"> around the body text
+ # not for special pages or file pages AND only when viewing AND if the page exists
+ # (or is in MW namespace, because that has default content)
+ if( !in_array( $this->getTitle()->getNamespace(), array( NS_SPECIAL, NS_FILE ) ) &&
+ in_array( $action, array( 'view', 'historysubmit' ) ) &&
+ ( $this->getTitle()->exists() || $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) ) {
+ $pageLang = $this->getTitle()->getPageLanguage();
+ $realBodyAttribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
+ 'class' => 'mw-content-'.$pageLang->getDir() );
$out->mBodytext = Html::rawElement( 'div', $realBodyAttribs, $out->mBodytext );
}
- $out->mBodytext .= $printfooter . $this->generateDebugHTML();
+
$tpl->setRef( 'bodytext', $out->mBodytext );
# Language links
@@ -489,12 +494,14 @@ class SkinTemplate extends Skin {
wfProfileIn( __METHOD__ . '-stuff5' );
# Personal toolbar
- $tpl->set( 'personal_urls', $this->buildPersonalUrls() );
- $content_actions = $this->buildContentActionUrls();
+ $tpl->set( 'personal_urls', $this->buildPersonalUrls( $out ) );
+ $content_navigation = $this->buildContentNavigationUrls( $out );
+ $content_actions = $this->buildContentActionUrls( $content_navigation );
+ $tpl->setRef( 'content_navigation', $content_navigation );
$tpl->setRef( 'content_actions', $content_actions );
$tpl->set( 'sidebar', $this->buildSidebar() );
- $tpl->set( 'nav_urls', $this->buildNavUrls() );
+ $tpl->set( 'nav_urls', $this->buildNavUrls( $out ) );
// Set the head scripts near the end, in case the above actions resulted in added scripts
if ( $this->useHeadElement ) {
@@ -503,11 +510,22 @@ class SkinTemplate extends Skin {
$tpl->set( 'headscripts', $out->getScript() );
}
+ $tpl->set( 'debughtml', $this->generateDebugHTML() );
+
// original version by hansm
if( !wfRunHooks( 'SkinTemplateOutputPageBeforeExec', array( &$this, &$tpl ) ) ) {
wfDebug( __METHOD__ . ": Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!\n" );
}
+ // Set the bodytext to another key so that skins can just output it on it's own
+ // and output printfooter and debughtml separately
+ $tpl->set( 'bodycontent', $tpl->data['bodytext'] );
+
+ // Append printfooter and debughtml onto bodytext so that skins that were already
+ // using bodytext before they were split out don't suddenly start not outputting information
+ $tpl->data['bodytext'] .= Html::rawElement( 'div', array( 'class' => 'printfooter' ), "\n{$tpl->data['printfooter']}" ) . "\n";
+ $tpl->data['bodytext'] .= $tpl->data['debughtml'];
+
// allow extensions adding stuff after the page content.
// See Skin::afterContentHook() for further documentation.
$tpl->set( 'dataAfterContent', $this->afterContentHook() );
@@ -536,35 +554,39 @@ class SkinTemplate extends Skin {
}
/**
+ * Output a boolean indiciating if buildPersonalUrls should output separate
+ * login and create account links or output a combined link
+ * By default we simply return a global config setting that affects most skins
+ * This is setup as a method so that like with $wgLogo and getLogo() a skin
+ * can override this setting and always output one or the other if it has
+ * a reason it can't output one of the two modes.
+ */
+ function useCombinedLoginLink() {
+ global $wgUseCombinedLoginLink;
+ return $wgUseCombinedLoginLink;
+ }
+
+ /**
* build array of urls for personal toolbar
* @return array
- * @private
*/
- function buildPersonalUrls() {
- global $wgOut, $wgRequest;
+ protected function buildPersonalUrls( OutputPage $out ) {
+ global $wgRequest;
- $title = $wgOut->getTitle();
+ $title = $out->getTitle();
$pageurl = $title->getLocalURL();
wfProfileIn( __METHOD__ );
/* set up the default links for the personal toolbar */
$personal_urls = array();
-
- # Due to bug 32276, if a user does not have read permissions,
- # $wgOut->getTitle() will just give Special:Badtitle, which is
- # not especially useful as a returnto parameter. Use the title
- # from the request instead, if there was one.
- $page = Title::newFromURL( $wgRequest->getVal( 'title', '' ) );
- $page = $wgRequest->getVal( 'returnto', $page );
- $returnto = '';
- if( strval( $page ) !== '' ) {
- $returnto = "returnto=$page";
- $query = $wgRequest->getVal( 'returntoquery', $this->thisquery );
- if( $query != '' ) {
- $returnto .= "&returntoquery=$query";
- }
+
+ $page = $wgRequest->getVal( 'returnto', $this->thispage );
+ $query = $wgRequest->getVal( 'returntoquery', $this->thisquery );
+ $a = array( 'returnto' => $page );
+ if( $query != '' ) {
+ $a['returntoquery'] = $query;
}
-
+ $returnto = wfArrayToCGI( $a );
if( $this->loggedin ) {
$personal_urls['userpage'] = array(
'text' => $this->username,
@@ -599,8 +621,7 @@ class SkinTemplate extends Skin {
# contain the original alias-with-subpage.
$origTitle = Title::newFromText( $wgRequest->getText( 'title' ) );
if( $origTitle instanceof Title && $origTitle->getNamespace() == NS_SPECIAL ) {
- list( $spName, $spPar ) =
- SpecialPage::resolveAliasWithSubpage( $origTitle->getText() );
+ list( $spName, $spPar ) = SpecialPageFactory::resolveAlias( $origTitle->getText() );
$active = $spName == 'Contributions'
&& ( ( $spPar && $spPar == $this->username )
|| $wgRequest->getText( 'target' ) == $this->username );
@@ -625,24 +646,42 @@ class SkinTemplate extends Skin {
);
} else {
global $wgUser;
- $loginlink = $wgUser->isAllowed( 'createaccount' )
+ $useCombinedLoginLink = $this->useCombinedLoginLink();
+ $loginlink = $wgUser->isAllowed( 'createaccount' ) && $useCombinedLoginLink
? 'nav-login-createaccount'
: 'login';
+ $is_signup = $wgRequest->getText('type') == "signup";
# anonlogin & login are the same
$login_url = array(
'text' => wfMsg( $loginlink ),
'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
- 'active' => $title->isSpecial( 'Userlogin' )
+ 'active' => $title->isSpecial( 'Userlogin' ) && ( $loginlink == "nav-login-createaccount" || !$is_signup )
);
- global $wgProto, $wgSecureLogin;
- if( $wgProto === 'http' && $wgSecureLogin ) {
+ if ( $wgUser->isAllowed( 'createaccount' ) && !$useCombinedLoginLink ) {
+ $createaccount_url = array(
+ 'text' => wfMsg( 'createaccount' ),
+ 'href' => self::makeSpecialUrl( 'Userlogin', "$returnto&type=signup" ),
+ 'active' => $title->isSpecial( 'Userlogin' ) && $is_signup
+ );
+ }
+ global $wgServer, $wgSecureLogin;
+ if( substr( $wgServer, 0, 5 ) === '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
+ # @todo FIXME: Class depends on skin
+ $login_url['class'] = 'link-https';
+ if ( isset($createaccount_url) ) {
+ $https_url = preg_replace( '/^http:/', 'https:',
+ $title->getFullURL("type=signup") );
+ $createaccount_url['href'] = $https_url;
+ # @todo FIXME: Class depends on skin
+ $createaccount_url['class'] = 'link-https';
+ }
}
+
if( $this->showIPinHeader() ) {
$href = &$this->userpageUrlDetails['href'];
$personal_urls['anonuserpage'] = array(
@@ -663,6 +702,9 @@ class SkinTemplate extends Skin {
} else {
$personal_urls['login'] = $login_url;
}
+ if ( isset($createaccount_url) ) {
+ $personal_urls['createaccount'] = $createaccount_url;
+ }
}
wfRunHooks( 'PersonalUrls', array( &$personal_urls, &$title ) );
@@ -670,6 +712,15 @@ class SkinTemplate extends Skin {
return $personal_urls;
}
+ /**
+ * TODO document
+ * @param $title Title
+ * @param $message String message key
+ * @param $selected Bool
+ * @param $query String
+ * @param $checkEdit Bool
+ * @return array
+ */
function tabAction( $title, $message, $selected, $query = '', $checkEdit = false ) {
$classes = array();
if( $selected ) {
@@ -680,10 +731,19 @@ class SkinTemplate extends Skin {
$query = 'action=edit&redlink=1';
}
- $text = wfMsg( $message );
- if ( wfEmptyMsg( $message, $text ) ) {
+ // wfMessageFallback will nicely accept $message as an array of fallbacks
+ // or just a single key
+ $msg = wfMessageFallback( $message );
+ if ( is_array($message) ) {
+ // for hook compatibility just keep the last message name
+ $message = end($message);
+ }
+ if ( $msg->exists() ) {
+ $text = $msg->text();
+ } else {
global $wgContLang;
- $text = $wgContLang->getFormattedNsText( MWNamespace::getSubject( $title->getNamespace() ) );
+ $text = $wgContLang->getFormattedNsText(
+ MWNamespace::getSubject( $title->getNamespace() ) );
}
$result = array();
@@ -696,7 +756,8 @@ class SkinTemplate extends Skin {
return array(
'class' => implode( ' ', $classes ),
'text' => $text,
- 'href' => $title->getLocalUrl( $query ) );
+ 'href' => $title->getLocalUrl( $query ),
+ 'primary' => true );
}
function makeTalkUrlDetails( $name, $urlaction = '' ) {
@@ -723,204 +784,372 @@ class SkinTemplate extends Skin {
}
/**
- * an array of edit links by default used for the tabs
+ * a structured array of links usually used for the tabs in a skin
+ *
+ * There are 4 standard sections
+ * namespaces: Used for namespace tabs like special, page, and talk namespaces
+ * views: Used for primary page views like read, edit, history
+ * actions: Used for most extra page actions like deletion, protection, etc...
+ * variants: Used to list the language variants for the page
+ *
+ * Each section's value is a key/value array of links for that section.
+ * The links themseves have these common keys:
+ * - class: The css classes to apply to the tab
+ * - text: The text to display on the tab
+ * - href: The href for the tab to point to
+ * - rel: An optional rel= for the tab's link
+ * - redundant: If true the tab will be dropped in skins using content_actions
+ * this is useful for tabs like "Read" which only have meaning in skins that
+ * take special meaning from the grouped structure of content_navigation
+ *
+ * Views also have an extra key which can be used:
+ * - primary: If this is not true skins like vector may try to hide the tab
+ * when the user has limited space in their browser window
+ *
+ * content_navigation using code also expects these ids to be present on the
+ * links, however these are usually automatically generated by SkinTemplate
+ * itself and are not necessary when using a hook. The only things these may
+ * matter to are people modifying content_navigation after it's initial creation:
+ * - id: A "preferred" id, most skins are best off outputting this preferred id for best compatibility
+ * - tooltiponly: This is set to true for some tabs in cases where the system
+ * believes that the accesskey should not be added to the tab.
+ *
* @return array
- * @private
*/
- function buildContentActionUrls() {
- global $wgContLang, $wgLang, $wgOut, $wgUser, $wgRequest, $wgArticle;
+ protected function buildContentNavigationUrls( OutputPage $out ) {
+ global $wgContLang, $wgLang, $wgUser, $wgRequest;
+ global $wgDisableLangConversion;
wfProfileIn( __METHOD__ );
+ $title = $this->getRelevantTitle(); // Display tabs for the relevant title rather than always the title itself
+ $onPage = $title->equals($this->getTitle());
+
+ $content_navigation = array(
+ 'namespaces' => array(),
+ 'views' => array(),
+ 'actions' => array(),
+ 'variants' => array()
+ );
+
+ // parameters
$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 ) );
+ $userCanRead = $title->userCanRead();
+ $skname = $this->skinname;
- if( $this->iscontent ) {
- $subjpage = $this->mTitle->getSubjectPage();
- $talkpage = $this->mTitle->getTalkPage();
+ $preventActiveTabs = false;
+ wfRunHooks( 'SkinTemplatePreventOtherActiveTabs', array( &$this, &$preventActiveTabs ) );
- $nskey = $this->mTitle->getNamespaceKey();
- $content_actions[$nskey] = $this->tabAction(
- $subjpage,
- $nskey,
- !$this->mTitle->isTalkPage() && !$prevent_active_tabs,
- '', $userCanRead
- );
+ // Checks if page is some kind of content
+ if( $title->getNamespace() != NS_SPECIAL ) {
+ // Gets page objects for the related namespaces
+ $subjectPage = $title->getSubjectPage();
+ $talkPage = $title->getTalkPage();
+
+ // Determines if this is a talk page
+ $isTalk = $title->isTalkPage();
+
+ // Generates XML IDs from namespace names
+ $subjectId = $title->getNamespaceKey( '' );
+
+ if ( $subjectId == 'main' ) {
+ $talkId = 'talk';
+ } else {
+ $talkId = "{$subjectId}_talk";
+ }
- $content_actions['talk'] = $this->tabAction(
- $talkpage,
- 'talk',
- $this->mTitle->isTalkPage() && !$prevent_active_tabs,
- '',
- $userCanRead
+ // Adds namespace links
+ $subjectMsg = array( "nstab-$subjectId" );
+ if ( $subjectPage->isMainPage() ) {
+ array_unshift($subjectMsg, 'mainpage-nstab');
+ }
+ $content_navigation['namespaces'][$subjectId] = $this->tabAction(
+ $subjectPage, $subjectMsg, !$isTalk && !$preventActiveTabs, '', $userCanRead
);
+ $content_navigation['namespaces'][$subjectId]['context'] = 'subject';
+ $content_navigation['namespaces'][$talkId] = $this->tabAction(
+ $talkPage, array( "nstab-$talkId", 'talk' ), $isTalk && !$preventActiveTabs, '', $userCanRead
+ );
+ $content_navigation['namespaces'][$talkId]['context'] = 'talk';
+
+ // Adds view view link
+ if ( $title->exists() && $userCanRead ) {
+ $content_navigation['views']['view'] = $this->tabAction(
+ $isTalk ? $talkPage : $subjectPage,
+ array( "$skname-view-view", 'view' ),
+ ( $onPage && ($action == 'view' || $action == 'purge' ) ), '', true
+ );
+ $content_navigation['views']['view']['redundant'] = true; // signal to hide this from simple content_actions
+ }
wfProfileIn( __METHOD__ . '-edit' );
- 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() || ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && !wfEmptyMsg( $this->mTitle->getText() ) ) )
- ? wfMsg( 'edit' )
- : wfMsg( 'create' ),
- 'href' => $this->mTitle->getLocalUrl( $this->editUrlOptions() )
- );
- // adds new section link if page is a current revision of a talk page or
- if ( ( $wgArticle && $wgArticle->isCurrent() && $istalk ) || $wgOut->showNewSectionLink() ) {
- if ( !$wgOut->forceHideNewSectionLink() ) {
- $content_actions['addsection'] = array(
+ // Checks if user can...
+ if (
+ // read and edit the current page
+ $userCanRead && $title->quickUserCan( 'edit' ) &&
+ (
+ // if it exists
+ $title->exists() ||
+ // or they can create one here
+ $title->quickUserCan( 'create' )
+ )
+ ) {
+ // Builds CSS class for talk page links
+ $isTalkClass = $isTalk ? ' istalk' : '';
+
+ // Determines if we're in edit mode
+ $selected = (
+ $onPage &&
+ ( $action == 'edit' || $action == 'submit' ) &&
+ ( $section != 'new' )
+ );
+ $msgKey = $title->exists() || ( $title->getNamespace() == NS_MEDIAWIKI && $title->getDefaultMessageText() !== false ) ?
+ "edit" : "create";
+ $content_navigation['views']['edit'] = array(
+ 'class' => ( $selected ? 'selected' : '' ) . $isTalkClass,
+ 'text' => wfMessageFallback( "$skname-view-$msgKey", $msgKey )->text(),
+ 'href' => $title->getLocalURL( $this->editUrlOptions() ),
+ 'primary' => true, // don't collapse this in vector
+ );
+ // Checks if this is a current rev of talk page and we should show a new
+ // section link
+ if ( ( $isTalk && $this->isRevisionCurrent() ) || ( $out->showNewSectionLink() ) ) {
+ // Checks if we should ever show a new section link
+ if ( !$out->forceHideNewSectionLink() ) {
+ // Adds new section link
+ //$content_navigation['actions']['addsection']
+ $content_navigation['views']['addsection'] = array(
'class' => $section == 'new' ? 'selected' : false,
- 'text' => wfMsg( 'addsection' ),
- 'href' => $this->mTitle->getLocalUrl( 'action=edit&section=new' )
+ 'text' => wfMessageFallback( "$skname-action-addsection", 'addsection' )->text(),
+ 'href' => $title->getLocalURL( 'action=edit&section=new' )
);
}
}
- } elseif ( $this->mTitle->hasSourceText() && $userCanRead ) {
- $content_actions['viewsource'] = array(
- 'class' => ($action == 'edit') ? 'selected' : false,
- 'text' => wfMsg( 'viewsource' ),
- 'href' => $this->mTitle->getLocalUrl( $this->editUrlOptions() )
+ // Checks if the page has some kind of viewable content
+ } elseif ( $title->hasSourceText() && $userCanRead ) {
+ // Adds view source view link
+ $content_navigation['views']['viewsource'] = array(
+ 'class' => ( $onPage && $action == 'edit' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-viewsource", 'viewsource' )->text(),
+ 'href' => $title->getLocalURL( $this->editUrlOptions() ),
+ 'primary' => true, // don't collapse this in vector
);
}
wfProfileOut( __METHOD__ . '-edit' );
wfProfileIn( __METHOD__ . '-live' );
- if ( $this->mTitle->exists() && $userCanRead ) {
- $content_actions['history'] = array(
- 'class' => ($action == 'history') ? 'selected' : false,
- 'text' => wfMsg( 'history_short' ),
- 'href' => $this->mTitle->getLocalUrl( 'action=history' ),
+ // Checks if the page exists
+ if ( $title->exists() && $userCanRead ) {
+ // Adds history view link
+ $content_navigation['views']['history'] = array(
+ 'class' => ( $onPage && $action == 'history' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-view-history", 'history_short' )->text(),
+ 'href' => $title->getLocalURL( 'action=history' ),
'rel' => 'archives',
);
if( $wgUser->isAllowed( 'delete' ) ) {
- $content_actions['delete'] = array(
- 'class' => ($action == 'delete') ? 'selected' : false,
- 'text' => wfMsg( 'delete' ),
- 'href' => $this->mTitle->getLocalUrl( 'action=delete' )
+ $content_navigation['actions']['delete'] = array(
+ 'class' => ( $onPage && $action == 'delete' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-delete", 'delete' )->text(),
+ 'href' => $title->getLocalURL( 'action=delete' )
);
}
- if ( $this->mTitle->quickUserCan( 'move' ) ) {
- $moveTitle = SpecialPage::getTitleFor( 'Movepage', $this->thispage );
- $content_actions['move'] = array(
- 'class' => $this->mTitle->isSpecial( 'Movepage' ) ? 'selected' : false,
- 'text' => wfMsg( 'move' ),
- 'href' => $moveTitle->getLocalUrl()
+ if ( $title->quickUserCan( 'move' ) ) {
+ $moveTitle = SpecialPage::getTitleFor( 'Movepage', $title->getPrefixedDBkey() );
+ $content_navigation['actions']['move'] = array(
+ 'class' => $this->getTitle()->isSpecial( 'Movepage' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-move", 'move' )->text(),
+ 'href' => $moveTitle->getLocalURL()
);
}
- if ( $this->mTitle->getNamespace() !== NS_MEDIAWIKI && $wgUser->isAllowed( 'protect' ) ) {
- if( !$this->mTitle->isProtected() ){
- $content_actions['protect'] = array(
- 'class' => ($action == 'protect') ? 'selected' : false,
- 'text' => wfMsg( 'protect' ),
- 'href' => $this->mTitle->getLocalUrl( 'action=protect' )
- );
-
- } else {
- $content_actions['unprotect'] = array(
- 'class' => ($action == 'unprotect') ? 'selected' : false,
- 'text' => wfMsg( 'unprotect' ),
- 'href' => $this->mTitle->getLocalUrl( 'action=unprotect' )
- );
- }
+ if ( $title->getNamespace() !== NS_MEDIAWIKI && $wgUser->isAllowed( 'protect' ) ) {
+ $mode = !$title->isProtected() ? 'protect' : 'unprotect';
+ $content_navigation['actions'][$mode] = array(
+ 'class' => ( $onPage && $action == $mode ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-$mode", $mode )->text(),
+ 'href' => $title->getLocalURL( "action=$mode" )
+ );
}
} else {
- //article doesn't exist or is deleted
- if( $wgUser->isAllowed( 'deletedhistory' ) && $wgUser->isAllowed( 'deletedtext' ) ) {
- $n = $this->mTitle->isDeleted();
+ // article doesn't exist or is deleted
+ if ( $wgUser->isAllowed( 'deletedhistory' ) ) {
+ $n = $title->isDeleted();
if( $n ) {
$undelTitle = SpecialPage::getTitleFor( 'Undelete' );
- $content_actions['undelete'] = array(
- 'class' => false,
- 'text' => wfMsgExt( 'undelete_short', array( 'parsemag' ), $wgLang->formatNum( $n ) ),
- 'href' => $undelTitle->getLocalUrl( 'target=' . urlencode( $this->thispage ) )
- #'href' => self::makeSpecialUrl( "Undelete/$this->thispage" )
+ // If the user can't undelete but can view deleted history show them a "View .. deleted" tab instead
+ $msgKey = $wgUser->isAllowed( 'undelete' ) ? 'undelete' : 'viewdeleted';
+ $content_navigation['actions']['undelete'] = array(
+ 'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" )
+ ->params( $wgLang->formatNum( $n ) )->text(),
+ 'href' => $undelTitle->getLocalURL( array( 'target' => $title->getPrefixedDBkey() ) )
);
}
}
- if ( $this->mTitle->getNamespace() !== NS_MEDIAWIKI && $wgUser->isAllowed( 'protect' ) ) {
- if( !$this->mTitle->getRestrictions( 'create' ) ) {
- $content_actions['protect'] = array(
- 'class' => ($action == 'protect') ? 'selected' : false,
- 'text' => wfMsg( 'protect' ),
- 'href' => $this->mTitle->getLocalUrl( 'action=protect' )
- );
-
- } else {
- $content_actions['unprotect'] = array(
- 'class' => ($action == 'unprotect') ? 'selected' : false,
- 'text' => wfMsg( 'unprotect' ),
- 'href' => $this->mTitle->getLocalUrl( 'action=unprotect' )
- );
- }
+ if ( $title->getNamespace() !== NS_MEDIAWIKI && $wgUser->isAllowed( 'protect' ) ) {
+ $mode = !$title->getRestrictions( 'create' ) ? 'protect' : 'unprotect';
+ $content_navigation['actions'][$mode] = array(
+ 'class' => ( $onPage && $action == $mode ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-$mode", $mode )->text(),
+ 'href' => $title->getLocalURL( "action=$mode" )
+ );
}
}
-
wfProfileOut( __METHOD__ . '-live' );
- if( $this->loggedin ) {
- if( !$this->mTitle->userIsWatching()) {
- $content_actions['watch'] = array(
- 'class' => ($action == 'watch' or $action == 'unwatch') ? 'selected' : false,
- 'text' => wfMsg( 'watch' ),
- 'href' => $this->mTitle->getLocalUrl( 'action=watch' )
- );
- } else {
- $content_actions['unwatch'] = array(
- 'class' => ($action == 'unwatch' or $action == 'watch') ? 'selected' : false,
- 'text' => wfMsg( 'unwatch' ),
- 'href' => $this->mTitle->getLocalUrl( 'action=unwatch' )
- );
- }
+ // Checks if the user is logged in
+ if ( $this->loggedin ) {
+ /**
+ * The following actions use messages which, if made particular to
+ * the any specific skins, would break the Ajax code which makes this
+ * action happen entirely inline. Skin::makeGlobalVariablesScript
+ * defines a set of messages in a javascript object - and these
+ * messages are assumed to be global for all skins. Without making
+ * a change to that procedure these messages will have to remain as
+ * the global versions.
+ */
+ $mode = $title->userIsWatching() ? 'unwatch' : 'watch';
+ $token = WatchAction::getWatchToken( $title, $wgUser, $mode );
+ $content_navigation['actions'][$mode] = array(
+ 'class' => $onPage && ( $action == 'watch' || $action == 'unwatch' ) ? 'selected' : false,
+ 'text' => wfMsg( $mode ), // uses 'watch' or 'unwatch' message
+ 'href' => $title->getLocalURL( array( 'action' => $mode, 'token' => $token ) )
+ );
}
-
- wfRunHooks( 'SkinTemplateTabs', array( $this, &$content_actions ) );
+ wfRunHooks( 'SkinTemplateNavigation', array( &$this, &$content_navigation ) );
} else {
- /* show special page tab */
-
- $content_actions[$this->mTitle->getNamespaceKey()] = array(
+ // If it's not content, it's got to be a special page
+ $content_navigation['namespaces']['special'] = array(
'class' => 'selected',
- 'text' => wfMsg('nstab-special'),
+ 'text' => wfMsg( 'nstab-special' ),
'href' => $wgRequest->getRequestURL(), // @bug 2457, 2510
+ 'context' => 'subject'
);
- wfRunHooks( 'SkinTemplateBuildContentActionUrlsAfterSpecialPage', array( &$this, &$content_actions ) );
+ wfRunHooks( 'SkinTemplateNavigation::SpecialPage',
+ array( &$this, &$content_navigation ) );
}
- /* show links to different language variants */
- global $wgDisableLangConversion;
+ // Gets list of language variants
$variants = $wgContLang->getVariants();
- if( !$wgDisableLangConversion && sizeof( $variants ) > 1 ) {
+ // Checks that language conversion is enabled and variants exist
+ if( !$wgDisableLangConversion && count( $variants ) > 1 ) {
+ // Gets preferred variant
$preferred = $wgContLang->getPreferredVariant();
- $vcount=0;
+ // Loops over each variant
foreach( $variants as $code ) {
+ // Gets variant name from language code
$varname = $wgContLang->getVariantname( $code );
- if( $varname == 'disable' )
+ // Checks if the variant is marked as disabled
+ if( $varname == 'disable' ) {
+ // Skips this variant
continue;
- $selected = ( $code == $preferred )? 'selected' : false;
- $content_actions['varlang-' . $vcount] = array(
- 'class' => $selected,
+ }
+ // Appends variant link
+ $content_navigation['variants'][] = array(
+ 'class' => ( $code == $preferred ) ? 'selected' : false,
'text' => $varname,
- 'href' => $this->mTitle->getLocalURL( '', $code )
+ 'href' => $title->getLocalURL( '', $code )
);
- $vcount ++;
}
}
- wfRunHooks( 'SkinTemplateContentActions', array( &$content_actions ) );
+ // Equiv to SkinTemplateContentActions
+ wfRunHooks( 'SkinTemplateNavigation::Universal', array( &$this, &$content_navigation ) );
+
+ // Setup xml ids and tooltip info
+ foreach ( $content_navigation as $section => &$links ) {
+ foreach ( $links as $key => &$link ) {
+ $xmlID = $key;
+ if ( isset( $link['context'] ) && $link['context'] == 'subject' ) {
+ $xmlID = 'ca-nstab-' . $xmlID;
+ } elseif ( isset( $link['context'] ) && $link['context'] == 'talk' ) {
+ $xmlID = 'ca-talk';
+ } elseif ( $section == "variants" ) {
+ $xmlID = 'ca-varlang-' . $xmlID;
+ } else {
+ $xmlID = 'ca-' . $xmlID;
+ }
+ $link['id'] = $xmlID;
+ }
+ }
+
+ # We don't want to give the watch tab an accesskey if the
+ # page is being edited, because that conflicts with the
+ # accesskey on the watch checkbox. We also don't want to
+ # give the edit tab an accesskey, because that's fairly su-
+ # perfluous and conflicts with an accesskey (Ctrl-E) often
+ # used for editing in Safari.
+ if( in_array( $action, array( 'edit', 'submit' ) ) ) {
+ if ( isset($content_navigation['views']['edit']) ) {
+ $content_navigation['views']['edit']['tooltiponly'] = true;
+ }
+ if ( isset($content_navigation['actions']['watch']) ) {
+ $content_navigation['actions']['watch']['tooltiponly'] = true;
+ }
+ if ( isset($content_navigation['actions']['unwatch']) ) {
+ $content_navigation['actions']['unwatch']['tooltiponly'] = true;
+ }
+ }
wfProfileOut( __METHOD__ );
+
+ return $content_navigation;
+ }
+
+ /**
+ * an array of edit links by default used for the tabs
+ * @return array
+ * @private
+ */
+ function buildContentActionUrls( $content_navigation ) {
+
+ wfProfileIn( __METHOD__ );
+
+ // content_actions has been replaced with content_navigation for backwards
+ // compatibility and also for skins that just want simple tabs content_actions
+ // is now built by flattening the content_navigation arrays into one
+
+ $content_actions = array();
+
+ foreach ( $content_navigation as $links ) {
+
+ foreach ( $links as $key => $value ) {
+
+ if ( isset($value["redundant"]) && $value["redundant"] ) {
+ // Redundant tabs are dropped from content_actions
+ continue;
+ }
+
+ // content_actions used to have ids built using the "ca-$key" pattern
+ // so the xmlID based id is much closer to the actual $key that we want
+ // for that reason we'll just strip out the ca- if present and use
+ // the latter potion of the "id" as the $key
+ if ( isset($value["id"]) && substr($value["id"], 0, 3) == "ca-" ) {
+ $key = substr($value["id"], 3);
+ }
+
+ if ( isset($content_actions[$key]) ) {
+ wfDebug( __METHOD__ . ": Found a duplicate key for $key while flattening content_navigation into content_actions." );
+ continue;
+ }
+
+ $content_actions[$key] = $value;
+
+ }
+
+ }
+
+ wfProfileOut( __METHOD__ );
+
return $content_actions;
}
@@ -929,8 +1158,8 @@ class SkinTemplate extends Skin {
* @return array
* @private
*/
- function buildNavUrls() {
- global $wgUseTrackbacks, $wgOut, $wgUser, $wgRequest;
+ protected function buildNavUrls( OutputPage $out ) {
+ global $wgUseTrackbacks, $wgUser, $wgRequest;
global $wgUploadNavigationUrl;
wfProfileIn( __METHOD__ );
@@ -954,32 +1183,34 @@ class SkinTemplate extends Skin {
// A print stylesheet is attached to all pages, but nobody ever
// figures that out. :) Add a link...
if( $this->iscontent && ( $action == 'view' || $action == 'purge' ) ) {
- if ( !$wgOut->isPrintable() ) {
+ if ( !$out->isPrintable() ) {
$nav_urls['print'] = array(
'text' => wfMsg( 'printableversion' ),
- 'href' => $wgRequest->appendQuery( 'printable=yes' )
+ 'href' => $this->getTitle()->getLocalURL(
+ $wgRequest->appendQueryValue( 'printable', 'yes', true ) )
);
}
// Also add a "permalink" while we're at it
- if ( $this->mRevisionId ) {
+ $revid = $this->getRevisionId();
+ if ( $revid ) {
$nav_urls['permalink'] = array(
'text' => wfMsg( 'permalink' ),
- 'href' => $wgOut->getTitle()->getLocalURL( "oldid=$this->mRevisionId" )
+ 'href' => $out->getTitle()->getLocalURL( "oldid=$revid" )
);
}
- // Copy in case this undocumented, shady hook tries to mess with internals
- $revid = $this->mRevisionId;
- wfRunHooks( 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink', array( &$this, &$nav_urls, &$revid, &$revid ) );
+ // Use the copy of revision ID in case this undocumented, shady hook tries to mess with internals
+ wfRunHooks( 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink',
+ array( &$this, &$nav_urls, &$revid, &$revid ) );
}
- if( $this->mTitle->getNamespace() != NS_SPECIAL ) {
+ if( $this->getTitle()->getNamespace() != NS_SPECIAL ) {
$wlhTitle = SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage );
$nav_urls['whatlinkshere'] = array(
'href' => $wlhTitle->getLocalUrl()
);
- if( $this->mTitle->getArticleId() ) {
+ if( $this->getTitle()->getArticleId() ) {
$rclTitle = SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage );
$nav_urls['recentchangeslinked'] = array(
'href' => $rclTitle->getLocalUrl()
@@ -989,17 +1220,19 @@ class SkinTemplate extends Skin {
}
if( $wgUseTrackbacks )
$nav_urls['trackbacklink'] = array(
- 'href' => $wgOut->getTitle()->trackbackURL()
+ 'href' => $out->getTitle()->trackbackURL()
);
}
- if( $this->mTitle->getNamespace() == NS_USER || $this->mTitle->getNamespace() == NS_USER_TALK ) {
- $rootUser = strtok( $this->mTitle->getText(), '/' );
- $id = User::idFromName( $rootUser );
- $ip = User::isIP( $rootUser );
+ $user = $this->getRelevantUser();
+ if ( $user ) {
+ $id = $user->getID();
+ $ip = $user->isAnon();
+ $rootUser = $user->getName();
} else {
$id = 0;
$ip = false;
+ $rootUser = null;
}
if( $id || $ip ) { # both anons and non-anons have contribs list
@@ -1022,7 +1255,7 @@ class SkinTemplate extends Skin {
if ( $wgUser->isAllowed( 'block' ) ) {
$nav_urls['blockip'] = array(
- 'href' => self::makeSpecialUrlSubpage( 'Blockip', $rootUser )
+ 'href' => self::makeSpecialUrlSubpage( 'Block', $rootUser )
);
} else {
$nav_urls['blockip'] = false;
@@ -1048,11 +1281,12 @@ class SkinTemplate extends Skin {
* @private
*/
function getNameSpaceKey() {
- return $this->mTitle->getNamespaceKey();
+ return $this->getTitle()->getNamespaceKey();
}
/**
* @private
+ * @todo FIXME: Why is this duplicated in/from OutputPage::getHeadScripts()??
*/
function setupUserJs( $allowUserJs ) {
global $wgRequest, $wgJsMimeType;
@@ -1061,7 +1295,7 @@ class SkinTemplate extends Skin {
$action = $wgRequest->getVal( 'action', 'view' );
if( $allowUserJs && $this->loggedin ) {
- if( $this->mTitle->isJsSubpage() and $this->userCanPreview( $action ) ) {
+ if( $this->getTitle()->isJsSubpage() and $this->userCanPreview( $action ) ) {
# XXX: additional security check/prompt?
$this->userjsprev = '/*<![CDATA[*/ ' . $wgRequest->getText( 'wpTextbox1' ) . ' /*]]>*/';
} else {
@@ -1191,9 +1425,528 @@ abstract class QuickTemplate {
/**
* @private
+ *
+ * @return bool
*/
function haveMsg( $str ) {
$msg = $this->translator->translate( $str );
return ( $msg != '-' ) && ( $msg != '' ); # ????
}
+
+ /**
+ * Get the Skin object related to this object
+ *
+ * @return Skin object
+ */
+ public function getSkin() {
+ return $this->data['skin'];
+ }
+}
+
+/**
+ * New base template for a skin's template extended from QuickTemplate
+ * this class features helper methods that provide common ways of interacting
+ * with the data stored in the QuickTemplate
+ */
+abstract class BaseTemplate extends QuickTemplate {
+
+ /**
+ * Create an array of common toolbox items from the data in the quicktemplate
+ * stored by SkinTemplate.
+ * The resulting array is built acording to a format intended to be passed
+ * through makeListItem to generate the html.
+ */
+ function getToolbox() {
+ wfProfileIn( __METHOD__ );
+
+ $toolbox = array();
+ if ( $this->data['notspecialpage'] ) {
+ $toolbox['whatlinkshere'] = $this->data['nav_urls']['whatlinkshere'];
+ $toolbox['whatlinkshere']['id'] = 't-whatlinkshere';
+ if ( $this->data['nav_urls']['recentchangeslinked'] ) {
+ $toolbox['recentchangeslinked'] = $this->data['nav_urls']['recentchangeslinked'];
+ $toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
+ $toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
+ }
+ }
+ if( isset( $this->data['nav_urls']['trackbacklink'] ) && $this->data['nav_urls']['trackbacklink'] ) {
+ $toolbox['trackbacklink'] = $this->data['nav_urls']['trackbacklink'];
+ $toolbox['trackbacklink']['id'] = 't-trackbacklink';
+ }
+ if ( $this->data['feeds'] ) {
+ $toolbox['feeds']['id'] = 'feedlinks';
+ $toolbox['feeds']['links'] = array();
+ foreach ( $this->data['feeds'] as $key => $feed ) {
+ $toolbox['feeds']['links'][$key] = $feed;
+ $toolbox['feeds']['links'][$key]['id'] = "feed-$key";
+ $toolbox['feeds']['links'][$key]['rel'] = 'alternate';
+ $toolbox['feeds']['links'][$key]['type'] = "application/{$key}+xml";
+ $toolbox['feeds']['links'][$key]['class'] = 'feedlink';
+ }
+ }
+ foreach ( array( 'contributions', 'log', 'blockip', 'emailuser', 'upload', 'specialpages' ) as $special ) {
+ if ( $this->data['nav_urls'][$special] ) {
+ $toolbox[$special] = $this->data['nav_urls'][$special];
+ $toolbox[$special]['id'] = "t-$special";
+ }
+ }
+ if ( !empty( $this->data['nav_urls']['print']['href'] ) ) {
+ $toolbox['print'] = $this->data['nav_urls']['print'];
+ $toolbox['print']['rel'] = 'alternate';
+ $toolbox['print']['msg'] = 'printableversion';
+ }
+ if( $this->data['nav_urls']['permalink'] ) {
+ $toolbox['permalink'] = $this->data['nav_urls']['permalink'];
+ if( $toolbox['permalink']['href'] === '' ) {
+ unset( $toolbox['permalink']['href'] );
+ $toolbox['ispermalink']['tooltiponly'] = true;
+ $toolbox['ispermalink']['id'] = 't-ispermalink';
+ $toolbox['ispermalink']['msg'] = 'permalink';
+ } else {
+ $toolbox['permalink']['id'] = 't-permalink';
+ }
+ }
+ wfRunHooks( 'BaseTemplateToolbox', array( &$this, &$toolbox ) );
+ wfProfileOut( __METHOD__ );
+ return $toolbox;
+ }
+
+ /**
+ * Create an array of personal tools items from the data in the quicktemplate
+ * stored by SkinTemplate.
+ * The resulting array is built acording to a format intended to be passed
+ * through makeListItem to generate the html.
+ * This is in reality the same list as already stored in personal_urls
+ * however it is reformatted so that you can just pass the individual items
+ * to makeListItem instead of hardcoding the element creation boilerplate.
+ */
+ function getPersonalTools() {
+ $personal_tools = array();
+ foreach( $this->data['personal_urls'] as $key => $ptool ) {
+ # The class on a personal_urls item is meant to go on the <a> instead
+ # of the <li> so we have to use a single item "links" array instead
+ # of using most of the personal_url's keys directly
+ $personal_tools[$key] = array();
+ $personal_tools[$key]["links"][] = array();
+ $personal_tools[$key]["links"][0]["single-id"] = $personal_tools[$key]["id"] = "pt-$key";
+ if ( isset($ptool["active"]) ) {
+ $personal_tools[$key]["active"] = $ptool["active"];
+ }
+ foreach ( array("href", "class", "text") as $k ) {
+ if ( isset($ptool[$k]) )
+ $personal_tools[$key]["links"][0][$k] = $ptool[$k];
+ }
+ }
+ return $personal_tools;
+ }
+
+ function getSidebar( $options = array() ) {
+ // Force the rendering of the following portals
+ $sidebar = $this->data['sidebar'];
+ if ( !isset( $sidebar['SEARCH'] ) ) {
+ $sidebar['SEARCH'] = true;
+ }
+ if ( !isset( $sidebar['TOOLBOX'] ) ) {
+ $sidebar['TOOLBOX'] = true;
+ }
+ if ( !isset( $sidebar['LANGUAGES'] ) ) {
+ $sidebar['LANGUAGES'] = true;
+ }
+
+ if ( !isset( $options['search'] ) || $options['search'] !== true ) {
+ unset( $sidebar['SEARCH'] );
+ }
+ if ( isset( $options['toolbox'] ) && $options['toolbox'] === false ) {
+ unset( $sidebar['TOOLBOX'] );
+ }
+ if ( isset( $options['languages'] ) && $options['languages'] === false ) {
+ unset( $sidebar['LANGUAGES'] );
+ }
+
+ $boxes = array();
+ foreach ( $sidebar as $boxName => $content ) {
+ if ( $content === false ) {
+ continue;
+ }
+ switch ( $boxName ) {
+ case 'SEARCH':
+ // Search is a special case, skins should custom implement this
+ $boxes[$boxName] = array(
+ 'id' => "p-search",
+ 'header' => wfMessage( 'search' )->text(),
+ 'generated' => false,
+ 'content' => true,
+ );
+ break;
+ case 'TOOLBOX':
+ $msgObj = wfMessage( 'toolbox' );
+ $boxes[$boxName] = array(
+ 'id' => "p-tb",
+ 'header' => $msgObj->exists() ? $msgObj->text() : 'toolbox',
+ 'generated' => false,
+ 'content' => $this->getToolbox(),
+ );
+ break;
+ case 'LANGUAGES':
+ if ( $this->data['language_urls'] ) {
+ $msgObj = wfMessage( 'otherlanguages' );
+ $boxes[$boxName] = array(
+ 'id' => "p-lang",
+ 'header' => $msgObj->exists() ? $msgObj->text() : 'otherlanguages',
+ 'generated' => false,
+ 'content' => $this->data['language_urls'],
+ );
+ }
+ break;
+ default:
+ $msgObj = wfMessage( $boxName );
+ $boxes[$boxName] = array(
+ 'id' => "p-$boxName",
+ 'header' => $msgObj->exists() ? $msgObj->text() : $boxName,
+ 'generated' => true,
+ 'content' => $content,
+ );
+ break;
+ }
+ }
+
+ // HACK: Compatibility with extensions still using SkinTemplateToolboxEnd
+ $hookContents = null;
+ if ( isset( $boxes['TOOLBOX'] ) ) {
+ ob_start();
+ // We pass an extra 'true' at the end so extensions using BaseTemplateToolbox
+ // can abort and avoid outputting double toolbox links
+ wfRunHooks( 'SkinTemplateToolboxEnd', array( &$this, true ) );
+ $hookContents = ob_get_contents();
+ ob_end_clean();
+ if ( !trim( $hookContents ) ) {
+ $hookContents = null;
+ }
+ }
+ // END hack
+
+ if ( isset( $options['htmlOnly'] ) && $options['htmlOnly'] === true ) {
+ foreach ( $boxes as $boxName => $box ) {
+ if ( is_array( $box['content'] ) ) {
+ $content = "<ul>";
+ foreach ( $box['content'] as $key => $val ) {
+ $content .= "\n " . $this->makeListItem( $key, $val );
+ }
+ // HACK, shove the toolbox end onto the toolbox if we're rendering itself
+ if ( $hookContents ) {
+ $content .= "\n $hookContents";
+ }
+ // END hack
+ $content .= "\n</ul>\n";
+ $boxes[$boxName]['content'] = $content;
+ }
+ }
+ } else {
+ if ( $hookContents ) {
+ $boxes['TOOLBOXEND'] = array(
+ 'id' => "p-toolboxend",
+ 'header' => $boxes['TOOLBOX']['header'],
+ 'generated' => false,
+ 'content' => "<ul>{$hookContents}</ul>",
+ );
+ // HACK: Make sure that TOOLBOXEND is sorted next to TOOLBOX
+ $boxes2 = array();
+ foreach ( $boxes as $key => $box ) {
+ if ( $key === 'TOOLBOXEND' ) {
+ continue;
+ }
+ $boxes2[$key] = $box;
+ if ( $key === 'TOOLBOX' ) {
+ $boxes2['TOOLBOXEND'] = $boxes['TOOLBOXEND'];
+ }
+ }
+ $boxes = $boxes2;
+ // END hack
+ }
+ }
+
+ return $boxes;
+ }
+
+ /**
+ * Makes a link, usually used by makeListItem to generate a link for an item
+ * in a list used in navigation lists, portlets, portals, sidebars, etc...
+ *
+ * $key is a string, usually a key from the list you are generating this link from
+ * $item is an array containing some of a specific set of keys.
+ * The text of the link will be generated either from the contents of the "text"
+ * key in the $item array, if a "msg" key is present a message by that name will
+ * be used, and if neither of those are set the $key will be used as a message name.
+ * If a "href" key is not present makeLink will just output htmlescaped text.
+ * The href, id, class, rel, and type keys are used as attributes for the link if present.
+ * If an "id" or "single-id" (if you don't want the actual id to be output on the link)
+ * is present it will be used to generate a tooltip and accesskey for the link.
+ * If you don't want an accesskey, set $item['tooltiponly'] = true;
+ * $options can be used to affect the output of a link:
+ * You can use a text-wrapper key to specify a list of elements to wrap the
+ * text of a link in. This should be an array of arrays containing a 'tag' and
+ * optionally an 'attributes' key. If you only have one element you don't need
+ * to wrap it in another array. eg: To use <a><span>...</span></a> in all links
+ * use array( 'text-wrapper' => array( 'tag' => 'span' ) ) for your options.
+ * A link-class key can be used to specify additional classes to apply to all links.
+ * A link-fallback can be used to specify a tag to use instead of <a> if there is
+ * no link. eg: If you specify 'link-fallback' => 'span' than any non-link will
+ * output a <span> instead of just text.
+ */
+ function makeLink( $key, $item, $options = array() ) {
+ if ( isset( $item['text'] ) ) {
+ $text = $item['text'];
+ } else {
+ $text = $this->translator->translate( isset( $item['msg'] ) ? $item['msg'] : $key );
+ }
+
+ $html = htmlspecialchars( $text );
+
+ if ( isset( $options['text-wrapper'] ) ) {
+ $wrapper = $options['text-wrapper'];
+ if ( isset( $wrapper['tag'] ) ) {
+ $wrapper = array( $wrapper );
+ }
+ while ( count( $wrapper ) > 0 ) {
+ $element = array_pop( $wrapper );
+ $html = Html::rawElement( $element['tag'], isset( $element['attributes'] ) ? $element['attributes'] : null, $html );
+ }
+ }
+
+ if ( isset( $item['href'] ) || isset( $options['link-fallback'] ) ) {
+ $attrs = $item;
+ foreach ( array( 'single-id', 'text', 'msg', 'tooltiponly' ) as $k ) {
+ unset( $attrs[$k] );
+ }
+
+ if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
+ $item['single-id'] = $item['id'];
+ }
+ if ( isset( $item['single-id'] ) ) {
+ if ( isset( $item['tooltiponly'] ) && $item['tooltiponly'] ) {
+ $title = Linker::titleAttrib( $item['single-id'] );
+ if ( $title !== false ) {
+ $attrs['title'] = $title;
+ }
+ } else {
+ $tip = Linker::tooltipAndAccesskeyAttribs( $item['single-id'] );
+ if ( isset( $tip['title'] ) && $tip['title'] !== false ) {
+ $attrs['title'] = $tip['title'];
+ }
+ if ( isset( $tip['accesskey'] ) && $tip['accesskey'] !== false ) {
+ $attrs['accesskey'] = $tip['accesskey'];
+ }
+ }
+ }
+ if ( isset( $options['link-class'] ) ) {
+ if ( isset( $attrs['class'] ) ) {
+ $attrs['class'] .= " {$options['link-class']}";
+ } else {
+ $attrs['class'] = $options['link-class'];
+ }
+ }
+ $html = Html::rawElement( isset( $attrs['href'] ) ? 'a' : $options['link-fallback'], $attrs, $html );
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generates a list item for a navigation, portlet, portal, sidebar... etc list
+ * $key is a string, usually a key from the list you are generating this link from
+ * $item is an array of list item data containing some of a specific set of keys.
+ * The "id" and "class" keys will be used as attributes for the list item,
+ * if "active" contains a value of true a "active" class will also be appended to class.
+ * If you want something other than a <li> you can pass a tag name such as
+ * "tag" => "span" in the $options array to change the tag used.
+ * link/content data for the list item may come in one of two forms
+ * A "links" key may be used, in which case it should contain an array with
+ * a list of links to include inside the list item, see makeLink for the format
+ * of individual links array items.
+ * Otherwise the relevant keys from the list item $item array will be passed
+ * to makeLink instead. Note however that "id" and "class" are used by the
+ * list item directly so they will not be passed to makeLink
+ * (however the link will still support a tooltip and accesskey from it)
+ * If you need an id or class on a single link you should include a "links"
+ * array with just one link item inside of it.
+ * $options is also passed on to makeLink calls
+ */
+ function makeListItem( $key, $item, $options = array() ) {
+ if ( isset( $item['links'] ) ) {
+ $html = '';
+ foreach ( $item['links'] as $linkKey => $link ) {
+ $html .= $this->makeLink( $linkKey, $link, $options );
+ }
+ } else {
+ $link = $item;
+ // These keys are used by makeListItem and shouldn't be passed on to the link
+ foreach ( array( 'id', 'class', 'active', 'tag' ) as $k ) {
+ unset( $link[$k] );
+ }
+ if ( isset( $item['id'] ) ) {
+ // The id goes on the <li> not on the <a> for single links
+ // but makeSidebarLink still needs to know what id to use when
+ // generating tooltips and accesskeys.
+ $link['single-id'] = $item['id'];
+ }
+ $html = $this->makeLink( $key, $link, $options );
+ }
+
+ $attrs = array();
+ foreach ( array( 'id', 'class' ) as $attr ) {
+ if ( isset( $item[$attr] ) ) {
+ $attrs[$attr] = $item[$attr];
+ }
+ }
+ if ( isset( $item['active'] ) && $item['active'] ) {
+ if ( !isset( $attrs['class'] ) ) {
+ $attrs['class'] = '';
+ }
+ $attrs['class'] .= ' active';
+ $attrs['class'] = trim( $attrs['class'] );
+ }
+ return Html::rawElement( isset( $options['tag'] ) ? $options['tag'] : 'li', $attrs, $html );
+ }
+
+ function makeSearchInput( $attrs = array() ) {
+ $realAttrs = array(
+ 'type' => 'search',
+ 'name' => 'search',
+ 'value' => isset( $this->data['search'] ) ? $this->data['search'] : '',
+ );
+ $realAttrs = array_merge( $realAttrs, Linker::tooltipAndAccesskeyAttribs( 'search' ), $attrs );
+ return Html::element( 'input', $realAttrs );
+ }
+
+ function makeSearchButton( $mode, $attrs = array() ) {
+ switch( $mode ) {
+ case 'go':
+ case 'fulltext':
+ $realAttrs = array(
+ 'type' => 'submit',
+ 'name' => $mode,
+ 'value' => $this->translator->translate(
+ $mode == 'go' ? 'searcharticle' : 'searchbutton' ),
+ );
+ $realAttrs = array_merge(
+ $realAttrs,
+ Linker::tooltipAndAccesskeyAttribs( "search-$mode" ),
+ $attrs
+ );
+ return Html::element( 'input', $realAttrs );
+ case 'image':
+ $buttonAttrs = array(
+ 'type' => 'submit',
+ 'name' => 'button',
+ );
+ $buttonAttrs = array_merge(
+ $buttonAttrs,
+ Linker::tooltipAndAccesskeyAttribs( 'search-fulltext' ),
+ $attrs
+ );
+ unset( $buttonAttrs['src'] );
+ unset( $buttonAttrs['alt'] );
+ $imgAttrs = array(
+ 'src' => $attrs['src'],
+ 'alt' => isset( $attrs['alt'] )
+ ? $attrs['alt']
+ : $this->translator->translate( 'searchbutton' ),
+ );
+ return Html::rawElement( 'button', $buttonAttrs, Html::element( 'img', $imgAttrs ) );
+ default:
+ throw new MWException( 'Unknown mode passed to BaseTemplate::makeSearchButton' );
+ }
+ }
+
+ /**
+ * Returns an array of footerlinks trimmed down to only those footer links that
+ * are valid.
+ * If you pass "flat" as an option then the returned array will be a flat array
+ * of footer icons instead of a key/value array of footerlinks arrays broken
+ * up into categories.
+ */
+ function getFooterLinks( $option = null ) {
+ $footerlinks = $this->data['footerlinks'];
+
+ // Reduce footer links down to only those which are being used
+ $validFooterLinks = array();
+ foreach( $footerlinks as $category => $links ) {
+ $validFooterLinks[$category] = array();
+ foreach( $links as $link ) {
+ if( isset( $this->data[$link] ) && $this->data[$link] ) {
+ $validFooterLinks[$category][] = $link;
+ }
+ }
+ if ( count( $validFooterLinks[$category] ) <= 0 ) {
+ unset( $validFooterLinks[$category] );
+ }
+ }
+
+ if ( $option == 'flat' ) {
+ // fold footerlinks into a single array using a bit of trickery
+ $validFooterLinks = call_user_func_array(
+ 'array_merge',
+ array_values( $validFooterLinks )
+ );
+ }
+
+ return $validFooterLinks;
+ }
+
+ /**
+ * Returns an array of footer icons filtered down by options relevant to how
+ * the skin wishes to display them.
+ * If you pass "icononly" as the option all footer icons which do not have an
+ * image icon set will be filtered out.
+ * If you pass "nocopyright" then MediaWiki's copyright icon will not be included
+ * in the list of footer icons. This is mostly useful for skins which only
+ * display the text from footericons instead of the images and don't want a
+ * duplicate copyright statement because footerlinks already rendered one.
+ */
+ function getFooterIcons( $option = null ) {
+ // Generate additional footer icons
+ $footericons = $this->data['footericons'];
+
+ if ( $option == 'icononly' ) {
+ // Unset any icons which don't have an image
+ foreach ( $footericons as &$footerIconsBlock ) {
+ foreach ( $footerIconsBlock as $footerIconKey => $footerIcon ) {
+ if ( !is_string( $footerIcon ) && !isset( $footerIcon['src'] ) ) {
+ unset( $footerIconsBlock[$footerIconKey] );
+ }
+ }
+ }
+ // Redo removal of any empty blocks
+ foreach ( $footericons as $footerIconsKey => &$footerIconsBlock ) {
+ if ( count( $footerIconsBlock ) <= 0 ) {
+ unset( $footericons[$footerIconsKey] );
+ }
+ }
+ } elseif ( $option == 'nocopyright' ) {
+ unset( $footericons['copyright']['copyright'] );
+ if ( count( $footericons['copyright'] ) <= 0 ) {
+ unset( $footericons['copyright'] );
+ }
+ }
+
+ return $footericons;
+ }
+
+ /**
+ * Output the basic end-page trail including bottomscripts, reporttime, and
+ * debug stuff. This should be called right before outputting the closing
+ * body and html tags.
+ */
+ function printTrail() { ?>
+<?php $this->html('bottomscripts'); /* JS call to runBodyOnloadHook */ ?>
+<?php $this->html('reporttime') ?>
+<?php if ( $this->data['debug'] ): ?>
+<!-- Debug output:
+<?php $this->text( 'debug' ); ?>
+
+-->
+<?php endif;
+ }
+
}
+
diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php
index 12ad517a..0b21326a 100644
--- a/includes/SpecialPage.php
+++ b/includes/SpecialPage.php
@@ -28,235 +28,53 @@
* @ingroup SpecialPage
*/
class SpecialPage {
- /**#@+
- * @access private
- */
- /**
- * The canonical name of this special page
- * Also used for the default <h1> heading, @see getDescription()
- */
- var $mName;
- /**
- * The local name of this special page
- */
- var $mLocalName;
- /**
- * Minimum user level required to access this page, or "" for anyone.
- * Also used to categorise the pages in Special:Specialpages
- */
- var $mRestriction;
- /**
- * Listed in Special:Specialpages?
- */
- var $mListed;
- /**
- * Function name called by the default execute()
- */
- var $mFunction;
- /**
- * File which needs to be included before the function above can be called
- */
- var $mFile;
- /**
- * Whether or not this special page is being included from an article
- */
- var $mIncluding;
- /**
- * Whether the special page can be included in an article
- */
- var $mIncludable;
- /**
- * Query parameters that can be passed through redirects
- */
- 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:
- ** array( 'SpecialPage', name, right )
- ** array( 'IncludableSpecialPage', name, right, listed? )
- ** array( 'UnlistedSpecialPage', name, right )
- ** array( 'SpecialRedirectToSpecial', name, page to redirect to, special page param, ... )
- */
- static public $mList = array(
- # Maintenance Reports
- 'BrokenRedirects' => array( 'SpecialPage', 'BrokenRedirects' ),
- 'Deadendpages' => array( 'SpecialPage', 'Deadendpages' ),
- 'DoubleRedirects' => array( 'SpecialPage', 'DoubleRedirects' ),
- 'Longpages' => array( 'SpecialPage', 'Longpages' ),
- 'Ancientpages' => array( 'SpecialPage', 'Ancientpages' ),
- 'Lonelypages' => array( 'SpecialPage', 'Lonelypages' ),
- 'Fewestrevisions' => array( 'SpecialPage', 'Fewestrevisions' ),
- 'Withoutinterwiki' => array( 'SpecialPage', 'Withoutinterwiki' ),
- 'Protectedpages' => 'SpecialProtectedpages',
- 'Protectedtitles' => 'SpecialProtectedtitles',
- 'Shortpages' => array( 'SpecialPage', 'Shortpages' ),
- 'Uncategorizedcategories' => array( 'SpecialPage', 'Uncategorizedcategories' ),
- 'Uncategorizedimages' => array( 'SpecialPage', 'Uncategorizedimages' ),
- 'Uncategorizedpages' => array( 'SpecialPage', 'Uncategorizedpages' ),
- 'Uncategorizedtemplates' => array( 'SpecialPage', 'Uncategorizedtemplates' ),
- 'Unusedcategories' => array( 'SpecialPage', 'Unusedcategories' ),
- 'Unusedimages' => array( 'SpecialPage', 'Unusedimages' ),
- 'Unusedtemplates' => array( 'SpecialPage', 'Unusedtemplates' ),
- 'Unwatchedpages' => array( 'SpecialPage', 'Unwatchedpages', 'unwatchedpages' ),
- 'Wantedcategories' => array( 'SpecialPage', 'Wantedcategories' ),
- 'Wantedfiles' => array( 'SpecialPage', 'Wantedfiles' ),
- 'Wantedpages' => array( 'IncludableSpecialPage', 'Wantedpages' ),
- 'Wantedtemplates' => array( 'SpecialPage', 'Wantedtemplates' ),
-
- # List of pages
- 'Allpages' => 'SpecialAllpages',
- 'Prefixindex' => 'SpecialPrefixindex',
- 'Categories' => 'SpecialCategories',
- 'Disambiguations' => array( 'SpecialPage', 'Disambiguations' ),
- 'Listredirects' => array( 'SpecialPage', 'Listredirects' ),
-
- # Login/create account
- 'Userlogin' => array( 'SpecialPage', 'Userlogin' ),
- 'CreateAccount' => array( 'SpecialRedirectToSpecial', 'CreateAccount', 'Userlogin', 'signup', array( 'uselang' ) ),
-
- # Users and rights
- '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' => 'SpecialLog',
- 'Watchlist' => array( 'SpecialPage', 'Watchlist' ),
- 'Newpages' => 'SpecialNewpages',
- 'Recentchanges' => 'SpecialRecentchanges',
- 'Recentchangeslinked' => 'SpecialRecentchangeslinked',
- 'Tags' => 'SpecialTags',
-
- # Media reports and uploads
- 'Listfiles' => array( 'SpecialPage', 'Listfiles' ),
- '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' => 'SpecialLockdb',
- 'Unlockdb' => 'SpecialUnlockdb',
-
- # Redirecting special pages
- 'LinkSearch' => array( 'SpecialPage', 'LinkSearch' ),
- 'Randompage' => 'Randompage',
- 'Randomredirect' => 'SpecialRandomredirect',
-
- # High use pages
- 'Mostlinkedcategories' => array( 'SpecialPage', 'Mostlinkedcategories' ),
- 'Mostimages' => array( 'SpecialPage', 'Mostimages' ),
- 'Mostlinked' => array( 'SpecialPage', 'Mostlinked' ),
- 'Mostlinkedtemplates' => array( 'SpecialPage', 'Mostlinkedtemplates' ),
- 'Mostcategories' => array( 'SpecialPage', 'Mostcategories' ),
- 'Mostrevisions' => array( 'SpecialPage', 'Mostrevisions' ),
-
- # Page tools
- 'ComparePages' => 'SpecialComparePages',
- 'Export' => 'SpecialExport',
- 'Import' => 'SpecialImport',
- 'Undelete' => 'UndeleteForm',
- 'Whatlinkshere' => 'SpecialWhatlinkshere',
- 'MergeHistory' => 'SpecialMergeHistory',
-
- # Other
- 'Booksources' => 'SpecialBookSources',
-
- # Unlisted / redirects
- 'Blankpage' => 'SpecialBlankpage',
- 'Blockme' => 'SpecialBlockme',
- 'Emailuser' => 'SpecialEmailUser',
- 'Movepage' => 'MovePageForm',
- 'Mycontributions' => 'SpecialMycontributions',
- 'Mypage' => 'SpecialMypage',
- 'Mytalk' => 'SpecialMytalk',
- 'Myuploads' => 'SpecialMyuploads',
- 'Revisiondelete' => 'SpecialRevisionDelete',
- 'Specialpages' => 'SpecialSpecialpages',
- 'Userlogout' => 'SpecialUserlogout',
- );
-
- static public $mAliases;
- static public $mListInitialised = false;
-
- /**#@-*/
- /**
- * Initialise the special page list
- * This must be called before accessing SpecialPage::$mList
- */
- static function initList() {
- global $wgSpecialPages;
- global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication;
+ // The canonical name of this special page
+ // Also used for the default <h1> heading, @see getDescription()
+ /*private*/ var $mName;
- if ( self::$mListInitialised ) {
- return;
- }
- wfProfileIn( __METHOD__ );
+ // The local name of this special page
+ private $mLocalName;
- # Better to set this now, to avoid infinite recursion in carelessly written hooks
- self::$mListInitialised = true;
+ // Minimum user level required to access this page, or "" for anyone.
+ // Also used to categorise the pages in Special:Specialpages
+ private $mRestriction;
- if( !$wgDisableCounters ) {
- self::$mList['Popularpages'] = array( 'SpecialPage', 'Popularpages' );
- }
+ // Listed in Special:Specialpages?
+ private $mListed;
- if( !$wgDisableInternalSearch ) {
- self::$mList['Search'] = array( 'SpecialPage', 'Search' );
- }
+ // Function name called by the default execute()
+ private $mFunction;
- if( $wgEmailAuthentication ) {
- self::$mList['Confirmemail'] = 'EmailConfirmation';
- self::$mList['Invalidateemail'] = 'EmailInvalidation';
- }
+ // File which needs to be included before the function above can be called
+ private $mFile;
- # Add extension special pages
- self::$mList = array_merge( self::$mList, $wgSpecialPages );
+ // Whether or not this special page is being included from an article
+ protected $mIncluding;
- # Run hooks
- # This hook can be used to remove undesired built-in special pages
- wfRunHooks( 'SpecialPage_initList', array( &self::$mList ) );
- wfProfileOut( __METHOD__ );
+ // Whether the special page can be included in an article
+ protected $mIncludable;
+
+ /**
+ * Current request context
+ * @var IContextSource
+ */
+ protected $mContext;
+
+ /**
+ * Initialise the special page list
+ * This must be called before accessing SpecialPage::$mList
+ * @deprecated since 1.18
+ */
+ static function initList() {
+ // Noop
}
+ /**
+ * @deprecated since 1.18
+ */
static function initAliasList() {
- if ( !is_null( self::$mAliases ) ) {
- return;
- }
-
- global $wgContLang;
- $aliases = $wgContLang->getSpecialPageAliases();
- $missingPages = self::$mList;
- self::$mAliases = array();
- foreach ( $aliases as $realName => $aliasList ) {
- foreach ( $aliasList as $alias ) {
- self::$mAliases[$wgContLang->caseFold( $alias )] = $realName;
- }
- unset( $missingPages[$realName] );
- }
- foreach ( $missingPages as $name => $stuff ) {
- self::$mAliases[$wgContLang->caseFold( $name )] = $name;
- }
+ // Noop
}
/**
@@ -265,19 +83,11 @@ class SpecialPage {
*
* @param $alias String
* @return String or false
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function resolveAlias( $alias ) {
- global $wgContLang;
-
- 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 {
- return false;
- }
+ list( $name, /*...*/ ) = SpecialPageFactory::resolveAlias( $alias );
+ return $name;
}
/**
@@ -287,16 +97,10 @@ class SpecialPage {
*
* @param $alias String
* @return Array
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function resolveAliasWithSubpage( $alias ) {
- $bits = explode( '/', $alias, 2 );
- $name = self::resolveAlias( $bits[0] );
- if( !isset( $bits[1] ) ) { // bug 2087
- $par = null;
- } else {
- $par = $bits[1];
- }
- return array( $name, $par );
+ return SpecialPageFactory::resolveAlias( $alias );
}
/**
@@ -305,14 +109,11 @@ class SpecialPage {
* an associative record to $wgSpecialPages. This avoids autoloading SpecialPage.
*
* @param $page SpecialPage
- * Deprecated in 1.7, warnings in 1.17, might be removed in 1.20
+ * @deprecated since 1.7, warnings in 1.17, might be removed in 1.20
*/
static function addPage( &$page ) {
wfDeprecated( __METHOD__ );
- if ( !self::$mListInitialised ) {
- self::initList();
- }
- self::$mList[$page->mName] = $page;
+ SpecialPageFactory::getList()->{$page->mName} = $page;
}
/**
@@ -320,46 +121,34 @@ class SpecialPage {
*
* @param $page Mixed: SpecialPage or string
* @param $group String
+ * @return null
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function setGroup( $page, $group ) {
- global $wgSpecialPageGroups;
- $name = is_object($page) ? $page->mName : $page;
- $wgSpecialPageGroups[$name] = $group;
+ return SpecialPageFactory::setGroup( $page, $group );
}
/**
- * Add a page to a certain display group for Special:SpecialPages
+ * Get the group that the special page belongs in on Special:SpecialPage
*
* @param $page SpecialPage
+ * @return null
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getGroup( &$page ) {
- global $wgSpecialPageGroups;
- static $specialPageGroupsCache = array();
- if( isset($specialPageGroupsCache[$page->mName]) ) {
- return $specialPageGroupsCache[$page->mName];
- }
- $group = wfMsg('specialpages-specialpagegroup-'.strtolower($page->mName));
- if( $group == ''
- || wfEmptyMsg('specialpages-specialpagegroup-'.strtolower($page->mName), $group ) ) {
- $group = isset($wgSpecialPageGroups[$page->mName])
- ? $wgSpecialPageGroups[$page->mName]
- : '-';
- }
- if( $group == '-' ) $group = 'other';
- $specialPageGroupsCache[$page->mName] = $group;
- return $group;
+ return SpecialPageFactory::getGroup( $page );
}
/**
* Remove a special page from the list
* Formerly used to disable expensive or dangerous special pages. The
* preferred method is now to add a SpecialPage_initList hook.
+ * @deprecated since 1.18
+ *
+ * @param $name String the page to remove
*/
static function removePage( $name ) {
- if ( !self::$mListInitialised ) {
- self::initList();
- }
- unset( self::$mList[$name] );
+ unset( SpecialPageFactory::getList()->$name );
}
/**
@@ -367,24 +156,10 @@ class SpecialPage {
*
* @param $name String: name of a special page
* @return Boolean: true if a special page exists with this name
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function exists( $name ) {
- global $wgContLang;
- if ( !self::$mListInitialised ) {
- self::initList();
- }
- if( !self::$mAliases ) {
- self::initAliasList();
- }
-
- # Remove special pages inline parameters:
- $bits = explode( '/', $name );
- $name = $wgContLang->caseFold($bits[0]);
-
- return
- array_key_exists( $name, self::$mList )
- or array_key_exists( $name, self::$mAliases )
- ;
+ return SpecialPageFactory::exists( $name );
}
/**
@@ -392,39 +167,22 @@ class SpecialPage {
*
* @param $name String
* @return SpecialPage object or null if the page doesn't exist
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getPage( $name ) {
- if ( !self::$mListInitialised ) {
- self::initList();
- }
- if ( array_key_exists( $name, self::$mList ) ) {
- $rec = self::$mList[$name];
- if ( is_string( $rec ) ) {
- $className = $rec;
- self::$mList[$name] = new $className;
- } elseif ( is_array( $rec ) ) {
- $className = array_shift( $rec );
- self::$mList[$name] = wfCreateObject( $className, $rec );
- }
- return self::$mList[$name];
- } else {
- return null;
- }
+ return SpecialPageFactory::getPage( $name );
}
/**
* Get a special page with a given localised name, or NULL if there
* is no such special page.
*
+ * @param $alias String
* @return SpecialPage object or null if the page doesn't exist
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getPageByAlias( $alias ) {
- $realName = self::resolveAlias( $alias );
- if ( $realName ) {
- return self::getPage( $realName );
- } else {
- return null;
- }
+ return SpecialPageFactory::getPage( $alias );
}
/**
@@ -432,46 +190,20 @@ class SpecialPage {
* for the current user, and everyone.
*
* @return Associative array mapping page's name to its SpecialPage object
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getUsablePages() {
- global $wgUser;
- if ( !self::$mListInitialised ) {
- self::initList();
- }
- $pages = array();
-
- foreach ( self::$mList as $name => $rec ) {
- $page = self::getPage( $name );
- if ( $page->isListed()
- && (
- !$page->isRestricted()
- || $page->userCanExecute( $wgUser )
- )
- ) {
- $pages[$name] = $page;
- }
- }
- return $pages;
+ return SpecialPageFactory::getUsablePages();
}
/**
* Return categorised listable special pages for all users
*
* @return Associative array mapping page's name to its SpecialPage object
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getRegularPages() {
- if ( !self::$mListInitialised ) {
- self::initList();
- }
- $pages = array();
-
- foreach ( self::$mList as $name => $rec ) {
- $page = self::getPage( $name );
- if ( $page->isListed() && !$page->isRestricted() ) {
- $pages[$name] = $page;
- }
- }
- return $pages;
+ return SpecialPageFactory::getRegularPages();
}
/**
@@ -479,106 +211,28 @@ class SpecialPage {
* for the current user, but not for everyone
*
* @return Associative array mapping page's name to its SpecialPage object
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getRestrictedPages() {
- global $wgUser;
- if( !self::$mListInitialised ) {
- self::initList();
- }
- $pages = array();
-
- foreach( self::$mList as $name => $rec ) {
- $page = self::getPage( $name );
- if(
- $page->isListed()
- && $page->isRestricted()
- && $page->userCanExecute( $wgUser )
- ) {
- $pages[$name] = $page;
- }
- }
- return $pages;
+ return SpecialPageFactory::getRestrictedPages();
}
/**
* Execute a special page path.
- * The path may contain parameters, e.g. Special:Name/Params
+ * The path may contain parameters, e.g. Special:Name/Params
* Extracts the special page name and call the execute method, passing the parameters
*
* Returns a title object if the page is redirected, false if there was no such special
* page, and true if it was successful.
*
- * @param $title a title object
- * @param $including output is being captured for use in {{special:whatever}}
- */
- static function executePath( &$title, $including = false ) {
- global $wgOut, $wgTitle, $wgRequest;
- wfProfileIn( __METHOD__ );
-
- # FIXME: redirects broken due to this call
- $bits = explode( '/', $title->getDBkey(), 2 );
- $name = $bits[0];
- if( !isset( $bits[1] ) ) { // bug 2087
- $par = null;
- } else {
- $par = $bits[1];
- }
- $page = SpecialPage::getPageByAlias( $name );
- # Nonexistent?
- if ( !$page ) {
- if ( !$including ) {
- $wgOut->setArticleRelated( false );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setStatusCode( 404 );
- $wgOut->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
- }
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- # Check for redirect
- if ( !$including ) {
- $redirect = $page->getRedirect( $par );
- if ( $redirect ) {
- $query = $page->getRedirectQuery();
- $url = $redirect->getFullUrl( $query );
- $wgOut->redirect( $url );
- wfProfileOut( __METHOD__ );
- return $redirect;
- }
- }
-
- # Redirect to canonical alias for GET commands
- # Not for POST, we'd lose the post data, so it's best to just distribute
- # the request. Such POST requests are possible for old extensions that
- # generate self-links without being aware that their default name has
- # changed.
- if ( !$including && $name != $page->getLocalName() && !$wgRequest->wasPosted() ) {
- $query = $_GET;
- unset( $query['title'] );
- $query = wfArrayToCGI( $query );
- $title = $page->getTitle( $par );
- $url = $title->getFullUrl( $query );
- $wgOut->redirect( $url );
- wfProfileOut( __METHOD__ );
- return $redirect;
- }
-
- if ( $including && !$page->includable() ) {
- wfProfileOut( __METHOD__ );
- return false;
- } elseif ( !$including ) {
- $wgTitle = $page->getTitle();
- }
- $page->including( $including );
-
- // Execute special page
- $profName = 'Special:' . $page->name();
- wfProfileIn( $profName );
- $page->execute( $par );
- wfProfileOut( $profName );
- wfProfileOut( __METHOD__ );
- return true;
+ * @param $title Title object
+ * @param $context IContextSource
+ * @param $including Bool output is being captured for use in {{special:whatever}}
+ * @return Bool
+ * @deprecated since 1.18 call SpecialPageFactory method directly
+ */
+ public static function executePath( &$title, IContextSource &$context, $including = false ) {
+ return SpecialPageFactory::executePath( $title, $context, $including );
}
/**
@@ -586,23 +240,12 @@ class SpecialPage {
* Returns false if there was no such special page, or a title object if it was
* a redirect.
*
+ * @param $title Title
* @return String: HTML fragment
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function capturePath( &$title ) {
- global $wgOut, $wgTitle;
-
- $oldTitle = $wgTitle;
- $oldOut = $wgOut;
- $wgOut = new OutputPage;
- $wgOut->setTitle( $title );
-
- $ret = SpecialPage::executePath( $title, true );
- if ( $ret === true ) {
- $ret = $wgOut->getHTML();
- }
- $wgTitle = $oldTitle;
- $wgOut = $oldOut;
- return $ret;
+ return SpecialPageFactory::capturePath( $title );
}
/**
@@ -612,42 +255,21 @@ class SpecialPage {
* @param $subpage Mixed: boolean false, or string
*
* @return String
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getLocalNameFor( $name, $subpage = false ) {
- global $wgContLang;
- $aliases = $wgContLang->getSpecialPageAliases();
- if ( isset( $aliases[$name][0] ) ) {
- $name = $aliases[$name][0];
- } else {
- // Try harder in case someone misspelled the correct casing
- $found = false;
- foreach ( $aliases as $n => $values ) {
- if ( strcasecmp( $name, $n ) === 0 ) {
- wfWarn( "Found alias defined for $n when searching for" .
- "special page aliases for $name. Case mismatch?" );
- $name = $values[0];
- $found = true;
- break;
- }
- }
- if ( !$found ) {
- wfWarn( "Did not find alias for special page '$name'. " .
- "Perhaps no aliases are defined for it?" );
- }
- }
- if ( $subpage !== false && !is_null( $subpage ) ) {
- $name = "$name/$subpage";
- }
- return $wgContLang->ucfirst( $name );
+ return SpecialPageFactory::getLocalNameFor( $name, $subpage );
}
/**
* Get a localised Title object for a specified special page name
*
+ * @param $name String
+ * @param $subpage String|Bool subpage string, or false to not use a subpage
* @return Title object
*/
- static function getTitleFor( $name, $subpage = false ) {
- $name = self::getLocalNameFor( $name, $subpage );
+ public static function getTitleFor( $name, $subpage = false ) {
+ $name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
if ( $name ) {
return Title::makeTitle( NS_SPECIAL, $name );
} else {
@@ -658,10 +280,12 @@ class SpecialPage {
/**
* Get a localised Title object for a page name with a possibly unvalidated subpage
*
+ * @param $name String
+ * @param $subpage String|Bool subpage string, or false to not use a subpage
* @return Title object or null if the page doesn't exist
*/
- static function getSafeTitleFor( $name, $subpage = false ) {
- $name = self::getLocalNameFor( $name, $subpage );
+ public static function getSafeTitleFor( $name, $subpage = false ) {
+ $name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
if ( $name ) {
return Title::makeTitleSafe( NS_SPECIAL, $name );
} else {
@@ -672,15 +296,12 @@ class SpecialPage {
/**
* Get a title for a given alias
*
+ * @param $alias String
* @return Title or null if there is no such alias
+ * @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getTitleForAlias( $alias ) {
- $name = self::resolveAlias( $alias );
- if ( $name ) {
- return self::getTitleFor( $name );
- } else {
- return null;
- }
+ return SpecialPageFactory::getTitleForAlias( $alias );
}
/**
@@ -695,19 +316,27 @@ class SpecialPage {
*
* @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 $listed Bool: whether the page is listed in Special:Specialpages
+ * @param $function Callback|Bool: 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
+ * @param $includable Bool: whether the page can be included in normal pages
*/
- public function __construct( $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
+ * @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 Bool: whether the page is listed in Special:Specialpages
+ * @param $function Callback|Bool: 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 Bool: whether the page can be included in normal pages
*/
private function init( $name, $restriction, $listed, $function, $file, $includable ) {
$this->mName = $name;
@@ -730,15 +359,16 @@ class SpecialPage {
* 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 $fName String Name of called method
* @param $a Array Arguments to the method
- * @deprecated Call isn't deprecated, but SpecialPage::SpecialPage() is
+ * @deprecated since 1.17, call parent::__construct()
*/
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" );
+ // Deprecated messages now, remove in 1.18 or 1.20?
+ wfDeprecated( __METHOD__ );
+
$name = isset( $a[0] ) ? $a[0] : '';
$restriction = isset( $a[1] ) ? $a[1] : '';
$listed = isset( $a[2] ) ? $a[2] : true;
@@ -746,48 +376,120 @@ class SpecialPage {
$file = isset( $a[4] ) ? $a[4] : 'default';
$includable = isset( $a[5] ) ? $a[5] : false;
$this->init( $name, $restriction, $listed, $function, $file, $includable );
+ } else {
+ $className = get_class( $this );
+ throw new MWException( "Call to undefined method $className::$fName" );
}
}
- /**#@+
- * Accessor
- *
- * @deprecated
- */
- function getName() { return $this->mName; }
- function getRestriction() { return $this->mRestriction; }
- function getFile() { return $this->mFile; }
- function isListed() { return $this->mListed; }
- /**#@-*/
-
- /**#@+
- * Accessor and mutator
- */
- function name( $x = null ) { return wfSetVar( $this->mName, $x ); }
- function restrictions( $x = null) {
- # Use the one below this
- wfDeprecated( __METHOD__ );
- return wfSetVar( $this->mRestriction, $x );
+ /**
+ * Get the name of this Special Page.
+ * @return String
+ */
+ function getName() {
+ return $this->mName;
}
+
+ /**
+ * Get the permission that a user must have to execute this page
+ * @return String
+ */
+ function getRestriction() {
+ return $this->mRestriction;
+ }
+
+ /**
+ * Get the file which will be included by SpecialPage::execute() if your extension is
+ * still stuck in the past and hasn't overridden the execute() method. No modern code
+ * should want or need to know this.
+ * @return String
+ * @deprecated since 1.18
+ */
+ function getFile() {
+ return $this->mFile;
+ }
+
+ // @todo FIXME: Decide which syntax to use for this, and stick to it
+ /**
+ * Whether this special page is listed in Special:SpecialPages
+ * @since r3583 (v1.3)
+ * @return Bool
+ */
+ function isListed() {
+ return $this->mListed;
+ }
+ /**
+ * Set whether this page is listed in Special:Specialpages, at run-time
+ * @since r3583 (v1.3)
+ * @param $listed Bool
+ * @return Bool
+ */
+ function setListed( $listed ) {
+ return wfSetVar( $this->mListed, $listed );
+ }
+ /**
+ * Get or set whether this special page is listed in Special:SpecialPages
+ * @since r11308 (v1.6)
+ * @param $x Bool
+ * @return Bool
+ */
+ function listed( $x = null) {
+ return wfSetVar( $this->mListed, $x );
+ }
+
+ /**
+ * Whether it's allowed to transclude the special page via {{Special:Foo/params}}
+ * @return Bool
+ */
+ public function isIncludable(){
+ return $this->mIncludable;
+ }
+
+ /**
+ * These mutators are very evil, as the relevant variables should not mutate. So
+ * don't use them.
+ * @param $x Mixed
+ * @return Mixed
+ * @deprecated since 1.18
+ */
+ function name( $x = null ) { return wfSetVar( $this->mName, $x ); }
function restriction( $x = null) { return wfSetVar( $this->mRestriction, $x ); }
- function listed( $x = null) { return wfSetVar( $this->mListed, $x ); }
function func( $x = null) { return wfSetVar( $this->mFunction, $x ); }
function file( $x = null) { return wfSetVar( $this->mFile, $x ); }
function includable( $x = null ) { return wfSetVar( $this->mIncludable, $x ); }
- function including( $x = null ) { return wfSetVar( $this->mIncluding, $x ); }
- /**#@-*/
+
+ /**
+ * Whether the special page is being evaluated via transclusion
+ * @param $x Bool
+ * @return Bool
+ */
+ function including( $x = null ) {
+ return wfSetVar( $this->mIncluding, $x );
+ }
/**
* Get the localised name of the special page
*/
function getLocalName() {
if ( !isset( $this->mLocalName ) ) {
- $this->mLocalName = self::getLocalNameFor( $this->mName );
+ $this->mLocalName = SpecialPageFactory::getLocalNameFor( $this->mName );
}
return $this->mLocalName;
}
/**
+ * Is this page expensive (for some definition of expensive)?
+ * Expensive pages are disabled or cached in miser mode. Originally used
+ * (and still overridden) by QueryPage and subclasses, moved here so that
+ * Special:SpecialPages can safely call it for all special pages.
+ *
+ * @return Boolean
+ */
+ public function isExpensive() {
+ return false;
+ }
+
+ /**
* Can be overridden by subclasses with more complicated permissions
* schemes.
*
@@ -808,7 +510,7 @@ class SpecialPage {
* @param $user User: the user to check
* @return Boolean: does the user have permission to view the page?
*/
- public function userCanExecute( $user ) {
+ public function userCanExecute( User $user ) {
return $user->isAllowed( $this->mRestriction );
}
@@ -816,18 +518,17 @@ class SpecialPage {
* Output an error message telling the user what access level they have to have
*/
function displayRestrictionError() {
- global $wgOut;
- $wgOut->permissionRequired( $this->mRestriction );
+ throw new PermissionsError( $this->mRestriction );
}
/**
* Sets headers - this should be called from the execute() method of all derived classes!
*/
function setHeaders() {
- global $wgOut;
- $wgOut->setArticleRelated( false );
- $wgOut->setRobotPolicy( "noindex,nofollow" );
- $wgOut->setPageTitle( $this->getDescription() );
+ $out = $this->getOutput();
+ $out->setArticleRelated( false );
+ $out->setRobotPolicy( "noindex,nofollow" );
+ $out->setPageTitle( $this->getDescription() );
}
/**
@@ -835,16 +536,16 @@ class SpecialPage {
* Checks user permissions, calls the function given in mFunction
*
* This must be overridden by subclasses; it will be made abstract in a future version
+ *
+ * @param $par String subpage string, if one was specified
*/
function execute( $par ) {
- global $wgUser;
-
$this->setHeaders();
- if ( $this->userCanExecute( $wgUser ) ) {
+ if ( $this->userCanExecute( $this->getUser() ) ) {
$func = $this->mFunction;
// only load file if the function does not exist
- if(!is_callable($func) and $this->mFile) {
+ if( !is_callable($func) && $this->mFile ) {
require_once( $this->mFile );
}
$this->outputHeader();
@@ -863,16 +564,16 @@ class SpecialPage {
* @param $summaryMessageKey String: message key of the summary
*/
function outputHeader( $summaryMessageKey = '' ) {
- global $wgOut, $wgContLang;
+ global $wgContLang;
if( $summaryMessageKey == '' ) {
- $msg = $wgContLang->lc( $this->name() ) . '-summary';
+ $msg = $wgContLang->lc( $this->getName() ) . '-summary';
} else {
$msg = $summaryMessageKey;
}
- $out = wfMsgNoTrans( $msg );
- if ( ! wfEmptyMsg( $msg, $out ) and $out !== '' and ! $this->including() ) {
- $wgOut->wrapWikiMsg( "<div class='mw-specialpage-summary'>\n$1\n</div>", $msg );
+ if ( !wfMessage( $msg )->isBlank() and ! $this->including() ) {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class='mw-specialpage-summary'>\n$1\n</div>", $msg );
}
}
@@ -894,6 +595,7 @@ class SpecialPage {
/**
* Get a self-referential title object
*
+ * @param $subpage String|Bool
* @return Title object
*/
function getTitle( $subpage = false ) {
@@ -901,39 +603,254 @@ class SpecialPage {
}
/**
- * Set whether this page is listed in Special:Specialpages, at run-time
+ * Sets the context this SpecialPage is executed in
+ *
+ * @param $context IContextSource
+ * @since 1.18
*/
- function setListed( $listed ) {
- return wfSetVar( $this->mListed, $listed );
+ public function setContext( $context ) {
+ $this->mContext = $context;
}
/**
- * If the special page is a redirect, then get the Title object it redirects to.
- * False otherwise.
+ * Gets the context this SpecialPage is executed in
+ *
+ * @return IContextSource
+ * @since 1.18
*/
- function getRedirect( $subpage ) {
- return false;
+ public function getContext() {
+ if ( $this->mContext instanceof IContextSource ) {
+ return $this->mContext;
+ } else {
+ wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
+ return RequestContext::getMain();
+ }
}
/**
- * Return part of the request string for a special redirect page
- * This allows passing, e.g. action=history to Special:Mypage, etc.
+ * Get the WebRequest being used for this instance
*
- * @return String
+ * @return WebRequest
+ * @since 1.18
*/
- function getRedirectQuery() {
- global $wgRequest;
- $params = array();
- foreach( $this->mAllowedRedirectParams as $arg ) {
- if( ( $val = $wgRequest->getVal( $arg, null ) ) !== null )
- $params[] = $arg . '=' . $val;
+ public function getRequest() {
+ return $this->getContext()->getRequest();
+ }
+
+ /**
+ * Get the OutputPage being used for this instance
+ *
+ * @return OutputPage
+ * @since 1.18
+ */
+ public function getOutput() {
+ return $this->getContext()->getOutput();
+ }
+
+ /**
+ * Shortcut to get the User executing this instance
+ *
+ * @return User
+ * @since 1.18
+ */
+ public function getUser() {
+ return $this->getContext()->getUser();
+ }
+
+ /**
+ * Shortcut to get the skin being used for this instance
+ *
+ * @return Skin
+ * @since 1.18
+ */
+ public function getSkin() {
+ return $this->getContext()->getSkin();
+ }
+
+ /**
+ * Shortcut to get user's language
+ *
+ * @return Language
+ * @since 1.18
+ */
+ public function getLang() {
+ return $this->getContext()->getLang();
+ }
+
+ /**
+ * Return the full title, including $par
+ *
+ * @return Title
+ * @since 1.18
+ */
+ public function getFullTitle() {
+ return $this->getContext()->getTitle();
+ }
+
+ /**
+ * Wrapper around wfMessage that sets the current context.
+ *
+ * @return Message
+ * @see wfMessage
+ */
+ public function msg( /* $args */ ) {
+ // Note: can't use func_get_args() directly as second or later item in
+ // a parameter list until PHP 5.3 or you get a fatal error.
+ // Works fine as the first parameter, which appears elsewhere in the
+ // code base. Sighhhh.
+ $args = func_get_args();
+ return call_user_func_array( array( $this->getContext(), 'msg' ), $args );
+ }
+
+ /**
+ * Adds RSS/atom links
+ *
+ * @param $params array
+ */
+ protected function addFeedLinks( $params ) {
+ global $wgFeedClasses, $wgOut;
+
+ $feedTemplate = wfScript( 'api' ) . '?';
+
+ foreach( $wgFeedClasses as $format => $class ) {
+ $theseParams = $params + array( 'feedformat' => $format );
+ $url = $feedTemplate . wfArrayToCGI( $theseParams );
+ $wgOut->addFeedLink( $format, $url );
}
-
- foreach( $this->mAddedRedirectParams as $arg => $val ) {
- $params[] = $arg . '=' . $val;
+ }
+}
+
+/**
+ * Special page which uses an HTMLForm to handle processing. This is mostly a
+ * clone of FormAction. More special pages should be built this way; maybe this could be
+ * a new structure for SpecialPages
+ */
+abstract class FormSpecialPage extends SpecialPage {
+
+ /**
+ * Get an HTMLForm descriptor array
+ * @return Array
+ */
+ protected abstract function getFormFields();
+
+ /**
+ * Add pre- or post-text to the form
+ * @return String HTML which will be sent to $form->addPreText()
+ */
+ protected function preText() { return ''; }
+ protected function postText() { return ''; }
+
+ /**
+ * Play with the HTMLForm if you need to more substantially
+ * @param $form HTMLForm
+ */
+ protected function alterForm( HTMLForm $form ) {}
+
+ /**
+ * Get the HTMLForm to control behaviour
+ * @return HTMLForm|null
+ */
+ protected function getForm() {
+ $this->fields = $this->getFormFields();
+
+ $form = new HTMLForm( $this->fields, $this->getContext() );
+ $form->setSubmitCallback( array( $this, 'onSubmit' ) );
+ $form->setWrapperLegend( wfMessage( strtolower( $this->getName() ) . '-legend' ) );
+ $form->addHeaderText(
+ wfMessage( strtolower( $this->getName() ) . '-text' )->parseAsBlock() );
+
+ // Retain query parameters (uselang etc)
+ $params = array_diff_key(
+ $this->getRequest()->getQueryValues(), array( 'title' => null ) );
+ $form->addHiddenField( 'redirectparams', wfArrayToCGI( $params ) );
+
+ $form->addPreText( $this->preText() );
+ $form->addPostText( $this->postText() );
+ $this->alterForm( $form );
+
+ // Give hooks a chance to alter the form, adding extra fields or text etc
+ wfRunHooks( "Special{$this->getName()}BeforeFormDisplay", array( &$form ) );
+
+ return $form;
+ }
+
+ /**
+ * Process the form on POST submission.
+ * @param $data Array
+ * @return Bool|Array true for success, false for didn't-try, array of errors on failure
+ */
+ public abstract function onSubmit( array $data );
+
+ /**
+ * Do something exciting on successful processing of the form, most likely to show a
+ * confirmation message
+ */
+ public abstract function onSuccess();
+
+ /**
+ * Basic SpecialPage workflow: get a form, send it to the user; get some data back,
+ *
+ * @param $par String Subpage string if one was specified
+ */
+ public function execute( $par ) {
+ $this->setParameter( $par );
+ $this->setHeaders();
+
+ // This will throw exceptions if there's a problem
+ $this->userCanExecute( $this->getUser() );
+
+ $form = $this->getForm();
+ if ( $form->show() ) {
+ $this->onSuccess();
}
-
- return count( $params ) ? implode( '&', $params ) : false;
+ }
+
+ /**
+ * Maybe do something interesting with the subpage parameter
+ * @param $par String
+ */
+ protected function setParameter( $par ){}
+
+ /**
+ * Checks if the given user (identified by an object) can perform this action. Can be
+ * overridden by sub-classes with more complicated permissions schemes. Failures here
+ * must throw subclasses of ErrorPageError
+ *
+ * @param $user User: the user to check, or null to use the context user
+ * @return Bool true
+ * @throws ErrorPageError
+ */
+ public function userCanExecute( User $user ) {
+ if ( $this->requiresWrite() && wfReadOnly() ) {
+ throw new ReadOnlyError();
+ }
+
+ if ( $this->getRestriction() !== null && !$user->isAllowed( $this->getRestriction() ) ) {
+ throw new PermissionsError( $this->getRestriction() );
+ }
+
+ if ( $this->requiresUnblock() && $user->isBlocked() ) {
+ $block = $user->mBlock;
+ throw new UserBlockedError( $block );
+ }
+
+ return true;
+ }
+
+ /**
+ * Whether this action requires the wiki not to be locked
+ * @return Bool
+ */
+ public function requiresWrite() {
+ return true;
+ }
+
+ /**
+ * Whether this action cannot be executed by a blocked user
+ * @return Bool
+ */
+ public function requiresUnblock() {
+ return true;
}
}
@@ -941,32 +858,107 @@ class SpecialPage {
* Shortcut to construct a special page which is unlisted by default
* @ingroup SpecialPage
*/
-class UnlistedSpecialPage extends SpecialPage
-{
+class UnlistedSpecialPage extends SpecialPage {
function __construct( $name, $restriction = '', $function = false, $file = 'default' ) {
parent::__construct( $name, $restriction, false, $function, $file );
}
+
+ public function isListed(){
+ return false;
+ }
}
/**
* Shortcut to construct an includable special page
* @ingroup SpecialPage
*/
-class IncludableSpecialPage extends SpecialPage
-{
- function __construct( $name, $restriction = '', $listed = true, $function = false, $file = 'default' ) {
+class IncludableSpecialPage extends SpecialPage {
+ function __construct(
+ $name, $restriction = '', $listed = true, $function = false, $file = 'default'
+ ) {
parent::__construct( $name, $restriction, $listed, $function, $file, true );
}
+
+ public function isIncludable(){
+ return true;
+ }
}
/**
* Shortcut to construct a special page alias.
* @ingroup SpecialPage
*/
-class SpecialRedirectToSpecial extends UnlistedSpecialPage {
+abstract class RedirectSpecialPage extends UnlistedSpecialPage {
+
+ // Query parameters that can be passed through redirects
+ protected $mAllowedRedirectParams = array();
+
+ // Query parameteres added by redirects
+ protected $mAddedRedirectParams = array();
+
+ public function execute( $par ){
+ $redirect = $this->getRedirect( $par );
+ $query = $this->getRedirectQuery();
+ // Redirect to a page title with possible query parameters
+ if ( $redirect instanceof Title ) {
+ $url = $redirect->getFullUrl( $query );
+ $this->getOutput()->redirect( $url );
+ wfProfileOut( __METHOD__ );
+ return $redirect;
+ // Redirect to index.php with query parameters
+ } elseif ( $redirect === true ) {
+ global $wgScript;
+ $url = $wgScript . '?' . wfArrayToCGI( $query );
+ $this->getOutput()->redirect( $url );
+ wfProfileOut( __METHOD__ );
+ return $redirect;
+ } else {
+ $class = __CLASS__;
+ throw new MWException( "RedirectSpecialPage $class doesn't redirect!" );
+ }
+ }
+
+ /**
+ * If the special page is a redirect, then get the Title object it redirects to.
+ * False otherwise.
+ *
+ * @param $par String Subpage string
+ * @return Title|false
+ */
+ abstract public function getRedirect( $par );
+
+ /**
+ * Return part of the request string for a special redirect page
+ * This allows passing, e.g. action=history to Special:Mypage, etc.
+ *
+ * @return String
+ */
+ public function getRedirectQuery() {
+ $params = array();
+
+ foreach( $this->mAllowedRedirectParams as $arg ) {
+ if( $this->getRequest()->getVal( $arg, null ) !== null ){
+ $params[$arg] = $this->getRequest()->getVal( $arg );
+ }
+ }
+
+ foreach( $this->mAddedRedirectParams as $arg => $val ) {
+ $params[$arg] = $val;
+ }
+
+ return count( $params )
+ ? $params
+ : false;
+ }
+}
+
+abstract class SpecialRedirectToSpecial extends RedirectSpecialPage {
var $redirName, $redirSubpage;
- function __construct( $name, $redirName, $redirSubpage = false, $allowedRedirectParams = array(), $addedRedirectParams = array() ) {
+ function __construct(
+ $name, $redirName, $redirSubpage = false,
+ $allowedRedirectParams = array(), $addedRedirectParams = array()
+ ) {
parent::__construct( $name );
$this->redirName = $redirName;
$this->redirSubpage = $redirSubpage;
@@ -974,7 +966,7 @@ class SpecialRedirectToSpecial extends UnlistedSpecialPage {
$this->mAddedRedirectParams = $addedRedirectParams;
}
- function getRedirect( $subpage ) {
+ public function getRedirect( $subpage ) {
if ( $this->redirSubpage === false ) {
return SpecialPage::getTitleFor( $this->redirName, $subpage );
} else {
@@ -983,7 +975,35 @@ class SpecialRedirectToSpecial extends UnlistedSpecialPage {
}
}
-/** SpecialMypage, SpecialMytalk and SpecialMycontributions special pages
+/**
+ * ListAdmins --> ListUsers/admin
+ */
+class SpecialListAdmins extends SpecialRedirectToSpecial {
+ function __construct(){
+ parent::__construct( 'ListAdmins', 'ListUsers', 'sysop' );
+ }
+}
+
+/**
+ * ListBots --> ListUsers/admin
+ */
+class SpecialListBots extends SpecialRedirectToSpecial {
+ function __construct(){
+ parent::__construct( 'ListAdmins', 'ListUsers', 'bot' );
+ }
+}
+
+/**
+ * CreateAccount --> UserLogin/signup
+ * @todo FIXME: This (and the rest of the login frontend) needs to die a horrible painful death
+ */
+class SpecialCreateAccount extends SpecialRedirectToSpecial {
+ function __construct(){
+ parent::__construct( 'CreateAccount', 'Userlogin', 'signup', array( 'uselang' ) );
+ }
+}
+/**
+ * SpecialMypage, SpecialMytalk and SpecialMycontributions special pages
* are used to get user independant links pointing to the user page, talk
* page and list of contributions.
* This can let us cache a single copy of any generated content for all
@@ -994,19 +1014,20 @@ class SpecialRedirectToSpecial extends UnlistedSpecialPage {
* Shortcut to construct a special page pointing to current user user's page.
* @ingroup SpecialPage
*/
-class SpecialMypage extends UnlistedSpecialPage {
+class SpecialMypage extends RedirectSpecialPage {
function __construct() {
parent::__construct( 'Mypage' );
$this->mAllowedRedirectParams = array( 'action' , 'preload' , 'editintro',
- 'section', 'oldid', 'diff', 'dir' );
+ 'section', 'oldid', 'diff', 'dir',
+ // Options for action=raw; missing ctype can break JS or CSS in some browsers
+ 'ctype', 'maxage', 'smaxage' );
}
function getRedirect( $subpage ) {
- global $wgUser;
if ( strval( $subpage ) !== '' ) {
- return Title::makeTitle( NS_USER, $wgUser->getName() . '/' . $subpage );
+ return Title::makeTitle( NS_USER, $this->getUser()->getName() . '/' . $subpage );
} else {
- return Title::makeTitle( NS_USER, $wgUser->getName() );
+ return Title::makeTitle( NS_USER, $this->getUser()->getName() );
}
}
}
@@ -1015,7 +1036,7 @@ class SpecialMypage extends UnlistedSpecialPage {
* Shortcut to construct a special page pointing to current user talk page.
* @ingroup SpecialPage
*/
-class SpecialMytalk extends UnlistedSpecialPage {
+class SpecialMytalk extends RedirectSpecialPage {
function __construct() {
parent::__construct( 'Mytalk' );
$this->mAllowedRedirectParams = array( 'action' , 'preload' , 'editintro',
@@ -1023,11 +1044,10 @@ class SpecialMytalk extends UnlistedSpecialPage {
}
function getRedirect( $subpage ) {
- global $wgUser;
if ( strval( $subpage ) !== '' ) {
- return Title::makeTitle( NS_USER_TALK, $wgUser->getName() . '/' . $subpage );
+ return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() . '/' . $subpage );
} else {
- return Title::makeTitle( NS_USER_TALK, $wgUser->getName() );
+ return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() );
}
}
}
@@ -1036,7 +1056,7 @@ class SpecialMytalk extends UnlistedSpecialPage {
* Shortcut to construct a special page pointing to current user contributions.
* @ingroup SpecialPage
*/
-class SpecialMycontributions extends UnlistedSpecialPage {
+class SpecialMycontributions extends RedirectSpecialPage {
function __construct() {
parent::__construct( 'Mycontributions' );
$this->mAllowedRedirectParams = array( 'limit', 'namespace', 'tagfilter',
@@ -1044,22 +1064,36 @@ class SpecialMycontributions extends UnlistedSpecialPage {
}
function getRedirect( $subpage ) {
- global $wgUser;
- return SpecialPage::getTitleFor( 'Contributions', $wgUser->getName() );
+ return SpecialPage::getTitleFor( 'Contributions', $this->getUser()->getName() );
}
}
/**
* Redirect to Special:Listfiles?user=$wgUser
*/
-class SpecialMyuploads extends UnlistedSpecialPage {
+class SpecialMyuploads extends RedirectSpecialPage {
function __construct() {
parent::__construct( 'Myuploads' );
$this->mAllowedRedirectParams = array( 'limit' );
}
-
+
function getRedirect( $subpage ) {
- global $wgUser;
- return SpecialPage::getTitleFor( 'Listfiles', $wgUser->getName() );
+ return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
+ }
+}
+
+/**
+ * Redirect from Special:PermanentLink/### to index.php?oldid=###
+ */
+class SpecialPermanentLink extends RedirectSpecialPage {
+ function __construct() {
+ parent::__construct( 'PermanentLink' );
+ $this->mAllowedRedirectParams = array();
+ }
+
+ function getRedirect( $subpage ) {
+ $subpage = intval( $subpage );
+ $this->mAddedRedirectParams['oldid'] = $subpage;
+ return true;
}
}
diff --git a/includes/SpecialPageFactory.php b/includes/SpecialPageFactory.php
new file mode 100644
index 00000000..2a2e6a4c
--- /dev/null
+++ b/includes/SpecialPageFactory.php
@@ -0,0 +1,554 @@
+<?php
+/**
+ * SpecialPage: handling special pages and lists thereof.
+ *
+ * To add a special page in an extension, add to $wgSpecialPages either
+ * an object instance or an array containing the name and constructor
+ * parameters. The latter is preferred for performance reasons.
+ *
+ * The object instantiated must be either an instance of SpecialPage or a
+ * sub-class thereof. It must have an execute() method, which sends the HTML
+ * for the special page to $wgOut. The parent class has an execute() method
+ * which distributes the call to the historical global functions. Additionally,
+ * execute() also checks if the user has the necessary access privileges
+ * and bails out if not.
+ *
+ * To add a core special page, use the similar static list in
+ * SpecialPage::$mList. To remove a core static special page at runtime, use
+ * a SpecialPage_initList hook.
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @defgroup SpecialPage SpecialPage
+ */
+
+/**
+ * Factory for handling the special page list and generating SpecialPage objects
+ * @ingroup SpecialPage
+ * @since 1.17
+ */
+class SpecialPageFactory {
+
+ /**
+ * List of special page names to the subclass of SpecialPage which handles them.
+ */
+ private static $mList = array(
+ // Maintenance Reports
+ 'BrokenRedirects' => 'BrokenRedirectsPage',
+ 'Deadendpages' => 'DeadendpagesPage',
+ 'DoubleRedirects' => 'DoubleRedirectsPage',
+ 'Longpages' => 'LongpagesPage',
+ 'Ancientpages' => 'AncientpagesPage',
+ 'Lonelypages' => 'LonelypagesPage',
+ 'Fewestrevisions' => 'FewestrevisionsPage',
+ 'Withoutinterwiki' => 'WithoutinterwikiPage',
+ 'Protectedpages' => 'SpecialProtectedpages',
+ 'Protectedtitles' => 'SpecialProtectedtitles',
+ 'Shortpages' => 'ShortpagesPage',
+ 'Uncategorizedcategories' => 'UncategorizedcategoriesPage',
+ 'Uncategorizedimages' => 'UncategorizedimagesPage',
+ 'Uncategorizedpages' => 'UncategorizedpagesPage',
+ 'Uncategorizedtemplates' => 'UncategorizedtemplatesPage',
+ 'Unusedcategories' => 'UnusedcategoriesPage',
+ 'Unusedimages' => 'UnusedimagesPage',
+ 'Unusedtemplates' => 'UnusedtemplatesPage',
+ 'Unwatchedpages' => 'UnwatchedpagesPage',
+ 'Wantedcategories' => 'WantedcategoriesPage',
+ 'Wantedfiles' => 'WantedfilesPage',
+ 'Wantedpages' => 'WantedpagesPage',
+ 'Wantedtemplates' => 'WantedtemplatesPage',
+
+ // List of pages
+ 'Allpages' => 'SpecialAllpages',
+ 'Prefixindex' => 'SpecialPrefixindex',
+ 'Categories' => 'SpecialCategories',
+ 'Disambiguations' => 'DisambiguationsPage',
+ 'Listredirects' => 'ListredirectsPage',
+
+ // Login/create account
+ 'Userlogin' => 'LoginForm',
+ 'CreateAccount' => 'SpecialCreateAccount',
+
+ // Users and rights
+ 'Block' => 'SpecialBlock',
+ 'Unblock' => 'SpecialUnblock',
+ 'BlockList' => 'SpecialBlockList',
+ 'ChangePassword' => 'SpecialChangePassword',
+ 'PasswordReset' => 'SpecialPasswordReset',
+ 'DeletedContributions' => 'DeletedContributionsPage',
+ 'Preferences' => 'SpecialPreferences',
+ 'Contributions' => 'SpecialContributions',
+ 'Listgrouprights' => 'SpecialListGroupRights',
+ 'Listusers' => 'SpecialListUsers' ,
+ 'Listadmins' => 'SpecialListAdmins',
+ 'Listbots' => 'SpecialListBots',
+ 'Activeusers' => 'SpecialActiveUsers',
+ 'Userrights' => 'UserrightsPage',
+ 'EditWatchlist' => 'SpecialEditWatchlist',
+
+ // Recent changes and logs
+ 'Newimages' => 'SpecialNewFiles',
+ 'Log' => 'SpecialLog',
+ 'Watchlist' => 'SpecialWatchlist',
+ 'Newpages' => 'SpecialNewpages',
+ 'Recentchanges' => 'SpecialRecentchanges',
+ 'Recentchangeslinked' => 'SpecialRecentchangeslinked',
+ 'Tags' => 'SpecialTags',
+
+ // Media reports and uploads
+ 'Listfiles' => 'SpecialListFiles',
+ 'Filepath' => 'SpecialFilepath',
+ 'MIMEsearch' => 'MIMEsearchPage',
+ 'FileDuplicateSearch' => 'FileDuplicateSearchPage',
+ 'Upload' => 'SpecialUpload',
+ 'UploadStash' => 'SpecialUploadStash',
+
+ // Wiki data and tools
+ 'Statistics' => 'SpecialStatistics',
+ 'Allmessages' => 'SpecialAllmessages',
+ 'Version' => 'SpecialVersion',
+ 'Lockdb' => 'SpecialLockdb',
+ 'Unlockdb' => 'SpecialUnlockdb',
+
+ // Redirecting special pages
+ 'LinkSearch' => 'LinkSearchPage',
+ 'Randompage' => 'Randompage',
+ 'Randomredirect' => 'SpecialRandomredirect',
+
+ // High use pages
+ 'Mostlinkedcategories' => 'MostlinkedCategoriesPage',
+ 'Mostimages' => 'MostimagesPage',
+ 'Mostlinked' => 'MostlinkedPage',
+ 'Mostlinkedtemplates' => 'MostlinkedTemplatesPage',
+ 'Mostcategories' => 'MostcategoriesPage',
+ 'Mostrevisions' => 'MostrevisionsPage',
+
+ // Page tools
+ 'ComparePages' => 'SpecialComparePages',
+ 'Export' => 'SpecialExport',
+ 'Import' => 'SpecialImport',
+ 'Undelete' => 'SpecialUndelete',
+ 'Whatlinkshere' => 'SpecialWhatlinkshere',
+ 'MergeHistory' => 'SpecialMergeHistory',
+
+ // Other
+ 'Booksources' => 'SpecialBookSources',
+
+ // Unlisted / redirects
+ 'Blankpage' => 'SpecialBlankpage',
+ 'Blockme' => 'SpecialBlockme',
+ 'Emailuser' => 'SpecialEmailUser',
+ 'Movepage' => 'MovePageForm',
+ 'Mycontributions' => 'SpecialMycontributions',
+ 'Mypage' => 'SpecialMypage',
+ 'Mytalk' => 'SpecialMytalk',
+ 'Myuploads' => 'SpecialMyuploads',
+ 'PermanentLink' => 'SpecialPermanentLink',
+ 'Revisiondelete' => 'SpecialRevisionDelete',
+ 'Specialpages' => 'SpecialSpecialpages',
+ 'Userlogout' => 'SpecialUserlogout',
+ );
+
+ private static $mAliases;
+
+ /**
+ * Initialise the special page list
+ * This must be called before accessing SpecialPage::$mList
+ *
+ * @return array
+ */
+ static function getList() {
+ global $wgSpecialPages;
+ global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication;
+
+ if ( !is_object( self::$mList ) ) {
+ wfProfileIn( __METHOD__ );
+
+ if ( !$wgDisableCounters ) {
+ self::$mList['Popularpages'] = 'PopularpagesPage';
+ }
+
+ if ( !$wgDisableInternalSearch ) {
+ self::$mList['Search'] = 'SpecialSearch';
+ }
+
+ if ( $wgEmailAuthentication ) {
+ self::$mList['Confirmemail'] = 'EmailConfirmation';
+ self::$mList['Invalidateemail'] = 'EmailInvalidation';
+ }
+
+ // Add extension special pages
+ self::$mList = array_merge( self::$mList, $wgSpecialPages );
+
+ // Run hooks
+ // This hook can be used to remove undesired built-in special pages
+ wfRunHooks( 'SpecialPage_initList', array( &self::$mList ) );
+
+ // Cast to object: func()[$key] doesn't work, but func()->$key does
+ settype( self::$mList, 'object' );
+
+ wfProfileOut( __METHOD__ );
+ }
+ return self::$mList;
+ }
+
+ /**
+ * Initialise and return the list of special page aliases. Returns an object with
+ * properties which can be accessed $obj->pagename - each property is an array of
+ * aliases; the first in the array is the cannonical alias. All registered special
+ * pages are guaranteed to have a property entry, and for that property array to
+ * contain at least one entry (English fallbacks will be added if necessary).
+ * @return Object
+ */
+ static function getAliasList() {
+ if ( !is_object( self::$mAliases ) ) {
+ global $wgContLang;
+ $aliases = $wgContLang->getSpecialPageAliases();
+
+ // Objects are passed by reference by default, need to create a copy
+ $missingPages = clone self::getList();
+
+ self::$mAliases = array();
+ foreach ( $aliases as $realName => $aliasList ) {
+ foreach ( $aliasList as $alias ) {
+ self::$mAliases[$wgContLang->caseFold( $alias )] = $realName;
+ }
+ unset( $missingPages->$realName );
+ }
+ foreach ( $missingPages as $name => $stuff ) {
+ self::$mAliases[$wgContLang->caseFold( $name )] = $name;
+ }
+
+ // Cast to object: func()[$key] doesn't work, but func()->$key does
+ self::$mAliases = (object)self::$mAliases;
+ }
+ return self::$mAliases;
+ }
+
+ /**
+ * Given a special page name with a possible subpage, return an array
+ * where the first element is the special page name and the second is the
+ * subpage.
+ *
+ * @param $alias String
+ * @return Array( String, String|null ), or array( null, null ) if the page is invalid
+ */
+ public static function resolveAlias( $alias ) {
+ global $wgContLang;
+ $bits = explode( '/', $alias, 2 );
+
+ $caseFoldedAlias = $wgContLang->caseFold( $bits[0] );
+ $caseFoldedAlias = str_replace( ' ', '_', $caseFoldedAlias );
+ if ( isset( self::getAliasList()->$caseFoldedAlias ) ) {
+ $name = self::getAliasList()->$caseFoldedAlias;
+ } else {
+ return array( null, null );
+ }
+
+ if ( !isset( $bits[1] ) ) { // bug 2087
+ $par = null;
+ } else {
+ $par = $bits[1];
+ }
+
+ return array( $name, $par );
+ }
+
+ /**
+ * Add a page to a certain display group for Special:SpecialPages
+ *
+ * @param $page Mixed: SpecialPage or string
+ * @param $group String
+ */
+ public static function setGroup( $page, $group ) {
+ global $wgSpecialPageGroups;
+ $name = is_object( $page ) ? $page->mName : $page;
+ $wgSpecialPageGroups[$name] = $group;
+ }
+
+ /**
+ * Get the group that the special page belongs in on Special:SpecialPage
+ *
+ * @param $page SpecialPage
+ */
+ public static function getGroup( &$page ) {
+ global $wgSpecialPageGroups;
+ static $specialPageGroupsCache = array();
+ if ( isset( $specialPageGroupsCache[$page->mName] ) ) {
+ return $specialPageGroupsCache[$page->mName];
+ }
+ $msg = wfMessage( 'specialpages-specialpagegroup-' . strtolower( $page->mName ) );
+ if ( !$msg->isBlank() ) {
+ $group = $msg->text();
+ } else {
+ $group = isset( $wgSpecialPageGroups[$page->mName] )
+ ? $wgSpecialPageGroups[$page->mName]
+ : '-';
+ }
+ if ( $group == '-' ) {
+ $group = 'other';
+ }
+ $specialPageGroupsCache[$page->mName] = $group;
+ return $group;
+ }
+
+ /**
+ * 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
+ */
+ public static function exists( $name ) {
+ list( $title, /*...*/ ) = self::resolveAlias( $name );
+ return property_exists( self::getList(), $title );
+ }
+
+ /**
+ * Find the object with a given name and return it (or NULL)
+ *
+ * @param $name String Special page name, may be localised and/or an alias
+ * @return SpecialPage object or null if the page doesn't exist
+ */
+ public static function getPage( $name ) {
+ list( $realName, /*...*/ ) = self::resolveAlias( $name );
+ if ( property_exists( self::getList(), $realName ) ) {
+ $rec = self::getList()->$realName;
+ if ( is_string( $rec ) ) {
+ $className = $rec;
+ return new $className;
+ } elseif ( is_array( $rec ) ) {
+ // @deprecated, officially since 1.18, unofficially since forever
+ wfDebug( "Array syntax for \$wgSpecialPages is deprecated, define a subclass of SpecialPage instead." );
+ $className = array_shift( $rec );
+ self::getList()->$realName = MWFunction::newObj( $className, $rec );
+ }
+ return self::getList()->$realName;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return categorised listable special pages which are available
+ * for the current user, and everyone.
+ *
+ * @return Array( String => Specialpage )
+ */
+ public static function getUsablePages() {
+ global $wgUser;
+ $pages = array();
+ foreach ( self::getList() as $name => $rec ) {
+ $page = self::getPage( $name );
+ if ( $page // not null
+ && $page->isListed()
+ && ( !$page->isRestricted() || $page->userCanExecute( $wgUser ) )
+ ) {
+ $pages[$name] = $page;
+ }
+ }
+ return $pages;
+ }
+
+ /**
+ * Return categorised listable special pages for all users
+ *
+ * @return Array( String => Specialpage )
+ */
+ public static function getRegularPages() {
+ $pages = array();
+ foreach ( self::getList() as $name => $rec ) {
+ $page = self::getPage( $name );
+ if ( $page->isListed() && !$page->isRestricted() ) {
+ $pages[$name] = $page;
+ }
+ }
+ return $pages;
+ }
+
+ /**
+ * Return categorised listable special pages which are available
+ * for the current user, but not for everyone
+ *
+ * @return Array( String => Specialpage )
+ */
+ public static function getRestrictedPages() {
+ global $wgUser;
+ $pages = array();
+ foreach ( self::getList() as $name => $rec ) {
+ $page = self::getPage( $name );
+ if (
+ $page->isListed()
+ && $page->isRestricted()
+ && $page->userCanExecute( $wgUser )
+ ) {
+ $pages[$name] = $page;
+ }
+ }
+ return $pages;
+ }
+
+ /**
+ * Execute a special page path.
+ * The path may contain parameters, e.g. Special:Name/Params
+ * Extracts the special page name and call the execute method, passing the parameters
+ *
+ * Returns a title object if the page is redirected, false if there was no such special
+ * page, and true if it was successful.
+ *
+ * @param $title Title object
+ * @param $context IContextSource
+ * @param $including Bool output is being captured for use in {{special:whatever}}
+ *
+ * @return bool
+ */
+ public static function executePath( Title &$title, IContextSource &$context, $including = false ) {
+ wfProfileIn( __METHOD__ );
+
+ // @todo FIXME: Redirects broken due to this call
+ $bits = explode( '/', $title->getDBkey(), 2 );
+ $name = $bits[0];
+ if ( !isset( $bits[1] ) ) { // bug 2087
+ $par = null;
+ } else {
+ $par = $bits[1];
+ }
+ $page = self::getPage( $name );
+ // Nonexistent?
+ if ( !$page ) {
+ $context->getOutput()->setArticleRelated( false );
+ $context->getOutput()->setRobotPolicy( 'noindex,nofollow' );
+ $context->getOutput()->setStatusCode( 404 );
+ $context->getOutput()->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ // Page exists, set the context
+ $page->setContext( $context );
+
+ if ( !$including ) {
+ // Redirect to canonical alias for GET commands
+ // Not for POST, we'd lose the post data, so it's best to just distribute
+ // the request. Such POST requests are possible for old extensions that
+ // generate self-links without being aware that their default name has
+ // changed.
+ if ( $name != $page->getLocalName() && !$context->getRequest()->wasPosted() ) {
+ $query = $context->getRequest()->getQueryValues();
+ unset( $query['title'] );
+ $query = wfArrayToCGI( $query );
+ $title = $page->getTitle( $par );
+ $url = $title->getFullUrl( $query );
+ $context->getOutput()->redirect( $url );
+ wfProfileOut( __METHOD__ );
+ return $title;
+ } else {
+ $context->setTitle( $page->getTitle( $par ) );
+ }
+
+ } elseif ( !$page->isIncludable() ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ $page->including( $including );
+
+ // Execute special page
+ $profName = 'Special:' . $page->getName();
+ wfProfileIn( $profName );
+ $page->execute( $par );
+ wfProfileOut( $profName );
+ wfProfileOut( __METHOD__ );
+ return true;
+ }
+
+ /**
+ * 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.
+ *
+ * Also saves the current $wgTitle, $wgOut, and $wgRequest variables so that
+ * the special page will get the context it'd expect on a normal request,
+ * and then restores them to their previous values after.
+ *
+ * @param $title Title
+ *
+ * @return String: HTML fragment
+ */
+ static function capturePath( &$title ) {
+ global $wgOut, $wgTitle, $wgRequest;
+
+ $oldTitle = $wgTitle;
+ $oldOut = $wgOut;
+ $oldRequest = $wgRequest;
+
+ // Don't want special pages interpreting ?feed=atom parameters.
+ $wgRequest = new FauxRequest( array() );
+
+ $context = new RequestContext;
+ $context->setTitle( $title );
+ $context->setRequest( $wgRequest );
+ $wgOut = $context->getOutput();
+
+ $ret = self::executePath( $title, $context, true );
+ if ( $ret === true ) {
+ $ret = $wgOut->getHTML();
+ }
+ $wgTitle = $oldTitle;
+ $wgOut = $oldOut;
+ $wgRequest = $oldRequest;
+ return $ret;
+ }
+
+ /**
+ * Get the local name for a specified canonical name
+ *
+ * @param $name String
+ * @param $subpage String|Bool
+ *
+ * @return String
+ */
+ static function getLocalNameFor( $name, $subpage = false ) {
+ global $wgContLang;
+ $aliases = $wgContLang->getSpecialPageAliases();
+
+ if ( isset( $aliases[$name][0] ) ) {
+ $name = $aliases[$name][0];
+ } else {
+ // Try harder in case someone misspelled the correct casing
+ $found = false;
+ foreach ( $aliases as $n => $values ) {
+ if ( strcasecmp( $name, $n ) === 0 ) {
+ wfWarn( "Found alias defined for $n when searching for " .
+ "special page aliases for $name. Case mismatch?" );
+ $name = $values[0];
+ $found = true;
+ break;
+ }
+ }
+ if ( !$found ) {
+ wfWarn( "Did not find alias for special page '$name'. " .
+ "Perhaps no aliases are defined for it?" );
+ }
+ }
+ if ( $subpage !== false && !is_null( $subpage ) ) {
+ $name = "$name/$subpage";
+ }
+ return $wgContLang->ucfirst( $name );
+ }
+
+ /**
+ * Get a title for a given alias
+ *
+ * @param $alias String
+ *
+ * @return Title or null if there is no such alias
+ */
+ static function getTitleForAlias( $alias ) {
+ $name = self::resolveAlias( $alias );
+ if ( $name ) {
+ return SpecialPage::getTitleFor( $name );
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/includes/SquidPurgeClient.php b/includes/SquidPurgeClient.php
index 1b315e5f..3de68578 100644
--- a/includes/SquidPurgeClient.php
+++ b/includes/SquidPurgeClient.php
@@ -33,6 +33,8 @@ class SquidPurgeClient {
/**
* Open a socket if there isn't one open already, return it.
* Returns false on error.
+ *
+ * @return false|resource
*/
protected function getSocket() {
if ( $this->socket !== null ) {
@@ -64,6 +66,7 @@ class SquidPurgeClient {
/**
* Get read socket array for select()
+ * @return array
*/
public function getReadSocketsForSelect() {
if ( $this->readState == 'idle' ) {
@@ -78,6 +81,7 @@ class SquidPurgeClient {
/**
* Get write socket array for select()
+ * @return array
*/
public function getWriteSocketsForSelect() {
if ( !strlen( $this->writeBuffer ) ) {
@@ -139,6 +143,8 @@ class SquidPurgeClient {
/**
* Queue a purge operation
+ *
+ * @param $url string
*/
public function queuePurge( $url ) {
$url = str_replace( "\n", '', $url );
@@ -151,6 +157,9 @@ class SquidPurgeClient {
}
}
+ /**
+ * @return bool
+ */
public function isIdle() {
return strlen( $this->writeBuffer ) == 0 && $this->readState == 'idle';
}
@@ -220,6 +229,10 @@ class SquidPurgeClient {
while ( $this->socket && $this->processReadBuffer() === 'continue' );
}
+ /**
+ * @throws MWException
+ * @return string
+ */
protected function processReadBuffer() {
switch ( $this->readState ) {
case 'idle':
@@ -259,6 +272,10 @@ class SquidPurgeClient {
}
}
+ /**
+ * @param $line
+ * @return
+ */
protected function processStatusLine( $line ) {
if ( !preg_match( '!^HTTP/(\d+)\.(\d+) (\d{3}) (.*)$!', $line, $m ) ) {
$this->log( 'invalid status line' );
@@ -275,6 +292,9 @@ class SquidPurgeClient {
$this->readState = 'header';
}
+ /**
+ * @param $line string
+ */
protected function processHeaderLine( $line ) {
if ( preg_match( '/^Content-Length: (\d+)$/i', $line, $m ) ) {
$this->bodyRemaining = intval( $m[1] );
@@ -305,6 +325,10 @@ class SquidPurgeClient {
}
class SquidPurgeClientPool {
+
+ /**
+ * @var array of SquidPurgeClient
+ */
var $clients = array();
var $timeout = 5;
@@ -314,6 +338,10 @@ class SquidPurgeClientPool {
}
}
+ /**
+ * @param $client SquidPurgeClient
+ * @return void
+ */
public function addClient( $client ) {
$this->clients[] = $client;
}
diff --git a/includes/Status.php b/includes/Status.php
index f049980f..6bd94564 100644
--- a/includes/Status.php
+++ b/includes/Status.php
@@ -17,7 +17,9 @@ class Status {
var $value;
/** Counters for batch operations */
- var $successCount = 0, $failCount = 0;
+ public $successCount = 0, $failCount = 0;
+ /** Array to indicate which items of the batch operations were successful */
+ public $success = array();
/*semi-private*/ var $errors = array();
/*semi-private*/ var $cleanCallback = false;
@@ -125,6 +127,10 @@ class Status {
$this->cleanCallback = false;
}
+ /**
+ * @param $params array
+ * @return array
+ */
protected function cleanParams( $params ) {
if ( !$this->cleanCallback ) {
return $params;
@@ -136,20 +142,25 @@ class Status {
return $cleanParams;
}
+ /**
+ * @param $item
+ * @return string
+ */
protected function getItemXML( $item ) {
$params = $this->cleanParams( $item['params'] );
$xml = "<{$item['type']}>\n" .
Xml::element( 'message', null, $item['message'] ) . "\n" .
- Xml::element( 'text', null, wfMsgReal( $item['message'], $params ) ) ."\n";
+ Xml::element( 'text', null, wfMsg( $item['message'], $params ) ) ."\n";
foreach ( $params as $param ) {
$xml .= Xml::element( 'param', null, $param );
}
- $xml .= "</{$this->type}>\n";
+ $xml .= "</{$item['type']}>\n";
return $xml;
}
/**
* Get the error list as XML
+ * @return string
*/
function getXML() {
$xml = "<errors>\n";
@@ -209,17 +220,15 @@ class Status {
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 );
+ return wfMsgNoTrans( $error['message'],
+ array_map( 'wfEscapeWikiText', $this->cleanParams( $error['params'] ) ) );
} else {
$message = array_shift($error);
- return wfMsgReal( $message,
- array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ),
- true, false, false );
+ return wfMsgNoTrans( $message,
+ array_map( 'wfEscapeWikiText', $this->cleanParams( $error ) ) );
}
} else {
- return wfMsgReal( $error, array(), true, false, false);
+ return wfMsgNoTrans( $error );
}
}
@@ -235,7 +244,7 @@ class Status {
/**
* Merge another status object into this one
*
- * @param $other Other Status object
+ * @param $other Status Other Status object
* @param $overwriteValue Boolean: whether to override the "value" member
*/
function merge( $other, $overwriteValue = false ) {
@@ -279,12 +288,31 @@ class Status {
if( $error['params'] ) {
$result[] = array_merge( array( $error['message'] ), $error['params'] );
} else {
- $result[] = $error['message'];
+ $result[] = array( $error['message'] );
}
}
}
return $result;
}
+
+ /**
+ * Returns a list of status messages of the given type, with message and
+ * params left untouched, like a sane version of getStatusArray
+ *
+ * @param $type String
+ *
+ * @return Array
+ */
+ public function getErrorsByType( $type ) {
+ $result = array();
+ foreach ( $this->errors as $error ) {
+ if ( $error['type'] === $type ) {
+ $result[] = $error;
+ }
+ }
+ return $result;
+ }
+
/**
* Returns true if the specified message is present as a warning or error
*
@@ -305,6 +333,8 @@ class Status {
* destination message, but keep the same parameters as in the original error.
*
* Return true if the replacement was done, false otherwise.
+ *
+ * @return bool
*/
function replaceMessage( $source, $dest ) {
$replaced = false;
diff --git a/includes/StreamFile.php b/includes/StreamFile.php
index 5f460ee3..d08cfec6 100644
--- a/includes/StreamFile.php
+++ b/includes/StreamFile.php
@@ -5,9 +5,14 @@
* @file
*/
-/** */
+/**
+ * @param $fname string
+ * @param $headers array
+ */
function wfStreamFile( $fname, $headers = array() ) {
- $stat = @stat( $fname );
+ wfSuppressWarnings();
+ $stat = stat( $fname );
+ wfRestoreWarnings();
if ( !$stat ) {
header( 'HTTP/1.0 404 Not Found' );
header( 'Cache-Control: no-cache' );
@@ -63,7 +68,11 @@ function wfStreamFile( $fname, $headers = array() ) {
readfile( $fname );
}
-/** */
+/**
+ * @param $filename string
+ * @param $safe bool
+ * @return null|string
+ */
function wfGetType( $filename, $safe = true ) {
global $wgTrivialMimeDetection;
diff --git a/includes/StringUtils.php b/includes/StringUtils.php
index c1e617a0..f405e616 100644
--- a/includes/StringUtils.php
+++ b/includes/StringUtils.php
@@ -13,6 +13,13 @@ class StringUtils {
* Compared to delimiterReplace(), this implementation is fast but memory-
* hungry and inflexible. The memory requirements are such that I don't
* recommend using it on anything but guaranteed small chunks of text.
+ *
+ * @param $startDelim
+ * @param $endDelim
+ * @param $replace
+ * @param $subject
+ *
+ * @return string
*/
static function hungryDelimiterReplace( $startDelim, $endDelim, $replace, $subject ) {
$segments = explode( $startDelim, $subject );
@@ -36,17 +43,19 @@ class StringUtils {
* This implementation is slower than hungryDelimiterReplace but uses far less
* memory. The delimiters are literal strings, not regular expressions.
*
+ * 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
+ * regex. In this implementation, the end must share no characters with the
+ * start, so e.g. /*\/ is not considered to be both the start and end of a
+ * comment. /*\/xy/*\/ is considered to be a single comment with contents /xy/.
+ *
* @param $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
+ * @return string
*/
- # 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
- # regex. In this implementation, the end must share no characters with the
- # start, so e.g. /*/ is not considered to be both the start and end of a
- # comment. /*/xy/*/ is considered to be a single comment with contents /xy/.
static function delimiterReplaceCallback( $startDelim, $endDelim, $callback, $subject, $flags = '' ) {
$inputPos = 0;
$outputPos = 0;
@@ -180,6 +189,9 @@ class StringUtils {
/**
* Workalike for explode() with limited memory usage.
* Returns an Iterator
+ * @param $separator
+ * @param $subject
+ * @return \ArrayIterator|\ExplodeIterator
*/
static function explode( $separator, $subject ) {
if ( substr_count( $subject, $separator ) > 1000 ) {
diff --git a/includes/StubObject.php b/includes/StubObject.php
index 678b2744..951cbaea 100644
--- a/includes/StubObject.php
+++ b/includes/StubObject.php
@@ -62,7 +62,7 @@ class StubObject {
* Create a new object to replace this stub object.
*/
function _newObject() {
- return wfCreateObject( $this->mClass, $this->mParams );
+ return MWFunction::newObj( $this->mClass, $this->mParams );
}
/**
@@ -110,6 +110,8 @@ class StubObject {
/**
* Stub object for the content language of this wiki. This object have to be in
* $wgContLang global.
+ *
+ * @deprecated since 1.18
*/
class StubContLang extends StubObject {
@@ -146,22 +148,6 @@ class StubUserLang extends StubObject {
}
function _newObject() {
- global $wgLanguageCode, $wgRequest, $wgUser, $wgContLang;
- $code = $wgRequest->getVal( 'uselang', $wgUser->getOption( 'language' ) );
- // BCP 47 - letter case MUST NOT carry meaning
- $code = strtolower( $code );
-
- # Validate $code
- if( empty( $code ) || !Language::isValidCode( $code ) || ( $code === 'qqq' ) ) {
- wfDebug( "Invalid user language code\n" );
- $code = $wgLanguageCode;
- }
-
- if( $code === $wgLanguageCode ) {
- return $wgContLang;
- } else {
- $obj = Language::factory( $code );
- return $obj;
- }
+ return RequestContext::getMain()->getLang();
}
}
diff --git a/includes/Title.php b/includes/Title.php
index 5ae2f1a0..33373b2c 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -1,25 +1,26 @@
<?php
/**
* See title.txt
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
*/
/**
- * @todo: 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' );
-}
-
-/**
- * @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;
@@ -41,7 +42,7 @@ class Title {
const CACHE_MAX = 1000;
/**
- * Used to be GAID_FOR_UPDATE define. Used with getArticleId() and friends
+ * Used to be GAID_FOR_UPDATE define. Used with getArticleID() and friends
* to use the master DB
*/
const GAID_FOR_UPDATE = 1;
@@ -76,7 +77,7 @@ class Title {
# Don't change the following default, NS_MAIN is hardcoded in several
# places. See bug 696.
var $mDefaultNamespace = NS_MAIN; // /< Namespace index when there is no namespace
- # Zero except in {{transclusion}} tags
+ # 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?
@@ -87,17 +88,16 @@ class Title {
/**
* Constructor
- * @private
*/
- /* private */ function __construct() { }
+ /*protected*/ function __construct() { }
/**
* Create a new Title from a prefixed DB key
*
- * @param $key \type{\string} The database key, which has underscores
+ * @param $key String the database key, which has underscores
* instead of spaces, possibly including namespace and
* interwiki prefixes
- * @return \type{Title} the new object, or NULL on an error
+ * @return Title, or NULL on an error
*/
public static function newFromDBkey( $key ) {
$t = new Title();
@@ -113,13 +113,13 @@ class Title {
* 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.
+ * @return Title, or null on an error.
*/
public static function newFromText( $text, $defaultNamespace = NS_MAIN ) {
if ( is_object( $text ) ) {
@@ -138,9 +138,7 @@ class Title {
return Title::$titleCache[$text];
}
- /**
- * Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
- */
+ # Convert things like &eacute; &#257; or &#x3017; into normalized (bug 14952) text
$filteredText = Sanitizer::decodeCharReferencesAndNormalize( $text );
$t = new Title();
@@ -177,8 +175,8 @@ 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
+ * @param $url String the title, as might be taken from a URL
+ * @return Title the new object, or NULL on an error
*/
public static function newFromURL( $url ) {
global $wgLegalTitleChars;
@@ -202,9 +200,9 @@ 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 Title::GAID_FOR_UPDATE to use master
- * @return \type{Title} the new object, or NULL on an error
+ * @param $id Int the page_id corresponding to the Title to create
+ * @param $flags Int use Title::GAID_FOR_UPDATE to use master
+ * @return Title the new object, or NULL on an error
*/
public static function newFromID( $id, $flags = 0 ) {
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
@@ -220,15 +218,15 @@ 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
+ * @param $ids Array of Int Array of IDs
+ * @return Array of Titles
*/
public static function newFromIDs( $ids ) {
if ( !count( $ids ) ) {
return array();
}
$dbr = wfGetDB( DB_SLAVE );
-
+
$res = $dbr->select(
'page',
array(
@@ -249,32 +247,52 @@ class Title {
/**
* Make a Title object from a DB row
*
- * @param $row \type{Row} (needs at least page_title,page_namespace)
- * @return \type{Title} corresponding Title
+ * @param $row Object database row (needs at least page_title,page_namespace)
+ * @return Title corresponding Title
*/
public static function newFromRow( $row ) {
$t = self::makeTitle( $row->page_namespace, $row->page_title );
-
- $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;
-
+ $t->loadFromRow( $row );
return $t;
}
/**
+ * Load Title object fields from a DB row.
+ * If false is given, the title will be treated as non-existing.
+ *
+ * @param $row Object|false database row
+ * @return void
+ */
+ public function loadFromRow( $row ) {
+ if ( $row ) { // page found
+ if ( isset( $row->page_id ) )
+ $this->mArticleID = (int)$row->page_id;
+ if ( isset( $row->page_len ) )
+ $this->mLength = (int)$row->page_len;
+ if ( isset( $row->page_is_redirect ) )
+ $this->mRedirect = (bool)$row->page_is_redirect;
+ if ( isset( $row->page_latest ) )
+ $this->mLatestID = (int)$row->page_latest;
+ } else { // page not found
+ $this->mArticleID = 0;
+ $this->mLength = 0;
+ $this->mRedirect = false;
+ $this->mLatestID = 0;
+ }
+ }
+
+ /**
* Create a new Title from a namespace index and a DB key.
* It's assumed that $ns and $title are *valid*, for instance when
* they came directly from the database or a special page name.
* For convenience, spaces are converted to underscores so that
* eg user_text fields can be used directly.
*
- * @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
+ * @param $ns Int the namespace of the article
+ * @param $title String the unprefixed database key form
+ * @param $fragment String the link fragment (after the "#")
+ * @param $interwiki String the interwiki prefix
+ * @return Title the new object
*/
public static function &makeTitle( $ns, $title, $fragment = '', $interwiki = '' ) {
$t = new Title();
@@ -293,11 +311,11 @@ class Title {
* The parameters will be checked for validity, which is a bit slower
* than makeTitle() but safer for user-provided data.
*
- * @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
+ * @param $ns Int the namespace of the article
+ * @param $title String database key form
+ * @param $fragment String the link fragment (after the "#")
+ * @param $interwiki String interwiki prefix
+ * @return Title the new object, or NULL on an error
*/
public static function makeTitleSafe( $ns, $title, $fragment = '', $interwiki = '' ) {
$t = new Title();
@@ -312,7 +330,7 @@ class Title {
/**
* Create a new Title for the Main Page
*
- * @return \type{Title} the new object
+ * @return Title the new object
*/
public static function newMainPage() {
$title = Title::newFromText( wfMsgForContent( 'mainpage' ) );
@@ -342,8 +360,8 @@ class Title {
* This will recurse down $wgMaxRedirects times or until a non-redirect target is hit
* in order to provide (hopefully) the Title of the final destination instead of another redirect
*
- * @param $text \type{\string} Text with possible redirect
- * @return \type{Title} The corresponding Title
+ * @param $text String Text with possible redirect
+ * @return Title
*/
public static function newFromRedirectRecurse( $text ) {
$titles = self::newFromRedirectArray( $text );
@@ -356,15 +374,11 @@ class Title {
* The last element in the array is the final destination after all redirects
* have been resolved (up to $wgMaxRedirects times)
*
- * @param $text \type{\string} Text with possible redirect
- * @return \type{\array} Array of Titles, with the destination last
+ * @param $text String Text with possible redirect
+ * @return Array of Titles, with the destination last
*/
public static function newFromRedirectArray( $text ) {
global $wgMaxRedirects;
- // are redirects disabled?
- if ( $wgMaxRedirects < 1 ) {
- return null;
- }
$title = self::newFromRedirectInternal( $text );
if ( is_null( $title ) ) {
return null;
@@ -395,10 +409,15 @@ class Title {
* Really extract the redirect destination
* Do not call this function directly, use one of the newFromRedirect* functions above
*
- * @param $text \type{\string} Text with possible redirect
- * @return \type{Title} The corresponding Title
+ * @param $text String Text with possible redirect
+ * @return Title
*/
protected static function newFromRedirectInternal( $text ) {
+ global $wgMaxRedirects;
+ if ( $wgMaxRedirects < 1 ) {
+ //redirects are disabled, so quit early
+ return null;
+ }
$redir = MagicWord::get( 'redirect' );
$text = trim( $text );
if ( $redir->matchStartAndRemove( $text ) ) {
@@ -411,9 +430,7 @@ class Title {
// and URL-decode links
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.
- $m[1] = urldecode( ltrim( $m[1], ':' ) );
+ $m[1] = rawurldecode( ltrim( $m[1], ':' ) );
}
$title = Title::newFromText( $m[1] );
// If the title is a redirect to bad special pages or is invalid, return null
@@ -433,9 +450,8 @@ class Title {
/**
* 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
+ * @param $id Int the page_id of the article
+ * @return Title an object representing the article, or NULL if no such article was found
*/
public static function nameOf( $id ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -457,7 +473,7 @@ class Title {
/**
* Get a regex character class describing the legal characters in a link
*
- * @return \type{\string} the list of characters, not delimited
+ * @return String the list of characters, not delimited
*/
public static function legalChars() {
global $wgLegalTitleChars;
@@ -468,10 +484,9 @@ class Title {
* Get a string representation of a title suitable for
* including in a search index
*
- * @param $ns \type{\int} a namespace index
- * @param $title \type{\string} text-form main part
- * @return \type{\string} a stripped-down title string ready for the
- * search index
+ * @param $ns Int a namespace index
+ * @param $title String text-form main part
+ * @return String a stripped-down title string ready for the search index
*/
public static function indexTitle( $ns, $title ) {
global $wgContLang;
@@ -496,11 +511,11 @@ 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
+ * @param $ns Int numerical representation of the namespace
+ * @param $title String the DB key form the title
+ * @param $fragment String The link fragment (after the "#")
+ * @param $interwiki String The interwiki prefix
+ * @return String the prefixed form of the title
*/
public static function makeName( $ns, $title, $fragment = '', $interwiki = '' ) {
global $wgContLang;
@@ -520,8 +535,7 @@ class Title {
* Determine whether the object refers to a page within
* this project.
*
- * @return \type{\bool} TRUE if this is an in-project interwiki link
- * or a wikilink, FALSE otherwise
+ * @return Bool TRUE if this is an in-project interwiki link or a wikilink, FALSE otherwise
*/
public function isLocal() {
if ( $this->mInterwiki != '' ) {
@@ -535,7 +549,7 @@ class Title {
* Determine whether the object refers to a page within
* this project and is transcludable.
*
- * @return \type{\bool} TRUE if this is transcludable
+ * @return Bool TRUE if this is transcludable
*/
public function isTrans() {
if ( $this->mInterwiki == '' ) {
@@ -546,10 +560,9 @@ class Title {
}
/**
- * Returns the DB name of the distant wiki
- * which owns the object.
+ * Returns the DB name of the distant wiki which owns the object.
*
- * @return \type{\string} the DB name
+ * @return String the DB name
*/
public function getTransWikiID() {
if ( $this->mInterwiki == '' ) {
@@ -581,14 +594,14 @@ class Title {
/**
* Get the text form (spaces not underscores) of the main part
*
- * @return \type{\string} Main part of the title
+ * @return 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
+ * @return String Main part of the title, URL-encoded
*/
public function getPartialURL() { return $this->mUrlform; }
@@ -600,7 +613,7 @@ class Title {
public function getDBkey() { return $this->mDbkeyform; }
/**
- * Get the namespace index, i.e.\ one of the NS_xxxx constants.
+ * Get the namespace index, i.e. one of the NS_xxxx constants.
*
* @return Integer: Namespace index
*/
@@ -625,13 +638,28 @@ class Title {
return MWNamespace::getCanonicalName( $this->mNamespace );
}
}
+
+ // Strip off subpages
+ $pagename = $this->getText();
+ if ( strpos( $pagename, '/' ) !== false ) {
+ list( $username , ) = explode( '/', $pagename, 2 );
+ } else {
+ $username = $pagename;
+ }
+
+ if ( $wgContLang->needsGenderDistinction() &&
+ MWNamespace::hasGenderDistinction( $this->mNamespace ) ) {
+ $gender = GenderCache::singleton()->getGenderOf( $username, __METHOD__ );
+ return $wgContLang->getGenderNsText( $this->mNamespace, $gender );
+ }
+
return $wgContLang->getNsText( $this->mNamespace );
}
/**
* Get the DB key with the initial letter case as specified by the user
*
- * @return \type{\string} DB key
+ * @return String DB key
*/
function getUserCaseDBKey() {
return $this->mUserCaseDBKey;
@@ -640,7 +668,7 @@ class Title {
/**
* Get the namespace text of the subject (rather than talk) page
*
- * @return \type{\string} Namespace text
+ * @return String Namespace text
*/
public function getSubjectNsText() {
global $wgContLang;
@@ -650,7 +678,7 @@ class Title {
/**
* Get the namespace text of the talk page
*
- * @return \type{\string} Namespace text
+ * @return String Namespace text
*/
public function getTalkNsText() {
global $wgContLang;
@@ -660,7 +688,7 @@ class Title {
/**
* Could this title have a corresponding talk page?
*
- * @return \type{\bool} TRUE or FALSE
+ * @return Bool TRUE or FALSE
*/
public function canTalk() {
return( MWNamespace::canTalk( $this->mNamespace ) );
@@ -669,20 +697,20 @@ class Title {
/**
* Get the interwiki prefix (or null string)
*
- * @return \type{\string} Interwiki prefix
+ * @return 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
+ * @return 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
+ * @return String Fragment in URL form
*/
public function getFragmentForURL() {
if ( $this->mFragment == '' ) {
@@ -695,14 +723,14 @@ class Title {
/**
* Get the default namespace index, for when there is no namespace
*
- * @return \type{\int} Default namespace index
+ * @return 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
+ * @return String a stripped-down title string ready for the
* search index
*/
public function getIndexTitle() {
@@ -712,7 +740,7 @@ class Title {
/**
* Get the prefixed database key form
*
- * @return \type{\string} the prefixed title, with underscores and
+ * @return String the prefixed title, with underscores and
* any interwiki and namespace prefixes
*/
public function getPrefixedDBkey() {
@@ -725,10 +753,11 @@ class Title {
* Get the prefixed title with spaces.
* This is the form usually used for display
*
- * @return \type{\string} the prefixed title, with spaces
+ * @return String the prefixed title, with spaces
*/
public function getPrefixedText() {
- if ( empty( $this->mPrefixedText ) ) { // FIXME: bad usage of empty() ?
+ // @todo FIXME: Bad usage of empty() ?
+ if ( empty( $this->mPrefixedText ) ) {
$s = $this->prefix( $this->mTextform );
$s = str_replace( '_', ' ', $s );
$this->mPrefixedText = $s;
@@ -740,8 +769,7 @@ 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 '#'
+ * @return String the prefixed title, with spaces and the fragment, including '#'
*/
public function getFullText() {
$text = $this->getPrefixedText();
@@ -752,9 +780,9 @@ class Title {
}
/**
- * Get the base name, i.e. the leftmost parts before the /
+ * Get the base page name, i.e. the leftmost part before any slashes
*
- * @return \type{\string} Base name
+ * @return String Base name
*/
public function getBaseText() {
if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
@@ -770,9 +798,9 @@ class Title {
}
/**
- * Get the lowest-level subpage name, i.e. the rightmost part after /
+ * Get the lowest-level subpage name, i.e. the rightmost part after any slashes
*
- * @return \type{\string} Subpage name
+ * @return String Subpage name
*/
public function getSubpageText() {
if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
@@ -785,7 +813,7 @@ class Title {
/**
* Get a URL-encoded form of the subpage text
*
- * @return \type{\string} URL-encoded subpage name
+ * @return String URL-encoded subpage name
*/
public function getSubpageUrlForm() {
$text = $this->getSubpageText();
@@ -796,7 +824,7 @@ class Title {
/**
* Get a URL-encoded title (not an actual URL) including interwiki
*
- * @return \type{\string} the URL-encoded form
+ * @return String the URL-encoded form
*/
public function getPrefixedURL() {
$s = $this->prefix( $this->mDbkeyform );
@@ -811,8 +839,8 @@ class Title {
* @param $query \twotypes{\string,\array} an optional query string, not used for interwiki
* links. Can be specified as an associative array as well, e.g.,
* array( 'action' => 'edit' ) (keys and values will be URL-escaped).
- * @param $variant \type{\string} language variant of url (for sr, zh..)
- * @return \type{\string} the URL
+ * @param $variant String language variant of url (for sr, zh..)
+ * @return String the URL
*/
public function getFullURL( $query = '', $variant = false ) {
global $wgServer, $wgRequest;
@@ -846,7 +874,7 @@ class Title {
# Finally, add the fragment.
$url .= $this->getFragmentForURL();
- wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
+ wfRunHooks( 'GetFullURL', array( &$this, &$url, $query, $variant ) );
return $url;
}
@@ -858,8 +886,8 @@ class Title {
* $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).
- * @param $variant \type{\string} language variant of url (for sr, zh..)
- * @return \type{\string} the URL
+ * @param $variant String language variant of url (for sr, zh..)
+ * @return String the URL
*/
public function getLocalURL( $query = '', $variant = false ) {
global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
@@ -911,6 +939,7 @@ class Title {
}
}
}
+
if ( $url === false ) {
if ( $query == '-' ) {
$query = '';
@@ -919,7 +948,7 @@ class Title {
}
}
- // FIXME: this causes breakage in various places when we
+ // @todo 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' ) {
$url = $wgServer . $url;
@@ -939,12 +968,12 @@ class Title {
* The result obviously should not be URL-escaped, but does need to be
* HTML-escaped if it's being output in HTML.
*
- * @param $query \type{\arrayof{\string}} An associative array of key => value pairs for the
+ * @param $query Array of Strings An associative array of key => value pairs for the
* query string. Keys and values will be escaped.
- * @param $variant \type{\string} Language variant of URL (for sr, zh..). Ignored
+ * @param $variant String language variant of URL (for sr, zh..). Ignored
* for external links. Default is "false" (same variant as current page,
* for anonymous users).
- * @return \type{\string} the URL
+ * @return String the URL
*/
public function getLinkUrl( $query = array(), $variant = false ) {
wfProfileIn( __METHOD__ );
@@ -963,8 +992,8 @@ 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
+ * @param $query String an optional query string
+ * @return String the URL
*/
public function escapeLocalURL( $query = '' ) {
return htmlspecialchars( $this->getLocalURL( $query ) );
@@ -974,33 +1003,63 @@ class Title {
* Get an HTML-escaped version of the URL form, suitable for
* using in a link, including the server name and fragment
*
- * @param $query \type{\string} an optional query string
- * @return \type{\string} the URL
+ * @param $query String an optional query string
+ * @return String the URL
*/
public function escapeFullURL( $query = '' ) {
return htmlspecialchars( $this->getFullURL( $query ) );
}
+
+ /**
+ * HTML-escaped version of getCanonicalURL()
+ */
+ public function escapeCanonicalURL( $query = '', $variant = false ) {
+ return htmlspecialchars( $this->getCanonicalURL( $query, $variant ) );
+ }
/**
* Get the URL form for an internal link.
* - Used in various Squid-related code, in case we have a different
* internal hostname for the server from the exposed one.
+ *
+ * This uses $wgInternalServer to qualify the path, or $wgServer
+ * if $wgInternalServer is not set. If the server variable used is
+ * protocol-relative, the URL will be expanded to http://
*
- * @param $query \type{\string} an optional query string
- * @param $variant \type{\string} language variant of url (for sr, zh..)
- * @return \type{\string} the URL
+ * @param $query String an optional query string
+ * @param $variant String language variant of url (for sr, zh..)
+ * @return String the URL
*/
public function getInternalURL( $query = '', $variant = false ) {
- global $wgInternalServer;
- $url = $wgInternalServer . $this->getLocalURL( $query, $variant );
- wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
+ global $wgInternalServer, $wgServer;
+ $server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
+ $url = wfExpandUrl( $server . $this->getLocalURL( $query, $variant ), PROTO_HTTP );
+ wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query, $variant ) );
+ return $url;
+ }
+
+ /**
+ * Get the URL for a canonical link, for use in things like IRC and
+ * e-mail notifications. Uses $wgCanonicalServer and the
+ * GetCanonicalURL hook.
+ *
+ * NOTE: Unlike getInternalURL(), the canonical URL includes the fragment
+ *
+ * @param $query string An optional query string
+ * @param $variant string Language variant of URL (for sr, zh, ...)
+ * @return string The URL
+ */
+ public function getCanonicalURL( $query = '', $variant = false ) {
+ global $wgCanonicalServer;
+ $url = wfExpandUrl( $this->getLocalURL( $query, $variant ) . $this->getFragmentForURL(), PROTO_CANONICAL );
+ wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query, $variant ) );
return $url;
}
/**
* Get the edit URL for this Title
*
- * @return \type{\string} the URL, or a null string if this is an
+ * @return String the URL, or a null string if this is an
* interwiki link
*/
public function getEditURL() {
@@ -1016,7 +1075,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
+ * @return String the text, including any prefixes
*/
public function getEscapedText() {
return htmlspecialchars( $this->getPrefixedText() );
@@ -1025,7 +1084,7 @@ class Title {
/**
* Is this Title interwiki?
*
- * @return \type{\bool}
+ * @return Bool
*/
public function isExternal() {
return ( $this->mInterwiki != '' );
@@ -1034,8 +1093,8 @@ class Title {
/**
* Is this page "semi-protected" - the *only* protection is autoconfirm?
*
- * @param $action \type{\string} Action to check (default: edit)
- * @return \type{\bool}
+ * @param $action String Action to check (default: edit)
+ * @return Bool
*/
public function isSemiProtected( $action = 'edit' ) {
if ( $this->exists() ) {
@@ -1060,9 +1119,9 @@ class Title {
/**
* Does the title correspond to a protected article?
*
- * @param $action \type{\string} the action the page is protected from,
+ * @param $action String the action the page is protected from,
* by default checks all actions.
- * @return \type{\bool}
+ * @return Bool
*/
public function isProtected( $action = '' ) {
global $wgRestrictionLevels;
@@ -1092,7 +1151,7 @@ class Title {
/**
* Is this a conversion table for the LanguageConverter?
*
- * @return \type{\bool}
+ * @return Bool
*/
public function isConversionTable() {
if(
@@ -1109,7 +1168,7 @@ class Title {
/**
* Is $wgUser watching this page?
*
- * @return \type{\bool}
+ * @return Bool
*/
public function userIsWatching() {
global $wgUser;
@@ -1134,24 +1193,31 @@ class Title {
*
* May provide false positives, but should never provide a false negative.
*
- * @param $action \type{\string} action that permission needs to be checked for
- * @return \type{\bool}
+ * @param $action String action that permission needs to be checked for
+ * @return Bool
*/
public function quickUserCan( $action ) {
return $this->userCan( $action, false );
}
/**
- * Determines if $wgUser is unable to edit this page because it has been protected
+ * Determines if $user is unable to edit this page because it has been protected
* by $wgNamespaceProtection.
*
- * @return \type{\bool}
+ * @param $user User object, $wgUser will be used if not passed
+ * @return Bool
*/
- public function isNamespaceProtected() {
- global $wgNamespaceProtection, $wgUser;
+ public function isNamespaceProtected( User $user = null ) {
+ global $wgNamespaceProtection;
+
+ if ( $user === null ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
+
if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
- if ( $right != '' && !$wgUser->isAllowed( $right ) ) {
+ if ( $right != '' && !$user->isAllowed( $right ) ) {
return true;
}
}
@@ -1162,9 +1228,9 @@ 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}
+ * @param $action String action that permission needs to be checked for
+ * @param $doExpensiveQueries Bool Set this to false to avoid doing unnecessary queries.
+ * @return Bool
*/
public function userCan( $action, $doExpensiveQueries = true ) {
global $wgUser;
@@ -1174,13 +1240,14 @@ class Title {
/**
* Can $user perform $action on this page?
*
- * FIXME: This *does not* check throttles (User::pingLimiter()).
+ * @todo FIXME: This *does not* check throttles (User::pingLimiter()).
*
- * @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 $ignoreErrors \type{\arrayof{\string}} Set this to a list of message keys whose corresponding errors may be ignored.
- * @return \type{\array} Array of arrays of the arguments to wfMsg to explain permissions problems.
+ * @param $action String action that permission needs to be checked for
+ * @param $user User to check
+ * @param $doExpensiveQueries Bool Set this to false to avoid doing unnecessary queries by
+ * skipping checks for cascading protections and user blocks.
+ * @param $ignoreErrors Array of Strings Set this to a list of message keys whose corresponding errors may be ignored.
+ * @return Array of arguments to wfMsg to explain permissions problems.
*/
public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) {
$errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
@@ -1289,13 +1356,13 @@ class Title {
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] ) ) {
+ } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
// A nested array representing multiple errors
$errors = array_merge( $errors, $result );
- } else if ( $result !== '' && is_string( $result ) ) {
+ } elseif ( $result !== '' && is_string( $result ) ) {
// A string representing a message-id
$errors[] = array( $result );
- } else if ( $result === false ) {
+ } elseif ( $result === false ) {
// a generic "We don't want them to do that"
$errors[] = array( 'badaccess-group0' );
}
@@ -1352,7 +1419,7 @@ class Title {
}
# Check $wgNamespaceProtection for restricted namespaces
- if ( $this->isNamespaceProtected() ) {
+ if ( $this->isNamespaceProtected( $user ) ) {
$ns = $this->mNamespace == NS_MAIN ?
wfMsg( 'nstab-main' ) : $this->getNsText();
$errors[] = $this->mNamespace == NS_MEDIAWIKI ?
@@ -1382,9 +1449,9 @@ class Title {
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' );
+ $errors[] = array( 'customcssprotected' );
+ } elseif ( $this->isJsSubpage() && !$user->isAllowed( 'edituserjs' ) ) {
+ $errors[] = array( 'customjsprotected' );
}
}
@@ -1429,9 +1496,9 @@ class Title {
/**
* Check restrictions on cascading pages.
- *
+ *
* @param $action String the action to check
- * @param $user User user to check
+ * @param $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
@@ -1470,7 +1537,7 @@ class Title {
* Check action permissions not already checked in checkQuickPermissions
*
* @param $action String the action to check
- * @param $user User user to check
+ * @param $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
@@ -1516,7 +1583,7 @@ class Title {
* Check that the user isn't blocked from editting.
*
* @param $action String the action to check
- * @param $user User user to check
+ * @param $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
@@ -1524,7 +1591,7 @@ class Title {
* @return Array list of errors
*/
private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
- if( $short && count( $errors ) > 0 ) {
+ if( !$doExpensiveQueries ) {
return $errors;
}
@@ -1534,8 +1601,14 @@ class Title {
$errors[] = array( 'confirmedittext' );
}
- // Edit blocks should not affect reading. Account creation blocks handled at userlogin.
- if ( $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) ) {
+ if ( in_array( $action, array( 'read', 'createaccount', 'unblock' ) ) ){
+ // Edit blocks should not affect reading.
+ // Account creation blocks handled at userlogin.
+ // Unblocking handled in SpecialUnblock
+ } elseif( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ){
+ // Don't block the user from editing their own talk page unless they've been
+ // explicitly blocked from that too.
+ } elseif( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
$block = $user->mBlock;
// This is from OutputPage::blockedPage
@@ -1555,29 +1628,16 @@ class Title {
}
$link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
- $blockid = $block->mId;
+ $blockid = $block->getId();
$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;
- }
- }
+ $blockExpiry = wfMessage( 'infiniteblock' )->text();
} else {
$blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
}
- $intended = $user->mBlock->mAddress;
+ $intended = strval( $user->mBlock->getTarget() );
$errors[] = array( ( $block->mAuto ? 'autoblockedtext' : 'blockedtext' ), $link, $reason, $ip, $name,
$blockid, $blockExpiry, $intended, $blockTimestamp );
@@ -1591,11 +1651,11 @@ class Title {
* 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.
+ * @param $action String action that permission needs to be checked for
+ * @param $user User to check
+ * @param $doExpensiveQueries Bool Set this to false to avoid doing unnecessary queries.
+ * @param $short Bool Set this to true to stop after the first permission error.
+ * @return Array of arrays of the arguments to wfMsg to explain permissions problems.
*/
protected function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true, $short = false ) {
wfProfileIn( __METHOD__ );
@@ -1626,7 +1686,7 @@ 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
+ * @return Mixed An associative array representing any existent title
* protection, or false if there's none.
*/
private function getTitleProtection() {
@@ -1655,9 +1715,9 @@ class Title {
/**
* 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
+ * @param $create_perm String Permission required for creation
+ * @param $reason String Reason for protection
+ * @param $expiry String Expiry timestamp
* @return boolean true
*/
public function updateTitleProtection( $create_perm, $reason, $expiry ) {
@@ -1673,10 +1733,10 @@ class Title {
$dbw = wfGetDB( DB_MASTER );
- $encodedExpiry = Block::encodeExpiry( $expiry, $dbw );
+ $encodedExpiry = $dbw->encodeExpiry( $expiry );
$expiry_description = '';
- if ( $encodedExpiry != 'infinity' ) {
+ if ( $encodedExpiry != $dbw->getInfinity() ) {
$expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ),
$wgContLang->date( $expiry ) , $wgContLang->time( $expiry ) ) . ')';
} else {
@@ -1689,7 +1749,7 @@ class Title {
'pt_namespace' => $namespace,
'pt_title' => $title,
'pt_create_perm' => $create_perm,
- 'pt_timestamp' => Block::encodeExpiry( wfTimestampNow(), $dbw ),
+ 'pt_timestamp' => $dbw->encodeExpiry( wfTimestampNow() ),
'pt_expiry' => $encodedExpiry,
'pt_user' => $wgUser->getId(),
'pt_reason' => $reason,
@@ -1735,7 +1795,7 @@ class Title {
* Would anybody with sufficient privileges be able to move this page?
* Some pages just aren't movable.
*
- * @return \type{\bool} TRUE or FALSE
+ * @return Bool TRUE or FALSE
*/
public function isMovable() {
return MWNamespace::isMovable( $this->getNamespace() ) && $this->getInterwiki() == '';
@@ -1744,7 +1804,7 @@ class Title {
/**
* Can $wgUser read this page?
*
- * @return \type{\bool}
+ * @return Bool
* @todo fold these checks into userCan()
*/
public function userCanRead() {
@@ -1760,7 +1820,7 @@ class Title {
# Not a public wiki, so no shortcut
$useShortcut = false;
} 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
@@ -1792,47 +1852,36 @@ class Title {
} else {
global $wgWhitelistRead;
- /**
- * Always grant access to the login page.
- * Even anons need to be able to log in.
- */
- if ( $this->isSpecial( 'Userlogin' ) || $this->isSpecial( 'Resetpass' ) ) {
+ # Always grant access to the login page.
+ # Even anons need to be able to log in.
+ if ( $this->isSpecial( 'Userlogin' ) || $this->isSpecial( 'ChangePassword' ) ) {
return true;
}
- /**
- * Bail out if there isn't whitelist
- */
+ # Bail out if there isn't whitelist
if ( !is_array( $wgWhitelistRead ) ) {
return false;
}
- /**
- * Check for explicit whitelisting
- */
+ # Check for explicit whitelisting
$name = $this->getPrefixedText();
$dbName = $this->getPrefixedDBKey();
// Check with and without underscores
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
- */
+ # Old settings might have the title prefixed with
+ # a colon for main-namespace pages
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 it's a special page, ditch the subpage bit and check again
if ( $this->getNamespace() == NS_SPECIAL ) {
$name = $this->getDBkey();
- list( $name, /* $subpage */ ) = SpecialPage::resolveAliasWithSubpage( $name );
+ list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
if ( $name === false ) {
# Invalid special page, but we show standard login required message
return false;
@@ -1849,9 +1898,22 @@ class Title {
}
/**
+ * Is this the mainpage?
+ * @note Title::newFromText seams to be sufficiently optimized by the title
+ * cache that we don't need to over-optimize by doing direct comparisons and
+ * acidentally creating new bugs where $title->equals( Title::newFromText() )
+ * ends up reporting something differently than $title->isMainPage();
+ *
+ * @return Bool
+ */
+ public function isMainPage() {
+ return $this->equals( Title::newMainPage() );
+ }
+
+ /**
* Is this a talk page of some sort?
*
- * @return \type{\bool}
+ * @return Bool
*/
public function isTalkPage() {
return MWNamespace::isTalk( $this->getNamespace() );
@@ -1860,7 +1922,7 @@ class Title {
/**
* Is this a subpage?
*
- * @return \type{\bool}
+ * @return Bool
*/
public function isSubpage() {
return MWNamespace::hasSubpages( $this->mNamespace )
@@ -1871,7 +1933,7 @@ class Title {
/**
* Does this have subpages? (Warning, usually requires an extra DB query.)
*
- * @return \type{\bool}
+ * @return Bool
*/
public function hasSubpages() {
if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
@@ -1897,7 +1959,7 @@ class Title {
/**
* Get all subpages of this page.
*
- * @param $limit Maximum number of subpages to fetch; -1 for no limit
+ * @param $limit Int 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
*/
@@ -1927,7 +1989,7 @@ class Title {
* Could this page contain custom CSS or JavaScript, based
* on the title?
*
- * @return \type{\bool}
+ * @return Bool
*/
public function isCssOrJsPage() {
return $this->mNamespace == NS_MEDIAWIKI
@@ -1936,7 +1998,7 @@ class Title {
/**
* Is this a .css or .js subpage of a user page?
- * @return \type{\bool}
+ * @return Bool
*/
public function isCssJsSubpage() {
return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
@@ -1945,8 +2007,8 @@ class Title {
/**
* Is this a *valid* .css or .js subpage of a user page?
*
- * @return \type{\bool}
- * @deprecated
+ * @return Bool
+ * @deprecated since 1.17
*/
public function isValidCssJsSubpage() {
return $this->isCssJsSubpage();
@@ -1966,7 +2028,7 @@ class Title {
/**
* Is this a .css subpage of a user page?
*
- * @return \type{\bool}
+ * @return Bool
*/
public function isCssSubpage() {
return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
@@ -1975,7 +2037,7 @@ class Title {
/**
* Is this a .js subpage of a user page?
*
- * @return \type{\bool}
+ * @return Bool
*/
public function isJsSubpage() {
return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
@@ -1985,12 +2047,12 @@ class Title {
* Protect css subpages of user pages: can $wgUser edit
* this page?
*
- * @return \type{\bool}
+ * @return Bool
* @todo XXX: this might be better using restrictions
*/
public function userCanEditCssSubpage() {
global $wgUser;
- return ( ( $wgUser->isAllowed( 'editusercssjs' ) && $wgUser->isAllowed( 'editusercss' ) )
+ return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
|| preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
}
@@ -1998,19 +2060,19 @@ class Title {
* Protect js subpages of user pages: can $wgUser edit
* this page?
*
- * @return \type{\bool}
+ * @return 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->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
+ || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
}
/**
* Cascading protection: Return true if cascading restrictions apply to this page, false if not.
*
- * @return \type{\bool} If the page is subject to cascading restrictions.
+ * @return Bool If the page is subject to cascading restrictions.
*/
public function isCascadeProtected() {
list( $sources, /* $restrictions */ ) = $this->getCascadeProtectionSources( false );
@@ -2020,20 +2082,20 @@ class Title {
/**
* Cascading protection: Get the source of any cascading restrictions on this page.
*
- * @param $getPages \type{\bool} Whether or not to retrieve the actual pages
+ * @param $getPages 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.
+ * @return Mixed Array of Title objects of the pages from which cascading restrictions
+ * have come, false for none, or true if such restrictions exist, but $getPages
+ * was not set. The restriction array is an array of each type, each of which
+ * contains a array of unique groups.
*/
public function getCascadeProtectionSources( $getPages = true ) {
+ global $wgContLang;
$pagerestrictions = array();
if ( isset( $this->mCascadeSources ) && $getPages ) {
return array( $this->mCascadeSources, $this->mCascadingRestrictions );
- } else if ( isset( $this->mHasCascadingRestrictions ) && !$getPages ) {
+ } elseif ( isset( $this->mHasCascadingRestrictions ) && !$getPages ) {
return array( $this->mHasCascadingRestrictions, $pagerestrictions );
}
@@ -2074,7 +2136,7 @@ class Title {
$purgeExpired = false;
foreach ( $res as $row ) {
- $expiry = Block::decodeExpiry( $row->pr_expiry );
+ $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
if ( $expiry > $now ) {
if ( $getPages ) {
$page_id = $row->pr_page;
@@ -2131,8 +2193,8 @@ 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
+ * @param $res 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 ) {
@@ -2155,13 +2217,14 @@ class Title {
* restrictions from page table (pre 1.10)
*/
public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
+ global $wgContLang;
$dbr = wfGetDB( DB_SLAVE );
$restrictionTypes = $this->getRestrictionTypes();
foreach ( $restrictionTypes as $type ) {
$this->mRestrictions[$type] = array();
- $this->mRestrictionsExpiry[$type] = Block::decodeExpiry( '' );
+ $this->mRestrictionsExpiry[$type] = $wgContLang->formatExpiry( '', TS_MW );
}
$this->mCascadeRestriction = false;
@@ -2195,17 +2258,16 @@ class Title {
$now = wfTimestampNow();
$purgeExpired = false;
+ # Cycle through all the restrictions.
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 ) )
continue;
// This code should be refactored, now that it's being used more generally,
// But I don't really see any harm in leaving it in Block for now -werdna
- $expiry = Block::decodeExpiry( $row->pr_expiry );
+ $expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
// Only apply the restrictions if they haven't expired!
if ( !$expiry || $expiry > $now ) {
@@ -2230,16 +2292,21 @@ class Title {
/**
* Load restrictions from the page_restrictions table
*
- * @param $oldFashionedRestrictions string comma-separated list of page
+ * @param $oldFashionedRestrictions String comma-separated list of page
* restrictions from page table (pre 1.10)
*/
public function loadRestrictions( $oldFashionedRestrictions = null ) {
+ global $wgContLang;
if ( !$this->mRestrictionsLoaded ) {
if ( $this->exists() ) {
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'page_restrictions', '*',
- array( 'pr_page' => $this->getArticleId() ), __METHOD__ );
+ $res = $dbr->select(
+ 'page_restrictions',
+ '*',
+ array( 'pr_page' => $this->getArticleId() ),
+ __METHOD__
+ );
$this->loadRestrictionsFromResultWrapper( $res, $oldFashionedRestrictions );
} else {
@@ -2247,7 +2314,7 @@ class Title {
if ( $title_protection ) {
$now = wfTimestampNow();
- $expiry = Block::decodeExpiry( $title_protection['pt_expiry'] );
+ $expiry = $wgContLang->formatExpiry( $title_protection['pt_expiry'], TS_MW );
if ( !$expiry || $expiry > $now ) {
// Apply the restrictions
@@ -2258,7 +2325,7 @@ class Title {
$this->mTitleProtection = false;
}
} else {
- $this->mRestrictionsExpiry['create'] = Block::decodeExpiry( '' );
+ $this->mRestrictionsExpiry['create'] = $wgContLang->formatExpiry( '', TS_MW );
}
$this->mRestrictionsLoaded = true;
}
@@ -2286,8 +2353,8 @@ class Title {
/**
* Accessor/initialisation for mRestrictions
*
- * @param $action \type{\string} action that permission needs to be checked for
- * @return \type{\arrayof{\string}} the array of groups allowed to edit this article
+ * @param $action String action that permission needs to be checked for
+ * @return Array of Strings the array of groups allowed to edit this article
*/
public function getRestrictions( $action ) {
if ( !$this->mRestrictionsLoaded ) {
@@ -2301,7 +2368,7 @@ 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
+ * @return String|Bool 14-char timestamp, or 'infinity' if the page is protected forever
* or not protected at all, or false if the action is not recognised.
*/
public function getRestrictionExpiry( $action ) {
@@ -2314,13 +2381,14 @@ class Title {
/**
* Is there a version of this page in the deletion archive?
*
- * @return \type{\int} the number of archived revisions
+ * @return Int the number of archived revisions
*/
public function isDeleted() {
if ( $this->getNamespace() < 0 ) {
$n = 0;
} else {
$dbr = wfGetDB( DB_SLAVE );
+
$n = $dbr->selectField( 'archive', 'COUNT(*)',
array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
__METHOD__
@@ -2362,9 +2430,9 @@ 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 Title::GAID_FOR_UPDATE to select
+ * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select
* for update
- * @return \type{\int} the ID
+ * @return Int the ID
*/
public function getArticleID( $flags = 0 ) {
if ( $this->getNamespace() < 0 ) {
@@ -2388,8 +2456,8 @@ 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 Title::GAID_FOR_UPDATE to select for update
- * @return \type{\bool}
+ * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @return Bool
*/
public function isRedirect( $flags = 0 ) {
if ( !is_null( $this->mRedirect ) ) {
@@ -2409,8 +2477,8 @@ 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 Title::GAID_FOR_UPDATE to select for update
- * @return \type{\bool}
+ * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @return Int
*/
public function getLength( $flags = 0 ) {
if ( $this->mLength != -1 ) {
@@ -2429,8 +2497,8 @@ class Title {
/**
* What is the page_latest field for this page?
*
- * @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
+ * @param $flags Int a bit field; may be Title::GAID_FOR_UPDATE to select for update
+ * @return Int or 0 if the page doesn't exist
*/
public function getLatestRevID( $flags = 0 ) {
if ( $this->mLatestID !== false ) {
@@ -2450,11 +2518,11 @@ class Title {
* This clears some fields in this object, and clears any associated
* keys in the "bad links" section of the link cache.
*
- * - This is called from Article::insertNewArticle() to allow
+ * - This is called from Article::doEdit() and Article::insertOn() to allow
* loading of the new page_id. It's also called from
* Article::doDeleteArticle()
*
- * @param $newid \type{\int} the new Article ID
+ * @param $newid Int the new Article ID
*/
public function resetArticleID( $newid ) {
$linkCache = LinkCache::singleton();
@@ -2475,7 +2543,7 @@ class Title {
/**
* Updates page_touched for this page; called from LinksUpdate.php
*
- * @return \type{\bool} true if the update succeded
+ * @return Bool true if the update succeded
*/
public function invalidateCache() {
if ( wfReadOnly() ) {
@@ -2496,15 +2564,16 @@ class Title {
* Prefix some arbitrary text with the namespace or interwiki prefix
* of this object
*
- * @param $name \type{\string} the text
- * @return \type{\string} the prefixed text
+ * @param $name String the text
+ * @return String the prefixed text
* @private
*/
- /* private */ function prefix( $name ) {
+ private function prefix( $name ) {
$p = '';
if ( $this->mInterwiki != '' ) {
$p = $this->mInterwiki . ':';
}
+
if ( 0 != $this->mNamespace ) {
$p .= $this->getNsText() . ':';
}
@@ -2516,7 +2585,7 @@ class Title {
* Note that this doesn't pick up many things that could be wrong with titles, but that
* replacing this regex with something valid will make many titles valid.
*
- * @return string regex string
+ * @return String regex string
*/
static function getTitleInvalidRegex() {
static $rxTc = false;
@@ -2541,7 +2610,7 @@ class Title {
/**
* Capitalize a text string for a title if it belongs to a namespace that capitalizes
*
- * @param $text string containing title to capitalize
+ * @param $text String containing title to capitalize
* @param $ns int namespace index, defaults to NS_MAIN
* @return String containing capitalized title
*/
@@ -2564,14 +2633,12 @@ class Title {
* namespace prefixes, sets the other forms, and canonicalizes
* everything.
*
- * @return \type{\bool} true on success
+ * @return Bool true on success
*/
private function secureAndSplit() {
global $wgContLang, $wgLocalInterwiki;
# Initialisation
- $rxTc = self::getTitleInvalidRegex();
-
$this->mInterwiki = $this->mFragment = '';
$this->mNamespace = $this->mDefaultNamespace; # Usually NS_MAIN
@@ -2586,7 +2653,6 @@ class Title {
# Note: use of the /u option on preg_replace here will cause
# input with invalid UTF-8 sequences to be nullified out in PHP 5.2.x,
# conveniently disabling them.
- #
$dbkey = preg_replace( '/[ _\xA0\x{1680}\x{180E}\x{2000}-\x{200A}\x{2028}\x{2029}\x{202F}\x{205F}\x{3000}]+/u', '_', $dbkey );
$dbkey = trim( $dbkey, '_' );
@@ -2603,7 +2669,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
@@ -2623,9 +2689,11 @@ class Title {
# 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] ) ) {
- return false; # Disallow Talk:File:x type titles...
- } else if ( Interwiki::isValidInterwiki( $x[1] ) ) {
- return false; # Disallow Talk:Interwiki:x type titles...
+ # Disallow Talk:File:x type titles...
+ return false;
+ } elseif ( Interwiki::isValidInterwiki( $x[1] ) ) {
+ # Disallow Talk:Interwiki:x type titles...
+ return false;
}
}
} elseif ( Interwiki::isValidInterwiki( $p ) ) {
@@ -2641,7 +2709,7 @@ class Title {
# Redundant interwiki prefix to the local wiki
if ( $wgLocalInterwiki !== false
- && 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) )
+ && 0 == strcasecmp( $this->mInterwiki, $wgLocalInterwiki ) )
{
if ( $dbkey == '' ) {
# Can't have an empty self-link
@@ -2667,13 +2735,12 @@ class Title {
} while ( true );
# We already know that some pages won't be in the database!
- #
if ( $this->mInterwiki != '' || NS_SPECIAL == $this->mNamespace ) {
$this->mArticleID = 0;
}
$fragment = strstr( $dbkey, '#' );
if ( false !== $fragment ) {
- $this->setFragment( preg_replace( '/^#_*/', '#', $fragment ) );
+ $this->setFragment( $fragment );
$dbkey = substr( $dbkey, 0, strlen( $dbkey ) - strlen( $fragment ) );
# remove whitespace again: prevents "Foo_bar_#"
# becoming "Foo_bar_"
@@ -2681,79 +2748,65 @@ class Title {
}
# Reject illegal characters.
- #
+ $rxTc = self::getTitleInvalidRegex();
if ( preg_match( $rxTc, $dbkey ) ) {
return false;
}
- /**
- * Pages with "/./" or "/../" appearing in the URLs will often be un-
- * reachable due to the way web browsers deal with 'relative' URLs.
- * Also, they conflict with subpage syntax. Forbid them explicitly.
- */
+ # Pages with "/./" or "/../" appearing in the URLs will often be un-
+ # reachable due to the way web browsers deal with 'relative' URLs.
+ # Also, they conflict with subpage syntax. Forbid them explicitly.
if ( strpos( $dbkey, '.' ) !== false &&
- ( $dbkey === '.' || $dbkey === '..' ||
- strpos( $dbkey, './' ) === 0 ||
- strpos( $dbkey, '../' ) === 0 ||
- strpos( $dbkey, '/./' ) !== false ||
- strpos( $dbkey, '/../' ) !== false ||
- substr( $dbkey, -2 ) == '/.' ||
- substr( $dbkey, -3 ) == '/..' ) )
+ ( $dbkey === '.' || $dbkey === '..' ||
+ strpos( $dbkey, './' ) === 0 ||
+ strpos( $dbkey, '../' ) === 0 ||
+ strpos( $dbkey, '/./' ) !== false ||
+ strpos( $dbkey, '/../' ) !== false ||
+ substr( $dbkey, -2 ) == '/.' ||
+ substr( $dbkey, -3 ) == '/..' ) )
{
return false;
}
- /**
- * Magic tilde sequences? Nu-uh!
- */
+ # Magic tilde sequences? Nu-uh!
if ( strpos( $dbkey, '~~~' ) !== false ) {
return false;
}
- /**
- * Limit the size of titles to 255 bytes.
- * This is typically the size of the underlying database field.
- * We make an exception for special pages, which don't need to be stored
- * in the database, and may edge over 255 bytes due to subpage syntax
- * for long titles, e.g. [[Special:Block/Long name]]
- */
+ # Limit the size of titles to 255 bytes. This is typically the size of the
+ # underlying database field. We make an exception for special pages, which
+ # don't need to be stored in the database, and may edge over 255 bytes due
+ # to subpage syntax for long titles, e.g. [[Special:Block/Long name]]
if ( ( $this->mNamespace != NS_SPECIAL && strlen( $dbkey ) > 255 ) ||
strlen( $dbkey ) > 512 )
{
return false;
}
- /**
- * Normally, all wiki links are forced to have
- * an initial capital letter so [[foo]] and [[Foo]]
- * point to the same place.
- *
- * Don't force it for interwikis, since the other
- * site might be case-sensitive.
- */
+ # Normally, all wiki links are forced to have an initial capital letter so [[foo]]
+ # and [[Foo]] point to the same place. Don't force it for interwikis, since the
+ # other site might be case-sensitive.
$this->mUserCaseDBKey = $dbkey;
if ( $this->mInterwiki == '' ) {
$dbkey = self::capitalize( $dbkey, $this->mNamespace );
}
- /**
- * Can't make a link to a namespace alone...
- * "empty" local links can only be self-links
- * with a fragment identifier.
- */
- if ( $dbkey == '' &&
- $this->mInterwiki == '' &&
- $this->mNamespace != NS_MAIN ) {
+ # Can't make a link to a namespace alone... "empty" local links can only be
+ # self-links with a fragment identifier.
+ if ( $dbkey == '' && $this->mInterwiki == '' && $this->mNamespace != NS_MAIN ) {
return false;
}
+
// Allow IPv6 usernames to start with '::' by canonicalizing IPv6 titles.
// IP names are not allowed for accounts, and can only be referring to
// edits from the IP. Given '::' abbreviations and caps/lowercaps,
// there are numerous ways to present the same IP. Having sp:contribs scan
// them all is silly and having some show the edits and others not is
// inconsistent. Same for talk/userpages. Keep them normalized instead.
- $dbkey = ( $this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK ) ?
- IP::sanitizeIP( $dbkey ) : $dbkey;
+ $dbkey = ( $this->mNamespace == NS_USER || $this->mNamespace == NS_USER_TALK )
+ ? IP::sanitizeIP( $dbkey )
+ : $dbkey;
+
// Any remaining initial :s are illegal.
if ( $dbkey !== '' && ':' == $dbkey { 0 } ) {
return false;
@@ -2776,7 +2829,7 @@ class Title {
* Deprecated for public use, use Title::makeTitle() with fragment parameter.
* Still in active use privately.
*
- * @param $fragment \type{\string} text
+ * @param $fragment String text
*/
public function setFragment( $fragment ) {
$this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
@@ -2816,7 +2869,7 @@ class Title {
* @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
+ * @return Array of Title objects linking here
*/
public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
$linkCache = LinkCache::singleton();
@@ -2859,7 +2912,7 @@ class Title {
* On heavily-used templates it will max out the memory.
*
* @param $options Array: may be FOR UPDATE
- * @return \type{\arrayof{Title}} the Title objects linking here
+ * @return Array of Title the Title objects linking here
*/
public function getTemplateLinksTo( $options = array() ) {
return $this->getLinksTo( $options, 'templatelinks', 'tl' );
@@ -2869,7 +2922,7 @@ class Title {
* Get an array of Title objects referring to non-existent articles linked from this page
*
* @todo check if needed (used only in SpecialBrokenRedirects.php, and should use redirect table in this case)
- * @return \type{\arrayof{Title}} the Title objects
+ * @return Array of Title the Title objects
*/
public function getBrokenLinksFrom() {
if ( $this->getArticleId() == 0 ) {
@@ -2906,7 +2959,7 @@ class Title {
* Get a list of URLs to purge from the Squid cache when this
* page changes
*
- * @return \type{\arrayof{\string}} the URLs
+ * @return Array of String the URLs
*/
public function getSquidURLs() {
global $wgContLang;
@@ -2942,8 +2995,8 @@ class Title {
/**
* Move this page without authentication
*
- * @param $nt \type{Title} the new page Title
- * @return \type{\mixed} true on success, getUserPermissionsErrors()-like array on failure
+ * @param $nt Title the new page Title
+ * @return Mixed true on success, getUserPermissionsErrors()-like array on failure
*/
public function moveNoAuth( &$nt ) {
return $this->moveTo( $nt, false );
@@ -2953,11 +3006,11 @@ 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 $auth \type{\bool} indicates whether $wgUser's permissions
+ * @param $nt Title the new title
+ * @param $auth Bool indicates whether $wgUser's permissions
* should be checked
- * @param $reason \type{\string} is the log summary of the move, used for spam checking
- * @return \type{\mixed} True on success, getUserPermissionsErrors()-like array on failure
+ * @param $reason String is the log summary of the move, used for spam checking
+ * @return Mixed True on success, getUserPermissionsErrors()-like array on failure
*/
public function isValidMoveOperation( &$nt, $auth = true, $reason = '' ) {
global $wgUser;
@@ -2989,28 +3042,13 @@ class Title {
}
if ( ( $this->getDBkey() == '' ) ||
( !$oldid ) ||
- ( $nt->getDBkey() == '' ) ) {
+ ( $nt->getDBkey() == '' ) ) {
$errors[] = array( 'badarticleerror' );
}
// Image-specific checks
if ( $this->getNamespace() == NS_FILE ) {
- if ( $nt->getNamespace() != NS_FILE ) {
- $errors[] = array( 'imagenocrossnamespace' );
- }
- $file = wfLocalFile( $this );
- if ( $file->exists() ) {
- if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
- $errors[] = array( 'imageinvalidfilename' );
- }
- if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
- $errors[] = array( 'imagetypemismatch' );
- }
- }
- $destfile = wfLocalFile( $nt );
- if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destfile->exists() && wfFindFile( $nt ) ) {
- $errors[] = array( 'file-exists-sharedrepo' );
- }
+ $errors = array_merge( $errors, $this->validateFileMoveOperation( $nt ) );
}
if ( $nt->getNamespace() == NS_FILE && $this->getNamespace() != NS_FILE ) {
@@ -3058,23 +3096,66 @@ class Title {
}
/**
+ * Check if the requested move target is a valid file move target
+ * @param Title $nt Target title
+ * @return array List of errors
+ */
+ protected function validateFileMoveOperation( $nt ) {
+ global $wgUser;
+
+ $errors = array();
+
+ // wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below
+
+ $file = wfLocalFile( $this );
+ if ( $file->exists() ) {
+ if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
+ $errors[] = array( 'imageinvalidfilename' );
+ }
+ if ( !File::checkExtensionCompatibility( $file, $nt->getDBkey() ) ) {
+ $errors[] = array( 'imagetypemismatch' );
+ }
+ }
+
+ if ( $nt->getNamespace() != NS_FILE ) {
+ $errors[] = array( 'imagenocrossnamespace' );
+ // From here we want to do checks on a file object, so if we can't
+ // create one, we must return.
+ return $errors;
+ }
+
+ // wfFindFile( $nt ) / wfLocalFile( $nt ) is allowed below here
+
+ $destFile = wfLocalFile( $nt );
+ if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
+ $errors[] = array( 'file-exists-sharedrepo' );
+ }
+
+ return $errors;
+ }
+
+ /**
* Move a title to a new location
*
- * @param $nt \type{Title} the new title
- * @param $auth \type{\bool} indicates whether $wgUser's permissions
+ * @param $nt Title the new title
+ * @param $auth Bool indicates whether $wgUser's permissions
* should be checked
- * @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.
+ * @param $reason String the reason for the move
+ * @param $createRedirect Bool Whether to create a redirect from the old title to the new title.
* Ignored if the user doesn't have the suppressredirect right.
- * @return \type{\mixed} true on success, getUserPermissionsErrors()-like array on failure
+ * @return Mixed true on success, getUserPermissionsErrors()-like array on failure
*/
public function moveTo( &$nt, $auth = true, $reason = '', $createRedirect = true ) {
+ global $wgUser;
$err = $this->isValidMoveOperation( $nt, $auth, $reason );
if ( is_array( $err ) ) {
+ // Auto-block user's IP if the account was "hard" blocked
+ $wgUser->spreadAnyEditBlock();
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
+ // If it is a file, move it first. It is done before all other moving stuff is
+ // done because it's hard to revert
$dbw = wfGetDB( DB_MASTER );
if ( $this->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $this );
@@ -3087,38 +3168,42 @@ class Title {
}
$dbw->begin(); # If $file was a LocalFile, its transaction would have closed our own.
- $pageid = $this->getArticleID( GAID_FOR_UPDATE );
+ $pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
$protected = $this->isProtected();
- if ( $nt->exists() ) {
- $err = $this->moveOverExistingRedirect( $nt, $reason, $createRedirect );
- $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 ) - ( $nt->exists() ? 1 : 0 );
+ // Do the actual move
+ $err = $this->moveToInternal( $nt, $reason, $createRedirect );
if ( is_array( $err ) ) {
- # FIXME: What about the File we have already moved?
+ # @todo FIXME: What about the File we have already moved?
$dbw->rollback();
return $err;
}
+
$redirid = $this->getArticleID();
// 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(
+ $prefixes = $dbw->select(
'categorylinks',
- 'cl_sortkey_prefix',
+ array( 'cl_sortkey_prefix', 'cl_to' ),
array( 'cl_from' => $pageid ),
__METHOD__
);
- $dbw->update( 'categorylinks',
- array(
- 'cl_sortkey' => Collation::singleton()->getSortKey(
- $nt->getCategorySortkey( $prefix ) ),
- 'cl_timestamp=cl_timestamp' ),
- array( 'cl_from' => $pageid ),
- __METHOD__ );
+ foreach ( $prefixes as $prefixRow ) {
+ $prefix = $prefixRow->cl_sortkey_prefix;
+ $catTo = $prefixRow->cl_to;
+ $dbw->update( 'categorylinks',
+ array(
+ 'cl_sortkey' => Collation::singleton()->getSortKey(
+ $nt->getCategorySortkey( $prefix ) ),
+ 'cl_timestamp=cl_timestamp' ),
+ array(
+ 'cl_from' => $pageid,
+ 'cl_to' => $catTo ),
+ __METHOD__
+ );
+ }
if ( $protected ) {
# Protect the redirect title as the title used to be...
@@ -3141,7 +3226,8 @@ class Title {
if ( $reason ) {
$comment .= wfMsgForContent( 'colon-separator' ) . $reason;
}
- $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) ); // FIXME: $params?
+ // @todo FIXME: $params?
+ $log->addEntry( 'move_prot', $nt, $comment, array( $this->getPrefixedText() ) );
}
# Update watchlists
@@ -3161,7 +3247,7 @@ class Title {
$u->doUpdate();
$dbw->commit();
-
+
# Update site_stats
if ( $this->isContentPage() && !$nt->isContentPage() ) {
# No longer a content page
@@ -3181,21 +3267,21 @@ class Title {
if ( $u ) {
$u->doUpdate();
}
+
# Update message cache for interface messages
- 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 ) {
- $wgMessageCache->replace( $this->getDBkey(), false );
+ MessageCache::singleton()->replace( $this->getDBkey(), false );
} else {
$oldarticle = new Article( $this );
- $wgMessageCache->replace( $this->getDBkey(), $oldarticle->getContent() );
+ MessageCache::singleton()->replace( $this->getDBkey(), $oldarticle->getContent() );
}
}
if ( $nt->getNamespace() == NS_MEDIAWIKI ) {
$newarticle = new Article( $nt );
- $wgMessageCache->replace( $nt->getDBkey(), $newarticle->getContent() );
+ MessageCache::singleton()->replace( $nt->getDBkey(), $newarticle->getContent() );
}
global $wgUser;
@@ -3204,69 +3290,75 @@ class Title {
}
/**
- * Move page to a title which is at present a redirect to the
- * source page
+ * Move page to a title which is either a redirect to the
+ * source page or nonexistent
*
- * @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.
- * Ignored if the user doesn't have the suppressredirect right
+ * @param $nt Title the page to move to, which should be a redirect or nonexistent
+ * @param $reason String The reason for the move
+ * @param $createRedirect Bool Whether to leave a redirect at the old title. Ignored
+ * if the user doesn't have the suppressredirect right
*/
- private function moveOverExistingRedirect( &$nt, $reason = '', $createRedirect = true ) {
- global $wgUseSquid, $wgUser, $wgContLang;
+ private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) {
+ global $wgUser, $wgContLang;
- $comment = wfMsgForContent( '1movedto2_redir', $this->getPrefixedText(), $nt->getPrefixedText() );
+ $moveOverRedirect = $nt->exists();
+
+ $commentMsg = ( $moveOverRedirect ? '1movedto2_redir' : '1movedto2' );
+ $comment = wfMsgForContent( $commentMsg, $this->getPrefixedText(), $nt->getPrefixedText() );
if ( $reason ) {
$comment .= wfMsgForContent( 'colon-separator' ) . $reason;
}
- # Truncate for whole multibyte characters. +5 bytes for ellipsis
- $comment = $wgContLang->truncate( $comment, 250 );
+ # Truncate for whole multibyte characters.
+ $comment = $wgContLang->truncate( $comment, 255 );
- $now = wfTimestampNow();
- $newid = $nt->getArticleID();
$oldid = $this->getArticleID();
$latest = $this->getLatestRevID();
$dbw = wfGetDB( DB_MASTER );
- $rcts = $dbw->timestamp( $nt->getEarliestRevTime() );
- $newns = $nt->getNamespace();
- $newdbk = $nt->getDBkey();
-
- # Delete the old redirect. We don't save it to history since
- # by definition if we've got here it's rather uninteresting.
- # We have to remove it so that the next step doesn't trigger
- # a conflict on the unique namespace+title index...
- $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
- if ( !$dbw->cascadingDeletes() ) {
- $dbw->delete( 'revision', array( 'rev_page' => $newid ), __METHOD__ );
- global $wgUseTrackbacks;
- if ( $wgUseTrackbacks ) {
- $dbw->delete( 'trackbacks', array( 'tb_page' => $newid ), __METHOD__ );
+ if ( $moveOverRedirect ) {
+ $rcts = $dbw->timestamp( $nt->getEarliestRevTime() );
+
+ $newid = $nt->getArticleID();
+ $newns = $nt->getNamespace();
+ $newdbk = $nt->getDBkey();
+
+ # Delete the old redirect. We don't save it to history since
+ # by definition if we've got here it's rather uninteresting.
+ # We have to remove it so that the next step doesn't trigger
+ # a conflict on the unique namespace+title index...
+ $dbw->delete( 'page', array( 'page_id' => $newid ), __METHOD__ );
+ if ( !$dbw->cascadingDeletes() ) {
+ $dbw->delete( 'revision', array( 'rev_page' => $newid ), __METHOD__ );
+ global $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__ );
+ $dbw->delete( 'templatelinks', array( 'tl_from' => $newid ), __METHOD__ );
+ $dbw->delete( 'externallinks', array( 'el_from' => $newid ), __METHOD__ );
+ $dbw->delete( 'langlinks', array( 'll_from' => $newid ), __METHOD__ );
+ $dbw->delete( 'iwlinks', array( 'iwl_from' => $newid ), __METHOD__ );
+ $dbw->delete( 'redirect', array( 'rd_from' => $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__ );
- $dbw->delete( 'templatelinks', array( 'tl_from' => $newid ), __METHOD__ );
- $dbw->delete( 'externallinks', array( 'el_from' => $newid ), __METHOD__ );
- $dbw->delete( 'langlinks', array( 'll_from' => $newid ), __METHOD__ );
- $dbw->delete( 'redirect', array( 'rd_from' => $newid ), __METHOD__ );
- }
- // If the redirect was recently created, it may have an entry in recentchanges still
- $dbw->delete( 'recentchanges',
- array( 'rc_timestamp' => $rcts, 'rc_namespace' => $newns, 'rc_title' => $newdbk, 'rc_new' => 1 ),
- __METHOD__
- );
+ // If the target page was recently created, it may have an entry in recentchanges still
+ $dbw->delete( 'recentchanges',
+ array( 'rc_timestamp' => $rcts, 'rc_namespace' => $newns, 'rc_title' => $newdbk, 'rc_new' => 1 ),
+ __METHOD__
+ );
+ }
# Save a null revision in the page's history notifying of the move
$nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
+ if ( !is_object( $nullRevision ) ) {
+ throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
+ }
$nullRevId = $nullRevision->insertOn( $dbw );
- $article = new Article( $this );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $wgUser ) );
-
+ $now = wfTimestampNow();
# Change the name of the target page:
$dbw->update( 'page',
/* SET */ array(
@@ -3280,6 +3372,10 @@ class Title {
);
$nt->resetArticleID( $oldid );
+ $article = new Article( $nt );
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $wgUser ) );
+ $article->setCachedLastEditTime( $now );
+
# Recreate the redirect, this time in the other direction.
if ( $createRedirect || !$wgUser->isAllowed( 'suppressredirect' ) ) {
$mwRedir = MagicWord::get( 'redirect' );
@@ -3312,103 +3408,17 @@ class Title {
# Log the move
$log = new LogPage( 'move' );
- $log->addEntry( 'move_redir', $this, $reason, array( 1 => $nt->getPrefixedText(), 2 => $redirectSuppressed ) );
-
- # Purge squid
- if ( $wgUseSquid ) {
- $urls = array_merge( $nt->getSquidURLs(), $this->getSquidURLs() );
- $u = new SquidUpdate( $urls );
- $u->doUpdate();
- }
-
- }
-
- /**
- * Move page to non-existing 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 $wgUser, $wgContLang;
-
- $comment = wfMsgForContent( '1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
- if ( $reason ) {
- $comment .= wfMsgExt( 'colon-separator',
- array( 'escapenoentities', 'content' ) );
- $comment .= $reason;
- }
- # Truncate for whole multibyte characters. +5 bytes for ellipsis
- $comment = $wgContLang->truncate( $comment, 250 );
-
- $oldid = $this->getArticleID();
- $latest = $this->getLatestRevId();
-
- $dbw = wfGetDB( DB_MASTER );
- $now = $dbw->timestamp();
-
- # Save a null revision in the page's history notifying of the move
- $nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true );
- if ( !is_object( $nullRevision ) ) {
- throw new MWException( 'No valid null revision produced in ' . __METHOD__ );
- }
- $nullRevId = $nullRevision->insertOn( $dbw );
-
- $article = new Article( $this );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $wgUser ) );
-
- # Rename page entry
- $dbw->update( 'page',
- /* SET */ array(
- 'page_touched' => $now,
- 'page_namespace' => $nt->getNamespace(),
- 'page_title' => $nt->getDBkey(),
- 'page_latest' => $nullRevId,
- ),
- /* WHERE */ array( 'page_id' => $oldid ),
- __METHOD__
- );
- $nt->resetArticleID( $oldid );
-
- if ( $createRedirect || !$wgUser->isAllowed( 'suppressredirect' ) ) {
- # Insert redirect
- $mwRedir = MagicWord::get( 'redirect' );
- $redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
- $redirectArticle = new Article( $this );
- $newid = $redirectArticle->insertOn( $dbw );
- $redirectRevision = new Revision( array(
- 'page' => $newid,
- 'comment' => $comment,
- 'text' => $redirectText ) );
- $redirectRevision->insertOn( $dbw );
- $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
-
- wfRunHooks( 'NewRevisionFromEditComplete', array( $redirectArticle, $redirectRevision, false, $wgUser ) );
+ $logType = ( $moveOverRedirect ? 'move_redir' : 'move' );
+ $log->addEntry( $logType, $this, $reason, array( 1 => $nt->getPrefixedText(), 2 => $redirectSuppressed ) );
- # Record the just-created redirect's linking to the page
- $dbw->insert( 'pagelinks',
- array(
- 'pl_from' => $newid,
- 'pl_namespace' => $nt->getNamespace(),
- 'pl_title' => $nt->getDBkey() ),
- __METHOD__ );
- $redirectSuppressed = false;
+ # Purge caches for old and new titles
+ if ( $moveOverRedirect ) {
+ # A simple purge is enough when moving over a redirect
+ $nt->purgeSquid();
} else {
- $this->resetArticleID( 0 );
- $redirectSuppressed = true;
+ # Purge caches as per article creation, including any pages that link to this title
+ Article::onArticleCreate( $nt );
}
-
- # Log the move
- $log = new LogPage( 'move' );
- $log->addEntry( 'move', $this, $reason, array( 1 => $nt->getPrefixedText(), 2 => $redirectSuppressed ) );
-
- # Purge caches as per article creation
- Article::onArticleCreate( $nt );
-
- # Purge old title from squid
- # The new title, and links to the new title, are purged in Article::onArticleCreate()
$this->purgeSquid();
}
@@ -3418,10 +3428,11 @@ class Title {
* @param $nt Title Move target
* @param $auth bool Whether $wgUser's permissions should be checked
* @param $reason string The reason for the move
- * @param $createRedirect bool Whether to create redirects from the old subpages to the new ones
- * Ignored if the user doesn't have the 'suppressredirect' right
+ * @param $createRedirect bool Whether to create redirects from the old subpages to
+ * the new ones Ignored if the user doesn't have the 'suppressredirect' right
* @return mixed array with old page titles as keys, and strings (new page titles) or
- * arrays (errors) as values, or an error array with numeric indices if no pages were moved
+ * arrays (errors) as values, or an error array with numeric indices if no pages
+ * were moved
*/
public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
global $wgMaximumMovedPages;
@@ -3488,7 +3499,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}
+ * @return Bool
*/
public function isSingleRevRedirect() {
$dbw = wfGetDB( DB_MASTER );
@@ -3525,8 +3536,8 @@ class Title {
* Checks if $this can be moved to a given Title
* - Selects for update, so don't call it unless you mean business
*
- * @param $nt \type{Title} the new title to check
- * @return \type{\bool} TRUE or FALSE
+ * @param $nt Title the new title to check
+ * @return Bool
*/
public function isValidMoveTarget( $nt ) {
# Is it an existing file?
@@ -3567,7 +3578,7 @@ class Title {
/**
* Can this title be added to a user's watchlist?
*
- * @return \type{\bool} TRUE or FALSE
+ * @return Bool TRUE or FALSE
*/
public function isWatchable() {
return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
@@ -3577,24 +3588,29 @@ class Title {
* Get categories to which this Title belongs and return an array of
* categories' names.
*
- * @return \type{\array} array an array of parents in the form:
- * $parent => $currentarticle
+ * @return Array of parents in the form:
+ * $parent => $currentarticle
*/
public function getParentCategories() {
global $wgContLang;
- $titlekey = $this->getArticleId();
- $dbr = wfGetDB( DB_SLAVE );
- $categorylinks = $dbr->tableName( 'categorylinks' );
+ $data = array();
- # NEW SQL
- $sql = "SELECT * FROM $categorylinks"
- . " WHERE cl_from='$titlekey'"
- . " AND cl_from <> '0'"
- . " ORDER BY cl_sortkey";
+ $titleKey = $this->getArticleId();
- $res = $dbr->query( $sql );
- $data = array();
+ if ( $titleKey === 0 ) {
+ return $data;
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $res = $dbr->select( 'categorylinks', '*',
+ array(
+ 'cl_from' => $titleKey,
+ ),
+ __METHOD__,
+ array()
+ );
if ( $dbr->numRows( $res ) > 0 ) {
foreach ( $res as $row ) {
@@ -3608,8 +3624,8 @@ class Title {
/**
* 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
+ * @param $children Array with the children in the keys, to check for circular refs
+ * @return Array Tree of parent categories
*/
public function getParentCategoryTree( $children = array() ) {
$stack = array();
@@ -3632,12 +3648,11 @@ class Title {
return $stack;
}
-
/**
* Get an associative array for selecting this title from
* the "page" table
*
- * @return \type{\array} Selection array
+ * @return Array suitable for the $where parameter of DB::select()
*/
public function pageCond() {
if ( $this->mArticleID > 0 ) {
@@ -3651,9 +3666,9 @@ 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} Title::GAID_FOR_UPDATE
- * @return \twotypes{\int,\bool} Old revision ID, or FALSE if none exists
+ * @param $revId Int Revision ID. Get the revision that was before this one.
+ * @param $flags Int Title::GAID_FOR_UPDATE
+ * @return Int|Bool Old revision ID, or FALSE if none exists
*/
public function getPreviousRevisionID( $revId, $flags = 0 ) {
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
@@ -3670,9 +3685,9 @@ 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} Title::GAID_FOR_UPDATE
- * @return \twotypes{\int,\bool} Next revision ID, or FALSE if none exists
+ * @param $revId Int Revision ID. Get the revision that was after this one.
+ * @param $flags Int Title::GAID_FOR_UPDATE
+ * @return Int|Bool Next revision ID, or FALSE if none exists
*/
public function getNextRevisionID( $revId, $flags = 0 ) {
$db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
@@ -3689,68 +3704,71 @@ class Title {
/**
* Get the first revision of the page
*
- * @param $flags \type{\int} Title::GAID_FOR_UPDATE
- * @return Revision (or NULL if page doesn't exist)
+ * @param $flags Int Title::GAID_FOR_UPDATE
+ * @return Revision|Null if page doesn't exist
*/
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 ) {
- return null;
- } else {
- return new Revision( $row );
+ if ( $pageId ) {
+ $db = ( $flags & self::GAID_FOR_UPDATE ) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+ $row = $db->selectRow( 'revision', '*',
+ array( 'rev_page' => $pageId ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
+ );
+ if ( $row ) {
+ return new Revision( $row );
+ }
}
+ return null;
}
/**
- * Check if this is a new page
+ * Get the oldest revision timestamp of this page
*
- * @return bool
+ * @param $flags Int Title::GAID_FOR_UPDATE
+ * @return String: MW timestamp
*/
- public function isNewPage() {
- $dbr = wfGetDB( DB_SLAVE );
- return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
+ public function getEarliestRevTime( $flags = 0 ) {
+ $rev = $this->getFirstRevision( $flags );
+ return $rev ? $rev->getTimestamp() : null;
}
/**
- * Get the oldest revision timestamp of this page
+ * Check if this is a new page
*
- * @return String: MW timestamp
+ * @return bool
*/
- public function getEarliestRevTime() {
+ public function isNewPage() {
$dbr = wfGetDB( DB_SLAVE );
- if ( $this->exists() ) {
- $min = $dbr->selectField( 'revision',
- 'MIN(rev_timestamp)',
- array( 'rev_page' => $this->getArticleId() ),
- __METHOD__ );
- return wfTimestampOrNull( TS_MW, $min );
- }
- return null;
+ return (bool)$dbr->selectField( 'page', 'page_is_new', $this->pageCond(), __METHOD__ );
}
/**
- * Get the number of revisions between the given revision IDs.
+ * Get the number of revisions between the given revision.
* Used for diffs and other things that really need it.
*
- * @param $old \type{\int} Revision ID.
- * @param $new \type{\int} Revision ID.
- * @return \type{\int} Number of revisions between these IDs.
+ * @param $old int|Revision Old revision or rev ID (first before range)
+ * @param $new int|Revision New revision or rev ID (first after range)
+ * @return Int Number of revisions between these revisions.
*/
public function countRevisionsBetween( $old, $new ) {
+ if ( !( $old instanceof Revision ) ) {
+ $old = Revision::newFromTitle( $this, (int)$old );
+ }
+ if ( !( $new instanceof Revision ) ) {
+ $new = Revision::newFromTitle( $this, (int)$new );
+ }
+ if ( !$old || !$new ) {
+ return 0; // nothing to compare
+ }
$dbr = wfGetDB( DB_SLAVE );
return (int)$dbr->selectField( 'revision', 'count(*)',
- 'rev_page = ' . intval( $this->getArticleId() ) .
- ' AND rev_id > ' . intval( $old ) .
- ' AND rev_id < ' . intval( $new ),
+ array(
+ 'rev_page' => $this->getArticleId(),
+ 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
+ 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
+ ),
__METHOD__
);
}
@@ -3759,30 +3777,38 @@ 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}
+ * @param $old int|Revision Old revision or rev ID (first before range)
+ * @param $new int|Revision New revision or rev ID (first after range)
+ * @param $limit Int Maximum number of authors
+ * @return Int Number of revision authors between these revisions.
*/
- 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',
+ public function countAuthorsBetween( $old, $new, $limit ) {
+ if ( !( $old instanceof Revision ) ) {
+ $old = Revision::newFromTitle( $this, (int)$old );
+ }
+ if ( !( $new instanceof Revision ) ) {
+ $new = Revision::newFromTitle( $this, (int)$new );
+ }
+ if ( !$old || !$new ) {
+ return 0; // nothing to compare
+ }
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
array(
- 'rev_page = ' . $this->getArticleID(),
- 'rev_id > ' . (int)$fromRevId,
- 'rev_id < ' . (int)$toRevId
+ 'rev_page' => $this->getArticleID(),
+ 'rev_timestamp > ' . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
+ 'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
), __METHOD__,
- array( 'LIMIT' => $limit )
+ array( 'LIMIT' => $limit + 1 ) // add one so caller knows it was truncated
);
- return (int)$db->numRows( $res );
+ return (int)$dbr->numRows( $res );
}
/**
* Compare with another title.
*
- * @param $title \type{Title}
- * @return \type{\bool} TRUE or FALSE
+ * @param $title Title
+ * @return Bool
*/
public function equals( Title $title ) {
// Note: === is necessary for proper matching of number-like titles.
@@ -3793,7 +3819,10 @@ class Title {
/**
* Callback for usort() to do title sorts by (namespace, title)
- *
+ *
+ * @param $a Title
+ * @param $b Title
+ *
* @return Integer: result of string comparison, or namespace comparison
*/
public static function compare( $a, $b ) {
@@ -3807,7 +3836,7 @@ class Title {
/**
* Return a string representation of this title
*
- * @return \type{\string} String representation of this title
+ * @return String representation of this title
*/
public function __toString() {
return $this->getPrefixedText();
@@ -3820,7 +3849,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}
+ * @return Bool
*/
public function exists() {
return $this->getArticleId() != 0;
@@ -3840,7 +3869,7 @@ class Title {
* existing code, but we might want to add an optional parameter to skip
* it and any other expensive checks.)
*
- * @return \type{\bool}
+ * @return Bool
*/
public function isAlwaysKnown() {
if ( $this->mInterwiki != '' ) {
@@ -3849,17 +3878,17 @@ class Title {
switch( $this->mNamespace ) {
case NS_MEDIA:
case NS_FILE:
- return (bool)wfFindFile( $this ); // file exists, possibly in a foreign repo
+ // file exists, possibly in a foreign repo
+ return (bool)wfFindFile( $this );
case NS_SPECIAL:
- return SpecialPage::exists( $this->getDBkey() ); // valid special page
+ // valid special page
+ return SpecialPageFactory::exists( $this->getDBkey() );
case NS_MAIN:
- return $this->mDbkeyform == ''; // selflink, possibly with fragment
+ // selflink, possibly with fragment
+ return $this->mDbkeyform == '';
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
+ // known system message
+ return $this->hasSourceText() !== false;
default:
return false;
}
@@ -3871,7 +3900,7 @@ class Title {
* links to the title should be rendered as "bluelinks" (as opposed to
* "redlinks" to non-existent pages).
*
- * @return \type{\bool}
+ * @return Bool
*/
public function isKnown() {
return $this->isAlwaysKnown() || $this->exists();
@@ -3889,21 +3918,44 @@ class Title {
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 );
+ // message content will be displayed, same for language subpages-
+ // Use always content language to avoid loading hundreds of languages
+ // to get the link color.
+ global $wgContLang;
+ list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
+ $message = wfMessage( $name )->inLanguage( $wgContLang )->useDatabase( false );
+ return $message->exists();
}
return false;
}
/**
+ * Get the default message text or false if the message doesn't exist
+ *
+ * @return String or false
+ */
+ public function getDefaultMessageText() {
+ global $wgContLang;
+
+ if ( $this->getNamespace() != NS_MEDIAWIKI ) { // Just in case
+ return false;
+ }
+
+ list( $name, $lang ) = MessageCache::singleton()->figureMessage( $wgContLang->lcfirst( $this->getText() ) );
+ $message = wfMessage( $name )->inLanguage( $lang )->useDatabase( false );
+
+ if ( $message->exists() ) {
+ return $message->plain();
+ } else {
+ return false;
+ }
+ }
+
+ /**
* Is this in a namespace that allows actual pages?
*
- * @return \type{\bool}
+ * @return Bool
* @internal note -- uses hardcoded namespace index instead of constants
*/
public function canExist() {
@@ -3929,7 +3981,7 @@ class Title {
* Get the last touched timestamp
*
* @param $db DatabaseBase: optional db
- * @return \type{\string} Last touched timestamp
+ * @return String last-touched timestamp
*/
public function getTouched( $db = null ) {
$db = isset( $db ) ? $db : wfGetDB( DB_SLAVE );
@@ -3941,7 +3993,7 @@ class Title {
* Get the timestamp when this page was updated since the user last saw it.
*
* @param $user User
- * @return Mixed: string/null
+ * @return String|Null
*/
public function getNotificationTimestamp( $user = null ) {
global $wgUser, $wgShowUpdatedMarker;
@@ -3977,7 +4029,7 @@ class Title {
/**
* Get the trackback URL for this page
*
- * @return \type{\string} Trackback URL
+ * @return String Trackback URL
*/
public function trackbackURL() {
global $wgScriptPath, $wgServer, $wgScriptExtension;
@@ -3989,7 +4041,7 @@ class Title {
/**
* Get the trackback RDF for this page
*
- * @return \type{\string} Trackback RDF
+ * @return String Trackback RDF
*/
public function trackbackRDF() {
$url = htmlspecialchars( $this->getFullURL() );
@@ -4003,8 +4055,8 @@ class Title {
// Spec: http://www.sixapart.com/pronet/docs/trackback_spec
return "<!--
<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"
- xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
- xmlns:trackback=\"http://madskills.com/public/xml/rss/module/trackback/\">
+ xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
+ xmlns:trackback=\"http://madskills.com/public/xml/rss/module/trackback/\">
<rdf:Description
rdf:about=\"$url\"
dc:identifier=\"$url\"
@@ -4018,7 +4070,7 @@ class Title {
* Generate strings used for xml 'id' names in monobook tabs
*
* @param $prepend string defaults to 'nstab-'
- * @return \type{\string} XML 'id' name
+ * @return String XML 'id' name
*/
public function getNamespaceKey( $prepend = 'nstab-' ) {
global $wgContLang;
@@ -4057,12 +4109,12 @@ class Title {
/**
* Returns true if this title resolves to the named special page
*
- * @param $name \type{\string} The special page name
+ * @param $name String The special page name
* @return boolean
*/
public function isSpecial( $name ) {
if ( $this->getNamespace() == NS_SPECIAL ) {
- list( $thisName, /* $subpage */ ) = SpecialPage::resolveAliasWithSubpage( $this->getDBkey() );
+ list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
if ( $name == $thisName ) {
return true;
}
@@ -4071,16 +4123,16 @@ class Title {
}
/**
- * If the Title refers to a special page alias which is not the local default,
+ * If the Title refers to a special page alias which is not the local default, resolve
+ * the alias, and localise the name as necessary. Otherwise, return $this
*
- * @return \type{Title} A new Title which points to the local default.
- * Otherwise, returns $this.
+ * @return Title
*/
public function fixSpecialName() {
if ( $this->getNamespace() == NS_SPECIAL ) {
- $canonicalName = SpecialPage::resolveAlias( $this->mDbkeyform );
+ list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
if ( $canonicalName ) {
- $localName = SpecialPage::getLocalNameFor( $canonicalName );
+ $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
if ( $localName != $this->mDbkeyform ) {
return Title::makeTitle( NS_SPECIAL, $localName );
}
@@ -4103,9 +4155,8 @@ class Title {
/**
* Get all extant redirects to this Title
*
- * @param $ns \twotypes{\int,\null} Single namespace to consider;
- * NULL to consider all namespaces
- * @return \type{\arrayof{Title}} Redirects to this title
+ * @param $ns Int|Null Single namespace to consider; NULL to consider all namespaces
+ * @return Array of Title redirects to this title
*/
public function getRedirectsHere( $ns = null ) {
$redirs = array();
@@ -4136,7 +4187,7 @@ class Title {
/**
* Check if this Title is a valid redirect target
*
- * @return \type{\bool}
+ * @return Bool
*/
public function isValidRedirectTarget() {
global $wgInvalidRedirectTargets;
@@ -4168,8 +4219,7 @@ class Title {
}
/**
- * Whether the magic words __INDEX__ and __NOINDEX__ function for
- * this page.
+ * Whether the magic words __INDEX__ and __NOINDEX__ function for this page.
*
* @return Boolean
*/
@@ -4190,23 +4240,27 @@ class Title {
* @return array applicable restriction types
*/
public function getRestrictionTypes() {
+ if ( $this->getNamespace() == NS_SPECIAL ) {
+ return array();
+ }
+
$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 ) );
+
+ wfDebug( __METHOD__ . ': applicable restriction types for ' .
+ $this->getPrefixedText() . ' are ' . implode( ',', $types ) . "\n" );
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
+ * 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
@@ -4216,7 +4270,7 @@ class Title {
$types = $wgRestrictionTypes;
if ( $exists ) {
# Remove the create restriction for existing titles
- $types = array_diff( $types, array( 'create' ) );
+ $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' ) );
@@ -4236,6 +4290,12 @@ class Title {
*/
public function getCategorySortkey( $prefix = '' ) {
$unprefixed = $this->getText();
+
+ // Anything that uses this hook should only depend
+ // on the Title object passed in, and should probably
+ // tell the users to run updateCollations.php --force
+ // in order to re-sort existing category relations.
+ wfRunHooks( 'GetDefaultSortkey', array( $this, &$unprefixed ) );
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.
@@ -4246,4 +4306,36 @@ class Title {
}
return $unprefixed;
}
+
+ /**
+ * Get the language in which the content of this page is written.
+ * Defaults to $wgContLang, but in certain cases it can be e.g.
+ * $wgLang (such as special pages, which are in the user language).
+ *
+ * @since 1.18
+ * @return object Language
+ */
+ public function getPageLanguage() {
+ global $wgLang;
+ if ( $this->getNamespace() == NS_SPECIAL ) {
+ // special pages are in the user language
+ return $wgLang;
+ } elseif ( $this->isRedirect() ) {
+ // the arrow on a redirect page is aligned according to the user language
+ return $wgLang;
+ } elseif ( $this->isCssOrJsPage() ) {
+ // css/js should always be LTR and is, in fact, English
+ return wfGetLangObj( 'en' );
+ } elseif ( $this->getNamespace() == NS_MEDIAWIKI ) {
+ // Parse mediawiki messages with correct target language
+ list( /* $unused */, $lang ) = MessageCache::singleton()->figureMessage( $this->getText() );
+ return wfGetLangObj( $lang );
+ }
+ global $wgContLang;
+ // If nothing special, it should be in the wiki content language
+ $pageLang = $wgContLang;
+ // Hook at the end because we don't want to override the above stuff
+ wfRunHooks( 'PageContentLanguage', array( $this, &$pageLang, $wgLang ) );
+ return wfGetLangObj( $pageLang );
+ }
}
diff --git a/includes/TitleArray.php b/includes/TitleArray.php
index f7a9e1dc..96960089 100644
--- a/includes/TitleArray.php
+++ b/includes/TitleArray.php
@@ -11,10 +11,10 @@
*/
abstract class TitleArray implements Iterator {
/**
- * @param $res result A MySQL result including at least page_namespace and
+ * @param $res ResultWrapper A SQL result including at least page_namespace and
* page_title -- also can have page_id, page_len, page_is_redirect,
* page_latest (if those will be used). See Title::newFromRow.
- * @return TitleArray
+ * @return TitleArrayFromResult
*/
static function newFromResult( $res ) {
$array = null;
@@ -27,6 +27,10 @@ abstract class TitleArray implements Iterator {
return $array;
}
+ /**
+ * @param $res ResultWrapper
+ * @return TitleArrayFromResult
+ */
protected static function newFromResult_internal( $res ) {
$array = new TitleArrayFromResult( $res );
return $array;
@@ -34,6 +38,10 @@ abstract class TitleArray implements Iterator {
}
class TitleArrayFromResult extends TitleArray {
+
+ /**
+ * @var ResultWrapper
+ */
var $res;
var $key, $current;
@@ -43,6 +51,10 @@ class TitleArrayFromResult extends TitleArray {
$this->setCurrent( $this->res->current() );
}
+ /**
+ * @param $row ResultWrapper
+ * @return void
+ */
protected function setCurrent( $row ) {
if ( $row === false ) {
$this->current = false;
@@ -51,6 +63,9 @@ class TitleArrayFromResult extends TitleArray {
}
}
+ /**
+ * @return int
+ */
public function count() {
return $this->res->numRows();
}
@@ -75,6 +90,9 @@ class TitleArrayFromResult extends TitleArray {
$this->setCurrent( $this->res->current() );
}
+ /**
+ * @return bool
+ */
function valid() {
return $this->current !== false;
}
diff --git a/includes/User.php b/includes/User.php
index 5760003b..a584f39d 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -1,23 +1,39 @@
<?php
/**
* Implements the User class for the %MediaWiki software.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
*/
/**
- * \int Number of characters in user_token field.
+ * Int Number of characters in user_token field.
* @ingroup Constants
*/
define( 'USER_TOKEN_LENGTH', 32 );
/**
- * \int Serialized record version.
+ * Int Serialized record version.
* @ingroup Constants
*/
define( 'MW_USER_VERSION', 8 );
/**
- * \string Some punctuation to prevent editing from broken text-mangling proxies.
+ * String Some punctuation to prevent editing from broken text-mangling proxies.
* @ingroup Constants
*/
define( 'EDIT_TOKEN_SUFFIX', '+\\' );
@@ -50,7 +66,7 @@ class User {
const EDIT_TOKEN_SUFFIX = EDIT_TOKEN_SUFFIX;
/**
- * \type{\arrayof{\string}} List of member variables which are saved to the
+ * Array of Strings List of member variables which are saved to the
* shared cache (memcached). Any operation which changes the
* corresponding database fields must call a cache-clearing function.
* @showinitializer
@@ -71,14 +87,14 @@ class User {
'mEmailTokenExpires',
'mRegistration',
'mEditCount',
- // user_group table
+ // user_groups table
'mGroups',
// user_properties table
'mOptionOverrides',
);
/**
- * \type{\arrayof{\string}} Core rights.
+ * Array of Strings Core rights.
* Each of these should have a corresponding message of the form
* "right-$right".
* @showinitializer
@@ -144,7 +160,7 @@ class User {
'writeapi',
);
/**
- * \string Cached results of getAllRights()
+ * String Cached results of getAllRights()
*/
static $mAllRights = false;
@@ -152,16 +168,24 @@ class User {
//@{
var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime,
$mEmail, $mTouched, $mToken, $mEmailAuthenticated,
- $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups, $mOptionOverrides;
+ $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups, $mOptionOverrides,
+ $mCookiePassword, $mEditCount, $mAllowUsertalk;
//@}
/**
- * \bool Whether the cache variables have been loaded.
+ * Bool Whether the cache variables have been loaded.
*/
- var $mDataLoaded, $mAuthLoaded, $mOptionsLoaded;
+ //@{
+ var $mOptionsLoaded;
+
+ /**
+ * Array with already loaded items or true if all items have been loaded.
+ */
+ private $mLoadedItems = array();
+ //@}
/**
- * \string Initialization data source if mDataLoaded==false. May be one of:
+ * String Initialization data source if mLoadedItems!==true. May be one of:
* - 'defaults' anonymous user initialised from class defaults
* - 'name' initialise from mName
* - 'id' initialise from mId
@@ -171,12 +195,27 @@ class User {
*/
var $mFrom;
- /** @name Lazy-initialized variables, invalidated with clearInstanceCache */
- //@{
- var $mNewtalk, $mDatePreference, $mBlockedby, $mHash, $mSkin, $mRights,
- $mBlockreason, $mBlock, $mEffectiveGroups, $mBlockedGlobally,
+ /**
+ * Lazy-initialized variables, invalidated with clearInstanceCache
+ */
+ var $mNewtalk, $mDatePreference, $mBlockedby, $mHash, $mRights,
+ $mBlockreason, $mEffectiveGroups, $mImplicitGroups, $mFormerGroups, $mBlockedGlobally,
$mLocked, $mHideName, $mOptions;
- //@}
+
+ /**
+ * @var WebRequest
+ */
+ private $mRequest;
+
+ /**
+ * @var Block
+ */
+ var $mBlock;
+
+ /**
+ * @var Block
+ */
+ private $mBlockedFromCreateAccount = false;
static $idCacheByName = array();
@@ -195,16 +234,23 @@ class User {
}
/**
+ * @return String
+ */
+ function __toString(){
+ return $this->getName();
+ }
+
+ /**
* Load the user table data for this object from the source given by mFrom.
*/
- function load() {
- if ( $this->mDataLoaded ) {
+ public function load() {
+ if ( $this->mLoadedItems === true ) {
return;
}
wfProfileIn( __METHOD__ );
# Set it now to avoid infinite recursion in accessors
- $this->mDataLoaded = true;
+ $this->mLoadedItems = true;
switch ( $this->mFrom ) {
case 'defaults':
@@ -234,10 +280,9 @@ class User {
/**
* Load user table data, given mId has already been set.
- * @return \bool false if the ID does not exist, true otherwise
- * @private
+ * @return Bool false if the ID does not exist, true otherwise
*/
- function loadFromId() {
+ public function loadFromId() {
global $wgMemc;
if ( $this->mId == 0 ) {
$this->loadDefaults();
@@ -273,7 +318,7 @@ class User {
/**
* Save user data to the shared cache
*/
- function saveToCache() {
+ public function saveToCache() {
$this->load();
$this->loadGroups();
$this->loadOptions();
@@ -291,7 +336,6 @@ class User {
$wgMemc->set( $key, $data );
}
-
/** @name newFrom*() static factory methods */
//@{
@@ -301,17 +345,17 @@ class User {
* This is slightly less efficient than newFromId(), so use newFromId() if
* you have both an ID and a name handy.
*
- * @param $name \string Username, validated by Title::newFromText()
- * @param $validate \mixed Validate username. Takes the same parameters as
+ * @param $name String Username, validated by Title::newFromText()
+ * @param $validate String|Bool Validate username. Takes the same parameters as
* User::getCanonicalName(), except that true is accepted as an alias
* for 'valid', for BC.
*
- * @return User The User object, or false if the username is invalid
+ * @return 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.
*/
- static function newFromName( $name, $validate = 'valid' ) {
+ public static function newFromName( $name, $validate = 'valid' ) {
if ( $validate === true ) {
$validate = 'valid';
}
@@ -323,6 +367,7 @@ class User {
$u = new User;
$u->mName = $name;
$u->mFrom = 'name';
+ $u->setItemLoaded( 'name' );
return $u;
}
}
@@ -330,13 +375,14 @@ class User {
/**
* Static factory method for creation from a given user ID.
*
- * @param $id \int Valid user ID
- * @return \type{User} The corresponding User object
+ * @param $id Int Valid user ID
+ * @return User The corresponding User object
*/
- static function newFromId( $id ) {
+ public static function newFromId( $id ) {
$u = new User;
$u->mId = $id;
$u->mFrom = 'id';
+ $u->setItemLoaded( 'id' );
return $u;
}
@@ -347,10 +393,10 @@ class User {
*
* If the code is invalid or has expired, returns NULL.
*
- * @param $code \string Confirmation code
- * @return \type{User}
+ * @param $code String Confirmation code
+ * @return User
*/
- static function newFromConfirmationCode( $code ) {
+ public static function newFromConfirmationCode( $code ) {
$dbr = wfGetDB( DB_SLAVE );
$id = $dbr->selectField( 'user', 'user_id', array(
'user_email_token' => md5( $code ),
@@ -367,21 +413,31 @@ class User {
* Create a new user object using data from session or cookies. If the
* login credentials are invalid, the result is an anonymous user.
*
- * @return \type{User}
+ * @param $request WebRequest object to use; $wgRequest will be used if
+ * ommited.
+ * @return User
*/
- static function newFromSession() {
+ public static function newFromSession( WebRequest $request = null ) {
$user = new User;
$user->mFrom = 'session';
+ $user->mRequest = $request;
return $user;
}
/**
* Create a new user object from a user row.
- * The row should have all fields from the user table in it.
- * @param $row array A row from the user table
- * @return \type{User}
- */
- static function newFromRow( $row ) {
+ * The row should have the following fields from the user table in it:
+ * - either user_name or user_id to load further data if needed (or both)
+ * - user_real_name
+ * - all other fields (email, password, etc.)
+ * It is useless to provide the remaining fields if either user_id,
+ * user_name and user_real_name are not provided because the whole row
+ * will be loaded once more from the database when accessing them.
+ *
+ * @param $row Array A row from the user table
+ * @return User
+ */
+ public static function newFromRow( $row ) {
$user = new User;
$user->loadFromRow( $row );
return $user;
@@ -389,11 +445,10 @@ class User {
//@}
-
/**
* Get the username corresponding to a given user ID
- * @param $id \int User ID
- * @return \string The corresponding username
+ * @param $id Int User ID
+ * @return String The corresponding username
*/
static function whoIs( $id ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -403,20 +458,20 @@ class User {
/**
* Get the real name of a user given their user ID
*
- * @param $id \int User ID
- * @return \string The corresponding user's real name
+ * @param $id Int User ID
+ * @return String The corresponding user's real name
*/
- static function whoIsReal( $id ) {
+ public static function whoIsReal( $id ) {
$dbr = wfGetDB( DB_SLAVE );
return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ), __METHOD__ );
}
/**
* Get database id given a user name
- * @param $name \string Username
- * @return \types{\int,\null} The corresponding user's ID, or null if user is nonexistent
+ * @param $name String Username
+ * @return Int|Null The corresponding user's ID, or null if user is nonexistent
*/
- static function idFromName( $name ) {
+ public static function idFromName( $name ) {
$nt = Title::makeTitleSafe( NS_USER, $name );
if( is_null( $nt ) ) {
# Illegal name
@@ -446,6 +501,13 @@ class User {
}
/**
+ * Reset the cache used in idFromName(). For use in tests.
+ */
+ public static function resetIdByNameCache() {
+ self::$idCacheByName = array();
+ }
+
+ /**
* Does the string match an anonymous IPv4 address?
*
* This function exists for username validation, in order to reject
@@ -458,10 +520,10 @@ class User {
* addresses like this, if we allowed accounts like this to be created
* new users could get the old edits of these anonymous users.
*
- * @param $name \string String to match
- * @return \bool True or false
+ * @param $name String to match
+ * @return Bool
*/
- static function isIP( $name ) {
+ public static function isIP( $name ) {
return preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/',$name) || IP::isIPv6($name);
}
@@ -473,10 +535,10 @@ class User {
* is longer than the maximum allowed username size or doesn't begin with
* a capital letter.
*
- * @param $name \string String to match
- * @return \bool True or false
+ * @param $name String to match
+ * @return Bool
*/
- static function isValidUserName( $name ) {
+ public static function isValidUserName( $name ) {
global $wgContLang, $wgMaxNameChars;
if ( $name == ''
@@ -527,10 +589,10 @@ class User {
* If an account already exists in this form, login will be blocked
* by a failure to pass this function.
*
- * @param $name \string String to match
- * @return \bool True or false
+ * @param $name String to match
+ * @return Bool
*/
- static function isUsableName( $name ) {
+ public static function isUsableName( $name ) {
global $wgReservedUsernames;
// Must be a valid username, obviously ;)
if ( !self::isValidUserName( $name ) ) {
@@ -564,10 +626,10 @@ class User {
* 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
+ * @param $name String to match
+ * @return Bool
*/
- static function isCreatableName( $name ) {
+ public static function isCreatableName( $name ) {
global $wgInvalidUsernameCharacters;
// Ensure that the username isn't longer than 235 bytes, so that
@@ -580,7 +642,7 @@ class User {
}
// Preg yells if you try to give it an empty string
- if( $wgInvalidUsernameCharacters ) {
+ if( $wgInvalidUsernameCharacters !== '' ) {
if( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
wfDebugLog( 'username', __METHOD__ .
": '$name' invalid due to wgInvalidUsernameCharacters" );
@@ -595,9 +657,9 @@ class User {
* Is the input a valid password for this user?
*
* @param $password String Desired password
- * @return bool True or false
+ * @return Bool
*/
- function isValidPassword( $password ) {
+ public function isValidPassword( $password ) {
//simple boolean wrapper for getPasswordValidity
return $this->getPasswordValidity( $password ) === true;
}
@@ -606,11 +668,11 @@ class User {
* Given unvalidated password input, return error message on failure.
*
* @param $password String Desired password
- * @return mixed: true on success, string of error message on failure
+ * @return mixed: true on success, string or array of error message on failure
*/
- function getPasswordValidity( $password ) {
+ public function getPasswordValidity( $password ) {
global $wgMinimalPasswordLength, $wgContLang;
-
+
static $blockedLogins = array(
'Useruser' => 'Passpass', 'Useruser1' => 'Passpass1', # r75589
'Apitestsysop' => 'testpass', 'Apitestuser' => 'testpass' # r75605
@@ -667,42 +729,25 @@ class User {
*
* @param $addr String E-mail address
* @return Bool
+ * @deprecated since 1.18 call Sanitizer::isValidEmail() directly
*/
public static function isValidEmailAddr( $addr ) {
- $result = null;
- if( !wfRunHooks( 'isValidEmailAddr', array( $addr, &$result ) ) ) {
- return $result;
- }
-
- // 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 );
+ return Sanitizer::validateEmail( $addr );
}
/**
* Given unvalidated user input, return a canonical username, or false if
* the username is invalid.
- * @param $name \string User input
- * @param $validate \types{\string,\bool} Type of validation to use:
+ * @param $name String User input
+ * @param $validate String|Bool type of validation to use:
* - false No validation
* - 'valid' Valid for batch processes
* - 'usable' Valid for batch processes and login
* - 'creatable' Valid for batch processes, login and account creation
+ *
+ * @return bool|string
*/
- static function getCanonicalName( $name, $validate = 'valid' ) {
+ public static function getCanonicalName( $name, $validate = 'valid' ) {
# Force usernames to capital
global $wgContLang;
$name = $wgContLang->ucfirst( $name );
@@ -753,10 +798,10 @@ class User {
* Count the number of edits of a user
* @todo It should not be static and some day should be merged as proper member function / deprecated -- domas
*
- * @param $uid \int User ID to check
- * @return \int The user's edit count
+ * @param $uid Int User ID to check
+ * @return Int the user's edit count
*/
- static function edits( $uid ) {
+ public static function edits( $uid ) {
wfProfileIn( __METHOD__ );
$dbr = wfGetDB( DB_SLAVE );
// check if the user_editcount field has been initialized
@@ -790,9 +835,9 @@ class User {
* Return a random password. Sourced from mt_rand, so it's not particularly secure.
* @todo hash random numbers to improve security, like generateToken()
*
- * @return \string New random password
+ * @return String new random password
*/
- static function randomPassword() {
+ public static function randomPassword() {
global $wgMinimalPasswordLength;
$pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
$l = strlen( $pwchars ) - 1;
@@ -801,7 +846,7 @@ class User {
$digit = mt_rand( 0, $pwlength - 1 );
$np = '';
for ( $i = 0; $i < $pwlength; $i++ ) {
- $np .= $i == $digit ? chr( mt_rand( 48, 57 ) ) : $pwchars{ mt_rand( 0, $l ) };
+ $np .= $i == $digit ? chr( mt_rand( 48, 57 ) ) : $pwchars[ mt_rand( 0, $l ) ];
}
return $np;
}
@@ -811,13 +856,12 @@ class User {
*
* @note This no longer clears uncached lazy-initialised properties;
* the constructor does that instead.
- * @private
+ *
+ * @param $name string
*/
- function loadDefaults( $name = false ) {
+ public function loadDefaults( $name = false ) {
wfProfileIn( __METHOD__ );
- global $wgRequest;
-
$this->mId = 0;
$this->mName = $name;
$this->mRealName = '';
@@ -827,8 +871,9 @@ class User {
$this->mOptionOverrides = null;
$this->mOptionsLoaded = false;
- if( $wgRequest->getCookie( 'LoggedOut' ) !== null ) {
- $this->mTouched = wfTimestamp( TS_MW, $wgRequest->getCookie( 'LoggedOut' ) );
+ $loggedOut = $this->getRequest()->getCookie( 'LoggedOut' );
+ if( $loggedOut !== null ) {
+ $this->mTouched = wfTimestamp( TS_MW, $loggedOut );
} else {
$this->mTouched = '0'; # Allow any pages to be cached
}
@@ -846,20 +891,40 @@ class User {
}
/**
- * @deprecated Use wfSetupSession().
+ * Return whether an item has been loaded.
+ *
+ * @param $item String: item to check. Current possibilities:
+ * - id
+ * - name
+ * - realname
+ * @param $all String: 'all' to check if the whole object has been loaded
+ * or any other string to check if only the item is available (e.g.
+ * for optimisation)
+ * @return Boolean
+ */
+ public function isItemLoaded( $item, $all = 'all' ) {
+ return ( $this->mLoadedItems === true && $all === 'all' ) ||
+ ( isset( $this->mLoadedItems[$item] ) && $this->mLoadedItems[$item] === true );
+ }
+
+ /**
+ * Set that an item has been loaded
+ *
+ * @param $item String
*/
- function SetupSession() {
- wfDeprecated( __METHOD__ );
- wfSetupSession();
+ private function setItemLoaded( $item ) {
+ if ( is_array( $this->mLoadedItems ) ) {
+ $this->mLoadedItems[$item] = true;
+ }
}
/**
* Load user data from the session or login cookie. If there are no valid
* credentials, initialises the user as an anonymous user.
- * @return \bool True if the user is logged in, false otherwise.
+ * @return Bool True if the user is logged in, false otherwise.
*/
private function loadFromSession() {
- global $wgRequest, $wgExternalAuthType, $wgAutocreatePolicy;
+ global $wgExternalAuthType, $wgAutocreatePolicy;
$result = null;
wfRunHooks( 'UserLoadFromSession', array( $this, &$result ) );
@@ -875,32 +940,32 @@ class User {
}
}
- if ( $wgRequest->getCookie( 'UserID' ) !== null ) {
- $sId = intval( $wgRequest->getCookie( 'UserID' ) );
- if( isset( $_SESSION['wsUserID'] ) && $sId != $_SESSION['wsUserID'] ) {
+ $request = $this->getRequest();
+
+ $cookieId = $request->getCookie( 'UserID' );
+ $sessId = $request->getSessionData( 'wsUserID' );
+
+ if ( $cookieId !== null ) {
+ $sId = intval( $cookieId );
+ if( $sessId !== null && $cookieId != $sessId ) {
$this->loadDefaults(); // Possible collision!
- wfDebugLog( 'loginSessions', "Session user ID ({$_SESSION['wsUserID']}) and
+ wfDebugLog( 'loginSessions', "Session user ID ($sessId) and
cookie user ID ($sId) don't match!" );
return false;
}
- $_SESSION['wsUserID'] = $sId;
- } else if ( isset( $_SESSION['wsUserID'] ) ) {
- if ( $_SESSION['wsUserID'] != 0 ) {
- $sId = $_SESSION['wsUserID'];
- } else {
- $this->loadDefaults();
- return false;
- }
+ $request->setSessionData( 'wsUserID', $sId );
+ } elseif ( $sessId !== null && $sessId != 0 ) {
+ $sId = $sessId;
} else {
$this->loadDefaults();
return false;
}
- if ( isset( $_SESSION['wsUserName'] ) ) {
- $sName = $_SESSION['wsUserName'];
- } else if ( $wgRequest->getCookie('UserName') !== null ) {
- $sName = $wgRequest->getCookie('UserName');
- $_SESSION['wsUserName'] = $sName;
+ if ( $request->getSessionData( 'wsUserName' ) !== null ) {
+ $sName = $request->getSessionData( 'wsUserName' );
+ } elseif ( $request->getCookie( 'UserName' ) !== null ) {
+ $sName = $request->getCookie( 'UserName' );
+ $request->setSessionData( 'wsUserName', $sName );
} else {
$this->loadDefaults();
return false;
@@ -920,11 +985,11 @@ class User {
return false;
}
- if ( isset( $_SESSION['wsToken'] ) ) {
- $passwordCorrect = $proposedUser->getToken() === $_SESSION['wsToken'];
+ if ( $request->getSessionData( 'wsToken' ) !== null ) {
+ $passwordCorrect = $proposedUser->getToken() === $request->getSessionData( 'wsToken' );
$from = 'session';
- } else if ( $wgRequest->getCookie( 'Token' ) !== null ) {
- $passwordCorrect = $proposedUser->getToken() === $wgRequest->getCookie( 'Token' );
+ } elseif ( $request->getCookie( 'Token' ) !== null ) {
+ $passwordCorrect = $proposedUser->getToken() === $request->getCookie( 'Token' );
$from = 'cookie';
} else {
# No session or persistent login cookie
@@ -934,7 +999,7 @@ class User {
if ( ( $sName === $proposedUser->getName() ) && $passwordCorrect ) {
$this->loadFromUserObject( $proposedUser );
- $_SESSION['wsToken'] = $this->mToken;
+ $request->setSessionData( 'wsToken', $this->mToken );
wfDebug( "User: logged in from $from\n" );
return true;
} else {
@@ -946,25 +1011,12 @@ class User {
}
/**
- * Load the data for this user object from another user object.
- */
- protected function loadFromUserObject( $user ) {
- $user->load();
- $user->loadGroups();
- $user->loadOptions();
- foreach ( self::$mCacheVars as $var ) {
- $this->$var = $user->$var;
- }
- }
-
- /**
* Load user and user_group data from the database.
- * $this::mId must be set, this is how the user is identified.
+ * $this->mId must be set, this is how the user is identified.
*
- * @return \bool True if the user exists, false if the user is anonymous
- * @private
+ * @return Bool True if the user exists, false if the user is anonymous
*/
- function loadFromDatabase() {
+ public function loadFromDatabase() {
# Paranoia
$this->mId = intval( $this->mId );
@@ -996,35 +1048,74 @@ class User {
/**
* Initialize this object from a row from the user table.
*
- * @param $row \type{\arrayof{\mixed}} Row from the user table to load.
+ * @param $row Array Row from the user table to load.
*/
- function loadFromRow( $row ) {
- $this->mDataLoaded = true;
+ public function loadFromRow( $row ) {
+ $all = true;
+
+ if ( isset( $row->user_name ) ) {
+ $this->mName = $row->user_name;
+ $this->mFrom = 'name';
+ $this->setItemLoaded( 'name' );
+ } else {
+ $all = false;
+ }
+
+ if ( isset( $row->user_real_name ) ) {
+ $this->mRealName = $row->user_real_name;
+ $this->setItemLoaded( 'realname' );
+ } else {
+ $all = false;
+ }
if ( isset( $row->user_id ) ) {
$this->mId = intval( $row->user_id );
+ $this->mFrom = 'id';
+ $this->setItemLoaded( 'id' );
+ } else {
+ $all = false;
+ }
+
+ if ( isset( $row->user_password ) ) {
+ $this->mPassword = $row->user_password;
+ $this->mNewpassword = $row->user_newpassword;
+ $this->mNewpassTime = wfTimestampOrNull( TS_MW, $row->user_newpass_time );
+ $this->mEmail = $row->user_email;
+ $this->decodeOptions( $row->user_options );
+ $this->mTouched = wfTimestamp(TS_MW,$row->user_touched);
+ $this->mToken = $row->user_token;
+ $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
+ $this->mEmailToken = $row->user_email_token;
+ $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
+ $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
+ $this->mEditCount = $row->user_editcount;
+ } else {
+ $all = false;
+ }
+
+ if ( $all ) {
+ $this->mLoadedItems = true;
+ }
+ }
+
+ /**
+ * Load the data for this user object from another user object.
+ *
+ * @param $user User
+ */
+ protected function loadFromUserObject( $user ) {
+ $user->load();
+ $user->loadGroups();
+ $user->loadOptions();
+ foreach ( self::$mCacheVars as $var ) {
+ $this->$var = $user->$var;
}
- $this->mName = $row->user_name;
- $this->mRealName = $row->user_real_name;
- $this->mPassword = $row->user_password;
- $this->mNewpassword = $row->user_newpassword;
- $this->mNewpassTime = wfTimestampOrNull( TS_MW, $row->user_newpass_time );
- $this->mEmail = $row->user_email;
- $this->decodeOptions( $row->user_options );
- $this->mTouched = wfTimestamp(TS_MW,$row->user_touched);
- $this->mToken = $row->user_token;
- $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
- $this->mEmailToken = $row->user_email_token;
- $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
- $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
- $this->mEditCount = $row->user_editcount;
}
/**
* Load the groups from the database if they aren't already loaded.
- * @private
*/
- function loadGroups() {
+ private function loadGroups() {
if ( is_null( $this->mGroups ) ) {
$dbr = wfGetDB( DB_MASTER );
$res = $dbr->select( 'user_groups',
@@ -1039,23 +1130,61 @@ class User {
}
/**
+ * Add the user to the group if he/she meets given criteria.
+ *
+ * Contrary to autopromotion by \ref $wgAutopromote, the group will be
+ * possible to remove manually via Special:UserRights. In such case it
+ * will not be re-added automatically. The user will also not lose the
+ * group if they no longer meet the criteria.
+ *
+ * @param $event String key in $wgAutopromoteOnce (each one has groups/criteria)
+ *
+ * @return array Array of groups the user has been promoted to.
+ *
+ * @see $wgAutopromoteOnce
+ */
+ public function addAutopromoteOnceGroups( $event ) {
+ global $wgAutopromoteOnceLogInRC;
+
+ $toPromote = array();
+ if ( $this->getId() ) {
+ $toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
+ if ( count( $toPromote ) ) {
+ $oldGroups = $this->getGroups(); // previous groups
+ foreach ( $toPromote as $group ) {
+ $this->addGroup( $group );
+ }
+ $newGroups = array_merge( $oldGroups, $toPromote ); // all groups
+
+ $log = new LogPage( 'rights', $wgAutopromoteOnceLogInRC /* in RC? */ );
+ $log->addEntry( 'autopromote',
+ $this->getUserPage(),
+ '', // no comment
+ array( implode( ', ', $oldGroups ), implode( ', ', $newGroups ) )
+ );
+ }
+ }
+ return $toPromote;
+ }
+
+ /**
* Clear various cached data stored in this object.
- * @param $reloadFrom \string Reload user and user_groups table data from a
+ * @param $reloadFrom bool|String Reload user and user_groups table data from a
* given source. May be "name", "id", "defaults", "session", or false for
* no reload.
*/
- function clearInstanceCache( $reloadFrom = false ) {
+ public function clearInstanceCache( $reloadFrom = false ) {
$this->mNewtalk = -1;
$this->mDatePreference = null;
$this->mBlockedby = -1; # Unset
$this->mHash = false;
- $this->mSkin = null;
$this->mRights = null;
$this->mEffectiveGroups = null;
+ $this->mImplicitGroups = null;
$this->mOptions = null;
if ( $reloadFrom ) {
- $this->mDataLoaded = false;
+ $this->mLoadedItems = array();
$this->mFrom = $reloadFrom;
}
}
@@ -1064,19 +1193,13 @@ class User {
* Combine the language default options with any site-specific options
* and add the default language variants.
*
- * @return \type{\arrayof{\string}} Array of options
+ * @return Array of String options
*/
- static function getDefaultOptions() {
- global $wgNamespacesToBeSearchedDefault;
- /**
- * Site defaults will override the global/language defaults
- */
- global $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
- $defOpt = $wgDefaultUserOptions + $wgContLang->getDefaultUserOptionOverrides();
+ public static function getDefaultOptions() {
+ global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
- /**
- * default language setting
- */
+ $defOpt = $wgDefaultUserOptions;
+ # default language setting
$variant = $wgContLang->getDefaultVariant();
$defOpt['variant'] = $variant;
$defOpt['language'] = $variant;
@@ -1085,14 +1208,16 @@ class User {
}
$defOpt['skin'] = $wgDefaultSkin;
+ wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) );
+
return $defOpt;
}
/**
* Get a given default option value.
*
- * @param $opt \string Name of option to retrieve
- * @return \string Default option value
+ * @param $opt String Name of option to retrieve
+ * @return String Default option value
*/
public static function getDefaultOption( $opt ) {
$defOpts = self::getDefaultOptions();
@@ -1106,17 +1231,15 @@ class User {
/**
* Get blocking information
- * @private
- * @param $bFromSlave \bool Whether to check the slave database first. To
+ * @param $bFromSlave Bool Whether to check the slave database first. To
* improve performance, non-critical checks are done
* against slaves. Check when actually saving should be
* done against master.
*/
- function getBlockedStatus( $bFromSlave = true ) {
+ private function getBlockedStatus( $bFromSlave = true ) {
global $wgProxyWhitelist, $wgUser;
if ( -1 != $this->mBlockedby ) {
- wfDebug( "User::getBlockedStatus: already loaded.\n" );
return;
}
@@ -1134,50 +1257,29 @@ class User {
$this->mHideName = 0;
$this->mAllowUsertalk = 0;
- # Check if we are looking at an IP or a logged-in user
- if ( $this->isIP( $this->getName() ) ) {
- $ip = $this->getName();
+ # We only need to worry about passing the IP address to the Block generator if the
+ # user is not immune to autoblocks/hardblocks, and they are the current user so we
+ # know which IP address they're actually coming from
+ if ( !$this->isAllowed( 'ipblock-exempt' ) && $this->getID() == $wgUser->getID() ) {
+ $ip = wfGetIP();
} else {
- # Check if we are looking at the current user
- # If we don't, and the user is logged in, we don't know about
- # his IP / autoblock status, so ignore autoblock of current user's IP
- if ( $this->getID() != $wgUser->getID() ) {
- $ip = '';
- } else {
- # Get IP of current user
- $ip = wfGetIP();
- }
- }
-
- if ( $this->isAllowed( 'ipblock-exempt' ) ) {
- # Exempt from all types of IP-block
- $ip = '';
+ $ip = null;
}
# User/IP blocking
- $this->mBlock = new Block();
- $this->mBlock->fromMaster( !$bFromSlave );
- if ( $this->mBlock->load( $ip , $this->mId ) ) {
+ $this->mBlock = Block::newFromTarget( $this->getName(), $ip, !$bFromSlave );
+ if ( $this->mBlock instanceof Block ) {
wfDebug( __METHOD__ . ": Found block.\n" );
- $this->mBlockedby = $this->mBlock->mBy;
- if( $this->mBlockedby == 0 )
- $this->mBlockedby = $this->mBlock->mByName;
+ $this->mBlockedby = $this->mBlock->getByName();
$this->mBlockreason = $this->mBlock->mReason;
$this->mHideName = $this->mBlock->mHideName;
- $this->mAllowUsertalk = $this->mBlock->mAllowUsertalk;
- if ( $this->isLoggedIn() && $wgUser->getID() == $this->getID() ) {
- $this->spreadBlock();
- }
- } else {
- // Bug 13611: don't remove mBlock here, to allow account creation blocks to
- // apply to users. Note that the existence of $this->mBlock is not used to
- // check for edit blocks, $this->mBlockedby is instead.
+ $this->mAllowUsertalk = !$this->mBlock->prevents( 'editownusertalk' );
}
# Proxy blocking
- if ( !$this->isAllowed( 'proxyunbannable' ) && !in_array( $ip, $wgProxyWhitelist ) ) {
+ if ( $ip !== null && !$this->isAllowed( 'proxyunbannable' ) && !in_array( $ip, $wgProxyWhitelist ) ) {
# Local list
- if ( wfIsLocallyBlockedProxy( $ip ) ) {
+ if ( self::isLocallyBlockedProxy( $ip ) ) {
$this->mBlockedby = wfMsg( 'proxyblocker' );
$this->mBlockreason = wfMsg( 'proxyblockreason' );
}
@@ -1200,11 +1302,11 @@ class User {
/**
* Whether the given IP is in a DNS blacklist.
*
- * @param $ip \string IP to check
- * @param $checkWhitelist Boolean: whether to check the whitelist first
- * @return \bool True if blacklisted.
+ * @param $ip String IP to check
+ * @param $checkWhitelist Bool: whether to check the whitelist first
+ * @return Bool True if blacklisted.
*/
- function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
+ public function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
global $wgEnableSorbs, $wgEnableDnsBlacklist,
$wgSorbsUrl, $wgDnsBlacklistUrls, $wgProxyWhitelist;
@@ -1221,15 +1323,15 @@ class User {
/**
* Whether the given IP is in a given DNS blacklist.
*
- * @param $ip \string IP to check
- * @param $bases \string or Array of Strings: URL of the DNS blacklist
- * @return \bool True if blacklisted.
+ * @param $ip String IP to check
+ * @param $bases String|Array of Strings: URL of the DNS blacklist
+ * @return Bool True if blacklisted.
*/
- function inDnsBlacklist( $ip, $bases ) {
+ public function inDnsBlacklist( $ip, $bases ) {
wfProfileIn( __METHOD__ );
$found = false;
- // FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
+ // @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
if( IP::isIPv4( $ip ) ) {
# Reverse IP, bug 21255
$ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
@@ -1256,17 +1358,46 @@ class User {
}
/**
+ * Check if an IP address is in the local proxy list
+ *
+ * @param $ip string
+ *
+ * @return bool
+ */
+ public static function isLocallyBlockedProxy( $ip ) {
+ global $wgProxyList;
+
+ if ( !$wgProxyList ) {
+ return false;
+ }
+ wfProfileIn( __METHOD__ );
+
+ if ( !is_array( $wgProxyList ) ) {
+ # Load from the specified file
+ $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
+ }
+
+ if ( !is_array( $wgProxyList ) ) {
+ $ret = false;
+ } elseif ( array_search( $ip, $wgProxyList ) !== false ) {
+ $ret = true;
+ } elseif ( array_key_exists( $ip, $wgProxyList ) ) {
+ # Old-style flipped proxy list
+ $ret = true;
+ } else {
+ $ret = false;
+ }
+ wfProfileOut( __METHOD__ );
+ return $ret;
+ }
+
+ /**
* Is this user subject to rate limiting?
*
- * @return \bool True if rate limited
+ * @return Bool True if rate limited
*/
public function isPingLimitable() {
- global $wgRateLimitsExcludedGroups;
global $wgRateLimitsExcludedIPs;
- if( array_intersect( $this->getEffectiveGroups(), $wgRateLimitsExcludedGroups ) ) {
- // Deprecated, but kept for backwards-compatibility config
- return false;
- }
if( in_array( wfGetIP(), $wgRateLimitsExcludedIPs ) ) {
// No other good way currently to disable rate limits
// for specific IPs. :P
@@ -1283,10 +1414,10 @@ class User {
* @note When using a shared cache like memcached, IP-address
* last-hit counters will be shared across wikis.
*
- * @param $action \string Action to enforce; 'edit' if unspecified
- * @return \bool True if a rate limiter was tripped
+ * @param $action String Action to enforce; 'edit' if unspecified
+ * @return Bool True if a rate limiter was tripped
*/
- function pingLimiter( $action = 'edit' ) {
+ public function pingLimiter( $action = 'edit' ) {
# Call the 'PingLimiter' hook
$result = false;
if( !wfRunHooks( 'PingLimiter', array( &$this, $action, $result ) ) ) {
@@ -1356,7 +1487,9 @@ class User {
if( $count > $max ) {
wfDebug( __METHOD__ . ": tripped! $key at $count $summary\n" );
if( $wgRateLimitLog ) {
- @error_log( wfTimestamp( TS_MW ) . ' ' . wfWikiID() . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
+ wfSuppressWarnings();
+ file_put_contents( $wgRateLimitLog, wfTimestamp( TS_MW ) . ' ' . wfWikiID() . ': ' . $this->getName() . " tripped $key at $count $summary\n", FILE_APPEND );
+ wfRestoreWarnings();
}
$triggered = true;
} else {
@@ -1376,28 +1509,35 @@ class User {
/**
* Check if user is blocked
*
- * @param $bFromSlave \bool Whether to check the slave database instead of the master
- * @return \bool True if blocked, false otherwise
+ * @param $bFromSlave Bool Whether to check the slave database instead of the master
+ * @return Bool True if blocked, false otherwise
*/
- function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
- wfDebug( "User::isBlocked: enter\n" );
+ public function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
+ return $this->getBlock( $bFromSlave ) instanceof Block && $this->getBlock()->prevents( 'edit' );
+ }
+
+ /**
+ * Get the block affecting the user, or null if the user is not blocked
+ *
+ * @param $bFromSlave Bool Whether to check the slave database instead of the master
+ * @return Block|null
+ */
+ public function getBlock( $bFromSlave = true ){
$this->getBlockedStatus( $bFromSlave );
- return $this->mBlockedby !== 0;
+ return $this->mBlock instanceof Block ? $this->mBlock : null;
}
/**
* Check if user is blocked from editing a particular article
*
- * @param $title \string Title to check
- * @param $bFromSlave \bool Whether to check the slave database instead of the master
- * @return \bool True if blocked, false otherwise
+ * @param $title Title to check
+ * @param $bFromSlave Bool whether to check the slave database instead of the master
+ * @return Bool
*/
function isBlockedFrom( $title, $bFromSlave = false ) {
global $wgBlockAllowsUTEdit;
wfProfileIn( __METHOD__ );
- wfDebug( __METHOD__ . ": enter\n" );
- wfDebug( __METHOD__ . ": asking isBlocked()\n" );
$blocked = $this->isBlocked( $bFromSlave );
$allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
# If a user's name is suppressed, they cannot make edits anywhere
@@ -1415,29 +1555,29 @@ class User {
/**
* If user is blocked, return the name of the user who placed the block
- * @return \string name of blocker
+ * @return String name of blocker
*/
- function blockedBy() {
+ public function blockedBy() {
$this->getBlockedStatus();
return $this->mBlockedby;
}
/**
* If user is blocked, return the specified reason for the block
- * @return \string Blocking reason
+ * @return String Blocking reason
*/
- function blockedFor() {
+ public function blockedFor() {
$this->getBlockedStatus();
return $this->mBlockreason;
}
/**
* If user is blocked, return the ID for the block
- * @return \int Block ID
+ * @return Int Block ID
*/
- function getBlockId() {
+ public function getBlockId() {
$this->getBlockedStatus();
- return ( $this->mBlock ? $this->mBlock->mId : false );
+ return ( $this->mBlock ? $this->mBlock->getId() : false );
}
/**
@@ -1445,17 +1585,17 @@ class User {
* Do not use for actual edit permission checks!
* This is intented for quick UI checks.
*
- * @param $ip \type{\string} IP address, uses current client if none given
- * @return \type{\bool} True if blocked, false otherwise
+ * @param $ip String IP address, uses current client if none given
+ * @return Bool True if blocked, false otherwise
*/
- function isBlockedGlobally( $ip = '' ) {
+ public function isBlockedGlobally( $ip = '' ) {
if( $this->mBlockedGlobally !== null ) {
return $this->mBlockedGlobally;
}
// User is already an IP?
if( IP::isIPAddress( $this->getName() ) ) {
$ip = $this->getName();
- } else if( !$ip ) {
+ } elseif( !$ip ) {
$ip = wfGetIP();
}
$blocked = false;
@@ -1467,9 +1607,9 @@ class User {
/**
* Check if user account is locked
*
- * @return \type{\bool} True if locked, false otherwise
+ * @return Bool True if locked, false otherwise
*/
- function isLocked() {
+ public function isLocked() {
if( $this->mLocked !== null ) {
return $this->mLocked;
}
@@ -1482,9 +1622,9 @@ class User {
/**
* Check if user account is hidden
*
- * @return \type{\bool} True if hidden, false otherwise
+ * @return Bool True if hidden, false otherwise
*/
- function isHidden() {
+ public function isHidden() {
if( $this->mHideName !== null ) {
return $this->mHideName;
}
@@ -1499,14 +1639,14 @@ class User {
/**
* Get the user's ID.
- * @return Integer The user's ID; 0 if the user is anonymous or nonexistent
+ * @return Int The user's ID; 0 if the user is anonymous or nonexistent
*/
- function getId() {
- if( $this->mId === null and $this->mName !== null
- and User::isIP( $this->mName ) ) {
+ public function getId() {
+ if( $this->mId === null && $this->mName !== null
+ && User::isIP( $this->mName ) ) {
// Special case, we know the user is anonymous
return 0;
- } elseif( $this->mId === null ) {
+ } elseif( !$this->isItemLoaded( 'id' ) ) {
// Don't load if this was initialized from an ID
$this->load();
}
@@ -1515,19 +1655,19 @@ class User {
/**
* Set the user and reload all fields according to a given ID
- * @param $v \int User ID to reload
+ * @param $v Int User ID to reload
*/
- function setId( $v ) {
+ public function setId( $v ) {
$this->mId = $v;
$this->clearInstanceCache( 'id' );
}
/**
* Get the user name, or the IP of an anonymous user
- * @return \string User's name or IP address
+ * @return String User's name or IP address
*/
- function getName() {
- if ( !$this->mDataLoaded && $this->mFrom == 'name' ) {
+ public function getName() {
+ if ( $this->isItemLoaded( 'name', 'only' ) ) {
# Special case optimisation
return $this->mName;
} else {
@@ -1551,26 +1691,26 @@ class User {
*
* @note User::newFromName() has rougly the same function, when the named user
* does not exist.
- * @param $str \string New user name to set
+ * @param $str String New user name to set
*/
- function setName( $str ) {
+ public function setName( $str ) {
$this->load();
$this->mName = $str;
}
/**
* Get the user's name escaped by underscores.
- * @return \string Username escaped by underscores.
+ * @return String Username escaped by underscores.
*/
- function getTitleKey() {
+ public function getTitleKey() {
return str_replace( ' ', '_', $this->getName() );
}
/**
* Check if the user has new messages.
- * @return \bool True if the user has new messages
+ * @return Bool True if the user has new messages
*/
- function getNewtalk() {
+ public function getNewtalk() {
$this->load();
# Load the newtalk status if it is unloaded (mNewtalk=-1)
@@ -1601,9 +1741,9 @@ class User {
/**
* Return the talk page(s) this user has new messages on.
- * @return \type{\arrayof{\string}} Array of page URLs
+ * @return Array of String page URLs
*/
- function getNewMessageLinks() {
+ public function getNewMessageLinks() {
$talks = array();
if( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) )
return $talks;
@@ -1619,13 +1759,12 @@ class User {
* Internal uncached check for new messages
*
* @see getNewtalk()
- * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise
- * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise
- * @param $fromMaster \bool true to fetch from the master, false for a slave
- * @return \bool True if the user has new messages
- * @private
+ * @param $field String 'user_ip' for anonymous users, 'user_id' otherwise
+ * @param $id String|Int User's IP address for anonymous users, User ID otherwise
+ * @param $fromMaster Bool true to fetch from the master, false for a slave
+ * @return Bool True if the user has new messages
*/
- function checkNewtalk( $field, $id, $fromMaster = false ) {
+ protected function checkNewtalk( $field, $id, $fromMaster = false ) {
if ( $fromMaster ) {
$db = wfGetDB( DB_MASTER );
} else {
@@ -1638,12 +1777,11 @@ class User {
/**
* Add or update the new messages flag
- * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise
- * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise
- * @return \bool True if successful, false otherwise
- * @private
+ * @param $field String 'user_ip' for anonymous users, 'user_id' otherwise
+ * @param $id String|Int User's IP address for anonymous users, User ID otherwise
+ * @return Bool True if successful, false otherwise
*/
- function updateNewtalk( $field, $id ) {
+ protected function updateNewtalk( $field, $id ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->insert( 'user_newtalk',
array( $field => $id ),
@@ -1660,12 +1798,11 @@ class User {
/**
* Clear the new messages flag for the given user
- * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise
- * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise
- * @return \bool True if successful, false otherwise
- * @private
+ * @param $field String 'user_ip' for anonymous users, 'user_id' otherwise
+ * @param $id String|Int User's IP address for anonymous users, User ID otherwise
+ * @return Bool True if successful, false otherwise
*/
- function deleteNewtalk( $field, $id ) {
+ protected function deleteNewtalk( $field, $id ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'user_newtalk',
array( $field => $id ),
@@ -1681,9 +1818,9 @@ class User {
/**
* Update the 'You have new messages!' status.
- * @param $val \bool Whether the user has new messages
+ * @param $val Bool Whether the user has new messages
*/
- function setNewtalk( $val ) {
+ public function setNewtalk( $val ) {
if( wfReadOnly() ) {
return;
}
@@ -1720,7 +1857,7 @@ class User {
/**
* Generate a current or new-future timestamp to be stored in the
* user_touched field when we update things.
- * @return \string Timestamp in TS_MW format
+ * @return String Timestamp in TS_MW format
*/
private static function newTouchedTimestamp() {
global $wgClockSkewFudge;
@@ -1747,7 +1884,7 @@ class User {
* Updates user_touched field, and removes account data from memcached
* for reload on the next hit.
*/
- function invalidateCache() {
+ public function invalidateCache() {
if( wfReadOnly() ) {
return;
}
@@ -1767,17 +1904,20 @@ class User {
/**
* Validate the cache for this account.
- * @param $timestamp \string A timestamp in TS_MW format
+ * @param $timestamp String A timestamp in TS_MW format
+ *
+ * @return bool
*/
- function validateCache( $timestamp ) {
+ public function validateCache( $timestamp ) {
$this->load();
return ( $timestamp >= $this->mTouched );
}
/**
* Get the user touched timestamp
+ * @return String timestamp
*/
- function getTouched() {
+ public function getTouched() {
$this->load();
return $this->mTouched;
}
@@ -1793,10 +1933,12 @@ class User {
* wipes it, so the account cannot be logged in until
* a new password is set, for instance via e-mail.
*
- * @param $str \string New password to set
+ * @param $str String New password to set
* @throws PasswordError on failure
+ *
+ * @return bool
*/
- function setPassword( $str ) {
+ public function setPassword( $str ) {
global $wgAuth;
if( $str !== null ) {
@@ -1807,8 +1949,14 @@ class User {
if( !$this->isValidPassword( $str ) ) {
global $wgMinimalPasswordLength;
$valid = $this->getPasswordValidity( $str );
- throw new PasswordError( wfMsgExt( $valid, array( 'parsemag' ),
- $wgMinimalPasswordLength ) );
+ if ( is_array( $valid ) ) {
+ $message = array_shift( $valid );
+ $params = $valid;
+ } else {
+ $message = $valid;
+ $params = array( $wgMinimalPasswordLength );
+ }
+ throw new PasswordError( wfMsgExt( $message, array( 'parsemag' ), $params ) );
}
}
@@ -1824,9 +1972,9 @@ class User {
/**
* Set the password and reset the random token unconditionally.
*
- * @param $str \string New password to set
+ * @param $str String New password to set
*/
- function setInternalPassword( $str ) {
+ public function setInternalPassword( $str ) {
$this->load();
$this->setToken();
@@ -1842,9 +1990,9 @@ class User {
/**
* Get the user's current token.
- * @return \string Token
+ * @return String Token
*/
- function getToken() {
+ public function getToken() {
$this->load();
return $this->mToken;
}
@@ -1853,10 +2001,9 @@ class User {
* Set the random token (used for persistent authentication)
* Called from loadDefaults() among other places.
*
- * @param $token \string If specified, set the token to this value
- * @private
+ * @param $token String|bool If specified, set the token to this value
*/
- function setToken( $token = false ) {
+ public function setToken( $token = false ) {
global $wgSecretKey, $wgProxyKey;
$this->load();
if ( !$token ) {
@@ -1876,10 +2023,9 @@ class User {
/**
* Set the cookie password
*
- * @param $str \string New cookie password
- * @private
+ * @param $str String New cookie password
*/
- function setCookiePassword( $str ) {
+ private function setCookiePassword( $str ) {
$this->load();
$this->mCookiePassword = md5( $str );
}
@@ -1887,10 +2033,10 @@ class User {
/**
* Set the password for a password reminder or new account email
*
- * @param $str \string New password to set
- * @param $throttle \bool If true, reset the throttle timestamp to the present
+ * @param $str String New password to set
+ * @param $throttle Bool If true, reset the throttle timestamp to the present
*/
- function setNewpassword( $str, $throttle = true ) {
+ public function setNewpassword( $str, $throttle = true ) {
$this->load();
$this->mNewpassword = self::crypt( $str );
if ( $throttle ) {
@@ -1901,9 +2047,9 @@ class User {
/**
* Has password reminder email been sent within the last
* $wgPasswordReminderResendTime hours?
- * @return \bool True or false
+ * @return Bool
*/
- function isPasswordReminderThrottled() {
+ public function isPasswordReminderThrottled() {
global $wgPasswordReminderResendTime;
$this->load();
if ( !$this->mNewpassTime || !$wgPasswordReminderResendTime ) {
@@ -1915,9 +2061,9 @@ class User {
/**
* Get the user's e-mail address
- * @return \string User's email address
+ * @return String User's email address
*/
- function getEmail() {
+ public function getEmail() {
$this->load();
wfRunHooks( 'UserGetEmail', array( $this, &$this->mEmail ) );
return $this->mEmail;
@@ -1925,9 +2071,9 @@ class User {
/**
* Get the timestamp of the user's e-mail authentication
- * @return \string TS_MW timestamp
+ * @return String TS_MW timestamp
*/
- function getEmailAuthenticationTimestamp() {
+ public function getEmailAuthenticationTimestamp() {
$this->load();
wfRunHooks( 'UserGetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
return $this->mEmailAuthenticated;
@@ -1935,9 +2081,9 @@ class User {
/**
* Set the user's e-mail address
- * @param $str \string New e-mail address
+ * @param $str String New e-mail address
*/
- function setEmail( $str ) {
+ public function setEmail( $str ) {
$this->load();
$this->mEmail = $str;
wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) );
@@ -1945,18 +2091,21 @@ class User {
/**
* Get the user's real name
- * @return \string User's real name
+ * @return String User's real name
*/
- function getRealName() {
- $this->load();
+ public function getRealName() {
+ if ( !$this->isItemLoaded( 'realname' ) ) {
+ $this->load();
+ }
+
return $this->mRealName;
}
/**
* Set the user's real name
- * @param $str \string New real name
+ * @param $str String New real name
*/
- function setRealName( $str ) {
+ public function setRealName( $str ) {
$this->load();
$this->mRealName = $str;
}
@@ -1964,13 +2113,15 @@ class User {
/**
* Get the user's current setting for a given option.
*
- * @param $oname \string The option to check
- * @param $defaultOverride \string A default value returned if the option does not exist
- * @return \string User's current value for the option
+ * @param $oname String The option to check
+ * @param $defaultOverride String A default value returned if the option does not exist
+ * @param $ignoreHidden Bool = whether to ignore the effects of $wgHiddenPrefs
+ * @return String User's current value for the option
* @see getBoolOption()
* @see getIntOption()
*/
- function getOption( $oname, $defaultOverride = null ) {
+ public function getOption( $oname, $defaultOverride = null, $ignoreHidden = false ) {
+ global $wgHiddenPrefs;
$this->loadOptions();
if ( is_null( $this->mOptions ) ) {
@@ -1980,6 +2131,15 @@ class User {
$this->mOptions = User::getDefaultOptions();
}
+ # We want 'disabled' preferences to always behave as the default value for
+ # users, even if they have set the option explicitly in their settings (ie they
+ # set it, and then it was disabled removing their ability to change it). But
+ # we don't want to erase the preferences in the database in case the preference
+ # is re-enabled again. So don't touch $mOptions, just override the returned value
+ if( in_array( $oname, $wgHiddenPrefs ) && !$ignoreHidden ){
+ return self::getDefaultOption( $oname );
+ }
+
if ( array_key_exists( $oname, $this->mOptions ) ) {
return $this->mOptions[$oname];
} else {
@@ -1993,31 +2153,45 @@ class User {
* @return array
*/
public function getOptions() {
+ global $wgHiddenPrefs;
$this->loadOptions();
- return $this->mOptions;
+ $options = $this->mOptions;
+
+ # We want 'disabled' preferences to always behave as the default value for
+ # users, even if they have set the option explicitly in their settings (ie they
+ # set it, and then it was disabled removing their ability to change it). But
+ # we don't want to erase the preferences in the database in case the preference
+ # is re-enabled again. So don't touch $mOptions, just override the returned value
+ foreach( $wgHiddenPrefs as $pref ){
+ $default = self::getDefaultOption( $pref );
+ if( $default !== null ){
+ $options[$pref] = $default;
+ }
+ }
+
+ return $options;
}
/**
* Get the user's current setting for a given option, as a boolean value.
*
- * @param $oname \string The option to check
- * @return \bool User's current value for the option
+ * @param $oname String The option to check
+ * @return Bool User's current value for the option
* @see getOption()
*/
- function getBoolOption( $oname ) {
+ public function getBoolOption( $oname ) {
return (bool)$this->getOption( $oname );
}
-
/**
* Get the user's current setting for a given option, as a boolean value.
*
- * @param $oname \string The option to check
- * @param $defaultOverride \int A default value returned if the option does not exist
- * @return \int User's current value for the option
+ * @param $oname String The option to check
+ * @param $defaultOverride Int A default value returned if the option does not exist
+ * @return Int User's current value for the option
* @see getOption()
*/
- function getIntOption( $oname, $defaultOverride=0 ) {
+ public function getIntOption( $oname, $defaultOverride=0 ) {
$val = $this->getOption( $oname );
if( $val == '' ) {
$val = $defaultOverride;
@@ -2028,18 +2202,13 @@ class User {
/**
* Set the given option for a user.
*
- * @param $oname \string The option to set
- * @param $val \mixed New value to set
+ * @param $oname String The option to set
+ * @param $val mixed New value to set
*/
- function setOption( $oname, $val ) {
+ public function setOption( $oname, $val ) {
$this->load();
$this->loadOptions();
- if ( $oname == 'skin' ) {
- # Clear cached skin, so the new one displays immediately in Special:Preferences
- $this->mSkin = null;
- }
-
// Explicitly NULL values should refer to defaults
global $wgDefaultUserOptions;
if( is_null( $val ) && isset( $wgDefaultUserOptions[$oname] ) ) {
@@ -2052,15 +2221,15 @@ class User {
/**
* Reset all options to the site defaults
*/
- function resetOptions() {
- $this->mOptions = User::getDefaultOptions();
+ public function resetOptions() {
+ $this->mOptions = self::getDefaultOptions();
}
/**
* Get the user's preferred date format.
- * @return \string User's preferred date format
+ * @return String User's preferred date format
*/
- function getDatePreference() {
+ public function getDatePreference() {
// Important migration for old data rows
if ( is_null( $this->mDatePreference ) ) {
global $wgLang;
@@ -2076,8 +2245,10 @@ class User {
/**
* Get the user preferred stub threshold
+ *
+ * @return int
*/
- function getStubThreshold() {
+ public function getStubThreshold() {
global $wgMaxArticleSize; # Maximum article size, in Kb
$threshold = intval( $this->getOption( 'stubthreshold' ) );
if ( $threshold > $wgMaxArticleSize * 1024 ) {
@@ -2090,7 +2261,7 @@ class User {
/**
* Get the permissions this user has.
- * @return \type{\arrayof{\string}} Array of permission names
+ * @return Array of String permission names
*/
function getRights() {
if ( is_null( $this->mRights ) ) {
@@ -2105,9 +2276,9 @@ class User {
/**
* Get the list of explicit group memberships this user has.
* The implicit * and user groups are not included.
- * @return \type{\arrayof{\string}} Array of internal group names
+ * @return Array of String internal group names
*/
- function getGroups() {
+ public function getGroups() {
$this->load();
return $this->mGroups;
}
@@ -2115,36 +2286,82 @@ class User {
/**
* Get the list of implicit group memberships this user has.
* This includes all explicit groups, plus 'user' if logged in,
- * '*' for all accounts and autopromoted groups
- * @param $recache \bool Whether to avoid the cache
- * @return \type{\arrayof{\string}} Array of internal group names
+ * '*' for all accounts, and autopromoted groups
+ * @param $recache Bool Whether to avoid the cache
+ * @return Array of String internal group names
*/
- function getEffectiveGroups( $recache = false ) {
+ public function getEffectiveGroups( $recache = false ) {
if ( $recache || is_null( $this->mEffectiveGroups ) ) {
wfProfileIn( __METHOD__ );
- $this->mEffectiveGroups = $this->getGroups();
- $this->mEffectiveGroups[] = '*';
- if( $this->getId() ) {
- $this->mEffectiveGroups[] = 'user';
+ $this->mEffectiveGroups = array_unique( array_merge(
+ $this->getGroups(), // explicit groups
+ $this->getAutomaticGroups( $recache ) // implicit groups
+ ) );
+ # Hook for additional groups
+ wfRunHooks( 'UserEffectiveGroups', array( &$this, &$this->mEffectiveGroups ) );
+ wfProfileOut( __METHOD__ );
+ }
+ return $this->mEffectiveGroups;
+ }
+
+ /**
+ * Get the list of implicit group memberships this user has.
+ * This includes 'user' if logged in, '*' for all accounts,
+ * and autopromoted groups
+ * @param $recache Bool Whether to avoid the cache
+ * @return Array of String internal group names
+ */
+ public function getAutomaticGroups( $recache = false ) {
+ if ( $recache || is_null( $this->mImplicitGroups ) ) {
+ wfProfileIn( __METHOD__ );
+ $this->mImplicitGroups = array( '*' );
+ if ( $this->getId() ) {
+ $this->mImplicitGroups[] = 'user';
- $this->mEffectiveGroups = array_unique( array_merge(
- $this->mEffectiveGroups,
+ $this->mImplicitGroups = array_unique( array_merge(
+ $this->mImplicitGroups,
Autopromote::getAutopromoteGroups( $this )
) );
-
- # Hook for additional groups
- wfRunHooks( 'UserEffectiveGroups', array( &$this, &$this->mEffectiveGroups ) );
+ }
+ if ( $recache ) {
+ # Assure data consistency with rights/groups,
+ # as getEffectiveGroups() depends on this function
+ $this->mEffectiveGroups = null;
}
wfProfileOut( __METHOD__ );
}
- return $this->mEffectiveGroups;
+ return $this->mImplicitGroups;
+ }
+
+ /**
+ * Returns the groups the user has belonged to.
+ *
+ * The user may still belong to the returned groups. Compare with getGroups().
+ *
+ * The function will not return groups the user had belonged to before MW 1.17
+ *
+ * @return array Names of the groups the user has belonged to.
+ */
+ public function getFormerGroups() {
+ if( is_null( $this->mFormerGroups ) ) {
+ $dbr = wfGetDB( DB_MASTER );
+ $res = $dbr->select( 'user_former_groups',
+ array( 'ufg_group' ),
+ array( 'ufg_user' => $this->mId ),
+ __METHOD__ );
+ $this->mFormerGroups = array();
+ foreach( $res as $row ) {
+ $this->mFormerGroups[] = $row->ufg_group;
+ }
+ }
+ return $this->mFormerGroups;
}
/**
* Get the user's edit count.
- * @return \int User'e edit count
+ * @return Int
*/
- function getEditCount() {
+ public function getEditCount() {
if( $this->getId() ) {
if ( !isset( $this->mEditCount ) ) {
/* Populate the count, if it has not been populated yet */
@@ -2160,20 +2377,21 @@ class User {
/**
* Add the user to the given group.
* This takes immediate effect.
- * @param $group \string Name of the group to add
+ * @param $group String Name of the group to add
*/
- function addGroup( $group ) {
- $dbw = wfGetDB( DB_MASTER );
- if( $this->getId() ) {
- $dbw->insert( 'user_groups',
- array(
- 'ug_user' => $this->getID(),
- 'ug_group' => $group,
- ),
- __METHOD__,
- array( 'IGNORE' ) );
+ public function addGroup( $group ) {
+ if( wfRunHooks( 'UserAddGroup', array( $this, &$group ) ) ) {
+ $dbw = wfGetDB( DB_MASTER );
+ if( $this->getId() ) {
+ $dbw->insert( 'user_groups',
+ array(
+ 'ug_user' => $this->getID(),
+ 'ug_group' => $group,
+ ),
+ __METHOD__,
+ array( 'IGNORE' ) );
+ }
}
-
$this->loadGroups();
$this->mGroups[] = $group;
$this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) );
@@ -2184,17 +2402,26 @@ class User {
/**
* Remove the user from the given group.
* This takes immediate effect.
- * @param $group \string Name of the group to remove
+ * @param $group String Name of the group to remove
*/
- function removeGroup( $group ) {
+ public function removeGroup( $group ) {
$this->load();
- $dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'user_groups',
- array(
- 'ug_user' => $this->getID(),
- 'ug_group' => $group,
- ), __METHOD__ );
-
+ if( wfRunHooks( 'UserRemoveGroup', array( $this, &$group ) ) ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete( 'user_groups',
+ array(
+ 'ug_user' => $this->getID(),
+ 'ug_group' => $group,
+ ), __METHOD__ );
+ // Remember that the user was in this group
+ $dbw->insert( 'user_former_groups',
+ array(
+ 'ufg_user' => $this->getID(),
+ 'ufg_group' => $group,
+ ),
+ __METHOD__,
+ array( 'IGNORE' ) );
+ }
$this->loadGroups();
$this->mGroups = array_diff( $this->mGroups, array( $group ) );
$this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) );
@@ -2204,36 +2431,59 @@ class User {
/**
* Get whether the user is logged in
- * @return \bool True or false
+ * @return Bool
*/
- function isLoggedIn() {
+ public function isLoggedIn() {
return $this->getID() != 0;
}
/**
* Get whether the user is anonymous
- * @return \bool True or false
+ * @return Bool
*/
- function isAnon() {
+ public function isAnon() {
return !$this->isLoggedIn();
}
/**
- * Get whether the user is a bot
- * @return \bool True or false
- * @deprecated
+ * Check if user is allowed to access a feature / make an action
+ *
+ * @internal param \String $varargs permissions to test
+ * @return Boolean: True if user is allowed to perform *any* of the given actions
+ *
+ * @return bool
*/
- function isBot() {
- wfDeprecated( __METHOD__ );
- return $this->isAllowed( 'bot' );
+ public function isAllowedAny( /*...*/ ){
+ $permissions = func_get_args();
+ foreach( $permissions as $permission ){
+ if( $this->isAllowed( $permission ) ){
+ return true;
+ }
+ }
+ return false;
}
/**
- * Check if user is allowed to access a feature / make an action
- * @param $action \string action to be checked
- * @return Boolean: True if action is allowed, else false
+ *
+ * @internal param $varargs string
+ * @return bool True if the user is allowed to perform *all* of the given actions
*/
- function isAllowed( $action = '' ) {
+ public function isAllowedAll( /*...*/ ){
+ $permissions = func_get_args();
+ foreach( $permissions as $permission ){
+ if( !$this->isAllowed( $permission ) ){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Internal mechanics of testing a permission
+ * @param $action String
+ * @return bool
+ */
+ public function isAllowed( $action = '' ) {
if ( $action === '' ) {
return true; // In the spirit of DWIM
}
@@ -2254,76 +2504,57 @@ class User {
*/
public function useRCPatrol() {
global $wgUseRCPatrol;
- return( $wgUseRCPatrol && ( $this->isAllowed( 'patrol' ) || $this->isAllowed( 'patrolmarks' ) ) );
+ return $wgUseRCPatrol && $this->isAllowedAny( 'patrol', 'patrolmarks' );
}
/**
* Check whether to enable new pages patrol features for this user
- * @return \bool True or false
+ * @return Bool True or false
*/
public function useNPPatrol() {
global $wgUseRCPatrol, $wgUseNPPatrol;
- return( ( $wgUseRCPatrol || $wgUseNPPatrol ) && ( $this->isAllowed( 'patrol' ) || $this->isAllowed( 'patrolmarks' ) ) );
+ return( ( $wgUseRCPatrol || $wgUseNPPatrol ) && ( $this->isAllowedAny( 'patrol', 'patrolmarks' ) ) );
}
/**
- * Get the current skin, loading it if required, and setting a title
- * @param $t Title: the title to use in the skin
- * @return Skin The current skin
- * @todo FIXME : need to check the old failback system [AV]
- */
- 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;
+ * Get the WebRequest object to use with this object
+ *
+ * @return WebRequest
+ */
+ public function getRequest() {
+ if ( $this->mRequest ) {
+ return $this->mRequest;
} else {
- return $this->mSkin;
- }
- }
-
- // Creates a Skin object, for getSkin()
- private function createSkinObject() {
- wfProfileIn( __METHOD__ );
-
- 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 $wgRequest;
}
+ }
- $skin = Skin::newFromKey( $userSkin );
- wfProfileOut( __METHOD__ );
-
- return $skin;
+ /**
+ * Get the current skin, loading it if required
+ * @return Skin The current skin
+ * @todo FIXME: Need to check the old failback system [AV]
+ * @deprecated since 1.18 Use ->getSkin() in the most relevant outputting context you have
+ */
+ public function getSkin() {
+ return RequestContext::getMain()->getSkin();
}
/**
* Check the watched status of an article.
- * @param $title \type{Title} Title of the article to look at
- * @return \bool True if article is watched
+ * @param $title Title of the article to look at
+ * @return Bool
*/
- function isWatched( $title ) {
+ public function isWatched( $title ) {
$wl = WatchedItem::fromUserTitle( $this, $title );
return $wl->isWatched();
}
/**
* Watch an article.
- * @param $title \type{Title} Title of the article to look at
+ * @param $title Title of the article to look at
*/
- function addWatch( $title ) {
+ public function addWatch( $title ) {
$wl = WatchedItem::fromUserTitle( $this, $title );
$wl->addWatch();
$this->invalidateCache();
@@ -2331,22 +2562,30 @@ class User {
/**
* Stop watching an article.
- * @param $title \type{Title} Title of the article to look at
+ * @param $title Title of the article to look at
*/
- function removeWatch( $title ) {
+ public function removeWatch( $title ) {
$wl = WatchedItem::fromUserTitle( $this, $title );
$wl->removeWatch();
$this->invalidateCache();
}
/**
+ * Cleans up watchlist by removing invalid entries from it
+ */
+ public function cleanupWatchlist() {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete( 'watchlist', array( 'wl_namespace < 0', 'wl_user' => $this->getId() ), __METHOD__ );
+ }
+
+ /**
* Clear the user's notification timestamp for the given title.
* If e-notif e-mails are on, they will receive notification mails on
* the next change of the page if it's watched etc.
- * @param $title \type{Title} Title of the article to look at
+ * @param $title Title of the article to look at
*/
- function clearNotification( &$title ) {
- global $wgUser, $wgUseEnotif, $wgShowUpdatedMarker;
+ public function clearNotification( &$title ) {
+ global $wgUseEnotif, $wgShowUpdatedMarker;
# Do nothing if the database is locked to writes
if( wfReadOnly() ) {
@@ -2374,13 +2613,11 @@ class User {
// and when it does have to be executed, it can be on a slave
// If this is the user's newtalk page, we always update the timestamp
if( $title->getNamespace() == NS_USER_TALK &&
- $title->getText() == $wgUser->getName() )
+ $title->getText() == $this->getName() )
{
$watched = true;
- } elseif ( $this->getId() == $wgUser->getId() ) {
- $watched = $title->userIsWatching();
} else {
- $watched = true;
+ $watched = $this->isWatched( $title );
}
// If the page is watched by the user (or may be watched), update the timestamp on any
@@ -2403,22 +2640,21 @@ class User {
* Resets all of the given user's page-change notification timestamps.
* If e-notif e-mails are on, they will receive notification mails on
* the next change of any watched page.
- *
- * @param $currentUser \int User ID
*/
- function clearAllNotifications( $currentUser ) {
+ public function clearAllNotifications() {
global $wgUseEnotif, $wgShowUpdatedMarker;
if ( !$wgUseEnotif && !$wgShowUpdatedMarker ) {
$this->setNewtalk( false );
return;
}
- if( $currentUser != 0 ) {
+ $id = $this->getId();
+ if( $id != 0 ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'watchlist',
array( /* SET */
'wl_notificationtimestamp' => null
), array( /* WHERE */
- 'wl_user' => $currentUser
+ 'wl_user' => $id
), __METHOD__
);
# We also need to clear here the "you have new message" notification for the own user_talk page
@@ -2428,10 +2664,9 @@ class User {
/**
* Set this user's options from an encoded string
- * @param $str \string Encoded options to import
- * @private
+ * @param $str String Encoded options to import
*/
- function decodeOptions( $str ) {
+ private function decodeOptions( $str ) {
if( !$str )
return;
@@ -2454,19 +2689,18 @@ class User {
/**
* Set a cookie on the user's client. Wrapper for
* WebResponse::setCookie
- * @param $name \string Name of the cookie to set
- * @param $value \string Value to set
- * @param $exp \int Expiration time, as a UNIX time value;
+ * @param $name String Name of the cookie to set
+ * @param $value String Value to set
+ * @param $exp Int Expiration time, as a UNIX time value;
* if 0 or not specified, use the default $wgCookieExpiration
*/
protected function setCookie( $name, $value, $exp = 0 ) {
- global $wgRequest;
- $wgRequest->response()->setcookie( $name, $value, $exp );
+ $this->getRequest()->response()->setcookie( $name, $value, $exp );
}
/**
* Clear a cookie on the user's client
- * @param $name \string Name of the cookie to clear
+ * @param $name String Name of the cookie to clear
*/
protected function clearCookie( $name ) {
$this->setCookie( $name, '', time() - 86400 );
@@ -2474,8 +2708,15 @@ class User {
/**
* Set the default cookies for this session on the user's client.
+ *
+ * @param $request WebRequest object to use; $wgRequest will be used if null
+ * is passed.
*/
- function setCookies() {
+ public function setCookies( $request = null ) {
+ if ( $request === null ) {
+ $request = $this->getRequest();
+ }
+
$this->load();
if ( 0 == $this->mId ) return;
$session = array(
@@ -2494,9 +2735,9 @@ class User {
}
wfRunHooks( 'UserSetCookies', array( $this, &$session, &$cookies ) );
- #check for null, since the hook could cause a null value
- if ( !is_null( $session ) && isset( $_SESSION ) ){
- $_SESSION = $session + $_SESSION;
+
+ foreach ( $session as $name => $value ) {
+ $request->setSessionData( $name, $value );
}
foreach ( $cookies as $name => $value ) {
if ( $value === false ) {
@@ -2510,7 +2751,7 @@ class User {
/**
* Log this user out.
*/
- function logout() {
+ public function logout() {
if( wfRunHooks( 'UserLogout', array( &$this ) ) ) {
$this->doLogout();
}
@@ -2518,13 +2759,12 @@ class User {
/**
* Clear the user's cookies and session, and reset the instance cache.
- * @private
* @see logout()
*/
- function doLogout() {
+ public function doLogout() {
$this->clearInstanceCache( 'defaults' );
- $_SESSION['wsUserID'] = 0;
+ $this->getRequest()->setSessionData( 'wsUserID', 0 );
$this->clearCookie( 'UserID' );
$this->clearCookie( 'Token' );
@@ -2537,7 +2777,7 @@ class User {
* Save this user's settings into the database.
* @todo Only rarely do all these fields need to be set!
*/
- function saveSettings() {
+ public function saveSettings() {
$this->load();
if ( wfReadOnly() ) { return; }
if ( 0 == $this->mId ) { return; }
@@ -2573,8 +2813,9 @@ class User {
/**
* If only this user's username is known, and it exists, return the user ID.
+ * @return Int
*/
- function idForName() {
+ public function idForName() {
$s = trim( $this->getName() );
if ( $s === '' ) return 0;
@@ -2589,10 +2830,10 @@ class User {
/**
* Add a user to the database, return the user object
*
- * @param $name \string Username to add
- * @param $params \type{\arrayof{\string}} Non-default parameters to save to the database:
- * - password The user's password. Password logins will be disabled if this is omitted.
- * - newpassword A temporary password mailed to the user
+ * @param $name String Username to add
+ * @param $params Array of Strings Non-default parameters to save to the database as user_* fields:
+ * - password The user's password hash. Password logins will be disabled if this is omitted.
+ * - newpassword Hash for a temporary password that has been mailed to the user
* - email The user's email address
* - email_authenticated The email authentication timestamp
* - real_name The user's real name
@@ -2600,9 +2841,9 @@ class User {
* - token Random authentication token. Do not set.
* - registration Registration timestamp. Do not set.
*
- * @return \type{User} A new User object, or null if the username already exists
+ * @return User object, or null if the username already exists
*/
- static function createNew( $name, $params = array() ) {
+ public static function createNew( $name, $params = array() ) {
$user = new User;
$user->load();
if ( isset( $params['options'] ) ) {
@@ -2641,7 +2882,7 @@ class User {
/**
* Add this existing user object to the database
*/
- function addToDatabase() {
+ public function addToDatabase() {
$this->load();
$dbw = wfGetDB( DB_MASTER );
$seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
@@ -2670,22 +2911,35 @@ class User {
}
/**
- * If this (non-anonymous) user is blocked, block any IP address
- * they've successfully logged in from.
+ * If this user is logged-in and blocked,
+ * block any IP address they've successfully logged in from.
+ * @return bool A block was spread
*/
- function spreadBlock() {
+ public function spreadAnyEditBlock() {
+ if ( $this->isLoggedIn() && $this->isBlocked() ) {
+ return $this->spreadBlock();
+ }
+ return false;
+ }
+
+ /**
+ * If this (non-anonymous) user is blocked,
+ * block the IP address they've successfully logged in from.
+ * @return bool A block was spread
+ */
+ protected function spreadBlock() {
wfDebug( __METHOD__ . "()\n" );
$this->load();
if ( $this->mId == 0 ) {
- return;
+ return false;
}
- $userblock = Block::newFromDB( '', $this->mId );
+ $userblock = Block::newFromTarget( $this->getName() );
if ( !$userblock ) {
- return;
+ return false;
}
- $userblock->doAutoblock( wfGetIP() );
+ return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
}
/**
@@ -2699,10 +2953,10 @@ 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
+ * @deprecated since 1.17 use the ParserOptions object to get the relevant options
+ * @return String Page rendering hash
*/
- function getPageRenderingHash() {
+ public function getPageRenderingHash() {
global $wgUseDynamicDates, $wgRenderHashAppend, $wgLang, $wgContLang;
if( $this->mHash ){
return $this->mHash;
@@ -2742,25 +2996,37 @@ class User {
/**
* Get whether the user is explicitly blocked from account creation.
- * @return \bool True if blocked
+ * @return Bool|Block
*/
- function isBlockedFromCreateAccount() {
+ public function isBlockedFromCreateAccount() {
$this->getBlockedStatus();
- return $this->mBlock && $this->mBlock->mCreateAccount;
+ if( $this->mBlock && $this->mBlock->prevents( 'createaccount' ) ){
+ return $this->mBlock;
+ }
+
+ # bug 13611: if the IP address the user is trying to create an account from is
+ # blocked with createaccount disabled, prevent new account creation there even
+ # when the user is logged in
+ if( $this->mBlockedFromCreateAccount === false ){
+ $this->mBlockedFromCreateAccount = Block::newFromTarget( null, wfGetIP() );
+ }
+ return $this->mBlockedFromCreateAccount instanceof Block && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
+ ? $this->mBlockedFromCreateAccount
+ : false;
}
/**
* Get whether the user is blocked from using Special:Emailuser.
- * @return Boolean: True if blocked
+ * @return Bool
*/
- function isBlockedFromEmailuser() {
+ public function isBlockedFromEmailuser() {
$this->getBlockedStatus();
- return $this->mBlock && $this->mBlock->mBlockEmail;
+ return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
}
/**
* Get whether the user is allowed to create an account.
- * @return Boolean: True if allowed
+ * @return Bool
*/
function isAllowedToCreateAccount() {
return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount();
@@ -2771,7 +3037,7 @@ class User {
*
* @return Title: User's personal page title
*/
- function getUserPage() {
+ public function getUserPage() {
return Title::makeTitle( NS_USER, $this->getName() );
}
@@ -2780,33 +3046,17 @@ class User {
*
* @return Title: User's talk page title
*/
- function getTalkPage() {
+ public function getTalkPage() {
$title = $this->getUserPage();
return $title->getTalkPage();
}
/**
- * Get the maximum valid user ID.
- * @return Integer: User ID
- * @static
- */
- function getMaxID() {
- static $res; // cache
-
- if ( isset( $res ) ) {
- return $res;
- } else {
- $dbr = wfGetDB( DB_SLAVE );
- 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 Boolean: True if the user is a newbie
+ * @return Bool
*/
- function isNewbie() {
+ public function isNewbie() {
return !$this->isAllowed( 'autoconfirmed' );
}
@@ -2815,8 +3065,8 @@ class User {
* @param $password String: user password.
* @return Boolean: True if the given password is correct, otherwise False.
*/
- function checkPassword( $password ) {
- global $wgAuth;
+ public function checkPassword( $password ) {
+ global $wgAuth, $wgLegacyEncoding;
$this->load();
// Even though we stop people from creating passwords that
@@ -2839,11 +3089,13 @@ class User {
}
if ( self::comparePasswords( $this->mPassword, $password, $this->mId ) ) {
return true;
- } elseif ( function_exists( 'iconv' ) ) {
+ } elseif ( $wgLegacyEncoding ) {
# Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
# Check for this with iconv
$cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $password );
- if ( self::comparePasswords( $this->mPassword, $cp1252Password, $this->mId ) ) {
+ if ( $cp1252Password != $password &&
+ self::comparePasswords( $this->mPassword, $cp1252Password, $this->mId ) )
+ {
return true;
}
}
@@ -2853,10 +3105,15 @@ class User {
/**
* Check if the given clear-text password matches the temporary password
* sent by e-mail for password reset operations.
+ *
+ * @param $plaintext string
+ *
* @return Boolean: True if matches, false otherwise
*/
- function checkTemporaryPassword( $plaintext ) {
+ public function checkTemporaryPassword( $plaintext ) {
global $wgNewPasswordExpiry;
+
+ $this->load();
if( self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ) ) {
if ( is_null( $this->mNewpassTime ) ) {
return true;
@@ -2874,18 +3131,22 @@ class User {
* login credentials aren't being hijacked with a foreign form
* submission.
*
- * @param $salt \types{\string,\arrayof{\string}} Optional function-specific data for hashing
- * @return \string The new edit token
+ * @param $salt String|Array of Strings Optional function-specific data for hashing
+ * @param $request WebRequest object to use or null to use $wgRequest
+ * @return String The new edit token
*/
- function editToken( $salt = '' ) {
+ public function editToken( $salt = '', $request = null ) {
+ if ( $request == null ) {
+ $request = $this->getRequest();
+ }
+
if ( $this->isAnon() ) {
return EDIT_TOKEN_SUFFIX;
} else {
- if( !isset( $_SESSION['wsEditToken'] ) ) {
+ $token = $request->getSessionData( 'wsEditToken' );
+ if ( $token === null ) {
$token = self::generateToken();
- $_SESSION['wsEditToken'] = $token;
- } else {
- $token = $_SESSION['wsEditToken'];
+ $request->setSessionData( 'wsEditToken', $token );
}
if( is_array( $salt ) ) {
$salt = implode( '|', $salt );
@@ -2897,8 +3158,8 @@ class User {
/**
* Generate a looking random token for various uses.
*
- * @param $salt \string Optional salt value
- * @return \string The new random token
+ * @param $salt String Optional salt value
+ * @return String The new random token
*/
public static function generateToken( $salt = '' ) {
$token = dechex( mt_rand() ) . dechex( mt_rand() );
@@ -2911,12 +3172,13 @@ class User {
* user's own login session, not a form submission from a third-party
* site.
*
- * @param $val \string Input value to compare
- * @param $salt \string Optional function-specific data for hashing
+ * @param $val String Input value to compare
+ * @param $salt String Optional function-specific data for hashing
+ * @param $request WebRequest object to use or null to use $wgRequest
* @return Boolean: Whether the token matches
*/
- function matchEditToken( $val, $salt = '' ) {
- $sessionToken = $this->editToken( $salt );
+ public function matchEditToken( $val, $salt = '', $request = null ) {
+ $sessionToken = $this->editToken( $salt, $request );
if ( $val != $sessionToken ) {
wfDebug( "User::matchEditToken: broken session data\n" );
}
@@ -2927,12 +3189,13 @@ class User {
* Check given value against the token value stored in the session,
* ignoring the suffix.
*
- * @param $val \string Input value to compare
- * @param $salt \string Optional function-specific data for hashing
+ * @param $val String Input value to compare
+ * @param $salt String Optional function-specific data for hashing
+ * @param $request WebRequest object to use or null to use $wgRequest
* @return Boolean: Whether the token matches
*/
- function matchEditTokenNoSuffix( $val, $salt = '' ) {
- $sessionToken = $this->editToken( $salt );
+ public function matchEditTokenNoSuffix( $val, $salt = '', $request = null ) {
+ $sessionToken = $this->editToken( $salt, $request );
return substr( $sessionToken, 0, 32 ) == substr( $val, 0, 32 );
}
@@ -2943,7 +3206,7 @@ class User {
* @param $type String: message to send, either "created", "changed" or "set"
* @return Status object
*/
- function sendConfirmationMail( $type = 'created' ) {
+ public function sendConfirmationMail( $type = 'created' ) {
global $wgLang;
$expiration = null; // gets passed-by-ref and defined in next line.
$token = $this->confirmationToken( $expiration );
@@ -2974,13 +3237,13 @@ class User {
* Send an e-mail to this user's account. Does not check for
* confirmed status or validity.
*
- * @param $subject \string Message subject
- * @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 Status object
+ * @param $subject String Message subject
+ * @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 Status
*/
- function sendMail( $subject, $body, $from = null, $replyto = null ) {
+ public function sendMail( $subject, $body, $from = null, $replyto = null ) {
if( is_null( $from ) ) {
global $wgPasswordSender, $wgPasswordSenderName;
$sender = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
@@ -2999,13 +3262,13 @@ class User {
* @note Call saveSettings() after calling this function to commit
* this change to the database.
*
- * @param[out] &$expiration \mixed Accepts the expiration time
- * @return \string New token
- * @private
+ * @param &$expiration \mixed Accepts the expiration time
+ * @return String New token
*/
- function confirmationToken( &$expiration ) {
+ private function confirmationToken( &$expiration ) {
+ global $wgUserEmailConfirmationTokenExpiry;
$now = time();
- $expires = $now + 7 * 24 * 60 * 60;
+ $expires = $now + $wgUserEmailConfirmationTokenExpiry;
$expiration = wfTimestamp( TS_MW, $expires );
$token = self::generateToken( $this->mId . $this->mEmail . $expires );
$hash = md5( $token );
@@ -3017,27 +3280,25 @@ class User {
/**
* Return a URL the user can use to confirm their email address.
- * @param $token \string Accepts the email confirmation token
- * @return \string New token URL
- * @private
+ * @param $token String Accepts the email confirmation token
+ * @return String New token URL
*/
- function confirmationTokenUrl( $token ) {
+ private function confirmationTokenUrl( $token ) {
return $this->getTokenUrl( 'ConfirmEmail', $token );
}
/**
* Return a URL the user can use to invalidate their email address.
- * @param $token \string Accepts the email confirmation token
- * @return \string New token URL
- * @private
+ * @param $token String Accepts the email confirmation token
+ * @return String New token URL
*/
- function invalidationTokenUrl( $token ) {
+ private function invalidationTokenUrl( $token ) {
return $this->getTokenUrl( 'Invalidateemail', $token );
}
/**
* Internal function to format the e-mail validation/invalidation URLs.
- * This uses $wgArticlePath directly as a quickie hack to use the
+ * This uses a quickie hack to use the
* hardcoded English names of the Special: pages, for ASCII safety.
*
* @note Since these URLs get dropped directly into emails, using the
@@ -3045,25 +3306,24 @@ class User {
* also sometimes can get corrupted in some browsers/mailers
* (bug 6957 with Gmail and Internet Explorer).
*
- * @param $page \string Special page
- * @param $token \string Token
- * @return \string Formatted URL
+ * @param $page String Special page
+ * @param $token String Token
+ * @return String Formatted URL
*/
protected function getTokenUrl( $page, $token ) {
- global $wgArticlePath;
- return wfExpandUrl(
- str_replace(
- '$1',
- "Special:$page/$token",
- $wgArticlePath ) );
+ // Hack to bypass localization of 'Special:'
+ $title = Title::makeTitle( NS_MAIN, "Special:$page/$token" );
+ return $title->getCanonicalUrl();
}
/**
* Mark the e-mail address confirmed.
*
* @note Call saveSettings() after calling this function to commit the change.
+ *
+ * @return true
*/
- function confirmEmail() {
+ public function confirmEmail() {
$this->setEmailAuthenticationTimestamp( wfTimestampNow() );
wfRunHooks( 'ConfirmEmailComplete', array( $this ) );
return true;
@@ -3074,6 +3334,7 @@ class User {
* address if it was already confirmed.
*
* @note Call saveSettings() after calling this function to commit the change.
+ * @return true
*/
function invalidateEmail() {
$this->load();
@@ -3086,7 +3347,7 @@ class User {
/**
* Set the e-mail authentication timestamp.
- * @param $timestamp \string TS_MW timestamp
+ * @param $timestamp String TS_MW timestamp
*/
function setEmailAuthenticationTimestamp( $timestamp ) {
$this->load();
@@ -3097,9 +3358,9 @@ class User {
/**
* Is this user allowed to send e-mails within limits of current
* site configuration?
- * @return Boolean: True if allowed
+ * @return Bool
*/
- function canSendEmail() {
+ public function canSendEmail() {
global $wgEnableEmail, $wgEnableUserEmail;
if( !$wgEnableEmail || !$wgEnableUserEmail || !$this->isAllowed( 'sendemail' ) ) {
return false;
@@ -3112,9 +3373,9 @@ class User {
/**
* Is this user allowed to receive e-mails within limits of current
* site configuration?
- * @return Boolean: True if allowed
+ * @return Bool
*/
- function canReceiveEmail() {
+ public function canReceiveEmail() {
return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' );
}
@@ -3126,19 +3387,22 @@ class User {
* confirmed their address by returning a code or using a password
* sent to the address from the wiki.
*
- * @return Boolean: True if confirmed
+ * @return Bool
*/
- function isEmailConfirmed() {
+ public function isEmailConfirmed() {
global $wgEmailAuthentication;
$this->load();
$confirmed = true;
if( wfRunHooks( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) {
- if( $this->isAnon() )
+ if( $this->isAnon() ) {
return false;
- if( !self::isValidEmailAddr( $this->mEmail ) )
+ }
+ if( !Sanitizer::validateEmail( $this->mEmail ) ) {
return false;
- if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
+ }
+ if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() ) {
return false;
+ }
return true;
} else {
return $confirmed;
@@ -3147,9 +3411,9 @@ class User {
/**
* Check whether there is an outstanding request for e-mail confirmation.
- * @return Boolean: True if pending
+ * @return Bool
*/
- function isEmailConfirmationPending() {
+ public function isEmailConfirmationPending() {
global $wgEmailAuthentication;
return $wgEmailAuthentication &&
!$this->isEmailConfirmed() &&
@@ -3160,43 +3424,52 @@ class User {
/**
* Get the timestamp of account creation.
*
- * @return \types{\string,\bool} string Timestamp of account creation, or false for
- * non-existent/anonymous user accounts.
+ * @return String|Bool Timestamp of account creation, or false for
+ * non-existent/anonymous user accounts.
*/
public function getRegistration() {
- return $this->getId() > 0
- ? $this->mRegistration
- : false;
+ if ( $this->isAnon() ) {
+ return false;
+ }
+ $this->load();
+ return $this->mRegistration;
}
/**
* Get the timestamp of the first edit
*
- * @return \types{\string,\bool} string Timestamp of first edit, or false for
- * non-existent/anonymous user accounts.
+ * @return String|Bool Timestamp of first edit, or false for
+ * non-existent/anonymous user accounts.
*/
public function getFirstEditTimestamp() {
- if( $this->getId() == 0 ) return false; // anons
+ if( $this->getId() == 0 ) {
+ return false; // anons
+ }
$dbr = wfGetDB( DB_SLAVE );
$time = $dbr->selectField( 'revision', 'rev_timestamp',
array( 'rev_user' => $this->getId() ),
__METHOD__,
array( 'ORDER BY' => 'rev_timestamp ASC' )
);
- if( !$time ) return false; // no edits
+ if( !$time ) {
+ return false; // no edits
+ }
return wfTimestamp( TS_MW, $time );
}
/**
* Get the permissions associated with a given list of groups
*
- * @param $groups \type{\arrayof{\string}} List of internal group names
- * @return \type{\arrayof{\string}} List of permission key names for given groups combined
+ * @param $groups Array of Strings List of internal group names
+ * @param $ns int
+ *
+ * @return Array of Strings List of permission key names for given groups combined
*/
- static function getGroupPermissions( $groups ) {
+ public static function getGroupPermissions( $groups ) {
global $wgGroupPermissions, $wgRevokePermissions;
$rights = array();
- // grant every granted permission first
+
+ // Grant every granted permission first
foreach( $groups as $group ) {
if( isset( $wgGroupPermissions[$group] ) ) {
$rights = array_merge( $rights,
@@ -3204,7 +3477,8 @@ class User {
array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
}
}
- // now revoke the revoked permissions
+
+ // Revoke the revoked permissions
foreach( $groups as $group ) {
if( isset( $wgRevokePermissions[$group] ) ) {
$rights = array_diff( $rights,
@@ -3217,10 +3491,13 @@ class User {
/**
* Get all the groups who have a given permission
*
- * @param $role \string Role to check
- * @return \type{\arrayof{\string}} List of internal group names with the given permission
+ * @param $role String Role to check
+ * @param $ns int
+ *
+ *
+ * @return Array of Strings List of internal group names with the given permission
*/
- static function getGroupsWithPermission( $role ) {
+ public static function getGroupsWithPermission( $role ) {
global $wgGroupPermissions;
$allowedGroups = array();
foreach ( $wgGroupPermissions as $group => $rights ) {
@@ -3234,29 +3511,23 @@ class User {
/**
* Get the localized descriptive name for a group, if it exists
*
- * @param $group \string Internal group name
- * @return \string Localized descriptive group name
+ * @param $group String Internal group name
+ * @return String Localized descriptive group name
*/
- static function getGroupName( $group ) {
- $key = "group-$group";
- $name = wfMsg( $key );
- return $name == '' || wfEmptyMsg( $key, $name )
- ? $group
- : $name;
+ public static function getGroupName( $group ) {
+ $msg = wfMessage( "group-$group" );
+ return $msg->isBlank() ? $group : $msg->text();
}
/**
* Get the localized descriptive name for a member of a group, if it exists
*
- * @param $group \string Internal group name
- * @return \string Localized name for group member
+ * @param $group String Internal group name
+ * @return String Localized name for group member
*/
- static function getGroupMember( $group ) {
- $key = "group-$group-member";
- $name = wfMsg( $key );
- return $name == '' || wfEmptyMsg( $key, $name )
- ? $group
- : $name;
+ public static function getGroupMember( $group ) {
+ $msg = wfMessage( "group-$group-member" );
+ return $msg->isBlank() ? $group : $msg->text();
}
/**
@@ -3265,7 +3536,7 @@ class User {
* are not included, as they are defined automatically, not in the database.
* @return Array of internal group names
*/
- static function getAllGroups() {
+ public static function getAllGroups() {
global $wgGroupPermissions, $wgRevokePermissions;
return array_diff(
array_merge( array_keys( $wgGroupPermissions ), array_keys( $wgRevokePermissions ) ),
@@ -3277,7 +3548,7 @@ class User {
* Get a list of all available permissions.
* @return Array of permission names
*/
- static function getAllRights() {
+ public static function getAllRights() {
if ( self::$mAllRights === false ) {
global $wgAvailableRights;
if ( count( $wgAvailableRights ) ) {
@@ -3292,7 +3563,7 @@ class User {
/**
* Get a list of implicit groups
- * @return \type{\arrayof{\string}} Array of internal group names
+ * @return Array of Strings Array of internal group names
*/
public static function getImplicitGroups() {
global $wgImplicitGroups;
@@ -3304,13 +3575,13 @@ class User {
/**
* Get the title of a page describing a particular group
*
- * @param $group \string Internal group name
- * @return \types{\type{Title},\bool} Title of the page if it exists, false otherwise
+ * @param $group String Internal group name
+ * @return Title|Bool Title of the page if it exists, false otherwise
*/
- static function getGroupPage( $group ) {
- $page = wfMsgForContent( 'grouppage-' . $group );
- if( !wfEmptyMsg( 'grouppage-' . $group, $page ) ) {
- $title = Title::newFromText( $page );
+ public static function getGroupPage( $group ) {
+ $msg = wfMessage( 'grouppage-' . $group )->inContentLanguage();
+ if( $msg->exists() ) {
+ $title = Title::newFromText( $msg->text() );
if( is_object( $title ) )
return $title;
}
@@ -3321,19 +3592,17 @@ class User {
* Create a link to the group in HTML, if available;
* else return the group name.
*
- * @param $group \string Internal name of the group
- * @param $text \string The text of the link
- * @return \string HTML link to the group
+ * @param $group String Internal name of the group
+ * @param $text String The text of the link
+ * @return String HTML link to the group
*/
- static function makeGroupLinkHTML( $group, $text = '' ) {
+ public static function makeGroupLinkHTML( $group, $text = '' ) {
if( $text == '' ) {
$text = self::getGroupName( $group );
}
$title = self::getGroupPage( $group );
if( $title ) {
- global $wgUser;
- $sk = $wgUser->getSkin();
- return $sk->link( $title, htmlspecialchars( $text ) );
+ return Linker::link( $title, htmlspecialchars( $text ) );
} else {
return $text;
}
@@ -3343,11 +3612,11 @@ class User {
* Create a link to the group in Wikitext, if available;
* else return the group name.
*
- * @param $group \string Internal name of the group
- * @param $text \string The text of the link
- * @return \string Wikilink to the group
+ * @param $group String Internal name of the group
+ * @param $text String The text of the link
+ * @return String Wikilink to the group
*/
- static function makeGroupLinkWiki( $group, $text = '' ) {
+ public static function makeGroupLinkWiki( $group, $text = '' ) {
if( $text == '' ) {
$text = self::getGroupName( $group );
}
@@ -3365,11 +3634,11 @@ class User {
*
* @param $group String: the group to check for whether it can add/remove
* @return Array array( 'add' => array( addablegroups ),
- * 'remove' => array( removablegroups ),
- * 'add-self' => array( addablegroups to self),
- * 'remove-self' => array( removable groups from self) )
+ * 'remove' => array( removablegroups ),
+ * 'add-self' => array( addablegroups to self),
+ * 'remove-self' => array( removable groups from self) )
*/
- static function changeableByGroup( $group ) {
+ public static function changeableByGroup( $group ) {
global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
$groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() );
@@ -3433,7 +3702,7 @@ class User {
* 'add-self' => array( addablegroups to self),
* 'remove-self' => array( removable groups from self) )
*/
- function changeableGroups() {
+ public function changeableGroups() {
if( $this->isAllowed( 'userrights' ) ) {
// This group gives the right to modify everything (reverse-
// compatibility with old "userrights lets you change
@@ -3473,7 +3742,7 @@ class User {
* Increment the user's edit-count field.
* Will have no effect for anonymous users.
*/
- function incEditCount() {
+ public function incEditCount() {
if( !$this->isAnon() ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'user',
@@ -3516,25 +3785,23 @@ class User {
/**
* Get the description of a given right
*
- * @param $right \string Right to query
- * @return \string Localized description of the right
+ * @param $right String Right to query
+ * @return String Localized description of the right
*/
- static function getRightDescription( $right ) {
+ public static function getRightDescription( $right ) {
$key = "right-$right";
- $name = wfMsg( $key );
- return $name == '' || wfEmptyMsg( $key, $name )
- ? $right
- : $name;
+ $msg = wfMessage( $key );
+ return $msg->isBlank() ? $right : $msg->text();
}
/**
* Make an old-style password hash
*
- * @param $password \string Plain-text password
- * @param $userId \string User ID
- * @return \string Password hash
+ * @param $password String Plain-text password
+ * @param $userId String User ID
+ * @return String Password hash
*/
- static function oldCrypt( $password, $userId ) {
+ public static function oldCrypt( $password, $userId ) {
global $wgPasswordSalt;
if ( $wgPasswordSalt ) {
return md5( $userId . '-' . md5( $password ) );
@@ -3546,12 +3813,13 @@ class User {
/**
* Make a new-style password hash
*
- * @param $password \string Plain-text password
- * @param $salt \string Optional salt, may be random or the user ID.
+ * @param $password String Plain-text password
+ * @param bool|string $salt Optional salt, may be random or the user ID.
+
* If unspecified or false, will generate one automatically
- * @return \string Password hash
+ * @return String Password hash
*/
- static function crypt( $password, $salt = false ) {
+ public static function crypt( $password, $salt = false ) {
global $wgPasswordSalt;
$hash = '';
@@ -3573,12 +3841,13 @@ class User {
* Compare a password hash with a plain-text password. Requires the user
* ID if there's a chance that the hash is an old-style hash.
*
- * @param $hash \string Password hash
- * @param $password \string Plain-text password to compare
- * @param $userId \string User ID for old-style password salt
- * @return Boolean:
+ * @param $hash String Password hash
+ * @param $password String Plain-text password to compare
+ * @param $userId String|bool User ID for old-style password salt
+ *
+ * @return Boolean
*/
- static function comparePasswords( $hash, $password, $userId = false ) {
+ public static function comparePasswords( $hash, $password, $userId = false ) {
$type = substr( $hash, 0, 3 );
$result = false;
@@ -3604,6 +3873,8 @@ class User {
*
* @param $byEmail Boolean: account made by email?
* @param $reason String: user supplied reason
+ *
+ * @return true
*/
public function addNewUserLogEntry( $byEmail = false, $reason = '' ) {
global $wgUser, $wgContLang, $wgNewUserLog;
@@ -3637,10 +3908,12 @@ class User {
/**
* Add an autocreate newuser log entry for this user
* Used by things like CentralAuth and perhaps other authplugins.
+ *
+ * @return true
*/
public function addNewUserLogEntryAutoCreate() {
- global $wgNewUserLog, $wgLogAutocreatedAccounts;
- if( !$wgNewUserLog || !$wgLogAutocreatedAccounts ) {
+ global $wgNewUserLog;
+ if( !$wgNewUserLog ) {
return true; // disabled
}
$log = new LogPage( 'newusers', false );
@@ -3648,6 +3921,9 @@ class User {
return true;
}
+ /**
+ * @todo document
+ */
protected function loadOptions() {
$this->load();
if ( $this->mOptionsLoaded || !$this->getId() )
@@ -3684,6 +3960,9 @@ class User {
wfRunHooks( 'UserLoadOptions', array( $this, &$this->mOptions ) );
}
+ /**
+ * @todo document
+ */
protected function saveOptions() {
global $wgAllowPrefChange;
@@ -3698,8 +3977,9 @@ class User {
// Allow hooks to abort, for instance to save to a global profile.
// Reset options to default state before saving.
- if( !wfRunHooks( 'UserSaveOptions', array( $this, &$saveOptions ) ) )
+ if( !wfRunHooks( 'UserSaveOptions', array( $this, &$saveOptions ) ) ) {
return;
+ }
foreach( $saveOptions as $key => $value ) {
# Don't bother storing default values
@@ -3747,6 +4027,8 @@ class User {
* actually just returns array() unconditionally at the moment. May as
* well keep it around for when the browser bugs get fixed, though.
*
+ * @todo FIXME: This does not belong here; put it in Html or Linker or somewhere
+ *
* @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
@@ -3762,7 +4044,7 @@ class User {
# Note that the pattern requirement will always be satisfied if the
# input is empty, so we need required in all cases.
#
- # FIXME (bug 23769): This needs to not claim the password is required
+ # @todo 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
@@ -3786,92 +4068,4 @@ 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/UserArray.php b/includes/UserArray.php
index d48a4440..6cce48c0 100644
--- a/includes/UserArray.php
+++ b/includes/UserArray.php
@@ -1,6 +1,10 @@
<?php
abstract class UserArray implements Iterator {
+ /**
+ * @param $res ResultWrapper
+ * @return UserArrayFromResult
+ */
static function newFromResult( $res ) {
$userArray = null;
if ( !wfRunHooks( 'UserArrayFromResult', array( &$userArray, $res ) ) ) {
@@ -12,33 +16,52 @@ abstract class UserArray implements Iterator {
return $userArray;
}
+ /**
+ * @param $ids array
+ * @return UserArrayFromResult
+ */
static function newFromIDs( $ids ) {
$ids = array_map( 'intval', (array)$ids ); // paranoia
- if ( !$ids )
+ if ( !$ids ) {
// Database::select() doesn't like empty arrays
return new ArrayIterator(array());
+ }
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'user', '*', array( 'user_id' => $ids ),
__METHOD__ );
return self::newFromResult( $res );
}
+ /**
+ * @param $res
+ * @return UserArrayFromResult
+ */
protected static function newFromResult_internal( $res ) {
- $userArray = new UserArrayFromResult( $res );
- return $userArray;
+ return new UserArrayFromResult( $res );
}
}
class UserArrayFromResult extends UserArray {
+
+ /**
+ * @var ResultWrapper
+ */
var $res;
var $key, $current;
+ /**
+ * @param $res ResultWrapper
+ */
function __construct( $res ) {
$this->res = $res;
$this->key = 0;
$this->setCurrent( $this->res->current() );
}
+ /**
+ * @param $row
+ * @return void
+ */
protected function setCurrent( $row ) {
if ( $row === false ) {
$this->current = false;
@@ -47,6 +70,9 @@ class UserArrayFromResult extends UserArray {
}
}
+ /**
+ * @return int
+ */
public function count() {
return $this->res->numRows();
}
@@ -71,6 +97,9 @@ class UserArrayFromResult extends UserArray {
$this->setCurrent( $this->res->current() );
}
+ /**
+ * @return bool
+ */
function valid() {
return $this->current !== false;
}
diff --git a/includes/UserMailer.php b/includes/UserMailer.php
index c15843d9..5976c6fd 100644
--- a/includes/UserMailer.php
+++ b/includes/UserMailer.php
@@ -31,7 +31,7 @@
*/
class MailAddress {
/**
- * @param $address Mixed: string with an email address, or a User object
+ * @param $address string|User 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
*/
@@ -55,16 +55,18 @@ 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() ) {
- global $wgEnotifUseRealName;
- $name = ( $wgEnotifUseRealName && $this->realName ) ? $this->realName : $this->name;
- $quoted = wfQuotedPrintable( $name );
- if ( strpos( $quoted, '.' ) !== false || strpos( $quoted, ',' ) !== false ) {
- $quoted = '"' . $quoted . '"';
+ if ( $this->address ) {
+ if ( $this->name != '' && !wfIsWindows() ) {
+ global $wgEnotifUseRealName;
+ $name = ( $wgEnotifUseRealName && $this->realName ) ? $this->realName : $this->name;
+ $quoted = UserMailer::quotedPrintable( $name );
+ if ( strpos( $quoted, '.' ) !== false || strpos( $quoted, ',' ) !== false ) {
+ $quoted = '"' . $quoted . '"';
+ }
+ return "$quoted <{$this->address}>";
+ } else {
+ return $this->address;
}
- return "$quoted <{$this->address}>";
- } else {
- return $this->address;
}
}
@@ -82,6 +84,13 @@ class UserMailer {
/**
* Send mail using a PEAR mailer
+ *
+ * @param $mailer
+ * @param $dest
+ * @param $headers
+ * @param $body
+ *
+ * @return Status
*/
protected static function sendWithPear( $mailer, $dest, $headers, $body ) {
$mailResult = $mailer->send( $dest, $headers, $body );
@@ -106,14 +115,15 @@ class UserMailer {
* @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
+ * @param $contentType String: optional custom Content-Type (default: text/plain; charset=UTF-8)
* @return Status object
*/
- public static function send( $to, $from, $subject, $body, $replyto = null, $contentType = null ) {
- global $wgSMTP, $wgOutputEncoding, $wgEnotifImpersonal;
+ public static function send( $to, $from, $subject, $body, $replyto = null, $contentType = 'text/plain; charset=UTF-8') {
+ global $wgSMTP, $wgEnotifImpersonal;
global $wgEnotifMaxRecips, $wgAdditionalMailParams;
if ( is_array( $to ) ) {
+ $emails = '';
// 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 ) {
@@ -126,13 +136,10 @@ class UserMailer {
}
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 ( function_exists( 'stream_resolve_include_path' ) ) {
+ $found = stream_resolve_include_path( 'Mail.php' );
+ } else {
+ $found = Fallback::stream_resolve_include_path( 'Mail.php' );
}
if ( !$found ) {
throw new MWException( 'PEAR mail package is not installed' );
@@ -140,17 +147,21 @@ class UserMailer {
require_once( 'Mail.php' );
$msgid = str_replace( " ", "_", microtime() );
- if ( function_exists( 'posix_getpid' ) )
+ if ( function_exists( 'posix_getpid' ) ) {
$msgid .= '.' . posix_getpid();
+ }
if ( is_array( $to ) ) {
$dest = array();
- foreach ( $to as $u )
+ foreach ( $to as $u ) {
$dest[] = $u->address;
- } else
+ }
+ } else {
$dest = $to->address;
+ }
$headers['From'] = $from->toString();
+ $headers['Return-Path'] = $from->toString();
if ( $wgEnotifImpersonal ) {
$headers['To'] = 'undisclosed-recipients:;';
@@ -162,13 +173,14 @@ class UserMailer {
if ( $replyto ) {
$headers['Reply-To'] = $replyto->toString();
}
- $headers['Subject'] = wfQuotedPrintable( $subject );
+ $headers['Subject'] = self::quotedPrintable( $subject );
$headers['Date'] = date( 'r' );
$headers['MIME-Version'] = '1.0';
$headers['Content-type'] = ( is_null( $contentType ) ?
- 'text/plain; charset=' . $wgOutputEncoding : $contentType );
+ 'text/plain; charset=UTF-8' : $contentType );
$headers['Content-transfer-encoding'] = '8bit';
- $headers['Message-ID'] = "<$msgid@" . $wgSMTP['IDHost'] . '>'; // FIXME
+ // @todo FIXME
+ $headers['Message-ID'] = "<$msgid@" . $wgSMTP['IDHost'] . '>';
$headers['X-Mailer'] = 'MediaWiki mailer';
wfSuppressWarnings();
@@ -193,9 +205,6 @@ class UserMailer {
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)
-
# Line endings need to be different on Unix and Windows due to
# the bug described at http://trac.wordpress.org/ticket/2603
if ( wfIsWindows() ) {
@@ -204,31 +213,32 @@ class UserMailer {
} else {
$endl = "\n";
}
- $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" .
- 'From: ' . $from->toString();
+
+ $headers = array(
+ "MIME-Version: 1.0",
+ "Content-type: $contentType",
+ "Content-Transfer-Encoding: 8bit",
+ "X-Mailer: MediaWiki mailer",
+ "From: " . $from->toString(),
+ );
if ( $replyto ) {
- $headers .= "{$endl}Reply-To: " . $replyto->toString();
+ $headers[] = "Reply-To: " . $replyto->toString();
}
+ $headers = implode( $endl, $headers );
+
wfDebug( "Sending mail via internal mail() function\n" );
self::$mErrorString = '';
$html_errors = ini_get( 'html_errors' );
ini_set( 'html_errors', '0' );
- set_error_handler( array( 'UserMailer', 'errorHandler' ) );
+ set_error_handler( 'UserMailer::errorHandler' );
- if ( is_array( $to ) ) {
- foreach ( $to as $recip ) {
- $sent = mail( $recip->toString(), wfQuotedPrintable( $subject ), $body, $headers, $wgAdditionalMailParams );
- }
- } else {
- $sent = mail( $to->toString(), wfQuotedPrintable( $subject ), $body, $headers, $wgAdditionalMailParams );
+ if ( !is_array( $to ) ) {
+ $to = array( $to );
+ }
+ foreach ( $to as $recip ) {
+ $sent = mail( $recip->toString(), self::quotedPrintable( $subject ), $body, $headers, $wgAdditionalMailParams );
}
restore_error_handler();
@@ -259,11 +269,41 @@ class UserMailer {
/**
* Converts a string into a valid RFC 822 "phrase", such as is used for the sender name
+ * @param $phrase string
+ * @return string
*/
public static function rfc822Phrase( $phrase ) {
$phrase = strtr( $phrase, array( "\r" => '', "\n" => '', '"' => '' ) );
return '"' . $phrase . '"';
}
+
+ /**
+ * Converts a string into quoted-printable format
+ * @since 1.17
+ */
+ public static function quotedPrintable( $string, $charset = '' ) {
+ # Probably incomplete; see RFC 2045
+ if( empty( $charset ) ) {
+ $charset = 'UTF-8';
+ }
+ $charset = strtoupper( $charset );
+ $charset = str_replace( 'ISO-8859', 'ISO8859', $charset ); // ?
+
+ $illegal = '\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\xff=';
+ $replace = $illegal . '\t ?_';
+ if( !preg_match( "/[$illegal]/", $string ) ) {
+ return $string;
+ }
+ $out = "=?$charset?Q?";
+ $out .= preg_replace_callback( "/([$replace])/",
+ array( __CLASS__, 'quotedPrintableCallback' ), $string );
+ $out .= '?=';
+ return $out;
+ }
+
+ protected static function quotedPrintableCallback( $matches ) {
+ return sprintf( "=%02X", ord( $matches[1] ) );
+ }
}
/**
@@ -307,8 +347,9 @@ class EmailNotification {
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();
@@ -360,7 +401,7 @@ class EmailNotification {
}
- /*
+ /**
* Immediate version of notifyOnPageChange().
*
* Send emails corresponding to the user $editor editing the page $title.
@@ -404,7 +445,9 @@ class EmailNotification {
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' ) ) {
+ } elseif ( $targetUser->getOption( 'enotifusertalkpages' ) &&
+ ( !$minorEdit || $targetUser->getOption( 'enotifminoredits' ) ) )
+ {
if ( $targetUser->isEmailConfirmed() ) {
wfDebug( __METHOD__ . ": sending talk page update notification\n" );
$this->compose( $targetUser );
@@ -466,7 +509,7 @@ class EmailNotification {
$keys = array();
if ( $this->oldid ) {
- $difflink = $this->title->getFullUrl( 'diff=0&oldid=' . $this->oldid );
+ $difflink = $this->title->getCanonicalUrl( 'diff=0&oldid=' . $this->oldid );
$keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastvisited', $difflink );
$keys['$OLDID'] = $this->oldid;
$keys['$CHANGEDORCREATED'] = wfMsgForContent( 'changed' );
@@ -478,22 +521,22 @@ class EmailNotification {
}
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=next" ) );
- }
+ $this->title->getCanonicalUrl( "oldid={$this->oldid}&diff=next" ) );
+ }
$body = strtr( $body, $keys );
$pagetitle = $this->title->getPrefixedText();
$keys['$PAGETITLE'] = $pagetitle;
- $keys['$PAGETITLE_URL'] = $this->title->getFullUrl();
+ $keys['$PAGETITLE_URL'] = $this->title->getCanonicalUrl();
$keys['$PAGEMINOREDIT'] = $medit;
$keys['$PAGESUMMARY'] = $summary;
- $keys['$UNWATCHURL'] = $this->title->getFullUrl( 'action=unwatch' );
+ $keys['$UNWATCHURL'] = $this->title->getCanonicalUrl( 'action=unwatch' );
$subject = strtr( $subject, $keys );
@@ -505,8 +548,8 @@ class EmailNotification {
$adminAddress = new MailAddress( $wgPasswordSender, $wgPasswordSenderName );
$editorAddress = new MailAddress( $editor );
if ( $wgEnotifRevealEditorAddress
- && ( $editor->getEmail() != '' )
- && $editor->getOption( 'enotifrevealaddr' ) ) {
+ && ( $editor->getEmail() != '' )
+ && $editor->getOption( 'enotifrevealaddr' ) ) {
if ( $wgEnotifFromEditor ) {
$from = $editorAddress;
} else {
@@ -518,7 +561,7 @@ class EmailNotification {
$replyto = new MailAddress( $wgNoReplyAddress );
}
- if ( $editor->isIP( $name ) ) {
+ if ( $editor->isAnon() ) {
# real anon (user:xxx.xxx.xxx.xxx)
$utext = wfMsgForContent( 'enotif_anon_editor', $name );
$subject = str_replace( '$PAGEEDITOR', $utext, $subject );
@@ -528,10 +571,10 @@ class EmailNotification {
$subject = str_replace( '$PAGEEDITOR', $name, $subject );
$keys['$PAGEEDITOR'] = $name;
$emailPage = SpecialPage::getSafeTitleFor( 'Emailuser', $name );
- $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getFullUrl();
+ $keys['$PAGEEDITOR_EMAIL'] = $emailPage->getCanonicalUrl();
}
$userPage = $editor->getUserPage();
- $keys['$PAGEEDITOR_WIKI'] = $userPage->getFullUrl();
+ $keys['$PAGEEDITOR_WIKI'] = $userPage->getCanonicalUrl();
$body = strtr( $body, $keys );
$body = wordwrap( $body, 72 );
@@ -618,28 +661,14 @@ class EmailNotification {
$body = str_replace(
array( '$WATCHINGUSERNAME',
- '$PAGEEDITDATE' ),
+ '$PAGEEDITDATE',
+ '$PAGEEDITTIME' ),
array( wfMsgForContent( 'enotif_impersonal_salutation' ),
- $wgContLang->timeanddate( $this->timestamp, true, false, false ) ),
+ $wgContLang->date( $this->timestamp, true, false, false ),
+ $wgContLang->time( $this->timestamp, true, false, false ) ),
$this->body );
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 ) {
- wfDeprecated( __FUNCTION__ );
- return UserMailer::send( $to, $from, $subject, $body, $replyto );
-}
-/**@}*/ \ No newline at end of file
diff --git a/includes/ViewCountUpdate.php b/includes/ViewCountUpdate.php
new file mode 100644
index 00000000..0642b630
--- /dev/null
+++ b/includes/ViewCountUpdate.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Update for the 'page_counter' field
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Update for the 'page_counter' field, when $wgDisableCounters is false.
+ *
+ * Depending on $wgHitcounterUpdateFreq, this will directly increment the
+ * 'page_counter' field or use the 'hitcounter' table and then collect the data
+ * from that table to update the 'page_counter' field in a batch operation.
+ */
+class ViewCountUpdate {
+ protected $id;
+
+ /**
+ * Constructor
+ *
+ * @param $id Integer: page ID to increment the view count
+ */
+ public function __construct( $id ) {
+ $this->id = intval( $id );
+ }
+
+ /**
+ * Run the update
+ */
+ public function doUpdate() {
+ global $wgHitcounterUpdateFreq;
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ if ( $wgHitcounterUpdateFreq <= 1 || $dbw->getType() == 'sqlite' ) {
+ $pageTable = $dbw->tableName( 'page' );
+ $dbw->query( "UPDATE $pageTable SET page_counter = page_counter + 1 WHERE page_id = {$this->id}" );
+ return;
+ }
+
+ # Not important enough to warrant an error page in case of failure
+ $oldignore = $dbw->ignoreErrors( true );
+
+ $dbw->insert( 'hitcounter', array( 'hc_id' => $this->id ), __METHOD__ );
+
+ $checkfreq = intval( $wgHitcounterUpdateFreq / 25 + 1 );
+ if ( rand() % $checkfreq == 0 && $dbw->lastErrno() == 0 ) {
+ $this->collect();
+ }
+
+ $dbw->ignoreErrors( $oldignore );
+ }
+
+ protected function collect() {
+ global $wgHitcounterUpdateFreq;
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $hitcounterTable = $dbw->tableName( 'hitcounter' );
+ $res = $dbw->query( "SELECT COUNT(*) as n FROM $hitcounterTable" );
+ $row = $dbw->fetchObject( $res );
+ $rown = intval( $row->n );
+
+ if ( $rown < $wgHitcounterUpdateFreq ) {
+ return;
+ }
+
+ wfProfileIn( __METHOD__ . '-collect' );
+ $old_user_abort = ignore_user_abort( true );
+
+ $dbw->lockTables( array(), array( 'hitcounter' ), __METHOD__, false );
+
+ $dbType = $dbw->getType();
+ $tabletype = $dbType == 'mysql' ? "ENGINE=HEAP " : '';
+ $acchitsTable = $dbw->tableName( 'acchits' );
+ $pageTable = $dbw->tableName( 'page' );
+
+ $dbw->query( "CREATE TEMPORARY TABLE $acchitsTable $tabletype AS " .
+ "SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable " .
+ 'GROUP BY hc_id', __METHOD__ );
+ $dbw->delete( 'hitcounter', '*', __METHOD__ );
+ $dbw->unlockTables( __METHOD__ );
+
+ if ( $dbType == 'mysql' ) {
+ $dbw->query( "UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n " .
+ 'WHERE page_id = hc_id', __METHOD__ );
+ } else {
+ $dbw->query( "UPDATE $pageTable SET page_counter=page_counter + hc_n " .
+ "FROM $acchitsTable WHERE page_id = hc_id", __METHOD__ );
+ }
+ $dbw->query( "DROP TABLE $acchitsTable", __METHOD__ );
+
+ ignore_user_abort( $old_user_abort );
+ wfProfileOut( __METHOD__ . '-collect' );
+ }
+}
diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php
index dd6e247b..031b2b48 100644
--- a/includes/WatchedItem.php
+++ b/includes/WatchedItem.php
@@ -133,6 +133,11 @@ class WatchedItem {
/**
* Handle duplicate entries. Backend for duplicateEntries().
+ *
+ * @param $ot Title
+ * @param $nt Title
+ *
+ * @return bool
*/
private static function doDuplicateEntries( $ot, $nt ) {
$oldnamespace = $ot->getNamespace();
diff --git a/includes/WatchlistEditor.php b/includes/WatchlistEditor.php
deleted file mode 100644
index 37673784..00000000
--- a/includes/WatchlistEditor.php
+++ /dev/null
@@ -1,528 +0,0 @@
-<?php
-
-/**
- * Provides the UI through which users can perform editing
- * operations on their watchlist
- *
- * @ingroup Watchlist
- * @author Rob Church <robchur@gmail.com>
- */
-class WatchlistEditor {
-
- /**
- * Editing modes
- */
- const EDIT_CLEAR = 1;
- const EDIT_RAW = 2;
- const EDIT_NORMAL = 3;
-
- /**
- * Main execution point
- *
- * @param $user User
- * @param $output OutputPage
- * @param $request WebRequest
- * @param $mode int
- */
- public function execute( $user, $output, $request, $mode ) {
- global $wgUser;
- if( wfReadOnly() ) {
- $output->readOnlyPage();
- return;
- }
- switch( $mode ) {
- case self::EDIT_CLEAR:
- // The "Clear" link scared people too much.
- // Pass on to the raw editor, from which it's very easy to clear.
- case self::EDIT_RAW:
- $output->setPageTitle( wfMsg( 'watchlistedit-raw-title' ) );
- if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) {
- $wanted = $this->extractTitles( $request->getText( 'titles' ) );
- $current = $this->getWatchlist( $user );
- if( count( $wanted ) > 0 ) {
- $toWatch = array_diff( $wanted, $current );
- $toUnwatch = array_diff( $current, $wanted );
- $this->watchTitles( $toWatch, $user );
- $this->unwatchTitles( $toUnwatch, $user );
- $user->invalidateCache();
- if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 )
- $output->addHTML( wfMsgExt( 'watchlistedit-raw-done', 'parse' ) );
- if( ( $count = count( $toWatch ) ) > 0 ) {
- $output->addHTML( wfMsgExt( 'watchlistedit-raw-added', 'parse', $count ) );
- $this->showTitles( $toWatch, $output, $wgUser->getSkin() );
- }
- if( ( $count = count( $toUnwatch ) ) > 0 ) {
- $output->addHTML( wfMsgExt( 'watchlistedit-raw-removed', 'parse', $count ) );
- $this->showTitles( $toUnwatch, $output, $wgUser->getSkin() );
- }
- } else {
- $this->clearWatchlist( $user );
- $user->invalidateCache();
- $output->addHTML( wfMsgExt( 'watchlistedit-raw-removed', 'parse', count( $current ) ) );
- $this->showTitles( $current, $output, $wgUser->getSkin() );
- }
- }
- $this->showRawForm( $output, $user );
- break;
- case self::EDIT_NORMAL:
- $output->setPageTitle( wfMsg( 'watchlistedit-normal-title' ) );
- if( $request->wasPosted() && $this->checkToken( $request, $wgUser ) ) {
- $titles = $this->extractTitles( $request->getArray( 'titles' ) );
- $this->unwatchTitles( $titles, $user );
- $user->invalidateCache();
- $output->addHTML( wfMsgExt( 'watchlistedit-normal-done', 'parse',
- $GLOBALS['wgLang']->formatNum( count( $titles ) ) ) );
- $this->showTitles( $titles, $output, $wgUser->getSkin() );
- }
- $this->showNormalForm( $output, $user );
- }
- }
-
- /**
- * Check the edit token from a form submission
- *
- * @param $request WebRequest
- * @param $user User
- * @return bool
- */
- private function checkToken( $request, $user ) {
- return $user->matchEditToken( $request->getVal( 'token' ), 'watchlistedit' );
- }
-
- /**
- * Extract a list of titles from a blob of text, returning
- * (prefixed) strings; unwatchable titles are ignored
- *
- * @param $list mixed
- * @return array
- */
- private function extractTitles( $list ) {
- $titles = array();
- if( !is_array( $list ) ) {
- $list = explode( "\n", trim( $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() ) {
- $titles[] = $title->getPrefixedText();
- }
- }
- }
- return array_unique( $titles );
- }
-
- /**
- * Print out a list of linked titles
- *
- * $titles can be an array of strings or Title objects; the former
- * is preferred, since Titles are very memory-heavy
- *
- * @param $titles An array of strings, or Title objects
- * @param $output OutputPage
- * @param $skin Skin
- */
- private function showTitles( $titles, $output, $skin ) {
- $talk = wfMsgHtml( 'talkpagelinktext' );
- // Do a batch existence check
- $batch = new LinkBatch();
- foreach( $titles as $title ) {
- if( !$title instanceof Title ) {
- $title = Title::newFromText( $title );
- }
- if( $title instanceof Title ) {
- $batch->addObj( $title );
- $batch->addObj( $title->getTalkPage() );
- }
- }
- $batch->execute();
- // Print out the list
- $output->addHTML( "<ul>\n" );
- foreach( $titles as $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" );
- }
- }
- $output->addHTML( "</ul>\n" );
- }
-
- /**
- * Count the number of titles on a user's watchlist, excluding talk pages
- *
- * @param $user User
- * @return int
- */
- private function countWatchlist( $user ) {
- $dbr = wfGetDB( DB_MASTER );
- $res = $dbr->select( 'watchlist', 'COUNT(*) AS count', array( 'wl_user' => $user->getId() ), __METHOD__ );
- $row = $dbr->fetchObject( $res );
- return ceil( $row->count / 2 ); // Paranoia
- }
-
- /**
- * Prepare a list of titles on a user's watchlist (excluding talk pages)
- * and return an array of (prefixed) strings
- *
- * @param $user User
- * @return array
- */
- private function getWatchlist( $user ) {
- $list = array();
- $dbr = wfGetDB( DB_MASTER );
- $res = $dbr->select(
- 'watchlist',
- '*',
- array(
- 'wl_user' => $user->getId(),
- ),
- __METHOD__
- );
- if( $res->numRows() > 0 ) {
- foreach ( $res as $row ) {
- $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
- if( $title instanceof Title && !$title->isTalkPage() )
- $list[] = $title->getPrefixedText();
- }
- $res->free();
- }
- return $list;
- }
-
- /**
- * Get a list of titles on a user's watchlist, excluding talk pages,
- * and return as a two-dimensional array with namespace, title and
- * redirect status
- *
- * @param $user User
- * @return array
- */
- private function getWatchlistInfo( $user ) {
- $titles = array();
- $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, 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();
- 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, $row->page_latest );
- } else {
- $cache->addBadLinkObj( $title );
- }
- // Ignore non-talk
- if( !$title->isTalkPage() ) {
- $titles[$row->wl_namespace][$row->wl_title] = $row->page_is_redirect;
- }
- }
- }
- }
- return $titles;
- }
-
- /**
- * Show a message indicating the number of items on the user's watchlist,
- * and return this count for additional checking
- *
- * @param $output OutputPage
- * @param $user User
- * @return int
- */
- private function showItemCount( $output, $user ) {
- if( ( $count = $this->countWatchlist( $user ) ) > 0 ) {
- $output->addHTML( wfMsgExt( 'watchlistedit-numitems', 'parse',
- $GLOBALS['wgLang']->formatNum( $count ) ) );
- } else {
- $output->addHTML( wfMsgExt( 'watchlistedit-noitems', 'parse' ) );
- }
- return $count;
- }
-
- /**
- * Remove all titles from a user's watchlist
- *
- * @param $user User
- */
- private function clearWatchlist( $user ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'watchlist', array( 'wl_user' => $user->getId() ), __METHOD__ );
- }
-
- /**
- * Add a list of titles to a user's watchlist
- *
- * $titles can be an array of strings or Title objects; the former
- * is preferred, since Titles are very memory-heavy
- *
- * @param $titles An array of strings, or Title objects
- * @param $user User
- */
- private function watchTitles( $titles, $user ) {
- $dbw = wfGetDB( DB_MASTER );
- $rows = array();
- foreach( $titles as $title ) {
- if( !$title instanceof Title ) {
- $title = Title::newFromText( $title );
- }
- if( $title instanceof Title ) {
- $rows[] = array(
- 'wl_user' => $user->getId(),
- 'wl_namespace' => ( $title->getNamespace() & ~1 ),
- 'wl_title' => $title->getDBkey(),
- 'wl_notificationtimestamp' => null,
- );
- $rows[] = array(
- 'wl_user' => $user->getId(),
- 'wl_namespace' => ( $title->getNamespace() | 1 ),
- 'wl_title' => $title->getDBkey(),
- 'wl_notificationtimestamp' => null,
- );
- }
- }
- $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' );
- }
-
- /**
- * Remove a list of titles from a user's watchlist
- *
- * $titles can be an array of strings or Title objects; the former
- * is preferred, since Titles are very memory-heavy
- *
- * @param $titles An array of strings, or Title objects
- * @param $user User
- */
- private function unwatchTitles( $titles, $user ) {
- $dbw = wfGetDB( DB_MASTER );
- foreach( $titles as $title ) {
- if( !$title instanceof Title ) {
- $title = Title::newFromText( $title );
- }
- if( $title instanceof Title ) {
- $dbw->delete(
- 'watchlist',
- array(
- 'wl_user' => $user->getId(),
- 'wl_namespace' => ( $title->getNamespace() & ~1 ),
- 'wl_title' => $title->getDBkey(),
- ),
- __METHOD__
- );
- $dbw->delete(
- 'watchlist',
- array(
- 'wl_user' => $user->getId(),
- 'wl_namespace' => ( $title->getNamespace() | 1 ),
- 'wl_title' => $title->getDBkey(),
- ),
- __METHOD__
- );
- $article = new Article($title);
- wfRunHooks('UnwatchArticleComplete',array(&$user,&$article));
- }
- }
- }
-
- /**
- * Show the standard watchlist editing form
- *
- * @param $output OutputPage
- * @param $user User
- */
- private function showNormalForm( $output, $user ) {
- global $wgUser;
- $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 .= 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() );
- $form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-normal-submit' ) ) . '</p>';
- $form .= '</fieldset></form>';
- $output->addHTML( $form );
- }
- }
-
- /**
- * Build the part of the standard watchlist editing form with the actual
- * title selection checkboxes and stuff. Also generates a table of
- * contents if there's more than one heading.
- *
- * @param $user User
- * @param $skin Skin (really, Linker)
- */
- private function buildRemoveList( $user, $skin ) {
- $list = "";
- $toc = $skin->tocIndent();
- $tocLength = 0;
- foreach( $this->getWatchlistInfo( $user ) as $namespace => $pages ) {
- $tocLength++;
- $heading = htmlspecialchars( $this->getNamespaceHeading( $namespace ) );
- $anchor = "editwatchlist-ns" . $namespace;
-
- $list .= $skin->makeHeadLine( 2, ">", $anchor, $heading, "" );
- $toc .= $skin->tocLine( $anchor, $heading, $tocLength, 1 ) . $skin->tocLineEnd();
-
- $list .= "<ul>\n";
- foreach( $pages as $dbkey => $redirect ) {
- $title = Title::makeTitleSafe( $namespace, $dbkey );
- $list .= $this->buildRemoveLine( $title, $redirect, $skin );
- }
- $list .= "</ul>\n";
- }
- // ISSUE: omit the TOC if the total number of titles is low?
- if( $tocLength > 1 ) {
- $list = $skin->tocList( $toc ) . $list;
- }
- return $list;
- }
-
- /**
- * Get the correct "heading" for a namespace
- *
- * @param $namespace int
- * @return string
- */
- private function getNamespaceHeading( $namespace ) {
- return $namespace == NS_MAIN
- ? wfMsgHtml( 'blanknamespace' )
- : htmlspecialchars( $GLOBALS['wgContLang']->getFormattedNsText( $namespace ) );
- }
-
- /**
- * Build a single list item containing a check box selecting a title
- * and a link to that title, with various additional bits
- *
- * @param $title Title
- * @param $redirect bool
- * @param $skin Skin
- * @return string
- */
- private function buildRemoveLine( $title, $redirect, $skin ) {
- global $wgLang;
-
- $link = $skin->link( $title );
- if( $redirect ) {
- $link = '<span class="watchlistredir">' . $link . '</span>';
- }
- $tools[] = $skin->link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) );
- if( $title->exists() ) {
- $tools[] = $skin->link(
- $title,
- wfMsgHtml( 'history_short' ),
- array(),
- array( 'action' => 'history' ),
- array( 'known', 'noclasses' )
- );
- }
- if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
- $tools[] = $skin->link(
- SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
- wfMsgHtml( 'contributions' ),
- array(),
- array(),
- 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
- *
- * @param $output OutputPage
- * @param $user User
- */
- public function showRawForm( $output, $user ) {
- global $wgUser;
- $this->showItemCount( $output, $user );
- $self = SpecialPage::getTitleFor( 'Watchlist' );
- $form = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $self->getLocalUrl( array( 'action' => 'raw' ) ) ) );
- $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' );
- $form .= "<br />\n";
- $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 ) {
- $form .= htmlspecialchars( $title ) . "\n";
- }
- $form .= '</textarea>';
- $form .= '<p>' . Xml::submitButton( wfMsg( 'watchlistedit-raw-submit' ) ) . '</p>';
- $form .= '</fieldset></form>';
- $output->addHTML( $form );
- }
-
- /**
- * Determine whether we are editing the watchlist, and if so, what
- * kind of editing operation
- *
- * @param $request WebRequest
- * @param $par mixed
- * @return int
- */
- public static function getMode( $request, $par ) {
- $mode = strtolower( $request->getVal( 'action', $par ) );
- switch( $mode ) {
- case 'clear':
- return self::EDIT_CLEAR;
- case 'raw':
- return self::EDIT_RAW;
- case 'edit':
- return self::EDIT_NORMAL;
- default:
- return false;
- }
- }
-
- /**
- * Build a set of links for convenient navigation
- * between watchlist viewing and editing modes
- *
- * @param $skin Skin to use
- * @return string
- */
- public static function buildTools( $skin ) {
- global $wgLang;
-
- $tools = array();
- $modes = array( 'view' => false, 'edit' => 'edit', 'raw' => 'raw' );
- foreach( $modes as $mode => $subpage ) {
- // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
- $tools[] = $skin->linkKnown(
- SpecialPage::getTitleFor( 'Watchlist', $subpage ),
- wfMsgHtml( "watchlisttools-{$mode}" )
- );
- }
- 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 940b693f..44fe6610 100644
--- a/includes/WebRequest.php
+++ b/includes/WebRequest.php
@@ -45,7 +45,7 @@ class WebRequest {
private $response;
public function __construct() {
- /// @todo Fixme: this preemptive de-quoting can interfere with other web libraries
+ /// @todo FIXME: This preemptive de-quoting can interfere with other web libraries
/// and increases our memory footprint. It would be cleaner to do on
/// demand; but currently we have no wrapper for $_SERVER etc.
$this->checkMagicQuotes();
@@ -56,6 +56,119 @@ class WebRequest {
}
/**
+ * Extract the PATH_INFO variable even when it isn't a reasonable
+ * value. On some large webhosts, PATH_INFO includes the script
+ * path as well as everything after it.
+ *
+ * @param $want string: If this is not 'all', then the function
+ * will return an empty array if it determines that the URL is
+ * inside a rewrite path.
+ *
+ * @return Array: 'title' key is the title of the article.
+ */
+ static public function getPathInfo( $want = 'all' ) {
+ // 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.
+ // So we will use REQUEST_URI if possible.
+ $matches = array();
+ if ( !empty( $_SERVER['REQUEST_URI'] ) ) {
+ // Slurp out the path portion to examine...
+ $url = $_SERVER['REQUEST_URI'];
+ if ( !preg_match( '!^https?://!', $url ) ) {
+ $url = 'http://unused' . $url;
+ }
+ $a = parse_url( $url );
+ if( $a ) {
+ $path = isset( $a['path'] ) ? $a['path'] : '';
+
+ global $wgScript;
+ if( $path == $wgScript && $want !== 'all' ) {
+ // Script inside a rewrite path?
+ // Abort to keep from breaking...
+ return $matches;
+ }
+ // Raw PATH_INFO style
+ $matches = self::extractTitle( $path, "$wgScript/$1" );
+
+ global $wgArticlePath;
+ if( !$matches && $wgArticlePath ) {
+ $matches = self::extractTitle( $path, $wgArticlePath );
+ }
+
+ global $wgActionPaths;
+ if( !$matches && $wgActionPaths ) {
+ $matches = self::extractTitle( $path, $wgActionPaths, 'action' );
+ }
+
+ global $wgVariantArticlePath, $wgContLang;
+ if( !$matches && $wgVariantArticlePath ) {
+ $variantPaths = array();
+ foreach( $wgContLang->getVariants() as $variant ) {
+ $variantPaths[$variant] =
+ str_replace( '$2', $variant, $wgVariantArticlePath );
+ }
+ $matches = self::extractTitle( $path, $variantPaths, 'variant' );
+ }
+ }
+ } elseif ( isset( $_SERVER['ORIG_PATH_INFO'] ) && $_SERVER['ORIG_PATH_INFO'] != '' ) {
+ // Mangled PATH_INFO
+ // http://bugs.php.net/bug.php?id=31892
+ // Also reported when ini_get('cgi.fix_pathinfo')==false
+ $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
+
+ } elseif ( isset( $_SERVER['PATH_INFO'] ) && ($_SERVER['PATH_INFO'] != '') ) {
+ // Regular old PATH_INFO yay
+ $matches['title'] = substr( $_SERVER['PATH_INFO'], 1 );
+ }
+
+ return $matches;
+ }
+
+ /**
+ * Work out an appropriate URL prefix containing scheme and host, based on
+ * information detected from $_SERVER
+ *
+ * @return string
+ */
+ public static function detectServer() {
+ list( $proto, $stdPort ) = self::detectProtocolAndStdPort();
+
+ $varNames = array( 'HTTP_HOST', 'SERVER_NAME', 'HOSTNAME', 'SERVER_ADDR' );
+ $host = 'localhost';
+ $port = $stdPort;
+ foreach ( $varNames as $varName ) {
+ if ( !isset( $_SERVER[$varName] ) ) {
+ continue;
+ }
+ $parts = IP::splitHostAndPort( $_SERVER[$varName] );
+ if ( !$parts ) {
+ // Invalid, do not use
+ continue;
+ }
+ $host = $parts[0];
+ if ( $parts[1] === false ) {
+ if ( isset( $_SERVER['SERVER_PORT'] ) ) {
+ $port = $_SERVER['SERVER_PORT'];
+ } // else leave it as $stdPort
+ } else {
+ $port = $parts[1];
+ }
+ break;
+ }
+
+ return $proto . '://' . IP::combineHostAndPort( $host, $port, $stdPort );
+ }
+
+ public static function detectProtocolAndStdPort() {
+ return ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) ? array( 'https', 443 ) : array( 'http', 80 );
+ }
+
+ public static function detectProtocol() {
+ list( $proto, $stdPort ) = self::detectProtocolAndStdPort();
+ return $proto;
+ }
+
+ /**
* Check for title, action, and/or variant data in the URL
* and interpolate it into the GET variables.
* This should only be run after $wgContLang is available,
@@ -65,66 +178,13 @@ class WebRequest {
public function interpolateTitle() {
global $wgUsePathInfo;
- // bug 16019: title interpolation on API queries is useless and possible harmful
+ // bug 16019: title interpolation on API queries is useless and sometimes 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.
- // So we will use REQUEST_URI if possible.
- $matches = array();
-
- if ( !empty( $_SERVER['REQUEST_URI'] ) ) {
- // Slurp out the path portion to examine...
- $url = $_SERVER['REQUEST_URI'];
- if ( !preg_match( '!^https?://!', $url ) ) {
- $url = 'http://unused' . $url;
- }
- $a = parse_url( $url );
- if( $a ) {
- $path = isset( $a['path'] ) ? $a['path'] : '';
-
- global $wgScript;
- if( $path == $wgScript ) {
- // Script inside a rewrite path?
- // Abort to keep from breaking...
- return;
- }
- // Raw PATH_INFO style
- $matches = $this->extractTitle( $path, "$wgScript/$1" );
-
- global $wgArticlePath;
- if( !$matches && $wgArticlePath ) {
- $matches = $this->extractTitle( $path, $wgArticlePath );
- }
-
- global $wgActionPaths;
- if( !$matches && $wgActionPaths ) {
- $matches = $this->extractTitle( $path, $wgActionPaths, 'action' );
- }
-
- global $wgVariantArticlePath, $wgContLang;
- if( !$matches && $wgVariantArticlePath ) {
- $variantPaths = array();
- foreach( $wgContLang->getVariants() as $variant ) {
- $variantPaths[$variant] =
- str_replace( '$2', $variant, $wgVariantArticlePath );
- }
- $matches = $this->extractTitle( $path, $variantPaths, 'variant' );
- }
- }
- } elseif ( isset( $_SERVER['ORIG_PATH_INFO'] ) && $_SERVER['ORIG_PATH_INFO'] != '' ) {
- // Mangled PATH_INFO
- // http://bugs.php.net/bug.php?id=31892
- // Also reported when ini_get('cgi.fix_pathinfo')==false
- $matches['title'] = substr( $_SERVER['ORIG_PATH_INFO'], 1 );
-
- } elseif ( isset( $_SERVER['PATH_INFO'] ) && ($_SERVER['PATH_INFO'] != '') ) {
- // Regular old PATH_INFO yay
- $matches['title'] = substr( $_SERVER['PATH_INFO'], 1 );
- }
+ $matches = self::getPathInfo( 'title' );
foreach( $matches as $key => $val) {
$this->data[$key] = $_GET[$key] = $_REQUEST[$key] = $val;
}
@@ -141,7 +201,7 @@ class WebRequest {
* passed on as the value of this URL parameter
* @return array of URL variables to interpolate; empty if no match
*/
- private function extractTitle( $path, $bases, $key=false ) {
+ private static function extractTitle( $path, $bases, $key = false ) {
foreach( (array)$bases as $keyValue => $base ) {
// Find the part after $wgArticlePath
$base = str_replace( '$1', '', $base );
@@ -211,7 +271,7 @@ class WebRequest {
}
} else {
global $wgContLang;
- $data = $wgContLang->normalize( $data );
+ $data = isset( $wgContLang ) ? $wgContLang->normalize( $data ) : UtfNormal::cleanUp( $data );
}
return $data;
}
@@ -269,7 +329,7 @@ class WebRequest {
}
/**
- * Set an aribtrary value into our get/post data.
+ * Set an arbitrary value into our get/post data.
*
* @param $key String: key name to use
* @param $value Mixed: value to set
@@ -357,7 +417,7 @@ class WebRequest {
public function getBool( $name, $default = 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
@@ -409,6 +469,8 @@ class WebRequest {
* Extracts the given named values into an array.
* If no arguments are given, returns all input values.
* No transformation is performed on the values.
+ *
+ * @return array
*/
public function getValues() {
$names = func_get_args();
@@ -427,6 +489,26 @@ class WebRequest {
}
/**
+ * Returns the names of all input values excluding those in $exclude.
+ *
+ * @param $exclude Array
+ * @return array
+ */
+ public function getValueNames( $exclude = array() ) {
+ return array_diff( array_keys( $this->getValues() ), $exclude );
+ }
+
+ /**
+ * Get the values passed in the query string.
+ * No transformation is performed on the values.
+ *
+ * @return Array
+ */
+ public function getQueryValues() {
+ return $_GET;
+ }
+
+ /**
* Returns true if the present request was reached by a POST operation,
* false otherwise (GET, HEAD, or command-line).
*
@@ -471,15 +553,18 @@ class WebRequest {
}
/**
- * Return the path portion of the request URI.
+ * Return the path and query string portion of the request URI.
+ * This will be suitable for use as a relative link in HTML output.
*
* @return String
*/
public function getRequestURL() {
- if( isset( $_SERVER['REQUEST_URI']) && strlen($_SERVER['REQUEST_URI']) ) {
+ if( isset( $_SERVER['REQUEST_URI'] ) && strlen( $_SERVER['REQUEST_URI'] ) ) {
$base = $_SERVER['REQUEST_URI'];
- } elseif( isset( $_SERVER['SCRIPT_NAME'] ) ) {
+ } elseif ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) && strlen( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
// Probably IIS; doesn't set REQUEST_URI
+ $base = $_SERVER['HTTP_X_ORIGINAL_URL'];
+ } elseif( isset( $_SERVER['SCRIPT_NAME'] ) ) {
$base = $_SERVER['SCRIPT_NAME'];
if( isset( $_SERVER['QUERY_STRING'] ) && $_SERVER['QUERY_STRING'] != '' ) {
$base .= '?' . $_SERVER['QUERY_STRING'];
@@ -487,8 +572,8 @@ class WebRequest {
} else {
// This shouldn't happen!
throw new MWException( "Web server doesn't provide either " .
- "REQUEST_URI or SCRIPT_NAME. Report details of your " .
- "web server configuration to http://bugzilla.wikimedia.org/" );
+ "REQUEST_URI, HTTP_X_ORIGINAL_URL or SCRIPT_NAME. Report details " .
+ "of your web server configuration to http://bugzilla.wikimedia.org/" );
}
// User-agents should not send a fragment with the URI, but
// if they do, and the web server passes it on to us, we
@@ -498,7 +583,7 @@ class WebRequest {
if( $hash !== false ) {
$base = substr( $base, 0, $hash );
}
- if( $base{0} == '/' ) {
+ if( $base[0] == '/' ) {
return $base;
} else {
// We may get paths with a host prepended; strip it.
@@ -507,13 +592,17 @@ class WebRequest {
}
/**
- * Return the request URI with the canonical service and hostname.
+ * Return the request URI with the canonical service and hostname, path,
+ * and query string. This will be suitable for use as an absolute link
+ * in HTML or other output.
+ *
+ * If $wgServer is protocol-relative, this will return a fully
+ * qualified URL with the protocol that was used for this request.
*
* @return String
*/
public function getFullRequestURL() {
- global $wgServer;
- return $wgServer . $this->getRequestURL();
+ return wfExpandUrl( $this->getRequestURL(), PROTO_CURRENT );
}
/**
@@ -523,23 +612,7 @@ class WebRequest {
* @return String
*/
public function appendQuery( $query ) {
- global $wgTitle;
- $basequery = '';
- foreach( $_GET 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 );
+ return $this->appendQueryArray( wfCgiToArray( $query ) );
}
/**
@@ -552,6 +625,12 @@ class WebRequest {
return htmlspecialchars( $this->appendQuery( $query ) );
}
+ /**
+ * @param $key
+ * @param $value
+ * @param $onlyquery bool
+ * @return String
+ */
public function appendQueryValue( $key, $value, $onlyquery = false ) {
return $this->appendQueryArray( array( $key => $value ), $onlyquery );
}
@@ -566,7 +645,7 @@ class WebRequest {
*/
public function appendQueryArray( $array, $onlyquery = false ) {
global $wgTitle;
- $newquery = $_GET;
+ $newquery = $this->getQueryValues();
unset( $newquery['title'] );
$newquery = array_merge( $newquery, $array );
$query = wfArrayToCGI( $newquery );
@@ -621,7 +700,7 @@ class WebRequest {
/**
* Return the size of the upload, or 0.
*
- * @deprecated
+ * @deprecated since 1.17
* @param $key String:
* @return integer
*/
@@ -660,7 +739,7 @@ class WebRequest {
/**
* Return a WebRequestUpload object corresponding to the key
*
- * @param @key string
+ * @param $key string
* @return WebRequestUpload
*/
public function getUpload( $key ) {
@@ -683,32 +762,52 @@ class WebRequest {
}
/**
+ * Initialise the header list
+ */
+ private function initHeaders() {
+ if ( count( $this->headers ) ) {
+ return;
+ }
+
+ if ( function_exists( 'apache_request_headers' ) ) {
+ foreach ( apache_request_headers() as $tempName => $tempValue ) {
+ $this->headers[ strtoupper( $tempName ) ] = $tempValue;
+ }
+ } else {
+ foreach ( $_SERVER as $name => $value ) {
+ if ( substr( $name, 0, 5 ) === 'HTTP_' ) {
+ $name = str_replace( '_', '-', substr( $name, 5 ) );
+ $this->headers[$name] = $value;
+ } elseif ( $name === 'CONTENT_LENGTH' ) {
+ $this->headers['CONTENT-LENGTH'] = $value;
+ }
+ }
+ }
+ }
+
+ /**
+ * Get an array containing all request headers
+ *
+ * @return Array mapping header name to its value
+ */
+ public function getAllHeaders() {
+ $this->initHeaders();
+ return $this->headers;
+ }
+
+ /**
* Get a request header, or false if it isn't set
* @param $name String: case-insensitive header name
+ *
+ * @return string|false
*/
public function getHeader( $name ) {
+ $this->initHeaders();
$name = strtoupper( $name );
- if ( function_exists( 'apache_request_headers' ) ) {
- if ( !$this->headers ) {
- foreach ( apache_request_headers() as $tempName => $tempValue ) {
- $this->headers[ strtoupper( $tempName ) ] = $tempValue;
- }
- }
- if ( isset( $this->headers[$name] ) ) {
- return $this->headers[$name];
- } else {
- return false;
- }
+ if ( isset( $this->headers[$name] ) ) {
+ return $this->headers[$name];
} 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 {
- return false;
- }
+ return false;
}
}
@@ -736,10 +835,13 @@ class WebRequest {
}
/**
- * Check if Internet Explorer will detect an incorrect cache extension in
+ * 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.
+ *
+ * @param $extWhitelist array
+ * @return bool
*/
public function checkUrlExtension( $extWhitelist = array() ) {
global $wgScriptExtension;
@@ -755,15 +857,18 @@ class WebRequest {
}
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
+ * Attempt to redirect to a URL with a QUERY_STRING that's not dangerous in
* IE 6. Returns true if it was successful, false otherwise.
+ *
+ * @param $url string
+ * @return bool
*/
protected function doSecurityRedirect( $url ) {
header( 'Location: ' . $url );
@@ -777,11 +882,11 @@ class WebRequest {
<body>
<h1>Security redirect</h1>
<p>
-We can't serve non-HTML content from the URL you have requested, because
+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
+<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>
@@ -806,8 +911,10 @@ HTML;
* Also checks for anything that looks like a file extension at the end of
* QUERY_STRING, since IE 6 and earlier will use this to get the file type
* if there was no dot before the question mark (bug 28235).
+ *
+ * @deprecated Use checkUrlExtension().
*/
- public function isPathInfoBad() {
+ public function isPathInfoBad( $extWhitelist = array() ) {
global $wgScriptExtension;
$extWhitelist[] = ltrim( $wgScriptExtension, '.' );
return IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist );
@@ -844,7 +951,7 @@ HTML;
foreach ( $langs as $lang => $val ) {
if ( $val === '' ) {
$langs[$lang] = 1;
- } else if ( $val == 0 ) {
+ } elseif ( $val == 0 ) {
unset($langs[$lang]);
}
}
@@ -1008,6 +1115,14 @@ class FauxRequest extends WebRequest {
return $this->data;
}
+ public function getQueryValues() {
+ if ( $this->wasPosted ) {
+ return array();
+ } else {
+ return $this->data;
+ }
+ }
+
public function wasPosted() {
return $this->wasPosted;
}
@@ -1020,28 +1135,6 @@ class FauxRequest extends WebRequest {
$this->notImplemented( __METHOD__ );
}
- public function appendQuery( $query ) {
- 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 ) {
return isset( $this->headers[$name] ) ? $this->headers[$name] : false;
}
@@ -1059,6 +1152,10 @@ class FauxRequest extends WebRequest {
$this->session[$key] = $data;
}
+ public function getSessionArray() {
+ return $this->session;
+ }
+
public function isPathInfoBad( $extWhitelist = array() ) {
return false;
}
diff --git a/includes/WebResponse.php b/includes/WebResponse.php
index 2b1ec04c..1101d75d 100644
--- a/includes/WebResponse.php
+++ b/includes/WebResponse.php
@@ -2,6 +2,21 @@
/**
* Classes used to send headers and cookies back to the user
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
*/
@@ -17,78 +32,121 @@ class WebResponse {
* header()
* @param $string String: header to output
* @param $replace Bool: replace current similar header
+ * @param $http_response_code null|int Forces the HTTP response code to the specified value.
*/
- public function header($string, $replace=true) {
- header($string,$replace);
+ public function header( $string, $replace = true, $http_response_code = null ) {
+ header( $string, $replace, $http_response_code );
}
- /** Set the browser cookie
+ /**
+ * Set the browser cookie
* @param $name String: name of cookie
* @param $value String: value to give cookie
* @param $expire Int: number of seconds til cookie expires
+ * @param $prefix String: Prefix to use, if not $wgCookiePrefix (use '' for no prefix)
+ * @param @domain String: Cookie domain to use, if not $wgCookieDomain
*/
- public function setcookie( $name, $value, $expire = 0 ) {
+ public function setcookie( $name, $value, $expire = 0, $prefix = null, $domain = null ) {
global $wgCookiePath, $wgCookiePrefix, $wgCookieDomain;
global $wgCookieSecure,$wgCookieExpiration, $wgCookieHttpOnly;
if ( $expire == 0 ) {
$expire = time() + $wgCookieExpiration;
}
- $httpOnlySafe = wfHttpOnlySafe();
+ if( $prefix === null ) {
+ $prefix = $wgCookiePrefix;
+ }
+ if( $domain === null ) {
+ $domain = $wgCookieDomain;
+ }
+ $httpOnlySafe = wfHttpOnlySafe() && $wgCookieHttpOnly;
wfDebugLog( 'cookie',
'setcookie: "' . implode( '", "',
array(
- $wgCookiePrefix . $name,
+ $prefix . $name,
$value,
$expire,
$wgCookiePath,
- $wgCookieDomain,
+ $domain,
$wgCookieSecure,
- $httpOnlySafe && $wgCookieHttpOnly ) ) . '"' );
- if( $httpOnlySafe && isset( $wgCookieHttpOnly ) ) {
- setcookie( $wgCookiePrefix . $name,
- $value,
- $expire,
- $wgCookiePath,
- $wgCookieDomain,
- $wgCookieSecure,
- $wgCookieHttpOnly );
- } else {
- // setcookie() fails on PHP 5.1 if you give it future-compat paramters.
- // stab stab!
- setcookie( $wgCookiePrefix . $name,
- $value,
- $expire,
- $wgCookiePath,
- $wgCookieDomain,
- $wgCookieSecure );
- }
+ $httpOnlySafe ) ) . '"' );
+ setcookie( $prefix . $name,
+ $value,
+ $expire,
+ $wgCookiePath,
+ $domain,
+ $wgCookieSecure,
+ $httpOnlySafe );
}
}
-
+/**
+ * @ingroup HTTP
+ */
class FauxResponse extends WebResponse {
private $headers;
private $cookies;
+ private $code;
+
+ /**
+ * Stores a HTTP header
+ * @param $string String: header to output
+ * @param $replace Bool: replace current similar header
+ * @param $http_response_code null|int Forces the HTTP response code to the specified value.
+ */
+ public function header( $string, $replace = true, $http_response_code = null ) {
+ if ( substr( $string, 0, 5 ) == 'HTTP/' ) {
+ $parts = explode( ' ', $string, 3 );
+ $this->code = intval( $parts[1] );
+ } else {
+ list( $key, $val ) = array_map( 'trim', explode( ":", $string, 2 ) );
- public function header($string, $replace=true) {
- list($key, $val) = explode(":", $string, 2);
+ if( $replace || !isset( $this->headers[$key] ) ) {
+ $this->headers[$key] = $val;
+ }
+ }
- if($replace || !isset($this->headers[$key])) {
- $this->headers[$key] = $val;
+ if ( $http_response_code !== null ) {
+ $this->code = intval( $http_response_code );
}
}
- public function getheader($key) {
- return $this->headers[$key];
+ /**
+ * @param $key string
+ * @return string
+ */
+ public function getheader( $key ) {
+ if ( isset( $this->headers[$key] ) ) {
+ return $this->headers[$key];
+ }
+ return null;
+ }
+
+ /**
+ * Get the HTTP response code, null if not set
+ *
+ * @return Int or null
+ */
+ public function getStatusCode() {
+ return $this->code;
}
- public function setcookie( $name, $value, $expire = 0 ) {
+ /**
+ * @param $name String: name of cookie
+ * @param $value String: value to give cookie
+ * @param $expire Int: number of seconds til cookie expires
+ */
+ public function setcookie( $name, $value, $expire = 0, $prefix = null, $domain = null ) {
$this->cookies[$name] = $value;
}
+ /**
+ * @param $name string
+ * @return string
+ */
public function getcookie( $name ) {
- if ( isset($this->cookies[$name]) ) {
+ if ( isset( $this->cookies[$name] ) ) {
return $this->cookies[$name];
}
+ return null;
}
-} \ No newline at end of file
+}
diff --git a/includes/WebStart.php b/includes/WebStart.php
index b5140718..6cfb4722 100644
--- a/includes/WebStart.php
+++ b/includes/WebStart.php
@@ -5,6 +5,21 @@
* configuration, and optionally loads Setup.php depending on whether
* MW_NO_SETUP is defined.
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
*/
@@ -42,6 +57,11 @@ if ( ini_get( 'register_globals' ) ) {
}
}
+# bug 15461: Make IE8 turn off content sniffing. Everbody else should ignore this
+# We're adding it here so that it's *always* set, even for alternate entry
+# points and when $wgOut gets disabled or overridden.
+header( 'X-Content-Type-Options: nosniff' );
+
$wgRequestTime = microtime(true);
# getrusage() does not exist on the Microsoft Windows platforms, catching this
if ( function_exists ( 'getrusage' ) ) {
@@ -67,45 +87,41 @@ if ( $IP === false ) {
$IP = realpath( '.' );
}
-
-# Start profiler
-if( file_exists("$IP/StartProfiler.php") ) {
- require_once( "$IP/StartProfiler.php" );
+if ( isset( $_SERVER['MW_COMPILED'] ) ) {
+ define( 'MW_COMPILED', 1 );
} else {
- require_once( "$IP/includes/ProfilerStub.php" );
+ # Get MWInit class
+ require_once( "$IP/includes/Init.php" );
+
+ # Start the autoloader, so that extensions can derive classes from core files
+ require_once( "$IP/includes/AutoLoader.php" );
+
+ # Load the profiler
+ require_once( "$IP/includes/profiler/Profiler.php" );
+
+ # Load up some global defines.
+ require_once( "$IP/includes/Defines.php" );
}
-wfProfileIn( 'WebStart.php-conf' );
-# Load up some global defines.
-require_once( "$IP/includes/Defines.php" );
-
-# Check for PHP 5
-if ( !function_exists( 'version_compare' )
- || version_compare( phpversion(), '5.0.0' ) < 0
-) {
- define( 'MW_PHP4', '1' );
- require( "$IP/includes/DefaultSettings.php" );
- require( "$IP/includes/templates/PHP4.php" );
- exit;
+# Start the profiler
+$wgProfiler = array();
+if ( file_exists( "$IP/StartProfiler.php" ) ) {
+ require( "$IP/StartProfiler.php" );
}
-# Start the autoloader, so that extensions can derive classes from core files
-require_once( "$IP/includes/AutoLoader.php" );
+wfProfileIn( 'WebStart.php-conf' );
+
# Load default settings
-require_once( "$IP/includes/DefaultSettings.php" );
+require_once( MWInit::compiledPath( "includes/DefaultSettings.php" ) );
if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
# Use a callback function to configure MediaWiki
- $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 );
+ MWFunction::call( MW_CONFIG_CALLBACK );
} else {
- if ( !defined('MW_CONFIG_FILE') )
- define('MW_CONFIG_FILE', "$IP/LocalSettings.php");
-
+ if ( !defined( 'MW_CONFIG_FILE' ) ) {
+ define('MW_CONFIG_FILE', MWInit::interpretedPath( '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
@@ -119,7 +135,7 @@ if ( defined( 'MW_CONFIG_CALLBACK' ) ) {
}
if ( $wgEnableSelenium ) {
- require_once( "$IP/includes/SeleniumWebSettings.php" );
+ require_once( MWInit::compiledPath( "includes/SeleniumWebSettings.php" ) );
}
wfProfileOut( 'WebStart.php-conf' );
@@ -130,12 +146,14 @@ wfProfileIn( 'WebStart.php-ob_start' );
# 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" );
+ if ( !defined( 'MW_COMPILED' ) ) {
+ require_once( "$IP/includes/OutputHandler.php" );
+ }
ob_start( 'wfOutputHandler' );
}
wfProfileOut( 'WebStart.php-ob_start' );
if ( !defined( 'MW_NO_SETUP' ) ) {
- require_once( "$IP/includes/Setup.php" );
+ require_once( MWInit::compiledPath( "includes/Setup.php" ) );
}
diff --git a/includes/Wiki.php b/includes/Wiki.php
index 4c3af0f7..173fcd94 100644
--- a/includes/Wiki.php
+++ b/includes/Wiki.php
@@ -1,241 +1,206 @@
<?php
/**
- * MediaWiki is the to-be base class for this whole project
+ * Helper class for the index.php entry point.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * The MediaWiki class is the helper class for the index.php entry point.
*
* @internal documentation reviewed 15 Mar 2010
*/
class MediaWiki {
- var $params = array();
-
- /** Constructor */
- function __construct() {}
/**
- * Stores key/value pairs to circumvent global variables
- * Note that keys are case-insensitive!
- *
- * @param $key String: key to store
- * @param $value Mixed: value to put for the key
+ * TODO: fold $output, etc, into this
+ * @var IContextSource
*/
- function setVal( $key, &$value ) {
- $key = strtolower( $key );
- $this->params[$key] =& $value;
- }
+ private $context;
- /**
- * Retrieves key/value pairs to circumvent global variables
- * Note that keys are case-insensitive!
- *
- * @param $key String: key to get
- * @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 );
- if( isset( $this->params[$key] ) ) {
- return $this->params[$key];
- }
- return $default;
+ public function request( WebRequest $x = null ){
+ $old = $this->context->getRequest();
+ $this->context->setRequest( $x );
+ return $old;
}
- /**
- * Initialization of ... everything
- * Performs the request too
- *
- * @param $title Title ($wgTitle)
- * @param $article Article
- * @param $output OutputPage
- * @param $user User
- * @param $request WebRequest
- */
- 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 ) ) {
- wfProfileOut( __METHOD__ );
- return;
- }
- // Call handleSpecialCases() to deal with all special requests...
- if( !$this->handleSpecialCases( $title, $output, $request ) ) {
- // ...otherwise treat it as an article view. The article
- // may be a redirect to another article or URL.
- $new_article = $this->initializeArticle( $title, $output, $request );
- if( is_object( $new_article ) ) {
- $article = $new_article;
- $this->performAction( $output, $article, $title, $user, $request );
- } elseif( is_string( $new_article ) ) {
- $output->redirect( $new_article );
- } else {
- wfProfileOut( __METHOD__ );
- throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle() returned neither an object nor a URL" );
- }
- }
- wfProfileOut( __METHOD__ );
+ public function output( OutputPage $x = null ){
+ $old = $this->context->getOutput();
+ $this->context->setOutput( $x );
+ return $old;
}
- /**
- * Check if the maximum lag of database slaves is higher that $maxLag, and
- * if it's the case, output an error message
- *
- * @param $maxLag int: maximum lag allowed for the request, as supplied by
- * the client
- * @return bool true if the request can continue
- */
- function checkMaxLag( $maxLag ) {
- list( $host, $lag ) = wfGetLB()->getMaxLag();
- if( $lag > $maxLag ) {
- wfMaxlagError( $host, $lag, $maxLag );
- return false;
- } else {
- return true;
+ public function __construct( IContextSource $context = null ) {
+ if ( !$context ) {
+ $context = RequestContext::getMain();
}
+
+ $this->context = $context;
+ $this->context->setTitle( $this->parseTitle() );
}
/**
- * Checks some initial queries
- * Note that $title here is *not* a Title object, but a string!
+ * Parse the request to get the Title object
*
- * @param $title String
- * @param $action String
* @return Title object to be $wgTitle
*/
- function checkInitialQueries( $title, $action ) {
- global $wgOut, $wgRequest, $wgContLang;
- if( $wgRequest->getVal( 'printable' ) === 'yes' ) {
- $wgOut->setPrintable();
- }
+ private function parseTitle() {
+ global $wgContLang;
- $curid = $wgRequest->getInt( 'curid' );
- if( $wgRequest->getCheck( 'search' ) ) {
+ $request = $this->context->getRequest();
+ $curid = $request->getInt( 'curid' );
+ $title = $request->getVal( 'title' );
+
+ 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).
$ret = SpecialPage::getTitleFor( 'Search' );
- } elseif( $curid ) {
+ } elseif ( $curid ) {
// URLs like this are generated by RC, because rc_title isn't always accurate
$ret = Title::newFromID( $curid );
- } elseif( $title == '' && $action != 'delete' ) {
+ } elseif ( $title == '' && $this->getAction() != 'delete' ) {
$ret = Title::newMainPage();
} else {
$ret = Title::newFromURL( $title );
// check variant links so that interwiki links don't have to worry
// about the possible different language variants
- if( count( $wgContLang->getVariants() ) > 1 && !is_null( $ret ) && $ret->getArticleID() == 0 )
+ if ( count( $wgContLang->getVariants() ) > 1
+ && !is_null( $ret ) && $ret->getArticleID() == 0 )
+ {
$wgContLang->findVariantLink( $title, $ret );
+ }
}
// For non-special titles, check for implicit titles
- if( is_null( $ret ) || $ret->getNamespace() != NS_SPECIAL ) {
+ if ( is_null( $ret ) || $ret->getNamespace() != NS_SPECIAL ) {
// We can have urls with just ?diff=,?oldid= or even just ?diff=
- $oldid = $wgRequest->getInt( 'oldid' );
- $oldid = $oldid ? $oldid : $wgRequest->getInt( 'diff' );
+ $oldid = $request->getInt( 'oldid' );
+ $oldid = $oldid ? $oldid : $request->getInt( 'diff' );
// Allow oldid to override a changed or missing title
- if( $oldid ) {
+ if ( $oldid ) {
$rev = Revision::newFromId( $oldid );
$ret = $rev ? $rev->getTitle() : $ret;
}
}
+
+ if ( $ret === null || ( $ret->getDBkey() == '' && $ret->getInterwiki() == '' ) ) {
+ $ret = SpecialPage::getTitleFor( 'Badtitle' );
+ }
+
return $ret;
}
/**
- * Checks for anon-cannot-read case
- *
- * @param $title Title
- * @param $output OutputPage
- * @return boolean true if successful
+ * Get the Title object that we'll be acting on, as specified in the WebRequest
+ * @return Title
*/
- function preliminaryChecks( &$title, &$output ) {
- global $wgTitle;
- // 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() ) {
- // Bug 32276: allowing the skin to generate output with $wgTitle
- // set to the input title would allow anonymous users to
- // determine whether a page exists, potentially leaking private data. In fact, the
- // curid and oldid request parameters would allow page titles to be enumerated even
- // when they are not guessable. So we reset the title to Special:Badtitle before the
- // permissions error is displayed.
- $badtitle = SpecialPage::getTitleFor( 'Badtitle' );
- $output->setTitle( $badtitle );
- $wgTitle = $badtitle;
-
- $output->loginToUse();
- $this->finalCleanup( $output );
- $output->disable();
- return false;
+ public function getTitle(){
+ if( $this->context->getTitle() === null ){
+ $this->context->setTitle( $this->parseTitle() );
}
- return true;
+ return $this->context->getTitle();
}
/**
- * Initialize some special cases:
+ * Performs the request.
* - bad titles
+ * - read restriction
* - local interwiki redirects
* - redirect loop
* - special pages
+ * - normal pages
*
- * @param $title Title
- * @param $output OutputPage
- * @param $request WebRequest
- * @return bool true if the request is already executed
+ * @return void
*/
- function handleSpecialCases( &$title, &$output, $request ) {
+ private function performRequest() {
+ global $wgServer, $wgUsePathInfo;
+
wfProfileIn( __METHOD__ );
- $action = $this->getVal( 'Action' );
+ $request = $this->context->getRequest();
+ $title = $this->context->getTitle();
+ $output = $this->context->getOutput();
+ $user = $this->context->getUser();
+
+ if ( $request->getVal( 'printable' ) === 'yes' ) {
+ $output->setPrintable();
+ }
+
+ $pageView = false; // was an article or special page viewed?
+
+ wfRunHooks( 'BeforeInitialize',
+ array( &$title, null, &$output, &$user, $request, $this ) );
// Invalid titles. Bug 21776: The interwikis must redirect even if the page name is empty.
- if( is_null($title) || ( ( $title->getDBkey() == '' ) && ( $title->getInterwiki() == '' ) ) ) {
- $title = SpecialPage::getTitleFor( 'Badtitle' );
- $output->setTitle( $title ); // bug 21456
+ if ( is_null( $title ) || ( $title->getDBkey() == '' && $title->getInterwiki() == '' ) ||
+ $title->isSpecial( 'Badtitle' ) )
+ {
+ $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
// Die now before we mess up $wgArticle and the skin stops working
throw new ErrorPageError( 'badtitle', 'badtitletext' );
-
+ // 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())
+ } elseif ( !$title->userCanRead() ) {
+ $output->loginToUse();
// Interwiki redirects
- } else if( $title->getInterwiki() != '' ) {
+ } elseif ( $title->getInterwiki() != '' ) {
$rdfrom = $request->getVal( 'rdfrom' );
- if( $rdfrom ) {
+ if ( $rdfrom ) {
$url = $title->getFullURL( 'rdfrom=' . urlencode( $rdfrom ) );
} else {
$query = $request->getValues();
unset( $query['title'] );
$url = $title->getFullURL( $query );
}
- /* Check for a redirect loop */
- if( !preg_match( '/^' . preg_quote( $this->getVal('Server'), '/' ) . '/', $url ) && $title->isLocal() ) {
+ // Check for a redirect loop
+ if ( !preg_match( '/^' . preg_quote( $wgServer, '/' ) . '/', $url )
+ && $title->isLocal() )
+ {
// 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
+ $this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
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()
- && ( $request->getVal( 'title' ) === null || $title->getPrefixedDBKey() != $request->getVal( 'title' ) )
- && !count( array_diff( array_keys( $request->getValues() ), array( 'action', 'title' ) ) ) )
+ } elseif ( $request->getVal( 'action', 'view' ) == 'view' && !$request->wasPosted()
+ && ( $request->getVal( 'title' ) === null ||
+ $title->getPrefixedDBKey() != $request->getVal( 'title' ) )
+ && !count( $request->getValueNames( array( 'action', 'title' ) ) )
+ && wfRunHooks( 'TestCanonicalRedirect', array( $request, $title, $output ) ) )
{
if ( $title->getNamespace() == NS_SPECIAL ) {
- list( $name, $subpage ) = SpecialPage::resolveAliasWithSubpage( $title->getDBkey() );
+ list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
if ( $name ) {
$title = SpecialPage::getTitleFor( $name, $subpage );
}
}
- $targetUrl = $title->getFullURL();
+ $targetUrl = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
// Redirect to canonical url, make it a 301 to allow caching
- if( $targetUrl == $request->getFullRequestURL() ) {
+ if ( $targetUrl == $request->getFullRequestURL() ) {
$message = "Redirect loop detected!\n\n" .
"This means the wiki got confused about what page was " .
"requested; this sometimes happens when moving a wiki " .
"to a new server or changing the server configuration.\n\n";
- if( $this->getVal( 'UsePathInfo' ) ) {
+ if ( $wgUsePathInfo ) {
$message .= "The wiki is trying to interpret the page " .
"title from the URL path portion (PATH_INFO), which " .
"sometimes fails depending on the web server. Try " .
@@ -250,78 +215,117 @@ class MediaWiki {
"to true.";
}
wfHttpError( 500, "Internal error", $message );
- wfProfileOut( __METHOD__ );
- return false;
} else {
$output->setSquidMaxage( 1200 );
$output->redirect( $targetUrl, '301' );
}
// Special pages
- } else if( NS_SPECIAL == $title->getNamespace() ) {
- /* actions that need to be made when we have a special pages */
- SpecialPage::executePath( $title );
+ } elseif ( NS_SPECIAL == $title->getNamespace() ) {
+ $pageView = true;
+ // Actions that need to be made when we have a special pages
+ SpecialPageFactory::executePath( $title, $this->context );
} else {
- /* No match to special cases */
- wfProfileOut( __METHOD__ );
- return false;
+ // ...otherwise treat it as an article view. The article
+ // may be a redirect to another article or URL.
+ $article = $this->initializeArticle();
+ if ( is_object( $article ) ) {
+ $pageView = true;
+ /**
+ * $wgArticle is deprecated, do not use it. This will possibly be removed
+ * entirely in 1.20 or 1.21
+ * @deprecated since 1.18
+ */
+ global $wgArticle;
+ $wgArticle = $article;
+
+ $this->performAction( $article );
+ } elseif ( is_string( $article ) ) {
+ $output->redirect( $article );
+ } else {
+ wfProfileOut( __METHOD__ );
+ throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle() returned neither an object nor a URL" );
+ }
+ }
+
+ if ( $pageView ) {
+ // Promote user to any groups they meet the criteria for
+ $user->addAutopromoteOnceGroups( 'onView' );
}
- /* Did match a special case */
+
wfProfileOut( __METHOD__ );
- return true;
}
/**
* Create an Article object of the appropriate class for the given page.
*
+ * @deprecated in 1.18; use Article::newFromTitle() instead
* @param $title Title
+ * @param $context IContextSource
* @return Article object
*/
- static function articleFromTitle( &$title ) {
- if( NS_MEDIA == $title->getNamespace() ) {
- // FIXME: where should this go?
- $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
- }
+ public static function articleFromTitle( $title, IContextSource $context ) {
+ return Article::newFromTitle( $title, $context );
+ }
- $article = null;
- wfRunHooks( 'ArticleFromTitle', array( &$title, &$article ) );
- if( $article ) {
- return $article;
+ /**
+ * Returns the action that will be executed, not necessarily the one passed
+ * passed through the "action" parameter. Actions disabled in
+ * $wgDisabledActions will be replaced by "nosuchaction"
+ *
+ * @return String: action
+ */
+ public function getAction() {
+ global $wgDisabledActions;
+
+ $request = $this->context->getRequest();
+ $action = $request->getVal( 'action', 'view' );
+
+ // Check for disabled actions
+ if ( in_array( $action, $wgDisabledActions ) ) {
+ return 'nosuchaction';
}
- switch( $title->getNamespace() ) {
- case NS_FILE:
- return new ImagePage( $title );
- case NS_CATEGORY:
- return new CategoryPage( $title );
- default:
- return new Article( $title );
+ // 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' ) ) {
+ return 'revisiondelete';
+ } else {
+ return 'view';
+ }
+ } elseif ( $action == 'editredlink' ) {
+ return 'edit';
}
+
+ return $action;
}
/**
- * Initialize the object to be known as $wgArticle for "standard" actions
+ * Initialize the main Article object for "standard" actions (view, etc)
* Create an Article object for the page, following redirects if needed.
*
- * @param $title Title ($wgTitle)
- * @param $output OutputPage ($wgOut)
- * @param $request WebRequest ($wgRequest)
* @return mixed an Article, or a string to redirect to another URL
*/
- function initializeArticle( &$title, &$output, $request ) {
+ private function initializeArticle() {
+ global $wgDisableHardRedirects;
+
wfProfileIn( __METHOD__ );
- $action = $this->getVal( 'action', 'view' );
- $article = self::articleFromTitle( $title );
+ $request = $this->context->getRequest();
+ $title = $this->context->getTitle();
+
+ $action = $request->getVal( 'action', 'view' );
+ $article = Article::newFromTitle( $title, $this->context );
// NS_MEDIAWIKI has no redirects.
// It is also used for CSS/JS, so performance matters here...
- if( $title->getNamespace() == NS_MEDIAWIKI ) {
+ if ( $title->getNamespace() == NS_MEDIAWIKI ) {
wfProfileOut( __METHOD__ );
return $article;
}
// Namespace might change when using redirects
// Check for redirects ...
- $file = ($title->getNamespace() == NS_FILE) ? $article->getFile() : null;
- if( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
+ $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
@@ -331,59 +335,53 @@ class MediaWiki {
// 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',
- array(&$title,&$request,&$ignoreRedirect,&$target,&$article) );
+ 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()) ) {
+ if ( !$ignoreRedirect && ( $target || $article->isRedirect() ) ) {
// Is the target already set by an extension?
$target = $target ? $target : $article->followRedirect();
- if( is_string( $target ) ) {
- if( !$this->getVal( 'DisableHardRedirects' ) ) {
+ if ( is_string( $target ) ) {
+ if ( !$wgDisableHardRedirects ) {
// we'll need to redirect
wfProfileOut( __METHOD__ );
return $target;
}
}
- if( is_object($target) ) {
+ if ( is_object( $target ) ) {
// Rewrite environment to redirected article
- $rarticle = self::articleFromTitle( $target );
- $rarticle->loadPageData( $rarticle->pageDataFromTitle( $dbr, $target ) );
- if( $rarticle->exists() || ( is_object( $file ) && !$file->isLocal() ) ) {
+ $rarticle = Article::newFromTitle( $target, $this->context );
+ $rarticle->loadPageData();
+ if ( $rarticle->exists() || ( is_object( $file ) && !$file->isLocal() ) ) {
$rarticle->setRedirectedFrom( $title );
$article = $rarticle;
- $title = $target;
- $output->setTitle( $title );
+ $this->context->setTitle( $target );
}
}
} else {
- $title = $article->getTitle();
+ $this->context->setTitle( $article->getTitle() );
}
}
+
wfProfileOut( __METHOD__ );
return $article;
}
/**
- * Cleaning up request by doing:
- ** deferred updates, DB transaction, and the output
- *
- * @param $output OutputPage
+ * Cleaning up request by doing deferred updates, DB transaction, and the output
*/
- function finalCleanup( &$output ) {
+ public function finalCleanup() {
wfProfileIn( __METHOD__ );
// 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->output();
+ $this->context->getOutput()->output();
// Do any deferred jobs
- wfDoUpdates( true );
+ wfDoUpdates( 'commit' );
$this->doJobs();
wfProfileOut( __METHOD__ );
}
@@ -391,20 +389,20 @@ class MediaWiki {
/**
* Do a job from the job queue
*/
- function doJobs() {
- $jobRunRate = $this->getVal( 'JobRunRate' );
+ private function doJobs() {
+ global $wgJobRunRate;
- if( $jobRunRate <= 0 || wfReadOnly() ) {
+ if ( $wgJobRunRate <= 0 || wfReadOnly() ) {
return;
}
- if( $jobRunRate < 1 ) {
+ if ( $wgJobRunRate < 1 ) {
$max = mt_getrandmax();
- if( mt_rand( 0, $max ) > $max * $jobRunRate ) {
+ if ( mt_rand( 0, $max ) > $max * $wgJobRunRate ) {
return;
}
$n = 1;
} else {
- $n = intval( $jobRunRate );
+ $n = intval( $wgJobRunRate );
}
while ( $n-- && false != ( $job = Job::pop() ) ) {
@@ -412,8 +410,8 @@ class MediaWiki {
$t = -wfTime();
$success = $job->run();
$t += wfTime();
- $t = round( $t*1000 );
- if( !$success ) {
+ $t = round( $t * 1000 );
+ if ( !$success ) {
$output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
} else {
$output .= "Success, Time: $t ms\n";
@@ -425,7 +423,8 @@ class MediaWiki {
/**
* Ends this task peacefully
*/
- function restInPeace() {
+ public function restInPeace() {
+ MessageCache::logMessages();
wfLogProfilingData();
// Commit and close up!
$factory = wfGetLBFactory();
@@ -437,126 +436,196 @@ class MediaWiki {
/**
* Perform one of the "standard" actions
*
- * @param $output OutputPage
* @param $article Article
- * @param $title Title
- * @param $user User
- * @param $request WebRequest
*/
- function performAction( &$output, &$article, &$title, &$user, &$request ) {
+ private function performAction( Page $article ) {
+ global $wgSquidMaxage, $wgUseExternalEditor;
+
wfProfileIn( __METHOD__ );
- if( !wfRunHooks( 'MediaWikiPerformAction', array( $output, $article, $title, $user, $request, $this ) ) ) {
+ $request = $this->context->getRequest();
+ $output = $this->context->getOutput();
+ $title = $this->context->getTitle();
+ $user = $this->context->getUser();
+
+ if ( !wfRunHooks( 'MediaWikiPerformAction',
+ array( $output, $article, $title, $user, $request, $this ) ) )
+ {
wfProfileOut( __METHOD__ );
return;
}
- $action = $this->getVal( 'Action' );
- if( in_array( $action, $this->getVal( 'DisabledActions', array() ) ) ) {
- /* No such action; this will switch to the default case */
- $action = 'nosuchaction';
- }
+ $act = $this->getAction();
- // 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';
- } else {
- $action = 'view';
- }
+ $action = Action::factory( $act, $article );
+ if ( $action instanceof Action ) {
+ $action->show();
+ wfProfileOut( __METHOD__ );
+ return;
}
- switch( $action ) {
+ switch( $act ) {
case 'view':
- $output->setSquidMaxage( $this->getVal( 'SquidMaxage' ) );
+ $output->setSquidMaxage( $wgSquidMaxage );
$article->view();
break;
case 'raw': // includes JS/CSS
- wfProfileIn( __METHOD__.'-raw' );
+ wfProfileIn( __METHOD__ . '-raw' );
$raw = new RawPage( $article );
$raw->view();
- wfProfileOut( __METHOD__.'-raw' );
+ wfProfileOut( __METHOD__ . '-raw' );
break;
- case 'watch':
- case 'unwatch':
case 'delete':
- case 'revert':
- case 'rollback':
case 'protect':
case 'unprotect':
- case 'info':
- case 'markpatrolled':
case 'render':
- case 'deletetrackback':
- case 'purge':
- $article->$action();
- break;
- case 'print':
- $article->view();
- break;
- case 'dublincore':
- if( !$this->getVal( 'EnableDublinCoreRdf' ) ) {
- wfHttpError( 403, 'Forbidden', wfMsg( 'nodublincore' ) );
- } else {
- $rdf = new DublinCoreRdf( $article );
- $rdf->show();
- }
- break;
- case 'creativecommons':
- if( !$this->getVal( 'EnableCreativeCommonsRdf' ) ) {
- wfHttpError( 403, 'Forbidden', wfMsg( 'nocreativecommons' ) );
- } else {
- $rdf = new CreativeCommonsRdf( $article );
- $rdf->show();
- }
- break;
- case 'credits':
- Credits::showPage( $article );
+ $article->$act();
break;
case 'submit':
- if( session_id() == '' ) {
- /* Send a cookie so anons get talk message notifications */
+ if ( session_id() == '' ) {
+ // Send a cookie so anons get talk message notifications
wfSetupSession();
}
- /* Continue... */
+ // Continue...
case 'edit':
- case 'editredlink':
- if( wfRunHooks( 'CustomEditor', array( $article, $user ) ) ) {
+ if ( wfRunHooks( 'CustomEditor', array( $article, $user ) ) ) {
$internal = $request->getVal( 'internaledit' );
$external = $request->getVal( 'externaledit' );
$section = $request->getVal( 'section' );
$oldid = $request->getVal( 'oldid' );
- if( !$this->getVal( 'UseExternalEditor' ) || $action=='submit' || $internal ||
- $section || $oldid || ( !$user->getOption( 'externaleditor' ) && !$external ) ) {
+ if ( !$wgUseExternalEditor || $act == 'submit' || $internal ||
+ $section || $oldid ||
+ ( !$user->getOption( 'externaleditor' ) && !$external ) )
+ {
$editor = new EditPage( $article );
$editor->submit();
- } elseif( $this->getVal( 'UseExternalEditor' ) && ( $external || $user->getOption( 'externaleditor' ) ) ) {
+ } elseif ( $wgUseExternalEditor
+ && ( $external || $user->getOption( 'externaleditor' ) ) )
+ {
$mode = $request->getVal( 'mode' );
- $extedit = new ExternalEdit( $article, $mode );
+ $extedit = new ExternalEdit( $article->getTitle(), $mode );
$extedit->edit();
}
}
break;
case 'history':
- if( $request->getFullRequestURL() == $title->getInternalURL( 'action=history' ) ) {
- $output->setSquidMaxage( $this->getVal( 'SquidMaxage' ) );
+ if ( $request->getFullRequestURL() == $title->getInternalURL( 'action=history' ) ) {
+ $output->setSquidMaxage( $wgSquidMaxage );
}
$history = new HistoryPage( $article );
$history->history();
break;
- case 'revisiondelete':
- // For show/hide submission from history page
- $special = SpecialPage::getPage( 'Revisiondelete' );
- $special->execute( '' );
- break;
default:
- if( wfRunHooks( 'UnknownAction', array( $action, $article ) ) ) {
+ if ( wfRunHooks( 'UnknownAction', array( $act, $article ) ) ) {
$output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
}
}
wfProfileOut( __METHOD__ );
+ }
+ /**
+ * Run the current MediaWiki instance
+ * index.php just calls this
+ */
+ public function run() {
+ try {
+ $this->checkMaxLag( true );
+ $this->main();
+ $this->restInPeace();
+ } catch ( Exception $e ) {
+ MWExceptionHandler::handle( $e );
+ }
}
+ /**
+ * Checks if the request should abort due to a lagged server,
+ * for given maxlag parameter.
+ *
+ * @param boolean $abort True if this class should abort the
+ * script execution. False to return the result as a boolean.
+ * @return boolean True if we passed the check, false if we surpass the maxlag
+ */
+ private function checkMaxLag( $abort ) {
+ global $wgShowHostnames;
+
+ wfProfileIn( __METHOD__ );
+ $maxLag = $this->context->getRequest()->getVal( 'maxlag' );
+ if ( !is_null( $maxLag ) ) {
+ $lb = wfGetLB(); // foo()->bar() is not supported in PHP4
+ list( $host, $lag ) = $lb->getMaxLag();
+ if ( $lag > $maxLag ) {
+ if ( $abort ) {
+ $resp = $this->context->getRequest()->response();
+ $resp->header( 'HTTP/1.1 503 Service Unavailable' );
+ $resp->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
+ $resp->header( 'X-Database-Lag: ' . intval( $lag ) );
+ $resp->header( 'Content-Type: text/plain' );
+ if( $wgShowHostnames ) {
+ echo "Waiting for $host: $lag seconds lagged\n";
+ } else {
+ echo "Waiting for a database server: $lag seconds lagged\n";
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+
+ if ( !$abort ) {
+ return false;
+ }
+ exit;
+ }
+ }
+ wfProfileOut( __METHOD__ );
+ return true;
+ }
+
+ private function main() {
+ global $wgUseFileCache, $wgTitle, $wgUseAjax;
+
+ wfProfileIn( __METHOD__ );
+
+ # Set title from request parameters
+ $wgTitle = $this->getTitle();
+ $action = $this->getAction();
+ $user = $this->context->getUser();
+
+ # Send Ajax requests to the Ajax dispatcher.
+ if ( $wgUseAjax && $action == 'ajax' ) {
+ $dispatcher = new AjaxDispatcher();
+ $dispatcher->performAction();
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
+ if ( $wgUseFileCache && $wgTitle->getNamespace() != NS_SPECIAL ) {
+ wfProfileIn( 'main-try-filecache' );
+ // Raw pages should handle cache control on their own,
+ // even when using file cache. This reduces hits from clients.
+ if ( HTMLFileCache::useFileCache() ) {
+ /* Try low-level file cache hit */
+ $cache = new HTMLFileCache( $wgTitle, $action );
+ if ( $cache->isFileCacheGood( /* Assume up to date */ ) ) {
+ /* Check incoming headers to see if client has this cached */
+ $timestamp = $cache->fileCacheTime();
+ if ( !$this->context->getOutput()->checkLastModified( $timestamp ) ) {
+ $cache->loadFromFileCache();
+ }
+ # Do any stats increment/watchlist stuff
+ $article = WikiPage::factory( $wgTitle );
+ $article->doViewUpdates( $user );
+ # Tell OutputPage that output is taken care of
+ $this->context->getOutput()->disable();
+ wfProfileOut( 'main-try-filecache' );
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+ }
+ wfProfileOut( 'main-try-filecache' );
+ }
+
+ $this->performRequest();
+ $this->finalCleanup();
+
+ wfProfileOut( __METHOD__ );
+ }
}
diff --git a/includes/WikiCategoryPage.php b/includes/WikiCategoryPage.php
new file mode 100644
index 00000000..42c2a6ce
--- /dev/null
+++ b/includes/WikiCategoryPage.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Special handling for category pages
+ */
+class WikiCategoryPage extends WikiPage {
+ /**
+ * Constructor from a page id
+ * @param $id Int article ID to load
+ */
+ public static function newFromID( $id ) {
+ $t = Title::newFromID( $id );
+ # @todo FIXME: Doesn't inherit right
+ return $t == null ? null : new self( $t );
+ # return $t == null ? null : new static( $t ); // PHP 5.3
+ }
+
+ /**
+ * Don't return a 404 for categories in use.
+ * In use defined as: either the actual page exists
+ * or the category currently has members.
+ */
+ public function hasViewableContent() {
+ if ( parent::hasViewableContent() ) {
+ return true;
+ } else {
+ $cat = Category::newFromTitle( $this->mTitle );
+ // If any of these are not 0, then has members
+ if ( $cat->getPageCount()
+ || $cat->getSubcatCount()
+ || $cat->getFileCount()
+ ) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/includes/WikiError.php b/includes/WikiError.php
index 452554c3..a634dff6 100644
--- a/includes/WikiError.php
+++ b/includes/WikiError.php
@@ -31,8 +31,11 @@
class WikiError {
/**
* @param $message string
+ *
+ * @deprecated since 1.17
*/
function __construct( $message ) {
+ wfDeprecated( __METHOD__ );
$this->mMessage = $message;
}
@@ -58,8 +61,11 @@ class WikiError {
*
* @param $object mixed
* @return bool
+ *
+ * @deprecated since 1.17
*/
public static function isError( $object ) {
+ wfDeprecated( __METHOD__ );
if ( $object instanceof WikiError ) {
return true;
} elseif ( $object instanceof Status ) {
@@ -78,8 +84,11 @@ class WikiErrorMsg extends WikiError {
/**
* @param $message String: wiki message name
* @param ... parameters to pass to wfMsg()
+ *
+ * @deprecated since 1.17
*/
function __construct( $message/*, ... */ ) {
+ wfDeprecated( __METHOD__ );
$args = func_get_args();
array_shift( $args );
$this->mMessage = wfMsgReal( $message, $args, true );
@@ -107,8 +116,11 @@ class WikiXmlError extends WikiError {
* @param $message string
* @param $context
* @param $offset Int
+ *
+ * @deprecated since 1.17
*/
function __construct( $parser, $message = 'XML parsing error', $context = null, $offset = 0 ) {
+ wfDeprecated( __METHOD__ );
$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/WikiFilePage.php b/includes/WikiFilePage.php
new file mode 100644
index 00000000..6727a8cd
--- /dev/null
+++ b/includes/WikiFilePage.php
@@ -0,0 +1,139 @@
+<?php
+/**
+ * Special handling for file pages
+ *
+ * @ingroup Media
+ */
+class WikiFilePage extends WikiPage {
+ protected $mFile = false; // !< File object
+ protected $mRepo = null; // !<
+ protected $mFileLoaded = false; // !<
+ protected $mDupes = null; // !<
+
+ function __construct( $title ) {
+ parent::__construct( $title );
+ $this->mDupes = null;
+ $this->mRepo = null;
+ }
+
+ public function getActionOverrides() {
+ return array( 'revert' => 'RevertFileAction' );
+ }
+
+ /**
+ * @param $file File:
+ * @return void
+ */
+ public function setFile( $file ) {
+ $this->mFile = $file;
+ $this->mFileLoaded = true;
+ }
+
+ protected function loadFile() {
+ if ( $this->mFileLoaded ) {
+ return true;
+ }
+ $this->mFileLoaded = true;
+
+ $this->mFile = false;
+ if ( !$this->mFile ) {
+ $this->mFile = wfFindFile( $this->mTitle );
+ if ( !$this->mFile ) {
+ $this->mFile = wfLocalFile( $this->mTitle ); // always a File
+ }
+ }
+ $this->mRepo = $this->mFile->getRepo();
+ }
+
+ public function getRedirectTarget() {
+ $this->loadFile();
+ if ( $this->mFile->isLocal() ) {
+ return parent::getRedirectTarget();
+ }
+ // Foreign image page
+ $from = $this->mFile->getRedirected();
+ $to = $this->mFile->getName();
+ if ( $from == $to ) {
+ return null;
+ }
+ return $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
+ }
+
+ public function followRedirect() {
+ $this->loadFile();
+ if ( $this->mFile->isLocal() ) {
+ return parent::followRedirect();
+ }
+ $from = $this->mFile->getRedirected();
+ $to = $this->mFile->getName();
+ if ( $from == $to ) {
+ return false;
+ }
+ return Title::makeTitle( NS_FILE, $to );
+ }
+
+ public function isRedirect( $text = false ) {
+ $this->loadFile();
+ if ( $this->mFile->isLocal() ) {
+ return parent::isRedirect( $text );
+ }
+
+ return (bool)$this->mFile->getRedirected();
+ }
+
+ public function isLocal() {
+ $this->loadFile();
+ return $this->mFile->isLocal();
+ }
+
+ public function getFile() {
+ $this->loadFile();
+ return $this->mFile;
+ }
+
+ public function getDuplicates() {
+ $this->loadFile();
+ if ( !is_null( $this->mDupes ) ) {
+ return $this->mDupes;
+ }
+ $hash = $this->mFile->getSha1();
+ if ( !( $hash ) ) {
+ return $this->mDupes = array();
+ }
+ $dupes = RepoGroup::singleton()->findBySha1( $hash );
+ // Remove duplicates with self and non matching file sizes
+ $self = $this->mFile->getRepoName() . ':' . $this->mFile->getName();
+ $size = $this->mFile->getSize();
+ foreach ( $dupes as $index => $file ) {
+ $key = $file->getRepoName() . ':' . $file->getName();
+ if ( $key == $self ) {
+ unset( $dupes[$index] );
+ }
+ if ( $file->getSize() != $size ) {
+ unset( $dupes[$index] );
+ }
+ }
+ $this->mDupes = $dupes;
+ return $this->mDupes;
+ }
+
+ /**
+ * Override handling of action=purge
+ */
+ public function doPurge() {
+ $this->loadFile();
+ if ( $this->mFile->exists() ) {
+ wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
+ $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
+ $update->doUpdate();
+ $this->mFile->upgradeRow();
+ $this->mFile->purgeCache();
+ } else {
+ wfDebug( 'ImagePage::doPurge no image for ' . $this->mFile->getName() . "; limiting purge to cache only\n" );
+ // even if the file supposedly doesn't exist, force any cached information
+ // to be updated (in case the cached information is wrong)
+ $this->mFile->purgeCache();
+ }
+ parent::doPurge();
+ }
+}
diff --git a/includes/WikiMap.php b/includes/WikiMap.php
index e12f7abe..458718ee 100644
--- a/includes/WikiMap.php
+++ b/includes/WikiMap.php
@@ -18,7 +18,7 @@ class WikiMap {
list( $major, $minor ) = $wgConf->siteFromDB( $wikiID );
if( isset( $major ) ) {
- $server = $wgConf->get( 'wgServer', $wikiID, $major,
+ $server = $wgConf->get( 'wgCanonicalServer', $wikiID, $major,
array( 'lang' => $minor, 'site' => $major ) );
$path = $wgConf->get( 'wgArticlePath', $wikiID, $major,
array( 'lang' => $minor, 'site' => $major ) );
@@ -65,9 +65,6 @@ class WikiMap {
* @return String: HTML link or false if the wiki was not found
*/
public static function makeForeignLink( $wikiID, $page, $text=null ) {
- global $wgUser;
- $sk = $wgUser->getSkin();
-
if ( !$text ) {
$text = $page;
}
@@ -77,7 +74,7 @@ class WikiMap {
return false;
}
- return $sk->makeExternalLink( $url, $text );
+ return Linker::makeExternalLink( $url, $text );
}
/**
@@ -132,17 +129,19 @@ class WikiReference {
*/
public function getDisplayName() {
$url = $this->getUrl( '' );
- $url = preg_replace( '!^https?://!', '', $url );
- $url = preg_replace( '!/index\.php(\?title=|/)$!', '/', $url );
- $url = preg_replace( '!/wiki/$!', '/', $url );
- $url = preg_replace( '!/$!', '', $url );
- return $url;
+ $parsed = wfParseUrl( $url );
+ if ( $parsed ) {
+ return $parsed['host'];
+ } else {
+ // Invalid URL. There's no sane thing to do here, so just return it
+ return $url;
+ }
}
/**
* Helper function for getUrl()
*
- * @todo FIXME: this may be generalized...
+ * @todo FIXME: This may be generalized...
* @param $page String: page name (must be normalised before calling this function!)
* @return String: Url fragment
*/
diff --git a/includes/WikiPage.php b/includes/WikiPage.php
new file mode 100644
index 00000000..9a0d9714
--- /dev/null
+++ b/includes/WikiPage.php
@@ -0,0 +1,2677 @@
+<?php
+/**
+ * Abstract class for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
+ */
+abstract class Page {}
+
+/**
+ * Class representing a MediaWiki article and history.
+ *
+ * Some fields are public only for backwards-compatibility. Use accessors.
+ * In the past, this class was part of Article.php and everything was public.
+ *
+ * @internal documentation reviewed 15 Mar 2010
+ */
+class WikiPage extends Page {
+ /**
+ * @var Title
+ * @protected
+ */
+ public $mTitle = null;
+
+ /**@{{
+ * @protected
+ */
+ public $mCounter = -1; // !< Integer (-1 means "not loaded")
+ public $mDataLoaded = false; // !< Boolean
+ public $mIsRedirect = false; // !< Boolean
+ public $mLatest = false; // !< Boolean
+ public $mPreparedEdit = false; // !< Array
+ public $mRedirectTarget = null; // !< Title object
+ public $mLastRevision = null; // !< Revision object
+ public $mTimestamp = ''; // !< String
+ public $mTouched = '19700101000000'; // !< String
+ /**@}}*/
+
+ /**
+ * @protected
+ * @var ParserOptions: ParserOptions object for $wgUser articles
+ */
+ public $mParserOptions;
+
+ /**
+ * Constructor and clear the article
+ * @param $title Title Reference to a Title object.
+ */
+ public function __construct( Title $title ) {
+ $this->mTitle = $title;
+ }
+
+ /**
+ * Create a WikiPage object of the appropriate class for the given title.
+ *
+ * @param $title Title
+ * @return WikiPage object of the appropriate type
+ */
+ public static function factory( Title $title ) {
+ $ns = $title->getNamespace();
+
+ if ( $ns == NS_MEDIA ) {
+ throw new MWException( "NS_MEDIA is a virtual namespace; use NS_FILE." );
+ } elseif ( $ns < 0 ) {
+ throw new MWException( "Invalid or virtual namespace $ns given." );
+ }
+
+ switch ( $ns ) {
+ case NS_FILE:
+ $page = new WikiFilePage( $title );
+ break;
+ case NS_CATEGORY:
+ $page = new WikiCategoryPage( $title );
+ break;
+ default:
+ $page = new WikiPage( $title );
+ }
+
+ return $page;
+ }
+
+ /**
+ * Constructor from a page id
+ *
+ * Always override this for all subclasses (until we use PHP with LSB)
+ *
+ * @param $id Int article ID to load
+ *
+ * @return WikiPage
+ */
+ public static function newFromID( $id ) {
+ $t = Title::newFromID( $id );
+ # @todo FIXME: Doesn't inherit right
+ return $t == null ? null : new self( $t );
+ # return $t == null ? null : new static( $t ); // PHP 5.3
+ }
+
+ /**
+ * Returns overrides for action handlers.
+ * Classes listed here will be used instead of the default one when
+ * (and only when) $wgActions[$action] === true. This allows subclasses
+ * to override the default behavior.
+ *
+ * @return Array
+ */
+ public function getActionOverrides() {
+ return array();
+ }
+
+ /**
+ * If this page is a redirect, get its target
+ *
+ * The target will be fetched from the redirect table if possible.
+ * If this page doesn't have an entry there, call insertRedirect()
+ * @return Title|mixed object, or null if this page is not a redirect
+ */
+ public function getRedirectTarget() {
+ if ( !$this->mTitle->isRedirect() ) {
+ return null;
+ }
+
+ if ( $this->mRedirectTarget !== null ) {
+ return $this->mRedirectTarget;
+ }
+
+ # Query the redirect table
+ $dbr = wfGetDB( DB_SLAVE );
+ $row = $dbr->selectRow( 'redirect',
+ array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
+ array( 'rd_from' => $this->getId() ),
+ __METHOD__
+ );
+
+ // 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();
+ }
+
+ /**
+ * Insert an entry for this page into the redirect table.
+ *
+ * Don't call this function directly unless you know what you're doing.
+ * @return Title object or null if not a redirect
+ */
+ public function insertRedirect() {
+ // recurse through to only get the final target
+ $retval = Title::newFromRedirectRecurse( $this->getRawText() );
+ 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' => $rt->getNamespace(),
+ 'rd_title' => $rt->getDBkey(),
+ 'rd_fragment' => $rt->getFragment(),
+ 'rd_interwiki' => $rt->getInterwiki(),
+ ),
+ __METHOD__
+ );
+ }
+
+ /**
+ * 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() {
+ return $this->getRedirectURL( $this->getRedirectTarget() );
+ }
+
+ /**
+ * 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() ) {
+ // Offsite wikis need an HTTP redirect.
+ //
+ // This can be hard to reverse and may produce loops,
+ // so they may be disabled in the site configuration.
+ $source = $this->mTitle->getFullURL( 'redirect=no' );
+ return $rt->getFullURL( 'rdfrom=' . urlencode( $source ) );
+ }
+ } else {
+ if ( $rt->getNamespace() == NS_SPECIAL ) {
+ // Gotta handle redirects to special pages differently:
+ // Fill the HTTP response "Location" header and ignore
+ // the rest of the page we're on.
+ //
+ // This can be hard to reverse, so they may be disabled.
+ if ( $rt->isSpecial( 'Userlogout' ) ) {
+ // rolleyes
+ } else {
+ return $rt->getFullURL();
+ }
+ }
+
+ return $rt;
+ }
+ }
+
+ // No or invalid redirect
+ return false;
+ }
+
+ /**
+ * Get the title object of the article
+ * @return Title object of this page
+ */
+ public function getTitle() {
+ return $this->mTitle;
+ }
+
+ /**
+ * Clear the object
+ */
+ public function clear() {
+ $this->mDataLoaded = false;
+
+ $this->mCounter = -1; # Not loaded
+ $this->mRedirectTarget = null; # Title object if set
+ $this->mLastRevision = null; # Latest revision
+ $this->mTimestamp = '';
+ $this->mTouched = '19700101000000';
+ $this->mIsRedirect = false;
+ $this->mLatest = false;
+ $this->mPreparedEdit = false;
+ }
+
+ /**
+ * Get the text that needs to be saved in order to undo all revisions
+ * between $undo and $undoafter. Revisions must belong to the same page,
+ * must exist and must not be deleted
+ * @param $undo Revision
+ * @param $undoafter Revision Must be an earlier revision than $undo
+ * @return mixed string on success, false on failure
+ */
+ public function getUndoText( Revision $undo, Revision $undoafter = null ) {
+ $cur_text = $this->getRawText();
+ if ( $cur_text === false ) {
+ return false; // no page
+ }
+ $undo_text = $undo->getText();
+ $undoafter_text = $undoafter->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 ) ) {
+ return false;
+ }
+
+ return $undone_text;
+ }
+
+ /**
+ * Return the list of revision fields that should be selected to create
+ * a new page.
+ *
+ * @return array
+ */
+ public static function selectFields() {
+ return array(
+ 'page_id',
+ 'page_namespace',
+ 'page_title',
+ 'page_restrictions',
+ 'page_counter',
+ 'page_is_redirect',
+ 'page_is_new',
+ 'page_random',
+ 'page_touched',
+ 'page_latest',
+ 'page_len',
+ );
+ }
+
+ /**
+ * Fetch a page record with the given conditions
+ * @param $dbr DatabaseBase object
+ * @param $conditions Array
+ * @return mixed Database result resource, or false on failure
+ */
+ protected function pageData( $dbr, $conditions ) {
+ $fields = self::selectFields();
+
+ wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
+
+ $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__ );
+
+ wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
+
+ return $row;
+ }
+
+ /**
+ * Fetch a page record matching the Title object's namespace and title
+ * using a sanitized title string
+ *
+ * @param $dbr DatabaseBase object
+ * @param $title Title object
+ * @return mixed Database result resource, or false on failure
+ */
+ public function pageDataFromTitle( $dbr, $title ) {
+ return $this->pageData( $dbr, array(
+ 'page_namespace' => $title->getNamespace(),
+ 'page_title' => $title->getDBkey() ) );
+ }
+
+ /**
+ * Fetch a page record matching the requested ID
+ *
+ * @param $dbr DatabaseBase
+ * @param $id Integer
+ * @return mixed Database result resource, or false on failure
+ */
+ public function pageDataFromId( $dbr, $id ) {
+ return $this->pageData( $dbr, array( 'page_id' => $id ) );
+ }
+
+ /**
+ * Set the general counter, title etc data loaded from
+ * some source.
+ *
+ * @param $data Object|String One of the following:
+ * A DB query result object or...
+ * "fromdb" to get from a slave DB or...
+ * "fromdbmaster" to get from the master DB
+ * @return void
+ */
+ public function loadPageData( $data = 'fromdb' ) {
+ if ( $data === 'fromdbmaster' ) {
+ $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
+ } elseif ( $data === 'fromdb' ) { // slave
+ $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
+ # Use a "last rev inserted" timestamp key to dimish the issue of slave lag.
+ # Note that DB also stores the master position in the session and checks it.
+ $touched = $this->getCachedLastEditTime();
+ if ( $touched ) { // key set
+ if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) {
+ $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
+ }
+ }
+ }
+
+ $lc = LinkCache::singleton();
+
+ if ( $data ) {
+ $lc->addGoodLinkObj( $data->page_id, $this->mTitle,
+ $data->page_len, $data->page_is_redirect, $data->page_latest );
+
+ $this->mTitle->loadFromRow( $data );
+
+ # Old-fashioned restrictions
+ $this->mTitle->loadRestrictions( $data->page_restrictions );
+
+ $this->mCounter = intval( $data->page_counter );
+ $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
+ $this->mIsRedirect = intval( $data->page_is_redirect );
+ $this->mLatest = intval( $data->page_latest );
+ } else {
+ $lc->addBadLinkObj( $this->mTitle );
+
+ $this->mTitle->loadFromRow( false );
+ }
+
+ $this->mDataLoaded = true;
+ }
+
+ /**
+ * @return int Page ID
+ */
+ public function getId() {
+ return $this->mTitle->getArticleID();
+ }
+
+ /**
+ * @return bool Whether or not the page exists in the database
+ */
+ public function exists() {
+ return $this->getId() > 0;
+ }
+
+ /**
+ * Check if this page is something we're going to be showing
+ * some sort of sensible content for. If we return false, page
+ * views (plain action=view) will return an HTTP 404 response,
+ * so spiders and robots can know they're following a bad link.
+ *
+ * @return bool
+ */
+ public function hasViewableContent() {
+ return $this->exists() || $this->mTitle->isAlwaysKnown();
+ }
+
+ /**
+ * @return int The view count for the page
+ */
+ public function getCount() {
+ if ( -1 == $this->mCounter ) {
+ $id = $this->getId();
+
+ if ( $id == 0 ) {
+ $this->mCounter = 0;
+ } else {
+ $dbr = wfGetDB( DB_SLAVE );
+ $this->mCounter = $dbr->selectField( 'page',
+ 'page_counter',
+ array( 'page_id' => $id ),
+ __METHOD__
+ );
+ }
+ }
+
+ return $this->mCounter;
+ }
+
+ /**
+ * Determine whether a page would be suitable for being counted as an
+ * article in the site_stats table based on the title & its content
+ *
+ * @param $editInfo Object or false: object returned by prepareTextForEdit(),
+ * if false, the current database state will be used
+ * @return Boolean
+ */
+ public function isCountable( $editInfo = false ) {
+ global $wgArticleCountMethod;
+
+ if ( !$this->mTitle->isContentPage() ) {
+ return false;
+ }
+
+ $text = $editInfo ? $editInfo->pst : false;
+
+ if ( $this->isRedirect( $text ) ) {
+ return false;
+ }
+
+ switch ( $wgArticleCountMethod ) {
+ case 'any':
+ return true;
+ case 'comma':
+ if ( $text === false ) {
+ $text = $this->getRawText();
+ }
+ return strpos( $text, ',' ) !== false;
+ case 'link':
+ if ( $editInfo ) {
+ // ParserOutput::getLinks() is a 2D array of page links, so
+ // to be really correct we would need to recurse in the array
+ // but the main array should only have items in it if there are
+ // links.
+ return (bool)count( $editInfo->output->getLinks() );
+ } else {
+ return (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
+ array( 'pl_from' => $this->getId() ), __METHOD__ );
+ }
+ }
+ }
+
+ /**
+ * Tests if the article text represents a redirect
+ *
+ * @param $text mixed string containing article contents, or boolean
+ * @return bool
+ */
+ public function isRedirect( $text = false ) {
+ if ( $text === false ) {
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
+ }
+
+ return (bool)$this->mIsRedirect;
+ } else {
+ return Title::newFromRedirect( $text ) !== null;
+ }
+ }
+
+ /**
+ * Loads everything except the text
+ * This isn't necessary for all uses, so it's only done if needed.
+ */
+ protected function loadLastEdit() {
+ if ( $this->mLastRevision !== null ) {
+ return; // already loaded
+ }
+
+ $latest = $this->getLatest();
+ if ( !$latest ) {
+ return; // page doesn't exist or is missing page_latest info
+ }
+
+ $revision = Revision::newFromPageId( $this->getId(), $latest );
+ if ( $revision ) { // sanity
+ $this->setLastEdit( $revision );
+ }
+ }
+
+ /**
+ * Set the latest revision
+ */
+ protected function setLastEdit( Revision $revision ) {
+ $this->mLastRevision = $revision;
+ $this->mTimestamp = $revision->getTimestamp();
+ }
+
+ /**
+ * Get the latest revision
+ * @return Revision|null
+ */
+ public function getRevision() {
+ $this->loadLastEdit();
+ if ( $this->mLastRevision ) {
+ return $this->mLastRevision;
+ }
+ return null;
+ }
+
+ /**
+ * Get the text of the current revision. No side-effects...
+ *
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @return String|false The text of the current revision
+ */
+ public function getText( $audience = Revision::FOR_PUBLIC ) {
+ $this->loadLastEdit();
+ if ( $this->mLastRevision ) {
+ return $this->mLastRevision->getText( $audience );
+ }
+ return false;
+ }
+
+ /**
+ * Get the text of the current revision. No side-effects...
+ *
+ * @return String|false The text of the current revision
+ */
+ public function getRawText() {
+ $this->loadLastEdit();
+ if ( $this->mLastRevision ) {
+ return $this->mLastRevision->getRawText();
+ }
+ return false;
+ }
+
+ /**
+ * @return string MW timestamp of last article revision
+ */
+ public function getTimestamp() {
+ // Check if the field has been filled by WikiPage::setTimestamp()
+ if ( !$this->mTimestamp ) {
+ $this->loadLastEdit();
+ }
+ return wfTimestamp( TS_MW, $this->mTimestamp );
+ }
+
+ /**
+ * Set the page timestamp (use only to avoid DB queries)
+ * @param $ts string MW timestamp of last article revision
+ * @return void
+ */
+ public function setTimestamp( $ts ) {
+ $this->mTimestamp = wfTimestamp( TS_MW, $ts );
+ }
+
+ /**
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @return int user ID for the user that made the last article revision
+ */
+ public function getUser( $audience = Revision::FOR_PUBLIC ) {
+ $this->loadLastEdit();
+ if ( $this->mLastRevision ) {
+ return $this->mLastRevision->getUser( $audience );
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @return string username of the user that made the last article revision
+ */
+ public function getUserText( $audience = Revision::FOR_PUBLIC ) {
+ $this->loadLastEdit();
+ if ( $this->mLastRevision ) {
+ return $this->mLastRevision->getUserText( $audience );
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @return string Comment stored for the last article revision
+ */
+ public function getComment( $audience = Revision::FOR_PUBLIC ) {
+ $this->loadLastEdit();
+ if ( $this->mLastRevision ) {
+ return $this->mLastRevision->getComment( $audience );
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * 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();
+ if ( $this->mLastRevision ) {
+ return $this->mLastRevision->isMinor();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get a list of users who have edited this article, not including the user who made
+ * the most recent revision, which you can get from $article->getUser() if you want it
+ * @return UserArrayFromResult
+ */
+ public function getContributors() {
+ # @todo FIXME: This is expensive; cache this info somewhere.
+
+ $dbr = wfGetDB( DB_SLAVE );
+
+ if ( $dbr->implicitGroupby() ) {
+ $realNameField = 'user_real_name';
+ } else {
+ $realNameField = 'FIRST(user_real_name) AS user_real_name';
+ }
+
+ $tables = array( 'revision', 'user' );
+
+ $fields = array(
+ 'rev_user as user_id',
+ 'rev_user_text AS user_name',
+ $realNameField,
+ 'MAX(rev_timestamp) AS timestamp',
+ );
+
+ $conds = array( 'rev_page' => $this->getId() );
+
+ // The user who made the top revision gets credited as "this page was last edited by
+ // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
+ $user = $this->getUser();
+ if ( $user ) {
+ $conds[] = "rev_user != $user";
+ } else {
+ $conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
+ }
+
+ $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0"; // username hidden?
+
+ $jconds = array(
+ 'user' => array( 'LEFT JOIN', 'rev_user = user_id' ),
+ );
+
+ $options = array(
+ 'GROUP BY' => array( 'rev_user', 'rev_user_text' ),
+ 'ORDER BY' => 'timestamp DESC',
+ );
+
+ $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $jconds );
+ return new UserArrayFromResult( $res );
+ }
+
+ /**
+ * Should the parser cache be used?
+ *
+ * @param $user User The relevant user
+ * @return boolean
+ */
+ public function isParserCacheUsed( User $user, $oldid ) {
+ global $wgEnableParserCache;
+
+ return $wgEnableParserCache
+ && $user->getStubThreshold() == 0
+ && $this->exists()
+ && empty( $oldid )
+ && !$this->mTitle->isCssOrJsPage()
+ && !$this->mTitle->isCssJsSubpage();
+ }
+
+ /**
+ * Perform the actions of a page purging
+ */
+ public function doPurge() {
+ global $wgUseSquid;
+
+ if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ){
+ return false;
+ }
+
+ // Invalidate the cache
+ $this->mTitle->invalidateCache();
+ $this->clear();
+
+ if ( $wgUseSquid ) {
+ // Commit the transaction before the purge is sent
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->commit();
+
+ // Send purge
+ $update = SquidUpdate::newSimplePurge( $this->mTitle );
+ $update->doUpdate();
+ }
+
+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ if ( $this->getId() == 0 ) {
+ $text = false;
+ } else {
+ $text = $this->getRawText();
+ }
+
+ MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
+ }
+ }
+
+ /**
+ * Insert a new empty page record for this article.
+ * This *must* be followed up by creating a revision
+ * and running $this->updateRevisionOn( ... );
+ * or else the record will be left in a funky state.
+ * Best if all done inside a transaction.
+ *
+ * @param $dbw DatabaseBase
+ * @return int The newly created page_id key, or false if the title already existed
+ * @private
+ */
+ public function insertOn( $dbw ) {
+ wfProfileIn( __METHOD__ );
+
+ $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
+ $dbw->insert( 'page', array(
+ 'page_id' => $page_id,
+ 'page_namespace' => $this->mTitle->getNamespace(),
+ 'page_title' => $this->mTitle->getDBkey(),
+ 'page_counter' => 0,
+ 'page_restrictions' => '',
+ 'page_is_redirect' => 0, # Will set this shortly...
+ 'page_is_new' => 1,
+ 'page_random' => wfRandom(),
+ 'page_touched' => $dbw->timestamp(),
+ 'page_latest' => 0, # Fill this in shortly...
+ 'page_len' => 0, # Fill this in shortly...
+ ), __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 DatabaseBase: object
+ * @param $revision Revision: For ID number, and text used to set
+ length and redirect status fields
+ * @param $lastRevision Integer: if given, will not overwrite the page field
+ * when different from the currently set value.
+ * Giving 0 indicates the new page flag should be set
+ * on.
+ * @param $lastRevIsRedirect Boolean: if given, will optimize adding and
+ * removing rows in redirect table.
+ * @return bool true on success, false on failure
+ * @private
+ */
+ public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
+ wfProfileIn( __METHOD__ );
+
+ $text = $revision->getText();
+ $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;
+ }
+
+ $now = wfTimestampNow();
+ $dbw->update( 'page',
+ array( /* SET */
+ 'page_latest' => $revision->getId(),
+ 'page_touched' => $dbw->timestamp( $now ),
+ 'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
+ 'page_is_redirect' => $rt !== null ? 1 : 0,
+ 'page_len' => strlen( $text ),
+ ),
+ $conditions,
+ __METHOD__ );
+
+ $result = $dbw->affectedRows() != 0;
+ if ( $result ) {
+ $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
+ $this->setCachedLastEditTime( $now );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $result;
+ }
+
+ /**
+ * Get the cached timestamp for the last time the page changed.
+ * This is only used to help handle slave lag by comparing to page_touched.
+ * @return string MW timestamp
+ */
+ protected function getCachedLastEditTime() {
+ global $wgMemc;
+ $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
+ return $wgMemc->get( $key );
+ }
+
+ /**
+ * Set the cached timestamp for the last time the page changed.
+ * This is only used to help handle slave lag by comparing to page_touched.
+ * @param $timestamp string
+ * @return void
+ */
+ public function setCachedLastEditTime( $timestamp ) {
+ global $wgMemc;
+ $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
+ $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60*15 );
+ }
+
+ /**
+ * Add row to the redirect table if this is a redirect, remove otherwise.
+ *
+ * @param $dbw DatabaseBase
+ * @param $redirectTitle Title object pointing to the redirect target,
+ * or NULL if this is not a redirect
+ * @param $lastRevIsRedirect If given, will optimize adding and
+ * removing rows in redirect table.
+ * @return bool true on success, false on failure
+ * @private
+ */
+ public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
+ // Always update redirects (target link might have changed)
+ // 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 ) {
+ return true;
+ }
+
+ wfProfileIn( __METHOD__ );
+ if ( $isRedirect ) {
+ $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 );
+ }
+
+ /**
+ * If the given revision is newer than the currently set page_latest,
+ * update the page record. Otherwise, do nothing.
+ *
+ * @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' ),
+ array(
+ 'page_id' => $this->getId(),
+ 'page_latest=rev_id' ),
+ __METHOD__ );
+
+ if ( $row ) {
+ if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $prev = $row->rev_id;
+ $lastRevIsRedirect = (bool)$row->page_is_redirect;
+ } else {
+ # No or missing previous revision; mark the page as new
+ $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 {
+ if ( is_null( $edittime ) ) {
+ $rev = Revision::newFromTitle( $this->mTitle );
+ } else {
+ $dbw = wfGetDB( DB_MASTER );
+ $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
+ }
+
+ if ( !$rev ) {
+ wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
+ $this->getId() . "; section: $section; edittime: $edittime)\n" );
+ wfProfileOut( __METHOD__ );
+ return null;
+ }
+
+ $oldtext = $rev->getText();
+
+ if ( $section == 'new' ) {
+ # Inserting a new section
+ $subject = $summary ? wfMsgForContent( 'newsectionheaderdefaultlevel', $summary ) . "\n\n" : '';
+ $text = strlen( trim( $oldtext ) ) > 0
+ ? "{$oldtext}\n\n{$subject}{$text}"
+ : "{$subject}{$text}";
+ } else {
+ # Replacing an existing section; roll out the big guns
+ global $wgParser;
+
+ $text = $wgParser->replaceSection( $oldtext, $section, $text );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $text;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Change an existing article or create a new article. Updates RC and all necessary caches,
+ * optionally via the deferred update array.
+ *
+ * @param $text String: new text
+ * @param $summary String: edit summary
+ * @param $flags Integer bitfield:
+ * EDIT_NEW
+ * Article is known or assumed to be non-existent, create a new one
+ * EDIT_UPDATE
+ * Article is known or assumed to be pre-existing, update it
+ * EDIT_MINOR
+ * Mark this edit minor, if the user is allowed to do so
+ * EDIT_SUPPRESS_RC
+ * Do not log the change in recentchanges
+ * EDIT_FORCE_BOT
+ * Mark the edit a "bot" edit regardless of user rights
+ * EDIT_DEFER_UPDATES
+ * Defer some of the updates until the end of index.php
+ * EDIT_AUTOSUMMARY
+ * Fill in blank summaries with generated text where possible
+ *
+ * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected.
+ * If EDIT_UPDATE is specified and the article doesn't exist, the function will an
+ * edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
+ * edit-already-exists error will be returned. These two conditions are also possible with
+ * auto-detection due to MediaWiki's performance-optimised locking strategy.
+ *
+ * @param $baseRevId the revision ID this edit was based off, if any
+ * @param $user User the user doing the edit
+ *
+ * @return Status object. Possible errors:
+ * edit-hook-aborted: The ArticleSave hook aborted the edit but didn't set the fatal flag of $status
+ * edit-gone-missing: In update mode, but the article didn't exist
+ * edit-conflict: In update mode, the article changed unexpectedly
+ * edit-no-change: Warning that the text was the same as before
+ * edit-already-exists: In creation mode, but the article already exists
+ *
+ * Extensions may define additional errors.
+ *
+ * $return->value will contain an associative array with members as follows:
+ * new: Boolean indicating if the function attempted to create a new article
+ * revision: The revision object for the inserted revision, or null
+ *
+ * Compatibility note: this function previously returned a boolean value indicating success/failure
+ */
+ public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
+ global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries;
+
+ # Low-level sanity check
+ if ( $this->mTitle->getText() === '' ) {
+ throw new MWException( 'Something is trying to edit an article with an empty title' );
+ }
+
+ wfProfileIn( __METHOD__ );
+
+ $user = is_null( $user ) ? $wgUser : $user;
+ $status = Status::newGood( array() );
+
+ # Load $this->mTitle->getArticleID() and $this->mLatest if it's not already
+ $this->loadPageData( 'fromdbmaster' );
+
+ $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" );
+
+ if ( $status->isOK() ) {
+ $status->fatal( 'edit-hook-aborted' );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ # Silently ignore EDIT_MINOR if not allowed
+ $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
+ $bot = $flags & EDIT_FORCE_BOT;
+
+ $oldtext = $this->getRawText(); // current revision
+ $oldsize = strlen( $oldtext );
+ $oldcountable = $this->isCountable();
+
+ # Provide autosummaries if one is not provided and autosummaries are enabled.
+ if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
+ $summary = self::getAutosummary( $oldtext, $text, $flags );
+ }
+
+ $editInfo = $this->prepareTextForEdit( $text, null, $user );
+ $text = $editInfo->pst;
+ $newsize = strlen( $text );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $now = wfTimestampNow();
+ $this->mTimestamp = $now;
+
+ 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 );
+ }
+
+ $revision = new Revision( array(
+ 'page' => $this->getId(),
+ 'comment' => $summary,
+ 'minor_edit' => $isminor,
+ 'text' => $text,
+ 'parent_id' => $this->mLatest,
+ 'user' => $user->getId(),
+ 'user_text' => $user->getName(),
+ 'timestamp' => $now
+ ) );
+
+ $changed = ( strcmp( $text, $oldtext ) != 0 );
+
+ if ( $changed ) {
+ if ( !$this->mLatest ) {
+ # Article gone missing
+ wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
+ $status->fatal( 'edit-gone-missing' );
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ $dbw->begin();
+ $revisionId = $revision->insertOn( $dbw );
+
+ # Update page
+ #
+ # Note that we use $this->mLatest instead of fetching a value from the master DB
+ # during the course of this function. This makes sure that EditPage can detect
+ # edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
+ # before this function is called. A previous function used a separate query, this
+ # creates a window where concurrent edits can cause an ignored edit conflict.
+ $ok = $this->updateRevisionOn( $dbw, $revision, $this->mLatest );
+
+ 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 {
+ global $wgUseRCPatrol;
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
+ # Update recentchanges
+ if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
+ # Mark as patrolled if the user can do so
+ $patrolled = $wgUseRCPatrol && !count(
+ $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
+ # Add RC row to the DB
+ $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
+ $this->mLatest, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
+ $revisionId, $patrolled
+ );
+
+ # Log auto-patrolled edits
+ if ( $patrolled ) {
+ PatrolLog::record( $rc, true );
+ }
+ }
+ $user->incEditCount();
+ $dbw->commit();
+ }
+ }
+
+ if ( !$wgDBtransactions ) {
+ ignore_user_abort( $userAbort );
+ }
+
+ // Now that ignore_user_abort is restored, we can respond to fatal errors
+ if ( !$status->isOK() ) {
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ # Update links tables, site stats, etc.
+ $this->doEditUpdates( $revision, $user, array( 'changed' => $changed,
+ 'oldcountable' => $oldcountable ) );
+
+ if ( !$changed ) {
+ $status->warning( 'edit-no-change' );
+ $revision = null;
+ // Keep the same revision ID, but do some updates on it
+ $revisionId = $this->getLatest();
+ // Update page_touched, this is usually implicit in the page update
+ // Other cache updates are done in onArticleEdit()
+ $this->mTitle->invalidateCache();
+ }
+ } else {
+ # Create new article
+ $status->value['new'] = true;
+
+ $dbw->begin();
+
+ # Add the page record; stake our claim on this title!
+ # This will return false if the article already exists
+ $newid = $this->insertOn( $dbw );
+
+ if ( $newid === false ) {
+ $dbw->rollback();
+ $status->fatal( 'edit-already-exists' );
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ # Save the revision text...
+ $revision = new Revision( array(
+ 'page' => $newid,
+ 'comment' => $summary,
+ 'minor_edit' => $isminor,
+ 'text' => $text,
+ 'user' => $user->getId(),
+ 'user_text' => $user->getName(),
+ 'timestamp' => $now
+ ) );
+ $revisionId = $revision->insertOn( $dbw );
+
+ $this->mTitle->resetArticleID( $newid );
+ # Update the LinkCache. Resetting the Title ArticleID means it will rely on having that already cached
+ # @todo FIXME?
+ LinkCache::singleton()->addGoodLinkObj( $newid, $this->mTitle, strlen( $text ), (bool)Title::newFromRedirect( $text ), $revisionId );
+
+ # Update the page record with revision data
+ $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 ) && !count(
+ $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
+ # 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 );
+ }
+ }
+ $user->incEditCount();
+ $dbw->commit();
+
+ # Update links, etc.
+ $this->doEditUpdates( $revision, $user, array( 'created' => true ) );
+
+ wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $text, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+ }
+
+ # Do updates right now unless deferral was requested
+ if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
+ wfDoUpdates();
+ }
+
+ // Return the new revision (or null) to the caller
+ $status->value['revision'] = $revision;
+
+ wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
+
+ # Promote user to any groups they meet the criteria for
+ $user->addAutopromoteOnceGroups( 'onEdit' );
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * Update the article's restriction field, and leave a log entry.
+ *
+ * @param $limit Array: set of restriction keys
+ * @param $reason String
+ * @param &$cascade Integer. Set to false if cascading protection isn't allowed.
+ * @param $expiry Array: per restriction type expiration
+ * @param $user User The user updating the restrictions
+ * @return bool true on success
+ */
+ public function updateRestrictions(
+ $limit = array(), $reason = '', &$cascade = 0, $expiry = array(), User $user = null
+ ) {
+ global $wgUser, $wgContLang;
+ $user = is_null( $user ) ? $wgUser : $user;
+
+ $restrictionTypes = $this->mTitle->getRestrictionTypes();
+
+ $id = $this->mTitle->getArticleID();
+
+ if ( $id <= 0 ) {
+ wfDebug( "updateRestrictions failed: article id $id <= 0\n" );
+ return false;
+ }
+
+ if ( wfReadOnly() ) {
+ wfDebug( "updateRestrictions failed: read-only\n" );
+ return false;
+ }
+
+ if ( count( $this->mTitle->getUserPermissionsErrors( 'protect', $user ) ) ) {
+ wfDebug( "updateRestrictions failed: insufficient permissions\n" );
+ return false;
+ }
+
+ if ( !$cascade ) {
+ $cascade = false;
+ }
+
+ // Take this opportunity to purge out expired restrictions
+ Title::purgeExpiredRestrictions();
+
+ # @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
+ # we expect a single selection, but the schema allows otherwise.
+ $current = array();
+ $updated = self::flattenRestrictions( $limit );
+ $changed = false;
+
+ foreach ( $restrictionTypes as $action ) {
+ if ( isset( $expiry[$action] ) ) {
+ # Get current restrictions on $action
+ $aLimits = $this->mTitle->getRestrictions( $action );
+ $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".
+ if ( $aRChanged &&
+ $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] )
+ {
+ $changed = true;
+ }
+ }
+ }
+
+ $current = self::flattenRestrictions( $current );
+
+ $changed = ( $changed || $current != $updated );
+ $changed = $changed || ( $updated && $this->mTitle->areRestrictionsCascading() != $cascade );
+ $protect = ( $updated != '' );
+
+ # If nothing's changed, do nothing
+ if ( $changed ) {
+ if ( wfRunHooks( 'ArticleProtect', array( &$this, &$user, $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 ) ) {
+ $cascade = false;
+ }
+
+ $cascade_description = '';
+
+ if ( $cascade ) {
+ $cascade_description = ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']';
+ }
+
+ if ( $reason ) {
+ $comment .= ": $reason";
+ }
+
+ $editComment = $comment;
+ $encodedExpiry = array();
+ $protect_description = '';
+ foreach ( $limit as $action => $restrictions ) {
+ if ( !isset( $expiry[$action] ) )
+ $expiry[$action] = $dbw->getInfinity();
+
+ $encodedExpiry[$action] = $dbw->encodeExpiry( $expiry[$action] );
+ if ( $restrictions != '' ) {
+ $protect_description .= "[$action=$restrictions] (";
+ if ( $encodedExpiry[$action] != 'infinity' ) {
+ $protect_description .= wfMsgForContent( 'protect-expiring',
+ $wgContLang->timeanddate( $expiry[$action], false, false ) ,
+ $wgContLang->date( $expiry[$action], false, false ) ,
+ $wgContLang->time( $expiry[$action], false, false ) );
+ } else {
+ $protect_description .= wfMsgForContent( 'protect-expiry-indefinite' );
+ }
+
+ $protect_description .= ') ';
+ }
+ }
+ $protect_description = trim( $protect_description );
+
+ if ( $protect_description && $protect ) {
+ $editComment .= " ($protect_description)";
+ }
+
+ if ( $cascade ) {
+ $editComment .= "$cascade_description";
+ }
+
+ # Update restrictions table
+ foreach ( $limit as $action => $restrictions ) {
+ if ( $restrictions != '' ) {
+ $dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ),
+ array( 'pr_page' => $id,
+ 'pr_type' => $action,
+ 'pr_level' => $restrictions,
+ 'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
+ 'pr_expiry' => $encodedExpiry[$action]
+ ),
+ __METHOD__
+ );
+ } else {
+ $dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
+ 'pr_type' => $action ), __METHOD__ );
+ }
+ }
+
+ # Insert a null revision
+ $nullRevision = Revision::newNullRevision( $dbw, $id, $editComment, true );
+ $nullRevId = $nullRevision->insertOn( $dbw );
+
+ $latest = $this->getLatest();
+ # Update page record
+ $dbw->update( 'page',
+ array( /* SET */
+ 'page_touched' => $dbw->timestamp(),
+ 'page_restrictions' => '',
+ 'page_latest' => $nullRevId
+ ), array( /* WHERE */
+ 'page_id' => $id
+ ), __METHOD__
+ );
+
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
+ wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
+
+ # Update the protection log
+ $log = new LogPage( 'protect' );
+ if ( $protect ) {
+ $params = array( $protect_description, $cascade ? 'cascade' : '' );
+ $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason ), $params );
+ } else {
+ $log->addEntry( 'unprotect', $this->mTitle, $reason );
+ }
+ } # End hook
+ } # End "changed" check
+
+ return true;
+ }
+
+ /**
+ * Take an array of page restrictions and flatten it to a string
+ * suitable for insertion into the page_restrictions field.
+ * @param $limit Array
+ * @return String
+ */
+ protected static function flattenRestrictions( $limit ) {
+ if ( !is_array( $limit ) ) {
+ throw new MWException( 'WikiPage::flattenRestrictions given non-array restriction set' );
+ }
+
+ $bits = array();
+ ksort( $limit );
+
+ foreach ( $limit as $action => $restrictions ) {
+ if ( $restrictions != '' ) {
+ $bits[] = "$action=$restrictions";
+ }
+ }
+
+ return implode( ':', $bits );
+ }
+
+ /**
+ * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
+ */
+ public function isBigDeletion() {
+ global $wgDeleteRevisionsLimit;
+
+ if ( $wgDeleteRevisionsLimit ) {
+ $revCount = $this->estimateRevisionCount();
+
+ return $revCount > $wgDeleteRevisionsLimit;
+ }
+
+ return false;
+ }
+
+ /**
+ * @return int approximate revision count
+ */
+ public function estimateRevisionCount() {
+ $dbr = wfGetDB( DB_SLAVE );
+
+ // For an exact count...
+ // return $dbr->selectField( 'revision', 'COUNT(*)',
+ // array( 'rev_page' => $this->getId() ), __METHOD__ );
+ return $dbr->estimateRowCount( 'revision', '*',
+ array( 'rev_page' => $this->getId() ), __METHOD__ );
+ }
+
+ /**
+ * Get the last N authors
+ * @param $num Integer: number of revisions to get
+ * @param $revLatest String: the latest rev_id, selected from the master (optional)
+ * @return array Array of authors, duplicates not removed
+ */
+ public function getLastNAuthors( $num, $revLatest = 0 ) {
+ wfProfileIn( __METHOD__ );
+ // First try the slave
+ // If that doesn't have the latest revision, try the master
+ $continue = 2;
+ $db = wfGetDB( DB_SLAVE );
+
+ do {
+ $res = $db->select( array( 'page', 'revision' ),
+ array( 'rev_id', 'rev_user_text' ),
+ array(
+ 'page_namespace' => $this->mTitle->getNamespace(),
+ 'page_title' => $this->mTitle->getDBkey(),
+ 'rev_page = page_id'
+ ), __METHOD__,
+ array(
+ 'ORDER BY' => 'rev_timestamp DESC',
+ '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--;
+ } else {
+ $continue = 0;
+ }
+ } while ( $continue );
+
+ $authors = array( $row->rev_user_text );
+
+ foreach ( $res as $row ) {
+ $authors[] = $row->rev_user_text;
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $authors;
+ }
+
+ /**
+ * Back-end article deletion
+ * Deletes the article with database consistency, writes logs, purges caches
+ *
+ * @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
+ * @param &$errors Array of errors to append to
+ * @param $user User The relevant user
+ * @return boolean true if successful
+ */
+ public function doDeleteArticle(
+ $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+ ) {
+ global $wgDeferredUpdateList, $wgUseTrackbacks, $wgUser;
+ $user = is_null( $user ) ? $wgUser : $user;
+
+ wfDebug( __METHOD__ . "\n" );
+
+ if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error ) ) ) {
+ return false;
+ }
+ $dbw = wfGetDB( DB_MASTER );
+ $t = $this->mTitle->getDBkey();
+ $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
+
+ if ( $t === '' || $id == 0 ) {
+ return false;
+ }
+
+ $u = new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 );
+ array_push( $wgDeferredUpdateList, $u );
+
+ // Bitfields to further suppress the content
+ if ( $suppress ) {
+ $bitfield = 0;
+ // This should be 15...
+ $bitfield |= Revision::DELETED_TEXT;
+ $bitfield |= Revision::DELETED_COMMENT;
+ $bitfield |= Revision::DELETED_USER;
+ $bitfield |= Revision::DELETED_RESTRICTED;
+ } else {
+ $bitfield = 'rev_deleted';
+ }
+
+ $dbw->begin();
+ // For now, shunt the revision data into the archive table.
+ // Text is *not* removed from the text table; bulk storage
+ // is left intact to avoid breaking block-compression or
+ // immutable storage schemes.
+ //
+ // For backwards compatibility, note that some older archive
+ // table entries will have ar_text and ar_flags fields still.
+ //
+ // In the future, we may keep revisions and mark them with
+ // the rev_deleted field, which is reserved for this purpose.
+ $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
+ array(
+ 'ar_namespace' => 'page_namespace',
+ 'ar_title' => 'page_title',
+ 'ar_comment' => 'rev_comment',
+ 'ar_user' => 'rev_user',
+ 'ar_user_text' => 'rev_user_text',
+ 'ar_timestamp' => 'rev_timestamp',
+ 'ar_minor_edit' => 'rev_minor_edit',
+ 'ar_rev_id' => 'rev_id',
+ 'ar_text_id' => 'rev_text_id',
+ 'ar_text' => '\'\'', // Be explicit to appease
+ 'ar_flags' => '\'\'', // MySQL's "strict mode"...
+ 'ar_len' => 'rev_len',
+ 'ar_page_id' => 'page_id',
+ 'ar_deleted' => $bitfield
+ ), array(
+ 'page_id' => $id,
+ 'page_id = rev_page'
+ ), __METHOD__
+ );
+
+ # Delete restrictions for it
+ $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
+
+ # 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;
+ }
+
+ # 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
+ if ( !$dbw->cascadingDeletes() ) {
+ $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
+
+ if ( $wgUseTrackbacks )
+ $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
+
+ # Delete outgoing links
+ $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
+ $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
+ $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
+ $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 ) );
+ }
+
+ # If using cleanup triggers, we can skip some manual deletes
+ if ( !$dbw->cleanupTriggers() ) {
+ # Clean up recentchanges entries...
+ $dbw->delete( 'recentchanges',
+ array( 'rc_type != ' . RC_LOG,
+ 'rc_namespace' => $this->mTitle->getNamespace(),
+ 'rc_title' => $this->mTitle->getDBkey() ),
+ __METHOD__ );
+ $dbw->delete( 'recentchanges',
+ array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
+ __METHOD__ );
+ }
+
+ # Clear caches
+ self::onArticleDelete( $this->mTitle );
+
+ # Clear the cached article id so the interface doesn't act like we exist
+ $this->mTitle->resetArticleID( 0 );
+
+ # Log the deletion, if the page was suppressed, log it at Oversight instead
+ $logtype = $suppress ? 'suppress' : 'delete';
+ $log = new LogPage( $logtype );
+
+ # Make sure logging got through
+ $log->addEntry( 'delete', $this->mTitle, $reason, array() );
+
+ if ( $commit ) {
+ $dbw->commit();
+ }
+
+ wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
+ return true;
+ }
+
+ /**
+ * Roll back the most recent consecutive set of edits to a page
+ * from the same user; fails if there are no eligible edits to
+ * roll back to, e.g. user is the sole contributor. This function
+ * performs permissions checks on $user, then calls commitRollback()
+ * to do the dirty work
+ *
+ * @param $fromP String: Name of the user whose edits to rollback.
+ * @param $summary String: Custom summary. Set to default summary if empty.
+ * @param $token String: Rollback token.
+ * @param $bot Boolean: If true, mark all reverted edits as bot.
+ *
+ * @param $resultDetails Array: contains result-specific array of additional values
+ * 'alreadyrolled' : 'current' (rev)
+ * success : 'summary' (str), 'current' (rev), 'target' (rev)
+ *
+ * @param $user User The user performing the rollback
+ * @return array of errors, each error formatted as
+ * array(messagekey, param1, param2, ...).
+ * On success, the array is empty. This array can also be passed to
+ * OutputPage::showPermissionsErrorPage().
+ */
+ public function doRollback(
+ $fromP, $summary, $token, $bot, &$resultDetails, User $user
+ ) {
+ $resultDetails = null;
+
+ # Check permissions
+ $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user );
+ $rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $user );
+ $errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
+
+ if ( !$user->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) ) {
+ $errors[] = array( 'sessionfailure' );
+ }
+
+ if ( $user->pingLimiter( 'rollback' ) || $user->pingLimiter() ) {
+ $errors[] = array( 'actionthrottledtext' );
+ }
+
+ # If there were errors, bail out now
+ if ( !empty( $errors ) ) {
+ return $errors;
+ }
+
+ return $this->commitRollback( $fromP, $summary, $bot, $resultDetails, $user );
+ }
+
+ /**
+ * Backend implementation of doRollback(), please refer there for parameter
+ * and return value documentation
+ *
+ * NOTE: This function does NOT check ANY permissions, it just commits the
+ * rollback to the DB Therefore, you should only call this function direct-
+ * ly if you want to use custom permissions checks. If you don't, use
+ * doRollback() instead.
+ * @param $fromP String: Name of the user whose edits to rollback.
+ * @param $summary String: Custom summary. Set to default summary if empty.
+ * @param $bot Boolean: If true, mark all reverted edits as bot.
+ *
+ * @param $resultDetails Array: contains result-specific array of additional values
+ * @param $guser User The user performing the rollback
+ */
+ public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) {
+ global $wgUseRCPatrol, $wgContLang;
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ if ( wfReadOnly() ) {
+ return array( array( 'readonlytext' ) );
+ }
+
+ # Get the last editor
+ $current = Revision::newFromTitle( $this->mTitle );
+ if ( is_null( $current ) ) {
+ # Something wrong... no page?
+ return array( array( 'notanarticle' ) );
+ }
+
+ $from = str_replace( '_', ' ', $fromP );
+ # User name given should match up with the top revision.
+ # If the user was deleted then $from should be empty.
+ if ( $from != $current->getUserText() ) {
+ $resultDetails = array( 'current' => $current );
+ return array( array( 'alreadyrolled',
+ htmlspecialchars( $this->mTitle->getPrefixedText() ),
+ htmlspecialchars( $fromP ),
+ htmlspecialchars( $current->getUserText() )
+ ) );
+ }
+
+ # Get the last edit not by this guy...
+ # Note: these may not be public values
+ $user = intval( $current->getRawUser() );
+ $user_text = $dbw->addQuotes( $current->getRawUserText() );
+ $s = $dbw->selectRow( 'revision',
+ array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
+ array( 'rev_page' => $current->getPage(),
+ "rev_user != {$user} OR rev_user_text != {$user_text}"
+ ), __METHOD__,
+ array( 'USE INDEX' => 'page_timestamp',
+ 'ORDER BY' => 'rev_timestamp DESC' )
+ );
+ if ( $s === false ) {
+ # No one else ever edited this page
+ return array( array( 'cantrollback' ) );
+ } elseif ( $s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER ) {
+ # Only admins can see this text
+ return array( array( 'notvisiblerev' ) );
+ }
+
+ $set = array();
+ if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
+ # Mark all reverted edits as bot
+ $set['rc_bot'] = 1;
+ }
+
+ if ( $wgUseRCPatrol ) {
+ # Mark all reverted edits as patrolled
+ $set['rc_patrolled'] = 1;
+ }
+
+ if ( count( $set ) ) {
+ $dbw->update( 'recentchanges', $set,
+ array( /* WHERE */
+ 'rc_cur_id' => $current->getPage(),
+ 'rc_user_text' => $current->getUserText(),
+ "rc_timestamp > '{$s->rev_timestamp}'",
+ ), __METHOD__
+ );
+ }
+
+ # Generate the edit summary if necessary
+ $target = Revision::newFromId( $s->rev_id );
+ if ( empty( $summary ) ) {
+ if ( $from == '' ) { // no public user name
+ $summary = wfMsgForContent( 'revertpage-nouser' );
+ } else {
+ $summary = wfMsgForContent( 'revertpage' );
+ }
+ }
+
+ # Allow the custom summary to use the same args as the default message
+ $args = array(
+ $target->getUserText(), $from, $s->rev_id,
+ $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
+ $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
+ );
+ $summary = wfMsgReplaceArgs( $summary, $args );
+
+ # Save
+ $flags = EDIT_UPDATE;
+
+ if ( $guser->isAllowed( 'minoredit' ) ) {
+ $flags |= EDIT_MINOR;
+ }
+
+ if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
+ $flags |= EDIT_FORCE_BOT;
+ }
+
+ # Actually store the edit
+ $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
+ if ( !empty( $status->value['revision'] ) ) {
+ $revId = $status->value['revision']->getId();
+ } else {
+ $revId = false;
+ }
+
+ wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
+
+ $resultDetails = array(
+ 'summary' => $summary,
+ 'current' => $current,
+ 'target' => $target,
+ 'newid' => $revId
+ );
+
+ return array();
+ }
+
+ /**
+ * Do standard deferred updates after page view
+ * @param $user User The relevant user
+ */
+ public function doViewUpdates( User $user ) {
+ global $wgDeferredUpdateList, $wgDisableCounters;
+ if ( wfReadOnly() ) {
+ return;
+ }
+
+ # Don't update page view counters on views from bot users (bug 14044)
+ if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->getId() ) {
+ $wgDeferredUpdateList[] = new ViewCountUpdate( $this->getId() );
+ $wgDeferredUpdateList[] = new SiteStatsUpdate( 1, 0, 0 );
+ }
+
+ # Update newtalk / watchlist notification status
+ $user->clearNotification( $this->mTitle );
+ }
+
+ /**
+ * Prepare text which is about to be saved.
+ * Returns a stdclass with source, pst and output members
+ */
+ public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
+ global $wgParser, $wgUser;
+ $user = is_null( $user ) ? $wgUser : $user;
+ // @TODO fixme: check $user->getId() here???
+ if ( $this->mPreparedEdit
+ && $this->mPreparedEdit->newText == $text
+ && $this->mPreparedEdit->revid == $revid
+ ) {
+ // Already prepared
+ return $this->mPreparedEdit;
+ }
+
+ $popts = ParserOptions::newFromUser( $user );
+ wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
+
+ $edit = (object)array();
+ $edit->revid = $revid;
+ $edit->newText = $text;
+ $edit->pst = $this->preSaveTransform( $text, $user, $popts );
+ $edit->popts = $this->getParserOptions( true );
+ $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $edit->popts, true, true, $revid );
+ $edit->oldText = $this->getRawText();
+
+ $this->mPreparedEdit = $edit;
+
+ return $edit;
+ }
+
+ /**
+ * Do standard deferred updates after page edit.
+ * Update links tables, site stats, search index and message cache.
+ * Purges pages that include this page if the text was changed here.
+ * Every 100th edit, prune the recent changes table.
+ *
+ * @private
+ * @param $revision Revision object
+ * @param $user User object that did the revision
+ * @param $options Array of options, following indexes are used:
+ * - changed: boolean, whether the revision changed the content (default true)
+ * - created: boolean, whether the revision created the page (default false)
+ * - oldcountable: boolean or null (default null):
+ * - boolean: whether the page was counted as an article before that
+ * revision, only used in changed is true and created is false
+ * - null: don't change the article count
+ */
+ public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
+ global $wgDeferredUpdateList, $wgEnableParserCache;
+
+ wfProfileIn( __METHOD__ );
+
+ $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
+ $text = $revision->getText();
+
+ # Parse the text
+ # Be careful not to double-PST: $text is usually already PST-ed once
+ if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
+ wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
+ $editInfo = $this->prepareTextForEdit( $text, $revision->getId(), $user );
+ } else {
+ wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
+ $editInfo = $this->mPreparedEdit;
+ }
+
+ # Save it to the parser cache
+ if ( $wgEnableParserCache ) {
+ $parserCache = ParserCache::singleton();
+ $parserCache->save( $editInfo->output, $this, $editInfo->popts );
+ }
+
+ # Update the links tables
+ $u = new LinksUpdate( $this->mTitle, $editInfo->output );
+ $u->doUpdate();
+
+ wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
+
+ if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
+ if ( 0 == mt_rand( 0, 99 ) ) {
+ // Flush old entries from the `recentchanges` table; we do this on
+ // random requests so as to avoid an increase in writes for no good reason
+ global $wgRCMaxAge;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
+ $dbw->delete(
+ 'recentchanges',
+ array( "rc_timestamp < '$cutoff'" ),
+ __METHOD__
+ );
+ }
+ }
+
+ $id = $this->getId();
+ $title = $this->mTitle->getPrefixedDBkey();
+ $shortTitle = $this->mTitle->getDBkey();
+
+ if ( 0 == $id ) {
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
+ if ( !$options['changed'] ) {
+ $good = 0;
+ $total = 0;
+ } elseif ( $options['created'] ) {
+ $good = (int)$this->isCountable( $editInfo );
+ $total = 1;
+ } elseif ( $options['oldcountable'] !== null ) {
+ $good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
+ $total = 0;
+ } else {
+ $good = 0;
+ $total = 0;
+ }
+
+ $wgDeferredUpdateList[] = new SiteStatsUpdate( 0, 1, $good, $total );
+ $wgDeferredUpdateList[] = new SearchUpdate( $id, $title, $text );
+
+ # If this is another user's talk page, update newtalk.
+ # Don't do this if $options['changed'] = false (null-edits) nor if
+ # it's a minor edit and the user doesn't want notifications for those.
+ if ( $options['changed']
+ && $this->mTitle->getNamespace() == NS_USER_TALK
+ && $shortTitle != $user->getTitleKey()
+ && !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
+ ) {
+ if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this ) ) ) {
+ $other = User::newFromName( $shortTitle, false );
+ if ( !$other ) {
+ wfDebug( __METHOD__ . ": invalid username\n" );
+ } elseif ( User::isIP( $shortTitle ) ) {
+ // An anonymous user
+ $other->setNewtalk( true );
+ } elseif ( $other->isLoggedIn() ) {
+ $other->setNewtalk( true );
+ } else {
+ wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
+ }
+ }
+ }
+
+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ MessageCache::singleton()->replace( $shortTitle, $text );
+ }
+
+ if( $options['created'] ) {
+ self::onArticleCreate( $this->mTitle );
+ } else {
+ self::onArticleEdit( $this->mTitle );
+ }
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Perform article updates on a special page creation.
+ *
+ * @param $rev Revision object
+ *
+ * @todo This is a shitty interface function. Kill it and replace the
+ * other shitty functions like doEditUpdates and such so it's not needed
+ * anymore.
+ * @deprecated since 1.18, use doEditUpdates()
+ */
+ public function createUpdates( $rev ) {
+ global $wgUser;
+ $this->doEditUpdates( $rev, $wgUser, array( 'created' => true ) );
+ }
+
+ /**
+ * This function is called right before saving the wikitext,
+ * so we can do things like signatures and links-in-context.
+ *
+ * @param $text String article contents
+ * @param $user User object: user doing the edit
+ * @param $popts ParserOptions object: parser options, default options for
+ * the user loaded if null given
+ * @return string article contents with altered wikitext markup (signatures
+ * converted, {{subst:}}, templates, etc.)
+ */
+ public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
+ global $wgParser, $wgUser;
+ $user = is_null( $user ) ? $wgUser : $user;
+
+ if ( $popts === null ) {
+ $popts = ParserOptions::newFromUser( $user );
+ }
+
+ return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+ }
+
+ /**
+ * 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() {
+ 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;
+ }
+
+ /**
+ * Edit an article without doing all that other stuff
+ * The article must already exist; link tables etc
+ * are not updated, caches are not flushed.
+ *
+ * @param $text String: text submitted
+ * @param $user User The relevant user
+ * @param $comment String: comment submitted
+ * @param $minor Boolean: whereas it's a minor modification
+ */
+ public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
+ wfProfileIn( __METHOD__ );
+
+ $dbw = wfGetDB( DB_MASTER );
+ $revision = new Revision( array(
+ 'page' => $this->getId(),
+ 'text' => $text,
+ 'comment' => $comment,
+ 'minor_edit' => $minor ? 1 : 0,
+ ) );
+ $revision->insertOn( $dbw );
+ $this->updateRevisionOn( $dbw, $revision );
+
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * The onArticle*() functions are supposed to be a kind of hooks
+ * which should be called whenever any of the specified actions
+ * are done.
+ *
+ * This is a good place to put code to clear caches, for instance.
+ *
+ * This is called on page move and undelete, as well as edit
+ *
+ * @param $title Title object
+ */
+ public static function onArticleCreate( $title ) {
+ # Update existence markers on article/talk tabs...
+ if ( $title->isTalkPage() ) {
+ $other = $title->getSubjectPage();
+ } else {
+ $other = $title->getTalkPage();
+ }
+
+ $other->invalidateCache();
+ $other->purgeSquid();
+
+ $title->touchLinks();
+ $title->purgeSquid();
+ $title->deleteTitleProtection();
+ }
+
+ /**
+ * Clears caches when article is deleted
+ *
+ * @param $title Title
+ */
+ public static function onArticleDelete( $title ) {
+ # Update existence markers on article/talk tabs...
+ if ( $title->isTalkPage() ) {
+ $other = $title->getSubjectPage();
+ } else {
+ $other = $title->getTalkPage();
+ }
+
+ $other->invalidateCache();
+ $other->purgeSquid();
+
+ $title->touchLinks();
+ $title->purgeSquid();
+
+ # File cache
+ HTMLFileCache::clearFileCache( $title );
+
+ # Messages
+ if ( $title->getNamespace() == NS_MEDIAWIKI ) {
+ MessageCache::singleton()->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 ) {
+ global $wgDeferredUpdateList;
+
+ // Invalidate caches of articles which include this page
+ $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
+
+ // Invalidate the caches of all pages which redirect here
+ $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
+
+ # Purge squid for this page only
+ $title->purgeSquid();
+
+ # Clear file cache for this page only
+ HTMLFileCache::clearFileCache( $title );
+ }
+
+ /**#@-*/
+
+ /**
+ * Return a list of templates used by this article.
+ * Uses the templatelinks table
+ *
+ * @return Array of Title objects
+ */
+ 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 );
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns a list of hidden categories this page is a member of.
+ * Uses the page_props and categorylinks tables.
+ *
+ * @return Array of Title objects
+ */
+ 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 );
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Return an applicable autosummary if one exists for the given edit.
+ * @param $oldtext String: the previous text of the page.
+ * @param $newtext String: The submitted text of the page.
+ * @param $flags Int bitmask: a bitmask of flags submitted for the edit.
+ * @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() ) ) {
+ $truncatedtext = $wgContLang->truncate(
+ str_replace( "\n", ' ', $newtext ),
+ max( 0, 250
+ - strlen( wfMsgForContent( 'autoredircomment' ) )
+ - strlen( $rt->getFullText() )
+ ) );
+ return wfMsgForContent( 'autoredircomment', $rt->getFullText(), $truncatedtext );
+ }
+
+ # New page autosummaries
+ if ( $flags & EDIT_NEW && strlen( $newtext ) ) {
+ # If they're making a new article, give its text, truncated, in the summary.
+
+ $truncatedtext = $wgContLang->truncate(
+ str_replace( "\n", ' ', $newtext ),
+ max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) );
+
+ return wfMsgForContent( 'autosumm-new', $truncatedtext );
+ }
+
+ # Blanking autosummaries
+ if ( $oldtext != '' && $newtext == '' ) {
+ return wfMsgForContent( 'autosumm-blank' );
+ } elseif ( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500 ) {
+ # Removing more than 90% of the article
+
+ $truncatedtext = $wgContLang->truncate(
+ $newtext,
+ max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) );
+
+ return wfMsgForContent( 'autosumm-replace', $truncatedtext );
+ }
+
+ # If we reach this point, there's no applicable autosummary for our case, so our
+ # autosummary is empty.
+ return '';
+ }
+
+ /**
+ * 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 getAutoDeleteReason( &$hasHistory ) {
+ global $wgContLang;
+
+ $dbw = wfGetDB( DB_MASTER );
+ // Get the last revision
+ $rev = Revision::newFromTitle( $this->getTitle() );
+
+ 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;
+ }
+ }
+
+ // Find out if there was only one contributor
+ // Only scan the last 20 revisions
+ $res = $dbw->select( 'revision', 'rev_user_text',
+ array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
+ __METHOD__,
+ array( 'LIMIT' => 20 )
+ );
+
+ if ( $res === false ) {
+ // This page has no revisions, which is very weird
+ return false;
+ }
+
+ $hasHistory = ( $res->numRows() > 1 );
+ $row = $dbw->fetchObject( $res );
+
+ 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;
+ }
+
+ // Generate the summary with a '$1' placeholder
+ if ( $blank ) {
+ // The current revision is blank and the one before is also
+ // blank. It's just not our lucky day
+ $reason = wfMsgForContent( 'exbeforeblank', '$1' );
+ } else {
+ if ( $onlyAuthor ) {
+ $reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor );
+ } else {
+ $reason = wfMsgForContent( 'excontent', '$1' );
+ }
+ }
+
+ if ( $reason == '-' ) {
+ // Allow these UI messages to be blanked out cleanly
+ return '';
+ }
+
+ // Replace newlines with spaces to prevent uglyness
+ $contents = preg_replace( "/[\n\r]/", ' ', $contents );
+ // Calculate the maximum amount of chars to get
+ // Max content length = max comment length - length of the comment (excl. $1)
+ $maxLength = 255 - ( strlen( $reason ) - 2 );
+ $contents = $wgContLang->truncate( $contents, $maxLength );
+ // Remove possible unfinished links
+ $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
+ // Now replace the '$1' placeholder
+ $reason = str_replace( '$1', $contents, $reason );
+
+ return $reason;
+ }
+
+ /**
+ * Get parser options suitable for rendering the primary article wikitext
+ * @param $canonical boolean Determines that the generated options must not depend on user preferences (see bug 14404)
+ * @return mixed ParserOptions object or boolean false
+ */
+ public function getParserOptions( $canonical = false ) {
+ global $wgUser, $wgLanguageCode;
+
+ if ( !$this->mParserOptions || $canonical ) {
+ $user = !$canonical ? $wgUser : new User;
+ $parserOptions = new ParserOptions( $user );
+ $parserOptions->setTidy( true );
+ $parserOptions->enableLimitReport();
+
+ if ( $canonical ) {
+ $parserOptions->setUserLang( $wgLanguageCode ); # Must be set explicitely
+ return $parserOptions;
+ }
+ $this->mParserOptions = $parserOptions;
+ }
+ // Clone to allow modifications of the return value without affecting cache
+ return clone $this->mParserOptions;
+ }
+
+ /**
+ * Get parser options suitable for rendering the primary article wikitext
+ * @param User $user
+ * @return ParserOptions
+ */
+ public function makeParserOptions( User $user ) {
+ $options = ParserOptions::newFromUser( $user );
+ $options->enableLimitReport(); // show inclusion/loop reports
+ $options->setTidy( true ); // fix bad HTML
+ return $options;
+ }
+
+ /**
+ * Update all the appropriate counts in the category table, given that
+ * we've added the categories $added and deleted the categories $deleted.
+ *
+ * @param $added array The names of categories that were added
+ * @param $deleted array The names of categories that were deleted
+ */
+ public function updateCategoryCounts( $added, $deleted ) {
+ $ns = $this->mTitle->getNamespace();
+ $dbw = wfGetDB( DB_MASTER );
+
+ # First make sure the rows exist. If one of the "deleted" ones didn't
+ # exist, we might legitimately not create it, but it's simpler to just
+ # create it and then give it a negative value, since the value is bogus
+ # anyway.
+ #
+ # Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE.
+ $insertCats = array_merge( $added, $deleted );
+ if ( !$insertCats ) {
+ # Okay, nothing to do
+ return;
+ }
+
+ $insertRows = array();
+
+ foreach ( $insertCats as $cat ) {
+ $insertRows[] = array(
+ 'cat_id' => $dbw->nextSequenceValue( 'category_cat_id_seq' ),
+ 'cat_title' => $cat
+ );
+ }
+ $dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' );
+
+ $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';
+ } elseif ( $ns == NS_FILE ) {
+ $addFields[] = 'cat_files = cat_files + 1';
+ $removeFields[] = 'cat_files = cat_files - 1';
+ }
+
+ if ( $added ) {
+ $dbw->update(
+ 'category',
+ $addFields,
+ array( 'cat_title' => $added ),
+ __METHOD__
+ );
+ }
+
+ if ( $deleted ) {
+ $dbw->update(
+ 'category',
+ $removeFields,
+ array( 'cat_title' => $deleted ),
+ __METHOD__
+ );
+ }
+ }
+
+ /**
+ * Updates cascading protections
+ *
+ * @param $parserOutput ParserOutput object for the current version
+ **/
+ public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
+ if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
+ return;
+ }
+
+ // templatelinks table may have become out of sync,
+ // especially if using variable-based transclusions.
+ // For paranoia, check if things have changed and if
+ // so apply updates to the database. This will ensure
+ // that cascaded protections apply as soon as the changes
+ // are visible.
+
+ # Get templates from templatelinks
+ $id = $this->mTitle->getArticleID();
+
+ $tlTemplates = array();
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( array( 'templatelinks' ),
+ array( 'tl_namespace', 'tl_title' ),
+ array( 'tl_from' => $id ),
+ __METHOD__
+ );
+
+ foreach ( $res as $row ) {
+ $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
+ }
+
+ # Get templates from parser output.
+ $poTemplates = array();
+ foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
+ foreach ( $templates as $dbk => $id ) {
+ $poTemplates["$ns:$dbk"] = true;
+ }
+ }
+
+ # Get the diff
+ $templates_diff = array_diff_key( $poTemplates, $tlTemplates );
+
+ if ( count( $templates_diff ) > 0 ) {
+ # Whee, link updates time.
+ $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
+ $u->doUpdate();
+ }
+ }
+
+ /*
+ * @deprecated since 1.18
+ */
+ public function quickEdit( $text, $comment = '', $minor = 0 ) {
+ global $wgUser;
+ return $this->doQuickEdit( $text, $wgUser, $comment, $minor );
+ }
+
+ /*
+ * @deprecated since 1.18
+ */
+ public function viewUpdates() {
+ global $wgUser;
+ return $this->doViewUpdates( $wgUser );
+ }
+
+ /*
+ * @deprecated since 1.18
+ */
+ public function useParserCache( $oldid ) {
+ global $wgUser;
+ return $this->isParserCacheUsed( $wgUser, $oldid );
+ }
+}
diff --git a/includes/Xml.php b/includes/Xml.php
index 970444fa..45ee8720 100644
--- a/includes/Xml.php
+++ b/includes/Xml.php
@@ -191,7 +191,7 @@ class Xml {
}
if( $year ) {
$encYear = intval( $year );
- } else if( $encMonth ) {
+ } elseif( $encMonth ) {
$thisMonth = intval( gmdate( 'n' ) );
$thisYear = intval( gmdate( 'Y' ) );
if( intval($encMonth) > $thisMonth ) {
@@ -209,8 +209,8 @@ class Xml {
/**
*
- * @param $selected The language code of the selected language
- * @param $customisedOnly If true only languages which have some content are listed
+ * @param $selected string The language code of the selected language
+ * @param $customisedOnly bool If true only languages which have some content are listed
* @return array of label and select
*/
public static function languageSelector( $selected, $customisedOnly = true ) {
@@ -250,10 +250,10 @@ class Xml {
* Shortcut to make a 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
+ * @param $attribs array other attributes
* @return string
*/
- public static function span( $text, $class, $attribs=array() ) {
+ public static function span( $text, $class, $attribs = array() ) {
return self::element( 'span', array( 'class' => $class ) + $attribs, $text );
}
@@ -261,23 +261,23 @@ class Xml {
* Shortcut to make a specific element with a class attribute
* @param $text content of the element, will be escaped
* @param $class class name of the span element
- * @param $tag element name
- * @param $attribs other attributes
+ * @param $tag string element name
+ * @param $attribs array other attributes
* @return string
*/
- public static function wrapClass( $text, $class, $tag='span', $attribs=array() ) {
+ public static function wrapClass( $text, $class, $tag = 'span', $attribs = array() ) {
return self::tags( $tag, array( 'class' => $class ) + $attribs, $text );
}
/**
* Convenience function to build an HTML text input field
* @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
+ * @param $size int value of the size attribute
+ * @param $value mixed value of the value attribute
+ * @param $attribs array other attributes
* @return string HTML
*/
- public static function input( $name, $size=false, $value=false, $attribs=array() ) {
+ public static function input( $name, $size = false, $value = false, $attribs = array() ) {
$attributes = array( 'name' => $name );
if( $size ) {
@@ -293,18 +293,22 @@ class Xml {
/**
* Convenience function to build an HTML password input field
- * @param $name value of the name attribute
- * @param $size value of the size attribute
- * @param $value value of the value attribute
- * @param $attribs other attributes
+ * @param $name string value of the name attribute
+ * @param $size int value of the size attribute
+ * @param $value mixed value of the value attribute
+ * @param $attribs array other attributes
* @return string HTML
*/
- public static function password( $name, $size=false, $value=false, $attribs=array() ) {
- return self::input( $name, $size, $value, array_merge($attribs, array('type' => 'password')));
+ public static function password( $name, $size = false, $value = false, $attribs = array() ) {
+ return self::input( $name, $size, $value, array_merge( $attribs, array( 'type' => 'password' ) ) );
}
/**
* Internal function for use in checkboxes and radio buttons and such.
+ *
+ * @param $name string
+ * @param $present bool
+ *
* @return array
*/
public static function attrib( $name, $present = true ) {
@@ -318,7 +322,7 @@ class Xml {
* @param $attribs Array other attributes
* @return string HTML
*/
- public static function check( $name, $checked=false, $attribs=array() ) {
+ public static function check( $name, $checked = false, $attribs=array() ) {
return self::element( 'input', array_merge(
array(
'name' => $name,
@@ -336,7 +340,7 @@ class Xml {
* @param $attribs other attributes
* @return string HTML
*/
- public static function radio( $name, $value, $checked=false, $attribs=array() ) {
+ public static function radio( $name, $value, $checked = false, $attribs = array() ) {
return self::element( 'input', array(
'name' => $name,
'type' => 'radio',
@@ -347,17 +351,23 @@ class Xml {
* Convenience function to build an HTML form label
* @param $label String text of the label
* @param $id
- * @param $attribs Array an attribute array. This will usuall be
+ * @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.
+ * so this function will cherry-pick appropriate attributes to
+ * apply to the label as well; only class and title are applied.
* @return string HTML
*/
- public static function label( $label, $id, $attribs=array() ) {
+ public static function label( $label, $id, $attribs = array() ) {
$a = array( 'for' => $id );
+
+ # FIXME avoid copy pasting below:
if( isset( $attribs['class'] ) ){
$a['class'] = $attribs['class'];
}
+ if( isset( $attribs['title'] ) ){
+ $a['title'] = $attribs['title'];
+ }
+
return self::element( 'label', $a, $label );
}
@@ -366,20 +376,29 @@ class Xml {
* @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 $size int value of the size attribute
* @param $value value of the value attribute
- * @param $attribs other attributes
+ * @param $attribs array other attributes
* @return string HTML
*/
- public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
+ public static function inputLabel( $label, $name, $id, $size=false, $value=false, $attribs = array() ) {
list( $label, $input ) = self::inputLabelSep( $label, $name, $id, $size, $value, $attribs );
return $label . '&#160;' . $input;
}
/**
* Same as Xml::inputLabel() but return input and label in an array
+ *
+ * @param $label
+ * @param $name
+ * @param $id
+ * @param $size
+ * @param $value
+ * @param $attribs array
+ *
+ * @return array
*/
- public static function inputLabelSep( $label, $name, $id, $size=false, $value=false, $attribs=array() ) {
+ public static function inputLabelSep( $label, $name, $id, $size = false, $value = false, $attribs = array() ) {
return array(
Xml::label( $label, $id, $attribs ),
self::input( $name, $size, $value, array( 'id' => $id ) + $attribs )
@@ -388,9 +407,16 @@ class Xml {
/**
* Convenience function to build an HTML checkbox with a label
+ *
+ * @param $label
+ * @param $name
+ * @param $id
+ * @param $checked bool
+ * @param $attribs array
+ *
* @return string HTML
*/
- public static function checkLabel( $label, $name, $id, $checked=false, $attribs=array() ) {
+ public static function checkLabel( $label, $name, $id, $checked = false, $attribs = array() ) {
return self::check( $name, $checked, array( 'id' => $id ) + $attribs ) .
'&#160;' .
self::label( $label, $id, $attribs );
@@ -398,9 +424,17 @@ class Xml {
/**
* Convenience function to build an HTML radio button with a label
+ *
+ * @param $label
+ * @param $name
+ * @param $value
+ * @param $id
+ * @param $checked bool
+ * @param $attribs array
+ *
* @return string HTML
*/
- public static function radioLabel( $label, $name, $value, $id, $checked=false, $attribs=array() ) {
+ public static function radioLabel( $label, $name, $value, $id, $checked = false, $attribs = array() ) {
return self::radio( $name, $value, $checked, array( 'id' => $id ) + $attribs ) .
'&#160;' .
self::label( $label, $id, $attribs );
@@ -412,18 +446,11 @@ class Xml {
* @param $attribs Array: optional custom attributes
* @return string HTML
*/
- public static function submitButton( $value, $attribs=array() ) {
+ public static function submitButton( $value, $attribs = array() ) {
return Html::element( 'input', array( 'type' => 'submit', 'value' => $value ) + $attribs );
}
/**
- * @deprecated Synonymous to Html::hidden()
- */
- public static function hidden( $name, $value, $attribs = array() ) {
- return Html::hidden( $name, $value, $attribs );
- }
-
- /**
* Convenience function to build an HTML drop-down list item.
* @param $text String: text for this item
* @param $value String: form submission value; if empty, use text
@@ -431,8 +458,8 @@ class Xml {
* @param $attribs array: optional additional HTML attributes
* @return string HTML
*/
- public static function option( $text, $value=null, $selected=false,
- $attribs=array() ) {
+ public static function option( $text, $value=null, $selected = false,
+ $attribs = array() ) {
if( !is_null( $value ) ) {
$attribs['value'] = $value;
}
@@ -446,14 +473,14 @@ class Xml {
* Build a drop-down box from a textual list.
*
* @param $name Mixed: Name and id for the drop-down
- * @param $class Mixed: CSS classes for the drop-down
+ * @param $list Mixed: Correctly formatted text (newline delimited) to be used to generate the options
* @param $other Mixed: Text for the "Other reasons" option
- * @param $list Mixed: Correctly formatted text to be used to generate the options
* @param $selected Mixed: Option which should be pre-selected
+ * @param $class Mixed: CSS classes for the drop-down
* @param $tabindex Mixed: Value of the tabindex attribute
* @return string
*/
- public static function listDropDown( $name= '', $list = '', $other = '', $selected = '', $class = '', $tabindex = Null ) {
+ public static function listDropDown( $name= '', $list = '', $other = '', $selected = '', $class = '', $tabindex = null ) {
$optgroup = false;
$options = self::option( $other, 'other', $selected === 'other' );
@@ -504,7 +531,9 @@ class Xml {
*
* @param $legend Legend of the fieldset. If evaluates to false, legend is not added.
* @param $content Pre-escaped content for the fieldset. If false, only open fieldset is returned.
- * @param $attribs Any attributes to fieldset-element.
+ * @param $attribs array Any attributes to fieldset-element.
+ *
+ * @return string
*/
public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
$s = Xml::openElement( 'fieldset', $attribs ) . "\n";
@@ -522,11 +551,13 @@ class Xml {
/**
* Shortcut for creating textareas.
*
- * @param $name The 'name' for the textarea
- * @param $content Content for the textarea
- * @param $cols The number of columns for the textarea
- * @param $rows The number of rows for the textarea
- * @param $attribs Any other attributes for the textarea
+ * @param $name string The 'name' for the textarea
+ * @param $content string Content for the textarea
+ * @param $cols int The number of columns for the textarea
+ * @param $rows int The number of rows for the textarea
+ * @param $attribs array Any other attributes for the textarea
+ *
+ * @return string
*/
public static function textarea( $name, $content, $cols = 40, $rows = 5, $attribs = array() ) {
return self::element( 'textarea',
@@ -577,6 +608,10 @@ class Xml {
* Arrays are converted to JS arrays, objects are converted to JS associative
* arrays (objects). So cast your PHP associative arrays to objects before
* passing them to here.
+ *
+ * @param $value
+ *
+ * @return string
*/
public static function encodeJsVar( $value ) {
if ( is_bool( $value ) ) {
@@ -617,13 +652,16 @@ class Xml {
}
/**
- * Create a call to a JavaScript function. The supplied arguments will be
- * encoded using Xml::encodeJsVar().
+ * 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
+ * @param $name String 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
+ *
+ * @return string
*/
public static function encodeJsCall( $name, $args ) {
$s = "$name(";
@@ -733,9 +771,9 @@ class Xml {
/**
* Build a table of data
- * @param $rows An array of arrays of strings, each to be a row in a table
- * @param $attribs An array of attributes to apply to the table tag [optional]
- * @param $headers An array of strings to use as table headers [optional]
+ * @param $rows array An array of arrays of strings, each to be a row in a table
+ * @param $attribs array An array of attributes to apply to the table tag [optional]
+ * @param $headers array An array of strings to use as table headers [optional]
* @return string
*/
public static function buildTable( $rows, $attribs = array(), $headers = null ) {
@@ -791,14 +829,25 @@ class XmlSelect {
}
}
+ /**
+ * @param $default
+ */
public function setDefault( $default ) {
$this->default = $default;
}
+ /**
+ * @param $name string
+ * @param $value
+ */
public function setAttribute( $name, $value ) {
$this->attributes[$name] = $value;
}
+ /**
+ * @param $name
+ * @return array|null
+ */
public function getAttribute( $name ) {
if ( isset($this->attributes[$name]) ) {
return $this->attributes[$name];
@@ -807,22 +856,36 @@ class XmlSelect {
}
}
+ /**
+ * @param $name
+ * @param $value bool
+ */
public function addOption( $name, $value = false ) {
// Stab stab stab
$value = ($value !== false) ? $value : $name;
- $this->options[] = Xml::option( $name, $value, $value === $this->default );
+ $this->options[] = array( $name => $value );
}
- // This accepts an array of form
- // label => value
- // label => ( label => value, label => value )
+ /**
+ * This accepts an array of form
+ * label => value
+ * label => ( label => value, label => value )
+ *
+ * @param $options
+ */
public function addOptions( $options ) {
- $this->options[] = trim(self::formatOptions( $options, $this->default ));
+ $this->options[] = $options;
}
- // This accepts an array of form
- // label => value
- // label => ( label => value, label => value )
+ /**
+ * This accepts an array of form
+ * label => value
+ * label => ( label => value, label => value )
+ *
+ * @param $options
+ * @param bool $default
+ * @return string
+ */
static function formatOptions( $options, $default = false ) {
$data = '';
foreach( $options as $label => $value ) {
@@ -837,15 +900,22 @@ class XmlSelect {
return $data;
}
+ /**
+ * @return string
+ */
public function getHTML() {
- return Xml::tags( 'select', $this->attributes, implode( "\n", $this->options ) );
+ $contents = '';
+ foreach ( $this->options as $options ) {
+ $contents .= self::formatOptions( $options, $this->default );
+ }
+ return Xml::tags( 'select', $this->attributes, rtrim( $contents ) );
}
}
/**
- * A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to
- * interpret a given string as being a JavaScript expression, instead of string
+ * 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:
diff --git a/includes/XmlTypeCheck.php b/includes/XmlTypeCheck.php
index a004ef4d..78dd259d 100644
--- a/includes/XmlTypeCheck.php
+++ b/includes/XmlTypeCheck.php
@@ -34,11 +34,16 @@ class XmlTypeCheck {
/**
* Get the root element. Simple accessor to $rootElement
+ *
+ * @return string
*/
public function getRootElement() {
return $this->rootElement;
}
+ /**
+ * @param $fname
+ */
private function run( $fname ) {
$parser = xml_parser_create_ns( 'UTF-8' );
@@ -65,6 +70,11 @@ class XmlTypeCheck {
xml_parser_free( $parser );
}
+ /**
+ * @param $parser
+ * @param $name
+ * @param $attribs
+ */
private function rootElementOpen( $parser, $name, $attribs ) {
$this->rootElement = $name;
@@ -76,7 +86,12 @@ class XmlTypeCheck {
xml_set_element_handler( $parser, false, false );
}
}
-
+
+ /**
+ * @param $parser
+ * @param $name
+ * @param $attribs
+ */
private function elementOpen( $parser, $name, $attribs ) {
if( call_user_func( $this->filterCallback, $name, $attribs ) ) {
// Filter hit!
diff --git a/includes/ZhClient.php b/includes/ZhClient.php
index a04220c6..8bb36b23 100644
--- a/includes/ZhClient.php
+++ b/includes/ZhClient.php
@@ -2,7 +2,6 @@
/**
* Client for querying zhdaemon
- *
*/
class ZhClient {
var $mHost, $mPort, $mFP, $mConnected;
@@ -10,9 +9,12 @@ class ZhClient {
/**
* Constructor
*
- * @access private
+ * @param $host
+ * @param $port
+ *
+ * @return ZhClient
*/
- function __construct($host, $port) {
+ function __construct( $host, $port ) {
$this->mHost = $host;
$this->mPort = $port;
$this->mConnected = $this->connect();
@@ -20,6 +22,8 @@ class ZhClient {
/**
* Check if connection to zhdaemon is successful
+ *
+ * @return bool
*/
function isconnected() {
return $this->mConnected;
@@ -29,11 +33,13 @@ class ZhClient {
* Establish conncetion
*
* @access private
+ *
+ * @return bool
*/
function connect() {
wfSuppressWarnings();
$errno = $errstr = '';
- $this->mFP = fsockopen($this->mHost, $this->mPort, $errno, $errstr, 30);
+ $this->mFP = fsockopen( $this->mHost, $this->mPort, $errno, $errstr, 30 );
wfRestoreWarnings();
if ( !$this->mFP ) {
return false;
@@ -45,31 +51,33 @@ class ZhClient {
* Query the daemon and return the result
*
* @access private
+ *
+ * @return string
*/
- function query($request) {
+ function query( $request ) {
if ( !$this->mConnected ) {
return false;
}
- fwrite($this->mFP, $request);
+ fwrite( $this->mFP, $request );
- $result=fgets($this->mFP, 1024);
+ $result = fgets( $this->mFP, 1024 );
- list($status, $len) = explode(" ", $result);
- if($status == 'ERROR') {
- //$len is actually the error code...
+ list( $status, $len ) = explode( ' ', $result );
+ if( $status == 'ERROR' ) {
+ // $len is actually the error code...
print "zhdaemon error $len<br />\n";
return false;
}
- $bytesread=0;
- $data='';
- while(!feof($this->mFP) && $bytesread<$len) {
- $str= fread($this->mFP, $len-$bytesread);
- $bytesread += strlen($str);
+ $bytesread = 0;
+ $data = '';
+ while( !feof( $this->mFP ) && $bytesread < $len ) {
+ $str = fread( $this->mFP, $len - $bytesread );
+ $bytesread += strlen( $str );
$data .= $str;
}
- //data should be of length $len. otherwise something is wrong
- if ( strlen($data) != $len ) {
+ // data should be of length $len. otherwise something is wrong
+ if ( strlen( $data ) != $len ) {
return false;
}
return $data;
@@ -78,14 +86,14 @@ class ZhClient {
/**
* Convert the input to a different language variant
*
- * @param $text string: input text
- * @param $tolang string: language variant
+ * @param $text String: input text
+ * @param $tolang String: language variant
* @return string the converted text
*/
- function convert($text, $tolang) {
- $len = strlen($text);
+ function convert( $text, $tolang ) {
+ $len = strlen( $text );
$q = "CONV $tolang $len\n$text";
- $result = $this->query($q);
+ $result = $this->query( $q );
if ( !$result ) {
$result = $text;
}
@@ -95,39 +103,39 @@ class ZhClient {
/**
* Convert the input to all possible variants
*
- * @param $text string: input text
+ * @param $text String: input text
* @return array langcode => converted_string
*/
- function convertToAllVariants($text) {
- $len = strlen($text);
+ function convertToAllVariants( $text ) {
+ $len = strlen( $text );
$q = "CONV ALL $len\n$text";
- $result = $this->query($q);
+ $result = $this->query( $q );
if ( !$result ) {
return false;
}
- list($infoline, $data) = explode('|', $result, 2);
- $info = explode(";", $infoline);
+ list( $infoline, $data ) = explode( '|', $result, 2 );
+ $info = explode( ';', $infoline );
$ret = array();
- $i=0;
- foreach($info as $variant) {
- list($code, $len) = explode(' ', $variant);
- $ret[strtolower($code)] = substr($data, $i, $len);
- $i+=$len;
+ $i = 0;
+ foreach( $info as $variant ) {
+ list( $code, $len ) = explode( ' ', $variant );
+ $ret[strtolower( $code )] = substr( $data, $i, $len );
+ $i += $len;
}
return $ret;
}
/**
* Perform word segmentation
*
- * @param $text string: input text
+ * @param $text String: input text
* @return string segmented text
*/
- function segment($text) {
- $len = strlen($text);
+ function segment( $text ) {
+ $len = strlen( $text );
$q = "SEG $len\n$text";
- $result = $this->query($q);
- if ( !$result ) {// fallback to character based segmentation
- $result = $this->segment($text);
+ $result = $this->query( $q );
+ if ( !$result ) { // fallback to character based segmentation
+ $result = $this->segment( $text );
}
return $result;
}
@@ -136,6 +144,6 @@ class ZhClient {
* Close the connection
*/
function close() {
- fclose($this->mFP);
+ fclose( $this->mFP );
}
}
diff --git a/includes/ZhConversion.php b/includes/ZhConversion.php
index 7ffdeab8..58bc98c9 100644
--- a/includes/ZhConversion.php
+++ b/includes/ZhConversion.php
@@ -1720,7 +1720,6 @@ $zh2Hant = array(
'躏' => '躪',
'躜' => '躦',
'躯' => '軀',
-'軿' => '𫚒',
'车' => '車',
'轧' => '軋',
'轨' => '軌',
@@ -2950,6 +2949,7 @@ $zh2Hant = array(
'不占卜' => '不占卜',
'不占吉凶' => '不占吉凶',
'不占算' => '不占算',
+'不只' => '不只',
'不好干涉' => '不好干涉',
'不好干预' => '不好干預',
'不好干預' => '不好干預',
@@ -3028,6 +3028,7 @@ $zh2Hant = array(
'中签' => '中籤',
'中美发表' => '中美發表',
'中药' => '中藥',
+'中西合并' => '中西合併',
'中风后' => '中風後',
'丰儀' => '丰儀',
'丰仪' => '丰儀',
@@ -3372,6 +3373,7 @@ $zh2Hant = array(
'于正昇' => '于正昇',
'于正昌' => '于正昌',
'于归' => '于歸',
+'于氏' => '于氏',
'于永波' => '于永波',
'于江震' => '于江震',
'于波' => '于波',
@@ -3518,6 +3520,7 @@ $zh2Hant = array(
'什锦面' => '什錦麵',
'什么' => '什麼',
'仇仇' => '仇讎',
+'今天' => '今天',
'他克制' => '他剋制',
'他钟' => '他鐘',
'付托' => '付託',
@@ -3549,6 +3552,7 @@ $zh2Hant = array(
'伊尔汗历表' => '伊爾汗曆表',
'伊达里子' => '伊達里子',
'伊适杰' => '伊適杰',
+'伊里布' => '伊里布',
'伊郁' => '伊鬱',
'伏几' => '伏几',
'伐罪吊民' => '伐罪弔民',
@@ -3897,6 +3901,7 @@ $zh2Hant = array(
'幸免' => '倖免',
'幸存' => '倖存',
'幸幸' => '倖幸',
+'候复' => '候覆',
'倛丑' => '倛醜',
'借听于聋' => '借聽於聾',
'倦游' => '倦遊',
@@ -4051,7 +4056,6 @@ $zh2Hant = array(
'八出祁山' => '八出祁山',
'八出逃' => '八出逃',
'八周后' => '八周後',
-'八大胡同' => '八大胡同',
'八天后' => '八天後',
'八字胡' => '八字鬍',
'八年' => '八年',
@@ -4100,6 +4104,7 @@ $zh2Hant = array(
'冬山庄' => '冬山庄',
'冬日里' => '冬日裡',
'冬游' => '冬遊',
+'冰冷' => '冰冷',
'冶游' => '冶遊',
'冷庄子' => '冷莊子',
'冷面相' => '冷面相',
@@ -4171,6 +4176,7 @@ $zh2Hant = array(
'划得来' => '划得來',
'划拳' => '划拳',
'划桨' => '划槳',
+'划槳' => '划槳',
'划水' => '划水',
'划算' => '划算',
'划船' => '划船',
@@ -4220,6 +4226,7 @@ $zh2Hant = array(
'克期' => '剋期',
'克死' => '剋死',
'克薄' => '剋薄',
+'前天' => '前天',
'前往' => '前往',
'前言不答后语' => '前言不答後語',
'前面店' => '前面店',
@@ -4277,6 +4284,7 @@ $zh2Hant = array(
'包扎' => '包紮',
'包庄' => '包莊',
'匏系' => '匏繫',
+'化学家' => '化學家',
'北岳' => '北嶽',
'北回线' => '北迴線',
'北回铁路' => '北迴鐵路',
@@ -4381,6 +4389,7 @@ $zh2Hant = array(
'口燥唇干' => '口燥唇乾',
'口腹之欲' => '口腹之慾',
'口里' => '口裡',
+'口试' => '口試',
'口钟' => '口鐘',
'古书云' => '古書云',
'古書云' => '古書云',
@@ -4437,6 +4446,7 @@ $zh2Hant = array(
'可自制' => '可自制',
'台子女' => '台子女',
'台子孙' => '台子孫',
+'台州' => '台州',
'台布景' => '台布景',
'台历史' => '台歷史',
'台钟' => '台鐘',
@@ -4467,8 +4477,8 @@ $zh2Hant = array(
'各辟' => '各闢',
'各类钟' => '各類鐘',
'合伙人' => '合伙人',
-'合并' => '合併',
'合伙' => '合夥',
+'合并' => '合并',
'合府上' => '合府上',
'合采' => '合採',
'合历' => '合曆',
@@ -4491,9 +4501,11 @@ $zh2Hant = array(
'后北街' => '后北街',
'后土' => '后土',
'后妃' => '后妃',
+'后姓' => '后姓',
'后安路' => '后安路',
'后平路' => '后平路',
'后座' => '后座',
+'后母戊' => '后母戊',
'后海湾' => '后海灣',
'后海灣' => '后海灣',
'后瑞站' => '后瑞站',
@@ -4656,6 +4668,7 @@ $zh2Hant = array(
'国历' => '國曆',
'国历代' => '國歷代',
'国历任' => '國歷任',
+'国历来' => '國歷來',
'国历史' => '國歷史',
'国历届' => '國歷屆',
'国历经' => '國歷經',
@@ -4818,6 +4831,7 @@ $zh2Hant = array(
'天后来' => '天後來',
'天后半' => '天後半',
'天后天' => '天後天',
+'天文学家' => '天文學家',
'天文学钟' => '天文學鐘',
'天文历表' => '天文曆表',
'天文钟' => '天文鐘',
@@ -4900,6 +4914,7 @@ $zh2Hant = array(
'嬴余' => '嬴餘',
'子之丰兮' => '子之丰兮',
'子云' => '子云',
+'子晳' => '子晳',
'字汇' => '字彙',
'字码表' => '字碼表',
'字里行间' => '字裡行間',
@@ -5301,12 +5316,14 @@ $zh2Hant = array(
'往日無仇' => '往日無讎',
'往里' => '往裡',
'往复' => '往複',
+'待复' => '待覆',
'很干' => '很乾',
'很凶' => '很兇',
'很丑' => '很醜',
'律历志' => '律曆志',
'后印' => '後印',
'后台老板' => '後台老板',
+'后天' => '後天',
'后庄' => '後庄',
'后面店' => '後面店',
'徐干' => '徐幹',
@@ -5656,11 +5673,12 @@ $zh2Hant = array(
'扯面' => '扯麵',
'扶余国' => '扶餘國',
'批准的' => '批准的',
-'批复' => '批複',
+'批复' => '批覆',
'批注' => '批註',
'批斗' => '批鬥',
'承制' => '承製',
'抑制作用' => '抑制作用',
+'抑制剂' => '抑制劑',
'抑郁' => '抑鬱',
'抓奸' => '抓姦',
'抓药' => '抓藥',
@@ -5999,6 +6017,7 @@ $zh2Hant = array(
'数天后' => '數天後',
'数字钟' => '數字鐘',
'数字钟表' => '數字鐘錶',
+'数学家' => '數學家',
'数罪并罚' => '數罪併罰',
'数与虏确' => '數與虜确',
'文丑' => '文丑',
@@ -6162,6 +6181,7 @@ $zh2Hant = array(
'升平' => '昇平',
'升阳' => '昇陽',
'昊天不吊' => '昊天不弔',
+'明天' => '明天',
'明征' => '明徵',
'明目张胆' => '明目張胆',
'明窗净几' => '明窗淨几',
@@ -6180,6 +6200,7 @@ $zh2Hant = array(
'春药' => '春藥',
'春游' => '春遊',
'春香斗学' => '春香鬥學',
+'昨天' => '昨天',
'时钟' => '時鐘',
'时间里' => '時間裡',
'晃荡' => '晃蕩',
@@ -6232,6 +6253,7 @@ $zh2Hant = array(
'更钟' => '更鐘',
'书呆子' => '書獃子',
'书签' => '書籤',
+'书面' => '書面',
'曼谷人' => '曼谷人',
'曾朴' => '曾樸',
'最多' => '最多',
@@ -6292,6 +6314,7 @@ $zh2Hant = array(
'木制' => '木製',
'木钟' => '木鐘',
'未干' => '未乾',
+'未干涉' => '未干涉',
'末药' => '末藥',
'本征' => '本徵',
'术赤' => '朮赤',
@@ -6470,7 +6493,6 @@ $zh2Hant = array(
'归余' => '歸餘',
'歹斗' => '歹鬥',
'死于' => '死於',
-'死胡同' => '死胡同',
'死里求生' => '死裡求生',
'死里逃生' => '死裡逃生',
'殖谷' => '殖穀',
@@ -6638,7 +6660,9 @@ $zh2Hant = array(
'清汤挂面' => '清湯掛麵',
'减肥药' => '減肥藥',
'渠冲' => '渠衝',
+'测试' => '測試',
'港制' => '港製',
+'游离' => '游離',
'浑朴' => '渾樸',
'浑个' => '渾箇',
'凑合着' => '湊合著',
@@ -6847,6 +6871,7 @@ $zh2Hant = array(
'牛肉面' => '牛肉麵',
'牛只' => '牛隻',
'物欲' => '物慾',
+'物理学家' => '物理學家',
'特别致' => '特别致',
'特制住' => '特制住',
'特制定' => '特制定',
@@ -6900,6 +6925,7 @@ $zh2Hant = array(
'玉历史' => '玉歷史',
'王侯后' => '王侯后',
'王后' => '王后',
+'王田里' => '王田里',
'王庄' => '王莊',
'王余鱼' => '王餘魚',
'珍肴异馔' => '珍肴異饌',
@@ -6926,6 +6952,7 @@ $zh2Hant = array(
'生力面' => '生力麵',
'生于' => '生於',
'生殖洄游' => '生殖洄游',
+'生物学家' => '生物學家',
'生物钟' => '生物鐘',
'生发生' => '生發生',
'生华发' => '生華髮',
@@ -6935,6 +6962,7 @@ $zh2Hant = array(
'产卵洄游' => '產卵洄游',
'用药' => '用藥',
'甩发' => '甩髮',
+'田庄英雄' => '田庄英雄',
'田谷' => '田穀',
'田庄' => '田莊',
'田里' => '田裡',
@@ -7178,6 +7206,7 @@ $zh2Hant = array(
'种师中' => '种師中',
'种师道' => '种師道',
'种放' => '种放',
+'科学家' => '科學家',
'科斗' => '科斗',
'科范' => '科範',
'秒表明' => '秒表明',
@@ -7273,6 +7302,7 @@ $zh2Hant = array(
'笨笨呆呆' => '笨笨呆呆',
'第四出局' => '第四出局',
'笔秃墨干' => '筆禿墨乾',
+'笔试' => '筆試',
'等于' => '等於',
'笋干' => '筍乾',
'筑前' => '筑前',
@@ -7360,6 +7390,7 @@ $zh2Hant = array(
'纪历' => '紀曆',
'纪历史' => '紀歷史',
'约占' => '約佔',
+'红后假说' => '紅后假說',
'红绳系足' => '紅繩繫足',
'红色长发' => '紅色長髮',
'红钟' => '紅鐘',
@@ -7562,6 +7593,7 @@ $zh2Hant = array(
'老板' => '老闆',
'老面皮' => '老面皮',
'考征' => '考徵',
+'考试' => '考試',
'而克制' => '而剋制',
'耍斗' => '耍鬥',
'耕佣' => '耕傭',
@@ -7723,7 +7755,6 @@ $zh2Hant = array(
'茶余' => '茶餘',
'茶面' => '茶麵',
'草丛里' => '草叢裡',
-'草广' => '草广',
'草荐' => '草荐',
'草药' => '草藥',
'荐居' => '荐居',
@@ -7960,7 +7991,6 @@ $zh2Hant = array(
'行凶後' => '行兇後',
'行于' => '行於',
'行百里者半于九十' => '行百里者半於九十',
-'胡同' => '衚衕',
'卫后庄公' => '衛後莊公',
'卫星钟' => '衛星鐘',
'冲上' => '衝上',
@@ -8191,6 +8221,7 @@ $zh2Hant = array(
'言大而夸' => '言大而夸',
'言辩而确' => '言辯而确',
'订制' => '訂製',
+'计划' => '計劃',
'计时表' => '計時錶',
'托了' => '託了',
'托事' => '託事',
@@ -8282,6 +8313,7 @@ $zh2Hant = array(
'谈征' => '談徵',
'请参阅' => '請參閱',
'请君入瓮' => '請君入甕',
+'请求' => '請求',
'请托' => '請託',
'咨询' => '諮詢',
'诸余' => '諸餘',
@@ -8466,10 +8498,15 @@ $zh2Hant = array(
'透辟' => '透闢',
'这只不' => '這只不',
'这只允' => '這只允',
+'这只可' => '這只可',
+'这只在' => '這只在',
'这只容' => '這只容',
'这只采' => '這只採',
'这只是' => '這只是',
+'这只会' => '這只會',
'这只用' => '這只用',
+'这只能' => '這只能',
+'这只需' => '這只需',
'这伙人' => '這夥人',
'这里' => '這裡',
'这钟' => '這鐘',
@@ -8544,7 +8581,8 @@ $zh2Hant = array(
'游踪' => '遊蹤',
'游逛' => '遊逛',
'游错' => '遊錯',
-'游离' => '遊離',
+'游离份子' => '遊離份子',
+'游离票' => '遊離票',
'游骑兵' => '遊騎兵',
'游魂' => '遊魂',
'过于' => '過於',
@@ -8574,8 +8612,14 @@ $zh2Hant = array(
'还采' => '還採',
'还冲' => '還衝',
'邋里邋遢' => '邋裡邋遢',
+'那只可' => '那只可',
+'那只在' => '那只在',
'那只是' => '那只是',
+'那只会' => '那只會',
'那只有' => '那只有',
+'那只用' => '那只用',
+'那只能' => '那只能',
+'那只需' => '那只需',
'那卷' => '那捲',
'那里' => '那裡',
'那只' => '那隻',
@@ -9173,6 +9217,7 @@ $zh2Hant = array(
'余桶' => '餘桶',
'余业' => '餘業',
'余款' => '餘款',
+'余欢' => '餘歡',
'余步' => '餘步',
'余殃' => '餘殃',
'余毒' => '餘毒',
@@ -9302,6 +9347,7 @@ $zh2Hant = array(
'高干预' => '高干預',
'高干' => '高幹',
'高度自制' => '高度自制',
+'高涌泉' => '高涌泉',
'高清愿' => '高清愿',
'髡发' => '髡髮',
'髭胡' => '髭鬍',
@@ -9729,7 +9775,6 @@ $zh2Hant = array(
);
$zh2Hans = array(
-'㑳' => '㑇',
'㞞' => '𪨊',
'㠏' => '㟆',
'㩜' => '㨫',
@@ -9737,33 +9782,11 @@ $zh2Hans = array(
'䊷' => '䌶',
'䋙' => '䌺',
'䋻' => '䌾',
-'䌈' => '𦈖',
'䝼' => '䞍',
-'䪏' => '𩏼',
-'䪗' => '𩐀',
-'䪘' => '𩏿',
-'䫴' => '𩖗',
-'䬘' => '𩙮',
-'䬝' => '𩙯',
-'䭀' => '𩠇',
-'䭃' => '𩠈',
-'䭿' => '𩧭',
-'䮝' => '𩧰',
-'䮞' => '𩨁',
-'䮠' => '𩧿',
-'䮳' => '𩨏',
-'䮾' => '𩧪',
'䯀' => '䯅',
'䰾' => '鲃',
-'䱙' => '𩾈',
-'䱬' => '𩾊',
-'䱰' => '𩾋',
-'䱷' => '䲣',
'䱽' => '䲝',
'䲁' => '鳚',
-'䲰' => '𪉂',
-'䴬' => '𪎈',
-'䴴' => '𪎋',
'丟' => '丢',
'並' => '并',
'乾' => '干',
@@ -9922,7 +9945,6 @@ $zh2Hans = array(
'嗎' => '吗',
'嗚' => '呜',
'嗩' => '唢',
-'嗰' => '𠮶',
'嗶' => '哔',
'嗹' => '𪡏',
'嘆' => '叹',
@@ -11722,12 +11744,10 @@ $zh2Hans = array(
'釤' => '钐',
'釧' => '钏',
'釩' => '钒',
-'釳' => '𨰿',
'釵' => '钗',
'釷' => '钍',
'釹' => '钕',
'釺' => '钎',
-'釾' => '䥺',
'鈀' => '钯',
'鈁' => '钫',
'鈃' => '钘',
@@ -11735,7 +11755,6 @@ $zh2Hans = array(
'鈇' => '𫓧',
'鈈' => '钚',
'鈉' => '钠',
-'鈋' => '𨱂',
'鈍' => '钝',
'鈎' => '钩',
'鈐' => '钤',
@@ -11744,15 +11763,12 @@ $zh2Hans = array(
'鈔' => '钞',
'鈕' => '钮',
'鈞' => '钧',
-'鈠' => '𨱁',
'鈣' => '钙',
'鈥' => '钬',
'鈦' => '钛',
'鈧' => '钪',
'鈮' => '铌',
-'鈯' => '𨱄',
'鈰' => '铈',
-'鈲' => '𨱃',
'鈳' => '钶',
'鈴' => '铃',
'鈷' => '钴',
@@ -11763,7 +11779,6 @@ $zh2Hans = array(
'鈾' => '铀',
'鈿' => '钿',
'鉀' => '钾',
-'鉁' => '𨱅',
'鉅' => '钜',
'鉈' => '铊',
'鉉' => '铉',
@@ -11807,7 +11822,6 @@ $zh2Hans = array(
'銬' => '铐',
'銱' => '铞',
'銳' => '锐',
-'銶' => '𨱇',
'銷' => '销',
'銹' => '锈',
'銻' => '锑',
@@ -11816,7 +11830,6 @@ $zh2Hans = array(
'鋃' => '锒',
'鋅' => '锌',
'鋇' => '钡',
-'鋉' => '𨱈',
'鋌' => '铤',
'鋏' => '铗',
'鋒' => '锋',
@@ -11839,7 +11852,6 @@ $zh2Hans = array(
'鋸' => '锯',
'鋼' => '钢',
'錁' => '锞',
-'錂' => '𨱋',
'錄' => '录',
'錆' => '锖',
'錇' => '锫',
@@ -11869,7 +11881,6 @@ $zh2Hans = array(
'鍀' => '锝',
'鍁' => '锨',
'鍃' => '锪',
-'鍄' => '𨱉',
'鍆' => '钔',
'鍇' => '锴',
'鍈' => '锳',
@@ -11885,7 +11896,6 @@ $zh2Hans = array(
'鍥' => '锲',
'鍩' => '锘',
'鍬' => '锹',
-'鍮' => '𨱎',
'鍰' => '锾',
'鍵' => '键',
'鍶' => '锶',
@@ -11912,19 +11922,15 @@ $zh2Hans = array(
'鎬' => '镐',
'鎭' => '鎮',
'鎮' => '镇',
-'鎯' => '𨱍',
'鎰' => '镒',
'鎲' => '镋',
'鎳' => '镍',
'鎵' => '镓',
-'鎷' => '𨰾',
'鎸' => '镌',
'鎿' => '镎',
'鏃' => '镞',
-'鏆' => '𨱌',
'鏇' => '镟',
'鏈' => '链',
-'鏉' => '𨱒',
'鏌' => '镆',
'鏍' => '镙',
'鏐' => '镠',
@@ -11945,13 +11951,10 @@ $zh2Hans = array(
'鏵' => '铧',
'鏷' => '镤',
'鏹' => '镪',
-'鏺' => '䥽',
'鏽' => '锈',
'鐃' => '铙',
'鐋' => '铴',
'鐍' => '𫔎',
-'鐎' => '𨱓',
-'鐏' => '𨱔',
'鐐' => '镣',
'鐒' => '铹',
'鐓' => '镦',
@@ -11960,13 +11963,11 @@ $zh2Hans = array(
'鐙' => '镫',
'鐝' => '镢',
'鐠' => '镨',
-'鐥' => '䦅',
'鐦' => '锎',
'鐧' => '锏',
'鐨' => '镄',
'鐫' => '镌',
'鐮' => '镰',
-'鐯' => '䦃',
'鐲' => '镯',
'鐳' => '镭',
'鐵' => '铁',
@@ -12006,10 +12007,8 @@ $zh2Hans = array(
'閉' => '闭',
'開' => '开',
'閌' => '闶',
-'閍' => '𨸂',
'閎' => '闳',
'閏' => '闰',
-'閐' => '𨸃',
'閑' => '闲',
'閒' => '闲',
'間' => '间',
@@ -12142,7 +12141,6 @@ $zh2Hans = array(
'頹' => '颓',
'頻' => '频',
'頽' => '颓',
-'顃' => '𩖖',
'顆' => '颗',
'題' => '题',
'額' => '额',
@@ -12169,16 +12167,13 @@ $zh2Hans = array(
'颭' => '飐',
'颮' => '飑',
'颯' => '飒',
-'颰' => '𩙥',
'颱' => '台',
'颳' => '刮',
'颶' => '飓',
-'颷' => '𩙪',
'颸' => '飔',
'颺' => '飏',
'颻' => '飖',
'颼' => '飕',
-'颾' => '𩙫',
'飀' => '飗',
'飄' => '飘',
'飆' => '飙',
@@ -12229,7 +12224,6 @@ $zh2Hans = array(
'餵' => '喂',
'餶' => '馉',
'餷' => '馇',
-'餸' => '𩠌',
'餺' => '馎',
'餼' => '饩',
'餾' => '馏',
@@ -12258,7 +12252,6 @@ $zh2Hans = array(
'馹' => '驲',
'駁' => '驳',
'駃' => '𫘝',
-'駎' => '𩧨',
'駐' => '驻',
'駑' => '驽',
'駒' => '驹',
@@ -12266,18 +12259,14 @@ $zh2Hans = array(
'駕' => '驾',
'駘' => '骀',
'駙' => '驸',
-'駚' => '𩧫',
'駛' => '驶',
'駝' => '驼',
'駟' => '驷',
'駡' => '骂',
'駢' => '骈',
-'駧' => '𩧲',
-'駩' => '𩧴',
'駭' => '骇',
'駰' => '骃',
'駱' => '骆',
-'駶' => '𩧺',
'駸' => '骎',
'駻' => '𫘣',
'駿' => '骏',
@@ -12289,16 +12278,11 @@ $zh2Hans = array(
'騍' => '骒',
'騎' => '骑',
'騏' => '骐',
-'騔' => '𩨀',
'騖' => '骛',
'騙' => '骗',
-'騚' => '𩨊',
-'騝' => '𩨃',
-'騟' => '𩨈',
'騠' => '𫘨',
'騤' => '骙',
'騧' => '䯄',
-'騪' => '𩨄',
'騫' => '骞',
'騭' => '骘',
'騮' => '骝',
@@ -12314,7 +12298,6 @@ $zh2Hans = array(
'驄' => '骢',
'驅' => '驱',
'驊' => '骅',
-'驋' => '𩧯',
'驌' => '骕',
'驍' => '骁',
'驏' => '骣',
@@ -12352,7 +12335,6 @@ $zh2Hans = array(
'魛' => '鱽',
'魟' => '𫚉',
'魢' => '鱾',
-'魥' => '𩽹',
'魨' => '鲀',
'魯' => '鲁',
'魴' => '鲂',
@@ -12369,13 +12351,10 @@ $zh2Hans = array(
'鮑' => '鲍',
'鮒' => '鲋',
'鮓' => '鲊',
-'鮕' => '𩾀',
'鮚' => '鲒',
'鮜' => '鲘',
'鮝' => '鲞',
'鮞' => '鲕',
-'鮟' => '𩽾',
-'鮣' => '䲟',
'鮦' => '鲖',
'鮪' => '鲔',
'鮫' => '鲛',
@@ -12384,11 +12363,9 @@ $zh2Hans = array(
'鮰' => '𫚔',
'鮳' => '鲓',
'鮶' => '鲪',
-'鮸' => '𩾃',
'鮺' => '鲝',
'鯀' => '鲧',
'鯁' => '鲠',
-'鯄' => '𩾁',
'鯆' => '𫚙',
'鯇' => '鲩',
'鯉' => '鲤',
@@ -12408,19 +12385,15 @@ $zh2Hans = array(
'鯪' => '鲮',
'鯫' => '鲰',
'鯰' => '鲇',
-'鯱' => '𩾇',
'鯴' => '鲺',
-'鯶' => '𩽼',
'鯷' => '鳀',
'鯽' => '鲫',
'鯿' => '鳊',
'鰁' => '鳈',
'鰂' => '鲗',
'鰃' => '鳂',
-'鰆' => '䲠',
'鰈' => '鲽',
'鰉' => '鳇',
-'鰌' => '䲡',
'鰍' => '鳅',
'鰏' => '鲾',
'鰐' => '鳄',
@@ -12432,7 +12405,6 @@ $zh2Hans = array(
'鰣' => '鲥',
'鰤' => '𫚕',
'鰥' => '鳏',
-'鰧' => '䲢',
'鰨' => '鳎',
'鰩' => '鳐',
'鰭' => '鳍',
@@ -12449,7 +12421,6 @@ $zh2Hans = array(
'鰾' => '鳔',
'鱂' => '鳉',
'鱅' => '鳙',
-'鱇' => '𩾌',
'鱈' => '鳕',
'鱉' => '鳖',
'鱒' => '鳟',
@@ -12479,7 +12450,6 @@ $zh2Hans = array(
'鳴' => '鸣',
'鳶' => '鸢',
'鳷' => '𫛛',
-'鳼' => '𪉃',
'鳾' => '䴓',
'鴃' => '𫛞',
'鴆' => '鸩',
@@ -12489,7 +12459,6 @@ $zh2Hans = array(
'鴕' => '鸵',
'鴗' => '𫁡',
'鴛' => '鸳',
-'鴜' => '𪉈',
'鴝' => '鸲',
'鴞' => '鸮',
'鴟' => '鸱',
@@ -12498,7 +12467,6 @@ $zh2Hans = array(
'鴨' => '鸭',
'鴯' => '鸸',
'鴰' => '鸹',
-'鴲' => '𪉆',
'鴴' => '鸻',
'鴷' => '䴕',
'鴻' => '鸿',
@@ -12510,7 +12478,6 @@ $zh2Hans = array(
'鵑' => '鹃',
'鵒' => '鹆',
'鵓' => '鹁',
-'鵚' => '𪉍',
'鵜' => '鹈',
'鵝' => '鹅',
'鵠' => '鹄',
@@ -12553,14 +12520,12 @@ $zh2Hans = array(
'鷈' => '䴘',
'鷊' => '鹝',
'鷓' => '鹧',
-'鷔' => '𪉑',
'鷖' => '鹥',
'鷗' => '鸥',
'鷙' => '鸷',
'鷚' => '鹨',
'鷥' => '鸶',
'鷦' => '鹪',
-'鷨' => '𪉊',
'鷫' => '鹔',
'鷯' => '鹩',
'鷲' => '鹫',
@@ -12588,13 +12553,10 @@ $zh2Hans = array(
'鹽' => '盐',
'麗' => '丽',
'麥' => '麦',
-'麨' => '𪎊',
'麩' => '麸',
'麪' => '面',
'麫' => '面',
'麯' => '曲',
-'麲' => '𪎉',
-'麳' => '𪎌',
'麴' => '曲',
'麵' => '面',
'麼' => '么',
@@ -12646,74 +12608,18 @@ $zh2Hans = array(
'𡻕' => '岁',
'𤪺' => '㻘',
'𤫩' => '㻏',
-'𦪙' => '䑽',
'𧜵' => '䙊',
'𧝞' => '䘛',
'𧦧' => '𫍟',
'𧩙' => '䜥',
'𧵳' => '䞌',
'𨋢' => '䢂',
-'𨥛' => '𨱀',
'𨦫' => '䦀',
'𨧜' => '䦁',
-'𨧱' => '𨱊',
-'𨫒' => '𨱐',
-'𨮂' => '𨱕',
'𨯅' => '䥿',
-'𩎢' => '𩏾',
-'𩏪' => '𩏽',
-'𩓣' => '𩖕',
-'𩗀' => '𩙦',
-'𩗡' => '𩙧',
-'𩘀' => '𩙩',
-'𩘝' => '𩙭',
-'𩘹' => '𩙨',
-'𩘺' => '𩙬',
-'𩙈' => '𩙰',
-'𩜦' => '𩠆',
-'𩝔' => '𩠋',
-'𩞯' => '䭪',
-'𩟐' => '𩠅',
-'𩡺' => '𩧦',
-'𩢡' => '𩧬',
-'𩢴' => '𩧵',
-'𩢸' => '𩧳',
-'𩢾' => '𩧮',
-'𩣏' => '𩧶',
'𩣑' => '䯃',
'𩣵' => '𩧻',
-'𩣺' => '𩧼',
-'𩤊' => '𩧩',
-'𩤙' => '𩨆',
-'𩤲' => '𩨉',
-'𩤸' => '𩨅',
-'𩥄' => '𩨋',
-'𩥇' => '𩨍',
-'𩥉' => '𩧱',
-'𩥑' => '𩨌',
-'𩧆' => '𩨐',
-'𩵩' => '𩽺',
-'𩵹' => '𩽻',
'𩶘' => '䲞',
-'𩶰' => '𩽿',
-'𩶱' => '𩽽',
-'𩷰' => '𩾄',
-'𩸃' => '𩾅',
-'𩸦' => '𩾆',
-'𩽇' => '𩾎',
-'𩿪' => '𪉄',
-'𪀦' => '𪉅',
-'𪀾' => '𪉋',
-'𪁈' => '𪉉',
-'𪁖' => '𪉌',
-'𪂆' => '𪉎',
-'𪃍' => '𪉐',
-'𪃏' => '𪉏',
-'𪄆' => '𪉔',
-'𪄕' => '𪉒',
-'𪇳' => '𪉕',
-'𪘀' => '𪚏',
-'𪘯' => '𪚐',
'𫚒' => '軿',
'《易乾' => '《易乾',
'不著痕跡' => '不着痕迹',
@@ -13062,6 +12968,7 @@ $zh2Hans = array(
'信著者' => '信著者',
'信著述' => '信著述',
'修鍊' => '修炼',
+'候覆' => '候复',
'候著' => '候着',
'候著書' => '候著书',
'候著书' => '候著书',
@@ -13118,6 +13025,7 @@ $zh2Hans = array(
'偷著者' => '偷著者',
'偷著述' => '偷著述',
'傢俬' => '傢俬',
+'僧伽吒' => '僧伽吒',
'光著' => '光着',
'光著書' => '光著书',
'光著书' => '光著书',
@@ -13681,6 +13589,7 @@ $zh2Hans = array(
'當著者' => '当著者',
'當著述' => '当著述',
'彰明較著' => '彰明较著',
+'待覆' => '待复',
'待著' => '待着',
'待著书' => '待著书',
'待著書' => '待著书',
@@ -13692,6 +13601,7 @@ $zh2Hans = array(
'待著称' => '待著称',
'待著者' => '待著者',
'待著述' => '待著述',
+'後姓' => '後姓',
'得著' => '得着',
'得著書' => '得著书',
'得著书' => '得著书',
@@ -13880,6 +13790,7 @@ $zh2Hans = array(
'扛著者' => '扛著者',
'扛著述' => '扛著述',
'執著' => '执著',
+'批覆' => '批复',
'找不著' => '找不着',
'找得著' => '找得着',
'抓著' => '抓着',
@@ -14527,6 +14438,7 @@ $zh2Hans = array(
'猜著稱' => '猜著称',
'猜著者' => '猜著者',
'猜著述' => '猜著述',
+'王道乾' => '王道乾',
'玩著' => '玩着',
'甜著' => '甜着',
'甜著書' => '甜著书',
@@ -15089,6 +15001,7 @@ $zh2Hans = array(
'見著述' => '见著述',
'視微知著' => '视微知著',
'言幾析理' => '言幾析理',
+'警戒著' => '警戒着',
'記著' => '记着',
'記著書' => '记著书',
'記著作' => '记著作',
@@ -15638,12 +15551,11 @@ $zh2TW = array(
'阿塞拜疆' => '亞塞拜然',
'人工智能' => '人工智慧',
'接口' => '介面',
-'任意球員' => '任意球員',
-'任意球员' => '任意球員',
'服务器' => '伺服器',
'字節' => '位元組',
'字节' => '位元組',
'作品裏' => '作品裡',
+'信道' => '信道',
'优先级' => '優先順序',
'元兇' => '元凶',
'元凶' => '元凶',
@@ -15670,7 +15582,6 @@ $zh2TW = array(
'兇殺' => '凶殺',
'凶杀' => '凶殺',
'凶殺' => '凶殺',
-'分布式' => '分散式',
'打印' => '列印',
'列支敦士登' => '列支敦斯登',
'剪彩' => '剪綵',
@@ -15829,6 +15740,7 @@ $zh2TW = array(
'波斯尼亚和黑塞哥维那' => '波士尼亞赫塞哥維納',
'博茨瓦纳' => '波札那',
'博茨瓦納' => '波札那',
+'流程控制' => '流程控制',
'侯赛因' => '海珊',
'侯賽因' => '海珊',
'深淵裏' => '深淵裡',
@@ -15904,7 +15816,6 @@ $zh2TW = array(
'肚裏' => '肚裡',
'肯尼亚' => '肯亞',
'肯雅' => '肯亞',
-'任意球' => '自由球',
'航天大学' => '航天大學',
'苦裏' => '苦裡',
'毛里塔尼亚' => '茅利塔尼亞',
@@ -15913,8 +15824,8 @@ $zh2TW = array(
'万历' => '萬曆',
'瓦努阿图' => '萬那杜',
'瓦努阿圖' => '萬那杜',
-'也門' => '葉門',
'也门' => '葉門',
+'也門' => '葉門',
'着' => '著',
'科摩羅' => '葛摩',
'科摩罗' => '葛摩',
@@ -15988,11 +15899,11 @@ $zh2TW = array(
'溫納圖萬' => '那杜',
'醫院裏' => '醫院裡',
'酰' => '醯',
-'巨商' => '鉅賈',
'钩' => '鉤',
'鈎' => '鉤',
'钩心斗角' => '鉤心鬥角',
'鈎心鬥角' => '鉤心鬥角',
+'锎' => '鉲',
'写保护' => '防寫',
'阿拉伯联合酋长国' => '阿拉伯聯合大公國',
'阿拉伯聯合酋長國' => '阿拉伯聯合大公國',
@@ -16817,7 +16728,6 @@ $zh2HK = array(
'拼著述' => '拼著述',
'拼著錄' => '拼著錄',
'拿著' => '拿着',
-'拿破崙' => '拿破侖',
'拿著作' => '拿著作',
'拿著名' => '拿著名',
'拿著稱' => '拿著稱',
@@ -17751,6 +17661,7 @@ $zh2HK = array(
'語著述' => '語著述',
'語著錄' => '語著錄',
'數據機' => '調制解調器',
+'警戒著' => '警戒着',
'變著' => '變着',
'變著作' => '變著作',
'變著名' => '變著名',
@@ -18269,7 +18180,6 @@ $zh2CN = array(
'甚麽' => '什么',
'甚麼' => '什么',
'乙太網' => '以太网',
-'自由球' => '任意球',
'優先順序' => '优先级',
'感測' => '传感',
'伯利茲' => '伯利兹',
@@ -18291,8 +18201,6 @@ $zh2CN = array(
'全形' => '全角',
'八進位制' => '八进位制',
'八進位' => '八进制',
-'公車' => '公共汽车',
-'公車上書' => '公车上书',
'六進位制' => '六进位制',
'六進位' => '六进制',
'記憶體' => '内存',
@@ -18304,7 +18212,6 @@ $zh2CN = array(
'幾內亞比索' => '几内亚比绍',
'梵谷' => '凡高',
'計程車' => '出租车',
-'分散式' => '分布式',
'解析度' => '分辨率',
'列支敦斯登' => '列支敦士登',
'賴比瑞亞' => '利比里亚',
@@ -18366,6 +18273,7 @@ $zh2CN = array(
'字型大小' => '字号',
'字型檔' => '字库',
'欄位' => '字段',
+'字母' => '字母',
'字元' => '字符',
'字節' => '字节',
'位元組' => '字节',
@@ -18381,7 +18289,6 @@ $zh2CN = array(
'尼日尔' => '尼日尔',
'章節附註' => '尾注',
'區域網' => '局域网',
-'鉅賈' => '巨商',
'巴貝多' => '巴巴多斯',
'巴布亞紐幾內亞' => '巴布亚新几内亚',
'布希' => '布什',
@@ -18442,6 +18349,7 @@ $zh2CN = array(
'波士尼亞赫塞哥維納' => '波斯尼亚和黑塞哥维那',
'辛巴威' => '津巴布韦',
'宏都拉斯' => '洪都拉斯',
+'滑鼠蛇' => '滑鼠蛇',
'滿16進位' => '满16进位',
'滿二進位' => '满二进位',
'滿八進位' => '满八进位',
@@ -18487,8 +18395,6 @@ $zh2CN = array(
'寮國' => '老挝',
'肯雅' => '肯尼亚',
'肯亞' => '肯尼亚',
-'自由球员' => '自由球员',
-'自由球員' => '自由球员',
'單車' => '自行车',
'太空梭' => '航天飞机',
'穿梭機' => '航天飞机',
diff --git a/includes/ZipDirectoryReader.php b/includes/ZipDirectoryReader.php
new file mode 100644
index 00000000..d21cf3b0
--- /dev/null
+++ b/includes/ZipDirectoryReader.php
@@ -0,0 +1,684 @@
+<?php
+
+/**
+ * A class for reading ZIP file directories, for the purposes of upload
+ * verification.
+ *
+ * Only a functional interface is provided: ZipFileReader::read(). No access is
+ * given to object instances.
+ *
+ */
+class ZipDirectoryReader {
+ /**
+ * Read a ZIP file and call a function for each file discovered in it.
+ *
+ * Because this class is aimed at verification, an error is raised on
+ * suspicious or ambiguous input, instead of emulating some standard
+ * behaviour.
+ *
+ * @param $fileName string The archive file name
+ * @param $callback Array The callback function. It will be called for each file
+ * with a single associative array each time, with members:
+ *
+ * - name: The file name. Directories conventionally have a trailing
+ * slash.
+ *
+ * - mtime: The file modification time, in MediaWiki 14-char format
+ *
+ * - size: The uncompressed file size
+ *
+ * @param $options Array An associative array of read options, with the option
+ * name in the key. This may currently contain:
+ *
+ * - zip64: If this is set to true, then we will emulate a
+ * library with ZIP64 support, like OpenJDK 7. If it is set to
+ * false, then we will emulate a library with no knowledge of
+ * ZIP64.
+ *
+ * NOTE: The ZIP64 code is untested and probably doesn't work. It
+ * turned out to be easier to just reject ZIP64 archive uploads,
+ * since they are likely to be very rare. Confirming safety of a
+ * ZIP64 file is fairly complex. What do you do with a file that is
+ * ambiguous and broken when read with a non-ZIP64 reader, but valid
+ * when read with a ZIP64 reader? This situation is normal for a
+ * valid ZIP64 file, and working out what non-ZIP64 readers will make
+ * of such a file is not trivial.
+ *
+ * @return Status object. The following fatal errors are defined:
+ *
+ * - zip-file-open-error: The file could not be opened.
+ *
+ * - zip-wrong-format: The file does not appear to be a ZIP file.
+ *
+ * - zip-bad: There was something wrong or ambiguous about the file
+ * data.
+ *
+ * - zip-unsupported: The ZIP file uses features which
+ * ZipDirectoryReader does not support.
+ *
+ * The default messages for those fatal errors are written in a way that
+ * makes sense for upload verification.
+ *
+ * If a fatal error is returned, more information about the error will be
+ * available in the debug log.
+ *
+ * Note that the callback function may be called any number of times before
+ * a fatal error is returned. If this occurs, the data sent to the callback
+ * function should be discarded.
+ */
+ public static function read( $fileName, $callback, $options = array() ) {
+ $zdr = new self( $fileName, $callback, $options );
+ return $zdr->execute();
+ }
+
+ /** The file name */
+ var $fileName;
+
+ /** The opened file resource */
+ var $file;
+
+ /** The cached length of the file, or null if it has not been loaded yet. */
+ var $fileLength;
+
+ /** A segmented cache of the file contents */
+ var $buffer;
+
+ /** The file data callback */
+ var $callback;
+
+ /** The ZIP64 mode */
+ var $zip64 = false;
+
+ /** Stored headers */
+ var $eocdr, $eocdr64, $eocdr64Locator;
+
+ /** The "extra field" ID for ZIP64 central directory entries */
+ const ZIP64_EXTRA_HEADER = 0x0001;
+
+ /** The segment size for the file contents cache */
+ const SEGSIZE = 16384;
+
+ /** The index of the "general field" bit for UTF-8 file names */
+ const GENERAL_UTF8 = 11;
+
+ /** The index of the "general field" bit for central directory encryption */
+ const GENERAL_CD_ENCRYPTED = 13;
+
+
+ /**
+ * Private constructor
+ */
+ protected function __construct( $fileName, $callback, $options ) {
+ $this->fileName = $fileName;
+ $this->callback = $callback;
+
+ if ( isset( $options['zip64'] ) ) {
+ $this->zip64 = $options['zip64'];
+ }
+ }
+
+ /**
+ * Read the directory according to settings in $this.
+ *
+ * @return Status
+ */
+ function execute() {
+ $this->file = fopen( $this->fileName, 'r' );
+ $this->data = array();
+ if ( !$this->file ) {
+ return Status::newFatal( 'zip-file-open-error' );
+ }
+
+ $status = Status::newGood();
+ try {
+ $this->readEndOfCentralDirectoryRecord();
+ if ( $this->zip64 ) {
+ list( $offset, $size ) = $this->findZip64CentralDirectory();
+ $this->readCentralDirectory( $offset, $size );
+ } else {
+ if ( $this->eocdr['CD size'] == 0xffffffff
+ || $this->eocdr['CD offset'] == 0xffffffff
+ || $this->eocdr['CD entries total'] == 0xffff )
+ {
+ $this->error( 'zip-unsupported', 'Central directory header indicates ZIP64, ' .
+ 'but we are in legacy mode. Rejecting this upload is necessary to avoid '.
+ 'opening vulnerabilities on clients using OpenJDK 7 or later.' );
+ }
+
+ list( $offset, $size ) = $this->findOldCentralDirectory();
+ $this->readCentralDirectory( $offset, $size );
+ }
+ } catch ( ZipDirectoryReaderError $e ) {
+ $status->fatal( $e->getErrorCode() );
+ }
+
+ fclose( $this->file );
+ return $status;
+ }
+
+ /**
+ * Throw an error, and log a debug message
+ */
+ function error( $code, $debugMessage ) {
+ wfDebug( __CLASS__.": Fatal error: $debugMessage\n" );
+ throw new ZipDirectoryReaderError( $code );
+ }
+
+ /**
+ * Read the header which is at the end of the central directory,
+ * unimaginatively called the "end of central directory record" by the ZIP
+ * spec.
+ */
+ function readEndOfCentralDirectoryRecord() {
+ $info = array(
+ 'signature' => 4,
+ 'disk' => 2,
+ 'CD start disk' => 2,
+ 'CD entries this disk' => 2,
+ 'CD entries total' => 2,
+ 'CD size' => 4,
+ 'CD offset' => 4,
+ 'file comment length' => 2,
+ );
+ $structSize = $this->getStructSize( $info );
+ $startPos = $this->getFileLength() - 65536 - $structSize;
+ if ( $startPos < 0 ) {
+ $startPos = 0;
+ }
+
+ $block = $this->getBlock( $startPos );
+ $sigPos = strrpos( $block, "PK\x05\x06" );
+ if ( $sigPos === false ) {
+ $this->error( 'zip-wrong-format',
+ "zip file lacks EOCDR signature. It probably isn't a zip file." );
+ }
+
+ $this->eocdr = $this->unpack( substr( $block, $sigPos ), $info );
+ $this->eocdr['EOCDR size'] = $structSize + $this->eocdr['file comment length'];
+
+ if ( $structSize + $this->eocdr['file comment length'] != strlen( $block ) - $sigPos ) {
+ $this->error( 'zip-bad', 'trailing bytes after the end of the file comment' );
+ }
+ if ( $this->eocdr['disk'] !== 0
+ || $this->eocdr['CD start disk'] !== 0 )
+ {
+ $this->error( 'zip-unsupported', 'more than one disk (in EOCDR)' );
+ }
+ $this->eocdr += $this->unpack(
+ $block,
+ array( 'file comment' => array( 'string', $this->eocdr['file comment length'] ) ),
+ $sigPos + $structSize );
+ $this->eocdr['position'] = $startPos + $sigPos;
+ }
+
+ /**
+ * Read the header called the "ZIP64 end of central directory locator". An
+ * error will be raised if it does not exist.
+ */
+ function readZip64EndOfCentralDirectoryLocator() {
+ $info = array(
+ 'signature' => array( 'string', 4 ),
+ 'eocdr64 start disk' => 4,
+ 'eocdr64 offset' => 8,
+ 'number of disks' => 4,
+ );
+ $structSize = $this->getStructSize( $info );
+
+ $block = $this->getBlock( $this->getFileLength() - $this->eocdr['EOCDR size']
+ - $structSize, $structSize );
+ $this->eocdr64Locator = $data = $this->unpack( $block, $info );
+
+ if ( $data['signature'] !== "PK\x06\x07" ) {
+ // Note: Java will allow this and continue to read the
+ // EOCDR64, so we have to reject the upload, we can't
+ // just use the EOCDR header instead.
+ $this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory locator' );
+ }
+ }
+
+ /**
+ * Read the header called the "ZIP64 end of central directory record". It
+ * may replace the regular "end of central directory record" in ZIP64 files.
+ */
+ function readZip64EndOfCentralDirectoryRecord() {
+ if ( $this->eocdr64Locator['eocdr64 start disk'] != 0
+ || $this->eocdr64Locator['number of disks'] != 0 )
+ {
+ $this->error( 'zip-unsupported', 'more than one disk (in EOCDR64 locator)' );
+ }
+
+ $info = array(
+ 'signature' => array( 'string', 4 ),
+ 'EOCDR64 size' => 8,
+ 'version made by' => 2,
+ 'version needed' => 2,
+ 'disk' => 4,
+ 'CD start disk' => 4,
+ 'CD entries this disk' => 8,
+ 'CD entries total' => 8,
+ 'CD size' => 8,
+ 'CD offset' => 8
+ );
+ $structSize = $this->getStructSize( $info );
+ $block = $this->getBlock( $this->eocdr64Locator['eocdr64 offset'], $structSize );
+ $this->eocdr64 = $data = $this->unpack( $block, $info );
+ if ( $data['signature'] !== "PK\x06\x06" ) {
+ $this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory record' );
+ }
+ if ( $data['disk'] !== 0
+ || $data['CD start disk'] !== 0 )
+ {
+ $this->error( 'zip-unsupported', 'more than one disk (in EOCDR64)' );
+ }
+ }
+
+ /**
+ * Find the location of the central directory, as would be seen by a
+ * non-ZIP64 reader.
+ *
+ * @return List containing offset, size and end position.
+ */
+ function findOldCentralDirectory() {
+ $size = $this->eocdr['CD size'];
+ $offset = $this->eocdr['CD offset'];
+ $endPos = $this->eocdr['position'];
+
+ // Some readers use the EOCDR position instead of the offset field
+ // to find the directory, so to be safe, we check if they both agree.
+ if ( $offset + $size != $endPos ) {
+ $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
+ 'of central directory record' );
+ }
+ return array( $offset, $size );
+ }
+
+ /**
+ * Find the location of the central directory, as would be seen by a
+ * ZIP64-compliant reader.
+ *
+ * @return List containing offset, size and end position.
+ */
+ function findZip64CentralDirectory() {
+ // The spec is ambiguous about the exact rules of precedence between the
+ // ZIP64 headers and the original headers. Here we follow zip_util.c
+ // from OpenJDK 7.
+ $size = $this->eocdr['CD size'];
+ $offset = $this->eocdr['CD offset'];
+ $numEntries = $this->eocdr['CD entries total'];
+ $endPos = $this->eocdr['position'];
+ if ( $size == 0xffffffff
+ || $offset == 0xffffffff
+ || $numEntries == 0xffff )
+ {
+ $this->readZip64EndOfCentralDirectoryLocator();
+
+ if ( isset( $this->eocdr64Locator['eocdr64 offset'] ) ) {
+ $this->readZip64EndOfCentralDirectoryRecord();
+ if ( isset( $this->eocdr64['CD offset'] ) ) {
+ $size = $this->eocdr64['CD size'];
+ $offset = $this->eocdr64['CD offset'];
+ $endPos = $this->eocdr64Locator['eocdr64 offset'];
+ }
+ }
+ }
+ // Some readers use the EOCDR position instead of the offset field
+ // to find the directory, so to be safe, we check if they both agree.
+ if ( $offset + $size != $endPos ) {
+ $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
+ 'of central directory record' );
+ }
+ return array( $offset, $size );
+ }
+
+ /**
+ * Read the central directory at the given location
+ */
+ function readCentralDirectory( $offset, $size ) {
+ $block = $this->getBlock( $offset, $size );
+
+ $fixedInfo = array(
+ 'signature' => array( 'string', 4 ),
+ 'version made by' => 2,
+ 'version needed' => 2,
+ 'general bits' => 2,
+ 'compression method' => 2,
+ 'mod time' => 2,
+ 'mod date' => 2,
+ 'crc-32' => 4,
+ 'compressed size' => 4,
+ 'uncompressed size' => 4,
+ 'name length' => 2,
+ 'extra field length' => 2,
+ 'comment length' => 2,
+ 'disk number start' => 2,
+ 'internal attrs' => 2,
+ 'external attrs' => 4,
+ 'local header offset' => 4,
+ );
+ $fixedSize = $this->getStructSize( $fixedInfo );
+
+ $pos = 0;
+ while ( $pos < $size ) {
+ $data = $this->unpack( $block, $fixedInfo, $pos );
+ $pos += $fixedSize;
+
+ if ( $data['signature'] !== "PK\x01\x02" ) {
+ $this->error( 'zip-bad', 'Invalid signature found in directory entry' );
+ }
+
+ $variableInfo = array(
+ 'name' => array( 'string', $data['name length'] ),
+ 'extra field' => array( 'string', $data['extra field length'] ),
+ 'comment' => array( 'string', $data['comment length'] ),
+ );
+ $data += $this->unpack( $block, $variableInfo, $pos );
+ $pos += $this->getStructSize( $variableInfo );
+
+ if ( $this->zip64 && (
+ $data['compressed size'] == 0xffffffff
+ || $data['uncompressed size'] == 0xffffffff
+ || $data['local header offset'] == 0xffffffff ) )
+ {
+ $zip64Data = $this->unpackZip64Extra( $data['extra field'] );
+ if ( $zip64Data ) {
+ $data = $zip64Data + $data;
+ }
+ }
+
+ if ( $this->testBit( $data['general bits'], self::GENERAL_CD_ENCRYPTED ) ) {
+ $this->error( 'zip-unsupported', 'central directory encryption is not supported' );
+ }
+
+ // Convert the timestamp into MediaWiki format
+ // For the format, please see the MS-DOS 2.0 Programmer's Reference,
+ // pages 3-5 and 3-6.
+ $time = $data['mod time'];
+ $date = $data['mod date'];
+
+ $year = 1980 + ( $date >> 9 );
+ $month = ( $date >> 5 ) & 15;
+ $day = $date & 31;
+ $hour = ( $time >> 11 ) & 31;
+ $minute = ( $time >> 5 ) & 63;
+ $second = ( $time & 31 ) * 2;
+ $timestamp = sprintf( "%04d%02d%02d%02d%02d%02d",
+ $year, $month, $day, $hour, $minute, $second );
+
+ // Convert the character set in the file name
+ if ( !function_exists( 'iconv' )
+ || $this->testBit( $data['general bits'], self::GENERAL_UTF8 ) )
+ {
+ $name = $data['name'];
+ } else {
+ $name = iconv( 'CP437', 'UTF-8', $data['name'] );
+ }
+
+ // Compile a data array for the user, with a sensible format
+ $userData = array(
+ 'name' => $name,
+ 'mtime' => $timestamp,
+ 'size' => $data['uncompressed size'],
+ );
+ call_user_func( $this->callback, $userData );
+ }
+ }
+
+ /**
+ * Interpret ZIP64 "extra field" data and return an associative array.
+ */
+ function unpackZip64Extra( $extraField ) {
+ $extraHeaderInfo = array(
+ 'id' => 2,
+ 'size' => 2,
+ );
+ $extraHeaderSize = $this->getStructSize( $extraHeaderInfo );
+
+ $zip64ExtraInfo = array(
+ 'uncompressed size' => 8,
+ 'compressed size' => 8,
+ 'local header offset' => 8,
+ 'disk number start' => 4,
+ );
+
+ $extraPos = 0;
+ while ( $extraPos < strlen( $extraField ) ) {
+ $extra = $this->unpack( $extraField, $extraHeaderInfo, $extraPos );
+ $extraPos += $extraHeaderSize;
+ $extra += $this->unpack( $extraField,
+ array( 'data' => array( 'string', $extra['size'] ) ),
+ $extraPos );
+ $extraPos += $extra['size'];
+
+ if ( $extra['id'] == self::ZIP64_EXTRA_HEADER ) {
+ return $this->unpack( $extra['data'], $zip64ExtraInfo );
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the length of the file.
+ */
+ function getFileLength() {
+ if ( $this->fileLength === null ) {
+ $stat = fstat( $this->file );
+ $this->fileLength = $stat['size'];
+ }
+ return $this->fileLength;
+ }
+
+ /**
+ * Get the file contents from a given offset. If there are not enough bytes
+ * in the file to satisfy the request, an exception will be thrown.
+ *
+ * @param $start The byte offset of the start of the block.
+ * @param $length The number of bytes to return. If omitted, the remainder
+ * of the file will be returned.
+ *
+ * @return string
+ */
+ function getBlock( $start, $length = null ) {
+ $fileLength = $this->getFileLength();
+ if ( $start >= $fileLength ) {
+ $this->error( 'zip-bad', "getBlock() requested position $start, " .
+ "file length is $fileLength" );
+ }
+ if ( $length === null ) {
+ $length = $fileLength - $start;
+ }
+ $end = $start + $length;
+ if ( $end > $fileLength ) {
+ $this->error( 'zip-bad', "getBlock() requested end position $end, " .
+ "file length is $fileLength" );
+ }
+ $startSeg = floor( $start / self::SEGSIZE );
+ $endSeg = ceil( $end / self::SEGSIZE );
+
+ $block = '';
+ for ( $segIndex = $startSeg; $segIndex <= $endSeg; $segIndex++ ) {
+ $block .= $this->getSegment( $segIndex );
+ }
+
+ $block = substr( $block,
+ $start - $startSeg * self::SEGSIZE,
+ $length );
+
+ if ( strlen( $block ) < $length ) {
+ $this->error( 'zip-bad', 'getBlock() returned an unexpectedly small amount of data' );
+ }
+
+ return $block;
+ }
+
+ /**
+ * Get a section of the file starting at position $segIndex * self::SEGSIZE,
+ * of length self::SEGSIZE. The result is cached. This is a helper function
+ * for getBlock().
+ *
+ * If there are not enough bytes in the file to satsify the request, the
+ * return value will be truncated. If a request is made for a segment beyond
+ * the end of the file, an empty string will be returned.
+ */
+ function getSegment( $segIndex ) {
+ if ( !isset( $this->buffer[$segIndex] ) ) {
+ $bytePos = $segIndex * self::SEGSIZE;
+ if ( $bytePos >= $this->getFileLength() ) {
+ $this->buffer[$segIndex] = '';
+ return '';
+ }
+ if ( fseek( $this->file, $bytePos ) ) {
+ $this->error( 'zip-bad', "seek to $bytePos failed" );
+ }
+ $seg = fread( $this->file, self::SEGSIZE );
+ if ( $seg === false ) {
+ $this->error( 'zip-bad', "read from $bytePos failed" );
+ }
+ $this->buffer[$segIndex] = $seg;
+ }
+ return $this->buffer[$segIndex];
+ }
+
+ /**
+ * Get the size of a structure in bytes. See unpack() for the format of $struct.
+ */
+ function getStructSize( $struct ) {
+ $size = 0;
+ foreach ( $struct as $type ) {
+ if ( is_array( $type ) ) {
+ list( $typeName, $fieldSize ) = $type;
+ $size += $fieldSize;
+ } else {
+ $size += $type;
+ }
+ }
+ return $size;
+ }
+
+ /**
+ * Unpack a binary structure. This is like the built-in unpack() function
+ * except nicer.
+ *
+ * @param $string The binary data input
+ *
+ * @param $struct An associative array giving structure members and their
+ * types. In the key is the field name. The value may be either an
+ * integer, in which case the field is a little-endian unsigned integer
+ * encoded in the given number of bytes, or an array, in which case the
+ * first element of the array is the type name, and the subsequent
+ * elements are type-dependent parameters. Only one such type is defined:
+ * - "string": The second array element gives the length of string.
+ * Not null terminated.
+ *
+ * @param $offset The offset into the string at which to start unpacking.
+ *
+ * @return Unpacked associative array. Note that large integers in the input
+ * may be represented as floating point numbers in the return value, so
+ * the use of weak comparison is advised.
+ */
+ function unpack( $string, $struct, $offset = 0 ) {
+ $size = $this->getStructSize( $struct );
+ if ( $offset + $size > strlen( $string ) ) {
+ $this->error( 'zip-bad', 'unpack() would run past the end of the supplied string' );
+ }
+
+ $data = array();
+ $pos = $offset;
+ foreach ( $struct as $key => $type ) {
+ if ( is_array( $type ) ) {
+ list( $typeName, $fieldSize ) = $type;
+ switch ( $typeName ) {
+ case 'string':
+ $data[$key] = substr( $string, $pos, $fieldSize );
+ $pos += $fieldSize;
+ break;
+ default:
+ throw new MWException( __METHOD__.": invalid type \"$typeName\"" );
+ }
+ } else {
+ // Unsigned little-endian integer
+ $length = intval( $type );
+ $bytes = substr( $string, $pos, $length );
+
+ // Calculate the value. Use an algorithm which automatically
+ // upgrades the value to floating point if necessary.
+ $value = 0;
+ for ( $i = $length - 1; $i >= 0; $i-- ) {
+ $value *= 256;
+ $value += ord( $string[$pos + $i] );
+ }
+
+ // Throw an exception if there was loss of precision
+ if ( $value > pow( 2, 52 ) ) {
+ $this->error( 'zip-unsupported', 'number too large to be stored in a double. ' .
+ 'This could happen if we tried to unpack a 64-bit structure ' .
+ 'at an invalid location.' );
+ }
+ $data[$key] = $value;
+ $pos += $length;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Returns a bit from a given position in an integer value, converted to
+ * boolean.
+ *
+ * @param $value integer
+ * @param $bitIndex The index of the bit, where 0 is the LSB.
+ */
+ function testBit( $value, $bitIndex ) {
+ return (bool)( ( $value >> $bitIndex ) & 1 );
+ }
+
+ /**
+ * Debugging helper function which dumps a string in hexdump -C format.
+ */
+ function hexDump( $s ) {
+ $n = strlen( $s );
+ for ( $i = 0; $i < $n; $i += 16 ) {
+ printf( "%08X ", $i );
+ for ( $j = 0; $j < 16; $j++ ) {
+ print " ";
+ if ( $j == 8 ) {
+ print " ";
+ }
+ if ( $i + $j >= $n ) {
+ print " ";
+ } else {
+ printf( "%02X", ord( $s[$i + $j] ) );
+ }
+ }
+
+ print " |";
+ for ( $j = 0; $j < 16; $j++ ) {
+ if ( $i + $j >= $n ) {
+ print " ";
+ } elseif ( ctype_print( $s[$i + $j] ) ) {
+ print $s[$i + $j];
+ } else {
+ print '.';
+ }
+ }
+ print "|\n";
+ }
+ }
+}
+
+/**
+ * Internal exception class. Will be caught by private code.
+ */
+class ZipDirectoryReaderError extends Exception {
+ var $code;
+
+ function __construct( $code ) {
+ $this->code = $code;
+ parent::__construct( "ZipDirectoryReader error: $code" );
+ }
+
+ function getErrorCode() {
+ return $this->code;
+ }
+}
diff --git a/includes/Credits.php b/includes/actions/CreditsAction.php
index e4c8be54..1040085b 100644
--- a/includes/Credits.php
+++ b/includes/actions/CreditsAction.php
@@ -19,51 +19,58 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* @file
+ * @ingroup Actions
* @author <evan@wikitravel.org>
*/
-class Credits {
+class CreditsAction extends FormlessAction {
+
+ public function getName() {
+ return 'credits';
+ }
+
+ public function getRestriction() {
+ return null;
+ }
+
+ protected function getDescription() {
+ return wfMsg( 'creditspage' );
+ }
+
/**
* This is largely cadged from PageHistory::history
- * @param $article Article object
+ *
+ * @return String HTML
*/
- public static function showPage( Article $article ) {
- global $wgOut;
-
+ public function onView() {
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 ( $this->page->getID() == 0 ) {
$s = wfMsg( 'nocredits' );
} else {
- $s = self::getCredits( $article, -1 );
+ $s = $this->getCredits( -1 );
}
- $wgOut->addHTML( $s );
-
wfProfileOut( __METHOD__ );
+
+ return Html::rawElement( 'div', array( 'id' => 'mw-credits' ), $s );
}
/**
- * Get a list of contributors of $article
- * @param $article Article object
+ * Get a list of contributors
+ *
* @param $cnt Int: maximum list of contributors to show
* @param $showIfMax Bool: whether to contributors if there more than $cnt
* @return String: html
*/
- public static function getCredits( Article $article, $cnt, $showIfMax = true ) {
+ public function getCredits( $cnt, $showIfMax = true ) {
wfProfileIn( __METHOD__ );
$s = '';
if ( isset( $cnt ) && $cnt != 0 ) {
- $s = self::getAuthor( $article );
+ $s = self::getAuthor( $this->page );
if ( $cnt > 1 || $cnt < 0 ) {
- $s .= ' ' . self::getContributors( $article, $cnt - 1, $showIfMax );
+ $s .= ' ' . $this->getContributors( $cnt - 1, $showIfMax );
}
}
@@ -74,8 +81,9 @@ class Credits {
/**
* Get the last author with the last modification time
* @param $article Article object
+ * @return String HTML
*/
- protected static function getAuthor( Article $article ) {
+ protected static function getAuthor( Page $article ) {
global $wgLang;
$user = User::newFromId( $article->getUser() );
@@ -88,28 +96,27 @@ class Credits {
$d = '';
$t = '';
}
- return wfMsgExt( 'lastmodifiedatby', 'parsemag', $d, $t, self::userLink( $user ), $user->getName() );
+ return wfMessage( 'lastmodifiedatby', $d, $t )->rawParams( self::userLink( $user ) )->params( $user->getName() )->escaped();
}
/**
* Get a list of contributors of $article
- * @param $article Article object
* @param $cnt Int: maximum list of contributors to show
* @param $showIfMax Bool: whether to contributors if there more than $cnt
* @return String: html
*/
- protected static function getContributors( Article $article, $cnt, $showIfMax ) {
+ protected function getContributors( $cnt, $showIfMax ) {
global $wgLang, $wgHiddenPrefs;
- $contributors = $article->getContributors();
+ $contributors = $this->page->getContributors();
$others_link = false;
# Hmm... too many to fit!
if ( $cnt > 0 && $contributors->count() > $cnt ) {
- $others_link = self::othersLink( $article );
+ $others_link = $this->othersLink();
if ( !$showIfMax )
- return wfMsgExt( 'othercontribs', 'parsemag', $others_link, $contributors->count() );
+ return wfMessage( 'othercontribs' )->rawParams( $others_link )->params( $contributors->count() )->escaped();
}
$real_names = array();
@@ -118,7 +125,7 @@ class Credits {
# Sift for real versus user names
foreach ( $contributors as $user ) {
- $cnt--;
+ $cnt--;
if ( $user->isLoggedIn() ) {
$link = self::link( $user );
if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
@@ -143,21 +150,15 @@ class Credits {
# "ThisSite user(s) A, B and C"
if ( count( $user_names ) ) {
- $user = wfMsgExt(
- 'siteusers',
- 'parsemag',
- $wgLang->listToText( $user_names ), count( $user_names )
- );
+ $user = wfMessage( 'siteusers' )->rawParams( $wgLang->listToText( $user_names ) )->params(
+ count( $user_names ) )->escaped();
} else {
$user = false;
}
if ( count( $anon_ips ) ) {
- $anon = wfMsgExt(
- 'anonusers',
- 'parsemag',
- $wgLang->listToText( $anon_ips ), count( $anon_ips )
- );
+ $anon = wfMessage( 'anonusers' )->rawParams( $wgLang->listToText( $anon_ips ) )->params(
+ count( $anon_ips ) )->escaped();
} else {
$anon = false;
}
@@ -165,17 +166,16 @@ class Credits {
# 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 ) {
+ if ( $s !== false ) {
array_push( $fulllist, $s );
}
}
- # Make the list into text...
- $creds = $wgLang->listToText( $fulllist );
-
+ $count = count( $fulllist );
# "Based on work by ..."
- return strlen( $creds )
- ? wfMsgExt( 'othercontribs', 'parsemag', $creds, count( $fulllist ) )
+ return $count
+ ? wfMessage( 'othercontribs' )->rawParams(
+ $wgLang->listToText( $fulllist ) )->params( $count )->escaped()
: '';
}
@@ -185,19 +185,18 @@ class Credits {
* @return String: html
*/
protected static function link( User $user ) {
- global $wgUser, $wgHiddenPrefs;
+ global $wgHiddenPrefs;
if ( !in_array( 'realname', $wgHiddenPrefs ) && !$user->isAnon() ) {
$real = $user->getRealName();
} else {
$real = false;
}
- $skin = $wgUser->getSkin();
- $page = $user->isAnon() ?
- SpecialPage::getTitleFor( 'Contributions', $user->getName() ) :
- $user->getUserPage();
+ $page = $user->isAnon()
+ ? SpecialPage::getTitleFor( 'Contributions', $user->getName() )
+ : $user->getUserPage();
- return $skin->link( $page, htmlspecialchars( $real ? $real : $user->getName() ) );
+ return Linker::link( $page, htmlspecialchars( $real ? $real : $user->getName() ) );
}
/**
@@ -214,7 +213,7 @@ class Credits {
if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
return $link;
} else {
- return wfMsgExt( 'siteuser', 'parsemag', $link, $user->getName() );
+ return wfMessage( 'siteuser' )->rawParams( $link )->params( $user->getName() )->escaped();
}
}
}
@@ -224,11 +223,9 @@ class Credits {
* @param $article Article object
* @return String: html
*/
- protected static function othersLink( Article $article ) {
- global $wgUser;
- $skin = $wgUser->getSkin();
- return $skin->link(
- $article->getTitle(),
+ protected function othersLink() {
+ return Linker::link(
+ $this->getTitle(),
wfMsgHtml( 'others' ),
array(),
array( 'action' => 'credits' ),
diff --git a/includes/actions/DeletetrackbackAction.php b/includes/actions/DeletetrackbackAction.php
new file mode 100644
index 00000000..0efebdf5
--- /dev/null
+++ b/includes/actions/DeletetrackbackAction.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Delete a trackback on a page
+ *
+ * Copyright © 2011 Alexandre Emsenhuber
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ */
+
+class DeletetrackbackAction extends FormlessAction {
+
+ public function getName() {
+ return 'deletetrackback';
+ }
+
+ public function getRestriction() {
+ return 'delete';
+ }
+
+ protected function getDescription() {
+ return '';
+ }
+
+ protected function checkCanExecute( User $user ) {
+ if ( !$user->matchEditToken( $this->getRequest()->getVal( 'token' ) ) ) {
+ throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
+ }
+
+ return parent::checkCanExecute( $user );
+ }
+
+ public function onView() {
+ $db = wfGetDB( DB_MASTER );
+ $db->delete( 'trackbacks', array( 'tb_id' => $this->getRequest()->getInt( 'tbid' ) ) );
+
+ $this->getOutput()->addWikiMsg( 'trackbackdeleteok' );
+ $this->getTitle()->invalidateCache();
+ }
+}
diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php
new file mode 100644
index 00000000..b0b5f259
--- /dev/null
+++ b/includes/actions/InfoAction.php
@@ -0,0 +1,151 @@
+<?php
+/**
+ * Display informations about a page.
+ * Very inefficient for the moment.
+ *
+ * Copyright © 2011 Alexandre Emsenhuber
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ */
+
+class InfoAction extends FormlessAction {
+
+ public function getName() {
+ return 'info';
+ }
+
+ public function getRestriction() {
+ return 'read';
+ }
+
+ protected function getDescription() {
+ return '';
+ }
+
+ public function requiresWrite() {
+ return false;
+ }
+
+ public function requiresUnblock() {
+ return false;
+ }
+
+ protected function getPageTitle() {
+ return wfMsg( 'pageinfo-title', $this->getTitle()->getSubjectPage()->getPrefixedText() );
+ }
+
+ public function onView() {
+ global $wgDisableCounters;
+
+ $title = $this->getTitle()->getSubjectPage();
+
+ $pageInfo = self::pageCountInfo( $title );
+ $talkInfo = self::pageCountInfo( $title->getTalkPage() );
+
+ return Html::rawElement( 'table', array( 'class' => 'wikitable mw-page-info' ),
+ Html::rawElement( 'tr', array(),
+ Html::element( 'th', array(), '' ) .
+ Html::element( 'th', array(), wfMsg( 'pageinfo-subjectpage' ) ) .
+ Html::element( 'th', array(), wfMsg( 'pageinfo-talkpage' ) )
+ ) .
+ Html::rawElement( 'tr', array(),
+ Html::element( 'th', array( 'colspan' => 3 ), wfMsg( 'pageinfo-header-edits' ) )
+ ) .
+ Html::rawElement( 'tr', array(),
+ Html::element( 'td', array(), wfMsg( 'pageinfo-edits' ) ) .
+ Html::element( 'td', array(), $this->getLang()->formatNum( $pageInfo['edits'] ) ) .
+ Html::element( 'td', array(), $this->getLang()->formatNum( $talkInfo['edits'] ) )
+ ) .
+ Html::rawElement( 'tr', array(),
+ Html::element( 'td', array(), wfMsg( 'pageinfo-authors' ) ) .
+ Html::element( 'td', array(), $this->getLang()->formatNum( $pageInfo['authors'] ) ) .
+ Html::element( 'td', array(), $this->getLang()->formatNum( $talkInfo['authors'] ) )
+ ) .
+ ( !$this->getUser()->isAllowed( 'unwatchedpages' ) ? '' :
+ Html::rawElement( 'tr', array(),
+ Html::element( 'th', array( 'colspan' => 3 ), wfMsg( 'pageinfo-header-watchlist' ) )
+ ) .
+ Html::rawElement( 'tr', array(),
+ Html::element( 'td', array(), wfMsg( 'pageinfo-watchers' ) ) .
+ Html::element( 'td', array( 'colspan' => 2 ), $this->getLang()->formatNum( $pageInfo['watchers'] ) )
+ )
+ ).
+ ( $wgDisableCounters ? '' :
+ Html::rawElement( 'tr', array(),
+ Html::element( 'th', array( 'colspan' => 3 ), wfMsg( 'pageinfo-header-views' ) )
+ ) .
+ Html::rawElement( 'tr', array(),
+ Html::element( 'td', array(), wfMsg( 'pageinfo-views' ) ) .
+ Html::element( 'td', array(), $this->getLang()->formatNum( $pageInfo['views'] ) ) .
+ Html::element( 'td', array(), $this->getLang()->formatNum( $talkInfo['views'] ) )
+ ) .
+ Html::rawElement( 'tr', array(),
+ Html::element( 'td', array(), wfMsg( 'pageinfo-viewsperedit' ) ) .
+ Html::element( 'td', array(), $this->getLang()->formatNum( sprintf( '%.2f', $pageInfo['edits'] ? $pageInfo['views'] / $pageInfo['edits'] : 0 ) ) ) .
+ Html::element( 'td', array(), $this->getLang()->formatNum( sprintf( '%.2f', $talkInfo['edits'] ? $talkInfo['views'] / $talkInfo['edits'] : 0 ) ) )
+ )
+ )
+ );
+ }
+
+ /**
+ * Return the total number of edits and number of unique editors
+ * on a given page. If page does not exist, returns false.
+ *
+ * @param $title Title object
+ * @return mixed array or boolean false
+ */
+ public static function pageCountInfo( $title ) {
+ $id = $title->getArticleId();
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $watchers = (int)$dbr->selectField(
+ 'watchlist',
+ 'COUNT(*)',
+ array(
+ 'wl_title' => $title->getDBkey(),
+ 'wl_namespace' => $title->getNamespace()
+ ),
+ __METHOD__
+ );
+
+ $edits = (int)$dbr->selectField(
+ 'revision',
+ 'COUNT(rev_page)',
+ array( 'rev_page' => $id ),
+ __METHOD__
+ );
+
+ $authors = (int)$dbr->selectField(
+ 'revision',
+ 'COUNT(DISTINCT rev_user_text)',
+ array( 'rev_page' => $id ),
+ __METHOD__
+ );
+
+ $views = (int)$dbr->selectField(
+ 'page',
+ 'page_counter',
+ array( 'page_id' => $id ),
+ __METHOD__
+ );
+
+ return array( 'watchers' => $watchers, 'edits' => $edits,
+ 'authors' => $authors, 'views' => $views );
+ }
+}
diff --git a/includes/actions/MarkpatrolledAction.php b/includes/actions/MarkpatrolledAction.php
new file mode 100644
index 00000000..a5d76627
--- /dev/null
+++ b/includes/actions/MarkpatrolledAction.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Mark a revision as patrolled on a page
+ *
+ * Copyright © 2011 Alexandre Emsenhuber
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ */
+
+class MarkpatrolledAction extends FormlessAction {
+
+ public function getName() {
+ return 'markpatrolled';
+ }
+
+ public function getRestriction() {
+ return 'read';
+ }
+
+ protected function getDescription() {
+ return '';
+ }
+
+ protected function checkCanExecute( User $user ) {
+ if ( !$user->matchEditToken( $this->getRequest()->getVal( 'token' ), $this->getRequest()->getInt( 'rcid' ) ) ) {
+ throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
+ }
+
+ return parent::checkCanExecute( $user );
+ }
+
+ public function onView() {
+ $rc = RecentChange::newFromId( $this->getRequest()->getInt( 'rcid' ) );
+
+ if ( is_null( $rc ) ) {
+ throw new ErrorPageError( 'markedaspatrollederror', 'markedaspatrollederrortext' );
+ }
+
+ $errors = $rc->doMarkPatrolled( $this->getUser() );
+
+ if ( in_array( array( 'rcpatroldisabled' ), $errors ) ) {
+ throw new ErrorPageError( 'rcpatroldisabled', 'rcpatroldisabledtext' );
+ }
+
+ if ( in_array( array( 'hookaborted' ), $errors ) ) {
+ // The hook itself has handled any output
+ return;
+ }
+
+ # It would be nice to see where the user had actually come from, but for now just guess
+ $returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges';
+ $return = SpecialPage::getTitleFor( $returnto );
+
+ if ( in_array( array( 'markedaspatrollederror-noautopatrol' ), $errors ) ) {
+ $this->getOutput()->setPageTitle( wfMsg( 'markedaspatrollederror' ) );
+ $this->getOutput()->addWikiMsg( 'markedaspatrollederror-noautopatrol' );
+ $this->getOutput()->returnToMain( null, $return );
+ return;
+ }
+
+ if ( !empty( $errors ) ) {
+ $this->getOutput()->showPermissionsErrorPage( $errors );
+ return;
+ }
+
+ # Inform the user
+ $this->getOutput()->setPageTitle( wfMsg( 'markedaspatrolled' ) );
+ $this->getOutput()->addWikiMsg( 'markedaspatrolledtext', $rc->getTitle()->getPrefixedText() );
+ $this->getOutput()->returnToMain( null, $return );
+ }
+}
diff --git a/includes/actions/PurgeAction.php b/includes/actions/PurgeAction.php
new file mode 100644
index 00000000..29cbf3ae
--- /dev/null
+++ b/includes/actions/PurgeAction.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * 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 distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ * @author <evan@wikitravel.org>
+ */
+
+class PurgeAction extends FormAction {
+
+ private $redirectParams;
+
+ public function getName() {
+ return 'purge';
+ }
+
+ public function getRestriction() {
+ return null;
+ }
+
+ public function requiresUnblock() {
+ return false;
+ }
+
+ public function getDescription() {
+ return '';
+ }
+
+ /**
+ * Just get an empty form with a single submit button
+ * @return array
+ */
+ protected function getFormFields() {
+ return array();
+ }
+
+ public function onSubmit( $data ) {
+ $this->page->doPurge();
+ return true;
+ }
+
+ /**
+ * purge is slightly weird because it can be either formed or formless depending
+ * on user permissions
+ */
+ public function show() {
+ $this->setHeaders();
+
+ // This will throw exceptions if there's a problem
+ $this->checkCanExecute( $this->getUser() );
+
+ if ( $this->getUser()->isAllowed( 'purge' ) ) {
+ $this->redirectParams = wfArrayToCGI( array_diff_key(
+ $this->getRequest()->getQueryValues(),
+ array( 'title' => null, 'action' => null )
+ ) );
+ $this->onSubmit( array() );
+ $this->onSuccess();
+ } else {
+ $this->redirectParams = $this->getRequest()->getVal( 'redirectparams', '' );
+ $form = $this->getForm();
+ if ( $form->show() ) {
+ $this->onSuccess();
+ }
+ }
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ $form->setSubmitText( wfMsg( 'confirm_purge_button' ) );
+ }
+
+ protected function preText() {
+ return wfMessage( 'confirm-purge-top' )->parse();
+ }
+
+ protected function postText() {
+ return wfMessage( 'confirm-purge-bottom' )->parse();
+ }
+
+ public function onSuccess() {
+ $this->getOutput()->redirect( $this->getTitle()->getFullUrl( $this->redirectParams ) );
+ }
+}
diff --git a/includes/actions/RevertAction.php b/includes/actions/RevertAction.php
new file mode 100644
index 00000000..bcb8cd8b
--- /dev/null
+++ b/includes/actions/RevertAction.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * File reversion user interface
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Action
+ * @ingroup Media
+ * @author Alexandre Emsenhuber
+ * @author Rob Church <robchur@gmail.com>
+ */
+
+/**
+ * Dummy class for pages not in NS_FILE
+ *
+ * @ingroup Action
+ */
+class RevertAction extends Action {
+
+ public function getName() {
+ return 'revert';
+ }
+
+ public function getRestriction() {
+ return 'read';
+ }
+
+ public function show() {
+ $this->getOutput()->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
+ }
+
+ public function execute() {}
+}
+
+/**
+ * Class for pages in NS_FILE
+ *
+ * @ingroup Action
+ */
+class RevertFileAction extends FormAction {
+ protected $oldFile;
+
+ public function getName() {
+ return 'revert';
+ }
+
+ public function getRestriction() {
+ return 'upload';
+ }
+
+ protected function checkCanExecute( User $user ) {
+ parent::checkCanExecute( $user );
+
+ $oldimage = $this->getRequest()->getText( 'oldimage' );
+ if ( strlen( $oldimage ) < 16
+ || strpos( $oldimage, '/' ) !== false
+ || strpos( $oldimage, '\\' ) !== false )
+ {
+ throw new ErrorPageError( 'internalerror', 'unexpected', array( 'oldimage', $oldimage ) );
+ }
+
+ $this->oldFile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $this->getTitle(), $oldimage );
+ if ( !$this->oldFile->exists() ) {
+ throw new ErrorPageError( '', 'filerevert-badversion' );
+ }
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ $form->setWrapperLegend( wfMsgHtml( 'filerevert-legend' ) );
+ $form->setSubmitText( wfMsg( 'filerevert-submit' ) );
+ $form->addHiddenField( 'oldimage', $this->getRequest()->getText( 'oldimage' ) );
+ }
+
+ protected function getFormFields() {
+ global $wgContLang;
+
+ $timestamp = $this->oldFile->getTimestamp();
+
+ return array(
+ 'intro' => array(
+ 'type' => 'info',
+ 'vertical-label' => true,
+ 'raw' => true,
+ 'default' => wfMsgExt( 'filerevert-intro', 'parse', $this->getTitle()->getText(),
+ $this->getLang()->date( $timestamp, true ), $this->getLang()->time( $timestamp, true ),
+ wfExpandUrl( $this->page->getFile()->getArchiveUrl( $this->getRequest()->getText( 'oldimage' ) ),
+ PROTO_CURRENT
+ ) )
+ ),
+ 'comment' => array(
+ 'type' => 'text',
+ 'label-message' => 'filerevert-comment',
+ 'default' => wfMsgForContent( 'filerevert-defaultcomment',
+ $wgContLang->date( $timestamp, false, false ), $wgContLang->time( $timestamp, false, false ) ),
+ )
+ );
+ }
+
+ public function onSubmit( $data ) {
+ $source = $this->page->getFile()->getArchiveVirtualUrl( $this->getRequest()->getText( 'oldimage' ) );
+ $comment = $data['comment'];
+ // TODO: Preserve file properties from database instead of reloading from file
+ return $this->page->getFile()->upload( $source, $comment, $comment );
+ }
+
+ public function onSuccess() {
+ $timestamp = $this->oldFile->getTimestamp();
+ $this->getOutput()->addHTML( wfMsgExt( 'filerevert-success', 'parse', $this->getTitle()->getText(),
+ $this->getLang()->date( $timestamp, true ),
+ $this->getLang()->time( $timestamp, true ),
+ wfExpandUrl( $this->page->getFile()->getArchiveUrl( $this->getRequest()->getText( 'oldimage' ) ),
+ PROTO_CURRENT
+ ) ) );
+ $this->getOutput()->returnToMain( false, $this->getTitle() );
+ }
+
+ protected function getPageTitle() {
+ return wfMsg( 'filerevert', $this->getTitle()->getText() );
+ }
+
+ protected function getDescription() {
+ return wfMsg(
+ 'filerevert-backlink',
+ Linker::linkKnown( $this->getTitle() )
+ );
+ }
+}
diff --git a/includes/actions/RevisiondeleteAction.php b/includes/actions/RevisiondeleteAction.php
new file mode 100644
index 00000000..2ac03d18
--- /dev/null
+++ b/includes/actions/RevisiondeleteAction.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * An action that just pass the request to Special:RevisionDelete
+ *
+ * Copyright © 2011 Alexandre Emsenhuber
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ * @author Alexandre Emsenhuber
+ */
+
+class RevisiondeleteAction extends FormlessAction {
+
+ public function getName() {
+ return 'revisiondelete';
+ }
+
+ public function getRestriction() {
+ return null;
+ }
+
+ public function requiresUnblock() {
+ return false;
+ }
+
+ public function getDescription() {
+ return '';
+ }
+
+ public function onView() {
+ return '';
+ }
+
+ public function show() {
+ $special = SpecialPageFactory::getPage( 'Revisiondelete' );
+ $special->setContext( $this->getContext() );
+ $special->execute( '' );
+ }
+}
diff --git a/includes/actions/RollbackAction.php b/includes/actions/RollbackAction.php
new file mode 100644
index 00000000..9036ebf5
--- /dev/null
+++ b/includes/actions/RollbackAction.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * Edit rollback user interface
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Action
+ */
+
+/**
+ * User interface for the rollback action
+ *
+ * @ingroup Action
+ */
+class RollbackAction extends FormlessAction {
+
+ public function getName() {
+ return 'rollback';
+ }
+
+ public function getRestriction() {
+ return 'rollback';
+ }
+
+ public function onView() {
+ $details = null;
+
+ $request = $this->getRequest();
+
+ $result = $this->page->doRollback(
+ $request->getVal( 'from' ),
+ $request->getText( 'summary' ),
+ $request->getVal( 'token' ),
+ $request->getBool( 'bot' ),
+ $details,
+ $this->getUser()
+ );
+
+ if ( in_array( array( 'actionthrottledtext' ), $result ) ) {
+ throw new ThrottledError;
+ }
+
+ if ( isset( $result[0][0] ) && ( $result[0][0] == 'alreadyrolled' || $result[0][0] == 'cantrollback' ) ) {
+ $this->getOutput()->setPageTitle( wfMsg( 'rollbackfailed' ) );
+ $errArray = $result[0];
+ $errMsg = array_shift( $errArray );
+ $this->getOutput()->addWikiMsgArray( $errMsg, $errArray );
+
+ if ( isset( $details['current'] ) ) {
+ $current = $details['current'];
+
+ if ( $current->getComment() != '' ) {
+ $this->getOutput()->addHTML( wfMessage( 'editcomment' )->rawParams(
+ Linker::formatComment( $current->getComment() ) )->parse() );
+ }
+ }
+
+ 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.
+ # Remove any 'readonlytext' error manually.
+ $out = array();
+ foreach ( $result as $error ) {
+ if ( $error != array( 'readonlytext' ) ) {
+ $out [] = $error;
+ }
+ }
+ $this->getOutput()->showPermissionsErrorPage( $out );
+
+ return;
+ }
+
+ if ( $result == array( array( 'readonlytext' ) ) ) {
+ throw new ReadOnlyError;
+ }
+
+ $current = $details['current'];
+ $target = $details['target'];
+ $newId = $details['newid'];
+ $this->getOutput()->setPageTitle( wfMsg( 'actioncomplete' ) );
+ $this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
+
+ if ( $current->getUserText() === '' ) {
+ $old = wfMsg( 'rev-deleted-user' );
+ } else {
+ $old = Linker::userLink( $current->getUser(), $current->getUserText() )
+ . Linker::userToolLinks( $current->getUser(), $current->getUserText() );
+ }
+
+ $new = Linker::userLink( $target->getUser(), $target->getUserText() )
+ . Linker::userToolLinks( $target->getUser(), $target->getUserText() );
+ $this->getOutput()->addHTML( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) );
+ $this->getOutput()->returnToMain( false, $this->getTitle() );
+
+ if ( !$request->getBool( 'hidediff', false ) && !$this->getUser()->getBoolOption( 'norollbackdiff', false ) ) {
+ $de = new DifferenceEngine( $this->getTitle(), $current->getId(), $newId, false, true );
+ $de->showDiff( '', '' );
+ }
+ }
+
+ protected function getDescription() {
+ return '';
+ }
+}
diff --git a/includes/actions/WatchAction.php b/includes/actions/WatchAction.php
new file mode 100644
index 00000000..52e66754
--- /dev/null
+++ b/includes/actions/WatchAction.php
@@ -0,0 +1,183 @@
+<?php
+/**
+ * Performs the watch and unwatch actions on a page
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ */
+
+class WatchAction extends FormAction {
+
+ public function getName() {
+ return 'watch';
+ }
+
+ public function getRestriction() {
+ return 'read';
+ }
+
+ public function requiresUnblock() {
+ return false;
+ }
+
+ protected function getDescription() {
+ return wfMsg( 'addwatch' );
+ }
+
+ /**
+ * Just get an empty form with a single submit button
+ * @return array
+ */
+ protected function getFormFields() {
+ return array();
+ }
+
+ public function onSubmit( $data ) {
+ wfProfileIn( __METHOD__ );
+ self::doWatch( $this->getTitle(), $this->getUser() );
+ wfProfileOut( __METHOD__ );
+ return true;
+ }
+
+ /**
+ * This can be either formed or formless depending on the session token given
+ */
+ public function show() {
+ $this->setHeaders();
+
+ $user = $this->getUser();
+ // This will throw exceptions if there's a problem
+ $this->checkCanExecute( $user );
+
+ // Must have valid token for this action/title
+ $salt = array( $this->getName(), $this->getTitle()->getDBkey() );
+
+ if ( $user->matchEditToken( $this->getRequest()->getVal( 'token' ), $salt ) ) {
+ $this->onSubmit( array() );
+ $this->onSuccess();
+ } else {
+ $form = $this->getForm();
+ if ( $form->show() ) {
+ $this->onSuccess();
+ }
+ }
+ }
+
+ protected function checkCanExecute( User $user ) {
+ // Must be logged in
+ if ( $user->isAnon() ) {
+ throw new ErrorPageError( 'watchnologin', 'watchnologintext' );
+ }
+
+ return parent::checkCanExecute( $user );
+ }
+
+ public static function doWatch( Title $title, User $user ) {
+ $page = new Article( $title, 0 );
+
+ if ( wfRunHooks( 'WatchArticle', array( &$user, &$page ) ) ) {
+ $user->addWatch( $title );
+ wfRunHooks( 'WatchArticleComplete', array( &$user, &$page ) );
+ }
+ return true;
+ }
+
+ public static function doUnwatch( Title $title, User $user ) {
+ $page = new Article( $title, 0 );
+
+ if ( wfRunHooks( 'UnwatchArticle', array( &$user, &$page ) ) ) {
+ $user->removeWatch( $title );
+ wfRunHooks( 'UnwatchArticleComplete', array( &$user, &$page ) );
+ }
+ return true;
+ }
+
+ /**
+ * Get token to watch (or unwatch) a page for a user
+ *
+ * @param Title $title Title object of page to watch
+ * @param User $title User for whom the action is going to be performed
+ * @param string $action Optionally override the action to 'unwatch'
+ * @return string Token
+ * @since 1.18
+ */
+ public static function getWatchToken( Title $title, User $user, $action = 'watch' ) {
+ if ( $action != 'unwatch' ) {
+ $action = 'watch';
+ }
+ $salt = array( $action, $title->getDBkey() );
+
+ // This token stronger salted and not compatible with ApiWatch
+ // It's title/action specific because index.php is GET and API is POST
+ return $user->editToken( $salt );
+ }
+
+ /**
+ * Get token to unwatch (or watch) a page for a user
+ *
+ * @param Title $title Title object of page to unwatch
+ * @param User $title User for whom the action is going to be performed
+ * @param string $action Optionally override the action to 'watch'
+ * @return string Token
+ * @since 1.18
+ */
+ public static function getUnwatchToken( Title $title, User $user, $action = 'unwatch' ) {
+ return self::getWatchToken( $title, $user, $action );
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ $form->setSubmitText( wfMsg( 'confirm-watch-button' ) );
+ }
+
+ protected function preText() {
+ return wfMessage( 'confirm-watch-top' )->parse();
+ }
+
+ public function onSuccess() {
+ $this->getOutput()->addWikiMsg( 'addedwatchtext', $this->getTitle()->getPrefixedText() );
+ }
+}
+
+class UnwatchAction extends WatchAction {
+
+ public function getName() {
+ return 'unwatch';
+ }
+
+ protected function getDescription() {
+ return wfMsg( 'removewatch' );
+ }
+
+ public function onSubmit( $data ) {
+ wfProfileIn( __METHOD__ );
+ self::doUnwatch( $this->getTitle(), $this->getUser() );
+ wfProfileOut( __METHOD__ );
+ return true;
+ }
+
+ protected function alterForm( HTMLForm $form ) {
+ $form->setSubmitText( wfMsg( 'confirm-unwatch-button' ) );
+ }
+
+ protected function preText() {
+ return wfMessage( 'confirm-unwatch-top' )->parse();
+ }
+
+ public function onSuccess() {
+ $this->getOutput()->addWikiMsg( 'removedwatchtext', $this->getTitle()->getPrefixedText() );
+ }
+}
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index 175fa6a1..30e42934 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 5, 2006
*
@@ -30,7 +30,7 @@
* The class functions are divided into several areas of functionality:
*
* Module parameters: Derived classes can define getAllowedParams() to specify
- * which parameters to expect,h ow to parse and validate them.
+ * which parameters to expect, how to parse and validate them.
*
* Profiling: various methods to allow keeping tabs on various tasks and their
* time costs
@@ -122,6 +122,9 @@ abstract class ApiBase {
/**
* Get the name of the module as shown in the profiler log
+ *
+ * @param $db DatabaseBase
+ *
* @return string
*/
public function getModuleProfileName( $db = false ) {
@@ -171,6 +174,24 @@ abstract class ApiBase {
}
/**
+ * Create a new RequestContext object to use e.g. for calls to other parts
+ * the software.
+ * The object will have the WebRequest and the User object set to the ones
+ * used in this instance.
+ *
+ * @return RequestContext
+ */
+ public function createContext() {
+ global $wgUser;
+
+ $context = new RequestContext;
+ $context->setRequest( $this->getMain()->getRequest() );
+ $context->setUser( $wgUser ); /// @todo FIXME: we should store the User object
+
+ return $context;
+ }
+
+ /**
* Set warning section for this module. Users should monitor this
* section to notice any changes in API. Multiple calls to this
* function will result in the warning messages being separated by
@@ -178,7 +199,8 @@ abstract class ApiBase {
* @param $warning string Warning message
*/
public function setWarning( $warning ) {
- $data = $this->getResult()->getData();
+ $result = $this->getResult();
+ $data = $result->getData();
if ( isset( $data['warnings'][$this->getModuleName()] ) ) {
// Don't add duplicate warnings
$warn_regex = preg_quote( $warning, '/' );
@@ -188,13 +210,13 @@ abstract class ApiBase {
$oldwarning = $data['warnings'][$this->getModuleName()]['*'];
// If there is a warning already, append it to the existing one
$warning = "$oldwarning\n$warning";
- $this->getResult()->unsetValue( 'warnings', $this->getModuleName() );
+ $result->unsetValue( 'warnings', $this->getModuleName() );
}
$msg = array();
ApiResult::setContent( $msg, $warning );
- $this->getResult()->disableSizeCheck();
- $this->getResult()->addValue( 'warnings', $this->getModuleName(), $msg );
- $this->getResult()->enableSizeCheck();
+ $result->disableSizeCheck();
+ $result->addValue( 'warnings', $this->getModuleName(), $msg );
+ $result->enableSizeCheck();
}
/**
@@ -235,8 +257,7 @@ abstract class ApiBase {
$msg .= "\nThis module only accepts POST requests";
}
if ( $this->isReadMode() || $this->isWriteMode() ||
- $this->mustBePosted() )
- {
+ $this->mustBePosted() ) {
$msg .= "\n";
}
@@ -246,20 +267,8 @@ abstract class ApiBase {
$msg .= "Parameters:\n$paramsMsg";
}
- // Examples
- $examples = $this->getExamples();
- if ( $examples !== false ) {
- if ( !is_array( $examples ) ) {
- $examples = array(
- $examples
- );
- }
-
- if ( count( $examples ) > 0 ) {
- $msg .= 'Example' . ( count( $examples ) > 1 ? 's' : '' ) . ":\n ";
- $msg .= implode( $lnPrfx, $examples ) . "\n";
- }
- }
+ $msg .= $this->makeHelpArrayToString( $lnPrfx, "Example", $this->getExamples() );
+ $msg .= $this->makeHelpArrayToString( $lnPrfx, "Help page", $this->getHelpUrls() );
if ( $this->getMain()->getShowVersions() ) {
$versions = $this->getVersion();
@@ -283,9 +292,33 @@ abstract class ApiBase {
}
/**
+ * @param $prefix string Text to split output items
+ * @param $title string What is being output
+ * @param $input string|array
+ * @return string
+ */
+ protected function makeHelpArrayToString( $prefix, $title, $input ) {
+ if ( $input === false ) {
+ return '';
+ }
+ if ( !is_array( $input ) ) {
+ $input = array(
+ $input
+ );
+ }
+
+ if ( count( $input ) > 0 ) {
+ $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n ";
+ $msg .= implode( $prefix, $input ) . "\n";
+ return $msg;
+ }
+ return '';
+ }
+
+ /**
* Generates the parameter descriptions for this module, to be displayed in the
* module's help.
- * @return string
+ * @return string or false
*/
public function makeHelpMsgParameters() {
$params = $this->getFinalParams();
@@ -293,7 +326,8 @@ abstract class ApiBase {
$paramsDescription = $this->getFinalParamDescription();
$msg = '';
- $paramPrefix = "\n" . str_repeat( ' ', 19 );
+ $paramPrefix = "\n" . str_repeat( ' ', 24 );
+ $descWordwrap = "\n" . str_repeat( ' ', 28 );
foreach ( $params as $paramName => $paramSettings ) {
$desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : '';
if ( is_array( $desc ) ) {
@@ -336,12 +370,16 @@ abstract class ApiBase {
$choices[] = $t;
}
}
- $desc .= $paramPrefix . $nothingPrompt . $prompt . implode( ', ', $choices );
+ $desc .= $paramPrefix . $nothingPrompt . $prompt;
+ $choicesstring = implode( ', ', $choices );
+ $desc .= wordwrap( $choicesstring, 100, $descWordwrap );
} else {
switch ( $type ) {
case 'namespace':
// Special handling because namespaces are type-limited, yet they are not given
- $desc .= $paramPrefix . $prompt . implode( ', ', MWNamespace::getValidNamespaces() );
+ $desc .= $paramPrefix . $prompt;
+ $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ),
+ 100, $descWordwrap );
break;
case 'limit':
$desc .= $paramPrefix . "No more than {$paramSettings[self :: PARAM_MAX]}";
@@ -371,7 +409,7 @@ abstract class ApiBase {
$isArray = is_array( $paramSettings[self::PARAM_TYPE] );
if ( !$isArray
- || $isArray && count( $paramSettings[self::PARAM_TYPE] ) > self::LIMIT_SML1) {
+ || $isArray && count( $paramSettings[self::PARAM_TYPE] ) > self::LIMIT_SML1 ) {
$desc .= $paramPrefix . "Maximum number of values " .
self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
}
@@ -386,7 +424,7 @@ abstract class ApiBase {
$desc .= $paramPrefix . "Default: $default";
}
- $msg .= sprintf( " %-14s - %s\n", $this->encodeParamName( $paramName ), $desc );
+ $msg .= sprintf( " %-19s - %s\n", $this->encodeParamName( $paramName ), $desc );
}
return $msg;
@@ -398,9 +436,13 @@ abstract class ApiBase {
/**
* Callback for preg_replace_callback() call in makeHelpMsg().
* Replaces a source file name with a link to ViewVC
+ *
+ * @return string
*/
public function makeHelpMsg_callback( $matches ) {
global $wgAutoloadClasses, $wgAutoloadLocalClasses;
+
+ $file = '';
if ( isset( $wgAutoloadLocalClasses[get_class( $this )] ) ) {
$file = $wgAutoloadLocalClasses[get_class( $this )];
} elseif ( isset( $wgAutoloadClasses[get_class( $this )] ) ) {
@@ -421,7 +463,7 @@ abstract class ApiBase {
// This is necessary to make stuff like ApiMain::getVersion()
// returning the version string for ApiBase work
if ( $path ) {
- return "{$matches[0]}\n http://svn.wikimedia.org/" .
+ return "{$matches[0]}\n https://svn.wikimedia.org/" .
"viewvc/mediawiki/trunk/" . dirname( $path ) .
"/{$matches[2]}";
}
@@ -437,8 +479,8 @@ abstract class ApiBase {
}
/**
- * Returns usage examples for this module. Return null if no examples are available.
- * @return mixed string or array of strings
+ * Returns usage examples for this module. Return false if no examples are available.
+ * @return false|string|array
*/
protected function getExamples() {
return false;
@@ -449,7 +491,7 @@ abstract class ApiBase {
* value) or (parameter name) => (array with PARAM_* constants as keys)
* Don't call this function directly: use getFinalParams() to allow
* hooks to modify parameters as needed.
- * @return array
+ * @return array or false
*/
protected function getAllowedParams() {
return false;
@@ -459,7 +501,7 @@ abstract class ApiBase {
* Returns an array of parameter descriptions.
* Don't call this functon directly: use getFinalParamDescription() to
* allow hooks to modify descriptions as needed.
- * @return array
+ * @return array or false
*/
protected function getParamDescription() {
return false;
@@ -468,7 +510,7 @@ abstract class ApiBase {
/**
* Get final list of parameters, after hooks have had a chance to
* tweak it as needed.
- * @return array
+ * @return array or false
*/
public function getFinalParams() {
$params = $this->getAllowedParams();
@@ -554,6 +596,54 @@ abstract class ApiBase {
}
/**
+ * Generates the possible errors requireOnlyOneParameter() can die with
+ *
+ * @param $params array
+ * @return array
+ */
+ public function getRequireOnlyOneParameterErrorMessages( $params ) {
+ $p = $this->getModulePrefix();
+ $params = implode( ", {$p}", $params );
+
+ return array(
+ array( 'code' => "{$p}missingparam", 'info' => "One of the parameters {$p}{$params} is required" ),
+ array( 'code' => "{$p}invalidparammix", 'info' => "The parameters {$p}{$params} can not be used together" )
+ );
+ }
+
+ /**
+ * Die if more than one of a certain set of parameters is set and not false.
+ *
+ * @param $params array
+ */
+ public function requireMaxOneParameter( $params ) {
+ $required = func_get_args();
+ array_shift( $required );
+
+ $intersection = array_intersect( array_keys( array_filter( $params,
+ array( $this, "parameterNotEmpty" ) ) ), $required );
+
+ if ( count( $intersection ) > 1 ) {
+ $this->dieUsage( 'The parameters ' . implode( ', ', $intersection ) . ' can not be used together', 'invalidparammix' );
+ }
+ }
+
+ /**
+ * Generates the possible error requireMaxOneParameter() can die with
+ *
+ * @param $params array
+ * @return array
+ */
+ public function getRequireMaxOneParameterErrorMessages( $params ) {
+ $p = $this->getModulePrefix();
+ $params = implode( ", {$p}", $params );
+
+ return array(
+ array( 'code' => "{$p}invalidparammix", 'info' => "The parameters {$p}{$params} can not be used together" )
+ );
+ }
+
+ /**
* Callback function used in requireOnlyOneParameter to check whether reequired parameters are set
*
* @param $x object Parameter to check is not null/false
@@ -564,7 +654,7 @@ abstract class ApiBase {
}
/**
- * @deprecated use MWNamespace::getValidNamespaces()
+ * @deprecated since 1.17 use MWNamespace::getValidNamespaces()
*/
public static function getValidNamespaces() {
return MWNamespace::getValidNamespaces();
@@ -576,7 +666,7 @@ abstract class ApiBase {
* @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
+ * @return bool
*/
protected function getWatchlistValue ( $watchlist, $titleObj, $userOption = null ) {
@@ -617,17 +707,17 @@ abstract class ApiBase {
* @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 ) {
+ protected function setWatch( $watch, $titleObj, $userOption = null ) {
$value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
if ( $value === null ) {
return;
}
- $articleObj = new Article( $titleObj );
+ global $wgUser;
if ( $value ) {
- $articleObj->doWatch();
+ WatchAction::doWatch( $titleObj, $wgUser );
} else {
- $articleObj->doUnwatch();
+ WatchAction::doUnwatch( $titleObj, $wgUser );
}
}
@@ -707,16 +797,18 @@ abstract class ApiBase {
$enforceLimits = isset ( $paramSettings[self::PARAM_RANGE_ENFORCE] )
? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
- if ( !is_null( $min ) || !is_null( $max ) ) {
- if ( is_array( $value ) ) {
- $value = array_map( 'intval', $value );
- foreach ( $value as &$v ) {
+ if ( is_array( $value ) ) {
+ $value = array_map( 'intval', $value );
+ if ( !is_null( $min ) || !is_null( $max ) ) {
+ foreach ( $value as &$v ) {
$this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
}
- } else {
- $value = intval( $value );
- $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
- }
+ }
+ } else {
+ $value = intval( $value );
+ if ( !is_null( $min ) || !is_null( $max ) ) {
+ $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
+ }
}
break;
case 'limit':
@@ -745,14 +837,13 @@ abstract class ApiBase {
}
break;
case 'timestamp':
- if ( $multi ) {
- ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
- }
- $value = wfTimestamp( TS_UNIX, $value );
- if ( $value === 0 ) {
- $this->dieUsage( "Invalid value '$value' for timestamp parameter $encParamName", "badtimestamp_{$encParamName}" );
+ if ( is_array( $value ) ) {
+ foreach ( $value as $key => $val ) {
+ $value[$key] = $this->validateTimestamp( $val, $encParamName );
+ }
+ } else {
+ $value = $this->validateTimestamp( $value, $encParamName );
}
- $value = wfTimestamp( TS_MW, $value );
break;
case 'user':
if ( !is_array( $value ) ) {
@@ -785,7 +876,7 @@ abstract class ApiBase {
if ( $deprecated && $value !== false ) {
$this->setWarning( "The $encParamName parameter has been deprecated." );
}
- } else if ( $required ) {
+ } elseif ( $required ) {
$this->dieUsageMsg( array( 'missingparam', $paramName ) );
}
@@ -848,8 +939,8 @@ abstract class ApiBase {
* Prints usage info on failure.
* @param $paramName string Parameter name
* @param $value int Parameter value
- * @param $min int Minimum value
- * @param $max int Maximum value for users
+ * @param $min int|null Minimum value
+ * @param $max int|null Maximum value for users
* @param $botMax int Maximum value for sysops/bots
* @param $enforceLimits Boolean Whether to enforce (die) if value is outside limits
*/
@@ -884,6 +975,19 @@ abstract class ApiBase {
}
/**
+ * @param $value string
+ * @param $paramName string
+ * @return string
+ */
+ function validateTimestamp( $value, $paramName ) {
+ $value = wfTimestamp( TS_UNIX, $value );
+ if ( $value === 0 ) {
+ $this->dieUsage( "Invalid value '$value' for timestamp parameter $paramName", "badtimestamp_{$paramName}" );
+ }
+ return wfTimestamp( TS_MW, $value );
+ }
+
+ /**
* Adds a warning to the output, else dies
*
* @param $msg String Message to show as a warning, or error message if dying
@@ -924,7 +1028,7 @@ abstract class ApiBase {
* @param $extradata array Data to add to the <error> element; array in ApiResult format
*/
public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
- wfProfileClose();
+ Profiler::instance()->close();
throw new UsageException( $description, $this->encodeParamName( $errorCode ), $httpRespCode, $extradata );
}
@@ -940,7 +1044,8 @@ abstract class ApiBase {
'ns-specialprotected' => array( 'code' => 'unsupportednamespace', 'info' => "Pages in the Special namespace can't be edited" ),
'protectedinterface' => array( 'code' => 'protectednamespace-interface', 'info' => "You're not allowed to edit interface messages" ),
'namespaceprotected' => array( 'code' => 'protectednamespace', 'info' => "You're not allowed to edit pages in the ``\$1'' namespace" ),
- 'customcssjsprotected' => array( 'code' => 'customcssjsprotected', 'info' => "You're not allowed to edit custom CSS and JavaScript pages" ),
+ 'customcssprotected' => array( 'code' => 'customcssprotected', 'info' => "You're not allowed to edit custom CSS pages" ),
+ 'customjsprotected' => array( 'code' => 'customjsprotected', 'info' => "You're not allowed to edit custom JavaScript pages" ),
'cascadeprotected' => array( 'code' => 'cascadeprotected', 'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page" ),
'protectedpagetext' => array( 'code' => 'protectedpage', 'info' => "The ``\$1'' right is required to edit this page" ),
'protect-cantedit' => array( 'code' => 'cantedit', 'info' => "You can't protect this page because you can't edit it" ),
@@ -1041,6 +1146,7 @@ abstract class ApiBase {
'sharedfile-exists' => array( 'code' => 'fileexists-sharedrepo-perm', 'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.' ),
'mustbeposted' => array( 'code' => 'mustbeposted', 'info' => "The \$1 module requires a POST request" ),
'show' => array( 'code' => 'show', 'info' => 'Incorrect parameter - mutually exclusive values may not be supplied' ),
+ 'specialpage-cantexecute' => array( 'code' => 'specialpage-cantexecute', 'info' => "You don't have permission to view the results of this special page" ),
// ApiEditPage messages
'noimageredirect-anon' => array( 'code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects" ),
@@ -1058,12 +1164,22 @@ abstract class ApiBase {
'emptynewsection' => array( 'code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.' ),
'revwrongpage' => array( 'code' => 'revwrongpage', 'info' => "r\$1 is not a revision of ``\$2''" ),
'undo-failure' => array( 'code' => 'undofailure', 'info' => 'Undo failed due to conflicting intermediate edits' ),
+
+ // Messages from WikiPage::doEit()
+ 'edit-hook-aborted' => array( 'code' => 'edit-hook-aborted', 'info' => "Your edit was aborted by an ArticleSave hook" ),
+ 'edit-gone-missing' => array( 'code' => 'edit-gone-missing', 'info' => "The page you tried to edit doesn't seem to exist anymore" ),
+ 'edit-conflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ),
+ 'edit-already-exists' => array( 'code' => 'edit-already-exists', 'info' => "It seems the page you tried to create already exist" ),
// uploadMsgs
'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.' ),
+
+ 'filename-tooshort' => array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
+ 'illegal-filename' => array( 'code' => 'illegal-filename', 'info' => 'The filename is not allowed' ),
+ 'filetype-missing' => array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ),
);
/**
@@ -1077,9 +1193,14 @@ abstract class ApiBase {
/**
* Output the error message related to a certain array
- * @param $error array Element of a getUserPermissionsErrors()-style array
+ * @param $error (array|string) Element of a getUserPermissionsErrors()-style array
*/
public function dieUsageMsg( $error ) {
+ # most of the time we send a 1 element, so we might as well send it as
+ # a string and make this an array here.
+ if( is_string( $error ) ) {
+ $error = array( $error );
+ }
$parsed = $this->parseMsg( $error );
$this->dieUsage( $parsed['info'], $parsed['code'] );
}
@@ -1091,6 +1212,14 @@ abstract class ApiBase {
*/
public function parseMsg( $error ) {
$key = array_shift( $error );
+
+ // Check whether the error array was nested
+ // array( array( <code>, <params> ), array( <another_code>, <params> ) )
+ if( is_array( $key ) ){
+ $error = $key;
+ $key = array_shift( $error );
+ }
+
if ( isset( self::$messageMap[$key] ) ) {
return array( 'code' =>
wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ),
@@ -1098,6 +1227,7 @@ abstract class ApiBase {
wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error )
);
}
+
// If the key isn't present, throw an "unknown error"
return $this->parseMsg( array( 'unknownerror', $key ) );
}
@@ -1144,7 +1274,7 @@ abstract class ApiBase {
/**
* Returns whether this module requires a Token to execute
- * @returns bool
+ * @return bool
*/
public function needsToken() {
return false;
@@ -1152,17 +1282,18 @@ abstract class ApiBase {
/**
* Returns the token salt if there is one, '' if the module doesn't require a salt, else false if the module doesn't need a token
- * @returns bool
+ * @return bool
*/
public function getTokenSalt() {
return false;
}
/**
- * Gets the user for whom to get the watchlist
- *
- * @returns User
- */
+ * Gets the user for whom to get the watchlist
+ *
+ * @param $params array
+ * @return User
+ */
public function getWatchlistUser( $params ) {
global $wgUser;
if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
@@ -1184,6 +1315,13 @@ abstract class ApiBase {
}
/**
+ * @return false|string|array Returns a false if the module has no help url, else returns a (array of) string
+ */
+ public function getHelpUrls() {
+ return false;
+ }
+
+ /**
* 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' => ... )
*/
@@ -1363,6 +1501,6 @@ abstract class ApiBase {
* @return string
*/
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiBase.php 82730 2011-02-24 16:03:05Z reedy $';
+ return __CLASS__ . ': $Id: ApiBase.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php
index 875b8aeb..8d718ab2 100644
--- a/includes/api/ApiBlock.php
+++ b/includes/api/ApiBlock.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 4, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,9 +37,6 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*/
class ApiBlock extends ApiBase {
- /**
- * Std ctor.
- */
public function __construct( $main, $action ) {
parent::__construct( $main, $action );
}
@@ -51,56 +48,71 @@ class ApiBlock extends ApiBase {
* of success. If it fails, the result will specify the nature of the error.
*/
public function execute() {
- global $wgUser, $wgBlockAllowsUTEdit;
+ global $wgUser;
$params = $this->extractRequestParams();
if ( $params['gettoken'] ) {
- $res['blocktoken'] = $wgUser->editToken();
+ $res['blocktoken'] = $wgUser->editToken( '', $this->getMain()->getRequest() );
$this->getResult()->addValue( null, $this->getModuleName(), $res );
return;
}
if ( !$wgUser->isAllowed( 'block' ) ) {
- $this->dieUsageMsg( array( 'cantblock' ) );
+ $this->dieUsageMsg( 'cantblock' );
}
# bug 15810: blocked admins should have limited access here
if ( $wgUser->isBlocked() ) {
- $status = IPBlockForm::checkUnblockSelf( $params['user'] );
+ $status = SpecialBlock::checkUnblockSelf( $params['user'] );
if ( $status !== true ) {
$this->dieUsageMsg( array( $status ) );
}
}
if ( $params['hidename'] && !$wgUser->isAllowed( 'hideuser' ) ) {
- $this->dieUsageMsg( array( 'canthide' ) );
+ $this->dieUsageMsg( 'canthide' );
}
- if ( $params['noemail'] && !IPBlockForm::canBlockEmail( $wgUser ) ) {
- $this->dieUsageMsg( array( 'cantblock-email' ) );
+ if ( $params['noemail'] && !SpecialBlock::canBlockEmail( $wgUser ) ) {
+ $this->dieUsageMsg( 'cantblock-email' );
}
- $form = new IPBlockForm( '' );
- $form->BlockAddress = $params['user'];
- $form->BlockReason = ( is_null( $params['reason'] ) ? '' : $params['reason'] );
- $form->BlockReasonList = 'other';
- $form->BlockExpiry = ( $params['expiry'] == 'never' ? 'infinite' : $params['expiry'] );
- $form->BlockOther = '';
- $form->BlockAnonOnly = $params['anononly'];
- $form->BlockCreateAccount = $params['nocreate'];
- $form->BlockEnableAutoblock = $params['autoblock'];
- $form->BlockEmail = $params['noemail'];
- $form->BlockHideName = $params['hidename'];
- $form->BlockAllowUsertalk = $params['allowusertalk'] && $wgBlockAllowsUTEdit;
- $form->BlockReblock = $params['reblock'];
-
- $userID = $expiry = null;
- $retval = $form->doBlock( $userID, $expiry );
- if ( count( $retval ) ) {
+ $data = array(
+ 'Target' => $params['user'],
+ 'Reason' => array(
+ is_null( $params['reason'] ) ? '' : $params['reason'],
+ 'other',
+ is_null( $params['reason'] ) ? '' : $params['reason']
+ ),
+ 'Expiry' => $params['expiry'] == 'never' ? 'infinite' : $params['expiry'],
+ 'HardBlock' => !$params['anononly'],
+ 'CreateAccount' => $params['nocreate'],
+ 'AutoBlock' => $params['autoblock'],
+ 'DisableEmail' => $params['noemail'],
+ 'HideUser' => $params['hidename'],
+ 'DisableUTEdit' => $params['allowusertalk'],
+ 'AlreadyBlocked' => $params['reblock'],
+ 'Watch' => $params['watchuser'],
+ 'Confirm' => true,
+ );
+
+ $retval = SpecialBlock::processForm( $data );
+ if ( $retval !== true ) {
// We don't care about multiple errors, just report one of them
$this->dieUsageMsg( $retval );
}
+ list( $target, /*...*/ ) = SpecialBlock::getTargetAndType( $params['user'] );
$res['user'] = $params['user'];
- $res['userID'] = intval( $userID );
- $res['expiry'] = ( $expiry == Block::infinity() ? 'infinite' : wfTimestamp( TS_ISO_8601, $expiry ) );
+ $res['userID'] = $target instanceof User ? $target->getId() : 0;
+
+ $block = Block::newFromTarget( $target );
+ if( $block instanceof Block ){
+ $res['expiry'] = $block->mExpiry == wfGetDB( DB_SLAVE )->getInfinity()
+ ? 'infinite'
+ : wfTimestamp( TS_ISO_8601, $block->mExpiry );
+ } else {
+ # should be unreachable
+ $res['expiry'] = '';
+ }
+
$res['reason'] = $params['reason'];
if ( $params['anononly'] ) {
$res['anononly'] = '';
@@ -120,6 +132,9 @@ class ApiBlock extends ApiBase {
if ( $params['allowusertalk'] ) {
$res['allowusertalk'] = '';
}
+ if ( $params['watchuser'] ) {
+ $res['watchuser'] = '';
+ }
$this->getResult()->addValue( null, $this->getModuleName(), $res );
}
@@ -149,6 +164,7 @@ class ApiBlock extends ApiBase {
'hidename' => false,
'allowusertalk' => false,
'reblock' => false,
+ 'watchuser' => false,
);
}
@@ -166,6 +182,7 @@ class ApiBlock extends ApiBase {
'hidename' => 'Hide the username from the block log. (Requires the "hideuser" right.)',
'allowusertalk' => 'Allow the user to edit their own talk page (depends on $wgBlockAllowsUTEdit)',
'reblock' => 'If the user is already blocked, overwrite the existing block',
+ 'watchuser' => 'Watch the user/IP\'s user and talk pages',
);
}
@@ -198,7 +215,11 @@ class ApiBlock extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Block';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiBlock.php 77192 2010-11-23 22:05:27Z btongminh $';
+ return __CLASS__ . ': $Id: ApiBlock.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php
new file mode 100644
index 00000000..d43fa53f
--- /dev/null
+++ b/includes/api/ApiComparePages.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ *
+ * Created on May 1, 2011
+ *
+ * Copyright © 2011 Sam Reed
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 ApiComparePages extends ApiBase {
+
+ public function __construct( $main, $action ) {
+ parent::__construct( $main, $action );
+ }
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+
+ $rev1 = $this->revisionOrTitle( $params['fromrev'], $params['fromtitle'] );
+ $rev2 = $this->revisionOrTitle( $params['torev'], $params['totitle'] );
+
+ $de = new DifferenceEngine( null,
+ $rev1,
+ $rev2,
+ null, // rcid
+ true,
+ false );
+
+ $vals = array();
+ if ( isset( $params['fromtitle'] ) ) {
+ $vals['fromtitle'] = $params['fromtitle'];
+ }
+ $vals['fromrevid'] = $rev1;
+ if ( isset( $params['totitle'] ) ) {
+ $vals['totitle'] = $params['totitle'];
+ }
+ $vals['torevid'] = $rev2;
+
+ $difftext = $de->getDiffBody();
+
+ if ( $difftext === false ) {
+ $this->dieUsage( 'The diff cannot be retrieved. ' .
+ 'Maybe one or both revisions do not exist or you do not have permission to view them.', 'baddiff' );
+ } else {
+ ApiResult::setContent( $vals, $difftext );
+ }
+
+ $this->getResult()->addValue( null, $this->getModuleName(), $vals );
+ }
+
+ /**
+ * @param $revision int
+ * @param $titleText string
+ * @return int
+ */
+ private function revisionOrTitle( $revision, $titleText ) {
+ if( $revision ){
+ return $revision;
+ } elseif( $titleText ) {
+ $title = Title::newFromText( $titleText );
+ if( !$title ){
+ $this->dieUsageMsg( array( 'invalidtitle', $titleText ) );
+ }
+ return $title->getLatestRevID();
+ }
+ $this->dieUsage( 'inputneeded', 'A title or a revision number is needed for both the from and the to parameters' );
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'fromtitle' => null,
+ 'fromrev' => array(
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
+ 'totitle' => null,
+ 'torev' => array(
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'fromtitle' => 'First title to compare',
+ 'fromrev' => 'First revision to compare',
+ 'totitle' => 'Second title to compare',
+ 'torev' => 'Second revision to compare',
+ );
+ }
+ public function getDescription() {
+ return array(
+ 'Get the difference between 2 pages',
+ 'You must pass a revision number or a page title for each part (1 and 2)'
+ );
+ }
+
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => 'inputneeded', 'info' => 'A title or a revision is needed' ),
+ array( 'invalidtitle', 'title' ),
+ array( 'code' => 'baddiff', 'info' => 'The diff cannot be retrieved. Maybe one or both revisions do not exist or you do not have permission to view them.' ),
+ ) );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'api.php?action=compare&fromrev=1&torev=2',
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiComparePages.php 92400 2011-07-17 16:51:11Z reedy $';
+ }
+}
diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php
index fbf62391..58befbfe 100644
--- a/includes/api/ApiDelete.php
+++ b/includes/api/ApiDelete.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Jun 30, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -65,7 +65,7 @@ class ApiDelete extends ApiBase {
}
}
if ( !$titleObj->exists() ) {
- $this->dieUsageMsg( array( 'notanarticle' ) );
+ $this->dieUsageMsg( 'notanarticle' );
}
$reason = ( isset( $params['reason'] ) ? $params['reason'] : null );
@@ -146,22 +146,17 @@ class ApiDelete extends ApiBase {
}
$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
- if ( $article->doDeleteArticle( $reason ) ) {
- wfRunHooks( 'ArticleDeleteComplete', array( &$article, &$wgUser, $reason, $article->getId() ) );
+ if ( $article->doDeleteArticle( $reason, false, 0, true, $error ) ) {
return array();
+ } else {
+ return array( array( 'cannotdelete', $article->getTitle()->getPrefixedText() ) );
}
- return array( array( 'cannotdelete', $article->mTitle->getPrefixedText() ) );
}
/**
- * @static
* @param $token
- * @param $title
+ * @param $title Title
* @param $oldimage
* @param $reason
* @param $suppress bool
@@ -255,12 +250,15 @@ class ApiDelete extends ApiBase {
}
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'invalidtitle', 'title' ),
- array( 'nosuchpageid', 'pageid' ),
- array( 'notanarticle' ),
- array( 'hookaborted', 'error' ),
- ) );
+ return array_merge( parent::getPossibleErrors(),
+ $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
+ array(
+ array( 'invalidtitle', 'title' ),
+ array( 'nosuchpageid', 'pageid' ),
+ array( 'notanarticle' ),
+ array( 'hookaborted', 'error' ),
+ )
+ );
}
public function needsToken() {
@@ -278,7 +276,11 @@ class ApiDelete extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Delete';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiDelete.php 77141 2010-11-23 10:04:38Z ialex $';
+ return __CLASS__ . ': $Id: ApiDelete.php 104449 2011-11-28 15:52:04Z reedy $';
}
-} \ No newline at end of file
+}
diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php
index f83bfdc9..947267f3 100644
--- a/includes/api/ApiDisabled.php
+++ b/includes/api/ApiDisabled.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 25, 2008
*
- * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -70,6 +70,6 @@ class ApiDisabled extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiDisabled.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiDisabled.php 79969 2011-01-10 22:36:26Z reedy $';
}
}
diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php
index 75cc0ba2..ffc82640 100644
--- a/includes/api/ApiEditPage.php
+++ b/includes/api/ApiEditPage.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on August 16, 2007
*
@@ -50,38 +50,40 @@ class ApiEditPage extends ApiBase {
is_null( $params['prependtext'] ) &&
$params['undo'] == 0 )
{
- $this->dieUsageMsg( array( 'missingtext' ) );
+ $this->dieUsageMsg( 'missingtext' );
}
$titleObj = Title::newFromText( $params['title'] );
if ( !$titleObj || $titleObj->isExternal() ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
-
- if( $params['redirect'] ) {
- if( $titleObj->isRedirect() ) {
+
+ $apiResult = $this->getResult();
+
+ if ( $params['redirect'] ) {
+ if ( $titleObj->isRedirect() ) {
$oldTitle = $titleObj;
-
+
$titles = Title::newFromRedirectArray( Revision::newFromTitle( $oldTitle )->getText( Revision::FOR_THIS_USER ) );
+ // array_shift( $titles );
$redirValues = array();
foreach ( $titles as $id => $newTitle ) {
-
- if( !isset( $titles[ $id - 1 ] ) ) {
+
+ 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 );
+ $apiResult->setIndexedTagName( $redirValues, 'r' );
+ $apiResult->addValue( null, 'redirects', $redirValues );
}
}
@@ -90,10 +92,10 @@ class ApiEditPage extends ApiBase {
$wgTitle = $titleObj;
if ( $params['createonly'] && $titleObj->exists() ) {
- $this->dieUsageMsg( array( 'createonly-exists' ) );
+ $this->dieUsageMsg( 'createonly-exists' );
}
if ( $params['nocreate'] && !$titleObj->exists() ) {
- $this->dieUsageMsg( array( 'nocreate-missing' ) );
+ $this->dieUsageMsg( 'nocreate-missing' );
}
// Now let's check whether we're even allowed to do this
@@ -161,7 +163,7 @@ class ApiEditPage extends ApiBase {
$newtext = $articleObj->getUndoText( $undoRev, $undoafterRev );
if ( $newtext === false ) {
- $this->dieUsageMsg( array( 'undo-failure' ) );
+ $this->dieUsageMsg( 'undo-failure' );
}
$params['text'] = $newtext;
// If no summary was given and we only undid one rev,
@@ -173,10 +175,12 @@ class ApiEditPage extends ApiBase {
// See if the MD5 hash checks out
if ( !is_null( $params['md5'] ) && md5( $toMD5 ) !== $params['md5'] ) {
- $this->dieUsageMsg( array( 'hashcheckfailed' ) );
+ $this->dieUsageMsg( 'hashcheckfailed' );
}
$ep = new EditPage( $articleObj );
+ $ep->setContextTitle( $titleObj );
+
// EditPage wants to parse its stuff from a WebRequest
// That interface kind of sucks, but it's workable
$reqArr = array(
@@ -251,10 +255,10 @@ class ApiEditPage extends ApiBase {
if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $ep->textbox1, &$r ) ) ) {
if ( count( $r ) ) {
$r['result'] = 'Failure';
- $this->getResult()->addValue( null, $this->getModuleName(), $r );
+ $apiResult->addValue( null, $this->getModuleName(), $r );
return;
} else {
- $this->dieUsageMsg( array( 'hookaborted' ) );
+ $this->dieUsageMsg( 'hookaborted' );
}
}
@@ -262,65 +266,65 @@ class ApiEditPage extends ApiBase {
$oldRevId = $articleObj->getRevIdFetched();
$result = null;
// Fake $wgRequest for some hooks inside EditPage
- // FIXME: This interface SUCKS
+ // @todo FIXME: This interface SUCKS
$oldRequest = $wgRequest;
$wgRequest = $req;
- $retval = $ep->internalAttemptSave( $result, $wgUser->isAllowed( 'bot' ) && $params['bot'] );
+ $status = $ep->internalAttemptSave( $result, $wgUser->isAllowed( 'bot' ) && $params['bot'] );
$wgRequest = $oldRequest;
global $wgMaxArticleSize;
- switch( $retval ) {
+ switch( $status->value ) {
case EditPage::AS_HOOK_ERROR:
case EditPage::AS_HOOK_ERROR_EXPECTED:
- $this->dieUsageMsg( array( 'hookaborted' ) );
+ $this->dieUsageMsg( 'hookaborted' );
case EditPage::AS_IMAGE_REDIRECT_ANON:
- $this->dieUsageMsg( array( 'noimageredirect-anon' ) );
+ $this->dieUsageMsg( 'noimageredirect-anon' );
case EditPage::AS_IMAGE_REDIRECT_LOGGED:
- $this->dieUsageMsg( array( 'noimageredirect-logged' ) );
+ $this->dieUsageMsg( 'noimageredirect-logged' );
case EditPage::AS_SPAM_ERROR:
$this->dieUsageMsg( array( 'spamdetected', $result['spam'] ) );
case EditPage::AS_FILTERING:
- $this->dieUsageMsg( array( 'filtered' ) );
+ $this->dieUsageMsg( 'filtered' );
case EditPage::AS_BLOCKED_PAGE_FOR_USER:
- $this->dieUsageMsg( array( 'blockedtext' ) );
+ $this->dieUsageMsg( 'blockedtext' );
case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED:
case EditPage::AS_CONTENT_TOO_BIG:
$this->dieUsageMsg( array( 'contenttoobig', $wgMaxArticleSize ) );
case EditPage::AS_READ_ONLY_PAGE_ANON:
- $this->dieUsageMsg( array( 'noedit-anon' ) );
+ $this->dieUsageMsg( 'noedit-anon' );
case EditPage::AS_READ_ONLY_PAGE_LOGGED:
- $this->dieUsageMsg( array( 'noedit' ) );
+ $this->dieUsageMsg( 'noedit' );
case EditPage::AS_READ_ONLY_PAGE:
$this->dieReadOnly();
case EditPage::AS_RATE_LIMITED:
- $this->dieUsageMsg( array( 'actionthrottledtext' ) );
+ $this->dieUsageMsg( 'actionthrottledtext' );
case EditPage::AS_ARTICLE_WAS_DELETED:
- $this->dieUsageMsg( array( 'wasdeleted' ) );
+ $this->dieUsageMsg( 'wasdeleted' );
case EditPage::AS_NO_CREATE_PERMISSION:
- $this->dieUsageMsg( array( 'nocreate-loggedin' ) );
+ $this->dieUsageMsg( 'nocreate-loggedin' );
case EditPage::AS_BLANK_ARTICLE:
- $this->dieUsageMsg( array( 'blankpage' ) );
+ $this->dieUsageMsg( 'blankpage' );
case EditPage::AS_CONFLICT_DETECTED:
- $this->dieUsageMsg( array( 'editconflict' ) );
+ $this->dieUsageMsg( 'editconflict' );
// case EditPage::AS_SUMMARY_NEEDED: Can't happen since we set wpIgnoreBlankSummary
case EditPage::AS_TEXTBOX_EMPTY:
- $this->dieUsageMsg( array( 'emptynewsection' ) );
+ $this->dieUsageMsg( 'emptynewsection' );
case EditPage::AS_SUCCESS_NEW_ARTICLE:
$r['new'] = '';
@@ -346,20 +350,17 @@ class ApiEditPage extends ApiBase {
break;
case EditPage::AS_SUMMARY_NEEDED:
- $this->dieUsageMsg( array( 'summaryrequired' ) );
+ $this->dieUsageMsg( 'summaryrequired' );
case EditPage::AS_END:
- // This usually means some kind of race condition
- // or DB weirdness occurred. Fall through to throw an unknown
- // error.
-
- // This needs fixing higher up, as Article::doEdit should be
- // used rather than Article::updateArticle, so that specific
- // error conditions can be returned
+ // $status came from WikiPage::doEdit()
+ $errors = $status->getErrorsArray();
+ $this->dieUsageMsg( $errors[0] ); // TODO: Add new errors to message map
+ break;
default:
- $this->dieUsageMsg( array( 'unknownerror', $retval ) );
+ $this->dieUsageMsg( array( 'unknownerror', $status->value ) );
}
- $this->getResult()->addValue( null, $this->getModuleName(), $r );
+ $apiResult->addValue( null, $this->getModuleName(), $r );
}
public function mustBePosted() {
@@ -406,6 +407,8 @@ class ApiEditPage extends ApiBase {
array( 'unknownerror', 'retval' ),
array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ),
array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ),
+ array( 'customcssprotected' ),
+ array( 'customjsprotected' ),
) );
}
@@ -468,12 +471,14 @@ class ApiEditPage extends ApiBase {
'title' => 'Page title',
'section' => 'Section number. 0 for the top section, \'new\' for a new section',
'text' => 'Page content',
- 'token' => 'Edit token. You can get one of these through prop=info',
+ 'token' => array( 'Edit token. You can get one of these through prop=info.',
+ 'The token should always be sent as the last parameter, or at least, after the text parameter'
+ ),
'summary' => 'Edit summary. Also section title when section=new',
'minor' => 'Minor edit',
'notminor' => 'Non-minor edit',
'bot' => 'Mark this edit as bot',
- 'basetimestamp' => array( 'Timestamp of the base revision (gotten through prop=revisions&rvprop=timestamp).',
+ 'basetimestamp' => array( 'Timestamp of the base revision (obtained through prop=revisions&rvprop=timestamp).',
'Used to detect edit conflicts; leave unset to ignore conflicts.'
),
'starttimestamp' => array( 'Timestamp when you obtained the edit token.',
@@ -516,7 +521,11 @@ class ApiEditPage extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Edit';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiEditPage.php 90492 2011-06-20 22:39:10Z reedy $';
+ return __CLASS__ . ': $Id: ApiEditPage.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php
index ab58eb18..9ce43183 100644
--- a/includes/api/ApiEmailUser.php
+++ b/includes/api/ApiEmailUser.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on June 1, 2008
*
@@ -144,7 +144,11 @@ class ApiEmailUser extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:E-mail';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiEmailUser.php 85354 2011-04-04 18:25:31Z demon $';
+ return __CLASS__ . ': $Id: ApiEmailUser.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php
index 6f2df1b8..6ec18463 100644
--- a/includes/api/ApiExpandTemplates.php
+++ b/includes/api/ApiExpandTemplates.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 05, 2007
*
@@ -61,6 +61,10 @@ class ApiExpandTemplates extends ApiBase {
global $wgParser;
$options = new ParserOptions();
+ if ( $params['includecomments'] ) {
+ $options->setRemoveComments( false );
+ }
+
if ( $params['generatexml'] ) {
$wgParser->startExternalParse( $title_obj, $options, OT_PREPROCESS );
$dom = $wgParser->preprocessToDom( $params['text'] );
@@ -86,8 +90,12 @@ class ApiExpandTemplates extends ApiBase {
'title' => array(
ApiBase::PARAM_DFLT => 'API',
),
- 'text' => null,
+ 'text' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ),
'generatexml' => false,
+ 'includecomments' => false,
);
}
@@ -96,11 +104,12 @@ class ApiExpandTemplates extends ApiBase {
'text' => 'Wikitext to convert',
'title' => 'Title of page',
'generatexml' => 'Generate XML parse tree',
+ 'includecomments' => 'Whether to include HTML comments in the output',
);
}
public function getDescription() {
- return 'This module expand all templates in wikitext';
+ return 'Expands all templates in wikitext';
}
protected function getExamples() {
@@ -109,7 +118,11 @@ class ApiExpandTemplates extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#expandtemplates';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiExpandTemplates.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiExpandTemplates.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiFeedContributions.php b/includes/api/ApiFeedContributions.php
new file mode 100644
index 00000000..c06b71af
--- /dev/null
+++ b/includes/api/ApiFeedContributions.php
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ *
+ *
+ * Created on June 06, 2011
+ *
+ * Copyright © 2011 Sam Reed
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * @ingroup API
+ */
+class ApiFeedContributions extends ApiBase {
+
+ public function __construct( $main, $action ) {
+ parent::__construct( $main, $action );
+ }
+
+ /**
+ * This module uses a custom feed wrapper printer.
+ */
+ public function getCustomPrinter() {
+ return new ApiFormatFeedWrapper( $this->getMain() );
+ }
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+
+ global $wgFeed, $wgFeedClasses, $wgSitename, $wgLanguageCode;
+
+ if( !$wgFeed ) {
+ $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
+ }
+
+ if( !isset( $wgFeedClasses[ $params['feedformat'] ] ) ) {
+ $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
+ }
+
+ global $wgMiserMode;
+ if ( $params['showsizediff'] && $wgMiserMode ) {
+ $this->dieUsage( 'Size difference is disabled in Miser Mode', 'sizediffdisabled' );
+ }
+
+ $msg = wfMsgForContent( 'Contributions' );
+ $feedTitle = $wgSitename . ' - ' . $msg . ' [' . $wgLanguageCode . ']';
+ $feedUrl = SpecialPage::getTitleFor( 'Contributions', $params['user'] )->getFullURL();
+
+ $target = $params['user'] == 'newbies'
+ ? 'newbies'
+ : Title::makeTitleSafe( NS_USER, $params['user'] )->getText();
+
+ $feed = new $wgFeedClasses[$params['feedformat']] (
+ $feedTitle,
+ htmlspecialchars( $msg ),
+ $feedUrl
+ );
+
+ $pager = new ContribsPager( array(
+ 'target' => $target,
+ 'namespace' => $params['namespace'],
+ 'year' => $params['year'],
+ 'month' => $params['month'],
+ 'tagFilter' => $params['tagfilter'],
+ 'deletedOnly' => $params['deletedonly'],
+ 'topOnly' => $params['toponly'],
+ 'showSizeDiff' => $params['showsizediff'],
+ ) );
+
+ $feedItems = array();
+ if( $pager->getNumRows() > 0 ) {
+ foreach ( $pager->mResult as $row ) {
+ $feedItems[] = $this->feedItem( $row );
+ }
+ }
+
+ ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
+ }
+
+ protected function feedItem( $row ) {
+ $title = Title::MakeTitle( intval( $row->page_namespace ), $row->page_title );
+ if( $title ) {
+ $date = $row->rev_timestamp;
+ $comments = $title->getTalkPage()->getFullURL();
+ $revision = Revision::newFromRow( $row);
+
+ return new FeedItem(
+ $title->getPrefixedText(),
+ $this->feedItemDesc( $revision ),
+ $title->getFullURL(),
+ $date,
+ $this->feedItemAuthor( $revision ),
+ $comments
+ );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @param $revision Revision
+ * @return string
+ */
+ protected function feedItemAuthor( $revision ) {
+ return $revision->getUserText();
+ }
+
+ /**
+ * @param $revision Revision
+ * @return string
+ */
+ protected function feedItemDesc( $revision ) {
+ if( $revision ) {
+ return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) .
+ htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
+ "</p>\n<hr />\n<div>" .
+ nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
+ }
+ return '';
+ }
+
+ public function getAllowedParams() {
+ global $wgFeedClasses;
+ $feedFormatNames = array_keys( $wgFeedClasses );
+ return array (
+ 'feedformat' => array(
+ ApiBase::PARAM_DFLT => 'rss',
+ ApiBase::PARAM_TYPE => $feedFormatNames
+ ),
+ 'user' => array(
+ ApiBase::PARAM_TYPE => 'user',
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'namespace' => array(
+ ApiBase::PARAM_TYPE => 'namespace',
+ ApiBase::PARAM_ISMULTI => true
+ ),
+ 'year' => array(
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
+ 'month' => array(
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
+ 'tagfilter' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => array_values( ChangeTags::listDefinedTags() ),
+ ApiBase::PARAM_DFLT => '',
+ ),
+ 'deletedonly' => false,
+ 'toponly' => false,
+ 'showsizediff' => false,
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'feedformat' => 'The format of the feed',
+ 'user' => 'What users to get the contributions for',
+ 'namespace' => 'What namespace to filter the contributions by',
+ 'year' => 'From year (and earlier)',
+ 'month' => 'From month (and earlier)',
+ 'tagfilter' => 'Filter contributions that have these tags',
+ 'deletedonly' => 'Show only deleted contributions',
+ 'toponly' => 'Only show edits that are latest revisions',
+ 'showsizediff' => 'Show the size difference between revisions. Disabled in Miser Mode',
+ );
+ }
+
+ public function getDescription() {
+ return 'Returns a user contributions feed';
+ }
+
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => 'feed-unavailable', 'info' => 'Syndication feeds are not available' ),
+ array( 'code' => 'feed-invalid', 'info' => 'Invalid subscription feed type' ),
+ array( 'code' => 'sizediffdisabled', 'info' => 'Size difference is disabled in Miser Mode' ),
+ ) );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'api.php?action=feedcontributions&user=Reedy',
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFeedContributions.php 95607 2011-08-27 19:28:13Z hashar $';
+ }
+} \ No newline at end of file
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
index e1ba61f6..75ce7ca0 100644
--- a/includes/api/ApiFeedWatchlist.php
+++ b/includes/api/ApiFeedWatchlist.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 13, 2006
*
@@ -56,11 +56,19 @@ class ApiFeedWatchlist extends ApiBase {
* Wrap the result as an RSS/Atom feed.
*/
public function execute() {
- global $wgFeedClasses, $wgFeedLimit, $wgSitename, $wgLanguageCode;
+ global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgSitename, $wgLanguageCode;
try {
$params = $this->extractRequestParams();
+ if( !$wgFeed ) {
+ $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' );
+ }
+
+ if( !isset( $wgFeedClasses[ $params['feedformat'] ] ) ) {
+ $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
+ }
+
// limit to the number of hours going from now back
$endTime = wfTimestamp( TS_MW, time() - intval( $params['hours'] * 60 * 60 ) );
@@ -90,7 +98,7 @@ class ApiFeedWatchlist extends ApiBase {
}
// Check for 'allrev' parameter, and if found, show all revisions to each page on wl.
- if ( !is_null( $params['allrev'] ) ) {
+ if ( $params['allrev'] ) {
$fauxReqArr['wlallrev'] = '';
}
@@ -109,10 +117,12 @@ class ApiFeedWatchlist extends ApiBase {
$feedItems[] = $this->createFeedItem( $info );
}
- $feedTitle = $wgSitename . ' - ' . wfMsgForContent( 'watchlist' ) . ' [' . $wgLanguageCode . ']';
+ $msg = wfMsgForContent( 'watchlist' );
+
+ $feedTitle = $wgSitename . ' - ' . $msg . ' [' . $wgLanguageCode . ']';
$feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL();
- $feed = new $wgFeedClasses[$params['feedformat']] ( $feedTitle, htmlspecialchars( wfMsgForContent( 'watchlist' ) ), $feedUrl );
+ $feed = new $wgFeedClasses[$params['feedformat']] ( $feedTitle, htmlspecialchars( $msg ), $feedUrl );
ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems );
@@ -171,7 +181,7 @@ class ApiFeedWatchlist extends ApiBase {
ApiBase::PARAM_MIN => 1,
ApiBase::PARAM_MAX => 72,
),
- 'allrev' => null,
+ 'allrev' => false,
'wlowner' => array(
ApiBase::PARAM_TYPE => 'user'
),
@@ -189,22 +199,33 @@ class ApiFeedWatchlist extends ApiBase {
'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'
+ 'linktodiffs' => 'Link to change differences instead of article pages'
);
}
public function getDescription() {
- return 'This module returns a watchlist feed';
+ return 'Returns a watchlist feed';
+ }
+
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => 'feed-unavailable', 'info' => 'Syndication feeds are not available' ),
+ array( 'code' => 'feed-invalid', 'info' => 'Invalid subscription feed type' ),
+ ) );
}
protected function getExamples() {
return array(
'api.php?action=feedwatchlist',
- 'api.php?action=feedwatchlist&allrev=allrev&linktodiffs=&hours=6'
+ 'api.php?action=feedwatchlist&allrev=&linktodiffs=&hours=6'
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Watchlist_feed';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFeedWatchlist.php 77674 2010-12-03 19:47:22Z catrope $';
+ return __CLASS__ . ': $Id: ApiFeedWatchlist.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiFileRevert.php b/includes/api/ApiFileRevert.php
new file mode 100644
index 00000000..1540fe6c
--- /dev/null
+++ b/includes/api/ApiFileRevert.php
@@ -0,0 +1,189 @@
+<?php
+/**
+ *
+ *
+ * Created on March 5, 2011
+ *
+ * Copyright © 2011 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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( "ApiBase.php" );
+}
+
+/**
+ * @ingroup API
+ */
+class ApiFileRevert extends ApiBase {
+
+ /**
+ * @var File
+ */
+ protected $file;
+ protected $archiveName;
+
+ protected $params;
+
+ public function __construct( $main, $action ) {
+ parent::__construct( $main, $action );
+ }
+
+ public function execute() {
+ global $wgUser;
+
+ $this->params = $this->extractRequestParams();
+ // Extract the file and archiveName from the request parameters
+ $this->validateParameters();
+
+ // Check whether we're allowed to revert this file
+ $this->checkPermissions( $wgUser );
+
+ $sourceUrl = $this->file->getArchiveVirtualUrl( $this->archiveName );
+ $status = $this->file->upload( $sourceUrl, $this->params['comment'], $this->params['comment'] );
+
+ if ( $status->isGood() ) {
+ $result = array( 'result' => 'Success' );
+ } else {
+ $result = array(
+ 'result' => 'Failure',
+ 'errors' => $this->getResult()->convertStatusToArray( $status ),
+ );
+ }
+
+ $this->getResult()->addValue( null, $this->getModuleName(), $result );
+
+ }
+
+ /**
+ * Checks that the user has permissions to perform this revert.
+ * Dies with usage message on inadequate permissions.
+ * @param $user User The user to check.
+ */
+ protected function checkPermissions( $user ) {
+ $permissionErrors = array_merge(
+ $this->file->getTitle()->getUserPermissionsErrors( 'edit' , $user ),
+ $this->file->getTitle()->getUserPermissionsErrors( 'upload' , $user )
+ );
+
+ if ( $permissionErrors ) {
+ $this->dieUsageMsg( $permissionErrors[0] );
+ }
+ }
+
+ /**
+ * Validate the user parameters and set $this->archiveName and $this->file.
+ * Throws an error if validation fails
+ */
+ protected function validateParameters() {
+ // Validate the input title
+ $title = Title::makeTitleSafe( NS_FILE, $this->params['filename'] );
+ if ( is_null( $title ) ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $this->params['filename'] ) );
+ }
+ // Check if the file really exists
+ $this->file = wfLocalFile( $title );
+ if ( !$this->file->exists() ) {
+ $this->dieUsageMsg( 'notanarticle' );
+ }
+
+ // Check if the archivename is valid for this file
+ $this->archiveName = $this->params['archivename'];
+ $oldFile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $this->archiveName );
+ if ( !$oldFile->exists() ) {
+ $this->dieUsageMsg( 'filerevert-badversion' );
+ }
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'filename' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'comment' => array(
+ ApiBase::PARAM_DFLT => '',
+ ),
+ 'archivename' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'token' => null,
+ );
+
+ }
+
+ public function getParamDescription() {
+ $params = array(
+ 'filename' => 'Target filename',
+ 'token' => 'Edit token. You can get one of these through prop=info',
+ 'comment' => 'Upload comment',
+ 'archivename' => 'Archive name of the revision to revert to',
+ );
+
+ return $params;
+
+ }
+
+ public function getDescription() {
+ return array(
+ 'Revert a file to an old version'
+ );
+ }
+
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(),
+ array(
+ array( 'mustbeloggedin', 'upload' ),
+ array( 'badaccess-groups' ),
+ array( 'invalidtitle', 'title' ),
+ array( 'notanarticle' ),
+ array( 'filerevert-badversion' ),
+ )
+ );
+ }
+
+ public function needsToken() {
+ return true;
+ }
+
+ public function getTokenSalt() {
+ return '';
+ }
+
+ protected function getExamples() {
+ return array(
+ 'Revert Wiki.png to the version of 20110305152740:',
+ ' api.php?action=filerevert&filename=Wiki.png&comment=Revert&archivename=20110305152740!Wiki.png&token=+\\',
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiFileRevert.php 92400 2011-07-17 16:51:11Z reedy $';
+ }
+}
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index 9d1dfbc1..ce881599 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 19, 2006
*
@@ -146,11 +146,11 @@ abstract class ApiFormatBase extends ApiBase {
return; // skip any initialization
}
- header( "Content-Type: $mime; charset=utf-8" );
+ $this->getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" );
if ( $isHtml ) {
?>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<!DOCTYPE HTML>
<html>
<head>
<?php if ( $this->mUnescapeAmps ) {
@@ -169,7 +169,7 @@ abstract class ApiFormatBase extends ApiBase {
<small>
You are looking at the HTML representation of the <?php echo( $this->mFormat ); ?> format.<br />
HTML is good for debugging, but probably is not suitable for your application.<br />
-See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
+See <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>, or
<a href='<?php echo( $script ); ?>'>API help</a> for more information.
</small>
<?php
@@ -257,15 +257,13 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
* @return string
*/
protected function formatHTML( $text ) {
- global $wgUrlProtocols;
-
// Escape everything first for full coverage
$text = htmlspecialchars( $text );
// encode all comments or tags as safe blue strings
$text = preg_replace( '/\&lt;(!--.*?--|.*?)\&gt;/', '<span style="color:blue;">&lt;\1&gt;</span>', $text );
// identify URLs
- $protos = implode( "|", $wgUrlProtocols );
+ $protos = wfUrlProtocolsWithoutProtRel();
// This regex hacks around bug 13218 (&quot; included in the URL)
$text = preg_replace( "#(($protos).*?)(&quot;)?([ \\'\"<>\n]|&lt;|&gt;|&quot;)#", '<a href="\\1">\\1</a>\\3\\4', $text );
// identify requests to api.php
@@ -294,12 +292,16 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or
return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName();
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Data_formats';
+ }
+
public function getDescription() {
return $this->getIsHtml() ? ' (pretty-print in HTML)' : '';
}
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiFormatBase.php 75970 2010-11-04 00:55:30Z reedy $';
+ return __CLASS__ . ': $Id: ApiFormatBase.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
@@ -368,6 +370,6 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatBase.php 75970 2010-11-04 00:55:30Z reedy $';
+ return __CLASS__ . ': $Id: ApiFormatBase.php 104449 2011-11-28 15:52:04Z reedy $';
}
-} \ No newline at end of file
+}
diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php
index d4aeb0b8..00b03494 100644
--- a/includes/api/ApiFormatDbg.php
+++ b/includes/api/ApiFormatDbg.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 22, 2006
*
- * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -55,6 +55,6 @@ class ApiFormatDbg extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatDbg.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiFormatDbg.php 78829 2010-12-22 20:52:06Z reedy $';
}
}
diff --git a/includes/api/ApiFormatDump.php b/includes/api/ApiFormatDump.php
index 6197563d..b88572e9 100644
--- a/includes/api/ApiFormatDump.php
+++ b/includes/api/ApiFormatDump.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on August 8, 2010
*
@@ -59,6 +59,6 @@ class ApiFormatDump extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id$';
+ return __CLASS__ . ': $Id: ApiFormatDump.php 79969 2011-01-10 22:36:26Z reedy $';
}
}
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
index 7c02baa0..92689084 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 19, 2006
*
@@ -97,6 +97,6 @@ class ApiFormatJson extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatJson.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiFormatJson.php 78829 2010-12-22 20:52:06Z reedy $';
}
}
diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php
index e83941d4..010966d6 100644
--- a/includes/api/ApiFormatPhp.php
+++ b/includes/api/ApiFormatPhp.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 22, 2006
*
@@ -52,6 +52,6 @@ class ApiFormatPhp extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatPhp.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiFormatPhp.php 78829 2010-12-22 20:52:06Z reedy $';
}
}
diff --git a/includes/api/ApiFormatRaw.php b/includes/api/ApiFormatRaw.php
index 98a50652..3b0c10ea 100644
--- a/includes/api/ApiFormatRaw.php
+++ b/includes/api/ApiFormatRaw.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Feb 2, 2009
*
- * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,7 +38,7 @@ class ApiFormatRaw extends ApiFormatBase {
/**
* Constructor
* @param $main ApiMain object
- * @param $errorFallback Formatter object to fall back on for errors
+ * @param $errorFallback ApiFormatBase object to fall back on for errors
*/
public function __construct( $main, $errorFallback ) {
parent::__construct( $main, 'raw' );
@@ -73,6 +73,6 @@ class ApiFormatRaw extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatRaw.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiFormatRaw.php 82429 2011-02-19 00:30:18Z reedy $';
}
}
diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php
index bbb268f1..bb5a3ba1 100644
--- a/includes/api/ApiFormatTxt.php
+++ b/includes/api/ApiFormatTxt.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 22, 2006
*
- * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -55,6 +55,6 @@ class ApiFormatTxt extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatTxt.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiFormatTxt.php 78829 2010-12-22 20:52:06Z reedy $';
}
}
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
index 6c1e3066..2598cc55 100644
--- a/includes/api/ApiFormatWddx.php
+++ b/includes/api/ApiFormatWddx.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 22, 2006
*
@@ -117,6 +117,6 @@ class ApiFormatWddx extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatWddx.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiFormatWddx.php 78829 2010-12-22 20:52:06Z reedy $';
}
}
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index 45ab73ef..3bdfdfa3 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 19, 2006
*
@@ -36,7 +36,9 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiFormatXml extends ApiFormatBase {
private $mRootElemName = 'api';
+ public static $namespace = 'http://www.mediawiki.org/xml/api/';
private $mDoubleQuote = false;
+ private $mIncludeNamespace = false;
private $mXslt = null;
public function __construct( $main, $format ) {
@@ -58,15 +60,22 @@ class ApiFormatXml extends ApiFormatBase {
public function execute() {
$params = $this->extractRequestParams();
$this->mDoubleQuote = $params['xmldoublequote'];
+ $this->mIncludeNamespace = $params['includexmlnamespace'];
$this->mXslt = $params['xslt'];
$this->printText( '<?xml version="1.0"?>' );
if ( !is_null( $this->mXslt ) ) {
$this->addXslt();
}
+ if ( $this->mIncludeNamespace ) {
+ $data = array( 'xmlns' => self::$namespace ) + $this->getResultData();
+ } else {
+ $data = $this->getResultData();
+ }
+
$this->printText(
self::recXmlPrint( $this->mRootElemName,
- $this->getResultData(),
+ $data,
$this->getIsHtml() ? - 2 : null,
$this->mDoubleQuote
)
@@ -85,6 +94,13 @@ class ApiFormatXml extends ApiFormatBase {
*
* 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.
+ *
+ * @param $elemName
+ * @param $elemValue
+ * @param $indent
+ * @param $doublequote bool
+ *
+ * @return string
*/
public static function recXmlPrint( $elemName, $elemValue, $indent, $doublequote = false ) {
$retval = '';
@@ -193,6 +209,7 @@ class ApiFormatXml extends ApiFormatBase {
return array(
'xmldoublequote' => false,
'xslt' => null,
+ 'includexmlnamespace' => false,
);
}
@@ -200,6 +217,7 @@ class ApiFormatXml extends ApiFormatBase {
return array(
'xmldoublequote' => 'If specified, double quotes all attributes and content',
'xslt' => 'If specified, adds <xslt> as stylesheet',
+ 'includexmlnamespace' => 'If specified, adds an XML namespace'
);
}
@@ -208,6 +226,6 @@ class ApiFormatXml extends ApiFormatBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatXml.php 73753 2010-09-25 16:56:03Z reedy $';
+ return __CLASS__ . ': $Id: ApiFormatXml.php 104476 2011-11-28 20:08:17Z reedy $';
}
}
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
index ccf52746..d62bbbba 100644
--- a/includes/api/ApiFormatYaml.php
+++ b/includes/api/ApiFormatYaml.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 19, 2006
*
@@ -33,25 +33,17 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* API YAML output formatter
* @ingroup API
*/
-class ApiFormatYaml extends ApiFormatBase {
-
- public function __construct( $main, $format ) {
- parent::__construct( $main, $format );
- }
+class ApiFormatYaml extends ApiFormatJson {
public function getMimeType() {
return 'application/yaml';
}
- public function execute() {
- $this->printText( Spyc::YAMLDump( $this->getResultData() ) );
- }
-
public function getDescription() {
return 'Output data in YAML format' . parent::getDescription();
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiFormatYaml.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiFormatYaml.php 86302 2011-04-18 11:42:44Z reedy $';
}
}
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
index eedbde13..4b2ced7e 100644
--- a/includes/api/ApiHelp.php
+++ b/includes/api/ApiHelp.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 6, 2006
*
@@ -93,6 +93,11 @@ class ApiHelp extends ApiBase {
$result->addValue( null, $this->getModuleName(), $r );
}
+ /**
+ * @param $module ApiBase
+ * @param $type String What type of request is this? e.g. action, query, list, prop, meta, format
+ * @return string
+ */
private function buildModuleHelp( $module, $type ) {
$msg = ApiMain::makeHelpMsgHeader( $module, $type );
@@ -149,7 +154,15 @@ class ApiHelp extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return array(
+ 'https://www.mediawiki.org/wiki/API:Main_page',
+ 'https://www.mediawiki.org/wiki/API:FAQ',
+ 'https://www.mediawiki.org/wiki/API:Quick_start_guide',
+ );
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiHelp.php 73863 2010-09-28 02:33:43Z brion $';
+ return __CLASS__ . ': $Id: ApiHelp.php 104439 2011-11-28 15:22:23Z reedy $';
}
}
diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php
index 1b5153f9..a1e5709a 100644
--- a/includes/api/ApiImport.php
+++ b/includes/api/ApiImport.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Feb 4, 2009
*
- * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -42,13 +42,14 @@ class ApiImport extends ApiBase {
public function execute() {
global $wgUser;
- if ( !$wgUser->isAllowed( 'import' ) ) {
- $this->dieUsageMsg( array( 'cantimport' ) );
- }
+
$params = $this->extractRequestParams();
$isUpload = false;
if ( isset( $params['interwikisource'] ) ) {
+ if ( !$wgUser->isAllowed( 'import' ) ) {
+ $this->dieUsageMsg( 'cantimport' );
+ }
if ( !isset( $params['interwikipage'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'interwikipage' ) );
}
@@ -61,7 +62,7 @@ class ApiImport extends ApiBase {
} else {
$isUpload = true;
if ( !$wgUser->isAllowed( 'importupload' ) ) {
- $this->dieUsageMsg( array( 'cantimport-upload' ) );
+ $this->dieUsageMsg( 'cantimport-upload' );
}
$source = ImportStreamSource::newFromUpload( 'xml' );
}
@@ -87,8 +88,9 @@ class ApiImport extends ApiBase {
}
$resultData = $reporter->getData();
- $this->getResult()->setIndexedTagName( $resultData, 'page' );
- $this->getResult()->addValue( null, $this->getModuleName(), $resultData );
+ $result = $this->getResult();
+ $result->setIndexedTagName( $resultData, 'page' );
+ $result->addValue( null, $this->getModuleName(), $resultData );
}
public function mustBePosted() {
@@ -131,7 +133,11 @@ class ApiImport extends ApiBase {
}
public function getDescription() {
- return 'Import a page from another wiki, or an XML file';
+ return array(
+ 'Import a page from another wiki, or an XML file.' ,
+ 'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
+ 'sending a file for the "xml" parameter.'
+ );
}
public function getPossibleErrors() {
@@ -159,8 +165,12 @@ class ApiImport extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Import';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiImport.php 77800 2010-12-05 14:22:49Z ialex $';
+ return __CLASS__ . ': $Id: ApiImport.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
@@ -171,6 +181,14 @@ class ApiImport extends ApiBase {
class ApiImportReporter extends ImportReporter {
private $mResultArr = array();
+ /**
+ * @param $title Title
+ * @param $origTitle Title
+ * @param $revisionCount int
+ * @param $successCount int
+ * @param $pageInfo
+ * @return void
+ */
function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
// Add a result entry
$r = array();
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
index 0675de7b..a09f0335 100644
--- a/includes/api/ApiLogin.php
+++ b/includes/api/ApiLogin.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 19, 2006
*
@@ -72,13 +72,15 @@ class ApiLogin extends ApiBase {
global $wgCookiePrefix, $wgUser, $wgPasswordAttemptThrottle;
- switch ( $authRes = $loginForm->authenticateUserData() ) {
+ $authRes = $loginForm->authenticateUserData();
+ switch ( $authRes ) {
case LoginForm::SUCCESS:
$wgUser->setOption( 'rememberpassword', 1 );
- $wgUser->setCookies();
+ $wgUser->setCookies( $this->getMain()->getRequest() );
- // Run hooks. FIXME: split back and frontend from this hook.
- // FIXME: This hook should be placed in the backend
+ // Run hooks.
+ // @todo FIXME: Split back and frontend from this hook.
+ // @todo FIXME: This hook should be placed in the backend
$injected_html = '';
wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$injected_html ) );
@@ -140,6 +142,11 @@ class ApiLogin extends ApiBase {
$result['result'] = 'Blocked';
break;
+ case LoginForm::ABORTED:
+ $result['result'] = 'Aborted';
+ $result['reason'] = $loginForm->mAbortLoginErrorMsg;
+ break;
+
default:
ApiBase::dieDebug( __METHOD__, "Unhandled case value: {$authRes}" );
}
@@ -175,7 +182,7 @@ class ApiLogin extends ApiBase {
public function getDescription() {
return array(
- 'This module is used to login and get the authentication tokens. ',
+ 'Log in and get the authentication tokens. ',
'In the event of a successful log-in, a cookie will be attached',
'to your session. In the event of a failed log-in, you will not ',
'be able to attempt another log-in through this method for 5 seconds.',
@@ -205,7 +212,11 @@ class ApiLogin extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Login';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiLogin.php 76080 2010-11-05 11:54:35Z catrope $';
+ return __CLASS__ . ': $Id: ApiLogin.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php
index 89326915..3639df3b 100644
--- a/includes/api/ApiLogout.php
+++ b/includes/api/ApiLogout.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Jan 4, 2008
*
@@ -64,7 +64,7 @@ class ApiLogout extends ApiBase {
}
public function getDescription() {
- return 'This module is used to logout and clear session data';
+ return 'Log out and clear session data';
}
protected function getExamples() {
@@ -73,7 +73,11 @@ class ApiLogout extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Logout';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiLogout.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiLogout.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index d5238a51..d24e1df2 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 4, 2006
*
@@ -60,10 +60,12 @@ class ApiMain extends ApiBase {
'expandtemplates' => 'ApiExpandTemplates',
'parse' => 'ApiParse',
'opensearch' => 'ApiOpenSearch',
+ 'feedcontributions' => 'ApiFeedContributions',
'feedwatchlist' => 'ApiFeedWatchlist',
'help' => 'ApiHelp',
'paraminfo' => 'ApiParamInfo',
'rsd' => 'ApiRsd',
+ 'compare' => 'ApiComparePages',
// Write modules
'purge' => 'ApiPurge',
@@ -76,6 +78,7 @@ class ApiMain extends ApiBase {
'move' => 'ApiMove',
'edit' => 'ApiEditPage',
'upload' => 'ApiUpload',
+ 'filerevert' => 'ApiFileRevert',
'emailuser' => 'ApiEmailUser',
'watch' => 'ApiWatch',
'patrol' => 'ApiPatrol',
@@ -123,7 +126,12 @@ class ApiMain extends ApiBase {
)
);
- private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames;
+ /**
+ * @var ApiFormatBase
+ */
+ private $mPrinter;
+
+ private $mModules, $mModuleNames, $mFormats, $mFormatNames;
private $mResult, $mAction, $mShowVersions, $mEnableWrite, $mRequest;
private $mInternalMode, $mSquidMaxage, $mModule;
@@ -175,6 +183,7 @@ class ApiMain extends ApiBase {
/**
* Return true if the API was started by other PHP code using FauxRequest
+ * @return bool
*/
public function isInternalMode() {
return $this->mInternalMode;
@@ -199,6 +208,8 @@ class ApiMain extends ApiBase {
/**
* Get the API module object. Only works after executeAction()
+ *
+ * @return ApiBase
*/
public function getModule() {
return $this->mModule;
@@ -215,6 +226,8 @@ class ApiMain extends ApiBase {
/**
* Set how long the response should be cached.
+ *
+ * @param $maxage
*/
public function setCacheMaxAge( $maxage ) {
$this->setCacheControl( array(
@@ -268,7 +281,7 @@ class ApiMain extends ApiBase {
}
/**
- * @deprecated Private caching is now the default, so there is usually no
+ * @deprecated since 1.17 Private caching is now the default, so there is usually no
* need to call this function. If there is a need, you can use
* $this->setCacheMode('private')
*/
@@ -283,6 +296,8 @@ class ApiMain extends ApiBase {
*
* Cache control values set here will only be used if the cache mode is not
* private, see setCacheMode().
+ *
+ * @param $directives array
*/
public function setCacheControl( $directives ) {
$this->mCacheControl = $directives + $this->mCacheControl;
@@ -296,7 +311,7 @@ class ApiMain extends ApiBase {
* given URL must either always or never call this function; if it sometimes does and
* sometimes doesn't, stuff will break.
*
- * @deprecated Use setCacheMode( 'anon-public-user-private' )
+ * @deprecated since 1.17 Use setCacheMode( 'anon-public-user-private' )
*/
public function setVaryCookie() {
$this->setCacheMode( 'anon-public-user-private' );
@@ -304,6 +319,10 @@ class ApiMain extends ApiBase {
/**
* Create an instance of an output formatter by its name
+ *
+ * @param $format string
+ *
+ * @return ApiFormatBase
*/
public function createPrinterByName( $format ) {
if ( !isset( $this->mFormats[$format] ) ) {
@@ -343,22 +362,21 @@ class ApiMain extends ApiBase {
wfDebugLog( 'exception', $e->getLogMessage() );
}
- //
// Handle any kind of exception by outputing properly formatted error message.
// If this fails, an unhandled exception should be thrown so that global error
// handler will process and log it.
- //
$errCode = $this->substituteResultWithError( $e );
// Error results should not be cached
$this->setCacheMode( 'private' );
+ $response = $this->getRequest()->response();
$headerStr = 'MediaWiki-API-Error: ' . $errCode;
if ( $e->getCode() === 0 ) {
- header( $headerStr );
+ $response->header( $headerStr );
} else {
- header( $headerStr, true, $e->getCode() );
+ $response->header( $headerStr, true, $e->getCode() );
}
// Reset and print just the error message
@@ -381,29 +399,44 @@ class ApiMain extends ApiBase {
}
protected function sendCacheHeaders() {
+ global $wgUseXVO, $wgOut, $wgVaryOnXFP;
+ $response = $this->getRequest()->response();
+
if ( $this->mCacheMode == 'private' ) {
- header( 'Cache-Control: private' );
+ $response->header( 'Cache-Control: private' );
return;
}
if ( $this->mCacheMode == 'anon-public-user-private' ) {
- global $wgUseXVO, $wgOut;
- header( 'Vary: Accept-Encoding, Cookie' );
+ $xfp = $wgVaryOnXFP ? ', X-Forwarded-Proto' : '';
+ $response->header( 'Vary: Accept-Encoding, Cookie' . $xfp );
if ( $wgUseXVO ) {
- header( $wgOut->getXVO() );
+ if ( $wgVaryOnXFP ) {
+ $wgOut->addVaryHeader( 'X-Forwarded-Proto' );
+ }
+ $response->header( $wgOut->getXVO() );
if ( $wgOut->haveCacheVaryCookies() ) {
// Logged in, mark this request private
- header( 'Cache-Control: private' );
+ $response->header( 'Cache-Control: private' );
return;
}
// Logged out, send normal public headers below
} elseif ( session_id() != '' ) {
// Logged in or otherwise has session (e.g. anonymous users who have edited)
// Mark request private
- header( 'Cache-Control: private' );
+ $response->header( 'Cache-Control: private' );
return;
} // else no XVO and anonymous, send public headers below
}
+
+ // Send public headers
+ if ( $wgVaryOnXFP ) {
+ $response->header( 'Vary: Accept-Encoding, X-Forwarded-Proto' );
+ if ( $wgUseXVO ) {
+ // Bleeeeegh. Our header setting system sucks
+ $response->header( 'X-Vary-Options: Accept-Encoding;list-contains=gzip, X-Forwarded-Proto' );
+ }
+ }
// If nobody called setCacheMaxAge(), use the (s)maxage parameters
if ( !isset( $this->mCacheControl['s-maxage'] ) ) {
@@ -417,7 +450,7 @@ class ApiMain extends ApiBase {
// Public cache not requested
// Sending a Vary header in this case is harmless, and protects us
// against conditional calls of setCacheMaxAge().
- header( 'Cache-Control: private' );
+ $response->header( 'Cache-Control: private' );
return;
}
@@ -426,7 +459,7 @@ class ApiMain extends ApiBase {
// Send an Expires header
$maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] );
$expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge );
- header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) );
+ $response->header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) );
// Construct the Cache-Control header
$ccHeader = '';
@@ -443,15 +476,17 @@ class ApiMain extends ApiBase {
}
}
- header( "Cache-Control: $ccHeader" );
+ $response->header( "Cache-Control: $ccHeader" );
}
/**
* Replace the result data with the information about an exception.
* Returns the error code
* @param $e Exception
+ * @return string
*/
protected function substituteResultWithError( $e ) {
+ $result = $this->getResult();
// Printer may not be initialized if the extractRequestParams() fails for the main module
if ( !isset ( $this->mPrinter ) ) {
// The printer has not been created yet. Try to manually get formatter value.
@@ -462,14 +497,12 @@ class ApiMain extends ApiBase {
$this->mPrinter = $this->createPrinterByName( $value );
if ( $this->mPrinter->getNeedsRawData() ) {
- $this->getResult()->setRawMode();
+ $result->setRawMode();
}
}
if ( $e instanceof UsageException ) {
- //
// User entered incorrect parameters - print usage screen
- //
$errMessage = $e->getMessageArray();
// Only print the help message when this is for the developer, not runtime
@@ -479,9 +512,7 @@ class ApiMain extends ApiBase {
} else {
global $wgShowSQLErrors, $wgShowExceptionDetails;
- //
// Something is seriously wrong
- //
if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) {
$info = 'Database query error';
} else {
@@ -495,32 +526,34 @@ class ApiMain extends ApiBase {
ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' );
}
- $this->getResult()->reset();
- $this->getResult()->disableSizeCheck();
+ $result->reset();
+ $result->disableSizeCheck();
// Re-add the id
$requestid = $this->getParameter( 'requestid' );
if ( !is_null( $requestid ) ) {
- $this->getResult()->addValue( null, 'requestid', $requestid );
+ $result->addValue( null, 'requestid', $requestid );
}
// servedby is especially useful when debugging errors
- $this->getResult()->addValue( null, 'servedby', wfHostName() );
- $this->getResult()->addValue( null, 'error', $errMessage );
+ $result->addValue( null, 'servedby', wfHostName() );
+ $result->addValue( null, 'error', $errMessage );
return $errMessage['code'];
}
/**
* Set up for the execution.
+ * @return array
*/
protected function setupExecuteAction() {
// First add the id to the top element
+ $result = $this->getResult();
$requestid = $this->getParameter( 'requestid' );
if ( !is_null( $requestid ) ) {
- $this->getResult()->addValue( null, 'requestid', $requestid );
+ $result->addValue( null, 'requestid', $requestid );
}
$servedby = $this->getParameter( 'servedby' );
if ( $servedby ) {
- $this->getResult()->addValue( null, 'servedby', wfHostName() );
+ $result->addValue( null, 'servedby', wfHostName() );
}
$params = $this->extractRequestParams();
@@ -553,8 +586,8 @@ class ApiMain extends ApiBase {
$this->dieUsageMsg( array( 'missingparam', 'token' ) );
} else {
global $wgUser;
- if ( !$wgUser->matchEditToken( $moduleParams['token'], $salt ) ) {
- $this->dieUsageMsg( array( 'sessionfailure' ) );
+ if ( !$wgUser->matchEditToken( $moduleParams['token'], $salt, $this->getRequest() ) ) {
+ $this->dieUsageMsg( 'sessionfailure' );
}
}
}
@@ -574,8 +607,11 @@ class ApiMain extends ApiBase {
$maxLag = $params['maxlag'];
list( $host, $lag ) = wfGetLB()->getMaxLag();
if ( $lag > $maxLag ) {
- header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
- header( 'X-Database-Lag: ' . intval( $lag ) );
+ $response = $this->getRequest()->response();
+
+ $response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
+ $response->header( 'X-Database-Lag: ' . intval( $lag ) );
+
if ( $wgShowHostnames ) {
$this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' );
} else {
@@ -587,7 +623,6 @@ class ApiMain extends ApiBase {
return true;
}
-
/**
* Check for sufficient permissions to execute
* @param $module ApiBase An Api module
@@ -597,14 +632,14 @@ class ApiMain extends ApiBase {
if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) &&
!$wgUser->isAllowed( 'read' ) )
{
- $this->dieUsageMsg( array( 'readrequired' ) );
+ $this->dieUsageMsg( 'readrequired' );
}
if ( $module->isWriteMode() ) {
if ( !$this->mEnableWrite ) {
- $this->dieUsageMsg( array( 'writedisabled' ) );
+ $this->dieUsageMsg( 'writedisabled' );
}
if ( !$wgUser->isAllowed( 'writeapi' ) ) {
- $this->dieUsageMsg( array( 'writerequired' ) );
+ $this->dieUsageMsg( 'writerequired' );
}
if ( wfReadOnly() ) {
$this->dieReadOnly();
@@ -666,6 +701,8 @@ class ApiMain extends ApiBase {
/**
* Print results using the current printer
+ *
+ * @param $isError bool
*/
protected function printResult( $isError ) {
$this->getResult()->cleanUpUTF8();
@@ -687,12 +724,17 @@ class ApiMain extends ApiBase {
$printer->profileOut();
}
+ /**
+ * @return bool
+ */
public function isReadMode() {
return false;
}
/**
* See ApiBase for description.
+ *
+ * @return array
*/
public function getAllowedParams() {
return array(
@@ -723,13 +765,22 @@ class ApiMain extends ApiBase {
/**
* See ApiBase for description.
+ *
+ * @return array
*/
public function getParamDescription() {
return array(
'format' => 'The format of the output',
'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',
+ 'maxlag' => array(
+ 'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.',
+ 'To save actions causing any more site replication lag, this parameter can make the client',
+ 'wait until the replication lag is less than the specified value.',
+ 'In case of a replag error, a HTTP 503 error is returned, with the message like',
+ '"Waiting for $host: $lag seconds lagged\n".',
+ 'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information',
+ ),
'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached',
'maxage' => 'Set the max-age header to this many seconds. Errors are never cached',
'requestid' => 'Request ID to distinguish requests. This will just be output back to you',
@@ -739,28 +790,40 @@ class ApiMain extends ApiBase {
/**
* See ApiBase for description.
+ *
+ * @return array
*/
public function getDescription() {
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: **',
+ '** https://www.mediawiki.org/wiki/API **',
+ '** **',
+ '**********************************************************************************************************',
'',
'Status: All features shown on this page should be working, but the API',
- ' is still in active development, and may change at any time.',
+ ' 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',
- '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',
+ 'Erroneous requests: When erroneous requests are sent to the API, a HTTP header will be sent',
+ ' with the key "MediaWiki-API-Error" and then both the value of the',
+ ' header and the error code sent back will be set to the same value',
+ '',
+ ' In the case of an invalid action being passed, these will have a value',
+ ' of "unknown_action"',
+ '',
+ ' For more information see https://www.mediawiki.org/wiki/API:Errors_and_warnings',
+ '',
+ 'Documentation: https://www.mediawiki.org/wiki/API:Main_page',
+ 'FAQ https://www.mediawiki.org/wiki/API:FAQ',
+ 'Mailing list: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api',
+ 'Api Announcements: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce',
+ 'Bugs & Requests: https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts',
'',
'',
'',
@@ -769,6 +832,9 @@ class ApiMain extends ApiBase {
);
}
+ /**
+ * @return array
+ */
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'readonlytext' ),
@@ -781,22 +847,26 @@ class ApiMain extends ApiBase {
/**
* Returns an array of strings with credits for the API
+ * @return array
*/
protected function getCredits() {
return array(
'API developers:',
- ' Roan Kattouw <Firstname>.<Lastname>@home.nl (lead developer Sep 2007-present)',
+ ' Roan Kattouw <Firstname>.<Lastname>@gmail.com (lead developer Sep 2007-present)',
' Victor Vasiliev - vasilvv at gee mail dot com',
' Bryan Tong Minh - bryan . tongminh @ gmail . com',
' Sam Reed - sam @ reedyboy . net',
' Yuri Astrakhan <Firstname><Lastname>@gmail.com (creator, lead developer Sep 2006-Sep 2007)',
'',
'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org',
- 'or file a bug report at http://bugzilla.wikimedia.org/'
+ 'or file a bug report at https://bugzilla.wikimedia.org/'
);
}
+
/**
* Sets whether the pretty-printer should format *bold* and $italics$
+ *
+ * @param $help bool
*/
public function setHelp( $help = true ) {
$this->mPrinter->setHelp( $help );
@@ -804,34 +874,39 @@ class ApiMain extends ApiBase {
/**
* Override the parent to generate help messages for all available modules.
+ *
+ * @return string
*/
public function makeHelpMsg() {
- global $wgMemc, $wgAPICacheHelp, $wgAPICacheHelpTimeout;
+ global $wgMemc, $wgAPICacheHelpTimeout;
$this->setHelp();
// Get help text from cache if present
$key = wfMemcKey( 'apihelp', $this->getModuleName(),
SpecialVersion::getVersion( 'nodb' ) .
- $this->getMain()->getShowVersions() );
- if ( $wgAPICacheHelp ) {
+ $this->getShowVersions() );
+ if ( $wgAPICacheHelpTimeout > 0 ) {
$cached = $wgMemc->get( $key );
if ( $cached ) {
return $cached;
}
}
$retval = $this->reallyMakeHelpMsg();
- if ( $wgAPICacheHelp ) {
+ if ( $wgAPICacheHelpTimeout > 0 ) {
$wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout );
}
return $retval;
}
+ /**
+ * @return mixed|string
+ */
public function reallyMakeHelpMsg() {
$this->setHelp();
// Use parent to make default message for the main module
$msg = parent::makeHelpMsg();
- $astriks = str_repeat( '*** ', 10 );
+ $astriks = str_repeat( '*** ', 14 );
$msg .= "\n\n$astriks Modules $astriks\n\n";
foreach ( array_keys( $this->mModules ) as $moduleName ) {
$module = new $this->mModules[$moduleName] ( $this, $moduleName );
@@ -867,6 +942,11 @@ class ApiMain extends ApiBase {
return $msg;
}
+ /**
+ * @param $module ApiBase
+ * @param $paramName String What type of request is this? e.g. action, query, list, prop, meta, format
+ * @return string
+ */
public static function makeHelpMsgHeader( $module, $paramName ) {
$modulePrefix = $module->getModulePrefix();
if ( strval( $modulePrefix ) !== '' ) {
@@ -876,37 +956,9 @@ class ApiMain extends ApiBase {
return "* $paramName={$module->getModuleName()} $modulePrefix*";
}
- private $mIsBot = null;
- private $mIsSysop = null;
private $mCanApiHighLimits = null;
/**
- * Returns true if the currently logged in user is a bot, false otherwise
- * OBSOLETE, use canApiHighLimits() instead
- */
- public function isBot() {
- if ( !isset( $this->mIsBot ) ) {
- global $wgUser;
- $this->mIsBot = $wgUser->isAllowed( 'bot' );
- }
- return $this->mIsBot;
- }
-
- /**
- * Similar to isBot(), this method returns true if the logged in user is
- * a sysop, and false if not.
- * OBSOLETE, use canApiHighLimits() instead
- */
- public function isSysop() {
- if ( !isset( $this->mIsSysop ) ) {
- global $wgUser;
- $this->mIsSysop = in_array( 'sysop', $wgUser->getGroups() );
- }
-
- return $this->mIsSysop;
- }
-
- /**
* Check whether the current user is allowed to use high limits
* @return bool
*/
@@ -930,11 +982,13 @@ class ApiMain extends ApiBase {
/**
* Returns the version information of this file, plus it includes
* the versions for all files that are not callable proper API modules
+ *
+ * @return array
*/
public function getVersion() {
- $vers = array ();
- $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
- $vers[] = __CLASS__ . ': $Id: ApiMain.php 76196 2010-11-06 16:11:19Z reedy $';
+ $vers = array();
+ $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/";
+ $vers[] = __CLASS__ . ': $Id: ApiMain.php 104449 2011-11-28 15:52:04Z reedy $';
$vers[] = ApiBase::getBaseVersion();
$vers[] = ApiFormatBase::getBaseVersion();
$vers[] = ApiQueryBase::getBaseVersion();
@@ -957,8 +1011,8 @@ 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.
*
- * @param $fmtName The identifier for this format.
- * @param $fmtClass The class implementing this format.
+ * @param $fmtName string The identifier for this format.
+ * @param $fmtClass ApiFormatBase The class implementing this format.
*/
protected function addFormat( $fmtName, $fmtClass ) {
$this->mFormats[$fmtName] = $fmtClass;
@@ -966,10 +1020,21 @@ class ApiMain extends ApiBase {
/**
* Get the array mapping module names to class names
+ * @return array
*/
function getModules() {
return $this->mModules;
}
+
+ /**
+ * Returns the list of supported formats in form ( 'format' => 'ClassName' )
+ *
+ * @since 1.18
+ * @return array
+ */
+ public function getFormats() {
+ return $this->mFormats;
+ }
}
/**
@@ -989,10 +1054,16 @@ class UsageException extends Exception {
$this->mExtraData = $extradata;
}
+ /**
+ * @return string
+ */
public function getCodeString() {
return $this->mCodestr;
}
+ /**
+ * @return array
+ */
public function getMessageArray() {
$result = array(
'code' => $this->mCodestr,
@@ -1004,6 +1075,9 @@ class UsageException extends Exception {
return $result;
}
+ /**
+ * @return string
+ */
public function __toString() {
return "{$this->getCodeString()}: {$this->getMessage()}";
}
diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php
index a93188bf..f15a086c 100644
--- a/includes/api/ApiMove.php
+++ b/includes/api/ApiMove.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 31, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -61,7 +61,7 @@ class ApiMove extends ApiBase {
}
if ( !$fromTitle->exists() ) {
- $this->dieUsageMsg( array( 'notanarticle' ) );
+ $this->dieUsageMsg( 'notanarticle' );
}
$fromTalk = $fromTitle->getTalkPage();
@@ -76,9 +76,9 @@ class ApiMove extends ApiBase {
&& wfFindFile( $toTitle ) )
{
if ( !$params['ignorewarnings'] && $wgUser->isAllowed( 'reupload-shared' ) ) {
- $this->dieUsageMsg( array( 'sharedfile-exists' ) );
+ $this->dieUsageMsg( 'sharedfile-exists' );
} elseif ( !$wgUser->isAllowed( 'reupload-shared' ) ) {
- $this->dieUsageMsg( array( 'cantoverwrite-sharedfile' ) );
+ $this->dieUsageMsg( 'cantoverwrite-sharedfile' );
}
}
@@ -107,15 +107,18 @@ class ApiMove extends ApiBase {
}
}
+ $result = $this->getResult();
+
// Move subpages
if ( $params['movesubpages'] ) {
$r['subpages'] = $this->moveSubpages( $fromTitle, $toTitle,
$params['reason'], $params['noredirect'] );
- $this->getResult()->setIndexedTagName( $r['subpages'], 'subpage' );
+ $result->setIndexedTagName( $r['subpages'], 'subpage' );
+
if ( $params['movetalk'] ) {
$r['subpages-talk'] = $this->moveSubpages( $fromTalk, $toTalk,
$params['reason'], $params['noredirect'] );
- $this->getResult()->setIndexedTagName( $r['subpages-talk'], 'subpage' );
+ $result->setIndexedTagName( $r['subpages-talk'], 'subpage' );
}
}
@@ -132,9 +135,16 @@ class ApiMove extends ApiBase {
$this->setWatch( $watch, $fromTitle, 'watchmoves' );
$this->setWatch( $watch, $toTitle, 'watchmoves' );
- $this->getResult()->addValue( null, $this->getModuleName(), $r );
+ $result->addValue( null, $this->getModuleName(), $r );
}
+ /**
+ * @param Title $fromTitle
+ * @param Title $toTitle
+ * @param $reason
+ * @param $noredirect
+ * @return array
+ */
public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect ) {
$retval = array();
$success = $fromTitle->moveSubpages( $toTitle, true, $reason, !$noredirect );
@@ -224,13 +234,16 @@ class ApiMove extends ApiBase {
}
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'invalidtitle', 'from' ),
- array( 'nosuchpageid', 'fromid' ),
- array( 'notanarticle' ),
- array( 'invalidtitle', 'to' ),
- array( 'sharedfile-exists' ),
- ) );
+ return array_merge( parent::getPossibleErrors(),
+ $this->getRequireOnlyOneParameterErrorMessages( array( 'from', 'fromid' ) ),
+ array(
+ array( 'invalidtitle', 'from' ),
+ array( 'nosuchpageid', 'fromid' ),
+ array( 'notanarticle' ),
+ array( 'invalidtitle', 'to' ),
+ array( 'sharedfile-exists' ),
+ )
+ );
}
public function needsToken() {
@@ -247,7 +260,11 @@ class ApiMove extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Move';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiMove.php 77192 2010-11-23 22:05:27Z btongminh $';
+ return __CLASS__ . ': $Id: ApiMove.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
index 885766d2..084c9860 100644
--- a/includes/api/ApiOpenSearch.php
+++ b/includes/api/ApiOpenSearch.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 13, 2006
*
@@ -60,7 +60,7 @@ class ApiOpenSearch extends ApiBase {
$searches = PrefixSearch::titleSearch( $search, $limit,
$namespaces );
-
+
// if the content language has variants, try to retrieve fallback results
$fallbackLimit = $limit - count( $searches );
if ( $fallbackLimit > 0 ) {
@@ -116,7 +116,7 @@ class ApiOpenSearch extends ApiBase {
}
public function getDescription() {
- return 'This module implements OpenSearch protocol';
+ return 'Searches the wiki using the OpenSearch protocol';
}
protected function getExamples() {
@@ -125,7 +125,11 @@ class ApiOpenSearch extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Opensearch';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiOpenSearch.php 79720 2011-01-06 14:48:34Z catrope $';
+ return __CLASS__ . ': $Id: ApiOpenSearch.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index 1cb12c07..cf10a9ac 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 24, 2006
*
@@ -59,6 +59,7 @@ class ApiPageSet extends ApiQueryBase {
* Constructor
* @param $query ApiQuery
* @param $resolveRedirects bool Whether redirects should be resolved
+ * @param $convertTitles bool
*/
public function __construct( $query, $resolveRedirects = false, $convertTitles = false ) {
parent::__construct( $query, 'query' );
@@ -212,7 +213,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Get a list of redirect resolutions - maps a title to its redirect
* target.
- * @return array prefixed_title (string) => prefixed_title (string)
+ * @return array prefixed_title (string) => Title object
*/
public function getRedirectTitles() {
return $this->mRedirectTitles;
@@ -351,7 +352,7 @@ class ApiPageSet extends ApiQueryBase {
*/
public function populateFromQueryResult( $db, $queryResult ) {
$this->profileIn();
- $this->initFromQueryResult( $db, $queryResult );
+ $this->initFromQueryResult( $queryResult );
$this->profileOut();
}
@@ -430,7 +431,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( $res, $linkBatch->data, true ); // process Titles
// Resolve any found redirects
$this->resolvePendingRedirects();
@@ -446,19 +447,25 @@ class ApiPageSet extends ApiQueryBase {
}
$pageids = array_map( 'intval', $pageids ); // paranoia
- $set = array(
- 'page_id' => $pageids
- );
- $db = $this->getDB();
+ $remaining = array_flip( $pageids );
- // Get pageIDs data from the `page` table
- $this->profileDBIn();
- $res = $db->select( 'page', $this->getPageTableFields(), $set,
- __METHOD__ );
- $this->profileDBOut();
+ $pageids = self::getPositiveIntegers( $pageids );
- $remaining = array_flip( $pageids );
- $this->initFromQueryResult( $db, $res, $remaining, false ); // process PageIDs
+ $res = null;
+ if ( count( $pageids ) ) {
+ $set = array(
+ 'page_id' => $pageids
+ );
+ $db = $this->getDB();
+
+ // Get pageIDs data from the `page` table
+ $this->profileDBIn();
+ $res = $db->select( 'page', $this->getPageTableFields(), $set,
+ __METHOD__ );
+ $this->profileDBOut();
+ }
+
+ $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs
// Resolve any found redirects
$this->resolvePendingRedirects();
@@ -467,7 +474,6 @@ 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 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
@@ -475,25 +481,27 @@ class ApiPageSet extends ApiQueryBase {
* If true, treat $remaining as an array of [ns][title]
* If false, treat it as an array of [pageIDs]
*/
- private function initFromQueryResult( $db, $res, &$remaining = null, $processTitles = null ) {
+ private function initFromQueryResult( $res, &$remaining = null, $processTitles = null ) {
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 );
+ if ( $res ) {
+ 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] );
+ // 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] );
+ }
}
- }
- // Store any extra fields requested by modules
- $this->processDbRow( $row );
+ // Store any extra fields requested by modules
+ $this->processDbRow( $row );
+ }
}
if ( isset( $remaining ) ) {
@@ -535,21 +543,25 @@ class ApiPageSet extends ApiQueryBase {
$pageids = array();
$remaining = array_flip( $revids );
- $tables = array( 'revision', 'page' );
- $fields = array( 'rev_id', 'rev_page' );
- $where = array( 'rev_id' => $revids, 'rev_page = page_id' );
-
- // Get pageIDs data from the `page` table
- $this->profileDBIn();
- $res = $db->select( $tables, $fields, $where, __METHOD__ );
- foreach ( $res as $row ) {
- $revid = intval( $row->rev_id );
- $pageid = intval( $row->rev_page );
- $this->mGoodRevIDs[$revid] = $pageid;
- $pageids[$pageid] = '';
- unset( $remaining[$revid] );
+ $revids = self::getPositiveIntegers( $revids );
+
+ if ( count( $revids ) ) {
+ $tables = array( 'revision', 'page' );
+ $fields = array( 'rev_id', 'rev_page' );
+ $where = array( 'rev_id' => $revids, 'rev_page = page_id' );
+
+ // Get pageIDs data from the `page` table
+ $this->profileDBIn();
+ $res = $db->select( $tables, $fields, $where, __METHOD__ );
+ foreach ( $res as $row ) {
+ $revid = intval( $row->rev_id );
+ $pageid = intval( $row->rev_page );
+ $this->mGoodRevIDs[$revid] = $pageid;
+ $pageids[$pageid] = '';
+ unset( $remaining[$revid] );
+ }
+ $this->profileDBOut();
}
- $this->profileDBOut();
$this->mMissingRevIDs = array_keys( $remaining );
@@ -589,7 +601,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 );
+ $this->initFromQueryResult( $res, $linkBatch->data, true );
}
}
}
@@ -611,16 +623,17 @@ class ApiPageSet extends ApiQueryBase {
array(
'rd_from',
'rd_namespace',
+ 'rd_fragment',
+ 'rd_interwiki',
'rd_title'
), array( 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ),
__METHOD__
);
$this->profileDBOut();
-
foreach ( $res as $row ) {
$rdfrom = intval( $row->rd_from );
$from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
- $to = Title::makeTitle( $row->rd_namespace, $row->rd_title )->getPrefixedText();
+ $to = Title::makeTitle( $row->rd_namespace, $row->rd_title, $row->rd_fragment, $row->rd_interwiki );
unset( $this->mPendingRedirectIDs[$rdfrom] );
if ( !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) {
$lb->add( $row->rd_namespace, $row->rd_title );
@@ -639,7 +652,7 @@ class ApiPageSet extends ApiQueryBase {
continue;
}
$lb->addObj( $rt );
- $this->mRedirectTitles[$title->getPrefixedText()] = $rt->getPrefixedText();
+ $this->mRedirectTitles[$title->getPrefixedText()] = $rt;
unset( $this->mPendingRedirectIDs[$id] );
}
}
@@ -685,7 +698,6 @@ class ApiPageSet extends ApiQueryBase {
$titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText();
}
-
if ( $titleObj->getNamespace() < 0 ) {
// Handle Special and Media pages
$titleObj = $titleObj->fixSpecialName();
@@ -712,6 +724,25 @@ class ApiPageSet extends ApiQueryBase {
return $linkBatch;
}
+ /**
+ * Returns the input array of integers with all values < 0 removed
+ *
+ * @param $array array
+ * @return array
+ */
+ private static function getPositiveIntegers( $array ) {
+ // bug 25734 API: possible issue with revids validation
+ // It seems with a load of revision rows, MySQL gets upset
+ // Remove any < 0 integers, as they can't be valid
+ foreach( $array as $i => $int ) {
+ if ( $int < 0 ) {
+ unset( $array[$i] );
+ }
+ }
+
+ return $array;
+ }
+
protected function getAllowedParams() {
return array(
'titles' => array(
@@ -744,6 +775,6 @@ class ApiPageSet extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiPageSet.php 76196 2010-11-06 16:11:19Z reedy $';
+ return __CLASS__ . ': $Id: ApiPageSet.php 89574 2011-06-06 15:58:55Z reedy $';
}
}
diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php
index a2c0bd11..5670b041 100644
--- a/includes/api/ApiParamInfo.php
+++ b/includes/api/ApiParamInfo.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Dec 01, 2007
*
- * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -70,6 +70,7 @@ class ApiParamInfo extends ApiBase {
$obj = new $qmodArr[$qm]( $this, $qm );
$a = $this->getClassInfo( $obj );
$a['name'] = $qm;
+ $a['querytype'] = $queryObj->getModuleType( $qm );
$r['querymodules'][] = $a;
}
$result->setIndexedTagName( $r['querymodules'], 'module' );
@@ -84,11 +85,16 @@ class ApiParamInfo extends ApiBase {
$result->addValue( null, $this->getModuleName(), $r );
}
+ /**
+ * @param $obj ApiBase
+ * @return ApiResult
+ */
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() );
+ $examples = (array)$obj->getExamples();
+ $retval['examples'] = implode( "\n", $examples );
$retval['version'] = implode( "\n", (array)$obj->getVersion() );
$retval['prefix'] = $obj->getModulePrefix();
@@ -110,6 +116,18 @@ class ApiParamInfo extends ApiBase {
return $retval;
}
+ $retval['helpurls'] = (array)$obj->getHelpUrls();
+ if ( isset( $retval['helpurls'][0] ) && $retval['helpurls'][0] === false ) {
+ $retval['helpurls'] = array();
+ }
+ $result->setIndexedTagName( $retval['helpurls'], 'helpurl' );
+
+ $retval['allexamples'] = $examples;
+ if ( isset( $retval['allexamples'][0] ) && $retval['allexamples'][0] === false ) {
+ $retval['allexamples'] = array();
+ }
+ $result->setIndexedTagName( $retval['allexamples'], 'example' );
+
$retval['parameters'] = array();
$paramDesc = $obj->getFinalParamDescription();
foreach ( $allowedParams as $n => $p ) {
@@ -117,6 +135,26 @@ class ApiParamInfo extends ApiBase {
if ( isset( $paramDesc[$n] ) ) {
$a['description'] = implode( "\n", (array)$paramDesc[$n] );
}
+
+ //handle shorthand
+ if( !is_array( $p ) ) {
+ $p = array(
+ ApiBase::PARAM_DFLT => $p,
+ );
+ }
+
+ //handle missing type
+ if ( !isset( $p[ApiBase::PARAM_TYPE] ) ) {
+ $dflt = isset( $p[ApiBase::PARAM_DFLT] ) ? $p[ApiBase::PARAM_DFLT] : null;
+ if ( is_bool( $dflt ) ) {
+ $p[ApiBase::PARAM_TYPE] = 'boolean';
+ } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
+ $p[ApiBase::PARAM_TYPE] = 'string';
+ } elseif ( is_int( $dflt ) ) {
+ $p[ApiBase::PARAM_TYPE] = 'integer';
+ }
+ }
+
if ( isset( $p[ApiBase::PARAM_DEPRECATED] ) && $p[ApiBase::PARAM_DEPRECATED] ) {
$a['deprecated'] = '';
}
@@ -124,29 +162,25 @@ class ApiParamInfo extends ApiBase {
$a['required'] = '';
}
- if ( !is_array( $p ) ) {
- if ( is_bool( $p ) ) {
- $a['type'] = 'bool';
- $a['default'] = ( $p ? 'true' : 'false' );
- } elseif ( is_string( $p ) || is_null( $p ) ) {
- $a['type'] = 'string';
- $a['default'] = strval( $p );
- } elseif ( is_int( $p ) ) {
- $a['type'] = 'integer';
- $a['default'] = intval( $p );
- }
- $retval['parameters'][] = $a;
- continue;
- }
-
if ( isset( $p[ApiBase::PARAM_DFLT] ) ) {
- $a['default'] = $p[ApiBase::PARAM_DFLT];
+ $type = $p[ApiBase::PARAM_TYPE];
+ if( $type === 'boolean' ) {
+ $a['default'] = ( $p[ApiBase::PARAM_DFLT] ? 'true' : 'false' );
+ } elseif( $type === 'string' ) {
+ $a['default'] = strval( $p[ApiBase::PARAM_DFLT] );
+ } elseif( $type === 'integer' ) {
+ $a['default'] = intval( $p[ApiBase::PARAM_DFLT] );
+ } else {
+ $a['default'] = $p[ApiBase::PARAM_DFLT];
+ }
}
if ( isset( $p[ApiBase::PARAM_ISMULTI] ) && $p[ApiBase::PARAM_ISMULTI] ) {
$a['multi'] = '';
$a['limit'] = $this->getMain()->canApiHighLimits() ?
- ApiBase::LIMIT_SML2 :
- ApiBase::LIMIT_SML1;
+ ApiBase::LIMIT_SML2 :
+ ApiBase::LIMIT_SML1;
+ $a['lowlimit'] = ApiBase::LIMIT_SML1;
+ $a['highlimit'] = ApiBase::LIMIT_SML2;
}
if ( isset( $p[ApiBase::PARAM_ALLOW_DUPLICATES] ) && $p[ApiBase::PARAM_ALLOW_DUPLICATES] ) {
@@ -175,7 +209,6 @@ class ApiParamInfo extends ApiBase {
// Errors
$retval['errors'] = $this->parseErrors( $obj->getPossibleErrors() );
-
$result->setIndexedTagName( $retval['errors'], 'error' );
return $retval;
@@ -217,7 +250,11 @@ class ApiParamInfo extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Parameter_information';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiParamInfo.php 87170 2011-04-30 16:57:22Z catrope $';
+ return __CLASS__ . ': $Id: ApiParamInfo.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index 2d12c233..6212b4ad 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -1,7 +1,5 @@
<?php
/**
- * API for MediaWiki 1.8+
- *
* Created on Dec 01, 2007
*
* Copyright © 2007 Yuri Astrakhan <Firstname><Lastname>@gmail.com
@@ -33,7 +31,6 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* @ingroup API
*/
class ApiParse extends ApiBase {
-
private $section, $text, $pstText = null;
public function __construct( $main, $action ) {
@@ -80,8 +77,10 @@ class ApiParse extends ApiBase {
$redirValues = null;
- if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
+ // Return result
+ $result = $this->getResult();
+ if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) {
if ( !is_null( $oldid ) ) {
// Don't use the parser cache
$rev = Revision::newFromID( $oldid );
@@ -96,13 +95,12 @@ class ApiParse extends ApiBase {
$wgTitle = $titleObj;
- //If for some reason the "oldid" is actually the current revision, it may be cached
- if ( $titleObj->getLatestRevID() === $oldid ) {
+ // If for some reason the "oldid" is actually the current revision, it may be cached
+ if ( $titleObj->getLatestRevID() === intval( $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 );
@@ -114,38 +112,45 @@ class ApiParse extends ApiBase {
$p_result = $wgParser->parse( $this->text, $titleObj, $popts );
}
-
} else { // Not $oldid
-
- if ( !is_null ( $pageid ) ) {
- $titleObj = Title::newFromID( $pageid );
-
- if ( !$titleObj ) {
- $this->dieUsageMsg( array( 'nosuchpageid', $pageid ) );
+ if ( $params['redirects'] ) {
+ $reqParams = array(
+ 'action' => 'query',
+ 'redirects' => '',
+ );
+ if ( !is_null ( $pageid ) ) {
+ $reqParams['pageids'] = $pageid;
+ } else { // $page
+ $reqParams['titles'] = $page;
}
- } 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;
+ $req = new FauxRequest( $reqParams );
+ $main = new ApiMain( $req );
+ $main->execute();
+ $data = $main->getResultData();
+ $redirValues = isset( $data['query']['redirects'] )
+ ? $data['query']['redirects']
+ : array();
+ $to = $page;
+ foreach ( (array)$redirValues as $r ) {
+ $to = $r['to'];
}
$titleObj = Title::newFromText( $to );
- if ( !$titleObj || !$titleObj->exists() ) {
- $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
+ } else {
+ if ( !is_null ( $pageid ) ) {
+ $reqParams['pageids'] = $pageid;
+ $titleObj = Title::newFromID( $pageid );
+ } else { // $page
+ $to = $page;
+ $titleObj = Title::newFromText( $to );
+ }
+ }
+ if ( !is_null ( $pageid ) ) {
+ if ( !$titleObj ) {
+ // Still throw nosuchpageid error if pageid was provided
+ $this->dieUsageMsg( array( 'nosuchpageid', $pageid ) );
}
+ } elseif ( !$titleObj || !$titleObj->exists() ) {
+ $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' );
}
$wgTitle = $titleObj;
@@ -157,13 +162,12 @@ class ApiParse extends ApiBase {
$p_result = $this->getParsedSectionOrText( $articleObj, $titleObj, $popts, $pageid,
isset( $prop['wikitext'] ) ) ;
}
-
} else { // Not $oldid, $pageid, $page. Hence based on $text
$this->text = $text;
$titleObj = Title::newFromText( $title );
if ( !$titleObj ) {
- $titleObj = Title::newFromText( 'API' );
+ $this->dieUsageMsg( array( 'invalidtitle', $title ) );
}
$wgTitle = $titleObj;
@@ -177,20 +181,25 @@ class ApiParse extends ApiBase {
if ( $params['onlypst'] ) {
// Build a result and bail out
$result_array['text'] = array();
- $this->getResult()->setContent( $result_array['text'], $this->pstText );
+ $result->setContent( $result_array['text'], $this->pstText );
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $this->getResult()->setContent( $result_array['wikitext'], $this->text );
+ $result->setContent( $result_array['wikitext'], $this->text );
}
- $this->getResult()->addValue( null, $this->getModuleName(), $result_array );
+ $result->addValue( null, $this->getModuleName(), $result_array );
return;
}
$p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts );
}
- // Return result
- $result = $this->getResult();
$result_array = array();
+
+ $result_array['title'] = $titleObj->getPrefixedText();
+
+ if ( !is_null( $oldid ) ) {
+ $result_array['revid'] = intval( $oldid );
+ }
+
if ( $params['redirects'] && !is_null( $redirValues ) ) {
$result_array['redirects'] = $redirValues;
}
@@ -244,31 +253,30 @@ class ApiParse extends ApiBase {
}
if ( isset( $prop['headitems'] ) || isset( $prop['headhtml'] ) ) {
- $out = new OutputPage;
- $out->addParserOutputNoText( $p_result );
- $userSkin = $wgUser->getSkin();
- }
+ $context = new RequestContext;
+ $context->getOutput()->addParserOutputNoText( $p_result );
- if ( isset( $prop['headitems'] ) ) {
- $headItems = $this->formatHeadItems( $p_result->getHeadItems() );
+ if ( isset( $prop['headitems'] ) ) {
+ $headItems = $this->formatHeadItems( $p_result->getHeadItems() );
- $userSkin->setupUserCss( $out );
- $css = $this->formatCss( $out->buildCssLinksArray() );
+ $context->getSkin()->setupUserCss( $context->getOutput() );
+ $css = $this->formatCss( $context->getOutput()->buildCssLinksArray() );
- $scripts = array( $out->getHeadScripts( $userSkin ) );
+ $scripts = array( $context->getOutput()->getHeadScripts( $context->getSkin() ) );
- $result_array['headitems'] = array_merge( $headItems, $css, $scripts );
- }
+ $result_array['headitems'] = array_merge( $headItems, $css, $scripts );
+ }
- if ( isset( $prop['headhtml'] ) ) {
- $result_array['headhtml'] = array();
- $result->setContent( $result_array['headhtml'], $out->headElement( $userSkin ) );
+ if ( isset( $prop['headhtml'] ) ) {
+ $result_array['headhtml'] = array();
+ $result->setContent( $result_array['headhtml'], $context->getOutput()->headElement( $context->getSkin() ) );
+ }
}
if ( isset( $prop['iwlinks'] ) ) {
$result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() );
}
-
+
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
$result->setContent( $result_array['wikitext'], $this->text );
@@ -278,10 +286,6 @@ class ApiParse extends ApiBase {
}
}
- if ( !is_null( $oldid ) ) {
- $result_array['revid'] = intval( $oldid );
- }
-
$result_mapping = array(
'redirects' => 'r',
'langlinks' => 'll',
@@ -303,11 +307,11 @@ class ApiParse extends ApiBase {
}
/**
- * @param $articleObj Article
- * @param $titleObj Title
- * @param $popts ParserOptions
- * @param $pageId Int
- * @param $getWikitext Bool
+ * @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 ) {
@@ -346,10 +350,10 @@ class ApiParse extends ApiBase {
$entry = array();
$bits = explode( ':', $link, 2 );
$title = Title::newFromText( $link );
-
+
$entry['lang'] = $bits[0];
if ( $title ) {
- $entry['url'] = $title->getFullURL();
+ $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
}
$this->getResult()->setContent( $entry, $bits[1] );
$result[] = $entry;
@@ -369,17 +373,41 @@ class ApiParse extends ApiBase {
}
private function categoriesHtml( $categories ) {
- global $wgOut, $wgUser;
- $wgOut->addCategoryLinks( $categories );
- $sk = $wgUser->getSkin();
- return $sk->getCategories();
+ $context = $this->createContext();
+ $context->getOutput()->addCategoryLinks( $categories );
+ return $context->getSkin()->getCategories();
}
+ /**
+ * @deprecated since 1.18 No modern skin generates language links this way, please use language links
+ * data to generate your own HTML.
+ */
private function languagesHtml( $languages ) {
- global $wgOut, $wgUser;
- $wgOut->setLanguageLinks( $languages );
- $sk = $wgUser->getSkin();
- return $sk->otherLanguages();
+ global $wgContLang, $wgHideInterlanguageLinks;
+
+ if ( $wgHideInterlanguageLinks || count( $languages ) == 0 ) {
+ return '';
+ }
+
+ $s = htmlspecialchars( wfMsg( 'otherlanguages' ) . wfMsg( 'colon-separator' ) );
+
+ $langs = array();
+ foreach ( $languages as $l ) {
+ $nt = Title::newFromText( $l );
+ $text = $wgContLang->getLanguageName( $nt->getInterwiki() );
+
+ $langs[] = Html::element( 'a',
+ array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => "external" ),
+ $text == '' ? $l : $text );
+ }
+
+ $s .= implode( htmlspecialchars( wfMsgExt( 'pipe-separator', 'escapenoentities' ) ), $langs );
+
+ if ( $wgContLang->isRTL() ) {
+ $s = Html::rawElement( 'span', array( 'dir' => "LTR" ), $s );
+ }
+
+ return $s;
}
private function formatLinks( $links ) {
@@ -407,7 +435,7 @@ class ApiParse extends ApiBase {
$title = Title::newFromText( "{$prefix}:{$title}" );
if ( $title ) {
- $entry['url'] = $title->getFullURL();
+ $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
}
$this->getResult()->setContent( $entry, $title->getFullText() );
@@ -455,9 +483,13 @@ class ApiParse extends ApiBase {
'text' => null,
'summary' => null,
'page' => null,
- 'pageid' => null,
+ 'pageid' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
'redirects' => false,
- 'oldid' => null,
+ 'oldid' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
'prop' => array(
ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid|displaytitle',
ApiBase::PARAM_ISMULTI => true,
@@ -493,7 +525,7 @@ class ApiParse extends ApiBase {
return array(
'text' => 'Wikitext to parse',
'summary' => 'Summary to parse',
- 'redirects' => "If the {$p}page parameter is set to a redirect, resolve it",
+ 'redirects' => "If the {$p}page or the {$p}pageid parameter is set to a redirect, resolve it",
'title' => 'Title of page the text belongs to',
'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",
@@ -501,16 +533,16 @@ class ApiParse extends ApiBase {
'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',
+ ' langlinks - Gives the language links in the parsed wikitext',
+ ' categories - Gives the categories in the parsed wikitext',
+ ' categorieshtml - Gives the HTML version of the categories',
+ ' languageshtml - Gives the HTML version of the language links',
' links - Gives the internal links in the parsed wikitext',
' templates - Gives the templates in the parsed wikitext',
' images - Gives the images in the parsed wikitext',
' 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',
+ ' 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',
@@ -532,7 +564,7 @@ class ApiParse extends ApiBase {
}
public function getDescription() {
- return 'This module parses wikitext and returns parser output';
+ return 'Parses wikitext and returns parser output';
}
public function getPossibleErrors() {
@@ -543,6 +575,7 @@ class ApiParse extends ApiBase {
array( 'code' => 'missingtitle', 'info' => 'The page you specified doesn\'t exist' ),
array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ),
array( 'nosuchpageid' ),
+ array( 'invalidtitle', 'title' ),
) );
}
@@ -552,7 +585,11 @@ class ApiParse extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#parse';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiParse.php 89672 2011-06-07 18:45:20Z catrope $';
+ return __CLASS__ . ': $Id: ApiParse.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php
index 08835743..8066e655 100644
--- a/includes/api/ApiPatrol.php
+++ b/includes/api/ApiPatrol.php
@@ -42,13 +42,15 @@ class ApiPatrol extends ApiBase {
* Patrols the article or provides the reason the patrol failed.
*/
public function execute() {
+ global $wgUser;
+
$params = $this->extractRequestParams();
$rc = RecentChange::newFromID( $params['rcid'] );
if ( !$rc instanceof RecentChange ) {
$this->dieUsageMsg( array( 'nosuchrcid', $params['rcid'] ) );
}
- $retval = RecentChange::markPatrolled( $params['rcid'] );
+ $retval = $rc->doMarkPatrolled( $wgUser );
if ( $retval ) {
$this->dieUsageMsg( reset( $retval ) );
@@ -108,7 +110,11 @@ class ApiPatrol extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Patrol';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiPatrol.php 78437 2010-12-15 14:14:16Z catrope $';
+ return __CLASS__ . ': $Id: ApiPatrol.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php
index 3a1d18e0..5556262e 100644
--- a/includes/api/ApiProtect.php
+++ b/includes/api/ApiProtect.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 1, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -63,6 +63,7 @@ class ApiProtect extends ApiBase {
}
$restrictionTypes = $titleObj->getRestrictionTypes();
+ $dbr = wfGetDB( DB_SLAVE );
$protections = array();
$expiryarray = array();
@@ -72,10 +73,10 @@ class ApiProtect extends ApiBase {
$protections[$p[0]] = ( $p[1] == 'all' ? '' : $p[1] );
if ( $titleObj->exists() && $p[0] == 'create' ) {
- $this->dieUsageMsg( array( 'create-titleexists' ) );
+ $this->dieUsageMsg( 'create-titleexists' );
}
if ( !$titleObj->exists() && $p[0] != 'create' ) {
- $this->dieUsageMsg( array( 'missingtitle-createonly' ) );
+ $this->dieUsageMsg( 'missingtitle-createonly' );
}
if ( !in_array( $p[0], $restrictionTypes ) && $p[0] != 'create' ) {
@@ -86,7 +87,7 @@ class ApiProtect extends ApiBase {
}
if ( in_array( $expiry[$i], array( 'infinite', 'indefinite', 'never' ) ) ) {
- $expiryarray[$p[0]] = Block::infinity();
+ $expiryarray[$p[0]] = $dbr->getInfinity();
} else {
$exp = strtotime( $expiry[$i] );
if ( $exp < 0 || !$exp ) {
@@ -100,7 +101,7 @@ class ApiProtect extends ApiBase {
$expiryarray[$p[0]] = $exp;
}
$resultProtections[] = array( $p[0] => $protections[$p[0]],
- 'expiry' => ( $expiryarray[$p[0]] == Block::infinity() ?
+ 'expiry' => ( $expiryarray[$p[0]] == $dbr->getInfinity() ?
'infinite' :
wfTimestamp( TS_ISO_8601, $expiryarray[$p[0]] ) ) );
}
@@ -129,8 +130,9 @@ class ApiProtect extends ApiBase {
$res['cascade'] = '';
}
$res['protections'] = $resultProtections;
- $this->getResult()->setIndexedTagName( $res['protections'], 'protection' );
- $this->getResult()->addValue( null, $this->getModuleName(), $res );
+ $result = $this->getResult();
+ $result->setIndexedTagName( $res['protections'], 'protection' );
+ $result->addValue( null, $this->getModuleName(), $res );
}
public function mustBePosted() {
@@ -222,7 +224,11 @@ class ApiProtect extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Protect';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiProtect.php 77192 2010-11-23 22:05:27Z btongminh $';
+ return __CLASS__ . ': $Id: ApiProtect.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php
index a17abf16..bdf911cb 100644
--- a/includes/api/ApiPurge.php
+++ b/includes/api/ApiPurge.php
@@ -49,6 +49,9 @@ class ApiPurge extends ApiBase {
!$this->getMain()->getRequest()->wasPosted() ) {
$this->dieUsageMsg( array( 'mustbeposted', $this->getModuleName() ) );
}
+
+ $forceLinkUpdate = $params['forcelinkupdate'];
+
$result = array();
foreach ( $params['titles'] as $t ) {
$r = array();
@@ -65,13 +68,39 @@ class ApiPurge extends ApiBase {
$result[] = $r;
continue;
}
- $article = MediaWiki::articleFromTitle( $title );
+ $context = $this->createContext();
+ $context->setTitle( $title );
+ $article = Article::newFromTitle( $title, $context );
$article->doPurge(); // Directly purge and skip the UI part of purge().
$r['purged'] = '';
+
+ if( $forceLinkUpdate ) {
+ if ( !$wgUser->pingLimiter() ) {
+ global $wgParser, $wgEnableParserCache;
+ $popts = new ParserOptions();
+ $p_result = $wgParser->parse( $article->getContent(), $title, $popts );
+
+ # Update the links tables
+ $u = new LinksUpdate( $title, $p_result );
+ $u->doUpdate();
+
+ $r['linkupdate'] = '';
+
+ if ( $wgEnableParserCache ) {
+ $pcache = ParserCache::singleton();
+ $pcache->save( $p_result, $article, $popts );
+ }
+ } else {
+ $this->setWarning( $this->parseMsg( array( 'actionthrottledtext' ) ) );
+ $forceLinkUpdate = false;
+ }
+ }
+
$result[] = $r;
}
- $this->getResult()->setIndexedTagName( $result, 'page' );
- $this->getResult()->addValue( null, $this->getModuleName(), $result );
+ $apiResult = $this->getResult();
+ $apiResult->setIndexedTagName( $result, 'page' );
+ $apiResult->addValue( null, $this->getModuleName(), $result );
}
public function isWriteMode() {
@@ -83,19 +112,21 @@ class ApiPurge extends ApiBase {
'titles' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_REQUIRED => true
- )
+ ),
+ 'forcelinkupdate' => false,
);
}
public function getParamDescription() {
return array(
'titles' => 'A list of titles',
+ 'forcelinkupdate' => 'Update the links tables',
);
}
public function getDescription() {
return array( 'Purge the cache for the given titles.',
- 'This module requires a POST request if the user is not logged in.'
+ 'Requires a POST request if the user is not logged in.'
);
}
@@ -111,7 +142,11 @@ class ApiPurge extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Purge';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiPurge.php 74944 2010-10-18 09:19:20Z catrope $';
+ return __CLASS__ . ': $Id: ApiPurge.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index f88aa850..49fd8569 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 7, 2006
*
@@ -43,8 +43,13 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQuery extends ApiBase {
private $mPropModuleNames, $mListModuleNames, $mMetaModuleNames;
+
+ /**
+ * @var ApiPageSet
+ */
private $mPageSet;
- private $params, $redirects, $convertTitles;
+
+ private $params, $redirects, $convertTitles, $iwUrl;
private $mQueryPropModules = array(
'info' => 'ApiQueryInfo',
@@ -77,6 +82,7 @@ class ApiQuery extends ApiBase {
'filearchive' => 'ApiQueryFilearchive',
'imageusage' => 'ApiQueryBacklinks',
'iwbacklinks' => 'ApiQueryIWBacklinks',
+ 'langbacklinks' => 'ApiQueryLangBacklinks',
'logevents' => 'ApiQueryLogEvents',
'recentchanges' => 'ApiQueryRecentChanges',
'search' => 'ApiQuerySearch',
@@ -88,6 +94,7 @@ class ApiQuery extends ApiBase {
'users' => 'ApiQueryUsers',
'random' => 'ApiQueryRandom',
'protectedtitles' => 'ApiQueryProtectedTitles',
+ 'querypage' => 'ApiQueryQueryPage',
);
private $mQueryMetaModules = array(
@@ -184,15 +191,15 @@ class ApiQuery extends ApiBase {
* @return mixed string or null
*/
function getModuleType( $moduleName ) {
- if ( array_key_exists ( $moduleName, $this->mQueryPropModules ) ) {
+ if ( isset( $this->mQueryPropModules[$moduleName] ) ) {
return 'prop';
}
- if ( array_key_exists ( $moduleName, $this->mQueryListModules ) ) {
+ if ( isset( $this->mQueryListModules[$moduleName] ) ) {
return 'list';
}
- if ( array_key_exists ( $moduleName, $this->mQueryMetaModules ) ) {
+ if ( isset( $this->mQueryMetaModules[$moduleName] ) ) {
return 'meta';
}
@@ -225,6 +232,7 @@ class ApiQuery extends ApiBase {
$this->params = $this->extractRequestParams();
$this->redirects = $this->params['redirects'];
$this->convertTitles = $this->params['converttitles'];
+ $this->iwUrl = $this->params['iwurl'];
// Create PageSet
$this->mPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles );
@@ -272,6 +280,8 @@ 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
* added to private data do not decrease the level of privacy.
+ *
+ * @return string
*/
protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
if ( $modCacheMode === 'anon-public-user-private' ) {
@@ -307,9 +317,8 @@ class ApiQuery extends ApiBase {
* @param $moduleList Array array(modulename => classname)
*/
private function instantiateModules( &$modules, $param, $moduleList ) {
- $list = @$this->params[$param];
- if ( !is_null ( $list ) ) {
- foreach ( $list as $moduleName ) {
+ if ( isset( $this->params[$param] ) ) {
+ foreach ( $this->params[$param] as $moduleName ) {
$modules[] = new $moduleList[$moduleName] ( $this, $moduleName );
}
}
@@ -359,10 +368,15 @@ class ApiQuery extends ApiBase {
// Interwiki titles
$intrwValues = array();
foreach ( $pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) {
- $intrwValues[] = array(
+ $item = array(
'title' => $rawTitleStr,
- 'iw' => $interwikiStr
+ 'iw' => $interwikiStr,
);
+ if ( $this->iwUrl ) {
+ $title = Title::newFromText( $rawTitleStr );
+ $item['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
+ }
+ $intrwValues[] = $item;
}
if ( count( $intrwValues ) ) {
@@ -372,11 +386,15 @@ class ApiQuery extends ApiBase {
// Show redirect information
$redirValues = array();
- foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo ) {
- $redirValues[] = array(
+ foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleTo ) {
+ $r = array(
'from' => strval( $titleStrFrom ),
- 'to' => $titleStrTo
+ 'to' => $titleTo->getPrefixedText(),
);
+ if ( $titleTo->getFragment() !== '' ) {
+ $r['tofragment'] = $titleTo->getFragment();
+ }
+ $redirValues[] = $r;
}
if ( count( $redirValues ) ) {
@@ -424,7 +442,7 @@ class ApiQuery extends ApiBase {
ApiQueryBase::addTitleInfo( $vals, $title );
$vals['special'] = '';
if ( $title->getNamespace() == NS_SPECIAL &&
- !SpecialPage::exists( $title->getDbKey() ) ) {
+ !SpecialPageFactory::exists( $title->getDbKey() ) ) {
$vals['missing'] = '';
} elseif ( $title->getNamespace() == NS_MEDIA &&
!wfFindFile( $title ) ) {
@@ -465,17 +483,13 @@ class ApiQuery extends ApiBase {
private function doExport( $pageSet, $result ) {
$exportTitles = array();
$titles = $pageSet->getGoodTitles();
- if( count( $titles ) ) {
+ if ( count( $titles ) ) {
foreach ( $titles as $title ) {
if ( $title->userCanRead() ) {
$exportTitles[] = $title;
}
}
}
- // only export when there are titles
- if ( !count( $exportTitles ) ) {
- return;
- }
$exporter = new WikiExporter( $this->getDB() );
// WikiExporter writes to stdout, so catch its
@@ -578,6 +592,7 @@ class ApiQuery extends ApiBase {
'indexpageids' => false,
'export' => false,
'exportnowrap' => false,
+ 'iwurl' => false,
);
}
@@ -586,16 +601,14 @@ 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
- $querySeparator = str_repeat( '--- ', 8 );
- $moduleSeparator = str_repeat( '*** ', 10 );
- $msg .= "\n$querySeparator Query: Prop $querySeparator\n\n";
+ $querySeparator = str_repeat( '--- ', 12 );
+ $moduleSeparator = str_repeat( '*** ', 14 );
+ $msg = "\n$querySeparator Query: Prop $querySeparator\n\n";
$msg .= $this->makeHelpMsgHelper( $this->mQueryPropModules, 'prop' );
$msg .= "\n$querySeparator Query: List $querySeparator\n\n";
$msg .= $this->makeHelpMsgHelper( $this->mQueryListModules, 'list' );
@@ -621,7 +634,7 @@ class ApiQuery extends ApiBase {
$moduleDescriptions = array();
foreach ( $moduleList as $moduleName => $moduleClass ) {
- $module = new $moduleClass ( $this, $moduleName, null );
+ $module = new $moduleClass( $this, $moduleName, null );
$msg = ApiMain::makeHelpMsgHeader( $module, $paramName );
$msg2 = $module->makeHelpMsg();
@@ -664,6 +677,7 @@ class ApiQuery extends ApiBase {
'indexpageids' => 'Include an additional pageids section listing all returned page IDs',
'export' => 'Export the current revisions of all given or generated pages',
'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export',
+ 'iwurl' => 'Whether to get the full URL if the title is an interwiki link',
);
}
@@ -688,10 +702,18 @@ class ApiQuery extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return array(
+ 'https://www.mediawiki.org/wiki/API:Meta',
+ 'https://www.mediawiki.org/wiki/API:Properties',
+ 'https://www.mediawiki.org/wiki/API:Lists',
+ );
+ }
+
public function getVersion() {
$psModule = new ApiPageSet( $this );
$vers = array();
- $vers[] = __CLASS__ . ': $Id: ApiQuery.php 80897 2011-01-24 18:57:42Z catrope $';
+ $vers[] = __CLASS__ . ': $Id: ApiQuery.php 104449 2011-11-28 15:52:04Z reedy $';
$vers[] = $psModule->getVersion();
return $vers;
}
diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php
index c1473252..c7f4b0aa 100644
--- a/includes/api/ApiQueryAllCategories.php
+++ b/includes/api/ApiQueryAllCategories.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on December 12, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -53,6 +53,10 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
$db = $this->getDB();
$params = $this->extractRequestParams();
@@ -65,6 +69,10 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
$to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
$this->addWhereRange( 'cat_title', $dir, $from, $to );
+ $min = $params['min'];
+ $max = $params['max'];
+ $this->addWhereRange( 'cat_pages', $dir, $min, $max );
+
if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'cat_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
}
@@ -144,6 +152,14 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
'descending'
),
),
+ 'min' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
+ 'max' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -165,6 +181,8 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
'to' => 'The category to stop enumerating at',
'prefix' => 'Search for all category titles that begin with this value',
'dir' => 'Direction to sort in',
+ 'min' => 'Minimum number of category members',
+ 'max' => 'Maximum number of category members',
'limit' => 'How many categories to return',
'prop' => array(
'Which properties to get',
@@ -185,7 +203,11 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Allcategories';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllCategories.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryAllCategories.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php
index 78784845..90620e91 100644
--- a/includes/api/ApiQueryAllLinks.php
+++ b/includes/api/ApiQueryAllLinks.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on July 7, 2007
*
@@ -52,6 +52,10 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
$db = $this->getDB();
$params = $this->extractRequestParams();
@@ -90,27 +94,22 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
);
}
- if ( !is_null( $params['from'] ) ) {
- $this->addWhere( 'pl_title>=' . $db->addQuotes( $this->titlePartToKey( $params['from'] ) ) );
- }
- if ( !is_null( $params['to'] ) ) {
- $this->addWhere( 'pl_title<=' . $db->addQuotes( $this->titlePartToKey( $params['to'] ) ) );
- }
+ $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
+ $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $this->addWhereRange( 'pl_title', 'newer', $from, $to );
+
if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'pl_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
}
- $this->addFields( array(
- 'pl_title',
- ) );
+ $this->addFields( 'pl_title' );
$this->addFieldsIf( 'pl_from', !$params['unique'] );
$this->addOption( 'USE INDEX', 'pl_namespace' );
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
- if ( $params['unique'] ) {
- $this->addOption( 'ORDER BY', 'pl_title' );
- } else {
+
+ if ( !$params['unique'] ) {
$this->addOption( 'ORDER BY', 'pl_title, pl_from' );
}
@@ -228,7 +227,11 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Alllinks';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllLinks.php 77192 2010-11-23 22:05:27Z btongminh $';
+ return __CLASS__ . ': $Id: ApiQueryAllLinks.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php
index 77f507fc..c0984d95 100644
--- a/includes/api/ApiQueryAllUsers.php
+++ b/includes/api/ApiQueryAllUsers.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on July 7, 2007
*
@@ -49,67 +49,119 @@ class ApiQueryAllUsers extends ApiQueryBase {
$fld_blockinfo = isset( $prop['blockinfo'] );
$fld_editcount = isset( $prop['editcount'] );
$fld_groups = isset( $prop['groups'] );
+ $fld_rights = isset( $prop['rights'] );
$fld_registration = isset( $prop['registration'] );
+ $fld_implicitgroups = isset( $prop['implicitgroups'] );
} else {
- $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration = $fld_rights = false;
+ $fld_blockinfo = $fld_editcount = $fld_groups = $fld_registration = $fld_rights = $fld_implicitgroups = false;
}
$limit = $params['limit'];
- $this->addTables( 'user', 'u1' );
+
+ $this->addTables( 'user' );
$useIndex = true;
- if ( !is_null( $params['from'] ) ) {
- $this->addWhere( 'u1.user_name >= ' . $db->addQuotes( $this->keyToTitle( $params['from'] ) ) );
+ $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' );
+ $from = is_null( $params['from'] ) ? null : $this->keyToTitle( $params['from'] );
+ $to = is_null( $params['to'] ) ? null : $this->keyToTitle( $params['to'] );
+
+ $this->addWhereRange( 'user_name', $dir, $from, $to );
+
+ if ( !is_null( $params['prefix'] ) ) {
+ $this->addWhere( 'user_name' . $db->buildLike( $this->keyToTitle( $params['prefix'] ), $db->anyString() ) );
}
- if ( !is_null( $params['to'] ) ) {
- $this->addWhere( 'u1.user_name <= ' . $db->addQuotes( $this->keyToTitle( $params['to'] ) ) );
+
+ if ( !is_null( $params['rights'] ) ) {
+ $groups = array();
+ foreach( $params['rights'] as $r ) {
+ $groups = array_merge( $groups, User::getGroupsWithPermission( $r ) );
+ }
+
+ $groups = array_unique( $groups );
+
+ if ( is_null( $params['group'] ) ) {
+ $params['group'] = $groups;
+ } else {
+ $params['group'] = array_unique( array_merge( $params['group'], $groups ) );
+ }
}
- if ( !is_null( $params['prefix'] ) ) {
- $this->addWhere( 'u1.user_name' . $db->buildLike( $this->keyToTitle( $params['prefix'] ), $db->anyString() ) );
+ if ( !is_null( $params['group'] ) && !is_null( $params['excludegroup'] ) ) {
+ $this->dieUsage( 'group and excludegroup cannot be used together', 'group-excludegroup' );
}
- if ( !is_null( $params['group'] ) ) {
+ if ( !is_null( $params['group'] ) && count( $params['group'] ) ) {
$useIndex = false;
// Filter only users that belong to a given group
$this->addTables( 'user_groups', 'ug1' );
- $ug1 = $this->getAliasedName( 'user_groups', 'ug1' );
- $this->addJoinConds( array( $ug1 => array( 'INNER JOIN', array( 'ug1.ug_user=u1.user_id',
+ $this->addJoinConds( array( 'ug1' => array( 'INNER JOIN', array( 'ug1.ug_user=user_id',
'ug1.ug_group' => $params['group'] ) ) ) );
}
+ if ( !is_null( $params['excludegroup'] ) && count( $params['excludegroup'] ) ) {
+ $useIndex = false;
+ // Filter only users don't belong to a given group
+ $this->addTables( 'user_groups', 'ug1' );
+
+ if ( count( $params['excludegroup'] ) == 1 ) {
+ $exclude = array( 'ug1.ug_group' => $params['excludegroup'][0] );
+ } else {
+ $exclude = array( $db->makeList( array( 'ug1.ug_group' => $params['excludegroup'] ), LIST_OR ) );
+ }
+ $this->addJoinConds( array( 'ug1' => array( 'LEFT OUTER JOIN',
+ array_merge( array( 'ug1.ug_user=user_id' ), $exclude )
+ )
+ ) );
+ $this->addWhere( 'ug1.ug_user IS NULL' );
+ }
+
if ( $params['witheditsonly'] ) {
- $this->addWhere( 'u1.user_editcount > 0' );
+ $this->addWhere( 'user_editcount > 0' );
}
- if ( $fld_groups ) {
+ $this->showHiddenUsersAddBlockInfo( $fld_blockinfo );
+
+ if ( $fld_groups || $fld_rights ) {
// Show the groups the given users belong to
// request more than needed to avoid not getting all rows that belong to one user
$groupCount = count( User::getAllGroups() );
$sqlLimit = $limit + $groupCount + 1;
$this->addTables( 'user_groups', 'ug2' );
- $tname = $this->getAliasedName( 'user_groups', 'ug2' );
- $this->addJoinConds( array( $tname => array( 'LEFT JOIN', 'ug2.ug_user=u1.user_id' ) ) );
+ $this->addJoinConds( array( 'ug2' => array( 'LEFT JOIN', 'ug2.ug_user=user_id' ) ) );
$this->addFields( 'ug2.ug_group ug_group2' );
} else {
$sqlLimit = $limit + 1;
}
- $this->showHiddenUsersAddBlockInfo( $fld_blockinfo );
+
+ if ( $params['activeusers'] ) {
+ global $wgActiveUserDays;
+ $this->addTables( 'recentchanges' );
+
+ $this->addJoinConds( array( 'recentchanges' => array(
+ 'INNER JOIN', 'rc_user_text=user_name'
+ ) ) );
+
+ $this->addFields( 'COUNT(*) AS recentedits' );
+
+ $this->addWhere( "rc_log_type IS NULL OR rc_log_type != 'newusers'" );
+ $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays*24*3600 );
+ $this->addWhere( "rc_timestamp >= {$db->addQuotes( $timestamp )}" );
+
+ $this->addOption( 'GROUP BY', 'user_name' );
+ }
$this->addOption( 'LIMIT', $sqlLimit );
$this->addFields( array(
- 'u1.user_name',
- 'u1.user_id'
+ 'user_name',
+ 'user_id'
) );
- $this->addFieldsIf( 'u1.user_editcount', $fld_editcount );
- $this->addFieldsIf( 'u1.user_registration', $fld_registration );
+ $this->addFieldsIf( 'user_editcount', $fld_editcount );
+ $this->addFieldsIf( 'user_registration', $fld_registration );
- $this->addOption( 'ORDER BY', 'u1.user_name' );
if ( $useIndex ) {
- $u1 = $this->getAliasedName( 'user', 'u1' );
- $this->addOption( 'USE INDEX', array( $u1 => 'user_name' ) );
+ $this->addOption( 'USE INDEX', array( 'user' => 'user_name' ) );
}
$res = $this->select( __METHOD__ );
@@ -153,12 +205,13 @@ class ApiQueryAllUsers extends ApiQueryBase {
// Record new user's data
$lastUser = $row->user_name;
$lastUserData = array(
- 'name' => $lastUser,
'userid' => $row->user_id,
+ 'name' => $lastUser,
);
- if ( $fld_blockinfo && !is_null( $row->blocker_name ) ) {
- $lastUserData['blockedby'] = $row->blocker_name;
+ if ( $fld_blockinfo && !is_null( $row->ipb_by_text ) ) {
+ $lastUserData['blockedby'] = $row->ipb_by_text;
$lastUserData['blockreason'] = $row->ipb_reason;
+ $lastUserData['blockexpiry'] = $row->ipb_expiry;
}
if ( $row->ipb_deleted ) {
$lastUserData['hidden'] = '';
@@ -166,11 +219,13 @@ class ApiQueryAllUsers extends ApiQueryBase {
if ( $fld_editcount ) {
$lastUserData['editcount'] = intval( $row->user_editcount );
}
+ if ( $params['activeusers'] ) {
+ $lastUserData['recenteditcount'] = intval( $row->recentedits );
+ }
if ( $fld_registration ) {
$lastUserData['registration'] = $row->user_registration ?
wfTimestamp( TS_ISO_8601, $row->user_registration ) : '';
}
-
}
if ( $sqlLimit == $count ) {
@@ -181,10 +236,31 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
// Add user's group info
- if ( $fld_groups && !is_null( $row->ug_group2 ) ) {
- $lastUserData['groups'][] = $row->ug_group2;
+ if ( $fld_groups ) {
+ if ( !isset( $lastUserData['groups'] ) ) {
+ $lastUserData['groups'] = ApiQueryUsers::getAutoGroups( User::newFromName( $lastUser ) );
+ }
+
+ if ( !is_null( $row->ug_group2 ) ) {
+ $lastUserData['groups'][] = $row->ug_group2;
+ }
$result->setIndexedTagName( $lastUserData['groups'], 'g' );
}
+
+ if ( $fld_implicitgroups && !isset( $lastUserData['implicitgroups'] ) ) {
+ $lastUserData['implicitgroups'] = ApiQueryUsers::getAutoGroups( User::newFromName( $lastUser ) );
+ $result->setIndexedTagName( $lastUserData['implicitgroups'], 'g' );
+ }
+ if ( $fld_rights ) {
+ if ( !isset( $lastUserData['rights'] ) ) {
+ $lastUserData['rights'] = User::getGroupPermissions( User::newFromName( $lastUser )->getAutomaticGroups() );
+ }
+ if ( !is_null( $row->ug_group2 ) ) {
+ $lastUserData['rights'] = array_unique( array_merge( $lastUserData['rights'],
+ User::getGroupPermissions( array( $row->ug_group2 ) ) ) );
+ }
+ $result->setIndexedTagName( $lastUserData['rights'], 'r' );
+ }
}
if ( is_array( $lastUserData ) ) {
@@ -204,18 +280,37 @@ class ApiQueryAllUsers extends ApiQueryBase {
}
public function getAllowedParams() {
+ $userGroups = User::getAllGroups();
return array(
'from' => null,
'to' => null,
'prefix' => null,
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending'
+ ),
+ ),
'group' => array(
- ApiBase::PARAM_TYPE => User::getAllGroups()
+ ApiBase::PARAM_TYPE => $userGroups,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'excludegroup' => array(
+ ApiBase::PARAM_TYPE => $userGroups,
+ ApiBase::PARAM_ISMULTI => true,
+ ),
+ 'rights' => array(
+ ApiBase::PARAM_TYPE => User::getAllRights(),
+ ApiBase::PARAM_ISMULTI => true,
),
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array(
'blockinfo',
'groups',
+ 'implicitgroups',
+ 'rights',
'editcount',
'registration'
)
@@ -228,24 +323,32 @@ class ApiQueryAllUsers extends ApiQueryBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'witheditsonly' => false,
+ 'activeusers' => false,
);
}
public function getParamDescription() {
+ global $wgActiveUserDays;
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',
+ 'dir' => 'Direction to sort in',
+ 'group' => 'Limit users to given group name(s)',
+ 'excludegroup' => 'Exclude users in given group name(s)',
+ 'rights' => 'Limit users to given right(s)',
'prop' => array(
'What pieces of information to include.',
- ' blockinfo - Adds the information about a current block on the user',
- ' groups - Lists groups that the user is in',
- ' 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' ),
+ ' blockinfo - Adds the information about a current block on the user',
+ ' groups - Lists groups that the user is in. This uses more server resources and may return fewer results than the limit',
+ ' implicitgroups - Lists all the groups the user is automatically in',
+ ' rights - Lists rights that the user has',
+ ' editcount - Adds the edit count of the user',
+ ' registration - Adds the timestamp of when the user registered if available (may be blank)',
+ ),
'limit' => 'How many total user names to return',
'witheditsonly' => 'Only list users who have made edits',
+ 'activeusers' => "Only list users active in the last {$wgActiveUserDays} days(s)"
);
}
@@ -253,13 +356,23 @@ class ApiQueryAllUsers extends ApiQueryBase {
return 'Enumerate all registered users';
}
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => 'group-excludegroup', 'info' => 'group and excludegroup cannot be used together' ),
+ ) );
+ }
+
protected function getExamples() {
return array(
'api.php?action=query&list=allusers&aufrom=Y',
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Allusers';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllUsers.php 85354 2011-04-04 18:25:31Z demon $';
+ return __CLASS__ . ': $Id: ApiQueryAllUsers.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryAllimages.php b/includes/api/ApiQueryAllimages.php
index a7825519..cafff871 100644
--- a/includes/api/ApiQueryAllimages.php
+++ b/includes/api/ApiQueryAllimages.php
@@ -38,16 +38,19 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*/
class ApiQueryAllimages extends ApiQueryGeneratorBase {
+ protected $mRepo;
+
public function __construct( $query, $moduleName ) {
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
+ * Override 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.
+ * @return DatabaseBase
*/
protected function getDB() {
return $this->mRepo->getSlaveDB();
@@ -61,6 +64,10 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
return 'public';
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
public function executeGenerator( $resultPageSet ) {
if ( $resultPageSet->isResolvingRedirects() ) {
$this->dieUsage( 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator', 'params' );
@@ -69,6 +76,10 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
$repo = $this->mRepo;
if ( !$repo instanceof LocalRepo ) {
@@ -98,12 +109,30 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
$sha1 = false;
if ( isset( $params['sha1'] ) ) {
+ if ( !$this->validateSha1Hash( $params['sha1'] ) ) {
+ $this->dieUsage( 'The SHA1 hash provided is not valid', 'invalidsha1hash' );
+ }
$sha1 = wfBaseConvert( $params['sha1'], 16, 36, 31 );
} elseif ( isset( $params['sha1base36'] ) ) {
$sha1 = $params['sha1base36'];
+ if ( !$this->validateSha1Base36Hash( $sha1 ) ) {
+ $this->dieUsage( 'The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash' );
+ }
}
if ( $sha1 ) {
- $this->addWhere( 'img_sha1=' . $db->addQuotes( $sha1 ) );
+ $this->addWhereFld( 'img_sha1', $sha1 );
+ }
+
+ if ( !is_null( $params['mime'] ) ) {
+ global $wgMiserMode;
+ if ( $wgMiserMode ) {
+ $this->dieUsage( 'MIME search disabled in Miser Mode', 'mimesearchdisabled' );
+ }
+
+ list( $major, $minor ) = File::splitMime( $params['mime'] );
+
+ $this->addWhereFld( 'img_major_mime', $major );
+ $this->addWhereFld( 'img_minor_mime', $minor );
}
$this->addTables( 'image' );
@@ -133,6 +162,8 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
$file = $repo->newFileFromRow( $row );
$info = array_merge( array( 'name' => $row->img_name ),
ApiQueryImageInfo::getInfo( $file, $prop, $result ) );
+ self::addTitleInfo( $info, $file->getTitle() );
+
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $info );
if ( !$fit ) {
$this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->img_name ) );
@@ -178,10 +209,11 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
'sha1' => null,
'sha1base36' => null,
'prop' => array(
- ApiBase::PARAM_TYPE => ApiQueryImageInfo::getPropertyNames(),
+ ApiBase::PARAM_TYPE => ApiQueryImageInfo::getPropertyNames( $this->propertyFilter ),
ApiBase::PARAM_DFLT => 'timestamp|url',
ApiBase::PARAM_ISMULTI => true
- )
+ ),
+ 'mime' => null,
);
}
@@ -196,24 +228,13 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
'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' => 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',
- ),
+ 'prop' => ApiQueryImageInfo::getPropertyDescriptions( $this->propertyFilter ),
+ 'mime' => 'What MIME type to search for. e.g. image/jpeg. Disabled in Miser Mode',
);
}
+ private $propertyFilter = array( 'archivename' );
+
public function getDescription() {
return 'Enumerate all images sequentially';
}
@@ -222,6 +243,9 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'params', 'info' => 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator' ),
array( 'code' => 'unsupportedrepo', 'info' => 'Local file repository does not support querying all images' ),
+ array( 'code' => 'mimesearchdisabled', 'info' => 'MIME search disabled in Miser Mode' ),
+ array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ),
+ array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ),
) );
}
@@ -236,7 +260,11 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Allimages';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllimages.php 71838 2010-08-28 01:18:18Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryAllimages.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryAllmessages.php b/includes/api/ApiQueryAllmessages.php
index 81ff255a..a26011bc 100644
--- a/includes/api/ApiQueryAllmessages.php
+++ b/includes/api/ApiQueryAllmessages.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Dec 1, 2007
*
@@ -43,12 +43,22 @@ class ApiQueryAllmessages extends ApiQueryBase {
public function execute() {
$params = $this->extractRequestParams();
- global $wgLang;
+ if ( is_null( $params['lang'] ) ) {
+ global $wgLang;
+ $langObj = $wgLang;
+ } else {
+ $langObj = Language::factory( $params['lang'] );
+ }
- $oldLang = null;
- if ( !is_null( $params['lang'] ) ) {
- $oldLang = $wgLang; // Keep $wgLang for restore later
- $wgLang = Language::factory( $params['lang'] );
+ if ( $params['enableparser'] ) {
+ if ( !is_null( $params['title'] ) ) {
+ $title = Title::newFromText( $params['title'] );
+ if ( !$title ) {
+ $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
+ }
+ } else {
+ $title = Title::newFromText( 'API' );
+ }
}
$prop = array_flip( (array)$params['prop'] );
@@ -62,7 +72,26 @@ class ApiQueryAllmessages extends ApiQueryBase {
$messages_target = $params['messages'];
}
- // Filter messages
+ // Filter messages that have the specified prefix
+ // Because we sorted the message array earlier, they will appear in a clump:
+ if ( isset( $params['prefix'] ) ) {
+ $skip = false;
+ $messages_filtered = array();
+ foreach ( $messages_target as $message ) {
+ // === 0: must be at beginning of string (position 0)
+ if ( strpos( $message, $params['prefix'] ) === 0 ) {
+ if( !$skip ) {
+ $skip = true;
+ }
+ $messages_filtered[] = $message;
+ } elseif ( $skip ) {
+ break;
+ }
+ }
+ $messages_target = $messages_filtered;
+ }
+
+ // Filter messages that contain specified string
if ( isset( $params['filter'] ) ) {
$messages_filtered = array();
foreach ( $messages_target as $message ) {
@@ -74,6 +103,18 @@ class ApiQueryAllmessages extends ApiQueryBase {
$messages_target = $messages_filtered;
}
+ // Whether we have any sort of message customisation filtering
+ $customiseFilterEnabled = $params['customised'] !== 'all';
+ if ( $customiseFilterEnabled ) {
+ global $wgContLang;
+ $lang = $langObj->getCode();
+
+ $customisedMessages = AllmessagesTablePager::getCustomisedStatuses(
+ array_map( array( $langObj, 'ucfirst'), $messages_target ), $lang, $lang != $wgContLang->getCode() );
+
+ $customised = $params['customised'] === 'modified';
+ }
+
// Get all requested messages and print the result
$skip = !is_null( $params['from'] );
$useto = !is_null( $params['to'] );
@@ -83,39 +124,47 @@ class ApiQueryAllmessages extends ApiQueryBase {
if ( $skip && $message === $params['from'] ) {
$skip = false;
}
-
- if( $useto && $message > $params['to'] ) {
+
+ if ( $useto && $message > $params['to'] ) {
break;
}
if ( !$skip ) {
$a = array( 'name' => $message );
- $args = null;
+ $args = array();
if ( isset( $params['args'] ) && count( $params['args'] ) != 0 ) {
$args = $params['args'];
}
- // Check if the parser is enabled:
- if ( $params['enableparser'] ) {
- $msg = wfMsgExt( $message, array( 'parsemag' ), $args );
- } elseif ( $args ) {
- $msgString = wfMsgGetKey( $message, true, false, false );
- $msg = wfMsgReplaceArgs( $msgString, $args );
- } else {
- $msg = wfMsgGetKey( $message, true, false, false );
+
+ if ( $customiseFilterEnabled ) {
+ $messageIsCustomised = isset( $customisedMessages['pages'][ $langObj->ucfirst( $message ) ] );
+ if ( $customised === $messageIsCustomised ) {
+ if ( $customised ) {
+ $a['customised'] = '';
+ }
+ } else {
+ continue;
+ }
}
- if ( wfEmptyMsg( $message, $msg ) ) {
+ $msg = wfMessage( $message, $args )->inLanguage( $langObj );
+
+ if ( !$msg->exists() ) {
$a['missing'] = '';
} else {
- ApiResult::setContent( $a, $msg );
+ // Check if the parser is enabled:
+ if ( $params['enableparser'] ) {
+ $msgString = $msg->title( $title )->text();
+ } else {
+ $msgString = $msg->plain();
+ }
+ ApiResult::setContent( $a, $msgString );
if ( isset( $prop['default'] ) ) {
- $default = wfMsgGetKey( $message, false, false, false );
- if ( $default !== $msg ) {
- if ( wfEmptyMsg( $message, $default ) ) {
- $a['defaultmissing'] = '';
- } else {
- $a['default'] = $default;
- }
+ $default = wfMessage( $message )->inLanguage( $langObj )->useDatabase( false );
+ if ( !$default->exists() ) {
+ $a['defaultmissing'] = '';
+ } elseif ( $default->plain() != $msgString ) {
+ $a['default'] = $default->plain();
}
}
}
@@ -127,10 +176,6 @@ class ApiQueryAllmessages extends ApiQueryBase {
}
}
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'message' );
-
- if ( !is_null( $oldLang ) ) {
- $wgLang = $oldLang; // Restore $oldLang
- }
}
public function getCacheMode( $params ) {
@@ -160,23 +205,37 @@ class ApiQueryAllmessages extends ApiQueryBase {
),
'enableparser' => false,
'args' => array(
- ApiBase::PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_ALLOW_DUPLICATES => true,
),
'filter' => array(),
+ 'customised' => array(
+ ApiBase::PARAM_DFLT => 'all',
+ ApiBase::PARAM_TYPE => array(
+ 'all',
+ 'modified',
+ 'unmodified'
+ )
+ ),
'lang' => null,
'from' => null,
'to' => null,
+ 'title' => null,
+ 'prefix' => null,
);
}
public function getParamDescription() {
return array(
- 'messages' => 'Which messages to output. "*" means all messages',
+ 'messages' => 'Which messages to output. "*" (default) means all messages',
'prop' => 'Which properties to get',
'enableparser' => array( 'Set to enable parser, will preprocess the wikitext of message',
'Will substitute magic words, handle templates etc.' ),
+ 'title' => 'Page name to use as context when parsing message (for enableparser option)',
'args' => 'Arguments to be substituted into message',
- 'filter' => 'Return only messages that contain this string',
+ 'prefix' => 'Return messages with this prefix',
+ 'filter' => 'Return only messages with names that contain this string',
+ 'customised' => 'Return only messages in this customisation state',
'lang' => 'Return messages in this language',
'from' => 'Return messages starting at this message',
'to' => 'Return messages ending at this message',
@@ -189,12 +248,16 @@ class ApiQueryAllmessages extends ApiQueryBase {
protected function getExamples() {
return array(
- 'api.php?action=query&meta=allmessages&amfilter=ipb-',
+ 'api.php?action=query&meta=allmessages&amprefix=ipb-',
'api.php?action=query&meta=allmessages&ammessages=august|mainpage&amlang=de',
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Meta#allmessages_.2F_am';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllmessages.php 73756 2010-09-25 17:08:23Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryAllmessages.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php
index 21f72916..42928418 100644
--- a/includes/api/ApiQueryAllpages.php
+++ b/includes/api/ApiQueryAllpages.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 25, 2006
*
@@ -48,6 +48,10 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
return 'public';
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
public function executeGenerator( $resultPageSet ) {
if ( $resultPageSet->isResolvingRedirects() ) {
$this->dieUsage( 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator', 'params' );
@@ -56,6 +60,10 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
$db = $this->getDB();
@@ -75,7 +83,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
$from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) );
$to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
$this->addWhereRange( 'page_title', $dir, $from, $to );
-
+
if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'page_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
}
@@ -103,30 +111,38 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
}
// Page protection filtering
- if ( !empty( $params['prtype'] ) ) {
+ if ( count( $params['prtype'] ) || $params['prexpiry'] != 'all' ) {
$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'] ) ) {
- // Remove the empty string and '*' from the prlevel array
- $prlevel = array_diff( $params['prlevel'], array( '', '*' ) );
+ if ( count( $params['prtype'] ) ) {
+ $this->addWhereFld( 'pr_type', $params['prtype'] );
- if ( !empty( $prlevel ) ) {
- $this->addWhereFld( 'pr_level', $prlevel );
- }
- }
- if ( $params['prfiltercascade'] == 'cascading' ) {
- $this->addWhereFld( 'pr_cascade', 1 );
- } elseif ( $params['prfiltercascade'] == 'noncascading' ) {
- $this->addWhereFld( 'pr_cascade', 0 );
- }
+ if ( isset( $params['prlevel'] ) ) {
+ // Remove the empty string and '*' from the prlevel array
+ $prlevel = array_diff( $params['prlevel'], array( '', '*' ) );
- $this->addOption( 'DISTINCT' );
+ if ( count( $prlevel ) ) {
+ $this->addWhereFld( 'pr_level', $prlevel );
+ }
+ }
+ if ( $params['prfiltercascade'] == 'cascading' ) {
+ $this->addWhereFld( 'pr_cascade', 1 );
+ } elseif ( $params['prfiltercascade'] == 'noncascading' ) {
+ $this->addWhereFld( 'pr_cascade', 0 );
+ }
+ $this->addOption( 'DISTINCT' );
+ }
$forceNameTitleIndex = false;
+ if ( $params['prexpiry'] == 'indefinite' ) {
+ $this->addWhere( "pr_expiry = {$db->addQuotes( $db->getInfinity() )} OR pr_expiry IS NULL" );
+ } elseif ( $params['prexpiry'] == 'definite' ) {
+ $this->addWhere( "pr_expiry != {$db->addQuotes( $db->getInfinity() )}" );
+ }
+
} elseif ( isset( $params['prlevel'] ) ) {
$this->dieUsage( 'prlevel may not be used without prtype', 'params' );
}
@@ -248,7 +264,15 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
'all'
),
ApiBase::PARAM_DFLT => 'all'
- )
+ ),
+ 'prexpiry' => array(
+ ApiBase::PARAM_TYPE => array(
+ 'indefinite',
+ 'definite',
+ 'all'
+ ),
+ ApiBase::PARAM_DFLT => 'all'
+ ),
);
}
@@ -267,7 +291,13 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
'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.'
+ 'limit' => 'How many total pages to return.',
+ 'prexpiry' => array(
+ 'Which protection expiry to filter the page on',
+ ' indefinite - Get only pages with indefinite protection expiry',
+ ' definite - Get only pages with a definite (specific) protection expiry',
+ ' all - Get pages with any protections expiry'
+ ),
);
}
@@ -295,7 +325,11 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Allpages';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryAllpages.php 85354 2011-04-04 18:25:31Z demon $';
+ return __CLASS__ . ': $Id: ApiQueryAllpages.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
index b412d2d6..472406ac 100644
--- a/includes/api/ApiQueryBacklinks.php
+++ b/includes/api/ApiQueryBacklinks.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 16, 2006
*
@@ -39,26 +39,44 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*/
class ApiQueryBacklinks extends ApiQueryGeneratorBase {
- private $params, $rootTitle, $contID, $redirID, $redirect;
+ /**
+ * @var Title
+ */
+ private $rootTitle;
+
+ private $params, $contID, $redirID, $redirect;
private $bl_ns, $bl_from, $bl_table, $bl_code, $bl_title, $bl_sort, $bl_fields, $hasNS;
- private $pageMap, $resultArr;
+
+ /**
+ * Maps ns and title to pageid
+ *
+ * @var array
+ */
+ private $pageMap = array();
+ private $resultArr;
+
+ private $redirTitles = array();
+ private $continueStr = null;
// output element name, database column field prefix, database table
private $backlinksSettings = array(
'backlinks' => array(
'code' => 'bl',
'prefix' => 'pl',
- 'linktbl' => 'pagelinks'
+ 'linktbl' => 'pagelinks',
+ 'helpurl' => 'https://www.mediawiki.org/wiki/API:Backlinks',
),
'embeddedin' => array(
'code' => 'ei',
'prefix' => 'tl',
- 'linktbl' => 'templatelinks'
+ 'linktbl' => 'templatelinks',
+ 'helpurl' => 'https://www.mediawiki.org/wiki/API:Embeddedin',
),
'imageusage' => array(
'code' => 'iu',
'prefix' => 'il',
- 'linktbl' => 'imagelinks'
+ 'linktbl' => 'imagelinks',
+ 'helpurl' => 'https://www.mediawiki.org/wiki/API:Imageusage',
)
);
@@ -73,6 +91,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->bl_from = $prefix . '_from';
$this->bl_table = $settings['linktbl'];
$this->bl_code = $code;
+ $this->helpUrl = $settings['helpurl'];
$this->hasNS = $moduleName !== 'imageusage';
if ( $this->hasNS ) {
@@ -103,6 +122,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function prepareFirstQuery( $resultPageSet = null ) {
/* SELECT page_id, page_title, page_namespace, page_is_redirect
* FROM pagelinks, page WHERE pl_from=page_id
@@ -141,6 +164,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->addOption( 'STRAIGHT_JOIN' );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function prepareSecondQuery( $resultPageSet = null ) {
/* SELECT page_id, page_title, page_namespace, page_is_redirect, pl_title, pl_namespace
FROM pagelinks, page WHERE pl_from=page_id
@@ -199,14 +226,21 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->addOption( 'USE INDEX', array( 'page' => 'PRIMARY' ) );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
$this->params = $this->extractRequestParams( false );
$this->redirect = isset( $this->params['redirect'] ) && $this->params['redirect'];
$userMax = ( $this->redirect ? ApiBase::LIMIT_BIG1 / 2 : ApiBase::LIMIT_BIG1 );
$botMax = ( $this->redirect ? ApiBase::LIMIT_BIG2 / 2 : ApiBase::LIMIT_BIG2 );
+
+ $result = $this->getResult();
+
if ( $this->params['limit'] == 'max' ) {
$this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
- $this->getResult()->setParsedLimit( $this->getModuleName(), $this->params['limit'] );
+ $result->setParsedLimit( $this->getModuleName(), $this->params['limit'] );
}
$this->processContinue();
@@ -215,9 +249,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$res = $this->select( __METHOD__ . '::firstQuery' );
$count = 0;
- $this->pageMap = array(); // Maps ns and title to pageid
- $this->continueStr = null;
- $this->redirTitles = array();
+
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...
@@ -248,9 +280,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
// 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}];
+ $parentID = $this->pageMap[$row-> { $this->bl_ns } ][$row-> { $this->bl_title } ];
} else {
- $parentID = $this->pageMap[NS_IMAGE][$row->{$this->bl_title}];
+ $parentID = $this->pageMap[NS_IMAGE][$row-> { $this->bl_title } ];
}
$this->continueStr = $this->getContinueRedirStr( $parentID, $row->page_id );
break;
@@ -265,13 +297,13 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
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 ) );
+ $fit = $result->addValue( 'query', $this->getModuleName(), array_values( $this->resultArr ) );
if ( !$fit ) {
// It didn't fit. Add elements one by one until the
// result is full.
foreach ( $this->resultArr as $pageID => $arr ) {
// Add the basic entry without redirlinks first
- $fit = $this->getResult()->addValue(
+ $fit = $result->addValue(
array( 'query', $this->getModuleName() ),
null, array_diff_key( $arr, array( 'redirlinks' => '' ) ) );
if ( !$fit ) {
@@ -280,8 +312,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
$hasRedirs = false;
- foreach ( (array)@$arr['redirlinks'] as $key => $redir ) {
- $fit = $this->getResult()->addValue(
+ $redirLinks = isset( $arr['redirlinks'] ) ? $arr['redirlinks'] : array();
+ foreach ( (array)$redirLinks as $key => $redir ) {
+ $fit = $result->addValue(
array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ),
$key, $redir );
if ( !$fit ) {
@@ -291,7 +324,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$hasRedirs = true;
}
if ( $hasRedirs ) {
- $this->getResult()->setIndexedTagName_internal(
+ $result->setIndexedTagName_internal(
array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ),
$this->bl_code );
}
@@ -301,7 +334,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
}
}
- $this->getResult()->setIndexedTagName_internal(
+ $result->setIndexedTagName_internal(
array( 'query', $this->getModuleName() ),
$this->bl_code
);
@@ -383,9 +416,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
$this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' );
}
$this->contID = $contID;
- $redirID = intval( @$continueList[3] );
+ $id2 = isset( $continueList[3] ) ? $continueList[3] : null;
+ $redirID = intval( $id2 );
- if ( $redirID === 0 && @$continueList[3] !== '0' ) {
+ if ( $redirID === 0 && $id2 !== '0' ) {
// This one isn't required
return;
}
@@ -496,7 +530,11 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
return $examples[$this->getModuleName()];
}
+ public function getHelpUrls() {
+ return $this->helpUrl;
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryBacklinks.php 75921 2010-11-03 12:49:21Z demon $';
+ return __CLASS__ . ': $Id: ApiQueryBacklinks.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 61a5b4c8..69e0a893 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 7, 2006
*
@@ -54,6 +54,8 @@ abstract class ApiQueryBase extends ApiBase {
*
* Public caching will only be allowed if *all* the modules that supply
* data for a given request return a cache mode of public.
+ *
+ * @return string
*/
public function getCacheMode( $params ) {
return 'private';
@@ -84,23 +86,14 @@ abstract class ApiQueryBase extends ApiBase {
$this->tables = array_merge( $this->tables, $tables );
} else {
if ( !is_null( $alias ) ) {
- $tables = $this->getAliasedName( $tables, $alias );
+ $this->tables[$alias] = $tables;
+ } else {
+ $this->tables[] = $tables;
}
- $this->tables[] = $tables;
}
}
/**
- * Get the SQL for a table name with alias
- * @param $table string Table name
- * @param $alias string Alias
- * @return string SQL
- */
- protected function getAliasedName( $table, $alias ) {
- return $this->getDB()->tableName( $table ) . ' ' . $alias;
- }
-
- /**
* Add a set of JOIN conditions to the internal array
*
* JOIN conditions are formatted as array( tablename => array(jointype,
@@ -118,7 +111,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Add a set of fields to select to the internal array
- * @param $value mixed Field name or array of field names
+ * @param $value array|string Field name or array of field names
*/
protected function addFields( $value ) {
if ( is_array( $value ) ) {
@@ -130,7 +123,7 @@ abstract class ApiQueryBase extends ApiBase {
/**
* Same as addFields(), but add the fields only if a condition is met
- * @param $value mixed See addFields()
+ * @param $value array|string See addFields()
* @param $condition bool If false, do nothing
* @return bool $condition
*/
@@ -227,6 +220,16 @@ abstract class ApiQueryBase extends ApiBase {
}
}
}
+ /**
+ * Add a WHERE clause corresponding to a range, similar to addWhereRange,
+ * but converts $start and $end to database timestamps.
+ * @see addWhereRange
+ */
+ protected function addTimestampWhereRange( $field, $dir, $start, $end, $sort = true ) {
+ $db = $this->getDb();
+ return $this->addWhereRange( $field, $dir,
+ $db->timestampOrNull( $start ), $db->timestampOrNull( $end ), $sort );
+ }
/**
* Add an option such as LIMIT or USE INDEX. If an option was set
@@ -359,14 +362,15 @@ abstract class ApiQueryBase extends ApiBase {
protected function setContinueEnumParameter( $paramName, $paramValue ) {
$paramName = $this->encodeParamName( $paramName );
$msg = array( $paramName => $paramValue );
- $this->getResult()->disableSizeCheck();
- $this->getResult()->addValue( 'query-continue', $this->getModuleName(), $msg );
- $this->getResult()->enableSizeCheck();
+ $result = $this->getResult();
+ $result->disableSizeCheck();
+ $result->addValue( 'query-continue', $this->getModuleName(), $msg );
+ $result->enableSizeCheck();
}
/**
* Get the Query database connection (read-only)
- * @return Database
+ * @return DatabaseBase
*/
protected function getDB() {
if ( is_null( $this->mDb ) ) {
@@ -450,6 +454,47 @@ abstract class ApiQueryBase extends ApiBase {
}
/**
+ * Gets the personalised direction parameter description
+ *
+ * @param string $p ModulePrefix
+ * @param string $extraDirText Any extra text to be appended on the description
+ * @return array
+ */
+ public function getDirectionDescription( $p = '', $extraDirText = '' ) {
+ return array(
+ "In which direction to enumerate{$extraDirText}",
+ " newer - List oldest first. Note: {$p}start has to be before {$p}end.",
+ " older - List newest first (default). Note: {$p}start has to be later than {$p}end.",
+ );
+ }
+
+ /**
+ * @param $query String
+ * @param $protocol String
+ * @return null|string
+ */
+ public function prepareUrlQuerySearchString( $query = null, $protocol = null) {
+ $db = $this->getDb();
+ if ( !is_null( $query ) || $query != '' ) {
+ if ( is_null( $protocol ) ) {
+ $protocol = 'http://';
+ }
+
+ $likeQuery = LinkFilter::makeLikeArray( $query, $protocol );
+ if ( !$likeQuery ) {
+ $this->dieUsage( 'Invalid query', 'bad_query' );
+ }
+
+ $likeQuery = LinkFilter::keepOneWildcard( $likeQuery );
+ return 'el_index ' . $db->buildLike( $likeQuery );
+ } elseif ( !is_null( $protocol ) ) {
+ return 'el_index ' . $db->buildLike( "$protocol", $db->anyString() );
+ }
+
+ return null;
+ }
+
+ /**
* Filters hidden users (where the user doesn't have the right to view them)
* Also adds relevant block information
*
@@ -479,6 +524,25 @@ abstract class ApiQueryBase extends ApiBase {
}
}
+ /**
+ * @param $hash string
+ * @return bool
+ */
+ public function validateSha1Hash( $hash ) {
+ return preg_match( '/[a-fA-F0-9]{40}/', $hash );
+ }
+
+ /**
+ * @param $hash string
+ * @return bool
+ */
+ public function validateSha1Base36Hash( $hash ) {
+ return preg_match( '/[a-zA-Z0-9]{31}/', $hash );
+ }
+
+ /**
+ * @return array
+ */
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'invalidtitle', 'title' ),
@@ -491,7 +555,7 @@ abstract class ApiQueryBase extends ApiBase {
* @return string
*/
public static function getBaseVersion() {
- return __CLASS__ . ': $Id: ApiQueryBase.php 85435 2011-04-05 14:00:08Z demon $';
+ return __CLASS__ . ': $Id: ApiQueryBase.php 103029 2011-11-14 20:58:30Z reedy $';
}
}
diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php
index 4edda645..503af7c7 100644
--- a/includes/api/ApiQueryBlocks.php
+++ b/includes/api/ApiQueryBlocks.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 10, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,30 +30,33 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
/**
- * Query module to enumerate all available pages.
+ * Query module to enumerate all user blocks
*
* @ingroup API
*/
class ApiQueryBlocks extends ApiQueryBase {
- var $users;
+ /**
+ * @var Array
+ */
+ protected $usernames;
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'bk' );
}
public function execute() {
- global $wgUser;
+ global $wgUser, $wgContLang;
$params = $this->extractRequestParams();
- if ( isset( $params['users'] ) && isset( $params['ip'] ) ) {
- $this->dieUsage( 'bkusers and bkip cannot be used together', 'usersandip' );
- }
+ $this->requireMaxOneParameter( $params, 'users', 'ip' );
$prop = array_flip( $params['prop'] );
$fld_id = isset( $prop['id'] );
$fld_user = isset( $prop['user'] );
+ $fld_userid = isset( $prop['userid'] );
$fld_by = isset( $prop['by'] );
+ $fld_byid = isset( $prop['byid'] );
$fld_timestamp = isset( $prop['timestamp'] );
$fld_expiry = isset( $prop['expiry'] );
$fld_reason = isset( $prop['reason'] );
@@ -65,35 +68,20 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addTables( 'ipblocks' );
$this->addFields( 'ipb_auto' );
- if ( $fld_id ) {
- $this->addFields( 'ipb_id' );
- }
- if ( $fld_user ) {
- $this->addFields( array( 'ipb_address', 'ipb_user' ) );
- }
- if ( $fld_by ) {
- $this->addTables( 'user' );
- $this->addFields( array( 'ipb_by', 'user_name' ) );
- $this->addWhere( 'user_id = ipb_by' );
- }
- if ( $fld_timestamp ) {
- $this->addFields( 'ipb_timestamp' );
- }
- if ( $fld_expiry ) {
- $this->addFields( 'ipb_expiry' );
- }
- if ( $fld_reason ) {
- $this->addFields( 'ipb_reason' );
- }
- if ( $fld_range ) {
- $this->addFields( array( 'ipb_range_start', 'ipb_range_end' ) );
- }
- if ( $fld_flags ) {
- $this->addFields( array( 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock', 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ) );
- }
+ $this->addFieldsIf ( 'ipb_id', $fld_id );
+ $this->addFieldsIf( array( 'ipb_address', 'ipb_user' ), $fld_user || $fld_userid );
+ $this->addFieldsIf( 'ipb_by_text', $fld_by );
+ $this->addFieldsIf( 'ipb_by', $fld_byid );
+ $this->addFieldsIf( 'ipb_timestamp', $fld_timestamp );
+ $this->addFieldsIf( 'ipb_expiry', $fld_expiry );
+ $this->addFieldsIf( 'ipb_reason', $fld_reason );
+ $this->addFieldsIf( array( 'ipb_range_start', 'ipb_range_end' ), $fld_range );
+ $this->addFieldsIf( array( 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock',
+ 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ),
+ $fld_flags );
$this->addOption( 'LIMIT', $params['limit'] + 1 );
- $this->addWhereRange( 'ipb_timestamp', $params['dir'], $params['start'], $params['end'] );
+ $this->addTimestampWhereRange( 'ipb_timestamp', $params['dir'], $params['start'], $params['end'] );
if ( isset( $params['ids'] ) ) {
$this->addWhereFld( 'ipb_id', $params['ids'] );
}
@@ -151,14 +139,20 @@ class ApiQueryBlocks extends ApiQueryBase {
if ( $fld_user && !$row->ipb_auto ) {
$block['user'] = $row->ipb_address;
}
+ if ( $fld_userid && !$row->ipb_auto ) {
+ $block['userid'] = $row->ipb_user;
+ }
if ( $fld_by ) {
- $block['by'] = $row->user_name;
+ $block['by'] = $row->ipb_by_text;
+ }
+ if ( $fld_byid ) {
+ $block['byid'] = $row->ipb_by;
}
if ( $fld_timestamp ) {
$block['timestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp );
}
if ( $fld_expiry ) {
- $block['expiry'] = Block::decodeExpiry( $row->ipb_expiry, TS_ISO_8601 );
+ $block['expiry'] = $wgContLang->formatExpiry( $row->ipb_expiry, TS_ISO_8601 );
}
if ( $fld_reason ) {
$block['reason'] = $row->ipb_reason;
@@ -248,7 +242,9 @@ class ApiQueryBlocks extends ApiQueryBase {
ApiBase::PARAM_TYPE => array(
'id',
'user',
+ 'userid',
'by',
+ 'byid',
'timestamp',
'expiry',
'reason',
@@ -264,7 +260,7 @@ class ApiQueryBlocks extends ApiQueryBase {
return array(
'start' => 'The timestamp to start enumerating from',
'end' => 'The timestamp to stop enumerating at',
- 'dir' => 'The direction in which to enumerate',
+ 'dir' => $this->getDirectionDescription( $this->getModulePrefix() ),
'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.',
@@ -272,9 +268,11 @@ class ApiQueryBlocks extends ApiQueryBase {
'limit' => 'The maximum amount of blocks to list',
'prop' => array(
'Which properties to get',
- ' id - Adds the id of the block',
+ ' id - Adds the ID of the block',
' user - Adds the username of the blocked user',
- ' by - Adds the username of the blocking admin',
+ ' userid - Adds the user ID of the blocked user',
+ ' by - Adds the username of the blocking user',
+ ' byid - Adds the user ID of the blocking user',
' timestamp - Adds the timestamp of when the block was given',
' expiry - Adds the timestamp of when the block expires',
' reason - Adds the reason given for the block',
@@ -290,7 +288,7 @@ class ApiQueryBlocks extends ApiQueryBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'usersandip', 'info' => 'bkusers and bkip cannot be used together' ),
+ $this->getRequireOnlyOneParameterErrorMessages( array( 'users', 'ip' ) ),
array( 'code' => 'cidrtoobroad', 'info' => 'CIDR ranges broader than /16 are not accepted' ),
array( 'code' => 'param_user', 'info' => 'User parameter may not be empty' ),
array( 'code' => 'param_user', 'info' => 'User name user is not valid' ),
@@ -304,7 +302,11 @@ class ApiQueryBlocks extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Blocks';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryBlocks.php 73858 2010-09-28 01:21:15Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryBlocks.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php
index b2769dc2..c2942493 100644
--- a/includes/api/ApiQueryCategories.php
+++ b/includes/api/ApiQueryCategories.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on May 13, 2007
*
@@ -52,6 +52,10 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return
+ */
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
return; // nothing to do
@@ -100,7 +104,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
}
if ( isset( $show['hidden'] ) && isset( $show['!hidden'] ) ) {
- $this->dieUsageMsg( array( 'show' ) );
+ $this->dieUsageMsg( 'show' );
}
if ( isset( $show['hidden'] ) || isset( $show['!hidden'] ) || isset( $prop['hidden'] ) )
{
@@ -246,7 +250,11 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#categories_.2F_cl';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryCategories.php 86474 2011-04-20 13:22:05Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryCategories.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php
index d4b64025..dd3bc391 100644
--- a/includes/api/ApiQueryCategoryInfo.php
+++ b/includes/api/ApiQueryCategoryInfo.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on May 13, 2007
*
@@ -119,7 +119,11 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
return 'api.php?action=query&prop=categoryinfo&titles=Category:Foo|Category:Bar';
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#categoryinfo_.2F_ci';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php
index bbcf8b9b..e48789fc 100644
--- a/includes/api/ApiQueryCategoryMembers.php
+++ b/includes/api/ApiQueryCategoryMembers.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on June 14, 2007
*
@@ -52,13 +52,29 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
$params = $this->extractRequestParams();
- $categoryTitle = Title::newFromText( $params['title'] );
+ $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
+
+ if ( isset( $params['title'] ) ) {
+ $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' );
+ }
+ } elseif( isset( $params['pageid'] ) ) {
+ $categoryTitle = Title::newFromID( $params['pageid'] );
+
+ if ( !$categoryTitle ) {
+ $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) );
+ } elseif ( $categoryTitle->getNamespace() != NS_CATEGORY ) {
+ $this->dieUsage( 'The category name you entered is not valid', 'invalidcategory' );
+ }
}
$prop = array_flip( $params['prop'] );
@@ -113,11 +129,11 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
'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] ) );
@@ -127,13 +143,22 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$contWhere = "cl_sortkey $op $escSortkey OR " .
"(cl_sortkey = $escSortkey AND " .
"cl_from $op= $from)";
-
+ // The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them
+ $this->addWhereRange( 'cl_sortkey', $dir, null, null );
+ $this->addWhereRange( 'cl_from', $dir, null, null );
} else {
+ $startsortkey = $params['startsortkeyprefix'] !== null ?
+ Collation::singleton()->getSortkey( $params['startsortkeyprefix'] ) :
+ $params['startsortkey'];
+ $endsortkey = $params['endsortkeyprefix'] !== null ?
+ Collation::singleton()->getSortkey( $params['endsortkeyprefix'] ) :
+ $params['endsortkey'];
+
// 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'] );
+ $startsortkey,
+ $endsortkey );
$this->addWhereRange( 'cl_from', $dir, null, null );
}
$this->addOption( 'USE INDEX', 'cl_sortkey' );
@@ -173,6 +198,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$res = $this->select( __METHOD__ );
$rows = iterator_to_array( $res );
}
+
+ $result = $this->getResult();
$count = 0;
foreach ( $rows as $row ) {
if ( ++ $count > $limit ) {
@@ -218,7 +245,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
if ( $fld_timestamp ) {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cl_timestamp );
}
- $fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ),
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $vals );
if ( !$fit ) {
if ( $params['sort'] == 'timestamp' ) {
@@ -237,7 +264,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
if ( is_null( $resultPageSet ) ) {
- $this->getResult()->setIndexedTagName_internal(
+ $result->setIndexedTagName_internal(
array( 'query', $this->getModuleName() ), 'cm' );
}
}
@@ -246,7 +273,9 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
return array(
'title' => array(
ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
+ ),
+ 'pageid' => array(
+ ApiBase::PARAM_TYPE => 'integer'
),
'prop' => array(
ApiBase::PARAM_DFLT => 'ids|title',
@@ -303,6 +332,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
),
'startsortkey' => null,
'endsortkey' => null,
+ 'startsortkeyprefix' => null,
+ 'endsortkeyprefix' => null,
);
}
@@ -310,7 +341,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
global $wgMiserMode;
$p = $this->getModulePrefix();
$desc = array(
- 'title' => 'Which category to enumerate (required). Must include Category: prefix',
+ 'title' => "Which category to enumerate (required). Must include Category: prefix. Cannot be used together with {$p}pageid",
+ 'pageid' => "Page ID of the category to enumerate. Cannot be used together with {$p}title",
'prop' => array(
'What pieces of information to include',
' ids - Adds the page ID',
@@ -326,17 +358,20 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
'dir' => 'In which direction to sort',
'start' => "Timestamp to start listing from. Can only be used with {$p}sort=timestamp",
'end' => "Timestamp to end listing at. Can only be used with {$p}sort=timestamp",
- 'startsortkey' => "Sortkey to start listing from. Can only be used with {$p}sort=sortkey",
- 'endsortkey' => "Sortkey to end listing at. Can only be used with {$p}sort=sortkey",
+ 'startsortkey' => "Sortkey to start listing from. Must be given in binary format. Can only be used with {$p}sort=sortkey",
+ 'endsortkey' => "Sortkey to end listing at. Must be given in binary format. Can only be used with {$p}sort=sortkey",
+ 'startsortkeyprefix' => "Sortkey prefix to start listing from. Can only be used with {$p}sort=sortkey. Overrides {$p}startsortkey",
+ 'endsortkeyprefix' => "Sortkey prefix to end listing BEFORE (not at, if this value occurs it will not be included!). Can only be used with {$p}sort=sortkey. Overrides {$p}endsortkey",
'continue' => 'For large categories, give the value retured from previous query',
'limit' => 'The maximum number of pages to return.',
);
+
if ( $wgMiserMode ) {
$desc['namespace'] = array(
$desc['namespace'],
- 'NOTE: Due to $wgMiserMode, using this may result in fewer than "limit" results',
+ "NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results",
'returned before continuing; in extreme cases, zero results may be returned.',
- 'Note that you can use cmtype=subcat or cmtype=file instead of cmnamespace=14 or 6.',
+ "Note that you can use {$p}type=subcat or {$p}type=file instead of {$p}namespace=14 or 6.",
);
}
return $desc;
@@ -347,11 +382,14 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
}
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'notitle', 'info' => 'The cmtitle parameter is required' ),
- array( 'code' => 'invalidcategory', 'info' => 'The category name you entered is not valid' ),
- array( 'code' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
- ) );
+ return array_merge( parent::getPossibleErrors(),
+ $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ),
+ array(
+ array( 'code' => 'invalidcategory', 'info' => 'The category name you entered is not valid' ),
+ array( 'code' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
+ array( 'nosuchpageid', 'pageid' ),
+ )
+ );
}
protected function getExamples() {
@@ -363,7 +401,11 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Categorymembers';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 86474 2011-04-20 13:22:05Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php
index 523862c0..f58b9ee5 100644
--- a/includes/api/ApiQueryDeletedrevs.php
+++ b/includes/api/ApiQueryDeletedrevs.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Jul 2, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -50,6 +50,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$db = $this->getDB();
$params = $this->extractRequestParams( false );
$prop = array_flip( $params['prop'] );
+ $fld_parentid = isset( $prop['parentid'] );
$fld_revid = isset( $prop['revid'] );
$fld_user = isset( $prop['user'] );
$fld_userid = isset( $prop['userid'] );
@@ -65,9 +66,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$titles = $pageSet->getTitles();
// 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
+ // 'revs': List deleted revs for certain titles (1)
+ // 'user': List deleted revs by a certain user (2)
+ // 'all': List all deleted revs in NS (3)
$mode = 'all';
if ( count( $titles ) > 0 ) {
$mode = 'revs';
@@ -75,6 +76,21 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$mode = 'user';
}
+ if ( $mode == 'revs' || $mode == 'user' ) {
+ // Ignore namespace and unique due to inability to know whether they were purposely set
+ foreach( array( 'from', 'to', 'prefix', /*'namespace',*/ 'continue', /*'unique'*/ ) as $p ) {
+ if ( !is_null( $params[$p] ) ) {
+ $this->dieUsage( "The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams');
+ }
+ }
+ } else {
+ foreach( array( 'start', 'end' ) as $p ) {
+ if ( !is_null( $params[$p] ) ) {
+ $this->dieUsage( "The {$p} parameter cannot be used in mode 3", 'badparams');
+ }
+ }
+ }
+
if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) {
$this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' );
}
@@ -82,24 +98,15 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->addTables( 'archive' );
$this->addWhere( 'ar_deleted = 0' );
$this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp' ) );
- if ( $fld_revid ) {
- $this->addFields( 'ar_rev_id' );
- }
- if ( $fld_user ) {
- $this->addFields( 'ar_user_text' );
- }
- if ( $fld_userid ) {
- $this->addFields( 'ar_user' );
- }
- if ( $fld_comment || $fld_parsedcomment ) {
- $this->addFields( 'ar_comment' );
- }
- if ( $fld_minor ) {
- $this->addFields( 'ar_minor_edit' );
- }
- if ( $fld_len ) {
- $this->addFields( 'ar_len' );
- }
+
+ $this->addFieldsIf( 'ar_parent_id', $fld_parentid );
+ $this->addFieldsIf( 'ar_rev_id', $fld_revid );
+ $this->addFieldsIf( 'ar_user_text', $fld_user );
+ $this->addFieldsIf( 'ar_user', $fld_userid );
+ $this->addFieldsIf( 'ar_comment', $fld_comment || $fld_parsedcomment );
+ $this->addFieldsIf( 'ar_minor_edit', $fld_minor );
+ $this->addFieldsIf( 'ar_len', $fld_len );
+
if ( $fld_content ) {
$this->addTables( 'text' );
$this->addFields( array( 'ar_text', 'ar_text_id', 'old_text', 'old_flags' ) );
@@ -125,9 +132,11 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $fld_token ) {
// Undelete tokens are identical for all pages, so we cache one here
- $token = $wgUser->editToken();
+ $token = $wgUser->editToken( '', $this->getMain()->getRequest() );
}
+ $dir = $params['dir'];
+
// We need a custom WHERE clause that matches all titles.
if ( $mode == 'revs' ) {
$lb = new LinkBatch( $titles );
@@ -135,9 +144,13 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->addWhere( $where );
} elseif ( $mode == 'all' ) {
$this->addWhereFld( 'ar_namespace', $params['namespace'] );
- if ( !is_null( $params['from'] ) ) {
- $from = $this->getDB()->strencode( $this->titleToKey( $params['from'] ) );
- $this->addWhere( "ar_title >= '$from'" );
+
+ $from = is_null( $params['from'] ) ? null : $this->titleToKey( $params['from'] );
+ $to = is_null( $params['to'] ) ? null : $this->titleToKey( $params['to'] );
+ $this->addWhereRange( 'ar_title', $dir, $from, $to );
+
+ if ( isset( $params['prefix'] ) ) {
+ $this->addWhere( 'ar_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
}
}
@@ -148,8 +161,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->getDB()->addQuotes( $params['excludeuser'] ) );
}
- if ( !is_null( $params['continue'] ) && ( $mode == 'all' || $mode == 'revs' ) )
- {
+ 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' );
@@ -157,7 +169,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$ns = intval( $cont[0] );
$title = $this->getDB()->strencode( $this->titleToKey( $cont[1] ) );
$ts = $this->getDB()->strencode( $cont[2] );
- $op = ( $params['dir'] == 'newer' ? '>' : '<' );
+ $op = ( $dir == 'newer' ? '>' : '<' );
$this->addWhere( "ar_namespace $op $ns OR " .
"(ar_namespace = $ns AND " .
"(ar_title $op '$title' OR " .
@@ -170,17 +182,16 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $mode == 'all' ) {
if ( $params['unique'] ) {
$this->addOption( 'GROUP BY', 'ar_title' );
- $this->addOption( 'ORDER BY', 'ar_title' );
} else {
$this->addOption( 'ORDER BY', 'ar_title, ar_timestamp' );
}
} else {
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 );
+ $this->addWhereRange( 'ar_namespace', $dir, null, null );
+ $this->addWhereRange( 'ar_title', $dir, null, null );
}
- $this->addWhereRange( 'ar_timestamp', $params['dir'], $params['start'], $params['end'] );
+ $this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] );
}
$res = $this->select( __METHOD__ );
$pageMap = array(); // Maps ns&title to (fake) pageid
@@ -203,6 +214,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $fld_revid ) {
$rev['revid'] = intval( $row->ar_rev_id );
}
+ if ( $fld_parentid ) {
+ $rev['parentid'] = intval( $row->ar_parent_id );
+ }
if ( $fld_user ) {
$rev['user'] = $row->ar_user_text;
}
@@ -273,6 +287,8 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
ApiBase::PARAM_DFLT => 'older'
),
'from' => null,
+ 'to' => null,
+ 'prefix' => null,
'continue' => null,
'unique' => false,
'user' => array(
@@ -296,6 +312,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
ApiBase::PARAM_DFLT => 'user|comment',
ApiBase::PARAM_TYPE => array(
'revid',
+ 'parentid',
'user',
'userid',
'comment',
@@ -314,13 +331,17 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
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)',
+ 'dir' => $this->getDirectionDescription( $this->getModulePrefix(), ' (1, 3)' ),
+ 'from' => 'Start listing at this title (3)',
+ 'to' => 'Stop listing at this title (3)',
+ 'prefix' => 'Search for all page titles that begin with this value (3)',
'limit' => 'The maximum amount of revisions to list',
'prop' => array(
'Which properties to get',
- ' revid - Adds the revision id of the deleted revision',
+ ' revid - Adds the revision ID of the deleted revision',
+ ' parentid - Adds the revision ID of the previous revision to the page',
' user - Adds the user who made the revision',
- ' userid - Adds the user id whom made the revision',
+ ' 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',
@@ -331,19 +352,19 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'namespace' => 'Only list pages in this namespace (3)',
'user' => 'Only list revisions by this user',
'excludeuser' => 'Don\'t list revisions by this user',
- 'from' => 'Start listing at this title (3)',
'continue' => 'When more results are available, use this to continue (3)',
'unique' => 'List only one revision for each page (3)',
);
}
public function getDescription() {
+ $p = $this->getModulePrefix();
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)',
+ 'Operates in three modes:',
+ ' 1) List deleted revisions for the given title(s), sorted by timestamp',
+ ' 2) List deleted contributions for the given user, sorted by timestamp (no titles specified)',
+ " 3) List all deleted revisions in the given namespace, sorted by title and timestamp (no titles specified, {$p}user not set)",
'Certain parameters only apply to some modes and are ignored in others.',
'For instance, a parameter marked (1) only applies to mode 1 and is ignored in modes 2 and 3',
);
@@ -355,6 +376,12 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ),
array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revision content' ),
array( 'code' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
+ array( 'code' => 'badparams', 'info' => "The 'from' parameter cannot be used in modes 1 or 2" ),
+ array( 'code' => 'badparams', 'info' => "The 'to' parameter cannot be used in modes 1 or 2" ),
+ array( 'code' => 'badparams', 'info' => "The 'prefix' parameter cannot be used in modes 1 or 2" ),
+ array( 'code' => 'badparams', 'info' => "The 'continue' parameter cannot be used in modes 1 or 2" ),
+ array( 'code' => 'badparams', 'info' => "The 'start' parameter cannot be used in mode 3" ),
+ array( 'code' => 'badparams', 'info' => "The 'end' parameter cannot be used in mode 3" ),
) );
}
@@ -371,7 +398,11 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Deletedrevs';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 77192 2010-11-23 22:05:27Z btongminh $';
+ return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryDisabled.php b/includes/api/ApiQueryDisabled.php
index b5712069..c9edd2e4 100644
--- a/includes/api/ApiQueryDisabled.php
+++ b/includes/api/ApiQueryDisabled.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 25, 2008
*
- * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -68,6 +68,6 @@ class ApiQueryDisabled extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryDisabled.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryDisabled.php 79969 2011-01-10 22:36:26Z reedy $';
}
}
diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php
index ffe98038..4c7c1fc2 100644
--- a/includes/api/ApiQueryDuplicateFiles.php
+++ b/includes/api/ApiQueryDuplicateFiles.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 27, 2008
*
- * Copyright © 2008 Roan Kattow <Firstname>,<Lastname>@home.nl
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -52,6 +52,10 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return
+ */
private function run( $resultPageSet = null ) {
$params = $this->extractRequestParams();
$namespaces = $this->getPageSet()->getAllTitlesByNamespace();
@@ -164,7 +168,11 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#duplicatefiles_.2F_df';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php
index ecd9e699..89928372 100644
--- a/includes/api/ApiQueryExtLinksUsage.php
+++ b/includes/api/ApiQueryExtLinksUsage.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on July 7, 2007
*
@@ -50,45 +50,32 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
$params = $this->extractRequestParams();
- $protocol = $params['protocol'];
$query = $params['query'];
+ $protocol = self::getProtocolPrefix( $params['protocol'] );
- // Find the right prefix
- global $wgUrlProtocols;
- if ( $protocol && !in_array( $protocol, $wgUrlProtocols ) ) {
- foreach ( $wgUrlProtocols as $p ) {
- if ( substr( $p, 0, strlen( $protocol ) ) === $protocol ) {
- $protocol = $p;
- break;
- }
- }
- } else {
- $protocol = null;
- }
-
- $db = $this->getDB();
$this->addTables( array( 'page', 'externallinks' ) ); // must be in this order for 'USE INDEX'
$this->addOption( 'USE INDEX', 'el_index' );
$this->addWhere( 'page_id=el_from' );
- $this->addWhereFld( 'page_namespace', $params['namespace'] );
- if ( !is_null( $query ) || $query != '' ) {
- if ( is_null( $protocol ) ) {
- $protocol = 'http://';
- }
+ global $wgMiserMode;
+ $miser_ns = array();
+ if ( $wgMiserMode ) {
+ $miser_ns = $params['namespace'];
+ } else {
+ $this->addWhereFld( 'page_namespace', $params['namespace'] );
+ }
- $likeQuery = LinkFilter::makeLikeArray( $query, $protocol );
- if ( !$likeQuery ) {
- $this->dieUsage( 'Invalid query', 'bad_query' );
- }
+ $whereQuery = $this->prepareUrlQuerySearchString( $query, $protocol );
- $likeQuery = LinkFilter::keepOneWildcard( $likeQuery );
- $this->addWhere( 'el_index ' . $db->buildLike( $likeQuery ) );
- } elseif ( !is_null( $protocol ) ) {
- $this->addWhere( 'el_index ' . $db->buildLike( "$protocol", $db->anyString() ) );
+ if ( $whereQuery !== null ) {
+ $this->addWhere( $whereQuery );
}
$prop = array_flip( $params['prop'] );
@@ -125,6 +112,10 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
break;
}
+ if ( count( $miser_ns ) && !in_array( $row->page_namespace, $miser_ns ) ) {
+ continue;
+ }
+
if ( is_null( $resultPageSet ) ) {
$vals = array();
if ( $fld_ids ) {
@@ -135,6 +126,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
ApiQueryBase::addTitleInfo( $vals, $title );
}
if ( $fld_url ) {
+ // We *could* run this through wfExpandUrl() but I think it's better to output the link verbatim, even if it's protocol-relative --Roan
$vals['url'] = $row->el_to;
}
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
@@ -154,12 +146,6 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
}
public function getAllowedParams() {
- global $wgUrlProtocols;
- $protocols = array( '' );
- foreach ( $wgUrlProtocols as $p ) {
- $protocols[] = substr( $p, 0, strpos( $p, ':' ) );
- }
-
return array(
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
@@ -174,7 +160,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
ApiBase::PARAM_TYPE => 'integer'
),
'protocol' => array(
- ApiBase::PARAM_TYPE => $protocols,
+ ApiBase::PARAM_TYPE => self::prepareProtocols(),
ApiBase::PARAM_DFLT => '',
),
'query' => null,
@@ -192,13 +178,42 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
);
}
+ public static function prepareProtocols() {
+ global $wgUrlProtocols;
+ $protocols = array( '' );
+ foreach ( $wgUrlProtocols as $p ) {
+ if ( $p !== '//' ) {
+ $protocols[] = substr( $p, 0, strpos( $p, ':' ) );
+ }
+ }
+ return $protocols;
+ }
+
+ public static function getProtocolPrefix( $protocol ) {
+ // Find the right prefix
+ global $wgUrlProtocols;
+ if ( $protocol && !in_array( $protocol, $wgUrlProtocols ) ) {
+ foreach ( $wgUrlProtocols as $p ) {
+ if ( substr( $p, 0, strlen( $protocol ) ) === $protocol ) {
+ $protocol = $p;
+ break;
+ }
+ }
+
+ return $protocol;
+ } else {
+ return null;
+ }
+ }
+
public function getParamDescription() {
+ global $wgMiserMode;
$p = $this->getModulePrefix();
- return array(
+ $desc = array(
'prop' => array(
'What pieces of information to include',
- ' ids - Adds the id of page',
- ' title - Adds the title and namespace id of the page',
+ ' 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"',
@@ -210,6 +225,16 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
'namespace' => 'The page namespace(s) to enumerate.',
'limit' => 'How many pages to return.'
);
+
+ if ( $wgMiserMode ) {
+ $desc['namespace'] = array(
+ $desc['namespace'],
+ "NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results",
+ 'returned before continuing; in extreme cases, zero results may be returned',
+ );
+ }
+
+ return $desc;
}
public function getDescription() {
@@ -228,7 +253,11 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Exturlusage';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php
index fbfcbfb9..ca1efbb1 100644
--- a/includes/api/ApiQueryExternalLinks.php
+++ b/includes/api/ApiQueryExternalLinks.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on May 13, 2007
*
@@ -46,6 +46,10 @@ class ApiQueryExternalLinks extends ApiQueryBase {
}
$params = $this->extractRequestParams();
+
+ $query = $params['query'];
+ $protocol = ApiQueryExtLinksUsage::getProtocolPrefix( $params['protocol'] );
+
$this->addFields( array(
'el_from',
'el_to'
@@ -54,13 +58,25 @@ class ApiQueryExternalLinks extends ApiQueryBase {
$this->addTables( 'externallinks' );
$this->addWhereFld( 'el_from', array_keys( $this->getPageSet()->getGoodTitles() ) );
+ $whereQuery = $this->prepareUrlQuerySearchString( $query, $protocol );
+
+ if ( $whereQuery !== null ) {
+ $this->addWhere( $whereQuery );
+ }
+
// Don't order by el_from if it's constant in the WHERE clause
if ( count( $this->getPageSet()->getGoodTitles() ) != 1 ) {
$this->addOption( 'ORDER BY', 'el_from' );
}
+ // If we're querying all protocols, use DISTINCT to avoid repeating protocol-relative links twice
+ if ( $protocol === null ) {
+ $this->addOption( 'DISTINCT' );
+ }
+
$this->addOption( 'LIMIT', $params['limit'] + 1 );
- if ( !is_null( $params['offset'] ) ) {
+ $offset = isset( $params['offset'] ) ? $params['offset'] : 0;
+ if ( $offset ) {
$this->addOption( 'OFFSET', $params['offset'] );
}
@@ -71,14 +87,15 @@ class ApiQueryExternalLinks extends ApiQueryBase {
if ( ++$count > $params['limit'] ) {
// We've reached the one extra which shows that
// there are additional pages to be had. Stop here...
- $this->setContinueEnumParameter( 'offset', @$params['offset'] + $params['limit'] );
+ $this->setContinueEnumParameter( 'offset', $offset + $params['limit'] );
break;
}
$entry = array();
+ // We *could* run this through wfExpandUrl() but I think it's better to output the link verbatim, even if it's protocol-relative --Roan
ApiResult::setContent( $entry, $row->el_to );
$fit = $this->addPageSubItem( $row->el_from, $entry );
if ( !$fit ) {
- $this->setContinueEnumParameter( 'offset', @$params['offset'] + $count - 1 );
+ $this->setContinueEnumParameter( 'offset', $offset + $count - 1 );
break;
}
}
@@ -97,14 +114,27 @@ class ApiQueryExternalLinks extends ApiQueryBase {
ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1,
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
- 'offset' => null,
+ 'offset' => array(
+ ApiBase::PARAM_TYPE => 'integer'
+ ),
+ 'protocol' => array(
+ ApiBase::PARAM_TYPE => ApiQueryExtLinksUsage::prepareProtocols(),
+ ApiBase::PARAM_DFLT => '',
+ ),
+ 'query' => null,
);
}
public function getParamDescription() {
+ $p = $this->getModulePrefix();
return array(
'limit' => 'How many links to return',
'offset' => 'When more results are available, use this to continue',
+ 'protocol' => array(
+ "Protocol of the url. If empty and {$p}query set, the protocol is http.",
+ "Leave both this and {$p}query empty to list all external links"
+ ),
+ 'query' => 'Search string without protocol. Useful for checking whether a certain page contains a certain external url',
);
}
@@ -112,6 +142,12 @@ class ApiQueryExternalLinks extends ApiQueryBase {
return 'Returns all external urls (not interwikies) from the given page(s)';
}
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'code' => 'bad_query', 'info' => 'Invalid query' ),
+ ) );
+ }
+
protected function getExamples() {
return array(
'Get a list of external links on the [[Main Page]]:',
@@ -119,7 +155,11 @@ class ApiQueryExternalLinks extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#extlinks_.2F_el';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php
index 05ccb346..541b25fc 100644
--- a/includes/api/ApiQueryFilearchive.php
+++ b/includes/api/ApiQueryFilearchive.php
@@ -59,7 +59,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
$fld_user = isset( $prop['user'] );
$fld_size = isset( $prop['size'] );
$fld_dimensions = isset( $prop['dimensions'] );
- $fld_description = isset( $prop['description'] );
+ $fld_description = isset( $prop['description'] ) || isset( $prop['parseddescription'] );
$fld_mime = isset( $prop['mime'] );
$fld_metadata = isset( $prop['metadata'] );
$fld_bitdepth = isset( $prop['bitdepth'] );
@@ -69,45 +69,57 @@ class ApiQueryFilearchive extends ApiQueryBase {
$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( array( 'fa_user', 'fa_user_text' ), $fld_user );
+ $this->addFieldsIf( array( 'fa_height', 'fa_width', 'fa_size' ), $fld_dimensions || $fld_size );
$this->addFieldsIf( 'fa_description', $fld_description );
-
- if ( $fld_mime ) {
- $this->addFields( array( 'fa_major_mime', 'fa_minor_mime' ) );
- }
-
+ $this->addFieldsIf( array( 'fa_major_mime', 'fa_minor_mime' ), $fld_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 );
+ $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) );
+ $this->addWhereRange( 'fa_name', $dir, $from, $to );
if ( isset( $params['prefix'] ) ) {
$this->addWhere( 'fa_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) );
}
-
+
+ $sha1Set = isset( $params['sha1'] );
+ $sha1base36Set = isset( $params['sha1base36'] );
+ if ( $sha1Set || $sha1base36Set ) {
+ global $wgMiserMode;
+ if ( $wgMiserMode ) {
+ $this->dieUsage( 'Search by hash disabled in Miser Mode', 'hashsearchdisabled' );
+ }
+
+ $sha1 = false;
+ if ( $sha1Set ) {
+ if ( !$this->validateSha1Hash( $params['sha1'] ) ) {
+ $this->dieUsage( 'The SHA1 hash provided is not valid', 'invalidsha1hash' );
+ }
+ $sha1 = wfBaseConvert( $params['sha1'], 16, 36, 31 );
+ } elseif ( $sha1base36Set ) {
+ if ( !$this->validateSha1Base36Hash( $params['sha1base36'] ) ) {
+ $this->dieUsage( 'The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash' );
+ }
+ $sha1 = $params['sha1base36'];
+ }
+ if ( $sha1 ) {
+ $this->addWhere( 'fa_storage_key ' . $db->buildLike( "{$sha1}.", $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' .
@@ -127,9 +139,11 @@ class ApiQueryFilearchive extends ApiQueryBase {
$file = array();
$file['name'] = $row->fa_name;
+ $title = Title::makeTitle( NS_FILE, $row->fa_name );
+ self::addTitleInfo( $file, $title );
if ( $fld_sha1 ) {
- $file['sha1'] = wfBaseConvert( $row->fa_storage_key, 36, 16, 40 );
+ $file['sha1'] = wfBaseConvert( LocalRepo::getHashFromKey( $row->fa_storage_key ), 36, 16, 40 );
}
if ( $fld_timestamp ) {
$file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp );
@@ -138,18 +152,28 @@ class ApiQueryFilearchive extends ApiQueryBase {
$file['userid'] = $row->fa_user;
$file['user'] = $row->fa_user_text;
}
- if ( $fld_size ) {
+ if ( $fld_size || $fld_dimensions ) {
$file['size'] = $row->fa_size;
- }
- if ( $fld_dimensions ) {
+
+ $pageCount = ArchivedFile::newFromRow( $row )->pageCount();
+ if ( $pageCount !== false ) {
+ $vals['pagecount'] = $pageCount;
+ }
+
$file['height'] = $row->fa_height;
$file['width'] = $row->fa_width;
}
if ( $fld_description ) {
$file['description'] = $row->fa_description;
+ if ( isset( $prop['parseddescription'] ) ) {
+ $file['parseddescription'] = $wgUser->getSkin()->formatComment(
+ $row->fa_description, $title );
+ }
}
if ( $fld_metadata ) {
- $file['metadata'] = $row->fa_metadata ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result ) : null;
+ $file['metadata'] = $row->fa_metadata
+ ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result )
+ : null;
}
if ( $fld_bitdepth ) {
$file['bitdepth'] = $row->fa_bits;
@@ -157,7 +181,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
if ( $fld_mime ) {
$file['mime'] = "$row->fa_major_mime/$row->fa_minor_mime";
}
-
+
if ( $row->fa_deleted & File::DELETED_FILE ) {
$file['filehidden'] = '';
}
@@ -172,7 +196,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
$file['suppressed'] = '';
}
-
+
$fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $file );
if ( !$fit ) {
$this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->fa_name ) );
@@ -186,6 +210,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
public function getAllowedParams() {
return array (
'from' => null,
+ 'to' => null,
'prefix' => null,
'limit' => array(
ApiBase::PARAM_DFLT => 10,
@@ -201,6 +226,8 @@ class ApiQueryFilearchive extends ApiQueryBase {
'descending'
)
),
+ 'sha1' => null,
+ 'sha1base36' => null,
'prop' => array(
ApiBase::PARAM_DFLT => 'timestamp',
ApiBase::PARAM_ISMULTI => true,
@@ -211,6 +238,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
'size',
'dimensions',
'description',
+ 'parseddescription',
'mime',
'metadata',
'bitdepth'
@@ -222,20 +250,24 @@ class ApiQueryFilearchive extends ApiQueryBase {
public function getParamDescription() {
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',
- 'limit' => 'How many total images to return',
+ 'limit' => 'How many images to return in total',
+ 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36. Disabled in Miser Mode",
+ 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki). Disabled in Miser Mode',
'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',
+ ' sha1 - Adds SHA-1 hash for the image',
+ ' timestamp - Adds timestamp for the uploaded version',
+ ' user - Adds user who uploaded the image version',
+ ' size - Adds the size of the image in bytes and the height, width and page count (if applicable)',
+ ' dimensions - Alias for size',
+ ' description - Adds description the image version',
+ ' parseddescription - Parse the description on the version',
+ ' mime - Adds MIME of the image',
+ ' metadata - Lists EXIF metadata for the version of the image',
+ ' bitdepth - Adds the bit depth of the version',
),
);
}
@@ -247,6 +279,9 @@ class ApiQueryFilearchive extends ApiQueryBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted file information' ),
+ array( 'code' => 'hashsearchdisabled', 'info' => 'Search by hash disabled in Miser Mode' ),
+ array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ),
+ array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ),
) );
}
@@ -259,6 +294,6 @@ class ApiQueryFilearchive extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryFilearchive.php 85354 2011-04-04 18:25:31Z demon $';
+ return __CLASS__ . ': $Id: ApiQueryFilearchive.php 91246 2011-07-01 02:25:19Z reedy $';
}
}
diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php
index 6958a253..1a8e9720 100644
--- a/includes/api/ApiQueryIWBacklinks.php
+++ b/includes/api/ApiQueryIWBacklinks.php
@@ -48,6 +48,10 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
public function run( $resultPageSet = null ) {
$params = $this->extractRequestParams();
@@ -115,11 +119,10 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
if ( !is_null( $resultPageSet ) ) {
$pages[] = Title::newFromRow( $row );
} else {
- $entry = array();
+ $entry = array( 'pageid' => $row->page_id );
- $entry['pageid'] = intval( $row->page_id );
- $entry['ns'] = intval( $row->page_namespace );
- $entry['title'] = $row->page_title;
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ ApiQueryBase::addTitleInfo( $entry, $title );
if ( $row->page_is_redirect ) {
$entry['redirect'] = '';
@@ -212,6 +215,6 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryIWBacklinks.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryIWBacklinks.php 84257 2011-03-18 19:15:33Z reedy $';
}
}
diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php
index e980d6a5..3215a96e 100644
--- a/includes/api/ApiQueryIWLinks.php
+++ b/includes/api/ApiQueryIWLinks.php
@@ -47,6 +47,11 @@ class ApiQueryIWLinks extends ApiQueryBase {
}
$params = $this->extractRequestParams();
+
+ if ( isset( $params['title'] ) && !isset( $params['prefix'] ) ) {
+ $this->dieUsageMsg( array( 'missingparam', 'prefix' ) );
+ }
+
$this->addFields( array(
'iwl_from',
'iwl_prefix',
@@ -74,12 +79,23 @@ class ApiQueryIWLinks extends ApiQueryBase {
);
}
- // 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' );
+ 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_from, iwl_prefix' );
+ // 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__ );
@@ -96,7 +112,7 @@ class ApiQueryIWLinks extends ApiQueryBase {
if ( !is_null( $params['url'] ) ) {
$title = Title::newFromText( "{$row->iwl_prefix}:{$row->iwl_title}" );
if ( $title ) {
- $entry['url'] = $title->getFullURL();
+ $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
}
}
@@ -124,6 +140,8 @@ class ApiQueryIWLinks extends ApiQueryBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'continue' => null,
+ 'prefix' => null,
+ 'title' => null,
);
}
@@ -132,6 +150,8 @@ class ApiQueryIWLinks extends ApiQueryBase {
'url' => 'Whether to get the full URL',
'limit' => 'How many interwiki links to return',
'continue' => 'When more results are available, use this to continue',
+ 'prefix' => 'Prefix for the interwiki',
+ 'title' => "Interwiki link to search for. Must be used with {$this->getModulePrefix()}prefix",
);
}
@@ -141,6 +161,7 @@ class ApiQueryIWLinks extends ApiQueryBase {
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' ),
) );
}
@@ -153,6 +174,6 @@ class ApiQueryIWLinks extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryIWLinks.php 77080 2010-11-21 17:27:13Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryIWLinks.php 96475 2011-09-07 19:37:56Z catrope $';
}
}
diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php
index 21696be2..5bacd636 100644
--- a/includes/api/ApiQueryImageInfo.php
+++ b/includes/api/ApiQueryImageInfo.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on July 6, 2007
*
@@ -108,17 +108,21 @@ class ApiQueryImageInfo extends ApiQueryBase {
break;
}
+ // Check if we can make the requested thumbnail, and get transform parameters.
+ $finalThumbParams = $this->mergeThumbParams( $img, $scale, $params['urlparam'] );
+
// 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'] )
- )
- {
+ ) {
$gotOne = true;
+
$fit = $this->addPageSubItem( $pageId,
- self::getInfo( $img, $prop, $result, $scale ) );
+ self::getInfo( $img, $prop, $result,
+ $finalThumbParams, $params['metadataversion'] ) );
if ( !$fit ) {
if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
// See the 'the user is screwed' comment above
@@ -147,7 +151,8 @@ class ApiQueryImageInfo extends ApiQueryBase {
break;
}
$fit = $this->addPageSubItem( $pageId,
- self::getInfo( $oldie, $prop, $result ) );
+ self::getInfo( $oldie, $prop, $result,
+ $finalThumbParams, $params['metadataversion'] ) );
if ( !$fit ) {
if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
$this->setContinueEnumParameter( 'start',
@@ -179,14 +184,16 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
/**
- * From parameters, construct a 'scale' array
- * @param $params Array:
+ * From parameters, construct a 'scale' array
+ * @param $params Array: Parameters passed to api.
* @return Array or Null: key-val array of 'width' and 'height', or null
- */
+ */
public function getScale( $params ) {
$p = $this->getModulePrefix();
+
+ // Height and width.
if ( $params['urlheight'] != -1 && $params['urlwidth'] == -1 ) {
- $this->dieUsage( "${p}urlheight cannot be used without {$p}urlwidth", "{$p}urlwidth" );
+ $this->dieUsage( "{$p}urlheight cannot be used without {$p}urlwidth", "{$p}urlwidth" );
}
if ( $params['urlwidth'] != -1 ) {
@@ -195,10 +202,63 @@ class ApiQueryImageInfo extends ApiQueryBase {
$scale['height'] = $params['urlheight'];
} else {
$scale = null;
+ if ( $params['urlparam'] ) {
+ $this->dieUsage( "{$p}urlparam requires {$p}urlwidth", "urlparam_no_width" );
+ }
+ return $scale;
}
+
return $scale;
}
+ /** Validate and merge scale parameters with handler thumb parameters, give error if invalid.
+ *
+ * We do this later than getScale, since we need the image
+ * to know which handler, since handlers can make their own parameters.
+ * @param File $image Image that params are for.
+ * @param Array $thumbParams thumbnail parameters from getScale
+ * @param String $otherParams of otherParams (iiurlparam).
+ * @return Array of parameters for transform.
+ */
+ protected function mergeThumbParams ( $image, $thumbParams, $otherParams ) {
+ if ( !$otherParams ) {
+ return $thumbParams;
+ }
+ $p = $this->getModulePrefix();
+
+ $h = $image->getHandler();
+ if ( !$h ) {
+ $this->setWarning( 'Could not create thumbnail because ' .
+ $image->getName() . ' does not have an associated image handler' );
+ return $thumbParams;
+ }
+
+ $paramList = $h->parseParamString( $otherParams );
+ if ( !$paramList ) {
+ // Just set a warning (instead of dieUsage), as in many cases
+ // we could still render the image using width and height parameters,
+ // and this type of thing could happen between different versions of
+ // handlers.
+ $this->setWarning( "Could not parse {$p}urlparam for " . $image->getName()
+ . '. Using only width and height' );
+ return $thumbParams;
+ }
+
+ if ( isset( $paramList['width'] ) ) {
+ if ( intval( $paramList['width'] ) != intval( $thumbParams['width'] ) ) {
+ $this->dieUsage( "{$p}urlparam had width of {$paramList['width']} but "
+ . "{$p}urlwidth was {$thumbParams['width']}", "urlparam_urlwidth_mismatch" );
+ }
+ }
+
+ foreach ( $paramList as $name => $value ) {
+ if ( !$h->validateParam( $name, $value ) ) {
+ $this->dieUsage( "Invalid value for {$p}urlparam ($name=$value)", "urlparam" );
+ }
+ }
+
+ return $thumbParams + $paramList;
+ }
/**
* Get result information for an image revision
@@ -206,10 +266,11 @@ class ApiQueryImageInfo extends ApiQueryBase {
* @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
+ * @param $thumbParams Array containing 'width' and 'height' items, or null
+ * @param $version string Version of image metadata (for things like jpeg which have different versions).
* @return Array: result array
*/
- static function getInfo( $file, $prop, $result, $scale = null ) {
+ static function getInfo( $file, $prop, $result, $thumbParams = null, $version = 'latest' ) {
$vals = array();
// Timestamp is shown even if the file is revdelete'd in interface
// so do same here.
@@ -242,7 +303,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
$vals['size'] = intval( $file->getSize() );
$vals['width'] = intval( $file->getWidth() );
$vals['height'] = intval( $file->getHeight() );
-
+
$pageCount = $file->pageCount();
if ( $pageCount !== false ) {
$vals['pagecount'] = $pageCount;
@@ -271,10 +332,11 @@ class ApiQueryImageInfo extends ApiQueryBase {
$sha1 = isset( $prop['sha1'] );
$meta = isset( $prop['metadata'] );
$mime = isset( $prop['mime'] );
+ $mediatype = isset( $prop['mediatype'] );
$archive = isset( $prop['archivename'] );
$bitdepth = isset( $prop['bitdepth'] );
- if ( ( $url || $sha1 || $meta || $mime || $archive || $bitdepth )
+ if ( ( $url || $sha1 || $meta || $mime || $mediatype || $archive || $bitdepth )
&& $file->isDeleted( File::DELETED_FILE ) ) {
$vals['filehidden'] = '';
@@ -283,10 +345,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
if ( $url ) {
- if ( !is_null( $scale ) && !$file->isOld() ) {
- $mto = $file->transform( array( 'width' => $scale['width'], 'height' => $scale['height'] ) );
+ if ( !is_null( $thumbParams ) ) {
+ $mto = $file->transform( $thumbParams );
if ( $mto && !$mto->isError() ) {
- $vals['thumburl'] = wfExpandUrl( $mto->getUrl() );
+ $vals['thumburl'] = wfExpandUrl( $mto->getUrl(), PROTO_CURRENT );
// 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
@@ -299,32 +361,39 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
if ( isset( $prop['thumbmime'] ) && $file->getHandler() ) {
- list( $ext, $mime ) = $file->getHandler()->getThumbType(
- substr( $mto->getPath(), strrpos( $mto->getPath(), '.' ) + 1 ),
+ list( $ext, $mime ) = $file->getHandler()->getThumbType(
+ substr( $mto->getPath(), strrpos( $mto->getPath(), '.' ) + 1 ),
$file->getMimeType(), $thumbParams );
$vals['thumbmime'] = $mime;
}
- } else if ( $mto && $mto->isError() ) {
+ } elseif ( $mto && $mto->isError() ) {
$vals['thumberror'] = $mto->toText();
}
}
- $vals['url'] = $file->getFullURL();
- $vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl() );
+ $vals['url'] = wfExpandUrl( $file->getFullURL(), PROTO_CURRENT );
+ $vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl(), PROTO_CURRENT );
}
-
+
if ( $sha1 ) {
$vals['sha1'] = wfBaseConvert( $file->getSha1(), 36, 16, 40 );
}
if ( $meta ) {
- $metadata = $file->getMetadata();
- $vals['metadata'] = $metadata ? self::processMetaData( unserialize( $metadata ), $result ) : null;
+ $metadata = unserialize( $file->getMetadata() );
+ if ( $version !== 'latest' ) {
+ $metadata = $file->convertMetadataVersion( $metadata, $version );
+ }
+ $vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null;
}
if ( $mime ) {
$vals['mime'] = $file->getMimeType();
}
+ if ( $mediatype ) {
+ $vals['mediatype'] = $file->getMediaType();
+ }
+
if ( $archive && $file->isOld() ) {
$vals['archivename'] = $file->getArchiveName();
}
@@ -336,7 +405,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
return $vals;
}
- /*
+ /**
*
* @param $metadata Array
* @param $result ApiResult
@@ -363,6 +432,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
return 'public';
}
+ /**
+ * @param $img File
+ * @return string
+ */
private function getContinueStr( $img ) {
return $img->getOriginalTitle()->getText() .
'|' . $img->getTimestamp();
@@ -396,63 +469,86 @@ class ApiQueryImageInfo extends ApiQueryBase {
ApiBase::PARAM_TYPE => 'integer',
ApiBase::PARAM_DFLT => -1
),
+ 'metadataversion' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_DFLT => '1',
+ ),
+ 'urlparam' => array(
+ ApiBase::PARAM_DFLT => '',
+ ApiBase::PARAM_TYPE => 'string',
+ ),
'continue' => null,
);
}
/**
* Returns all possible parameters to iiprop
+ *
+ * @param array $filter List of properties to filter out
+ *
+ * @return Array
+ */
+ public static function getPropertyNames( $filter = array() ) {
+ return array_diff( array_keys( self::getProperties() ), $filter );
+ }
+
+ /**
+ * Returns array key value pairs of properties and their descriptions
+ *
+ * @return array
*/
- public static function getPropertyNames() {
+ private static function getProperties() {
return array(
- 'timestamp',
- 'user',
- 'userid',
- 'comment',
- 'parsedcomment',
- 'url',
- 'size',
- 'dimensions', // For backwards compatibility with Allimages
- 'sha1',
- 'mime',
- 'thumbmime',
- 'metadata',
- 'archivename',
- 'bitdepth',
+ 'timestamp' => ' timestamp - Adds timestamp for the uploaded version',
+ 'user' => ' user - Adds the user who uploaded the image version',
+ 'userid' => ' userid - Add the user ID that uploaded the image version',
+ 'comment' => ' comment - Comment on the version',
+ 'parsedcomment' => ' parsedcomment - Parse the comment on the version',
+ 'url' => ' url - Gives URL to the image and the description page',
+ 'size' => ' size - Adds the size of the image in bytes and the height, width and page count (if applicable)',
+ 'dimensions' => ' dimensions - Alias for size', // For backwards compatibility with Allimages
+ 'sha1' => ' sha1 - Adds SHA-1 hash for the image',
+ 'mime' => ' mime - Adds MIME type of the image',
+ 'thumbmime' => ' thumbmime - Adds MIME type of the image thumbnail (requires url)',
+ 'mediatype' => ' mediatype - Adds the media type of the image',
+ 'metadata' => ' metadata - Lists EXIF metadata for the version of the image',
+ 'archivename' => ' archivename - Adds the file name of the archive version for non-latest versions',
+ 'bitdepth' => ' bitdepth - Adds the bit depth of the version',
);
}
+ /**
+ * Returns the descriptions for the properties provided by getPropertyNames()
+ *
+ * @param array $filter List of properties to filter out
+ *
+ * @return array
+ */
+ public static function getPropertyDescriptions( $filter = array() ) {
+ return array_merge(
+ array( 'What image information to get:' ),
+ array_values( array_diff_key( self::getProperties(), array_flip( $filter ) ) )
+ );
+ }
/**
- * Return the API documentation for the parameters.
- * @return {Array} parameter documentation.
+ * 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',
- ' 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',
- ),
+ 'prop' => self::getPropertyDescriptions(),
'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",
+ 'urlparam' => array( "A handler specific parameter string. For example, pdf's ",
+ "might use 'page15-100px'. {$p}urlwidth must be used and be consistent with {$p}urlparam" ),
'limit' => 'How many image revisions to return',
'start' => 'Timestamp to start listing from',
'end' => 'Timestamp to stop listing at',
+ 'metadataversion' => array( "Version of metadata to use. if 'latest' is specified, use latest version.",
+ "Defaults to '1' for backwards compatibility" ),
'continue' => 'If the query response includes a continue value, use it here to get another page of results'
);
}
@@ -462,8 +558,13 @@ class ApiQueryImageInfo extends ApiQueryBase {
}
public function getPossibleErrors() {
+ $p = $this->getModulePrefix();
return array_merge( parent::getPossibleErrors(), array(
- array( 'code' => 'iiurlwidth', 'info' => 'iiurlheight cannot be used without iiurlwidth' ),
+ array( 'code' => "{$p}urlwidth", 'info' => "{$p}urlheight cannot be used without {$p}urlwidth" ),
+ array( 'code' => 'urlparam', 'info' => "Invalid value for {$p}urlparam" ),
+ array( 'code' => 'urlparam_no_width', 'info' => "{$p}urlparam requires {$p}urlwidth" ),
+ array( 'code' => 'urlparam_urlwidth_mismatch', 'info' => "The width set in {$p}urlparm doesnt't " .
+ "match the one in {$p}urlwidth" ),
) );
}
@@ -474,7 +575,11 @@ class ApiQueryImageInfo extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryImageInfo.php 85435 2011-04-05 14:00:08Z demon $';
+ return __CLASS__ . ': $Id: ApiQueryImageInfo.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php
index af2920c7..9dfdf341 100644
--- a/includes/api/ApiQueryImages.php
+++ b/includes/api/ApiQueryImages.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on May 13, 2007
*
@@ -48,6 +48,9 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ */
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
return; // nothing to do
@@ -84,6 +87,19 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
+ if ( !is_null( $params['images'] ) ) {
+ $images = array();
+ foreach ( $params['images'] as $img ) {
+ $title = Title::newFromText( $img );
+ if ( !$title || $title->getNamespace() != NS_FILE ) {
+ $this->setWarning( "``$img'' is not a file" );
+ } else {
+ $images[] = $title->getDBkey();
+ }
+ }
+ $this->addWhereFld( 'il_to', $images );
+ }
+
$res = $this->select( __METHOD__ );
if ( is_null( $resultPageSet ) ) {
@@ -136,6 +152,9 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'continue' => null,
+ 'images' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ )
);
}
@@ -143,6 +162,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
return array(
'limit' => 'How many images to return',
'continue' => 'When more results are available, use this to continue',
+ 'images' => 'Only list these images. Useful for checking whether a certain page has a certain Image.',
);
}
@@ -165,7 +185,11 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#images_.2F_im';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryImages.php 73543 2010-09-22 16:50:09Z platonides $';
+ return __CLASS__ . ': $Id: ApiQueryImages.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index 59f61de1..fef1c6fc 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 25, 2006
*
@@ -41,12 +41,23 @@ class ApiQueryInfo extends ApiQueryBase {
$fld_readable = false, $fld_watched = false,
$fld_preload = false, $fld_displaytitle = false;
+ private $params, $titles, $missing, $everything, $pageCounter;
+
+ private $pageRestrictions, $pageIsRedir, $pageIsNew, $pageTouched,
+ $pageLatest, $pageLength;
+
+ private $protections, $watched, $talkids, $subjectids, $displaytitles;
+
private $tokenFunctions;
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'in' );
}
+ /**
+ * @param $pageSet ApiPageSet
+ * @return void
+ */
public function requestExtraData( $pageSet ) {
global $wgDisableCounters;
@@ -87,6 +98,7 @@ class ApiQueryInfo extends ApiQueryBase {
'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' ),
'email' => array( 'ApiQueryInfo', 'getEmailToken' ),
'import' => array( 'ApiQueryInfo', 'getImportToken' ),
+ 'watch' => array( 'ApiQueryInfo', 'getWatchToken'),
);
wfRunHooks( 'APIQueryInfoTokens', array( &$this->tokenFunctions ) );
return $this->tokenFunctions;
@@ -193,7 +205,7 @@ class ApiQueryInfo extends ApiQueryBase {
public static function getImportToken( $pageid, $title ) {
global $wgUser;
- if ( !$wgUser->isAllowed( 'import' ) ) {
+ if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) {
return false;
}
@@ -206,6 +218,21 @@ class ApiQueryInfo extends ApiQueryBase {
return $cachedImportToken;
}
+ public static function getWatchToken( $pageid, $title ) {
+ global $wgUser;
+ if ( !$wgUser->isLoggedIn() ) {
+ return false;
+ }
+
+ static $cachedWatchToken = null;
+ if ( !is_null( $cachedWatchToken ) ) {
+ return $cachedWatchToken;
+ }
+
+ $cachedWatchToken = $wgUser->editToken( 'watch' );
+ return $cachedWatchToken;
+ }
+
public function execute() {
$this->params = $this->extractRequestParams();
if ( !is_null( $this->params['prop'] ) ) {
@@ -353,8 +380,8 @@ class ApiQueryInfo extends ApiQueryBase {
}
if ( $this->fld_url ) {
- $pageInfo['fullurl'] = $title->getFullURL();
- $pageInfo['editurl'] = $title->getFullURL( 'action=edit' );
+ $pageInfo['fullurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
+ $pageInfo['editurl'] = wfExpandUrl( $title->getFullURL( 'action=edit' ), PROTO_CURRENT );
}
if ( $this->fld_readable && $title->userCanRead() ) {
$pageInfo['readable'] = '';
@@ -386,6 +413,7 @@ class ApiQueryInfo extends ApiQueryBase {
* Get information about protections and put it in $protections
*/
private function getProtectionInfo() {
+ global $wgContLang;
$this->protections = array();
$db = $this->getDB();
@@ -404,7 +432,7 @@ class ApiQueryInfo extends ApiQueryBase {
$a = array(
'type' => $row->pr_type,
'level' => $row->pr_level,
- 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 )
+ 'expiry' => $wgContLang->formatExpiry( $row->pr_expiry, TS_ISO_8601 )
);
if ( $row->pr_cascade ) {
$a['cascade'] = '';
@@ -461,7 +489,7 @@ class ApiQueryInfo extends ApiQueryBase {
$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 )
+ 'expiry' => $wgContLang->formatExpiry( $row->pt_expiry, TS_ISO_8601 )
);
}
}
@@ -495,7 +523,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->protections[$row->tl_namespace][$row->tl_title][] = array(
'type' => $row->pr_type,
'level' => $row->pr_level,
- 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
+ 'expiry' => $wgContLang->formatExpiry( $row->pr_expiry, TS_ISO_8601 ),
'source' => $source->getPrefixedText()
);
}
@@ -518,7 +546,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->protections[NS_FILE][$row->il_to][] = array(
'type' => $row->pr_type,
'level' => $row->pr_level,
- 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ),
+ 'expiry' => $wgContLang->formatExpiry( $row->pr_expiry, TS_ISO_8601 ),
'source' => $source->getPrefixedText()
);
}
@@ -700,7 +728,11 @@ class ApiQueryInfo extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#info_.2F_in';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryInfo.php 78439 2010-12-15 14:23:46Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryInfo.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryLangBacklinks.php b/includes/api/ApiQueryLangBacklinks.php
new file mode 100644
index 00000000..e09384e5
--- /dev/null
+++ b/includes/api/ApiQueryLangBacklinks.php
@@ -0,0 +1,220 @@
+<?php
+/**
+ * API for MediaWiki 1.17+
+ *
+ * Created on May 14, 2011
+ *
+ * Copyright © 2011 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 ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'lbl' );
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
+ public function run( $resultPageSet = null ) {
+ $params = $this->extractRequestParams();
+
+ if ( isset( $params['title'] ) && !isset( $params['lang'] ) ) {
+ $this->dieUsageMsg( array( 'missingparam', 'lang' ) );
+ }
+
+ 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(
+ "ll_lang > '$prefix' OR " .
+ "(ll_lang = '$prefix' AND " .
+ "(ll_title > '$title' OR " .
+ "(ll_title = '$title' AND " .
+ "ll_from >= $from)))"
+ );
+ }
+
+ $prop = array_flip( $params['prop'] );
+ $lllang = isset( $prop['lllang'] );
+ $lltitle = isset( $prop['lltitle'] );
+
+ $this->addTables( array( 'langlinks', 'page' ) );
+ $this->addWhere( 'll_from = page_id' );
+
+ $this->addFields( array( 'page_id', 'page_title', 'page_namespace', 'page_is_redirect',
+ 'll_from', 'll_lang', 'll_title' ) );
+
+ if ( isset( $params['lang'] ) ) {
+ $this->addWhereFld( 'll_lang', $params['lang'] );
+ if ( isset( $params['title'] ) ) {
+ $this->addWhereFld( 'll_title', $params['title'] );
+ $this->addOption( 'ORDER BY', 'll_from' );
+ } else {
+ $this->addOption( 'ORDER BY', 'll_title, ll_from' );
+ }
+ } else {
+ $this->addOption( 'ORDER BY', 'll_lang, ll_title, ll_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->ll_lang}|{$row->ll_title}|{$row->ll_from}" );
+ break;
+ }
+
+ if ( !is_null( $resultPageSet ) ) {
+ $pages[] = Title::newFromRow( $row );
+ } else {
+ $entry = array( 'pageid' => $row->page_id );
+
+ $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ ApiQueryBase::addTitleInfo( $entry, $title );
+
+ if ( $row->page_is_redirect ) {
+ $entry['redirect'] = '';
+ }
+
+ if ( $lllang ) {
+ $entry['lllang'] = $row->ll_lang;
+ }
+
+ if ( $lltitle ) {
+ $entry['lltitle'] = $row->ll_title;
+ }
+
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $entry );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'continue', "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}" );
+ break;
+ }
+ }
+ }
+
+ if ( is_null( $resultPageSet ) ) {
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'll' );
+ } else {
+ $resultPageSet->populateFromTitles( $pages );
+ }
+ }
+
+ public function getCacheMode( $params ) {
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'lang' => 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(
+ 'lllang',
+ 'lltitle',
+ ),
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'lang' => 'Language for the language link',
+ 'title' => "Language link to search for. Must be used with {$this->getModulePrefix()}lang",
+ 'continue' => 'When more results are available, use this to continue',
+ 'prop' => array(
+ 'Which properties to get',
+ ' lllang - Adds the language code of the language link',
+ ' lltitle - Adds the title of the language ink',
+ ),
+ 'limit' => 'How many total pages to return',
+ );
+ }
+
+ public function getDescription() {
+ return array( 'Find all pages that link to the given language link.',
+ 'Can be used to find all links with a language code, or',
+ 'all links to a title (with a given language).',
+ 'Using neither parameter is effectively "All Language Links"',
+ );
+ }
+
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'missingparam', 'lang' ),
+ 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=langbacklinks&lbltitle=Test&lbllang=fr',
+ 'api.php?action=query&generator=langbacklinks&glbltitle=Test&lbllang=fr&prop=info'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryLangBacklinks.php 88429 2011-05-19 21:13:03Z reedy $';
+ }
+}
diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php
index c2ecbfee..b2a974ad 100644
--- a/includes/api/ApiQueryLangLinks.php
+++ b/includes/api/ApiQueryLangLinks.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on May 13, 2007
*
@@ -46,6 +46,11 @@ class ApiQueryLangLinks extends ApiQueryBase {
}
$params = $this->extractRequestParams();
+
+ if ( isset( $params['title'] ) && !isset( $params['lang'] ) ) {
+ $this->dieUsageMsg( array( 'missingparam', 'lang' ) );
+ }
+
$this->addFields( array(
'll_from',
'll_lang',
@@ -69,12 +74,23 @@ class ApiQueryLangLinks extends ApiQueryBase {
);
}
- // Don't order by ll_from if it's constant in the WHERE clause
- if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
- $this->addOption( 'ORDER BY', 'll_lang' );
+ if ( isset( $params['lang'] ) ) {
+ $this->addWhereFld( 'll_lang', $params['lang'] );
+ if ( isset( $params['title'] ) ) {
+ $this->addWhereFld( 'll_title', $params['title'] );
+ $this->addOption( 'ORDER BY', 'll_from' );
+ } else {
+ $this->addOption( 'ORDER BY', 'll_title, ll_from' );
+ }
} else {
- $this->addOption( 'ORDER BY', 'll_from, ll_lang' );
+ // Don't order by ll_from if it's constant in the WHERE clause
+ if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
+ $this->addOption( 'ORDER BY', 'll_lang' );
+ } else {
+ $this->addOption( 'ORDER BY', 'll_from, ll_lang' );
+ }
}
+
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$res = $this->select( __METHOD__ );
@@ -90,7 +106,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
if ( $params['url'] ) {
$title = Title::newFromText( "{$row->ll_lang}:{$row->ll_title}" );
if ( $title ) {
- $entry['url'] = $title->getFullURL();
+ $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
}
}
ApiResult::setContent( $entry, $row->ll_title );
@@ -117,6 +133,8 @@ class ApiQueryLangLinks extends ApiQueryBase {
),
'continue' => null,
'url' => false,
+ 'lang' => null,
+ 'title' => null,
);
}
@@ -125,6 +143,8 @@ class ApiQueryLangLinks extends ApiQueryBase {
'limit' => 'How many langlinks to return',
'continue' => 'When more results are available, use this to continue',
'url' => 'Whether to get the full URL',
+ 'lang' => 'Language code',
+ 'title' => "Link to search for. Must be used with {$this->getModulePrefix()}lang",
);
}
@@ -134,6 +154,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
+ array( 'missingparam', 'lang' ),
array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ),
) );
}
@@ -145,7 +166,11 @@ class ApiQueryLangLinks extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#langlinks_.2F_ll';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryLangLinks.php 77660 2010-12-03 14:44:07Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryLangLinks.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php
index 4f3bad3b..fa2495a9 100644
--- a/includes/api/ApiQueryLinks.php
+++ b/includes/api/ApiQueryLinks.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on May 12, 2007
*
@@ -39,7 +39,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
const LINKS = 'links';
const TEMPLATES = 'templates';
- private $table, $prefix, $description;
+ private $table, $prefix, $description, $helpUrl;
public function __construct( $query, $moduleName ) {
switch ( $moduleName ) {
@@ -48,12 +48,14 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->prefix = 'pl';
$this->description = 'link';
$this->titlesParam = 'titles';
+ $this->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#links_.2F_pl';
break;
case self::TEMPLATES:
$this->table = 'templatelinks';
$this->prefix = 'tl';
$this->description = 'template';
$this->titlesParam = 'templates';
+ $this->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#templates_.2F_tl';
break;
default:
ApiBase::dieDebug( __METHOD__, 'Unknown module name' );
@@ -74,6 +76,10 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return
+ */
private function run( $resultPageSet = null ) {
if ( $this->getPageSet()->getGoodTitleCount() == 0 ) {
return; // nothing to do
@@ -213,7 +219,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
);
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 ) {
+ } elseif ( $this->getModuleName() == self::TEMPLATES ) {
$arr[$this->titlesParam] = 'Only list these templates. Useful for checking whether a certain page uses a certain template.';
}
return $arr;
@@ -234,7 +240,11 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return $this->helpUrl;
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryLinks.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryLinks.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
index 7d69ca39..1420e0a7 100644
--- a/includes/api/ApiQueryLogEvents.php
+++ b/includes/api/ApiQueryLogEvents.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 16, 2006
*
@@ -86,13 +86,10 @@ class ApiQueryLogEvents extends ApiQueryBase {
'log_deleted',
) );
- $this->addFieldsIf( 'log_id', $this->fld_ids );
- $this->addFieldsIf( 'page_id', $this->fld_ids );
- $this->addFieldsIf( 'log_user', $this->fld_user );
- $this->addFieldsIf( 'user_name', $this->fld_user );
+ $this->addFieldsIf( array( 'log_id', 'page_id' ), $this->fld_ids );
+ $this->addFieldsIf( array( 'log_user', 'user_name' ), $this->fld_user );
$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( array( 'log_namespace', 'log_title' ), $this->fld_title || $this->fld_parsedcomment );
$this->addFieldsIf( 'log_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'log_params', $this->fld_details );
@@ -114,13 +111,12 @@ class ApiQueryLogEvents extends ApiQueryBase {
list( $type, $action ) = explode( '/', $params['action'] );
$this->addWhereFld( 'log_type', $type );
$this->addWhereFld( 'log_action', $action );
- }
- else if ( !is_null( $params['type'] ) ) {
+ } elseif ( !is_null( $params['type'] ) ) {
$this->addWhereFld( 'log_type', $params['type'] );
$index['logging'] = 'type_time';
}
- $this->addWhereRange( 'log_timestamp', $params['dir'], $params['start'], $params['end'] );
+ $this->addTimestampWhereRange( 'log_timestamp', $params['dir'], $params['start'], $params['end'] );
$limit = $params['limit'];
$this->addOption( 'LIMIT', $limit + 1 );
@@ -148,6 +144,22 @@ class ApiQueryLogEvents extends ApiQueryBase {
$index['logging'] = is_null( $user ) ? 'page_time' : array( 'page_time', 'user_time' );
}
+ $prefix = $params['prefix'];
+
+ if ( !is_null( $prefix ) ) {
+ global $wgMiserMode;
+ if ( $wgMiserMode ) {
+ $this->dieUsage( 'Prefix search disabled in Miser Mode', 'prefixsearchdisabled' );
+ }
+
+ $title = Title::newFromText( $prefix );
+ if ( is_null( $title ) ) {
+ $this->dieUsage( "Bad title value '$prefix'", 'param_prefix' );
+ }
+ $this->addWhereFld( 'log_namespace', $title->getNamespace() );
+ $this->addWhere( 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() ) );
+ }
+
$this->addOption( 'USE INDEX', $index );
// Paranoia: avoid brute force searches (bug 17342)
@@ -160,6 +172,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
$count = 0;
$res = $this->select( __METHOD__ );
+ $result = $this->getResult();
foreach ( $res as $row ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
@@ -171,25 +184,25 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( !$vals ) {
continue;
}
- $fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals );
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->log_timestamp ) );
break;
}
}
- $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' );
}
/**
- * @static
* @param $result ApiResult
- * @param $vals
- * @param $params
- * @param $type
+ * @param $vals array
+ * @param $params string
+ * @param $type string
+ * @param $action string
* @param $ts
* @return array
*/
- public static function addLogParams( $result, &$vals, $params, $type, $ts ) {
+ public static function addLogParams( $result, &$vals, $params, $type, $action, $ts ) {
$params = explode( "\n", $params );
switch ( $type ) {
case 'move':
@@ -219,11 +232,14 @@ class ApiQueryLogEvents extends ApiQueryBase {
$params = null;
break;
case 'block':
+ if ( $action == 'unblock' ) {
+ break;
+ }
$vals2 = array();
list( $vals2['duration'], $vals2['flags'] ) = $params;
// Indefinite blocks have no expiry time
- if ( Block::parseExpiryInput( $params[0] ) !== Block::infinity() ) {
+ if ( SpecialBlock::parseExpiryInput( $params[0] ) !== wfGetDB( DB_SLAVE )->getInfinity() ) {
$vals2['expiry'] = wfTimestamp( TS_ISO_8601,
strtotime( $params[0], wfTimestamp( TS_UNIX, $ts ) ) );
}
@@ -268,8 +284,11 @@ class ApiQueryLogEvents extends ApiQueryBase {
$vals['actionhidden'] = '';
} else {
self::addLogParams(
- $this->getResult(), $vals,
- $row->log_params, $row->log_type,
+ $this->getResult(),
+ $vals,
+ $row->log_params,
+ $row->log_type,
+ $row->log_action,
$row->log_timestamp
);
}
@@ -285,7 +304,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
if ( $this->fld_userid ) {
$vals['userid'] = $row->user_id;
}
-
+
if ( !$row->log_user ) {
$vals['anon'] = '';
}
@@ -372,6 +391,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
),
'user' => null,
'title' => null,
+ 'prefix' => null,
'tag' => null,
'limit' => array(
ApiBase::PARAM_DFLT => 10,
@@ -384,27 +404,29 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
public function getParamDescription() {
+ $p = $this->getModulePrefix();
return array(
'prop' => array(
'Which properties to get',
- ' ids - Adds the id of the log event',
+ ' 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',
+ ' 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)',
- 'action' => "Filter log actions to only this type. Overrides {$this->getModulePrefix()}type",
+ 'type' => 'Filter log entries to only this type',
+ 'action' => "Filter log actions to only this type. Overrides {$p}type",
'start' => 'The timestamp to start enumerating from',
'end' => 'The timestamp to end enumerating',
- 'dir' => 'In which direction to enumerate',
+ 'dir' => $this->getDirectionDescription( $p ),
'user' => 'Filter entries to those made by the given user',
'title' => 'Filter entries to those related to a page',
+ 'prefix' => 'Filter entries that start with this prefix. Disabled in Miser Mode',
'limit' => 'How many total event entries to return',
'tag' => 'Only list event entries tagged with this tag',
);
@@ -418,6 +440,8 @@ class ApiQueryLogEvents extends ApiQueryBase {
return array_merge( parent::getPossibleErrors(), array(
array( 'code' => 'param_user', 'info' => 'User name $user not found' ),
array( 'code' => 'param_title', 'info' => 'Bad title value \'title\'' ),
+ array( 'code' => 'param_prefix', 'info' => 'Bad title value \'prefix\'' ),
+ array( 'code' => 'prefixsearchdisabled', 'info' => 'Prefix search disabled in Miser Mode' ),
) );
}
@@ -427,7 +451,11 @@ class ApiQueryLogEvents extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Logevents';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryLogEvents.php 74535 2010-10-09 00:01:45Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryLogEvents.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryPageProps.php b/includes/api/ApiQueryPageProps.php
index 894e812d..64b8511d 100644
--- a/includes/api/ApiQueryPageProps.php
+++ b/includes/api/ApiQueryPageProps.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Aug 7, 2010
*
@@ -43,55 +43,59 @@ class ApiQueryPageProps extends ApiQueryBase {
}
public function execute() {
- $this->params = $this->extractRequestParams();
-
# Only operate on existing pages
$pages = $this->getPageSet()->getGoodTitles();
if ( !count( $pages ) ) {
# Nothing to do
return;
}
-
+
+ $this->params = $this->extractRequestParams();
+
$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'] ) );
}
-
+
+ if ( $this->params['prop'] ) {
+ $this->addWhereFld( 'pp_propname', $this->params['prop'] );
+ }
+
# 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
+ # 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 );
@@ -99,7 +103,7 @@ class ApiQueryPageProps extends ApiQueryBase {
}
/**
- * Add page properties to an ApiResult, adding a continue
+ * Add page properties to an ApiResult, adding a continue
* parameter if it doesn't fit.
*
* @param $result ApiResult
@@ -109,7 +113,7 @@ class ApiQueryPageProps extends ApiQueryBase {
*/
private function addPageProps( $result, $page, $props ) {
$fit = $result->addValue( array( 'query', 'pages', $page ), 'pageprops', $props );
-
+
if ( !$fit ) {
$this->setContinueEnumParameter( 'continue', $page );
}
@@ -120,31 +124,35 @@ class ApiQueryPageProps extends ApiQueryBase {
return 'public';
}
- public function getAllowedParams() {
- return array( 'continue' => null );
+ public function getAllowedParams() {
+ return array(
+ 'continue' => null,
+ 'prop' => null,
+ );
}
public function getParamDescription() {
- return array( 'continue' => 'When more results are available, use this to continue' );
+ return array(
+ 'continue' => 'When more results are available, use this to continue',
+ 'prop' => 'Page prop to look on the page for. Useful for checking whether a certain page uses a certain page prop.'
+ );
}
public function getDescription() {
return 'Get various properties defined in the page content';
}
- public function 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 getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#pageprops_.2F_pp';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryPageProps.php 85211 2011-04-02 21:01:00Z demon $';
+ return __CLASS__ . ': $Id: ApiQueryPageProps.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php
index e647c39f..14df7446 100644
--- a/includes/api/ApiQueryProtectedTitles.php
+++ b/includes/api/ApiQueryProtectedTitles.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Feb 13, 2009
*
- * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -48,6 +48,10 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
$params = $this->extractRequestParams();
@@ -60,7 +64,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$this->addFieldsIf( 'pt_expiry', isset( $prop['expiry'] ) );
$this->addFieldsIf( 'pt_create_perm', isset( $prop['level'] ) );
- $this->addWhereRange( 'pt_timestamp', $params['dir'], $params['start'], $params['end'] );
+ $this->addTimestampWhereRange( 'pt_timestamp', $params['dir'], $params['start'], $params['end'] );
$this->addWhereFld( 'pt_namespace', $params['namespace'] );
$this->addWhereFld( 'pt_create_perm', $params['level'] );
@@ -77,6 +81,9 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
$count = 0;
$result = $this->getResult();
+
+ $titles = array();
+
foreach ( $res as $row ) {
if ( ++ $count > $params['limit'] ) {
// We've reached the one extra which shows that there are additional pages to be had. Stop here...
@@ -110,7 +117,8 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
}
if ( isset( $prop['expiry'] ) ) {
- $vals['expiry'] = Block::decodeExpiry( $row->pt_expiry, TS_ISO_8601 );
+ global $wgContLang;
+ $vals['expiry'] = $wgContLang->formatExpiry( $row->pt_expiry, TS_ISO_8601 );
}
if ( isset( $prop['level'] ) ) {
@@ -165,8 +173,8 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
'dir' => array(
ApiBase::PARAM_DFLT => 'older',
ApiBase::PARAM_TYPE => array(
- 'older',
- 'newer'
+ 'newer',
+ 'older'
)
),
'start' => array(
@@ -196,13 +204,13 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
'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',
+ 'dir' => $this->getDirectionDescription( $this->getModulePrefix() ),
'limit' => 'How many total pages to return',
'prop' => array(
'Which properties to get',
' timestamp - Adds the timestamp of when protection was added',
- ' user - Adds the user to add the protection',
- ' userid - Adds the user id to add the protection',
+ ' user - Adds the user that added the protection',
+ ' userid - Adds the user id that added the protection',
' comment - Adds the comment for the protection',
' parsedcomment - Adds the parsed comment for the protection',
' expiry - Adds the timestamp of when the protection will be lifted',
@@ -222,7 +230,11 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Protectedtitles';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryProtectedTitles.php 71838 2010-08-28 01:18:18Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryProtectedTitles.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryQueryPage.php b/includes/api/ApiQueryQueryPage.php
new file mode 100644
index 00000000..e22cf8eb
--- /dev/null
+++ b/includes/api/ApiQueryQueryPage.php
@@ -0,0 +1,198 @@
+<?php
+/**
+ *
+ *
+ * Created on Dec 22, 2010
+ *
+ * Copyright © 2010 Roan Kattouw <Firstname>.<Lastname>@gmail.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ // Eclipse helper - will be ignored in production
+ require_once( 'ApiQueryBase.php' );
+}
+
+/**
+ * Query module to get the results of a QueryPage-based special page
+ *
+ * @ingroup API
+ */
+class ApiQueryQueryPage extends ApiQueryGeneratorBase {
+ private $qpMap;
+
+ /**
+ * Some query pages are useless because they're available elsewhere in the API
+ */
+ private $uselessQueryPages = array(
+ 'MIMEsearch', // aiprop=mime
+ 'LinkSearch', // list=exturlusage
+ 'FileDuplicateSearch', // prop=duplicatefiles
+ );
+
+ public function __construct( $query, $moduleName ) {
+ parent::__construct( $query, $moduleName, 'qp' );
+ // We need to do this to make sure $wgQueryPages is set up
+ // This SUCKS
+ global $IP;
+ require_once( "$IP/includes/QueryPage.php" );
+
+ // Build mapping from special page names to QueryPage classes
+ global $wgQueryPages;
+ $this->qpMap = array();
+ foreach ( $wgQueryPages as $page ) {
+ if( !in_array( $page[1], $this->uselessQueryPages ) ) {
+ $this->qpMap[$page[1]] = $page[0];
+ }
+ }
+ }
+
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
+ public function run( $resultPageSet = null ) {
+ global $wgUser;
+ $params = $this->extractRequestParams();
+ $result = $this->getResult();
+
+ $qp = new $this->qpMap[$params['page']]();
+ if ( !$qp->userCanExecute( $wgUser ) ) {
+ $this->dieUsageMsg( 'specialpage-cantexecute' );
+ }
+
+ $r = array( 'name' => $params['page'] );
+ if ( $qp->isCached() ) {
+ if ( !$qp->isCacheable() ) {
+ $r['disabled'] = '';
+ } else {
+ $r['cached'] = '';
+ $ts = $qp->getCachedTimestamp();
+ if ( $ts ) {
+ $r['cachedtimestamp'] = wfTimestamp( TS_ISO_8601, $ts );
+ }
+ }
+ }
+ $result->addValue( array( 'query' ), $this->getModuleName(), $r );
+
+ if ( $qp->isCached() && !$qp->isCacheable() ) {
+ // Disabled query page, don't run the query
+ return;
+ }
+
+ $res = $qp->doQuery( $params['offset'], $params['limit'] + 1 );
+ $count = 0;
+ $titles = array();
+ foreach ( $res as $row ) {
+ if ( ++$count > $params['limit'] ) {
+ // We've had enough
+ $this->setContinueEnumParameter( 'offset', $params['offset'] + $params['limit'] );
+ break;
+ }
+
+ $title = Title::makeTitle( $row->namespace, $row->title );
+ if ( is_null( $resultPageSet ) ) {
+ $data = array( 'value' => $row->value );
+ if ( $qp->usesTimestamps() ) {
+ $data['timestamp'] = wfTimestamp( TS_ISO_8601, $row->value );
+ }
+ self::addTitleInfo( $data, $title );
+
+ foreach ( $row as $field => $value ) {
+ if ( !in_array( $field, array( 'namespace', 'title', 'value', 'qc_type' ) ) ) {
+ $data['databaseResult'][$field] = $value;
+ }
+ }
+
+ $fit = $result->addValue( array( 'query', $this->getModuleName(), 'results' ), null, $data );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'offset', $params['offset'] + $count - 1 );
+ break;
+ }
+ } else {
+ $titles[] = $title;
+ }
+ }
+ if ( is_null( $resultPageSet ) ) {
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName(), 'results' ), 'page' );
+ } else {
+ $resultPageSet->populateFromTitles( $titles );
+ }
+ }
+
+ public function getCacheMode( $params ) {
+ $qp = new $this->qpMap[$params['page']]();
+ if ( $qp->getRestriction() != '' ) {
+ return 'private';
+ }
+ return 'public';
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'page' => array(
+ ApiBase::PARAM_TYPE => array_keys( $this->qpMap ),
+ ApiBase::PARAM_REQUIRED => true
+ ),
+ 'offset' => 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
+ ),
+ );
+ }
+
+ public function getParamDescription() {
+ return array(
+ 'page' => 'The name of the special page. Note, this is case sensitive',
+ 'offset' => 'When more results are available, use this to continue',
+ 'limit' => 'Number of results to return',
+ );
+ }
+
+ public function getDescription() {
+ return 'Get a list provided by a QueryPage-based special page';
+ }
+
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ ) );
+ }
+
+ protected function getExamples() {
+ return array(
+ 'api.php?action=query&list=querypage&qppage=Ancientpages'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id: ApiQueryQueryPage.php 99989 2011-10-16 22:24:58Z reedy $';
+ }
+}
diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php
index b3b840fd..dea0b0f5 100644
--- a/includes/api/ApiQueryRandom.php
+++ b/includes/api/ApiQueryRandom.php
@@ -1,7 +1,7 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Monday, January 28, 2008
*
@@ -36,7 +36,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* @ingroup API
*/
- class ApiQueryRandom extends ApiQueryGeneratorBase {
+class ApiQueryRandom extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rn' );
@@ -50,6 +50,14 @@ if ( !defined( 'MEDIAWIKI' ) ) {
$this->run( $resultPageSet );
}
+ /**
+ * @param $randstr
+ * @param $limit
+ * @param $namespace
+ * @param $resultPageSet ApiPageSet
+ * @param $redirect
+ * @return void
+ */
protected function prepareQuery( $randstr, $limit, $namespace, &$resultPageSet, $redirect ) {
$this->resetQueryParams();
$this->addTables( 'page' );
@@ -65,7 +73,11 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
}
- protected function runQuery( &$resultPageSet ) {
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return int
+ */
+ protected function runQuery( $resultPageSet = null ) {
$res = $this->select( __METHOD__ );
$count = 0;
foreach ( $res as $row ) {
@@ -92,6 +104,10 @@ if ( !defined( 'MEDIAWIKI' ) ) {
return $count;
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
public function run( $resultPageSet = null ) {
$params = $this->extractRequestParams();
$result = $this->getResult();
diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php
index fb0d42b8..b144a7cf 100644
--- a/includes/api/ApiQueryRecentChanges.php
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 19, 2006
*
@@ -35,7 +35,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*
* @ingroup API
*/
-class ApiQueryRecentChanges extends ApiQueryBase {
+class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rc' );
@@ -43,7 +43,8 @@ class ApiQueryRecentChanges extends ApiQueryBase {
private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
$fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false,
- $fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false, $fld_tags = false;
+ $fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false,
+ $fld_tags = false, $token = array();
private $tokenFunctions;
@@ -71,6 +72,12 @@ class ApiQueryRecentChanges extends ApiQueryBase {
return $this->tokenFunctions;
}
+ /**
+ * @param $pageid
+ * @param $title
+ * @param $rc RecentChange
+ * @return bool|String
+ */
public static function getPatrolToken( $pageid, $title, $rc ) {
global $wgUser;
if ( !$wgUser->useRCPatrol() && ( !$wgUser->useNPPatrol() ||
@@ -84,7 +91,7 @@ class ApiQueryRecentChanges extends ApiQueryBase {
if ( is_null( $cachedPatrolToken ) ) {
$cachedPatrolToken = $wgUser->editToken( 'patrol' );
}
-
+
return $cachedPatrolToken;
}
@@ -108,10 +115,20 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$this->fld_tags = isset( $prop['tags'] );
}
+ public function execute() {
+ $this->run();
+ }
+
+ public function executeGenerator( $resultPageSet ) {
+ $this->run( $resultPageSet );
+ }
+
/**
* Generates and outputs the result of this query based upon the provided parameters.
+ *
+ * @param $resultPageSet ApiPageSet
*/
- public function execute() {
+ public function run( $resultPageSet = null ) {
global $wgUser;
/* Get the parameters of the request. */
$params = $this->extractRequestParams();
@@ -123,7 +140,7 @@ class ApiQueryRecentChanges extends ApiQueryBase {
*/
$this->addTables( 'recentchanges' );
$index = array( 'recentchanges' => 'rc_timestamp' ); // May change
- $this->addWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
+ $this->addTimestampWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
$this->addWhereFld( 'rc_namespace', $params['namespace'] );
$this->addWhereFld( 'rc_deleted', 0 );
@@ -140,9 +157,8 @@ class ApiQueryRecentChanges extends ApiQueryBase {
|| ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
|| ( isset( $show['redirect'] ) && isset( $show['!redirect'] ) )
|| ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
- )
- {
- $this->dieUsageMsg( array( 'show' ) );
+ ) {
+ $this->dieUsageMsg( 'show' );
}
// Check permissions
@@ -195,6 +211,7 @@ class ApiQueryRecentChanges extends ApiQueryBase {
'rc_deleted'
) );
+ $showRedirects = false;
/* Determine what properties we need to display. */
if ( !is_null( $params['prop'] ) ) {
$prop = array_flip( $params['prop'] );
@@ -207,27 +224,15 @@ class ApiQueryRecentChanges extends ApiQueryBase {
}
/* Add fields to our query if they are specified as a needed parameter. */
- $this->addFieldsIf( 'rc_id', $this->fld_ids );
- $this->addFieldsIf( 'rc_this_oldid', $this->fld_ids );
- $this->addFieldsIf( 'rc_last_oldid', $this->fld_ids );
+ $this->addFieldsIf( array( 'rc_id', 'rc_this_oldid', 'rc_last_oldid' ), $this->fld_ids );
$this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment );
$this->addFieldsIf( 'rc_user', $this->fld_user );
$this->addFieldsIf( 'rc_user_text', $this->fld_user || $this->fld_userid );
- $this->addFieldsIf( 'rc_minor', $this->fld_flags );
- $this->addFieldsIf( 'rc_bot', $this->fld_flags );
- $this->addFieldsIf( 'rc_new', $this->fld_flags );
- $this->addFieldsIf( 'rc_old_len', $this->fld_sizes );
- $this->addFieldsIf( 'rc_new_len', $this->fld_sizes );
+ $this->addFieldsIf( array( 'rc_minor', 'rc_new', 'rc_bot' ) , $this->fld_flags );
+ $this->addFieldsIf( array( 'rc_old_len', 'rc_new_len' ), $this->fld_sizes );
$this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
- $this->addFieldsIf( 'rc_logid', $this->fld_loginfo );
- $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'] ) ) {
- $this->addTables( 'page' );
- $this->addJoinConds( array( 'page' => array( 'LEFT JOIN', array( 'rc_namespace=page_namespace', 'rc_title=page_title' ) ) ) );
- $this->addFields( 'page_is_redirect' );
- }
+ $this->addFieldsIf( array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ), $this->fld_loginfo );
+ $showRedirects = $this->fld_redirect || isset( $show['redirect'] ) || isset( $show['!redirect'] );
}
if ( $this->fld_tags ) {
@@ -236,6 +241,16 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$this->addFields( 'ts_tags' );
}
+ if ( $params['toponly'] || $showRedirects ) {
+ $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 ( $params['toponly'] ) {
+ $this->addWhere( 'rc_this_oldid = page_latest' );
+ }
+ }
+
if ( !is_null( $params['tag'] ) ) {
$this->addTables( 'change_tag' );
$this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) );
@@ -252,6 +267,10 @@ class ApiQueryRecentChanges extends ApiQueryBase {
/* Perform the actual query. */
$res = $this->select( __METHOD__ );
+ $titles = array();
+
+ $result = $this->getResult();
+
/* Iterate through the rows, adding data extracted from them to our query result. */
foreach ( $res as $row ) {
if ( ++ $count > $params['limit'] ) {
@@ -260,22 +279,30 @@ class ApiQueryRecentChanges extends ApiQueryBase {
break;
}
- /* Extract the data from a single row. */
- $vals = $this->extractRowInfo( $row );
+ if ( is_null( $resultPageSet ) ) {
+ /* Extract the data from a single row. */
+ $vals = $this->extractRowInfo( $row );
- /* Add that row's data to our final output. */
- if ( !$vals ) {
- continue;
- }
- $fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals );
- if ( !$fit ) {
- $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
- break;
+ /* Add that row's data to our final output. */
+ if ( !$vals ) {
+ continue;
+ }
+ $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals );
+ if ( !$fit ) {
+ $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) );
+ break;
+ }
+ } else {
+ $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
}
}
- /* Format the result */
- $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'rc' );
+ if ( is_null( $resultPageSet ) ) {
+ /* Format the result */
+ $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'rc' );
+ } else {
+ $resultPageSet->populateFromTitles( $titles );
+ }
}
/**
@@ -288,8 +315,7 @@ class ApiQueryRecentChanges extends ApiQueryBase {
public function extractRowInfo( $row ) {
/* 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 !== '' )
- {
+ 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 );
}
@@ -405,8 +431,11 @@ class ApiQueryRecentChanges extends ApiQueryBase {
$vals['logaction'] = $row->rc_log_action;
ApiQueryLogEvents::addLogParams(
$this->getResult(),
- $vals, $row->rc_params,
- $row->rc_log_type, $row->rc_timestamp
+ $vals,
+ $row->rc_params,
+ $row->rc_log_action,
+ $row->rc_log_type,
+ $row->rc_timestamp
);
}
@@ -550,15 +579,17 @@ class ApiQueryRecentChanges extends ApiQueryBase {
'new',
'log'
)
- )
+ ),
+ 'toponly' => false,
);
}
public function getParamDescription() {
+ $p = $this->getModulePrefix();
return array(
'start' => 'The timestamp to start enumerating from',
'end' => 'The timestamp to end enumerating',
- 'dir' => 'In which direction to enumerate',
+ 'dir' => $this->getDirectionDescription( $p ),
'namespace' => 'Filter log entries to only this namespace(s)',
'user' => 'Only list changes by this user',
'excludeuser' => 'Don\'t list changes by this user',
@@ -571,21 +602,22 @@ class ApiQueryRecentChanges extends ApiQueryBase {
' 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',
+ ' 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',
+ ' patrolled - Tags edits that 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 only items that meet this criteria.',
- "For example, to see only minor edits done by logged-in users, set {$this->getModulePrefix()}show=minor|!anon"
+ "For example, to see only minor edits done by logged-in users, set {$p}show=minor|!anon"
),
'type' => 'Which types of changes to show',
'limit' => 'How many total changes to return',
'tag' => 'Only list changes tagged with this tag',
+ 'toponly' => 'Only list changes which are the latest revision',
);
}
@@ -607,7 +639,11 @@ class ApiQueryRecentChanges extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Recentchanges';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 78437 2010-12-15 14:14:16Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index 64a0a4ea..e6b21a92 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 7, 2006
*
@@ -39,7 +39,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
class ApiQueryRevisions extends ApiQueryBase {
private $diffto, $difftotext, $expandTemplates, $generateXML, $section,
- $token;
+ $token, $parseContent;
public function __construct( $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'rv' );
@@ -73,6 +73,12 @@ class ApiQueryRevisions extends ApiQueryBase {
return $this->tokenFunctions;
}
+ /**
+ * @param $pageid
+ * @param $title Title
+ * @param $rev Revision
+ * @return bool|String
+ */
public static function getRollbackToken( $pageid, $title, $rev ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'rollback' ) ) {
@@ -119,8 +125,7 @@ class ApiQueryRevisions extends ApiQueryBase {
$params['diffto'] = 0;
}
if ( ( !ctype_digit( $params['diffto'] ) || $params['diffto'] < 0 )
- && $params['diffto'] != 'prev' && $params['diffto'] != 'next' )
- {
+ && $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,
@@ -169,7 +174,6 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->getResult()->setParsedLimit( $this->getModuleName(), $limit );
}
-
if ( !is_null( $this->token ) || $pageCount > 0 ) {
$this->addFields( Revision::selectPageFields() );
}
@@ -224,10 +228,9 @@ class ApiQueryRevisions extends ApiQueryBase {
}
}
- //Bug 24166 - API error when using rvprop=tags
+ // Bug 24166 - API error when using rvprop=tags
$this->addTables( 'revision' );
-
if ( $enumRevMode ) {
// This is mostly to prevent parameter errors (and optimize SQL?)
if ( !is_null( $params['startid'] ) && !is_null( $params['start'] ) ) {
@@ -249,14 +252,14 @@ class ApiQueryRevisions extends ApiQueryBase {
// 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'] ) ) {
- $this->addWhereRange( 'rev_timestamp', $params['dir'],
+ $this->addTimestampWhereRange( 'rev_timestamp', $params['dir'],
$params['start'], $params['end'] );
} else {
$this->addWhereRange( 'rev_id', $params['dir'],
$params['startid'], $params['endid'] );
// One of start and end can be set
// If neither is set, this does nothing
- $this->addWhereRange( 'rev_timestamp', $params['dir'],
+ $this->addTimestampWhereRange( 'rev_timestamp', $params['dir'],
$params['start'], $params['end'], false );
}
@@ -479,27 +482,7 @@ class ApiQueryRevisions extends ApiQueryBase {
$text = $wgParser->preprocess( $text, $title, new ParserOptions() );
}
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();
+ $text = $wgParser->parse( $text, $title, new ParserOptions() )->getText();
}
ApiResult::setContent( $vals, $text );
} elseif ( $this->fld_content ) {
@@ -627,9 +610,9 @@ class ApiQueryRevisions extends ApiQueryBase {
'endid' => 'Stop revision enumeration on this revid (enum)',
'start' => 'From which revision timestamp to start enumeration (enum)',
'end' => 'Enumerate up to this timestamp (enum)',
- 'dir' => 'Direction of enumeration - towards "newer" or "older" revisions (enum)',
- 'user' => 'Only include revisions made by user',
- 'excludeuser' => 'Exclude revisions made by user',
+ 'dir' => $this->getDirectionDescription( $p, ' (enum)' ),
+ 'user' => 'Only include revisions made by user (enum)',
+ 'excludeuser' => 'Exclude revisions made by user (enum)',
'expandtemplates' => 'Expand templates in revision content',
'generatexml' => 'Generate XML parse tree for revision content',
'parse' => 'Parse revision content. For performance reasons if this option is used, rvlimit is enforced to 1.',
@@ -647,7 +630,7 @@ class ApiQueryRevisions extends ApiQueryBase {
public function getDescription() {
return array(
'Get revision information',
- 'This module may be used in several ways:',
+ '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',
@@ -685,7 +668,11 @@ class ApiQueryRevisions extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Properties#revisions_.2F_rv';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryRevisions.php 75521 2010-10-27 11:50:20Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryRevisions.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php
index 3cf693af..42bed93a 100644
--- a/includes/api/ApiQuerySearch.php
+++ b/includes/api/ApiQuerySearch.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on July 30, 2007
*
@@ -48,6 +48,10 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
global $wgContLang;
$params = $this->extractRequestParams();
@@ -93,16 +97,17 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$this->dieUsage( "{$what} search is disabled", "search-{$what}-disabled" );
}
+ $apiResult = $this->getResult();
// Add search meta data to result
if ( isset( $searchInfo['totalhits'] ) ) {
$totalhits = $matches->getTotalHits();
if ( $totalhits !== null ) {
- $this->getResult()->addValue( array( 'query', 'searchinfo' ),
+ $apiResult->addValue( array( 'query', 'searchinfo' ),
'totalhits', $totalhits );
}
}
if ( isset( $searchInfo['suggestion'] ) && $matches->hasSuggestion() ) {
- $this->getResult()->addValue( array( 'query', 'searchinfo' ),
+ $apiResult->addValue( array( 'query', 'searchinfo' ),
'suggestion', $matches->getSuggestionQuery() );
}
@@ -110,7 +115,9 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
$titles = array();
$count = 0;
- while ( $result = $matches->next() ) {
+ $result = $matches->next();
+
+ while ( $result ) {
if ( ++ $count > $limit ) {
// We've reached the one extra which shows that there are additional items to be had. Stop here...
$this->setContinueEnumParameter( 'offset', $params['offset'] + $params['limit'] );
@@ -119,6 +126,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
// Silently skip broken and missing titles
if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
+ $result = $matches->next();
continue;
}
@@ -155,7 +163,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
if ( !is_null( $result->getSectionTitle() ) ) {
if ( isset( $prop['sectiontitle'] ) ) {
- $vals['sectiontitle'] = $result->getSectionTitle();
+ $vals['sectiontitle'] = $result->getSectionTitle()->getFragment();
}
if ( isset( $prop['sectionsnippet'] ) ) {
$vals['sectionsnippet'] = $result->getSectionSnippet();
@@ -166,7 +174,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
}
// Add item to results and see whether it fits
- $fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ),
+ $fit = $apiResult->addValue( array( 'query', $this->getModuleName() ),
null, $vals );
if ( !$fit ) {
$this->setContinueEnumParameter( 'offset', $params['offset'] + $count - 1 );
@@ -175,10 +183,12 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
} else {
$titles[] = $title;
}
+
+ $result = $matches->next();
}
if ( is_null( $resultPageSet ) ) {
- $this->getResult()->setIndexedTagName_internal( array(
+ $apiResult->setIndexedTagName_internal( array(
'query', $this->getModuleName()
), 'p' );
} else {
@@ -260,10 +270,10 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
' 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',
+ ' redirectsnippet - Adds a parsed snippet of the redirect title',
+ ' redirecttitle - Adds the title of the matching redirect',
+ ' sectionsnippet - Adds a parsed snippet of the matching section title',
+ ' sectiontitle - Adds the title of the matching section',
' hasrelated - Indicates whether a related search is available',
),
'redirects' => 'Include redirect pages in the search',
@@ -291,7 +301,11 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Search';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQuerySearch.php 76300 2010-11-08 12:23:24Z reedy $';
+ return __CLASS__ . ': $Id: ApiQuerySearch.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
index 379a4228..b6cedc6c 100644
--- a/includes/api/ApiQuerySiteinfo.php
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 25, 2006
*
@@ -85,6 +85,18 @@ class ApiQuerySiteinfo extends ApiQueryBase {
case 'languages':
$fit = $this->appendLanguages( $p );
break;
+ case 'skins':
+ $fit = $this->appendSkins( $p );
+ break;
+ case 'extensiontags':
+ $fit = $this->appendExtensionTags( $p );
+ break;
+ case 'functionhooks':
+ $fit = $this->appendFunctionHooks( $p );
+ break;
+ case 'showhooks':
+ $fit = $this->appendSubscribedHooks( $p );
+ break;
default:
ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" );
}
@@ -105,7 +117,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data = array();
$mainPage = Title::newMainPage();
$data['mainpage'] = $mainPage->getPrefixedText();
- $data['base'] = $mainPage->getFullUrl();
+ $data['base'] = wfExpandUrl( $mainPage->getFullUrl(), PROTO_CURRENT );
$data['sitename'] = $GLOBALS['wgSitename'];
$data['generator'] = "MediaWiki {$GLOBALS['wgVersion']}";
$data['phpversion'] = phpversion();
@@ -157,6 +169,12 @@ class ApiQuerySiteinfo extends ApiQueryBase {
$data['wikiid'] = wfWikiID();
$data['time'] = wfTimestamp( TS_ISO_8601, time() );
+ if ( $GLOBALS['wgMiserMode'] ) {
+ $data['misermode'] = '';
+ }
+
+ wfRunHooks( 'APIQuerySiteInfoGeneralInfo', array( $this, &$data ) );
+
return $this->getResult()->addValue( 'query', $property, $data );
}
@@ -212,8 +230,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
protected function appendSpecialPageAliases( $property ) {
global $wgContLang;
$data = array();
- foreach ( $wgContLang->getSpecialPageAliases() as $specialpage => $aliases )
- {
+ foreach ( $wgContLang->getSpecialPageAliases() as $specialpage => $aliases ) {
$arr = array( 'realname' => $specialpage, 'aliases' => $aliases );
$this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' );
$data[] = $arr;
@@ -241,7 +258,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
protected function appendInterwikiMap( $property, $filter ) {
$this->resetQueryParams();
$this->addTables( 'interwiki' );
- $this->addFields( array( 'iw_prefix', 'iw_local', 'iw_url' ) );
+ $this->addFields( array( 'iw_prefix', 'iw_local', 'iw_url', 'iw_wikiid', 'iw_api' ) );
if ( $filter === 'local' ) {
$this->addWhere( 'iw_local = 1' );
@@ -267,7 +284,13 @@ class ApiQuerySiteinfo extends ApiQueryBase {
if ( isset( $langNames[$row->iw_prefix] ) ) {
$val['language'] = $langNames[$row->iw_prefix];
}
- $val['url'] = $row->iw_url;
+ $val['url'] = wfExpandUrl( $row->iw_url, PROTO_CURRENT );
+ if( isset( $row->iw_wikiid ) ) {
+ $val['wikiid'] = $row->iw_wikiid;
+ }
+ if( isset( $row->iw_api ) ) {
+ $val['api'] = $row->iw_api;
+ }
$data[] = $val;
}
@@ -279,12 +302,12 @@ class ApiQuerySiteinfo extends ApiQueryBase {
protected function appendDbReplLagInfo( $property, $includeAll ) {
global $wgShowHostnames;
$data = array();
+ $lb = wfGetLB();
if ( $includeAll ) {
if ( !$wgShowHostnames ) {
$this->dieUsage( 'Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied' );
}
- $lb = wfGetLB();
$lags = $lb->getLagTimes();
foreach ( $lags as $i => $lag ) {
$data[] = array(
@@ -293,9 +316,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
);
}
} else {
- list( $host, $lag ) = wfGetLB()->getMaxLag();
+ list( $host, $lag, $index ) = $lb->getMaxLag();
$data[] = array(
- 'host' => $wgShowHostnames ? $host : '',
+ 'host' => $wgShowHostnames
+ ? $lb->getServerName( $index )
+ : '',
'lag' => intval( $lag )
);
}
@@ -324,46 +349,47 @@ class ApiQuerySiteinfo extends ApiQueryBase {
protected function appendUserGroups( $property, $numberInGroup ) {
global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
-
+
$data = array();
+ $result = $this->getResult();
foreach ( $wgGroupPermissions as $group => $permissions ) {
$arr = array(
'name' => $group,
'rights' => array_keys( $permissions, true ),
);
-
+
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] ) ) {
+
+ foreach ( $groupArr as $type => $rights ) {
+ if ( isset( $rights[$group] ) ) {
$arr[$type] = $rights[$group];
- $this->getResult()->setIndexedTagName( $arr[$type], 'group' );
+ $result->setIndexedTagName( $arr[$type], 'group' );
}
}
-
- $this->getResult()->setIndexedTagName( $arr['rights'], 'permission' );
+
+ $result->setIndexedTagName( $arr['rights'], 'permission' );
$data[] = $arr;
}
-
- $this->getResult()->setIndexedTagName( $data, 'group' );
- return $this->getResult()->addValue( 'query', $property, $data );
+
+ $result->setIndexedTagName( $data, 'group' );
+ return $result->addValue( 'query', $property, $data );
}
protected function appendFileExtensions( $property ) {
@@ -423,11 +449,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
return $this->getResult()->addValue( 'query', $property, $data );
}
-
protected function appendRightsInfo( $property ) {
global $wgRightsPage, $wgRightsUrl, $wgRightsText;
$title = Title::newFromText( $wgRightsPage );
- $url = $title ? $title->getFullURL() : $wgRightsUrl;
+ $url = $title ? wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ) : $wgRightsUrl;
$text = $wgRightsText;
if ( !$text && $title ) {
$text = $title->getPrefixedText();
@@ -452,6 +477,57 @@ class ApiQuerySiteinfo extends ApiQueryBase {
return $this->getResult()->addValue( 'query', $property, $data );
}
+ public function appendSkins( $property ) {
+ $data = array();
+ foreach ( Skin::getSkinNames() as $name => $displayName ) {
+ $skin = array( 'code' => $name );
+ ApiResult::setContent( $skin, $displayName );
+ $data[] = $skin;
+ }
+ $this->getResult()->setIndexedTagName( $data, 'skin' );
+ return $this->getResult()->addValue( 'query', $property, $data );
+ }
+
+ public function appendExtensionTags( $property ) {
+ global $wgParser;
+ $wgParser->firstCallInit();
+ $tags = array_map( array( $this, 'formatParserTags'), $wgParser->getTags() );
+ $this->getResult()->setIndexedTagName( $tags, 't' );
+ return $this->getResult()->addValue( 'query', $property, $tags );
+ }
+
+ public function appendFunctionHooks( $property ) {
+ global $wgParser;
+ $wgParser->firstCallInit();
+ $hooks = $wgParser->getFunctionHooks();
+ $this->getResult()->setIndexedTagName( $hooks, 'h' );
+ return $this->getResult()->addValue( 'query', $property, $hooks );
+ }
+
+ private function formatParserTags( $item ) {
+ return "<{$item}>";
+ }
+
+ public function appendSubscribedHooks( $property ) {
+ global $wgHooks;
+ $myWgHooks = $wgHooks;
+ ksort( $myWgHooks );
+
+ $data = array();
+ foreach ( $myWgHooks as $hook => $hooks ) {
+ $arr = array(
+ 'name' => $hook,
+ 'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $hooks ),
+ );
+
+ $this->getResult()->setIndexedTagName( $arr['subscribers'], 's' );
+ $data[] = $arr;
+ }
+
+ $this->getResult()->setIndexedTagName( $data, 'hook' );
+ return $this->getResult()->addValue( 'query', $property, $data );
+ }
+
public function getCacheMode( $params ) {
return 'public';
}
@@ -475,6 +551,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
'fileextensions',
'rightsinfo',
'languages',
+ 'skins',
+ 'extensiontags',
+ 'functionhooks',
+ 'showhooks',
)
),
'filteriw' => array(
@@ -505,6 +585,10 @@ class ApiQuerySiteinfo extends ApiQueryBase {
' 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',
+ ' skins - Returns a list of all enabled skins',
+ ' extensiontags - Returns a list of parser extension tags',
+ ' functionhooks - Returns a list of parser function hooks',
+ ' showhooks - Returns a list of all subscribed hooks (contents of $wgHooks)'
),
'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
'showalldb' => 'List all database servers, not just the one lagging the most',
@@ -530,7 +614,11 @@ class ApiQuerySiteinfo extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Meta#siteinfo_.2F_si';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 77192 2010-11-23 22:05:27Z btongminh $';
+ return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryStashImageInfo.php b/includes/api/ApiQueryStashImageInfo.php
index 769b3e9d..9a6e8530 100644
--- a/includes/api/ApiQueryStashImageInfo.php
+++ b/includes/api/ApiQueryStashImageInfo.php
@@ -38,58 +38,53 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
$prop = array_flip( $params['prop'] );
$scale = $this->getScale( $params );
-
+
$result = $this->getResult();
-
+
+ if ( !$params['filekey'] && !$params['sessionkey'] ) {
+ $this->dieUsage( "One of filekey or sessionkey must be supplied", 'nofilekey');
+ }
+
try {
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
-
- foreach ( $params['sessionkey'] as $sessionkey ) {
- $file = $stash->getFile( $sessionkey );
- $imageInfo = self::getInfo( $file, $prop, $result, $scale );
+
+ foreach ( $params['filekey'] as $filekey ) {
+ $file = $stash->getFile( $filekey );
+ $finalThumbParam = $this->mergeThumbParams( $file, $scale, $params['urlparam'] );
+ $imageInfo = ApiQueryImageInfo::getInfo( $file, $prop, $result, $finalThumbParam );
$result->addValue( array( 'query', $this->getModuleName() ), null, $imageInfo );
$result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $modulePrefix );
}
-
+ //TODO: update exception handling here to understand current getFile exceptions
} catch ( UploadStashNotAvailableException $e ) {
$this->dieUsage( "Session not available: " . $e->getMessage(), "nosession" );
} catch ( UploadStashFileNotFoundException $e ) {
$this->dieUsage( "File not found: " . $e->getMessage(), "invalidsessiondata" );
} catch ( UploadStashBadPathException $e ) {
$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',
- );
+ }
}
+ private $propertyFilter = array(
+ 'user', 'userid', 'comment', 'parsedcomment',
+ 'mediatype', 'archivename',
+ );
public function getAllowedParams() {
return array(
- 'sessionkey' => array(
+ 'filekey' => array(
ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_REQUIRED => true,
+ ApiBase::PARAM_DFLT => null
+ ),
+ 'sessionkey' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_DFLT => null
),
'prop' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_DFLT => 'timestamp|url',
- ApiBase::PARAM_TYPE => self::getPropertyNames()
+ ApiBase::PARAM_TYPE => self::getPropertyNames( $this->propertyFilter )
),
'urlwidth' => array(
ApiBase::PARAM_TYPE => 'integer',
@@ -98,32 +93,28 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
'urlheight' => array(
ApiBase::PARAM_TYPE => 'integer',
ApiBase::PARAM_DFLT => -1
- )
+ ),
+ 'urlparam' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_DFLT => '',
+ ),
);
}
/**
* Return the API documentation for the parameters.
- * @return {Array} parameter documentation.
+ * @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.',
+ 'prop' => self::getPropertyDescriptions( $this->propertyFilter ),
+ 'filekey' => 'Key that identifies a previous upload that was stashed temporarily.',
+ 'sessionkey' => 'Alias for filekey, for backward compatibility.',
'urlwidth' => "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
- 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth"
+ 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth",
+ 'urlparam' => array( "A handler specific parameter string. For example, pdf's ",
+ "might use 'page15-100px'. {$p}urlwidth must be used and be consistent with {$p}urlparam" ),
);
}
@@ -131,21 +122,15 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
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',
+ 'api.php?action=query&prop=stashimageinfo&siifilekey=124sd34rsdf567',
+ 'api.php?action=query&prop=stashimageinfo&siifilekey=b34edoe3|bceffd4&siiurlwidth=120&siiprop=url',
);
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryStashImageInfo.php 81000 2011-01-25 22:49:34Z catrope $';
+ return __CLASS__ . ': $Id: ApiQueryStashImageInfo.php 92459 2011-07-18 19:31:38Z raindrift $';
}
}
diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php
index e88ec9b5..14ca14b7 100644
--- a/includes/api/ApiQueryTags.php
+++ b/includes/api/ApiQueryTags.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Jul 9, 2009
*
@@ -36,7 +36,12 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*/
class ApiQueryTags extends ApiQueryBase {
- private $limit, $result;
+ /**
+ * @var ApiResult
+ */
+ private $result;
+
+ private $limit;
private $fld_displayname = false, $fld_description = false,
$fld_hitcount = false;
@@ -59,9 +64,7 @@ class ApiQueryTags extends ApiQueryBase {
$this->addTables( 'change_tag' );
$this->addFields( 'ct_tag' );
- if ( $this->fld_hitcount ) {
- $this->addFields( 'count(*) AS hitcount' );
- }
+ $this->addFieldsIf( 'count(*) AS hitcount', $this->fld_hitcount );
$this->addOption( 'LIMIT', $this->limit + 1 );
$this->addOption( 'GROUP BY', 'ct_tag' );
@@ -110,9 +113,8 @@ class ApiQueryTags extends ApiQueryBase {
}
if ( $this->fld_description ) {
- $msg = wfMsg( "tag-$tagName-description" );
- $msg = wfEmptyMsg( "tag-$tagName-description", $msg ) ? '' : $msg;
- $tag['description'] = $msg;
+ $msg = wfMessage( "tag-$tagName-description" );
+ $tag['description'] = $msg->exists() ? $msg->text() : '';
}
if ( $this->fld_hitcount ) {
@@ -183,6 +185,6 @@ class ApiQueryTags extends ApiQueryBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryTags.php 73858 2010-09-28 01:21:15Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryTags.php 90542 2011-06-21 20:05:00Z ialex $';
}
}
diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php
index 5d63fa60..e958c729 100644
--- a/includes/api/ApiQueryUserContributions.php
+++ b/includes/api/ApiQueryUserContributions.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 16, 2006
*
@@ -81,6 +81,7 @@ class ApiQueryContributions extends ApiQueryBase {
$this->prefixMode = false;
$this->multiUserMode = ( count( $this->params['user'] ) > 1 );
}
+
$this->prepareQuery();
// Do the actual query.
@@ -120,6 +121,8 @@ class ApiQueryContributions extends ApiQueryBase {
/**
* Validate the 'user' parameter and set the value to compare
* against `revision`.`rev_user_text`
+ *
+ * @param $user string
*/
private function prepareUsername( $user ) {
if ( !is_null( $user ) && $user !== '' ) {
@@ -179,7 +182,7 @@ class ApiQueryContributions extends ApiQueryBase {
if ( $this->multiUserMode ) {
$this->addWhereRange( 'rev_user_text', $this->params['dir'], null, null );
}
- $this->addWhereRange( 'rev_timestamp',
+ $this->addTimestampWhereRange( 'rev_timestamp',
$this->params['dir'], $this->params['start'], $this->params['end'] );
$this->addWhereFld( 'page_namespace', $this->params['namespace'] );
@@ -188,7 +191,7 @@ class ApiQueryContributions extends ApiQueryBase {
$show = array_flip( $show );
if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
|| ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) ) ) {
- $this->dieUsageMsg( array( 'show' ) );
+ $this->dieUsageMsg( 'show' );
}
$this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) );
@@ -246,8 +249,7 @@ class ApiQueryContributions extends ApiQueryBase {
// $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( array( 'rev_minor_edit', 'rev_parent_id' ), $this->fld_flags );
$this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
if ( $this->fld_tags ) {
@@ -264,6 +266,10 @@ class ApiQueryContributions extends ApiQueryBase {
$index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id';
}
+ if ( $this->params['toponly'] ) {
+ $this->addWhere( 'rev_id = page_latest' );
+ }
+
$this->addOption( 'USE INDEX', $index );
}
@@ -409,6 +415,7 @@ class ApiQueryContributions extends ApiQueryBase {
)
),
'tag' => null,
+ 'toponly' => false,
);
}
@@ -422,12 +429,12 @@ class ApiQueryContributions extends ApiQueryBase {
'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)',
+ 'dir' => $this->getDirectionDescription( $p ),
'namespace' => 'Only list contributions in these namespaces',
'prop' => array(
'Include additional pieces of information',
- ' ids - Adds the page id and revision id',
- ' title - Adds the title and namespace id of the page',
+ ' 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',
@@ -439,6 +446,7 @@ class ApiQueryContributions extends ApiQueryBase {
'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',
+ 'toponly' => 'Only list changes which are the latest revision',
);
}
@@ -462,7 +470,11 @@ class ApiQueryContributions extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Usercontribs';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryUserContributions.php 75096 2010-10-20 18:50:33Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryUserContributions.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php
index ec7b74b3..8a8ce118 100644
--- a/includes/api/ApiQueryUserInfo.php
+++ b/includes/api/ApiQueryUserInfo.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on July 30, 2007
*
@@ -55,7 +55,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
protected function getCurrentUserInfo() {
- global $wgUser, $wgRequest;
+ global $wgUser, $wgRequest, $wgHiddenPrefs;
$result = $this->getResult();
$vals = array();
$vals['id'] = intval( $wgUser->getId() );
@@ -83,6 +83,11 @@ class ApiQueryUserInfo extends ApiQueryBase {
$result->setIndexedTagName( $vals['groups'], 'g' ); // even if empty
}
+ if ( isset( $this->prop['implicitgroups'] ) ) {
+ $vals['implicitgroups'] = ApiQueryUsers::getAutoGroups( $wgUser );
+ $result->setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty
+ }
+
if ( isset( $this->prop['rights'] ) ) {
// User::getRights() may return duplicate values, strip them
$vals['rights'] = array_values( array_unique( $wgUser->getRights() ) );
@@ -101,12 +106,10 @@ class ApiQueryUserInfo extends ApiQueryBase {
$vals['options'] = $wgUser->getOptions();
}
- if (
- isset( $this->prop['preferencestoken'] ) &&
+ if ( isset( $this->prop['preferencestoken'] ) &&
is_null( $this->getMain()->getRequest()->getVal( 'callback' ) )
- )
- {
- $vals['preferencestoken'] = $wgUser->editToken();
+ ) {
+ $vals['preferencestoken'] = $wgUser->editToken( '', $this->getMain()->getRequest() );
}
if ( isset( $this->prop['editcount'] ) ) {
@@ -117,6 +120,10 @@ class ApiQueryUserInfo extends ApiQueryBase {
$vals['ratelimits'] = $this->getRateLimits();
}
+ if ( isset( $this->prop['realname'] ) && !in_array( 'realname', $wgHiddenPrefs ) ) {
+ $vals['realname'] = $wgUser->getRealName();
+ }
+
if ( isset( $this->prop['email'] ) ) {
$vals['email'] = $wgUser->getEmail();
$auth = $wgUser->getEmailAuthenticationTimestamp();
@@ -125,6 +132,13 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
}
+ if ( isset( $this->prop['registrationdate'] ) ) {
+ $regDate = $wgUser->getRegistration();
+ if ( $regDate !== false ) {
+ $vals['registrationdate'] = wfTimestamp( TS_ISO_8601, $regDate );
+ }
+ }
+
if ( isset( $this->prop['acceptlang'] ) ) {
$langs = $wgRequest->getAcceptLang();
$acceptLang = array();
@@ -182,6 +196,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
'blockinfo',
'hasmsg',
'groups',
+ 'implicitgroups',
'rights',
'changeablegroups',
'options',
@@ -189,7 +204,9 @@ class ApiQueryUserInfo extends ApiQueryBase {
'editcount',
'ratelimits',
'email',
+ 'realname',
'acceptlang',
+ 'registrationdate'
)
)
);
@@ -202,13 +219,17 @@ class ApiQueryUserInfo extends ApiQueryBase {
' blockinfo - Tags if the current user is blocked, by whom, and for what reason',
' hasmsg - Adds a tag "message" if the current user has pending messages',
' groups - Lists all the groups the current user belongs to',
+ ' implicitgroups - Lists all the groups the current user is automatically a member of',
' rights - Lists all the rights the current user has',
' changeablegroups - Lists the groups the current user can add to and remove from',
' options - Lists all preferences the current user has set',
+ ' preferencestoken - Get a token to change current user\'s preferences',
' editcount - Adds the current user\'s edit count',
' ratelimits - Lists all rate limits applying to the current user',
+ ' realname - Adds the user\'s real name',
' email - Adds the user\'s email address and email authentication date',
' acceptlang - Echoes the Accept-Language header sent by the client in a structured format',
+ ' registrationdate - Adds the user\'s registration date',
)
);
}
@@ -224,7 +245,11 @@ class ApiQueryUserInfo extends ApiQueryBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Meta#userinfo_.2F_ui';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryUserInfo.php 75937 2010-11-03 17:01:21Z reedy $';
+ return __CLASS__ . ': $Id: ApiQueryUserInfo.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php
index 2619d200..6eee1331 100644
--- a/includes/api/ApiQueryUsers.php
+++ b/includes/api/ApiQueryUsers.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on July 30, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,7 +34,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*
* @ingroup API
*/
- class ApiQueryUsers extends ApiQueryBase {
+class ApiQueryUsers extends ApiQueryBase {
private $tokenFunctions, $prop;
@@ -66,6 +66,10 @@ if ( !defined( 'MEDIAWIKI' ) ) {
return $this->tokenFunctions;
}
+ /**
+ * @param $user User
+ * @return String
+ */
public static function getUserrightsToken( $user ) {
global $wgUser;
// Since the permissions check for userrights is non-trivial,
@@ -104,14 +108,16 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
}
+ $result = $this->getResult();
+
if ( count( $goodNames ) ) {
- $this->addTables( 'user', 'u1' );
- $this->addFields( 'u1.*' );
- $this->addWhereFld( 'u1.user_name', $goodNames );
+ $this->addTables( 'user' );
+ $this->addFields( '*' );
+ $this->addWhereFld( 'user_name', $goodNames );
- if ( isset( $this->prop['groups'] ) ) {
+ if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) {
$this->addTables( 'user_groups' );
- $this->addJoinConds( array( 'user_groups' => array( 'LEFT JOIN', 'ug_user=u1.user_id' ) ) );
+ $this->addJoinConds( array( 'user_groups' => array( 'LEFT JOIN', 'ug_user=user_id' ) ) );
$this->addFields( 'ug_group' );
}
@@ -119,9 +125,12 @@ if ( !defined( 'MEDIAWIKI' ) ) {
$data = array();
$res = $this->select( __METHOD__ );
+
foreach ( $res as $row ) {
$user = User::newFromRow( $row );
$name = $user->getName();
+
+ $data[$name]['userid'] = $user->getId();
$data[$name]['name'] = $name;
if ( isset( $this->prop['editcount'] ) ) {
@@ -132,19 +141,30 @@ if ( !defined( 'MEDIAWIKI' ) ) {
$data[$name]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() );
}
- 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'][] = $row->ug_group;
+ if ( isset( $this->prop['groups'] ) ) {
+ if ( !isset( $data[$name]['groups'] ) ) {
+ $data[$name]['groups'] = self::getAutoGroups( $user );
+ }
+
+ if ( !is_null( $row->ug_group ) ) {
+ // This row contains only one group, others will be added from other rows
+ $data[$name]['groups'][] = $row->ug_group;
+ }
+ }
+
+ if ( isset( $this->prop['implicitgroups'] ) && !isset( $data[$name]['implicitgroups'] ) ) {
+ $data[$name]['implicitgroups'] = self::getAutoGroups( $user );
}
- if ( isset( $this->prop['rights'] ) && !is_null( $row->ug_group ) ) {
+ if ( isset( $this->prop['rights'] ) ) {
if ( !isset( $data[$name]['rights'] ) ) {
- $data[$name]['rights'] = User::getGroupPermissions( User::getImplicitGroups() );
+ $data[$name]['rights'] = User::getGroupPermissions( $user->getAutomaticGroups() );
}
- $data[$name]['rights'] = array_unique( array_merge( $data[$name]['rights'],
- User::getGroupPermissions( array( $row->ug_group ) ) ) );
- $result->setIndexedTagName( $data[$name]['rights'], 'r' );
+ if ( !is_null( $row->ug_group ) ) {
+ $data[$name]['rights'] = array_unique( array_merge( $data[$name]['rights'],
+ User::getGroupPermissions( array( $row->ug_group ) ) ) );
+ }
}
if ( $row->ipb_deleted ) {
$data[$name]['hidden'] = '';
@@ -180,6 +200,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
}
}
+
// Second pass: add result data to $retval
foreach ( $goodNames as $u ) {
if ( !isset( $data[$u] ) ) {
@@ -207,13 +228,16 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
} else {
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' );
+ $result->setIndexedTagName( $data[$u]['groups'], 'g' );
+ }
+ if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) {
+ $result->setIndexedTagName( $data[$u]['implicitgroups'], 'g' );
+ }
+ if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) {
+ $result->setIndexedTagName( $data[$u]['rights'], 'r' );
}
}
+
$fit = $result->addValue( array( 'query', $this->getModuleName() ),
null, $data[$u] );
if ( !$fit ) {
@@ -223,15 +247,17 @@ if ( !defined( 'MEDIAWIKI' ) ) {
}
$done[] = $u;
}
- return $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' );
+ return $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' );
}
/**
- * Gets all the groups that a user is automatically a member of
+ * Gets all the groups that a user is automatically a member of (implicit groups)
+ * @param $user User
* @return array
*/
public static function getAutoGroups( $user ) {
- $groups = array( '*' );
+ $groups = array();
+ $groups[] = '*';
if ( !$user->isAnon() ) {
$groups[] = 'user';
@@ -256,6 +282,8 @@ if ( !defined( 'MEDIAWIKI' ) ) {
ApiBase::PARAM_TYPE => array(
'blockinfo',
'groups',
+ 'implicitgroups',
+ 'rights',
'editcount',
'registration',
'emailable',
@@ -276,13 +304,14 @@ if ( !defined( 'MEDIAWIKI' ) ) {
return array(
'prop' => array(
'What pieces of information to include',
- ' blockinfo - Tags if the user is blocked, by whom, and for what reason',
- ' groups - Lists all the groups the user(s) belongs to',
- ' 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"',
+ ' blockinfo - Tags if the user is blocked, by whom, and for what reason',
+ ' groups - Lists all the groups the user(s) belongs to',
+ ' implicitgroups - Lists all the groups a user is automatically a member of',
+ ' rights - Lists all the rights the user(s) has',
+ ' editcount - Adds the user\'s edit count',
+ ' registration - Adds the user\'s registration timestamp',
+ ' emailable - Tags if the user can and wants to receive 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',
@@ -297,7 +326,11 @@ if ( !defined( 'MEDIAWIKI' ) ) {
return 'api.php?action=query&list=users&ususers=brion|TimStarling&usprop=groups|editcount|gender';
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Users';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryUsers.php 85354 2011-04-04 18:25:31Z demon $';
+ return __CLASS__ . ': $Id: ApiQueryUsers.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php
index 784f89c0..a5eb23eb 100644
--- a/includes/api/ApiQueryWatchlist.php
+++ b/includes/api/ApiQueryWatchlist.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 25, 2006
*
@@ -51,8 +51,12 @@ 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_userid = false;
+ $fld_notificationtimestamp = false, $fld_userid = false, $fld_loginfo = false;
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
$this->selectNamedDB( 'watchlist', DB_SLAVE, 'watchlist' );
@@ -74,6 +78,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->fld_sizes = isset( $prop['sizes'] );
$this->fld_patrol = isset( $prop['patrol'] );
$this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] );
+ $this->fld_loginfo = isset( $prop['loginfo'] );
if ( $this->fld_patrol ) {
if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
@@ -85,25 +90,25 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->addFields( array(
'rc_namespace',
'rc_title',
- 'rc_timestamp'
+ 'rc_timestamp',
+ 'rc_type',
) );
if ( is_null( $resultPageSet ) ) {
$this->addFields( array(
'rc_cur_id',
- 'rc_this_oldid'
+ 'rc_this_oldid',
+ 'rc_last_oldid',
) );
- $this->addFieldsIf( 'rc_new', $this->fld_flags );
- $this->addFieldsIf( 'rc_minor', $this->fld_flags );
- $this->addFieldsIf( 'rc_bot', $this->fld_flags );
+ $this->addFieldsIf( array( 'rc_new', 'rc_minor', 'rc_bot' ), $this->fld_flags );
$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 );
- $this->addFieldsIf( 'rc_old_len', $this->fld_sizes );
- $this->addFieldsIf( 'rc_new_len', $this->fld_sizes );
+ $this->addFieldsIf( array( 'rc_old_len', 'rc_new_len' ), $this->fld_sizes );
$this->addFieldsIf( 'wl_notificationtimestamp', $this->fld_notificationtimestamp );
+ $this->addFieldsIf( array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ), $this->fld_loginfo );
} elseif ( $params['allrev'] ) {
$this->addFields( 'rc_this_oldid' );
} else {
@@ -111,27 +116,33 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
$this->addTables( array(
+ 'recentchanges',
'watchlist',
- 'page',
- 'recentchanges'
) );
$userId = $user->getId();
+ $this->addJoinConds( array( 'watchlist' => array('INNER JOIN',
+ array(
+ 'wl_user' => $userId,
+ 'wl_namespace=rc_namespace',
+ 'wl_title=rc_title'
+ ) ) ) );
+
$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'],
- $db->timestamp( $params['start'] ),
- $db->timestamp( $params['end'] ) );
+ $this->addTimestampWhereRange( 'rc_timestamp', $params['dir'],
+ $params['start'], $params['end'] );
$this->addWhereFld( 'wl_namespace', $params['namespace'] );
- $this->addWhereIf( 'rc_this_oldid=page_latest', !$params['allrev'] );
+
+ if ( !$params['allrev'] ) {
+ $this->addTables( 'page' );
+ $this->addJoinConds( array( 'page' => array( 'LEFT JOIN','rc_cur_id=page_id' ) ) );
+ $this->addWhere( 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG );
+ }
if ( !is_null( $params['show'] ) ) {
$show = array_flip( $params['show'] );
@@ -143,7 +154,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
|| ( isset ( $show['patrolled'] ) && isset ( $show['!patrolled'] ) )
)
{
- $this->dieUsageMsg( array( 'show' ) );
+ $this->dieUsageMsg( 'show' );
}
// Check permissions.
@@ -172,11 +183,9 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
$this->addWhereFld( 'rc_user_text', $params['user'] );
}
if ( !is_null( $params['excludeuser'] ) ) {
- $this->addWhere( 'rc_user_text != ' . $this->getDB()->addQuotes( $params['excludeuser'] ) );
+ $this->addWhere( 'rc_user_text != ' . $db->addQuotes( $params['excludeuser'] ) );
}
-
-
// 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' );
@@ -225,6 +234,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
if ( $this->fld_ids ) {
$vals['pageid'] = intval( $row->rc_cur_id );
$vals['revid'] = intval( $row->rc_this_oldid );
+ $vals['old_revid'] = intval( $row->rc_last_oldid );
}
$title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
@@ -240,7 +250,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
if ( $this->fld_userid ) {
- $vals['user'] = $row->rc_user;
+ $vals['user'] = $row->rc_user;
}
if ( !$row->rc_user ) {
@@ -284,8 +294,21 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) {
- global $wgUser;
- $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->rc_comment, $title );
+ $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title );
+ }
+
+ if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
+ $vals['logid'] = intval( $row->rc_logid );
+ $vals['logtype'] = $row->rc_log_type;
+ $vals['logaction'] = $row->rc_log_action;
+ ApiQueryLogEvents::addLogParams(
+ $this->getResult(),
+ $vals,
+ $row->rc_params,
+ $row->rc_log_type,
+ $row->rc_log_action,
+ $row->rc_timestamp
+ );
}
return $vals;
@@ -338,7 +361,8 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'timestamp',
'patrol',
'sizes',
- 'notificationtimestamp'
+ 'notificationtimestamp',
+ 'loginfo',
)
),
'show' => array(
@@ -364,6 +388,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
}
public function getParamDescription() {
+ $p = $this->getModulePrefix();
return array(
'allrev' => 'Include multiple revisions of the same page within given timeframe',
'start' => 'The timestamp to start enumerating from',
@@ -371,7 +396,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
'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',
+ 'dir' => $this->getDirectionDescription( $p ),
'limit' => 'How many total results to return per request',
'prop' => array(
'Which additional items to get (non-generator mode only).',
@@ -384,12 +409,13 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
' 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',
+ ' sizes - Adds the old and new lengths of the page',
' notificationtimestamp - Adds timestamp of when the user was last notified about the edit',
+ ' loginfo - Adds log information where appropriate',
),
'show' => array(
'Show only items that meet this criteria.',
- "For example, to see only minor edits done by logged-in users, set {$this->getModulePrefix()}show=minor|!anon"
+ "For example, to see only minor edits done by logged-in users, set {$p}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'
@@ -423,7 +449,11 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Watchlist';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryWatchlist.php 85435 2011-04-05 14:00:08Z demon $';
+ return __CLASS__ . ': $Id: ApiQueryWatchlist.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php
index 0e5617e3..b008eab2 100644
--- a/includes/api/ApiQueryWatchlistRaw.php
+++ b/includes/api/ApiQueryWatchlistRaw.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Oct 4, 2008
*
- * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2008 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -49,6 +49,10 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
$this->run( $resultPageSet );
}
+ /**
+ * @param $resultPageSet ApiPageSet
+ * @return void
+ */
private function run( $resultPageSet = null ) {
$this->selectNamedDB( 'watchlist', DB_SLAVE, 'watchlist' );
@@ -59,7 +63,7 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
$prop = array_flip( (array)$params['prop'] );
$show = array_flip( (array)$params['show'] );
if ( isset( $show['changed'] ) && isset( $show['!changed'] ) ) {
- $this->dieUsageMsg( array( 'show' ) );
+ $this->dieUsageMsg( 'show' );
}
$this->addTables( 'watchlist' );
@@ -201,6 +205,6 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 70647 2010-08-07 19:59:42Z ialex $';
+ return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 88416 2011-05-19 17:51:16Z hashar $';
}
-} \ No newline at end of file
+}
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
index 9d42a58e..f7ea0045 100644
--- a/includes/api/ApiResult.php
+++ b/includes/api/ApiResult.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 4, 2006
*
@@ -249,6 +249,12 @@ class ApiResult extends ApiBase {
* 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
+ *
+ * @param $path
+ * @param $name string
+ * @param $value mixed
+ * @param $overwrite bool
+ *
* @return bool True if $value fits in the result, false if not
*/
public function addValue( $path, $name, $value, $overwrite = false ) {
@@ -257,6 +263,9 @@ class ApiResult extends ApiBase {
if ( $this->mCheckingSize ) {
$newsize = $this->mSize + self::size( $value );
if ( $newsize > $wgAPIMaxResultSize ) {
+ $this->setWarning(
+ "This result was truncated because it would otherwise be larger than the " .
+ "limit of {$wgAPIMaxResultSize} bytes" );
return false;
}
$this->mSize = $newsize;
@@ -327,6 +336,8 @@ class ApiResult extends ApiBase {
/**
* Callback function for cleanUpUTF8()
+ *
+ * @param $s string
*/
private static function cleanUp_helper( &$s ) {
if ( !is_string( $s ) ) {
@@ -336,11 +347,31 @@ class ApiResult extends ApiBase {
$s = $wgContLang->normalize( $s );
}
+ /**
+ * Converts a Status object to an array suitable for addValue
+ * @param Status $status
+ * @param string $errorType
+ * @return array
+ */
+ public function convertStatusToArray( $status, $errorType = 'error' ) {
+ if ( $status->isGood() ) {
+ return array();
+ }
+
+ $result = array();
+ foreach ( $status->getErrorsByType( $errorType ) as $error ) {
+ $this->setIndexedTagName( $error['params'], 'param' );
+ $result[] = $error;
+ }
+ $this->setIndexedTagName( $result, $errorType );
+ return $result;
+ }
+
public function execute() {
ApiBase::dieDebug( __METHOD__, 'execute() is not supported on Result object' );
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiResult.php 74230 2010-10-03 19:07:11Z reedy $';
+ return __CLASS__ . ': $Id: ApiResult.php 91144 2011-06-29 23:46:39Z reedy $';
}
}
diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php
index e31bfed8..a149fcaf 100644
--- a/includes/api/ApiRollback.php
+++ b/includes/api/ApiRollback.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Jun 20, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,7 +38,15 @@ class ApiRollback extends ApiBase {
parent::__construct( $main, $action );
}
- private $mTitleObj = null, $mUser = null;
+ /**
+ * @var Title
+ */
+ private $mTitleObj = null;
+
+ /**
+ * @var User
+ */
+ private $mUser = null;
public function execute() {
$params = $this->extractRequestParams();
@@ -47,7 +55,7 @@ class ApiRollback extends ApiBase {
$titleObj = $this->getTitle();
$articleObj = new Article( $titleObj );
$summary = ( isset( $params['summary'] ) ? $params['summary'] : '' );
- $details = null;
+ $details = array();
$retval = $articleObj->doRollback( $this->getUser(), $summary, $params['token'], $params['markbot'], $details );
if ( $retval ) {
@@ -170,7 +178,7 @@ class ApiRollback extends ApiBase {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
if ( !$this->mTitleObj->exists() ) {
- $this->dieUsageMsg( array( 'notanarticle' ) );
+ $this->dieUsageMsg( 'notanarticle' );
}
return $this->mTitleObj;
@@ -183,7 +191,11 @@ class ApiRollback extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Rollback';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiRollback.php 75602 2010-10-28 00:04:48Z reedy $';
+ return __CLASS__ . ': $Id: ApiRollback.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiRsd.php b/includes/api/ApiRsd.php
index 7bc4722c..f9a4d285 100644
--- a/includes/api/ApiRsd.php
+++ b/includes/api/ApiRsd.php
@@ -47,7 +47,8 @@ class ApiRsd extends ApiBase {
$service = array( 'apis' => $this->formatRsdApiList() );
ApiResult::setContent( $service, 'MediaWiki', 'engineName' );
- ApiResult::setContent( $service, 'http://www.mediawiki.org/', 'engineLink' );
+ ApiResult::setContent( $service, 'https://www.mediawiki.org/', 'engineLink' );
+ ApiResult::setContent( $service, Title::newMainPage()->getCanonicalUrl(), 'homePageLink' );
$result->setIndexedTagName( $service['apis'], 'api' );
@@ -67,7 +68,7 @@ class ApiRsd extends ApiBase {
}
public function getDescription() {
- return 'Export an RSD schema';
+ return 'Export an RSD (Really Simple Discovery) schema';
}
protected function getExamples() {
@@ -97,10 +98,10 @@ class ApiRsd extends ApiBase {
$apis = array(
'MediaWiki' => array(
// The API link is required for all RSD API entries.
- 'apiLink' => wfExpandUrl( wfScript( 'api' ) ),
+ 'apiLink' => wfExpandUrl( wfScript( 'api' ), PROTO_CURRENT ),
// Docs link is optional, but recommended.
- 'docs' => 'http://mediawiki.org/wiki/API',
+ 'docs' => 'https://www.mediawiki.org/wiki/API',
// Some APIs may need a blog ID, but it may be left blank.
'blogID' => '',
@@ -160,7 +161,7 @@ class ApiRsd extends ApiBase {
}
public function getVersion() {
- return __CLASS__ . ': $Id: ApiRsd.php 76195 2010-11-06 15:57:15Z btongminh $';
+ return __CLASS__ . ': $Id: ApiRsd.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
@@ -169,12 +170,12 @@ class ApiFormatXmlRsd extends ApiFormatXml {
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 $';
+ return __CLASS__ . ': $Id: ApiRsd.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php
index 4f6e4fb7..51ee0241 100644
--- a/includes/api/ApiUnblock.php
+++ b/includes/api/ApiUnblock.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Sep 7, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -49,40 +49,42 @@ class ApiUnblock extends ApiBase {
$params = $this->extractRequestParams();
if ( $params['gettoken'] ) {
- $res['unblocktoken'] = $wgUser->editToken();
+ $res['unblocktoken'] = $wgUser->editToken( '', $this->getMain()->getRequest() );
$this->getResult()->addValue( null, $this->getModuleName(), $res );
return;
}
if ( is_null( $params['id'] ) && is_null( $params['user'] ) ) {
- $this->dieUsageMsg( array( 'unblock-notarget' ) );
+ $this->dieUsageMsg( 'unblock-notarget' );
}
if ( !is_null( $params['id'] ) && !is_null( $params['user'] ) ) {
- $this->dieUsageMsg( array( 'unblock-idanduser' ) );
+ $this->dieUsageMsg( 'unblock-idanduser' );
}
if ( !$wgUser->isAllowed( 'block' ) ) {
- $this->dieUsageMsg( array( 'cantunblock' ) );
+ $this->dieUsageMsg( 'cantunblock' );
}
# bug 15810: blocked admins should have limited access here
if ( $wgUser->isBlocked() ) {
- $status = IPBlockForm::checkUnblockSelf( $params['user'] );
+ $status = SpecialBlock::checkUnblockSelf( $params['user'] );
if ( $status !== true ) {
- $this->dieUsageMsg( array( $status ) );
+ $this->dieUsageMsg( $status );
}
}
- $id = $params['id'];
- $user = $params['user'];
- $reason = ( is_null( $params['reason'] ) ? '' : $params['reason'] );
- $retval = IPUnblockForm::doUnblock( $id, $user, $reason, $range );
- if ( $retval ) {
- $this->dieUsageMsg( $retval );
+ $data = array(
+ 'Target' => is_null( $params['id'] ) ? $params['user'] : "#{$params['id']}",
+ 'Reason' => is_null( $params['reason'] ) ? '' : $params['reason']
+ );
+ $block = Block::newFromTarget( $data['Target'] );
+ $retval = SpecialUnblock::processUnblock( $data );
+ if ( $retval !== true ) {
+ $this->dieUsageMsg( $retval[0] );
}
- $res['id'] = intval( $id );
- $res['user'] = $user;
- $res['reason'] = $reason;
+ $res['id'] = $block->getId();
+ $res['user'] = $block->getType() == Block::TYPE_AUTO ? '' : $block->getTarget();
+ $res['reason'] = $params['reason'];
$this->getResult()->addValue( null, $this->getModuleName(), $res );
}
@@ -96,7 +98,9 @@ class ApiUnblock extends ApiBase {
public function getAllowedParams() {
return array(
- 'id' => null,
+ 'id' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
'user' => null,
'token' => null,
'gettoken' => false,
@@ -144,7 +148,11 @@ class ApiUnblock extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Block';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiUnblock.php 74098 2010-10-01 20:12:50Z reedy $';
+ return __CLASS__ . ': $Id: ApiUnblock.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php
index 3c7d91a5..c2aa2a00 100644
--- a/includes/api/ApiUndelete.php
+++ b/includes/api/ApiUndelete.php
@@ -1,10 +1,10 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Jul 3, 2007
*
- * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2007 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -43,11 +43,11 @@ class ApiUndelete extends ApiBase {
$params = $this->extractRequestParams();
if ( !$wgUser->isAllowed( 'undelete' ) ) {
- $this->dieUsageMsg( array( 'permdenied-undelete' ) );
+ $this->dieUsageMsg( 'permdenied-undelete' );
}
if ( $wgUser->isBlocked() ) {
- $this->dieUsageMsg( array( 'blockedtext' ) );
+ $this->dieUsageMsg( 'blockedtext' );
}
$titleObj = Title::newFromText( $params['title'] );
@@ -69,7 +69,7 @@ class ApiUndelete extends ApiBase {
$pa = new PageArchive( $titleObj );
$retval = $pa->undelete( ( isset( $params['timestamps'] ) ? $params['timestamps'] : array() ), $params['reason'] );
if ( !is_array( $retval ) ) {
- $this->dieUsageMsg( array( 'cannotundelete' ) );
+ $this->dieUsageMsg( 'cannotundelete' );
}
if ( $retval[1] ) {
@@ -103,7 +103,8 @@ class ApiUndelete extends ApiBase {
'token' => null,
'reason' => '',
'timestamps' => array(
- ApiBase::PARAM_ISMULTI => true
+ ApiBase::PARAM_TYPE => 'timestamp',
+ ApiBase::PARAM_ISMULTI => true,
),
'watchlist' => array(
ApiBase::PARAM_DFLT => 'preferences',
@@ -158,7 +159,11 @@ class ApiUndelete extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Undelete';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiUndelete.php 74098 2010-10-01 20:12:50Z reedy $';
+ return __CLASS__ . ': $Id: ApiUndelete.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php
index e7d7b939..1dab0310 100644
--- a/includes/api/ApiUpload.php
+++ b/includes/api/ApiUpload.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Aug 21, 2008
*
@@ -33,7 +33,12 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* @ingroup API
*/
class ApiUpload extends ApiBase {
+
+ /**
+ * @var UploadBase
+ */
protected $mUpload = null;
+
protected $mParams;
public function __construct( $main, $action ) {
@@ -45,7 +50,7 @@ class ApiUpload extends ApiBase {
// Check whether upload is enabled
if ( !UploadBase::isEnabled() ) {
- $this->dieUsageMsg( array( 'uploaddisabled' ) );
+ $this->dieUsageMsg( 'uploaddisabled' );
}
// Parameter handling
@@ -54,6 +59,11 @@ class ApiUpload extends ApiBase {
// Add the uploaded file to the params array
$this->mParams['file'] = $request->getFileName( 'file' );
+ // Copy the session key to the file key, for backward compatibility.
+ if( !$this->mParams['filekey'] && $this->mParams['sessionkey'] ) {
+ $this->mParams['filekey'] = $this->mParams['sessionkey'];
+ }
+
// Select an upload module
if ( !$this->selectUploadModule() ) {
// This is not a true upload, but a status request or similar
@@ -77,34 +87,40 @@ class ApiUpload extends ApiBase {
// 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' ) );
+
+ // Check if the user has the rights to modify or overwrite the requested title
+ // (This check is irrelevant if stashing is already requested, since the errors
+ // can always be fixed by changing the title)
+ if ( ! $this->mParams['stash'] ) {
+ $permErrors = $this->mUpload->verifyTitlePermissions( $wgUser );
+ if ( $permErrors !== true ) {
+ $this->dieRecoverableError( $permErrors[0], 'filename' );
+ }
}
// Prepare the API result
$result = array();
-
+
$warnings = $this->getApiWarnings();
- if ( $warnings ) {
+ 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 ) {
+ try {
+ $result['filekey'] = $this->performStash();
+ $result['sessionkey'] = $result['filekey']; // backwards compatibility
+ } catch ( MWException $e ) {
$result['warnings']['stashfailed'] = $e->getMessage();
}
- } elseif ( $this->mParams['stash'] ) {
+ } 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 ) {
+ $result['result'] = 'Success';
+ $result['filekey'] = $this->performStash();
+ $result['sessionkey'] = $result['filekey']; // backwards compatibility
+ } catch ( MWException $e ) {
$this->dieUsage( $e->getMessage(), 'stashfailed' );
}
} else {
@@ -113,52 +129,76 @@ class ApiUpload extends ApiBase {
$result = $this->performUpload();
}
- if ( $result['result'] === 'Success' ) {
+ 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
+ * Stash the file and return the file key
* Also re-raises exceptions with slightly more informative message strings (useful for API)
* @throws MWException
- * @return {String} session key
+ * @return String file key
*/
function performStash() {
try {
- $sessionKey = $this->mUpload->stashSessionFile()->getSessionKey();
+ $fileKey = $this->mUpload->stashFile()->getFileKey();
} catch ( MWException $e ) {
- throw new MWException( 'Stashing temporary file failed: ' . get_class($e) . ' ' . $e->getMessage() );
+ $message = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage();
+ wfDebug( __METHOD__ . ' ' . $message . "\n");
+ throw new MWException( $message );
}
- return $sessionKey;
+ return $fileKey;
}
+ /**
+ * Throw an error that the user can recover from by providing a better
+ * value for $parameter
+ *
+ * @param $error array Error array suitable for passing to dieUsageMsg()
+ * @param $parameter string Parameter that needs revising
+ * @param $data array Optional extra data to pass to the user
+ * @throws UsageException
+ */
+ function dieRecoverableError( $error, $parameter, $data = array() ) {
+ try {
+ $data['filekey'] = $this->performStash();
+ $data['sessionkey'] = $data['filekey'];
+ } catch ( MWException $e ) {
+ $data['stashfailed'] = $e->getMessage();
+ }
+ $data['invalidparameter'] = $parameter;
+
+ $parsed = $this->parseMsg( $error );
+ $this->dieUsage( $parsed['info'], $parsed['code'], 0, $data );
+ }
/**
* 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;
+ * 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', 'statuskey' );
+ 'filekey', 'file', 'url', 'statuskey' );
+
+ if ( $this->mParams['statuskey'] ) {
+ $this->checkAsyncDownloadEnabled();
- 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');
+ $this->dieUsage( 'No result in session data', 'missingresult' );
}
if ( $sessionData['result'] == 'Warning' ) {
$sessionData['warnings'] = $this->transformWarnings( $sessionData['warnings'] );
@@ -166,28 +206,24 @@ class ApiUpload extends ApiBase {
}
$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'] ) {
+ if ( $this->mParams['filekey'] ) {
// Upload stashed in a previous request
- $sessionData = $request->getSessionData( UploadBase::getSessionKeyName() );
- if ( !UploadFromStash::isValidSessionKey( $this->mParams['sessionkey'], $sessionData ) ) {
- $this->dieUsageMsg( array( 'invalid-session-key' ) );
+ if ( !UploadFromStash::isValidKey( $this->mParams['filekey'] ) ) {
+ $this->dieUsageMsg( 'invalid-file-key' );
}
- $this->mUpload = new UploadFromStash();
- $this->mUpload->initialize( $this->mParams['filename'],
- $this->mParams['sessionkey'],
- $sessionData[$this->mParams['sessionkey']] );
-
+ // context allows access to the current user without creating new $wgUser references
+ $context = $this->createContext();
+ $this->mUpload = new UploadFromStash( $context->getUser() );
+ $this->mUpload->initialize( $this->mParams['filekey'], $this->mParams['filename'] );
} elseif ( isset( $this->mParams['file'] ) ) {
$this->mUpload = new UploadFromFile();
@@ -198,16 +234,18 @@ class ApiUpload extends ApiBase {
} elseif ( isset( $this->mParams['url'] ) ) {
// Make sure upload by URL is enabled:
if ( !UploadFromUrl::isEnabled() ) {
- $this->dieUsageMsg( array( 'copyuploaddisabled' ) );
+ $this->dieUsageMsg( 'copyuploaddisabled' );
}
$async = false;
if ( $this->mParams['asyncdownload'] ) {
+ $this->checkAsyncDownloadEnabled();
+
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 {
@@ -219,7 +257,7 @@ class ApiUpload extends ApiBase {
$this->mParams['url'], $async );
}
-
+
return true;
}
@@ -236,7 +274,7 @@ class ApiUpload extends ApiBase {
if ( !$user->isLoggedIn() ) {
$this->dieUsageMsg( array( 'mustbeloggedin', 'upload' ) );
} else {
- $this->dieUsageMsg( array( 'badaccess-groups' ) );
+ $this->dieUsageMsg( 'badaccess-groups' );
}
}
}
@@ -254,15 +292,29 @@ class ApiUpload extends ApiBase {
// TODO: Move them to ApiBase's message map
switch( $verification['status'] ) {
+ // Recoverable errors
+ case UploadBase::MIN_LENGTH_PARTNAME:
+ $this->dieRecoverableError( 'filename-tooshort', 'filename' );
+ break;
+ case UploadBase::ILLEGAL_FILENAME:
+ $this->dieRecoverableError( 'illegal-filename', 'filename',
+ array( 'filename' => $verification['filtered'] ) );
+ break;
+ case UploadBase::FILETYPE_MISSING:
+ $this->dieRecoverableError( 'filetype-missing', 'filename' );
+ break;
+ case UploadBase::WINDOWS_NONASCII_FILENAME:
+ $this->dieRecoverableError( 'windows-nonascii-filename', 'filename' );
+ break;
+
+ // Unrecoverable errors
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(
@@ -270,13 +322,6 @@ class ApiUpload extends ApiBase {
'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',
@@ -306,30 +351,35 @@ class ApiUpload extends ApiBase {
if ( !$this->mParams['ignorewarnings'] ) {
$warnings = $this->mUpload->checkWarnings();
- if ( $warnings ) {
- // Add indices
- $this->getResult()->setIndexedTagName( $warnings, 'warning' );
-
- if ( isset( $warnings['duplicate'] ) ) {
- $dupes = array();
- foreach ( $warnings['duplicate'] as $dupe ) {
- $dupes[] = $dupe->getName();
- }
- $this->getResult()->setIndexedTagName( $dupes, 'duplicate' );
- $warnings['duplicate'] = $dupes;
- }
+ }
+ return $this->transformWarnings( $warnings );
+ }
+
+ protected function transformWarnings( $warnings ) {
+ if ( $warnings ) {
+ // Add indices
+ $result = $this->getResult();
+ $result->setIndexedTagName( $warnings, 'warning' );
- if ( isset( $warnings['exists'] ) ) {
- $warning = $warnings['exists'];
- unset( $warnings['exists'] );
- $warnings[$warning['warning']] = $warning['file']->getName();
+ if ( isset( $warnings['duplicate'] ) ) {
+ $dupes = array();
+ foreach ( $warnings['duplicate'] as $dupe ) {
+ $dupes[] = $dupe->getName();
}
+ $result->setIndexedTagName( $dupes, 'duplicate' );
+ $warnings['duplicate'] = $dupes;
}
- }
+ if ( isset( $warnings['exists'] ) ) {
+ $warning = $warnings['exists'];
+ unset( $warnings['exists'] );
+ $warnings[$warning['warning']] = $warning['file']->getName();
+ }
+ }
return $warnings;
}
+
/**
* Perform the actual upload. Returns a suitable result array on success;
* dies on failure.
@@ -376,10 +426,19 @@ class ApiUpload extends ApiBase {
$result['result'] = 'Success';
$result['filename'] = $file->getName();
-
return $result;
}
+ /**
+ * Checks if asynchronous copy uploads are enabled and throws an error if they are not.
+ */
+ protected function checkAsyncDownloadEnabled() {
+ global $wgAllowAsyncCopyUploads;
+ if ( !$wgAllowAsyncCopyUploads ) {
+ $this->dieUsage( 'Asynchronous copy uploads disabled', 'asynccopyuploaddisabled');
+ }
+ }
+
public function mustBePosted() {
return true;
}
@@ -413,18 +472,18 @@ class ApiUpload extends ApiBase {
'ignorewarnings' => false,
'file' => null,
'url' => null,
- 'sessionkey' => null,
+ 'filekey' => null,
+ 'sessionkey' => array(
+ ApiBase::PARAM_DFLT => null,
+ ApiBase::PARAM_DEPRECATED => true,
+ ),
'stash' => false,
+
+ 'asyncdownload' => false,
+ 'leavemessage' => false,
+ 'statuskey' => null,
);
- global $wgAllowAsyncCopyUploads;
- if ( $wgAllowAsyncCopyUploads ) {
- $params += array(
- 'asyncdownload' => false,
- 'leavemessage' => false,
- 'statuskey' => null,
- );
- }
return $params;
}
@@ -439,18 +498,14 @@ class ApiUpload extends ApiBase {
'ignorewarnings' => 'Ignore any warnings',
'file' => 'File contents',
'url' => 'Url to fetch the file from',
- '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.'
- );
+ 'filekey' => 'Key that identifies a previous upload that was stashed temporarily.',
+ 'sessionkey' => 'Same as filekey, maintained for backward compatibility.',
+ 'stash' => 'If set, the server will not add the file to the repository and stash it temporarily.',
- 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',
- );
- }
+ 'asyncdownload' => 'Make fetching a URL asynchronous',
+ 'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished',
+ 'statuskey' => 'Fetch the upload status for this file key',
+ );
return $params;
@@ -461,32 +516,32 @@ class ApiUpload extends ApiBase {
'Upload a file, or get the status of pending uploads. Several methods are available:',
' * Upload file contents directly, using the "file" parameter',
' * Have the MediaWiki server fetch a file from a URL, using the "url" parameter',
- ' * Complete an earlier upload that failed due to warnings, using the "sessionkey" parameter',
+ ' * Complete an earlier upload that failed due to warnings, using the "filekey" parameter',
'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
- 'sending the "file". 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'
+ 'sending the "file". Also you must get and send an edit token before doing any upload stuff'
);
}
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'uploaddisabled' ),
- array( 'invalid-session-key' ),
- array( 'uploaddisabled' ),
- array( 'badaccess-groups' ),
- array( 'mustbeloggedin', 'upload' ),
- array( 'badaccess-groups' ),
- array( 'badaccess-groups' ),
- array( 'code' => 'fetchfileerror', 'info' => '' ),
- array( 'code' => 'nomodule', 'info' => 'No upload module set' ),
- array( 'code' => 'empty-file', 'info' => 'The file you submitted was empty' ),
- array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ),
- array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
- array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ),
- array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ),
- array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ),
- ) );
+ return array_merge( parent::getPossibleErrors(),
+ $this->getRequireOnlyOneParameterErrorMessages( array( 'filekey', 'file', 'url', 'statuskey' ) ),
+ array(
+ array( 'uploaddisabled' ),
+ array( 'invalid-file-key' ),
+ array( 'uploaddisabled' ),
+ array( 'mustbeloggedin', 'upload' ),
+ array( 'badaccess-groups' ),
+ array( 'code' => 'fetchfileerror', 'info' => '' ),
+ array( 'code' => 'nomodule', 'info' => 'No upload module set' ),
+ array( 'code' => 'empty-file', 'info' => 'The file you submitted was empty' ),
+ array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ),
+ array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ),
+ array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ),
+ array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ),
+ array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ),
+ array( 'code' => 'asynccopyuploaddisabled', 'info' => 'Asynchronous copy uploads disabled' ),
+ )
+ );
}
public function needsToken() {
@@ -502,11 +557,15 @@ class ApiUpload extends ApiBase {
'Upload from a URL:',
' api.php?action=upload&filename=Wiki.png&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png',
'Complete an upload that failed due to warnings:',
- ' api.php?action=upload&filename=Wiki.png&sessionkey=sessionkey&ignorewarnings=1',
+ ' api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1',
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Upload';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiUpload.php 51812 2009-06-12 23:45:20Z dale $';
+ return __CLASS__ . ': $Id: ApiUpload.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php
index f9fe9ad2..93d4ef25 100644
--- a/includes/api/ApiUserrights.php
+++ b/includes/api/ApiUserrights.php
@@ -1,11 +1,11 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Mar 24, 2009
*
- * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ * Copyright © 2009 Roan Kattouw <Firstname>.<Lastname>@gmail.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -53,9 +53,10 @@ class ApiUserrights extends ApiBase {
$user, (array)$params['add'],
(array)$params['remove'], $params['reason'] );
- $this->getResult()->setIndexedTagName( $r['added'], 'group' );
- $this->getResult()->setIndexedTagName( $r['removed'], 'group' );
- $this->getResult()->addValue( null, $this->getModuleName(), $r );
+ $result = $this->getResult();
+ $result->setIndexedTagName( $r['added'], 'group' );
+ $result->setIndexedTagName( $r['removed'], 'group' );
+ $result->addValue( null, $this->getModuleName(), $r );
}
/**
@@ -138,7 +139,11 @@ class ApiUserrights extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:User_group_membership';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiUserrights.php 75602 2010-10-28 00:04:48Z reedy $';
+ return __CLASS__ . ': $Id: ApiUserrights.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php
index e9560a4d..6fc55905 100644
--- a/includes/api/ApiWatch.php
+++ b/includes/api/ApiWatch.php
@@ -1,6 +1,6 @@
<?php
/**
- * API for MediaWiki 1.8+
+ *
*
* Created on Jan 4, 2008
*
@@ -49,40 +49,52 @@ class ApiWatch extends ApiBase {
$params = $this->extractRequestParams();
$title = Title::newFromText( $params['title'] );
- if ( !$title ) {
+ if ( !$title || $title->getNamespace() < 0 ) {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
- $article = new Article( $title );
+ $article = new Article( $title, 0 );
$res = array( 'title' => $title->getPrefixedText() );
if ( $params['unwatch'] ) {
$res['unwatched'] = '';
$res['message'] = wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() );
- $success = $article->doUnwatch();
+ $success = WatchAction::doUnwatch( $title, $wgUser );
} else {
$res['watched'] = '';
$res['message'] = wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() );
- $success = $article->doWatch();
+ $success = UnwatchAction::doWatch( $title, $wgUser );
}
if ( !$success ) {
- $this->dieUsageMsg( array( 'hookaborted' ) );
+ $this->dieUsageMsg( 'hookaborted' );
}
$this->getResult()->addValue( null, $this->getModuleName(), $res );
}
+ public function mustBePosted() {
+ return true;
+ }
+
public function isWriteMode() {
return true;
}
+ public function needsToken() {
+ return true;
+ }
+
+ public function getTokenSalt() {
+ return 'watch';
+ }
+
public function getAllowedParams() {
return array(
'title' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
-
'unwatch' => false,
+ 'token' => null,
);
}
@@ -90,6 +102,7 @@ class ApiWatch extends ApiBase {
return array(
'title' => 'The page to (un)watch',
'unwatch' => 'If set the page will be unwatched rather than watched',
+ 'token' => 'A token previously acquired via prop=info',
);
}
@@ -112,7 +125,11 @@ class ApiWatch extends ApiBase {
);
}
+ public function getHelpUrls() {
+ return 'https://www.mediawiki.org/wiki/API:Watch';
+ }
+
public function getVersion() {
- return __CLASS__ . ': $Id: ApiWatch.php 77192 2010-11-23 22:05:27Z btongminh $';
+ return __CLASS__ . ': $Id: ApiWatch.php 104449 2011-11-28 15:52:04Z reedy $';
}
}
diff --git a/includes/CacheDependency.php b/includes/cache/CacheDependency.php
index 74ca2864..aa020664 100644
--- a/includes/CacheDependency.php
+++ b/includes/cache/CacheDependency.php
@@ -58,6 +58,10 @@ class DependencyWrapper {
/**
* Store the wrapper to a cache
+ *
+ * @param $cache BagOStuff
+ * @param $key
+ * @param $expiry
*/
function storeToCache( $cache, $key, $expiry = 0 ) {
$this->initialiseDeps();
@@ -69,7 +73,7 @@ class DependencyWrapper {
* it will be generated with the callback function (if present), and the newly
* calculated value will be stored to the cache in a wrapper.
*
- * @param $cache Object: a cache object such as $wgMemc
+ * @param $cache BagOStuff a cache object such as $wgMemc
* @param $key String: the cache key
* @param $expiry Integer: the expiry timestamp or interval in seconds
* @param $callback Mixed: the callback for generating the value, or false
@@ -156,6 +160,9 @@ class FileDependency extends CacheDependency {
}
}
+ /**
+ * @return bool
+ */
function isExpired() {
if ( !file_exists( $this->filename ) ) {
if ( $this->timestamp === false ) {
@@ -204,11 +211,16 @@ class TitleDependency extends CacheDependency {
/**
* Get rid of bulky Title object for sleep
+ *
+ * @return array
*/
function __sleep() {
return array( 'ns', 'dbk', 'touched' );
}
+ /**
+ * @return Title
+ */
function getTitle() {
if ( !isset( $this->titleObj ) ) {
$this->titleObj = Title::makeTitle( $this->ns, $this->dbk );
@@ -217,6 +229,9 @@ class TitleDependency extends CacheDependency {
return $this->titleObj;
}
+ /**
+ * @return bool
+ */
function isExpired() {
$touched = $this->getTitle()->getTouched();
@@ -292,6 +307,9 @@ class TitleListDependency extends CacheDependency {
$this->timestamps = $this->calculateTimestamps();
}
+ /**
+ * @return array
+ */
function __sleep() {
return array( 'timestamps' );
}
@@ -304,6 +322,9 @@ class TitleListDependency extends CacheDependency {
return $this->linkBatch;
}
+ /**
+ * @return bool
+ */
function isExpired() {
$newTimestamps = $this->calculateTimestamps();
@@ -345,6 +366,9 @@ class GlobalDependency extends CacheDependency {
$this->value = $GLOBALS[$name];
}
+ /**
+ * @return bool
+ */
function isExpired() {
return $GLOBALS[$this->name] != $this->value;
}
@@ -361,6 +385,9 @@ class ConstantDependency extends CacheDependency {
$this->value = constant( $name );
}
+ /**
+ * @return bool
+ */
function isExpired() {
return constant( $this->name ) != $this->value;
}
diff --git a/includes/HTMLCacheUpdate.php b/includes/cache/HTMLCacheUpdate.php
index f0456a22..d542800d 100644
--- a/includes/HTMLCacheUpdate.php
+++ b/includes/cache/HTMLCacheUpdate.php
@@ -18,14 +18,19 @@
* batches, but of course LIMIT with an offset is inefficient on the DB side.
*
* The class is nevertheless a vast improvement on the previous method of using
- * Image::getLinksTo() and Title::touchArray(), which uses about 2KB of memory per
+ * File::getLinksTo() and Title::touchArray(), which uses about 2KB of memory per
* link.
*
* @ingroup Cache
*/
class HTMLCacheUpdate
{
- public $mTitle, $mTable, $mPrefix, $mStart, $mEnd;
+ /**
+ * @var Title
+ */
+ public $mTitle;
+
+ public $mTable, $mPrefix, $mStart, $mEnd;
public $mRowsPerJob, $mRowsPerQuery;
function __construct( $titleTo, $table, $start = false, $end = false ) {
@@ -75,7 +80,7 @@ class HTMLCacheUpdate
$this->invalidateTitles( $titleArray );
} else {
# Partitioning was excessively inaccurate. Divide the job further.
- # This can occur when a large number of links are added in a short
+ # This can occur when a large number of links are added in a short
# period of time, say by updating a heavily-used template.
$this->insertJobsFromTitles( $titleArray );
}
@@ -85,6 +90,8 @@ class HTMLCacheUpdate
* Partition the current range given by $this->mStart and $this->mEnd,
* using a pre-calculated title array which gives the links in that range.
* Queue the resulting jobs.
+ *
+ * @param $titleArray array
*/
protected function insertJobsFromTitles( $titleArray ) {
# We make subpartitions in the sense that the start of the first job
@@ -95,7 +102,7 @@ class HTMLCacheUpdate
$numTitles = 0;
foreach ( $titleArray as $title ) {
$id = $title->getArticleID();
- # $numTitles is now the number of titles in the current job not
+ # $numTitles is now the number of titles in the current job not
# including the current ID
if ( $numTitles >= $this->mRowsPerJob ) {
# Add a job up to but not including the current ID
@@ -121,7 +128,7 @@ class HTMLCacheUpdate
if ( count( $jobs ) < 2 ) {
# I don't think this is possible at present, but handling this case
- # makes the code a bit more robust against future code updates and
+ # makes the code a bit more robust against future code updates and
# avoids a potential infinite loop of repartitioning
wfDebug( __METHOD__.": repartitioning failed!\n" );
$this->invalidateTitles( $titleArray );
@@ -149,15 +156,6 @@ class HTMLCacheUpdate
}
/**
- * Invalidate a range of pages, right now
- * @deprecated
- */
- public function invalidate( $startId = false, $endId = false ) {
- $titleArray = $this->mCache->getLinks( $this->mTable, $startId, $endId );
- $this->invalidateTitles( $titleArray );
- }
-
- /**
* Invalidate an array (or iterator) of Title objects, right now
*/
protected function invalidateTitles( $titleArray ) {
@@ -205,7 +203,7 @@ class HTMLCacheUpdate
/**
* Job wrapper for HTMLCacheUpdate. Gets run whenever a related
* job gets called from the queue.
- *
+ *
* @ingroup JobQueue
*/
class HTMLCacheUpdateJob extends Job {
diff --git a/includes/HTMLFileCache.php b/includes/cache/HTMLFileCache.php
index 26cb147d..1095da2c 100644
--- a/includes/HTMLFileCache.php
+++ b/includes/cache/HTMLFileCache.php
@@ -20,9 +20,14 @@
* @ingroup Cache
*/
class HTMLFileCache {
- var $mTitle, $mFileCache, $mType;
- public function __construct( &$title, $type = 'view' ) {
+ /**
+ * @var Title
+ */
+ var $mTitle;
+ var $mFileCache, $mType;
+
+ public function __construct( $title, $type = 'view' ) {
$this->mTitle = $title;
$this->mType = ($type == 'raw' || $type == 'view' ) ? $type : false;
$this->fileCacheName(); // init name
@@ -64,34 +69,42 @@ class HTMLFileCache {
}
public function isFileCached() {
- if( $this->mType === false ) return false;
+ if( $this->mType === false ) {
+ return false;
+ }
return file_exists( $this->fileCacheName() );
}
public function fileCacheTime() {
return wfTimestamp( TS_MW, filemtime( $this->fileCacheName() ) );
}
-
+
/**
* Check if pages can be cached for this request/user
* @return bool
*/
public static function useFileCache() {
global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest, $wgLang, $wgContLang;
- if( !$wgUseFileCache ) return false;
+ if( !$wgUseFileCache ) {
+ return false;
+ }
// Get all query values
$queryVals = $wgRequest->getValues();
foreach( $queryVals as $query => $val ) {
- if( $query == 'title' || $query == 'curid' ) continue;
+ if( $query == 'title' || $query == 'curid' ) {
+ continue;
// Normal page view in query form can have action=view.
// Raw hits for pages also stored, like .css pages for example.
- else if( $query == 'action' && ($val == 'view' || $val == 'raw') ) continue;
- else if( $query == 'usemsgcache' && $val == 'yes' ) continue;
+ } elseif( $query == 'action' && $val == 'view' ) {
+ continue;
+ } elseif( $query == 'usemsgcache' && $val == 'yes' ) {
+ continue;
// Below are header setting params
- else if( $query == 'maxage' || $query == 'smaxage' || $query == 'ctype' || $query == 'gen' )
+ } elseif( $query == 'maxage' || $query == 'smaxage' || $query == 'ctype' || $query == 'gen' ) {
continue;
- else
+ } else {
return false;
+ }
}
// Check for non-standard user language; this covers uselang,
// and extensions for auto-detecting user language.
@@ -101,14 +114,18 @@ class HTMLFileCache {
return !$wgShowIPinHeader && !$wgUser->getId() && !$wgUser->getNewtalk() && $ulang == $clang;
}
- /*
- * Check if up to date cache file exists
- * @param $timestamp string
- */
+ /**
+ * Check if up to date cache file exists
+ * @param $timestamp string
+ *
+ * @return bool
+ */
public function isFileCacheGood( $timestamp = '' ) {
global $wgCacheEpoch;
- if( !$this->isFileCached() ) return false;
+ if( !$this->isFileCached() ) {
+ return false;
+ }
$cachetime = $this->fileCacheTime();
$good = $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime;
@@ -138,14 +155,14 @@ class HTMLFileCache {
/* Working directory to/from output */
public function loadFromFileCache() {
- global $wgOut, $wgMimeType, $wgOutputEncoding, $wgLanguageCode;
+ global $wgOut, $wgMimeType, $wgLanguageCode;
wfDebug( __METHOD__ . "()\n");
$filename = $this->fileCacheName();
// Raw pages should handle cache control on their own,
// even when using file cache. This reduces hits from clients.
if( $this->mType !== 'raw' ) {
$wgOut->sendCacheControl();
- header( "Content-Type: $wgMimeType; charset={$wgOutputEncoding}" );
+ header( "Content-Type: $wgMimeType; charset=UTF-8" );
header( "Content-Language: $wgLanguageCode" );
}
diff --git a/includes/LinkBatch.php b/includes/cache/LinkBatch.php
index e689f966..0bd869fc 100644
--- a/includes/LinkBatch.php
+++ b/includes/cache/LinkBatch.php
@@ -27,11 +27,16 @@ class LinkBatch {
* Use ->setCaller( __METHOD__ ) to indicate which code is using this
* class. Only used in debugging output.
* @since 1.17
+ *
+ * @param $caller
*/
public function setCaller( $caller ) {
$this->caller = $caller;
}
+ /**
+ * @param $title Title
+ */
public function addObj( $title ) {
if ( is_object( $title ) ) {
$this->add( $title->getNamespace(), $title->getDBkey() );
@@ -54,6 +59,8 @@ class LinkBatch {
/**
* Set the link list to a given 2-d array
* First key is the namespace, second is the DB key, value arbitrary
+ *
+ * @param $array array
*/
public function setArray( $array ) {
$this->data = $array;
@@ -61,6 +68,8 @@ class LinkBatch {
/**
* Returns true if no pages have been added, false otherwise.
+ *
+ * @return bool
*/
public function isEmpty() {
return ($this->getSize() == 0);
@@ -68,6 +77,8 @@ class LinkBatch {
/**
* Returns the size of the batch.
+ *
+ * @return int
*/
public function getSize() {
return count( $this->data );
@@ -77,10 +88,10 @@ class LinkBatch {
* Do the query and add the results to the LinkCache object
* Return an array mapping PDBK to ID
*/
- public function execute() {
- $linkCache = LinkCache::singleton();
- return $this->executeInto( $linkCache );
- }
+ public function execute() {
+ $linkCache = LinkCache::singleton();
+ return $this->executeInto( $linkCache );
+ }
/**
* Do the query and add the results to a given LinkCache object
@@ -90,6 +101,7 @@ class LinkBatch {
wfProfileIn( __METHOD__ );
$res = $this->doQuery();
$ids = $this->addResultToCache( $cache, $res );
+ $this->doGenderQuery();
wfProfileOut( __METHOD__ );
return $ids;
}
@@ -99,6 +111,9 @@ class LinkBatch {
* As normal, titles will go into the static Title cache field.
* This function *also* stores extra fields of the title used for link
* parsing to avoid extra DB queries.
+ *
+ * @param $cache
+ * @param $res
*/
public function addResultToCache( $cache, $res ) {
if ( !$res ) {
@@ -153,6 +168,20 @@ class LinkBatch {
return $res;
}
+ public function doGenderQuery() {
+ if ( $this->isEmpty() ) {
+ return false;
+ }
+
+ global $wgContLang;
+ if ( !$wgContLang->needsGenderDistinction() ) {
+ return false;
+ }
+
+ $genderCache = GenderCache::singleton();
+ $genderCache->dolinkBatch( $this->data, $this->caller );
+ }
+
/**
* Construct a WHERE clause which will match all the given titles.
*
diff --git a/includes/LinkCache.php b/includes/cache/LinkCache.php
index dce34592..aeb10eb0 100644
--- a/includes/LinkCache.php
+++ b/includes/cache/LinkCache.php
@@ -7,13 +7,15 @@
class LinkCache {
// Increment $mClassVer whenever old serialized versions of this class
// becomes incompatible with the new version.
- /* private */ var $mClassVer = 4;
+ private $mClassVer = 4;
- /* private */ var $mGoodLinks, $mBadLinks;
- /* private */ var $mForUpdate;
+ private $mGoodLinks, $mBadLinks;
+ private $mForUpdate;
/**
* Get an instance of this class
+ *
+ * @return LinkCache
*/
static function &singleton() {
static $instance;
@@ -32,11 +34,17 @@ class LinkCache {
/**
* General accessor to get/set whether SELECT FOR UPDATE should be used
+ *
+ * @return bool
*/
public function forUpdate( $update = null ) {
return wfSetVar( $this->mForUpdate, $update );
}
+ /**
+ * @param $title
+ * @return array|int
+ */
public function getGoodLinkID( $title ) {
if ( array_key_exists( $title, $this->mGoodLinks ) ) {
return $this->mGoodLinks[$title];
@@ -61,6 +69,10 @@ class LinkCache {
}
}
+ /**
+ * @param $title
+ * @return bool
+ */
public function isBadLink( $title ) {
return array_key_exists( $title, $this->mBadLinks );
}
@@ -83,6 +95,9 @@ class LinkCache {
'revision' => intval( $revision ) );
}
+ /**
+ * @param $title Title
+ */
public function addBadLinkObj( $title ) {
$dbkey = $title->getPrefixedDbKey();
if ( !$this->isBadLink( $dbkey ) ) {
@@ -94,6 +109,9 @@ class LinkCache {
unset( $this->mBadLinks[$title] );
}
+ /**
+ * @param $title Title
+ */
public function clearLink( $title ) {
$dbkey = $title->getPrefixedDbKey();
if( isset($this->mBadLinks[$dbkey]) ) {
@@ -136,7 +154,7 @@ class LinkCache {
wfProfileIn( __METHOD__ );
$key = $nt->getPrefixedDBkey();
- if ( $this->isBadLink( $key ) ) {
+ if ( $this->isBadLink( $key ) || $nt->isExternal() ) {
wfProfileOut( __METHOD__ );
return 0;
}
@@ -150,7 +168,7 @@ class LinkCache {
wfProfileOut( __METHOD__ );
return 0;
}
-
+
# Some fields heavily used for linking...
if ( $this->mForUpdate ) {
$db = wfGetDB( DB_MASTER );
@@ -164,7 +182,7 @@ class LinkCache {
$options = array();
}
- $s = $db->selectRow( 'page',
+ $s = $db->selectRow( 'page',
array( 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
array( 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey() ),
__METHOD__, $options );
diff --git a/includes/MemcachedSessions.php b/includes/cache/MemcachedSessions.php
index 4f10f99c..36733595 100644
--- a/includes/MemcachedSessions.php
+++ b/includes/cache/MemcachedSessions.php
@@ -92,4 +92,7 @@ function memsess_gc( $maxlifetime ) {
return true;
}
-session_set_save_handler( 'memsess_open', 'memsess_close', 'memsess_read', 'memsess_write', 'memsess_destroy', 'memsess_gc' );
+function memsess_write_close() {
+ session_write_close();
+}
+
diff --git a/includes/MessageCache.php b/includes/cache/MessageCache.php
index a111211f..79883844 100644
--- a/includes/MessageCache.php
+++ b/includes/cache/MessageCache.php
@@ -7,9 +7,9 @@
/**
*
*/
-define( 'MSG_LOAD_TIMEOUT', 60);
-define( 'MSG_LOCK_TIMEOUT', 10);
-define( 'MSG_WAIT_TIMEOUT', 10);
+define( 'MSG_LOAD_TIMEOUT', 60 );
+define( 'MSG_LOCK_TIMEOUT', 10 );
+define( 'MSG_WAIT_TIMEOUT', 10 );
define( 'MSG_CACHE_VERSION', 1 );
/**
@@ -42,6 +42,61 @@ class MessageCache {
/// Variable for tracking which variables are already loaded
protected $mLoadedLanguages = array();
+ /**
+ * Used for automatic detection of most used messages.
+ */
+ protected $mRequestedMessages = array();
+
+ /**
+ * How long the message request counts are stored. Longer period gives
+ * better sample, but also takes longer to adapt changes. The counts
+ * are aggregrated per day, regardless of the value of this variable.
+ */
+ protected static $mAdaptiveDataAge = 604800; // Is 7*24*3600
+
+ /**
+ * Filter the tail of less used messages that are requested more seldom
+ * than this factor times the number of request of most requested message.
+ * These messages are not loaded in the default set, but are still cached
+ * individually on demand with the normal cache expiry time.
+ */
+ protected static $mAdaptiveInclusionThreshold = 0.05;
+
+ /**
+ * Singleton instance
+ *
+ * @var MessageCache
+ */
+ private static $instance;
+
+ /**
+ * @var bool
+ */
+ protected $mInParser = false;
+
+ /**
+ * Get the signleton instance of this class
+ *
+ * @since 1.18
+ * @return MessageCache object
+ */
+ public static function singleton() {
+ if ( is_null( self::$instance ) ) {
+ global $wgUseDatabaseMessages, $wgMsgCacheExpiry;
+ self::$instance = new self( wfGetMessageCacheStorage(), $wgUseDatabaseMessages, $wgMsgCacheExpiry );
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Destroy the singleton instance
+ *
+ * @since 1.18
+ */
+ public static function destroyInstance() {
+ self::$instance = null;
+ }
+
function __construct( $memCached, $useDB, $expiry ) {
if ( !$memCached ) {
$memCached = wfGetCache( CACHE_NONE );
@@ -52,9 +107,10 @@ class MessageCache {
$this->mExpiry = $expiry;
}
-
/**
* ParserOptions is lazy initialised.
+ *
+ * @return ParserOptions
*/
function getParserOptions() {
if ( !$this->mParserOptions ) {
@@ -101,9 +157,9 @@ class MessageCache {
return false; // Wrong hash
}
} else {
- $localHash=substr(fread($file,40),8);
- fclose($file);
- if ($hash!=$localHash) {
+ $localHash = substr( fread( $file, 40 ), 8 );
+ fclose( $file );
+ if ( $hash != $localHash ) {
return false; // Wrong hash
}
@@ -133,7 +189,9 @@ class MessageCache {
fwrite( $file, $hash . $serialized );
fclose( $file );
- @chmod( $filename, 0666 );
+ wfSuppressWarnings();
+ chmod( $filename, 0666 );
+ wfRestoreWarnings();
}
function saveToScript( $array, $hash, $code ) {
@@ -141,10 +199,10 @@ class MessageCache {
$filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
$tempFilename = $filename . '.tmp';
- wfMkdirParents( $wgCacheDirectory ); // might fail
+ wfMkdirParents( $wgCacheDirectory ); // might fail
wfSuppressWarnings();
- $file = fopen( $tempFilename, 'w');
+ $file = fopen( $tempFilename, 'w' );
wfRestoreWarnings();
if ( !$file ) {
@@ -152,20 +210,20 @@ class MessageCache {
return;
}
- fwrite($file,"<?php\n//$hash\n\n \$this->mCache = array(");
+ fwrite( $file, "<?php\n//$hash\n\n \$this->mCache = array(" );
foreach ( $array as $key => $message ) {
- $key = $this->escapeForScript($key);
- $message = $this->escapeForScript($message);
- fwrite($file, "'$key' => '$message',\n");
+ $key = $this->escapeForScript( $key );
+ $message = $this->escapeForScript( $message );
+ fwrite( $file, "'$key' => '$message',\n" );
}
- fwrite($file,");\n?>");
- fclose($file);
- rename($tempFilename, $filename);
+ fwrite( $file, ");\n?>" );
+ fclose( $file);
+ rename( $tempFilename, $filename );
}
- function escapeForScript($string) {
+ function escapeForScript( $string ) {
$string = str_replace( '\\', '\\\\', $string );
$string = str_replace( '\'', '\\\'', $string );
return $string;
@@ -173,6 +231,8 @@ class MessageCache {
/**
* Set the cache to $cache, if it is valid. Otherwise set the cache to false.
+ *
+ * @return bool
*/
function setCache( $cache, $code ) {
if ( isset( $cache['VERSION'] ) && $cache['VERSION'] == MSG_CACHE_VERSION ) {
@@ -212,7 +272,9 @@ class MessageCache {
}
# Don't do double loading...
- if ( isset($this->mLoadedLanguages[$code]) ) return true;
+ if ( isset( $this->mLoadedLanguages[$code] ) ) {
+ return true;
+ }
# 8 lines of code just to say (once) that message cache is disabled
if ( $this->mDisable ) {
@@ -230,7 +292,6 @@ class MessageCache {
$where = array(); # Debug info, delayed to avoid spamming debug log too much
$cacheKey = wfMemcKey( 'messages', $code ); # Key in memc for messages
-
# (1) local cache
# Hash of the contents is stored in memcache, to detect if local cache goes
# out of date (due to update in other thread?)
@@ -258,14 +319,13 @@ class MessageCache {
wfProfileOut( __METHOD__ . '-fromcache' );
}
-
# (3)
# Nothing in caches... so we need create one and store it in caches
if ( !$success ) {
$where[] = 'cache is empty';
$where[] = 'loading from database';
- $this->lock($cacheKey);
+ $this->lock( $cacheKey );
# Limit the concurrency of loadFromDB to a single process
# This prevents the site from going down when the cache expires
@@ -280,7 +340,7 @@ class MessageCache {
if ( $success ) {
$this->mMemc->delete( $statusKey );
} else {
- $this->mMemc->set( $statusKey, 'error', 60*5 );
+ $this->mMemc->set( $statusKey, 'error', 60 * 5 );
wfDebug( "MemCached set error in MessageCache: restart memcached server!\n" );
}
}
@@ -309,12 +369,12 @@ class MessageCache {
* $wgMaxMsgCacheEntrySize are assigned a special value, and are loaded
* on-demand from the database later.
*
- * @param $code Optional language code, see documenation of load().
- * @return Array: Loaded messages for storing in caches.
+ * @param $code String: language code.
+ * @return Array: loaded messages for storing in caches.
*/
- function loadFromDB( $code = false ) {
+ function loadFromDB( $code ) {
wfProfileIn( __METHOD__ );
- global $wgMaxMsgCacheEntrySize, $wgContLanguageCode;
+ global $wgMaxMsgCacheEntrySize, $wgLanguageCode, $wgAdaptiveMessageCache;
$dbr = wfGetDB( DB_SLAVE );
$cache = array();
@@ -324,19 +384,26 @@ class MessageCache {
'page_namespace' => NS_MEDIAWIKI,
);
- if ( $code ) {
- # Is this fast enough. Should not matter if the filtering is done in the
- # database or in code.
- if ( $code !== $wgContLanguageCode ) {
- # Messages for particular language
- $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" );
- } else {
- # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
- # other than language code.
- $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
+ $mostused = array();
+ if ( $wgAdaptiveMessageCache ) {
+ $mostused = $this->getMostUsedMessages();
+ if ( $code !== $wgLanguageCode ) {
+ foreach ( $mostused as $key => $value ) {
+ $mostused[$key] = "$value/$code";
+ }
}
}
+ if ( count( $mostused ) ) {
+ $conds['page_title'] = $mostused;
+ } elseif ( $code !== $wgLanguageCode ) {
+ $conds[] = 'page_title' . $dbr->buildLike( $dbr->anyString(), "/$code" );
+ } else {
+ # Effectively disallows use of '/' character in NS_MEDIAWIKI for uses
+ # other than language code.
+ $conds[] = 'page_title NOT' . $dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
+ }
+
# Conditions to fetch oversized pages to ignore them
$bigConds = $conds;
$bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
@@ -353,12 +420,30 @@ class MessageCache {
$smallConds[] = 'rev_text_id=old_id';
$smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
- $res = $dbr->select( array( 'page', 'revision', 'text' ),
+ $res = $dbr->select(
+ array( 'page', 'revision', 'text' ),
array( 'page_title', 'old_text', 'old_flags' ),
- $smallConds, __METHOD__ . "($code)-small" );
+ $smallConds,
+ __METHOD__ . "($code)-small"
+ );
foreach ( $res as $row ) {
- $cache[$row->page_title] = ' ' . Revision::getRevisionText( $row );
+ $text = Revision::getRevisionText( $row );
+ if( $text === false ) {
+ // Failed to fetch data; possible ES errors?
+ // Store a marker to fetch on-demand as a workaround...
+ $entry = '!TOO BIG';
+ wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$row->page_title} ($code)" );
+ } else {
+ $entry = ' ' . $text;
+ }
+ $cache[$row->page_title] = $entry;
+ }
+
+ foreach ( $mostused as $key ) {
+ if ( !isset( $cache[$key] ) ) {
+ $cache[$key] = '!NONEXISTENT';
+ }
}
$cache['VERSION'] = MSG_CACHE_VERSION;
@@ -384,34 +469,29 @@ class MessageCache {
list( $msg, $code ) = $this->figureMessage( $title );
$cacheKey = wfMemcKey( 'messages', $code );
- $this->load($code);
- $this->lock($cacheKey);
-
- if ( is_array($this->mCache[$code]) ) {
- $titleKey = wfMemcKey( 'messages', 'individual', $title );
-
- if ( $text === false ) {
- # Article was deleted
- unset( $this->mCache[$code][$title] );
- $this->mMemc->delete( $titleKey );
-
- } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
- # Check for size
- $this->mCache[$code][$title] = '!TOO BIG';
- $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
+ $this->load( $code );
+ $this->lock( $cacheKey );
- } else {
- $this->mCache[$code][$title] = ' ' . $text;
- $this->mMemc->delete( $titleKey );
- }
+ $titleKey = wfMemcKey( 'messages', 'individual', $title );
- # Update caches
- $this->saveToCaches( $this->mCache[$code], true, $code );
+ if ( $text === false ) {
+ # Article was deleted
+ $this->mCache[$code][$title] = '!NONEXISTENT';
+ $this->mMemc->delete( $titleKey );
+ } elseif ( strlen( $text ) > $wgMaxMsgCacheEntrySize ) {
+ # Check for size
+ $this->mCache[$code][$title] = '!TOO BIG';
+ $this->mMemc->set( $titleKey, ' ' . $text, $this->mExpiry );
+ } else {
+ $this->mCache[$code][$title] = ' ' . $text;
+ $this->mMemc->delete( $titleKey );
}
- $this->unlock($cacheKey);
+
+ # Update caches
+ $this->saveToCaches( $this->mCache[$code], true, $code );
+ $this->unlock( $cacheKey );
// Also delete cached sidebar... just in case it is affected
- global $parserMemc;
$codes = array( $code );
if ( $code === 'en' ) {
// Delete all sidebars, like for example on action=purge on the
@@ -419,16 +499,17 @@ class MessageCache {
$codes = array_keys( Language::getLanguageNames() );
}
+ global $parserMemc;
foreach ( $codes as $code ) {
$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 ) );
+ wfRunHooks( 'MessageCacheReplace', array( $title, $text ) );
wfProfileOut( __METHOD__ );
}
@@ -474,16 +555,16 @@ class MessageCache {
*
* @return Boolean: success
*/
- function lock($key) {
+ function lock( $key ) {
$lockKey = $key . ':lock';
- for ($i=0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
- sleep(1);
+ for ( $i = 0; $i < MSG_WAIT_TIMEOUT && !$this->mMemc->add( $lockKey, 1, MSG_LOCK_TIMEOUT ); $i++ ) {
+ sleep( 1 );
}
return $i >= MSG_WAIT_TIMEOUT;
}
- function unlock($key) {
+ function unlock( $key ) {
$lockKey = $key . ':lock';
$this->mMemc->delete( $lockKey );
}
@@ -508,8 +589,13 @@ class MessageCache {
function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
global $wgLanguageCode, $wgContLang;
+ if ( is_int( $key ) ) {
+ // "Non-string key given" exception sometimes happens for numerical strings that become ints somewhere on their way here
+ $key = strval( $key );
+ }
+
if ( !is_string( $key ) ) {
- throw new MWException( "Non-string key given" );
+ throw new MWException( 'Non-string key given' );
}
if ( strval( $key ) === '' ) {
@@ -536,10 +622,17 @@ class MessageCache {
$uckey = $wgContLang->ucfirst( $lckey );
}
+ /**
+ * Record each message request, but only once per request.
+ * This information is not used unless $wgAdaptiveMessageCache
+ * is enabled.
+ */
+ $this->mRequestedMessages[$uckey] = true;
+
# Try the MediaWiki namespace
if( !$this->mDisable && $useDB ) {
$title = $uckey;
- if(!$isFullKey && ( $langcode != $wgLanguageCode ) ) {
+ if( !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
$title .= '/' . $langcode;
}
$message = $this->getMsgFromNamespace( $title, $langcode );
@@ -568,9 +661,9 @@ class MessageCache {
}
# Is this a custom message? Try the default language in the db...
- if( ($message === false || $message === '-' ) &&
+ if( ( $message === false || $message === '-' ) &&
!$this->mDisable && $useDB &&
- !$isFullKey && ($langcode != $wgLanguageCode) ) {
+ !$isFullKey && ( $langcode != $wgLanguageCode ) ) {
$message = $this->getMsgFromNamespace( $uckey, $wgLanguageCode );
}
@@ -600,37 +693,46 @@ class MessageCache {
* @param $code String: code denoting the language to try.
*/
function getMsgFromNamespace( $title, $code ) {
- $type = false;
- $message = false;
+ global $wgAdaptiveMessageCache;
$this->load( $code );
if ( isset( $this->mCache[$code][$title] ) ) {
$entry = $this->mCache[$code][$title];
- $type = substr( $entry, 0, 1 );
- if ( $type == ' ' ) {
+ if ( substr( $entry, 0, 1 ) === ' ' ) {
return substr( $entry, 1 );
+ } elseif ( $entry === '!NONEXISTENT' ) {
+ return false;
+ } elseif( $entry === '!TOO BIG' ) {
+ // Fall through and try invididual message cache below
+ }
+ } else {
+ // XXX: This is not cached in process cache, should it?
+ $message = false;
+ wfRunHooks( 'MessagesPreLoad', array( $title, &$message ) );
+ if ( $message !== false ) {
+ return $message;
}
- }
- # Call message hooks, in case they are defined
- wfRunHooks('MessagesPreLoad', array( $title, &$message ) );
- if ( $message !== false ) {
- return $message;
+ /**
+ * If message cache is in normal mode, it is guaranteed
+ * (except bugs) that there is always entry (or placeholder)
+ * in the cache if message exists. Thus we can do minor
+ * performance improvement and return false early.
+ */
+ if ( !$wgAdaptiveMessageCache ) {
+ return false;
+ }
}
- $titleKey = wfMemcKey( 'messages', 'individual', $title );
-
# Try the individual message cache
+ $titleKey = wfMemcKey( 'messages', 'individual', $title );
$entry = $this->mMemc->get( $titleKey );
if ( $entry ) {
- $type = substr( $entry, 0, 1 );
-
- if ( $type === ' ' ) {
- # Ok!
- $message = substr( $entry, 1 );
+ if ( substr( $entry, 0, 1 ) === ' ' ) {
$this->mCache[$code][$title] = $entry;
- return $message;
+ return substr( $entry, 1 );
} elseif ( $entry === '!NONEXISTENT' ) {
+ $this->mCache[$code][$title] = '!NONEXISTENT';
return false;
} else {
# Corrupt/obsolete entry, delete it
@@ -638,27 +740,62 @@ class MessageCache {
}
}
- # Try loading it from the DB
+ # Try loading it from the database
$revision = Revision::newFromTitle( Title::makeTitle( NS_MEDIAWIKI, $title ) );
- if( $revision ) {
+ if ( $revision ) {
$message = $revision->getText();
- $this->mCache[$code][$title] = ' ' . $message;
- $this->mMemc->set( $titleKey, ' ' . $message, $this->mExpiry );
+ if ($message === false) {
+ // A possibly temporary loading failure.
+ wfDebugLog( 'MessageCache', __METHOD__ . ": failed to load message page text for {$title->getDbKey()} ($code)" );
+ } else {
+ $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
+ $message = false;
+ $this->mCache[$code][$title] = '!NONEXISTENT';
$this->mMemc->set( $titleKey, '!NONEXISTENT', $this->mExpiry );
- $this->mCache[$code][$title] = false;
}
+
return $message;
}
- function transform( $message, $interface = false, $language = null ) {
+ /**
+ * @param $message string
+ * @param $interface bool
+ * @param $language
+ * @param $title Title
+ * @return string
+ */
+ function transform( $message, $interface = false, $language = null, $title = null ) {
// Avoid creating parser if nothing to transform
if( strpos( $message, '{{' ) === false ) {
return $message;
}
+ if ( $this->mInParser ) {
+ return $message;
+ }
+
+ $parser = $this->getParser();
+ if ( $parser ) {
+ $popts = $this->getParserOptions();
+ $popts->setInterfaceMessage( $interface );
+ $popts->setTargetLanguage( $language );
+
+ $userlang = $popts->setUserLang( $language );
+ $this->mInParser = true;
+ $message = $parser->transformMsg( $message, $popts, $title );
+ $this->mInParser = false;
+ $popts->setUserLang( $userlang );
+ }
+ return $message;
+ }
+
+ /**
+ * @return Parser
+ */
+ function getParser() {
global $wgParser, $wgParserConf;
if ( !$this->mParser && isset( $wgParser ) ) {
# Do some initialisation so that we don't have to do it twice
@@ -671,35 +808,60 @@ class MessageCache {
} else {
$this->mParser = clone $wgParser;
}
- #wfDebug( __METHOD__ . ": following contents triggered transform: $message\n" );
}
- if ( $this->mParser ) {
- $popts = $this->getParserOptions();
- $popts->setInterfaceMessage( $interface );
+ return $this->mParser;
+ }
+
+ /**
+ * @param $text string
+ * @param $string Title|string
+ * @param $title Title
+ * @param $interface bool
+ * @param $linestart bool
+ * @param $language
+ * @return ParserOutput
+ */
+ public function parse( $text, $title = null, $linestart = true, $interface = false, $language = null ) {
+ if ( $this->mInParser ) {
+ return htmlspecialchars( $text );
+ }
+
+ $parser = $this->getParser();
+ $popts = $this->getParserOptions();
+
+ if ( $interface ) {
+ $popts->setInterfaceMessage( true );
+ }
+ if ( $language !== null ) {
$popts->setTargetLanguage( $language );
- $userlang = $popts->setUserLang( $language );
- $message = $this->mParser->transformMsg( $message, $popts );
- $popts->setUserLang( $userlang );
}
- return $message;
- }
- function disable() { $this->mDisable = true; }
- function enable() { $this->mDisable = false; }
+ wfProfileIn( __METHOD__ );
+ if ( !$title || !$title instanceof Title ) {
+ global $wgTitle;
+ $title = $wgTitle;
+ }
+ // Sometimes $wgTitle isn't set either...
+ if ( !$title ) {
+ # It's not uncommon having a null $wgTitle in scripts. See r80898
+ # Create a ghost title in such case
+ $title = Title::newFromText( 'Dwimmerlaik' );
+ }
+
+ $this->mInParser = true;
+ $res = $parser->parse( $text, $title, $popts, $linestart );
+ $this->mInParser = false;
- /** @deprecated */
- function disableTransform(){
- wfDeprecated( __METHOD__ );
- }
- function enableTransform() {
- wfDeprecated( __METHOD__ );
+ wfProfileOut( __METHOD__ );
+ return $res;
}
- function setTransform( $x ) {
- wfDeprecated( __METHOD__ );
+
+ function disable() {
+ $this->mDisable = true;
}
- function getTransform() {
- wfDeprecated( __METHOD__ );
- return false;
+
+ function enable() {
+ $this->mDisable = false;
}
/**
@@ -716,77 +878,94 @@ class MessageCache {
$this->mLoadedLanguages = array();
}
- /**
- * Add a message to the cache
- * @deprecated Use $wgExtensionMessagesFiles
- *
- * @param $key Mixed
- * @param $value Mixed
- * @param $lang String: the messages language, English by default
- */
- function addMessage( $key, $value, $lang = 'en' ) {
- wfDeprecated( __METHOD__ );
- $lc = Language::getLocalisationCache();
- $lc->addLegacyMessages( array( $lang => array( $key => $value ) ) );
- }
-
- /**
- * Add an associative array of message to the cache
- * @deprecated Use $wgExtensionMessagesFiles
- *
- * @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__ );
- $lc = Language::getLocalisationCache();
- $lc->addLegacyMessages( array( $lang => $messages ) );
- }
-
- /**
- * Add a 2-D array of messages by lang. Useful for extensions.
- * @deprecated Use $wgExtensionMessagesFiles
- *
- * @param $messages Array: the array to be added
- */
- function addMessagesByLang( $messages ) {
- wfDeprecated( __METHOD__ );
- $lc = Language::getLocalisationCache();
- $lc->addLegacyMessages( $messages );
- }
-
- /**
- * Set a hook for addMessagesByLang()
- */
- function setExtensionMessagesHook( $callback ) {
- $this->mAddMessagesHook = $callback;
- }
-
- /**
- * @deprecated
- */
- function loadAllMessages( $lang = false ) {
- }
-
- /**
- * @deprecated
- */
- function loadMessagesFile( $filename, $langcode = false ) {
- }
-
public function figureMessage( $key ) {
global $wgLanguageCode;
$pieces = explode( '/', $key );
- if( count( $pieces ) < 2 )
+ if( count( $pieces ) < 2 ) {
return array( $key, $wgLanguageCode );
+ }
$lang = array_pop( $pieces );
$validCodes = Language::getLanguageNames();
- if( !array_key_exists( $lang, $validCodes ) )
+ if( !array_key_exists( $lang, $validCodes ) ) {
return array( $key, $wgLanguageCode );
+ }
$message = implode( '/', $pieces );
return array( $message, $lang );
}
+ public static function logMessages() {
+ wfProfileIn( __METHOD__ );
+ global $wgAdaptiveMessageCache;
+ if ( !$wgAdaptiveMessageCache || !self::$instance instanceof MessageCache ) {
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
+ $cachekey = wfMemckey( 'message-profiling' );
+ $cache = wfGetCache( CACHE_DB );
+ $data = $cache->get( $cachekey );
+
+ if ( !$data ) {
+ $data = array();
+ }
+
+ $age = self::$mAdaptiveDataAge;
+ $filterDate = substr( wfTimestamp( TS_MW, time() - $age ), 0, 8 );
+ foreach ( array_keys( $data ) as $key ) {
+ if ( $key < $filterDate ) {
+ unset( $data[$key] );
+ }
+ }
+
+ $index = substr( wfTimestampNow(), 0, 8 );
+ if ( !isset( $data[$index] ) ) {
+ $data[$index] = array();
+ }
+
+ foreach ( self::$instance->mRequestedMessages as $message => $_ ) {
+ if ( !isset( $data[$index][$message] ) ) {
+ $data[$index][$message] = 0;
+ }
+ $data[$index][$message]++;
+ }
+
+ $cache->set( $cachekey, $data );
+ wfProfileOut( __METHOD__ );
+ }
+
+ public function getMostUsedMessages() {
+ wfProfileIn( __METHOD__ );
+ $cachekey = wfMemcKey( 'message-profiling' );
+ $cache = wfGetCache( CACHE_DB );
+ $data = $cache->get( $cachekey );
+ if ( !$data ) {
+ wfProfileOut( __METHOD__ );
+ return array();
+ }
+
+ $list = array();
+
+ foreach( $data as $messages ) {
+ foreach( $messages as $message => $count ) {
+ $key = $message;
+ if ( !isset( $list[$key] ) ) {
+ $list[$key] = 0;
+ }
+ $list[$key] += $count;
+ }
+ }
+
+ $max = max( $list );
+ foreach ( $list as $message => $count ) {
+ if ( $count < intval( $max * self::$mAdaptiveInclusionThreshold ) ) {
+ unset( $list[$message] );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return array_keys( $list );
+ }
+
}
diff --git a/includes/SquidUpdate.php b/includes/cache/SquidUpdate.php
index 31f7aa68..d47b5b5e 100644
--- a/includes/SquidUpdate.php
+++ b/includes/cache/SquidUpdate.php
@@ -25,6 +25,11 @@ class SquidUpdate {
$this->urlArr = $urlArr;
}
+ /**
+ * @param $title Title
+ *
+ * @return SquidUpdate
+ */
static function newFromLinksTo( &$title ) {
global $wgMaxSquidPurgeTitles;
wfProfileIn( __METHOD__ );
@@ -52,6 +57,11 @@ class SquidUpdate {
/**
* Create a SquidUpdate from an array of Title objects, or a TitleArray object
+ *
+ * @param $titles array
+ * @param $urlArr array
+ *
+ * @return SquidUpdate
*/
static function newFromTitles( $titles, $urlArr = array() ) {
global $wgMaxSquidPurgeTitles;
@@ -65,20 +75,32 @@ class SquidUpdate {
return new SquidUpdate( $urlArr );
}
+ /**
+ * @param $title Title
+ *
+ * @return SquidUpdate
+ */
static function newSimplePurge( &$title ) {
$urlArr = $title->getSquidURLs();
return new SquidUpdate( $urlArr );
}
+ /**
+ * Purges the list of URLs passed to the constructor
+ */
function doUpdate() {
SquidUpdate::purge( $this->urlArr );
}
- /* Purges a list of Squids defined in $wgSquidServers.
- $urlArr should contain the full URLs to purge as values
- (example: $urlArr[] = 'http://my.host/something')
- XXX report broken Squids per mail or log */
-
+ /**
+ * Purges a list of Squids defined in $wgSquidServers.
+ * $urlArr should contain the full URLs to purge as values
+ * (example: $urlArr[] = 'http://my.host/something')
+ * XXX report broken Squids per mail or log
+ *
+ * @param $urlArr array
+ * @return void
+ */
static function purge( $urlArr ) {
global $wgSquidServers, $wgHTCPMulticastAddress, $wgHTCPPort;
@@ -92,7 +114,7 @@ class SquidUpdate {
}
if ( $wgHTCPMulticastAddress && $wgHTCPPort ) {
- return SquidUpdate::HTCPPurge( $urlArr );
+ SquidUpdate::HTCPPurge( $urlArr );
}
wfProfileIn( __METHOD__ );
@@ -120,13 +142,17 @@ class SquidUpdate {
wfProfileOut( __METHOD__ );
}
+ /**
+ * @throws MWException
+ * @param $urlArr array
+ */
static function HTCPPurge( $urlArr ) {
global $wgHTCPMulticastAddress, $wgHTCPMulticastTTL, $wgHTCPPort;
wfProfileIn( __METHOD__ );
- $htcpOpCLR = 4; // HTCP CLR
+ $htcpOpCLR = 4; // HTCP CLR
- // FIXME PHP doesn't support these socket constants (include/linux/in.h)
+ // @todo FIXME: PHP doesn't support these socket constants (include/linux/in.h)
if( !defined( "IPPROTO_IP" ) ) {
define( "IPPROTO_IP", 0 );
define( "IP_MULTICAST_LOOP", 34 );
@@ -190,13 +216,11 @@ class SquidUpdate {
*
* Client functions should not need to call this.
*
+ * @param $url string
+ *
* @return string
*/
static function expand( $url ) {
- global $wgInternalServer;
- if( $url != '' && $url{0} == '/' ) {
- return $wgInternalServer . $url;
- }
- return $url;
+ return wfExpandUrl( $url, PROTO_INTERNAL );
}
}
diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php
new file mode 100644
index 00000000..3357268d
--- /dev/null
+++ b/includes/db/CloneDatabase.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * Helper class for making a copy of the database, mostly for unit testing.
+ *
+ * Copyright © 2010 Chad Horohoe <chad@anyonecanedit.org>
+ * 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
+ *
+ * @ingroup Database
+ */
+
+class CloneDatabase {
+
+ /**
+ * Table prefix for cloning
+ * @var String
+ */
+ private $newTablePrefix = '';
+
+ /**
+ * Current table prefix
+ * @var String
+ */
+ private $oldTablePrefix = '';
+
+ /**
+ * List of tables to be cloned
+ * @var Array
+ */
+ private $tablesToClone = array();
+
+ /**
+ * Should we DROP tables containing the new names?
+ * @var Bool
+ */
+ private $dropCurrentTables = true;
+
+ /**
+ * Whether to use temporary tables or not
+ * @var Bool
+ */
+ private $useTemporaryTables = true;
+
+ /**
+ * Constructor
+ *
+ * @param $db DatabaseBase A database subclass
+ * @param $tablesToClone Array An array of tables to clone, unprefixed
+ * @param $newTablePrefix String Prefix to assign to the tables
+ * @param $oldTablePrefix String Prefix on current tables, if not $wgDBprefix
+ * @param $dropCurrentTables bool
+ */
+ public function __construct( DatabaseBase $db, array $tablesToClone,
+ $newTablePrefix, $oldTablePrefix = '', $dropCurrentTables = true )
+ {
+ $this->db = $db;
+ $this->tablesToClone = $tablesToClone;
+ $this->newTablePrefix = $newTablePrefix;
+ $this->oldTablePrefix = $oldTablePrefix ? $oldTablePrefix : $this->db->tablePrefix();
+ $this->dropCurrentTables = $dropCurrentTables;
+ }
+
+ /**
+ * Set whether to use temporary tables or not
+ * @param $u Bool Use temporary tables when cloning the structure
+ */
+ public function useTemporaryTables( $u = true ) {
+ $this->useTemporaryTables = $u;
+ }
+
+ /**
+ * Clone the table structure
+ */
+ public function cloneTableStructure() {
+
+ foreach( $this->tablesToClone as $tbl ) {
+ # Clean up from previous aborted run. So that table escaping
+ # works correctly across DB engines, we need to change the pre-
+ # fix back and forth so tableName() works right.
+
+ self::changePrefix( $this->oldTablePrefix );
+ $oldTableName = $this->db->tableName( $tbl, false );
+
+ self::changePrefix( $this->newTablePrefix );
+ $newTableName = $this->db->tableName( $tbl, false );
+
+ if( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres' ) ) ) {
+ $this->db->dropTable( $tbl, __METHOD__ );
+ wfDebug( __METHOD__." dropping {$newTableName}\n", true);
+ //Dropping the oldTable because the prefix was changed
+ }
+
+ # Create new table
+ wfDebug( __METHOD__." duplicating $oldTableName to $newTableName\n", true );
+ $this->db->duplicateTableStructure( $oldTableName, $newTableName, $this->useTemporaryTables );
+
+ }
+
+ }
+
+ /**
+ * Change the prefix back to the original.
+ * @param $dropTables bool Optionally drop the tables we created
+ */
+ public function destroy( $dropTables = false ) {
+ if( $dropTables ) {
+ self::changePrefix( $this->newTablePrefix );
+ foreach( $this->tablesToClone as $tbl ) {
+ $this->db->dropTable( $tbl );
+ }
+ }
+ self::changePrefix( $this->oldTablePrefix );
+ }
+
+ /**
+ * Change the table prefix on all open DB connections/
+ *
+ * @param $prefix
+ * @return void
+ */
+ public static function changePrefix( $prefix ) {
+ global $wgDBprefix;
+ wfGetLBFactory()->forEachLB( array( 'CloneDatabase', 'changeLBPrefix' ), array( $prefix ) );
+ $wgDBprefix = $prefix;
+ }
+
+ /**
+ * @param $lb LoadBalancer
+ * @param $prefix
+ * @return void
+ */
+ public static function changeLBPrefix( $lb, $prefix ) {
+ $lb->forEachOpenConnection( array( 'CloneDatabase', 'changeDBPrefix' ), array( $prefix ) );
+ }
+
+ /**
+ * @param $db DatabaseBase
+ * @param $prefix
+ * @return void
+ */
+ public static function changeDBPrefix( $db, $prefix ) {
+ $db->tablePrefix( $prefix );
+ }
+}
diff --git a/includes/db/Database.php b/includes/db/Database.php
index 5acb67fa..75e6a91d 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -28,7 +28,7 @@ interface DatabaseType {
*
* @return string
*/
- public function getType();
+ function getType();
/**
* Open a connection to the database. Usually aborts on failure
@@ -40,38 +40,28 @@ interface DatabaseType {
* @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 );
+ function open( $server, $user, $password, $dbName );
/**
* Fetch the next row from the given result object, in object form.
* Fields can be retrieved with $row->fieldname, with fields acting like
* member variables.
*
- * @param $res SQL result object as returned from DatabaseBase::query(), etc.
+ * @param $res ResultWrapper|object as returned from DatabaseBase::query(), etc.
* @return Row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
- public function fetchObject( $res );
+ 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.
+ * @param $res ResultWrapper result object as returned from DatabaseBase::query(), etc.
* @return Row object
* @throws DBUnexpectedError Thrown if the database returns an error
*/
- public function fetchRow( $res );
+ function fetchRow( $res );
/**
* Get the number of rows in a result object
@@ -79,7 +69,7 @@ interface DatabaseType {
* @param $res Mixed: A SQL result
* @return int
*/
- public function numRows( $res );
+ function numRows( $res );
/**
* Get the number of fields in a result object
@@ -88,7 +78,7 @@ interface DatabaseType {
* @param $res Mixed: A SQL result
* @return int
*/
- public function numFields( $res );
+ function numFields( $res );
/**
* Get a field name in a result object
@@ -98,7 +88,7 @@ interface DatabaseType {
* @param $n Integer
* @return string
*/
- public function fieldName( $res, $n );
+ function fieldName( $res, $n );
/**
* Get the inserted value of an auto-increment row
@@ -112,7 +102,7 @@ interface DatabaseType {
*
* @return int
*/
- public function insertId();
+ function insertId();
/**
* Change the position of the cursor in a result object
@@ -121,7 +111,7 @@ interface DatabaseType {
* @param $res Mixed: A SQL result
* @param $row Mixed: Either MySQL row or ResultWrapper
*/
- public function dataSeek( $res, $row );
+ function dataSeek( $res, $row );
/**
* Get the last error number
@@ -129,7 +119,7 @@ interface DatabaseType {
*
* @return int
*/
- public function lastErrno();
+ function lastErrno();
/**
* Get a description of the last error
@@ -137,7 +127,7 @@ interface DatabaseType {
*
* @return string
*/
- public function lastError();
+ function lastError();
/**
* mysql_fetch_field() wrapper
@@ -145,8 +135,10 @@ interface DatabaseType {
*
* @param $table string: table name
* @param $field string: field name
+ *
+ * @return Field
*/
- public function fieldInfo( $table, $field );
+ function fieldInfo( $table, $field );
/**
* Get information about an index into an object
@@ -163,7 +155,7 @@ interface DatabaseType {
*
* @return int
*/
- public function affectedRows();
+ function affectedRows();
/**
* Wrapper for addslashes()
@@ -171,7 +163,7 @@ interface DatabaseType {
* @param $s string: to be slashed.
* @return string: slashed string.
*/
- public function strencode( $s );
+ function strencode( $s );
/**
* Returns a wikitext link to the DB's website, e.g.,
@@ -181,7 +173,7 @@ interface DatabaseType {
*
* @return string: wikitext of a link to the server software's web site
*/
- public static function getSoftwareLink();
+ static function getSoftwareLink();
/**
* A string describing the current software version, like from
@@ -189,7 +181,7 @@ interface DatabaseType {
*
* @return string: Version information from the database server.
*/
- public function getServerVersion();
+ function getServerVersion();
/**
* A string describing the current software version, and possibly
@@ -198,7 +190,7 @@ interface DatabaseType {
*
* @return string: Version information from the database server
*/
- public function getServerInfo();
+ function getServerInfo();
}
/**
@@ -215,7 +207,12 @@ abstract class DatabaseBase implements DatabaseType {
protected $mDoneWrites = false;
protected $mPHPError = false;
- protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
+ protected $mServer, $mUser, $mPassword, $mDBname;
+
+ /**
+ * @var DatabaseBase
+ */
+ protected $mConn = null;
protected $mOpened = false;
protected $mTablePrefix;
@@ -244,15 +241,39 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Boolean, controls output of large amounts of debug information
+ * Boolean, controls output of large amounts of debug information.
+ * @param $debug:
+ * - true to enable debugging
+ * - false to disable debugging
+ * - omitted or null to do nothing
+ *
+ * @return The previous value of the flag
*/
function debug( $debug = null ) {
return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
}
/**
- * Turns buffering of SQL result sets on (true) or off (false).
- * Default is "on" and it should not be changed without good reasons.
+ * Turns buffering of SQL result sets on (true) or off (false). Default is
+ * "on".
+ *
+ * Unbuffered queries are very troublesome in MySQL:
+ *
+ * - If another query is executed while the first query is being read
+ * out, the first query is killed. This means you can't call normal
+ * MediaWiki functions while you are reading an unbuffered query result
+ * from a normal wfGetDB() connection.
+ *
+ * - Unbuffered queries cause the MySQL server to use large amounts of
+ * memory and to hold broad locks which block other queries.
+ *
+ * If you want to limit client-side memory, it's almost always better to
+ * split up queries into batches using a LIMIT clause than to switch off
+ * buffering.
+ *
+ * @param $buffer null|bool
+ *
+ * @return The previous value of the flag
*/
function bufferResults( $buffer = null ) {
if ( is_null( $buffer ) ) {
@@ -268,32 +289,50 @@ abstract class DatabaseBase implements DatabaseType {
* database errors. Default is on (false). When turned off, the
* code should use lastErrno() and lastError() to handle the
* situation as appropriate.
+ *
+ * @return The previous value of the flag.
*/
function ignoreErrors( $ignoreErrors = null ) {
return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
}
/**
- * The current depth of nested transactions
- * @param $level Integer: , default NULL.
+ * Gets or sets the current transaction level.
+ *
+ * Historically, transactions were allowed to be "nested". This is no
+ * longer supported, so this function really only returns a boolean.
+ *
+ * @param $level An integer (0 or 1), or omitted to leave it unchanged.
+ * @return The previous value
*/
function trxLevel( $level = null ) {
return wfSetVar( $this->mTrxLevel, $level );
}
/**
- * Number of errors logged, only useful when errors are ignored
+ * Get/set the number of errors logged. Only useful when errors are ignored
+ * @param $count The count to set, or omitted to leave it unchanged.
+ * @return The error count
*/
function errorCount( $count = null ) {
return wfSetVar( $this->mErrorCount, $count );
}
+ /**
+ * Get/set the table prefix.
+ * @param $prefix The table prefix to set, or omitted to leave it unchanged.
+ * @return The previous table prefix.
+ */
function tablePrefix( $prefix = null ) {
- return wfSetVar( $this->mTablePrefix, $prefix );
+ return wfSetVar( $this->mTablePrefix, $prefix, true );
}
/**
- * Properties passed down from the server info array of the load balancer
+ * Get properties passed down from the server info array of the load
+ * balancer.
+ *
+ * @param $name The entry of the info array to get, or null to get the
+ * whole array
*/
function getLBInfo( $name = null ) {
if ( is_null( $name ) ) {
@@ -307,6 +346,15 @@ abstract class DatabaseBase implements DatabaseType {
}
}
+ /**
+ * Set the LB info array, or a member of it. If called with one parameter,
+ * the LB info array is set to that parameter. If it is called with two
+ * parameters, the member with the given name is set to the given value.
+ *
+ * @param $name
+ * @param $value
+ * @return void
+ */
function setLBInfo( $name, $value = null ) {
if ( is_null( $value ) ) {
$this->mLBInfo = $name;
@@ -400,20 +448,28 @@ abstract class DatabaseBase implements DatabaseType {
* Return the last query that went through DatabaseBase::query()
* @return String
*/
- function lastQuery() { return $this->mLastQuery; }
+ function lastQuery() {
+ return $this->mLastQuery;
+ }
/**
* Returns true if the connection may have been used for write queries.
* Should return true if unsure.
+ *
+ * @return bool
*/
- function doneWrites() { return $this->mDoneWrites; }
+ function doneWrites() {
+ return $this->mDoneWrites;
+ }
/**
* Is a connection to the database open?
* @return Boolean
*/
- function isOpen() { return $this->mOpened; }
+ function isOpen() {
+ return $this->mOpened;
+ }
/**
* Set a flag for this connection
@@ -457,6 +513,9 @@ abstract class DatabaseBase implements DatabaseType {
return $this->$name;
}
+ /**
+ * @return string
+ */
function getWikiID() {
if ( $this->mTablePrefix ) {
return "{$this->mDBname}-{$this->mTablePrefix}";
@@ -466,9 +525,11 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Return a path to the DBMS-specific schema, otherwise default to tables.sql
+ * Return a path to the DBMS-specific schema file, otherwise default to tables.sql
+ *
+ * @return string
*/
- public function getSchema() {
+ public function getSchemaPath() {
global $IP;
if ( file_exists( "$IP/maintenance/" . $this->getType() . "/tables.sql" ) ) {
return "$IP/maintenance/" . $this->getType() . "/tables.sql";
@@ -493,12 +554,8 @@ abstract class DatabaseBase implements DatabaseType {
function __construct( $server = false, $user = false, $password = false, $dbName = false,
$flags = 0, $tablePrefix = 'get from global'
) {
- global $wgOut, $wgDBprefix, $wgCommandLineMode;
+ global $wgDBprefix, $wgCommandLineMode;
- # Can't get a reference if it hasn't been set yet
- if ( !isset( $wgOut ) ) {
- $wgOut = null;
- }
$this->mFlags = $flags;
if ( $this->mFlags & DBO_DEFAULT ) {
@@ -509,13 +566,6 @@ abstract class DatabaseBase implements DatabaseType {
}
}
- /*
- // Faster read-only access
- if ( wfReadOnly() ) {
- $this->mFlags |= DBO_PERSISTENT;
- $this->mFlags &= ~DBO_TRX;
- }*/
-
/** Get the default table prefix*/
if ( $tablePrefix == 'get from global' ) {
$this->mTablePrefix = $wgDBprefix;
@@ -523,14 +573,16 @@ abstract class DatabaseBase implements DatabaseType {
$this->mTablePrefix = $tablePrefix;
}
- if ( $server ) {
+ if ( $user ) {
$this->open( $server, $user, $password, $dbName );
}
}
/**
* Same as new DatabaseMysql( ... ), kept for backward compatibility
- * @deprecated
+ * @deprecated since 1.17
+ *
+ * @return DatabaseMysql
*/
static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
wfDeprecated( __METHOD__ );
@@ -554,10 +606,10 @@ abstract class DatabaseBase implements DatabaseType {
*
* @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
+ * Valid options are: host, user, password, dbname, flags, tablePrefix
* @return DatabaseBase subclass or null
*/
- public final static function newFromType( $dbType, $p = array() ) {
+ public final static function factory( $dbType, $p = array() ) {
$canonicalDBTypes = array(
'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2'
);
@@ -571,7 +623,7 @@ abstract class DatabaseBase implements DatabaseType {
isset( $p['password'] ) ? $p['password'] : false,
isset( $p['dbname'] ) ? $p['dbname'] : false,
isset( $p['flags'] ) ? $p['flags'] : 0,
- isset( $p['tableprefix'] ) ? $p['tableprefix'] : 'get from global'
+ isset( $p['tablePrefix'] ) ? $p['tablePrefix'] : 'get from global'
);
} else {
return null;
@@ -627,36 +679,51 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * The DBMS-dependent part of query()
+ *
+ * @param $sql String: SQL query.
+ * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure
+ */
+ protected abstract function doQuery( $sql );
+
+ /**
* Determine whether a query writes to the DB.
* Should return true if unsure.
+ *
+ * @return bool
*/
function isWriteQuery( $sql ) {
return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW|\(SELECT)\b/i', $sql );
}
/**
- * Usually aborts on failure. If errors are explicitly ignored, returns success.
+ * Run an SQL query and return the result. Normally throws a DBQueryError
+ * on failure. If errors are ignored, returns false instead.
+ *
+ * In new code, the query wrappers select(), insert(), update(), delete(),
+ * etc. should be used where possible, since they give much better DBMS
+ * independence and automatically quote or validate user input in a variety
+ * of contexts. This function is generally only useful for queries which are
+ * explicitly DBMS-dependent and are unsupported by the query wrappers, such
+ * as CREATE TABLE.
+ *
+ * However, the query wrappers themselves should call this function.
*
* @param $sql String: SQL query
* @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST
* comment (you can use __METHOD__ or add some extra info)
* @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
* maybe best to catch the exception instead?
- * @return boolean or ResultWrapper. true for a successful write query, ResultWrapper object for a successful read query,
- * or false on failure if $tempIgnore set
+ * @return boolean|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
*/
public function query( $sql, $fname = '', $tempIgnore = false ) {
- global $wgProfiler;
-
$isMaster = !is_null( $this->getLBInfo( 'master' ) );
- if ( isset( $wgProfiler ) ) {
+ if ( !Profiler::instance()->isStub() ) {
# generalizeSQL will probably cut down the query to reasonable
# logging size most of the time. The substr is really just a sanity check.
- # Who's been wasting my precious column space? -- TS
- # $profName = 'query: ' . $fname . ' ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
-
if ( $isMaster ) {
$queryProf = 'query-m: ' . substr( DatabaseBase::generalizeSQL( $sql ), 0, 255 );
$totalProf = 'DatabaseBase::query-master';
@@ -671,34 +738,30 @@ abstract class DatabaseBase implements DatabaseType {
$this->mLastQuery = $sql;
if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
- // Set a flag indicating that writes have been done
+ # Set a flag indicating that writes have been done
wfDebug( __METHOD__ . ": Writes done: $sql\n" );
$this->mDoneWrites = true;
}
# Add a comment for easy SHOW PROCESSLIST interpretation
- # if ( $fname ) {
- global $wgUser;
- if ( is_object( $wgUser ) && $wgUser->mDataLoaded ) {
- $userName = $wgUser->getName();
- if ( mb_strlen( $userName ) > 15 ) {
- $userName = mb_substr( $userName, 0, 15 ) . '...';
- }
- $userName = str_replace( '/', '', $userName );
- } else {
- $userName = '';
+ global $wgUser;
+ if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
+ $userName = $wgUser->getName();
+ if ( mb_strlen( $userName ) > 15 ) {
+ $userName = mb_substr( $userName, 0, 15 ) . '...';
}
- $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 );
- # } else {
- # $commentedSql = $sql;
- # }
+ $userName = str_replace( '/', '', $userName );
+ } else {
+ $userName = '';
+ }
+ $commentedSql = preg_replace( '/\s/', " /* $fname $userName */ ", $sql, 1 );
# If DBO_TRX is set, start a transaction
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
- // is really used by application
+ # avoid establishing transactions for SHOW and SET statements too -
+ # that would delay transaction initializations to once connection
+ # is really used by application
$sqlstart = substr( $sql, 0, 10 ); // very much worth it, benchmark certified(tm)
if ( strpos( $sqlstart, "SHOW " ) !== 0 and strpos( $sqlstart, "SET " ) !== 0 )
$this->begin();
@@ -751,7 +814,7 @@ abstract class DatabaseBase implements DatabaseType {
$this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
}
- if ( isset( $wgProfiler ) ) {
+ if ( !Profiler::instance()->isStub() ) {
wfProfileOut( $queryProf );
wfProfileOut( $totalProf );
}
@@ -760,6 +823,9 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Report a query error. Log the error, and if neither the object ignore
+ * flag nor the $tempIgnore flag is set, throw a DBQueryError.
+ *
* @param $error String
* @param $errno Integer
* @param $sql String
@@ -782,7 +848,6 @@ abstract class DatabaseBase implements DatabaseType {
}
}
-
/**
* Intended to be compatible with the PEAR::DB wrapper functions.
* http://pear.php.net/manual/en/package.database.db.intro-execute.php
@@ -791,6 +856,12 @@ abstract class DatabaseBase implements DatabaseType {
* ! = raw SQL bit (a function for instance)
* & = filename; reads the file and inserts as a blob
* (we don't use this though...)
+ *
+ * This function should not be used directly by new code outside of the
+ * database classes. The query wrapper functions (select() etc.) should be
+ * used instead.
+ *
+ * @return array
*/
function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
/* MySQL doesn't support prepared statements (yet), so just
@@ -799,6 +870,9 @@ abstract class DatabaseBase implements DatabaseType {
return array( 'query' => $sql, 'func' => $func );
}
+ /**
+ * Free a prepared query, generated by prepare().
+ */
function freePrepared( $prepared ) {
/* No-op by default */
}
@@ -807,6 +881,8 @@ abstract class DatabaseBase implements DatabaseType {
* Execute a prepared query with the various arguments
* @param $prepared String: the prepared sql
* @param $args Mixed: Either an array here, or put scalars as varargs
+ *
+ * @return ResultWrapper
*/
function execute( $prepared, $args = null ) {
if ( !is_array( $args ) ) {
@@ -823,8 +899,15 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Prepare & execute an SQL statement, quoting and inserting arguments
* in the appropriate places.
+ *
+ * This function should not be used directly by new code outside of the
+ * database classes. The query wrapper functions (select() etc.) should be
+ * used instead.
+ *
* @param $query String
* @param $args ...
+ *
+ * @return ResultWrapper
*/
function safeQuery( $query, $args = null ) {
$prepared = $this->prepare( $query, 'DatabaseBase::safeQuery' );
@@ -886,21 +969,24 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Free a result object
+ * Free a result object returned by query() or select(). It's usually not
+ * necessary to call this, just use unset() or let the variable holding
+ * the result object go out of scope.
+ *
* @param $res Mixed: A SQL result
*/
function freeResult( $res ) {
- # Stub. Might not really need to be overridden, since results should
- # be freed by PHP when the variable goes out of scope anyway.
}
/**
- * Simple UPDATE wrapper
- * Usually aborts on failure
+ * Simple UPDATE wrapper.
+ * Usually throws a DBQueryError on failure.
* If errors are explicitly ignored, returns success
*
* This function exists for historical reasons, DatabaseBase::update() has a more standard
* calling convention and feature set
+ *
+ * @return bool
*/
function set( $table, $var, $value, $cond, $fname = 'DatabaseBase::set' ) {
$table = $this->tableName( $table );
@@ -911,11 +997,25 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Simple SELECT wrapper, returns a single field, input must be encoded
- * Usually aborts on failure
- * If errors are explicitly ignored, returns FALSE on failure
+ * A SELECT wrapper which returns a single field from a single result row.
+ *
+ * Usually throws a DBQueryError on failure. If errors are explicitly
+ * ignored, returns false on failure.
+ *
+ * If no result rows are returned from the query, false is returned.
+ *
+ * @param $table string|array Table name. See DatabaseBase::select() for details.
+ * @param $var string The field name to select. This must be a valid SQL
+ * fragment: do not use unvalidated user input.
+ * @param $cond string|array The condition array. See DatabaseBase::select() for details.
+ * @param $fname string The function name of the caller.
+ * @param $options string|array The query options. See DatabaseBase::select() for details.
+ *
+ * @return false|mixed The value from the field, or false on failure.
*/
- function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField', $options = array() ) {
+ function selectField( $table, $var, $cond = '', $fname = 'DatabaseBase::selectField',
+ $options = array() )
+ {
if ( !is_array( $options ) ) {
$options = array( $options );
}
@@ -939,13 +1039,12 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns an optional USE INDEX clause to go after the table, and a
- * string to go at the end of the query
- *
- * @private
+ * string to go at the end of the query.
*
* @param $options Array: associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
* @return Array
+ * @see DatabaseBase::select()
*/
function makeSelectOptions( $options ) {
$preLimitTail = $postLimitTail = '';
@@ -960,7 +1059,10 @@ abstract class DatabaseBase implements DatabaseType {
}
if ( isset( $options['GROUP BY'] ) ) {
- $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
+ $gb = is_array( $options['GROUP BY'] )
+ ? implode( ',', $options['GROUP BY'] )
+ : $options['GROUP BY'];
+ $preLimitTail .= " GROUP BY {$gb}";
}
if ( isset( $options['HAVING'] ) ) {
@@ -968,7 +1070,10 @@ abstract class DatabaseBase implements DatabaseType {
}
if ( isset( $options['ORDER BY'] ) ) {
- $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
+ $ob = is_array( $options['ORDER BY'] )
+ ? implode( ',', $options['ORDER BY'] )
+ : $options['ORDER BY'];
+ $preLimitTail .= " ORDER BY {$ob}";
}
// if (isset($options['LIMIT'])) {
@@ -1032,49 +1137,176 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * SELECT wrapper
+ * Execute a SELECT query constructed using the various parameters provided.
+ * See below for full details of the parameters.
+ *
+ * @param $table String|Array Table name
+ * @param $vars String|Array Field names
+ * @param $conds String|Array Conditions
+ * @param $fname String Caller function name
+ * @param $options Array Query options
+ * @param $join_conds Array Join conditions
+ *
+ *
+ * @param $table string|array
+ *
+ * May be either an array of table names, or a single string holding a table
+ * name. If an array is given, table aliases can be specified, for example:
+ *
+ * array( 'a' => 'user' )
+ *
+ * This includes the user table in the query, with the alias "a" available
+ * for use in field names (e.g. a.user_name).
+ *
+ * All of the table names given here are automatically run through
+ * DatabaseBase::tableName(), which causes the table prefix (if any) to be
+ * added, and various other table name mappings to be performed.
+ *
+ *
+ * @param $vars string|array
+ *
+ * May be either a field name or an array of field names. The field names
+ * here are complete fragments of SQL, for direct inclusion into the SELECT
+ * query. Expressions and aliases may be specified as in SQL, for example:
+ *
+ * array( 'MAX(rev_id) AS maxrev' )
+ *
+ * If an expression is given, care must be taken to ensure that it is
+ * DBMS-independent.
+ *
+ *
+ * @param $conds string|array
+ *
+ * May be either a string containing a single condition, or an array of
+ * conditions. If an array is given, the conditions constructed from each
+ * element are combined with AND.
+ *
+ * Array elements may take one of two forms:
+ *
+ * - Elements with a numeric key are interpreted as raw SQL fragments.
+ * - Elements with a string key are interpreted as equality conditions,
+ * where the key is the field name.
+ * - If the value of such an array element is a scalar (such as a
+ * string), it will be treated as data and thus quoted appropriately.
+ * If it is null, an IS NULL clause will be added.
+ * - If the value is an array, an IN(...) clause will be constructed,
+ * such that the field name may match any of the elements in the
+ * array. The elements of the array will be quoted.
+ *
+ * Note that expressions are often DBMS-dependent in their syntax.
+ * DBMS-independent wrappers are provided for constructing several types of
+ * expression commonly used in condition queries. See:
+ * - DatabaseBase::buildLike()
+ * - DatabaseBase::conditional()
+ *
+ *
+ * @param $options string|array
+ *
+ * Optional: Array of query options. Boolean options are specified by
+ * including them in the array as a string value with a numeric key, for
+ * example:
+ *
+ * array( 'FOR UPDATE' )
+ *
+ * The supported options are:
+ *
+ * - OFFSET: Skip this many rows at the start of the result set. OFFSET
+ * with LIMIT can theoretically be used for paging through a result set,
+ * but this is discouraged in MediaWiki for performance reasons.
+ *
+ * - LIMIT: Integer: return at most this many rows. The rows are sorted
+ * and then the first rows are taken until the limit is reached. LIMIT
+ * is applied to a result set after OFFSET.
+ *
+ * - FOR UPDATE: Boolean: lock the returned rows so that they can't be
+ * changed until the next COMMIT.
+ *
+ * - DISTINCT: Boolean: return only unique result rows.
+ *
+ * - GROUP BY: May be either an SQL fragment string naming a field or
+ * expression to group by, or an array of such SQL fragments.
+ *
+ * - HAVING: A string containing a HAVING clause.
+ *
+ * - ORDER BY: May be either an SQL fragment giving a field name or
+ * expression to order by, or an array of such SQL fragments.
+ *
+ * - USE INDEX: This may be either a string giving the index name to use
+ * for the query, or an array. If it is an associative array, each key
+ * gives the table name (or alias), each value gives the index name to
+ * use for that table. All strings are SQL fragments and so should be
+ * validated by the caller.
+ *
+ * - EXPLAIN: In MySQL, this causes an EXPLAIN SELECT query to be run,
+ * instead of SELECT.
+ *
+ * And also the following boolean MySQL extensions, see the MySQL manual
+ * for documentation:
+ *
+ * - LOCK IN SHARE MODE
+ * - STRAIGHT_JOIN
+ * - HIGH_PRIORITY
+ * - SQL_BIG_RESULT
+ * - SQL_BUFFER_RESULT
+ * - SQL_SMALL_RESULT
+ * - SQL_CALC_FOUND_ROWS
+ * - SQL_CACHE
+ * - SQL_NO_CACHE
+ *
*
- * @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 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 DatabaseBase::fetchObject or whatever), or false on failure
+ * @param $join_conds string|array
+ *
+ * Optional associative array of table-specific join conditions. In the
+ * most common case, this is unnecessary, since the join condition can be
+ * in $conds. However, it is useful for doing a LEFT JOIN.
+ *
+ * The key of the array contains the table name or alias. The value is an
+ * array with two elements, numbered 0 and 1. The first gives the type of
+ * join, the second is an SQL fragment giving the join condition for that
+ * table. For example:
+ *
+ * array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ *
+ * @return ResultWrapper. If the query returned no rows, a ResultWrapper
+ * with no rows in it will be returned. If there was a query error, a
+ * DBQueryError exception will be thrown, except if the "ignore errors"
+ * option was set, in which case false will be returned.
*/
- function select( $table, $vars, $conds = '', $fname = 'DatabaseBase::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
+ * The equivalent of DatabaseBase::select() except that the constructed SQL
+ * is returned, instead of being immediately executed.
+ *
+ * @param $table string|array Table name
+ * @param $vars string|array Field names
+ * @param $conds string|array Conditions
+ * @param $fname string Caller function name
+ * @param $options string|array Query options
+ * @param $join_conds string|array Join conditions
*
- * @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 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
+ * @return SQL query string.
+ * @see DatabaseBase::select()
*/
function selectSQLText( $table, $vars, $conds = '', $fname = 'DatabaseBase::select', $options = array(), $join_conds = array() ) {
if ( is_array( $vars ) ) {
$vars = implode( ',', $vars );
}
- if ( !is_array( $options ) ) {
- $options = array( $options );
- }
+ $options = (array)$options;
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 );
+ $useIndex = ( isset( $options['USE INDEX'] ) && is_array( $options['USE INDEX'] ) )
+ ? $options['USE INDEX']
+ : array();
+ if ( count( $join_conds ) || count( $useIndex ) ) {
+ $from = ' FROM ' .
+ $this->tableNamesWithUseIndexOrJOIN( $table, $useIndex, $join_conds );
} else {
$from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
}
@@ -1099,9 +1331,10 @@ abstract class DatabaseBase implements DatabaseType {
$sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
}
- if ( isset( $options['LIMIT'] ) )
+ if ( isset( $options['LIMIT'] ) ) {
$sql = $this->limitResult( $sql, $options['LIMIT'],
isset( $options['OFFSET'] ) ? $options['OFFSET'] : false );
+ }
$sql = "$sql $postLimitTail";
if ( isset( $options['EXPLAIN'] ) ) {
@@ -1112,24 +1345,22 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Single row SELECT wrapper
- * Aborts or returns FALSE on error
+ * Single row SELECT wrapper. Equivalent to DatabaseBase::select(), except
+ * that a single row object is returned. If the query returns no rows,
+ * false is returned.
*
- * @param $table String: table name
- * @param $vars String: the selected variables
- * @param $conds Array: a condition map, terms are ANDed together.
- * Items with numeric keys are taken to be literal conditions
- * Takes an array of selected variables, and a condition map, which is ANDed
- * e.g: selectRow( "page", array( "page_id" ), array( "page_namespace" =>
- * NS_MAIN, "page_title" => "Astronomy" ) ) would return an object where
- * $obj- >page_id is the ID of the Astronomy article
- * @param $fname String: Calling function name
- * @param $options Array
- * @param $join_conds Array
+ * @param $table string|array Table name
+ * @param $vars string|array Field names
+ * @param $conds|array Conditions
+ * @param $fname string Caller function name
+ * @param $options string|array Query options
+ * @param $join_conds array|string Join conditions
*
- * @todo migrate documentation to phpdocumentor format
+ * @return ResultWrapper|bool
*/
- function selectRow( $table, $vars, $conds, $fname = 'DatabaseBase::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 );
@@ -1147,10 +1378,17 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Estimate rows in dataset
- * Returns estimated count - not necessarily an accurate estimate across different databases,
- * so use sparingly
- * Takes same arguments as DatabaseBase::select()
+ * Estimate rows in dataset.
+ *
+ * MySQL allows you to estimate the number of rows that would be returned
+ * by a SELECT query, using EXPLAIN SELECT. The estimate is provided using
+ * index cardinality statistics, and is notoriously inaccurate, especially
+ * when large numbers of rows have recently been added or deleted.
+ *
+ * For DBMSs that don't support fast result size estimation, this function
+ * will actually perform the SELECT COUNT(*).
+ *
+ * Takes the same arguments as DatabaseBase::select().
*
* @param $table String: table name
* @param $vars Array: unused
@@ -1159,7 +1397,9 @@ abstract class DatabaseBase implements DatabaseType {
* @param $options Array: options for select
* @return Integer: row count
*/
- public function estimateRowCount( $table, $vars = '*', $conds = '', $fname = 'DatabaseBase::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 );
@@ -1213,8 +1453,10 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Determines whether an index exists
- * Usually aborts on failure
+ * Usually throws a DBQueryError on failure
* If errors are explicitly ignored, returns NULL on failure
+ *
+ * @return bool|null
*/
function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
$info = $this->indexInfo( $table, $index, $fname );
@@ -1227,6 +1469,10 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Query whether a given table exists
+ *
+ * @string table
+ *
+ * @return bool
*/
function tableExists( $table ) {
$table = $this->tableName( $table );
@@ -1250,6 +1496,11 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Determines if a given index is unique
+ *
+ * @param $table string
+ * @param $index string
+ *
+ * @return bool
*/
function indexUnique( $table, $index ) {
$indexInfo = $this->indexInfo( $table, $index );
@@ -1262,18 +1513,45 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * INSERT wrapper, inserts an array into a table
+ * Helper for DatabaseBase::insert().
+ *
+ * @param $options array
+ * @return string
+ */
+ function makeInsertOptions( $options ) {
+ return implode( ' ', $options );
+ }
+
+ /**
+ * INSERT wrapper, inserts an array into a table.
*
- * $a may be a single associative array, or an array of these with numeric keys, for
- * multi-row insert.
+ * $a may be either:
*
- * Usually aborts on failure
- * If errors are explicitly ignored, returns success
+ * - A single associative array. The array keys are the field names, and
+ * the values are the values to insert. The values are treated as data
+ * and will be quoted appropriately. If NULL is inserted, this will be
+ * converted to a database NULL.
+ * - An array with numeric keys, holding a list of associative arrays.
+ * This causes a multi-row INSERT on DBMSs that support it. The keys in
+ * each subarray must be identical to each other, and in the same order.
+ *
+ * Usually throws a DBQueryError on failure. If errors are explicitly ignored,
+ * returns success.
+ *
+ * $options is an array of options, with boolean options encoded as values
+ * with numeric keys, in the same style as $options in
+ * DatabaseBase::select(). Supported options are:
+ *
+ * - IGNORE: Boolean: if present, duplicate key errors are ignored, and
+ * any rows which cause duplicate key errors are not inserted. It's
+ * possible to determine how many rows were successfully inserted using
+ * DatabaseBase::affectedRows().
*
- * @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
+ * @param $table String Table name. This will be passed through
+ * DatabaseBase::tableName().
+ * @param $a Array of rows to insert
+ * @param $fname String Calling function name (use __METHOD__) for logs/profiling
+ * @param $options Array of options
*
* @return bool
*/
@@ -1289,6 +1567,8 @@ abstract class DatabaseBase implements DatabaseType {
$options = array( $options );
}
+ $options = $this->makeInsertOptions( $options );
+
if ( isset( $a[0] ) && is_array( $a[0] ) ) {
$multi = true;
$keys = array_keys( $a[0] );
@@ -1297,7 +1577,7 @@ abstract class DatabaseBase implements DatabaseType {
$keys = array_keys( $a );
}
- $sql = 'INSERT ' . implode( ' ', $options ) .
+ $sql = 'INSERT ' . $options .
" INTO $table (" . implode( ',', $keys ) . ') VALUES ';
if ( $multi ) {
@@ -1320,7 +1600,6 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Make UPDATE options for the DatabaseBase::update function
*
- * @private
* @param $options Array: The options passed to DatabaseBase::update
* @return string
*/
@@ -1343,15 +1622,26 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * UPDATE wrapper, takes a condition array and a SET array
+ * UPDATE wrapper. Takes a condition array and a SET array.
+ *
+ * @param $table String name of the table to UPDATE. This will be passed through
+ * DatabaseBase::tableName().
+ *
+ * @param $values Array: An array of values to SET. For each array element,
+ * the key gives the field name, and the value gives the data
+ * to set that field to. The data will be quoted by
+ * DatabaseBase::addQuotes().
*
- * @param $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
+ * @param $conds Array: An array of conditions (WHERE). See
+ * DatabaseBase::select() for the details of the format of
+ * condition arrays. Use '*' to update all rows.
+ *
+ * @param $fname String: The function name of the caller (from __METHOD__),
+ * for logging and profiling.
+ *
+ * @param $options Array: An array of UPDATE options, can be:
+ * - IGNORE: Ignore unique key conflicts
+ * - LOW_PRIORITY: MySQL-specific, see MySQL manual.
* @return Boolean
*/
function update( $table, $values, $conds, $fname = 'DatabaseBase::update', $options = array() ) {
@@ -1368,12 +1658,16 @@ abstract class DatabaseBase implements DatabaseType {
/**
* 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
+ * @param $a Array containing the data
+ * @param $mode:
+ * - LIST_COMMA: comma separated, no field names
+ * - LIST_AND: ANDed WHERE clause (without the WHERE). See
+ * the documentation for $conds in DatabaseBase::select().
+ * - LIST_OR: ORed WHERE clause (without the WHERE)
+ * - LIST_SET: comma separated with field names, like a SET clause
+ * - LIST_NAMES: comma separated field names
+ *
+ * @return string
*/
function makeList( $a, $mode = LIST_COMMA ) {
if ( !is_array( $a ) ) {
@@ -1434,7 +1728,8 @@ abstract class DatabaseBase implements DatabaseType {
* Build a partial where clause from a 2-d array such as used for LinkBatch.
* The keys on each level may be either integers or strings.
*
- * @param $data Array: organized as 2-d array(baseKeyVal => array(subKeyVal => <ignored>, ...), ...)
+ * @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.
@@ -1462,14 +1757,28 @@ abstract class DatabaseBase implements DatabaseType {
* Bitwise operations
*/
+ /**
+ * @param $field
+ * @return string
+ */
function bitNot( $field ) {
return "(~$field)";
}
+ /**
+ * @param $fieldLeft
+ * @param $fieldRight
+ * @return string
+ */
function bitAnd( $fieldLeft, $fieldRight ) {
return "($fieldLeft & $fieldRight)";
}
+ /**
+ * @param $fieldLeft
+ * @param $fieldRight
+ * @return string
+ */
function bitOr( $fieldLeft, $fieldRight ) {
return "($fieldLeft | $fieldRight)";
}
@@ -1484,6 +1793,7 @@ abstract class DatabaseBase implements DatabaseType {
# Stub. Shouldn't cause serious problems if it's not overridden, but
# if your database engine supports a concept similar to MySQL's
# databases you may as well.
+ $this->mDBname = $db;
return true;
}
@@ -1512,15 +1822,17 @@ abstract class DatabaseBase implements DatabaseType {
* when calling query() directly.
*
* @param $name String: database table name
+ * @param $quoted Boolean: Automatically pass the table name through
+ * addIdentifierQuotes() so that it can be used in a query.
* @return String: full database name
*/
- function tableName( $name ) {
+ function tableName( $name, $quoted = true ) {
global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
# Skip the entire process when we have a string quoted on both ends.
# Note that we check the end so that we will still quote any use of
# use of `database`.table. But won't break things if someone wants
# to query a database table with a dot in the name.
- if ( $name[0] == '`' && substr( $name, -1, 1 ) == '`' ) {
+ if ( $this->isQuotedIdentifier( $name ) ) {
return $name;
}
@@ -1540,23 +1852,23 @@ abstract class DatabaseBase implements DatabaseType {
# the correct table.
$dbDetails = array_reverse( explode( '.', $name, 2 ) );
if ( isset( $dbDetails[1] ) ) {
- @list( $table, $database ) = $dbDetails;
+ list( $table, $database ) = $dbDetails;
} else {
- @list( $table ) = $dbDetails;
+ 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.
+ # A database name has been specified in input. We don't want any
+ # prefixes added.
if ( isset( $database ) ) {
- $table = ( $table[0] == '`' ? $table : "`{$table}`" );
+ $prefix = '';
}
# 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.
&& isset( $wgSharedDB ) # We have a shared database
- && $table[0] != '`' # Paranoia check to prevent shared tables listing '`table`'
+ && !$this->isQuotedIdentifier( $table ) # Paranoia check to prevent shared tables listing '`table`'
&& isset( $wgSharedTables )
&& is_array( $wgSharedTables )
&& in_array( $table, $wgSharedTables ) ) { # A shared table is selected
@@ -1566,9 +1878,13 @@ abstract class DatabaseBase implements DatabaseType {
# Quote the $database and $table and apply the prefix if not quoted.
if ( isset( $database ) ) {
- $database = ( $database[0] == '`' ? $database : "`{$database}`" );
+ $database = ( !$quoted || $this->isQuotedIdentifier( $database ) ? $database : $this->addIdentifierQuotes( $database ) );
+ }
+
+ $table = "{$prefix}{$table}";
+ if ( $quoted && !$this->isQuotedIdentifier( $table ) ) {
+ $table = $this->addIdentifierQuotes( "{$table}" );
}
- $table = ( $table[0] == '`' ? $table : "`{$prefix}{$table}`" );
# Merge our database and table into our final table name.
$tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
@@ -1650,45 +1966,54 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * @private
+ * Get the aliased table name clause for a FROM clause
+ * which might have a JOIN and/or USE INDEX clause
+ *
+ * @param $tables array( [alias] => table )
+ * @param $use_index array() Same as for select()
+ * @param $join_conds array() Same as for select()
+ * @return string
*/
- function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
+ protected 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();
+ $use_index = (array)$use_index;
+ $join_conds = (array)$join_conds;
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[$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 );
+ // Is there a JOIN clause for this table?
+ if ( isset( $join_conds[$alias] ) ) {
+ list( $joinType, $conds ) = $join_conds[$alias];
+ $tableClause = $joinType;
+ $tableClause .= ' ' . $this->tableNameWithAlias( $table, $alias );
+ if ( isset( $use_index[$alias] ) ) { // has USE INDEX?
+ $use = $this->useIndexClause( implode( ',', (array)$use_index[$alias] ) );
+ if ( $use != '' ) {
+ $tableClause .= ' ' . $use;
+ }
+ }
+ $on = $this->makeList( (array)$conds, LIST_AND );
if ( $on != '' ) {
$tableClause .= ' ON (' . $on . ')';
}
$retJOIN[] = $tableClause;
- // Is there an INDEX clause?
- } else if ( isset( $use_index_safe[$alias] ) ) {
+ // Is there an INDEX clause for this table?
+ } elseif ( isset( $use_index[$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[$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 . ')';
- }
+ $tableClause .= ' ' . $this->useIndexClause(
+ implode( ',', (array)$use_index[$alias] ) );
- $retJOIN[] = $tableClause;
+ $ret[] = $tableClause;
} else {
$tableClause = $this->tableNameWithAlias( $table, $alias );
+
$ret[] = $tableClause;
}
}
@@ -1703,6 +2028,10 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Get the name of an index in a given table
+ *
+ * @param $index
+ *
+ * @return string
*/
function indexName( $index ) {
// Backwards-compatibility hack
@@ -1722,6 +2051,10 @@ abstract class DatabaseBase implements DatabaseType {
/**
* If it's a string, adds quotes and backslashes
* Otherwise returns as-is
+ *
+ * @param $s string
+ *
+ * @return string
*/
function addQuotes( $s ) {
if ( $s === null ) {
@@ -1740,16 +2073,32 @@ abstract class DatabaseBase implements DatabaseType {
* 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.
- */
+ *
+ * @return string
+ */
public function addIdentifierQuotes( $s ) {
return '"' . str_replace( '"', '""', $s ) . '"';
}
/**
+ * Returns if the given identifier looks quoted or not according to
+ * the database convention for quoting identifiers .
+ *
+ * @param $name string
+ *
+ * @return boolean
+ */
+ public function isQuotedIdentifier( $name ) {
+ return $name[0] == '"' && substr( $name, -1, 1 ) == '"';
+ }
+
+ /**
* 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
+ * @deprecated since 1.18 use addIdentifierQuotes
+ *
+ * @return string
*/
function quote_ident( $s ) {
wfDeprecated( __METHOD__ );
@@ -1760,7 +2109,7 @@ abstract class DatabaseBase implements DatabaseType {
* 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 ???
+ * @deprecated since 1.17, warnings in 1.17, removed in ???
*/
public function escapeLike( $s ) {
wfDeprecated( __METHOD__ );
@@ -1809,6 +2158,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns a token for buildLike() that denotes a '_' to be used in a LIKE query
+ *
+ * @return LikeMatch
*/
function anyChar() {
return new LikeMatch( '_' );
@@ -1816,6 +2167,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
+ *
+ * @rerturn LikeMatch
*/
function anyString() {
return new LikeMatch( '%' );
@@ -1843,15 +2196,24 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * 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
+ * REPLACE query wrapper.
*
- * 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 is a very handy MySQL extension, which functions like an INSERT
+ * except that when there is a duplicate key error, the old row is deleted
+ * and the new row is inserted in its place.
+ *
+ * We simulate this with standard SQL with a DELETE followed by INSERT. To
+ * perform the delete, we need to know what the unique indexes are so that
+ * we know how to find the conflicting rows.
+ *
+ * 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 $rows Can be either a single row to insert, or multiple rows,
+ * in the same format as for DatabaseBase::insert()
+ * @param $uniqueIndexes is an array of indexes. Each element may be either
+ * a field name or an array of field names
*
* @param $table String: The table to replace the row(s) in.
* @param $uniqueIndexes Array: An associative array of indexes
@@ -1859,6 +2221,61 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
*/
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
+ $quotedTable = $this->tableName( $table );
+
+ if ( count( $rows ) == 0 ) {
+ return;
+ }
+
+ # Single row case
+ if ( !is_array( reset( $rows ) ) ) {
+ $rows = array( $rows );
+ }
+
+ foreach( $rows as $row ) {
+ # Delete rows which collide
+ if ( $uniqueIndexes ) {
+ $sql = "DELETE FROM $quotedTable 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 );
+ }
+
+ # Now insert the row
+ $this->insert( $table, $row );
+ }
+ }
+
+ /**
+ * REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE
+ * statement.
+ *
+ * @param $table Table name
+ * @param $rows Rows to insert
+ * @param $fname Caller function name
+ */
+ protected function nativeReplace( $table, $rows, $fname ) {
$table = $this->tableName( $table );
# Single row case
@@ -1883,35 +2300,42 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * DELETE where the condition is a join
- * MySQL does this with a multi-table DELETE syntax, PostgreSQL does it with sub-selects
+ * DELETE where the condition is a join.
*
- * For safety, an empty $conds will not delete everything. If you want to delete all rows where the
- * join condition matches, set $conds='*'
+ * MySQL overrides this to use a multi-table DELETE syntax, in other databases
+ * we use sub-selects
*
- * DO NOT put the join condition in $conds
+ * For safety, an empty $conds will not delete everything. If you want to
+ * delete all rows where the join condition matches, set $conds='*'.
*
- * @param $delTable String: The table to delete from.
- * @param $joinTable String: The other table.
- * @param $delVar String: The variable to join on, in the first table.
- * @param $joinVar String: The variable to join on, in the second table.
- * @param $conds Array: Condition array of field names mapped to variables, ANDed together in the WHERE clause
- * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
+ * DO NOT put the join condition in $conds.
+ *
+ * @param $delTable String: The table to delete from.
+ * @param $joinTable String: The other table.
+ * @param $delVar String: The variable to join on, in the first table.
+ * @param $joinVar String: The variable to join on, in the second table.
+ * @param $conds Array: Condition array of field names mapped to variables,
+ * ANDed together in the WHERE clause
+ * @param $fname String: Calling function name (use __METHOD__) for
+ * logs/profiling
*/
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds,
+ $fname = 'DatabaseBase::deleteJoin' )
+ {
if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabaseBase::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 ";
-
+ $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 );
}
/**
@@ -1939,16 +2363,22 @@ abstract class DatabaseBase implements DatabaseType {
* MySQL's LOW_PRIORITY. If no such feature exists, return an empty
* string and nothing bad should happen.
*
- * @return string Returns the text of the low priority option if it is supported, or a blank string otherwise
+ * @return string Returns the text of the low priority option if it is
+ * supported, or a blank string otherwise
*/
function lowPriorityOption() {
return '';
}
/**
- * DELETE query wrapper
+ * DELETE query wrapper.
*
- * Use $conds == "*" to delete all rows
+ * @param $table Array Table name
+ * @param $conds String|Array of conditions. See $conds in DatabaseBase::select() for
+ * the format. Use $conds == "*" to delete all rows
+ * @param $fname String name of the calling function
+ *
+ * @return bool
*/
function delete( $table, $conds, $fname = 'DatabaseBase::delete' ) {
if ( !$conds ) {
@@ -1966,13 +2396,33 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * 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 DatabaseBase::addQuotes()
- * $conds may be "*" to copy the whole table
- * srcTable may be an array of tables.
+ * INSERT SELECT wrapper. Takes data from a SELECT query and inserts it
+ * into another table.
+ *
+ * @param $destTable The table name to insert into
+ * @param $srcTable May be either a table name, or an array of table names
+ * to include in a join.
+ *
+ * @param $varMap must be an associative array of the form
+ * array( 'dest1' => 'source1', ...). Source items may be literals
+ * rather than field names, but strings should be quoted with
+ * DatabaseBase::addQuotes()
+ *
+ * @param $conds Condition array. See $conds in DatabaseBase::select() for
+ * the details of the format of condition arrays. May be "*" to copy the
+ * whole table.
+ *
+ * @param $fname The function name of the caller, from __METHOD__
+ *
+ * @param $insertOptions Options for the INSERT part of the query, see
+ * DatabaseBase::insert() for details.
+ * @param $selectOptions Options for the SELECT part of the query, see
+ * DatabaseBase::select() for details.
+ *
+ * @return ResultWrapper
*/
- function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseBase::insertSelect',
+ function insertSelect( $destTable, $srcTable, $varMap, $conds,
+ $fname = 'DatabaseBase::insertSelect',
$insertOptions = array(), $selectOptions = array() )
{
$destTable = $this->tableName( $destTable );
@@ -2023,6 +2473,8 @@ abstract class DatabaseBase implements DatabaseType {
* @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)
+ *
+ * @return string
*/
function limitResult( $sql, $limit, $offset = false ) {
if ( !is_numeric( $limit ) ) {
@@ -2080,24 +2532,18 @@ abstract class DatabaseBase implements DatabaseType {
* @param $orig String: column to modify
* @param $old String: column to seek
* @param $new String: column to replace with
+ *
+ * @return string
*/
function strreplace( $orig, $old, $new ) {
return "REPLACE({$orig}, {$old}, {$new})";
}
/**
- * 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
+ *
+ * @return bool
*/
function wasDeadlock() {
return false;
@@ -2107,6 +2553,8 @@ abstract class DatabaseBase implements DatabaseType {
* Determines if the last query error was something that should be dealt
* with by pinging the connection and reissuing the query.
* STUB
+ *
+ * @return bool
*/
function wasErrorReissuable() {
return false;
@@ -2115,6 +2563,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Determines if the last failure was due to the database being read-only.
* STUB
+ *
+ * @return bool
*/
function wasReadOnlyError() {
return false;
@@ -2180,20 +2630,20 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Do a SELECT MASTER_POS_WAIT()
+ * Wait for the slave to catch up to a given master position.
+ *
+ * @param $pos DBMasterPos object
+ * @param $timeout Integer: the maximum number of seconds to wait for
+ * synchronisation
*
- * @param $pos MySQLMasterPos object
- * @param $timeout Integer: the maximum number of seconds to wait for synchronisation
+ * @return An integer: zero if the slave was past that position already,
+ * greater than zero if we waited for some period of time, less than
+ * zero if we timed out.
*/
- function masterPosWait( MySQLMasterPos $pos, $timeout ) {
+ function masterPosWait( DBMasterPos $pos, $timeout ) {
$fname = 'DatabaseBase::masterPosWait';
wfProfileIn( $fname );
- # Commit any open transactions
- if ( $this->mTrxLevel ) {
- $this->commit();
- }
-
if ( !is_null( $this->mFakeSlaveLag ) ) {
$wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
@@ -2213,55 +2663,36 @@ abstract class DatabaseBase implements DatabaseType {
}
}
- # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
- $encFile = $this->addQuotes( $pos->file );
- $encPos = intval( $pos->pos );
- $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
- $res = $this->doQuery( $sql );
+ wfProfileOut( $fname );
- if ( $res && $row = $this->fetchRow( $res ) ) {
- wfProfileOut( $fname );
- return $row[0];
- } else {
- wfProfileOut( $fname );
- return false;
- }
+ # Real waits are implemented in the subclass.
+ return 0;
}
/**
- * Get the position of the master from SHOW SLAVE STATUS
+ * Get the replication position of this slave
+ *
+ * @return DBMasterPos, or false if this is not a slave.
*/
function getSlavePos() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
$pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag );
wfDebug( __METHOD__ . ": fake slave pos = $pos\n" );
return $pos;
- }
-
- $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
- $row = $this->fetchObject( $res );
-
- if ( $row ) {
- $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
- return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
} else {
+ # Stub
return false;
}
}
/**
- * Get the position of the master from SHOW MASTER STATUS
+ * Get the position of this master
+ *
+ * @return DBMasterPos, or false if this is not a master
*/
function getMasterPos() {
if ( $this->mFakeMaster ) {
return new MySQLMasterPos( 'fake', microtime( true ) );
- }
-
- $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
- $row = $this->fetchObject( $res );
-
- if ( $row ) {
- return new MySQLMasterPos( $row->File, $row->Position );
} else {
return false;
}
@@ -2297,28 +2728,12 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Begin a transaction, committing any previously open transaction
- * @deprecated use begin()
- */
- function immediateBegin( $fname = 'DatabaseBase::immediateBegin' ) {
- wfDeprecated( __METHOD__ );
- $this->begin();
- }
-
- /**
- * Commit transaction, if one is open
- * @deprecated use commit()
- */
- function immediateCommit( $fname = 'DatabaseBase::immediateCommit' ) {
- wfDeprecated( __METHOD__ );
- $this->commit();
- }
-
- /**
* Creates a new table with structure copied from existing table
* Note that unlike most database abstraction functions, this function does not
* automatically append database prefix, because it works at a lower
* abstraction level.
+ * The table names passed to this function shall not be quoted (this
+ * function calls addIdentifierQuotes when needed).
*
* @param $oldName String: name of table whose structure should be copied
* @param $newName String: name of table to be created
@@ -2326,19 +2741,46 @@ abstract class DatabaseBase implements DatabaseType {
* @param $fname String: calling function name
* @return Boolean: true if operation was successful
*/
- function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseBase::duplicateTableStructure' ) {
- throw new MWException( 'DatabaseBase::duplicateTableStructure is not implemented in descendant class' );
+ 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
+ * List all tables on the database
+ *
+ * @param $prefix Only show tables with this prefix, e.g. mw_
+ * @param $fname String: calling function name
+ */
+ function listTables( $prefix = null, $fname = 'DatabaseBase::listTables' ) {
+ throw new MWException( 'DatabaseBase::listTables is not implemented in descendant class' );
+ }
+
+ /**
+ * Convert a timestamp in one of the formats accepted by wfTimestamp()
+ * to the format used for inserting into timestamp fields in this DBMS.
+ *
+ * The result is unquoted, and needs to be passed through addQuotes()
+ * before it can be included in raw SQL.
+ *
+ * @return string
*/
function timestamp( $ts = 0 ) {
return wfTimestamp( TS_MW, $ts );
}
/**
- * Local database timestamp format or null
+ * Convert a timestamp in one of the formats accepted by wfTimestamp()
+ * to the format used for inserting into timestamp fields in this DBMS. If
+ * NULL is input, it is passed through, allowing NULL values to be inserted
+ * into timestamp fields.
+ *
+ * The result is unquoted, and needs to be passed through addQuotes()
+ * before it can be included in raw SQL.
+ *
+ * @return string
*/
function timestampOrNull( $ts = null ) {
if ( is_null( $ts ) ) {
@@ -2349,7 +2791,15 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * @todo document
+ * Take the result from a query, and wrap it in a ResultWrapper if
+ * necessary. Boolean values are passed through as is, to indicate success
+ * of write queries or failure.
+ *
+ * Once upon a time, DatabaseBase::query() returned a bare MySQL result
+ * resource, and it was necessary to call this function to convert it to
+ * a wrapper. Nowadays, raw database objects are never exposed to external
+ * callers, so this is unnecessary in external code. For compatibility with
+ * old code, ResultWrapper objects are passed through unaltered.
*/
function resultObject( $result ) {
if ( empty( $result ) ) {
@@ -2382,8 +2832,12 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Get slave lag.
- * Currently supported only by MySQL
+ * Get slave lag. Currently supported only by MySQL.
+ *
+ * Note that this function will generate a fatal error on many
+ * installations. Most callers should use LoadBalancer::safeGetLag()
+ * instead.
+ *
* @return Database replication lag in seconds
*/
function getLag() {
@@ -2391,30 +2845,29 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * Get status information from SHOW STATUS in an associative array
- */
- function getStatus( $which = "%" ) {
- $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
- $status = array();
-
- foreach ( $res as $row ) {
- $status[$row->Variable_name] = $row->Value;
- }
-
- return $status;
- }
-
- /**
* Return the maximum number of items allowed in a list, or 0 for unlimited.
+ *
+ * return int
*/
function maxListLen() {
return 0;
}
+ /**
+ * Some DBMSs have a special format for inserting into blob fields, they
+ * don't allow simple quoted strings to be inserted. To insert into such
+ * a field, pass the data through this function before passing it to
+ * DatabaseBase::insert().
+ */
function encodeBlob( $b ) {
return $b;
}
+ /**
+ * Some DBMSs return a special placeholder object representing blob fields
+ * in result objects. Pass the object through this function to return the
+ * original string.
+ */
function decodeBlob( $b ) {
return $b;
}
@@ -2431,21 +2884,23 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Read and execute SQL commands from a file.
- * Returns true on success, error string or exception on failure (depending on object's error ignore settings)
+ *
+ * Returns true on success, error string or exception on failure (depending
+ * on object's error ignore settings).
+ *
* @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
+ * @param $fname String: Calling function name or false if name should be
+ * generated dynamically using $filename
*/
function sourceFile( $filename, $lineCallback = false, $resultCallback = false, $fname = false ) {
+ wfSuppressWarnings();
$fp = fopen( $filename, 'r' );
+ wfRestoreWarnings();
if ( false === $fp ) {
- if ( !defined( "MEDIAWIKI_INSTALL" ) )
- throw new MWException( "Could not open \"{$filename}\".\n" );
- else
- return "Could not open \"{$filename}\".\n";
+ throw new MWException( "Could not open \"{$filename}\".\n" );
}
if ( !$fname ) {
@@ -2456,12 +2911,8 @@ abstract class DatabaseBase implements DatabaseType {
$error = $this->sourceStream( $fp, $lineCallback, $resultCallback, $fname );
}
catch ( MWException $e ) {
- if ( defined( "MEDIAWIKI_INSTALL" ) ) {
- $error = $e->getMessage();
- } else {
- fclose( $fp );
- throw $e;
- }
+ fclose( $fp );
+ throw $e;
}
fclose( $fp );
@@ -2490,7 +2941,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* 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
+ * 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.
@@ -2500,14 +2951,19 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * 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
+ * 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 Resource: 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, $fname = 'DatabaseBase::sourceStream' ) {
+ function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
+ $fname = 'DatabaseBase::sourceStream' )
+ {
$cmd = "";
$done = false;
$dollarquote = false;
@@ -2517,7 +2973,7 @@ abstract class DatabaseBase implements DatabaseType {
call_user_func( $lineCallback );
}
- $line = trim( fgets( $fp, 1024 ) );
+ $line = trim( fgets( $fp ) );
$sl = strlen( $line ) - 1;
if ( $sl < 0 ) {
@@ -2538,7 +2994,7 @@ abstract class DatabaseBase implements DatabaseType {
$dollarquote = true;
}
}
- else if ( !$dollarquote ) {
+ elseif ( !$dollarquote ) {
if ( ';' == $line { $sl } && ( $sl < 2 || ';' != $line { $sl - 1 } ) ) {
$done = true;
$line = substr( $line, 0, $sl );
@@ -2574,16 +3030,19 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * 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
- *
+ * Database independent variable replacement. Replaces a set of variables
+ * in an 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 table prefix and
+ * table options its use should be avoided.
+ *
* @param $ins String: SQL statement to replace variables in
* @return String The new SQL statement with variables replaced
*/
@@ -2602,6 +3061,10 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Replace variables in sourced SQL
+ *
+ * @param $ins string
+ *
+ * @return string
*/
protected function replaceVars( $ins ) {
$ins = $this->replaceSchemaVars( $ins );
@@ -2631,8 +3094,11 @@ abstract class DatabaseBase implements DatabaseType {
/**
* 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.
+ *
+ * Override this in derived classes to provide variables for tables.sql
+ * and SQL patch files.
+ *
+ * @return array
*/
protected function getDefaultSchemaVars() {
return array();
@@ -2640,7 +3106,10 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Table name callback
- * @private
+ *
+ * @param $matches array
+ *
+ * @return string
*/
protected function tableNameCallback( $matches ) {
return $this->tableName( $matches[1] );
@@ -2648,6 +3117,10 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Index name callback
+ *
+ * @param $matches array
+ *
+ * @return string
*/
protected function indexNameCallback( $matches ) {
return $this->indexName( $matches[1] );
@@ -2698,6 +3171,8 @@ abstract class DatabaseBase implements DatabaseType {
* @param $write Array of tables to lock for write access
* @param $method String name of caller
* @param $lowPriority bool Whether to indicate writes to be LOW PRIORITY
+ *
+ * @return bool
*/
public function lockTables( $read, $write, $method, $lowPriority = true ) {
return true;
@@ -2707,12 +3182,31 @@ abstract class DatabaseBase implements DatabaseType {
* Unlock specific tables
*
* @param $method String the caller
+ *
+ * @return bool
*/
public function unlockTables( $method ) {
return true;
}
/**
+ * Delete a table
+ * @param $tableName string
+ * @param $fName string
+ * @return bool|ResultWrapper
+ */
+ public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) {
+ if( !$this->tableExists( $tableName ) ) {
+ return false;
+ }
+ $sql = "DROP TABLE " . $this->tableName( $tableName );
+ if( $this->cascadingDeletes() ) {
+ $sql .= " CASCADE";
+ }
+ return $this->query( $sql, $fName );
+ }
+
+ /**
* Get search engine class. All subclasses of this need to implement this
* if they wish to use searching.
*
@@ -2723,537 +3217,40 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
- * 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.
+ * Find out when 'infinity' is. Most DBMSes support this. This is a special
+ * keyword for timestamps in PostgreSQL, and works with CHAR(14) as well
+ * because "i" sorts after all numbers.
*
- * @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
- *****************************************************************************/
-
-/**
- * Utility class.
- * @ingroup Database
- */
-class DBObject {
- public $mData;
-
- function __construct( $data ) {
- $this->mData = $data;
- }
-
- function isLOB() {
- return false;
- }
-
- function data() {
- return $this->mData;
- }
-}
-
-/**
- * Utility class
- * @ingroup Database
- *
- * This allows us to distinguish a blob from a normal string and an array of strings
- */
-class Blob {
- private $mData;
-
- function __construct( $data ) {
- $this->mData = $data;
- }
-
- function fetch() {
- return $this->mData;
- }
-}
-
-/**
- * Base for all database-specific classes representing information about database fields
- * @ingroup Database
- */
-interface Field {
- /**
- * Field name
- * @return string
- */
- function name();
-
- /**
- * Name of table this field belongs to
- * @return string
- */
- function tableName();
-
- /**
- * Database type
- * @return string
- */
- function type();
-
- /**
- * Whether this field can store NULL values
- * @return bool
- */
- function isNullable();
-}
-
-/******************************************************************************
- * Error classes
- *****************************************************************************/
-
-/**
- * Database error base class
- * @ingroup Database
- */
-class DBError extends MWException {
- public $db;
-
- /**
- * Construct a database error
- * @param $db Database object which threw the error
- * @param $error A simple error message to be used for debugging
+ * @return String
*/
- function __construct( DatabaseBase &$db, $error ) {
- $this->db =& $db;
- parent::__construct( $error );
- }
-
- function getText() {
- global $wgShowDBErrorBacktrace;
-
- $s = $this->getMessage() . "\n";
-
- if ( $wgShowDBErrorBacktrace ) {
- $s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
- }
-
- return $s;
- }
-}
-
-/**
- * @ingroup Database
- */
-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 );
- }
-
- function useOutputPage() {
- // Not likely to work
- return false;
- }
-
- function useMessageCache() {
- // Not likely to work
- return false;
- }
-
- function getLogMessage() {
- # Don't send to the exception log
- return false;
- }
-
- function getPageTitle() {
- global $wgSitename, $wgLang;
-
- $header = "$wgSitename has a problem";
-
- if ( $wgLang instanceof Language ) {
- $header = htmlspecialchars( $wgLang->getMessage( 'dberr-header' ) );
- }
-
- return $header;
- }
-
- function getHTML() {
- global $wgLang, $wgMessageCache, $wgUseFileCache, $wgShowDBErrorBacktrace;
-
- $sorry = 'Sorry! This site is experiencing technical difficulties.';
- $again = 'Try waiting a few minutes and reloading.';
- $info = '(Can\'t contact the database server: $1)';
-
- if ( $wgLang instanceof Language ) {
- $sorry = htmlspecialchars( $wgLang->getMessage( 'dberr-problems' ) );
- $again = htmlspecialchars( $wgLang->getMessage( 'dberr-again' ) );
- $info = htmlspecialchars( $wgLang->getMessage( 'dberr-info' ) );
- }
-
- # No database access
- if ( is_object( $wgMessageCache ) ) {
- $wgMessageCache->disable();
- }
-
- if ( trim( $this->error ) == '' ) {
- $this->error = $this->db->getProperty( 'mServer' );
- }
-
- $noconnect = "<p><strong>$sorry</strong><br />$again</p><p><small>$info</small></p>";
- $text = str_replace( '$1', $this->error, $noconnect );
-
- if ( $wgShowDBErrorBacktrace ) {
- $text .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
- }
-
- $extra = $this->searchForm();
-
- if ( $wgUseFileCache ) {
- try {
- $cache = $this->fileCachedPage();
- # Cached version on file system?
- if ( $cache !== null ) {
- # Hack: extend the body for error messages
- $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 ) {
- $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 ) {
- // Do nothing, just use the default page
- }
- }
-
- # Headers needed here - output is just the error message
- return $this->htmlHeader() . "$text<hr />$extra" . $this->htmlFooter();
- }
-
- function searchForm() {
- 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";
-
- if ( $wgLang instanceof Language ) {
- $usegoogle = htmlspecialchars( $wgLang->getMessage( 'dberr-usegoogle' ) );
- $outofdate = htmlspecialchars( $wgLang->getMessage( 'dberr-outofdate' ) );
- $googlesearch = htmlspecialchars( $wgLang->getMessage( 'searchbutton' ) );
- }
-
- $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="$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" />
- <div>
- <input type="radio" name="sitesearch" id="gwiki" value="$server" checked="checked" /><label for="gwiki">$sitename</label>
- <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
- </div>
-</form>
-<!-- SiteSearch Google -->
-EOT;
- return $trygoogle;
- }
-
- function fileCachedPage() {
- global $wgTitle, $wgLang, $wgOut;
-
- if ( $wgOut->isDisabled() ) {
- return; // Done already?
- }
-
- $mainpage = 'Main Page';
-
- if ( $wgLang instanceof Language ) {
- $mainpage = htmlspecialchars( $wgLang->getMessage( 'mainpage' ) );
- }
-
- if ( $wgTitle ) {
- $t =& $wgTitle;
- } else {
- $t = Title::newFromText( $mainpage );
- }
-
- $cache = new HTMLFileCache( $t );
- if ( $cache->isFileCached() ) {
- return $cache->fetchPageText();
- } else {
- return '';
- }
+ public function getInfinity() {
+ return 'infinity';
}
- function htmlBodyOnly() {
- return true;
- }
-}
-
-/**
- * @ingroup Database
- */
-class DBQueryError extends DBError {
- public $error, $errno, $sql, $fname;
-
- function __construct( DatabaseBase &$db, $error, $errno, $sql, $fname ) {
- $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: 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;
- $this->fname = $fname;
- }
-
- 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 ) {
- return $this->msg( 'sqlhidden', 'SQL hidden' );
- } else {
- return $this->sql;
- }
- }
-
- function getLogMessage() {
- # Don't send to the exception log
- return false;
- }
-
- function getPageTitle() {
- return $this->msg( 'databaseerror', 'Database error' );
- }
-
- 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;
- }
-}
-
-/**
- * @ingroup Database
- */
-class DBUnexpectedError extends DBError {}
-
-
-/**
- * Result wrapper for grabbing data queried by someone else
- * @ingroup Database
- */
-class ResultWrapper implements Iterator {
- var $db, $result, $pos = 0, $currentRow = null;
-
/**
- * Create a new result object from a result resource and a Database object
+ * Encode an expiry time
+ *
+ * @param $expiry String: timestamp for expiry, or the 'infinity' string
+ * @return String
*/
- function __construct( $database, $result ) {
- $this->db = $database;
-
- if ( $result instanceof ResultWrapper ) {
- $this->result = $result->result;
+ public function encodeExpiry( $expiry ) {
+ if ( $expiry == '' || $expiry == $this->getInfinity() ) {
+ return $this->getInfinity();
} else {
- $this->result = $result;
+ return $this->timestamp( $expiry );
}
}
/**
- * Get the number of rows in a result object
- */
- function numRows() {
- return $this->db->numRows( $this );
- }
-
- /**
- * 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.
+ * Allow or deny "big selects" for this session only. This is done by setting
+ * the sql_big_selects session variable.
*
- * @return MySQL row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchObject() {
- return $this->db->fetchObject( $this );
- }
-
- /**
- * Fetch the next row from the given result object, in associative array
- * form. Fields are retrieved with $row['fieldname'].
+ * This is a MySQL-specific feature.
*
- * @return MySQL row object
- * @throws DBUnexpectedError Thrown if the database returns an error
- */
- function fetchRow() {
- return $this->db->fetchRow( $this );
- }
-
- /**
- * Free a result object
+ * @param $value Mixed: true for allow, false for deny, or "default" to
+ * restore the initial value
*/
- function free() {
- $this->db->freeResult( $this );
- unset( $this->result );
- unset( $this->db );
- }
-
- /**
- * Change the position of the cursor in a result object
- * See mysql_data_seek()
- */
- function seek( $row ) {
- $this->db->dataSeek( $this, $row );
- }
-
- /*********************
- * Iterator functions
- * Note that using these in combination with the non-iterator functions
- * above may cause rows to be skipped or repeated.
- */
-
- function rewind() {
- if ( $this->numRows() ) {
- $this->db->dataSeek( $this, 0 );
- }
- $this->pos = 0;
- $this->currentRow = null;
- }
-
- function current() {
- if ( is_null( $this->currentRow ) ) {
- $this->next();
- }
- return $this->currentRow;
- }
-
- function key() {
- return $this->pos;
- }
-
- function next() {
- $this->pos++;
- $this->currentRow = $this->fetchObject();
- return $this->currentRow;
- }
-
- function valid() {
- return $this->current() !== false;
- }
-}
-
-/**
- * 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 DatabaseBase::anyChar() and anyString() instead.
- */
-class LikeMatch {
- private $str;
-
- public function __construct( $s ) {
- $this->str = $s;
- }
-
- public function toString() {
- return $this->str;
+ public function setBigSelects( $value = true ) {
+ // no-op
}
}
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
new file mode 100644
index 00000000..b7fb1b22
--- /dev/null
+++ b/includes/db/DatabaseError.php
@@ -0,0 +1,314 @@
+<?php
+
+/**
+ * Database error base class
+ * @ingroup Database
+ */
+class DBError extends MWException {
+
+ /**
+ * @var DatabaseBase
+ */
+ public $db;
+
+ /**
+ * Construct a database error
+ * @param $db DatabaseBase object which threw the error
+ * @param $error String A simple error message to be used for debugging
+ */
+ function __construct( DatabaseBase &$db, $error ) {
+ $this->db = $db;
+ parent::__construct( $error );
+ }
+
+ /**
+ * @param $html string
+ * @return string
+ */
+ protected function getContentMessage( $html ) {
+ if ( $html ) {
+ return nl2br( htmlspecialchars( $this->getMessage() ) );
+ } else {
+ return $this->getMessage();
+ }
+ }
+
+ /**
+ * @return string
+ */
+ function getText() {
+ global $wgShowDBErrorBacktrace;
+
+ $s = $this->getContentMessage( false ) . "\n";
+
+ if ( $wgShowDBErrorBacktrace ) {
+ $s .= "Backtrace:\n" . $this->getTraceAsString() . "\n";
+ }
+
+ return $s;
+ }
+
+ /**
+ * @return string
+ */
+ function getHTML() {
+ global $wgShowDBErrorBacktrace;
+
+ $s = $this->getContentMessage( true );
+
+ if ( $wgShowDBErrorBacktrace ) {
+ $s .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
+ }
+
+ return $s;
+ }
+}
+
+/**
+ * @ingroup Database
+ */
+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 );
+ }
+
+ function useOutputPage() {
+ // Not likely to work
+ return false;
+ }
+
+ function msg( $key, $fallback /*[, params...] */ ) {
+ global $wgLang;
+
+ $args = array_slice( func_get_args(), 2 );
+
+ if ( $this->useMessageCache() ) {
+ $message = $wgLang->getMessage( $key );
+ } else {
+ $message = $fallback;
+ }
+ return wfMsgReplaceArgs( $message, $args );
+ }
+
+ function getLogMessage() {
+ # Don't send to the exception log
+ return false;
+ }
+
+ /**
+ * @return string
+ */
+ function getPageTitle() {
+ global $wgSitename;
+ return htmlspecialchars( $this->msg( 'dberr-header', "$wgSitename has a problem" ) );
+ }
+
+ /**
+ * @return string
+ */
+ function getHTML() {
+ global $wgShowDBErrorBacktrace;
+
+ $sorry = htmlspecialchars( $this->msg( 'dberr-problems', 'Sorry! This site is experiencing technical difficulties.' ) );
+ $again = htmlspecialchars( $this->msg( 'dberr-again', 'Try waiting a few minutes and reloading.' ) );
+ $info = htmlspecialchars( $this->msg( 'dberr-info', '(Can\'t contact the database server: $1)' ) );
+
+ # No database access
+ MessageCache::singleton()->disable();
+
+ if ( trim( $this->error ) == '' ) {
+ $this->error = $this->db->getProperty( 'mServer' );
+ }
+
+ $this->error = Html::element( 'span', array( 'dir' => 'ltr' ), $this->error );
+
+ $noconnect = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
+ $text = str_replace( '$1', $this->error, $noconnect );
+
+ if ( $wgShowDBErrorBacktrace ) {
+ $text .= '<p>Backtrace:</p><p>' . nl2br( htmlspecialchars( $this->getTraceAsString() ) );
+ }
+
+ $extra = $this->searchForm();
+
+ return "$text<hr />$extra";
+ }
+
+ public function reportHTML(){
+ global $wgUseFileCache;
+
+ # Check whether we can serve a file-cached copy of the page with the error underneath
+ if ( $wgUseFileCache ) {
+ try {
+ $cache = $this->fileCachedPage();
+ # Cached version on file system?
+ if ( $cache !== null ) {
+ # Hack: extend the body for error messages
+ $cache = str_replace( array( '</html>', '</body>' ), '', $cache );
+ # Add cache notice...
+ $cache .= '<div style="color:red;font-size:150%;font-weight:bold;">'.
+ htmlspecialchars( $this->msg( 'dberr-cachederror',
+ 'This is a cached copy of the requested page, and may not be up to date. ' ) ) .
+ '</div>';
+
+ # Output cached page with notices on bottom and re-close body
+ echo "{$cache}<hr />{$this->getHTML()}</body></html>";
+ return;
+ }
+ } catch ( MWException $e ) {
+ // Do nothing, just use the default page
+ }
+ }
+
+ # We can't, cough and die in the usual fashion
+ return parent::reportHTML();
+ }
+
+ /**
+ * @return string
+ */
+ function searchForm() {
+ global $wgSitename, $wgServer, $wgRequest;
+
+ $usegoogle = htmlspecialchars( $this->msg( 'dberr-usegoogle', 'You can try searching via Google in the meantime.' ) );
+ $outofdate = htmlspecialchars( $this->msg( 'dberr-outofdate', 'Note that their indexes of our content may be out of date.' ) );
+ $googlesearch = htmlspecialchars( $this->msg( 'searchbutton', 'Search' ) );
+
+ $search = htmlspecialchars( $wgRequest->getVal( '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="$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" />
+ <div>
+ <input type="radio" name="sitesearch" id="gwiki" value="$server" checked="checked" /><label for="gwiki">$sitename</label>
+ <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
+ </div>
+</form>
+<!-- SiteSearch Google -->
+EOT;
+ return $trygoogle;
+ }
+
+ /**
+ * @return string
+ */
+ private function fileCachedPage() {
+ global $wgTitle, $wgOut;
+
+ if ( $wgOut->isDisabled() ) {
+ return; // Done already?
+ }
+
+ if ( $wgTitle ) {
+ $t =& $wgTitle;
+ } else {
+ $t = Title::newFromText( $this->msg( 'mainpage', 'Main Page' ) );
+ }
+
+ $cache = new HTMLFileCache( $t );
+ if ( $cache->isFileCached() ) {
+ return $cache->fetchPageText();
+ } else {
+ return '';
+ }
+ }
+}
+
+/**
+ * @ingroup Database
+ */
+class DBQueryError extends DBError {
+ public $error, $errno, $sql, $fname;
+
+ function __construct( DatabaseBase &$db, $error, $errno, $sql, $fname ) {
+ $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: http://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
+ "Query: $sql\n" .
+ "Function: $fname\n" .
+ "Error: $errno $error\n";
+ global $wgShowDBErrorBacktrace;
+ if( $wgShowDBErrorBacktrace ) {
+ $message .= $this->getTraceAsString();
+ }
+ parent::__construct( $db, $message );
+
+ $this->error = $error;
+ $this->errno = $errno;
+ $this->sql = $sql;
+ $this->fname = $fname;
+ }
+
+ /**
+ * @param $html string
+ * @return string
+ */
+ function getContentMessage( $html ) {
+ if ( $this->useMessageCache() ) {
+ if ( $html ) {
+ $msg = 'dberrortext';
+ $sql = htmlspecialchars( $this->getSQL() );
+ $fname = htmlspecialchars( $this->fname );
+ $error = htmlspecialchars( $this->error );
+ } else {
+ $msg = 'dberrortextcl';
+ $sql = $this->getSQL();
+ $fname = $this->fname;
+ $error = $this->error;
+ }
+ return wfMsg( $msg, $sql, $fname, $this->errno, $error );
+ } else {
+ return parent::getContentMessage( $html );
+ }
+ }
+
+ /**
+ * @return String
+ */
+ function getSQL() {
+ global $wgShowSQLErrors;
+
+ if ( !$wgShowSQLErrors ) {
+ return $this->msg( 'sqlhidden', 'SQL hidden' );
+ } else {
+ return $this->sql;
+ }
+ }
+
+ function getLogMessage() {
+ # Don't send to the exception log
+ return false;
+ }
+
+ /**
+ * @return String
+ */
+ function getPageTitle() {
+ return $this->msg( 'databaseerror', 'Database error' );
+ }
+}
+
+/**
+ * @ingroup Database
+ */
+class DBUnexpectedError extends DBError {}
diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php
index becca11e..147b9d59 100644
--- a/includes/db/DatabaseIbm_db2.php
+++ b/includes/db/DatabaseIbm_db2.php
@@ -114,7 +114,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
protected $mPHPError = false;
protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
- protected $mOut, $mOpened = false;
+ protected $mOpened = false;
protected $mTablePrefix;
protected $mFlags;
@@ -144,7 +144,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
public $mStmtOptions = array();
/** Default schema */
- const USE_GLOBAL = 'mediawiki';
+ const USE_GLOBAL = 'get from global';
/** Option that applies to nothing */
const NONE_OPTION = 0x00;
@@ -268,6 +268,10 @@ class DatabaseIbm_db2 extends DatabaseBase {
}
// configure the connection and statement objects
+ /*
+ $this->setDB2Option( 'cursor', 'DB2_SCROLLABLE',
+ self::CONN_OPTION | self::STMT_OPTION );
+ */
$this->setDB2Option( 'db2_attr_case', 'DB2_CASE_LOWER',
self::CONN_OPTION | self::STMT_OPTION );
$this->setDB2Option( 'deferred_prepare', 'DB2_DEFERRED_PREPARE_ON',
@@ -321,27 +325,17 @@ class DatabaseIbm_db2 extends DatabaseBase {
* @return a fresh connection
*/
public function open( $server, $user, $password, $dbName ) {
- // Load the port number
- global $wgDBport;
wfProfileIn( __METHOD__ );
- // Load IBM DB2 driver if missing
+ # Load IBM DB2 driver if missing
wfDl( 'ibm_db2' );
- // Test for IBM DB2 support, to avoid suppressed fatal error
+ # Test for IBM DB2 support, to avoid suppressed fatal error
if ( !function_exists( 'db2_connect' ) ) {
- $error = <<<ERROR
-DB2 functions missing, have you enabled the ibm_db2 extension for PHP?
-
-ERROR;
- $this->installPrint( $error );
- $this->reportConnectionError( $error );
+ throw new DBConnectionError( $this, "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?" );
}
- if ( strlen( $user ) < 1 ) {
- wfProfileOut( __METHOD__ );
- return null;
- }
+ global $wgDBport;
// Close existing connection
$this->close();
@@ -354,24 +348,26 @@ ERROR;
$this->openUncataloged( $dbName, $user, $password, $server, $port );
- // Apply connection config
- 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 ) {
$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" );
-
wfProfileOut( __METHOD__ );
- return null;
+ wfDebug( "DB connection error\n" );
+ wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
+ wfDebug( $this->lastError() . "\n" );
+ throw new DBConnectionError( $this, $this->lastError() );
}
+ // Apply connection config
+ 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 );
+
$this->mOpened = true;
$this->applySchema();
@@ -383,7 +379,9 @@ ERROR;
* Opens a cataloged database connection, sets mConn
*/
protected function openCataloged( $dbName, $user, $password ) {
- @$this->mConn = db2_pconnect( $dbName, $user, $password );
+ wfSuppressWarnings();
+ $this->mConn = db2_pconnect( $dbName, $user, $password );
+ wfRestoreWarnings();
}
/**
@@ -391,16 +389,10 @@ ERROR;
*/
protected function openUncataloged( $dbName, $user, $password, $server, $port )
{
- $str = "DRIVER={IBM DB2 ODBC DRIVER};";
- $str .= "DATABASE=$dbName;";
- $str .= "HOSTNAME=$server;";
- // port was formerly validated to not be 0
- $str .= "PORT=$port;";
- $str .= "PROTOCOL=TCPIP;";
- $str .= "UID=$user;";
- $str .= "PWD=$password;";
-
- @$this->mConn = db2_pconnect( $str, $user, $password );
+ $dsn = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$dbName;CHARSET=UTF-8;HOSTNAME=$server;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;";
+ wfSuppressWarnings();
+ $this->mConn = db2_pconnect($dsn, "", "", array());
+ wfRestoreWarnings();
}
/**
@@ -420,23 +412,6 @@ ERROR;
}
/**
- * Returns a fresh instance of this class
- *
- * @param $server String: hostname of database server
- * @param $user String: username
- * @param $password String
- * @param $dbName String: database name on the server
- * @param $flags Integer: database behaviour flags (optional, unused)
- * @return DatabaseIbm_db2 object
- */
- static function newFromParams( $server, $user, $password, $dbName,
- $flags = 0 )
- {
- return new DatabaseIbm_db2( $server, $user, $password, $dbName,
- $flags );
- }
-
- /**
* Retrieves the most current database error
* Forces a database rollback
*/
@@ -482,15 +457,19 @@ ERROR;
* The DBMS-dependent part of query()
* @param $sql String: SQL query.
* @return object Result object for fetch functions or false on failure
- * @access private
*/
- /*private*/
- public function doQuery( $sql ) {
+ protected function doQuery( $sql ) {
$this->applySchema();
-
+
+ // Needed to handle any UTF-8 encoding issues in the raw sql
+ // Note that we fully support prepared statements for DB2
+ // prepare() and execute() should be used instead of doQuery() whenever possible
+ $sql = utf8_decode($sql);
+
$ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
if( $ret == false ) {
$error = db2_stmt_errormsg();
+
$this->installPrint( "<pre>$sql</pre>" );
$this->installPrint( $error );
throw new DBUnexpectedError( $this, 'SQL error: '
@@ -515,17 +494,18 @@ ERROR;
*/
public function tableExists( $table ) {
$schema = $this->mSchema;
- $sql = <<< EOF
-SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST
-WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema'
-EOF;
+
+ $sql = "SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST WHERE ST.NAME = '" .
+ strtoupper( $table ) .
+ "' AND ST.CREATOR = '" .
+ strtoupper( $schema ) . "'";
$res = $this->query( $sql );
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' || $count == 1 ) {
return true;
@@ -547,7 +527,9 @@ EOF;
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @$row = db2_fetch_object( $res );
+ wfSuppressWarnings();
+ $row = db2_fetch_object( $res );
+ wfRestoreWarnings();
if( $this->lastErrno() ) {
throw new DBUnexpectedError( $this, 'Error in fetchObject(): '
. htmlspecialchars( $this->lastError() ) );
@@ -567,51 +549,17 @@ EOF;
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @$row = db2_fetch_array( $res );
- if ( $this->lastErrno() ) {
- throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
- . htmlspecialchars( $this->lastError() ) );
- }
- return $row;
- }
-
- /**
- * Create tables, stored procedures, and so on
- */
- public function setup_database() {
- 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>';
- } else {
- print ' done</li>';
- }
- $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() ) {
- $this->installPrint(
- 'Errors encountered during table creation -- rolled back' );
- $this->installPrint( 'Please install again' );
- $this->rollback();
- } else {
- $this->commit();
+ if ( db2_num_rows( $res ) > 0) {
+ wfSuppressWarnings();
+ $row = db2_fetch_array( $res );
+ wfRestoreWarnings();
+ if ( $this->lastErrno() ) {
+ throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
+ . htmlspecialchars( $this->lastError() ) );
}
- } catch ( MWException $mwe ) {
- print "<br><pre>$mwe</pre><br>";
+ return $row;
}
+ return false;
}
/**
@@ -797,9 +745,10 @@ EOF;
* Handle reserved keyword replacement in table names
*
* @param $name Object
+ * @param $name Boolean
* @return String
*/
- public function tableName( $name ) {
+ public function tableName( $name, $quoted = true ) {
// we want maximum compatibility with MySQL schema
return $name;
}
@@ -915,9 +864,9 @@ EOF;
} else {
$sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )';
}
- //$this->installPrint( "Preparing the following SQL:" );
- //$this->installPrint( "$sql" );
- //$this->installPrint( print_r( $args, true ));
+ $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
@@ -990,18 +939,22 @@ EOF;
*/
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 = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '"
+ . strtoupper( $table )
+ . "' AND TBCREATOR = '"
+ . strtoupper( $schema )
+ . "' AND KEYSEQ > 0" );
+
$keys = array();
for (
- $row = $this->fetchObject( $keyres );
+ $row = $this->fetchRow( $keyres );
$row != null;
- $row = $this->fetchObject( $keyres )
+ $row = $this->fetchRow( $keyres )
)
{
- $keys[] = strtolower( $row->column_name );
+ $keys[] = strtolower( $row[0] );
}
// remove primary keys
foreach ( $args as $ai => $row ) {
@@ -1084,66 +1037,6 @@ EOF;
}
/**
- * Simulates REPLACE with a DELETE followed by INSERT
- * @param $table Object
- * @param $uniqueIndexes Array consisting of indexes and arrays of indexes
- * @param $rows Array: rows to insert
- * @param $fname String: name of the function for profiling
- * @return nothing
- */
- function replace( $table, $uniqueIndexes, $rows,
- $fname = 'DatabaseIbm_db2::replace' )
- {
- $table = $this->tableName( $table );
-
- if ( count( $rows )==0 ) {
- return;
- }
-
- # Single row case
- if ( !is_array( reset( $rows ) ) ) {
- $rows = array( $rows );
- }
-
- foreach( $rows as $row ) {
- # 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 );
- }
-
- # 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 );
- }
- }
-
- /**
* Returns the number of rows in the result set
* Has to be called right after the corresponding select query
* @param $res Object result set
@@ -1153,6 +1046,7 @@ EOF;
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+
if ( $this->mNumRows ) {
return $this->mNumRows;
} else {
@@ -1186,7 +1080,10 @@ EOF;
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- if ( !@db2_free_result( $res ) ) {
+ wfSuppressWarnings();
+ $ok = db2_free_result( $res );
+ wfRestoreWarnings();
+ if ( !$ok ) {
throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
}
}
@@ -1365,14 +1262,6 @@ EOF;
######################################
/**
* Not implemented
- * @return string ''
- */
- public function getStatus( $which = '%' ) {
- $this->installPrint( 'Not implemented for DB2: getStatus()' );
- return '';
- }
- /**
- * Not implemented
* @return string $sql
*/
public function limitResultForUpdate( $sql, $num ) {
@@ -1496,39 +1385,6 @@ SQL;
}
/**
- * DELETE where the condition is a join
- * @param $delTable String: deleting from this table
- * @param $joinTable String: using data from this table
- * @param $delVar String: variable in deleteable table
- * @param $joinVar String: variable in data table
- * @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" )
- {
- if ( !$conds ) {
- throw new DBUnexpectedError( $this,
- 'DatabaseIbm_db2::deleteJoin() called with empty $conds' );
- }
-
- $delTable = $this->tableName( $delTable );
- $joinTable = $this->tableName( $joinTable );
- $sql = <<<SQL
-DELETE FROM $delTable
-WHERE $delVar IN (
- SELECT $joinVar FROM $joinTable
-
-SQL;
- if ( $conds != '*' ) {
- $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
- }
- $sql .= ' )';
-
- $this->query( $sql, $fname );
- }
-
- /**
* Description is left as an exercise for the reader
* @param $b Mixed: data to be encoded
* @return IBM_DB2Blob
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index 41ba2d08..cbdf89ca 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -17,6 +17,8 @@ class DatabaseMssql extends DatabaseBase {
var $mLastResult = NULL;
var $mAffectedRows = NULL;
+ var $mPort;
+
function cascadingDeletes() {
return true;
}
@@ -42,10 +44,6 @@ class DatabaseMssql extends DatabaseBase {
return false;
}
- static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
- return new DatabaseMssql( $server, $user, $password, $dbName, $flags );
- }
-
/**
* Usually aborts on failure
*/
@@ -83,7 +81,7 @@ class DatabaseMssql extends DatabaseBase {
$ntAuthPassTest = strtolower( $password );
// Decide which auth scenerio to use
- if( ( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ) ){
+ if( $ntAuthPassTest == 'ntauth' && $ntAuthUserTest == 'ntauth' ){
// Don't add credentials to $connectionInfo
} else {
$connectionInfo['UID'] = $user;
@@ -91,7 +89,9 @@ class DatabaseMssql extends DatabaseBase {
}
// End NT Auth Hack
- $this->mConn = @sqlsrv_connect( $server, $connectionInfo );
+ wfSuppressWarnings();
+ $this->mConn = sqlsrv_connect( $server, $connectionInfo );
+ wfRestoreWarnings();
if ( $this->mConn === false ) {
wfDebug( "DB connection error\n" );
@@ -117,7 +117,7 @@ class DatabaseMssql extends DatabaseBase {
}
}
- function doQuery( $sql ) {
+ protected function doQuery( $sql ) {
wfDebug( "SQL: [$sql]\n" );
$this->offset = 0;
@@ -241,16 +241,18 @@ class DatabaseMssql extends DatabaseBase {
function lastError() {
if ( $this->mConn ) {
return $this->getErrors();
- }
- else {
+ } else {
return "No database connection";
}
}
function lastErrno() {
$err = sqlsrv_errors( SQLSRV_ERR_ALL );
- if ( $err[0] ) return $err[0]['code'];
- else return 0;
+ if ( $err[0] ) {
+ return $err[0]['code'];
+ } else {
+ return 0;
+ }
}
function affectedRows() {
@@ -321,7 +323,6 @@ class DatabaseMssql extends DatabaseBase {
return $rows;
}
-
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
@@ -344,7 +345,7 @@ class DatabaseMssql extends DatabaseBase {
$row->Column_name = trim( $col );
$result[] = clone $row;
}
- } else if ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) {
+ } elseif ( $index == 'PRIMARY' && stristr( $row->index_description, 'PRIMARY' ) ) {
$row->Non_unique = 0;
$cols = explode( ", ", $row->index_keys );
foreach ( $cols as $col ) {
@@ -383,7 +384,6 @@ class DatabaseMssql extends DatabaseBase {
$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
@@ -421,7 +421,6 @@ class DatabaseMssql extends DatabaseBase {
$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;
@@ -436,7 +435,7 @@ class DatabaseMssql extends DatabaseBase {
// 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 ) {
+ if ( $ignoreClause ) {
$prival = $a[$keys[0]];
$sqlPre .= "IF NOT EXISTS (SELECT * FROM $table WHERE $keys[0] = '$prival')";
}
@@ -453,14 +452,14 @@ class DatabaseMssql extends DatabaseBase {
$sql .= ',';
}
if ( is_string( $value ) ) {
- $sql .= $this->addIdentifierQuotes( $value );
+ $sql .= $this->addQuotes( $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() );
+ $sql .= $this->addQuotes( $value );
} else {
- $sql .= $this->addIdentifierQuotes( serialize( $value ) );
+ $sql .= $this->addQuotes( serialize( $value ) );
}
} else {
$sql .= $value;
@@ -497,12 +496,11 @@ class DatabaseMssql extends DatabaseBase {
* srcTable may be an array of tables.
*/
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabaseMssql::insertSelect',
- $insertOptions = array(), $selectOptions = array() )
- {
+ $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 );
+ throw new DBQueryError( $this, $this->getErrors(), $this->lastErrno(), /*$sql*/ '', $fname );
} elseif ( $ret != NULL ) {
// remember number of rows affected
$this->mAffectedRows = sqlsrv_rows_affected( $ret );
@@ -512,35 +510,6 @@ class DatabaseMssql extends DatabaseBase {
}
/**
- * 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.
- *
- * 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 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 $name;
- }
-
- /**
* Return the next in a sequence, save the value for retrieval via insertId()
*/
function nextSequenceValue( $seqName ) {
@@ -570,82 +539,6 @@ class DatabaseMssql extends DatabaseBase {
}
}
-
- # 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 );
- }
-
- foreach ( $rows as $row ) {
- # 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 );
- }
-
- # 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 );
- }
- }
-
- # DELETE where the condition is a join
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseMssql::deleteJoin" ) {
- if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabaseMssql::deleteJoin() called with empty $conds' );
- }
-
- $delTable = $this->tableName( $delTable );
- $joinTable = $this->tableName( $joinTable );
- $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
- if ( $conds != '*' ) {
- $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
- }
- $sql .= ')';
-
- $this->query( $sql, $fname );
- }
-
# Returns the size of a text field, or -1 for "unlimited"
function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
@@ -654,7 +547,9 @@ class DatabaseMssql extends DatabaseBase {
$res = $this->query( $sql );
$row = $this->fetchRow( $res );
$size = -1;
- if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) $size = $row['CHARACTER_MAXIMUM_LENGTH'];
+ if ( strtolower( $row['DATA_TYPE'] ) != 'text' ) {
+ $size = $row['CHARACTER_MAXIMUM_LENGTH'];
+ }
return $size;
}
@@ -713,7 +608,6 @@ class DatabaseMssql extends DatabaseBase {
return $sql;
}
-
function timestamp( $ts = 0 ) {
return wfTimestamp( TS_ISO_8601, $ts );
}
@@ -731,7 +625,9 @@ class DatabaseMssql extends DatabaseBase {
function getServerVersion() {
$server_info = sqlsrv_server_info( $this->mConn );
$version = 'Error';
- if ( isset( $server_info['SQLServerVersion'] ) ) $version = $server_info['SQLServerVersion'];
+ if ( isset( $server_info['SQLServerVersion'] ) ) {
+ $version = $server_info['SQLServerVersion'];
+ }
return $version;
}
@@ -742,10 +638,11 @@ class DatabaseMssql extends DatabaseBase {
print( "Error in tableExists query: " . $this->getErrors() );
return false;
}
- if ( sqlsrv_fetch( $res ) )
+ if ( sqlsrv_fetch( $res ) ) {
return true;
- else
+ } else {
return false;
+ }
}
/**
@@ -759,10 +656,11 @@ class DatabaseMssql extends DatabaseBase {
print( "Error in fieldExists query: " . $this->getErrors() );
return false;
}
- if ( sqlsrv_fetch( $res ) )
+ if ( sqlsrv_fetch( $res ) ) {
return true;
- else
+ } else {
return false;
+ }
}
function fieldInfo( $table, $field ) {
@@ -780,10 +678,6 @@ class DatabaseMssql extends DatabaseBase {
return false;
}
- public function unixTimestamp( $field ) {
- return "DATEDIFF(s,CONVERT(datetime,'1/1/1970'),$field)";
- }
-
/**
* Begin a transaction, committing any previously open transaction
*/
@@ -809,48 +703,6 @@ class DatabaseMssql extends DatabaseBase {
$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();
- }
-
/**
* Escapes a identifier for use inm SQL.
* Throws an exception if it is invalid.
@@ -957,12 +809,12 @@ class DatabaseMssql extends DatabaseBase {
$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] ) ) {
+ } elseif ( isset( $use_index_safe[$table] ) ) {
$tableClause = $this->tableName( $table );
$tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
$ret[] = $tableClause;
// Is there a JOIN clause?
- } else if ( isset( $join_conds_safe[$table] ) ) {
+ } elseif ( isset( $join_conds_safe[$table] ) ) {
$tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
$tableClause .= ' ON (' . $this->makeList( (array)$join_conds_safe[$table][1], LIST_AND ) . ')';
$retJOIN[] = $tableClause;
@@ -990,6 +842,15 @@ class DatabaseMssql extends DatabaseBase {
}
}
+ public function addIdentifierQuotes( $s ) {
+ // http://msdn.microsoft.com/en-us/library/aa223962.aspx
+ return '[' . $s . ']';
+ }
+
+ public function isQuotedIdentifier( $name ) {
+ return $name[0] == '[' && substr( $name, -1, 1 ) == ']';
+ }
+
function selectDB( $db ) {
return ( $this->query( "SET DATABASE $db" ) !== false );
}
@@ -1012,11 +873,19 @@ class DatabaseMssql extends DatabaseBase {
}
}
- 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( $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';
+ 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, '' );
@@ -1037,6 +906,14 @@ class DatabaseMssql extends DatabaseBase {
return "SearchMssql";
}
+ /**
+ * Since MSSQL doesn't recognize the infinity keyword, set date manually.
+ * @todo Remove magic date
+ */
+ public function getInfinity() {
+ return '3000-01-31 00:00:00.000';
+ }
+
} // end DatabaseMssql class
/**
@@ -1051,9 +928,10 @@ class MssqlField implements Field {
$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->nullable = !( strtolower( $info['IS_NULLABLE'] ) == 'no' );
$this->type = $info['DATA_TYPE'];
}
+
function name() {
return $this->name;
}
@@ -1087,26 +965,29 @@ class MssqlField implements Field {
*/
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" );
+ public function __construct( $queryresult = false ) {
+ $this->mCursor = 0;
+ $this->mRows = array();
+ $this->mNumFields = sqlsrv_num_fields( $queryresult );
+ $this->mFieldMeta = sqlsrv_field_metadata( $queryresult );
+
+ $rows = sqlsrv_fetch_array( $queryresult, SQLSRV_FETCH_ASSOC );
+
+ foreach( $rows as $row ) {
+ if ( $row !== null ) {
+ foreach ( $row as $k => $v ) {
+ if ( is_object( $v ) && method_exists( $v, 'format' ) ) {// DateTime Object
+ $row[$k] = $v->format( "Y-m-d\TH:i:s\Z" );
+ }
}
+ $this->mRows[] = $row;// read results into memory, cursors are not supported
}
- $this->mRows[] = $row;// read results into memory, cursors are not supported
}
+ $this->mRowCount = count( $this->mRows );
+ sqlsrv_free_stmt( $queryresult );
}
- $this->mRowCount = count( $this->mRows );
- sqlsrv_free_stmt( $queryresult );
- }
- private function array_to_obj( $array, &$obj ) {
+ private function array_to_obj( $array, &$obj ) {
foreach ( $array as $key => $value ) {
if ( is_array( $value ) ) {
$obj->$key = new stdClass();
@@ -1118,109 +999,108 @@ class MssqlResult {
}
}
return $obj;
- }
-
- 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;
+
+ 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;
+ }
+
+ $this->mCursor++;
+ return $ret;
+ }
+
+ public function get( $pos, $fld ) {
+ return $this->mRows[$pos][$fld];
}
- switch( $mode ) {
- case SQLSRV_FETCH_ASSOC:
- $ret = $this->mRows[$this->mCursor];
- break;
- case SQLSRV_FETCH_NUMERIC:
- $ret = $arrNum;
- break;
- case 'OBJECT':
- $o = new $object_class;
- $ret = $this->array_to_obj( $this->mRows[$this->mCursor], $o );
- break;
- case SQLSRV_FETCH_BOTH:
- default:
- $ret = $this->mRows[$this->mCursor] + $arrNum;
- break;
- }
-
- $this->mCursor++;
- return $ret;
- }
-
- 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;
+
+ 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++;
}
- $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;
- }
-
- public function free() {
- unset( $this->mRows );
- return;
- }
+ // 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;
+ }
+ public function free() {
+ unset( $this->mRows );
+ return;
+ }
}
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index ed276ec5..9cbd455e 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -18,7 +18,7 @@ class DatabaseMysql extends DatabaseBase {
return 'mysql';
}
- /*private*/ function doQuery( $sql ) {
+ protected function doQuery( $sql ) {
if( $this->bufferResults() ) {
$ret = mysql_query( $sql, $this->mConn );
} else {
@@ -95,7 +95,9 @@ class DatabaseMysql extends DatabaseBase {
wfProfileOut("dbconnect-$server");
if ( $dbName != '' && $this->mConn !== false ) {
- $success = @/**/mysql_select_db( $dbName, $this->mConn );
+ wfSuppressWarnings();
+ $success = mysql_select_db( $dbName, $this->mConn );
+ wfRestoreWarnings();
if ( !$success ) {
$error = "Error selecting database $dbName on server {$this->mServer} " .
"from client host " . wfHostname() . "\n";
@@ -152,7 +154,10 @@ class DatabaseMysql extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- if ( !@/**/mysql_free_result( $res ) ) {
+ wfSuppressWarnings();
+ $ok = mysql_free_result( $res );
+ wfRestoreWarnings();
+ if ( !$ok ) {
throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
}
}
@@ -161,7 +166,9 @@ class DatabaseMysql extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @/**/$row = mysql_fetch_object( $res );
+ wfSuppressWarnings();
+ $row = mysql_fetch_object( $res );
+ wfRestoreWarnings();
if( $this->lastErrno() ) {
throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
}
@@ -172,7 +179,9 @@ class DatabaseMysql extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @/**/$row = mysql_fetch_array( $res );
+ wfSuppressWarnings();
+ $row = mysql_fetch_array( $res );
+ wfRestoreWarnings();
if ( $this->lastErrno() ) {
throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
}
@@ -183,7 +192,9 @@ class DatabaseMysql extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @/**/$n = mysql_num_rows( $res );
+ wfSuppressWarnings();
+ $n = mysql_num_rows( $res );
+ wfRestoreWarnings();
if( $this->lastErrno() ) {
throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
}
@@ -241,6 +252,10 @@ class DatabaseMysql extends DatabaseBase {
function affectedRows() { return mysql_affected_rows( $this->mConn ); }
+ function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMysql::replace' ) {
+ return $this->nativeReplace( $table, $rows, $fname );
+ }
+
/**
* Estimate rows in dataset
* Returns estimated count, based on EXPLAIN output
@@ -329,6 +344,10 @@ class DatabaseMysql extends DatabaseBase {
return "`" . $this->strencode( $s ) . "`";
}
+ public function isQuotedIdentifier( $name ) {
+ return strlen($name) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
+ }
+
function ping() {
$ping = mysql_ping( $this->mConn );
if ( $ping ) {
@@ -344,7 +363,11 @@ class DatabaseMysql extends DatabaseBase {
/**
* Returns slave lag.
- * At the moment, this will only work if the DB user has the PROCESS privilege
+ *
+ * On MySQL 4.1.9 and later, this will do a SHOW SLAVE STATUS. On earlier
+ * versions of MySQL, it uses SHOW PROCESSLIST, which requires the PROCESS
+ * privilege.
+ *
* @result int
*/
function getLag() {
@@ -352,6 +375,31 @@ class DatabaseMysql extends DatabaseBase {
wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
return $this->mFakeSlaveLag;
}
+
+ if ( version_compare( $this->getServerVersion(), '4.1.9', '>=' ) ) {
+ return $this->getLagFromSlaveStatus();
+ } else {
+ return $this->getLagFromProcesslist();
+ }
+ }
+
+ function getLagFromSlaveStatus() {
+ $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
+ if ( !$res ) {
+ return false;
+ }
+ $row = $res->fetchObject();
+ if ( !$row ) {
+ return false;
+ }
+ if ( strval( $row->Seconds_Behind_Master ) === '' ) {
+ return false;
+ } else {
+ return intval( $row->Seconds_Behind_Master );
+ }
+ }
+
+ function getLagFromProcesslist() {
$res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
if( !$res ) {
return false;
@@ -384,6 +432,83 @@ class DatabaseMysql extends DatabaseBase {
}
return false;
}
+
+ /**
+ * Wait for the slave to catch up to a given master position.
+ *
+ * @param $pos DBMasterPos object
+ * @param $timeout Integer: the maximum number of seconds to wait for synchronisation
+ */
+ function masterPosWait( DBMasterPos $pos, $timeout ) {
+ $fname = 'DatabaseBase::masterPosWait';
+ wfProfileIn( $fname );
+
+ # Commit any open transactions
+ if ( $this->mTrxLevel ) {
+ $this->commit();
+ }
+
+ if ( !is_null( $this->mFakeSlaveLag ) ) {
+ $status = parent::masterPosWait( $pos, $timeout );
+ wfProfileOut( $fname );
+ return $status;
+ }
+
+ # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
+ $encFile = $this->addQuotes( $pos->file );
+ $encPos = intval( $pos->pos );
+ $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
+ $res = $this->doQuery( $sql );
+
+ if ( $res && $row = $this->fetchRow( $res ) ) {
+ wfProfileOut( $fname );
+ return $row[0];
+ } else {
+ wfProfileOut( $fname );
+ return false;
+ }
+ }
+
+ /**
+ * Get the position of the master from SHOW SLAVE STATUS
+ *
+ * @return MySQLMasterPos|false
+ */
+ function getSlavePos() {
+ if ( !is_null( $this->mFakeSlaveLag ) ) {
+ return parent::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;
+ return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get the position of the master from SHOW MASTER STATUS
+ *
+ * @return MySQLMasterPos|false
+ */
+ function getMasterPos() {
+ if ( $this->mFakeMaster ) {
+ return parent::getMasterPos();
+ }
+
+ $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
+ $row = $this->fetchObject( $res );
+
+ if ( $row ) {
+ return new MySQLMasterPos( $row->File, $row->Position );
+ } else {
+ return false;
+ }
+ }
function getServerVersion() {
return mysql_get_server_info( $this->mConn );
@@ -478,8 +603,23 @@ class DatabaseMysql extends DatabaseBase {
$this->query( "SET sql_big_selects=$encValue", __METHOD__ );
}
- public function unixTimestamp( $field ) {
- return "UNIX_TIMESTAMP($field)";
+ /**
+ * DELETE where the condition is a join. MySql uses multi-table deletes.
+ */
+ function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
+ if ( !$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 );
+ }
+
+ return $this->query( $sql, $fname );
}
/**
@@ -516,26 +656,79 @@ class DatabaseMysql extends DatabaseBase {
# Note that we don't bother changing around the prefixes here be-
# cause we know we're using MySQL anyway.
- $res = $this->query( "SHOW CREATE TABLE $oldName" );
+ $res = $this->query( 'SHOW CREATE TABLE ' . $this->addIdentifierQuotes( $oldName ) );
$row = $this->fetchRow( $res );
$oldQuery = $row[1];
$query = preg_replace( '/CREATE TABLE `(.*?)`/',
- "CREATE $tmp TABLE `$newName`", $oldQuery );
+ "CREATE $tmp TABLE " . $this->addIdentifierQuotes( $newName ), $oldQuery );
if ($oldQuery === $query) {
# Couldn't do replacement
throw new MWException( "could not create temporary table $newName" );
}
} else {
+ $newName = $this->addIdentifierQuotes( $newName );
+ $oldName = $this->addIdentifierQuotes( $oldName );
$query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
}
$this->query( $query, $fname );
}
+
+ /**
+ * List all tables on the database
+ *
+ * @param $prefix Only show tables with this prefix, e.g. mw_
+ * @param $fname String: calling function name
+ */
+ function listTables( $prefix = null, $fname = 'DatabaseMysql::listTables' ) {
+ $result = $this->query( "SHOW TABLES", $fname);
+
+ $endArray = array();
+
+ foreach( $result as $table ) {
+ $vars = get_object_vars($table);
+ $table = array_pop( $vars );
+
+ if( !$prefix || strpos( $table, $prefix ) === 0 ) {
+ $endArray[] = $table;
+ }
+ }
+
+ return $endArray;
+ }
+
+ public function dropTable( $tableName, $fName = 'DatabaseMysql::dropTable' ) {
+ if( !$this->tableExists( $tableName ) ) {
+ return false;
+ }
+ return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
+ }
+ /**
+ * @return array
+ */
protected function getDefaultSchemaVars() {
$vars = parent::getDefaultSchemaVars();
- $vars['wgDBTableOptions'] = $GLOBALS['wgDBTableOptions'];
+ $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
+ $vars['wgDBTableOptions'] = str_replace( 'CHARSET=mysql4', 'CHARSET=binary', $GLOBALS['wgDBTableOptions'] );
return $vars;
}
+
+ /**
+ * Get status information from SHOW STATUS in an associative array
+ *
+ * @return array
+ */
+ function getMysqlStatus( $which = "%" ) {
+ $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
+ $status = array();
+
+ foreach ( $res as $row ) {
+ $status[$row->Variable_name] = $row->Value;
+ }
+
+ return $status;
+ }
+
}
/**
@@ -593,7 +786,7 @@ class MySQLField implements Field {
}
}
-class MySQLMasterPos {
+class MySQLMasterPos implements DBMasterPos {
var $file, $pos;
function __construct( $file, $pos ) {
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index 3c4d00ac..b64e66c2 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -16,7 +16,7 @@ class ORAResult {
private $rows;
private $cursor;
private $nrows;
-
+
private $columns = array();
private function array_unique_md( $array_in ) {
@@ -34,6 +34,11 @@ class ORAResult {
return $array_out;
}
+ /**
+ * @param $db DatabaseBase
+ * @param $stmt
+ * @param bool $unique
+ */
function __construct( &$db, $stmt, $unique = false ) {
$this->db =& $db;
@@ -165,7 +170,6 @@ class ORAField implements Field {
class DatabaseOracle extends DatabaseBase {
var $mInsertId = null;
var $mLastResult = null;
- var $numeric_version = null;
var $lastResult = null;
var $cursor = 0;
var $mAffectedRows;
@@ -180,7 +184,8 @@ class DatabaseOracle extends DatabaseBase {
function __construct( $server = false, $user = false, $password = false, $dbName = false,
$flags = 0, $tablePrefix = 'get from global' )
{
- $tablePrefix = $tablePrefix == 'get from global' ? $tablePrefix : strtoupper( $tablePrefix );
+ global $wgDBprefix;
+ $tablePrefix = $tablePrefix == 'get from global' ? strtoupper( $wgDBprefix ) : strtoupper( $tablePrefix );
parent::__construct( $server, $user, $password, $dbName, $flags, $tablePrefix );
wfRunHooks( 'DatabaseOraclePostInit', array( $this ) );
}
@@ -219,11 +224,6 @@ class DatabaseOracle extends DatabaseBase {
return true;
}
- static function newFromParams( $server, $user, $password, $dbName, $flags = 0 )
- {
- return new DatabaseOracle( $server, $user, $password, $dbName, $flags );
- }
-
/**
* Usually aborts on failure
*/
@@ -232,6 +232,7 @@ class DatabaseOracle extends DatabaseBase {
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->mUser = $user;
$this->mPassword = $password;
// changed internal variables functions
@@ -245,7 +246,7 @@ class DatabaseOracle extends DatabaseBase {
$this->mServer = $server;
if ( !$dbName ) {
$this->mDBname = $user;
- } else {
+ } else {
$this->mDBname = $dbName;
}
}
@@ -255,11 +256,13 @@ class DatabaseOracle extends DatabaseBase {
}
$session_mode = $this->mFlags & DBO_SYSDBA ? OCI_SYSDBA : OCI_DEFAULT;
+ wfSuppressWarnings();
if ( $this->mFlags & DBO_DEFAULT ) {
$this->mConn = oci_new_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
} else {
$this->mConn = oci_connect( $this->mUser, $this->mPassword, $this->mServer, $this->defaultCharset, $session_mode );
}
+ wfRestoreWarnings();
if ( $this->mUser != $this->mDBname ) {
//change current schema in session
@@ -276,7 +279,6 @@ class DatabaseOracle extends DatabaseBase {
$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
$this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
-
return $this->mConn;
}
@@ -297,10 +299,10 @@ class DatabaseOracle extends DatabaseBase {
}
function execFlags() {
- return $this->mTrxLevel ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS;
+ return $this->mTrxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
}
- function doQuery( $sql ) {
+ protected function doQuery( $sql ) {
wfDebug( "SQL: [$sql]\n" );
if ( !mb_check_encoding( $sql ) ) {
throw new MWException( "SQL encoding is invalid\n$sql" );
@@ -357,7 +359,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
-
+
$res->free();
}
@@ -365,7 +367,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
-
+
return $res->fetchObject();
}
@@ -478,16 +480,16 @@ class DatabaseOracle extends DatabaseBase {
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 ) {
+ return $bind;
+ } elseif ( $includeCol ) {
$bind = "$col = ";
}
-
+
if ( $val == '' && $val !== 0 && $col_type != 'BLOB' && $col_type != 'CLOB' ) {
$val = null;
}
@@ -505,7 +507,7 @@ class DatabaseOracle extends DatabaseBase {
} else {
$bind .= ':' . $col;
}
-
+
return $bind;
}
@@ -525,7 +527,7 @@ class DatabaseOracle extends DatabaseBase {
} else {
$first = false;
}
-
+
$sql .= $this->fieldBindStatement( $table, $col, $val );
}
$sql .= ')';
@@ -551,7 +553,7 @@ class DatabaseOracle extends DatabaseBase {
}
$val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
- if ( oci_bind_by_name( $stmt, ":$col", $val ) === false ) {
+ if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
$e = oci_error( $stmt );
$this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
return false;
@@ -652,8 +654,7 @@ class DatabaseOracle extends DatabaseBase {
return $retval;
}
- function tableName( $name ) {
- global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
+ function tableName( $name, $quoted = true ) {
/*
Replace reserved words with better ones
Using uppercase because that's the only way Oracle can handle
@@ -668,53 +669,13 @@ class DatabaseOracle extends DatabaseBase {
break;
}
- /*
- The rest of procedure is equal to generic Databse class
- except for the quoting style
- */
- if ( $name[0] == '"' && substr( $name, - 1, 1 ) == '"' ) {
- return $name;
- }
- if ( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) {
- return $name;
- }
- $dbDetails = array_reverse( explode( '.', $name, 2 ) );
- if ( isset( $dbDetails[1] ) ) {
- @list( $table, $database ) = $dbDetails;
- } else {
- @list( $table ) = $dbDetails;
- }
-
- $prefix = $this->mTablePrefix;
-
- if ( isset( $database ) ) {
- $table = ( $table[0] == '`' ? $table : "`{$table}`" );
- }
-
- if ( !isset( $database ) && isset( $wgSharedDB ) && $table[0] != '"'
- && isset( $wgSharedTables )
- && is_array( $wgSharedTables )
- && in_array( $table, $wgSharedTables )
- ) {
- $database = $wgSharedDB;
- $prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
- }
-
- if ( isset( $database ) ) {
- $database = ( $database[0] == '"' ? $database : "\"{$database}\"" );
- }
- $table = ( $table[0] == '"') ? $table : "\"{$prefix}{$table}\"" ;
-
- $tableName = ( isset( $database ) ? "{$database}.{$table}" : "{$table}" );
-
- return strtoupper( $tableName );
+ return parent::tableName( strtoupper( $name ), $quoted );
}
function tableNameInternal( $name ) {
$name = $this->tableName( $name );
- return preg_replace( '/.*\."(.*)"/', '$1', $name);
+ return preg_replace( '/.*\.(.*)/', '$1', $name);
}
-
/**
* Return the next in a sequence, save the value for retrieval via insertId()
*/
@@ -730,96 +691,26 @@ class DatabaseOracle extends DatabaseBase {
*/
private function getSequenceData( $table ) {
if ( $this->sequenceData == null ) {
- $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\'' );
+ $result = $this->doQuery( "SELECT lower(asq.sequence_name),
+ lower(atc.table_name),
+ lower(atc.column_name)
+ FROM all_sequences asq, all_tab_columns atc
+ WHERE decode(atc.table_name, '{$this->mTablePrefix}MWUSER', '{$this->mTablePrefix}USER', atc.table_name) || '_' ||
+ atc.column_name || '_SEQ' = '{$this->mTablePrefix}' || asq.sequence_name
+ AND asq.sequence_owner = upper('{$this->mDBname}')
+ AND atc.owner = upper('{$this->mDBname}')" );
while ( ( $row = $result->fetchRow() ) !== false ) {
- $this->sequenceData[$this->tableName( $row[1] )] = array(
+ $this->sequenceData[$row[1]] = array(
'sequence' => $row[0],
'column' => $row[2]
);
}
}
-
+ $table = strtolower( $this->removeIdentifierQuotes( $this->tableName( $table ) ) );
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.
- *
- * @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 );
-
- if ( count( $rows ) == 0 ) {
- return;
- }
-
- # Single row case
- if ( !is_array( reset( $rows ) ) ) {
- $rows = array( $rows );
- }
-
- $sequenceData = $this->getSequenceData( $table );
-
- foreach ( $rows as $row ) {
- # Delete rows which collide
- if ( $uniqueIndexes ) {
- $deleteConds = array();
- foreach ( $uniqueIndexes as $key=>$index ) {
- if ( is_array( $index ) ) {
- $deleteConds2 = array();
- foreach ( $index as $col ) {
- $deleteConds2[$col] = $row[$col];
- }
- $deleteConds[$key] = $this->makeList( $deleteConds2, LIST_AND );
- } else {
- $deleteConds[$index] = $row[$index];
- }
- }
- $deleteConds = array( $this->makeList( $deleteConds, LIST_OR ) );
- $this->delete( $table, $deleteConds, $fname );
- }
-
-
- if ( $sequenceData !== false && !isset( $row[$sequenceData['column']] ) ) {
- $row[$sequenceData['column']] = $this->nextSequenceValue( $sequenceData['sequence'] );
- }
-
- # Now insert the row
- $this->insert( $table, $row, $fname );
- }
- }
-
- # DELETE where the condition is a join
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseOracle::deleteJoin' ) {
- if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabaseOracle::deleteJoin() called with empty $conds' );
- }
-
- $delTable = $this->tableName( $delTable );
- $joinTable = $this->tableName( $joinTable );
- $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
- if ( $conds != '*' ) {
- $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
- }
- $sql .= ')';
-
- $this->query( $sql, $fname );
- }
-
# Returns the size of a text field, or -1 for "unlimited"
function textFieldSize( $table, $field ) {
$fieldInfoData = $this->fieldInfo( $table, $field );
@@ -849,26 +740,21 @@ class DatabaseOracle extends DatabaseBase {
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' ) {
- global $wgDBprefix;
-
$temporary = $temporary ? 'TRUE' : 'FALSE';
- $newName = trim( strtoupper( $newName ), '"');
- $oldName = trim( strtoupper( $oldName ), '"');
+ $newName = strtoupper( $newName );
+ $oldName = strtoupper( $oldName );
- $tabName = substr( $newName, strlen( $wgDBprefix ) );
+ $tabName = substr( $newName, strlen( $this->mTablePrefix ) );
$oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
+ $newPrefix = strtoupper( $this->mTablePrefix );
- return $this->doQuery( 'BEGIN DUPLICATE_TABLE(\'' . $tabName . '\', \'' . $oldPrefix . '\', \'' . strtoupper( $wgDBprefix ) . '\', ' . $temporary . '); END;' );
+ return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', '$oldPrefix', '$newPrefix', $temporary ); END;" );
}
function listTables( $prefix = null, $fname = 'DatabaseOracle::listTables' ) {
@@ -876,8 +762,9 @@ class DatabaseOracle extends DatabaseBase {
if (!empty($prefix)) {
$listWhere = ' AND table_name LIKE \''.strtoupper($prefix).'%\'';
}
-
- $result = $this->doQuery( "SELECT table_name FROM user_tables WHERE table_name NOT LIKE '%!_IDX$_' ESCAPE '!' $listWhere" );
+
+ $owner = strtoupper( $this->mDBname );
+ $result = $this->doQuery( "SELECT table_name FROM all_tables WHERE owner='$owner' AND table_name NOT LIKE '%!_IDX\$_' ESCAPE '!' $listWhere" );
// dirty code ... i know
$endArray = array();
@@ -898,7 +785,7 @@ class DatabaseOracle extends DatabaseBase {
if( !$this->tableExists( $tableName ) ) {
return false;
}
-
+
return $this->doQuery( "DROP TABLE $tableName CASCADE CONSTRAINTS PURGE" );
}
@@ -942,15 +829,35 @@ class DatabaseOracle extends DatabaseBase {
$rset = $this->doQuery( 'SELECT version FROM product_component_version WHERE UPPER(product) LIKE \'ORACLE DATABASE%\'' );
if ( !( $row = $rset->fetchRow() ) ) {
return oci_server_version( $this->mConn );
- }
+ }
return $row['version'];
}
/**
+ * Query whether a given index exists
+ */
+ function indexExists( $table, $index, $fname = 'DatabaseOracle::indexExists' ) {
+ $table = $this->tableName( $table );
+ $table = strtoupper( $this->removeIdentifierQuotes( $table ) );
+ $index = strtoupper( $index );
+ $owner = strtoupper( $this->mDBname );
+ $SQL = "SELECT 1 FROM all_indexes WHERE owner='$owner' AND index_name='{$table}_{$index}'";
+ $res = $this->doQuery( $SQL );
+ if ( $res ) {
+ $count = $res->numRows();
+ $res->free();
+ } else {
+ $count = 0;
+ }
+ return $count != 0;
+ }
+
+ /**
* Query whether a given table exists (in the given schema, or the default mw one if not given)
*/
function tableExists( $table ) {
- $table = $this->addQuotes( trim( $this->tableName($table), '"' ) );
+ $table = $this->tableName( $table );
+ $table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
$owner = $this->addQuotes( strtoupper( $this->mDBname ) );
$SQL = "SELECT 1 FROM all_tables WHERE owner=$owner AND table_name=$table";
$res = $this->doQuery( $SQL );
@@ -960,7 +867,7 @@ class DatabaseOracle extends DatabaseBase {
} else {
$count = 0;
}
- return $count!=0;
+ return $count;
}
/**
@@ -971,6 +878,7 @@ class DatabaseOracle extends DatabaseBase {
*
* @param $table Array
* @param $field String
+ * @return ORAField|ORAResult
*/
private function fieldInfoMulti( $table, $field ) {
$field = strtoupper( $field );
@@ -978,7 +886,7 @@ class DatabaseOracle extends DatabaseBase {
$table = array_map( array( &$this, 'tableNameInternal' ), $table );
$tableWhere = 'IN (';
foreach( $table as &$singleTable ) {
- $singleTable = strtoupper( trim( $singleTable, '"' ) );
+ $singleTable = $this->removeIdentifierQuotes($singleTable);
if ( isset( $this->mFieldInfoCache["$singleTable.$field"] ) ) {
return $this->mFieldInfoCache["$singleTable.$field"];
}
@@ -986,7 +894,7 @@ class DatabaseOracle extends DatabaseBase {
}
$tableWhere = rtrim( $tableWhere, ',' ) . ')';
} else {
- $table = strtoupper( trim( $this->tableNameInternal( $table ), '"' ) );
+ $table = $this->removeIdentifierQuotes( $this->tableNameInternal( $table ) );
if ( isset( $this->mFieldInfoCache["$table.$field"] ) ) {
return $this->mFieldInfoCache["$table.$field"];
}
@@ -1018,6 +926,12 @@ class DatabaseOracle extends DatabaseBase {
return $fieldInfoTemp;
}
+ /**
+ * @throws DBUnexpectedError
+ * @param $table
+ * @param $field
+ * @return ORAField
+ */
function fieldInfo( $table, $field ) {
if ( is_array( $table ) ) {
throw new DBUnexpectedError( $this, 'DatabaseOracle::fieldInfo called with table array!' );
@@ -1027,12 +941,17 @@ class DatabaseOracle extends DatabaseBase {
function begin( $fname = 'DatabaseOracle::begin' ) {
$this->mTrxLevel = 1;
+ $this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
}
function commit( $fname = 'DatabaseOracle::commit' ) {
if ( $this->mTrxLevel ) {
- oci_commit( $this->mConn );
+ $ret = oci_commit( $this->mConn );
+ if ( !$ret ) {
+ throw new DBUnexpectedError( $this, $this->lastError() );
+ }
$this->mTrxLevel = 0;
+ $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
}
}
@@ -1040,6 +959,7 @@ class DatabaseOracle extends DatabaseBase {
if ( $this->mTrxLevel ) {
oci_rollback( $this->mConn );
$this->mTrxLevel = 0;
+ $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
}
}
@@ -1154,15 +1074,23 @@ class DatabaseOracle extends DatabaseBase {
}
public function addIdentifierQuotes( $s ) {
- if ( !$this->mFlags & DBO_DDLMODE ) {
- $s = '"' . str_replace( '"', '""', $s ) . '"';
+ if ( !$this->getFlag( DBO_DDLMODE ) ) {
+ $s = '/*Q*/' . $s;
}
return $s;
}
+ public function removeIdentifierQuotes( $s ) {
+ return strpos($s, '/*Q*/') === FALSE ? $s : substr($s, 5);
+ }
+
+ public function isQuotedIdentifier( $s ) {
+ return strpos($s, '/*Q*/') !== FALSE;
+ }
+
private function wrapFieldForWhere( $table, &$col, &$val ) {
global $wgContLang;
-
+
$col_info = $this->fieldInfoMulti( $table, $col );
$col_type = $col_info != false ? $col_info->type() : 'CONSTANT';
if ( $col_type == 'CLOB' ) {
@@ -1244,20 +1172,38 @@ class DatabaseOracle extends DatabaseBase {
if ( is_array($conds) ) {
$conds = $this->wrapConditionsForWhere( $table, $conds );
}
+ // a hack for deleting pages, users and images (which have non-nullable FKs)
+ // all deletions on these tables have transactions so final failure rollbacks these updates
+ $table = $this->tableName( $table );
+ if ( $table == $this->tableName( 'page' ) ) {
+ $this->update( 'recentchanges', array( 'rc_cur_id' => 0 ), array( 'rc_cur_id' => $conds['page_id'] ), $fname );
+ } elseif ( $table == $this->tableName( 'user' ) ) {
+ $this->update( 'archive', array( 'ar_user' => 0 ), array( 'ar_user' => $conds['user_id'] ), $fname );
+ $this->update( 'ipblocks', array( 'ipb_user' => 0 ), array( 'ipb_user' => $conds['user_id'] ), $fname );
+ $this->update( 'image', array( 'img_user' => 0 ), array( 'img_user' => $conds['user_id'] ), $fname );
+ $this->update( 'oldimage', array( 'oi_user' => 0 ), array( 'oi_user' => $conds['user_id'] ), $fname );
+ $this->update( 'filearchive', array( 'fa_deleted_user' => 0 ), array( 'fa_deleted_user' => $conds['user_id'] ), $fname );
+ $this->update( 'filearchive', array( 'fa_user' => 0 ), array( 'fa_user' => $conds['user_id'] ), $fname );
+ $this->update( 'uploadstash', array( 'us_user' => 0 ), array( 'us_user' => $conds['user_id'] ), $fname );
+ $this->update( 'recentchanges', array( 'rc_user' => 0 ), array( 'rc_user' => $conds['user_id'] ), $fname );
+ $this->update( 'logging', array( 'log_user' => 0 ), array( 'log_user' => $conds['user_id'] ), $fname );
+ } elseif ( $table == $this->tableName( 'image' ) ) {
+ $this->update( 'oldimage', array( 'oi_name' => 0 ), array( 'oi_name' => $conds['img_name'] ), $fname );
+ }
return parent::delete( $table, $conds, $fname );
}
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 {
@@ -1303,8 +1249,8 @@ class DatabaseOracle extends DatabaseBase {
throw new DBUnexpectedError( $this, "Cannot create LOB descriptor: " . $e['message'] );
}
- if ( $col_type == 'BLOB' ) {
- $lob[$col]->writeTemporary( $val );
+ if ( $col_type == 'BLOB' ) {
+ $lob[$col]->writeTemporary( $val );
oci_bind_by_name( $stmt, ":$col", $lob[$col], - 1, SQLT_BLOB );
} else {
$lob[$col]->writeTemporary( $val );
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index bc71a9a5..742a8b51 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -9,8 +9,14 @@
class PostgresField implements Field {
private $name, $tablename, $type, $nullable, $max_length, $deferred, $deferrable, $conname;
- static function fromText($db, $table, $field) {
- global $wgDBmwschema;
+ /**
+ * @param $db DatabaseBase
+ * @param $table
+ * @param $field
+ * @return null|PostgresField
+ */
+ static function fromText( $db, $table, $field ) {
+ global $wgDBmwschema;
$q = <<<SQL
SELECT
@@ -33,7 +39,7 @@ AND relname=%s
AND attname=%s;
SQL;
- $table = $db->tableName( $table );
+ $table = $db->tableName( $table, false );
$res = $db->query(
sprintf( $q,
$db->addQuotes( $wgDBmwschema ),
@@ -137,10 +143,6 @@ class DatabasePostgres extends DatabaseBase {
return $this->numRows( $res );
}
- static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
- return new DatabasePostgres( $server, $user, $password, $dbName, $flags );
- }
-
/**
* Usually aborts on failure
*/
@@ -155,9 +157,10 @@ class DatabasePostgres extends DatabaseBase {
if ( !strlen( $user ) ) { # e.g. the class is being loaded
return;
}
+
$this->close();
$this->mServer = $server;
- $this->mPort = $port = $wgDBport;
+ $port = $wgDBport;
$this->mUser = $user;
$this->mPassword = $password;
$this->mDBname = $dbName;
@@ -194,21 +197,34 @@ class DatabasePostgres extends DatabaseBase {
$this->doQuery( "SET client_min_messages = 'ERROR'" );
}
- $this->doQuery( "SET client_encoding='UTF8'" );
+ $this->query( "SET client_encoding='UTF8'", __METHOD__ );
+ $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
+ $this->query( "SET timezone = 'GMT'", __METHOD__ );
- global $wgDBmwschema, $wgDBts2schema;
- if ( isset( $wgDBmwschema ) && isset( $wgDBts2schema )
- && $wgDBmwschema !== 'mediawiki'
- && preg_match( '/^\w+$/', $wgDBmwschema )
- && preg_match( '/^\w+$/', $wgDBts2schema )
- ) {
+ global $wgDBmwschema;
+ if ( $this->schemaExists( $wgDBmwschema ) ) {
$safeschema = $this->addIdentifierQuotes( $wgDBmwschema );
- $this->doQuery( "SET search_path = $safeschema, $wgDBts2schema, public" );
+ $this->doQuery( "SET search_path = $safeschema" );
+ } else {
+ $this->doQuery( "SET search_path = public" );
}
return $this->mConn;
}
+ /**
+ * Postgres doesn't support selectDB in the same way MySQL does. So if the
+ * DB name doesn't match the open connection, open a new one
+ * @return
+ */
+ function selectDB( $db ) {
+ if ( $this->mDBname !== $db ) {
+ return (bool)$this->open( $this->mServer, $this->mUser, $this->mPassword, $db );
+ } else {
+ return true;
+ }
+ }
+
function makeConnectionString( $vars ) {
$s = '';
foreach ( $vars as $name => $value ) {
@@ -230,7 +246,7 @@ class DatabasePostgres extends DatabaseBase {
}
}
- function doQuery( $sql ) {
+ protected function doQuery( $sql ) {
if ( function_exists( 'mb_convert_encoding' ) ) {
$sql = mb_convert_encoding( $sql, 'UTF-8' );
}
@@ -247,7 +263,10 @@ class DatabasePostgres extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- if ( !@pg_free_result( $res ) ) {
+ wfSuppressWarnings();
+ $ok = pg_free_result( $res );
+ wfRestoreWarnings();
+ if ( !$ok ) {
throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
}
}
@@ -256,11 +275,12 @@ class DatabasePostgres extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @$row = pg_fetch_object( $res );
- # FIXME: HACK HACK HACK HACK debug
+ wfSuppressWarnings();
+ $row = pg_fetch_object( $res );
+ wfRestoreWarnings();
+ # @todo FIXME: HACK HACK HACK HACK debug
- # TODO:
- # hashar : not sure if the following test really trigger if the object
+ # @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 ) ) );
@@ -272,7 +292,9 @@ class DatabasePostgres extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @$row = pg_fetch_array( $res );
+ wfSuppressWarnings();
+ $row = pg_fetch_array( $res );
+ wfRestoreWarnings();
if( pg_last_error( $this->mConn ) ) {
throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
@@ -283,7 +305,9 @@ class DatabasePostgres extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
- @$n = pg_num_rows( $res );
+ wfSuppressWarnings();
+ $n = pg_num_rows( $res );
+ wfRestoreWarnings();
if( pg_last_error( $this->mConn ) ) {
throw new DBUnexpectedError( $this, 'SQL error: ' . htmlspecialchars( pg_last_error( $this->mConn ) ) );
}
@@ -529,7 +553,7 @@ class DatabasePostgres extends DatabaseBase {
* Source items may be literals rather then field names, but strings should be quoted with Database::addQuotes()
* $conds may be "*" to copy the whole table
* srcTable may be an array of tables.
- * @todo FIXME: implement this a little better (seperate select/insert)?
+ * @todo FIXME: Implement this a little better (seperate select/insert)?
*/
function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'DatabasePostgres::insertSelect',
$insertOptions = array(), $selectOptions = array() )
@@ -598,7 +622,7 @@ class DatabasePostgres extends DatabaseBase {
return $res;
}
- function tableName( $name ) {
+ function tableName( $name, $quoted = true ) {
# Replace reserved words with better ones
switch( $name ) {
case 'user':
@@ -606,7 +630,7 @@ class DatabasePostgres extends DatabaseBase {
case 'text':
return 'pagecontent';
default:
- return $name;
+ return parent::tableName( $name, $quoted );
}
}
@@ -632,83 +656,6 @@ class DatabasePostgres extends DatabaseBase {
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
- */
- function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabasePostgres::replace' ) {
- $table = $this->tableName( $table );
-
- if ( count( $rows ) == 0 ) {
- return;
- }
-
- # Single row case
- if ( !is_array( reset( $rows ) ) ) {
- $rows = array( $rows );
- }
-
- foreach( $rows as $row ) {
- # 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 );
- }
-
- # 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 );
- }
- }
-
- # DELETE where the condition is a join
- function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabasePostgres::deleteJoin' ) {
- if ( !$conds ) {
- throw new DBUnexpectedError( $this, 'DatabasePostgres::deleteJoin() called with empty $conds' );
- }
-
- $delTable = $this->tableName( $delTable );
- $joinTable = $this->tableName( $joinTable );
- $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
- if ( $conds != '*' ) {
- $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
- }
- $sql .= ')';
-
- $this->query( $sql, $fname );
- }
-
# Returns the size of a text field, or -1 for "unlimited"
function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
@@ -735,6 +682,8 @@ class DatabasePostgres extends DatabaseBase {
}
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabasePostgres::duplicateTableStructure' ) {
+ $newName = $this->addIdentifierQuotes( $newName );
+ $oldName = $this->addIdentifierQuotes( $oldName );
return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname );
}
@@ -788,6 +737,7 @@ class DatabasePostgres extends DatabaseBase {
if ( !$schema ) {
$schema = $wgDBmwschema;
}
+ $table = $this->tableName( $table, false );
$etable = $this->addQuotes( $table );
$eschema = $this->addQuotes( $schema );
$SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
@@ -899,6 +849,10 @@ SQL;
return $sql;
}
+ /**
+ * @param $b
+ * @return Blob
+ */
function encodeBlob( $b ) {
return new Blob( pg_escape_bytea( $this->mConn, $b ) );
}
@@ -914,6 +868,10 @@ SQL;
return pg_escape_string( $this->mConn, $s );
}
+ /**
+ * @param $s null|bool|Blob
+ * @return int|string
+ */
function addQuotes( $s ) {
if ( is_null( $s ) ) {
return 'NULL';
@@ -971,13 +929,21 @@ SQL;
}
if ( isset( $options['GROUP BY'] ) ) {
- $preLimitTail .= ' GROUP BY ' . $options['GROUP BY'];
+ $gb = is_array( $options['GROUP BY'] )
+ ? implode( ',', $options['GROUP BY'] )
+ : $options['GROUP BY'];
+ $preLimitTail .= " GROUP BY {$gb}";
}
+
if ( isset( $options['HAVING'] ) ) {
$preLimitTail .= " HAVING {$options['HAVING']}";
}
+
if ( isset( $options['ORDER BY'] ) ) {
- $preLimitTail .= ' ORDER BY ' . $options['ORDER BY'];
+ $ob = is_array( $options['ORDER BY'] )
+ ? implode( ',', $options['ORDER BY'] )
+ : $options['ORDER BY'];
+ $preLimitTail .= " ORDER BY {$ob}";
}
//if ( isset( $options['LIMIT'] ) ) {
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index 503ebdf6..e298175d 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -20,8 +20,18 @@ class DatabaseSqlite extends DatabaseBase {
var $mName;
/**
+ * @var PDO
+ */
+ protected $mConn;
+
+ /**
* Constructor.
* Parameters $server, $user and $password are not used.
+ * @param $server string
+ * @param $user string
+ * @param $password string
+ * @param $dbName string
+ * @param $flags int
*/
function __construct( $server = false, $user = false, $password = false, $dbName = false, $flags = 0 ) {
$this->mName = $dbName;
@@ -35,21 +45,31 @@ class DatabaseSqlite extends DatabaseBase {
}
}
+ /**
+ * @return string
+ */
function getType() {
return 'sqlite';
}
/**
* @todo: check if it should be true like parent class
+ *
+ * @return bool
*/
- function implicitGroupby() { return false; }
-
- static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
- return new DatabaseSqlite( $server, $user, $password, $dbName, $flags );
+ function implicitGroupby() {
+ return false;
}
/** Open an SQLite database and return a resource handle to it
* NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
+ *
+ * @param $server
+ * @param $user
+ * @param $pass
+ * @param $dbName
+ *
+ * @return PDO
*/
function open( $server, $user, $pass, $dbName ) {
global $wgSQLiteDataDir;
@@ -65,7 +85,10 @@ class DatabaseSqlite extends DatabaseBase {
/**
* Opens a database file
- * @return SQL connection or false if failed
+ *
+ * @param $fileName string
+ *
+ * @return PDO|false SQL connection or false if failed
*/
function openFile( $fileName ) {
$this->mDatabaseFile = $fileName;
@@ -93,6 +116,8 @@ class DatabaseSqlite extends DatabaseBase {
/**
* Close an SQLite database
+ *
+ * @return bool
*/
function close() {
$this->mOpened = false;
@@ -115,7 +140,7 @@ class DatabaseSqlite extends DatabaseBase {
/**
* Check if the searchindext table is FTS enabled.
- * @returns false if not enabled.
+ * @return false if not enabled.
*/
function checkForEnabledSearch() {
if ( self::$fulltextEnabled === null ) {
@@ -154,9 +179,12 @@ class DatabaseSqlite extends DatabaseBase {
/**
* 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
+ *
+ * @return ResultWrapper
*/
function attachDatabase( $name, $file = false, $fname = 'DatabaseSqlite::attachDatabase' ) {
global $wgSQLiteDataDir;
@@ -169,6 +197,10 @@ class DatabaseSqlite extends DatabaseBase {
/**
* @see DatabaseBase::isWriteQuery()
+ *
+ * @param $sql string
+ *
+ * @return bool
*/
function isWriteQuery( $sql ) {
return parent::isWriteQuery( $sql ) && !preg_match( '/^ATTACH\b/i', $sql );
@@ -176,8 +208,12 @@ class DatabaseSqlite extends DatabaseBase {
/**
* SQLite doesn't allow buffered results or data seeking etc, so we'll use fetchAll as the result
+ *
+ * @param $sql string
+ *
+ * @return ResultWrapper
*/
- function doQuery( $sql ) {
+ protected function doQuery( $sql ) {
$res = $this->mConn->query( $sql );
if ( $res === false ) {
return false;
@@ -189,6 +225,9 @@ class DatabaseSqlite extends DatabaseBase {
return $res;
}
+ /**
+ * @param $res ResultWrapper
+ */
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
$res->result = null;
@@ -197,6 +236,10 @@ class DatabaseSqlite extends DatabaseBase {
}
}
+ /**
+ * @param $res ResultWrapper
+ * @return
+ */
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
$r =& $res->result;
@@ -219,6 +262,10 @@ class DatabaseSqlite extends DatabaseBase {
return false;
}
+ /**
+ * @param $res ResultWrapper
+ * @return bool|mixed
+ */
function fetchRow( $res ) {
if ( $res instanceof ResultWrapper ) {
$r =& $res->result;
@@ -235,37 +282,60 @@ class DatabaseSqlite extends DatabaseBase {
/**
* The PDO::Statement class implements the array interface so count() will work
+ *
+ * @param $res ResultWrapper
+ *
+ * @return int
*/
function numRows( $res ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
return count( $r );
}
+ /**
+ * @param $res ResultWrapper
+ * @return int
+ */
function numFields( $res ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
return is_array( $r ) ? count( $r[0] ) : 0;
}
+ /**
+ * @param $res ResultWrapper
+ * @param $n
+ * @return bool
+ */
function fieldName( $res, $n ) {
$r = $res instanceof ResultWrapper ? $res->result : $res;
if ( is_array( $r ) ) {
$keys = array_keys( $r[0] );
return $keys[$n];
}
- return false;
+ return false;
}
/**
* Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
+ *
+ * @param $name
+ * @param bool $quoted
+ * @return string
*/
- function tableName( $name ) {
+ function tableName( $name, $quoted = true ) {
// table names starting with sqlite_ are reserved
- if ( strpos( $name, 'sqlite_' ) === 0 ) return $name;
- return str_replace( '`', '', parent::tableName( $name ) );
+ if ( strpos( $name, 'sqlite_' ) === 0 ) {
+ return $name;
+ }
+ return str_replace( '"', '', parent::tableName( $name, $quoted ) );
}
/**
* Index names have DB scope
+ *
+ * @param $index string
+ *
+ * @return string
*/
function indexName( $index ) {
return $index;
@@ -273,11 +343,17 @@ class DatabaseSqlite extends DatabaseBase {
/**
* This must be called after nextSequenceVal
+ *
+ * @return int
*/
function insertId() {
return $this->mConn->lastInsertId();
}
+ /**
+ * @param $res ResultWrapper
+ * @param $row
+ */
function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
$r =& $res->result;
@@ -292,6 +368,9 @@ class DatabaseSqlite extends DatabaseBase {
}
}
+ /**
+ * @return string
+ */
function lastError() {
if ( !is_object( $this->mConn ) ) {
return "Cannot return last error, no db connection";
@@ -300,6 +379,9 @@ class DatabaseSqlite extends DatabaseBase {
return isset( $e[2] ) ? $e[2] : '';
}
+ /**
+ * @return string
+ */
function lastErrno() {
if ( !is_object( $this->mConn ) ) {
return "Cannot return last error, no db connection";
@@ -309,6 +391,9 @@ class DatabaseSqlite extends DatabaseBase {
}
}
+ /**
+ * @return int
+ */
function affectedRows() {
return $this->mAffectedRows;
}
@@ -317,6 +402,8 @@ class DatabaseSqlite extends DatabaseBase {
* Returns information about an index
* Returns false if the index does not exist
* - if errors are explicitly ignored, returns NULL on failure
+ *
+ * @return array
*/
function indexInfo( $table, $index, $fname = 'DatabaseSqlite::indexExists' ) {
$sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')';
@@ -334,6 +421,12 @@ class DatabaseSqlite extends DatabaseBase {
return $info;
}
+ /**
+ * @param $table
+ * @param $index
+ * @param $fname string
+ * @return bool|null
+ */
function indexUnique( $table, $index, $fname = 'DatabaseSqlite::indexUnique' ) {
$row = $this->selectRow( 'sqlite_master', '*',
array(
@@ -356,6 +449,10 @@ class DatabaseSqlite extends DatabaseBase {
/**
* Filter the options used in SELECT statements
+ *
+ * @param $options array
+ *
+ * @return array
*/
function makeSelectOptions( $options ) {
foreach ( $options as $k => $v ) {
@@ -367,22 +464,44 @@ class DatabaseSqlite extends DatabaseBase {
}
/**
- * Based on generic method (parent) with some prior SQLite-sepcific adjustments
+ * @param $options array
+ * @return string
*/
- function insert( $table, $a, $fname = 'DatabaseSqlite::insert', $options = array() ) {
- if ( !count( $a ) ) {
- return true;
- }
- if ( !is_array( $options ) ) {
- $options = array( $options );
- }
+ function makeUpdateOptions( $options ) {
+ $options = self::fixIgnore( $options );
+ return parent::makeUpdateOptions( $options );
+ }
+ /**
+ * @param $options array
+ * @return array
+ */
+ static function fixIgnore( $options ) {
# SQLite uses OR IGNORE not just IGNORE
foreach ( $options as $k => $v ) {
if ( $v == 'IGNORE' ) {
$options[$k] = 'OR IGNORE';
}
}
+ return $options;
+ }
+
+ /**
+ * @param $options array
+ * @return string
+ */
+ function makeInsertOptions( $options ) {
+ $options = self::fixIgnore( $options );
+ return parent::makeInsertOptions( $options );
+ }
+
+ /**
+ * 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;
+ }
# SQLite can't handle multi-row inserts, so divide up into multiple single-row inserts
if ( isset( $a[0] ) && is_array( $a[0] ) ) {
@@ -399,6 +518,13 @@ class DatabaseSqlite extends DatabaseBase {
return $ret;
}
+ /**
+ * @param $table
+ * @param $uniqueIndexes
+ * @param $rows
+ * @param $fname string
+ * @return bool|ResultWrapper
+ */
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseSqlite::replace' ) {
if ( !count( $rows ) ) return true;
@@ -406,12 +532,12 @@ class DatabaseSqlite extends DatabaseBase {
if ( isset( $rows[0] ) && is_array( $rows[0] ) ) {
$ret = true;
foreach ( $rows as $v ) {
- if ( !parent::replace( $table, $uniqueIndexes, $v, "$fname/multi-row" ) ) {
+ if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) {
$ret = false;
}
}
} else {
- $ret = parent::replace( $table, $uniqueIndexes, $rows, "$fname/single-row" );
+ $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" );
}
return $ret;
@@ -420,32 +546,47 @@ class DatabaseSqlite extends DatabaseBase {
/**
* Returns the size of a text field, or -1 for "unlimited"
* In SQLite this is SQLITE_MAX_LENGTH, by default 1GB. No way to query it though.
+ *
+ * @return int
*/
function textFieldSize( $table, $field ) {
- return - 1;
+ return -1;
}
+ /**
+ * @return bool
+ */
function unionSupportsOrderAndLimit() {
return false;
}
+ /**
+ * @param $sqls
+ * @param $all
+ * @return string
+ */
function unionQueries( $sqls, $all ) {
$glue = $all ? ' UNION ALL ' : ' UNION ';
return implode( $glue, $sqls );
}
- public function unixTimestamp( $field ) {
- return $field;
- }
-
+ /**
+ * @return bool
+ */
function wasDeadlock() {
return $this->lastErrno() == 5; // SQLITE_BUSY
}
+ /**
+ * @return bool
+ */
function wasErrorReissuable() {
return $this->lastErrno() == 17; // SQLITE_SCHEMA;
}
+ /**
+ * @return bool
+ */
function wasReadOnlyError() {
return $this->lastErrno() == 8; // SQLITE_READONLY;
}
@@ -475,6 +616,8 @@ class DatabaseSqlite extends DatabaseBase {
/**
* Get information about a given field
* Returns false if the field does not exist.
+ *
+ * @return SQLiteField|false
*/
function fieldInfo( $table, $field ) {
$tableName = $this->tableName( $table );
@@ -489,35 +632,58 @@ class DatabaseSqlite extends DatabaseBase {
}
function begin( $fname = '' ) {
- if ( $this->mTrxLevel == 1 ) $this->commit();
+ if ( $this->mTrxLevel == 1 ) {
+ $this->commit();
+ }
$this->mConn->beginTransaction();
$this->mTrxLevel = 1;
}
function commit( $fname = '' ) {
- if ( $this->mTrxLevel == 0 ) return;
+ if ( $this->mTrxLevel == 0 ) {
+ return;
+ }
$this->mConn->commit();
$this->mTrxLevel = 0;
}
function rollback( $fname = '' ) {
- if ( $this->mTrxLevel == 0 ) return;
+ if ( $this->mTrxLevel == 0 ) {
+ return;
+ }
$this->mConn->rollBack();
$this->mTrxLevel = 0;
}
+ /**
+ * @param $sql
+ * @param $num
+ * @return string
+ */
function limitResultForUpdate( $sql, $num ) {
return $this->limitResult( $sql, $num );
}
+ /**
+ * @param $s string
+ * @return string
+ */
function strencode( $s ) {
return substr( $this->addQuotes( $s ), 1, - 1 );
}
+ /**
+ * @param $b
+ * @return Blob
+ */
function encodeBlob( $b ) {
return new Blob( $b );
}
+ /**
+ * @param $b Blob|string
+ * @return string
+ */
function decodeBlob( $b ) {
if ( $b instanceof Blob ) {
$b = $b->fetch();
@@ -525,6 +691,10 @@ class DatabaseSqlite extends DatabaseBase {
return $b;
}
+ /**
+ * @param $s Blob|string
+ * @return string
+ */
function addQuotes( $s ) {
if ( $s instanceof Blob ) {
return "x'" . bin2hex( $s->fetch() ) . "'";
@@ -533,6 +703,9 @@ class DatabaseSqlite extends DatabaseBase {
}
}
+ /**
+ * @return string
+ */
function buildLike() {
$params = func_get_args();
if ( count( $params ) > 0 && is_array( $params[0] ) ) {
@@ -541,6 +714,9 @@ class DatabaseSqlite extends DatabaseBase {
return parent::buildLike( $params ) . "ESCAPE '\' ";
}
+ /**
+ * @return string
+ */
public function getSearchEngine() {
return "SearchSqlite";
}
@@ -554,6 +730,10 @@ class DatabaseSqlite extends DatabaseBase {
return call_user_func_array( $function, $args );
}
+ /**
+ * @param $s string
+ * @return string
+ */
protected function replaceVars( $s ) {
$s = parent::replaceVars( $s );
if ( preg_match( '/^\s*(CREATE|ALTER) TABLE/i', $s ) ) {
@@ -596,23 +776,75 @@ class DatabaseSqlite extends DatabaseBase {
return $s;
}
- /*
+ /**
* Build a concatenation list to feed into a SQL query
+ *
+ * @param $stringList array
+ *
+ * @return string
*/
function buildConcat( $stringList ) {
return '(' . implode( ') || (', $stringList ) . ')';
}
+ /**
+ * @throws MWException
+ * @param $oldName
+ * @param $newName
+ * @param $temporary bool
+ * @param $fname string
+ * @return bool|ResultWrapper
+ */
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseSqlite::duplicateTableStructure' ) {
- $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name='$oldName' AND type='table'", $fname );
+ $res = $this->query( "SELECT sql FROM sqlite_master WHERE tbl_name=" . $this->addQuotes( $oldName ) . " AND type='table'", $fname );
$obj = $this->fetchObject( $res );
if ( !$obj ) {
throw new MWException( "Couldn't retrieve structure for table $oldName" );
}
$sql = $obj->sql;
- $sql = preg_replace( '/\b' . preg_quote( $oldName ) . '\b/', $newName, $sql, 1 );
+ $sql = preg_replace( '/(?<=\W)"?' . preg_quote( trim( $this->addIdentifierQuotes( $oldName ), '"' ) ) . '"?(?=\W)/', $this->addIdentifierQuotes( $newName ), $sql, 1 );
+ if ( $temporary ) {
+ if ( preg_match( '/^\\s*CREATE\\s+VIRTUAL\\s+TABLE\b/i', $sql ) ) {
+ wfDebug( "Table $oldName is virtual, can't create a temporary duplicate.\n" );
+ } else {
+ $sql = str_replace( 'CREATE TABLE', 'CREATE TEMPORARY TABLE', $sql );
+ }
+ }
return $this->query( $sql, $fname );
}
+
+
+ /**
+ * List all tables on the database
+ *
+ * @param $prefix Only show tables with this prefix, e.g. mw_
+ * @param $fname String: calling function name
+ *
+ * @return array
+ */
+ function listTables( $prefix = null, $fname = 'DatabaseSqlite::listTables' ) {
+ $result = $this->select(
+ 'sqlite_master',
+ 'name',
+ "type='table'"
+ );
+
+ $endArray = array();
+
+ foreach( $result as $table ) {
+ $vars = get_object_vars($table);
+ $table = array_pop( $vars );
+
+ if( !$prefix || strpos( $table, $prefix ) === 0 ) {
+ if ( strpos( $table, 'sqlite_' ) !== 0 ) {
+ $endArray[] = $table;
+ }
+
+ }
+ }
+
+ return $endArray;
+ }
} // end DatabaseSqlite class
@@ -656,6 +888,9 @@ class SQLiteField implements Field {
return $this->info->dflt_value;
}
+ /**
+ * @return bool
+ */
function isNullable() {
return !$this->info->notnull;
}
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
new file mode 100644
index 00000000..d1bced6b
--- /dev/null
+++ b/includes/db/DatabaseUtility.php
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Utility class.
+ * @ingroup Database
+ */
+class DBObject {
+ public $mData;
+
+ function __construct( $data ) {
+ $this->mData = $data;
+ }
+
+ function isLOB() {
+ return false;
+ }
+
+ function data() {
+ return $this->mData;
+ }
+}
+
+/**
+ * Utility class
+ * @ingroup Database
+ *
+ * This allows us to distinguish a blob from a normal string and an array of strings
+ */
+class Blob {
+ private $mData;
+
+ function __construct( $data ) {
+ $this->mData = $data;
+ }
+
+ function fetch() {
+ return $this->mData;
+ }
+}
+
+/**
+ * Base for all database-specific classes representing information about database fields
+ * @ingroup Database
+ */
+interface Field {
+ /**
+ * Field name
+ * @return string
+ */
+ function name();
+
+ /**
+ * Name of table this field belongs to
+ * @return string
+ */
+ function tableName();
+
+ /**
+ * Database type
+ * @return string
+ */
+ function type();
+
+ /**
+ * Whether this field can store NULL values
+ * @return bool
+ */
+ function isNullable();
+}
+
+/**
+ * Result wrapper for grabbing data queried by someone else
+ * @ingroup Database
+ */
+class ResultWrapper implements Iterator {
+ var $db, $result, $pos = 0, $currentRow = null;
+
+ /**
+ * Create a new result object from a result resource and a Database object
+ *
+ * @param DatabaseBase $database
+ * @param resource $result
+ */
+ function __construct( $database, $result ) {
+ $this->db = $database;
+
+ if ( $result instanceof ResultWrapper ) {
+ $this->result = $result->result;
+ } else {
+ $this->result = $result;
+ }
+ }
+
+ /**
+ * Get the number of rows in a result object
+ *
+ * @return integer
+ */
+ function numRows() {
+ return $this->db->numRows( $this );
+ }
+
+ /**
+ * 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.
+ *
+ * @return object
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ function fetchObject() {
+ return $this->db->fetchObject( $this );
+ }
+
+ /**
+ * Fetch the next row from the given result object, in associative array
+ * form. Fields are retrieved with $row['fieldname'].
+ *
+ * @return Array
+ * @throws DBUnexpectedError Thrown if the database returns an error
+ */
+ function fetchRow() {
+ return $this->db->fetchRow( $this );
+ }
+
+ /**
+ * Free a result object
+ */
+ function free() {
+ $this->db->freeResult( $this );
+ unset( $this->result );
+ unset( $this->db );
+ }
+
+ /**
+ * Change the position of the cursor in a result object.
+ * See mysql_data_seek()
+ *
+ * @param $row integer
+ */
+ function seek( $row ) {
+ $this->db->dataSeek( $this, $row );
+ }
+
+ /*********************
+ * Iterator functions
+ * Note that using these in combination with the non-iterator functions
+ * above may cause rows to be skipped or repeated.
+ */
+
+ function rewind() {
+ if ( $this->numRows() ) {
+ $this->db->dataSeek( $this, 0 );
+ }
+ $this->pos = 0;
+ $this->currentRow = null;
+ }
+
+ function current() {
+ if ( is_null( $this->currentRow ) ) {
+ $this->next();
+ }
+ return $this->currentRow;
+ }
+
+ function key() {
+ return $this->pos;
+ }
+
+ function next() {
+ $this->pos++;
+ $this->currentRow = $this->fetchObject();
+ return $this->currentRow;
+ }
+
+ function valid() {
+ return $this->current() !== false;
+ }
+}
+
+/**
+ * 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;
+ }
+
+ /**
+ * @return int
+ */
+ function numRows() {
+ return count( $this->result );
+ }
+
+ function fetchRow() {
+ if ( $this->pos < count( $this->result ) ) {
+ $this->currentRow = $this->result[$this->pos];
+ } else {
+ $this->currentRow = false;
+ }
+ $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->fetchRow();
+ if ( $this->currentRow ) {
+ return (object)$this->currentRow;
+ } else {
+ return false;
+ }
+ }
+
+ function rewind() {
+ $this->pos = 0;
+ $this->currentRow = null;
+ }
+
+ function next() {
+ return $this->fetchObject();
+ }
+}
+
+/**
+ * Used by DatabaseBase::buildLike() to represent characters that have special meaning in SQL LIKE clauses
+ * and thus need no escaping. Don't instantiate it manually, use DatabaseBase::anyChar() and anyString() instead.
+ */
+class LikeMatch {
+ private $str;
+
+ /**
+ * Store a string into a LikeMatch marker object.
+ *
+ * @param String $s
+ */
+ public function __construct( $s ) {
+ $this->str = $s;
+ }
+
+ /**
+ * Return the original stored string.
+ *
+ * @return String
+ */
+ public function toString() {
+ return $this->str;
+ }
+}
+
+/**
+ * An object representing a master or slave position in a replicated setup.
+ */
+interface DBMasterPos {
+}
+
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index f84a70e5..22a84960 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -11,6 +11,10 @@
* @ingroup Database
*/
abstract class LBFactory {
+
+ /**
+ * @var LBFactory
+ */
static $instance;
/**
@@ -23,15 +27,9 @@ abstract class LBFactory {
}
/**
- * 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
+ *
+ * @return LBFactory
*/
static function &singleton() {
if ( is_null( self::$instance ) ) {
@@ -56,6 +54,8 @@ abstract class LBFactory {
/**
* Set the instance to be the given object
+ *
+ * @param $instance LBFactory
*/
static function setInstance( $instance ) {
self::destroyInstance();
@@ -84,21 +84,25 @@ abstract class LBFactory {
*/
abstract function getMainLB( $wiki = false );
- /*
+ /**
* Create a new load balancer for external storage. The resulting object will be
* untracked, not chronology-protected, and the caller is responsible for
* cleaning it up.
*
* @param $cluster String: external storage cluster, or false for core
* @param $wiki String: wiki ID, or false for the current wiki
+ *
+ * @return LoadBalancer
*/
abstract function newExternalLB( $cluster, $wiki = false );
- /*
+ /**
* Get a cached (tracked) load balancer for external storage
*
* @param $cluster String: external storage cluster, or false for core
* @param $wiki String: wiki ID, or false for the current wiki
+ *
+ * @return LoadBalancer
*/
abstract function &getExternalLB( $cluster, $wiki = false );
@@ -141,6 +145,10 @@ abstract class LBFactory {
* A simple single-master LBFactory that gets its configuration from the b/c globals
*/
class LBFactory_Simple extends LBFactory {
+
+ /**
+ * @var LoadBalancer
+ */
var $mainLB;
var $extLBs = array();
@@ -151,6 +159,10 @@ class LBFactory_Simple extends LBFactory {
$this->chronProt = new ChronologyProtector;
}
+ /**
+ * @param $wiki
+ * @return LoadBalancer
+ */
function newMainLB( $wiki = false ) {
global $wgDBservers, $wgMasterWaitTimeout;
if ( $wgDBservers ) {
@@ -174,6 +186,10 @@ class LBFactory_Simple extends LBFactory {
));
}
+ /**
+ * @param $wiki
+ * @return LoadBalancer
+ */
function getMainLB( $wiki = false ) {
if ( !isset( $this->mainLB ) ) {
$this->mainLB = $this->newMainLB( $wiki );
@@ -183,6 +199,12 @@ class LBFactory_Simple extends LBFactory {
return $this->mainLB;
}
+ /**
+ * @throws MWException
+ * @param $cluster
+ * @param $wiki
+ * @return LoadBalancer
+ */
function newExternalLB( $cluster, $wiki = false ) {
global $wgExternalServers;
if ( !isset( $wgExternalServers[$cluster] ) ) {
@@ -193,6 +215,11 @@ class LBFactory_Simple extends LBFactory {
));
}
+ /**
+ * @param $cluster
+ * @param $wiki
+ * @return array
+ */
function &getExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
$this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
diff --git a/includes/db/LBFactory_Multi.php b/includes/db/LBFactory_Multi.php
index 0d411ec6..61e56e78 100644
--- a/includes/db/LBFactory_Multi.php
+++ b/includes/db/LBFactory_Multi.php
@@ -98,6 +98,10 @@ class LBFactory_Multi extends LBFactory {
return $section;
}
+ /**
+ * @param $wiki string
+ * @return LoadBalancer
+ */
function newMainLB( $wiki = false ) {
list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
$section = $this->getSectionForWiki( $wiki );
@@ -111,6 +115,10 @@ class LBFactory_Multi extends LBFactory {
return $this->newLoadBalancer( $this->serverTemplate, $this->sectionLoads[$section], $groupLoads );
}
+ /**
+ * @param $wiki
+ * @return LoadBalancer
+ */
function getMainLB( $wiki = false ) {
$section = $this->getSectionForWiki( $wiki );
if ( !isset( $this->mainLBs[$section] ) ) {
@@ -122,6 +130,11 @@ class LBFactory_Multi extends LBFactory {
return $this->mainLBs[$section];
}
+ /**
+ * @param $cluster
+ * @param $wiki
+ * @return LoadBalancer
+ */
function newExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->externalLoads[$cluster] ) ) {
throw new MWException( __METHOD__.": Unknown cluster \"$cluster\"" );
@@ -136,6 +149,11 @@ class LBFactory_Multi extends LBFactory {
return $this->newLoadBalancer( $template, $this->externalLoads[$cluster], array() );
}
+ /**
+ * @param $cluster
+ * @param $wiki
+ * @return LoadBalancer
+ */
function &getExternalLB( $cluster, $wiki = false ) {
if ( !isset( $this->extLBs[$cluster] ) ) {
$this->extLBs[$cluster] = $this->newExternalLB( $cluster, $wiki );
@@ -146,6 +164,8 @@ class LBFactory_Multi extends LBFactory {
/**
* Make a new load balancer object based on template and load array
+ *
+ * @return LoadBalancer
*/
function newLoadBalancer( $template, $loads, $groupLoads ) {
global $wgMasterWaitTimeout;
@@ -159,6 +179,8 @@ class LBFactory_Multi extends LBFactory {
/**
* Make a server array as expected by LoadBalancer::__construct, using a template and load array
+ *
+ * @return array
*/
function makeServerArray( $template, $loads, $groupLoads ) {
$servers = array();
diff --git a/includes/db/LBFactory_Single.php b/includes/db/LBFactory_Single.php
index 25acdc5b..89b41321 100644
--- a/includes/db/LBFactory_Single.php
+++ b/includes/db/LBFactory_Single.php
@@ -7,29 +7,55 @@ class LBFactory_Single extends LBFactory {
protected $lb;
/**
- * @param $conf An associative array with one member:
+ * @param $conf array An associative array with one member:
* - connection: The DatabaseBase connection object
*/
function __construct( $conf ) {
$this->lb = new LoadBalancer_Single( $conf );
}
+ /**
+ * @param $wiki
+ *
+ * @return LoadBalancer_Single
+ */
function newMainLB( $wiki = false ) {
return $this->lb;
}
+ /**
+ * @param $wiki
+ *
+ * @return LoadBalancer_Single
+ */
function getMainLB( $wiki = false ) {
return $this->lb;
}
+ /**
+ * @param $cluster
+ * @param $wiki
+ *
+ * @return LoadBalancer_Single
+ */
function newExternalLB( $cluster, $wiki = false ) {
return $this->lb;
}
+ /**
+ * @param $cluster
+ * @param $wiki
+ *
+ * @return LoadBalancer_Single
+ */
function &getExternalLB( $cluster, $wiki = false ) {
return $this->lb;
}
+ /**
+ * @param $callback string|array
+ * @param $params array
+ */
function forEachLB( $callback, $params = array() ) {
call_user_func_array( $callback, array_merge( array( $this->lb ), $params ) );
}
@@ -39,8 +65,15 @@ class LBFactory_Single extends LBFactory {
* Helper class for LBFactory_Single.
*/
class LoadBalancer_Single extends LoadBalancer {
+
+ /**
+ * @var DatabaseBase
+ */
var $db;
+ /**
+ * @param $params array
+ */
function __construct( $params ) {
$this->db = $params['connection'];
parent::__construct( array( 'servers' => array( array(
@@ -51,6 +84,13 @@ class LoadBalancer_Single extends LoadBalancer {
) ) ) );
}
+ /**
+ *
+ * @param $server string
+ * @param $dbNameOverride bool
+ *
+ * @return DatabaseBase
+ */
function reallyOpenConnection( $server, $dbNameOverride = false ) {
return $this->db;
}
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index d899ce07..c7210c4c 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -13,13 +13,13 @@
* @ingroup Database
*/
class LoadBalancer {
- /* private */ var $mServers, $mConns, $mLoads, $mGroupLoads;
- /* private */ var $mErrorConnection;
- /* private */ var $mReadIndex, $mAllowLagged;
- /* private */ var $mWaitForPos, $mWaitTimeout;
- /* private */ var $mLaggedSlaveMode, $mLastError = 'Unknown error';
- /* private */ var $mParentInfo, $mLagTimes;
- /* private */ var $mLoadMonitorClass, $mLoadMonitor;
+ private $mServers, $mConns, $mLoads, $mGroupLoads;
+ private $mErrorConnection;
+ private $mReadIndex, $mAllowLagged;
+ private $mWaitForPos, $mWaitTimeout;
+ private $mLaggedSlaveMode, $mLastError = 'Unknown error';
+ private $mParentInfo, $mLagTimes;
+ private $mLoadMonitorClass, $mLoadMonitor;
/**
* @param $params Array with keys:
@@ -27,8 +27,7 @@ class LoadBalancer {
* masterWaitTimeout Replication lag wait timeout
* loadMonitor Name of a class used to fetch server lag and load.
*/
- function __construct( $params )
- {
+ function __construct( $params ) {
if ( !isset( $params['servers'] ) ) {
throw new MWException( __CLASS__.': missing servers parameter' );
}
@@ -51,8 +50,17 @@ class LoadBalancer {
$this->mLaggedSlaveMode = false;
$this->mErrorConnection = false;
$this->mAllowLagged = false;
- $this->mLoadMonitorClass = isset( $params['loadMonitor'] )
- ? $params['loadMonitor'] : 'LoadMonitor_MySQL';
+
+ if ( isset( $params['loadMonitor'] ) ) {
+ $this->mLoadMonitorClass = $params['loadMonitor'];
+ } else {
+ $master = reset( $params['servers'] );
+ if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) {
+ $this->mLoadMonitorClass = 'LoadMonitor_MySQL';
+ } else {
+ $this->mLoadMonitorClass = 'LoadMonitor_Null';
+ }
+ }
foreach( $params['servers'] as $i => $server ) {
$this->mLoads[$i] = $server['load'];
@@ -69,6 +77,8 @@ class LoadBalancer {
/**
* Get a LoadMonitor instance
+ *
+ * @return LoadMonitor
*/
function getLoadMonitor() {
if ( !isset( $this->mLoadMonitor ) ) {
@@ -88,9 +98,12 @@ class LoadBalancer {
/**
* Given an array of non-normalised probabilities, this function will select
* an element and return the appropriate key
+ *
+ * @param $weights
+ *
+ * @return int
*/
- function pickRandom( $weights )
- {
+ function pickRandom( $weights ) {
if ( !is_array( $weights ) || count( $weights ) == 0 ) {
return false;
}
@@ -116,6 +129,11 @@ class LoadBalancer {
return $i;
}
+ /**
+ * @param $loads
+ * @param $wiki bool
+ * @return bool|int|string
+ */
function getRandomNonLagged( $loads, $wiki = false ) {
# Unset excessively lagged servers
$lags = $this->getLagTimes( $wiki );
@@ -160,11 +178,14 @@ class LoadBalancer {
* always return a consistent index during a given invocation
*
* Side effect: opens connections to databases
+ * @param $group bool
+ * @param $wiki bool
+ * @return bool|int|string
*/
function getReaderIndex( $group = false, $wiki = false ) {
global $wgReadOnly, $wgDBClusterTimeout, $wgDBAvgStatusPoll, $wgDBtype;
- # FIXME: For now, only go through all this for mysql databases
+ # @todo FIXME: For now, only go through all this for mysql databases
if ($wgDBtype != 'mysql') {
return $this->getWriterIndex();
}
@@ -220,7 +241,7 @@ class LoadBalancer {
$i = $this->getRandomNonLagged( $currentLoads, $wiki );
if ( $i === false && count( $currentLoads ) != 0 ) {
# All slaves lagged. Switch to read-only mode
- $wgReadOnly = 'The database has been automatically locked ' .
+ $wgReadOnly = 'The database has been automatically locked ' .
'while the slave database servers catch up to the master';
$i = $this->pickRandom( $currentLoads );
$laggedSlaveMode = true;
@@ -229,7 +250,7 @@ class LoadBalancer {
if ( $i === false ) {
# pickRandom() returned false
- # This is permanent and means the configuration or the load monitor
+ # This is permanent and means the configuration or the load monitor
# wants us to return false.
wfDebugLog( 'connect', __METHOD__.": pickRandom() returned false\n" );
wfProfileOut( __METHOD__ );
@@ -247,7 +268,7 @@ class LoadBalancer {
}
// Perform post-connection backoff
- $threshold = isset( $this->mServers[$i]['max threads'] )
+ $threshold = isset( $this->mServers[$i]['max threads'] )
? $this->mServers[$i]['max threads'] : false;
$backoff = $this->getLoadMonitor()->postConnectionBackoff( $conn, $threshold );
@@ -256,7 +277,7 @@ class LoadBalancer {
if ( $wiki !== false ) {
$this->reuseConnection( $conn );
}
-
+
if ( $backoff ) {
# Post-connection overload, don't use this server for now
$totalThreadsConnected += $backoff;
@@ -339,7 +360,7 @@ class LoadBalancer {
}
wfProfileOut( __METHOD__ );
}
-
+
/**
* Set the master wait position and wait for ALL slaves to catch up to it
*/
@@ -347,7 +368,7 @@ class LoadBalancer {
wfProfileIn( __METHOD__ );
$this->mWaitForPos = $pos;
for ( $i = 1; $i < count( $this->mServers ); $i++ ) {
- $this->doWait( $i );
+ $this->doWait( $i , true );
}
wfProfileOut( __METHOD__ );
}
@@ -355,6 +376,8 @@ class LoadBalancer {
/**
* Get any open connection to a given server index, local or foreign
* Returns false if there is no connection open
+ *
+ * @return DatabaseBase
*/
function getAnyOpenConnection( $i ) {
foreach ( $this->mConns as $conns ) {
@@ -368,12 +391,20 @@ class LoadBalancer {
/**
* Wait for a given slave to catch up to the master pos stored in $this
*/
- function doWait( $index ) {
+ function doWait( $index, $open = false ) {
# Find a connection to wait on
$conn = $this->getAnyOpenConnection( $index );
if ( !$conn ) {
- wfDebug( __METHOD__ . ": no connection open\n" );
- return false;
+ if ( !$open ) {
+ wfDebug( __METHOD__ . ": no connection open\n" );
+ return false;
+ } else {
+ $conn = $this->openConnection( $index );
+ if ( !$conn ) {
+ wfDebug( __METHOD__ . ": failed to open connection\n" );
+ return false;
+ }
+ }
}
wfDebug( __METHOD__.": Waiting for slave #$index to catch up...\n" );
@@ -392,11 +423,11 @@ class LoadBalancer {
/**
* Get a connection by index
* This is the main entry point for this class.
- *
+ *
* @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 ) {
@@ -460,6 +491,8 @@ class LoadBalancer {
* Mark a foreign connection as being available for reuse under a different
* DB name or prefix. This mechanism is reference-counted, and must be called
* the same number of times as getConnection() to work.
+ *
+ * @param DatabaseBase $conn
*/
public function reuseConnection( $conn ) {
$serverIndex = $conn->getLBInfo('serverIndex');
@@ -506,8 +539,8 @@ class LoadBalancer {
* On error, returns false, and the connection which caused the
* error will be available via $this->mErrorConnection.
*
- * @param $i Integer: server index
- * @param $wiki String: wiki ID to open
+ * @param $i Integer server index
+ * @param $wiki String wiki ID to open
* @return DatabaseBase
*
* @access private
@@ -615,6 +648,7 @@ class LoadBalancer {
*
* @param $index Integer: server index
* @access private
+ * @return bool
*/
function isOpen( $index ) {
if( !is_integer( $index ) ) {
@@ -627,10 +661,13 @@ class LoadBalancer {
* Really opens a connection. Uncached.
* Returns a Database object whether or not the connection was successful.
* @access private
+ *
+ * @return DatabaseBase
*/
function reallyOpenConnection( $server, $dbNameOverride = false ) {
if( !is_array( $server ) ) {
- throw new MWException( 'You must update your load-balancing configuration. See DefaultSettings.php entry for $wgDBservers.' );
+ throw new MWException( 'You must update your load-balancing configuration. ' .
+ 'See DefaultSettings.php entry for $wgDBservers.' );
}
$host = $server['host'];
@@ -643,12 +680,13 @@ class LoadBalancer {
# Create object
wfDebug( "Connecting to $host $dbname...\n" );
try {
- $db = DatabaseBase::newFromType( $server['type'], $server );
+ $db = DatabaseBase::factory( $server['type'], $server );
} catch ( DBConnectionError $e ) {
- // FIXME: This is probably the ugliest thing I have ever done to
+ // 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 to $host $dbname.\n" );
} else {
@@ -669,7 +707,7 @@ class LoadBalancer {
if ( !is_object( $conn ) ) {
// No last connection, probably due to all servers being too busy
- wfLogDBError( "LB failure with no last connection\n" );
+ wfLogDBError( "LB failure with no last connection. Connection error: {$this->mLastError}\n" );
$conn = new Database;
// If all servers were busy, mLastError will contain something sensible
throw new DBConnectionError( $conn, $this->mLastError );
@@ -687,6 +725,8 @@ class LoadBalancer {
/**
* Returns true if the specified index is a valid server index
+ *
+ * @return bool
*/
function haveIndex( $i ) {
return array_key_exists( $i, $this->mServers );
@@ -694,6 +734,8 @@ class LoadBalancer {
/**
* Returns true if the specified index is valid and has non-zero load
+ *
+ * @return bool
*/
function isNonZeroLoad( $i ) {
return array_key_exists( $i, $this->mServers ) && $this->mLoads[$i] != 0;
@@ -701,6 +743,8 @@ class LoadBalancer {
/**
* Get the number of defined servers (not the number of open connections)
+ *
+ * @return int
*/
function getServerCount() {
return count( $this->mServers );
@@ -732,6 +776,13 @@ class LoadBalancer {
}
/**
+ * Sets the server info structure for the given index. Entry at index $i is created if it doesn't exist
+ */
+ function setServerInfo( $i, $serverInfo ) {
+ $this->mServers[$i] = $serverInfo;
+ }
+
+ /**
* Get the current master position for chronology control purposes
* @return mixed
*/
@@ -774,6 +825,8 @@ class LoadBalancer {
/**
* Deprecated function, typo in function name
+ *
+ * @deprecated in 1.18
*/
function closeConnecton( $conn ) {
$this->closeConnection( $conn );
@@ -783,7 +836,7 @@ class LoadBalancer {
* 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
+ * @param $conn
* @return void
*/
function closeConnection( $conn ) {
@@ -818,7 +871,9 @@ class LoadBalancer {
}
}
- /* Issue COMMIT only on master, only if queries were done on connection */
+ /**
+ * Issue COMMIT only on master, only if queries were done on connection
+ */
function commitMasterChanges() {
// Always 0, but who knows.. :)
$masterIndex = $this->getWriterIndex();
@@ -843,10 +898,11 @@ class LoadBalancer {
}
/* Disables/enables lag checks */
- function allowLagged($mode=null) {
- if ($mode===null)
+ function allowLagged( $mode = null ) {
+ if ( $mode === null) {
return $this->mAllowLagged;
- $this->mAllowLagged=$mode;
+ }
+ $this->mAllowLagged = $mode;
}
function pingAll() {
@@ -880,47 +936,84 @@ class LoadBalancer {
/**
* Get the hostname and lag time of the most-lagged slave.
* This is useful for maintenance scripts that need to throttle their updates.
- * May attempt to open connections to slaves on the default DB.
+ * May attempt to open connections to slaves on the default DB. If there is
+ * no lag, the maximum lag will be reported as -1.
+ *
* @param $wiki string Wiki ID, or false for the default database
+ *
+ * @return array ( host, max lag, index of max lagged host )
*/
function getMaxLag( $wiki = false ) {
$maxLag = -1;
$host = '';
- foreach ( $this->mServers as $i => $conn ) {
- $conn = false;
- if ( $wiki === false ) {
- $conn = $this->getAnyOpenConnection( $i );
- }
- if ( !$conn ) {
- $conn = $this->openConnection( $i, $wiki );
- }
- if ( !$conn ) {
- continue;
- }
- $lag = $conn->getLag();
- if ( $lag > $maxLag ) {
- $maxLag = $lag;
- $host = $this->mServers[$i]['host'];
+ $maxIndex = 0;
+ if ( $this->getServerCount() > 1 ) { // no replication = no lag
+ foreach ( $this->mServers as $i => $conn ) {
+ $conn = false;
+ if ( $wiki === false ) {
+ $conn = $this->getAnyOpenConnection( $i );
+ }
+ if ( !$conn ) {
+ $conn = $this->openConnection( $i, $wiki );
+ }
+ if ( !$conn ) {
+ continue;
+ }
+ $lag = $conn->getLag();
+ if ( $lag > $maxLag ) {
+ $maxLag = $lag;
+ $host = $this->mServers[$i]['host'];
+ $maxIndex = $i;
+ }
}
}
- return array( $host, $maxLag );
+ return array( $host, $maxLag, $maxIndex );
}
/**
* Get lag time for each server
* Results are cached for a short time in memcached, and indefinitely in the process cache
+ *
+ * @param $wiki
+ *
+ * @return array
*/
function getLagTimes( $wiki = false ) {
# Try process cache
if ( isset( $this->mLagTimes ) ) {
return $this->mLagTimes;
}
- # No, send the request to the load monitor
- $this->mLagTimes = $this->getLoadMonitor()->getLagTimes( array_keys( $this->mServers ), $wiki );
+ if ( $this->getServerCount() == 1 ) {
+ # No replication
+ $this->mLagTimes = array( 0 => 0 );
+ } else {
+ # Send the request to the load monitor
+ $this->mLagTimes = $this->getLoadMonitor()->getLagTimes(
+ array_keys( $this->mServers ), $wiki );
+ }
return $this->mLagTimes;
}
/**
+ * Get the lag in seconds for a given connection, or zero if this load
+ * balancer does not have replication enabled.
+ *
+ * This should be used in preference to Database::getLag() in cases where
+ * replication may not be in use, since there is no way to determine if
+ * replication is in use at the connection level without running
+ * potentially restricted queries such as SHOW SLAVE STATUS. Using this
+ * function instead of Database::getLag() avoids a fatal error in this
+ * case on many installations.
+ */
+ function safeGetLag( $conn ) {
+ if ( $this->getServerCount() == 1 ) {
+ return 0;
+ } else {
+ return $conn->getLag();
+ }
+ }
+
+ /**
* Clear the cache for getLagTimes
*/
function clearLagTimeCache() {
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index 9b959728..a6370c9e 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -14,12 +14,14 @@
interface LoadMonitor {
/**
* Construct a new LoadMonitor with a given LoadBalancer parent
+ *
+ * @param LoadBalancer $parent
*/
function __construct( $parent );
-
+
/**
* Perform pre-connection load ratio adjustment.
- * @param $loads Array
+ * @param $loads array
* @param $group String: the selected query group
* @param $wiki String
*/
@@ -28,13 +30,13 @@ interface LoadMonitor {
/**
* Perform post-connection backoff.
*
- * If the connection is in overload, this should return a backoff factor
- * which will be used to control polling time. The number of threads
+ * If the connection is in overload, this should return a backoff factor
+ * which will be used to control polling time. The number of threads
* connected is a good measure.
*
* If there is no overload, zero can be returned.
*
- * A threshold thread count is given, the concrete class may compare this
+ * A threshold thread count is given, the concrete class may compare this
* to the running thread count. The threshold may be false, which indicates
* that the sysadmin has not configured this feature.
*
@@ -45,10 +47,30 @@ interface LoadMonitor {
/**
* Return an estimate of replication lag for each server
+ *
+ * @param $serverIndexes
+ * @param $wiki
+ *
+ * @return array
*/
function getLagTimes( $serverIndexes, $wiki );
}
+class LoadMonitor_Null implements LoadMonitor {
+ function __construct( $parent ) {
+ }
+
+ function scaleLoads( &$loads, $group = false, $wiki = false ) {
+ }
+
+ function postConnectionBackoff( $conn, $threshold ) {
+ }
+
+ function getLagTimes( $serverIndexes, $wiki ) {
+ return array_fill_keys( $serverIndexes, 0 );
+ }
+}
+
/**
* Basic MySQL load monitor with no external dependencies
@@ -57,16 +79,38 @@ interface LoadMonitor {
* @ingroup Database
*/
class LoadMonitor_MySQL implements LoadMonitor {
- var $parent; // LoadBalancer
+ /**
+ * @var LoadBalancer
+ */
+ var $parent;
+
+ /**
+ * @param LoadBalancer $parent
+ */
function __construct( $parent ) {
$this->parent = $parent;
}
+ /**
+ * @param $loads
+ * @param $group bool
+ * @param $wiki bool
+ */
function scaleLoads( &$loads, $group = false, $wiki = false ) {
}
+ /**
+ * @param $serverIndexes
+ * @param $wiki
+ * @return array
+ */
function getLagTimes( $serverIndexes, $wiki ) {
+ if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
+ // Single server only, just return zero without caching
+ return array( 0 => 0 );
+ }
+
wfProfileIn( __METHOD__ );
$expiry = 5;
$requestRate = 10;
@@ -74,7 +118,7 @@ class LoadMonitor_MySQL implements LoadMonitor {
global $wgMemc;
if ( empty( $wgMemc ) )
$wgMemc = wfGetMainCache();
-
+
$masterName = $this->parent->getServerName( 0 );
$memcKey = wfMemcKey( 'lag_times', $masterName );
$times = $wgMemc->get( $memcKey );
@@ -117,12 +161,19 @@ class LoadMonitor_MySQL implements LoadMonitor {
return $lagTimes;
}
+ /**
+ * @param $conn DatabaseBase
+ * @param $threshold
+ * @return int
+ */
function postConnectionBackoff( $conn, $threshold ) {
if ( !$threshold ) {
return 0;
}
- $status = $conn->getStatus("Thread%");
+ $status = $conn->getMysqlStatus("Thread%");
if ( $status['Threads_running'] > $threshold ) {
+ $server = $conn->getProperty( 'mServer' );
+ wfLogDBError( "LB backoff from $server - Threads_running = {$status['Threads_running']}\n" );
return $status['Threads_connected'];
} else {
return 0;
diff --git a/includes/diff/WikiDiff.php b/includes/diff/DairikiDiff.php
index 2d904c96..8f19712b 100644
--- a/includes/diff/WikiDiff.php
+++ b/includes/diff/DairikiDiff.php
@@ -41,9 +41,10 @@ class _DiffOp {
class _DiffOp_Copy extends _DiffOp {
var $type = 'copy';
- function __construct ( $orig, $closing = false ) {
- if ( !is_array( $closing ) )
- $closing = $orig;
+ function __construct( $orig, $closing = false ) {
+ if ( !is_array( $closing ) ) {
+ $closing = $orig;
+ }
$this->orig = $orig;
$this->closing = $closing;
}
@@ -61,7 +62,7 @@ class _DiffOp_Copy extends _DiffOp {
class _DiffOp_Delete extends _DiffOp {
var $type = 'delete';
- function __construct ( $lines ) {
+ function __construct( $lines ) {
$this->orig = $lines;
$this->closing = false;
}
@@ -79,7 +80,7 @@ class _DiffOp_Delete extends _DiffOp {
class _DiffOp_Add extends _DiffOp {
var $type = 'add';
- function __construct ( $lines ) {
+ function __construct( $lines ) {
$this->closing = $lines;
$this->orig = false;
}
@@ -97,7 +98,7 @@ class _DiffOp_Add extends _DiffOp {
class _DiffOp_Change extends _DiffOp {
var $type = 'change';
- function __construct ( $orig, $closing ) {
+ function __construct( $orig, $closing ) {
$this->orig = $orig;
$this->closing = $closing;
}
@@ -135,6 +136,15 @@ class _DiffEngine {
const MAX_XREF_LENGTH = 10000;
+ protected $xchanged, $ychanged;
+
+ protected $xv = array(), $yv = array();
+ protected $xind = array(), $yind = array();
+
+ protected $seq = array(), $in_seq = array();
+
+ protected $lcs = 0;
+
function diff ( $from_lines, $to_lines ) {
wfProfileIn( __METHOD__ );
@@ -162,24 +172,28 @@ class _DiffEngine {
$copy[] = $from_lines[$xi++];
++$yi;
}
- if ( $copy )
- $edits[] = new _DiffOp_Copy( $copy );
+ if ( $copy ) {
+ $edits[] = new _DiffOp_Copy( $copy );
+ }
// Find deletes & adds.
$delete = array();
- while ( $xi < $n_from && $this->xchanged[$xi] )
- $delete[] = $from_lines[$xi++];
+ 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 );
+ 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;
@@ -209,15 +223,17 @@ class _DiffEngine {
// Skip leading common lines.
for ( $skip = 0; $skip < $n_from && $skip < $n_to; $skip++ ) {
- if ( $from_lines[$skip] !== $to_lines[$skip] )
- break;
+ 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;
+ if ( $from_lines[$xi] !== $to_lines[$yi] ) {
+ break;
+ }
$this->xchanged[$xi] = $this->ychanged[$yi] = false;
}
@@ -228,16 +244,18 @@ class _DiffEngine {
for ( $yi = $skip; $yi < $n_to - $endskip; $yi++ ) {
$line = $to_lines[$yi];
- if ( ( $this->ychanged[$yi] = empty( $xhash[$this->_line_hash( $line )] ) ) )
- continue;
+ 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;
+ if ( ( $this->xchanged[$xi] = empty( $yhash[$this->_line_hash( $line )] ) ) ) {
+ continue;
+ }
$this->xv[] = $line;
$this->xind[] = $xi;
}
@@ -259,7 +277,8 @@ class _DiffEngine {
}
}
- /* Divide the Largest Common Subsequence (LCS) of the sequences
+ /**
+ * Divide the Largest Common Subsequence (LCS) of the sequences
* [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally
* sized segments.
*
@@ -275,23 +294,25 @@ class _DiffEngine {
* 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 ) {
+ 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 );
+ 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;
+ 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;
@@ -301,9 +322,11 @@ class _DiffEngine {
$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];
+ 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++ ) {
@@ -329,7 +352,7 @@ class _DiffEngine {
$this->in_seq[$this->seq[$k]] = false;
$this->seq[$k] = $y;
$this->in_seq[$y] = 1;
- } else if ( empty( $this->in_seq[$y] ) ) {
+ } elseif ( empty( $this->in_seq[$y] ) ) {
$k = $this->_lcs_pos( $y );
assert( $k > 0 );
$ymids[$k] = $ymids[$k -1];
@@ -350,7 +373,7 @@ class _DiffEngine {
return array( $this->lcs, $seps );
}
- function _lcs_pos ( $ypos ) {
+ function _lcs_pos( $ypos ) {
$end = $this->lcs;
if ( $end == 0 || $ypos > $this->seq[$end] ) {
$this->seq[++$this->lcs] = $ypos;
@@ -361,10 +384,11 @@ class _DiffEngine {
$beg = 1;
while ( $beg < $end ) {
$mid = (int)( ( $beg + $end ) / 2 );
- if ( $ypos > $this->seq[$mid] )
- $beg = $mid + 1;
- else
- $end = $mid;
+ if ( $ypos > $this->seq[$mid] ) {
+ $beg = $mid + 1;
+ } else {
+ $end = $mid;
+ }
}
assert( $ypos != $this->seq[$end] );
@@ -375,7 +399,8 @@ class _DiffEngine {
return $end;
}
- /* Find LCS of two sequences.
+ /**
+ * 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
@@ -401,9 +426,9 @@ class _DiffEngine {
--$ylim;
}
- if ( $xoff == $xlim || $yoff == $ylim )
- $lcs = 0;
- else {
+ 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));
@@ -415,10 +440,12 @@ class _DiffEngine {
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;
+ 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 );
@@ -430,7 +457,8 @@ class _DiffEngine {
}
}
- /* Adjust inserts/deletes of identical lines to join changes
+ /**
+ * Adjust inserts/deletes of identical lines to join changes
* as much as possible.
*
* We do something when a run of changed lines include a
@@ -442,7 +470,7 @@ class _DiffEngine {
*
* This is extracted verbatim from analyze.c (GNU diffutils-2.7).
*/
- function _shift_boundaries ( $lines, &$changed, $other_changed ) {
+ function _shift_boundaries( $lines, &$changed, $other_changed ) {
wfProfileIn( __METHOD__ );
$i = 0;
$j = 0;
@@ -463,8 +491,9 @@ class _DiffEngine {
* Furthermore, $j is always kept so that $j == $other_len or
* $other_changed[$j] == false.
*/
- while ( $j < $other_len && $other_changed[$j] )
- $j++;
+ while ( $j < $other_len && $other_changed[$j] ) {
+ $j++;
+ }
while ( $i < $len && ! $changed[$i] ) {
assert( '$j < $other_len && ! $other_changed[$j]' );
@@ -473,14 +502,16 @@ class _DiffEngine {
$j++;
}
- if ( $i == $len )
- break;
+ if ( $i == $len ) {
+ break;
+ }
$start = $i;
// Find the end of this run of changes.
- while ( ++$i < $len && $changed[$i] )
- continue;
+ while ( ++$i < $len && $changed[$i] ) {
+ continue;
+ }
do {
/*
@@ -497,11 +528,13 @@ class _DiffEngine {
while ( $start > 0 && $lines[$start - 1] == $lines[$i - 1] ) {
$changed[--$start] = 1;
$changed[--$i] = false;
- while ( $start > 0 && $changed[$start - 1] )
- $start--;
+ while ( $start > 0 && $changed[$start - 1] ) {
+ $start--;
+ }
assert( '$j > 0' );
- while ( $other_changed[--$j] )
- continue;
+ while ( $other_changed[--$j] ) {
+ continue;
+ }
assert( '$j >= 0 && !$other_changed[$j]' );
}
@@ -522,15 +555,17 @@ class _DiffEngine {
while ( $i < $len && $lines[$start] == $lines[$i] ) {
$changed[$start++] = false;
$changed[$i++] = 1;
- while ( $i < $len && $changed[$i] )
- $i++;
+ 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 ( $j < $other_len && $other_changed[$j] ) {
+ $j++;
+ }
}
}
} while ( $runlength != $i - $start );
@@ -543,8 +578,9 @@ class _DiffEngine {
$changed[--$start] = 1;
$changed[--$i] = 0;
assert( '$j > 0' );
- while ( $other_changed[--$j] )
- continue;
+ while ( $other_changed[--$j] ) {
+ continue;
+ }
assert( '$j >= 0 && !$other_changed[$j]' );
}
}
@@ -558,8 +594,7 @@ class _DiffEngine {
* @private
* @ingroup DifferenceEngine
*/
-class Diff
-{
+class Diff {
var $edits;
/**
@@ -586,7 +621,7 @@ class Diff
* @return object A Diff object representing the inverse of the
* original diff.
*/
- function reverse () {
+ function reverse() {
$rev = $this;
$rev->edits = array();
foreach ( $this->edits as $edit ) {
@@ -600,10 +635,11 @@ class Diff
*
* @return bool True iff two sequences were identical.
*/
- function isEmpty () {
+ function isEmpty() {
foreach ( $this->edits as $edit ) {
- if ( $edit->type != 'copy' )
- return false;
+ if ( $edit->type != 'copy' ) {
+ return false;
+ }
}
return true;
}
@@ -615,11 +651,12 @@ class Diff
*
* @return int The length of the LCS.
*/
- function lcs () {
+ function lcs() {
$lcs = 0;
foreach ( $this->edits as $edit ) {
- if ( $edit->type == 'copy' )
- $lcs += sizeof( $edit->orig );
+ if ( $edit->type == 'copy' ) {
+ $lcs += sizeof( $edit->orig );
+ }
}
return $lcs;
}
@@ -636,8 +673,9 @@ class Diff
$lines = array();
foreach ( $this->edits as $edit ) {
- if ( $edit->orig )
- array_splice( $lines, sizeof( $lines ), 0, $edit->orig );
+ if ( $edit->orig ) {
+ array_splice( $lines, sizeof( $lines ), 0, $edit->orig );
+ }
}
return $lines;
}
@@ -654,8 +692,9 @@ class Diff
$lines = array();
foreach ( $this->edits as $edit ) {
- if ( $edit->closing )
- array_splice( $lines, sizeof( $lines ), 0, $edit->closing );
+ if ( $edit->closing ) {
+ array_splice( $lines, sizeof( $lines ), 0, $edit->closing );
+ }
}
return $lines;
}
@@ -665,24 +704,29 @@ class Diff
*
* This is here only for debugging purposes.
*/
- function _check ( $from_lines, $to_lines ) {
+ function _check( $from_lines, $to_lines ) {
wfProfileIn( __METHOD__ );
- if ( serialize( $from_lines ) != serialize( $this->orig() ) )
- trigger_error( "Reconstructed original doesn't match", E_USER_ERROR );
- if ( serialize( $to_lines ) != serialize( $this->closing() ) )
- trigger_error( "Reconstructed closing doesn't match", E_USER_ERROR );
+ 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 );
+ 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 );
+ if ( $prevtype == $edit->type ) {
+ trigger_error( 'Edit sequence is non-optimal', E_USER_ERROR );
+ }
$prevtype = $edit->type;
}
@@ -697,8 +741,7 @@ class Diff
* @private
* @ingroup DifferenceEngine
*/
-class MappedDiff extends Diff
-{
+class MappedDiff extends Diff {
/**
* Constructor.
*
@@ -723,7 +766,7 @@ class MappedDiff extends Diff
* have the same number of elements as $to_lines.
*/
function __construct( $from_lines, $to_lines,
- $mapped_from_lines, $mapped_to_lines ) {
+ $mapped_from_lines, $mapped_to_lines ) {
wfProfileIn( __METHOD__ );
assert( sizeof( $from_lines ) == sizeof( $mapped_from_lines ) );
@@ -779,7 +822,7 @@ class DiffFormatter {
/**
* Format a diff.
*
- * @param $diff object A Diff object.
+ * @param $diff Diff A Diff object.
* @return string The formatted output.
*/
function format( $diff ) {
@@ -799,42 +842,44 @@ class DiffFormatter {
if ( is_array( $block ) ) {
if ( sizeof( $edit->orig ) <= $nlead + $ntrail ) {
$block[] = $edit;
- }
- else {
+ } 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 );
+ $y0, $ntrail + $yi - $y0,
+ $block );
$block = false;
}
}
$context = $edit->orig;
- }
- else {
- if ( ! is_array( $block ) ) {
+ } 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 );
+ if ( $context ) {
+ $block[] = new _DiffOp_Copy( $context );
+ }
}
$block[] = $edit;
}
- if ( $edit->orig )
- $xi += sizeof( $edit->orig );
- if ( $edit->closing )
- $yi += sizeof( $edit->closing );
+ 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 );
+ if ( is_array( $block ) ) {
+ $this->_block( $x0, $xi - $x0,
+ $y0, $yi - $y0,
+ $block );
+ }
$end = $this->_end_diff();
wfProfileOut( __METHOD__ );
@@ -845,16 +890,17 @@ class DiffFormatter {
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 );
+ 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__ );
@@ -871,10 +917,12 @@ class DiffFormatter {
}
function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
- if ( $xlen > 1 )
- $xbeg .= "," . ( $xbeg + $xlen - 1 );
- if ( $ylen > 1 )
- $ybeg .= "," . ( $ybeg + $ylen - 1 );
+ if ( $xlen > 1 ) {
+ $xbeg .= ',' . ( $xbeg + $xlen - 1 );
+ }
+ if ( $ylen > 1 ) {
+ $ybeg .= ',' . ( $ybeg + $ylen - 1 );
+ }
return $xbeg . ( $xlen ? ( $ylen ? 'c' : 'd' ) : 'a' ) . $ybeg;
}
@@ -887,8 +935,9 @@ class DiffFormatter {
}
function _lines( $lines, $prefix = ' ' ) {
- foreach ( $lines as $line )
- echo "$prefix $line\n";
+ foreach ( $lines as $line ) {
+ echo "$prefix $line\n";
+ }
}
function _context( $lines ) {
@@ -913,7 +962,6 @@ class DiffFormatter {
* A formatter that outputs unified diffs
* @ingroup DifferenceEngine
*/
-
class UnifiedDiffFormatter extends DiffFormatter {
var $leading_context_lines = 2;
var $trailing_context_lines = 2;
@@ -942,48 +990,48 @@ class ArrayDiffFormatter extends DiffFormatter {
$oldline = 1;
$newline = 1;
$retval = array();
- foreach ( $diff->edits as $edit )
- switch( $edit->type ) {
- case 'add':
- foreach ( $edit->closing as $l ) {
- $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(
+ );
+ }
+ 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(
+ );
+ }
+ break;
+ case 'change':
+ foreach ( $edit->orig as $i => $l ) {
+ $retval[] = array(
'action' => 'change',
'old' => $l,
- 'new' => @$edit->closing[$i],
+ 'new' => isset( $edit->closing[$i] ) ? $edit->closing[$i] : null,
'oldline' => $oldline++,
'newline' => $newline++,
- );
- }
- break;
- case 'copy':
- $oldline += count( $edit->orig );
- $newline += count( $edit->orig );
+ );
+ }
+ 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
- *
+ * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3
*/
define( 'NBSP', '&#160;' ); // iso-8859-x non-breaking space.
@@ -994,46 +1042,50 @@ define( 'NBSP', '&#160;' ); // iso-8859-x non-breaking space.
* @ingroup DifferenceEngine
*/
class _HWLDF_WordAccumulator {
- function __construct () {
+ function __construct() {
$this->_lines = array();
$this->_line = '';
$this->_group = '';
$this->_tag = '';
}
- function _flushGroup ( $new_tag ) {
+ function _flushGroup( $new_tag ) {
if ( $this->_group !== '' ) {
- if ( $this->_tag == 'ins' )
- $this->_line .= '<ins class="diffchange diffchange-inline">' .
- htmlspecialchars ( $this->_group ) . '</ins>';
- elseif ( $this->_tag == 'del' )
- $this->_line .= '<del class="diffchange diffchange-inline">' .
- htmlspecialchars ( $this->_group ) . '</del>';
- else
- $this->_line .= htmlspecialchars ( $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 ) {
+ 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 );
+ 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 );
+ 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 == '' ) {
+ continue;
+ }
if ( $word[0] == "\n" ) {
$this->_flushLine( $tag );
$word = substr( $word, 1 );
@@ -1060,8 +1112,8 @@ class WordLevelDiff extends MappedDiff {
function __construct ( $orig_lines, $closing_lines ) {
wfProfileIn( __METHOD__ );
- list ( $orig_words, $orig_stripped ) = $this->_split( $orig_lines );
- list ( $closing_words, $closing_stripped ) = $this->_split( $closing_lines );
+ list( $orig_words, $orig_stripped ) = $this->_split( $orig_lines );
+ list( $closing_words, $closing_stripped ) = $this->_split( $closing_lines );
parent::__construct( $orig_words, $closing_words,
$orig_stripped, $closing_stripped );
@@ -1089,7 +1141,7 @@ class WordLevelDiff extends MappedDiff {
} else {
$m = array();
if ( preg_match_all( '/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs',
- $line, $m ) )
+ $line, $m ) )
{
$words = array_merge( $words, $m[0] );
$stripped = array_merge( $stripped, $m[1] );
@@ -1100,30 +1152,32 @@ class WordLevelDiff extends MappedDiff {
return array( $words, $stripped );
}
- function orig () {
+ 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' );
+ 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 () {
+ 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' );
+ if ( $edit->type == 'copy' ) {
+ $closing->addWords( $edit->closing );
+ } elseif ( $edit->closing ) {
+ $closing->addWords( $edit->closing, 'ins' );
+ }
}
$lines = $closing->getLines();
wfProfileOut( __METHOD__ );
@@ -1173,7 +1227,7 @@ class TableDiffFormatter extends DiffFormatter {
# HTML-escape parameter before calling this
function deletedLine( $line ) {
- return $this->wrapLine( '&minus;', 'diff-deletedline', $line );
+ return $this->wrapLine( '−', 'diff-deletedline', $line );
}
# HTML-escape parameter before calling this
@@ -1197,14 +1251,14 @@ class TableDiffFormatter extends DiffFormatter {
foreach ( $lines as $line ) {
echo '<tr>' . $this->emptyLine() .
$this->addedLine( '<ins class="diffchange">' .
- htmlspecialchars ( $line ) . '</ins>' ) . "</tr>\n";
+ htmlspecialchars( $line ) . '</ins>' ) . "</tr>\n";
}
}
function _deleted( $lines ) {
foreach ( $lines as $line ) {
echo '<tr>' . $this->deletedLine( '<del class="diffchange">' .
- htmlspecialchars ( $line ) . '</del>' ) .
+ htmlspecialchars( $line ) . '</del>' ) .
$this->emptyLine() . "</tr>\n";
}
}
@@ -1212,8 +1266,8 @@ class TableDiffFormatter extends DiffFormatter {
function _context( $lines ) {
foreach ( $lines as $line ) {
echo '<tr>' .
- $this->contextLine( htmlspecialchars ( $line ) ) .
- $this->contextLine( htmlspecialchars ( $line ) ) . "</tr>\n";
+ $this->contextLine( htmlspecialchars( $line ) ) .
+ $this->contextLine( htmlspecialchars( $line ) ) . "</tr>\n";
}
}
diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php
index edf35a92..5902461b 100644
--- a/includes/diff/DifferenceEngine.php
+++ b/includes/diff/DifferenceEngine.php
@@ -22,11 +22,19 @@ class DifferenceEngine {
/**#@+
* @private
*/
- var $mOldid, $mNewid, $mTitle;
+ var $mOldid, $mNewid;
var $mOldtitle, $mNewtitle, $mPagetitle;
var $mOldtext, $mNewtext;
- var $mOldPage, $mNewPage;
+
+ /**
+ * @var Title
+ */
+ var $mOldPage, $mNewPage, $mTitle;
var $mRcidMarkPatrolled;
+
+ /**
+ * @var Revision
+ */
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?
@@ -49,9 +57,9 @@ class DifferenceEngine {
/**
* 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 $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
*/
@@ -62,7 +70,7 @@ class DifferenceEngine {
$this->mTitle = $titleObj;
} else {
global $wgTitle;
- $this->mTitle = $wgTitle;
+ $this->mTitle = $wgTitle; // @TODO: get rid of this
}
wfDebug( "DifferenceEngine old '$old' new '$new' rcid '$rcid'\n" );
@@ -91,26 +99,82 @@ class DifferenceEngine {
$this->unhide = $unhide;
}
+ /**
+ * @param $value bool
+ */
function setReducedLineNumbers( $value = true ) {
$this->mReducedLineNumbers = $value;
}
+ /**
+ * @return Title
+ */
function getTitle() {
return $this->mTitle;
}
+ /**
+ * @return bool
+ */
function wasCacheHit() {
return $this->mCacheHit;
}
+ /**
+ * @return int
+ */
function getOldid() {
return $this->mOldid;
}
+ /**
+ * @return Bool|int
+ */
function getNewid() {
return $this->mNewid;
}
+ /**
+ * Look up a special:Undelete link to the given deleted revision id,
+ * as a workaround for being unable to load deleted diffs in currently.
+ *
+ * @param int $id revision ID
+ * @return mixed URL or false
+ */
+ function deletedLink( $id ) {
+ global $wgUser;
+ if ( $wgUser->isAllowed( 'deletedhistory' ) ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $row = $dbr->selectRow('archive', '*',
+ array( 'ar_rev_id' => $id ),
+ __METHOD__ );
+ if ( $row ) {
+ $rev = Revision::newFromArchiveRow( $row );
+ $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
+ return SpecialPage::getTitleFor( 'Undelete' )->getFullURL( array(
+ 'target' => $title->getPrefixedText(),
+ 'timestamp' => $rev->getTimestamp()
+ ));
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Build a wikitext link toward a deleted revision, if viewable.
+ *
+ * @param int $id revision ID
+ * @return string wikitext fragment
+ */
+ function deletedIdMarker( $id ) {
+ $link = $this->deletedLink( $id );
+ if ( $link ) {
+ return "[$link $id]";
+ } else {
+ return $id;
+ }
+ }
+
function showDiffPage( $diffOnly = false ) {
global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
wfProfileIn( __METHOD__ );
@@ -122,14 +186,14 @@ class DifferenceEngine {
# 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;
+ global $wgCanonicalServer, $wgScript, $wgLang;
$wgOut->disable();
- header ( "Content-type: application/x-external-editor; charset=" . $wgInputEncoding );
- $url1 = $this->mTitle->getFullURL( array(
+ header ( "Content-type: application/x-external-editor; charset=UTF-8" );
+ $url1 = $this->mTitle->getCanonical( array(
'action' => 'raw',
'oldid' => $this->mOldid
) );
- $url2 = $this->mTitle->getFullURL( array(
+ $url2 = $this->mTitle->getCanonical( array(
'action' => 'raw',
'oldid' => $this->mNewid
) );
@@ -138,7 +202,7 @@ class DifferenceEngine {
[Process]
Type=Diff text
Engine=MediaWiki
- Script={$wgServer}{$wgScript}
+ Script={$wgCanonicalServer}{$wgScript}
Special namespace={$special}
[File]
@@ -157,10 +221,13 @@ CONTROL;
$wgOut->setArticleFlag( false );
if ( !$this->loadRevisionData() ) {
+ // Sounds like a deleted revision... Let's see what we can do.
$t = $this->mTitle->getPrefixedText();
- $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ), $this->mOldid, $this->mNewid );
+ $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ),
+ $this->deletedIdMarker( $this->mOldid ),
+ $this->deletedIdMarker( $this->mNewid ) );
$wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
- $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", $d );
+ $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", "<span class='plainlinks'>$d</span>" );
wfProfileOut( __METHOD__ );
return;
}
@@ -181,8 +248,6 @@ CONTROL;
return;
}
- $wgOut->suppressQuickbar();
-
$oldTitle = $this->mOldPage->getPrefixedText();
$newTitle = $this->mNewPage->getPrefixedText();
if ( $oldTitle == $newTitle ) {
@@ -206,6 +271,9 @@ CONTROL;
}
$sk = $wgUser->getSkin();
+ if ( method_exists( $sk, 'suppressQuickbar' ) ) {
+ $sk->suppressQuickbar();
+ }
// Check if page is editable
$editable = $this->mNewRev->getTitle()->userCan( 'edit' );
@@ -372,7 +440,7 @@ CONTROL;
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",
+ $wgOut->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
array( $msg ) );
} else {
# Give explanation and add a link to view the diff...
@@ -382,7 +450,7 @@ CONTROL;
'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 ) );
+ $wgOut->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n", array( $msg, $link ) );
}
# Otherwise, output a regular diff...
} else {
@@ -390,7 +458,7 @@ CONTROL;
$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";
+ $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" . wfMsgExt( $msg, 'parseinline' ) . "</div>\n";
}
$this->showDiff( $oldHeader, $newHeader, $notice );
if ( !$diffOnly ) {
@@ -400,6 +468,10 @@ CONTROL;
wfProfileOut( __METHOD__ );
}
+ /**
+ * @param $rev Revision
+ * @return String
+ */
protected function revisionDeleteLink( $rev ) {
global $wgUser;
$link = '';
@@ -431,42 +503,37 @@ CONTROL;
function renderNewRevision() {
global $wgOut, $wgUser;
wfProfileIn( __METHOD__ );
+ # Add "current version as of X" title
+ $wgOut->addHTML( "<hr class='diff-hr' />
+ <h2 class='diff-currentversion-title'>{$this->mPagetitle}</h2>\n" );
+ # Page content may be handled by a hooked call instead...
+ if ( wfRunHooks( 'ArticleContentOnDiff', array( $this, $wgOut ) ) ) {
+ # Use the current version parser cache if applicable
+ $pCache = true;
+ if ( !$this->mNewRev->isCurrent() ) {
+ $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
+ $pCache = false;
+ }
- $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' );
- }
-
- $pCache = true;
- if ( !$this->mNewRev->isCurrent() ) {
- $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false );
- $pCache = false;
- }
-
- $this->loadNewText();
- if ( is_object( $this->mNewRev ) ) {
+ $this->loadNewText();
$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" );
- }
- } elseif ( wfRunHooks( 'ArticleContentOnDiff', array( $this, $wgOut ) ) ) {
- if ( $pCache ) {
+ if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
+ // Stolen from Article::view --AG 2007-10-11
+ // Give hooks a chance to customise the output
+ // @TODO: standardize this crap into one function
+ 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 ( $pCache ) {
$article = new Article( $this->mTitle, 0 );
$pOutput = ParserCache::singleton()->get( $article, $wgOut->parserOptions() );
- if ( $pOutput ) {
+ if( $pOutput ) {
$wgOut->addParserOutput( $pOutput );
} else {
$article->doViewParse();
@@ -474,12 +541,11 @@ CONTROL;
} else {
$wgOut->addWikiTextTidy( $this->mNewtext );
}
- }
- if ( is_object( $this->mNewRev ) && !$this->mNewRev->isCurrent() ) {
- $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
+ if ( !$this->mNewRev->isCurrent() ) {
+ $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
+ }
}
-
# Add redundant patrol link on bottom...
if ( $this->mRcidMarkPatrolled && $this->mTitle->quickUserCan( 'patrol' ) ) {
$sk = $wgUser->getSkin();
@@ -491,9 +557,9 @@ CONTROL;
wfMsgHtml( 'markaspatrolleddiff' ),
array(),
array(
- 'action' => 'markpatrolled',
- 'rcid' => $this->mRcidMarkPatrolled,
- 'token' => $token,
+ 'action' => 'markpatrolled',
+ 'rcid' => $this->mRcidMarkPatrolled,
+ 'token' => $token,
)
) . ']</div>'
);
@@ -514,9 +580,11 @@ CONTROL;
#
if ( ! $this->loadNewText() ) {
$t = $this->mTitle->getPrefixedText();
- $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ), $this->mOldid, $this->mNewid );
+ $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ),
+ $this->deletedIdMarker( $this->mOldid ),
+ $this->deletedIdMarker( $this->mNewid ) );
$wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
- $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", $d );
+ $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", "<span class='plainlinks'>$d</span>" );
wfProfileOut( __METHOD__ );
return;
}
@@ -571,6 +639,8 @@ CONTROL;
/**
* Get the diff text, send it to $wgOut
* Returns false if the diff could not be generated, otherwise returns true
+ *
+ * @return bool
*/
function showDiff( $otitle, $ntitle, $notice = '' ) {
global $wgOut;
@@ -590,8 +660,7 @@ CONTROL;
*/
function showDiffStyle() {
global $wgOut;
- $wgOut->addModuleStyles( 'mediawiki.legacy.diff' );
- $wgOut->addModuleScripts( 'mediawiki.legacy.diff' );
+ $wgOut->addModuleStyles( 'mediawiki.action.history.diff' );
}
/**
@@ -691,16 +760,12 @@ CONTROL;
global $wgExternalDiffEngine;
if ( $wgExternalDiffEngine == 'wikidiff' && !function_exists( 'wikidiff_do_diff' ) ) {
wfProfileIn( __METHOD__ . '-php_wikidiff.so' );
- wfSuppressWarnings();
- dl( 'php_wikidiff.so' );
- wfRestoreWarnings();
+ wfDl( 'php_wikidiff' );
wfProfileOut( __METHOD__ . '-php_wikidiff.so' );
}
- else if ( $wgExternalDiffEngine == 'wikidiff2' && !function_exists( 'wikidiff2_do_diff' ) ) {
+ elseif ( $wgExternalDiffEngine == 'wikidiff2' && !function_exists( 'wikidiff2_do_diff' ) ) {
wfProfileIn( __METHOD__ . '-php_wikidiff2.so' );
- wfSuppressWarnings();
wfDl( 'wikidiff2' );
- wfRestoreWarnings();
wfProfileOut( __METHOD__ . '-php_wikidiff2.so' );
}
}
@@ -714,6 +779,8 @@ CONTROL;
function generateDiffBody( $otext, $ntext ) {
global $wgExternalDiffEngine, $wgContLang;
+ wfProfileIn( __METHOD__ );
+
$otext = str_replace( "\r\n", "\n", $otext );
$ntext = str_replace( "\r\n", "\n", $ntext );
@@ -724,6 +791,7 @@ CONTROL;
# input text to be HTML-escaped already
$otext = htmlspecialchars ( $wgContLang->segmentForDiff( $otext ) );
$ntext = htmlspecialchars ( $wgContLang->segmentForDiff( $ntext ) );
+ wfProfileOut( __METHOD__ );
return $wgContLang->unsegmentForDiff( wikidiff_do_diff( $otext, $ntext, 2 ) ) .
$this->debug( 'wikidiff1' );
}
@@ -735,6 +803,7 @@ CONTROL;
$text = wikidiff2_do_diff( $otext, $ntext, 2 );
$text .= $this->debug( 'wikidiff2' );
wfProfileOut( 'wikidiff2_do_diff' );
+ wfProfileOut( __METHOD__ );
return $text;
}
if ( $wgExternalDiffEngine != 'wikidiff3' && $wgExternalDiffEngine !== false ) {
@@ -776,7 +845,6 @@ CONTROL;
$difftext = $wgContLang->unsegmentForDiff( $formatter->format( $diffs ) ) .
wfProfileOut( __METHOD__ );
return $difftext;
- $this->debug();
}
/**
@@ -828,18 +896,18 @@ CONTROL;
return '';
}
- $oldid = $this->mOldRev->getId();
- $newid = $this->mNewRev->getId();
- if ( $oldid > $newid ) {
- $tmp = $oldid; $oldid = $newid; $newid = $tmp;
+ if ( $this->mOldRev->getTimestamp() > $this->mNewRev->getTimestamp() ) {
+ $oldRev = $this->mNewRev; // flip
+ $newRev = $this->mOldRev; // flip
+ } else { // normal case
+ $oldRev = $this->mOldRev;
+ $newRev = $this->mNewRev;
}
- $nEdits = $this->mTitle->countRevisionsBetween( $oldid, $newid );
+ $nEdits = $this->mTitle->countRevisionsBetween( $oldRev, $newRev );
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 );
+ $limit = 100; // use diff-multi-manyusers if too many users
+ $numUsers = $this->mTitle->countAuthorsBetween( $oldRev, $newRev, $limit );
return self::intermediateEditsMsg( $nEdits, $numUsers, $limit );
}
return ''; // nothing
@@ -866,9 +934,15 @@ CONTROL;
/**
* Add the header to a diff body
+ *
+ * @return string
*/
- static function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
- $header = "<table class='diff'>";
+ function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
+ // shared.css sets diff in interface language/dir,
+ // but the actual content should be in the page language/dir
+ $pageLang = $this->mTitle->getPageLanguage();
+ $tableClass = 'diff diff-contentalign-' . htmlspecialchars( $pageLang->alignStart() );
+ $header = "<table class='$tableClass'>";
if ( $diff ) { // Safari/Chrome show broken output if cols not used
$header .= "
<col class='diff-marker' />
@@ -916,6 +990,8 @@ CONTROL;
* 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.
+ *
+ * @return bool
*/
function loadRevisionData() {
global $wgLang, $wgUser;
@@ -930,8 +1006,9 @@ CONTROL;
$this->mNewRev = $this->mNewid
? Revision::newFromId( $this->mNewid )
: Revision::newFromTitle( $this->mTitle );
- if ( !$this->mNewRev instanceof Revision )
+ 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();
@@ -980,7 +1057,7 @@ CONTROL;
}
if ( !$this->mNewRev->userCan( Revision::DELETED_TEXT ) ) {
$this->mNewtitle = "<span class='history-deleted'>{$this->mPagetitle}</span>";
- } else if ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ } elseif ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
$this->mNewtitle = "<span class='history-deleted'>{$this->mNewtitle}</span>";
}
@@ -1022,20 +1099,20 @@ CONTROL;
$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>)";
+ $undoLink = Html::element( 'a', array(
+ 'href' => $this->mNewPage->getLocalUrl( array(
+ 'action' => 'edit',
+ 'undoafter' => $this->mOldid,
+ 'undo' => $this->mNewid ) ),
+ 'title' => $wgUser->getSkin()->titleAttrib( 'undo' )
+ ), wfMsg( 'editundo' ) );
+ $this->mNewtitle .= ' (' . $undoLink . ')';
}
if ( !$this->mOldRev->userCan( Revision::DELETED_TEXT ) ) {
$this->mOldtitle = '<span class="history-deleted">' . $this->mOldPagetitle . '</span>';
- } else if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ } elseif ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
$this->mOldtitle = '<span class="history-deleted">' . $this->mOldtitle . '</span>';
}
}
@@ -1045,6 +1122,8 @@ CONTROL;
/**
* Load the text of the revisions, as well as revision data.
+ *
+ * @return bool
*/
function loadText() {
if ( $this->mTextLoaded == 2 ) {
@@ -1074,6 +1153,8 @@ CONTROL;
/**
* Load the text of the new revision, not the old one
+ *
+ * @return bool
*/
function loadNewText() {
if ( $this->mTextLoaded >= 1 ) {
diff --git a/includes/diff/WikiDiff3.php b/includes/diff/WikiDiff3.php
index 8def296d..27d3d5b8 100644
--- a/includes/diff/WikiDiff3.php
+++ b/includes/diff/WikiDiff3.php
@@ -239,7 +239,7 @@ class WikiDiff3 {
$starty - 1, $V, $snake )
+ $this->lcs_rec( $startx + $len, $topl1, $starty + $len,
$topl2, $V, $snake );
- } else if ( $d == 1 ) {
+ } elseif ( $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
@@ -332,7 +332,7 @@ class WikiDiff3 {
// check to see if we can cut down the diagonal range
if ( $x >= $N && $end_forward > $k - 1 ) {
$end_forward = $k - 1;
- } else if ( $absy - $bottoml2 >= $M ) {
+ } elseif ( $absy - $bottoml2 >= $M ) {
$start_forward = $k + 1;
$value_to_add_forward = 0;
}
@@ -366,7 +366,7 @@ class WikiDiff3 {
if ( $x <= 0 ) {
$start_backward = $k + 1;
$value_to_add_backward = 0;
- } else if ( $y <= 0 && $end_backward > $k - 1 ) {
+ } elseif ( $y <= 0 && $end_backward > $k - 1 ) {
$end_backward = $k - 1;
}
}
@@ -400,7 +400,7 @@ class WikiDiff3 {
// check to see if we can cut down the diagonal range
if ( $x >= $N && $end_forward > $k - 1 ) {
$end_forward = $k - 1;
- } else if ( $absy -$bottoml2 >= $M ) {
+ } elseif ( $absy -$bottoml2 >= $M ) {
$start_forward = $k + 1;
$value_to_add_forward = 0;
}
@@ -441,7 +441,7 @@ class WikiDiff3 {
if ( $x <= 0 ) {
$start_backward = $k + 1;
$value_to_add_backward = 0;
- } else if ( $y <= 0 && $end_backward > $k - 1 ) {
+ } elseif ( $y <= 0 && $end_backward > $k - 1 ) {
$end_backward = $k - 1;
}
}
@@ -510,7 +510,7 @@ class WikiDiff3 {
$max_progress[0][0] = $x;
$max_progress[0][1] = $y;
$max_progress[0][2] = $progress;
- } else if ( $progress == $max_progress[0][2] ) {
+ } elseif ( $progress == $max_progress[0][2] ) {
++$num_progress;
$max_progress[$num_progress][0] = $x;
$max_progress[$num_progress][1] = $y;
@@ -537,7 +537,7 @@ class WikiDiff3 {
$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 ) {
+ } elseif ( $progress == $max_progress[0][2] && !$max_progress_forward ) {
++$num_progress;
$max_progress[$num_progress][0] = $x;
$max_progress[$num_progress][1] = $y;
diff --git a/includes/extauth/MediaWiki.php b/includes/extauth/MediaWiki.php
index 9df4ea1f..0a5efae6 100644
--- a/includes/extauth/MediaWiki.php
+++ b/includes/extauth/MediaWiki.php
@@ -50,8 +50,17 @@
* @ingroup ExternalUser
*/
class ExternalUser_MediaWiki extends ExternalUser {
- private $mRow, $mDb;
+ private $mRow;
+ /**
+ * @var DatabaseBase
+ */
+ private $mDb;
+
+ /**
+ * @param $name string
+ * @return bool
+ */
protected function initFromName( $name ) {
# We might not need the 'usable' bit, but let's be safe. Theoretically
# this might return wrong results for old versions, but it's probably
@@ -65,20 +74,28 @@ class ExternalUser_MediaWiki extends ExternalUser {
return $this->initFromCond( array( 'user_name' => $name ) );
}
+ /**
+ * @param $id int
+ * @return bool
+ */
protected function initFromId( $id ) {
return $this->initFromCond( array( 'user_id' => $id ) );
}
+ /**
+ * @param $cond array
+ * @return bool
+ */
private function initFromCond( $cond ) {
global $wgExternalAuthConf;
- $this->mDb = DatabaseBase::newFromType( $wgExternalAuthConf['DBtype'],
+ $this->mDb = DatabaseBase::factory( $wgExternalAuthConf['DBtype'],
array(
- 'server' => $wgExternalAuthConf['DBserver'],
+ 'host' => $wgExternalAuthConf['DBserver'],
'user' => $wgExternalAuthConf['DBuser'],
'password' => $wgExternalAuthConf['DBpassword'],
'dbname' => $wgExternalAuthConf['DBname'],
- 'tableprefix' => $wgExternalAuthConf['DBprefix'],
+ 'tablePrefix' => $wgExternalAuthConf['DBprefix'],
)
);
@@ -105,6 +122,9 @@ class ExternalUser_MediaWiki extends ExternalUser {
return $this->mRow->user_id;
}
+ /**
+ * @return string
+ */
public function getName() {
return $this->mRow->user_name;
}
@@ -117,7 +137,7 @@ class ExternalUser_MediaWiki extends ExternalUser {
}
public function getPref( $pref ) {
- # FIXME: Return other prefs too. Lots of global-riddled code that does
+ # @todo FIXME: Return other prefs too. Lots of global-riddled code that does
# this normally.
if ( $pref === 'emailaddress'
&& $this->row->user_email_authenticated !== null ) {
@@ -126,8 +146,11 @@ class ExternalUser_MediaWiki extends ExternalUser {
return null;
}
+ /**
+ * @return array
+ */
public function getGroups() {
- # FIXME: Untested.
+ # @todo FIXME: Untested.
$groups = array();
$res = $this->mDb->select(
'user_groups',
diff --git a/includes/extauth/vB.php b/includes/extauth/vB.php
index 860048f3..f516c423 100644
--- a/includes/extauth/vB.php
+++ b/includes/extauth/vB.php
@@ -34,7 +34,7 @@
* 'username' => 'forum',
* 'password' => 'udE,jSqDJ<""p=fI.K9',
* 'dbname' => 'forum',
- * 'tableprefix' => '',
+ * 'tablePrefix' => '',
* 'cookieprefix' => 'bb'
* );
*
@@ -108,8 +108,8 @@ class ExternalUser_vB extends ExternalUser {
$wgExternalAuthConf['username'],
$wgExternalAuthConf['password'],
$wgExternalAuthConf['dbname'],
- false, 0,
- $wgExternalAuthConf['tableprefix']
+ 0,
+ $wgExternalAuthConf['tablePrefix']
);
}
diff --git a/includes/filerepo/ArchivedFile.php b/includes/filerepo/ArchivedFile.php
index ecc09978..0d9e349b 100644
--- a/includes/filerepo/ArchivedFile.php
+++ b/includes/filerepo/ArchivedFile.php
@@ -16,7 +16,6 @@ class ArchivedFile {
* @private
*/
var $id, # filearchive row ID
- $title, # image title
$name, # image name
$group, # FileStore storage group
$key, # FileStore sha1 key
@@ -32,10 +31,27 @@ class ArchivedFile {
$user_text, # user name of uploader
$timestamp, # time of upload
$dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
- $deleted; # Bitfield akin to rev_deleted
+ $deleted, # Bitfield akin to rev_deleted
+ $pageCount,
+ $archive_name;
+
+ /**
+ * @var MediaHandler
+ */
+ var $handler;
+ /**
+ * @var Title
+ */
+ var $title; # image title
/**#@-*/
+ /**
+ * @throws MWException
+ * @param Title $title
+ * @param int $id
+ * @param string $key
+ */
function __construct( $title, $id=0, $key='' ) {
$this->id = -1;
$this->title = false;
@@ -57,19 +73,22 @@ class ArchivedFile {
$this->dataLoaded = false;
$this->exists = false;
- if( is_object($title) ) {
+ if( is_object( $title ) ) {
$this->title = $title;
$this->name = $title->getDBkey();
}
- if ($id)
+ if ($id) {
$this->id = $id;
+ }
- if ($key)
+ if ($key) {
$this->key = $key;
+ }
- if (!$id && !$key && !is_object($title))
+ if ( !$id && !$key && !is_object( $title ) ) {
throw new MWException( "No specifications provided to ArchivedFile constructor." );
+ }
}
/**
@@ -82,17 +101,20 @@ class ArchivedFile {
}
$conds = array();
- if( $this->id > 0 )
+ if( $this->id > 0 ) {
$conds['fa_id'] = $this->id;
+ }
if( $this->key ) {
$conds['fa_storage_group'] = $this->group;
$conds['fa_storage_key'] = $this->key;
}
- if( $this->title )
+ if( $this->title ) {
$conds['fa_name'] = $this->title->getDBkey();
+ }
- if( !count($conds))
+ if( !count($conds)) {
throw new MWException( "No specific information for retrieving archived file" );
+ }
if( !$this->title || $this->title->getNamespace() == NS_FILE ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -119,8 +141,7 @@ class ArchivedFile {
$conds,
__METHOD__,
array( 'ORDER BY' => 'fa_timestamp DESC' ) );
-
- if ( $dbr->numRows( $res ) == 0 ) {
+ if ( $res == false || $dbr->numRows( $res ) == 0 ) {
// this revision does not exist?
return;
}
@@ -277,6 +298,32 @@ class ArchivedFile {
}
/**
+ * Get a MediaHandler instance for this file
+ * @return MediaHandler
+ */
+ function getHandler() {
+ if ( !isset( $this->handler ) ) {
+ $this->handler = MediaHandler::getHandler( $this->getMimeType() );
+ }
+ return $this->handler;
+ }
+
+ /**
+ * Returns the number of pages of a multipage document, or false for
+ * documents which aren't multipage documents
+ */
+ function pageCount() {
+ if ( !isset( $this->pageCount ) ) {
+ if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
+ $this->pageCount = $this->handler->pageCount( $this );
+ } else {
+ $this->pageCount = false;
+ }
+ }
+ return $this->pageCount;
+ }
+
+ /**
* Return the type of the media in the file.
* Use the value returned by this function with the MEDIATYPE_xxx constants.
*/
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php
index e2251b2b..2610ac6e 100644
--- a/includes/filerepo/FSRepo.php
+++ b/includes/filerepo/FSRepo.php
@@ -65,6 +65,10 @@ class FSRepo extends FileRepo {
/**
* Get the local directory corresponding to one of the three basic zones
+ *
+ * @param $zone string
+ *
+ * @return string
*/
function getZonePath( $zone ) {
switch ( $zone ) {
@@ -83,6 +87,10 @@ class FSRepo extends FileRepo {
/**
* @see FileRepo::getZoneUrl()
+ *
+ * @param $zone string
+ *
+ * @return url
*/
function getZoneUrl( $zone ) {
switch ( $zone ) {
@@ -103,6 +111,10 @@ class FSRepo extends FileRepo {
* Get a URL referring to this repository, with the private mwrepo protocol.
* The suffix, if supplied, is considered to be unencoded, and will be
* URL-encoded before being returned.
+ *
+ * @param $suffix string
+ *
+ * @return string
*/
function getVirtualUrl( $suffix = false ) {
$path = 'mwrepo://' . $this->name;
@@ -114,10 +126,14 @@ class FSRepo extends FileRepo {
/**
* Get the local path corresponding to a virtual URL
+ *
+ * @param $url string
+ *
+ * @return string
*/
function resolveVirtualUrl( $url ) {
if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
- throw new MWException( __METHOD__.': unknown protoocl' );
+ throw new MWException( __METHOD__.': unknown protocol' );
}
$bits = explode( '/', substr( $url, 9 ), 3 );
@@ -146,16 +162,23 @@ class FSRepo extends FileRepo {
* same contents as the source
*/
function storeBatch( $triplets, $flags = 0 ) {
+ wfDebug( __METHOD__ . ': Storing ' . count( $triplets ) .
+ " triplets; flags: {$flags}\n" );
+
+ // Try creating directories
if ( !wfMkdirParents( $this->directory ) ) {
return $this->newFatal( 'upload_directory_missing', $this->directory );
}
if ( !is_writable( $this->directory ) ) {
return $this->newFatal( 'upload_directory_read_only', $this->directory );
}
+
+ // Validate each triplet
$status = $this->newGood();
foreach ( $triplets as $i => $triplet ) {
list( $srcPath, $dstZone, $dstRel ) = $triplet;
+ // Resolve destination path
$root = $this->getZonePath( $dstZone );
if ( !$root ) {
throw new MWException( "Invalid zone: $dstZone" );
@@ -166,6 +189,7 @@ class FSRepo extends FileRepo {
$dstPath = "$root/$dstRel";
$dstDir = dirname( $dstPath );
+ // Create destination directories for this triplet
if ( !is_dir( $dstDir ) ) {
if ( !wfMkdirParents( $dstDir ) ) {
return $this->newFatal( 'directorycreateerror', $dstDir );
@@ -175,6 +199,7 @@ class FSRepo extends FileRepo {
}
}
+ // Resolve source
if ( self::isVirtualUrl( $srcPath ) ) {
$srcPath = $triplets[$i][0] = $this->resolveVirtualUrl( $srcPath );
}
@@ -183,6 +208,8 @@ class FSRepo extends FileRepo {
$status->fatal( 'filenotfound', $srcPath );
continue;
}
+
+ // Check overwriting
if ( !( $flags & self::OVERWRITE ) && file_exists( $dstPath ) ) {
if ( $flags & self::OVERWRITE_SAME ) {
$hashSource = sha1_file( $srcPath );
@@ -196,6 +223,7 @@ class FSRepo extends FileRepo {
}
}
+ // Windows does not support moving over existing files, so explicitly delete them
$deleteDest = wfIsWindows() && ( $flags & self::OVERWRITE );
// Abort now on failure
@@ -203,7 +231,8 @@ class FSRepo extends FileRepo {
return $status;
}
- foreach ( $triplets as $triplet ) {
+ // Execute the store operation for each triplet
+ foreach ( $triplets as $i => $triplet ) {
list( $srcPath, $dstZone, $dstRel ) = $triplet;
$root = $this->getZonePath( $dstZone );
$dstPath = "$root/$dstRel";
@@ -222,6 +251,20 @@ class FSRepo extends FileRepo {
$status->error( 'filecopyerror', $srcPath, $dstPath );
$good = false;
}
+ if ( !( $flags & self::SKIP_VALIDATION ) ) {
+ wfSuppressWarnings();
+ $hashSource = sha1_file( $srcPath );
+ $hashDest = sha1_file( $dstPath );
+ wfRestoreWarnings();
+
+ if ( $hashDest === false || $hashSource !== $hashDest ) {
+ wfDebug( __METHOD__ . ': File copy validation failed: ' .
+ "$srcPath ($hashSource) to $dstPath ($hashDest)\n" );
+
+ $status->error( 'filecopyerror', $srcPath, $dstPath );
+ $good = false;
+ }
+ }
}
if ( $good ) {
$this->chmod( $dstPath );
@@ -229,47 +272,81 @@ class FSRepo extends FileRepo {
} else {
$status->failCount++;
}
+ $status->success[$i] = $good;
}
return $status;
}
+
+ /**
+ * Deletes a batch of files. Each file can be a (zone, rel) pairs, a
+ * virtual url or a real path. It will try to delete each file, but
+ * ignores any errors that may occur
+ *
+ * @param $pairs array List of files to delete
+ */
+ function cleanupBatch( $files ) {
+ foreach ( $files as $file ) {
+ if ( is_array( $file ) ) {
+ // This is a pair, extract it
+ list( $zone, $rel ) = $file;
+ $root = $this->getZonePath( $zone );
+ $path = "$root/$rel";
+ } else {
+ if ( self::isVirtualUrl( $file ) ) {
+ // This is a virtual url, resolve it
+ $path = $this->resolveVirtualUrl( $file );
+ } else {
+ // This is a full file name
+ $path = $file;
+ }
+ }
+
+ wfSuppressWarnings();
+ unlink( $path );
+ wfRestoreWarnings();
+ }
+ }
function append( $srcPath, $toAppendPath, $flags = 0 ) {
$status = $this->newGood();
// Resolve the virtual URL
- if ( self::isVirtualUrl( $srcPath ) ) {
- $srcPath = $this->resolveVirtualUrl( $srcPath );
+ if ( self::isVirtualUrl( $toAppendPath ) ) {
+ $toAppendPath = $this->resolveVirtualUrl( $toAppendPath );
}
// Make sure the files are there
- if ( !is_file( $srcPath ) )
- $status->fatal( 'filenotfound', $srcPath );
-
if ( !is_file( $toAppendPath ) )
$status->fatal( 'filenotfound', $toAppendPath );
+ if ( !is_file( $srcPath ) )
+ $status->fatal( 'filenotfound', $srcPath );
+
if ( !$status->isOk() ) return $status;
// Do the append
- $chunk = file_get_contents( $toAppendPath );
+ $chunk = file_get_contents( $srcPath );
if( $chunk === false ) {
- $status->fatal( 'fileappenderrorread', $toAppendPath );
+ $status->fatal( 'fileappenderrorread', $srcPath );
}
if( $status->isOk() ) {
- if ( file_put_contents( $srcPath, $chunk, FILE_APPEND ) ) {
- $status->value = $srcPath;
+ if ( file_put_contents( $toAppendPath, $chunk, FILE_APPEND ) ) {
+ $status->value = $toAppendPath;
} else {
- $status->fatal( 'fileappenderror', $toAppendPath, $srcPath);
+ $status->fatal( 'fileappenderror', $srcPath, $toAppendPath);
}
}
if ( $flags & self::DELETE_SOURCE ) {
- unlink( $toAppendPath );
+ unlink( $srcPath );
}
return $status;
}
+ /* We can actually append to the files, so no-op needed here. */
+ function appendFinish( $toAppendPath ) {}
+
/**
* Checks existence of specified array of files.
*
@@ -515,14 +592,18 @@ class FSRepo extends FileRepo {
$good = true;
if ( file_exists( $archivePath ) ) {
# A file with this content hash is already archived
- if ( !@unlink( $srcPath ) ) {
+ wfSuppressWarnings();
+ $good = unlink( $srcPath );
+ wfRestoreWarnings();
+ if ( !$good ) {
$status->error( 'filedeleteerror', $srcPath );
- $good = false;
}
} else{
- if ( !@rename( $srcPath, $archivePath ) ) {
+ wfSuppressWarnings();
+ $good = rename( $srcPath, $archivePath );
+ wfRestoreWarnings();
+ if ( !$good ) {
$status->error( 'filerenameerror', $srcPath, $archivePath );
- $good = false;
} else {
$this->chmod( $archivePath );
}
@@ -564,8 +645,11 @@ class FSRepo extends FileRepo {
continue;
}
$dir = opendir( $path );
- while ( false !== ( $name = readdir( $dir ) ) ) {
- call_user_func( $callback, $path . '/' . $name );
+ if ($dir) {
+ while ( false !== ( $name = readdir( $dir ) ) ) {
+ call_user_func( $callback, $path . '/' . $name );
+ }
+ closedir( $dir );
}
}
}
diff --git a/includes/filerepo/File.php b/includes/filerepo/File.php
index 192e8c8a..6b35102b 100644
--- a/includes/filerepo/File.php
+++ b/includes/filerepo/File.php
@@ -52,7 +52,23 @@ abstract class File {
/**
* The following member variables are not lazy-initialised
*/
- var $repo, $title, $lastError, $redirected, $redirectedTitle;
+
+ /**
+ * @var LocalRepo
+ */
+ var $repo;
+
+ /**
+ * @var Title
+ */
+ var $title;
+
+ var $lastError, $redirected, $redirectedTitle;
+
+ /**
+ * @var MediaHandler
+ */
+ protected $handler;
/**
* Call this constructor from child classes
@@ -101,6 +117,8 @@ abstract class File {
*
* @param $old File Old file
* @param $new string New name
+ *
+ * @return bool|null
*/
static function checkExtensionCompatibility( File $old, $new ) {
$oldMime = $old->getMimeType();
@@ -122,10 +140,10 @@ abstract class File {
* Split an internet media type into its two components; if not
* a two-part name, set the minor type to 'unknown'.
*
- * @param $mime "text/html" etc
+ * @param string $mime "text/html" etc
* @return array ("text", "html") etc
*/
- static function splitMime( $mime ) {
+ public static function splitMime( $mime ) {
if( strpos( $mime, '/' ) !== false ) {
return explode( '/', $mime, 2 );
} else {
@@ -135,6 +153,8 @@ abstract class File {
/**
* Return the name of this file
+ *
+ * @return string
*/
public function getName() {
if ( !isset( $this->name ) ) {
@@ -145,6 +165,8 @@ abstract class File {
/**
* Get the file extension, e.g. "svg"
+ *
+ * @return string
*/
function getExtension() {
if ( !isset( $this->extension ) ) {
@@ -157,20 +179,26 @@ abstract class File {
/**
* Return the associated title object
+ * @return Title
*/
public function getTitle() { return $this->title; }
-
+
/**
* Return the title used to find this file
+ *
+ * @return Title
*/
public function getOriginalTitle() {
- if ( $this->redirected )
+ if ( $this->redirected ) {
return $this->getRedirectedTitle();
+ }
return $this->title;
}
/**
* Return the URL of the file
+ *
+ * @return string
*/
public function getUrl() {
if ( !isset( $this->url ) ) {
@@ -187,15 +215,21 @@ abstract class File {
* @return String
*/
public function getFullUrl() {
- return wfExpandUrl( $this->getUrl() );
+ return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
+ }
+
+ public function getCanonicalUrl() {
+ return wfExpandUrl( $this->getUrl(), PROTO_CANONICAL );
}
+ /**
+ * @return string
+ */
function getViewURL() {
if( $this->mustRender()) {
if( $this->canRender() ) {
return $this->createThumb( $this->getWidth() );
- }
- else {
+ } else {
wfDebug(__METHOD__.': supposed to render '.$this->getName().' ('.$this->getMimeType()."), but can't!\n");
return $this->getURL(); #hm... return NULL?
}
@@ -212,7 +246,10 @@ abstract class File {
* i.e. whether the files are all found in the same directory,
* or in hashed paths like /images/3/3c.
*
- * May return false if the file is not locally accessible.
+ * Most callers don't check the return value, but ForeignAPIFile::getPath
+ * returns false.
+ *
+ * @return string|false
*/
public function getPath() {
if ( !isset( $this->path ) ) {
@@ -222,9 +259,14 @@ abstract class File {
}
/**
- * Alias for getPath()
- */
+ * Alias for getPath()
+ *
+ * @deprecated since 1.18 Use getPath().
+ *
+ * @return string
+ */
public function getFullPath() {
+ wfDeprecated( __METHOD__ );
return $this->getPath();
}
@@ -234,8 +276,14 @@ abstract class File {
*
* STUB
* Overridden by LocalFile, UnregisteredLocalFile
+ *
+ * @param $page int
+ *
+ * @return number
*/
- public function getWidth( $page = 1 ) { return false; }
+ public function getWidth( $page = 1 ) {
+ return false;
+ }
/**
* Return the height of the image. Returns false if the height is unknown
@@ -243,19 +291,29 @@ abstract class File {
*
* STUB
* Overridden by LocalFile, UnregisteredLocalFile
+ *
+ * @return false|number
*/
- public function getHeight( $page = 1 ) { return false; }
+ public function getHeight( $page = 1 ) {
+ return false;
+ }
/**
* Returns ID or name of user who uploaded the file
* STUB
*
* @param $type string 'text' or 'id'
+ *
+ * @return string|int
*/
- public function getUser( $type='text' ) { return null; }
+ public function getUser( $type = 'text' ) {
+ return null;
+ }
/**
* Get the duration of a media file in seconds
+ *
+ * @return number
*/
public function getLength() {
$handler = $this->getHandler();
@@ -267,45 +325,76 @@ 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;
- }
- }
-
+ * Return true if the file is vectorized
+ *
+ * @return bool
+ */
+ public function isVectorized() {
+ $handler = $this->getHandler();
+ if ( $handler ) {
+ return $handler->isVectorized( $this );
+ } else {
+ return false;
+ }
+ }
/**
* Get handler-specific metadata
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
*/
- public function getMetadata() { return false; }
+ public function getMetadata() {
+ return false;
+ }
+
+ /**
+ * get versioned metadata
+ *
+ * @param $metadata Mixed Array or String of (serialized) metadata
+ * @param $version integer version number.
+ * @return Array containing metadata, or what was passed to it on fail (unserializing if not array)
+ */
+ public function convertMetadataVersion($metadata, $version) {
+ $handler = $this->getHandler();
+ if ( !is_array( $metadata ) ) {
+ //just to make the return type consistant
+ $metadata = unserialize( $metadata );
+ }
+ if ( $handler ) {
+ return $handler->convertMetadataVersion( $metadata, $version );
+ } else {
+ return $metadata;
+ }
+ }
/**
* Return the bit depth of the file
* Overridden by LocalFile
* STUB
*/
- public function getBitDepth() { return 0; }
+ public function getBitDepth() {
+ return 0;
+ }
/**
* Return the size of the image file, in bytes
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
*/
- public function getSize() { return false; }
+ public function getSize() {
+ return false;
+ }
/**
* Returns the mime type of the file.
* Overridden by LocalFile, UnregisteredLocalFile
* STUB
+ *
+ * @return string
*/
- function getMimeType() { return 'unknown/unknown'; }
+ function getMimeType() {
+ return 'unknown/unknown';
+ }
/**
* Return the type of the media in the file.
@@ -324,6 +413,8 @@ abstract class File {
* that can be converted to a format
* supported by all browsers (namely GIF, PNG and JPEG),
* or if it is an SVG image and SVG conversion is enabled.
+ *
+ * @return bool
*/
function canRender() {
if ( !isset( $this->canRender ) ) {
@@ -355,6 +446,8 @@ abstract class File {
/**
* Alias for canRender()
+ *
+ * @return bool
*/
function allowInlineDisplay() {
return $this->canRender();
@@ -370,6 +463,8 @@ abstract class File {
*
* Note that this function will always return true if allowInlineDisplay()
* or isTrustedFile() is true for this file.
+ *
+ * @return bool
*/
function isSafeFile() {
if ( !isset( $this->isSafeFile ) ) {
@@ -378,41 +473,64 @@ abstract class File {
return $this->isSafeFile;
}
- /** Accessor for __get() */
+ /**
+ * Accessor for __get()
+ *
+ * @return bool
+ */
protected function getIsSafeFile() {
return $this->isSafeFile();
}
- /** Uncached accessor */
+ /**
+ * Uncached accessor
+ *
+ * @return bool
+ */
protected function _getIsSafeFile() {
- if ($this->allowInlineDisplay()) return true;
- if ($this->isTrustedFile()) return true;
+ if ( $this->allowInlineDisplay() ) {
+ return true;
+ }
+ if ($this->isTrustedFile()) {
+ return true;
+ }
global $wgTrustedMediaFormats;
- $type= $this->getMediaType();
- $mime= $this->getMimeType();
+ $type = $this->getMediaType();
+ $mime = $this->getMimeType();
#wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
- if (!$type || $type===MEDIATYPE_UNKNOWN) return false; #unknown type, not trusted
- if ( in_array( $type, $wgTrustedMediaFormats) ) return true;
+ if ( !$type || $type === MEDIATYPE_UNKNOWN ) {
+ return false; #unknown type, not trusted
+ }
+ if ( in_array( $type, $wgTrustedMediaFormats ) ) {
+ return true;
+ }
- if ($mime==="unknown/unknown") return false; #unknown type, not trusted
- if ( in_array( $mime, $wgTrustedMediaFormats) ) return true;
+ if ( $mime === "unknown/unknown" ) {
+ return false; #unknown type, not trusted
+ }
+ if ( in_array( $mime, $wgTrustedMediaFormats) ) {
+ return true;
+ }
return false;
}
- /** Returns true if the file is flagged as trusted. Files flagged that way
- * can be linked to directly, even if that is not allowed for this type of
- * file normally.
- *
- * This is a dummy function right now and always returns false. It could be
- * implemented to extract a flag from the database. The trusted flag could be
- * set on upload, if the user has sufficient privileges, to bypass script-
- * and html-filters. It may even be coupled with cryptographics signatures
- * or such.
- */
+ /**
+ * Returns true if the file is flagged as trusted. Files flagged that way
+ * can be linked to directly, even if that is not allowed for this type of
+ * file normally.
+ *
+ * This is a dummy function right now and always returns false. It could be
+ * implemented to extract a flag from the database. The trusted flag could be
+ * set on upload, if the user has sufficient privileges, to bypass script-
+ * and html-filters. It may even be coupled with cryptographics signatures
+ * or such.
+ *
+ * @return bool
+ */
function isTrustedFile() {
#this could be implemented to check a flag in the databas,
#look for signatures, etc
@@ -435,12 +553,14 @@ abstract class File {
* It would be unsafe to include private images, making public thumbnails inadvertently
*
* @return boolean Whether file exists in the repository and is includable.
- * @public
*/
- function isVisible() {
+ public function isVisible() {
return $this->exists();
}
+ /**
+ * @return string
+ */
function getTransformScript() {
if ( !isset( $this->transformScript ) ) {
$this->transformScript = false;
@@ -456,6 +576,10 @@ abstract class File {
/**
* Get a ThumbnailImage which is the same size as the source
+ *
+ * @param $handlerParams array
+ *
+ * @return string
*/
function getUnscaledThumb( $handlerParams = array() ) {
$hp =& $handlerParams;
@@ -473,14 +597,28 @@ abstract class File {
*
* @param $params Array: handler-specific parameters
* @private -ish
+ *
+ * @return string
*/
function thumbName( $params ) {
+ return $this->generateThumbName( $this->getName(), $params );
+ }
+
+ /**
+ * Generate a thumbnail file name from a name and specified parameters
+ *
+ * @param string $name
+ * @param array $params Parameters which will be passed to MediaHandler::makeParamString
+ *
+ * @return string
+ */
+ function generateThumbName( $name, $params ) {
if ( !$this->getHandler() ) {
return null;
}
$extension = $this->getExtension();
list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType(), $params );
- $thumbName = $this->handler->makeParamString( $params ) . '-' . $this->getName();
+ $thumbName = $this->handler->makeParamString( $params ) . '-' . $name;
if ( $thumbExt != $extension ) {
$thumbName .= ".$thumbExt";
}
@@ -501,6 +639,8 @@ abstract class File {
*
* @param $width Integer: maximum width of the generated thumbnail
* @param $height Integer: maximum height of the image (optional)
+ *
+ * @return string
*/
public function createThumb( $width, $height = -1 ) {
$params = array( 'width' => $width );
@@ -513,30 +653,6 @@ 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.
- *
- * For non-image formats, this may return a filetype-specific icon.
- *
- * @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;
- }
- return $this->transform( $params, 0 );
- }
-
- /**
* Transform a media file
*
* @param $params Array: an associative array of handler-specific parameters.
@@ -558,7 +674,7 @@ abstract class File {
// Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
$descriptionUrl = $this->getDescriptionUrl();
if ( $descriptionUrl ) {
- $params['descriptionUrl'] = $wgServer . $descriptionUrl;
+ $params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
}
$script = $this->getTransformScript();
@@ -586,8 +702,8 @@ abstract class File {
if ( file_exists( $thumbPath )) {
$thumbTime = filemtime( $thumbPath );
if ( $thumbTime !== FALSE &&
- gmdate( 'YmdHis', $thumbTime ) >= $wgThumbnailEpoch ) {
-
+ gmdate( 'YmdHis', $thumbTime ) >= $wgThumbnailEpoch ) {
+
$thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
break;
}
@@ -603,9 +719,9 @@ abstract class File {
$thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
}
}
-
- // Purge. Useful in the event of Core -> Squid connection failure or squid
- // purge collisions from elsewhere during failure. Don't keep triggering for
+
+ // Purge. Useful in the event of Core -> Squid connection failure or squid
+ // purge collisions from elsewhere during failure. Don't keep triggering for
// "thumbs" which have the main image URL though (bug 13776)
if ( $wgUseSquid && ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL()) ) {
SquidUpdate::purge( array( $thumbUrl ) );
@@ -625,6 +741,7 @@ abstract class File {
/**
* Get a MediaHandler instance for this file
+ * @return MediaHandler
*/
function getHandler() {
if ( !isset( $this->handler ) ) {
@@ -664,7 +781,9 @@ abstract class File {
* STUB
* Overridden by LocalFile
*/
- function getThumbnails() { return array(); }
+ function getThumbnails() {
+ return array();
+ }
/**
* Purge shared caches such as thumbnails and DB data caching
@@ -711,6 +830,8 @@ abstract class File {
* @param $start timestamp Only revisions older than $start will be returned
* @param $end timestamp Only revisions newer than $end will be returned
* @param $inc bool Include the endpoints of the time range
+ *
+ * @return array
*/
function getHistory($limit = null, $start = null, $end = null, $inc=true) {
return array();
@@ -740,6 +861,8 @@ abstract class File {
* Get the filename hash component of the directory including trailing slash,
* e.g. f/fa/
* If the repository is not hashed, returns an empty string.
+ *
+ * @return string
*/
function getHashPath() {
if ( !isset( $this->hashPath ) ) {
@@ -750,6 +873,8 @@ abstract class File {
/**
* Get the path of the file relative to the public zone root
+ *
+ * @return string
*/
function getRel() {
return $this->getHashPath() . $this->getName();
@@ -757,12 +882,20 @@ abstract class File {
/**
* Get urlencoded relative path of the file
+ *
+ * @return string
*/
function getUrlRel() {
return $this->getHashPath() . rawurlencode( $this->getName() );
}
- /** Get the relative path for an archive file */
+ /**
+ * Get the relative path for an archived file
+ *
+ * @param $suffix bool|string if not false, the name of an archived thumbnail file
+ *
+ * @return string
+ */
function getArchiveRel( $suffix = false ) {
$path = 'archive/' . $this->getHashPath();
if ( $suffix === false ) {
@@ -773,21 +906,68 @@ abstract class File {
return $path;
}
- /** Get the path of the archive directory, or a particular file if $suffix is specified */
+ /**
+ * Get the relative path for an archived file's thumbs directory
+ * or a specific thumb if the $suffix is given.
+ *
+ * @param $archiveName string the timestamped name of an archived image
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ */
+ function getArchiveThumbRel( $archiveName, $suffix = false ) {
+ $path = 'archive/' . $this->getHashPath() . $archiveName . "/";
+ if ( $suffix === false ) {
+ $path = substr( $path, 0, -1 );
+ } else {
+ $path .= $suffix;
+ }
+ return $path;
+ }
+
+ /**
+ * Get the path of the archived file.
+ *
+ * @param $suffix bool|string if not false, the name of an archived file.
+ *
+ * @return string
+ */
function getArchivePath( $suffix = false ) {
- return $this->repo->getZonePath('public') . '/' . $this->getArchiveRel( $suffix );
+ return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
}
- /** Get the path of the thumbnail directory, or a particular file if $suffix is specified */
+ /**
+ * Get the path of the archived file's thumbs, or a particular thumb if $suffix is specified
+ *
+ * @param $archiveName string the timestamped name of an archived image
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
+ function getArchiveThumbPath( $archiveName, $suffix = false ) {
+ return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getArchiveThumbRel( $archiveName, $suffix );
+ }
+
+ /**
+ * Get the path of the thumbnail directory, or a particular file if $suffix is specified
+ *
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
function getThumbPath( $suffix = false ) {
- $path = $this->repo->getZonePath('thumb') . '/' . $this->getRel();
+ $path = $this->repo->getZonePath( 'thumb' ) . '/' . $this->getRel();
if ( $suffix !== false ) {
$path .= '/' . $suffix;
}
return $path;
}
- /** Get the URL of the archive directory, or a particular file if $suffix is specified */
+ /**
+ * Get the URL of the archive directory, or a particular file if $suffix is specified
+ *
+ * @param $suffix bool|string if not false, the name of an archived file
+ *
+ * @return string
+ */
function getArchiveUrl( $suffix = false ) {
$path = $this->repo->getZoneUrl('public') . '/archive/' . $this->getHashPath();
if ( $suffix === false ) {
@@ -798,7 +978,31 @@ abstract class File {
return $path;
}
- /** Get the URL of the thumbnail directory, or a particular file if $suffix is specified */
+ /**
+ * Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified
+ *
+ * @param $archiveName string the timestamped name of an archived image
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
+ function getArchiveThumbUrl( $archiveName, $suffix = false ) {
+ $path = $this->repo->getZoneUrl('thumb') . '/archive/' . $this->getHashPath() . rawurlencode( $archiveName ) . "/";
+ if ( $suffix === false ) {
+ $path = substr( $path, 0, -1 );
+ } else {
+ $path .= rawurlencode( $suffix );
+ }
+ return $path;
+ }
+
+ /**
+ * Get the URL of the thumbnail directory, or a particular file if $suffix is specified
+ *
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return path
+ */
function getThumbUrl( $suffix = false ) {
$path = $this->repo->getZoneUrl('thumb') . '/' . $this->getUrlRel();
if ( $suffix !== false ) {
@@ -807,7 +1011,13 @@ abstract class File {
return $path;
}
- /** Get the virtual URL for an archive file or directory */
+ /**
+ * Get the virtual URL for an archived file's thumbs, or a specific thumb.
+ *
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
function getArchiveVirtualUrl( $suffix = false ) {
$path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
if ( $suffix === false ) {
@@ -818,7 +1028,13 @@ abstract class File {
return $path;
}
- /** Get the virtual URL for a thumbnail file or directory */
+ /**
+ * Get the virtual URL for a thumbnail file or directory
+ *
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
function getThumbVirtualUrl( $suffix = false ) {
$path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
if ( $suffix !== false ) {
@@ -827,7 +1043,13 @@ abstract class File {
return $path;
}
- /** Get the virtual URL for the file itself */
+ /**
+ * Get the virtual URL for the file itself
+ *
+ * @param $suffix bool|string if not false, the name of a thumbnail file
+ *
+ * @return string
+ */
function getVirtualUrl( $suffix = false ) {
$path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
if ( $suffix !== false ) {
@@ -843,6 +1065,9 @@ abstract class File {
return $this->repo->isHashed();
}
+ /**
+ * @throws MWException
+ */
function readOnlyError() {
throw new MWException( get_class($this) . ': write operations are not supported' );
}
@@ -851,6 +1076,12 @@ abstract class File {
* Record a file upload in the upload log and the image table
* STUB
* Overridden by LocalFile
+ * @param $oldver
+ * @param $desc
+ * @param $license string
+ * @param $copyStatus string
+ * @param $source string
+ * @param $watch bool
*/
function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
$this->readOnlyError();
@@ -879,46 +1110,8 @@ abstract class File {
}
/**
- * Get an array of Title objects which are articles which use this file
- * Also adds their IDs to the link cache
- *
- * This is mostly copied from Title::getLinksTo()
- *
- * @deprecated Use HTMLCacheUpdate, this function uses too much memory
+ * @return bool
*/
- function getLinksTo( $options = array() ) {
- wfDeprecated( __METHOD__ );
- wfProfileIn( __METHOD__ );
-
- // Note: use local DB not repo DB, we want to know local links
- if ( count( $options ) > 0 ) {
- $db = wfGetDB( DB_MASTER );
- } else {
- $db = wfGetDB( DB_SLAVE );
- }
- $linkCache = LinkCache::singleton();
-
- $encName = $db->addQuotes( $this->getName() );
- $res = $db->select( array( 'page', 'imagelinks'),
- 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 ) ) {
- 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;
- }
- }
- }
- wfProfileOut( __METHOD__ );
- return $retVal;
- }
-
function formatMetadata() {
if ( !$this->getHandler() ) {
return false;
@@ -944,8 +1137,11 @@ abstract class File {
function getRepoName() {
return $this->repo ? $this->repo->getName() : 'unknown';
}
- /*
+
+ /**
* Returns the repository
+ *
+ * @return FileRepo
*/
function getRepo() {
return $this->repo;
@@ -954,6 +1150,8 @@ abstract class File {
/**
* Returns true if the image is an old version
* STUB
+ *
+ * @return bool
*/
function isOld() {
return false;
@@ -962,15 +1160,19 @@ abstract class File {
/**
* Is this file a "deleted" file in a private archive?
* STUB
+ *
+ * @param $field
+ *
+ * @return bool
*/
function isDeleted( $field ) {
return false;
}
-
+
/**
* Return the deletion bitfield
* STUB
- */
+ */
function getVisibility() {
return 0;
}
@@ -1025,21 +1227,21 @@ abstract class File {
*
* May throw database exceptions on error.
*
- * @param $versions set of record ids of deleted items to restore,
+ * @param $versions array set of record ids of deleted items to restore,
* or empty to restore all revisions.
- * @param $unsuppress remove restrictions on content upon restoration?
- * @return the number of file revisions restored if successful,
+ * @param $unsuppress bool remove restrictions on content upon restoration?
+ * @return int|false the number of file revisions restored if successful,
* or false on failure
* STUB
* Overridden by LocalFile
*/
- function restore( $versions=array(), $unsuppress=false ) {
+ function restore( $versions = array(), $unsuppress = false ) {
$this->readOnlyError();
}
/**
- * Returns 'true' if this file is a type which supports multiple pages,
- * e.g. DJVU or PDF. Note that this may be true even if the file in
+ * Returns 'true' if this file is a type which supports multiple pages,
+ * e.g. DJVU or PDF. Note that this may be true even if the file in
* question only has a single page.
*
* @return Bool
@@ -1051,6 +1253,8 @@ abstract class File {
/**
* Returns the number of pages of a multipage document, or false for
* documents which aren't multipage documents
+ *
+ * @return false|int
*/
function pageCount() {
if ( !isset( $this->pageCount ) ) {
@@ -1065,6 +1269,12 @@ abstract class File {
/**
* Calculate the height of a thumbnail using the source and destination width
+ *
+ * @param $srcWidth
+ * @param $srcHeight
+ * @param $dstWidth
+ *
+ * @return int
*/
static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
// Exact integer multiply followed by division
@@ -1092,6 +1302,8 @@ abstract class File {
/**
* Get the URL of the image description page. May return false if it is
* unknown or not applicable.
+ *
+ * @return string
*/
function getDescriptionUrl() {
return $this->repo->getDescriptionUrl( $this->getName() );
@@ -1099,6 +1311,8 @@ abstract class File {
/**
* Get the HTML text of the description page, if available
+ *
+ * @return string
*/
function getDescriptionText() {
global $wgMemc, $wgLang;
@@ -1109,7 +1323,7 @@ abstract class File {
if ( $renderUrl ) {
if ( $this->repo->descriptionCacheExpiry > 0 ) {
wfDebug("Attempting to get the description from cache...");
- $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
+ $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
$this->getName() );
$obj = $wgMemc->get($key);
if ($obj) {
@@ -1132,6 +1346,8 @@ abstract class File {
/**
* Get discription of file revision
* STUB
+ *
+ * @return string
*/
function getDescription() {
return null;
@@ -1140,6 +1356,8 @@ abstract class File {
/**
* Get the 14-character timestamp of the file upload, or false if
* it doesn't exist
+ *
+ * @return string
*/
function getTimestamp() {
$path = $this->getPath();
@@ -1151,6 +1369,8 @@ abstract class File {
/**
* Get the SHA-1 base 36 hash of the file
+ *
+ * @return string
*/
function getSha1() {
return self::sha1Base36( $this->getPath() );
@@ -1158,6 +1378,8 @@ abstract class File {
/**
* Get the deletion archive key, <sha1>.<ext>
+ *
+ * @return string
*/
function getStorageKey() {
$hash = $this->getSha1();
@@ -1166,7 +1388,7 @@ abstract class File {
}
$ext = $this->getExtension();
$dotExt = $ext === '' ? '' : ".$ext";
- return $hash . $dotExt;
+ return $hash . $dotExt;
}
/**
@@ -1186,6 +1408,8 @@ abstract class File {
* @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.
+ *
+ * @return array
*/
static function getPropsFromPath( $path, $ext = true ) {
wfProfileIn( __METHOD__ );
@@ -1258,7 +1482,9 @@ abstract class File {
* 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
* fairly neatly.
*
- * Returns false on failure
+ * @param $path string
+ *
+ * @return false|string False on failure
*/
static function sha1Base36( $path ) {
wfSuppressWarnings();
@@ -1271,6 +1497,9 @@ abstract class File {
}
}
+ /**
+ * @return string
+ */
function getLongDesc() {
$handler = $this->getHandler();
if ( $handler ) {
@@ -1280,6 +1509,9 @@ abstract class File {
}
}
+ /**
+ * @return string
+ */
function getShortDesc() {
$handler = $this->getHandler();
if ( $handler ) {
@@ -1289,6 +1521,9 @@ abstract class File {
}
}
+ /**
+ * @return string
+ */
function getDimensionsString() {
$handler = $this->getHandler();
if ( $handler ) {
@@ -1298,22 +1533,36 @@ abstract class File {
}
}
+ /**
+ * @return
+ */
function getRedirected() {
return $this->redirected;
}
-
+
+ /**
+ * @return Title
+ */
function getRedirectedTitle() {
if ( $this->redirected ) {
- if ( !$this->redirectTitle )
+ if ( !$this->redirectTitle ) {
$this->redirectTitle = Title::makeTitle( NS_FILE, $this->redirected );
+ }
return $this->redirectTitle;
}
}
+ /**
+ * @param $from
+ * @return void
+ */
function redirectedFrom( $from ) {
$this->redirected = $from;
}
+ /**
+ * @return bool
+ */
function isMissing() {
return false;
}
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index ff73a73c..843f09a9 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -17,6 +17,7 @@ abstract class FileRepo {
const DELETE_SOURCE = 1;
const OVERWRITE = 2;
const OVERWRITE_SAME = 4;
+ const SKIP_VALIDATION = 8;
var $thumbScriptUrl, $transformVia404;
var $descBaseUrl, $scriptDirUrl, $scriptExtension, $articleUrl;
@@ -39,7 +40,7 @@ abstract class FileRepo {
$this->initialCapital = MWNamespace::isCapitalized( NS_FILE );
foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
- 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl', 'scriptExtension' )
+ 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl', 'scriptExtension' )
as $var )
{
if ( isset( $info[$var] ) ) {
@@ -51,6 +52,10 @@ abstract class FileRepo {
/**
* Determine if a string is an mwrepo:// URL
+ *
+ * @param $url string
+ *
+ * @return bool
*/
static function isVirtualUrl( $url ) {
return substr( $url, 0, 9 ) == 'mwrepo://';
@@ -65,9 +70,11 @@ abstract class FileRepo {
* instance of the repository's old file class instead of a
* current file. Repositories not supporting version control
* should return false if this parameter is set.
+ *
+ * @return File
*/
function newFile( $title, $time = false ) {
- if ( !($title instanceof Title) ) {
+ if ( !( $title instanceof Title ) ) {
$title = Title::makeTitleSafe( NS_FILE, $title );
if ( !is_object( $title ) ) {
return null;
@@ -90,7 +97,7 @@ abstract class FileRepo {
* version control should return false if the time is specified.
*
* @param $title Mixed: Title object or string
- * @param $options Associative array of options:
+ * @param $options array Associative array of options:
* time: requested time for an archived image, or false for the
* current version. An image object will be returned which was
* created at the specified time.
@@ -100,14 +107,11 @@ abstract class FileRepo {
* private: If true, return restricted (deleted) files if the current
* user is allowed to view them. Otherwise, such files will not
* be found.
+ *
+ * @return File|false
*/
function findFile( $title, $options = array() ) {
- if ( !is_array( $options ) ) {
- // MW 1.15 compat
- $time = $options;
- } else {
- $time = isset( $options['time'] ) ? $options['time'] : false;
- }
+ $time = isset( $options['time'] ) ? $options['time'] : false;
if ( !($title instanceof Title) ) {
$title = Title::makeTitleSafe( NS_FILE, $title );
if ( !is_object( $title ) ) {
@@ -126,9 +130,9 @@ abstract class FileRepo {
if ( $time !== false ) {
$img = $this->newFile( $title, $time );
if ( $img && $img->exists() ) {
- if ( !$img->isDeleted(File::DELETED_FILE) ) {
- return $img;
- } else if ( !empty( $options['private'] ) && $img->userCan(File::DELETED_FILE) ) {
+ if ( !$img->isDeleted( File::DELETED_FILE ) ) {
+ return $img; // always OK
+ } elseif ( !empty( $options['private'] ) && $img->userCan( File::DELETED_FILE ) ) {
return $img;
}
}
@@ -139,7 +143,7 @@ abstract class FileRepo {
return false;
}
$redir = $this->checkRedirect( $title );
- if( $redir && $redir->getNamespace() == NS_FILE) {
+ if( $redir && $title->getNamespace() == NS_FILE) {
$img = $this->newFile( $redir );
if( !$img ) {
return false;
@@ -152,7 +156,7 @@ abstract class FileRepo {
return false;
}
- /*
+ /**
* Find many files at once.
* @param $items An array of titles, or an array of findFile() options with
* the "title" option giving the title. Example:
@@ -181,58 +185,32 @@ abstract class FileRepo {
}
/**
- * Create a new File object from the local repository
- * @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 {
- if ( $this->fileFactoryKey ) {
- return call_user_func( $this->fileFactoryKey, $sha1, $this );
- }
- }
- return false;
- }
-
- /**
* Find an instance of the file with this key, created at the specified time
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
- * @param $sha1 String
+ * @param $sha1 String base 36 SHA-1 hash
* @param $options Option array, same as findFile().
*/
function findFileFromKey( $sha1, $options = array() ) {
- if ( !is_array( $options ) ) {
- # MW 1.15 compat
- $time = $options;
- } else {
- $time = isset( $options['time'] ) ? $options['time'] : false;
- }
+ $time = isset( $options['time'] ) ? $options['time'] : false;
- # First try the current version of the file to see if it precedes the timestamp
- $img = $this->newFileFromKey( $sha1 );
- if ( !$img ) {
- return false;
+ # First try to find a matching current version of a file...
+ if ( $this->fileFactoryKey ) {
+ $img = call_user_func( $this->fileFactoryKey, $sha1, $this, $time );
+ } else {
+ return false; // find-by-sha1 not supported
}
- if ( $img->exists() && ( !$time || $img->getTimestamp() == $time ) ) {
+ if ( $img && $img->exists() ) {
return $img;
}
- # Now try an old version of the file
- if ( $time !== false ) {
- $img = $this->newFileFromKey( $sha1, $time );
+ # Now try to find a matching old version of a file...
+ if ( $time !== false && $this->oldFileFactoryKey ) { // find-by-sha1 supported?
+ $img = call_user_func( $this->oldFileFactoryKey, $sha1, $this, $time );
if ( $img && $img->exists() ) {
- if ( !$img->isDeleted(File::DELETED_FILE) ) {
- return $img;
- } else if ( !empty( $options['private'] ) && $img->userCan(File::DELETED_FILE) ) {
+ if ( !$img->isDeleted( File::DELETED_FILE ) ) {
+ return $img; // always OK
+ } elseif ( !empty( $options['private'] ) && $img->userCan( File::DELETED_FILE ) ) {
return $img;
}
}
@@ -265,6 +243,7 @@ abstract class FileRepo {
/**
* Get the name of an image from its title object
+ * @param $title Title
*/
function getNameFromTitle( $title ) {
if ( $this->initialCapital != MWNamespace::isCapitalized( NS_FILE ) ) {
@@ -306,18 +285,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 );
- }
+ return wfAppendQuery( "{$this->scriptDirUrl}/{$entry}{$ext}", $query );
+ }
/**
* Get the URL of an image description page. May return false if it is
@@ -367,7 +346,7 @@ abstract class FileRepo {
$query .= '&uselang=' . $lang;
}
if ( isset( $this->scriptDirUrl ) ) {
- return $this->makeUrl(
+ return $this->makeUrl(
'title=' .
wfUrlencode( 'Image:' . $name ) .
"&$query" );
@@ -380,7 +359,7 @@ abstract class FileRepo {
}
}
}
-
+
/**
* Get the URL of the stylesheet to apply to description pages
* @return string
@@ -433,7 +412,8 @@ abstract class FileRepo {
/**
- * Append the contents of the source path to the given file.
+ * Append the contents of the source path to the given file, OR queue
+ * the appending operation in anticipation of a later appendFinish() call.
* @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
@@ -443,6 +423,13 @@ abstract class FileRepo {
abstract function append( $srcPath, $toAppendPath, $flags = 0 );
/**
+ * Finish the append operation.
+ * @param $toAppendPath String: path to append to.
+ * @return mixed Status or false
+ */
+ abstract function appendFinish( $toAppendPath );
+
+ /**
* Remove a temporary file or mark it for garbage collection
* @param $virtualUrl String: the virtual URL returned by storeTemp
* @return Boolean: true on success, false on failure
@@ -602,7 +589,7 @@ abstract class FileRepo {
function newFatal( $message /*, parameters...*/ ) {
$params = func_get_args();
array_unshift( $params, $this );
- return call_user_func_array( array( 'FileRepoStatus', 'newFatal' ), $params );
+ return MWInit::callStaticMethod( 'FileRepoStatus', 'newFatal', $params );
}
/**
@@ -624,6 +611,7 @@ abstract class FileRepo {
* STUB
*
* @param $title Title of image
+ * @return Bool
*/
function checkRedirect( $title ) {
return false;
@@ -658,11 +646,7 @@ abstract class FileRepo {
return null;
}
// 'shared-repo-name-wikimediacommons' is used when $wgUseInstantCommons = true
- $repoName = wfMsg( 'shared-repo-name-' . $this->name );
- if ( !wfEmptyMsg( 'shared-repo-name-' . $this->name, $repoName ) ) {
- return $repoName;
- }
- return wfMsg( 'shared-repo' );
+ return wfMessageFallback( 'shared-repo-name-' . $this->name, 'shared-repo' )->text();
}
/**
@@ -696,9 +680,11 @@ abstract class FileRepo {
array_unshift( $args, 'filerepo', $this->getName() );
return call_user_func_array( 'wfMemcKey', $args );
}
-
+
/**
* Get an UploadStash associated with this repo.
+ *
+ * @return UploadStash
*/
function getUploadStash() {
return new UploadStash( $this );
diff --git a/includes/filerepo/FileRepoStatus.php b/includes/filerepo/FileRepoStatus.php
index 161284c0..4eea9030 100644
--- a/includes/filerepo/FileRepoStatus.php
+++ b/includes/filerepo/FileRepoStatus.php
@@ -13,6 +13,10 @@
class FileRepoStatus extends Status {
/**
* Factory function for fatal errors
+ *
+ * @param $repo FileRepo
+ *
+ * @return FileRepoStatus
*/
static function newFatal( $repo /*, parameters...*/ ) {
$params = array_slice( func_get_args(), 1 );
@@ -22,12 +26,20 @@ class FileRepoStatus extends Status {
return $result;
}
+ /**
+ * @param $repo FileRepo
+ * @param $value
+ * @return FileRepoStatus
+ */
static function newGood( $repo = false, $value = null ) {
$result = new self( $repo );
$result->value = $value;
return $result;
}
+ /**
+ * @param $repo FileRepo
+ */
function __construct( $repo = false ) {
if ( $repo ) {
$this->cleanCallback = $repo->getErrorCleanupFunction();
diff --git a/includes/filerepo/ForeignAPIFile.php b/includes/filerepo/ForeignAPIFile.php
index 56fed75e..53c4a3bd 100644
--- a/includes/filerepo/ForeignAPIFile.php
+++ b/includes/filerepo/ForeignAPIFile.php
@@ -15,7 +15,13 @@
class ForeignAPIFile extends File {
private $mExists;
-
+
+ /**
+ * @param $title
+ * @param $repo ForeignApiRepo
+ * @param $info
+ * @param bool $exists
+ */
function __construct( $title, $repo, $info, $exists = false ) {
parent::__construct( $title, $repo );
$this->mInfo = $info;
@@ -23,7 +29,6 @@ class ForeignAPIFile extends File {
}
/**
- * @static
* @param $title Title
* @param $repo ForeignApiRepo
* @return ForeignAPIFile|null
@@ -32,7 +37,9 @@ class ForeignAPIFile extends File {
$data = $repo->fetchImageQuery( array(
'titles' => 'File:' . $title->getDBKey(),
'iiprop' => self::getProps(),
- 'prop' => 'imageinfo' ) );
+ 'prop' => 'imageinfo',
+ 'iimetadataversion' => MediaHandler::getMetadataVersion()
+ ) );
$info = $repo->getImageInfo( $data );
@@ -76,20 +83,26 @@ class ForeignAPIFile extends File {
// show icon
return parent::transform( $params, $flags );
}
+
+ // Note, the this->canRender() check above implies
+ // that we have a handler, and it can do makeParamString.
+ $otherParams = $this->handler->makeParamString( $params );
+
$thumbUrl = $this->repo->getThumbUrlFromCache(
$this->getName(),
isset( $params['width'] ) ? $params['width'] : -1,
- isset( $params['height'] ) ? $params['height'] : -1 );
+ isset( $params['height'] ) ? $params['height'] : -1,
+ $otherParams );
return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );
}
// Info we can get from API...
public function getWidth( $page = 1 ) {
- return intval( @$this->mInfo['width'] );
+ return isset( $this->mInfo['width'] ) ? intval( $this->mInfo['width'] ) : 0;
}
public function getHeight( $page = 1 ) {
- return intval( @$this->mInfo['height'] );
+ return isset( $this->mInfo['height'] ) ? intval( $this->mInfo['height'] ) : 0;
}
public function getMetadata() {
@@ -148,7 +161,7 @@ class ForeignAPIFile extends File {
return $this->mInfo['mime'];
}
- /// @todo Fixme: may guess wrong on file types that can be eg audio or video
+ /// @todo FIXME: May guess wrong on file types that can be eg audio or video
function getMediaType() {
$magic = MimeMagic::singleton();
return $magic->getMediaType( null, $this->getMimeType() );
@@ -182,7 +195,7 @@ class ForeignAPIFile extends File {
$handle = opendir( $dir );
if ( $handle ) {
while ( false !== ( $file = readdir($handle) ) ) {
- if ( $file{0} != '.' ) {
+ if ( $file[0] != '.' ) {
$files[] = $file;
}
}
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index e4188d6b..502b8c1d 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -25,13 +25,13 @@ 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";
+ const VERSION = "2.1";
var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' );
/* Check back with Commons after a day */
- var $apiThumbCacheExpiry = 86400;
+ var $apiThumbCacheExpiry = 86400; /* 24*60*60 */
/* Redownload thumbnail files after a month */
- var $fileCacheExpiry = 2629743;
+ var $fileCacheExpiry = 2592000; /* 86400*30 */
/* Local image directory */
var $directory;
var $thumbDir;
@@ -75,6 +75,8 @@ class ForeignAPIRepo extends FileRepo {
/**
* Per docs in FileRepo, this needs to return false if we don't support versioned
* files. Well, we don't.
+ *
+ * @return File
*/
function newFile( $title, $time = false ) {
if ( $time ) {
@@ -83,26 +85,34 @@ class ForeignAPIRepo extends FileRepo {
return parent::newFile( $title, $time );
}
-/**
- * No-ops
- */
+ /**
+ * No-ops
+ */
+
function storeBatch( $triplets, $flags = 0 ) {
return false;
}
+
function storeTemp( $originalName, $srcPath ) {
return false;
}
+
function append( $srcPath, $toAppendPath, $flags = 0 ){
return false;
}
+
+ function appendFinish( $toAppendPath ){
+ return false;
+ }
+
function publishBatch( $triplets, $flags = 0 ) {
return false;
}
+
function deleteBatch( $sourceDestPairs ) {
return false;
}
-
function fileExistsBatch( $files, $flags = 0 ) {
$results = array();
foreach ( $files as $k => $f ) {
@@ -110,7 +120,7 @@ class ForeignAPIRepo extends FileRepo {
$results[$k] = true;
unset( $files[$k] );
} elseif( self::isVirtualUrl( $f ) ) {
- # TODO! FIXME! We need to be able to handle virtual
+ # @todo FIXME: We need to be able to handle virtual
# URLs better, at least when we know they refer to the
# same repo.
$results[$k] = false;
@@ -129,6 +139,7 @@ class ForeignAPIRepo extends FileRepo {
}
return $results;
}
+
function getFileProps( $virtualUrl ) {
return false;
}
@@ -197,12 +208,13 @@ class ForeignAPIRepo extends FileRepo {
return $ret;
}
- function getThumbUrl( $name, $width=-1, $height=-1, &$result=NULL ) {
+ function getThumbUrl( $name, $width = -1, $height = -1, &$result = null, $otherParams = '' ) {
$data = $this->fetchImageQuery( array(
'titles' => 'File:' . $name,
'iiprop' => 'url|timestamp',
'iiurlwidth' => $width,
'iiurlheight' => $height,
+ 'iiurlparam' => $otherParams,
'prop' => 'imageinfo' ) );
$info = $this->getImageInfo( $data );
@@ -215,7 +227,7 @@ class ForeignAPIRepo extends FileRepo {
}
}
- /*
+ /**
* Return the imageurl from cache if possible
*
* If the url has been requested today, get it from cache
@@ -224,15 +236,17 @@ class ForeignAPIRepo extends FileRepo {
* @param $name String is a dbkey form of a title
* @param $width
* @param $height
+ * @param String $param Other rendering parameters (page number, etc) from handler's makeParamString.
*/
- function getThumbUrlFromCache( $name, $width, $height ) {
+ function getThumbUrlFromCache( $name, $width, $height, $params="" ) {
global $wgMemc;
if ( !$this->canCacheThumbs() ) {
- return $this->getThumbUrl( $name, $width, $height );
+ $result = null; // can't pass "null" by reference, but it's ok as default value
+ return $this->getThumbUrl( $name, $width, $height, $result, $params );
}
$key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $name );
- $sizekey = "$width:$height";
+ $sizekey = "$width:$height:$params";
/* Get the array of urls that we already know */
$knownThumbUrls = $wgMemc->get($key);
@@ -241,14 +255,15 @@ class ForeignAPIRepo extends FileRepo {
$knownThumbUrls = array();
} else {
if( isset( $knownThumbUrls[$sizekey] ) ) {
- wfDebug("Got thumburl from local cache. {$knownThumbUrls[$sizekey]} \n");
+ wfDebug( __METHOD__ . ': Got thumburl from local cache: ' .
+ "{$knownThumbUrls[$sizekey]} \n");
return $knownThumbUrls[$sizekey];
}
/* This size is not yet known */
}
$metadata = null;
- $foreignUrl = $this->getThumbUrl( $name, $width, $height, $metadata );
+ $foreignUrl = $this->getThumbUrl( $name, $width, $height, $metadata, $params );
if( !$foreignUrl ) {
wfDebug( __METHOD__ . " Could not find thumburl\n" );
@@ -273,7 +288,7 @@ class ForeignAPIRepo extends FileRepo {
$diff = abs( $modified - $current );
if( $remoteModified < $modified && $diff < $this->fileCacheExpiry ) {
/* Use our current and already downloaded thumbnail */
- $knownThumbUrls["$width:$height"] = $localUrl;
+ $knownThumbUrls[$sizekey] = $localUrl;
$wgMemc->set( $key, $knownThumbUrls, $this->apiThumbCacheExpiry );
return $localUrl;
}
@@ -291,7 +306,7 @@ class ForeignAPIRepo extends FileRepo {
}
}
- # FIXME: Delete old thumbs that aren't being used. Maintenance script?
+ # @todo FIXME: Delete old thumbs that aren't being used. Maintenance script?
wfSuppressWarnings();
if( !file_put_contents( $localFilename, $thumb ) ) {
wfRestoreWarnings();
@@ -355,7 +370,7 @@ class ForeignAPIRepo extends FileRepo {
public static function httpGet( $url, $timeout = 'default', $options = array() ) {
$options['timeout'] = $timeout;
/* Http::get */
- $url = wfExpandUrl( $url );
+ $url = wfExpandUrl( $url, PROTO_HTTP );
wfDebug( "ForeignAPIRepo: HTTP GET: $url\n" );
$options['method'] = "GET";
diff --git a/includes/filerepo/ForeignDBFile.php b/includes/filerepo/ForeignDBFile.php
index 5f04ea73..09bee39c 100644
--- a/includes/filerepo/ForeignDBFile.php
+++ b/includes/filerepo/ForeignDBFile.php
@@ -12,6 +12,13 @@
* @ingroup FileRepo
*/
class ForeignDBFile extends LocalFile {
+
+ /**
+ * @param $title
+ * @param $repo
+ * @param $unused
+ * @return ForeignDBFile
+ */
static function newFromTitle( $title, $repo, $unused = null ) {
return new self( $title, $repo );
}
@@ -19,6 +26,11 @@ class ForeignDBFile extends LocalFile {
/**
* Create a ForeignDBFile from a title
* Do not call this except from inside a repo class.
+ *
+ * @param $row
+ * @param $repo
+ *
+ * @return ForeignDBFile
*/
static function newFromRow( $row, $repo ) {
$title = Title::makeTitle( NS_FILE, $row->img_name );
@@ -35,21 +47,30 @@ class ForeignDBFile extends LocalFile {
$watch = false, $timestamp = false ) {
$this->readOnlyError();
}
+
function restore( $versions = array(), $unsuppress = false ) {
$this->readOnlyError();
}
+
function delete( $reason, $suppress = false ) {
$this->readOnlyError();
}
+
function move( $target ) {
$this->readOnlyError();
}
-
+
+ /**
+ * @return string
+ */
function getDescriptionUrl() {
// Restore remote behaviour
return File::getDescriptionUrl();
}
+ /**
+ * @return string
+ */
function getDescriptionText() {
// Restore remote behaviour
return File::getDescriptionText();
diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php
index a756703f..0311ebcd 100644
--- a/includes/filerepo/ForeignDBRepo.php
+++ b/includes/filerepo/ForeignDBRepo.php
@@ -35,14 +35,14 @@ class ForeignDBRepo extends LocalRepo {
function getMasterDB() {
if ( !isset( $this->dbConn ) ) {
- $this->dbConn = DatabaseBase::newFromType( $this->dbType,
+ $this->dbConn = DatabaseBase::factory( $this->dbType,
array(
'host' => $this->dbServer,
'user' => $this->dbUser,
'password' => $this->dbPassword,
'dbname' => $this->dbName,
'flags' => $this->dbFlags,
- 'tableprefix' => $this->tablePrefix
+ 'tablePrefix' => $this->tablePrefix
)
);
}
diff --git a/includes/filerepo/Image.php b/includes/filerepo/Image.php
deleted file mode 100644
index 59a07ef9..00000000
--- a/includes/filerepo/Image.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-/**
- * Backward compatibility code for MW < 1.11
- *
- * @file
- */
-
-/**
- * Backwards compatibility class
- *
- * @deprecated. Will be removed in 1.18!
- * @ingroup FileRepo
- */
-class Image extends LocalFile {
- function __construct( $title ) {
- wfDeprecated( __METHOD__ );
- $repo = RepoGroup::singleton()->getLocalRepo();
- parent::__construct( $title, $repo );
- }
-
- /**
- * Wrapper for wfFindFile(), for backwards-compatibility only
- * Do not use in core code.
- * @deprecated
- */
- static function newFromTitle( $title, $repo, $time = null ) {
- wfDeprecated( __METHOD__ );
- $img = wfFindFile( $title, array( 'time' => $time ) );
- if ( !$img ) {
- $img = wfLocalFile( $title );
- }
- return $img;
- }
-
- /**
- * Wrapper for wfFindFile(), for backwards-compatibility only.
- * Do not use in core code.
- *
- * @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
- */
- static function newFromName( $name ) {
- wfDeprecated( __METHOD__ );
- $title = Title::makeTitleSafe( NS_FILE, $name );
- if ( is_object( $title ) ) {
- $img = wfFindFile( $title );
- if ( !$img ) {
- $img = wfLocalFile( $title );
- }
- return $img;
- } else {
- return null;
- }
- }
-
- /**
- * Return the URL of an image, provided its name.
- *
- * Backwards-compatibility for extensions.
- * Note that fromSharedDirectory will only use the shared path for files
- * that actually exist there now, and will return local paths otherwise.
- *
- * @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
- */
- static function imageUrl( $name, $fromSharedDirectory = false ) {
- wfDeprecated( __METHOD__ );
- $image = null;
- if( $fromSharedDirectory ) {
- $image = wfFindFile( $name );
- }
- if( !$image ) {
- $image = wfLocalFile( $name );
- }
- return $image->getUrl();
- }
-}
diff --git a/includes/filerepo/LocalFile.php b/includes/filerepo/LocalFile.php
index 5489ecb2..14da9122 100644
--- a/includes/filerepo/LocalFile.php
+++ b/includes/filerepo/LocalFile.php
@@ -33,7 +33,7 @@ class LocalFile extends File {
* @private
*/
var
- $fileExists, # does the file file exist on disk? (loadFromXxx)
+ $fileExists, # does the file exist on disk? (loadFromXxx)
$historyLine, # Number of line to return by nextHistoryLine() (constructor)
$historyRes, # result of the query for the file's history (nextHistoryLine)
$width, # \
@@ -63,6 +63,12 @@ class LocalFile extends File {
* Do not call this except from inside a repo class.
*
* Note: $unused param is only here to avoid an E_STRICT
+ *
+ * @param $title
+ * @param $repo
+ * @param $unused
+ *
+ * @return LocalFile
*/
static function newFromTitle( $title, $repo, $unused = null ) {
return new self( $title, $repo );
@@ -71,6 +77,11 @@ class LocalFile extends File {
/**
* Create a LocalFile from a title
* Do not call this except from inside a repo class.
+ *
+ * @param $row
+ * @param $repo
+ *
+ * @return LocalFile
*/
static function newFromRow( $row, $repo ) {
$title = Title::makeTitle( NS_FILE, $row->img_name );
@@ -83,17 +94,22 @@ class LocalFile extends File {
/**
* Create a LocalFile from a SHA-1 key
* Do not call this except from inside a repo class.
+ *
+ * @param $sha1 string base-36 SHA-1
+ * @param $repo LocalRepo
+ * @param string|bool $timestamp MW_timestamp (optional)
+ *
+ * @return bool|LocalFile
*/
static function newFromKey( $sha1, $repo, $timestamp = false ) {
- $conds = array( 'img_sha1' => $sha1 );
+ $dbr = $repo->getSlaveDB();
+ $conds = array( 'img_sha1' => $sha1 );
if ( $timestamp ) {
- $conds['img_timestamp'] = $timestamp;
+ $conds['img_timestamp'] = $dbr->timestamp( $timestamp );
}
- $dbr = $repo->getSlaveDB();
$row = $dbr->selectRow( 'image', self::selectFields(), $conds, __METHOD__ );
-
if ( $row ) {
return self::newFromRow( $row, $repo );
} else {
@@ -333,6 +349,7 @@ class LocalFile extends File {
* Upgrade a row if it needs it
*/
function maybeUpgradeRow() {
+ global $wgUpdateCompatibleMetadata;
if ( wfReadOnly() ) {
return;
}
@@ -344,9 +361,14 @@ class LocalFile extends File {
$this->upgraded = true;
} else {
$handler = $this->getHandler();
- if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
- $this->upgradeRow();
- $this->upgraded = true;
+ if ( $handler ) {
+ $validity = $handler->isMetadataValid( $this, $this->metadata );
+ if ( $validity === MediaHandler::METADATA_BAD
+ || ( $validity === MediaHandler::METADATA_COMPATIBLE && $wgUpdateCompatibleMetadata )
+ ) {
+ $this->upgradeRow();
+ $this->upgraded = true;
+ }
}
}
}
@@ -540,8 +562,8 @@ class LocalFile extends File {
/** isTrustedFile inherited */
/**
- * Returns true if the file file exists on disk.
- * @return boolean Whether file file exist on disk.
+ * Returns true if the file exists on disk.
+ * @return boolean Whether file exist on disk.
*/
public function exists() {
$this->load();
@@ -552,7 +574,6 @@ class LocalFile extends File {
/** getUnscaledThumb inherited */
/** thumbName inherited */
/** createThumb inherited */
- /** getThumbnail inherited */
/** transform inherited */
/**
@@ -591,12 +612,19 @@ class LocalFile extends File {
/**
* Get all thumbnail names previously generated for this file
+ * @param $archiveName string|false Name of an archive file
+ * @return array first element is the base dir, then files in that base dir.
*/
- function getThumbnails() {
+ function getThumbnails( $archiveName = false ) {
$this->load();
+ if ( $archiveName ) {
+ $dir = $this->getArchiveThumbPath( $archiveName );
+ } else {
+ $dir = $this->getThumbPath();
+ }
$files = array();
- $dir = $this->getThumbPath();
+ $files[] = $dir;
if ( is_dir( $dir ) ) {
$handle = opendir( $dir );
@@ -633,6 +661,11 @@ class LocalFile extends File {
$hashedName = md5( $this->getName() );
$oldKey = $this->repo->getSharedCacheKey( 'oldfile', $hashedName );
+ // Must purge thumbnails for old versions too! bug 30192
+ foreach( $this->getHistory() as $oldFile ) {
+ $oldFile->purgeThumbnails();
+ }
+
if ( $oldKey ) {
$wgMemc->delete( $oldKey );
}
@@ -653,32 +686,76 @@ class LocalFile extends File {
}
/**
- * Delete cached transformed files
+ * Delete cached transformed files for archived files
+ * @param $archiveName string name of the archived file
*/
- function purgeThumbnails() {
+ function purgeOldThumbnails( $archiveName ) {
global $wgUseSquid;
+ // get a list of old thumbnails and URLs
+ $files = $this->getThumbnails( $archiveName );
+ $dir = array_shift( $files );
+ $this->purgeThumbList( $dir, $files );
- // Delete thumbnails
- $files = $this->getThumbnails();
- $dir = $this->getThumbPath();
- $urls = array();
+ // Directory should be empty, delete it too. This will probably suck on
+ // something like NFS or if the directory isn't actually empty, so hide
+ // the warnings :D
+ wfSuppressWarnings();
+ if( !rmdir( $dir ) ) {
+ wfDebug( __METHOD__ . ": unable to remove archive directory: $dir\n" );
+ }
+ wfRestoreWarnings();
- 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
- if ( strpos( $file, $this->getName() ) !== false ) {
- $url = $this->getThumbUrl( $file );
- $urls[] = $url;
- @unlink( "$dir/$file" );
+ // Purge the squid
+ if ( $wgUseSquid ) {
+ $urls = array();
+ foreach( $files as $file ) {
+ $urls[] = $this->getArchiveThumbUrl( $archiveName, $file );
}
+ SquidUpdate::purge( $urls );
}
+ }
+
+
+ /**
+ * Delete cached transformed files for the current version only.
+ */
+ function purgeThumbnails() {
+ global $wgUseSquid;
+ // get a list of thumbnails and URLs
+ $files = $this->getThumbnails();
+ $dir = array_shift( $files );
+ $this->purgeThumbList( $dir, $files );
// Purge the squid
if ( $wgUseSquid ) {
+ $urls = array();
+ foreach( $files as $file ) {
+ $urls[] = $this->getThumbUrl( $file );
+ }
SquidUpdate::purge( $urls );
}
}
+ /**
+ * Delete a list of thumbnails visible at urls
+ * @param $dir string base dir of the files.
+ * @param $files array of strings: relative filenames (to $dir)
+ */
+ function purgeThumbList($dir, $files) {
+ global $wgExcludeFromThumbnailPurge;
+
+ wfDebug( __METHOD__ . ": " . var_export( $files, true ) . "\n" );
+ 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
+ if ( strpos( $file, $this->getName() ) !== false ) {
+ wfSuppressWarnings();
+ unlink( "$dir/$file" );
+ wfRestoreWarnings();
+ }
+ }
+ }
+
/** purgeDescription inherited */
/** purgeEverything inherited */
@@ -786,7 +863,6 @@ class LocalFile extends File {
/** getRel inherited */
/** getUrlRel inherited */
/** getArchiveRel inherited */
- /** getThumbRel inherited */
/** getArchivePath inherited */
/** getThumbPath inherited */
/** getArchiveUrl inherited */
@@ -828,7 +904,6 @@ class LocalFile extends File {
/**
* Record a file upload in the upload log and the image table
- * @deprecated use upload()
*/
function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
$watch = false, $timestamp = false )
@@ -844,7 +919,6 @@ class LocalFile extends File {
$wgUser->addWatch( $this->getTitle() );
}
return true;
-
}
/**
@@ -883,7 +957,7 @@ class LocalFile extends File {
# Fail now if the file isn't there
if ( !$this->fileExists ) {
- wfDebug( __METHOD__ . ": File " . $this->getPath() . " went missing!\n" );
+ wfDebug( __METHOD__ . ": File " . $this->getRel() . " went missing!\n" );
return false;
}
@@ -961,8 +1035,10 @@ class LocalFile extends File {
} else {
# This is a new file
# Update the image count
+ $dbw->begin();
$site_stats = $dbw->tableName( 'site_stats' );
$dbw->query( "UPDATE $site_stats SET ss_images=ss_images+1", __METHOD__ );
+ $dbw->commit();
}
$descTitle = $this->getTitle();
@@ -1036,16 +1112,32 @@ class LocalFile extends File {
*
* @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
+ * File::DELETE_SOURCE Delete the source file, i.e. move rather than copy
* @return FileRepoStatus object. On success, the value member contains the
* archive name, or an empty string if it was a new file.
*/
function publish( $srcPath, $flags = 0 ) {
+ return $this->publishTo( $srcPath, $this->getRel(), $flags );
+ }
+
+ /**
+ * Move or copy a file to a specified location. 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 $srcPath String: local filesystem path to the source image
+ * @param $dstRel String: target relative path
+ * @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
+ * archive name, or an empty string if it was a new file.
+ */
+ function publishTo( $srcPath, $dstRel, $flags = 0 ) {
$this->lock();
- $dstRel = $this->getRel();
- $archiveName = gmdate( 'YmdHis' ) . '!' . $this->getName();
+ $archiveName = wfTimestamp( TS_MW ) . '!'. $this->getName();
$archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
$flags = $flags & File::DELETE_SOURCE ? LocalRepo::DELETE_SOURCE : 0;
$status = $this->repo->publish( $srcPath, $dstRel, $archiveRel, $flags );
@@ -1130,6 +1222,7 @@ class LocalFile extends File {
array( 'oi_name' => $this->getName() ) );
foreach ( $result as $row ) {
$batch->addOld( $row->oi_archive_name );
+ $this->purgeOldThumbnails( $row->oi_archive_name );
}
$status = $batch->execute();
@@ -1164,6 +1257,7 @@ class LocalFile extends File {
$batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$batch->addOld( $archiveName );
+ $this->purgeOldThumbnails( $archiveName );
$status = $batch->execute();
$this->unlock();
@@ -1198,7 +1292,7 @@ class LocalFile extends File {
$status = $batch->execute();
- if ( !$status->ok ) {
+ if ( !$status->isGood() ) {
return $status;
}
@@ -1312,7 +1406,13 @@ class LocalFile extends File {
* @ingroup FileRepo
*/
class LocalFileDeleteBatch {
- var $file, $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
+
+ /**
+ * @var LocalFile
+ */
+ var $file;
+
+ var $reason, $srcRels = array(), $archiveUrls = array(), $deletionBatch, $suppress;
var $status;
function __construct( File $file, $reason = '', $suppress = false ) {
@@ -1344,7 +1444,7 @@ class LocalFileDeleteBatch {
return array( $oldRels, $deleteCurrent );
}
- /*protected*/ function getHashes() {
+ protected function getHashes() {
$hashes = array();
list( $oldRels, $deleteCurrent ) = $this->getOldRels();
@@ -1623,7 +1723,12 @@ class LocalFileDeleteBatch {
* @ingroup FileRepo
*/
class LocalFileRestoreBatch {
- var $file, $cleanupBatch, $ids, $all, $unsuppress = false;
+ /**
+ * @var LocalFile
+ */
+ var $file;
+
+ var $cleanupBatch, $ids, $all, $unsuppress = false;
function __construct( File $file, $unsuppress = false ) {
$this->file = $file;
@@ -1827,9 +1932,11 @@ class LocalFileRestoreBatch {
$storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
$status->merge( $storeStatus );
- if ( !$status->ok ) {
- // Store batch returned a critical error -- this usually means nothing was stored
- // Stop now and return an error
+ if ( !$status->isGood() ) {
+ // Even if some files could be copied, fail entirely as that is the
+ // easiest thing to do without data loss
+ $this->cleanupFailedBatch( $storeStatus, $storeBatch );
+ $status->ok = false;
$this->file->unlock();
return $status;
@@ -1934,6 +2041,27 @@ class LocalFileRestoreBatch {
return $status;
}
+
+ /**
+ * Cleanup a failed batch. The batch was only partially successful, so
+ * rollback by removing all items that were succesfully copied.
+ *
+ * @param Status $storeStatus
+ * @param array $storeBatch
+ */
+ function cleanupFailedBatch( $storeStatus, $storeBatch ) {
+ $cleanupBatch = array();
+
+ foreach ( $storeStatus->success as $i => $success ) {
+ // Check if this item of the batch was successfully copied
+ if ( $success ) {
+ // Item was successfully copied and needs to be removed again
+ // Extract ($dstZone, $dstRel) from the batch
+ $cleanupBatch[] = array( $storeBatch[$i][1], $storeBatch[$i][2] );
+ }
+ }
+ $this->file->repo->cleanupBatch( $cleanupBatch );
+ }
}
# ------------------------------------------------------------------------------
@@ -1983,14 +2111,14 @@ class LocalFileMoveBatch {
$bits = explode( '!', $oldName, 2 );
if ( count( $bits ) != 2 ) {
- wfDebug( "Invalid old file name: $oldName \n" );
+ wfDebug( "Old file name missing !: '$oldName' \n" );
continue;
}
list( $timestamp, $filename ) = $bits;
if ( $this->oldName != $filename ) {
- wfDebug( "Invalid old file name: $oldName \n" );
+ wfDebug( "Old file name doesn't match: '$oldName' \n" );
continue;
}
@@ -2017,15 +2145,31 @@ class LocalFileMoveBatch {
$triplets = $this->getMoveTriplets();
$triplets = $this->removeNonexistentFiles( $triplets );
- $statusDb = $this->doDBUpdates();
- 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() ) {
+ // Copy the files into their new location
+ $statusMove = $repo->storeBatch( $triplets );
+ wfDebugLog( 'imagemove', "Moved files for {$this->file->name}: {$statusMove->successCount} successes, {$statusMove->failCount} failures" );
+ if ( !$statusMove->isGood() ) {
wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
+ $this->cleanupTarget( $triplets );
+ $statusMove->ok = false;
+ return $statusMove;
+ }
+
+ $this->db->begin();
+ $statusDb = $this->doDBUpdates();
+ wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
+ if ( !$statusDb->isGood() ) {
$this->db->rollback();
+ // Something went wrong with the DB updates, so remove the target files
+ $this->cleanupTarget( $triplets );
+ $statusDb->ok = false;
+ return $statusDb;
}
+ $this->db->commit();
+
+ // Everything went ok, remove the source files
+ $this->cleanupSource( $triplets );
$status->merge( $statusDb );
$status->merge( $statusMove );
@@ -2056,6 +2200,8 @@ class LocalFileMoveBatch {
$status->successCount++;
} else {
$status->failCount++;
+ $status->fatal( 'imageinvalidfilename' );
+ return $status;
}
// Update old images
@@ -2073,6 +2219,9 @@ class LocalFileMoveBatch {
$total = $this->oldCount;
$status->successCount += $affected;
$status->failCount += $total - $affected;
+ if ( $status->failCount ) {
+ $status->error( 'imageinvalidfilename' );
+ }
return $status;
}
@@ -2117,4 +2266,32 @@ class LocalFileMoveBatch {
return $filteredTriplets;
}
+
+ /**
+ * Cleanup a partially moved array of triplets by deleting the target
+ * files. Called if something went wrong half way.
+ */
+ function cleanupTarget( $triplets ) {
+ // Create dest pairs from the triplets
+ $pairs = array();
+ foreach ( $triplets as $triplet ) {
+ $pairs[] = array( $triplet[1], $triplet[2] );
+ }
+
+ $this->file->repo->cleanupBatch( $pairs );
+ }
+
+ /**
+ * Cleanup a fully moved array of triplets by deleting the source files.
+ * Called at the end of the move process if everything else went ok.
+ */
+ function cleanupSource( $triplets ) {
+ // Create source file names from the triplets
+ $files = array();
+ foreach ( $triplets as $triplet ) {
+ $files[] = $triplet[0];
+ }
+
+ $this->file->repo->cleanupBatch( $files );
+ }
}
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
index 02883c53..9089f4d7 100644
--- a/includes/filerepo/LocalRepo.php
+++ b/includes/filerepo/LocalRepo.php
@@ -20,6 +20,11 @@ class LocalRepo extends FSRepo {
var $fileFromRowFactory = array( 'LocalFile', 'newFromRow' );
var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
+ /**
+ * @throws MWException
+ * @param $row
+ * @return File
+ */
function newFileFromRow( $row ) {
if ( isset( $row->img_name ) ) {
return call_user_func( $this->fileFromRowFactory, $row, $this );
@@ -30,6 +35,11 @@ class LocalRepo extends FSRepo {
}
}
+ /**
+ * @param $title
+ * @param $archiveName
+ * @return OldLocalFile
+ */
function newFromArchiveName( $title, $archiveName ) {
return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
}
@@ -39,13 +49,16 @@ class LocalRepo extends FSRepo {
* filearchive table. This needs to be done in the repo because it needs to
* interleave database locks with file operations, which is potentially a
* remote operation.
+ *
+ * @param $storageKeys array
+ *
* @return FileRepoStatus
*/
function cleanupDeletedBatch( $storageKeys ) {
$root = $this->getZonePath( 'deleted' );
$dbw = $this->getMasterDB();
$status = $this->newGood();
- $storageKeys = array_unique($storageKeys);
+ $storageKeys = array_unique( $storageKeys );
foreach ( $storageKeys as $key ) {
$hashPath = $this->getDeletedHashPath( $key );
$path = "$root/$hashPath$key";
@@ -54,8 +67,8 @@ class LocalRepo extends FSRepo {
array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
__METHOD__, array( 'FOR UPDATE' ) );
if( !$inuse ) {
- $sha1 = substr( $key, 0, strcspn( $key, '.' ) );
- $ext = substr( $key, strcspn($key,'.') + 1 );
+ $sha1 = self::getHashFromKey( $key );
+ $ext = substr( $key, strcspn( $key, '.' ) + 1 );
$ext = File::normalizeExtension($ext);
$inuse = $dbw->selectField( 'oldimage', '1',
array( 'oi_sha1' => $sha1,
@@ -65,7 +78,10 @@ class LocalRepo extends FSRepo {
}
if ( !$inuse ) {
wfDebug( __METHOD__ . ": deleting $key\n" );
- if ( !@unlink( $path ) ) {
+ wfSuppressWarnings();
+ $unlink = unlink( $path );
+ wfRestoreWarnings();
+ if ( !$unlink ) {
$status->error( 'undelete-cleanup-error', $path );
$status->failCount++;
}
@@ -77,6 +93,16 @@ class LocalRepo extends FSRepo {
}
return $status;
}
+
+ /**
+ * Gets the SHA1 hash from a storage key
+ *
+ * @param string $key
+ * @return string
+ */
+ public static function getHashFromKey( $key ) {
+ return strtok( $key, '.' );
+ }
/**
* Checks if there is a redirect named as $title
@@ -87,7 +113,7 @@ class LocalRepo extends FSRepo {
global $wgMemc;
if( is_string( $title ) ) {
- $title = Title::newFromTitle( $title );
+ $title = Title::newFromText( $title );
}
if( $title instanceof Title && $title->getNamespace() == NS_MEDIA ) {
$title = Title::makeTitle( NS_FILE, $title->getText() );
@@ -135,6 +161,7 @@ class LocalRepo extends FSRepo {
/**
* Function link Title::getArticleID().
* We can't say Title object, what database it should use, so we duplicate that function here.
+ * @param $title Title
*/
protected function getArticleID( $title ) {
if( !$title instanceof Title ) {
diff --git a/includes/filerepo/NullRepo.php b/includes/filerepo/NullRepo.php
index d5a1ee03..cac3e5d8 100644
--- a/includes/filerepo/NullRepo.php
+++ b/includes/filerepo/NullRepo.php
@@ -23,6 +23,9 @@ class NullRepo extends FileRepo {
function append( $srcPath, $toAppendPath, $flags = 0 ){
return false;
}
+ function appendFinish( $toAppendPath ){
+ return false;
+ }
function publishBatch( $triplets, $flags = 0 ) {
return false;
}
diff --git a/includes/filerepo/OldLocalFile.php b/includes/filerepo/OldLocalFile.php
index 9efe998f..bcb22c17 100644
--- a/includes/filerepo/OldLocalFile.php
+++ b/includes/filerepo/OldLocalFile.php
@@ -1,6 +1,6 @@
<?php
/**
- * Old file in the in the oldimage table
+ * Old file in the oldimage table
*
* @file
* @ingroup FileRepo
@@ -19,8 +19,9 @@ class OldLocalFile extends LocalFile {
static function newFromTitle( $title, $repo, $time = null ) {
# The null default value is only here to avoid an E_STRICT
- if( $time === null )
+ if ( $time === null ) {
throw new MWException( __METHOD__.' got null for $time parameter' );
+ }
return new self( $title, $repo, $time, null );
}
@@ -34,15 +35,27 @@ class OldLocalFile extends LocalFile {
$file->loadFromRow( $row, 'oi_' );
return $file;
}
-
+
+ /**
+ * Create a OldLocalFile from a SHA-1 key
+ * Do not call this except from inside a repo class.
+ *
+ * @param $sha1 string base-36 SHA-1
+ * @param $repo LocalRepo
+ * @param string|bool $timestamp MW_timestamp (optional)
+ *
+ * @return bool|OldLocalFile
+ */
static function newFromKey( $sha1, $repo, $timestamp = false ) {
+ $dbr = $repo->getSlaveDB();
+
$conds = array( 'oi_sha1' => $sha1 );
- if( $timestamp ) {
- $conds['oi_timestamp'] = $timestamp;
+ if ( $timestamp ) {
+ $conds['oi_timestamp'] = $dbr->timestamp( $timestamp );
}
- $dbr = $repo->getSlaveDB();
+
$row = $dbr->selectRow( 'oldimage', self::selectFields(), $conds, __METHOD__ );
- if( $row ) {
+ if ( $row ) {
return self::newFromRow( $row, $repo );
} else {
return false;
@@ -205,4 +218,77 @@ class OldLocalFile extends LocalFile {
$this->load();
return Revision::userCanBitfield( $this->deleted, $field );
}
+
+ /**
+ * Upload a file directly into archive. Generally for Special:Import.
+ *
+ * @param $srcPath string File system path of the source file
+ * @param $archiveName string Full archive name of the file, in the form
+ * $timestamp!$filename, where $filename must match $this->getName()
+ *
+ * @return FileRepoStatus
+ */
+ function uploadOld( $srcPath, $archiveName, $timestamp, $comment, $user, $flags = 0 ) {
+ $this->lock();
+
+ $dstRel = 'archive/' . $this->getHashPath() . $archiveName;
+ $status = $this->publishTo( $srcPath, $dstRel,
+ $flags & File::DELETE_SOURCE ? FileRepo::DELETE_SOURCE : 0
+ );
+
+ if ( $status->isGood() ) {
+ if ( !$this->recordOldUpload( $srcPath, $archiveName, $timestamp, $comment, $user ) ) {
+ $status->fatal( 'filenotfound', $srcPath );
+ }
+ }
+
+ $this->unlock();
+
+ return $status;
+ }
+
+ /**
+ * Record a file upload in the oldimage table, without adding log entries.
+ *
+ * @param $srcPath string File system path to the source file
+ * @param $archiveName string The archive name of the file
+ * @param $comment string Upload comment
+ * @param $user User User who did this upload
+ * @return bool
+ */
+ function recordOldUpload( $srcPath, $archiveName, $timestamp, $comment, $user ) {
+ $dbw = $this->repo->getMasterDB();
+ $dbw->begin();
+
+ $dstPath = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
+ $props = self::getPropsFromPath( $dstPath );
+ if ( !$props['fileExists'] ) {
+ return false;
+ }
+
+ $dbw->insert( 'oldimage',
+ array(
+ 'oi_name' => $this->getName(),
+ 'oi_archive_name' => $archiveName,
+ 'oi_size' => $props['size'],
+ 'oi_width' => intval( $props['width'] ),
+ 'oi_height' => intval( $props['height'] ),
+ 'oi_bits' => $props['bits'],
+ 'oi_timestamp' => $dbw->timestamp( $timestamp ),
+ 'oi_description' => $comment,
+ 'oi_user' => $user->getId(),
+ 'oi_user_text' => $user->getName(),
+ 'oi_metadata' => $props['metadata'],
+ 'oi_media_type' => $props['media_type'],
+ 'oi_major_mime' => $props['major_mime'],
+ 'oi_minor_mime' => $props['minor_mime'],
+ 'oi_sha1' => $props['sha1'],
+ ), __METHOD__
+ );
+
+ $dbw->commit();
+
+ return true;
+ }
+
}
diff --git a/includes/filerepo/README b/includes/filerepo/README
index d3aea9f0..db46ff8a 100644
--- a/includes/filerepo/README
+++ b/includes/filerepo/README
@@ -39,3 +39,21 @@ LocalRepo.php. LocalRepo provides only file access, and LocalFile provides
database access and higher-level functions such as cache management.
Tim Starling, June 2007
+
+Structure:
+
+File.php defines an abstract class File.
+ ForeignAPIFile.php extends File.
+ LocalFile.php extends File.
+ ForeignDBFile.php extends LocalFile
+ Image.php extends LocalFile
+ UnregisteredLocalFile.php extends File.
+FileRepo.php defined an abstract class FileRepo.
+ ForeignAPIRepo.php extends FileRepo
+ FSRepo extends FileRepo
+ LocalRepo.php extends FSRepo
+ ForeignDBRepo.php extends LocalRepo
+ ForeignDBViaLBRepo.php extends LocalRepo
+ NullRepo extends FileRepo
+
+Russ Nelson, March 2011
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index b9996941..d4875908 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -16,16 +16,26 @@
* @ingroup FileRepo
*/
class RepoGroup {
- var $localRepo, $foreignRepos, $reposInitialised = false;
+
+ /**
+ * @var LocalRepo
+ */
+ var $localRepo;
+
+ var $foreignRepos, $reposInitialised = false;
var $localInfo, $foreignInfo;
var $cache;
+ /**
+ * @var RepoGroup
+ */
protected static $instance;
const MAX_CACHE_SIZE = 1000;
/**
* Get a RepoGroup instance. At present only one instance of RepoGroup is
* needed in a MediaWiki invocation, this may change in the future.
+ * @return RepoGroup
*/
static function singleton() {
if ( self::$instance ) {
@@ -46,6 +56,8 @@ class RepoGroup {
/**
* Set the singleton instance to a given object
+ *
+ * @param $instance RepoGroup
*/
static function setSingleton( $instance ) {
self::$instance = $instance;
@@ -70,8 +82,8 @@ class RepoGroup {
* Search repositories for an image.
* You can also use wfFindFile() to do this.
*
- * @param $title Mixed: Title object or string
- * @param $options Associative array of options:
+ * @param $title Title|string Title object or string
+ * @param $options array Associative array of options:
* time: requested time for an archived image, or false for the
* current version. An image object will be returned which was
* created at the specified time.
@@ -101,7 +113,7 @@ class RepoGroup {
}
if ( $title->getNamespace() != NS_MEDIA && $title->getNamespace() != NS_FILE ) {
- throw new MWException( __METHOD__ . ' recieved an Title object with incorrect namespace' );
+ throw new MWException( __METHOD__ . ' received an Title object with incorrect namespace' );
}
# Check the cache
@@ -204,14 +216,44 @@ class RepoGroup {
return false;
}
+ /**
+ * Find an instance of the file with this key, created at the specified time
+ * Returns false if the file does not exist.
+ *
+ * @param $hash String base 36 SHA-1 hash
+ * @param $options Option array, same as findFile()
+ * @return File object or false if it is not found
+ */
+ function findFileFromKey( $hash, $options = array() ) {
+ if ( !$this->reposInitialised ) {
+ $this->initialiseRepos();
+ }
+
+ $file = $this->localRepo->findFileFromKey( $hash, $options );
+ if ( !$file ) {
+ foreach ( $this->foreignRepos as $repo ) {
+ $file = $repo->findFileFromKey( $hash, $options );
+ if ( $file ) break;
+ }
+ }
+ return $file;
+ }
+
+ /**
+ * Find all instances of files with this key
+ *
+ * @param $hash String base 36 SHA-1 hash
+ * @return Array of File objects
+ */
function findBySha1( $hash ) {
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
$result = $this->localRepo->findBySha1( $hash );
- foreach ( $this->foreignRepos as $repo )
+ foreach ( $this->foreignRepos as $repo ) {
$result = array_merge( $result, $repo->findBySha1( $hash ) );
+ }
return $result;
}
@@ -247,6 +289,8 @@ class RepoGroup {
/**
* Get the local repository, i.e. the one corresponding to the local image
* table. Files are typically uploaded to the local repository.
+ *
+ * @return LocalRepo
*/
function getLocalRepo() {
return $this->getRepo( 'local' );
@@ -307,7 +351,7 @@ class RepoGroup {
*/
function splitVirtualUrl( $url ) {
if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
- throw new MWException( __METHOD__.': unknown protoocl' );
+ throw new MWException( __METHOD__.': unknown protocol' );
}
$bits = explode( '/', substr( $url, 9 ), 3 );
diff --git a/includes/filerepo/UnregisteredLocalFile.php b/includes/filerepo/UnregisteredLocalFile.php
index 990a218c..2df9a9b5 100644
--- a/includes/filerepo/UnregisteredLocalFile.php
+++ b/includes/filerepo/UnregisteredLocalFile.php
@@ -19,16 +19,38 @@
* @ingroup FileRepo
*/
class UnregisteredLocalFile extends File {
- var $title, $path, $mime, $handler, $dims;
+ var $title, $path, $mime, $dims;
+ /**
+ * @var MediaHandler
+ */
+ var $handler;
+
+ /**
+ * @param $path
+ * @param $mime
+ * @return UnregisteredLocalFile
+ */
static function newFromPath( $path, $mime ) {
return new UnregisteredLocalFile( false, false, $path, $mime );
}
+ /**
+ * @param $title
+ * @param $repo
+ * @return UnregisteredLocalFile
+ */
static function newFromTitle( $title, $repo ) {
return new UnregisteredLocalFile( $title, $repo, false, false );
}
+ /**
+ * @throws MWException
+ * @param $title string
+ * @param $repo FSRepo
+ * @param $path string
+ * @param $mime string
+ */
function __construct( $title = false, $repo = false, $path = false, $mime = false ) {
if ( !( $title && $repo ) && !$path ) {
throw new MWException( __METHOD__.': not enough parameters, must specify title and repo, or a full path' );
diff --git a/includes/installer/CliInstaller.php b/includes/installer/CliInstaller.php
index 9e8fb2c5..2ae9d143 100644
--- a/includes/installer/CliInstaller.php
+++ b/includes/installer/CliInstaller.php
@@ -13,6 +13,7 @@
* @since 1.17
*/
class CliInstaller extends Installer {
+ private $specifiedScriptPath = false;
private $optionMap = array(
'dbtype' => 'wgDBtype',
@@ -45,6 +46,10 @@ class CliInstaller extends Installer {
parent::__construct();
+ if ( isset( $option['scriptpath'] ) ) {
+ $this->specifiedScriptPath = true;
+ }
+
foreach ( $this->optionMap as $opt => $global ) {
if ( isset( $option[$opt] ) ) {
$GLOBALS[$global] = $option[$opt];
@@ -81,7 +86,7 @@ class CliInstaller extends Installer {
$this->setVar( '_InstallUser',
$option['installdbuser'] );
$this->setVar( '_InstallPassword',
- $option['installdbpass'] );
+ isset( $option['installdbpass'] ) ? $option['installdbpass'] : "" );
}
if ( isset( $option['pass'] ) ) {
@@ -136,6 +141,8 @@ class CliInstaller extends Installer {
}
/**
+ * @param $params array
+ *
* @return string
*/
protected function getMessageText( $params ) {
@@ -168,4 +175,16 @@ class CliInstaller extends Installer {
exit;
}
}
+
+ public function envCheckPath( ) {
+ if ( !$this->specifiedScriptPath ) {
+ $this->showMessage( 'config-no-cli-uri', $this->getVar("wgScriptPath") );
+ }
+ return parent::envCheckPath();
+ }
+
+ public function dirIsExecutable( $dir, $url ) {
+ $this->showMessage( 'config-no-cli-uploads-check', $dir );
+ return false;
+ }
}
diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php
index 0da24f8e..aadd6b49 100644
--- a/includes/installer/DatabaseInstaller.php
+++ b/includes/installer/DatabaseInstaller.php
@@ -157,7 +157,7 @@ abstract class DatabaseInstaller {
$this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
$this->db->begin( __METHOD__ );
- $error = $this->db->sourceFile( $this->db->getSchema() );
+ $error = $this->db->sourceFile( $this->db->getSchemaPath() );
if( $error !== true ) {
$this->db->reportQueryError( $error, 0, '', __METHOD__ );
$this->db->rollback( __METHOD__ );
@@ -181,26 +181,9 @@ abstract class DatabaseInstaller {
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' ) );
+ DatabaseUpdater::newForDB( $this->db )->doUpdates( array( 'extensions' ) );
return $status;
}
@@ -361,6 +344,8 @@ abstract class DatabaseInstaller {
/**
* Get a labelled text box to configure a local variable.
+ *
+ * @return string
*/
public function getTextBox( $var, $label, $attribs = array(), $helpData = "" ) {
$name = $this->getName() . '_' . $var;
@@ -381,6 +366,8 @@ abstract class DatabaseInstaller {
/**
* Get a labelled password box to configure a local variable.
* Implements password hiding.
+ *
+ * @return string
*/
public function getPasswordBox( $var, $label, $attribs = array(), $helpData = "" ) {
$name = $this->getName() . '_' . $var;
@@ -400,6 +387,8 @@ abstract class DatabaseInstaller {
/**
* Get a labelled checkbox to configure a local boolean variable.
+ *
+ * @return string
*/
public function getCheckBox( $var, $label, $attribs = array(), $helpData = "" ) {
$name = $this->getName() . '_' . $var;
diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php
index 57d82ea3..89fb16eb 100644
--- a/includes/installer/DatabaseUpdater.php
+++ b/includes/installer/DatabaseUpdater.php
@@ -31,12 +31,6 @@ abstract class DatabaseUpdater {
protected $extensionUpdates = array();
/**
- * Used to hold schema files during installation process
- * @var array
- */
- protected $newExtensions = array();
-
- /**
* Handle to the database subclass
*
* @var DatabaseBase
@@ -46,7 +40,8 @@ abstract class DatabaseUpdater {
protected $shared = false;
protected $postDatabaseUpdateMaintenance = array(
- 'DeleteDefaultMessages'
+ 'DeleteDefaultMessages',
+ 'FixExtLinksProtocolRelative',
);
/**
@@ -65,6 +60,7 @@ abstract class DatabaseUpdater {
} else {
$this->maintenance = new FakeMaintenance;
}
+ $this->maintenance->setDB( $db );
$this->initOldGlobals();
$this->loadExtensions();
wfRunHooks( 'LoadExtensionSchemaUpdates', array( $this ) );
@@ -176,23 +172,6 @@ abstract class DatabaseUpdater {
}
/**
- * 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
@@ -210,8 +189,8 @@ abstract class DatabaseUpdater {
*
* @param $what Array: what updates to perform
*/
- public function doUpdates( $what = array( 'core', 'extensions', 'purge' ) ) {
- global $wgVersion;
+ public function doUpdates( $what = array( 'core', 'extensions', 'purge', 'stats' ) ) {
+ global $wgLocalisationCacheConf, $wgVersion;
$what = array_flip( $what );
if ( isset( $what['core'] ) ) {
@@ -224,10 +203,14 @@ abstract class DatabaseUpdater {
$this->setAppliedUpdates( $wgVersion, $this->updates );
- if( isset( $what['purge'] ) ) {
+ if ( isset( $what['purge'] ) ) {
$this->purgeCache();
+
+ if ( $wgLocalisationCacheConf['manualRecache'] ) {
+ $this->rebuildLocalisationCache();
+ }
}
- if ( isset( $what['core'] ) ) {
+ if ( isset( $what['stats'] ) ) {
$this->checkStats();
}
}
@@ -254,6 +237,7 @@ abstract class DatabaseUpdater {
}
protected function setAppliedUpdates( $version, $updates = array() ) {
+ $this->db->clearFlag( DBO_DDLMODE );
if( !$this->canUseNewUpdatelog() ) {
return;
}
@@ -261,12 +245,14 @@ abstract class DatabaseUpdater {
$this->db->insert( 'updatelog',
array( 'ul_key' => $key, 'ul_value' => serialize( $updates ) ),
__METHOD__ );
+ $this->db->setFlag( DBO_DDLMODE );
}
/**
* 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!
+ * @param $key String Name of the key to check for
*/
public function updateRowExists( $key ) {
$row = $this->db->selectRow(
@@ -279,6 +265,23 @@ abstract class DatabaseUpdater {
}
/**
+ * Helper function: Add a key to the updatelog table
+ * Obviously, only use this for updates that occur after the updatelog table was
+ * created!
+ * @param $key String Name of key to insert
+ * @param $val String [optional] value to insert along with the key
+ */
+ public function insertUpdateRow( $key, $val = null ) {
+ $this->db->clearFlag( DBO_DDLMODE );
+ $values = array( 'ul_key' => $key );
+ if( $val && $this->canUseNewUpdatelog() ) {
+ $values['ul_value'] = $val;
+ }
+ $this->db->insert( 'updatelog', $values, __METHOD__, 'IGNORE' );
+ $this->db->setFlag( DBO_DDLMODE );
+ }
+
+ /**
* 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
@@ -459,13 +462,17 @@ abstract class DatabaseUpdater {
* @param $fullpath Boolean: whether to treat $patch path as a relative or not
*/
public function modifyField( $table, $field, $patch, $fullpath = false ) {
+ $updateKey = "$table-$field-$patch";
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" );
+ } elseif( $this->updateRowExists( $updateKey ) ) {
+ $this->output( "...$field in table $table already modified by patch $patch\n" );
} else {
$this->output( "Modifying $field field of table $table..." );
$this->applyPatch( $patch, $fullpath );
+ $this->insertUpdateRow( $updateKey );
$this->output( "ok\n" );
}
}
@@ -495,7 +502,7 @@ abstract class DatabaseUpdater {
$this->output( "done.\n" );
return;
}
- SiteStatsInit::doAllAndCommit( false );
+ SiteStatsInit::doAllAndCommit( $this->db );
}
# Common updater functions
@@ -525,7 +532,7 @@ abstract class DatabaseUpdater {
"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 = $this->maintenance->runChild( 'PopulateLogUsertext' );
$task->execute();
$this->output( "Done populating log_user_text field.\n" );
}
@@ -540,7 +547,7 @@ abstract class DatabaseUpdater {
"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 = $this->maintenance->runChild( 'PopulateLogSearch' );
$task->execute();
$this->output( "Done populating log_search table.\n" );
}
@@ -568,7 +575,18 @@ abstract class DatabaseUpdater {
return;
}
- $task = new UpdateCollation();
+ $task = $this->maintenance->runChild( 'UpdateCollation' );
$task->execute();
}
+
+ protected function rebuildLocalisationCache() {
+ /**
+ * @var $cl RebuildLocalisationCache
+ */
+ $cl = $this->maintenance->runChild( 'RebuildLocalisationCache', 'rebuildLocalisationCache.php' );
+ $this->output( "Rebuilding localisation cache...\n" );
+ $cl->setForce();
+ $cl->execute();
+ $this->output( "Rebuilding localisation cache done.\n" );
+ }
}
diff --git a/includes/installer/Ibm_db2Installer.php b/includes/installer/Ibm_db2Installer.php
new file mode 100644
index 00000000..78e607fb
--- /dev/null
+++ b/includes/installer/Ibm_db2Installer.php
@@ -0,0 +1,251 @@
+<?php
+/**
+ * IBM_DB2-specific installer.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for setting up the MediaWiki database using IBM_DB2.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class Ibm_db2Installer extends DatabaseInstaller {
+
+
+ protected $globalNames = array(
+ 'wgDBserver',
+ 'wgDBport',
+ 'wgDBname',
+ 'wgDBuser',
+ 'wgDBpassword',
+ 'wgDBmwschema',
+ );
+
+ protected $internalDefaults = array(
+ '_InstallUser' => 'db2admin'
+ );
+
+ /**
+ * Get the DB2 database extension name
+ * @return string
+ */
+ public function getName(){
+ return 'ibm_db2';
+ }
+
+ /**
+ * Determine whether the DB2 database extension is currently available in PHP
+ * @return boolean
+ */
+ public function isCompiled() {
+ return self::checkExtension( 'ibm_db2' );
+ }
+
+ /**
+ * Generate a connection form for a DB2 database
+ * @return string
+ */
+ public function getConnectForm() {
+ return
+ $this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) .
+ $this->getTextBox( 'wgDBport', 'config-db-port', array(), $this->parent->getHelpBox( '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();
+ }
+
+ /**
+ * Validate and then execute the connection form for a DB2 database
+ * @return Status
+ */
+ public function submitConnectForm() {
+ // Get variables from the request
+ $newValues = $this->setVarsFromRequest(
+ array( 'wgDBserver', 'wgDBport', 'wgDBname',
+ 'wgDBmwschema', 'wgDBuser', 'wgDBpassword' ) );
+
+ // 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 ( !strlen( $newValues['wgDBmwschema'] ) ) {
+ $status->fatal( 'config-invalid-schema' );
+ }
+ elseif ( !preg_match( '/^[a-zA-Z0-9_]*$/', $newValues['wgDBmwschema'] ) ) {
+ $status->fatal( 'config-invalid-schema', $newValues['wgDBmwschema'] );
+ }
+ if ( !strlen( $newValues['wgDBport'] ) ) {
+ $status->fatal( 'config-invalid-port' );
+ }
+ elseif ( !preg_match( '/^[0-9_]*$/', $newValues['wgDBport'] ) ) {
+ $status->fatal( 'config-invalid-port', $newValues['wgDBport'] );
+ }
+
+ // Submit user box
+ if ( $status->isOK() ) {
+ $status->merge( $this->submitInstallUserBox() );
+ }
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ global $wgDBport;
+ $wgDBport = $newValues['wgDBport'];
+
+ // Try to connect
+ $status->merge( $this->getConnection() );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ $this->parent->setVar( 'wgDBuser', $this->getVar( '_InstallUser' ) );
+ $this->parent->setVar( 'wgDBpassword', $this->getVar( '_InstallPassword' ) );
+
+ return $status;
+ }
+
+ /**
+ * Open a DB2 database connection
+ * @return Status
+ */
+ public function openConnection() {
+ $status = Status::newGood();
+ try {
+ $db = new DatabaseIbm_db2(
+ $this->getVar( 'wgDBserver' ),
+ $this->getVar( '_InstallUser' ),
+ $this->getVar( '_InstallPassword' ),
+ $this->getVar( 'wgDBname' ),
+ 0,
+ $this->getVar( 'wgDBmwschema' )
+ );
+ $status->value = $db;
+ } catch ( DBConnectionError $e ) {
+ $status->fatal( 'config-connection-error', $e->getMessage() );
+ }
+ return $status;
+ }
+
+ /**
+ * Create a DB2 database for MediaWiki
+ * @return Status
+ */
+ 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 )
+ . " AUTOMATIC STORAGE YES"
+ . " USING CODESET UTF-8 TERRITORY US COLLATE USING SYSTEM"
+ . " PAGESIZE 32768", __METHOD__ );
+ $conn->selectDB( $dbName );
+ }
+ $this->setupSchemaVars();
+ return $status;
+ }
+
+ /**
+ * Create tables from scratch.
+ * First check if pagesize >= 32k.
+ *
+ * @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' );
+ return $status;
+ }
+
+ /* Check for pagesize */
+ $status = $this->checkPageSize();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ $this->db->setFlag( DBO_DDLMODE ); // For Oracle's handling of schema files
+ $this->db->begin( __METHOD__ );
+
+ $error = $this->db->sourceFile( $this->db->getSchemaPath() );
+ 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;
+ }
+
+ /**
+ * Check if database has a tablspace with pagesize >= 32k.
+ *
+ * @return Status
+ */
+ public function checkPageSize() {
+ $status = $this->getConnection();
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ $this->db->selectDB( $this->getVar( 'wgDBname' ) );
+
+ try {
+ $result = $this->db->query( 'SELECT PAGESIZE FROM SYSCAT.TABLESPACES' );
+ if( $result == false ) {
+ $status->fatal( 'config-connection-error', '' );
+ }
+ else {
+ while ( $row = $this->db->fetchRow( $result ) ) {
+ if( $row[0] >= 32768 ) {
+ return $status;
+ }
+ }
+ $status->fatal( 'config-ibm_db2-low-db-pagesize', '' );
+ }
+ } catch ( DBUnexpectedError $e ) {
+ $status->fatal( 'config-connection-error', $e->getMessage() );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Generate the code to store the DB2-specific settings defined by the configuration form
+ * @return string
+ */
+ public function getLocalSettings() {
+ $schema = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBmwschema' ) );
+ $port = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgDBport' ) );
+ return
+"# IBM_DB2 specific settings
+\$wgDBmwschema = \"{$schema}\";
+\$wgDBport = \"{$port}\";";
+ }
+
+ public function __construct($parent) {
+ parent::__construct($parent);
+ }
+}
diff --git a/includes/installer/Ibm_db2Updater.php b/includes/installer/Ibm_db2Updater.php
new file mode 100644
index 00000000..39a9fb79
--- /dev/null
+++ b/includes/installer/Ibm_db2Updater.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * IBM_DB2-specific updater.
+ *
+ * @file
+ * @ingroup Deployment
+ */
+
+/**
+ * Class for handling updates to IBM_DB2 databases.
+ *
+ * @ingroup Deployment
+ * @since 1.17
+ */
+class Ibm_db2Updater extends DatabaseUpdater {
+
+ /**
+ * Get the changes in the DB2 database scheme since MediaWiki 1.14
+ * @return array
+ */
+ protected function getCoreUpdateList() {
+ return array(
+ // 1.14
+ array( 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
+ array( 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
+
+ // 1.15
+ array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
+ array( 'addTable', 'tag_summary', 'patch-change_tag_summary.sql' ),
+ array( 'addTable', 'valid_tag', 'patch-change_valid_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( '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' ),
+
+ // 1.17
+ array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
+ array( 'addField', 'updatelog', 'ul_value', 'patch-ul_value.sql' ),
+ array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api_and_wikiid.sql' ),
+ array( 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ),
+ array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
+ array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
+
+ // Tables
+ array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
+ array( 'addTable', 'msg_resource_links', 'patch-msg_resource_links.sql' ),
+ array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
+ array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
+
+ // Indexes
+ array( 'addIndex', 'msg_resource_links', 'uq61_msg_resource_links', 'patch-uq_61_msg_resource_links.sql' ),
+ array( 'addIndex', 'msg_resource', 'uq81_msg_resource', 'patch-uq_81_msg_resource.sql' ),
+ array( 'addIndex', 'module_deps', 'uq96_module_deps', 'patch-uq_96_module_deps.sql' ),
+
+ // Fields
+ array( 'addField', 'categorylinks', 'cl_sortkey_prefix', 'patch-cl_sortkey_prefix-field.sql' ),
+ array( 'addField', 'categorylinks', 'cl_collation', 'patch-cl_collation-field.sql' ),
+ array( 'addField', 'categorylinks', 'cl_type', 'patch-cl_type-field.sql' ),
+ array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api-field.sql' ),
+ array( 'addField', 'interwiki', 'iw_wikiid', 'patch-iw_wikiid-field.sql' )
+ );
+ }
+} \ No newline at end of file
diff --git a/includes/installer/InstallDocFormatter.php b/includes/installer/InstallDocFormatter.php
new file mode 100644
index 00000000..5801f26d
--- /dev/null
+++ b/includes/installer/InstallDocFormatter.php
@@ -0,0 +1,42 @@
+<?php
+
+class InstallDocFormatter {
+ static function format( $text ) {
+ $obj = new self( $text );
+ return $obj->execute();
+ }
+
+ protected function __construct( $text ) {
+ $this->text = $text;
+ }
+
+ protected function execute() {
+ $text = $this->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;
+ }
+
+ protected function replaceBugLinks( $matches ) {
+ return '<span class="config-plainlink">[https://bugzilla.wikimedia.org/' .
+ $matches[1] . ' bug ' . $matches[1] . ']</span>';
+ }
+
+ protected function replaceConfigLinks( $matches ) {
+ return '<span class="config-plainlink">[http://www.mediawiki.org/wiki/Manual:' .
+ $matches[1] . ' ' . $matches[1] . ']</span>';
+ }
+}
diff --git a/includes/installer/Installer.i18n.php b/includes/installer/Installer.i18n.php
index dc3147a7..e9ede6f9 100644
--- a/includes/installer/Installer.i18n.php
+++ b/includes/installer/Installer.i18n.php
@@ -75,10 +75,10 @@ This program is distributed in the hope that it will be useful, but '''without a
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]
+ 'config-sidebar' => "* [//www.mediawiki.org MediaWiki home]
+* [//www.mediawiki.org/wiki/Help:Contents User's Guide]
+* [//www.mediawiki.org/wiki/Manual:Contents Administrator's Guide]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
----
* <doclink href=Readme>Read me</doclink>
* <doclink href=ReleaseNotes>Release notes</doclink>
@@ -94,16 +94,16 @@ 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].",
+If you run a high-traffic site, you should read a little on [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalization].",
'config-unicode-update-warning' => "'''Warning''': The installed version of the Unicode normalization wrapper uses an older version of [http://site.icu-project.org/ the ICU project's] library.
-You should [http://www.mediawiki.org/wiki/Unicode_normalization_considerations upgrade] if you are at all concerned about using Unicode.",
+You should [//www.mediawiki.org/wiki/Unicode_normalization_considerations upgrade] if you are at all concerned about using Unicode.",
'config-no-db' => 'Could not find a suitable database driver! You need to install a database driver for PHP.
The following database types are supported: $1.
If you 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-no-fts3' => "'''Warning''': SQLite is compiled without the [//sqlite.org/fts3.html FTS3 module], search features will be unavailable on this backend.",
'config-register-globals' => "'''Warning: PHP's <code>[http://php.net/register_globals register_globals]</code> option is enabled.'''
'''Disable it if you can.'''
MediaWiki will work, but your server is exposed to potential security vulnerabilities.",
@@ -132,12 +132,14 @@ MediaWiki requires UTF-8 support to function correctly.",
'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-xcache' => '[http://xcache.lighttpd.net/ 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].
+ 'config-no-cache' => "'''Warning:''' Could not find [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] or [http://www.iis.net/download/WinCacheForPhp WinCache].
Object caching is not enabled.",
+ 'config-mod-security' => "'''Warning''': your web server has [http://modsecurity.org/ mod_security] enabled. If misconfigured, it can cause problems for MediaWiki or other software that allows users to post arbitrary content.
+Refer to [http://modsecurity.org/documentation/ mod_security documentation] or contact your host's support if you encounter random errors.",
'config-diff3-bad' => 'GNU diff3 not found.',
'config-imagemagick' => 'Found ImageMagick: <code>$1</code>.
Image thumbnailing will be enabled if you enable uploads.',
@@ -147,10 +149,15 @@ Image thumbnailing will be enabled if you enable uploads.',
Image thumbnailing will be disabled.',
'config-no-uri' => "'''Error:''' Could not determine the current URI.
Installation aborted.",
+ 'config-no-cli-uri' => "'''Warning''': No --scriptpath specified, using default: <code>$1</code>.",
+ 'config-using-server' => 'Using server name "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'Using server URL "<nowiki>$1$2</nowiki>".',
'config-uploads-not-safe' => "'''Warning:''' Your default directory for uploads <code>$1</code> is vulnerable to arbitrary scripts execution.
-Although MediaWiki checks all uploaded files for security threats, it is highly recommended to [http://www.mediawiki.org/wiki/Manual:Security#Upload_security close this security vulnerability] before enabling uploads.",
+Although MediaWiki checks all uploaded files for security threats, it is highly recommended to [//www.mediawiki.org/wiki/Manual:Security#Upload_security close this security vulnerability] before enabling uploads.",
+ 'config-no-cli-uploads-check' => "'''Warning:''' Your default directory for uploads (<code>$1</code>) is not checked for vulnerability
+to arbitrary script execution during the CLI install.",
'config-brokenlibxml' => 'Your system has a combination of PHP and libxml2 versions which is buggy and can cause hidden data corruption in MediaWiki and other web applications.
-Upgrade to PHP 5.2.9 or later and libxml2 2.7.3 or later ([http://bugs.php.net/bug.php?id=45996 bug filed with PHP]).
+Upgrade to PHP 5.2.9 or later and libxml2 2.7.3 or later ([//bugs.php.net/bug.php?id=45996 bug filed with PHP]).
Installation aborted.',
'config-using531' => 'MediaWiki cannot be used with PHP $1 due to a bug involving reference parameters to <code>__call()</code>.
Upgrade to PHP 5.3.2 or higher, or downgrade to PHP 5.3.0 to resolve this.
@@ -162,7 +169,9 @@ Installation aborted.',
If you are using shared web hosting, your hosting provider should give you the correct host name in their documentation.
-If you are installing on a Windows server and using MySQL, using "localhost" may not work for the server name. If it does not, try "127.0.0.1" for the local IP address.',
+If you are installing on a Windows server and using MySQL, using "localhost" may not work for the server name. If it does not, try "127.0.0.1" for the local IP address.
+
+If you are using PostgreSQL, leave this field blank to connect via a Unix socket.',
'config-db-host-oracle' => 'Database TNS:',
'config-db-host-oracle-help' => 'Enter a valid [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; a tnsnames.ora file must be visible to this installation.<br />If you are using client libraries 10g or newer you can also use the [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect] naming method.',
'config-db-wiki-settings' => 'Identify this wiki',
@@ -205,7 +214,7 @@ This field is usually left empty.',
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].",
+but it will not let you store characters above the [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
'config-mysql-old' => 'MySQL $1 or later is required, you have $2.',
'config-db-port' => 'Database port:',
'config-db-schema' => 'Schema for MediaWiki',
@@ -229,6 +238,7 @@ Consider putting the database somewhere else altogether, for example in <code>/v
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki supports the following database systems:
$1
@@ -238,10 +248,12 @@ If you do not see the database system you are trying to use listed below, then f
'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-support-ibm_db2' => '* $1 is a commercial enterprise database.',
'config-header-mysql' => 'MySQL settings',
'config-header-postgres' => 'PostgreSQL settings',
'config-header-sqlite' => 'SQLite settings',
'config-header-oracle' => 'Oracle settings',
+ 'config-header-ibm_db2' => 'IBM DB2 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"',
@@ -315,6 +327,13 @@ The account you specify here must already exist.',
'config-mysql-engine' => 'Storage engine:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Warning''': You have selected MyISAM as storage engine for MySQL, which is not recommended for use with MediaWiki, because:
+* it barely supports concurrency due to table locking
+* it is more prone to corruption than other engines
+* the MediaWiki codebase does not always handle MyISAM as it should
+
+If your MySQL installation supports InnoDB, it is highly recommended that you choose that instead.
+If your MySQL installation does not support InnoDB, maybe it's time for an upgrade.",
'config-mysql-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.
@@ -325,7 +344,10 @@ MyISAM databases tend to get corrupted more often than InnoDB databases.",
'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].",
+In '''UTF-8 mode''', MySQL will know what character set your data is in, and can present and convert it appropriately, but it will not let you store characters above the [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+
+ 'config-ibm_db2-low-db-pagesize' => "Your DB2 database has a default tablespace with an insufficient pagesize. The pagesize has to be '''32K''' or greater.",
+
'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.',
@@ -361,6 +383,8 @@ Specify a different username.',
'config-subscribe' => 'Subscribe to the [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release announcements mailing list].',
'config-subscribe-help' => 'This is a low-volume mailing list used for release announcements, including important security announcements.
You should subscribe to it and update your MediaWiki installation when new versions come out.',
+ 'config-subscribe-noemail' => 'You tried to subscribe to the release announcements mailing list without providing an e-mail address.
+Please provide an e-mail address if you wish to subscribe to the mailing list.',
'config-almost-done' => 'You are almost done!
You can now skip the remaining configuration and install the wiki right now.',
'config-optional-continue' => 'Ask me more questions.',
@@ -382,14 +406,14 @@ A wiki with '''{{int:config-profile-no-anon}}''' provides extra accountability,
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].",
+More complex user rights configurations are available after installation, see the [//www.mediawiki.org/wiki/Manual:User_rights relevant manual entry].",
'config-license' => 'Copyright and license:',
'config-license-none' => 'No license footer',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by' => 'Creative Commons Attribution',
'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero',
- 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
- 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 or later',
+ 'config-license-cc-0' => 'Creative Commons Zero (Public Domain)',
+ 'config-license-gfdl' => 'GNU Free Documentation License 1.3 or later',
'config-license-pd' => 'Public Domain',
'config-license-cc-choose' => 'Select a custom Creative Commons license',
'config-license-help' => "Many public wikis put all contributions under a [http://freedomdefined.org/Definition free license].
@@ -398,8 +422,9 @@ 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.",
+Wikipedia previously used the GNU Free Documentation License.
+The GFDL is a valid license, but it is difficult to understand.
+It is also difficult to reuse content licensed under the GFDL.",
'config-email-settings' => '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.
@@ -421,7 +446,7 @@ 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.
+For more information, read the [//www.mediawiki.org/wiki/Manual:Security security section] in the manual.
To enable file uploads, change the mode on the <code>images</code> subdirectory under MediaWiki's root directory so that the web server can write to it.
Then enable this option.",
@@ -434,7 +459,7 @@ 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.
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] is a feature that allows wikis to use images, sounds and other media found on the [//commons.wikimedia.org/ Wikimedia Commons] site.
In order to do this, MediaWiki requires access to the Internet.
For more information on this feature, including instructions on how to set it up for wikis other than the Wikimedia Commons, consult [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos the manual].',
@@ -524,9 +549,17 @@ $3
When that has been done, you can '''[$2 enter your wiki]'''.",
'config-download-localsettings' => 'Download LocalSettings.php',
'config-help' => 'help',
+ 'mainpagetext' => "'''MediaWiki has been successfully installed.'''",
+ 'mainpagedocfooter' => "Consult the [//meta.wikimedia.org/wiki/Help:Contents User's Guide] for information on using the wiki software.
+
+== Getting started ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
);
/** Message documentation (Message documentation)
+ * @author Amire80
* @author Dani
* @author EugeneZelenko
* @author Kghbln
@@ -535,6 +568,7 @@ When that has been done, you can '''[$2 enter your wiki]'''.",
* @author Platonides
* @author Purodha
* @author Raymond
+ * @author SPQRobin
* @author Siebrand
* @author Umherirrender
*/
@@ -556,10 +590,15 @@ $messages['qqq'] = array(
'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-sidebar' => 'Maximum width for words is 24 characters. Only visible part of the translation counts to this limit.',
'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-no-db' => 'Do not translate: <code>./configure --with-mysql</code>.
+<br />
+Do not translate: <code>php5-mysql</code>.
+
+Parameters:
+* $1 is comma separated list of database types supported 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.',
@@ -569,6 +608,13 @@ $messages['qqq'] = array(
'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-imagemagick' => '$1 is ImageMagick\'s <code>convert</code> executable file name.
+
+Add dir="ltr" to the <nowiki><code></nowiki> for right-to-left languages.',
+ 'config-no-cli-uri' => 'Parameters:
+* $1 is the default value for scriptpath.',
+ 'config-no-cli-uploads-check' => 'CLI = Call Level Interface',
+ 'config-suhosin-max-value-length' => 'Message shown when PHP parameter suhosin.get.max_value_length is between 0 and 1023 (that max value is hard set in MediaWiki software',
'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\"",
@@ -580,6 +626,9 @@ $messages['qqq'] = array(
* $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-connection-error' => '$1 is the external error from the database, such as "DB connection error: Access denied for user \'dba\'@\'localhost\' (using password: YES) (localhost)."
+
+If you\'re translating this message to a right-to-left language, consider writing <nowiki><div dir="ltr">$1.</div></nowiki>. (When the bidi features for HTML5 will be implemented in the browsers, it will probably be a good idea to write it as <nowiki><div dir="auto">$1.</div></nowiki>.)',
'config-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" ?
@@ -597,7 +646,7 @@ Parameters:
* {{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-logo-help' => '',
'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}}',
@@ -623,6 +672,9 @@ This message refers to a block of HTML being embedded into the installer page. I
'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}}',
+ 'mainpagetext' => 'Along with {{msg|mainpagedocfooter}}, the text you will see on the Main Page when your wiki is installed.',
+ 'mainpagedocfooter' => 'Along with {{msg|mainpagetext}}, the text you will see on the Main Page when your wiki is installed.
+This might be a good place to put information about <nowiki>{{GRAMMAR:}}</nowiki>. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/fi]] for an example. For languages having grammatical distinctions and not having an appropriate <nowiki>{{GRAMMAR:}}</nowiki> software available, a suggestion to check and possibly amend the messages having <nowiki>{{SITENAME}}</nowiki> may be valuable. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/ksh]] for an example.',
);
/** Magyar (magázó) (Magyar (magázó))
@@ -647,7 +699,7 @@ Ellenőrizze, hogy a php.ini-ben a <code>session.save_path</code> beállítás a
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].",
+Ha nagy látogatottságú oldalt üzemeltet, itt találhat információkat [//www.mediawiki.org/wiki/Unicode_normalization_considerations a témáról].",
'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.",
@@ -665,7 +717,7 @@ Ha a fiók nem létezik és a telepítést végző fiók rendelkezik megfelelő
'''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.",
+nem használhatja a [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] feletti karaktereket.",
'config-db-schema-help' => 'A fenti sémák általában megfelelőek.
Csak akkor módosítson rajta, ha szükség van rá.',
'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>).
@@ -682,8 +734,8 @@ chmod a+w $3</pre>',
'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.
+ 'config-instantcommons-help' => 'Az [//www.mediawiki.org/wiki/InstantCommons Instant Commons] lehetővé teszi, hogy a wikin használhassák a [//commons.wikimedia.org/ Wikimedia Commons] oldalon található képeket, hangokat és más médiafájlokat.
+A használatához a MediaWikinek internethozzáférésre van szüksége.
A funkcióról és hogy hogyan állítható be más wikik esetén [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos a kézikönyvben] találhat további információkat.',
'config-install-done' => "'''Gratulálunk!'''
@@ -696,6 +748,37 @@ Ez tartalmazza az összes beállítást.
'''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]'''.",
+ 'mainpagedocfooter' => "Ha segítségre van szüksége a wikiszoftver használatához, akkor keresse fel a [//meta.wikimedia.org/wiki/Help:Contents User's Guide] oldalt.
+
+== Alapok (angol nyelven) ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Beállítások listája]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki GyIK]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-kiadások levelezőlistája]",
+);
+
+/** Moroccan Spoken Arabic (Maġribi)
+ * @author Enzoreg
+ */
+$messages['ary'] = array(
+ 'mainpagetext' => "'''MediaWiki ṫ'instala be najaḫ.'''",
+ 'mainpagedocfooter' => 'Ila bġiṫiw meĝlomaṫ ĥrin baċ ṫesṫeĝmlo had l-lojisyél siro ċofo [//meta.wikimedia.org/wiki/Aide:Contenu Gid dyal l-mosṫeĥdim]
+
+== L-bdaya mĝa MediaWiki ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista dyal l-paramétraṫ dyal l-konfigurasyon]
+* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ fe MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista dyal l-modakaraṫ ĝla versyonaṫ jdad dyal MediaWiki]',
+);
+
+/** Español (formal) (Español (formal))
+ * @author Dferg
+ */
+$messages['es-formal'] = array(
+ 'mainpagedocfooter' => 'Consulte usted la [//meta.wikimedia.org/wiki/Ayuda:Contenido Guía de usuario] para obtener información sobre el uso del software wiki.
+
+== Empezando ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de ajustes de configuración]
+* [//www.mediawiki.org/wiki/Manual:FAQ/es FAQ de MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de distribución de MediaWiki]',
);
/** Afrikaans (Afrikaans)
@@ -732,10 +815,10 @@ Kontroleer u php.ini en maak seker dat <code>session.save_path</code> na 'n geld
'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)
+ 'config-sidebar' => '* [//www.mediawiki.org MediaWiki tuisblad]
+* [//www.mediawiki.org/wiki/Help:Contents Gebruikershandleiding] (Engelstalig)
+* [//www.mediawiki.org/wiki/Manual:Contents Administrateurshandleiding] (Engelstalig)
+* [//www.mediawiki.org/wiki/Manual:FAQ Algemene vrae] (Engelstalig)
----
* <doclink href=Readme>Lees my</doclink>
* <doclink href=ReleaseNotes>Vrystellingsnotas</doclink>
@@ -748,7 +831,7 @@ 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.
+ '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',
@@ -767,6 +850,7 @@ Die installasie mag moontlik faal!",
'config-db-password' => 'Databasis wagwoord:',
'config-db-prefix' => 'Voorvoegsel vir databasistabelle:',
'config-db-charset' => 'Karakterstelsel vir databasis',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binêr',
'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
'config-mysql-old' => 'U moet MySQL $1 of later gebruik.
U gebruik tans $2.',
@@ -775,18 +859,26 @@ U gebruik tans $2.',
'config-sqlite-dir' => 'Gids vir SQLite se data:',
'config-oracle-def-ts' => 'Standaard tabelruimte:',
'config-oracle-temp-ts' => 'Tydelike tabelruimte:',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-header-mysql' => 'MySQL-instellings',
'config-header-postgres' => 'PostgreSQL-instellings',
'config-header-sqlite' => 'SQLite-instellings',
'config-header-oracle' => 'Oracle-instellings',
+ 'config-header-ibm_db2' => 'Instellings vir IBM DB2',
'config-invalid-db-type' => 'Ongeldige databasistipe',
'config-missing-db-name' => 'U moet \'n waarde vir "Databasnaam" verskaf',
'config-sqlite-readonly' => 'Die lêer <code>$1</code> kan nie geskryf word nie.',
'config-sqlite-cant-create-db' => 'Kon nie databasislêer <code>$1</code> skep nie.',
+ 'config-upgrade-done-no-regenerate' => 'Opgradering is voltooi.
+
+U kan nou [$1 u wiki gebruik].',
'config-regenerate' => 'Herskep LocalSettings.php →',
'config-show-table-status' => 'Die uitvoer van SHOW TABLE STATUS het gefaal!',
+ 'config-db-web-account' => 'Databasisgebruiker vir toegang tot die web',
+ 'config-mysql-engine' => 'Stoor-enjin:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-charset' => 'Karakterstelsel vir databasis:',
'config-mysql-binary' => 'Binêr',
'config-mysql-utf8' => 'UTF-8',
'config-site-name' => 'Naam van die wiki:',
@@ -828,29 +920,36 @@ U gebruik tans $2.',
'config-install-step-failed' => 'het misluk',
'config-install-extensions' => 'Insluitende uitbreidings',
'config-install-database' => 'Stel die databasis op',
+ 'config-install-pg-schema-not-exist' => 'Die skema vir PostgreSQL bestaan ​​nie.',
'config-install-pg-schema-failed' => 'Die skep van tabelle het gefaal.
Maak seker dat die gebruiker "$1" na skema "$2" mag skryf.',
'config-install-pg-commit' => 'Wysigings word gestoor',
+ 'config-install-pg-plpgsql' => 'Kontroleer vir taal PL/pgSQL',
'config-pg-no-plpgsql' => 'U moet die taal PL/pgSQL in die database $1 installeer',
'config-install-user' => 'Besig om die databasisgebruiker te skep',
+ 'config-install-user-alreadyexists' => 'Gebruiker "$1" bestaan al reeds',
+ 'config-install-user-create-failed' => 'Skep van gebruiker "$1" het gefaal: $2',
'config-install-user-grant-failed' => 'Die toekenning van regte aan gebruiker "$1" het gefaal: $2',
'config-install-tables' => 'Skep tabelle',
- 'config-install-tables-exist' => "'''Waarskuwing''': Dit lyk of MediaWiki se tabelle reeds bestaan.
+ 'config-install-tables-exist' => "'''Waarskuwing''': Dit lyk of MediaWiki se tabelle reeds bestaan.
Die skep van tabelle word oorgeslaan.",
'config-install-tables-failed' => "'''Fout''': die skep van 'n tabel het gefaal met die volgende fout: $1",
'config-install-interwiki' => 'Besig om data in die interwiki-tabel in te laai',
'config-install-interwiki-list' => 'Kon nie die lêer <code>interwiki.list</code> vind nie.',
'config-install-interwiki-exists' => "'''Waarskuwing''': Die interwiki-tabel bevat reeds inskrywings.
Die standaardlys word oorgeslaan.",
+ 'config-install-stats' => 'Inisialiseer statistieke',
'config-install-keys' => 'Genereer geheime sleutel',
'config-install-sysop' => "Skep 'n gebruiker vir die administrateur",
+ 'config-install-subscribe-fail' => 'Kon nie vir MediaWiki-announce inskryf nie: $1',
'config-install-mainpage' => 'Skep die hoofblad met standaard inhoud',
+ 'config-install-extension-tables' => 'Skep tabelle vir aangeskakel uitbreidings',
'config-install-mainpage-failed' => 'Kon nie die hoofblad laai nie: $1',
- 'config-install-done' => "'''Veels geluk!'''
-U het MediaWiki suksesvol geïnstalleer.
+ '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.
+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.
@@ -858,16 +957,98 @@ U sal dit moet [$1 aflaai] en dit in die hoofgids van u wiki-installasie plaas;
As dit gedoen is, kan u '''[u $2 wiki besoek]'''.",
'config-download-localsettings' => 'Laai LocalSettings.php af',
'config-help' => 'hulp',
+ 'mainpagetext' => "'''MediaWiki is suksesvol geïnstalleer.'''",
+ 'mainpagedocfooter' => "Konsulteer '''[//meta.wikimedia.org/wiki/Help:Contents User's Guide]''' vir inligting oor hoe om die wikisagteware te gebruik.
+
+== Hoe om te Begin ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Gheg Albanian (Gegë)
+ * @author Bresta
+ */
+$messages['aln'] = array(
+ 'mainpagetext' => "'''MediaWiki software u instalue me sukses.'''",
+ 'mainpagedocfooter' => 'Për mâ shumë informata rreth përdorimit të softwareit wiki, ju lutem shikoni [//meta.wikimedia.org/wiki/Help:Contents dokumentacionin].
+
+
+== Për fillim ==
+
+* [//www.mediawiki.org/wiki/Help:Configuration_settings Konfigurimi i MediaWikit]
+* [//www.mediawiki.org/wiki/Help:FAQ Pyetjet e shpeshta rreth MediaWikit]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Njoftime rreth MediaWikit]',
+);
+
+/** Amharic (አማርኛ) */
+$messages['am'] = array(
+ 'mainpagetext' => "'''MediaWiki በትክክል ማስገባቱ ተከናወነ።'''",
+ 'mainpagedocfooter' => "ስለ ዊኪ ሶፍትዌር ጥቅም ለመረዳት፣ [//meta.wikimedia.org/wiki/Help:Contents User's Guide] ያንብቡ።
+
+== ለመጀመር ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Aragonese (Aragonés)
+ * @author Juanpabl
+ */
+$messages['an'] = array(
+ 'mainpagetext' => "'''O programa MediaWiki s'ha instalato correctament.'''",
+ 'mainpagedocfooter' => "Consulta a [//meta.wikimedia.org/wiki/Help:Contents Guía d'usuario] ta mirar información sobre cómo usar o software wiki.
+
+== Ta prencipiar ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de caracteristicas confegurables]
+* [//www.mediawiki.org/wiki/Manual:FAQ Preguntas cutianas sobre MediaWiki (FAQ)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correu sobre ta anuncios de MediaWiki]",
+);
+
+/** Old English (Ænglisc)
+ * @author Gott wisst
+ */
+$messages['ang'] = array(
+ 'mainpagetext' => "'''MediaǷiki hafaþ ȝeƿorden spēdiȝe inseted.'''",
+ 'mainpagedocfooter' => 'Þeahta þone [//meta.wikimedia.org/wiki/Help:Contents Brūcenda Lǣdend] on helpe mid þǣre nytte of ƿikisōftƿare.
+
+== Beȝinnunȝ ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Onfæstnunȝa ȝesetednessa ȝetæl]
+* [//www.mediawiki.org/wiki/Manual:FAQ Ȝetæl oft ascodra ascunȝa ymb MediaǷiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ǣrendunȝȝetæl nīƿra MediaǷiki forþsendnessa]',
);
/** Arabic (العربية)
* @author Meno25
+ * @author OsamaK
+ * @author روخو
*/
$messages['ar'] = array(
+ 'config-desc' => 'مثبت لميدياويكي',
+ 'config-title' => 'ميدياويكي 1$ التثبيت',
+ 'config-information' => 'معلومات',
+ 'config-back' => '→ ارجع',
+ 'config-continue' => 'استمر ←',
+ 'config-page-language' => 'اللغة',
+ 'config-page-name' => 'الاسم',
+ 'config-db-username' => 'اسم مستخدم قاعدة البيانات:',
+ 'config-db-password' => 'كلمة سر قاعدة البيانات:',
+ 'config-db-port' => 'منفذ قاعدة البيانات:',
'config-type-mysql' => 'ماي إس كيو إل',
'config-type-postgres' => 'بوستجر إس كيو إل',
'config-type-sqlite' => 'إس كيو لايت',
'config-type-oracle' => 'أوراكل',
+ 'config-admin-email' => 'عنوان البريد الإلكتروني:',
+ 'mainpagetext' => "'''تم تثبيت ميدياويكي بنجاح.'''",
+ 'mainpagedocfooter' => 'استشر [//meta.wikimedia.org/wiki/Help:Contents دليل المستخدم] لمعلومات حول استخدام برنامج الويكي.
+
+== البداية ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings قائمة إعدادات الضبط]
+* [//www.mediawiki.org/wiki/Manual:FAQ أسئلة متكررة حول ميدياويكي]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce القائمة البريدية الخاصة بإصدار ميدياويكي]',
);
/** Aramaic (ܐܪܡܝܐ)
@@ -891,6 +1072,145 @@ $messages['arc'] = array(
'config-email-settings' => 'ܛܘܝܒ̈ܐ ܕܒܝܠܕܪܐ ܐܠܩܛܪܘܢܝܐ',
);
+/** Egyptian Spoken Arabic (مصرى) */
+$messages['arz'] = array(
+ 'mainpagetext' => "''' ميدياويكى اتنزلت بنجاح.'''",
+ 'mainpagedocfooter' => 'اسال [//meta.wikimedia.org/wiki/Help:Contents دليل اليوزر] للمعلومات حوالين استخدام برنامج الويكى.
+
+== البداية ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings لستة اعدادات الضبط]
+* [//www.mediawiki.org/wiki/Manual:FAQ أسئلة بتكرر حوالين الميدياويكى]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce لستة الايميلات بتاعة اعلانات الميدياويكى]',
+);
+
+/** Assamese (অসমীয়া)
+ * @author Chaipau
+ * @author Gitartha.bordoloi
+ */
+$messages['as'] = array(
+ 'mainpagetext' => "'''মিডিয়াৱিকি সফলভাবে ইন্সটল কৰা হ'ল ।'''",
+ 'mainpagedocfooter' => "ৱিকি চ'ফটৱেৰ কেনেকৈ ব্যৱহাৰ কৰিব [//meta.wikimedia.org/wiki/Help:Contents সদস্যৰ সহায়িকা] চাওঁক ।
+
+== আৰম্ভণি কৰিবলৈ ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Asturian (Asturianu) */
+$messages['ast'] = array(
+ 'mainpagetext' => "'''MediaWiki instalóse correchamente.'''",
+ 'mainpagedocfooter' => "Visita la [//meta.wikimedia.org/wiki/Help:Contents Guía d'usuariu] pa saber cómo usar esti software wiki.
+
+== Empecipiando ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Llista de les opciones de configuración]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ de MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de corréu de les ediciones de MediaWiki]",
+);
+
+/** Kotava (Kotava) */
+$messages['avk'] = array(
+ 'mainpagetext' => "'''MediaWiki inkeyen talpeyot.'''",
+);
+
+/** Azerbaijani (Azərbaycanca)
+ * @author Cekli829
+ * @author Vago
+ * @author Wertuose
+ */
+$messages['az'] = array(
+ 'config-back' => '← Geri',
+ 'config-continue' => 'Davam et →',
+ 'config-page-language' => 'Dil',
+ 'config-page-welcome' => 'MediaWiki-yə xoş gəlmişsiniz!',
+ 'config-page-dbconnect' => 'Verilənlər bazasına birləşdir',
+ 'config-page-dbsettings' => 'Verilənlər bazasının nizamlanması',
+ 'config-page-name' => 'Ad',
+ 'config-page-options' => 'Nizamlamalar:',
+ 'config-page-install' => 'Nizamlama',
+ 'config-page-complete' => 'Komplektləşdir!',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-ns-generic' => 'Layihə',
+ 'config-admin-name' => 'Sizin adınız:',
+ 'config-admin-password' => 'Parol:',
+ 'config-admin-email' => 'E-poçt ünvanı',
+ 'config-help' => 'kömək',
+ 'mainpagetext' => "'''MediaWiki müvəffəqiyyətlə quraşdırıldı.'''",
+ 'mainpagedocfooter' => 'Bu vikinin istifadəsi ilə bağlı məlumat almaq üçün [//meta.wikimedia.org/wiki/Help:Contents İstifadəçi məlumat səhifəsinə] baxın.
+
+== Faydalı keçidlər ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Tənzimləmələrin siyahısı]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki haqqında tez-tez soruşulan suallar]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-poçt siyahısı]',
+);
+
+/** Bashkir (Башҡортса)
+ * @author Haqmar
+ */
+$messages['ba'] = array(
+ 'mainpagetext' => '«MediaWiki» уңышлы рәүештә ҡоролдо.',
+ 'mainpagedocfooter' => 'Был вики менән эшләү тураһында мәғлүмәтте [//meta.wikimedia.org/wiki/Ярҙам:Белешмә ошонда] табып була.
+
+== Файҙалы сығанаҡтар ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Көйләүҙәр исемлеге (инг.)];
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki тураһында йыш бирелгән һорауҙар һәм яуаптар (инг.)];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-ның яңы версиялары тураһында хәбәрҙәр алып тороу].',
+);
+
+/** Bavarian (Boarisch)
+ * @author Mucalexx
+ */
+$messages['bar'] = array(
+ 'mainpagetext' => "'''MediaWiki is erfoigreich installird worn.'''",
+ 'mainpagedocfooter' => 'A Hüf zur da Benützung und Konfigurazion voh da Wiki-Software findst auf [//meta.wikimedia.org/wiki/Help:Contents Benützerhåndbuach].
+
+== Starthüfe ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listen voh de Konfigurazionsvariaablen]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglisten voh de neichen MediaWiki-Versionen]',
+);
+
+/** Southern Balochi (بلوچی مکرانی) */
+$messages['bcc'] = array(
+ 'mainpagetext' => "'''مدیا وی کی گون موفقیت نصب بوت.'''",
+ 'mainpagedocfooter' => "مشورت کنیت گون [//meta.wikimedia.org/wiki/Help:Contents User's Guide] په گشیترین اطلاعات په استفاده چه برنامه ویکی.
+
+== شروع بیت ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Bikol Central (Bikol Central) */
+$messages['bcl'] = array(
+ 'mainpagetext' => "'''Instalado na an MediaWiki.'''",
+ 'mainpagedocfooter' => "Konsultarón tabì an [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para sa impormasyon sa paggamit nin progama kaining wiki.
+
+== Pagpopoon ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Belarusian (Беларуская) */
+$messages['be'] = array(
+ 'mainpagetext' => "'''MediaWiki паспяхова ўсталяваная.'''",
+ 'mainpagedocfooter' => 'Гл. [//meta.wikimedia.org/wiki/Help:Contents Дапаможнік карыстальніка (англ.)] па далейшыя звесткі аб карыстанні вікі-праграмамі.
+
+== З чаго пачаць ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Пералік параметраў канфігурацыі (англ.)]
+* [//www.mediawiki.org/wiki/Manual:FAQ ЧАПЫ MediaWiki (англ.)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Ліставанне аб выпусках MediaWiki (англ.)]',
+);
+
/** Belarusian (Taraškievica orthography) (‪Беларуская (тарашкевіца)‬)
* @author EugeneZelenko
* @author Jim-by
@@ -961,10 +1281,10 @@ This program is distributed in the hope that it will be useful, but '''without a
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 Адказы на частыя пытаньні]
+ 'config-sidebar' => '* [//www.mediawiki.org Хатняя старонка MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Даведка для ўдзельнікаў]
+* [//www.mediawiki.org/wiki/Manual:Contents Даведка для адміністратараў]
+* [//www.mediawiki.org/wiki/Manual:FAQ Адказы на частыя пытаньні]
----
* <doclink href=Readme>Прачытайце</doclink>
* <doclink href=ReleaseNotes>Паляпшэньні ў вэрсіі</doclink>
@@ -980,17 +1300,16 @@ You should have received <doclink href=Copying>a copy of the GNU General Public
'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-нармалізацыю].",
+Калі ў Вас сайт з высокай наведваемасьцю, раім пачытаць пра [//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.
+Раім [//www.mediawiki.org/wiki/Unicode_normalization_considerations абнавіць], калі ваш сайт будзе працаваць зь Unicode.",
+ 'config-no-db' => 'Немагчыма знайсьці адпаведны драйвэр базы зьвестак. Вам неабходна ўсталяваць драйвэр базы зьвестак для PHP.
Падтрымліваюцца наступныя тыпы базаў зьвестак: $1.
-Калі вы выкарыстоўваеце агульны хостынг, запытайцеся ў свайго хостынг-правайдэра наконт усталяваньня патрабуемага драйвэр базы зьвестак.
-Калі Вы кампілявалі PHP самастойна, пераканфігуруйце і сабярыце яго з уключаным кліентам базаў зьвестак, напрыклад, <code>./configure --with-mysql</code>.
-Калі Вы ўсталёўвалі PHP з Debian/Ubuntu-рэпазытарыя, то вам трэба ўсталяваць дадаткова пакет <code>php5-mysql</code>',
- 'config-no-fts3' => "'''Папярэджаньне''': SQLite створаны без модуля [http://sqlite.org/fts3.html FTS3], для гэтага ўнутранага інтэрфэйсу ня будзе даступная магчымасьць пошуку.",
+Калі вы выкарыстоўваеце агульны хостынг, запытайцеся ў свайго хостынг-правайдэра наконт усталяваньня патрабуемага драйвэру базы зьвестак.
+Калі Вы кампілявалі PHP самастойна, пераканфігуруйце і сабярыце яго з дазволеным кліентам базаў зьвестак, напрыклад, <code>./configure --with-mysql</code>.
+Калі Вы ўсталёўвалі PHP з пакетаў Debian ці Ubuntu, то Вам трэба ўсталяваць дадаткова модуль <code>php5-mysql</code>.',
+ 'config-no-fts3' => "'''Папярэджаньне''': SQLite створаны без модуля [//sqlite.org/fts3.html FTS3], для гэтага ўнутранага інтэрфэйсу ня будзе даступная магчымасьць пошуку.",
'config-register-globals' => "'''Папярэджаньне: уключаная опцыя PHP <code>[http://php.net/register_globals register_globals]</code>.'''
'''Адключыце яе, калі можаце.'''
MediaWiki будзе працаваць, але гэта панізіць узровень бясьпекі сэрвэра.",
@@ -1019,12 +1338,14 @@ MediaWiki патрабуе падтрымкі UTF-8 для слушнай пра
'config-memory-bad' => "'''Папярэджаньне:''' памер PHP <code>memory_limit</code> складае $1.
Верагодна, гэта вельмі мала.
Усталяваньне можа быць няўдалым!",
- 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] усталяваны',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ 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-no-cache' => "'''Папярэджаньне:''' немагчыма знайсьці [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ці [http://www.iis.net/download/WinCacheForPhp WinCache].
Аб’ектнае кэшаваньне ня ўключанае.",
+ 'config-mod-security' => "'''Папярэджаньне''': на Вашым ўэб-сэрверы ўключаны [http://modsecurity.org/ mod_security]. У выпадку няслушнай наладцы, ён можа стаць прычынай праблемаў для MediaWiki ці іншага праграмнага забесьпячэньня, якое дазваляе ўдзельнікам дасылаць на сэрвэр любы зьмест.
+Глядзіце [http://modsecurity.org/documentation/ дакумэнтацыю mod_security] ці зьвярніцеся ў падтрымку Вашага хосту, калі ў Вас узьнікаюць выпадковыя праблемы.",
'config-diff3-bad' => 'GNU diff3 ня знойдзены.',
'config-imagemagick' => 'Знойдзены ImageMagick: <code>$1</code>.
Пасьля ўключэньня загрузак будзе ўключанае маштабаваньне выяваў.',
@@ -1034,14 +1355,20 @@ MediaWiki патрабуе падтрымкі UTF-8 для слушнай пра
Маштабаваньне выяваў будзе адключанае.',
'config-no-uri' => "'''Памылка:''' Не магчыма вызначыць цяперашні URI.
Усталяваньне спыненае.",
+ 'config-no-cli-uri' => "'''Папярэджаньне''': Не пазначаны --scriptpath, па змоўчваньні выкарыстоўваецца: <code>$1</code>.",
+ 'config-using-server' => 'Выкарыстоўваецца назва сэрвэра «<nowiki>$1</nowiki>».',
+ 'config-using-uri' => 'Выкарыстоўваецца URL-спасылка сэрвэра «<nowiki>$1$2</nowiki>».',
'config-uploads-not-safe' => "'''Папярэджаньне:''' дырэкторыя для загрузак па змоўчваньні <code>$1</code> уразьлівая да выкананьня адвольнага коду.
-Хоць MediaWiki і правярае ўсе файлы перад захаваньнем, вельмі рэкамэндуецца [http://www.mediawiki.org/wiki/Manual:Security#Upload_security закрыць гэтую ўразьлівасьць] перад уключэньнем магчымасьці загрузкі файлаў.",
+Хоць MediaWiki і правярае ўсе файлы перад захаваньнем, вельмі рэкамэндуецца [//www.mediawiki.org/wiki/Manual:Security#Upload_security закрыць гэтую ўразьлівасьць] перад уключэньнем магчымасьці загрузкі файлаў.",
+ 'config-no-cli-uploads-check' => "'''Папярэджаньне:''' Вашая дырэкторыя для загрузак па змоўчваньні (<code>$1</code>), не правераная на ўразьлівасьць да выкананьня адвольных скрыптоў падчас усталяваньня CLI.
+.",
'config-brokenlibxml' => 'У Вашай сыстэме ўсталяваныя PHP і libxml2 зь несумяшчальнымі вэрсіямі, што можа прывесьці да пашкоджаньня зьвестак MediaWiki і іншых ўэб-дастасаваньняў.
-Абнавіце PHP да вэрсіі 5.2.9 ці болей позьняй, а libxml2 да 2.7.3 ці болей позьняй ([http://bugs.php.net/bug.php?id=45996 паведамленьне пра памылку на сайце PHP]).
+Абнавіце PHP да вэрсіі 5.2.9 ці болей позьняй, а libxml2 да 2.7.3 ці болей позьняй ([//bugs.php.net/bug.php?id=45996 паведамленьне пра памылку на сайце PHP]).
Усталяваньне перарванае.',
'config-using531' => 'PHP $1 не сумяшчальнае з MediaWiki з-за памылкі ў перадачы парамэтраў па ўказальніку да <code>__call()</code>.
Абнавіце PHP да вэрсіі 5.3.2 ці болей позьняй, ці адкаціце да вэрсіі 5.3.0 каб гэта выправіць.
Усталяваньне перарванае.',
+ 'config-suhosin-max-value-length' => 'Suhosin усталяваны і абмяжоўвае даўжыню парамэтра GET у $1 {{PLURAL:$1|байт|байты|байтаў}}. ResourceLoader для MediaWiki будзе абходзіць гэтае абмежаваньне, што, аднак, адаб’ецца на хуткадзеяньні. Калі магчыма, варта ўстанавіць suhosin.get.max_value_length роўным 1024 ці больш у php.ini, а таксама ўстанавіць тое ж значэньне для $wgResourceLoaderMaxQueryLength у LocalSettings.php.',
'config-db-type' => 'Тып базы зьвестак:',
'config-db-host' => 'Хост базы зьвестак:',
'config-db-host-help' => 'Калі сэрвэр Вашай базы зьвестак знаходзіцца на іншым сэрвэры, увядзіце тут імя хоста ці IP-адрас.
@@ -1089,12 +1416,13 @@ MediaWiki патрабуе падтрымкі UTF-8 для слушнай пра
У '''бінарным (binary)''' рэжыме MediaWiki захоўвае тэксты ў UTF-8 у палёх тыпу binary.
Гэты рэжым болей эфэктыўны за рэжым MySQL UTF-8 і дазваляе выкарыстоўваць увесь абсяг сымбаляў Unicode.
У рэжыме '''UTF-8''' MySQL будзе ведаць, у якім кадаваньне Вы зьмяшчаеце зьвесткі, і будзе вяртаць іх у адпаведным кадаваньні,
-але MySQL ня можа ўтрымліваць сымбалі па-за [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Стандартным шматмоўным пластом] сымбаляў Unicode.",
+але MySQL ня можа ўтрымліваць сымбалі па-за [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Стандартным шматмоўным пластом] сымбаляў Unicode.",
'config-mysql-old' => 'Патрабуецца MySQL $1 ці навейшая, усталяваная вэрсія $2.',
'config-db-port' => 'Порт базы зьвестак:',
'config-db-schema' => 'Схема для MediaWiki',
'config-db-schema-help' => 'Гэтая схема слушная ў большасьці выпадкаў.
Зьмяняйце яе толькі тады, калі Вы ведаеце, што гэта неабходна.',
+ 'config-pg-test-error' => "Немагчыма далучыцца да базы зьвестак '''$1''': $2",
'config-sqlite-dir' => 'Дырэкторыя зьвестак SQLite:',
'config-sqlite-dir-help' => "SQLite захоўвае ўсе зьвесткі ў адзіным файле.
@@ -1112,6 +1440,7 @@ MediaWiki патрабуе падтрымкі UTF-8 для слушнай пра
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki падтрымлівае наступныя сыстэмы базаў зьвестак:
$1
@@ -1121,10 +1450,12 @@ $1
'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-support-ibm_db2' => '* $1 — база зьвестак камэрцыйнага прадпрыемства.',
'config-header-mysql' => 'Налады MySQL',
'config-header-postgres' => 'Налады PostgreSQL',
'config-header-sqlite' => 'Налады SQLite',
'config-header-oracle' => 'Налады Oracle',
+ 'config-header-ibm_db2' => 'Налады IBM DB2',
'config-invalid-db-type' => 'Няслушны тып базы зьвестак',
'config-missing-db-name' => 'Вы павінны ўвесьці значэньне парамэтру «Імя базы зьвестак»',
'config-missing-db-host' => 'Вы павінны ўвесьці значэньне парамэтру «Хост базы зьвестак»',
@@ -1198,6 +1529,13 @@ chmod a+w $3</pre>',
'config-mysql-engine' => 'Рухавік сховішча:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Папярэджаньне''': Вы выбралі MyISAM у якасьці рухавіка для захоўваньня зьвестак у MySQL, які не рэкамэндуецца да выкарыстаньня з MediaWiki па прычынах:
+* кепская падтрымка паралельнай апрацоўкі з-за таблічных блякаваньняў;
+* большая імавернасьць пашкоджаньня зьвестак у параўнаньні зь іншымі рухавікамі;
+* код MediaWiki не ва ўсіх выпадках улічвае асаблівасьці MyISAM.
+
+Калі Ваш MySQL-сэрвэр падтрымлівае InnoDB, вельмі рэкамэндуецца выкарыстаньне менавіта гэтага рухавіка.
+Калі MySQL-сэрвэр не падтрымлівае InnoDB, пэўна, настаў час абнавіць яго.",
'config-mysql-engine-help' => "'''InnoDB''' — звычайна найбольш слушны варыянт, таму што добра падтрымлівае паралелізм.
'''MyISAM''' можа быць хутчэйшай у вікі з адным удзельнікам, ці толькі для чытаньня.
@@ -1208,7 +1546,8 @@ chmod a+w $3</pre>',
'config-mysql-charset-help' => "У '''двайковым рэжыме''', MediaWiki захоўвае тэкст у кадаваньні UTF-8 у базе зьвестак у двайковых палях.
Гэта болей эфэктыўна за рэжым MySQL UTF-8, і дазваляе Вам выкарыстоўваць увесь дыяпазон сымбаляў Unicode.
-У '''рэжыме UTF-8''', MySQL ведае, якая табліцы сымбаляў выкарыстоўваецца ў Вашых зьвестках, і можа адпаведна прадстаўляць і канвэртаваць іх, але гэта не дазволіць Вам захоўваць сымбалі па-за межамі [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базавага шматмоўнага дыяпазону].",
+У '''рэжыме UTF-8''', MySQL ведае, якая табліцы сымбаляў выкарыстоўваецца ў Вашых зьвестках, і можа адпаведна прадстаўляць і канвэртаваць іх, але гэта не дазволіць Вам захоўваць сымбалі па-за межамі [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базавага шматмоўнага дыяпазону].",
+ 'config-ibm_db2-low-db-pagesize' => "Вашая база зьвестак DB2 мае таблічную прасторну зь недастатковым памерам старонкі. Памер старонкі мусіць быць ня менш за '''32к'''.",
'config-site-name' => 'Назва вікі:',
'config-site-name-help' => 'Назва будзе паказвацца ў загалоўку браўзэра і ў некаторых іншых месцах.',
'config-site-name-blank' => 'Увядзіце назву сайта.',
@@ -1244,6 +1583,8 @@ chmod a+w $3</pre>',
'config-subscribe' => 'Падпісацца на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce сьпіс распаўсюджаньня навінаў пра зьяўленьне новых вэрсіяў].',
'config-subscribe-help' => 'Гэта ня вельмі актыўны сьпіс распаўсюджаньня навінаў пра зьяўленьне новых вэрсіяў, які ўключаючы важныя навіны пра бясьпеку.
Вам неабходна падпісацца на яго і абнавіць Вашае ўсталяваньне MediaWiki, калі зьявяцца новыя вэрсіі.',
+ 'config-subscribe-noemail' => 'Вы спрабавалі падпісацца на рассылку паведамленьняў пра выхад новых вэрсіяў, не пазначыўшы адрас электроннай пошты.
+Калі ласка, падайце слушны адрас, калі Вы жадаеце падпісацца на рассылку.',
'config-almost-done' => 'Вы амаль што скончылі!
Астатнія налады можна прапусьціць і пачаць усталяваньне вікі.',
'config-optional-continue' => 'Задаць болей пытаньняў.',
@@ -1265,23 +1606,25 @@ chmod a+w $3</pre>',
Сцэнар '''{{int:config-profile-fishbowl}}''' дазваляе рэдагаваць зацьверджаным удзельнікам, але ўсе могуць праглядаць старонкі іх гісторыю.
'''{{int:config-profile-private}}''' дазваляе праглядаць і рэдагаваць старонкі толькі зацьверджаным удзельнікам.
-Больш складаныя правы ўдзельнікаў даступныя пасьля ўсталяваньня, глядзіце [http://www.mediawiki.org/wiki/Manual:User_rights адпаведную старонку дакумэнтацыі].",
+Больш складаныя правы ўдзельнікаў даступныя пасьля ўсталяваньня, глядзіце [//www.mediawiki.org/wiki/Manual:User_rights адпаведную старонку дакумэнтацыі].",
'config-license' => 'Аўтарскія правы і ліцэнзія:',
'config-license-none' => 'Без інфармацыі пра ліцэнзію',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by' => 'Creative Commons Attribution',
'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero',
- 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
- 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 ці болей позьняя',
+ 'config-license-cc-0' => 'Creative Commons Zero (грамадзкі набытак)',
+ 'config-license-gfdl' => 'GNU Free Documentation License 1.3 ці болей позьняя',
'config-license-pd' => 'Грамадзкі набытак',
'config-license-cc-choose' => 'Выберыце іншую ліцэнзію Creative Commons',
- 'config-license-help' => "Шматлікія адкрытыя вікі разьмяшчаюць унёскі на ўмовах ліцэнзіі [http://freedomdefined.org/Definition вольнай ліцэнзіі].
-Гэта дазваляе ствараць сэнс супольнай уласнасьці і садзейнічае доўгатэрміновым унёскам.
-Гэта не неабходна для прыватных і карпаратыўных вікі.
+ 'config-license-help' => "Шматлікія адкрытыя вікі публікуюць увесь унёсак у праект на ўмовах [http://freedomdefined.org/Definition вольнай ліцэнзіі].
+Гэта дазваляе ствараць эфэкт супольнай уласнасьці і садзейнічае доўгатэрміноваму ўнёску.
+Для прыватных і карпаратыўных вікі гэта не зьяўляецца неабходнасьцю.
-Калі Вы жадаеце выкарыстоўваць тэкст з Вікіпэдыі, і жадаеце каб Вікіпэдыя магла прынімаць тэкст скапіяваны з Вашай вікі, Вам неабходна выбраць ліцэнзію '''Creative Commons Attribution Share Alike'''.
+Калі Вы жадаеце выкарыстоўваць тэкст зь Вікіпэдыі, і жадаеце, каб Вікіпэдыя магла прымаць тэксты, скапіяваныя з Вашай вікі, Вам неабходна выбраць ліцэнзію '''Creative Commons Attribution Share Alike'''.
-Раней Вікіпэдыя выкарыстоўвала ліцэнзію GNU Free Documentation. Яна ўсё яшчэ дзейнічае, але яна ўтрымлівае некаторыя моманты, якія ўскладняюць паўторнае выкарыстоўваньне і інтэрпрэтацыю матэрыялаў.",
+Раней Вікіпэдыя выкарыстоўвала ліцэнзію GNU Free Documentation.
+Яна ўсё яшчэ дзейнічае, але яна ўтрымлівае некаторыя моманты,
+якія ўскладняюць паўторнае выкарыстоўваньне і інтэрпрэтацыю матэрыялаў.",
'config-email-settings' => 'Налады электроннай пошты',
'config-enable-email' => 'Дазволіць выходзячыя электронныя лісты',
'config-enable-email-help' => 'Калі Вы жадаеце, каб працавала электронная пошта, неабходна сканфігураваць PHP [http://www.php.net/manual/en/mail.configuration.php адпаведным чынам].
@@ -1303,7 +1646,7 @@ chmod a+w $3</pre>',
'config-upload-settings' => 'Загрузкі выяваў і файлаў',
'config-upload-enable' => 'Дазволіць загрузку файлаў',
'config-upload-help' => 'Дазвол загрузкі файлаў можа патэнцыйна пагражаць бясьпекі сэрвэра.
-Дадатковую інфармацыю можна атрымаць ў [http://www.mediawiki.org/wiki/Manual:Security разьдзеле бясьпекі].
+Дадатковую інфармацыю можна атрымаць ў [//www.mediawiki.org/wiki/Manual:Security разьдзеле бясьпекі].
Каб дазволіць загрузку файлаў, зьмяніце рэжым падкаталёга <code>images</code> у карэннай дырэкторыі MediaWiki так, каб ўэб-сэрвэр меў доступ на запіс.
Потым дазвольце гэтую магчымасьць.',
@@ -1312,12 +1655,12 @@ chmod a+w $3</pre>',
У ідэальным выпадку, яна не павінна мець доступу з Інтэрнэту.',
'config-logo' => 'URL-адрас лягатыпу:',
'config-logo-help' => 'Афармленьне MediaWiki па змоўчваньні уключае прастору для лягатыпу памерам 135×160 піксэляў у верхнім левым куце.
-Загрузіце выяву адпаведнага памеру, і увядзіце тут URL-адрас.
+Загрузіце выяву адпаведнага памеру і увядзіце тут URL-адрас.
-Калі Вы не жадаеце мець ніякага лягатыпу, пакіньце гэтае поле пустым.',
+Калі Вы не жадаеце мець ніякага лягатыпу, пакіньце поле пустым.',
'config-instantcommons' => 'Дазволіць Instant Commons',
- 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] — магчымасьць, якая дазваляе вікі выкарыстоўваць выявы, гукі і іншыя мэдыя, якія знаходзяцца на сайце [http://commons.wikimedia.org/ Wikimedia Commons].
-Каб гэта зрабіць, MediaWiki патрабуе доступу да Інтэрнэту.
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] — магчымасьць, якая дазваляе вікі выкарыстоўваць выявы, гукі і іншыя мэдыя, якія знаходзяцца на сайце [//commons.wikimedia.org/ Wikimedia Commons].
+Каб гэта зрабіць, MediaWiki патрабуе доступу да Інтэрнэту.
Каб даведацца болей пра гэтую магчымасьць, уключаючы інструкцыю пра тое, як яе ўстанавіць ў любой вікі, акрамя Wikimedia Commons, глядзіце [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos дакумэнтацыю].',
'config-cc-error' => 'Выбар ліцэнзіі Creative Commons ня даў вынікаў.
@@ -1353,6 +1696,7 @@ chmod a+w $3</pre>',
'config-install-step-failed' => 'не атрымалася',
'config-install-extensions' => 'Уключаючы пашырэньні',
'config-install-database' => 'Налада базы зьвестак',
+ 'config-install-schema' => 'Стварэньне схемы',
'config-install-pg-schema-not-exist' => 'Схема PostgreSQL не існуе',
'config-install-pg-schema-failed' => 'Немагчыма стварыць табліцу.
Упэўніцеся, што карыстальнік «$1» можа пісаць у схему «$2».',
@@ -1360,10 +1704,17 @@ chmod a+w $3</pre>',
'config-install-pg-plpgsql' => 'Праверка падтрымкі мовы PL/pgSQL',
'config-pg-no-plpgsql' => 'Вам неабходна ўсталяваць падтрымку мовы PL/pgSQL у базе зьвестак $1',
'config-pg-no-create-privs' => 'Рахунак, які Вы пазначылі для ўсталяваньня ня мае дастаткова правоў для стварэньня рахунку.',
+ 'config-pg-not-in-role' => 'Пазначаны Вамі рахунак для ўэб-карыстальніка ўжо існуе.
+Пазначаны Вамі рахунак для ўсталяваньня ня мае правоў і не зьяўляецца сябрам ролі ўэб-карыстальніка, таму немагчыма стварыць аб’екты, якія належаць ўэб-карыстальніку.
+
+Цяпер MediaWiki патрабуе, каб табліцы належалі да ўэб-карыстальніку. Калі ласка, пазначце іншы рахунак, ці націсьніце кнопку «Вярнуцца» і пазначце карыстальніка з неабходнымі для ўсталяваньня правамі.',
'config-install-user' => 'Стварэньне карыстальніка базы зьвестак',
'config-install-user-alreadyexists' => 'Удзельнік «$1» ужо існуе',
'config-install-user-create-failed' => 'Немагчыма стварыць ўдзельніка «$1»: $2',
'config-install-user-grant-failed' => 'Немагчыма даць правы удзельніку «$1»: $2',
+ 'config-install-user-missing' => 'Пазначаны карыстальнік «$1» не існуе.',
+ 'config-install-user-missing-create' => 'Пазначаны карыстальнік «$1» не існуе.
+Калі ласка, пазначце «стварыць рахунак», калі Вы жадаеце яго стварыць.',
'config-install-tables' => 'Стварэньне табліцаў',
'config-install-tables-exist' => "'''Папярэджаньне''': Выглядае, што табліцы MediaWiki ужо існуюць.
Стварэньне прапушчанае.",
@@ -1373,10 +1724,11 @@ chmod a+w $3</pre>',
'config-install-interwiki-exists' => "'''Папярэджаньне''': выглядае, што табліца інтэрвікі ўжо запоўненая.
Сьпіс па змоўчваньні прапушчаны.",
'config-install-stats' => 'Ініцыялізацыі статыстыкі',
- 'config-install-keys' => 'Стварэньне сакрэтнага ключа',
+ 'config-install-keys' => 'Стварэньне сакрэтных ключоў',
'config-insecure-keys' => "'''Папярэджаньне:''' {{PLURAL:$2|Ключ бясьпекі $1 створаны|Ключы бясьпекі $1 створаныя}} падчас усталяваньня, не зьяўляюцца поўнасьцю бясьпечнымі. Рэкамэндуецца зьмяніць {{PLURAL:$2|яго ўручную|іх уручную}}.",
'config-install-sysop' => 'Стварэньне рахунку адміністратара',
- 'config-install-subscribe-fail' => 'Немагчыма падпісацца на «mediawiki-announce»',
+ 'config-install-subscribe-fail' => 'Немагчыма падпісацца на «mediawiki-announce»: $1',
+ 'config-install-subscribe-notpossible' => 'cURL не ўсталяваны, allow_url_fopen недаступны.',
'config-install-mainpage' => 'Стварэньне галоўнай старонкі са зьместам па змоўчваньні',
'config-install-extension-tables' => 'Стварэньне табліцаў для ўключаных пашырэньняў',
'config-install-mainpage-failed' => 'Немагчыма ўставіць галоўную старонку: $1',
@@ -1397,6 +1749,13 @@ $3
Калі Вы гэта зробіце, Вы можаце '''[$2 ўвайсьці ў Вашую вікі]'''.",
'config-download-localsettings' => 'Загрузіць LocalSettings.php',
'config-help' => 'дапамога',
+ 'mainpagetext' => "'''MediaWiki пасьпяхова ўсталяваная.'''",
+ 'mainpagedocfooter' => 'Глядзіце [//meta.wikimedia.org/wiki/Help:Contents дапаможнік карыстальніка] для атрыманьня інфармацыі па карыстаньні вікі-праграмамі.
+
+== З чаго пачаць ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Сьпіс парамэтраў канфігурацыі]
+* [//www.mediawiki.org/wiki/Manual:FAQ Частыя пытаньні MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Рассылка паведамленьняў пра зьяўленьне новых вэрсіяў MediaWiki]',
);
/** Bulgarian (Български)
@@ -1424,6 +1783,12 @@ $1',
$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' => 'Език на уикито:',
@@ -1460,10 +1825,10 @@ $1
За повече подробности се препоръчва преглеждането на Общия публичен лиценз на 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 ЧЗВ]
+ 'config-sidebar' => '* [//www.mediawiki.org Сайт на МедияУики]
+* [//www.mediawiki.org/wiki/Help:Contents Наръчник на потребителя]
+* [//www.mediawiki.org/wiki/Manual:Contents Наръчник на администратора]
+* [//www.mediawiki.org/wiki/Manual:FAQ ЧЗВ]
----
* <doclink href=Readme>Документация</doclink>
* <doclink href=ReleaseNotes>Бележки за версията</doclink>
@@ -1479,9 +1844,16 @@ $1
'config-unicode-using-utf8' => 'Използване на utf8_normalize.so от Brion Vibber за нормализация на Уникод.',
'config-unicode-using-intl' => 'Използване на разширението [http://pecl.php.net/intl intl PECL] за нормализация на Уникод.',
'config-unicode-pure-php-warning' => "'''Предупреждение''': [http://pecl.php.net/intl Разширението intl PECL] не е налично за справяне с нормализацията на Уникод, превключване към по-бавното изпълнение на чист PHP.
-Ако сайтът е с голям трафик, препоръчително е запознаването с [http://www.mediawiki.org/wiki/Unicode_normalization_considerations нормализацията на Уникод].",
- 'config-no-db' => 'Не може да бъде открит подходящ драйвер за база от данни!',
- 'config-no-fts3' => "'''Предупреждение''': SQLite е компилирана без [http://sqlite.org/fts3.html модула FTS3], затова възможностите за търсене няма да са достъпни.",
+Ако сайтът е с голям трафик, препоръчително е запознаването с [//www.mediawiki.org/wiki/Unicode_normalization_considerations нормализацията на Уникод].",
+ 'config-unicode-update-warning' => "'''Предупреждение''': Инсталираната версия на Обвивката за нормализация на Unicode използва по-старата версия на библиотеката на [http://site.icu-project.org/ проекта ICU].
+Необходимо е да [//www.mediawiki.org/wiki/Unicode_normalization_considerations инсталирате по-нова верия], в случай че сте загрижени за използването на Unicode.",
+ 'config-no-db' => 'Не може да бъде открит подходящ драйвер за база от данни! Необходимо е да се инсталира драйвер за база от данни за PHP.
+Поддържат се следните типове базни от данни: $1.
+
+Ако използвате споделен хостинг, помолете доставчика на услугата да инсталира подходящ драйвер за база от данни.
+Ако сами сте компилирали PHP, преконфигурирайте го с включен клиент за база от данни, например чрез използване на <code>./configure --with-mysql</code>.
+Ако сте инсталирали PHP от пакет за Debian или Убунту, необходимо е, също така, да инсталирате и модула php5-mysql.',
+ 'config-no-fts3' => "'''Предупреждение''': SQLite е компилирана без [//sqlite.org/fts3.html модула FTS3], затова възможностите за търсене няма да са достъпни.",
'config-register-globals' => "'''Предупреждение: Настройката на PHP <code>[http://php.net/register_globals register_globals]</code> е включена.'''
'''При възможност е препоръчително тя да бъде изключена.'''
МедияУики ще работи, но сървърът е изложен на евентуални пропуски в сигурността.",
@@ -1502,17 +1874,19 @@ $1
'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-xcache' => '[http://xcache.lighttpd.net/ 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-no-cache' => "'''Предупреждение:''' Не бяха открити [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC] [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
Обектното кеширане не е включено.",
'config-diff3-bad' => 'GNU diff3 не беше намерен.',
'config-imagemagick' => 'Открит е ImageMagick: <code>$1</code>.
@@ -1521,17 +1895,29 @@ $1
Ако качването на файлове бъде включено, ще бъде включена възможността за преоразмеряване на картинки.',
'config-no-scaling' => 'Не са открити библиотеките GD или ImageMagick.
Преоразмеряването на картинки ще бъде изключено.',
- 'config-no-uri' => "'''Грешка:''' Не може да се определи текущия адрес.
+ 'config-no-uri' => "'''Грешка:''' Не може да се определи текущия адрес.
Инсталация беше прекратена.",
+ 'config-using-server' => 'Използване на сървърното име "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'Използване на сървърния адрес (URL) "<nowiki>$1$2</nowiki>".',
'config-uploads-not-safe' => "'''Предупреждение:''' Папката по подразбиране за качване <code>$1</code> е уязвима от изпълнение на зловредни скриптове.
-Въпреки че МедияУики извършва проверка за заплахи в сигурността на всички качени файлове, силно препоръчително е да се [http://www.mediawiki.org/wiki/Manual:Security#Upload_security затвори тази уязвимост в сигурността] преди разрешаване за качване на файлове.",
+Въпреки че МедияУики извършва проверка за заплахи в сигурността на всички качени файлове, силно препоръчително е да се [//www.mediawiki.org/wiki/Manual:Security#Upload_security затвори тази уязвимост в сигурността] преди разрешаване за качване на файлове.",
+ 'config-brokenlibxml' => 'Вашата система използа комбинация от версии на PHP и libxml2, които са с много грешки и могат да причинят скрити повреди на данните в МедияУики или други уеб приложения.
+Необходимо е обновяване до PHP 5.2.9 или по-нова версия и libxml2 2.7.3 или по-нова версия ([//bugs.php.net/bug.php?id=45996 докладвана грешка при PHP]).
+Инсталацията беше прекратена.',
+ 'config-using531' => 'МедияУики не може да се използва с PHP $1 заради проблем с референтните параметри за <code>__call()</code>.
+За разрешаване на този проблем е необходимо да се обнови до PHP 5.3.2 или по-нова версия или да се инсталира по-стара версия, напр. PHP 5.3.0.
+Инсталацията беше прекратена.',
+ 'config-suhosin-max-value-length' => 'Suhosin е инсталиран и ограничава дължината на параметъра GET на $1 байта. Компонентът на МедияУики ResourceLoader ще може да пренебрегне частично това ограничение, но това ще намали производителността. По възможност е препоръчително да се настрои suhosin.get.max_value_length на 1024 или по-голяма стойност в php.ini и в LocalSettings.php да се настрои $wgResourceLoaderMaxQueryLength със същата стойност.',
'config-db-type' => 'Тип на базата от данни:',
'config-db-host' => 'Хост на базата от данни:',
'config-db-host-help' => 'Ако базата от данни е на друг сървър, в кутията се въвежда името на хоста или IP адреса.
Ако се използва споделен уеб хостинг, доставчикът на услугата би трябвало да е предоставил в документацията си коректния хост.
-Ако инсталацията протича на Windows-сървър и се използва MySQL, използването на "localhost" може да е неприемливо. В такива случаи се използва "127.0.0.1" за локален IP адрес.',
+Ако инсталацията протича на Windows-сървър и се използва MySQL, използването на "localhost" може да е неприемливо. В такива случаи се използва "127.0.0.1" за локален IP адрес.
+
+При използване на PostgreSQL, това поле се оставя празно, за свързване чрез Unix socket.',
+ 'config-db-host-oracle' => 'TNS на базата данни:',
'config-db-wiki-settings' => 'Идентифициране на това уики',
'config-db-name' => 'Име на базата от данни:',
'config-db-name-help' => 'Избира се име, което да идентифицира уикито.
@@ -1542,6 +1928,8 @@ $1
'config-db-install-account' => 'Потребителска сметка за инсталацията',
'config-db-username' => 'Потребителско име за базата от данни:',
'config-db-password' => 'Парола за базата от данни:',
+ 'config-db-password-empty' => 'Въведете парола за новия потребител на базата от данни: $1.
+Въпреки че е допустимо да се създават потребители без пароли, това е незащитено действие.',
'config-db-install-username' => 'Въвежда се потребителско име, което ще се използва за свързване с базата от данни по време на процеса по инсталация.
Това не е потребителско име за сметка в МедияУики; това е потребителско име за базата от данни.',
'config-db-install-password' => 'Въвежда се парола, която ще бъде използвана за свързване с базата от данни по време на инсталационния процес.
@@ -1564,6 +1952,7 @@ $1
'config-db-schema' => 'Схема за МедияУики',
'config-db-schema-help' => 'Схемата по-горе обикновено е коректна.
Промени се извършват ако наистина е необходимо.',
+ 'config-pg-test-error' => "Невъзможно свързване с базата данни '''$1''': $2",
'config-sqlite-dir' => 'Директория за данни на SQLite:',
'config-sqlite-dir-help' => "SQLite съхранява всички данни в един файл.
@@ -1575,6 +1964,7 @@ $1
Това включва сурови данни за потребителите (адреси за е-поща, хеширани пароли), както и изтрити версии на страници и друга чувствителна и с ограничен достъп информация от и за уикито.
Базата от данни е препоръчително да се разположи на друго място, например в <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'МедияУики поддържа следните системи за бази от данни:
$1
@@ -1584,13 +1974,18 @@ $1
'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-support-ibm_db2' => '* $1 е комерсиална фирмена база от данни.',
'config-header-mysql' => 'Настройки за MySQL',
'config-header-postgres' => 'Настройки за PostgreSQL',
'config-header-sqlite' => 'Настройки за SQLite',
'config-header-oracle' => 'Настройки за Oracle',
+ 'config-header-ibm_db2' => 'Настройки за IBM DB2',
'config-invalid-db-type' => 'Невалиден тип база от данни',
'config-missing-db-name' => 'Необходимо е да се въведе стойност за "Име на базата от данни"',
'config-missing-db-host' => 'Необходимо е да се въведе стойност за "Хост на базата от данни"',
+ 'config-missing-db-server-oracle' => 'Необходимо е да се въведе стойност за "Database TNS"',
+ 'config-invalid-db-server-oracle' => 'Невалиден TNS на базата от данни "$1".
+Допустими са само ASCII букви (a-z, A-Z), цифри (0-9), символите за долна черта (_) и точка (.).',
'config-invalid-db-name' => 'Невалидно име на базата от данни "$1".
Използват се само ASCII букви (a-z, A-Z), цифри (0-9), долни черти (_) и тирета (-).',
'config-invalid-db-prefix' => 'Невалидна представка за базата от данни "$1".
@@ -1606,8 +2001,32 @@ $1
'config-sqlite-name-help' => 'Избира се име, което да идентифицира уикито.
Не се използват интервали или тирета.
Това име ще се използва за име на файла за данни на SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'Дикректорията за данни <code><nowiki>$1</nowiki></code> не може да бъде създадена, тъй като уеб сървърът няма права за писане в родителската директория <code><nowiki>$2</nowiki></code>.
+
+Инсталаторът разпознава потребителското име, с което работи уеб сървърът.
+Уверете се, че той притежава права за писане в директорията <code><nowiki>$3</nowiki></code> преди да продължите.
+В Unix/Линукс системи можете да използвате:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Дикректорията за данни <code><nowiki>$1</nowiki></code> не може да бъде създадена, тъй като уеб сървърът няма права за писане в родителската директория <code><nowiki>$2</nowiki></code>.
+
+Инсталаторът не може да определи потребителското име, с което работи уеб сървърът.
+Уверете се, че в директория <code><nowiki>$3</nowiki></code> може да бъде писано от уебсървъра (или от други потребители!) преди да продължите.
+На Unix/Линукс системи можете да използвате:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
'config-sqlite-mkdir-error' => 'Грешка при създаване на директорията за данни "$1".
Проверете местоположението ѝ и опитайте отново.',
+ 'config-sqlite-dir-unwritable' => 'Уебсървърът няма права за писане в директория "$1".
+Променете правата му така, че да може да пише в нея, и опитайте отново.',
+ 'config-sqlite-connection-error' => '$1.
+
+Проверете директорията за данни и името на базата от данни по-долу и опитайте отново.',
'config-sqlite-readonly' => 'Файлът <code>$1</code> няма права за писане.',
'config-sqlite-cant-create-db' => 'Файлът за базата от данни <code>$1</code> не може да бъде създаден.',
'config-sqlite-fts3-downgrade' => 'Липсва поддръжката на FTS3 за PHP, извършен беше downgradе на таблиците',
@@ -1634,6 +2053,13 @@ $1
'config-mysql-engine' => 'Хранилище на данни:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Предупреждение''': Избрана е MyISAM като система за складиране в MySQL, която не се препоръчва за използване с МедияУики, защото:
+* почти не поддържа паралелност заради заключване на таблиците
+* е по-податлива на повреди в сравнение с други системи
+* кодът на МедияУики не винаги поддържа MyISAM коректно
+
+Ако инсталацията на MySQL поддържа InnoDB, силно е препоръчително да се използва тя.
+Ако инсталацията на MySQL не поддържа InnoDB, вероятно е време за обновяване.",
'config-mysql-engine-help' => "'''InnoDB''' почти винаги е най-добрата възможност заради навременната си поддръжка.
'''MyISAM''' може да е по-бърза при инсталации с един потребител или само за четене.
@@ -1644,7 +2070,7 @@ $1
'config-mysql-charset-help' => "В '''бинарен режим''' МедияУики съхранява текстовете в UTF-8 в бинарни полета в базата от данни.
Това е по-ефективно от UTF-8 режима на MySQL и позволява използването на пълния набор от символи в Уникод.
-В '''UTF-8 режим''' MySQL ще знае в кой набор от символи са данните от уикито и ще може да ги показва и променя по подходящ начин, но няма да позволява складиране на символи извън [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основния многоезичен набор].",
+В '''UTF-8 режим''' MySQL ще знае в кой набор от символи са данните от уикито и ще може да ги показва и променя по подходящ начин, но няма да позволява складиране на символи извън [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основния многоезичен набор].",
'config-site-name' => 'Име на уикито:',
'config-site-name-help' => 'Това име ще се показва в заглавната лента на браузъра и на различни други места.',
'config-site-name-blank' => 'Необходимо е да се въведе име на уикито.',
@@ -1658,6 +2084,8 @@ $1
Обикновено представката произлиза от името на уикито, но не може да съдържа символи като "#" или ":".',
'config-ns-invalid' => 'Посоченото именно пространство "<nowiki>$1</nowiki>" е невалидно.
Необходимо е да бъде посочено друго.',
+ 'config-ns-conflict' => 'Посоченото именно пространство "<nowiki>$1</nowiki>" е в конфликт с използваното по подразбиране именно пространство MediaWiki.
+Необходимо е да се посочи друго именно пространство.',
'config-admin-box' => 'Администраторска сметка',
'config-admin-name' => 'Потребителско име:',
'config-admin-password' => 'Парола:',
@@ -1678,6 +2106,8 @@ $1
'config-subscribe' => 'Абониране за [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce пощенския списък за нови версии].',
'config-subscribe-help' => 'Това е пощенски списък с малко трафик, който се използва за съобщения при излизане на нови версии, както и за важни проблеми със сигурността.
Абонирането е препоръчително, както и надграждането на инсталацията на МедияУики при излизането на нова версия.',
+ 'config-subscribe-noemail' => 'Опитахте да се абонирате за пощенския списък за нови версии без да посочите адрес за електронна поща.
+Необходимо е да се предостави адрес за електронна поща, в случай че желаете да се абонирате за пощенския списък.',
'config-almost-done' => 'Инсталацията е почти готова!
Възможно е пропускане на оставащата конфигурация и моментално инсталиране на уикито.',
'config-optional-continue' => 'Задаване на допълнителни въпроси.',
@@ -1699,13 +2129,14 @@ $1
Уики, което е '''{{int:config-profile-fishbowl}}''' позволява на всички да преглеждат страниците, но само предварително одобрени редактори могат да редактират съдържанието.
В '''{{int:config-profile-private}}''' само предварително одобрени потребители могат да четат и редактират съдържанието.
-Детайлно обяснение на конфигурациите на потребителските права е достъпно след инсталацията в [http://www.mediawiki.org/wiki/Manual:User_rights Наръчника за потребителски права].",
+Детайлно обяснение на конфигурациите на потребителските права е достъпно след инсталацията в [//www.mediawiki.org/wiki/Manual:User_rights Наръчника за потребителски права].",
'config-license' => 'Авторски права и лиценз:',
'config-license-none' => 'Без лиценз',
'config-license-cc-by-sa' => 'Криейтив Комънс Признание-Споделяне на споделеното',
+ 'config-license-cc-by' => 'Криейтив Комънс Признание',
'config-license-cc-by-nc-sa' => 'Криейтив Комънс Признание-Некомерсиално-Споделяне на споделеното',
- 'config-license-gfdl-old' => 'Лиценз за свободна документация на GNU 1.2',
- 'config-license-gfdl-current' => 'Лиценз за свободна документация на GNU 1.3 или по-нов',
+ 'config-license-cc-0' => 'Криейтив Комънс Нула (обществено достояние)',
+ 'config-license-gfdl' => 'Лиценз за свободна документация на GNU 1.3 или по-нов',
'config-license-pd' => 'Обществено достояние',
'config-license-cc-choose' => 'Избиране на друг лиценз от Криейтив Комънс',
'config-license-help' => "Много публични уикита поставят всички приноси под [http://freedomdefined.org/Definition/Bg свободен лиценз].
@@ -1715,7 +2146,7 @@ $1
Ако е необходимо да се използват текстове от Уикипедия, както и Уикипедия да може да използва текстове от уикито, необходимо е да се избере лиценз '''Криейтив Комънс Признание-Споделяне на споделеното'''.
Лицензът за свободна документация на GNU е старият лиценз на съдържанието на Уикипедия.
-Той все още е валиден лиценз, но някои негови условия правят по-сложни повторното използване и интерпретацията.",
+Той все още е валиден лиценз, но някои негови условия са трудни за разбиране и правят по-сложни повторното използване и интерпретацията.",
'config-email-settings' => 'Настройки за е-поща',
'config-enable-email' => 'Разрешаване на изходящи е-писма',
'config-enable-email-help' => 'За да работят възможностите за използване на е-поща, необходимо е [http://www.php.net/manual/en/mail.configuration.php настройките за поща на PHP] да бъдат конфигурирани правилно.
@@ -1737,7 +2168,7 @@ $1
'config-upload-settings' => 'Картинки и качване на файлове',
'config-upload-enable' => 'Позволяне качването на файлове',
'config-upload-help' => 'Качването на файлове е възможно да доведе до пробели със сигурността на сървъра.
-Повече информация по темата има в [http://www.mediawiki.org/wiki/Manual:Security раздела за сигурност] в Наръчника.
+Повече информация по темата има в [//www.mediawiki.org/wiki/Manual:Security раздела за сигурност] в Наръчника.
За позволяване качването на файлове, необходимо е уебсървърът да може да записва в поддиректорията на МедияУики <code>images</code>.
След като това условие е изпълнено, функционалността може да бъде активирана.',
@@ -1745,16 +2176,19 @@ $1
'config-upload-deleted-help' => 'Избиране на директория, в която ще се складират изтритите файлове.
В най-добрия случай тя не трябва да е достъпна през уеб.',
'config-logo' => 'Адрес на логото:',
- 'config-logo-help' => 'Обликът по подразбиране на МедияУики вклчва място с размери 135х160 пиксела за лого в горния ляв ъгъл.
+ 'config-logo-help' => 'Обликът по подразбиране на МедияУики вклчва място с размери 135х160 пиксела за лого над страничното меню.
Ако има наличен файл с подходящ размер, неговият адрес може да бъде посочен тук.
-Ако не е необходимо лого, полето се оставя празно.',
+Ако не е необходимо лого, полето може да се остави празно.',
'config-instantcommons' => 'Включване на Instant Commons',
- 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] е функционалност, която позволява на уикитата да използват картинки, звуци и друга медиа от сайта на Уикимедия [http://commons.wikimedia.org/ Общомедия].
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] е функционалност, която позволява на уикитата да използват картинки, звуци и друга медиа от сайта на Уикимедия [//commons.wikimedia.org/ Общомедия].
За да е възможно това, МедияУики изисква достъп до Интернет.
Повече информация за тази функционалност, както и инструкции за настройване за други уикита, различни от Общомедия, е налична в [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos наръчника].',
+ 'config-cc-error' => 'Избирането на лиценз на Криейтив Комънс не даде резултат.
+Необходимо е името на лиценза да бъде въведено ръчно.',
'config-cc-again' => 'Повторно избиране...',
+ 'config-cc-not-chosen' => 'Изберете кой лиценз на Криейтив Комънс желаете и щракнете "proceed".',
'config-advanced-settings' => 'Разширена конфигурация',
'config-cache-options' => 'Настройки за обектното кеширане:',
'config-cache-help' => 'Обектното кеширане се използва за подобряване на скоростта на МедияУики чрез кеширане на често използваните данни.
@@ -1764,12 +2198,18 @@ $1
'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}}".
@@ -1778,16 +2218,24 @@ $1
'config-install-step-failed' => 'неуспешно',
'config-install-extensions' => 'Добавяне на разширенията',
'config-install-database' => 'Създаване на базата от данни',
+ 'config-install-schema' => 'Създаване на схема',
'config-install-pg-schema-not-exist' => 'PostgreSQL схемата не съществува',
'config-install-pg-schema-failed' => 'Създаването на таблиците пропадна.
Необходимо е потребител "$1" да има права за писане в схемата "$2".',
'config-install-pg-plpgsql' => 'Проверяване за езика PL/pgSQL',
'config-pg-no-plpgsql' => 'Необходимо е да се инсталира езикът PL/pgSQL в базата от данни $1',
'config-pg-no-create-privs' => 'Посочената сметка за инсталацията не притежава достатъчно права за създаване на сметка.',
+ 'config-pg-not-in-role' => 'Посочената сметка за уеб потребител вече съществува.
+Посочената сметка за инсталация не с права на суперпотребител и не е член на ролите на уеб потребителя и не може да създава обекти, собственост на уеб потребителя.
+
+Текущо МедияУики изисква таблиците да са собственост на уеб потребителя. Необходимо е да се посочи друго потребителско име за уеб или да се натисне "връщане" и да се избере друг потребител за инсталацията с подходящите права.',
'config-install-user' => 'Създаване на потребител за базата от данни',
'config-install-user-alreadyexists' => 'Потребител „$1“ вече съществува',
'config-install-user-create-failed' => 'Създаването на потребител „$1“ беше неуспешно: $2',
'config-install-user-grant-failed' => 'Предоставянето на права на потребител "$1" беше неуспешно: $2',
+ 'config-install-user-missing' => 'Посоченият потребител " $1 "не съществува.',
+ 'config-install-user-missing-create' => 'Посоченият потребител "$1" не съществува.
+Ако желаете да го създадете, поставете отметка на "създаване на сметка".',
'config-install-tables' => 'Създаване на таблиците',
'config-install-tables-exist' => "'''Предупреждение''': Таблиците за МедияУики изглежда вече съществуват.
Пропускане на създаването им.",
@@ -1797,10 +2245,11 @@ $1
'config-install-interwiki-exists' => "'''Предупреждение''': Таблицата с междууикита изглежда вече съдържа данни.
Пропускане на списъка по подразбиране.",
'config-install-stats' => 'Инициализиране на статистиките',
- 'config-install-keys' => 'Генериране на таен ключ',
+ 'config-install-keys' => 'Генериране на тайни ключове',
'config-insecure-keys' => "'''Предупреждение:''' {{PLURAL:$2|Сигурният ключ, създаден по време на инсталацията, не е напълно надежден|Сигурните ключове, създадени по време на инсталацията, не са напълно надеждни}} $1 . Обмислете да {{PLURAL:$2|го|ги}} смените ръчно.",
'config-install-sysop' => 'Създаване на администраторска сметка',
- 'config-install-subscribe-fail' => 'Невъзможно беше абонирането за mediawiki-announce',
+ 'config-install-subscribe-fail' => 'Невъзможно беше абонирането за mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'не е инсталиран cURL и allow_url_fopen не е налична.',
'config-install-mainpage' => 'Създаване на Началната страница със съдържание по подразбиране',
'config-install-extension-tables' => 'Създаване на таблици за включените разширения',
'config-install-mainpage-failed' => 'Вмъкването на Началната страница беше невъзможно: $1',
@@ -1821,6 +2270,154 @@ $3
Когато файлът вече е в основната директория, '''[$2 уикито ще е достъпно на този адрес]'''.",
'config-download-localsettings' => 'Изтегляне на LocalSettings.php',
'config-help' => 'помощ',
+ 'mainpagetext' => "'''Уикито беше успешно инсталирано.'''",
+ 'mainpagedocfooter' => 'Разгледайте [//meta.wikimedia.org/wiki/Help:Contents ръководството] за подробна информация относно използването на софтуера.
+
+== Първи стъпки ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Конфигурационни настройки]
+* [//www.mediawiki.org/wiki/Manual:FAQ ЧЗВ за МедияУики]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Пощенски списък относно нови версии на МедияУики]',
+);
+
+/** Banjar (Bahasa Banjar)
+ * @author Ezagren
+ * @author J Subhi
+ */
+$messages['bjn'] = array(
+ 'mainpagetext' => "'''MediaWiki sudah tapasang awan sukses'''.",
+ 'mainpagedocfooter' => 'Carii panjalasan [//meta.wikimedia.org/wiki/Help:Contents Panduan Pamuruk] gasan mamuruk parangkat lunak wiki
+
+== Gasan bamula ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Daptar konpigurasi setélan]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki nang rancak ditakunakan]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki rilis milis]',
+);
+
+/** Bengali (বাংলা)
+ * @author Bellayet
+ * @author Wikitanvir
+ */
+$messages['bn'] = array(
+ 'config-desc' => 'মিডিয়াউইকির জন্য ইন্সটলার',
+ 'config-title' => 'মিডিয়াউইকি $1 ইন্সটলেশন',
+ 'config-information' => 'তথ্য',
+ 'config-localsettings-key' => 'হালনাগাদ কি',
+ 'config-session-error' => 'সেশন শুরুতে ত্রুটি: $1',
+ 'config-your-language' => 'আপনার ভাষা:',
+ 'config-your-language-help' => 'ইন্সটল করা সময় ব্যবহারের জন্য ভাষা নির্বাচন করুন।',
+ 'config-wiki-language' => 'উইকি ভাষা:',
+ 'config-back' => '← পেছনে',
+ 'config-continue' => 'অব্যাহত →',
+ 'config-page-language' => 'ভাষা',
+ 'config-page-welcome' => 'মিডিয়াউইকিতে স্বাগতম!',
+ 'config-page-dbconnect' => 'ডেটাবেজে সংযোগ দিন',
+ 'config-page-upgrade' => 'ইতিমধ্যেই থাকা ইন্সটলেশন হালনাগাদ করুন',
+ 'config-page-dbsettings' => 'ডেটাবেজ সেটিংস',
+ 'config-page-name' => 'নাম',
+ 'config-page-options' => 'অপশন',
+ 'config-page-install' => 'ইন্সটল',
+ 'config-page-complete' => 'সম্পূর্ণ!',
+ 'config-page-restart' => 'পুনরায় ইন্সটল প্রক্রিয়া চালু করুন',
+ 'config-page-readme' => 'এটি পড়ুন',
+ 'config-page-releasenotes' => 'রিলিজ নোট',
+ 'config-page-copying' => 'অনুলেপন',
+ 'config-page-upgradedoc' => 'হালনাগাদকরণ',
+ 'config-page-existingwiki' => 'ইতিমধ্যেই থাকা উইকি',
+ 'config-restart' => 'হ্যাঁ, পুনরায় চালু করুন',
+ 'config-env-php' => 'পিএইচপি $1 ইন্সটল করা হয়েছে।',
+ 'config-db-type' => 'ডেটাবেজের ধরন:',
+ 'config-db-host' => 'ডেটাবেজের হোস্ট:',
+ 'config-db-install-account' => 'ইন্সটলের জন্য ব্যবহারকারী অ্যাকাউন্ট',
+ 'config-db-username' => 'ডেটাবেজের ব্যবহারকারী নাম:',
+ 'config-db-password' => 'ডেটাবেজের শব্দচাবি:',
+ 'config-db-charset' => 'ডেটাবেজের অক্ষর সেট',
+ 'config-db-port' => 'ডেটাবেজ পোর্ট:',
+ 'config-db-schema' => 'মিডিয়াউইকির স্কিমা',
+ 'config-sqlite-dir' => 'এসকিউলাইট ডেটা ডিরেক্টরি:',
+ 'config-oracle-def-ts' => 'পূর্বনির্ধারিত টেবিলস্পেস',
+ 'config-oracle-temp-ts' => 'সাময়কি টেবিলস্পেস:',
+ 'config-type-ibm_db2' => 'আইবিএম ডিবি২',
+ 'config-header-mysql' => 'মাইএসকিউএল সেটিংস',
+ 'config-header-postgres' => 'পোস্টগ্রেএসকিউএল সেটিংস',
+ 'config-header-sqlite' => 'এসকিউলাইট সেটিংস',
+ 'config-header-oracle' => 'ওরাকল সেটিংস',
+ 'config-header-ibm_db2' => 'আইবিএম ডিবি২ সেটিংস',
+ 'config-invalid-db-type' => 'ডেটাবেজের ধরন অগ্রহযোগ্য',
+ 'config-missing-db-name' => 'আপনাকে অবশ্যই "ডেটাবেজ নাম"-এর জন্য একটি মান প্রবেশ করাতে হবে',
+ 'config-missing-db-host' => 'আপনাকে অবশ্যই "ডেটাবেজ হোস্ট"-এর জন্য একটি মান প্রবেশ করাতে হবে',
+ 'config-missing-db-server-oracle' => 'আপনাকে অবশ্যই "ডেটাবেজ টিএনএস"-এর জন্য একটি মান প্রবেশ করাতে হবে',
+ 'config-mysql-engine' => 'সংরক্ষণ ইঞ্জিন:',
+ 'config-mysql-innodb' => 'ইনোডিবি',
+ 'config-mysql-myisam' => 'মাইআইএসএএম',
+ 'config-mysql-charset' => 'ডেটাবেজের অক্ষর সেট',
+ 'config-mysql-binary' => 'বাইনারি',
+ 'config-mysql-utf8' => 'ইউটিএফ-৮',
+ 'config-site-name' => 'উইকির নাম:',
+ 'config-site-name-blank' => 'একটি সাইটের নাম প্রবেশ করান।',
+ 'config-project-namespace' => 'প্রকল্প নামস্থান:',
+ 'config-ns-generic' => 'প্রকল্প',
+ 'config-ns-site-name' => 'উইকি নামের অনুরুপ: $1',
+ 'config-ns-other' => 'অন্যান্য (নির্দিষ্ট করুন)',
+ 'config-ns-other-default' => 'মাইউইকি',
+ 'config-admin-box' => 'প্রশাসক অ্যাকাউন্ট',
+ 'config-admin-name' => 'আপনার নাম:',
+ 'config-admin-password' => 'শব্দচাবি:',
+ 'config-admin-password-confirm' => 'শব্দচাবি আবারও প্রবেশ করান:',
+ 'config-admin-name-blank' => 'একটি প্রশাসক ব্যবহারকারী নাম প্রবেশ করান',
+ 'config-admin-password-blank' => 'প্রশাসক অ্যাকাউন্টের জন্য পাসওয়ার্ড প্রবেশ করান।',
+ 'config-admin-password-same' => 'পাসওয়ার্ড অবশ্যই ব্যবহারকারী নামের অনুরুপ হওয়া চলবে না।',
+ 'config-admin-password-mismatch' => 'আপনি যে দুটি শব্দচাবি দিয়েছেন তারা পরস্পর মেলেনি।',
+ 'config-admin-email' => 'ইমেইল ঠিকানা:',
+ 'config-optional-continue' => 'আরও প্রশ্ন জিজ্ঞেস করুন।',
+ 'config-optional-skip' => 'আমি ইতিমধ্যেই বিরক্ত হয়ে গেছি, উইকিটি ইন্সটল করো।',
+ 'config-profile' => 'ব্যবহারকারী অধিকার প্রোফাইল:',
+ 'config-profile-wiki' => 'গতানুগতিক উইকি',
+ 'config-profile-no-anon' => 'অ্যাকাউন্ট তৈরি করা বাধ্যতামূলক',
+ 'config-profile-fishbowl' => 'শুধুমাত্র নির্ধারিত সম্পাদকদের জন্যই',
+ 'config-profile-private' => 'ব্যক্তিগত উইকি',
+ 'config-license' => 'কপিরাইট ও লাইসেন্স:',
+ 'config-license-none' => 'কোনো লাইসেন্স ফুটার নেই',
+ 'config-license-cc-by-sa' => 'ক্রিয়েটিভ কমন্স অ্যাট্রিবিউশন শেয়ার অ্যালাইক',
+ 'config-license-cc-by-nc-sa' => 'ক্রিয়েটিভ কমন্স অ্যাট্রিবিউশন নন-কমার্শিয়াল শেয়ার অ্যালাইক',
+ 'config-license-cc-0' => 'ক্রিয়েটিভ কমন্স জিরো',
+ 'config-license-pd' => 'পাবলিক ডোমেইন',
+ 'config-license-cc-choose' => 'একটি স্বনির্ধারিত ক্রিয়েটিভ কমন্স লাইসেন্ট নির্বাচন করুন',
+ 'config-email-settings' => 'ই-মেইল সেটিংস',
+ 'config-email-user' => 'ব্যবহারকারী-থেকে-ব্যবহারকারী ই-মেইল সুবিধা সক্রিয় করো',
+ 'config-upload-settings' => 'চিত্র এবং ফাইল আপলোড',
+ 'config-upload-enable' => 'ফাইল আপলোড সক্রিয় করো',
+ 'config-upload-deleted' => 'অপসারণকৃত ফাইলের ডিরেক্টরি:',
+ 'config-logo' => 'লোগো ইউআরএল:',
+ 'config-memcached-servers' => 'মেমক্যাশেকৃত সার্ভারসমূহ:',
+ 'config-extensions' => 'এক্সটেনশন',
+ 'config-install-step-done' => 'সম্পন্ন',
+ 'config-install-step-failed' => 'ব্যর্থ',
+ 'config-install-extensions' => 'এক্সটেনশন সহকারে',
+ 'config-install-database' => 'ডেটাবেজ সেটআপ',
+ 'config-install-pg-schema-not-exist' => 'পোস্টগ্রেএসকিউএল স্কিমা খুঁজে পাওয়া যায়নি।',
+ 'config-install-tables' => 'টেবিল তৈরি',
+ 'config-install-keys' => 'গোপন কি তৈরি',
+ 'config-help' => 'সাহায্য',
+ 'mainpagetext' => "'''মিডিয়াউইকি সফলভাবে ইন্সটল করা হয়েছে।'''",
+ 'mainpagedocfooter' => 'কী ভাবে উইকি সফটওয়্যারটি ব্যবহারকার করবেন, তা জানতে [//meta.wikimedia.org/wiki/Help:Contents ব্যবহারকারী সহায়িকা] দেখুন।
+
+== কোথা থেকে শুরু করবেন ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings কনফিগারেশন সেটিংস তালিকা]
+* [//www.mediawiki.org/wiki/Manual:FAQ প্রশ্নোত্তরে মিডিয়াউইকি]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce মিডিয়াউইকি রিলিজের মেইলিং লিস্ট]',
+);
+
+/** Bishnupria Manipuri (ইমার ঠার/বিষ্ণুপ্রিয়া মণিপুরী) */
+$messages['bpy'] = array(
+ 'mainpagetext' => "'''মিডিয়াউইকি হবাবালা ইয়া ইন্সটল ইল.'''",
+ 'mainpagedocfooter' => 'উইকি সফটৱ্যার এহান আতানির বারে দরকার ইলে [//meta.wikimedia.org/wiki/Help:Contents আতাকুরার গাইড]হানর পাঙলাক নেগা।
+
+== অকরানিহান ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings কনফিগারেশন সেটিংর তালিকাহান]
+* [//www.mediawiki.org/wiki/Manual:FAQ মিডিয়া উইকি আঙলাক]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce মিডিয়া উইকির ফঙপার বারে মেইলর তালিকাহান]',
);
/** Breton (Brezhoneg)
@@ -1837,7 +2434,7 @@ $messages['br'] = array(
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.',
+Evit lakaat ar staliadur-mañ a-live, implijit update.php e plas',
'config-localsettings-key' => "Alc'hwez hizivaat :",
'config-localsettings-badkey' => "Direizh eo an alc'hwez merket ganeoc'h",
'config-upgrade-key-missing' => 'Kavet ez eus bet ur staliadur kent eus MediaWiki.
@@ -1851,6 +2448,10 @@ Kemmit LocalSettings.php evit ma vo termenet an argemmenn-se, ha klikit war « K
$1",
'config-session-error' => "Fazi e-ser loc'hañ an dalc'h : $1",
+ 'config-session-expired' => "Kloz eo an dalc'h evit doare.
+Kefluniet eo an dalc'hoù evit padout $1.
+Kreskiñ ar pad-mañ a c'hallit dre e arventenniñ <code>session.gc_maxlifetime</code> e php.ini.
+Adgrogit gant ar staliadur.",
'config-no-session' => "Kolle teo bet roadennoù ho talc'h !
Gwiriit ar restr php.ini ha bezit sur emañ staliet <code>session.save_path</code> en ur c'havlec'h a zere.",
'config-your-language' => 'Ho yezh :',
@@ -1879,10 +2480,24 @@ Gwiriit ar restr php.ini ha bezit sur emañ staliet <code>session.save_path</cod
'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-copyright' => "=== Gwiriañ aozer ha Termenoù implijout ===
+
+$1
+
+Ur meziant frank eo ar programm-mañ; gallout a rit skignañ anezhañ ha/pe kemmañ anezhañ dindan termenoù ar GNU Aotre-implijout Foran Hollek evel m'emañ embannet gant Diazezadur ar Meziantoù Frank; pe diouzh stumm 2 an aotre-implijout, pe (evel mar karit) diouzh ne vern pe stumm nevesoc'h.
+
+Ingalet eo ar programm gant ar spi e vo talvoudus met n'eus '''tamm gwarant ebet'''; hep zoken gwarant empleg ar '''varc'hadusted''' pe an '''azaster ouzh ur pal bennak'''. Gwelet ar GNU Aotre-Implijout Foran Hollek evit muioc'h a ditouroù.
+
+Sañset oc'h bezañ resevet <doclink href=Copying>un eilskrid eus ar GNU Aotre-implijout Foran Hollek</doclink> a-gevret gant ar programm-mañ; ma n'hoc'h eus ket, skrivit da Diazezadur ar Meziantoù Frank/Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, SUA pe [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html lennit anezhañ enlinenn].",
+ 'config-sidebar' => "* [//www.mediawiki.org MediaWiki degemer]
+* [//www.mediawiki.org/wiki/Help:Contents Sturlevr an implijerien]
+* [//www.mediawiki.org/wiki/Manual:Contents Sturlevr ar verourien]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAG]
+----
+* <doclink href=Readme>Lennit-me</doclink>
+* <doclink href=ReleaseNotes>Notennoù embann</doclink>
+* <doclink href=Copying>Oc'h eilañ</doclink>
+* <doclink href=UpgradeDoc>O hizivaat</doclink>",
'config-env-good' => 'Gwiriet eo bet an endro.
Gallout a rit staliañ MediaWiki.',
'config-env-bad' => "Gwiriet eo bet an endro.
@@ -1892,30 +2507,88 @@ Ne c'hallit ket staliañ MediaWiki.",
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-unicode-pure-php-warning' => "'''Diwallit''' : N'haller ket kaout an [http://pecl.php.net/intl intl PECL astenn] evit merañ reoladur Unicode, a zistro d'ar stumm gorrek emplementet e-PHP.
+Ma lakait da dreiñ ul lec'hienn darempredet-stank e vo mat deoc'h lenn un tammig bihan diwar-benn se war [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalization]. (e saozneg)",
+ 'config-unicode-update-warning' => "'''Diwallit''': ober a ra stumm staliet endalc'her skoueriekaat Unicode gant ur stumm kozh eus [http://site.icu-project.org/ levraoueg meziantoù ar raktres ICU].
+Dleout a rafec'h [//www.mediawiki.org/wiki/Unicode_normalization_considerations hizivaat] ma seblant deoc'h bezañ pouezus ober gant Unicode.",
+ 'config-no-db' => "N'eus ket bet gallet kavout ur sturier diazoù roadennoù a zere ! Ret eo deoc'h staliañ ur sturier diazoù roadennoù evit PHP.
+Skoret eo an diazoù roadennoù da-heul : $1.
+
+Ma rit gant un herberc'hiañ kenrannet, goulennit digant ho herberc'hier staliañ ur sturier diaz roadennoù azas.
+Ma kempunit PHP c'hwi hoc'h-unan, adkeflugnit-eñ en ur weredekaat un arval diaz roadennoù, da skouer en ur ober gant <code>./configure --mysql</code>.
+M'hoc'h eus staliet PHP adalek ur pakad Debian pe Ubuntu, eo ret deoc'h staliañ ar vodulenn php5-mysql ivez.",
+ 'config-no-fts3' => "'''Diwallit ''': Kempunet eo SQLite hep ar [//sqlite.org/fts3.html vodulenn FTS3]; ne vo ket posupl ober gant an arc'hwelioù klask er staliadur-mañ",
+ 'config-register-globals' => "'''Diwallit : Gweredekaet eo dibarzh <code>[http://php.net/register_globals register_globals]</code> PHP.'''
+'''Diweredekait anezhañ ma c'hallit.'''
+Mont a raio MediaWiki en-dro met fazioù surentez a c'hallo c'hoari war ho servijer",
+ 'config-magic-quotes-runtime' => "'''Fazi groñs : gweredekaet eo [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] !'''
+Breinañ a ra an dibarzh-mañ ar roadennoù en ur mod dic'hortoz.
+N'hallit ket staliañ pe ober gant MediaWiki e-keit ha m'eo gweredekaet an dibarzh-se.",
+ 'config-magic-quotes-sybase' => "'''Fazi groñs : gweredekaet eo [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] !'''
+Breinañ a ra an dibarzh-mañ ar roadennoù en ur mod dic'hortoz.
+N'hallit ket staliañ pe ober gant MediaWiki e-keit ha m'eo gweredekaet an dibarzh-se.",
+ 'config-mbstring' => "'''Fazi groñs : gweredekaet eo [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] !'''
+Degas a ra an dibarzh-mañ fazioù ha gallout a ra breinañ ar roadennoù en ur mod dic'hortoz.
+N'hallit ket staliañ pe ober gant MediaWiki e-keit ha m'eo gweredekaet an dibarzh-se.",
'config-ze1' => "'''Fazi diremed : [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mod] zo gweredekaet !'''
An dibarzh-mañ zo kaoz da zrein euzhus gant MediaWiki.
Ne c'hallit ket staliañ nag implijout MediaWiki keit ha m'eo gweredekaet an dibarzh-mañ.",
+ 'config-safe-mode' => "'''Diwallit :''' Gweredekaet eo [http://www.php.net/features.safe-mode mod surentez] PHP.
+Kudennoù a c'hall sevel abalamour da gement-se, dreist-holl ma pellgargit restroù ha ma skorit <code>math</code>.",
+ 'config-xml-bad' => "Mankout a ra modulenn XML PHP.
+Ezhomm en deus MediaWiki eus arc'hwelioù zo eus ar vodulenn-se ha ne'z aio ket en-dro gant ar c'hefluniadur zo.
+M'emaoc'h gant Mandrake, stailhit pakad php-xml.",
+ 'config-pcre' => "Evit doare e vank ar vodulenn skorañ PCRE.
+Evit mont en-dro plaen en deus ezhomm MediaWiki eus an arc'hwelioù jediñ reoliek kenglotus gant Perl.",
+ 'config-pcre-no-utf8' => "'''Fazi groñs ''': evit doare eo bet kempunet modulenn PCRE PHP hep ar skor PCRE_UTF8.
+Ezhomm en deus MediaWiki eus UTF-8 evit mont plaen en-dro.",
'config-memory-raised' => '<code>memory_limit</code> ar PHP zo $1, kemmet e $2.',
'config-memory-bad' => "'''Diwallit :''' Da $1 emañ arventenn <code>memory_limit</code> PHP.
Re izel eo moarvat.
Marteze e c'hwito ar staliadenn !",
- 'config-xcache' => 'Staliet eo [http://trac.lighttpd.net/xcache/ XCache]',
+ 'config-xcache' => 'Staliet eo [http://xcache.lighttpd.net/ 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-no-cache' => "'''Diwallit:''' N'eus ket bet gallet kavout [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] pe [http://www.iis.net/download/WinCacheForPhp WinCache].
+N'eo ket gweredekaet ar c'hrubuilhañ traezoù.",
'config-diff3-bad' => "N'eo ket bet kavet GNU diff3.",
+ 'config-imagemagick' => "ImageMagick kavet : <code>$1</code>.
+Gweredekaet e vo ar bihanaat skeudennoù ma vez gweredekaet ganeoc'h ar pellgargañ restroù.",
+ 'config-gd' => "Kavet eo bet al levraoueg c'hrafek GD enframmet.
+Gweredekaet e vo ar bihanaat skeudennoù ma vez gweredekaet an enporzhiañ restroù.",
+ 'config-no-scaling' => "N'eus ket bet gallet kavout al levraoueg GD pe ImageMagick.
+Diweredekaet e vo ar bihanaat skeudennoù.",
'config-no-uri' => "'''Fazi :''' N'eus ket tu da anavezout URI ar skript red.
Staliadur nullet.",
+ 'config-uploads-not-safe' => "'''Diwallit :'''Bresk eo ho kavlec'h pellgargañ dre ziouer <code>$1</code> rak gallout a ra erounit ne vern pe skript.
+ha pa vefe gwiriet gant MediaWiki an holl restroù pellgarget eo erbedet-groñs da [//www.mediawiki.org/wiki/Manual:Security#Upload_security serriñ ar breskter surentez-mañ] a-rao gweredekaat ar pellgargañ.",
+ 'config-brokenlibxml' => "Ur meskad stummoù PHP ha libxml2 dreinek a vez implijet gant ho reizhiad. Gallout a ra breinañ ar roadennoù e MediaWiki hag en arloadoù web all.
+Hizivait da PHP 5.2.9 pe nevesoc'h ha libxml2 2.7.3 pe nevesoc'h ([//bugs.php.net/bug.php?id=45996 draet renablet gant PHP]).
+Staliadur paouezet.",
+ 'config-using531' => "N'haller ket implijout MediaWiki gant PHP $1 abalamour d'un draen a zegas trubuilh en arventennoù kaset en ur ober dave da <code>__call()</code>.
+Hizivait ho reizhiad gant PHP 5.3.2 pe nevesoc'h, pe distroit da PHP 5.3.0 evit renkañ an dra-se.
+Staliadur paouezet.",
'config-db-type' => 'Doare an diaz roadennoù :',
'config-db-host' => 'Anv implijer an diaz roadennoù :',
+ 'config-db-host-help' => "M'emañ ho servijer roadennoù war ur servijer disheñvel, merkit amañ an anv ostiz pe ar chomlec'h IP.
+
+Ma rit gant un herberc'hiañ kenrannet, e tlefe ho herberc'hier bezañ pourchaset deoc'h un anv ostiz reizh en teulioù titouriñ.
+
+M'emaoc'h o staliañ ur servijer Windows ha ma rit gant MySQL, marteze ne'z aio ket en-dro \"localhost\" evel anv servijer. Ma ne dro ket, klaskit ober gant \"127.0.0.1\" da chomlec'h IP lechel.",
'config-db-host-oracle' => 'TNS an diaz roadennoù :',
'config-db-wiki-settings' => 'Anavezout ar wiki-mañ',
'config-db-name' => 'Anv an diaz roadennoù :',
+ 'config-db-name-help' => "Dibabit un anv evit ho wiki.
+Na lakait ket a esaouennoù ennañ.
+
+Ma ri gant un herberc'hiañ kenrannet e vo pourchaset deoc'h un anv diaz roadennoù dibar da vezañ graet gantañ gant ho herberc'hier pe e lezo ac'hanoc'h da grouiñ diazoù roadennoù dre ur banell gontrolliñ.",
'config-db-name-oracle' => 'Brastres diaz roadennoù :',
'config-db-install-account' => 'Kont implijer evit ar staliadur',
'config-db-username' => 'Anv implijer an diaz roadennoù :',
'config-db-password' => 'Ger-tremen an diaz roadennoù :',
+ 'config-db-password-empty' => "Lakait ur ger-tremen evit kont nevez an diaz roadennoù : $1.
+Ha pa vefe posupl da grouiñ kontoù hep ger-tremen, n'eo ket erbedet evit abegoù surentez.",
'config-db-install-username' => "Ebarzhit an anv implijer a vo implijet da gevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.
N'eo ket anv implijer ar gont MediaWiki, an anv implijer evit ho tiaz roadennoù eo.",
'config-db-install-password' => "Ebarzhit ar ger-tremen a vo implijet da gevreañ ouzh an diaz roadennoù e-pad an argerzh staliañ.
@@ -1931,6 +2604,9 @@ N'eo ket ar ger-tremen evit ar gont MediaWiki, ar ger-tremen evit ho tiaz roaden
'config-mysql-old' => "Rekis eo MySQL $1 pe ur stumm nevesoc'h; ober a rit gant $2.",
'config-db-port' => 'Porzh an diaz roadennoù :',
'config-db-schema' => 'Brastres evit MediaWiki',
+ 'config-db-schema-help' => "Peurliesañ e vo digudenn ar chema-mañ.
+Arabat cheñch anezho ma n'hoc'h eus ket ezhomm d'en ober.",
+ 'config-pg-test-error' => "N'haller ket kevreañ ouzh an diaz-titouroù '''$1''' : $2",
'config-sqlite-dir' => "Kavlec'h roadennoù SQLite :",
'config-oracle-def-ts' => 'Esaouenn stokañ ("tablespace") dre ziouer :',
'config-oracle-temp-ts' => "Esaouenn stokañ (''tablespace'') da c'hortoz :",
@@ -1938,19 +2614,22 @@ N'eo ket ar ger-tremen evit ar gont MediaWiki, ar ger-tremen evit ho tiaz roaden
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'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-postgres' => "* Ur reizhiad diaz titouroù brudet ha digor eo $1. Gallout a ra ober evit MySQL ([http://www.php.net/manual/en/pgsql.installation.php Penaos kempunañ PHP gant skor PostgreSQL]). Gallout a ra bezañ un nebeud drein bihan enni ha n'eo ket erbedet he implijout en un endro produiñ.",
'config-support-sqlite' => "* $1 zo ur reizhiad diaz titouroù skañv skoret eus ar c'hentañ. ([http://www.php.net/manual/en/pdo.installation.php Penaos kempunañ PHP gant skor SQLite], implijout a ra PDO)",
'config-support-oracle' => '* $1 zo un diaz titouroù kenwerzhel. ([http://www.php.net/manual/en/oci8.installation.php Penaos kempunañ PHP gant skor OCI8])',
+ 'config-support-ibm_db2' => '* Un diaz titouroù evit embregerezhioù kenwerzhel eo $1.',
'config-header-mysql' => 'Arventennoù MySQL',
'config-header-postgres' => 'Arventennoù PostgreSQL',
'config-header-sqlite' => 'Arventennoù SQLite',
'config-header-oracle' => 'Arventennoù Oracle',
+ 'config-header-ibm_db2' => 'Arventennoù IBM DB2',
'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ù"',
@@ -1966,17 +2645,32 @@ Ober hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9), arouezennoù isli
Gwiriit anv an ostiz, an anv implijer, ar ger-tremen ha klaskit en-dro.',
'config-invalid-schema' => 'Chema direizh evit MediaWiki "$1".
Grit hepken gant lizherennoù ASCII (a-z, A-Z), sifroù (0-9) hag arouezennoù islinennañ (_).',
+ 'config-db-sys-create-oracle' => "N'anavez ar stalier nemet ar c'hontoù SYSDBA evit krouiñ kontoù nevez.",
+ 'config-db-sys-user-exists-oracle' => 'Bez\' ez eus eus ar gont "$1" c\'hoazh. N\'haller ober gant SYSDBA nemet evit krouiñ kontoù nevez !',
'config-postgres-old' => "Rekis eo PostgreSQL $1 pe ur stumm nevesoc'h; ober a rit gant $2.",
+ 'config-sqlite-name-help' => "Dibabit un anv dibar d'ho wiki.
+Arabat ober gant esaouennoù pe barrennigoù-stagañ.
+Implijet e vo evit ar restr roadennoù SQLite.",
'config-sqlite-mkdir-error' => 'Ur fazi zo bet e-ser krouiñ ar c\'havlec\'h roadennoù "$1".
Gwiriañ al lec\'hiadur ha klask en-dro.',
+ 'config-sqlite-dir-unwritable' => 'Dibosupl skrivañ er c\'havlec\'h "$1".
+Cheñchit ar aotreoù evit ma c\'hallfe ar servijer web skrivañ ennañ ha klaskit en-dro.',
+ 'config-sqlite-connection-error' => "$1.
+
+Gwiriañ ar c'havlec'h roadennoù hag anv an diaz roadennoù a-is ha klaskit en-dro.",
'config-sqlite-readonly' => "N'haller ket skrivañ er restr <code>$1</code>.",
'config-sqlite-cant-create-db' => "N'haller ket krouiñ restr an diaz roadennoù <code>$1</code>.",
+ 'config-sqlite-fts3-downgrade' => "N'eo ket kenglotus ar PHP gant FTS3, o lakaat an taolennoù da glotañ gant ur stumm koshoc'h",
+ 'config-can-upgrade' => "Taolennoù MediaWiki zo en diaz titouroù.
+Da hizivaat anezho da VediaWiki $1, klikañ war '''Kenderc'hel'''.",
'config-upgrade-done-no-regenerate' => 'Hizivadenn kaset da benn.
Gallout a rit [$1 kregiñ da implijout ho wiki].',
'config-regenerate' => 'Adgenel LocalSettings.php →',
'config-show-table-status' => "C'hwitet ar reked SHOW TABLE STATUS !",
+ 'config-unknown-collation' => "'''Diwallit :''' Emañ an diaz roadennoù o renkañ an traoù diouzh un urzh lizherennek dianav.",
'config-db-web-account' => 'Kont an diaz roadennoù evit ar voned Kenrouedad',
+ 'config-db-web-help' => 'Diuzañ an anv implijer hag ar ger-tremen a vo implijet gant ar servijer web evit kevreañ ouzh ar servijer diaz roadennoù pa vez ar wiki o vont en-dro war ar pemdez.',
'config-db-web-account-same' => 'Ober gant an hevelep kont hag an hini implijet evit ar staliañ',
'config-db-web-create' => "Krouiñ ar gont ma n'eus ket anezhi c'hoazh",
'config-mysql-engine' => 'Lusker stokañ :',
@@ -1986,12 +2680,17 @@ Gallout a rit [$1 kregiñ da implijout ho wiki].',
'config-mysql-binary' => 'Binarel',
'config-mysql-utf8' => 'UTF-8',
'config-site-name' => 'Anv ar wiki :',
+ 'config-site-name-help' => "Dont a raio war wel e barrenn ditl ar merdeer hag e meur a lec'h all c'hoazh.",
'config-site-name-blank' => "Lakait anv ul lec'hienn .",
'config-project-namespace' => 'Esaouenn anv ar raktres :',
'config-ns-generic' => 'Raktres',
'config-ns-site-name' => 'Hevelep anv hag hini ar wiki : $1',
'config-ns-other' => 'All (spisaat)',
'config-ns-other-default' => 'MaWiki',
+ 'config-ns-invalid' => 'Direizh eo an esaouenn anv "<nowiki>$1</nowiki>" spisaet.
+Merkit un esaouenn anv disheñvel evit ar raktres.',
+ 'config-ns-conflict' => 'Tabut zo etre an esaouenn anv spisaet "<nowiki>$1</nowiki>" hag un esaouenn anv dre ziouer eus MediaWiki.
+Spisait un anv raktres esaouenn anv all.',
'config-admin-box' => 'Kont merour',
'config-admin-name' => "Hoc'h anv :",
'config-admin-password' => 'Ger-tremen :',
@@ -1999,13 +2698,13 @@ Gallout a rit [$1 kregiñ da implijout ho wiki].',
'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-name-invalid' => 'Direizh eo an anv implijer spisaet "<nowiki>$1</nowiki>".
+Merkit un anv implijer all.',
'config-admin-password-blank' => 'Reiñ ur ger-tremen evit kont ar merour.',
'config-admin-password-same' => "Ne c'hall ket ar ger-tremen bezañ heñvel ouzh anv ar gont.",
'config-admin-password-mismatch' => "Ne glot ket ar gerioù-tremen hoc'h eus merket an eil gant egile.",
'config-admin-email' => "Chomlec'h postel :",
- 'config-admin-email-help' => "Merkit ur chomlec'h postel amañ evit gallout resev posteloù a-berzh implijerien all eus ar wiki, adderaouekaat ho ker-tremen ha bezañ kelaouet eus ar c'hemmoù degaset d'ar pajennoù zo en ho roll evezhiañ.",
+ 'config-admin-email-help' => "Merkit ur chomlec'h postel amañ evit gallout resev posteloù a-berzh implijerien all eus ar wiki, adderaouekaat ho ker-tremen ha bezañ kelaouet eus ar c'hemmoù degaset d'ar pajennoù zo en ho roll evezhiañ. Gallout a rit lezel ar vaezienn-mañ goullo.",
'config-admin-error-user' => 'Fazi diabarzh en ur grouiñ ur merer gant an anv "<nowiki>$1</nowiki>".',
'config-admin-error-password' => 'Fazi diabarzh o lakaat ur ger-tremen evit ar merour « <nowiki>$1</nowiki> » : <pre>$2</pre>',
'config-admin-error-bademail' => "Ebarzhet hoc'h eus ur chomlec'h postel direizh.",
@@ -2021,56 +2720,94 @@ Gellout a rit tremen ar c'hefluniadur nevez ha staliañ ar wiki war-eeun.",
'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-cc-by-sa' => 'Creative Commons Deroadenn Kenrannañ heñvel',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Deroadenn Angenwerzhel Kenrannañ heñvel',
+ 'config-license-cc-0' => 'Creative Commons Zero (Domani foran)',
'config-license-pd' => 'Domani foran',
'config-license-cc-choose' => 'Dibabit un aotre-implijout Creative Commons personelaet',
'config-email-settings' => 'Arventennoù ar postel',
'config-enable-email' => 'Gweredekaat ar posteloù a ya kuit',
+ 'config-enable-email-help' => "Mar fell deoc'h ober gant ar posteler eo ret deoc'h [http://www.php.net/manual/en/mail.configuration.php kefluniañ arventennoù postel PHP] ervat.
+Mar ne fell ket deoc'h ober gant ar servij posteloù e c'hall bezañ diweredekaet amañ.",
'config-email-user' => 'Gweredekaat ar posteloù a implijer da implijer',
'config-email-user-help' => "Aotren a ra an holl implijerien da gas posteloù an eil d'egile mard eo bet gweredekaet an arc'hwel ganto en ho penndibaboù.",
+ 'config-email-usertalk' => 'Gweredekaat kemennadur pajennoù kaozeal an implijerien',
+ 'config-email-usertalk-help' => "Talvezout a ra d'an implijerien da resev kemennadennoù ma vez kemmet o fajennoù kaozeal, ma vez gweredekaet en o fenndibaboù.",
'config-email-watchlist' => "Gweredekaat ar c'hemenn listenn evezhiañ",
+ 'config-email-watchlist-help' => "Talvezout a ra d'an implijerien da resev kemennadennoù diwar-benn ar pajennoù evezhiet ganto, ma vez gweredekaet en o fenndibaboù.",
'config-email-auth' => 'Gweredekaat an dilesadur dre bostel',
'config-email-sender' => "Chomlec'h postel respont :",
- 'config-email-sender-help' => "Merkit ar chomlec'h postel da vezañ implijet da chomlec'h distreiñ ar posteloù a ya er-maez.
+ 'config-email-sender-help' => "Merkit ar chomlec'h postel da vezañ implijet da chomlec'h distreiñ ar posteloù a ya er-maez.
Di e vo kaset ar posteloù distaolet.
Niverus eo ar servijerioù postel a c'houlenn da nebeutañ un [http://fr.wikipedia.org/wiki/Nom_de_domaine anv domani] reizh.",
'config-upload-settings' => 'Pellgargañ skeudennoù ha restroù',
'config-upload-enable' => 'Gweredekaat ar pellgargañ restroù',
'config-upload-deleted' => "Kavlec'h evit ar restroù dilamet :",
+ 'config-upload-deleted-help' => "Dibab ur c'havlec'h da ziellaouiñ ar restroù diverket.
+Ar pep gwellañ e vije ma ne vije ket tu d'e dizhout adalek ar Genrouedad.",
'config-logo' => 'URL al logo :',
'config-instantcommons' => "Gweredekaat ''InstantCommons''",
+ 'config-cc-error' => "N'eus deuet disoc'h ebet gant dibaber aotreoù-implijout Creative Commons.
+Merkit anv an aotre-implijout gant an dorn.",
'config-cc-again' => 'Dibabit adarre...',
+ 'config-cc-not-chosen' => 'Dibabit an aotre-implijout Creative Commons a fell deoc\'h ober gantañ ha klikit war "kenderc\'hel".',
'config-advanced-settings' => 'Kefluniadur araokaet',
+ 'config-cache-options' => 'Arventennoù evit krubuilhañ traezoù :',
'config-cache-accel' => 'Krubuilhañ traezoù PHP (APC, 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).",
+Ret eo spisaat unan dre linenn ha spisaat ar porzh da vezañ implijet. Da skouer :
+127.0.0.1:11211
+192.168.1.25:1234",
+ 'config-memcache-needservers' => "Diuzet hoc'h eus Memcached evel seurt krubuilh met n'hoc'h eus spisaet servijer ebet.",
+ 'config-memcache-badip' => "Ur chomlec'h IP direizh hoc'h eus lakaet evit Memcached : $1.",
+ 'config-memcache-badport' => 'Niverennoù porzh Memcached a zlefe bezañ etre $1 ha $2.',
'config-extensions' => 'Astennoù',
- 'config-install-alreadydone' => "'''Diwallit''': Staliet hoc'h eus MediaWiki dija war a seblant hag emaoc'h o klask e staliañ c'hoazh.
+ 'config-extensions-help' => "N'eo ket bet detektet an astennoù rollet a-us en ho kavlec'h <code>./astennoù</code>.
+
+Marteze e vo ezhomm kefluniañ pelloc'h met gallout a rit o gweredekaat bremañ.",
+ 'config-install-alreadydone' => "'''Diwallit''': Staliet hoc'h eus MediaWiki dija war a seblant hag emaoc'h o klask e staliañ c'hoazh.
Kit d'ar bajenn war-lerc'h, mar plij.",
+ 'config-install-begin' => 'Pa vo bet pouezet ganeoc\'h war "{{int:config-continue}}" e krogo staliadur MediaWiki.
+Pouezit war Kent mar fell deoc\'h cheñch tra pe dra.',
'config-install-step-done' => 'graet',
'config-install-step-failed' => "c'hwitet",
'config-install-extensions' => 'En ur gontañ an astennoù',
'config-install-database' => 'Krouiñ an diaz roadennoù',
+ 'config-install-schema' => 'O krouiñ ar chema',
+ 'config-install-pg-schema-not-exist' => "N'eus ket eus chema PostgreSQL.",
'config-install-pg-schema-failed' => "C'hwitet eo krouidigezh an taolennoù.
Gwiriit hag-eñ e c'hall an implijer « $1 » skrivañ er brastres « $2 ».",
'config-install-pg-commit' => "O wiriekaat ar c'hemmoù",
'config-install-pg-plpgsql' => 'O wiriañ ar yezh PL/pgSQL',
'config-pg-no-plpgsql' => "Ret eo deoc'h staliañ ar yezh PL/pgSQL en diaz roadennoù $1",
+ 'config-pg-no-create-privs' => "N'eus ket gwirioù a-walc'h gant ar gont hoc'h eus merket evit ar staliadur evit gallout krouiñ ur gont.",
'config-install-user' => 'O krouiñ an diaz roadennoù implijer',
+ 'config-install-user-alreadyexists' => 'An implijer "$1" zo anezhañ dija',
+ 'config-install-user-create-failed' => 'Fazi e-ser krouiñ an implijer "$1" : $2',
+ 'config-install-user-grant-failed' => 'N\'eus ket bet gallet reiñ an aotre d\'an implijer "$1" : $2',
'config-install-tables' => 'Krouiñ taolennoù',
'config-install-tables-failed' => "'''Fazi :''' c'hwitet eo krouidigezh an daolenn gant ar fazi-mañ : $1",
+ 'config-install-interwiki' => 'O leuniañ dre ziouer an daolenn etrewiki',
'config-install-interwiki-list' => "Ne c'haller ket kavout ar restr <code>interwiki.list</code>.",
'config-install-stats' => 'O sevel ar stadegoù',
- 'config-install-keys' => "Genel an alc'hwez kuzh",
+ 'config-install-keys' => "Genel an alc'hwezioù kuzh",
'config-install-sysop' => 'Krouidigezh kont ar merour',
- 'config-install-subscribe-fail' => "Ne c'haller ket koumanantiñ da mediawiki-announce",
+ 'config-install-subscribe-fail' => "N'haller ket koumanantiñ da mediawiki-announce : $1",
'config-install-mainpage' => "O krouiñ ar bajenn bennañ gant un endalc'had dre ziouer",
+ 'config-install-extension-tables' => 'O krouiñ taolennoù evit an astennoù gweredekaet',
'config-install-mainpage-failed' => "Ne c'haller ket ensoc'hañ ar bajenn bennañ: $1",
'config-download-localsettings' => 'Pellgargañ LocalSettings.php',
'config-help' => 'skoazell',
+ 'mainpagetext' => "'''Meziant MediaWiki staliet.'''",
+ 'mainpagedocfooter' => "Sellit ouzh [//meta.wikimedia.org/wiki/Help:Contents Sturlevr an implijerien] evit gouzout hiroc'h war an doare da implijout ar meziant wiki.
+
+== Kregiñ ganti ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
);
/** Bosnian (Bosanski)
@@ -2109,10 +2846,10 @@ Provjerite vaš php.ini i provjerite da li je <code>session.save_path</code> pos
'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]
+ 'config-sidebar' => '* [//www.mediawiki.org MediaWiki Početna strana]
+* [//www.mediawiki.org/wiki/Help:Contents Vodič za korisnike]
+* [//www.mediawiki.org/wiki/Manual:Contents Vodič za administratore]
+* [//www.mediawiki.org/wiki/Manual:FAQ NPP]
----
* <doclink href=Readme>Pročitaj me</doclink>
* <doclink href=ReleaseNotes>Napomene izdanja</doclink>
@@ -2121,12 +2858,20 @@ Provjerite vaš php.ini i provjerite da li je <code>session.save_path</code> pos
'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-no-db' => 'Nije mogao biti pronađen pogodan driver za bazu podataka! Morate instalirati driver baze podataka za PHP.
+Slijedeće vrste baza podataka su podržane: $1.
+
+Ako se na dijeljenom serveru, tražite od vašeg pružaoca usluga da instalira pogodan driver za bazu podataka.
+Ako se sami kompajlirali PHP, podesite ga sa omogućenim klijentom baze podataka, koristeći naprimjer <code>./configure --with-mysql</code>.
+Ako ste instalirali PHP iz Debian ili Ubuntu paketa, možda morate instalirati i modul php5-mysql.',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] je instaliran',
'config-apc' => '[http://www.php.net/apc APC] je instaliran',
'config-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-type' => 'Vrsta baze podataka:',
+ 'config-db-host' => 'Domaćin baze podataka:',
+ 'config-db-wiki-settings' => 'Identificiraj ovu wiki',
'config-db-name' => 'Naziv baze podataka:',
'config-db-name-oracle' => 'Šema baze podataka:',
'config-header-mysql' => 'Postavke MySQL',
@@ -2142,16 +2887,104 @@ Ako želite regenerisati vašu datoteku <code>LocalSettings.php</code>, kliknite
Ovo '''nije preporučeno''' osim ako nemate problema s vašom wiki.",
'config-admin-name' => 'Vaše ime:',
'config-admin-password' => 'Šifra:',
+ 'mainpagetext' => "'''MediaViki softver is uspješno instaliran.'''",
+ 'mainpagedocfooter' => 'Kontaktirajte [//meta.wikimedia.org/wiki/Help:Contents uputstva za korisnike] za informacije o upotrebi wiki programa.
+
+== Početak ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista postavki]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki najčešće postavljana pitanja]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista E-Mail adresa MediaWiki]',
+);
+
+/** Catalan (Català) */
+$messages['ca'] = array(
+ 'mainpagetext' => "'''El programari del MediaWiki s'ha instaŀlat correctament.'''",
+ 'mainpagedocfooter' => "Consulteu la [//meta.wikimedia.org/wiki/Help:Contents Guia d'Usuari] per a més informació sobre com utilitzar-lo.
+
+== Per a començar ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Llista de característiques configurables]
+* [//www.mediawiki.org/wiki/Manual:FAQ PMF del MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Llista de correu (''listserv'') per a anuncis del MediaWiki]",
);
/** Chechen (Нохчийн)
* @author Sasan700
*/
$messages['ce'] = array(
- 'config-no-fts3' => "'''Тергам бе''': SQLite гулйина хуттург йоцуш [http://sqlite.org/fts3.html FTS3] — лахар болхбеш хир дац оцу бухца.",
+ 'config-no-fts3' => "'''Тергам бе''': SQLite гулйина хуттург йоцуш [//sqlite.org/fts3.html FTS3] — лахар болхбеш хир дац оцу бухца.",
+ 'mainpagetext' => "'''Вики-белха гlирс «MediaWiki» кхочуш дика дlахlоттийна.'''",
+ 'mainpagedocfooter' => 'Викийца болх бан хаамаш карор бу хlокху чохь [//meta.wikimedia.org/wiki/%D0%9F%D0%BE%D0%BC%D0%BE%D1%89%D1%8C:%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5 нисвохааман куьйгаллица].
+
+== Цхьаболу пайде гlирсаш ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Гlирс нисбан тарлушболу могlам];
+* [//www.mediawiki.org/wiki/Manual:FAQ Сих сиха лушдолу хаттарш а жоьпаш оцу MediaWiki];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Хаам бохьуьйту араяларца башхонца керла MediaWiki].',
);
-/** Czech (Česky) */
+/** Cebuano (Cebuano) */
+$messages['ceb'] = array(
+ 'mainpagetext' => "'''Malamposon ang pag-instalar sa MediaWiki.'''",
+ 'mainpagedocfooter' => 'Konsultaha ang [//meta.wikimedia.org/wiki/Help:Contents Giya sa mga gumagamit] alang sa impormasyon unsaon paggamit niining wiki nga software.
+
+== Pagsugod ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listahan sa mga setting sa kompigurasyon]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ sa MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list sa mga release sa MediaWiki]',
+);
+
+/** Sorani (کوردی)
+ * @author Asoxor
+ */
+$messages['ckb'] = array(
+ 'mainpagetext' => "'''میدیاویکی بە سەرکەوتوویی دامەزرا.'''",
+ 'mainpagedocfooter' => 'پرس بکە بە [//meta.wikimedia.org/wiki/Help:Contents ڕێنوێنیی بەکارھێنەران] بۆ زانیاری سەبارەت بە بەکارھێنانی نەرمامێری ویکی.
+
+== دەستپێکردن ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings لیستی ڕێکخستنەکان شێوەپێدان]
+* [//www.mediawiki.org/wiki/Manual:FAQ پرسیارە دوپاتکراوەکانی میدیاویکی (MediaWiki FAQ)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce لیستی ئیمەیلی وەشانەکانی میدیاویکی]',
+);
+
+/** Capiznon (Capiceño)
+ * @author Oxyzen
+ */
+$messages['cps'] = array(
+ 'mainpagetext' => "'''Madalag-on nga na-install ang MediaWiki.'''",
+ 'mainpagedocfooter' => 'Kunsultahon ang [//meta.wikimedia.org/wiki/Help:Pagtuytoy sa Manug-usar] para sa impormasyon sa paggamit sang wiki nga "software".
+
+==Pag-umpisa==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista sang mga setting sang konpigurayon]
+* [//www.mediawiki.org/wiki/Manual:FAQ Mga perme napangkot sa MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista sang mga ginapadal-an sang sulat sang MediaWiki]',
+);
+
+/** Crimean Turkish (Latin script) (‪Qırımtatarca (Latin)‬) */
+$messages['crh-latn'] = array(
+ 'mainpagetext' => "'''MediaWiki muvafaqiyetnen quruldı.'''",
+ 'mainpagedocfooter' => "Bu vikiniñ yol-yoruğını [//meta.wikimedia.org/wiki/Help:Contents User's Guide qullanıcı qılavuzından] ögrenip olasıñız.
+
+== Bazı faydalı saytlar ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Olucı sazlamalar cedveli];
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki boyunca sıq berilgen suallernen cevaplar];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-niñ yañı versiyalarınıñ çıquvından haber yiberüv].",
+);
+
+/** Crimean Turkish (Cyrillic script) (‪Къырымтатарджа (Кирилл)‬) */
+$messages['crh-cyrl'] = array(
+ 'mainpagetext' => "'''MediaWiki мувафакъиетнен къурулды.'''",
+ 'mainpagedocfooter' => "Бу викининъ ёл-ёругъыны [//meta.wikimedia.org/wiki/Help:Contents User's Guide къулланыджы къылавузындан] огренип оласынъыз.
+
+== Базы файдалы сайтлар ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Олуджы сазламалар джедвели];
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki боюнджа сыкъ берильген суаллернен джеваплар];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-нинъ янъы версияларынынъ чыкъувындан хабер йиберюв].",
+);
+
+/** Czech (Česky)
+ * @author Danny B.
+ */
$messages['cs'] = array(
'config-information' => 'Informace',
'config-continue' => 'Pokračovat →',
@@ -2167,6 +3000,48 @@ $messages['cs'] = array(
'config-admin-email' => 'E-mailová adresa:',
'config-email-settings' => 'Nastavení e-mailu',
'config-install-step-failed' => 'selhaly',
+ 'mainpagetext' => "'''MediaWiki byla úspěšně nainstalována.'''",
+ 'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents Uživatelská příručka] vám napoví, jak MediaWiki používat.
+
+== Začínáme ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Nastavení konfigurace]
+* [//www.mediawiki.org/wiki/Manual:FAQ Často kladené otázky o MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-mailová konference oznámení MediaWiki]',
+);
+
+/** Kashubian (Kaszëbsczi) */
+$messages['csb'] = array(
+ 'mainpagetext' => "'''MediaWiki òsta zainstalowónô.'''",
+);
+
+/** Chuvash (Чӑвашла) */
+$messages['cv'] = array(
+ 'mainpagetext' => "'''«MediaWiki» вики-движока лартасси ăнăçлă вĕçленчĕ.'''",
+ 'mainpagedocfooter' => 'Ку википе ĕçлеме пулăшакан информацине [//meta.wikimedia.org/wiki/%D0%9F%D0%BE%D0%BC%D0%BE%D1%89%D1%8C:%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5 усăç руководствинче] тупма пултаратăр.
+
+== Пулăшма пултарĕç ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Ĕнерлевсен списокĕ];
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki тăрăх час-часах ыйтакан ыйтусемпе хуравсем];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki çĕнĕ верси тухнине пĕлтерекен рассылка].',
+);
+
+/** Welsh (Cymraeg) */
+$messages['cy'] = array(
+ 'mainpagetext' => "'''Wedi llwyddo gosod meddalwedd MediaWiki yma'''",
+ 'mainpagedocfooter' => 'Ceir cymorth (yn Saesneg) ar ddefnyddio meddalwedd wici yn y [//meta.wikimedia.org/wiki/Help:Contents Canllaw Defnyddwyr] ar wefan Wikimedia.
+
+==Cychwyn arni==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Rhestr gosodiadau wrth gyflunio]
+* [//www.mediawiki.org/wiki/Manual:FAQ Cwestiynau poblogaidd ar MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Rhestr postio datganiadau MediaWiki]',
+);
+
+/** Danish (Dansk) */
+$messages['da'] = array(
+ 'mainpagetext' => "'''MediaWiki er nu installeret.'''",
+ 'mainpagedocfooter' => 'Se vores engelsksprogede [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentation om tilpasning af brugergrænsefladen] og [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide brugervejledningen] for oplysninger om opsætning og anvendelse.',
);
/** German (Deutsch)
@@ -2228,21 +3103,21 @@ Die Datei <code>php.ini</code> muss geprüft und es muss dabei sichergestellt we
'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 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 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]
+ 'config-sidebar' => '* [//www.mediawiki.org Website von MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Benutzeranleitung]
+* [//www.mediawiki.org/wiki/Manual:Contents Administratorenanleitung]
+* [//www.mediawiki.org/wiki/Manual:FAQ Häufig gestellte Fragen]
----
* <doclink href=Readme>Lies mich</doclink>
* <doclink href=ReleaseNotes>Versionsinformationen</doclink>
@@ -2258,17 +3133,16 @@ 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.
+Sofern eine Website mit großer Benutzeranzahl betrieben wird, sollten weitere Informationen auf der Webseite [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-Normalisierung (en)] gelesen werden.",
+ 'config-unicode-update-warning' => "'''Warnung:''' Die installierte Version des Unicode-Normalisierungswrappers nutzt einer ältere Version der Bibliothek des [http://site.icu-project.org/ ICU-Projekts].
+Diese sollte [//www.mediawiki.org/wiki/Unicode_normalization_considerations aktualisiert] werden, sofern auf die Verwendung von Unicode Wert gelegt wird.",
+ 'config-no-db' => 'Es konnte kein adäquater Datenbanktreiber gefunden werden. Es muss daher ein Datenbanktreiber für PHP installiert werden.
Die folgenden Datenbanksysteme werden unterstützt: $1
Sofern ein gemeinschaftlich genutzter Server für das Hosting verwendet wird, muss der Hoster gefragt werden einen adäquaten Datenbanktreiber zu installieren.
Sofern PHP selbst kompiliert wurde, muss es mit es neu konfiguriert werden, wobei der Datenbankclient zu aktivierten ist. Hierzu kann beispielsweise <code>./configure --with-mysql</code> ausgeführt werden.
Sofern PHP über die Paketverwaltung einer Debian- oder Ubuntu-Installation installiert wurde, muss das „php5-mysql“-Paket nachinstalliert werden.',
- 'config-no-fts3' => "'''Warnung:''' SQLite wurde ohne das [http://sqlite.org/fts3.html FTS3-Modul] kompiliert, so dass keine Suchfunktionen zur Verfügung stehen.",
+ 'config-no-fts3' => "'''Warnung:''' SQLite wurde ohne das [//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.",
@@ -2297,12 +3171,14 @@ MediaWiki benötigt die UTF-8-Unterstützung, um fehlerfrei lauffähig zu sein."
'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-xcache' => '[http://xcache.lighttpd.net/ 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.
+ 'config-no-cache' => "'''Warnung:''' [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] konnten nicht gefunden werden.
Das Objektcaching ist daher nicht aktiviert.",
+ 'config-mod-security' => "'''Warnung:''' Auf dem Webserver wurde [http://modsecurity.org/ ModSecurity] aktiviert. Sofern falsch konfiguriert, kann dies zu Problemen mit MediaWiki sowie anderer Software auf dem Server führen und es Benutzern ermöglichen beliebige Inhalte im Wiki einzustellen.
+Für weitere Informationen empfehlen wir die [http://modsecurity.org/documentation/ Dokumentation zu ModSecurity] oder den Kontakt zum Hoster, sofern Fehler auftreten.",
'config-diff3-bad' => 'GNU diff3 wurde nicht gefunden.',
'config-imagemagick' => 'ImageMagick wurde gefunden: <code>$1</code>.
Miniaturansichten von Bildern werden möglich sein, sobald das Hochladen von Dateien aktiviert wurde.',
@@ -2312,27 +3188,34 @@ Miniaturansichten von Bildern werden möglich sein, sobald das Hochladen von Dat
Miniaturansichten von Bildern sind daher nicht möglich.',
'config-no-uri' => "'''Fehler:''' Die aktuelle URL konnte nicht ermittelt werden.
Der Installationsvorgang wurde daher abgebrochen.",
+ 'config-no-cli-uri' => "'''Warnung''': Es wurde kein Pfad zum Skipt (--scriptpath) angegeben. Daher wird der Standardpfad genutzt: <code>$1</code>.",
+ 'config-using-server' => 'Der Servername „<nowiki>$1</nowiki>“ wird verwendet.',
+ 'config-using-uri' => 'Verwende Server-URL „<nowiki>$1$2</nowiki>“.',
'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.",
+Obwohl MediaWiki die hochgeladenen Dateien auf Sicherheitsrisiken überprüft, wird dennoch dringend empfohlen diese [//www.mediawiki.org/wiki/Manual:Security#Upload_security Sicherheitslücke] zu schließen, bevor das Hochladen von Dateien aktiviert wird.",
+ 'config-no-cli-uploads-check' => "'''Warnung''': Das Standardverzeichnis für hochgeladene Dateien (<code>$1</code>) wird nicht auf Sicherheitsanfälligkeiten bezüglich willkürlicher Skriptausführungen während der Installation des ''Call Level Interface'' (CLI) geprüft.",
'config-brokenlibxml' => 'Das System nutzt eine Kombination aus PHP- und libxml2-Versionen, die fehleranfällig ist und versteckte Datenfehler bei MediaWiki und anderen Webanwendungen verursachen kann.
-PHP muss auf Version 5.2.9 oder später sowie libxml2 auf die Version 2.7.3 oder später aktualisiert werden, um das Problem zu lösen. Installationsabbruch ([http://bugs.php.net/bug.php?id=45996 siehe hierzu die Fehlermeldung bei PHP]).',
+PHP muss auf Version 5.2.9 oder später sowie libxml2 auf die Version 2.7.3 oder später aktualisiert werden, um das Problem zu lösen. Installationsabbruch ([//bugs.php.net/bug.php?id=45996 siehe hierzu die Fehlermeldung bei PHP]).',
'config-using531' => 'MediaWiki kann nicht zusammen mit PHP $1 verwendet werden. Grund hierfür ist ein Fehler im Zusammenhang mit den Verweisparametern zu <code>__call()</code>.
PHP muss auf Version 5.3.2 oder höher oder 5.3.0 oder niedriger aktualisiert werden, um das Problem zu beheben.
Die Installation wurde abgebrochen.',
+ 'config-suhosin-max-value-length' => 'Suhosin ist installiert, was die Länge des GET-Parameters auf $1 Bytes beschränkt. Der ResouceLoader von MediaWiki wird zwar unter diesen Bedingungen funktionieren, allerdings nur mit verminderter Leistungsfähigkeit. Sofern möglich sollte der Parameter suhosin.get.max_value_length in der Datei php.ini auf 1024 oder höher festgelegt werden. Gleichzeitig muß der Parameter $wgResourceLoaderMaxQueryLength in der Datei LocalSettings.php auf den selben Wert eingestellt werden.',
'config-db-type' => 'Datenbanksystem:',
'config-db-host' => 'Datenbankserver:',
'config-db-host-help' => 'Sofern sich die Datenbank auf einem anderen Server befindet, ist hier der Servername oder die entsprechende IP-Adresse anzugeben.
Sofern ein gemeinschaftlich genutzter Server verwendet wird, sollte der Hoster den zutreffenden Servernamen in seiner Dokumentation angegeben haben.
-Sofern auf einem Windows-Server installiert und MySQL genutzt wird, funktioniert der Servername „localhost“ voraussichtlich nicht. Wenn nicht, sollte „127.0.0.1“ oder die lokale IP-Adresse angegeben werden.',
+Sofern auf einem Windows-Server installiert und MySQL genutzt wird, funktioniert der Servername „localhost“ voraussichtlich nicht. Wenn nicht, sollte „127.0.0.1“ oder die lokale IP-Adresse angegeben werden.
+
+Sofern PostgresQL genutzt wird, muss dieses Feld leer gelassen werden, um über ein Unix-Socket zu verwinden.',
'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-wiki-settings' => 'Bitte Daten zur eindeutigen Identifikation dieses Wikis angeben',
'config-db-name' => 'Datenbankname:',
'config-db-name-help' => 'Bitte einen Namen angeben, mit dem das Wiki identifiziert werden kann.
Dabei sollten keine Leerzeichen verwendet werden.
-
+
Sofern ein gemeinschaftlich genutzter Server verwendet wird, sollte der Hoster den Datenbanknamen angegeben oder aber die Erstellung einer Datenbank über ein entsprechendes Interface gestattet haben.',
'config-db-name-oracle' => 'Datenbankschema:',
'config-db-account-oracle-warn' => 'Es gibt drei von MediaWiki unterstützte Möglichkeiten Oracle als Datenbank einzurichten:
@@ -2353,7 +3236,7 @@ Obzwar es möglich ist Datenbankbenutzer ohne Passwort anzulegen, so ist dies ab
'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.
+ 'config-db-prefix-help' => 'Sofern eine Datenbank für mehrere Wikiinstallationen oder eine Wikiinstallation und eine andere Programminstallation genutzt werden soll, muss ein Datenbanktabellenpräfix angegeben werden, um Datenbankprobleme zu vermeiden.
Es können keine Leerzeichen verwendet werden.
Gewöhnlich bleibt dieses Datenfeld leer.',
@@ -2366,12 +3249,13 @@ Gewöhnlich bleibt dieses Datenfeld leer.',
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.",
+allerdings können keine Zeichen außerhalb des [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
'config-mysql-old' => 'MySQL $1 oder höher wird benötigt. MySQL $2 ist momentan vorhanden.',
'config-db-port' => 'Datenbankport:',
'config-db-schema' => 'Datenschema für MediaWiki',
'config-db-schema-help' => 'Dieses Datenschema ist in der Regel allgemein verwendbar.
Nur Änderungen daran vornehmen, sofern es gute Gründe dafür gibt.',
+ 'config-pg-test-error' => "Es kann keine Verbindung zur Datenbank '''$1''' hergestellt werden: $2",
'config-sqlite-dir' => 'SQLite-Datenverzeichnis:',
'config-sqlite-dir-help' => "SQLite speichert alle Daten in einer einzigen Datei.
@@ -2389,6 +3273,7 @@ Es ist daher zu erwägen die Datendatei an gänzlich anderer Stelle abzulegen, b
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki unterstützt die folgenden Datenbanksysteme:
$1
@@ -2398,10 +3283,12 @@ Sofern nicht das Datenbanksystem angezeigt wird, das verwendet werden soll, gibt
'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-support-ibm_db2' => '* $1 ist eine kommerzielle Unternehmensdatenbank',
'config-header-mysql' => 'MySQL-Einstellungen',
'config-header-postgres' => 'PostgreSQL-Einstellungen',
'config-header-sqlite' => 'SQLite-Einstellungen',
'config-header-oracle' => 'Oracle-Einstellungen',
+ 'config-header-ibm_db2' => 'IBM DB2-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.',
@@ -2476,6 +3363,13 @@ Das hier angegebene Konto muss bereits vorhanden sein.',
'config-mysql-engine' => 'Speicher-Engine:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Warnung:''' Es wurde MyISAM als Speicher-Engine für MySQL ausgewählt, die aus folgenden Gründen nicht die für den Einsatz mit MediaWiki empfohlene ist:
+* sie unterstützt aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen
+* sie ist anfälliger für Datenprobleme
+* sie wird von MediaWiki nicht immer adäquat unterstützt
+
+Sofern die vorhandene MySQL-Installation die Speicher-Engine InnoDB unterstützt, wird deren Verwendung eindringlich empfohlen.
+Sofern sie sie nicht unterstützt, sollte eine entsprechende Aktualisierung nunmehr Erwägung gezogen werden.",
'config-mysql-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.
@@ -2487,14 +3381,15 @@ Bei MyISAM-Datenbanken treten tendenziell häufiger Fehler auf als bei InnoDB-Da
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.",
+allerdings können keine Zeichen außerhalb des [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
+ 'config-ibm_db2-low-db-pagesize' => "Die DB2-Datenbank verfügt über einen Standardtabellenraum mit einer unzureichenden Seitengröße. Die Seitengröße muss '''32 000'' oder größer sein.",
'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-site-name-blank' => 'Den Namen des Wikis angeben.',
'config-project-namespace' => 'Name des Projektnamensraums:',
'config-ns-generic' => 'Projekt',
'config-ns-site-name' => 'Entspricht dem Namen des Wikis: $1',
- 'config-ns-other' => 'Sonstige (bitte angeben)',
+ 'config-ns-other' => 'Anderer Name (bitte angeben)',
'config-ns-other-default' => 'MeinWiki',
'config-project-namespace-help' => "Dem Beispiel von Wikipedia folgend, unterscheiden viele Wikis zwischen den Seiten für Inhalte und denen für Richtlinien. Letztere werden im „'''Projektnamensraum'''“ hinterlegt.
Alle Seiten dieses Namensraumes verfügen über einen Seitenpräfix, der nun an dieser Stelle angegeben werden kann.
@@ -2523,9 +3418,11 @@ Bitte einen abweichenden Benutzernamen angeben.',
'config-subscribe' => 'Bitte die Mailingliste [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mitteilungen zu Versionsveröffentlichungen] abonnieren.',
'config-subscribe-help' => 'Es handelt sich hierbei um eine Mailingliste mit wenigen Aussendungen, die für Mitteilungen zu Versionsveröffentlichungen, einschließlich wichtiger Sicherheitsveröffentlichungen, genutzt wird.
Diese Mailingliste sollte abonniert werden. Zudem sollte die MediaWiki-Installation stets aktualisiert werden, sobald eine neue Programmversion veröffentlicht wurde.',
+ 'config-subscribe-noemail' => 'Beim Abonnieren der Mailingliste mit Mitteilungen zu Versionsveröffentlichungen wurde keine E-Mail-Adresse angegeben.
+Bitte eine E-Mail-Adresse angeben, sofern die Mailingliste abonniert werden soll.',
'config-almost-done' => 'Der Vorgang ist fast abgeschlossen!
Die verbliebenen Konfigurationseinstellungen können übersprungen und das Wiki umgehend installiert werden.',
- 'config-optional-continue' => 'Sollen weitere Konfigurationseinstellungen vorgenommen werden?',
+ 'config-optional-continue' => 'Ja, es sollen weitere Konfigurationseinstellungen vorgenommen werden.',
'config-optional-skip' => 'Nein, das Wiki soll nun installiert werden.',
'config-profile' => 'Profil der Benutzerberechtigungen:',
'config-profile-wiki' => 'offenes Wiki',
@@ -2535,29 +3432,30 @@ Die verbliebenen Konfigurationseinstellungen können übersprungen und das 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.
+Allerdings finden etliche Menschen Wikis auch mit anderen Bearbeitungskonzepten sinnvoll. Manchmal ist es zudem nicht einfach alle Beteiligten von den Vorteilen des „Wiki-Prinzips” zu überzeugen. Darum ist diese Auswahl möglich.
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.
+Ein Wiki bei dem die '''{{int:config-profile-no-anon}}''' ist, fordert von den Benutzern eine höhere Verantwortung für ihre Bearbeitungen ein, könnte allerdings Personen, die nur gelegentlich Bearbeitungen vornehmen wollen, abschrecken. Ein Wiki für '''{{int:config-profile-fishbowl}}''' gestattet es nur bestimmten Benutzern Bearbeitungen vorzunehmen. Allerdings kann dabei die Allgemeinheit die Seiten immer noch betrachten und Änderungen nachvollziehen. Ein '''{{int:config-profile-private}}''' gestattet es nur ausgewählten Benutzern, Seiten zu betrachten sowie zu bearbeiten.
-Komplexere Konzepte zur Zugriffssteuerung können erst nach abgeschlossenem Installationsvorgang eingerichtet werden. Hierzu gibt es weitere Informationen auf der Website mit der [http://www.mediawiki.org/wiki/Manual:User_rights entsprechenden Anleitung].",
+Komplexere Konzepte zur Zugriffssteuerung können erst nach abgeschlossenem Installationsvorgang eingerichtet werden. Hierzu gibt es weitere Informationen auf der Website mit der [//www.mediawiki.org/wiki/Manual:User_rights entsprechenden Anleitung].",
'config-license' => 'Lizenz:',
'config-license-none' => 'Keine Lizenzangabe in der Fußzeile',
'config-license-cc-by-sa' => 'Creative Commons „Namensnennung, Weitergabe unter gleichen Bedingungen“',
+ 'config-license-cc-by' => 'Creative Commons „Namensnennung“',
'config-license-cc-by-nc-sa' => 'Creative Commons „Namensnennung, nicht kommerziell, Weitergabe unter gleichen Bedingungen“',
- 'config-license-cc-0' => 'Creative Commons „Zero“',
- '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-cc-0' => 'Creative Commons „Zero“ (Gemeinfreiheit)',
+ 'config-license-gfdl' => 'GNU-Lizenz für freie Dokumentation 1.3 oder höher',
'config-license-pd' => 'Gemeinfreiheit',
'config-license-cc-choose' => 'Eine benutzerdefinierte Creative-Commons-Lizenz auswählen',
'config-license-help' => 'Viele öffentliche Wikis publizieren alle Beiträge unter einer [http://freedomdefined.org/Definition/De freien Lizenz].
Dies trägt dazu bei ein Gefühl von Gemeinschaft zu schaffen und ermutigt zu längerfristiger Mitarbeit.
Dahingegen ist im Allgemeinen eine freie Lizenz auf geschlossenen Wikis nicht notwendig.
-Sofern man Texte aus der Wikipedia verwenden möchte und umgekehrt, sollte die Creative Commons-Lizens „Namensnennung, Weitergabe unter gleichen Bedingungen“ gewählt werden.
+Sofern man Texte aus der Wikipedia verwenden möchte und umgekehrt, sollte die Creative Commons-Lizenz „Namensnennung, Weitergabe unter gleichen Bedingungen“ gewählt werden.
-Die 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.',
+Die Wikipedia nutzte vormals die GNU-Lizenz für freie Dokumentation (GFDL).
+Die GFDL ist eine gültige Lizenz, die allerdings schwer zu verstehen ist.
+Es ist zudem schwierig gemäß dieser Lizenz lizenzierte Inhalten wiederzuverwenden.',
'config-email-settings' => 'E-Mail-Einstellungen',
'config-enable-email' => 'Ausgehende E-Mails ermöglichen',
'config-enable-email-help' => 'Sofern die E-Mail-Funktionen genutzt werden sollen, müssen die entsprechenden [http://www.php.net/manual/en/mail.configuration.php PHP-E-Mail-Einstellungen] richtig konfiguriert werden.
@@ -2579,7 +3477,7 @@ Bei viele E-Mail-Servern muss der Teil der E-Mail-Adresse mit der Domainangabe k
'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.
+Weitere Informationen hierzu sollen im [//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.',
@@ -2587,18 +3485,18 @@ Hernach kann diese Option aktiviert werden.',
'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.
+ 'config-logo-help' => 'Die Standardoberfläche von MediaWiki verfügt links oberhalb der Seitenleiste ü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.
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons InstantCommons] ist eine Funktion, die es Wikis ermöglicht, Bild-, Klang- und andere Mediendateien zu nutzen, die auf der Website [//commons.wikimedia.org/ Wikimedia Commons] verfügbar sind.
+Um diese Funktion 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-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:',
@@ -2624,22 +3522,30 @@ Sie könnten zusätzliche Konfigurierung erfordern, können aber bereits jetzt a
'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.',
+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-schema' => 'Datenschema wird erstellt',
+ 'config-install-pg-schema-not-exist' => 'Das PostgesSQL-Datenschema ist nicht vorhanden',
'config-install-pg-schema-failed' => 'Das Erstellen der Datentabellen ist gescheitert.
Es muss sichergestellt sein, dass der Benutzer „$1“ Schreibzugriff auf das Datenschema „$2“ hat.',
'config-install-pg-commit' => 'Änderungen anwenden',
'config-install-pg-plpgsql' => 'Suche nach der Datenbanksprache PL/pgSQL',
'config-pg-no-plpgsql' => 'Für Datenbank $1 muss die Datenbanksprache PL/pgSQL installiert werden',
'config-pg-no-create-privs' => 'Das für die Installation angegeben Konto verfügt nicht über ausreichende Berechtigungen, um ein Datenbanknutzerkonto zu erstellen.',
+ 'config-pg-not-in-role' => 'Das für den Wikibenutzer angegebene Benutzerkonto ist bereits vorhanden.
+Das für den Installationsvorgang angegebene Benutzerkonto ist kein Supernutzer und nicht Mitglied der Benutzergruppe der Wikibenutzer, so dass keine dem Wikibenutzer zugeordneten Datenobjekte erstellt werden konnten.
+
+Für MediaWiki ist es momentan erforderlich, dass die Tabellen dem Wikibenutzer zugeordnet sind. Bitte einen anderen Namen für den Wikibenutzer angeben oder „zurück“ anklicken, um einen ausreichend berechtigten Benutzer für den Installationsvorgang anzugeben.',
'config-install-user' => 'Datenbankbenutzer wird erstellt',
'config-install-user-alreadyexists' => 'Datenbankbenutzer „$1“ ist bereits vorhanden',
'config-install-user-create-failed' => 'Das Anlegen des Datenbankbenutzers „$1“ ist gescheitert: $2',
'config-install-user-grant-failed' => 'Die Gewährung der Berechtigung für Datenbankbenutzer „$1“ ist gescheitert: $2',
+ 'config-install-user-missing' => 'Der angegebene Benutzer „$1“ ist nicht vorhanden.',
+ 'config-install-user-missing-create' => 'Der angegebene Benutzer „$1“ ist nicht vorhanden.
+Bitte das Auswahlkästchen „Benutzerkonto erstellen“ anklicken, sofern dieser erstellt werden soll.',
'config-install-tables' => 'Datentabellen werden erstellt',
'config-install-tables-exist' => "'''Warnung:''' Es wurden MediaWiki-Datentabellen gefunden.
Die Erstellung wurde übersprungen.",
@@ -2648,11 +3554,12 @@ Die Erstellung wurde übersprungen.",
'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-install-stats' => 'Statistiken werden initialisiert',
+ 'config-install-keys' => 'Geheimschlüssel werden erstellt',
'config-insecure-keys' => "'''Warnung:''' {{PLURAL:$2|Der Geheimschlüssel|Die Geheimschlüssel}} $1 {{PLURAL:$2|der|die}} während des Installationsvorgangs generiert 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-subscribe-fail' => 'Abonnieren von „mediawiki-announce“ ist gescheitert: $1',
+ 'config-install-subscribe-notpossible' => 'cURL ist nicht installiert und allow_url_fopen ist nicht verfügbar.',
'config-install-mainpage' => 'Erstellung der Hauptseite mit Standardinhalten',
'config-install-extension-tables' => 'Erstellung der Tabellen für die aktivierten Erweiterungen',
'config-install-mainpage-failed' => 'Die Hauptseite konnte nicht erstellt werden: $1',
@@ -2662,20 +3569,84 @@ 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.
+Diese Datei muss nun heruntergeladen und anschließend in das Stammverzeichnis der MediaWiki-Installation hochgeladen werden. Dies ist dasselbe Verzeichnis, in dem sich auch die Datei <code>index.php</code> befindet. Das Herunterladen sollte inzwischen automatisch gestartet worden sein.
-Sofern dies nicht der Fall war, oder das Herunterladen unterbrochen wurde, kann der Vorgang durch einen Klick auf untenstehenden Link erneut gestartet werden:
+Sofern dies nicht der Fall war, oder das Herunterladen unterbrochen wurde, kann der Vorgang durch einen Klick auf den folgenden Link erneut gestartet werden:
$3
-'''Hinweis:''' 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.
+'''Hinweis:''' Sofern das Herunterladen der Konfigurationsdatei nicht jetzt durchgeführt wird, wird sie zu einem späteren Zeitpunkt nach Beenden des Installationsprogramms, nicht mehr zur Verfügung stehen.
-Sobald dies alles erledigt wurde, kann auf das '''[$2 Wiki zugegriffen werden]'''.",
+Sobald alles erledigt wurde, kann auf das '''[$2 Wiki zugegriffen werden]'''. Wir wünschen viel Spaß mit dem Wiki.",
'config-download-localsettings' => 'LocalSettings.php herunterladen',
'config-help' => 'Hilfe',
+ 'mainpagetext' => "'''MediaWiki wurde erfolgreich installiert.'''",
+ 'mainpagedocfooter' => 'Hilfe zur Benutzung und Konfiguration der Wiki-Software findest du im [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuch].
+
+== Starthilfen ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste der Konfigurationsvariablen]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]',
+);
+
+/** German (formal address) (‪Deutsch (Sie-Form)‬)
+ * @author MichaelFrey
+ */
+$messages['de-formal'] = array(
+ 'mainpagedocfooter' => 'Hilfe zur Benutzung und Konfiguration der Wiki-Software finden Sie im [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuch].
+
+== Starthilfen ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste der Konfigurationsvariablen]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]',
+);
+
+/** Zazaki (Zazaki) */
+$messages['diq'] = array(
+ 'mainpagetext' => "'''MediaWiki vıst ra ser, vıraziya.'''",
+ 'mainpagedocfooter' => 'Seba gurenayış u eyarkerdışê Wiki-Softwarey [//meta.wikimedia.org/wiki/Help:Contents İdarê karberi] de mıracaet ke.
+
+== Yardımê Sıftekerdışi ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista eyaranê vıraştışi]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki de ÇZP]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki ra lista serbest-dayışê postey]',
+);
+
+/** Lower Sorbian (Dolnoserbski) */
+$messages['dsb'] = array(
+ 'mainpagetext' => "'''MediaWiki jo se wuspěšnje instalěrowało.'''",
+ 'mainpagedocfooter' => "Pomoc pśi wužywanju softwary wiki namakajoš pód [//meta.wikimedia.org/wiki/Help:Contents User's Guide].
+
+== Na zachopjenje ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfiguracija lisćiny połoženjow]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ (pšašanja a wótegrona)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lisćina e-mailowych nakładow MediaWiki]",
+);
+
+/** Central Dusun (Dusun Bundu-liwan)
+ * @author FRANELYA
+ */
+$messages['dtp'] = array(
+ 'mainpagetext' => "'''Nopongo no do popodokot ot ModiaWiki.'''",
+ 'mainpagedocfooter' => 'Rujuko hilo [//meta.wikimedia.org/wiki/Help:Contents Ponudukan Momomoguno] kokomoi koilaan do momoguno posusuang-suangon wiki.
+
+== Kopotimpuunan ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lis papatantu nuludan]
+* [//www.mediawiki.org/wiki/Manual:FAQ Ponguhatan Koinsoruan om Simbar ModiaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lis pininsuratan pinolabus do ModiaWiki]',
+);
+
+/** Greek (Ελληνικά) */
+$messages['el'] = array(
+ 'mainpagetext' => "'''To λογισμικό MediaWiki εγκαταστάθηκε με επιτυχία.'''",
+ 'mainpagedocfooter' => 'Περισσότερες πληροφορίες σχετικά με τη χρήση και με τη ρύθμιση παραμέτρων θα βρείτε στους συνδέσμους: [//meta.wikimedia.org/wiki/MediaWiki_localisation Οδηγίες για τροποποίηση του περιβάλλοντος εργασίας] και [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Εγχειρίδιο χρήστη].',
);
/** Esperanto (Esperanto)
+ * @author Airon90
* @author Yekrats
*/
$messages['eo'] = array(
@@ -2689,11 +3660,21 @@ $messages['eo'] = array(
'config-page-options' => 'Agordoj',
'config-page-install' => 'Instali',
'config-page-complete' => 'Farita!',
+ 'mainpagetext' => "'''MediaWiki estis sukcese instalita.'''",
+ 'mainpagedocfooter' => "Konsultu la [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide User's Guide] por informo pri uzado de vikia programaro.
+
+==Kiel komenci==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listo de konfiguraĵoj] (angla)
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Oftaj Demandoj] (angla)
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki dissendolisto pri anoncoj] (angla)",
);
/** Spanish (Español)
* @author Crazymadlover
* @author Danke7
+ * @author Locos epraix
+ * @author Od1n
* @author Platonides
* @author Sanbec
* @author Translationista
@@ -2702,8 +3683,9 @@ $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-localsettings-upgrade' => 'Se ha encontrado un archivo <code>LocalSettings.php</code>.
+Para actualizar esta instalación, por favor ingresa el valor de <code>$wgUpgradeKey</code> en el cuadro de abajo.
+Lo encontrarás en LocalSettings.php.',
'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.
@@ -2746,10 +3728,15 @@ Este programa es distribuido en la esperanza de que sea útil, pero '''sin cualq
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-sidebar' => '* [//www.mediawiki.org Página principal de MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Guía del usuario]
+* [//www.mediawiki.org/wiki/Manual:Contents Guía del administrador]
+* [//www.mediawiki.org/wiki/Manual:FAQ Preguntas frecuentes]
+----
+* <doclink href=Readme>Léeme</doclink>
+* <doclink href=ReleaseNotes>Notas de la versión</doclink>
+* <doclink href=Copying>Copia</doclink>
+* <doclink href=UpgradeDoc>Actualización</doclink>',
'config-env-good' => 'El entorno ha sido comprobado.
Puedes instalar MediaWiki.',
'config-env-bad' => 'El entorno ha sido comprobado.
@@ -2757,18 +3744,16 @@ 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-pure-php-warning' => "'''Advertencia''': La [http://pecl.php.net/intl extensión intl] no está disponible para efectuar la normalización Unicode. Utilizando la implementación más lenta en PHP.
+Si tu web tiene mucho tráfico, te recomendamos leer acerca de la [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalización Unicode].",
'config-unicode-update-warning' => "'''Warning''': La versión instalada del contenedor de normalización Unicode usa una versión anterior de la biblioteca del [http://site.icu-project.org/ proyecto ICU].
-Deberás [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.",
+Deberás [//www.mediawiki.org/wiki/Unicode_normalization_considerations actualizar] si realmente deseas usar Unicode.",
+ 'config-no-db' => 'No fue posible encontrar un controlador adecuado para la base de datos! Necesitas instalar un controlador de base de datos para PHP.
+Las siguientes bases de datos son soportadas: $1.
+Si estás en alojamiento compartido, pregunta a tu proveedor el instalar un controlador de base de datos adecuado.
+Si estás compilando PHP por ti mismo, reconfigúralo con un cliente de base de datos disponible, por ejemplo usando <code>./configure --with-mysql</code>.
+Si instalaste PHP de un paquete Debian o Ubuntu, entonces también necesitas instalar el módulo php5-mysql.',
+ 'config-no-fts3' => "'''Advertencia''': SQLite está compilado sin el [//sqlite.org/fts3.html módulo FTS3]. Las funcionalidades de búsqueda no estarán disponibles en esta instalación.",
'config-register-globals' => "'''Advertencia: La opción de <code>[http://php.net/register_globals register_globals]</code> de PHP está habilitada.'''
'''Desactívela si puede.'''
MediaWiki funcionará, pero tu servidor quedará expuesto a vulnerabilidades de seguridad potenciales.",
@@ -2795,11 +3780,11 @@ MediaWiki necesita que las funciones de expresiones regulares compatibles con Pe
'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-xcache' => '[http://xcache.lighttpd.net/ 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].
+ 'config-no-cache' => "'''Advertencia:''' No pudo encontrarse [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
El caché de objetos no está habilitado.",
'config-diff3-bad' => 'GNU diff3 no se encuentra.',
'config-imagemagick' => 'ImageMagick encontrado: <code>$1</code>.
@@ -2830,20 +3815,20 @@ Si esta cuenta no existe y la cuenta de instalación tiene suficientes privilegi
'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-db-schema-help' => 'Estos esquemas usualmente estarán bien.
+Altéralos sólo si tienes la seguridad de que necesitas hacerlo.',
'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:
+ 'config-support-info' => 'MediaWiki es compatible con los siguientes sistemas de bases de datos:
-$1
+$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-postgres' => '$1 es un popular sistema de base de datos de código abierto, alternativa a MySQL. ([http://www.php.net/manual/es/pgsql.installation.php cómo compilar PHP con compatibilidad PostgreSQL]). Puede haber algunos defectos menores destacables, y no es recomendable para uso en un entorno de producción.',
'config-support-sqlite' => '* $1 es una base de datos ligera con gran compatibilidad con MediaWiki. ([http://www.php.net/manual/es/pdo.installation.php Cómo compilar PHP con compatibilidad SQLite], 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',
@@ -2853,9 +3838,9 @@ Si no encuentras en el listado el sistema de base de datos que estás intentando
'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 (_).',
+Usa sólo caracteres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_)y guiones (-).',
'config-invalid-db-prefix' => 'El prefijo de la base de datos "$1" es inválido.
-Use sólo carateres ASCII: letras (a-z, A-Z), guarismos (0-9) y guiones bajos (_).',
+Use sólo carateres ASCII: letras (a-z, A-Z), números (0-9), guiones bajos (_) y guiones (-).',
'config-connection-error' => '$1.
Verifique el servidor, el nombre de usuario y la contraseña, e intente de nuevo.',
@@ -2867,7 +3852,7 @@ 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".
+ '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.
@@ -2884,7 +3869,7 @@ Para actualizarlas a MediaWiki $1, haz clic en '''Continuar'''.",
'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.
+ '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',
@@ -2907,7 +3892,7 @@ Las bases de datos MyISAM tienden a corromperse más a menudo que las bases de d
'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-name' => 'Su nombre:',
'config-admin-password' => 'Contraseña:',
'config-admin-password-confirm' => 'Repita la contraseña:',
'config-admin-help' => 'Escribe aquí el nombre de usuario que desees, como por ejemplo "Pedro Bloggs".
@@ -2919,7 +3904,7 @@ Especifique un nombre de usuario diferente.',
'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-email-help' => 'Introduce aquí un correo electrónico que te permita recibir mensajes de otros usuarios del wiki, vuelve a configurar tu contraseña y recibe notificaciones de cambios realizados a tus páginas vigiladas. Puedes dejar este campo vacío.',
'config-admin-error-user' => 'Error interno al crear un administrador con el nombre "<nowiki>$1</nowiki>".',
'config-admin-error-password' => 'Error interno al establecer una contraseña para el administrador " <nowiki>$1</nowiki> ": <pre>$2</pre>',
'config-subscribe' => 'Suscribirse para recibir [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce avisos de nuevas versiones].',
@@ -2938,8 +3923,6 @@ Ahora puedes saltarte el resto de pasos e instalar el wiki con valores predeterm
'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',
@@ -2960,24 +3943,26 @@ Muchos servidores de correo electrónico exigen que por lo menos la parte del no
'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.
+ '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.
+ '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.
+ '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-memcached-help' => 'Lista de direcciones IP que serán usadas por Memcached.
+Deben especificarse una por cada línea y especificar el puerto a utilizar. Por ejemplo:
+127.0.0.1:11211
+192.168.1.25:1234',
'config-extensions' => 'Extensiones',
'config-extensions-help' => 'Se ha detectado en tu directorio <code>./extensions</code> las extensiones listadas arriba.
@@ -2999,8 +3984,22 @@ Asegúrate de que el usuario "$1" puede escribir en el esquema "$2".',
'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-keys' => 'Generación de claves secretas',
'config-install-sysop' => 'Creando cuenta de usuario del administrador',
+ 'mainpagetext' => "'''MediaWiki ha sido instalado con éxito.'''",
+ 'mainpagedocfooter' => 'Consulta la [//meta.wikimedia.org/wiki/Ayuda:Contenido Guía de usuario] para obtener información sobre el uso del software wiki.
+
+== Empezando ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de ajustes de configuración]
+* [//www.mediawiki.org/wiki/Manual:FAQ/es FAQ de MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo de anuncios de distribución de MediaWiki]',
+);
+
+/** Estonian (Eesti) */
+$messages['et'] = array(
+ 'mainpagetext' => "'''MediaWiki tarkvara on edukalt paigaldatud.'''",
+ 'mainpagedocfooter' => 'Juhiste saamiseks kasutamise ning konfigureerimise kohta vaata palun inglisekeelset [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentatsiooni liidese kohaldamisest]
+ning [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide kasutusjuhendit].',
);
/** Basque (Euskara)
@@ -3029,12 +4028,12 @@ $messages['eu'] = array(
'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-sidebar' => '* [//www.mediawiki.org MediaWiki nagusia]
+* [//www.mediawiki.org/wiki/Help:Contents Erabiltzaileentzako Gida]
+* [//www.mediawiki.org/wiki/Manual:Contents Administratzaileentzako Gida]
+* [//www.mediawiki.org/wiki/Manual:FAQ MEG]',
'config-env-php' => 'PHP $1 instalatuta dago.',
- 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] instalatuta dago',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ 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',
@@ -3075,15 +4074,76 @@ $messages['eu'] = array(
'config-email-settings' => 'E-posta hobespenak',
'config-logo' => 'Logo URL:',
'config-install-step-done' => 'egina',
+ 'mainpagetext' => "'''MediaWiki arrakastaz instalatu da.'''",
+ 'mainpagedocfooter' => 'Ikus [//meta.wikimedia.org/wiki/Help:Contents Erabiltzaile Gida] wiki softwarea erabiltzen hasteko informazio gehiagorako.
+
+== Nola hasi ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigurazio balioen zerrenda]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ (Maiz egindako galderak)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWikiren argitalpenen posta zerrenda]',
+);
+
+/** Extremaduran (Estremeñu) */
+$messages['ext'] = array(
+ 'mainpagetext' => "'''MeyaGüiqui s'á istalau satihatoriamenti.'''",
+ 'mainpagedocfooter' => "Consurta la [//meta.wikimedia.org/wiki/Help:Contents User's Guide] pa sabel mas al tentu el huncionamientu el software güiqui.
+
+== Esminciandu ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
);
/** Persian (فارسی)
* @author Mjbmr
*/
$messages['fa'] = 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-name' => 'نام',
+ 'config-page-options' => 'گزینه‌ها',
+ 'config-page-install' => 'نصب',
+ 'config-page-complete' => 'کامل!',
+ 'config-page-readme' => 'مرا بخوان',
+ 'config-page-releasenotes' => 'یادداشت‌های انتشار',
+ 'config-page-existingwiki' => 'ویکی موجود',
+ 'config-db-type' => 'نوع پایگاه اطلاعات:',
+ 'config-db-host' => 'میزبان پایگاه اطلاعات:',
+ 'config-db-username' => 'نام کاربری پایگاه اطلاعات:',
+ 'config-db-password' => 'کلمه عبور پایگاه اطلاعات:',
+ 'config-site-name' => 'نام ویکی:',
+ 'config-site-name-blank' => 'نام تارنما را وارد کنید.',
+ 'config-project-namespace' => 'فضای نام پروژه:',
+ 'config-admin-name' => 'نام شما:',
+ 'config-admin-password' => 'کلمه عبور:',
+ 'config-admin-password-confirm' => 'دوباره کلمه عبور:',
+ 'config-admin-email' => 'پست الکترونیکی شما:',
+ 'config-profile-private' => 'ویکی خصوصی',
+ 'config-license' => 'حق تکثیر و مجوز:',
+ 'config-license-cc-choose' => 'انتخاب یک مجوز سفارشی عوام خلاق',
+ 'config-email-settings' => 'تنظیمات پست الکترونیکی',
+ 'config-upload-enable' => 'فعال سازی بارگذاری پرونده',
+ 'config-install-step-done' => 'انجام شد',
+ 'config-install-step-failed' => 'ناموفق بود',
+ 'config-help' => 'راهنما',
+ 'mainpagetext' => "'''نرم‌افزار ویکی با موفقیت نصب شد.'''",
+ 'mainpagedocfooter' => 'از [//meta.wikimedia.org/wiki/Help:Contents راهنمای کاربران]
+برای استفاده از نرم‌افزار ویکی کمک بگیرید.
+
+== آغاز به کار ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings تنظیم پیکربندی]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki پرسش‌های متداول]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce فهرست پست الکترونیکی نسخه‌های مدیاویکی]',
);
/** Finnish (Suomi)
@@ -3100,10 +4160,12 @@ $messages['fi'] = array(
'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-localsettings-key' => 'Päivitysavain',
+ 'config-localsettings-badkey' => 'Antamasi avain on virheellinen.',
'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.
+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.',
@@ -3122,35 +4184,39 @@ Käynnistä asennusprosessi uudelleen.',
'config-page-complete' => 'Valmis!',
'config-page-restart' => 'Aloita asennus alusta',
'config-page-readme' => 'Lue minut',
- 'config-page-releasenotes' => 'Julkaisun tiedot',
+ 'config-page-releasenotes' => 'Julkaisutiedot',
'config-page-copying' => 'Kopiointi',
'config-page-upgradedoc' => 'Päivittäminen',
+ 'config-page-existingwiki' => 'Aikaisempi asennus',
'config-help-restart' => 'Haluatko poistaa kaikki annetut tiedot ja aloittaa asennuksen alusta?',
'config-restart' => 'Kyllä',
'config-welcome' => '=== Ympäristön tarkistukset ===
Varmistetaan MediaWikin asennettavuus tähän ympäristöön.
Sinun pitäisi antaa näiden tarkistusten tulokset, jos tarvitset apua asennuksen aikana.',
- 'config-sidebar' => '* [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-sidebar' => '* [//www.mediawiki.org MediaWikin kotisivu]
+* [//www.mediawiki.org/wiki/Help:Contents Käyttöopas]
+* [//www.mediawiki.org/wiki/Manual:Contents Hallintaopas]
+* [//www.mediawiki.org/wiki/Manual:FAQ UKK]
+----
+* <doclink href=Readme>Lue minut</doclink>
+* <doclink href=ReleaseNotes>Julkaisutiedot</doclink>
+* <doclink href=Copying>Kopiointi</doclink>
+* <doclink href=UpgradeDoc>Päivittäminen</doclink>',
'config-env-good' => 'Asennusympäristö on tarkastettu.
Voit asentaa MediaWikin.',
'config-env-bad' => 'Asennusympäristö on tarkastettu.
Et voi asentaa MediaWikiä.',
'config-env-php' => 'PHP $1 on asennettu.',
- 'config-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-no-db' => 'Sopivaa tietokanta-ajuria ei löytynyt! Sinun täytyy asentaa tietokanta-ajurit PHP:lle.
+Seuraavat tietokantatyypit ovat tuettuja: $1.',
'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-memory-bad' => "'''Varoitus:''' PHP:n <code>memory_limit</code> on $1.
+Tämä on luultavasti liian alhainen.
+Asennus saattaa epäonnistua!",
+ 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] on asennettu',
'config-apc' => '[http://www.php.net/apc APC] on asennettu.',
'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] on asennettu',
'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] on asennettu',
@@ -3170,24 +4236,37 @@ MediaWiki vaatii toimiakseen Perl-yhteensopivat säännölliset lausekkeet.',
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-support-ibm_db2' => '* $1 on kaupallinen tietokanta yrityskäyttöön.',
'config-header-mysql' => 'MySQL-asetukset',
'config-header-postgres' => 'PostgreSQL-asetukset',
'config-header-sqlite' => 'SQLite-asetukset',
'config-header-oracle' => 'Oracle-asetukset',
+ 'config-header-ibm_db2' => 'IBM DB2 -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 (_).',
+Käytä ainoastaan kirjaimia (a-z, A-Z), numeroita (0-9), alaviivoja (_) ja tavuviivoja (-).',
'config-invalid-db-prefix' => '”$1” ei kelpaa tietokannan etuliitteeksi.
-Se voi sisältää vain kirjaimia (a-z, A-Z), numeroita (0-9) ja alaviivan (_).',
+Käytä ainoastaan kirjaimia (a-z, A-Z), numeroita (0-9), alaviivoja (_) ja tavuviivoja (-).',
'config-postgres-old' => 'MediaWiki tarvitsee PostgreSQL:n version $1 tai uudemman. Nykyinen versio on $2.',
- 'config-sqlite-name-help' => 'Valitse nimi joka yksilöi tämän wikin.
+ '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ä.',
+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-upgrade-done' => "Päivitys valmis.
+
+Voit [$1 aloittaa wikin käytön].
+
+Napsauta alla olevaa painiketta, jos haluat luoda uudelleen <code>LocalSettings.php</code>-tiedoston.
+Tämä '''ei ole suositeltavaa''', jos wikissäsi ei ole ongelmia.",
+ 'config-upgrade-done-no-regenerate' => 'Päivitys valmis.
+
+Voit [$1 aloittaa wikin käytön].',
+ 'config-regenerate' => 'Luo LocalSettings.php uudelleen →',
'config-show-table-status' => 'Kysely SHOW TABLE STATUS epäonnistui!',
'config-mysql-engine' => 'Tallennusmoottori',
'config-mysql-innodb' => 'InnoDB',
@@ -3195,28 +4274,65 @@ Muuta hakemiston käyttöoikeuksia siten, että palvelinohjelmisto voi kirjoitta
'config-mysql-binary' => 'Binääri',
'config-mysql-utf8' => 'UTF-8',
'config-site-name' => 'Wikin nimi',
- 'config-project-namespace' => 'Projektinimiavaruus:',
+ 'config-project-namespace' => 'Projektinimiavaruus',
'config-ns-generic' => 'Projekti',
+ 'config-ns-site-name' => 'Sama kuin wikin nimi: $1',
+ 'config-ns-other' => 'Muu (määritä)',
'config-admin-name' => 'Nimesi',
'config-admin-password' => 'Salasana',
'config-admin-password-confirm' => 'Salasana uudelleen',
'config-admin-name-blank' => 'Anna ylläpitäjän käyttäjänimi.',
'config-admin-email' => 'Sähköpostiosoite',
+ 'config-almost-done' => 'Olet jo lähes valmis!
+Voit ohittaa jäljellä olevat määritykset ja asentaa wikin juuri nyt.',
+ 'config-profile-wiki' => 'Perinteinen wiki',
+ 'config-profile-no-anon' => 'Tunnuksen luonti vaaditaan',
'config-profile-private' => 'Yksityinen wiki',
+ 'config-email-settings' => 'Sähköpostiasetukset',
+ 'config-logo' => 'Logon URL-osoite',
+ 'config-cc-again' => 'Valitse uudelleen...',
+ 'config-extensions' => 'Laajennukset',
'config-install-step-done' => 'tehty',
'config-install-step-failed' => 'epäonnistui',
+ 'config-download-localsettings' => 'Lataa LocalSettings.php',
'config-help' => 'ohje',
+ 'mainpagetext' => "'''MediaWiki on onnistuneesti asennettu.'''",
+ 'mainpagedocfooter' => "Lisätietoja käytöstä on sivulla [//meta.wikimedia.org/wiki/Help:Contents User's Guide].
+
+=== Lisäohjeita ===
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Asetusten teko-ohjeita]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWikin FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Sähköpostilista, jolla tiedotetaan MediaWikin uusista versioista]
+
+=== Asetukset ===
+
+Tarkista, että alla olevat taivutusmuodot ovat oikein. Jos eivät, tee tarvittavat muutokset LocalSettings.php:hen seuraavasti:
+ \$wgGrammarForms['fi']['genitive']['{{SITENAME}}'] = '...';
+ \$wgGrammarForms['fi']['partitive']['{{SITENAME}}'] = '...';
+ \$wgGrammarForms['fi']['elative']['{{SITENAME}}'] = '...';
+ \$wgGrammarForms['fi']['inessive']['{{SITENAME}}'] = '...';
+ \$wgGrammarForms['fi']['illative']['{{SITENAME}}'] = '...';
+Taivutusmuodot: {{GRAMMAR:genitive|{{SITENAME}}}} (yön) — {{GRAMMAR:partitive|{{SITENAME}}}} (yötä) — {{GRAMMAR:elative|{{SITENAME}}}} (yöstä) — {{GRAMMAR:inessive|{{SITENAME}}}} (yössä) — {{GRAMMAR:illative|{{SITENAME}}}} (yöhön).",
+);
+
+/** Faroese (Føroyskt) */
+$messages['fo'] = array(
+ 'mainpagetext' => "'''Innlegging av Wiki-ritbúnaði væleydnað.'''",
);
/** French (Français)
* @author Aadri
* @author Crochet.david
+ * @author Gomoko
+ * @author Grondin
* @author Hashar
* @author IAlex
* @author Jean-Frédéric
* @author McDutchie
* @author Peter17
* @author Sherbrooke
+ * @author Urhixidur
* @author Verdy p
* @author Yumeki
*/
@@ -3227,7 +4343,7 @@ $messages['fr'] = array(
'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é.
+ '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',
@@ -3285,10 +4401,10 @@ Ce programme est distribué dans l’espoir qu’il sera utile, mais '''sans auc
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]
+ 'config-sidebar' => '* [//www.mediawiki.org MediaWiki Accueil]
+* [//www.mediawiki.org/wiki/Help:Contents Guide de l’utilisateur]
+* [//www.mediawiki.org/wiki/Manual:Contents Guide de l’administrateur]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
----
* <doclink href=Readme>Lisez-moi</doclink>
* <doclink href=ReleaseNotes>Notes de pblication</doclink>
@@ -3299,22 +4415,20 @@ 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é.
+ '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).",
+Si vous utilisez un site web très fréquenté, vous devriez lire ceci : [//www.mediawiki.org/wiki/Unicode_normalization_considerations ''Unicode normalization''] (en anglais).",
'config-unicode-update-warning' => "'''Attention''': La version installée du ''wrapper'' de normalisation Unicode utilise une vieille version de la [http://site.icu-project.org/ bibliothèque logicielle ''ICU Project''].
-Vous devriez faire une [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.",
+Vous devriez faire une [//www.mediawiki.org/wiki/Unicode_normalization_considerations mise à jour] (texte en anglais) si l'usage d'Unicode vous semble important.",
+ 'config-no-db' => "Impossible de trouver un pilote de base de données approprié ! Vous devez installer un pilote pour PHP. Ces types de bases de données sont reconnus : $1.
+
+Si vous êtes sur un site partagé, demandez à votre hébergeur d'installer un pilote de base de données approprié. Si vous avez compilé PHP, le configurer avec client de base de données activé, par exemple en insérant la directive <code>./configure --with-mysql</code>.
+
+Si vous avez installé PHP d'une distribution Debian ou Ubuntu, vous devez aussi installer le module <code>php5-mysql</code>.",
+ 'config-no-fts3' => "'''Attention :''' SQLite est compilé sans le module [//sqlite.org/fts3.html FTS3] ; les fonctions de recherche ne seront pas disponibles sur ce moteur.",
'config-register-globals' => "'''Attention : l'option <code>[http://php.net/register_globals register_globals]</code> de PHP est activée.'''
'''Désactivez-la si vous le pouvez.'''
MediaWiki fonctionnera, mais votre serveur sera exposé à de potentielles failles de sécurité.",
@@ -3335,44 +4449,53 @@ Ceci peut causer des problèmes, en particulier si vous utilisez le téléversem
'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' => 'Le module de support PCRE semble manquer.
+MediaWiki requiert les fonctions d’expressions rationnelles compatibles avec Perl.',
'config-pcre-no-utf8' => "'''Erreur fatale''': Le module PCRE de PHP semble être compilé sans le support PCRE_UTF8.
MédiaWiki nécessite la gestion d’UTF-8 pour fonctionner correctement.",
'config-memory-raised' => 'Le paramètre <code>memory_limit</code> de PHP était à $1, porté à $2.',
'config-memory-bad' => "'''Attention :''' Le paramètre <code>memory_limit</code> de PHP est à $1.
Cette valeur est probablement trop faible.
Il est possible que l’installation échoue !",
- 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] est installé',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ 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].
+ 'config-no-cache' => "'''Attention :''' Impossible de trouver [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].
La mise en cache d'objets n'est pas activée.",
+ 'config-mod-security' => "'''Attention''': Votre serveur web a [http://modsecurity.org/ mod_security] activé. S&il est mal configuré, cela peut poser des problèmes à MediaWiki ou à d'autres applications qui permettent aux utilisateurs de publier un contenu quelconque.
+Reportez-vous à [http://modsecurity.org/documentation/ la documentation de mod_security] ou contactez le support de votre hébergeur si vous rencontrez des erreurs aléatoires.",
'config-diff3-bad' => 'GNU diff3 introuvable.',
- 'config-imagemagick' => "ImageMagick trouvé : <code>$1</code>.
+ '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.
+ '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.
+ '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.
+ '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-no-cli-uri' => "'''Attention''': Aucun --scriptpath n'a été spécifié; <code>$1</code> sera utilisé par défaut",
+ 'config-using-server' => 'En utilisant le nom du serveur "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'Utilise l\'URL du serveur "<nowiki>$1$2</nowiki>".',
+ 'config-uploads-not-safe' => "'''Attention:''' Votre répertoire par défaut pour les téléchargements, <code>$1</code>, est vulnérable, car il peut exécuter n'importe quel script.
+Bien que MediaWiki vérifie tous les fichiers téléchargés, il est fortement recommandé de [//www.mediawiki.org/wiki/Manual:Security#Upload_security fermer cette vulnérabilité de sécurité] (texte en anglais) avant d'activer les téléchargements.",
+ 'config-no-cli-uploads-check' => "'''Attention:''' Votre répertoire par défaut pour les imports(<code>$1</code>) n'est pas contrôlé concernant la vulnérabilité d'exécution de scripts arbitraires lors de l'installation CLI.",
'config-brokenlibxml' => 'Votre système utilise une combinaison de versions de PHP et libxml2 qui est boguée et peut engendrer des corruptions cachées de données dans MediaWiki et d’autres applications web.
-Veuillez mettre à jour votre système vers PHP 5.2.9 ou plus récent et libxml2 2.7.3 ou plus récent ([http://bugs.php.net/bug.php?id=45996 bogue déposé auprès de PHP]).
+Veuillez mettre à jour votre système vers PHP 5.2.9 ou plus récent et libxml2 2.7.3 ou plus récent ([//bugs.php.net/bug.php?id=45996 bogue déposé auprès de PHP]).
Installation interrompue.',
'config-using531' => 'MediaWiki ne peut pas être utilisé avec PHP $1 à cause d’un bogue affectant les paramètres passés par référence à <code>__call()</code>.
Veuillez mettre à jour votre système vers PHP 5.3.2 ou plus récent ou revenir à PHP 5.3.0 pour résoudre ce problème.
Installation interrompue.',
+ 'config-suhosin-max-value-length' => 'Suhosin est installé et limite la longueur du paramètre GET à $1 octets. Le <code>ResourceLoader</code> de MediaWiki va répondre en respectant cette limite, mais ses performances seront dégradées. Si possible, vous devriez définir <code>suhosin.get.max_value_length</code> à 1024 ou plus dans le fichier <code>php.ini</code>, et fixer <code>$wgResourceLoaderMaxQueryLength</code> à la même valeur dans <code>LocalSettings.php</code>.',
'config-db-type' => 'Type de base de données :',
'config-db-host' => 'Nom d’hôte de la base de données :',
- 'config-db-host-help' => "Si votre serveur de base de données est sur un serveur différent, saisissez ici son nom d’hôte ou son adresse IP.
+ 'config-db-host-help' => 'Si votre serveur de base de données est sur un serveur différent, saisissez ici son nom d’hôte ou son adresse IP.
Si vous utilisez un hébergement mutualisé, votre hébergeur doit vous avoir fourni le nom d’hôte correct dans sa documentation.
-Si vous installez sur un serveur Windows et utilisez MySQL, « localhost » peut ne pas fonctionner comme nom de serveur. S'il ne fonctionne pas, essayez « 127.0.0.1 » comme adresse IP locale.",
+Si vous installez sur un serveur Windows et utilisez MySQL, « localhost » peut ne pas fonctionner comme nom de serveur. S’il ne fonctionne pas, essayez « 127.0.0.1 » comme adresse IP locale.
+
+Si vous utilisez PostgreSQL, laissez ce champ vide pour vous connecter via un socket Unix.',
'config-db-host-oracle' => 'Nom TNS de la base de données :',
'config-db-host-oracle-help' => 'Entrez un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nom de connexion locale] valide ; un fichier tnsnames.ora doit être visible par cette installation.<br /> Si vous utilisez les bibliothèques clientes version 10g ou plus récentes, vous pouvez également utiliser la méthode de nommage [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
'config-db-wiki-settings' => 'Identifier ce wiki',
@@ -3382,9 +4505,9 @@ 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 :
+ '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.
+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",
@@ -3397,32 +4520,33 @@ Bien qu'il soit possible de créer un compte sans mot de passe, ce n'est pas rec
'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.
+ '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.
+ '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 !
+ '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).",
+En ''mode binaire'', MediaWiki stocke le texte UTF-8 dans des champs binaires de la base de données. C'est plus efficace que le ''mode UTF-8'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.
+En ''mode UTF-8'', MySQL connaîtra le jeu de caractères de vos données et pourra présenter et convertir les données de manière appropriée, mais il ne vous laissera pas stocker les caractères au-dessus du [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingue de base] (en anglais).",
'config-mysql-old' => 'MySQL $1 ou version ultérieure est requis, vous avez $2.',
'config-db-port' => 'Port de la base de données :',
'config-db-schema' => 'Schéma pour MediaWiki',
'config-db-schema-help' => "Les schémas ci-dessus sont généralement corrects.
Ne les changez que si vous êtes sûr que c'est nécessaire.",
+ 'config-pg-test-error' => "Impossible de se connecter à la base de données '''$1''' : $2",
'config-sqlite-dir' => 'Dossier des données SQLite :',
- 'config-sqlite-dir-help' => "SQLite stocke toutes les données dans un fichier unique.
+ '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.
+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.
+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.
@@ -3434,19 +4558,22 @@ Envisagez de placer la base de données ailleurs, par exemple dans <code>/var/li
'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 :
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-support-info' => "MediaWiki supporte ces systèmes de bases de données :
-$1
+$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-postgres' => "* $1 est un système de base de données populaire et ''open source'' qui peut être une alternative à MySQL ([http://www.php.net/manual/en/pgsql.installation.php how to compile PHP with PostgreSQL support]). Il peut contenir quelques bogues mineurs et n'est pas recommandé dans un environnement de production.",
'config-support-sqlite' => '* $1 est un système de base de données léger qui est bien supporté. ([http://www.php.net/manual/en/pdo.installation.php How to compile PHP with SQLite support], utilise PDO)',
'config-support-oracle' => '* $1 est un système commercial de gestion de base de données d’entreprise. ([Http://www.php.net/manual/en/oci8.installation.php Comment compiler PHP avec le support OCI8])',
+ 'config-support-ibm_db2' => "* $1 est une base de données d'entreprise commerciale.",
'config-header-mysql' => 'Paramètres de MySQL',
'config-header-postgres' => 'Paramètres de PostgreSQL',
'config-header-sqlite' => 'Paramètres de SQLite',
'config-header-oracle' => 'Paramètres d’Oracle',
+ 'config-header-ibm_db2' => 'paramètres de IBM DB2',
'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 »",
@@ -3455,41 +4582,41 @@ Si vous ne voyez pas le système de base de données que vous essayez d'utiliser
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 ».
+ '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 ».
+ '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.
+ '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 :
+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
+<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.
+ '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 :
+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
+<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 ».
+ '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.
@@ -3497,13 +4624,13 @@ Vérifier le répertoire des données et le nom de la base de données ci-dessou
'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.
+ '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.
+ 'config-upgrade-done' => "Mise à jour complétée.
-Vous pouvez maintenant [$1 commencer à utiliser votre wiki].
+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.
+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.
@@ -3515,21 +4642,27 @@ Vous pouvez maintenant [$1 commencer à utiliser votre wiki].',
'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.
+ '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].
+ 'config-mysql-myisam-dep' => "''' Avertissement ''': vous avez sélectionné MyISAM comme moteur de stockage pour MySQL, ce qui n'est pas recommandé pour une utilisation avec MediaWiki, parce que:
+ * il supporte à peine la simultanéité en raison de verrouillage de table
+ * il est plus sujet à la corruption que les autres moteurs
+ * le codebase MediaWiki ne gère pas toujours MyISAM comme il se doit
+Si votre installation MySQL supporte InnoDB, il est fortement recommandé que vous le choisissez plutôt. Si votre installation MySQL ne supporte pas les tables InnoDB, il est peut-être temps de faire une une mise à niveau.",
+ '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.
+ '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).",
+En ''mode binaire'', MediaWiki stocke le texte UTF-8 dans des champs binaires de la base de données. C'est plus efficace que le ''mode UTF-8'' de MySQL, et vous permet d'utiliser toute la gamme des caractères Unicode.
+En ''mode UTF-8'', MySQL connaîtra le jeu de caractères de vos données et pourra présenter et convertir les données de manière appropriée, mais il ne vous laissera pas stocker les caractères au-dessus du [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingue de base] (en anglais).",
+ 'config-ibm_db2-low-db-pagesize' => "Votre base de données DB2 a un espace de stockage par défaut avec un pagesize insuffisant. Le pagesize doit être au minimum '''32K'''.",
'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.',
@@ -3538,21 +4671,21 @@ En ''mode UTF-8'', MySQL connaîtra le jeu de caractères de vos données et pou
'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.
+ '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.
+ '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.
+ '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 ».
+ '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.
+ '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.",
@@ -3563,8 +4696,9 @@ Indiquez un nom d'utilisateur différent.",
'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.
+ 'config-subscribe-help' => "Il s'agit d'une liste de diffusion à faible volume utilisée servant à annoncer les nouvelles versions, y compris les versions améliorant la sécurité du logiciel.
Vous devriez y souscrire et mettre à jour votre version de MediaWiki lorsque de nouvelles versions sont publiées.",
+ 'config-subscribe-noemail' => "Vous avez essayé de vous abonner à la liste de diffusion des communiqués, sans fournir une adresse courriel ! S'il vous plaît, fournir une adresse électronique si vous souhaitez vous abonner à la liste de diffusion.",
'config-almost-done' => 'Vous avez presque fini !
Vous pouvez passer la configuration restante et installer immédiatement le wiki.',
'config-optional-continue' => 'Me poser davantage de questions.',
@@ -3580,33 +4714,33 @@ Avec MediaWiki, il est facile de vérifier les modifications récentes et de ré
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-wiki}}''' autorise toute personne à modifier, y compris sans s’identifier.
'''{{int:config-profile-no-anon}}''' fournit plus de contrôle, par l’identification, mais peut rebuter les contributeurs occasionnels.
'''{{int:config-profile-fishbowl}}''' autorise la modification par les utilisateurs approuvés, mais le public peut toujours lire les pages et leur historique.
'''{{int:config-profile-private}}''' n’autorise que les utilisateurs approuvés à voir et modifier les pages.
-Des configurations de droits d’utilisateurs plus complexes sont disponibles après l'installation, voir la [http://www.mediawiki.org/wiki/Manual:User_rights page correspondante du manuel].",
+Des configurations de droits d’utilisateurs plus complexes sont disponibles après l'installation, voir la [//www.mediawiki.org/wiki/Manual:User_rights page correspondante du manuel].",
'config-license' => "Droits d'auteur et licence :",
'config-license-none' => 'Aucune licence en bas de page',
'config-license-cc-by-sa' => "Creative Commons attribution partage à l'identique",
+ 'config-license-cc-by' => 'Creative Commons Attribution',
'config-license-cc-by-nc-sa' => "Creative Commons 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-cc-0' => 'Creative Commons Zero (domaine public)',
+ 'config-license-gfdl' => 'GNU Free Documentation License 1.3 ou ultérieure',
'config-license-pd' => 'Domaine public',
'config-license-cc-choose' => 'Sélectionner une licence Creative Commons personnalisée',
'config-license-help' => "Beaucoup de wikis publics mettent l'ensemble des contributions sous [http://freedomdefined.org/Definition/Fr licence libre].
Cela contribue à créer un sentiment d'appartenance dans leur communauté et encourage les contributions sur le long terme.
-Ce n'est généralement pas nécessaire pour un wiki privé ou d'entreprise.
+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.",
+Wikipédia a déjà été publié selon les termes de la [http://fr.wikipedia.org/wiki/Licence_de_documentation_libre_GNU ''GNU Free Documentation License''] (GFDL).
+C'est une licence valide, mais elle est difficile à comprendre. De plus, elle possède des caractéristiques qui rendent difficiles la réutilisation.",
'config-email-settings' => 'Paramètres de courriel',
'config-enable-email' => 'Activer les courriels sortants',
- 'config-enable-email-help' => 'Si vous souhaitez utiliser le courriel, vous devez [http://www.php.net/manual/en/mail.configuration.php configurer des paramètres PHP] (texte en anglais).
+ '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.",
@@ -3615,59 +4749,59 @@ Si vous ne voulez pas du service de courriel, vous pouvez le désactiver ici.',
'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.
+ '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.
+ '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).
+ 'config-upload-help' => "Le téléchargement des fichiers expose votre serveur à des risques de sécurité.
+Pour plus d'informations, lire la section [//www.mediawiki.org/wiki/Manual:Security ''Security''] du manuel d'installation (en anglais).
-Pour autoriser le téléchargement des fichiers, modifier le mode du sous-répertoire <code>images</code> qui se situe sous le répertoire racine de MediaWiki.
+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.
+ '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.
+ '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.
+ 'config-instantcommons-help' => "[//www.mediawiki.org/wiki/InstantCommons InstantCommons] est un service qui permet d'utiliser les images, les sons et les autres médias disponibles sur le site [//commons.wikimedia.org/ Wikimedia Commons].
+Pour se faire, il faut que MediaWiki accède à Internet.
Pour plus d'informations sur ce service, y compris les instructions sur la façon de le configurer pour d'autres wikis que Wikimedia Commons, consultez le [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.
+ '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.
+ '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
+ '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.
+ '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>.
+ '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.
+ '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.",
@@ -3675,49 +4809,327 @@ Si vous voulez apporter des modifications, appuyez sur Retour.",
'config-install-step-failed' => 'échec',
'config-install-extensions' => 'Inclusion des extensions',
'config-install-database' => 'Création de la base de données',
+ 'config-install-schema' => 'Création de schéma',
'config-install-pg-schema-not-exist' => "Le schéma PostgreSQL n'existe pas",
- 'config-install-pg-schema-failed' => "Échec lors de la création des tables.
+ '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-pg-not-in-role' => "Le compte que vous avez spécifié pour l'utilisateur web existe déjà !
+Le compte que vous avez spécifié pour l'installation n'est pas un super-utilisateur et n'est pas membre du rôle de l'internaute, il est donc incapable de créer des objets appartenant à l'utilisateur web.!
+
+MediaWiki exige actuellement que les tableaux soient possédés par un utilisateur web. S'il vous plaît, spécifier un autre nom de compte web, ou cliquez sur \"retour\" et spécifier un utilisateur avec les privilèges suffisants.",
'config-install-user' => "Création d'un utilisateur de la base de données",
'config-install-user-alreadyexists' => "L'utilisateur « $1 » existe déjà.",
'config-install-user-create-failed' => "Échec lors de la création de l'utilisateur « $1 » : $2",
'config-install-user-grant-failed' => "Échec lors de l'ajout de permissions à l'utilisateur « $1 » : $2",
+ 'config-install-user-missing' => 'L\'utilisateur "$1" n\'existe pas.',
+ 'config-install-user-missing-create' => 'L\'utilisateur "$1" n\'existe pas !
+S\'il vous plaît, cocher "Compte de créer" dans la case ci-dessous si vous voulez le créer.',
'config-install-tables' => 'Création des tables',
- 'config-install-tables-exist' => "'''Avertissement:''' Les tables MediaWiki semblent déjà exister.
+ '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.
+ 'config-install-interwiki-exists' => "'''Attention:''' La table des interwikis semble déjà contenir des entrées.
La liste par défaut ne sera pas inscrite.",
'config-install-stats' => 'Initialisation des statistiques',
'config-install-keys' => 'Génération de la clé secrète',
+ 'config-insecure-keys' => "'''Avertissement''' : {{PLURAL:$2|Une clé de sécurité générée ($1) pendant l'installation n'est pas complètement sécuritaire. Envisagez de la modifier manuellement.|Des clés de sécurité générées ($1) pendant l'installation ne sont pas complètement sécuritaires. Envisagez de les modifier manuellement.}}",
'config-install-sysop' => 'Création du compte administrateur',
- 'config-install-subscribe-fail' => "Impossible de s'abonner à mediawiki-announce",
+ 'config-install-subscribe-fail' => "Impossible de s'abonner à mediawiki-announce : $1",
+ 'config-install-subscribe-notpossible' => 'cURL n’est pas installé et allow_url_fopen n’est pas disponible.',
'config-install-mainpage' => 'Création de la page principale avec un contenu par défaut',
'config-install-extension-tables' => 'Création de tables pour les extensions activées',
'config-install-mainpage-failed' => 'Impossible d’insérer la page principale: $1',
- 'config-install-done' => "'''Félicitations!'''
-Vous avez réussi à installer MediaWiki.
+ '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
+$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.
+'''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',
+ 'mainpagetext' => "'''MediaWiki a été installé avec succès.'''",
+ 'mainpagedocfooter' => 'Consultez le [//meta.wikimedia.org/wiki/Aide:Contenu Guide de l’utilisateur] pour plus d’informations sur l’utilisation de ce logiciel.
+
+== Démarrer avec MediaWiki ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste des paramètres de configuration]
+* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ sur MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liste de discussion sur les distributions de MediaWiki]',
+);
+
+/** Cajun French (Français cadien) */
+$messages['frc'] = array(
+ 'mainpagetext' => "'''Vous avez bien installé MediaWiki.'''",
+ 'mainpagedocfooter' => 'Lisez la [//meta.wikimedia.org/wiki/Help:Contents Guide des Useurs] pour apprendre à user le wiki software.
+
+== Pour Commencer ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Réglage]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki: Questions Souvent Posées]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki Liste à Malle]',
+);
+
+/** Franco-Provençal (Arpetan)
+ * @author ChrisPtDe
+ */
+$messages['frp'] = array(
+ 'config-desc' => 'La programeria d’enstalacion de MediaWiki',
+ 'config-title' => 'Enstalacion de MediaWiki $1',
+ 'config-information' => 'Enformacions',
+ 'config-localsettings-key' => 'Cllâf de misa a jorn :',
+ 'config-session-error' => 'Èrror pendent l’emmodâ de la sèance : $1',
+ 'config-your-language' => 'Voutra lengoua :',
+ 'config-wiki-language' => 'Lengoua du vouiqui :',
+ 'config-back' => '← Retôrn',
+ 'config-continue' => 'Continuar →',
+ 'config-page-language' => 'Lengoua',
+ 'config-page-welcome' => 'Benvegnua dessus MediaWiki !',
+ 'config-page-dbconnect' => 'Sè branchiér a la bâsa de balyês',
+ 'config-page-upgrade' => 'Betar a jorn l’enstalacion ègzistenta',
+ 'config-page-dbsettings' => 'Paramètres de la bâsa de balyês',
+ 'config-page-name' => 'Nom',
+ 'config-page-options' => 'Chouèx',
+ 'config-page-install' => 'Enstalar',
+ 'config-page-complete' => 'Chavonâ !',
+ 'config-page-restart' => 'Tornar emmodar l’enstalacion',
+ 'config-page-readme' => 'Liéséd-mè',
+ 'config-page-releasenotes' => 'Notes de publecacion',
+ 'config-page-copying' => 'Copia',
+ 'config-page-upgradedoc' => 'Misa a jorn',
+ 'config-page-existingwiki' => 'Vouiqui ègzistent',
+ 'config-env-php' => 'PHP $1 est enstalâ.',
+ 'config-env-php-toolow' => 'PHP $1 est enstalâ.
+Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
+ 'config-memory-raised' => 'Lo paramètre <code>memory_limit</code> de PHP ére a $1, portâ a $2.',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] est enstalâ',
+ 'config-apc' => '[http://www.php.net/apc APC] est enstalâ',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] est enstalâ',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] est enstalâ',
+ 'config-diff3-bad' => 'GNU diff3 entrovâblo.',
+ 'config-db-type' => 'Tipo de bâsa de balyês :',
+ 'config-db-host' => 'Hôto de la bâsa de balyês :',
+ 'config-db-host-oracle' => 'TNS de la bâsa de balyês :',
+ 'config-db-wiki-settings' => 'Identifiar cél vouiqui',
+ 'config-db-name' => 'Nom de la bâsa de balyês :',
+ 'config-db-name-oracle' => 'Plan de bâsa de balyês :',
+ 'config-db-install-account' => 'Compto utilisator por l’enstalacion',
+ 'config-db-username' => 'Nom d’utilisator de la bâsa de balyês :',
+ 'config-db-password' => 'Mot de pâssa de la bâsa de balyês :',
+ 'config-db-wiki-account' => 'Compto utilisator por l’opèracion normala',
+ 'config-db-prefix' => 'Prèfixo de les trâbles de la bâsa de balyês :',
+ 'config-db-charset' => 'Juè de caractèros de la bâsa de balyês',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binèro',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 rètrocompatiblo UTF-8',
+ 'config-mysql-old' => 'MySQL $1 ou ben ples novél est nècèssèro, vos avéd $2.',
+ 'config-db-port' => 'Pôrt de la bâsa de balyês :',
+ 'config-db-schema' => 'Plan por MediaWiki',
+ 'config-sqlite-dir' => 'Dossiér de les balyês SQLite :',
+ 'config-oracle-def-ts' => "Èspâço de stocâjo (''tablespace'') per dèfôt :",
+ 'config-oracle-temp-ts' => "Èspâço de stocâjo (''tablespace'') temporèro :",
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-header-mysql' => 'Paramètres de MySQL',
+ 'config-header-postgres' => 'Paramètres de PostgreSQL',
+ 'config-header-sqlite' => 'Paramètres de SQLite',
+ 'config-header-oracle' => 'Paramètres d’Oracle',
+ 'config-header-ibm_db2' => 'Paramètres d’IBM DB2',
+ 'config-invalid-db-type' => 'Tipo de bâsa de balyês envalido',
+ 'config-missing-db-name' => 'Vos dête buchiér una valor por « Nom de la bâsa de balyês »',
+ 'config-missing-db-host' => 'Vos dête buchiér una valor por « Hôto de la bâsa de balyês »',
+ 'config-missing-db-server-oracle' => 'Vos dête buchiér una valor por « TNS de la bâsa de balyês »',
+ 'config-sqlite-readonly' => 'Lo fichiér <code>$1</code> est pas accèssiblo en ècritura.',
+ 'config-regenerate' => 'Refâre LocalSettings.php →',
+ 'config-show-table-status' => 'Falyita de la requéta SHOW TABLE STATUS !',
+ 'config-db-web-account' => 'Compto de la bâsa de balyês por l’accès vouèbe',
+ 'config-db-web-account-same' => 'Utilisâd lo mémo compto que por l’enstalacion',
+ 'config-db-web-create' => 'Féte lo compto s’ègziste p’oncor',
+ 'config-mysql-engine' => 'Motor de stocâjo :',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-charset' => 'Juè de caractèros de la bâsa de balyês :',
+ 'config-mysql-binary' => 'Binèro',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Nom du vouiqui :',
+ 'config-site-name-blank' => 'Buchiéd un nom de seto.',
+ 'config-project-namespace' => 'Èspâço de noms du projèt :',
+ 'config-ns-generic' => 'Projèt',
+ 'config-ns-site-name' => 'Mémo nom que lo vouiqui : $1',
+ 'config-ns-other' => 'Ôtro (spècefiar)',
+ 'config-ns-other-default' => 'MonVouiqui',
+ 'config-admin-box' => 'Compto administrator',
+ 'config-admin-name' => 'Voutron nom :',
+ 'config-admin-password' => 'Mot de pâssa :',
+ 'config-admin-password-confirm' => 'Tornar buchiér lo mot de pâssa :',
+ 'config-admin-name-blank' => 'Buchiéd un nom d’administrator.',
+ 'config-admin-password-blank' => 'Buchiéd un mot de pâssa por lo compto administrator.',
+ 'config-admin-email' => 'Adrèce èlèctronica :',
+ 'config-optional-continue' => 'Mè posar més de quèstions.',
+ 'config-profile' => 'Profil des drêts d’utilisator :',
+ 'config-profile-wiki' => 'Vouiqui tradicionâl',
+ 'config-profile-no-anon' => 'Crèacion de compto nècèssèra',
+ 'config-profile-fishbowl' => 'Ren que los èditors ôtorisâs',
+ 'config-profile-private' => 'Vouiqui privâ',
+ 'config-license' => 'Drêts d’ôtor et licence :',
+ 'config-license-none' => 'Gins de licence d’avâl la pâge',
+ 'config-license-cc-by-sa' => 'Creative Commons patèrnitât - partâjo a l’identico',
+ 'config-license-cc-by' => 'Creative Commons patèrnitât',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons patèrnitât pas comèrciâla - partâjo a l’identico',
+ 'config-license-cc-0' => 'Creative Commons Zero (domêno publico)',
+ 'config-license-gfdl' => 'Licence de documentacion abada GNU 1.3 ou ben ples novèla',
+ 'config-license-pd' => 'Domêno publico',
+ 'config-license-cc-choose' => 'Chouèsir una licence Creative Commons pèrsonalisâ',
+ 'config-email-settings' => 'Paramètres de mèssageria èlèctronica',
+ 'config-enable-email' => 'Activar los mèssâjos que sôrtont',
+ 'config-email-user' => 'Activar los mèssâjos d’utilisator a utilisator',
+ 'config-email-usertalk' => 'Activar la notificacion de les pâges de discussion ux utilisators',
+ 'config-email-watchlist' => 'Activar la notificacion de la lista de survelyence',
+ 'config-email-auth' => 'Activar l’ôtenticacion per mèssageria èlèctronica',
+ 'config-email-sender' => 'Adrèce èlèctronica de retôrn :',
+ 'config-upload-settings' => 'Tèlèchargement de les émâges et des fichiérs',
+ 'config-upload-enable' => 'Activar lo tèlèchargement des fichiérs',
+ 'config-upload-deleted' => 'Dossiér por los fichiérs suprimâs :',
+ 'config-logo' => 'URL du logô :',
+ 'config-instantcommons' => 'Activar Instant Commons',
+ 'config-cc-again' => 'Tornâd chouèsir...',
+ 'config-advanced-settings' => 'Configuracion avanciê',
+ 'config-cache-options' => 'Paramètres por la misa en cache de les chouses :',
+ 'config-cache-accel' => 'Misa en cache de les chouses PHP (APC, eAccelerator, XCache ou ben WinCache)',
+ 'config-memcached-servers' => 'Sèrvors por memcached :',
+ 'config-extensions' => 'Èxtensions',
+ 'config-install-step-done' => 'fêt',
+ 'config-install-step-failed' => 'falyita',
+ 'config-install-extensions' => 'Encllusion de les èxtensions',
+ 'config-install-database' => 'Crèacion de la bâsa de balyês',
+ 'config-install-schema' => 'Crèacion de plan',
+ 'config-install-pg-schema-not-exist' => 'Lo plan PostgreSQL ègziste pas',
+ 'config-install-pg-commit' => 'Validacion des changements',
+ 'config-install-pg-plpgsql' => 'Contrôlo du lengâjo PL/pgSQL',
+ 'config-install-user' => 'Crèacion d’un utilisator de la bâsa de balyês',
+ 'config-install-user-alreadyexists' => 'L’utilisator « $1 » ègziste ja',
+ 'config-install-user-create-failed' => 'Falyita pendent la crèacion a l’utilisator « $1 » : $2',
+ 'config-install-user-grant-failed' => 'Falyita pendent l’aponsa de pèrmissions a l’utilisator « $1 » : $2',
+ 'config-install-tables' => 'Crèacion de les trâbles',
+ 'config-install-interwiki' => 'Remplissâjo per dèfôt de la trâbla des entèrvouiquis',
+ 'config-install-interwiki-list' => 'Empossiblo de trovar lo fichiér <code>interwiki.list</code>.',
+ 'config-install-stats' => 'Inicialisacion de les statistiques',
+ 'config-install-keys' => 'G·ènèracion de les cllâfs secrètes',
+ 'config-install-sysop' => 'Crèacion du compto administrator',
+ 'config-install-subscribe-fail' => 'Empossiblo de s’abonar a mediawiki-announce : $1',
+ 'config-install-mainpage' => 'Crèacion de la pâge principâla avouéc un contegnu per dèfôt',
+ 'config-install-extension-tables' => 'Crèacion de trâbles por les èxtensions activâs',
+ 'config-install-mainpage-failed' => 'Empossiblo d’entrebetar la pâge principâla : $1',
+ 'config-download-localsettings' => 'Tèlèchargiér LocalSettings.php',
+ 'config-help' => 'éde',
+ 'mainpagetext' => "'''MediaWiki at étâ enstalâ avouéc reusséta.'''",
+ 'mainpagedocfooter' => 'Vêde lo [//meta.wikimedia.org/wiki/Aide:Contenu guido d’utilisator] por més d’enformacions sur l’usâjo de la programeria vouiqui.
+
+== Emmodar avouéc MediaWiki ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista des paramètres de configuracion]
+* [//www.mediawiki.org/wiki/Manual:FAQ/fr FDQ sur MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussion sur les distribucions de MediaWiki]',
+);
+
+/** Northern Frisian (Nordfriisk)
+ * @author Pyt
+ */
+$messages['frr'] = array(
+ 'mainpagetext' => "'''MediaWiki wörd ma erfolch instaliird.'''",
+ 'mainpagedocfooter' => 'Heelp tu jü benjüting än konfigurasjoon foon e Wiki-software fanst dü önj dåt [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuch].
+
+
+== Startheelpe ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste der Konfigurationsvariablen]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]',
+);
+
+/** Friulian (Furlan) */
+$messages['fur'] = array(
+ 'mainpagetext' => "'''MediaWiki e je stade instalade cun sucès.'''",
+);
+
+/** Western Frisian (Frysk) */
+$messages['fy'] = array(
+ 'mainpagetext' => "'''MediaWiki-program goed ynstallearre.'''",
+ 'mainpagedocfooter' => "Rieplachtsje de [//meta.wikimedia.org/wiki/Help:Ynhâldsopjefte hantlieding] foar ynformaasje oer it gebrûk fan 'e wikisoftware.
+
+== Mear help oer Mediawiki ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings List mei ynstellings]
+* [//www.mediawiki.org/wiki/Manual:FAQ Faak stelde fragen (FAQ)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglist foar oankundigings fan nije ferzjes]",
+);
+
+/** Irish (Gaeilge) */
+$messages['ga'] = array(
+ 'mainpagetext' => "'''D'éirigh le suiteáil MediaWiki.'''",
+ 'mainpagedocfooter' => 'Féach ar [//meta.wikimedia.org/wiki/MediaWiki_localisation doiciméid um conas an chomhéadán a athrú]
+agus an [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Lámhleabhar úsáideora] chun cabhair úsáide agus fíoraíochta a fháil.',
+);
+
+/** Gagauz (Gagauz) */
+$messages['gag'] = array(
+ 'mainpagetext' => "'''MediaWiki başarılan kuruldu.'''",
+ 'mainpagedocfooter' => "Vikilän iş uurunda bilgi almaa için [//meta.wikimedia.org/wiki/Help:Contents User's Guide] sayfasına bakınız
+
+== Eni başlayanlar için ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Simplified Gan script (‪赣语(简体)‬) */
+$messages['gan-hans'] = array(
+ 'mainpagetext' => "'''安装正MediaWiki喽。'''",
+ 'mainpagedocfooter' => '参看[//meta.wikimedia.org/wiki/Help:Contents 用户指南]里头会话到啷用wiki软件
+
+== 开始使用 ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置设定列表]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 平常问题解答]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 发布email清单]',
+);
+
+/** Traditional Gan script (‪贛語(繁體)‬) */
+$messages['gan-hant'] = array(
+ 'mainpagetext' => "'''安裝正MediaWiki嘍。'''",
+ 'mainpagedocfooter' => '參看[//meta.wikimedia.org/wiki/Help:Contents 用戶指南]裡頭會話到啷用wiki軟件
+
+== 開始使用 ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定列表]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 平常問題解答]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈email清單]',
+);
+
+/** Scottish Gaelic (Gàidhlig)
+ * @author Akerbeltz
+ */
+$messages['gd'] = array(
+ 'mainpagetext' => "'''Chaidh MediaWiki a stàladh gu soirbheachail.'''",
+ 'mainpagedocfooter' => "Cuir sùil air [//meta.wikimedia.org/wiki/Help:Contents treòir nan cleachdaichean] airson fiosrachadh mu chleachdadh a' bhathar-bhog wiki.
+
+== Toiseach tòiseachaidh ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liosta suidheachadh nan roghainnean]
+* [//www.mediawiki.org/wiki/Manual:FAQ CÀBHA MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liosta puist nan sgaoilidhean MediaWiki]",
);
/** Galician (Galego)
+ * @author Elisardojm
* @author Toliño
*/
$messages['gl'] = array(
@@ -3738,7 +5150,7 @@ $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.
+ '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',
@@ -3771,8 +5183,8 @@ Comprobe o seu php.ini e asegúrese de que en <code>session.save_path</code> est
'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.
+ 'config-welcome' => '=== Comprobación da contorna ===
+Cómpre realizar unhas comprobacións básicas para ver se a contorna é axeitada 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 ===
@@ -3780,22 +5192,22 @@ $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'''.
+Este programa distribúese coa esperanza de que poida ser útil, pero '''sen garantía ningunha'''; nin sequera a garantía implícita de '''comercialización''' ou '''adecuación a unha finalidade específica'''.
Olle a licenza pública xeral GNU para obter máis detalles.
-Debería recibir <doclink href=Copying>unha copia da licenza pública xeral GNU</doclink> xunto ao programa; se non é así, escriba á Free Software Foundation, Inc., 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]
+Debería recibir <doclink href=Copying>unha copia da licenza pública xeral GNU</doclink> xunto ao programa; se non é así, escriba á Free Software Foundation, Inc., rúa Franklin, número 51, quinto andar, Boston, Massachusetts, 02110-1301, Estados Unidos de América ou [http://www.gnu.org/copyleft/gpl.html lea a licenza en liña].",
+ 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/gl Páxina principal de MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Guía de usuario]
+* [//www.mediawiki.org/wiki/Manual:Contents Guía de administrador]
+* [//www.mediawiki.org/wiki/Manual:FAQ Preguntas máis frecuentes]
----
* <doclink href=Readme>Léame</doclink>
* <doclink href=ReleaseNotes>Notas de lanzamento</doclink>
* <doclink href=Copying>Copia</doclink>
* <doclink href=UpgradeDoc>Actualización</doclink>',
- 'config-env-good' => 'Rematou a comprobación do entorno.
+ 'config-env-good' => 'Rematou a comprobación da contorna.
Pode instalar MediaWiki.',
- 'config-env-bad' => 'Rematou a comprobación do entorno.
+ 'config-env-bad' => 'Rematou a comprobación da contorna.
Non pode instalar MediaWiki.',
'config-env-php' => 'Está instalado o PHP $1.',
'config-env-php-toolow' => 'Está instalado o PHP $1.
@@ -3803,17 +5215,16 @@ 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].",
+Se o seu sitio posúe un alto tráfico de visitantes, debería ler un chisco sobre a [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalización Unicode].",
'config-unicode-update-warning' => "'''Atención:''' A versión instalada da envoltura de normalización Unicode emprega unha versión vella da biblioteca [http://site.icu-project.org/ do proxecto ICU].
-Debería [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.
+Debería [//www.mediawiki.org/wiki/Unicode_normalization_considerations actualizar] se o uso de Unicode é importante para vostede.",
+ 'config-no-db' => 'Non se puido atopar un controlador axeitado para a base de datos! Necesita instalar un controlador de base de datos para PHP.
+Os tipos de base de datos admitidos son os seguintes: $1.
Se está nun aloxamento compartido, pregunte ao seu provedor de hospedaxe para instalar un controlador de base de datos axeitado.
Se compilou o PHP vostede mesmo, reconfigúreo activando un cliente de base de datos, por exemplo, usando <code>./configure --with-mysql</code>.
Se instalou o PHP desde un paquete Debian ou Ubuntu, entón tamén necesita instalar o módulo php5-mysql.',
- 'config-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-no-fts3' => "'''Atención:''' O SQLite está compilado sen o [//sqlite.org/fts3.html módulo FTS3]; as características de procura non estarán dispoñibles nesta instalación.",
'config-register-globals' => "'''Atención: A opción PHP <code>[http://php.net/register_globals register_globals]</code> está activada.'''
'''Desactívea se pode.'''
MediaWiki funcionará, pero o seu servidor está exposto a potenciais vulnerabilidades de seguridade.",
@@ -3842,12 +5253,14 @@ MediaWiki necesita soporte UTF-8 para funcionar correctamente.",
'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-xcache' => '[http://xcache.lighttpd.net/ 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].
+ 'config-no-cache' => "'''Atención:''' Non se puido atopar [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].
A caché de obxectos está desactivada.",
+ 'config-mod-security' => "'''Atención:''' O seu servidor web ten o [http://modsecurity.org/ mod_security] activado. Se estivese mal configurado, pode causar problemas a MediaWiki ou calquera outro software que permita aos usuarios publicar contidos arbitrarios.
+Olle a [http://modsecurity.org/documentation/ documentación do mod_security] ou póñase en contacto co soporte do seu servidor se atopa erros aleatorios.",
'config-diff3-bad' => 'GNU diff3 non se atopou.',
'config-imagemagick' => 'ImageMagick atopado: <code>$1</code>.
As miniaturas de imaxes estarán dispoñibles se activa as cargas.',
@@ -3857,21 +5270,28 @@ As miniaturas de imaxes estarán dispoñibles se activa as cargas.',
As miniaturas de imaxes estarán desactivadas.',
'config-no-uri' => "'''Erro:''' Non se puido determinar o URI actual.
Instalación abortada.",
+ 'config-no-cli-uri' => "'''Aviso:''' Non se especificou ningún --scriptpath; por defecto, usarase: <code>$1</code>.",
+ 'config-using-server' => 'Usando o nome do servidor "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'Usando o URL do servidor "<nowiki>$1$2</nowiki>".',
'config-uploads-not-safe' => "'''Atención:''' O seu directorio por defecto para as cargas, <code>$1</code>, é vulnerable a execucións arbitrarias de escrituras.
-Aínda que MediaWiki comproba todos os ficheiros cargados por se houbese ameazas de seguridade, é amplamente recomendable [http://www.mediawiki.org/wiki/Manual:Security#Upload_security pechar esta vulnerabilidade de seguridade] antes de activar as cargas.",
+Aínda que MediaWiki comproba todos os ficheiros cargados por se houbese ameazas de seguridade, é amplamente recomendable [//www.mediawiki.org/wiki/Manual:Security#Upload_security pechar esta vulnerabilidade de seguridade] antes de activar as cargas.",
+ 'config-no-cli-uploads-check' => "'''Atención:''' Durante a instalación CLI, o seu directorio por defecto para as cargas, <code>$1</code>, non se comproba fronte a posibles vulnerabilidades de execucións arbitrarias de escrituras.",
'config-brokenlibxml' => 'O seu sistema ten unha combinación de versións de PHP e libxml2 que pode ser problemático e causar corrupción de datos en MediaWiki e outras aplicacións web.
-Actualice o sistema á versión 5.2.9 ou posterior do PHP e á 2.7.3 ou posterior de libxml2 ([http://bugs.php.net/bug.php?id=45996 erro presentado co PHP]).
+Actualice o sistema á versión 5.2.9 ou posterior do PHP e á 2.7.3 ou posterior de libxml2 ([//bugs.php.net/bug.php?id=45996 erro presentado co PHP]).
Instalación abortada.',
'config-using531' => 'O PHP $1 non é compatible con MediaWiki debido a un erro que afecta aos parámetros de referencia de <code>__call()</code>.
Actualice o sistema á versión 5.3.2 ou posterior do PHP ou volva á versión 5.3.0 do PHP para arranxar o problema.
Instalación abortada.',
+ 'config-suhosin-max-value-length' => 'Suhosin está instalado e limita a lonxitude do parámetro GET a $1 bytes. O compoñente ResourceLoader (Cargador de recursos) de MediaWiki traballa neste límite, pero este prexudica o rendemento. Se é posible, debería establecer suhosin.get.max_value_length no valor 1024 ou superior en php.ini e establecer $wgResourceLoaderMaxQueryLength no mesmo valor en LocalSettings.php.',
'config-db-type' => 'Tipo de base de datos:',
'config-db-host' => 'Servidor da base de datos:',
'config-db-host-help' => 'Se o servidor da súa base de datos está nun servidor diferente, escriba o nome do servidor ou o enderezo IP aquí.
Se está usando un aloxamento web compartido, o seu provedor de hospedaxe debe darlle o nome de servidor correcto na súa documentación.
-Se está a realizar a instalación nun servidor de Windows con MySQL, o nome "localhost" pode non valer como servidor. Se non funcionase, inténteo con "127.0.0.1" como enderezo IP local.',
+Se está a realizar a instalación nun servidor de Windows con MySQL, o nome "localhost" pode non valer como servidor. Se non funcionase, inténteo con "127.0.0.1" como enderezo IP local.
+
+Se está usando PostgreSQL, deixe este campo en branco para facer a conexión a través do conectador Unix.',
'config-db-host-oracle' => 'TNS da base de datos:',
'config-db-host-oracle-help' => 'Insira un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nome de conexión local] válido; cómpre que haxa visible un ficheiro tnsnames.ora para esta instalación.<br />Se está a empregar bibliotecas cliente versión 10g ou máis recentes, tamén pode usar o método de atribución de nomes [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
'config-db-wiki-settings' => 'Identificar o wiki',
@@ -3912,12 +5332,13 @@ O normal é que este campo quede baleiro.',
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].",
+pero non lle deixará gardar caracteres por riba do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingüe básico].",
'config-mysql-old' => 'Necesítase MySQL $1 ou posterior; ten a versión $2.',
'config-db-port' => 'Porto da base de datos:',
'config-db-schema' => 'Esquema para MediaWiki',
'config-db-schema-help' => 'O normal é que este esquema sexa correcto.
Cámbieo soamente se sabe que é necesario.',
+ 'config-pg-test-error' => "Non se pode conectar coa base de datos '''$1''': $2",
'config-sqlite-dir' => 'Directorio de datos SQLite:',
'config-sqlite-dir-help' => "SQLite recolle todos os datos nun ficheiro único.
@@ -3935,19 +5356,22 @@ Considere poñer a base de datos nun só lugar, por exemplo en <code>/var/lib/me
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'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-postgres' => '* $1 é un sistema de base de datos popular e de código aberto como alternativa a MySQL ([http://www.php.net/manual/en/pgsql.installation.php como compilar o PHP con soporte PostgreSQL]). É posible que haxa algúns pequenos erros e non se recomenda o seu uso nunha contorna de produción.',
'config-support-sqlite' => '* $1 é un sistema de base de datos lixeiro moi ben soportado. ([http://www.php.net/manual/en/pdo.installation.php Como compilar o PHP con soporte SQLite], emprega PDO)',
'config-support-oracle' => '* $1 é un sistema comercial de xestión de base de datos de empresa. ([http://www.php.net/manual/en/oci8.installation.php Como compilar PHP con soporte OCI8])',
+ 'config-support-ibm_db2' => '* $1 é unha base de datos de empresa comercial.',
'config-header-mysql' => 'Configuración do MySQL',
'config-header-postgres' => 'Configuración do PostgreSQL',
'config-header-sqlite' => 'Configuración do SQLite',
'config-header-oracle' => 'Configuración do Oracle',
+ 'config-header-ibm_db2' => 'Configuración de IBM DB2',
'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"',
@@ -4021,6 +5445,13 @@ A conta que se especifique aquí xa debe existir.',
'config-mysql-engine' => 'Motor de almacenamento:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Atención:''' Seleccionou MyISAM como o motor de almacenamento para MySQL, unha combinación non recomendada para MediaWiki, porque:
+* practicamente non soporta os accesos simultáneos debido ao bloqueo de táboas
+* é máis propenso a corromperse ca outros motores
+* o código base de MediaWiki non sempre manexa o MyISAM como debera
+
+Se a súa instalación MySQL soporta InnoDB, recoméndase elixilo no canto de MyISAM.
+Se a súa instalación MySQL non soporta InnoDB, quizais sexa boa idea realizar unha actualización.",
'config-mysql-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.
@@ -4032,7 +5463,8 @@ As bases de datos MyISAM tenden a se corromper máis a miúdo ca as bases de dat
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].",
+pero non lle deixará gardar caracteres por riba do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes plan multilingüe básico].",
+ 'config-ibm_db2-low-db-pagesize' => "A súa base de datos DB2 ten un espazo de táboa cun tamaño de páxina insuficiente. O tamaño de páxina debe ser '''32k''' ou maior.",
'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.',
@@ -4068,6 +5500,8 @@ Especifique un nome de usuario diferente.',
'config-subscribe' => 'Subscríbase á [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de correo de anuncios sobre lanzamentos].',
'config-subscribe-help' => 'Esta é unha lista de correos de baixo volume usada para anuncios sobre lanzamentos de novas versións, incluíndo avisos de seguridade importantes.
Debería subscribirse a ela e actualizar a súa instalación MediaWiki cando saian as novas versións.',
+ 'config-subscribe-noemail' => 'Intentou subscribirse á lista de correo dos anuncios de novos lanzamentos sen proporcionar o enderezo de correo electrónico.
+Dea un enderezo de correo electrónico se quere efectuar a subscrición á lista de correo.',
'config-almost-done' => 'Xa case rematou!
Neste paso pode saltar o resto da configuración e instalar o wiki agora mesmo.',
'config-optional-continue' => 'Facédeme máis preguntas.',
@@ -4088,24 +5522,25 @@ A opción '''{{int:config-profile-no-anon}}''' proporciona un control maior, per
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].",
+Hai dispoñibles configuracións de dereitos de usuario máis complexas despois da instalación; bótelle un ollo a [//www.mediawiki.org/wiki/Manual:User_rights esta entrada no manual].",
'config-license' => 'Dereitos de autor e licenza:',
'config-license-none' => 'Sen licenza ao pé',
'config-license-cc-by-sa' => 'Creative Commons recoñecemento compartir igual',
+ 'config-license-cc-by' => 'Creative Commons recoñecemento',
'config-license-cc-by-nc-sa' => 'Creative Commons recoñecemento non comercial compartir igual',
- 'config-license-cc-0' => 'Creative Commons Zero',
- '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-cc-0' => 'Creative Commons Zero (dominio público)',
+ 'config-license-gfdl' => 'Licenza de documentación libre de GNU 1.3 ou posterior',
'config-license-pd' => 'Dominio público',
'config-license-cc-choose' => 'Seleccione unha licenza Creative Commons personalizada',
'config-license-help' => "Moitos wikis públicos liberan todas as súas contribucións baixo unha [http://freedomdefined.org/Definition/Gl licenza libre].
-Isto axuda a crear un sentido de propiedade na comunidade e anima a seguir contribuíndo durante moito tempo.
+Isto axuda a crear un sentido de propiedade comunitaria e anima a seguir contribuíndo durante moito tempo.
Xeralmente, non é necesario nos wikis privados ou de empresas.
Se quere poder empregar textos da Wikipedia, así como que a Wikipedia poida aceptar textos copiados do seu wiki, escolla a licenza '''Creative Commons recoñecemento compartir igual'''.
A licenza de documentación libre de GNU era a licenza anterior da Wikipedia.
-Malia aínda ser unha licenza válida, esta ten algunhas características que poden facer o reuso e a interpretación difíciles.",
+Malia aínda ser unha licenza válida, é difícil de entender.
+Tamén é difícil reusar contidos baixo esta licenza.",
'config-email-settings' => 'Configuración do correo electrónico',
'config-enable-email' => 'Activar os correos electrónicos de saída',
'config-enable-email-help' => 'Se quere que o correo electrónico funcione, cómpre configurar os [http://www.php.net/manual/en/mail.configuration.php parámetros PHP] correctamente.
@@ -4127,7 +5562,7 @@ Moitos servidores de correo electrónico esixen que polo menos a parte do nome d
'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 obter máis información, lea a [//www.mediawiki.org/wiki/Manual:Security sección de seguridade] no manual.
Para activar a carga de ficheiros, cambie o modo no subdirectorio <code>images</code> que está baixo o directorio raíz de MediaWiki, de xeito que o servidor web poida escribir nel.
A continuación, active esta opción.',
@@ -4135,13 +5570,13 @@ A continuación, active esta opción.',
'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í.
+ 'config-logo-help' => 'A aparencia de MediaWiki por defecto inclúe espazo para un logo de 135x160 píxeles por riba do menú lateral.
+Cargue unha imaxe do tamaño axeitado e introduza o enderezo URL aquí.
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.
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons InstantCommons] é unha característica que permite aos wikis usar imaxes, sons e outros ficheiros multimedia atopados no sitio da [//commons.wikimedia.org/wiki/Portada_galega Wikimedia Commons].
+Para facer isto, MediaWiki necesita acceso á internet.
Para obter máis información sobre esta característica, incluíndo as instrucións sobre como configuralo para outros wikis que non sexan a Wikimedia Commons, consulte [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos o manual].',
'config-cc-error' => 'A escolla da licenza Creative Commons non deu resultados.
@@ -4162,7 +5597,7 @@ Debe especificarse un por liña, así como o porto a usar. Por exemplo:
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.
+ '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',
@@ -4177,6 +5612,7 @@ Se aínda quere facer algún cambio, volva atrás.',
'config-install-step-failed' => 'erro',
'config-install-extensions' => 'Incluíndo as extensións',
'config-install-database' => 'Configurando a base de datos',
+ 'config-install-schema' => 'Creando o esquema',
'config-install-pg-schema-not-exist' => 'O esquema PostgreSQL non existe.',
'config-install-pg-schema-failed' => 'Fallou a creación de táboas.
Asegúrese de que o usuario "$1" pode escribir no esquema "$2".',
@@ -4184,10 +5620,17 @@ Asegúrese de que o usuario "$1" pode escribir no esquema "$2".',
'config-install-pg-plpgsql' => 'Comprobación da lingua PL/pgSQL',
'config-pg-no-plpgsql' => 'Cómpre instalar a lingua PL/pgSQL na base de datos $1',
'config-pg-no-create-privs' => 'A conta especificada para a instalación non ten os privilexios necesarios para crear unha conta.',
+ 'config-pg-not-in-role' => 'A conta especificada para o usuario web xa existe.
+A conta que especificou para a instalación non é un superusuario e non pertence ao grupo de usuarios con acceso á web, polo que non pode crear obxectos pertencentes ao usuario da rede.
+
+Actualmente, MediaWiki necesita que as táboas sexan propiedade do usuario da rede. Especifique outro nome de conta web ou prema no botón "Atrás" e dea un usuario de instalación cos privilexios axeitados.',
'config-install-user' => 'Creando o usuario da base de datos',
'config-install-user-alreadyexists' => 'O usuario "$1" xa existe',
'config-install-user-create-failed' => 'A creación do usuario "$1" fallou: $2',
'config-install-user-grant-failed' => 'Fallou a concesión de permisos ao usuario "$1": $2',
+ 'config-install-user-missing' => 'O usuario especificado, "$1", non existe.',
+ 'config-install-user-missing-create' => 'O usuario especificado, "$1", non existe.
+Prema na caixa de verificación "crear unha conta" que hai a continuación se quere crear unha.',
'config-install-tables' => 'Creando as táboas',
'config-install-tables-exist' => "'''Atención:''' Semella que as táboas de MediaWiki xa existen.
Saltando a creación.",
@@ -4197,9 +5640,11 @@ Saltando a creación.",
'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-keys' => 'Xerando as claves secretas',
+ 'config-insecure-keys' => "'''Atención:''' {{PLURAL:$2|A clave de seguridade|As claves de seguridade}} ($1) {{PLURAL:$2|xerada|xeradas}} durante a instalación non {{PLURAL:$2|é|son}} completamente {{PLURAL:$2|segura|seguras}}. Considere a posibilidade de {{PLURAL:$2|cambiala|cambialas}} manualmente.",
'config-install-sysop' => 'Creando a conta de usuario de administrador',
- 'config-install-subscribe-fail' => 'Non se puido subscribir á lista mediawiki-announce',
+ 'config-install-subscribe-fail' => 'Non se puido subscribir á lista mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'cURL non está instalado e allow_url_fopen non está dispoñible.',
'config-install-mainpage' => 'Creando a páxina principal co contido por defecto',
'config-install-extension-tables' => 'Creando as táboas para as extensións activadas',
'config-install-mainpage-failed' => 'Non se puido inserir a páxina principal: $1',
@@ -4220,6 +5665,26 @@ $3
Cando faga todo isto, xa poderá '''[$2 entrar no seu wiki]'''.",
'config-download-localsettings' => 'Descargar o LocalSettings.php',
'config-help' => 'axuda',
+ 'mainpagetext' => "'''MediaWiki instalouse correctamente.'''",
+ 'mainpagedocfooter' => 'Consulte a [//meta.wikimedia.org/wiki/Help:Contents Guía do usuario] para máis información sobre como usar o software wiki.
+
+== Comezando ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de opcións de configuración]
+* [//www.mediawiki.org/wiki/Manual:FAQ Preguntas frecuentes sobre MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de correo das edicións de MediaWiki]',
+);
+
+/** Ancient Greek (Ἀρχαία ἑλληνικὴ)
+ * @author Omnipaedista
+ */
+$messages['grc'] = array(
+ 'mainpagetext' => "'''Ἡ ἐγκατάστασις τῆς MediaWiki ἦν ἐπιτυχής'''",
+ 'mainpagedocfooter' => 'Βουλευθήσεσθε τὰς [//meta.wikimedia.org/wiki/Help:Contents βουλὰς τοῖς Χρωμένοις] ἵνα πληροφορηθῇτε περὶ τοῦ βίκιλογισμικοῦ.
+
+== Ἄρξασθε ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Διαλογή παραμέτρων διαμορφώσεως]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki: τὰ πολλάκις αἰτηθέντα]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Διαλογή διαλέξεων ἐπὶ τῶν διανομῶν τῆς MediaWiki]',
);
/** Swiss German (Alemannisch)
@@ -4275,10 +5740,10 @@ Des Programm isch e freji Software, d. h. s cha, no dr Bedingige vu dr GNU Gener
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-sidebar' => '* [//www.mediawiki.org MediaWiki Websyte vu MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Nutzeraaleitig zue MediaWiki]
+* [//www.mediawiki.org/wiki/Manual:Contents Adminischtratoreaaleitig zue MediaWiki]
+* [//www.mediawiki.org/wiki/Manual:FAQ Vilmol gstellti Froge zue MediaWiki]',
'config-env-good' => 'D Inschtallationsumgäbig isch prieft wore.
Du chasch MediaWiki inschtalliere.',
'config-env-bad' => 'D Inschtallationsumgäbigisch prieft wore.
@@ -4287,17 +5752,11 @@ Du chasch MediaWiki nit inschtalliere.',
'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)].",
+Wänn Du ne Websyte mit ere große Bsuechrzahl bedrybsch, sottsch e weng ebis läse iber [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-Normalisierig (en)].",
'config-unicode-update-warning' => "'''Warnig:''' Di inschtalliert Version vum Unicode-Normalisierigswrapper verwändet e elteri Version vu dr Bibliothek vum [http://site.icu-project.org/ ICU-Projäkt].
-Du sottsch si [http://www.mediawiki.org/wiki/Unicode_normalization_considerations aktualisiere], wänn Dor d Verwändig vu Unicode wichtig isch.",
+Du sottsch si [//www.mediawiki.org/wiki/Unicode_normalization_considerations aktualisiere], wänn Dor d Verwändig vu Unicode wichtig isch.",
'config-no-db' => 'S isch kei adäquate Datebanktryyber gfunde wore!',
- '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-no-fts3' => "'''Warnig:''' SQLite isch ohni s [//sqlite.org/fts3.html FTS3-Modul] kumpiliert wore, s stehn kei Suechfunktione z Verfiegig.",
'config-register-globals' => "'''Warnig: Dr Parameter <code>[http://php.net/register_globals register_globals]</code> vu PHP isch aktiviert.'''
'''Är sott deaktiviert wäre, wänn des megli isch.'''
D MediaWiki-Inschtallation lauft einwäg, aber dr Server isch aafällig fi megligi Sicherheitsprobläm.",
@@ -4326,16 +5785,53 @@ MediaWiki brucht d UTF-8-Unterstitzi zum fählerfrej lauffähig syy.",
'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-xcache' => '[http://xcache.lighttpd.net/ 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.
+ 'config-no-cache' => "'''Warnig:''' [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] hän nit chenne gfunde wäre.
S Objäktcaching isch wäge däm nit aktiviert.",
'config-diff3-bad' => 'GNU diff3 isch nit gfunde wore.',
'config-imagemagick' => 'ImageMagick isch gfunde wore: <code>$1</code>.
Miniaturaasichte vu Bilder sin megli, sobald s Uffelade vu Dateie aktiviert isch.',
'config-help' => 'Hilf',
+ 'mainpagetext' => "'''MediaWiki isch erfolgrich inschtalliert worre.'''",
+ 'mainpagedocfooter' => 'Lueg uf d [//meta.wikimedia.org/wiki/MediaWiki_localisation Dokumentation fir d Aapassig vu dr Benutzeroberflächi] un s [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbuech] fir d Hilf iber d Benutzig un s Yystelle.',
+);
+
+/** Gujarati (ગુજરાતી)
+ * @author Dineshjk
+ */
+$messages['gu'] = array(
+ 'mainpagetext' => "'''મિડીયાવિકિ સફળતાપૂર્વક ઇન્સટોલ થયું છે.'''",
+ 'mainpagedocfooter' => 'વિકિ સોફ્ટવેર વાપરવાની માહીતિ માટે [//meta.wikimedia.org/wiki/Help:Contents સભ્ય માર્ગદર્શિકા] જુઓ.
+
+== શરૂઆતના તબક્કે ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings કોનફીગ્યુરેશન સેટીંગ્સની યાદી]
+* [//www.mediawiki.org/wiki/Manual:FAQ વારંવાર પુછાતા પ્રશ્નો]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce મિડીયાવિકિ રીલીઝ મેઇલીંગ લીસ્ટ]',
+);
+
+/** Manx (Gaelg) */
+$messages['gv'] = array(
+ 'mainpagetext' => "'''Ta MediaWiki currit stiagh nish.'''",
+);
+
+/** Hakka (Hak-kâ-fa) */
+$messages['hak'] = array(
+ 'mainpagetext' => "'''Yí-kîn sṳ̀n-kûng ôn-chông MediaWiki.'''",
+ 'mainpagedocfooter' => 'chhiáng fóng-mun [//meta.wikimedia.org/wiki/Help:Contents Yung-fu sú-chhak] yî-khi̍p sṳ́-yung chhṳ́ wiki ngiôn-khien ke sin-sit!
+
+== Ngi̍p-mùn ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki Phi-chṳ sat-thin chhîn-tân]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Phìn-sòng mun-thì kié-tap]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki fat-phu email chhîn-tân]',
+);
+
+/** Hawaiian (Hawai`i) */
+$messages['haw'] = array(
+ 'mainpagetext' => "'''Ua pono ka ho‘ouka ‘ana o MediaWiki.'''",
);
/** Hebrew (עברית)
@@ -4391,7 +5887,7 @@ $1',
'config-page-copying' => 'העתקה',
'config-page-upgradedoc' => 'שדרוג',
'config-page-existingwiki' => 'ויקי קיים',
- 'config-help-restart' => 'האם ברצונך לפנות את כל הנתונים שנשמרו שהוזנו על ידיך ולהתחיל מחדש את תהליך ההתקנה?',
+ 'config-help-restart' => 'האם ברצונך לנקות את כל הנתונים שהזנת ולהתחיל מחדש את תהליך ההתקנה?',
'config-restart' => 'כן, להפעיל מחדש',
'config-welcome' => '=== בדיקות סביבה ===
בדיקות בסיסיות מתבצעות כדי לבדוק שהסביבה מתאימה להתקנת מדיה־ויקי.
@@ -4405,10 +5901,10 @@ $1
תכנית זו מופצת בתקווה שתהיה מועילה, אבל '''בלא אחריות כלשהי'''; ואפילו ללא האחריות המשתמעת בדבר '''מסחריותה''' או '''התאמתה למטרה '''מסוימת'''. לפרטים נוספים, ניתן לעיין ברישיון הציבורי הכללי של 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 שו״ת]
+ 'config-sidebar' => '* [//www.mediawiki.org אתר הבית של מדיה־ויקי]
+* [//www.mediawiki.org/wiki/Help:Contents המדריך למשתמשים]
+* [//www.mediawiki.org/wiki/Manual:Contents המדריך למנהלים]
+* [//www.mediawiki.org/wiki/Manual:FAQ שו״ת]
----
* <doclink href=Readme>קרא אותי</doclink>
* <doclink href=ReleaseNotes>הערות גרסה</doclink>
@@ -4418,23 +5914,22 @@ $1
אפשר להתקין מדיה־ויקי.',
'config-env-bad' => 'הסביבה שלכם נבדקה.
אי־אפשר להתקין מדיה־ויקי.',
- 'config-env-php' => 'מותקנת PHP $1.',
- 'config-env-php-toolow' => 'מותקנת PHP $1.
-למדיה־ויקי נדרשת PHP $2 או גרסה גבוהה יותר.',
+ 'config-env-php' => 'מותקנת <span dir="ltr">PHP $1</span>.',
+ 'config-env-php-toolow' => 'מותקנת <span dir="ltr">PHP $1</span>.
+למדיה־ויקי נדרשת <span dir="ltr">PHP $2</span> או גרסה גבוהה יותר.',
'config-unicode-using-utf8' => 'משתמש ב־normalize.so של בריון ויבר לנרמול יוניקוד.',
'config-unicode-using-intl' => 'משתמש בהרחבת [http://pecl.php.net/intl הרחבת intl PECL] לנרמול יוניקוד',
'config-unicode-pure-php-warning' => "'''אזהרה''': [http://pecl.php.net/intl הרחבת intl PECL] אינה זמינה לטיפול בנרמול יוניקוד. משתמש ביישום PHP טהור ואטי יותר.
-אם זה אתר בעל תעבורה גבוהה, כדאי לקרוא את המסמך הבא: [http://www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode normalization].",
+אם זהו אתר בעל תעבורה גבוהה, כדאי לקרוא את המסמך הבא: [//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.
+כדאי [//www.mediawiki.org/wiki/Unicode_normalization_considerations לעדכן] אם חשוב לכם הטיפול ביוניקוד.",
+ 'config-no-db' => 'לא נמצא דרייבר מסד נתונים מתאים. יש להתקין דרייבר מסד נתונים ל־PHP.
נתמכים הסוגים הבאים של מסדי נתונים: $1.
אם אתם משתמשים באירוח משותף, בקשו מספק האירוח שלכם להתקין דרייבר מסד נתונים מתאים.
-אם קמפלתם את PHP בעצמכם, הגדירו אותו מחדש והפעילו את לקוח מסד נתונים (database client), למשל בעזרת <code>./configure --with-mysql</code>.
-אם התקנתם את PHP מחבילה של דביאן או אובונטו, יש להתקין את המודול php5-mysql.',
- 'config-no-fts3' => "'''אזהרה''': SQLite מקומפל ללא [http://sqlite.org/fts3.html מודול FTS]. יכולות חיפוש לא יהיו זמינות בהתקנה הזאת.",
+אם קִמפלתם את PHP בעצמכם, הגדירו אותו מחדש והפעילו את לקוח מסד נתונים, למשל באמצעות <code dir="ltr">./configure --with-mysql</code>.
+אם התקנתם את PHP כחבילה של דביאן או של אובונטו, יש להתקין את המודול php5-mysql.',
+ 'config-no-fts3' => "'''אזהרה''': SQLite מקומפל ללא [//sqlite.org/fts3.html מודול FTS]. יכולות חיפוש לא יהיו זמינות בהתקנה הזאת.",
'config-register-globals' => "'''אזהרה: האפשרות <code>[http://php.net/register_globals register_globals]</code> של PHP מופעלת.'''
'''כבו אותה אם אתם יכולים.'''
מדיה־ויקי תעבוד, אבל השרת שלכם חשוף לפגיעות אבטחה.",
@@ -4463,14 +5958,16 @@ $1
'config-memory-bad' => "'''אזהרה:''' ערך האפשרות <code>memory_limit</code> של PHP הוא $1.
זה כנראה נמוך מדי.
ההתקנה עשויה להיכשל!",
- 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] מותקן',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ 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-no-cache' => "'''אזהרה:''' אחת מהתוכנות הבאות לא נמצאה: [http://eaccelerator.sourceforge.net eAccelerator]&rlm;, [http://www.php.net/apc APC]&rlm;, [http://xcache.lighttpd.net/ XCache] או [http://www.iis.net/download/WinCacheForPhp WinCache].
מטמון עצמים לא מופעל.",
+ 'config-mod-security' => "'''אזהרה''': בשרת הווב שלכם מופעל [http://modsecurity.org/ mod_security]. אם הוא לא מוגדר טוב, זה יכול לגרום לבעיות במדיה־ויקי ובתכנה אחרת שמאפשרת למשתמשים לשלוח תוכן שרירותי.
+קראו את [http://modsecurity.org/documentation/ התיעוד של mod_security] או צרו קשר עם אנשי התמיכה של שירותי האירוח שלכם אם אתם נתקלים בשגיאות אקראיות.",
'config-diff3-bad' => 'GNU diff3 לא נמצא.',
- 'config-imagemagick' => 'נמצא ImageMagick&rlm;: <code>$1</code>.
+ 'config-imagemagick' => 'נמצא ImageMagick&rlm;: <code dir="ltr">$1</code>.
מזעור תמונות יופעל, אם תפעילו את האפשרות להעלות קבצים.',
'config-gd' => 'נמצאה ספריית הגרפיקה GD המובנית.
מזעור תמונות יופעל, אם תפעילו את האפשרות להעלות קבצים.',
@@ -4478,21 +5975,28 @@ $1
מזעור תמונות לא יופעל.',
'config-no-uri' => "'''שגיאה:''' אי־אפשר לזהות את הכתובת הנוכחית.
ההתקנה בוטלה.",
- 'config-uploads-not-safe' => "'''אזהרה:''' התיקייה ההתחלתית להעלות <code>$1</code> חשופה להרצת סקריפטים.
-מדיה־ויקי בודקת את כל הקבצים המוּעלים לאיומי אבטחה, מומלץ מאוד למנוע את [http://www.mediawiki.org/wiki/Manual:Security#Upload_security פרצת האבטחה] הזאת לפני שאתם מפעילים את ההעלאות.",
+ 'config-no-cli-uri' => 'אזהרה: לא הוגדר <span dir="ltr">--scriptpath</span>, משתמש בבררת המחדל: <code dir="ltr">$1</code>.',
+ 'config-using-server' => 'שם השרת בשימוש: "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'נעשה שימוש בכתובת השרת "<nowiki>$1$2</nowiki>".',
+ 'config-uploads-not-safe' => "'''אזהרה:''' תיקיית ההעלאות ההתחלתית <code>$1</code> חשופה להרצת סקריפטים שרירותיים.
+מדיה־ויקי בודקת את כל הקבצים המוּעלים לאיומי אבטחה, מומלץ מאוד למנוע את [//www.mediawiki.org/wiki/Manual:Security#Upload_security פרצת האבטחה] הזאת לפני שאתם מפעילים את ההעלאות.",
+ 'config-no-cli-uploads-check' => "'''אזהרה:''' תיקיית בררת המחדל להעלאות (<code>$1</code>) לא נבדקת לפגיעוּת להרצת תסריטים אקראיים בזמן התקנה דרך CLI.",
'config-brokenlibxml' => 'במערכת שלכם יש שילוב של גרסאות של PHP ושל libxml2 שחשוף לבאגים ויכול לגרום לעיוות נתונים נסתר במדיה־ויקי וביישומי רשת אחרים.
-שדרגו ל־PHP 5.2.9 או לגרסה חדשה יותר ול־libxml2 2.7.3 או גרסה חדשה יותר ([http://bugs.php.net/bug.php?id=45996 באג מתויק ב־PHP]).
+שדרגו ל־PHP 5.2.9 או לגרסה חדשה יותר ול־libxml2 2.7.3 או גרסה חדשה יותר ([//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-using531' => 'אי־אפשר להשתמש במדיה־ויקי עם <span dir="ltr">PHP $1</span> בגלל באג בפרמטרים של הפניות (reference parameters) ל־<code dir="ltr">__call()</code>.
+שדרגו ל־PHP 5.3.2 או לגרסה גבוהה יותר כדי לתקן את זה ([//bugs.php.net/bug.php?id=50394 bug filed with PHP]) או שַנמכו ל־PHP 5.3.0 כדי לפתור את הבעיה הזאת.
ההתקנה בוטלה.',
+ 'config-suhosin-max-value-length' => 'מותקן פה Suhosin והוא מגביל את אורך פרמטר GET ל־$1 בתים. רכיב ResourceLoader של מדיה־ויקי יעקוף את המגלבה הזאת, אבל זה יפגע בביצועים. אם זה בכלל אפשרי, כדי לתקן את הערך של suhosin.get.max_value_length ל־1024 בקובץ php.ini ולהגדיר את ‎$wgResourceLoaderMaxQueryLength לאותו הערך בקובץ LocalSettings.php.',
'config-db-type' => 'סוג מסד הנתונים:',
'config-db-host' => 'שרת מסד הנתונים:',
- 'config-db-host-help' => 'אם שרת מסד הנתונים שלכם נמצא על שרת מחשב אחר, הקלידו את שם המחשב או כתובת ה־IP כאן.
+ 'config-db-host-help' => 'אם שרת מסד הנתונים שלכם נמצא על שרת אחר, הקלידו את שם המחשב או את כתובת ה־IP כאן.
אם אתם משתמשים באירוח משותף, ספק האירוח שלכם אמור לתת לכם את שם השרת הנכון במסמכים.
-אם אתם מתקינים בשרת חלונות ומשתמשים ב־MySQL, השימוש ב־localhost עשוי לא לעבוד. אם הוא לא עובד, נסו את "127.0.0.1" בתור כתובת ה־IP המקומית.',
+אם אתם מתקינים בשרת Windows ומשתמשים ב־MySQL, השימוש ב־localhost עשוי לא לעבוד. אם הוא לא עובד, נסו את "127.0.0.1" בתור כתובת ה־IP המקומית.
+
+אם אתם משתמשים ב־PostgreSQL, תשאירו את השדה הזה ריק כדי להתחבר דרך שקע יוניקס.',
'config-db-host-oracle' => 'TNS של מסד הנתונים:',
'config-db-host-oracle-help' => 'הקלידו [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm שם חיבור מקומי (Local Connect Name)] תקין; הקובץ tnsnames.ora צריך להיות זמין להתקנה הזאת.<br />
אם אתם משתמשים ב־client libraries 10g או בגרסה חדשה יותר, אתם יכולים גם להשתמש בשיטת מתן השמות [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
@@ -4535,12 +6039,13 @@ $1
ב'''מצב בינרי''' (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.',
+ב'''מצב UTF-8'''&rlm; (UTF-8 mode)&rlm; MySQL יֵדַע מה קבוצת התווים (character set) של הטקסט שלכם ויציג וימיר אותו בהתאם, אבל לא יאפשר לכם לשמור תווים שאינם נמצאים בטווח הרב־לשוני הבסיסי ([//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane]).",
+ 'config-mysql-old' => 'נדרשת גרסה <span dir="ltr">$1</span> של MySQL או גרסה חדשה יותר. הגרסה הנוכחית שלכם היא $2.',
'config-db-port' => 'פִּתְחַת מסד הנתונים (database port):',
'config-db-schema' => 'סכמה למדיה־ויקי',
'config-db-schema-help' => 'הסְכֵמָה הבאה בדרך כלל מתאימה.
שנו אותה רק אם אתם יודעים שאתם חייבים.',
+ 'config-pg-test-error' => "ההתחברות למסד הנתונים '''$1''' לא מצליחה: $2",
'config-sqlite-dir' => 'תיקיית נתונים (data directory) של SQLite:',
'config-sqlite-dir-help' => 'SQLite שומר את כל הנתונים בקובץ אחד.
@@ -4548,11 +6053,12 @@ $1
היא לא צריכה נגישה לכולם דרך האינטרנט – בגלל זה איננו שמים אותה באותו מקום עם קובצי ה־PHP.
-תוכנת ההתקנה תכתוב קובץ <span dir="ltr"><code>.htaccess</code></span> יחד אִתו, אבל אם זה ייכשל, מישהו יוכל להשיג גישה למסד הנתונים שלכם. שם נמצא מידע מפורש של משתמשים (כתובות דוא״ל, ססמאות מגובבות) וגם גרסאות מחוקות של דפים ומידע מוגבל אחר.
+תוכנת ההתקנה תכתוב קובץ <code dir="ltr">.htaccess</code> יחד אִתו, אבל אם זה ייכשל, מישהו יוכל להשיג גישה למסד הנתונים שלכם. שם נמצא מידע מפורש של משתמשים (כתובות דוא״ל, ססמאות מגובבות) וגם גרסאות מחוקות של דפים ומידע מוגבל אחר.
-כדאי לשקול לשים את מסד הנתונים במקום אחר לגמרי, למשל ב־<span dir="ltr"><code>/var/lib/mediawiki/yourwik</code></span>.',
+כדאי לשקול לשים את מסד הנתונים במקום אחר לגמרי, למשל ב־<code dir="ltr">/var/lib/mediawiki/yourwik</code>.',
'config-oracle-def-ts' => 'מרחב טבלאות לפי בררת מחדל (default tablespace):',
'config-oracle-temp-ts' => 'מרחב טבלאות זמני (temporary tablespace):',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'מדיה־ויקי תומכת במערכות מסדי הנתונים הבאות:
$1
@@ -4562,10 +6068,12 @@ $1
'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-support-ibm_db2' => '* $1 הוא מסד נתונים מסחרי ארגוני.',
'config-header-mysql' => 'הגדרות MySQL',
'config-header-postgres' => 'הגדרות PostgreSQL',
'config-header-sqlite' => 'הגדרות SQLite',
'config-header-oracle' => 'הגדרות Oracle',
+ 'config-header-ibm_db2' => 'תצורת IBM DB2',
'config-invalid-db-type' => 'סוג מסד הנתונים שגוי',
'config-missing-db-name' => 'עליך להזין ערך עבור "שם מסד הנתונים"',
'config-missing-db-host' => 'יש להכניס ערך לשדה "שרת מסד הנתונים"',
@@ -4576,9 +6084,9 @@ $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-connection-error' => '<div dir="ltr">$1.</div>
-בדקו את שם השרת, את שם המשתמש ואת הססמה ונסו שוב.',
+בדקו את שם השרת, את שם המשתמש ואת הססמה בטופס להלן ונסו שוב.',
'config-invalid-schema' => '"$1" היא סכמה לא תקינה עבור מדיה־ויקי.
יש להשתמש רק באותיות ASCII&rlm; (a עד z&rlm;, A עד Z), סְפָרוֹת (0 עד 9) וקווים תחתיים (_).',
'config-db-sys-create-oracle' => 'תוכנית ההתקנה תומכת רק בשימוש בחשבון SYSDBA ליצירת חשבון חדש.',
@@ -4637,6 +6145,15 @@ chmod a+w $3</pre></div>',
'config-db-web-no-create-privs' => 'לחשבון שהקלדתם להתקנה אין מספיק הרשאות ליצירת חשבות.
החשבון שאתם מקלידים כאן צריך להיות קיים.',
'config-mysql-engine' => 'מנגנון האחסון:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''אזהרה''': בחרתם ב־MyISAM בתור מנוע אחסון של MySQL, וזה לא מומלץ מהסיבות הבאות:
+* המנוע הזה בקושי תומך בעיבוד מקבילי בגלל נעילת טבלאות
+* הוא פחות עמיד בפני אובדן מידע ממנועים אחרים
+* הקוד של מדיה־ויקי לא תמיד מטפל ב־MyISAM כפי שצריך
+
+אם התקנת MySQL שלכם תומכת ב־InnoDB, מומלץ מאוד שתבחרו באפשרות הזאת.
+אם התקנת MySQL שלכם אינה תומכת ב־InnoDB, אולי זה הזמן לשקול לשדרג אותה.",
'config-mysql-engine-help' => "'''InnoDB''' הוא כמעט תמיד האפשרות הטובה ביותר, כי במנוע הזה יש תמיכה טובה ביותר בעיבוד מקבילי.
'''MyISAM''' עשוי להיות בהתקנות שמיועדות למשתמש אחד ולהתקנות לקריאה בלבד.
@@ -4647,7 +6164,8 @@ chmod a+w $3</pre></div>',
'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]).",
+ב'''מצב UTF-8'''&rlm; (UTF-8 mode)&rlm; MySQL יֵדַע מה קבוצת התווים (character set) של הטקסט שלכם ויציג וימיר אותו בהתאם, אבל לא יאפשר לכם לשמור תווים שאינם נמצאים בטווח הרב־לשוני הבסיסי ([//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane]).",
+ 'config-ibm_db2-low-db-pagesize' => "במסד הנתונים DB2 שלכם יש מרחב טבלאות לפי מחדלי עם גודל דף בלתי־מספיק. גודל הדף צריך להיות '''32K''' או יותר.",
'config-site-name' => 'שם הוויקי:',
'config-site-name-help' => 'זה יופיע בשורת הכותרת של הדפדפן ובמקומות רבים אחרים.',
'config-site-name-blank' => 'נא להזין שם לאתר.',
@@ -4657,8 +6175,8 @@ chmod a+w $3</pre></div>',
'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>" מתנגש עם מרחב שמות מובנה של מדיה־ויקי.
@@ -4683,6 +6201,8 @@ chmod a+w $3</pre></div>',
'config-subscribe' => 'להירשם ל[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה עם הודעות על גרסאות חדשות].',
'config-subscribe-help' => 'זוהי רשימת תפוצה עם הודעות מעטות שמשמשת להודעות על הוצאת גרסאות, כולל עדכוני אבטחה חשובים.
מומלץ להירשם אליה ולעדכן את מדיה־ויקי כאשר יוצאות גרסאות חדשות.',
+ 'config-subscribe-noemail' => 'ניסיתם להירשם לרשימת תפוצה של הודעות בלי לתת כתובת דוא"ל.
+נא לתת כתובת דוא"ל אם אתם רוצים להירשם לרשימת התפוצה.',
'config-almost-done' => 'כמעט סיימתם!
אפשר לדלג על שאר ההגדרות ולהתקין את הוויקי כבר עכשיו.',
'config-optional-continue' => 'הצגת שאלות נוספות.',
@@ -4703,14 +6223,14 @@ chmod a+w $3</pre></div>',
בתסריט '''{{int:config-profile-fishbowl}}''' רק משתמשים שקיבלו אישור יכולים לערוך, אבל כל הגולשים יכולים לקרוא את הדפים ואת גרסאותיהם הקודמות.
ב'''{{int:config-profile-private}}''' רק משתמשים שקיבלו אישור יכולים לקרוא ולערוך דפים.
-הגדרות מורכבות של הרשאות אפשריות אחרי ההתקנה, ר׳ את [http://www.mediawiki.org/wiki/Manual:User_rights הפרק על הנושא הזה בספר ההדרכה].",
+הגדרות מורכבות של הרשאות אפשריות אחרי ההתקנה, ר׳ את [//www.mediawiki.org/wiki/Manual:User_rights הפרק על הנושא הזה בספר ההדרכה].",
'config-license' => 'זכויות יוצרים ורישיון:',
'config-license-none' => 'ללא כותרת תחתית עם רישיון',
'config-license-cc-by-sa' => 'קריאייטיב קומונז–ייחוס–שיתוף זהה',
+ 'config-license-cc-by' => 'קריאייטיב קומונז–ייחוס',
'config-license-cc-by-nc-sa' => 'קריאייטיב קומונז ייחוס–ללא שימוש מסחרי–שיתוף זהה',
- 'config-license-cc-0' => 'Creative Commons אפס',
- 'config-license-gfdl-old' => 'רישיון חופשי למסמכים של גנו, גרסה 1.2',
- 'config-license-gfdl-current' => 'רישיון חופשי למסמכים של גנו, גרסה 1.3 או גרסה מאוחרת יותר',
+ 'config-license-cc-0' => 'קריאייטיב קומונז אפס (נחלת הכלל)',
+ 'config-license-gfdl' => 'רישיון חופשי למסמכים של גנו גרסה 1.3 או חדשה יותר',
'config-license-pd' => 'נחלת הכלל',
'config-license-cc-choose' => 'בחרו רישיון קריאייטיב קומונז מותאם אישית',
'config-license-help' => "אתרי ויקי ציבוריים רבים מפרסמים את כל התרומות תחת [http://freedomdefined.org/Definition רישיון חופשי].
@@ -4719,8 +6239,9 @@ chmod a+w $3</pre></div>',
אם אתם רוצים אפשרות להשתמש בטקסט מוויקיפדיה ואתם רוצים שוויקיפדיה תוכל לקבל עותקים של טקסטים מהוויקי שלכם, כדאי לכם לבחור ב'''רישיון קריאייטיב קומונז ייחוס–שיתוף זהה''' (CC-BY-SA).
-הרישיון החופשי למסמכים של גנו הוא הרישיון שבו ויקיפדיה השתמשה בעבר (GNU FDL או GFDL).
-הוא עדיין תקין, אבל יש בו תכונות מסוימות שמקשות על שימוש חוזר ועל פרשנות.",
+ויקיפדיה השתמשה בעבר ברישיון החופשי למסמכים של גנו (GNU FDL או GFDL).
+הוא עדיין רישיון תקין, אבל קשה להבנה.
+כמו־כן, קשה לעשות שימוש חוזר ביצירות שפורסמו לפי GFDL.",
'config-email-settings' => 'הגדרות דוא״ל',
'config-enable-email' => 'להפעיל דוא״ל יוצא',
'config-enable-email-help' => 'אם אתם רוצים שדוא״ל יעבוד, [http://www.php.net/manual/en/mail.configuration.php אפשרויות הדוא״ל של PHP] צריכות להיות מוגדרות נכון.
@@ -4740,22 +6261,22 @@ chmod a+w $3</pre></div>',
לשם יישלחו תגובות שגיאה (bounce).
שרתי דוא״ל רבים דורשים שלפחות החלק של המתחם יהיה תקין.',
'config-upload-settings' => 'העלאת קבצים ותמונות',
- 'config-upload-enable' => 'אפשור העלאת קבצים',
+ 'config-upload-enable' => 'לאפשר העלאת קבצים',
'config-upload-help' => 'העלאות קבצים חושפות את השרת שלכם לסיכוני אבטחה.
-למידע נוסף, קִראו את [http://www.mediawiki.org/wiki/Manual:Security חלק האבטחה] בספר ההדרכה.
+למידע נוסף, קִראו את [//www.mediawiki.org/wiki/Manual:Security חלק האבטחה] בספר ההדרכה.
כדי להפעיל העלאת קבצים שנו את ההרשאות של התיקייה <code>images</code> תחת תיקיית השורש של מדיה־ויקי כך ששרת הווב יוכל לכתוב אליה.
זה מפעיל את האפשרות הזאת.',
- 'config-upload-deleted' => 'תיקיית הקבצים שנמחקו:',
+ 'config-upload-deleted' => 'תיקיית לקבצים שנמחקו:',
'config-upload-deleted-help' => 'בחרו את התיקייה לארכוב קבצים מחוקים.
כדאי שזה לא יהיה נגיש לכל העולם דרך הרשת.',
'config-logo' => 'כתובת הסמל:',
- 'config-logo-help' => 'המראה ההתחלתי של מדיה־ויקי מכיל מקום לסמל של 135 על 160 פיקסלים בפינה השמאלית העליונה (ימנית עבור שפות שנכתבות מימין לשמאל).
+ '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).
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] היא תכונה שמאפשרת לאתרי ויקי להשתמש בתמונות, בצלילים ובמדיה אחרת שנמצאת באתר [//commons.wikimedia.org/ ויקישיתוף] (Wikimedia Commons).
כדי לעשות את זה, מדיה־ויקי צריך לגשת לאינטרנט.
למידע נוסף על התכונה הזאת, כולל הוראות איך להפעיל את זה לאתרי ויקי שאינם ויקישיתוף, ר׳ [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos את ספר ההדרכה].',
@@ -4792,6 +6313,7 @@ chmod a+w $3</pre></div>',
'config-install-step-failed' => 'נכשל',
'config-install-extensions' => 'כולל הרחבות',
'config-install-database' => 'הקמת מסד נתונים',
+ 'config-install-schema' => 'יצירת סכמה',
'config-install-pg-schema-not-exist' => 'סכמה של PostgreSQL אינה קיימת',
'config-install-pg-schema-failed' => 'יצירת טבלאות נכשלה.
ודאו כי המשתמש "$1" יכול לכתוב לסכמה "$2".',
@@ -4799,10 +6321,17 @@ chmod a+w $3</pre></div>',
'config-install-pg-plpgsql' => 'בדיקת שפת PL/pgSQL',
'config-pg-no-plpgsql' => 'צריך להתקין את שפת PL/pgSQL במסד הנתונים $1',
'config-pg-no-create-privs' => 'לחשבון שהגדרתם להתקנה אין מספיק הרשאות ליצירת חשבון.',
+ 'config-pg-not-in-role' => 'החשבון שציינתם עבור משתמש שרת הווב כבר קיים.
+החשבון שסיפקתם להתקנה אינו חשבון בעל הרשאות (superuser) ואינו חבר בתפקיד (role) של משתמש שרת הווב, אז אין אפשרות ליצור עצמים בבעלות משתמש שרת הווב.
+
+כעת נדרש במדיה־ויקי שהטבלאות יהיו בבעלות של משתמש שרת הווב. נא לציין שם חשבון שרת וב אחר או ללחוץ על כפתור "אחורה" ולציין משתמש התקנה בעל הרשאות מתאימות.',
'config-install-user' => 'יצירת חשבון במסד נתונים',
'config-install-user-alreadyexists' => 'המשתמש "$1" כבר קיים',
'config-install-user-create-failed' => 'יצירת משתמש "$1" נכשלה: $2',
'config-install-user-grant-failed' => 'מתן הרשאות למשתמש "$1" נכשל: $2',
+ 'config-install-user-missing' => 'המשתמש "$1" שצוין אינו קיים.',
+ 'config-install-user-missing-create' => 'המשתמש "$1" שצוין אינו קיים.
+נא ללחוץ על תיבת בסימון "יצירת חשבון" להלן אם אתם רוצים ליצור אותו.',
'config-install-tables' => 'יצירת טבלאות',
'config-install-tables-exist' => "'''אזהרה:''' נראה שטבלאות מדיה־ויקי כבר קיימות.
מדלג על יצירתן.",
@@ -4813,8 +6342,10 @@ chmod a+w $3</pre></div>',
מדלג על הרשומה ההתחלתית.",
'config-install-stats' => 'אתחול סטטיסטיקות',
'config-install-keys' => 'יצירת מפתחות סודיים',
+ 'config-insecure-keys' => "'''אזהרה''': {{PLURAL:$2|מפתח|מפתחות}} אבטחה ($1) {{PLURAL:$2|שנוצר|שנוצרו}} במהלך ההתקנה {{PLURAL:$2|אינו בטוח|אינם בטוחים}} מספיק. מומלץ לשקול לשנות {{PLURAL:$2|אותו|אותם}} ידנית.",
'config-install-sysop' => 'יצירת חשבון מפעיל',
- 'config-install-subscribe-fail' => 'הרישום ל־mediawiki-announce לא הצליח',
+ 'config-install-subscribe-fail' => 'הרישום ל־mediawiki-announce לא הצליח: $1',
+ 'config-install-subscribe-notpossible' => 'cURL אינה מותקנת ו־allow_url_fopen אינה זמינה.',
'config-install-mainpage' => 'יצירת דף ראשי עם תוכן לפי בררת מחדל.',
'config-install-extension-tables' => 'יצירת טבלאות להרחבות מופעלות',
'config-install-mainpage-failed' => 'לא הצליחה הכנסת דף ראשי: $1.',
@@ -4824,17 +6355,68 @@ chmod a+w $3</pre></div>',
תוכנת ההתקנה יצרה את הקובץ <code>LocalSettings.php</code>.
הוא מכיל את כל ההגדרות שלכם.
-תצטרכו להוריד אותו ולשים אותו בבסיס ההתקנה של הוויקי שלכם (אות התיקייה שבה נמצא הקובץ index.php). ההורדה הייתה אמורה להתחיל באופן אוטומטי.
+תצטרכו להוריד אותו ולשים אותו בבסיס ההתקנה של הוויקי שלכם (אותה התיקייה שבה נמצא הקובץ index.php). ההורדה הייתה אמורה להתחיל באופן אוטומטי.
-אם ההורדה לא התחילה, אם אם ביטלתם אותה, אפשר להתחיל אותה מחדש בלחיצה על הקישור הבא:
+אם ההורדה לא התחילה, או אם ביטלתם אותה, אפשר להתחיל אותה מחדש בלחיצה על הקישור הבא:
$3
-'''שימו לב''': אם לא תעשו זאת עכשיו, קובץ ההגדרות המחולל לא יהיה זמין לכם שוב.
+'''שימו לב''': אם לא תעשו זאת עכשיו, קובץ ההגדרות המחוּלל לא יהיה זמין לכם שוב.
אחרי שתעשו את זה, תוכלו '''[$2 להיכנס לוויקי שלכם]'''.",
'config-download-localsettings' => 'הורדת LocalSettings.php',
'config-help' => 'עזרה',
+ 'mainpagetext' => "'''תוכנת מדיה־ויקי הותקנה בהצלחה.'''",
+ 'mainpagedocfooter' => 'היעזרו ב[//meta.wikimedia.org/wiki/Help:Contents מדריך למשתמש] למידע על שימוש בתוכנת הוויקי.
+
+== קישורים שימושיים ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings רשימת ההגדרות]
+* [//www.mediawiki.org/wiki/Manual:FAQ שאלות ותשובות על מדיה־ויקי]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce רשימת התפוצה על השקת גרסאות]',
+);
+
+/** Hindi (हिन्दी) */
+$messages['hi'] = array(
+ 'mainpagetext' => "'''मीडियाविकिका इन्स्टॉलेशन पूरा हो गया हैं ।'''",
+ 'mainpagedocfooter' => 'विकि सॉफ्टवेयरके इस्तेमाल के लिये [//meta.wikimedia.org/wiki/Help:Contents उपयोगकर्ता गाईड] देखें ।
+
+== शुरुवात करें ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings कॉन्फिगरेशन सेटींगकी सूची]
+* [//www.mediawiki.org/wiki/Manual:FAQ मीडियाविकिके बारे में प्राय: पूछे जाने वाले सवाल]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce मीडियाविकि मेलिंग लिस्ट]',
+);
+
+/** Fiji Hindi (Latin script) (Fiji Hindi)
+ * @author Thakurji
+ */
+$messages['hif-latn'] = array(
+ 'mainpagetext' => "'''MediaWiki ke safalta se install kar dewa gais hai.'''",
+ 'mainpagedocfooter' => "Wiki software ke use kare ke aur jaankari ke khatir [//meta.wikimedia.org/wiki/Help:Contents User's Guide] ke dekho.
+
+== Getting started ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Hiligaynon (Ilonggo)
+ * @author Anjoeli9806
+ */
+$messages['hil'] = array(
+ 'mainpagetext' => "'''Ang MediaWiki madinalag-on nga na-instala.'''",
+ 'mainpagedocfooter' => " Magkonsulta sa [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para sa mga impormasyon sa paggamit sang wiki nga software.
+
+== Pag-umpisa ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista sang mga konpigorasyon sang pagkay-o]
+* [//www.mediawiki.org/wiki/Manual:FAQ Mga Masami Pamangkoton sa MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista sang mga ginapadal-an sang sulat kon may paguha-on nga MediaWiki]",
+);
+
+/** Croatian (Hrvatski) */
+$messages['hr'] = array(
+ 'mainpagetext' => "'''Softver MediaWiki je uspješno instaliran.'''",
+ 'mainpagedocfooter' => 'Pogledajte [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentaciju o prilagodbi sučelja]
+i [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Vodič za suradnike] za pomoć pri uporabi i podešavanju.',
);
/** Upper Sorbian (Hornjoserbsce)
@@ -4891,10 +6473,10 @@ Skontroluj swój php.ini a zawěsć, zo <code>session.save_path</code> je na pra
'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]
+ 'config-sidebar' => '* [//www.mediawiki.org MediaWiki Startowa strona MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Nawod za wužiwarjow]
+* [//www.mediawiki.org/wiki/Manual:Contents Nawod za administratorow]
+* [//www.mediawiki.org/wiki/Manual:FAQ Huste prašenja]
----
* <doclink href=Readme>Čitaj mje</doclink>
* <doclink href=ReleaseNotes>Wersijowe informacije</doclink>
@@ -4909,8 +6491,13 @@ Njemóžeš MediaWiki instalować.',
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-no-db' => 'Njeda so přihódny ćěrjak datoweje banki namakać! Dyrbiš ćěrjak datoweje banki za PHP instalować.
+Slědowace typy datoweje banki so podpěruja: $1.
+
+Jeli wužiwaš zhromadnje wužiwany serwer, proš swojeho poskićowarja, zo by přihódny ćěrjak datoweje banki instalował.
+Jeli sy PHP sam kompilował, konfiguruj jón znowa z aktiwizowanym programom datoweje banki, na přikład z pomocu <code>./configure --with-mysql</code>.
+Jeli sy PHP z Debianoweho abo Ubuntuoweho paketa instalował, dyrbiš tež modul php5-mysql instalować.',
+ 'config-no-fts3' => "'''Warnowanje''': SQLite je so bjez [//sqlite.org/fts3.html FTS3-modula] kompilował, pytanske funkcije njebudu k dispoziciji stać.",
'config-register-globals' => "'''Warnowanje: Funkcija <code>[http://php.net/register_globals register_globals]</code> PHP je zmóžnjena.'''
'''Znjemóžń ju, jeli móžeš.'''
MediaWiki budźe fungować, ale twój serwer je potencielnym wěstotnym njedostatkam wustajeny.",
@@ -4926,7 +6513,7 @@ Jeli wužiwaš Mandrake, instaluj paket php-xml.',
'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-xcache' => '[http://xcache.lighttpd.net/ 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',
@@ -4944,7 +6531,7 @@ Instalacija bu přetorhnjena.",
'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ć.
+ '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.',
@@ -4969,7 +6556,7 @@ Změń ju jenož, jeli su přeswědčiwe přičiny za to.',
'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-support-postgres' => '* $1 je popularny system datoweje banki zjawneho žórła jako alternatiwa k MySQL ([http://www.php.net/manual/en/pgsql.installation.php nawod za kompilowanje PHP z podpěru PostgreSQL]). Móhło hišće někotre zmylki eksistować, a njeporuča so jón w produktiwnej wokolinje wužiwać.',
'config-header-mysql' => 'Nastajenja MySQL',
'config-header-postgres' => 'Nastajenja PostgreSQL',
'config-header-sqlite' => 'Nastajenja SQLite',
@@ -5050,7 +6637,7 @@ Podaj druhe wužiwarske mjeno.',
'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-email-help' => 'Zapodaj tu e-mejlowu adresu, zo by přijimanje e-mejlow wot druhich wužiwarjow w tutym wikiju zmóžnił, swoje hesło wróćo stajił a zdźělenki wo změnach na swojich wobkedźbowanych stronach dostał. Móžeš polo prózdne wostajić.',
'config-admin-error-user' => 'Interny zmylk při wutworjenju administratora z mjenom "<nowiki>$1</nowiki>".',
'config-admin-error-password' => 'Interny zmylk při nastajenju hesła za administratora "<nowiki>$1</nowiki>": <pre>$2</pre>',
'config-admin-error-bademail' => 'Sy njepłaćiwu e-mejlowu adresu zapodał.',
@@ -5068,9 +6655,7 @@ Móžeš nětko zbytnu konfiguraciju přeskočić a wiki hnydom instalować.',
'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-cc-0' => 'Creative Commons Zero (zjawnosći přistupny)',
'config-license-pd' => 'Powšitkownosći přistupny',
'config-license-cc-choose' => 'Swójsku licencu Creative Commons wubrać',
'config-email-settings' => 'E-mejlowe nastajenja',
@@ -5143,14 +6728,36 @@ Wutworjenje so přeskakuje.",
'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-keys' => 'Tajne kluče so tworja',
'config-install-sysop' => 'Tworjenje administratoroweho wužiwarskeho konta',
- 'config-install-subscribe-fail' => 'Abonowanje "mediawiki-announce" njemóžno',
+ 'config-install-subscribe-fail' => 'Abonowanje "mediawiki-announce" njemóžno: $1',
'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',
+ 'mainpagetext' => "'''MediaWiki bu wuspěšnje instalowany.'''",
+ 'mainpagedocfooter' => 'Prošu hlej [//meta.wikimedia.org/wiki/Help:Contents dokumentaciju] za informacije wo wužiwanju softwary.
+
+== Za nowačkow ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Wo nastajenjach]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]',
+);
+
+/** Haitian (Kreyòl ayisyen)
+ * @author Boukman
+ */
+$messages['ht'] = array(
+ 'mainpagetext' => "'''MedyaWiki byen enstale l.'''",
+ 'mainpagedocfooter' => 'Konsilte [//meta.wikimedia.org/wiki/Help:Konteni Gid Itilizatè] pou enfòmasyon sou kijan pou w itilize logisyèl wiki a.
+
+== Kijan pou kòmanse ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lis paramèt yo pou konfigirasyon]
+* [//www.mediawiki.org/wiki/Manyèl:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lis diskisyon ki parèt sou MediaWiki]',
);
/** Hungarian (Magyar)
@@ -5163,8 +6770,17 @@ $messages['hu'] = array(
'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-cli-upgrade' => 'A LocalSettings.php fájl megtalálható.
+A telepített rendszer frissítéséhez futtasd az update.php-t.',
'config-localsettings-key' => 'Frissítési kulcs:',
'config-localsettings-badkey' => 'A megadott kulcs érvénytelen.',
+ 'config-upgrade-key-missing' => 'A telepítő a MediaWiki meglévő példányát észlelte.
+A telepített rendszer frissítéséhez helyezd el az alábbi sort a LocalSettings.php végére:
+
+$1',
+ 'config-localsettings-incomplete' => 'A meglévő LocalSettings.php hiányosnak tűnik.
+A(z) $1 változó értéke nincs beállítva.
+Módosítsd a LocalSettings.php fájlt úgy, hogy ez a változó be legyen állítva, majd kattints a „Folytatás” gombra.',
'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',
@@ -5210,10 +6826,10 @@ Ez a program szabad szoftver; terjeszthető illetve módosítható a Free Softwa
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]
+ 'config-sidebar' => '* [//www.mediawiki.org A MediaWiki honlapja]
+* [//www.mediawiki.org/wiki/Help:Contents Felhasználói kézikönyv]
+* [//www.mediawiki.org/wiki/Manual:Contents Útmutató adminisztrátoroknak]
+* [//www.mediawiki.org/wiki/Manual:FAQ GyIK]
----
* <doclink href=Readme>Ismertető</doclink>
* <doclink href=ReleaseNotes>Kiadási megjegyzések</doclink>
@@ -5229,9 +6845,16 @@ 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.",
+Ha nagy látogatottságú oldalt üzemeltetsz, itt találhatsz további információkat [//www.mediawiki.org/wiki/Unicode_normalization_considerations a témáról].",
+ 'config-unicode-update-warning' => "'''Figyelmeztetés''': Az Unicode normalizáláshoz szükséges burkolókönyvtár [http://site.icu-project.org/ ICU projekt] függvénykönyvtárának régebbi változatát használja.
+Ha ügyelni kívánsz a Unicode használatára, fontold meg a [//www.mediawiki.org/wiki/Unicode_normalization_considerations frissítését].",
+ 'config-no-db' => 'Nem sikerült egyetlen használható adatbázis-illesztőprogramot sem találni. Telepítened kell egyet a PHP-hez.
+A következő adatbázistípusok támogatottak: $1.
+
+Ha megosztott tárhelyszolgáltatást használsz, kérd meg a szolgáltatódat, hogy telepítsen egy megfelelő illesztőprogramot.
+Ha a PHP-t magad fordítottad, konfiguráld újra úgy, hogy engedélyezve legyen egy adatbáziskliens, pl. a <code>./configure --with-mysql</code> parancs használatával.
+Ha a PHP-t Debian vagy Ubuntu csomaggal telepítetted, akkor szükséged lesz a php5-mysql modulra is.',
+ 'config-no-fts3' => "'''Figyelmeztetés''': Az SQLite [//sqlite.org/fts3.html FTS3 modul] nélkül lett fordítva, a keresési funkciók nem fognak működni ezen a rendszeren.",
'config-register-globals' => "'''Figyelmeztetés: A PHP <code>[http://php.net/register_globals register_globals]</code> beállítása engedélyezve van.'''
'''Tiltsd le, ha van rá lehetőséged.'''
A MediaWiki működőképes a beállítás használata mellett, de a szerver biztonsági kockázatnak lesz kitéve.",
@@ -5247,20 +6870,24 @@ A MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
'config-ze1' => "'''Kritikus hiba: a [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase] aktív!'''
Ez a beállítás borzalmas hibákat okoz a MediaWiki futása során.
A MediaWiki csak akkor telepíthető, ha ki van kapcsolva.",
+ 'config-safe-mode' => "'''Figyelmeztetés:''' A PHP [http://www.php.net/features.safe-mode safe mode]-ja be van kapcsolva.
+Problémákat okozhat, különösen a fájlfeltöltéseknél és a <code>math</code>-támogatás használatánál.",
'config-xml-bad' => 'A PHP XML-modulja hiányzik.
Egyes MediaWiki-funkciók, melyek ezt a modult igénylik, nem fognak működni ilyen beállítások mellett.
Ha Madrake-et futtatsz, telepítsd a php-xml csomagot.',
'config-pcre' => 'Úgy tűnik, hogy a PCRE támogató modul hiányzik.
A MediaWikinek Perl-kompatibilis reguláriskifejezés-függvényekre van szüksége a működéshez.',
+ 'config-pcre-no-utf8' => "'''Kritikus hiba''': Úgy tűnik, hogy a PHP PRCE modulja PRCE_UTF8 támogatás nélkül lett fordítva.
+A MediaWikinek UTF-8-támogatásra van szüksége a helyes működéshez.",
'config-memory-raised' => 'A PHP <code>memory_limit</code> beállításának értéke: $1. Meg lett növelve a következő értékre: $2.',
'config-memory-bad' => "'''Figyelmeztetés:''' A PHP <code>memory_limit</code> beállításának értéke $1.
Ez az érték valószínűleg túl kevés, a telepítés sikertelen lehet.",
- 'config-xcache' => 'Az [http://trac.lighttpd.net/xcache/ XCache] telepítve van',
+ 'config-xcache' => 'Az [http://xcache.lighttpd.net/ 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-no-cache' => "'''Figyelmeztetés:''' Nem található [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] és [http://www.iis.net/download/WinCacheForPhp WinCache] sem.
+Objektum-gyorsítótárazás nem lesz engedélyezve.",
'config-diff3-bad' => 'GNU diff3 nem található.',
'config-imagemagick' => 'Az ImageMagick megtalálható a rendszeren: <code>$1</code>.
A bélyegképek készítése engedélyezve lesz a feltöltések engedélyezése esetén.',
@@ -5270,8 +6897,17 @@ Bélyegképek készítése működni fog, miután engedélyezted a fájlfeltölt
A bélyegképek készítése le lesz tiltva.',
'config-no-uri' => "'''Hiba:''' Nem sikerült megállapítani a jelenlegi URI-t.
Telepítés megszakítva.",
+ 'config-using-server' => 'A következő szervernév használata: „<nowiki>$1</nowiki>”.',
+ 'config-using-uri' => 'A következő szerver URL-cím használata: „<nowiki>$1$2</nowiki>”.',
'config-uploads-not-safe' => "'''Figyelmeztetés:''' a feltöltésekhez használt alapértelmezett könyvtárban (<code>$1</code>) tetszőleges külső szkript futtatható.
-Habár a MediaWiki ellenőrzi a feltöltött fájlokat az efféle biztonsági veszélyek megtalálása érdekében, a feltöltés engedélyezése előtt erősen ajánlott a [http://www.mediawiki.org/wiki/Manual:Security#Upload_security a sérülékenység megszüntetése].",
+Habár a MediaWiki ellenőrzi a feltöltött fájlokat az efféle biztonsági veszélyek megtalálása érdekében, a feltöltés engedélyezése előtt erősen ajánlott a [//www.mediawiki.org/wiki/Manual:Security#Upload_security a sérülékenység megszüntetése].",
+ 'config-brokenlibxml' => 'A rendszereden a PHP és libxml2 verziók olyan kombinációja található meg, ami hibásan működik, és észrevehetetlen adatkárosodást okoz a MediaWikiben és más webalkalmazásokban.
+Frissíts a PHP 5.2.9-es vagy újabb, valamint a libxml2 2.7.3 vgy újabb verziójára ([//bugs.php.net/bug.php?id=45996 A hiba bejelentése a PHP-nél]).
+Telepítés megszakítva.',
+ 'config-using531' => 'A MediaWiki nem használható a PHP $1-es verziójával, mert hiba van a <code>__call()</code> függvénynek átadott referenciaparaméterekkel.
+A probléma kiküszöböléséhez frissíts a PHP 5.3.2-es verziójára, vagy használd a korábbi, 5.3.0-ásat.
+Telepítés megszakítva.',
+ 'config-suhosin-max-value-length' => 'A Suhosin telepítve van, és a GET paraméter hosszát $1 bájtra korlátozza. A MediaWiki erőforrásbetöltő összetevője megkerüli a problémát, de így csökkenni fog a teljesítmény. Ha lehetséges, állítsd be a suhosin.get.max_value_length értékét legalább 1024-re a php.iniben, és állítsd be a $wgResourceLoaderMaxQueryLength változót ugyanerre az értékre a LocalSettings.php-ben.',
'config-db-type' => 'Adatbázis típusa:',
'config-db-host' => 'Adatbázis hosztneve:',
'config-db-host-help' => 'Ha az adatbázisszerver másik szerveren található, add meg a hosztnevét vagy az IP-címét.
@@ -5287,9 +6923,16 @@ Ne tartalmazzon szóközt.
Ha megosztott webtárhelyet használsz, a szolgáltatód vagy egy konkrét adatbázisnevet ad neked használatra, vagy te magad hozhatsz létre adatbázisokat a vezérlőpulton keresztül.',
'config-db-name-oracle' => 'Adatbázisséma:',
+ 'config-db-account-oracle-warn' => 'Oracle adatbázisba való telepítésnek három támogatott módja van:
+
+Ha a telepítési folyamat során adatbázisfiókot szeretnél létrehozni, akkor egy olyan fiókot kell használnod, mely rendelkezik SYSDBA jogosultsággal, majd meg kell adnod a létrehozandó, webes hozzáféréshez használt fiók adatait. Emellett a fiók kézzel is létrehozható, ekkor ennek az adatait kell megadni (a fióknak rendelkeznie kell megfelelő jogosul adatbázis-objektumok létrehozásához), vagy megadhatsz két fiókot: egyet a létrehozáshoz szükséges jogosultságokkal, és egy korlátozottat a webes hozzáféréshez.
+
+A megfelelő jogosultságokkal rendelkező fiók létrehozásához használható szkript a szoftver „maintenance/oracle/” könyvtárában található. Ne feledd, hogy korlátozott fiók használatakor az alapértelmezett fiókkal nem végezhetőek el a karbantartási műveletek.',
'config-db-install-account' => 'A telepítéshez használt felhasználói fiók adatai',
'config-db-username' => 'Felhasználónév:',
'config-db-password' => 'Jelszó:',
+ 'config-db-password-empty' => 'Írd be az új adatbázis-felhasználó jelszavát: $1
+Van lehetőség jelszó nélküli felhasználók létrehozására, azonban ez nem ajánlott.',
'config-db-install-username' => 'Írd be az adatbázisrendszerhez való csatlakozáshoz használt felhasználónevet.
Ez nem a MediaWiki fiók felhasználóneve; ez az adatbázisrendszeren használt felhasználóneved.',
'config-db-install-password' => 'Írd be az adatbázisrendszerhez való csatlakozáshoz használt jelszót.
@@ -5313,15 +6956,26 @@ A mezőt általában üresen kell hagyni.',
'''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.",
+nem használhatod a [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] feletti karaktereket.",
'config-mysql-old' => 'A MySQL $1 vagy újabb verziója szükséges, a rendszeren $2 van.',
'config-db-port' => 'Adatbázisport:',
'config-db-schema' => 'MediaWiki-séma',
'config-db-schema-help' => 'A fenti sémák általában megfelelőek.
Csak akkor módosíts rajtuk, ha tudod, hogy szükséges.',
+ 'config-pg-test-error' => "Nem sikerült csatlakozni a(z) '''$1''' adatbázishoz: $2",
'config-sqlite-dir' => 'SQLite-adatkönyvtár:',
+ 'config-sqlite-dir-help' => "Az SQLite minden adatot egyetlen fájlban tárol.
+
+A megadott könyvtárban írási jogosultsággal kell rendelkeznie a webszervernek.
+
+'''Nem''' szabad elérhetőnek lennie weben keresztül, ezért nem rakjuk oda, ahol a PHP-fájljaid vannak.
+
+A telepítő készít egy <code>.htaccess</code> fájlt az adatbázis mellé, azonban ha valamilyen okból nem sikerül, akkor akárki hozzáférhet a teljes adatbázisodhoz. Ez a felhasználók adatai (e-mail címek, jelszók hashei) mellett a törölt változatokat és más, korlátozott hozzáférésű információkat is tartalmaz.
+
+Fontold meg az adatbázis más helyre történő elhelyezését, például a <code>/var/lib/mediawiki/tewikid</code> könyvtárba.",
'config-oracle-def-ts' => 'Alapértelmezett táblatér:',
'config-oracle-temp-ts' => 'Ideiglenes táblatér:',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'A MediaWiki a következő adatbázisrendszereket támogatja:
$1
@@ -5331,12 +6985,15 @@ Ha az alábbi listán nem találod azt a rendszert, melyet használni szeretnél
'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-support-ibm_db2' => '* Az $1 kereskedelmi vállalati adatbázisrendszer.',
'config-header-mysql' => 'MySQL-beállítások',
'config-header-postgres' => 'PostgreSQL-beállítások',
'config-header-sqlite' => 'SQLite-beállítások',
'config-header-oracle' => 'Oracle-beállítások',
+ 'config-header-ibm_db2' => 'IBM DB2-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-name' => 'Meg kell adnod az „Adatbázisnév” értékét',
+ 'config-missing-db-host' => 'Meg kell adnod az „Adatbázis hosztneve” értékét',
'config-missing-db-server-oracle' => 'Meg kell adnod az „Adatbázis TNS” értékét',
'config-invalid-db-server-oracle' => 'Érvénytelen adatbázis TNS: „$1”
Csak ASCII betűk (a-z, A-Z), számok (0-9), alulvonás (_) és pont (.) használható.',
@@ -5352,6 +7009,19 @@ Csak ASCII-karakterek (a-z, A-Z), számok (0-9) és alulvonás (_) használható
'config-db-sys-create-oracle' => 'A telepítő csak a SYSDBA fiókkal tud új felhasználói fiókot létrehozni.',
'config-db-sys-user-exists-oracle' => 'Már létezik „$1” nevű felhasználói fiók. A SYSDBA csak új fiók létrehozására használható!',
'config-postgres-old' => 'A PostgreSQL $1 vagy újabb verziója szükséges, a rendszeren $2 van.',
+ 'config-sqlite-name-help' => 'Válassz egy nevet a wiki azonosítására.
+Ne tartalmazzon szóközt vagy kötőjelet.
+Ez lesz az SQLite-adatfájl neve.',
+ 'config-sqlite-parent-unwritable-group' => 'Nem hozható létre a(z) <code><nowiki>$1</nowiki></code> adatkönyvtár, mert a szülőkönyvtárba (<code><nowiki>$2</nowiki></code>) nem írhat a webszerver.
+
+A telepítő megállapította, hogy mely felhasználó futtatja a webszervert.
+A folytatáshoz tedd írhatóvá a(z) <code><nowiki>$3</nowiki></code> könyvtárat.
+Unix/Linux rendszeren tedd a következőt:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
'config-sqlite-parent-unwritable-nogroup' => 'Nem lehet létrehozni az adatok tárolásához szükséges <code><nowiki>$1</nowiki></code> könyvtárat, mert a webszerver nem írhat a szülőkönyvtárba (<code><nowiki>$2</nowiki></code>).
A telepítő nem tudta megállapíteni, hogy melyik felhasználói fiókon fut a webszerver.
@@ -5369,16 +7039,38 @@ Módosítsd a jogosultságokat úgy, hogy a webszerver tudjon oda írni, majd pr
Ellenőrizd az adatkönyvtárat és az adatbázisnevet, majd próbáld újra.',
'config-sqlite-readonly' => 'A következő fájl nem írható: <code>$1</code>.',
+ 'config-sqlite-cant-create-db' => 'Nem sikerült létrehozni a következő adatbázisfájlt: <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'A PHP nem rendelkezik FTS3-támogatással, táblák visszaminősítése',
+ 'config-can-upgrade' => "Ebben az adatábizban MediaWiki-táblák találhatóak.
+A MediaWiki $1 verzióra történő frissítéséhez kattints a '''Folytatás''' gombra.",
+ 'config-upgrade-done' => "A frissítés befejeződött.
+
+Most már '''[$1 beléphetsz a wikibe]'''.
+
+Ha újra szeretnéd generálni a <code>LocalSettings.php</code> fájlt, kattints az alábbi gombra.
+Ez '''nem ajánlott''', csak akkor, ha problémák vannak a wikivel.",
+ 'config-upgrade-done-no-regenerate' => "A frissítés befejeződött.
+
+Most már '''[$1 beléphetsz a wikibe]'''.",
'config-regenerate' => 'LocalSettings.php elkészítése újra →',
'config-show-table-status' => 'A 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-account-same' => 'A telepítéshez használt fiók használata',
'config-db-web-create' => 'Fiók létrehozása, ha még nem létezik.',
+ 'config-db-web-no-create-privs' => 'A telepítéshez megadott fiók nem rendelkezik megfelelő jogosultságokkal új felhasználó létrehozásához.
+Az itt megadott fióknak léteznie kell.',
'config-mysql-engine' => 'Tárolómotor:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Figyelmeztetés''': A MyISAM tárolómotort választottad, ami nem ajánlott a MediaWiki használatánál, mert:
+* nagyon rosszul kezeli a párhuzamos lekéréseket a táblák zárolása miatt
+* sokkal nagyobb az esélye az adatkorrupció kialakulásának
+* a MediaWiki kódbázisa nem mindig úgy kezeli a MyISAM-ot, ahogyan kellene
+
+Ha a feltelepített MySQL támogatja az InnoDB-t, erősen ajánlott, hogy inkább azt válaszd.
+Ha nem, akkor lehet, hogy itt az ideje a frissítésnek.",
'config-mysql-engine-help' => "A legtöbb esetben az '''InnoDB''' a legjobb választás, mivel megfelelően támogatja a párhuzamosságot.
A '''MyISAM''' gyorsabb megoldás lehet egyfelhasználós vagy csak olvasható környezetekben, azonban a MyISAM-adatbázisok sokkal gyakrabban sérülnek meg, mint az InnoDB-adatbázisok.",
@@ -5388,7 +7080,8 @@ A '''MyISAM''' gyorsabb megoldás lehet egyfelhasználós vagy csak olvasható k
'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.",
+'''UTF-8-as módban''' a MySQL tudni fogja,hogy az adatok milyen karakterkészlettel rendelkeznek, és megfelelően átalakítja őket, azonban nem tárolhatóak olyan karakterek, melyek a [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane] felett vannak.",
+ 'config-ibm_db2-low-db-pagesize' => "A DB2 adatbázisodnak alapértelmezett táblatere van elégtelen lapmérettel. A lapméretnek legalább '''32K'''-nak kell lennie.",
'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.',
@@ -5400,6 +7093,10 @@ Ez sokkal hatékonyabb a MySQL UTF-8-as módjánál, és lehetővé teszi a telj
'config-project-namespace-help' => "A Wikipédia példáját követve számos wiki elkülöníti egy '''projekt névtérbe''' az irányelveit a tartalommal rendelkező lapoktól
Az ebben a névtérben található lapok nevei egy előtaggal kezdődnek, amit itt adhatsz meg.
Általában az előtag a wiki nevéből származik, de nem tartalmazhat írásjeleket, például „#”-t vagy „:”-t.",
+ 'config-ns-invalid' => 'A megadott névtér („<nowiki>$1</nowiki>”) érvénytelen.
+Válassz másik projektnévteret!',
+ 'config-ns-conflict' => 'A megadott névtér („<nowiki>$1</nowiki>”) ütközik az egyik alapértelmezett MediaWiki-névtérrel.
+Válassz másik projektnévteret!',
'config-admin-box' => 'Adminisztrátori fiók',
'config-admin-name' => 'Név:',
'config-admin-password' => 'Jelszó:',
@@ -5414,8 +7111,14 @@ Adj meg egy másik felhasználónevet.',
'config-admin-password-mismatch' => 'A megadott jelszavak nem egyeznek.',
'config-admin-email' => 'E-mail cím:',
'config-admin-email-help' => 'Add meg az e-mail címedet, hogy más felhasználók küldhessenek e-maileket a wikin keresztül, új jelszót tudj kérni, és értesülhess a figyelőlistádon lévő lapokon történt változásokról. Üresen is hagyhatod ezt a mezőt.',
+ 'config-admin-error-user' => 'Belső hiba történt a(z) „<nowiki>$1</nowiki>” nevű adminisztrátor létrehozásakor.',
+ 'config-admin-error-password' => 'Belső hiba történt a(z) „<nowiki>$1</nowiki>” nevű adminisztrátor jelszavának beállításakor: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Érvénytelen e-mail címet adtál meg.',
+ 'config-subscribe' => 'Feliratkozás a [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce kiadási bejelentések levelezőlistájára].',
'config-subscribe-help' => 'Ez egy alacsony forgalmú levelezőlista, ahol a kiadásokkal kapcsolatos bejelentések jelennek meg, a fontos biztonsági javításokkal együtt.
Ajánlott feliratkozni rá, és frissíteni a MediaWikit, ha új verzió jön ki.',
+ 'config-subscribe-noemail' => 'Anélkül próbáltál feliratkozni a kiadási bejelentések levelezőlistájára, hogy megadtál volna egy e-mail címet.
+Adj meg egyet, ha fel szeretnél iratkozni a levelezőlistára.',
'config-almost-done' => 'Már majdnem kész!
A további konfigurációt kihagyhatod, és most azonnal elindíthatod a wiki telepítését.',
'config-optional-continue' => 'További információk megadása.',
@@ -5435,41 +7138,70 @@ Választhatsz!
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].",
+Telepítés után jóval összetettebb jogosultságrendszer állítható össze, további információ a [//www.mediawiki.org/wiki/Manual:User_rights kézikönyv kapcsolódó bejegyzésében].",
'config-license' => 'Szerzői jog és licenc:',
'config-license-none' => 'Nincs licencjelzés',
'config-license-cc-by-sa' => 'Creative Commons Nevezd meg! - Így add tovább!',
+ 'config-license-cc-by' => 'Creative Commons Nevezd meg!',
'config-license-cc-by-nc-sa' => 'Creative Commons Nevezd meg! - Ne add el! - Így add tovább!',
- 'config-license-cc-0' => 'Creative Commons Zero',
- '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-cc-0' => 'Creative Commons Zero (közkincs)',
+ 'config-license-gfdl' => 'GNU Szabad Dokumentációs Licenc 1.3 vagy újabb',
'config-license-pd' => 'Közkincs',
'config-license-cc-choose' => 'Creative Commons-licenc választása',
- 'config-license-help' => "A legtöbb wiki [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.
+ 'config-license-help' => "A legtöbb wiki valamilyen [http://freedomdefined.org/Definition szabad licenc] alatt teszi közzé a szerkesztéseit.
+Ez erősíti a közösségi tulajdon érzését, és elősegíti a hosszú távú közreműködők megjelenését.
+Általában nem szükséges magán- vagy vállalati wiki esetén.
Ha a Wikipédiáról szeretnél szövegeket másolni, és a Wikipédián felhasználhassák a wikidben található szöveget, akkor a '''Creative Commons Nevezd meg! - Így add tovább!''' lehetőséget válaszd.
-A 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.",
+A Wikipédia korábban a GNU Szabad Dokumentációs Licencet használta.
+Ez a licenc még ma is használható, azonban nem könnyű megérteni,
+továbbá a GFDL alatt közzétett tartalom újrafelhasználása nehézkes.",
'config-email-settings' => 'E-mail beállítások',
'config-enable-email' => 'Kimenő e-mailek engedélyezése',
'config-enable-email-help' => 'E-mailek küldéséhez [http://www.php.net/manual/en/mail.configuration.php a PHP mail beállításait] megfelelően meg kell adni.
Ha nem akarsz semmilyen e-mailes funkciót használni, itt tilthatod le őket.',
+ 'config-email-user' => 'A felhasználók küldhetnek egymásnak e-maileket',
+ 'config-email-user-help' => 'Bármelyik felhasználó küldhet másiknak e-mail üzenetet, amennyiben engedélyezték a lehetőséget a beállításaiknál.',
+ 'config-email-usertalk' => 'Vitalapi értesítések engedélyezése',
+ 'config-email-usertalk-help' => 'A felhasználók értesítéseket kapnak a vitalapjuk változásairól, amennyiben engedélyezték ezt a lehetőséget a beállításaiknál.',
+ 'config-email-watchlist' => 'Figyelőlistai értesítések engedélyezése',
+ 'config-email-watchlist-help' => 'A felhasználók értesítéseket kapnak a figyelt lapjaik változásairól, amennyiben engedélyezték ezt a lehetőséget a beállításaiknál.',
+ 'config-email-auth' => 'E-mailes hitelesítés engedélyezése',
+ 'config-email-auth-help' => "Ha a beállítás engedélyezve van, a felhasználóknak meg kell erősíteniük az e-mail címüket egy kiküldött link segítségével, amikor megadják vagy módosítják azt.
+Csak a megerősített e-mail címmel rendelkezők kaphatnak e-maileket más felhasználóktól vagy értesítéseket.
+A beállítás engedélyezése '''ajánlott''' publikus wikiknél, mivel így megakadályozható az e-mailes funkciókkal való visszaélés.",
'config-email-sender' => 'Válaszcím:',
+ 'config-email-sender-help' => 'Add meg a kimenő e-mail-üzenetek válaszcímét.
+Ide lesznek küldve a visszapattant üzenetek is.
+Számos levelezőszerver számára a cím domainrészének érvényesnek kell lennie.',
'config-upload-settings' => 'Képek és fájlok feltöltése',
'config-upload-enable' => 'Fájlfeltöltés engedélyezése',
+ 'config-upload-help' => 'A fájlfeltöltés lehetséges biztonsági kockázatoknak teszi ki a szerveredet.
+További információért olvasd el a [//www.mediawiki.org/wiki/Manual:Security biztonságról szóló szakaszt] a kézikönyvben.
+
+A fájlfeltöltés engedélyezéséhez változtasd meg a MediaWiki gyökérkönyvtárában található <code>images</code> alkönyvtár jogosultságát úgy, hogy a szerver írhasson oda, majd engedélyezd itt a beállítást.',
'config-upload-deleted' => 'Törölt fájlok könyvtára:',
+ 'config-upload-deleted-help' => 'Válaszd ki azt a könyvtárat, ahol a törölt fájlok lesznek archiválva.
+Normális esetben ennek nem szabad elérhetőnek lennie az internetről.',
'config-logo' => 'A logó URL-címe:',
+ 'config-logo-help' => 'A MediaWiki alapértelmezett felülete helyet ad egy 135×160 pixeles logónak a bal felső sarokban.
+Tölts fel egy megfelelő méretű képet, majd írd be ide az URL-címét!
+
+Ha nem szeretnél logót használni, egyszerűen hagyd üresen a mezőt.',
'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.
+ 'config-instantcommons-help' => 'Az [//www.mediawiki.org/wiki/InstantCommons Instant Commons] lehetővé teszi, hogy a wikin használhassák a [//commons.wikimedia.org/ Wikimedia Commons] oldalon található képeket, hangokat és más médiafájlokat.
+A használatához a MediaWikinek internethozzáférésre van szüksége.
A funkcióról és hogy hogyan állítható be más wikik esetén [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos a kézikönyvben] találhatsz további információkat.',
+ 'config-cc-error' => 'A Creative Commons-licencválasztó nem tért vissza eredménnyel.
+Add meg kézzel a licencet.',
'config-cc-again' => 'Válassz újra…',
+ 'config-cc-not-chosen' => 'Válaszd ki a kívánt Creative Commons licencet, majd kattints a „Folytatás gombra”!',
'config-advanced-settings' => 'Haladó beállítások',
'config-cache-options' => 'Objektum-gyorsítótárazás beállításai:',
+ 'config-cache-help' => 'Az objektumgyorsítótárazás célja, hogy felgyorsítsa a MediaWiki működését a gyakran használt adatok gyorsítótárazásával.
+Közepes vagy nagyobb oldalak esetén erősen ajánlott a használata, de kisebb oldalak esetén is hasznos lehet.',
'config-cache-none' => 'Nincs gyorsítótárazás (minden funkció működik, de nagyobb wiki esetében lassabb működést eredményezhet)',
'config-cache-accel' => 'PHP-objektumok gyorsítótárazása (APC, eAccelerator, XCache or WinCache)',
'config-cache-memcached' => 'Memcached használata (további telepítés és konfigurálás szükséges)',
@@ -5478,23 +7210,52 @@ A funkcióról és hogy hogyan állítható be más wikik esetén [http://mediaw
Vesszővel kell elválasztani őket, és meg kell adni a portot is. Például:
127.0.0.1:11211
192.168.1.25:11211',
+ 'config-memcache-needservers' => 'Memcachedet választottad gyorsítótárnak, de nem adtál meg egyetlen szervert sem.',
+ 'config-memcache-badip' => 'Érvénytelen IP-címet adtál meg a Memcachednek: $1.',
+ 'config-memcache-noport' => 'Nem adtál meg portot a Memcached-szervernek: $1.
+Ha nem ismered a portszámot, használd az alapértelmezettet: 11211.',
+ 'config-memcache-badport' => 'A Memcached a(z) $1 és $2 közötti portokat szokta használni.',
'config-extensions' => 'Kiterjesztések',
+ 'config-extensions-help' => 'A fent felsorolt kiterjesztések találhatóak meg az <code>./extensions</code> könyvtárban.
+
+Lehetséges, hogy további beállításra lesz szükség hozzájuk, de már most engedélyezheted őket.',
+ 'config-install-alreadydone' => "'''Figyelmeztetés:''' Úgy tűnik, hogy a MediaWiki telepítve van, és te ismét megpróbálod telepíteni.
+Folytasd a következő oldalon.",
+ 'config-install-begin' => 'A „{{int:config-continue}}” gomb megnyomása elindítja a MediaWiki telepítését.
+Ha szeretnél módosítani a beállításokon, kattints a vissza gombra.',
'config-install-step-done' => 'kész',
'config-install-step-failed' => 'sikertelen',
'config-install-extensions' => 'Kiterjesztések beillesztése',
'config-install-database' => 'Adatbázis felállítása',
+ 'config-install-schema' => 'Adatbázis-szerkezet létrehozása',
+ 'config-install-pg-schema-not-exist' => 'A PostgreSQL-adatbázis nem létezik.',
+ 'config-install-pg-schema-failed' => 'A táblák létrehozása nem sikerült.
+Ellenőrizd, hogy „$1” felhasználó írhat-e a következő adatbázisba: „$2”.',
+ 'config-install-pg-commit' => 'Változtatások közzététele',
+ 'config-install-pg-plpgsql' => 'PL/pgSQL nyelv meglétének ellenőrzése',
+ 'config-pg-no-plpgsql' => 'Telepítened kell a PL/pgSQL nyelvet a következő adatbázishoz: $1',
+ 'config-pg-no-create-privs' => 'A telepítéshez megadott felhasználói fiók nem rendelkezik új fiók létrehozásához szükséges jogosultságokkal.',
'config-install-user' => 'Adatbázis-felhasználó létrehozása',
+ 'config-install-user-alreadyexists' => 'Már létezik „$1” nevű felhasználó',
+ 'config-install-user-create-failed' => 'Nem sikerült a(z) „$1” nevű felhasználó létrehozása: $2',
+ 'config-install-user-grant-failed' => 'Nem sikerült jogosultságokkal felruházni a(z) „$1” nevű felhasználót: $2',
+ 'config-install-user-missing' => 'A megadott felhasználó („$1”) nem létezik.',
+ 'config-install-user-missing-create' => 'A megadott felhasználó („$1”) nem létezik.
+Pipáld ki a „Fiók létrehozása” dobozt, ha létre szeretnéd hozni.',
'config-install-tables' => 'Táblák létrehozása',
'config-install-tables-exist' => "'''Figyelmeztetés''': úgy tűnik, hogy a MediaWiki táblái már léteznek.
Létrehozás kihagyása.",
'config-install-tables-failed' => "'''Hiba''': a tábla létrehozása nem sikerült a következő miatt: $1",
'config-install-interwiki' => 'Alapértelmezett nyelvközihivatkozás-tábla feltöltése',
'config-install-interwiki-list' => 'Az <code>interwiki.list</code> fájl nem található.',
+ 'config-install-interwiki-exists' => "'''Figyelmeztetés''': Úgy tűnik, hogy az interwiki táblában már vannak bejegyzések.
+Alapértelmezett lista kihagyása.",
'config-install-stats' => 'Statisztika inicializálása',
'config-install-keys' => 'Titkos kulcsok generálása',
'config-insecure-keys' => "'''Figyelmeztetés:''' A telepítés során generált $1 {{PLURAL:$2|biztonsági kulcs|biztonsági kulcsok}} nem teljesen $1 {{PLURAL:$2|biztonságos|biztonságosak}}. Érdemes {{PLURAL:$2||őket}} manuálisan megváltoztatni.",
'config-install-sysop' => 'Az adminisztrátor felhasználói fiókjának létrehozása',
- 'config-install-subscribe-fail' => 'Nem sikerült feliratkozni a mediawiki-announce levelezőlistára',
+ 'config-install-subscribe-fail' => 'Nem sikerült feliratkozni a mediawiki-announce levelezőlistára: $1',
+ 'config-install-subscribe-notpossible' => 'A cURL nincs telepítve és az allow_url_fopen nem érhető el.',
'config-install-mainpage' => 'Kezdőlap létrehozása az alapértelmezett tartalommal',
'config-install-extension-tables' => 'Táblák létrehozása az engedélyezett kiterjesztésekhez',
'config-install-mainpage-failed' => 'Nemsikerült létrehozni a kezdőlapot: $1',
@@ -5514,6 +7275,25 @@ $3
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',
+ 'mainpagetext' => "'''A MediaWiki telepítése sikeresen befejeződött.'''",
+ 'mainpagedocfooter' => "Ha segítségre van szükséged a wikiszoftver használatához, akkor keresd fel a [//meta.wikimedia.org/wiki/Help:Contents User's Guide] oldalt.
+
+== Alapok (angol nyelven) ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Beállítások listája]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki GyIK]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-kiadások levelezőlistája]",
+);
+
+/** Armenian (Հայերեն) */
+$messages['hy'] = array(
+ 'mainpagetext' => "'''«MediaWiki» ծրագիրը հաջողությամբ տեղադրվեց։'''",
+ 'mainpagedocfooter' => "Այցելեք [//meta.wikimedia.org/wiki/Help:Contents User's Guide]՝ վիքի ծրագրային ապահովման օգտագործման մասին տեղեկությունների համար։
+
+== Որոշ օգտակար ռեսուրսներ ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
);
/** Interlingua (Interlingua)
@@ -5583,10 +7363,10 @@ Iste programma es distribuite in le sperantia que illo sia utile, ma '''sin gara
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]
+ 'config-sidebar' => '* [//www.mediawiki.org Pagina principal de MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Guida pro usatores]
+* [//www.mediawiki.org/wiki/Manual:Contents Guida pro administratores]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
----
* <doclink href=Readme>Lege me</doclink>
* <doclink href=ReleaseNotes>Notas de iste version</doclink>
@@ -5602,17 +7382,16 @@ 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].",
+Si tu sito ha un alte volumine de traffico, tu deberea informar te un poco super le [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalisation Unicode].",
'config-unicode-update-warning' => "'''Aviso''': Le version installate del bibliotheca inveloppante pro normalisation Unicode usa un version ancian del bibliotheca del [http://site.icu-project.org/ projecto ICU].
-Tu deberea [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.
+Tu deberea [//www.mediawiki.org/wiki/Unicode_normalization_considerations actualisar lo] si le uso de Unicode importa a te.",
+ 'config-no-db' => 'Non poteva trovar un driver appropriate pro le base de datos! Es necessari installar un driver de base de datos pro PHP.
Le sequente typos de base de datos es supportate: $1.
-Si tu sito usa un servitor partite (shared hosting), demanda a tu providitor de installar un driver de base de datos appropriate.
+Si tu sito usa un servitor dividite (shared hosting), demanda a tu providitor de installar un driver de base de datos appropriate.
Si tu compilava PHP tu mesme, reconfigura lo con un cliente de base de datos activate, per exemplo usante <code>./configure --with-mysql</code>.
Si tu installava PHP ex un pacchetto Debian o Ubuntu, tu debe installar equalmente le modulo php5-mysql.',
- 'config-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-no-fts3' => "'''Attention''': SQLite es compilate sin [//sqlite.org/fts3.html modulo FTS3]; functionalitate de recerca non essera disponibile in iste back-end.",
'config-register-globals' => "'''Attention: le option <code>[http://php.net/register_globals register_globals]</code> de PHP es activate.'''
'''Disactiva lo si tu pote.'''
MediaWiki functionara, ma tu servitor es exponite a potential vulnerabilitates de securitate.",
@@ -5641,12 +7420,14 @@ MediaWiki require supporto de UTF-8 pro functionar correctemente.",
'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-xcache' => '[http://xcache.lighttpd.net/ 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].
+ 'config-no-cache' => "'''Aviso:''' Non poteva trovar [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
Le cache de objectos non es activate.",
+ 'config-mod-security' => "'''Attention''': [http://modsecurity.org/ mod_security] es active in tu servitor web. Si mal configurate, isto pote causar problemas pro MediaWiki o altere software que permitte al usatores de publicar contento arbitrari.
+Consulta le [http://modsecurity.org/documentation/ documentation de mod_security] o contacta le servicio de adjuta de tu host si tu incontra estranie errores.",
'config-diff3-bad' => 'GNU diff3 non trovate.',
'config-imagemagick' => 'ImageMagick trovate: <code>$1</code>.
Le miniaturas de imagines essera activate si tu activa le incargamento de files.',
@@ -5656,21 +7437,29 @@ Le miniaturas de imagines essera activate si tu activa le incargamento de files.
Le miniaturas de imagines essera disactivate.',
'config-no-uri' => "'''Error:''' Non poteva determinar le URI actual.
Installation abortate.",
+ 'config-no-cli-uri' => "'''Attention''': Cammino al script (--scriptpath) non specificate. Le predefinition es usate: <code>$1</code>.",
+ 'config-using-server' => 'Es usate le nomine de servitor "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'Le URL de servitor "<nowiki>$1$2</nowiki>" es usate.',
'config-uploads-not-safe' => "'''Aviso:''' Le directorio predefinite pro files incargate <code>$1</code> es vulnerabile al execution arbitrari de scripts.
-Ben que MediaWiki verifica tote le files incargate contra le menacias de securitate, il es altemente recommendate [http://www.mediawiki.org/wiki/Manual:Security#Upload_security remediar iste vulnerabilitate de securitate] ante de activar le incargamento de files.",
+Ben que MediaWiki verifica tote le files incargate contra le menacias de securitate, il es altemente recommendate [//www.mediawiki.org/wiki/Manual:Security#Upload_security remediar iste vulnerabilitate de securitate] ante de activar le incargamento de files.",
+ 'config-no-cli-uploads-check' => "'''Attention:''' Le directorio predefinite pro files incargate (<code>$1</code>) non es verificate contra le vulnerabilitate
+al execution arbitrari de scripts durante le installation de CLI.",
'config-brokenlibxml' => 'Vostre systema ha un combination de versiones de PHP e libxml2 que es defectuose e pote causar corruption celate de datos in MediaWiki e altere applicationes web.
-Actualisa a PHP 5.2.9 o plus recente e libxml2 2.7.3 o plus recente ([http://bugs.php.net/bug.php?id=45996 problema reportate presso PHP]).
+Actualisa a PHP 5.2.9 o plus recente e libxml2 2.7.3 o plus recente ([//bugs.php.net/bug.php?id=45996 problema reportate presso PHP]).
Installation abortate.',
'config-using531' => 'MediaWiki non pote esser usate con PHP $1 a causa de un defecto concernente parametros de referentia a <code>__call()</code>.
Actualisa a PHP 5.3.2 o plus recente, o retrograda a PHP 5.3.0 pro remediar isto.
Installation abortate.',
+ 'config-suhosin-max-value-length' => 'Suhosin es installate e limita le longitude del parametro GET a $1 bytes. Le componente ResourceLoader de MediaWiki pote contornar iste limite, ma isto degradara le rendimento. Si possibile, tu deberea mitter suhosin.get.max_value_length a 1024 o plus in php.ini , e mitter $wgResourceLoaderMaxQueryLength al mesme valor in LocalSettings.php .',
'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.',
+Si tu face le installation in un servitor Windows e usa MySQL, le nomine "localhost" possibilemente non functiona como nomine de servitor. In tal caso, essaya "127.0.0.1", i.e. le adresse IP local.
+
+Si tu usa PostgreSQL, lassa iste campo vacue pro connecter via un "socket" de Unix.',
'config-db-host-oracle' => 'TNS del base de datos:',
'config-db-host-oracle-help' => 'Entra un [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm nomine Local Connect] valide; un file tnsnames.ora debe esser visibile a iste installation.<br />Si tu usa bibliothecas de cliente 10g o plus recente, tu pote anque usar le methodo de nomination [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
'config-db-wiki-settings' => 'Identificar iste wiki',
@@ -5711,12 +7500,13 @@ Iste campo usualmente resta vacue.',
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].",
+ma non te permittera immagazinar characteres supra le [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Basic].",
'config-mysql-old' => 'MySQL $1 o plus recente es requirite, tu ha $2.',
'config-db-port' => 'Porto de base de datos:',
'config-db-schema' => 'Schema pro MediaWiki',
'config-db-schema-help' => 'Iste schema es generalmente correcte.
Solmente cambia lo si tu es secur que es necessari.',
+ 'config-pg-test-error' => "Impossibile connecter al base de datos '''$1''': $2",
'config-sqlite-dir' => 'Directorio pro le datos de SQLite:',
'config-sqlite-dir-help' => "SQLite immagazina tote le datos in un sol file.
@@ -5734,6 +7524,7 @@ Considera poner le base de datos in un loco completemente differente, per exempl
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'MediaWiki supporta le sequente systemas de base de datos:
$1
@@ -5743,10 +7534,12 @@ Si tu non vide hic infra le systema de base de datos que tu tenta usar, alora se
'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-support-ibm_db2' => '* $1 es un systema commercial de base de datos pro interprisas.',
'config-header-mysql' => 'Configuration de MySQL',
'config-header-postgres' => 'Configuration de PostgreSQL',
'config-header-sqlite' => 'Configuration de SQLite',
'config-header-oracle' => 'Configuration de Oracle',
+ 'config-header-ibm_db2' => 'Configurationes pro IBM DB2',
'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"',
@@ -5820,6 +7613,13 @@ Le conto que tu specifica hic debe jam exister.',
'config-mysql-engine' => 'Motor de immagazinage:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "* '''Attention:''' Tu ha seligite MyISAM como motor de immagazinage pro MySQL, lo que non es recommendate pro uso con MediaWiki, perque:
+* illo a pena supporta le processamento simultanee a causa del blocada le tabulas
+* illo es plus susceptibile al corruption que altere motores
+* le base de codice de MediaWiki non sempre manea MyISAM como illo deberea
+
+Si tu installation de MySQL supporta InnoDB, es multo recommendate que tu selige iste in su loco.
+Si tu installation de MySQL non supporta InnoDB, forsan isto es un bon occasion pro actualisar lo.",
'config-mysql-engine-help' => "'''InnoDB''' es quasi sempre le melior option, post que illo ha bon supporto pro simultaneitate.
'''MyISAM''' pote esser plus rapide in installationes a usator singule o a lectura solmente.
@@ -5830,7 +7630,8 @@ Le bases de datos MyISAM tende a esser corrumpite plus frequentemente que le bas
'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].",
+In '''modo UTF-8''', MySQL cognoscera le codification de characteres usate pro tu dats, e pote presentar e converter lo appropriatemente, ma illo non permittera immagazinar characteres supra le [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Basic].",
+ 'config-ibm_db2-low-db-pagesize' => 'Tu base de datos DB2 ha un "tablespace" (spatio de tabella) predefinite con un "pagesize" (dimension de pagina) insufficiente. Le "pagesize" debe esser \'\'\'32K\'\'\' o plus.',
'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.',
@@ -5866,6 +7667,8 @@ Specifica un altere nomine de usator.',
'config-subscribe' => 'Subscribe al [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce lista de diffusion pro annuncios de nove versiones].',
'config-subscribe-help' => 'Isto es un lista de e-mail a basse volumine pro annuncios de nove versiones, includente importante annuncios de securitate.
Tu deberea subscriber a illo e actualisar tu installation de MediaWiki quando nove versiones es editate.',
+ 'config-subscribe-noemail' => 'Tu tentava abonar te al lista de diffusion pro annunciamento de nove versiones sin fornir un adresse de e-mail.
+Per favor specifica un adresse de e-mail si tu vole abonar te al lista de diffusion.',
'config-almost-done' => 'Tu ha quasi finite!
Tu pote ora saltar le configuration remanente e installar le wiki immediatemente.',
'config-optional-continue' => 'Pone me plus questiones.',
@@ -5887,14 +7690,14 @@ Un wiki con '''{{int:config-profile-no-anon}}''' attribue additional responsabil
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].",
+Configurationes de derectos de usator plus complexe es disponibile post installation, vide le [//www.mediawiki.org/wiki/Manual:User_rights pertinente section del manual].",
'config-license' => 'Copyright e licentia:',
'config-license-none' => 'Nulle licentia in pede de paginas',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by' => 'Creative Commons Attribution',
'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero',
- '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-cc-0' => 'Creative Commons Zero (dominio public)',
+ 'config-license-gfdl' => 'Licentia GNU pro Documentation Libere 1.3 o plus recente',
'config-license-pd' => 'Dominio public',
'config-license-cc-choose' => 'Seliger un licentia Creative Commons personalisate',
'config-license-help' => "Multe wikis public pone tote le contributiones sub un [http://freedomdefined.org/Definition/Ia?uselang=ia licentia libere].
@@ -5903,8 +7706,9 @@ 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.",
+Wikipedia usava anteriormente le Licentia GNU pro Documentation Libere (GFDL).
+Iste es un licentia valide, ma es difficile a comprender.
+Il es anque difficile reusar le contento licentiate sub GFDL.",
'config-email-settings' => 'Configuration de e-mail',
'config-enable-email' => 'Activar le e-mail sortiente',
'config-enable-email-help' => 'Si tu vole que e-mail functiona, [http://www.php.net/manual/en/mail.configuration.php le optiones de e-mail de PHP] debe esser configurate correctemente.
@@ -5926,7 +7730,7 @@ Multe servitores de e-mail require que al minus le parte de nomine de dominio si
'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 plus information, lege le [//www.mediawiki.org/wiki/Manual:Security section de securitate] in le manual.
Pro activar le incargamento de files, cambia le modo in le subdirectorio <code>images</code> sub le directorio-radice de MediaWiki de sorta que le servitor web pote scriber in illo.
Postea activa iste option.',
@@ -5934,13 +7738,13 @@ Postea activa iste option.',
'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.
+ 'config-logo-help' => 'Le apparentia predefinite de MediaWiki include spatio pro un logotypo de 135×160 pixels supra le menu del barra lateral.
Incarga un imagine con le dimensiones appropriate, e entra le URL hic.
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.
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] es un function que permitte a wikis de usar imagines, sonos e altere multimedia trovate in le sito [//commons.wikimedia.org/ Wikimedia Commons].
+Pro poter facer isto, MediaWiki require accesso a Internet.
Pro plus information super iste function, includente instructiones super como configurar lo pro wikis altere que Wikimedia Commons, consulta [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos le manual].',
'config-cc-error' => 'Le selector de licentia Creative Commons non dava un resultato.
@@ -5976,6 +7780,7 @@ Pro facer alterationes, clicca sur "Retro".',
'config-install-step-failed' => 'fallite',
'config-install-extensions' => 'Include le extensiones',
'config-install-database' => 'Configura le base de datos',
+ 'config-install-schema' => 'Creation de schema',
'config-install-pg-schema-not-exist' => 'Iste schema de PostgreSQL non existe',
'config-install-pg-schema-failed' => 'Le creation del tabellas falleva.
Assecura te que le usator "$1" pote scriber in le schema "$2".',
@@ -5983,10 +7788,17 @@ Assecura te que le usator "$1" pote scriber in le schema "$2".',
'config-install-pg-plpgsql' => 'Verifica le presentia del linguage PL/pgSQL',
'config-pg-no-plpgsql' => 'Es necessari installar le linguage PL/pgSQL in le base de datos $1',
'config-pg-no-create-privs' => 'Le conto que tu specificava pro installation non ha sufficiente privilegios pro crear un conto.',
+ 'config-pg-not-in-role' => 'Le conto que tu specificava pro le usator web ja existe.
+Le conto que tu specificava pro installation non es superusator e non es membro del rolo de usator web, dunque es incapace de crear objectos possedite per le usator web.
+
+MediaWiki require actualmente que le tabellas sia possedite per le usator web. Per favor specifica un altere nomine de conto web, o clicca super "retornar" e specifica un usator de installation con sufficiente privilegios.',
'config-install-user' => 'Crea usator pro base de datos',
'config-install-user-alreadyexists' => 'Le usator "$1" ja existe',
'config-install-user-create-failed' => 'Le creation del usator "$1" ha fallite: $2',
'config-install-user-grant-failed' => 'Le concession de permission al usator "$1" falleva: $2',
+ 'config-install-user-missing' => 'Le usator specificate, "$1", non existe.',
+ 'config-install-user-missing-create' => 'Le usator specificate, "$1", non existe.
+Per favor marca le quadrato "crear conto" hic infra si tu vole crear lo.',
'config-install-tables' => 'Crea tabellas',
'config-install-tables-exist' => "'''Aviso''': Il pare que le tabellas de MediaWiki jam existe.
Le creation es saltate.",
@@ -5996,9 +7808,11 @@ Le creation es saltate.",
'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-keys' => 'Generation de claves secrete',
+ 'config-insecure-keys' => "'''Attention:''' {{PLURAL:$2|Un clave|Alcun claves}} secur ($1) generate durante le installation non es completemente secur. Considera cambiar {{PLURAL:$2|lo|los}} manualmente.",
'config-install-sysop' => 'Crea conto de usator pro administrator',
- 'config-install-subscribe-fail' => 'Impossibile subscriber a mediawiki-announce',
+ 'config-install-subscribe-fail' => 'Impossibile subscriber a mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'cURL non es installate e allow_url_fopen non es disponibile.',
'config-install-mainpage' => 'Crea pagina principal con contento predefinite',
'config-install-extension-tables' => 'Creation de tabellas pro le extensiones activate',
'config-install-mainpage-failed' => 'Non poteva inserer le pagina principal: $1',
@@ -6020,6 +7834,13 @@ $3
Post facer isto, tu pote '''[$2 entrar in tu wiki]'''.",
'config-download-localsettings' => 'Discargar LocalSettings.php',
'config-help' => 'adjuta',
+ 'mainpagetext' => "'''MediaWiki ha essite installate con successo.'''",
+ 'mainpagedocfooter' => 'Consulta le [//meta.wikimedia.org/wiki/Help:Contents Guida del usator] pro informationes super le uso del software wiki.
+
+== Pro initiar ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de configurationes]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ a proposito de MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de diffusion pro annuncios de nove versiones de MediaWiki]',
);
/** Indonesian (Bahasa Indonesia)
@@ -6035,7 +7856,7 @@ $messages['id'] = array(
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.',
+Untuk meningkatkan versi, harap jalankan update.php.',
'config-localsettings-key' => 'Kunci pemutakhiran:',
'config-localsettings-badkey' => 'Kunci yang Anda berikan tidak benar',
'config-upgrade-key-missing' => 'Suatu instalasi MediaWiki telah terdeteksi.
@@ -6091,29 +7912,35 @@ Program ini didistribusikan dengan harapan bahwa itu akan berguna, tetapi '''tan
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-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/id Situs MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents/id Pedoman Pengguna]
+* [//www.mediawiki.org/wiki/Manual:Contents/id Pedoman Administrator]
+* [//www.mediawiki.org/wiki/Manual:FAQ/id FAQ]
+----
+* <doclink href=Readme>Read me</doclink>
+* <doclink href=ReleaseNotes>Release notes</doclink>
+* <doclink href=Copying>Copying</doclink>
+* <doclink href=UpgradeDoc>Upgrading</doclink>',
'config-env-good' => 'Kondisi telah diperiksa.
Anda dapat menginstal MediaWiki.',
'config-env-bad' => 'Kondisi telah diperiksa.
Anda tidak dapat menginstal MediaWiki.',
'config-env-php' => 'PHP $1 diinstal.',
+ 'config-env-php-toolow' => 'PHP $1 telah terinstal.
+Namun, MediaWiki memerlukan PHP $2 atau lebih tinggi.',
'config-unicode-using-utf8' => 'Menggunakan utf8_normalize.so Brion Vibber untuk normalisasi Unicode.',
'config-unicode-using-intl' => 'Menggunakan [http://pecl.php.net/intl ekstensi PECL intl] untuk normalisasi Unicode.',
- 'config-unicode-pure-php-warning' => "'''Peringatan''': [http://pecl.php.net/intl Ekstensi intl PECL] untuk menangani normalisasi Unicode tidak tersedia, kembali menggunakan implementasi murni PHP yang lambat.
-Jika Anda menjalankan situs berlalu lintas tinggi, Anda harus sedikit membaca [http://www.mediawiki.org/wiki/Unicode_normalization_considerations normalisasi Unicode].",
+ 'config-unicode-pure-php-warning' => "'''Peringatan''': [http://pecl.php.net/intl Ekstensi intl PECL] untuk menangani normalisasi Unicode tidak tersedia, kembali menggunakan implementasi murni PHP yang lambat.
+Jika Anda menjalankan situs berlalu lintas tinggi, Anda harus sedikit membaca [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalisasi Unicode].",
'config-unicode-update-warning' => "'''Peringatan''': Versi terinstal dari pembungkus normalisasi Unicode menggunakan versi lama pustaka [http://site.icu-project.org/ proyek ICU].
-Anda harus [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.
+Anda harus [//www.mediawiki.org/wiki/Unicode_normalization_considerations memutakhirkannya] jika Anda ingin menggunakan Unicode.",
+ 'config-no-db' => 'Pengandar basis data yang sesuai tidak ditemukan! Anda perlu menginstal pengandar basis data untuk PHP.
Jenis basis data yang didukung: $1.
-Jika Anda menggunakan inang bersama, mintalah penyedia inang Anda untuk menginstal pengandar basis data yang cocok.
+Jika Anda menggunakan inang bersama, mintalah penyedia inang Anda untuk menginstal pengandar basis data yang sesuai.
Jika Anda mengompilasi sendiri PHP, ubahlah konfigurasinya dengan mengaktifkan klien basis data, misalnya menggunakan <code>./configure --with-mysql</code>.
Jika Anda menginstal PHP dari paket Debian atau Ubuntu, maka Anda juga perlu menginstal modul php5-mysql.',
- 'config-no-fts3' => "'''Peringatan''': SQLite dikompilasi tanpa [http://sqlite.org/fts3.html modul FTS3], fitur pencarian tidak akan tersedia pada konfigurasi ini.",
+ 'config-no-fts3' => "'''Peringatan''': SQLite dikompilasi tanpa [//sqlite.org/fts3.html modul FTS3], fitur pencarian tidak akan tersedia pada konfigurasi ini.",
'config-register-globals' => "'''Peringatan: Opsi <code>[http://php.net/register_globals register_globals]</code> PHP diaktifkan.'''
'''Nonaktifkan kalau bisa.'''
MediaWiki akan bekerja, tetapi server Anda memiliki potensi kerentanan keamanan.",
@@ -6142,13 +7969,13 @@ MediaWiki memerlukan dukungan UTF-8 untuk berfungsi dengan benar.",
'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-xcache' => '[http://xcache.lighttpd.net/ 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-no-cache' => "'''Peringatan:''' Tidak dapat menemukan [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache], atau [http://www.iis.net/download/WinCacheForPhp WinCache]. Pinggahan obyek tidak dinonaktifkan.",
'config-diff3-bad' => 'GNU diff3 tidak ditemukan.',
- 'config-imagemagick' => 'ImageMagick ditemukan: <code>$1</code> .
+ '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.',
@@ -6157,40 +7984,48 @@ 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.",
+Meskipun MediaWiki memeriksa semua berkas unggahan untuk ancaman keamanan, sangat dianjurkan untuk [//www.mediawiki.org/wiki/Manual:Security#Upload_security menutup kerentanan keamanan ini] sebelum mengaktifkan pengunggahan.",
'config-brokenlibxml' => 'Sistem Anda memiliki kombinasi versi PHP dan libxml2 yang memiliki bug dan dapat menyebabkan kerusakan data tersembunyi pada MediaWiki dan aplikasi web lain.
-Mutakhirkan ke PHP 5.2.9 atau yang lebih baru dan libxml2 2.7.3 atau yang lebih baru ([http://bugs.php.net/bug.php?id=45996 arsip bug di PHP]).
+Mutakhirkan ke PHP 5.2.9 atau yang lebih baru dan libxml2 2.7.3 atau yang lebih baru ([//bugs.php.net/bug.php?id=45996 arsip bug di PHP]).
Instalasi dibatalkan.',
- 'config-using531' => '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]).
+ 'config-using531' => 'MediaWiki tidak dapat dijalankan dengan PHP $1 karena bug yang melibatkan parameter referensi untuk <code>__call()</code> .
+Tingkatkan ke PHP 5.3.2 atau lebih baru, atau turunkan ke PHP versi 5.3.0 untuk menyelesaikan hal ini.
Instalasi dibatalkan.',
+ 'config-suhosin-max-value-length' => 'Suhosin terpasang dan membatasi panjang parameter GET sebesar $1 bita. Komponen ResourceLoader MediaWiki akan mengatasi batasan ini, tapi penanganannya akan menurunkan kinerja. Jika memungkinkan, Anda sebaiknya menetapkan nilai suhosin.get.max_value_length menjadi 1024 atau lebih tinggi dalam php.ini dan menyetel $wgResourceLoaderMaxQueryLength dengan nilai yang sama dalam LocalSettings.php.',
'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.
+ '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 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.
+ 'config-db-name-help' => 'Pilih nama yang mengidentifikasikan wiki Anda.
+Nama tersebut tidak boleh mengandung spasi.
Jika Anda menggunakan inang web bersama, penyedia inang Anda dapat memberikan Anda nama basis data khusus untuk digunakan atau mengizinkan Anda membuat basis data melalui panel kontrol.',
'config-db-name-oracle' => 'Skema basis data:',
+ 'config-db-account-oracle-warn' => 'Ada tiga skenario yang didukung untuk instalasi Oracle sebagai basis data pendukung:
+
+Jika Anda ingin membuat akun basis data sebagai bagian dari proses instalasi, silakan masukkan akun dengan peran SYSDBA sebagai akun basis data untuk instalasi dan tentukan kredensial yang diinginkan untuk akun akses web. Jika tidak, Anda dapat membuat akun akses web secara manual dan hanya memberikan akun tersebut (jika memiliki izin yang diperlukan untuk membuat objek skema) atau memasukkan dua akun yang berbeda, satu dengan hak membuat objek dan satu dibatasi untuk akses web.
+
+Skrip untuk membuat akun dengan privilese yang diperlukan dapat ditemukan pada direktori "maintenance/oracle/" instalasi ini. Harap diingat bahwa penggunaan akun terbatas akan menonaktifkan semua kemampuan pemeliharaan dengan akun bawaan.',
'config-db-install-account' => 'Akun pengguna untuk instalasi',
'config-db-username' => 'Nama pengguna basis data:',
'config-db-password' => 'Kata sandi basis data:',
- 'config-db-install-username' => 'Masukkan nama pengguna yang akan digunakan untuk terhubung ke basis data selama proses instalasi.
+ 'config-db-password-empty' => 'Silakan masukkan sandi untuk pengguna basis data baru: $1.
+Meskipun dimungkinkan untuk membuat pengguna tanpa sandi, hal itu tidak aman.',
+ 'config-db-install-username' => 'Masukkan nama pengguna yang akan digunakan untuk terhubung ke basis data selama proses instalasi.
Ini bukan nama pengguna akun MediaWiki, melainkan nama pengguna untuk basis data Anda.',
- 'config-db-install-password' => 'Masukkan sandi yang akan digunakan untuk terhubung ke basis data selama proses instalasi.
+ '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.
+ '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.
@@ -6201,15 +8036,15 @@ Prefiks ini biasanya dibiarkan kosong.',
'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!
+ '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.
+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].",
+Dalam '''modus UTF-8''', MySQL akan tahu apa set karakter data anda dan dapat menyajikan dan mengubahnya denga tepat, namun tidak akan mengizinkan Anda menyimpan karakter di atas [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
'config-mysql-old' => 'MySQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.',
'config-db-port' => 'Porta basis data:',
'config-db-schema' => 'Skema untuk MediaWiki',
- 'config-db-schema-help' => 'Skema di atas biasanya benar.
+ 'config-db-schema-help' => 'Skema ini biasanya berjalan baik.
Ubah hanya jika Anda tahu Anda perlu mengubahnya.',
'config-sqlite-dir' => 'Direktori data SQLite:',
'config-sqlite-dir-help' => "SQLite menyimpan semua data dalam satu berkas.
@@ -6228,19 +8063,22 @@ Pertimbangkan untuk menempatkan basis data di tempat lain, misalnya di <code>/va
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki mendukung sistem basis data berikut:
+ 'config-type-ibm_db2' => 'IBM DB2',
+ '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-postgres' => '* $1 adalah sistem basis data sumber terbuka populer sebagai alternatif untuk MySQL ([http://www.php.net/manual/en/pgsql.installation.php cara mengompilasi PHP dengan dukungan PostgreSQL]). Mungkin ada beberapa bug terbuka dan alternatif ini tidak direkomendasikan untuk dipakai dalam lingkungan produksi.',
'config-support-sqlite' => '* $1 adalah sistem basis data yang ringan yang sangat baik dukungannya. ([http://www.php.net/manual/en/pdo.installation.php cara mengompilasi PHP dengan dukungan SQLite], menggunakan PDO)',
'config-support-oracle' => '* $1 adalah basis data komersial untuka perusahaan. ([http://www.php.net/manual/en/oci8.installation.php cara mengompilasi PHP dengan dukungan OCI8])',
+ 'config-support-ibm_db2' => '* $1 adalah basis data-perusahaan komersial.',
'config-header-mysql' => 'Pengaturan MySQL',
'config-header-postgres' => 'Pengaturan PostgreSQL',
'config-header-sqlite' => 'Pengaturan SQLite',
'config-header-oracle' => 'Pengaturan Oracle',
+ 'config-header-ibm_db2' => 'Pengaturan IBM DB2',
'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"',
@@ -6256,9 +8094,11 @@ Gunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), garis bawah (_), dan tanda hu
Periksa nama inang, pengguna, dan sandi di bawah ini dan coba lagi.',
'config-invalid-schema' => 'Skema MediaWiki "$1" tidak sah.
Gunakan hanya huruf ASCII (a-z, A-Z), angka (0-9), dan garis bawah (_).',
+ 'config-db-sys-create-oracle' => 'Penginstal hanya mendukung penggunaan akun SYSDBA untuk membuat akun baru.',
+ 'config-db-sys-user-exists-oracle' => 'Akun pengguna "$1"sudah ada. SYSDBA hanya dapat digunakan untuk membuat akun baru!',
'config-postgres-old' => 'PostgreSQL $1 atau versi terbaru diperlukan, Anda menggunakan $2.',
- 'config-sqlite-name-help' => 'Pilih nama yang mengidentifikasi wiki Anda.
-Jangan gunakan spasi atau tanda hubung.
+ '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.
@@ -6279,7 +8119,7 @@ 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".
+ '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.',
@@ -6307,7 +8147,7 @@ Anda sekarang dapat [$1 mulai menggunakan wiki Anda].',
'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.
+ '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',
@@ -6322,7 +8162,8 @@ Basis data MyISAM cenderung lebih sering rusak daripada basis data InnoDB.",
'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].",
+Dalam '''modus UTF-8''', MySQL akan tahu apa set karakter data dan dapat menampilkan dan mengubahnya sesuai keperluan, tetapi tidak akan mengizinkan Anda menyimpan karakter di atas [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ 'config-ibm_db2-low-db-pagesize' => "Basis data DB2 Anda tidak memiliki pagesize yang cukup untuk tablespace bawaan. Pagesize harus sama atau lebih dari '''32K'''.",
'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.',
@@ -6331,11 +8172,13 @@ Dalam '''modus UTF-8''', MySQL akan tahu apa set karakter data dan dapat menampi
'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.
+ '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.
+ 'config-ns-invalid' => 'Ruang nama "<nowiki>$1</nowiki>" yang ditentukan tidak sah.
Berikan ruang nama proyek lain.',
+ 'config-ns-conflict' => 'Ruang nama "<nowiki>$1</nowiki>" yang diberikan berkonflik dengan ruang nama bawaan MediaWiki.
+Tentukan ruang nama proyek yang berbeda.',
'config-admin-box' => 'Akun pengurus',
'config-admin-name' => 'Nama Anda:',
'config-admin-password' => 'Kata sandi:',
@@ -6343,20 +8186,20 @@ Berikan ruang nama proyek lain.',
'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.
+ '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-email-help' => 'Masukkan alamat surel untuk memungkinkan Anda menerima surel dari pengguna lain, menyetel ulang sandi, dan mendapat pemberitahuan tentang perubahan atas daftar pantauan Anda. Anda dapat mengosongkan bidang ini.',
'config-admin-error-user' => 'Kesalahan internal saat membuat admin dengan nama "<nowiki>$1</nowiki>".',
'config-admin-error-password' => 'Kesalahan internal saat membuat sandi untuk admin "<nowiki>$1</nowiki>":<pre>$2</pre>',
'config-admin-error-bademail' => 'Anda memasukkan alamat surel yang tidak sah',
'config-subscribe' => 'Berlangganan ke [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce milis pengumuman rilis].',
'config-subscribe-help' => 'Ini adalah milis bervolume rendah yang digunakan untuk pengumuman rilis, termasuk pengumuman keamanan penting.
Anda sebaiknya berlangganan dan memperbarui instalasi MediaWiki saat versi baru keluar.',
- 'config-almost-done' => 'Anda hampir selesai!
+ '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.',
@@ -6365,35 +8208,35 @@ Anda sekarang dapat melewati sisa konfigurasi dan menginstal wiki sekarang.',
'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.
+ '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.
+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-profile-wiki}}''' memungkinkan setiap orang untuk menyunting, bahkan tanpa masuk.
+'''{{int:config-profile-no-anon}}''' menyediakan akuntabilitas tambahan, tetapi dapat mencegah kontributor biasa.
-'''{{int:config-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.
+'''{{int:config-profile-fishbowl}}''' memungkinkan pengguna yang disetujui untuk menyunting, tetapi publik dapat melihat halaman, termasuk riwayatnya.
+'''{{int:config-profile-private}}''' hanya memungkinkan pengguna yang disetujui untuk melihat dan menyunting halaman.
-Konfigurasi hak pengguna yang lebih kompleks tersedia setelah instalasi. Lihat [http://www.mediawiki.org/wiki/Manual:User_rights/id entri manual terkait].",
+Konfigurasi hak pengguna yang lebih kompleks tersedia setelah instalasi. Lihat [//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' => 'Creative Commons Atribusi',
'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-cc-0' => 'Creative Commons Zero (Domain Publik)',
+ 'config-license-gfdl' => 'Lisensi Dokumentasi Bebas GNU 1.3 atau versi terbaru',
'config-license-pd' => 'Domain Umum',
'config-license-cc-choose' => 'Pilih lisensi Creative Commons kustom',
- 'config-license-help' => "Banyak wiki publik 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.
+ 'config-license-help' => "Banyak wiki publik melisensikan semua kontribusi di bawah [http://freedomdefined.org/Definition lisensi bebas].
+Hal ini membantu menciptakan rasa kepemilikan komunitas dan mendorong kontribusi jangka panjang.
+Hal ini umumnya tidak diperlukan untuk wiki pribadi atau perusahaan.
-Jika Anda ingin dapat menggunakan teks dari Wikipedia dan Anda ingin Wikipedia untuk dapat menerima teks yang disalin dari wiki Anda, Anda harus memilih'''Creative Commons Attribution Share Alike'''.
+Jika Anda ingin dapat menggunakan teks dari Wikipedia dan Anda ingin agar Wikipedia dapat menerima teks yang disalin dari wiki Anda, Anda harus memilih'''Creative Commons Attribution Share Alike'''.
-GNU Free Documentation License adalah lisensi sebelumnya dari Wikipedia.
-Lisensi ini masih sah, namun memiliki beberapa fitur yang menyulitkan pemakaian ulang dan interpretasi.",
+Wikipedia sebelumnya menggunakan GNU Free Documentation License.
+Lisensi ini masih sah, namun sulit dipahami.
+Selain itu, sulit untuk menggunakan ulang konten yang dilisensikan di bawah GFDL.",
'config-email-settings' => 'Pengaturan surel',
'config-enable-email' => 'Aktifkan surel keluar',
'config-enable-email-help' => 'Jika Anda ingin mengaktifkan surel, [http://www.php.net/manual/en/mail.configuration.php setelah surel PHP] perlu dikonfigurasi dengan benar.
@@ -6415,7 +8258,7 @@ 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 informasi lebih lanjut, baca [//www.mediawiki.org/wiki/Manual:Security/id manual keamanan].
Untuk mengaktifkan pengunggahan berkas, ubah modus subdirektori <code>images</code> di bawah direktori akar MediaWiki agar server web dapat menulis ke sana.
Kemudian aktifkan opsi ini.',
@@ -6423,13 +8266,13 @@ Kemudian aktifkan opsi ini.',
'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.
+ 'config-logo-help' => 'Kulit bawaan MediaWiki memberikan ruang untuk logo berukuran 135x160 piksel di atas menu bilah samping.
+Unggah gambar dengan ukuran yang sesuai, lalu masukkan URL di sini.
Jika Anda tidak ingin menyertakan logo, biarkan kotak ini kosong.',
'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.
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] adalah fitur yang memungkinkan wiki untuk menggunakan gambar, suara, dan media lain dari [//commons.wikimedia.org/ Wikimedia Commons].
+Untuk melakukannya, MediaWiki memerlukan akses ke Internet.
Untuk informasi lebih lanjut tentang fitur ini, termasuk petunjuk tentang cara untuk mengatur untuk wiki selain Wikimedia Commons, baca [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos manual].',
'config-cc-error' => 'Pemilih lisensi Creative Commons tidak memberikan hasil.
@@ -6445,23 +8288,36 @@ Situs berukuran sedang hingga besar sangat dianjurkan untuk mengaktifkan fitur i
'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).',
+Harus dispesifikasikan per baris berikut porta yang akan digunakan. Contoh:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Anda memilih Memcached sebagai jenis singgahan, tetapi tidak menentukan server apa pun.',
+ 'config-memcache-badip' => 'Anda memasukkan alamat IP yang tidak sah untuk Memcached: $1 .',
+ 'config-memcache-noport' => 'Anda tidak menentukan suatu porta untuk digunakan oleh server Memcached: $1.
+Jika Anda tidak tahu porta tersebut, porta bawaan adalah 11211.',
+ 'config-memcache-badport' => 'Nomor porta Memcached harus antara $1 dan $2.',
'config-extensions' => 'Ekstensi',
'config-extensions-help' => 'Ekstensi yang tercantum di atas terdeteksi di direktori <code>./extensions</code>.
Ekstensi tersebut mungkin memerlukan konfigurasi tambahan, tetapi Anda dapat mengaktifkannya sekarang.',
'config-install-alreadydone' => "'''Peringatan:''' Anda tampaknya telah menginstal MediaWiki dan mencoba untuk menginstalnya lagi.
Lanjutkan ke halaman berikutnya.",
+ 'config-install-begin' => 'Dengan menekan "{{int:config-continue}}", Anda akan memulai instalasi MediaWiki.
+Jika Anda masih ingin membuat perubahan, tekan "{{int:config-back}}".',
'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.
+ 'config-install-database' => 'Menyiapkan basis data',
+ 'config-install-pg-schema-not-exist' => 'Skema PostgreSQL tidak tersedia.',
+ 'config-install-pg-schema-failed' => 'Pembuatan tabel gagal.
Pastikan bahwa pengguna "$1" dapat menulis ke skema "$2".',
'config-install-pg-commit' => 'Melakukan perubahan',
'config-install-pg-plpgsql' => 'Memeriksa bahasa PL / pgSQL',
'config-pg-no-plpgsql' => 'Anda perlu menginstal bahasa PL/pgSQL pada basis data $1',
+ 'config-pg-no-create-privs' => 'Akun yang Anda tetapkan untuk instalasi tidak memiliki hak yang cukup untuk membuat akun.',
'config-install-user' => 'Membuat pengguna basis data',
+ 'config-install-user-alreadyexists' => 'Pengguna "$1" sudah ada',
+ 'config-install-user-create-failed' => 'Pembuatan pengguna "$1" gagal: $2',
'config-install-user-grant-failed' => 'Memberikan izin untuk pengguna "$1" gagal: $2',
'config-install-tables' => 'Membuat tabel',
'config-install-tables-exist' => "'''Peringatan''': Tabel MediaWiki sepertinya sudah ada.
@@ -6471,10 +8327,13 @@ Melompati pembuatan.",
'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-stats' => 'Inisialisasi statistik',
+ 'config-install-keys' => 'Membuat kunci rahasia',
+ 'config-insecure-keys' => "'''Peringatan:''' {{PLURAL:$2|Suatu|Beberapa}} kunci aman ($1) yang dibuat selama instalasi {{PLURAL:$2|tidak|tidak}} benar-benar aman. Pertimbangkan untuk mengubah {{PLURAL:$2|kunci|kunci-kunci}} tersebut secara manual.",
'config-install-sysop' => 'Membuat akun pengguna pengurus',
'config-install-subscribe-fail' => 'Tidak dapat berlangganan mediawiki-announce',
'config-install-mainpage' => 'Membuat halaman utama dengan konten bawaan',
+ 'config-install-extension-tables' => 'Pembuatan tabel untuk ekstensi yang diaktifkan',
'config-install-mainpage-failed' => 'Tidak dapat membuat halaman utama: $1',
'config-install-done' => "'''Selamat!'''
Anda telah berhasil menginstal MediaWiki.
@@ -6482,12 +8341,30 @@ 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.
+Anda perlu mengunduh berkas itu dan meletakkannya di direktori instalasi wiki (direktori yang sama dengan index.php). Pengunduhan akan dimulai secara otomatis.
+
+Jika pengunduhan tidak terjadi, atau jika Anda membatalkannya, Anda dapat mengulangi pengunduhan dengan mengeklik tautan berikut:
+
+$3
+
+'''Catatan''': Jika Anda tidak melakukannya sekarang, berkas konfigurasi yang dihasilkan ini tidak akan tersedia lagi setelah Anda keluar dari proses instalasi tanpa mengunduhnya.
Setelah melakukannya, Anda dapat '''[$2 memasuki wiki Anda]'''.",
'config-download-localsettings' => 'Unduh LocalSettings.php',
'config-help' => 'bantuan',
+ 'mainpagetext' => "'''MediaWiki telah terpasang dengan sukses'''.",
+ 'mainpagedocfooter' => 'Silakan baca [//www.mediawiki.org/wiki/Help:Contents/id Panduan Pengguna] untuk cara penggunaan perangkat lunak wiki ini.
+
+== Memulai penggunaan ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings/id Daftar pengaturan konfigurasi]
+* [//www.mediawiki.org/wiki/Manual:FAQ/id Daftar pertanyaan yang sering diajukan mengenai MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]',
+);
+
+/** Interlingue (Interlingue) */
+$messages['ie'] = array(
+ 'mainpagetext' => "'''Software del wiki installat con successe.'''",
);
/** Igbo (Igbo)
@@ -6496,35 +8373,179 @@ Setelah melakukannya, Anda dapat '''[$2 memasuki wiki Anda]'''.",
$messages['ig'] = array(
'config-admin-password' => 'Okwúngáfè:',
'config-admin-password-confirm' => 'Okwúngáfè mgbe ozor:',
+ 'mainpagetext' => "'''MediaWiki a banyélé nke oma.'''",
+ 'mainpagedocfooter' => "Gbàkpó [//meta.wikimedia.org/wiki/Help:Contents Ǹdù Ọ'bànifé] màkà ụmá màkà Í jí ngwa nsónùsòrò bu wiki.
+
+== I bídó ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Ndétu ndósé ihe]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce wéfù ndétu nke ozi MediaWiki]",
+);
+
+/** Iloko (Ilokano) */
+$messages['ilo'] = array(
+ 'mainpagetext' => "'''Sibaballigi a nainstolar ti MediaWiki.'''",
+);
+
+/** Ido (Ido)
+ * @author Wyvernoid
+ */
+$messages['io'] = array(
+ 'mainpagetext' => "'''MediaWiki instalesis sucese.'''",
+ 'mainpagedocfooter' => "Videz la [//meta.wikimedia.org/wiki/Help:Contents Guidilo por Uzanti] por informo pri uzar la wiki programo.
+
+== Komencar ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listo di ''Configuration setting'']
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki OQQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki nova versioni posto-listo]",
+);
+
+/** Icelandic (Íslenska) */
+$messages['is'] = array(
+ 'mainpagetext' => "'''Uppsetning á MediaWiki heppnaðist.'''",
+ 'mainpagedocfooter' => 'Ráðfærðu þig við [//meta.wikimedia.org/wiki/Help:Contents Notandahandbókina] fyrir frekari upplýsingar um notkun wiki-hugbúnaðarins.
+
+== Fyrir byrjendur ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listi yfir uppsetningarstillingar]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Algengar spurningar MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Póstlisti MediaWiki-útgáfa]',
);
/** Italian (Italiano)
* @author Beta16
+ * @author Karika
*/
$messages['it'] = array(
+ 'config-desc' => 'Il programma di installazione per MediaWiki',
+ 'config-title' => 'Installazione MediaWiki $1',
'config-information' => 'Informazioni',
+ 'config-localsettings-upgrade' => 'È stato rilevato un file <code>LocalSettings.php</code>.
+Per aggiornare questa installazione, si prega di inserire il valore di <code>$wgUpgradeKey</code> nella casella qui sotto.
+Lo potete trovare in LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => 'È stato rilevato un file LocalSettings.php.
+Per aggiornare questa installazione, eseguire update.php',
+ 'config-localsettings-key' => 'Chiave di aggiornamento:',
+ 'config-localsettings-badkey' => 'La chiave che hai fornito non è corretta.',
+ 'config-upgrade-key-missing' => "È stata rilevata un'installazione esistente di MediaWiki.
+Per aggiornare questa installazione, si prega di inserire la seguente riga nella parte inferiore del tuo LocalSettings.php:
+
+$1",
+ 'config-localsettings-incomplete' => 'Il file LocalSettings.php esistente sembra essere incompleto.
+La variabile $1 non è impostata.
+Cambia LocalSettings.php in modo che questa variabile sia impostata e fai clic su "Continua".',
+ 'config-localsettings-connection-error' => 'Si è verificato un errore durante la connessione al database utilizzando le impostazioni specificate in LocalSettings.php o AdminSettings.php. Si prega di correggere queste impostazioni e riprovare.
+
+$1',
+ 'config-session-error' => "Errore nell'avvio della sessione: $1",
+ 'config-session-expired' => 'I dati della sessione sembrano essere scaduti.
+Le sessioni sono configurate per una durata di $1.
+Puoi aumentarla impostando <code>session.gc_maxlifetime</code> nel file php.ini.
+Riavvia il processo di installazione.',
+ 'config-no-session' => 'I dati della sessione sono andati persi!
+Controlla il tuo file php.ini ed assicurati che <code>session.save_path</code> è impostato su una directory appropriata.',
+ 'config-your-language' => 'La tua lingua:',
+ 'config-your-language-help' => 'Seleziona una lingua da utilizzare durante il processo di installazione.',
+ 'config-wiki-language' => 'La lingua del wiki:',
+ 'config-wiki-language-help' => 'Seleziona la lingua che verrà prevalentemente usata nel wiki.',
'config-back' => '← Indietro',
'config-continue' => 'Continua →',
'config-page-language' => 'Lingua',
+ 'config-page-welcome' => 'Benvenuti in MediaWiki!',
+ 'config-page-dbconnect' => 'Connessione al database',
+ 'config-page-upgrade' => "Aggiornamento dell'installazione esistente",
+ 'config-page-dbsettings' => 'Impostazioni del database',
'config-page-name' => 'Nome',
'config-page-options' => 'Opzioni',
'config-page-install' => 'Installa',
'config-page-complete' => 'Completa!',
+ 'config-page-restart' => 'Riavvio installazione',
'config-page-readme' => 'Leggimi',
'config-page-releasenotes' => 'Note di versione',
+ 'config-page-upgradedoc' => 'Aggiornamento',
+ 'config-page-existingwiki' => 'Wiki esistenti',
+ 'config-help-restart' => 'Vuoi cancellare tutti i dati salvati che hai inserito e riavviare il processo di installazione?',
+ 'config-restart' => 'Sì, riavvia',
+ 'config-welcome' => "=== Controllo dell'ambiente ===
+Vengono eseguiti controlli di base per vedere se questo ambiente è adatto per l'installazione di MediaWiki.
+Se hai bisogno di aiuto durante l'installazione, è necessario fornire i risultati di questi controlli.",
+ 'config-env-good' => "L'ambiente è stato controllato.
+È possibile installare MediaWiki.",
+ 'config-env-bad' => "L'ambiente è stato controllato.
+Non è possibile installare MediaWiki.",
+ 'config-env-php' => 'PHP $1 è installato.',
+ 'config-env-php-toolow' => 'PHP $1 è installato.
+Tuttavia, MediaWiki richiede PHP $2 o superiore.',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] è installato',
+ 'config-apc' => '[http://www.php.net/apc APC] è installato',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] è installato',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] è installato',
+ 'config-diff3-bad' => 'GNU diff3 non trovato.',
+ 'config-db-type' => 'Tipo di database:',
+ 'config-pg-test-error' => "Impossibile connettersi al database '''$1''': $2",
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-header-mysql' => 'Impostazioni MySQL',
+ 'config-header-postgres' => 'Impostazioni PostgreSQL',
+ 'config-header-sqlite' => 'Impostazioni SQLite',
+ 'config-header-oracle' => 'Impostazioni Oracle',
+ 'config-header-ibm_db2' => 'Impostazioni IBM DB2',
+ 'config-invalid-db-type' => 'Tipo di database non valido',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-ns-generic' => 'Progetto',
+ 'config-ns-site-name' => 'Stesso nome wiki: $1',
+ 'config-admin-box' => 'Account amministratore',
+ 'config-admin-name' => 'Tuo nome:',
+ 'config-admin-password' => 'Password:',
+ 'config-admin-password-confirm' => 'Ripeti la password:',
+ 'config-admin-help' => 'Inserisci il tuo nome utente scelto qui, ad esempio "Mario Rossi".
+Questo è il nome che userai per accedere al wiki.',
+ 'config-admin-name-blank' => "Inserisci un nome utente per l'amministratore.",
+ 'config-admin-name-invalid' => 'Il nome utente specificato "<nowiki>$1</nowiki>" non è valido.
+Specificare un nome utente diverso.',
+ 'config-admin-password-blank' => "Inserisci una password per l'account di amministratore.",
+ 'config-admin-password-same' => 'La password non deve essere uguale al nome utente.',
+ 'config-admin-password-mismatch' => 'Le password inserite non coincidono tra loro.',
+ 'config-admin-email' => 'Indirizzo e-mail:',
+ 'config-license-cc-by-sa' => 'Creative Commons Attribuzione-Condividi allo stesso modo',
+ 'config-license-cc-by' => 'Creative Commons Attribuzione',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Attribuzione-Non commerciale-Condividi allo stesso modo',
+ 'config-license-cc-0' => 'Creative Commons Zero (pubblico dominio)',
+ 'config-license-gfdl' => 'GNU Free Documentation License 1.3 o versioni successive',
+ 'config-license-pd' => 'Pubblico dominio',
+ 'config-email-settings' => 'Impostazioni e-mail',
+ 'config-install-interwiki-list' => 'Impossibile leggere il file <code>interwiki.list</code>.',
+ 'config-install-stats' => 'Inizializzazione delle statistiche',
+ 'config-install-keys' => 'Generazione delle chiavi segrete',
+ 'config-install-sysop' => "Creazione dell'account utente per l'amministratore",
+ 'config-install-subscribe-fail' => 'Impossibile sottoscrivere mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'cURL non è installato e allow_url_fopen non è disponibile.',
+ 'config-install-mainpage' => 'Creazione della pagina principale con contenuto predefinito',
+ 'config-install-mainpage-failed' => 'Impossibile inserire la pagina principale: $1',
+ 'config-download-localsettings' => 'Scarica LocalSettings.php',
+ 'mainpagetext' => "'''Installazione di MediaWiki completata correttamente.'''",
+ 'mainpagedocfooter' => "Consultare la [//meta.wikimedia.org/wiki/Aiuto:Sommario Guida utente] per maggiori informazioni sull'uso di questo software wiki.
+
+== Per iniziare ==
+I seguenti collegamenti sono in lingua inglese:
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Impostazioni di configurazione]
+* [//www.mediawiki.org/wiki/Manual:FAQ Domande frequenti su MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annunci MediaWiki]",
);
/** Japanese (日本語)
* @author Aphaia
* @author Iwai.masaharu
* @author Mizusumashi
+ * @author Ninomy
* @author Ohgi
* @author Whym
* @author Yanajin66
* @author 青子守歌
*/
$messages['ja'] = array(
- 'config-desc' => 'MediaWikiのためのインストーラー',
+ 'config-desc' => 'MediaWikiのインストーラー',
'config-title' => 'MediaWiki $1のインストール',
'config-information' => '情報',
'config-localsettings-upgrade' => '<code>LocalSettings.php</code>ファイルが検出されました。
@@ -6532,6 +8553,10 @@ $messages['ja'] = array(
LocalSettings.phpの中にそれはあるでしょう。',
'config-localsettings-key' => 'アップグレードキー:',
'config-localsettings-badkey' => '与えられたキーが間違っています',
+ 'config-upgrade-key-missing' => 'MediaWikiの既存インストールを検出しました。
+インストールをアップグレードするために、次の行をLocalSettings.phpの末尾に挿入してください:
+
+$1',
'config-localsettings-incomplete' => '現在のLocalSettings.phpは不完全であるようです。
変数$1が設定されていません。
LocalSettings.phpを変更してこの変数を設定して、『{{int:Config-continue}}』を押してください。',
@@ -6558,16 +8583,16 @@ php.iniを確認し、<code>session.save_path</code>が適切なディレクト
'config-page-install' => 'インストール',
'config-page-complete' => '完了!',
'config-page-restart' => 'インストールを再起動',
- 'config-page-readme' => 'リードミー',
+ 'config-page-readme' => 'お読みください',
'config-page-releasenotes' => 'リリースノート',
'config-page-copying' => 'コピー',
- 'config-page-upgradedoc' => '更新',
+ 'config-page-upgradedoc' => 'アップグレード',
'config-page-existingwiki' => '既存のウィキ',
- 'config-help-restart' => '入力された全て保存データを消去し、インストール作業を再起動しますか?',
+ 'config-help-restart' => '入力された全ての保存データを消去し、インストール作業を再起動しますか?',
'config-restart' => 'はい、再起動します',
'config-welcome' => '=== 環境の確認 ===
-基本的な確認では、この環境がMediaWikiの導入に適しているかを確認します。
-インストール中に必要になったとき、この確認結果を利用して下さい。',
+基本的な確認では、現在の環境がMediaWikiのインストールに適しているかを確認します。
+インストール中に助けが必要になった場合は、この確認結果を提供して下さい。',
'config-copyright' => '=== 著作権および規約 ===
$1
@@ -6577,34 +8602,35 @@ $1
詳しくは、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]
+ 'config-sidebar' => '* [//www.mediawiki.org MediaWikiのホーム]
+* [//www.mediawiki.org/wiki/Help:Contents 利用者向け案内]
+* [//www.mediawiki.org/wiki/Manual:Contents 管理者向け案内]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
----
* <doclink href=Readme>お読みください</doclink>
* <doclink href=ReleaseNotes>リリースノート</doclink>
* <doclink href=Copying>コピー</doclink>
* <doclink href=UpgradeDoc>アップグレード</doclink>',
- 'config-env-good' => '環境は確認されました。
-MediaWikiをインストール出来ます。',
- 'config-env-bad' => '環境が確認されました。
-MediaWikiをインストール出来ません。',
+ 'config-env-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-env-php-toolow' => 'PHP $1 がインストールされています。
+しかし、MediaWikiには PHP $2 以上が必要です。',
+ 'config-unicode-using-utf8' => 'Unicode正規化に、Brion Vibberのutf8_normalize.soを使用。',
+ 'config-unicode-using-intl' => 'Unicode正規化に[http://pecl.php.net/intl intl PECL 拡張機能]を使用。',
+ 'config-unicode-pure-php-warning' => "'''警告''':Unicode正規化の処理に [http://pecl.php.net/intl intl PECL 拡張機能]が使用可能ではなく、処理の遅いピュア PHP の実装を代わりに用いています。
+高トラフィックのサイトを運営する場合は、[//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode正規化に関するページ]をお読み下さい。",
+ 'config-unicode-update-warning' => "'''警告''':インストールされているバージョンのUnicode正規化ラッパーは、[http://site.icu-project.org/ ICUプロジェクト]のライブラリの古いバージョンを使用しています。
+Unicodeを少しでも利用する可能性があるなら、[//www.mediawiki.org/wiki/Unicode_normalization_considerations アップグレード]する必要があります。",
+ 'config-no-db' => '適切なデータベースドライバが見つかりませんでした!PHPにデータベースドライバをインストールする必要があります。
+次の種類のデータベースが使用できます: $1
+
+共有サーバを使用している場合は、サーバの管理者に適切なデータベースドライバのインストールを依頼してください。
+PHPを自分でコンパイルした場合は、たとえば<code>./configure --with-mysql</code>を実行して、データベースクライアントを使用可能に再設定してください。
+DebianまたはUbuntuのパッケージからPHPをインストールした場合は、php5-mysqlモジュールもインストールする必要があります。',
+ 'config-no-fts3' => "'''警告''':SQLiteは[//sqlite.org/fts3.html FTS3]モジュールなしでコンパイルされており、検索機能はこのバックエンドで利用不可能になります。",
'config-register-globals' => "'''警告:PHPの<code>[http://php.net/register_globals register_globals]</code>オプションが有効になっています。'''
'''可能なら無効化してください。'''
MediaWikiは動作しますが、サーバーは、潜在的なセキュリティ脆弱性を露呈します。",
@@ -6633,11 +8659,11 @@ MediaWikiにはUTF-8サポートの関数が必要です。",
'config-memory-bad' => "'''警告:'''PHPの<code>memory_limit</code>は$1です。
これは、非常に遅い可能性があります。
インストールが失敗するかもしれません!",
- 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache]がインストール済み',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ 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-no-cache' => "'''警告:'''[http://eaccelerator.sourceforge.net eAccelerator]、[http://www.php.net/apc APC]、[http://xcache.lighttpd.net/ XCache]あるいは[http://www.iis.net/download/WinCacheForPhp WinCache]のいずれも見つかりませんでした。
オブジェクトのキャッシュは有効化されません。",
'config-diff3-bad' => 'GNU diff3が見つかりません。',
'config-imagemagick' => 'ImageMagickが見つかりました:<code>$1</code>。
@@ -6649,13 +8675,14 @@ MediaWikiにはUTF-8サポートの関数が必要です。",
'config-no-uri' => "'''エラー:'''現在のURIを決定できませんでした。
インストールは中止されました。",
'config-uploads-not-safe' => "'''警告:'''アップロードの既定ディレクトリ<code>$1</code>が、任意のスクリプト実行に関して脆弱性があります。
-MediaWikiはアップロードされたファイルのセキュリティ上の脅威を確認しますが、アップロードを有効化するまえに、[http://www.mediawiki.org/wiki/Manual:Security#Upload_security このセキュリティ上の脆弱性を閉じる]ことが強く推奨されます。",
+MediaWikiはアップロードされたファイルのセキュリティ上の脅威を確認しますが、アップロードを有効化するまえに、[//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でのバグ情報])。
+PHPを5.2.9かそれ以降のバージョンに、libxml2を2.7.3かそれ以降のバージョンにアップグレードしてください([//bugs.php.net/bug.php?id=45996 PHPでのバグ情報])。
インストールを終了します。',
'config-using531' => 'PHP$1は<code>__call()</code>の引数参照に関するバグのため、MediaWikiと互換性がありません。
-PHP5.3.2以降に更新するか、この([http://bugs.php.net/bug.php?id=50394 PHPに提出されたバグ])を修正するためにPHP5.3.0へ戻してください。
+PHP5.3.2以降に更新するか、この([//bugs.php.net/bug.php?id=50394 PHPに提出されたバグ])を修正するためにPHP5.3.0へ戻してください。
インストールは中止されました。',
+ 'config-suhosin-max-value-length' => 'Suhosin がインストールされており、GETパラメータの長さを $1 バイトに制限しています。MediaWiki の ResourceLoader コンポーネントはこの制限を回避しますが、パフォーマンスは低下します。可能な限り、php.ini で suhosin.get.max_value_length を 1024 以上に設定し、同じ値を LocalSettings.php 中で $wgResourceLoaderMaxQueryLength に設定してください。',
'config-db-type' => 'データベースの種類:',
'config-db-host' => 'データベースのホスト:',
'config-db-host-help' => 'データベースサーバーが異なったサーバー上にある場合、ホスト名またはIPアドレスをここに入力してください。
@@ -6675,6 +8702,8 @@ WindowsでMySQLを使用している場合に、「localhost」は、サーバ
'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' => 'インストール作業中にデータベースに接続するための利用者名とパスワードを入力してください。',
@@ -6696,7 +8725,7 @@ WindowsでMySQLを使用している場合に、「localhost」は、サーバ
'''バイナリー系式'''では、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 基本多言語面]の外にある文字を格納できるようにはなりません。",
+[//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' => 'メディアウィキの図式',
@@ -6718,6 +8747,7 @@ WindowsでMySQLを使用している場合に、「localhost」は、サーバ
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'メディアウィキは次のようなデータベースシステムをサポートする:
$1
@@ -6727,12 +8757,15 @@ $1
'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-support-ibm_db2' => '* $1 は商業企業のデータベースです。',
'config-header-mysql' => 'MySQLの設定',
'config-header-postgres' => 'PostgreSQLの設定',
'config-header-sqlite' => 'SQLiteの設定',
'config-header-oracle' => 'Oracleの設定',
+ 'config-header-ibm_db2' => 'IBM DB2の設定',
'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」です。
アスキー文字(a-z, A-Z)、数字(0-9)およびアンダーバー(_)とドット(.)のみを使用してください。',
@@ -6812,14 +8845,14 @@ chmod a+w $3</pre>',
これは、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 基本多言語面]の外にある文字を格納できるようにはなりません。",
+[//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' => 'その他(指定してください)',
'config-ns-other-default' => 'マイウィキ',
'config-project-namespace-help' => "ウィキペディアの例に従えば、多くのウィキは「'''プロジェクトの名前空間'''」において、コンテンツのページとは分離した独自のポリシーページを持つ。
伝統的にはこの接頭辞はウィキのページから派生される。しかし、\"#\" や \":\"のような句切り記号は含んでいない。",
@@ -6844,10 +8877,10 @@ chmod a+w $3</pre>',
'config-subscribe' => '[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce リリース告知のメーリングリスト]を購読する。',
'config-subscribe-help' => 'これは、リリースの告知(重要なセキュリティに関する案内を含む)に使われる、低容量のメーリングリストです。
このメーリングリストを購読して、新しいバージョンが出た場合にMediaWikiを更新してください。',
- 'config-almost-done' => 'あなたはほとんど完璧です!
-設定を残すことをはぶいて、今すぐにウィキをインストールできます。',
+ 'config-almost-done' => 'これでほとんどお終いです!
+残りの設定を飛ばして、今すぐにウィキをインストールできます。',
'config-optional-continue' => '私にもっと質問してください。',
- 'config-optional-skip' => 'すでに飽きてしまった、ウィキをインストールするだけです。',
+ 'config-optional-skip' => 'もう飽きてしまったので、とにかくウィキをインストールしてください。',
'config-profile' => '正しいプロフィールのユーザ:',
'config-profile-wiki' => '伝統的なウィキ',
'config-profile-no-anon' => 'アカウントの作成が必要',
@@ -6865,13 +8898,11 @@ MediaWikiでは、最近の更新を確認し、神経質な、もしくは悪
'''{{int:config-profile-fishbowl}}'''のウィキは、承認された利用者は編集でき、一方、一般の人はページ(とその履歴)の閲覧が可能です。
'''{{int:config-profile-private}}'''は、承認された利用者がページを閲覧可能で、そのグループが編集可能です。
-より複雑な利用者権限の設定は、インストール後に設定可能です。詳細は[http://www.mediawiki.org/wiki/Manual:User_rights 関連するマニュアル]をご覧ください。",
+より複雑な利用者権限の設定は、インストール後に設定可能です。詳細は[//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 フリーライセンス]の元に置かれています。
@@ -6903,7 +8934,7 @@ GNUフリー文書利用許諾契約書はウィキペディアが採用して
'config-upload-settings' => '画像およびファイルのアップロード',
'config-upload-enable' => 'ファイルのアップロードを有効にする',
'config-upload-help' => 'ファイルのアップロードは潜在的にあなたのサーバにセキュリティー上の危険をさらします。
-更なる情報のために、マニュアルの[http://www.mediawiki.org/wiki/Manual:Security security section] を読むことをすすめます。
+更なる情報のために、マニュアルの[//www.mediawiki.org/wiki/Manual:Security security section] を読むことをすすめます。
ファイルのアップロードを可能にするために、メディアウィキのルートディレクトリ下の<code>images</code>サブディレクトリのモードを変更します。そうすることにより、ウェブサーバはそこに書き込みが可能になります。
そして、このオプションを有効にしてください。',
@@ -6916,7 +8947,7 @@ GNUフリー文書利用許諾契約書はウィキペディアが採用して
もし、ロゴを望まないならば、このボックスを空白状態のままにしてください。',
'config-instantcommons' => 'InstantCommons機能を有効にする',
- 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons InstantCommons]は、[http://commons.wikimedia.org/ ウィキメディア・コモンズ]のサイトで見つかった画像や音声、その他のメディアをウィキ上で利用することができるようになる機能です。
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons InstantCommons]は、[//commons.wikimedia.org/ ウィキメディア・コモンズ]のサイトで見つかった画像や音声、その他のメディアをウィキ上で利用することができるようになる機能です。
これを有効化するには、MediaWikiはインターネットに接続できなければなりません。
ウィキメディアコモンズ以外のウィキを同じように設定する方法など、この機能に関する詳細な情報は、[http://mediawiki.org/wiki/Manual:$wgForeignFileRepos マニュアル]をご覧ください。',
@@ -6965,7 +8996,7 @@ GNUフリー文書利用許諾契約書はウィキペディアが採用して
'config-install-sysop' => '管理者のユーザーアカウントを作成する',
'config-install-mainpage' => '既定の接続でメインページを作成',
'config-install-mainpage-failed' => 'メインページを挿入できませんでした:$1',
- 'config-install-done' => "'''おめでとうございます!'''
+ 'config-install-done' => "'''おめでとうございます!'''
MediaWikiのインストールに成功しました。
<code>LocalSettings.php</code>ファイルが生成されました。
@@ -6982,9 +9013,138 @@ $3
それを完了すれば、'''[$2 ウィキに入る]'''ことができます。",
'config-download-localsettings' => 'LocalSettings.phpをダウンロード',
'config-help' => 'ヘルプ',
+ 'mainpagetext' => "'''MediaWikiが正常にインストールされました。'''",
+ 'mainpagedocfooter' => 'ウィキソフトウェアの使い方に関する情報は[//meta.wikimedia.org/wiki/Help:Contents 利用者案内]を参照してください。
+
+== はじめましょう ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings/ja 設定の一覧]
+* [//www.mediawiki.org/wiki/Manual:FAQ/ja MediaWiki よくある質問と回答]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWikiリリース情報メーリングリスト]',
+);
+
+/** Jamaican Creole English (Patois)
+ * @author Yocahuna
+ */
+$messages['jam'] = array(
+ 'mainpagetext' => "'''MediaWiki don instaal soksesful.'''",
+ 'mainpagedocfooter' => "Kansolt di [//meta.wikimedia.org/wiki/Help:Contents User's Guide] fi infamieshan ou fi yuuz di wiki saafwier.
+
+== Taatop ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Jutish (Jysk)
+ * @author Huslåke
+ */
+$messages['jut'] = array(
+ 'mainpagetext' => "'''MediaWiki er nu installeret.'''",
+ 'mainpagedocfooter' => "Se vores engelskspråĝede [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentåsje tilpasnenge'm åf æ brugergrænseflade] og [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide æ brugervejlednenge] før åplysnenger åpsætnenge'm og anvendelse.",
+);
+
+/** Javanese (Basa Jawa) */
+$messages['jv'] = array(
+ 'mainpagetext' => "'''Prangkat empuk wiki wis suksès dipasang.'''",
+ 'mainpagedocfooter' => "Mangga maca [//meta.wikimedia.org/wiki/Help:Contents User's Guide] kanggo katrangan luwih langkung prakara panggunan prangkat empuk wiki
+== Miwiti panggunan ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Daftar pangaturan préférènsi]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]",
+);
+
+/** Georgian (ქართული) */
+$messages['ka'] = array(
+ 'mainpagetext' => "'''მედიავიკი წარმატებით ჩაიტვირთა.'''",
+ 'mainpagedocfooter' => 'ვიკი პროგრამის გამოყენების ინფორმაციისთვის იხილეთ [//meta.wikimedia.org/wiki/Help:Contents მომხმარებლის მეგზური].
+
+== დაწყება ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings კონფიგურაციის მაჩვენებლების სია]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce მედიავიკის გამოცემის დაგზავნის სია]',
+);
+
+/** Kara-Kalpak (Qaraqalpaqsha) */
+$messages['kaa'] = array(
+ 'mainpagetext' => "'''MediaWiki tabıslı ornatıldı.'''",
+ 'mainpagedocfooter' => "Wiki bag'darlamasın qollanıw haqqındag'i mag'lıwmat usın [//meta.wikimedia.org/wiki/Help:Contents Paydalanıwshılar qollanbasınan] ken'es alın'.
+
+== Baslaw ushın ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfiguratsiya sazlaw dizimi]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWikidin' Ko'p Soralatug'ın Sorawları]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki haqqında xat tarqatıw dizimi]",
+);
+
+/** Адыгэбзэ (Адыгэбзэ)
+ * @author Bogups
+ */
+$messages['kbd-cyrl'] = array(
+ 'mainpagetext' => "'''«MediaWiki» узыншу хэгъува.'''",
+ 'mainpagedocfooter' => 'Мы виким и лэжьыгъэ хъыбархэр здэбгъуэтыфынур [//meta.wikimedia.org/wiki/%D0%9F%D0%BE%D0%BC%D0%BE%D1%89%D1%8C:%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5 дэӀэпыкъуэгъу тхылъым].
+
+
+== Къыщхьэпэгъуэ хъуфынухэр ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Зэгъэзэхуэгъуэ гуэрэхэм я тхылъ];
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-м упщӀэ нахъыбу ятхэмрэ я жэуапхэмрэ];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-м и версиэ щӀэуэ къэжахэм я къэӀохугъуэ].',
+);
+
+/** Khowar (کھوار)
+ * @author Rachitrali
+ */
+$messages['khw'] = array(
+ 'mainpagetext' => "\"<big>'''میڈیاوکیو کامیابیو سورا چالو کورونو بیتی شیر۔.'''</big>\"",
+);
+
+/** Kirmanjki (Kırmancki)
+ * @author Mirzali
+ */
+$messages['kiu'] = array(
+ 'mainpagetext' => "'''MediaWiki fist ra ser, vıraziya.'''",
+ 'mainpagedocfooter' => "Serba melumatê gurenaena ''wiki software''i [//meta.wikimedia.org/wiki/Help:Contents İdarê karberi] de mıracaet ke.
+
+== Gamê verêni ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista ayarunê vırastene]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki de ÇZP]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki ra lista serbest-daena postey]",
+);
+
+/** Kazakh (Arabic script) (‫قازاقشا (تٴوتە)‬) */
+$messages['kk-arab'] = array(
+ 'mainpagetext' => "'''مەدىياۋىيكىي بۋماسى ٴساتتى ورناتىلدى.'''",
+ 'mainpagedocfooter' => 'ۋىيكىي باعدارلامالىق جاساقتاماسىن قالاي قولداناتىن اقپاراتى ٴۇشىن [//meta.wikimedia.org/wiki/Help:Contents پايدالانۋشىلىق نۇسقاۋلارىنان] كەڭەس الىڭىز.
+
+== باستاۋ ٴۇشىن ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings باپتالىم قالاۋلارىنىڭ ٴتىزىمى]
+* [//www.mediawiki.org/wiki/Manual:FAQ مەدىياۋىيكىيدىڭ جىيى قويىلعان ساۋالدارى]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce مەدىياۋىيكىي شىعۋ تۋرالى حات تاراتۋ ٴتىزىمى]',
+);
+
+/** Kazakh (Cyrillic script) (‪Қазақша (кирил)‬) */
+$messages['kk-cyrl'] = array(
+ 'mainpagetext' => "'''МедиаУики бумасы сәтті орнатылды.'''",
+ 'mainpagedocfooter' => 'Уики бағдарламалық жасақтамасын қалай қолданатын ақпараты үшін [//meta.wikimedia.org/wiki/Help:Contents Пайдаланушылық нұсқауларынан] кеңес алыңыз.
+
+== Бастау үшін ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Бапталым қалауларының тізімі]
+* [//www.mediawiki.org/wiki/Manual:FAQ МедиаУикидің Жиы Қойылған Сауалдары]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce МедиаУики шығу туралы хат тарату тізімі]',
+);
+
+/** Kazakh (Latin script) (‪Qazaqşa (latın)‬) */
+$messages['kk-latn'] = array(
+ 'mainpagetext' => "'''MedïaWïkï bwması sätti ornatıldı.'''",
+ 'mainpagedocfooter' => 'Wïkï bağdarlamalıq jasaqtamasın qalaý qoldanatın aqparatı üşin [//meta.wikimedia.org/wiki/Help:Contents Paýdalanwşılıq nusqawlarınan] keñes alıñız.
+
+== Bastaw üşin ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Baptalım qalawlarınıñ tizimi]
+* [//www.mediawiki.org/wiki/Manual:FAQ MedïaWïkïdiñ Jïı Qoýılğan Sawaldarı]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MedïaWïkï şığw twralı xat taratw tizimi]',
);
/** Khmer (ភាសាខ្មែរ)
+ * @author Thearith
* @author គីមស៊្រុន
*/
$messages['km'] = array(
@@ -7003,6 +9163,51 @@ $messages['km'] = array(
'config-page-complete' => 'បញ្ចប់!',
'config-page-restart' => 'តំលើងឡើងវិញ',
'config-help' => 'ជំនួយ',
+ 'mainpagetext' => "'''មេឌាវិគីត្រូវបានដំឡើងសំរេចហើយ​។'''",
+ 'mainpagedocfooter' => 'សូមពិនិត្យមើល [//meta.wikimedia.org/wiki/ជំនួយ​៖ ខ្លឹមសារ​ណែនាំ​ប្រើប្រាស់]សម្រាប់​ព័ត៌មាន​​បន្ថែមចំពោះ​ការប្រើប្រាស់ ផ្នែកទន់វិគី​។
+
+== ចាប់ផ្ដើមជាមួយមេឌាវិគី ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings បញ្ជីកំណត់ទម្រង់]
+* [//www.mediawiki.org/wiki/Manual:FAQ/km សំណួរញឹកញាប់​មេឌាវិគី]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce បញ្ជី​ពិភាក្សា​ការផ្សព្វផ្សាយ​របស់​មេឌាវិគី]',
+);
+
+/** Kannada (ಕನ್ನಡ) */
+$messages['kn'] = array(
+ 'mainpagetext' => "'''ವಿಕಿ ತಂತ್ರಾಂಶವನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಅನುಸ್ಥಾಪಿಸಲಾಯಿತು.'''",
+ 'mainpagedocfooter' => 'ವಿಕಿ ತಂತ್ರಾಂಶವನ್ನು ಬಳಸುವ ಬಗ್ಗೆ ಮಾಹಿತಿಗೆ [//meta.wikimedia.org/wiki/Help:Contents ಬಳಕೆದಾರರಿಗೆ ನಿರ್ದೇಶನ ಪುಟ] ನೋಡಿ.
+
+== ಪ್ರಾರಂಭಿಸುವುದು ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ ಮೀಡಿಯವಿಕಿ FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]',
+);
+
+/** Korean (한국어) */
+$messages['ko'] = array(
+ 'mainpagetext' => "'''미디어위키가 성공적으로 설치되었습니다.'''",
+ 'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents 이곳]에서 위키 프로그램에 대한 정보를 얻을 수 있습니다.
+
+== 시작하기 ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings 설정하기]
+* [//www.mediawiki.org/wiki/Manual:FAQ 미디어위키 FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 미디어위키 발표 메일링 리스트]',
+);
+
+/** Karachay-Balkar (Къарачай-Малкъар)
+ * @author Iltever
+ */
+$messages['krc'] = array(
+ 'mainpagetext' => "'''«MediaWiki» тыйыншлы салынды.'''",
+ 'mainpagedocfooter' => "Бу вики бла къалай ишлерге ангылатхан информацияны [//meta.wikimedia.org/wiki/Help:Contents_User's_Guide къошулуучугъа юретиуде] табаргъа боллукъду.
+
+== Файдалы ресурсла ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings тюрлендириулени списогу (ингил.)];
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-ни юсюнден кёб берилген соруула];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-ни джангы версиясыны чыкъгъанын билдириу письмола].",
);
/** Colognian (Ripoarisch)
@@ -7075,10 +9280,10 @@ Dat Projramm weed wigger jejovve met dä Hoffnung, dat et jät nöz, ävver '''o
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]
+ 'config-sidebar' => '* [//www.mediawiki.org MediaWiki sing Hompäjdsch]
+* [//www.mediawiki.org/wiki/Help:Contents Handbooch för Aanwender]
+* [//www.mediawiki.org/wiki/Manual:Contents Handbooch för Administratore un Wiki_Köbesse]
+* [//www.mediawiki.org/wiki/Manual:FAQ Öff jeshtallte Froore met Antwoote]
----
* <doclink href=Readme>Liß Mesch! (<i lang="en">Read me</i>)</doclink>
* <doclink href=ReleaseNotes><i lang="en">Release notes</i> Övver heh di Projrammversion</doclink>
@@ -7094,17 +9299,17 @@ Do kanns MediaWiki nit opsäze.',
'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.',
+För jruuße Wikis met vill Metmaachere doht Üsch die Sigg övver et [//www.mediawiki.org/wiki/Unicode_normalization_considerations <i lang="en">Unicode</i>-Nommaliseere] (es op Änglesch) aanloore.',
'config-unicode-update-warning' => "'''Opjepaß:''' Dat Projramm för der <i lang=\"en\">Unicode</i> zo normaliseere boud em Momang op en ählter Version vun dä Bibliothek vum [http://site.icu-project.org/ ICU-Projäk] op.
-Doht di [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.
+Doht di [//www.mediawiki.org/wiki/Unicode_normalization_considerations op der neuste Shtand bränge], wann auf dat Wiki em Äänz <i lang=\"en\">Unicode</i> bruche sull.",
+ 'config-no-db' => 'Mer kunnte kei zopaß Daatebangk-Driiverprojamm fenge.
+Mer bruche e Daatebangk-Driiverprojamm för PHP. Dat moß enjeresht wääde.
Mer künne met heh dä Daatebangke ömjonn: $1.
Wann De nit om eijene Rääshner bes, moß De Dinge <i lang="en">provider</i> bedde, dat hä Der ene zopaß Driiver enresht.
-Wann de PHP sellver övversaz häs, donn 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 sellver övversaz häs, donn e Zohjangsprjramm för en Daatebangk enbenge, för e Beishpell met: <code lang="en">./configure --with-mysql</code> op ene <i lang="en">command shell</i>.
Wann De PHP uss enem <i lang="en">Debian</i> udder <i lang="en">Ubuntu</i> Pakätt enjeresht häs, moß De dann och noch et <code lang="en">php5-mysql</code> op Dinge Räschner bränge.',
- 'config-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-no-fts3' => "'''Opjepaß:''' De Projramme vum <i lang=\"en\">SQLite</i> sin der ohne et [//sqlite.org/fts3.html FTS3-Modul] övversaz, dröm wääde de Funxjohne för et Söhke fähle.",
'config-register-globals' => "'''Opjepaß:''' dem PHP singe Schallder <code lang=\"en\">[http://php.net/register_globals register_globals]</code> es enjeschalldt.
'''Donn dä ußmaache, wann De kann.'''
MediaWiki löp och esu, dä künnt ävver Sesherheitslöcke opmaache, di mer noch nit jefonge un eruß jemaat hät.",
@@ -7136,12 +9341,16 @@ MediaWiki bruch dä UTF-8-Krohm ävver, öm ohne Fähler loufe ze künne.",
'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-xcache' => 'Dä <code lang="en">[http://xcache.lighttpd.net/ XCache]</code> es ennjeresht.',
'config-apc' => 'Dä <code lang="en">[http://www.php.net/apc APC]</code> es ennjeresht.',
'config-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.
+ '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://xcache.lighttpd.net/ XCache]</code> un dä <code lang="en">[http://www.iis.net/download/WinCacheForPhp WinCache]</code> nit fenge.
Et <i lang="en">object caching</i> es nit müjjelesh un ußjeschalldt.',
+ 'config-mod-security' => "'''Opjepaß''': Dinge Webßööver hät <code lang=\"en\">[http://modsecurity.org/ mod_security]</code> enjeschalldt. If misconfigured, it can cause problems for MediaWiki or other software that allows users to post arbitrary content.
+Refer to <code lang=\"en\">[http://modsecurity.org/documentation/ mod_security documentation]</code> udder contact your host's support if you encounter zohfälleje Fähler.
+
+",
'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ß.',
@@ -7151,20 +9360,27 @@ Et Ömrääschne en Minni-Beldsche weed müjjelesch sin, wann De et Belder Huhla
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].
+ 'config-no-cli-uri' => "'''Opjepaß''': <code lang=\"en\">--scriptpath</code> es nit aanjejovve, mer nämme der Schtandatt: <code>\$1</code>.",
+ 'config-using-server' => 'Mer nämmen dem ẞööver singe Name: „<nowiki>$1</nowiki>“.',
+ 'config-using-uri' => 'Mer nämmen dem ẞööver singe <i lang="en">URL</i>: „<nowiki>$1$2</nowiki>“.',
+ 'config-uploads-not-safe' => "'''Opjepaß:''' Uß däm jewöhnlijje Verzeichnes för de huhjelaade Datteie, dat es <code>$1</code>, künnte öhnzwällsche Skrepte un Projramme ußjeföhrt wääde. Och wann MediaWiki de huhjelaade Datteie prööf, dat kein bekannte Risike dren sin, sullt mer doch dat [//www.mediawiki.org/wiki/Manual:Security#Upload_security Sesherheitsloch] zoh maache, ih dat mer et Dattei Huhlaade zohlöht.",
+ 'config-no-cli-uploads-check' => "'''Opjepaß''': <code>\$1</code> es dat Schtandatt-Verzeijschneß för et Datteije-Huhlaade. Beim Opsäze met <abbr lang=\"en\" title=\"Call Level Interface\">CLI</abbr> donn mer ävver nit övverpröhve, dat dat jeschöz es dojääje, dat Skrepte vun doh loufe künne, di mer nit loufe han well.",
+ 'config-brokenlibxml' => 'Op Dingem Rääschner loufe Versione vun PHP un <code lang="en">libxml2</code> zosamme, di ävver nit zosamme paßße, un de Daate em MediaWiki un ander Web_Aanwändunge [//bugs.php.net/bug.php?id=45996 bug kapott maache].
Jangk op PHP 5.2.9 udder dohnoh un op <code lang="en">libxml2</code> 2.7.3 udder dohnoh.
Heh jeihd et nit wigger.',
- 'config-using531' => 'MediaWiki läuf nit met PHP $1 zosamme wääje enem [http://bugs.php.net/bug.php?id=50394 Fähler em Zosammehang met Parrameetere för <code lang="en">__call()</code>].
+ 'config-using531' => 'MediaWiki läuf nit met PHP $1 zosamme wääje enem [//bugs.php.net/bug.php?id=50394 Fähler em Zosammehang met Parrameetere för <code lang="en">__call()</code>].
Jangk op de Version 5.3.2 vum <i lang="en">PHP</i> ov dohnoh, udder op de Version 5.3.0 udder dovöör, öm dat Problem ze ömjonn.
Heh jeiht et nit wigger.',
+ 'config-suhosin-max-value-length' => '<i lang="en">Suhosin</i> es enschtalleet. Dröm kann ene <code lang="en">GET</code>-Parrameeter nit övver {{PLURAL:$1|ei Byte|$q Bytes|noll Byte}} lang wääde. En MediaWiki singe <i lang="en"ResouceLoader</i> kütt doh zwa drömeröm, ävver dat brems. Wann müjelesch, doht <code lang="en">suhosin.get.max_value_length</code> en dä Dattei <code lang="en">php.ini</code> op 1024 Bytes udder drövver enschtälle. un dann moß <code lang="en">$wgResourceLoaderMaxQueryLength</code> en dä Dattei <code lang="en">LocalSettings.php</code> op däsälve Wäät jesaz wääde.',
'config-db-type' => 'De Zoot Daatebangk:',
'config-db-host' => 'Dä Name vun däm Rääschner met dä Daatebangk:',
'config-db-host-help' => 'Wann Dinge ẞööver för de Daatebangk ob enem andere Rääschner es, donn heh dämm singe Name udder <i lang="en">IP</i>-Addräß enjävve.
Wann De ob enem Meetẞööver beß, weet Der Dinge Provaider odder däm sing Dokemäntazjuhn saare, wat De endraare moß.
-Wann De ob enem ẞööver onger <i lang="en">Windows</i> am enshtalleere bes un en <i lang="en">MySQL</i>-Daatebangk häs, künnd_et sin, dat „<code lang="en">localhost</code>“ nit douch för der Name vum ẞööver. Wann dad-esu es, versöhg et ens met „<code lang="en">127.0.0.1</code>“ als <i lang="en">IP</i>-Addräß vum eije Rääschner.',
+Wann De ob enem ẞööver onger <i lang="en">Windows</i> am enshtalleere bes un en <i lang="en">MySQL</i>-Daatebangk häs, künnd_et sin, dat „<code lang="en">localhost</code>“ nit douch för der Name vum ẞööver. Wann dad-esu es, versöhg et ens met „<code lang="en">127.0.0.1</code>“ als <i lang="en">IP</i>-Addräß vum eije Rääschner.
+
+Wann De ene <i lang="en">PostgreSQL</i>-ẞööver häs, donn dat Fäld läddesch lohße, öm en Verbendung övver e <i lang="en">Unix socket</i> opzemaache.',
'config-db-host-oracle' => 'Dä Daatebangk ier <i lang="en" title="Transparent Network Substrate">TNS</i>:',
'config-db-host-oracle-help' => 'Donn ene jöltije [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm „<i lang="en">Local Connect</i>“-Name] aanjävve. De Dattei „<code lang="en">tnsnames.ora</code>“ moß för heh dat Projamm seschbaa un ze Lässe sin.<br />Wann heh de Projamm_Biblijoteeke für de Aanwänderprojramme för de Version 10g udder neuer enjesaz wääde, kam_mer och et [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm „<i lang="en">Easy Connect</i>“] jenumme wääde för der Name ze verjävve.',
'config-db-wiki-settings' => 'De Daate vum Wiki',
@@ -7213,12 +9429,13 @@ Beim Shpeishere em '''binäre Fomaat''' deiht MediaWiki de Täxte, di em UTF-8 F
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.",
+allerdengs künne kein Zeishe ußerhalv vum [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke jrundlääje Knubbel för vill Shprooche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeshpeishert wääde.",
'config-mysql-old' => 'Mer bruche <i lang="en">MySQL</i> $1 udder neuer. Em Momang es <i lang="en">MySQL</i> $2 aam Loufe.',
'config-db-port' => 'De Pooz-Nommer (<i lang="en">port</i>) för de Daatebangk:',
'config-db-schema' => 'Et Schema en de Datebangk för MediaWiki:',
'config-db-schema-help' => 'För jewöhnlesch es dat Schema en Odenong.
Donn bloß jät draan ändere, wann De sescher weiß, dat dat nüüdesch es.',
+ 'config-pg-test-error' => "Mer krijje kein Verbendung zor Daatebank '''$1''': $2",
'config-sqlite-dir' => 'Dem <i lang="en">SQLite</i> sing Daateverzeishnes:',
'config-sqlite-dir-help' => '<i lang="en">SQLite</i> hät all sing Daate zosamme en en einzel Dattei.
@@ -7236,6 +9453,7 @@ Donn Ding Daatebangk et beß janz woh anders hen, noh <code lang="en">/var/lib/m
'config-type-postgres' => '<i lang="en">PostgreSQL</i>',
'config-type-sqlite' => '<i lang="en">SQLite</i>',
'config-type-oracle' => '<i lang="en">Oracle</i>',
+ 'config-type-ibm_db2' => 'Dä <i lang="en">IBM</i> ier <i lang="en">DB2</i>',
'config-support-info' => 'MediaWiki kann met heh dä Daatebangk_Süßteeme zosamme jonn:
$1
@@ -7245,10 +9463,12 @@ Wann dat Daatebangk_Süßteem, wat De nämme wells, onge nit dobei es, dann donn
'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-support-ibm_db2' => '* $1 es en Datebengk för et Jeschäff un fö Ongernehme.',
'config-header-mysql' => 'De Enshtällunge för de <i lang="en">MySQL</i> Daatebangk',
'config-header-postgres' => 'De Enshtällunge för de <i lang="en">PostgreSQL</i> Daatebangk',
'config-header-sqlite' => 'De Enshtällunge för de <i lang="en">SQLite</i> Daatebangk',
'config-header-oracle' => 'De Enshtällunge för de <i lang="en">Oracle</i> Daatebangk',
+ 'config-header-ibm_db2' => 'De Enshtällunge för de <i lang="en">IBM</i> ier <i lang="en">DB2</i>',
'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.',
@@ -7309,7 +9529,7 @@ 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-account' => 'Dä Zohjang zor Daatebangk för et Wiki',
'config-db-web-help' => 'Donn ene Name un e Paßwoot för der Zohjang zor Daatebangk för et Wiki em nomaale Bedrief aanjävve.',
'config-db-web-account-same' => 'Donn dersällve Zohjang nämme, wi heh beim Opsäze.',
'config-db-web-create' => 'Donn dä Zohjang aanlääje, wann dä noch nit doh es.',
@@ -7318,6 +9538,14 @@ Dä aanjejovve Zohjang för der Nomaalbedrief moß dröm schunn enjersht sen!',
'config-mysql-engine' => 'De Zoot udder et Fommaat vun de Tabälle:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => '\'\'\'Opjepaß:\'\'\' <i lang="en">MyISAM</i> es als Speicher för <i lang="en">MySQL</i> nit joot för et Zosammeschpell met MediaWiki nit zo ämfähle:
+* sie unterstützt aufgrund von Tabellensperrungen kaum die nebenläufige Ausführung von Aktionen
+* Dat Fomaat es anfällesch för Probleme met de Daate.
+* Et weed vun MediaWiki nit immer passend ongerschtöz.
+
+Wann Ding <i lang="en">MySQL</i> et schpeischere en <i lang="en">InnoDB</i>-Datteije nit ongerschtöz, wird deren Verwendung eindringlich empfohlen.
+Sofern sie sie nicht unterstützt, sollte eine entsprechende Aktualisierung nunmehr Erwägung gezogen werden op dämm ẞööver.
+',
'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.
@@ -7329,7 +9557,8 @@ MyISAM-Daatebangke han em Schnett mieh Fähler un jon flöcker kappott, wi InnoD
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.",
+allerdengs künne kein Zeishe ußerhalv vum [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke jrundlääje Knubbel för vill Shprooche (<i lang=\"en\">Basic Multilingual Plane — BMP</i>)] afjeshpeishert wääde.",
+ 'config-ibm_db2-low-db-pagesize' => "De <i lang=\"en\">DB2</i> Daatebangk heh hät ene standattmääßeje Plaz för Tabälle met zoh klein Sigge. Dä Plaz en de Sigge moß '''32K''' udder mieh sin.",
'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.',
@@ -7366,9 +9595,10 @@ De kanns dat Fäld ävver och läddesch lohße.',
'config-subscribe' => 'Donn de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce <i lang="en">e-mail</i>-Leß met de Aanköndijunge vum MediaWiki] abonnere.',
'config-subscribe-help' => 'Do kumme bloß winnish Meddeilunge un di jonn övver neu Versiohne vom MediaWiki un weeshtejje Saache vun däm sing Sesherheit.
Do sullts se abbonneere, un Ding MediWiki_Projramme op der neue Shtand bränge, wann neu Version eruß kumme.',
+ 'config-subscribe-noemail' => 'Do has versöhk, der ohne en Addräß för Ding <i lang="en">e-mail<i> aanzejävve, de Aanköndijonge för Aanköndijunge för neue Versione ze abboneere. Jivv en Addräß aan, wann De di Aanköndijonge hann wells.',
'config-almost-done' => 'Do bes beinah dorsh!
Do künnts jez der Räß vun de einzel Enshtellunge övverjonn, un et Wiki tiräktemang fäädesch opsäze.',
- 'config-optional-continue' => 'Wells De noch mieh Frore jeshtallt krijje un noch mieh Enshtällunge maache?',
+ 'config-optional-continue' => 'De wells noch mieh Frore jeshtallt krijje un noch mieh Enshtällunge maache?',
'config-optional-skip' => 'Nä, lohß dä Ömshtand, donn eifarr_et Wiki opsäze.',
'config-profile' => 'Enshtällunge för de Metmaacher ier Rääschte:',
'config-profile-wiki' => 'E tradizjonäll offe Wiki',
@@ -7389,23 +9619,22 @@ Esu häß De de Wahl:
'''{{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.",
+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 [//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-cc-0' => '<i lang="en">Creative Commons</i> „Noll“ (jemeinfrei udder Pablic Domain)',
+ 'config-license-gfdl' => 'De <i lang="en">GNU</i>-Lizänz för frei Dokemäntazjuhne, Version 1.3 udder en späädere',
'config-license-pd' => 'Allmende (jemeinfrei, <i lang="en">public domain</i>)',
'config-license-cc-choose' => 'En <i lang="en">Creative Commons</i> Lizänz, sellver ußjesöhk:',
'config-license-help' => "Ättlijje öffentleje Wikis donn iehr Beidrääsh onger en [http://freedomdefined.org/Definition frei Lizänz] shtelle.
Dat hellef, e Jeföhl vun Jemeinsamkeid opzeboue, un op lange Seesh emmer wider Beidrääsch ze krijje.
Dat es nit onbedengk nüüdesh för e Jeschäffs- udder Privaat_Wiki.
-Wä Stöcke uß de Wikipedia bruche well, un han well, dat de Wikipedia uss_em eije Wiki jät övvernämme kann, sullt 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.
+Wä Stöcke uß de Wikipedia bruche well, un han well, dat de Wikipedia uss_em eije Wiki jät övvernämme kann, sullt „'''<i lang=\"en\">Creative Commons</i>, dem Schriever singe Name moß jenannt wääde, un Wiggerjävve zoh dersellve Bedengunge es zohjelohße'''“ ußwähle.
-De su jenannte '''<i lang=\"en\">GNU Free Documentation License</i>''' (de freije Lizänz för Dokemäntazjuhne vun dä GNU) sen de ahle Lizänzbedenonge vun de Wikipedia. Se es emmer noch in Odenong un jöltesch, ävver se hädd e paa Eijeschaffte, die et Wiggerjävve, et widder Verwände un et Ußlääje schwieeresch maache.",
+De su jenannte '''<i lang=\"en\">GNU Free Documentation License</i>''' (de freije Lizänz för Dokemäntazjuhne vun dä GNU) sen de ahle Lizänzbedenonge vun de Wikipedia. Se es emmer noch in Odenong un jöltesch, ävver se es schwer ze vershtonn un et Wiggerjävve un widder Verwände es manshmool schwieeresch domet.",
'config-email-settings' => 'Enschtellunge för de <i lang="en">e-mail</i>',
'config-enable-email' => 'De <i lang="en">e-mail</i> noh druße zohlohße',
'config-enable-email-help' => 'Sulle <i lang="en">e-mails</i> zohjelohße sin, moß mer, domet et noher flupp, de [http://www.php.net/manual/en/mail.configuration.php Enschtellunge em PHP för de <i lang="en">e-mails</i>] zopaß jemaat han.
@@ -7427,7 +9656,7 @@ Vill ẞöövere för de <i lang="en">e-mail</i> welle winnischßdens ene jölti
'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.
+Mieh doh drövver kam_mer em [//www.mediawiki.org/wiki/Manual:Security Kapitel övver de Sescherheit] em Handbooch lässe.
Öm et Huhlaade zohzelohße donn de Rääschde för der Zohjreff op dat Ongerverzeischneß <code lang="en">images</code> em MediaWiki singem Houpverzeischneß esu enshtälle, dat et Webßööverprojramm doh Datteije un Verzeischneße eren schrieve kann.
Donoh donn heh di Saach zohlohße.',
@@ -7440,7 +9669,7 @@ 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.
+ 'config-instantcommons-help' => '<i lang="en">[//www.mediawiki.org/wiki/InstantCommons InstantCommons]</i> es en Eijeschaff, di et för Wikis müjjelesch määt, Belder, Tondatteie un ander Meedijedatteie enzebenge, di op dä Webßait vun de <i lang="en">[//commons.wikimedia.org/ Wikimedia Commons]</i> ongerjebraat sin. Öm dat noze ze künne, moß dä ẞööver vum MediaWiki en Verbendung nohm Internet opnämme künne.
Mieh Aanjaabe doh drövver un en Aanleidung, wi mer och ander Wikis ußer de <i lang="en">Wikimedia Commons</i> doför enreeschte kann, fengk mer em [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.
@@ -7449,7 +9678,7 @@ Donn de Lizänz sellver beshtemme.',
'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,
+ '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>)',
@@ -7477,6 +9706,7 @@ Wann De noch Änderonge maache wells, dann kleck op „{{int:config-back}}“.',
'config-install-step-failed' => 'donävve jejange',
'config-install-extensions' => 'Zohsazprojramme enjeschloße',
'config-install-database' => 'Ben de Daatebangk aam ennreeschte.',
+ 'config-install-schema' => 'Dat Schema en dä Daatebank weed aanjelaat.',
'config-install-pg-schema-not-exist' => 'Dat Scheema för <i lang="en">PostgreSQL</i> es nit doh.',
'config-install-pg-schema-failed' => 'Et Tabälle-Opsäze es donävve jejange.
Donn doför sorrje, dat dä Daatebangk-Aanwänder „$1“ en dämm Daatebangkscheema „$2“ schrieve kann.',
@@ -7488,6 +9718,9 @@ Donn doför sorrje, dat dä Daatebangk-Aanwänder „$1“ en dämm Daatebangksc
'config-install-user-alreadyexists' => 'Dä Aanwender „$1“ för dä Zohjref op de Daatebangk kann nit aanjelaat wääde, et jidd_en alld.',
'config-install-user-create-failed' => 'Dä Aanwender „$1“ för dä Zohjref op de Daatebangk kunnt nit aanjelaat wääde, wäje: <code lang="en">$2</code>',
'config-install-user-grant-failed' => 'Däm Daatebangk-Aanwänder sing Beräschtijunge ze säze däät nit fluppe wääje: $2',
+ 'config-install-user-missing' => 'Dä aanjejovve Metmaacher „$1“ jidd_et nit.',
+ 'config-install-user-missing-create' => '{{int:Config-install-user-missing}}
+Donn e Höhksche en et Käßje „{{int:Createaccount}}“ onge, wann De dä aanlääje wells.',
'config-install-tables' => 'Ben de Daatebangk-Tabälle aam aanlääje.',
'config-install-tables-exist' => "'''Opjepaß''': Et schingk, dem MediaWiki sing Tabälle sin alt doh.
Doh dom_mer nix aanlääje.",
@@ -7500,7 +9733,8 @@ Doh dom_mer nix dobei.",
'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-subscribe-fail' => 'Mer künne de <i lang="en">e-mail</i>-Leß <code lang="en">mediawiki-announce</code> nit abonneere: $1',
+ 'config-install-subscribe-notpossible' => '<code lang="en">cURL</code> es nit enstalleed un <code lang="en">allow_url_fopen</code>es nit doh.',
'config-install-mainpage' => 'Ben de Houpsigg med enem shtandatmääßeje Enhald aam aanlääje',
'config-install-extension-tables' => 'Ben Datebangk-Tabälle för de Zohsazprojramme aam ennreschte',
'config-install-mainpage-failed' => 'Kunnt de Houpsigg nit afshpeishere: $1',
@@ -7516,19 +9750,48 @@ Wann domet jet nit jeflupp hät, udder De di Dattei norr_ens han wells, donn op
\$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.
+'''Opjepaß''': Wann De dat jez nit deihß, es alles verschött, wat De bes jöz enjejovve häs, weil di Dattei fott es en däm Momang, woh heh dat Projamm aam Engk es.
Wann De mem Ronger- un widder Huhlaade fäädesh bes, kanns De '''[\$2 en Ding Wiki jonn]'''.",
- 'config-download-localsettings' => 'Donn de Dattei <code lang="en">LocalSettings.php</code> eronger laade',
+ 'config-download-localsettings' => 'Donn di Dattei <code lang="en">LocalSettings.php</code> eronger laade',
'config-help' => 'Hölp',
+ 'mainpagetext' => "'''MediaWiki es jetz enstalleet.'''",
+ 'mainpagedocfooter' => 'Luur en et (änglesche) [//meta.wikimedia.org/wiki/Help:Contents Handboch] wann De wesse wells wie de Wiki-Soffwär jebruch un bedeent wääde muss.
+
+== För dä Aanfang ==
+Dat es och all op Änglesch:
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]',
);
-/** Kurdish (Latin) (Kurdî (Latin))
+/** Kurdish (Latin script) (‪Kurdî (latînî)‬)
* @author George Animal
*/
$messages['ku-latn'] = array(
'config-page-language' => 'Ziman',
'config-page-name' => 'Nav',
+ 'mainpagetext' => "'''MediaWiki serketî hate çêkirin.'''",
+ 'mainpagedocfooter' => 'Alîkarî ji bo bikaranîn û guherandin yê datayê Wîkî tu di bin [//meta.wikimedia.org/wiki/Help:Contents pirtûka alîkarîyê ji bikarhêneran] da dikarê bibînê.
+
+== Alîkarî ji bo destpêkê ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lîsteya varîyablên konfîgûrasîyonê]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lîsteya e-nameyên versyonên nuh yê MediaWiki]',
+);
+
+/** Ladino (Ladino)
+ * @author Universal Life
+ */
+$messages['lad'] = array(
+ 'mainpagetext' => "'''MedyaViki ya se kureó con reuxitá.'''",
+ 'mainpagedocfooter' => 'Konsulta la [//meta.wikimedia.org/wiki/Ayudo:Contenido Guía de usador] para tomar enformasyones encima de como usar el lojikal viki.
+
+== En Empeçando ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings La lista de los arreglamientos de la konfiggurasyón]
+* [//www.mediawiki.org/wiki/Manual:FAQ/lad DDS de MedyaViki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce La lista de las letrales (e-mail) de MedyaViki]',
);
/** Luxembourgish (Lëtzebuergesch)
@@ -7570,10 +9833,10 @@ Dir fannt en am LocalSettings.php.",
'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]
+ 'config-sidebar' => '* [//www.mediawiki.org MediaWiki Haaptsäit]
+* [//www.mediawiki.org/wiki/Help:Contents Benotzerguide]
+* [//www.mediawiki.org/wiki/Manual:Contents Guide fir Administrateuren]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
----
* <doclink href=Readme>Liest dëst</doclink>
* <doclink href=ReleaseNotes>Informatioune vun der aktueller Versioun</doclink>
@@ -7584,15 +9847,23 @@ Dir kënnt MediaWiki installéieren.',
'config-env-bad' => 'Den Environnement gouf iwwerpréift.
Dir kënnt MediWiki net installéieren.',
'config-env-php' => 'PHP $1 ass installéiert.',
+ 'config-env-php-toolow' => 'PHP $1 ass installéiert.
+Awer MediaWiki brauch PHP $2 oder méi héich.',
'config-unicode-using-utf8' => "Fir d'Unicode-Normalisatioun gëtt dem Brion Vibber säin <code>utf8_normalize.so</code> benotzt.",
- 'config-no-db' => 'Et konnt kee passenden Datebank-Driver fonnt ginn!',
- 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] ass installéiert',
+ 'config-no-db' => "Et konnt kee passenden Datebank-Driver fonnt ginn! Dir musst een Datebank-Driver fir PHP installéieren.
+Dës Datebank-Type ginn ënnerstëtzt: $1.
+
+Wann Dir op engem gesharte Server sidd, da frot Ären Hosting-Provider fir de passenden Datebank-Driver z'installéieren.
+Wann Dir PHP selwer compiléiert hutt, da reconfiguréiert en mat dem ageschalten Datebank-Client, zum Beispill an deem Dir <code>./configure --with-mysql</code> benotzt.
+Wann Dir PHP vun engem Debian oder Ubuntu Package aus installéiert hutt, da musst Dir och den php5-mysql Modul installéieren.",
+ 'config-xcache' => '[http://xcache.lighttpd.net/ 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-using-server' => 'De Servernumm "<nowiki>$1</nowiki>" gëtt benotzt.',
'config-db-type' => 'Datebanktyp:',
'config-db-host-oracle' => 'Datebank-TNS:',
'config-db-wiki-settings' => 'Dës Wiki identifizéieren',
@@ -7619,10 +9890,13 @@ Wann et de Kont net gëtt, a wann den Installatiouns-Kont genuch Rechter huet, g
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-support-ibm_db2' => '* $1 ass eng kommerziell Firma fir Datebanken',
'config-header-mysql' => 'MySQL-Astellungen',
'config-header-postgres' => 'PostgreSQL-Astellungen',
'config-header-sqlite' => 'SQLite-Astellungen',
'config-header-oracle' => 'Oracle-Astellungen',
+ 'config-header-ibm_db2' => 'IBM DB2-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',
@@ -7633,6 +9907,10 @@ Benotzt keng Espacen a Bindestrécher.
E gëtt fir den Numm vum SQLite Date-Fichier benotzt.',
'config-sqlite-readonly' => 'An de Fichier <code>$1</code> Kann net geschriwwe ginn.',
'config-sqlite-cant-create-db' => 'Den Datebank-Fichier <code>$1</code> konnt net ugeluecht ginn.',
+ 'config-upgrade-done-no-regenerate' => "D'Aktualisatioun ass ofgeschloss.
+
+Dir kënnt elo [$1 ufänken Är Wiki ze benotzen]",
+ 'config-regenerate' => 'LocalSettings.php regeneréieren →',
'config-db-web-account' => 'Datebankkont fir den Accès iwwer de Web',
'config-db-web-account-same' => 'Dee selwechte Kont wéi bei der Installatioun benotzen',
'config-db-web-create' => 'De Kont uleeë wann et e net scho gëtt',
@@ -7652,6 +9930,8 @@ E gëtt fir den Numm vum SQLite Date-Fichier benotzt.',
'config-admin-name' => 'Ären Numm:',
'config-admin-password' => 'Passwuert:',
'config-admin-password-confirm' => 'Passwuert confirméieren:',
+ 'config-admin-help' => 'Gitt w.e.g. Äre gewënschte Benotzernumm hei an, zum Beispill "Jang Muller".
+Dësen Numm gëtt da gebraucht fir sech an d\'Wiki anzeloggen.',
'config-admin-name-blank' => 'Gitt e Benotzernumm fir den Administrateur an.',
'config-admin-name-invalid' => 'De spezifizéierte Benotzernumm "<nowiki>$1</nowiki>" ass net valabel.
Spezifizéiert en anere Benotzernumm.',
@@ -7686,6 +9966,7 @@ Dir kënnt elo déi Astellungen déi nach iwwreg sinn iwwersprangen an d'Wiki el
'config-upload-enable' => 'Eropluede vu Fichieren aschalten',
'config-upload-deleted' => 'Repertoire fir geläschte Fichieren:',
'config-logo' => 'URL vum Logo:',
+ 'config-instantcommons' => '"Instant Commons" aktivéieren',
'config-cc-again' => 'Nach eng kéier eraussichen...',
'config-advanced-settings' => 'Erweidert Astellungen',
'config-extensions' => 'Erweiderungen',
@@ -7697,12 +9978,154 @@ Dir kënnt elo déi Astellungen déi nach iwwreg sinn iwwersprangen an d'Wiki el
'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-user-missing' => 'De Benotzer "$1" deen ugi gouf gëtt et net.',
'config-install-tables' => 'Tabelle ginn ugeluecht',
'config-install-interwiki' => 'Standard Interwiki-Tabell gëtt ausgefëllt',
'config-install-interwiki-list' => 'De Fichier <code>interwiki.list</code> gouf net fonnt.',
'config-install-stats' => 'Initialisatioun vun de Statistiken',
'config-install-keys' => 'Generéiere vum Geheimschlëssel',
'config-install-sysop' => 'Administrateur Benotzerkont gëtt ugeluecht',
+ 'config-install-mainpage-failed' => "D'Haaptsäit konnt net dragesat ginn: $1",
+ 'config-download-localsettings' => 'LocalSettings.php eroflueden',
+ 'config-help' => 'Hëllef',
+ 'mainpagetext' => "'''MediaWiki gouf installéiert.'''",
+ 'mainpagedocfooter' => "Kuckt w.e.g. [//meta.wikimedia.org/wiki/Help:Contents d'Benotzerhandbuch] fir den Interface ze personnaliséieren.
+
+== Starthëllefen ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Hëllef bei der Konfiguratioun]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglëscht vun neie MediaWiki-Versiounen]",
+);
+
+/** Lingua Franca Nova (Lingua Franca Nova) */
+$messages['lfn'] = array(
+ 'mainpagetext' => "'''MediaWiki es aora instalada.'''",
+ 'mainpagedocfooter' => 'Atenda la [//meta.wikimedia.org/wiki/Help:Contents Gida per Usores] per informa supra la usa de la programa de vici.
+
+== Comensa ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de ajustas de la desinia]
+* [//www.mediawiki.org/wiki/Manual:FAQ Demandas comun de MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista per receta anunsias de novas supra MediaWiki]',
+);
+
+/** Ganda (Luganda)
+ * @author Kizito
+ */
+$messages['lg'] = array(
+ 'mainpagetext' => 'MediaWiki kati ewangidwa ku sisitemu yo',
+ 'mainpagedocfooter' => "Okuyiga ku nkozesa ya sofutiweya owa wiki, kebera [//meta.wikimedia.org/wiki/Help:Contents Okulagirira Abakozesa].
+
+== Amagezi agakuyamba okutandika ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lukalala lw'eby'enteekateeka yo]
+* [//www.mediawiki.org/wiki/Manual:FAQ Ebiter'okubuuzibwa ku MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Wewandise ofunenga amawulire aga email ag'ebifa ku MediaWiki]",
+);
+
+/** Limburgish (Limburgs) */
+$messages['li'] = array(
+ 'mainpagetext' => "'''MediaWiki software succesvol geïnsjtalleerd.'''",
+ 'mainpagedocfooter' => "Raodpleeg de [//meta.wikimedia.org/wiki/NL_Help:Inhoudsopgave handjleiding] veur informatie euver 't gebroek van de wikisoftware.
+
+== Mieë hölp ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lies mit instellinge]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki VGV (FAQ)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki mailinglies veur nuuj versies]",
+);
+
+/** Lao (ລາວ) */
+$messages['lo'] = array(
+ 'mainpagetext' => "'''ຕິດຕັ້ງມີເດຍວິກິນີ້ສຳເລັດແລ້ວ.'''",
+);
+
+/** Lithuanian (Lietuvių) */
+$messages['lt'] = array(
+ 'mainpagetext' => "'''MediaWiki sėkmingai įdiegta.'''",
+ 'mainpagedocfooter' => 'Informacijos apie wiki programinės įrangos naudojimą, ieškokite [//meta.wikimedia.org/wiki/Help:Contents žinyne].
+
+== Pradžiai ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigūracijos nustatymų sąrašas]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki DUK]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki pranešimai paštu apie naujas versijas]',
+);
+
+/** Latvian (Latviešu)
+ * @author GreenZeb
+ */
+$messages['lv'] = array(
+ 'config-back' => '← Atpakaļ',
+ 'config-continue' => 'Turpināt →',
+ 'config-page-language' => 'Valoda',
+ 'config-page-welcome' => 'Laipni lūdzam MediaWiki!',
+ 'config-page-dbconnect' => 'Savienoties ar datubāzi',
+ 'config-page-upgrade' => 'Atjaunināt pašreizējo instalāciju',
+ 'config-page-dbsettings' => 'Datubāzes iestatījumi',
+ 'config-page-name' => 'Vārds',
+ 'config-page-options' => 'Iespējas',
+ 'config-page-install' => 'Instalēt',
+ 'config-page-complete' => 'Pabeigts!',
+ 'config-page-restart' => 'Pārstartēt instalāciju',
+ 'config-page-readme' => 'Lasīt mani',
+ 'config-page-releasenotes' => 'Informācija par laidienu',
+ 'mainpagetext' => "'''MediaWiki veiksmīgi ieinstalēts'''",
+ 'mainpagedocfooter' => 'Izlasi [//meta.wikimedia.org/wiki/Help:Contents Lietotāja pamācību], lai iegūtu vairāk informācijas par Wiki programmatūras lietošanu.
+
+== Pirmie soļi ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigurācijas iespēju saraksts]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki J&A]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Parakstīties uz paziņojumiem par jaunām MediaWiki versijām]',
+);
+
+/** Literary Chinese (文言) */
+$messages['lzh'] = array(
+ 'mainpagetext' => "'''共筆臺已立'''",
+ 'mainpagedocfooter' => "欲識維基,見[//meta.wikimedia.org/wiki/Help:Contents User's Guide]
+
+== 始 ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Lazuri (Lazuri)
+ * @author Bombola
+ */
+$messages['lzz'] = array(
+ 'mainpagetext' => "'''Mediawiki dido k'ai ik'idu.'''",
+ 'mainpagedocfooter' => "Vik'i şeni muç'o ixmarinen ya mutxanepe oguru şeni [//meta.wikimedia.org/wiki/Help:Contents oxmaruşi rexberis] o3'k'edit.
+
+== Ağani na gyoç’k’u maxmarepe ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Ok'iduşi ayarepeşi liste]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki P'anda Na-k'itxu K'itxalape]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-mailepeşiş liste]",
+);
+
+/** Maithili (मैथिली)
+ * @author Umeshberma
+ */
+$messages['mai'] = array(
+ 'mainpagetext' => "'''मीडियाविकी नीक जकाँ प्रस्थापित भेल।'''",
+ 'mainpagedocfooter' => "सम्पर्क करू [//meta.wikimedia.org/wiki/Help:Contents User's Guide] विकी तंत्रांशक प्रयोगक जानकारी लेल।
+
+==प्रारम्भ कोना करी==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Moksha (Мокшень) */
+$messages['mdf'] = array(
+ 'mainpagetext' => "'''МедиаВикить арафтозь лац.'''",
+ 'mainpagedocfooter' => 'Ванк [//meta.wikimedia.org/wiki/Help:Contents Ветямовал Тиинди] тяса ули кода содамс Вики програпнень эрявикснень колга.
+
+== Эрявикс сюлмафксне ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Васьфневи арафнематнень кярькссь]
+* [//www.mediawiki.org/wiki/Manual:FAQ МедиаВикить Сидеста Кеподеви Кизефксне]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce МедиаВикить од верзиятнень колга кулянь пачфтема]',
);
/** Malagasy (Malagasy)
@@ -7751,8 +10174,35 @@ $messages['mg'] = array(
'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-install-keys' => 'Fanamboarana lakile miafina',
'config-help' => 'fanoroana',
+ 'mainpagetext' => "'''Tafajoro soa aman-tsara ny rindrankajy Wiki.'''",
+ 'mainpagedocfooter' => "Vangio ny [//meta.wikimedia.org/wiki/Aide:Contenu Fanoroana ho an'ny mpampiasa] ra te hitady fanoroana momba ny fampiasan'ity rindrankajy ity.
+
+== Hanomboka amin'ny MediaWiki ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lisitra ny paramètre de configuration]
+* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ momba ny MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Resaka momba ny fizaràn'ny MediaWiki]",
+);
+
+/** Eastern Mari (Олык Марий) */
+$messages['mhr'] = array(
+ 'mainpagetext' => "'''MediaWiki сай шындыме.'''",
+);
+
+/** Minangkabau (Baso Minangkabau)
+ * @author Luthfi94
+ */
+$messages['min'] = array(
+ 'mainpagetext' => "'''MediaWiki alah tapasang jo sukses'''.",
+ 'mainpagedocfooter' => 'Silakan baco [//www.mediawiki.org/wiki/Help:Contents/id Panduan Pangguno] untuak caro panggunoan parangkaik lunak wiki iko.
+
+== Mamulai panggunoan ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings/id Dafta pangaturan konfigurasi]
+* [//www.mediawiki.org/wiki/Manual:FAQ/id Dafta patanyoan nan acok diajukan manganai MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Milis rilis MediaWiki]',
);
/** Macedonian (Македонски)
@@ -7822,10 +10272,10 @@ $1
Повеќе информации ќе најдете во текстот на ГНУ-овата општа јавна лиценца.
Би требало да имате добиено <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 ЧПП]
+ 'config-sidebar' => '* [//www.mediawiki.org Домашна страница на МедијаВики]
+* [//www.mediawiki.org/wiki/Help:Contents Водич за корисници]
+* [//www.mediawiki.org/wiki/Manual:Contents Водич за администратори]
+* [//www.mediawiki.org/wiki/Manual:FAQ ЧПП]
----
* <doclink href=Readme>Прочитај ме</doclink>
* <doclink href=ReleaseNotes>Белешки за изданието</doclink>
@@ -7842,17 +10292,16 @@ $1
'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 уникодната нормализација].",
+Ако имате високопрометно мрежно место, тогаш ќе треба да прочитате повеќе за [//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.
+За да користите Уникод, ќе треба да направите [//www.mediawiki.org/wiki/Unicode_normalization_considerations надградба].",
+ 'config-no-db' => 'Не можев да пронајдам соодветен двигател за базата на податоци! Ќе треба да инсталирате двигател за базата на податоци за 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-no-fts3' => "'''Предупредување''': SQLite iе составен без модулот [//sqlite.org/fts3.html FTS3] - за оваа база нема да има можност за пребарување.",
'config-register-globals' => "'''Предупредување: Можноста <code>[http://php.net/register_globals register_globals]</code> за PHP е овозможена.'''
'''Оневозможете ја ако е можно.'''
МедијаВики ќе работи, но опслужувачот ви е изложен на безбедносни ризици.",
@@ -7881,12 +10330,14 @@ $1
'config-memory-bad' => "'''Предупредување:''' <code>memory_limit</code> за PHP изнесува $1.
Ова е веројатно премалку.
Инсталацијата може да не успее!",
- 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] е инсталиран',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ 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-no-cache' => "'''Предупредување:''' Не можев да го најдам [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
Кеширањето на објекти не е овозможено.",
+ 'config-mod-security' => "'''Предупредување''': на вашиот опслужувач има овозможено [http://modsecurity.org/ mod_security]. Ако не е поставено како што треба, ова може да предизвика проблеми кај МедијаВики и други програми што им овозможуваат на корисниците да објавуваат произволни содржини.
+Погледнете ја [http://modsecurity.org/documentation/ mod_security документацијата] или обратете се кај домаќинот ако наидете на случајни грешки.",
'config-diff3-bad' => 'GNU diff3 не е пронајден.',
'config-imagemagick' => 'Пронајден е ImageMagick: <code>$1</code>.
Ако овозможите подигање, тогаш ќе биде овозможена минијатуризација на сликите.',
@@ -7896,19 +10347,27 @@ $1
Минијатуризацијата на сликите ќе биде оневозможена.',
'config-no-uri' => "'''Грешка:''' Не можев да го утврдам тековниот URI.
Инсталацијата е откажана.",
+ 'config-no-cli-uri' => "'''Предупредување''': Нема наведено --scriptpath. Ќе се користи основниот: <code>$1</code>.",
+ 'config-using-server' => 'Користите опслужувач под името „<nowiki>$1</nowiki>“.',
+ 'config-using-uri' => 'Користите опслужувач со URL-адреса „<nowiki>$1$2</nowiki>“.',
'config-uploads-not-safe' => "'''Предупредување:''' Вашата матична папка за подигање <code>$1</code> е подложна на извршување (пуштање) на произволни скрипти.
-Иако МедијаВики врши безбедносни проверки на сите подигнати податотеки, ве советуваме [http://www.mediawiki.org/wiki/Manual:Security#Upload_security да ја затворите оваа безбедносна дупка] пред да овозможите подигање.",
+Иако МедијаВики врши безбедносни проверки на сите подигнати податотеки, ве советуваме [//www.mediawiki.org/wiki/Manual:Security#Upload_security да ја затворите оваа безбедносна дупка] пред да овозможите подигање.",
+ 'config-no-cli-uploads-check' => "'''Предупредување:''' Вашата основна папка за подигања (<code>$1</code>) не е проверена дали е подложна
+произволно извршување на скрипти во текот на инсталацијата на посредникот на повикувачко ниво (CLI).",
'config-brokenlibxml' => 'Вашиот систем има комбинација од PHP и libxml2 верзии и затоа има грешки и може да предизвика скриено расипување на податоците кај МедијаВики и други мрежни програми.
-Надградете го на PHP 5.2.9 и libxml2 2.7.3 или нивни понови верзии! ПРЕКИНУВАМ ([http://bugs.php.net/bug.php?id=45996 грешката е заведена во PHP]).',
+Надградете го на PHP 5.2.9 и libxml2 2.7.3 или нивни понови верзии! ПРЕКИНУВАМ ([//bugs.php.net/bug.php?id=45996 грешката е заведена во PHP]).',
'config-using531' => 'МедијаВики не може да се користи со PHP $1 поради грешка кај упатните параметри за <code>__call()</code>.
За да го решите проблемот, надградете го на PHP 5.3.2 или понова верзија, или пак користете го постариот PHP 5.3.0.',
+ 'config-suhosin-max-value-length' => 'Suhosin е инсталиран и ја ограничува должината на параметарот GET на $1 bytes. Делот ResourceLoader на МедијаВики ќе ја заобиколува ова граница, но со тоа ќе се влоши делотворноста. Ако е воопшто можно, на suhosin.get.max_value_length треба да го наместите на 1024 или поевеќе во php.ini , и да му ја зададете истата вредност на $wgResourceLoaderMaxQueryLength во LocalSettings.php .',
'config-db-type' => 'Тип на база:',
'config-db-host' => 'Домаќин на базата:',
- 'config-db-host-help' => 'Ако вашата база е на друг опслужувач, тогаш тука внесете го името на домаќинот илиу IP-адресата.
+ 'config-db-host-help' => 'Ако вашата база е на друг опслужувач, тогаш тука внесете го името на домаќинот или IP-адресата.
+
+Ако користите заедничко (споделено) вдомување, тогаш вашиот вдомител треба да го наведе точното име на домаќинот во неговата документација.
-Ако користите заедничко (споделено) вдомување, тогаш вашиот вдомител треба да го доде точното име на домаќинот и неговата документација.
+Ако инсталирате на опслужувач на Windows и користите MySQL, можноста „localhost“ може да не функционира за опслужувачкото име. Во тој случај, обидете се со внесување на „127.0.0.1“ како локална IP-адреса.
-Ако инсталирате на опслужувач на Windows и користите MySQL, можноста „localhost“ може да не функционира за опслужувачкото име. Во тој случај, обидете се со внесување на „127.0.0.1“ како локална IP-адреса',
+Ако користите PostgreSQL, оставете го полево празно за да се поврзете преку Unix-приклучок.',
'config-db-host-oracle' => 'TNS на базата:',
'config-db-host-oracle-help' => 'Внесете важечко [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm локално име за поврзување]. На оваа инсталација мора да ѝ биде видлива податотеката tnsnames.ora.<br />Ако користите клиентски библиотеки 10g или понови, тогаш можете да го користите и методот на иметнување на [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
'config-db-wiki-settings' => 'Идентификувај го викиво',
@@ -7949,12 +10408,13 @@ $1
Во '''бинарен режим''', во базата МедијаВики го складира UTF-8 текстот во бинарни полиња.
Ова е поефикансно отколку UTF-8 режимот на MySQL бидејќи ви овозможува да го користите целиот спектар на уникодни знаци.
Во '''UTF-8 режим''', MySQL ќе знае на кој збир знаци припаѓаат вашите податоци, и може соодветно да ги претстави и претвори,
-но нема да ви дозволи да складирате знаци над [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основната повеќејазична рамнина].",
+но нема да ви дозволи да складирате знаци над [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основната повеќејазична рамнина].",
'config-mysql-old' => 'Се бара MySQL $1 или поново, а вие имате $2.',
'config-db-port' => 'Порта на базата:',
'config-db-schema' => 'Шема за МедијаВики',
'config-db-schema-help' => 'Оваа шема обично по правило ќе работи нормално.
Сменете ја само ако знаете дека треба да се смени.',
+ 'config-pg-test-error' => "Не можам да се поврзам со базата '''$1''': $2",
'config-sqlite-dir' => 'Папка на SQLite-податоци:',
'config-sqlite-dir-help' => "SQLite ги складира сите податоци во една податотека.
@@ -7972,6 +10432,7 @@ $1
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-info' => 'МедијаВики ги поддржува следниве системи на бази на податоци:
$1
@@ -7981,10 +10442,12 @@ $1
'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-support-ibm_db2' => '* $1 is комерцијална база на податоциза фирми.',
'config-header-mysql' => 'Нагодувања на MySQL',
'config-header-postgres' => 'Нагодувања на PostgreSQL',
'config-header-sqlite' => 'Нагодувања на SQLite',
'config-header-oracle' => 'Нагодувања на Oracle',
+ 'config-header-ibm_db2' => 'Нагодувања на IBM DB2',
'config-invalid-db-type' => 'Неважечки тип на база',
'config-missing-db-name' => 'Мора да внесете значење за параметарот „Име на базата“',
'config-missing-db-host' => 'Мора да внесете вредност за „Домаќин на базата на податоци“',
@@ -8058,6 +10521,13 @@ chmod a+w $3</pre>',
'config-mysql-engine' => 'Складишен погон:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Предупредување''': Го одбравте MyISAM како складишен погон за MySQL. Но тој не се препорачува за МедијаВики бидејќи:
+* одвај поддржува едновременост поради заклучување на табелите
+* поподложен на расипување од другите погони
+* кодната база на МедијаВики не секогаш може да работи со MyISAM како што треба
+
+Ако вашата инсталација на MySQL поддржува InnoDB, тогаш сериозно препорачуваме да го користите него наместо MyISAM.
+Ако вашата инсталација на MySQL не поддржува InnoDB, веројатно дошло време за надградба.",
'config-mysql-engine-help' => "'''InnoDB''' речиси секогаш е најдобар избор, бидејќи има добра поддршка за едновременост.
'''MyISAM''' може да е побрз кај инсталациите наменети за само еден корисник или незаписни инсталации (само читање).
@@ -8068,7 +10538,8 @@ chmod a+w $3</pre>',
'config-mysql-charset-help' => "Во '''бинарен режим''', во базата на податоци МедијаВики складира UTF-8 текст во бинарни полиња.
Ова е поефикасно отколку TF-8 режимот на MySQL, и ви овозможува да ја користите целата палета на уникодни знаци.
-Во '''UTF-8 режим''', MySQL ќе знае на кој збир знаци припаѓаат вашите податоци, и може соодветно да ги претстави и претвори, но нема да ви дозволи да складиратезнаци над [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основната повеќејазична рамнина].",
+Во '''UTF-8 режим''', MySQL ќе знае на кој збир знаци припаѓаат вашите податоци, и може соодветно да ги претстави и претвори, но нема да ви дозволи да складиратезнаци над [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Основната повеќејазична рамнина].",
+ 'config-ibm_db2-low-db-pagesize' => "Вашата база на податоци DB2 има основно-зададен табеларен простор со недоволна големина на страниците. Таа треба да изнесува барем '''32 килобајти'''.",
'config-site-name' => 'Име на викито:',
'config-site-name-help' => 'Ова ќе се појавува во заглавната лента на прелистувачот и на разни други места.',
'config-site-name-blank' => 'Внесете име на мрежното место.',
@@ -8104,6 +10575,8 @@ chmod a+w $3</pre>',
'config-subscribe' => 'Претплатете се на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce release поштенскиот список за известувања].',
'config-subscribe-help' => 'Ова е нископрометен поштенски список кој се користи за соопштувања во врска со изданија, вклучувајќи важни безбедносни соопштенија.
Треба да се претплатите и да ја надградувате вашата инсталација на МедијаВики кога излегуваат нови верзии.',
+ 'config-subscribe-noemail' => 'Се обидовте да се претплатите на поштенскиот список со известувања за нови изданија без да наведете е-пошта.
+Наведете е-поштенска адреса ако сакате да се претплатите на списокот.',
'config-almost-done' => 'Уште малку сте готови!
Сега можете да ги прескокнете преостанатите поставувања и веднаш да го инсталирате викито.',
'config-optional-continue' => 'Постави ми повеќе прашања.',
@@ -8125,24 +10598,25 @@ chmod a+w $3</pre>',
'''{{int:config-profile-fishbowl}}''' — може да уредуваат само уредници што имаат добиено дозвола за тоа, но јавноста може да ги гледа страниците, вклучувајќи ја нивната историја.
'''{{int:config-profile-private}}''' — страниците се видливи и уредливи само за овластени корисници.
-По инсталацијата имате на избор и посложени кориснички права и поставки. Погледајте во [http://www.mediawiki.org/wiki/Manual:User_rights прирачникот].",
+По инсталацијата имате на избор и посложени кориснички права и поставки. Погледајте во [//www.mediawiki.org/wiki/Manual:User_rights прирачникот].",
'config-license' => 'Авторски права и лиценца:',
'config-license-none' => 'Без подножје за лиценца',
'config-license-cc-by-sa' => 'Creative Commons НаведиИзвор СподелиПодИстиУслови',
+ 'config-license-cc-by' => 'Криејтив комонс НаведиИзвор',
'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-0' => 'Криејтив комонс Нула (јавен домен)',
+ 'config-license-gfdl' => 'ГНУ-ова лиценца за слободна документација 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] треба да се правилно наместени.
@@ -8164,7 +10638,7 @@ chmod a+w $3</pre>',
'config-upload-settings' => 'Подигање на слики и податотеки',
'config-upload-enable' => 'Овозможи подигање на податотеки',
'config-upload-help' => 'Подигањето на податотеки потенцијално го изложуваат вашиот опслужувач на безбедносни ризици.
-За повеќе информации, прочитајте го [http://www.mediawiki.org/wiki/Manual:Security поглавието за безбедност] во прирачникот.
+За повеќе информации, прочитајте го [//www.mediawiki.org/wiki/Manual:Security поглавието за безбедност] во прирачникот.
За да овозможите подигање на податотеки, сменете го режимот на потпапката <code>images</code> во основната папка на МедијаВики, за да му овозможите на мрежниот опслужувач да запишува во неа.
Потоа овозможете ја оваа функција.',
@@ -8172,13 +10646,13 @@ chmod a+w $3</pre>',
'config-upload-deleted-help' => 'Одберете во која папка да се архивираат избришаните податотеки.
Најдобро би било ако таа не е достапна преку интернет.',
'config-logo' => 'URL за логото:',
- 'config-logo-help' => 'Матичното руво на МедијаВики има простор за лого од 135x160 пиксели во горниот лев агол.
+ 'config-logo-help' => 'Матичното руво на МедијаВики има простор за лого од 135 x 160 пиксели над страничната лента.
Подигнете слика со соодветна големина, и тука внесете ја URL-адресата.
Ако не сакате да имате лого, тогаш оставете го ова поле празно.',
'config-instantcommons' => 'Овозможи Instant Commons',
- 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons Instant Commons] е функција која им овозможува на викијата да користат слики, звучни записи и други мултимедијални содржини од [http://commons.wikimedia.org/ Заедничката Ризница].
-За да може ова да работи, МедијаВики бара пристап до интернет.
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] е функција која им овозможува на викијата да користат слики, звучни записи и други мултимедијални содржини од [//commons.wikimedia.org/ Заедничката Ризница].
+За да може ова да работи, МедијаВики бара пристап до интернет.
За повеќе информации за оваа функција и напатствија за нејзино поставување на вики (сите други освен Ризницата), коносултирајте го [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos прирачникот].',
'config-cc-error' => 'Изборникот на Creative Commons лиценца не даде резултати.
@@ -8214,6 +10688,7 @@ chmod a+w $3</pre>',
'config-install-step-failed' => 'не успеа',
'config-install-extensions' => 'Вклучувам додатоци',
'config-install-database' => 'Ја поставувам базата на податоци',
+ 'config-install-schema' => 'Создавам шема',
'config-install-pg-schema-not-exist' => 'PostgreSQL-шемата не постои',
'config-install-pg-schema-failed' => 'Создавањето натабелите не успеа.
Проверете дали корисникот „$1“ може да запишува во шемата „$2“.',
@@ -8221,10 +10696,17 @@ chmod a+w $3</pre>',
'config-install-pg-plpgsql' => 'Проверувам јазик PL/pgSQL',
'config-pg-no-plpgsql' => 'Ќе треба да го инсталирате јазикот PL/pgSQL во базата $1',
'config-pg-no-create-privs' => 'Сметката што ја наведовте за инсталацијата нема доволно привилегии за да создаде друга сметка.',
+ 'config-pg-not-in-role' => 'Сметката што ја наведовте за мрежниот корисник веќе постои.
+Сметката што ја наведовте за инсталација не е суперкорисник и не ѝ припаѓа на улогата на мрежниот корисник, па затоа не може да создава објекти во негова сопственост.
+
+МедијаВики налага дека табелите мора да се во сопственост на мрежниот корисник. Наведете друга мрежна сметка, или стиснете на „назад“ и наведете соодветно привилегиран корисник за инталацијата.',
'config-install-user' => 'Создавам корисник за базата',
'config-install-user-alreadyexists' => 'Корисникот „$1“ веќе постои',
'config-install-user-create-failed' => 'Создавањето на корисникот „$1“ не успеа: $2',
'config-install-user-grant-failed' => 'Доделувањето на дозвола на корисникот „$1“ не успеа: $2',
+ 'config-install-user-missing' => 'Наведениот корисник „$1“ не постои.',
+ 'config-install-user-missing-create' => 'Наведениот корисник „$1“ не постои.
+Ако сакате да го создадете, штиклирајте ја можноста „создај сметка“.',
'config-install-tables' => 'Создавам табели',
'config-install-tables-exist' => "'''Предупредување''': Изгледа дека табелите за МедијаВики веќе постојат.
Го прескокнувам создавањето.",
@@ -8234,10 +10716,11 @@ chmod a+w $3</pre>',
'config-install-interwiki-exists' => "'''Предупредување''': Табелата со интервикија веќе содржи ставки.
Го прескокнувам основно-зададениот список.",
'config-install-stats' => 'Ги подготвувам статистиките',
- 'config-install-keys' => 'Создавам таен клуч',
+ 'config-install-keys' => 'Создавање на тајни клучеви',
'config-insecure-keys' => "'''Предупредување:''' {{PLURAL:$2|Безбедносниот клуч $1 создаден во текот на инсталацијата не е сосем безбеден|Безбедносните клучеви $1 создадени во текот на инсталацијата не се сосем безбедни}}. Ви препорачуваме да {{PLURAL:$2|го|ги}} смените рачно.",
'config-install-sysop' => 'Создавање на администраторска корисничка сметка',
- 'config-install-subscribe-fail' => 'Не можам да ве претплатам на објавите на МедијаВики',
+ 'config-install-subscribe-fail' => 'Не можам да ве претплатам на известувањето mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'cURL не е инсталиран, а allow_url_fopen не е достапно.',
'config-install-mainpage' => 'Создавам главна страница со стандардна содржина',
'config-install-extension-tables' => 'Изработка на табели за овозможени додатоци',
'config-install-mainpage-failed' => 'Не можев да вметнам главна страница: $1',
@@ -8258,10 +10741,18 @@ $3
Откога ќе завршите со тоа, можете да '''[$2 влезете на вашето вики]'''.",
'config-download-localsettings' => 'Преземи го LocalSettings.php',
'config-help' => 'помош',
+ 'mainpagetext' => "'''МедијаВики е успешно инсталиран.'''",
+ 'mainpagedocfooter' => 'Погледнете го [//meta.wikimedia.org/wiki/Help:Contents Упатството за корисници] за подетални иформации како се користи вики-програмот.
+
+==Од каде да почнете==
+* [//meta.wikimedia.org/wiki/Manual:Configuration_settings Список на нагодувања]
+* [//meta.wikimedia.org/wiki/Manual:FAQ ЧПП (често поставувани прашања) за МедијаВики].
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Поштенски список на МедијаВики за нови верзии]',
);
/** Malayalam (മലയാളം)
* @author Praveenp
+ * @author Sadik Khalid
*/
$messages['ml'] = array(
'config-desc' => 'മീഡിയവിക്കി ഇൻസ്റ്റോളർ',
@@ -8295,10 +10786,10 @@ $messages['ml'] = array(
'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-sidebar' => '* [//www.mediawiki.org മീഡിയവിക്കി പ്രധാനതാൾ]
+* [//www.mediawiki.org/wiki/Help:Contents ഉപയോക്തൃസഹായി]
+* [//www.mediawiki.org/wiki/Manual:Contents കാര്യനിർവഹണസഹായി]
+* [//www.mediawiki.org/wiki/Manual:FAQ പതിവുചോദ്യങ്ങൾ]',
'config-env-php' => 'പി.എച്ച്.പി. $1 ഇൻസ്റ്റോൾ ചെയ്തിട്ടുണ്ട്.',
'config-no-db' => 'അനുയോജ്യമായ ഡേറ്റാബേസ് ഡ്രൈവർ കണ്ടെത്താനായില്ല!',
'config-memory-raised' => 'പി.എച്ച്.പി.യുടെ <code>memory_limit</code> $1 ആണ്, $2 ആയി ഉയർത്തിയിരിക്കുന്നു.',
@@ -8357,7 +10848,7 @@ $1
'config-almost-done' => 'മിക്കവാറും പൂർത്തിയായിരിക്കുന്നു!
ബാക്കിയുള്ളവ അവഗണിച്ച് വിക്കി ഇൻസ്റ്റോൾ ചെയ്യാവുന്നതാണ്.',
'config-optional-continue' => 'കൂടുതൽ ചോദ്യങ്ങൾ ചോദിക്കൂ.',
- 'config-optional-skip' => 'ഞാൻ മടുത്തു, ഇൻസ്റ്റോൾ ചെയ്ത് തീർക്ക്.',
+ 'config-optional-skip' => 'എനിക്ക് മടുത്തു, ഒന്ന് ഇൻസ്റ്റോൾ ചെയ്ത് തീർക്ക്.',
'config-profile-wiki' => 'പരമ്പരാഗത വിക്കി',
'config-profile-no-anon' => 'അംഗത്വ സൃഷ്ടി ചെയ്യേണ്ടതുണ്ട്',
'config-profile-fishbowl' => 'അനുവാദമുള്ളവർ മാത്രം തിരുത്തുക',
@@ -8365,9 +10856,7 @@ $1
'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-license-pd' => 'പൊതുസഞ്ചയം',
'config-email-settings' => 'ഇമെയിൽ സജ്ജീകരണങ്ങൾ',
'config-enable-email-help' => "ഇമെയിൽ പ്രവർത്തിക്കണമെങ്കിൽ, [http://www.php.net/manual/en/mail.configuration.php PHP's മെയിൽ സജ്ജീകരണങ്ങൾ] ശരിയായി ക്രമീകരിക്കേണ്ടതുണ്ട്.
ഇമെയിൽ സൗകര്യം ആവശ്യമില്ലെങ്കിൽ, ഇവിടെത്തന്നെ അത് നിർജ്ജീവമാക്കാം.",
@@ -8398,15 +10887,23 @@ $1
'config-install-mainpage' => 'സ്വാഭാവിക ഉള്ളടക്കത്തോടുകൂടി പ്രധാനതാൾ സൃഷ്ടിക്കുന്നു',
'config-install-mainpage-failed' => 'പ്രധാന താൾ ഉൾപ്പെടുത്താൻ കഴിഞ്ഞില്ല: $1',
'config-install-done' => "'''അഭിനന്ദനങ്ങൾ!'''
-താങ്കൾ വിജയകരമായി മീഡിയവിക്കി ഇൻസ്റ്റോൾ ചെയ്തിരിക്കുന്നു.
+താങ്കൾ വിജയകരമായി മീഡിയവിക്കി സജ്ജീകരിച്ചിരിക്കുന്നു.
+
+ഇൻസ്റ്റോളർ താങ്കളുടെ എല്ലാ ക്രമീകരണങ്ങളുമടങ്ങുന്ന <code>LocalSettings.php</code> ഫയൽ സൃഷ്ടിച്ചിട്ടുണ്ട്.
-ഇൻസ്റ്റോളർ ഒരു <code>LocalSettings.php</code> ഫയൽ സൃഷ്ടിച്ചിട്ടുണ്ട്.
-അതിൽ താങ്കളുടെ എല്ലാ ക്രമീകരണങ്ങളുമുണ്ട്.
+പ്രസ്തുത പ്രമാണം ഡൗൺലോഡ് ചെയ്ത് താങ്കളുടെ വിക്കി സജ്ജീകരണത്തിന്റെ അടിസ്ഥാന ഡയറക്റ്ററിയിൽ ഇടേണ്ടതാണ് (index.php കിടക്കുന്ന അതേ ഡയറക്റ്ററിയിൽ). ഡൗൺലോഡിങ്ങ് സ്വയം ആരംഭിക്കുന്നതാണ്. ഡൗൺലോഡിങ്ങ് സ്വയം തുടങ്ങാതിരിക്കുകയോ, താങ്കൾ റദ്ദാക്കുകയോ ചെയ്ത പക്ഷം താഴെ കാണുന്ന കണ്ണിയിൽ ഞെക്കുക:
+$3
-താങ്കൾ അത് [$1 എടുത്ത്] താങ്കളുടെ വിക്കി ഇൻസ്റ്റലേഷന്റെ അടിസ്ഥാന ഡയറക്റ്ററിയിൽ ഇടുക (index.php കിടക്കുന്ന അതേ ഡയറക്റ്ററി).
-'''ശ്രദ്ധിക്കുക''': ഇത് ഇപ്പോൾ ചെയ്തില്ലെങ്കിൽ, സൃഷ്ടിക്കപ്പെട്ട കോൺഫിഗറേഷൻ ഫയൽ എടുക്കാതെ ഇൻസ്റ്റലേഷൻ പ്രക്രിയയിൽ നിന്ന് പുറത്തിറങ്ങിയാൽ പിന്നീട് ലഭ്യമായിരിക്കില്ല.
+'''ശ്രദ്ധിക്കുക''': താങ്കൾ ഇപ്പോൾ ചെയ്തില്ലെങ്കിൽ, ഫയൽ എടുക്കാതെ ഇൻസ്റ്റലേഷൻ പ്രക്രിയയിൽ നിന്ന് പുറത്തിറങ്ങിയാൽ, സൃഷ്ടിക്കപ്പെട്ട ക്രമീകരണങ്ങളടങ്ങുന്ന പ്രമാണം പിന്നീട് ലഭ്യമായിരിക്കില്ല.
-ചെയ്തശേഷം, താങ്കൾക്ക് '''[$2 വിക്കിയിൽ പ്രവേശിക്കാം]'''.",
+മുകളിൽ പറഞ്ഞ പ്രകാരം ചെയ്തു കഴിഞ്ഞാൽ, താങ്കൾക്ക് '''[$2 വിക്കിയിൽ പ്രവേശിക്കാവുന്നതാണ്]'''.",
+ 'mainpagetext' => "'''മീഡിയവിക്കി വിജയകരമായി സജ്ജീകരിച്ചിരിക്കുന്നു.'''",
+ 'mainpagedocfooter' => 'വിക്കി സോഫ്റ്റ്‌വെയർ ഉപയോഗിക്കുന്നതിനെ കുറിച്ചുള്ള വിശദാംശങ്ങൾക്ക് [//meta.wikimedia.org/wiki/Help:Contents സോഫ്റ്റ്‌വെയർ സഹായി] കാണുക.
+
+== പ്രാരംഭസഹായികൾ ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings ക്രമീകരണങ്ങളുടെ പട്ടിക]
+* [//www.mediawiki.org/wiki/Manual:FAQ മീഡിയവിക്കി പതിവുചോദ്യങ്ങൾ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce മീഡിയവിക്കി പ്രകാശന മെയിലിങ് ലിസ്റ്റ്]',
);
/** Mongolian (Монгол)
@@ -8414,6 +10911,104 @@ $1
*/
$messages['mn'] = array(
'config-page-language' => 'Хэл',
+ 'mainpagetext' => "'''МедиаВики амжилттай суулаа.'''",
+ 'mainpagedocfooter' => 'Вики программыг хэрэглэх талаар заавар авахын тулд [//meta.wikimedia.org/wiki/Help:Contents хэрэглэгчийн гарын авлага]-г үзнэ үү.
+
+== Эхлэх ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Тохиргоо]
+* [//www.mediawiki.org/wiki/Manual:FAQ МедиаВикигийн тогтмол тавигддаг асуултууд]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce МедиаВикигийн мэдээний мэйл явуулах жагсаалт]',
+);
+
+/** Marathi (मराठी) */
+$messages['mr'] = array(
+ 'mainpagetext' => "'''मीडियाविकीचे इन्स्टॉलेशन पूर्ण.'''",
+ 'mainpagedocfooter' => 'विकी सॉफ्टवेअर वापरण्याकरिता [//meta.wikimedia.org/wiki/Help:Contents यूजर गाईड] पहा.
+
+== सुरुवात ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings कॉन्फिगरेशन सेटींगची यादी]
+* [//www.mediawiki.org/wiki/Manual:FAQ मीडियाविकी नेहमी विचारले जाणारे प्रश्न]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce मीडियाविकि मेलिंग लिस्ट]',
+);
+
+/** Malay (Bahasa Melayu)
+ * @author Anakmalaysia
+ */
+$messages['ms'] = array(
+ 'config-back' => '← Undur',
+ 'config-continue' => 'Teruskan →',
+ 'config-page-language' => 'Bahasa',
+ 'config-page-welcome' => 'Selamat datang ke MediaWiki!',
+ 'config-page-dbconnect' => 'Bersambung dengan pangkalan data',
+ 'config-page-upgrade' => 'Naik taraf pemasangan sedia ada',
+ 'config-page-dbsettings' => 'Tetapan pangkalan data',
+ 'config-page-name' => 'Nama',
+ 'config-page-options' => 'Pilihan',
+ 'config-page-install' => 'Pasang',
+ 'config-env-php' => 'PHP $1 dipasang.',
+ 'config-env-php-toolow' => 'PHP $1 dipasang.
+Bagaimanapun, MediaWiki memerlukan PHP $2 ke atas.',
+ 'config-unicode-using-utf8' => 'utf8_normalize.so oleh Brion Vibber digunakan untuk penormalan Unicode.',
+ 'config-unicode-using-intl' => '[http://pecl.php.net/intl Sambungan intl PECL] digunakan untuk penormalan Unicode.',
+ 'config-db-charset' => 'Peranggu aksara pangkalan data',
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-mysql-engine' => 'Enjin storan:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-charset' => 'Peranggu aksara pangkalan data:',
+ 'config-mysql-binary' => 'Perduaan',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Nama wiki:',
+ 'config-site-name-help' => 'Ini akan dipaparkan pada bar tajuk perisian pelayar dan tempat-tempat lain yang berkenaan.',
+ 'config-site-name-blank' => 'Isikan nama tapak.',
+ 'config-project-namespace' => 'Ruang nama projek:',
+ 'config-ns-generic' => 'Projek',
+ 'config-ns-site-name' => 'Sama dengan nama wiki: $1',
+ 'config-ns-other' => 'Lain-lain (nyatakan)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-admin-password' => 'Kata laluan:',
+ 'config-admin-email' => 'Alamat e-mel:',
+ 'config-license' => 'Hak cipta dan lesen:',
+ 'config-license-none' => 'Tiada pengaki lesen',
+ 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by' => 'Creative Commons Attribution',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
+ 'config-license-cc-0' => 'Creative Commons Zero (Domain Awam)',
+ 'config-license-gfdl' => 'Lesen Dokumentasi Bebas GNU 1.3 ke atas',
+ 'config-license-pd' => 'Domain Awam',
+ 'config-email-settings' => 'Tetapan e-mel',
+ 'config-install-step-done' => 'siap',
+ 'config-install-step-failed' => 'gagal',
+ 'config-help' => 'bantuan',
+ 'mainpagetext' => "'''MediaWiki telah berjaya dipasang.'''",
+ 'mainpagedocfooter' => 'Sila rujuk [//meta.wikimedia.org/wiki/Help:Contents Panduan Penggunaan] untuk maklumat mengenai penggunaan perisian wiki ini.
+
+== Untuk bermula ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Senarai tetapan konfigurasi]
+* [//www.mediawiki.org/wiki/Manual:FAQ Soalan Lazim MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Senarai mel bagi keluaran MediaWiki]',
+);
+
+/** Maltese (Malti)
+ * @author Chrisportelli
+ */
+$messages['mt'] = array(
+ 'mainpagetext' => "'''MediaWiki ġie installat b'suċċess.'''",
+ 'mainpagedocfooter' => "Ikkonsulta l-[//meta.wikimedia.org/wiki/Help:Contents Gwida għall-utenti] sabiex tikseb iktar informazzjoni dwar kif tuża' s-softwer tal-wiki.
+
+== Biex tibda ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista ta' preferenzi għall-konfigurazzjoni]
+* [//www.mediawiki.org/wiki/Manual:FAQ Mistoqsijiet rikorrenti fuq il-MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Il-lista tal-posta tħabbar 'l MediaWiki]",
+);
+
+/** Burmese (မြန်မာဘာသာ)
+ * @author Lionslayer
+ */
+$messages['my'] = array(
+ 'mainpagetext' => "'''မီဒီယာဝီကီကို အောင်မြင်စွာ သွင်းပြီးပါပြီ။'''",
);
/** Erzya (Эрзянь)
@@ -8428,6 +11023,66 @@ $messages['myv'] = array(
'config-admin-password-confirm' => 'Совамо валот одов:',
'config-admin-email' => 'Е-сёрма паргот:',
'config-install-step-done' => 'теезь',
+ 'mainpagetext' => "'''МедияВикинь тевс аравтомазо парсте лиссь.'''",
+);
+
+/** Mazanderani (مازِرونی)
+ * @author محک
+ */
+$messages['mzn'] = array(
+ 'config-help' => 'راهنما',
+);
+
+/** Nahuatl (Nāhuatl) */
+$messages['nah'] = array(
+ 'mainpagetext' => "'''MediaHuiqui cualli ōmotlahtlāli.'''",
+);
+
+/** Min Nan Chinese (Bân-lâm-gú)
+ * @author Ianbu
+ */
+$messages['nan'] = array(
+ 'mainpagetext' => "'''MediaWiki已經裝好矣。'''",
+ 'mainpagedocfooter' => '請查看[//meta.wikimedia.org/wiki/Help:Contents 用者說明書]的資料通使用wiki 軟體
+
+== 入門 ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings 配置的設定]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki時常問答]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki的公布列單]',
+);
+
+/** Low German (Plattdüütsch) */
+$messages['nds'] = array(
+ 'mainpagetext' => "'''De MediaWiki-Software is mit Spood installeert worrn.'''",
+ 'mainpagedocfooter' => 'Kiek de [//meta.wikimedia.org/wiki/MediaWiki_localisation Dokumentatschoon för dat Anpassen vun de Brukerböversiet]
+un dat [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Brukerhandbook] för Hülp to de Bruuk un Konfiguratschoon.',
+);
+
+/** Nedersaksisch (Nedersaksisch)
+ * @author Servien
+ */
+$messages['nds-nl'] = array(
+ 'mainpagetext' => "'''’t Installeren van de MediaWiki programmatuur is succesvol.'''",
+ 'mainpagedocfooter' => 'Bekiek de [//meta.wikimedia.org/wiki/Help:Contents haandleiding] veur informasie over t gebruuk van de wikiprogrammatuur.
+
+== Meer hulpe ==
+* [//www.mediawiki.org/wiki/Help:Configuration_settings Lieste mit instellingen]
+* [//www.mediawiki.org/wiki/Help:FAQ MediaWiki-vragen die vake esteld wörden]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki-postlieste veur nieje versies]',
+);
+
+/** Nepali (नेपाली)
+ * @author Bhawani Gautam
+ * @author RajeshPandey
+ */
+$messages['ne'] = array(
+ 'mainpagetext' => "'''मीडिया सफलतापूर्वक कम्प्यूटरमा स्थापित भयो ।'''",
+ 'mainpagedocfooter' => ' विकी अनुप्रयोग कसरी प्रयोग गर्ने भन्ने जानकारीको लागि [//meta.wikimedia.org/wiki/Help:Contents प्रयोगकर्ता सहायता] हेर्नुहोस्
+
+== सुरू गर्नको लागि ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings विन्यास सेटिङ्ग सूची]
+* [//www.mediawiki.org/wiki/Manual:FAQ मेडियाविकि सामान्य प्रश्नका उत्तरहरु]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce मेडियाविकि सुचना मेलिङ्ग सूची]',
);
/** Dutch (Nederlands)
@@ -8436,11 +11091,12 @@ $messages['myv'] = array(
* @author Purodha
* @author SPQRobin
* @author Siebrand
+ * @author Tjcool007
*/
$messages['nl'] = array(
'config-desc' => 'Het installatieprogramma voor MediaWiki',
'config-title' => 'Installatie MediaWiki $1',
- 'config-information' => 'Informatie',
+ 'config-information' => 'Gegevens',
'config-localsettings-upgrade' => 'Er is een bestaand instellingenbestand <code>LocalSettings.php</code> gevonden.
Voer de waarde van <code>$wgUpgradeKey</code> in in onderstaande invoerveld om deze installatie bij te werken.
De instelling is terug te vinden in LocalSettings.php.',
@@ -8479,7 +11135,7 @@ Controleer uw php.ini en zorg dat er een juiste map is ingesteld voor <code>sess
'config-page-name' => 'Naam',
'config-page-options' => 'Opties',
'config-page-install' => 'Installeren',
- 'config-page-complete' => 'Afgerond!',
+ 'config-page-complete' => 'Voltooid!',
'config-page-restart' => 'Installatie herstarten',
'config-page-readme' => 'Lees mij',
'config-page-releasenotes' => 'Release notes',
@@ -8488,8 +11144,8 @@ Controleer uw php.ini en zorg dat er een juiste map is ingesteld voor <code>sess
'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.
+ 'config-welcome' => '=== Controle omgeving ===
+Er worden een aantal basiscontroles uitgevoerd met als doel vast te stellen of deze omgeving geschikt is voor een installatie van MediaWiki.
Als u hulp nodig hebt bij de installatie, lever deze gegevens dan ook aan.',
'config-copyright' => "=== Auteursrechten en voorwaarden ===
@@ -8498,13 +11154,13 @@ $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.
+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)
+ 'config-sidebar' => '* [//www.mediawiki.org MediaWiki thuispagina]
+* [//www.mediawiki.org/wiki/Help:Contents Gebruikershandleiding] (Engelstalig)
+* [//www.mediawiki.org/wiki/Manual:Contents Beheerdershandleiding] (Engelstalig)
+* [//www.mediawiki.org/wiki/Manual:FAQ Veel gestelde vragen] (Engelstalig)
----
* <doclink href=Readme>Leesmij</doclink> (Engelstalig)
* <doclink href=ReleaseNotes>Release notes</doclink> (Engelstalig)
@@ -8520,17 +11176,17 @@ 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].",
+Als u MediaWiki voor een website met veel verkeer installeert, lees u dan in over [//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.
+U moet [//www.mediawiki.org/wiki/Unicode_normalization_considerations bijwerken] als Unicode voor u van belang is.",
+ 'config-no-db' => 'Het was niet mogelijk een geschikte databasedriver te vinden voor PHP.
+U moet een databasedriver installeren voor PHP.
De volgende databases worden ondersteund: $1.
Als u op een gedeelde omgeving zit, vraag dan aan uw hostingprovider een geschikte databasedriver te installeren.
Als u PHP zelf hebt gecompileerd, wijzig dan uw instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysql</code>.
Als u PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook de module php5-mysql.',
- 'config-no-fts3' => "'''Waarschuwing''': SQLite is gecompileerd zonder de module [http://sqlite.org/fts3.html FTS3]; er zijn geen zoekfuncties niet beschikbaar.",
+ 'config-no-fts3' => "'''Waarschuwing''': SQLite is gecompileerd zonder de module [//sqlite.org/fts3.html FTS3]; er zijn geen zoekfuncties niet beschikbaar.",
'config-register-globals' => "'''Waarschuwing: De PHP-optie <code>[http://php.net/register_globals register_globals]</code> is ingeschakeld.'''
'''Schakel deze uit als dat mogelijk is.'''
MediaWiki kan ermee werken, maar uw server is dan meer kwetsbaar voor beveiligingslekken.",
@@ -8560,12 +11216,14 @@ MediaWiki heeft ondersteuning voor UTF-8 nodig om correct te kunnen werken.",
'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-xcache' => '[http://xcache.lighttpd.net/ XCache] is op dit moment geïnstalleerd',
'config-apc' => '[http://www.php.net/apc APC] is op dit moment geïnstalleerd',
'config-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-mod-security' => "'''Waarschuwing:''' uw webserver heeft de module [http://modsecurity.org/ mod_security] ingeschakeld. Als deze onjuist is ingesteld, kan dit problemen geven in combinatie met MediaWiki of andere software die gebruikers in staat stelt willekeurige inhoud te posten.
+Lees de [http://modsecurity.org/documentation/ documentatie over mod_security] of neem contact op met de helpdesk van uw provider als u tegen problemen aanloopt.",
'config-diff3-bad' => 'GNU diff3 niet aangetroffen.',
'config-imagemagick' => 'ImageMagick aangetroffen: <code>$1</code>.
Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden inschakelt.',
@@ -8575,13 +11233,18 @@ Het aanmaken van miniaturen van afbeeldingen wordt ingeschakeld als u uploaden i
Het maken van miniaturen van afbeeldingen wordt uitgeschakeld.',
'config-no-uri' => "'''Fout:''' de huidige URI kon niet vastgesteld worden.
De installatie is afgebroken.",
+ 'config-no-cli-uri' => "'''Waarschuwing:''' de parameter ==scriptpath is niet opgegeven. De standaardwaarde wordt gebruikt: <code>$1</code>.",
+ 'config-using-server' => 'Servernaam "<nowiki>$1</nowiki>" wordt gebruikt.',
+ 'config-using-uri' => 'De server-URL "<nowiki>$1$2</nowiki>" wordt gebruikt.',
'config-uploads-not-safe' => "'''Waarschuwing:''' uw uploadmap <code>$1</code> kan gebruikt worden voor het arbitrair uitvoeren van scripts.
-Hoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [http://www.mediawiki.org/wiki/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
+Hoewel MediaWiki alle toegevoegde bestanden controleert op bedreigingen, is het zeer aan te bevelen het [//www.mediawiki.org/wiki/Manual:Security#Upload_security beveiligingslek te verhelpen] alvorens uploads in te schakelen.",
+ 'config-no-cli-uploads-check' => "''Waarschuwing:'' uw standaardmap voor uploads (<code>$1</code>) wordt niet gecontroleerd op kwetsbaarheden voor het uitvoeren van willekeurige scripts gedurende de CLI-installatie.",
'config-brokenlibxml' => 'Uw systeem heeft een combinatie van PHP- en libxml2-versies geïnstalleerd die is foutgevoelig is en kan leiden tot onzichtbare beschadiging van gegevens in MediaWiki en andere webapplicaties.
-Upgrade naar PHP 5.2.9 of hoger en libxml2 2.7.3 of hoger! De installatie wordt afgebroken ([http://bugs.php.net/bug.php?id=45996 bij PHP gerapporteerde fout]).',
+Upgrade naar PHP 5.2.9 of hoger en libxml2 2.7.3 of hoger! De installatie wordt afgebroken ([//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-suhosin-max-value-length' => 'Suhosin is geïnstalleerd en beperkt de lengte van de GET-parameter tot $1 bytes. De ResourceLoader van MediaWiki omzeilt deze beperking, maar dat is slecht voor de prestaties. Als het mogelijk is, moet u de waarde "suhosin.get.max_value_length" in php.ini instellen op 1024 of hoger en $wgResourceLoaderMaxQueryLength in LocalSettings.php op dezelfde waarde instellen.',
'config-db-type' => 'Databasetype:',
'config-db-host' => 'Databasehost:',
'config-db-host-help' => 'Als uw databaseserver een andere server is, voer dan de hostnaam of het IP-adres hier in.
@@ -8589,7 +11252,9 @@ De installatie wordt afgebroken.',
Als u gebruik maakt van gedeelde webhosting, hoort uw provider u de juiste hostnaam te hebben verstrekt.
Als u MediaWiki op een Windowsserver installeert en MySQL gebruikt, dan werkt "localhost" mogelijk niet als servernaam.
-Als het inderdaad niet werkt, probeer dan "127.0.0.1" te gebruiken als lokaal IP-adres.',
+Als het inderdaad niet werkt, probeer dan "127.0.0.1" te gebruiken als lokaal IP-adres.
+
+Als u PostgreSQL gebruikt, laat dit veld dan leeg om via een Unix-socket te verbinden.',
'config-db-host-oracle' => 'Database-TNS:',
'config-db-host-oracle-help' => 'Voer een geldige [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name] in; een tnsnames.ora-bestand moet zichtbaar zijn voor deze installatie.<br />Als u gebruik maakt van clientlibraries 10g of een latere versie, kunt u ook gebruik maken van de naamgevingsmethode [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
'config-db-wiki-settings' => 'Identificeer deze wiki',
@@ -8629,13 +11294,14 @@ Dit veld wordt meestal leeg gelaten.",
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.",
+Het is dan niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
'config-mysql-old' => 'U moet MySQL $1 of later gebruiken.
U gebruikt $2.',
'config-db-port' => 'Databasepoort:',
'config-db-schema' => 'Schema voor MediaWiki',
'config-db-schema-help' => 'Dit schema klopt meestal.
Wijzig het alleen als u weet dat dit nodig is.',
+ 'config-pg-test-error' => "Kan geen verbinding maken met database '''$1''': $2",
'config-sqlite-dir' => 'Gegevensmap voor SQLite:',
'config-sqlite-dir-help' => "SQLite slaat alle gegevens op in een enkel bestand.
@@ -8644,7 +11310,7 @@ De map die u opgeeft moet schrijfbaar zijn voor de webserver tijdens de installa
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.
+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:',
@@ -8653,19 +11319,22 @@ Overweeg om de database op een totaal andere plaats neer te zetten, bijvoorbeeld
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'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-postgres' => '* $1 is een populair open source databasesysteem als alternatief voor MySQL ([http://www.php.net/manual/en/pgsql.installation.php hoe PHP gecompileerd moet zijn met ondersteuning voor PostgreSQL]). Het is mogelijk dat er een aantal bekende problemen zijn met MediaWiki in combinatie met deze database en daarom wordt PostgreSQL niet aanbevolen voor een productieomgeving.',
'config-support-sqlite' => '* $1 is een zeer goed ondersteund lichtgewicht databasesysteem ([http://www.php.net/manual/en/pdo.installation.php hoe PHP gecompileerd zijn met ondersteuning voor SQLite]; gebruikt PDO)',
'config-support-oracle' => '* $1 is een commerciële data voor grote bedrijven ([http://www.php.net/manual/en/oci8.installation.php PHP compileren met ondersteuning voor OCI8]).',
+ 'config-support-ibm_db2' => '* $1 is een commerciële enterprisedatabase.',
'config-header-mysql' => 'MySQL-instellingen',
'config-header-postgres' => 'PostgreSQL-instellingen',
'config-header-sqlite' => 'SQLite-instellingen',
'config-header-oracle' => 'Oracle-instellingen',
+ 'config-header-ibm_db2' => 'Instellingen voor IBM DB2',
'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"',
@@ -8741,6 +11410,13 @@ De gebruiker die u hier opgeeft moet al bestaan.',
'config-mysql-engine' => 'Opslagmethode:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Waarschuwing''': U hebt MyISAM geselecteerd als opslagengine voor MySQL. Dit is niet aan te raden voor MediaWiki omdat:
+* het nauwelijks ondersteuning biedt voor gebruik door meerdere gebruikers tegelijkertijd door het locken van tabellen;
+* het meer vatbaar is voor corruptie dan andere engines;
+* de code van MediaWiki niet alstijd omgaat met MyISAM zoals dat zou moeten.
+
+Als uw installatie van MySQL InnoDB ondersteunt, gebruik dat dan vooral.
+Als uw installatie van MySQL geen ondersteuning heeft voor InnoDB, denk dan na over upgraden.",
'config-mysql-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.
@@ -8752,14 +11428,15 @@ MyISAM-databases raken vaker corrupt dan InnoDB-databases.",
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.",
+Het is dat niet mogelijk tekens op te slaan die de \"[//nl.wikipedia.org/wiki/Lijst_van_Unicode-subbereiken#Basic_Multilingual_Plane Basic Multilingual Plane]\" te boven gaan.",
+ 'config-ibm_db2-low-db-pagesize' => "Uw DB2-database heeft een standaard tablespace met een onvoldoende grote pagesize. De pagesize moet tenminste '''32K''' zijn.",
'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' => 'Andere (geef aan welke)',
'config-ns-other-default' => 'MijnWiki',
'config-project-namespace-help' => "In het kielzog van Wikipedia beheren veel wiki's hun beleidspagina's apart van hun inhoudelijke pagina's in een \"'''projectnaamruimte'''\".
Alle paginanamen in deze naamruimte beginnen met een bepaald voorvoegsel dat u hier kunt aangeven.
@@ -8781,13 +11458,15 @@ Kies een andere gebruikersnaam.',
'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-email-help' => "Voer hier een e-mailadres in om e-mail te kunnen ontvangen van andere gebruikers op de wiki, uw wachtwoord opnieuw in te kunnen stellen en op de hoogte te worden gehouden van wijzigingen van pagina's op uw volglijst. U kunt het veld leeg laten.",
'config-admin-error-user' => 'Interne fout bij het aanmaken van een beheerder met de naam "<nowiki>$1</nowiki>".',
'config-admin-error-password' => 'Interne fout bij het instellen van een wachtwoord voor de bejeerder "<nowiki>$1</nowiki>": <pre>$2</pre>',
'config-admin-error-bademail' => 'U hebt een ongeldig e-mailadres opgegeven',
'config-subscribe' => 'Abonneren op de [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailinglijst releaseaankondigen].',
'config-subscribe-help' => 'Dit is een mailinglijst met een laag volume voor aankondigingen van nieuwe versies, inclusief belangrijke aankondigingen met betrekking tot beveiliging.
Abonneer uzelf erop en werk uw MediaWiki-installatie bij als er nieuwe versies uitkomen.',
+ 'config-subscribe-noemail' => 'U hebt geprobeerd zich te abonneren op de mailinglijst voor release-aankondigingen zonder een e-mailadres op te geven.
+Geef een e-mailadres op als u zich wil abonneren op de mailinglijst.',
'config-almost-done' => 'U bent bijna klaar!
Als u wilt kunt u de overige instellingen overslaan en de wiki nu installeren.',
'config-optional-continue' => 'Stel me meer vragen.',
@@ -8809,14 +11488,14 @@ Een wiki met '''{{int:config-profile-no-anon}}\" biedt extra verantwoordelijkhei
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].",
+Meer complexe instellingen voor gebruikersrechten zijn te maken na de installatie; hierover is meer te lezen in de [//www.mediawiki.org/wiki/Manual:User_rights handleiding].",
'config-license' => 'Auteursrechten en licentie:',
'config-license-none' => 'Geen licentie in de voettekst',
'config-license-cc-by-sa' => 'Creative Commons Naamsvermelding-Gelijk delen',
+ 'config-license-cc-by' => 'Creative Commons Naamsvermelding',
'config-license-cc-by-nc-sa' => 'Creative Commons Naamsvermelding-Niet Commercieel-Gelijk delen',
- 'config-license-cc-0' => 'Creative Commons Zero',
- 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
- 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 of hoger',
+ 'config-license-cc-0' => 'Creative Commons Zero (Publiek domein)',
+ 'config-license-gfdl' => 'GNU Free Documentation License 1.3 of hoger',
'config-license-pd' => 'Publiek domein',
'config-license-cc-choose' => 'Een Creative Commons-licentie selecteren',
'config-license-help' => "In veel openbare wiki's zijn alle bijdragen beschikbaar onder een [http://freedomdefined.org/Definition vrije licentie].
@@ -8825,8 +11504,9 @@ 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.",
+De GNU Free Documentation License is de oude licentie voor inhoud uit Wikipedia.
+Dit is nog steeds een geldige licentie, maar deze licentie is lastig te begrijpen.
+Het is ook lastig inhoud te hergebruiken onder de GFDL.",
'config-email-settings' => 'E-mailinstellingen',
'config-enable-email' => 'Uitgaande e-mail inschakelen',
'config-enable-email-help' => "Als u wilt dat e-mailen mogelijk is, dan moeten [http://www.php.net/manual/en/mail.configuration.php PHP's e-mailinstellingen] correct zijn.
@@ -8848,21 +11528,21 @@ 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.
+Er is meer [//www.mediawiki.org/wiki/Manual:Security informatie over beveiliging] beschikbaar in de handleiding.
-Om het bestandsuploads mogelijk te maken kunt u de rechten op de submap <code>images</code> onder de hoofdmap van MediaWiki aanpassen, zodat de webserver erin kan schrijven.
+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.
+ 'config-logo-help' => 'Het standaarduiterlijk van MediaWiki bevat ruimte voor een logo van 135x160 pixels boven het menu.
Upload een afbeelding met de juiste afmetingen en voer de URL hier in.
Als 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.
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] is functie die het mogelijk maakt om afbeeldingen, geluidsbestanden en andere mediabestanden te gebruiken van de website [//commons.wikimedia.org/ Wikimedia Commons].
+Hiervoor heeft MediaWiki toegang nodig tot Internet.
Meer informatie over deze functie en hoe deze in te stellen voor andere wiki\'s dan Wikimedia Commons is te vinden in de [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos handleiding].',
'config-cc-error' => 'De licentiekiezer van Creative Commons heeft geen resultaat opgeleverd.
@@ -8900,6 +11580,7 @@ Als u nog wijzigingen wilt maken, klik dan op "Terug".',
'config-install-step-failed' => 'Mislukt',
'config-install-extensions' => 'Inclusief uitbreidingen',
'config-install-database' => 'Database inrichten',
+ 'config-install-schema' => 'Het schema wordt aangemaakt',
'config-install-pg-schema-not-exist' => 'Het schema voor PostgreSQL bestaat niet',
'config-install-pg-schema-failed' => 'Het aanmaken van de tabellen is mislukt.
Zorg dat de gebruiker "$1" in het schema "$2" mag schrijven.',
@@ -8907,22 +11588,31 @@ Zorg dat de gebruiker "$1" in het schema "$2" mag schrijven.',
'config-install-pg-plpgsql' => 'Controle op de taal PL/pgSQL',
'config-pg-no-plpgsql' => 'U moet de taal PL/pgSQL installeren in de database $1',
'config-pg-no-create-privs' => 'De gebruiker die u hebt opgegeven door de installatie heeft niet voldoende rechten om een gebruiker aan te maken.',
+ 'config-pg-not-in-role' => 'De gebruiker die u hebt opgegeven voor de webgebruiker bestaat al.
+De gebruiker die u hebt opgegeven voor installatie is geen superuser en geen lid van de rol van de webgebruiker, en kan het dus geen objecten aanmaken die van de webgebruiker zijn.
+
+MediaWiki vereist momenteel dat de tabellen van de webgebruiker zijn. Geef een andere webgebruikersnaam op, of klik op "terug" en geef een gebruiker op die voldoende installatierechten heeft.',
'config-install-user' => 'Databasegebruiker aan het aanmaken',
'config-install-user-alreadyexists' => 'Gebruiker "$1" bestaat al',
'config-install-user-create-failed' => 'Het aanmaken van de gebruiker "$1" is mislukt: $2',
'config-install-user-grant-failed' => 'Het geven van rechten aan gebruiker "$1" is mislukt: $2',
+ 'config-install-user-missing' => 'De opgegeven gebruiker "$1" bestaat niet.',
+ 'config-install-user-missing-create' => 'De opgegeven gebruiker "$1" bestaat niet.
+Klik op "registreren" onderaan als u het wilt aanmaken.',
'config-install-tables' => 'Tabellen aanmaken',
- 'config-install-tables-exist' => "'''Waarschuwing''': de MediaWiki-tabellen lijken al te bestaan.
+ '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.
+ '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-keys' => 'Bezig met aanmaken van geheime sleutels',
+ 'config-insecure-keys' => "'''Waarschuwing:''' De {{PLURAL:$2|sleutel die is aangemaakt|sleutels die zijn aangemaakt}} ($1) tijdens de installatie {{PLURAL:$2|is|zijn}} niet volledig veilig. Overweeg deze handmatig te wijzigen.",
'config-install-sysop' => 'Gebruiker voor beheerder aanmaken',
- 'config-install-subscribe-fail' => 'Het is niet mogelijk te abonneren op mediawiki-announce',
+ 'config-install-subscribe-fail' => 'Het is niet mogelijk te abonneren op mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'cURL is niet geïnstalleerd en <code>allow_url_fopen</code> is niet beschikbaar.',
'config-install-mainpage' => 'Hoofdpagina aanmaken met standaard inhoud',
'config-install-extension-tables' => 'Tabellen voor ingeschakelde uitbreidingen worden aangemaakt',
'config-install-mainpage-failed' => 'Het was niet mogelijk de hoofdpagina in te voegen: $1',
@@ -8944,9 +11634,18 @@ $3
Na het plaatsen van het bestand met instellingen kunt u '''[$2 uw wiki betreden]'''.",
'config-download-localsettings' => 'LocalSettings.php downloaden',
'config-help' => 'hulp',
+ 'mainpagetext' => "'''De installatie van MediaWiki is geslaagd.'''",
+ 'mainpagedocfooter' => 'Raadpleeg de [//meta.wikimedia.org/wiki/NL_Help:Inhoudsopgave handleiding] voor informatie over het gebruik van de wikisoftware.
+
+== Meer hulp over MediaWiki ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lijst met instellingen]
+* [//www.mediawiki.org/wiki/Manual:FAQ Veelgestelde vragen (FAQ)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailinglijst voor aankondigingen van nieuwe versies]',
);
/** Norwegian Nynorsk (‪Norsk (nynorsk)‬)
+ * @author Harald Khan
* @author Nghtwlkr
*/
$messages['nn'] = array(
@@ -8959,7 +11658,7 @@ $messages['nn'] = array(
'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-xcache' => '[http://xcache.lighttpd.net/ 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',
@@ -8987,9 +11686,17 @@ Berre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).',
'config-postgres-old' => 'PostgreSQL $1 eller seinare krevst, du har $2.',
'config-email-settings' => 'E-postinnstillingar',
'config-logo' => 'Logo-URL:',
+ 'mainpagetext' => "'''MediaWiki er no installert.'''",
+ 'mainpagedocfooter' => 'Sjå [//meta.wikimedia.org/wiki/Help:Contents brukarmanualen] for informasjon om bruk og oppsettshjelp for wikiprogramvara.
+
+==Kome i gang==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste over oppsettsinnstillingar]
+* [//www.mediawiki.org/wiki/Manual:FAQ Spørsmål og svar om MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postliste med informasjon om nye MediaWiki-versjonar]',
);
/** Norwegian (bokmål)‬ (‪Norsk (bokmål)‬)
+ * @author Event
* @author Jon Harald Søby
* @author Nghtwlkr
*/
@@ -8997,9 +11704,23 @@ $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-localsettings-upgrade' => 'En <code>LocalSettings.php</code>-fil har blitt oppdaget.
+For å oppgradere denne installasjonen, skriv inn verdien av <code>$wgUpgradeKey</code> i boksen nedenfor.
+Du finner denne i LocalSettings.php.',
+ 'config-localsettings-cli-upgrade' => "Filen ''LocalSettings.php'' er funnet.
+For å oppgradere denne installasjonen, vennligst kjør ''update.php'' i stedet",
+ 'config-localsettings-key' => 'Oppgraderingsnøkkel:',
+ 'config-localsettings-badkey' => 'Nøkkelen du oppga er feil.',
+ 'config-upgrade-key-missing' => "En eksisterende installasjon av MediaWiki er funnet.
+For å oppgradere denne installasjonen, vær vennlig å legge til følgende linje helt til slutt i din ''LocalSettings.php''-fil:
+
+$1",
+ 'config-localsettings-incomplete' => "Den eksisterende ''LocalSettings.php'' ser ut til å være ufullstendig.
+Variabelen $1 har ingen verdi.
+Vær vennlig å endre ''LocalSettings.php'' slik at variabelen får en verdi, og klikk ''Fortsett''.",
+ 'config-localsettings-connection-error' => "Det ble funnet en feil ved tilknytning av databasen med innstillingene i ''LocalSettings.php'' eller ''AdminSettings.php''. Vær vennlig å rette opp disse innstillingene og prøv igjen.
+
+$1",
'config-session-error' => 'Feil under oppstart av økt: $1',
'config-session-expired' => 'Dine øktdata ser ut til å ha utløpt.
Økter er konfigurert for en levetid på $1.
@@ -9027,6 +11748,7 @@ Sjekk din php.ini og sørg for at <code>session.save_path</code> er satt til en
'config-page-releasenotes' => 'Utgivelsesnotat',
'config-page-copying' => 'Kopiering',
'config-page-upgradedoc' => 'Oppgradering',
+ 'config-page-existingwiki' => 'Eksisterende wiki',
'config-help-restart' => 'Ønsker du å fjerne alle lagrede data som du har skrevet inn og starte installasjonsprosessen på nytt?',
'config-restart' => 'Ja, start på nytt',
'config-welcome' => '=== Miljøsjekker ===
@@ -9042,29 +11764,35 @@ Dette programmet er distribuert i håp om at det vil være nyttig, men '''uten n
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-sidebar' => '* [//www.mediawiki.org MediaWiki hjem]
+* [//www.mediawiki.org/wiki/Help:Contents Brukerguide]
+* [//www.mediawiki.org/wiki/Manual:Contents Administratorguide]
+* [//www.mediawiki.org/wiki/Manual:FAQ OSS]
+----
+* <doclink href=Readme>Les meg</doclink>
+* <doclink href=ReleaseNotes>Utgivelsesnotater</doclink>
+* <doclink href=Copying>Kopiering</doclink>
+* <doclink href=UpgradeDoc>Oppgradering</doclink>',
'config-env-good' => 'Miljøet har blitt sjekket.
Du kan installere MediaWiki.',
'config-env-bad' => 'Miljøet har blitt sjekket.
Du kan installere MediaWiki.',
'config-env-php' => 'PHP $1 er innstallert.',
+ 'config-env-php-toolow' => 'PHP $1 er installert.
+MediaWiki krever imidlertid PHP $2 eller høyere.',
'config-unicode-using-utf8' => 'Bruker Brion Vibbers utf8_normalize.so for Unicode-normalisering.',
'config-unicode-using-intl' => 'Bruker [http://pecl.php.net/intl intl PECL-utvidelsen] for Unicode-normalisering.',
- 'config-unicode-pure-php-warning' => "'''Advarsel''': [http://pecl.php.net/intl intl PECL-utvidelsen] er ikke tilgjengelig for å håndtere Unicode-normaliseringen.
-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-pure-php-warning' => "'''Advarsel''': [http://pecl.php.net/intl intl PECL-utvidelsen] er ikke tilgjengelig for å håndtere Unicode-normaliseringen, faller tilbake til en langsommere ren-PHP-implementasjon.
+Om du kjører et nettsted med høy trafikk bør du lese litt om [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-normalisering].",
'config-unicode-update-warning' => "'''Advarsel''': Den installerte versjonen av Unicode-normalisereren bruker en eldre versjon av [http://site.icu-project.org/ ICU-prosjektets] bibliotek.
-Du bør [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.
+Du bør [//www.mediawiki.org/wiki/Unicode_normalization_considerations oppgradere] om du er bekymret for å bruke Unicode.",
+ 'config-no-db' => 'Fant ikke en passende databasedriver! Du må installere en databasedriver for PHP.
+Følgende databasetyper er støttet: $1
-Om du er på delt tjener, spør din tjenerleverandør om å installere en passende databasedriver.
+Om du er på delt vertsskap, spør din vertsleverandør om å installere en passende databasedriver.
Om du kompilerte PHP selv, rekonfigirer den med en aktivert databaseklient, for eksempel ved å bruke <code>./configure --with-mysql</code>.
-Om du installerte PHP fra en Debian eller Ubuntu-pakke må du også installere modulen php5-mysql.',
- 'config-no-fts3' => "'''Advarsel''': SQLite er kompilert uten [http://sqlite.org/fts3.html FTS3-modulen], søkefunksjoner vil ikke være tilgjengelig på dette bakstykket.",
+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 [//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.",
@@ -9087,16 +11815,20 @@ MediaWiki krever funksjonene i denne modulen og vil ikke virke i denne konfigura
Hvis du kjører Mandrak, installer pakken php-xml.',
'config-pcre' => 'PCRE-støttemodulen ser ut til å mangle.
MediaWiki krever funksjonene for de Perl-kompatible regulære uttrykkene for å virke.',
+ 'config-pcre-no-utf8' => "'''Fatal''': PHPs PCRE modul ser ut til å være kompilert uten PCRE_UTF8-støtte.
+MediaWiki krever UTF-8-støtte for å fungere riktig.",
'config-memory-raised' => 'PHPs <code>memory_limit</code> er $1, økt til $2.',
'config-memory-bad' => "'''Advarsel:''' PHPs <code>memory_limit</code> er $1.
Dette er sannsynligvis for lavt.
Installasjonen kan mislykkes!",
- 'config-xcache' => '[http://trac.lighttpd.net/xcache/ XCache] er innstallert',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ 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].
+ 'config-no-cache' => "'''Advarsel:''' Kunne ikke finne [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].
Objekthurtiglagring er ikke aktivert.",
+ 'config-mod-security' => "'''Advarsel''': Din web-tjener har [http://modsecurity.org/ mod_security] påslått. Hvis denne er feilinnstilt, kan det gi problemer for MediaWiki eller annen programvare som tillater brukere å poste vilkårlig innhold.
+Sjekk [http://modsecurity.org/documentation/ mod_security-dokumentasjonen] eller ta kontakt med din nettleverandør hvis du opplever tilfeldige feil.",
'config-diff3-bad' => 'GNU diff3 ikke funnet.',
'config-imagemagick' => 'Fant ImageMagick: <code>$1</code>.
Bildeminiatyrisering vil aktiveres om du aktiverer opplastinger.',
@@ -9106,8 +11838,19 @@ Bildeminiatyrisering vil aktiveres om du aktiverer opplastinger.',
Bildeminiatyrisering vil være deaktivert.',
'config-no-uri' => "'''Feil:''' Kunne ikke bestemme gjeldende URI.
Installasjon avbrutt.",
+ 'config-no-cli-uri' => "'''Advarsel''': Ingen --scriptpath er angitt; bruker standard: <code>$1</code>.",
+ 'config-using-server' => 'Bruker servernavnet "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'Bruker server-URL "<nowiki>$1$2</nowiki>".',
'config-uploads-not-safe' => "'''Advarsel:''' Din standardmappe for opplastinger <code>$1</code> er sårbar for kjøring av vilkårlige skript.
-Selv om MediaWiki sjekker alle opplastede filer for sikkerhetstrusler er det sterkt anbefalt å [http://www.mediawiki.org/wiki/Manual:Security#Upload_security lukke denne sikkerhetssårbarheten] før du aktiverer opplastinger.",
+Selv om MediaWiki sjekker alle opplastede filer for sikkerhetstrusler er det sterkt anbefalt å [//www.mediawiki.org/wiki/Manual:Security#Upload_security lukke denne sikkerhetssårbarheten] før du aktiverer opplastinger.",
+ 'config-no-cli-uploads-check' => "'''Advarsel:''' Din standard-katalog for opplastinger (<code>$1</code>) er ikke kontrollert for sårbarhet overfor vilkårlig skript-kjøring under CLI-installasjonen.",
+ 'config-brokenlibxml' => 'Ditt system har en kombinasjon av PHP- og libxml2-versjoner som er feilaktige og kan forårsake skjult dataødeleggelse i MediaWiki og andre web-applikasjoner.
+Oppgrader til PHP 5.2.9 eller nyere og libxml 2 2.7.3 eller nyere ([//bugs.php.net/bug.php?id=45996 Feil-liste for PHP]).
+Installasjon abortert.',
+ 'config-using531' => 'MediaWiki kan ikke brukes med PHP $1 på grunn av en feil med referanseparametere til <code>__call()</code>.
+Oppgrader til PHP 5.3.2 eller høyere, eller nedgrader til PHP 5.3.0 for å løse dette.
+Installasjonen avbrutt.',
+ 'config-suhosin-max-value-length' => 'Suhosin er installert og begrenser GET-parameterlengder til $1 bytes. MediaWiki\'s ResourceLoader-komponent klarer å komme rundt denne begrensningen, med med redusert ytelse. På mulig bør du sette suhosin.get.max_value_length til minst 1024 i php.ini, og sette $wgResourceLoaderMaxQueryLength til samme verdi i LocalSettings.php.',
'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.
@@ -9120,13 +11863,19 @@ Hvis du installerer på en Windowstjener og bruker MySQL kan det hende at «loca
'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.
+Det bør ikke inneholde mellomrom.
Hvis du bruker en delt nettvert vil verten din enten gi deg et spesifikt databasenavn å bruke, eller la deg opprette databaser via kontrollpanelet.',
'config-db-name-oracle' => 'Databaseskjema:',
'config-db-install-account' => 'Brukerkonto for installasjon',
'config-db-username' => 'Databasebrukernavn:',
'config-db-password' => 'Databasepassord:',
+ 'config-db-password-empty' => 'Skriv inn et passord for den nye databasebrukeren: $1.
+Det er mulig å opprette brukere uten passord, men dette er ikke sikkert.',
+ 'config-db-install-username' => 'Skriv inn brukernavnet som vil bli brukt til å koble til databasen under installasjonsprosessen.
+Dette er ikke brukernavnet på MediaWiki-kontoen; dette er brukernavnet for databasen din.',
+ 'config-db-install-password' => 'Skriv inn passordet som vil bli brukt til å koble til databasen under installasjonsprosessen.
+Dette er ikke passordet på MediaWiki-kontoen; dette er passordet for databasen din.',
'config-db-install-help' => 'Skriv inn brukernavnet og passordet som vil bli brukt for å koble til databasen under installasjonsprosessen.',
'config-db-account-lock' => 'Bruk det samme brukernavnet og passordet under normal drift',
'config-db-wiki-account' => 'Brukerkonto for normal drift',
@@ -9134,7 +11883,7 @@ Hvis du bruker en delt nettvert vil verten din enten gi deg et spesifikt databas
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.
+Ikke bruk mellomrom.
Dette feltet er vanligvis tomt.',
'config-db-charset' => 'Databasetegnsett',
@@ -9146,12 +11895,12 @@ Dette feltet er vanligvis tomt.',
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]».",
+men det vil ikke la deg lagre tegn over «[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
'config-mysql-old' => 'MySQL $1 eller senere kreves, du har $2.',
'config-db-port' => 'Databaseport:',
'config-db-schema' => 'Skjema for MediaWiki',
- 'config-db-schema-help' => 'Ovennevnte skjema er som regel riktig.
-Bare endre dem hvis du vet at du trenger det.',
+ 'config-db-schema-help' => 'Dette skjemaet er som regel riktig.
+Bare endre det hvis du vet at du trenger det.',
'config-sqlite-dir' => 'SQLite datamappe:',
'config-sqlite-dir-help' => "SQLite lagrer alle data i en enkelt fil.
@@ -9168,33 +11917,39 @@ Vurder å plassere databasen et helt annet sted, for eksempel i <code>/var/lib/m
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'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-postgres' => '* $1 er et populært åpen kildekode-databasesystem som er et alternativ til MySQL ([http://www.php.net/manual/en/pgsql.installation.php hvordan kompilere PHP med PostgreSQL-støtte]). Det kan være noen små utestående feil og det anbefales ikke for bruk i et produksjonsmiljø.',
'config-support-sqlite' => '* $1 er et lettvekts-databasesystem som er veldig godt støttet. ([http://www.php.net/manual/en/pdo.installation.php hvordan kompilere PHP med SQLite-støtte], bruker PDO)',
'config-support-oracle' => '* $1 er en kommersiell bedriftsdatabase. ([http://www.php.net/manual/en/oci8.installation.php Hvordan kompilere PHP med OCI8-støtte])',
+ 'config-support-ibm_db2' => '* $1 er en kommersiell bedriftsdatabase.',
'config-header-mysql' => 'MySQL-innstillinger',
'config-header-postgres' => 'PostgreSQL-innstillinger',
'config-header-sqlite' => 'SQLite-innstillinger',
'config-header-oracle' => 'Oracle-innstillinger',
+ 'config-header-ibm_db2' => 'IBM DB2-innstillinger',
'config-invalid-db-type' => 'Ugyldig databasetype',
'config-missing-db-name' => 'Du må skrive inn en verdi for «Databasenavn»',
+ 'config-missing-db-host' => 'Du må skrive inn en verdi for «Databasevert»',
'config-missing-db-server-oracle' => 'Du må skrive inn en verdi for «Database TNS»',
'config-invalid-db-server-oracle' => 'Ugyldig database-TNS «$1».
Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_) og punktum (.).',
'config-invalid-db-name' => 'Ugyldig databasenavn «$1».
-Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_).',
+Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9), undestreker (_) og bindestreker (-).',
'config-invalid-db-prefix' => 'Ugyldig databaseprefiks «$1».
-Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_).',
+Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9), undestreker (_) og bindestreker (-).',
'config-connection-error' => '$1.
Sjekk verten, brukernavnet og passordet nedenfor og prøv igjen.',
'config-invalid-schema' => 'Ugyldig skjema for MediaWiki «$1».
Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_).',
+ 'config-db-sys-create-oracle' => 'Installasjonsprogrammet støtter kun bruk av en SYSDBA-konto for opprettelse av en ny konto.',
+ 'config-db-sys-user-exists-oracle' => 'Brukerkontoen «$1» finnes allerede. SYSDBA kan kun brukes for oppretting av nye kontoer!',
'config-postgres-old' => 'PostgreSQL $1 eller senere kreves, du har $2.',
'config-sqlite-name-help' => 'Velg et navn som identifiserer wikien din.
Ikke bruk mellomrom eller bindestreker.
@@ -9236,6 +11991,9 @@ Du kan nå [$1 begynne å bruke wikien din].
Hvis du ønsker å regenerere <code>LocalSettings.php</code>-filen din, klikk på knappen nedenfor.
Dette er '''ikke anbefalt''' med mindre du har problemer med wikien din.",
+ 'config-upgrade-done-no-regenerate' => 'Oppgradering fullført.
+
+Du kan nå [$1 begynne å bruke wikien din].',
'config-regenerate' => 'Regenerer LocalSettings.php →',
'config-show-table-status' => 'SHOW TABLE STATUS etterspørselen mislyktes!',
'config-unknown-collation' => "'''Advarsel:''' Databasen bruker en ukjent sortering.",
@@ -9259,7 +12017,8 @@ MyISAM-databaser har en tendens til å bli ødelagt oftere enn InnoDB-databaser.
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]».",
+men det vil ikke la deg lagre tegn over «[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
+ 'config-ibm_db2-low-db-pagesize' => "DB2-databasen din har et standard tabellområde med en utilstrekkelig pagesize. Pagesize må være '''32K''' eller større.",
'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.',
@@ -9273,6 +12032,8 @@ Alle sidetitler i dette navnerommet starter med et gitt prefiks som du kan angi
Tradisjonelt er dette prefikset avledet fra navnet på wikien, men det kan ikke innholde punkttegn som «#» eller «:».",
'config-ns-invalid' => 'Det angitte navnerommet «<nowiki>$1</nowiki>» er ugyldig.
Angi et annet prosjektnavnerom.',
+ 'config-ns-conflict' => 'Det angitte navnerommet «<nowiki>$1</nowiki>» er i konflikt med et standard MediaWiki-navnerom.
+Angi et annet prosjekt-navnerom.',
'config-admin-box' => 'Administratorkonto',
'config-admin-name' => 'Ditt navn:',
'config-admin-password' => 'Passord:',
@@ -9286,9 +12047,10 @@ Angi et annet brukernavn.',
'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-email-help' => 'Skriv inn en e-postadresse her for at du skal kunne motta e-post fra andre brukere på wikien, tilbakestille passordet ditt, og bli varslet om endringer på sider på overvåkningslisten din. Du kan la dette feltet stå tomt.',
'config-admin-error-user' => 'Intern feil ved opprettelse av en admin med navnet «<nowiki>$1</nowiki>».',
'config-admin-error-password' => 'Intern feil ved opprettelse av passord for admin «<nowiki>$1</nowiki>»: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Du har skrevet inn en ugyldig e-postadresse.',
'config-subscribe' => 'Abonner på [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-postlisten for utgivelsesannonseringer].',
'config-subscribe-help' => 'Dette er en lav-volums e-postliste brukt til utgivelsesannonseringer, herunder viktige sikkerhetsannonseringer.
Du bør abonnere på den og oppdatere MediaWikiinstallasjonen din når nye versjoner kommer ut.',
@@ -9313,12 +12075,11 @@ En wiki med '''{{int:config-profile-no-anon}}''' tilbyr ekstra ansvarlighet, men
'''{{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].",
+Mer komplekse konfigurasjoner av brukerrettigheter er tilgjengelig etter installasjon, se det [//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-cc-0' => 'Creative Commons Zero',
'config-license-pd' => 'Offentlig rom',
'config-license-cc-choose' => 'Velg en egendefinert Creative Commons-lisens',
'config-email-settings' => 'E-postinnstillinger',
@@ -9342,7 +12103,7 @@ 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 mer informasjon, les [//www.mediawiki.org/wiki/Manual:Security sikkerhetsseksjonen] i manualen.
For å aktivere filopplastinger, endre modusen i <code>images</code>-undermappen i MediaWikis rotmappe slik at nettjeneren kan skrive til den.
Aktiver så dette alternativet.',
@@ -9355,8 +12116,8 @@ 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.
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] er en funksjon som gjør det mulig for wikier å bruke bilder, lyder og andre media funnet på nettstedet [//commons.wikimedia.org/ Wikimedia Commons].
+For å gjøre dette krever MediaWiki tilgang til internett.
For mer informasjon om denne funksjonen, inklusive instruksjoner om hvordan man setter opp dette for andre wikier enn Wikimedia Commons, konsulter [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos manualen].',
'config-cc-again' => 'Velg igjen...',
@@ -9368,12 +12129,79 @@ For mer informasjon om denne funksjonen, inklusive instruksjoner om hvordan man
'config-install-extensions' => 'Inkludert utvidelser',
'config-install-database' => 'Setter opp database',
'config-install-user' => 'Oppretter databasebruker',
+ 'config-install-user-alreadyexists' => 'Brukeren «$1» finnes allerede',
+ 'config-install-user-create-failed' => 'Opprettelse av brukeren «$1» mislyktes: $2',
+ 'config-install-user-grant-failed' => 'Å gi tillatelse til brukeren «$1» mislyktes: $2',
'config-install-tables' => 'Oppretter tabeller',
+ 'config-install-mainpage-failed' => 'Kunne ikke sette inn hovedside: $1',
+ 'config-help' => 'hjelp',
+ 'mainpagetext' => "'''MediaWiki-programvaren er nå installert.'''",
+ 'mainpagedocfooter' => 'Se [//meta.wikimedia.org/wiki/Help:Contents brukerveiledningen] for informasjon om hvordan du bruker wiki-programvaren.
+
+==Å starte==
+*[//www.mediawiki.org/wiki/Manual:Configuration_settings Oppsettsliste]
+*[//www.mediawiki.org/wiki/Manual:FAQ Ofte stilte spørsmål]
+*[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-postliste]',
+);
+
+/** Occitan (Occitan) */
+$messages['oc'] = array(
+ 'mainpagetext' => "'''MediaWiki es estat installat amb succès.'''",
+ 'mainpagedocfooter' => "Consultatz lo [//meta.wikimedia.org/wiki/Ajuda:Contengut Guida de l'utilizaire] per mai d'entresenhas sus l'utilizacion d'aqueste logicial.
+
+== Començar amb MediaWiki ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista dels paramètres de configuracion]
+* [//www.mediawiki.org/wiki/Manual:FAQ/fr FAQ MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussions de las parucions de MediaWiki]",
+);
+
+/** Ossetic (Ирон)
+ * @author Amikeco
+ */
+$messages['os'] = array(
+ 'config-page-language' => 'Æвзаг',
+ 'mainpagetext' => "'''Вики-скрипт «MediaWiki» æнтыстджынæй æвæрд æрцыд.'''",
+);
+
+/** Punjabi (ਪੰਜਾਬੀ) */
+$messages['pa'] = array(
+ 'mainpagetext' => "'''ਮੀਡਿਆਵਿਕਿ ਠੀਕ ਤਰ੍ਹਾਂ ਇੰਸਟਾਲ ਹੋ ਗਿਆ ਹੈ।'''",
+);
+
+/** Pampanga (Kapampangan) */
+$messages['pam'] = array(
+ 'mainpagetext' => "'''Melaus ing pamipalyari ning MediaWiki.'''",
+ 'mainpagedocfooter' => "Basan me ing [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para king impormasiun keng pamangamit ning wiki software.
+
+== Pamagumpisa ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Picard (Picard) */
+$messages['pcd'] = array(
+ 'mainpagetext' => "'''MediaWiki o té instalé aveuc victoère.'''",
+);
+
+/** Deitsch (Deitsch)
+ * @author Xqt
+ */
+$messages['pdc'] = array(
+ 'mainpagedocfooter' => "Hilf fer's Yuuse unn Konfiguriere vun de Wiki-Software kansch finne im [//meta.wikimedia.org/wiki/Help:Contents Handbuch fer Yuuser].
+
+== Hilf zum Schtaerte ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lischt vun Gnepp zum Konfiguriere]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Eposchde-Lischt fer neie MediaWiki-Versione]",
);
/** Polish (Polski)
* @author Holek
* @author Sp5uhe
+ * @author Woytecr
*/
$messages['pl'] = array(
'config-desc' => 'Instalator MediaWiki',
@@ -9383,7 +12211,7 @@ $messages['pl'] = array(
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.',
+Aktualizację należy wykonać poprzez uruchomienie update.php',
'config-localsettings-key' => 'Klucz aktualizacji',
'config-localsettings-badkey' => 'Podany klucz jest nieprawidłowy',
'config-upgrade-key-missing' => 'Wykryto zainstalowane wcześniej MediaWiki.
@@ -9428,22 +12256,22 @@ Sprawdź plik php.ini i upewnij się, że <code>session.save_path</code> wskazuj
'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.
+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.
+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.
+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]
+ 'config-sidebar' => '* [//www.mediawiki.org Strona domowa MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Podręcznik użytkownika]
+* [//www.mediawiki.org/wiki/Manual:Contents Podręcznik administratora]
+* [//www.mediawiki.org/wiki/Manual:FAQ Odpowiedzi na często zadawane pytania]
----
* <doclink href=Readme>Przeczytaj to</doclink>
* <doclink href=ReleaseNotes>Informacje o tej wersji</doclink>
@@ -9454,21 +12282,21 @@ Możesz teraz zainstalować MediaWiki.',
'config-env-bad' => 'Środowisko oprogramowania zostało sprawdzone.
Nie możesz zainstalować MediaWiki.',
'config-env-php' => 'Zainstalowane jest PHP w wersji $1.',
+ 'config-env-php-toolow' => 'Zainstalowane jest PHP $1.
+Jednak MediaWiki wymaga PHP $2 lub nowszego.',
'config-unicode-using-utf8' => 'Korzystanie z normalizacji Unicode utf8_normalize.so napisanej przez Brion Vibbera.',
'config-unicode-using-intl' => 'Korzystanie z [http://pecl.php.net/intl rozszerzenia intl PECL] do normalizacji Unicode.',
'config-unicode-pure-php-warning' => "'''Uwaga!''' [http://pecl.php.net/intl Rozszerzenie intl PECL] do obsługi normalizacji Unicode nie jest dostępne. Użyta zostanie mało wydajna zwykła implementacja w PHP.
-Jeśli prowadzisz stronę o dużym natężeniu ruchu, powinieneś zapoznać się z informacjami o [http://www.mediawiki.org/wiki/Unicode_normalization_considerations normalizacji Unicode].",
+Jeśli prowadzisz stronę o dużym natężeniu ruchu, powinieneś zapoznać się z informacjami o [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalizacji Unicode].",
'config-unicode-update-warning' => "'''Uwaga''' – zainstalowana wersja normalizacji Unicode korzysta z nieaktualnej biblioteki [http://site.icu-project.org/ projektu ICU].
-Powinieneś [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>.
+Powinieneś [//www.mediawiki.org/wiki/Unicode_normalization_considerations zrobić aktualizację] jeśli chcesz korzystać w pełni z Unicode.",
+ 'config-no-db' => 'Nie można odnaleźć właściwego sterownika bazy danych! Musisz zainstalować sterownik bazy danych dla PHP.
+Można użyć następujących typów baz danych: $1.
+
+Jeżeli korzystasz ze współdzielonego hostingu, zwróć się do administratora o zainstalowanie odpowiedniego sterownika bazy danych.
+Jeśli skompilowałeś PHP samodzielnie, skonfiguruj je ponownie z włączonym klientem bazy danych, na przykład za pomocą polecenia <code>./configure --with-mysql</code>.
Jeśli zainstalowałeś PHP jako pakiet Debiana lub Ubuntu, musisz również zainstalować moduł php5-mysql.',
- 'config-no-fts3' => "'''Uwaga''' – SQLite został skompilowany bez [http://sqlite.org/fts3.html modułu FTS3] – funkcje wyszukiwania nie będą dostępne.",
+ 'config-no-fts3' => "'''Uwaga''' – SQLite został skompilowany bez [//sqlite.org/fts3.html modułu FTS3] – funkcje wyszukiwania nie będą dostępne.",
'config-register-globals' => "'''Uwaga – w konfiguracji PHP włączona jest opcja <code>[http://php.net/register_globals register_globals]</code>.'''
'''Jeśli możesz, wyłącz ją.'''
MediaWiki będzie działać, ale Twój serwer może być narażony potencjalnymi lukami w zabezpieczeniach.",
@@ -9494,14 +12322,14 @@ MediaWiki do pracy wymaga funkcji obsługi wyrażeń regularnych kompatybilnej z
'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.
+ '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].
+ 'config-no-cache' => "'''Uwaga:''' Nie można odnaleźć [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ 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>.
@@ -9512,13 +12340,14 @@ Miniatury grafik będą generowane jeśli włączysz przesyłanie plików.',
Tworzenie miniatur grafik będzie wyłączone.',
'config-no-uri' => "'''Błąd.''' Nie można określić aktualnego URI.
Instalacja została przerwana.",
+ 'config-using-server' => 'Przy użyciu nazwy serwera „<nowiki>$1</nowiki>“.',
'config-uploads-not-safe' => "'''Uwaga''' – domyślny katalog do którego zapisywane są przesyłane pliki <code>$1</code> jest podatny na wykonanie dowolnego skryptu.
-Chociaż MediaWiki sprawdza wszystkie przesłane pliki pod kątem bezpieczeństwa, zaleca się jednak, aby [http://www.mediawiki.org/wiki/Manual:Security#Upload_security zamknąć tę lukę w zabezpieczeniach] przed włączeniem przesyłania plików.",
+Chociaż MediaWiki sprawdza wszystkie przesłane pliki pod kątem bezpieczeństwa, zaleca się jednak, aby [//www.mediawiki.org/wiki/Manual:Security#Upload_security zamknąć tę lukę w zabezpieczeniach] przed włączeniem przesyłania plików.",
'config-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]).
+Wykonaj aktualizację PHP do wersji 5.2.9 lub późniejszej oraz libxml2 do wersji 2.7.3 lub późniejszej ([//bugs.php.net/bug.php?id=45996 błąd w PHP]).
Instalacja została przerwana.',
- 'config-using531' => '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]).
+ 'config-using531' => 'MediaWiki nie może być używane z PHP $1 z powodu błędu dotyczącego referencyjnych argumentów funkcji <code>__call()</code>.
+Uaktualnij do PHP 5.3.2 lub nowszego. Możesz również cofnąć wersję do PHP 5.3.0, aby naprawić ten błąd.
Instalacja została przerwana.',
'config-db-type' => 'Typ bazy danych',
'config-db-host' => 'Adres serwera bazy danych',
@@ -9526,12 +12355,14 @@ Instalacja została przerwana.',
Jeśli korzystasz ze współdzielonego hostingu, operator serwera powinien podać Ci prawidłową nazwę serwera w swojej dokumentacji.
-Jeśli instalujesz oprogramowanie na serwerze Windowsowym i korzystasz z MySQL, użycie „localhost” może nie zadziałać jako nazwa hosta. Jeśli wystąpi ten problem użyj „127.0.0.1” jako lokalnego adresu IP.',
+Jeśli instalujesz oprogramowanie na serwerze Windowsowym i korzystasz z MySQL, użycie „localhost” może nie zadziałać jako nazwa hosta. Jeśli wystąpi ten problem użyj „127.0.0.1” jako lokalnego adresu IP.
+
+Jeżeli korzystasz z PostgreSQL, pozostaw to pole puste, aby połączyć się poprzez gniazdo Unix‐a.',
'config-db-host-oracle' => '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.
+ '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.',
@@ -9539,6 +12370,8 @@ Jeśli korzystasz ze współdzielonego hostingu, dostawca usługi hostingowej mo
'config-db-install-account' => 'Konto użytkownika dla instalatora',
'config-db-username' => 'Nazwa użytkownika bazy danych',
'config-db-password' => 'Hasło bazy danych',
+ 'config-db-password-empty' => 'Wprowadź hasło dla nowego użytkownika bazy danych: $1.
+Choć istnieje możliwość tworzenia użytkowników bez hasła, nie jest to bezpieczne.',
'config-db-install-username' => 'Wprowadź nazwę użytkownika, który będzie używany do łączenia się z bazą danych podczas procesu instalacji.
Nie jest to nazwa konta MediaWiki, a użytkownika bazy danych.',
'config-db-install-password' => 'Wprowadź hasło, które będzie wykorzystywane do łączenia się z bazą danych w procesie instalacji.
@@ -9556,22 +12389,26 @@ To nie jest hasło konta MediaWiki, lecz hasło do 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-pg-test-error' => "Nie można połączyć się z bazą danych''' $1 ''': $2",
'config-sqlite-dir' => 'Katalog danych SQLite',
'config-oracle-def-ts' => 'Domyślna przestrzeń tabel',
'config-oracle-temp-ts' => 'Przestrzeń tabel tymczasowych',
+ 'config-type-ibm_db2' => 'IBM DB2',
'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-postgres' => '* $1 jest popularnym systemem baz danych z otwartym kodem alternatywnym dla MySQL ([http://www.php.net/manual/en/pgsql.installation.php jak skompilować PHP ze wsparciem dla PostgreSQL]). Z powodu możliwości wystąpienia drobnych błędów, nie jest zalecane użycie go w środowisku produkcyjnym.',
'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-support-ibm_db2' => '* $1 jest komercyjną zaawansowaną bazą danych.',
'config-header-mysql' => 'Ustawienia MySQL',
'config-header-postgres' => 'Ustawienia PostgreSQL',
'config-header-sqlite' => 'Ustawienia SQLite',
'config-header-oracle' => 'Ustawienia Oracle',
+ 'config-header-ibm_db2' => 'ustawienia dla IBM DB2',
'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”',
@@ -9582,28 +12419,30 @@ Używaj wyłącznie liter ASCII (a-z, A-Z), cyfr (0-9), podkreślenia (_) i krop
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.
+ '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-db-sys-create-oracle' => 'Instalator może wykorzystać wyłącznie konto SYSDBA do tworzenia nowych kont użytkowników.',
+ 'config-db-sys-user-exists-oracle' => 'Konto użytkownika „$1“ już istnieje. SYSDBA można użyć tylko do utworzenia nowego konta!',
'config-postgres-old' => '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”.
+ '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.
+ '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.
+ '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.
+ 'config-upgrade-done-no-regenerate' => 'Aktualizacja zakończona.
Możesz wreszcie [$1 zacząć korzystać ze swojej wiki].',
'config-regenerate' => 'Ponowne generowanie LocalSettings.php →',
@@ -9613,7 +12452,7 @@ Możesz wreszcie [$1 zacząć korzystać ze swojej wiki].',
'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.
+ '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',
@@ -9635,7 +12474,7 @@ Podaj inną przestrzeń nazw projektu.',
'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”.
+ '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.
@@ -9644,7 +12483,7 @@ Podaj inną nazwę.',
'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-email-help' => 'Wpisz adres e‐mail, aby mieć możliwość odbierania e‐maili od innych użytkowników wiki, zresetowania hasła oraz otrzymywania powiadomień o zmianach na stronach z listy obserwowanych. Możesz pozostawić to pole niewypełnione.',
'config-admin-error-user' => 'Błąd wewnętrzny podczas tworzenia konta administratora o nazwie „<nowiki>$1</nowiki>”.',
'config-admin-error-password' => 'Wewnętrzny błąd podczas ustawiania hasła dla administratora „<nowiki>$1</nowiki>”: <pre>$2</pre>',
'config-admin-error-bademail' => 'Wpisałeś nieprawidłowy adres e‐mail',
@@ -9663,9 +12502,10 @@ Możesz pominąć pozostałe czynności konfiguracyjne i zainstalować 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' => 'Creative Commons – za podaniem autora',
'config-license-cc-by-nc-sa' => 'Creative Commons – za uznaniem autora, bez użycia komercyjnego, na tych samych zasadach',
- 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
- 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 lub późniejsza',
+ 'config-license-cc-0' => 'Creative Commons – zero (domena publiczna)',
+ 'config-license-gfdl' => 'GNU licencja wolnej dokumentacji 1.3 lub nowsza',
'config-license-pd' => 'Domena publiczna',
'config-license-cc-choose' => 'Wybierz własną licencję Creative Commons',
'config-email-settings' => 'Ustawienia e-maili',
@@ -9695,7 +12535,7 @@ Wpisz nazwę licencji ręcznie.',
'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.
+ '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',
@@ -9711,22 +12551,32 @@ 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-alreadyexists' => 'Konto użytkownika „$1“ już istnieje',
'config-install-user-grant-failed' => 'Przyznanie uprawnień użytkownikowi „$1” nie powiodło się – $2',
+ 'config-install-user-missing' => 'Nie istnieje konto użytkownika „$1“.',
'config-install-tables' => 'Tworzenie tabel',
- 'config-install-tables-exist' => "'''Uwaga''' – wygląda na to, że tabele MediaWiki już istnieją.
+ '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.
+ '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-keys' => 'Generowanie tajnych kluczy',
'config-install-sysop' => 'Tworzenie konta administratora',
- 'config-install-subscribe-fail' => 'Nie można zapisać na listę „mediawiki-announce“',
+ 'config-install-subscribe-fail' => 'Nie można zapisać na listę „mediawiki-announce“ – $1',
'config-install-mainpage' => 'Tworzenie strony głównej z domyślną zawartością',
+ 'config-install-extension-tables' => 'Tworzenie tabel dla aktywnych rozszerzeń',
'config-install-mainpage-failed' => 'Nie udało się wstawić strony głównej – $1',
'config-download-localsettings' => 'Pobierz LocalSettings.php',
'config-help' => 'pomoc',
+ 'mainpagetext' => "'''Instalacja MediaWiki powiodła się.'''",
+ 'mainpagedocfooter' => 'Zobacz [//meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika] w celu uzyskania informacji o działaniu oprogramowania wiki.
+
+== Na początek ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista ustawień konfiguracyjnych]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komunikaty o nowych wersjach MediaWiki]',
);
/** Piedmontese (Piemontèis)
@@ -9741,7 +12591,10 @@ $messages['pms'] = array(
'config-localsettings-upgrade' => "A l'é stàit trovà n'archivi <code>LocalSettings.php</code>.
Për agiorné cost'anstalassion, ch'a anserissa ël valor ëd <code>\$wgUpgradeKey</code> ant la casela sì-sota.
A la trovrà an LocalSetting.php.",
+ 'config-localsettings-cli-upgrade' => "N'archivi LocalSettings.php a l'é stàit trovà.
+Për agiorné sta instalassion, për piasì fà anvece giré update.php",
'config-localsettings-key' => "Ciav d'agiornament:",
+ 'config-localsettings-badkey' => "La ciav ch'it l'has dàit a l'é pa giusta.",
'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.
@@ -9783,10 +12636,15 @@ Cost-sì a l'é un programa lìber e a gràtis: a peul ridistribuilo e/o modific
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-sidebar' => "* [//www.mediawiki.org Intrada MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Guida dl'Utent]
+* [//www.mediawiki.org/wiki/Manual:Contents Guida dl'Aministrator]
+* [//www.mediawiki.org/wiki/Manual:FAQ Soens an ciamo]
+----
+* <doclink href=Readme>Ch'am lesa</doclink>
+* <doclink href=ReleaseNotes>Nòte ëd publicassion</doclink>
+* <doclink href=Copying>Còpia</doclink>
+* <doclink href=UpgradeDoc>Agiornament</doclink>",
'config-env-good' => "L'ambient a l'é stàit controlà.
It peule instalé MediaWiki.",
'config-env-bad' => "L'ambient a l'é stàit controlà.
@@ -9795,17 +12653,11 @@ It peule pa instalé MediaWiki.",
'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].",
+S'a gestiss un sit a àut tràfich, a dovrìa lese cheicòs an sla [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalisassion Unicode].",
'config-unicode-update-warning' => "'''Avis:''' La version instalà dlë spassiador ëd normalisassion Unicode a deuvra na version veja ëd la librarìa dël [http://site.icu-project.org/ proget ICU].
-A dovrìa fé n'[http://www.mediawiki.org/wiki/Unicode_normalization_considerations agiornament] s'a l'é anteressà a dovré Unicode.",
+A dovrìa fé n'[//www.mediawiki.org/wiki/Unicode_normalization_considerations agiornament] s'a l'é anteressà a dovré Unicode.",
'config-no-db' => 'Impossìbil 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-no-fts3' => "'''Avis''': SQLite a l'é compilà sensa ël mòdul [//sqlite.org/fts3.html FTS3], le funsion d'arserca a saran pa disponìbij su cost motor.",
'config-register-globals' => "'''Avis: L'opsion <code>[http://php.net/register_globals register_globals]</code> ëd PHP a l'é abilità.'''
'''Ch'a la disabìlita s'a peul.'''
MediaWiki a marcërà, ma sò servent a l'é espòst a 'd possìbij vunerabilità ëd sicurëssa.",
@@ -9832,11 +12684,11 @@ MediaWiki a l'ha da manca dle funsion d'espression regolar Perl-compatìbij për
'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-xcache' => "[http://xcache.lighttpd.net/ 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-no-cache' => "'''Avis:''' As treuva pa [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache]. Ël buté d'oget an memòria local a l'é pa abilità.",
'config-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.",
@@ -9847,7 +12699,7 @@ 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.",
+Bele che MediaWiki a contròla j'aspet ëd sicurëssa ëd tùit j'archivi carià, a l'é motobin arcomandà ëd [//www.mediawiki.org/wiki/Manual:Security#Upload_security saré ës përtus ëd sicurëssa] prima d'abilité ij cariament.",
'config-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.
@@ -9885,7 +12737,7 @@ Cost camp a l'é lassà normalment veuid.",
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].",
+An '''manera UTF-8''', MySQL a arconòss an che ansema ëd caràter a son ij sò dat, e a peul presenteje e convertije apropriatament, ma a-j lassrà pa memorisé ij caràter dzora al [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes pian multilenghe ëd base].",
'config-mysql-old' => "A-i é da manca ëd MySQL $1 o pi recent, chiel a l'ha $2.",
'config-db-port' => 'Porta dla base ëd dàit:',
'config-db-schema' => 'Schema për MediaWiki',
@@ -9992,7 +12844,7 @@ La base ëd dàit MyISAM a tira a corompse pi 'd soens che la base ëd dàit Inn
'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].",
+An '''manera UTF-8''', MySQL a conossrà an che ansem ëd caràter a son ij sò dat, e a peul presenteje e convertije apropriatament, ma a-j lassa pa memorisé ij caràter ëdzora al [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes pian multilenghìstich ëd base].",
'config-site-name' => 'Nòm ëd la wiki:',
'config-site-name-help' => "Sòn a comparirà ant la bara dël tìtol dël navigador e an vàire d'àutri pòst.",
'config-site-name-blank' => "Ch'a buta un nòm ëd sit.",
@@ -10046,13 +12898,11 @@ Na wiki con '''{{int:config-profile-no-anon}}''' a dà pì 'd contròl, ma a pe
Ë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].",
+Configurassion ëd drit d'utent pi complicà a son disponìbij apress l'instalassion, vëdde la [//www.mediawiki.org/wiki/Manual:User_rights pàgina a pòsta dël manual].",
'config-license' => "Drit d'autor e licensa",
'config-license-none' => 'Gnun-a licensa an nòta an bass',
'config-license-cc-by-sa' => 'Creative Commons atribussion an part uguaj',
'config-license-cc-by-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à.
@@ -10083,7 +12933,7 @@ Motobin ëd servent ëd pòsta a ciamo che almanch la part dël nòm ëd domini
'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.
+Per pi d'anformassion, ch'a lesa la [//www.mediawiki.org/wiki/Manual:Security session ëd sicurëssa] d'ës manual.
Për abilité ël cariament d'archivi, ch'a modìfica la manera dël sot-dossié dle <code>figure</code> sota al dossié rèis ëd MediaWiki an manera che ël servent dl'aragnà a peussa scrivlo.
Peui ch'a abìlita costa opsion.",
@@ -10096,8 +12946,8 @@ Ch'a dëscaria na figura ëd la dimension aproprià, e ch'a anserissa l'anliura
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à.
+ 'config-instantcommons-help' => "[//www.mediawiki.org/wiki/InstantCommons Instant Commons] a l'é na funsion ch'a përmët a le wiki ëd dovré dle figure, dij son e d'àutri mojen trovà an sël sit [//commons.wikimedia.org/ Wikimedia Commons].
+Për dovré sossì, MediaWiki a l'ha da manca dl'acess a la ragnà.
Për pi d'anformassion su sta funsion, comprèise j'istrussion ëd com ampostela për wiki diferente da Wikimedia Commons, ch'a consulta [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à.
@@ -10148,20 +12998,82 @@ A dovrà [$1 dëscarielo] e butelo ant la bas ëd l'instalassion ëd soa wiki (
'''Nòta''': S'a lo fa nen adess, cost archivi ëd configurassion generà a sarà pa disponìbil për chiel pi tard s'a chita l'instalassion sensa dëscarielo.
Quand che a l'é stàit fàit, a peul '''[$2 intré an soa wiki]'''.",
+ 'config-help' => 'agiut',
+ 'mainpagetext' => "'''MediaWiki a l'é staita anstalà a la përfession.'''",
+ 'mainpagedocfooter' => "Che a varda la [//meta.wikimedia.org/wiki/Help:Contents User's Guide] për avèj dj'anformassion ant sël coma dovré ël programa dla wiki.
+
+== Për anandiesse a travajé ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista dij paràmeter ëd configurassion]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki Chestion frequente]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista ëd discussion an sla distribussion ëd MediaWiki]",
+);
+
+/** Pontic (Ποντιακά)
+ * @author Sinopeus
+ */
+$messages['pnt'] = array(
+ 'mainpagetext' => "'''To λογισμικόν MediaWiki εθέκεν.'''",
+);
+
+/** Prussian (Prūsiskan)
+ * @author Nertiks
+ */
+$messages['prg'] = array(
+ 'mainpagetext' => "'''MediaWiki's instalaciōni izpalla.'''",
+ 'mainpagedocfooter' => 'Wīdais [//meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika] kāi gaūlai informaciōnei ezze wiki prōgramijas tērpausnan.
+
+== En pagaūseņu ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]',
);
/** Pashto (پښتو)
* @author Ahmed-Najib-Biabani-Ibrahimkhel
*/
$messages['ps'] = array(
+ 'config-information' => 'مالومات',
'config-your-language' => 'ستاسې ژبه:',
'config-wiki-language' => 'د ويکي ژبه:',
'config-page-language' => 'ژبه',
'config-page-welcome' => 'مېډياويکي ته ښه راغلاست!',
'config-page-name' => 'نوم',
+ 'config-page-options' => 'خوښنې',
'config-page-install' => 'لګول',
'config-page-complete' => 'بشپړ!',
'config-env-php' => 'د $1 PHP نصب شو.',
+ 'config-db-type' => 'د توکبنسټ ډول:',
+ 'config-db-host' => 'د توکبنسټ کوربه:',
+ 'config-db-host-oracle' => 'د توکبنسټ TNS:',
+ 'config-db-name' => 'د توکبنسټ نوم:',
+ 'config-db-username' => 'د توکبنسټ کارن-نوم:',
+ 'config-db-password' => 'د توکبنسټ پټنوم:',
+ 'config-header-mysql' => 'د MySQL امستنې',
+ 'config-header-postgres' => 'د PostgreSQL امستنې',
+ 'config-header-sqlite' => 'د SQLite امستنې',
+ 'config-header-oracle' => 'د اورېکل امستنې',
+ 'config-header-ibm_db2' => 'د IBM DB2 امستنې',
+ 'config-sqlite-readonly' => 'د <code>$1</code> دوتنه د ليکلو وړ نه ده.',
+ 'config-sqlite-cant-create-db' => 'د توکبنسټ دوتنه <code>$1</code> جوړه نه شوه.',
+ 'config-site-name' => 'د ويکي نوم:',
+ 'config-site-name-blank' => 'د وېبځي نوم وليکۍ.',
+ 'config-project-namespace' => 'د پروژې نوم-تشيال:',
+ 'config-ns-generic' => 'پروژه',
+ 'config-admin-box' => 'د پازوال ګڼون',
+ 'config-admin-name' => 'ستاسې نوم:',
+ 'config-admin-password' => 'پټنوم:',
+ 'config-admin-password-confirm' => 'پټنوم يو ځل بيا:',
+ 'config-admin-email' => 'برېښليک پته:',
+ 'config-profile-wiki' => 'دوديزه ويکي',
+ 'config-email-settings' => 'د برېښليک امستنې',
+ 'config-install-step-done' => 'ترسره شو',
+ 'mainpagetext' => "'''MediaWiki په برياليتوب سره نصب شو.'''",
+ 'mainpagedocfooter' => 'د ويکي ساوترې د کارولو د مالوماتو په اړه [//meta.wikimedia.org/wiki/Help:Contents د کارن لارښود] سره سلا وکړۍ.
+
+== پيلول ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings د امستنو د سازونې لړليک]
+* [//www.mediawiki.org/wiki/Manual:FAQ د ميډياويکي ډېرځليزې پوښتنې]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce د مېډياويکي د برېښليکونو لړليک]',
);
/** Portuguese (Português)
@@ -10189,7 +13101,7 @@ $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.
+ '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',
@@ -10225,7 +13137,7 @@ Verifique o seu php.ini e certifique-se de que em <code>session.save_path</code>
'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 ===
+ 'config-copyright' => "=== Direitos de autor e Condições de uso ===
$1
@@ -10234,11 +13146,11 @@ Este programa é software livre; pode redistribuí-lo e/ou modificá-lo nos term
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]
+Em conjunto com este programa deve ter recebido <doclink href=Copying>uma cópia da licença GNU General Public License</doclink>; se não a recebeu, peça-a por escrito a Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA ou [http://www.gnu.org/copyleft/gpl.html leia-a na internet].",
+ 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/pt Página principal do MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents/pt Ajuda]
+* [//www.mediawiki.org/wiki/Manual:Contents/pt Manual técnico]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
----
* <doclink href=Readme>Leia-me</doclink>
* <doclink href=ReleaseNotes>Notas de lançamento</doclink>
@@ -10249,22 +13161,20 @@ 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.
+ '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].",
+Se o seu site tem alto volume de tráfego, devia informar-se um pouco sobre a [//www.mediawiki.org/wiki/Unicode_normalization_considerations/pt normalização Unicode].",
'config-unicode-update-warning' => "'''Aviso''': A versão instalada do wrapper de normalização Unicode usa uma versão mais antiga da biblioteca do [http://site.icu-project.org/ 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.
+Devia [//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! Precisa de instalar um controlador para o PHP. São aceites os seguintes tipos de base 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 usa alojamento partilhado, peça ao fornecedor do alojamento para instalar um controlador apropriado.
+Se foi você quem compilou o PHP, reconfigure-o com um cliente de base de dados activado; por exemplo, usando <code>./configure --with-mysql</code>.
Se instalou o PHP a partir de um pacote Debian ou Ubuntu, então precisa de instalar também o módulo php5-mysql.",
- 'config-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-no-fts3' => "'''Aviso''': O SQLite foi compilado sem o módulo [//sqlite.org/fts3.html FTS3]; as funcionalidades de pesquisa não estarão disponíveis nesta instalação.",
'config-register-globals' => "'''Aviso: A opção <code>[http://php.net/register_globals register_globals]</code> do PHP está activada.'''
'''Desactive-a, se puder.'''
O MediaWiki funciona mesmo assim, mas o seu servidor está exposto a potenciais vulnerabilidades de segurança.",
@@ -10293,11 +13203,11 @@ O MediaWiki necessita do suporte UTF-8 para funcionar correctamente.",
'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-xcache' => '[http://xcache.lighttpd.net/ 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].
+ 'config-no-cache' => "'''Aviso:''' Não foram encontrados [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ 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>.
@@ -10308,14 +13218,19 @@ Se possibilitar uploads, a miniaturização de imagens será activada.',
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-no-cli-uri' => "'''Aviso''': Não foi especificado um --scriptpath; por omissão, será usado: <code>$1</code>.",
+ 'config-using-server' => 'Será usado o nome do servidor "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'Será usada a URL do servidor "<nowiki>$1$2</nowiki>".',
'config-uploads-not-safe' => "'''Aviso:''' O 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.",
+Embora o MediaWiki verifique a existência de ameaças de segurança em todos os ficheiros enviados, é altamente recomendado que [//www.mediawiki.org/wiki/Manual:Security#Upload_security vede esta vulnerabilidade de segurança] antes de possibilitar uploads.",
+ 'config-no-cli-uploads-check' => "'''Aviso:''' Durante a instalação da CLI (\"Call Level Interface\", a Interface ao Nível da Chamada de Execução), o directório por omissão para uploads, <code>\$1</code>, não é verificado para determinar se é vulnerável à execução de código arbitrário.",
'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]).
+Actualize para o PHP versão 5.2.9 ou posterior e libxml2 versão 2.7.3 ou posterior ([//bugs.php.net/bug.php?id=45996 incidência reportada no PHP]).
Instalação interrompida.',
'config-using531' => 'O MediaWiki não pode ser usado com o PHP $1 devido a um problema que envolve parâmetros de referência para <code>__call()</code>.
Para resolver este problema, actualize o PHP para a versão 5.3.2 ou posterior, ou reverta-o para a 5.3.0.
Instalação interrompida.',
+ 'config-suhosin-max-value-length' => 'O Suhosin está instalado e limita a $1 bytes o comprimento do parâmetro GET. O componente ResourceLoader do MediaWiki pode tornear este limite, mas prejudicando o desempenho. Se lhe for possível, deve atribuir o valor 1024 ou maior ao parâmetro suhosin.get.max_value_length no ficheiro php.ini, e definir o mesmo valor para $wgResourceLoaderMaxQueryLength no ficheiro LocalSettings.php.',
'config-db-type' => 'Tipo da base de dados:',
'config-db-host' => 'Servidor da base de dados:',
'config-db-host-help' => 'Se a base de dados estiver num servidor separado, introduza aqui o nome ou o endereço IP desse servidor.
@@ -10332,7 +13247,7 @@ 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:
+ '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.
@@ -10363,12 +13278,13 @@ Normalmente, este campo deve ficar vazio.',
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].",
+mas não lhe permitirá armazenar caracteres acima do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Básico].",
'config-mysql-old' => 'É necessário o MySQL $1 ou posterior; tem a versão $2.',
'config-db-port' => 'Porta da base de dados:',
'config-db-schema' => "Esquema ''(schema)'' do MediaWiki",
'config-db-schema-help' => 'Normalmente, este esquema ("schema") estará correcto.
Altere-o só se souber que precisa de o fazer.',
+ 'config-pg-test-error' => "Não foi possível criar uma ligação à base de dados '''$1''': $2",
'config-sqlite-dir' => 'Directório de dados do SQLite:',
'config-sqlite-dir-help' => "O SQLite armazena todos os dados num único ficheiro.
@@ -10386,19 +13302,22 @@ Considere colocar a base de dados num local completamente diferente, como, por e
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'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-postgres' => '* $1 é uma plataforma de base de dados comum, de fonte aberta, alternativa ao MySQL ([http://www.php.net/manual/en/pgsql.installation.php como compilar PHP com suporte PostgreSQL]). Poderão existir alguns pequenos problemas e não é recomendado o seu uso em ambientes de exploração/produção.',
'config-support-sqlite' => '* $1 é uma plataforma de base de dados ligeira muito bem suportada. ([http://www.php.net/manual/en/pdo.installation.php Como compilar PHP com suporte SQLite], usa PDO)',
'config-support-oracle' => '* $1 é uma base de dados de uma empresa comercial. ([http://www.php.net/manual/en/oci8.installation.php How to compile PHP with OCI8 support])',
+ 'config-support-ibm_db2' => '* $1 é uma base de dados empresarial.',
'config-header-mysql' => 'Definições MySQL',
'config-header-postgres' => 'Definições PostgreSQL',
'config-header-sqlite' => 'Definições SQLite',
'config-header-oracle' => 'Definições Oracle',
+ 'config-header-ibm_db2' => 'Configurações da IBM DB2',
'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"',
@@ -10472,6 +13391,13 @@ A conta que especificar aqui já tem de existir.',
'config-mysql-engine' => 'Motor de armazenamento:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''Aviso''': Seleccionou o MyISAM para motor de armazenamento do MySQL, uma combinação desaconselhada para usar com o MediaWiki porque:
+* praticamente não permite acessos simultâneos, devido aos bloqueios de tabelas
+* o MyISAM é mais susceptível a perdas da integridade dos dados do que outros motores
+* o código do MediaWiki não trabalha devidamente com o MyISAM
+
+Se a sua instalação do MySQL suporta InnoDB, é altamente recomendado que o escolha em vez do MyISAM.
+Se não suporta o InnoDB, talvez esta seja uma boa altura para fazer a actualização para a versão mais recente do MySQL.",
'config-mysql-engine-help' => "'''InnoDB''' é quase sempre a melhor opção, porque suporta bem acessos simultâneos ''(concurrency)''.
'''MyISAM''' pode ser mais rápido no modo de utilizador único ou em instalações somente para leitura.
@@ -10483,7 +13409,8 @@ As bases de dados MyISAM tendem a ficar corrompidas com maior frequência do que
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].",
+mas não lhe permitirá armazenar caracteres acima do [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Plano Multilingue Básico].",
+ 'config-ibm_db2-low-db-pagesize' => "A sua base de dados DB2 tem um tablespace padrão com um pagesize insuficiente. O pagesize tem de ser '''32K'' ou maior.",
'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.',
@@ -10519,6 +13446,8 @@ Introduza um nome de utilizador diferente.',
'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-subscribe-noemail' => 'Tentou subscrever a lista de divulgação dos anúncios de novas versões, sem fornecer um endereço de correio electrónico.
+Para subscrever esta lista de divulgação tem de fornecer um endereço de correio electrónico.',
'config-almost-done' => 'Está quase a terminar!
Agora pode saltar as configurações restantes e instalar já a wiki.',
'config-optional-continue' => 'Faz-me mais perguntas.',
@@ -10540,24 +13469,25 @@ Uma wiki com '''{{int:config-profile-no-anon}}''' atribui mais responsabilidade,
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].",
+Após a instalação, estarão disponíveis mais configurações de privilégios. Consulte [//www.mediawiki.org/wiki/Manual:User_rights a entrada relevante no Manual].",
'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-cc-by-sa' => 'Creative Commons - Atribuição - Partilha nos Mesmos Termos',
+ 'config-license-cc-by' => 'Creative Commons - Atribuição',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons - Atribuição - Uso Não Comercial - Partilha nos Mesmos Termos',
+ 'config-license-cc-0' => 'Creative Commons Zero (Domínio Público)',
+ 'config-license-gfdl' => 'GNU Free Documentation License 1.3 ou posterior',
'config-license-pd' => 'Domínio Público',
'config-license-cc-choose' => '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.
+Se pretende que seja possível usar textos da Wikipédia na sua wiki e que seja possível a Wikipédia aceitar textos copiados da sua wiki, deve escolher a licença Creative Commons - Atribuição - Partilha nos Mesmos Termos.
-A licença 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.',
+A licença anterior da Wikipédia era a licença GNU Free Documentation License.
+A GFDL é uma licença válida, mas de difícil compreensão.
+Também é difícil reutilizar conteúdos licenciados com a GFDL.',
'config-email-settings' => 'Definições do correio electrónico',
'config-enable-email' => '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.
@@ -10579,7 +13509,7 @@ Muitos servidores de correio electrónico exigem que pelo menos a parte do nome
'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 mais informações, leia a [//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.',
@@ -10587,13 +13517,13 @@ Depois active esta opção.',
'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.
+ 'config-logo-help' => 'O tema padrão do MediaWiki inclui espaço para um logótipo de 135x160 pixels acima do menu da barra lateral.
+Coloque na wiki uma imagem com estas dimensões e introduza aqui a URL dessa imagem.
Se não pretende usar um logótipo, deixe este campo em branco.',
'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.
+ 'config-instantcommons-help' => 'O [//www.mediawiki.org/wiki/InstantCommons Instant Commons] é uma funcionalidade que permite que as wikis usem imagens, áudio e outros ficheiros multimédia disponíveis no site [//commons.wikimedia.org/ Wikimedia Commons].
+Para poder usá-los, o MediaWiki necessita de acesso à internet.
Para mais informações sobre esta funcionalidade, incluindo instruções sobre como configurá-la para usar outras wikis em vez da Wikimedia Commons, consulte o [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.
@@ -10623,12 +13553,13 @@ Se não sabe qual é a porta, a predefinida é a 11211.',
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.
+ '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-schema' => "A criar o esquema (''schema'') da base de dados",
'config-install-pg-schema-not-exist' => "O esquema ''(schema)'' PostgreSQL não existe",
'config-install-pg-schema-failed' => 'A criação das tabelas falhou.
Certifique-se de que o utilizador "$1" pode escrever no esquema \'\'(schema)\'\' "$2".',
@@ -10636,10 +13567,17 @@ Certifique-se de que o utilizador "$1" pode escrever no esquema \'\'(schema)\'\'
'config-install-pg-plpgsql' => 'A verificar a presença da linguagem PL/pgSQL',
'config-pg-no-plpgsql' => 'É preciso instalar a linguagem PL/pgSQL na base de dados $1',
'config-pg-no-create-privs' => 'A conta que especificou para a instalação não tem privilégios suficientes para criar uma conta.',
+ 'config-pg-not-in-role' => 'A conta que especificou para o utilizador da internet já existe.
+A conta que especificou para a instalação não é a de um super-utilizador e não pertence ao grupo de utilizadores de acesso pela internet, por isso não pode criar objectos que pertencem ao utilizador da internet.
+
+O MediaWiki necessita que as tabelas pertençam ao utilizador da internet. Especifique outra conta de internet, ou clique "voltar" e especifique um utilizador com os privilégios necessários para a instalação.',
'config-install-user' => 'A criar o utilizador da base de dados',
'config-install-user-alreadyexists' => 'O utilizador "$1" já existe',
'config-install-user-create-failed' => 'A criação do utilizador "$1" falhou: $2',
'config-install-user-grant-failed' => 'A atribuição das permissões ao utilizador "$1" falhou: $2',
+ 'config-install-user-missing' => 'O utilizador especificado, "$1", não existe.',
+ 'config-install-user-missing-create' => 'O utilizador especificado, "$1", não existe.
+Marque a caixa de selecção "criar conta" abaixo se pretende criá-la, por favor.',
'config-install-tables' => 'A criar as tabelas',
'config-install-tables-exist' => "'''Aviso''': As tabelas do MediaWiki parecem já existir.
A criação das tabelas será saltada.",
@@ -10649,9 +13587,11 @@ A criação das tabelas será saltada.",
'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-keys' => 'A gerar as chaves secretas',
+ 'config-insecure-keys' => "'''Warning:''' {{PLURAL:$2|A chave segura|As chaves seguras}} ($1) {{PLURAL:$2|gerada durante a instalação não é completamente segura|geradas durante a instalação não são completamente seguras}}. Considere a possibilidade de {{PLURAL:$2|alterá-la|alterá-las}} manualmente.",
'config-install-sysop' => 'A criar a conta de administrador',
- 'config-install-subscribe-fail' => 'Não foi possível subscrever a lista mediawiki-announce',
+ 'config-install-subscribe-fail' => 'Não foi possível subscrever a lista mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'cURL não está instalado e allow_url_fopen não está disponível.',
'config-install-mainpage' => 'A criar a página principal com o conteúdo padrão.',
'config-install-extension-tables' => 'A criar as tabelas das extensões activadas',
'config-install-mainpage-failed' => 'Não foi possível inserir a página principal: $1',
@@ -10672,6 +13612,14 @@ $3
Depois de terminar o passo anterior, pode '''[$2 entrar na wiki]'''.",
'config-download-localsettings' => 'Download do LocalSettings.php',
'config-help' => 'ajuda',
+ 'mainpagetext' => "'''MediaWiki instalado com sucesso.'''",
+ 'mainpagedocfooter' => 'Consulte o [//meta.wikimedia.org/wiki/Help:Contents Guia de Utilizadores] para informações sobre o uso do software wiki.
+
+== Onde começar ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de opções de configuração]
+* [//www.mediawiki.org/wiki/Manual:FAQ Perguntas e respostas frequentes sobre o MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Subscreva a lista de divulgação de novas versões do MediaWiki]',
);
/** Brazilian Portuguese (Português do Brasil)
@@ -10736,10 +13684,10 @@ Este programa é distribuído na esperança de que seja útil, mas '''sem qualqu
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-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/pt Página principal do MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents/pt Ajuda]
+* [//www.mediawiki.org/wiki/Manual:Contents/pt Manual técnico]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]',
'config-env-good' => 'O ambiente foi verificado.
Você pode instalar o MediaWiki.',
'config-env-bad' => 'O ambiente foi verificado.
@@ -10748,15 +13696,9 @@ Você não pode instalar o MediaWiki.',
'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].",
+Se o seu site tem um alto volume de tráfego, devia informar-se um pouco sobre a [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalização Unicode].",
'config-no-db' => 'Não foi possível encontrar um driver de banco de dados adequado!',
- '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-no-fts3' => "' ' 'Aviso' ' ': O SQLite foi compilado sem o módulo [//sqlite.org/fts3.html FTS3]; as funcionalidades de pesquisa não estarão disponíveis nesta instalação.",
'config-register-globals' => "' ' 'Aviso: A opção <code>[http://php.net/register_globals register_globals]</code> do PHP está ativada.'''
' ' 'Desative-a, se puder.'''
O MediaWiki funcionará mesmo assim, mas o seu servidor ficará exposto a potenciais vulnerabilidades de segurança.",
@@ -10764,9 +13706,48 @@ O MediaWiki funcionará mesmo assim, mas o seu servidor ficará exposto a potenc
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.',
+ 'mainpagetext' => "'''MediaWiki instalado com sucesso.'''",
+ 'mainpagedocfooter' => 'Consulte o [//meta.wikimedia.org/wiki/Help:Contents Manual de Usuário] para informações de como usar o software wiki.
+
+== Começando ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista de opções de configuração]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ do MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussão com avisos de novas versões do MediaWiki]',
+);
+
+/** Quechua (Runa Simi) */
+$messages['qu'] = array(
+ 'mainpagetext' => "'''MediaWiki nisqa llamp'u kaqqa aypaylla takyachisqañam.'''",
+ 'mainpagedocfooter' => "Wiki llamp'u kaqmanta willasunaykipaqqa [//meta.wikimedia.org/wiki/Help:Contents Ruraqpaq yanapana] ''(User's Guide)'' sutiyuq p'anqata qhaway.
+
+== Qallarichkaspa ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Romagnol (Rumagnôl) */
+$messages['rgn'] = array(
+ 'mainpagetext' => "'''L'instalaziòn d'MediaWiki l'è andêda ben'''",
+);
+
+/** Romansh (Rumantsch)
+ * @author Gion-andri
+ */
+$messages['rm'] = array(
+ 'mainpagetext' => "'''MediaWiki è vegnì installà cun success.'''",
+ 'mainpagedocfooter' => "Consultai il [//meta.wikimedia.org/wiki/Help:Contents manual per utilisaders] per infurmaziuns davart l'utilisaziun da questa software da wiki.
+
+== Cumenzar ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Glista da las opziuns per la configuraziun]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Glista da mail da MediaWiki cun annunzias da novas versiuns]",
);
/** Romanian (Română)
+ * @author Minisarm
* @author Stelistcristi
*/
$messages['ro'] = array(
@@ -10796,12 +13777,36 @@ $messages['ro'] = array(
'config-missing-db-name' => 'Trebuie să introduci o valoare pentru „Numele bazei de date”',
'config-ns-generic' => 'Proiect',
'config-admin-password' => 'Parolă:',
+ 'mainpagetext' => "'''Programul Wiki a fost instalat cu succes.'''",
+ 'mainpagedocfooter' => 'Consultați [//meta.wikimedia.org/wiki/Help:Contents Ghidul utilizatorului (en)] pentru informații despre utilizarea software-ului wiki.
+
+== Primii pași ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista parametrilor configurabili (en)]
+* [//www.mediawiki.org/wiki/Manual:FAQ Întrebări frecvente despre MediaWiki (en)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discuții a MediaWiki (en)]',
+);
+
+/** Tarandíne (Tarandíne)
+ * @author Joetaras
+ */
+$messages['roa-tara'] = array(
+ 'config-help' => 'ajute',
+ 'mainpagetext' => "'''MediaUicchi ha state 'nstallete.'''",
+ 'mainpagedocfooter' => "Vè vide [//meta.wikimedia.org/wiki/Help:Contents User's Guide] pe l'mbormaziune sus a cumme s'ause 'u softuer wiki.
+
+== Pe accumenzà ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste pe le configuraziune]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ de MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Liste d'a poste de MediaWiki]",
);
/** Russian (Русский)
+ * @author Adata80
* @author DCamer
* @author Eleferen
+ * @author Kaganer
* @author Krinkle
+ * @author Lockal
* @author MaxSem
* @author Yuriy Apostol
* @author Александр Сигачёв
@@ -10811,7 +13816,7 @@ $messages['ru'] = array(
'config-desc' => 'Инсталлятор MediaWiki',
'config-title' => 'Установка MediaWiki $1',
'config-information' => 'Информация',
- 'config-localsettings-upgrade' => 'Обнаружен файл <code>LocalSettings.php</code>.
+ 'config-localsettings-upgrade' => 'Обнаружен файл <code>LocalSettings.php</code>.
Для обновления этой установки, пожалуйста, введите значение <code>$wgUpgradeKey</code>.
Его можно найти в файле LocalSettings.php.',
'config-localsettings-cli-upgrade' => 'Обнаружен файл LocalSettings.php.
@@ -10829,11 +13834,11 @@ $1',
$1',
'config-session-error' => 'Ошибка при запуске сессии: $1',
- 'config-session-expired' => 'Ваша сессия истекла.
-Сессии настроены на длительность $1.
-Вы её можете увеличить, изменив <code>session.gc_maxlifetime</code> в php.ini.
+ 'config-session-expired' => 'Ваша сессия истекла.
+Сессии настроены на длительность $1.
+Вы её можете увеличить, изменив <code>session.gc_maxlifetime</code> в php.ini.
Перезапустите процесс установки.',
- 'config-no-session' => 'Данные сессии потеряны!
+ 'config-no-session' => 'Данные сессии потеряны!
Проверьте ваш php.ini и убедитесь, что <code>session.save_path</code> установлен в соответствующий каталог.',
'config-your-language' => 'Ваш язык:',
'config-your-language-help' => 'Выберите язык, на котором будет происходить процесс установки.',
@@ -10861,7 +13866,7 @@ $1',
'config-welcome' => '=== Проверка окружения ===
Проводятся базовые проверки с целью определить, подходит ли данная система для установки MediaWiki.
Укажите результаты этих проверок при обращении за помощью с установкой.',
- 'config-copyright' => "=== Авторские права и условия ===
+ 'config-copyright' => "=== Авторские права и условия ===
$1
@@ -10870,10 +13875,10 @@ MediaWiki является свободным программным обесп
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]
+ 'config-sidebar' => '* [//www.mediawiki.org Сайт MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents/ru Справка для пользователей]
+* [//www.mediawiki.org/wiki/Manual:Contents/ru Справка для администраторов]
+* [//www.mediawiki.org/wiki/Manual:FAQ/ru FAQ]
----
* <doclink href=Readme>Readme-файл</doclink>
* <doclink href=ReleaseNotes>Информация о выпуске</doclink>
@@ -10881,24 +13886,23 @@ MediaWiki распространяется в надежде, что она бу
* <doclink href=UpgradeDoc>Обновление</doclink>',
'config-env-good' => 'Проверка внешней среды была успешно проведена.
Вы можете установить MediaWiki.',
- 'config-env-bad' => 'Была проведена проверка внешней среды.
+ '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-pure-php-warning' => "'''Внимание!''': [http://pecl.php.net/intl расширение intl из PECL] недоступно для нормализации Юникода, будет использоваться медленная реализация на чистом PHP.
+Если ваш сайт работает под высокой нагрузкой, вам следует больше узнать о [//www.mediawiki.org/wiki/Unicode_normalization_considerations нормализации Юникода].",
'config-unicode-update-warning' => "'''Предупреждение''': установленная версия обёртки нормализации Юникода использует старую версию библиотеки [http://site.icu-project.org/ проекта ICU].
-Вы должны [http://www.mediawiki.org/wiki/Unicode_normalization_considerations обновить версию], если хотите полноценно использовать Юникод.",
- 'config-no-db' => 'Не найдено поддержки баз данных!',
- 'config-no-db-help' => 'Вам необходимо установить драйвера базы данных для PHP.
-Поддерживаются следующие типы баз данных: $1.
+Вы должны [//www.mediawiki.org/wiki/Unicode_normalization_considerations обновить версию], если хотите полноценно использовать Юникод.",
+ 'config-no-db' => 'Не найдено поддержки баз данных! Вам необходимо установить драйвера базы данных для PHP.
+Поддерживаются следующие базы данных: $1.
-Если вы используете виртуальный хостинг, обратитесь к своему хостинг-провайдеру с просьбой установить подходящий драйвер базы данных.
-Если вы скомпилировали PHP сами, сконфигурируйте его снова с включенным клиентом базы данных, например, с помощью <code>./configure --with-mysql</code>.
+Если вы используете виртуальный хостинг, обратитесь к своему хостинг-провайдеру с просьбой установить подходящий драйвер базы данных.
+Если вы скомпилировали PHP сами, сконфигурируйте его снова с включенным клиентом базы данных, например, с помощью <code>./configure --with-mysql</code>.
Если вы установили PHP из пакетов Debian или Ubuntu, то вам также необходимо установить модуль php5-mysql.',
- 'config-no-fts3' => "'''Внимание''': SQLite собран без модуля [http://sqlite.org/fts3.html FTS3] — поиск не будет работать для этой базы данных.",
+ 'config-no-fts3' => "'''Внимание''': SQLite собран без модуля [//sqlite.org/fts3.html FTS3] — поиск не будет работать для этой базы данных.",
'config-register-globals' => "'''Внимание: PHP-опция <code>[http://php.net/register_globals register_globals]</code> включена.'''
'''Отключите её, если это возможно.'''
MediaWiki будет работать, но это снизит безопасность сервера и увеличит риск проникновения извне.",
@@ -10919,50 +13923,56 @@ MediaWiki будет работать, но это снизит безопасн
'config-xml-bad' => 'XML-модуль РНР отсутствует.
MediaWiki не будет работать в этой конфигурации, так как требуется функционал этого модуля.
Если вы работаете в Mandrake, установите PHP XML-пакет.',
- 'config-pcre' => 'Модуль поддержки PCRE не найден.
+ 'config-pcre' => 'Модуль поддержки PCRE не найден.
Для работы MediaWiki требуется поддержка Perl-совместимых регулярных выражений.',
- 'config-pcre-no-utf8' => "'''Фатальная ошибка'''. Модуль PCRE для PHP, похоже, собран без поддержки PCRE_UTF8.
+ '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-xcache' => '[http://xcache.lighttpd.net/ 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-no-cache' => "'''Внимание:''' Не найдены [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
Кэширование объектов будет отключено.",
+ 'config-mod-security' => "'''Внимание''': на вашем веб-сервере включен [http://modsecurity.org/ mod_security]. При неправильной настройке он может вызывать проблемы для MediaWiki или другого ПО, позволяющего пользователям отправлять на сервер произвольный текст.
+Обратитесь к [http://modsecurity.org/documentation/ документации mod_security] или в поддержку вашего хостера, если при работе возникают непонятные ошибки.",
'config-diff3-bad' => 'GNU diff3 не найден.',
'config-imagemagick' => 'Обнаружен ImageMagick: <code>$1</code>.
Возможно отображение миниатюр изображений, если вы разрешите закачки файлов.',
- 'config-gd' => 'Найдена встроенная графическая библиотека GD.
+ 'config-gd' => 'Найдена встроенная графическая библиотека GD.
Возможность использования миниатюр изображений будет включена, если вы включите их загрузку.',
'config-no-scaling' => 'Не удалось найти встроенную библиотеку GD или ImageMagick.
Возможность использования миниатюр изображений будет отключена.',
- 'config-no-uri' => "'''Ошибка:''' Не могу определить текущий URI.
+ '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-using-server' => 'Будет использовано имя сервера «<nowiki>$1</nowiki>».',
+ 'config-uploads-not-safe' => "'''Внимание:''' директория, используемая по умолчанию для загрузок (<code>$1</code>) уязвима к выполнению произвольных скриптов.
+Хотя MediaWiki проверяет все загружаемые файлы на наличие угроз, настоятельно рекомендуется [//www.mediawiki.org/wiki/Manual:Security#Upload_security закрыть данную уязвимость] перед включением загрузки файлов.",
+ 'config-brokenlibxml' => 'В вашей системе имеется сочетание версий PHP и libxml2, могущее привести к скрытым повреждениям данных в MediaWiki и других веб-приложениях.
+Обновите PHP до версии 5.2.9 или старше и libxml2 до 2.7.3 или старше ([//bugs.php.net/bug.php?id=45996 сведения об ошибке]).
Установка прервана.',
'config-using531' => 'PHP $1 не совместим с MediaWiki из-за ошибки с параметрами-ссылками при вызовах <code>__call()</code>.
Обновитесь до PHP 5.3.2 и выше, или откатитесь до PHP 5.3.0, чтобы избежать этой проблемы.
Установка прервана.',
+ 'config-suhosin-max-value-length' => 'Suhosin установлен и ограничивает длину параметра GET до $1 байт. Компонент MediaWiki ResourceLoader будет обходить это ограничение, но это снизит производительность. Если это возможно, следует установить suhosin.get.max_value_length 1024 или выше в php.ini, а также установить для $wgResourceLoaderMaxQueryLength такое же значение в LocalSettings.php.',
'config-db-type' => 'Тип базы данных:',
'config-db-host' => 'Хост базы данных:',
- 'config-db-host-help' => 'Если сервер базы данных находится на другом сервере, введите здесь его имя хоста или IP-адрес.
+ 'config-db-host-help' => 'Если сервер базы данных находится на другом сервере, введите здесь его имя хоста или IP-адрес.
Если вы используете виртуальный хостинг, ваш провайдер должен указать правильное имя хоста в своей документации.
-Если вы устанавливаете систему на сервере под Windows и используете MySQL, имя сервера «localhost» может не работать. В этом случае попробуйте указать «127.0.0.1».',
+Если вы устанавливаете систему на сервере под Windows и используете MySQL, имя сервера «localhost» может не работать. В этом случае попробуйте указать 127.0.0.1 локальный IP-адрес.
+
+Если вы используете PostgreSQL, оставьте это поле пустым для подключения через сокет Unix.',
'config-db-host-oracle' => 'TNS базы данных:',
'config-db-host-oracle-help' => 'Введите действительный [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; файл tnsnames.ora должен быть видимым для этой инсталляции. <br />При использовании клиентских библиотек версии 10g и старше также возможно использовать метод именования [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
'config-db-wiki-settings' => 'Идентификация этой вики',
'config-db-name' => 'Имя базы данных:',
- 'config-db-name-help' => 'Выберите название-идентификатор для вашей вики.
-Оно не должно содержать пробелов.
+ 'config-db-name-help' => 'Выберите название-идентификатор для вашей вики.
+Оно не должно содержать пробелов.
Если вы используете виртуальный хостинг, провайдер или выдаст вам конкретное имя базы данных, или позволит создавать базы данных с помощью панели управления.',
'config-db-name-oracle' => 'Схема базы данных:',
@@ -10974,46 +13984,47 @@ MediaWiki требует поддержки UTF-8 для корректной р
'config-db-install-account' => 'Учётная запись для установки',
'config-db-username' => 'Имя пользователя базы данных:',
'config-db-password' => 'Пароль базы данных:',
- 'config-db-password-empty' => 'Пожалуйста, введите пароль для нового пользователя базы данных «$1».
+ 'config-db-password-empty' => 'Пожалуйста, введите пароль для нового пользователя базы данных «$1».
Хотя и возможно создание пользователей без паролей, это небезопасно.',
- 'config-db-install-username' => 'Введите имя пользователя, которое будет использоваться для подключения к базе данных в процессе установки.
+ 'config-db-install-username' => 'Введите имя пользователя, которое будет использоваться для подключения к базе данных в процессе установки.
Это не имя пользователя MediaWiki, это имя пользователя для базы данных.',
- 'config-db-install-password' => 'Введите пароль, который будет использоваться для подключения к базе данных в процессе установки.
+ 'config-db-install-password' => 'Введите пароль, который будет использоваться для подключения к базе данных в процессе установки.
Это не пароль пользователя MediaWiki, это пароль для базы данных.',
'config-db-install-help' => 'Введите имя пользователя и пароль, которые будут использоваться для подключения к базе данных во время процесса установки.',
'config-db-account-lock' => 'Использовать то же имя пользователя и пароль для обычной работы',
'config-db-wiki-account' => 'Учётная запись для обычной работы',
- 'config-db-wiki-help' => 'Введите имя пользователя и пароль, которые будут использоваться для подключения к базе данных во время обычной работы вики.
+ 'config-db-wiki-help' => 'Введите имя пользователя и пароль, которые будут использоваться для подключения к базе данных во время обычной работы вики.
Если такой учётной записи не существует, а установочная учётная запись имеет достаточно привилегий, то обычная учётная запись будет создана с минимально необходимыми для работы вики привилегиями.',
'config-db-prefix' => 'Префикс таблиц базы данных:',
- 'config-db-prefix-help' => 'Если вам нужно делить одну базу данных между несколькими вики, или между MediaWiki и другими веб-приложениями, вы можете добавить префикс для всех имён таблиц.
-Не используйте пробелы.
+ '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 символы могут быть искажены, а резервная копия окажется негодной!
+ '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].",
+В '''бинарном режиме''' MediaWiki хранит юникодный текст в базе в виде двоичных полей.
+Это более эффективно, чем MySQL в режиме UTF-8, позволяет использовать полный набор символов Юникода.
+В '''режиме UTF-8''' MySQL будет знать к какому набору символу относятся ваши данные, сможет представлять и преобразовать их надлежащим образом (буква Ё окажется при сортировке после буквы Е, а не после буквы Я, как в бинарном режиме),
+но не позволит вам сохранять символы, выходящие за пределы [//ru.wikipedia.org/wiki/Символы,_представленные_в_Юникоде#.D0.91.D0.B0.D0.B7.D0.BE.D0.B2.D0.B0.D1.8F_.D0.BC.D0.BD.D0.BE.D0.B3.D0.BE.D1.8F.D0.B7.D1.8B.D0.BA.D0.BE.D0.B2.D0.B0.D1.8F_.D0.BF.D0.BB.D0.BE.D1.81.D0.BA.D0.BE.D1.81.D1.82.D1.8C BMP].",
'config-mysql-old' => 'Необходим MySQL $1 или более поздняя версия. У вас установлен MySQL $2.',
'config-db-port' => 'Порт базы данных:',
'config-db-schema' => 'Схема для MediaWiki',
'config-db-schema-help' => 'Эта схема обычно работают хорошо.
Изменяйте её только если знаете, что вам это нужно.',
+ 'config-pg-test-error' => "Не удаётся подключиться к базе данных '''$1''': $2",
'config-sqlite-dir' => 'Директория данных SQLite:',
- 'config-sqlite-dir-help' => "SQLite хранит все данные в одном файле.
+ 'config-sqlite-dir-help' => "SQLite хранит все данные в одном файле.
-Директория, которую вы должны указать, должна быть доступна для записи веб-сервером во время установки.
+Директория, которую вы должны указать, должна быть доступна для записи веб-сервером во время установки.
Она '''не должна''' быть доступна через Интернет, поэтому не должна совпадать с той, где хранятся PHP файлы.
Установщик запишет в эту директорию файл <code>.htaccess</code>, но если это не сработает, кто-нибудь может получить доступ ко всей базе данных.
-В этой базе находится в том числе и информация о пользователях (адреса электронной почты, хэши паролей), а также удалённые страницы и другие секретные данные о вики.
+В этой базе находится в том числе и информация о пользователях (адреса электронной почты, хэши паролей), а также удалённые страницы и другие секретные данные о вики.
По возможности, расположите базу данных где-нибудь в стороне, например, в <code>/var/lib/mediawiki/yourwiki</code>.",
'config-oracle-def-ts' => 'Пространство таблиц по умолчанию:',
@@ -11022,19 +14033,22 @@ MediaWiki требует поддержки UTF-8 для корректной р
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
- 'config-support-info' => 'MediaWiki поддерживает следующие СУБД:
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-support-info' => 'MediaWiki поддерживает следующие СУБД:
-$1
+$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-support-ibm_db2' => '$1 — коммерческая база данных масштаба предприятия.',
'config-header-mysql' => 'Настройки MySQL',
'config-header-postgres' => 'Настройки PostgreSQL',
'config-header-sqlite' => 'Настройки SQLite',
'config-header-oracle' => 'Настройки Oracle',
+ 'config-header-ibm_db2' => 'Настройки IBM DB2',
'config-invalid-db-type' => 'Неверный тип базы данных',
'config-missing-db-name' => 'Вы должны ввести значение параметра «Имя базы данных»',
'config-missing-db-host' => 'Необходимо ввести значение параметра «Сервер базы данных»',
@@ -11048,29 +14062,29 @@ $1
'config-connection-error' => '$1.
Проверьте хост, имя пользователя и пароль и попробуйте ещё раз.',
- 'config-invalid-schema' => 'Неправильная схема для MediaWiki «$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' => 'Выберите имя-идентификатор для вашей вики.
+ 'config-sqlite-name-help' => 'Выберите имя-идентификатор для вашей вики.
Не используйте дефисы и пробелы.
Эта строка будет использоваться в имени файла SQLite.',
- 'config-sqlite-parent-unwritable-group' => 'Не удалось создать директорию данных <nowiki><code>$1</code></nowiki>, так как у веб-сервера нет прав записи в родительскую директорию <nowiki><code>$2</code></nowiki>.
+ 'config-sqlite-parent-unwritable-group' => 'Не удалось создать директорию данных <nowiki><code>$1</code></nowiki>, так как у веб-сервера нет прав записи в родительскую директорию <nowiki><code>$2</code></nowiki>.
-Установщик определил пользователя, под которым работает веб-сервер.
-Сделайте директорию <nowiki><code>$3</code></nowiki> доступной для записи и продолжите.
-В Unix/Linux системе выполните:
+Установщик определил пользователя, под которым работает веб-сервер.
+Сделайте директорию <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>.
+ 'config-sqlite-parent-unwritable-nogroup' => 'Не удалось создать директорию для данных <code><nowiki>$1</nowiki></code>, так как у веб-сервера нет прав на запись в родительскую директорию <code><nowiki>$2</nowiki></code>.
-Программа установки не смогла определить пользователя, под которым работает веб-сервер.
-Для продолжения сделайте каталог <code><nowiki>$3</nowiki></code> глобально доступным для записи серверу (и другим).
-В Unix/Linux сделайте:
+Программа установки не смогла определить пользователя, под которым работает веб-сервер.
+Для продолжения сделайте каталог <code><nowiki>$3</nowiki></code> глобально доступным для записи серверу (и другим).
+В Unix/Linux сделайте:
<pre>cd $2
mkdir $3
@@ -11079,19 +14093,19 @@ chmod a+w $3</pre>',
Проверьте расположение и повторите попытку.',
'config-sqlite-dir-unwritable' => 'Невозможно произвести запись в каталог «$1».
Измените настройки доступа так, чтобы веб-сервер мог записывать в этот каталог, и попробуйте ещё раз.',
- 'config-sqlite-connection-error' => '$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.
+ 'config-can-upgrade' => "В базе данных найдены таблицы MediaWiki.
Чтобы обновить их до MediaWiki $1, нажмите на кнопку '''«Продолжить»'''.",
- 'config-upgrade-done' => "Обновление завершено.
+ 'config-upgrade-done' => "Обновление завершено.
-Теперь вы можете [$1 начать использовать вики].
+Теперь вы можете [$1 начать использовать вики].
-Если вы хотите повторно создать файл <code>LocalSettings.php</code>, нажмите на кнопку ниже.
+Если вы хотите повторно создать файл <code>LocalSettings.php</code>, нажмите на кнопку ниже.
Это действие '''не рекомендуется''', если у вас не возникло проблем при установке.",
'config-upgrade-done-no-regenerate' => 'Обновление завершено.
@@ -11103,21 +14117,29 @@ chmod a+w $3</pre>',
'config-db-web-help' => 'Выберите имя пользователя и пароль, которые веб-сервер будет использовать для подключения к серверу базы данных при обычной работе вики.',
'config-db-web-account-same' => 'Использовать ту же учётную запись, что и для установки',
'config-db-web-create' => 'Создать учётную запись, если она ещё не существует',
- 'config-db-web-no-create-privs' => 'Учётная запись, указанная вами для установки, не обладает достаточными правами для создания учётной записи.
+ 'config-db-web-no-create-privs' => 'Учётная запись, указанная вами для установки, не обладает достаточными правами для создания учётной записи.
Указанная здесь учётная запись уже должна существовать.',
'config-mysql-engine' => 'Движок базы данных:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "''' Внимание.''' Вы выбрали механизм MyISAM для хранения данных MySQL. Он не рекомендуется к использованию по следующим причинам:
+* он слабо поддерживает параллелизм из-за табличных блокировок;
+* более склонен к потере данных, по сравнению с другими механизмами;
+* код MediaWiki не всегда учитывает особенности MyISAM должным образом.
+
+Если ваша установка MySQL поддерживает InnoDB, настоятельно рекомендуется выбрать этот механизм.
+Если ваша установка MySQL не поддерживает InnoDB, возможно, настало время обновиться.",
'config-mysql-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.
+ 'config-mysql-charset-help' => "В '''двоичном режиме''' MediaWiki хранит UTF-8 текст в бинарных полях базы данных.
+Это более эффективно, чем ''UTF-8 режим'' MySQL, и позволяет использовать полный набор символов Unicode.
-В '''режиме UTF-8''' MySQL будет знать в какой кодировке находятся Ваши данные и может отображать и преобразовывать их соответствующим образом, но это не позволит вам хранить символы выше [http://en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базовой Многоязыковой Плоскости].",
+В '''режиме UTF-8''' MySQL будет знать в какой кодировке находятся Ваши данные и может отображать и преобразовывать их соответствующим образом, но это не позволит вам хранить символы выше [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Базовой Многоязыковой Плоскости].",
+ 'config-ibm_db2-low-db-pagesize' => "В вашей базе данных DB2 по умолчанию задано табличное пространство с недостаточным размером страницы. Размер страницы должен быть не менее '''32K'''.",
'config-site-name' => 'Название вики:',
'config-site-name-help' => 'Название будет отображаться в заголовке окна браузера и в некоторых других местах вики.',
'config-site-name-blank' => 'Введите название сайта.',
@@ -11126,10 +14148,10 @@ chmod a+w $3</pre>',
'config-ns-site-name' => 'То же, что имя вики: $1',
'config-ns-other' => 'Другое (укажите)',
'config-ns-other-default' => 'MyWiki',
- 'config-project-namespace-help' => "Следуя примеру Википедии, многие вики хранят свои страницы правил отдельно от страниц основного содержания, в так называемом '''«пространстве имён проекта»'''.
-Все названия страниц в этом пространстве имён начинается с определённого префикса, который вы можете задать здесь.
+ 'config-project-namespace-help' => "Следуя примеру Википедии, многие вики хранят свои страницы правил отдельно от страниц основного содержания, в так называемом '''«пространстве имён проекта»'''.
+Все названия страниц в этом пространстве имён начинается с определённого префикса, который вы можете задать здесь.
Обычно, этот префикс происходит от имени вики, но он не может содержать знаки препинания, символы «#» или «:».",
- 'config-ns-invalid' => 'Указанное пространство имён <nowiki>$1</nowiki> недопустимо.
+ 'config-ns-invalid' => 'Указанное пространство имён <nowiki>$1</nowiki> недопустимо.
Укажите другое пространство имён проекта.',
'config-ns-conflict' => 'Указанное пространство имён «<nowiki>$1</nowiki>» конфликтует со стандартным пространством имён MediaWiki.
Укажите другое пространство имён проекта.',
@@ -11140,7 +14162,7 @@ chmod a+w $3</pre>',
'config-admin-help' => 'Введите ваше имя пользователя здесь, например, «Иван Иванов».
Это имя будет использоваться для входа в вики.',
'config-admin-name-blank' => 'Введите имя пользователя администратора.',
- 'config-admin-name-invalid' => 'Указанное имя пользователя «<nowiki>$1</nowiki>» недопустимо.
+ 'config-admin-name-invalid' => 'Указанное имя пользователя «<nowiki>$1</nowiki>» недопустимо.
Укажите другое имя пользователя.',
'config-admin-password-blank' => 'Введите пароль для учётной записи администратора.',
'config-admin-password-same' => 'Пароль не должен быть таким же, как имя пользователя.',
@@ -11153,7 +14175,9 @@ chmod a+w $3</pre>',
'config-subscribe' => 'Подписаться на [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce рассылку новостей о появлении новых версий MediaWiki].',
'config-subscribe-help' => 'Это список рассылки с малым числом сообщений, используется для анонса новых выпусков и сообщений о проблемах с безопасностью.
Вам следует подписаться на него и обновлять движок MediaWiki, по мере выхода новых версий.',
- 'config-almost-done' => 'Вы почти у цели!
+ 'config-subscribe-noemail' => 'Вы попытались подписаться на список рассылки уведомлений о новых выпусках без указания адреса электронной почты.
+Укажите адрес электронной почты, если вы хотите подписаться на список рассылки.',
+ 'config-almost-done' => 'Вы почти у цели!
Остальные настройки можно пропустить и приступить к установке вики.',
'config-optional-continue' => 'Произвести тонкую настройку',
'config-optional-skip' => 'Хватит, установить вики',
@@ -11168,28 +14192,28 @@ chmod a+w $3</pre>',
Однако, движок MediaWiki можно использовать и иными способами, и не далеко не всех удаётся убедить в преимуществах открытой вики-работы.
Так что в вас есть выбор.
-Конфигурация '''«{{int:config-profile-wiki}}»''' позволяет всем править страницы даже не регистрируясь на сайте. Конфигурация '''{{int:config-profile-no-anon}}''' обеспечивает дополнительный учёт, но может отсечь случайных участников.
+Конфигурация '''«{{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 соответствующее руководство].",
+Более сложные схемы разграничения прав можно настроить после установки, см. [//www.mediawiki.org/wiki/Manual:User_rights соответствующее руководство].",
'config-license' => 'Авторские права и лицензии:',
'config-license-none' => 'Не указывать лицензию в колонтитуле внизу страницы',
- 'config-license-cc-by-sa' => 'Creative Commons атрибуция — с сохранением условий',
+ 'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by' => 'Creative Commons Attribution',
'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
- 'config-license-cc-0' => 'Creative Commons Zero',
- 'config-license-gfdl-old' => 'GNU Free Documentation License 1.2',
- 'config-license-gfdl-current' => 'GNU Free Documentation License 1.3 или более поздней версии',
+ 'config-license-cc-0' => 'Creative Commons Zero (общественное достояние)',
+ 'config-license-gfdl' => 'GNU Free Documentation License 1.3 или более поздняя',
'config-license-pd' => 'Общественное достояние',
'config-license-cc-choose' => 'Выберите одну из лицензий Creative Commons',
'config-license-help' => "Многие общедоступные вики разрешают использовать свои материалы на условиях [http://freedomdefined.org/Definition/Ru свободных лицензий].
Это помогает созданию чувства общности, стимулирует долгосрочное участие.
-Но в этом нет необходимости для частных или корпоративных вики.
+Но в этом нет необходимости для частных или корпоративных вики.
-Если вы хотите использовать тексты из Википедии или хотите, что в Википедию можно было копировать тексты из вашей вики, вам следует выбрать '''Creative Commons Attribution Share Alike'''.
+Если вы хотите использовать тексты из Википедии или хотите, что в Википедию можно было копировать тексты из вашей вики, вам следует выбрать '''Creative Commons Attribution Share Alike'''.
-GNU Free Documentation License раньше была основной лицензией Википедии.
-Она все ещё используется, однако, она имеет некоторые особенности, осложняющие повторное использование и интерпретацию её материалов.",
+Википедия ранее использовала лицензию GNU Free Documentation License.
+GFDL может быть использована, но она сложна для понимания и осложняет повторное использование материалов.",
'config-email-settings' => 'Настройки электронной почты',
'config-enable-email' => 'Включить исходящие e-mail',
'config-enable-email-help' => 'Если вы хотите, чтобы электронная почта работала, необходимо выполнить [http://www.php.net/manual/en/mail.configuration.php соответствующие настройки PHP].
@@ -11205,13 +14229,13 @@ GNU Free Documentation License раньше была основной лицен
Только прошедшие проверку подлинности адреса электронной почты, могут получать электронные письма от других пользователей или изменять уведомления, отправляемые по электронной почте.
Включение этой опции '''рекомендуется''' для открытых вики в целях пресечения потенциальных злоупотреблений возможностями электронной почты.",
'config-email-sender' => 'Обратный адрес электронной почты:',
- 'config-email-sender-help' => 'Введите адрес электронной почты для использования в качестве обратного адреса исходящей электронной почты.
+ 'config-email-sender-help' => 'Введите адрес электронной почты для использования в качестве обратного адреса исходящей электронной почты.
На него будут отправляться отказы.
Многие почтовые серверы требуют, чтобы по крайней мере доменное имя в нём было правильным.',
'config-upload-settings' => 'Загрузка изображений и файлов',
'config-upload-enable' => 'Разрешить загрузку файлов',
'config-upload-help' => 'Разрешение загрузки файлов, потенциально, может привести к угрозе безопасности сервера.
-Для получения дополнительной информации, прочтите в руководстве [http://www.mediawiki.org/wiki/Manual:Security раздел, посвящённый безопасности].
+Для получения дополнительной информации, прочтите в руководстве [//www.mediawiki.org/wiki/Manual:Security раздел, посвящённый безопасности].
Чтобы разрешить загрузку файлов, необходимо изменить права на каталог <code>images</code>, в корневой директории MediaWiki так, чтобы веб-сервер мог записывать в него файлы.
Затем включите эту опцию.',
@@ -11219,13 +14243,13 @@ GNU Free Documentation License раньше была основной лицен
'config-upload-deleted-help' => 'Выберите каталог, в котором будут храниться архивы удалённых файлов.
В идеальном случае, в этот каталог не должно быть доступа из сети Интернет.',
'config-logo' => 'URL логотипа:',
- 'config-logo-help' => 'Тема по умолчанию для MediaWiki включает пространство для логотипа размером 135x160 в левом верхнем углу.
-Загрузите изображение соответствующего размера, и введите его 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 необходим доступ к Интернету.
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] — это функция, позволяющая использовать изображения, звуки и другие медиафайлы с Викисклада ([//commons.wikimedia.org/ Wikimedia Commons]).
+Для работы этой функции MediaWiki необходим доступ к Интернету.
Дополнительную информацию об Instant Commons, в том числе указания о том, как её настроить для других вики, отличных от Викисклада, можно найти в [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos руководстве].',
'config-cc-error' => 'Механизм выбора лицензии Creative Commons не вернул результата.
@@ -11234,13 +14258,13 @@ GNU Free Documentation License раньше была основной лицен
'config-cc-not-chosen' => 'Выберите, какую лицензию Creative Commons Вы хотите использовать, и нажмите кнопку "Продолжить".',
'config-advanced-settings' => 'Дополнительные настройки',
'config-cache-options' => 'Параметры кэширования объектов:',
- 'config-cache-help' => 'Кэширование объектов используется для повышения скорости MediaWiki путем кэширования часто используемых данных.
+ '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.
+ 'config-memcached-help' => 'Список IP-адресов, используемых Memcached.
Перечислите по одному адресу на строку с указанием портов. Например:
127.0.0.1:11211
192.168.1.25:1234',
@@ -11250,7 +14274,7 @@ GNU Free Documentation License раньше была основной лицен
Если вы не знаете порт, по умолчанию используется 11211.',
'config-memcache-badport' => 'Номера портов Memcached должны лежать в пределах от $1 до $2.',
'config-extensions' => 'Расширения',
- 'config-extensions-help' => 'Расширения MediaWiki, перечисленные выше, были найдены в каталоге <code>./extensions</code>.
+ 'config-extensions-help' => 'Расширения MediaWiki, перечисленные выше, были найдены в каталоге <code>./extensions</code>.
Они могут потребовать дополнительные настройки, но их можно включить прямо сейчас',
'config-install-alreadydone' => "'''Предупреждение:''' Вы, кажется, уже устанавливали MediaWiki и пытаетесь произвести повторную установку.
@@ -11261,6 +14285,7 @@ GNU Free Documentation License раньше была основной лицен
'config-install-step-failed' => 'не удалось',
'config-install-extensions' => 'В том числе расширения',
'config-install-database' => 'Настройка базы данных',
+ 'config-install-schema' => 'Создание схемы',
'config-install-pg-schema-not-exist' => 'Схемы PostgreSQL не существует',
'config-install-pg-schema-failed' => 'Не удалось создать таблицы.
Убедитесь в том, что пользователь «$1» может писать в схему «$2».',
@@ -11268,10 +14293,17 @@ GNU Free Documentation License раньше была основной лицен
'config-install-pg-plpgsql' => 'Проверка языка PL/pgSQL',
'config-pg-no-plpgsql' => 'Вам необходимо установить поддержку языка PL/pgSQL для базы данных $1',
'config-pg-no-create-privs' => 'Учётная запись, указанная для установки, не обладает достаточными привилегиями для создания учётной записи.',
+ 'config-pg-not-in-role' => 'Указанная учётная запись веб-пользователя уже существует.
+Указанная для установки учётная запись не является записью суперпользователя, и не относится к роли веб-пользователя, поэтому не получается создать объекты, принадлежащие веб-пользователю.
+
+MediaWiki в настоящее время требует, чтобы владельцем таблиц был веб-пользователь. Пожалуйста, укажите другое имя учётной записи для веб, или нажмите кнопку «назад» и укажите пользователя с достаточными для установки правами.',
'config-install-user' => 'Создание базы данных пользователей',
'config-install-user-alreadyexists' => 'Участник «$1» уже существует',
'config-install-user-create-failed' => 'Не получилось создать участника «$1»: $2',
'config-install-user-grant-failed' => 'Ошибка предоставления прав пользователю «$1»: $2',
+ 'config-install-user-missing' => 'Указанного пользователя «$1» не существует.',
+ 'config-install-user-missing-create' => 'Указанного пользователя «$1» не существует.
+Пожалуйста поставьте ниже отметку «Создать учётную запись», если вы хотите создать его.',
'config-install-tables' => 'Создание таблиц',
'config-install-tables-exist' => "'''Предупреждение''': таблицы MediaWiki, возможно, уже существуют.
Пропуск повторного создания.",
@@ -11279,12 +14311,13 @@ GNU Free Documentation License раньше была основной лицен
'config-install-interwiki' => 'Заполнение таблицы интервики значениями по умолчанию',
'config-install-interwiki-list' => 'Не удалось найти файл <code>interwiki.list</code>.',
'config-install-interwiki-exists' => "'''Предупреждение''': в интервики-таблице, кажется, уже есть записи.
-Создание стандартного списка, пропущено.",
+Создание стандартного списка пропущено.",
'config-install-stats' => 'Статистика инициализации',
- 'config-install-keys' => 'Создание секретного ключа',
+ 'config-install-keys' => 'Создание секретных ключей',
'config-insecure-keys' => "'''Предупреждение.''' {{PLURAL:$2|Ключ безопасности $1, созданный во время установки, недостаточно надёжен|Ключи безопасности $1, созданные во время установки, недостаточно надёжны}}. Рассмотрите возможность {{PLURAL:$2|его|их}} изменения вручную.",
'config-install-sysop' => 'Создание учётной записи администратора',
- 'config-install-subscribe-fail' => 'Не удаётся подписаться на mediawiki-announce',
+ 'config-install-subscribe-fail' => 'Не удаётся подписаться на mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'cURL не установлен и не доступна опция allow_url_fopen.',
'config-install-mainpage' => 'Создание главной страницы с содержимым по умолчанию',
'config-install-extension-tables' => 'Создание таблиц для включённых расширений',
'config-install-mainpage-failed' => 'Не удаётся вставить главную страницу: $1',
@@ -11305,6 +14338,143 @@ $3
По окончании действий, описанных выше, вы сможете '''[$2 войти в вашу вики]'''.",
'config-download-localsettings' => 'Загрузить LocalSettings.php',
'config-help' => 'справка',
+ 'mainpagetext' => "'''Вики-движок «MediaWiki» успешно установлен.'''",
+ 'mainpagedocfooter' => 'Информацию по работе с этой вики можно найти в [//meta.wikimedia.org/wiki/%D0%9F%D0%BE%D0%BC%D0%BE%D1%89%D1%8C:%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5 справочном руководстве].
+
+== Некоторые полезные ресурсы ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Список возможных настроек];
+* [//www.mediawiki.org/wiki/Manual:FAQ Часто задаваемые вопросы и ответы по MediaWiki];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Рассылка уведомлений о выходе новых версий MediaWiki].',
+);
+
+/** Rusyn (Русиньскый)
+ * @author Gazeb
+ */
+$messages['rue'] = array(
+ 'mainpagetext' => "'''MediaWiki была успішно наіншталована.'''",
+ 'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents Мануял хоснователя] Вам порадить, як хосновати MediaWiki.
+
+== Про початок ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Наставлїня конфіґурації]
+* [//www.mediawiki.org/wiki/Manual:FAQ Часты вопросы о MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Розосыланя повідомлїнь про новы верзії MediaWiki]',
+);
+
+/** Sanskrit (संस्कृत)
+ * @author Hemant wikikosh1
+ */
+$messages['sa'] = array(
+ 'mainpagetext' => 'मीडियाविकि तु सफलतया अन्तःस्थापितमस्ति',
+);
+
+/** Sakha (Саха тыла) */
+$messages['sah'] = array(
+ 'mainpagetext' => "'''«MediaWiki» сөпкө туруорулунна.'''",
+ 'mainpagedocfooter' => 'Биики программатын туһунан [//meta.wikimedia.org/wiki/Help:Contents справочникка] көрүөххүн сөп.
+
+== Саҕаланыыта ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Конфигурация уларытыытын параметрдара]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki релизтарын почтовай испииһэгэ]',
+);
+
+/** Sardinian (Sardu)
+ * @author Andria
+ */
+$messages['sc'] = array(
+ 'mainpagetext' => "'''MediaWiki est stadu installadu in modu currègidu.'''",
+);
+
+/** Sicilian (Sicilianu) */
+$messages['scn'] = array(
+ 'mainpagetext' => "'''Nstallazzioni di MediaWiki cumplitata currettamenti.'''",
+ 'mainpagedocfooter' => "Pi favuri taliari [//meta.wikimedia.org/wiki/Help:Contents Guida utenti] pi aiutu supra l'usu e la cunfigurazzioni di stu software wiki.
+
+== P'accuminzari ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Alencu di mpustazzioni di cunfigurazzioni]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list dî rilassi di MediaWiki]",
+);
+
+/** Scots (Scots) */
+$messages['sco'] = array(
+ 'mainpagetext' => "'''MediaWiki haes been installit wi speed.'''",
+ 'mainpagedocfooter' => "Aks the [//meta.wikimedia.org/wiki/Help:Contents Uiser's Manual] for speirins aboot using the wiki saftware.
+
+== Gettin startit ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settins leet]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki releese mailin leet]",
+);
+
+/** Sassaresu (Sassaresu) */
+$messages['sdc'] = array(
+ 'mainpagetext' => "'''Isthallazioni di MediaWiki accabadda currentementi.'''",
+ 'mainpagedocfooter' => "Cunsultha la [//meta.wikimedia.org/wiki/Aggiuddu:Summàriu Ghia utenti] pa maggiori infuimmazioni i l'usu di chisthu software wiki.
+
+== Pa ischuminzà ==
+Li sighenti cullegamenti so in linga ingrese:
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Impusthazioni di cunfigurazioni]
+* [//www.mediawiki.org/wiki/Manual:FAQ Prigonti friquenti i MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list annùnzii MediaWiki]",
+);
+
+/** Cmique Itom (Cmique Itom) */
+$messages['sei'] = array(
+ 'mainpagetext' => "'''MediaWiki coccebj installöx successua zo mii.'''",
+);
+
+/** Serbo-Croatian (Srpskohrvatski)
+ * @author OC Ripper
+ */
+$messages['sh'] = array(
+ 'mainpagetext' => "'''MediaWiki softver is uspješno instaliran.'''",
+ 'mainpagedocfooter' => 'Kontaktirajte [//meta.wikimedia.org/wiki/Help:Contents uputstva za korisnike] za informacije o upotrebi wiki programa.
+
+== Početak ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista postavki]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki najčešće postavljana pitanja]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista E-Mail adresa MediaWiki]',
+);
+
+/** Tachelhit (Tašlḥiyt/ⵜⴰⵛⵍⵃⵉⵜ)
+ * @author Dalinanir
+ */
+$messages['shi'] = array(
+ 'mainpagetext' => "'''MediaWiki tǧizn (tsrbk) bla tamukrist.'''",
+ 'mainpagedocfooter' => 'Ẓr taǧttnn [//meta.wikimedia.org/wiki/Aide:Contenu Guide de l’utilisateur] bac ad tawit inɣmisn yaḍn f manik sa tswwurt asɣẓan ad.
+
+== Izwir d MediaWiki ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Umuɣ n iɣwwarn n usgadda ]
+* [//www.mediawiki.org/wiki/Manual:FAQ/fr Isqqsitn f MidyWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Umuɣ n imsgdaln f imbḍitn n MidyaWiki]',
+);
+
+/** Sinhala (සිංහල) */
+$messages['si'] = array(
+ 'mainpagetext' => "'''මාධ්‍යවිකි සාර්ථක ලෙස ස්ථාපනය කරන ලදි.'''",
+ 'mainpagedocfooter' => 'විකි මෘදුකාංග භාවිතා කිරීම පිළිබඳ තොරතුරු සඳහා [//meta.wikimedia.org/wiki/Help:Contents පරිශීලකයන් සඳහා නියමුව] හදාරන්න.
+
+== ඇරඹුම ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings වින්‍යාස පරිස්ථිතීන් ලැයිස්තුව]
+* [//www.mediawiki.org/wiki/Manual:FAQ මාධ්‍යවිකි නිතර-අසන-පැන]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce මාධ්‍යවිකි නිකුතුව තැපැල් ලැයිස්තුව]',
+);
+
+/** Slovak (Slovenčina) */
+$messages['sk'] = array(
+ 'mainpagetext' => "'''Softvér MediaWiki bol úspešne nainštalovaný.'''",
+ 'mainpagedocfooter' => 'Informácie ako používať wiki softvér nájdete v [//meta.wikimedia.org/wiki/Help:Contents Používateľskej príručke].
+
+== Začíname ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Zoznam konfiguračných nastavení]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce mailing list nových verzií MediaWiki]',
);
/** Slovenian (Slovenščina)
@@ -11314,31 +14484,315 @@ $messages['sl'] = array(
'config-desc' => 'Namestitveni program za MediaWiki',
'config-title' => 'Namestitev MediaWiki $1',
'config-information' => 'Informacije',
+ 'config-localsettings-cli-upgrade' => 'Zaznana je bila datoteka LocalSettings.php.
+Za nadgradnjo te namestitve zaženite update.php',
+ 'config-localsettings-key' => 'Nadgraditveni ključ:',
+ 'config-localsettings-badkey' => 'Naveden ključ je napačen.',
+ 'config-upgrade-key-missing' => 'Zaznana je bila obstoječa namestitev MediaWiki.
+Za nadgradnjo te namestitve vstavite naslednjo vrstico na dno vaše LocalSettings.php:
+
+$1',
+ 'config-session-error' => 'Napaka pri začenjanju seje: $1',
+ 'config-session-expired' => 'Kot kaže, so vaši podatki seje potekli.
+Seje so konfigurirane za dobo $1.
+To lahko povečate tako, da nastavite <code>session.gc_maxlifetime</code> v php.ini.
+Ponovno zaženite postopek namestitve.',
+ 'config-no-session' => 'Vaši podatki seje so bili izgubljeni!
+Preverite vaš php.ini in se prepričajte, da je <code>session.save_path</code> nastavljena na ustrezno mapo.',
'config-your-language' => 'Vaš jezik:',
+ 'config-your-language-help' => 'Izberite jezik, ki bo uporabljen med postopkom namestitve.',
+ 'config-wiki-language' => 'Jezik wikija:',
+ 'config-wiki-language-help' => 'Izberite jezik, v katerem bo wiki večinoma pisan.',
'config-back' => '← Nazaj',
'config-continue' => 'Nadaljuj →',
'config-page-language' => 'Jezik',
'config-page-welcome' => 'Dobrodošli na MediaWiki!',
+ 'config-page-dbconnect' => 'Vzpostavi povezavo z zbirko podatkov',
+ 'config-page-upgrade' => 'Nadgradi obstoječo namestitev',
+ 'config-page-dbsettings' => 'Nastavitve zbirke podatkov',
'config-page-name' => 'Ime',
'config-page-options' => 'Možnosti',
'config-page-install' => 'Namesti',
'config-page-complete' => 'Končano!',
+ 'config-page-restart' => 'Ponovno zaženi namestitev',
'config-page-readme' => 'Beri me',
+ 'config-page-releasenotes' => 'Opombe ob izidu',
'config-page-copying' => 'Kopiranje',
'config-page-upgradedoc' => 'Nadgrajevanje',
+ 'config-page-existingwiki' => 'Obstoječ wiki',
+ 'config-help-restart' => 'Želite počistiti vse shranjene podatke, ki ste jih vnesti, in ponovno začeti s postopkom namestitve?',
+ 'config-restart' => 'Da, ponovno zaženi',
+ 'config-welcome' => '=== Pregledi okolja ===
+Izvedeni so osnovni pregledi, da vidimo, če je okolje primerno za namestitev MediaWiki.
+Če med namestitvijo potrebujete pomoč, posredujte tudi rezultate teh pregledov.',
+ 'config-sidebar' => '* [//www.mediawiki.org Domača stran MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Vodnik za uporabnike]
+* [//www.mediawiki.org/wiki/Manual:Contents Vodnik za administratorje]
+* [//www.mediawiki.org/wiki/Manual:FAQ Pogosto zastavljena vprašanja]
+----
+* <doclink href=Readme>Beri me</doclink>
+* <doclink href=ReleaseNotes>Opombe ob izidu</doclink>
+* <doclink href=Copying>Kopiranje</doclink>
+* <doclink href=UpgradeDoc>Nadgrajevanje</doclink>',
+ 'config-env-good' => 'Okolje je pregledano.
+Lahko namestite MediaWiki.',
+ 'config-env-bad' => 'Okolje je pregledano.
+Ne morete namestiti MediaWiki.',
+ 'config-env-php' => 'Nameščen je PHP $1.',
+ 'config-env-php-toolow' => 'Nameščen je PHP $1.
+Vendar pa MediaWiki zahteva PHP $2 ali višji.',
+ 'config-unicode-using-utf8' => 'Uporaba utf8_normalize.so Briona Vibberja za normalizacijo unikoda.',
+ 'config-unicode-using-intl' => 'Uporaba [http://pecl.php.net/intl razširitve PECL intl] za normalizacijo unikoda.',
+ 'config-memory-raised' => 'PHP-jev <code>memory_limit</code> je $1, dvignjen na $2.',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] je nameščen',
+ 'config-apc' => '[http://www.php.net/apc APC] je nameščen',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] je nameščen',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] je nameščen',
+ 'config-diff3-bad' => 'GNU diff3 ni bilo mogoče najti.',
+ 'config-db-type' => 'Vrsta zbirke podatkov:',
+ 'config-db-host' => 'Gostitelj zbirke podatkov:',
+ 'config-db-host-oracle' => 'TNS zbirke podatkov:',
+ 'config-db-wiki-settings' => 'Prepoznaj ta wiki:',
'config-db-name' => 'Ime zbirke podatkov:',
+ 'config-db-name-oracle' => 'Shema zbirke podatkov:',
'config-db-username' => 'Uporabniško ime zbirke podatkov:',
'config-db-password' => 'Geslo zbirke podatkov:',
+ 'config-db-prefix' => 'Predpona tabel zbirke podatkov:',
+ 'config-db-charset' => 'Nabor znakov zbirke podatkov',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 dvojiško',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 nazaj združljiv UTF-8',
+ 'config-mysql-old' => 'Potreben je MySQL $1 ali novejši; vi imate $2.',
+ 'config-db-port' => 'Vrata zbirke podatkov:',
+ 'config-db-schema' => 'Shema MediaWiki',
+ 'config-db-schema-help' => 'Ta shema je po navadi v redu.
+Spremenite jo samo, če veste, da jo morate.',
+ 'config-sqlite-dir' => 'Mapa podatkov SQLite:',
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-support-info' => 'MediaWiki podpira naslednje sisteme zbirk podatkov:
+
+$1
+
+Če zgoraj ne vidite navedenega sistema zbirk podatkov, ki ga poskušate uporabiti, sledite navodilom na spodnji povezavi, da omogočite podporo.',
+ 'config-header-mysql' => 'Nastavitve MySQL',
+ 'config-header-postgres' => 'Nastavitve PostgreSQL',
+ 'config-header-sqlite' => 'Nastavitve SQLite',
+ 'config-header-oracle' => 'Nastavitve Oracle',
+ 'config-header-ibm_db2' => 'Nastavitve IBM DB2',
+ 'config-invalid-db-type' => 'Neveljavna vrsta zbirke podatkov',
+ 'config-missing-db-name' => 'Vnesti morate vrednost za »Ime zbirke podatkov«',
+ 'config-missing-db-host' => 'Vnesti morate vrednost za »Gostitelj zbirke podatkov«',
+ 'config-missing-db-server-oracle' => 'Vnesti morate vrednost za »TNS zbirke podatkov«',
+ 'config-invalid-db-server-oracle' => 'Neveljaven TNS zbirke podatkov »$1«.
+Uporabljajte samo črke ASCII (a-z, A-Z), številke (0-9), podčrtaje (_) in pike (.).',
+ 'config-invalid-db-name' => 'Neveljavno ime zbirke podatkov »$1«.
+Uporabljajte samo črke ASCII (a-z, A-Z), številke (0-9), podčrtaje (_) in vezaje (-).',
+ 'config-invalid-db-prefix' => 'Neveljavna predpona zbirke podatkov »$1«.
+Uporabljajte samo črke ASCII (a-z, A-Z), številke (0-9), podčrtaje (_) in vezaje (-).',
+ 'config-connection-error' => '$1.
+
+Preverite gostitelja, uporabniško ime in geslo spodaj ter poskusite znova.',
+ 'config-postgres-old' => 'Potreben je PostgreSQL $1 ali novejši; vi imate $2.',
+ 'config-sqlite-connection-error' => '$1.
+
+Preverite mapo podatkov in ime zbirke podatkov spodaj ter poskusite znova.',
+ 'config-sqlite-readonly' => 'Datoteka <code>$1</code> ni zapisljiva.',
+ 'config-sqlite-cant-create-db' => 'Ne morem ustvariti datoteke zbirke podatkov <code>$1</code>.',
+ 'config-upgrade-done-no-regenerate' => 'Nadgradnja je končana.
+
+Sedaj lahko [$1 začnete uporabljati vaš wiki].',
+ 'config-regenerate' => 'Ponovno ustvari LocalSettings.php →',
+ 'config-show-table-status' => 'Poizvedba SHOW TABLE STATUS ni uspela!',
+ 'config-unknown-collation' => "'''Opozorilo:''' Zbirke podatkov uporablja neprepoznano razvrščanje znakov.",
+ 'config-db-web-account' => 'Račun zbirke podatkov za spletni dostop',
+ 'config-db-web-account-same' => 'Uporabi enak račun kot za namestitev',
+ 'config-db-web-create' => 'Ustvari račun, če že ne obstaja',
+ 'config-mysql-engine' => 'Pogon skladiščenja:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-charset' => 'Nabor znakov zbirke podatkov:',
+ 'config-mysql-binary' => 'Dvojiško',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Ime wikija:',
+ 'config-site-name-help' => 'To bo prikazano v naslovni vrstici brskalnika in na drugih različnih mestih.',
+ 'config-site-name-blank' => 'Vnesite ime strani.',
+ 'config-project-namespace' => 'Imenski prostor projekta:',
+ 'config-ns-generic' => 'Projekt',
+ 'config-ns-site-name' => 'Enako kot ime wikija: $1',
+ 'config-ns-other' => 'Drugo (navedite)',
+ 'config-ns-other-default' => 'MojWiki',
+ 'config-ns-invalid' => 'Naveden imenski prostor »<nowiki>$1</nowiki>« ni veljaven.
+Določite drug imenski prostor projekta.',
+ 'config-ns-conflict' => 'Naveden imenski prostor »<nowiki>$1</nowiki>« je v sporu s privzetim imenskim prostorom MediaWiki.
+Določite drug imenski prostor projekta.',
+ 'config-admin-box' => 'Administratorski račun',
+ 'config-admin-name' => 'Vaše ime:',
'config-admin-password' => 'Geslo:',
+ 'config-admin-password-confirm' => 'Geslo, ponovno:',
+ 'config-admin-help' => 'Tukaj vnesite želeno uporabniško ime, na primer »Janez Blog«.
+To je ime, ki ga boste uporabljali za prijavo v wiki.',
+ 'config-admin-name-blank' => 'Vnesite uporabniško ime administratorja.',
+ 'config-admin-name-invalid' => 'Navedeno uporabniško ime »<nowiki>$1</nowiki>« ni veljavno.
+Določite drugo uporabniško ime.',
+ 'config-admin-password-blank' => 'Vnesite geslo za administratorski račun.',
+ 'config-admin-password-same' => 'Geslo ne sme biti enako kot uporabniško ime.',
+ 'config-admin-password-mismatch' => 'Vneseni gesli se ne ujemata.',
+ 'config-admin-email' => 'E-poštni naslov:',
+ 'config-admin-error-user' => 'Med ustvarjanjem administratorja »<nowiki>$1</nowiki>« je prišlo do notranje napake.',
+ 'config-admin-error-password' => 'Med nastavljanjem gesla za administratorja »<nowiki>$1</nowiki>« je prišlo do notranje napake: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Vnesli ste neveljaven e-poštni naslov.',
+ 'config-subscribe' => 'Naročite se na [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce poštni seznam obvestil o izdajah].',
+ 'config-almost-done' => 'Skoraj ste že končali!
+Sedaj lahko preskočite preostalo konfiguriranje in zdaj namestite wiki.',
+ 'config-optional-continue' => 'Zastavi mi več vprašanj.',
+ 'config-optional-skip' => 'Se že dolgočasim; samo namesti wiki.',
+ 'config-profile' => 'Profil uporabniških pravic:',
+ 'config-profile-wiki' => 'Klasičen wiki',
+ 'config-profile-no-anon' => 'Zahtevano je ustvarjanje računa',
+ 'config-profile-fishbowl' => 'Samo pooblaščeni urejevalci',
+ 'config-profile-private' => 'Zasebni wiki',
+ 'config-license' => 'Avtorske pravice in dovoljenje:',
+ 'config-license-none' => 'Brez noge dovoljenja',
+ 'config-license-cc-by-sa' => 'Creative Commons Priznanje avtorstva-Deljenje pod enakimi pogoji',
+ 'config-license-cc-by' => 'Creative Commons Priznanje avtorstva',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons Priznanje avtorstva-Nekomercialno-Deljenje pod enakimi pogoji',
+ 'config-license-cc-0' => 'Creative Commons Zero (javna last)',
+ 'config-license-pd' => 'Javna last',
+ 'config-license-cc-choose' => 'Izberite dovoljenje Creative Commons po meri',
+ 'config-email-settings' => 'Nastavitve e-pošte',
+ 'config-enable-email' => 'Omogoči odhodno e-pošto',
+ 'config-email-user' => 'Omogoči e-pošto med uporabniki',
+ 'config-email-auth' => 'Omogoči overitev preko e-pošte',
+ 'config-email-sender' => 'E-poštni naslov za vrnjeno pošto:',
+ 'config-upload-settings' => 'Nalaganje slike in datotek',
+ 'config-upload-enable' => 'Omogoči nalaganje datotek',
+ 'config-upload-deleted' => 'Mapa za izbrisane datoteke:',
+ 'config-upload-deleted-help' => 'Izberite mapo za arhiviranje izbrisanih datotek.
+Najbolje je, da mapa ni dostopna preko spleta.',
+ 'config-logo' => 'URL logotipa:',
+ 'config-cc-error' => 'Izbirnik dovoljenja Creative Commons ni vrnil nobenih rezultatov.
+Vnesite ime dovoljenja ročno.',
+ 'config-cc-again' => 'Izberi ponovno ...',
+ 'config-cc-not-chosen' => 'Izberite dovoljenje Creative Commons, ki ga želite dodati, in kliknite »proceed«.',
+ 'config-advanced-settings' => 'Napredna konfiguracija',
+ 'config-cache-accel' => 'Predpomnjenje predmetov PHP (APC, eAccelerator, XCache ali WinCache)',
+ 'config-cache-memcached' => 'Uporabi Memcached (zahteva dodatno namestitev in konfiguracijo)',
+ 'config-memcached-servers' => 'Strežniki Memcached:',
+ 'config-memcache-badip' => 'Vnesli ste neveljaven IP-naslov za Memcached: $1',
+ 'config-extensions' => 'Razširitve',
+ 'config-install-step-done' => 'končano',
+ 'config-install-step-failed' => 'spodletelo',
+ 'config-install-database' => 'Vzpostavljanje zbirke podatkov',
+ 'config-install-pg-schema-not-exist' => 'Shema PostgreSQL ne obstaja.',
+ 'config-install-user-alreadyexists' => 'Uporabnik »$1« že obstaja',
+ 'config-install-tables' => 'Ustvarjanje tabel',
+ 'config-download-localsettings' => 'Prenesi LocalSettings.php',
+ 'config-help' => 'pomoč',
+ 'mainpagetext' => "'''Programje MediaWiki je bilo uspešno nameščeno.'''",
+ 'mainpagedocfooter' => 'Za uporabo in pomoč pri nastavitvi, prosimo, preglejte [//meta.wikimedia.org/wiki/MediaWiki_localisation dokumentacijo za prilagajanje vmesnika]
+in [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Uporabniški priročnik].',
);
-/** Serbian Cyrillic ekavian (‪Српски (ћирилица)‬) */
+/** Lower Silesian (Schläsch)
+ * @author Äberlausitzer
+ */
+$messages['sli'] = array(
+ 'mainpagetext' => "'''MediaWiki wourde erfolgreich installiert.'''",
+ 'mainpagedocfooter' => 'Hilfe zur Benutzung und Konfiguration der Wiki-Software fendest du eim [//meta.wikimedia.org/wiki/Help:Contents Benutzerhandbichl].
+
+== Stoarthilfa ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Liste der Konfigurationsvariablen]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki-FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailingliste neuer MediaWiki-Versionen]',
+);
+
+/** Somali (Soomaaliga)
+ * @author Maax
+ */
+$messages['so'] = array(
+ 'mainpagetext' => "'''MediaWiki Si fiican oo kuugu install gareeyay.'''",
+ 'mainpagedocfooter' => "Meeshaan ka akhriso sidii aad u isticmaali leheed brogramka wiki [//meta.wikimedia.org/wiki/Help:Contents User's Guide] .
+== Bilaaw ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Albanian (Shqip) */
+$messages['sq'] = array(
+ 'mainpagetext' => "'''MediaWiki software u instalua me sukses.'''",
+ 'mainpagedocfooter' => 'Për më shumë informata rreth përdorimit të softwerit wiki , ju lutem shikoni [//meta.wikimedia.org/wiki/Help:Contents dokumentacionin përkatës].
+
+== Sa për fillim==
+* [//www.mediawiki.org/wiki/Help:Configuration_settings Parazgjedhjet e MediaWiki-t]
+* [//www.mediawiki.org/wiki/Help:FAQ Pyetjet e shpeshta rreth MediaWiki-t]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Njoftime rreth MediaWiki-t]',
+);
+
+/** Serbian (Cyrillic script) (‪Српски (ћирилица)‬)
+ * @author Rancher
+ */
$messages['sr-ec'] = array(
'config-continue' => 'Настави →',
'config-page-language' => 'Језик',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'mainpagetext' => "'''Медијавики је успешно инсталиран.'''",
+ 'mainpagedocfooter' => 'Погледајте [//meta.wikimedia.org/wiki/Help:Contents кориснички водич] за коришћење програма.
+
+== Увод ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Помоћ у вези са подешавањима]
+* [//www.mediawiki.org/wiki/Manual:FAQ Често постављена питања]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Дописна листа о издањима Медијавикија]',
+);
+
+/** Serbian (Latin script) (‪Srpski (latinica)‬) */
+$messages['sr-el'] = array(
+ 'mainpagetext' => "'''MedijaViki je uspešno instaliran.'''",
+ 'mainpagedocfooter' => 'Molimo vidite [//meta.wikimedia.org/wiki/Help:Contents korisnički vodič] za informacije o upotrebi viki softvera.
+
+== Za početak ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Pomoć u vezi sa podešavanjima]
+* [//www.mediawiki.org/wiki/Manual:FAQ Najčešće postavljena pitanja]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mejling lista o izdanjima MedijaVikija]',
+);
+
+/** Sranan Tongo (Sranantongo) */
+$messages['srn'] = array(
+ 'mainpagetext' => "'''MediaWiki seti kon bun.'''",
+ 'mainpagedocfooter' => 'Luku na ini a [//meta.wikimedia.org/wiki/Help:Yepi yepibuku] fu si fa fu kebrouki a wikisoftware.
+
+== Moro yepi ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Den seti]
+* [//www.mediawiki.org/wiki/Manual:FAQ Sani di ben aksi furu (FAQ)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Boskopu grupu gi nyun meki]',
+);
+
+/** Swati (SiSwati) */
+$messages['ss'] = array(
+ 'mainpagetext' => "'''i-MediaWiki seyifakeke ngalokuphelele.'''",
+);
+
+/** Seeltersk (Seeltersk)
+ * @author Maartenvdbent
+ */
+$messages['stq'] = array(
+ 'mainpagetext' => "'''Ju MediaWiki Software wuude mäd Ärfoulch installierd.'''",
+ 'mainpagedocfooter' => 'Sjuch ju [//meta.wikimedia.org/wiki/MediaWiki_localization Dokumentation tou de Anpaasenge fon dän Benutseruurfläche] un dät [//meta.wikimedia.org/wiki/Help:Contents Benutserhondbouk] foar Hälpe tou ju Benutsenge un Konfiguration.',
+);
+
+/** Sundanese (Basa Sunda) */
+$messages['su'] = array(
+ 'mainpagetext' => "'''''Software'' MediaWiki geus diinstal.'''",
+ 'mainpagedocfooter' => "Mangga tingal ''[//meta.wikimedia.org/wiki/MediaWiki_localisation documentation on customizing the interface]'' jeung [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide Tungtunan Pamaké] pikeun pitulung maké jeung konfigurasi.",
);
/** Swedish (Svenska)
+ * @author Skalman
* @author WikiPhoenix
*/
$messages['sv'] = array(
@@ -11368,12 +14822,13 @@ $messages['sv'] = array(
'config-page-releasenotes' => 'Utgivningsanteckningar',
'config-page-copying' => 'Kopiering',
'config-page-upgradedoc' => 'Uppgradering',
+ 'config-page-existingwiki' => 'Befintlig wiki',
'config-help-restart' => 'Vill du rensa all sparad data som du har skrivit in och starta om installationen?',
'config-restart' => 'Ja, starta om',
- 'config-sidebar' => '* [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]
+ 'config-sidebar' => '* [//www.mediawiki.org MediaWikis hemsida]
+* [//www.mediawiki.org/wiki/Help:Contents Användarguide]
+* [//www.mediawiki.org/wiki/Manual:Contents Administratörguide]
+* [//www.mediawiki.org/wiki/Manual:FAQ Frågor och svar]
----
* <doclink href=Readme>Läs mig</doclink>
* <doclink href=ReleaseNotes>Utgivningsanteckningar</doclink>
@@ -11386,6 +14841,14 @@ 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-xcache' => '[http://xcache.lighttpd.net/ XCache] är installerad',
+ 'config-apc' => '[http://www.php.net/apc APC] är installerad',
+ 'config-eaccel' => '[http://eaccelerator.sourceforge.net/ eAccelerator] är installerad',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] är installerad',
+ 'config-db-wiki-settings' => 'Identifiera denna wiki',
+ 'config-db-name' => 'Databasnamn:',
+ 'config-db-username' => 'Databas-användarnamn:',
+ 'config-db-password' => 'Databas-lösenord:',
'config-header-mysql' => 'MySQL-inställningar',
'config-header-postgres' => 'PostgreSQL-inställningar',
'config-header-sqlite' => 'SQLite-inställningar',
@@ -11402,6 +14865,40 @@ Använd bara ASCII-bokstäver (a-z, A-Z), siffror (0-9), understreck (_) och bin
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 (-).',
+ 'mainpagetext' => "'''MediaWiki har installerats utan problem.'''",
+ 'mainpagedocfooter' => 'Information om hur wiki-programvaran används finns i [//meta.wikimedia.org/wiki/Help:Contents användarguiden].
+
+== Att komma igång ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista över konfigurationsinställningar]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mail list]',
+);
+
+/** Swahili (Kiswahili)
+ * @author Lloffiwr
+ */
+$messages['sw'] = array(
+ 'mainpagetext' => "'''MediaWiki imefanikiwa kuingizwa.'''",
+ 'mainpagedocfooter' => 'Shauriana na [//meta.wikimedia.org/wiki/Help:Contents Mwongozo wa Mtumiaji] kwa habari juu ya utumiaji wa bidhaa pepe ya wiki.
+
+== Msaada wa kianzio ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Orodha ya mipangilio ya msingi]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ ya MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Orodha ya utoaji wa habari za MediaWiki]',
+);
+
+/** Silesian (Ślůnski)
+ * @author Djpalar
+ */
+$messages['szl'] = array(
+ 'mainpagetext' => "'''Sztalowańy MediaWiki śe udoło.'''",
+ 'mainpagedocfooter' => 'Uobezdrzij [//meta.wikimedia.org/wiki/Help:Contents przewodńik sprowjacza], kaj sům informacyje uo dźołańu uoprogramowańo MediaWiki.
+
+== Na sztart ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista sztalowań konfiguracyje]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Komuńikaty uo nowych wersyjach MediaWiki]',
);
/** Tamil (தமிழ்)
@@ -11415,6 +14912,26 @@ $messages['ta'] = array(
'config-page-language' => 'மொழி',
'config-page-name' => 'பெயர்',
'config-page-options' => 'விருப்பத்தேர்வுகள்',
+ 'mainpagetext' => "'''விக்கி மென்பொருள் வெற்றிகரமாக உள்ளிடப்பட்டது.'''",
+ 'mainpagedocfooter' => 'விக்கி மென்பொருளைப் பயன்படுத்துவது தொடர்பாக [//meta.wikimedia.org/wiki/Help:Contents பயனர் வழிகாட்டியைப்] பார்க்க.
+
+== தொடக்கப்படிகள் ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings அமைப்புக்களை மாற்றம் செய்தல்]
+* [//www.mediawiki.org/wiki/Manual:FAQ மிடியாவிக்கி பொதுவான கேள்விகள்]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce மீடியாவிக்கி வெளியீடு மின்னஞ்சல் பட்டியல்]',
+);
+
+/** Tulu (ತುಳು) */
+$messages['tcy'] = array(
+ 'mainpagetext' => "'''ಮೀಡಿಯವಿಕಿ ಯಶಸ್ವಿಯಾದ್ ಇನ್’ಸ್ಟಾಲ್ ಆಂಡ್.'''",
+ 'mainpagedocfooter' => 'ವಿಕಿ ತಂತ್ರಾಂಶನ್ ಉಪಗೋಗ ಮನ್ಪುನ ಬಗ್ಗೆ ಮಾಹಿತಿಗ್ [//meta.wikimedia.org/wiki/Help:Contents ಸದಸ್ಯೆರ್ನ ನಿರ್ದೇಶನ ಪುಟ] ತೂಲೆ.
+
+== ಎಂಚ ಶುರು ಮಲ್ಪುನಿ ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ ಮೀಡಿಯವಿಕಿ FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]',
);
/** Telugu (తెలుగు)
@@ -11458,6 +14975,7 @@ $messages['te'] = array(
'config-admin-password' => 'సంకేతపదం:',
'config-admin-password-confirm' => 'సంకేతపదం మళ్ళీ:',
'config-admin-email' => 'ఈ-మెయిలు చిరునామా:',
+ 'config-optional-continue' => 'నన్ను మరిన్ని ప్రశ్నలు అడుగు.',
'config-profile-wiki' => 'సంప్రదాయ వికీ',
'config-profile-no-anon' => 'ఖాతా సృష్టింపు తప్పనిసరి',
'config-profile-private' => 'అంతరంగిక వికీ',
@@ -11467,6 +14985,76 @@ $messages['te'] = array(
'config-upload-deleted' => 'తొలగించిన దస్త్రాల కొరకు సంచయం:',
'config-install-step-done' => 'పూర్తయింది',
'config-install-step-failed' => 'విఫలమైంది',
+ 'config-help' => 'సహాయం',
+ 'mainpagetext' => "'''మీడియా వికీని విజయవంతంగా ప్రతిష్టించాం.'''",
+ 'mainpagedocfooter' => 'వికీ సాఫ్టువేరును వాడటనికి కావలిసిన సమాచారం కోసం [//meta.wikimedia.org/wiki/Help:Contents వాడుకరుల గైడు]ను సందర్శించండి.
+
+== మొదలు పెట్టండి ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings మీడియావికీ పనితీరు, అమరిక మార్చుకునేందుకు వీలుకల్పించే చిహ్నాల జాబితా]
+* [//www.mediawiki.org/wiki/Manual:FAQ మీడియావికీపై తరుచుగా అడిగే ప్రశ్నలు]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce మీడియావికీ సాఫ్టువేరు కొత్త వెర్షను విడుదలల గురించి తెలిపే మెయిలింగు లిస్టు]',
+);
+
+/** Tetum (Tetun)
+ * @author MF-Warburg
+ */
+$messages['tet'] = array(
+ 'config-page-language' => 'Lian',
+ 'config-page-name' => 'Naran',
+);
+
+/** Tajik (Cyrillic script) (Тоҷикӣ) */
+$messages['tg-cyrl'] = array(
+ 'mainpagetext' => "'''Нармафзори МедиаВики бо муваффақият насб шуд.'''",
+ 'mainpagedocfooter' => 'Аз [//meta.wikimedia.org/wiki/Help:Contents Роҳнамои Корбарон] барои истифодаи нармафзори вики кӯмак бигиред.
+
+== Оғоз ба кор ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Феҳристи танзимоти пайгирбандӣ]
+* [//www.mediawiki.org/wiki/Manual:FAQ Пурсишҳои МедиаВики]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Феҳристи ройномаҳои нусхаҳои МедиаВики]',
+);
+
+/** Tajik (Latin script) (tojikī)
+ * @author Liangent
+ */
+$messages['tg-latn'] = array(
+ 'mainpagetext' => "'''Narmafzori MediaViki bo muvaffaqijat nasb şud.'''",
+ 'mainpagedocfooter' => 'Az [//meta.wikimedia.org/wiki/Help:Contents Rohnamoi Korbaron] baroi istifodai narmafzori viki kūmak bigired.
+
+== Oƣoz ba kor ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Fehristi tanzimoti pajgirbandī]
+* [//www.mediawiki.org/wiki/Manual:FAQ Pursişhoi MediaViki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Fehristi rojnomahoi nusxahoi MediaViki]',
+);
+
+/** Thai (ไทย)
+ * @author Korrawit
+ */
+$messages['th'] = array(
+ 'mainpagetext' => "'''ติดตั้งซอฟต์แวร์มีเดียวิกิเรียบร้อย'''",
+ 'mainpagedocfooter' => 'ศึกษา[//meta.wikimedia.org/wiki/Help:Contents คู่มือการใช้งาน] สำหรับเริ่มต้นใช้งานซอฟต์แวร์วิกิ
+
+== เริ่มต้น ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings รายการการปรับแต่งระบบ] (ภาษาอังกฤษ)
+* [//www.mediawiki.org/wiki/Manual:FAQ คำถามที่ถามบ่อยในมีเดียวิกิ] (ภาษาอังกฤษ)
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce เมลลิงลิสต์ของมีเดียวิกิ]',
+);
+
+/** Turkmen (Türkmençe)
+ * @author Hanberke
+ */
+$messages['tk'] = array(
+ 'mainpagetext' => "'''MediaWiki şowlulyk bilen guruldy.'''",
+ 'mainpagedocfooter' => 'Wiki programmasynyň ulanylyşy hakynda maglumat almak üçin [//meta.wikimedia.org/wiki/Help:Contents ulanyjy gollanmasyna] serediň.
+
+== Öwrenjeler ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Konfigurasiýa sazlamalary]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki SSS]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-poçta sanawy]',
);
/** Tagalog (Tagalog)
@@ -11477,8 +15065,17 @@ $messages['tl'] = array(
'config-desc' => 'Ang instalador para sa MediaWiki',
'config-title' => 'Instalasyong $1 ng MediaWiki',
'config-information' => 'Kabatiran',
+ 'config-localsettings-cli-upgrade' => 'Napansin ang isang talaksan ng LocalSettings.php.
+Upang isapanahon ang pagtatalagang ito, mangyaring patakbuhin sa halip ang update.php',
'config-localsettings-key' => 'Susi ng pagsasapanahon:',
'config-localsettings-badkey' => 'Hindi tama ang susing ibinigay mo.',
+ 'config-upgrade-key-missing' => 'Napansin ang isang umiiral na pagtatalaga ng MediaWiki.
+Upang isapanahon ang katalagahang ito, mangyaring ilagay ang sumusunod na guhit sa ilalim ng iyong LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'Lumilitaw na hindi pa buo ang umiiral na LocalSettings.php.
+Ang pabagu-bagong $1 ay hindi nakatakda.
+Mangyaring baguhin ang LocalSettings.php upang ang maitakda ang pagpapabagu-bagong ito, at pindutin ang "Magpatuloy".',
'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.',
@@ -11518,10 +15115,10 @@ Ipinamamahagi ang programang ito na umaasang magiging gamitin, subaliut '''walan
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]
+ 'config-sidebar' => '* [//www.mediawiki.org Tahanan ng MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Gabay ng Tagagamit]
+* [//www.mediawiki.org/wiki/Manual:Contents Gabay ng Tagapangasiwa]
+* [//www.mediawiki.org/wiki/Manual:FAQ Mga Malimit Itanong]
----
* <doclink href=Readme>Basahin ako</doclink>
* <doclink href=ReleaseNotes>Mga tala ng paglalabas</doclink>
@@ -11536,16 +15133,21 @@ Hindi mo mailuklok ang MediaWiki.',
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-no-db' => 'Hindi matagpuan ang isang angkop na tagapagmaneho ng kalipunan ng datos! Kailangan mong magluklok ng isang tagapagmaneho ng kalipunan ng dato para sa PHP.
+Tinatangkilik ang sumusunod na mga uri ng kalipunan ng dato: $1.
+
+Kung ikaw ay nasa isang pinagsasaluhang pagpapasinaya, hilingin sa iyong tagapagbigay ng pagpapasinaya na iluklok ang isang angkop na tagapagmaneho ng kalipunan ng dato.
+Kung ikaw mismo ang nangalap ng PHP, muling isaayos ito na pinagagana ang isang kliyente ng kalipunan ng dato, halimbawa na ang paggamit ng <code>./configure --with-mysql</code>.
+Kung iniluklok mo ang PHP mula sa isang pakete ng Debian o Ubuntu, kung gayon kailangan mo ring magluklok ng modyul na php5-mysql.',
'config-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-xcache' => 'Ininstala na ang [http://xcache.lighttpd.net/ 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].
+ 'config-no-cache' => "'''Babala:''' Hindi mahanap ang [http://eaccelerator.sourceforge.net eAccelerator], [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
Hindi pinapagana ang pagbabaon ng mga bagay.",
'config-diff3-bad' => 'Hindi natagpuan ang GNU diff3.',
'config-imagemagick' => 'Natagpuan ang ImageMagick: <code>$1</code>.
@@ -11559,6 +15161,7 @@ Pinigilan ang pag-iinstala.",
'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-name-oracle' => 'Balangkas 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:',
@@ -11578,22 +15181,40 @@ 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-type-ibm_db2' => 'DB2 ng IBM',
'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-header-ibm_db2' => 'Mga katakdaan ng DB2 ng IBM',
'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-missing-db-name' => 'Dapat kang magpasok ng isang halaga para sa "Pangalan ng kalipunan ng dato"',
+ 'config-missing-db-host' => 'Dapat kang magpasok ng isang halaga para sa "Tagapagpasinaya ng kalipunan ng dato"',
+ 'config-missing-db-server-oracle' => 'Dapat kang magpasok ng isang halaga para sa "TNS ng kalipunan ng dato"',
'config-invalid-db-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".
+ '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-connection-error' => '$1.
+
+Suriin ang punong-abala, pangalan ng tagagamit at hudyat na nasa ibaba at subukan ulit.',
'config-postgres-old' => 'Kailangan ang PostgreSQL $1 o mas bago, mayroon kang $2.',
+ 'config-sqlite-mkdir-error' => 'Kamalian sa paglikha ng direktoryo ng datong "$1".
+Suriin ang kinalalagyan at subukang muli.',
+ 'config-sqlite-dir-unwritable' => 'Hindi nagawang magsulat sa direktoryong "$1".
+Baguhin ang mga kapahintulutan nito upang makapagsulat dito ang tagapaghain ng sapot, at subukang muli.',
+ 'config-sqlite-connection-error' => '$1.
+
+Surrin ang direktoryo ng dato at pangalan ng kalipunan ng datong nasa ibaba at subukan uli.',
'config-sqlite-readonly' => 'Ang talaksang <code>$1</code> ay hindi maisusulat.',
'config-sqlite-cant-create-db' => 'Hindi malikha ang talaksang <code>$1</code> ng kalipunan ng dato.',
'config-sqlite-fts3-downgrade' => 'Nawawala ang suportang FTS3 ng PHP, ibinababa ang uri ng mga talahanayan',
+ 'config-upgrade-done-no-regenerate' => 'Buo na ang pagsasapanahon.
+
+Maaari ka na ngayong [$1 magsimula sa paggamit ng wiki mo].',
'config-regenerate' => 'Muling likhain ang LocalSettings.php →',
'config-show-table-status' => 'Nabigo ang pagtatanong na IPAKITA ANG KALAGAYAN NG TALAHANAYAN!',
+ 'config-unknown-collation' => "'''Babala:''' Ang kalipunan ng dato ay gumagagamit ng hindi nakikilalang pag-iipon.",
'config-db-web-account' => 'Akawnt ng kalipunan ng dato para sa pagpunta sa web',
'config-db-web-help' => 'Piliin ang pangalan ng tagagamit at hudyat na gagamitin ng tagapaghain ng web upang umugnay sa tagapaghain ng kalipunan ng dato, habang nasa pangkaraniwang pagtakbo ng wiki.',
'config-db-web-account-same' => 'Gamitin ang gayun din akawnt katulad ng sa pagluluklok',
@@ -11625,6 +15246,7 @@ Tumukoy ng ibang pangalan ng tagagamit.',
'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-admin-error-bademail' => 'Nagpasok ka ng isang hindi katanggap-tanggap na tirahan ng e-liham.',
'config-subscribe' => 'Tumanggap mula sa [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce talaan ng mga pinadadalhan ng mga nilalabas na mga pabatid].',
'config-almost-done' => 'Halos tapos ka na!
Maaari mo ngayong laktawan ang natitira pang pag-aayos at iluklok na ang wiki ngayon.',
@@ -11639,8 +15261,6 @@ Maaari mo ngayong laktawan ang natitira pang pag-aayos at iluklok na ang wiki ng
'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',
@@ -11677,6 +15297,7 @@ Kung hindi mo alam ang daungan, ang likas na nakatakda ay 11211.',
'config-install-step-failed' => 'nabigo',
'config-install-extensions' => 'Isinasama ang mga karugtong',
'config-install-database' => 'Inihahanda ang kalipunan ng dato',
+ 'config-install-pg-schema-not-exist' => 'Hindi umiiral ang panukala ng PostgreSQL.',
'config-install-pg-schema-failed' => 'Nabigo ang paglikha ng mga talahanayan.
Tiyakin na ang tagagamit na "$1" ay maaaring makasulat sa balangkas na "$2".',
'config-install-pg-commit' => 'Isinasagawa ang mga pagbabago',
@@ -11719,6 +15340,71 @@ $3
Kapag nagawa na iyan, maaari ka nang '''[$2 pumasok sa wiki mo]'''.",
'config-download-localsettings' => 'Ikargang paibaba ang LocalSettings.php',
'config-help' => 'saklolo',
+ 'mainpagetext' => "'''Matagumpay na ininstala ang MediaWiki.'''",
+ 'mainpagedocfooter' => "Silipin ang [//meta.wikimedia.org/wiki/Help:Contents Patnubay sa Tagagamit] (''\"User's Guide\"'') para sa kaalaman sa paggamit ng wiking ''software''.
+
+== Pagsisimula ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Tala ng mga nakatakdang kumpigurasyon]
+* [//www.mediawiki.org/wiki/Manual:FAQ Mga malimit itanong sa MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Tala ng mga pinadadalhan ng liham ng MediaWiki]",
+);
+
+/** Turkish (Türkçe) */
+$messages['tr'] = array(
+ 'mainpagetext' => "'''MediaWiki başarı ile kuruldu.'''",
+ 'mainpagedocfooter' => 'Viki yazılımının kullanımı hakkında bilgi almak için [//meta.wikimedia.org/wiki/Help:Contents kullanıcı rehberine] bakınız.
+
+== Yeni Başlayanlar ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Yapılandırma ayarlarının listesi]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki SSS]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki e-posta listesi]',
+);
+
+/** Tatar (Cyrillic script) (Татарча)
+ * @author KhayR
+ */
+$messages['tt-cyrl'] = array(
+ 'mainpagetext' => '«MediaWiki» уңышлы куелды.',
+ 'mainpagedocfooter' => "Бу вики турында мәгълүматны [//meta.wikimedia.org/wiki/Ярдәм:Эчтәлек биредә] табып була.
+
+== Кайбер файдалы ресурслар ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Көйләнмәләр исемлеге (инг.)];
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki турында еш бирелгән сораулар һәм җаваплар (инг.)];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki'ның яңа версияләре турында хәбәрләр яздырып алу].",
+);
+
+/** Tatar (Latin script) (Tatarça)
+ * @author Don Alessandro
+ */
+$messages['tt-latn'] = array(
+ 'mainpagetext' => '«MediaWiki» uñışlı quyıldı.',
+ 'mainpagedocfooter' => "Bu wiki turında mäğlümatnı [//meta.wikimedia.org/wiki/Yärdäm:Eçtälek biredä] tabıp bula.
+
+== Qayber faydalı resurslar ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Köylänmälär isemlege (ing.)];
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki turında yış birelgän sorawlar häm cawaplar (ing.)];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki'nıñ yaña versiäläre turında xäbärlär yazdırıp alu].",
+);
+
+/** Udmurt (Удмурт)
+ * @author Andrewboltachev
+ */
+$messages['udm'] = array(
+ 'mainpagetext' => "'''MediaWiki движок азинлыко пуктэмын.'''",
+);
+
+/** Uyghur (Arabic script) (ئۇيغۇرچە)
+ * @author Sahran
+ */
+$messages['ug-arab'] = array(
+ 'mainpagetext' => "'''MediaWiki مۇۋەپپەقىيەتلىك قاچىلاندى.'''",
+ 'mainpagedocfooter' => '[//meta.wikimedia.org/wiki/Help:Contents ئىشلەتكۈچى قوللانمىسى] نى زىيارەت قىلىپ wiki يۇمشاق دېتالىنى ئىشلىتىش ئۇچۇرىغا ئېرىشىڭ.
+
+== دەسلەپكى ساۋات ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings سەپلىمە تەڭشەك تىزىملىكى]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki كۆپ ئۇچرايدىغان مەسىلىلەرگە جاۋاب]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki تارقاتقان ئېلخەت تىزىملىكى]',
);
/** Ukrainian (Українська)
@@ -11731,8 +15417,8 @@ $messages['uk'] = array(
'config-desc' => 'Інсталятор MediaWiki',
'config-title' => 'Встановлення MediaWiki $1',
'config-information' => 'Інформація',
- 'config-localsettings-upgrade' => "'''Увага''': було виявлено файл <code>LocalSettings.php</code>.
-Ваше програмне забезпечення може бути оновлено.
+ 'config-localsettings-upgrade' => "'''Увага''': було виявлено файл <code>LocalSettings.php</code>.
+Ваше програмне забезпечення може бути оновлено.
Будь-ласка, перемістіть файл <code>LocalSettings.php</code> в іншу безпечну директорію, а потім знову запустіть програму установки.",
'config-session-error' => 'Помилка початку сесії: $1',
'config-your-language' => 'Ваша мова:',
@@ -11760,20 +15446,24 @@ $messages['uk'] = array(
'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-sidebar' => '* [//www.mediawiki.org Сайт MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents/uk Керівництво користувача]
+* [//www.mediawiki.org/wiki/Manual:Contents/uk Керівництво адміністратора]
+* [//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-unicode-pure-php-warning' => "'''Увага''': [http://pecl.php.net/intl міжнародне розширення PECL] не може провести нормалізацію Юнікоду.
+Якщо ваш сайт має високий трафік, вам варто почитати про [//www.mediawiki.org/wiki/Unicode_normalization_considerations нормалізацію Юнікоду].",
+ 'config-no-db' => 'Не вдалося знайти відповідний драйвер бази даних! Вам необхідно встановити драйвер бази даних для PHP. Підтримуються такі типи баз даних: $1.
+
+Якщо ви користуєтесь віртуальним хостингом, попросіть вашого хостинг-провайдера інсталювати відповідний драйвер бази даних.
+Якщо ви скомпілювали PHP самостійно, переналаштуйте його з включенням клієнта бази даних, наприклад за допомогою <code>./configure --with-mysql</code>.
+Якщо установлено PHP з пакетів Debian або Ubuntu, тоді ви також повинні встановити php5-mysql модуль.',
+ 'config-xcache' => '[http://xcache.lighttpd.net/ 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] встановлено',
@@ -11786,7 +15476,7 @@ $messages['uk'] = array(
'config-invalid-db-type' => 'Невірний тип бази даних',
'config-invalid-db-name' => 'Неприпустима назва бази даних "$1".
Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і дефіси (-).',
- 'config-invalid-db-prefix' => 'Неприпустимий префікс бази даних "$1".
+ 'config-invalid-db-prefix' => 'Неприпустимий префікс бази даних "$1".
Використовуйте тільки ASCII букви (a-z, A-Z), цифри (0-9), знаки підкреслення (_) і дефіси (-).',
'config-sqlite-cant-create-db' => 'Не вдалося створити файл бази даних <code>$1</code>.',
'config-db-web-create' => 'Створити обліковий запис, якщо його ще не існує',
@@ -11803,7 +15493,6 @@ $messages['uk'] = array(
'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' => 'Каталог для вилучених файлів:',
@@ -11812,6 +15501,149 @@ $messages['uk'] = array(
'config-install-step-done' => 'виконано',
'config-install-step-failed' => 'не вдалося',
'config-install-interwiki-list' => 'Не вдалося знайти файл <code>interwiki.list</code>.',
+ 'mainpagetext' => 'Програмне забезпечення «MediaWiki» успішно встановлене.',
+ 'mainpagedocfooter' => 'Інформацію про роботу з цією вікі можна знайти в [//meta.wikimedia.org/wiki/%D0%9F%D0%BE%D0%BC%D0%BE%D1%89%D1%8C:%D0%A1%D0%BE%D0%B4%D0%B5%D1%80%D0%B6%D0%B0%D0%BD%D0%B8%D0%B5 посібнику користувача].
+
+== Деякі корисні ресурси ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Список налаштувань];
+* [//www.mediawiki.org/wiki/Manual:FAQ Часті питання з приводу MediaWiki];
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Розсилка повідомлень про появу нових версій MediaWiki].',
+);
+
+/** Urdu (اردو) */
+$messages['ur'] = array(
+ 'mainpagetext' => "'''میڈیاوکی کو کامیابی سے چالو کردیا گیا ہے۔.'''",
+);
+
+/** Uzbek (O'zbek) */
+$messages['uz'] = array(
+ 'mainpagetext' => "'''MediaWiki muvaffaqiyatli o'rnatildi.'''",
+ 'mainpagedocfooter' => "Wiki dasturini ishlatish haqida ma'lumot olish uchun [//meta.wikimedia.org/wiki/Help:Contents Foydalanuvchi qo'llanmasi] sahifasiga murojaat qiling.
+
+== Dastlabki qadamlar ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Moslamalar ro'yxati]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki haqida ko'p so'raladigan savollar]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki yangi versiyasi chiqqanda xabar berish ro'yxati]",
+);
+
+/** Vèneto (Vèneto)
+ * @author Vajotwo
+ */
+$messages['vec'] = array(
+ 'mainpagetext' => "'''Instałasion de MediaWiki conpletà coretamente.'''",
+ 'mainpagedocfooter' => "Varda ła [//meta.wikimedia.org/wiki/Aiuto:Sommario Guida utente] par majori informasion so l'uso de sto software wiki.
+
+== Par scumisiar ==
+
+I seguenti cołegamenti i xé en łengua inglese:
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Inpostasion de configurasion]
+* [//www.mediawiki.org/wiki/Manual:FAQ Domande frequenti so MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailing list anunsi MediaWiki]",
+);
+
+/** Veps (Vepsan kel')
+ * @author Игорь Бродский
+ */
+$messages['vep'] = array(
+ 'mainpagetext' => "'''MediaWiki-likutim om seižutadud jügedusita.'''",
+ 'mainpagedocfooter' => 'Kc. [//meta.wikimedia.org/wiki/Help:Kävutajan abukirj], miše sada informacijad wikin kävutamižes.
+
+== Erased tarbhaižed resursad ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Järgendusiden nimikirjutez]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce počtnimikirjutez]',
+);
+
+/** Vietnamese (Tiếng Việt) */
+$messages['vi'] = array(
+ 'mainpagetext' => "'''MediaWiki đã được cài đặt thành công.'''",
+ 'mainpagedocfooter' => 'Xin đọc [//meta.wikimedia.org/wiki/Help:Contents Hướng dẫn sử dụng] để biết thêm thông tin về cách sử dụng phần mềm wiki.
+
+== Để bắt đầu ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Danh sách các thiết lập cấu hình]
+* [//www.mediawiki.org/wiki/Manual:FAQ Các câu hỏi thường gặp MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Danh sách gửi thư về việc phát hành MediaWiki]',
+);
+
+/** Volapük (Volapük) */
+$messages['vo'] = array(
+ 'mainpagetext' => "'''El MediaWiki pestiton benosekiko.'''",
+ 'mainpagedocfooter' => 'Konsultolös [//meta.wikimedia.org/wiki/Help:Contents Gebanageidian] ad tuvön nünis dö geb programema vükik.
+
+== Nüdugot ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Parametalised]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki: SSP]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Potalised tefü fomams nulik ela MediaWiki]',
+);
+
+/** Võro (Võro) */
+$messages['vro'] = array(
+ 'mainpagetext' => "'''MediaWiki tarkvara paika säet.'''",
+ 'mainpagedocfooter' => 'Vikitarkvara pruukmisõ kotsilõ loeq mano:
+* [//meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide MediaWiki pruukmisoppus (inglüse keelen)].
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Säädmiisi oppus (inglüse keelen)]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki kõgõ küsütümbäq küsümiseq (inglüse keelen)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce E-postilist, minka andas teedäq MediaWiki vahtsist kujõst].',
+);
+
+/** Walloon (Walon) */
+$messages['wa'] = array(
+ 'mainpagetext' => "'''Li programe Wiki a stî astalé a l' idêye.'''",
+);
+
+/** Waray (Winaray)
+ * @author Harvzsf
+ */
+$messages['war'] = array(
+ 'mainpagetext' => "'''Malinamposon an pag-instalar han MediaWiki.'''",
+ 'mainpagedocfooter' => "Kitaa an [//meta.wikimedia.org/wiki/Help:Contents User's Guide] para hin impormasyon ha paggamit han wiki nga softweyr.
+
+== Ha pagtikang==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Wolof (Wolof) */
+$messages['wo'] = array(
+ 'mainpagetext' => "'''Campug MediaWiki gi sotti na . '''",
+ 'mainpagedocfooter' => 'Saytul [//meta.wikimedia.org/wiki/Ndimbal:Ndefu Gindikaayu jëfandikukat bi] ngir yeneeni xibaar ci jëfandiku gu tëriin gi.
+
+== Tambali ak MediaWiki ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Limu jumtukaayi kocc-koccal gi]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ MediaWiki]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Limu waxtaan ci liy-génn ci MediaWiki]',
+);
+
+/** Wu (吴语)
+ * @author Wu-chinese.com
+ */
+$messages['wuu'] = array(
+ 'mainpagetext' => "'''MediaWiki安装成功哉!'''",
+ 'mainpagedocfooter' => '请访问[//meta.wikimedia.org/wiki/Help:Contents 用户手册]以获得使用此维基软件个信息!
+
+== 入门 ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置设置列表]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常见问题解答]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 发布邮件列表]',
+);
+
+/** Kalmyk (Хальмг)
+ * @author Huuchin
+ */
+$messages['xal'] = array(
+ 'mainpagetext' => "Йовудта Mediawiki гүүлһүдә тәвллһн.'''",
+ 'mainpagedocfooter' => 'Тер бики закллһна теткүл ю кеһәд олзлх туск [//meta.wikimedia.org/wiki/Help:Contents көтлвр] дастн.
+
+== Туста заавр ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Көгүдә бүрткл]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki туск ЮмБи]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki шинҗллһнә бүрткл]',
);
/** Yiddish (ייִדיש)
@@ -11819,11 +15651,55 @@ $messages['uk'] = array(
*/
$messages['yi'] = array(
'config-admin-name' => 'אײַער נאָמען:',
+ 'mainpagetext' => "'''מעדיעוויקי אינסטאלירט מיט דערפאלג.'''",
+ 'mainpagedocfooter' => "גיט זיך אן עצה מיט [//meta.wikimedia.org/wiki/Help:Contents באניצער'ס וועגווײַזער] פֿאר אינפֿארמאציע וויאזוי זיך באנוצן מיט וויקי ווייכוואַרג.
+
+== נוצליכע וועבלינקען פֿאַר אנהייבערס ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings רשימה פון קאנפֿיגוראציעס]
+* [//www.mediawiki.org/wiki/Manual:FAQ אפֿט געפֿרעגטע שאלות]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce מעדיעוויקי באפֿרײַאונג פאסטליסטע]",
+);
+
+/** Yoruba (Yorùbá)
+ * @author Demmy
+ */
+$messages['yo'] = array(
+ 'mainpagetext' => "'''MediaWiki ti jẹ́ gbígbékọ́sínú láyọrísírere.'''",
+ 'mainpagedocfooter' => "Ẹ ṣàbẹ̀wò sí [//meta.wikimedia.org/wiki/Help:Contents User's Guide] fún ìfitólétí nípa líló atòlànà wíkì.
+
+== Láti bẹ̀rẹ̀ ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]",
+);
+
+/** Cantonese (粵語) */
+$messages['yue'] = array(
+ 'mainpagetext' => "'''MediaWiki已經裝好。'''",
+ 'mainpagedocfooter' => '參閱[//meta.wikimedia.org/wiki/Help:Contents 用戶指引](英),裏面有資料講點用wiki軟件。
+
+==開始使用==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings 配置設定清單](英)
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題](英)
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件名單](英)',
+);
+
+/** Zeeuws (Zeêuws) */
+$messages['zea'] = array(
+ 'mainpagetext' => "'''De installaotie van MediaWiki is geslaegd.'''",
+ 'mainpagedocfooter' => "Raedpleeg de [//meta.wikimedia.org/wiki/ZEA_Ulpe:Inhoudsopgaeve andleidieng] voe informatie over 't gebruuk van de wikisoftware.
+
+== Meer ulpe over MediaWiki ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lieste mie instelliengen]
+* [//www.mediawiki.org/wiki/Manual:FAQ Veehestelde vraehen (FAQ)]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Mailienglieste voe ankondigiengen van nieuwe versies]",
);
/** Simplified Chinese (‪中文(简体)‬)
* @author Hydra
* @author PhiLiP
+ * @author Xiaomingyan
* @author 阿pp
*/
$messages['zh-hans'] = array(
@@ -11831,7 +15707,7 @@ $messages['zh-hans'] = array(
'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-cli-upgrade' => '已检测到LocalSettings.php文件。要升级该配置,请直接运行update.php。',
'config-localsettings-key' => '升级密钥:',
'config-localsettings-badkey' => '您提供的密钥不正确。',
'config-upgrade-key-missing' => '检测到MediaWiki的配置已经存在。若要升级该配置,请将下面一行文本添加到LocalSettings.php的底部:
@@ -11878,24 +15754,27 @@ $1',
本程序是基于使用目的而加以发布,然而'''不负任何担保责任''';亦无对'''适售性'''或'''特定目的适用性'''所为的默示性担保。详情请参照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-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/zh-hans MediaWiki首页]
+* [//www.mediawiki.org/wiki/Help:Contents/zh-hans 用户指南]
+* [//www.mediawiki.org/wiki/Manual:Contents 管理员指南]
+* [//www.mediawiki.org/wiki/Manual:FAQ/zh-hans 常见问题解答]
+----
+* <doclink href=Readme>自述文件</doclink>
+* <doclink href=ReleaseNotes>发行说明</doclink>
+* <doclink href=Copying>协议副本</doclink>
+* <doclink href=UpgradeDoc>升级</doclink>',
'config-env-good' => '环境检查已经完成。您可以安装MediaWiki。',
'config-env-bad' => '环境检查已经完成。您不能安装MediaWiki。',
'config-env-php' => 'PHP $1已安装。',
+ 'config-env-php-toolow' => '已安装PHP $1;但是,MediaWiki需要PHP $2或更高版本。',
'config-unicode-using-utf8' => '使用Brion Vibber的utf8_normalize.so实现Unicode正常化。',
'config-unicode-using-intl' => '使用[http://pecl.php.net/intl intl PECL扩展]实现Unicode正常化。',
- 'config-unicode-pure-php-warning' => "'''警告''':[http://pecl.php.net/intl intl PECL扩展]无法处理Unicode正常化,故只能退而采用运行较慢的纯PHP实现的方法。如果您运行着一个高流量的站点,请参阅[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-unicode-pure-php-warning' => "'''警告''':[http://pecl.php.net/intl intl PECL扩展]无法处理Unicode正常化,故只能退而采用运行较慢的纯PHP实现的方法。如果您运行着一个高流量的站点,请参阅[//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode正常化]一文。",
+ 'config-unicode-update-warning' => "'''警告''':Unicode正常化封装器的已安装版本使用了旧版本的[http://site.icu-project.org/ ICU项目]库。如果您需要使用Unicode,请将其[//www.mediawiki.org/wiki/Unicode_normalization_considerations 升级]。",
+ 'config-no-db' => '找不到合适的数据库驱动!您需要为PHP安装数据库驱动。目前支持以下数据库:$1。
+
+如果您正在使用共享主机,请向您的主机提供商申请安装合适的数据库驱动。如果您通过自行编译安装的PHP,请对其进行重新配置以启用数据库客户端,例如使用<code>./configure --with-mysql</code>。如果您通过Debian或Ubuntu包安装的PHP,您还需要安装php5-mysql模块。',
+ 'config-no-fts3' => "'''警告''':已编译的SQLite不包含[//sqlite.org/fts3.html FTS3模块],后台搜索功能将不可用。",
'config-register-globals' => "'''警告:PHP的<code>[http://php.net/register_globals register_globals]</code>选项被启用。请尽量禁用该功能,'''虽然不会影响MediaWiki的运行,但您的服务器会被暴露给潜在的安全漏洞。",
'config-magic-quotes-runtime' => "'''致命错误:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]被启用!'''
此选项会无法预测地破坏输入的数据,请将其禁用,否则您将不能安装或使用MediaWiki。",
@@ -11911,20 +15790,25 @@ $1',
'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-xcache' => '[http://xcache.lighttpd.net/ 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-no-cache' => "'''警告:'''找不到[http://eaccelerator.sourceforge.net eAccelerator]、[http://www.php.net/apc APC]、[http://xcache.lighttpd.net/ 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-no-cli-uri' => "'''警告''':未指定--scriptpath参数,使用默认值:<code>$1</code>。",
+ 'config-using-server' => '使用服务器名“<nowiki>$1</nowiki>”。',
+ 'config-using-uri' => '使用服务器URL“<nowiki>$1$2</nowiki>”。',
+ 'config-uploads-not-safe' => "'''警告:'''您的默认上传目录<code>$1</code>存在允许执行任意脚本的漏洞。尽管MediaWiki会对所有已上传的文件进行安全检查,但我们仍然强烈建议您在启用上传功能前[//www.mediawiki.org/wiki/Manual:Security#Upload_security 关闭该安全漏洞]。",
+ 'config-no-cli-uploads-check' => "'''警告''':在CLI安装过程中,没有对您的默认上传目录(<code>$1</code>)进行执行任意脚本的漏洞检查。",
+ 'config-brokenlibxml' => '您的系统安装的PHP和libxml2版本组合存在故障,并可能在MediaWiki和其他web应用程序中造成隐藏的数据损坏。请将PHP升级到5.2.9或以上,libxml2升级到2.7.3或以上([//bugs.php.net/bug.php?id=45996 PHP的故障报告])。安装已中断。',
+ 'config-using531' => '由于函数<code>__call()</code>的引用参数存在故障,PHP $1和MediaWiki无法兼容。请升级到PHP 5.3.2或更高版本,或降级到PHP 5.3.0以修复该问题。安装已中断。',
+ 'config-suhosin-max-value-length' => 'Suhosin已经安装并将GET请求的参数长度限制在$1字节。MediaWiki的ResourceLoader部件可以在此限制下正常工作,但其性能会被降低。如果可能,请在php.ini中将suhosin.get.max_value_length设为1024或更高值,并在LocalSettings.php中将$wgResourceLoaderMaxQueryLength设为同一值。',
'config-db-type' => '数据库类型:',
'config-db-host' => '数据库主机:',
'config-db-host-help' => '如果您的数据库位于另一台服务器上,在此输入主机名或IP地址。
@@ -11940,9 +15824,15 @@ Object caching is not enabled.",
如果您正在使用共享web主机,您的主机提供商或会给您指定一个数据库名称,或会让您通过控制面板创建数据库。',
'config-db-name-oracle' => '数据库模式:',
+ 'config-db-account-oracle-warn' => '现有三种已支持方案可以将Oracle设置为后端数据库:
+
+如果您希望在安装过程中创建数据库帐户,请为安装程序提供具有SYSDBA角色的数据库帐户,并为web访问帐户指定所需身份证明;否则您可以手动创建web访问的账户并仅须提供该帐户(确保帐户已有创建方案对象(schema object)的所需权限);或提供两个不同的帐户,其一具有创建权限,另一则被限制为web访问。
+
+具有所需权限账户的创建脚本存放于本程序的“maintenance/oracle/”目录下。请注意,使用受限制的帐户将禁用默认帐户的所有维护性功能。',
'config-db-install-account' => '用于安装的用户帐号',
'config-db-username' => '数据库用户名:',
'config-db-password' => '数据库密码:',
+ 'config-db-password-empty' => '请为新数据库用户$1输入密码。尽管您可以创建不使用密码的用户,但这样做并不安全。',
'config-db-install-username' => '请输入在安装过程中用于连接数据库的用户名。请勿输入MediaWiki帐号的用户名,请输入您数据库的用户名。',
'config-db-install-password' => '请输入在安装过程中用于连接数据库的密码。请勿输入MediaWiki帐号的密码,请输入您数据库的密码。',
'config-db-install-help' => '请输入在安装过程中用于连接数据库的用户名和密码。',
@@ -11961,11 +15851,12 @@ Object caching is not enabled.",
在'''二进制模式'''下,MediaWiki会将UTF-8编码的文本存于数据库的二进制字段中。相对于MySQL的UTF-8模式,这种方法效率更高,并允许您使用全范围的Unicode字符。
-在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[http://zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
+在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[//zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
'config-mysql-old' => '需要MySQL $1或更新的版本,您的版本为$2。',
'config-db-port' => '数据库端口:',
'config-db-schema' => 'MediaWiki的数据库模式',
- 'config-db-schema-help' => '上述数据库模式的设置通常是正确的。请在有此需求时才更改它们。',
+ 'config-db-schema-help' => '此数据库模式通常是正确的,请在有明确需求时才改动之。',
+ 'config-pg-test-error' => "无法连接到数据库'''$1''':$2",
'config-sqlite-dir' => 'SQLite数据目录:',
'config-sqlite-dir-help' => "SQLite会将所有的数据存储于单一文件中。
@@ -11978,19 +15869,22 @@ Object caching is not enabled.",
请考虑将数据库统一放置在某处,如<code>/var/lib/mediawiki/yourwiki</code>下。",
'config-oracle-def-ts' => '默认表空间:',
'config-oracle-temp-ts' => '临时表空间:',
+ 'config-type-ibm_db2' => 'IBM DB2',
'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-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-support-ibm_db2' => '* $1是一种商用企业级数据库。',
'config-header-mysql' => 'MySQL设置',
'config-header-postgres' => 'PostgreSQL设置',
'config-header-sqlite' => 'SQLite设置',
'config-header-oracle' => 'Oracle设置',
+ 'config-header-ibm_db2' => 'IBM DB2设置',
'config-invalid-db-type' => '无效的数据库类型',
'config-missing-db-name' => '您必须为“数据库名称”输入内容',
'config-missing-db-host' => '您必须为“数据库主机”输入内容',
@@ -12002,6 +15896,8 @@ $1
请检查下列的主机、用户名和密码设置后重试。',
'config-invalid-schema' => '无效的MediaWiki数据库模式“$1”。请只使用ASCII字母(a-z、A-Z)、数字(0-9)和下划线(_)。',
+ 'config-db-sys-create-oracle' => '安装程序仅支持使用SYSDBA帐户创建新帐户。',
+ 'config-db-sys-user-exists-oracle' => '用户帐户“$1”已经存在。SYSDBA仅可用于创建新帐户!',
'config-postgres-old' => '需要PostgreSQL $1或更新的版本,您的版本为$2。',
'config-sqlite-name-help' => '请为您的wiki指定一个用于标识的名称。请勿使用空格或连字号,该名称将被用作SQLite的数据文件名。',
'config-sqlite-parent-unwritable-group' => '由于父目录<code><nowiki>$2</nowiki></code>对网页服务器不可写,无法创建数据目录<code><nowiki>$1</nowiki></code>。
@@ -12047,6 +15943,13 @@ chmod a+w $3</pre>',
'config-mysql-engine' => '存储引擎:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''警告''':您选择了MyISAM作为MySQL的存储引擎,MediaWiki并不推荐您这么做,因为:
+* 它仅能通过表锁定来勉强支持并发
+* 与其他引擎相比,它更容易被损坏
+* MediaWiki代码库并不总会去处理MyISAM
+
+如果您的MySQL程序支持InnoDB,我们高度推荐您使用该引擎替代MyISAM。
+如果您的MySQL程序不支持InnoDB,请考虑升级。",
'config-mysql-engine-help' => "'''InnoDB'''通常是最佳选项,因为它对并发操作有着良好的支持。
'''MyISAM'''在单用户或只读环境下可能会有更快的性能表现。但MyISAM数据库出错的概率一般要大于InnoDB数据库。",
@@ -12055,7 +15958,8 @@ chmod a+w $3</pre>',
'config-mysql-utf8' => 'UTF-8',
'config-mysql-charset-help' => "在'''二进制模式'''下,MediaWiki会将UTF-8编码的文本存于数据库的二进制字段中。相对于MySQL的UTF-8模式,这种方法效率更高,并允许您使用全范围的Unicode字符。
-在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[http://zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
+在'''UTF-8模式'''下,MySQL将知道您数据使用的字符集,并能适当地提供和转换内容。但这样做您将无法在数据库中存储[//zh.wikipedia.org/wiki/基本多文种平面 基本多文种平面]以外的字符。",
+ 'config-ibm_db2-low-db-pagesize' => "您的DB2数据库默认表空间的页长(pagesize)不足。至少需要'''32K'''或更大的页长。",
'config-site-name' => 'Wiki的名称:',
'config-site-name-help' => '填入的内容会出现在浏览器的标题栏以及其他多处位置中。',
'config-site-name-blank' => '输入网站的名称。',
@@ -12066,6 +15970,7 @@ chmod a+w $3</pre>',
'config-ns-other-default' => '我的Wiki',
'config-project-namespace-help' => "依循维基百科形成的惯例,许多wiki将他们的方针页面存放在与内容页面不同的“'''项目名字空间'''”中。所有位于该名字空间下的页面标题都会被冠以固定的前缀,您可以在此处指定这一前缀。传统上,这一前缀应与wiki的命名保持一致,但请勿在其中使用标点符号,如“#”或“:”。",
'config-ns-invalid' => '指定的名字空间“<nowiki>$1</nowiki>”无效,请为项目名字空间指定其他名称。',
+ 'config-ns-conflict' => '指定的名字空间“<nowiki>$1</nowiki>”与默认的MediaWiki名字空间冲突。请指定一个不同的项目名字空间。',
'config-admin-box' => '管理员帐号',
'config-admin-name' => '您的名字:',
'config-admin-password' => '密码:',
@@ -12077,12 +15982,13 @@ chmod a+w $3</pre>',
'config-admin-password-same' => '密码不能和用户名相同。',
'config-admin-password-mismatch' => '两次输入的密码并不相同。',
'config-admin-email' => '电子邮件地址:',
- 'config-admin-email-help' => '在此输入电子邮件地址,这样您将可以收到本wiki上的其他用户发来的电子邮件,可以重置您的密码,并能在监视列表中的页面被更改时收到邮件通知。',
+ 'config-admin-email-help' => '输入电子邮件地址后,您可以收到此wiki上其他用户发来的电子邮件,并能重置您的密码,还可在监视列表中页面被更改时收到邮件通知。您可以将此字段留空。',
'config-admin-error-user' => '在创建用户名为“<nowiki>$1</nowiki>”的管理员帐号时发生内部错误。',
'config-admin-error-password' => '在为管理员“<nowiki>$1</nowiki>”设置密码时发生内部错误:<pre>$2</pre>',
'config-admin-error-bademail' => '您输入了无效的电子邮件地址。',
'config-subscribe' => '订阅[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 发行公告邮件列表]。',
'config-subscribe-help' => '此低流量的邮件列表仅用于发行公告,其中包括重要安全公告。请订阅该列表以便在新的版本推出时升级您的MediaWiki。',
+ 'config-subscribe-noemail' => '您选择了订阅发行公告邮件列表,但没有提供电子邮件地址。请提供一个电子邮件地址以订阅邮件列表。',
'config-almost-done' => '您几乎已经完成了!现在您可以跳过剩下的配置流程并立即安装wiki。',
'config-optional-continue' => '多问我一些问题吧。',
'config-optional-skip' => '我已经不耐烦了,赶紧安装我的wiki。',
@@ -12099,20 +16005,21 @@ chmod a+w $3</pre>',
'''{{int:config-profile-fishbowl}}'''模式只允许获批准的用户编辑,但对公众开放页面浏览(包括历史记录)。'''{{int:config-profile-private}}'''则只允许获批准的用户浏览、编辑页面。
-安装完成后,您还可以对用户权限进行更多、更复杂的配置,参见[http://www.mediawiki.org/wiki/Manual:User_rights 相关的使用手册]。",
+安装完成后,您还可以对用户权限进行更多、更复杂的配置,参见[//www.mediawiki.org/wiki/Manual:User_rights 相关的使用手册]。",
'config-license' => '版权和许可证:',
'config-license-none' => '页脚无许可证',
'config-license-cc-by-sa' => '知识共享署名-相同方式分享',
+ 'config-license-cc-by' => '知识共享署名',
'config-license-cc-by-nc-sa' => '知识共享署名-非商业性使用-相同方式共享',
- 'config-license-gfdl-old' => 'GNU自由文档许可证 1.2',
- 'config-license-gfdl-current' => 'GNU自由文档许可证 1.3或更高版本',
+ 'config-license-cc-0' => '知识共享Zero(公有领域)',
+ 'config-license-gfdl' => 'GNU自由文档许可证1.3或更高版本',
'config-license-pd' => '公有领域',
'config-license-cc-choose' => '选择自定义的知识共享许可证',
'config-license-help' => "许多公共wiki会以[http://freedomdefined.org/Definition 自由许可证]的方式释放出编者的所有贡献。这有助于构建社区的主人翁意识,并能鼓励长期贡献。对于非公共wiki或公司wiki,这并非必要条件。
如果您希望使用来自维基百科的内容,并希望维基百科能接受复制自您的wiki的内容,请选择'''知识共享署名-相同方式共享'''。
-GNU自由文档许可证是维基百科曾经使用过的许可证,并迄今有效。然而,该许可证的一些特性会增加重用或演绎内容的难度。",
+GNU自由文档许可证是维基百科曾经使用过的许可证,并迄今有效。然而,该许可证难以理解,并会增加重用内容的难度。",
'config-email-settings' => '电子邮件设置',
'config-enable-email' => '启用出站电子邮件',
'config-enable-email-help' => '如果您希望使用电子邮件功能,请正确配置[http://www.php.net/manual/en/mail.configuration.php PHP的邮件设定]。如果您不需要任何电子邮件功能,请在此处禁用它。',
@@ -12128,17 +16035,17 @@ GNU自由文档许可证是维基百科曾经使用过的许可证,并迄今
'config-email-sender-help' => '输入要用来发送出站电子邮件的地址,该地址将会收到被拒收的邮件。许多邮件服务器要求域名部分必须有效。',
'config-upload-settings' => '图像和文件上传',
'config-upload-enable' => '启用文件上传',
- 'config-upload-help' => '文件上传可能会将您的服务器暴露在安全风险下。有关更多的信息,请参阅手册的[http://www.mediawiki.org/wiki/Manual:Security 安全部分]。
+ 'config-upload-help' => '文件上传可能会将您的服务器暴露在安全风险下。有关更多的信息,请参阅手册的[//www.mediawiki.org/wiki/Manual:Security 安全部分]。
要启用文件上传,请先将MediaWiki根目录下的<code>images</code>子目录更改为对web服务器可写,然后再启用此选项。',
'config-upload-deleted' => '已删除文件的目录:',
'config-upload-deleted-help' => '指定用于存放被删除文件的目录。理想情况下,该目录不应能通过web访问。',
'config-logo' => '标志URL:',
- 'config-logo-help' => '在MediaWiki的默认外观中,左上角部位有一块135x160像素的区域可用于展示站点的标志。请上传一幅相应大小的图像,并在此输入URL。
+ 'config-logo-help' => '在MediaWiki的默认外观中,左侧栏菜单之上有一块135x160像素的标志区。请上传一幅相应大小的图像,并在此输入URL。
如果您不希望使用标志,请将本处留空。',
'config-instantcommons' => '启用即时共享资源',
- 'config-instantcommons-help' => '[http://www.mediawiki.org/wiki/InstantCommons 即时共享资源]可以让wiki使用来自[http://commons.wikimedia.org/ 维基共享资源]网站的图像、音频和其他媒体文件。要启用该功能,MediaWiki必须能够访问互联网。
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons 即时共享资源]可以让wiki使用来自[//commons.wikimedia.org/ 维基共享资源]网站的图像、音频和其他媒体文件。要启用该功能,MediaWiki必须能够访问互联网。
有关此功能的详细信息,包括如何将其他wiki网站设为具有类似共享功能的方法,请参考[http://mediawiki.org/wiki/Manual:$wgForeignFileRepos 手册]。',
'config-cc-error' => '知识共享许可证挑选器无法找到结果,请手动输入许可证的名称。',
@@ -12151,25 +16058,40 @@ GNU自由文档许可证是维基百科曾经使用过的许可证,并迄今
'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-memcached-help' => '用于Memcached的IP地址列表。请保持每行一条,并指定要使用的端口。例如:
+127.0.0.1:11211
+192.168.1.25:1234',
+ 'config-memcache-needservers' => '您选择了Memcached作为您的缓存,但并未指定任何服务器。',
+ 'config-memcache-badip' => '您为Memcached输入了无效的IP地址:$1。',
+ 'config-memcache-noport' => '您没有指定Memcached服务器的端口:$1。如果您不清楚端口是多少,默认值为11211。',
+ 'config-memcache-badport' => 'Memcached的端口号应该在$1到$2之间。',
'config-extensions' => '扩展',
'config-extensions-help' => '已在您的<code>./extensions</code>目录中发现下列扩展。
您可能要对它们进行额外的配置,但您现在可以启用它们。',
'config-install-alreadydone' => "'''警告:'''您似乎已经安装了MediaWiki,并试图重新安装它。请前往下一个页面。",
- 'config-install-begin' => '点击继续后,您将开始安装MediaWiki。如果您还想对配置作一些修改,请点击后退。',
+ 'config-install-begin' => '点击“{{int:config-continue}}”后,您将开始安装MediaWiki。如果您还想对配置作一些修改,请点击后退。',
'config-install-step-done' => '完成',
'config-install-step-failed' => '失败',
'config-install-extensions' => '正在启用扩展',
'config-install-database' => '正在配置数据库',
+ 'config-install-schema' => '创建架构',
'config-install-pg-schema-not-exist' => 'PostgreSQL 架构不存在',
'config-install-pg-schema-failed' => '创建数据表失败。请确保用户“$1”拥有写入模式“$2”的权限。',
'config-install-pg-commit' => '正在提交更改',
'config-install-pg-plpgsql' => '正在检查PL/pgSQL语言',
'config-pg-no-plpgsql' => '您需要为数据库$1安装PL/pgSQL语言',
'config-pg-no-create-privs' => '为安装程序指定的帐号缺少创建帐号的权限。',
+ 'config-pg-not-in-role' => '您指定为web用户的帐户已经存在。
+您给本程序指定的帐户不是超级用户,也不是web用户角色的成员,所以它不能创建web用户所拥有的对象。
+
+MediaWiki当前需要使用由web用户所有的表。请指定另一个web帐户名称,或点击“后退”并指定具有适当权限的安装用户。',
'config-install-user' => '正在创建数据库用户',
+ 'config-install-user-alreadyexists' => '用户“$1”已存在',
+ 'config-install-user-create-failed' => '创建用户“$1”失败:$2',
'config-install-user-grant-failed' => '授予用户“$1”权限失败:$2',
+ 'config-install-user-missing' => '指定的用户“$1”不存在。',
+ 'config-install-user-missing-create' => '指定的用户“$1”不存在。如果您想要创建一名,请点选“创建帐户”下面的复选框。',
'config-install-tables' => '正在创建数据表',
'config-install-tables-exist' => "'''警告''':MediaWiki的数据表似乎已经存在,跳过创建。",
'config-install-tables-failed' => "'''错误''':创建数据表出错,下为错误信息:$1",
@@ -12177,9 +16099,11 @@ GNU自由文档许可证是维基百科曾经使用过的许可证,并迄今
'config-install-interwiki-list' => '找不到文件<code>interwiki.list</code>。',
'config-install-interwiki-exists' => "'''警告''':跨wiki数据表似乎已有内容,跳过默认列表。",
'config-install-stats' => '初始化统计',
- 'config-install-keys' => '正在生成密钥',
+ 'config-install-keys' => '生成密钥中',
+ 'config-insecure-keys' => "'''警告''':在安装过程中生成的{{PLURAL:$2|安全密钥|安全密钥}}($1){{PLURAL:$2|并|并}}不一定安全。请考虑手动更改{{PLURAL:$2|它|它们}}。",
'config-install-sysop' => '正在创建管理员用户帐号',
- 'config-install-subscribe-fail' => '无法订阅mediawiki-announce',
+ 'config-install-subscribe-fail' => '无法订阅mediawiki-announce:$1',
+ 'config-install-subscribe-notpossible' => '没有安装cURL,allow_url_fopen也不可用。',
'config-install-mainpage' => '正在创建显示默认内容的首页',
'config-install-extension-tables' => '正在为已启用扩展创建数据表',
'config-install-mainpage-failed' => '无法插入首页:$1',
@@ -12199,6 +16123,13 @@ $3
当本步骤完成后,您可以 '''[$2 进入您的wiki]'''。",
'config-download-localsettings' => '下载LocalSettings.php',
'config-help' => '帮助',
+ 'mainpagetext' => "'''已成功安装MediaWiki。'''",
+ 'mainpagedocfooter' => '请查阅[//meta.wikimedia.org/wiki/Help:Contents 用户指南]以获取使用本wiki软件的信息!
+
+== 入门 ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置设置列表]
+* [//www.mediawiki.org/wiki/Manual:FAQ/zh-hans MediaWiki常见问题]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki发布邮件列表]',
);
/** Traditional Chinese (‪中文(繁體)‬)
@@ -12264,5 +16195,35 @@ $messages['zh-hant'] = array(
'config-install-step-failed' => '失敗',
'config-install-pg-commit' => '提交更改',
'config-help' => '說明',
+ 'mainpagetext' => "'''已成功安裝 MediaWiki。'''",
+ 'mainpagedocfooter' => '請參閱[//meta.wikimedia.org/wiki/Help:Contents 用戶手冊]以獲得使用此 wiki 軟體的訊息!
+
+== 入門 ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定清單]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題解答]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件清單]',
+);
+
+/** Chinese (Hong Kong) (‪中文(香港)‬)
+ * @author Mark85296341
+ */
+$messages['zh-hk'] = array(
+ 'mainpagedocfooter' => '請參閱[//meta.wikimedia.org/wiki/Help:Contents 用戶手冊]以獲得使用此 wiki 軟件的訊息!
+
+== 入門 ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定清單]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題解答]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件清單]',
+);
+
+/** Chinese (Taiwan) (‪中文(台灣)‬) */
+$messages['zh-tw'] = array(
+ 'mainpagedocfooter' => '請參閱 [//meta.wikimedia.org/wiki/Help:Contents 使用者手冊] 以獲得使用此 wiki 軟體的訊息!
+
+== 入門 ==
+
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings MediaWiki 配置設定清單]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki 常見問題解答]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki 發佈郵件清單]',
);
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
index 9ae5e3f3..8101f7d6 100644
--- a/includes/installer/Installer.php
+++ b/includes/installer/Installer.php
@@ -73,6 +73,7 @@ abstract class Installer {
'postgres',
'oracle',
'sqlite',
+ 'ibm_db2',
);
/**
@@ -98,6 +99,7 @@ abstract class Installer {
'envCheckCache',
'envCheckDiff3',
'envCheckGraphics',
+ 'envCheckServer',
'envCheckPath',
'envCheckExtension',
'envCheckShellLocale',
@@ -130,6 +132,7 @@ abstract class Installer {
'wgDiff3',
'wgImageMagickConvertCommand',
'IP',
+ 'wgServer',
'wgScriptPath',
'wgScriptExtension',
'wgMetaNamespace',
@@ -236,6 +239,10 @@ abstract class Installer {
* @var array
*/
public $licenses = array(
+ 'cc-by' => array(
+ 'url' => 'http://creativecommons.org/licenses/by/3.0/',
+ 'icon' => '{$wgStylePath}/common/images/cc-by.png',
+ ),
'cc-by-sa' => array(
'url' => 'http://creativecommons.org/licenses/by-sa/3.0/',
'icon' => '{$wgStylePath}/common/images/cc-by-sa.png',
@@ -249,14 +256,10 @@ abstract class Installer {
'icon' => '{$wgStylePath}/common/images/cc-0.png',
),
'pd' => array(
- 'url' => 'http://creativecommons.org/licenses/publicdomain/',
+ 'url' => '',
'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(
+ 'gfdl' => array(
'url' => 'http://www.gnu.org/copyleft/fdl.html',
'icon' => '{$wgStylePath}/common/images/gnu-fdl.png',
),
@@ -310,7 +313,7 @@ abstract class Installer {
* Constructor, always call this from child classes.
*/
public function __construct() {
- global $wgExtensionMessagesFiles, $wgUser, $wgHooks;
+ global $wgExtensionMessagesFiles, $wgUser;
// Disable the i18n cache and LoadBalancer
Language::getLocalisationCache()->disableBackend();
@@ -329,12 +332,14 @@ abstract class Installer {
$this->settings[$var] = $GLOBALS[$var];
}
+ $compiledDBs = array();
foreach ( self::getDBTypes() as $type ) {
$installer = $this->getDBInstaller( $type );
if ( !$installer->isCompiled() ) {
continue;
}
+ $compiledDBs[] = $type;
$defaults = $installer->getGlobalDefaults();
@@ -346,6 +351,7 @@ abstract class Installer {
}
}
}
+ $this->setVar( '_CompiledDBs', $compiledDBs );
$this->parserTitle = Title::newFromText( 'Installer' );
$this->parserOptions = new ParserOptions; // language will be wrong :(
@@ -354,6 +360,8 @@ abstract class Installer {
/**
* Get a list of known DB types.
+ *
+ * @return array
*/
public static function getDBTypes() {
return self::$dbTypes;
@@ -557,6 +565,9 @@ abstract class Installer {
return $html;
}
+ /**
+ * @return ParserOptions
+ */
public function getParserOptions() {
return $this->parserOptions;
}
@@ -573,6 +584,10 @@ abstract class Installer {
/**
* Install step which adds a row to the site_stats table with appropriate
* initial values.
+ *
+ * @param $installer DatabaseInstaller
+ *
+ * @return Status
*/
public function populateSiteStats( DatabaseInstaller $installer ) {
$status = $installer->getConnection();
@@ -609,24 +624,15 @@ abstract class Installer {
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;
+ $allNames[] = wfMsg( "config-type-$name" );
}
- $this->setVar( '_CompiledDBs', $compiledDBs );
-
- if ( !$compiledDBs ) {
+ if ( !$this->getVar( '_CompiledDBs' ) ) {
$this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
- // FIXME: this only works for the web installer!
+ // @todo FIXME: This only works for the web installer!
return false;
}
@@ -834,6 +840,15 @@ abstract class Installer {
}
/**
+ * Environment check for the server hostname.
+ */
+ protected function envCheckServer() {
+ $server = WebRequest::detectServer();
+ $this->showMessage( 'config-using-server', $server );
+ $this->setVar( 'wgServer', $server );
+ }
+
+ /**
* Environment check for setting $IP and $wgScriptPath.
*/
protected function envCheckPath() {
@@ -841,31 +856,15 @@ abstract class Installer {
$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 );
+ $this->showMessage( 'config-using-uri', $this->getVar( 'wgServer' ), $this->getVar( 'wgScriptPath' ) );
+ return true;
}
/**
* Environment check for setting the preferred PHP file extension.
*/
protected function envCheckExtension() {
- // FIXME: detect this properly
+ // @todo FIXME: Detect this properly
if ( defined( 'MW_INSTALL_PHP5_EXT' ) ) {
$ext = 'php5';
} else {
@@ -951,10 +950,10 @@ abstract class Installer {
* TODO: document
*/
protected function envCheckUploadsDirectory() {
- global $IP, $wgServer;
+ global $IP;
$dir = $IP . '/images/';
- $url = $wgServer . $this->getVar( 'wgScriptPath' ) . '/images/';
+ $url = $this->getVar( 'wgServer' ) . $this->getVar( 'wgScriptPath' ) . '/images/';
$safe = !$this->dirIsExecutable( $dir, $url );
if ( $safe ) {
@@ -972,7 +971,10 @@ abstract class Installer {
protected function envCheckSuhosinMaxValueLength() {
$maxValueLength = ini_get( 'suhosin.get.max_value_length' );
if ( $maxValueLength > 0 ) {
- $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
+ if( $maxValueLength < 1024 ) {
+ # Only warn if the value is below the sane 1024
+ $this->showMessage( 'config-suhosin-max-value-length', $maxValueLength );
+ }
} else {
$maxValueLength = -1;
}
@@ -988,12 +990,12 @@ abstract class Installer {
$c = hexdec($c);
if ($c <= 0x7F) {
return chr($c);
- } else if ($c <= 0x7FF) {
+ } elseif ($c <= 0x7FF) {
return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
- } else if ($c <= 0xFFFF) {
+ } elseif ($c <= 0xFFFF) {
return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
. chr(0x80 | $c & 0x3F);
- } else if ($c <= 0x10FFFF) {
+ } elseif ($c <= 0x10FFFF) {
return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
. chr(0x80 | $c >> 6 & 0x3F)
. chr(0x80 | $c & 0x3F);
@@ -1168,6 +1170,8 @@ abstract class Installer {
/**
* ParserOptions are constructed before we determined the language, so fix it
+ *
+ * @param $lang Language
*/
public function setParserLanguage( $lang ) {
$this->parserOptions->setTargetLanguage( $lang );
@@ -1193,11 +1197,14 @@ abstract class Installer {
}
$exts = array();
- $dir = $this->getVar( 'IP' ) . '/extensions';
- $dh = opendir( $dir );
+ $extDir = $this->getVar( 'IP' ) . '/extensions';
+ $dh = opendir( $extDir );
while ( ( $file = readdir( $dh ) ) !== false ) {
- if( file_exists( "$dir/$file/$file.php" ) ) {
+ if( !is_dir( "$extDir/$file" ) ) {
+ continue;
+ }
+ if( file_exists( "$extDir/$file/$file.php" ) ) {
$exts[] = $file;
}
}
@@ -1224,10 +1231,12 @@ abstract class Installer {
* @see https://bugzilla.wikimedia.org/show_bug.cgi?id=26857
*/
global $wgAutoloadClasses;
+ $wgAutoloadClasses = array();
+
require( "$IP/includes/DefaultSettings.php" );
foreach( $exts as $e ) {
- require_once( $IP . '/extensions' . "/$e/$e.php" );
+ require_once( "$IP/extensions/$e/$e.php" );
}
$hooksWeWant = isset( $wgHooks['LoadExtensionSchemaUpdates'] ) ?
@@ -1515,4 +1524,14 @@ abstract class Installer {
public function addInstallStep( $callback, $findStep = 'BEGINNING' ) {
$this->extraInstallSteps[$findStep][] = $callback;
}
+
+ /**
+ * Disable the time limit for execution.
+ * Some long-running pages (Install, Upgrade) will want to do this
+ */
+ protected function disableTimeLimit() {
+ wfSuppressWarnings();
+ set_time_limit( 0 );
+ wfRestoreWarnings();
+ }
}
diff --git a/includes/installer/LocalSettingsGenerator.php b/includes/installer/LocalSettingsGenerator.php
index 2d7fd252..a3ab21ea 100644
--- a/includes/installer/LocalSettingsGenerator.php
+++ b/includes/installer/LocalSettingsGenerator.php
@@ -39,7 +39,7 @@ class LocalSettingsGenerator {
$confItems = array_merge(
array(
- 'wgScriptPath', 'wgScriptExtension',
+ 'wgServer', 'wgScriptPath', 'wgScriptExtension',
'wgPasswordSender', 'wgImageMagickConvertCommand', 'wgShellLocale',
'wgLanguageCode', 'wgEnableEmail', 'wgEnableUserEmail', 'wgDiff3',
'wgEnotifUserTalk', 'wgEnotifWatchlist', 'wgEmailAuthentication',
@@ -129,7 +129,7 @@ class LocalSettingsGenerator {
foreach( $this->extensions as $extName ) {
$encExtName = self::escapePhpString( $extName );
- $localSettings .= "require_once( \"extensions/$encExtName/$encExtName.php\" );\n";
+ $localSettings .= "require_once( \"\$IP/extensions/$encExtName/$encExtName.php\" );\n";
}
}
@@ -249,6 +249,9 @@ if ( !defined( 'MEDIAWIKI' ) ) {
\$wgScriptPath = \"{$this->values['wgScriptPath']}\";
\$wgScriptExtension = \"{$this->values['wgScriptExtension']}\";
+## The protocol and server name to use in fully-qualified URLs
+\$wgServer = \"{$this->values['wgServer']}\";
+
## The relative URL path to the skins directory
\$wgStylePath = \"\$wgScriptPath/skins\";
@@ -301,16 +304,12 @@ if ( !defined( 'MEDIAWIKI' ) ) {
## 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
+# Site language code, should be one of the list in ./languages/Names.php
\$wgLanguageCode = \"{$this->values['wgLanguageCode']}\";
\$wgSecretKey = \"{$this->values['wgSecretKey']}\";
@@ -326,7 +325,6 @@ if ( !defined( 'MEDIAWIKI' ) ) {
## 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']}\";
@@ -336,14 +334,13 @@ if ( !defined( 'MEDIAWIKI' ) ) {
# 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']};
-";
+
+{$groupRights}";
}
}
diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php
index 2fe16dcf..3bb8c114 100644
--- a/includes/installer/MysqlInstaller.php
+++ b/includes/installer/MysqlInstaller.php
@@ -42,6 +42,9 @@ class MysqlInstaller extends DatabaseInstaller {
'CREATE TEMPORARY TABLES',
);
+ /**
+ * @return string
+ */
public function getName() {
return 'mysql';
}
@@ -50,14 +53,23 @@ class MysqlInstaller extends DatabaseInstaller {
parent::__construct( $parent );
}
+ /**
+ * @return Bool
+ */
public function isCompiled() {
return self::checkExtension( 'mysql' );
}
+ /**
+ * @return array
+ */
public function getGlobalDefaults() {
return array();
}
+ /**
+ * @return string
+ */
public function getConnectForm() {
return
$this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) .
@@ -112,6 +124,9 @@ class MysqlInstaller extends DatabaseInstaller {
return $status;
}
+ /**
+ * @return Status
+ */
public function openConnection() {
$status = Status::newGood();
try {
@@ -188,6 +203,8 @@ class MysqlInstaller extends DatabaseInstaller {
/**
* Get a list of storage engines that are available and supported
+ *
+ * @return array
*/
public function getEngines() {
$engines = array( 'InnoDB', 'MyISAM' );
@@ -216,6 +233,8 @@ class MysqlInstaller extends DatabaseInstaller {
/**
* Get a list of character sets that are available and supported
+ *
+ * @return array
*/
public function getCharsets() {
$status = $this->getConnection();
@@ -232,6 +251,8 @@ class MysqlInstaller extends DatabaseInstaller {
/**
* Return true if the install user can create accounts
+ *
+ * @return bool
*/
public function canCreateAccounts() {
$status = $this->getConnection();
@@ -305,6 +326,9 @@ class MysqlInstaller extends DatabaseInstaller {
return true;
}
+ /**
+ * @return string
+ */
public function getSettingsForm() {
if ( $this->canCreateAccounts() ) {
$noCreateMsg = false;
@@ -319,13 +343,33 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !in_array( $this->getVar( '_MysqlEngine' ), $engines ) ) {
$this->setVar( '_MysqlEngine', reset( $engines ) );
}
+
+ $s .= Xml::openElement( 'div', array(
+ 'id' => 'dbMyisamWarning'
+ ));
+ $s .= $this->parent->getWarningBox( wfMsg( 'config-mysql-myisam-dep' ) );
+ $s .= Xml::closeElement( 'div' );
+
+ if( $this->getVar( '_MysqlEngine' ) != 'MyISAM' ) {
+ $s .= Xml::openElement( 'script', array( 'type' => 'text/javascript' ) );
+ $s .= '$(\'#dbMyisamWarning\').hide();';
+ $s .= Xml::closeElement( 'script' );
+ }
+
if ( count( $engines ) >= 2 ) {
$s .= $this->getRadioSet( array(
'var' => '_MysqlEngine',
'label' => 'config-mysql-engine',
'itemLabelPrefix' => 'config-mysql-',
- 'values' => $engines
- ));
+ 'values' => $engines,
+ 'itemAttribs' => array(
+ 'MyISAM' => array(
+ 'class' => 'showHideRadio',
+ 'rel' => 'dbMyisamWarning'),
+ 'InnoDB' => array(
+ 'class' => 'hideShowRadio',
+ 'rel' => 'dbMyisamWarning')
+ )));
$s .= $this->parent->getHelpBox( 'config-mysql-engine-help' );
}
@@ -349,6 +393,9 @@ class MysqlInstaller extends DatabaseInstaller {
return $s;
}
+ /**
+ * @return Status
+ */
public function submitSettingsForm() {
$this->setVarsFromRequest( array( '_MysqlEngine', '_MysqlCharset' ) );
$status = $this->submitWebUserBox();
@@ -404,6 +451,9 @@ class MysqlInstaller extends DatabaseInstaller {
$this->parent->addInstallStep( $callback, 'tables' );
}
+ /**
+ * @return Status
+ */
public function setupDatabase() {
$status = $this->getConnection();
if ( !$status->isOK() ) {
@@ -419,6 +469,9 @@ class MysqlInstaller extends DatabaseInstaller {
return $status;
}
+ /**
+ * @return Status
+ */
public function setupUser() {
$dbUser = $this->getVar( 'wgDBuser' );
if( $dbUser == $this->getVar( '_InstallUser' ) ) {
@@ -499,7 +552,6 @@ class MysqlInstaller extends DatabaseInstaller {
}
// 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 {
@@ -540,7 +592,7 @@ class MysqlInstaller extends DatabaseInstaller {
} catch( DBQueryError $dqe ) {
return false;
}
-
+
}
/**
@@ -562,6 +614,8 @@ class MysqlInstaller extends DatabaseInstaller {
/**
* Get variables to substitute into tables.sql and the SQL patch files.
+ *
+ * @return array
*/
public function getSchemaVars() {
return array(
diff --git a/includes/installer/MysqlUpdater.php b/includes/installer/MysqlUpdater.php
index 9bbda5db..761d2338 100644
--- a/includes/installer/MysqlUpdater.php
+++ b/includes/installer/MysqlUpdater.php
@@ -163,18 +163,25 @@ class MysqlUpdater extends DatabaseUpdater {
// 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', '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( '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( '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.18
+ array( 'doUserNewTalkTimestampNotNull' ),
+ array( 'addIndex', 'user', 'user_email', 'patch-user_email_index.sql' ),
+ array( 'modifyField', 'user_properties', 'up_property', 'patch-up_property.sql' ),
+ array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
+ array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql'),
);
}
@@ -246,6 +253,9 @@ class MysqlUpdater extends DatabaseUpdater {
*/
protected function doIndexUpdate() {
$meta = $this->db->fieldInfo( 'recentchanges', 'rc_timestamp' );
+ if ( $meta === false ) {
+ throw new MWException( 'Missing rc_timestamp field of recentchanges table. Should not happen.' );
+ }
if ( $meta->isMultipleKey() ) {
$this->output( "...indexes seem up to 20031107 standards\n" );
return;
@@ -649,7 +659,7 @@ class MysqlUpdater extends DatabaseUpdater {
foreach ( $res as $row ) {
$count = ( $count + 1 ) % 100;
if ( $count == 0 ) {
- wfWaitForSlaves( 10 );
+ wfWaitForSlaves();
}
$this->db->insert( 'templatelinks',
array(
@@ -829,4 +839,16 @@ class MysqlUpdater extends DatabaseUpdater {
$this->output( "...ll_lang is up-to-date.\n" );
}
}
+
+ protected function doUserNewTalkTimestampNotNull() {
+ $info = $this->db->fieldInfo( 'user_newtalk', 'user_last_timestamp' );
+ if ( $info->isNullable() ) {
+ $this->output( "...user_last_timestamp is already nullable.\n" );
+ return;
+ }
+
+ $this->output( "Making user_last_timestamp nullable... " );
+ $this->applyPatch( 'patch-user-newtalk-timestamp-null.sql' );
+ $this->output( "done.\n" );
+ }
}
diff --git a/includes/installer/OracleInstaller.php b/includes/installer/OracleInstaller.php
index 8c3e40e1..175baf0b 100644
--- a/includes/installer/OracleInstaller.php
+++ b/includes/installer/OracleInstaller.php
@@ -27,7 +27,7 @@ class OracleInstaller extends DatabaseInstaller {
'_OracleTempTS' => 'TEMP',
'_InstallUser' => 'SYSDBA',
);
-
+
public $minimumVersion = '9.0.1'; // 9iR1
protected $connError = null;
@@ -92,7 +92,7 @@ class OracleInstaller extends DatabaseInstaller {
// Scenario 1: Install with a manually created account
$status = $this->getConnection();
if ( !$status->isOK() ) {
- if ( $this->connError == 28009 ) {
+ if ( $this->connError == 28009 ) {
// _InstallUser seems to be a SYSDBA
// Scenario 2: Create user with SYSDBA and install with new user
$status = $this->submitWebUserBox();
@@ -199,7 +199,7 @@ class OracleInstaller extends DatabaseInstaller {
// normaly only SYSDBA users can create accounts
$status = $this->openSYSDBAConnection();
if ( !$status->isOK() ) {
- if ( $this->connError == 1031 ) {
+ if ( $this->connError == 1031 ) {
// insufficient privileges (looks like a normal user)
$status = $this->openConnection();
if ( !$status->isOK() ) {
diff --git a/includes/installer/OracleUpdater.php b/includes/installer/OracleUpdater.php
index 7664a418..fb8032f5 100644
--- a/includes/installer/OracleUpdater.php
+++ b/includes/installer/OracleUpdater.php
@@ -13,15 +13,31 @@
* @since 1.17
*/
class OracleUpdater extends DatabaseUpdater {
+
+ /**
+ * Handle to the database subclass
+ *
+ * @var DatabaseOracle
+ */
+ protected $db;
+
protected function getCoreUpdateList() {
return array(
- // 1.16
+ // 1.17
array( 'doNamespaceDefaults' ),
array( 'doFKRenameDeferr' ),
array( 'doFunctions17' ),
array( 'doSchemaUpgrade17' ),
array( 'doInsertPage0' ),
array( 'doRemoveNotNullEmptyDefaults' ),
+ array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql' ),
+
+ //1.18
+ array( 'addIndex', 'user', 'i02', 'patch-user_email_index.sql' ),
+ array( 'modifyField', 'user_properties', 'up_property', 'patch-up_property.sql' ),
+ array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
+ array( 'doRebuildDuplicateFunction' ),
+
);
}
@@ -74,7 +90,7 @@ class OracleUpdater extends DatabaseUpdater {
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' ) ) ) {
+ if ( $this->db->tableExists( 'iwlinks' ) ) {
$this->output( "schema seem to be up to date.\n" );
return;
}
@@ -90,7 +106,7 @@ class OracleUpdater extends DatabaseUpdater {
$row = array(
'page_id' => 0,
'page_namespace' => 0,
- 'page_title' => ' ',
+ 'page_title' => ' ',
'page_counter' => 0,
'page_is_redirect' => 0,
'page_is_new' => 0,
@@ -119,9 +135,20 @@ class OracleUpdater extends DatabaseUpdater {
}
/**
+ * rebuilding of the function that duplicates tables for tests
+ */
+ protected function doRebuildDuplicateFunction() {
+ $this->output( "Rebuilding duplicate function ... " );
+ $this->applyPatch( 'patch_rebuild_dupfunc.sql', false );
+ $this->output( "ok\n" );
+ }
+
+ /**
* Overload: after this action field info table has to be rebuilt
+ *
+ * @param $what array
*/
- public function doUpdates( $what = array( 'core', 'extensions', 'purge' ) ) {
+ public function doUpdates( $what = array( 'core', 'extensions', 'purge', 'stats' ) ) {
parent::doUpdates( $what );
$this->db->query( 'BEGIN fill_wiki_info; END;' );
diff --git a/includes/installer/PhpBugTests.php b/includes/installer/PhpBugTests.php
index 9cafd150..bb1f7d11 100644
--- a/includes/installer/PhpBugTests.php
+++ b/includes/installer/PhpBugTests.php
@@ -47,7 +47,7 @@ class PhpXmlBugTester {
/**
* 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
+ * @see http://bugs.php.net/bug.php?id=50394
* @ingroup PHPBugTests
*/
class PhpRefCallBugTester {
diff --git a/includes/installer/PostgresInstaller.php b/includes/installer/PostgresInstaller.php
index 20575b62..7c4a6a80 100644
--- a/includes/installer/PostgresInstaller.php
+++ b/includes/installer/PostgresInstaller.php
@@ -115,7 +115,6 @@ class PostgresInstaller extends DatabaseInstaller {
protected function openConnectionWithParams( $user, $password, $dbName ) {
$status = Status::newGood();
try {
- $GLOBALS['wgDBport'] = $this->getVar( 'wgDBport' );
$db = new DatabasePostgres(
$this->getVar( 'wgDBserver' ),
$user,
@@ -130,7 +129,7 @@ class PostgresInstaller extends DatabaseInstaller {
/**
* Get a special type of connection
- * @param $type See openPgConnection() for details.
+ * @param $type string See openPgConnection() for details.
* @return Status
*/
protected function getPgConnection( $type ) {
@@ -152,35 +151,35 @@ class PostgresInstaller extends DatabaseInstaller {
* 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.
+ * 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
+ * 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
+ * - 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
+ * @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( '_InstallUser' ),
$this->getVar( '_InstallPassword' ) );
case 'create-schema':
- return $this->openConnectionWithParams(
+ return $this->openConnectionWithParams(
$this->getVar( '_InstallUser' ),
$this->getVar( '_InstallPassword' ),
$this->getVar( 'wgDBname' ) );
@@ -208,7 +207,6 @@ class PostgresInstaller extends DatabaseInstaller {
$status = Status::newGood();
foreach ( $dbs as $db ) {
try {
- $GLOBALS['wgDBport'] = $this->getVar( 'wgDBport' );
$conn = new DatabasePostgres(
$this->getVar( 'wgDBserver' ),
$user,
@@ -238,7 +236,7 @@ class PostgresInstaller extends DatabaseInstaller {
$conn = $status->value;
$superuser = $this->getVar( '_InstallUser' );
- $row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*',
+ $row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*',
array( 'rolname' => $superuser ), __METHOD__ );
return $row;
}
@@ -256,7 +254,7 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$perms ) {
return false;
}
- return $perms->rolsuper === 't';
+ return $perms->rolsuper === 't';
}
public function getSettingsForm() {
@@ -313,15 +311,15 @@ class PostgresInstaller extends DatabaseInstaller {
}
// Existing web account. Test the connection.
- $status = $this->openConnectionToAnyDB(
+ $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
+ // 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();
@@ -446,14 +444,14 @@ class PostgresInstaller extends DatabaseInstaller {
try {
$conn->query( "CREATE SCHEMA $safeschema AUTHORIZATION $safeuser" );
} catch ( DBQueryError $e ) {
- return Status::newFatal( 'config-install-pg-schema-failed',
+ 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",
+ $conn->query( "ALTER ROLE $safeuser SET search_path = $safeschema, public",
__METHOD__ );
}
@@ -489,8 +487,8 @@ class PostgresInstaller extends DatabaseInstaller {
// 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
+
+ // 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() ) {
@@ -499,7 +497,7 @@ class PostgresInstaller extends DatabaseInstaller {
$conn->query( $sql, __METHOD__ );
} catch ( DBQueryError $e ) {
- return Status::newFatal( 'config-install-user-create-failed',
+ return Status::newFatal( 'config-install-user-create-failed',
$this->getVar( 'wgDBuser' ), $e->getMessage() );
}
}
@@ -545,7 +543,7 @@ class PostgresInstaller extends DatabaseInstaller {
$status->fatal( 'config-install-pg-schema-not-exist' );
return $status;
}
- $error = $conn->sourceFile( $conn->getSchema() );
+ $error = $conn->sourceFile( $conn->getSchemaPath() );
if( $error !== true ) {
$conn->reportQueryError( $error, 0, '', __METHOD__ );
$conn->rollback( __METHOD__ );
@@ -561,7 +559,7 @@ class PostgresInstaller extends DatabaseInstaller {
}
public function setupPLpgSQL() {
- // Connect as the install user, since it owns the database and so is
+ // 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() ) {
@@ -576,7 +574,7 @@ class PostgresInstaller extends DatabaseInstaller {
return Status::newGood();
}
- // plpgsql is not installed, but if we have a pg_pltemplate table, we
+ // 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"' ),
diff --git a/includes/installer/PostgresUpdater.php b/includes/installer/PostgresUpdater.php
index 272638ce..2c9c0595 100644
--- a/includes/installer/PostgresUpdater.php
+++ b/includes/installer/PostgresUpdater.php
@@ -130,8 +130,6 @@ class PostgresUpdater extends DatabaseUpdater {
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', '' ),
diff --git a/includes/installer/SqliteInstaller.php b/includes/installer/SqliteInstaller.php
index 2edb3d9b..144e710d 100644
--- a/includes/installer/SqliteInstaller.php
+++ b/includes/installer/SqliteInstaller.php
@@ -14,6 +14,11 @@
*/
class SqliteInstaller extends DatabaseInstaller {
+ /**
+ * @var DatabaseSqlite
+ */
+ public $db;
+
protected $globalNames = array(
'wgDBname',
'wgSQLiteDataDir',
@@ -45,8 +50,12 @@ class SqliteInstaller extends DatabaseInstaller {
$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.
+ *
+ * @param $path string
+ *
+ * @return string
*/
private static function realpath( $path ) {
$result = realpath( $path );
@@ -56,14 +65,16 @@ class SqliteInstaller extends DatabaseInstaller {
return $result;
}
+ /**
+ * @return Status
+ */
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() )
- {
+ if ( $result->isOK() ) {
# Try expanding again in case we've just created it
$dir = self::realpath( $dir );
$this->setVar( 'wgSQLiteDataDir', $dir );
@@ -71,6 +82,11 @@ class SqliteInstaller extends DatabaseInstaller {
return $result;
}
+ /**
+ * @param $dir
+ * @param $create bool
+ * @return Status
+ */
private static function dataDirOKmaybeCreate( $dir, $create = false ) {
if ( !is_dir( $dir ) ) {
if ( !is_writable( dirname( $dir ) ) ) {
@@ -103,6 +119,9 @@ class SqliteInstaller extends DatabaseInstaller {
return Status::newGood();
}
+ /**
+ * @return Status
+ */
public function openConnection() {
global $wgSQLiteDataDir;
@@ -110,7 +129,7 @@ class SqliteInstaller extends DatabaseInstaller {
$dir = $this->getVar( 'wgSQLiteDataDir' );
$dbName = $this->getVar( 'wgDBname' );
try {
- # FIXME: need more sensible constructor parameters, e.g. single associative array
+ # @todo FIXME: Need more sensible constructor parameters, e.g. single associative array
# Setting globals kind of sucks
$wgSQLiteDataDir = $dir;
$db = new DatabaseSqlite( false, false, false, $dbName );
@@ -121,6 +140,9 @@ class SqliteInstaller extends DatabaseInstaller {
return $status;
}
+ /**
+ * @return bool
+ */
public function needsUpgrade() {
$dir = $this->getVar( 'wgSQLiteDataDir' );
$dbName = $this->getVar( 'wgDBname' );
@@ -133,6 +155,9 @@ class SqliteInstaller extends DatabaseInstaller {
return parent::needsUpgrade();
}
+ /**
+ * @return Status
+ */
public function setupDatabase() {
$dir = $this->getVar( 'wgSQLiteDataDir' );
@@ -162,11 +187,18 @@ class SqliteInstaller extends DatabaseInstaller {
return $this->getConnection();
}
+ /**
+ * @return Staus
+ */
public function createTables() {
$status = parent::createTables();
return $this->setupSearchIndex( $status );
}
+ /**
+ * @param $status Status
+ * @return Status
+ */
public function setupSearchIndex( &$status ) {
global $IP;
@@ -181,6 +213,9 @@ class SqliteInstaller extends DatabaseInstaller {
return $status;
}
+ /**
+ * @return string
+ */
public function getLocalSettings() {
$dir = LocalSettingsGenerator::escapePhpString( $this->getVar( 'wgSQLiteDataDir' ) );
return
diff --git a/includes/installer/SqliteUpdater.php b/includes/installer/SqliteUpdater.php
index d1a6c20b..6b819f8e 100644
--- a/includes/installer/SqliteUpdater.php
+++ b/includes/installer/SqliteUpdater.php
@@ -52,6 +52,13 @@ class SqliteUpdater extends DatabaseUpdater {
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' ),
+
+ // 1.18
+ array( 'addIndex', 'user', 'user_email', 'patch-user_email_index.sql' ),
+ array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
+ array( 'addTable', 'user_former_groups', 'patch-user_former_groups.sql'),
);
}
diff --git a/includes/installer/WebInstaller.php b/includes/installer/WebInstaller.php
index b75db74e..40726437 100644
--- a/includes/installer/WebInstaller.php
+++ b/includes/installer/WebInstaller.php
@@ -247,6 +247,10 @@ class WebInstaller extends Installer {
$this->currentPageName = $page->getName();
$this->startPageWrapper( $pageName );
+ if( $page->isSlow() ) {
+ $this->disableTimeLimit();
+ }
+
$result = $page->execute();
$this->endPageWrapper();
@@ -300,6 +304,8 @@ class WebInstaller extends Installer {
/**
* Start the PHP session. This may be called before execute() to start the PHP session.
+ *
+ * @return bool
*/
public function startSession() {
if( wfIniGetBool( 'session.auto_start' ) || session_id() ) {
@@ -325,6 +331,8 @@ class WebInstaller extends Installer {
*
* This is used by mw-config/index.php to prevent multiple installations of MW
* on the same cookie domain from interfering with each other.
+ *
+ * @return string
*/
public function getFingerprint() {
// Get the base URL of the installation
@@ -390,7 +398,7 @@ class WebInstaller extends Installer {
/**
* Get a URL for submission back to the same script.
*
- * @param $query: Array
+ * @param $query array
* @return string
*/
public function getUrl( $query = array() ) {
@@ -412,9 +420,6 @@ class WebInstaller extends Installer {
* @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 );
@@ -587,6 +592,8 @@ class WebInstaller extends Installer {
* Get HTML for an error box with an icon.
*
* @param $text String: wikitext, get this with wfMsgNoTrans()
+ *
+ * @return string
*/
public function getErrorBox( $text ) {
return $this->getInfoBox( $text, 'critical-32.png', 'config-error-box' );
@@ -596,6 +603,8 @@ class WebInstaller extends Installer {
* Get HTML for a warning box with an icon.
*
* @param $text String: wikitext, get this with wfMsgNoTrans()
+ *
+ * @return string
*/
public function getWarningBox( $text ) {
return $this->getInfoBox( $text, 'warning-32.png', 'config-warning-box' );
@@ -607,29 +616,21 @@ class WebInstaller extends Installer {
* @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
+ *
+ * @return string
*/
- 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;
+ public function getInfoBox( $text, $icon = false, $class = false ) {
+ $text = $this->parse( $text, true );
+ $icon = ( $icon == false ) ? '../skins/common/images/info-32.png' : '../skins/common/images/'.$icon;
+ $alt = wfMsg( 'config-information' );
+ return Html::infoBox( $text, $icon, $alt, $class, false );
}
/**
* Get small text indented help for a preceding form field.
* Parameters like wfMsg().
+ *
+ * @return string
*/
public function getHelpBox( $msg /*, ... */ ) {
$args = func_get_args();
@@ -685,6 +686,8 @@ class WebInstaller extends Installer {
/**
* Label a control by wrapping a config-input div around it and putting a
* label before it.
+ *
+ * @return string
*/
public function label( $msg, $forId, $contents, $helpData = "" ) {
if ( strval( $msg ) == '' ) {
@@ -724,6 +727,8 @@ class WebInstaller extends Installer {
* controlName: The name for the input element (optional)
* value: The current value of the variable (optional)
* help: The html for the help text (optional)
+ *
+ * @return string
*/
public function getTextBox( $params ) {
if ( !isset( $params['controlName'] ) ) {
@@ -769,6 +774,8 @@ class WebInstaller extends Installer {
* controlName: The name for the input element (optional)
* value: The current value of the variable (optional)
* help: The html for the help text (optional)
+ *
+ * @return string
*/
public function getTextArea( $params ) {
if ( !isset( $params['controlName'] ) ) {
@@ -816,6 +823,8 @@ class WebInstaller extends Installer {
* controlName: The name for the input element (optional)
* value: The current value of the variable (optional)
* help: The html for the help text (optional)
+ *
+ * @return string
*/
public function getPasswordBox( $params ) {
if ( !isset( $params['value'] ) ) {
@@ -843,6 +852,8 @@ class WebInstaller extends Installer {
* controlName: The name for the input element (optional)
* value: The current value of the variable (optional)
* help: The html for the help text (optional)
+ *
+ * @return string
*/
public function getCheckBox( $params ) {
if ( !isset( $params['controlName'] ) ) {
@@ -896,6 +907,8 @@ class WebInstaller extends Installer {
* controlName: The name for the input element (optional)
* value: The current value of the variable (optional)
* help: The html for the help text (optional)
+ *
+ * @return string
*/
public function getRadioSet( $params ) {
if ( !isset( $params['controlName'] ) ) {
@@ -948,6 +961,8 @@ class WebInstaller extends Installer {
/**
* Output an error or warning box using a Status object.
+ *
+ * @param $status Status
*/
public function showStatusBox( $status ) {
if( !$status->isGood() ) {
@@ -970,6 +985,8 @@ class WebInstaller extends Installer {
*
* @param $varNames Array
* @param $prefix String: the prefix added to variables to obtain form names
+ *
+ * @return array
*/
public function setVarsFromRequest( $varNames, $prefix = 'config_' ) {
$newValues = array();
@@ -995,6 +1012,8 @@ class WebInstaller extends Installer {
/**
* Helper for Installer::docLink()
+ *
+ * @return string
*/
protected function getDocUrl( $page ) {
$url = "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
@@ -1008,6 +1027,8 @@ class WebInstaller extends Installer {
/**
* Extension tag hook for a documentation link.
+ *
+ * @return string
*/
public function docLink( $linkText, $attribs, $parser ) {
$url = $this->getDocUrl( $attribs['href'] );
@@ -1015,9 +1036,10 @@ class WebInstaller extends Installer {
htmlspecialchars( $linkText ) .
'</a>';
}
-
+
/**
* Helper for "Download LocalSettings" link on WebInstall_Complete
+ *
* @return String Html for download link
*/
public function downloadLinkHook( $text, $attribs, $parser ) {
@@ -1031,4 +1053,27 @@ class WebInstaller extends Installer {
$img . ' ' . wfMsgHtml( 'config-download-localsettings' ) );
return Html::rawElement( 'div', array( 'class' => 'config-download-link' ), $anchor );
}
+
+ public function envCheckPath( ) {
+ // PHP_SELF isn't available sometimes, such as when PHP is CGI but
+ // cgi.fix_pathinfo is disabled. In that case, fall back to SCRIPT_NAME
+ // to get the path to the current script... hopefully it's reliable. SIGH
+ $path = false;
+ if ( !empty( $_SERVER['PHP_SELF'] ) ) {
+ $path = $_SERVER['PHP_SELF'];
+ } elseif ( !empty( $_SERVER['SCRIPT_NAME'] ) ) {
+ $path = $_SERVER['SCRIPT_NAME'];
+ }
+ if ($path !== false) {
+ $uri = preg_replace( '{^(.*)/(mw-)?config.*$}', '$1', $path );
+ $this->setVar( 'wgScriptPath', $uri );
+ } else {
+ $this->showError( 'config-no-uri' );
+ return false;
+ }
+
+
+ return parent::envCheckPath();
+ }
+
}
diff --git a/includes/installer/WebInstallerOutput.php b/includes/installer/WebInstallerOutput.php
index cb708d13..9eb2c2c6 100644
--- a/includes/installer/WebInstallerOutput.php
+++ b/includes/installer/WebInstallerOutput.php
@@ -113,11 +113,11 @@ class WebInstallerOutput {
}
/**
- * URL for index.php?css=foobar
+ * <link> to index.php?css=foobar for the <head>
* @return String
*/
private function getCssUrl( ) {
- return $_SERVER['PHP_SELF'] . '?css=' . $this->getDir();
+ return Html::linkedStyle( $_SERVER['PHP_SELF'] . '?css=' . $this->getDir() );
}
public function useShortHeader( $use = true ) {
@@ -139,22 +139,33 @@ class WebInstallerOutput {
}
}
+ /**
+ * @return string
+ */
public function getDir() {
global $wgLang;
- if( !is_object( $wgLang ) || !$wgLang->isRtl() )
+ if( !is_object( $wgLang ) || !$wgLang->isRtl() ) {
return 'ltr';
- else
+ } else {
return 'rtl';
+ }
}
+ /**
+ * @return string
+ */
public function getLanguageCode() {
global $wgLang;
- if( !is_object( $wgLang ) )
+ if( !is_object( $wgLang ) ) {
return 'en';
- else
+ } else {
return $wgLang->getCode();
+ }
}
+ /**
+ * @return array
+ */
public function getHeadAttribs() {
return array(
'dir' => $this->getDir(),
@@ -195,7 +206,7 @@ class WebInstallerOutput {
<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 $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"; ?>
@@ -249,7 +260,7 @@ class WebInstallerOutput {
<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->getCssUrl() . "\n"; ?>
<?php echo $this->getJQuery(); ?>
<?php echo Html::linkedScript( '../skins/common/config.js' ); ?>
</head>
diff --git a/includes/installer/WebInstallerPage.php b/includes/installer/WebInstallerPage.php
index 5c30182b..dc9c6d27 100644
--- a/includes/installer/WebInstallerPage.php
+++ b/includes/installer/WebInstallerPage.php
@@ -32,6 +32,15 @@ abstract class WebInstallerPage {
$this->parent = $parent;
}
+ /**
+ * Is this a slow-running page in the installer? If so, WebInstaller will
+ * set_time_limit(0) before calling execute(). Right now this only applies
+ * to Install and Upgrade pages
+ */
+ public function isSlow() {
+ return false;
+ }
+
public function addHTML( $html ) {
$this->parent->output->addHTML( $html );
}
@@ -103,6 +112,8 @@ abstract class WebInstallerPage {
* Get the starting tags of a fieldset.
*
* @param $legend String: message name
+ *
+ * @return string
*/
protected function getFieldsetStart( $legend ) {
return "\n<fieldset><legend>" . wfMsgHtml( $legend ) . "</legend>\n";
@@ -110,6 +121,8 @@ abstract class WebInstallerPage {
/**
* Get the end tag of a fieldset.
+ *
+ * @returns string
*/
protected function getFieldsetEnd() {
return "</fieldset>\n";
@@ -122,7 +135,7 @@ abstract class WebInstallerPage {
$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">'
+ '<div id="config-live-log"><textarea name="LiveLog" rows="10" cols="30" readonly="readonly">'
);
$this->parent->output->flush();
}
@@ -131,7 +144,7 @@ abstract class WebInstallerPage {
* Opposite to startLiveBox()
*/
protected function endLiveBox() {
- $this->addHTML( '</textarea>
+ $this->addHTML( '</textarea></div>
<script>jQuery( "#config-spinner" ).hide()</script>' );
$this->parent->output->flush();
}
@@ -198,10 +211,15 @@ class WebInstaller_Language extends WebInstallerPage {
/**
* Get a <select> for selecting languages.
+ *
+ * @return string
*/
- public function getLanguageSelector( $name, $label, $selectedCode ) {
+ public function getLanguageSelector( $name, $label, $selectedCode, $helpHtml = '' ) {
global $wgDummyLanguageCodes;
- $s = Html::openElement( 'select', array( 'id' => $name, 'name' => $name ) ) . "\n";
+
+ $s = $helpHtml;
+
+ $s .= Html::openElement( 'select', array( 'id' => $name, 'name' => $name, 'tabindex' => $this->parent->nextTabIndex() ) ) . "\n";
$languages = Language::getLanguageNames();
ksort( $languages );
@@ -408,7 +426,7 @@ class WebInstaller_DBConnect extends WebInstallerPage {
$dbSupport = '';
foreach( $this->parent->getDBTypes() as $type ) {
- $link = DatabaseBase::newFromType( $type )->getSoftwareLink();
+ $link = DatabaseBase::factory( $type )->getSoftwareLink();
$dbSupport .= wfMsgNoTrans( "config-support-$type", $link ) . "\n";
}
$this->addHTML( $this->parent->getInfoBox(
@@ -458,6 +476,9 @@ class WebInstaller_DBConnect extends WebInstallerPage {
}
class WebInstaller_Upgrade extends WebInstallerPage {
+ public function isSlow() {
+ return true;
+ }
public function execute() {
if ( $this->getVar( '_UpgradeDone' ) ) {
@@ -522,7 +543,7 @@ class WebInstaller_Upgrade extends WebInstallerPage {
$this->addHTML(
$this->parent->getInfoBox(
wfMsgNoTrans( $msg,
- $GLOBALS['wgServer'] .
+ $this->getVar( 'wgServer' ) .
$this->getVar( 'wgScriptPath' ) . '/index' .
$this->getVar( 'wgScriptExtension' )
), 'tick-32.png'
@@ -722,7 +743,6 @@ class WebInstaller_Name extends WebInstallerPage {
// Validate password
$msg = false;
- $valid = false;
$pwd = $this->getVar( '_AdminPassword' );
$user = User::newFromName( $cname );
$valid = $user && $user->getPasswordValidity( $pwd );
@@ -746,10 +766,16 @@ class WebInstaller_Name extends WebInstallerPage {
// Validate e-mail if provided
$email = $this->getVar( '_AdminEmail' );
- if( $email && !User::isValidEmailAddr( $email ) ) {
+ if( $email && !Sanitizer::validateEmail( $email ) ) {
$this->parent->showError( 'config-admin-error-bademail' );
$retVal = false;
}
+ // If they asked to subscribe to mediawiki-announce but didn't give
+ // an e-mail, show an error. Bug 29332
+ if( !$email && $this->getVar( '_Subscribe' ) ) {
+ $this->parent->showError( 'config-subscribe-noemail' );
+ $retVal = false;
+ }
return $retVal;
}
@@ -892,6 +918,15 @@ class WebInstaller_Options extends WebInstallerPage {
}
$caches[] = 'memcached';
+ // We'll hide/show this on demand when the value changes, see config.js.
+ $cacheval = $this->getVar( 'wgMainCacheType' );
+ if (!$cacheval) {
+ // We need to set a default here; but don't hardcode it
+ // or we lose it every time we reload the page for validation
+ // or going back!
+ $cacheval = 'none';
+ }
+ $hidden = ($cacheval == 'memcached') ? '' : 'display: none';
$this->addHTML(
# Advanced settings
$this->getFieldSetStart( 'config-advanced-settings' ) .
@@ -901,10 +936,10 @@ class WebInstaller_Options extends WebInstallerPage {
'label' => 'config-cache-options',
'itemLabelPrefix' => 'config-cache-',
'values' => $caches,
- 'value' => 'none',
+ 'value' => $cacheval,
) ) .
$this->parent->getHelpBox( 'config-cache-help' ) .
- '<div id="config-memcachewrapper">' .
+ "<div id=\"config-memcachewrapper\" style=\"$hidden\">" .
$this->parent->getTextArea( array(
'var' => '_MemCachedServers',
'label' => 'config-memcached-servers',
@@ -916,9 +951,12 @@ class WebInstaller_Options extends WebInstallerPage {
$this->endForm();
}
+ /**
+ * @return string
+ */
public function getCCPartnerUrl() {
- global $wgServer;
- $exitUrl = $wgServer . $this->parent->getUrl( array(
+ $server = $this->getVar( 'wgServer' );
+ $exitUrl = $server . $this->parent->getUrl( array(
'page' => 'Options',
'SubmitCC' => 'indeed',
'config__LicenseCode' => 'cc',
@@ -926,7 +964,7 @@ class WebInstaller_Options extends WebInstallerPage {
'config_wgRightsText' => '[license_name]',
'config_wgRightsIcon' => '[license_button]',
) );
- $styleUrl = $wgServer . dirname( dirname( $this->parent->getUrl() ) ) .
+ $styleUrl = $server . dirname( dirname( $this->parent->getUrl() ) ) .
'/skins/common/config-cc.css';
$iframeUrl = 'http://creativecommons.org/license/?' .
wfArrayToCGI( array(
@@ -1050,8 +1088,10 @@ class WebInstaller_Options extends WebInstallerPage {
}
foreach( $memcServers as $server ) {
- $memcParts = explode( ":", $server );
- if( !IP::isValid( $memcParts[0] ) ) {
+ $memcParts = explode( ":", $server, 2 );
+ if ( !isset( $memcParts[0] )
+ || ( !IP::isValid( $memcParts[0] )
+ && ( gethostbyname( $memcParts[0] ) == $memcParts[0] ) ) ) {
$this->parent->showError( 'config-memcache-badip', $memcParts[0] );
return false;
} elseif( !isset( $memcParts[1] ) ) {
@@ -1069,6 +1109,9 @@ class WebInstaller_Options extends WebInstallerPage {
}
class WebInstaller_Install extends WebInstallerPage {
+ public function isSlow() {
+ return true;
+ }
public function execute() {
if( $this->getVar( '_UpgradeDone' ) ) {
@@ -1104,6 +1147,10 @@ class WebInstaller_Install extends WebInstallerPage {
}
}
+ /**
+ * @param $step
+ * @param $status Status
+ */
public function endStage( $step, $status ) {
if ( $step == 'extension-tables' ) {
$this->endLiveBox();
@@ -1126,7 +1173,7 @@ 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 ) );
+ $lsUrl = $this->getVar( '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='
@@ -1141,7 +1188,7 @@ class WebInstaller_Complete extends WebInstallerPage {
$this->parent->getInfoBox(
wfMsgNoTrans( 'config-install-done',
$lsUrl,
- $GLOBALS['wgServer'] .
+ $this->getVar( 'wgServer' ) .
$this->getVar( 'wgScriptPath' ) . '/index' .
$this->getVar( 'wgScriptExtension' ),
'<downloadlink/>'
@@ -1179,45 +1226,16 @@ abstract class WebInstaller_Document extends WebInstallerPage {
public function execute() {
$text = $this->getFileContents();
- $text = $this->formatTextFile( $text );
+ $text = InstallDocFormatter::format( $text );
$this->parent->output->addWikiText( $text );
$this->startForm();
$this->endForm( false );
}
- public function getFileContents() {
+ 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 {
diff --git a/includes/Interwiki.php b/includes/interwiki/Interwiki.php
index 5a3b6556..71bd9725 100644
--- a/includes/Interwiki.php
+++ b/includes/interwiki/Interwiki.php
@@ -141,11 +141,19 @@ class Interwiki {
*/
protected static function load( $prefix ) {
global $wgMemc, $wgInterwikiExpiry;
- $key = wfMemcKey( 'interwiki', $prefix );
- $mc = $wgMemc->get( $key );
- if( $mc && is_array( $mc ) ) { // is_array is hack for old keys
- $iw = Interwiki::loadFromArray( $mc );
+ $iwData = false;
+ if ( !wfRunHooks( 'InterwikiLoadPrefix', array( $prefix, &$iwData ) ) ) {
+ return Interwiki::loadFromArray( $iwData );
+ }
+
+ if ( !$iwData ) {
+ $key = wfMemcKey( 'interwiki', $prefix );
+ $iwData = $wgMemc->get( $key );
+ }
+
+ if( $iwData && is_array( $iwData ) ) { // is_array is hack for old keys
+ $iw = Interwiki::loadFromArray( $iwData );
if( $iw ) {
return $iw;
}
@@ -177,11 +185,11 @@ class Interwiki {
* @return Boolean: whether everything was there
*/
protected static function loadFromArray( $mc ) {
- if( isset( $mc['iw_url'] ) && isset( $mc['iw_local'] ) && isset( $mc['iw_trans'] ) ) {
+ if( isset( $mc['iw_url'] ) ) {
$iw = new Interwiki();
$iw->mURL = $mc['iw_url'];
- $iw->mLocal = $mc['iw_local'];
- $iw->mTrans = $mc['iw_trans'];
+ $iw->mLocal = isset( $mc['iw_local'] ) ? $mc['iw_local'] : 0;
+ $iw->mTrans = isset( $mc['iw_trans'] ) ? $mc['iw_trans'] : 0;
$iw->mAPI = isset( $mc['iw_api'] ) ? $mc['iw_api'] : '';
$iw->mWikiID = isset( $mc['iw_wikiid'] ) ? $mc['iw_wikiid'] : '';
@@ -248,9 +256,8 @@ class Interwiki {
* @return String
*/
public function getName() {
- $key = 'interwiki-name-' . $this->mPrefix;
- $msg = wfMsgForContent( $key );
- return wfEmptyMsg( $key, $msg ) ? '' : $msg;
+ $msg = wfMessage( 'interwiki-name-' . $this->mPrefix )->inContentLanguage();
+ return !$msg->exists() ? '' : $msg;
}
/**
@@ -259,8 +266,7 @@ class Interwiki {
* @return String
*/
public function getDescription() {
- $key = 'interwiki-desc-' . $this->mPrefix;
- $msg = wfMsgForContent( $key );
- return wfEmptyMsg( $key, $msg ) ? '' : $msg;
+ $msg = wfMessage( 'interwiki-desc-' . $this->mPrefix )->inContentLanguage();
+ return !$msg->exists() ? '' : $msg;
}
}
diff --git a/includes/job/DoubleRedirectJob.php b/includes/job/DoubleRedirectJob.php
index 3b4b0188..d7991f5e 100644
--- a/includes/job/DoubleRedirectJob.php
+++ b/includes/job/DoubleRedirectJob.php
@@ -13,13 +13,17 @@
*/
class DoubleRedirectJob extends Job {
var $reason, $redirTitle, $destTitleText;
+
+ /**
+ * @var User
+ */
static $user;
/**
* Insert jobs into the job queue to fix redirects to the given title
* @param $reason String: the reason for the fix, see message double-redirect-fixed-<reason>
* @param $redirTitle Title: the title which has changed, redirects pointing to this title are fixed
- * @param $destTitle Not used
+ * @param $destTitle bool Not used
*/
public static function fixRedirects( $reason, $redirTitle, $destTitle = false ) {
# Need to use the master to get the redirect table updated in the same transaction
@@ -53,6 +57,7 @@ class DoubleRedirectJob extends Job {
}
Job::batchInsert( $jobs );
}
+
function __construct( $title, $params = false, $id = 0 ) {
parent::__construct( 'fixDoubleRedirect', $title, $params, $id );
$this->reason = $params['reason'];
@@ -128,6 +133,9 @@ class DoubleRedirectJob extends Job {
/**
* Get the final destination of a redirect
+ *
+ * @param $title Title
+ *
* @return false if the specified title is not a redirect, or if it is a circular redirect
*/
public static function getFinalDestination( $title ) {
diff --git a/includes/job/JobQueue.php b/includes/job/JobQueue.php
index 8eec8215..0d917ba3 100644
--- a/includes/job/JobQueue.php
+++ b/includes/job/JobQueue.php
@@ -16,8 +16,13 @@ if ( !defined( 'MEDIAWIKI' ) ) {
* @ingroup JobQueue
*/
abstract class Job {
+
+ /**
+ * @var Title
+ */
+ var $title;
+
var $command,
- $title,
$params,
$id,
$removeDuplicates,
@@ -41,21 +46,28 @@ abstract class Job {
* Pop a job of a certain type. This tries less hard than pop() to
* actually find a job; it may be adversely affected by concurrent job
* runners.
+ *
+ * @param $type string
+ *
+ * @return Job
*/
static function pop_type( $type ) {
wfProfilein( __METHOD__ );
$dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+
$row = $dbw->selectRow(
'job',
'*',
array( 'job_cmd' => $type ),
__METHOD__,
- array( 'LIMIT' => 1 )
+ array( 'LIMIT' => 1, 'FOR UPDATE' )
);
if ( $row === false ) {
+ $dbw->commit();
wfProfileOut( __METHOD__ );
return false;
}
@@ -63,20 +75,21 @@ abstract class Job {
/* Ensure we "own" this row */
$dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
$affected = $dbw->affectedRows();
+ $dbw->commit();
if ( $affected == 0 ) {
wfProfileOut( __METHOD__ );
return false;
}
+ wfIncrStats( 'job-pop' );
$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 );
- $dbw->delete( 'job', $job->insertFields(), __METHOD__ );
- $dbw->commit();
+ $job->removeDuplicates();
wfProfileOut( __METHOD__ );
return $job;
@@ -89,6 +102,7 @@ abstract class Job {
* @return Job or false if there's no jobs
*/
static function pop( $offset = 0 ) {
+ global $wgJobTypesExcludedFromDefaultQueue;
wfProfileIn( __METHOD__ );
$dbr = wfGetDB( DB_SLAVE );
@@ -99,17 +113,27 @@ abstract class Job {
NB: If random fetch previously was used, offset
will always be ahead of few entries
*/
+ $conditions = array();
+ if ( count( $wgJobTypesExcludedFromDefaultQueue ) != 0 ) {
+ foreach ( $wgJobTypesExcludedFromDefaultQueue as $cmdType ) {
+ $conditions[] = "job_cmd != " . $dbr->addQuotes( $cmdType );
+ }
+ }
+ $offset = intval( $offset );
+ $options = array( 'ORDER BY' => 'job_id', 'USE INDEX' => 'PRIMARY' );
- $row = $dbr->selectRow( 'job', '*', "job_id >= ${offset}", __METHOD__,
- array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 ) );
+ $row = $dbr->selectRow( 'job', '*',
+ array_merge( $conditions, array( "job_id >= $offset" ) ),
+ __METHOD__,
+ $options
+ );
// Refetching without offset is needed as some of job IDs could have had delayed commits
// and have lower IDs than jobs already executed, blame concurrency :)
//
if ( $row === false ) {
if ( $offset != 0 ) {
- $row = $dbr->selectRow( 'job', '*', '', __METHOD__,
- array( 'ORDER BY' => 'job_id', 'LIMIT' => 1 ) );
+ $row = $dbr->selectRow( 'job', '*', $conditions, __METHOD__, $options );
}
if ( $row === false ) {
@@ -158,16 +182,14 @@ abstract class Job {
// If execution got to here, there's a row in $row that has been deleted from the database
// by this thread. Hence the concurrent pop was successful.
+ wfIncrStats( 'job-pop' );
$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 );
// Remove any duplicates it may have later in the queue
- // Deadlock prone section
- $dbw->begin();
- $dbw->delete( 'job', $job->insertFields(), __METHOD__ );
- $dbw->commit();
+ $job->removeDuplicates();
wfProfileOut( __METHOD__ );
return $job;
@@ -272,6 +294,12 @@ abstract class Job {
* Non-static functions
*------------------------------------------------------------------------*/
+ /**
+ * @param $command
+ * @param $title
+ * @param $params array
+ * @param int $id
+ */
function __construct( $command, $title, $params = false, $id = 0 ) {
$this->command = $command;
$this->title = $title;
@@ -298,6 +326,7 @@ abstract class Job {
return;
}
}
+ wfIncrStats( 'job-insert' );
return $dbw->insert( 'job', $fields, __METHOD__ );
}
@@ -312,6 +341,27 @@ abstract class Job {
);
}
+ /**
+ * Remove jobs in the job queue which are duplicates of this job.
+ * This is deadlock-prone and so starts its own transaction.
+ */
+ function removeDuplicates() {
+ if ( !$this->removeDuplicates ) {
+ return;
+ }
+
+ $fields = $this->insertFields();
+ unset( $fields['job_id'] );
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin();
+ $dbw->delete( 'job', $fields, __METHOD__ );
+ $affected = $dbw->affectedRows();
+ $dbw->commit();
+ if ( $affected ) {
+ wfIncrStats( 'job-dup-delete', $affected );
+ }
+ }
+
function toString() {
$paramString = '';
if ( $this->params ) {
diff --git a/includes/job/RefreshLinksJob.php b/includes/job/RefreshLinksJob.php
index cc91fa81..910f0c58 100644
--- a/includes/job/RefreshLinksJob.php
+++ b/includes/job/RefreshLinksJob.php
@@ -119,7 +119,7 @@ class RefreshLinksJob2 extends Job {
$update = new LinksUpdate( $title, $parserOutput, false );
$update->doUpdate();
wfProfileOut( __METHOD__.'-update' );
- wfWaitForSlaves( 5 );
+ wfWaitForSlaves();
}
wfProfileOut( __METHOD__ );
diff --git a/includes/job/UploadFromUrlJob.php b/includes/job/UploadFromUrlJob.php
index 63166ef9..3f915245 100644
--- a/includes/job/UploadFromUrlJob.php
+++ b/includes/job/UploadFromUrlJob.php
@@ -8,7 +8,7 @@
/**
* 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.
@@ -17,8 +17,15 @@
*/
class UploadFromUrlJob extends Job {
const SESSION_KEYNAME = 'wsUploadFromUrlJobData';
-
+
+ /**
+ * @var UploadFromUrl
+ */
public $upload;
+
+ /**
+ * @var User
+ */
protected $user;
public function __construct( $title, $params, $id = 0 ) {
@@ -28,20 +35,20 @@ class UploadFromUrlJob extends Job {
public function run() {
# Initialize this object and the upload object
$this->upload = new UploadFromUrl();
- $this->upload->initialize(
- $this->title->getText(),
+ $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 ) {
@@ -49,17 +56,17 @@ class UploadFromUrlJob extends Job {
$this->leaveMessage( $status );
return true;
}
-
+
# Check warnings
if ( !$this->params['ignoreWarnings'] ) {
$warnings = $this->upload->checkWarnings();
- if ( $warnings ) {
+ if ( $warnings ) {
wfSetupSession( $this->params['sessionId'] );
-
+
if ( $this->params['leaveMessage'] ) {
- $this->user->leaveUserMessage(
+ $this->user->leaveUserMessage(
wfMsg( 'upload-warning-subj' ),
- wfMsg( 'upload-warning-msg',
+ wfMsg( 'upload-warning-msg',
$this->params['sessionKey'],
$this->params['url'] )
);
@@ -67,17 +74,17 @@ class UploadFromUrlJob extends Job {
$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(
+ $status = $this->upload->performUpload(
$this->params['comment'],
$this->params['pageText'],
$this->params['watch'],
@@ -85,47 +92,47 @@ class UploadFromUrlJob extends Job {
);
$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',
+ wfMsg( 'upload-success-msg',
$this->upload->getTitle()->getText(),
- $this->params['url']
+ $this->params['url']
) );
} else {
$this->user->leaveUserMessage( wfMsg( 'upload-failure-subj' ),
- wfMsg( 'upload-failure-msg',
+ wfMsg( 'upload-failure-msg',
$status->getWikiText(),
$this->params['url']
) );
}
} else {
- wfSetupSession( $this->params['sessionId'] );
+ wfSetupSession( $this->params['sessionId'] );
if ( $status->isOk() ) {
- $this->storeResultInSession( 'Success',
+ $this->storeResultInSession( 'Success',
'filename', $this->upload->getLocalFile()->getName() );
} else {
$this->storeResultInSession( 'Failure',
'errors', $status->getErrorsArray() );
}
- session_write_close();
+ 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
@@ -135,7 +142,7 @@ class UploadFromUrlJob extends Job {
$session['result'] = $result;
$session[$dataKey] = $dataValue;
}
-
+
/**
* Initialize the session data. Sets the intial result to queued.
*/
@@ -143,7 +150,7 @@ class UploadFromUrlJob extends Job {
$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();
diff --git a/includes/json/FormatJson.php b/includes/json/FormatJson.php
index b7049aeb..006f7720 100644
--- a/includes/json/FormatJson.php
+++ b/includes/json/FormatJson.php
@@ -18,6 +18,11 @@ class FormatJson {
*
* @param $value Mixed: the value being encoded. Can be any type except a resource.
* @param $isHtml Boolean
+ *
+ * @todo FIXME: "$isHtml" parameter's purpose is not documented. It appears to
+ * map to a parameter labeled "pretty-print output with indents and
+ * newlines" in Services_JSON::encode(), which has no string relation
+ * to HTML output.
*
* @return string
*/
@@ -25,7 +30,7 @@ class FormatJson {
// 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 );
} else {
diff --git a/includes/json/Services_JSON.php b/includes/json/Services_JSON.php
index 5b4e0503..72bd616e 100644
--- a/includes/json/Services_JSON.php
+++ b/includes/json/Services_JSON.php
@@ -51,7 +51,7 @@
* @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 90492 2011-06-20 22:39:10Z reedy $
+* @version CVS: $Id: Services_JSON.php 95607 2011-08-27 19:28:13Z hashar $
* @license http://www.opensource.org/licenses/bsd-license.php
* @see http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/
@@ -168,7 +168,7 @@ class Services_JSON
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
}
- $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+ $bytes = (ord($utf16[0]) << 8) | ord($utf16[1]);
switch(true) {
case ((0x7F & $bytes) == $bytes):
@@ -182,11 +182,11 @@ class Services_JSON
return chr(0xC0 | (($bytes >> 6) & 0x1F))
. chr(0x80 | ($bytes & 0x3F));
- case (0xFC00 & $bytes) == 0xD800 && strlen($utf16) >= 4 && (0xFC & ord($utf16{2})) == 0xDC:
+ case (0xFC00 & $bytes) == 0xD800 && strlen($utf16) >= 4 && (0xFC & ord($utf16[2])) == 0xDC:
// return a 4-byte UTF-8 character
$char = ((($bytes & 0x03FF) << 10)
- | ((ord($utf16{2}) & 0x03) << 8)
- | ord($utf16{3}));
+ | ((ord($utf16[2]) & 0x03) << 8)
+ | ord($utf16[3]));
$char += 0x10000;
return chr(0xF0 | (($char >> 18) & 0x07))
. chr(0x80 | (($char >> 12) & 0x3F))
@@ -232,25 +232,25 @@ class Services_JSON
case 2:
// return a UTF-16 character from a 2-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr(0x07 & (ord($utf8{0}) >> 2))
- . chr((0xC0 & (ord($utf8{0}) << 6))
- | (0x3F & ord($utf8{1})));
+ return chr(0x07 & (ord($utf8[0]) >> 2))
+ . chr((0xC0 & (ord($utf8[0]) << 6))
+ | (0x3F & ord($utf8[1])));
case 3:
// return a UTF-16 character from a 3-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- return chr((0xF0 & (ord($utf8{0}) << 4))
- | (0x0F & (ord($utf8{1}) >> 2)))
- . chr((0xC0 & (ord($utf8{1}) << 6))
- | (0x7F & ord($utf8{2})));
+ return chr((0xF0 & (ord($utf8[0]) << 4))
+ | (0x0F & (ord($utf8[1]) >> 2)))
+ . chr((0xC0 & (ord($utf8[1]) << 6))
+ | (0x7F & ord($utf8[2])));
case 4:
// return a UTF-16 surrogate pair from a 4-byte UTF-8 char
- if(ord($utf8{0}) > 0xF4) return ''; # invalid
- $char = ((0x1C0000 & (ord($utf8{0}) << 18))
- | (0x03F000 & (ord($utf8{1}) << 12))
- | (0x000FC0 & (ord($utf8{2}) << 6))
- | (0x00003F & ord($utf8{3})));
+ if(ord($utf8[0]) > 0xF4) return ''; # invalid
+ $char = ((0x1C0000 & (ord($utf8[0]) << 18))
+ | (0x03F000 & (ord($utf8[1]) << 12))
+ | (0x000FC0 & (ord($utf8[2]) << 6))
+ | (0x00003F & ord($utf8[3])));
if($char > 0x10FFFF) return ''; # invalid
$char -= 0x10000;
return chr(0xD8 | (($char >> 18) & 0x03))
@@ -331,7 +331,7 @@ class Services_JSON
*/
for ($c = 0; $c < $strlen_var; ++$c) {
- $ord_var_c = ord($var{$c});
+ $ord_var_c = ord($var[$c]);
switch (true) {
case $ord_var_c == 0x08:
@@ -354,18 +354,18 @@ class Services_JSON
case $ord_var_c == 0x2F:
case $ord_var_c == 0x5C:
// double quote, slash, slosh
- $ascii .= '\\'.$var{$c};
+ $ascii .= '\\'.$var[$c];
break;
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
- $ascii .= $var{$c};
+ $ascii .= $var[$c];
break;
case (($ord_var_c & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
- $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+ $char = pack('C*', $ord_var_c, ord($var[$c + 1]));
$c += 1;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
@@ -375,8 +375,8 @@ class Services_JSON
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
- ord($var{$c + 1}),
- ord($var{$c + 2}));
+ ord($var[$c + 1]),
+ ord($var[$c + 2]));
$c += 2;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
@@ -387,9 +387,9 @@ class Services_JSON
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
// These will always return a surrogate pair
$char = pack('C*', $ord_var_c,
- ord($var{$c + 1}),
- ord($var{$c + 2}),
- ord($var{$c + 3}));
+ ord($var[$c + 1]),
+ ord($var[$c + 2]),
+ ord($var[$c + 3]));
$c += 3;
$utf16 = $this->utf82utf16($char);
if($utf16 == '') {
@@ -575,7 +575,7 @@ class Services_JSON
for ($c = 0; $c < $strlen_chrs; ++$c) {
$substr_chrs_c_2 = substr($chrs, $c, 2);
- $ord_chrs_c = ord($chrs{$c});
+ $ord_chrs_c = ord($chrs[$c]);
switch (true) {
case $substr_chrs_c_2 == '\b':
@@ -605,7 +605,7 @@ class Services_JSON
case $substr_chrs_c_2 == '\\/':
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
- $utf8 .= $chrs{++$c};
+ $utf8 .= $chrs[++$c];
}
break;
@@ -628,7 +628,7 @@ class Services_JSON
break;
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
- $utf8 .= $chrs{$c};
+ $utf8 .= $chrs[$c];
break;
case ($ord_chrs_c & 0xE0) == 0xC0:
@@ -675,7 +675,7 @@ class Services_JSON
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
// array, or object notation
- if ($str{0} == '[') {
+ if ($str[0] == '[') {
$stk = array(SERVICES_JSON_IN_ARR);
$arr = array();
} else {
@@ -714,7 +714,7 @@ class Services_JSON
$top = end($stk);
$substr_chrs_c_2 = substr($chrs, $c, 2);
- if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+ if (($c == $strlen_chrs) || (($chrs[$c] == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
// found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list
$slice = substr($chrs, $top['where'], ($c - $top['where']));
@@ -756,37 +756,37 @@ class Services_JSON
}
- } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+ } elseif ((($chrs[$c] == '"') || ($chrs[$c] == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
// found a quote, and we are not inside a string
- array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs[$c]));
//print("Found start of string at {$c}\n");
- } elseif (($chrs{$c} == $top['delim']) &&
+ } elseif (($chrs[$c] == $top['delim']) &&
($top['what'] == SERVICES_JSON_IN_STR) &&
- (($chrs{$c - 1} != '\\') ||
- ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) {
+ (($chrs[$c - 1] != '\\') ||
+ ($chrs[$c - 1] == '\\' && $chrs[$c - 2] == '\\'))) {
// found a quote, we're in a string, and it's not escaped
array_pop($stk);
//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
- } elseif (($chrs{$c} == '[') &&
+ } elseif (($chrs[$c] == '[') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-bracket, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
//print("Found start of array at {$c}\n");
- } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+ } elseif (($chrs[$c] == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
// found a right-bracket, and we're in an array
array_pop($stk);
//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
- } elseif (($chrs{$c} == '{') &&
+ } elseif (($chrs[$c] == '{') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-brace, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
//print("Found start of object at {$c}\n");
- } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+ } elseif (($chrs[$c] == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
// found a right-brace, and we're in an object
array_pop($stk);
//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
diff --git a/includes/libs/CSSMin.php b/includes/libs/CSSMin.php
index c0e78112..4012b695 100644
--- a/includes/libs/CSSMin.php
+++ b/includes/libs/CSSMin.php
@@ -1,24 +1,24 @@
<?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.
+ * 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>
@@ -26,9 +26,9 @@
* @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
*
@@ -37,9 +37,9 @@ class CSSMin {
*/
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',
@@ -51,9 +51,9 @@ class CSSMin {
'tiff' => 'image/tiff',
'xbm' => 'image/x-xbitmap',
);
-
+
/* Static Methods */
-
+
/**
* Gets a list of local file paths which are referenced in a CSS style sheet
*
@@ -78,7 +78,7 @@ class CSSMin {
}
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
@@ -92,7 +92,7 @@ class CSSMin {
// 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' ) ) {
+ } elseif ( function_exists( 'mime_content_type' ) ) {
// Before this was deprecated in PHP 5.3, this was how you got the mime-type of a file
return mime_content_type( $file );
} else {
@@ -104,7 +104,7 @@ class CSSMin {
}
return false;
}
-
+
/**
* Remaps CSS URL paths and automatically embeds data URIs for URL rules
* preceded by an /* @embed * / comment
@@ -130,12 +130,20 @@ class CSSMin {
// 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] )
- );
+ // Replace the file path with an expanded (possibly protocol-relative) URL
+ // ...but only if wfExpandUrl() is even available.
+ // This will not be the case if we're running outside of MW
+ $lengthIncrease = 0;
+ if ( function_exists( 'wfExpandUrl' ) ) {
+ $expanded = wfExpandUrl( $match['file'][0], PROTO_RELATIVE );
+ $origLength = strlen( $match['file'][0] );
+ $lengthIncrease = strlen( $expanded ) - $origLength;
+ $source = substr_replace( $source, $expanded,
+ $match['file'][1], $origLength
+ );
+ }
// Move the offset to the end of the match, leaving it alone
- $offset = $match[0][1] + strlen( $match[0][0] );
+ $offset = $match[0][1] + strlen( $match[0][0] ) + $lengthIncrease;
continue;
}
// Shortcuts
@@ -175,9 +183,9 @@ class CSSMin {
}
if ( $replacement === false ) {
// Assume that all paths are relative to $remote, and make them absolute
- $replacement = "{$embed}{$pre}url({$url}){$post};";
+ $replacement = "{$embed}{$pre}url({$url}){$post};";
}
- } else if ( $local === false ) {
+ } elseif ( $local === false ) {
// Assume that all paths are relative to $remote, and make them absolute
$replacement = "{$embed}{$pre}url({$url}{$query}){$post};";
}
diff --git a/includes/libs/HttpStatus.php b/includes/libs/HttpStatus.php
new file mode 100644
index 00000000..2985c652
--- /dev/null
+++ b/includes/libs/HttpStatus.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * @todo document
+ */
+class HttpStatus {
+
+ /**
+ * Get the message associed with the HTTP response code $code
+ *
+ * Replace OutputPage::getStatusMessage( $code )
+ *
+ * @param $code Integer: status code
+ * @return String or null: message or null if $code is not in the list of
+ * messages
+ */
+ public static function getMessage( $code ) {
+ static $statusMessage = array(
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-Status',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Request Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ 507 => 'Insufficient Storage'
+ );
+ return isset( $statusMessage[$code] ) ? $statusMessage[$code] : null;
+ }
+
+}
diff --git a/includes/libs/jsminplus.php b/includes/libs/jsminplus.php
new file mode 100644
index 00000000..bab4ff49
--- /dev/null
+++ b/includes/libs/jsminplus.php
@@ -0,0 +1,2094 @@
+<?php
+
+/**
+ * JSMinPlus version 1.3
+ *
+ * Minifies a javascript file using a javascript parser
+ *
+ * This implements a PHP port of Brendan Eich's Narcissus open source javascript engine (in javascript)
+ * References: http://en.wikipedia.org/wiki/Narcissus_(JavaScript_engine)
+ * Narcissus sourcecode: http://mxr.mozilla.org/mozilla/source/js/narcissus/
+ * JSMinPlus weblog: http://crisp.tweakblogs.net/blog/cat/716
+ *
+ * Tino Zijdel <crisp@tweakers.net>
+ *
+ * Usage: $minified = JSMinPlus::minify($script [, $filename])
+ *
+ * Versionlog (see also changelog.txt):
+ * 19-07-2011 - expanded operator and keyword defines. Fixes the notices when creating several JSTokenizer
+ * 17-05-2009 - fixed hook:colon precedence, fixed empty body in loop and if-constructs
+ * 18-04-2009 - fixed crashbug in PHP 5.2.9 and several other bugfixes
+ * 12-04-2009 - some small bugfixes and performance improvements
+ * 09-04-2009 - initial open sourced version 1.0
+ *
+ * Latest version of this script: http://files.tweakers.net/jsminplus/jsminplus.zip
+ *
+ */
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Narcissus JavaScript engine.
+ *
+ * The Initial Developer of the Original Code is
+ * Brendan Eich <brendan@mozilla.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2004
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s): Tino Zijdel <crisp@tweakers.net>
+ * PHP port, modifications and minifier routine are (C) 2009
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+define('TOKEN_END', 1);
+define('TOKEN_NUMBER', 2);
+define('TOKEN_IDENTIFIER', 3);
+define('TOKEN_STRING', 4);
+define('TOKEN_REGEXP', 5);
+define('TOKEN_NEWLINE', 6);
+define('TOKEN_CONDCOMMENT_START', 7);
+define('TOKEN_CONDCOMMENT_END', 8);
+
+define('JS_SCRIPT', 100);
+define('JS_BLOCK', 101);
+define('JS_LABEL', 102);
+define('JS_FOR_IN', 103);
+define('JS_CALL', 104);
+define('JS_NEW_WITH_ARGS', 105);
+define('JS_INDEX', 106);
+define('JS_ARRAY_INIT', 107);
+define('JS_OBJECT_INIT', 108);
+define('JS_PROPERTY_INIT', 109);
+define('JS_GETTER', 110);
+define('JS_SETTER', 111);
+define('JS_GROUP', 112);
+define('JS_LIST', 113);
+
+define('DECLARED_FORM', 0);
+define('EXPRESSED_FORM', 1);
+define('STATEMENT_FORM', 2);
+
+/* Operators */
+define('OP_SEMICOLON', ';');
+define('OP_COMMA', ',');
+define('OP_HOOK', '?');
+define('OP_COLON', ':');
+define('OP_OR', '||');
+define('OP_AND', '&&');
+define('OP_BITWISE_OR', '|');
+define('OP_BITWISE_XOR', '^');
+define('OP_BITWISE_AND', '&');
+define('OP_STRICT_EQ', '===');
+define('OP_EQ', '==');
+define('OP_ASSIGN', '=');
+define('OP_STRICT_NE', '!==');
+define('OP_NE', '!=');
+define('OP_LSH', '<<');
+define('OP_LE', '<=');
+define('OP_LT', '<');
+define('OP_URSH', '>>>');
+define('OP_RSH', '>>');
+define('OP_GE', '>=');
+define('OP_GT', '>');
+define('OP_INCREMENT', '++');
+define('OP_DECREMENT', '--');
+define('OP_PLUS', '+');
+define('OP_MINUS', '-');
+define('OP_MUL', '*');
+define('OP_DIV', '/');
+define('OP_MOD', '%');
+define('OP_NOT', '!');
+define('OP_BITWISE_NOT', '~');
+define('OP_DOT', '.');
+define('OP_LEFT_BRACKET', '[');
+define('OP_RIGHT_BRACKET', ']');
+define('OP_LEFT_CURLY', '{');
+define('OP_RIGHT_CURLY', '}');
+define('OP_LEFT_PAREN', '(');
+define('OP_RIGHT_PAREN', ')');
+define('OP_CONDCOMMENT_END', '@*/');
+
+define('OP_UNARY_PLUS', 'U+');
+define('OP_UNARY_MINUS', 'U-');
+
+/* Keywords */
+define('KEYWORD_BREAK', 'break');
+define('KEYWORD_CASE', 'case');
+define('KEYWORD_CATCH', 'catch');
+define('KEYWORD_CONST', 'const');
+define('KEYWORD_CONTINUE', 'continue');
+define('KEYWORD_DEBUGGER', 'debugger');
+define('KEYWORD_DEFAULT', 'default');
+define('KEYWORD_DELETE', 'delete');
+define('KEYWORD_DO', 'do');
+define('KEYWORD_ELSE', 'else');
+define('KEYWORD_ENUM', 'enum');
+define('KEYWORD_FALSE', 'false');
+define('KEYWORD_FINALLY', 'finally');
+define('KEYWORD_FOR', 'for');
+define('KEYWORD_FUNCTION', 'function');
+define('KEYWORD_IF', 'if');
+define('KEYWORD_IN', 'in');
+define('KEYWORD_INSTANCEOF', 'instanceof');
+define('KEYWORD_NEW', 'new');
+define('KEYWORD_NULL', 'null');
+define('KEYWORD_RETURN', 'return');
+define('KEYWORD_SWITCH', 'switch');
+define('KEYWORD_THIS', 'this');
+define('KEYWORD_THROW', 'throw');
+define('KEYWORD_TRUE', 'true');
+define('KEYWORD_TRY', 'try');
+define('KEYWORD_TYPEOF', 'typeof');
+define('KEYWORD_VAR', 'var');
+define('KEYWORD_VOID', 'void');
+define('KEYWORD_WHILE', 'while');
+define('KEYWORD_WITH', 'with');
+
+
+class JSMinPlus
+{
+ private $parser;
+ private $reserved = array(
+ 'break', 'case', 'catch', 'continue', 'default', 'delete', 'do',
+ 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof',
+ 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var',
+ 'void', 'while', 'with',
+ // Words reserved for future use
+ 'abstract', 'boolean', 'byte', 'char', 'class', 'const', 'debugger',
+ 'double', 'enum', 'export', 'extends', 'final', 'float', 'goto',
+ 'implements', 'import', 'int', 'interface', 'long', 'native',
+ 'package', 'private', 'protected', 'public', 'short', 'static',
+ 'super', 'synchronized', 'throws', 'transient', 'volatile',
+ // These are not reserved, but should be taken into account
+ // in isValidIdentifier (See jslint source code)
+ 'arguments', 'eval', 'true', 'false', 'Infinity', 'NaN', 'null', 'undefined'
+ );
+
+ private function __construct()
+ {
+ $this->parser = new JSParser();
+ }
+
+ public static function minify($js, $filename='')
+ {
+ static $instance;
+
+ // this is a singleton
+ if(!$instance)
+ $instance = new JSMinPlus();
+
+ return $instance->min($js, $filename);
+ }
+
+ private function min($js, $filename)
+ {
+ try
+ {
+ $n = $this->parser->parse($js, $filename, 1);
+ return $this->parseTree($n);
+ }
+ catch(Exception $e)
+ {
+ echo $e->getMessage() . "\n";
+ }
+
+ return false;
+ }
+
+ private function parseTree($n, $noBlockGrouping = false)
+ {
+ $s = '';
+
+ switch ($n->type)
+ {
+ case KEYWORD_FUNCTION:
+ $s .= 'function' . ($n->name ? ' ' . $n->name : '') . '(';
+ $params = $n->params;
+ for ($i = 0, $j = count($params); $i < $j; $i++)
+ $s .= ($i ? ',' : '') . $params[$i];
+ $s .= '){' . $this->parseTree($n->body, true) . '}';
+ break;
+
+ case JS_SCRIPT:
+ // we do nothing with funDecls or varDecls
+ $noBlockGrouping = true;
+ // FALL THROUGH
+
+ case JS_BLOCK:
+ $childs = $n->treeNodes;
+ $lastType = 0;
+ for ($c = 0, $i = 0, $j = count($childs); $i < $j; $i++)
+ {
+ $type = $childs[$i]->type;
+ $t = $this->parseTree($childs[$i]);
+ if (strlen($t))
+ {
+ if ($c)
+ {
+ $s = rtrim($s, ';');
+
+ if ($type == KEYWORD_FUNCTION && $childs[$i]->functionForm == DECLARED_FORM)
+ {
+ // put declared functions on a new line
+ $s .= "\n";
+ }
+ elseif ($type == KEYWORD_VAR && $type == $lastType)
+ {
+ // mutiple var-statements can go into one
+ $t = ',' . substr($t, 4);
+ }
+ else
+ {
+ // add terminator
+ $s .= ';';
+ }
+ }
+
+ $s .= $t;
+
+ $c++;
+ $lastType = $type;
+ }
+ }
+
+ if ($c > 1 && !$noBlockGrouping)
+ {
+ $s = '{' . $s . '}';
+ }
+ break;
+
+ case KEYWORD_IF:
+ $s = 'if(' . $this->parseTree($n->condition) . ')';
+ $thenPart = $this->parseTree($n->thenPart);
+ $elsePart = $n->elsePart ? $this->parseTree($n->elsePart) : null;
+
+ // empty if-statement
+ if ($thenPart == '')
+ $thenPart = ';';
+
+ if ($elsePart)
+ {
+ // be carefull and always make a block out of the thenPart; could be more optimized but is a lot of trouble
+ if ($thenPart != ';' && $thenPart[0] != '{')
+ $thenPart = '{' . $thenPart . '}';
+
+ $s .= $thenPart . 'else';
+
+ // we could check for more, but that hardly ever applies so go for performance
+ if ($elsePart[0] != '{')
+ $s .= ' ';
+
+ $s .= $elsePart;
+ }
+ else
+ {
+ $s .= $thenPart;
+ }
+ break;
+
+ case KEYWORD_SWITCH:
+ $s = 'switch(' . $this->parseTree($n->discriminant) . '){';
+ $cases = $n->cases;
+ for ($i = 0, $j = count($cases); $i < $j; $i++)
+ {
+ $case = $cases[$i];
+ if ($case->type == KEYWORD_CASE)
+ $s .= 'case' . ($case->caseLabel->type != TOKEN_STRING ? ' ' : '') . $this->parseTree($case->caseLabel) . ':';
+ else
+ $s .= 'default:';
+
+ $statement = $this->parseTree($case->statements, true);
+ if ($statement)
+ {
+ $s .= $statement;
+ // no terminator for last statement
+ if ($i + 1 < $j)
+ $s .= ';';
+ }
+ }
+ $s .= '}';
+ break;
+
+ case KEYWORD_FOR:
+ $s = 'for(' . ($n->setup ? $this->parseTree($n->setup) : '')
+ . ';' . ($n->condition ? $this->parseTree($n->condition) : '')
+ . ';' . ($n->update ? $this->parseTree($n->update) : '') . ')';
+
+ $body = $this->parseTree($n->body);
+ if ($body == '')
+ $body = ';';
+
+ $s .= $body;
+ break;
+
+ case KEYWORD_WHILE:
+ $s = 'while(' . $this->parseTree($n->condition) . ')';
+
+ $body = $this->parseTree($n->body);
+ if ($body == '')
+ $body = ';';
+
+ $s .= $body;
+ break;
+
+ case JS_FOR_IN:
+ $s = 'for(' . ($n->varDecl ? $this->parseTree($n->varDecl) : $this->parseTree($n->iterator)) . ' in ' . $this->parseTree($n->object) . ')';
+
+ $body = $this->parseTree($n->body);
+ if ($body == '')
+ $body = ';';
+
+ $s .= $body;
+ break;
+
+ case KEYWORD_DO:
+ $s = 'do{' . $this->parseTree($n->body, true) . '}while(' . $this->parseTree($n->condition) . ')';
+ break;
+
+ case KEYWORD_BREAK:
+ case KEYWORD_CONTINUE:
+ $s = $n->value . ($n->label ? ' ' . $n->label : '');
+ break;
+
+ case KEYWORD_TRY:
+ $s = 'try{' . $this->parseTree($n->tryBlock, true) . '}';
+ $catchClauses = $n->catchClauses;
+ for ($i = 0, $j = count($catchClauses); $i < $j; $i++)
+ {
+ $t = $catchClauses[$i];
+ $s .= 'catch(' . $t->varName . ($t->guard ? ' if ' . $this->parseTree($t->guard) : '') . '){' . $this->parseTree($t->block, true) . '}';
+ }
+ if ($n->finallyBlock)
+ $s .= 'finally{' . $this->parseTree($n->finallyBlock, true) . '}';
+ break;
+
+ case KEYWORD_THROW:
+ $s = 'throw ' . $this->parseTree($n->exception);
+ break;
+
+ case KEYWORD_RETURN:
+ $s = 'return';
+ if ($n->value)
+ {
+ $t = $this->parseTree($n->value);
+ if (strlen($t))
+ {
+ if ( $t[0] != '(' && $t[0] != '[' && $t[0] != '{' &&
+ $t[0] != '"' && $t[0] != "'" && $t[0] != '/'
+ )
+ $s .= ' ';
+
+ $s .= $t;
+ }
+ }
+ break;
+
+ case KEYWORD_WITH:
+ $s = 'with(' . $this->parseTree($n->object) . ')' . $this->parseTree($n->body);
+ break;
+
+ case KEYWORD_VAR:
+ case KEYWORD_CONST:
+ $s = $n->value . ' ';
+ $childs = $n->treeNodes;
+ for ($i = 0, $j = count($childs); $i < $j; $i++)
+ {
+ $t = $childs[$i];
+ $s .= ($i ? ',' : '') . $t->name;
+ $u = $t->initializer;
+ if ($u)
+ $s .= '=' . $this->parseTree($u);
+ }
+ break;
+
+ case KEYWORD_DEBUGGER:
+ throw new Exception('NOT IMPLEMENTED: DEBUGGER');
+ break;
+
+ case TOKEN_CONDCOMMENT_START:
+ case TOKEN_CONDCOMMENT_END:
+ $s = $n->value . ($n->type == TOKEN_CONDCOMMENT_START ? ' ' : '');
+ $childs = $n->treeNodes;
+ for ($i = 0, $j = count($childs); $i < $j; $i++)
+ $s .= $this->parseTree($childs[$i]);
+ break;
+
+ case OP_SEMICOLON:
+ if ($expression = $n->expression)
+ $s = $this->parseTree($expression);
+ break;
+
+ case JS_LABEL:
+ $s = $n->label . ':' . $this->parseTree($n->statement);
+ break;
+
+ case OP_COMMA:
+ $childs = $n->treeNodes;
+ for ($i = 0, $j = count($childs); $i < $j; $i++)
+ $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
+ break;
+
+ case OP_ASSIGN:
+ $s = $this->parseTree($n->treeNodes[0]) . $n->value . $this->parseTree($n->treeNodes[1]);
+ break;
+
+ case OP_HOOK:
+ $s = $this->parseTree($n->treeNodes[0]) . '?' . $this->parseTree($n->treeNodes[1]) . ':' . $this->parseTree($n->treeNodes[2]);
+ break;
+
+ case OP_OR: case OP_AND:
+ case OP_BITWISE_OR: case OP_BITWISE_XOR: case OP_BITWISE_AND:
+ case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
+ case OP_LT: case OP_LE: case OP_GE: case OP_GT:
+ case OP_LSH: case OP_RSH: case OP_URSH:
+ case OP_MUL: case OP_DIV: case OP_MOD:
+ $s = $this->parseTree($n->treeNodes[0]) . $n->type . $this->parseTree($n->treeNodes[1]);
+ break;
+
+ case OP_PLUS:
+ case OP_MINUS:
+ $left = $this->parseTree($n->treeNodes[0]);
+ $right = $this->parseTree($n->treeNodes[1]);
+
+ switch ($n->treeNodes[1]->type)
+ {
+ case OP_PLUS:
+ case OP_MINUS:
+ case OP_INCREMENT:
+ case OP_DECREMENT:
+ case OP_UNARY_PLUS:
+ case OP_UNARY_MINUS:
+ $s = $left . $n->type . ' ' . $right;
+ break;
+
+ case TOKEN_STRING:
+ //combine concatted strings with same quotestyle
+ if ($n->type == OP_PLUS && substr($left, -1) == $right[0])
+ {
+ $s = substr($left, 0, -1) . substr($right, 1);
+ break;
+ }
+ // FALL THROUGH
+
+ default:
+ $s = $left . $n->type . $right;
+ }
+ break;
+
+ case KEYWORD_IN:
+ $s = $this->parseTree($n->treeNodes[0]) . ' in ' . $this->parseTree($n->treeNodes[1]);
+ break;
+
+ case KEYWORD_INSTANCEOF:
+ $s = $this->parseTree($n->treeNodes[0]) . ' instanceof ' . $this->parseTree($n->treeNodes[1]);
+ break;
+
+ case KEYWORD_DELETE:
+ $s = 'delete ' . $this->parseTree($n->treeNodes[0]);
+ break;
+
+ case KEYWORD_VOID:
+ $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
+ break;
+
+ case KEYWORD_TYPEOF:
+ $s = 'typeof ' . $this->parseTree($n->treeNodes[0]);
+ break;
+
+ case OP_NOT:
+ case OP_BITWISE_NOT:
+ case OP_UNARY_PLUS:
+ case OP_UNARY_MINUS:
+ $s = $n->value . $this->parseTree($n->treeNodes[0]);
+ break;
+
+ case OP_INCREMENT:
+ case OP_DECREMENT:
+ if ($n->postfix)
+ $s = $this->parseTree($n->treeNodes[0]) . $n->value;
+ else
+ $s = $n->value . $this->parseTree($n->treeNodes[0]);
+ break;
+
+ case OP_DOT:
+ $s = $this->parseTree($n->treeNodes[0]) . '.' . $this->parseTree($n->treeNodes[1]);
+ break;
+
+ case JS_INDEX:
+ $s = $this->parseTree($n->treeNodes[0]);
+ // See if we can replace named index with a dot saving 3 bytes
+ if ( $n->treeNodes[0]->type == TOKEN_IDENTIFIER &&
+ $n->treeNodes[1]->type == TOKEN_STRING &&
+ $this->isValidIdentifier(substr($n->treeNodes[1]->value, 1, -1))
+ )
+ $s .= '.' . substr($n->treeNodes[1]->value, 1, -1);
+ else
+ $s .= '[' . $this->parseTree($n->treeNodes[1]) . ']';
+ break;
+
+ case JS_LIST:
+ $childs = $n->treeNodes;
+ for ($i = 0, $j = count($childs); $i < $j; $i++)
+ $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
+ break;
+
+ case JS_CALL:
+ $s = $this->parseTree($n->treeNodes[0]) . '(' . $this->parseTree($n->treeNodes[1]) . ')';
+ break;
+
+ case KEYWORD_NEW:
+ case JS_NEW_WITH_ARGS:
+ $s = 'new ' . $this->parseTree($n->treeNodes[0]) . '(' . ($n->type == JS_NEW_WITH_ARGS ? $this->parseTree($n->treeNodes[1]) : '') . ')';
+ break;
+
+ case JS_ARRAY_INIT:
+ $s = '[';
+ $childs = $n->treeNodes;
+ for ($i = 0, $j = count($childs); $i < $j; $i++)
+ {
+ $s .= ($i ? ',' : '') . $this->parseTree($childs[$i]);
+ }
+ $s .= ']';
+ break;
+
+ case JS_OBJECT_INIT:
+ $s = '{';
+ $childs = $n->treeNodes;
+ for ($i = 0, $j = count($childs); $i < $j; $i++)
+ {
+ $t = $childs[$i];
+ if ($i)
+ $s .= ',';
+ if ($t->type == JS_PROPERTY_INIT)
+ {
+ // Ditch the quotes when the index is a valid identifier
+ if ( $t->treeNodes[0]->type == TOKEN_STRING &&
+ $this->isValidIdentifier(substr($t->treeNodes[0]->value, 1, -1))
+ )
+ $s .= substr($t->treeNodes[0]->value, 1, -1);
+ else
+ $s .= $t->treeNodes[0]->value;
+
+ $s .= ':' . $this->parseTree($t->treeNodes[1]);
+ }
+ else
+ {
+ $s .= $t->type == JS_GETTER ? 'get' : 'set';
+ $s .= ' ' . $t->name . '(';
+ $params = $t->params;
+ for ($i = 0, $j = count($params); $i < $j; $i++)
+ $s .= ($i ? ',' : '') . $params[$i];
+ $s .= '){' . $this->parseTree($t->body, true) . '}';
+ }
+ }
+ $s .= '}';
+ break;
+
+ case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
+ case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
+ $s = $n->value;
+ break;
+
+ case JS_GROUP:
+ $s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
+ break;
+
+ default:
+ throw new Exception('UNKNOWN TOKEN TYPE: ' . $n->type);
+ }
+
+ return $s;
+ }
+
+ private function isValidIdentifier($string)
+ {
+ return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved);
+ }
+}
+
+class JSParser
+{
+ private $t;
+
+ private $opPrecedence = array(
+ ';' => 0,
+ ',' => 1,
+ '=' => 2, '?' => 2, ':' => 2,
+ // The above all have to have the same precedence, see bug 330975
+ '||' => 4,
+ '&&' => 5,
+ '|' => 6,
+ '^' => 7,
+ '&' => 8,
+ '==' => 9, '!=' => 9, '===' => 9, '!==' => 9,
+ '<' => 10, '<=' => 10, '>=' => 10, '>' => 10, 'in' => 10, 'instanceof' => 10,
+ '<<' => 11, '>>' => 11, '>>>' => 11,
+ '+' => 12, '-' => 12,
+ '*' => 13, '/' => 13, '%' => 13,
+ 'delete' => 14, 'void' => 14, 'typeof' => 14,
+ '!' => 14, '~' => 14, 'U+' => 14, 'U-' => 14,
+ '++' => 15, '--' => 15,
+ 'new' => 16,
+ '.' => 17,
+ JS_NEW_WITH_ARGS => 0, JS_INDEX => 0, JS_CALL => 0,
+ JS_ARRAY_INIT => 0, JS_OBJECT_INIT => 0, JS_GROUP => 0
+ );
+
+ private $opArity = array(
+ ',' => -2,
+ '=' => 2,
+ '?' => 3,
+ '||' => 2,
+ '&&' => 2,
+ '|' => 2,
+ '^' => 2,
+ '&' => 2,
+ '==' => 2, '!=' => 2, '===' => 2, '!==' => 2,
+ '<' => 2, '<=' => 2, '>=' => 2, '>' => 2, 'in' => 2, 'instanceof' => 2,
+ '<<' => 2, '>>' => 2, '>>>' => 2,
+ '+' => 2, '-' => 2,
+ '*' => 2, '/' => 2, '%' => 2,
+ 'delete' => 1, 'void' => 1, 'typeof' => 1,
+ '!' => 1, '~' => 1, 'U+' => 1, 'U-' => 1,
+ '++' => 1, '--' => 1,
+ 'new' => 1,
+ '.' => 2,
+ JS_NEW_WITH_ARGS => 2, JS_INDEX => 2, JS_CALL => 2,
+ JS_ARRAY_INIT => 1, JS_OBJECT_INIT => 1, JS_GROUP => 1,
+ TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1
+ );
+
+ public function __construct()
+ {
+ $this->t = new JSTokenizer();
+ }
+
+ public function parse($s, $f, $l)
+ {
+ // initialize tokenizer
+ $this->t->init($s, $f, $l);
+
+ $x = new JSCompilerContext(false);
+ $n = $this->Script($x);
+ if (!$this->t->isDone())
+ throw $this->t->newSyntaxError('Syntax error');
+
+ return $n;
+ }
+
+ private function Script($x)
+ {
+ $n = $this->Statements($x);
+ $n->type = JS_SCRIPT;
+ $n->funDecls = $x->funDecls;
+ $n->varDecls = $x->varDecls;
+
+ return $n;
+ }
+
+ private function Statements($x)
+ {
+ $n = new JSNode($this->t, JS_BLOCK);
+ array_push($x->stmtStack, $n);
+
+ while (!$this->t->isDone() && $this->t->peek() != OP_RIGHT_CURLY)
+ $n->addNode($this->Statement($x));
+
+ array_pop($x->stmtStack);
+
+ return $n;
+ }
+
+ private function Block($x)
+ {
+ $this->t->mustMatch(OP_LEFT_CURLY);
+ $n = $this->Statements($x);
+ $this->t->mustMatch(OP_RIGHT_CURLY);
+
+ return $n;
+ }
+
+ private function Statement($x)
+ {
+ $tt = $this->t->get();
+ $n2 = null;
+
+ // Cases for statements ending in a right curly return early, avoiding the
+ // common semicolon insertion magic after this switch.
+ switch ($tt)
+ {
+ case KEYWORD_FUNCTION:
+ return $this->FunctionDefinition(
+ $x,
+ true,
+ count($x->stmtStack) > 1 ? STATEMENT_FORM : DECLARED_FORM
+ );
+ break;
+
+ case OP_LEFT_CURLY:
+ $n = $this->Statements($x);
+ $this->t->mustMatch(OP_RIGHT_CURLY);
+ return $n;
+
+ case KEYWORD_IF:
+ $n = new JSNode($this->t);
+ $n->condition = $this->ParenExpression($x);
+ array_push($x->stmtStack, $n);
+ $n->thenPart = $this->Statement($x);
+ $n->elsePart = $this->t->match(KEYWORD_ELSE) ? $this->Statement($x) : null;
+ array_pop($x->stmtStack);
+ return $n;
+
+ case KEYWORD_SWITCH:
+ $n = new JSNode($this->t);
+ $this->t->mustMatch(OP_LEFT_PAREN);
+ $n->discriminant = $this->Expression($x);
+ $this->t->mustMatch(OP_RIGHT_PAREN);
+ $n->cases = array();
+ $n->defaultIndex = -1;
+
+ array_push($x->stmtStack, $n);
+
+ $this->t->mustMatch(OP_LEFT_CURLY);
+
+ while (($tt = $this->t->get()) != OP_RIGHT_CURLY)
+ {
+ switch ($tt)
+ {
+ case KEYWORD_DEFAULT:
+ if ($n->defaultIndex >= 0)
+ throw $this->t->newSyntaxError('More than one switch default');
+ // FALL THROUGH
+ case KEYWORD_CASE:
+ $n2 = new JSNode($this->t);
+ if ($tt == KEYWORD_DEFAULT)
+ $n->defaultIndex = count($n->cases);
+ else
+ $n2->caseLabel = $this->Expression($x, OP_COLON);
+ break;
+ default:
+ throw $this->t->newSyntaxError('Invalid switch case');
+ }
+
+ $this->t->mustMatch(OP_COLON);
+ $n2->statements = new JSNode($this->t, JS_BLOCK);
+ while (($tt = $this->t->peek()) != KEYWORD_CASE && $tt != KEYWORD_DEFAULT && $tt != OP_RIGHT_CURLY)
+ $n2->statements->addNode($this->Statement($x));
+
+ array_push($n->cases, $n2);
+ }
+
+ array_pop($x->stmtStack);
+ return $n;
+
+ case KEYWORD_FOR:
+ $n = new JSNode($this->t);
+ $n->isLoop = true;
+ $this->t->mustMatch(OP_LEFT_PAREN);
+
+ if (($tt = $this->t->peek()) != OP_SEMICOLON)
+ {
+ $x->inForLoopInit = true;
+ if ($tt == KEYWORD_VAR || $tt == KEYWORD_CONST)
+ {
+ $this->t->get();
+ $n2 = $this->Variables($x);
+ }
+ else
+ {
+ $n2 = $this->Expression($x);
+ }
+ $x->inForLoopInit = false;
+ }
+
+ if ($n2 && $this->t->match(KEYWORD_IN))
+ {
+ $n->type = JS_FOR_IN;
+ if ($n2->type == KEYWORD_VAR)
+ {
+ if (count($n2->treeNodes) != 1)
+ {
+ throw $this->t->SyntaxError(
+ 'Invalid for..in left-hand side',
+ $this->t->filename,
+ $n2->lineno
+ );
+ }
+
+ // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
+ $n->iterator = $n2->treeNodes[0];
+ $n->varDecl = $n2;
+ }
+ else
+ {
+ $n->iterator = $n2;
+ $n->varDecl = null;
+ }
+
+ $n->object = $this->Expression($x);
+ }
+ else
+ {
+ $n->setup = $n2 ? $n2 : null;
+ $this->t->mustMatch(OP_SEMICOLON);
+ $n->condition = $this->t->peek() == OP_SEMICOLON ? null : $this->Expression($x);
+ $this->t->mustMatch(OP_SEMICOLON);
+ $n->update = $this->t->peek() == OP_RIGHT_PAREN ? null : $this->Expression($x);
+ }
+
+ $this->t->mustMatch(OP_RIGHT_PAREN);
+ $n->body = $this->nest($x, $n);
+ return $n;
+
+ case KEYWORD_WHILE:
+ $n = new JSNode($this->t);
+ $n->isLoop = true;
+ $n->condition = $this->ParenExpression($x);
+ $n->body = $this->nest($x, $n);
+ return $n;
+
+ case KEYWORD_DO:
+ $n = new JSNode($this->t);
+ $n->isLoop = true;
+ $n->body = $this->nest($x, $n, KEYWORD_WHILE);
+ $n->condition = $this->ParenExpression($x);
+ if (!$x->ecmaStrictMode)
+ {
+ // <script language="JavaScript"> (without version hints) may need
+ // automatic semicolon insertion without a newline after do-while.
+ // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
+ $this->t->match(OP_SEMICOLON);
+ return $n;
+ }
+ break;
+
+ case KEYWORD_BREAK:
+ case KEYWORD_CONTINUE:
+ $n = new JSNode($this->t);
+
+ if ($this->t->peekOnSameLine() == TOKEN_IDENTIFIER)
+ {
+ $this->t->get();
+ $n->label = $this->t->currentToken()->value;
+ }
+
+ $ss = $x->stmtStack;
+ $i = count($ss);
+ $label = $n->label;
+ if ($label)
+ {
+ do
+ {
+ if (--$i < 0)
+ throw $this->t->newSyntaxError('Label not found');
+ }
+ while ($ss[$i]->label != $label);
+ }
+ else
+ {
+ do
+ {
+ if (--$i < 0)
+ throw $this->t->newSyntaxError('Invalid ' . $tt);
+ }
+ while (!$ss[$i]->isLoop && ($tt != KEYWORD_BREAK || $ss[$i]->type != KEYWORD_SWITCH));
+ }
+
+ $n->target = $ss[$i];
+ break;
+
+ case KEYWORD_TRY:
+ $n = new JSNode($this->t);
+ $n->tryBlock = $this->Block($x);
+ $n->catchClauses = array();
+
+ while ($this->t->match(KEYWORD_CATCH))
+ {
+ $n2 = new JSNode($this->t);
+ $this->t->mustMatch(OP_LEFT_PAREN);
+ $n2->varName = $this->t->mustMatch(TOKEN_IDENTIFIER)->value;
+
+ if ($this->t->match(KEYWORD_IF))
+ {
+ if ($x->ecmaStrictMode)
+ throw $this->t->newSyntaxError('Illegal catch guard');
+
+ if (count($n->catchClauses) && !end($n->catchClauses)->guard)
+ throw $this->t->newSyntaxError('Guarded catch after unguarded');
+
+ $n2->guard = $this->Expression($x);
+ }
+ else
+ {
+ $n2->guard = null;
+ }
+
+ $this->t->mustMatch(OP_RIGHT_PAREN);
+ $n2->block = $this->Block($x);
+ array_push($n->catchClauses, $n2);
+ }
+
+ if ($this->t->match(KEYWORD_FINALLY))
+ $n->finallyBlock = $this->Block($x);
+
+ if (!count($n->catchClauses) && !$n->finallyBlock)
+ throw $this->t->newSyntaxError('Invalid try statement');
+ return $n;
+
+ case KEYWORD_CATCH:
+ case KEYWORD_FINALLY:
+ throw $this->t->newSyntaxError($tt + ' without preceding try');
+
+ case KEYWORD_THROW:
+ $n = new JSNode($this->t);
+ $n->exception = $this->Expression($x);
+ break;
+
+ case KEYWORD_RETURN:
+ if (!$x->inFunction)
+ throw $this->t->newSyntaxError('Invalid return');
+
+ $n = new JSNode($this->t);
+ $tt = $this->t->peekOnSameLine();
+ if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
+ $n->value = $this->Expression($x);
+ else
+ $n->value = null;
+ break;
+
+ case KEYWORD_WITH:
+ $n = new JSNode($this->t);
+ $n->object = $this->ParenExpression($x);
+ $n->body = $this->nest($x, $n);
+ return $n;
+
+ case KEYWORD_VAR:
+ case KEYWORD_CONST:
+ $n = $this->Variables($x);
+ break;
+
+ case TOKEN_CONDCOMMENT_START:
+ case TOKEN_CONDCOMMENT_END:
+ $n = new JSNode($this->t);
+ return $n;
+
+ case KEYWORD_DEBUGGER:
+ $n = new JSNode($this->t);
+ break;
+
+ case TOKEN_NEWLINE:
+ case OP_SEMICOLON:
+ $n = new JSNode($this->t, OP_SEMICOLON);
+ $n->expression = null;
+ return $n;
+
+ default:
+ if ($tt == TOKEN_IDENTIFIER)
+ {
+ $this->t->scanOperand = false;
+ $tt = $this->t->peek();
+ $this->t->scanOperand = true;
+ if ($tt == OP_COLON)
+ {
+ $label = $this->t->currentToken()->value;
+ $ss = $x->stmtStack;
+ for ($i = count($ss) - 1; $i >= 0; --$i)
+ {
+ if ($ss[$i]->label == $label)
+ throw $this->t->newSyntaxError('Duplicate label');
+ }
+
+ $this->t->get();
+ $n = new JSNode($this->t, JS_LABEL);
+ $n->label = $label;
+ $n->statement = $this->nest($x, $n);
+
+ return $n;
+ }
+ }
+
+ $n = new JSNode($this->t, OP_SEMICOLON);
+ $this->t->unget();
+ $n->expression = $this->Expression($x);
+ $n->end = $n->expression->end;
+ break;
+ }
+
+ if ($this->t->lineno == $this->t->currentToken()->lineno)
+ {
+ $tt = $this->t->peekOnSameLine();
+ if ($tt != TOKEN_END && $tt != TOKEN_NEWLINE && $tt != OP_SEMICOLON && $tt != OP_RIGHT_CURLY)
+ throw $this->t->newSyntaxError('Missing ; before statement');
+ }
+
+ $this->t->match(OP_SEMICOLON);
+
+ return $n;
+ }
+
+ private function FunctionDefinition($x, $requireName, $functionForm)
+ {
+ $f = new JSNode($this->t);
+
+ if ($f->type != KEYWORD_FUNCTION)
+ $f->type = ($f->value == 'get') ? JS_GETTER : JS_SETTER;
+
+ if ($this->t->match(TOKEN_IDENTIFIER))
+ $f->name = $this->t->currentToken()->value;
+ elseif ($requireName)
+ throw $this->t->newSyntaxError('Missing function identifier');
+
+ $this->t->mustMatch(OP_LEFT_PAREN);
+ $f->params = array();
+
+ while (($tt = $this->t->get()) != OP_RIGHT_PAREN)
+ {
+ if ($tt != TOKEN_IDENTIFIER)
+ throw $this->t->newSyntaxError('Missing formal parameter');
+
+ array_push($f->params, $this->t->currentToken()->value);
+
+ if ($this->t->peek() != OP_RIGHT_PAREN)
+ $this->t->mustMatch(OP_COMMA);
+ }
+
+ $this->t->mustMatch(OP_LEFT_CURLY);
+
+ $x2 = new JSCompilerContext(true);
+ $f->body = $this->Script($x2);
+
+ $this->t->mustMatch(OP_RIGHT_CURLY);
+ $f->end = $this->t->currentToken()->end;
+
+ $f->functionForm = $functionForm;
+ if ($functionForm == DECLARED_FORM)
+ array_push($x->funDecls, $f);
+
+ return $f;
+ }
+
+ private function Variables($x)
+ {
+ $n = new JSNode($this->t);
+
+ do
+ {
+ $this->t->mustMatch(TOKEN_IDENTIFIER);
+
+ $n2 = new JSNode($this->t);
+ $n2->name = $n2->value;
+
+ if ($this->t->match(OP_ASSIGN))
+ {
+ if ($this->t->currentToken()->assignOp)
+ throw $this->t->newSyntaxError('Invalid variable initialization');
+
+ $n2->initializer = $this->Expression($x, OP_COMMA);
+ }
+
+ $n2->readOnly = $n->type == KEYWORD_CONST;
+
+ $n->addNode($n2);
+ array_push($x->varDecls, $n2);
+ }
+ while ($this->t->match(OP_COMMA));
+
+ return $n;
+ }
+
+ private function Expression($x, $stop=false)
+ {
+ $operators = array();
+ $operands = array();
+ $n = false;
+
+ $bl = $x->bracketLevel;
+ $cl = $x->curlyLevel;
+ $pl = $x->parenLevel;
+ $hl = $x->hookLevel;
+
+ while (($tt = $this->t->get()) != TOKEN_END)
+ {
+ if ($tt == $stop &&
+ $x->bracketLevel == $bl &&
+ $x->curlyLevel == $cl &&
+ $x->parenLevel == $pl &&
+ $x->hookLevel == $hl
+ )
+ {
+ // Stop only if tt matches the optional stop parameter, and that
+ // token is not quoted by some kind of bracket.
+ break;
+ }
+
+ switch ($tt)
+ {
+ case OP_SEMICOLON:
+ // NB: cannot be empty, Statement handled that.
+ break 2;
+
+ case OP_HOOK:
+ if ($this->t->scanOperand)
+ break 2;
+
+ while ( !empty($operators) &&
+ $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
+ )
+ $this->reduce($operators, $operands);
+
+ array_push($operators, new JSNode($this->t));
+
+ ++$x->hookLevel;
+ $this->t->scanOperand = true;
+ $n = $this->Expression($x);
+
+ if (!$this->t->match(OP_COLON))
+ break 2;
+
+ --$x->hookLevel;
+ array_push($operands, $n);
+ break;
+
+ case OP_COLON:
+ if ($x->hookLevel)
+ break 2;
+
+ throw $this->t->newSyntaxError('Invalid label');
+ break;
+
+ case OP_ASSIGN:
+ if ($this->t->scanOperand)
+ break 2;
+
+ // Use >, not >=, for right-associative ASSIGN
+ while ( !empty($operators) &&
+ $this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt]
+ )
+ $this->reduce($operators, $operands);
+
+ array_push($operators, new JSNode($this->t));
+ end($operands)->assignOp = $this->t->currentToken()->assignOp;
+ $this->t->scanOperand = true;
+ break;
+
+ case KEYWORD_IN:
+ // An in operator should not be parsed if we're parsing the head of
+ // a for (...) loop, unless it is in the then part of a conditional
+ // expression, or parenthesized somehow.
+ if ($x->inForLoopInit && !$x->hookLevel &&
+ !$x->bracketLevel && !$x->curlyLevel &&
+ !$x->parenLevel
+ )
+ break 2;
+ // FALL THROUGH
+ case OP_COMMA:
+ // A comma operator should not be parsed if we're parsing the then part
+ // of a conditional expression unless it's parenthesized somehow.
+ if ($tt == OP_COMMA && $x->hookLevel &&
+ !$x->bracketLevel && !$x->curlyLevel &&
+ !$x->parenLevel
+ )
+ break 2;
+ // Treat comma as left-associative so reduce can fold left-heavy
+ // COMMA trees into a single array.
+ // FALL THROUGH
+ case OP_OR:
+ case OP_AND:
+ case OP_BITWISE_OR:
+ case OP_BITWISE_XOR:
+ case OP_BITWISE_AND:
+ case OP_EQ: case OP_NE: case OP_STRICT_EQ: case OP_STRICT_NE:
+ case OP_LT: case OP_LE: case OP_GE: case OP_GT:
+ case KEYWORD_INSTANCEOF:
+ case OP_LSH: case OP_RSH: case OP_URSH:
+ case OP_PLUS: case OP_MINUS:
+ case OP_MUL: case OP_DIV: case OP_MOD:
+ case OP_DOT:
+ if ($this->t->scanOperand)
+ break 2;
+
+ while ( !empty($operators) &&
+ $this->opPrecedence[end($operators)->type] >= $this->opPrecedence[$tt]
+ )
+ $this->reduce($operators, $operands);
+
+ if ($tt == OP_DOT)
+ {
+ $this->t->mustMatch(TOKEN_IDENTIFIER);
+ array_push($operands, new JSNode($this->t, OP_DOT, array_pop($operands), new JSNode($this->t)));
+ }
+ else
+ {
+ array_push($operators, new JSNode($this->t));
+ $this->t->scanOperand = true;
+ }
+ break;
+
+ case KEYWORD_DELETE: case KEYWORD_VOID: case KEYWORD_TYPEOF:
+ case OP_NOT: case OP_BITWISE_NOT: case OP_UNARY_PLUS: case OP_UNARY_MINUS:
+ case KEYWORD_NEW:
+ if (!$this->t->scanOperand)
+ break 2;
+
+ array_push($operators, new JSNode($this->t));
+ break;
+
+ case OP_INCREMENT: case OP_DECREMENT:
+ if ($this->t->scanOperand)
+ {
+ array_push($operators, new JSNode($this->t)); // prefix increment or decrement
+ }
+ else
+ {
+ // Don't cross a line boundary for postfix {in,de}crement.
+ $t = $this->t->tokens[($this->t->tokenIndex + $this->t->lookahead - 1) & 3];
+ if ($t && $t->lineno != $this->t->lineno)
+ break 2;
+
+ if (!empty($operators))
+ {
+ // Use >, not >=, so postfix has higher precedence than prefix.
+ while ($this->opPrecedence[end($operators)->type] > $this->opPrecedence[$tt])
+ $this->reduce($operators, $operands);
+ }
+
+ $n = new JSNode($this->t, $tt, array_pop($operands));
+ $n->postfix = true;
+ array_push($operands, $n);
+ }
+ break;
+
+ case KEYWORD_FUNCTION:
+ if (!$this->t->scanOperand)
+ break 2;
+
+ array_push($operands, $this->FunctionDefinition($x, false, EXPRESSED_FORM));
+ $this->t->scanOperand = false;
+ break;
+
+ case KEYWORD_NULL: case KEYWORD_THIS: case KEYWORD_TRUE: case KEYWORD_FALSE:
+ case TOKEN_IDENTIFIER: case TOKEN_NUMBER: case TOKEN_STRING: case TOKEN_REGEXP:
+ if (!$this->t->scanOperand)
+ break 2;
+
+ array_push($operands, new JSNode($this->t));
+ $this->t->scanOperand = false;
+ break;
+
+ case TOKEN_CONDCOMMENT_START:
+ case TOKEN_CONDCOMMENT_END:
+ if ($this->t->scanOperand)
+ array_push($operators, new JSNode($this->t));
+ else
+ array_push($operands, new JSNode($this->t));
+ break;
+
+ case OP_LEFT_BRACKET:
+ if ($this->t->scanOperand)
+ {
+ // Array initialiser. Parse using recursive descent, as the
+ // sub-grammar here is not an operator grammar.
+ $n = new JSNode($this->t, JS_ARRAY_INIT);
+ while (($tt = $this->t->peek()) != OP_RIGHT_BRACKET)
+ {
+ if ($tt == OP_COMMA)
+ {
+ $this->t->get();
+ $n->addNode(null);
+ continue;
+ }
+
+ $n->addNode($this->Expression($x, OP_COMMA));
+ if (!$this->t->match(OP_COMMA))
+ break;
+ }
+
+ $this->t->mustMatch(OP_RIGHT_BRACKET);
+ array_push($operands, $n);
+ $this->t->scanOperand = false;
+ }
+ else
+ {
+ // Property indexing operator.
+ array_push($operators, new JSNode($this->t, JS_INDEX));
+ $this->t->scanOperand = true;
+ ++$x->bracketLevel;
+ }
+ break;
+
+ case OP_RIGHT_BRACKET:
+ if ($this->t->scanOperand || $x->bracketLevel == $bl)
+ break 2;
+
+ while ($this->reduce($operators, $operands)->type != JS_INDEX)
+ continue;
+
+ --$x->bracketLevel;
+ break;
+
+ case OP_LEFT_CURLY:
+ if (!$this->t->scanOperand)
+ break 2;
+
+ // Object initialiser. As for array initialisers (see above),
+ // parse using recursive descent.
+ ++$x->curlyLevel;
+ $n = new JSNode($this->t, JS_OBJECT_INIT);
+ while (!$this->t->match(OP_RIGHT_CURLY))
+ {
+ do
+ {
+ $tt = $this->t->get();
+ $tv = $this->t->currentToken()->value;
+ if (($tv == 'get' || $tv == 'set') && $this->t->peek() == TOKEN_IDENTIFIER)
+ {
+ if ($x->ecmaStrictMode)
+ throw $this->t->newSyntaxError('Illegal property accessor');
+
+ $n->addNode($this->FunctionDefinition($x, true, EXPRESSED_FORM));
+ }
+ else
+ {
+ switch ($tt)
+ {
+ case TOKEN_IDENTIFIER:
+ case TOKEN_NUMBER:
+ case TOKEN_STRING:
+ $id = new JSNode($this->t);
+ break;
+
+ case OP_RIGHT_CURLY:
+ if ($x->ecmaStrictMode)
+ throw $this->t->newSyntaxError('Illegal trailing ,');
+ break 3;
+
+ default:
+ throw $this->t->newSyntaxError('Invalid property name');
+ }
+
+ $this->t->mustMatch(OP_COLON);
+ $n->addNode(new JSNode($this->t, JS_PROPERTY_INIT, $id, $this->Expression($x, OP_COMMA)));
+ }
+ }
+ while ($this->t->match(OP_COMMA));
+
+ $this->t->mustMatch(OP_RIGHT_CURLY);
+ break;
+ }
+
+ array_push($operands, $n);
+ $this->t->scanOperand = false;
+ --$x->curlyLevel;
+ break;
+
+ case OP_RIGHT_CURLY:
+ if (!$this->t->scanOperand && $x->curlyLevel != $cl)
+ throw new Exception('PANIC: right curly botch');
+ break 2;
+
+ case OP_LEFT_PAREN:
+ if ($this->t->scanOperand)
+ {
+ array_push($operators, new JSNode($this->t, JS_GROUP));
+ }
+ else
+ {
+ while ( !empty($operators) &&
+ $this->opPrecedence[end($operators)->type] > $this->opPrecedence[KEYWORD_NEW]
+ )
+ $this->reduce($operators, $operands);
+
+ // Handle () now, to regularize the n-ary case for n > 0.
+ // We must set scanOperand in case there are arguments and
+ // the first one is a regexp or unary+/-.
+ $n = end($operators);
+ $this->t->scanOperand = true;
+ if ($this->t->match(OP_RIGHT_PAREN))
+ {
+ if ($n && $n->type == KEYWORD_NEW)
+ {
+ array_pop($operators);
+ $n->addNode(array_pop($operands));
+ }
+ else
+ {
+ $n = new JSNode($this->t, JS_CALL, array_pop($operands), new JSNode($this->t, JS_LIST));
+ }
+
+ array_push($operands, $n);
+ $this->t->scanOperand = false;
+ break;
+ }
+
+ if ($n && $n->type == KEYWORD_NEW)
+ $n->type = JS_NEW_WITH_ARGS;
+ else
+ array_push($operators, new JSNode($this->t, JS_CALL));
+ }
+
+ ++$x->parenLevel;
+ break;
+
+ case OP_RIGHT_PAREN:
+ if ($this->t->scanOperand || $x->parenLevel == $pl)
+ break 2;
+
+ while (($tt = $this->reduce($operators, $operands)->type) != JS_GROUP &&
+ $tt != JS_CALL && $tt != JS_NEW_WITH_ARGS
+ )
+ {
+ continue;
+ }
+
+ if ($tt != JS_GROUP)
+ {
+ $n = end($operands);
+ if ($n->treeNodes[1]->type != OP_COMMA)
+ $n->treeNodes[1] = new JSNode($this->t, JS_LIST, $n->treeNodes[1]);
+ else
+ $n->treeNodes[1]->type = JS_LIST;
+ }
+
+ --$x->parenLevel;
+ break;
+
+ // Automatic semicolon insertion means we may scan across a newline
+ // and into the beginning of another statement. If so, break out of
+ // the while loop and let the t.scanOperand logic handle errors.
+ default:
+ break 2;
+ }
+ }
+
+ if ($x->hookLevel != $hl)
+ throw $this->t->newSyntaxError('Missing : in conditional expression');
+
+ if ($x->parenLevel != $pl)
+ throw $this->t->newSyntaxError('Missing ) in parenthetical');
+
+ if ($x->bracketLevel != $bl)
+ throw $this->t->newSyntaxError('Missing ] in index expression');
+
+ if ($this->t->scanOperand)
+ throw $this->t->newSyntaxError('Missing operand');
+
+ // Resume default mode, scanning for operands, not operators.
+ $this->t->scanOperand = true;
+ $this->t->unget();
+
+ while (count($operators))
+ $this->reduce($operators, $operands);
+
+ return array_pop($operands);
+ }
+
+ private function ParenExpression($x)
+ {
+ $this->t->mustMatch(OP_LEFT_PAREN);
+ $n = $this->Expression($x);
+ $this->t->mustMatch(OP_RIGHT_PAREN);
+
+ return $n;
+ }
+
+ // Statement stack and nested statement handler.
+ private function nest($x, $node, $end = false)
+ {
+ array_push($x->stmtStack, $node);
+ $n = $this->statement($x);
+ array_pop($x->stmtStack);
+
+ if ($end)
+ $this->t->mustMatch($end);
+
+ return $n;
+ }
+
+ private function reduce(&$operators, &$operands)
+ {
+ $n = array_pop($operators);
+ $op = $n->type;
+ $arity = $this->opArity[$op];
+ $c = count($operands);
+ if ($arity == -2)
+ {
+ // Flatten left-associative trees
+ if ($c >= 2)
+ {
+ $left = $operands[$c - 2];
+ if ($left->type == $op)
+ {
+ $right = array_pop($operands);
+ $left->addNode($right);
+ return $left;
+ }
+ }
+ $arity = 2;
+ }
+
+ // Always use push to add operands to n, to update start and end
+ $a = array_splice($operands, $c - $arity);
+ for ($i = 0; $i < $arity; $i++)
+ $n->addNode($a[$i]);
+
+ // Include closing bracket or postfix operator in [start,end]
+ $te = $this->t->currentToken()->end;
+ if ($n->end < $te)
+ $n->end = $te;
+
+ array_push($operands, $n);
+
+ return $n;
+ }
+}
+
+class JSCompilerContext
+{
+ public $inFunction = false;
+ public $inForLoopInit = false;
+ public $ecmaStrictMode = false;
+ public $bracketLevel = 0;
+ public $curlyLevel = 0;
+ public $parenLevel = 0;
+ public $hookLevel = 0;
+
+ public $stmtStack = array();
+ public $funDecls = array();
+ public $varDecls = array();
+
+ public function __construct($inFunction)
+ {
+ $this->inFunction = $inFunction;
+ }
+}
+
+class JSNode
+{
+ private $type;
+ private $value;
+ private $lineno;
+ private $start;
+ private $end;
+
+ public $treeNodes = array();
+ public $funDecls = array();
+ public $varDecls = array();
+
+ public function __construct($t, $type=0)
+ {
+ if ($token = $t->currentToken())
+ {
+ $this->type = $type ? $type : $token->type;
+ $this->value = $token->value;
+ $this->lineno = $token->lineno;
+ $this->start = $token->start;
+ $this->end = $token->end;
+ }
+ else
+ {
+ $this->type = $type;
+ $this->lineno = $t->lineno;
+ }
+
+ if (($numargs = func_num_args()) > 2)
+ {
+ $args = func_get_args();
+ for ($i = 2; $i < $numargs; $i++)
+ $this->addNode($args[$i]);
+ }
+ }
+
+ // we don't want to bloat our object with all kind of specific properties, so we use overloading
+ public function __set($name, $value)
+ {
+ $this->$name = $value;
+ }
+
+ public function __get($name)
+ {
+ if (isset($this->$name))
+ return $this->$name;
+
+ return null;
+ }
+
+ public function addNode($node)
+ {
+ if ($node !== null)
+ {
+ if ($node->start < $this->start)
+ $this->start = $node->start;
+ if ($this->end < $node->end)
+ $this->end = $node->end;
+ }
+
+ $this->treeNodes[] = $node;
+ }
+}
+
+class JSTokenizer
+{
+ private $cursor = 0;
+ private $source;
+
+ public $tokens = array();
+ public $tokenIndex = 0;
+ public $lookahead = 0;
+ public $scanNewlines = false;
+ public $scanOperand = true;
+
+ public $filename;
+ public $lineno;
+
+ private $keywords = array(
+ 'break',
+ 'case', 'catch', 'const', 'continue',
+ 'debugger', 'default', 'delete', 'do',
+ 'else', 'enum',
+ 'false', 'finally', 'for', 'function',
+ 'if', 'in', 'instanceof',
+ 'new', 'null',
+ 'return',
+ 'switch',
+ 'this', 'throw', 'true', 'try', 'typeof',
+ 'var', 'void',
+ 'while', 'with'
+ );
+
+ private $opTypeNames = array(
+ ';' => 'SEMICOLON',
+ ',' => 'COMMA',
+ '?' => 'HOOK',
+ ':' => 'COLON',
+ '||' => 'OR',
+ '&&' => 'AND',
+ '|' => 'BITWISE_OR',
+ '^' => 'BITWISE_XOR',
+ '&' => 'BITWISE_AND',
+ '===' => 'STRICT_EQ',
+ '==' => 'EQ',
+ '=' => 'ASSIGN',
+ '!==' => 'STRICT_NE',
+ '!=' => 'NE',
+ '<<' => 'LSH',
+ '<=' => 'LE',
+ '<' => 'LT',
+ '>>>' => 'URSH',
+ '>>' => 'RSH',
+ '>=' => 'GE',
+ '>' => 'GT',
+ '++' => 'INCREMENT',
+ '--' => 'DECREMENT',
+ '+' => 'PLUS',
+ '-' => 'MINUS',
+ '*' => 'MUL',
+ '/' => 'DIV',
+ '%' => 'MOD',
+ '!' => 'NOT',
+ '~' => 'BITWISE_NOT',
+ '.' => 'DOT',
+ '[' => 'LEFT_BRACKET',
+ ']' => 'RIGHT_BRACKET',
+ '{' => 'LEFT_CURLY',
+ '}' => 'RIGHT_CURLY',
+ '(' => 'LEFT_PAREN',
+ ')' => 'RIGHT_PAREN',
+ '@*/' => 'CONDCOMMENT_END'
+ );
+
+ private $assignOps = array('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
+ private $opRegExp;
+
+ public function __construct()
+ {
+ $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', array_keys($this->opTypeNames))) . ')#';
+ }
+
+ public function init($source, $filename = '', $lineno = 1)
+ {
+ $this->source = $source;
+ $this->filename = $filename ? $filename : '[inline]';
+ $this->lineno = $lineno;
+
+ $this->cursor = 0;
+ $this->tokens = array();
+ $this->tokenIndex = 0;
+ $this->lookahead = 0;
+ $this->scanNewlines = false;
+ $this->scanOperand = true;
+ }
+
+ public function getInput($chunksize)
+ {
+ if ($chunksize)
+ return substr($this->source, $this->cursor, $chunksize);
+
+ return substr($this->source, $this->cursor);
+ }
+
+ public function isDone()
+ {
+ return $this->peek() == TOKEN_END;
+ }
+
+ public function match($tt)
+ {
+ return $this->get() == $tt || $this->unget();
+ }
+
+ public function mustMatch($tt)
+ {
+ if (!$this->match($tt))
+ throw $this->newSyntaxError('Unexpected token; token ' . $tt . ' expected');
+
+ return $this->currentToken();
+ }
+
+ public function peek()
+ {
+ if ($this->lookahead)
+ {
+ $next = $this->tokens[($this->tokenIndex + $this->lookahead) & 3];
+ if ($this->scanNewlines && $next->lineno != $this->lineno)
+ $tt = TOKEN_NEWLINE;
+ else
+ $tt = $next->type;
+ }
+ else
+ {
+ $tt = $this->get();
+ $this->unget();
+ }
+
+ return $tt;
+ }
+
+ public function peekOnSameLine()
+ {
+ $this->scanNewlines = true;
+ $tt = $this->peek();
+ $this->scanNewlines = false;
+
+ return $tt;
+ }
+
+ public function currentToken()
+ {
+ if (!empty($this->tokens))
+ return $this->tokens[$this->tokenIndex];
+ }
+
+ public function get($chunksize = 1000)
+ {
+ while($this->lookahead)
+ {
+ $this->lookahead--;
+ $this->tokenIndex = ($this->tokenIndex + 1) & 3;
+ $token = $this->tokens[$this->tokenIndex];
+ if ($token->type != TOKEN_NEWLINE || $this->scanNewlines)
+ return $token->type;
+ }
+
+ $conditional_comment = false;
+
+ // strip whitespace and comments
+ while(true)
+ {
+ $input = $this->getInput($chunksize);
+
+ // whitespace handling; gobble up \r as well (effectively we don't have support for MAC newlines!)
+ $re = $this->scanNewlines ? '/^[ \r\t]+/' : '/^\s+/';
+ if (preg_match($re, $input, $match))
+ {
+ $spaces = $match[0];
+ $spacelen = strlen($spaces);
+ $this->cursor += $spacelen;
+ if (!$this->scanNewlines)
+ $this->lineno += substr_count($spaces, "\n");
+
+ if ($spacelen == $chunksize)
+ continue; // complete chunk contained whitespace
+
+ $input = $this->getInput($chunksize);
+ if ($input == '' || $input[0] != '/')
+ break;
+ }
+
+ // Comments
+ if (!preg_match('/^\/(?:\*(@(?:cc_on|if|elif|else|end))?.*?\*\/|\/[^\n]*)/s', $input, $match))
+ {
+ if (!$chunksize)
+ break;
+
+ // retry with a full chunk fetch; this also prevents breakage of long regular expressions (which will never match a comment)
+ $chunksize = null;
+ continue;
+ }
+
+ // check if this is a conditional (JScript) comment
+ if (!empty($match[1]))
+ {
+ $match[0] = '/*' . $match[1];
+ $conditional_comment = true;
+ break;
+ }
+ else
+ {
+ $this->cursor += strlen($match[0]);
+ $this->lineno += substr_count($match[0], "\n");
+ }
+ }
+
+ if ($input == '')
+ {
+ $tt = TOKEN_END;
+ $match = array('');
+ }
+ elseif ($conditional_comment)
+ {
+ $tt = TOKEN_CONDCOMMENT_START;
+ }
+ else
+ {
+ switch ($input[0])
+ {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (preg_match('/^\d+\.\d*(?:[eE][-+]?\d+)?|^\d+(?:\.\d*)?[eE][-+]?\d+/', $input, $match))
+ {
+ $tt = TOKEN_NUMBER;
+ }
+ else if (preg_match('/^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/', $input, $match))
+ {
+ // this should always match because of \d+
+ $tt = TOKEN_NUMBER;
+ }
+ break;
+
+ case '"':
+ case "'":
+ if (preg_match('/^"(?:\\\\(?:.|\r?\n)|[^\\\\"\r\n]+)*"|^\'(?:\\\\(?:.|\r?\n)|[^\\\\\'\r\n]+)*\'/', $input, $match))
+ {
+ $tt = TOKEN_STRING;
+ }
+ else
+ {
+ if ($chunksize)
+ return $this->get(null); // retry with a full chunk fetch
+
+ throw $this->newSyntaxError('Unterminated string literal');
+ }
+ break;
+
+ case '/':
+ if ($this->scanOperand && preg_match('/^\/((?:\\\\.|\[(?:\\\\.|[^\]])*\]|[^\/])+)\/([gimy]*)/', $input, $match))
+ {
+ $tt = TOKEN_REGEXP;
+ break;
+ }
+ // FALL THROUGH
+
+ case '|':
+ case '^':
+ case '&':
+ case '<':
+ case '>':
+ case '+':
+ case '-':
+ case '*':
+ case '%':
+ case '=':
+ case '!':
+ // should always match
+ preg_match($this->opRegExp, $input, $match);
+ $op = $match[0];
+ if (in_array($op, $this->assignOps) && $input[strlen($op)] == '=')
+ {
+ $tt = OP_ASSIGN;
+ $match[0] .= '=';
+ }
+ else
+ {
+ $tt = $op;
+ if ($this->scanOperand)
+ {
+ if ($op == OP_PLUS)
+ $tt = OP_UNARY_PLUS;
+ elseif ($op == OP_MINUS)
+ $tt = OP_UNARY_MINUS;
+ }
+ $op = null;
+ }
+ break;
+
+ case '.':
+ if (preg_match('/^\.\d+(?:[eE][-+]?\d+)?/', $input, $match))
+ {
+ $tt = TOKEN_NUMBER;
+ break;
+ }
+ // FALL THROUGH
+
+ case ';':
+ case ',':
+ case '?':
+ case ':':
+ case '~':
+ case '[':
+ case ']':
+ case '{':
+ case '}':
+ case '(':
+ case ')':
+ // these are all single
+ $match = array($input[0]);
+ $tt = $input[0];
+ break;
+
+ case '@':
+ // check end of conditional comment
+ if (substr($input, 0, 3) == '@*/')
+ {
+ $match = array('@*/');
+ $tt = TOKEN_CONDCOMMENT_END;
+ }
+ else
+ throw $this->newSyntaxError('Illegal token');
+ break;
+
+ case "\n":
+ if ($this->scanNewlines)
+ {
+ $match = array("\n");
+ $tt = TOKEN_NEWLINE;
+ }
+ else
+ throw $this->newSyntaxError('Illegal token');
+ break;
+
+ default:
+ // Fast path for identifiers: word chars followed by whitespace or various other tokens.
+ // Note we don't need to exclude digits in the first char, as they've already been found
+ // above.
+ if (!preg_match('/^[$\w]+(?=[\s\/\|\^\&<>\+\-\*%=!.;,\?:~\[\]\{\}\(\)@])/', $input, $match))
+ {
+ // Character classes per ECMA-262 edition 5.1 section 7.6
+ // Per spec, must accept Unicode 3.0, *may* accept later versions.
+ // We'll take whatever PCRE understands, which should be more recent.
+ $identifierStartChars = "\\p{L}\\p{Nl}" . # UnicodeLetter
+ "\$" .
+ "_";
+ $identifierPartChars = $identifierStartChars .
+ "\\p{Mn}\\p{Mc}" . # UnicodeCombiningMark
+ "\\p{Nd}" . # UnicodeDigit
+ "\\p{Pc}"; # UnicodeConnectorPunctuation
+ $unicodeEscape = "\\\\u[0-9A-F-a-f]{4}";
+ $identifierRegex = "/^" .
+ "(?:[$identifierStartChars]|$unicodeEscape)" .
+ "(?:[$identifierPartChars]|$unicodeEscape)*" .
+ "/uS";
+ if (preg_match($identifierRegex, $input, $match))
+ {
+ if (strpos($match[0], '\\') !== false) {
+ // Per ECMA-262 edition 5.1, section 7.6 escape sequences should behave as if they were
+ // the original chars, but only within the boundaries of the identifier.
+ $decoded = preg_replace_callback('/\\\\u([0-9A-Fa-f]{4})/',
+ array(__CLASS__, 'unicodeEscapeCallback'),
+ $match[0]);
+
+ // Since our original regex didn't de-escape the originals, we need to check for validity again.
+ // No need to worry about token boundaries, as anything outside the identifier is illegal!
+ if (!preg_match("/^[$identifierStartChars][$identifierPartChars]*$/u", $decoded)) {
+ throw $this->newSyntaxError('Illegal token');
+ }
+
+ // Per spec it _ought_ to work to use these escapes for keywords words as well...
+ // but IE rejects them as invalid, while Firefox and Chrome treat them as identifiers
+ // that don't match the keyword.
+ if (in_array($decoded, $this->keywords)) {
+ throw $this->newSyntaxError('Illegal token');
+ }
+
+ // TODO: save the decoded form for output?
+ }
+ }
+ else
+ throw $this->newSyntaxError('Illegal token');
+ }
+ $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER;
+ }
+ }
+
+ $this->tokenIndex = ($this->tokenIndex + 1) & 3;
+
+ if (!isset($this->tokens[$this->tokenIndex]))
+ $this->tokens[$this->tokenIndex] = new JSToken();
+
+ $token = $this->tokens[$this->tokenIndex];
+ $token->type = $tt;
+
+ if ($tt == OP_ASSIGN)
+ $token->assignOp = $op;
+
+ $token->start = $this->cursor;
+
+ $token->value = $match[0];
+ $this->cursor += strlen($match[0]);
+
+ $token->end = $this->cursor;
+ $token->lineno = $this->lineno;
+
+ return $tt;
+ }
+
+ public function unget()
+ {
+ if (++$this->lookahead == 4)
+ throw $this->newSyntaxError('PANIC: too much lookahead!');
+
+ $this->tokenIndex = ($this->tokenIndex - 1) & 3;
+ }
+
+ public function newSyntaxError($m)
+ {
+ return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno);
+ }
+
+ public static function unicodeEscapeCallback($m)
+ {
+ return html_entity_decode('&#x' . $m[1]. ';', ENT_QUOTES, 'UTF-8');
+ }
+}
+
+class JSToken
+{
+ public $type;
+ public $value;
+ public $start;
+ public $end;
+ public $lineno;
+ public $assignOp;
+}
+
diff --git a/includes/libs/spyc.php b/includes/libs/spyc.php
deleted file mode 100644
index bc92e869..00000000
--- a/includes/libs/spyc.php
+++ /dev/null
@@ -1,248 +0,0 @@
-<?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/
- * @copyright Copyright 2005-2006 Chris Wanstrath
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
- */
-
-/**
- * The Simple PHP YAML Class.
- *
- * This class can be used to read a YAML file and convert its contents
- * into a PHP array. It currently supports a very limited subsection of
- * the YAML spec.
- *
- * @ingroup API
- */
-class Spyc {
-
- /**
- * Dump YAML from PHP array statically
- *
- * The dump method, when supplied with an array, will do its best
- * to convert the array into friendly YAML. Pretty simple. Feel free to
- * save the returned string as nothing.yml and pass it around.
- *
- * Oh, and you can decide how big the indent is and what the wordwrap
- * for folding is. Pretty cool -- just pass in 'false' for either if
- * you want to use the default.
- *
- * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
- * you can turn off wordwrap by passing in 0.
- *
- * @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;
- return $spyc->dump( $array, $indent, $wordwrap );
- }
-
- /**
- * Dump PHP array to YAML
- *
- * The dump method, when supplied with an array, will do its best
- * to convert the array into friendly YAML. Pretty simple. Feel free to
- * save the returned string as tasteful.yml and pass it around.
- *
- * Oh, and you can decide how big the indent is and what the wordwrap
- * for folding is. Pretty cool -- just pass in 'false' for either if
- * you want to use the default.
- *
- * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
- * you can turn off wordwrap by passing in 0.
- *
- * @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 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.
-
- // New features and options.
- if ( $indent === false or !is_numeric( $indent ) ) {
- $this->_dumpIndent = 2;
- } else {
- $this->_dumpIndent = $indent;
- }
-
- if ( $wordwrap === false or !is_numeric( $wordwrap ) ) {
- $this->_dumpWordWrap = 40;
- } else {
- $this->_dumpWordWrap = $wordwrap;
- }
-
- // New YAML document
- $string = "---\n";
-
- // Start at the base of the array and move through it.
- foreach ( $array as $key => $value ) {
- $string .= $this->_yamlize( $key, $value, 0 );
- }
- return $string;
- }
-
- /**** 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;
-
- /**** Private Methods ****/
-
- /**
- * Attempts to convert a key / value array item to YAML
- *
- * @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 ) ) {
- // It has children. What to do?
- // Make it the right kind of item
- $string = $this->_dumpNode( $key, null, $indent );
- // Add the indent
- $indent += $this->_dumpIndent;
- // Yamlize the array
- $string .= $this->_yamlizeArray( $value, $indent );
- } elseif ( !is_array( $value ) ) {
- // It doesn't have children. Yip.
- $string = $this->_dumpNode( $key, $value, $indent );
- }
- return $string;
- }
-
- /**
- * Attempts to convert an array to YAML
- *
- * @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 ) ) {
- $string = '';
- foreach ( $array as $key => $value ) {
- $string .= $this->_yamlize( $key, $value, $indent );
- }
- return $string;
- } else {
- 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 String: the string to check
- * @return Boolean
- */
- function _needLiteral( $value ) {
- // Check whether the string contains # or : or begins with any of:
- // [ - ? , [ ] { } ! * & | > ' " % @ ` ]
- // or is a number or contains newlines
- return (bool)( gettype( $value ) == "string" &&
- ( is_numeric( $value ) ||
- strpos( $value, "\n" ) ||
- preg_match( "/[#:]/", $value ) ||
- preg_match( "/^[-?,[\]{}!*&|>'\"%@`]/", $value ) ) );
- }
-
- /**
- * Returns YAML from a key and a value
- *
- * @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 );
- }
-
- $spaces = str_repeat( ' ', $indent );
-
- if ( is_int( $key ) ) {
- // It's a sequence
- if ( $value !== '' && !is_null( $value ) )
- $string = $spaces . '- ' . $value . "\n";
- else
- $string = $spaces . "-\n";
- } else {
- if ( $key == '*' ) // bug 21922 - Quote asterix used as keys
- $key = "'*'";
-
- // It's mapped
- if ( $value !== '' && !is_null( $value ) )
- $string = $spaces . $key . ': ' . $value . "\n";
- else
- $string = $spaces . $key . ":\n";
- }
- return $string;
- }
-
- /**
- * Creates a literal block for dumping
- *
- * @param $value String
- * @param $indent Integer: the value of the indent
- * @return String
- */
- private function _doLiteralBlock( $value, $indent ) {
- $exploded = explode( "\n", $value );
- $newValue = '|-';
- $indent += $this->_dumpIndent;
- $spaces = str_repeat( ' ', $indent );
- foreach ( $exploded as $line ) {
- $newValue .= "\n" . $spaces . trim( $line );
- }
- return $newValue;
- }
-
- /**
- * Folds a string of text, if necessary
- *
- * @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
- if ( $this->_dumpWordWrap === 0 ) {
- return $value;
- }
-
- if ( strlen( $value ) > $this->_dumpWordWrap ) {
- $indent += $this->_dumpIndent;
- $indent = str_repeat( ' ', $indent );
- $wrapped = wordwrap( $value, $this->_dumpWordWrap, "\n$indent" );
- $value = ">-\n" . $indent . $wrapped;
- }
- return $value;
- }
-}
diff --git a/includes/media/BMP.php b/includes/media/BMP.php
index de836b59..6886e950 100644
--- a/includes/media/BMP.php
+++ b/includes/media/BMP.php
@@ -13,22 +13,39 @@
* @ingroup Media
*/
class BmpHandler extends BitmapHandler {
- // We never want to use .bmp in an <img/> tag
+
+ /**
+ * @param $file
+ * @return bool
+ */
function mustRender( $file ) {
return true;
}
- // Render files as PNG
+ /**
+ * Render files as PNG
+ *
+ * @param $text
+ * @param $mime
+ * @param $params
+ * @return array
+ */
function getThumbType( $text, $mime, $params = null ) {
return array( 'png', 'image/png' );
}
- /*
+ /**
* Get width and height from the bmp header.
+ *
+ * @param $image
+ * @param $filename
+ * @return array
*/
function getImageSize( $image, $filename ) {
- $f = fopen( $filename, 'r' );
- if(!$f) return false;
+ $f = fopen( $filename, 'rb' );
+ if( !$f ) {
+ return false;
+ }
$header = fread( $f, 54 );
fclose($f);
@@ -37,8 +54,12 @@ class BmpHandler extends BitmapHandler {
$h = substr( $header, 22, 4);
// Convert the unsigned long 32 bits (little endian):
- $w = unpack( 'V' , $w );
- $h = unpack( 'V' , $h );
+ try {
+ $w = wfUnpack( 'V', $w, 4 );
+ $h = wfUnpack( 'V', $h, 4 );
+ } catch ( MWException $e ) {
+ return false;
+ }
return array( $w[1], $h[1] );
}
}
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
index f5f7ba6d..5f796095 100644
--- a/includes/media/Bitmap.php
+++ b/includes/media/Bitmap.php
@@ -12,6 +12,14 @@
* @ingroup Media
*/
class BitmapHandler extends ImageHandler {
+
+ /**
+ * @param $image File
+ * @param $params array Transform parameters. Entries with the keys 'width'
+ * and 'height' are the respective screen width and height, while the keys
+ * 'physicalWidth' and 'physicalHeight' indicate the thumbnail dimensions.
+ * @return bool
+ */
function normaliseParams( $image, &$params ) {
global $wgMaxImageArea;
if ( !parent::normaliseParams( $image, $params ) ) {
@@ -19,25 +27,26 @@ class BitmapHandler extends ImageHandler {
}
$mimeType = $image->getMimeType();
+ # Obtain the source, pre-rotation dimensions
$srcWidth = $image->getWidth( $params['page'] );
$srcHeight = $image->getHeight( $params['page'] );
# Don't make an image bigger than the source
- $params['physicalWidth'] = $params['width'];
- $params['physicalHeight'] = $params['height'];
-
if ( $params['physicalWidth'] >= $srcWidth ) {
$params['physicalWidth'] = $srcWidth;
$params['physicalHeight'] = $srcHeight;
+
# Skip scaling limit checks if no scaling is required
- if ( !$image->mustRender() )
+ # due to requested size being bigger than source.
+ 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
+ # @todo FIXME: This actually only applies to ImageMagick
if ( $mimeType !== 'image/jpeg' &&
$srcWidth * $srcHeight > $wgMaxImageArea )
{
@@ -46,6 +55,30 @@ class BitmapHandler extends ImageHandler {
return true;
}
+
+ /**
+ * Extracts the width/height if the image will be scaled before rotating
+ *
+ * This will match the physical size/aspect ratio of the original image
+ * prior to application of the rotation -- so for a portrait image that's
+ * stored as raw landscape with 90-degress rotation, the resulting size
+ * will be wider than it is tall.
+ *
+ * @param $params array Parameters as returned by normaliseParams
+ * @param $rotation int The rotation angle that will be applied
+ * @return array ($width, $height) array
+ */
+ public function extractPreRotationDimensions( $params, $rotation ) {
+ if ( $rotation == 90 || $rotation == 270 ) {
+ # We'll resize before rotation, so swap the dimensions again
+ $width = $params['physicalHeight'];
+ $height = $params['physicalWidth'];
+ } else {
+ $width = $params['physicalWidth'];
+ $height = $params['physicalHeight'];
+ }
+ return array( $width, $height );
+ }
// Function that returns the number of pixels to be thumbnailed.
@@ -54,10 +87,15 @@ class BitmapHandler extends ImageHandler {
return $width * $height;
}
+ /**
+ * @param $image File
+ * @param $dstPath
+ * @param $dstUrl
+ * @param $params
+ * @param int $flags
+ * @return MediaTransformError|ThumbnailImage|TransformParameterError
+ */
function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
- global $wgUseImageMagick;
- global $wgCustomConvertCommand, $wgUseImageResize;
-
if ( !$this->normaliseParams( $image, $params ) ) {
return new TransformParameterError( $params );
}
@@ -79,6 +117,7 @@ class BitmapHandler extends ImageHandler {
'mimeType' => $image->getMimeType(),
'srcPath' => $image->getPath(),
'dstPath' => $dstPath,
+ 'dstUrl' => $dstUrl,
);
wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath\n" );
@@ -93,20 +132,7 @@ class BitmapHandler extends ImageHandler {
}
# Determine scaler type
- if ( !$dstPath ) {
- # No output path available, client side scaling only
- $scaler = 'client';
- } elseif ( !$wgUseImageResize ) {
- $scaler = 'client';
- } elseif ( $wgUseImageMagick ) {
- $scaler = 'im';
- } elseif ( $wgCustomConvertCommand ) {
- $scaler = 'custom';
- } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
- $scaler = 'gd';
- } else {
- $scaler = 'client';
- }
+ $scaler = self::getScalerType( $dstPath );
wfDebug( __METHOD__ . ": scaler $scaler\n" );
if ( $scaler == 'client' ) {
@@ -127,13 +153,28 @@ class BitmapHandler extends ImageHandler {
return $this->getClientScalingThumbnailImage( $image, $scalerParams );
}
+ # Try a hook
+ $mto = null;
+ wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) );
+ if ( !is_null( $mto ) ) {
+ wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" );
+ $scaler = 'hookaborted';
+ }
+
switch ( $scaler ) {
+ case 'hookaborted':
+ # Handled by the hook above
+ $err = $mto->isError() ? $mto : false;
+ break;
case 'im':
$err = $this->transformImageMagick( $image, $scalerParams );
break;
case 'custom':
$err = $this->transformCustom( $image, $scalerParams );
break;
+ case 'imext':
+ $err = $this->transformImageMagickExt( $image, $scalerParams );
+ break;
case 'gd':
default:
$err = $this->transformGd( $image, $scalerParams );
@@ -149,6 +190,8 @@ class BitmapHandler extends ImageHandler {
# Thumbnail was zero-byte and had to be removed
return new MediaTransformError( 'thumbnail_error',
$scalerParams['clientWidth'], $scalerParams['clientHeight'] );
+ } elseif ( $mto ) {
+ return $mto;
} else {
return new ThumbnailImage( $image, $dstUrl, $scalerParams['clientWidth'],
$scalerParams['clientHeight'], $dstPath );
@@ -156,12 +199,49 @@ class BitmapHandler extends ImageHandler {
}
/**
+ * Returns which scaler type should be used. Creates parent directories
+ * for $dstPath and returns 'client' on error
+ *
+ * @return string client,im,custom,gd
+ */
+ protected static function getScalerType( $dstPath, $checkDstPath = true ) {
+ global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand;
+
+ if ( !$dstPath && $checkDstPath ) {
+ # No output path available, client side scaling only
+ $scaler = 'client';
+ } elseif ( !$wgUseImageResize ) {
+ $scaler = 'client';
+ } elseif ( $wgUseImageMagick ) {
+ $scaler = 'im';
+ } elseif ( $wgCustomConvertCommand ) {
+ $scaler = 'custom';
+ } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
+ $scaler = 'gd';
+ } elseif ( class_exists( 'Imagick' ) ) {
+ $scaler = 'imext';
+ } else {
+ $scaler = 'client';
+ }
+
+ if ( $scaler != 'client' && $dstPath ) {
+ if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+ # Unable to create a path for the thumbnail
+ return 'client';
+ }
+ }
+ return $scaler;
+ }
+
+ /**
* Get a ThumbnailImage that respresents an image that will be scaled
* client side
*
* @param $image File File associated with this thumbnail
* @param $params array Array with scaler params
* @return ThumbnailImage
+ *
+ * @fixme no rotation support
*/
protected function getClientScalingThumbnailImage( $image, $params ) {
return new ThumbnailImage( $image, $image->getURL(),
@@ -215,7 +295,7 @@ class BitmapHandler extends ImageHandler {
// 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';
+ $animation_post = '-fuzz 5% -layers optimizeTransparency';
}
}
}
@@ -225,6 +305,9 @@ class BitmapHandler extends ImageHandler {
if ( strval( $wgImageMagickTempDir ) !== '' ) {
$env['MAGICK_TMPDIR'] = $wgImageMagickTempDir;
}
+
+ $rotation = $this->getRotation( $image );
+ list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
$cmd =
wfEscapeShellArg( $wgImageMagickConvertCommand ) .
@@ -237,12 +320,13 @@ class BitmapHandler extends ImageHandler {
// 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']}!" ) .
+ " -thumbnail " . wfEscapeShellArg( "{$width}x{$height}!" ) .
// 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" .
+ " -depth 8 $sharpen " .
+ " -rotate -$rotation " .
" {$animation_post} " .
wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) ) . " 2>&1";
@@ -261,6 +345,84 @@ class BitmapHandler extends ImageHandler {
}
/**
+ * Transform an image using the Imagick PHP extension
+ *
+ * @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 transformImageMagickExt( $image, $params ) {
+ global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea;
+
+ try {
+ $im = new Imagick();
+ $im->readImage( $params['srcPath'] );
+
+ if ( $params['mimeType'] == 'image/jpeg' ) {
+ // Sharpening, see bug 6193
+ if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
+ / ( $params['srcWidth'] + $params['srcHeight'] )
+ < $wgSharpenReductionThreshold ) {
+ // Hack, since $wgSharpenParamater is written specifically for the command line convert
+ list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter );
+ $im->sharpenImage( $radius, $sigma );
+ }
+ $im->setCompressionQuality( 80 );
+ } elseif( $params['mimeType'] == 'image/png' ) {
+ $im->setCompressionQuality( 95 );
+ } 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
+ $im->setImageScene( 0 );
+ } elseif ( $this->isAnimatedImage( $image ) ) {
+ // Coalesce is needed to scale animated GIFs properly (bug 1017).
+ $im = $im->coalesceImages();
+ }
+ }
+
+ $rotation = $this->getRotation( $image );
+ list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
+
+ $im->setImageBackgroundColor( new ImagickPixel( 'white' ) );
+
+ // Call Imagick::thumbnailImage on each frame
+ foreach ( $im as $i => $frame ) {
+ if ( !$frame->thumbnailImage( $width, $height, /* fit */ false ) ) {
+ return $this->getMediaTransformError( $params, "Error scaling frame $i" );
+ }
+ }
+ $im->setImageDepth( 8 );
+
+ if ( $rotation ) {
+ if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
+ return $this->getMediaTransformError( $params, "Error rotating $rotation degrees" );
+ }
+ }
+
+ if ( $this->isAnimatedImage( $image ) ) {
+ wfDebug( __METHOD__ . ": Writing animated thumbnail\n" );
+ // This is broken somehow... can't find out how to fix it
+ $result = $im->writeImages( $params['dstPath'], true );
+ } else {
+ $result = $im->writeImage( $params['dstPath'] );
+ }
+ if ( !$result ) {
+ return $this->getMediaTransformError( $params,
+ "Unable to write thumbnail to {$params['dstPath']}" );
+ }
+
+ } catch ( ImagickException $e ) {
+ return $this->getMediaTransformError( $params, $e->getMessage() );
+ }
+
+ return false;
+
+ }
+
+ /**
* Transform an image using a custom command
*
* @param $image File File associated with this thumbnail
@@ -306,12 +468,12 @@ class BitmapHandler extends ImageHandler {
}
/**
* 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 ) {
+ public function getMediaTransformError( $params, $errMsg ) {
return new MediaTransformError( 'thumbnail_error', $params['clientWidth'],
$params['clientHeight'], $errMsg );
}
@@ -360,8 +522,10 @@ class BitmapHandler extends ImageHandler {
}
$src_image = call_user_func( $loader, $params['srcPath'] );
- $dst_image = imagecreatetruecolor( $params['physicalWidth'],
- $params['physicalHeight'] );
+
+ $rotation = function_exists( 'imagerotate' ) ? $this->getRotation( $image ) : 0;
+ list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
+ $dst_image = imagecreatetruecolor( $width, $height );
// Initialise the destination image to transparent instead of
// the default solid black, to support PNG and GIF transparency nicely
@@ -374,15 +538,21 @@ class BitmapHandler extends ImageHandler {
// It may just uglify them, and completely breaks transparency.
imagecopyresized( $dst_image, $src_image,
0, 0, 0, 0,
- $params['physicalWidth'], $params['physicalHeight'],
+ $width, $height,
imagesx( $src_image ), imagesy( $src_image ) );
} else {
imagecopyresampled( $dst_image, $src_image,
0, 0, 0, 0,
- $params['physicalWidth'], $params['physicalHeight'],
+ $width, $height,
imagesx( $src_image ), imagesy( $src_image ) );
}
+ if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) {
+ $rot_image = imagerotate( $dst_image, $rotation, 0 );
+ imagedestroy( $dst_image );
+ $dst_image = $rot_image;
+ }
+
imagesavealpha( $dst_image, true );
call_user_func( $saveType, $dst_image, $params['dstPath'] );
@@ -508,98 +678,57 @@ class BitmapHandler extends ImageHandler {
imagejpeg( $dst_image, $thumbPath, 95 );
}
-
- function getMetadata( $image, $filename ) {
- global $wgShowEXIF;
- if ( $wgShowEXIF && file_exists( $filename ) ) {
- $exif = new Exif( $filename );
- $data = $exif->getFilteredData();
- if ( $data ) {
- $data['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
- return serialize( $data );
- } else {
- return '0';
- }
- } else {
- return '';
- }
- }
-
- function getMetadataType( $image ) {
- return 'exif';
- }
-
- function isMetadataValid( $image, $metadata ) {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- # Metadata disabled and so an empty field is expected
- return true;
- }
- if ( $metadata === '0' ) {
- # Special value indicating that there is no EXIF data in the file
- return true;
- }
- wfSuppressWarnings();
- $exif = unserialize( $metadata );
- wfRestoreWarnings();
- if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
- $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
- {
- # Wrong version
- wfDebug( __METHOD__ . ": wrong version\n" );
- return false;
- }
- return true;
+ /**
+ * On supporting image formats, try to read out the low-level orientation
+ * of the file and return the angle that the file needs to be rotated to
+ * be viewed.
+ *
+ * This information is only useful when manipulating the original file;
+ * the width and height we normally work with is logical, and will match
+ * any produced output views.
+ *
+ * The base BitmapHandler doesn't understand any metadata formats, so this
+ * is left up to child classes to implement.
+ *
+ * @param $file File
+ * @return int 0, 90, 180 or 270
+ */
+ public function getRotation( $file ) {
+ return 0;
}
/**
- * Get a list of EXIF metadata items which should be displayed when
- * the metadata table is collapsed.
+ * Returns whether the current scaler supports rotation (im and gd do)
*
- * @return array of strings
- * @access private
+ * @return bool
*/
- function visibleMetadataFields() {
- $fields = array();
- $lines = explode( "\n", wfMsgForContent( 'metadata-fields' ) );
- foreach ( $lines as $line ) {
- $matches = array();
- if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
- $fields[] = $matches[1];
- }
+ public static function canRotate() {
+ $scaler = self::getScalerType( null, false );
+ switch ( $scaler ) {
+ case 'im':
+ # ImageMagick supports autorotation
+ return true;
+ case 'imext':
+ # Imagick::rotateImage
+ return true;
+ case 'gd':
+ # GD's imagerotate function is used to rotate images, but not
+ # all precompiled PHP versions have that function
+ return function_exists( 'imagerotate' );
+ default:
+ # Other scalers don't support rotation
+ return false;
}
- $fields = array_map( 'strtolower', $fields );
- return $fields;
}
- function formatMetadata( $image ) {
- $result = array(
- 'visible' => array(),
- 'collapsed' => array()
- );
- $metadata = $image->getMetadata();
- if ( !$metadata ) {
- return false;
- }
- $exif = unserialize( $metadata );
- if ( !$exif ) {
- return false;
- }
- unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
- $format = new FormatExif( $exif );
-
- $formatted = $format->getFormattedData();
- // Sort fields into visible and collapsed
- $visibleFields = $this->visibleMetadataFields();
- foreach ( $formatted as $name => $value ) {
- $tag = strtolower( $name );
- self::addMeta( $result,
- in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
- 'exif',
- $tag,
- $value
- );
- }
- return $result;
+ /**
+ * Rerurns whether the file needs to be rendered. Returns true if the
+ * file requires rotation and we are able to rotate it.
+ *
+ * @param $file File
+ * @return bool
+ */
+ public function mustRender( $file ) {
+ return self::canRotate() && $this->getRotation( $file ) != 0;
}
}
diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php
new file mode 100644
index 00000000..d1caa67a
--- /dev/null
+++ b/includes/media/BitmapMetadataHandler.php
@@ -0,0 +1,269 @@
+<?php
+/**
+Class to deal with reconciling and extracting metadata from bitmap images.
+This is meant to comply with http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf
+
+This sort of acts as an intermediary between MediaHandler::getMetadata
+and the various metadata extractors.
+
+@todo other image formats.
+*/
+class BitmapMetadataHandler {
+
+ private $metadata = array();
+ private $metaPriority = array(
+ 20 => array( 'other' ),
+ 40 => array( 'native' ),
+ 60 => array( 'iptc-good-hash', 'iptc-no-hash' ),
+ 70 => array( 'xmp-deprecated' ),
+ 80 => array( 'xmp-general' ),
+ 90 => array( 'xmp-exif' ),
+ 100 => array( 'iptc-bad-hash' ),
+ 120 => array( 'exif' ),
+ );
+ private $iptcType = 'iptc-no-hash';
+
+ /**
+ * This does the photoshop image resource app13 block
+ * of interest, IPTC-IIM metadata is stored here.
+ *
+ * Mostly just calls doPSIR and doIPTC
+ *
+ * @param String $app13 String containing app13 block from jpeg file
+ */
+ private function doApp13 ( $app13 ) {
+ $this->iptcType = JpegMetadataExtractor::doPSIR( $app13 );
+
+ $iptc = IPTC::parse( $app13 );
+ $this->addMetadata( $iptc, $this->iptcType );
+ }
+
+
+ /**
+ * Get exif info using exif class.
+ * Basically what used to be in BitmapHandler::getMetadata().
+ * Just calls stuff in the Exif class.
+ *
+ * @param $filename string
+ */
+ function getExif ( $filename, $byteOrder ) {
+ global $wgShowEXIF;
+ if ( file_exists( $filename ) && $wgShowEXIF ) {
+ $exif = new Exif( $filename, $byteOrder );
+ $data = $exif->getFilteredData();
+ if ( $data ) {
+ $this->addMetadata( $data, 'exif' );
+ }
+ }
+ }
+ /** Add misc metadata. Warning: atm if the metadata category
+ * doesn't have a priority, it will be silently discarded.
+ *
+ * @param Array $metaArray array of metadata values
+ * @param string $type type. defaults to other. if two things have the same type they're merged
+ */
+ function addMetadata ( $metaArray, $type = 'other' ) {
+ if ( isset( $this->metadata[$type] ) ) {
+ /* merge with old data */
+ $metaArray = $metaArray + $this->metadata[$type];
+ }
+
+ $this->metadata[$type] = $metaArray;
+ }
+
+ /**
+ * Merge together the various types of metadata
+ * the different types have different priorites,
+ * and are merged in order.
+ *
+ * This function is generally called by the media handlers' getMetadata()
+ *
+ * @return Array metadata array
+ */
+ function getMetadataArray () {
+ // this seems a bit ugly... This is all so its merged in right order
+ // based on the MWG recomendation.
+ $temp = Array();
+ krsort( $this->metaPriority );
+ foreach ( $this->metaPriority as $pri ) {
+ foreach ( $pri as $type ) {
+ if ( isset( $this->metadata[$type] ) ) {
+ // Do some special casing for multilingual values.
+ // Don't discard translations if also as a simple value.
+ foreach ( $this->metadata[$type] as $itemName => $item ) {
+ if ( is_array( $item ) && isset( $item['_type'] ) && $item['_type'] === 'lang' ) {
+ if ( isset( $temp[$itemName] ) && !is_array( $temp[$itemName] ) ) {
+ $default = $temp[$itemName];
+ $temp[$itemName] = $item;
+ $temp[$itemName]['x-default'] = $default;
+ unset( $this->metadata[$type][$itemName] );
+ }
+ }
+ }
+
+ $temp = $temp + $this->metadata[$type];
+ }
+ }
+ }
+ return $temp;
+ }
+
+ /** Main entry point for jpeg's.
+ *
+ * @param $filename string filename (with full path)
+ * @return metadata result array.
+ * @throws MWException on invalid file.
+ */
+ static function Jpeg ( $filename ) {
+ $showXMP = function_exists( 'xml_parser_create_ns' );
+ $meta = new self();
+
+ $seg = JpegMetadataExtractor::segmentSplitter( $filename );
+ if ( isset( $seg['COM'] ) && isset( $seg['COM'][0] ) ) {
+ $meta->addMetadata( Array( 'JPEGFileComment' => $seg['COM'] ), 'native' );
+ }
+ if ( isset( $seg['PSIR'] ) ) {
+ $meta->doApp13( $seg['PSIR'] );
+ }
+ if ( isset( $seg['XMP'] ) && $showXMP ) {
+ $xmp = new XMPReader();
+ $xmp->parse( $seg['XMP'] );
+ foreach ( $seg['XMP_ext'] as $xmpExt ) {
+ /* Support for extended xmp in jpeg files
+ * is not well tested and a bit fragile.
+ */
+ $xmp->parseExtended( $xmpExt );
+
+ }
+ $res = $xmp->getResults();
+ foreach ( $res as $type => $array ) {
+ $meta->addMetadata( $array, $type );
+ }
+ }
+ if ( isset( $seg['byteOrder'] ) ) {
+ $meta->getExif( $filename, $seg['byteOrder'] );
+ }
+ return $meta->getMetadataArray();
+ }
+
+ /** Entry point for png
+ * At some point in the future this might
+ * merge the png various tEXt chunks to that
+ * are interesting, but for now it only does XMP
+ *
+ * @param $filename String full path to file
+ * @return Array Array for storage in img_metadata.
+ */
+ static public function PNG ( $filename ) {
+ $showXMP = function_exists( 'xml_parser_create_ns' );
+
+ $meta = new self();
+ $array = PNGMetadataExtractor::getMetadata( $filename );
+ if ( isset( $array['text']['xmp']['x-default'] ) && $array['text']['xmp']['x-default'] !== '' && $showXMP ) {
+ $xmp = new XMPReader();
+ $xmp->parse( $array['text']['xmp']['x-default'] );
+ $xmpRes = $xmp->getResults();
+ foreach ( $xmpRes as $type => $xmpSection ) {
+ $meta->addMetadata( $xmpSection, $type );
+ }
+ }
+ unset( $array['text']['xmp'] );
+ $meta->addMetadata( $array['text'], 'native' );
+ unset( $array['text'] );
+ $array['metadata'] = $meta->getMetadataArray();
+ $array['metadata']['_MW_PNG_VERSION'] = PNGMetadataExtractor::VERSION;
+ return $array;
+ }
+
+ /** function for gif images.
+ *
+ * They don't really have native metadata, so just merges together
+ * XMP and image comment.
+ *
+ * @param $filename full path to file
+ * @return Array metadata array
+ */
+ static public function GIF ( $filename ) {
+
+ $meta = new self();
+ $baseArray = GIFMetadataExtractor::getMetadata( $filename );
+
+ if ( count( $baseArray['comment'] ) > 0 ) {
+ $meta->addMetadata( array( 'GIFFileComment' => $baseArray['comment'] ), 'native' );
+ }
+
+ if ( $baseArray['xmp'] !== '' && function_exists( 'xml_parser_create_ns' ) ) {
+ $xmp = new XMPReader();
+ $xmp->parse( $baseArray['xmp'] );
+ $xmpRes = $xmp->getResults();
+ foreach ( $xmpRes as $type => $xmpSection ) {
+ $meta->addMetadata( $xmpSection, $type );
+ }
+
+ }
+
+ unset( $baseArray['comment'] );
+ unset( $baseArray['xmp'] );
+
+ $baseArray['metadata'] = $meta->getMetadataArray();
+ $baseArray['metadata']['_MW_GIF_VERSION'] = GIFMetadataExtractor::VERSION;
+ return $baseArray;
+ }
+
+ /**
+ * This doesn't do much yet, but eventually I plan to add
+ * XMP support for Tiff. (PHP's exif support already extracts
+ * but needs some further processing because PHP's exif support
+ * is stupid...)
+ *
+ * @todo Add XMP support, so this function actually makes
+ * sense to put here.
+ *
+ * The various exceptions this throws are caught later.
+ * @param $filename String
+ * @return Array The metadata.
+ */
+ static public function Tiff ( $filename ) {
+ if ( file_exists( $filename ) ) {
+ $byteOrder = self::getTiffByteOrder( $filename );
+ if ( !$byteOrder ) {
+ throw new MWException( "Error determining byte order of $filename" );
+ }
+ $exif = new Exif( $filename, $byteOrder );
+ $data = $exif->getFilteredData();
+ if ( $data ) {
+ $data['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+ return $data;
+ } else {
+ throw new MWException( "Could not extract data from tiff file $filename" );
+ }
+ } else {
+ throw new MWException( "File doesn't exist - $filename" );
+ }
+ }
+ /**
+ * Read the first 2 bytes of a tiff file to figure out
+ * Little Endian or Big Endian. Needed for exif stuff.
+ *
+ * @param $filename String The filename
+ * @return String 'BE' or 'LE' or false
+ */
+ static function getTiffByteOrder( $filename ) {
+ $fh = fopen( $filename, 'rb' );
+ if ( !$fh ) return false;
+ $head = fread( $fh, 2 );
+ fclose( $fh );
+
+ switch( $head ) {
+ case 'II':
+ return 'LE'; // II for intel.
+ case 'MM':
+ return 'BE'; // MM for motorla.
+ default:
+ return false; // Something went wrong.
+
+ }
+ }
+
+
+}
diff --git a/includes/media/Bitmap_ClientOnly.php b/includes/media/Bitmap_ClientOnly.php
index 9f6f7b33..50679229 100644
--- a/includes/media/Bitmap_ClientOnly.php
+++ b/includes/media/Bitmap_ClientOnly.php
@@ -15,10 +15,24 @@
* @ingroup Media
*/
class BitmapHandler_ClientOnly extends BitmapHandler {
+
+ /**
+ * @param $image File
+ * @param $params
+ * @return bool
+ */
function normaliseParams( $image, &$params ) {
return ImageHandler::normaliseParams( $image, $params );
}
+ /**
+ * @param $image File
+ * @param $dstPath
+ * @param $dstUrl
+ * @param $params
+ * @param int $flags
+ * @return ThumbnailImage|TransformParameterError
+ */
function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
if ( !$this->normaliseParams( $image, $params ) ) {
return new TransformParameterError( $params );
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
index cc3f1db5..2833f683 100644
--- a/includes/media/DjVu.php
+++ b/includes/media/DjVu.php
@@ -5,13 +5,17 @@
* @file
* @ingroup Media
*/
-
+
/**
* Handler for DjVu images
*
* @ingroup Media
*/
class DjVuHandler extends ImageHandler {
+
+ /**
+ * @return bool
+ */
function isEnabled() {
global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
@@ -22,9 +26,25 @@ class DjVuHandler extends ImageHandler {
}
}
- function mustRender( $file ) { return true; }
- function isMultiPage( $file ) { return true; }
+ /**
+ * @param $file
+ * @return bool
+ */
+ function mustRender( $file ) {
+ return true;
+ }
+
+ /**
+ * @param $file
+ * @return bool
+ */
+ function isMultiPage( $file ) {
+ return true;
+ }
+ /**
+ * @return array
+ */
function getParamMap() {
return array(
'img_width' => 'width',
@@ -32,6 +52,11 @@ class DjVuHandler extends ImageHandler {
);
}
+ /**
+ * @param $name
+ * @param $value
+ * @return bool
+ */
function validateParam( $name, $value ) {
if ( in_array( $name, array( 'width', 'height', 'page' ) ) ) {
if ( $value <= 0 ) {
@@ -44,6 +69,10 @@ class DjVuHandler extends ImageHandler {
}
}
+ /**
+ * @param $params
+ * @return bool|string
+ */
function makeParamString( $params ) {
$page = isset( $params['page'] ) ? $params['page'] : 1;
if ( !isset( $params['width'] ) ) {
@@ -52,6 +81,10 @@ class DjVuHandler extends ImageHandler {
return "page{$page}-{$params['width']}px";
}
+ /**
+ * @param $str
+ * @return array|bool
+ */
function parseParamString( $str ) {
$m = false;
if ( preg_match( '/^page(\d+)-(\d+)px$/', $str, $m ) ) {
@@ -61,6 +94,10 @@ class DjVuHandler extends ImageHandler {
}
}
+ /**
+ * @param $params
+ * @return array
+ */
function getScriptParams( $params ) {
return array(
'width' => $params['width'],
@@ -68,6 +105,14 @@ class DjVuHandler extends ImageHandler {
);
}
+ /**
+ * @param $image File
+ * @param $dstPath
+ * @param $dstUrl
+ * @param $params
+ * @param int $flags
+ * @return MediaTransformError|ThumbnailImage|TransformParameterError
+ */
function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
global $wgDjvuRenderer, $wgDjvuPostProcessor;
@@ -75,7 +120,9 @@ class DjVuHandler extends ImageHandler {
// normaliseParams will inevitably give.
$xml = $image->getMetadata();
if ( !$xml ) {
- return new MediaTransformError( 'thumbnail_error', @$params['width'], @$params['height'],
+ $width = isset( $params['width'] ) ? $params['width'] : 0;
+ $height = isset( $params['height'] ) ? $params['height'] : 0;
+ return new MediaTransformError( 'thumbnail_error', $width, $height,
wfMsg( 'djvu_no_xml' ) );
}
@@ -100,7 +147,8 @@ class DjVuHandler extends ImageHandler {
# Use a subshell (brackets) to aggregate stderr from both pipeline commands
# before redirecting it to the overall stdout. This works in both Linux and Windows XP.
- $cmd = '(' . wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page} -size={$width}x{$height} " .
+ $cmd = '(' . wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page}" .
+ " -size={$params['physicalWidth']}x{$params['physicalHeight']} " .
wfEscapeShellArg( $srcPath );
if ( $wgDjvuPostProcessor ) {
$cmd .= " | {$wgDjvuPostProcessor}";
@@ -125,6 +173,8 @@ class DjVuHandler extends ImageHandler {
/**
* Cache an instance of DjVuImage in an Image object, return that instance
+ *
+ * @return DjVuImage
*/
function getDjVuImage( $image, $path ) {
if ( !$image ) {
@@ -139,6 +189,7 @@ class DjVuHandler extends ImageHandler {
/**
* Cache a document tree for the DjVu XML metadata
+ * @param $image File
*/
function getMetaTree( $image , $gettext = false ) {
if ( isset( $image->dejaMetaTree ) ) {
@@ -159,11 +210,11 @@ class DjVuHandler extends ImageHandler {
$image->djvuTextTree = false;
$tree = new SimpleXMLElement( $metadata );
if( $tree->getName() == 'mw-djvu' ) {
- foreach($tree->children() as $b){
+ foreach($tree->children() as $b){
if( $b->getName() == 'DjVuTxt' ) {
$image->djvuTextTree = $b;
}
- else if ( $b->getName() == 'DjVuXML' ) {
+ elseif ( $b->getName() == 'DjVuXML' ) {
$image->dejaMetaTree = $b;
}
}
diff --git a/includes/media/Exif.php b/includes/media/Exif.php
new file mode 100644
index 00000000..345a6f19
--- /dev/null
+++ b/includes/media/Exif.php
@@ -0,0 +1,836 @@
+<?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
+ *
+ * @ingroup Media
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason, 2009 Brent Garber
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification
+ * @file
+ */
+
+/**
+ * Class to extract and validate Exif data from jpeg (and possibly tiff) files.
+ * @ingroup Media
+ */
+class Exif {
+
+ const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer.
+ const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
+ const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer.
+ const LONG = 4; //!< A 32-bit (4-byte) unsigned integer.
+ const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
+ const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition
+ const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
+ const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
+ const IGNORE = -1; // A fake value for things we don't want or don't support.
+
+ //@{
+ /* @var array
+ * @private
+ */
+
+ /**
+ * Exif tags grouped by category, the tagname itself is the key and the type
+ * is the value, in the case of more than one possible value type they are
+ * separated by commas.
+ */
+ var $mExifTags;
+
+ /**
+ * The raw Exif data returned by exif_read_data()
+ */
+ var $mRawExifData;
+
+ /**
+ * A Filtered version of $mRawExifData that has been pruned of invalid
+ * tags and tags that contain content they shouldn't contain according
+ * to the Exif specification
+ */
+ var $mFilteredExifData;
+
+ /**
+ * Filtered and formatted Exif data, see FormatMetadata::getFormattedData()
+ */
+ var $mFormattedExifData;
+
+ //@}
+
+ //@{
+ /* @var string
+ * @private
+ */
+
+ /**
+ * The file being processed
+ */
+ var $file;
+
+ /**
+ * The basename of the file being processed
+ */
+ var $basename;
+
+ /**
+ * The private log to log to, e.g. 'exif'
+ */
+ var $log = false;
+
+ /**
+ * The byte order of the file. Needed because php's
+ * extension doesn't fully process some obscure props.
+ */
+ private $byteOrder;
+ //@}
+
+ /**
+ * Constructor
+ *
+ * @param $file String: filename.
+ * @todo FIXME: The following are broke:
+ * SubjectArea. Need to test the more obscure tags.
+ *
+ * DigitalZoomRatio = 0/0 is rejected. need to determine if that's valid.
+ * possibly should treat 0/0 = 0. need to read exif spec on that.
+ */
+ function __construct( $file, $byteOrder = '' ) {
+ /**
+ * Page numbers here refer to pages in the EXIF 2.2 standard
+ *
+ * Note, Exif::UNDEFINED is treated as a string, not as an array of bytes
+ * so don't put a count parameter for any UNDEFINED values.
+ *
+ * @link http://exif.org/Exif2-2.PDF The Exif 2.2 specification
+ */
+ $this->mExifTags = array(
+ # TIFF Rev. 6.0 Attribute Information (p22)
+ 'IFD0' => array(
+ # Tags relating to image structure
+ 'ImageWidth' => Exif::SHORT.','.Exif::LONG, # Image width
+ 'ImageLength' => Exif::SHORT.','.Exif::LONG, # Image height
+ 'BitsPerSample' => array( Exif::SHORT, 3 ), # Number of bits per component
+ # "When a primary image is JPEG compressed, this designation is not"
+ # "necessary and is omitted." (p23)
+ 'Compression' => Exif::SHORT, # Compression scheme #p23
+ 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23
+ 'Orientation' => Exif::SHORT, # Orientation of image #p24
+ 'SamplesPerPixel' => Exif::SHORT, # Number of components
+ 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
+ 'YCbCrSubSampling' => array( Exif::SHORT, 2), # Subsampling ratio of Y to C #p24
+ 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
+ 'XResolution' => Exif::RATIONAL, # Image resolution in width direction
+ 'YResolution' => Exif::RATIONAL, # Image resolution in height direction
+ 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
+
+ # Tags relating to recording offset
+ 'StripOffsets' => Exif::SHORT.','.Exif::LONG, # Image data location
+ 'RowsPerStrip' => Exif::SHORT.','.Exif::LONG, # Number of rows per strip
+ 'StripByteCounts' => Exif::SHORT.','.Exif::LONG, # Bytes per compressed strip
+ 'JPEGInterchangeFormat' => Exif::SHORT.','.Exif::LONG, # Offset to JPEG SOI
+ 'JPEGInterchangeFormatLength' => Exif::SHORT.','.Exif::LONG, # Bytes of JPEG data
+
+ # Tags relating to image data characteristics
+ 'TransferFunction' => Exif::IGNORE, # Transfer function
+ 'WhitePoint' => array( Exif::RATIONAL, 2), # White point chromaticity
+ 'PrimaryChromaticities' => array( Exif::RATIONAL, 6), # Chromaticities of primarities
+ 'YCbCrCoefficients' => array( Exif::RATIONAL, 3), # Color space transformation matrix coefficients #p27
+ 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6), # Pair of black and white reference values
+
+ # Other tags
+ 'DateTime' => Exif::ASCII, # File change date and time
+ 'ImageDescription' => Exif::ASCII, # Image title
+ 'Make' => Exif::ASCII, # Image input equipment manufacturer
+ 'Model' => Exif::ASCII, # Image input equipment model
+ 'Software' => Exif::ASCII, # Software used
+ 'Artist' => Exif::ASCII, # Person who created the image
+ 'Copyright' => Exif::ASCII, # Copyright holder
+ ),
+
+ # Exif IFD Attribute Information (p30-31)
+ 'EXIF' => array(
+ # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
+ # to the EXIF 2.1 AND 2.2 standards
+ 'ExifVersion' => Exif::UNDEFINED, # Exif version
+ 'FlashPixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
+
+ # Tags relating to Image Data Characteristics
+ 'ColorSpace' => Exif::SHORT, # Color space information #p32
+
+ # Tags relating to image configuration
+ 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
+ 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
+ 'PixelYDimension' => Exif::SHORT.','.Exif::LONG, # Valid image width
+ 'PixelXDimension' => Exif::SHORT.','.Exif::LONG, # Valid image height
+
+ # Tags relating to related user information
+ 'MakerNote' => Exif::IGNORE, # Manufacturer notes
+ 'UserComment' => Exif::UNDEFINED, # User comments #p34
+
+ # Tags relating to related file information
+ 'RelatedSoundFile' => Exif::ASCII, # Related audio file
+
+ # Tags relating to date and time
+ 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36
+ 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation
+ 'SubSecTime' => Exif::ASCII, # DateTime subseconds
+ 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds
+ 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds
+
+ # Tags relating to picture-taking conditions (p31)
+ 'ExposureTime' => Exif::RATIONAL, # Exposure time
+ 'FNumber' => Exif::RATIONAL, # F Number
+ 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38
+ 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity
+ 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating
+ 'OECF' => Exif::IGNORE,
+ # Optoelectronic conversion factor. Note: We don't have support for this atm.
+ 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed
+ 'ApertureValue' => Exif::RATIONAL, # Aperture
+ 'BrightnessValue' => Exif::SRATIONAL, # Brightness
+ 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias
+ 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture
+ 'SubjectDistance' => Exif::RATIONAL, # Subject distance
+ 'MeteringMode' => Exif::SHORT, # Metering mode #p40
+ 'LightSource' => Exif::SHORT, # Light source #p40-41
+ 'Flash' => Exif::SHORT, # Flash #p41-42
+ 'FocalLength' => Exif::RATIONAL, # Lens focal length
+ 'SubjectArea' => array( Exif::SHORT, 4 ), # Subject area
+ 'FlashEnergy' => Exif::RATIONAL, # Flash energy
+ 'SpatialFrequencyResponse' => Exif::IGNORE, # Spatial frequency response. Not supported atm.
+ 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
+ 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
+ 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
+ 'SubjectLocation' => array( Exif::SHORT, 2), # Subject location
+ 'ExposureIndex' => Exif::RATIONAL, # Exposure index
+ 'SensingMethod' => Exif::SHORT, # Sensing method #p46
+ 'FileSource' => Exif::UNDEFINED, # File source #p47
+ 'SceneType' => Exif::UNDEFINED, # Scene type #p47
+ 'CFAPattern' => Exif::IGNORE, # CFA pattern. not supported atm.
+ 'CustomRendered' => Exif::SHORT, # Custom image processing #p48
+ 'ExposureMode' => Exif::SHORT, # Exposure mode #p48
+ 'WhiteBalance' => Exif::SHORT, # White Balance #p49
+ 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
+ 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
+ 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
+ 'GainControl' => Exif::SHORT, # Scene control #p49-50
+ 'Contrast' => Exif::SHORT, # Contrast #p50
+ 'Saturation' => Exif::SHORT, # Saturation #p50
+ 'Sharpness' => Exif::SHORT, # Sharpness #p50
+ 'DeviceSettingDescription' => Exif::IGNORE,
+ # Device settings description. This could maybe be supported. Need to find an
+ # example file that uses this to see if it has stuff of interest in it.
+ 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51
+
+ 'ImageUniqueID' => Exif::ASCII, # Unique image ID
+ ),
+
+ # GPS Attribute Information (p52)
+ 'GPS' => array(
+ 'GPSVersion' => Exif::UNDEFINED,
+ # Should be an array of 4 Exif::BYTE's. However php treats it as an undefined
+ # Note exif standard calls this GPSVersionID, but php doesn't like the id suffix
+ 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
+ 'GPSLatitude' => array( Exif::RATIONAL, 3 ), # Latitude
+ 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
+ 'GPSLongitude' => array( Exif::RATIONAL, 3), # Longitude
+ 'GPSAltitudeRef' => Exif::UNDEFINED,
+ # Altitude reference. Note, the exif standard says this should be an EXIF::Byte,
+ # but php seems to disagree.
+ 'GPSAltitude' => Exif::RATIONAL, # Altitude
+ 'GPSTimeStamp' => array( Exif::RATIONAL, 3), # GPS time (atomic clock)
+ 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
+ 'GPSStatus' => Exif::ASCII, # Receiver status #p54
+ 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
+ 'GPSDOP' => Exif::RATIONAL, # Measurement precision
+ 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55
+ 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver
+ 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55
+ 'GPSTrack' => Exif::RATIONAL, # Direction of movement
+ 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56
+ 'GPSImgDirection' => Exif::RATIONAL, # Direction of image
+ 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used
+ 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56
+ 'GPSDestLatitude' => array( Exif::RATIONAL, 3 ), # Latitude destination
+ 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57
+ 'GPSDestLongitude' => array( Exif::RATIONAL, 3 ), # Longitude of destination
+ 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57
+ 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination
+ 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58
+ 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination
+ 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method
+ 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area
+ 'GPSDateStamp' => Exif::ASCII, # GPS date
+ 'GPSDifferential' => Exif::SHORT, # GPS differential correction
+ ),
+ );
+
+ $this->file = $file;
+ $this->basename = wfBaseName( $this->file );
+ if ( $byteOrder === 'BE' || $byteOrder === 'LE' ) {
+ $this->byteOrder = $byteOrder;
+ } else {
+ // Only give a warning for b/c, since originally we didn't
+ // require this. The number of things affected by this is
+ // rather small.
+ wfWarn( 'Exif class did not have byte order specified. '
+ . 'Some properties may be decoded incorrectly.' );
+ $this->byteOrder = 'BE'; // BE seems about twice as popular as LE in jpg's.
+ }
+
+ $this->debugFile( $this->basename, __FUNCTION__, true );
+ if( function_exists( 'exif_read_data' ) ) {
+ wfSuppressWarnings();
+ $data = exif_read_data( $this->file, 0, true );
+ wfRestoreWarnings();
+ } else {
+ throw new MWException( "Internal error: exif_read_data not present. \$wgShowEXIF may be incorrectly set or not checked by an extension." );
+ }
+ /**
+ * exif_read_data() will return false on invalid input, such as
+ * when somebody uploads a file called something.jpeg
+ * containing random gibberish.
+ */
+ $this->mRawExifData = $data ? $data : array();
+ $this->makeFilteredData();
+ $this->collapseData();
+ $this->debugFile( __FUNCTION__, false );
+ }
+
+ /**
+ * Make $this->mFilteredExifData
+ */
+ function makeFilteredData() {
+ $this->mFilteredExifData = Array();
+
+ foreach ( array_keys( $this->mRawExifData ) as $section ) {
+ if ( !in_array( $section, array_keys( $this->mExifTags ) ) ) {
+ $this->debug( $section , __FUNCTION__, "'$section' is not a valid Exif section" );
+ continue;
+ }
+
+ foreach ( array_keys( $this->mRawExifData[$section] ) as $tag ) {
+ if ( !in_array( $tag, array_keys( $this->mExifTags[$section] ) ) ) {
+ $this->debug( $tag, __FUNCTION__, "'$tag' is not a valid tag in '$section'" );
+ continue;
+ }
+
+ $this->mFilteredExifData[$tag] = $this->mRawExifData[$section][$tag];
+ // This is ok, as the tags in the different sections do not conflict.
+ // except in computed and thumbnail section, which we don't use.
+
+ $value = $this->mRawExifData[$section][$tag];
+ if ( !$this->validate( $section, $tag, $value ) ) {
+ $this->debug( $value, __FUNCTION__, "'$tag' contained invalid data" );
+ unset( $this->mFilteredExifData[$tag] );
+ }
+ }
+ }
+ }
+
+ /**
+ * Collapse some fields together.
+ * This converts some fields from exif form, to a more friendly form.
+ * For example GPS latitude to a single number.
+ *
+ * The rationale behind this is that we're storing data, not presenting to the user
+ * For example a longitude is a single number describing how far away you are from
+ * the prime meridian. Well it might be nice to split it up into minutes and seconds
+ * for the user, it doesn't really make sense to split a single number into 4 parts
+ * for storage. (degrees, minutes, second, direction vs single floating point number).
+ *
+ * Other things this might do (not really sure if they make sense or not):
+ * Dates -> mediawiki date format.
+ * convert values that can be in different units to be in one standardized unit.
+ *
+ * As an alternative approach, some of this could be done in the validate phase
+ * if we make up our own types like Exif::DATE.
+ */
+ function collapseData( ) {
+
+ $this->exifGPStoNumber( 'GPSLatitude' );
+ $this->exifGPStoNumber( 'GPSDestLatitude' );
+ $this->exifGPStoNumber( 'GPSLongitude' );
+ $this->exifGPStoNumber( 'GPSDestLongitude' );
+
+ if ( isset( $this->mFilteredExifData['GPSAltitude'] ) && isset( $this->mFilteredExifData['GPSAltitudeRef'] ) ) {
+ if ( $this->mFilteredExifData['GPSAltitudeRef'] === "\1" ) {
+ $this->mFilteredExifData['GPSAltitude'] *= - 1;
+ }
+ unset( $this->mFilteredExifData['GPSAltitudeRef'] );
+ }
+
+ $this->exifPropToOrd( 'FileSource' );
+ $this->exifPropToOrd( 'SceneType' );
+
+ $this->charCodeString( 'UserComment' );
+ $this->charCodeString( 'GPSProcessingMethod');
+ $this->charCodeString( 'GPSAreaInformation' );
+
+ //ComponentsConfiguration should really be an array instead of a string...
+ //This turns a string of binary numbers into an array of numbers.
+
+ if ( isset ( $this->mFilteredExifData['ComponentsConfiguration'] ) ) {
+ $val = $this->mFilteredExifData['ComponentsConfiguration'];
+ $ccVals = array();
+ for ($i = 0; $i < strlen($val); $i++) {
+ $ccVals[$i] = ord( substr($val, $i, 1) );
+ }
+ $ccVals['_type'] = 'ol'; //this is for formatting later.
+ $this->mFilteredExifData['ComponentsConfiguration'] = $ccVals;
+ }
+
+ //GPSVersion(ID) is treated as the wrong type by php exif support.
+ //Go through each byte turning it into a version string.
+ //For example: "\x02\x02\x00\x00" -> "2.2.0.0"
+
+ //Also change exif tag name from GPSVersion (what php exif thinks it is)
+ //to GPSVersionID (what the exif standard thinks it is).
+
+ if ( isset ( $this->mFilteredExifData['GPSVersion'] ) ) {
+ $val = $this->mFilteredExifData['GPSVersion'];
+ $newVal = '';
+ for ($i = 0; $i < strlen($val); $i++) {
+ if ( $i !== 0 ) {
+ $newVal .= '.';
+ }
+ $newVal .= ord( substr($val, $i, 1) );
+ }
+ if ( $this->byteOrder === 'LE' ) {
+ // Need to reverse the string
+ $newVal2 = '';
+ for ( $i = strlen( $newVal ) - 1; $i >= 0; $i-- ) {
+ $newVal2 .= substr( $newVal, $i, 1 );
+ }
+ $this->mFilteredExifData['GPSVersionID'] = $newVal2;
+ } else {
+ $this->mFilteredExifData['GPSVersionID'] = $newVal;
+ }
+ unset( $this->mFilteredExifData['GPSVersion'] );
+ }
+
+ }
+ /**
+ * Do userComment tags and similar. See pg. 34 of exif standard.
+ * basically first 8 bytes is charset, rest is value.
+ * This has not been tested on any shift-JIS strings.
+ * @param $prop String prop name.
+ */
+ private function charCodeString ( $prop ) {
+ if ( isset( $this->mFilteredExifData[$prop] ) ) {
+
+ if ( strlen($this->mFilteredExifData[$prop]) <= 8 ) {
+ //invalid. Must be at least 9 bytes long.
+
+ $this->debug( $this->mFilteredExifData[$prop] , __FUNCTION__, false );
+ unset($this->mFilteredExifData[$prop]);
+ return;
+ }
+ $charCode = substr( $this->mFilteredExifData[$prop], 0, 8);
+ $val = substr( $this->mFilteredExifData[$prop], 8);
+
+
+ switch ($charCode) {
+ case "\x4A\x49\x53\x00\x00\x00\x00\x00":
+ //JIS
+ $charset = "Shift-JIS";
+ break;
+ case "UNICODE\x00":
+ $charset = "UTF-16" . $this->byteOrder;
+ break;
+ default: //ascii or undefined.
+ $charset = "";
+ break;
+ }
+ // This could possibly check to see if iconv is really installed
+ // or if we're using the compatibility wrapper in globalFunctions.php
+ if ($charset) {
+ wfSuppressWarnings();
+ $val = iconv($charset, 'UTF-8//IGNORE', $val);
+ wfRestoreWarnings();
+ } else {
+ // if valid utf-8, assume that, otherwise assume windows-1252
+ $valCopy = $val;
+ UtfNormal::quickIsNFCVerify( $valCopy ); //validates $valCopy.
+ if ( $valCopy !== $val ) {
+ wfSuppressWarnings();
+ $val = iconv('Windows-1252', 'UTF-8//IGNORE', $val);
+ wfRestoreWarnings();
+ }
+ }
+
+ //trim and check to make sure not only whitespace.
+ $val = trim($val);
+ if ( strlen( $val ) === 0 ) {
+ //only whitespace.
+ $this->debug( $this->mFilteredExifData[$prop] , __FUNCTION__, "$prop: Is only whitespace" );
+ unset($this->mFilteredExifData[$prop]);
+ return;
+ }
+
+ //all's good.
+ $this->mFilteredExifData[$prop] = $val;
+ }
+ }
+ /**
+ * Convert an Exif::UNDEFINED from a raw binary string
+ * to its value. This is sometimes needed depending on
+ * the type of UNDEFINED field
+ * @param $prop String name of property
+ */
+ private function exifPropToOrd ( $prop ) {
+ if ( isset( $this->mFilteredExifData[$prop] ) ) {
+ $this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
+ }
+ }
+ /**
+ * Convert gps in exif form to a single floating point number
+ * for example 10 degress 20`40`` S -> -10.34444
+ * @param String $prop a gps coordinate exif tag name (like GPSLongitude)
+ */
+ private function exifGPStoNumber ( $prop ) {
+ $loc =& $this->mFilteredExifData[$prop];
+ $dir =& $this->mFilteredExifData[$prop . 'Ref'];
+ $res = false;
+
+ if ( isset( $loc ) && isset( $dir ) && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' ) ) {
+ list( $num, $denom ) = explode( '/', $loc[0] );
+ $res = $num / $denom;
+ list( $num, $denom ) = explode( '/', $loc[1] );
+ $res += ( $num / $denom ) * ( 1 / 60 );
+ list( $num, $denom ) = explode( '/', $loc[2] );
+ $res += ( $num / $denom ) * ( 1 / 3600 );
+
+ if ( $dir === 'S' || $dir === 'W' ) {
+ $res *= - 1; // make negative
+ }
+ }
+
+ // update the exif records.
+
+ if ( $res !== false ) { // using !== as $res could potentially be 0
+ $this->mFilteredExifData[$prop] = $res;
+ unset( $this->mFilteredExifData[$prop . 'Ref'] );
+ } else { // if invalid
+ unset( $this->mFilteredExifData[$prop] );
+ unset( $this->mFilteredExifData[$prop . 'Ref'] );
+ }
+ }
+
+ /**
+ * Use FormatMetadata to create formatted values for display to user
+ * (is this ever used?)
+ *
+ * @deprecated since 1.18
+ */
+ function makeFormattedData( ) {
+ wfDeprecated( __METHOD__ );
+ $this->mFormattedExifData = FormatMetadata::getFormattedData(
+ $this->mFilteredExifData );
+ }
+ /**#@-*/
+
+ /**#@+
+ * @return array
+ */
+ /**
+ * Get $this->mRawExifData
+ */
+ function getData() {
+ return $this->mRawExifData;
+ }
+
+ /**
+ * Get $this->mFilteredExifData
+ */
+ function getFilteredData() {
+ return $this->mFilteredExifData;
+ }
+
+ /**
+ * Get $this->mFormattedExifData
+ *
+ * This returns the data for display to user.
+ * Its unclear if this is ever used.
+ *
+ * @deprecated since 1.18
+ */
+ function getFormattedData() {
+ wfDeprecated( __METHOD__ );
+ if (!$this->mFormattedExifData) {
+ $this->makeFormattedData();
+ }
+ return $this->mFormattedExifData;
+ }
+ /**#@-*/
+
+ /**
+ * The version of the output format
+ *
+ * Before the actual metadata information is saved in the database we
+ * strip some of it since we don't want to save things like thumbnails
+ * which usually accompany Exif data. This value gets saved in the
+ * database along with the actual Exif data, and if the version in the
+ * database doesn't equal the value returned by this function the Exif
+ * data is regenerated.
+ *
+ * @return int
+ */
+ public static function version() {
+ return 2; // We don't need no bloddy constants!
+ }
+
+ /**#@+
+ * Validates if a tag value is of the type it should be according to the Exif spec
+ *
+ * @private
+ *
+ * @param $in Mixed: the input value to check
+ * @return bool
+ */
+ private function isByte( $in ) {
+ if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 255 ) {
+ $this->debug( $in, __FUNCTION__, true );
+ return true;
+ } else {
+ $this->debug( $in, __FUNCTION__, false );
+ return false;
+ }
+ }
+
+ /**
+ * @param $in
+ * @return bool
+ */
+ private function isASCII( $in ) {
+ if ( is_array( $in ) ) {
+ return false;
+ }
+
+ if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
+ $this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
+ return false;
+ }
+
+ if ( preg_match( '/^\s*$/', $in ) ) {
+ $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @param $in
+ * @return bool
+ */
+ private function isShort( $in ) {
+ if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 65536 ) {
+ $this->debug( $in, __FUNCTION__, true );
+ return true;
+ } else {
+ $this->debug( $in, __FUNCTION__, false );
+ return false;
+ }
+ }
+
+ /**
+ * @param $in
+ * @return bool
+ */
+ private function isLong( $in ) {
+ if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 4294967296 ) {
+ $this->debug( $in, __FUNCTION__, true );
+ return true;
+ } else {
+ $this->debug( $in, __FUNCTION__, false );
+ return false;
+ }
+ }
+
+ /**
+ * @param $in
+ * @return bool
+ */
+ private function isRational( $in ) {
+ $m = array();
+ if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
+ return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
+ } else {
+ $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
+ return false;
+ }
+ }
+
+ /**
+ * @param $in
+ * @return bool
+ */
+ private function isUndefined( $in ) {
+ $this->debug( $in, __FUNCTION__, true );
+ return true;
+ }
+
+ /**
+ * @param $in
+ * @return bool
+ */
+ private function isSlong( $in ) {
+ if ( $this->isLong( abs( $in ) ) ) {
+ $this->debug( $in, __FUNCTION__, true );
+ return true;
+ } else {
+ $this->debug( $in, __FUNCTION__, false );
+ return false;
+ }
+ }
+
+ /**
+ * @param $in
+ * @return bool
+ */
+ private function isSrational( $in ) {
+ $m = array();
+ if ( !is_array( $in ) && preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
+ return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
+ } else {
+ $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
+ return false;
+ }
+ }
+ /**#@-*/
+
+ /**
+ * Validates if a tag has a legal value according to the Exif spec
+ *
+ * @private
+ * @param $section String: section where tag is located.
+ * @param $tag String: the tag to check.
+ * @param $val Mixed: the value of the tag.
+ * @param $recursive Boolean: true if called recursively for array types.
+ * @return bool
+ */
+ private function validate( $section, $tag, $val, $recursive = false ) {
+ $debug = "tag is '$tag'";
+ $etype = $this->mExifTags[$section][$tag];
+ $ecount = 1;
+ if( is_array( $etype ) ) {
+ list( $etype, $ecount ) = $etype;
+ if ( $recursive )
+ $ecount = 1; // checking individual elements
+ }
+ $count = count( $val );
+ if( $ecount != $count ) {
+ $this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
+ return false;
+ }
+ if( $count > 1 ) {
+ foreach( $val as $v ) {
+ if( !$this->validate( $section, $tag, $v, true ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ // Does not work if not typecast
+ switch( (string)$etype ) {
+ case (string)Exif::BYTE:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return $this->isByte( $val );
+ case (string)Exif::ASCII:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return $this->isASCII( $val );
+ case (string)Exif::SHORT:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return $this->isShort( $val );
+ case (string)Exif::LONG:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return $this->isLong( $val );
+ case (string)Exif::RATIONAL:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return $this->isRational( $val );
+ case (string)Exif::UNDEFINED:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return $this->isUndefined( $val );
+ case (string)Exif::SLONG:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return $this->isSlong( $val );
+ case (string)Exif::SRATIONAL:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return $this->isSrational( $val );
+ case (string)Exif::SHORT.','.Exif::LONG:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return $this->isShort( $val ) || $this->isLong( $val );
+ case (string)Exif::IGNORE:
+ $this->debug( $val, __FUNCTION__, $debug );
+ return false;
+ default:
+ $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
+ return false;
+ }
+ }
+
+ /**
+ * Convenience function for debugging output
+ *
+ * @private
+ *
+ * @param $in Mixed:
+ * @param $fname String:
+ * @param $action Mixed: , default NULL.
+ */
+ private function debug( $in, $fname, $action = null ) {
+ if ( !$this->log ) {
+ return;
+ }
+ $type = gettype( $in );
+ $class = ucfirst( __CLASS__ );
+ if ( $type === 'array' ) {
+ $in = print_r( $in, true );
+ }
+
+ if ( $action === true ) {
+ wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n");
+ } elseif ( $action === false ) {
+ wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n");
+ } elseif ( $action === null ) {
+ wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n");
+ } else {
+ wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n");
+ }
+ }
+
+ /**
+ * Convenience function for debugging output
+ *
+ * @private
+ *
+ * @param $fname String: the name of the function calling this function
+ * @param $io Boolean: Specify whether we're beginning or ending
+ */
+ private function debugFile( $fname, $io ) {
+ if ( !$this->log ) {
+ return;
+ }
+ $class = ucfirst( __CLASS__ );
+ if ( $io ) {
+ wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
+ } else {
+ wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'\n" );
+ }
+ }
+}
+
diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php
new file mode 100644
index 00000000..05ce161b
--- /dev/null
+++ b/includes/media/ExifBitmap.php
@@ -0,0 +1,210 @@
+<?php
+/**
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Stuff specific to JPEG and (built-in) TIFF handler.
+ * All metadata related, since both JPEG and TIFF support Exif.
+ *
+ * @ingroup Media
+ */
+class ExifBitmapHandler extends BitmapHandler {
+
+ const BROKEN_FILE = '-1'; // error extracting metadata
+ const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata.
+
+ function convertMetadataVersion( $metadata, $version = 1 ) {
+ // basically flattens arrays.
+ $version = explode(';', $version, 2);
+ $version = intval($version[0]);
+ if ( $version < 1 || $version >= 2 ) {
+ return $metadata;
+ }
+
+ $avoidHtml = true;
+
+ if ( !is_array( $metadata ) ) {
+ $metadata = unserialize( $metadata );
+ }
+ if ( !isset( $metadata['MEDIAWIKI_EXIF_VERSION'] ) || $metadata['MEDIAWIKI_EXIF_VERSION'] != 2 ) {
+ return $metadata;
+ }
+
+ // Treat Software as a special case because in can contain
+ // an array of (SoftwareName, Version).
+ if (isset( $metadata['Software'] )
+ && is_array( $metadata['Software'] )
+ && is_array( $metadata['Software'][0])
+ && isset( $metadata['Software'][0][0] )
+ && isset( $metadata['Software'][0][1])
+ ) {
+ $metadata['Software'] = $metadata['Software'][0][0] . ' (Version '
+ . $metadata['Software'][0][1] . ')';
+ }
+
+ // ContactInfo also has to be dealt with specially
+ if ( isset( $metadata['Contact'] ) ) {
+ $metadata['Contact'] =
+ FormatMetadata::collapseContactInfo(
+ $metadata['Contact'] );
+ }
+
+ foreach ( $metadata as &$val ) {
+ if ( is_array( $val ) ) {
+ $val = FormatMetadata::flattenArray( $val, 'ul', $avoidHtml );
+ }
+ }
+ $metadata['MEDIAWIKI_EXIF_VERSION'] = 1;
+ return $metadata;
+ }
+
+ function isMetadataValid( $image, $metadata ) {
+ global $wgShowEXIF;
+ if ( !$wgShowEXIF ) {
+ # Metadata disabled and so an empty field is expected
+ return self::METADATA_GOOD;
+ }
+ if ( $metadata === self::OLD_BROKEN_FILE ) {
+ # Old special value indicating that there is no EXIF data in the file.
+ # or that there was an error well extracting the metadata.
+ wfDebug( __METHOD__ . ": back-compat version\n");
+ return self::METADATA_COMPATIBLE;
+ }
+ if ( $metadata === self::BROKEN_FILE ) {
+ return self::METADATA_GOOD;
+ }
+ wfSuppressWarnings();
+ $exif = unserialize( $metadata );
+ wfRestoreWarnings();
+ if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
+ $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
+ {
+ if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) &&
+ $exif['MEDIAWIKI_EXIF_VERSION'] == 1 )
+ {
+ //back-compatible but old
+ wfDebug( __METHOD__.": back-compat version\n" );
+ return self::METADATA_COMPATIBLE;
+ }
+ # Wrong (non-compatible) version
+ wfDebug( __METHOD__.": wrong version\n" );
+ return self::METADATA_BAD;
+ }
+ return self::METADATA_GOOD;
+ }
+
+ /**
+ * @param $image File
+ * @return array|bool
+ */
+ function formatMetadata( $image ) {
+ $metadata = $image->getMetadata();
+ if ( $metadata === self::OLD_BROKEN_FILE ||
+ $metadata === self::BROKEN_FILE ||
+ $this->isMetadataValid( $image, $metadata ) === self::METADATA_BAD )
+ {
+ // So we don't try and display metadata from PagedTiffHandler
+ // for example when using InstantCommons.
+ return false;
+ }
+
+ $exif = unserialize( $metadata );
+ if ( !$exif ) {
+ return false;
+ }
+ unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
+ if ( count( $exif ) == 0 ) {
+ return false;
+ }
+ return $this->formatMetadataHelper( $exif );
+ }
+
+ function getMetadataType( $image ) {
+ return 'exif';
+ }
+
+ /**
+ * Wrapper for base classes ImageHandler::getImageSize() that checks for
+ * rotation reported from metadata and swaps the sizes to match.
+ *
+ * @param File $image
+ * @param string $path
+ * @return array
+ */
+ function getImageSize( $image, $path ) {
+ global $wgEnableAutoRotation;
+ $gis = parent::getImageSize( $image, $path );
+
+ // Don't just call $image->getMetadata(); File::getPropsFromPath() calls us with a bogus object.
+ // This may mean we read EXIF data twice on initial upload.
+ if ( $wgEnableAutoRotation ) {
+ $meta = $this->getMetadata( $image, $path );
+ $rotation = $this->getRotationForExif( $meta );
+ } else {
+ $rotation = 0;
+ }
+
+ if ($rotation == 90 || $rotation == 270) {
+ $width = $gis[0];
+ $gis[0] = $gis[1];
+ $gis[1] = $width;
+ }
+ return $gis;
+ }
+
+ /**
+ * On supporting image formats, try to read out the low-level orientation
+ * of the file and return the angle that the file needs to be rotated to
+ * be viewed.
+ *
+ * This information is only useful when manipulating the original file;
+ * the width and height we normally work with is logical, and will match
+ * any produced output views.
+ *
+ * @param $file File
+ * @return int 0, 90, 180 or 270
+ */
+ public function getRotation( $file ) {
+ global $wgEnableAutoRotation;
+ if ( !$wgEnableAutoRotation ) {
+ return 0;
+ }
+
+ $data = $file->getMetadata();
+ return $this->getRotationForExif( $data );
+ }
+
+ /**
+ * Given a chunk of serialized Exif metadata, return the orientation as
+ * degrees of rotation.
+ *
+ * @param string $data
+ * @return int 0, 90, 180 or 270
+ * @fixme orientation can include flipping as well; see if this is an issue!
+ */
+ protected function getRotationForExif( $data ) {
+ if ( !$data ) {
+ return 0;
+ }
+ wfSuppressWarnings();
+ $data = unserialize( $data );
+ wfRestoreWarnings();
+ if ( isset( $data['Orientation'] ) ) {
+ # See http://sylvana.net/jpegcrop/exif_orientation.html
+ switch ( $data['Orientation'] ) {
+ case 8:
+ return 90;
+ case 3:
+ return 180;
+ case 6:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+ return 0;
+ }
+}
+
diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php
new file mode 100644
index 00000000..47fc1adc
--- /dev/null
+++ b/includes/media/FormatMetadata.php
@@ -0,0 +1,1354 @@
+<?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
+ *
+ * @ingroup Media
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason, 2009 Brent Garber, 2010 Brian Wolff
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
+ * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification
+ * @file
+ */
+
+
+/**
+ * Format Image metadata values into a human readable form.
+ *
+ * Note lots of these messages use the prefix 'exif' even though
+ * they may not be exif properties. For example 'exif-ImageDescription'
+ * can be the Exif ImageDescription, or it could be the iptc-iim caption
+ * property, or it could be the xmp dc:description property. This
+ * is because these messages should be independent of how the data is
+ * stored, sine the user doesn't care if the description is stored in xmp,
+ * exif, etc only that its a description. (Additionally many of these properties
+ * are merged together following the MWG standard, such that for example,
+ * exif properties override XMP properties that mean the same thing if
+ * there is a conflict).
+ *
+ * It should perhaps use a prefix like 'metadata' instead, but there
+ * is already a large number of messages using the 'exif' prefix.
+ *
+ * @ingroup Media
+ */
+class FormatMetadata {
+
+ /**
+ * Numbers given by Exif user agents are often magical, that is they
+ * should be replaced by a detailed explanation depending on their
+ * value which most of the time are plain integers. This function
+ * formats Exif (and other metadata) values into human readable form.
+ *
+ * @param $tags Array: the Exif data to format ( as returned by
+ * Exif::getFilteredData() or BitmapMetadataHandler )
+ * @return array
+ */
+ public static function getFormattedData( $tags ) {
+ global $wgLang;
+
+ $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
+ unset( $tags['ResolutionUnit'] );
+
+ foreach ( $tags as $tag => &$vals ) {
+
+ // This seems ugly to wrap non-array's in an array just to unwrap again,
+ // especially when most of the time it is not an array
+ if ( !is_array( $tags[$tag] ) ) {
+ $vals = Array( $vals );
+ }
+
+ // _type is a special value to say what array type
+ if ( isset( $tags[$tag]['_type'] ) ) {
+ $type = $tags[$tag]['_type'];
+ unset( $vals['_type'] );
+ } else {
+ $type = 'ul'; // default unordered list.
+ }
+
+ //This is done differently as the tag is an array.
+ if ($tag == 'GPSTimeStamp' && count($vals) === 3) {
+ //hour min sec array
+
+ $h = explode('/', $vals[0]);
+ $m = explode('/', $vals[1]);
+ $s = explode('/', $vals[2]);
+
+ // this should already be validated
+ // when loaded from file, but it could
+ // come from a foreign repo, so be
+ // paranoid.
+ if ( !isset($h[1])
+ || !isset($m[1])
+ || !isset($s[1])
+ || $h[1] == 0
+ || $m[1] == 0
+ || $s[1] == 0
+ ) {
+ continue;
+ }
+ $tags[$tag] = intval( $h[0] / $h[1] )
+ . ':' . str_pad( intval( $m[0] / $m[1] ), 2, '0', STR_PAD_LEFT )
+ . ':' . str_pad( intval( $s[0] / $s[1] ), 2, '0', STR_PAD_LEFT );
+
+ $time = wfTimestamp( TS_MW, '1971:01:01 ' . $tags[$tag] );
+ // the 1971:01:01 is just a placeholder, and not shown to user.
+ if ( $time && intval( $time ) > 0 ) {
+ $tags[$tag] = $wgLang->time( $time );
+ }
+ continue;
+ }
+
+ // The contact info is a multi-valued field
+ // instead of the other props which are single
+ // valued (mostly) so handle as a special case.
+ if ( $tag === 'Contact' ) {
+ $vals = self::collapseContactInfo( $vals );
+ continue;
+ }
+
+ foreach ( $vals as &$val ) {
+
+ switch( $tag ) {
+ case 'Compression':
+ switch( $val ) {
+ case 1: case 2: case 3: case 4:
+ case 5: case 6: case 7: case 8:
+ case 32773: case 32946: case 34712:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'PhotometricInterpretation':
+ switch( $val ) {
+ case 2: case 6:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'Orientation':
+ switch( $val ) {
+ case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'PlanarConfiguration':
+ switch( $val ) {
+ case 1: case 2:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ // TODO: YCbCrSubSampling
+ case 'YCbCrPositioning':
+ switch ( $val ) {
+ case 1:
+ case 2:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'XResolution':
+ case 'YResolution':
+ switch( $resolutionunit ) {
+ case 2:
+ $val = self::msg( 'XYResolution', 'i', self::formatNum( $val ) );
+ break;
+ case 3:
+ $val = self::msg( 'XYResolution', 'c', self::formatNum( $val ) );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ // TODO: YCbCrCoefficients #p27 (see annex E)
+ case 'ExifVersion': case 'FlashpixVersion':
+ $val = "$val" / 100;
+ break;
+
+ case 'ColorSpace':
+ switch( $val ) {
+ case 1: case 65535:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'ComponentsConfiguration':
+ switch( $val ) {
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'DateTime':
+ case 'DateTimeOriginal':
+ case 'DateTimeDigitized':
+ case 'DateTimeReleased':
+ case 'DateTimeExpires':
+ case 'GPSDateStamp':
+ case 'dc-date':
+ case 'DateTimeMetadata':
+ if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) {
+ $val = wfMsg( 'exif-unknowndate' );
+ } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D', $val ) ) {
+ $time = wfTimestamp( TS_MW, $val );
+ if ( $time && intval( $time ) > 0 ) {
+ $val = $wgLang->timeanddate( $time );
+ }
+ } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
+ // If only the date but not the time is filled in.
+ $time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
+ . substr( $val, 5, 2 )
+ . substr( $val, 8, 2 )
+ . '000000' );
+ if ( $time && intval( $time ) > 0 ) {
+ $val = $wgLang->date( $time );
+ }
+ }
+ // else it will just output $val without formatting it.
+ break;
+
+ case 'ExposureProgram':
+ switch( $val ) {
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'SubjectDistance':
+ $val = self::msg( $tag, '', self::formatNum( $val ) );
+ break;
+
+ case 'MeteringMode':
+ switch( $val ) {
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'LightSource':
+ switch( $val ) {
+ case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
+ case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
+ case 21: case 22: case 23: case 24: case 255:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'Flash':
+ $flashDecode = array(
+ 'fired' => $val & bindec( '00000001' ),
+ 'return' => ( $val & bindec( '00000110' ) ) >> 1,
+ 'mode' => ( $val & bindec( '00011000' ) ) >> 3,
+ 'function' => ( $val & bindec( '00100000' ) ) >> 5,
+ 'redeye' => ( $val & bindec( '01000000' ) ) >> 6,
+// 'reserved' => ($val & bindec( '10000000' )) >> 7,
+ );
+
+ # We do not need to handle unknown values since all are used.
+ foreach ( $flashDecode as $subTag => $subValue ) {
+ # We do not need any message for zeroed values.
+ if ( $subTag != 'fired' && $subValue == 0 ) {
+ continue;
+ }
+ $fullTag = $tag . '-' . $subTag ;
+ $flashMsgs[] = self::msg( $fullTag, $subValue );
+ }
+ $val = $wgLang->commaList( $flashMsgs );
+ break;
+
+ case 'FocalPlaneResolutionUnit':
+ switch( $val ) {
+ case 2:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'SensingMethod':
+ switch( $val ) {
+ case 1: case 2: case 3: case 4: case 5: case 7: case 8:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'FileSource':
+ switch( $val ) {
+ case 3:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'SceneType':
+ switch( $val ) {
+ case 1:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'CustomRendered':
+ switch( $val ) {
+ case 0: case 1:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'ExposureMode':
+ switch( $val ) {
+ case 0: case 1: case 2:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'WhiteBalance':
+ switch( $val ) {
+ case 0: case 1:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'SceneCaptureType':
+ switch( $val ) {
+ case 0: case 1: case 2: case 3:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'GainControl':
+ switch( $val ) {
+ case 0: case 1: case 2: case 3: case 4:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'Contrast':
+ switch( $val ) {
+ case 0: case 1: case 2:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'Saturation':
+ switch( $val ) {
+ case 0: case 1: case 2:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'Sharpness':
+ switch( $val ) {
+ case 0: case 1: case 2:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'SubjectDistanceRange':
+ switch( $val ) {
+ case 0: case 1: case 2: case 3:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ //The GPS...Ref values are kept for compatibility, probably won't be reached.
+ case 'GPSLatitudeRef':
+ case 'GPSDestLatitudeRef':
+ switch( $val ) {
+ case 'N': case 'S':
+ $val = self::msg( 'GPSLatitude', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'GPSLongitudeRef':
+ case 'GPSDestLongitudeRef':
+ switch( $val ) {
+ case 'E': case 'W':
+ $val = self::msg( 'GPSLongitude', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'GPSAltitude':
+ if ( $val < 0 ) {
+ $val = self::msg( 'GPSAltitude', 'below-sealevel', self::formatNum( -$val, 3 ) );
+ } else {
+ $val = self::msg( 'GPSAltitude', 'above-sealevel', self::formatNum( $val, 3 ) );
+ }
+ break;
+
+ case 'GPSStatus':
+ switch( $val ) {
+ case 'A': case 'V':
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'GPSMeasureMode':
+ switch( $val ) {
+ case 2: case 3:
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+
+ case 'GPSTrackRef':
+ case 'GPSImgDirectionRef':
+ case 'GPSDestBearingRef':
+ switch( $val ) {
+ case 'T': case 'M':
+ $val = self::msg( 'GPSDirection', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'GPSLatitude':
+ case 'GPSDestLatitude':
+ $val = self::formatCoords( $val, 'latitude' );
+ break;
+ case 'GPSLongitude':
+ case 'GPSDestLongitude':
+ $val = self::formatCoords( $val, 'longitude' );
+ break;
+
+ case 'GPSSpeedRef':
+ switch( $val ) {
+ case 'K': case 'M': case 'N':
+ $val = self::msg( 'GPSSpeed', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'GPSDestDistanceRef':
+ switch( $val ) {
+ case 'K': case 'M': case 'N':
+ $val = self::msg( 'GPSDestDistance', $val );
+ break;
+ default:
+ /* If not recognized, display as is. */
+ break;
+ }
+ break;
+
+ case 'GPSDOP':
+ // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
+ if ( $val <= 2 ) {
+ $val = self::msg( $tag, 'excellent', self::formatNum( $val ) );
+ } elseif ( $val <= 5 ) {
+ $val = self::msg( $tag, 'good', self::formatNum( $val ) );
+ } elseif ( $val <= 10 ) {
+ $val = self::msg( $tag, 'moderate', self::formatNum( $val ) );
+ } elseif ( $val <= 20 ) {
+ $val = self::msg( $tag, 'fair', self::formatNum( $val ) );
+ } else {
+ $val = self::msg( $tag, 'poor', self::formatNum( $val ) );
+ }
+ break;
+
+ // This is not in the Exif standard, just a special
+ // case for our purposes which enables wikis to wikify
+ // the make, model and software name to link to their articles.
+ case 'Make':
+ case 'Model':
+ $val = self::msg( $tag, '', $val );
+ break;
+
+ case 'Software':
+ if ( is_array( $val ) ) {
+ //if its a software, version array.
+ $val = wfMsg( 'exif-software-version-value', $val[0], $val[1] );
+ } else {
+ $val = self::msg( $tag, '', $val );
+ }
+ break;
+
+ case 'ExposureTime':
+ // Show the pretty fraction as well as decimal version
+ $val = wfMsg( 'exif-exposuretime-format',
+ self::formatFraction( $val ), self::formatNum( $val ) );
+ break;
+ case 'ISOSpeedRatings':
+ // If its = 65535 that means its at the
+ // limit of the size of Exif::short and
+ // is really higher.
+ if ( $val == '65535' ) {
+ $val = self::msg( $tag, 'overflow' );
+ } else {
+ $val = self::formatNum( $val );
+ }
+ break;
+ case 'FNumber':
+ $val = wfMsg( 'exif-fnumber-format',
+ self::formatNum( $val ) );
+ break;
+
+ case 'FocalLength': case 'FocalLengthIn35mmFilm':
+ $val = wfMsg( 'exif-focallength-format',
+ self::formatNum( $val ) );
+ break;
+
+ case 'MaxApertureValue':
+ if ( strpos( $val, '/' ) !== false ) {
+ // need to expand this earlier to calculate fNumber
+ list($n, $d) = explode('/', $val);
+ if ( is_numeric( $n ) && is_numeric( $d ) ) {
+ $val = $n / $d;
+ }
+ }
+ if ( is_numeric( $val ) ) {
+ $fNumber = pow( 2, $val / 2 );
+ if ( $fNumber !== false ) {
+ $val = wfMsg( 'exif-maxaperturevalue-value',
+ self::formatNum( $val ),
+ self::formatNum( $fNumber, 2 )
+ );
+ }
+ }
+ break;
+
+ case 'iimCategory':
+ switch( strtolower( $val ) ) {
+ // See pg 29 of IPTC photo
+ // metadata standard.
+ case 'ace': case 'clj':
+ case 'dis': case 'fin':
+ case 'edu': case 'evn':
+ case 'hth': case 'hum':
+ case 'lab': case 'lif':
+ case 'pol': case 'rel':
+ case 'sci': case 'soi':
+ case 'spo': case 'war':
+ case 'wea':
+ $val = self::msg(
+ 'iimcategory',
+ $val
+ );
+ }
+ break;
+ case 'SubjectNewsCode':
+ // Essentially like iimCategory.
+ // 8 (numeric) digit hierarchical
+ // classification. We decode the
+ // first 2 digits, which provide
+ // a broad category.
+ $val = self::convertNewsCode( $val );
+ break;
+ case 'Urgency':
+ // 1-8 with 1 being highest, 5 normal
+ // 0 is reserved, and 9 is 'user-defined'.
+ $urgency = '';
+ if ( $val == 0 || $val == 9 ) {
+ $urgency = 'other';
+ } elseif ( $val < 5 && $val > 1 ) {
+ $urgency = 'high';
+ } elseif ( $val == 5 ) {
+ $urgency = 'normal';
+ } elseif ( $val <= 8 && $val > 5) {
+ $urgency = 'low';
+ }
+
+ if ( $urgency !== '' ) {
+ $val = self::msg( 'urgency',
+ $urgency, $val
+ );
+ }
+ break;
+
+ // Things that have a unit of pixels.
+ case 'OriginalImageHeight':
+ case 'OriginalImageWidth':
+ case 'PixelXDimension':
+ case 'PixelYDimension':
+ case 'ImageWidth':
+ case 'ImageLength':
+ $val = self::formatNum( $val ) . ' ' . wfMsg( 'unit-pixel' );
+ break;
+
+ // Do not transform fields with pure text.
+ // For some languages the formatNum()
+ // conversion results to wrong output like
+ // foo,bar@example,com or foo٫bar@example٫com.
+ // Also some 'numeric' things like Scene codes
+ // are included here as we really don't want
+ // commas inserted.
+ case 'ImageDescription':
+ case 'Artist':
+ case 'Copyright':
+ case 'RelatedSoundFile':
+ case 'ImageUniqueID':
+ case 'SpectralSensitivity':
+ case 'GPSSatellites':
+ case 'GPSVersionID':
+ case 'GPSMapDatum':
+ case 'Keywords':
+ case 'WorldRegionDest':
+ case 'CountryDest':
+ case 'CountryCodeDest':
+ case 'ProvinceOrStateDest':
+ case 'CityDest':
+ case 'SublocationDest':
+ case 'WorldRegionCreated':
+ case 'CountryCreated':
+ case 'CountryCodeCreated':
+ case 'ProvinceOrStateCreated':
+ case 'CityCreated':
+ case 'SublocationCreated':
+ case 'ObjectName':
+ case 'SpecialInstructions':
+ case 'Headline':
+ case 'Credit':
+ case 'Source':
+ case 'EditStatus':
+ case 'FixtureIdentifier':
+ case 'LocationDest':
+ case 'LocationDestCode':
+ case 'Writer':
+ case 'JPEGFileComment':
+ case 'iimSupplementalCategory':
+ case 'OriginalTransmissionRef':
+ case 'Identifier':
+ case 'dc-contributor':
+ case 'dc-coverage':
+ case 'dc-publisher':
+ case 'dc-relation':
+ case 'dc-rights':
+ case 'dc-source':
+ case 'dc-type':
+ case 'Lens':
+ case 'SerialNumber':
+ case 'CameraOwnerName':
+ case 'Label':
+ case 'Nickname':
+ case 'RightsCertificate':
+ case 'CopyrightOwner':
+ case 'UsageTerms':
+ case 'WebStatement':
+ case 'OriginalDocumentID':
+ case 'LicenseUrl':
+ case 'MorePermissionsUrl':
+ case 'AttributionUrl':
+ case 'PreferredAttributionName':
+ case 'PNGFileComment':
+ case 'Disclaimer':
+ case 'ContentWarning':
+ case 'GIFFileComment':
+ case 'SceneCode':
+ case 'IntellectualGenre':
+ case 'Event':
+ case 'OrginisationInImage':
+ case 'PersonInImage':
+
+ $val = htmlspecialchars( $val );
+ break;
+
+ case 'ObjectCycle':
+ switch ( $val ) {
+ case 'a': case 'p': case 'b':
+ $val = self::msg( $tag, $val );
+ break;
+ default:
+ $val = htmlspecialchars( $val );
+ break;
+ }
+ break;
+ case 'Copyrighted':
+ switch( $val ) {
+ case 'True': case 'False':
+ $val = self::msg( $tag, $val );
+ break;
+ }
+ break;
+ case 'Rating':
+ if ( $val == '-1' ) {
+ $val = self::msg( $tag, 'rejected' );
+ } else {
+ $val = self::formatNum( $val );
+ }
+ break;
+
+ case 'LanguageCode':
+ $lang = $wgLang->getLanguageName( strtolower( $val ) );
+ if ($lang) {
+ $val = htmlspecialchars( $lang );
+ } else {
+ $val = htmlspecialchars( $val );
+ }
+ break;
+
+ default:
+ $val = self::formatNum( $val );
+ break;
+ }
+ }
+ // End formatting values, start flattening arrays.
+ $vals = self::flattenArray( $vals, $type );
+
+ }
+ return $tags;
+ }
+
+ /**
+ * A function to collapse multivalued tags into a single value.
+ * This turns an array of (for example) authors into a bulleted list.
+ *
+ * This is public on the basis it might be useful outside of this class.
+ *
+ * @param $vals Array array of values
+ * @param $type String Type of array (either lang, ul, ol).
+ * lang = language assoc array with keys being the lang code
+ * ul = unordered list, ol = ordered list
+ * type can also come from the '_type' member of $vals.
+ * @param $noHtml Boolean If to avoid returning anything resembling
+ * html. (Ugly hack for backwards compatibility with old mediawiki).
+ * @return String single value (in wiki-syntax).
+ */
+ public static function flattenArray( $vals, $type = 'ul', $noHtml = false ) {
+ if ( isset( $vals['_type'] ) ) {
+ $type = $vals['_type'];
+ unset( $vals['_type'] );
+ }
+
+ if ( !is_array( $vals ) ) {
+ return $vals; // do nothing if not an array;
+ }
+ elseif ( count( $vals ) === 1 && $type !== 'lang' ) {
+ return $vals[0];
+ }
+ elseif ( count( $vals ) === 0 ) {
+ wfDebug( __METHOD__ . ' metadata array with 0 elements!' );
+ return ""; // paranoia. This should never happen
+ }
+ /* @todo FIXME: This should hide some of the list entries if there are
+ * say more than four. Especially if a field is translated into 20
+ * languages, we don't want to show them all by default
+ */
+ else {
+ global $wgContLang;
+ switch( $type ) {
+ case 'lang':
+ // Display default, followed by ContLang,
+ // followed by the rest in no particular
+ // order.
+
+ // Todo: hide some items if really long list.
+
+ $content = '';
+
+ $cLang = $wgContLang->getCode();
+ $defaultItem = false;
+ $defaultLang = false;
+
+ // If default is set, save it for later,
+ // as we don't know if it's equal to
+ // one of the lang codes. (In xmp
+ // you specify the language for a
+ // default property by having both
+ // a default prop, and one in the language
+ // that are identical)
+ if ( isset( $vals['x-default'] ) ) {
+ $defaultItem = $vals['x-default'];
+ unset( $vals['x-default'] );
+ }
+ // Do contentLanguage.
+ if ( isset( $vals[$cLang] ) ) {
+ $isDefault = false;
+ if ( $vals[$cLang] === $defaultItem ) {
+ $defaultItem = false;
+ $isDefault = true;
+ }
+ $content .= self::langItem(
+ $vals[$cLang], $cLang,
+ $isDefault, $noHtml );
+
+ unset( $vals[$cLang] );
+ }
+
+ // Now do the rest.
+ foreach ( $vals as $lang => $item ) {
+ if ( $item === $defaultItem ) {
+ $defaultLang = $lang;
+ continue;
+ }
+ $content .= self::langItem( $item,
+ $lang, false, $noHtml );
+ }
+ if ( $defaultItem !== false ) {
+ $content = self::langItem( $defaultItem,
+ $defaultLang, true, $noHtml )
+ . $content;
+ }
+ if ( $noHtml ) {
+ return $content;
+ }
+ return '<ul class="metadata-langlist">' .
+ $content .
+ '</ul>';
+ case 'ol':
+ if ( $noHtml ) {
+ return "\n#" . implode( "\n#", $vals );
+ }
+ return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
+ case 'ul':
+ default:
+ if ( $noHtml ) {
+ return "\n*" . implode( "\n*", $vals );
+ }
+ return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
+ }
+ }
+ }
+
+ /** Helper function for creating lists of translations.
+ *
+ * @param $value String value (this is not escaped)
+ * @param $lang String lang code of item or false
+ * @param $default Boolean if it is default value.
+ * @param $noHtml Boolean If to avoid html (for back-compat)
+ * @return language item (Note: despite how this looks,
+ * this is treated as wikitext not html).
+ */
+ private static function langItem( $value, $lang, $default = false, $noHtml = false ) {
+ global $wgContLang;
+ if ( $lang === false && $default === false) {
+ throw new MWException('$lang and $default cannot both '
+ . 'be false.');
+ }
+
+ if ( $noHtml ) {
+ $wrappedValue = $value;
+ } else {
+ $wrappedValue = '<span class="mw-metadata-lang-value">'
+ . $value . '</span>';
+ }
+
+ if ( $lang === false ) {
+ if ( $noHtml ) {
+ return wfMsg( 'metadata-langitem-default',
+ $wrappedValue ) . "\n\n";
+ } /* else */
+ return '<li class="mw-metadata-lang-default">'
+ . wfMsg( 'metadata-langitem-default',
+ $wrappedValue )
+ . "</li>\n";
+ }
+
+ $lowLang = strtolower( $lang );
+ $langName = $wgContLang->getLanguageName( $lowLang );
+ if ( $langName === '' ) {
+ //try just the base language name. (aka en-US -> en ).
+ list( $langPrefix ) = explode( '-', $lowLang, 2 );
+ $langName = $wgContLang->getLanguageName( $langPrefix );
+ if ( $langName === '' ) {
+ // give up.
+ $langName = $lang;
+ }
+ }
+ // else we have a language specified
+
+ if ( $noHtml ) {
+ return '*' . wfMsg( 'metadata-langitem',
+ $wrappedValue, $langName, $lang );
+ } /* else: */
+
+ $item = '<li class="mw-metadata-lang-code-'
+ . $lang;
+ if ( $default ) {
+ $item .= ' mw-metadata-lang-default';
+ }
+ $item .= '" lang="' . $lang . '">';
+ $item .= wfMsg( 'metadata-langitem',
+ $wrappedValue, $langName, $lang );
+ $item .= "</li>\n";
+ return $item;
+ }
+
+ /**
+ * Convenience function for getFormattedData()
+ *
+ * @private
+ *
+ * @param $tag String: the tag name to pass on
+ * @param $val String: the value of the tag
+ * @param $arg String: an argument to pass ($1)
+ * @param $arg2 String: a 2nd argument to pass ($2)
+ * @return string A wfMsg of "exif-$tag-$val" in lower case
+ */
+ static function msg( $tag, $val, $arg = null, $arg2 = null ) {
+ global $wgContLang;
+
+ if ($val === '')
+ $val = 'value';
+ return wfMsg( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 );
+ }
+
+ /**
+ * Format a number, convert numbers from fractions into floating point
+ * numbers, joins arrays of numbers with commas.
+ *
+ * @private
+ *
+ * @param $num Mixed: the value to format
+ * @param $round digits to round to or false.
+ * @return mixed A floating point number or whatever we were fed
+ */
+ static function formatNum( $num, $round = false ) {
+ global $wgLang;
+ $m = array();
+ if( is_array($num) ) {
+ $out = array();
+ foreach( $num as $number ) {
+ $out[] = self::formatNum($number);
+ }
+ return $wgLang->commaList( $out );
+ }
+ if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
+ if ( $m[2] != 0 ) {
+ $newNum = $m[1] / $m[2];
+ if ( $round !== false ) {
+ $newNum = round( $newNum, $round );
+ }
+ } else {
+ $newNum = $num;
+ }
+
+ return $wgLang->formatNum( $newNum );
+ } else {
+ if ( is_numeric( $num ) && $round !== false ) {
+ $num = round( $num, $round );
+ }
+ return $wgLang->formatNum( $num );
+ }
+ }
+
+ /**
+ * Format a rational number, reducing fractions
+ *
+ * @private
+ *
+ * @param $num Mixed: the value to format
+ * @return mixed A floating point number or whatever we were fed
+ */
+ static function formatFraction( $num ) {
+ $m = array();
+ if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
+ $numerator = intval( $m[1] );
+ $denominator = intval( $m[2] );
+ $gcd = self::gcd( abs( $numerator ), $denominator );
+ if( $gcd != 0 ) {
+ // 0 shouldn't happen! ;)
+ return self::formatNum( $numerator / $gcd ) . '/' . self::formatNum( $denominator / $gcd );
+ }
+ }
+ return self::formatNum( $num );
+ }
+
+ /**
+ * Calculate the greatest common divisor of two integers.
+ *
+ * @param $a Integer: Numerator
+ * @param $b Integer: Denominator
+ * @return int
+ * @private
+ */
+ static function gcd( $a, $b ) {
+ /*
+ // http://en.wikipedia.org/wiki/Euclidean_algorithm
+ // Recursive form would be:
+ if( $b == 0 )
+ return $a;
+ else
+ return gcd( $b, $a % $b );
+ */
+ while( $b != 0 ) {
+ $remainder = $a % $b;
+
+ // tail recursion...
+ $a = $b;
+ $b = $remainder;
+ }
+ return $a;
+ }
+
+ /** Fetch the human readable version of a news code.
+ * A news code is an 8 digit code. The first two
+ * digits are a general classification, so we just
+ * translate that.
+ *
+ * Note, leading 0's are significant, so this is
+ * a string, not an int.
+ *
+ * @param $val String: The 8 digit news code.
+ * @return The human readable form
+ */
+ static private function convertNewsCode( $val ) {
+ if ( !preg_match( '/^\d{8}$/D', $val ) ) {
+ // Not a valid news code.
+ return $val;
+ }
+ $cat = '';
+ switch( substr( $val , 0, 2 ) ) {
+ case '01':
+ $cat = 'ace';
+ break;
+ case '02':
+ $cat = 'clj';
+ break;
+ case '03':
+ $cat = 'dis';
+ break;
+ case '04':
+ $cat = 'fin';
+ break;
+ case '05':
+ $cat = 'edu';
+ break;
+ case '06':
+ $cat = 'evn';
+ break;
+ case '07':
+ $cat = 'hth';
+ break;
+ case '08':
+ $cat = 'hum';
+ break;
+ case '09':
+ $cat = 'lab';
+ break;
+ case '10':
+ $cat = 'lif';
+ break;
+ case '11':
+ $cat = 'pol';
+ break;
+ case '12':
+ $cat = 'rel';
+ break;
+ case '13':
+ $cat = 'sci';
+ break;
+ case '14':
+ $cat = 'soi';
+ break;
+ case '15':
+ $cat = 'spo';
+ break;
+ case '16':
+ $cat = 'war';
+ break;
+ case '17':
+ $cat = 'wea';
+ break;
+ }
+ if ( $cat !== '' ) {
+ $catMsg = self::msg( 'iimcategory', $cat );
+ $val = self::msg( 'subjectnewscode', '', $val, $catMsg );
+ }
+ return $val;
+ }
+
+ /**
+ * Format a coordinate value, convert numbers from floating point
+ * into degree minute second representation.
+ *
+ * @param $coords Array: degrees, minutes and seconds
+ * @param $type String: latitude or longitude (for if its a NWS or E)
+ * @return mixed A floating point number or whatever we were fed
+ */
+ static function formatCoords( $coord, $type ) {
+ $ref = '';
+ if ( $coord < 0 ) {
+ $nCoord = -$coord;
+ if ( $type === 'latitude' ) {
+ $ref = 'S';
+ }
+ elseif ( $type === 'longitude' ) {
+ $ref = 'W';
+ }
+ }
+ else {
+ $nCoord = $coord;
+ if ( $type === 'latitude' ) {
+ $ref = 'N';
+ }
+ elseif ( $type === 'longitude' ) {
+ $ref = 'E';
+ }
+ }
+
+ $deg = floor( $nCoord );
+ $min = floor( ( $nCoord - $deg ) * 60.0 );
+ $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
+
+ $deg = self::formatNum( $deg );
+ $min = self::formatNum( $min );
+ $sec = self::formatNum( $sec );
+
+ return wfMsg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord );
+ }
+
+ /**
+ * Format the contact info field into a single value.
+ *
+ * @param $vals Array array with fields of the ContactInfo
+ * struct defined in the IPTC4XMP spec. Or potentially
+ * an array with one element that is a free form text
+ * value from the older iptc iim 1:118 prop.
+ *
+ * This function might be called from
+ * JpegHandler::convertMetadataVersion which is why it is
+ * public.
+ *
+ * @return String of html-ish looking wikitext
+ */
+ public static function collapseContactInfo( $vals ) {
+ if( ! ( isset( $vals['CiAdrExtadr'] )
+ || isset( $vals['CiAdrCity'] )
+ || isset( $vals['CiAdrCtry'] )
+ || isset( $vals['CiEmailWork'] )
+ || isset( $vals['CiTelWork'] )
+ || isset( $vals['CiAdrPcode'] )
+ || isset( $vals['CiAdrRegion'] )
+ || isset( $vals['CiUrlWork'] )
+ ) ) {
+ // We don't have any sub-properties
+ // This could happen if its using old
+ // iptc that just had this as a free-form
+ // text value.
+ // Note: We run this through htmlspecialchars
+ // partially to be consistent, and partially
+ // because people often insert >, etc into
+ // the metadata which should not be interpreted
+ // but we still want to auto-link urls.
+ foreach( $vals as &$val ) {
+ $val = htmlspecialchars( $val );
+ }
+ return self::flattenArray( $vals );
+ } else {
+ // We have a real ContactInfo field.
+ // Its unclear if all these fields have to be
+ // set, so assume they do not.
+ $url = $tel = $street = $city = $country = '';
+ $email = $postal = $region = '';
+
+ // Also note, some of the class names this uses
+ // are similar to those used by hCard. This is
+ // mostly because they're sensible names. This
+ // does not (and does not attempt to) output
+ // stuff in the hCard microformat. However it
+ // might output in the adr microformat.
+
+ if ( isset( $vals['CiAdrExtadr'] ) ) {
+ // Todo: This can potentially be multi-line.
+ // Need to check how that works in XMP.
+ $street = '<span class="extended-address">'
+ . htmlspecialchars(
+ $vals['CiAdrExtadr'] )
+ . '</span>';
+ }
+ if ( isset( $vals['CiAdrCity'] ) ) {
+ $city = '<span class="locality">'
+ . htmlspecialchars( $vals['CiAdrCity'] )
+ . '</span>';
+ }
+ if ( isset( $vals['CiAdrCtry'] ) ) {
+ $country = '<span class="country-name">'
+ . htmlspecialchars( $vals['CiAdrCtry'] )
+ . '</span>';
+ }
+ if ( isset( $vals['CiEmailWork'] ) ) {
+ $emails = array();
+ // Have to split multiple emails at commas/new lines.
+ $splitEmails = explode( "\n", $vals['CiEmailWork'] );
+ foreach ( $splitEmails as $e1 ) {
+ // Also split on comma
+ foreach ( explode( ',', $e1 ) as $e2 ) {
+ $finalEmail = trim( $e2 );
+ if ( $finalEmail == ',' || $finalEmail == '' ) {
+ continue;
+ }
+ if ( strpos( $finalEmail, '<' ) !== false ) {
+ // Don't do fancy formatting to
+ // "My name" <foo@bar.com> style stuff
+ $emails[] = $finalEmail;
+ } else {
+ $emails[] = '[mailto:'
+ . $finalEmail
+ . ' <span class="email">'
+ . $finalEmail
+ . '</span>]';
+ }
+ }
+ }
+ $email = implode( ', ', $emails );
+ }
+ if ( isset( $vals['CiTelWork'] ) ) {
+ $tel = '<span class="tel">'
+ . htmlspecialchars( $vals['CiTelWork'] )
+ . '</span>';
+ }
+ if ( isset( $vals['CiAdrPcode'] ) ) {
+ $postal = '<span class="postal-code">'
+ . htmlspecialchars(
+ $vals['CiAdrPcode'] )
+ . '</span>';
+ }
+ if ( isset( $vals['CiAdrRegion'] ) ) {
+ // Note this is province/state.
+ $region = '<span class="region">'
+ . htmlspecialchars(
+ $vals['CiAdrRegion'] )
+ . '</span>';
+ }
+ if ( isset( $vals['CiUrlWork'] ) ) {
+ $url = '<span class="url">'
+ . htmlspecialchars( $vals['CiUrlWork'] )
+ . '</span>';
+ }
+ return wfMsg( 'exif-contact-value', $email, $url,
+ $street, $city, $region, $postal, $country,
+ $tel );
+ }
+ }
+}
+
+/** For compatability with old FormatExif class
+ * which some extensions use.
+ *
+ * @deprecated since 1.18
+ *
+**/
+class FormatExif {
+ var $meta;
+ function FormatExif ( $meta ) {
+ wfDeprecated(__METHOD__);
+ $this->meta = $meta;
+ }
+
+ function getFormattedData ( ) {
+ return FormatMetadata::getFormattedData( $this->meta );
+ }
+}
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
index c4ede331..325ceb9a 100644
--- a/includes/media/GIF.php
+++ b/includes/media/GIF.php
@@ -12,56 +12,104 @@
* @ingroup Media
*/
class GIFHandler extends BitmapHandler {
+
+ const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
function getMetadata( $image, $filename ) {
- if ( !isset( $image->parsedGIFMetadata ) ) {
- try {
- $image->parsedGIFMetadata = GIFMetadataExtractor::getMetadata( $filename );
- } catch( Exception $e ) {
- // Broken file?
- wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
- return '0';
- }
+ try {
+ $parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
+ } catch( Exception $e ) {
+ // Broken file?
+ wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+ return self::BROKEN_FILE;
}
- return serialize( $image->parsedGIFMetadata );
-
+ return serialize($parsedGIFMetadata);
}
-
+
+ /**
+ * @param $image File
+ * @return array|bool
+ */
function formatMetadata( $image ) {
- return false;
+ $meta = $image->getMetadata();
+
+ if ( !$meta ) {
+ return false;
+ }
+ $meta = unserialize( $meta );
+ if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
+ return false;
+ }
+
+ if ( isset( $meta['metadata']['_MW_GIF_VERSION'] ) ) {
+ unset( $meta['metadata']['_MW_GIF_VERSION'] );
+ }
+ return $this->formatMetadataHelper( $meta['metadata'] );
}
-
+
+ /**
+ * @param $image File
+ * @param $width
+ * @param $height
+ * @return
+ */
function getImageArea( $image, $width, $height ) {
$ser = $image->getMetadata();
- if ($ser) {
- $metadata = unserialize($ser);
+ if ( $ser ) {
+ $metadata = unserialize( $ser );
return $width * $height * $metadata['frameCount'];
} else {
return $width * $height;
}
}
+ /**
+ * @param $image File
+ * @return bool
+ */
function isAnimatedImage( $image ) {
$ser = $image->getMetadata();
- if ($ser) {
+ if ( $ser ) {
$metadata = unserialize($ser);
- if( $metadata['frameCount'] > 1 ) return true;
+ if( $metadata['frameCount'] > 1 ) {
+ return true;
+ }
}
return false;
}
-
+
function getMetadataType( $image ) {
return 'parsed-gif';
}
-
+
function isMetadataValid( $image, $metadata ) {
+ if ( $metadata === self::BROKEN_FILE ) {
+ // Do not repetitivly regenerate metadata on broken file.
+ return self::METADATA_GOOD;
+ }
+
wfSuppressWarnings();
$data = unserialize( $metadata );
wfRestoreWarnings();
- return (boolean) $data;
+
+ if ( !$data || !is_array( $data ) ) {
+ wfDebug(__METHOD__ . ' invalid GIF metadata' );
+ return self::METADATA_BAD;
+ }
+
+ if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
+ || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION ) {
+ wfDebug(__METHOD__ . ' old but compatible GIF metadata' );
+ return self::METADATA_COMPATIBLE;
+ }
+ return self::METADATA_GOOD;
}
+ /**
+ * @param $image File
+ * @return string
+ */
function getLongDesc( $image ) {
global $wgLang;
@@ -71,20 +119,25 @@ class GIFHandler extends BitmapHandler {
$metadata = unserialize($image->getMetadata());
wfRestoreWarnings();
- if (!$metadata || $metadata['frameCount'] <= 1)
+ if (!$metadata || $metadata['frameCount'] <= 1) {
return $original;
-
+ }
+
+ /* Preserve original image info string, but strip the last char ')' so we can add even more */
$info = array();
$info[] = $original;
- if ($metadata['looped'])
+ if ( $metadata['looped'] ) {
$info[] = wfMsgExt( 'file-info-gif-looped', 'parseinline' );
+ }
- if ($metadata['frameCount'] > 1)
+ if ( $metadata['frameCount'] > 1 ) {
$info[] = wfMsgExt( 'file-info-gif-frames', 'parseinline', $metadata['frameCount'] );
+ }
- if ($metadata['duration'])
+ if ( $metadata['duration'] ) {
$info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
+ }
return $wgLang->commaList( $info );
}
diff --git a/includes/media/GIFMetadataExtractor.php b/includes/media/GIFMetadataExtractor.php
index bc1a4804..5dbeb8f8 100644
--- a/includes/media/GIFMetadataExtractor.php
+++ b/includes/media/GIFMetadataExtractor.php
@@ -21,164 +21,294 @@ class GIFMetadataExtractor {
static $gif_extension_sep;
static $gif_term;
+ const VERSION = 1;
+
+ // Each sub-block is less than or equal to 255 bytes.
+ // Most of the time its 255 bytes, except for in XMP
+ // blocks, where it's usually between 32-127 bytes each.
+ const MAX_SUBBLOCKS = 262144; // 5mb divided by 20.
+
+ /**
+ * @throws Exception
+ * @param $filename string
+ * @return array
+ */
static function getMetadata( $filename ) {
self::$gif_frame_sep = pack( "C", ord("," ) );
self::$gif_extension_sep = pack( "C", ord("!" ) );
self::$gif_term = pack( "C", ord(";" ) );
-
+
$frameCount = 0;
$duration = 0.0;
$isLooped = false;
+ $xmp = "";
+ $comment = array();
- if (!$filename)
+ if ( !$filename ) {
throw new Exception( "No file name specified" );
- elseif ( !file_exists($filename) || is_dir($filename) )
+ } elseif ( !file_exists( $filename ) || is_dir( $filename ) ) {
throw new Exception( "File $filename does not exist" );
-
- $fh = fopen( $filename, 'r' );
-
- if (!$fh)
+ }
+
+ $fh = fopen( $filename, 'rb' );
+
+ if ( !$fh ) {
throw new Exception( "Unable to open file $filename" );
-
+ }
+
// Check for the GIF header
$buf = fread( $fh, 6 );
if ( !($buf == 'GIF87a' || $buf == 'GIF89a') ) {
throw new Exception( "Not a valid GIF file; header: $buf" );
}
-
+
// Skip over width and height.
fread( $fh, 4 );
-
+
// Read BPP
$buf = fread( $fh, 1 );
$bpp = self::decodeBPP( $buf );
-
+
// Skip over background and aspect ratio
fread( $fh, 2 );
-
+
// Skip over the GCT
self::readGCT( $fh, $bpp );
-
+
while( !feof( $fh ) ) {
$buf = fread( $fh, 1 );
-
+
if ($buf == self::$gif_frame_sep) {
// Found a frame
$frameCount++;
-
+
## Skip bounding box
fread( $fh, 8 );
-
+
## Read BPP
$buf = fread( $fh, 1 );
$bpp = self::decodeBPP( $buf );
-
+
## Read GCT
self::readGCT( $fh, $bpp );
fread( $fh, 1 );
self::skipBlock( $fh );
} elseif ( $buf == self::$gif_extension_sep ) {
$buf = fread( $fh, 1 );
+ if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" );
$extension_code = unpack( 'C', $buf );
$extension_code = $extension_code[1];
-
+
if ($extension_code == 0xF9) {
// Graphics Control Extension.
fread( $fh, 1 ); // Block size
-
+
fread( $fh, 1 ); // Transparency, disposal method, user input
-
+
$buf = fread( $fh, 2 ); // Delay, in hundredths of seconds.
+ if ( strlen( $buf ) < 2 ) throw new Exception( "Ran out of input" );
$delay = unpack( 'v', $buf );
$delay = $delay[1];
$duration += $delay * 0.01;
-
+
fread( $fh, 1 ); // Transparent colour index
-
+
$term = fread( $fh, 1 ); // Should be a terminator
+ if ( strlen( $term ) < 1 ) throw new Exception( "Ran out of input" );
$term = unpack( 'C', $term );
$term = $term[1];
- if ($term != 0 )
+ if ($term != 0 ) {
throw new Exception( "Malformed Graphics Control Extension block" );
+ }
+ } elseif ($extension_code == 0xFE) {
+ // Comment block(s).
+ $data = self::readBlock( $fh );
+ if ( $data === "" ) {
+ throw new Exception( 'Read error, zero-length comment block' );
+ }
+
+ // The standard says this should be ASCII, however its unclear if
+ // thats true in practise. Check to see if its valid utf-8, if so
+ // assume its that, otherwise assume its windows-1252 (iso-8859-1)
+ $dataCopy = $data;
+ // quickIsNFCVerify has the side effect of replacing any invalid characters
+ UtfNormal::quickIsNFCVerify( $dataCopy );
+
+ if ( $dataCopy !== $data ) {
+ wfSuppressWarnings();
+ $data = iconv( 'windows-1252', 'UTF-8', $data );
+ wfRestoreWarnings();
+ }
+
+ $commentCount = count( $comment );
+ if ( $commentCount === 0
+ || $comment[$commentCount-1] !== $data )
+ {
+ // Some applications repeat the same comment on each
+ // frame of an animated GIF image, so if this comment
+ // is identical to the last, only extract once.
+ $comment[] = $data;
+ }
} elseif ($extension_code == 0xFF) {
// Application extension (Netscape info about the animated gif)
+ // or XMP (or theoretically any other type of extension block)
$blockLength = fread( $fh, 1 );
+ if ( strlen( $blockLength ) < 1 ) throw new Exception( "Ran out of input" );
$blockLength = unpack( 'C', $blockLength );
$blockLength = $blockLength[1];
$data = fread( $fh, $blockLength );
-
- // NETSCAPE2.0 (application name)
- if ($blockLength != 11 || $data != 'NETSCAPE2.0') {
+
+ if ($blockLength != 11 ) {
+ wfDebug( __METHOD__ . ' GIF application block with wrong length' );
fseek( $fh, -($blockLength + 1), SEEK_CUR );
self::skipBlock( $fh );
continue;
}
+
+ // NETSCAPE2.0 (application name for animated gif)
+ if ( $data == 'NETSCAPE2.0' ) {
- $data = fread( $fh, 2 ); // Block length and introduction, should be 03 01
-
- if ($data != "\x03\x01") {
- throw new Exception( "Expected \x03\x01, got $data" );
- }
-
- // Unsigned little-endian integer, loop count or zero for "forever"
- $loopData = fread( $fh, 2 );
- $loopData = unpack( 'v', $loopData );
- $loopCount = $loopData[1];
-
- if ($loopCount != 1) {
- $isLooped = true;
+ $data = fread( $fh, 2 ); // Block length and introduction, should be 03 01
+
+ if ($data != "\x03\x01") {
+ throw new Exception( "Expected \x03\x01, got $data" );
+ }
+
+ // Unsigned little-endian integer, loop count or zero for "forever"
+ $loopData = fread( $fh, 2 );
+ if ( strlen( $loopData ) < 2 ) throw new Exception( "Ran out of input" );
+ $loopData = unpack( 'v', $loopData );
+ $loopCount = $loopData[1];
+
+ if ($loopCount != 1) {
+ $isLooped = true;
+ }
+
+ // Read out terminator byte
+ fread( $fh, 1 );
+ } elseif ( $data == 'XMP DataXMP' ) {
+ // application name for XMP data.
+ // see pg 18 of XMP spec part 3.
+
+ $xmp = self::readBlock( $fh, true );
+
+ if ( substr( $xmp, -257, 3 ) !== "\x01\xFF\xFE"
+ || substr( $xmp, -4 ) !== "\x03\x02\x01\x00" )
+ {
+ // this is just a sanity check.
+ throw new Exception( "XMP does not have magic trailer!" );
+ }
+
+ // strip out trailer.
+ $xmp = substr( $xmp, 0, -257 );
+
+ } else {
+ // unrecognized extension block
+ fseek( $fh, -($blockLength + 1), SEEK_CUR );
+ self::skipBlock( $fh );
+ continue;
}
-
- // Read out terminator byte
- fread( $fh, 1 );
} else {
self::skipBlock( $fh );
}
} elseif ( $buf == self::$gif_term ) {
break;
} else {
+ if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" );
$byte = unpack( 'C', $buf );
$byte = $byte[1];
throw new Exception( "At position: ".ftell($fh). ", Unknown byte ".$byte );
}
}
-
+
return array(
'frameCount' => $frameCount,
'looped' => $isLooped,
- 'duration' => $duration
+ 'duration' => $duration,
+ 'xmp' => $xmp,
+ 'comment' => $comment,
);
-
}
-
+
+ /**
+ * @param $fh
+ * @param $bpp
+ * @return void
+ */
static function readGCT( $fh, $bpp ) {
- if ($bpp > 0) {
- for( $i=1; $i<=pow(2,$bpp); ++$i ) {
+ if ( $bpp > 0 ) {
+ for( $i=1; $i<=pow( 2, $bpp ); ++$i ) {
fread( $fh, 3 );
}
}
}
-
+
+ /**
+ * @param $data
+ * @return int
+ */
static function decodeBPP( $data ) {
+ if ( strlen( $data ) < 1 ) throw new Exception( "Ran out of input" );
$buf = unpack( 'C', $data );
$buf = $buf[1];
$bpp = ( $buf & 7 ) + 1;
$buf >>= 7;
-
+
$have_map = $buf & 1;
-
+
return $have_map ? $bpp : 0;
}
-
+
+ /**
+ * @param $fh
+ * @return
+ */
static function skipBlock( $fh ) {
while ( !feof( $fh ) ) {
$buf = fread( $fh, 1 );
+ if ( strlen( $buf ) < 1 ) throw new Exception( "Ran out of input" );
$block_len = unpack( 'C', $buf );
$block_len = $block_len[1];
- if ($block_len == 0)
+ if ($block_len == 0) {
return;
+ }
fread( $fh, $block_len );
}
}
+ /**
+ * Read a block. In the GIF format, a block is made up of
+ * several sub-blocks. Each sub block starts with one byte
+ * saying how long the sub-block is, followed by the sub-block.
+ * The entire block is terminated by a sub-block of length
+ * 0.
+ * @param $fh FileHandle
+ * @param $includeLengths Boolean Include the length bytes of the
+ * sub-blocks in the returned value. Normally this is false,
+ * except XMP is weird and does a hack where you need to keep
+ * these length bytes.
+ * @return The data.
+ */
+ static function readBlock( $fh, $includeLengths = false ) {
+ $data = '';
+ $subLength = fread( $fh, 1 );
+ $blocks = 0;
+
+ while( $subLength !== "\0" ) {
+ $blocks++;
+ if ( $blocks > self::MAX_SUBBLOCKS ) {
+ throw new Exception( "MAX_SUBBLOCKS exceeded (over $blocks sub-blocks)" );
+ }
+ if ( feof( $fh ) ) {
+ throw new Exception( "Read error: Unexpected EOF." );
+ }
+ if ( $includeLengths ) {
+ $data .= $subLength;
+ }
+
+ $data .= fread( $fh, ord( $subLength ) );
+ $subLength = fread( $fh, 1 );
+ }
+ return $data;
+ }
}
diff --git a/includes/media/Generic.php b/includes/media/Generic.php
index fa4e731a..48735ebf 100644
--- a/includes/media/Generic.php
+++ b/includes/media/Generic.php
@@ -13,7 +13,9 @@
*/
abstract class MediaHandler {
const TRANSFORM_LATER = 1;
-
+ const METADATA_GOOD = true;
+ const METADATA_BAD = false;
+ const METADATA_COMPATIBLE = 2; // for old but backwards compatible.
/**
* Instance cache
*/
@@ -21,6 +23,10 @@ abstract class MediaHandler {
/**
* Get a MediaHandler for a given MIME type from the instance cache
+ *
+ * @param $type string
+ *
+ * @return MediaHandler
*/
static function getHandler( $type ) {
global $wgMediaHandlers;
@@ -44,20 +50,27 @@ abstract class MediaHandler {
*/
abstract function getParamMap();
- /*
+ /**
* Validate a thumbnail parameter at parse time.
* Return true to accept the parameter, and false to reject it.
* If you return false, the parser will do something quiet and forgiving.
+ *
+ * @param $name
+ * @param $value
*/
abstract function validateParam( $name, $value );
/**
* Merge a parameter array into a string appropriate for inclusion in filenames
+ *
+ * @param $params array
*/
abstract function makeParamString( $params );
/**
* Parse a param string made with makeParamString back into an array
+ *
+ * @param $str string
*/
abstract function parseParamString( $str );
@@ -65,6 +78,8 @@ abstract class MediaHandler {
* Changes the parameter array as necessary, ready for transformation.
* Should be idempotent.
* Returns false if the parameters are unacceptable and the transform should fail
+ * @param $image
+ * @param $params
*/
abstract function normaliseParams( $image, &$params );
@@ -89,15 +104,66 @@ abstract class MediaHandler {
function getMetadata( $image, $path ) { return ''; }
/**
+ * Get metadata version.
+ *
+ * This is not used for validating metadata, this is used for the api when returning
+ * metadata, since api content formats should stay the same over time, and so things
+ * using ForiegnApiRepo can keep backwards compatibility
+ *
+ * All core media handlers share a common version number, and extensions can
+ * use the GetMetadataVersion hook to append to the array (they should append a unique
+ * string so not to get confusing). If there was a media handler named 'foo' with metadata
+ * version 3 it might add to the end of the array the element 'foo=3'. if the core metadata
+ * version is 2, the end version string would look like '2;foo=3'.
+ *
+ * @return string version string
+ */
+ static function getMetadataVersion () {
+ $version = Array( '2' ); // core metadata version
+ wfRunHooks('GetMetadataVersion', Array(&$version));
+ return implode( ';', $version);
+ }
+
+ /**
+ * Convert metadata version.
+ *
+ * By default just returns $metadata, but can be used to allow
+ * media handlers to convert between metadata versions.
+ *
+ * @param $metadata Mixed String or Array metadata array (serialized if string)
+ * @param $version Integer target version
+ * @return Array serialized metadata in specified version, or $metadata on fail.
+ */
+ function convertMetadataVersion( $metadata, $version = 1 ) {
+ if ( !is_array( $metadata ) ) {
+
+ //unserialize to keep return parameter consistent.
+ wfSuppressWarnings();
+ $ret = unserialize( $metadata );
+ wfRestoreWarnings();
+ return $ret;
+ }
+ return $metadata;
+ }
+
+ /**
* Get a string describing the type of metadata, for display purposes.
+ *
+ * @return string
*/
function getMetadataType( $image ) { return false; }
/**
* Check if the metadata string is valid for this handler.
- * If it returns false, Image will reload the metadata from the file and update the database
+ * If it returns MediaHandler::METADATA_BAD (or false), Image
+ * will reload the metadata from the file and update the database.
+ * MediaHandler::METADATA_GOOD for if the metadata is a-ok,
+ * MediaHanlder::METADATA_COMPATIBLE if metadata is old but backwards
+ * compatible (which may or may not trigger a metadata reload).
*/
- function isMetadataValid( $image, $metadata ) { return true; }
+ function isMetadataValid( $image, $metadata ) {
+ return self::METADATA_GOOD;
+ }
/**
@@ -142,6 +208,18 @@ abstract class MediaHandler {
* @return array thumbnail extension and MIME type
*/
function getThumbType( $ext, $mime, $params = null ) {
+ $magic = MimeMagic::singleton();
+ if ( !$ext || $magic->isMatchingExtension( $ext, $mime ) === false ) {
+ // The extension is not valid for this mime type and we do
+ // recognize the mime type
+ $extensions = $magic->getExtensionsForType( $mime );
+ if ( $extensions ) {
+ return array( strtok( $extensions, ' ' ), $mime );
+ }
+ }
+
+ // The extension is correct (true) or the mime type is unknown to
+ // MediaWiki (null)
return array( $ext, $mime );
}
@@ -176,6 +254,8 @@ abstract class MediaHandler {
* Currently "width" and "height" are understood, but this might be
* expanded in the future.
* Returns false if unknown or if the document is not multi-page.
+ *
+ * @param $image File
*/
function getPageDimensions( $image, $page ) {
$gis = $this->getImageSize( $image, $image->getPath() );
@@ -213,7 +293,7 @@ abstract class MediaHandler {
*/
/**
- * FIXME: I don't really like this interface, it's not very flexible
+ * @todo FIXME: I don't really like this interface, it's not very flexible
* I think the media handler should generate HTML instead. It can do
* all the formatting according to some standard. That makes it possible
* to do things like visual indication of grouped and chained streams
@@ -223,22 +303,104 @@ abstract class MediaHandler {
return false;
}
+ /** sorts the visible/invisible field.
+ * Split off from ImageHandler::formatMetadata, as used by more than
+ * one type of handler.
+ *
+ * This is used by the media handlers that use the FormatMetadata class
+ *
+ * @param $metadataArray Array metadata array
+ * @return array for use displaying metadata.
+ */
+ function formatMetadataHelper( $metadataArray ) {
+ $result = array(
+ 'visible' => array(),
+ 'collapsed' => array()
+ );
+
+ $formatted = FormatMetadata::getFormattedData( $metadataArray );
+ // Sort fields into visible and collapsed
+ $visibleFields = $this->visibleMetadataFields();
+ foreach ( $formatted as $name => $value ) {
+ $tag = strtolower( $name );
+ self::addMeta( $result,
+ in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
+ 'exif',
+ $tag,
+ $value
+ );
+ }
+ return $result;
+ }
+
+ /**
+ * Get a list of metadata items which should be displayed when
+ * the metadata table is collapsed.
+ *
+ * @return array of strings
+ * @access protected
+ */
+ function visibleMetadataFields() {
+ $fields = array();
+ $lines = explode( "\n", wfMsgForContent( 'metadata-fields' ) );
+ foreach( $lines as $line ) {
+ $matches = array();
+ if( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
+ $fields[] = $matches[1];
+ }
+ }
+ $fields = array_map( 'strtolower', $fields );
+ return $fields;
+ }
+
+
/**
- * @todo Fixme: document this!
- * 'value' thingy goes into a wikitext table; it used to be escaped but
- * that was incompatible with previous practice of customized display
+ * This is used to generate an array element for each metadata value
+ * That array is then used to generate the table of metadata values
+ * on the image page
+ *
+ * @param &$array Array An array containing elements for each type of visibility
+ * and each of those elements being an array of metadata items. This function adds
+ * a value to that array.
+ * @param $visbility string ('visible' or 'collapsed') if this value is hidden
+ * by default.
+ * @param $type String type of metadata tag (currently always 'exif')
+ * @param $id String the name of the metadata tag (like 'artist' for example).
+ * its name in the table displayed is the message "$type-$id" (Ex exif-artist ).
+ * @param $value String thingy goes into a wikitext table; it used to be escaped but
+ * that was incompatible with previous practise of customized display
* with wikitext formatting via messages such as 'exif-model-value'.
* So the escaping is taken back out, but generally this seems a confusing
* interface.
+ * @param $param String value to pass to the message for the name of the field
+ * as $1. Currently this parameter doesn't seem to ever be used.
+ *
+ * Note, everything here is passed through the parser later on (!)
*/
protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) {
+ $msgName = "$type-$id";
+ if ( wfEmptyMsg( $msgName ) ) {
+ // This is for future compatibility when using instant commons.
+ // So as to not display as ugly a name if a new metadata
+ // property is defined that we don't know about
+ // (not a major issue since such a property would be collapsed
+ // by default).
+ wfDebug( __METHOD__ . ' Unknown metadata name: ' . $id . "\n" );
+ $name = wfEscapeWikiText( $id );
+ } else {
+ $name = wfMsg( $msgName, $param );
+ }
$array[$visibility][] = array(
'id' => "$type-$id",
- 'name' => wfMsg( "$type-$id", $param ),
+ 'name' => $name,
'value' => $value
);
}
+ /**
+ * @param $file File
+ * @return string
+ */
function getShortDesc( $file ) {
global $wgLang;
$nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
@@ -246,14 +408,21 @@ abstract class MediaHandler {
return "$nbytes";
}
+ /**
+ * @param $file File
+ * @return string
+ */
function getLongDesc( $file ) {
- global $wgUser;
- $sk = $wgUser->getSkin();
+ global $wgLang;
return wfMsgExt( 'file-info', 'parseinline',
- $sk->formatSize( $file->getSize() ),
+ $wgLang->formatSize( $file->getSize() ),
$file->getMimeType() );
}
-
+
+ /**
+ * @param $file File
+ * @return string
+ */
static function getGeneralShortDesc( $file ) {
global $wgLang;
$nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
@@ -261,11 +430,14 @@ abstract class MediaHandler {
return "$nbytes";
}
+ /**
+ * @param $file File
+ * @return string
+ */
static function getGeneralLongDesc( $file ) {
- global $wgUser;
- $sk = $wgUser->getSkin();
+ global $wgLang;
return wfMsgExt( 'file-info', 'parseinline',
- $sk->formatSize( $file->getSize() ),
+ $wgLang->formatSize( $file->getSize() ),
$file->getMimeType() );
}
@@ -281,10 +453,10 @@ abstract class MediaHandler {
/**
* File validation hook called on upload.
*
- * If the file at the given local path is not valid, or its MIME type does not
+ * 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
*/
@@ -321,12 +493,13 @@ abstract class MediaHandler {
* @ingroup Media
*/
abstract class ImageHandler extends MediaHandler {
+
+ /**
+ * @param $file File
+ * @return bool
+ */
function canRender( $file ) {
- if ( $file->getWidth() && $file->getHeight() ) {
- return true;
- } else {
- return false;
- }
+ return ( $file->getWidth() && $file->getHeight() );
}
function getParamMap() {
@@ -371,6 +544,11 @@ abstract class ImageHandler extends MediaHandler {
return array( 'width' => $params['width'] );
}
+ /**
+ * @param $image File
+ * @param $params
+ * @return bool
+ */
function normaliseParams( $image, &$params ) {
$mimeType = $image->getMimeType();
@@ -392,13 +570,44 @@ abstract class ImageHandler extends MediaHandler {
$srcWidth = $image->getWidth( $params['page'] );
$srcHeight = $image->getHeight( $params['page'] );
+
if ( isset( $params['height'] ) && $params['height'] != -1 ) {
+ # Height & width were both set
if ( $params['width'] * $srcHeight > $params['height'] * $srcWidth ) {
+ # Height is the relative smaller dimension, so scale width accordingly
$params['width'] = wfFitBoxWidth( $srcWidth, $srcHeight, $params['height'] );
+
+ if ( $params['width'] == 0 ) {
+ # Very small image, so we need to rely on client side scaling :(
+ $params['width'] = 1;
+ }
+
+ $params['physicalWidth'] = $params['width'];
+ } else {
+ # Height was crap, unset it so that it will be calculated later
+ unset( $params['height'] );
}
}
- $params['height'] = File::scaleHeight( $srcWidth, $srcHeight, $params['width'] );
- if ( !$this->validateThumbParams( $params['width'], $params['height'], $srcWidth, $srcHeight, $mimeType ) ) {
+
+ if ( !isset( $params['physicalWidth'] ) ) {
+ # Passed all validations, so set the physicalWidth
+ $params['physicalWidth'] = $params['width'];
+ }
+
+ # Because thumbs are only referred to by width, the height always needs
+ # to be scaled by the width to keep the thumbnail sizes consistent,
+ # even if it was set inside the if block above
+ $params['physicalHeight'] = File::scaleHeight( $srcWidth, $srcHeight,
+ $params['physicalWidth'] );
+
+ # Set the height if it was not validated in the if block higher up
+ if ( !isset( $params['height'] ) || $params['height'] == -1 ) {
+ $params['height'] = $params['physicalHeight'];
+ }
+
+
+ if ( !$this->validateThumbParams( $params['physicalWidth'],
+ $params['physicalHeight'], $srcWidth, $srcHeight, $mimeType ) ) {
return false;
}
return true;
@@ -435,9 +644,19 @@ abstract class ImageHandler extends MediaHandler {
}
$height = File::scaleHeight( $srcWidth, $srcHeight, $width );
+ if ( $height == 0 ) {
+ # Force height to be at least 1 pixel
+ $height = 1;
+ }
return true;
}
+ /**
+ * @param $image File
+ * @param $script
+ * @param $params
+ * @return bool|ThumbnailImage
+ */
function getScriptedTransform( $image, $script, $params ) {
if ( !$this->normaliseParams( $image, $params ) ) {
return false;
@@ -461,6 +680,10 @@ abstract class ImageHandler extends MediaHandler {
return false;
}
+ /**
+ * @param $file File
+ * @return string
+ */
function getShortDesc( $file ) {
global $wgLang;
$nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
@@ -470,15 +693,34 @@ abstract class ImageHandler extends MediaHandler {
return "$widthheight ($nbytes)";
}
+ /**
+ * @param $file File
+ * @return string
+ */
function getLongDesc( $file ) {
global $wgLang;
- return wfMsgExt('file-info-size', 'parseinline',
- $wgLang->formatNum( $file->getWidth() ),
- $wgLang->formatNum( $file->getHeight() ),
- $wgLang->formatSize( $file->getSize() ),
- $file->getMimeType() );
+ $pages = $file->pageCount();
+ if ( $pages === false || $pages <= 1 ) {
+ $msg = wfMsgExt('file-info-size', 'parseinline',
+ $wgLang->formatNum( $file->getWidth() ),
+ $wgLang->formatNum( $file->getHeight() ),
+ $wgLang->formatSize( $file->getSize() ),
+ $file->getMimeType() );
+ } else {
+ $msg = wfMsgExt('file-info-size-pages', 'parseinline',
+ $wgLang->formatNum( $file->getWidth() ),
+ $wgLang->formatNum( $file->getHeight() ),
+ $wgLang->formatSize( $file->getSize() ),
+ $file->getMimeType(),
+ $wgLang->formatNum( $pages ) );
+ }
+ return $msg;
}
+ /**
+ * @param $file File
+ * @return string
+ */
function getDimensionsString( $file ) {
global $wgLang;
$pages = $file->pageCount();
diff --git a/includes/media/IPTC.php b/includes/media/IPTC.php
new file mode 100644
index 00000000..1d19791c
--- /dev/null
+++ b/includes/media/IPTC.php
@@ -0,0 +1,576 @@
+<?php
+/**
+*Class for some IPTC functions.
+
+*/
+class IPTC {
+
+ /**
+ * This takes the results of iptcparse() and puts it into a
+ * form that can be handled by mediawiki. Generally called from
+ * BitmapMetadataHandler::doApp13.
+ *
+ * @see http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
+ *
+ * @param $rawData String app13 block from jpeg containing iptc/iim data
+ * @return Array iptc metadata array
+ */
+ static function parse( $rawData ) {
+ $parsed = iptcparse( $rawData );
+ $data = Array();
+ if (!is_array($parsed)) {
+ return $data;
+ }
+
+ $c = '';
+ //charset info contained in tag 1:90.
+ if (isset($parsed['1#090']) && isset($parsed['1#090'][0])) {
+ $c = self::getCharset($parsed['1#090'][0]);
+ if ($c === false) {
+ //Unknown charset. refuse to parse.
+ //note: There is a different between
+ //unknown and no charset specified.
+ return array();
+ }
+ unset( $parsed['1#090'] );
+ }
+
+ foreach ( $parsed as $tag => $val ) {
+ if ( isset( $val[0] ) && trim($val[0]) == '' ) {
+ wfDebugLog('iptc', "IPTC tag $tag had only whitespace as its value.");
+ continue;
+ }
+ switch( $tag ) {
+ case '2#120': /*IPTC caption. mapped with exif ImageDescription*/
+ $data['ImageDescription'] = self::convIPTC( $val, $c );
+ break;
+ case '2#116': /* copyright. Mapped with exif copyright */
+ $data['Copyright'] = self::convIPTC( $val, $c );
+ break;
+ case '2#080': /* byline. Mapped with exif Artist */
+ /* merge with byline title (2:85)
+ * like how exif does it with
+ * Title, person. Not sure if this is best
+ * approach since we no longer have the two fields
+ * separate. each byline title entry corresponds to a
+ * specific byline. */
+
+ $bylines = self::convIPTC( $val, $c );
+ if ( isset( $parsed['2#085'] ) ) {
+ $titles = self::convIPTC( $parsed['2#085'], $c );
+ } else {
+ $titles = array();
+ }
+
+ for ( $i = 0; $i < count( $titles ); $i++ ) {
+ if ( isset( $bylines[$i] ) ) {
+ // theoretically this should always be set
+ // but doesn't hurt to be careful.
+ $bylines[$i] = $titles[$i] . ', ' . $bylines[$i];
+ }
+ }
+ $data['Artist'] = $bylines;
+ break;
+ case '2#025': /* keywords */
+ $data['Keywords'] = self::convIPTC( $val, $c );
+ break;
+ case '2#101': /* Country (shown)*/
+ $data['CountryDest'] = self::convIPTC( $val, $c );
+ break;
+ case '2#095': /* state/province (shown) */
+ $data['ProvinceOrStateDest'] = self::convIPTC( $val, $c );
+ break;
+ case '2#090': /* city (Shown) */
+ $data['CityDest'] = self::convIPTC( $val, $c );
+ break;
+ case '2#092': /* sublocation (shown) */
+ $data['SublocationDest'] = self::convIPTC( $val, $c );
+ break;
+ case '2#005': /* object name/title */
+ $data['ObjectName'] = self::convIPTC( $val, $c );
+ break;
+ case '2#040': /* special instructions */
+ $data['SpecialInstructions'] = self::convIPTC( $val, $c );
+ break;
+ case '2#105': /* headline*/
+ $data['Headline'] = self::convIPTC( $val, $c );
+ break;
+ case '2#110': /* credit */
+ /*"Identifies the provider of the objectdata,
+ * not necessarily the owner/creator". */
+ $data['Credit'] = self::convIPTC( $val, $c );
+ break;
+ case '2#115': /* source */
+ /* "Identifies the original owner of the intellectual content of the
+ *objectdata. This could be an agency, a member of an agency or
+ *an individual." */
+ $data['Source'] = self::convIPTC( $val, $c );
+ break;
+
+ case '2#007': /* edit status (lead, correction, etc) */
+ $data['EditStatus'] = self::convIPTC( $val, $c );
+ break;
+ case '2#015': /* category. deprecated. max 3 letters in theory, often more */
+ $data['iimCategory'] = self::convIPTC( $val, $c );
+ break;
+ case '2#020': /* category. deprecated. */
+ $data['iimSupplementalCategory'] = self::convIPTC( $val, $c );
+ break;
+ case '2#010': /*urgency (1-8. 1 most, 5 normal, 8 low priority)*/
+ $data['Urgency'] = self::convIPTC( $val, $c );
+ break;
+ case '2#022':
+ /* "Identifies objectdata that recurs often and predictably...
+ * Example: Euroweather" */
+ $data['FixtureIdentifier'] = self::convIPTC( $val, $c );
+ break;
+ case '2#026':
+ /* Content location code (iso 3166 + some custom things)
+ * ex: TUR (for turkey), XUN (for UN), XSP (outer space)
+ * See wikipedia article on iso 3166 and appendix D of iim std. */
+ $data['LocationDestCode'] = self::convIPTC( $val, $c );
+ break;
+ case '2#027':
+ /* Content location name. Full printable name
+ * of location of photo. */
+ $data['LocationDest'] = self::convIPTC( $val, $c );
+ break;
+ case '2#065':
+ /* Originating Program.
+ * Combine with Program version (2:70) if present.
+ */
+ $software = self::convIPTC( $val, $c );
+
+ if ( count( $software ) !== 1 ) {
+ //according to iim standard this cannot have multiple values
+ //so if there is more than one, something weird is happening,
+ //and we skip it.
+ wfDebugLog( 'iptc', 'IPTC: Wrong count on 2:65 Software field' );
+ break;
+ }
+
+ if ( isset( $parsed['2#070'] ) ) {
+ //if a version is set for the software.
+ $softwareVersion = self::convIPTC( $parsed['2#070'], $c );
+ unset($parsed['2#070']);
+ $data['Software'] = array( array( $software[0], $softwareVersion[0] ) );
+ } else {
+ $data['Software'] = $software;
+ }
+ break;
+ case '2#075':
+ /* Object cycle.
+ * a for morning (am), p for evening, b for both */
+ $data['ObjectCycle'] = self::convIPTC( $val, $c );
+ break;
+ case '2#100':
+ /* Country/Primary location code.
+ * "Indicates the code of the country/primary location where the
+ * intellectual property of the objectdata was created"
+ * unclear how this differs from 2#026
+ */
+ $data['CountryCodeDest'] = self::convIPTC( $val, $c );
+ break;
+ case '2#103':
+ /* original transmission ref.
+ * "A code representing the location of original transmission ac-
+ * cording to practises of the provider."
+ */
+ $data['OriginalTransmissionRef'] = self::convIPTC( $val, $c );
+ break;
+ case '2#118': /*contact*/
+ $data['Contact'] = self::convIPTC( $val, $c );
+ break;
+ case '2#122':
+ /* Writer/Editor
+ * "Identification of the name of the person involved in the writing,
+ * editing or correcting the objectdata or caption/abstract."
+ */
+ $data['Writer'] = self::convIPTC( $val, $c );
+ break;
+ case '2#135': /* lang code */
+ $data['LanguageCode'] = self::convIPTC( $val, $c );
+ break;
+
+ // Start date stuff.
+ // It doesn't accept incomplete dates even though they are valid
+ // according to spec.
+ // Should potentially store timezone as well.
+ case '2#055':
+ //Date created (not date digitized).
+ //Maps to exif DateTimeOriginal
+ if ( isset( $parsed['2#060'] ) ) {
+ $time = $parsed['2#060'];
+ } else {
+ $time = Array();
+ }
+ $timestamp = self::timeHelper( $val, $time, $c );
+ if ($timestamp) {
+ $data['DateTimeOriginal'] = $timestamp;
+ }
+ break;
+
+ case '2#062':
+ //Date converted to digital representation.
+ //Maps to exif DateTimeDigitized
+ if ( isset( $parsed['2#063'] ) ) {
+ $time = $parsed['2#063'];
+ } else {
+ $time = Array();
+ }
+ $timestamp = self::timeHelper( $val, $time, $c );
+ if ($timestamp) {
+ $data['DateTimeDigitized'] = $timestamp;
+ }
+ break;
+
+ case '2#030':
+ //Date released.
+ if ( isset( $parsed['2#035'] ) ) {
+ $time = $parsed['2#035'];
+ } else {
+ $time = Array();
+ }
+ $timestamp = self::timeHelper( $val, $time, $c );
+ if ($timestamp) {
+ $data['DateTimeReleased'] = $timestamp;
+ }
+ break;
+
+ case '2#037':
+ //Date expires.
+ if ( isset( $parsed['2#038'] ) ) {
+ $time = $parsed['2#038'];
+ } else {
+ $time = Array();
+ }
+ $timestamp = self::timeHelper( $val, $time, $c );
+ if ($timestamp) {
+ $data['DateTimeExpires'] = $timestamp;
+ }
+ break;
+
+ case '2#000': /* iim version */
+ // unlike other tags, this is a 2-byte binary number.
+ //technically this is required if there is iptc data
+ //but in practise it isn't always there.
+ if ( strlen( $val[0] ) == 2 ) {
+ //if is just to be paranoid.
+ $versionValue = ord( substr( $val[0], 0, 1 ) ) * 256;
+ $versionValue += ord( substr( $val[0], 1, 1 ) );
+ $data['iimVersion'] = $versionValue;
+ }
+ break;
+
+ case '2#004':
+ // IntellectualGenere.
+ // first 4 characters are an id code
+ // That we're not really interested in.
+
+ // This prop is weird, since it's
+ // allowed to have multiple values
+ // in iim 4.1, but not in the XMP
+ // stuff. We're going to just
+ // extract the first value.
+ $con = self::ConvIPTC( $val, $c );
+ if ( strlen( $con[0] ) < 5 ) {
+ wfDebugLog( 'iptc', 'IPTC: '
+ . '2:04 too short. '
+ . 'Ignoring.' );
+ break;
+ }
+ $extracted = substr( $con[0], 4 );
+ $data['IntellectualGenre'] = $extracted;
+ break;
+
+ case '2#012':
+ // Subject News code - this is a compound field
+ // at the moment we only extract the subject news
+ // code, which is an 8 digit (ascii) number
+ // describing the subject matter of the content.
+ $codes = self::convIPTC( $val, $c );
+ foreach ( $codes as $ic ) {
+ $fields = explode(':', $ic, 3 );
+
+ if ( count( $fields ) < 2 ||
+ $fields[0] !== 'IPTC' )
+ {
+ wfDebugLog( 'IPTC', 'IPTC: '
+ . 'Invalid 2:12 - ' . $ic );
+ break;
+ }
+ $data['SubjectNewsCode'] = $fields[1];
+ }
+ break;
+
+ // purposely does not do 2:125, 2:130, 2:131,
+ // 2:47, 2:50, 2:45, 2:42, 2:8, 2:3
+ // 2:200, 2:201, 2:202
+ // or the audio stuff (2:150 to 2:154)
+
+ case '2#070':
+ case '2#060':
+ case '2#063':
+ case '2#085':
+ case '2#038':
+ case '2#035':
+ //ignore. Handled elsewhere.
+ break;
+
+ default:
+ wfDebugLog( 'iptc', "Unsupported iptc tag: $tag. Value: " . implode( ',', $val ));
+ break;
+ }
+
+ }
+ return $data;
+ }
+
+ /**
+ * Convert an iptc date and time tags into the exif format
+ *
+ * @todo Potentially this should also capture the timezone offset.
+ * @param Array $date The date tag
+ * @param Array $time The time tag
+ * @param $c
+ * @return String Date in exif format.
+ */
+ private static function timeHelper( $date, $time, $c ) {
+ if ( count( $date ) === 1 ) {
+ //the standard says this should always be 1
+ //just double checking.
+ list($date) = self::convIPTC( $date, $c );
+ } else {
+ return null;
+ }
+
+ if ( count( $time ) === 1 ) {
+ list($time) = self::convIPTC( $time, $c );
+ $dateOnly = false;
+ } else {
+ $time = '000000+0000'; //placeholder
+ $dateOnly = true;
+ }
+
+ if ( ! ( preg_match('/\d\d\d\d\d\d[-+]\d\d\d\d/', $time)
+ && preg_match('/\d\d\d\d\d\d\d\d/', $date)
+ && substr($date, 0, 4) !== '0000'
+ && substr($date, 4, 2) !== '00'
+ && substr($date, 6, 2) !== '00'
+ ) ) {
+ //something wrong.
+ // Note, this rejects some valid dates according to iptc spec
+ // for example: the date 00000400 means the photo was taken in
+ // April, but the year and day is unknown. We don't process these
+ // types of incomplete dates atm.
+ wfDebugLog( 'iptc', "IPTC: invalid time ( $time ) or date ( $date )");
+ return null;
+ }
+
+ $unixTS = wfTimestamp( TS_UNIX, $date . substr( $time, 0, 6 ));
+ if ( $unixTS === false ) {
+ wfDebugLog( 'iptc', "IPTC: can't convert date to TS_UNIX: $date $time." );
+ return null;
+ }
+
+ $tz = ( intval( substr( $time, 7, 2 ) ) *60*60 )
+ + ( intval( substr( $time, 9, 2 ) ) * 60 );
+
+ if ( substr( $time, 6, 1 ) === '-' ) {
+ $tz = - $tz;
+ }
+
+ $finalTimestamp = wfTimestamp( TS_EXIF, $unixTS + $tz );
+ if ( $finalTimestamp === false ) {
+ wfDebugLog( 'iptc', "IPTC: can't make final timestamp. Date: " . ( $unixTS + $tz ) );
+ return null;
+ }
+ if ( $dateOnly ) {
+ //return the date only
+ return substr( $finalTimestamp, 0, 10 );
+ } else {
+ return $finalTimestamp;
+ }
+ }
+
+ /**
+ * Helper function to convert charset for iptc values.
+ * @param $data Mixed String or Array: The iptc string
+ * @param $charset String: The charset
+ *
+ * @return string
+ */
+ private static function convIPTC ( $data, $charset ) {
+ if ( is_array( $data ) ) {
+ foreach ($data as &$val) {
+ $val = self::convIPTCHelper( $val, $charset );
+ }
+ } else {
+ $data = self::convIPTCHelper( $data, $charset );
+ }
+
+ return $data;
+ }
+ /**
+ * Helper function of a helper function to convert charset for iptc values.
+ * @param $data Mixed String or Array: The iptc string
+ * @param $charset String: The charset
+ *
+ * @return string
+ */
+ private static function convIPTCHelper ( $data, $charset ) {
+ if ( $charset ) {
+ wfSuppressWarnings();
+ $data = iconv($charset, "UTF-8//IGNORE", $data);
+ wfRestoreWarnings();
+ if ($data === false) {
+ $data = "";
+ wfDebugLog('iptc', __METHOD__ . " Error converting iptc data charset $charset to utf-8");
+ }
+ } else {
+ //treat as utf-8 if is valid utf-8. otherwise pretend its windows-1252
+ // most of the time if there is no 1:90 tag, it is either ascii, latin1, or utf-8
+ $oldData = $data;
+ UtfNormal::quickIsNFCVerify( $data ); //make $data valid utf-8
+ if ($data === $oldData) {
+ return $data; //if validation didn't change $data
+ } else {
+ return self::convIPTCHelper( $oldData, 'Windows-1252' );
+ }
+ }
+ return trim( $data );
+ }
+
+ /**
+ * take the value of 1:90 tag and returns a charset
+ * @param String $tag 1:90 tag.
+ * @return string charset name or "?"
+ * Warning, this function does not (and is not intended to) detect
+ * all iso 2022 escape codes. In practise, the code for utf-8 is the
+ * only code that seems to have wide use. It does detect that code.
+ */
+ static function getCharset($tag) {
+
+ //According to iim standard, charset is defined by the tag 1:90.
+ //in which there are iso 2022 escape sequences to specify the character set.
+ //the iim standard seems to encourage that all necessary escape sequences are
+ //in the 1:90 tag, but says it doesn't have to be.
+
+ //This is in need of more testing probably. This is definitely not complete.
+ //however reading the docs of some other iptc software, it appears that most iptc software
+ //only recognizes utf-8. If 1:90 tag is not present content is
+ // usually ascii or iso-8859-1 (and sometimes utf-8), but no guarantee.
+
+ //This also won't work if there are more than one escape sequence in the 1:90 tag
+ //or if something is put in the G2, or G3 charsets, etc. It will only reliably recognize utf-8.
+
+ // This is just going through the charsets mentioned in appendix C of the iim standard.
+
+ // \x1b = ESC.
+ switch ( $tag ) {
+ case "\x1b%G": //utf-8
+ //Also call things that are compatible with utf-8, utf-8 (e.g. ascii)
+ case "\x1b(B": // ascii
+ case "\x1b(@": // iso-646-IRV (ascii in latest version, $ different in older version)
+ $c = 'UTF-8';
+ break;
+ case "\x1b(A": //like ascii, but british.
+ $c = 'ISO646-GB';
+ break;
+ case "\x1b(C": //some obscure sweedish/finland encoding
+ $c = 'ISO-IR-8-1';
+ break;
+ case "\x1b(D":
+ $c = 'ISO-IR-8-2';
+ break;
+ case "\x1b(E": //some obscure danish/norway encoding
+ $c = 'ISO-IR-9-1';
+ break;
+ case "\x1b(F":
+ $c = 'ISO-IR-9-2';
+ break;
+ case "\x1b(G":
+ $c = 'SEN_850200_B'; // aka iso 646-SE; ascii-like
+ break;
+ case "\x1b(I":
+ $c = "ISO646-IT";
+ break;
+ case "\x1b(L":
+ $c = "ISO646-PT";
+ break;
+ case "\x1b(Z":
+ $c = "ISO646-ES";
+ break;
+ case "\x1b([":
+ $c = "GREEK7-OLD";
+ break;
+ case "\x1b(K":
+ $c = "ISO646-DE";
+ break;
+ case "\x1b(N": //crylic
+ $c = "ISO_5427";
+ break;
+ case "\x1b(`": //iso646-NO
+ $c = "NS_4551-1";
+ break;
+ case "\x1b(f": //iso646-FR
+ $c = "NF_Z_62-010";
+ break;
+ case "\x1b(g":
+ $c = "PT2"; //iso646-PT2
+ break;
+ case "\x1b(h":
+ $c = "ES2";
+ break;
+ case "\x1b(i": //iso646-HU
+ $c = "MSZ_7795.3";
+ break;
+ case "\x1b(w":
+ $c = "CSA_Z243.4-1985-1";
+ break;
+ case "\x1b(x":
+ $c = "CSA_Z243.4-1985-2";
+ break;
+ case "\x1b\$(B":
+ case "\x1b\$B":
+ case "\x1b&@\x1b\$B":
+ case "\x1b&@\x1b\$(B":
+ $c = "JIS_C6226-1983";
+ break;
+ case "\x1b-A": // iso-8859-1. at least for the high code characters.
+ case "\x1b(@\x1b-A":
+ case "\x1b(B\x1b-A":
+ $c = 'ISO-8859-1';
+ break;
+ case "\x1b-B": // iso-8859-2. at least for the high code characters.
+ $c = 'ISO-8859-2';
+ break;
+ case "\x1b-C": // iso-8859-3. at least for the high code characters.
+ $c = 'ISO-8859-3';
+ break;
+ case "\x1b-D": // iso-8859-4. at least for the high code characters.
+ $c = 'ISO-8859-4';
+ break;
+ case "\x1b-E": // iso-8859-5. at least for the high code characters.
+ $c = 'ISO-8859-5';
+ break;
+ case "\x1b-F": // iso-8859-6. at least for the high code characters.
+ $c = 'ISO-8859-6';
+ break;
+ case "\x1b-G": // iso-8859-7. at least for the high code characters.
+ $c = 'ISO-8859-7';
+ break;
+ case "\x1b-H": // iso-8859-8. at least for the high code characters.
+ $c = 'ISO-8859-8';
+ break;
+ case "\x1b-I": // CSN_369103. at least for the high code characters.
+ $c = 'CSN_369103';
+ break;
+ default:
+ wfDebugLog('iptc', __METHOD__ . 'Unknown charset in iptc 1:90: ' . bin2hex( $tag ) );
+ //at this point just give up and refuse to parse iptc?
+ $c = false;
+ }
+ return $c;
+ }
+}
diff --git a/includes/media/Jpeg.php b/includes/media/Jpeg.php
new file mode 100644
index 00000000..7033409b
--- /dev/null
+++ b/includes/media/Jpeg.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * JPEG specific handler.
+ * Inherits most stuff from BitmapHandler, just here to do the metadata handler differently.
+ *
+ * Metadata stuff common to Jpeg and built-in Tiff (not PagedTiffHandler) is
+ * in ExifBitmapHandler.
+ *
+ * @ingroup Media
+ */
+class JpegHandler extends ExifBitmapHandler {
+
+ function getMetadata ( $image, $filename ) {
+ try {
+ $meta = BitmapMetadataHandler::Jpeg( $filename );
+ if ( !is_array( $meta ) ) {
+ // This should never happen, but doesn't hurt to be paranoid.
+ throw new MWException('Metadata array is not an array');
+ }
+ $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+ return serialize( $meta );
+ }
+ catch ( MWException $e ) {
+ // BitmapMetadataHandler throws an exception in certain exceptional cases like if file does not exist.
+ wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+
+ /* This used to use 0 (ExifBitmapHandler::OLD_BROKEN_FILE) for the cases
+ * * No metadata in the file
+ * * Something is broken in the file.
+ * However, if the metadata support gets expanded then you can't tell if the 0 is from
+ * a broken file, or just no props found. A broken file is likely to stay broken, but
+ * a file which had no props could have props once the metadata support is improved.
+ * Thus switch to using -1 to denote only a broken file, and use an array with only
+ * MEDIAWIKI_EXIF_VERSION to denote no props.
+ */
+ return ExifBitmapHandler::BROKEN_FILE;
+ }
+ }
+
+}
+
diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php
new file mode 100644
index 00000000..4769bf8e
--- /dev/null
+++ b/includes/media/JpegMetadataExtractor.php
@@ -0,0 +1,252 @@
+<?php
+/**
+* Class for reading jpegs and extracting metadata.
+* see also BitmapMetadataHandler.
+*
+* Based somewhat on GIFMetadataExtrator.
+*/
+class JpegMetadataExtractor {
+
+ const MAX_JPEG_SEGMENTS = 200;
+ // the max segment is a sanity check.
+ // A jpeg file should never even remotely have
+ // that many segments. Your average file has about 10.
+
+ /** Function to extract metadata segments of interest from jpeg files
+ * based on GIFMetadataExtractor.
+ *
+ * we can almost use getimagesize to do this
+ * but gis doesn't support having multiple app1 segments
+ * and those can't extract xmp on files containing both exif and xmp data
+ *
+ * @param String $filename name of jpeg file
+ * @return Array of interesting segments.
+ * @throws MWException if given invalid file.
+ */
+ static function segmentSplitter ( $filename ) {
+ $showXMP = function_exists( 'xml_parser_create_ns' );
+
+ $segmentCount = 0;
+
+ $segments = array(
+ 'XMP_ext' => array(),
+ 'COM' => array(),
+ );
+
+ if ( !$filename ) {
+ throw new MWException( "No filename specified for " . __METHOD__ );
+ }
+ if ( !file_exists( $filename ) || is_dir( $filename ) ) {
+ throw new MWException( "Invalid file $filename passed to " . __METHOD__ );
+ }
+
+ $fh = fopen( $filename, "rb" );
+
+ if ( !$fh ) {
+ throw new MWException( "Could not open file $filename" );
+ }
+
+ $buffer = fread( $fh, 2 );
+ if ( $buffer !== "\xFF\xD8" ) {
+ throw new MWException( "Not a jpeg, no SOI" );
+ }
+ while ( !feof( $fh ) ) {
+ $buffer = fread( $fh, 1 );
+ $segmentCount++;
+ if ( $segmentCount > self::MAX_JPEG_SEGMENTS ) {
+ // this is just a sanity check
+ throw new MWException( 'Too many jpeg segments. Aborting' );
+ }
+ if ( $buffer !== "\xFF" ) {
+ throw new MWException( "Error reading jpeg file marker. Expected 0xFF but got " . bin2hex( $buffer ) );
+ }
+
+ $buffer = fread( $fh, 1 );
+ while( $buffer === "\xFF" && !feof( $fh ) ) {
+ // Skip through any 0xFF padding bytes.
+ $buffer = fread( $fh, 1 );
+ }
+ if ( $buffer === "\xFE" ) {
+
+ // COM section -- file comment
+ // First see if valid utf-8,
+ // if not try to convert it to windows-1252.
+ $com = $oldCom = trim( self::jpegExtractMarker( $fh ) );
+ UtfNormal::quickIsNFCVerify( $com );
+ // turns $com to valid utf-8.
+ // thus if no change, its utf-8, otherwise its something else.
+ if ( $com !== $oldCom ) {
+ wfSuppressWarnings();
+ $com = $oldCom = iconv( 'windows-1252', 'UTF-8//IGNORE', $oldCom );
+ wfRestoreWarnings();
+ }
+ // Try it again, if its still not a valid string, then probably
+ // binary junk or some really weird encoding, so don't extract.
+ UtfNormal::quickIsNFCVerify( $com );
+ if ( $com === $oldCom ) {
+ $segments["COM"][] = $oldCom;
+ } else {
+ wfDebug( __METHOD__ . ' Ignoring JPEG comment as is garbage.' );
+ }
+
+ } elseif ( $buffer === "\xE1" ) {
+ // APP1 section (Exif, XMP, and XMP extended)
+ // only extract if XMP is enabled.
+ $temp = self::jpegExtractMarker( $fh );
+ // check what type of app segment this is.
+ if ( substr( $temp, 0, 29 ) === "http://ns.adobe.com/xap/1.0/\x00" && $showXMP ) {
+ $segments["XMP"] = substr( $temp, 29 );
+ } elseif ( substr( $temp, 0, 35 ) === "http://ns.adobe.com/xmp/extension/\x00" && $showXMP ) {
+ $segments["XMP_ext"][] = substr( $temp, 35 );
+ } elseif ( substr( $temp, 0, 29 ) === "XMP\x00://ns.adobe.com/xap/1.0/\x00" && $showXMP ) {
+ // Some images (especially flickr images) seem to have this.
+ // I really have no idea what the deal is with them, but
+ // whatever...
+ $segments["XMP"] = substr( $temp, 29 );
+ wfDebug( __METHOD__ . ' Found XMP section with wrong app identifier '
+ . "Using anyways.\n" );
+ } elseif ( substr( $temp, 0, 6 ) === "Exif\0\0" ) {
+ // Just need to find out what the byte order is.
+ // because php's exif plugin sucks...
+ // This is a II for little Endian, MM for big. Not a unicode BOM.
+ $byteOrderMarker = substr( $temp, 6, 2 );
+ if ( $byteOrderMarker === 'MM' ) {
+ $segments['byteOrder'] = 'BE';
+ } elseif ( $byteOrderMarker === 'II' ) {
+ $segments['byteOrder'] = 'LE';
+ } else {
+ wfDebug( __METHOD__ . ' Invalid byte ordering?!' );
+ }
+ }
+ } elseif ( $buffer === "\xED" ) {
+ // APP13 - PSIR. IPTC and some photoshop stuff
+ $temp = self::jpegExtractMarker( $fh );
+ if ( substr( $temp, 0, 14 ) === "Photoshop 3.0\x00" ) {
+ $segments["PSIR"] = $temp;
+ }
+ } elseif ( $buffer === "\xD9" || $buffer === "\xDA" ) {
+ // EOI - end of image or SOS - start of scan. either way we're past any interesting segments
+ return $segments;
+ } else {
+ // segment we don't care about, so skip
+ $size = wfUnpack( "nint", fread( $fh, 2 ), 2 );
+ if ( $size['int'] <= 2 ) throw new MWException( "invalid marker size in jpeg" );
+ fseek( $fh, $size['int'] - 2, SEEK_CUR );
+ }
+
+ }
+ // shouldn't get here.
+ throw new MWException( "Reached end of jpeg file unexpectedly" );
+ }
+
+ /**
+ * Helper function for jpegSegmentSplitter
+ * @param &$fh FileHandle for jpeg file
+ * @return data content of segment.
+ */
+ private static function jpegExtractMarker( &$fh ) {
+ $size = wfUnpack( "nint", fread( $fh, 2 ), 2 );
+ if ( $size['int'] <= 2 ) throw new MWException( "invalid marker size in jpeg" );
+ $segment = fread( $fh, $size['int'] - 2 );
+ if ( strlen( $segment ) !== $size['int'] - 2 ) throw new MWException( "Segment shorter than expected" );
+ return $segment;
+ }
+
+ /**
+ * This reads the photoshop image resource.
+ * Currently it only compares the iptc/iim hash
+ * with the stored hash, which is used to determine the precedence
+ * of the iptc data. In future it may extract some other info, like
+ * url of copyright license.
+ *
+ * This should generally be called by BitmapMetadataHandler::doApp13()
+ *
+ * @param String $app13 photoshop psir app13 block from jpg.
+ * @return String if the iptc hash is good or not.
+ */
+ public static function doPSIR ( $app13 ) {
+ if ( !$app13 ) {
+ return;
+ }
+ // First compare hash with real thing
+ // 0x404 contains IPTC, 0x425 has hash
+ // This is used to determine if the iptc is newer than
+ // the xmp data, as xmp programs update the hash,
+ // where non-xmp programs don't.
+
+ $offset = 14; // skip past PHOTOSHOP 3.0 identifier. should already be checked.
+ $appLen = strlen( $app13 );
+ $realHash = "";
+ $recordedHash = "";
+
+ // the +12 is the length of an empty item.
+ while ( $offset + 12 <= $appLen ) {
+ $valid = true;
+ if ( substr( $app13, $offset, 4 ) !== '8BIM' ) {
+ // its supposed to be 8BIM
+ // but apparently sometimes isn't esp. in
+ // really old jpg's
+ $valid = false;
+ }
+ $offset += 4;
+ $id = substr( $app13, $offset, 2 );
+ // id is a 2 byte id number which identifies
+ // the piece of info this record contains.
+
+ $offset += 2;
+
+ // some record types can contain a name, which
+ // is a pascal string 0-padded to be an even
+ // number of bytes. Most times (and any time
+ // we care) this is empty, making it two null bytes.
+
+ $lenName = ord( substr( $app13, $offset, 1 ) ) + 1;
+ // we never use the name so skip it. +1 for length byte
+ if ( $lenName % 2 == 1 ) {
+ $lenName++;
+ } // pad to even.
+ $offset += $lenName;
+
+ // now length of data (unsigned long big endian)
+ $lenData = wfUnpack( 'Nlen', substr( $app13, $offset, 4 ), 4 );
+ // PHP can take issue with very large unsigned ints and make them negative.
+ // Which should never ever happen, as this has to be inside a segment
+ // which is limited to a 16 bit number.
+ if ( $lenData['len'] < 0 ) throw new MWException( "Too big PSIR (" . $lenData['len'] . ')' );
+
+ $offset += 4; // 4bytes length field;
+
+ // this should not happen, but check.
+ if ( $lenData['len'] + $offset > $appLen ) {
+ wfDebug( __METHOD__ . " PSIR data too long.\n" );
+ return 'iptc-no-hash';
+ }
+
+ if ( $valid ) {
+ switch ( $id ) {
+ case "\x04\x04":
+ // IPTC block
+ $realHash = md5( substr( $app13, $offset, $lenData['len'] ), true );
+ break;
+ case "\x04\x25":
+ $recordedHash = substr( $app13, $offset, $lenData['len'] );
+ break;
+ }
+ }
+
+ // if odd, add 1 to length to account for
+ // null pad byte.
+ if ( $lenData['len'] % 2 == 1 ) $lenData['len']++;
+ $offset += $lenData['len'];
+
+ }
+
+ if ( !$realHash || !$recordedHash ) {
+ return 'iptc-no-hash';
+ } elseif ( $realHash === $recordedHash ) {
+ return 'iptc-good-hash';
+ } else { /*$realHash !== $recordedHash */
+ return 'iptc-bad-hash';
+ }
+ }
+}
diff --git a/includes/media/MediaTransformOutput.php b/includes/media/MediaTransformOutput.php
index c441f06c..f170bb9d 100644
--- a/includes/media/MediaTransformOutput.php
+++ b/includes/media/MediaTransformOutput.php
@@ -12,7 +12,12 @@
* @ingroup Media
*/
abstract class MediaTransformOutput {
- var $file, $width, $height, $url, $page, $path;
+ /**
+ * @var File
+ */
+ var $file;
+
+ var $width, $height, $url, $page, $path;
/**
* Get the width of the output box
@@ -45,7 +50,7 @@ abstract class MediaTransformOutput {
/**
* Fetch HTML for this transform output
*
- * @param $options Associative array of options. Boolean options
+ * @param $options array Associative array of options. Boolean options
* should be indicated with a value of true for true, and false or
* absent for false.
*
@@ -73,6 +78,11 @@ abstract class MediaTransformOutput {
/**
* Wrap some XHTML text in an anchor tag with the given attributes
+ *
+ * @param $linkAttribs array
+ * @param $contents string
+ *
+ * @return string
*/
protected function linkWrap( $linkAttribs, $contents ) {
if ( $linkAttribs ) {
@@ -82,6 +92,11 @@ abstract class MediaTransformOutput {
}
}
+ /**
+ * @param $title string
+ * @param $params array
+ * @return array
+ */
function getDescLinkAttribs( $title = null, $params = '' ) {
$query = $this->page ? ( 'page=' . urlencode( $this->page ) ) : '';
if( $params ) {
@@ -98,7 +113,6 @@ abstract class MediaTransformOutput {
}
}
-
/**
* Media transform output for images
*
@@ -131,7 +145,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 $options Associative array of options. Boolean options
+ * @param $options array Associative array of options. Boolean options
* should be indicated with a value of true for true, and false or
* absent for false.
*
@@ -212,8 +226,8 @@ class MediaTransformError extends MediaTransformOutput {
$htmlArgs = array_map( 'htmlspecialchars', $args );
$htmlArgs = array_map( 'nl2br', $htmlArgs );
- $this->htmlMsg = wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $msg, true ) ), $htmlArgs );
- $this->textMsg = wfMsgReal( $msg, $args );
+ $this->htmlMsg = wfMessage( $msg )->rawParams( $htmlArgs )->escaped();
+ $this->textMsg = wfMessage( $msg )->rawParams( $htmlArgs )->text();
$this->width = intval( $width );
$this->height = intval( $height );
$this->url = false;
diff --git a/includes/media/PNG.php b/includes/media/PNG.php
index 5197282c..8fe9ecb4 100644
--- a/includes/media/PNG.php
+++ b/includes/media/PNG.php
@@ -12,26 +12,51 @@
* @ingroup Media
*/
class PNGHandler extends BitmapHandler {
-
+
+ const BROKEN_FILE = '0';
+
+ /**
+ * @param File $image
+ * @param string $filename
+ * @return string
+ */
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';
- }
+ try {
+ $metadata = BitmapMetadataHandler::PNG( $filename );
+ } catch( Exception $e ) {
+ // Broken file?
+ wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+ return self::BROKEN_FILE;
}
- return serialize($image->parsedPNGMetadata);
-
+ return serialize($metadata);
}
-
+
+ /**
+ * @param $image File
+ * @return array|bool
+ */
function formatMetadata( $image ) {
- return false;
+ $meta = $image->getMetadata();
+
+ if ( !$meta ) {
+ return false;
+ }
+ $meta = unserialize( $meta );
+ if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
+ return false;
+ }
+
+ if ( isset( $meta['metadata']['_MW_PNG_VERSION'] ) ) {
+ unset( $meta['metadata']['_MW_PNG_VERSION'] );
+ }
+ return $this->formatMetadataHelper( $meta['metadata'] );
}
-
+
+ /**
+ * @param $image File
+ * @return bool
+ */
function isAnimatedImage( $image ) {
$ser = $image->getMetadata();
if ($ser) {
@@ -46,11 +71,33 @@ class PNGHandler extends BitmapHandler {
}
function isMetadataValid( $image, $metadata ) {
+
+ if ( $metadata === self::BROKEN_FILE ) {
+ // Do not repetitivly regenerate metadata on broken file.
+ return self::METADATA_GOOD;
+ }
+
wfSuppressWarnings();
$data = unserialize( $metadata );
wfRestoreWarnings();
- return (boolean) $data;
+
+ if ( !$data || !is_array( $data ) ) {
+ wfDebug(__METHOD__ . ' invalid png metadata' );
+ return self::METADATA_BAD;
+ }
+
+ if ( !isset( $data['metadata']['_MW_PNG_VERSION'] )
+ || $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION ) {
+ wfDebug(__METHOD__ . ' old but compatible png metadata' );
+ return self::METADATA_COMPATIBLE;
+ }
+ return self::METADATA_GOOD;
}
+
+ /**
+ * @param $image File
+ * @return string
+ */
function getLongDesc( $image ) {
global $wgLang;
$original = parent::getLongDesc( $image );
@@ -65,16 +112,19 @@ class PNGHandler extends BitmapHandler {
$info = array();
$info[] = $original;
- if ($metadata['loopCount'] == 0)
+ if ( $metadata['loopCount'] == 0 ) {
$info[] = wfMsgExt( 'file-info-png-looped', 'parseinline' );
- elseif ($metadata['loopCount'] > 1)
+ } elseif ( $metadata['loopCount'] > 1 ) {
$info[] = wfMsgExt( 'file-info-png-repeat', 'parseinline', $metadata['loopCount'] );
+ }
- if ($metadata['frameCount'] > 0)
+ if ( $metadata['frameCount'] > 0 ) {
$info[] = wfMsgExt( 'file-info-png-frames', 'parseinline', $metadata['frameCount'] );
+ }
- if ($metadata['duration'])
+ if ( $metadata['duration'] ) {
$info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
+ }
return $wgLang->commaList( $info );
}
diff --git a/includes/media/PNGMetadataExtractor.php b/includes/media/PNGMetadataExtractor.php
index 6a931e6c..d3c44d4f 100644
--- a/includes/media/PNGMetadataExtractor.php
+++ b/includes/media/PNGMetadataExtractor.php
@@ -1,6 +1,6 @@
<?php
/**
- * PNG frame counter.
+ * PNG frame counter and metadata extractor.
* Slightly derived from GIFMetadataExtractor.php
* Deliberately not using MWExceptions to avoid external dependencies, encouraging
* redistribution.
@@ -17,26 +17,61 @@
class PNGMetadataExtractor {
static $png_sig;
static $CRC_size;
+ static $text_chunks;
+
+ const VERSION = 1;
+ const MAX_CHUNK_SIZE = 3145728; // 3 megabytes
static function getMetadata( $filename ) {
self::$png_sig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
self::$CRC_size = 4;
-
+ /* based on list at http://owl.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData
+ * and http://www.w3.org/TR/PNG/#11keywords
+ */
+ self::$text_chunks = array(
+ 'xml:com.adobe.xmp' => 'xmp',
+ # Artist is unofficial. Author is the recommended
+ # keyword in the PNG spec. However some people output
+ # Artist so support both.
+ 'artist' => 'Artist',
+ 'model' => 'Model',
+ 'make' => 'Make',
+ 'author' => 'Artist',
+ 'comment' => 'PNGFileComment',
+ 'description' => 'ImageDescription',
+ 'title' => 'ObjectName',
+ 'copyright' => 'Copyright',
+ # Source as in original device used to make image
+ # not as in who gave you the image
+ 'source' => 'Model',
+ 'software' => 'Software',
+ 'disclaimer' => 'Disclaimer',
+ 'warning' => 'ContentWarning',
+ 'url' => 'Identifier', # Not sure if this is best mapping. Maybe WebStatement.
+ 'label' => 'Label',
+ 'creation time' => 'DateTimeDigitized',
+ /* Other potentially useful things - Document */
+ );
+
$frameCount = 0;
$loopCount = 1;
+ $text = array();
$duration = 0.0;
+ $bitDepth = 0;
+ $colorType = 'unknown';
- if (!$filename)
+ if ( !$filename ) {
throw new Exception( __METHOD__ . ": No file name specified" );
- elseif ( !file_exists($filename) || is_dir($filename) )
+ } elseif ( !file_exists( $filename ) || is_dir( $filename ) ) {
throw new Exception( __METHOD__ . ": File $filename does not exist" );
-
- $fh = fopen( $filename, 'r' );
-
- if (!$fh) {
+ }
+
+ $fh = fopen( $filename, 'rb' );
+
+ 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 ) {
@@ -44,22 +79,54 @@ class PNGMetadataExtractor {
}
// Read chunks
- while( !feof( $fh ) ) {
+ while ( !feof( $fh ) ) {
$buf = fread( $fh, 4 );
- if( !$buf ) {
+ if ( !$buf || strlen( $buf ) < 4 ) {
throw new Exception( __METHOD__ . ": Read error" );
}
- $chunk_size = unpack( "N", $buf);
+ $chunk_size = unpack( "N", $buf );
$chunk_size = $chunk_size[1];
+ if ( $chunk_size < 0 ) {
+ throw new Exception( __METHOD__ . ": Chunk size too big for unpack" );
+ }
+
$chunk_type = fread( $fh, 4 );
- if( !$chunk_type ) {
+ if ( !$chunk_type || strlen( $chunk_type ) < 4 ) {
throw new Exception( __METHOD__ . ": Read error" );
}
- if ( $chunk_type == "acTL" ) {
+ if ( $chunk_type == "IHDR" ) {
+ $buf = self::read( $fh, $chunk_size );
+ if ( !$buf || strlen( $buf ) < $chunk_size ) {
+ throw new Exception( __METHOD__ . ": Read error" );
+ }
+ $bitDepth = ord( substr( $buf, 8, 1 ) );
+ // Detect the color type in British English as per the spec
+ // http://www.w3.org/TR/PNG/#11IHDR
+ switch ( ord( substr( $buf, 9, 1 ) ) ) {
+ case 0:
+ $colorType = 'greyscale';
+ break;
+ case 2:
+ $colorType = 'truecolour';
+ break;
+ case 3:
+ $colorType = 'index-coloured';
+ break;
+ case 4:
+ $colorType = 'greyscale-alpha';
+ break;
+ case 6:
+ $colorType = 'truecolour-alpha';
+ break;
+ default:
+ $colorType = 'unknown';
+ break;
+ }
+ } elseif ( $chunk_type == "acTL" ) {
$buf = fread( $fh, $chunk_size );
- if( !$buf ) {
+ if( !$buf || strlen( $buf ) < $chunk_size || $chunk_size < 4 ) {
throw new Exception( __METHOD__ . ": Read error" );
}
@@ -67,20 +134,216 @@ class PNGMetadataExtractor {
$frameCount = $actl['frames'];
$loopCount = $actl['plays'];
} elseif ( $chunk_type == "fcTL" ) {
- $buf = fread( $fh, $chunk_size );
- if( !$buf ) {
+ $buf = self::read( $fh, $chunk_size );
+ if ( !$buf || strlen( $buf ) < $chunk_size ) {
+ throw new Exception( __METHOD__ . ": Read error" );
+ }
+ $buf = substr( $buf, 20 );
+ if ( strlen( $buf ) < 4 ) {
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'] ) {
+ 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 == "iTXt" ) {
+ // Extracts iTXt chunks, uncompressing if necessary.
+ $buf = self::read( $fh, $chunk_size );
+ $items = array();
+ if ( preg_match(
+ '/^([^\x00]{1,79})\x00(\x00|\x01)\x00([^\x00]*)(.)[^\x00]*\x00(.*)$/Ds',
+ $buf, $items )
+ ) {
+ /* $items[1] = text chunk name, $items[2] = compressed flag,
+ * $items[3] = lang code (or ""), $items[4]= compression type.
+ * $items[5] = content
+ */
+
+ // Theoretically should be case-sensitive, but in practise...
+ $items[1] = strtolower( $items[1] );
+ if ( !isset( self::$text_chunks[$items[1]] ) ) {
+ // Only extract textual chunks on our list.
+ fseek( $fh, self::$CRC_size, SEEK_CUR );
+ continue;
+ }
+
+ $items[3] = strtolower( $items[3] );
+ if ( $items[3] == '' ) {
+ // if no lang specified use x-default like in xmp.
+ $items[3] = 'x-default';
+ }
+
+ // if compressed
+ if ( $items[2] == "\x01" ) {
+ if ( function_exists( 'gzuncompress' ) && $items[4] === "\x00" ) {
+ wfSuppressWarnings();
+ $items[5] = gzuncompress( $items[5] );
+ wfRestoreWarnings();
+
+ if ( $items[5] === false ) {
+ // decompression failed
+ wfDebug( __METHOD__ . ' Error decompressing iTxt chunk - ' . $items[1] );
+ fseek( $fh, self::$CRC_size, SEEK_CUR );
+ continue;
+ }
+
+ } else {
+ wfDebug( __METHOD__ . ' Skipping compressed png iTXt chunk due to lack of zlib,'
+ . ' or potentially invalid compression method' );
+ fseek( $fh, self::$CRC_size, SEEK_CUR );
+ continue;
+ }
+ }
+ $finalKeyword = self::$text_chunks[ $items[1] ];
+ $text[ $finalKeyword ][ $items[3] ] = $items[5];
+ $text[ $finalKeyword ]['_type'] = 'lang';
+
+ } else {
+ // Error reading iTXt chunk
+ throw new Exception( __METHOD__ . ": Read error on iTXt chunk" );
+ }
+
+ } elseif ( $chunk_type == 'tEXt' ) {
+ $buf = self::read( $fh, $chunk_size );
+
+ // In case there is no \x00 which will make explode fail.
+ if ( strpos( $buf, "\x00" ) === false ) {
+ throw new Exception( __METHOD__ . ": Read error on tEXt chunk" );
+ }
+
+ list( $keyword, $content ) = explode( "\x00", $buf, 2 );
+ if ( $keyword === '' || $content === '' ) {
+ throw new Exception( __METHOD__ . ": Read error on tEXt chunk" );
+ }
+
+ // Theoretically should be case-sensitive, but in practise...
+ $keyword = strtolower( $keyword );
+ if ( !isset( self::$text_chunks[ $keyword ] ) ) {
+ // Don't recognize chunk, so skip.
+ fseek( $fh, self::$CRC_size, SEEK_CUR );
+ continue;
+ }
+ wfSuppressWarnings();
+ $content = iconv( 'ISO-8859-1', 'UTF-8', $content );
+ wfRestoreWarnings();
+
+ if ( $content === false ) {
+ throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
+ }
+
+ $finalKeyword = self::$text_chunks[ $keyword ];
+ $text[ $finalKeyword ][ 'x-default' ] = $content;
+ $text[ $finalKeyword ]['_type'] = 'lang';
+
+ } elseif ( $chunk_type == 'zTXt' ) {
+ if ( function_exists( 'gzuncompress' ) ) {
+ $buf = self::read( $fh, $chunk_size );
+
+ // In case there is no \x00 which will make explode fail.
+ if ( strpos( $buf, "\x00" ) === false ) {
+ throw new Exception( __METHOD__ . ": Read error on zTXt chunk" );
+ }
+
+ list( $keyword, $postKeyword ) = explode( "\x00", $buf, 2 );
+ if ( $keyword === '' || $postKeyword === '' ) {
+ throw new Exception( __METHOD__ . ": Read error on zTXt chunk" );
+ }
+ // Theoretically should be case-sensitive, but in practise...
+ $keyword = strtolower( $keyword );
+
+ if ( !isset( self::$text_chunks[ $keyword ] ) ) {
+ // Don't recognize chunk, so skip.
+ fseek( $fh, self::$CRC_size, SEEK_CUR );
+ continue;
+ }
+ $compression = substr( $postKeyword, 0, 1 );
+ $content = substr( $postKeyword, 1 );
+ if ( $compression !== "\x00" ) {
+ wfDebug( __METHOD__ . " Unrecognized compression method in zTXt ($keyword). Skipping." );
+ fseek( $fh, self::$CRC_size, SEEK_CUR );
+ continue;
+ }
+
+ wfSuppressWarnings();
+ $content = gzuncompress( $content );
+ wfRestoreWarnings();
+
+ if ( $content === false ) {
+ // decompression failed
+ wfDebug( __METHOD__ . ' Error decompressing zTXt chunk - ' . $keyword );
+ fseek( $fh, self::$CRC_size, SEEK_CUR );
+ continue;
+ }
+
+ wfSuppressWarnings();
+ $content = iconv( 'ISO-8859-1', 'UTF-8', $content );
+ wfRestoreWarnings();
+
+ if ( $content === false ) {
+ throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
+ }
+
+ $finalKeyword = self::$text_chunks[ $keyword ];
+ $text[ $finalKeyword ][ 'x-default' ] = $content;
+ $text[ $finalKeyword ]['_type'] = 'lang';
+
+ } else {
+ wfDebug( __METHOD__ . " Cannot decompress zTXt chunk due to lack of zlib. Skipping." );
+ fseek( $fh, $chunk_size, SEEK_CUR );
+ }
+ } elseif ( $chunk_type == 'tIME' ) {
+ // last mod timestamp.
+ if ( $chunk_size !== 7 ) {
+ throw new Exception( __METHOD__ . ": tIME wrong size" );
+ }
+ $buf = self::read( $fh, $chunk_size );
+ if ( !$buf || strlen( $buf ) < $chunk_size ) {
+ throw new Exception( __METHOD__ . ": Read error" );
+ }
+
+ // Note: spec says this should be UTC.
+ $t = unpack( "ny/Cm/Cd/Ch/Cmin/Cs", $buf );
+ $strTime = sprintf( "%04d%02d%02d%02d%02d%02d",
+ $t['y'], $t['m'], $t['d'], $t['h'],
+ $t['min'], $t['s'] );
+
+ $exifTime = wfTimestamp( TS_EXIF, $strTime );
+
+ if ( $exifTime ) {
+ $text['DateTime'] = $exifTime;
+ }
+
+ } elseif ( $chunk_type == 'pHYs' ) {
+ // how big pixels are (dots per meter).
+ if ( $chunk_size !== 9 ) {
+ throw new Exception( __METHOD__ . ": pHYs wrong size" );
+ }
+
+ $buf = self::read( $fh, $chunk_size );
+ if ( !$buf || strlen( $buf ) < $chunk_size ) {
+ throw new Exception( __METHOD__ . ": Read error" );
+ }
+
+ $dim = unpack( "Nwidth/Nheight/Cunit", $buf );
+ if ( $dim['unit'] == 1 ) {
+ // Need to check for negative because php
+ // doesn't deal with super-large unsigned 32-bit ints well
+ if ( $dim['width'] > 0 && $dim['height'] > 0 ) {
+ // unit is meters
+ // (as opposed to 0 = undefined )
+ $text['XResolution'] = $dim['width']
+ . '/100';
+ $text['YResolution'] = $dim['height']
+ . '/100';
+ $text['ResolutionUnit'] = 3;
+ // 3 = dots per cm (from Exif).
+ }
+ }
+
} elseif ( $chunk_type == "IEND" ) {
break;
} else {
@@ -90,15 +353,59 @@ class PNGMetadataExtractor {
}
fclose( $fh );
- if( $loopCount > 1 ) {
+ if ( $loopCount > 1 ) {
$duration *= $loopCount;
}
+ if ( isset( $text['DateTimeDigitized'] ) ) {
+ // Convert date format from rfc2822 to exif.
+ foreach ( $text['DateTimeDigitized'] as $name => &$value ) {
+ if ( $name === '_type' ) {
+ continue;
+ }
+
+ // @todo FIXME: Currently timezones are ignored.
+ // possibly should be wfTimestamp's
+ // responsibility. (at least for numeric TZ)
+ $formatted = wfTimestamp( TS_EXIF, $value );
+ if ( $formatted ) {
+ // Only change if we could convert the
+ // date.
+ // The png standard says it should be
+ // in rfc2822 format, but not required.
+ // In general for the exif stuff we
+ // prettify the date if we can, but we
+ // display as-is if we cannot or if
+ // it is invalid.
+ // So do the same here.
+
+ $value = $formatted;
+ }
+ }
+ }
return array(
'frameCount' => $frameCount,
'loopCount' => $loopCount,
- 'duration' => $duration
+ 'duration' => $duration,
+ 'text' => $text,
+ 'bitDepth' => $bitDepth,
+ 'colorType' => $colorType,
);
-
+
+ }
+ /**
+ * Read a chunk, checking to make sure its not too big.
+ *
+ * @param $fh resource The file handle
+ * @param $size Integer size in bytes.
+ * @throws Exception if too big.
+ * @return String The chunk.
+ */
+ static private function read( $fh, $size ) {
+ if ( $size > self::MAX_CHUNK_SIZE ) {
+ throw new Exception( __METHOD__ . ': Chunk size of ' . $size .
+ ' too big. Max size is: ' . self::MAX_CHUNK_SIZE );
+ }
+ return fread( $fh, $size );
}
}
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
index a78be952..ceffd7c3 100644
--- a/includes/media/SVG.php
+++ b/includes/media/SVG.php
@@ -32,6 +32,10 @@ class SvgHandler extends ImageHandler {
return true;
}
+ /**
+ * @param $file File
+ * @return bool
+ */
function isAnimatedImage( $file ) {
# TODO: detect animated SVGs
$metadata = $file->getMetadata();
@@ -44,14 +48,17 @@ class SvgHandler extends ImageHandler {
return false;
}
+ /**
+ * @param $image File
+ * @param $params
+ * @return bool
+ */
function normaliseParams( $image, &$params ) {
global $wgSVGMaxSize;
if ( !parent::normaliseParams( $image, $params ) ) {
return false;
}
# Don't make an image bigger than wgMaxSVGSize on the smaller side
- $params['physicalWidth'] = $params['width'];
- $params['physicalHeight'] = $params['height'];
if ( $params['physicalWidth'] <= $params['physicalHeight'] ) {
if ( $params['physicalWidth'] > $wgSVGMaxSize ) {
$srcWidth = $image->getWidth( $params['page'] );
@@ -70,6 +77,14 @@ class SvgHandler extends ImageHandler {
return true;
}
+ /**
+ * @param $image File
+ * @param $dstPath
+ * @param $dstUrl
+ * @param $params
+ * @param int $flags
+ * @return bool|MediaTransformError|ThumbnailImage|TransformParameterError
+ */
function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
if ( !$this->normaliseParams( $image, $params ) ) {
return new TransformParameterError( $params );
@@ -97,7 +112,7 @@ class SvgHandler extends ImageHandler {
}
}
- /*
+ /**
* Transform an SVG file to PNG
* This function can be called outside of thumbnail contexts
* @param string $srcPath
@@ -111,19 +126,32 @@ class SvgHandler extends ImageHandler {
$err = false;
$retval = '';
if ( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
- $cmd = str_replace(
- array( '$path/', '$width', '$height', '$input', '$output' ),
- array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
- intval( $width ),
- intval( $height ),
- wfEscapeShellArg( $srcPath ),
- wfEscapeShellArg( $dstPath ) ),
- $wgSVGConverters[$wgSVGConverter]
- ) . " 2>&1";
- wfProfileIn( 'rsvg' );
- wfDebug( __METHOD__.": $cmd\n" );
- $err = wfShellExec( $cmd, $retval );
- wfProfileOut( 'rsvg' );
+ if ( is_array( $wgSVGConverters[$wgSVGConverter] ) ) {
+ // This is a PHP callable
+ $func = $wgSVGConverters[$wgSVGConverter][0];
+ $args = array_merge( array( $srcPath, $dstPath, $width, $height ),
+ array_slice( $wgSVGConverters[$wgSVGConverter], 1 ) );
+ if ( !is_callable( $func ) ) {
+ throw new MWException( "$func is not callable" );
+ }
+ $err = call_user_func_array( $func, $args );
+ $retval = (bool)$err;
+ } else {
+ // External command
+ $cmd = str_replace(
+ array( '$path/', '$width', '$height', '$input', '$output' ),
+ array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
+ intval( $width ),
+ intval( $height ),
+ wfEscapeShellArg( $srcPath ),
+ wfEscapeShellArg( $dstPath ) ),
+ $wgSVGConverters[$wgSVGConverter]
+ ) . " 2>&1";
+ wfProfileIn( 'rsvg' );
+ wfDebug( __METHOD__.": $cmd\n" );
+ $err = wfShellExec( $cmd, $retval );
+ wfProfileOut( 'rsvg' );
+ }
}
$removed = $this->removeBadFile( $dstPath, $retval );
if ( $retval != 0 || $removed ) {
@@ -133,7 +161,27 @@ class SvgHandler extends ImageHandler {
}
return true;
}
+
+ public static function rasterizeImagickExt( $srcPath, $dstPath, $width, $height ) {
+ $im = new Imagick( $srcPath );
+ $im->setImageFormat( 'png' );
+ $im->setBackgroundColor( 'transparent' );
+ $im->setImageDepth( 8 );
+
+ if ( !$im->thumbnailImage( intval( $width ), intval( $height ), /* fit */ false ) ) {
+ return 'Could not resize image';
+ }
+ if ( !$im->writeImage( $dstPath ) ) {
+ return "Could not write to $dstPath";
+ }
+ }
+ /**
+ * @param $file File
+ * @param $path
+ * @param bool $metadata
+ * @return array
+ */
function getImageSize( $file, $path, $metadata = false ) {
if ( $metadata === false ) {
$metadata = $file->getMetaData();
@@ -150,6 +198,10 @@ class SvgHandler extends ImageHandler {
return array( 'png', 'image/png' );
}
+ /**
+ * @param $file File
+ * @return string
+ */
function getLongDesc( $file ) {
global $wgLang;
return wfMsgExt( 'svg-long-desc', 'parseinline',
@@ -171,7 +223,9 @@ class SvgHandler extends ImageHandler {
}
function unpackMetadata( $metadata ) {
- $unser = @unserialize( $metadata );
+ wfSuppressWarnings();
+ $unser = unserialize( $metadata );
+ wfRestoreWarnings();
if ( isset( $unser['version'] ) && $unser['version'] == self::SVG_METADATA_VERSION ) {
return $unser;
} else {
@@ -192,6 +246,10 @@ class SvgHandler extends ImageHandler {
return $fields;
}
+ /**
+ * @param $file File
+ * @return array|bool
+ */
function formatMetadata( $file ) {
$result = array(
'visible' => array(),
diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php
index 66ae1edf..22ef8e61 100644
--- a/includes/media/SVGMetadataExtractor.php
+++ b/includes/media/SVGMetadataExtractor.php
@@ -55,7 +55,7 @@ class SVGReader {
$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." );
@@ -84,14 +84,14 @@ class SVGReader {
wfRestoreWarnings();
}
- /*
+ /**
* @return Array with the known metadata
*/
public function getMetadata() {
return $this->metadata;
}
- /*
+ /**
* Read the SVG
*/
public function read() {
@@ -139,10 +139,12 @@ class SVGReader {
$keepReading = $this->reader->next();
}
+ $this->reader->close();
+
return true;
}
- /*
+ /**
* Read a textelement from an element
*
* @param String $name of the element that we are reading from
@@ -155,7 +157,7 @@ class SVGReader {
}
$keepReading = $this->reader->read();
while( $keepReading ) {
- if( $this->reader->localName == $name && $this->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::END_ELEMENT ) {
+ if( $this->reader->localName == $name && $this->reader->namespaceURI == self::NS_SVG && $this->reader->nodeType == XmlReader::END_ELEMENT ) {
break;
} elseif( $this->reader->nodeType == XmlReader::TEXT ){
$this->metadata[$metafield] = trim( $this->reader->value );
@@ -175,20 +177,27 @@ class SVGReader {
return;
}
// TODO: find and store type of xml snippet. metadata['metadataType'] = "rdf"
- $this->metadata[$metafield] = trim( $this->reader->readInnerXML() );
+ if( method_exists( $this->reader, 'readInnerXML' ) ) {
+ $this->metadata[$metafield] = trim( $this->reader->readInnerXML() );
+ } else {
+ throw new MWException( "The PHP XMLReader extension does not come with readInnerXML() method. Your libxml is probably out of date (need 2.6.20 or later)." );
+ }
$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" );
+ $this->debug ( "animate filter for tag $name" );
if( $this->reader->nodeType != XmlReader::ELEMENT ) {
return;
}
+ if ( $this->reader->isEmptyElement ) {
+ return;
+ }
$exitDepth = $this->reader->depth;
$keepReading = $this->reader->read();
while( $keepReading ) {
@@ -230,7 +239,7 @@ class SVGReader {
wfDebug( "SVGReader WARN: $data\n" );
}
- /*
+ /**
* Parse the attributes of an SVG element
*
* The parser has to be in the start element of <svg>
diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php
index 8773201f..0f317e1a 100644
--- a/includes/media/Tiff.php
+++ b/includes/media/Tiff.php
@@ -11,27 +11,74 @@
*
* @ingroup Media
*/
-class TiffHandler extends BitmapHandler {
+class TiffHandler extends ExifBitmapHandler {
/**
* Conversion to PNG for inline display can be disabled here...
* Note scaling should work with ImageMagick, but may not with GD scaling.
+ *
+ * Files pulled from an another MediaWiki instance via ForeignAPIRepo /
+ * InstantCommons will have thumbnails managed from the remote instance,
+ * so we can skip this check.
+ *
+ * @param $file
+ *
+ * @return bool
*/
function canRender( $file ) {
global $wgTiffThumbnailType;
- return (bool)$wgTiffThumbnailType;
+ return (bool)$wgTiffThumbnailType
+ || ($file->getRepo() instanceof ForeignAPIRepo);
}
/**
* Browsers don't support TIFF inline generally...
* For inline display, we need to convert to PNG.
+ *
+ * @param $file
+ *
+ * @return bool
*/
function mustRender( $file ) {
return true;
}
+ /**
+ * @param $ext
+ * @param $mime
+ * @param $params
+ * @return bool
+ */
function getThumbType( $ext, $mime, $params = null ) {
global $wgTiffThumbnailType;
return $wgTiffThumbnailType;
}
+
+ /**
+ * @param $image
+ * @param $filename
+ * @return string
+ */
+ function getMetadata( $image, $filename ) {
+ global $wgShowEXIF;
+ if ( $wgShowEXIF ) {
+ try {
+ $meta = BitmapMetadataHandler::Tiff( $filename );
+ if ( !is_array( $meta ) ) {
+ // This should never happen, but doesn't hurt to be paranoid.
+ throw new MWException('Metadata array is not an array');
+ }
+ $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
+ return serialize( $meta );
+ }
+ catch ( MWException $e ) {
+ // BitmapMetadataHandler throws an exception in certain exceptional
+ // cases like if file does not exist.
+ wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
+ return ExifBitmapHandler::BROKEN_FILE;
+ }
+ } else {
+ return '';
+ }
+ }
}
diff --git a/includes/media/XMP.php b/includes/media/XMP.php
new file mode 100644
index 00000000..1e578582
--- /dev/null
+++ b/includes/media/XMP.php
@@ -0,0 +1,1174 @@
+<?php
+/**
+* Class for reading xmp data containing properties relevant to
+* images, and spitting out an array that FormatExif accepts.
+*
+* Note, this is not meant to recognize every possible thing you can
+* encode in XMP. It should recognize all the properties we want.
+* For example it doesn't have support for structures with multiple
+* nesting levels, as none of the properties we're supporting use that
+* feature. If it comes across properties it doesn't recognize, it should
+* ignore them.
+*
+* The public methods one would call in this class are
+* - parse( $content )
+* Reads in xmp content.
+* Can potentially be called multiple times with partial data each time.
+* - parseExtended( $content )
+* Reads XMPExtended blocks (jpeg files only).
+* - getResults
+* Outputs a results array.
+*
+* Note XMP kind of looks like rdf. They are not the same thing - XMP is
+* encoded as a specific subset of rdf. This class can read XMP. It cannot
+* read rdf.
+*
+*/
+class XMPReader {
+
+ private $curItem = array(); // array to hold the current element (and previous element, and so on)
+ private $ancestorStruct = false; // the structure name when processing nested structures.
+ private $charContent = false; // temporary holder for character data that appears in xmp doc.
+ private $mode = array(); // stores the state the xmpreader is in (see MODE_FOO constants)
+ private $results = array(); // array to hold results
+ private $processingArray = false; // if we're doing a seq or bag.
+ private $itemLang = false; // used for lang alts only
+
+ private $xmlParser;
+ private $charset = false;
+ private $extendedXMPOffset = 0;
+
+ protected $items;
+
+ /**
+ * These are various mode constants.
+ * they are used to figure out what to do
+ * with an element when its encountered.
+ *
+ * For example, MODE_IGNORE is used when processing
+ * a property we're not interested in. So if a new
+ * element pops up when we're in that mode, we ignore it.
+ */
+ const MODE_INITIAL = 0;
+ const MODE_IGNORE = 1;
+ const MODE_LI = 2;
+ const MODE_LI_LANG = 3;
+ const MODE_QDESC = 4;
+
+ // The following MODE constants are also used in the
+ // $items array to denote what type of property the item is.
+ const MODE_SIMPLE = 10;
+ const MODE_STRUCT = 11; // structure (associative array)
+ const MODE_SEQ = 12; // ordered list
+ const MODE_BAG = 13; // unordered list
+ const MODE_LANG = 14;
+ const MODE_ALT = 15; // non-language alt. Currently not implemented, and not needed atm.
+ const MODE_BAGSTRUCT = 16; // A BAG of Structs.
+
+ const NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
+ const NS_XML = 'http://www.w3.org/XML/1998/namespace';
+
+
+ /**
+ * Constructor.
+ *
+ * Primary job is to initialize the XMLParser
+ */
+ function __construct() {
+
+ if ( !function_exists( 'xml_parser_create_ns' ) ) {
+ // this should already be checked by this point
+ throw new MWException( 'XMP support requires XML Parser' );
+ }
+
+ $this->items = XMPInfo::getItems();
+
+ $this->resetXMLParser();
+
+ }
+ /**
+ * Main use is if a single item has multiple xmp documents describing it.
+ * For example in jpeg's with extendedXMP
+ */
+ private function resetXMLParser() {
+
+ if ($this->xmlParser) {
+ //is this needed?
+ xml_parser_free( $this->xmlParser );
+ }
+
+ $this->xmlParser = xml_parser_create_ns( 'UTF-8', ' ' );
+ xml_parser_set_option( $this->xmlParser, XML_OPTION_CASE_FOLDING, 0 );
+ xml_parser_set_option( $this->xmlParser, XML_OPTION_SKIP_WHITE, 1 );
+
+ xml_set_element_handler( $this->xmlParser,
+ array( $this, 'startElement' ),
+ array( $this, 'endElement' ) );
+
+ xml_set_character_data_handler( $this->xmlParser, array( $this, 'char' ) );
+ }
+
+ /** Destroy the xml parser
+ *
+ * Not sure if this is actually needed.
+ */
+ function __destruct() {
+ // not sure if this is needed.
+ xml_parser_free( $this->xmlParser );
+ }
+
+ /** Get the result array. Do some post-processing before returning
+ * the array, and transform any metadata that is special-cased.
+ *
+ * @return Array array of results as an array of arrays suitable for
+ * FormatMetadata::getFormattedData().
+ */
+ public function getResults() {
+ // xmp-special is for metadata that affects how stuff
+ // is extracted. For example xmpNote:HasExtendedXMP.
+
+ // It is also used to handle photoshop:AuthorsPosition
+ // which is weird and really part of another property,
+ // see 2:85 in IPTC. See also pg 21 of IPTC4XMP standard.
+ // The location fields also use it.
+
+ $data = $this->results;
+
+ wfRunHooks('XMPGetResults', Array(&$data));
+
+ if ( isset( $data['xmp-special']['AuthorsPosition'] )
+ && is_string( $data['xmp-special']['AuthorsPosition'] )
+ && isset( $data['xmp-general']['Artist'][0] )
+ ) {
+ // Note, if there is more than one creator,
+ // this only applies to first. This also will
+ // only apply to the dc:Creator prop, not the
+ // exif:Artist prop.
+
+ $data['xmp-general']['Artist'][0] =
+ $data['xmp-special']['AuthorsPosition'] . ', '
+ . $data['xmp-general']['Artist'][0];
+ }
+
+ // Go through the LocationShown and LocationCreated
+ // changing it to the non-hierarchal form used by
+ // the other location fields.
+
+ if ( isset( $data['xmp-special']['LocationShown'][0] )
+ && is_array( $data['xmp-special']['LocationShown'][0] )
+ ) {
+ // the is_array is just paranoia. It should always
+ // be an array.
+ foreach( $data['xmp-special']['LocationShown'] as $loc ) {
+ if ( !is_array( $loc ) ) {
+ // To avoid copying over the _type meta-fields.
+ continue;
+ }
+ foreach( $loc as $field => $val ) {
+ $data['xmp-general'][$field . 'Dest'][] = $val;
+ }
+ }
+ }
+ if ( isset( $data['xmp-special']['LocationCreated'][0] )
+ && is_array( $data['xmp-special']['LocationCreated'][0] )
+ ) {
+ // the is_array is just paranoia. It should always
+ // be an array.
+ foreach( $data['xmp-special']['LocationCreated'] as $loc ) {
+ if ( !is_array( $loc ) ) {
+ // To avoid copying over the _type meta-fields.
+ continue;
+ }
+ foreach( $loc as $field => $val ) {
+ $data['xmp-general'][$field . 'Created'][] = $val;
+ }
+ }
+ }
+
+
+ // We don't want to return the special values, since they're
+ // special and not info to be stored about the file.
+ unset( $data['xmp-special'] );
+
+ // Convert GPSAltitude to negative if below sea level.
+ if ( isset( $data['xmp-exif']['GPSAltitudeRef'] ) ) {
+ if ( $data['xmp-exif']['GPSAltitudeRef'] == '1'
+ && isset( $data['xmp-exif']['GPSAltitude'] )
+ ) {
+ $data['xmp-exif']['GPSAltitude'] *= -1;
+ }
+ unset( $data['xmp-exif']['GPSAltitudeRef'] );
+ }
+
+ return $data;
+ }
+
+ /**
+ * Main function to call to parse XMP. Use getResults to
+ * get results.
+ *
+ * Also catches any errors during processing, writes them to
+ * debug log, blanks result array and returns false.
+ *
+ * @param String: $content XMP data
+ * @param Boolean: $allOfIt If this is all the data (true) or if its split up (false). Default true
+ * @param Boolean: $reset - does xml parser need to be reset. Default false
+ * @return Boolean success.
+ */
+ public function parse( $content, $allOfIt = true, $reset = false ) {
+ if ( $reset ) {
+ $this->resetXMLParser();
+ }
+ try {
+
+ // detect encoding by looking for BOM which is supposed to be in processing instruction.
+ // see page 12 of http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf
+ if ( !$this->charset ) {
+ $bom = array();
+ if ( preg_match( '/\xEF\xBB\xBF|\xFE\xFF|\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\xFF\xFE/',
+ $content, $bom )
+ ) {
+ switch ( $bom[0] ) {
+ case "\xFE\xFF":
+ $this->charset = 'UTF-16BE';
+ break;
+ case "\xFF\xFE":
+ $this->charset = 'UTF-16LE';
+ break;
+ case "\x00\x00\xFE\xFF":
+ $this->charset = 'UTF-32BE';
+ break;
+ case "\xFF\xFE\x00\x00":
+ $this->charset = 'UTF-32LE';
+ break;
+ case "\xEF\xBB\xBF":
+ $this->charset = 'UTF-8';
+ break;
+ default:
+ //this should be impossible to get to
+ throw new MWException("Invalid BOM");
+ break;
+
+ }
+
+ } else {
+ // standard specifically says, if no bom assume utf-8
+ $this->charset = 'UTF-8';
+ }
+ }
+ if ( $this->charset !== 'UTF-8' ) {
+ //don't convert if already utf-8
+ wfSuppressWarnings();
+ $content = iconv( $this->charset, 'UTF-8//IGNORE', $content );
+ wfRestoreWarnings();
+ }
+
+ $ok = xml_parse( $this->xmlParser, $content, $allOfIt );
+ if ( !$ok ) {
+ $error = xml_error_string( xml_get_error_code( $this->xmlParser ) );
+ $where = 'line: ' . xml_get_current_line_number( $this->xmlParser )
+ . ' column: ' . xml_get_current_column_number( $this->xmlParser )
+ . ' byte offset: ' . xml_get_current_byte_index( $this->xmlParser );
+
+ wfDebugLog( 'XMP', "XMPReader::parse : Error reading XMP content: $error ($where)" );
+ $this->results = array(); // blank if error.
+ return false;
+ }
+ } catch ( MWException $e ) {
+ wfDebugLog( 'XMP', 'XMP parse error: ' . $e );
+ $this->results = array();
+ return false;
+ }
+ return true;
+ }
+
+ /** Entry point for XMPExtended blocks in jpeg files
+ *
+ * @todo In serious need of testing
+ * @see http://www.adobe.ge/devnet/xmp/pdfs/XMPSpecificationPart3.pdf XMP spec part 3 page 20
+ * @param String $content XMPExtended block minus the namespace signature
+ * @return Boolean If it succeeded.
+ */
+ public function parseExtended( $content ) {
+ // @todo FIXME: This is untested. Hard to find example files
+ // or programs that make such files..
+ $guid = substr( $content, 0, 32 );
+ if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] )
+ || $this->results['xmp-special']['HasExtendedXMP'] !== $guid ) {
+ wfDebugLog('XMP', __METHOD__ . " Ignoring XMPExtended block due to wrong guid (guid= '$guid' )");
+ return false;
+ }
+ $len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
+
+ if (!$len || $len['length'] < 4 || $len['offset'] < 0 || $len['offset'] > $len['length'] ) {
+ wfDebugLog('XMP', __METHOD__ . 'Error reading extended XMP block, invalid length or offset.');
+ return false;
+ }
+
+
+ // we're not very robust here. we should accept it in the wrong order. To quote
+ // the xmp standard:
+ // "A JPEG writer should write the ExtendedXMP marker segments in order, immediately following the
+ // StandardXMP. However, the JPEG standard does not require preservation of marker segment order. A
+ // robust JPEG reader should tolerate the marker segments in any order."
+ //
+ // otoh the probability that an image will have more than 128k of metadata is rather low...
+ // so the probability that it will have > 128k, and be in the wrong order is very low...
+
+ if ( $len['offset'] !== $this->extendedXMPOffset ) {
+ wfDebugLog('XMP', __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
+ . $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')');
+ return false;
+ }
+
+ if ( $len['offset'] === 0 ) {
+ // if we're starting the extended block, we've probably already
+ // done the XMPStandard block, so reset.
+ $this->resetXMLParser();
+ }
+
+ $this->extendedXMPOffset += $len['length'];
+
+ $actualContent = substr( $content, 40 );
+
+ if ( $this->extendedXMPOffset === strlen( $actualContent ) ) {
+ $atEnd = true;
+ } else {
+ $atEnd = false;
+ }
+
+ wfDebugLog('XMP', __METHOD__ . 'Parsing a XMPExtended block');
+ return $this->parse( $actualContent, $atEnd );
+ }
+
+ /**
+ * Character data handler
+ * Called whenever character data is found in the xmp document.
+ *
+ * does nothing if we're in MODE_IGNORE or if the data is whitespace
+ * throws an error if we're not in MODE_SIMPLE (as we're not allowed to have character
+ * data in the other modes).
+ *
+ * As an example, this happens when we encounter XMP like:
+ * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
+ * and are processing the 0/10 bit.
+ *
+ * @param $parser XMLParser reference to the xml parser
+ * @param $data String Character data
+ * @throws MWException on invalid data
+ */
+ function char( $parser, $data ) {
+
+ $data = trim( $data );
+ if ( trim( $data ) === "" ) {
+ return;
+ }
+
+ if ( !isset( $this->mode[0] ) ) {
+ throw new MWException( 'Unexpected character data before first rdf:Description element' );
+ }
+
+ if ( $this->mode[0] === self::MODE_IGNORE ) return;
+
+ if ( $this->mode[0] !== self::MODE_SIMPLE
+ && $this->mode[0] !== self::MODE_QDESC
+ ) {
+ throw new MWException( 'character data where not expected. (mode ' . $this->mode[0] . ')' );
+ }
+
+ // to check, how does this handle w.s.
+ if ( $this->charContent === false ) {
+ $this->charContent = $data;
+ } else {
+ $this->charContent .= $data;
+ }
+
+ }
+
+ /** When we hit a closing element in MODE_IGNORE
+ * Check to see if this is the element we started to ignore,
+ * in which case we get out of MODE_IGNORE
+ *
+ * @param $elm String Namespace of element followed by a space and then tag name of element.
+ */
+ private function endElementModeIgnore ( $elm ) {
+
+ if ( $this->curItem[0] === $elm ) {
+ array_shift( $this->curItem );
+ array_shift( $this->mode );
+ }
+ return;
+
+ }
+
+ /**
+ * Hit a closing element when in MODE_SIMPLE.
+ * This generally means that we finished processing a
+ * property value, and now have to save the result to the
+ * results array
+ *
+ * For example, when processing:
+ * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
+ * this deals with when we hit </exif:DigitalZoomRatio>.
+ *
+ * Or it could be if we hit the end element of a property
+ * of a compound data structure (like a member of an array).
+ *
+ * @param $elm String namespace, space, and tag name.
+ */
+ private function endElementModeSimple ( $elm ) {
+ if ( $this->charContent !== false ) {
+ if ( $this->processingArray ) {
+ // if we're processing an array, use the original element
+ // name instead of rdf:li.
+ list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
+ } else {
+ list( $ns, $tag ) = explode( ' ', $elm, 2 );
+ }
+ $this->saveValue( $ns, $tag, $this->charContent );
+
+ $this->charContent = false; // reset
+ }
+ array_shift( $this->curItem );
+ array_shift( $this->mode );
+
+ }
+
+ /**
+ * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
+ * generally means we've finished processing a nested structure.
+ * resets some internal variables to indicate that.
+ *
+ * Note this means we hit the </closing element> not the </rdf:Seq>.
+ *
+ * For example, when processing:
+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+ * </rdf:Seq> </exif:ISOSpeedRatings>
+ *
+ * This method is called when we hit the </exif:ISOSpeedRatings> tag.
+ *
+ * @param $elm String namespace . space . tag name.
+ */
+ private function endElementNested( $elm ) {
+
+ /* cur item must be the same as $elm, unless if in MODE_STRUCT
+ in which case it could also be rdf:Description */
+ if ( $this->curItem[0] !== $elm
+ && !( $elm === self::NS_RDF . ' Description'
+ && $this->mode[0] === self::MODE_STRUCT )
+ ) {
+ throw new MWException( "nesting mismatch. got a </$elm> but expected a </" . $this->curItem[0] . '>' );
+ }
+
+ // Validate structures.
+ list( $ns, $tag ) = explode( ' ', $elm, 2 );
+ if ( isset( $this->items[$ns][$tag]['validate'] ) ) {
+
+ $info =& $this->items[$ns][$tag];
+ $finalName = isset( $info['map_name'] )
+ ? $info['map_name'] : $tag;
+
+ $validate = is_array( $info['validate'] ) ? $info['validate']
+ : array( 'XMPValidate', $info['validate'] );
+
+ if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
+ // This can happen if all the members of the struct failed validation.
+ wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> has no valid members." );
+
+ } elseif ( is_callable( $validate ) ) {
+ $val =& $this->results['xmp-' . $info['map_group']][$finalName];
+ call_user_func_array( $validate, array( $info, &$val, false ) );
+ if ( is_null( $val ) ) {
+ // the idea being the validation function will unset the variable if
+ // its invalid.
+ wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> failed validation." );
+ unset( $this->results['xmp-' . $info['map_group']][$finalName] );
+ }
+ } else {
+ wfDebugLog( 'XMP', __METHOD__ . " Validation function for $finalName ("
+ . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
+ }
+ }
+
+ array_shift( $this->curItem );
+ array_shift( $this->mode );
+ $this->ancestorStruct = false;
+ $this->processingArray = false;
+ $this->itemLang = false;
+ }
+
+ /**
+ * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
+ * Add information about what type of element this is.
+ *
+ * Note we still have to hit the outer </property>
+ *
+ * For example, when processing:
+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+ * </rdf:Seq> </exif:ISOSpeedRatings>
+ *
+ * This method is called when we hit the </rdf:Seq>.
+ * (For comparison, we call endElementModeSimple when we
+ * hit the </rdf:li>)
+ *
+ * @param $elm String namespace . ' ' . element name
+ */
+ private function endElementModeLi( $elm ) {
+
+ list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
+ $info = $this->items[$ns][$tag];
+ $finalName = isset( $info['map_name'] )
+ ? $info['map_name'] : $tag;
+
+ array_shift( $this->mode );
+
+ if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
+ wfDebugLog( 'XMP', __METHOD__ . " Empty compund element $finalName." );
+ return;
+ }
+
+ if ( $elm === self::NS_RDF . ' Seq' ) {
+ $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ol';
+ } elseif ( $elm === self::NS_RDF . ' Bag' ) {
+ $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ul';
+ } elseif ( $elm === self::NS_RDF . ' Alt' ) {
+ // extra if needed as you could theoretically have a non-language alt.
+ if ( $info['mode'] === self::MODE_LANG ) {
+ $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'lang';
+ }
+
+ } else {
+ throw new MWException( __METHOD__ . " expected </rdf:seq> or </rdf:bag> but instead got $elm." );
+ }
+ }
+
+ /**
+ * End element while in MODE_QDESC
+ * mostly when ending an element when we have a simple value
+ * that has qualifiers.
+ *
+ * Qualifiers aren't all that common, and we don't do anything
+ * with them.
+ *
+ * @param $elm String namespace and element
+ */
+ private function endElementModeQDesc( $elm ) {
+
+ if ( $elm === self::NS_RDF . ' value' ) {
+ list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
+ $this->saveValue( $ns, $tag, $this->charContent );
+ return;
+ } else {
+ array_shift( $this->mode );
+ array_shift( $this->curItem );
+ }
+
+
+ }
+
+ /**
+ * Handler for hitting a closing element.
+ *
+ * generally just calls a helper function depending on what
+ * mode we're in.
+ *
+ * Ignores the outer wrapping elements that are optional in
+ * xmp and have no meaning.
+ *
+ * @param $parser XMLParser
+ * @param $elm String namespace . ' ' . element name
+ */
+ function endElement( $parser, $elm ) {
+ if ( $elm === ( self::NS_RDF . ' RDF' )
+ || $elm === 'adobe:ns:meta/ xmpmeta'
+ || $elm === 'adobe:ns:meta/ xapmeta' )
+ {
+ // ignore these.
+ return;
+ }
+
+ if ( $elm === self::NS_RDF . ' type' ) {
+ // these aren't really supported properly yet.
+ // However, it appears they almost never used.
+ wfDebugLog( 'XMP', __METHOD__ . ' encountered <rdf:type>' );
+ }
+
+ if ( strpos( $elm, ' ' ) === false ) {
+ // This probably shouldn't happen.
+ // However, there is a bug in an adobe product
+ // that forgets the namespace on some things.
+ // (Luckily they are unimportant things).
+ wfDebugLog( 'XMP', __METHOD__ . " Encountered </$elm> which has no namespace. Skipping." );
+ return;
+ }
+
+ if ( count( $this->mode[0] ) === 0 ) {
+ // This should never ever happen and means
+ // there is a pretty major bug in this class.
+ throw new MWException( 'Encountered end element with no mode' );
+ }
+
+ if ( count( $this->curItem ) == 0 && $this->mode[0] !== self::MODE_INITIAL ) {
+ // just to be paranoid. Should always have a curItem, except for initially
+ // (aka during MODE_INITAL).
+ throw new MWException( "Hit end element </$elm> but no curItem" );
+ }
+
+ switch( $this->mode[0] ) {
+ case self::MODE_IGNORE:
+ $this->endElementModeIgnore( $elm );
+ break;
+ case self::MODE_SIMPLE:
+ $this->endElementModeSimple( $elm );
+ break;
+ case self::MODE_STRUCT:
+ case self::MODE_SEQ:
+ case self::MODE_BAG:
+ case self::MODE_LANG:
+ case self::MODE_BAGSTRUCT:
+ $this->endElementNested( $elm );
+ break;
+ case self::MODE_INITIAL:
+ if ( $elm === self::NS_RDF . ' Description' ) {
+ array_shift( $this->mode );
+ } else {
+ throw new MWException( 'Element ended unexpectedly while in MODE_INITIAL' );
+ }
+ break;
+ case self::MODE_LI:
+ case self::MODE_LI_LANG:
+ $this->endElementModeLi( $elm );
+ break;
+ case self::MODE_QDESC:
+ $this->endElementModeQDesc( $elm );
+ break;
+ default:
+ wfDebugLog( 'XMP', __METHOD__ . " no mode (elm = $elm)" );
+ break;
+ }
+ }
+
+ /**
+ * Hit an opening element while in MODE_IGNORE
+ *
+ * XMP is extensible, so ignore any tag we don't understand.
+ *
+ * Mostly ignores, unless we encounter the element that we are ignoring.
+ * in which case we add it to the item stack, so we can ignore things
+ * that are nested, correctly.
+ *
+ * @param $elm String namespace . ' ' . tag name
+ */
+ private function startElementModeIgnore( $elm ) {
+ if ( $elm === $this->curItem[0] ) {
+ array_unshift( $this->curItem, $elm );
+ array_unshift( $this->mode, self::MODE_IGNORE );
+ }
+ }
+
+ /**
+ * Start element in MODE_BAG (unordered array)
+ * this should always be <rdf:Bag>
+ *
+ * @param $elm String namespace . ' ' . tag
+ * @throws MWException if we have an element that's not <rdf:Bag>
+ */
+ private function startElementModeBag( $elm ) {
+ if ( $elm === self::NS_RDF . ' Bag' ) {
+ array_unshift( $this->mode, self::MODE_LI );
+ } else {
+ throw new MWException( "Expected <rdf:Bag> but got $elm." );
+ }
+
+ }
+
+ /**
+ * Start element in MODE_SEQ (ordered array)
+ * this should always be <rdf:Seq>
+ *
+ * @param $elm String namespace . ' ' . tag
+ * @throws MWException if we have an element that's not <rdf:Seq>
+ */
+ private function startElementModeSeq( $elm ) {
+ if ( $elm === self::NS_RDF . ' Seq' ) {
+ array_unshift( $this->mode, self::MODE_LI );
+ } elseif ( $elm === self::NS_RDF . ' Bag' ) {
+ # bug 27105
+ wfDebugLog( 'XMP', __METHOD__ . ' Expected an rdf:Seq, but got an rdf:Bag. Pretending'
+ . ' it is a Seq, since some buggy software is known to screw this up.' );
+ array_unshift( $this->mode, self::MODE_LI );
+ } else {
+ throw new MWException( "Expected <rdf:Seq> but got $elm." );
+ }
+
+ }
+
+ /**
+ * Start element in MODE_LANG (language alternative)
+ * this should always be <rdf:Alt>
+ *
+ * This tag tends to be used for metadata like describe this
+ * picture, which can be translated into multiple languages.
+ *
+ * XMP supports non-linguistic alternative selections,
+ * which are really only used for thumbnails, which
+ * we don't care about.
+ *
+ * @param $elm String namespace . ' ' . tag
+ * @throws MWException if we have an element that's not <rdf:Alt>
+ */
+ private function startElementModeLang( $elm ) {
+ if ( $elm === self::NS_RDF . ' Alt' ) {
+ array_unshift( $this->mode, self::MODE_LI_LANG );
+ } else {
+ throw new MWException( "Expected <rdf:Seq> but got $elm." );
+ }
+
+ }
+
+ /**
+ * Handle an opening element when in MODE_SIMPLE
+ *
+ * This should not happen often. This is for if a simple element
+ * already opened has a child element. Could happen for a
+ * qualified element.
+ *
+ * For example:
+ * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
+ * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
+ * </exif:DigitalZoomRatio>
+ *
+ * This method is called when processing the <rdf:Description> element
+ *
+ * @param $elm String namespace and tag names separated by space.
+ * @param $attribs Array Attributes of the element.
+ */
+ private function startElementModeSimple( $elm, $attribs ) {
+ if ( $elm === self::NS_RDF . ' Description' ) {
+ // If this value has qualifiers
+ array_unshift( $this->mode, self::MODE_QDESC );
+ array_unshift( $this->curItem, $this->curItem[0] );
+
+ if ( isset( $attribs[self::NS_RDF . ' value'] ) ) {
+ list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
+ $this->saveValue( $ns, $tag, $attribs[self::NS_RDF . ' value'] );
+ }
+ } elseif ( $elm === self::NS_RDF . ' value' ) {
+ // This should not be here.
+ throw new MWException( __METHOD__ . ' Encountered <rdf:value> where it was unexpected.' );
+
+ } else {
+ // something else we don't recognize, like a qualifier maybe.
+ wfDebugLog( 'XMP', __METHOD__ . " Encountered element <$elm> where only expecting character data as value of " . $this->curItem[0] );
+ array_unshift( $this->mode, self::MODE_IGNORE );
+ array_unshift( $this->curItem, $elm );
+
+ }
+
+ }
+
+ /**
+ * Start an element when in MODE_QDESC.
+ * This generally happens when a simple element has an inner
+ * rdf:Description to hold qualifier elements.
+ *
+ * For example in:
+ * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
+ * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
+ * </exif:DigitalZoomRatio>
+ * Called when processing the <rdf:value> or <foo:someQualifier>.
+ *
+ * @param $elm String namespace and tag name separated by a space.
+ *
+ */
+ private function startElementModeQDesc( $elm ) {
+ if ( $elm === self::NS_RDF . ' value' ) {
+ return; // do nothing
+ } else {
+ // otherwise its a qualifier, which we ignore
+ array_unshift( $this->mode, self::MODE_IGNORE );
+ array_unshift( $this->curItem, $elm );
+ }
+ }
+
+ /**
+ * Starting an element when in MODE_INITIAL
+ * This usually happens when we hit an element inside
+ * the outer rdf:Description
+ *
+ * This is generally where most properties start.
+ *
+ * @param $ns String Namespace
+ * @param $tag String tag name (without namespace prefix)
+ * @param $attribs Array array of attributes
+ */
+ private function startElementModeInitial( $ns, $tag, $attribs ) {
+ if ( $ns !== self::NS_RDF ) {
+
+ if ( isset( $this->items[$ns][$tag] ) ) {
+ if ( isset( $this->items[$ns][$tag]['structPart'] ) ) {
+ // If this element is supposed to appear only as
+ // a child of a structure, but appears here (not as
+ // a child of a struct), then something weird is
+ // happening, so ignore this element and its children.
+
+ wfDebugLog( 'XMP', "Encountered <$ns:$tag> outside"
+ . " of its expected parent. Ignoring." );
+
+ array_unshift( $this->mode, self::MODE_IGNORE );
+ array_unshift( $this->curItem, $ns . ' ' . $tag );
+ return;
+ }
+ $mode = $this->items[$ns][$tag]['mode'];
+ array_unshift( $this->mode, $mode );
+ array_unshift( $this->curItem, $ns . ' ' . $tag );
+ if ( $mode === self::MODE_STRUCT ) {
+ $this->ancestorStruct = isset( $this->items[$ns][$tag]['map_name'] )
+ ? $this->items[$ns][$tag]['map_name'] : $tag;
+ }
+ if ( $this->charContent !== false ) {
+ // Something weird.
+ // Should not happen in valid XMP.
+ throw new MWException( 'tag nested in non-whitespace characters.' );
+ }
+ } else {
+ // This element is not on our list of allowed elements so ignore.
+ wfDebugLog( 'XMP', __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
+ array_unshift( $this->mode, self::MODE_IGNORE );
+ array_unshift( $this->curItem, $ns . ' ' . $tag );
+ return;
+ }
+
+ }
+ // process attributes
+ $this->doAttribs( $attribs );
+ }
+
+ /**
+ * Hit an opening element when in a Struct (MODE_STRUCT)
+ * This is generally for fields of a compound property.
+ *
+ * Example of a struct (abbreviated; flash has more properties):
+ *
+ * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
+ * <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
+ *
+ * or:
+ *
+ * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
+ * <exif:Mode>1</exif:Mode></exif:Flash>
+ *
+ * @param $ns String namespace
+ * @param $tag String tag name (no ns)
+ * @param $attribs Array array of attribs w/ values.
+ */
+ private function startElementModeStruct( $ns, $tag, $attribs ) {
+ if ( $ns !== self::NS_RDF ) {
+
+ if ( isset( $this->items[$ns][$tag] ) ) {
+ if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
+ && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] ) )
+ {
+ // This assumes that we don't have inter-namespace nesting
+ // which we don't in all the properties we're interested in.
+ throw new MWException( " <$tag> appeared nested in <" . $this->ancestorStruct
+ . "> where it is not allowed." );
+ }
+ array_unshift( $this->mode, $this->items[$ns][$tag]['mode'] );
+ array_unshift( $this->curItem, $ns . ' ' . $tag );
+ if ( $this->charContent !== false ) {
+ // Something weird.
+ // Should not happen in valid XMP.
+ throw new MWException( "tag <$tag> nested in non-whitespace characters (" . $this->charContent . ")." );
+ }
+ } else {
+ array_unshift( $this->mode, self::MODE_IGNORE );
+ array_unshift( $this->curItem, $elm );
+ return;
+ }
+
+ }
+
+ if ( $ns === self::NS_RDF && $tag === 'Description' ) {
+ $this->doAttribs( $attribs );
+ array_unshift( $this->mode, self::MODE_STRUCT );
+ array_unshift( $this->curItem, $this->curItem[0] );
+ }
+ }
+
+ /**
+ * opening element in MODE_LI
+ * process elements of arrays.
+ *
+ * Example:
+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
+ * </rdf:Seq> </exif:ISOSpeedRatings>
+ * This method is called when we hit the <rdf:li> element.
+ *
+ * @param $elm String: namespace . ' ' . tagname
+ * @param $attribs Array: Attributes. (needed for BAGSTRUCTS)
+ * @throws MWException if gets a tag other than <rdf:li>
+ */
+ private function startElementModeLi( $elm, $attribs ) {
+ if ( ( $elm ) !== self::NS_RDF . ' li' ) {
+ throw new MWException( "<rdf:li> expected but got $elm." );
+ }
+
+ if ( !isset( $this->mode[1] ) ) {
+ // This should never ever ever happen. Checking for it
+ // to be paranoid.
+ throw new MWException( 'In mode Li, but no 2xPrevious mode!' );
+ }
+
+ if ( $this->mode[1] === self::MODE_BAGSTRUCT ) {
+ // This list item contains a compound (STRUCT) value.
+ array_unshift( $this->mode, self::MODE_STRUCT );
+ array_unshift( $this->curItem, $elm );
+ $this->processingArray = true;
+
+ if ( !isset( $this->curItem[1] ) ) {
+ // be paranoid.
+ throw new MWException( 'Can not find parent of BAGSTRUCT.' );
+ }
+ list( $curNS, $curTag ) = explode( ' ', $this->curItem[1] );
+ $this->ancestorStruct = isset( $this->items[$curNS][$curTag]['map_name'] )
+ ? $this->items[$curNS][$curTag]['map_name'] : $curTag;
+
+ $this->doAttribs( $attribs );
+
+ } else {
+ // Normal BAG or SEQ containing simple values.
+ array_unshift( $this->mode, self::MODE_SIMPLE );
+ // need to add curItem[0] on again since one is for the specific item
+ // and one is for the entire group.
+ array_unshift( $this->curItem, $this->curItem[0] );
+ $this->processingArray = true;
+ }
+
+ }
+
+ /**
+ * Opening element in MODE_LI_LANG.
+ * process elements of language alternatives
+ *
+ * Example:
+ * <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">My house
+ * </rdf:li> </rdf:Alt> </dc:title>
+ *
+ * This method is called when we hit the <rdf:li> element.
+ *
+ * @param $elm String namespace . ' ' . tag
+ * @param $attribs array array of elements (most importantly xml:lang)
+ * @throws MWException if gets a tag other than <rdf:li> or if no xml:lang
+ */
+ private function startElementModeLiLang( $elm, $attribs ) {
+ if ( $elm !== self::NS_RDF . ' li' ) {
+ throw new MWException( __METHOD__ . " <rdf:li> expected but got $elm." );
+ }
+ if ( !isset( $attribs[ self::NS_XML . ' lang'] )
+ || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[ self::NS_XML . ' lang' ] ) )
+ {
+ throw new MWException( __METHOD__
+ . " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
+ }
+
+ // Lang is case-insensitive.
+ $this->itemLang = strtolower( $attribs[ self::NS_XML . ' lang' ] );
+
+ // need to add curItem[0] on again since one is for the specific item
+ // and one is for the entire group.
+ array_unshift( $this->curItem, $this->curItem[0] );
+ array_unshift( $this->mode, self::MODE_SIMPLE );
+ $this->processingArray = true;
+ }
+
+ /**
+ * Hits an opening element.
+ * Generally just calls a helper based on what MODE we're in.
+ * Also does some initial set up for the wrapper element
+ *
+ * @param $parser XMLParser
+ * @param $elm String namespace <space> element
+ * @param $attribs Array attribute name => value
+ */
+ function startElement( $parser, $elm, $attribs ) {
+
+ if ( $elm === self::NS_RDF . ' RDF'
+ || $elm === 'adobe:ns:meta/ xmpmeta'
+ || $elm === 'adobe:ns:meta/ xapmeta')
+ {
+ /* ignore. */
+ return;
+ } elseif ( $elm === self::NS_RDF . ' Description' ) {
+ if ( count( $this->mode ) === 0 ) {
+ // outer rdf:desc
+ array_unshift( $this->mode, self::MODE_INITIAL );
+ }
+ } elseif ( $elm === self::NS_RDF . ' type' ) {
+ // This doesn't support rdf:type properly.
+ // In practise I have yet to see a file that
+ // uses this element, however it is mentioned
+ // on page 25 of part 1 of the xmp standard.
+ //
+ // also it seems as if exiv2 and exiftool do not support
+ // this either (That or I misunderstand the standard)
+ wfDebugLog( 'XMP', __METHOD__ . ' Encountered <rdf:type> which isn\'t currently supported' );
+ }
+
+ if ( strpos( $elm, ' ' ) === false ) {
+ // This probably shouldn't happen.
+ wfDebugLog( 'XMP', __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." );
+ return;
+ }
+
+ list( $ns, $tag ) = explode( ' ', $elm, 2 );
+
+ if ( count( $this->mode ) === 0 ) {
+ // This should not happen.
+ throw new MWException('Error extracting XMP, '
+ . "encountered <$elm> with no mode" );
+ }
+
+ switch( $this->mode[0] ) {
+ case self::MODE_IGNORE:
+ $this->startElementModeIgnore( $elm );
+ break;
+ case self::MODE_SIMPLE:
+ $this->startElementModeSimple( $elm, $attribs );
+ break;
+ case self::MODE_INITIAL:
+ $this->startElementModeInitial( $ns, $tag, $attribs );
+ break;
+ case self::MODE_STRUCT:
+ $this->startElementModeStruct( $ns, $tag, $attribs );
+ break;
+ case self::MODE_BAG:
+ case self::MODE_BAGSTRUCT:
+ $this->startElementModeBag( $elm );
+ break;
+ case self::MODE_SEQ:
+ $this->startElementModeSeq( $elm );
+ break;
+ case self::MODE_LANG:
+ $this->startElementModeLang( $elm );
+ break;
+ case self::MODE_LI_LANG:
+ $this->startElementModeLiLang( $elm, $attribs );
+ break;
+ case self::MODE_LI:
+ $this->startElementModeLi( $elm, $attribs );
+ break;
+ case self::MODE_QDESC:
+ $this->startElementModeQDesc( $elm );
+ break;
+ default:
+ throw new MWException( 'StartElement in unknown mode: ' . $this->mode[0] );
+ break;
+ }
+ }
+
+ /**
+ * Process attributes.
+ * Simple values can be stored as either a tag or attribute
+ *
+ * Often the initial <rdf:Description> tag just has all the simple
+ * properties as attributes.
+ *
+ * Example:
+ * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
+ *
+ * @param $attribs Array attribute=>value array.
+ */
+ private function doAttribs( $attribs ) {
+
+ // first check for rdf:parseType attribute, as that can change
+ // how the attributes are interperted.
+
+ if ( isset( $attribs[self::NS_RDF . ' parseType'] )
+ && $attribs[self::NS_RDF . ' parseType'] === 'Resource'
+ && $this->mode[0] === self::MODE_SIMPLE )
+ {
+ // this is equivalent to having an inner rdf:Description
+ $this->mode[0] = self::MODE_QDESC;
+ }
+ foreach ( $attribs as $name => $val ) {
+
+
+ if ( strpos( $name, ' ' ) === false ) {
+ // This shouldn't happen, but so far some old software forgets namespace
+ // on rdf:about.
+ wfDebugLog( 'XMP', __METHOD__ . ' Encountered non-namespaced attribute: '
+ . " $name=\"$val\". Skipping. " );
+ continue;
+ }
+ list( $ns, $tag ) = explode( ' ', $name, 2 );
+ if ( $ns === self::NS_RDF ) {
+ if ( $tag === 'value' || $tag === 'resource' ) {
+ // resource is for url.
+ // value attribute is a weird way of just putting the contents.
+ $this->char( $this->xmlParser, $val );
+ }
+ } elseif ( isset( $this->items[$ns][$tag] ) ) {
+ if ( $this->mode[0] === self::MODE_SIMPLE ) {
+ throw new MWException( __METHOD__
+ . " $ns:$tag found as attribute where not allowed" );
+ }
+ $this->saveValue( $ns, $tag, $val );
+ } else {
+ wfDebugLog( 'XMP', __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
+ }
+ }
+ }
+
+ /**
+ * Given an extracted value, save it to results array
+ *
+ * note also uses $this->ancestorStruct and
+ * $this->processingArray to determine what name to
+ * save the value under. (in addition to $tag).
+ *
+ * @param $ns String namespace of tag this is for
+ * @param $tag String tag name
+ * @param $val String value to save
+ */
+ private function saveValue( $ns, $tag, $val ) {
+
+ $info =& $this->items[$ns][$tag];
+ $finalName = isset( $info['map_name'] )
+ ? $info['map_name'] : $tag;
+ if ( isset( $info['validate'] ) ) {
+ $validate = is_array( $info['validate'] ) ? $info['validate']
+ : array( 'XMPValidate', $info['validate'] );
+
+ if ( is_callable( $validate ) ) {
+ call_user_func_array( $validate, array( $info, &$val, true ) );
+ // the reasoning behind using &$val instead of using the return value
+ // is to be consistent between here and validating structures.
+ if ( is_null( $val ) ) {
+ wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> failed validation." );
+ return;
+ }
+ } else {
+ wfDebugLog( 'XMP', __METHOD__ . " Validation function for $finalName ("
+ . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
+ }
+ }
+
+ if ( $this->ancestorStruct && $this->processingArray ) {
+ // Aka both an array and a struct. ( self::MODE_BAGSTRUCT )
+ $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][][$finalName] = $val;
+ } elseif ( $this->ancestorStruct ) {
+ $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][$finalName] = $val;
+ } elseif ( $this->processingArray ) {
+ if ( $this->itemLang === false ) {
+ // normal array
+ $this->results['xmp-' . $info['map_group']][$finalName][] = $val;
+ } else {
+ // lang array.
+ $this->results['xmp-' . $info['map_group']][$finalName][$this->itemLang] = $val;
+ }
+ } else {
+ $this->results['xmp-' . $info['map_group']][$finalName] = $val;
+ }
+ }
+}
diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php
new file mode 100644
index 00000000..1d580ff7
--- /dev/null
+++ b/includes/media/XMPInfo.php
@@ -0,0 +1,1139 @@
+<?php
+/**
+* This class is just a container for a big array
+* used by XMPReader to determine which XMP items to
+* extract.
+*/
+class XMPInfo {
+
+ /** get the items array
+ * @return Array XMP item configuration array.
+ */
+ public static function getItems ( ) {
+ if( !self::$ranHooks ) {
+ // This is for if someone makes a custom metadata extension.
+ // For example, a medical wiki might want to decode DICOM xmp properties.
+ wfRunHooks('XMPGetInfo', Array(&self::$items));
+ self::$ranHooks = true; // Only want to do this once.
+ }
+ return self::$items;
+ }
+
+ static private $ranHooks = false;
+
+ /**
+ * XMPInfo::$items keeps a list of all the items
+ * we are interested to extract, as well as
+ * information about the item like what type
+ * it is.
+ *
+ * Format is an array of namespaces,
+ * each containing an array of tags
+ * each tag is an array of information about the
+ * tag, including:
+ * * map_group - what group (used for precedence during conflicts)
+ * * mode - What type of item (self::MODE_SIMPLE usually, see above for all values)
+ * * validate - method to validate input. Could also post-process the input. A string value is assumed to be a static method of XMPValidate. Can also take a array( 'className', 'methodName' ).
+ * * choices - array of potential values (format of 'value' => true ). Only used with validateClosed
+ * * rangeLow and rangeHigh - alternative to choices for numeric ranges. Again for validateClosed only.
+ * * children - for MODE_STRUCT items, allowed children.
+ * * structPart - Indicates that this element can only appear as a member of a structure.
+ *
+ * currently this just has a bunch of exif values as this class is only half-done
+ */
+
+ static private $items = array(
+ 'http://ns.adobe.com/exif/1.0/' => array(
+ 'ApertureValue' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'BrightnessValue' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'CompressedBitsPerPixel' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'DigitalZoomRatio' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'ExposureBiasValue' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'ExposureIndex' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'ExposureTime' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'FlashEnergy' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
+ ),
+ 'FNumber' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'FocalLength' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'FocalPlaneXResolution' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'FocalPlaneYResolution' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'GPSAltitude' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
+ ),
+ 'GPSDestBearing' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'GPSDestDistance' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'GPSDOP' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'GPSImgDirection' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'GPSSpeed' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'GPSTrack' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'MaxApertureValue' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'ShutterSpeedValue' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ 'SubjectDistance' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational'
+ ),
+ /* Flash */
+ 'Flash' => array(
+ 'mode' => XMPReader::MODE_STRUCT,
+ 'children' => array(
+ 'Fired' => true,
+ 'Function' => true,
+ 'Mode' => true,
+ 'RedEyeMode' => true,
+ 'Return' => true,
+ ),
+ 'validate' => 'validateFlash',
+ 'map_group' => 'exif',
+ ),
+ 'Fired' => array(
+ 'map_group' => 'exif',
+ 'validate' => 'validateBoolean',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'Function' => array(
+ 'map_group' => 'exif',
+ 'validate' => 'validateBoolean',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'Mode' => array(
+ 'map_group' => 'exif',
+ 'validate' => 'validateClosed',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'choices' => array( '0' => true, '1' => true,
+ '2' => true, '3' => true ),
+ 'structPart'=> true,
+ ),
+ 'Return' => array(
+ 'map_group' => 'exif',
+ 'validate' => 'validateClosed',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'choices' => array( '0' => true,
+ '2' => true, '3' => true ),
+ 'structPart'=> true,
+ ),
+ 'RedEyeMode' => array(
+ 'map_group' => 'exif',
+ 'validate' => 'validateBoolean',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ /* End Flash */
+ 'ISOSpeedRatings' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger'
+ ),
+ /* end rational things */
+ 'ColorSpace' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '65535' => true ),
+ ),
+ 'ComponentsConfiguration' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true,
+ '5' => true, '6' => true )
+ ),
+ 'Contrast' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true, '2' => true )
+ ),
+ 'CustomRendered' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true )
+ ),
+ 'DateTimeOriginal' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
+ ),
+ 'DateTimeDigitized' => array( /* xmp:CreateDate */
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
+ ),
+ /* todo: there might be interesting information in
+ * exif:DeviceSettingDescription, but need to find an
+ * example
+ */
+ 'ExifVersion' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'ExposureMode' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
+ 'rangeHigh' => 2,
+ ),
+ 'ExposureProgram' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
+ 'rangeHigh' => 8,
+ ),
+ 'FileSource' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '3' => true )
+ ),
+ 'FlashpixVersion' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'FocalLengthIn35mmFilm' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ ),
+ 'FocalPlaneResolutionUnit' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '3' => true ),
+ ),
+ 'GainControl' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
+ 'rangeHigh' => 4,
+ ),
+ /* this value is post-processed out later */
+ 'GPSAltitudeRef' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true ),
+ ),
+ 'GPSAreaInformation' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'GPSDestBearingRef' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'T' => true, 'M' => true ),
+ ),
+ 'GPSDestDistanceRef' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'K' => true, 'M' => true,
+ 'N' => true ),
+ ),
+ 'GPSDestLatitude' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
+ ),
+ 'GPSDestLongitude' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
+ ),
+ 'GPSDifferential' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true ),
+ ),
+ 'GPSImgDirectionRef' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'T' => true, 'M' => true ),
+ ),
+ 'GPSLatitude' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
+ ),
+ 'GPSLongitude' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateGPS',
+ ),
+ 'GPSMapDatum' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'GPSMeasureMode' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '3' => true )
+ ),
+ 'GPSProcessingMethod' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'GPSSatellites' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'GPSSpeedRef' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'K' => true, 'M' => true,
+ 'N' => true ),
+ ),
+ 'GPSStatus' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'A' => true, 'V' => true )
+ ),
+ 'GPSTimeStamp' => array(
+ 'map_group' => 'exif',
+ // Note: in exif, GPSDateStamp does not include
+ // the time, where here it does.
+ 'map_name' => 'GPSDateStamp',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
+ ),
+ 'GPSTrackRef' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( 'T' => true, 'M' => true )
+ ),
+ 'GPSVersionID' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'ImageUniqueID' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'LightSource' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ /* can't use a range, as it skips... */
+ 'choices' => array( '0' => true, '1' => true,
+ '2' => true, '3' => true, '4' => true,
+ '9' => true, '10' => true, '11' => true,
+ '12' => true, '13' => true,
+ '14' => true, '15' => true,
+ '17' => true, '18' => true,
+ '19' => true, '20' => true,
+ '21' => true, '22' => true,
+ '23' => true, '24' => true,
+ '255' => true,
+ ),
+ ),
+ 'MeteringMode' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
+ 'rangeHigh' => 6,
+ 'choices' => array( '255' => true ),
+ ),
+ /* Pixel(X|Y)Dimension are rather useless, but for
+ * completeness since we do it with exif.
+ */
+ 'PixelXDimension' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ ),
+ 'PixelYDimension' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ ),
+ 'Saturation' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
+ 'rangeHigh' => 2,
+ ),
+ 'SceneCaptureType' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
+ 'rangeHigh' => 3,
+ ),
+ 'SceneType' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true ),
+ ),
+ // Note, 6 is not valid SensingMethod.
+ 'SensingMethod' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 1,
+ 'rangeHigh' => 5,
+ 'choices' => array( '7' => true, 8 => true ),
+ ),
+ 'Sharpness' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
+ 'rangeHigh' => 2,
+ ),
+ 'SpectralSensitivity' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ // This tag should perhaps be displayed to user better.
+ 'SubjectArea' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger',
+ ),
+ 'SubjectDistanceRange' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'rangeLow' => 0,
+ 'rangeHigh' => 3,
+ ),
+ 'SubjectLocation' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger',
+ ),
+ 'UserComment' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_LANG,
+ ),
+ 'WhiteBalance' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '0' => true, '1' => true )
+ ),
+ ),
+ 'http://ns.adobe.com/tiff/1.0/' => array(
+ 'Artist' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'BitsPerSample' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateInteger',
+ ),
+ 'Compression' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '6' => true ),
+ ),
+ /* this prop should not be used in XMP. dc:rights is the correct prop */
+ 'Copyright' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_LANG,
+ ),
+ 'DateTime' => array( /* proper prop is xmp:ModifyDate */
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
+ ),
+ 'ImageDescription' => array( /* proper one is dc:description */
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_LANG,
+ ),
+ 'ImageLength' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ ),
+ 'ImageWidth' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ ),
+ 'Make' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'Model' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ /**** Do not extract this property
+ * It interferes with auto exif rotation.
+ * 'Orientation' => array(
+ * 'map_group' => 'exif',
+ * 'mode' => XMPReader::MODE_SIMPLE,
+ * 'validate' => 'validateClosed',
+ * 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true, 5 => true,
+ * '6' => true, '7' => true, '8' => true ),
+ *),
+ ******/
+ 'PhotometricInterpretation' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '6' => true ),
+ ),
+ 'PlanerConfiguration' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '2' => true ),
+ ),
+ 'PrimaryChromaticities' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
+ ),
+ 'ReferenceBlackWhite' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
+ ),
+ 'ResolutionUnit' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '2' => true, '3' => true ),
+ ),
+ 'SamplesPerPixel' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ ),
+ 'Software' => array( /* see xmp:CreatorTool */
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ /* ignore TransferFunction */
+ 'WhitePoint' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
+ ),
+ 'XResolution' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
+ ),
+ 'YResolution' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRational',
+ ),
+ 'YCbCrCoefficients' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateRational',
+ ),
+ 'YCbCrPositioning' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '2' => true ),
+ ),
+ 'YCbCrSubSampling' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateClosed',
+ 'choices' => array( '1' => true, '2' => true ),
+ ),
+ ),
+ 'http://ns.adobe.com/exif/1.0/aux/' => array(
+ 'Lens' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'SerialNumber' => array(
+ 'map_group' => 'exif',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'OwnerName' => array(
+ 'map_group' => 'exif',
+ 'map_name' => 'CameraOwnerName',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ ),
+ 'http://purl.org/dc/elements/1.1/' => array(
+ 'title' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'ObjectName',
+ 'mode' => XMPReader::MODE_LANG
+ ),
+ 'description' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'ImageDescription',
+ 'mode' => XMPReader::MODE_LANG
+ ),
+ 'contributor' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'dc-contributor',
+ 'mode' => XMPReader::MODE_BAG
+ ),
+ 'coverage' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'dc-coverage',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'creator' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'Artist', //map with exif Artist, iptc byline (2:80)
+ 'mode' => XMPReader::MODE_SEQ,
+ ),
+ 'date' => array(
+ 'map_group' => 'general',
+ // Note, not mapped with other date properties, as this type of date is
+ // non-specific: "A point or period of time associated with an event in
+ // the lifecycle of the resource"
+ 'map_name' => 'dc-date',
+ 'mode' => XMPReader::MODE_SEQ,
+ 'validate' => 'validateDate',
+ ),
+ /* Do not extract dc:format, as we've got better ways to determine mimetype */
+ 'identifier' => array(
+ 'map_group' => 'deprecated',
+ 'map_name' => 'Identifier',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'language' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'LanguageCode', /* mapped with iptc 2:135 */
+ 'mode' => XMPReader::MODE_BAG,
+ 'validate' => 'validateLangCode',
+ ),
+ 'publisher' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'dc-publisher',
+ 'mode' => XMPReader::MODE_BAG,
+ ),
+ // for related images/resources
+ 'relation' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'dc-relation',
+ 'mode' => XMPReader::MODE_BAG,
+ ),
+ 'rights' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'Copyright',
+ 'mode' => XMPReader::MODE_LANG,
+ ),
+ // Note: source is not mapped with iptc source, since iptc
+ // source describes the source of the image in terms of a person
+ // who provided the image, where this is to describe an image that the
+ // current one is based on.
+ 'source' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'dc-source',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'subject' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'Keywords', /* maps to iptc 2:25 */
+ 'mode' => XMPReader::MODE_BAG,
+ ),
+ 'type' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'dc-type',
+ 'mode' => XMPReader::MODE_BAG,
+ ),
+ ),
+ 'http://ns.adobe.com/xap/1.0/' => array(
+ 'CreateDate' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'DateTimeDigitized',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateDate',
+ ),
+ 'CreatorTool' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'Software',
+ 'mode' => XMPReader::MODE_SIMPLE
+ ),
+ 'Identifier' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_BAG,
+ ),
+ 'Label' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'ModifyDate' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'DateTime',
+ 'validate' => 'validateDate',
+ ),
+ 'MetadataDate' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ // map_name to be consistent with other date names.
+ 'map_name' => 'DateTimeMetadata',
+ 'validate' => 'validateDate',
+ ),
+ 'Nickname' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'Rating' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateRating',
+ ),
+ ),
+ 'http://ns.adobe.com/xap/1.0/rights/' => array(
+ 'Certificate' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'RightsCertificate',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'Marked' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'Copyrighted',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateBoolean',
+ ),
+ 'Owner' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'CopyrightOwner',
+ 'mode' => XMPReader::MODE_BAG,
+ ),
+ // this seems similar to dc:rights.
+ 'UsageTerms' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_LANG,
+ ),
+ 'WebStatement' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ ),
+ // XMP media management.
+ 'http://ns.adobe.com/xap/1.0/mm/' => array(
+ // if we extract the exif UniqueImageID, might
+ // as well do this too.
+ 'OriginalDocumentID' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ // It might also be useful to do xmpMM:LastURL
+ // and xmpMM:DerivedFrom as you can potentially,
+ // get the url of this document/source for this
+ // document. However whats more likely is you'd
+ // get a file:// url for the path of the doc,
+ // which is somewhat of a privacy issue.
+ ),
+ 'http://creativecommons.org/ns#' => array(
+ 'license' => array(
+ 'map_name' => 'LicenseUrl',
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'morePermissions' => array(
+ 'map_name' => 'MorePermissionsUrl',
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'attributionURL' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'AttributionUrl',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'attributionName' => array(
+ 'map_group' => 'general',
+ 'map_name' => 'PreferredAttributionName',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ ),
+ //Note, this property affects how jpeg metadata is extracted.
+ 'http://ns.adobe.com/xmp/note/' => array(
+ 'HasExtendedXMP' => array(
+ 'map_group' => 'special',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ ),
+ /* Note, in iptc schemas, the legacy properties are denoted
+ * as deprecated, since other properties should used instead,
+ * and properties marked as deprecated in the standard are
+ * are marked as general here as they don't have replacements
+ */
+ 'http://ns.adobe.com/photoshop/1.0/' => array(
+ 'City' => array(
+ 'map_group' => 'deprecated',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CityDest',
+ ),
+ 'Country' => array(
+ 'map_group' => 'deprecated',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CountryDest',
+ ),
+ 'State' => array(
+ 'map_group' => 'deprecated',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'ProvinceOrStateDest',
+ ),
+ 'DateCreated' => array(
+ 'map_group' => 'deprecated',
+ // marking as deprecated as the xmp prop preferred
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'DateTimeOriginal',
+ 'validate' => 'validateDate',
+ // note this prop is an XMP, not IPTC date
+ ),
+ 'CaptionWriter' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'Writer',
+ ),
+ 'Instructions' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'SpecialInstructions',
+ ),
+ 'TransmissionReference' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'OriginalTransmissionRef',
+ ),
+ 'AuthorsPosition' => array(
+ /* This corresponds with 2:85
+ * By-line Title, which needs to be
+ * handled weirdly to correspond
+ * with iptc/exif. */
+ 'map_group' => 'special',
+ 'mode' => XMPReader::MODE_SIMPLE
+ ),
+ 'Credit' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'Source' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'Urgency' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'Category' => array(
+ // Note, this prop is deprecated, but in general
+ // group since it doesn't have a replacement.
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'iimCategory',
+ ),
+ 'SupplementalCategories' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'iimSupplementalCategory',
+ ),
+ 'Headline' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE
+ ),
+ ),
+ 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/' => array(
+ 'CountryCode' => array(
+ 'map_group' => 'deprecated',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'CountryCodeDest',
+ ),
+ 'IntellectualGenre' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ // Note, this is a six digit code.
+ // See: http://cv.iptc.org/newscodes/scene/
+ // Since these aren't really all that common,
+ // we just show the number.
+ 'Scene' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_BAG,
+ 'validate' => 'validateInteger',
+ 'map_name' => 'SceneCode',
+ ),
+ /* Note: SubjectCode should be an 8 ascii digits.
+ * it is not really an integer (has leading 0's,
+ * cannot have a +/- sign), but validateInteger
+ * will let it through.
+ */
+ 'SubjectCode' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'SubjectNewsCode',
+ 'validate' => 'validateInteger'
+ ),
+ 'Location' => array(
+ 'map_group' => 'deprecated',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'map_name' => 'SublocationDest',
+ ),
+ 'CreatorContactInfo' => array(
+ /* Note this maps to 2:118 in iim
+ * (Contact) field. However those field
+ * types are slightly different - 2:118
+ * is free form text field, where this
+ * is more structured.
+ */
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_STRUCT,
+ 'map_name' => 'Contact',
+ 'children' => array(
+ 'CiAdrExtadr' => true,
+ 'CiAdrCity' => true,
+ 'CiAdrCtry' => true,
+ 'CiEmailWork' => true,
+ 'CiTelWork' => true,
+ 'CiAdrPcode' => true,
+ 'CiAdrRegion' => true,
+ 'CiUrlWork' => true,
+ ),
+ ),
+ 'CiAdrExtadr' => array( /* address */
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'CiAdrCity' => array( /* city */
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'CiAdrCtry' => array( /* country */
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'CiEmailWork' => array( /* email (possibly separated by ',') */
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'CiTelWork' => array( /* telephone */
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'CiAdrPcode' => array( /* postal code */
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'CiAdrRegion' => array( /* province/state */
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'CiUrlWork' => array( /* url. Multiple may be separated by comma. */
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ /* End contact info struct properties */
+ ),
+ 'http://iptc.org/std/Iptc4xmpExt/2008-02-29/' => array(
+ 'Event' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ ),
+ 'OrganisationInImageName' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_BAG,
+ 'map_name' => 'OrganisationInImage'
+ ),
+ 'PersonInImage' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_BAG,
+ ),
+ 'MaxAvailHeight' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ 'map_name' => 'OriginalImageHeight',
+ ),
+ 'MaxAvailWidth' => array(
+ 'map_group' => 'general',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'validate' => 'validateInteger',
+ 'map_name' => 'OriginalImageWidth',
+ ),
+ // LocationShown and LocationCreated are handled
+ // specially because they are hierarchical, but we
+ // also want to merge with the old non-hierarchical.
+ 'LocationShown' => array(
+ 'map_group' => 'special',
+ 'mode' => XMPReader::MODE_BAGSTRUCT,
+ 'children' => array(
+ 'WorldRegion' => true,
+ 'CountryCode' => true, /* iso code */
+ 'CountryName' => true,
+ 'ProvinceState' => true,
+ 'City' => true,
+ 'Sublocation' => true,
+ ),
+ ),
+ 'LocationCreated' => array(
+ 'map_group' => 'special',
+ 'mode' => XMPReader::MODE_BAGSTRUCT,
+ 'children' => array(
+ 'WorldRegion' => true,
+ 'CountryCode' => true, /* iso code */
+ 'CountryName' => true,
+ 'ProvinceState' => true,
+ 'City' => true,
+ 'Sublocation' => true,
+ ),
+ ),
+ 'WorldRegion' => array(
+ 'map_group' => 'special',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'CountryCode' => array(
+ 'map_group' => 'special',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'CountryName' => array(
+ 'map_group' => 'special',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ 'map_name' => 'Country',
+ ),
+ 'ProvinceState' => array(
+ 'map_group' => 'special',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ 'map_name' => 'ProvinceOrState',
+ ),
+ 'City' => array(
+ 'map_group' => 'special',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+ 'Sublocation' => array(
+ 'map_group' => 'special',
+ 'mode' => XMPReader::MODE_SIMPLE,
+ 'structPart'=> true,
+ ),
+
+ /* Other props that might be interesting but
+ * Not currently extracted:
+ * ArtworkOrObject, (info about objects in picture)
+ * DigitalSourceType
+ * RegistryId
+ */
+ ),
+
+ /* Plus props we might want to consider:
+ * (Note: some of these have unclear/incomplete definitions
+ * from the iptc4xmp standard).
+ * ImageSupplier (kind of like iptc source field)
+ * ImageSupplierId (id code for image from supplier)
+ * CopyrightOwner
+ * ImageCreator
+ * Licensor
+ * Various model release fields
+ * Property release fields.
+ */
+ );
+}
diff --git a/includes/media/XMPValidate.php b/includes/media/XMPValidate.php
new file mode 100644
index 00000000..0f1d375c
--- /dev/null
+++ b/includes/media/XMPValidate.php
@@ -0,0 +1,323 @@
+<?php
+/**
+* This contains some static methods for
+* validating XMP properties. See XMPInfo and XMPReader classes.
+*
+* Each of these functions take the same parameters
+* * an info array which is a subset of the XMPInfo::items array
+* * A value (passed as reference) to validate. This can be either a
+* simple value or an array
+* * A boolean to determine if this is validating a simple or complex values
+*
+* It should be noted that when an array is being validated, typically the validation
+* function is called once for each value, and then once at the end for the entire array.
+*
+* These validation functions can also be used to modify the data. See the gps and flash one's
+* for example.
+*
+* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf starting at pg 28
+* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf starting at pg 11
+*/
+class XMPValidate {
+ /**
+ * function to validate boolean properties ( True or False )
+ *
+ * @param $info Array information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
+ public static function validateBoolean( $info, &$val, $standalone ) {
+ if ( !$standalone ) {
+ // this only validates standalone properties, not arrays, etc
+ return;
+ }
+ if ( $val !== 'True' && $val !== 'False' ) {
+ wfDebugLog( 'XMP', __METHOD__ . " Expected True or False but got $val" );
+ $val = null;
+ }
+
+ }
+
+ /**
+ * function to validate rational properties ( 12/10 )
+ *
+ * @param $info Array information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
+ public static function validateRational( $info, &$val, $standalone ) {
+ if ( !$standalone ) {
+ // this only validates standalone properties, not arrays, etc
+ return;
+ }
+ if ( !preg_match( '/^(?:-?\d+)\/(?:\d+[1-9]|[1-9]\d*)$/D', $val ) ) {
+ wfDebugLog( 'XMP', __METHOD__ . " Expected rational but got $val" );
+ $val = null;
+ }
+
+ }
+
+ /**
+ * function to validate rating properties -1, 0-5
+ *
+ * if its outside of range put it into range.
+ *
+ * @see MWG spec
+ * @param $info Array information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
+ public static function validateRating( $info, &$val, $standalone ) {
+ if ( !$standalone ) {
+ // this only validates standalone properties, not arrays, etc
+ return;
+ }
+ if ( !preg_match( '/^[-+]?\d*(?:\.?\d*)$/D', $val )
+ || !is_numeric($val)
+ ) {
+ wfDebugLog( 'XMP', __METHOD__ . " Expected rating but got $val" );
+ $val = null;
+ return;
+ } else {
+ $nVal = (float) $val;
+ if ( $nVal < 0 ) {
+ // We do < 0 here instead of < -1 here, since
+ // the values between 0 and -1 are also illegal
+ // as -1 is meant as a special reject rating.
+ wfDebugLog( 'XMP', __METHOD__ . " Rating too low, setting to -1 (Rejected)");
+ $val = '-1';
+ return;
+ }
+ if ( $nVal > 5 ) {
+ wfDebugLog( 'XMP', __METHOD__ . " Rating too high, setting to 5");
+ $val = '5';
+ return;
+ }
+ }
+ }
+
+ /**
+ * function to validate integers
+ *
+ * @param $info Array information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
+ public static function validateInteger( $info, &$val, $standalone ) {
+ if ( !$standalone ) {
+ // this only validates standalone properties, not arrays, etc
+ return;
+ }
+ if ( !preg_match( '/^[-+]?\d+$/D', $val ) ) {
+ wfDebugLog( 'XMP', __METHOD__ . " Expected integer but got $val" );
+ $val = null;
+ }
+
+ }
+
+ /**
+ * function to validate properties with a fixed number of allowed
+ * choices. (closed choice)
+ *
+ * @param $info Array information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
+ public static function validateClosed( $info, &$val, $standalone ) {
+ if ( !$standalone ) {
+ // this only validates standalone properties, not arrays, etc
+ return;
+ }
+
+ //check if its in a numeric range
+ $inRange = false;
+ if ( isset( $info['rangeLow'] )
+ && isset( $info['rangeHigh'] )
+ && is_numeric( $val )
+ && ( intval( $val ) <= $info['rangeHigh'] )
+ && ( intval( $val ) >= $info['rangeLow'] )
+ ) {
+ $inRange = true;
+ }
+
+ if ( !isset( $info['choices'][$val] ) && !$inRange ) {
+ wfDebugLog( 'XMP', __METHOD__ . " Expected closed choice, but got $val" );
+ $val = null;
+ }
+ }
+
+ /**
+ * function to validate and modify flash structure
+ *
+ * @param $info Array information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
+ public static function validateFlash( $info, &$val, $standalone ) {
+ if ( $standalone ) {
+ // this only validates flash structs, not individual properties
+ return;
+ }
+ if ( !( isset( $val['Fired'] )
+ && isset( $val['Function'] )
+ && isset( $val['Mode'] )
+ && isset( $val['RedEyeMode'] )
+ && isset( $val['Return'] )
+ ) ) {
+ wfDebugLog( 'XMP', __METHOD__ . " Flash structure did not have all the required components" );
+ $val = null;
+ } else {
+ $val = ( "\0" | ( $val['Fired'] === 'True' )
+ | ( intval( $val['Return'] ) << 1 )
+ | ( intval( $val['Mode'] ) << 3 )
+ | ( ( $val['Function'] === 'True' ) << 5 )
+ | ( ( $val['RedEyeMode'] === 'True' ) << 6 ) );
+ }
+ }
+
+ /**
+ * function to validate LangCode properties ( en-GB, etc )
+ *
+ * This is just a naive check to make sure it somewhat looks like a lang code.
+ *
+ * @see rfc 3066
+ * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf page 30 (section 8.2.2.5)
+ *
+ * @param $info Array information about current property
+ * @param &$val Mixed current value to validate
+ * @param $standalone Boolean if this is a simple property or array
+ */
+ public static function validateLangCode( $info, &$val, $standalone ) {
+ if ( !$standalone ) {
+ // this only validates standalone properties, not arrays, etc
+ return;
+ }
+ if ( !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $val) ) {
+ //this is a rather naive check.
+ wfDebugLog( 'XMP', __METHOD__ . " Expected Lang code but got $val" );
+ $val = null;
+ }
+
+ }
+
+ /**
+ * function to validate date properties, and convert to Exif format.
+ *
+ * @param $info Array information about current property
+ * @param &$val Mixed current value to validate. Converts to TS_EXIF as a side-effect.
+ * @param $standalone Boolean if this is a simple property or array
+ */
+ public static function validateDate( $info, &$val, $standalone ) {
+ if ( !$standalone ) {
+ // this only validates standalone properties, not arrays, etc
+ return;
+ }
+ $res = array();
+ if ( !preg_match(
+ /* ahh! scary regex... */
+ '/^([0-3]\d{3})(?:-([01]\d)(?:-([0-3]\d)(?:T([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.\d+)?)?([-+]\d{2}:\d{2}|Z)?)?)?)?$/D'
+ , $val, $res)
+ ) {
+ wfDebugLog( 'XMP', __METHOD__ . " Expected date but got $val" );
+ $val = null;
+ } else {
+ /*
+ * $res is formatted as follows:
+ * 0 -> full date.
+ * 1 -> year, 2-> month, 3-> day, 4-> hour, 5-> minute, 6->second
+ * 7-> Timezone specifier (Z or something like +12:30 )
+ * many parts are optional, some aren't. For example if you specify
+ * minute, you must specify hour, day, month, and year but not second or TZ.
+ */
+
+ /*
+ * First of all, if year = 0000, Something is wrongish,
+ * so don't extract. This seems to happen when
+ * some programs convert between metadata formats.
+ */
+ if ( $res[1] === '0000' ) {
+ wfDebugLog( 'XMP', __METHOD__ . " Invalid date (year 0): $val" );
+ $val = null;
+ return;
+ }
+ //if month, etc unspecified, full out as 01.
+ $res[2] = isset( $res[2] ) ? $res[2] : '01'; //month
+ $res[3] = isset( $res[3] ) ? $res[3] : '01'; //day
+ if ( !isset( $res[4] ) ) { //hour
+ //just have the year month day
+ $val = $res[1] . ':' . $res[2] . ':' . $res[3];
+ return;
+ }
+ //if hour is set, so is minute or regex above will fail.
+ //Extra check for empty string necessary due to TZ but no second case.
+ $res[6] = isset( $res[6] ) && $res[6] != '' ? $res[6] : '00';
+
+ if ( !isset( $res[7] ) || $res[7] === 'Z' ) {
+ $val = $res[1] . ':' . $res[2] . ':' . $res[3]
+ . ' ' . $res[4] . ':' . $res[5] . ':' . $res[6];
+ return;
+ }
+
+ //do timezone processing. We've already done the case that tz = Z.
+
+ $unix = wfTimestamp( TS_UNIX, $res[1] . $res[2] . $res[3] . $res[4] . $res[5] . $res[6] );
+ $offset = intval( substr( $res[7], 1, 2 ) ) * 60 * 60;
+ $offset += intval( substr( $res[7], 4, 2 ) ) * 60;
+ if ( substr( $res[7], 0, 1 ) === '-' ) {
+ $offset = -$offset;
+ }
+ $val = wfTimestamp( TS_EXIF, $unix + $offset );
+ }
+
+ }
+
+ /** function to validate, and more importantly
+ * translate the XMP DMS form of gps coords to
+ * the decimal form we use.
+ *
+ * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf
+ * section 1.2.7.4 on page 23
+ *
+ * @param $info Array unused (info about prop)
+ * @param &$val String GPS string in either DDD,MM,SSk or
+ * or DDD,MM.mmk form
+ * @param $standalone Boolean if its a simple prop (should always be true)
+ */
+ public static function validateGPS ( $info, &$val, $standalone ) {
+ if ( !$standalone ) {
+ return;
+ }
+
+ $m = array();
+ if ( preg_match(
+ '/(\d{1,3}),(\d{1,2}),(\d{1,2})([NWSE])/D',
+ $val, $m )
+ ) {
+ $coord = intval( $m[1] );
+ $coord += intval( $m[2] ) * (1/60);
+ $coord += intval( $m[3] ) * (1/3600);
+ if ( $m[4] === 'S' || $m[4] === 'W' ) {
+ $coord = -$coord;
+ }
+ $val = $coord;
+ return;
+ } elseif ( preg_match(
+ '/(\d{1,3}),(\d{1,2}(?:.\d*)?)([NWSE])/D',
+ $val, $m )
+ ) {
+ $coord = intval( $m[1] );
+ $coord += floatval( $m[2] ) * (1/60);
+ if ( $m[3] === 'S' || $m[3] === 'W' ) {
+ $coord = -$coord;
+ }
+ $val = $coord;
+ return;
+
+ } else {
+ wfDebugLog( 'XMP', __METHOD__
+ . " Expected GPSCoordinate, but got $val." );
+ $val = null;
+ return;
+ }
+ }
+}
diff --git a/includes/mime.info b/includes/mime.info
index 3a32f5d9..d705715d 100644
--- a/includes/mime.info
+++ b/includes/mime.info
@@ -10,6 +10,7 @@ image/gif [BITMAP]
image/png image/x-png [BITMAP]
image/ief [BITMAP]
image/jpeg [BITMAP]
+image/jp2 [BITMAP]
image/xbm [BITMAP]
image/tiff [BITMAP]
image/x-icon [BITMAP]
@@ -102,4 +103,4 @@ 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]
-
+application/acad application/x-acad application/autocad_dwg image/x-dwg application/dwg application/x-dwg application/x-autocad image/vnd.dwg drawing/dwg [DRAWING]
diff --git a/includes/mime.types b/includes/mime.types
index bf8c6dd1..9e640322 100644
--- a/includes/mime.types
+++ b/includes/mime.types
@@ -1,3 +1,4 @@
+application/acad dwg
application/andrew-inset ez
application/mac-binhex40 hqx
application/mac-compactpro cpt
@@ -79,6 +80,7 @@ image/bmp bmp
image/cgm cgm
image/gif gif
image/ief ief
+image/jp2 j2k jp2 jpg2
image/jpeg jpeg jpg jpe
image/png png apng
image/svg+xml svg
diff --git a/includes/normal/CleanUpTest.php b/includes/normal/CleanUpTest.php
deleted file mode 100644
index 549a0406..00000000
--- a/includes/normal/CleanUpTest.php
+++ /dev/null
@@ -1,425 +0,0 @@
-<?php
-/**
- * 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' ) {
- die( "Run me from the command line please.\n" );
-}
-
-/** */
-if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
- dl( 'php_utfnormal.so' );
-}
-
-#ini_set( 'memory_limit', '40M' );
-
-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';
-
-/**
- * Additional tests for UtfNormal::cleanUp() function, inclusion
- * regression checks for known problems.
- * Requires PHPUnit.
- *
- * @ingroup UtfNormal
- * @private
- */
-class CleanUpTest extends PHPUnit_Framework_TestCase {
- /** @todo document */
- function setUp() {
- }
-
- /** @todo document */
- function tearDown() {
- }
-
- /** @todo document */
- function testAscii() {
- $text = 'This is plain ASCII text.';
- $this->assertEquals( $text, UtfNormal::cleanUp( $text ) );
- }
-
- /** @todo document */
- function testNull() {
- $text = "a \x00 null";
- $expect = "a \xef\xbf\xbd null";
- $this->assertEquals(
- bin2hex( $expect ),
- bin2hex( UtfNormal::cleanUp( $text ) ) );
- }
-
- /** @todo document */
- function testLatin() {
- $text = "L'\xc3\xa9cole";
- $this->assertEquals( $text, UtfNormal::cleanUp( $text ) );
- }
-
- /** @todo document */
- function testLatinNormal() {
- $text = "L'e\xcc\x81cole";
- $expect = "L'\xc3\xa9cole";
- $this->assertEquals( $expect, UtfNormal::cleanUp( $text ) );
- }
-
- /**
- * This test is *very* expensive!
- * @todo document
- */
- function XtestAllChars() {
- $rep = UTF8_REPLACEMENT;
- for( $i = 0x0; $i < UNICODE_MAX; $i++ ) {
- $char = codepointToUtf8( $i );
- $clean = UtfNormal::cleanUp( $char );
- $x = sprintf( "%04X", $i );
- if( $i % 0x1000 == 0 ) echo "U+$x\n";
- if( $i == 0x0009 ||
- $i == 0x000a ||
- $i == 0x000d ||
- ($i > 0x001f && $i < UNICODE_SURROGATE_FIRST) ||
- ($i > UNICODE_SURROGATE_LAST && $i < 0xfffe ) ||
- ($i > 0xffff && $i <= UNICODE_MAX ) ) {
- if( isset( UtfNormal::$utfCanonicalComp[$char] ) || isset( UtfNormal::$utfCanonicalDecomp[$char] ) ) {
- $comp = UtfNormal::NFC( $char );
- $this->assertEquals(
- bin2hex( $comp ),
- bin2hex( $clean ),
- "U+$x should be decomposed" );
- } else {
- $this->assertEquals(
- bin2hex( $char ),
- bin2hex( $clean ),
- "U+$x should be intact" );
- }
- } else {
- $this->assertEquals( bin2hex( $rep ), bin2hex( $clean ), $x );
- }
- }
- }
-
- /** @todo document */
- function testAllBytes() {
- $this->doTestBytes( '', '' );
- $this->doTestBytes( 'x', '' );
- $this->doTestBytes( '', 'x' );
- $this->doTestBytes( 'x', 'x' );
- }
-
- /** @todo document */
- function doTestBytes( $head, $tail ) {
- for( $i = 0x0; $i < 256; $i++ ) {
- $char = $head . chr( $i ) . $tail;
- $clean = UtfNormal::cleanUp( $char );
- $x = sprintf( "%02X", $i );
- if( $i == 0x0009 ||
- $i == 0x000a ||
- $i == 0x000d ||
- ($i > 0x001f && $i < 0x80) ) {
- $this->assertEquals(
- bin2hex( $char ),
- bin2hex( $clean ),
- "ASCII byte $x should be intact" );
- if( $char != $clean ) return;
- } else {
- $norm = $head . UTF8_REPLACEMENT . $tail;
- $this->assertEquals(
- bin2hex( $norm ),
- bin2hex( $clean ),
- "Forbidden byte $x should be rejected" );
- if( $norm != $clean ) return;
- }
- }
- }
-
- /** @todo document */
- function testDoubleBytes() {
- $this->doTestDoubleBytes( '', '' );
- $this->doTestDoubleBytes( 'x', '' );
- $this->doTestDoubleBytes( '', 'x' );
- $this->doTestDoubleBytes( 'x', 'x' );
- }
-
- /**
- * @todo document
- */
- function doTestDoubleBytes( $head, $tail ) {
- for( $first = 0xc0; $first < 0x100; $first++ ) {
- for( $second = 0x80; $second < 0x100; $second++ ) {
- $char = $head . chr( $first ) . chr( $second ) . $tail;
- $clean = UtfNormal::cleanUp( $char );
- $x = sprintf( "%02X,%02X", $first, $second );
- if( $first > 0xc1 &&
- $first < 0xe0 &&
- $second < 0xc0 ) {
- $norm = UtfNormal::NFC( $char );
- $this->assertEquals(
- bin2hex( $norm ),
- bin2hex( $clean ),
- "Pair $x should be intact" );
- if( $norm != $clean ) return;
- } elseif( $first > 0xfd || $second > 0xbf ) {
- # fe and ff are not legal head bytes -- expect two replacement chars
- $norm = $head . UTF8_REPLACEMENT . UTF8_REPLACEMENT . $tail;
- $this->assertEquals(
- bin2hex( $norm ),
- bin2hex( $clean ),
- "Forbidden pair $x should be rejected" );
- if( $norm != $clean ) return;
- } else {
- $norm = $head . UTF8_REPLACEMENT . $tail;
- $this->assertEquals(
- bin2hex( $norm ),
- bin2hex( $clean ),
- "Forbidden pair $x should be rejected" );
- if( $norm != $clean ) return;
- }
- }
- }
- }
-
- /** @todo document */
- function testTripleBytes() {
- $this->doTestTripleBytes( '', '' );
- $this->doTestTripleBytes( 'x', '' );
- $this->doTestTripleBytes( '', 'x' );
- $this->doTestTripleBytes( 'x', 'x' );
- }
-
- /** @todo document */
- function doTestTripleBytes( $head, $tail ) {
- for( $first = 0xc0; $first < 0x100; $first++ ) {
- for( $second = 0x80; $second < 0x100; $second++ ) {
- #for( $third = 0x80; $third < 0x100; $third++ ) {
- for( $third = 0x80; $third < 0x81; $third++ ) {
- $char = $head . chr( $first ) . chr( $second ) . chr( $third ) . $tail;
- $clean = UtfNormal::cleanUp( $char );
- $x = sprintf( "%02X,%02X,%02X", $first, $second, $third );
- if( $first >= 0xe0 &&
- $first < 0xf0 &&
- $second < 0xc0 &&
- $third < 0xc0 ) {
- if( $first == 0xe0 && $second < 0xa0 ) {
- $this->assertEquals(
- bin2hex( $head . UTF8_REPLACEMENT . $tail ),
- bin2hex( $clean ),
- "Overlong triplet $x should be rejected" );
- } elseif( $first == 0xed &&
- ( chr( $first ) . chr( $second ) . chr( $third )) >= UTF8_SURROGATE_FIRST ) {
- $this->assertEquals(
- bin2hex( $head . UTF8_REPLACEMENT . $tail ),
- bin2hex( $clean ),
- "Surrogate triplet $x should be rejected" );
- } else {
- $this->assertEquals(
- bin2hex( UtfNormal::NFC( $char ) ),
- bin2hex( $clean ),
- "Triplet $x should be intact" );
- }
- } elseif( $first > 0xc1 && $first < 0xe0 && $second < 0xc0 ) {
- $this->assertEquals(
- bin2hex( UtfNormal::NFC( $head . chr( $first ) . chr( $second ) ) . UTF8_REPLACEMENT . $tail ),
- bin2hex( $clean ),
- "Valid 2-byte $x + broken tail" );
- } elseif( $second > 0xc1 && $second < 0xe0 && $third < 0xc0 ) {
- $this->assertEquals(
- bin2hex( $head . UTF8_REPLACEMENT . UtfNormal::NFC( chr( $second ) . chr( $third ) . $tail ) ),
- bin2hex( $clean ),
- "Broken head + valid 2-byte $x" );
- } elseif( ( $first > 0xfd || $second > 0xfd ) &&
- ( ( $second > 0xbf && $third > 0xbf ) ||
- ( $second < 0xc0 && $third < 0xc0 ) ||
- ( $second > 0xfd ) ||
- ( $third > 0xfd ) ) ) {
- # fe and ff are not legal head bytes -- expect three replacement chars
- $this->assertEquals(
- bin2hex( $head . UTF8_REPLACEMENT . UTF8_REPLACEMENT . UTF8_REPLACEMENT . $tail ),
- bin2hex( $clean ),
- "Forbidden triplet $x should be rejected" );
- } elseif( $first > 0xc2 && $second < 0xc0 && $third < 0xc0 ) {
- $this->assertEquals(
- bin2hex( $head . UTF8_REPLACEMENT . $tail ),
- bin2hex( $clean ),
- "Forbidden triplet $x should be rejected" );
- } else {
- $this->assertEquals(
- bin2hex( $head . UTF8_REPLACEMENT . UTF8_REPLACEMENT . $tail ),
- bin2hex( $clean ),
- "Forbidden triplet $x should be rejected" );
- }
- }
- }
- }
- }
-
- /** @todo document */
- function testChunkRegression() {
- # Check for regression against a chunking bug
- $text = "\x46\x55\xb8" .
- "\xdc\x96" .
- "\xee" .
- "\xe7" .
- "\x44" .
- "\xaa" .
- "\x2f\x25";
- $expect = "\x46\x55\xef\xbf\xbd" .
- "\xdc\x96" .
- "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\x44" .
- "\xef\xbf\xbd" .
- "\x2f\x25";
-
- $this->assertEquals(
- bin2hex( $expect ),
- bin2hex( UtfNormal::cleanUp( $text ) ) );
- }
-
- /** @todo document */
- function testInterposeRegression() {
- $text = "\x4e\x30" .
- "\xb1" . # bad tail
- "\x3a" .
- "\x92" . # bad tail
- "\x62\x3a" .
- "\x84" . # bad tail
- "\x43" .
- "\xc6" . # bad head
- "\x3f" .
- "\x92" . # bad tail
- "\xad" . # bad tail
- "\x7d" .
- "\xd9\x95";
-
- $expect = "\x4e\x30" .
- "\xef\xbf\xbd" .
- "\x3a" .
- "\xef\xbf\xbd" .
- "\x62\x3a" .
- "\xef\xbf\xbd" .
- "\x43" .
- "\xef\xbf\xbd" .
- "\x3f" .
- "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\x7d" .
- "\xd9\x95";
-
- $this->assertEquals(
- bin2hex( $expect ),
- bin2hex( UtfNormal::cleanUp( $text ) ) );
- }
-
- /** @todo document */
- function testOverlongRegression() {
- $text = "\x67" .
- "\x1a" . # forbidden ascii
- "\xea" . # bad head
- "\xc1\xa6" . # overlong sequence
- "\xad" . # bad tail
- "\x1c" . # forbidden ascii
- "\xb0" . # bad tail
- "\x3c" .
- "\x9e"; # bad tail
- $expect = "\x67" .
- "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\x3c" .
- "\xef\xbf\xbd";
- $this->assertEquals(
- bin2hex( $expect ),
- bin2hex( UtfNormal::cleanUp( $text ) ) );
- }
-
- /** @todo document */
- function testSurrogateRegression() {
- $text = "\xed\xb4\x96" . # surrogate 0xDD16
- "\x83" . # bad tail
- "\xb4" . # bad tail
- "\xac"; # bad head
- $expect = "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\xef\xbf\xbd";
- $this->assertEquals(
- bin2hex( $expect ),
- bin2hex( UtfNormal::cleanUp( $text ) ) );
- }
-
- /** @todo document */
- function testBomRegression() {
- $text = "\xef\xbf\xbe" . # U+FFFE, illegal char
- "\xb2" . # bad tail
- "\xef" . # bad head
- "\x59";
- $expect = "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\xef\xbf\xbd" .
- "\x59";
- $this->assertEquals(
- bin2hex( $expect ),
- bin2hex( UtfNormal::cleanUp( $text ) ) );
- }
-
- /** @todo document */
- function testForbiddenRegression() {
- $text = "\xef\xbf\xbf"; # U+FFFF, illegal char
- $expect = "\xef\xbf\xbd";
- $this->assertEquals(
- bin2hex( $expect ),
- bin2hex( UtfNormal::cleanUp( $text ) ) );
- }
-
- /** @todo document */
- function testHangulRegression() {
- $text = "\xed\x9c\xaf" . # Hangul char
- "\xe1\x87\x81"; # followed by another final jamo
- $expect = $text; # Should *not* change.
- $this->assertEquals(
- bin2hex( $expect ),
- bin2hex( UtfNormal::cleanUp( $text ) ) );
- }
-}
-
-
-$suite = new PHPUnit_Framework_TestSuite( 'CleanUpTest' );
-$result = PHPUnit_TextUI_TestRunner::run( $suite );
-
-if( !$result->wasSuccessful() ) {
- exit( -1 );
-}
-exit( 0 );
diff --git a/includes/normal/Makefile b/includes/normal/Makefile
index 69ff3da1..f0c340f6 100644
--- a/includes/normal/Makefile
+++ b/includes/normal/Makefile
@@ -5,8 +5,8 @@
## when the data was generated from a previous version.
#BASE=http://www.unicode.org/Public/UNIDATA
-# Explicitly using Unicode 5.1
-BASE=http://www.unicode.org/Public/5.1.0/ucd
+# Explicitly using Unicode 6.0
+BASE=http://www.unicode.org/Public/6.0.0/ucd
# Can override to php-cli or php5 or whatevah
PHP=php
@@ -24,21 +24,17 @@ UtfNormalData.inc : UtfNormalGenerate.php UtfNormalUtil.php UnicodeData.txt Comp
Utf8Case.php : Utf8CaseGenerate.php UtfNormalUtil.php UnicodeData.txt
$(PHP) Utf8CaseGenerate.php
-test : testutf8 testclean UtfNormalTest.php UtfNormalData.inc NormalizationTest.txt
+test : testutf8 UtfNormalTest.php UtfNormalData.inc NormalizationTest.txt
$(PHP) UtfNormalTest.php
testutf8 : Utf8Test.php UTF-8-test.txt
$(PHP) Utf8Test.php
-testclean : CleanUpTest.php
- $(PHP) CleanUpTest.php
-
bench : UtfNormalData.inc testdata/washington.txt testdata/berlin.txt testdata/tokyo.txt testdata/young.txt testdata/bulgakov.txt
$(PHP) UtfNormalBench.php
icutest : UtfNormalData.inc NormalizationTest.txt
$(PHP) Utf8Test.php --icu
- $(PHP) CleanUpTest.php --icu
$(PHP) UtfNormalTest.php --icu
icubench : UtfNormalData.inc testdata/washington.txt testdata/berlin.txt testdata/tokyo.txt testdata/young.txt testdata/bulgakov.txt
diff --git a/includes/normal/Utf8Case.php b/includes/normal/Utf8Case.php
index 89b1a892..abc56e4c 100644
--- a/includes/normal/Utf8Case.php
+++ b/includes/normal/Utf8Case.php
@@ -215,6 +215,8 @@ $wikiUpperChars = array(
'ȱ' => 'Ȱ',
'ȳ' => 'Ȳ',
'ȼ' => 'Ȼ',
+ 'ȿ' => 'Ȿ',
+ 'ɀ' => 'Ɀ',
'ɂ' => 'Ɂ',
'ɇ' => 'Ɇ',
'ɉ' => 'Ɉ',
@@ -223,6 +225,7 @@ $wikiUpperChars = array(
'ɏ' => 'Ɏ',
'ɐ' => 'Ɐ',
'ɑ' => 'Ɑ',
+ 'ɒ' => 'Ɒ',
'ɓ' => 'Ɓ',
'ɔ' => 'Ɔ',
'ɖ' => 'Ɖ',
@@ -231,6 +234,7 @@ $wikiUpperChars = array(
'ɛ' => 'Ɛ',
'ɠ' => 'Ɠ',
'ɣ' => 'Ɣ',
+ 'ɥ' => 'Ɥ',
'ɨ' => 'Ɨ',
'ɩ' => 'Ɩ',
'ɫ' => 'Ɫ',
@@ -453,6 +457,8 @@ $wikiUpperChars = array(
'ԟ' => 'Ԟ',
'ԡ' => 'Ԡ',
'ԣ' => 'Ԣ',
+ 'ԥ' => 'Ԥ',
+ 'ԧ' => 'Ԧ',
'ա' => 'Ա',
'բ' => 'Բ',
'գ' => 'Գ',
@@ -863,6 +869,8 @@ $wikiUpperChars = array(
'ⳟ' => 'Ⳟ',
'ⳡ' => 'Ⳡ',
'ⳣ' => 'Ⳣ',
+ 'ⳬ' => 'Ⳬ',
+ 'ⳮ' => 'Ⳮ',
'ⴀ' => 'Ⴀ',
'ⴁ' => 'Ⴁ',
'ⴂ' => 'Ⴂ',
@@ -917,6 +925,7 @@ $wikiUpperChars = array(
'ꙛ' => 'Ꙛ',
'ꙝ' => 'Ꙝ',
'ꙟ' => 'Ꙟ',
+ 'ꙡ' => 'Ꙡ',
'ꙣ' => 'Ꙣ',
'ꙥ' => 'Ꙥ',
'ꙧ' => 'Ꙧ',
@@ -981,6 +990,12 @@ $wikiUpperChars = array(
'ꞅ' => 'Ꞅ',
'ꞇ' => 'Ꞇ',
'ꞌ' => 'Ꞌ',
+ 'ꞑ' => 'Ꞑ',
+ 'ꞡ' => 'Ꞡ',
+ 'ꞣ' => 'Ꞣ',
+ 'ꞥ' => 'Ꞥ',
+ 'ꞧ' => 'Ꞧ',
+ 'ꞩ' => 'Ꞩ',
'a' => 'A',
'b' => 'B',
'c' => 'C',
@@ -1477,6 +1492,8 @@ $wikiLowerChars = array(
'Ԟ' => 'ԟ',
'Ԡ' => 'ԡ',
'Ԣ' => 'ԣ',
+ 'Ԥ' => 'ԥ',
+ 'Ԧ' => 'ԧ',
'Ա' => 'ա',
'Բ' => 'բ',
'Գ' => 'գ',
@@ -1877,8 +1894,11 @@ $wikiLowerChars = array(
'Ɑ' => 'ɑ',
'Ɱ' => 'ɱ',
'Ɐ' => 'ɐ',
+ 'Ɒ' => 'ɒ',
'Ⱳ' => 'ⱳ',
'Ⱶ' => 'ⱶ',
+ 'Ȿ' => 'ȿ',
+ 'Ɀ' => 'ɀ',
'Ⲁ' => 'ⲁ',
'Ⲃ' => 'ⲃ',
'Ⲅ' => 'ⲅ',
@@ -1929,6 +1949,8 @@ $wikiLowerChars = array(
'Ⳟ' => 'ⳟ',
'Ⳡ' => 'ⳡ',
'Ⳣ' => 'ⳣ',
+ 'Ⳬ' => 'ⳬ',
+ 'Ⳮ' => 'ⳮ',
'Ꙁ' => 'ꙁ',
'Ꙃ' => 'ꙃ',
'Ꙅ' => 'ꙅ',
@@ -1945,6 +1967,7 @@ $wikiLowerChars = array(
'Ꙛ' => 'ꙛ',
'Ꙝ' => 'ꙝ',
'Ꙟ' => 'ꙟ',
+ 'Ꙡ' => 'ꙡ',
'Ꙣ' => 'ꙣ',
'Ꙥ' => 'ꙥ',
'Ꙧ' => 'ꙧ',
@@ -2010,6 +2033,13 @@ $wikiLowerChars = array(
'Ꞅ' => 'ꞅ',
'Ꞇ' => 'ꞇ',
'Ꞌ' => 'ꞌ',
+ 'Ɥ' => 'ɥ',
+ 'Ꞑ' => 'ꞑ',
+ 'Ꞡ' => 'ꞡ',
+ 'Ꞣ' => 'ꞣ',
+ 'Ꞥ' => 'ꞥ',
+ 'Ꞧ' => 'ꞧ',
+ 'Ꞩ' => 'ꞩ',
'A' => 'a',
'B' => 'b',
'C' => 'c',
diff --git a/includes/normal/Utf8CaseGenerate.php b/includes/normal/Utf8CaseGenerate.php
index 8dbff1db..368d0bcd 100644
--- a/includes/normal/Utf8CaseGenerate.php
+++ b/includes/normal/Utf8CaseGenerate.php
@@ -29,6 +29,7 @@ if( php_sapi_name() != 'cli' ) {
die( "Run me from the command line please.\n" );
}
+require_once 'UtfNormalDefines.php';
require_once 'UtfNormalUtil.php';
$in = fopen("UnicodeData.txt", "rt" );
diff --git a/includes/normal/Utf8Test.php b/includes/normal/Utf8Test.php
index 53108bc4..6eae6e72 100644
--- a/includes/normal/Utf8Test.php
+++ b/includes/normal/Utf8Test.php
@@ -26,6 +26,8 @@
*/
/** */
+
+require_once 'UtfNormalDefines.php';
require_once 'UtfNormalUtil.php';
require_once 'UtfNormal.php';
mb_internal_encoding( "utf-8" );
diff --git a/includes/normal/UtfNormal.php b/includes/normal/UtfNormal.php
index 116fb8f0..919278c9 100644
--- a/includes/normal/UtfNormal.php
+++ b/includes/normal/UtfNormal.php
@@ -28,8 +28,6 @@
* @defgroup UtfNormal UtfNormal
*/
-require_once dirname(__FILE__).'/UtfNormalUtil.php';
-
/**
* For using the ICU wrapper
*/
@@ -210,7 +208,7 @@ class UtfNormal {
UtfNormal::loadData();
$len = strlen( $string );
for( $i = 0; $i < $len; $i++ ) {
- $c = $string{$i};
+ $c = $string[$i];
$n = ord( $c );
if( $n < 0x80 ) {
continue;
@@ -301,7 +299,7 @@ class UtfNormal {
foreach( $matches[1] as $str ) {
$chunk = strlen( $str );
- if( $str{0} < "\x80" ) {
+ if( $str[0] < "\x80" ) {
# ASCII chunk: guaranteed to be valid UTF-8
# and in normal form C, so skip over it.
$base += $chunk;
@@ -319,13 +317,13 @@ class UtfNormal {
$len = $chunk + 1; # Counting down is faster. I'm *so* sorry.
for( $i = -1; --$len; ) {
- $remaining = $tailBytes[$c = $str{++$i}];
+ $remaining = $tailBytes[$c = $str[++$i]];
if( $remaining ) {
# UTF-8 head byte!
$sequence = $head = $c;
do {
# Look for the defined number of tail bytes...
- if( --$len && ( $c = $str{++$i} ) >= "\x80" && $c < "\xc0" ) {
+ if( --$len && ( $c = $str[++$i] ) >= "\x80" && $c < "\xc0" ) {
# Legal tail bytes are nice.
$sequence .= $c;
} else {
@@ -513,7 +511,7 @@ class UtfNormal {
$len = strlen( $string );
$out = '';
for( $i = 0; $i < $len; $i++ ) {
- $c = $string{$i};
+ $c = $string[$i];
$n = ord( $c );
if( $n < 0x80 ) {
# ASCII chars never decompose
@@ -540,9 +538,9 @@ class UtfNormal {
# A lookup table would be slightly faster,
# but adds a lot of memory & disk needs.
#
- $index = ( (ord( $c{0} ) & 0x0f) << 12
- | (ord( $c{1} ) & 0x3f) << 6
- | (ord( $c{2} ) & 0x3f) )
+ $index = ( (ord( $c[0] ) & 0x0f) << 12
+ | (ord( $c[1] ) & 0x3f) << 6
+ | (ord( $c[2] ) & 0x3f) )
- UNICODE_HANGUL_FIRST;
$l = intval( $index / UNICODE_HANGUL_NCOUNT );
$v = intval( ($index % UNICODE_HANGUL_NCOUNT) / UNICODE_HANGUL_TCOUNT);
@@ -575,7 +573,7 @@ class UtfNormal {
$combiners = array();
$lastClass = -1;
for( $i = 0; $i < $len; $i++ ) {
- $c = $string{$i};
+ $c = $string[$i];
$n = ord( $c );
if( $n >= 0x80 ) {
if( $n >= 0xf0 ) {
@@ -631,7 +629,7 @@ class UtfNormal {
$x1 = ord(substr(UTF8_HANGUL_VBASE,0,1));
$x2 = ord(substr(UTF8_HANGUL_TEND,0,1));
for( $i = 0; $i < $len; $i++ ) {
- $c = $string{$i};
+ $c = $string[$i];
$n = ord( $c );
if( $n < 0x80 ) {
# No combining characters here...
@@ -691,8 +689,8 @@ class UtfNormal {
#
#$lIndex = utf8ToCodepoint( $startChar ) - UNICODE_HANGUL_LBASE;
#$vIndex = utf8ToCodepoint( $c ) - UNICODE_HANGUL_VBASE;
- $lIndex = ord( $startChar{2} ) - 0x80;
- $vIndex = ord( $c{2} ) - 0xa1;
+ $lIndex = ord( $startChar[2] ) - 0x80;
+ $vIndex = ord( $c[2] ) - 0xa1;
$hangulPoint = UNICODE_HANGUL_FIRST +
UNICODE_HANGUL_TCOUNT *
@@ -710,23 +708,23 @@ class UtfNormal {
$startChar <= UTF8_HANGUL_LAST &&
!$lastHangul ) {
# $tIndex = utf8ToCodepoint( $c ) - UNICODE_HANGUL_TBASE;
- $tIndex = ord( $c{2} ) - 0xa7;
- if( $tIndex < 0 ) $tIndex = ord( $c{2} ) - 0x80 + (0x11c0 - 0x11a7);
+ $tIndex = ord( $c[2] ) - 0xa7;
+ if( $tIndex < 0 ) $tIndex = ord( $c[2] ) - 0x80 + (0x11c0 - 0x11a7);
# Increment the code point by $tIndex, without
# the function overhead of decoding and recoding UTF-8
#
- $tail = ord( $startChar{2} ) + $tIndex;
+ $tail = ord( $startChar[2] ) + $tIndex;
if( $tail > 0xbf ) {
$tail -= 0x40;
- $mid = ord( $startChar{1} ) + 1;
+ $mid = ord( $startChar[1] ) + 1;
if( $mid > 0xbf ) {
- $startChar{0} = chr( ord( $startChar{0} ) + 1 );
+ $startChar[0] = chr( ord( $startChar[0] ) + 1 );
$mid -= 0x40;
}
- $startChar{1} = chr( $mid );
+ $startChar[1] = chr( $mid );
}
- $startChar{2} = chr( $tail );
+ $startChar[2] = chr( $tail );
# If there's another jamo char after this, *don't* try to merge it.
$lastHangul = 1;
@@ -755,7 +753,7 @@ class UtfNormal {
$len = strlen( $string );
$out = '';
for( $i = 0; $i < $len; $i++ ) {
- $out .= $string{$i};
+ $out .= $string[$i];
}
return $out;
}
diff --git a/includes/normal/UtfNormalBench.php b/includes/normal/UtfNormalBench.php
index 2229dbb4..944c4435 100644
--- a/includes/normal/UtfNormalBench.php
+++ b/includes/normal/UtfNormalBench.php
@@ -28,6 +28,7 @@ if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
dl( 'php_utfnormal.so' );
}
+require_once 'UtfNormalDefines.php';
require_once 'UtfNormalUtil.php';
require_once 'UtfNormal.php';
diff --git a/includes/normal/UtfNormalData.inc b/includes/normal/UtfNormalData.inc
index 1d6b4680..68cc1ef4 100644
--- a/includes/normal/UtfNormalData.inc
+++ b/includes/normal/UtfNormalData.inc
@@ -5,9 +5,9 @@
*
* @file
*/
-
-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";}' );
+
+UtfNormal::$utfCombiningClass = unserialize( 'a:606:{s:2:"̀";i:230;s:2:"́";i:230;s:2:"̂";i:230;s:2:"̃";i:230;s:2:"̄";i:230;s:2:"̅";i:230;s:2:"̆";i:230;s:2:"̇";i:230;s:2:"̈";i:230;s:2:"̉";i:230;s:2:"̊";i:230;s:2:"̋";i:230;s:2:"̌";i:230;s:2:"̍";i:230;s:2:"̎";i:230;s:2:"̏";i:230;s:2:"̐";i:230;s:2:"̑";i:230;s:2:"̒";i:230;s:2:"̓";i:230;s:2:"̔";i:230;s:2:"̕";i:232;s:2:"̖";i:220;s:2:"̗";i:220;s:2:"̘";i:220;s:2:"̙";i:220;s:2:"̚";i:232;s:2:"̛";i:216;s:2:"̜";i:220;s:2:"̝";i:220;s:2:"̞";i:220;s:2:"̟";i:220;s:2:"̠";i:220;s:2:"̡";i:202;s:2:"̢";i:202;s:2:"̣";i:220;s:2:"̤";i:220;s:2:"̥";i:220;s:2:"̦";i:220;s:2:"̧";i:202;s:2:"̨";i:202;s:2:"̩";i:220;s:2:"̪";i:220;s:2:"̫";i:220;s:2:"̬";i:220;s:2:"̭";i:220;s:2:"̮";i:220;s:2:"̯";i:220;s:2:"̰";i:220;s:2:"̱";i:220;s:2:"̲";i:220;s:2:"̳";i:220;s:2:"̴";i:1;s:2:"̵";i:1;s:2:"̶";i:1;s:2:"̷";i:1;s:2:"̸";i:1;s:2:"̹";i:220;s:2:"̺";i:220;s:2:"̻";i:220;s:2:"̼";i:220;s:2:"̽";i:230;s:2:"̾";i:230;s:2:"̿";i:230;s:2:"̀";i:230;s:2:"́";i:230;s:2:"͂";i:230;s:2:"̓";i:230;s:2:"̈́";i:230;s:2:"ͅ";i:240;s:2:"͆";i:230;s:2:"͇";i:220;s:2:"͈";i:220;s:2:"͉";i:220;s:2:"͊";i:230;s:2:"͋";i:230;s:2:"͌";i:230;s:2:"͍";i:220;s:2:"͎";i:220;s:2:"͐";i:230;s:2:"͑";i:230;s:2:"͒";i:230;s:2:"͓";i:220;s:2:"͔";i:220;s:2:"͕";i:220;s:2:"͖";i:220;s:2:"͗";i:230;s:2:"͘";i:232;s:2:"͙";i:220;s:2:"͚";i:220;s:2:"͛";i:230;s:2:"͜";i:233;s:2:"͝";i:234;s:2:"͞";i:234;s:2:"͟";i:233;s:2:"͠";i:234;s:2:"͡";i:234;s:2:"͢";i:233;s:2:"ͣ";i:230;s:2:"ͤ";i:230;s:2:"ͥ";i:230;s:2:"ͦ";i:230;s:2:"ͧ";i:230;s:2:"ͨ";i:230;s:2:"ͩ";i:230;s:2:"ͪ";i:230;s:2:"ͫ";i:230;s:2:"ͬ";i:230;s:2:"ͭ";i:230;s:2:"ͮ";i:230;s:2:"ͯ";i:230;s:2:"҃";i:230;s:2:"҄";i:230;s:2:"҅";i:230;s:2:"҆";i:230;s:2:"҇";i:230;s:2:"֑";i:220;s:2:"֒";i:230;s:2:"֓";i:230;s:2:"֔";i:230;s:2:"֕";i:230;s:2:"֖";i:220;s:2:"֗";i:230;s:2:"֘";i:230;s:2:"֙";i:230;s:2:"֚";i:222;s:2:"֛";i:220;s:2:"֜";i:230;s:2:"֝";i:230;s:2:"֞";i:230;s:2:"֟";i:230;s:2:"֠";i:230;s:2:"֡";i:230;s:2:"֢";i:220;s:2:"֣";i:220;s:2:"֤";i:220;s:2:"֥";i:220;s:2:"֦";i:220;s:2:"֧";i:220;s:2:"֨";i:230;s:2:"֩";i:230;s:2:"֪";i:220;s:2:"֫";i:230;s:2:"֬";i:230;s:2:"֭";i:222;s:2:"֮";i:228;s:2:"֯";i:230;s:2:"ְ";i:10;s:2:"ֱ";i:11;s:2:"ֲ";i:12;s:2:"ֳ";i:13;s:2:"ִ";i:14;s:2:"ֵ";i:15;s:2:"ֶ";i:16;s:2:"ַ";i:17;s:2:"ָ";i:18;s:2:"ֹ";i:19;s:2:"ֺ";i:19;s:2:"ֻ";i:20;s:2:"ּ";i:21;s:2:"ֽ";i:22;s:2:"ֿ";i:23;s:2:"ׁ";i:24;s:2:"ׂ";i:25;s:2:"ׄ";i:230;s:2:"ׅ";i:220;s:2:"ׇ";i:18;s:2:"ؐ";i:230;s:2:"ؑ";i:230;s:2:"ؒ";i:230;s:2:"ؓ";i:230;s:2:"ؔ";i:230;s:2:"ؕ";i:230;s:2:"ؖ";i:230;s:2:"ؗ";i:230;s:2:"ؘ";i:30;s:2:"ؙ";i:31;s:2:"ؚ";i:32;s:2:"ً";i:27;s:2:"ٌ";i:28;s:2:"ٍ";i:29;s:2:"َ";i:30;s:2:"ُ";i:31;s:2:"ِ";i:32;s:2:"ّ";i:33;s:2:"ْ";i:34;s:2:"ٓ";i:230;s:2:"ٔ";i:230;s:2:"ٕ";i:220;s:2:"ٖ";i:220;s:2:"ٗ";i:230;s:2:"٘";i:230;s:2:"ٙ";i:230;s:2:"ٚ";i:230;s:2:"ٛ";i:230;s:2:"ٜ";i:220;s:2:"ٝ";i:230;s:2:"ٞ";i:230;s:2:"ٟ";i:220;s:2:"ٰ";i:35;s:2:"ۖ";i:230;s:2:"ۗ";i:230;s:2:"ۘ";i:230;s:2:"ۙ";i:230;s:2:"ۚ";i:230;s:2:"ۛ";i:230;s:2:"ۜ";i:230;s:2:"۟";i:230;s:2:"۠";i:230;s:2:"ۡ";i:230;s:2:"ۢ";i:230;s:2:"ۣ";i:220;s:2:"ۤ";i:230;s:2:"ۧ";i:230;s:2:"ۨ";i:230;s:2:"۪";i:220;s:2:"۫";i:230;s:2:"۬";i:230;s:2:"ۭ";i:220;s:2:"ܑ";i:36;s:2:"ܰ";i:230;s:2:"ܱ";i:220;s:2:"ܲ";i:230;s:2:"ܳ";i:230;s:2:"ܴ";i:220;s:2:"ܵ";i:230;s:2:"ܶ";i:230;s:2:"ܷ";i:220;s:2:"ܸ";i:220;s:2:"ܹ";i:220;s:2:"ܺ";i:230;s:2:"ܻ";i:220;s:2:"ܼ";i:220;s:2:"ܽ";i:230;s:2:"ܾ";i:220;s:2:"ܿ";i:230;s:2:"݀";i:230;s:2:"݁";i:230;s:2:"݂";i:220;s:2:"݃";i:230;s:2:"݄";i:220;s:2:"݅";i:230;s:2:"݆";i:220;s:2:"݇";i:230;s:2:"݈";i:220;s:2:"݉";i:230;s:2:"݊";i:230;s:2:"߫";i:230;s:2:"߬";i:230;s:2:"߭";i:230;s:2:"߮";i:230;s:2:"߯";i:230;s:2:"߰";i:230;s:2:"߱";i:230;s:2:"߲";i:220;s:2:"߳";i:230;s:3:"ࠖ";i:230;s:3:"ࠗ";i:230;s:3:"࠘";i:230;s:3:"࠙";i:230;s:3:"ࠛ";i:230;s:3:"ࠜ";i:230;s:3:"ࠝ";i:230;s:3:"ࠞ";i:230;s:3:"ࠟ";i:230;s:3:"ࠠ";i:230;s:3:"ࠡ";i:230;s:3:"ࠢ";i:230;s:3:"ࠣ";i:230;s:3:"ࠥ";i:230;s:3:"ࠦ";i:230;s:3:"ࠧ";i:230;s:3:"ࠩ";i:230;s:3:"ࠪ";i:230;s:3:"ࠫ";i:230;s:3:"ࠬ";i:230;s:3:"࠭";i:230;s:3:"࡙";i:220;s:3:"࡚";i:220;s:3:"࡛";i:220;s:3:"़";i:7;s:3:"्";i:9;s:3:"॑";i:230;s:3:"॒";i:220;s:3:"॓";i:230;s:3:"॔";i:230;s:3:"়";i:7;s:3:"্";i:9;s:3:"਼";i:7;s:3:"੍";i:9;s:3:"઼";i:7;s:3:"્";i:9;s:3:"଼";i:7;s:3:"୍";i:9;s:3:"்";i:9;s:3:"్";i:9;s:3:"ౕ";i:84;s:3:"ౖ";i:91;s:3:"಼";i:7;s:3:"್";i:9;s:3:"്";i:9;s:3:"්";i:9;s:3:"ุ";i:103;s:3:"ู";i:103;s:3:"ฺ";i:9;s:3:"่";i:107;s:3:"้";i:107;s:3:"๊";i:107;s:3:"๋";i:107;s:3:"ຸ";i:118;s:3:"ູ";i:118;s:3:"່";i:122;s:3:"້";i:122;s:3:"໊";i:122;s:3:"໋";i:122;s:3:"༘";i:220;s:3:"༙";i:220;s:3:"༵";i:220;s:3:"༷";i:220;s:3:"༹";i:216;s:3:"ཱ";i:129;s:3:"ི";i:130;s:3:"ུ";i:132;s:3:"ེ";i:130;s:3:"ཻ";i:130;s:3:"ོ";i:130;s:3:"ཽ";i:130;s:3:"ྀ";i:130;s:3:"ྂ";i:230;s:3:"ྃ";i:230;s:3:"྄";i:9;s:3:"྆";i:230;s:3:"྇";i:230;s:3:"࿆";i:220;s:3:"့";i:7;s:3:"္";i:9;s:3:"်";i:9;s:3:"ႍ";i:220;s:3:"፝";i:230;s:3:"፞";i:230;s:3:"፟";i:230;s:3:"᜔";i:9;s:3:"᜴";i:9;s:3:"្";i:9;s:3:"៝";i:230;s:3:"ᢩ";i:228;s:3:"᤹";i:222;s:3:"᤺";i:230;s:3:"᤻";i:220;s:3:"ᨗ";i:230;s:3:"ᨘ";i:220;s:3:"᩠";i:9;s:3:"᩵";i:230;s:3:"᩶";i:230;s:3:"᩷";i:230;s:3:"᩸";i:230;s:3:"᩹";i:230;s:3:"᩺";i:230;s:3:"᩻";i:230;s:3:"᩼";i:230;s:3:"᩿";i:220;s:3:"᬴";i:7;s:3:"᭄";i:9;s:3:"᭫";i:230;s:3:"᭬";i:220;s:3:"᭭";i:230;s:3:"᭮";i:230;s:3:"᭯";i:230;s:3:"᭰";i:230;s:3:"᭱";i:230;s:3:"᭲";i:230;s:3:"᭳";i:230;s:3:"᮪";i:9;s:3:"᯦";i:7;s:3:"᯲";i:9;s:3:"᯳";i:9;s:3:"᰷";i:7;s:3:"᳐";i:230;s:3:"᳑";i:230;s:3:"᳒";i:230;s:3:"᳔";i:1;s:3:"᳕";i:220;s:3:"᳖";i:220;s:3:"᳗";i:220;s:3:"᳘";i:220;s:3:"᳙";i:220;s:3:"᳚";i:230;s:3:"᳛";i:230;s:3:"᳜";i:220;s:3:"᳝";i:220;s:3:"᳞";i:220;s:3:"᳟";i:220;s:3:"᳠";i:230;s:3:"᳢";i:1;s:3:"᳣";i:1;s:3:"᳤";i:1;s:3:"᳥";i:1;s:3:"᳦";i:1;s:3:"᳧";i:1;s:3:"᳨";i:1;s:3:"᳭";i:220;s:3:"᷀";i:230;s:3:"᷁";i:230;s:3:"᷂";i:220;s:3:"᷃";i:230;s:3:"᷄";i:230;s:3:"᷅";i:230;s:3:"᷆";i:230;s:3:"᷇";i:230;s:3:"᷈";i:230;s:3:"᷉";i:230;s:3:"᷊";i:220;s:3:"᷋";i:230;s:3:"᷌";i:230;s:3:"᷍";i:234;s:3:"᷎";i:214;s:3:"᷏";i:220;s:3:"᷐";i:202;s:3:"᷑";i:230;s:3:"᷒";i:230;s:3:"ᷓ";i:230;s:3:"ᷔ";i:230;s:3:"ᷕ";i:230;s:3:"ᷖ";i:230;s:3:"ᷗ";i:230;s:3:"ᷘ";i:230;s:3:"ᷙ";i:230;s:3:"ᷚ";i:230;s:3:"ᷛ";i:230;s:3:"ᷜ";i:230;s:3:"ᷝ";i:230;s:3:"ᷞ";i:230;s:3:"ᷟ";i:230;s:3:"ᷠ";i:230;s:3:"ᷡ";i:230;s:3:"ᷢ";i:230;s:3:"ᷣ";i:230;s:3:"ᷤ";i:230;s:3:"ᷥ";i:230;s:3:"ᷦ";i:230;s:3:"᷼";i:233;s:3:"᷽";i:220;s:3:"᷾";i:230;s:3:"᷿";i:220;s:3:"⃐";i:230;s:3:"⃑";i:230;s:3:"⃒";i:1;s:3:"⃓";i:1;s:3:"⃔";i:230;s:3:"⃕";i:230;s:3:"⃖";i:230;s:3:"⃗";i:230;s:3:"⃘";i:1;s:3:"⃙";i:1;s:3:"⃚";i:1;s:3:"⃛";i:230;s:3:"⃜";i:230;s:3:"⃡";i:230;s:3:"⃥";i:1;s:3:"⃦";i:1;s:3:"⃧";i:230;s:3:"⃨";i:220;s:3:"⃩";i:230;s:3:"⃪";i:1;s:3:"⃫";i:1;s:3:"⃬";i:220;s:3:"⃭";i:220;s:3:"⃮";i:220;s:3:"⃯";i:220;s:3:"⃰";i:230;s:3:"⳯";i:230;s:3:"⳰";i:230;s:3:"⳱";i:230;s:3:"⵿";i:9;s:3:"ⷠ";i:230;s:3:"ⷡ";i:230;s:3:"ⷢ";i:230;s:3:"ⷣ";i:230;s:3:"ⷤ";i:230;s:3:"ⷥ";i:230;s:3:"ⷦ";i:230;s:3:"ⷧ";i:230;s:3:"ⷨ";i:230;s:3:"ⷩ";i:230;s:3:"ⷪ";i:230;s:3:"ⷫ";i:230;s:3:"ⷬ";i:230;s:3:"ⷭ";i:230;s:3:"ⷮ";i:230;s:3:"ⷯ";i:230;s:3:"ⷰ";i:230;s:3:"ⷱ";i:230;s:3:"ⷲ";i:230;s:3:"ⷳ";i:230;s:3:"ⷴ";i:230;s:3:"ⷵ";i:230;s:3:"ⷶ";i:230;s:3:"ⷷ";i:230;s:3:"ⷸ";i:230;s:3:"ⷹ";i:230;s:3:"ⷺ";i:230;s:3:"ⷻ";i:230;s:3:"ⷼ";i:230;s:3:"ⷽ";i:230;s:3:"ⷾ";i:230;s:3:"ⷿ";i:230;s:3:"〪";i:218;s:3:"〫";i:228;s:3:"〬";i:232;s:3:"〭";i:222;s:3:"〮";i:224;s:3:"〯";i:224;s:3:"゙";i:8;s:3:"゚";i:8;s:3:"꙯";i:230;s:3:"꙼";i:230;s:3:"꙽";i:230;s:3:"꛰";i:230;s:3:"꛱";i:230;s:3:"꠆";i:9;s:3:"꣄";i:9;s:3:"꣠";i:230;s:3:"꣡";i:230;s:3:"꣢";i:230;s:3:"꣣";i:230;s:3:"꣤";i:230;s:3:"꣥";i:230;s:3:"꣦";i:230;s:3:"꣧";i:230;s:3:"꣨";i:230;s:3:"꣩";i:230;s:3:"꣪";i:230;s:3:"꣫";i:230;s:3:"꣬";i:230;s:3:"꣭";i:230;s:3:"꣮";i:230;s:3:"꣯";i:230;s:3:"꣰";i:230;s:3:"꣱";i:230;s:3:"꤫";i:220;s:3:"꤬";i:220;s:3:"꤭";i:220;s:3:"꥓";i:9;s:3:"꦳";i:7;s:3:"꧀";i:9;s:3:"ꪰ";i:230;s:3:"ꪲ";i:230;s:3:"ꪳ";i:230;s:3:"ꪴ";i:220;s:3:"ꪷ";i:230;s:3:"ꪸ";i:230;s:3:"ꪾ";i:230;s:3:"꪿";i:230;s:3:"꫁";i:230;s:3:"꯭";i:9;s:3:"ﬞ";i:26;s:3:"︠";i:230;s:3:"︡";i:230;s:3:"︢";i:230;s:3:"︣";i:230;s:3:"︤";i:230;s:3:"︥";i:230;s:3:"︦";i:230;s:4:"𐇽";i:220;s:4:"𐨍";i:220;s:4:"𐨏";i:230;s:4:"𐨸";i:230;s:4:"𐨹";i:1;s:4:"𐨺";i:220;s:4:"𐨿";i:9;s:4:"𑁆";i:9;s:4:"𑂹";i:9;s:4:"𑂺";i:7;s:4:"𝅥";i:216;s:4:"𝅦";i:216;s:4:"𝅧";i:1;s:4:"𝅨";i:1;s:4:"𝅩";i:1;s:4:"𝅭";i:226;s:4:"𝅮";i:216;s:4:"𝅯";i:216;s:4:"𝅰";i:216;s:4:"𝅱";i:216;s:4:"𝅲";i:216;s:4:"𝅻";i:220;s:4:"𝅼";i:220;s:4:"𝅽";i:220;s:4:"𝅾";i:220;s:4:"𝅿";i:220;s:4:"𝆀";i:220;s:4:"𝆁";i:220;s:4:"𝆂";i:220;s:4:"𝆅";i:230;s:4:"𝆆";i:230;s:4:"𝆇";i:230;s:4:"𝆈";i:230;s:4:"𝆉";i:230;s:4:"𝆊";i:220;s:4:"𝆋";i:220;s:4:"𝆪";i:230;s:4:"𝆫";i:230;s:4:"𝆬";i:230;s:4:"𝆭";i:230;s:4:"𝉂";i:230;s:4:"𝉃";i:230;s:4:"𝉄";i:230;}' );
+UtfNormal::$utfCanonicalComp = unserialize( 'a:1868:{s:3:"À";s:2:"À";s:3:"Á";s:2:"Á";s:3:"Â";s:2:"Â";s:3:"Ã";s:2:"Ã";s:3:"Ä";s:2:"Ä";s:3:"Å";s:2:"Å";s:3:"Ç";s:2:"Ç";s:3:"È";s:2:"È";s:3:"É";s:2:"É";s:3:"Ê";s:2:"Ê";s:3:"Ë";s:2:"Ë";s:3:"Ì";s:2:"Ì";s:3:"Í";s:2:"Í";s:3:"Î";s:2:"Î";s:3:"Ï";s:2:"Ï";s:3:"Ñ";s:2:"Ñ";s:3:"Ò";s:2:"Ò";s:3:"Ó";s:2:"Ó";s:3:"Ô";s:2:"Ô";s:3:"Õ";s:2:"Õ";s:3:"Ö";s:2:"Ö";s:3:"Ù";s:2:"Ù";s:3:"Ú";s:2:"Ú";s:3:"Û";s:2:"Û";s:3:"Ü";s:2:"Ü";s:3:"Ý";s:2:"Ý";s:3:"à";s:2:"à";s:3:"á";s:2:"á";s:3:"â";s:2:"â";s:3:"ã";s:2:"ã";s:3:"ä";s:2:"ä";s:3:"å";s:2:"å";s:3:"ç";s:2:"ç";s:3:"è";s:2:"è";s:3:"é";s:2:"é";s:3:"ê";s:2:"ê";s:3:"ë";s:2:"ë";s:3:"ì";s:2:"ì";s:3:"í";s:2:"í";s:3:"î";s:2:"î";s:3:"ï";s:2:"ï";s:3:"ñ";s:2:"ñ";s:3:"ò";s:2:"ò";s:3:"ó";s:2:"ó";s:3:"ô";s:2:"ô";s:3:"õ";s:2:"õ";s:3:"ö";s:2:"ö";s:3:"ù";s:2:"ù";s:3:"ú";s:2:"ú";s:3:"û";s:2:"û";s:3:"ü";s:2:"ü";s:3:"ý";s:2:"ý";s:3:"ÿ";s:2:"ÿ";s:3:"Ā";s:2:"Ā";s:3:"ā";s:2:"ā";s:3:"Ă";s:2:"Ă";s:3:"ă";s:2:"ă";s:3:"Ą";s:2:"Ą";s:3:"ą";s:2:"ą";s:3:"Ć";s:2:"Ć";s:3:"ć";s:2:"ć";s:3:"Ĉ";s:2:"Ĉ";s:3:"ĉ";s:2:"ĉ";s:3:"Ċ";s:2:"Ċ";s:3:"ċ";s:2:"ċ";s:3:"Č";s:2:"Č";s:3:"č";s:2:"č";s:3:"Ď";s:2:"Ď";s:3:"ď";s:2:"ď";s:3:"Ē";s:2:"Ē";s:3:"ē";s:2:"ē";s:3:"Ĕ";s:2:"Ĕ";s:3:"ĕ";s:2:"ĕ";s:3:"Ė";s:2:"Ė";s:3:"ė";s:2:"ė";s:3:"Ę";s:2:"Ę";s:3:"ę";s:2:"ę";s:3:"Ě";s:2:"Ě";s:3:"ě";s:2:"ě";s:3:"Ĝ";s:2:"Ĝ";s:3:"ĝ";s:2:"ĝ";s:3:"Ğ";s:2:"Ğ";s:3:"ğ";s:2:"ğ";s:3:"Ġ";s:2:"Ġ";s:3:"ġ";s:2:"ġ";s:3:"Ģ";s:2:"Ģ";s:3:"ģ";s:2:"ģ";s:3:"Ĥ";s:2:"Ĥ";s:3:"ĥ";s:2:"ĥ";s:3:"Ĩ";s:2:"Ĩ";s:3:"ĩ";s:2:"ĩ";s:3:"Ī";s:2:"Ī";s:3:"ī";s:2:"ī";s:3:"Ĭ";s:2:"Ĭ";s:3:"ĭ";s:2:"ĭ";s:3:"Į";s:2:"Į";s:3:"į";s:2:"į";s:3:"İ";s:2:"İ";s:3:"Ĵ";s:2:"Ĵ";s:3:"ĵ";s:2:"ĵ";s:3:"Ķ";s:2:"Ķ";s:3:"ķ";s:2:"ķ";s:3:"Ĺ";s:2:"Ĺ";s:3:"ĺ";s:2:"ĺ";s:3:"Ļ";s:2:"Ļ";s:3:"ļ";s:2:"ļ";s:3:"Ľ";s:2:"Ľ";s:3:"ľ";s:2:"ľ";s:3:"Ń";s:2:"Ń";s:3:"ń";s:2:"ń";s:3:"Ņ";s:2:"Ņ";s:3:"ņ";s:2:"ņ";s:3:"Ň";s:2:"Ň";s:3:"ň";s:2:"ň";s:3:"Ō";s:2:"Ō";s:3:"ō";s:2:"ō";s:3:"Ŏ";s:2:"Ŏ";s:3:"ŏ";s:2:"ŏ";s:3:"Ő";s:2:"Ő";s:3:"ő";s:2:"ő";s:3:"Ŕ";s:2:"Ŕ";s:3:"ŕ";s:2:"ŕ";s:3:"Ŗ";s:2:"Ŗ";s:3:"ŗ";s:2:"ŗ";s:3:"Ř";s:2:"Ř";s:3:"ř";s:2:"ř";s:3:"Ś";s:2:"Ś";s:3:"ś";s:2:"ś";s:3:"Ŝ";s:2:"Ŝ";s:3:"ŝ";s:2:"ŝ";s:3:"Ş";s:2:"Ş";s:3:"ş";s:2:"ş";s:3:"Š";s:2:"Š";s:3:"š";s:2:"š";s:3:"Ţ";s:2:"Ţ";s:3:"ţ";s:2:"ţ";s:3:"Ť";s:2:"Ť";s:3:"ť";s:2:"ť";s:3:"Ũ";s:2:"Ũ";s:3:"ũ";s:2:"ũ";s:3:"Ū";s:2:"Ū";s:3:"ū";s:2:"ū";s:3:"Ŭ";s:2:"Ŭ";s:3:"ŭ";s:2:"ŭ";s:3:"Ů";s:2:"Ů";s:3:"ů";s:2:"ů";s:3:"Ű";s:2:"Ű";s:3:"ű";s:2:"ű";s:3:"Ų";s:2:"Ų";s:3:"ų";s:2:"ų";s:3:"Ŵ";s:2:"Ŵ";s:3:"ŵ";s:2:"ŵ";s:3:"Ŷ";s:2:"Ŷ";s:3:"ŷ";s:2:"ŷ";s:3:"Ÿ";s:2:"Ÿ";s:3:"Ź";s:2:"Ź";s:3:"ź";s:2:"ź";s:3:"Ż";s:2:"Ż";s:3:"ż";s:2:"ż";s:3:"Ž";s:2:"Ž";s:3:"ž";s:2:"ž";s:3:"Ơ";s:2:"Ơ";s:3:"ơ";s:2:"ơ";s:3:"Ư";s:2:"Ư";s:3:"ư";s:2:"ư";s:3:"Ǎ";s:2:"Ǎ";s:3:"ǎ";s:2:"ǎ";s:3:"Ǐ";s:2:"Ǐ";s:3:"ǐ";s:2:"ǐ";s:3:"Ǒ";s:2:"Ǒ";s:3:"ǒ";s:2:"ǒ";s:3:"Ǔ";s:2:"Ǔ";s:3:"ǔ";s:2:"ǔ";s:4:"Ǖ";s:2:"Ǖ";s:4:"ǖ";s:2:"ǖ";s:4:"Ǘ";s:2:"Ǘ";s:4:"ǘ";s:2:"ǘ";s:4:"Ǚ";s:2:"Ǚ";s:4:"ǚ";s:2:"ǚ";s:4:"Ǜ";s:2:"Ǜ";s:4:"ǜ";s:2:"ǜ";s:4:"Ǟ";s:2:"Ǟ";s:4:"ǟ";s:2:"ǟ";s:4:"Ǡ";s:2:"Ǡ";s:4:"ǡ";s:2:"ǡ";s:4:"Ǣ";s:2:"Ǣ";s:4:"ǣ";s:2:"ǣ";s:3:"Ǧ";s:2:"Ǧ";s:3:"ǧ";s:2:"ǧ";s:3:"Ǩ";s:2:"Ǩ";s:3:"ǩ";s:2:"ǩ";s:3:"Ǫ";s:2:"Ǫ";s:3:"ǫ";s:2:"ǫ";s:4:"Ǭ";s:2:"Ǭ";s:4:"ǭ";s:2:"ǭ";s:4:"Ǯ";s:2:"Ǯ";s:4:"ǯ";s:2:"ǯ";s:3:"ǰ";s:2:"ǰ";s:3:"Ǵ";s:2:"Ǵ";s:3:"ǵ";s:2:"ǵ";s:3:"Ǹ";s:2:"Ǹ";s:3:"ǹ";s:2:"ǹ";s:4:"Ǻ";s:2:"Ǻ";s:4:"ǻ";s:2:"ǻ";s:4:"Ǽ";s:2:"Ǽ";s:4:"ǽ";s:2:"ǽ";s:4:"Ǿ";s:2:"Ǿ";s:4:"ǿ";s:2:"ǿ";s:3:"Ȁ";s:2:"Ȁ";s:3:"ȁ";s:2:"ȁ";s:3:"Ȃ";s:2:"Ȃ";s:3:"ȃ";s:2:"ȃ";s:3:"Ȅ";s:2:"Ȅ";s:3:"ȅ";s:2:"ȅ";s:3:"Ȇ";s:2:"Ȇ";s:3:"ȇ";s:2:"ȇ";s:3:"Ȉ";s:2:"Ȉ";s:3:"ȉ";s:2:"ȉ";s:3:"Ȋ";s:2:"Ȋ";s:3:"ȋ";s:2:"ȋ";s:3:"Ȍ";s:2:"Ȍ";s:3:"ȍ";s:2:"ȍ";s:3:"Ȏ";s:2:"Ȏ";s:3:"ȏ";s:2:"ȏ";s:3:"Ȑ";s:2:"Ȑ";s:3:"ȑ";s:2:"ȑ";s:3:"Ȓ";s:2:"Ȓ";s:3:"ȓ";s:2:"ȓ";s:3:"Ȕ";s:2:"Ȕ";s:3:"ȕ";s:2:"ȕ";s:3:"Ȗ";s:2:"Ȗ";s:3:"ȗ";s:2:"ȗ";s:3:"Ș";s:2:"Ș";s:3:"ș";s:2:"ș";s:3:"Ț";s:2:"Ț";s:3:"ț";s:2:"ț";s:3:"Ȟ";s:2:"Ȟ";s:3:"ȟ";s:2:"ȟ";s:3:"Ȧ";s:2:"Ȧ";s:3:"ȧ";s:2:"ȧ";s:3:"Ȩ";s:2:"Ȩ";s:3:"ȩ";s:2:"ȩ";s:4:"Ȫ";s:2:"Ȫ";s:4:"ȫ";s:2:"ȫ";s:4:"Ȭ";s:2:"Ȭ";s:4:"ȭ";s:2:"ȭ";s:3:"Ȯ";s:2:"Ȯ";s:3:"ȯ";s:2:"ȯ";s:4:"Ȱ";s:2:"Ȱ";s:4:"ȱ";s:2:"ȱ";s:3:"Ȳ";s:2:"Ȳ";s:3:"ȳ";s:2:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:4:"̈́";s:2:"̈́";s:2:"ʹ";s:2:"ʹ";s:1:";";s:2:";";s:4:"΅";s:2:"΅";s:4:"Ά";s:2:"Ά";s:2:"·";s:2:"·";s:4:"Έ";s:2:"Έ";s:4:"Ή";s:2:"Ή";s:4:"Ί";s:2:"Ί";s:4:"Ό";s:2:"Ό";s:4:"Ύ";s:2:"Ύ";s:4:"Ώ";s:2:"Ώ";s:4:"ΐ";s:2:"ΐ";s:4:"Ϊ";s:2:"Ϊ";s:4:"Ϋ";s:2:"Ϋ";s:4:"ά";s:2:"ά";s:4:"έ";s:2:"έ";s:4:"ή";s:2:"ή";s:4:"ί";s:2:"ί";s:4:"ΰ";s:2:"ΰ";s:4:"ϊ";s:2:"ϊ";s:4:"ϋ";s:2:"ϋ";s:4:"ό";s:2:"ό";s:4:"ύ";s:2:"ύ";s:4:"ώ";s:2:"ώ";s:4:"ϓ";s:2:"ϓ";s:4:"ϔ";s:2:"ϔ";s:4:"Ѐ";s:2:"Ѐ";s:4:"Ё";s:2:"Ё";s:4:"Ѓ";s:2:"Ѓ";s:4:"Ї";s:2:"Ї";s:4:"Ќ";s:2:"Ќ";s:4:"Ѝ";s:2:"Ѝ";s:4:"Ў";s:2:"Ў";s:4:"Й";s:2:"Й";s:4:"й";s:2:"й";s:4:"ѐ";s:2:"ѐ";s:4:"ё";s:2:"ё";s:4:"ѓ";s:2:"ѓ";s:4:"ї";s:2:"ї";s:4:"ќ";s:2:"ќ";s:4:"ѝ";s:2:"ѝ";s:4:"ў";s:2:"ў";s:4:"Ѷ";s:2:"Ѷ";s:4:"ѷ";s:2:"ѷ";s:4:"Ӂ";s:2:"Ӂ";s:4:"ӂ";s:2:"ӂ";s:4:"Ӑ";s:2:"Ӑ";s:4:"ӑ";s:2:"ӑ";s:4:"Ӓ";s:2:"Ӓ";s:4:"ӓ";s:2:"ӓ";s:4:"Ӗ";s:2:"Ӗ";s:4:"ӗ";s:2:"ӗ";s:4:"Ӛ";s:2:"Ӛ";s:4:"ӛ";s:2:"ӛ";s:4:"Ӝ";s:2:"Ӝ";s:4:"ӝ";s:2:"ӝ";s:4:"Ӟ";s:2:"Ӟ";s:4:"ӟ";s:2:"ӟ";s:4:"Ӣ";s:2:"Ӣ";s:4:"ӣ";s:2:"ӣ";s:4:"Ӥ";s:2:"Ӥ";s:4:"ӥ";s:2:"ӥ";s:4:"Ӧ";s:2:"Ӧ";s:4:"ӧ";s:2:"ӧ";s:4:"Ӫ";s:2:"Ӫ";s:4:"ӫ";s:2:"ӫ";s:4:"Ӭ";s:2:"Ӭ";s:4:"ӭ";s:2:"ӭ";s:4:"Ӯ";s:2:"Ӯ";s:4:"ӯ";s:2:"ӯ";s:4:"Ӱ";s:2:"Ӱ";s:4:"ӱ";s:2:"ӱ";s:4:"Ӳ";s:2:"Ӳ";s:4:"ӳ";s:2:"ӳ";s:4:"Ӵ";s:2:"Ӵ";s:4:"ӵ";s:2:"ӵ";s:4:"Ӹ";s:2:"Ӹ";s:4:"ӹ";s:2:"ӹ";s:4:"آ";s:2:"آ";s:4:"أ";s:2:"أ";s:4:"ؤ";s:2:"ؤ";s:4:"إ";s:2:"إ";s:4:"ئ";s:2:"ئ";s:4:"ۀ";s:2:"ۀ";s:4:"ۂ";s:2:"ۂ";s:4:"ۓ";s:2:"ۓ";s:6:"ऩ";s:3:"ऩ";s:6:"ऱ";s:3:"ऱ";s:6:"ऴ";s:3:"ऴ";s:6:"ো";s:3:"ো";s:6:"ৌ";s:3:"ৌ";s:6:"ୈ";s:3:"ୈ";s:6:"ୋ";s:3:"ୋ";s:6:"ୌ";s:3:"ୌ";s:6:"ஔ";s:3:"ஔ";s:6:"ொ";s:3:"ொ";s:6:"ோ";s:3:"ோ";s:6:"ௌ";s:3:"ௌ";s:6:"ై";s:3:"ై";s:6:"ೀ";s:3:"ೀ";s:6:"ೇ";s:3:"ೇ";s:6:"ೈ";s:3:"ೈ";s:6:"ೊ";s:3:"ೊ";s:6:"ೋ";s:3:"ೋ";s:6:"ൊ";s:3:"ൊ";s:6:"ോ";s:3:"ോ";s:6:"ൌ";s:3:"ൌ";s:6:"ේ";s:3:"ේ";s:6:"ො";s:3:"ො";s:6:"ෝ";s:3:"ෝ";s:6:"ෞ";s:3:"ෞ";s:6:"ཱི";s:3:"ཱི";s:6:"ཱུ";s:3:"ཱུ";s:6:"ཱྀ";s:3:"ཱྀ";s:6:"ဦ";s:3:"ဦ";s:6:"ᬆ";s:3:"ᬆ";s:6:"ᬈ";s:3:"ᬈ";s:6:"ᬊ";s:3:"ᬊ";s:6:"ᬌ";s:3:"ᬌ";s:6:"ᬎ";s:3:"ᬎ";s:6:"ᬒ";s:3:"ᬒ";s:6:"ᬻ";s:3:"ᬻ";s:6:"ᬽ";s:3:"ᬽ";s:6:"ᭀ";s:3:"ᭀ";s:6:"ᭁ";s:3:"ᭁ";s:6:"ᭃ";s:3:"ᭃ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:4:"Ḉ";s:3:"Ḉ";s:4:"ḉ";s:3:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:4:"Ḕ";s:3:"Ḕ";s:4:"ḕ";s:3:"ḕ";s:4:"Ḗ";s:3:"Ḗ";s:4:"ḗ";s:3:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:4:"Ḝ";s:3:"Ḝ";s:4:"ḝ";s:3:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:4:"Ḯ";s:3:"Ḯ";s:4:"ḯ";s:3:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:5:"Ḹ";s:3:"Ḹ";s:5:"ḹ";s:3:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:4:"Ṍ";s:3:"Ṍ";s:4:"ṍ";s:3:"ṍ";s:4:"Ṏ";s:3:"Ṏ";s:4:"ṏ";s:3:"ṏ";s:4:"Ṑ";s:3:"Ṑ";s:4:"ṑ";s:3:"ṑ";s:4:"Ṓ";s:3:"Ṓ";s:4:"ṓ";s:3:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:5:"Ṝ";s:3:"Ṝ";s:5:"ṝ";s:3:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:4:"Ṥ";s:3:"Ṥ";s:4:"ṥ";s:3:"ṥ";s:4:"Ṧ";s:3:"Ṧ";s:4:"ṧ";s:3:"ṧ";s:5:"Ṩ";s:3:"Ṩ";s:5:"ṩ";s:3:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:4:"Ṹ";s:3:"Ṹ";s:4:"ṹ";s:3:"ṹ";s:4:"Ṻ";s:3:"Ṻ";s:4:"ṻ";s:3:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:4:"ẛ";s:3:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:4:"Ấ";s:3:"Ấ";s:4:"ấ";s:3:"ấ";s:4:"Ầ";s:3:"Ầ";s:4:"ầ";s:3:"ầ";s:4:"Ẩ";s:3:"Ẩ";s:4:"ẩ";s:3:"ẩ";s:4:"Ẫ";s:3:"Ẫ";s:4:"ẫ";s:3:"ẫ";s:5:"Ậ";s:3:"Ậ";s:5:"ậ";s:3:"ậ";s:4:"Ắ";s:3:"Ắ";s:4:"ắ";s:3:"ắ";s:4:"Ằ";s:3:"Ằ";s:4:"ằ";s:3:"ằ";s:4:"Ẳ";s:3:"Ẳ";s:4:"ẳ";s:3:"ẳ";s:4:"Ẵ";s:3:"Ẵ";s:4:"ẵ";s:3:"ẵ";s:5:"Ặ";s:3:"Ặ";s:5:"ặ";s:3:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:4:"Ế";s:3:"Ế";s:4:"ế";s:3:"ế";s:4:"Ề";s:3:"Ề";s:4:"ề";s:3:"ề";s:4:"Ể";s:3:"Ể";s:4:"ể";s:3:"ể";s:4:"Ễ";s:3:"Ễ";s:4:"ễ";s:3:"ễ";s:5:"Ệ";s:3:"Ệ";s:5:"ệ";s:3:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:4:"Ố";s:3:"Ố";s:4:"ố";s:3:"ố";s:4:"Ồ";s:3:"Ồ";s:4:"ồ";s:3:"ồ";s:4:"Ổ";s:3:"Ổ";s:4:"ổ";s:3:"ổ";s:4:"Ỗ";s:3:"Ỗ";s:4:"ỗ";s:3:"ỗ";s:5:"Ộ";s:3:"Ộ";s:5:"ộ";s:3:"ộ";s:4:"Ớ";s:3:"Ớ";s:4:"ớ";s:3:"ớ";s:4:"Ờ";s:3:"Ờ";s:4:"ờ";s:3:"ờ";s:4:"Ở";s:3:"Ở";s:4:"ở";s:3:"ở";s:4:"Ỡ";s:3:"Ỡ";s:4:"ỡ";s:3:"ỡ";s:4:"Ợ";s:3:"Ợ";s:4:"ợ";s:3:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:4:"Ứ";s:3:"Ứ";s:4:"ứ";s:3:"ứ";s:4:"Ừ";s:3:"Ừ";s:4:"ừ";s:3:"ừ";s:4:"Ử";s:3:"Ử";s:4:"ử";s:3:"ử";s:4:"Ữ";s:3:"Ữ";s:4:"ữ";s:3:"ữ";s:4:"Ự";s:3:"Ự";s:4:"ự";s:3:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:4:"ἀ";s:3:"ἀ";s:4:"ἁ";s:3:"ἁ";s:5:"ἂ";s:3:"ἂ";s:5:"ἃ";s:3:"ἃ";s:5:"ἄ";s:3:"ἄ";s:5:"ἅ";s:3:"ἅ";s:5:"ἆ";s:3:"ἆ";s:5:"ἇ";s:3:"ἇ";s:4:"Ἀ";s:3:"Ἀ";s:4:"Ἁ";s:3:"Ἁ";s:5:"Ἂ";s:3:"Ἂ";s:5:"Ἃ";s:3:"Ἃ";s:5:"Ἄ";s:3:"Ἄ";s:5:"Ἅ";s:3:"Ἅ";s:5:"Ἆ";s:3:"Ἆ";s:5:"Ἇ";s:3:"Ἇ";s:4:"ἐ";s:3:"ἐ";s:4:"ἑ";s:3:"ἑ";s:5:"ἒ";s:3:"ἒ";s:5:"ἓ";s:3:"ἓ";s:5:"ἔ";s:3:"ἔ";s:5:"ἕ";s:3:"ἕ";s:4:"Ἐ";s:3:"Ἐ";s:4:"Ἑ";s:3:"Ἑ";s:5:"Ἒ";s:3:"Ἒ";s:5:"Ἓ";s:3:"Ἓ";s:5:"Ἔ";s:3:"Ἔ";s:5:"Ἕ";s:3:"Ἕ";s:4:"ἠ";s:3:"ἠ";s:4:"ἡ";s:3:"ἡ";s:5:"ἢ";s:3:"ἢ";s:5:"ἣ";s:3:"ἣ";s:5:"ἤ";s:3:"ἤ";s:5:"ἥ";s:3:"ἥ";s:5:"ἦ";s:3:"ἦ";s:5:"ἧ";s:3:"ἧ";s:4:"Ἠ";s:3:"Ἠ";s:4:"Ἡ";s:3:"Ἡ";s:5:"Ἢ";s:3:"Ἢ";s:5:"Ἣ";s:3:"Ἣ";s:5:"Ἤ";s:3:"Ἤ";s:5:"Ἥ";s:3:"Ἥ";s:5:"Ἦ";s:3:"Ἦ";s:5:"Ἧ";s:3:"Ἧ";s:4:"ἰ";s:3:"ἰ";s:4:"ἱ";s:3:"ἱ";s:5:"ἲ";s:3:"ἲ";s:5:"ἳ";s:3:"ἳ";s:5:"ἴ";s:3:"ἴ";s:5:"ἵ";s:3:"ἵ";s:5:"ἶ";s:3:"ἶ";s:5:"ἷ";s:3:"ἷ";s:4:"Ἰ";s:3:"Ἰ";s:4:"Ἱ";s:3:"Ἱ";s:5:"Ἲ";s:3:"Ἲ";s:5:"Ἳ";s:3:"Ἳ";s:5:"Ἴ";s:3:"Ἴ";s:5:"Ἵ";s:3:"Ἵ";s:5:"Ἶ";s:3:"Ἶ";s:5:"Ἷ";s:3:"Ἷ";s:4:"ὀ";s:3:"ὀ";s:4:"ὁ";s:3:"ὁ";s:5:"ὂ";s:3:"ὂ";s:5:"ὃ";s:3:"ὃ";s:5:"ὄ";s:3:"ὄ";s:5:"ὅ";s:3:"ὅ";s:4:"Ὀ";s:3:"Ὀ";s:4:"Ὁ";s:3:"Ὁ";s:5:"Ὂ";s:3:"Ὂ";s:5:"Ὃ";s:3:"Ὃ";s:5:"Ὄ";s:3:"Ὄ";s:5:"Ὅ";s:3:"Ὅ";s:4:"ὐ";s:3:"ὐ";s:4:"ὑ";s:3:"ὑ";s:5:"ὒ";s:3:"ὒ";s:5:"ὓ";s:3:"ὓ";s:5:"ὔ";s:3:"ὔ";s:5:"ὕ";s:3:"ὕ";s:5:"ὖ";s:3:"ὖ";s:5:"ὗ";s:3:"ὗ";s:4:"Ὑ";s:3:"Ὑ";s:5:"Ὓ";s:3:"Ὓ";s:5:"Ὕ";s:3:"Ὕ";s:5:"Ὗ";s:3:"Ὗ";s:4:"ὠ";s:3:"ὠ";s:4:"ὡ";s:3:"ὡ";s:5:"ὢ";s:3:"ὢ";s:5:"ὣ";s:3:"ὣ";s:5:"ὤ";s:3:"ὤ";s:5:"ὥ";s:3:"ὥ";s:5:"ὦ";s:3:"ὦ";s:5:"ὧ";s:3:"ὧ";s:4:"Ὠ";s:3:"Ὠ";s:4:"Ὡ";s:3:"Ὡ";s:5:"Ὢ";s:3:"Ὢ";s:5:"Ὣ";s:3:"Ὣ";s:5:"Ὤ";s:3:"Ὤ";s:5:"Ὥ";s:3:"Ὥ";s:5:"Ὦ";s:3:"Ὦ";s:5:"Ὧ";s:3:"Ὧ";s:4:"ὰ";s:3:"ὰ";s:2:"ά";s:3:"ά";s:4:"ὲ";s:3:"ὲ";s:2:"έ";s:3:"έ";s:4:"ὴ";s:3:"ὴ";s:2:"ή";s:3:"ή";s:4:"ὶ";s:3:"ὶ";s:2:"ί";s:3:"ί";s:4:"ὸ";s:3:"ὸ";s:2:"ό";s:3:"ό";s:4:"ὺ";s:3:"ὺ";s:2:"ύ";s:3:"ύ";s:4:"ὼ";s:3:"ὼ";s:2:"ώ";s:3:"ώ";s:5:"ᾀ";s:3:"ᾀ";s:5:"ᾁ";s:3:"ᾁ";s:5:"ᾂ";s:3:"ᾂ";s:5:"ᾃ";s:3:"ᾃ";s:5:"ᾄ";s:3:"ᾄ";s:5:"ᾅ";s:3:"ᾅ";s:5:"ᾆ";s:3:"ᾆ";s:5:"ᾇ";s:3:"ᾇ";s:5:"ᾈ";s:3:"ᾈ";s:5:"ᾉ";s:3:"ᾉ";s:5:"ᾊ";s:3:"ᾊ";s:5:"ᾋ";s:3:"ᾋ";s:5:"ᾌ";s:3:"ᾌ";s:5:"ᾍ";s:3:"ᾍ";s:5:"ᾎ";s:3:"ᾎ";s:5:"ᾏ";s:3:"ᾏ";s:5:"ᾐ";s:3:"ᾐ";s:5:"ᾑ";s:3:"ᾑ";s:5:"ᾒ";s:3:"ᾒ";s:5:"ᾓ";s:3:"ᾓ";s:5:"ᾔ";s:3:"ᾔ";s:5:"ᾕ";s:3:"ᾕ";s:5:"ᾖ";s:3:"ᾖ";s:5:"ᾗ";s:3:"ᾗ";s:5:"ᾘ";s:3:"ᾘ";s:5:"ᾙ";s:3:"ᾙ";s:5:"ᾚ";s:3:"ᾚ";s:5:"ᾛ";s:3:"ᾛ";s:5:"ᾜ";s:3:"ᾜ";s:5:"ᾝ";s:3:"ᾝ";s:5:"ᾞ";s:3:"ᾞ";s:5:"ᾟ";s:3:"ᾟ";s:5:"ᾠ";s:3:"ᾠ";s:5:"ᾡ";s:3:"ᾡ";s:5:"ᾢ";s:3:"ᾢ";s:5:"ᾣ";s:3:"ᾣ";s:5:"ᾤ";s:3:"ᾤ";s:5:"ᾥ";s:3:"ᾥ";s:5:"ᾦ";s:3:"ᾦ";s:5:"ᾧ";s:3:"ᾧ";s:5:"ᾨ";s:3:"ᾨ";s:5:"ᾩ";s:3:"ᾩ";s:5:"ᾪ";s:3:"ᾪ";s:5:"ᾫ";s:3:"ᾫ";s:5:"ᾬ";s:3:"ᾬ";s:5:"ᾭ";s:3:"ᾭ";s:5:"ᾮ";s:3:"ᾮ";s:5:"ᾯ";s:3:"ᾯ";s:4:"ᾰ";s:3:"ᾰ";s:4:"ᾱ";s:3:"ᾱ";s:5:"ᾲ";s:3:"ᾲ";s:4:"ᾳ";s:3:"ᾳ";s:4:"ᾴ";s:3:"ᾴ";s:4:"ᾶ";s:3:"ᾶ";s:5:"ᾷ";s:3:"ᾷ";s:4:"Ᾰ";s:3:"Ᾰ";s:4:"Ᾱ";s:3:"Ᾱ";s:4:"Ὰ";s:3:"Ὰ";s:2:"Ά";s:3:"Ά";s:4:"ᾼ";s:3:"ᾼ";s:2:"ι";s:3:"ι";s:4:"῁";s:3:"῁";s:5:"ῂ";s:3:"ῂ";s:4:"ῃ";s:3:"ῃ";s:4:"ῄ";s:3:"ῄ";s:4:"ῆ";s:3:"ῆ";s:5:"ῇ";s:3:"ῇ";s:4:"Ὲ";s:3:"Ὲ";s:2:"Έ";s:3:"Έ";s:4:"Ὴ";s:3:"Ὴ";s:2:"Ή";s:3:"Ή";s:4:"ῌ";s:3:"ῌ";s:5:"῍";s:3:"῍";s:5:"῎";s:3:"῎";s:5:"῏";s:3:"῏";s:4:"ῐ";s:3:"ῐ";s:4:"ῑ";s:3:"ῑ";s:4:"ῒ";s:3:"ῒ";s:2:"ΐ";s:3:"ΐ";s:4:"ῖ";s:3:"ῖ";s:4:"ῗ";s:3:"ῗ";s:4:"Ῐ";s:3:"Ῐ";s:4:"Ῑ";s:3:"Ῑ";s:4:"Ὶ";s:3:"Ὶ";s:2:"Ί";s:3:"Ί";s:5:"῝";s:3:"῝";s:5:"῞";s:3:"῞";s:5:"῟";s:3:"῟";s:4:"ῠ";s:3:"ῠ";s:4:"ῡ";s:3:"ῡ";s:4:"ῢ";s:3:"ῢ";s:2:"ΰ";s:3:"ΰ";s:4:"ῤ";s:3:"ῤ";s:4:"ῥ";s:3:"ῥ";s:4:"ῦ";s:3:"ῦ";s:4:"ῧ";s:3:"ῧ";s:4:"Ῠ";s:3:"Ῠ";s:4:"Ῡ";s:3:"Ῡ";s:4:"Ὺ";s:3:"Ὺ";s:2:"Ύ";s:3:"Ύ";s:4:"Ῥ";s:3:"Ῥ";s:4:"῭";s:3:"῭";s:2:"΅";s:3:"΅";s:1:"`";s:3:"`";s:5:"ῲ";s:3:"ῲ";s:4:"ῳ";s:3:"ῳ";s:4:"ῴ";s:3:"ῴ";s:4:"ῶ";s:3:"ῶ";s:5:"ῷ";s:3:"ῷ";s:4:"Ὸ";s:3:"Ὸ";s:2:"Ό";s:3:"Ό";s:4:"Ὼ";s:3:"Ὼ";s:2:"Ώ";s:3:"Ώ";s:4:"ῼ";s:3:"ῼ";s:2:"´";s:3:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:2:"Ω";s:3:"Ω";s:1:"K";s:3:"K";s:2:"Å";s:3:"Å";s:5:"↚";s:3:"↚";s:5:"↛";s:3:"↛";s:5:"↮";s:3:"↮";s:5:"⇍";s:3:"⇍";s:5:"⇎";s:3:"⇎";s:5:"⇏";s:3:"⇏";s:5:"∄";s:3:"∄";s:5:"∉";s:3:"∉";s:5:"∌";s:3:"∌";s:5:"∤";s:3:"∤";s:5:"∦";s:3:"∦";s:5:"≁";s:3:"≁";s:5:"≄";s:3:"≄";s:5:"≇";s:3:"≇";s:5:"≉";s:3:"≉";s:3:"≠";s:3:"≠";s:5:"≢";s:3:"≢";s:5:"≭";s:3:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:5:"≰";s:3:"≰";s:5:"≱";s:3:"≱";s:5:"≴";s:3:"≴";s:5:"≵";s:3:"≵";s:5:"≸";s:3:"≸";s:5:"≹";s:3:"≹";s:5:"⊀";s:3:"⊀";s:5:"⊁";s:3:"⊁";s:5:"⊄";s:3:"⊄";s:5:"⊅";s:3:"⊅";s:5:"⊈";s:3:"⊈";s:5:"⊉";s:3:"⊉";s:5:"⊬";s:3:"⊬";s:5:"⊭";s:3:"⊭";s:5:"⊮";s:3:"⊮";s:5:"⊯";s:3:"⊯";s:5:"⋠";s:3:"⋠";s:5:"⋡";s:3:"⋡";s:5:"⋢";s:3:"⋢";s:5:"⋣";s:3:"⋣";s:5:"⋪";s:3:"⋪";s:5:"⋫";s:3:"⋫";s:5:"⋬";s:3:"⋬";s:5:"⋭";s:3:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:6:"が";s:3:"が";s:6:"ぎ";s:3:"ぎ";s:6:"ぐ";s:3:"ぐ";s:6:"げ";s:3:"げ";s:6:"ご";s:3:"ご";s:6:"ざ";s:3:"ざ";s:6:"じ";s:3:"じ";s:6:"ず";s:3:"ず";s:6:"ぜ";s:3:"ぜ";s:6:"ぞ";s:3:"ぞ";s:6:"だ";s:3:"だ";s:6:"ぢ";s:3:"ぢ";s:6:"づ";s:3:"づ";s:6:"で";s:3:"で";s:6:"ど";s:3:"ど";s:6:"ば";s:3:"ば";s:6:"ぱ";s:3:"ぱ";s:6:"び";s:3:"び";s:6:"ぴ";s:3:"ぴ";s:6:"ぶ";s:3:"ぶ";s:6:"ぷ";s:3:"ぷ";s:6:"べ";s:3:"べ";s:6:"ぺ";s:3:"ぺ";s:6:"ぼ";s:3:"ぼ";s:6:"ぽ";s:3:"ぽ";s:6:"ゔ";s:3:"ゔ";s:6:"ゞ";s:3:"ゞ";s:6:"ガ";s:3:"ガ";s:6:"ギ";s:3:"ギ";s:6:"グ";s:3:"グ";s:6:"ゲ";s:3:"ゲ";s:6:"ゴ";s:3:"ゴ";s:6:"ザ";s:3:"ザ";s:6:"ジ";s:3:"ジ";s:6:"ズ";s:3:"ズ";s:6:"ゼ";s:3:"ゼ";s:6:"ゾ";s:3:"ゾ";s:6:"ダ";s:3:"ダ";s:6:"ヂ";s:3:"ヂ";s:6:"ヅ";s:3:"ヅ";s:6:"デ";s:3:"デ";s:6:"ド";s:3:"ド";s:6:"バ";s:3:"バ";s:6:"パ";s:3:"パ";s:6:"ビ";s:3:"ビ";s:6:"ピ";s:3:"ピ";s:6:"ブ";s:3:"ブ";s:6:"プ";s:3:"プ";s:6:"ベ";s:3:"ベ";s:6:"ペ";s:3:"ペ";s:6:"ボ";s:3:"ボ";s:6:"ポ";s:3:"ポ";s:6:"ヴ";s:3:"ヴ";s:6:"ヷ";s:3:"ヷ";s:6:"ヸ";s:3:"ヸ";s:6:"ヹ";s:3:"ヹ";s:6:"ヺ";s:3:"ヺ";s:6:"ヾ";s:3:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:4:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:4:"廊";s:3:"朗";s:4:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:4:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:4:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:4:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:4:"異";s:3:"北";s:4:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:4:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:4:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:4:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:4:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:4:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:4:"侮";s:3:"僧";s:4:"僧";s:3:"免";s:4:"免";s:3:"勉";s:4:"勉";s:3:"勤";s:4:"勤";s:3:"卑";s:4:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:4:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:4:"屮";s:3:"悔";s:4:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:4:"憎";s:3:"懲";s:4:"懲";s:3:"敏";s:4:"敏";s:3:"既";s:3:"既";s:3:"暑";s:4:"暑";s:3:"梅";s:4:"梅";s:3:"海";s:4:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:4:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:4:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:4:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"著";s:4:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:4:"𤋮";s:3:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:4:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:4:"勇";s:3:"勺";s:4:"勺";s:3:"啕";s:3:"啕";s:3:"喙";s:4:"喙";s:3:"嗢";s:3:"嗢";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:4:"慎";s:3:"愈";s:3:"愈";s:3:"慠";s:3:"慠";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"望";s:4:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"滛";s:3:"滛";s:3:"滋";s:4:"滋";s:3:"瀞";s:4:"瀞";s:3:"瞧";s:3:"瞧";s:3:"爵";s:4:"爵";s:3:"犯";s:3:"犯";s:3:"瑱";s:4:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"盛";s:3:"盛";s:3:"直";s:4:"直";s:3:"睊";s:4:"睊";s:3:"着";s:3:"着";s:3:"磌";s:4:"磌";s:3:"窱";s:3:"窱";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"缾";s:3:"缾";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:4:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"調";s:3:"調";s:3:"請";s:3:"請";s:3:"諭";s:4:"諭";s:3:"變";s:4:"變";s:3:"輸";s:4:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"韛";s:3:"韛";s:3:"頋";s:4:"頋";s:3:"鬒";s:4:"鬒";s:4:"𢡊";s:3:"𢡊";s:4:"𢡄";s:3:"𢡄";s:4:"𣏕";s:3:"𣏕";s:3:"㮝";s:4:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:4:"䀹";s:4:"𥉉";s:3:"𥉉";s:4:"𥳐";s:3:"𥳐";s:4:"𧻓";s:3:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:8:"𑂚";s:4:"𑂚";s:8:"𑂜";s:4:"𑂜";s:8:"𑂫";s:4:"𑂫";s:3:"丽";s:4:"丽";s:3:"丸";s:4:"丸";s:3:"乁";s:4:"乁";s:4:"𠄢";s:4:"𠄢";s:3:"你";s:4:"你";s:3:"侻";s:4:"侻";s:3:"倂";s:4:"倂";s:3:"偺";s:4:"偺";s:3:"備";s:4:"備";s:3:"像";s:4:"像";s:3:"㒞";s:4:"㒞";s:4:"𠘺";s:4:"𠘺";s:3:"兔";s:4:"兔";s:3:"兤";s:4:"兤";s:3:"具";s:4:"具";s:4:"𠔜";s:4:"𠔜";s:3:"㒹";s:4:"㒹";s:3:"內";s:4:"內";s:3:"再";s:4:"再";s:4:"𠕋";s:4:"𠕋";s:3:"冗";s:4:"冗";s:3:"冤";s:4:"冤";s:3:"仌";s:4:"仌";s:3:"冬";s:4:"冬";s:4:"𩇟";s:4:"𩇟";s:3:"凵";s:4:"凵";s:3:"刃";s:4:"刃";s:3:"㓟";s:4:"㓟";s:3:"刻";s:4:"刻";s:3:"剆";s:4:"剆";s:3:"割";s:4:"割";s:3:"剷";s:4:"剷";s:3:"㔕";s:4:"㔕";s:3:"包";s:4:"包";s:3:"匆";s:4:"匆";s:3:"卉";s:4:"卉";s:3:"博";s:4:"博";s:3:"即";s:4:"即";s:3:"卽";s:4:"卽";s:3:"卿";s:4:"卿";s:4:"𠨬";s:4:"𠨬";s:3:"灰";s:4:"灰";s:3:"及";s:4:"及";s:3:"叟";s:4:"叟";s:4:"𠭣";s:4:"𠭣";s:3:"叫";s:4:"叫";s:3:"叱";s:4:"叱";s:3:"吆";s:4:"吆";s:3:"咞";s:4:"咞";s:3:"吸";s:4:"吸";s:3:"呈";s:4:"呈";s:3:"周";s:4:"周";s:3:"咢";s:4:"咢";s:3:"哶";s:4:"哶";s:3:"唐";s:4:"唐";s:3:"啓";s:4:"啓";s:3:"啣";s:4:"啣";s:3:"善";s:4:"善";s:3:"喫";s:4:"喫";s:3:"喳";s:4:"喳";s:3:"嗂";s:4:"嗂";s:3:"圖";s:4:"圖";s:3:"圗";s:4:"圗";s:3:"噑";s:4:"噑";s:3:"噴";s:4:"噴";s:3:"壮";s:4:"壮";s:3:"城";s:4:"城";s:3:"埴";s:4:"埴";s:3:"堍";s:4:"堍";s:3:"型";s:4:"型";s:3:"堲";s:4:"堲";s:3:"報";s:4:"報";s:3:"墬";s:4:"墬";s:4:"𡓤";s:4:"𡓤";s:3:"売";s:4:"売";s:3:"壷";s:4:"壷";s:3:"夆";s:4:"夆";s:3:"多";s:4:"多";s:3:"夢";s:4:"夢";s:3:"奢";s:4:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:3:"姬";s:4:"姬";s:3:"娛";s:4:"娛";s:3:"娧";s:4:"娧";s:3:"姘";s:4:"姘";s:3:"婦";s:4:"婦";s:3:"㛮";s:4:"㛮";s:3:"㛼";s:4:"㛼";s:3:"嬈";s:4:"嬈";s:3:"嬾";s:4:"嬾";s:4:"𡧈";s:4:"𡧈";s:3:"寃";s:4:"寃";s:3:"寘";s:4:"寘";s:3:"寳";s:4:"寳";s:4:"𡬘";s:4:"𡬘";s:3:"寿";s:4:"寿";s:3:"将";s:4:"将";s:3:"当";s:4:"当";s:3:"尢";s:4:"尢";s:3:"㞁";s:4:"㞁";s:3:"屠";s:4:"屠";s:3:"峀";s:4:"峀";s:3:"岍";s:4:"岍";s:4:"𡷤";s:4:"𡷤";s:3:"嵃";s:4:"嵃";s:4:"𡷦";s:4:"𡷦";s:3:"嵮";s:4:"嵮";s:3:"嵫";s:4:"嵫";s:3:"嵼";s:4:"嵼";s:3:"巡";s:4:"巡";s:3:"巢";s:4:"巢";s:3:"㠯";s:4:"㠯";s:3:"巽";s:4:"巽";s:3:"帨";s:4:"帨";s:3:"帽";s:4:"帽";s:3:"幩";s:4:"幩";s:3:"㡢";s:4:"㡢";s:4:"𢆃";s:4:"𢆃";s:3:"㡼";s:4:"㡼";s:3:"庰";s:4:"庰";s:3:"庳";s:4:"庳";s:3:"庶";s:4:"庶";s:4:"𪎒";s:4:"𪎒";s:3:"廾";s:4:"廾";s:4:"𢌱";s:4:"𢌱";s:3:"舁";s:4:"舁";s:3:"弢";s:4:"弢";s:3:"㣇";s:4:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:3:"形";s:4:"形";s:3:"彫";s:4:"彫";s:3:"㣣";s:4:"㣣";s:3:"徚";s:4:"徚";s:3:"忍";s:4:"忍";s:3:"志";s:4:"志";s:3:"忹";s:4:"忹";s:3:"悁";s:4:"悁";s:3:"㤺";s:4:"㤺";s:3:"㤜";s:4:"㤜";s:4:"𢛔";s:4:"𢛔";s:3:"惇";s:4:"惇";s:3:"慈";s:4:"慈";s:3:"慌";s:4:"慌";s:3:"慺";s:4:"慺";s:3:"憲";s:4:"憲";s:3:"憤";s:4:"憤";s:3:"憯";s:4:"憯";s:3:"懞";s:4:"懞";s:3:"成";s:4:"成";s:3:"戛";s:4:"戛";s:3:"扝";s:4:"扝";s:3:"抱";s:4:"抱";s:3:"拔";s:4:"拔";s:3:"捐";s:4:"捐";s:4:"𢬌";s:4:"𢬌";s:3:"挽";s:4:"挽";s:3:"拼";s:4:"拼";s:3:"捨";s:4:"捨";s:3:"掃";s:4:"掃";s:3:"揤";s:4:"揤";s:4:"𢯱";s:4:"𢯱";s:3:"搢";s:4:"搢";s:3:"揅";s:4:"揅";s:3:"掩";s:4:"掩";s:3:"㨮";s:4:"㨮";s:3:"摩";s:4:"摩";s:3:"摾";s:4:"摾";s:3:"撝";s:4:"撝";s:3:"摷";s:4:"摷";s:3:"㩬";s:4:"㩬";s:3:"敬";s:4:"敬";s:4:"𣀊";s:4:"𣀊";s:3:"旣";s:4:"旣";s:3:"書";s:4:"書";s:3:"晉";s:4:"晉";s:3:"㬙";s:4:"㬙";s:3:"㬈";s:4:"㬈";s:3:"㫤";s:4:"㫤";s:3:"冒";s:4:"冒";s:3:"冕";s:4:"冕";s:3:"最";s:4:"最";s:3:"暜";s:4:"暜";s:3:"肭";s:4:"肭";s:3:"䏙";s:4:"䏙";s:3:"朡";s:4:"朡";s:3:"杞";s:4:"杞";s:3:"杓";s:4:"杓";s:4:"𣏃";s:4:"𣏃";s:3:"㭉";s:4:"㭉";s:3:"柺";s:4:"柺";s:3:"枅";s:4:"枅";s:3:"桒";s:4:"桒";s:4:"𣑭";s:4:"𣑭";s:3:"梎";s:4:"梎";s:3:"栟";s:4:"栟";s:3:"椔";s:4:"椔";s:3:"楂";s:4:"楂";s:3:"榣";s:4:"榣";s:3:"槪";s:4:"槪";s:3:"檨";s:4:"檨";s:4:"𣚣";s:4:"𣚣";s:3:"櫛";s:4:"櫛";s:3:"㰘";s:4:"㰘";s:3:"次";s:4:"次";s:4:"𣢧";s:4:"𣢧";s:3:"歔";s:4:"歔";s:3:"㱎";s:4:"㱎";s:3:"歲";s:4:"歲";s:3:"殟";s:4:"殟";s:3:"殻";s:4:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:3:"汎";s:4:"汎";s:4:"𣲼";s:4:"𣲼";s:3:"沿";s:4:"沿";s:3:"泍";s:4:"泍";s:3:"汧";s:4:"汧";s:3:"洖";s:4:"洖";s:3:"派";s:4:"派";s:3:"浩";s:4:"浩";s:3:"浸";s:4:"浸";s:3:"涅";s:4:"涅";s:4:"𣴞";s:4:"𣴞";s:3:"洴";s:4:"洴";s:3:"港";s:4:"港";s:3:"湮";s:4:"湮";s:3:"㴳";s:4:"㴳";s:3:"滇";s:4:"滇";s:4:"𣻑";s:4:"𣻑";s:3:"淹";s:4:"淹";s:3:"潮";s:4:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:3:"濆";s:4:"濆";s:3:"瀹";s:4:"瀹";s:3:"瀛";s:4:"瀛";s:3:"㶖";s:4:"㶖";s:3:"灊";s:4:"灊";s:3:"災";s:4:"災";s:3:"灷";s:4:"灷";s:3:"炭";s:4:"炭";s:4:"𠔥";s:4:"𠔥";s:3:"煅";s:4:"煅";s:4:"𤉣";s:4:"𤉣";s:3:"熜";s:4:"熜";s:4:"𤎫";s:4:"𤎫";s:3:"爨";s:4:"爨";s:3:"牐";s:4:"牐";s:4:"𤘈";s:4:"𤘈";s:3:"犀";s:4:"犀";s:3:"犕";s:4:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:3:"獺";s:4:"獺";s:3:"王";s:4:"王";s:3:"㺬";s:4:"㺬";s:3:"玥";s:4:"玥";s:3:"㺸";s:4:"㺸";s:3:"瑇";s:4:"瑇";s:3:"瑜";s:4:"瑜";s:3:"璅";s:4:"璅";s:3:"瓊";s:4:"瓊";s:3:"㼛";s:4:"㼛";s:3:"甤";s:4:"甤";s:4:"𤰶";s:4:"𤰶";s:3:"甾";s:4:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"𢆟";s:4:"𢆟";s:3:"瘐";s:4:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:3:"㿼";s:4:"㿼";s:3:"䀈";s:4:"䀈";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:3:"眞";s:4:"眞";s:3:"真";s:4:"真";s:3:"瞋";s:4:"瞋";s:3:"䁆";s:4:"䁆";s:3:"䂖";s:4:"䂖";s:4:"𥐝";s:4:"𥐝";s:3:"硎";s:4:"硎";s:3:"䃣";s:4:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:3:"秫";s:4:"秫";s:3:"䄯";s:4:"䄯";s:3:"穊";s:4:"穊";s:3:"穏";s:4:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:3:"竮";s:4:"竮";s:3:"䈂";s:4:"䈂";s:4:"𥮫";s:4:"𥮫";s:3:"篆";s:4:"篆";s:3:"築";s:4:"築";s:3:"䈧";s:4:"䈧";s:4:"𥲀";s:4:"𥲀";s:3:"糒";s:4:"糒";s:3:"䊠";s:4:"䊠";s:3:"糨";s:4:"糨";s:3:"糣";s:4:"糣";s:3:"紀";s:4:"紀";s:4:"𥾆";s:4:"𥾆";s:3:"絣";s:4:"絣";s:3:"䌁";s:4:"䌁";s:3:"緇";s:4:"緇";s:3:"縂";s:4:"縂";s:3:"繅";s:4:"繅";s:3:"䌴";s:4:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:3:"䍙";s:4:"䍙";s:4:"𦋙";s:4:"𦋙";s:3:"罺";s:4:"罺";s:4:"𦌾";s:4:"𦌾";s:3:"羕";s:4:"羕";s:3:"翺";s:4:"翺";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:3:"聠";s:4:"聠";s:4:"𦖨";s:4:"𦖨";s:3:"聰";s:4:"聰";s:4:"𣍟";s:4:"𣍟";s:3:"䏕";s:4:"䏕";s:3:"育";s:4:"育";s:3:"脃";s:4:"脃";s:3:"䐋";s:4:"䐋";s:3:"脾";s:4:"脾";s:3:"媵";s:4:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:3:"舄";s:4:"舄";s:3:"辞";s:4:"辞";s:3:"䑫";s:4:"䑫";s:3:"芑";s:4:"芑";s:3:"芋";s:4:"芋";s:3:"芝";s:4:"芝";s:3:"劳";s:4:"劳";s:3:"花";s:4:"花";s:3:"芳";s:4:"芳";s:3:"芽";s:4:"芽";s:3:"苦";s:4:"苦";s:4:"𦬼";s:4:"𦬼";s:3:"茝";s:4:"茝";s:3:"荣";s:4:"荣";s:3:"莭";s:4:"莭";s:3:"茣";s:4:"茣";s:3:"莽";s:4:"莽";s:3:"菧";s:4:"菧";s:3:"荓";s:4:"荓";s:3:"菊";s:4:"菊";s:3:"菌";s:4:"菌";s:3:"菜";s:4:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:3:"䔫";s:4:"䔫";s:3:"蓱";s:4:"蓱";s:3:"蓳";s:4:"蓳";s:3:"蔖";s:4:"蔖";s:4:"𧏊";s:4:"𧏊";s:3:"蕤";s:4:"蕤";s:4:"𦼬";s:4:"𦼬";s:3:"䕝";s:4:"䕝";s:3:"䕡";s:4:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:3:"䕫";s:4:"䕫";s:3:"虐";s:4:"虐";s:3:"虧";s:4:"虧";s:3:"虩";s:4:"虩";s:3:"蚩";s:4:"蚩";s:3:"蚈";s:4:"蚈";s:3:"蜎";s:4:"蜎";s:3:"蛢";s:4:"蛢";s:3:"蜨";s:4:"蜨";s:3:"蝫";s:4:"蝫";s:3:"螆";s:4:"螆";s:3:"䗗";s:4:"䗗";s:3:"蟡";s:4:"蟡";s:3:"蠁";s:4:"蠁";s:3:"䗹";s:4:"䗹";s:3:"衠";s:4:"衠";s:3:"衣";s:4:"衣";s:4:"𧙧";s:4:"𧙧";s:3:"裗";s:4:"裗";s:3:"裞";s:4:"裞";s:3:"䘵";s:4:"䘵";s:3:"裺";s:4:"裺";s:3:"㒻";s:4:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:3:"䚾";s:4:"䚾";s:3:"䛇";s:4:"䛇";s:3:"誠";s:4:"誠";s:3:"豕";s:4:"豕";s:4:"𧲨";s:4:"𧲨";s:3:"貫";s:4:"貫";s:3:"賁";s:4:"賁";s:3:"贛";s:4:"贛";s:3:"起";s:4:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:3:"跋";s:4:"跋";s:3:"趼";s:4:"趼";s:3:"跰";s:4:"跰";s:4:"𠣞";s:4:"𠣞";s:3:"軔";s:4:"軔";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:3:"邔";s:4:"邔";s:3:"郱";s:4:"郱";s:3:"鄑";s:4:"鄑";s:4:"𨜮";s:4:"𨜮";s:3:"鄛";s:4:"鄛";s:3:"鈸";s:4:"鈸";s:3:"鋗";s:4:"鋗";s:3:"鋘";s:4:"鋘";s:3:"鉼";s:4:"鉼";s:3:"鏹";s:4:"鏹";s:3:"鐕";s:4:"鐕";s:4:"𨯺";s:4:"𨯺";s:3:"開";s:4:"開";s:3:"䦕";s:4:"䦕";s:3:"閷";s:4:"閷";s:4:"𨵷";s:4:"𨵷";s:3:"䧦";s:4:"䧦";s:3:"雃";s:4:"雃";s:3:"嶲";s:4:"嶲";s:3:"霣";s:4:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:3:"䩮";s:4:"䩮";s:3:"䩶";s:4:"䩶";s:3:"韠";s:4:"韠";s:4:"𩐊";s:4:"𩐊";s:3:"䪲";s:4:"䪲";s:4:"𩒖";s:4:"𩒖";s:3:"頩";s:4:"頩";s:4:"𩖶";s:4:"𩖶";s:3:"飢";s:4:"飢";s:3:"䬳";s:4:"䬳";s:3:"餩";s:4:"餩";s:3:"馧";s:4:"馧";s:3:"駂";s:4:"駂";s:3:"駾";s:4:"駾";s:3:"䯎";s:4:"䯎";s:4:"𩬰";s:4:"𩬰";s:3:"鱀";s:4:"鱀";s:3:"鳽";s:4:"鳽";s:3:"䳎";s:4:"䳎";s:3:"䳭";s:4:"䳭";s:3:"鵧";s:4:"鵧";s:4:"𪃎";s:4:"𪃎";s:3:"䳸";s:4:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:3:"麻";s:4:"麻";s:3:"䵖";s:4:"䵖";s:3:"黹";s:4:"黹";s:3:"黾";s:4:"黾";s:3:"鼅";s:4:"鼅";s:3:"鼏";s:4:"鼏";s:3:"鼖";s:4:"鼖";s:3:"鼻";s:4:"鼻";s:4:"𪘀";s:4:"𪘀";}' );
+UtfNormal::$utfCanonicalDecomp = unserialize( 'a:2049:{s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:";";s:1:";";s:2:"΅";s:4:"΅";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϓ";s:4:"ϓ";s:2:"ϔ";s:4:"ϔ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"ᬆ";s:6:"ᬆ";s:3:"ᬈ";s:6:"ᬈ";s:3:"ᬊ";s:6:"ᬊ";s:3:"ᬌ";s:6:"ᬌ";s:3:"ᬎ";s:6:"ᬎ";s:3:"ᬒ";s:6:"ᬒ";s:3:"ᬻ";s:6:"ᬻ";s:3:"ᬽ";s:6:"ᬽ";s:3:"ᭀ";s:6:"ᭀ";s:3:"ᭁ";s:6:"ᭁ";s:3:"ᭃ";s:6:"ᭃ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẛ";s:4:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"ι";s:2:"ι";s:3:"῁";s:4:"῁";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:"῍";s:3:"῎";s:5:"῎";s:3:"῏";s:5:"῏";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:"῝";s:3:"῞";s:5:"῞";s:3:"῟";s:5:"῟";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:4:"῭";s:3:"΅";s:4:"΅";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:2:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:3:"Ω";s:2:"Ω";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"⫝̸";s:5:"⫝̸";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"ゞ";s:6:"ゞ";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:3:"𤋮";s:4:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:4:"𑂚";s:8:"𑂚";s:4:"𑂜";s:8:"𑂜";s:4:"𑂫";s:8:"𑂫";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' );
+UtfNormal::$utfCheckNFC = unserialize( 'a:1221:{s:2:"̀";s:1:"N";s:2:"́";s:1:"N";s:2:"̓";s:1:"N";s:2:"̈́";s:1:"N";s:2:"ʹ";s:1:"N";s:2:";";s:1:"N";s:2:"·";s:1:"N";s:3:"क़";s:1:"N";s:3:"ख़";s:1:"N";s:3:"ग़";s:1:"N";s:3:"ज़";s:1:"N";s:3:"ड़";s:1:"N";s:3:"ढ़";s:1:"N";s:3:"फ़";s:1:"N";s:3:"य़";s:1:"N";s:3:"ড়";s:1:"N";s:3:"ঢ়";s:1:"N";s:3:"য়";s:1:"N";s:3:"ਲ਼";s:1:"N";s:3:"ਸ਼";s:1:"N";s:3:"ਖ਼";s:1:"N";s:3:"ਗ਼";s:1:"N";s:3:"ਜ਼";s:1:"N";s:3:"ਫ਼";s:1:"N";s:3:"ଡ଼";s:1:"N";s:3:"ଢ଼";s:1:"N";s:3:"གྷ";s:1:"N";s:3:"ཌྷ";s:1:"N";s:3:"དྷ";s:1:"N";s:3:"བྷ";s:1:"N";s:3:"ཛྷ";s:1:"N";s:3:"ཀྵ";s:1:"N";s:3:"ཱི";s:1:"N";s:3:"ཱུ";s:1:"N";s:3:"ྲྀ";s:1:"N";s:3:"ླྀ";s:1:"N";s:3:"ཱྀ";s:1:"N";s:3:"ྒྷ";s:1:"N";s:3:"ྜྷ";s:1:"N";s:3:"ྡྷ";s:1:"N";s:3:"ྦྷ";s:1:"N";s:3:"ྫྷ";s:1:"N";s:3:"ྐྵ";s:1:"N";s:3:"ά";s:1:"N";s:3:"έ";s:1:"N";s:3:"ή";s:1:"N";s:3:"ί";s:1:"N";s:3:"ό";s:1:"N";s:3:"ύ";s:1:"N";s:3:"ώ";s:1:"N";s:3:"Ά";s:1:"N";s:3:"ι";s:1:"N";s:3:"Έ";s:1:"N";s:3:"Ή";s:1:"N";s:3:"ΐ";s:1:"N";s:3:"Ί";s:1:"N";s:3:"ΰ";s:1:"N";s:3:"Ύ";s:1:"N";s:3:"΅";s:1:"N";s:3:"`";s:1:"N";s:3:"Ό";s:1:"N";s:3:"Ώ";s:1:"N";s:3:"´";s:1:"N";s:3:" ";s:1:"N";s:3:" ";s:1:"N";s:3:"Ω";s:1:"N";s:3:"K";s:1:"N";s:3:"Å";s:1:"N";s:3:"〈";s:1:"N";s:3:"〉";s:1:"N";s:3:"⫝̸";s:1:"N";s:3:"豈";s:1:"N";s:3:"更";s:1:"N";s:3:"車";s:1:"N";s:3:"賈";s:1:"N";s:3:"滑";s:1:"N";s:3:"串";s:1:"N";s:3:"句";s:1:"N";s:3:"龜";s:1:"N";s:3:"龜";s:1:"N";s:3:"契";s:1:"N";s:3:"金";s:1:"N";s:3:"喇";s:1:"N";s:3:"奈";s:1:"N";s:3:"懶";s:1:"N";s:3:"癩";s:1:"N";s:3:"羅";s:1:"N";s:3:"蘿";s:1:"N";s:3:"螺";s:1:"N";s:3:"裸";s:1:"N";s:3:"邏";s:1:"N";s:3:"樂";s:1:"N";s:3:"洛";s:1:"N";s:3:"烙";s:1:"N";s:3:"珞";s:1:"N";s:3:"落";s:1:"N";s:3:"酪";s:1:"N";s:3:"駱";s:1:"N";s:3:"亂";s:1:"N";s:3:"卵";s:1:"N";s:3:"欄";s:1:"N";s:3:"爛";s:1:"N";s:3:"蘭";s:1:"N";s:3:"鸞";s:1:"N";s:3:"嵐";s:1:"N";s:3:"濫";s:1:"N";s:3:"藍";s:1:"N";s:3:"襤";s:1:"N";s:3:"拉";s:1:"N";s:3:"臘";s:1:"N";s:3:"蠟";s:1:"N";s:3:"廊";s:1:"N";s:3:"朗";s:1:"N";s:3:"浪";s:1:"N";s:3:"狼";s:1:"N";s:3:"郎";s:1:"N";s:3:"來";s:1:"N";s:3:"冷";s:1:"N";s:3:"勞";s:1:"N";s:3:"擄";s:1:"N";s:3:"櫓";s:1:"N";s:3:"爐";s:1:"N";s:3:"盧";s:1:"N";s:3:"老";s:1:"N";s:3:"蘆";s:1:"N";s:3:"虜";s:1:"N";s:3:"路";s:1:"N";s:3:"露";s:1:"N";s:3:"魯";s:1:"N";s:3:"鷺";s:1:"N";s:3:"碌";s:1:"N";s:3:"祿";s:1:"N";s:3:"綠";s:1:"N";s:3:"菉";s:1:"N";s:3:"錄";s:1:"N";s:3:"鹿";s:1:"N";s:3:"論";s:1:"N";s:3:"壟";s:1:"N";s:3:"弄";s:1:"N";s:3:"籠";s:1:"N";s:3:"聾";s:1:"N";s:3:"牢";s:1:"N";s:3:"磊";s:1:"N";s:3:"賂";s:1:"N";s:3:"雷";s:1:"N";s:3:"壘";s:1:"N";s:3:"屢";s:1:"N";s:3:"樓";s:1:"N";s:3:"淚";s:1:"N";s:3:"漏";s:1:"N";s:3:"累";s:1:"N";s:3:"縷";s:1:"N";s:3:"陋";s:1:"N";s:3:"勒";s:1:"N";s:3:"肋";s:1:"N";s:3:"凜";s:1:"N";s:3:"凌";s:1:"N";s:3:"稜";s:1:"N";s:3:"綾";s:1:"N";s:3:"菱";s:1:"N";s:3:"陵";s:1:"N";s:3:"讀";s:1:"N";s:3:"拏";s:1:"N";s:3:"樂";s:1:"N";s:3:"諾";s:1:"N";s:3:"丹";s:1:"N";s:3:"寧";s:1:"N";s:3:"怒";s:1:"N";s:3:"率";s:1:"N";s:3:"異";s:1:"N";s:3:"北";s:1:"N";s:3:"磻";s:1:"N";s:3:"便";s:1:"N";s:3:"復";s:1:"N";s:3:"不";s:1:"N";s:3:"泌";s:1:"N";s:3:"數";s:1:"N";s:3:"索";s:1:"N";s:3:"參";s:1:"N";s:3:"塞";s:1:"N";s:3:"省";s:1:"N";s:3:"葉";s:1:"N";s:3:"說";s:1:"N";s:3:"殺";s:1:"N";s:3:"辰";s:1:"N";s:3:"沈";s:1:"N";s:3:"拾";s:1:"N";s:3:"若";s:1:"N";s:3:"掠";s:1:"N";s:3:"略";s:1:"N";s:3:"亮";s:1:"N";s:3:"兩";s:1:"N";s:3:"凉";s:1:"N";s:3:"梁";s:1:"N";s:3:"糧";s:1:"N";s:3:"良";s:1:"N";s:3:"諒";s:1:"N";s:3:"量";s:1:"N";s:3:"勵";s:1:"N";s:3:"呂";s:1:"N";s:3:"女";s:1:"N";s:3:"廬";s:1:"N";s:3:"旅";s:1:"N";s:3:"濾";s:1:"N";s:3:"礪";s:1:"N";s:3:"閭";s:1:"N";s:3:"驪";s:1:"N";s:3:"麗";s:1:"N";s:3:"黎";s:1:"N";s:3:"力";s:1:"N";s:3:"曆";s:1:"N";s:3:"歷";s:1:"N";s:3:"轢";s:1:"N";s:3:"年";s:1:"N";s:3:"憐";s:1:"N";s:3:"戀";s:1:"N";s:3:"撚";s:1:"N";s:3:"漣";s:1:"N";s:3:"煉";s:1:"N";s:3:"璉";s:1:"N";s:3:"秊";s:1:"N";s:3:"練";s:1:"N";s:3:"聯";s:1:"N";s:3:"輦";s:1:"N";s:3:"蓮";s:1:"N";s:3:"連";s:1:"N";s:3:"鍊";s:1:"N";s:3:"列";s:1:"N";s:3:"劣";s:1:"N";s:3:"咽";s:1:"N";s:3:"烈";s:1:"N";s:3:"裂";s:1:"N";s:3:"說";s:1:"N";s:3:"廉";s:1:"N";s:3:"念";s:1:"N";s:3:"捻";s:1:"N";s:3:"殮";s:1:"N";s:3:"簾";s:1:"N";s:3:"獵";s:1:"N";s:3:"令";s:1:"N";s:3:"囹";s:1:"N";s:3:"寧";s:1:"N";s:3:"嶺";s:1:"N";s:3:"怜";s:1:"N";s:3:"玲";s:1:"N";s:3:"瑩";s:1:"N";s:3:"羚";s:1:"N";s:3:"聆";s:1:"N";s:3:"鈴";s:1:"N";s:3:"零";s:1:"N";s:3:"靈";s:1:"N";s:3:"領";s:1:"N";s:3:"例";s:1:"N";s:3:"禮";s:1:"N";s:3:"醴";s:1:"N";s:3:"隸";s:1:"N";s:3:"惡";s:1:"N";s:3:"了";s:1:"N";s:3:"僚";s:1:"N";s:3:"寮";s:1:"N";s:3:"尿";s:1:"N";s:3:"料";s:1:"N";s:3:"樂";s:1:"N";s:3:"燎";s:1:"N";s:3:"療";s:1:"N";s:3:"蓼";s:1:"N";s:3:"遼";s:1:"N";s:3:"龍";s:1:"N";s:3:"暈";s:1:"N";s:3:"阮";s:1:"N";s:3:"劉";s:1:"N";s:3:"杻";s:1:"N";s:3:"柳";s:1:"N";s:3:"流";s:1:"N";s:3:"溜";s:1:"N";s:3:"琉";s:1:"N";s:3:"留";s:1:"N";s:3:"硫";s:1:"N";s:3:"紐";s:1:"N";s:3:"類";s:1:"N";s:3:"六";s:1:"N";s:3:"戮";s:1:"N";s:3:"陸";s:1:"N";s:3:"倫";s:1:"N";s:3:"崙";s:1:"N";s:3:"淪";s:1:"N";s:3:"輪";s:1:"N";s:3:"律";s:1:"N";s:3:"慄";s:1:"N";s:3:"栗";s:1:"N";s:3:"率";s:1:"N";s:3:"隆";s:1:"N";s:3:"利";s:1:"N";s:3:"吏";s:1:"N";s:3:"履";s:1:"N";s:3:"易";s:1:"N";s:3:"李";s:1:"N";s:3:"梨";s:1:"N";s:3:"泥";s:1:"N";s:3:"理";s:1:"N";s:3:"痢";s:1:"N";s:3:"罹";s:1:"N";s:3:"裏";s:1:"N";s:3:"裡";s:1:"N";s:3:"里";s:1:"N";s:3:"離";s:1:"N";s:3:"匿";s:1:"N";s:3:"溺";s:1:"N";s:3:"吝";s:1:"N";s:3:"燐";s:1:"N";s:3:"璘";s:1:"N";s:3:"藺";s:1:"N";s:3:"隣";s:1:"N";s:3:"鱗";s:1:"N";s:3:"麟";s:1:"N";s:3:"林";s:1:"N";s:3:"淋";s:1:"N";s:3:"臨";s:1:"N";s:3:"立";s:1:"N";s:3:"笠";s:1:"N";s:3:"粒";s:1:"N";s:3:"狀";s:1:"N";s:3:"炙";s:1:"N";s:3:"識";s:1:"N";s:3:"什";s:1:"N";s:3:"茶";s:1:"N";s:3:"刺";s:1:"N";s:3:"切";s:1:"N";s:3:"度";s:1:"N";s:3:"拓";s:1:"N";s:3:"糖";s:1:"N";s:3:"宅";s:1:"N";s:3:"洞";s:1:"N";s:3:"暴";s:1:"N";s:3:"輻";s:1:"N";s:3:"行";s:1:"N";s:3:"降";s:1:"N";s:3:"見";s:1:"N";s:3:"廓";s:1:"N";s:3:"兀";s:1:"N";s:3:"嗀";s:1:"N";s:3:"塚";s:1:"N";s:3:"晴";s:1:"N";s:3:"凞";s:1:"N";s:3:"猪";s:1:"N";s:3:"益";s:1:"N";s:3:"礼";s:1:"N";s:3:"神";s:1:"N";s:3:"祥";s:1:"N";s:3:"福";s:1:"N";s:3:"靖";s:1:"N";s:3:"精";s:1:"N";s:3:"羽";s:1:"N";s:3:"蘒";s:1:"N";s:3:"諸";s:1:"N";s:3:"逸";s:1:"N";s:3:"都";s:1:"N";s:3:"飯";s:1:"N";s:3:"飼";s:1:"N";s:3:"館";s:1:"N";s:3:"鶴";s:1:"N";s:3:"侮";s:1:"N";s:3:"僧";s:1:"N";s:3:"免";s:1:"N";s:3:"勉";s:1:"N";s:3:"勤";s:1:"N";s:3:"卑";s:1:"N";s:3:"喝";s:1:"N";s:3:"嘆";s:1:"N";s:3:"器";s:1:"N";s:3:"塀";s:1:"N";s:3:"墨";s:1:"N";s:3:"層";s:1:"N";s:3:"屮";s:1:"N";s:3:"悔";s:1:"N";s:3:"慨";s:1:"N";s:3:"憎";s:1:"N";s:3:"懲";s:1:"N";s:3:"敏";s:1:"N";s:3:"既";s:1:"N";s:3:"暑";s:1:"N";s:3:"梅";s:1:"N";s:3:"海";s:1:"N";s:3:"渚";s:1:"N";s:3:"漢";s:1:"N";s:3:"煮";s:1:"N";s:3:"爫";s:1:"N";s:3:"琢";s:1:"N";s:3:"碑";s:1:"N";s:3:"社";s:1:"N";s:3:"祉";s:1:"N";s:3:"祈";s:1:"N";s:3:"祐";s:1:"N";s:3:"祖";s:1:"N";s:3:"祝";s:1:"N";s:3:"禍";s:1:"N";s:3:"禎";s:1:"N";s:3:"穀";s:1:"N";s:3:"突";s:1:"N";s:3:"節";s:1:"N";s:3:"練";s:1:"N";s:3:"縉";s:1:"N";s:3:"繁";s:1:"N";s:3:"署";s:1:"N";s:3:"者";s:1:"N";s:3:"臭";s:1:"N";s:3:"艹";s:1:"N";s:3:"艹";s:1:"N";s:3:"著";s:1:"N";s:3:"褐";s:1:"N";s:3:"視";s:1:"N";s:3:"謁";s:1:"N";s:3:"謹";s:1:"N";s:3:"賓";s:1:"N";s:3:"贈";s:1:"N";s:3:"辶";s:1:"N";s:3:"逸";s:1:"N";s:3:"難";s:1:"N";s:3:"響";s:1:"N";s:3:"頻";s:1:"N";s:3:"恵";s:1:"N";s:3:"𤋮";s:1:"N";s:3:"舘";s:1:"N";s:3:"並";s:1:"N";s:3:"况";s:1:"N";s:3:"全";s:1:"N";s:3:"侀";s:1:"N";s:3:"充";s:1:"N";s:3:"冀";s:1:"N";s:3:"勇";s:1:"N";s:3:"勺";s:1:"N";s:3:"喝";s:1:"N";s:3:"啕";s:1:"N";s:3:"喙";s:1:"N";s:3:"嗢";s:1:"N";s:3:"塚";s:1:"N";s:3:"墳";s:1:"N";s:3:"奄";s:1:"N";s:3:"奔";s:1:"N";s:3:"婢";s:1:"N";s:3:"嬨";s:1:"N";s:3:"廒";s:1:"N";s:3:"廙";s:1:"N";s:3:"彩";s:1:"N";s:3:"徭";s:1:"N";s:3:"惘";s:1:"N";s:3:"慎";s:1:"N";s:3:"愈";s:1:"N";s:3:"憎";s:1:"N";s:3:"慠";s:1:"N";s:3:"懲";s:1:"N";s:3:"戴";s:1:"N";s:3:"揄";s:1:"N";s:3:"搜";s:1:"N";s:3:"摒";s:1:"N";s:3:"敖";s:1:"N";s:3:"晴";s:1:"N";s:3:"朗";s:1:"N";s:3:"望";s:1:"N";s:3:"杖";s:1:"N";s:3:"歹";s:1:"N";s:3:"殺";s:1:"N";s:3:"流";s:1:"N";s:3:"滛";s:1:"N";s:3:"滋";s:1:"N";s:3:"漢";s:1:"N";s:3:"瀞";s:1:"N";s:3:"煮";s:1:"N";s:3:"瞧";s:1:"N";s:3:"爵";s:1:"N";s:3:"犯";s:1:"N";s:3:"猪";s:1:"N";s:3:"瑱";s:1:"N";s:3:"甆";s:1:"N";s:3:"画";s:1:"N";s:3:"瘝";s:1:"N";s:3:"瘟";s:1:"N";s:3:"益";s:1:"N";s:3:"盛";s:1:"N";s:3:"直";s:1:"N";s:3:"睊";s:1:"N";s:3:"着";s:1:"N";s:3:"磌";s:1:"N";s:3:"窱";s:1:"N";s:3:"節";s:1:"N";s:3:"类";s:1:"N";s:3:"絛";s:1:"N";s:3:"練";s:1:"N";s:3:"缾";s:1:"N";s:3:"者";s:1:"N";s:3:"荒";s:1:"N";s:3:"華";s:1:"N";s:3:"蝹";s:1:"N";s:3:"襁";s:1:"N";s:3:"覆";s:1:"N";s:3:"視";s:1:"N";s:3:"調";s:1:"N";s:3:"諸";s:1:"N";s:3:"請";s:1:"N";s:3:"謁";s:1:"N";s:3:"諾";s:1:"N";s:3:"諭";s:1:"N";s:3:"謹";s:1:"N";s:3:"變";s:1:"N";s:3:"贈";s:1:"N";s:3:"輸";s:1:"N";s:3:"遲";s:1:"N";s:3:"醙";s:1:"N";s:3:"鉶";s:1:"N";s:3:"陼";s:1:"N";s:3:"難";s:1:"N";s:3:"靖";s:1:"N";s:3:"韛";s:1:"N";s:3:"響";s:1:"N";s:3:"頋";s:1:"N";s:3:"頻";s:1:"N";s:3:"鬒";s:1:"N";s:3:"龜";s:1:"N";s:3:"𢡊";s:1:"N";s:3:"𢡄";s:1:"N";s:3:"𣏕";s:1:"N";s:3:"㮝";s:1:"N";s:3:"䀘";s:1:"N";s:3:"䀹";s:1:"N";s:3:"𥉉";s:1:"N";s:3:"𥳐";s:1:"N";s:3:"𧻓";s:1:"N";s:3:"齃";s:1:"N";s:3:"龎";s:1:"N";s:3:"יִ";s:1:"N";s:3:"ײַ";s:1:"N";s:3:"שׁ";s:1:"N";s:3:"שׂ";s:1:"N";s:3:"שּׁ";s:1:"N";s:3:"שּׂ";s:1:"N";s:3:"אַ";s:1:"N";s:3:"אָ";s:1:"N";s:3:"אּ";s:1:"N";s:3:"בּ";s:1:"N";s:3:"גּ";s:1:"N";s:3:"דּ";s:1:"N";s:3:"הּ";s:1:"N";s:3:"וּ";s:1:"N";s:3:"זּ";s:1:"N";s:3:"טּ";s:1:"N";s:3:"יּ";s:1:"N";s:3:"ךּ";s:1:"N";s:3:"כּ";s:1:"N";s:3:"לּ";s:1:"N";s:3:"מּ";s:1:"N";s:3:"נּ";s:1:"N";s:3:"סּ";s:1:"N";s:3:"ףּ";s:1:"N";s:3:"פּ";s:1:"N";s:3:"צּ";s:1:"N";s:3:"קּ";s:1:"N";s:3:"רּ";s:1:"N";s:3:"שּ";s:1:"N";s:3:"תּ";s:1:"N";s:3:"וֹ";s:1:"N";s:3:"בֿ";s:1:"N";s:3:"כֿ";s:1:"N";s:3:"פֿ";s:1:"N";s:4:"𝅗𝅥";s:1:"N";s:4:"𝅘𝅥";s:1:"N";s:4:"𝅘𝅥𝅮";s:1:"N";s:4:"𝅘𝅥𝅯";s:1:"N";s:4:"𝅘𝅥𝅰";s:1:"N";s:4:"𝅘𝅥𝅱";s:1:"N";s:4:"𝅘𝅥𝅲";s:1:"N";s:4:"𝆹𝅥";s:1:"N";s:4:"𝆺𝅥";s:1:"N";s:4:"𝆹𝅥𝅮";s:1:"N";s:4:"𝆺𝅥𝅮";s:1:"N";s:4:"𝆹𝅥𝅯";s:1:"N";s:4:"𝆺𝅥𝅯";s:1:"N";s:4:"丽";s:1:"N";s:4:"丸";s:1:"N";s:4:"乁";s:1:"N";s:4:"𠄢";s:1:"N";s:4:"你";s:1:"N";s:4:"侮";s:1:"N";s:4:"侻";s:1:"N";s:4:"倂";s:1:"N";s:4:"偺";s:1:"N";s:4:"備";s:1:"N";s:4:"僧";s:1:"N";s:4:"像";s:1:"N";s:4:"㒞";s:1:"N";s:4:"𠘺";s:1:"N";s:4:"免";s:1:"N";s:4:"兔";s:1:"N";s:4:"兤";s:1:"N";s:4:"具";s:1:"N";s:4:"𠔜";s:1:"N";s:4:"㒹";s:1:"N";s:4:"內";s:1:"N";s:4:"再";s:1:"N";s:4:"𠕋";s:1:"N";s:4:"冗";s:1:"N";s:4:"冤";s:1:"N";s:4:"仌";s:1:"N";s:4:"冬";s:1:"N";s:4:"况";s:1:"N";s:4:"𩇟";s:1:"N";s:4:"凵";s:1:"N";s:4:"刃";s:1:"N";s:4:"㓟";s:1:"N";s:4:"刻";s:1:"N";s:4:"剆";s:1:"N";s:4:"割";s:1:"N";s:4:"剷";s:1:"N";s:4:"㔕";s:1:"N";s:4:"勇";s:1:"N";s:4:"勉";s:1:"N";s:4:"勤";s:1:"N";s:4:"勺";s:1:"N";s:4:"包";s:1:"N";s:4:"匆";s:1:"N";s:4:"北";s:1:"N";s:4:"卉";s:1:"N";s:4:"卑";s:1:"N";s:4:"博";s:1:"N";s:4:"即";s:1:"N";s:4:"卽";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"𠨬";s:1:"N";s:4:"灰";s:1:"N";s:4:"及";s:1:"N";s:4:"叟";s:1:"N";s:4:"𠭣";s:1:"N";s:4:"叫";s:1:"N";s:4:"叱";s:1:"N";s:4:"吆";s:1:"N";s:4:"咞";s:1:"N";s:4:"吸";s:1:"N";s:4:"呈";s:1:"N";s:4:"周";s:1:"N";s:4:"咢";s:1:"N";s:4:"哶";s:1:"N";s:4:"唐";s:1:"N";s:4:"啓";s:1:"N";s:4:"啣";s:1:"N";s:4:"善";s:1:"N";s:4:"善";s:1:"N";s:4:"喙";s:1:"N";s:4:"喫";s:1:"N";s:4:"喳";s:1:"N";s:4:"嗂";s:1:"N";s:4:"圖";s:1:"N";s:4:"嘆";s:1:"N";s:4:"圗";s:1:"N";s:4:"噑";s:1:"N";s:4:"噴";s:1:"N";s:4:"切";s:1:"N";s:4:"壮";s:1:"N";s:4:"城";s:1:"N";s:4:"埴";s:1:"N";s:4:"堍";s:1:"N";s:4:"型";s:1:"N";s:4:"堲";s:1:"N";s:4:"報";s:1:"N";s:4:"墬";s:1:"N";s:4:"𡓤";s:1:"N";s:4:"売";s:1:"N";s:4:"壷";s:1:"N";s:4:"夆";s:1:"N";s:4:"多";s:1:"N";s:4:"夢";s:1:"N";s:4:"奢";s:1:"N";s:4:"𡚨";s:1:"N";s:4:"𡛪";s:1:"N";s:4:"姬";s:1:"N";s:4:"娛";s:1:"N";s:4:"娧";s:1:"N";s:4:"姘";s:1:"N";s:4:"婦";s:1:"N";s:4:"㛮";s:1:"N";s:4:"㛼";s:1:"N";s:4:"嬈";s:1:"N";s:4:"嬾";s:1:"N";s:4:"嬾";s:1:"N";s:4:"𡧈";s:1:"N";s:4:"寃";s:1:"N";s:4:"寘";s:1:"N";s:4:"寧";s:1:"N";s:4:"寳";s:1:"N";s:4:"𡬘";s:1:"N";s:4:"寿";s:1:"N";s:4:"将";s:1:"N";s:4:"当";s:1:"N";s:4:"尢";s:1:"N";s:4:"㞁";s:1:"N";s:4:"屠";s:1:"N";s:4:"屮";s:1:"N";s:4:"峀";s:1:"N";s:4:"岍";s:1:"N";s:4:"𡷤";s:1:"N";s:4:"嵃";s:1:"N";s:4:"𡷦";s:1:"N";s:4:"嵮";s:1:"N";s:4:"嵫";s:1:"N";s:4:"嵼";s:1:"N";s:4:"巡";s:1:"N";s:4:"巢";s:1:"N";s:4:"㠯";s:1:"N";s:4:"巽";s:1:"N";s:4:"帨";s:1:"N";s:4:"帽";s:1:"N";s:4:"幩";s:1:"N";s:4:"㡢";s:1:"N";s:4:"𢆃";s:1:"N";s:4:"㡼";s:1:"N";s:4:"庰";s:1:"N";s:4:"庳";s:1:"N";s:4:"庶";s:1:"N";s:4:"廊";s:1:"N";s:4:"𪎒";s:1:"N";s:4:"廾";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"舁";s:1:"N";s:4:"弢";s:1:"N";s:4:"弢";s:1:"N";s:4:"㣇";s:1:"N";s:4:"𣊸";s:1:"N";s:4:"𦇚";s:1:"N";s:4:"形";s:1:"N";s:4:"彫";s:1:"N";s:4:"㣣";s:1:"N";s:4:"徚";s:1:"N";s:4:"忍";s:1:"N";s:4:"志";s:1:"N";s:4:"忹";s:1:"N";s:4:"悁";s:1:"N";s:4:"㤺";s:1:"N";s:4:"㤜";s:1:"N";s:4:"悔";s:1:"N";s:4:"𢛔";s:1:"N";s:4:"惇";s:1:"N";s:4:"慈";s:1:"N";s:4:"慌";s:1:"N";s:4:"慎";s:1:"N";s:4:"慌";s:1:"N";s:4:"慺";s:1:"N";s:4:"憎";s:1:"N";s:4:"憲";s:1:"N";s:4:"憤";s:1:"N";s:4:"憯";s:1:"N";s:4:"懞";s:1:"N";s:4:"懲";s:1:"N";s:4:"懶";s:1:"N";s:4:"成";s:1:"N";s:4:"戛";s:1:"N";s:4:"扝";s:1:"N";s:4:"抱";s:1:"N";s:4:"拔";s:1:"N";s:4:"捐";s:1:"N";s:4:"𢬌";s:1:"N";s:4:"挽";s:1:"N";s:4:"拼";s:1:"N";s:4:"捨";s:1:"N";s:4:"掃";s:1:"N";s:4:"揤";s:1:"N";s:4:"𢯱";s:1:"N";s:4:"搢";s:1:"N";s:4:"揅";s:1:"N";s:4:"掩";s:1:"N";s:4:"㨮";s:1:"N";s:4:"摩";s:1:"N";s:4:"摾";s:1:"N";s:4:"撝";s:1:"N";s:4:"摷";s:1:"N";s:4:"㩬";s:1:"N";s:4:"敏";s:1:"N";s:4:"敬";s:1:"N";s:4:"𣀊";s:1:"N";s:4:"旣";s:1:"N";s:4:"書";s:1:"N";s:4:"晉";s:1:"N";s:4:"㬙";s:1:"N";s:4:"暑";s:1:"N";s:4:"㬈";s:1:"N";s:4:"㫤";s:1:"N";s:4:"冒";s:1:"N";s:4:"冕";s:1:"N";s:4:"最";s:1:"N";s:4:"暜";s:1:"N";s:4:"肭";s:1:"N";s:4:"䏙";s:1:"N";s:4:"朗";s:1:"N";s:4:"望";s:1:"N";s:4:"朡";s:1:"N";s:4:"杞";s:1:"N";s:4:"杓";s:1:"N";s:4:"𣏃";s:1:"N";s:4:"㭉";s:1:"N";s:4:"柺";s:1:"N";s:4:"枅";s:1:"N";s:4:"桒";s:1:"N";s:4:"梅";s:1:"N";s:4:"𣑭";s:1:"N";s:4:"梎";s:1:"N";s:4:"栟";s:1:"N";s:4:"椔";s:1:"N";s:4:"㮝";s:1:"N";s:4:"楂";s:1:"N";s:4:"榣";s:1:"N";s:4:"槪";s:1:"N";s:4:"檨";s:1:"N";s:4:"𣚣";s:1:"N";s:4:"櫛";s:1:"N";s:4:"㰘";s:1:"N";s:4:"次";s:1:"N";s:4:"𣢧";s:1:"N";s:4:"歔";s:1:"N";s:4:"㱎";s:1:"N";s:4:"歲";s:1:"N";s:4:"殟";s:1:"N";s:4:"殺";s:1:"N";s:4:"殻";s:1:"N";s:4:"𣪍";s:1:"N";s:4:"𡴋";s:1:"N";s:4:"𣫺";s:1:"N";s:4:"汎";s:1:"N";s:4:"𣲼";s:1:"N";s:4:"沿";s:1:"N";s:4:"泍";s:1:"N";s:4:"汧";s:1:"N";s:4:"洖";s:1:"N";s:4:"派";s:1:"N";s:4:"海";s:1:"N";s:4:"流";s:1:"N";s:4:"浩";s:1:"N";s:4:"浸";s:1:"N";s:4:"涅";s:1:"N";s:4:"𣴞";s:1:"N";s:4:"洴";s:1:"N";s:4:"港";s:1:"N";s:4:"湮";s:1:"N";s:4:"㴳";s:1:"N";s:4:"滋";s:1:"N";s:4:"滇";s:1:"N";s:4:"𣻑";s:1:"N";s:4:"淹";s:1:"N";s:4:"潮";s:1:"N";s:4:"𣽞";s:1:"N";s:4:"𣾎";s:1:"N";s:4:"濆";s:1:"N";s:4:"瀹";s:1:"N";s:4:"瀞";s:1:"N";s:4:"瀛";s:1:"N";s:4:"㶖";s:1:"N";s:4:"灊";s:1:"N";s:4:"災";s:1:"N";s:4:"灷";s:1:"N";s:4:"炭";s:1:"N";s:4:"𠔥";s:1:"N";s:4:"煅";s:1:"N";s:4:"𤉣";s:1:"N";s:4:"熜";s:1:"N";s:4:"𤎫";s:1:"N";s:4:"爨";s:1:"N";s:4:"爵";s:1:"N";s:4:"牐";s:1:"N";s:4:"𤘈";s:1:"N";s:4:"犀";s:1:"N";s:4:"犕";s:1:"N";s:4:"𤜵";s:1:"N";s:4:"𤠔";s:1:"N";s:4:"獺";s:1:"N";s:4:"王";s:1:"N";s:4:"㺬";s:1:"N";s:4:"玥";s:1:"N";s:4:"㺸";s:1:"N";s:4:"㺸";s:1:"N";s:4:"瑇";s:1:"N";s:4:"瑜";s:1:"N";s:4:"瑱";s:1:"N";s:4:"璅";s:1:"N";s:4:"瓊";s:1:"N";s:4:"㼛";s:1:"N";s:4:"甤";s:1:"N";s:4:"𤰶";s:1:"N";s:4:"甾";s:1:"N";s:4:"𤲒";s:1:"N";s:4:"異";s:1:"N";s:4:"𢆟";s:1:"N";s:4:"瘐";s:1:"N";s:4:"𤾡";s:1:"N";s:4:"𤾸";s:1:"N";s:4:"𥁄";s:1:"N";s:4:"㿼";s:1:"N";s:4:"䀈";s:1:"N";s:4:"直";s:1:"N";s:4:"𥃳";s:1:"N";s:4:"𥃲";s:1:"N";s:4:"𥄙";s:1:"N";s:4:"𥄳";s:1:"N";s:4:"眞";s:1:"N";s:4:"真";s:1:"N";s:4:"真";s:1:"N";s:4:"睊";s:1:"N";s:4:"䀹";s:1:"N";s:4:"瞋";s:1:"N";s:4:"䁆";s:1:"N";s:4:"䂖";s:1:"N";s:4:"𥐝";s:1:"N";s:4:"硎";s:1:"N";s:4:"碌";s:1:"N";s:4:"磌";s:1:"N";s:4:"䃣";s:1:"N";s:4:"𥘦";s:1:"N";s:4:"祖";s:1:"N";s:4:"𥚚";s:1:"N";s:4:"𥛅";s:1:"N";s:4:"福";s:1:"N";s:4:"秫";s:1:"N";s:4:"䄯";s:1:"N";s:4:"穀";s:1:"N";s:4:"穊";s:1:"N";s:4:"穏";s:1:"N";s:4:"𥥼";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"竮";s:1:"N";s:4:"䈂";s:1:"N";s:4:"𥮫";s:1:"N";s:4:"篆";s:1:"N";s:4:"築";s:1:"N";s:4:"䈧";s:1:"N";s:4:"𥲀";s:1:"N";s:4:"糒";s:1:"N";s:4:"䊠";s:1:"N";s:4:"糨";s:1:"N";s:4:"糣";s:1:"N";s:4:"紀";s:1:"N";s:4:"𥾆";s:1:"N";s:4:"絣";s:1:"N";s:4:"䌁";s:1:"N";s:4:"緇";s:1:"N";s:4:"縂";s:1:"N";s:4:"繅";s:1:"N";s:4:"䌴";s:1:"N";s:4:"𦈨";s:1:"N";s:4:"𦉇";s:1:"N";s:4:"䍙";s:1:"N";s:4:"𦋙";s:1:"N";s:4:"罺";s:1:"N";s:4:"𦌾";s:1:"N";s:4:"羕";s:1:"N";s:4:"翺";s:1:"N";s:4:"者";s:1:"N";s:4:"𦓚";s:1:"N";s:4:"𦔣";s:1:"N";s:4:"聠";s:1:"N";s:4:"𦖨";s:1:"N";s:4:"聰";s:1:"N";s:4:"𣍟";s:1:"N";s:4:"䏕";s:1:"N";s:4:"育";s:1:"N";s:4:"脃";s:1:"N";s:4:"䐋";s:1:"N";s:4:"脾";s:1:"N";s:4:"媵";s:1:"N";s:4:"𦞧";s:1:"N";s:4:"𦞵";s:1:"N";s:4:"𣎓";s:1:"N";s:4:"𣎜";s:1:"N";s:4:"舁";s:1:"N";s:4:"舄";s:1:"N";s:4:"辞";s:1:"N";s:4:"䑫";s:1:"N";s:4:"芑";s:1:"N";s:4:"芋";s:1:"N";s:4:"芝";s:1:"N";s:4:"劳";s:1:"N";s:4:"花";s:1:"N";s:4:"芳";s:1:"N";s:4:"芽";s:1:"N";s:4:"苦";s:1:"N";s:4:"𦬼";s:1:"N";s:4:"若";s:1:"N";s:4:"茝";s:1:"N";s:4:"荣";s:1:"N";s:4:"莭";s:1:"N";s:4:"茣";s:1:"N";s:4:"莽";s:1:"N";s:4:"菧";s:1:"N";s:4:"著";s:1:"N";s:4:"荓";s:1:"N";s:4:"菊";s:1:"N";s:4:"菌";s:1:"N";s:4:"菜";s:1:"N";s:4:"𦰶";s:1:"N";s:4:"𦵫";s:1:"N";s:4:"𦳕";s:1:"N";s:4:"䔫";s:1:"N";s:4:"蓱";s:1:"N";s:4:"蓳";s:1:"N";s:4:"蔖";s:1:"N";s:4:"𧏊";s:1:"N";s:4:"蕤";s:1:"N";s:4:"𦼬";s:1:"N";s:4:"䕝";s:1:"N";s:4:"䕡";s:1:"N";s:4:"𦾱";s:1:"N";s:4:"𧃒";s:1:"N";s:4:"䕫";s:1:"N";s:4:"虐";s:1:"N";s:4:"虜";s:1:"N";s:4:"虧";s:1:"N";s:4:"虩";s:1:"N";s:4:"蚩";s:1:"N";s:4:"蚈";s:1:"N";s:4:"蜎";s:1:"N";s:4:"蛢";s:1:"N";s:4:"蝹";s:1:"N";s:4:"蜨";s:1:"N";s:4:"蝫";s:1:"N";s:4:"螆";s:1:"N";s:4:"䗗";s:1:"N";s:4:"蟡";s:1:"N";s:4:"蠁";s:1:"N";s:4:"䗹";s:1:"N";s:4:"衠";s:1:"N";s:4:"衣";s:1:"N";s:4:"𧙧";s:1:"N";s:4:"裗";s:1:"N";s:4:"裞";s:1:"N";s:4:"䘵";s:1:"N";s:4:"裺";s:1:"N";s:4:"㒻";s:1:"N";s:4:"𧢮";s:1:"N";s:4:"𧥦";s:1:"N";s:4:"䚾";s:1:"N";s:4:"䛇";s:1:"N";s:4:"誠";s:1:"N";s:4:"諭";s:1:"N";s:4:"變";s:1:"N";s:4:"豕";s:1:"N";s:4:"𧲨";s:1:"N";s:4:"貫";s:1:"N";s:4:"賁";s:1:"N";s:4:"贛";s:1:"N";s:4:"起";s:1:"N";s:4:"𧼯";s:1:"N";s:4:"𠠄";s:1:"N";s:4:"跋";s:1:"N";s:4:"趼";s:1:"N";s:4:"跰";s:1:"N";s:4:"𠣞";s:1:"N";s:4:"軔";s:1:"N";s:4:"輸";s:1:"N";s:4:"𨗒";s:1:"N";s:4:"𨗭";s:1:"N";s:4:"邔";s:1:"N";s:4:"郱";s:1:"N";s:4:"鄑";s:1:"N";s:4:"𨜮";s:1:"N";s:4:"鄛";s:1:"N";s:4:"鈸";s:1:"N";s:4:"鋗";s:1:"N";s:4:"鋘";s:1:"N";s:4:"鉼";s:1:"N";s:4:"鏹";s:1:"N";s:4:"鐕";s:1:"N";s:4:"𨯺";s:1:"N";s:4:"開";s:1:"N";s:4:"䦕";s:1:"N";s:4:"閷";s:1:"N";s:4:"𨵷";s:1:"N";s:4:"䧦";s:1:"N";s:4:"雃";s:1:"N";s:4:"嶲";s:1:"N";s:4:"霣";s:1:"N";s:4:"𩅅";s:1:"N";s:4:"𩈚";s:1:"N";s:4:"䩮";s:1:"N";s:4:"䩶";s:1:"N";s:4:"韠";s:1:"N";s:4:"𩐊";s:1:"N";s:4:"䪲";s:1:"N";s:4:"𩒖";s:1:"N";s:4:"頋";s:1:"N";s:4:"頋";s:1:"N";s:4:"頩";s:1:"N";s:4:"𩖶";s:1:"N";s:4:"飢";s:1:"N";s:4:"䬳";s:1:"N";s:4:"餩";s:1:"N";s:4:"馧";s:1:"N";s:4:"駂";s:1:"N";s:4:"駾";s:1:"N";s:4:"䯎";s:1:"N";s:4:"𩬰";s:1:"N";s:4:"鬒";s:1:"N";s:4:"鱀";s:1:"N";s:4:"鳽";s:1:"N";s:4:"䳎";s:1:"N";s:4:"䳭";s:1:"N";s:4:"鵧";s:1:"N";s:4:"𪃎";s:1:"N";s:4:"䳸";s:1:"N";s:4:"𪄅";s:1:"N";s:4:"𪈎";s:1:"N";s:4:"𪊑";s:1:"N";s:4:"麻";s:1:"N";s:4:"䵖";s:1:"N";s:4:"黹";s:1:"N";s:4:"黾";s:1:"N";s:4:"鼅";s:1:"N";s:4:"鼏";s:1:"N";s:4:"鼖";s:1:"N";s:4:"鼻";s:1:"N";s:4:"𪘀";s:1:"N";s:2:"̀";s:1:"M";s:2:"́";s:1:"M";s:2:"̂";s:1:"M";s:2:"̃";s:1:"M";s:2:"̄";s:1:"M";s:2:"̆";s:1:"M";s:2:"̇";s:1:"M";s:2:"̈";s:1:"M";s:2:"̉";s:1:"M";s:2:"̊";s:1:"M";s:2:"̋";s:1:"M";s:2:"̌";s:1:"M";s:2:"̏";s:1:"M";s:2:"̑";s:1:"M";s:2:"̓";s:1:"M";s:2:"̔";s:1:"M";s:2:"̛";s:1:"M";s:2:"̣";s:1:"M";s:2:"̤";s:1:"M";s:2:"̥";s:1:"M";s:2:"̦";s:1:"M";s:2:"̧";s:1:"M";s:2:"̨";s:1:"M";s:2:"̭";s:1:"M";s:2:"̮";s:1:"M";s:2:"̰";s:1:"M";s:2:"̱";s:1:"M";s:2:"̸";s:1:"M";s:2:"͂";s:1:"M";s:2:"ͅ";s:1:"M";s:2:"ٓ";s:1:"M";s:2:"ٔ";s:1:"M";s:2:"ٕ";s:1:"M";s:3:"़";s:1:"M";s:3:"া";s:1:"M";s:3:"ৗ";s:1:"M";s:3:"ା";s:1:"M";s:3:"ୖ";s:1:"M";s:3:"ୗ";s:1:"M";s:3:"ா";s:1:"M";s:3:"ௗ";s:1:"M";s:3:"ౖ";s:1:"M";s:3:"ೂ";s:1:"M";s:3:"ೕ";s:1:"M";s:3:"ೖ";s:1:"M";s:3:"ാ";s:1:"M";s:3:"ൗ";s:1:"M";s:3:"්";s:1:"M";s:3:"ා";s:1:"M";s:3:"ෟ";s:1:"M";s:3:"ီ";s:1:"M";s:3:"ᅡ";s:1:"M";s:3:"ᅢ";s:1:"M";s:3:"ᅣ";s:1:"M";s:3:"ᅤ";s:1:"M";s:3:"ᅥ";s:1:"M";s:3:"ᅦ";s:1:"M";s:3:"ᅧ";s:1:"M";s:3:"ᅨ";s:1:"M";s:3:"ᅩ";s:1:"M";s:3:"ᅪ";s:1:"M";s:3:"ᅫ";s:1:"M";s:3:"ᅬ";s:1:"M";s:3:"ᅭ";s:1:"M";s:3:"ᅮ";s:1:"M";s:3:"ᅯ";s:1:"M";s:3:"ᅰ";s:1:"M";s:3:"ᅱ";s:1:"M";s:3:"ᅲ";s:1:"M";s:3:"ᅳ";s:1:"M";s:3:"ᅴ";s:1:"M";s:3:"ᅵ";s:1:"M";s:3:"ᆨ";s:1:"M";s:3:"ᆩ";s:1:"M";s:3:"ᆪ";s:1:"M";s:3:"ᆫ";s:1:"M";s:3:"ᆬ";s:1:"M";s:3:"ᆭ";s:1:"M";s:3:"ᆮ";s:1:"M";s:3:"ᆯ";s:1:"M";s:3:"ᆰ";s:1:"M";s:3:"ᆱ";s:1:"M";s:3:"ᆲ";s:1:"M";s:3:"ᆳ";s:1:"M";s:3:"ᆴ";s:1:"M";s:3:"ᆵ";s:1:"M";s:3:"ᆶ";s:1:"M";s:3:"ᆷ";s:1:"M";s:3:"ᆸ";s:1:"M";s:3:"ᆹ";s:1:"M";s:3:"ᆺ";s:1:"M";s:3:"ᆻ";s:1:"M";s:3:"ᆼ";s:1:"M";s:3:"ᆽ";s:1:"M";s:3:"ᆾ";s:1:"M";s:3:"ᆿ";s:1:"M";s:3:"ᇀ";s:1:"M";s:3:"ᇁ";s:1:"M";s:3:"ᇂ";s:1:"M";s:3:"ᬵ";s:1:"M";s:3:"゙";s:1:"M";s:3:"゚";s:1:"M";s:4:"𑂺";s:1:"M";}' );
diff --git a/includes/normal/UtfNormalDataK.inc b/includes/normal/UtfNormalDataK.inc
index 9d30f282..661d2cda 100644
--- a/includes/normal/UtfNormalDataK.inc
+++ b/includes/normal/UtfNormalDataK.inc
@@ -6,5 +6,5 @@
* @file
*/
-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:"𪘀";}' );
+UtfNormal::$utfCompatibilityDecomp = unserialize( 'a:5559:{s:2:" ";s:1:" ";s:2:"¨";s:3:" ̈";s:2:"ª";s:1:"a";s:2:"¯";s:3:" ̄";s:2:"²";s:1:"2";s:2:"³";s:1:"3";s:2:"´";s:3:" ́";s:2:"µ";s:2:"μ";s:2:"¸";s:3:" ̧";s:2:"¹";s:1:"1";s:2:"º";s:1:"o";s:2:"¼";s:5:"1⁄4";s:2:"½";s:5:"1⁄2";s:2:"¾";s:5:"3⁄4";s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"IJ";s:2:"IJ";s:2:"ij";s:2:"ij";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ŀ";s:3:"L·";s:2:"ŀ";s:3:"l·";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"ʼn";s:3:"ʼn";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"ſ";s:1:"s";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"DŽ";s:4:"DŽ";s:2:"Dž";s:4:"Dž";s:2:"dž";s:4:"dž";s:2:"LJ";s:2:"LJ";s:2:"Lj";s:2:"Lj";s:2:"lj";s:2:"lj";s:2:"NJ";s:2:"NJ";s:2:"Nj";s:2:"Nj";s:2:"nj";s:2:"nj";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"DZ";s:2:"DZ";s:2:"Dz";s:2:"Dz";s:2:"dz";s:2:"dz";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"ʰ";s:1:"h";s:2:"ʱ";s:2:"ɦ";s:2:"ʲ";s:1:"j";s:2:"ʳ";s:1:"r";s:2:"ʴ";s:2:"ɹ";s:2:"ʵ";s:2:"ɻ";s:2:"ʶ";s:2:"ʁ";s:2:"ʷ";s:1:"w";s:2:"ʸ";s:1:"y";s:2:"˘";s:3:" ̆";s:2:"˙";s:3:" ̇";s:2:"˚";s:3:" ̊";s:2:"˛";s:3:" ̨";s:2:"˜";s:3:" ̃";s:2:"˝";s:3:" ̋";s:2:"ˠ";s:2:"ɣ";s:2:"ˡ";s:1:"l";s:2:"ˢ";s:1:"s";s:2:"ˣ";s:1:"x";s:2:"ˤ";s:2:"ʕ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:"ͺ";s:3:" ͅ";s:2:";";s:1:";";s:2:"΄";s:3:" ́";s:2:"΅";s:5:" ̈́";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϐ";s:2:"β";s:2:"ϑ";s:2:"θ";s:2:"ϒ";s:2:"Υ";s:2:"ϓ";s:4:"Ύ";s:2:"ϔ";s:4:"Ϋ";s:2:"ϕ";s:2:"φ";s:2:"ϖ";s:2:"π";s:2:"ϰ";s:2:"κ";s:2:"ϱ";s:2:"ρ";s:2:"ϲ";s:2:"ς";s:2:"ϴ";s:2:"Θ";s:2:"ϵ";s:2:"ε";s:2:"Ϲ";s:2:"Σ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"և";s:4:"եւ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ٵ";s:4:"اٴ";s:2:"ٶ";s:4:"وٴ";s:2:"ٷ";s:4:"ۇٴ";s:2:"ٸ";s:4:"يٴ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"ำ";s:6:"ํา";s:3:"ຳ";s:6:"ໍາ";s:3:"ໜ";s:6:"ຫນ";s:3:"ໝ";s:6:"ຫມ";s:3:"༌";s:3:"་";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ཷ";s:9:"ྲཱྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཹ";s:9:"ླཱྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"ჼ";s:3:"ნ";s:3:"ᬆ";s:6:"ᬆ";s:3:"ᬈ";s:6:"ᬈ";s:3:"ᬊ";s:6:"ᬊ";s:3:"ᬌ";s:6:"ᬌ";s:3:"ᬎ";s:6:"ᬎ";s:3:"ᬒ";s:6:"ᬒ";s:3:"ᬻ";s:6:"ᬻ";s:3:"ᬽ";s:6:"ᬽ";s:3:"ᭀ";s:6:"ᭀ";s:3:"ᭁ";s:6:"ᭁ";s:3:"ᭃ";s:6:"ᭃ";s:3:"ᴬ";s:1:"A";s:3:"ᴭ";s:2:"Æ";s:3:"ᴮ";s:1:"B";s:3:"ᴰ";s:1:"D";s:3:"ᴱ";s:1:"E";s:3:"ᴲ";s:2:"Ǝ";s:3:"ᴳ";s:1:"G";s:3:"ᴴ";s:1:"H";s:3:"ᴵ";s:1:"I";s:3:"ᴶ";s:1:"J";s:3:"ᴷ";s:1:"K";s:3:"ᴸ";s:1:"L";s:3:"ᴹ";s:1:"M";s:3:"ᴺ";s:1:"N";s:3:"ᴼ";s:1:"O";s:3:"ᴽ";s:2:"Ȣ";s:3:"ᴾ";s:1:"P";s:3:"ᴿ";s:1:"R";s:3:"ᵀ";s:1:"T";s:3:"ᵁ";s:1:"U";s:3:"ᵂ";s:1:"W";s:3:"ᵃ";s:1:"a";s:3:"ᵄ";s:2:"ɐ";s:3:"ᵅ";s:2:"ɑ";s:3:"ᵆ";s:3:"ᴂ";s:3:"ᵇ";s:1:"b";s:3:"ᵈ";s:1:"d";s:3:"ᵉ";s:1:"e";s:3:"ᵊ";s:2:"ə";s:3:"ᵋ";s:2:"ɛ";s:3:"ᵌ";s:2:"ɜ";s:3:"ᵍ";s:1:"g";s:3:"ᵏ";s:1:"k";s:3:"ᵐ";s:1:"m";s:3:"ᵑ";s:2:"ŋ";s:3:"ᵒ";s:1:"o";s:3:"ᵓ";s:2:"ɔ";s:3:"ᵔ";s:3:"ᴖ";s:3:"ᵕ";s:3:"ᴗ";s:3:"ᵖ";s:1:"p";s:3:"ᵗ";s:1:"t";s:3:"ᵘ";s:1:"u";s:3:"ᵙ";s:3:"ᴝ";s:3:"ᵚ";s:2:"ɯ";s:3:"ᵛ";s:1:"v";s:3:"ᵜ";s:3:"ᴥ";s:3:"ᵝ";s:2:"β";s:3:"ᵞ";s:2:"γ";s:3:"ᵟ";s:2:"δ";s:3:"ᵠ";s:2:"φ";s:3:"ᵡ";s:2:"χ";s:3:"ᵢ";s:1:"i";s:3:"ᵣ";s:1:"r";s:3:"ᵤ";s:1:"u";s:3:"ᵥ";s:1:"v";s:3:"ᵦ";s:2:"β";s:3:"ᵧ";s:2:"γ";s:3:"ᵨ";s:2:"ρ";s:3:"ᵩ";s:2:"φ";s:3:"ᵪ";s:2:"χ";s:3:"ᵸ";s:2:"н";s:3:"ᶛ";s:2:"ɒ";s:3:"ᶜ";s:1:"c";s:3:"ᶝ";s:2:"ɕ";s:3:"ᶞ";s:2:"ð";s:3:"ᶟ";s:2:"ɜ";s:3:"ᶠ";s:1:"f";s:3:"ᶡ";s:2:"ɟ";s:3:"ᶢ";s:2:"ɡ";s:3:"ᶣ";s:2:"ɥ";s:3:"ᶤ";s:2:"ɨ";s:3:"ᶥ";s:2:"ɩ";s:3:"ᶦ";s:2:"ɪ";s:3:"ᶧ";s:3:"ᵻ";s:3:"ᶨ";s:2:"ʝ";s:3:"ᶩ";s:2:"ɭ";s:3:"ᶪ";s:3:"ᶅ";s:3:"ᶫ";s:2:"ʟ";s:3:"ᶬ";s:2:"ɱ";s:3:"ᶭ";s:2:"ɰ";s:3:"ᶮ";s:2:"ɲ";s:3:"ᶯ";s:2:"ɳ";s:3:"ᶰ";s:2:"ɴ";s:3:"ᶱ";s:2:"ɵ";s:3:"ᶲ";s:2:"ɸ";s:3:"ᶳ";s:2:"ʂ";s:3:"ᶴ";s:2:"ʃ";s:3:"ᶵ";s:2:"ƫ";s:3:"ᶶ";s:2:"ʉ";s:3:"ᶷ";s:2:"ʊ";s:3:"ᶸ";s:3:"ᴜ";s:3:"ᶹ";s:2:"ʋ";s:3:"ᶺ";s:2:"ʌ";s:3:"ᶻ";s:1:"z";s:3:"ᶼ";s:2:"ʐ";s:3:"ᶽ";s:2:"ʑ";s:3:"ᶾ";s:2:"ʒ";s:3:"ᶿ";s:2:"θ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẚ";s:3:"aʾ";s:3:"ẛ";s:3:"ṡ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"᾽";s:3:" ̓";s:3:"ι";s:2:"ι";s:3:"᾿";s:3:" ̓";s:3:"῀";s:3:" ͂";s:3:"῁";s:5:" ̈͂";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:" ̓̀";s:3:"῎";s:5:" ̓́";s:3:"῏";s:5:" ̓͂";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:" ̔̀";s:3:"῞";s:5:" ̔́";s:3:"῟";s:5:" ̔͂";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:5:" ̈̀";s:3:"΅";s:5:" ̈́";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:3:" ́";s:3:"῾";s:3:" ̔";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:"‑";s:3:"‐";s:3:"‗";s:3:" ̳";s:3:"․";s:1:".";s:3:"‥";s:2:"..";s:3:"…";s:3:"...";s:3:" ";s:1:" ";s:3:"″";s:6:"′′";s:3:"‴";s:9:"′′′";s:3:"‶";s:6:"‵‵";s:3:"‷";s:9:"‵‵‵";s:3:"‼";s:2:"!!";s:3:"‾";s:3:" ̅";s:3:"⁇";s:2:"??";s:3:"⁈";s:2:"?!";s:3:"⁉";s:2:"!?";s:3:"⁗";s:12:"′′′′";s:3:" ";s:1:" ";s:3:"⁰";s:1:"0";s:3:"ⁱ";s:1:"i";s:3:"⁴";s:1:"4";s:3:"⁵";s:1:"5";s:3:"⁶";s:1:"6";s:3:"⁷";s:1:"7";s:3:"⁸";s:1:"8";s:3:"⁹";s:1:"9";s:3:"⁺";s:1:"+";s:3:"⁻";s:3:"−";s:3:"⁼";s:1:"=";s:3:"⁽";s:1:"(";s:3:"⁾";s:1:")";s:3:"ⁿ";s:1:"n";s:3:"₀";s:1:"0";s:3:"₁";s:1:"1";s:3:"₂";s:1:"2";s:3:"₃";s:1:"3";s:3:"₄";s:1:"4";s:3:"₅";s:1:"5";s:3:"₆";s:1:"6";s:3:"₇";s:1:"7";s:3:"₈";s:1:"8";s:3:"₉";s:1:"9";s:3:"₊";s:1:"+";s:3:"₋";s:3:"−";s:3:"₌";s:1:"=";s:3:"₍";s:1:"(";s:3:"₎";s:1:")";s:3:"ₐ";s:1:"a";s:3:"ₑ";s:1:"e";s:3:"ₒ";s:1:"o";s:3:"ₓ";s:1:"x";s:3:"ₔ";s:2:"ə";s:3:"ₕ";s:1:"h";s:3:"ₖ";s:1:"k";s:3:"ₗ";s:1:"l";s:3:"ₘ";s:1:"m";s:3:"ₙ";s:1:"n";s:3:"ₚ";s:1:"p";s:3:"ₛ";s:1:"s";s:3:"ₜ";s:1:"t";s:3:"₨";s:2:"Rs";s:3:"℀";s:3:"a/c";s:3:"℁";s:3:"a/s";s:3:"ℂ";s:1:"C";s:3:"℃";s:3:"°C";s:3:"℅";s:3:"c/o";s:3:"℆";s:3:"c/u";s:3:"ℇ";s:2:"Ɛ";s:3:"℉";s:3:"°F";s:3:"ℊ";s:1:"g";s:3:"ℋ";s:1:"H";s:3:"ℌ";s:1:"H";s:3:"ℍ";s:1:"H";s:3:"ℎ";s:1:"h";s:3:"ℏ";s:2:"ħ";s:3:"ℐ";s:1:"I";s:3:"ℑ";s:1:"I";s:3:"ℒ";s:1:"L";s:3:"ℓ";s:1:"l";s:3:"ℕ";s:1:"N";s:3:"№";s:2:"No";s:3:"ℙ";s:1:"P";s:3:"ℚ";s:1:"Q";s:3:"ℛ";s:1:"R";s:3:"ℜ";s:1:"R";s:3:"ℝ";s:1:"R";s:3:"℠";s:2:"SM";s:3:"℡";s:3:"TEL";s:3:"™";s:2:"TM";s:3:"ℤ";s:1:"Z";s:3:"Ω";s:2:"Ω";s:3:"ℨ";s:1:"Z";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"ℬ";s:1:"B";s:3:"ℭ";s:1:"C";s:3:"ℯ";s:1:"e";s:3:"ℰ";s:1:"E";s:3:"ℱ";s:1:"F";s:3:"ℳ";s:1:"M";s:3:"ℴ";s:1:"o";s:3:"ℵ";s:2:"א";s:3:"ℶ";s:2:"ב";s:3:"ℷ";s:2:"ג";s:3:"ℸ";s:2:"ד";s:3:"ℹ";s:1:"i";s:3:"℻";s:3:"FAX";s:3:"ℼ";s:2:"π";s:3:"ℽ";s:2:"γ";s:3:"ℾ";s:2:"Γ";s:3:"ℿ";s:2:"Π";s:3:"⅀";s:3:"∑";s:3:"ⅅ";s:1:"D";s:3:"ⅆ";s:1:"d";s:3:"ⅇ";s:1:"e";s:3:"ⅈ";s:1:"i";s:3:"ⅉ";s:1:"j";s:3:"⅐";s:5:"1⁄7";s:3:"⅑";s:5:"1⁄9";s:3:"⅒";s:6:"1⁄10";s:3:"⅓";s:5:"1⁄3";s:3:"⅔";s:5:"2⁄3";s:3:"⅕";s:5:"1⁄5";s:3:"⅖";s:5:"2⁄5";s:3:"⅗";s:5:"3⁄5";s:3:"⅘";s:5:"4⁄5";s:3:"⅙";s:5:"1⁄6";s:3:"⅚";s:5:"5⁄6";s:3:"⅛";s:5:"1⁄8";s:3:"⅜";s:5:"3⁄8";s:3:"⅝";s:5:"5⁄8";s:3:"⅞";s:5:"7⁄8";s:3:"⅟";s:4:"1⁄";s:3:"Ⅰ";s:1:"I";s:3:"Ⅱ";s:2:"II";s:3:"Ⅲ";s:3:"III";s:3:"Ⅳ";s:2:"IV";s:3:"Ⅴ";s:1:"V";s:3:"Ⅵ";s:2:"VI";s:3:"Ⅶ";s:3:"VII";s:3:"Ⅷ";s:4:"VIII";s:3:"Ⅸ";s:2:"IX";s:3:"Ⅹ";s:1:"X";s:3:"Ⅺ";s:2:"XI";s:3:"Ⅻ";s:3:"XII";s:3:"Ⅼ";s:1:"L";s:3:"Ⅽ";s:1:"C";s:3:"Ⅾ";s:1:"D";s:3:"Ⅿ";s:1:"M";s:3:"ⅰ";s:1:"i";s:3:"ⅱ";s:2:"ii";s:3:"ⅲ";s:3:"iii";s:3:"ⅳ";s:2:"iv";s:3:"ⅴ";s:1:"v";s:3:"ⅵ";s:2:"vi";s:3:"ⅶ";s:3:"vii";s:3:"ⅷ";s:4:"viii";s:3:"ⅸ";s:2:"ix";s:3:"ⅹ";s:1:"x";s:3:"ⅺ";s:2:"xi";s:3:"ⅻ";s:3:"xii";s:3:"ⅼ";s:1:"l";s:3:"ⅽ";s:1:"c";s:3:"ⅾ";s:1:"d";s:3:"ⅿ";s:1:"m";s:3:"↉";s:5:"0⁄3";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"∬";s:6:"∫∫";s:3:"∭";s:9:"∫∫∫";s:3:"∯";s:6:"∮∮";s:3:"∰";s:9:"∮∮∮";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"①";s:1:"1";s:3:"②";s:1:"2";s:3:"③";s:1:"3";s:3:"④";s:1:"4";s:3:"⑤";s:1:"5";s:3:"⑥";s:1:"6";s:3:"⑦";s:1:"7";s:3:"⑧";s:1:"8";s:3:"⑨";s:1:"9";s:3:"⑩";s:2:"10";s:3:"⑪";s:2:"11";s:3:"⑫";s:2:"12";s:3:"⑬";s:2:"13";s:3:"⑭";s:2:"14";s:3:"⑮";s:2:"15";s:3:"⑯";s:2:"16";s:3:"⑰";s:2:"17";s:3:"⑱";s:2:"18";s:3:"⑲";s:2:"19";s:3:"⑳";s:2:"20";s:3:"⑴";s:3:"(1)";s:3:"⑵";s:3:"(2)";s:3:"⑶";s:3:"(3)";s:3:"⑷";s:3:"(4)";s:3:"⑸";s:3:"(5)";s:3:"⑹";s:3:"(6)";s:3:"⑺";s:3:"(7)";s:3:"⑻";s:3:"(8)";s:3:"⑼";s:3:"(9)";s:3:"⑽";s:4:"(10)";s:3:"⑾";s:4:"(11)";s:3:"⑿";s:4:"(12)";s:3:"⒀";s:4:"(13)";s:3:"⒁";s:4:"(14)";s:3:"⒂";s:4:"(15)";s:3:"⒃";s:4:"(16)";s:3:"⒄";s:4:"(17)";s:3:"⒅";s:4:"(18)";s:3:"⒆";s:4:"(19)";s:3:"⒇";s:4:"(20)";s:3:"⒈";s:2:"1.";s:3:"⒉";s:2:"2.";s:3:"⒊";s:2:"3.";s:3:"⒋";s:2:"4.";s:3:"⒌";s:2:"5.";s:3:"⒍";s:2:"6.";s:3:"⒎";s:2:"7.";s:3:"⒏";s:2:"8.";s:3:"⒐";s:2:"9.";s:3:"⒑";s:3:"10.";s:3:"⒒";s:3:"11.";s:3:"⒓";s:3:"12.";s:3:"⒔";s:3:"13.";s:3:"⒕";s:3:"14.";s:3:"⒖";s:3:"15.";s:3:"⒗";s:3:"16.";s:3:"⒘";s:3:"17.";s:3:"⒙";s:3:"18.";s:3:"⒚";s:3:"19.";s:3:"⒛";s:3:"20.";s:3:"⒜";s:3:"(a)";s:3:"⒝";s:3:"(b)";s:3:"⒞";s:3:"(c)";s:3:"⒟";s:3:"(d)";s:3:"⒠";s:3:"(e)";s:3:"⒡";s:3:"(f)";s:3:"⒢";s:3:"(g)";s:3:"⒣";s:3:"(h)";s:3:"⒤";s:3:"(i)";s:3:"⒥";s:3:"(j)";s:3:"⒦";s:3:"(k)";s:3:"⒧";s:3:"(l)";s:3:"⒨";s:3:"(m)";s:3:"⒩";s:3:"(n)";s:3:"⒪";s:3:"(o)";s:3:"⒫";s:3:"(p)";s:3:"⒬";s:3:"(q)";s:3:"⒭";s:3:"(r)";s:3:"⒮";s:3:"(s)";s:3:"⒯";s:3:"(t)";s:3:"⒰";s:3:"(u)";s:3:"⒱";s:3:"(v)";s:3:"⒲";s:3:"(w)";s:3:"⒳";s:3:"(x)";s:3:"⒴";s:3:"(y)";s:3:"⒵";s:3:"(z)";s:3:"Ⓐ";s:1:"A";s:3:"Ⓑ";s:1:"B";s:3:"Ⓒ";s:1:"C";s:3:"Ⓓ";s:1:"D";s:3:"Ⓔ";s:1:"E";s:3:"Ⓕ";s:1:"F";s:3:"Ⓖ";s:1:"G";s:3:"Ⓗ";s:1:"H";s:3:"Ⓘ";s:1:"I";s:3:"Ⓙ";s:1:"J";s:3:"Ⓚ";s:1:"K";s:3:"Ⓛ";s:1:"L";s:3:"Ⓜ";s:1:"M";s:3:"Ⓝ";s:1:"N";s:3:"Ⓞ";s:1:"O";s:3:"Ⓟ";s:1:"P";s:3:"Ⓠ";s:1:"Q";s:3:"Ⓡ";s:1:"R";s:3:"Ⓢ";s:1:"S";s:3:"Ⓣ";s:1:"T";s:3:"Ⓤ";s:1:"U";s:3:"Ⓥ";s:1:"V";s:3:"Ⓦ";s:1:"W";s:3:"Ⓧ";s:1:"X";s:3:"Ⓨ";s:1:"Y";s:3:"Ⓩ";s:1:"Z";s:3:"ⓐ";s:1:"a";s:3:"ⓑ";s:1:"b";s:3:"ⓒ";s:1:"c";s:3:"ⓓ";s:1:"d";s:3:"ⓔ";s:1:"e";s:3:"ⓕ";s:1:"f";s:3:"ⓖ";s:1:"g";s:3:"ⓗ";s:1:"h";s:3:"ⓘ";s:1:"i";s:3:"ⓙ";s:1:"j";s:3:"ⓚ";s:1:"k";s:3:"ⓛ";s:1:"l";s:3:"ⓜ";s:1:"m";s:3:"ⓝ";s:1:"n";s:3:"ⓞ";s:1:"o";s:3:"ⓟ";s:1:"p";s:3:"ⓠ";s:1:"q";s:3:"ⓡ";s:1:"r";s:3:"ⓢ";s:1:"s";s:3:"ⓣ";s:1:"t";s:3:"ⓤ";s:1:"u";s:3:"ⓥ";s:1:"v";s:3:"ⓦ";s:1:"w";s:3:"ⓧ";s:1:"x";s:3:"ⓨ";s:1:"y";s:3:"ⓩ";s:1:"z";s:3:"⓪";s:1:"0";s:3:"⨌";s:12:"∫∫∫∫";s:3:"⩴";s:3:"::=";s:3:"⩵";s:2:"==";s:3:"⩶";s:3:"===";s:3:"⫝̸";s:5:"⫝̸";s:3:"ⱼ";s:1:"j";s:3:"ⱽ";s:1:"V";s:3:"ⵯ";s:3:"ⵡ";s:3:"⺟";s:3:"母";s:3:"⻳";s:3:"龟";s:3:"⼀";s:3:"一";s:3:"⼁";s:3:"丨";s:3:"⼂";s:3:"丶";s:3:"⼃";s:3:"丿";s:3:"⼄";s:3:"乙";s:3:"⼅";s:3:"亅";s:3:"⼆";s:3:"二";s:3:"⼇";s:3:"亠";s:3:"⼈";s:3:"人";s:3:"⼉";s:3:"儿";s:3:"⼊";s:3:"入";s:3:"⼋";s:3:"八";s:3:"⼌";s:3:"冂";s:3:"⼍";s:3:"冖";s:3:"⼎";s:3:"冫";s:3:"⼏";s:3:"几";s:3:"⼐";s:3:"凵";s:3:"⼑";s:3:"刀";s:3:"⼒";s:3:"力";s:3:"⼓";s:3:"勹";s:3:"⼔";s:3:"匕";s:3:"⼕";s:3:"匚";s:3:"⼖";s:3:"匸";s:3:"⼗";s:3:"十";s:3:"⼘";s:3:"卜";s:3:"⼙";s:3:"卩";s:3:"⼚";s:3:"厂";s:3:"⼛";s:3:"厶";s:3:"⼜";s:3:"又";s:3:"⼝";s:3:"口";s:3:"⼞";s:3:"囗";s:3:"⼟";s:3:"土";s:3:"⼠";s:3:"士";s:3:"⼡";s:3:"夂";s:3:"⼢";s:3:"夊";s:3:"⼣";s:3:"夕";s:3:"⼤";s:3:"大";s:3:"⼥";s:3:"女";s:3:"⼦";s:3:"子";s:3:"⼧";s:3:"宀";s:3:"⼨";s:3:"寸";s:3:"⼩";s:3:"小";s:3:"⼪";s:3:"尢";s:3:"⼫";s:3:"尸";s:3:"⼬";s:3:"屮";s:3:"⼭";s:3:"山";s:3:"⼮";s:3:"巛";s:3:"⼯";s:3:"工";s:3:"⼰";s:3:"己";s:3:"⼱";s:3:"巾";s:3:"⼲";s:3:"干";s:3:"⼳";s:3:"幺";s:3:"⼴";s:3:"广";s:3:"⼵";s:3:"廴";s:3:"⼶";s:3:"廾";s:3:"⼷";s:3:"弋";s:3:"⼸";s:3:"弓";s:3:"⼹";s:3:"彐";s:3:"⼺";s:3:"彡";s:3:"⼻";s:3:"彳";s:3:"⼼";s:3:"心";s:3:"⼽";s:3:"戈";s:3:"⼾";s:3:"戶";s:3:"⼿";s:3:"手";s:3:"⽀";s:3:"支";s:3:"⽁";s:3:"攴";s:3:"⽂";s:3:"文";s:3:"⽃";s:3:"斗";s:3:"⽄";s:3:"斤";s:3:"⽅";s:3:"方";s:3:"⽆";s:3:"无";s:3:"⽇";s:3:"日";s:3:"⽈";s:3:"曰";s:3:"⽉";s:3:"月";s:3:"⽊";s:3:"木";s:3:"⽋";s:3:"欠";s:3:"⽌";s:3:"止";s:3:"⽍";s:3:"歹";s:3:"⽎";s:3:"殳";s:3:"⽏";s:3:"毋";s:3:"⽐";s:3:"比";s:3:"⽑";s:3:"毛";s:3:"⽒";s:3:"氏";s:3:"⽓";s:3:"气";s:3:"⽔";s:3:"水";s:3:"⽕";s:3:"火";s:3:"⽖";s:3:"爪";s:3:"⽗";s:3:"父";s:3:"⽘";s:3:"爻";s:3:"⽙";s:3:"爿";s:3:"⽚";s:3:"片";s:3:"⽛";s:3:"牙";s:3:"⽜";s:3:"牛";s:3:"⽝";s:3:"犬";s:3:"⽞";s:3:"玄";s:3:"⽟";s:3:"玉";s:3:"⽠";s:3:"瓜";s:3:"⽡";s:3:"瓦";s:3:"⽢";s:3:"甘";s:3:"⽣";s:3:"生";s:3:"⽤";s:3:"用";s:3:"⽥";s:3:"田";s:3:"⽦";s:3:"疋";s:3:"⽧";s:3:"疒";s:3:"⽨";s:3:"癶";s:3:"⽩";s:3:"白";s:3:"⽪";s:3:"皮";s:3:"⽫";s:3:"皿";s:3:"⽬";s:3:"目";s:3:"⽭";s:3:"矛";s:3:"⽮";s:3:"矢";s:3:"⽯";s:3:"石";s:3:"⽰";s:3:"示";s:3:"⽱";s:3:"禸";s:3:"⽲";s:3:"禾";s:3:"⽳";s:3:"穴";s:3:"⽴";s:3:"立";s:3:"⽵";s:3:"竹";s:3:"⽶";s:3:"米";s:3:"⽷";s:3:"糸";s:3:"⽸";s:3:"缶";s:3:"⽹";s:3:"网";s:3:"⽺";s:3:"羊";s:3:"⽻";s:3:"羽";s:3:"⽼";s:3:"老";s:3:"⽽";s:3:"而";s:3:"⽾";s:3:"耒";s:3:"⽿";s:3:"耳";s:3:"⾀";s:3:"聿";s:3:"⾁";s:3:"肉";s:3:"⾂";s:3:"臣";s:3:"⾃";s:3:"自";s:3:"⾄";s:3:"至";s:3:"⾅";s:3:"臼";s:3:"⾆";s:3:"舌";s:3:"⾇";s:3:"舛";s:3:"⾈";s:3:"舟";s:3:"⾉";s:3:"艮";s:3:"⾊";s:3:"色";s:3:"⾋";s:3:"艸";s:3:"⾌";s:3:"虍";s:3:"⾍";s:3:"虫";s:3:"⾎";s:3:"血";s:3:"⾏";s:3:"行";s:3:"⾐";s:3:"衣";s:3:"⾑";s:3:"襾";s:3:"⾒";s:3:"見";s:3:"⾓";s:3:"角";s:3:"⾔";s:3:"言";s:3:"⾕";s:3:"谷";s:3:"⾖";s:3:"豆";s:3:"⾗";s:3:"豕";s:3:"⾘";s:3:"豸";s:3:"⾙";s:3:"貝";s:3:"⾚";s:3:"赤";s:3:"⾛";s:3:"走";s:3:"⾜";s:3:"足";s:3:"⾝";s:3:"身";s:3:"⾞";s:3:"車";s:3:"⾟";s:3:"辛";s:3:"⾠";s:3:"辰";s:3:"⾡";s:3:"辵";s:3:"⾢";s:3:"邑";s:3:"⾣";s:3:"酉";s:3:"⾤";s:3:"釆";s:3:"⾥";s:3:"里";s:3:"⾦";s:3:"金";s:3:"⾧";s:3:"長";s:3:"⾨";s:3:"門";s:3:"⾩";s:3:"阜";s:3:"⾪";s:3:"隶";s:3:"⾫";s:3:"隹";s:3:"⾬";s:3:"雨";s:3:"⾭";s:3:"靑";s:3:"⾮";s:3:"非";s:3:"⾯";s:3:"面";s:3:"⾰";s:3:"革";s:3:"⾱";s:3:"韋";s:3:"⾲";s:3:"韭";s:3:"⾳";s:3:"音";s:3:"⾴";s:3:"頁";s:3:"⾵";s:3:"風";s:3:"⾶";s:3:"飛";s:3:"⾷";s:3:"食";s:3:"⾸";s:3:"首";s:3:"⾹";s:3:"香";s:3:"⾺";s:3:"馬";s:3:"⾻";s:3:"骨";s:3:"⾼";s:3:"高";s:3:"⾽";s:3:"髟";s:3:"⾾";s:3:"鬥";s:3:"⾿";s:3:"鬯";s:3:"⿀";s:3:"鬲";s:3:"⿁";s:3:"鬼";s:3:"⿂";s:3:"魚";s:3:"⿃";s:3:"鳥";s:3:"⿄";s:3:"鹵";s:3:"⿅";s:3:"鹿";s:3:"⿆";s:3:"麥";s:3:"⿇";s:3:"麻";s:3:"⿈";s:3:"黃";s:3:"⿉";s:3:"黍";s:3:"⿊";s:3:"黑";s:3:"⿋";s:3:"黹";s:3:"⿌";s:3:"黽";s:3:"⿍";s:3:"鼎";s:3:"⿎";s:3:"鼓";s:3:"⿏";s:3:"鼠";s:3:"⿐";s:3:"鼻";s:3:"⿑";s:3:"齊";s:3:"⿒";s:3:"齒";s:3:"⿓";s:3:"龍";s:3:"⿔";s:3:"龜";s:3:"⿕";s:3:"龠";s:3:" ";s:1:" ";s:3:"〶";s:3:"〒";s:3:"〸";s:3:"十";s:3:"〹";s:3:"卄";s:3:"〺";s:3:"卅";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"゛";s:4:" ゙";s:3:"゜";s:4:" ゚";s:3:"ゞ";s:6:"ゞ";s:3:"ゟ";s:6:"より";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"ヿ";s:6:"コト";s:3:"ㄱ";s:3:"ᄀ";s:3:"ㄲ";s:3:"ᄁ";s:3:"ㄳ";s:3:"ᆪ";s:3:"ㄴ";s:3:"ᄂ";s:3:"ㄵ";s:3:"ᆬ";s:3:"ㄶ";s:3:"ᆭ";s:3:"ㄷ";s:3:"ᄃ";s:3:"ㄸ";s:3:"ᄄ";s:3:"ㄹ";s:3:"ᄅ";s:3:"ㄺ";s:3:"ᆰ";s:3:"ㄻ";s:3:"ᆱ";s:3:"ㄼ";s:3:"ᆲ";s:3:"ㄽ";s:3:"ᆳ";s:3:"ㄾ";s:3:"ᆴ";s:3:"ㄿ";s:3:"ᆵ";s:3:"ㅀ";s:3:"ᄚ";s:3:"ㅁ";s:3:"ᄆ";s:3:"ㅂ";s:3:"ᄇ";s:3:"ㅃ";s:3:"ᄈ";s:3:"ㅄ";s:3:"ᄡ";s:3:"ㅅ";s:3:"ᄉ";s:3:"ㅆ";s:3:"ᄊ";s:3:"ㅇ";s:3:"ᄋ";s:3:"ㅈ";s:3:"ᄌ";s:3:"ㅉ";s:3:"ᄍ";s:3:"ㅊ";s:3:"ᄎ";s:3:"ㅋ";s:3:"ᄏ";s:3:"ㅌ";s:3:"ᄐ";s:3:"ㅍ";s:3:"ᄑ";s:3:"ㅎ";s:3:"ᄒ";s:3:"ㅏ";s:3:"ᅡ";s:3:"ㅐ";s:3:"ᅢ";s:3:"ㅑ";s:3:"ᅣ";s:3:"ㅒ";s:3:"ᅤ";s:3:"ㅓ";s:3:"ᅥ";s:3:"ㅔ";s:3:"ᅦ";s:3:"ㅕ";s:3:"ᅧ";s:3:"ㅖ";s:3:"ᅨ";s:3:"ㅗ";s:3:"ᅩ";s:3:"ㅘ";s:3:"ᅪ";s:3:"ㅙ";s:3:"ᅫ";s:3:"ㅚ";s:3:"ᅬ";s:3:"ㅛ";s:3:"ᅭ";s:3:"ㅜ";s:3:"ᅮ";s:3:"ㅝ";s:3:"ᅯ";s:3:"ㅞ";s:3:"ᅰ";s:3:"ㅟ";s:3:"ᅱ";s:3:"ㅠ";s:3:"ᅲ";s:3:"ㅡ";s:3:"ᅳ";s:3:"ㅢ";s:3:"ᅴ";s:3:"ㅣ";s:3:"ᅵ";s:3:"ㅤ";s:3:"ᅠ";s:3:"ㅥ";s:3:"ᄔ";s:3:"ㅦ";s:3:"ᄕ";s:3:"ㅧ";s:3:"ᇇ";s:3:"ㅨ";s:3:"ᇈ";s:3:"ㅩ";s:3:"ᇌ";s:3:"ㅪ";s:3:"ᇎ";s:3:"ㅫ";s:3:"ᇓ";s:3:"ㅬ";s:3:"ᇗ";s:3:"ㅭ";s:3:"ᇙ";s:3:"ㅮ";s:3:"ᄜ";s:3:"ㅯ";s:3:"ᇝ";s:3:"ㅰ";s:3:"ᇟ";s:3:"ㅱ";s:3:"ᄝ";s:3:"ㅲ";s:3:"ᄞ";s:3:"ㅳ";s:3:"ᄠ";s:3:"ㅴ";s:3:"ᄢ";s:3:"ㅵ";s:3:"ᄣ";s:3:"ㅶ";s:3:"ᄧ";s:3:"ㅷ";s:3:"ᄩ";s:3:"ㅸ";s:3:"ᄫ";s:3:"ㅹ";s:3:"ᄬ";s:3:"ㅺ";s:3:"ᄭ";s:3:"ㅻ";s:3:"ᄮ";s:3:"ㅼ";s:3:"ᄯ";s:3:"ㅽ";s:3:"ᄲ";s:3:"ㅾ";s:3:"ᄶ";s:3:"ㅿ";s:3:"ᅀ";s:3:"ㆀ";s:3:"ᅇ";s:3:"ㆁ";s:3:"ᅌ";s:3:"ㆂ";s:3:"ᇱ";s:3:"ㆃ";s:3:"ᇲ";s:3:"ㆄ";s:3:"ᅗ";s:3:"ㆅ";s:3:"ᅘ";s:3:"ㆆ";s:3:"ᅙ";s:3:"ㆇ";s:3:"ᆄ";s:3:"ㆈ";s:3:"ᆅ";s:3:"ㆉ";s:3:"ᆈ";s:3:"ㆊ";s:3:"ᆑ";s:3:"ㆋ";s:3:"ᆒ";s:3:"ㆌ";s:3:"ᆔ";s:3:"ㆍ";s:3:"ᆞ";s:3:"ㆎ";s:3:"ᆡ";s:3:"㆒";s:3:"一";s:3:"㆓";s:3:"二";s:3:"㆔";s:3:"三";s:3:"㆕";s:3:"四";s:3:"㆖";s:3:"上";s:3:"㆗";s:3:"中";s:3:"㆘";s:3:"下";s:3:"㆙";s:3:"甲";s:3:"㆚";s:3:"乙";s:3:"㆛";s:3:"丙";s:3:"㆜";s:3:"丁";s:3:"㆝";s:3:"天";s:3:"㆞";s:3:"地";s:3:"㆟";s:3:"人";s:3:"㈀";s:5:"(ᄀ)";s:3:"㈁";s:5:"(ᄂ)";s:3:"㈂";s:5:"(ᄃ)";s:3:"㈃";s:5:"(ᄅ)";s:3:"㈄";s:5:"(ᄆ)";s:3:"㈅";s:5:"(ᄇ)";s:3:"㈆";s:5:"(ᄉ)";s:3:"㈇";s:5:"(ᄋ)";s:3:"㈈";s:5:"(ᄌ)";s:3:"㈉";s:5:"(ᄎ)";s:3:"㈊";s:5:"(ᄏ)";s:3:"㈋";s:5:"(ᄐ)";s:3:"㈌";s:5:"(ᄑ)";s:3:"㈍";s:5:"(ᄒ)";s:3:"㈎";s:8:"(가)";s:3:"㈏";s:8:"(나)";s:3:"㈐";s:8:"(다)";s:3:"㈑";s:8:"(라)";s:3:"㈒";s:8:"(마)";s:3:"㈓";s:8:"(바)";s:3:"㈔";s:8:"(사)";s:3:"㈕";s:8:"(아)";s:3:"㈖";s:8:"(자)";s:3:"㈗";s:8:"(차)";s:3:"㈘";s:8:"(카)";s:3:"㈙";s:8:"(타)";s:3:"㈚";s:8:"(파)";s:3:"㈛";s:8:"(하)";s:3:"㈜";s:8:"(주)";s:3:"㈝";s:17:"(오전)";s:3:"㈞";s:14:"(오후)";s:3:"㈠";s:5:"(一)";s:3:"㈡";s:5:"(二)";s:3:"㈢";s:5:"(三)";s:3:"㈣";s:5:"(四)";s:3:"㈤";s:5:"(五)";s:3:"㈥";s:5:"(六)";s:3:"㈦";s:5:"(七)";s:3:"㈧";s:5:"(八)";s:3:"㈨";s:5:"(九)";s:3:"㈩";s:5:"(十)";s:3:"㈪";s:5:"(月)";s:3:"㈫";s:5:"(火)";s:3:"㈬";s:5:"(水)";s:3:"㈭";s:5:"(木)";s:3:"㈮";s:5:"(金)";s:3:"㈯";s:5:"(土)";s:3:"㈰";s:5:"(日)";s:3:"㈱";s:5:"(株)";s:3:"㈲";s:5:"(有)";s:3:"㈳";s:5:"(社)";s:3:"㈴";s:5:"(名)";s:3:"㈵";s:5:"(特)";s:3:"㈶";s:5:"(財)";s:3:"㈷";s:5:"(祝)";s:3:"㈸";s:5:"(労)";s:3:"㈹";s:5:"(代)";s:3:"㈺";s:5:"(呼)";s:3:"㈻";s:5:"(学)";s:3:"㈼";s:5:"(監)";s:3:"㈽";s:5:"(企)";s:3:"㈾";s:5:"(資)";s:3:"㈿";s:5:"(協)";s:3:"㉀";s:5:"(祭)";s:3:"㉁";s:5:"(休)";s:3:"㉂";s:5:"(自)";s:3:"㉃";s:5:"(至)";s:3:"㉄";s:3:"問";s:3:"㉅";s:3:"幼";s:3:"㉆";s:3:"文";s:3:"㉇";s:3:"箏";s:3:"㉐";s:3:"PTE";s:3:"㉑";s:2:"21";s:3:"㉒";s:2:"22";s:3:"㉓";s:2:"23";s:3:"㉔";s:2:"24";s:3:"㉕";s:2:"25";s:3:"㉖";s:2:"26";s:3:"㉗";s:2:"27";s:3:"㉘";s:2:"28";s:3:"㉙";s:2:"29";s:3:"㉚";s:2:"30";s:3:"㉛";s:2:"31";s:3:"㉜";s:2:"32";s:3:"㉝";s:2:"33";s:3:"㉞";s:2:"34";s:3:"㉟";s:2:"35";s:3:"㉠";s:3:"ᄀ";s:3:"㉡";s:3:"ᄂ";s:3:"㉢";s:3:"ᄃ";s:3:"㉣";s:3:"ᄅ";s:3:"㉤";s:3:"ᄆ";s:3:"㉥";s:3:"ᄇ";s:3:"㉦";s:3:"ᄉ";s:3:"㉧";s:3:"ᄋ";s:3:"㉨";s:3:"ᄌ";s:3:"㉩";s:3:"ᄎ";s:3:"㉪";s:3:"ᄏ";s:3:"㉫";s:3:"ᄐ";s:3:"㉬";s:3:"ᄑ";s:3:"㉭";s:3:"ᄒ";s:3:"㉮";s:6:"가";s:3:"㉯";s:6:"나";s:3:"㉰";s:6:"다";s:3:"㉱";s:6:"라";s:3:"㉲";s:6:"마";s:3:"㉳";s:6:"바";s:3:"㉴";s:6:"사";s:3:"㉵";s:6:"아";s:3:"㉶";s:6:"자";s:3:"㉷";s:6:"차";s:3:"㉸";s:6:"카";s:3:"㉹";s:6:"타";s:3:"㉺";s:6:"파";s:3:"㉻";s:6:"하";s:3:"㉼";s:15:"참고";s:3:"㉽";s:12:"주의";s:3:"㉾";s:6:"우";s:3:"㊀";s:3:"一";s:3:"㊁";s:3:"二";s:3:"㊂";s:3:"三";s:3:"㊃";s:3:"四";s:3:"㊄";s:3:"五";s:3:"㊅";s:3:"六";s:3:"㊆";s:3:"七";s:3:"㊇";s:3:"八";s:3:"㊈";s:3:"九";s:3:"㊉";s:3:"十";s:3:"㊊";s:3:"月";s:3:"㊋";s:3:"火";s:3:"㊌";s:3:"水";s:3:"㊍";s:3:"木";s:3:"㊎";s:3:"金";s:3:"㊏";s:3:"土";s:3:"㊐";s:3:"日";s:3:"㊑";s:3:"株";s:3:"㊒";s:3:"有";s:3:"㊓";s:3:"社";s:3:"㊔";s:3:"名";s:3:"㊕";s:3:"特";s:3:"㊖";s:3:"財";s:3:"㊗";s:3:"祝";s:3:"㊘";s:3:"労";s:3:"㊙";s:3:"秘";s:3:"㊚";s:3:"男";s:3:"㊛";s:3:"女";s:3:"㊜";s:3:"適";s:3:"㊝";s:3:"優";s:3:"㊞";s:3:"印";s:3:"㊟";s:3:"注";s:3:"㊠";s:3:"項";s:3:"㊡";s:3:"休";s:3:"㊢";s:3:"写";s:3:"㊣";s:3:"正";s:3:"㊤";s:3:"上";s:3:"㊥";s:3:"中";s:3:"㊦";s:3:"下";s:3:"㊧";s:3:"左";s:3:"㊨";s:3:"右";s:3:"㊩";s:3:"医";s:3:"㊪";s:3:"宗";s:3:"㊫";s:3:"学";s:3:"㊬";s:3:"監";s:3:"㊭";s:3:"企";s:3:"㊮";s:3:"資";s:3:"㊯";s:3:"協";s:3:"㊰";s:3:"夜";s:3:"㊱";s:2:"36";s:3:"㊲";s:2:"37";s:3:"㊳";s:2:"38";s:3:"㊴";s:2:"39";s:3:"㊵";s:2:"40";s:3:"㊶";s:2:"41";s:3:"㊷";s:2:"42";s:3:"㊸";s:2:"43";s:3:"㊹";s:2:"44";s:3:"㊺";s:2:"45";s:3:"㊻";s:2:"46";s:3:"㊼";s:2:"47";s:3:"㊽";s:2:"48";s:3:"㊾";s:2:"49";s:3:"㊿";s:2:"50";s:3:"㋀";s:4:"1月";s:3:"㋁";s:4:"2月";s:3:"㋂";s:4:"3月";s:3:"㋃";s:4:"4月";s:3:"㋄";s:4:"5月";s:3:"㋅";s:4:"6月";s:3:"㋆";s:4:"7月";s:3:"㋇";s:4:"8月";s:3:"㋈";s:4:"9月";s:3:"㋉";s:5:"10月";s:3:"㋊";s:5:"11月";s:3:"㋋";s:5:"12月";s:3:"㋌";s:2:"Hg";s:3:"㋍";s:3:"erg";s:3:"㋎";s:2:"eV";s:3:"㋏";s:3:"LTD";s:3:"㋐";s:3:"ア";s:3:"㋑";s:3:"イ";s:3:"㋒";s:3:"ウ";s:3:"㋓";s:3:"エ";s:3:"㋔";s:3:"オ";s:3:"㋕";s:3:"カ";s:3:"㋖";s:3:"キ";s:3:"㋗";s:3:"ク";s:3:"㋘";s:3:"ケ";s:3:"㋙";s:3:"コ";s:3:"㋚";s:3:"サ";s:3:"㋛";s:3:"シ";s:3:"㋜";s:3:"ス";s:3:"㋝";s:3:"セ";s:3:"㋞";s:3:"ソ";s:3:"㋟";s:3:"タ";s:3:"㋠";s:3:"チ";s:3:"㋡";s:3:"ツ";s:3:"㋢";s:3:"テ";s:3:"㋣";s:3:"ト";s:3:"㋤";s:3:"ナ";s:3:"㋥";s:3:"ニ";s:3:"㋦";s:3:"ヌ";s:3:"㋧";s:3:"ネ";s:3:"㋨";s:3:"ノ";s:3:"㋩";s:3:"ハ";s:3:"㋪";s:3:"ヒ";s:3:"㋫";s:3:"フ";s:3:"㋬";s:3:"ヘ";s:3:"㋭";s:3:"ホ";s:3:"㋮";s:3:"マ";s:3:"㋯";s:3:"ミ";s:3:"㋰";s:3:"ム";s:3:"㋱";s:3:"メ";s:3:"㋲";s:3:"モ";s:3:"㋳";s:3:"ヤ";s:3:"㋴";s:3:"ユ";s:3:"㋵";s:3:"ヨ";s:3:"㋶";s:3:"ラ";s:3:"㋷";s:3:"リ";s:3:"㋸";s:3:"ル";s:3:"㋹";s:3:"レ";s:3:"㋺";s:3:"ロ";s:3:"㋻";s:3:"ワ";s:3:"㋼";s:3:"ヰ";s:3:"㋽";s:3:"ヱ";s:3:"㋾";s:3:"ヲ";s:3:"㌀";s:15:"アパート";s:3:"㌁";s:12:"アルファ";s:3:"㌂";s:15:"アンペア";s:3:"㌃";s:9:"アール";s:3:"㌄";s:15:"イニング";s:3:"㌅";s:9:"インチ";s:3:"㌆";s:9:"ウォン";s:3:"㌇";s:18:"エスクード";s:3:"㌈";s:12:"エーカー";s:3:"㌉";s:9:"オンス";s:3:"㌊";s:9:"オーム";s:3:"㌋";s:9:"カイリ";s:3:"㌌";s:12:"カラット";s:3:"㌍";s:12:"カロリー";s:3:"㌎";s:12:"ガロン";s:3:"㌏";s:12:"ガンマ";s:3:"㌐";s:12:"ギガ";s:3:"㌑";s:12:"ギニー";s:3:"㌒";s:12:"キュリー";s:3:"㌓";s:18:"ギルダー";s:3:"㌔";s:6:"キロ";s:3:"㌕";s:18:"キログラム";s:3:"㌖";s:18:"キロメートル";s:3:"㌗";s:15:"キロワット";s:3:"㌘";s:12:"グラム";s:3:"㌙";s:18:"グラムトン";s:3:"㌚";s:18:"クルゼイロ";s:3:"㌛";s:12:"クローネ";s:3:"㌜";s:9:"ケース";s:3:"㌝";s:9:"コルナ";s:3:"㌞";s:12:"コーポ";s:3:"㌟";s:12:"サイクル";s:3:"㌠";s:15:"サンチーム";s:3:"㌡";s:15:"シリング";s:3:"㌢";s:9:"センチ";s:3:"㌣";s:9:"セント";s:3:"㌤";s:12:"ダース";s:3:"㌥";s:9:"デシ";s:3:"㌦";s:9:"ドル";s:3:"㌧";s:6:"トン";s:3:"㌨";s:6:"ナノ";s:3:"㌩";s:9:"ノット";s:3:"㌪";s:9:"ハイツ";s:3:"㌫";s:18:"パーセント";s:3:"㌬";s:12:"パーツ";s:3:"㌭";s:15:"バーレル";s:3:"㌮";s:18:"ピアストル";s:3:"㌯";s:12:"ピクル";s:3:"㌰";s:9:"ピコ";s:3:"㌱";s:9:"ビル";s:3:"㌲";s:18:"ファラッド";s:3:"㌳";s:12:"フィート";s:3:"㌴";s:18:"ブッシェル";s:3:"㌵";s:9:"フラン";s:3:"㌶";s:15:"ヘクタール";s:3:"㌷";s:9:"ペソ";s:3:"㌸";s:12:"ペニヒ";s:3:"㌹";s:9:"ヘルツ";s:3:"㌺";s:12:"ペンス";s:3:"㌻";s:15:"ページ";s:3:"㌼";s:12:"ベータ";s:3:"㌽";s:15:"ポイント";s:3:"㌾";s:12:"ボルト";s:3:"㌿";s:6:"ホン";s:3:"㍀";s:15:"ポンド";s:3:"㍁";s:9:"ホール";s:3:"㍂";s:9:"ホーン";s:3:"㍃";s:12:"マイクロ";s:3:"㍄";s:9:"マイル";s:3:"㍅";s:9:"マッハ";s:3:"㍆";s:9:"マルク";s:3:"㍇";s:15:"マンション";s:3:"㍈";s:12:"ミクロン";s:3:"㍉";s:6:"ミリ";s:3:"㍊";s:18:"ミリバール";s:3:"㍋";s:9:"メガ";s:3:"㍌";s:15:"メガトン";s:3:"㍍";s:12:"メートル";s:3:"㍎";s:12:"ヤード";s:3:"㍏";s:9:"ヤール";s:3:"㍐";s:9:"ユアン";s:3:"㍑";s:12:"リットル";s:3:"㍒";s:6:"リラ";s:3:"㍓";s:12:"ルピー";s:3:"㍔";s:15:"ルーブル";s:3:"㍕";s:6:"レム";s:3:"㍖";s:18:"レントゲン";s:3:"㍗";s:9:"ワット";s:3:"㍘";s:4:"0点";s:3:"㍙";s:4:"1点";s:3:"㍚";s:4:"2点";s:3:"㍛";s:4:"3点";s:3:"㍜";s:4:"4点";s:3:"㍝";s:4:"5点";s:3:"㍞";s:4:"6点";s:3:"㍟";s:4:"7点";s:3:"㍠";s:4:"8点";s:3:"㍡";s:4:"9点";s:3:"㍢";s:5:"10点";s:3:"㍣";s:5:"11点";s:3:"㍤";s:5:"12点";s:3:"㍥";s:5:"13点";s:3:"㍦";s:5:"14点";s:3:"㍧";s:5:"15点";s:3:"㍨";s:5:"16点";s:3:"㍩";s:5:"17点";s:3:"㍪";s:5:"18点";s:3:"㍫";s:5:"19点";s:3:"㍬";s:5:"20点";s:3:"㍭";s:5:"21点";s:3:"㍮";s:5:"22点";s:3:"㍯";s:5:"23点";s:3:"㍰";s:5:"24点";s:3:"㍱";s:3:"hPa";s:3:"㍲";s:2:"da";s:3:"㍳";s:2:"AU";s:3:"㍴";s:3:"bar";s:3:"㍵";s:2:"oV";s:3:"㍶";s:2:"pc";s:3:"㍷";s:2:"dm";s:3:"㍸";s:3:"dm2";s:3:"㍹";s:3:"dm3";s:3:"㍺";s:2:"IU";s:3:"㍻";s:6:"平成";s:3:"㍼";s:6:"昭和";s:3:"㍽";s:6:"大正";s:3:"㍾";s:6:"明治";s:3:"㍿";s:12:"株式会社";s:3:"㎀";s:2:"pA";s:3:"㎁";s:2:"nA";s:3:"㎂";s:3:"μA";s:3:"㎃";s:2:"mA";s:3:"㎄";s:2:"kA";s:3:"㎅";s:2:"KB";s:3:"㎆";s:2:"MB";s:3:"㎇";s:2:"GB";s:3:"㎈";s:3:"cal";s:3:"㎉";s:4:"kcal";s:3:"㎊";s:2:"pF";s:3:"㎋";s:2:"nF";s:3:"㎌";s:3:"μF";s:3:"㎍";s:3:"μg";s:3:"㎎";s:2:"mg";s:3:"㎏";s:2:"kg";s:3:"㎐";s:2:"Hz";s:3:"㎑";s:3:"kHz";s:3:"㎒";s:3:"MHz";s:3:"㎓";s:3:"GHz";s:3:"㎔";s:3:"THz";s:3:"㎕";s:3:"μl";s:3:"㎖";s:2:"ml";s:3:"㎗";s:2:"dl";s:3:"㎘";s:2:"kl";s:3:"㎙";s:2:"fm";s:3:"㎚";s:2:"nm";s:3:"㎛";s:3:"μm";s:3:"㎜";s:2:"mm";s:3:"㎝";s:2:"cm";s:3:"㎞";s:2:"km";s:3:"㎟";s:3:"mm2";s:3:"㎠";s:3:"cm2";s:3:"㎡";s:2:"m2";s:3:"㎢";s:3:"km2";s:3:"㎣";s:3:"mm3";s:3:"㎤";s:3:"cm3";s:3:"㎥";s:2:"m3";s:3:"㎦";s:3:"km3";s:3:"㎧";s:5:"m∕s";s:3:"㎨";s:6:"m∕s2";s:3:"㎩";s:2:"Pa";s:3:"㎪";s:3:"kPa";s:3:"㎫";s:3:"MPa";s:3:"㎬";s:3:"GPa";s:3:"㎭";s:3:"rad";s:3:"㎮";s:7:"rad∕s";s:3:"㎯";s:8:"rad∕s2";s:3:"㎰";s:2:"ps";s:3:"㎱";s:2:"ns";s:3:"㎲";s:3:"μs";s:3:"㎳";s:2:"ms";s:3:"㎴";s:2:"pV";s:3:"㎵";s:2:"nV";s:3:"㎶";s:3:"μV";s:3:"㎷";s:2:"mV";s:3:"㎸";s:2:"kV";s:3:"㎹";s:2:"MV";s:3:"㎺";s:2:"pW";s:3:"㎻";s:2:"nW";s:3:"㎼";s:3:"μW";s:3:"㎽";s:2:"mW";s:3:"㎾";s:2:"kW";s:3:"㎿";s:2:"MW";s:3:"㏀";s:3:"kΩ";s:3:"㏁";s:3:"MΩ";s:3:"㏂";s:4:"a.m.";s:3:"㏃";s:2:"Bq";s:3:"㏄";s:2:"cc";s:3:"㏅";s:2:"cd";s:3:"㏆";s:6:"C∕kg";s:3:"㏇";s:3:"Co.";s:3:"㏈";s:2:"dB";s:3:"㏉";s:2:"Gy";s:3:"㏊";s:2:"ha";s:3:"㏋";s:2:"HP";s:3:"㏌";s:2:"in";s:3:"㏍";s:2:"KK";s:3:"㏎";s:2:"KM";s:3:"㏏";s:2:"kt";s:3:"㏐";s:2:"lm";s:3:"㏑";s:2:"ln";s:3:"㏒";s:3:"log";s:3:"㏓";s:2:"lx";s:3:"㏔";s:2:"mb";s:3:"㏕";s:3:"mil";s:3:"㏖";s:3:"mol";s:3:"㏗";s:2:"PH";s:3:"㏘";s:4:"p.m.";s:3:"㏙";s:3:"PPM";s:3:"㏚";s:2:"PR";s:3:"㏛";s:2:"sr";s:3:"㏜";s:2:"Sv";s:3:"㏝";s:2:"Wb";s:3:"㏞";s:5:"V∕m";s:3:"㏟";s:5:"A∕m";s:3:"㏠";s:4:"1日";s:3:"㏡";s:4:"2日";s:3:"㏢";s:4:"3日";s:3:"㏣";s:4:"4日";s:3:"㏤";s:4:"5日";s:3:"㏥";s:4:"6日";s:3:"㏦";s:4:"7日";s:3:"㏧";s:4:"8日";s:3:"㏨";s:4:"9日";s:3:"㏩";s:5:"10日";s:3:"㏪";s:5:"11日";s:3:"㏫";s:5:"12日";s:3:"㏬";s:5:"13日";s:3:"㏭";s:5:"14日";s:3:"㏮";s:5:"15日";s:3:"㏯";s:5:"16日";s:3:"㏰";s:5:"17日";s:3:"㏱";s:5:"18日";s:3:"㏲";s:5:"19日";s:3:"㏳";s:5:"20日";s:3:"㏴";s:5:"21日";s:3:"㏵";s:5:"22日";s:3:"㏶";s:5:"23日";s:3:"㏷";s:5:"24日";s:3:"㏸";s:5:"25日";s:3:"㏹";s:5:"26日";s:3:"㏺";s:5:"27日";s:3:"㏻";s:5:"28日";s:3:"㏼";s:5:"29日";s:3:"㏽";s:5:"30日";s:3:"㏾";s:5:"31日";s:3:"㏿";s:3:"gal";s:3:"ꝰ";s:3:"ꝯ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:3:"𤋮";s:4:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"ff";s:2:"ff";s:3:"fi";s:2:"fi";s:3:"fl";s:2:"fl";s:3:"ffi";s:3:"ffi";s:3:"ffl";s:3:"ffl";s:3:"ſt";s:2:"st";s:3:"st";s:2:"st";s:3:"ﬓ";s:4:"մն";s:3:"ﬔ";s:4:"մե";s:3:"ﬕ";s:4:"մի";s:3:"ﬖ";s:4:"վն";s:3:"ﬗ";s:4:"մխ";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"ﬠ";s:2:"ע";s:3:"ﬡ";s:2:"א";s:3:"ﬢ";s:2:"ד";s:3:"ﬣ";s:2:"ה";s:3:"ﬤ";s:2:"כ";s:3:"ﬥ";s:2:"ל";s:3:"ﬦ";s:2:"ם";s:3:"ﬧ";s:2:"ר";s:3:"ﬨ";s:2:"ת";s:3:"﬩";s:1:"+";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:3:"ﭏ";s:4:"אל";s:3:"ﭐ";s:2:"ٱ";s:3:"ﭑ";s:2:"ٱ";s:3:"ﭒ";s:2:"ٻ";s:3:"ﭓ";s:2:"ٻ";s:3:"ﭔ";s:2:"ٻ";s:3:"ﭕ";s:2:"ٻ";s:3:"ﭖ";s:2:"پ";s:3:"ﭗ";s:2:"پ";s:3:"ﭘ";s:2:"پ";s:3:"ﭙ";s:2:"پ";s:3:"ﭚ";s:2:"ڀ";s:3:"ﭛ";s:2:"ڀ";s:3:"ﭜ";s:2:"ڀ";s:3:"ﭝ";s:2:"ڀ";s:3:"ﭞ";s:2:"ٺ";s:3:"ﭟ";s:2:"ٺ";s:3:"ﭠ";s:2:"ٺ";s:3:"ﭡ";s:2:"ٺ";s:3:"ﭢ";s:2:"ٿ";s:3:"ﭣ";s:2:"ٿ";s:3:"ﭤ";s:2:"ٿ";s:3:"ﭥ";s:2:"ٿ";s:3:"ﭦ";s:2:"ٹ";s:3:"ﭧ";s:2:"ٹ";s:3:"ﭨ";s:2:"ٹ";s:3:"ﭩ";s:2:"ٹ";s:3:"ﭪ";s:2:"ڤ";s:3:"ﭫ";s:2:"ڤ";s:3:"ﭬ";s:2:"ڤ";s:3:"ﭭ";s:2:"ڤ";s:3:"ﭮ";s:2:"ڦ";s:3:"ﭯ";s:2:"ڦ";s:3:"ﭰ";s:2:"ڦ";s:3:"ﭱ";s:2:"ڦ";s:3:"ﭲ";s:2:"ڄ";s:3:"ﭳ";s:2:"ڄ";s:3:"ﭴ";s:2:"ڄ";s:3:"ﭵ";s:2:"ڄ";s:3:"ﭶ";s:2:"ڃ";s:3:"ﭷ";s:2:"ڃ";s:3:"ﭸ";s:2:"ڃ";s:3:"ﭹ";s:2:"ڃ";s:3:"ﭺ";s:2:"چ";s:3:"ﭻ";s:2:"چ";s:3:"ﭼ";s:2:"چ";s:3:"ﭽ";s:2:"چ";s:3:"ﭾ";s:2:"ڇ";s:3:"ﭿ";s:2:"ڇ";s:3:"ﮀ";s:2:"ڇ";s:3:"ﮁ";s:2:"ڇ";s:3:"ﮂ";s:2:"ڍ";s:3:"ﮃ";s:2:"ڍ";s:3:"ﮄ";s:2:"ڌ";s:3:"ﮅ";s:2:"ڌ";s:3:"ﮆ";s:2:"ڎ";s:3:"ﮇ";s:2:"ڎ";s:3:"ﮈ";s:2:"ڈ";s:3:"ﮉ";s:2:"ڈ";s:3:"ﮊ";s:2:"ژ";s:3:"ﮋ";s:2:"ژ";s:3:"ﮌ";s:2:"ڑ";s:3:"ﮍ";s:2:"ڑ";s:3:"ﮎ";s:2:"ک";s:3:"ﮏ";s:2:"ک";s:3:"ﮐ";s:2:"ک";s:3:"ﮑ";s:2:"ک";s:3:"ﮒ";s:2:"گ";s:3:"ﮓ";s:2:"گ";s:3:"ﮔ";s:2:"گ";s:3:"ﮕ";s:2:"گ";s:3:"ﮖ";s:2:"ڳ";s:3:"ﮗ";s:2:"ڳ";s:3:"ﮘ";s:2:"ڳ";s:3:"ﮙ";s:2:"ڳ";s:3:"ﮚ";s:2:"ڱ";s:3:"ﮛ";s:2:"ڱ";s:3:"ﮜ";s:2:"ڱ";s:3:"ﮝ";s:2:"ڱ";s:3:"ﮞ";s:2:"ں";s:3:"ﮟ";s:2:"ں";s:3:"ﮠ";s:2:"ڻ";s:3:"ﮡ";s:2:"ڻ";s:3:"ﮢ";s:2:"ڻ";s:3:"ﮣ";s:2:"ڻ";s:3:"ﮤ";s:4:"ۀ";s:3:"ﮥ";s:4:"ۀ";s:3:"ﮦ";s:2:"ہ";s:3:"ﮧ";s:2:"ہ";s:3:"ﮨ";s:2:"ہ";s:3:"ﮩ";s:2:"ہ";s:3:"ﮪ";s:2:"ھ";s:3:"ﮫ";s:2:"ھ";s:3:"ﮬ";s:2:"ھ";s:3:"ﮭ";s:2:"ھ";s:3:"ﮮ";s:2:"ے";s:3:"ﮯ";s:2:"ے";s:3:"ﮰ";s:4:"ۓ";s:3:"ﮱ";s:4:"ۓ";s:3:"ﯓ";s:2:"ڭ";s:3:"ﯔ";s:2:"ڭ";s:3:"ﯕ";s:2:"ڭ";s:3:"ﯖ";s:2:"ڭ";s:3:"ﯗ";s:2:"ۇ";s:3:"ﯘ";s:2:"ۇ";s:3:"ﯙ";s:2:"ۆ";s:3:"ﯚ";s:2:"ۆ";s:3:"ﯛ";s:2:"ۈ";s:3:"ﯜ";s:2:"ۈ";s:3:"ﯝ";s:4:"ۇٴ";s:3:"ﯞ";s:2:"ۋ";s:3:"ﯟ";s:2:"ۋ";s:3:"ﯠ";s:2:"ۅ";s:3:"ﯡ";s:2:"ۅ";s:3:"ﯢ";s:2:"ۉ";s:3:"ﯣ";s:2:"ۉ";s:3:"ﯤ";s:2:"ې";s:3:"ﯥ";s:2:"ې";s:3:"ﯦ";s:2:"ې";s:3:"ﯧ";s:2:"ې";s:3:"ﯨ";s:2:"ى";s:3:"ﯩ";s:2:"ى";s:3:"ﯪ";s:6:"ئا";s:3:"ﯫ";s:6:"ئا";s:3:"ﯬ";s:6:"ئە";s:3:"ﯭ";s:6:"ئە";s:3:"ﯮ";s:6:"ئو";s:3:"ﯯ";s:6:"ئو";s:3:"ﯰ";s:6:"ئۇ";s:3:"ﯱ";s:6:"ئۇ";s:3:"ﯲ";s:6:"ئۆ";s:3:"ﯳ";s:6:"ئۆ";s:3:"ﯴ";s:6:"ئۈ";s:3:"ﯵ";s:6:"ئۈ";s:3:"ﯶ";s:6:"ئې";s:3:"ﯷ";s:6:"ئې";s:3:"ﯸ";s:6:"ئې";s:3:"ﯹ";s:6:"ئى";s:3:"ﯺ";s:6:"ئى";s:3:"ﯻ";s:6:"ئى";s:3:"ﯼ";s:2:"ی";s:3:"ﯽ";s:2:"ی";s:3:"ﯾ";s:2:"ی";s:3:"ﯿ";s:2:"ی";s:3:"ﰀ";s:6:"ئج";s:3:"ﰁ";s:6:"ئح";s:3:"ﰂ";s:6:"ئم";s:3:"ﰃ";s:6:"ئى";s:3:"ﰄ";s:6:"ئي";s:3:"ﰅ";s:4:"بج";s:3:"ﰆ";s:4:"بح";s:3:"ﰇ";s:4:"بخ";s:3:"ﰈ";s:4:"بم";s:3:"ﰉ";s:4:"بى";s:3:"ﰊ";s:4:"بي";s:3:"ﰋ";s:4:"تج";s:3:"ﰌ";s:4:"تح";s:3:"ﰍ";s:4:"تخ";s:3:"ﰎ";s:4:"تم";s:3:"ﰏ";s:4:"تى";s:3:"ﰐ";s:4:"تي";s:3:"ﰑ";s:4:"ثج";s:3:"ﰒ";s:4:"ثم";s:3:"ﰓ";s:4:"ثى";s:3:"ﰔ";s:4:"ثي";s:3:"ﰕ";s:4:"جح";s:3:"ﰖ";s:4:"جم";s:3:"ﰗ";s:4:"حج";s:3:"ﰘ";s:4:"حم";s:3:"ﰙ";s:4:"خج";s:3:"ﰚ";s:4:"خح";s:3:"ﰛ";s:4:"خم";s:3:"ﰜ";s:4:"سج";s:3:"ﰝ";s:4:"سح";s:3:"ﰞ";s:4:"سخ";s:3:"ﰟ";s:4:"سم";s:3:"ﰠ";s:4:"صح";s:3:"ﰡ";s:4:"صم";s:3:"ﰢ";s:4:"ضج";s:3:"ﰣ";s:4:"ضح";s:3:"ﰤ";s:4:"ضخ";s:3:"ﰥ";s:4:"ضم";s:3:"ﰦ";s:4:"طح";s:3:"ﰧ";s:4:"طم";s:3:"ﰨ";s:4:"ظم";s:3:"ﰩ";s:4:"عج";s:3:"ﰪ";s:4:"عم";s:3:"ﰫ";s:4:"غج";s:3:"ﰬ";s:4:"غم";s:3:"ﰭ";s:4:"فج";s:3:"ﰮ";s:4:"فح";s:3:"ﰯ";s:4:"فخ";s:3:"ﰰ";s:4:"فم";s:3:"ﰱ";s:4:"فى";s:3:"ﰲ";s:4:"في";s:3:"ﰳ";s:4:"قح";s:3:"ﰴ";s:4:"قم";s:3:"ﰵ";s:4:"قى";s:3:"ﰶ";s:4:"قي";s:3:"ﰷ";s:4:"كا";s:3:"ﰸ";s:4:"كج";s:3:"ﰹ";s:4:"كح";s:3:"ﰺ";s:4:"كخ";s:3:"ﰻ";s:4:"كل";s:3:"ﰼ";s:4:"كم";s:3:"ﰽ";s:4:"كى";s:3:"ﰾ";s:4:"كي";s:3:"ﰿ";s:4:"لج";s:3:"ﱀ";s:4:"لح";s:3:"ﱁ";s:4:"لخ";s:3:"ﱂ";s:4:"لم";s:3:"ﱃ";s:4:"لى";s:3:"ﱄ";s:4:"لي";s:3:"ﱅ";s:4:"مج";s:3:"ﱆ";s:4:"مح";s:3:"ﱇ";s:4:"مخ";s:3:"ﱈ";s:4:"مم";s:3:"ﱉ";s:4:"مى";s:3:"ﱊ";s:4:"مي";s:3:"ﱋ";s:4:"نج";s:3:"ﱌ";s:4:"نح";s:3:"ﱍ";s:4:"نخ";s:3:"ﱎ";s:4:"نم";s:3:"ﱏ";s:4:"نى";s:3:"ﱐ";s:4:"ني";s:3:"ﱑ";s:4:"هج";s:3:"ﱒ";s:4:"هم";s:3:"ﱓ";s:4:"هى";s:3:"ﱔ";s:4:"هي";s:3:"ﱕ";s:4:"يج";s:3:"ﱖ";s:4:"يح";s:3:"ﱗ";s:4:"يخ";s:3:"ﱘ";s:4:"يم";s:3:"ﱙ";s:4:"يى";s:3:"ﱚ";s:4:"يي";s:3:"ﱛ";s:4:"ذٰ";s:3:"ﱜ";s:4:"رٰ";s:3:"ﱝ";s:4:"ىٰ";s:3:"ﱞ";s:5:" ٌّ";s:3:"ﱟ";s:5:" ٍّ";s:3:"ﱠ";s:5:" َّ";s:3:"ﱡ";s:5:" ُّ";s:3:"ﱢ";s:5:" ِّ";s:3:"ﱣ";s:5:" ّٰ";s:3:"ﱤ";s:6:"ئر";s:3:"ﱥ";s:6:"ئز";s:3:"ﱦ";s:6:"ئم";s:3:"ﱧ";s:6:"ئن";s:3:"ﱨ";s:6:"ئى";s:3:"ﱩ";s:6:"ئي";s:3:"ﱪ";s:4:"بر";s:3:"ﱫ";s:4:"بز";s:3:"ﱬ";s:4:"بم";s:3:"ﱭ";s:4:"بن";s:3:"ﱮ";s:4:"بى";s:3:"ﱯ";s:4:"بي";s:3:"ﱰ";s:4:"تر";s:3:"ﱱ";s:4:"تز";s:3:"ﱲ";s:4:"تم";s:3:"ﱳ";s:4:"تن";s:3:"ﱴ";s:4:"تى";s:3:"ﱵ";s:4:"تي";s:3:"ﱶ";s:4:"ثر";s:3:"ﱷ";s:4:"ثز";s:3:"ﱸ";s:4:"ثم";s:3:"ﱹ";s:4:"ثن";s:3:"ﱺ";s:4:"ثى";s:3:"ﱻ";s:4:"ثي";s:3:"ﱼ";s:4:"فى";s:3:"ﱽ";s:4:"في";s:3:"ﱾ";s:4:"قى";s:3:"ﱿ";s:4:"قي";s:3:"ﲀ";s:4:"كا";s:3:"ﲁ";s:4:"كل";s:3:"ﲂ";s:4:"كم";s:3:"ﲃ";s:4:"كى";s:3:"ﲄ";s:4:"كي";s:3:"ﲅ";s:4:"لم";s:3:"ﲆ";s:4:"لى";s:3:"ﲇ";s:4:"لي";s:3:"ﲈ";s:4:"ما";s:3:"ﲉ";s:4:"مم";s:3:"ﲊ";s:4:"نر";s:3:"ﲋ";s:4:"نز";s:3:"ﲌ";s:4:"نم";s:3:"ﲍ";s:4:"نن";s:3:"ﲎ";s:4:"نى";s:3:"ﲏ";s:4:"ني";s:3:"ﲐ";s:4:"ىٰ";s:3:"ﲑ";s:4:"ير";s:3:"ﲒ";s:4:"يز";s:3:"ﲓ";s:4:"يم";s:3:"ﲔ";s:4:"ين";s:3:"ﲕ";s:4:"يى";s:3:"ﲖ";s:4:"يي";s:3:"ﲗ";s:6:"ئج";s:3:"ﲘ";s:6:"ئح";s:3:"ﲙ";s:6:"ئخ";s:3:"ﲚ";s:6:"ئم";s:3:"ﲛ";s:6:"ئه";s:3:"ﲜ";s:4:"بج";s:3:"ﲝ";s:4:"بح";s:3:"ﲞ";s:4:"بخ";s:3:"ﲟ";s:4:"بم";s:3:"ﲠ";s:4:"به";s:3:"ﲡ";s:4:"تج";s:3:"ﲢ";s:4:"تح";s:3:"ﲣ";s:4:"تخ";s:3:"ﲤ";s:4:"تم";s:3:"ﲥ";s:4:"ته";s:3:"ﲦ";s:4:"ثم";s:3:"ﲧ";s:4:"جح";s:3:"ﲨ";s:4:"جم";s:3:"ﲩ";s:4:"حج";s:3:"ﲪ";s:4:"حم";s:3:"ﲫ";s:4:"خج";s:3:"ﲬ";s:4:"خم";s:3:"ﲭ";s:4:"سج";s:3:"ﲮ";s:4:"سح";s:3:"ﲯ";s:4:"سخ";s:3:"ﲰ";s:4:"سم";s:3:"ﲱ";s:4:"صح";s:3:"ﲲ";s:4:"صخ";s:3:"ﲳ";s:4:"صم";s:3:"ﲴ";s:4:"ضج";s:3:"ﲵ";s:4:"ضح";s:3:"ﲶ";s:4:"ضخ";s:3:"ﲷ";s:4:"ضم";s:3:"ﲸ";s:4:"طح";s:3:"ﲹ";s:4:"ظم";s:3:"ﲺ";s:4:"عج";s:3:"ﲻ";s:4:"عم";s:3:"ﲼ";s:4:"غج";s:3:"ﲽ";s:4:"غم";s:3:"ﲾ";s:4:"فج";s:3:"ﲿ";s:4:"فح";s:3:"ﳀ";s:4:"فخ";s:3:"ﳁ";s:4:"فم";s:3:"ﳂ";s:4:"قح";s:3:"ﳃ";s:4:"قم";s:3:"ﳄ";s:4:"كج";s:3:"ﳅ";s:4:"كح";s:3:"ﳆ";s:4:"كخ";s:3:"ﳇ";s:4:"كل";s:3:"ﳈ";s:4:"كم";s:3:"ﳉ";s:4:"لج";s:3:"ﳊ";s:4:"لح";s:3:"ﳋ";s:4:"لخ";s:3:"ﳌ";s:4:"لم";s:3:"ﳍ";s:4:"له";s:3:"ﳎ";s:4:"مج";s:3:"ﳏ";s:4:"مح";s:3:"ﳐ";s:4:"مخ";s:3:"ﳑ";s:4:"مم";s:3:"ﳒ";s:4:"نج";s:3:"ﳓ";s:4:"نح";s:3:"ﳔ";s:4:"نخ";s:3:"ﳕ";s:4:"نم";s:3:"ﳖ";s:4:"نه";s:3:"ﳗ";s:4:"هج";s:3:"ﳘ";s:4:"هم";s:3:"ﳙ";s:4:"هٰ";s:3:"ﳚ";s:4:"يج";s:3:"ﳛ";s:4:"يح";s:3:"ﳜ";s:4:"يخ";s:3:"ﳝ";s:4:"يم";s:3:"ﳞ";s:4:"يه";s:3:"ﳟ";s:6:"ئم";s:3:"ﳠ";s:6:"ئه";s:3:"ﳡ";s:4:"بم";s:3:"ﳢ";s:4:"به";s:3:"ﳣ";s:4:"تم";s:3:"ﳤ";s:4:"ته";s:3:"ﳥ";s:4:"ثم";s:3:"ﳦ";s:4:"ثه";s:3:"ﳧ";s:4:"سم";s:3:"ﳨ";s:4:"سه";s:3:"ﳩ";s:4:"شم";s:3:"ﳪ";s:4:"شه";s:3:"ﳫ";s:4:"كل";s:3:"ﳬ";s:4:"كم";s:3:"ﳭ";s:4:"لم";s:3:"ﳮ";s:4:"نم";s:3:"ﳯ";s:4:"نه";s:3:"ﳰ";s:4:"يم";s:3:"ﳱ";s:4:"يه";s:3:"ﳲ";s:6:"ـَّ";s:3:"ﳳ";s:6:"ـُّ";s:3:"ﳴ";s:6:"ـِّ";s:3:"ﳵ";s:4:"طى";s:3:"ﳶ";s:4:"طي";s:3:"ﳷ";s:4:"عى";s:3:"ﳸ";s:4:"عي";s:3:"ﳹ";s:4:"غى";s:3:"ﳺ";s:4:"غي";s:3:"ﳻ";s:4:"سى";s:3:"ﳼ";s:4:"سي";s:3:"ﳽ";s:4:"شى";s:3:"ﳾ";s:4:"شي";s:3:"ﳿ";s:4:"حى";s:3:"ﴀ";s:4:"حي";s:3:"ﴁ";s:4:"جى";s:3:"ﴂ";s:4:"جي";s:3:"ﴃ";s:4:"خى";s:3:"ﴄ";s:4:"خي";s:3:"ﴅ";s:4:"صى";s:3:"ﴆ";s:4:"صي";s:3:"ﴇ";s:4:"ضى";s:3:"ﴈ";s:4:"ضي";s:3:"ﴉ";s:4:"شج";s:3:"ﴊ";s:4:"شح";s:3:"ﴋ";s:4:"شخ";s:3:"ﴌ";s:4:"شم";s:3:"ﴍ";s:4:"شر";s:3:"ﴎ";s:4:"سر";s:3:"ﴏ";s:4:"صر";s:3:"ﴐ";s:4:"ضر";s:3:"ﴑ";s:4:"طى";s:3:"ﴒ";s:4:"طي";s:3:"ﴓ";s:4:"عى";s:3:"ﴔ";s:4:"عي";s:3:"ﴕ";s:4:"غى";s:3:"ﴖ";s:4:"غي";s:3:"ﴗ";s:4:"سى";s:3:"ﴘ";s:4:"سي";s:3:"ﴙ";s:4:"شى";s:3:"ﴚ";s:4:"شي";s:3:"ﴛ";s:4:"حى";s:3:"ﴜ";s:4:"حي";s:3:"ﴝ";s:4:"جى";s:3:"ﴞ";s:4:"جي";s:3:"ﴟ";s:4:"خى";s:3:"ﴠ";s:4:"خي";s:3:"ﴡ";s:4:"صى";s:3:"ﴢ";s:4:"صي";s:3:"ﴣ";s:4:"ضى";s:3:"ﴤ";s:4:"ضي";s:3:"ﴥ";s:4:"شج";s:3:"ﴦ";s:4:"شح";s:3:"ﴧ";s:4:"شخ";s:3:"ﴨ";s:4:"شم";s:3:"ﴩ";s:4:"شر";s:3:"ﴪ";s:4:"سر";s:3:"ﴫ";s:4:"صر";s:3:"ﴬ";s:4:"ضر";s:3:"ﴭ";s:4:"شج";s:3:"ﴮ";s:4:"شح";s:3:"ﴯ";s:4:"شخ";s:3:"ﴰ";s:4:"شم";s:3:"ﴱ";s:4:"سه";s:3:"ﴲ";s:4:"شه";s:3:"ﴳ";s:4:"طم";s:3:"ﴴ";s:4:"سج";s:3:"ﴵ";s:4:"سح";s:3:"ﴶ";s:4:"سخ";s:3:"ﴷ";s:4:"شج";s:3:"ﴸ";s:4:"شح";s:3:"ﴹ";s:4:"شخ";s:3:"ﴺ";s:4:"طم";s:3:"ﴻ";s:4:"ظم";s:3:"ﴼ";s:4:"اً";s:3:"ﴽ";s:4:"اً";s:3:"ﵐ";s:6:"تجم";s:3:"ﵑ";s:6:"تحج";s:3:"ﵒ";s:6:"تحج";s:3:"ﵓ";s:6:"تحم";s:3:"ﵔ";s:6:"تخم";s:3:"ﵕ";s:6:"تمج";s:3:"ﵖ";s:6:"تمح";s:3:"ﵗ";s:6:"تمخ";s:3:"ﵘ";s:6:"جمح";s:3:"ﵙ";s:6:"جمح";s:3:"ﵚ";s:6:"حمي";s:3:"ﵛ";s:6:"حمى";s:3:"ﵜ";s:6:"سحج";s:3:"ﵝ";s:6:"سجح";s:3:"ﵞ";s:6:"سجى";s:3:"ﵟ";s:6:"سمح";s:3:"ﵠ";s:6:"سمح";s:3:"ﵡ";s:6:"سمج";s:3:"ﵢ";s:6:"سمم";s:3:"ﵣ";s:6:"سمم";s:3:"ﵤ";s:6:"صحح";s:3:"ﵥ";s:6:"صحح";s:3:"ﵦ";s:6:"صمم";s:3:"ﵧ";s:6:"شحم";s:3:"ﵨ";s:6:"شحم";s:3:"ﵩ";s:6:"شجي";s:3:"ﵪ";s:6:"شمخ";s:3:"ﵫ";s:6:"شمخ";s:3:"ﵬ";s:6:"شمم";s:3:"ﵭ";s:6:"شمم";s:3:"ﵮ";s:6:"ضحى";s:3:"ﵯ";s:6:"ضخم";s:3:"ﵰ";s:6:"ضخم";s:3:"ﵱ";s:6:"طمح";s:3:"ﵲ";s:6:"طمح";s:3:"ﵳ";s:6:"طمم";s:3:"ﵴ";s:6:"طمي";s:3:"ﵵ";s:6:"عجم";s:3:"ﵶ";s:6:"عمم";s:3:"ﵷ";s:6:"عمم";s:3:"ﵸ";s:6:"عمى";s:3:"ﵹ";s:6:"غمم";s:3:"ﵺ";s:6:"غمي";s:3:"ﵻ";s:6:"غمى";s:3:"ﵼ";s:6:"فخم";s:3:"ﵽ";s:6:"فخم";s:3:"ﵾ";s:6:"قمح";s:3:"ﵿ";s:6:"قمم";s:3:"ﶀ";s:6:"لحم";s:3:"ﶁ";s:6:"لحي";s:3:"ﶂ";s:6:"لحى";s:3:"ﶃ";s:6:"لجج";s:3:"ﶄ";s:6:"لجج";s:3:"ﶅ";s:6:"لخم";s:3:"ﶆ";s:6:"لخم";s:3:"ﶇ";s:6:"لمح";s:3:"ﶈ";s:6:"لمح";s:3:"ﶉ";s:6:"محج";s:3:"ﶊ";s:6:"محم";s:3:"ﶋ";s:6:"محي";s:3:"ﶌ";s:6:"مجح";s:3:"ﶍ";s:6:"مجم";s:3:"ﶎ";s:6:"مخج";s:3:"ﶏ";s:6:"مخم";s:3:"ﶒ";s:6:"مجخ";s:3:"ﶓ";s:6:"همج";s:3:"ﶔ";s:6:"همم";s:3:"ﶕ";s:6:"نحم";s:3:"ﶖ";s:6:"نحى";s:3:"ﶗ";s:6:"نجم";s:3:"ﶘ";s:6:"نجم";s:3:"ﶙ";s:6:"نجى";s:3:"ﶚ";s:6:"نمي";s:3:"ﶛ";s:6:"نمى";s:3:"ﶜ";s:6:"يمم";s:3:"ﶝ";s:6:"يمم";s:3:"ﶞ";s:6:"بخي";s:3:"ﶟ";s:6:"تجي";s:3:"ﶠ";s:6:"تجى";s:3:"ﶡ";s:6:"تخي";s:3:"ﶢ";s:6:"تخى";s:3:"ﶣ";s:6:"تمي";s:3:"ﶤ";s:6:"تمى";s:3:"ﶥ";s:6:"جمي";s:3:"ﶦ";s:6:"جحى";s:3:"ﶧ";s:6:"جمى";s:3:"ﶨ";s:6:"سخى";s:3:"ﶩ";s:6:"صحي";s:3:"ﶪ";s:6:"شحي";s:3:"ﶫ";s:6:"ضحي";s:3:"ﶬ";s:6:"لجي";s:3:"ﶭ";s:6:"لمي";s:3:"ﶮ";s:6:"يحي";s:3:"ﶯ";s:6:"يجي";s:3:"ﶰ";s:6:"يمي";s:3:"ﶱ";s:6:"ممي";s:3:"ﶲ";s:6:"قمي";s:3:"ﶳ";s:6:"نحي";s:3:"ﶴ";s:6:"قمح";s:3:"ﶵ";s:6:"لحم";s:3:"ﶶ";s:6:"عمي";s:3:"ﶷ";s:6:"كمي";s:3:"ﶸ";s:6:"نجح";s:3:"ﶹ";s:6:"مخي";s:3:"ﶺ";s:6:"لجم";s:3:"ﶻ";s:6:"كمم";s:3:"ﶼ";s:6:"لجم";s:3:"ﶽ";s:6:"نجح";s:3:"ﶾ";s:6:"جحي";s:3:"ﶿ";s:6:"حجي";s:3:"ﷀ";s:6:"مجي";s:3:"ﷁ";s:6:"فمي";s:3:"ﷂ";s:6:"بحي";s:3:"ﷃ";s:6:"كمم";s:3:"ﷄ";s:6:"عجم";s:3:"ﷅ";s:6:"صمم";s:3:"ﷆ";s:6:"سخي";s:3:"ﷇ";s:6:"نجي";s:3:"ﷰ";s:6:"صلے";s:3:"ﷱ";s:6:"قلے";s:3:"ﷲ";s:8:"الله";s:3:"ﷳ";s:8:"اكبر";s:3:"ﷴ";s:8:"محمد";s:3:"ﷵ";s:8:"صلعم";s:3:"ﷶ";s:8:"رسول";s:3:"ﷷ";s:8:"عليه";s:3:"ﷸ";s:8:"وسلم";s:3:"ﷹ";s:6:"صلى";s:3:"ﷺ";s:33:"صلى الله عليه وسلم";s:3:"ﷻ";s:15:"جل جلاله";s:3:"﷼";s:8:"ریال";s:3:"︐";s:1:",";s:3:"︑";s:3:"、";s:3:"︒";s:3:"。";s:3:"︓";s:1:":";s:3:"︔";s:1:";";s:3:"︕";s:1:"!";s:3:"︖";s:1:"?";s:3:"︗";s:3:"〖";s:3:"︘";s:3:"〗";s:3:"︙";s:3:"...";s:3:"︰";s:2:"..";s:3:"︱";s:3:"—";s:3:"︲";s:3:"–";s:3:"︳";s:1:"_";s:3:"︴";s:1:"_";s:3:"︵";s:1:"(";s:3:"︶";s:1:")";s:3:"︷";s:1:"{";s:3:"︸";s:1:"}";s:3:"︹";s:3:"〔";s:3:"︺";s:3:"〕";s:3:"︻";s:3:"【";s:3:"︼";s:3:"】";s:3:"︽";s:3:"《";s:3:"︾";s:3:"》";s:3:"︿";s:3:"〈";s:3:"﹀";s:3:"〉";s:3:"﹁";s:3:"「";s:3:"﹂";s:3:"」";s:3:"﹃";s:3:"『";s:3:"﹄";s:3:"』";s:3:"﹇";s:1:"[";s:3:"﹈";s:1:"]";s:3:"﹉";s:3:" ̅";s:3:"﹊";s:3:" ̅";s:3:"﹋";s:3:" ̅";s:3:"﹌";s:3:" ̅";s:3:"﹍";s:1:"_";s:3:"﹎";s:1:"_";s:3:"﹏";s:1:"_";s:3:"﹐";s:1:",";s:3:"﹑";s:3:"、";s:3:"﹒";s:1:".";s:3:"﹔";s:1:";";s:3:"﹕";s:1:":";s:3:"﹖";s:1:"?";s:3:"﹗";s:1:"!";s:3:"﹘";s:3:"—";s:3:"﹙";s:1:"(";s:3:"﹚";s:1:")";s:3:"﹛";s:1:"{";s:3:"﹜";s:1:"}";s:3:"﹝";s:3:"〔";s:3:"﹞";s:3:"〕";s:3:"﹟";s:1:"#";s:3:"﹠";s:1:"&";s:3:"﹡";s:1:"*";s:3:"﹢";s:1:"+";s:3:"﹣";s:1:"-";s:3:"﹤";s:1:"<";s:3:"﹥";s:1:">";s:3:"﹦";s:1:"=";s:3:"﹨";s:1:"\\";s:3:"﹩";s:1:"$";s:3:"﹪";s:1:"%";s:3:"﹫";s:1:"@";s:3:"ﹰ";s:3:" ً";s:3:"ﹱ";s:4:"ـً";s:3:"ﹲ";s:3:" ٌ";s:3:"ﹴ";s:3:" ٍ";s:3:"ﹶ";s:3:" َ";s:3:"ﹷ";s:4:"ـَ";s:3:"ﹸ";s:3:" ُ";s:3:"ﹹ";s:4:"ـُ";s:3:"ﹺ";s:3:" ِ";s:3:"ﹻ";s:4:"ـِ";s:3:"ﹼ";s:3:" ّ";s:3:"ﹽ";s:4:"ـّ";s:3:"ﹾ";s:3:" ْ";s:3:"ﹿ";s:4:"ـْ";s:3:"ﺀ";s:2:"ء";s:3:"ﺁ";s:4:"آ";s:3:"ﺂ";s:4:"آ";s:3:"ﺃ";s:4:"أ";s:3:"ﺄ";s:4:"أ";s:3:"ﺅ";s:4:"ؤ";s:3:"ﺆ";s:4:"ؤ";s:3:"ﺇ";s:4:"إ";s:3:"ﺈ";s:4:"إ";s:3:"ﺉ";s:4:"ئ";s:3:"ﺊ";s:4:"ئ";s:3:"ﺋ";s:4:"ئ";s:3:"ﺌ";s:4:"ئ";s:3:"ﺍ";s:2:"ا";s:3:"ﺎ";s:2:"ا";s:3:"ﺏ";s:2:"ب";s:3:"ﺐ";s:2:"ب";s:3:"ﺑ";s:2:"ب";s:3:"ﺒ";s:2:"ب";s:3:"ﺓ";s:2:"ة";s:3:"ﺔ";s:2:"ة";s:3:"ﺕ";s:2:"ت";s:3:"ﺖ";s:2:"ت";s:3:"ﺗ";s:2:"ت";s:3:"ﺘ";s:2:"ت";s:3:"ﺙ";s:2:"ث";s:3:"ﺚ";s:2:"ث";s:3:"ﺛ";s:2:"ث";s:3:"ﺜ";s:2:"ث";s:3:"ﺝ";s:2:"ج";s:3:"ﺞ";s:2:"ج";s:3:"ﺟ";s:2:"ج";s:3:"ﺠ";s:2:"ج";s:3:"ﺡ";s:2:"ح";s:3:"ﺢ";s:2:"ح";s:3:"ﺣ";s:2:"ح";s:3:"ﺤ";s:2:"ح";s:3:"ﺥ";s:2:"خ";s:3:"ﺦ";s:2:"خ";s:3:"ﺧ";s:2:"خ";s:3:"ﺨ";s:2:"خ";s:3:"ﺩ";s:2:"د";s:3:"ﺪ";s:2:"د";s:3:"ﺫ";s:2:"ذ";s:3:"ﺬ";s:2:"ذ";s:3:"ﺭ";s:2:"ر";s:3:"ﺮ";s:2:"ر";s:3:"ﺯ";s:2:"ز";s:3:"ﺰ";s:2:"ز";s:3:"ﺱ";s:2:"س";s:3:"ﺲ";s:2:"س";s:3:"ﺳ";s:2:"س";s:3:"ﺴ";s:2:"س";s:3:"ﺵ";s:2:"ش";s:3:"ﺶ";s:2:"ش";s:3:"ﺷ";s:2:"ش";s:3:"ﺸ";s:2:"ش";s:3:"ﺹ";s:2:"ص";s:3:"ﺺ";s:2:"ص";s:3:"ﺻ";s:2:"ص";s:3:"ﺼ";s:2:"ص";s:3:"ﺽ";s:2:"ض";s:3:"ﺾ";s:2:"ض";s:3:"ﺿ";s:2:"ض";s:3:"ﻀ";s:2:"ض";s:3:"ﻁ";s:2:"ط";s:3:"ﻂ";s:2:"ط";s:3:"ﻃ";s:2:"ط";s:3:"ﻄ";s:2:"ط";s:3:"ﻅ";s:2:"ظ";s:3:"ﻆ";s:2:"ظ";s:3:"ﻇ";s:2:"ظ";s:3:"ﻈ";s:2:"ظ";s:3:"ﻉ";s:2:"ع";s:3:"ﻊ";s:2:"ع";s:3:"ﻋ";s:2:"ع";s:3:"ﻌ";s:2:"ع";s:3:"ﻍ";s:2:"غ";s:3:"ﻎ";s:2:"غ";s:3:"ﻏ";s:2:"غ";s:3:"ﻐ";s:2:"غ";s:3:"ﻑ";s:2:"ف";s:3:"ﻒ";s:2:"ف";s:3:"ﻓ";s:2:"ف";s:3:"ﻔ";s:2:"ف";s:3:"ﻕ";s:2:"ق";s:3:"ﻖ";s:2:"ق";s:3:"ﻗ";s:2:"ق";s:3:"ﻘ";s:2:"ق";s:3:"ﻙ";s:2:"ك";s:3:"ﻚ";s:2:"ك";s:3:"ﻛ";s:2:"ك";s:3:"ﻜ";s:2:"ك";s:3:"ﻝ";s:2:"ل";s:3:"ﻞ";s:2:"ل";s:3:"ﻟ";s:2:"ل";s:3:"ﻠ";s:2:"ل";s:3:"ﻡ";s:2:"م";s:3:"ﻢ";s:2:"م";s:3:"ﻣ";s:2:"م";s:3:"ﻤ";s:2:"م";s:3:"ﻥ";s:2:"ن";s:3:"ﻦ";s:2:"ن";s:3:"ﻧ";s:2:"ن";s:3:"ﻨ";s:2:"ن";s:3:"ﻩ";s:2:"ه";s:3:"ﻪ";s:2:"ه";s:3:"ﻫ";s:2:"ه";s:3:"ﻬ";s:2:"ه";s:3:"ﻭ";s:2:"و";s:3:"ﻮ";s:2:"و";s:3:"ﻯ";s:2:"ى";s:3:"ﻰ";s:2:"ى";s:3:"ﻱ";s:2:"ي";s:3:"ﻲ";s:2:"ي";s:3:"ﻳ";s:2:"ي";s:3:"ﻴ";s:2:"ي";s:3:"ﻵ";s:6:"لآ";s:3:"ﻶ";s:6:"لآ";s:3:"ﻷ";s:6:"لأ";s:3:"ﻸ";s:6:"لأ";s:3:"ﻹ";s:6:"لإ";s:3:"ﻺ";s:6:"لإ";s:3:"ﻻ";s:4:"لا";s:3:"ﻼ";s:4:"لا";s:3:"!";s:1:"!";s:3:""";s:1:""";s:3:"#";s:1:"#";s:3:"$";s:1:"$";s:3:"%";s:1:"%";s:3:"&";s:1:"&";s:3:"'";s:1:"\'";s:3:"(";s:1:"(";s:3:")";s:1:")";s:3:"*";s:1:"*";s:3:"+";s:1:"+";s:3:",";s:1:",";s:3:"-";s:1:"-";s:3:".";s:1:".";s:3:"/";s:1:"/";s:3:"0";s:1:"0";s:3:"1";s:1:"1";s:3:"2";s:1:"2";s:3:"3";s:1:"3";s:3:"4";s:1:"4";s:3:"5";s:1:"5";s:3:"6";s:1:"6";s:3:"7";s:1:"7";s:3:"8";s:1:"8";s:3:"9";s:1:"9";s:3:":";s:1:":";s:3:";";s:1:";";s:3:"<";s:1:"<";s:3:"=";s:1:"=";s:3:">";s:1:">";s:3:"?";s:1:"?";s:3:"@";s:1:"@";s:3:"A";s:1:"A";s:3:"B";s:1:"B";s:3:"C";s:1:"C";s:3:"D";s:1:"D";s:3:"E";s:1:"E";s:3:"F";s:1:"F";s:3:"G";s:1:"G";s:3:"H";s:1:"H";s:3:"I";s:1:"I";s:3:"J";s:1:"J";s:3:"K";s:1:"K";s:3:"L";s:1:"L";s:3:"M";s:1:"M";s:3:"N";s:1:"N";s:3:"O";s:1:"O";s:3:"P";s:1:"P";s:3:"Q";s:1:"Q";s:3:"R";s:1:"R";s:3:"S";s:1:"S";s:3:"T";s:1:"T";s:3:"U";s:1:"U";s:3:"V";s:1:"V";s:3:"W";s:1:"W";s:3:"X";s:1:"X";s:3:"Y";s:1:"Y";s:3:"Z";s:1:"Z";s:3:"[";s:1:"[";s:3:"\";s:1:"\\";s:3:"]";s:1:"]";s:3:"^";s:1:"^";s:3:"_";s:1:"_";s:3:"`";s:1:"`";s:3:"a";s:1:"a";s:3:"b";s:1:"b";s:3:"c";s:1:"c";s:3:"d";s:1:"d";s:3:"e";s:1:"e";s:3:"f";s:1:"f";s:3:"g";s:1:"g";s:3:"h";s:1:"h";s:3:"i";s:1:"i";s:3:"j";s:1:"j";s:3:"k";s:1:"k";s:3:"l";s:1:"l";s:3:"m";s:1:"m";s:3:"n";s:1:"n";s:3:"o";s:1:"o";s:3:"p";s:1:"p";s:3:"q";s:1:"q";s:3:"r";s:1:"r";s:3:"s";s:1:"s";s:3:"t";s:1:"t";s:3:"u";s:1:"u";s:3:"v";s:1:"v";s:3:"w";s:1:"w";s:3:"x";s:1:"x";s:3:"y";s:1:"y";s:3:"z";s:1:"z";s:3:"{";s:1:"{";s:3:"|";s:1:"|";s:3:"}";s:1:"}";s:3:"~";s:1:"~";s:3:"⦅";s:3:"⦅";s:3:"⦆";s:3:"⦆";s:3:"。";s:3:"。";s:3:"「";s:3:"「";s:3:"」";s:3:"」";s:3:"、";s:3:"、";s:3:"・";s:3:"・";s:3:"ヲ";s:3:"ヲ";s:3:"ァ";s:3:"ァ";s:3:"ィ";s:3:"ィ";s:3:"ゥ";s:3:"ゥ";s:3:"ェ";s:3:"ェ";s:3:"ォ";s:3:"ォ";s:3:"ャ";s:3:"ャ";s:3:"ュ";s:3:"ュ";s:3:"ョ";s:3:"ョ";s:3:"ッ";s:3:"ッ";s:3:"ー";s:3:"ー";s:3:"ア";s:3:"ア";s:3:"イ";s:3:"イ";s:3:"ウ";s:3:"ウ";s:3:"エ";s:3:"エ";s:3:"オ";s:3:"オ";s:3:"カ";s:3:"カ";s:3:"キ";s:3:"キ";s:3:"ク";s:3:"ク";s:3:"ケ";s:3:"ケ";s:3:"コ";s:3:"コ";s:3:"サ";s:3:"サ";s:3:"シ";s:3:"シ";s:3:"ス";s:3:"ス";s:3:"セ";s:3:"セ";s:3:"ソ";s:3:"ソ";s:3:"タ";s:3:"タ";s:3:"チ";s:3:"チ";s:3:"ツ";s:3:"ツ";s:3:"テ";s:3:"テ";s:3:"ト";s:3:"ト";s:3:"ナ";s:3:"ナ";s:3:"ニ";s:3:"ニ";s:3:"ヌ";s:3:"ヌ";s:3:"ネ";s:3:"ネ";s:3:"ノ";s:3:"ノ";s:3:"ハ";s:3:"ハ";s:3:"ヒ";s:3:"ヒ";s:3:"フ";s:3:"フ";s:3:"ヘ";s:3:"ヘ";s:3:"ホ";s:3:"ホ";s:3:"マ";s:3:"マ";s:3:"ミ";s:3:"ミ";s:3:"ム";s:3:"ム";s:3:"メ";s:3:"メ";s:3:"モ";s:3:"モ";s:3:"ヤ";s:3:"ヤ";s:3:"ユ";s:3:"ユ";s:3:"ヨ";s:3:"ヨ";s:3:"ラ";s:3:"ラ";s:3:"リ";s:3:"リ";s:3:"ル";s:3:"ル";s:3:"レ";s:3:"レ";s:3:"ロ";s:3:"ロ";s:3:"ワ";s:3:"ワ";s:3:"ン";s:3:"ン";s:3:"゙";s:3:"゙";s:3:"゚";s:3:"゚";s:3:"ᅠ";s:3:"ᅠ";s:3:"ᄀ";s:3:"ᄀ";s:3:"ᄁ";s:3:"ᄁ";s:3:"ᆪ";s:3:"ᆪ";s:3:"ᄂ";s:3:"ᄂ";s:3:"ᆬ";s:3:"ᆬ";s:3:"ᆭ";s:3:"ᆭ";s:3:"ᄃ";s:3:"ᄃ";s:3:"ᄄ";s:3:"ᄄ";s:3:"ᄅ";s:3:"ᄅ";s:3:"ᆰ";s:3:"ᆰ";s:3:"ᆱ";s:3:"ᆱ";s:3:"ᆲ";s:3:"ᆲ";s:3:"ᆳ";s:3:"ᆳ";s:3:"ᆴ";s:3:"ᆴ";s:3:"ᆵ";s:3:"ᆵ";s:3:"ᄚ";s:3:"ᄚ";s:3:"ᄆ";s:3:"ᄆ";s:3:"ᄇ";s:3:"ᄇ";s:3:"ᄈ";s:3:"ᄈ";s:3:"ᄡ";s:3:"ᄡ";s:3:"ᄉ";s:3:"ᄉ";s:3:"ᄊ";s:3:"ᄊ";s:3:"ᄋ";s:3:"ᄋ";s:3:"ᄌ";s:3:"ᄌ";s:3:"ᄍ";s:3:"ᄍ";s:3:"ᄎ";s:3:"ᄎ";s:3:"ᄏ";s:3:"ᄏ";s:3:"ᄐ";s:3:"ᄐ";s:3:"ᄑ";s:3:"ᄑ";s:3:"ᄒ";s:3:"ᄒ";s:3:"ᅡ";s:3:"ᅡ";s:3:"ᅢ";s:3:"ᅢ";s:3:"ᅣ";s:3:"ᅣ";s:3:"ᅤ";s:3:"ᅤ";s:3:"ᅥ";s:3:"ᅥ";s:3:"ᅦ";s:3:"ᅦ";s:3:"ᅧ";s:3:"ᅧ";s:3:"ᅨ";s:3:"ᅨ";s:3:"ᅩ";s:3:"ᅩ";s:3:"ᅪ";s:3:"ᅪ";s:3:"ᅫ";s:3:"ᅫ";s:3:"ᅬ";s:3:"ᅬ";s:3:"ᅭ";s:3:"ᅭ";s:3:"ᅮ";s:3:"ᅮ";s:3:"ᅯ";s:3:"ᅯ";s:3:"ᅰ";s:3:"ᅰ";s:3:"ᅱ";s:3:"ᅱ";s:3:"ᅲ";s:3:"ᅲ";s:3:"ᅳ";s:3:"ᅳ";s:3:"ᅴ";s:3:"ᅴ";s:3:"ᅵ";s:3:"ᅵ";s:3:"¢";s:2:"¢";s:3:"£";s:2:"£";s:3:"¬";s:2:"¬";s:3:" ̄";s:3:" ̄";s:3:"¦";s:2:"¦";s:3:"¥";s:2:"¥";s:3:"₩";s:3:"₩";s:3:"│";s:3:"│";s:3:"←";s:3:"←";s:3:"↑";s:3:"↑";s:3:"→";s:3:"→";s:3:"↓";s:3:"↓";s:3:"■";s:3:"■";s:3:"○";s:3:"○";s:4:"𑂚";s:8:"𑂚";s:4:"𑂜";s:8:"𑂜";s:4:"𑂫";s:8:"𑂫";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"𝐀";s:1:"A";s:4:"𝐁";s:1:"B";s:4:"𝐂";s:1:"C";s:4:"𝐃";s:1:"D";s:4:"𝐄";s:1:"E";s:4:"𝐅";s:1:"F";s:4:"𝐆";s:1:"G";s:4:"𝐇";s:1:"H";s:4:"𝐈";s:1:"I";s:4:"𝐉";s:1:"J";s:4:"𝐊";s:1:"K";s:4:"𝐋";s:1:"L";s:4:"𝐌";s:1:"M";s:4:"𝐍";s:1:"N";s:4:"𝐎";s:1:"O";s:4:"𝐏";s:1:"P";s:4:"𝐐";s:1:"Q";s:4:"𝐑";s:1:"R";s:4:"𝐒";s:1:"S";s:4:"𝐓";s:1:"T";s:4:"𝐔";s:1:"U";s:4:"𝐕";s:1:"V";s:4:"𝐖";s:1:"W";s:4:"𝐗";s:1:"X";s:4:"𝐘";s:1:"Y";s:4:"𝐙";s:1:"Z";s:4:"𝐚";s:1:"a";s:4:"𝐛";s:1:"b";s:4:"𝐜";s:1:"c";s:4:"𝐝";s:1:"d";s:4:"𝐞";s:1:"e";s:4:"𝐟";s:1:"f";s:4:"𝐠";s:1:"g";s:4:"𝐡";s:1:"h";s:4:"𝐢";s:1:"i";s:4:"𝐣";s:1:"j";s:4:"𝐤";s:1:"k";s:4:"𝐥";s:1:"l";s:4:"𝐦";s:1:"m";s:4:"𝐧";s:1:"n";s:4:"𝐨";s:1:"o";s:4:"𝐩";s:1:"p";s:4:"𝐪";s:1:"q";s:4:"𝐫";s:1:"r";s:4:"𝐬";s:1:"s";s:4:"𝐭";s:1:"t";s:4:"𝐮";s:1:"u";s:4:"𝐯";s:1:"v";s:4:"𝐰";s:1:"w";s:4:"𝐱";s:1:"x";s:4:"𝐲";s:1:"y";s:4:"𝐳";s:1:"z";s:4:"𝐴";s:1:"A";s:4:"𝐵";s:1:"B";s:4:"𝐶";s:1:"C";s:4:"𝐷";s:1:"D";s:4:"𝐸";s:1:"E";s:4:"𝐹";s:1:"F";s:4:"𝐺";s:1:"G";s:4:"𝐻";s:1:"H";s:4:"𝐼";s:1:"I";s:4:"𝐽";s:1:"J";s:4:"𝐾";s:1:"K";s:4:"𝐿";s:1:"L";s:4:"𝑀";s:1:"M";s:4:"𝑁";s:1:"N";s:4:"𝑂";s:1:"O";s:4:"𝑃";s:1:"P";s:4:"𝑄";s:1:"Q";s:4:"𝑅";s:1:"R";s:4:"𝑆";s:1:"S";s:4:"𝑇";s:1:"T";s:4:"𝑈";s:1:"U";s:4:"𝑉";s:1:"V";s:4:"𝑊";s:1:"W";s:4:"𝑋";s:1:"X";s:4:"𝑌";s:1:"Y";s:4:"𝑍";s:1:"Z";s:4:"𝑎";s:1:"a";s:4:"𝑏";s:1:"b";s:4:"𝑐";s:1:"c";s:4:"𝑑";s:1:"d";s:4:"𝑒";s:1:"e";s:4:"𝑓";s:1:"f";s:4:"𝑔";s:1:"g";s:4:"𝑖";s:1:"i";s:4:"𝑗";s:1:"j";s:4:"𝑘";s:1:"k";s:4:"𝑙";s:1:"l";s:4:"𝑚";s:1:"m";s:4:"𝑛";s:1:"n";s:4:"𝑜";s:1:"o";s:4:"𝑝";s:1:"p";s:4:"𝑞";s:1:"q";s:4:"𝑟";s:1:"r";s:4:"𝑠";s:1:"s";s:4:"𝑡";s:1:"t";s:4:"𝑢";s:1:"u";s:4:"𝑣";s:1:"v";s:4:"𝑤";s:1:"w";s:4:"𝑥";s:1:"x";s:4:"𝑦";s:1:"y";s:4:"𝑧";s:1:"z";s:4:"𝑨";s:1:"A";s:4:"𝑩";s:1:"B";s:4:"𝑪";s:1:"C";s:4:"𝑫";s:1:"D";s:4:"𝑬";s:1:"E";s:4:"𝑭";s:1:"F";s:4:"𝑮";s:1:"G";s:4:"𝑯";s:1:"H";s:4:"𝑰";s:1:"I";s:4:"𝑱";s:1:"J";s:4:"𝑲";s:1:"K";s:4:"𝑳";s:1:"L";s:4:"𝑴";s:1:"M";s:4:"𝑵";s:1:"N";s:4:"𝑶";s:1:"O";s:4:"𝑷";s:1:"P";s:4:"𝑸";s:1:"Q";s:4:"𝑹";s:1:"R";s:4:"𝑺";s:1:"S";s:4:"𝑻";s:1:"T";s:4:"𝑼";s:1:"U";s:4:"𝑽";s:1:"V";s:4:"𝑾";s:1:"W";s:4:"𝑿";s:1:"X";s:4:"𝒀";s:1:"Y";s:4:"𝒁";s:1:"Z";s:4:"𝒂";s:1:"a";s:4:"𝒃";s:1:"b";s:4:"𝒄";s:1:"c";s:4:"𝒅";s:1:"d";s:4:"𝒆";s:1:"e";s:4:"𝒇";s:1:"f";s:4:"𝒈";s:1:"g";s:4:"𝒉";s:1:"h";s:4:"𝒊";s:1:"i";s:4:"𝒋";s:1:"j";s:4:"𝒌";s:1:"k";s:4:"𝒍";s:1:"l";s:4:"𝒎";s:1:"m";s:4:"𝒏";s:1:"n";s:4:"𝒐";s:1:"o";s:4:"𝒑";s:1:"p";s:4:"𝒒";s:1:"q";s:4:"𝒓";s:1:"r";s:4:"𝒔";s:1:"s";s:4:"𝒕";s:1:"t";s:4:"𝒖";s:1:"u";s:4:"𝒗";s:1:"v";s:4:"𝒘";s:1:"w";s:4:"𝒙";s:1:"x";s:4:"𝒚";s:1:"y";s:4:"𝒛";s:1:"z";s:4:"𝒜";s:1:"A";s:4:"𝒞";s:1:"C";s:4:"𝒟";s:1:"D";s:4:"𝒢";s:1:"G";s:4:"𝒥";s:1:"J";s:4:"𝒦";s:1:"K";s:4:"𝒩";s:1:"N";s:4:"𝒪";s:1:"O";s:4:"𝒫";s:1:"P";s:4:"𝒬";s:1:"Q";s:4:"𝒮";s:1:"S";s:4:"𝒯";s:1:"T";s:4:"𝒰";s:1:"U";s:4:"𝒱";s:1:"V";s:4:"𝒲";s:1:"W";s:4:"𝒳";s:1:"X";s:4:"𝒴";s:1:"Y";s:4:"𝒵";s:1:"Z";s:4:"𝒶";s:1:"a";s:4:"𝒷";s:1:"b";s:4:"𝒸";s:1:"c";s:4:"𝒹";s:1:"d";s:4:"𝒻";s:1:"f";s:4:"𝒽";s:1:"h";s:4:"𝒾";s:1:"i";s:4:"𝒿";s:1:"j";s:4:"𝓀";s:1:"k";s:4:"𝓁";s:1:"l";s:4:"𝓂";s:1:"m";s:4:"𝓃";s:1:"n";s:4:"𝓅";s:1:"p";s:4:"𝓆";s:1:"q";s:4:"𝓇";s:1:"r";s:4:"𝓈";s:1:"s";s:4:"𝓉";s:1:"t";s:4:"𝓊";s:1:"u";s:4:"𝓋";s:1:"v";s:4:"𝓌";s:1:"w";s:4:"𝓍";s:1:"x";s:4:"𝓎";s:1:"y";s:4:"𝓏";s:1:"z";s:4:"𝓐";s:1:"A";s:4:"𝓑";s:1:"B";s:4:"𝓒";s:1:"C";s:4:"𝓓";s:1:"D";s:4:"𝓔";s:1:"E";s:4:"𝓕";s:1:"F";s:4:"𝓖";s:1:"G";s:4:"𝓗";s:1:"H";s:4:"𝓘";s:1:"I";s:4:"𝓙";s:1:"J";s:4:"𝓚";s:1:"K";s:4:"𝓛";s:1:"L";s:4:"𝓜";s:1:"M";s:4:"𝓝";s:1:"N";s:4:"𝓞";s:1:"O";s:4:"𝓟";s:1:"P";s:4:"𝓠";s:1:"Q";s:4:"𝓡";s:1:"R";s:4:"𝓢";s:1:"S";s:4:"𝓣";s:1:"T";s:4:"𝓤";s:1:"U";s:4:"𝓥";s:1:"V";s:4:"𝓦";s:1:"W";s:4:"𝓧";s:1:"X";s:4:"𝓨";s:1:"Y";s:4:"𝓩";s:1:"Z";s:4:"𝓪";s:1:"a";s:4:"𝓫";s:1:"b";s:4:"𝓬";s:1:"c";s:4:"𝓭";s:1:"d";s:4:"𝓮";s:1:"e";s:4:"𝓯";s:1:"f";s:4:"𝓰";s:1:"g";s:4:"𝓱";s:1:"h";s:4:"𝓲";s:1:"i";s:4:"𝓳";s:1:"j";s:4:"𝓴";s:1:"k";s:4:"𝓵";s:1:"l";s:4:"𝓶";s:1:"m";s:4:"𝓷";s:1:"n";s:4:"𝓸";s:1:"o";s:4:"𝓹";s:1:"p";s:4:"𝓺";s:1:"q";s:4:"𝓻";s:1:"r";s:4:"𝓼";s:1:"s";s:4:"𝓽";s:1:"t";s:4:"𝓾";s:1:"u";s:4:"𝓿";s:1:"v";s:4:"𝔀";s:1:"w";s:4:"𝔁";s:1:"x";s:4:"𝔂";s:1:"y";s:4:"𝔃";s:1:"z";s:4:"𝔄";s:1:"A";s:4:"𝔅";s:1:"B";s:4:"𝔇";s:1:"D";s:4:"𝔈";s:1:"E";s:4:"𝔉";s:1:"F";s:4:"𝔊";s:1:"G";s:4:"𝔍";s:1:"J";s:4:"𝔎";s:1:"K";s:4:"𝔏";s:1:"L";s:4:"𝔐";s:1:"M";s:4:"𝔑";s:1:"N";s:4:"𝔒";s:1:"O";s:4:"𝔓";s:1:"P";s:4:"𝔔";s:1:"Q";s:4:"𝔖";s:1:"S";s:4:"𝔗";s:1:"T";s:4:"𝔘";s:1:"U";s:4:"𝔙";s:1:"V";s:4:"𝔚";s:1:"W";s:4:"𝔛";s:1:"X";s:4:"𝔜";s:1:"Y";s:4:"𝔞";s:1:"a";s:4:"𝔟";s:1:"b";s:4:"𝔠";s:1:"c";s:4:"𝔡";s:1:"d";s:4:"𝔢";s:1:"e";s:4:"𝔣";s:1:"f";s:4:"𝔤";s:1:"g";s:4:"𝔥";s:1:"h";s:4:"𝔦";s:1:"i";s:4:"𝔧";s:1:"j";s:4:"𝔨";s:1:"k";s:4:"𝔩";s:1:"l";s:4:"𝔪";s:1:"m";s:4:"𝔫";s:1:"n";s:4:"𝔬";s:1:"o";s:4:"𝔭";s:1:"p";s:4:"𝔮";s:1:"q";s:4:"𝔯";s:1:"r";s:4:"𝔰";s:1:"s";s:4:"𝔱";s:1:"t";s:4:"𝔲";s:1:"u";s:4:"𝔳";s:1:"v";s:4:"𝔴";s:1:"w";s:4:"𝔵";s:1:"x";s:4:"𝔶";s:1:"y";s:4:"𝔷";s:1:"z";s:4:"𝔸";s:1:"A";s:4:"𝔹";s:1:"B";s:4:"𝔻";s:1:"D";s:4:"𝔼";s:1:"E";s:4:"𝔽";s:1:"F";s:4:"𝔾";s:1:"G";s:4:"𝕀";s:1:"I";s:4:"𝕁";s:1:"J";s:4:"𝕂";s:1:"K";s:4:"𝕃";s:1:"L";s:4:"𝕄";s:1:"M";s:4:"𝕆";s:1:"O";s:4:"𝕊";s:1:"S";s:4:"𝕋";s:1:"T";s:4:"𝕌";s:1:"U";s:4:"𝕍";s:1:"V";s:4:"𝕎";s:1:"W";s:4:"𝕏";s:1:"X";s:4:"𝕐";s:1:"Y";s:4:"𝕒";s:1:"a";s:4:"𝕓";s:1:"b";s:4:"𝕔";s:1:"c";s:4:"𝕕";s:1:"d";s:4:"𝕖";s:1:"e";s:4:"𝕗";s:1:"f";s:4:"𝕘";s:1:"g";s:4:"𝕙";s:1:"h";s:4:"𝕚";s:1:"i";s:4:"𝕛";s:1:"j";s:4:"𝕜";s:1:"k";s:4:"𝕝";s:1:"l";s:4:"𝕞";s:1:"m";s:4:"𝕟";s:1:"n";s:4:"𝕠";s:1:"o";s:4:"𝕡";s:1:"p";s:4:"𝕢";s:1:"q";s:4:"𝕣";s:1:"r";s:4:"𝕤";s:1:"s";s:4:"𝕥";s:1:"t";s:4:"𝕦";s:1:"u";s:4:"𝕧";s:1:"v";s:4:"𝕨";s:1:"w";s:4:"𝕩";s:1:"x";s:4:"𝕪";s:1:"y";s:4:"𝕫";s:1:"z";s:4:"𝕬";s:1:"A";s:4:"𝕭";s:1:"B";s:4:"𝕮";s:1:"C";s:4:"𝕯";s:1:"D";s:4:"𝕰";s:1:"E";s:4:"𝕱";s:1:"F";s:4:"𝕲";s:1:"G";s:4:"𝕳";s:1:"H";s:4:"𝕴";s:1:"I";s:4:"𝕵";s:1:"J";s:4:"𝕶";s:1:"K";s:4:"𝕷";s:1:"L";s:4:"𝕸";s:1:"M";s:4:"𝕹";s:1:"N";s:4:"𝕺";s:1:"O";s:4:"𝕻";s:1:"P";s:4:"𝕼";s:1:"Q";s:4:"𝕽";s:1:"R";s:4:"𝕾";s:1:"S";s:4:"𝕿";s:1:"T";s:4:"𝖀";s:1:"U";s:4:"𝖁";s:1:"V";s:4:"𝖂";s:1:"W";s:4:"𝖃";s:1:"X";s:4:"𝖄";s:1:"Y";s:4:"𝖅";s:1:"Z";s:4:"𝖆";s:1:"a";s:4:"𝖇";s:1:"b";s:4:"𝖈";s:1:"c";s:4:"𝖉";s:1:"d";s:4:"𝖊";s:1:"e";s:4:"𝖋";s:1:"f";s:4:"𝖌";s:1:"g";s:4:"𝖍";s:1:"h";s:4:"𝖎";s:1:"i";s:4:"𝖏";s:1:"j";s:4:"𝖐";s:1:"k";s:4:"𝖑";s:1:"l";s:4:"𝖒";s:1:"m";s:4:"𝖓";s:1:"n";s:4:"𝖔";s:1:"o";s:4:"𝖕";s:1:"p";s:4:"𝖖";s:1:"q";s:4:"𝖗";s:1:"r";s:4:"𝖘";s:1:"s";s:4:"𝖙";s:1:"t";s:4:"𝖚";s:1:"u";s:4:"𝖛";s:1:"v";s:4:"𝖜";s:1:"w";s:4:"𝖝";s:1:"x";s:4:"𝖞";s:1:"y";s:4:"𝖟";s:1:"z";s:4:"𝖠";s:1:"A";s:4:"𝖡";s:1:"B";s:4:"𝖢";s:1:"C";s:4:"𝖣";s:1:"D";s:4:"𝖤";s:1:"E";s:4:"𝖥";s:1:"F";s:4:"𝖦";s:1:"G";s:4:"𝖧";s:1:"H";s:4:"𝖨";s:1:"I";s:4:"𝖩";s:1:"J";s:4:"𝖪";s:1:"K";s:4:"𝖫";s:1:"L";s:4:"𝖬";s:1:"M";s:4:"𝖭";s:1:"N";s:4:"𝖮";s:1:"O";s:4:"𝖯";s:1:"P";s:4:"𝖰";s:1:"Q";s:4:"𝖱";s:1:"R";s:4:"𝖲";s:1:"S";s:4:"𝖳";s:1:"T";s:4:"𝖴";s:1:"U";s:4:"𝖵";s:1:"V";s:4:"𝖶";s:1:"W";s:4:"𝖷";s:1:"X";s:4:"𝖸";s:1:"Y";s:4:"𝖹";s:1:"Z";s:4:"𝖺";s:1:"a";s:4:"𝖻";s:1:"b";s:4:"𝖼";s:1:"c";s:4:"𝖽";s:1:"d";s:4:"𝖾";s:1:"e";s:4:"𝖿";s:1:"f";s:4:"𝗀";s:1:"g";s:4:"𝗁";s:1:"h";s:4:"𝗂";s:1:"i";s:4:"𝗃";s:1:"j";s:4:"𝗄";s:1:"k";s:4:"𝗅";s:1:"l";s:4:"𝗆";s:1:"m";s:4:"𝗇";s:1:"n";s:4:"𝗈";s:1:"o";s:4:"𝗉";s:1:"p";s:4:"𝗊";s:1:"q";s:4:"𝗋";s:1:"r";s:4:"𝗌";s:1:"s";s:4:"𝗍";s:1:"t";s:4:"𝗎";s:1:"u";s:4:"𝗏";s:1:"v";s:4:"𝗐";s:1:"w";s:4:"𝗑";s:1:"x";s:4:"𝗒";s:1:"y";s:4:"𝗓";s:1:"z";s:4:"𝗔";s:1:"A";s:4:"𝗕";s:1:"B";s:4:"𝗖";s:1:"C";s:4:"𝗗";s:1:"D";s:4:"𝗘";s:1:"E";s:4:"𝗙";s:1:"F";s:4:"𝗚";s:1:"G";s:4:"𝗛";s:1:"H";s:4:"𝗜";s:1:"I";s:4:"𝗝";s:1:"J";s:4:"𝗞";s:1:"K";s:4:"𝗟";s:1:"L";s:4:"𝗠";s:1:"M";s:4:"𝗡";s:1:"N";s:4:"𝗢";s:1:"O";s:4:"𝗣";s:1:"P";s:4:"𝗤";s:1:"Q";s:4:"𝗥";s:1:"R";s:4:"𝗦";s:1:"S";s:4:"𝗧";s:1:"T";s:4:"𝗨";s:1:"U";s:4:"𝗩";s:1:"V";s:4:"𝗪";s:1:"W";s:4:"𝗫";s:1:"X";s:4:"𝗬";s:1:"Y";s:4:"𝗭";s:1:"Z";s:4:"𝗮";s:1:"a";s:4:"𝗯";s:1:"b";s:4:"𝗰";s:1:"c";s:4:"𝗱";s:1:"d";s:4:"𝗲";s:1:"e";s:4:"𝗳";s:1:"f";s:4:"𝗴";s:1:"g";s:4:"𝗵";s:1:"h";s:4:"𝗶";s:1:"i";s:4:"𝗷";s:1:"j";s:4:"𝗸";s:1:"k";s:4:"𝗹";s:1:"l";s:4:"𝗺";s:1:"m";s:4:"𝗻";s:1:"n";s:4:"𝗼";s:1:"o";s:4:"𝗽";s:1:"p";s:4:"𝗾";s:1:"q";s:4:"𝗿";s:1:"r";s:4:"𝘀";s:1:"s";s:4:"𝘁";s:1:"t";s:4:"𝘂";s:1:"u";s:4:"𝘃";s:1:"v";s:4:"𝘄";s:1:"w";s:4:"𝘅";s:1:"x";s:4:"𝘆";s:1:"y";s:4:"𝘇";s:1:"z";s:4:"𝘈";s:1:"A";s:4:"𝘉";s:1:"B";s:4:"𝘊";s:1:"C";s:4:"𝘋";s:1:"D";s:4:"𝘌";s:1:"E";s:4:"𝘍";s:1:"F";s:4:"𝘎";s:1:"G";s:4:"𝘏";s:1:"H";s:4:"𝘐";s:1:"I";s:4:"𝘑";s:1:"J";s:4:"𝘒";s:1:"K";s:4:"𝘓";s:1:"L";s:4:"𝘔";s:1:"M";s:4:"𝘕";s:1:"N";s:4:"𝘖";s:1:"O";s:4:"𝘗";s:1:"P";s:4:"𝘘";s:1:"Q";s:4:"𝘙";s:1:"R";s:4:"𝘚";s:1:"S";s:4:"𝘛";s:1:"T";s:4:"𝘜";s:1:"U";s:4:"𝘝";s:1:"V";s:4:"𝘞";s:1:"W";s:4:"𝘟";s:1:"X";s:4:"𝘠";s:1:"Y";s:4:"𝘡";s:1:"Z";s:4:"𝘢";s:1:"a";s:4:"𝘣";s:1:"b";s:4:"𝘤";s:1:"c";s:4:"𝘥";s:1:"d";s:4:"𝘦";s:1:"e";s:4:"𝘧";s:1:"f";s:4:"𝘨";s:1:"g";s:4:"𝘩";s:1:"h";s:4:"𝘪";s:1:"i";s:4:"𝘫";s:1:"j";s:4:"𝘬";s:1:"k";s:4:"𝘭";s:1:"l";s:4:"𝘮";s:1:"m";s:4:"𝘯";s:1:"n";s:4:"𝘰";s:1:"o";s:4:"𝘱";s:1:"p";s:4:"𝘲";s:1:"q";s:4:"𝘳";s:1:"r";s:4:"𝘴";s:1:"s";s:4:"𝘵";s:1:"t";s:4:"𝘶";s:1:"u";s:4:"𝘷";s:1:"v";s:4:"𝘸";s:1:"w";s:4:"𝘹";s:1:"x";s:4:"𝘺";s:1:"y";s:4:"𝘻";s:1:"z";s:4:"𝘼";s:1:"A";s:4:"𝘽";s:1:"B";s:4:"𝘾";s:1:"C";s:4:"𝘿";s:1:"D";s:4:"𝙀";s:1:"E";s:4:"𝙁";s:1:"F";s:4:"𝙂";s:1:"G";s:4:"𝙃";s:1:"H";s:4:"𝙄";s:1:"I";s:4:"𝙅";s:1:"J";s:4:"𝙆";s:1:"K";s:4:"𝙇";s:1:"L";s:4:"𝙈";s:1:"M";s:4:"𝙉";s:1:"N";s:4:"𝙊";s:1:"O";s:4:"𝙋";s:1:"P";s:4:"𝙌";s:1:"Q";s:4:"𝙍";s:1:"R";s:4:"𝙎";s:1:"S";s:4:"𝙏";s:1:"T";s:4:"𝙐";s:1:"U";s:4:"𝙑";s:1:"V";s:4:"𝙒";s:1:"W";s:4:"𝙓";s:1:"X";s:4:"𝙔";s:1:"Y";s:4:"𝙕";s:1:"Z";s:4:"𝙖";s:1:"a";s:4:"𝙗";s:1:"b";s:4:"𝙘";s:1:"c";s:4:"𝙙";s:1:"d";s:4:"𝙚";s:1:"e";s:4:"𝙛";s:1:"f";s:4:"𝙜";s:1:"g";s:4:"𝙝";s:1:"h";s:4:"𝙞";s:1:"i";s:4:"𝙟";s:1:"j";s:4:"𝙠";s:1:"k";s:4:"𝙡";s:1:"l";s:4:"𝙢";s:1:"m";s:4:"𝙣";s:1:"n";s:4:"𝙤";s:1:"o";s:4:"𝙥";s:1:"p";s:4:"𝙦";s:1:"q";s:4:"𝙧";s:1:"r";s:4:"𝙨";s:1:"s";s:4:"𝙩";s:1:"t";s:4:"𝙪";s:1:"u";s:4:"𝙫";s:1:"v";s:4:"𝙬";s:1:"w";s:4:"𝙭";s:1:"x";s:4:"𝙮";s:1:"y";s:4:"𝙯";s:1:"z";s:4:"𝙰";s:1:"A";s:4:"𝙱";s:1:"B";s:4:"𝙲";s:1:"C";s:4:"𝙳";s:1:"D";s:4:"𝙴";s:1:"E";s:4:"𝙵";s:1:"F";s:4:"𝙶";s:1:"G";s:4:"𝙷";s:1:"H";s:4:"𝙸";s:1:"I";s:4:"𝙹";s:1:"J";s:4:"𝙺";s:1:"K";s:4:"𝙻";s:1:"L";s:4:"𝙼";s:1:"M";s:4:"𝙽";s:1:"N";s:4:"𝙾";s:1:"O";s:4:"𝙿";s:1:"P";s:4:"𝚀";s:1:"Q";s:4:"𝚁";s:1:"R";s:4:"𝚂";s:1:"S";s:4:"𝚃";s:1:"T";s:4:"𝚄";s:1:"U";s:4:"𝚅";s:1:"V";s:4:"𝚆";s:1:"W";s:4:"𝚇";s:1:"X";s:4:"𝚈";s:1:"Y";s:4:"𝚉";s:1:"Z";s:4:"𝚊";s:1:"a";s:4:"𝚋";s:1:"b";s:4:"𝚌";s:1:"c";s:4:"𝚍";s:1:"d";s:4:"𝚎";s:1:"e";s:4:"𝚏";s:1:"f";s:4:"𝚐";s:1:"g";s:4:"𝚑";s:1:"h";s:4:"𝚒";s:1:"i";s:4:"𝚓";s:1:"j";s:4:"𝚔";s:1:"k";s:4:"𝚕";s:1:"l";s:4:"𝚖";s:1:"m";s:4:"𝚗";s:1:"n";s:4:"𝚘";s:1:"o";s:4:"𝚙";s:1:"p";s:4:"𝚚";s:1:"q";s:4:"𝚛";s:1:"r";s:4:"𝚜";s:1:"s";s:4:"𝚝";s:1:"t";s:4:"𝚞";s:1:"u";s:4:"𝚟";s:1:"v";s:4:"𝚠";s:1:"w";s:4:"𝚡";s:1:"x";s:4:"𝚢";s:1:"y";s:4:"𝚣";s:1:"z";s:4:"𝚤";s:2:"ı";s:4:"𝚥";s:2:"ȷ";s:4:"𝚨";s:2:"Α";s:4:"𝚩";s:2:"Β";s:4:"𝚪";s:2:"Γ";s:4:"𝚫";s:2:"Δ";s:4:"𝚬";s:2:"Ε";s:4:"𝚭";s:2:"Ζ";s:4:"𝚮";s:2:"Η";s:4:"𝚯";s:2:"Θ";s:4:"𝚰";s:2:"Ι";s:4:"𝚱";s:2:"Κ";s:4:"𝚲";s:2:"Λ";s:4:"𝚳";s:2:"Μ";s:4:"𝚴";s:2:"Ν";s:4:"𝚵";s:2:"Ξ";s:4:"𝚶";s:2:"Ο";s:4:"𝚷";s:2:"Π";s:4:"𝚸";s:2:"Ρ";s:4:"𝚹";s:2:"Θ";s:4:"𝚺";s:2:"Σ";s:4:"𝚻";s:2:"Τ";s:4:"𝚼";s:2:"Υ";s:4:"𝚽";s:2:"Φ";s:4:"𝚾";s:2:"Χ";s:4:"𝚿";s:2:"Ψ";s:4:"𝛀";s:2:"Ω";s:4:"𝛁";s:3:"∇";s:4:"𝛂";s:2:"α";s:4:"𝛃";s:2:"β";s:4:"𝛄";s:2:"γ";s:4:"𝛅";s:2:"δ";s:4:"𝛆";s:2:"ε";s:4:"𝛇";s:2:"ζ";s:4:"𝛈";s:2:"η";s:4:"𝛉";s:2:"θ";s:4:"𝛊";s:2:"ι";s:4:"𝛋";s:2:"κ";s:4:"𝛌";s:2:"λ";s:4:"𝛍";s:2:"μ";s:4:"𝛎";s:2:"ν";s:4:"𝛏";s:2:"ξ";s:4:"𝛐";s:2:"ο";s:4:"𝛑";s:2:"π";s:4:"𝛒";s:2:"ρ";s:4:"𝛓";s:2:"ς";s:4:"𝛔";s:2:"σ";s:4:"𝛕";s:2:"τ";s:4:"𝛖";s:2:"υ";s:4:"𝛗";s:2:"φ";s:4:"𝛘";s:2:"χ";s:4:"𝛙";s:2:"ψ";s:4:"𝛚";s:2:"ω";s:4:"𝛛";s:3:"∂";s:4:"𝛜";s:2:"ε";s:4:"𝛝";s:2:"θ";s:4:"𝛞";s:2:"κ";s:4:"𝛟";s:2:"φ";s:4:"𝛠";s:2:"ρ";s:4:"𝛡";s:2:"π";s:4:"𝛢";s:2:"Α";s:4:"𝛣";s:2:"Β";s:4:"𝛤";s:2:"Γ";s:4:"𝛥";s:2:"Δ";s:4:"𝛦";s:2:"Ε";s:4:"𝛧";s:2:"Ζ";s:4:"𝛨";s:2:"Η";s:4:"𝛩";s:2:"Θ";s:4:"𝛪";s:2:"Ι";s:4:"𝛫";s:2:"Κ";s:4:"𝛬";s:2:"Λ";s:4:"𝛭";s:2:"Μ";s:4:"𝛮";s:2:"Ν";s:4:"𝛯";s:2:"Ξ";s:4:"𝛰";s:2:"Ο";s:4:"𝛱";s:2:"Π";s:4:"𝛲";s:2:"Ρ";s:4:"𝛳";s:2:"Θ";s:4:"𝛴";s:2:"Σ";s:4:"𝛵";s:2:"Τ";s:4:"𝛶";s:2:"Υ";s:4:"𝛷";s:2:"Φ";s:4:"𝛸";s:2:"Χ";s:4:"𝛹";s:2:"Ψ";s:4:"𝛺";s:2:"Ω";s:4:"𝛻";s:3:"∇";s:4:"𝛼";s:2:"α";s:4:"𝛽";s:2:"β";s:4:"𝛾";s:2:"γ";s:4:"𝛿";s:2:"δ";s:4:"𝜀";s:2:"ε";s:4:"𝜁";s:2:"ζ";s:4:"𝜂";s:2:"η";s:4:"𝜃";s:2:"θ";s:4:"𝜄";s:2:"ι";s:4:"𝜅";s:2:"κ";s:4:"𝜆";s:2:"λ";s:4:"𝜇";s:2:"μ";s:4:"𝜈";s:2:"ν";s:4:"𝜉";s:2:"ξ";s:4:"𝜊";s:2:"ο";s:4:"𝜋";s:2:"π";s:4:"𝜌";s:2:"ρ";s:4:"𝜍";s:2:"ς";s:4:"𝜎";s:2:"σ";s:4:"𝜏";s:2:"τ";s:4:"𝜐";s:2:"υ";s:4:"𝜑";s:2:"φ";s:4:"𝜒";s:2:"χ";s:4:"𝜓";s:2:"ψ";s:4:"𝜔";s:2:"ω";s:4:"𝜕";s:3:"∂";s:4:"𝜖";s:2:"ε";s:4:"𝜗";s:2:"θ";s:4:"𝜘";s:2:"κ";s:4:"𝜙";s:2:"φ";s:4:"𝜚";s:2:"ρ";s:4:"𝜛";s:2:"π";s:4:"𝜜";s:2:"Α";s:4:"𝜝";s:2:"Β";s:4:"𝜞";s:2:"Γ";s:4:"𝜟";s:2:"Δ";s:4:"𝜠";s:2:"Ε";s:4:"𝜡";s:2:"Ζ";s:4:"𝜢";s:2:"Η";s:4:"𝜣";s:2:"Θ";s:4:"𝜤";s:2:"Ι";s:4:"𝜥";s:2:"Κ";s:4:"𝜦";s:2:"Λ";s:4:"𝜧";s:2:"Μ";s:4:"𝜨";s:2:"Ν";s:4:"𝜩";s:2:"Ξ";s:4:"𝜪";s:2:"Ο";s:4:"𝜫";s:2:"Π";s:4:"𝜬";s:2:"Ρ";s:4:"𝜭";s:2:"Θ";s:4:"𝜮";s:2:"Σ";s:4:"𝜯";s:2:"Τ";s:4:"𝜰";s:2:"Υ";s:4:"𝜱";s:2:"Φ";s:4:"𝜲";s:2:"Χ";s:4:"𝜳";s:2:"Ψ";s:4:"𝜴";s:2:"Ω";s:4:"𝜵";s:3:"∇";s:4:"𝜶";s:2:"α";s:4:"𝜷";s:2:"β";s:4:"𝜸";s:2:"γ";s:4:"𝜹";s:2:"δ";s:4:"𝜺";s:2:"ε";s:4:"𝜻";s:2:"ζ";s:4:"𝜼";s:2:"η";s:4:"𝜽";s:2:"θ";s:4:"𝜾";s:2:"ι";s:4:"𝜿";s:2:"κ";s:4:"𝝀";s:2:"λ";s:4:"𝝁";s:2:"μ";s:4:"𝝂";s:2:"ν";s:4:"𝝃";s:2:"ξ";s:4:"𝝄";s:2:"ο";s:4:"𝝅";s:2:"π";s:4:"𝝆";s:2:"ρ";s:4:"𝝇";s:2:"ς";s:4:"𝝈";s:2:"σ";s:4:"𝝉";s:2:"τ";s:4:"𝝊";s:2:"υ";s:4:"𝝋";s:2:"φ";s:4:"𝝌";s:2:"χ";s:4:"𝝍";s:2:"ψ";s:4:"𝝎";s:2:"ω";s:4:"𝝏";s:3:"∂";s:4:"𝝐";s:2:"ε";s:4:"𝝑";s:2:"θ";s:4:"𝝒";s:2:"κ";s:4:"𝝓";s:2:"φ";s:4:"𝝔";s:2:"ρ";s:4:"𝝕";s:2:"π";s:4:"𝝖";s:2:"Α";s:4:"𝝗";s:2:"Β";s:4:"𝝘";s:2:"Γ";s:4:"𝝙";s:2:"Δ";s:4:"𝝚";s:2:"Ε";s:4:"𝝛";s:2:"Ζ";s:4:"𝝜";s:2:"Η";s:4:"𝝝";s:2:"Θ";s:4:"𝝞";s:2:"Ι";s:4:"𝝟";s:2:"Κ";s:4:"𝝠";s:2:"Λ";s:4:"𝝡";s:2:"Μ";s:4:"𝝢";s:2:"Ν";s:4:"𝝣";s:2:"Ξ";s:4:"𝝤";s:2:"Ο";s:4:"𝝥";s:2:"Π";s:4:"𝝦";s:2:"Ρ";s:4:"𝝧";s:2:"Θ";s:4:"𝝨";s:2:"Σ";s:4:"𝝩";s:2:"Τ";s:4:"𝝪";s:2:"Υ";s:4:"𝝫";s:2:"Φ";s:4:"𝝬";s:2:"Χ";s:4:"𝝭";s:2:"Ψ";s:4:"𝝮";s:2:"Ω";s:4:"𝝯";s:3:"∇";s:4:"𝝰";s:2:"α";s:4:"𝝱";s:2:"β";s:4:"𝝲";s:2:"γ";s:4:"𝝳";s:2:"δ";s:4:"𝝴";s:2:"ε";s:4:"𝝵";s:2:"ζ";s:4:"𝝶";s:2:"η";s:4:"𝝷";s:2:"θ";s:4:"𝝸";s:2:"ι";s:4:"𝝹";s:2:"κ";s:4:"𝝺";s:2:"λ";s:4:"𝝻";s:2:"μ";s:4:"𝝼";s:2:"ν";s:4:"𝝽";s:2:"ξ";s:4:"𝝾";s:2:"ο";s:4:"𝝿";s:2:"π";s:4:"𝞀";s:2:"ρ";s:4:"𝞁";s:2:"ς";s:4:"𝞂";s:2:"σ";s:4:"𝞃";s:2:"τ";s:4:"𝞄";s:2:"υ";s:4:"𝞅";s:2:"φ";s:4:"𝞆";s:2:"χ";s:4:"𝞇";s:2:"ψ";s:4:"𝞈";s:2:"ω";s:4:"𝞉";s:3:"∂";s:4:"𝞊";s:2:"ε";s:4:"𝞋";s:2:"θ";s:4:"𝞌";s:2:"κ";s:4:"𝞍";s:2:"φ";s:4:"𝞎";s:2:"ρ";s:4:"𝞏";s:2:"π";s:4:"𝞐";s:2:"Α";s:4:"𝞑";s:2:"Β";s:4:"𝞒";s:2:"Γ";s:4:"𝞓";s:2:"Δ";s:4:"𝞔";s:2:"Ε";s:4:"𝞕";s:2:"Ζ";s:4:"𝞖";s:2:"Η";s:4:"𝞗";s:2:"Θ";s:4:"𝞘";s:2:"Ι";s:4:"𝞙";s:2:"Κ";s:4:"𝞚";s:2:"Λ";s:4:"𝞛";s:2:"Μ";s:4:"𝞜";s:2:"Ν";s:4:"𝞝";s:2:"Ξ";s:4:"𝞞";s:2:"Ο";s:4:"𝞟";s:2:"Π";s:4:"𝞠";s:2:"Ρ";s:4:"𝞡";s:2:"Θ";s:4:"𝞢";s:2:"Σ";s:4:"𝞣";s:2:"Τ";s:4:"𝞤";s:2:"Υ";s:4:"𝞥";s:2:"Φ";s:4:"𝞦";s:2:"Χ";s:4:"𝞧";s:2:"Ψ";s:4:"𝞨";s:2:"Ω";s:4:"𝞩";s:3:"∇";s:4:"𝞪";s:2:"α";s:4:"𝞫";s:2:"β";s:4:"𝞬";s:2:"γ";s:4:"𝞭";s:2:"δ";s:4:"𝞮";s:2:"ε";s:4:"𝞯";s:2:"ζ";s:4:"𝞰";s:2:"η";s:4:"𝞱";s:2:"θ";s:4:"𝞲";s:2:"ι";s:4:"𝞳";s:2:"κ";s:4:"𝞴";s:2:"λ";s:4:"𝞵";s:2:"μ";s:4:"𝞶";s:2:"ν";s:4:"𝞷";s:2:"ξ";s:4:"𝞸";s:2:"ο";s:4:"𝞹";s:2:"π";s:4:"𝞺";s:2:"ρ";s:4:"𝞻";s:2:"ς";s:4:"𝞼";s:2:"σ";s:4:"𝞽";s:2:"τ";s:4:"𝞾";s:2:"υ";s:4:"𝞿";s:2:"φ";s:4:"𝟀";s:2:"χ";s:4:"𝟁";s:2:"ψ";s:4:"𝟂";s:2:"ω";s:4:"𝟃";s:3:"∂";s:4:"𝟄";s:2:"ε";s:4:"𝟅";s:2:"θ";s:4:"𝟆";s:2:"κ";s:4:"𝟇";s:2:"φ";s:4:"𝟈";s:2:"ρ";s:4:"𝟉";s:2:"π";s:4:"𝟊";s:2:"Ϝ";s:4:"𝟋";s:2:"ϝ";s:4:"𝟎";s:1:"0";s:4:"𝟏";s:1:"1";s:4:"𝟐";s:1:"2";s:4:"𝟑";s:1:"3";s:4:"𝟒";s:1:"4";s:4:"𝟓";s:1:"5";s:4:"𝟔";s:1:"6";s:4:"𝟕";s:1:"7";s:4:"𝟖";s:1:"8";s:4:"𝟗";s:1:"9";s:4:"𝟘";s:1:"0";s:4:"𝟙";s:1:"1";s:4:"𝟚";s:1:"2";s:4:"𝟛";s:1:"3";s:4:"𝟜";s:1:"4";s:4:"𝟝";s:1:"5";s:4:"𝟞";s:1:"6";s:4:"𝟟";s:1:"7";s:4:"𝟠";s:1:"8";s:4:"𝟡";s:1:"9";s:4:"𝟢";s:1:"0";s:4:"𝟣";s:1:"1";s:4:"𝟤";s:1:"2";s:4:"𝟥";s:1:"3";s:4:"𝟦";s:1:"4";s:4:"𝟧";s:1:"5";s:4:"𝟨";s:1:"6";s:4:"𝟩";s:1:"7";s:4:"𝟪";s:1:"8";s:4:"𝟫";s:1:"9";s:4:"𝟬";s:1:"0";s:4:"𝟭";s:1:"1";s:4:"𝟮";s:1:"2";s:4:"𝟯";s:1:"3";s:4:"𝟰";s:1:"4";s:4:"𝟱";s:1:"5";s:4:"𝟲";s:1:"6";s:4:"𝟳";s:1:"7";s:4:"𝟴";s:1:"8";s:4:"𝟵";s:1:"9";s:4:"𝟶";s:1:"0";s:4:"𝟷";s:1:"1";s:4:"𝟸";s:1:"2";s:4:"𝟹";s:1:"3";s:4:"𝟺";s:1:"4";s:4:"𝟻";s:1:"5";s:4:"𝟼";s:1:"6";s:4:"𝟽";s:1:"7";s:4:"𝟾";s:1:"8";s:4:"𝟿";s:1:"9";s:4:"🄀";s:2:"0.";s:4:"🄁";s:2:"0,";s:4:"🄂";s:2:"1,";s:4:"🄃";s:2:"2,";s:4:"🄄";s:2:"3,";s:4:"🄅";s:2:"4,";s:4:"🄆";s:2:"5,";s:4:"🄇";s:2:"6,";s:4:"🄈";s:2:"7,";s:4:"🄉";s:2:"8,";s:4:"🄊";s:2:"9,";s:4:"🄐";s:3:"(A)";s:4:"🄑";s:3:"(B)";s:4:"🄒";s:3:"(C)";s:4:"🄓";s:3:"(D)";s:4:"🄔";s:3:"(E)";s:4:"🄕";s:3:"(F)";s:4:"🄖";s:3:"(G)";s:4:"🄗";s:3:"(H)";s:4:"🄘";s:3:"(I)";s:4:"🄙";s:3:"(J)";s:4:"🄚";s:3:"(K)";s:4:"🄛";s:3:"(L)";s:4:"🄜";s:3:"(M)";s:4:"🄝";s:3:"(N)";s:4:"🄞";s:3:"(O)";s:4:"🄟";s:3:"(P)";s:4:"🄠";s:3:"(Q)";s:4:"🄡";s:3:"(R)";s:4:"🄢";s:3:"(S)";s:4:"🄣";s:3:"(T)";s:4:"🄤";s:3:"(U)";s:4:"🄥";s:3:"(V)";s:4:"🄦";s:3:"(W)";s:4:"🄧";s:3:"(X)";s:4:"🄨";s:3:"(Y)";s:4:"🄩";s:3:"(Z)";s:4:"🄪";s:7:"〔S〕";s:4:"🄫";s:1:"C";s:4:"🄬";s:1:"R";s:4:"🄭";s:2:"CD";s:4:"🄮";s:2:"WZ";s:4:"🄰";s:1:"A";s:4:"🄱";s:1:"B";s:4:"🄲";s:1:"C";s:4:"🄳";s:1:"D";s:4:"🄴";s:1:"E";s:4:"🄵";s:1:"F";s:4:"🄶";s:1:"G";s:4:"🄷";s:1:"H";s:4:"🄸";s:1:"I";s:4:"🄹";s:1:"J";s:4:"🄺";s:1:"K";s:4:"🄻";s:1:"L";s:4:"🄼";s:1:"M";s:4:"🄽";s:1:"N";s:4:"🄾";s:1:"O";s:4:"🄿";s:1:"P";s:4:"🅀";s:1:"Q";s:4:"🅁";s:1:"R";s:4:"🅂";s:1:"S";s:4:"🅃";s:1:"T";s:4:"🅄";s:1:"U";s:4:"🅅";s:1:"V";s:4:"🅆";s:1:"W";s:4:"🅇";s:1:"X";s:4:"🅈";s:1:"Y";s:4:"🅉";s:1:"Z";s:4:"🅊";s:2:"HV";s:4:"🅋";s:2:"MV";s:4:"🅌";s:2:"SD";s:4:"🅍";s:2:"SS";s:4:"🅎";s:3:"PPV";s:4:"🅏";s:2:"WC";s:4:"🆐";s:2:"DJ";s:4:"🈀";s:6:"ほか";s:4:"🈁";s:6:"ココ";s:4:"🈂";s:3:"サ";s:4:"🈐";s:3:"手";s:4:"🈑";s:3:"字";s:4:"🈒";s:3:"双";s:4:"🈓";s:6:"デ";s:4:"🈔";s:3:"二";s:4:"🈕";s:3:"多";s:4:"🈖";s:3:"解";s:4:"🈗";s:3:"天";s:4:"🈘";s:3:"交";s:4:"🈙";s:3:"映";s:4:"🈚";s:3:"無";s:4:"🈛";s:3:"料";s:4:"🈜";s:3:"前";s:4:"🈝";s:3:"後";s:4:"🈞";s:3:"再";s:4:"🈟";s:3:"新";s:4:"🈠";s:3:"初";s:4:"🈡";s:3:"終";s:4:"🈢";s:3:"生";s:4:"🈣";s:3:"販";s:4:"🈤";s:3:"声";s:4:"🈥";s:3:"吹";s:4:"🈦";s:3:"演";s:4:"🈧";s:3:"投";s:4:"🈨";s:3:"捕";s:4:"🈩";s:3:"一";s:4:"🈪";s:3:"三";s:4:"🈫";s:3:"遊";s:4:"🈬";s:3:"左";s:4:"🈭";s:3:"中";s:4:"🈮";s:3:"右";s:4:"🈯";s:3:"指";s:4:"🈰";s:3:"走";s:4:"🈱";s:3:"打";s:4:"🈲";s:3:"禁";s:4:"🈳";s:3:"空";s:4:"🈴";s:3:"合";s:4:"🈵";s:3:"満";s:4:"🈶";s:3:"有";s:4:"🈷";s:3:"月";s:4:"🈸";s:3:"申";s:4:"🈹";s:3:"割";s:4:"🈺";s:3:"営";s:4:"🉀";s:9:"〔本〕";s:4:"🉁";s:9:"〔三〕";s:4:"🉂";s:9:"〔二〕";s:4:"🉃";s:9:"〔安〕";s:4:"🉄";s:9:"〔点〕";s:4:"🉅";s:9:"〔打〕";s:4:"🉆";s:9:"〔盗〕";s:4:"🉇";s:9:"〔勝〕";s:4:"🉈";s:9:"〔敗〕";s:4:"🉐";s:3:"得";s:4:"🉑";s:3:"可";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' );
diff --git a/includes/normal/UtfNormalDefines.php b/includes/normal/UtfNormalDefines.php
index d759c64c..6c4d8b76 100644
--- a/includes/normal/UtfNormalDefines.php
+++ b/includes/normal/UtfNormalDefines.php
@@ -1,6 +1,10 @@
<?php
/**
- * Some constant definitions for the unicode normalization module
+ * Some constant definitions for the unicode normalization module.
+ *
+ * Note: these constants must all be resolvable at compile time by HipHop,
+ * since this file will not be executed during request startup for a compiled
+ * MediaWiki.
*
* @file
* @ingroup UtfNormal
diff --git a/includes/normal/UtfNormalGenerate.php b/includes/normal/UtfNormalGenerate.php
index a5e2885a..e4c1138e 100644
--- a/includes/normal/UtfNormalGenerate.php
+++ b/includes/normal/UtfNormalGenerate.php
@@ -29,6 +29,7 @@ if( php_sapi_name() != 'cli' ) {
die( "Run me from the command line please.\n" );
}
+require_once 'UtfNormalDefines.php';
require_once 'UtfNormalUtil.php';
$in = fopen("DerivedNormalizationProps.txt", "rt" );
diff --git a/includes/normal/UtfNormalMemStress.php b/includes/normal/UtfNormalMemStress.php
new file mode 100644
index 00000000..1277dc20
--- /dev/null
+++ b/includes/normal/UtfNormalMemStress.php
@@ -0,0 +1,110 @@
+<?php
+/**
+ * Approximate benchmark for some basic operations.
+ * Runs large chunks of text through cleanup with a lowish memory limit,
+ * to test regression on mem usage (bug 28146)
+ *
+ * Copyright © 2004-2011 Brion Vibber <brion@wikimedia.org>
+ * 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( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
+ dl( 'php_utfnormal.so' );
+}
+
+require_once 'UtfNormalDefines.php';
+require_once 'UtfNormalUtil.php';
+require_once 'UtfNormal.php';
+
+define( 'BENCH_CYCLES', 1 );
+define( 'BIGSIZE', 1024 * 1024 * 10); // 10m
+ini_set('memory_limit', BIGSIZE + 120 * 1024 * 1024);
+
+if( php_sapi_name() != 'cli' ) {
+ die( "Run me from the command line please.\n" );
+}
+
+$testfiles = array(
+ 'testdata/washington.txt' => 'English text',
+ 'testdata/berlin.txt' => 'German text',
+ 'testdata/bulgakov.txt' => 'Russian text',
+ 'testdata/tokyo.txt' => 'Japanese text',
+ 'testdata/young.txt' => 'Korean text'
+);
+$normalizer = new UtfNormal;
+UtfNormal::loadData();
+foreach( $testfiles as $file => $desc ) {
+ benchmarkTest( $normalizer, $file, $desc );
+}
+
+# -------
+
+function benchmarkTest( &$u, $filename, $desc ) {
+ print "Testing $filename ($desc)...\n";
+ $data = file_get_contents( $filename );
+ $all = $data;
+ while (strlen($all) < BIGSIZE) {
+ $all .= $all;
+ }
+ $data = $all;
+ echo "Data is " . strlen($data) . " bytes.\n";
+ $forms = array(
+ 'quickIsNFCVerify',
+ 'cleanUp',
+ );
+ foreach( $forms as $form ) {
+ if( is_array( $form ) ) {
+ $str = $data;
+ foreach( $form as $step ) {
+ $str = benchmarkForm( $u, $str, $step );
+ }
+ } else {
+ benchmarkForm( $u, $data, $form );
+ }
+ }
+}
+
+function benchTime(){
+ $st = explode( ' ', microtime() );
+ return (float)$st[0] + (float)$st[1];
+}
+
+function benchmarkForm( &$u, &$data, $form ) {
+ #$start = benchTime();
+ for( $i = 0; $i < BENCH_CYCLES; $i++ ) {
+ $start = benchTime();
+ $out = $u->$form( $data, UtfNormal::$utfCanonicalDecomp );
+ $deltas[] = (benchTime() - $start);
+ }
+ #$delta = (benchTime() - $start) / BENCH_CYCLES;
+ sort( $deltas );
+ $delta = $deltas[0]; # Take shortest time
+
+ $rate = intval( strlen( $data ) / $delta );
+ $same = (0 == strcmp( $data, $out ) );
+
+ printf( " %20s %6.1fms %12s bytes/s (%s)\n",
+ $form,
+ $delta*1000.0,
+ number_format( $rate ),
+ ($same ? 'no change' : 'changed' ) );
+ return $out;
+}
diff --git a/includes/normal/UtfNormalTest.php b/includes/normal/UtfNormalTest.php
index f78775ce..e5ae7f72 100644
--- a/includes/normal/UtfNormalTest.php
+++ b/includes/normal/UtfNormalTest.php
@@ -49,6 +49,7 @@ if( isset( $_SERVER['argv'] ) && in_array( '--icu', $_SERVER['argv'] ) ) {
dl( 'php_utfnormal.so' );
}
+require_once 'UtfNormalDefines.php';
require_once 'UtfNormalUtil.php';
require_once 'UtfNormal.php';
diff --git a/includes/normal/UtfNormalTest2.php b/includes/normal/UtfNormalTest2.php
index fafd5475..28be4838 100644
--- a/includes/normal/UtfNormalTest2.php
+++ b/includes/normal/UtfNormalTest2.php
@@ -194,12 +194,12 @@ echo "done.\n";
function unichr($c) {
if ($c <= 0x7F) {
return chr($c);
- } else if ($c <= 0x7FF) {
+ } elseif ($c <= 0x7FF) {
return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
- } else if ($c <= 0xFFFF) {
+ } elseif ($c <= 0xFFFF) {
return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
. chr(0x80 | $c & 0x3F);
- } else if ($c <= 0x10FFFF) {
+ } elseif ($c <= 0x10FFFF) {
return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
. chr(0x80 | $c >> 6 & 0x3F)
. chr(0x80 | $c & 0x3F);
diff --git a/includes/normal/UtfNormalUtil.php b/includes/normal/UtfNormalUtil.php
index 0c78e5ec..bfad7095 100644
--- a/includes/normal/UtfNormalUtil.php
+++ b/includes/normal/UtfNormalUtil.php
@@ -25,8 +25,6 @@
* @ingroup UtfNormal
*/
-require_once dirname(__FILE__).'/UtfNormalDefines.php';
-
/**
* Return UTF-8 sequence for a given Unicode code point.
* May die if fed out of range data.
@@ -93,7 +91,7 @@ function utf8ToHexSequence( $str ) {
*/
function utf8ToCodepoint( $char ) {
# Find the length
- $z = ord( $char{0} );
+ $z = ord( $char[0] );
if ( $z & 0x80 ) {
$length = 0;
while ( $z & 0x80 ) {
@@ -118,7 +116,7 @@ function utf8ToCodepoint( $char ) {
# Add in the free bits from subsequent bytes
for ( $i=1; $i<$length; $i++ ) {
$z <<= 6;
- $z |= ord( $char{$i} ) & 0x3f;
+ $z |= ord( $char[$i] ) & 0x3f;
}
return $z;
diff --git a/includes/objectcache/APCBagOStuff.php b/includes/objectcache/APCBagOStuff.php
new file mode 100644
index 00000000..dd4a76e1
--- /dev/null
+++ b/includes/objectcache/APCBagOStuff.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * This is a wrapper for APC's shared memory functions
+ *
+ * @ingroup Cache
+ */
+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;
+ }
+
+ public function keys() {
+ $info = apc_cache_info( 'user' );
+ $list = $info['cache_list'];
+ $keys = array();
+
+ foreach ( $list as $entry ) {
+ $keys[] = $entry['info'];
+ }
+
+ return $keys;
+ }
+}
+
diff --git a/includes/objectcache/BagOStuff.php b/includes/objectcache/BagOStuff.php
new file mode 100644
index 00000000..97b6cb2c
--- /dev/null
+++ b/includes/objectcache/BagOStuff.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * 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.
+ *
+ * backends for local hash array and SQL table included:
+ * <code>
+ * $bag = new HashBagOStuff();
+ * $bag = new SqlBagOStuff(); # connect to db first
+ * </code>
+ *
+ * @ingroup Cache
+ */
+abstract class BagOStuff {
+ private $debugMode = false;
+
+ /**
+ * @param $bool bool
+ */
+ public function setDebug( $bool ) {
+ $this->debugMode = $bool;
+ }
+
+ /* *** THE GUTS OF THE OPERATION *** */
+ /* Override these with functional things in subclasses */
+
+ /**
+ * Get an item with the given key. Returns false if it does not exist.
+ * @param $key string
+ *
+ * @return bool|Object
+ */
+ abstract public function get( $key );
+
+ /**
+ * Set an item.
+ * @param $key string
+ * @param $value mixed
+ * @param $exptime int Either an interval in seconds or a unix timestamp for expiry
+ */
+ abstract public function set( $key, $value, $exptime = 0 );
+
+ /**
+ * Delete an item.
+ * @param $key string
+ * @param $time int Amount of time to delay the operation (mostly memcached-specific)
+ */
+ abstract public function delete( $key, $time = 0 );
+
+ public function lock( $key, $timeout = 0 ) {
+ /* stub */
+ return true;
+ }
+
+ public function unlock( $key ) {
+ /* stub */
+ return true;
+ }
+
+ public function keys() {
+ /* stub */
+ return array();
+ }
+
+ /**
+ * Delete all objects expiring before a certain date.
+ *
+ * @return true on success, false if unimplemented
+ */
+ public function deleteObjectsExpiringBefore( $date ) {
+ // stub
+ return false;
+ }
+
+ /* *** Emulated functions *** */
+
+ public function add( $key, $value, $exptime = 0 ) {
+ if ( !$this->get( $key ) ) {
+ $this->set( $key, $value, $exptime );
+
+ return true;
+ }
+ }
+
+ public function replace( $key, $value, $exptime = 0 ) {
+ if ( $this->get( $key ) !== false ) {
+ $this->set( $key, $value, $exptime );
+ }
+ }
+
+ /**
+ * @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 null;
+ }
+
+ $value = intval( $value );
+
+ if ( ( $n = $this->get( $key ) ) !== false ) {
+ $n += $value;
+ $this->set( $key, $n ); // exptime?
+ }
+ $this->unlock( $key );
+
+ return $n;
+ }
+
+ public function decr( $key, $value = 1 ) {
+ return $this->incr( $key, - $value );
+ }
+
+ public function debug( $text ) {
+ if ( $this->debugMode ) {
+ $class = get_class( $this );
+ wfDebug( "$class debug: $text\n" );
+ }
+ }
+
+ /**
+ * Convert an optionally relative time to an absolute time
+ */
+ protected function convertExpiry( $exptime ) {
+ if ( ( $exptime != 0 ) && ( $exptime < 86400 * 3650 /* 10 years */ ) ) {
+ return time() + $exptime;
+ } else {
+ return $exptime;
+ }
+ }
+}
+
+
diff --git a/includes/objectcache/DBABagOStuff.php b/includes/objectcache/DBABagOStuff.php
new file mode 100644
index 00000000..783cd22b
--- /dev/null
+++ b/includes/objectcache/DBABagOStuff.php
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * Cache that uses DBA as a backend.
+ * Slow due to the need to constantly open and close the file to avoid holding
+ * writer locks. Intended for development use only, as a memcached workalike
+ * for systems that don't have it.
+ *
+ * @ingroup Cache
+ */
+class DBABagOStuff extends BagOStuff {
+ var $mHandler, $mFile, $mReader, $mWriter, $mDisabled;
+
+ 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" );
+ $this->mHandler = $wgDBAhandler;
+ }
+
+ /**
+ * Encode value and expiry for storage
+ * @param $value
+ * @param $expiry
+ *
+ * @return string
+ */
+ function encode( $value, $expiry ) {
+ # Convert to absolute time
+ $expiry = $this->convertExpiry( $expiry );
+
+ return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value );
+ }
+
+ /**
+ * @return array list containing value first and expiry second
+ */
+ function decode( $blob ) {
+ if ( !is_string( $blob ) ) {
+ return array( null, 0 );
+ } else {
+ return array(
+ unserialize( substr( $blob, 11 ) ),
+ intval( substr( $blob, 0, 10 ) )
+ );
+ }
+ }
+
+ function getReader() {
+ if ( file_exists( $this->mFile ) ) {
+ $handle = dba_open( $this->mFile, 'rl', $this->mHandler );
+ } else {
+ $handle = $this->getWriter();
+ }
+
+ if ( !$handle ) {
+ wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
+ }
+
+ return $handle;
+ }
+
+ 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 );
+
+ if ( !is_null( $val ) && $expiry && $expiry < time() ) {
+ # Key is expired, delete it
+ $handle = $this->getWriter();
+ dba_delete( $key, $handle );
+ dba_close( $handle );
+ wfDebug( __METHOD__ . ": $key expired\n" );
+ $val = null;
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $val;
+ }
+
+ 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;
+ }
+
+ 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 );
+ $ret = dba_insert( $key, $blob, $handle );
+ # This time if it failed then it will be handled by the caller like any other race
+ }
+ }
+
+ dba_close( $handle );
+
+ wfProfileOut( __METHOD__ );
+ return $ret;
+ }
+
+ function keys() {
+ $reader = $this->getReader();
+ $k1 = dba_firstkey( $reader );
+
+ if ( !$k1 ) {
+ return array();
+ }
+
+ $result[] = $k1;
+
+ while ( $key = dba_nextkey( $reader ) ) {
+ $result[] = $key;
+ }
+
+ return $result;
+ }
+}
+
diff --git a/includes/objectcache/EhcacheBagOStuff.php b/includes/objectcache/EhcacheBagOStuff.php
new file mode 100644
index 00000000..75aad27a
--- /dev/null
+++ b/includes/objectcache/EhcacheBagOStuff.php
@@ -0,0 +1,230 @@
+<?php
+
+/**
+ * Client for the Ehcache RESTful web service - http://ehcache.org/documentation/cache_server.html
+ * TODO: Simplify configuration and add to the installer.
+ */
+class EhcacheBagOStuff extends BagOStuff {
+ var $servers, $cacheName, $connectTimeout, $timeout, $curlOptions,
+ $requestData, $requestDataPos;
+
+ var $curls = array();
+
+ function __construct( $params ) {
+ if ( !defined( 'CURLOPT_TIMEOUT_MS' ) ) {
+ throw new MWException( __CLASS__.' requires curl version 7.16.2 or later.' );
+ }
+ if ( !extension_loaded( 'zlib' ) ) {
+ throw new MWException( __CLASS__.' requires the zlib extension' );
+ }
+ if ( !isset( $params['servers'] ) ) {
+ throw new MWException( __METHOD__.': servers parameter is required' );
+ }
+ $this->servers = $params['servers'];
+ $this->cacheName = isset( $params['cache'] ) ? $params['cache'] : 'mw';
+ $this->connectTimeout = isset( $params['connectTimeout'] )
+ ? $params['connectTimeout'] : 1;
+ $this->timeout = isset( $params['timeout'] ) ? $params['timeout'] : 1;
+ $this->curlOptions = array(
+ CURLOPT_CONNECTTIMEOUT_MS => intval( $this->connectTimeout * 1000 ),
+ CURLOPT_TIMEOUT_MS => intval( $this->timeout * 1000 ),
+ CURLOPT_RETURNTRANSFER => 1,
+ CURLOPT_CUSTOMREQUEST => 'GET',
+ CURLOPT_POST => 0,
+ CURLOPT_POSTFIELDS => '',
+ CURLOPT_HTTPHEADER => array(),
+ );
+ }
+
+ public function get( $key ) {
+ wfProfileIn( __METHOD__ );
+ $response = $this->doItemRequest( $key );
+ if ( !$response || $response['http_code'] == 404 ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ if ( $response['http_code'] >= 300 ) {
+ wfDebug( __METHOD__.": GET failure, got HTTP {$response['http_code']}\n" );
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $body = $response['body'];
+ $type = $response['content_type'];
+ if ( $type == 'application/vnd.php.serialized+deflate' ) {
+ $body = gzinflate( $body );
+ if ( !$body ) {
+ wfDebug( __METHOD__.": error inflating $key\n" );
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $data = unserialize( $body );
+ } elseif ( $type == 'application/vnd.php.serialized' ) {
+ $data = unserialize( $body );
+ } else {
+ wfDebug( __METHOD__.": unknown content type \"$type\"\n" );
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $data;
+ }
+
+ public function set( $key, $value, $expiry = 0 ) {
+ wfProfileIn( __METHOD__ );
+ $expiry = $this->convertExpiry( $expiry );
+ $ttl = $expiry ? $expiry - time() : 2147483647;
+ $blob = serialize( $value );
+ if ( strlen( $blob ) > 100 ) {
+ $blob = gzdeflate( $blob );
+ $contentType = 'application/vnd.php.serialized+deflate';
+ } else {
+ $contentType = 'application/vnd.php.serialized';
+ }
+
+ $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
+
+ if ( $code == 404 ) {
+ // Maybe the cache does not exist yet, let's try creating it
+ if ( !$this->createCache( $key ) ) {
+ wfDebug( __METHOD__.": cache creation failed\n" );
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $code = $this->attemptPut( $key, $blob, $contentType, $ttl );
+ }
+
+ $result = false;
+ if ( !$code ) {
+ wfDebug( __METHOD__.": PUT failure for key $key\n" );
+ } elseif ( $code >= 300 ) {
+ wfDebug( __METHOD__.": PUT failure for key $key: HTTP $code\n" );
+ } else {
+ $result = true;
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $result;
+ }
+
+ public function delete( $key, $time = 0 ) {
+ wfProfileIn( __METHOD__ );
+ $response = $this->doItemRequest( $key,
+ array( CURLOPT_CUSTOMREQUEST => 'DELETE' ) );
+ $code = isset( $response['http_code'] ) ? $response['http_code'] : 0;
+ if ( !$response || ( $code != 404 && $code >= 300 ) ) {
+ wfDebug( __METHOD__.": DELETE failure for key $key\n" );
+ $result = false;
+ } else {
+ $result = true;
+ }
+ wfProfileOut( __METHOD__ );
+ return $result;
+ }
+
+ protected function getCacheUrl( $key ) {
+ if ( count( $this->servers ) == 1 ) {
+ $server = reset( $this->servers );
+ } else {
+ // Use consistent hashing
+ $hashes = array();
+ foreach ( $this->servers as $server ) {
+ $hashes[$server] = md5( $server . '/' . $key );
+ }
+ asort( $hashes );
+ reset( $hashes );
+ $server = key( $hashes );
+ }
+ return "http://$server/ehcache/rest/{$this->cacheName}";
+ }
+
+ /**
+ * Get a cURL handle for the given cache URL.
+ * We cache the handles to allow keepalive.
+ */
+ protected function getCurl( $cacheUrl ) {
+ if ( !isset( $this->curls[$cacheUrl] ) ) {
+ $this->curls[$cacheUrl] = curl_init();
+ }
+ return $this->curls[$cacheUrl];
+ }
+
+ protected function attemptPut( $key, $data, $type, $ttl ) {
+ // In initial benchmarking, it was 30 times faster to use CURLOPT_POST
+ // than CURLOPT_UPLOAD with CURLOPT_READFUNCTION. This was because
+ // CURLOPT_UPLOAD was pushing the request headers first, then waiting
+ // for an ACK packet, then sending the data, whereas CURLOPT_POST just
+ // sends the headers and the data in a single send().
+ $response = $this->doItemRequest( $key,
+ array(
+ CURLOPT_POST => 1,
+ CURLOPT_CUSTOMREQUEST => 'PUT',
+ CURLOPT_POSTFIELDS => $data,
+ CURLOPT_HTTPHEADER => array(
+ 'Content-Type: ' . $type,
+ 'ehcacheTimeToLiveSeconds: ' . $ttl
+ )
+ )
+ );
+ if ( !$response ) {
+ return 0;
+ } else {
+ return $response['http_code'];
+ }
+ }
+
+ protected function createCache( $key ) {
+ wfDebug( __METHOD__.": creating cache for $key\n" );
+ $response = $this->doCacheRequest( $key,
+ array(
+ CURLOPT_POST => 1,
+ CURLOPT_CUSTOMREQUEST => 'PUT',
+ CURLOPT_POSTFIELDS => '',
+ ) );
+ if ( !$response ) {
+ wfDebug( __CLASS__.": failed to create cache for $key\n" );
+ return false;
+ }
+ if ( $response['http_code'] == 201 /* created */
+ || $response['http_code'] == 409 /* already there */ )
+ {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected function doCacheRequest( $key, $curlOptions = array() ) {
+ $cacheUrl = $this->getCacheUrl( $key );
+ $curl = $this->getCurl( $cacheUrl );
+ return $this->doRequest( $curl, $cacheUrl, $curlOptions );
+ }
+
+ protected function doItemRequest( $key, $curlOptions = array() ) {
+ $cacheUrl = $this->getCacheUrl( $key );
+ $curl = $this->getCurl( $cacheUrl );
+ $url = $cacheUrl . '/' . rawurlencode( $key );
+ return $this->doRequest( $curl, $url, $curlOptions );
+ }
+
+ protected function doRequest( $curl, $url, $curlOptions = array() ) {
+ if ( array_diff_key( $curlOptions, $this->curlOptions ) ) {
+ // var_dump( array_diff_key( $curlOptions, $this->curlOptions ) );
+ throw new MWException( __METHOD__.": to prevent options set in one doRequest() " .
+ "call from affecting subsequent doRequest() calls, only options listed " .
+ "in \$this->curlOptions may be specified in the \$curlOptions parameter." );
+ }
+ $curlOptions += $this->curlOptions;
+ $curlOptions[CURLOPT_URL] = $url;
+
+ curl_setopt_array( $curl, $curlOptions );
+ $result = curl_exec( $curl );
+ if ( $result === false ) {
+ wfDebug( __CLASS__.": curl error: " . curl_error( $curl ) . "\n" );
+ return false;
+ }
+ $info = curl_getinfo( $curl );
+ $info['body'] = $result;
+ return $info;
+ }
+}
diff --git a/includes/objectcache/EmptyBagOStuff.php b/includes/objectcache/EmptyBagOStuff.php
new file mode 100644
index 00000000..e956e2ee
--- /dev/null
+++ b/includes/objectcache/EmptyBagOStuff.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * A BagOStuff object with no objects in it. Used to provide a no-op object to calling code.
+ *
+ * @ingroup Cache
+ */
+class EmptyBagOStuff extends BagOStuff {
+ function get( $key ) {
+ return false;
+ }
+
+ function set( $key, $value, $exp = 0 ) {
+ return true;
+ }
+
+ function delete( $key, $time = 0 ) {
+ return true;
+ }
+}
+
+/**
+ * Backwards compatibility alias for EmptyBagOStuff
+ * @deprecated since 1.18
+ */
+class FakeMemCachedClient extends EmptyBagOStuff {
+}
diff --git a/includes/objectcache/HashBagOStuff.php b/includes/objectcache/HashBagOStuff.php
new file mode 100644
index 00000000..36773306
--- /dev/null
+++ b/includes/objectcache/HashBagOStuff.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * This is a test of the interface, mainly. It stores things in an associative
+ * array, which is not going to persist between program runs.
+ *
+ * @ingroup Cache
+ */
+class HashBagOStuff extends BagOStuff {
+ var $bag;
+
+ function __construct() {
+ $this->bag = array();
+ }
+
+ protected function expire( $key ) {
+ $et = $this->bag[$key][1];
+
+ if ( ( $et == 0 ) || ( $et > time() ) ) {
+ return false;
+ }
+
+ $this->delete( $key );
+
+ return true;
+ }
+
+ function get( $key ) {
+ if ( !isset( $this->bag[$key] ) ) {
+ return false;
+ }
+
+ if ( $this->expire( $key ) ) {
+ return false;
+ }
+
+ return $this->bag[$key][0];
+ }
+
+ function set( $key, $value, $exptime = 0 ) {
+ $this->bag[$key] = array( $value, $this->convertExpiry( $exptime ) );
+ }
+
+ function delete( $key, $time = 0 ) {
+ if ( !isset( $this->bag[$key] ) ) {
+ return false;
+ }
+
+ unset( $this->bag[$key] );
+
+ return true;
+ }
+
+ function keys() {
+ return array_keys( $this->bag );
+ }
+}
+
diff --git a/includes/memcached-client.php b/includes/objectcache/MemcachedClient.php
index 53f0324f..dd4401a8 100644
--- a/includes/memcached-client.php
+++ b/includes/objectcache/MemcachedClient.php
@@ -51,7 +51,7 @@
* '127.0.0.1:10020'),
* 'debug' => false,
* 'compress_threshold' => 10240,
- * 'persistant' => true));
+ * 'persistent' => true));
*
* $mc->add('key', array('some', 'array'));
* $mc->replace('key', 'some random string');
@@ -158,12 +158,12 @@ class MWMemcached {
var $_compress_threshold;
/**
- * Are we using persistant links?
+ * Are we using persistent links?
*
* @var boolean
* @access private
*/
- var $_persistant;
+ var $_persistent;
/**
* If only using one server; contains ip:port to connect to
@@ -245,12 +245,11 @@ class MWMemcached {
* @return mixed
*/
public function __construct( $args ) {
- global $wgMemCachedTimeout;
$this->set_servers( isset( $args['servers'] ) ? $args['servers'] : array() );
$this->_debug = isset( $args['debug'] ) ? $args['debug'] : false;
$this->stats = array();
$this->_compress_threshold = isset( $args['compress_threshold'] ) ? $args['compress_threshold'] : 0;
- $this->_persistant = isset( $args['persistant'] ) ? $args['persistant'] : false;
+ $this->_persistent = isset( $args['persistent'] ) ? $args['persistent'] : false;
$this->_compress_enable = true;
$this->_have_zlib = function_exists( 'gzcompress' );
@@ -258,9 +257,9 @@ class MWMemcached {
$this->_host_dead = array();
$this->_timeout_seconds = 0;
- $this->_timeout_microseconds = $wgMemCachedTimeout;
+ $this->_timeout_microseconds = isset( $args['timeout'] ) ? $args['timeout'] : 100000;
- $this->_connect_timeout = 0.01;
+ $this->_connect_timeout = isset( $args['connect_timeout'] ) ? $args['connect_timeout'] : 0.1;
$this->_connect_attempts = 2;
}
@@ -345,6 +344,16 @@ class MWMemcached {
return false;
}
+ public function lock( $key, $timeout = 0 ) {
+ /* stub */
+ return true;
+ }
+
+ public function unlock( $key ) {
+ /* stub */
+ return true;
+ }
+
// }}}
// {{{ disconnect_all()
@@ -432,8 +441,12 @@ class MWMemcached {
}
}
+ $value = false;
+ if ( isset( $val[$key] ) ) {
+ $value = $val[$key];
+ }
wfProfileOut( __METHOD__ );
- return @$val[$key];
+ return $value;
}
// }}}
@@ -551,7 +564,7 @@ class MWMemcached {
* with a \n. This is with the PHP flag auto_detect_line_endings set
* to falase (the default).
*
- * @param $sock Ressource: socket to send command on
+ * @param $sock Resource: socket to send command on
* @param $cmd String: command to run
*
* @return Array: output array
@@ -695,7 +708,7 @@ class MWMemcached {
$errno = $errstr = null;
for( $i = 0; !$sock && $i < $this->_connect_attempts; $i++ ) {
wfSuppressWarnings();
- if ( $this->_persistant == 1 ) {
+ if ( $this->_persistent == 1 ) {
$sock = pfsockopen( $ip, $port, $errno, $errstr, $timeout );
} else {
$sock = fsockopen( $ip, $port, $errno, $errstr, $timeout );
@@ -854,7 +867,7 @@ class MWMemcached {
/**
* Load items into $ret from $sock
*
- * @param $sock Ressource: socket to read from
+ * @param $sock Resource: socket to read from
* @param $ret Array: returned values
*
* @access private
@@ -945,6 +958,12 @@ class MWMemcached {
} else {
$this->stats[$cmd] = 1;
}
+
+ // Memcached doesn't seem to handle very high TTL values very well,
+ // so clamp them at 30 days
+ if ( $exp > 2592000 ) {
+ $exp = 2592000;
+ }
$flags = 0;
diff --git a/includes/objectcache/MemcachedPhpBagOStuff.php b/includes/objectcache/MemcachedPhpBagOStuff.php
new file mode 100644
index 00000000..14016683
--- /dev/null
+++ b/includes/objectcache/MemcachedPhpBagOStuff.php
@@ -0,0 +1,178 @@
+<?php
+
+/**
+ * A wrapper class for the pure-PHP memcached client, exposing a BagOStuff interface.
+ */
+class MemcachedPhpBagOStuff extends BagOStuff {
+
+ /**
+ * @var MemCachedClientforWiki
+ */
+ protected $client;
+
+ /**
+ * Constructor.
+ *
+ * Available parameters are:
+ * - servers: The list of IP:port combinations holding the memcached servers.
+ * - debug: Whether to set the debug flag in the underlying client.
+ * - persistent: Whether to use a persistent connection
+ * - compress_threshold: The minimum size an object must be before it is compressed
+ * - timeout: The read timeout in microseconds
+ * - connect_timeout: The connect timeout in seconds
+ *
+ * @param $params array
+ */
+ function __construct( $params ) {
+ if ( !isset( $params['servers'] ) ) {
+ $params['servers'] = $GLOBALS['wgMemCachedServers'];
+ }
+ if ( !isset( $params['debug'] ) ) {
+ $params['debug'] = $GLOBALS['wgMemCachedDebug'];
+ }
+ if ( !isset( $params['persistent'] ) ) {
+ $params['persistent'] = $GLOBALS['wgMemCachedPersistent'];
+ }
+ if ( !isset( $params['compress_threshold'] ) ) {
+ $params['compress_threshold'] = 1500;
+ }
+ if ( !isset( $params['timeout'] ) ) {
+ $params['timeout'] = $GLOBALS['wgMemCachedTimeout'];
+ }
+ if ( !isset( $params['connect_timeout'] ) ) {
+ $params['connect_timeout'] = 0.1;
+ }
+
+ $this->client = new MemCachedClientforWiki( $params );
+ $this->client->set_servers( $params['servers'] );
+ $this->client->set_debug( $params['debug'] );
+ }
+
+ /**
+ * @param $debug bool
+ */
+ public function setDebug( $debug ) {
+ $this->client->set_debug( $debug );
+ }
+
+ /**
+ * @param $key string
+ * @return Mixed
+ */
+ public function get( $key ) {
+ return $this->client->get( $this->encodeKey( $key ) );
+ }
+
+ /**
+ * @param $key string
+ * @param $value
+ * @param $exptime int
+ * @return bool
+ */
+ public function set( $key, $value, $exptime = 0 ) {
+ return $this->client->set( $this->encodeKey( $key ), $value, $exptime );
+ }
+
+ /**
+ * @param $key string
+ * @param $time int
+ * @return bool
+ */
+ public function delete( $key, $time = 0 ) {
+ return $this->client->delete( $this->encodeKey( $key ), $time );
+ }
+
+ /**
+ * @param $key
+ * @param $timeout int
+ * @return
+ */
+ public function lock( $key, $timeout = 0 ) {
+ return $this->client->lock( $this->encodeKey( $key ), $timeout );
+ }
+
+ /**
+ * @param $key string
+ * @return Mixed
+ */
+ public function unlock( $key ) {
+ return $this->client->unlock( $this->encodeKey( $key ) );
+ }
+
+ /**
+ * @param $key string
+ * @param $value int
+ * @return Mixed
+ */
+ public function add( $key, $value, $exptime = 0 ) {
+ return $this->client->add( $this->encodeKey( $key ), $value, $exptime );
+ }
+
+ /**
+ * @param $key string
+ * @param $value int
+ * @param $exptime
+ * @return Mixed
+ */
+ public function replace( $key, $value, $exptime = 0 ) {
+ return $this->client->replace( $this->encodeKey( $key ), $value, $exptime );
+ }
+
+ /**
+ * @param $key string
+ * @param $value int
+ * @return Mixed
+ */
+ public function incr( $key, $value = 1 ) {
+ return $this->client->incr( $this->encodeKey( $key ), $value );
+ }
+
+ /**
+ * @param $key string
+ * @param $value int
+ * @return Mixed
+ */
+ public function decr( $key, $value = 1 ) {
+ return $this->client->decr( $this->encodeKey( $key ), $value );
+ }
+
+ /**
+ * Get the underlying client object. This is provided for debugging
+ * purposes.
+ *
+ * @return MemCachedClientforWiki
+ */
+ public function getClient() {
+ return $this->client;
+ }
+
+ /**
+ * Encode a key for use on the wire inside the memcached protocol.
+ *
+ * We encode spaces and line breaks to avoid protocol errors. We encode
+ * the other control characters for compatibility with libmemcached
+ * verify_key. We leave other punctuation alone, to maximise backwards
+ * compatibility.
+ */
+ public function encodeKey( $key ) {
+ return preg_replace_callback( '/[\x00-\x20\x25\x7f]+/',
+ array( $this, 'encodeKeyCallback' ), $key );
+ }
+
+ protected function encodeKeyCallback( $m ) {
+ return rawurlencode( $m[0] );
+ }
+
+ /**
+ * Decode a key encoded with encodeKey(). This is provided as a convenience
+ * function for debugging.
+ *
+ * @param $key string
+ *
+ * @return string
+ */
+ public function decodeKey( $key ) {
+ return urldecode( $key );
+ }
+}
+
diff --git a/includes/objectcache/MultiWriteBagOStuff.php b/includes/objectcache/MultiWriteBagOStuff.php
new file mode 100644
index 00000000..2b88b427
--- /dev/null
+++ b/includes/objectcache/MultiWriteBagOStuff.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * A cache class that replicates all writes to multiple child caches. Reads
+ * are implemented by reading from the caches in the order they are given in
+ * the configuration until a cache gives a positive result.
+ */
+class MultiWriteBagOStuff extends BagOStuff {
+ var $caches;
+
+ /**
+ * Constructor. Parameters are:
+ *
+ * - caches: This should have a numbered array of cache parameter
+ * structures, in the style required by $wgObjectCaches. See
+ * the documentation of $wgObjectCaches for more detail.
+ *
+ * @param $params array
+ */
+ public function __construct( $params ) {
+ if ( !isset( $params['caches'] ) ) {
+ throw new MWException( __METHOD__.': the caches parameter is required' );
+ }
+
+ $this->caches = array();
+ foreach ( $params['caches'] as $cacheInfo ) {
+ $this->caches[] = ObjectCache::newFromParams( $cacheInfo );
+ }
+ }
+
+ public function setDebug( $debug ) {
+ $this->doWrite( 'setDebug', $debug );
+ }
+
+ public function get( $key ) {
+ foreach ( $this->caches as $cache ) {
+ $value = $cache->get( $key );
+ if ( $value !== false ) {
+ return $value;
+ }
+ }
+ return false;
+ }
+
+ public function set( $key, $value, $exptime = 0 ) {
+ return $this->doWrite( 'set', $key, $value, $exptime );
+ }
+
+ public function delete( $key, $time = 0 ) {
+ return $this->doWrite( 'delete', $key, $time );
+ }
+
+ public function add( $key, $value, $exptime = 0 ) {
+ return $this->doWrite( 'add', $key, $value, $exptime );
+ }
+
+ public function replace( $key, $value, $exptime = 0 ) {
+ return $this->doWrite( 'replace', $key, $value, $exptime );
+ }
+
+ public function incr( $key, $value = 1 ) {
+ return $this->doWrite( 'incr', $key, $value );
+ }
+
+ public function decr( $key, $value = 1 ) {
+ return $this->doWrite( 'decr', $key, $value );
+ }
+
+ public function lock( $key, $timeout = 0 ) {
+ // Lock only the first cache, to avoid deadlocks
+ if ( isset( $this->caches[0] ) ) {
+ return $this->caches[0]->lock( $key, $timeout );
+ } else {
+ return true;
+ }
+ }
+
+ public function unlock( $key ) {
+ if ( isset( $this->caches[0] ) ) {
+ return $this->caches[0]->unlock( $key );
+ } else {
+ return true;
+ }
+ }
+
+ protected function doWrite( $method /*, ... */ ) {
+ $ret = true;
+ $args = func_get_args();
+ array_shift( $args );
+
+ foreach ( $this->caches as $cache ) {
+ if ( !call_user_func_array( array( $cache, $method ), $args ) ) {
+ $ret = false;
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * Delete objects expiring before a certain date.
+ *
+ * Succeed if any of the child caches succeed.
+ */
+ public function deleteObjectsExpiringBefore( $date ) {
+ $ret = false;
+ foreach ( $this->caches as $cache ) {
+ if ( $cache->deleteObjectsExpiringBefore( $date ) ) {
+ $ret = true;
+ }
+ }
+ return $ret;
+ }
+}
diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php
new file mode 100644
index 00000000..99e38953
--- /dev/null
+++ b/includes/objectcache/ObjectCache.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * Functions to get cache objects
+ *
+ * @file
+ * @ingroup Cache
+ */
+class ObjectCache {
+ static $instances = array();
+
+ /**
+ * Get a cached instance of the specified type of cache object.
+ *
+ * @param $id
+ *
+ * @return object
+ */
+ static function getInstance( $id ) {
+ if ( isset( self::$instances[$id] ) ) {
+ return self::$instances[$id];
+ }
+
+ $object = self::newFromId( $id );
+ self::$instances[$id] = $object;
+ return $object;
+ }
+
+ /**
+ * Clear all the cached instances.
+ */
+ static function clear() {
+ self::$instances = array();
+ }
+
+ /**
+ * Create a new cache object of the specified type.
+ *
+ * @param $id
+ *
+ * @return ObjectCache
+ */
+ static function newFromId( $id ) {
+ global $wgObjectCaches;
+
+ if ( !isset( $wgObjectCaches[$id] ) ) {
+ throw new MWException( "Invalid object cache type \"$id\" requested. " .
+ "It is not present in \$wgObjectCaches." );
+ }
+
+ return self::newFromParams( $wgObjectCaches[$id] );
+ }
+
+ /**
+ * Create a new cache object from parameters
+ *
+ * @param $params array
+ *
+ * @return ObjectCache
+ */
+ static function newFromParams( $params ) {
+ if ( isset( $params['factory'] ) ) {
+ return call_user_func( $params['factory'], $params );
+ } elseif ( isset( $params['class'] ) ) {
+ $class = $params['class'];
+ return new $class( $params );
+ } else {
+ throw new MWException( "The definition of cache type \"" . print_r( $params, true ) . "\" lacks both " .
+ "factory and class parameters." );
+ }
+ }
+
+ /**
+ * Factory function referenced from DefaultSettings.php for CACHE_ANYTHING
+ */
+ static function newAnything( $params ) {
+ global $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType;
+ $candidates = array( $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType );
+ foreach ( $candidates as $candidate ) {
+ if ( $candidate !== CACHE_NONE && $candidate !== CACHE_ANYTHING ) {
+ return self::getInstance( $candidate );
+ }
+ }
+ return self::getInstance( CACHE_DB );
+ }
+
+ /**
+ * Factory function referenced from DefaultSettings.php for CACHE_ACCEL.
+ *
+ * @return ObjectCache
+ */
+ static function newAccelerator( $params ) {
+ if ( function_exists( 'eaccelerator_get' ) ) {
+ $id = 'eaccelerator';
+ } elseif ( function_exists( 'apc_fetch') ) {
+ $id = 'apc';
+ } elseif( function_exists( 'xcache_get' ) && wfIniGetBool( 'xcache.var_size' ) ) {
+ $id = 'xcache';
+ } elseif( function_exists( 'wincache_ucache_get' ) ) {
+ $id = 'wincache';
+ } else {
+ throw new MWException( "CACHE_ACCEL requested but no suitable object " .
+ "cache is present. You may want to install APC." );
+ }
+ return self::newFromId( $id );
+ }
+
+ /**
+ * Factory function that creates a memcached client object.
+ * The idea of this is that it might eventually detect and automatically
+ * support the PECL extension, assuming someone can get it to compile.
+ *
+ * @param $params array
+ *
+ * @return MemcachedPhpBagOStuff
+ */
+ static function newMemcached( $params ) {
+ return new MemcachedPhpBagOStuff( $params );
+ }
+}
diff --git a/includes/objectcache/SqlBagOStuff.php b/includes/objectcache/SqlBagOStuff.php
new file mode 100644
index 00000000..78817d0b
--- /dev/null
+++ b/includes/objectcache/SqlBagOStuff.php
@@ -0,0 +1,432 @@
+<?php
+
+/**
+ * Class to store objects in the database
+ *
+ * @ingroup Cache
+ */
+class SqlBagOStuff extends BagOStuff {
+
+ /**
+ * @var LoadBalancer
+ */
+ var $lb;
+
+ /**
+ * @var DatabaseBase
+ */
+ var $db;
+ var $serverInfo;
+ var $lastExpireAll = 0;
+ var $purgePeriod = 100;
+ var $shards = 1;
+ var $tableName = 'objectcache';
+
+ /**
+ * Constructor. Parameters are:
+ * - server: A server info structure in the format required by each
+ * element in $wgDBServers.
+ *
+ * - purgePeriod: The average number of object cache requests in between
+ * garbage collection operations, where expired entries
+ * are removed from the database. Or in other words, the
+ * reciprocal of the probability of purging on any given
+ * request. If this is set to zero, purging will never be
+ * done.
+ *
+ * - tableName: The table name to use, default is "objectcache".
+ *
+ * - shards: The number of tables to use for data storage. If this is
+ * more than 1, table names will be formed in the style
+ * objectcacheNNN where NNN is the shard index, between 0 and
+ * shards-1. The number of digits will be the minimum number
+ * required to hold the largest shard index. Data will be
+ * distributed across all tables by key hash. This is for
+ * MySQL bugs 61735 and 61736.
+ *
+ * @param $params array
+ */
+ public function __construct( $params ) {
+ if ( isset( $params['server'] ) ) {
+ $this->serverInfo = $params['server'];
+ $this->serverInfo['load'] = 1;
+ }
+ if ( isset( $params['purgePeriod'] ) ) {
+ $this->purgePeriod = intval( $params['purgePeriod'] );
+ }
+ if ( isset( $params['tableName'] ) ) {
+ $this->tableName = $params['tableName'];
+ }
+ if ( isset( $params['shards'] ) ) {
+ $this->shards = intval( $params['shards'] );
+ }
+ }
+
+ /**
+ * @return DatabaseBase
+ */
+ protected function getDB() {
+ if ( !isset( $this->db ) ) {
+ # If server connection info was given, use that
+ if ( $this->serverInfo ) {
+ $this->lb = new LoadBalancer( array(
+ 'servers' => array( $this->serverInfo ) ) );
+ $this->db = $this->lb->getConnection( DB_MASTER );
+ $this->db->clearFlag( DBO_TRX );
+ } else {
+ # We must keep a separate connection to MySQL in order to avoid deadlocks
+ # However, SQLite has an opposite behaviour.
+ # @todo Investigate behaviour for other databases
+ if ( wfGetDB( DB_MASTER )->getType() == 'sqlite' ) {
+ $this->db = wfGetDB( DB_MASTER );
+ } else {
+ $this->lb = wfGetLBFactory()->newMainLB();
+ $this->db = $this->lb->getConnection( DB_MASTER );
+ $this->db->clearFlag( DBO_TRX );
+ }
+ }
+ }
+
+ return $this->db;
+ }
+
+ /**
+ * Get the table name for a given key
+ */
+ protected function getTableByKey( $key ) {
+ if ( $this->shards > 1 ) {
+ $hash = hexdec( substr( md5( $key ), 0, 8 ) ) & 0x7fffffff;
+ return $this->getTableByShard( $hash % $this->shards );
+ } else {
+ return $this->tableName;
+ }
+ }
+
+ /**
+ * Get the table name for a given shard index
+ */
+ protected function getTableByShard( $index ) {
+ if ( $this->shards > 1 ) {
+ $decimals = strlen( $this->shards - 1 );
+ return $this->tableName .
+ sprintf( "%0{$decimals}d", $index );
+ } else {
+ return $this->tableName;
+ }
+ }
+
+ public function get( $key ) {
+ # expire old entries if any
+ $this->garbageCollect();
+ $db = $this->getDB();
+ $tableName = $this->getTableByKey( $key );
+ $row = $db->selectRow( $tableName, 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 {
+ $db->begin();
+ # Put the expiry time in the WHERE condition to avoid deleting a
+ # newly-inserted value
+ $db->delete( $tableName,
+ array(
+ 'keyname' => $key,
+ 'exptime' => $row->exptime
+ ), __METHOD__ );
+ $db->commit();
+ } 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 ) {
+ $encExpiry = $this->getMaxDateTime();
+ } else {
+ if ( $exptime < 3.16e8 ) { # ~10 years
+ $exptime += time();
+ }
+
+ $encExpiry = $db->timestamp( $exptime );
+ }
+ try {
+ $db->begin();
+ // (bug 24425) use a replace if the db supports it instead of
+ // delete/insert to avoid clashes with conflicting keynames
+ $db->replace(
+ $this->getTableByKey( $key ),
+ array( 'keyname' ),
+ array(
+ 'keyname' => $key,
+ 'value' => $db->encodeBlob( $this->serialize( $value ) ),
+ 'exptime' => $encExpiry
+ ), __METHOD__ );
+ $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(
+ $this->getTableByKey( $key ),
+ array( 'keyname' => $key ),
+ __METHOD__ );
+ $db->commit();
+ } catch ( DBQueryError $e ) {
+ $this->handleWriteError( $e );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public function incr( $key, $step = 1 ) {
+ $db = $this->getDB();
+ $tableName = $this->getTableByKey( $key );
+ $step = intval( $step );
+
+ try {
+ $db->begin();
+ $row = $db->selectRow(
+ $tableName,
+ array( 'value', 'exptime' ),
+ array( 'keyname' => $key ),
+ __METHOD__,
+ array( 'FOR UPDATE' ) );
+ if ( $row === false ) {
+ // Missing
+ $db->commit();
+
+ return null;
+ }
+ $db->delete( $tableName, array( 'keyname' => $key ), __METHOD__ );
+ if ( $this->isExpired( $row->exptime ) ) {
+ // Expired, do not reinsert
+ $db->commit();
+
+ return null;
+ }
+
+ $oldValue = intval( $this->unserialize( $db->decodeBlob( $row->value ) ) );
+ $newValue = $oldValue + $step;
+ $db->insert( $tableName,
+ array(
+ 'keyname' => $key,
+ 'value' => $db->encodeBlob( $this->serialize( $newValue ) ),
+ 'exptime' => $row->exptime
+ ), __METHOD__, 'IGNORE' );
+
+ if ( $db->affectedRows() == 0 ) {
+ // Race condition. See bug 28611
+ $newValue = null;
+ }
+ $db->commit();
+ } catch ( DBQueryError $e ) {
+ $this->handleWriteError( $e );
+
+ return null;
+ }
+
+ return $newValue;
+ }
+
+ public function keys() {
+ $db = $this->getDB();
+ $result = array();
+
+ for ( $i = 0; $i < $this->shards; $i++ ) {
+ $res = $db->select( $this->getTableByShard( $i ),
+ array( 'keyname' ), false, __METHOD__ );
+ foreach ( $res as $row ) {
+ $result[] = $row->keyname;
+ }
+ }
+
+ return $result;
+ }
+
+ protected function isExpired( $exptime ) {
+ return $exptime != $this->getMaxDateTime() && wfTimestamp( TS_UNIX, $exptime ) < time();
+ }
+
+ protected function getMaxDateTime() {
+ if ( time() > 0x7fffffff ) {
+ return $this->getDB()->timestamp( 1 << 62 );
+ } else {
+ return $this->getDB()->timestamp( 0x7fffffff );
+ }
+ }
+
+ protected function garbageCollect() {
+ if ( !$this->purgePeriod ) {
+ // Disabled
+ return;
+ }
+ // Only purge on one in every $this->purgePeriod requests.
+ if ( $this->purgePeriod !== 1 && mt_rand( 0, $this->purgePeriod - 1 ) ) {
+ return;
+ }
+ $now = time();
+ // Avoid repeating the delete within a few seconds
+ if ( $now > ( $this->lastExpireAll + 1 ) ) {
+ $this->lastExpireAll = $now;
+ $this->expireAll();
+ }
+ }
+
+ public function expireAll() {
+ $this->deleteObjectsExpiringBefore( wfTimestampNow() );
+ }
+
+ /**
+ * Delete objects from the database which expire before a certain date.
+ */
+ public function deleteObjectsExpiringBefore( $timestamp ) {
+ $db = $this->getDB();
+ $dbTimestamp = $db->timestamp( $timestamp );
+
+ try {
+ for ( $i = 0; $i < $this->shards; $i++ ) {
+ $db->begin();
+ $db->delete(
+ $this->getTableByShard( $i ),
+ array( 'exptime < ' . $db->addQuotes( $dbTimestamp ) ),
+ __METHOD__ );
+ $db->commit();
+ }
+ } catch ( DBQueryError $e ) {
+ $this->handleWriteError( $e );
+ }
+ return true;
+ }
+
+ public function deleteAll() {
+ $db = $this->getDB();
+
+ try {
+ for ( $i = 0; $i < $this->shards; $i++ ) {
+ $db->begin();
+ $db->delete( $this->getTableByShard( $i ), '*', __METHOD__ );
+ $db->commit();
+ }
+ } catch ( DBQueryError $e ) {
+ $this->handleWriteError( $e );
+ }
+ }
+
+ /**
+ * Serialize an object and, if possible, compress the representation.
+ * On typical message and page data, this can provide a 3X decrease
+ * in storage requirements.
+ *
+ * @param $data mixed
+ * @return string
+ */
+ protected function serialize( &$data ) {
+ $serial = serialize( $data );
+
+ if ( function_exists( 'gzdeflate' ) ) {
+ return gzdeflate( $serial );
+ } else {
+ return $serial;
+ }
+ }
+
+ /**
+ * Unserialize and, if necessary, decompress an object.
+ * @param $serial string
+ * @return mixed
+ */
+ protected function unserialize( $serial ) {
+ if ( function_exists( 'gzinflate' ) ) {
+ wfSuppressWarnings();
+ $decomp = gzinflate( $serial );
+ wfRestoreWarnings();
+
+ if ( false !== $decomp ) {
+ $serial = $decomp;
+ }
+ }
+
+ $ret = unserialize( $serial );
+
+ return $ret;
+ }
+
+ /**
+ * Handle a DBQueryError which occurred during a write operation.
+ * Ignore errors which are due to a read-only database, rethrow others.
+ */
+ 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 );
+ }
+
+ /**
+ * Create shard tables. For use from eval.php.
+ */
+ public function createTables() {
+ $db = $this->getDB();
+ if ( $db->getType() !== 'mysql'
+ || version_compare( $db->getServerVersion(), '4.1.0', '<' ) )
+ {
+ throw new MWException( __METHOD__ . ' is not supported on this DB server' );
+ }
+
+ for ( $i = 0; $i < $this->shards; $i++ ) {
+ $db->begin();
+ $db->query(
+ 'CREATE TABLE ' . $db->tableName( $this->getTableByShard( $i ) ) .
+ ' LIKE ' . $db->tableName( 'objectcache' ),
+ __METHOD__ );
+ $db->commit();
+ }
+ }
+}
+
+/**
+ * Backwards compatibility alias
+ */
+class MediaWikiBagOStuff extends SqlBagOStuff { }
+
diff --git a/includes/objectcache/WinCacheBagOStuff.php b/includes/objectcache/WinCacheBagOStuff.php
new file mode 100644
index 00000000..7f464946
--- /dev/null
+++ b/includes/objectcache/WinCacheBagOStuff.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * 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 ) {
+ $result = wincache_ucache_set( $key, serialize( $value ), $expire );
+
+ /* wincache_ucache_set returns an empty array on success if $value
+ was an array, bool otherwise */
+ return ( is_array( $result ) && $result === array() ) || $result;
+ }
+
+ /**
+ * 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();
+
+ if ( is_null( $list ) ) {
+ return array();
+ }
+
+ foreach ( $list as $entry ) {
+ $keys[] = $entry['key_name'];
+ }
+
+ return $keys;
+ }
+}
diff --git a/includes/objectcache/XCacheBagOStuff.php b/includes/objectcache/XCacheBagOStuff.php
new file mode 100644
index 00000000..0ddf1245
--- /dev/null
+++ b/includes/objectcache/XCacheBagOStuff.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Wrapper for XCache object caching functions; identical interface
+ * to the APC wrapper
+ *
+ * @ingroup Cache
+ */
+class XCacheBagOStuff extends BagOStuff {
+ /**
+ * Get a value from the XCache object cache
+ *
+ * @param $key String: cache key
+ * @return mixed
+ */
+ public function get( $key ) {
+ $val = xcache_get( $key );
+
+ if ( is_string( $val ) ) {
+ $val = unserialize( $val );
+ }
+
+ return $val;
+ }
+
+ /**
+ * Store a value in the XCache 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 ) {
+ xcache_set( $key, serialize( $value ), $expire );
+ return true;
+ }
+
+ /**
+ * Remove a value from the XCache object cache
+ *
+ * @param $key String: cache key
+ * @param $time Int: not used in this implementation
+ * @return bool
+ */
+ public function delete( $key, $time = 0 ) {
+ xcache_unset( $key );
+ return true;
+ }
+}
+
diff --git a/includes/objectcache/eAccelBagOStuff.php b/includes/objectcache/eAccelBagOStuff.php
new file mode 100644
index 00000000..30d24e80
--- /dev/null
+++ b/includes/objectcache/eAccelBagOStuff.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * This is a wrapper for eAccelerator's shared memory functions.
+ *
+ * This is basically identical to the deceased Turck MMCache version,
+ * mostly because eAccelerator is based on Turck MMCache.
+ *
+ * @ingroup Cache
+ */
+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;
+ }
+}
+
diff --git a/includes/parser/CoreLinkFunctions.php b/includes/parser/CoreLinkFunctions.php
index 913ec22b..8de13278 100644
--- a/includes/parser/CoreLinkFunctions.php
+++ b/includes/parser/CoreLinkFunctions.php
@@ -10,11 +10,25 @@
* @ingroup Parser
*/
class CoreLinkFunctions {
+ /**
+ * @param $parser Parser_LinkHooks
+ * @return bool
+ */
static function register( $parser ) {
$parser->setLinkHook( NS_CATEGORY, array( __CLASS__, 'categoryLinkHook' ) );
return true;
}
+ /**
+ * @param $parser Parser
+ * @param $holders LinkHolderArray
+ * @param $markers LinkMarkerReplacer
+ * @param Title $title
+ * @param $titleText
+ * @param null $displayText
+ * @param bool $leadingColon
+ * @return bool
+ */
static function defaultLinkHook( $parser, $holders, $markers,
Title $title, $titleText, &$displayText = null, &$leadingColon = false ) {
if( isset($displayText) && $markers->findMarker( $displayText ) ) {
@@ -25,9 +39,19 @@ class CoreLinkFunctions {
# Return false so that this link is reverted back to WikiText
return false;
}
- return $holders->makeHolder( $title, isset($displayText) ? $displayText : $titleText, '', '', '' );
+ return $holders->makeHolder( $title, isset($displayText) ? $displayText : $titleText, array(), '', '' );
}
-
+
+ /**
+ * @param $parser Parser
+ * @param $holders LinkHolderArray
+ * @param $markers LinkMarkerReplacer
+ * @param Title $title
+ * @param $titleText
+ * @param null $sortText
+ * @param bool $leadingColon
+ * @return bool|string
+ */
static function categoryLinkHook( $parser, $holders, $markers,
Title $title, $titleText, &$sortText = null, &$leadingColon = false ) {
global $wgContLang;
@@ -48,5 +72,5 @@ class CoreLinkFunctions {
$parser->mOutput->addCategory( $title->getDBkey(), $sortText );
return '';
}
-
+
}
diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php
index 94949221..eebed44c 100644
--- a/includes/parser/CoreParserFunctions.php
+++ b/includes/parser/CoreParserFunctions.php
@@ -10,6 +10,10 @@
* @ingroup Parser
*/
class CoreParserFunctions {
+ /**
+ * @param $parser Parser
+ * @return void
+ */
static function register( $parser ) {
global $wgAllowDisplayTitle, $wgAllowSlowParserFunctions;
@@ -31,6 +35,8 @@ class CoreParserFunctions {
$parser->setFunctionHook( 'localurle', array( __CLASS__, 'localurle' ), SFH_NO_HASH );
$parser->setFunctionHook( 'fullurl', array( __CLASS__, 'fullurl' ), SFH_NO_HASH );
$parser->setFunctionHook( 'fullurle', array( __CLASS__, 'fullurle' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'canonicalurl', array( __CLASS__, 'canonicalurl' ), SFH_NO_HASH );
+ $parser->setFunctionHook( 'canonicalurle', array( __CLASS__, 'canonicalurle' ), SFH_NO_HASH );
$parser->setFunctionHook( 'formatnum', array( __CLASS__, 'formatnum' ), SFH_NO_HASH );
$parser->setFunctionHook( 'grammar', array( __CLASS__, 'grammar' ), SFH_NO_HASH );
$parser->setFunctionHook( 'gender', array( __CLASS__, 'gender' ), SFH_NO_HASH );
@@ -83,18 +89,27 @@ class CoreParserFunctions {
}
}
+ /**
+ * @param $parser Parser
+ * @param string $part1
+ * @return array
+ */
static function intFunction( $parser, $part1 = '' /*, ... */ ) {
if ( strval( $part1 ) !== '' ) {
$args = array_slice( func_get_args(), 2 );
- $message = wfMsgGetKey( $part1, true, $parser->getOptions()->getUserLang(), false );
- $message = wfMsgReplaceArgs( $message, $args );
- $message = $parser->replaceVariables( $message ); // like $wgMessageCache->transform()
- return $message;
+ $message = wfMessage( $part1, $args )->inLanguage( $parser->getOptions()->getUserLang() )->plain();
+ return array( $message, 'noparse' => false );
} else {
return array( 'found' => false );
}
}
+ /**
+ * @param $parser Parser
+ * @param $date
+ * @param null $defaultPref
+ * @return mixed|string
+ */
static function formatDate( $parser, $date, $defaultPref = null ) {
$df = DateFormatter::getInstance();
@@ -172,6 +187,11 @@ class CoreParserFunctions {
return $wgContLang->ucfirst( $s );
}
+ /**
+ * @param $parser Parser
+ * @param string $s
+ * @return
+ */
static function lc( $parser, $s = '' ) {
global $wgContLang;
if ( is_callable( array( $parser, 'markerSkipCallback' ) ) ) {
@@ -181,6 +201,11 @@ class CoreParserFunctions {
}
}
+ /**
+ * @param $parser Parser
+ * @param string $s
+ * @return
+ */
static function uc( $parser, $s = '' ) {
global $wgContLang;
if ( is_callable( array( $parser, 'markerSkipCallback' ) ) ) {
@@ -194,6 +219,8 @@ class CoreParserFunctions {
static function localurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeLocalURL', $s, $arg ); }
static function fullurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getFullURL', $s, $arg ); }
static function fullurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeFullURL', $s, $arg ); }
+ static function canonicalurl( $parser, $s = '', $arg = null ) { return self::urlFunction( 'getCanonicalURL', $s, $arg ); }
+ static function canonicalurle( $parser, $s = '', $arg = null ) { return self::urlFunction( 'escapeCanonicalURL', $s, $arg ); }
static function urlFunction( $func, $s = '', $arg = null ) {
$title = Title::newFromText( $s );
@@ -219,6 +246,12 @@ class CoreParserFunctions {
}
}
+ /**
+ * @param $parser Parser
+ * @param string $num
+ * @param null $raw
+ * @return
+ */
static function formatNum( $parser, $num = '', $raw = null) {
if ( self::israw( $raw ) ) {
return $parser->getFunctionLang()->parseFormattedNumber( $num );
@@ -227,35 +260,54 @@ class CoreParserFunctions {
}
}
+ /**
+ * @param $parser Parser
+ * @param string $case
+ * @param string $word
+ * @return
+ */
static function grammar( $parser, $case = '', $word = '' ) {
return $parser->getFunctionLang()->convertGrammar( $word, $case );
}
- static function gender( $parser, $user ) {
+ /**
+ * @param $parser Parser
+ * @param $username string
+ * @return
+ */
+ static function gender( $parser, $username ) {
wfProfileIn( __METHOD__ );
$forms = array_slice( func_get_args(), 2);
+ $username = trim( $username );
+
// default
$gender = User::getDefaultOption( 'gender' );
// allow prefix.
- $title = Title::newFromText( $user );
+ $title = Title::newFromText( $username );
- if ( is_object( $title ) && $title->getNamespace() == NS_USER )
- $user = $title->getText();
+ if ( $title && $title->getNamespace() == NS_USER ) {
+ $username = $title->getText();
+ }
- // check parameter, or use $wgUser if in interface message
- $user = User::newFromName( $user );
+ // check parameter, or use the ParserOptions if in interface message
+ $user = User::newFromName( $username );
if ( $user ) {
$gender = $user->getOption( 'gender' );
- } elseif ( $parser->getOptions()->getInterfaceMessage() ) {
- global $wgUser;
- $gender = $wgUser->getOption( 'gender' );
+ } elseif ( $username === '' && $parser->getOptions()->getInterfaceMessage() ) {
+ $gender = $parser->getOptions()->getUser()->getOption( 'gender' );
}
$ret = $parser->getFunctionLang()->gender( $gender, $forms );
wfProfileOut( __METHOD__ );
return $ret;
}
+
+ /**
+ * @param $parser Parser
+ * @param string $text
+ * @return
+ */
static function plural( $parser, $text = '' ) {
$forms = array_slice( func_get_args(), 2 );
$text = $parser->getFunctionLang()->parseFormattedNumber( $text );
@@ -396,10 +448,11 @@ class CoreParserFunctions {
return '';
return wfUrlencode( $t->getSubjectNsText() );
}
- /*
+
+ /**
* Functions to get and normalize pagenames, corresponding to the magic words
* of the same names
- */
+ */
static function pagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) )
@@ -410,7 +463,7 @@ class CoreParserFunctions {
$t = Title::newFromText( $title );
if ( is_null( $t ) )
return '';
- return $t->getPartialURL();
+ return wfEscapeWikiText( $t->getPartialURL() );
}
static function fullpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
@@ -422,31 +475,31 @@ class CoreParserFunctions {
$t = Title::newFromText( $title );
if ( is_null( $t ) || !$t->canTalk() )
return '';
- return $t->getPrefixedURL();
+ return wfEscapeWikiText( $t->getPrefixedURL() );
}
static function subpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) )
return '';
- return $t->getSubpageText();
+ return wfEscapeWikiText( $t->getSubpageText() );
}
static function subpagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) )
return '';
- return $t->getSubpageUrlForm();
+ return wfEscapeWikiText( $t->getSubpageUrlForm() );
}
static function basepagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) )
return '';
- return $t->getBaseText();
+ return wfEscapeWikiText( $t->getBaseText() );
}
static function basepagenamee( $parser, $title = null ) {
$t = Title::newFromText( $title );
if ( is_null( $t ) )
return '';
- return wfUrlEncode( str_replace( ' ', '_', $t->getBaseText() ) );
+ return wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $t->getBaseText() ) ) );
}
static function talkpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
@@ -458,7 +511,7 @@ class CoreParserFunctions {
$t = Title::newFromText( $title );
if ( is_null( $t ) || !$t->canTalk() )
return '';
- return $t->getTalkPage()->getPrefixedUrl();
+ return wfEscapeWikiText( $t->getTalkPage()->getPrefixedUrl() );
}
static function subjectpagename( $parser, $title = null ) {
$t = Title::newFromText( $title );
@@ -470,7 +523,7 @@ class CoreParserFunctions {
$t = Title::newFromText( $title );
if ( is_null( $t ) )
return '';
- return $t->getSubjectPage()->getPrefixedUrl();
+ return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedUrl() );
}
/**
@@ -503,12 +556,13 @@ class CoreParserFunctions {
* Return the size of the given page, or 0 if it's nonexistent. This is an
* expensive parser function and can't be called too many times per page.
*
- * @todo Fixme: This doesn't work correctly on preview for getting the size
+ * @todo FIXME: This doesn't work correctly on preview for getting the size
* of the current page.
- * @todo Fixme: Title::getLength() documentation claims that it adds things
+ * @todo FIXME: Title::getLength() documentation claims that it adds things
* to the link cache, so the local cache here should be unnecessary, but
* in fact calling getLength() repeatedly for the same $page does seem to
* run one query for each call?
+ * @param $parser Parser
*/
static function pagesize( $parser, $page = '', $raw = null ) {
static $cache = array();
@@ -546,10 +600,25 @@ class CoreParserFunctions {
return implode( $restrictions, ',' );
}
- static function language( $parser, $arg = '' ) {
+ /**
+ * Gives language names.
+ * @param $parser Parser
+ * @param $code String Language code
+ * @param $language String Language code
+ * @return String
+ */
+ static function language( $parser, $code = '', $language = '' ) {
global $wgContLang;
- $lang = $wgContLang->getLanguageName( strtolower( $arg ) );
- return $lang != '' ? $lang : $arg;
+ $code = strtolower( $code );
+ $language = strtolower( $language );
+
+ if ( $language !== '' ) {
+ $names = Language::getTranslatedLanguageNames( $language );
+ return isset( $names[$code] ) ? $names[$code] : wfBCP47( $code );
+ }
+
+ $lang = $wgContLang->getLanguageName( $code );
+ return $lang !== '' ? $lang : wfBCP47( $code );
}
/**
@@ -586,12 +655,17 @@ class CoreParserFunctions {
return self::pad( $string, $length, $padding );
}
+ /**
+ * @param $parser Parser
+ * @param $text
+ * @return string
+ */
static function anchorencode( $parser, $text ) {
return substr( $parser->guessSectionNameFromWikiText( $text ), 1);
}
static function special( $parser, $text ) {
- list( $page, $subpage ) = SpecialPage::resolveAliasWithSubpage( $text );
+ list( $page, $subpage ) = SpecialPageFactory::resolveAlias( $text );
if ( $page ) {
$title = SpecialPage::getTitleFor( $page, $subpage );
return $title;
@@ -600,6 +674,11 @@ class CoreParserFunctions {
}
}
+ /**
+ * @param $parser Parser
+ * @param $text
+ * @return string
+ */
public static function defaultsort( $parser, $text ) {
$text = trim( $text );
if( strlen( $text ) == 0 )
@@ -616,11 +695,41 @@ class CoreParserFunctions {
'</span>' );
}
- public static function filepath( $parser, $name='', $option='' ) {
+ // Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}} or {{filepath|300|nowiki}}
+ public static function filepath( $parser, $name='', $argA='', $argB='' ) {
$file = wfFindFile( $name );
- if( $file ) {
+ $size = '';
+ $argA_int = intval( $argA );
+ $argB_int = intval( $argB );
+
+ if ( $argB_int > 0 ) {
+ // {{filepath: | option | size }}
+ $size = $argB_int;
+ $option = $argA;
+
+ } elseif ( $argA_int > 0 ) {
+ // {{filepath: | size [|option] }}
+ $size = $argA_int;
+ $option = $argB;
+
+ } else {
+ // {{filepath: [|option] }}
+ $option = $argA;
+ }
+
+ if ( $file ) {
$url = $file->getFullUrl();
- if( $option == 'nowiki' ) {
+
+ // If a size is requested...
+ if ( is_integer( $size ) ) {
+ $mto = $file->transform( array( 'width' => $size ) );
+ // ... and we can
+ if ( $mto && !$mto->isError() ) {
+ // ... change the URL to point to a thumbnail.
+ $url = wfExpandUrl( $mto->getUrl(), PROTO_RELATIVE );
+ }
+ }
+ if ( $option == 'nowiki' ) {
return array( $url, 'nowiki' => true );
}
return $url;
diff --git a/includes/parser/CoreTagHooks.php b/includes/parser/CoreTagHooks.php
index 33f3c824..7d488c4b 100644
--- a/includes/parser/CoreTagHooks.php
+++ b/includes/parser/CoreTagHooks.php
@@ -10,19 +10,30 @@
* @ingroup Parser
*/
class CoreTagHooks {
+ /**
+ * @param $parser Parser
+ * @return void
+ */
static function register( $parser ) {
- global $wgRawHtml, $wgUseTeX;
+ global $wgRawHtml;
$parser->setHook( 'pre', array( __CLASS__, 'pre' ) );
$parser->setHook( 'nowiki', array( __CLASS__, 'nowiki' ) );
$parser->setHook( 'gallery', array( __CLASS__, 'gallery' ) );
if ( $wgRawHtml ) {
$parser->setHook( 'html', array( __CLASS__, 'html' ) );
}
- if ( $wgUseTeX ) {
- $parser->setHook( 'math', array( __CLASS__, 'math' ) );
- }
}
+ /**
+ * Core parser tag hook function for 'pre'.
+ * Text is treated roughly as 'nowiki' wrapped in an HTML 'pre' tag;
+ * valid HTML attributes are passed on.
+ *
+ * @param string $text
+ * @param array $attribs
+ * @param Parser $parser
+ * @return string HTML
+ */
static function pre( $text, $attribs, $parser ) {
// Backwards-compatibility hack
$content = StringUtils::delimiterReplace( '<nowiki>', '</nowiki>', '$1', $text, 'i' );
@@ -33,6 +44,20 @@ class CoreTagHooks {
'</pre>';
}
+ /**
+ * Core parser tag hook function for 'html', used only when
+ * $wgRawHtml is enabled.
+ *
+ * This is potentially unsafe and should be used only in very careful
+ * circumstances, as the contents are emitted as raw HTML.
+ *
+ * Uses undocumented extended tag hook return values, introduced in r61913.
+ *
+ * @param $content string
+ * @param $attributes array
+ * @param $parser Parser
+ * @return array
+ */
static function html( $content, $attributes, $parser ) {
global $wgRawHtml;
if( $wgRawHtml ) {
@@ -42,16 +67,38 @@ class CoreTagHooks {
}
}
+ /**
+ * Core parser tag hook function for 'nowiki'. Text within this section
+ * gets interpreted as a string of text with HTML-compatible character
+ * references, and wiki markup within it will not be expanded.
+ *
+ * Uses undocumented extended tag hook return values, introduced in r61913.
+ *
+ * @param $content string
+ * @param $attributes array
+ * @param $parser Parser
+ * @return array
+ */
static function nowiki( $content, $attributes, $parser ) {
$content = strtr( $content, array( '-{' => '-&#123;', '}-' => '&#125;-' ) );
return array( Xml::escapeTagsOnly( $content ), 'markerType' => 'nowiki' );
}
- static function math( $content, $attributes, $parser ) {
- global $wgContLang;
- return $wgContLang->armourMath( MathRenderer::renderMath( $content, $attributes, $parser->getOptions() ) );
- }
-
+ /**
+ * Core parser tag hook function for 'gallery'.
+ *
+ * Renders a thumbnail list of the given images, with optional captions.
+ * Full syntax documented on the wiki:
+ *
+ * http://www.mediawiki.org/wiki/Help:Images#Gallery_syntax
+ *
+ * @todo break Parser::renderImageGallery out here too.
+ *
+ * @param string $content
+ * @param array $attributes
+ * @param Parser $parser
+ * @return string HTML
+ */
static function gallery( $content, $attributes, $parser ) {
return $parser->renderImageGallery( $content, $attributes );
}
diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php
index cf510171..6559e886 100644
--- a/includes/parser/DateFormatter.php
+++ b/includes/parser/DateFormatter.php
@@ -182,8 +182,8 @@ class DateFormatter
$bits = array();
$key = $this->keys[$this->mSource];
for ( $p=0; $p < strlen($key); $p++ ) {
- if ( $key{$p} != ' ' ) {
- $bits[$key{$p}] = $matches[$p+1];
+ if ( $key[$p] != ' ' ) {
+ $bits[$key[$p]] = $matches[$p+1];
}
}
@@ -224,7 +224,7 @@ class DateFormatter
}
for ( $p=0; $p < strlen( $format ); $p++ ) {
- $char = $format{$p};
+ $char = $format[$p];
switch ( $char ) {
case 'd': # ISO day of month
$text .= $bits['d'];
@@ -327,7 +327,7 @@ class DateFormatter
* @todo document
*/
function makeNormalYear( $iso ) {
- if ( $iso{0} == '-' ) {
+ if ( $iso[0] == '-' ) {
$text = (intval( substr( $iso, 1 ) ) + 1) . ' BC';
} else {
$text = intval( $iso );
diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php
index 19313b80..5418b6e5 100644
--- a/includes/parser/LinkHolderArray.php
+++ b/includes/parser/LinkHolderArray.php
@@ -12,6 +12,7 @@ class LinkHolderArray {
var $internals = array(), $interwikis = array();
var $size = 0;
var $parent;
+ protected $tempIdOffset;
function __construct( $parent ) {
$this->parent = $parent;
@@ -26,8 +27,51 @@ class LinkHolderArray {
}
}
+ /**
+ * Don't serialize the parent object, it is big, and not needed when it is
+ * a parameter to mergeForeign(), which is the only application of
+ * serializing at present.
+ *
+ * Compact the titles, only serialize the text form.
+ */
+ function __sleep() {
+ foreach ( $this->internals as &$nsLinks ) {
+ foreach ( $nsLinks as &$entry ) {
+ unset( $entry['title'] );
+ }
+ }
+ unset( $nsLinks );
+ unset( $entry );
+
+ foreach ( $this->interwikis as &$entry ) {
+ unset( $entry['title'] );
+ }
+ unset( $entry );
+
+ return array( 'internals', 'interwikis', 'size' );
+ }
+
+ /**
+ * Recreate the Title objects
+ */
+ function __wakeup() {
+ foreach ( $this->internals as &$nsLinks ) {
+ foreach ( $nsLinks as &$entry ) {
+ $entry['title'] = Title::newFromText( $entry['pdbk'] );
+ }
+ }
+ unset( $nsLinks );
+ unset( $entry );
+
+ foreach ( $this->interwikis as &$entry ) {
+ $entry['title'] = Title::newFromText( $entry['pdbk'] );
+ }
+ unset( $entry );
+ }
+
/**
* Merge another LinkHolderArray into this one
+ * @param $other LinkHolderArray
*/
function merge( $other ) {
foreach ( $other->internals as $ns => $entries ) {
@@ -42,6 +86,86 @@ class LinkHolderArray {
}
/**
+ * Merge a LinkHolderArray from another parser instance into this one. The
+ * keys will not be preserved. Any text which went with the old
+ * LinkHolderArray and needs to work with the new one should be passed in
+ * the $texts array. The strings in this array will have their link holders
+ * converted for use in the destination link holder. The resulting array of
+ * strings will be returned.
+ *
+ * @param $other LinkHolderArray
+ * @param $texts Array of strings
+ * @return Array
+ */
+ function mergeForeign( $other, $texts ) {
+ $this->tempIdOffset = $idOffset = $this->parent->nextLinkID();
+ $maxId = 0;
+
+ # Renumber internal links
+ foreach ( $other->internals as $ns => $nsLinks ) {
+ foreach ( $nsLinks as $key => $entry ) {
+ $newKey = $idOffset + $key;
+ $this->internals[$ns][$newKey] = $entry;
+ $maxId = $newKey > $maxId ? $newKey : $maxId;
+ }
+ }
+ $texts = preg_replace_callback( '/(<!--LINK \d+:)(\d+)(-->)/',
+ array( $this, 'mergeForeignCallback' ), $texts );
+
+ # Renumber interwiki links
+ foreach ( $other->interwikis as $key => $entry ) {
+ $newKey = $idOffset + $key;
+ $this->interwikis[$newKey] = $entry;
+ $maxId = $newKey > $maxId ? $newKey : $maxId;
+ }
+ $texts = preg_replace_callback( '/(<!--IWLINK )(\d+)(-->)/',
+ array( $this, 'mergeForeignCallback' ), $texts );
+
+ # Set the parent link ID to be beyond the highest used ID
+ $this->parent->setLinkID( $maxId + 1 );
+ $this->tempIdOffset = null;
+ return $texts;
+ }
+
+ protected function mergeForeignCallback( $m ) {
+ return $m[1] . ( $m[2] + $this->tempIdOffset ) . $m[3];
+ }
+
+ /**
+ * Get a subset of the current LinkHolderArray which is sufficient to
+ * interpret the given text.
+ */
+ function getSubArray( $text ) {
+ $sub = new LinkHolderArray( $this->parent );
+
+ # Internal links
+ $pos = 0;
+ while ( $pos < strlen( $text ) ) {
+ if ( !preg_match( '/<!--LINK (\d+):(\d+)-->/',
+ $text, $m, PREG_OFFSET_CAPTURE, $pos ) )
+ {
+ break;
+ }
+ $ns = $m[1][0];
+ $key = $m[2][0];
+ $sub->internals[$ns][$key] = $this->internals[$ns][$key];
+ $pos = $m[0][1] + strlen( $m[0][0] );
+ }
+
+ # Interwiki links
+ $pos = 0;
+ while ( $pos < strlen( $text ) ) {
+ if ( !preg_match( '/<!--IWLINK (\d+)-->/', $text, $m, PREG_OFFSET_CAPTURE, $pos ) ) {
+ break;
+ }
+ $key = $m[1][0];
+ $sub->interwikis[$key] = $this->interwikis[$key];
+ $pos = $m[0][1] + strlen( $m[0][0] );
+ }
+ return $sub;
+ }
+
+ /**
* Returns true if the memory requirements of this object are getting large
*/
function isBig() {
@@ -65,8 +189,9 @@ class LinkHolderArray {
* parsing of interwiki links, and secondly to allow all existence checks and
* article length checks (for stub links) to be bundled into a single query.
*
+ * @param $nt Title
*/
- function makeHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
+ function makeHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
wfProfileIn( __METHOD__ );
if ( ! is_object($nt) ) {
# Fail gracefully
@@ -80,7 +205,7 @@ class LinkHolderArray {
'text' => $prefix.$text.$inside,
'pdbk' => $nt->getPrefixedDBkey(),
);
- if ( $query !== '' ) {
+ if ( $query !== array() ) {
$entry['query'] = $query;
}
@@ -102,18 +227,7 @@ class LinkHolderArray {
}
/**
- * Get the stub threshold
- */
- function getStubThreshold() {
- global $wgUser;
- if ( !isset( $this->stubThreshold ) ) {
- $this->stubThreshold = $wgUser->getStubThreshold();
- }
- return $this->stubThreshold;
- }
-
- /**
- * FIXME: update documentation. makeLinkObj() is deprecated.
+ * @todo FIXME: Update documentation. makeLinkObj() is deprecated.
* Replace <!--LINK--> link placeholders with actual links, in the buffer
* Placeholders created in Skin::makeLinkObj()
* Returns an array of link CSS classes, indexed by PDBK.
@@ -140,14 +254,12 @@ class LinkHolderArray {
global $wgContLang;
$colours = array();
- $sk = $this->parent->getOptions()->getSkin( $this->parent->mTitle );
$linkCache = LinkCache::singleton();
$output = $this->parent->getOutput();
wfProfileIn( __METHOD__.'-check' );
$dbr = wfGetDB( DB_SLAVE );
- $page = $dbr->tableName( 'page' );
- $threshold = $this->getStubThreshold();
+ $threshold = $this->parent->getOptions()->getStubThreshold();
# Sort by namespace
ksort( $this->internals );
@@ -155,8 +267,7 @@ class LinkHolderArray {
$linkcolour_ids = array();
# Generate query
- $query = false;
- $current = null;
+ $queries = array();
foreach ( $this->internals as $ns => $entries ) {
foreach ( $entries as $entry ) {
$title = $entry['title'];
@@ -174,32 +285,35 @@ class LinkHolderArray {
} elseif ( $ns == NS_SPECIAL ) {
$colours[$pdbk] = 'new';
} elseif ( ( $id = $linkCache->getGoodLinkID( $pdbk ) ) != 0 ) {
- $colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
+ $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
$output->addLink( $title, $id );
$linkcolour_ids[$id] = $pdbk;
} elseif ( $linkCache->isBadLink( $pdbk ) ) {
$colours[$pdbk] = 'new';
} else {
# Not in the link cache, add it to the query
- if ( !isset( $current ) ) {
- $current = $ns;
- $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;
- $query .= ")) OR (page_namespace=$ns AND page_title IN(";
- } else {
- $query .= ', ';
- }
-
- $query .= $dbr->addQuotes( $title->getDBkey() );
+ $queries[$ns][] = $title->getDBkey();
}
}
}
- if ( $query ) {
- $query .= '))';
+ if ( $queries ) {
+ $where = array();
+ foreach( $queries as $ns => $pages ){
+ $where[] = $dbr->makeList(
+ array(
+ 'page_namespace' => $ns,
+ 'page_title' => $pages,
+ ),
+ LIST_AND
+ );
+ }
- $res = $dbr->query( $query, __METHOD__ );
+ $res = $dbr->select(
+ 'page',
+ array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest' ),
+ $dbr->makeList( $where, LIST_OR ),
+ __METHOD__
+ );
# Fetch data and form into an associative array
# non-existent = broken
@@ -208,10 +322,10 @@ class LinkHolderArray {
$pdbk = $title->getPrefixedDBkey();
$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
+ # @todo FIXME: Convoluted data flow
# The redirect status and length is passed to getLinkColour via the LinkCache
# Use formal parameters instead
- $colours[$pdbk] = $sk->getLinkColour( $title, $threshold );
+ $colours[$pdbk] = Linker::getLinkColour( $title, $threshold );
//add id to the extension todolist
$linkcolour_ids[$s->page_id] = $pdbk;
}
@@ -235,23 +349,29 @@ class LinkHolderArray {
foreach ( $entries as $index => $entry ) {
$pdbk = $entry['pdbk'];
$title = $entry['title'];
- $query = isset( $entry['query'] ) ? $entry['query'] : '';
+ $query = isset( $entry['query'] ) ? $entry['query'] : array();
$key = "$ns:$index";
$searchkey = "<!--LINK $key-->";
- if ( !isset( $colours[$pdbk] ) || $colours[$pdbk] == 'new' ) {
- $linkCache->addBadLinkObj( $title );
+ $displayText = $entry['text'];
+ if ( $displayText === '' ) {
+ $displayText = null;
+ }
+ if ( !isset( $colours[$pdbk] ) ) {
$colours[$pdbk] = 'new';
+ }
+ $attribs = array();
+ if ( $colours[$pdbk] == 'new' ) {
+ $linkCache->addBadLinkObj( $title );
$output->addLink( $title, 0 );
- // FIXME: replace deprecated makeBrokenLinkObj() by link()
- $replacePairs[$searchkey] = $sk->makeBrokenLinkObj( $title,
- $entry['text'],
- $query );
+ $type = array( 'broken' );
} else {
- // FIXME: replace deprecated makeColouredLinkObj() by link()
- $replacePairs[$searchkey] = $sk->makeColouredLinkObj( $title, $colours[$pdbk],
- $entry['text'],
- $query );
+ if ( $colours[$pdbk] != '' ) {
+ $attribs['class'] = $colours[$pdbk];
+ }
+ $type = array( 'known', 'noclasses' );
}
+ $replacePairs[$searchkey] = Linker::link( $title, $displayText,
+ $attribs, $query, $type );
}
}
$replacer = new HashtableReplacer( $replacePairs, 1 );
@@ -278,11 +398,10 @@ class LinkHolderArray {
wfProfileIn( __METHOD__ );
# Make interwiki link HTML
- $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'] );
+ $replacePairs[$key] = Linker::link( $link['title'], $link['text'] );
$output->addInterwikiLink( $link['title'] );
}
$replacer = new HashtableReplacer( $replacePairs, 1 );
@@ -303,11 +422,10 @@ class LinkHolderArray {
$variantMap = array(); // maps $pdbkey_Variant => $keys (of link holders)
$output = $this->parent->getOutput();
$linkCache = LinkCache::singleton();
- $sk = $this->parent->getOptions()->getSkin( $this->parent->mTitle );
- $threshold = $this->getStubThreshold();
+ $threshold = $this->parent->getOptions()->getStubThreshold();
$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.
@@ -322,14 +440,14 @@ class LinkHolderArray {
'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 );
@@ -341,9 +459,8 @@ class LinkHolderArray {
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 ( $textVariant != $titlesAttrs[$i]['titleText'] ) {
+ $variantTitle = Title::makeTitle( $titlesAttrs[$i]['ns'], $textVariant );
if( is_null( $variantTitle ) ) {
continue;
}
@@ -372,11 +489,12 @@ class LinkHolderArray {
if(!$linkBatch->isEmpty()){
// construct query
$dbr = wfGetDB( DB_SLAVE );
- $page = $dbr->tableName( 'page' );
- $titleClause = $linkBatch->constructSet('page', $dbr);
- $variantQuery = "SELECT page_id, page_namespace, page_title, page_is_redirect, page_len";
- $variantQuery .= " FROM $page WHERE $titleClause";
- $varRes = $dbr->query( $variantQuery, __METHOD__ );
+ $varRes = $dbr->select( 'page',
+ array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len' ),
+ $linkBatch->constructSet( 'page', $dbr ),
+ __METHOD__
+ );
+
$linkcolour_ids = array();
// for each found variants, figure out link holders and replace
@@ -387,14 +505,14 @@ class LinkHolderArray {
$vardbk = $variantTitle->getDBkey();
$holderKeys = array();
- if(isset($variantMap[$varPdbk])){
+ if( isset( $variantMap[$varPdbk] ) ) {
$holderKeys = $variantMap[$varPdbk];
$linkCache->addGoodLinkObj( $s->page_id, $variantTitle, $s->page_len, $s->page_is_redirect );
$output->addLink( $variantTitle, $s->page_id );
}
// loop over link holders
- foreach($holderKeys as $key){
+ foreach( $holderKeys as $key ) {
list( $ns, $index ) = explode( ':', $key, 2 );
$entry =& $this->internals[$ns][$index];
$pdbk = $entry['pdbk'];
@@ -405,10 +523,10 @@ class LinkHolderArray {
$entry['pdbk'] = $varPdbk;
// set pdbk and colour
- # FIXME: convoluted data flow
+ # @todo FIXME: Convoluted data flow
# The redirect status and length is passed to getLinkColour via the LinkCache
# Use formal parameters instead
- $colours[$varPdbk] = $sk->getLinkColour( $variantTitle, $threshold );
+ $colours[$varPdbk] = Linker::getLinkColour( $variantTitle, $threshold );
$linkcolour_ids[$s->page_id] = $pdbk;
}
}
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index 4a3aa03b..8d4c60df 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -24,18 +24,20 @@
* 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
+ * getSection()
+ * Return the content of a section from an article for section editing
+ * replaceSection()
+ * Replaces a section by number inside an article
* getPreloadText()
* Removes <noinclude> sections, and <includeonly> tags.
*
* Globals used:
* objects: $wgLang, $wgContLang
*
- * NOT $wgArticle, $wgUser or $wgTitle. Keep them away!
+ * NOT $wgUser or $wgTitle. Keep them away!
*
* settings:
- * $wgUseTex*, $wgUseDynamicDates*, $wgInterwikiMagic*,
+ * $wgUseDynamicDates*, $wgInterwikiMagic*,
* $wgNamespacesWithSubpages, $wgAllowExternalImages*,
* $wgLocaltimezone, $wgAllowSpecialInclusion*,
* $wgMaxArticleSize*
@@ -53,6 +55,12 @@ class Parser {
*/
const VERSION = '1.6.4';
+ /**
+ * Update this version number when the output of serialiseHalfParsedText()
+ * changes in an incompatible way
+ */
+ const HALF_PARSED_VERSION = 2;
+
# Flags for Parser::setFunctionHook
# Also available as global constants from Defines.php
const SFH_NO_HASH = 1;
@@ -89,50 +97,99 @@ class Parser {
const MARKER_SUFFIX = "-QINU\x7f";
# Persistent:
- var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables;
- var $mSubstWords, $mImageParams, $mImageParamsMagicArray, $mStripList, $mMarkerIndex;
- var $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols, $mDefaultStripList;
- var $mVarCache, $mConf, $mFunctionTagHooks;
+ var $mTagHooks = array();
+ var $mTransparentTagHooks = array();
+ var $mFunctionHooks = array();
+ var $mFunctionSynonyms = array( 0 => array(), 1 => array() );
+ var $mFunctionTagHooks = array();
+ var $mStripList = array();
+ var $mDefaultStripList = array();
+ var $mVarCache = array();
+ var $mImageParams = array();
+ var $mImageParamsMagicArray = array();
+ var $mMarkerIndex = 0;
+ var $mFirstCall = true;
+
+ # Initialised by initialiseVariables()
+
+ /**
+ * @var MagicWordArray
+ */
+ var $mVariables;
+ /**
+ * @var MagicWordArray
+ */
+ var $mSubstWords;
+ var $mConf, $mPreprocessor, $mExtLinkBracketedRegex, $mUrlProtocols; # Initialised in constructor
# Cleared with clearState():
- var $mOutput, $mAutonumber, $mDTopen, $mStripState;
+ /**
+ * @var ParserOutput
+ */
+ var $mOutput;
+ var $mAutonumber, $mDTopen;
+
+ /**
+ * @var StripState
+ */
+ var $mStripState;
+
var $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
- var $mLinkHolders, $mLinkID;
+ /**
+ * @var LinkHolderArray
+ */
+ var $mLinkHolders;
+
+ var $mLinkID;
var $mIncludeSizes, $mPPNodeCount, $mDefaultSort;
var $mTplExpandCache; # empty-frame expansion cache
var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
var $mExpensiveFunctionCount; # number of expensive parser function calls
+ /**
+ * @var User
+ */
+ var $mUser; # User object; only used when doing pre-save transform
+
# Temporary
# These are variables reset at least once per parse regardless of $clearState
- var $mOptions; # ParserOptions object
+
+ /**
+ * @var ParserOptions
+ */
+ var $mOptions;
+
+ /**
+ * @var Title
+ */
var $mTitle; # Title context, used for self-link rendering and similar things
var $mOutputType; # Output type, one of the OT_xxx constants
var $ot; # Shortcut alias, see setOutputType()
+ var $mRevisionObject; # The revision object of the specified revision ID
var $mRevisionId; # ID to display in {{REVISIONID}} tags
var $mRevisionTimestamp; # The timestamp of the specified revision ID
+ var $mRevisionUser; # User to display in {{REVISIONUSER}} tag
var $mRevIdForTs; # The revision ID which was used to fetch the timestamp
/**
+ * @var string
+ */
+ var $mUniqPrefix;
+
+ /**
* Constructor
- *
- * @public
*/
- function __construct( $conf = array() ) {
+ public function __construct( $conf = array() ) {
$this->mConf = $conf;
- $this->mTagHooks = array();
- $this->mTransparentTagHooks = array();
- $this->mFunctionHooks = array();
- $this->mFunctionTagHooks = array();
- $this->mFunctionSynonyms = array( 0 => array(), 1 => array() );
- $this->mDefaultStripList = $this->mStripList = array();
$this->mUrlProtocols = wfUrlProtocols();
- $this->mExtLinkBracketedRegex = '/\[(\b(' . wfUrlProtocols() . ')'.
+ $this->mExtLinkBracketedRegex = '/\[((' . wfUrlProtocols() . ')'.
'[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/S';
- $this->mVarCache = array();
if ( isset( $conf['preprocessorClass'] ) ) {
$this->mPreprocessorClass = $conf['preprocessorClass'];
+ } elseif ( defined( 'MW_COMPILED' ) ) {
+ # Preprocessor_Hash is much faster than Preprocessor_DOM in compiled mode
+ $this->mPreprocessorClass = 'Preprocessor_Hash';
} elseif ( extension_loaded( 'domxml' ) ) {
# 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" );
@@ -142,8 +199,7 @@ class Parser {
} else {
$this->mPreprocessorClass = 'Preprocessor_Hash';
}
- $this->mMarkerIndex = 0;
- $this->mFirstCall = true;
+ wfDebug( __CLASS__ . ": using preprocessor: {$this->mPreprocessorClass}\n" );
}
/**
@@ -151,7 +207,7 @@ class Parser {
*/
function __destruct() {
if ( isset( $this->mLinkHolders ) ) {
- $this->mLinkHolders->__destruct();
+ unset( $this->mLinkHolders );
}
foreach ( $this as $name => $value ) {
unset( $this->$name );
@@ -193,13 +249,14 @@ class Parser {
$this->mLastSection = '';
$this->mDTopen = false;
$this->mIncludeCount = array();
- $this->mStripState = new StripState;
$this->mArgStack = false;
$this->mInPre = false;
$this->mLinkHolders = new LinkHolderArray( $this );
$this->mLinkID = 0;
- $this->mRevisionTimestamp = $this->mRevisionId = null;
+ $this->mRevisionObject = $this->mRevisionTimestamp =
+ $this->mRevisionId = $this->mRevisionUser = null;
$this->mVarCache = array();
+ $this->mUser = null;
/**
* Prefix for temporary replacement strings for the multipass parser.
@@ -214,6 +271,7 @@ class Parser {
# $this->mUniqPrefix = "\x07UNIQ" . Parser::getRandomString();
# Changed to \x7f to allow XML double-parsing -- TS
$this->mUniqPrefix = "\x7fUNIQ" . self::getRandomString();
+ $this->mStripState = new StripState( $this->mUniqPrefix );
# Clear these on every parse, bug 4549
@@ -245,7 +303,7 @@ class Parser {
* Do not call this function recursively.
*
* @param $text String: text we want to parse
- * @param $title A title object
+ * @param $title Title object
* @param $options ParserOptions
* @param $linestart boolean
* @param $clearState boolean
@@ -263,20 +321,19 @@ class Parser {
wfProfileIn( __METHOD__ );
wfProfileIn( $fname );
- $this->mOptions = $options;
- if ( $clearState ) {
- $this->clearState();
- }
-
- $this->setTitle( $title ); # Page title has to be set for the pre-processor
+ $this->startParse( $title, $options, self::OT_HTML, $clearState );
$oldRevisionId = $this->mRevisionId;
+ $oldRevisionObject = $this->mRevisionObject;
$oldRevisionTimestamp = $this->mRevisionTimestamp;
+ $oldRevisionUser = $this->mRevisionUser;
if ( $revid !== null ) {
$this->mRevisionId = $revid;
+ $this->mRevisionObject = null;
$this->mRevisionTimestamp = null;
+ $this->mRevisionUser = null;
}
- $this->setOutputType( self::OT_HTML );
+
wfRunHooks( 'ParserBeforeStrip', array( &$this, &$text, &$this->mStripState ) );
# No more strip!
wfRunHooks( 'ParserAfterStrip', array( &$this, &$text, &$this->mStripState ) );
@@ -327,7 +384,7 @@ class Parser {
|| $wgDisableTitleConversion
|| isset( $this->mDoubleUnderscores['nocontentconvert'] )
|| isset( $this->mDoubleUnderscores['notitleconvert'] )
- || $this->mOutput->getDisplayTitle() !== false ) )
+ || $this->mOutput->getDisplayTitle() !== false ) )
{
$convruletitle = $wgContLang->getConvRuleTitle();
if ( $convruletitle ) {
@@ -342,23 +399,7 @@ class Parser {
wfRunHooks( 'ParserBeforeTidy', array( &$this, &$text ) );
-//!JF Move to its own function
-
- $uniq_prefix = $this->mUniqPrefix;
- $matches = array();
- $elements = array_keys( $this->mTransparentTagHooks );
- $text = $this->extractTagsAndParams( $elements, $text, $matches, $uniq_prefix );
-
- 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 ) );
- } else {
- $output = $tag;
- }
- $this->mStripState->general->setPair( $marker, $output );
- }
+ $text = $this->replaceTransparentTags( $text );
$text = $this->mStripState->unstripGeneral( $text );
$text = Sanitizer::normalizeCharReferences( $text );
@@ -415,7 +456,9 @@ class Parser {
$this->mOutput->setText( $text );
$this->mRevisionId = $oldRevisionId;
+ $this->mRevisionObject = $oldRevisionObject;
$this->mRevisionTimestamp = $oldRevisionTimestamp;
+ $this->mRevisionUser = $oldRevisionUser;
wfProfileOut( $fname );
wfProfileOut( __METHOD__ );
@@ -430,6 +473,8 @@ class Parser {
*
* @param $text String: text extension wants to have parsed
* @param $frame PPFrame: The frame to use for expanding any template variables
+ *
+ * @return string
*/
function recursiveTagParse( $text, $frame=false ) {
wfProfileIn( __METHOD__ );
@@ -446,10 +491,7 @@ class Parser {
*/
function preprocess( $text, Title $title, ParserOptions $options, $revid = null ) {
wfProfileIn( __METHOD__ );
- $this->mOptions = $options;
- $this->clearState();
- $this->setOutputType( self::OT_PREPROCESS );
- $this->setTitle( $title );
+ $this->startParse( $title, $options, self::OT_PREPROCESS, true );
if ( $revid !== null ) {
$this->mRevisionId = $revid;
}
@@ -469,10 +511,7 @@ class Parser {
*/
public function getPreloadText( $text, Title $title, ParserOptions $options ) {
# Parser (re)initialisation
- $this->mOptions = $options;
- $this->clearState();
- $this->setOutputType( self::OT_PLAIN );
- $this->setTitle( $title );
+ $this->startParse( $title, $options, self::OT_PLAIN, true );
$flags = PPFrame::NO_ARGS | PPFrame::NO_TEMPLATES;
$dom = $this->preprocessToDom( $text, self::PTD_FOR_INCLUSION );
@@ -484,21 +523,30 @@ class Parser {
/**
* Get a random string
*
- * @private
- * @static
+ * @return string
*/
- static private function getRandomString() {
+ static public function getRandomString() {
return dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) );
}
/**
+ * Set the current user.
+ * Should only be used when doing pre-save transform.
+ *
+ * @param $user Mixed: User object or null (to reset)
+ */
+ function setUser( $user ) {
+ $this->mUser = $user;
+ }
+
+ /**
* Accessor for mUniqPrefix.
*
* @return String
*/
public function uniqPrefix() {
if ( !isset( $this->mUniqPrefix ) ) {
- # @todo Fixme: this is probably *horribly wrong*
+ # @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.
@@ -511,11 +559,13 @@ class Parser {
/**
* Set the context title
+ *
+ * @param $t Title
*/
function setTitle( $t ) {
- if ( !$t || $t instanceof FakeTitle ) {
- $t = Title::newFromText( 'NO TITLE' );
- }
+ if ( !$t || $t instanceof FakeTitle ) {
+ $t = Title::newFromText( 'NO TITLE' );
+ }
if ( strval( $t->getFragment() ) !== '' ) {
# Strip the fragment to avoid various odd effects
@@ -599,19 +649,47 @@ class Parser {
return wfSetVar( $this->mOptions, $x );
}
+ /**
+ * @return int
+ */
function nextLinkID() {
return $this->mLinkID++;
}
- function getFunctionLang() {
- global $wgLang, $wgContLang;
+ /**
+ * @param $id int
+ */
+ function setLinkID( $id ) {
+ $this->mLinkID = $id;
+ }
+ /**
+ * @return Language
+ */
+ function getFunctionLang() {
$target = $this->mOptions->getTargetLanguage();
if ( $target !== null ) {
return $target;
- } else {
- return $this->mOptions->getInterfaceMessage() ? $wgLang : $wgContLang;
+ } elseif( $this->mOptions->getInterfaceMessage() ) {
+ global $wgLang;
+ return $wgLang;
+ } elseif( is_null( $this->mTitle ) ) {
+ throw new MWException( __METHOD__.': $this->mTitle is null' );
}
+ return $this->mTitle->getPageLanguage();
+ }
+
+ /**
+ * Get a User object either from $this->mUser, if set, or from the
+ * ParserOptions object otherwise
+ *
+ * @return User object
+ */
+ function getUser() {
+ if ( !is_null( $this->mUser ) ) {
+ return $this->mUser;
+ }
+ return $this->mOptions->getUser();
}
/**
@@ -638,15 +716,13 @@ class Parser {
* array( 'param' => 'x' ),
* '<element param="x">tag content</element>' ) )
*
- * @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
+ * @param $elements array list of element names. Comments are always extracted.
+ * @param $text string Source text string.
+ * @param $matches array Out parameter, Array: extracted tags
+ * @param $uniq_prefix string
* @return String: stripped text
- *
- * @static
*/
- public function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = '' ) {
+ public static function extractTagsAndParams( $elements, $text, &$matches, $uniq_prefix = '' ) {
static $n = 1;
$stripped = '';
$matches = array();
@@ -710,77 +786,33 @@ class Parser {
/**
* Get a list of strippable XML-like elements
+ *
+ * @return array
*/
function getStripList() {
return $this->mStripList;
}
/**
- * @deprecated use replaceVariables
- */
- function strip( $text, $state, $stripcomments = false , $dontstrip = array() ) {
- return $text;
- }
-
- /**
- * Restores pre, math, and other extensions removed by strip()
- *
- * always call unstripNoWiki() after this one
- * @private
- * @deprecated use $this->mStripState->unstrip()
- */
- function unstrip( $text, $state ) {
- return $state->unstripGeneral( $text );
- }
-
- /**
- * Always call this after unstrip() to preserve the order
- *
- * @private
- * @deprecated use $this->mStripState->unstrip()
- */
- function unstripNoWiki( $text, $state ) {
- return $state->unstripNoWiki( $text );
- }
-
- /**
- * @deprecated use $this->mStripState->unstripBoth()
- */
- function unstripForHTML( $text ) {
- return $this->mStripState->unstripBoth( $text );
- }
-
- /**
* Add an item to the strip state
* Returns the unique tag which must be inserted into the stripped text
* The tag will be replaced with the original text in unstrip()
- *
- * @private
*/
function insertStripItem( $text ) {
$rnd = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
$this->mMarkerIndex++;
- $this->mStripState->general->setPair( $rnd, $text );
+ $this->mStripState->addGeneral( $rnd, $text );
return $rnd;
}
/**
- * Interface with html tidy
- * @deprecated Use MWTidy::tidy()
- */
- public static function tidy( $text ) {
- wfDeprecated( __METHOD__ );
- return MWTidy::tidy( $text );
- }
-
- /**
* parse the wiki syntax used to render tables
*
* @private
*/
function doTableStuff( $text ) {
wfProfileIn( __METHOD__ );
-
+
$lines = StringUtils::explode( "\n", $text );
$out = '';
$td_history = array(); # Is currently a td tag open?
@@ -793,7 +825,7 @@ class Parser {
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;
}
@@ -1043,7 +1075,7 @@ class Parser {
*/
function doMagicLinks( $text ) {
wfProfileIn( __METHOD__ );
- $prots = $this->mUrlProtocols;
+ $prots = wfUrlProtocolsWithoutProtRel();
$urlChar = self::EXT_LINK_URL_CLASS;
$text = preg_replace_callback(
'!(?: # Start cases
@@ -1052,15 +1084,20 @@ class Parser {
(\\b(?:$prots)$urlChar+) | # m[3]: Free external links" . '
(?:RFC|PMID)\s+([0-9]+) | # m[4]: RFC or PMID, capture number
ISBN\s+(\b # m[5]: ISBN, capture number
- (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix
- (?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters
- [0-9Xx] # check digit
- \b)
+ (?: 97[89] [\ \-]? )? # optional 13-digit ISBN prefix
+ (?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters
+ [0-9Xx] # check digit
+ \b)
)!x', array( &$this, 'magicLinkCallback' ), $text );
wfProfileOut( __METHOD__ );
return $text;
}
+ /**
+ * @throws MWException
+ * @param $m array
+ * @return HTML|string
+ */
function magicLinkCallback( $m ) {
if ( isset( $m[1] ) && $m[1] !== '' ) {
# Skip anchor
@@ -1087,10 +1124,8 @@ class Parser {
throw new MWException( __METHOD__.': unrecognised match type "' .
substr( $m[0], 0, 20 ) . '"' );
}
- $url = wfMsgForContent( $urlmsg, $id);
- $sk = $this->mOptions->getSkin( $this->mTitle );
- $la = $sk->getExternalLinkAttributes( "external $CssClass" );
- return "<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>";
+ $url = wfMsgForContent( $urlmsg, $id );
+ return Linker::makeExternalLink( $url, "{$keyword} {$id}", true, $CssClass );
} elseif ( isset( $m[5] ) && $m[5] !== '' ) {
# ISBN
$isbn = $m[5];
@@ -1117,7 +1152,6 @@ class Parser {
global $wgContLang;
wfProfileIn( __METHOD__ );
- $sk = $this->mOptions->getSkin( $this->mTitle );
$trail = '';
# The characters '<' and '>' (which were escaped by
@@ -1148,7 +1182,7 @@ class Parser {
$text = $this->maybeMakeExternalImage( $url );
if ( $text === false ) {
# Not an image, make a link
- $text = $sk->makeExternalLink( $url, $wgContLang->markNoConversion($url), true, 'free',
+ $text = Linker::makeExternalLink( $url, $wgContLang->markNoConversion($url), true, 'free',
$this->getExternalLinkAttribs( $url ) );
# Register it in the output object...
# Replace unnecessary URL escape codes with their equivalent characters
@@ -1355,7 +1389,7 @@ class Parser {
/**
* Replace external links (REL)
*
- * Note: this is all very hackish and the order of execution matters a lot.
+ * Note: this is all very hackish and the order of execution matters a lot.
* Make sure to run maintenance/parserTests.php if you change this code.
*
* @private
@@ -1364,8 +1398,6 @@ class Parser {
global $wgContLang;
wfProfileIn( __METHOD__ );
- $sk = $this->mOptions->getSkin( $this->mTitle );
-
$bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
$s = array_shift( $bits );
@@ -1399,16 +1431,10 @@ class Parser {
# 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 ) {
- $langObj = $this->getFunctionLang();
- $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
- $linktype = 'autonumber';
- } else {
- # Otherwise just use the URL
- $text = htmlspecialchars( $url );
- $linktype = 'free';
- }
+ # Autonumber
+ $langObj = $this->getFunctionLang();
+ $text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
+ $linktype = 'autonumber';
} else {
# Have link text, e.g. [http://domain.tld/some.link text]s
# Check for trail
@@ -1423,7 +1449,7 @@ class Parser {
# This means that users can paste URLs directly into the text
# Funny characters like ö aren't valid in URLs anyway
# This was changed in August 2004
- $s .= $sk->makeExternalLink( $url, $text, false, $linktype,
+ $s .= Linker::makeExternalLink( $url, $text, false, $linktype,
$this->getExternalLinkAttribs( $url ) ) . $dtrail . $trail;
# Register link in the output object.
@@ -1449,23 +1475,12 @@ class Parser {
*/
function getExternalLinkAttribs( $url = false ) {
$attribs = array();
- global $wgNoFollowLinks, $wgNoFollowNsExceptions;
+ global $wgNoFollowLinks, $wgNoFollowNsExceptions, $wgNoFollowDomainExceptions;
$ns = $this->mTitle->getNamespace();
- if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions ) ) {
+ if ( $wgNoFollowLinks && !in_array( $ns, $wgNoFollowNsExceptions ) &&
+ !wfMatchesDomainList( $url, $wgNoFollowDomainExceptions ) )
+ {
$attribs['rel'] = 'nofollow';
-
- global $wgNoFollowDomainExceptions;
- if ( $wgNoFollowDomainExceptions ) {
- $bits = wfParseUrl( $url );
- if ( is_array( $bits ) && isset( $bits['host'] ) ) {
- foreach ( $wgNoFollowDomainExceptions as $domain ) {
- if ( substr( $bits['host'], -strlen( $domain ) ) == $domain ) {
- unset( $attribs['rel'] );
- break;
- }
- }
- }
- }
}
if ( $this->mOptions->getExternalLinkTarget() ) {
$attribs['target'] = $this->mOptions->getExternalLinkTarget();
@@ -1473,7 +1488,6 @@ class Parser {
return $attribs;
}
-
/**
* Replace unusual URL escape codes with their equivalent characters
*
@@ -1513,7 +1527,6 @@ class Parser {
* @private
*/
function maybeMakeExternalImage( $url ) {
- $sk = $this->mOptions->getSkin( $this->mTitle );
$imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
$imagesexception = !empty( $imagesfrom );
$text = false;
@@ -1532,10 +1545,10 @@ class Parser {
$imagematch = false;
}
if ( $this->mOptions->getAllowExternalImages()
- || ( $imagesexception && $imagematch ) ) {
+ || ( $imagesexception && $imagematch ) ) {
if ( preg_match( self::EXT_IMAGE_REGEX, $url ) ) {
# Image found
- $text = $sk->makeExternalImage( $url );
+ $text = Linker::makeExternalImage( $url );
}
}
if ( !$text && $this->mOptions->getEnableImageWhitelist()
@@ -1548,7 +1561,7 @@ class Parser {
}
if ( preg_match( '/' . str_replace( '/', '\\/', $entry ) . '/i', $url ) ) {
# Image matches a whitelist entry
- $text = $sk->makeExternalImage( $url );
+ $text = Linker::makeExternalImage( $url );
break;
}
}
@@ -1589,10 +1602,9 @@ class Parser {
$e1_img = "/^([{$tc}]+)\\|(.*)\$/sD";
}
- $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
$s = $a->current();
@@ -1684,14 +1696,14 @@ class Parser {
# fix up urlencoded title texts
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;'), rawurldecode( $m[1] ) );
}
$trail = $m[3];
} elseif ( preg_match( $e1_img, $line, $m ) ) { # Invalid, but might be an image with a link in its caption
$might_be_img = true;
$text = $m[2];
if ( strpos( $m[1], '%' ) !== false ) {
- $m[1] = urldecode( $m[1] );
+ $m[1] = rawurldecode( $m[1] );
}
$trail = "";
} else { # Invalid form; output directly
@@ -1705,7 +1717,7 @@ class Parser {
# Don't allow internal links to pages containing
# PROTO: where PROTO is a valid URL protocol; these
# should be external links.
- if ( preg_match( '/^\b(?:' . wfUrlProtocols() . ')/', $m[1] ) ) {
+ if ( preg_match( '/^(?:' . wfUrlProtocols() . ')/', $m[1] ) ) {
$s .= $prefix . '[[' . $line ;
wfProfileOut( __METHOD__."-misc" );
continue;
@@ -1787,9 +1799,10 @@ class Parser {
$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);
+ # [[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
@@ -1823,14 +1836,13 @@ class Parser {
$holders->merge( $this->replaceInternalLinks2( $text ) );
}
# cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
- $s .= $prefix . $this->armorLinks( $this->makeImage( $nt, $text, $holders ) ) . $trail;
+ $s .= $prefix . $this->armorLinks(
+ $this->makeImage( $nt, $text, $holders ) ) . $trail;
} else {
$s .= $prefix . $trail;
}
- $this->mOutput->addImage( $nt->getDBkey() );
wfProfileOut( __METHOD__."-image" );
continue;
-
}
if ( $ns == NS_CATEGORY ) {
@@ -1851,7 +1863,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;
@@ -1861,26 +1873,24 @@ class Parser {
# Self-link checking
if ( $nt->getFragment() === '' && $ns != NS_SPECIAL ) {
if ( in_array( $nt->getPrefixedText(), $selflink, true ) ) {
- $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail );
+ $s .= $prefix . Linker::makeSelfLinkObj( $nt, $text, '', $trail );
continue;
}
}
# NS_MEDIA is a pseudo-namespace for linking directly to a file
- # FIXME: Should do batch file existence checks, see comment below
+ # @todo FIXME: Should do batch file existence checks, see comment below
if ( $ns == NS_MEDIA ) {
wfProfileIn( __METHOD__."-media" );
# Give extensions a chance to select the file revision for us
- $skip = $time = false;
- wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$nt, &$skip, &$time ) );
- if ( $skip ) {
- $link = $sk->link( $nt );
- } else {
- $link = $sk->makeMediaLinkObj( $nt, $text, $time );
- }
+ $time = $sha1 = $descQuery = false;
+ wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ array( $this, $nt, &$time, &$sha1, &$descQuery ) );
+ # Fetch and register the file (file title may be different via hooks)
+ list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $time, $sha1 );
# Cloak with NOPARSE to avoid replacement in replaceExternalLinks
- $s .= $prefix . $this->armorLinks( $link ) . $trail;
- $this->mOutput->addImage( $nt->getDBkey() );
+ $s .= $prefix . $this->armorLinks(
+ Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
wfProfileOut( __METHOD__."-media" );
continue;
}
@@ -1889,14 +1899,14 @@ class Parser {
# Some titles, such as valid special pages or files in foreign repos, should
# be shown as bluelinks even though they're not included in the page table
#
- # FIXME: isAlwaysKnown() can be expensive for file links; we should really do
+ # @todo 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() ) {
$this->mOutput->addLink( $nt );
- $s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix );
+ $s .= $this->makeKnownLinkHolder( $nt, $text, array(), $trail, $prefix );
} else {
# Links will be added to the output link list after checking
- $s .= $holders->makeHolder( $nt, $text, '', $trail, $prefix );
+ $s .= $holders->makeHolder( $nt, $text, array(), $trail, $prefix );
}
wfProfileOut( __METHOD__."-always_known" );
}
@@ -1905,18 +1915,6 @@ class Parser {
}
/**
- * Make a link placeholder. The text returned can be later resolved to a real link with
- * replaceLinkHolders(). This is done for two reasons: firstly to avoid further
- * parsing of interwiki links, and secondly to allow all existence checks and
- * article length checks (for stub links) to be bundled into a single query.
- *
- * @deprecated
- */
- function makeLinkHolder( &$nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
- return $this->mLinkHolders->makeHolder( $nt, $text, $query, $trail, $prefix );
- }
-
- /**
* Render a forced-blue link inline; protect against double expansion of
* URLs if we're in a mode that prepends full URL prefixes to internal links.
* Since this little disaster has to split off the trail text to avoid
@@ -1925,16 +1923,23 @@ class Parser {
*
* @param $nt Title
* @param $text String
- * @param $query String
+ * @param $query Array or String
* @param $trail String
* @param $prefix String
* @return String: HTML-wikitext mix oh yuck
*/
- function makeKnownLinkHolder( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
+ function makeKnownLinkHolder( $nt, $text = '', $query = array(), $trail = '', $prefix = '' ) {
list( $inside, $trail ) = Linker::splitTrail( $trail );
- $sk = $this->mOptions->getSkin( $this->mTitle );
- # FIXME: use link() instead of deprecated makeKnownLinkObj()
- $link = $sk->makeKnownLinkObj( $nt, $text, $query, $inside, $prefix );
+
+ if ( is_string( $query ) ) {
+ $query = wfCgiToArray( $query );
+ }
+ if ( $text == '' ) {
+ $text = htmlspecialchars( $nt->getPrefixedText() );
+ }
+
+ $link = Linker::linkKnown( $nt, "$prefix$text$inside", array(), $query );
+
return $this->armorLinks( $link ) . $trail;
}
@@ -1977,6 +1982,8 @@ class Parser {
/**#@+
* Used by doBlockLevels()
* @private
+ *
+ * @return string
*/
function closeParagraph() {
$result = '';
@@ -2001,7 +2008,7 @@ class Parser {
}
for ( $i = 0; $i < $shorter; ++$i ) {
- if ( $st1{$i} != $st2{$i} ) {
+ if ( $st1[$i] != $st2[$i] ) {
break;
}
}
@@ -2012,6 +2019,8 @@ class Parser {
* These next three functions open, continue, and close the list
* element appropriate to the prefix character passed into them.
* @private
+ *
+ * @return string
*/
function openList( $char ) {
$result = $this->closeParagraph();
@@ -2036,6 +2045,8 @@ class Parser {
* TODO: document
* @param $char String
* @private
+ *
+ * @return string
*/
function nextItem( $char ) {
if ( '*' === $char || '#' === $char ) {
@@ -2060,6 +2071,8 @@ class Parser {
* TODO: document
* @param $char String
* @private
+ *
+ * @return string
*/
function closeList( $char ) {
if ( '*' === $char ) {
@@ -2178,7 +2191,7 @@ class Parser {
$output .= $this->openList( $char );
if ( ';' === $char ) {
- # FIXME: This is dupe of code above
+ # @todo FIXME: This is dupe of code above
if ( $this->findColonNoLinks( $t, $term, $t2 ) !== false ) {
$t = $t2;
$output .= $term . $this->nextItem( ':' );
@@ -2200,7 +2213,7 @@ class Parser {
'<td|<th|<\\/?div|<hr|<\\/pre|<\\/p|'.$this->mUniqPrefix.'-pre|<\\/li|<\\/ul|<\\/ol|<\\/?center)/iS', $t );
if ( $openmatch or $closematch ) {
$paragraphStack = false;
- # TODO bug 5718: paragraph closed
+ # TODO bug 5718: paragraph closed
$output .= $this->closeParagraph();
if ( $preOpenMatch and !$preCloseMatch ) {
$this->mInPre = true;
@@ -2299,7 +2312,7 @@ class Parser {
$stack = 0;
$len = strlen( $str );
for( $i = 0; $i < $len; $i++ ) {
- $c = $str{$i};
+ $c = $str[$i];
switch( $state ) {
# (Using the number is a performance hack for common cases)
@@ -2435,6 +2448,9 @@ class Parser {
* Return value of a magic variable (like PAGENAME)
*
* @private
+ *
+ * @param $index integer
+ * @param $frame PPFrame
*/
function getVariableValue( $index, $frame=false ) {
global $wgContLang, $wgSitename, $wgServer;
@@ -2521,25 +2537,25 @@ class Parser {
$value = wfEscapeWikiText( $this->mTitle->getText() );
break;
case 'pagenamee':
- $value = $this->mTitle->getPartialURL();
+ $value = wfEscapeWikiText( $this->mTitle->getPartialURL() );
break;
case 'fullpagename':
$value = wfEscapeWikiText( $this->mTitle->getPrefixedText() );
break;
case 'fullpagenamee':
- $value = $this->mTitle->getPrefixedURL();
+ $value = wfEscapeWikiText( $this->mTitle->getPrefixedURL() );
break;
case 'subpagename':
$value = wfEscapeWikiText( $this->mTitle->getSubpageText() );
break;
case 'subpagenamee':
- $value = $this->mTitle->getSubpageUrlForm();
+ $value = wfEscapeWikiText( $this->mTitle->getSubpageUrlForm() );
break;
case 'basepagename':
$value = wfEscapeWikiText( $this->mTitle->getBaseText() );
break;
case 'basepagenamee':
- $value = wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) );
+ $value = wfEscapeWikiText( wfUrlEncode( str_replace( ' ', '_', $this->mTitle->getBaseText() ) ) );
break;
case 'talkpagename':
if ( $this->mTitle->canTalk() ) {
@@ -2552,7 +2568,7 @@ class Parser {
case 'talkpagenamee':
if ( $this->mTitle->canTalk() ) {
$talkPage = $this->mTitle->getTalkPage();
- $value = $talkPage->getPrefixedUrl();
+ $value = wfEscapeWikiText( $talkPage->getPrefixedUrl() );
} else {
$value = '';
}
@@ -2563,7 +2579,7 @@ class Parser {
break;
case 'subjectpagenamee':
$subjPage = $this->mTitle->getSubjectPage();
- $value = $subjPage->getPrefixedUrl();
+ $value = wfEscapeWikiText( $subjPage->getPrefixedUrl() );
break;
case 'revisionid':
# Let the edit saving system know we should parse the page
@@ -2719,10 +2735,8 @@ class Parser {
case 'server':
return $wgServer;
case 'servername':
- wfSuppressWarnings(); # May give an E_WARNING in PHP < 5.3.3
- $serverName = parse_url( $wgServer, PHP_URL_HOST );
- wfRestoreWarnings();
- return $serverName ? $serverName : $wgServer;
+ $serverParts = wfParseUrl( $wgServer );
+ return $serverParts && isset( $serverParts['host'] ) ? $serverParts['host'] : $wgServer;
case 'scriptpath':
return $wgScriptPath;
case 'stylepath':
@@ -2783,6 +2797,8 @@ class Parser {
* dependency requirements.
*
* @private
+ *
+ * @return PPNode
*/
function preprocessToDom( $text, $flags = 0 ) {
$dom = $this->getPreprocessor()->preprocessToObj( $text, $flags );
@@ -2791,6 +2807,8 @@ class Parser {
/**
* Return a three-element array: leading whitespace, string contents, trailing whitespace
+ *
+ * @return array
*/
public static function splitWhitespace( $s ) {
$ltrimmed = ltrim( $s );
@@ -2821,6 +2839,8 @@ class Parser {
* Providing arguments this way may be useful for extensions wishing to perform variable replacement explicitly.
* @param $argsOnly Boolean: only do argument (triple-brace) expansion, not double-brace expansion
* @private
+ *
+ * @return string
*/
function replaceVariables( $text, $frame = false, $argsOnly = false ) {
# Is there any text? Also, Prevent too big inclusions!
@@ -2844,7 +2864,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.
+ *
+ * @return array
+ */
static function createAssocArgs( $args ) {
$assocArgs = array();
$index = 1;
@@ -2918,7 +2942,7 @@ class Parser {
$isLocalObj = false; # $text is a DOM node needing expansion in the current frame
# Title object, where $text came from
- $title = null;
+ $title = false;
# $part1 is the bit before the first |, and must contain only title characters.
# Various prefixes will be stripped from it later.
@@ -2930,8 +2954,10 @@ class Parser {
$originalTitle = $part1;
# $args is a list of argument nodes, starting from index 0, not including $part1
+ # @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object
$args = ( null == $piece['parts'] ) ? array() : $piece['parts'];
wfProfileOut( __METHOD__.'-setup' );
+ wfProfileIn( __METHOD__."-title-$originalTitle" );
# SUBST
wfProfileIn( __METHOD__.'-modifiers' );
@@ -3100,7 +3126,7 @@ class Parser {
&& $this->mOptions->getAllowSpecialInclusion()
&& $this->ot['html'] )
{
- $text = SpecialPage::capturePath( $title );
+ $text = SpecialPageFactory::capturePath( $title );
if ( is_string( $text ) ) {
$found = true;
$isHTML = true;
@@ -3150,6 +3176,7 @@ class Parser {
# Recover the source wikitext and return it
if ( !$found ) {
$text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
+ wfProfileOut( __METHOD__."-title-$originalTitle" );
wfProfileOut( __METHOD__ );
return array( 'object' => $text );
}
@@ -3218,6 +3245,7 @@ class Parser {
$ret = array( 'text' => $text );
}
+ wfProfileOut( __METHOD__."-title-$originalTitle" );
wfProfileOut( __METHOD__ );
return $ret;
}
@@ -3225,6 +3253,8 @@ class Parser {
/**
* Get the semi-parsed DOM representation of a template with a given title,
* and its redirect destination title. Cached.
+ *
+ * @return array
*/
function getTemplateDom( $title ) {
$cacheTitle = $title;
@@ -3260,6 +3290,8 @@ class Parser {
/**
* Fetch the unparsed text of a template and register a reference to it.
+ * @param Title $title
+ * @return Array ( string or false, Title )
*/
function fetchTemplateAndTitle( $title ) {
$templateCb = $this->mOptions->getTemplateCallback(); # Defaults to Parser::statelessFetchTemplate()
@@ -3274,6 +3306,11 @@ class Parser {
return array( $text, $finalTitle );
}
+ /**
+ * Fetch the unparsed text of a template and register a reference to it.
+ * @param Title $title
+ * @return mixed string or false
+ */
function fetchTemplate( $title ) {
$rv = $this->fetchTemplateAndTitle( $title );
return $rv[0];
@@ -3282,8 +3319,10 @@ class Parser {
/**
* Static function to get a template
* Can be overridden via ParserOptions::setTemplateCallback().
+ *
+ * @return array
*/
- static function statelessFetchTemplate( $title, $parser=false ) {
+ static function statelessFetchTemplate( $title, $parser = false ) {
$text = $skip = false;
$finalTitle = $title;
$deps = array();
@@ -3292,17 +3331,22 @@ class Parser {
for ( $i = 0; $i < 2 && is_object( $title ); $i++ ) {
# Give extensions a chance to select the revision instead
$id = false; # Assume current
- wfRunHooks( 'BeforeParserFetchTemplateAndtitle', array( $parser, &$title, &$skip, &$id ) );
+ wfRunHooks( 'BeforeParserFetchTemplateAndtitle',
+ array( $parser, $title, &$skip, &$id ) );
if ( $skip ) {
$text = false;
$deps[] = array(
- 'title' => $title,
- 'page_id' => $title->getArticleID(),
- 'rev_id' => null );
+ 'title' => $title,
+ 'page_id' => $title->getArticleID(),
+ 'rev_id' => null
+ );
break;
}
- $rev = $id ? Revision::newFromId( $id ) : Revision::newFromTitle( $title );
+ # Get the revision
+ $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 ) {
@@ -3311,20 +3355,27 @@ class Parser {
}
$deps[] = array(
- 'title' => $title,
- 'page_id' => $title->getArticleID(),
- 'rev_id' => $rev_id );
+ 'title' => $title,
+ 'page_id' => $title->getArticleID(),
+ 'rev_id' => $rev_id );
+ if ( $rev && !$title->equals( $rev->getTitle() ) ) {
+ # We fetched a rev from a different title; register it too...
+ $deps[] = array(
+ 'title' => $rev->getTitle(),
+ 'page_id' => $rev->getPage(),
+ 'rev_id' => $rev_id );
+ }
if ( $rev ) {
$text = $rev->getText();
} elseif ( $title->getNamespace() == NS_MEDIAWIKI ) {
global $wgContLang;
- $message = $wgContLang->lcfirst( $title->getText() );
- $text = wfMsgForContentNoTrans( $message );
- if ( wfEmptyMsg( $message, $text ) ) {
+ $message = wfMessage( $wgContLang->lcfirst( $title->getText() ) )->inContentLanguage();
+ if ( !$message->exists() ) {
$text = false;
break;
}
+ $text = $message->plain();
} else {
break;
}
@@ -3342,7 +3393,56 @@ class Parser {
}
/**
+ * Fetch a file and its title and register a reference to it.
+ * @param Title $title
+ * @param string $time MW timestamp
+ * @param string $sha1 base 36 SHA-1
+ * @return mixed File or false
+ */
+ function fetchFile( $title, $time = false, $sha1 = false ) {
+ $res = $this->fetchFileAndTitle( $title, $time, $sha1 );
+ return $res[0];
+ }
+
+ /**
+ * Fetch a file and its title and register a reference to it.
+ * @param Title $title
+ * @param string $time MW timestamp
+ * @param string $sha1 base 36 SHA-1
+ * @return Array ( File or false, Title of file )
+ */
+ function fetchFileAndTitle( $title, $time = false, $sha1 = false ) {
+ if ( $time === '0' ) {
+ $file = false; // broken thumbnail forced by hook
+ } elseif ( $sha1 ) { // get by (sha1,timestamp)
+ $file = RepoGroup::singleton()->findFileFromKey( $sha1, array( 'time' => $time ) );
+ } else { // get by (name,timestamp)
+ $file = wfFindFile( $title, array( 'time' => $time ) );
+ }
+ $time = $file ? $file->getTimestamp() : false;
+ $sha1 = $file ? $file->getSha1() : false;
+ # Register the file as a dependency...
+ $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
+ if ( $file && !$title->equals( $file->getTitle() ) ) {
+ # Update fetched file title
+ $title = $file->getTitle();
+ if ( is_null( $file->getRedirectedTitle() ) ) {
+ # This file was not a redirect, but the title does not match.
+ # Register under the new name because otherwise the link will
+ # get lost.
+ $this->mOutput->addImage( $title->getDBkey(), $time, $sha1 );
+ }
+ }
+ return array( $file, $title );
+ }
+
+ /**
* Transclude an interwiki link.
+ *
+ * @param $title Title
+ * @param $action
+ *
+ * @return string
*/
function interwikiTransclude( $title, $action ) {
global $wgEnableScaryTranscluding;
@@ -3359,6 +3459,10 @@ class Parser {
return $this->fetchScaryTemplateMaybeFromCache( $url );
}
+ /**
+ * @param $url string
+ * @return Mixed|String
+ */
function fetchScaryTemplateMaybeFromCache( $url ) {
global $wgTranscludeCacheExpiry;
$dbr = wfGetDB( DB_SLAVE );
@@ -3383,10 +3487,14 @@ class Parser {
return $text;
}
-
/**
* Triple brace replacement -- used for template arguments
* @private
+ *
+ * @param $peice array
+ * @param $frame PPFrame
+ *
+ * @return array
*/
function argSubstitution( $piece, $frame ) {
wfProfileIn( __METHOD__ );
@@ -3399,9 +3507,9 @@ class Parser {
$text = $frame->getArgument( $argName );
if ( $text === false && $parts->getLength() > 0
&& (
- $this->ot['html']
- || $this->ot['pre']
- || ( $this->ot['wiki'] && $frame->isTemplate() )
+ $this->ot['html']
+ || $this->ot['pre']
+ || ( $this->ot['wiki'] && $frame->isTemplate() )
)
) {
# No match in frame, use the supplied default
@@ -3440,6 +3548,8 @@ class Parser {
* inner Contents of extension element
* noClose Original text did not have a close tag
* @param $frame PPFrame
+ *
+ * @return string
*/
function extensionSubstitution( $params, $frame ) {
$name = $frame->expand( $params['name'] );
@@ -3508,9 +3618,9 @@ class Parser {
if ( $markerType === 'none' ) {
return $output;
} elseif ( $markerType === 'nowiki' ) {
- $this->mStripState->nowiki->setPair( $marker, $output );
+ $this->mStripState->addNoWiki( $marker, $output );
} elseif ( $markerType === 'general' ) {
- $this->mStripState->general->setPair( $marker, $output );
+ $this->mStripState->addGeneral( $marker, $output );
} else {
throw new MWException( __METHOD__.': invalid marker type' );
}
@@ -3525,7 +3635,7 @@ class Parser {
* @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 ) ) {
+ if ( $this->mIncludeSizes[$type] + $size > $this->mOptions->getMaxIncludeSize() ) {
return false;
} else {
$this->mIncludeSizes[$type] += $size;
@@ -3582,7 +3692,7 @@ class Parser {
}
# (bug 8068) Allow control over whether robots index a page.
#
- # FIXME (bug 14899): __INDEX__ always overrides __NOINDEX__ here! This
+ # @todo 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() ) {
$this->mOutput->setIndexPolicy( 'noindex' );
@@ -3592,7 +3702,7 @@ class Parser {
$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, '' );
@@ -3610,6 +3720,10 @@ class Parser {
* @return Boolean: whether the addition was successful
*/
protected function addTrackingCategory( $msg ) {
+ if ( $this->mTitle->getNamespace() === NS_SPECIAL ) {
+ wfDebug( __METHOD__.": Not adding tracking category $msg to special page!\n" );
+ return false;
+ }
$cat = wfMsgForContent( $msg );
# Allow tracking categories to be disabled by setting them to "-"
@@ -3643,16 +3757,17 @@ class Parser {
* @private
*/
function formatHeadings( $text, $origText, $isMain=true ) {
- global $wgMaxTocLevel, $wgContLang, $wgHtml5, $wgExperimentalHtmlIds;
+ global $wgMaxTocLevel, $wgHtml5, $wgExperimentalHtmlIds;
- $doNumberHeadings = $this->mOptions->getNumberHeadings();
-
# Inhibit editsection links if requested in the page
if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
$showEditLink = 0;
} else {
$showEditLink = $this->mOptions->getEditSection();
}
+ if ( $showEditLink ) {
+ $this->mOutput->setEditSectionTokens( true );
+ }
# Get all headlines for numbering them and adding funky stuff like [edit]
# links - this is for later, but we need the number of headlines right now
@@ -3683,9 +3798,6 @@ class Parser {
$enoughToc = true;
}
- # We need this to perform operations on the HTML
- $sk = $this->mOptions->getSkin( $this->mTitle );
-
# headline counter
$headlineCount = 0;
$numVisible = 0;
@@ -3736,7 +3848,7 @@ class Parser {
$sublevelCount[$toclevel] = 0;
if ( $toclevel<$wgMaxTocLevel ) {
$prevtoclevel = $toclevel;
- $toc .= $sk->tocIndent();
+ $toc .= Linker::tocIndent();
$numVisible++;
}
} elseif ( $level < $prevlevel && $toclevel > 1 ) {
@@ -3759,16 +3871,16 @@ class Parser {
if ( $toclevel<$wgMaxTocLevel ) {
if ( $prevtoclevel < $wgMaxTocLevel ) {
# Unindent only if the previous toc level was shown :p
- $toc .= $sk->tocUnindent( $prevtoclevel - $toclevel );
+ $toc .= Linker::tocUnindent( $prevtoclevel - $toclevel );
$prevtoclevel = $toclevel;
} else {
- $toc .= $sk->tocLineEnd();
+ $toc .= Linker::tocLineEnd();
}
}
} else {
# No change in level, end TOC line
if ( $toclevel<$wgMaxTocLevel ) {
- $toc .= $sk->tocLineEnd();
+ $toc .= Linker::tocLineEnd();
}
}
@@ -3782,7 +3894,7 @@ class Parser {
if ( $dot ) {
$numbering .= '.';
}
- $numbering .= $wgContLang->formatNum( $sublevelCount[$i] );
+ $numbering .= $this->getFunctionLang()->formatNum( $sublevelCount[$i] );
$dot = 1;
}
}
@@ -3837,10 +3949,10 @@ class Parser {
'noninitial' );
}
- # HTML names must be case-insensitively unique (bug 10721).
- # This does not apply to Unicode characters per
+ # 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.
+ # @todo FIXME: We may be changing them depending on the current locale.
$arrayKey = strtolower( $safeHeadline );
if ( $legacyHeadline === false ) {
$legacyArrayKey = false;
@@ -3861,7 +3973,7 @@ class Parser {
}
# Don't number the heading if it is the only one (looks silly)
- if ( $doNumberHeadings && count( $matches[3] ) > 1) {
+ if ( count( $matches[3] ) > 1 && $this->mOptions->getNumberHeadings() ) {
# the two are different if the line contains a link
$headline = $numbering . ' ' . $headline;
}
@@ -3876,7 +3988,7 @@ class Parser {
$legacyAnchor .= '_' . $refers[$legacyArrayKey];
}
if ( $enoughToc && ( !isset( $wgMaxTocLevel ) || $toclevel < $wgMaxTocLevel ) ) {
- $toc .= $sk->tocLine( $anchor, $tocline,
+ $toc .= Linker::tocLine( $anchor, $tocline,
$numbering, $toclevel, ( $isTemplate ? false : $sectionIndex ) );
}
@@ -3905,18 +4017,33 @@ class Parser {
);
# give headline the correct <h#> tag
- if ( $showEditLink && $sectionIndex !== false ) {
+ if ( $sectionIndex !== false ) {
+ // Output edit section links as markers with styles that can be customized by skins
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", null, $this->mOptions->getUserLang() );
+ $editlinkArgs = array( $titleText, "T-$sectionIndex"/*, null */ );
+ } else {
+ $editlinkArgs = array( $this->mTitle->getPrefixedText(), $sectionIndex, $headlineHint );
+ }
+ // We use a bit of pesudo-xml for editsection markers. The language converter is run later on
+ // Using a UNIQ style marker leads to the converter screwing up the tokens when it converts stuff
+ // And trying to insert strip tags fails too. At this point all real inputted tags have already been escaped
+ // so we don't have to worry about a user trying to input one of these markers directly.
+ // We use a page and section attribute to stop the language converter from converting these important bits
+ // of data, but put the headline hint inside a content block because the language converter is supposed to
+ // be able to convert that piece of data.
+ $editlink = '<mw:editsection page="' . htmlspecialchars($editlinkArgs[0]);
+ $editlink .= '" section="' . htmlspecialchars($editlinkArgs[1]) .'"';
+ if ( isset($editlinkArgs[2]) ) {
+ $editlink .= '>' . $editlinkArgs[2] . '</mw:editsection>';
} else {
- $editlink = $sk->doEditSectionLink( $this->mTitle, $sectionIndex, $headlineHint, $this->mOptions->getUserLang() );
+ $editlink .= '/>';
}
} else {
$editlink = '';
}
- $head[$headlineCount] = $sk->makeHeadline( $level,
+ $head[$headlineCount] = Linker::makeHeadline( $level,
$matches['attrib'][$headlineCount], $anchor, $headline,
$editlink, $legacyAnchor );
@@ -3932,9 +4059,9 @@ class Parser {
if ( $enoughToc ) {
if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
- $toc .= $sk->tocUnindent( $prevtoclevel - 1 );
+ $toc .= Linker::tocUnindent( $prevtoclevel - 1 );
}
- $toc = $sk->tocList( $toc, $this->mOptions->getUserLang() );
+ $toc = Linker::tocList( $toc, $this->mOptions->getUserLang() );
$this->mOutput->setTOCHTML( $toc );
}
@@ -3985,21 +4112,21 @@ class Parser {
* @param $clearState Boolean: whether to clear the parser state first
* @return String: the altered wiki markup
*/
- public function preSaveTransform( $text, Title $title, $user, $options, $clearState = true ) {
- $this->mOptions = $options;
- $this->setTitle( $title );
- $this->setOutputType( self::OT_WIKI );
-
- if ( $clearState ) {
- $this->clearState();
- }
+ public function preSaveTransform( $text, Title $title, User $user, ParserOptions $options, $clearState = true ) {
+ $this->startParse( $title, $options, self::OT_WIKI, $clearState );
+ $this->setUser( $user );
$pairs = array(
"\r\n" => "\n",
);
$text = str_replace( array_keys( $pairs ), array_values( $pairs ), $text );
- $text = $this->pstPass2( $text, $user );
+ if( $options->getPreSaveTransform() ) {
+ $text = $this->pstPass2( $text, $user );
+ }
$text = $this->mStripState->unstripBoth( $text );
+
+ $this->setUser( null ); #Reset
+
return $text;
}
@@ -4032,9 +4159,9 @@ class Parser {
# 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;
+ $msg = wfMessage( $key )->inContentLanguage();
+ if ( $msg->exists() ) {
+ $tzMsg = $msg->text();
}
date_default_timezone_set( $oldtz );
@@ -4093,6 +4220,8 @@ class Parser {
* validated, ready-to-insert wikitext.
* If you have pre-fetched the nickname or the fancySig option, you can
* specify them here to save a database query.
+ * Do not reuse this parser instance after calling getUserSig(),
+ * as it may have changed if it's the $wgParser.
*
* @param $user User
* @param $nickname String: nickname to use or false to use user's default nickname
@@ -4136,11 +4265,9 @@ class Parser {
# If we're still here, make it a link to the user page
$userText = wfEscapeWikiText( $username );
$nickText = wfEscapeWikiText( $nickname );
- if ( $user->isAnon() ) {
- return wfMsgExt( 'signature-anon', array( 'content', 'parsemag' ), $userText, $nickText );
- } else {
- return wfMsgExt( 'signature', array( 'content', 'parsemag' ), $userText, $nickText );
- }
+ $msgName = $user->isAnon() ? 'signature-anon' : 'signature';
+
+ return wfMessage( $msgName, $userText, $nickText )->inContentLanguage()->title( $this->getTitle() )->text();
}
/**
@@ -4177,7 +4304,7 @@ class Parser {
return $text;
}
- # FIXME: regex doesn't respect extension tags or nowiki
+ # @todo FIXME: Regex doesn't respect extension tags or nowiki
# => Move this logic to braceSubstitution()
$substWord = MagicWord::get( 'subst' );
$substRegex = '/\{\{(?!(?:' . $substWord->getBaseRegex() . '))/x' . $substWord->getRegexCase();
@@ -4212,6 +4339,10 @@ class Parser {
* so that an external function can call some class members with confidence
*/
public function startExternalParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
+ $this->startParse( $title, $options, $outputType, $clearState );
+ }
+
+ private function startParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
$this->setTitle( $title );
$this->mOptions = $options;
$this->setOutputType( $outputType );
@@ -4225,10 +4356,10 @@ class Parser {
*
* @param $text String: the text to preprocess
* @param $options ParserOptions: options
+ * @param $title Title object or null to use $wgTitle
* @return String
*/
- public function transformMsg( $text, $options ) {
- global $wgTitle;
+ public function transformMsg( $text, $options, $title = null ) {
static $executing = false;
# Guard against infinite recursion
@@ -4238,7 +4369,10 @@ class Parser {
$executing = true;
wfProfileIn( __METHOD__ );
- $title = $wgTitle;
+ if ( !$title ) {
+ global $wgTitle;
+ $title = $wgTitle;
+ }
if ( !$title ) {
# It's not uncommon having a null $wgTitle in scripts. See r80898
# Create a ghost title in such case
@@ -4254,17 +4388,29 @@ class Parser {
/**
* 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, $frame ) { ... }
*
* Transform and return $text. Use $parser for any required context, e.g. use
* $parser->getTitle() and $parser->getOptions() not $wgTitle or $wgOut->mParserOptions
*
+ * Hooks may return extended information by returning an array, of which the
+ * first numbered element (index 0) must be the return string, and all other
+ * entries are extracted into local variables within an internal function
+ * in the Parser class.
+ *
+ * This interface (introduced r61913) appears to be undocumented, but
+ * 'markerName' is used by some core tag hooks to override which strip
+ * array their results are placed in. **Use great caution if attempting
+ * this interface, as it is not documented and injudicious use could smash
+ * private variables.**
+ *
* @param $tag Mixed: the tag to use, e.g. 'hook' for <hook>
* @param $callback Mixed: the callback function (and object) to use for the tag
* @return The old value of the mTagHooks array associated with the hook
*/
public function setHook( $tag, $callback ) {
$tag = strtolower( $tag );
+ if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) throw new MWException( "Invalid character {$m[0]} in setHook('$tag', ...) call" );
$oldVal = isset( $this->mTagHooks[$tag] ) ? $this->mTagHooks[$tag] : null;
$this->mTagHooks[$tag] = $callback;
if ( !in_array( $tag, $this->mStripList ) ) {
@@ -4274,8 +4420,25 @@ class Parser {
return $oldVal;
}
+ /**
+ * As setHook(), but letting the contents be parsed.
+ *
+ * Transparent tag hooks are like regular XML-style tag hooks, except they
+ * operate late in the transformation sequence, on HTML instead of wikitext.
+ *
+ * This is probably obsoleted by things dealing with parser frames?
+ * The only extension currently using it is geoserver.
+ *
+ * @since 1.10
+ * @todo better document or deprecate this
+ *
+ * @param $tag Mixed: the tag to use, e.g. 'hook' for <hook>
+ * @param $callback Mixed: the callback function (and object) to use for the tag
+ * @return The old value of the mTagHooks array associated with the hook
+ */
function setTransparentTagHook( $tag, $callback ) {
$tag = strtolower( $tag );
+ if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) throw new MWException( "Invalid character {$m[0]} in setTransparentHook('$tag', ...) call" );
$oldVal = isset( $this->mTransparentTagHooks[$tag] ) ? $this->mTransparentTagHooks[$tag] : null;
$this->mTransparentTagHooks[$tag] = $callback;
@@ -4380,6 +4543,7 @@ class Parser {
*/
function setFunctionTagHook( $tag, $callback, $flags ) {
$tag = strtolower( $tag );
+ if ( preg_match( '/[<>\r\n]/', $tag, $m ) ) throw new MWException( "Invalid character {$m[0]} in setFunctionTagHook('$tag', ...) call" );
$old = isset( $this->mFunctionTagHooks[$tag] ) ?
$this->mFunctionTagHooks[$tag] : null;
$this->mFunctionTagHooks[$tag] = array( $callback, $flags );
@@ -4392,7 +4556,7 @@ class Parser {
}
/**
- * FIXME: update documentation. makeLinkObj() is deprecated.
+ * @todo FIXME: Update documentation. makeLinkObj() is deprecated.
* Replace <!--LINK--> link placeholders with actual links, in the buffer
* Placeholders created in Skin::makeLinkObj()
* Returns an array of link CSS classes, indexed by PDBK.
@@ -4420,6 +4584,10 @@ class Parser {
* given as text will return the HTML of a gallery with two images,
* labeled 'The number "1"' and
* 'A tree'.
+ *
+ * @param string $text
+ * @param array $param
+ * @return string HTML
*/
function renderImageGallery( $text, $params ) {
$ig = new ImageGallery();
@@ -4429,8 +4597,6 @@ class Parser {
$ig->setParser( $this );
$ig->setHideBadImages();
$ig->setAttributes( Sanitizer::validateTagAttributes( $params, 'table' ) );
- $ig->useSkin( $this->mOptions->getSkin( $this->mTitle ) );
- $ig->mRevisionId = $this->mRevisionId;
if ( isset( $params['showfilename'] ) ) {
$ig->setShowFilename( true );
@@ -4467,28 +4633,40 @@ class Parser {
}
if ( strpos( $matches[0], '%' ) !== false ) {
- $matches[1] = urldecode( $matches[1] );
+ $matches[1] = rawurldecode( $matches[1] );
}
- $tp = Title::newFromText( $matches[1] );
- $nt =& $tp;
- if ( is_null( $nt ) ) {
+ $title = Title::newFromText( $matches[1], NS_FILE );
+ if ( is_null( $title ) ) {
# Bogus title. Ignore these so we don't bomb out later.
continue;
}
+
+ $label = '';
+ $alt = '';
if ( isset( $matches[3] ) ) {
- $label = $matches[3];
- } else {
- $label = '';
+ // look for an |alt= definition while trying not to break existing
+ // captions with multiple pipes (|) in it, until a more sensible grammar
+ // is defined for images in galleries
+
+ $matches[3] = $this->recursiveTagParse( trim( $matches[3] ) );
+ $altmatches = StringUtils::explode('|', $matches[3]);
+ $magicWordAlt = MagicWord::get( 'img_alt' );
+
+ foreach ( $altmatches as $altmatch ) {
+ $match = $magicWordAlt->matchVariableStartToEnd( $altmatch );
+ if ( $match ) {
+ $alt = $this->stripAltText( $match, false );
+ }
+ else {
+ // concatenate all other pipes
+ $label .= '|' . $altmatch;
+ }
+ }
+ // remove the first pipe
+ $label = substr( $label, 1 );
}
- $html = $this->recursiveTagParse( trim( $label ) );
-
- $ig->add( $nt, $html );
-
- # Only add real images (bug #5586)
- if ( $nt->getNamespace() == NS_FILE ) {
- $this->mOutput->addImage( $nt->getDBkey() );
- }
+ $ig->add( $title, $label, $alt );
}
return $ig->toHTML();
}
@@ -4539,6 +4717,7 @@ class Parser {
* @param $title Title
* @param $options String
* @param $holders LinkHolderArray
+ * @return string HTML
*/
function makeImage( $title, $options, $holders = false ) {
# Check if the options text is of the form "options|alt text"
@@ -4567,23 +4746,23 @@ class Parser {
# * text-bottom
$parts = StringUtils::explode( "|", $options );
- $sk = $this->mOptions->getSkin( $this->mTitle );
# Give extensions a chance to select the file revision for us
- $skip = $time = $descQuery = false;
- wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$title, &$skip, &$time, &$descQuery ) );
+ $time = $sha1 = $descQuery = false;
+ wfRunHooks( 'BeforeParserFetchFileAndTitle',
+ array( $this, $title, &$time, &$sha1, &$descQuery ) );
+ # Fetch and register the file (file title may be different via hooks)
+ list( $file, $title ) = $this->fetchFileAndTitle( $title, $time, $sha1 );
- if ( $skip ) {
- return $sk->link( $title );
- }
-
- # Get the file
- $file = wfFindFile( $title, array( 'time' => $time ) );
# Get parameter map
$handler = $file ? $file->getHandler() : false;
list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
+ if ( !$file ) {
+ $this->addTrackingCategory( 'broken-file-category' );
+ }
+
# Process the input parameters
$caption = '';
$params = array( 'frame' => array(), 'handler' => array(),
@@ -4627,7 +4806,7 @@ class Parser {
switch( $paramName ) {
case 'manualthumb':
case 'alt':
- # @todo Fixme: possibly check validity here for
+ # @todo FIXME: Possibly check validity here for
# manualthumb? downstream behavior seems odd with
# missing manual thumbs.
$validated = true;
@@ -4687,9 +4866,9 @@ class Parser {
# Will the image be presented in a frame, with the caption below?
$imageIsFramed = isset( $params['frame']['frame'] ) ||
- isset( $params['frame']['framed'] ) ||
- isset( $params['frame']['thumbnail'] ) ||
- isset( $params['frame']['manualthumb'] );
+ isset( $params['frame']['framed'] ) ||
+ isset( $params['frame']['thumbnail'] ) ||
+ isset( $params['frame']['manualthumb'] );
# In the old days, [[Image:Foo|text...]] would set alt text. Later it
# came to also set the caption, ordinary text after the image -- which
@@ -4733,7 +4912,8 @@ class Parser {
wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params ) );
# Linker does the rest
- $ret = $sk->makeImageLink2( $title, $file, $params['frame'], $params['handler'], $time, $descQuery, $this->mOptions->getThumbSize() );
+ $ret = Linker::makeImageLink2( $title, $file, $params['frame'], $params['handler'],
+ $time, $descQuery, $this->mOptions->getThumbSize() );
# Give the handler a chance to modify the parser object
if ( $handler ) {
@@ -4743,6 +4923,11 @@ class Parser {
return $ret;
}
+ /**
+ * @param $caption
+ * @param $holders LinkHolderArray
+ * @return mixed|String
+ */
protected function stripAltText( $caption, $holders ) {
# Strip bad stuff out of the title (tooltip). We can't just use
# replaceLinkHoldersText() here, because if this function is called
@@ -4779,7 +4964,6 @@ class Parser {
* @param $text String
* @param $frame PPFrame
* @return String
- * @private
*/
function attributeStripCallback( &$text, $frame = false ) {
$text = $this->replaceVariables( $text, $frame );
@@ -4789,12 +4973,39 @@ class Parser {
/**
* Accessor
+ *
+ * @return array
*/
function getTags() {
return array_merge( array_keys( $this->mTransparentTagHooks ), array_keys( $this->mTagHooks ) );
}
/**
+ * Replace transparent tags in $text with the values given by the callbacks.
+ *
+ * Transparent tag hooks are like regular XML-style tag hooks, except they
+ * operate late in the transformation sequence, on HTML instead of wikitext.
+ */
+ function replaceTransparentTags( $text ) {
+ $matches = array();
+ $elements = array_keys( $this->mTransparentTagHooks );
+ $text = self::extractTagsAndParams( $elements, $text, $matches, $this->mUniqPrefix );
+ $replacements = array();
+
+ 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 ) );
+ } else {
+ $output = $tag;
+ }
+ $replacements[$marker] = $output;
+ }
+ return strtr( $text, $replacements );
+ }
+
+ /**
* Break wikitext input into sections, and either pull or replace
* some particular section's text.
*
@@ -4814,17 +5025,18 @@ 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.
*
+ * Section 0 is always considered to exist, even if it only contains the empty
+ * string. If $text is the empty string and section 0 is replaced, $newText is
+ * returned.
+ *
* @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->mOptions = new ParserOptions;
- $this->clearState();
- $this->setTitle( $wgTitle ); # not generally used but removes an ugly failure mode
- $this->setOutputType( self::OT_PLAIN );
+ global $wgTitle; # not generally used but removes an ugly failure mode
+ $this->startParse( $wgTitle, new ParserOptions, self::OT_PLAIN, true );
$outText = '';
$frame = $this->getPreprocessor()->newFrame();
@@ -4837,6 +5049,25 @@ class Parser {
$flags |= self::PTD_FOR_INCLUSION;
}
}
+
+ # Check for empty input
+ if ( strval( $text ) === '' ) {
+ # Only sections 0 and T-0 exist in an empty document
+ if ( $sectionIndex == 0 ) {
+ if ( $mode === 'get' ) {
+ return '';
+ } else {
+ return $newText;
+ }
+ } else {
+ if ( $mode === 'get' ) {
+ return $newText;
+ } else {
+ return $text;
+ }
+ }
+ }
+
# Preprocess the text
$root = $this->preprocessToDom( $text, $flags );
@@ -4930,12 +5161,13 @@ class Parser {
/**
* This function returns $oldtext after the content of the section
- * specified by $section has been replaced with $text.
+ * specified by $section has been replaced with $text. If the target
+ * section does not exist, $oldtext is returned unchanged.
*
- * @param $text String: former text of the article
+ * @param $oldtext String: former text of the article
* @param $section Numeric: section identifier
* @param $text String: replacing text
- * #return String: modified text
+ * @return String: modified text
*/
public function replaceSection( $oldtext, $section, $text ) {
return $this->extractSections( $oldtext, $section, "replace", $text );
@@ -4951,30 +5183,44 @@ class Parser {
}
/**
+ * Get the revision object for $this->mRevisionId
+ *
+ * @return Revision|null either a Revision object or null
+ */
+ protected function getRevisionObject() {
+ if ( !is_null( $this->mRevisionObject ) ) {
+ return $this->mRevisionObject;
+ }
+ if ( is_null( $this->mRevisionId ) ) {
+ return null;
+ }
+
+ $this->mRevisionObject = Revision::newFromId( $this->mRevisionId );
+ return $this->mRevisionObject;
+ }
+
+ /**
* Get the timestamp associated with the current revision, adjusted for
* the default server-local timestamp
*/
function getRevisionTimestamp() {
if ( is_null( $this->mRevisionTimestamp ) ) {
wfProfileIn( __METHOD__ );
- global $wgContLang;
- $dbr = wfGetDB( DB_SLAVE );
- $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.
- $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.
- $this->mRevisionTimestamp = $wgContLang->userAdjust( $timestamp, '' );
+
+ $revObject = $this->getRevisionObject();
+ $timestamp = $revObject ? $revObject->getTimestamp() : false;
+
+ if( $timestamp !== false ) {
+ global $wgContLang;
+
+ # 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__ );
}
@@ -4987,16 +5233,18 @@ class Parser {
* @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 ) {
- $revision = Revision::newFromId( $this->mRevisionId );
- $revuser = $revision->getUserText();
- } else {
- global $wgUser;
- $revuser = $wgUser->getName();
+ if( is_null( $this->mRevisionUser ) ) {
+ $revObject = $this->getRevisionObject();
+
+ # if this template is subst: the revision id will be blank,
+ # so just use the current user's name
+ if( $revObject ) {
+ $this->mRevisionUser = $revObject->getUserText();
+ } elseif( $this->ot['wiki'] || $this->mOptions->getIsPreview() ) {
+ $this->mRevisionUser = $this->getUser()->getName();
+ }
}
- return $revuser;
+ return $this->mRevisionUser;
}
/**
@@ -5083,7 +5331,8 @@ class Parser {
$text = preg_replace( '/\[\[:?([^[|]+)\|([^[]+)\]\]/', '$2', $text );
$text = preg_replace( '/\[\[:?([^[]+)\|?\]\]/', '$1', $text );
- # Strip external link markup (FIXME: Not Tolerant to blank link text
+ # Strip external link markup
+ # @todo FIXME: Not tolerant to blank link text
# I.E. [http://www.mediawiki.org] will render as [1] or something depending
# on how many empty links there are on the page - need to figure that out.
$text = preg_replace( '/\[(?:' . wfUrlProtocols() . ')([^ ]+?) ([^[]+)\]/', '$2', $text );
@@ -5098,36 +5347,39 @@ class Parser {
/**
* strip/replaceVariables/unstrip for preprocessor regression testing
+ *
+ * @return string
*/
- function testSrvus( $text, $title, ParserOptions $options, $outputType = self::OT_HTML ) {
- $this->mOptions = $options;
- $this->clearState();
- if ( !$title instanceof Title ) {
- $title = Title::newFromText( $title );
- }
- $this->mTitle = $title;
- $this->setOutputType( $outputType );
+ function testSrvus( $text, Title $title, ParserOptions $options, $outputType = self::OT_HTML ) {
+ $this->startParse( $title, $options, $outputType, true );
+
$text = $this->replaceVariables( $text );
$text = $this->mStripState->unstripBoth( $text );
$text = Sanitizer::removeHTMLtags( $text );
return $text;
}
- function testPst( $text, $title, $options ) {
- global $wgUser;
- if ( !$title instanceof Title ) {
- $title = Title::newFromText( $title );
- }
- return $this->preSaveTransform( $text, $title, $wgUser, $options );
+ function testPst( $text, Title $title, ParserOptions $options ) {
+ return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
}
- function testPreprocess( $text, $title, $options ) {
- if ( !$title instanceof Title ) {
- $title = Title::newFromText( $title );
- }
+ function testPreprocess( $text, Title $title, ParserOptions $options ) {
return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
}
+ /**
+ * Call a callback function on all regions of the given text that are not
+ * inside strip markers, and replace those regions with the return value
+ * of the callback. For example, with input:
+ *
+ * aaa<MARKER>bbb
+ *
+ * This will call the callback function twice, with 'aaa' and 'bbb'. Those
+ * two strings will be replaced with the value returned by the callback in
+ * each case.
+ *
+ * @return string
+ */
function markerSkipCallback( $s, $callback ) {
$i = 0;
$out = '';
@@ -5152,168 +5404,72 @@ class Parser {
return $out;
}
- function serialiseHalfParsedText( $text ) {
- $data = array();
- $data['text'] = $text;
-
- # 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 ) ) )
- {
- $end_pos += strlen( self::MARKER_SUFFIX );
- $marker = substr( $text, $start_pos, $end_pos-$start_pos );
-
- if ( !empty( $this->mStripState->general->data[$marker] ) ) {
- $replaceArray = $stripState->general;
- $stripText = $this->mStripState->general->data[$marker];
- } elseif ( !empty( $this->mStripState->nowiki->data[$marker] ) ) {
- $replaceArray = $stripState->nowiki;
- $stripText = $this->mStripState->nowiki->data[$marker];
- } else {
- throw new MWException( "Hanging strip marker: '$marker'." );
- }
-
- $replaceArray->setPair( $marker, $stripText );
- $pos = $end_pos;
- }
- $data['stripstate'] = $stripState;
-
- # 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 ) ) ) {
- list( $ns, $trail ) = explode( ':', substr( $text, $start_pos + strlen( '<!--LINK ' ) ), 2 );
-
- $ns = trim( $ns );
- if ( empty( $links['internal'][$ns] ) ) {
- $links['internal'][$ns] = array();
- }
-
- $key = trim( substr( $trail, 0, strpos( $trail, '-->' ) ) );
- $links['internal'][$ns][] = $this->mLinkHolders->internals[$ns][$key];
- $pos = $start_pos + strlen( "<!--LINK $ns:$key-->" );
- }
-
- $pos = 0;
-
- # 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];
- $pos = $start_pos + strlen( "<!--IWLINK $key-->" );
- }
-
- $data['linkholder'] = $links;
-
+ /**
+ * Save the parser state required to convert the given half-parsed text to
+ * HTML. "Half-parsed" in this context means the output of
+ * recursiveTagParse() or internalParse(). This output has strip markers
+ * from replaceVariables (extensionSubstitution() etc.), and link
+ * placeholders from replaceLinkHolders().
+ *
+ * Returns an array which can be serialized and stored persistently. This
+ * array can later be loaded into another parser instance with
+ * unserializeHalfParsedText(). The text can then be safely incorporated into
+ * the return value of a parser hook.
+ *
+ * @return array
+ */
+ function serializeHalfParsedText( $text ) {
+ wfProfileIn( __METHOD__ );
+ $data = array(
+ 'text' => $text,
+ 'version' => self::HALF_PARSED_VERSION,
+ 'stripState' => $this->mStripState->getSubState( $text ),
+ 'linkHolders' => $this->mLinkHolders->getSubArray( $text )
+ );
+ wfProfileOut( __METHOD__ );
return $data;
}
/**
- * TODO: document
- * @param $data Array
- * @param $intPrefix String unique identifying prefix
+ * Load the parser state given in the $data array, which is assumed to
+ * have been generated by serializeHalfParsedText(). The text contents is
+ * extracted from the array, and its markers are transformed into markers
+ * appropriate for the current Parser instance. This transformed text is
+ * returned, and can be safely included in the return value of a parser
+ * hook.
+ *
+ * If the $data array has been stored persistently, the caller should first
+ * check whether it is still valid, by calling isValidHalfParsedText().
+ *
+ * @param $data Serialized data
* @return String
*/
- function unserialiseHalfParsedText( $data, $intPrefix = null ) {
- if ( !$intPrefix ) {
- $intPrefix = self::getRandomString();
+ function unserializeHalfParsedText( $data ) {
+ if ( !isset( $data['version'] ) || $data['version'] != self::HALF_PARSED_VERSION ) {
+ throw new MWException( __METHOD__.': invalid version' );
}
# 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
- $text = $data['text'];
- $links = $data['linkholder'];
-
- # Internal...
- foreach ( $links['internal'] as $ns => $nsLinks ) {
- foreach ( $nsLinks as $key => $entry ) {
- $newKey = $intPrefix . '-' . $key;
- $this->mLinkHolders->internals[$ns][$newKey] = $entry;
-
- $text = str_replace( "<!--LINK $ns:$key-->", "<!--LINK $ns:$newKey-->", $text );
- }
- }
+ $texts = array( $data['text'] );
+ $texts = $this->mStripState->merge( $data['stripState'], $texts );
- # Interwiki...
- foreach ( $links['interwiki'] as $key => $entry ) {
- $newKey = "$intPrefix-$key";
- $this->mLinkHolders->interwikis[$newKey] = $entry;
-
- $text = str_replace( "<!--IWLINK $key-->", "<!--IWLINK $newKey-->", $text );
- }
+ # Now renumber links
+ $texts = $this->mLinkHolders->mergeForeign( $data['linkHolders'], $texts );
# Should be good to go.
- return $text;
- }
-}
-
-/**
- * @todo document, briefly.
- * @ingroup Parser
- */
-class StripState {
- var $general, $nowiki;
-
- function __construct() {
- $this->general = new ReplacementArray;
- $this->nowiki = new ReplacementArray;
- }
-
- function unstripGeneral( $text ) {
- wfProfileIn( __METHOD__ );
- do {
- $oldText = $text;
- $text = $this->general->replace( $text );
- } while ( $text !== $oldText );
- wfProfileOut( __METHOD__ );
- return $text;
- }
-
- function unstripNoWiki( $text ) {
- wfProfileIn( __METHOD__ );
- do {
- $oldText = $text;
- $text = $this->nowiki->replace( $text );
- } while ( $text !== $oldText );
- wfProfileOut( __METHOD__ );
- return $text;
- }
-
- function unstripBoth( $text ) {
- wfProfileIn( __METHOD__ );
- do {
- $oldText = $text;
- $text = $this->general->replace( $text );
- $text = $this->nowiki->replace( $text );
- } while ( $text !== $oldText );
- wfProfileOut( __METHOD__ );
- return $text;
+ return $texts[0];
}
-}
-/**
- * @todo document, briefly.
- * @ingroup Parser
- */
-class OnlyIncludeReplacer {
- var $output = '';
-
- function replace( $matches ) {
- if ( substr( $matches[1], -1 ) === "\n" ) {
- $this->output .= substr( $matches[1], 0, -1 );
- } else {
- $this->output .= $matches[1];
- }
+ /**
+ * Returns true if the given array, presumed to be generated by
+ * serializeHalfParsedText(), is compatible with the current version of the
+ * parser.
+ *
+ * @param $data Array
+ *
+ * @return bool
+ */
+ function isValidHalfParsedText( $data ) {
+ return isset( $data['version'] ) && $data['version'] == self::HALF_PARSED_VERSION;
}
}
diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php
index 1e028ae5..dcbf7a4d 100644
--- a/includes/parser/ParserCache.php
+++ b/includes/parser/ParserCache.php
@@ -31,13 +31,18 @@ class ParserCache {
*
* @param $memCached Object
*/
- function __construct( $memCached ) {
+ protected function __construct( $memCached ) {
if ( !$memCached ) {
throw new MWException( "Tried to create a ParserCache with an invalid memcached" );
}
$this->mMemc = $memCached;
}
+ /**
+ * @param $article Article
+ * @param $hash string
+ * @return mixed|string
+ */
protected function getParserOutputKey( $article, $hash ) {
global $wgRequest;
@@ -49,6 +54,10 @@ class ParserCache {
return $key;
}
+ /**
+ * @param $article Article
+ * @return mixed|string
+ */
protected function getOptionsKey( $article ) {
$pageid = $article->getID();
return wfMemcKey( 'pcache', 'idoptions', "{$pageid}" );
@@ -63,6 +72,9 @@ class ParserCache {
* $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)
+ *
+ * @param $article Article
+ * @param $popts ParserOptions
*/
function getETag( $article, $popts ) {
return 'W/"' . $this->getParserOutputKey( $article,
@@ -72,6 +84,9 @@ class ParserCache {
/**
* Retrieve the ParserOutput from ParserCache, even if it's outdated.
+ * @param $article Article
+ * @param $popts ParserOptions
+ * @return ParserOutput|false
*/
public function getDirty( $article, $popts ) {
$value = $this->get( $article, $popts, true );
@@ -82,6 +97,9 @@ class ParserCache {
* 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.
+ *
+ * @param $article Article
+ * @param $popts ParserOptions
*/
public function getKey( $article, $popts, $useOutdated = true ) {
global $wgCacheEpoch;
@@ -116,6 +134,12 @@ class ParserCache {
/**
* Retrieve the ParserOutput from ParserCache.
* false if not found or outdated.
+ *
+ * @param $article Article
+ * @param $popts ParserOptions
+ * @param $useOutdated
+ *
+ * @return ParserOutput|false
*/
public function get( $article, $popts, $useOutdated = false ) {
global $wgCacheEpoch;
@@ -150,6 +174,11 @@ class ParserCache {
}
wfDebug( "Found.\n" );
+
+ // The edit section preference may not be the appropiate one in
+ // the ParserOutput, as we are not storing it in the parsercache
+ // key. Force it here. See bug 31445.
+ $value->setEditSectionTokens( $popts->getEditSection() );
if ( !$useOutdated && $value->expired( $touched ) ) {
wfIncrStats( "pcache_miss_expired" );
@@ -157,9 +186,6 @@ class ParserCache {
wfDebug( "ParserOutput key expired, touched $touched, epoch $wgCacheEpoch, cached $cacheTime\n" );
$value = false;
} else {
- if ( isset( $value->mTimestamp ) ) {
- $article->mTimestamp = $value->mTimestamp;
- }
wfIncrStats( "pcache_hit" );
}
@@ -167,7 +193,12 @@ class ParserCache {
return $value;
}
-
+ /**
+ * @param $parserOutput ParserOutput
+ * @param $article Article
+ * @param $popts ParserOptions
+ * @return void
+ */
public function save( $parserOutput, $article, $popts ) {
$expire = $parserOutput->getCacheExpiry();
@@ -183,7 +214,8 @@ class ParserCache {
$optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() );
- $parserOutputKey = $this->getParserOutputKey( $article, $popts->optionsHash( $optionsKey->mUsedOptions ) );
+ $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();
diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php
index 1bda0792..07752768 100644
--- a/includes/parser/ParserOptions.php
+++ b/includes/parser/ParserOptions.php
@@ -5,7 +5,7 @@
* @file
* @ingroup Parser
*/
-
+
/**
* Set options of the Parser
* @todo document
@@ -18,46 +18,51 @@ class ParserOptions {
var $mAllowExternalImages; # Allow external images inline
var $mAllowExternalImagesFrom; # If not, any exception?
var $mEnableImageWhitelist; # If not or it doesn't match, should we check an on-wiki whitelist?
- var $mSkin; # Reference to the preferred skin
- var $mDateFormat; # Date format index
- var $mEditSection; # Create "edit section" links
- var $mNumberHeadings; # Automatically number headings
+ var $mDateFormat = null; # Date format index
+ var $mEditSection = true; # Create "edit section" links
var $mAllowSpecialInclusion; # Allow inclusion of special pages
- var $mTidy; # Ask for tidy cleanup
- var $mInterfaceMessage; # Which lang to call for PLURAL and GRAMMAR
- var $mTargetLanguage; # Overrides above setting with arbitrary language
+ var $mTidy = false; # Ask for tidy cleanup
+ var $mInterfaceMessage = false; # Which lang to call for PLURAL and GRAMMAR
+ var $mTargetLanguage = null; # Overrides above setting with arbitrary language
var $mMaxIncludeSize; # Maximum size of template expansions, in bytes
var $mMaxPPNodeCount; # Maximum number of nodes touched by PPFrame::expand()
var $mMaxPPExpandDepth; # Maximum recursion depth in PPFrame::expand()
var $mMaxTemplateDepth; # Maximum recursion depth for templates within templates
- var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
- var $mTemplateCallback; # Callback for template fetching
- var $mEnableLimitReport; # Enable limit report in an HTML comment on output
+ var $mRemoveComments = true; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
+ var $mTemplateCallback = # Callback for template fetching
+ array( 'Parser', 'statelessFetchTemplate' );
+ var $mEnableLimitReport = false; # Enable limit report in an HTML comment on output
var $mTimestamp; # Timestamp used for {{CURRENTDAY}} etc.
var $mExternalLinkTarget; # Target attribute for external links
+ var $mCleanSignatures; #
+ var $mPreSaveTransform = true; # Transform wiki markup when saving the page.
+
+ var $mNumberHeadings; # Automatically number headings
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; #
+ private $mStubThreshold; # Maximum article size of an article to be marked as "stub"
+ var $mUserLang; # Language code of the User language.
+
+ /**
+ * @var User
+ */
+ var $mUser; # Stored user object
+ var $mIsPreview = false; # Parsing the page for a "preview" operation
+ var $mIsSectionPreview = false; # Parsing the page for a "preview" operation on a single section
+ var $mIsPrintable = false; # Parsing the printable version of the page
- 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() { $this->optionUsed('editsection');
- return $this->mEditSection; }
- function getNumberHeadings() { $this->optionUsed('numberheadings');
- return $this->mNumberHeadings; }
+ function getEditSection() { return $this->mEditSection; }
+ function getNumberHeadings() { $this->optionUsed( 'numberheadings' );
+ return $this->mNumberHeadings; }
function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; }
function getTidy() { return $this->mTidy; }
function getInterfaceMessage() { return $this->mInterfaceMessage; }
@@ -71,25 +76,32 @@ class ParserOptions {
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 getMath() { $this->optionUsed( 'math' );
+ return $this->mMath; }
+ function getThumbSize() { $this->optionUsed( 'thumbsize' );
+ return $this->mThumbSize; }
+ function getStubThreshold() { $this->optionUsed( 'stubthreshold' );
+ return $this->mStubThreshold; }
+
function getIsPreview() { return $this->mIsPreview; }
function getIsSectionPreview() { return $this->mIsSectionPreview; }
- function getIsPrintable() { $this->optionUsed('printable');
- return $this->mIsPrintable; }
+ function getIsPrintable() { $this->optionUsed( 'printable' );
+ return $this->mIsPrintable; }
+ function getUser() { return $this->mUser; }
+ function getPreSaveTransform() { return $this->mPreSaveTransform; }
+ /**
+ * @param $title Title
+ * @return Skin
+ * @deprecated since 1.18 Use Linker::* instead
+ */
function getSkin( $title = null ) {
- if ( !isset( $this->mSkin ) ) {
- $this->mSkin = $this->mUser->getSkin( $title );
- }
- return $this->mSkin;
+ wfDeprecated( __METHOD__ );
+ return new DummyLinker;
}
function getDateFormat() {
- $this->optionUsed('dateformat');
+ $this->optionUsed( 'dateformat' );
if ( !isset( $this->mDateFormat ) ) {
$this->mDateFormat = $this->mUser->getDatePreference();
}
@@ -107,9 +119,11 @@ class ParserOptions {
* 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).
+ * @return String Language code
+ * @since 1.17
*/
function getUserLang() {
- $this->optionUsed('userlang');
+ $this->optionUsed( 'userlang' );
return $this->mUserLang;
}
@@ -122,9 +136,9 @@ class ParserOptions {
function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); }
function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); }
function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); }
- function setTidy( $x ) { return wfSetVar( $this->mTidy, $x); }
+ function setTidy( $x ) { return wfSetVar( $this->mTidy, $x ); }
function setSkin( $x ) { $this->mSkin = $x; }
- function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); }
+ function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x ); }
function setTargetLanguage( $x ) { return wfSetVar( $this->mTargetLanguage, $x, true ); }
function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); }
function setMaxPPNodeCount( $x ) { return wfSetVar( $this->mMaxPPNodeCount, $x ); }
@@ -136,9 +150,16 @@ class ParserOptions {
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 setUserLang( $x ) {
+ if ( $x instanceof Language ) {
+ $x = $x->getCode();
+ }
+ return wfSetVar( $this->mUserLang, $x );
+ }
function setThumbSize( $x ) { return wfSetVar( $this->mThumbSize, $x ); }
-
+ function setStubThreshold( $x ) { return wfSetVar( $this->mStubThreshold, $x ); }
+ function setPreSaveTransform( $x ) { return wfSetVar( $this->mPreSaveTransform, $x ); }
+
function setIsPreview( $x ) { return wfSetVar( $this->mIsPreview, $x ); }
function setIsSectionPreview( $x ) { return wfSetVar( $this->mIsSectionPreview, $x ); }
function setIsPrintable( $x ) { return wfSetVar( $this->mIsPrintable, $x ); }
@@ -191,30 +212,19 @@ class ParserOptions {
$this->mAllowExternalImages = $wgAllowExternalImages;
$this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
$this->mEnableImageWhitelist = $wgEnableImageWhitelist;
- $this->mSkin = null; # Deferred
- $this->mDateFormat = null; # Deferred
- $this->mEditSection = true;
- $this->mNumberHeadings = $user->getOption( 'numberheadings' );
$this->mAllowSpecialInclusion = $wgAllowSpecialInclusion;
- $this->mTidy = false;
- $this->mInterfaceMessage = false;
- $this->mTargetLanguage = null; // default depends on InterfaceMessage setting
$this->mMaxIncludeSize = $wgMaxArticleSize * 1024;
$this->mMaxPPNodeCount = $wgMaxPPNodeCount;
$this->mMaxPPExpandDepth = $wgMaxPPExpandDepth;
$this->mMaxTemplateDepth = $wgMaxTemplateDepth;
- $this->mRemoveComments = true;
- $this->mTemplateCallback = array( 'Parser', 'statelessFetchTemplate' );
- $this->mEnableLimitReport = false;
$this->mCleanSignatures = $wgCleanSignatures;
$this->mExternalLinkTarget = $wgExternalLinkTarget;
+
+ $this->mNumberHeadings = $user->getOption( 'numberheadings' );
$this->mMath = $user->getOption( 'math' );
- $this->mUserLang = $wgLang->getCode();
$this->mThumbSize = $user->getOption( 'thumbsize' );
-
- $this->mIsPreview = false;
- $this->mIsSectionPreview = false;
- $this->mIsPrintable = false;
+ $this->mStubThreshold = $user->getStubThreshold();
+ $this->mUserLang = $wgLang->getCode();
wfProfileOut( __METHOD__ );
}
@@ -226,7 +236,7 @@ class ParserOptions {
function registerWatcher( $callback ) {
$this->onAccessCallback = $callback;
}
-
+
/**
* Called when an option is accessed.
*/
@@ -235,9 +245,9 @@ class ParserOptions {
call_user_func( $this->onAccessCallback, $optionName );
}
}
-
+
/**
- * Returns the full array of options that would have been used by
+ * 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.
*/
@@ -249,14 +259,14 @@ class ParserOptions {
}
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
+ * 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,
@@ -270,48 +280,49 @@ class ParserOptions {
global $wgContLang, $wgRenderHashAppend;
$confstr = '';
-
- if ( in_array( 'math', $forOptions ) )
+
+ if ( in_array( 'math', $forOptions ) ) {
$confstr .= $this->mMath;
- else
+ } else {
$confstr .= '*';
-
+ }
+
// Space assigned for the stubthreshold but unused
- // since it disables the parser cache, its value will always
+ // 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
+ if ( in_array( 'stubthreshold', $forOptions ) ) {
+ $confstr .= '!' . $this->mStubThreshold;
+ } else {
$confstr .= '!*' ;
+ }
- if ( in_array( 'dateformat', $forOptions ) )
+ if ( in_array( 'dateformat', $forOptions ) ) {
$confstr .= '!' . $this->getDateFormat();
-
- if ( in_array( 'numberheadings', $forOptions ) )
+ }
+
+ if ( in_array( 'numberheadings', $forOptions ) ) {
$confstr .= '!' . ( $this->mNumberHeadings ? '1' : '' );
- else
+ } else {
$confstr .= '!*';
-
- if ( in_array( 'userlang', $forOptions ) )
+ }
+
+ if ( in_array( 'userlang', $forOptions ) ) {
$confstr .= '!' . $this->mUserLang;
- else
+ } else {
$confstr .= '!*';
+ }
- if ( in_array( 'thumbsize', $forOptions ) )
+ if ( in_array( 'thumbsize', $forOptions ) ) {
$confstr .= '!' . $this->mThumbSize;
- else
+ } else {
$confstr .= '!*';
+ }
// add in language specific options, if any
- // FIXME: This is just a way of retrieving the url/user preferred variant
+ // @todo 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 ) ) {
@@ -319,20 +330,21 @@ class ParserOptions {
} elseif ( !$this->mEditSection ) {
$confstr .= '!edit=0';
}
-
- if ( $this->mIsPrintable && in_array( 'printable', $forOptions ) )
+
+ 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 1e4765db..403b6625 100644
--- a/includes/parser/ParserOutput.php
+++ b/includes/parser/ParserOutput.php
@@ -21,14 +21,15 @@ class CacheTime {
function containsOldMagic() { return $this->mContainsOldMagic; }
function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
-
- /**
- * setCacheTime() sets the timestamp expressing when the page has been rendered.
+
+ /**
+ * setCacheTime() sets the timestamp expressing when the page has been rendered.
* This doesn not control expiry, see updateCacheExpiry() for that!
+ * @param $t string
+ * @return string
*/
- function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); }
+ 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.
@@ -36,16 +37,20 @@ class CacheTime {
* 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().
+ *
+ * @param $seconds number
*/
function updateCacheExpiry( $seconds ) {
$seconds = (int)$seconds;
- if ( $this->mCacheExpiry === null || $this->mCacheExpiry > $seconds )
- $this->mCacheExpiry = $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 )
+ if ( $this->mCacheExpiry !== null && $this->mCacheExpiry <= 0 ) {
$this->mCacheTime = -1;
+ }
}
/**
@@ -59,28 +64,36 @@ class CacheTime {
function getCacheExpiry() {
global $wgParserCacheExpireTime;
- if ( $this->mCacheTime < 0 ) return 0; // old-style marker for "not cachable"
+ if ( $this->mCacheTime < 0 ) {
+ return 0;
+ } // old-style marker for "not cachable"
$expire = $this->mCacheExpiry;
- if ( $expire === null )
+ if ( $expire === null ) {
$expire = $wgParserCacheExpireTime;
- else
+ } 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;
+ if ( $expire <= 0 ) {
+ return 0; // not cachable
+ } else {
+ return $expire;
+ }
}
-
+ /**
+ * @return bool
+ */
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
@@ -100,8 +113,7 @@ class CacheTime {
}
}
-class ParserOutput extends CacheTime
-{
+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
@@ -110,6 +122,7 @@ class ParserOutput extends CacheTime
$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
+ $mImageTimeKeys = array(), # DB keys of the images used mapped to sha1 and MW timestamp
$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?
@@ -117,13 +130,19 @@ class ParserOutput extends CacheTime
$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
+ $mModuleScripts = array(), # Modules of which only the JS will be loaded by the resource loader
+ $mModuleStyles = array(), # Modules of which only the CSSS will be loaded by the resource loader
+ $mModuleMessages = array(), # Modules of which only the messages will be loaded by the resource loader
$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
+ $mEditSectionTokens = false, # prefix/suffix markers if edit sections were output as tokens
$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)
+ private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
+
+ const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
function __construct( $text = '', $languageLinks = array(), $categoryLinks = array(),
$containsOldMagic = false, $titletext = '' )
@@ -135,21 +154,55 @@ class ParserOutput extends CacheTime
$this->mTitleText = $titletext;
}
- function getText() { return $this->mText; }
+ function getText() {
+ if ( $this->mEditSectionTokens ) {
+ return preg_replace_callback( ParserOutput::EDITSECTION_REGEX,
+ array( &$this, 'replaceEditSectionLinksCallback' ), $this->mText );
+ } else {
+ return preg_replace( ParserOutput::EDITSECTION_REGEX, '', $this->mText );
+ }
+ return $this->mText;
+ }
+
+ /**
+ * callback used by getText to replace editsection tokens
+ * @private
+ */
+ function replaceEditSectionLinksCallback( $m ) {
+ global $wgOut, $wgLang;
+ $args = array(
+ htmlspecialchars_decode($m[1]),
+ htmlspecialchars_decode($m[2]),
+ isset($m[4]) ? $m[3] : null,
+ );
+ $args[0] = Title::newFromText( $args[0] );
+ if ( !is_object($args[0]) ) {
+ throw new MWException("Bad parser output text.");
+ }
+ $args[] = $wgLang->getCode();
+ $skin = $wgOut->getSkin();
+ return call_user_func_array( array( $skin, 'doEditSectionLink' ), $args );
+ }
+
function &getLanguageLinks() { return $this->mLanguageLinks; }
function getInterwikiLinks() { return $this->mInterwikiLinks; }
function getCategoryLinks() { return array_keys( $this->mCategories ); }
function &getCategories() { return $this->mCategories; }
function getTitleText() { return $this->mTitleText; }
function getSections() { return $this->mSections; }
+ function getEditSectionTokens() { return $this->mEditSectionTokens; }
function &getLinks() { return $this->mLinks; }
function &getTemplates() { return $this->mTemplates; }
+ function &getTemplateIds() { return $this->mTemplateIds; }
function &getImages() { return $this->mImages; }
+ function &getImageTimeKeys() { return $this->mImageTimeKeys; }
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 getModuleScripts() { return $this->mModuleScripts; }
+ function getModuleStyles() { return $this->mModuleStyles; }
+ function getModuleMessages() { return $this->mModuleMessages; }
function getOutputHooks() { return (array)$this->mOutputHooks; }
function getWarnings() { return array_keys( $this->mWarnings ); }
function getIndexPolicy() { return $this->mIndexPolicy; }
@@ -161,6 +214,7 @@ class ParserOutput extends CacheTime
function setTitleText( $t ) { return wfSetVar( $this->mTitleText, $t ); }
function setSections( $toc ) { return wfSetVar( $this->mSections, $toc ); }
+ function setEditSectionTokens( $t ) { return wfSetVar( $this->mEditSectionTokens, $t ); }
function setIndexPolicy( $policy ) { return wfSetVar( $this->mIndexPolicy, $policy ); }
function setTOCHTML( $tochtml ) { return wfSetVar( $this->mTOCHTML, $tochtml ); }
@@ -226,10 +280,27 @@ class ParserOutput extends CacheTime
$this->mLinks[$ns][$dbk] = $id;
}
- function addImage( $name ) {
+ /**
+ * Register a file dependency for this output
+ * @param $name string Title dbKey
+ * @param $timestamp string MW timestamp of file creation (or false if non-existing)
+ * @param $sha string base 36 SHA-1 of file (or false if non-existing)
+ * @return void
+ */
+ function addImage( $name, $timestamp = null, $sha1 = null ) {
$this->mImages[$name] = 1;
+ if ( $timestamp !== null && $sha1 !== null ) {
+ $this->mImageTimeKeys[$name] = array( 'time' => $timestamp, 'sha1' => $sha1 );
+ }
}
+ /**
+ * Register a template dependency for this output
+ * @param $title Title
+ * @param $page_id
+ * @param $rev_id
+ * @return void
+ */
function addTemplate( $title, $page_id, $rev_id ) {
$ns = $title->getNamespace();
$dbk = $title->getDBkey();
@@ -271,10 +342,22 @@ class ParserOutput extends CacheTime
}
}
- function addModules( $modules ) {
+ public function addModules( $modules ) {
$this->mModules = array_merge( $this->mModules, (array) $modules );
}
+ public function addModuleScripts( $modules ) {
+ $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
+ }
+
+ public function addModuleStyles( $modules ) {
+ $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
+ }
+
+ public function addModuleMessages( $modules ) {
+ $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules );
+ }
+
/**
* Override the title to be used for display
* -- this is assumed to have been validated
@@ -293,7 +376,7 @@ class ParserOutput extends CacheTime
* @return String
*/
public function getDisplayTitle() {
- $t = $this->getTitleText( );
+ $t = $this->getTitleText();
if( $t === '' ) {
return false;
}
@@ -333,11 +416,11 @@ class ParserOutput extends CacheTime
/**
* Returns the options from its ParserOptions which have been taken
* into account to produce this output or false if not available.
- * @return mixed Array/false
+ * @return mixed Array
*/
public function getUsedOptions() {
if ( !isset( $this->mAccessedOptions ) ) {
- return false;
+ return array();
}
return array_keys( $this->mAccessedOptions );
}
diff --git a/includes/parser/Parser_DiffTest.php b/includes/parser/Parser_DiffTest.php
index c6dd76e5..efad33f9 100644
--- a/includes/parser/Parser_DiffTest.php
+++ b/includes/parser/Parser_DiffTest.php
@@ -111,6 +111,10 @@ class Parser_DiffTest
}
}
+ /**
+ * @param $parser Parser
+ * @return bool
+ */
function onClearState( &$parser ) {
// hack marker prefixes to get identical output
if ( !isset( $this->dtUniqPrefix ) ) {
diff --git a/includes/parser/Parser_LinkHooks.php b/includes/parser/Parser_LinkHooks.php
index 7c17ce4e..90e44943 100644
--- a/includes/parser/Parser_LinkHooks.php
+++ b/includes/parser/Parser_LinkHooks.php
@@ -9,8 +9,7 @@
* Parser with LinkHooks experiment
* @ingroup Parser
*/
-class Parser_LinkHooks extends Parser
-{
+class Parser_LinkHooks extends Parser {
/**
* Update this version number when the ParserOutput format
* changes in an incompatible way, so the parser cache
@@ -38,10 +37,8 @@ class Parser_LinkHooks extends Parser
/**
* Constructor
- *
- * @public
*/
- function __construct( $conf = array() ) {
+ public function __construct( $conf = array() ) {
parent::__construct( $conf );
$this->mLinkHooks = array();
}
@@ -82,8 +79,6 @@ class Parser_LinkHooks extends Parser
* True) (Treat as link) Parse the link according to normal link rules
* False) (Bad link) Just output the raw wikitext (You may modify the text first)
*
- * @public
- *
* @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:
@@ -91,7 +86,7 @@ class Parser_LinkHooks extends Parser
*
* @return The old callback function for this name, if any
*/
- function setLinkHook( $ns, $callback, $flags = 0 ) {
+ public function setLinkHook( $ns, $callback, $flags = 0 ) {
if( $flags & SLH_PATTERN && !is_string($ns) )
throw new MWException( __METHOD__.'() expecting a regex string pattern.' );
elseif( $flags | ~SLH_PATTERN && !is_int($ns) )
@@ -232,7 +227,7 @@ class Parser_LinkHooks extends Parser
wfProfileOut( __METHOD__."-misc" );
# Make title object
wfProfileIn( __METHOD__."-title" );
- $title = Title::newFromText( $this->mStripState->unstripNoWiki($titleText) );
+ $title = Title::newFromText( $this->mStripState->unstripNoWiki( $titleText ) );
if( !$title ) {
wfProfileOut( __METHOD__."-title" );
wfProfileOut( __METHOD__ );
@@ -244,7 +239,7 @@ class Parser_LinkHooks extends Parser
# Default for Namespaces is a default link
# ToDo: Default for patterns is plain wikitext
$return = true;
- if( isset($this->mLinkHooks[$ns]) ) {
+ if( isset( $this->mLinkHooks[$ns] ) ) {
list( $callback, $flags ) = $this->mLinkHooks[$ns];
if( $flags & SLH_PATTERN ) {
$args = array( $parser, $holders, $markers, $titleText, &$paramText, &$leadingColon );
@@ -253,14 +248,14 @@ class Parser_LinkHooks extends Parser
}
# Workaround for PHP bug 35229 and similar
if ( !is_callable( $callback ) ) {
- throw new MWException( "Tag hook for $name is not callable\n" );
+ throw new MWException( "Tag hook for namespace $ns is not callable\n" );
}
$return = call_user_func_array( $callback, $args );
}
if( $return === true ) {
# True (treat as plain link) was returned, call the defaultLinkHook
- $args = array( $parser, $holders, $markers, $title, $titleText, &$paramText, &$leadingColon );
- $return = call_user_func_array( array( 'CoreLinkFunctions', 'defaultLinkHook' ), $args );
+ $return = CoreLinkFunctions::defaultLinkHook( $parser, $holders, $markers, $title,
+ $titleText, $paramText, $leadingColon );
}
if( $return === false ) {
# False (no link) was returned, output plain wikitext
diff --git a/includes/parser/Preprocessor.php b/includes/parser/Preprocessor.php
index c31f37bf..d6328aa7 100644
--- a/includes/parser/Preprocessor.php
+++ b/includes/parser/Preprocessor.php
@@ -9,19 +9,44 @@
* @ingroup Parser
*/
interface Preprocessor {
- /** Create a new preprocessor object based on an initialised Parser object */
+ /**
+ * Create a new preprocessor object based on an initialised Parser object
+ *
+ * @param $parser Parser
+ */
function __construct( $parser );
- /** Create a new top-level frame for expansion of a page */
+ /**
+ * Create a new top-level frame for expansion of a page
+ *
+ * @return PPFrame
+ */
function newFrame();
- /** Create a new custom frame for programmatic use of parameter replacement as used in some extensions */
+ /**
+ * Create a new custom frame for programmatic use of parameter replacement as used in some extensions
+ *
+ * @param $args array
+ *
+ * @return PPFrame
+ */
function newCustomFrame( $args );
- /** Create a new custom node for programmatic use of parameter replacement as used in some extensions */
+ /**
+ * Create a new custom node for programmatic use of parameter replacement as used in some extensions
+ *
+ * @param $values
+ */
function newPartNodeArray( $values );
- /** Preprocess text to a PPNode */
+ /**
+ * Preprocess text to a PPNode
+ *
+ * @param $text
+ * @param $flags
+ *
+ * @return PPNode
+ */
function preprocessToObj( $text, $flags = 0 );
}
@@ -39,6 +64,11 @@ interface PPFrame {
/**
* Create a child frame
+ *
+ * @param $args array
+ * @param $title Title
+ *
+ * @return PPFrame
*/
function newChild( $args = false, $title = false );
@@ -70,6 +100,8 @@ interface PPFrame {
/**
* Returns true if there are no arguments in this frame
+ *
+ * @return bool
*/
function isEmpty();
@@ -95,6 +127,10 @@ interface PPFrame {
/**
* Returns true if the infinite loop check is OK, false if a loop is detected
+ *
+ * @param $title
+ *
+ * @return bool
*/
function loopCheck( $title );
@@ -126,6 +162,8 @@ interface PPNode {
/**
* Get the first child of a tree node. False if there isn't one.
+ *
+ * @return PPNode
*/
function getFirstChild();
diff --git a/includes/parser/Preprocessor_DOM.php b/includes/parser/Preprocessor_DOM.php
index 2b635f7c..755563a0 100644
--- a/includes/parser/Preprocessor_DOM.php
+++ b/includes/parser/Preprocessor_DOM.php
@@ -5,12 +5,18 @@
* @file
* @ingroup Parser
*/
-
+
/**
* @ingroup Parser
*/
class Preprocessor_DOM implements Preprocessor {
- var $parser, $memoryLimit;
+
+ /**
+ * @var Parser
+ */
+ var $parser;
+
+ var $memoryLimit;
const CACHE_VERSION = 1;
@@ -27,21 +33,31 @@ class Preprocessor_DOM implements Preprocessor {
}
}
+ /**
+ * @return PPFrame_DOM
+ */
function newFrame() {
return new PPFrame_DOM( $this );
}
+ /**
+ * @param $args
+ * @return PPCustomFrame_DOM
+ */
function newCustomFrame( $args ) {
return new PPCustomFrame_DOM( $this, $args );
}
+ /**
+ * @param $values
+ * @return PPNode_DOM
+ */
function newPartNodeArray( $values ) {
//NOTE: DOM manipulation is slower than building & parsing XML! (or so Tim sais)
- $xml = "";
- $xml .= "<list>";
+ $xml = "<list>";
foreach ( $values as $k => $val ) {
-
+
if ( is_int( $k ) ) {
$xml .= "<part><name index=\"$k\"/><value>" . htmlspecialchars( $val ) ."</value></part>";
} else {
@@ -59,6 +75,10 @@ class Preprocessor_DOM implements Preprocessor {
return $node;
}
+ /**
+ * @throws MWException
+ * @return bool
+ */
function memCheck() {
if ( $this->memoryLimit === false ) {
return;
@@ -91,14 +111,15 @@ class Preprocessor_DOM implements Preprocessor {
* cache may be implemented at a later date which takes further advantage of these strict
* dependency requirements.
*
- * @private
+ * @return PPNode_DOM
*/
function preprocessToObj( $text, $flags = 0 ) {
wfProfileIn( __METHOD__ );
global $wgMemc, $wgPreprocessorCacheThreshold;
-
+
$xml = false;
- $cacheable = strlen( $text ) > $wgPreprocessorCacheThreshold;
+ $cacheable = ( $wgPreprocessorCacheThreshold !== false
+ && strlen( $text ) > $wgPreprocessorCacheThreshold );
if ( $cacheable ) {
wfProfileIn( __METHOD__.'-cacheable' );
@@ -134,7 +155,8 @@ class Preprocessor_DOM implements Preprocessor {
if ( !$result ) {
// Try running the XML through UtfNormal to get rid of invalid characters
$xml = UtfNormal::cleanUp( $xml );
- $result = $dom->loadXML( $xml );
+ // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 don't barf when the XML is >256 levels deep
+ $result = $dom->loadXML( $xml, 1 << 19 );
if ( !$result ) {
throw new MWException( __METHOD__.' generated invalid XML' );
}
@@ -147,7 +169,12 @@ class Preprocessor_DOM implements Preprocessor {
wfProfileOut( __METHOD__ );
return $obj;
}
-
+
+ /**
+ * @param $text string
+ * @param $flags int
+ * @return string
+ */
function preprocessToXml( $text, $flags = 0 ) {
wfProfileIn( __METHOD__ );
$rules = array(
@@ -317,7 +344,7 @@ class Preprocessor_DOM implements Preprocessor {
// Search backwards for leading whitespace
$wsStart = $i ? ( $i - strspn( $revText, ' ', strlen( $text ) - $i ) ) : 0;
// Search forwards for trailing whitespace
- // $wsEnd will be the position of the last space
+ // $wsEnd will be the position of the last space (or the '>' if there's none)
$wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 );
// Eat the line if possible
// TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
@@ -344,13 +371,11 @@ class Preprocessor_DOM implements Preprocessor {
if ( $stack->top ) {
$part = $stack->top->getCurrentPart();
- if ( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) {
- // Comments abutting, no change in visual end
- $part->commentEnd = $wsEnd;
- } else {
+ if ( ! (isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 )) {
$part->visualEnd = $wsStart;
- $part->commentEnd = $endPos;
}
+ // Else comments abutting, no change in visual end
+ $part->commentEnd = $endPos;
}
$i = $endPos + 1;
$inner = substr( $text, $startPos, $endPos - $startPos + 1 );
@@ -389,8 +414,8 @@ class Preprocessor_DOM implements Preprocessor {
} else {
$attrEnd = $tagEndPos;
// Find closing tag
- if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
- $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
+ if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
+ $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
{
$inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
$i = $matches[0][1] + strlen( $matches[0][0] );
@@ -423,9 +448,7 @@ class Preprocessor_DOM implements Preprocessor {
$accum .= '<inner>' . htmlspecialchars( $inner ) . '</inner>';
}
$accum .= $close . '</ext>';
- }
-
- elseif ( $found == 'line-start' ) {
+ } elseif ( $found == 'line-start' ) {
// Is this the start of a heading?
// Line break belongs before the heading element in any case
if ( $fakeLineStart ) {
@@ -453,9 +476,7 @@ class Preprocessor_DOM implements Preprocessor {
extract( $flags );
$i += $count;
}
- }
-
- elseif ( $found == 'line-end' ) {
+ } elseif ( $found == 'line-end' ) {
$piece = $stack->top;
// A heading must be open, otherwise \n wouldn't have been in the search list
assert( $piece->open == "\n" );
@@ -522,7 +543,7 @@ class Preprocessor_DOM implements Preprocessor {
'open' => $curChar,
'close' => $rule['end'],
'count' => $count,
- 'lineStart' => ($i > 0 && $text[$i-1] == "\n"),
+ 'lineStart' => ($i == 0 || $text[$i-1] == "\n"),
);
$stack->push( $piece );
@@ -557,7 +578,7 @@ class Preprocessor_DOM implements Preprocessor {
}
}
- if ($matchingCount <= 0) {
+ if ( $matchingCount <= 0 ) {
# No matching element found in callback array
# Output a literal closing brace and continue
$accum .= htmlspecialchars( str_repeat( $curChar, $count ) );
@@ -607,7 +628,7 @@ class Preprocessor_DOM implements Preprocessor {
$accum =& $stack->getAccum();
# Re-add the old stack element if it still has unmatched opening characters remaining
- if ($matchingCount < $piece->count) {
+ if ( $matchingCount < $piece->count ) {
$piece->parts = array( new PPDPart );
$piece->count -= $matchingCount;
# do we still qualify for any callback with remaining count?
@@ -630,16 +651,12 @@ class Preprocessor_DOM implements Preprocessor {
# Add XML element to the enclosing accumulator
$accum .= $element;
- }
-
- elseif ( $found == 'pipe' ) {
+ } elseif ( $found == 'pipe' ) {
$findEquals = true; // shortcut for getFlags()
$stack->addPart();
$accum =& $stack->getAccum();
++$i;
- }
-
- elseif ( $found == 'equals' ) {
+ } elseif ( $found == 'equals' ) {
$findEquals = false; // shortcut for getFlags()
$stack->getCurrentPart()->eqpos = strlen( $accum );
$accum .= '=';
@@ -655,7 +672,7 @@ class Preprocessor_DOM implements Preprocessor {
$xml = $stack->rootAccum;
wfProfileOut( __METHOD__ );
-
+
return $xml;
}
}
@@ -665,7 +682,12 @@ class Preprocessor_DOM implements Preprocessor {
* @ingroup Parser
*/
class PPDStack {
- var $stack, $rootAccum, $top;
+ var $stack, $rootAccum;
+
+ /**
+ * @var PPDStack
+ */
+ var $top;
var $out;
var $elementClass = 'PPDStackElement';
@@ -678,6 +700,9 @@ class PPDStack {
$this->accum =& $this->rootAccum;
}
+ /**
+ * @return int
+ */
function count() {
return count( $this->stack );
}
@@ -726,6 +751,9 @@ class PPDStack {
$this->accum =& $this->top->getAccum();
}
+ /**
+ * @return array
+ */
function getFlags() {
if ( !count( $this->stack ) ) {
return array(
@@ -773,6 +801,9 @@ class PPDStackElement {
return $this->parts[count($this->parts) - 1];
}
+ /**
+ * @return array
+ */
function getFlags() {
$partCount = count( $this->parts );
$findPipe = $this->open != "\n" && $this->open != '[';
@@ -785,6 +816,8 @@ class PPDStackElement {
/**
* Get the output string that would result if the close is not found.
+ *
+ * @return string
*/
function breakSyntax( $openingCount = false ) {
if ( $this->open == "\n" ) {
@@ -829,7 +862,21 @@ class PPDPart {
* @ingroup Parser
*/
class PPFrame_DOM implements PPFrame {
- var $preprocessor, $parser, $title;
+
+ /**
+ * @var Preprocessor
+ */
+ var $preprocessor;
+
+ /**
+ * @var Parser
+ */
+ var $parser;
+
+ /**
+ * @var Title
+ */
+ var $title;
var $titleCache;
/**
@@ -847,7 +894,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;
@@ -861,6 +908,8 @@ class PPFrame_DOM implements PPFrame {
/**
* Create a new child frame
* $args is optionally a multi-root PPNode or array containing the template arguments
+ *
+ * @return PPTemplateFrame_DOM
*/
function newChild( $args = false, $title = false ) {
$namedArgs = array();
@@ -896,6 +945,12 @@ class PPFrame_DOM implements PPFrame {
return new PPTemplateFrame_DOM( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
}
+ /**
+ * @throws MWException
+ * @param $root
+ * @param $flags int
+ * @return string
+ */
function expand( $root, $flags = 0 ) {
static $expansionDepth = 0;
if ( is_string( $root ) ) {
@@ -1058,11 +1113,11 @@ class PPFrame_DOM implements PPFrame {
# Heading
$s = $this->expand( $contextNode->childNodes, $flags );
- # Insert a heading marker only for <h> children of <root>
- # This is to stop extractSections from going over multiple tree levels
- if ( $contextNode->parentNode->nodeName == 'root'
- && $this->parser->ot['html'] )
- {
+ # Insert a heading marker only for <h> children of <root>
+ # This is to stop extractSections from going over multiple tree levels
+ if ( $contextNode->parentNode->nodeName == 'root'
+ && $this->parser->ot['html'] )
+ {
# Insert heading index marker
$headingIndex = $contextNode->getAttribute( 'i' );
$titleText = $this->title->getPrefixedDBkey();
@@ -1071,7 +1126,7 @@ class PPFrame_DOM implements PPFrame {
$marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX;
$count = $contextNode->getAttribute( 'level' );
$s = substr( $s, 0, $count ) . $marker . substr( $s, $count );
- $this->parser->mStripState->general->setPair( $marker, '' );
+ $this->parser->mStripState->addGeneral( $marker, '' );
}
$out .= $s;
} else {
@@ -1107,6 +1162,11 @@ class PPFrame_DOM implements PPFrame {
return $outStack[0];
}
+ /**
+ * @param $sep
+ * @param $flags
+ * @return string
+ */
function implodeWithFlags( $sep, $flags /*, ... */ ) {
$args = array_slice( func_get_args(), 2 );
@@ -1132,6 +1192,8 @@ class PPFrame_DOM implements PPFrame {
/**
* Implode with no flags specified
* This previously called implodeWithFlags but has now been inlined to reduce stack depth
+ *
+ * @return string
*/
function implode( $sep /*, ... */ ) {
$args = array_slice( func_get_args(), 1 );
@@ -1160,6 +1222,8 @@ class PPFrame_DOM implements PPFrame {
/**
* Makes an object that, when expand()ed, will be the same as one obtained
* with implode()
+ *
+ * @return array
*/
function virtualImplode( $sep /*, ... */ ) {
$args = array_slice( func_get_args(), 1 );
@@ -1225,20 +1289,31 @@ class PPFrame_DOM implements PPFrame {
}
}
+ /**
+ * @return array
+ */
function getArguments() {
return array();
}
+ /**
+ * @return array
+ */
function getNumberedArguments() {
return array();
}
+ /**
+ * @return array
+ */
function getNamedArguments() {
return array();
}
/**
* Returns true if there are no arguments in this frame
+ *
+ * @return bool
*/
function isEmpty() {
return true;
@@ -1250,6 +1325,8 @@ class PPFrame_DOM implements PPFrame {
/**
* Returns true if the infinite loop check is OK, false if a loop is detected
+ *
+ * @return bool
*/
function loopCheck( $title ) {
return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
@@ -1257,6 +1334,8 @@ class PPFrame_DOM implements PPFrame {
/**
* Return true if the frame is a template frame
+ *
+ * @return bool
*/
function isTemplate() {
return false;
@@ -1268,9 +1347,21 @@ class PPFrame_DOM implements PPFrame {
* @ingroup Parser
*/
class PPTemplateFrame_DOM extends PPFrame_DOM {
- var $numberedArgs, $namedArgs, $parent;
+ var $numberedArgs, $namedArgs;
+
+ /**
+ * @var PPFrame_DOM
+ */
+ var $parent;
var $numberedExpansionCache, $namedExpansionCache;
+ /**
+ * @param $preprocessor
+ * @param $parent PPFrame_DOM
+ * @param $numberedArgs array
+ * @param $namedArgs array
+ * @param $title Title
+ */
function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
parent::__construct( $preprocessor );
@@ -1305,8 +1396,11 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
$s .= '}';
return $s;
}
+
/**
* Returns true if there are no arguments in this frame
+ *
+ * @return bool
*/
function isEmpty() {
return !count( $this->numberedArgs ) && !count( $this->namedArgs );
@@ -1321,7 +1415,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
}
return $arguments;
}
-
+
function getNumberedArguments() {
$arguments = array();
foreach ( array_keys($this->numberedArgs) as $key ) {
@@ -1329,7 +1423,7 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
}
return $arguments;
}
-
+
function getNamedArguments() {
$arguments = array();
foreach ( array_keys($this->namedArgs) as $key ) {
@@ -1371,6 +1465,8 @@ class PPTemplateFrame_DOM extends PPFrame_DOM {
/**
* Return true if the frame is a template frame
+ *
+ * @return bool
*/
function isTemplate() {
return true;
@@ -1405,6 +1501,9 @@ class PPCustomFrame_DOM extends PPFrame_DOM {
return $s;
}
+ /**
+ * @return bool
+ */
function isEmpty() {
return !count( $this->args );
}
@@ -1421,14 +1520,22 @@ class PPCustomFrame_DOM extends PPFrame_DOM {
* @ingroup Parser
*/
class PPNode_DOM implements PPNode {
+
+ /**
+ * @var DOMElement
+ */
var $node;
+ var $xpath;
function __construct( $node, $xpath = false ) {
$this->node = $node;
}
- function __get( $name ) {
- if ( $name == 'xpath' ) {
+ /**
+ * @return DOMXPath
+ */
+ function getXPath() {
+ if ( $this->xpath === null ) {
$this->xpath = new DOMXPath( $this->node->ownerDocument );
}
return $this->xpath;
@@ -1446,22 +1553,39 @@ class PPNode_DOM implements PPNode {
return $s;
}
+ /**
+ * @return bool|PPNode_DOM
+ */
function getChildren() {
return $this->node->childNodes ? new self( $this->node->childNodes ) : false;
}
+ /**
+ * @return bool|PPNode_DOM
+ */
function getFirstChild() {
return $this->node->firstChild ? new self( $this->node->firstChild ) : false;
}
+ /**
+ * @return bool|PPNode_DOM
+ */
function getNextSibling() {
return $this->node->nextSibling ? new self( $this->node->nextSibling ) : false;
}
+ /**
+ * @param $type
+ *
+ * @return bool|PPNode_DOM
+ */
function getChildrenOfType( $type ) {
- return new self( $this->xpath->query( $type, $this->node ) );
+ return new self( $this->getXPath()->query( $type, $this->node ) );
}
+ /**
+ * @return int
+ */
function getLength() {
if ( $this->node instanceof DOMNodeList ) {
return $this->node->length;
@@ -1470,11 +1594,18 @@ class PPNode_DOM implements PPNode {
}
}
+ /**
+ * @param $i
+ * @return bool|PPNode_DOM
+ */
function item( $i ) {
$item = $this->node->item( $i );
return $item ? new self( $item ) : false;
}
+ /**
+ * @return string
+ */
function getName() {
if ( $this->node instanceof DOMNodeList ) {
return '#nodelist';
@@ -1488,10 +1619,13 @@ class PPNode_DOM implements PPNode {
* name PPNode name
* index String index
* value PPNode value
+ *
+ * @return array
*/
function splitArg() {
- $names = $this->xpath->query( 'name', $this->node );
- $values = $this->xpath->query( 'value', $this->node );
+ $xpath = $this->getXPath();
+ $names = $xpath->query( 'name', $this->node );
+ $values = $xpath->query( 'value', $this->node );
if ( !$names->length || !$values->length ) {
throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
}
@@ -1506,12 +1640,15 @@ class PPNode_DOM implements PPNode {
/**
* Split an <ext> node into an associative array containing name, attr, inner and close
* All values in the resulting array are PPNodes. Inner and close are optional.
+ *
+ * @return array
*/
function splitExt() {
- $names = $this->xpath->query( 'name', $this->node );
- $attrs = $this->xpath->query( 'attr', $this->node );
- $inners = $this->xpath->query( 'inner', $this->node );
- $closes = $this->xpath->query( 'close', $this->node );
+ $xpath = $this->getXPath();
+ $names = $xpath->query( 'name', $this->node );
+ $attrs = $xpath->query( 'attr', $this->node );
+ $inners = $xpath->query( 'inner', $this->node );
+ $closes = $xpath->query( 'close', $this->node );
if ( !$names->length || !$attrs->length ) {
throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
}
@@ -1531,7 +1668,7 @@ class PPNode_DOM implements PPNode {
* Split a <h> node
*/
function splitHeading() {
- if ( !$this->nodeName == 'h' ) {
+ if ( $this->getName() !== 'h' ) {
throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
}
return array(
diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php
index 6cb2febc..c2d7d3d8 100644
--- a/includes/parser/Preprocessor_Hash.php
+++ b/includes/parser/Preprocessor_Hash.php
@@ -5,7 +5,7 @@
* @file
* @ingroup Parser
*/
-
+
/**
* Differences from DOM schema:
* * attribute nodes are children
@@ -13,22 +13,36 @@
* @ingroup Parser
*/
class Preprocessor_Hash implements Preprocessor {
+ /**
+ * @var Parser
+ */
var $parser;
-
+
const CACHE_VERSION = 1;
function __construct( $parser ) {
$this->parser = $parser;
}
+ /**
+ * @return PPFrame_Hash
+ */
function newFrame() {
return new PPFrame_Hash( $this );
}
+ /**
+ * @param $args
+ * @return PPCustomFrame_Hash
+ */
function newCustomFrame( $args ) {
return new PPCustomFrame_Hash( $this, $args );
}
+ /**
+ * @param $values array
+ * @return PPNode_Hash_Array
+ */
function newPartNodeArray( $values ) {
$list = array();
@@ -76,16 +90,15 @@ class Preprocessor_Hash implements Preprocessor {
* cache may be implemented at a later date which takes further advantage of these strict
* dependency requirements.
*
- * @private
+ * @return PPNode_Hash_Tree
*/
function preprocessToObj( $text, $flags = 0 ) {
wfProfileIn( __METHOD__ );
-
-
+
// Check cache.
global $wgMemc, $wgPreprocessorCacheThreshold;
-
- $cacheable = strlen( $text ) > $wgPreprocessorCacheThreshold;
+
+ $cacheable = $wgPreprocessorCacheThreshold !== false && strlen( $text ) > $wgPreprocessorCacheThreshold;
if ( $cacheable ) {
wfProfileIn( __METHOD__.'-cacheable' );
@@ -272,7 +285,7 @@ class Preprocessor_Hash implements Preprocessor {
// Search backwards for leading whitespace
$wsStart = $i ? ( $i - strspn( $revText, ' ', strlen( $text ) - $i ) ) : 0;
// Search forwards for trailing whitespace
- // $wsEnd will be the position of the last space
+ // $wsEnd will be the position of the last space (or the '>' if there's none)
$wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 );
// Eat the line if possible
// TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
@@ -302,13 +315,11 @@ class Preprocessor_Hash implements Preprocessor {
if ( $stack->top ) {
$part = $stack->top->getCurrentPart();
- if ( isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 ) {
- // Comments abutting, no change in visual end
- $part->commentEnd = $wsEnd;
- } else {
+ if ( ! (isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 )) {
$part->visualEnd = $wsStart;
- $part->commentEnd = $endPos;
}
+ // Else comments abutting, no change in visual end
+ $part->commentEnd = $endPos;
}
$i = $endPos + 1;
$inner = substr( $text, $startPos, $endPos - $startPos + 1 );
@@ -348,8 +359,8 @@ class Preprocessor_Hash implements Preprocessor {
} else {
$attrEnd = $tagEndPos;
// Find closing tag
- if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
- $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
+ if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
+ $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
{
$inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
$i = $matches[0][1] + strlen( $matches[0][0] );
@@ -414,9 +425,7 @@ class Preprocessor_Hash implements Preprocessor {
extract( $stack->getFlags() );
$i += $count;
}
- }
-
- elseif ( $found == 'line-end' ) {
+ } elseif ( $found == 'line-end' ) {
$piece = $stack->top;
// A heading must be open, otherwise \n wouldn't have been in the search list
assert( $piece->open == "\n" );
@@ -478,9 +487,7 @@ class Preprocessor_Hash 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 );
@@ -491,7 +498,7 @@ class Preprocessor_Hash implements Preprocessor {
'open' => $curChar,
'close' => $rule['end'],
'count' => $count,
- 'lineStart' => ($i > 0 && $text[$i-1] == "\n"),
+ 'lineStart' => ($i == 0 || $text[$i-1] == "\n"),
);
$stack->push( $piece );
@@ -502,9 +509,7 @@ class Preprocessor_Hash implements Preprocessor {
$accum->addLiteral( 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;
@@ -644,16 +649,12 @@ class Preprocessor_Hash implements Preprocessor {
} else {
$accum->addAccum( $element );
}
- }
-
- elseif ( $found == 'pipe' ) {
+ } elseif ( $found == 'pipe' ) {
$findEquals = true; // shortcut for getFlags()
$stack->addPart();
$accum =& $stack->getAccum();
++$i;
- }
-
- elseif ( $found == 'equals' ) {
+ } elseif ( $found == 'equals' ) {
$findEquals = false; // shortcut for getFlags()
$accum->addNodeWithText( 'equals', '=' );
$stack->getCurrentPart()->eqpos = $accum->lastNode;
@@ -676,7 +677,7 @@ class Preprocessor_Hash implements Preprocessor {
$rootNode = new PPNode_Hash_Tree( 'root' );
$rootNode->firstChild = $stack->rootAccum->firstNode;
$rootNode->lastChild = $stack->rootAccum->lastNode;
-
+
// Cache
if ($cacheable) {
$cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );
@@ -685,7 +686,7 @@ class Preprocessor_Hash implements Preprocessor {
wfProfileOut( __METHOD__.'-cacheable' );
wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" );
}
-
+
wfProfileOut( __METHOD__ );
return $rootNode;
}
@@ -714,6 +715,8 @@ class PPDStackElement_Hash extends PPDStackElement {
/**
* Get the accumulator that would result if the close is not found.
+ *
+ * @return PPDAccum_Hash
*/
function breakSyntax( $openingCount = false ) {
if ( $this->open == "\n" ) {
@@ -818,7 +821,21 @@ class PPDAccum_Hash {
* @ingroup Parser
*/
class PPFrame_Hash implements PPFrame {
- var $preprocessor, $parser, $title;
+
+ /**
+ * @var Parser
+ */
+ var $parser;
+
+ /**
+ * @var Preprocessor
+ */
+ var $preprocessor;
+
+ /**
+ * @var Title
+ */
+ var $title;
var $titleCache;
/**
@@ -850,6 +867,11 @@ class PPFrame_Hash implements PPFrame {
/**
* Create a new child frame
* $args is optionally a multi-root PPNode or array containing the template arguments
+ *
+ * @param $args PPNode_Hash_Array|array
+ * @param $title Title|false
+ *
+ * @return PPTemplateFrame_Hash
*/
function newChild( $args = false, $title = false ) {
$namedArgs = array();
@@ -880,14 +902,19 @@ class PPFrame_Hash implements PPFrame {
return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
}
+ /**
+ * @throws MWException
+ * @param $root
+ * @param $flags int
+ * @return string
+ */
function expand( $root, $flags = 0 ) {
static $expansionDepth = 0;
if ( is_string( $root ) ) {
return $root;
}
- if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() )
- {
+ if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
return '<span class="error">Node-count limit exceeded</span>';
}
if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
@@ -1016,7 +1043,7 @@ class PPFrame_Hash implements PPFrame {
$serial = count( $this->parser->mHeadings ) - 1;
$marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX;
$s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
- $this->parser->mStripState->general->setPair( $marker, '' );
+ $this->parser->mStripState->addGeneral( $marker, '' );
$out .= $s;
} else {
# Expand in virtual stack
@@ -1050,6 +1077,11 @@ class PPFrame_Hash implements PPFrame {
return $outStack[0];
}
+ /**
+ * @param $sep
+ * @param $flags
+ * @return string
+ */
function implodeWithFlags( $sep, $flags /*, ... */ ) {
$args = array_slice( func_get_args(), 2 );
@@ -1077,6 +1109,7 @@ class PPFrame_Hash implements PPFrame {
/**
* Implode with no flags specified
* This previously called implodeWithFlags but has now been inlined to reduce stack depth
+ * @return string
*/
function implode( $sep /*, ... */ ) {
$args = array_slice( func_get_args(), 1 );
@@ -1105,6 +1138,8 @@ class PPFrame_Hash implements PPFrame {
/**
* Makes an object that, when expand()ed, will be the same as one obtained
* with implode()
+ *
+ * @return PPNode_Hash_Array
*/
function virtualImplode( $sep /*, ... */ ) {
$args = array_slice( func_get_args(), 1 );
@@ -1132,6 +1167,8 @@ class PPFrame_Hash implements PPFrame {
/**
* Virtual implode with brackets
+ *
+ * @return PPNode_Hash_Array
*/
function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
$args = array_slice( func_get_args(), 3 );
@@ -1162,6 +1199,10 @@ class PPFrame_Hash implements PPFrame {
return 'frame{}';
}
+ /**
+ * @param $level bool
+ * @return array|bool|String
+ */
function getPDBK( $level = false ) {
if ( $level === false ) {
return $this->title->getPrefixedDBkey();
@@ -1170,31 +1211,50 @@ class PPFrame_Hash implements PPFrame {
}
}
+ /**
+ * @return array
+ */
function getArguments() {
return array();
}
+ /**
+ * @return array
+ */
function getNumberedArguments() {
return array();
}
+ /**
+ * @return array
+ */
function getNamedArguments() {
return array();
}
/**
* Returns true if there are no arguments in this frame
+ *
+ * @return bool
*/
function isEmpty() {
return true;
}
+ /**
+ * @param $name
+ * @return bool
+ */
function getArgument( $name ) {
return false;
}
/**
* Returns true if the infinite loop check is OK, false if a loop is detected
+ *
+ * @param $title Title
+ *
+ * @return bool
*/
function loopCheck( $title ) {
return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
@@ -1202,6 +1262,8 @@ class PPFrame_Hash implements PPFrame {
/**
* Return true if the frame is a template frame
+ *
+ * @return bool
*/
function isTemplate() {
return false;
@@ -1216,6 +1278,13 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
var $numberedArgs, $namedArgs, $parent;
var $numberedExpansionCache, $namedExpansionCache;
+ /**
+ * @param $preprocessor
+ * @param $parent
+ * @param $numberedArgs array
+ * @param $namedArgs array
+ * @param $title Title
+ */
function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
parent::__construct( $preprocessor );
@@ -1252,11 +1321,16 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
}
/**
* Returns true if there are no arguments in this frame
+ *
+ * @return bool
*/
function isEmpty() {
return !count( $this->numberedArgs ) && !count( $this->namedArgs );
}
+ /**
+ * @return array
+ */
function getArguments() {
$arguments = array();
foreach ( array_merge(
@@ -1266,7 +1340,10 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
}
return $arguments;
}
-
+
+ /**
+ * @return array
+ */
function getNumberedArguments() {
$arguments = array();
foreach ( array_keys($this->numberedArgs) as $key ) {
@@ -1274,7 +1351,10 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
}
return $arguments;
}
-
+
+ /**
+ * @return array
+ */
function getNamedArguments() {
$arguments = array();
foreach ( array_keys($this->namedArgs) as $key ) {
@@ -1283,6 +1363,10 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
return $arguments;
}
+ /**
+ * @param $index
+ * @return array|bool
+ */
function getNumberedArgument( $index ) {
if ( !isset( $this->numberedArgs[$index] ) ) {
return false;
@@ -1294,6 +1378,10 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
return $this->numberedExpansionCache[$index];
}
+ /**
+ * @param $name
+ * @return bool
+ */
function getNamedArgument( $name ) {
if ( !isset( $this->namedArgs[$name] ) ) {
return false;
@@ -1306,6 +1394,10 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
return $this->namedExpansionCache[$name];
}
+ /**
+ * @param $name
+ * @return array|bool
+ */
function getArgument( $name ) {
$text = $this->getNumberedArgument( $name );
if ( $text === false ) {
@@ -1316,6 +1408,8 @@ class PPTemplateFrame_Hash extends PPFrame_Hash {
/**
* Return true if the frame is a template frame
+ *
+ * @return bool
*/
function isTemplate() {
return true;
@@ -1350,10 +1444,17 @@ class PPCustomFrame_Hash extends PPFrame_Hash {
return $s;
}
+ /**
+ * @return bool
+ */
function isEmpty() {
return !count( $this->args );
}
+ /**
+ * @param $index
+ * @return bool
+ */
function getArgument( $index ) {
if ( !isset( $this->args[$index] ) ) {
return false;
@@ -1390,6 +1491,11 @@ class PPNode_Hash_Tree implements PPNode {
}
}
+ /**
+ * @param $name
+ * @param $text
+ * @return PPNode_Hash_Tree
+ */
static function newWithText( $name, $text ) {
$obj = new self( $name );
$obj->addChild( new PPNode_Hash_Text( $text ) );
@@ -1405,6 +1511,9 @@ class PPNode_Hash_Tree implements PPNode {
}
}
+ /**
+ * @return PPNode_Hash_Array
+ */
function getChildren() {
$children = array();
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
@@ -1431,9 +1540,24 @@ class PPNode_Hash_Tree implements PPNode {
return $children;
}
- function getLength() { return false; }
- function item( $i ) { return false; }
+ /**
+ * @return bool
+ */
+ function getLength() {
+ return false;
+ }
+ /**
+ * @param $i
+ * @return bool
+ */
+ function item( $i ) {
+ return false;
+ }
+
+ /**
+ * @return string
+ */
function getName() {
return $this->name;
}
@@ -1443,6 +1567,8 @@ class PPNode_Hash_Tree implements PPNode {
* name PPNode name
* index String index
* value PPNode value
+ *
+ * @return array
*/
function splitArg() {
$bits = array();
@@ -1474,6 +1600,8 @@ class PPNode_Hash_Tree implements PPNode {
/**
* Split an <ext> node into an associative array containing name, attr, inner and close
* All values in the resulting array are PPNodes. Inner and close are optional.
+ *
+ * @return array
*/
function splitExt() {
$bits = array();
@@ -1499,6 +1627,8 @@ class PPNode_Hash_Tree implements PPNode {
/**
* Split an <h> node
+ *
+ * @return array
*/
function splitHeading() {
if ( $this->name !== 'h' ) {
@@ -1523,6 +1653,8 @@ class PPNode_Hash_Tree implements PPNode {
/**
* Split a <template> or <tplarg> node
+ *
+ * @return array
*/
function splitTemplate() {
$parts = array();
diff --git a/includes/parser/Preprocessor_HipHop.hphp b/includes/parser/Preprocessor_HipHop.hphp
new file mode 100644
index 00000000..dc404f7c
--- /dev/null
+++ b/includes/parser/Preprocessor_HipHop.hphp
@@ -0,0 +1,1941 @@
+<?php
+/**
+ * A preprocessor optimised for HipHop, using HipHop-specific syntax.
+ * vim: ft=php
+ *
+ * @file
+ * @ingroup Parser
+ */
+
+/**
+ * @ingroup Parser
+ */
+class Preprocessor_HipHop implements Preprocessor {
+ /**
+ * @var Parser
+ */
+ var $parser;
+
+ const CACHE_VERSION = 1;
+
+ function __construct( $parser ) {
+ $this->parser = $parser;
+ }
+
+ /**
+ * @return PPFrame_HipHop
+ */
+ function newFrame() {
+ return new PPFrame_HipHop( $this );
+ }
+
+ /**
+ * @param $args
+ * @return PPCustomFrame_HipHop
+ */
+ function newCustomFrame( array $args ) {
+ return new PPCustomFrame_HipHop( $this, $args );
+ }
+
+ /**
+ * @param $values array
+ * @return PPNode_HipHop_Array
+ */
+ function newPartNodeArray( $values ) {
+ $list = array();
+
+ foreach ( $values as $k => $val ) {
+ $partNode = new PPNode_HipHop_Tree( 'part' );
+ $nameNode = new PPNode_HipHop_Tree( 'name' );
+
+ if ( is_int( $k ) ) {
+ $nameNode->addChild( new PPNode_HipHop_Attr( 'index', $k ) );
+ $partNode->addChild( $nameNode );
+ } else {
+ $nameNode->addChild( new PPNode_HipHop_Text( $k ) );
+ $partNode->addChild( $nameNode );
+ $partNode->addChild( new PPNode_HipHop_Text( '=' ) );
+ }
+
+ $valueNode = new PPNode_HipHop_Tree( 'value' );
+ $valueNode->addChild( new PPNode_HipHop_Text( $val ) );
+ $partNode->addChild( $valueNode );
+
+ $list[] = $partNode;
+ }
+
+ $node = new PPNode_HipHop_Array( $list );
+ return $node;
+ }
+
+ /**
+ * Preprocess some wikitext and return the document tree.
+ * This is the ghost of Parser::replace_variables().
+ *
+ * @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.
+ *
+ * The generated DOM tree must depend only on the input text and the flags.
+ * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
+ *
+ * Any flag added to the $flags parameter here, or any other parameter liable to cause a
+ * change in the DOM tree for a given text, must be passed through the section identifier
+ * in the section edit link and thus back to extractSections().
+ *
+ * The output of this function is currently only cached in process memory, but a persistent
+ * cache may be implemented at a later date which takes further advantage of these strict
+ * dependency requirements.
+ *
+ * @return PPNode_HipHop_Tree
+ */
+ function preprocessToObj( string $text, int $flags = 0 ) {
+ wfProfileIn( __METHOD__ );
+
+ // Check cache.
+ global $wgMemc, $wgPreprocessorCacheThreshold;
+
+ $cacheable = ($wgPreprocessorCacheThreshold !== false && strlen( $text ) > $wgPreprocessorCacheThreshold);
+ if ( $cacheable ) {
+ wfProfileIn( __METHOD__.'-cacheable' );
+
+ $cacheKey = strval( wfMemcKey( 'preprocess-hash', md5($text), $flags ) );
+ $cacheValue = strval( $wgMemc->get( $cacheKey ) );
+ if ( $cacheValue !== '' ) {
+ $version = substr( $cacheValue, 0, 8 );
+ if ( intval( $version ) == self::CACHE_VERSION ) {
+ $hash = unserialize( substr( $cacheValue, 8 ) );
+ // From the cache
+ wfDebugLog( "Preprocessor",
+ "Loaded preprocessor hash from memcached (key $cacheKey)" );
+ wfProfileOut( __METHOD__.'-cacheable' );
+ wfProfileOut( __METHOD__ );
+ return $hash;
+ }
+ }
+ wfProfileIn( __METHOD__.'-cache-miss' );
+ }
+
+ $rules = array(
+ '{' => array(
+ 'end' => '}',
+ 'names' => array(
+ 2 => 'template',
+ 3 => 'tplarg',
+ ),
+ 'min' => 2,
+ 'max' => 3,
+ ),
+ '[' => array(
+ 'end' => ']',
+ 'names' => array( 2 => 'LITERAL' ),
+ 'min' => 2,
+ 'max' => 2,
+ )
+ );
+
+ $forInclusion = (bool)( $flags & Parser::PTD_FOR_INCLUSION );
+
+ $xmlishElements = (array)$this->parser->getStripList();
+ $enableOnlyinclude = false;
+ if ( $forInclusion ) {
+ $ignoredTags = array( 'includeonly', '/includeonly' );
+ $ignoredElements = array( 'noinclude' );
+ $xmlishElements[] = 'noinclude';
+ if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) {
+ $enableOnlyinclude = true;
+ }
+ } else if ( $this->parser->ot['wiki'] ) {
+ $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude', 'includeonly', '/includeonly' );
+ $ignoredElements = array();
+ } else {
+ $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' );
+ $ignoredElements = array( 'includeonly' );
+ $xmlishElements[] = 'includeonly';
+ }
+ $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
+
+ // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
+ $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
+
+ $stack = new PPDStack_HipHop;
+
+ $searchBase = "[{<\n";
+ $revText = strrev( $text ); // For fast reverse searches
+
+ $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start
+ $accum = $stack->getAccum(); # Current accumulator
+ $headingIndex = 1;
+ $stackFlags = array(
+ 'findPipe' => false, # True to take notice of pipe characters
+ 'findEquals' => false, # True to find equals signs in arguments
+ 'inHeading' => false, # True if $i is inside a possible heading
+ );
+ $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i
+ $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude>
+ $fakeLineStart = true; # Do a line-start run without outputting an LF character
+
+ while ( true ) {
+ //$this->memCheck();
+
+ if ( $findOnlyinclude ) {
+ // Ignore all input up to the next <onlyinclude>
+ $variantStartPos = strpos( $text, '<onlyinclude>', $i );
+ if ( $variantStartPos === false ) {
+ // Ignored section runs to the end
+ $accum->addNodeWithText( 'ignore', strval( substr( $text, $i ) ) );
+ break;
+ }
+ $startPos1 = intval( $variantStartPos );
+ $tagEndPos = $startPos1 + strlen( '<onlyinclude>' ); // past-the-end
+ $accum->addNodeWithText( 'ignore', strval( substr( $text, $i, $tagEndPos - $i ) ) );
+ $i = $tagEndPos;
+ $findOnlyinclude = false;
+ }
+
+ if ( $fakeLineStart ) {
+ $found = 'line-start';
+ $curChar = '';
+ } else {
+ # Find next opening brace, closing brace or pipe
+ $search = $searchBase;
+ if ( $stack->top === false ) {
+ $currentClosing = '';
+ } else {
+ $currentClosing = strval( $stack->getTop()->close );
+ $search .= $currentClosing;
+ }
+ if ( $stackFlags['findPipe'] ) {
+ $search .= '|';
+ }
+ if ( $stackFlags['findEquals'] ) {
+ // First equals will be for the template
+ $search .= '=';
+ }
+ $rule = null;
+ # Output literal section, advance input counter
+ $literalLength = intval( strcspn( $text, $search, $i ) );
+ if ( $literalLength > 0 ) {
+ $accum->addLiteral( strval( substr( $text, $i, $literalLength ) ) );
+ $i += $literalLength;
+ }
+ if ( $i >= strlen( $text ) ) {
+ if ( $currentClosing === "\n" ) {
+ // Do a past-the-end run to finish off the heading
+ $curChar = '';
+ $found = 'line-end';
+ } else {
+ # All done
+ break;
+ }
+ } else {
+ $curChar = $text[$i];
+ if ( $curChar === '|' ) {
+ $found = 'pipe';
+ } elseif ( $curChar === '=' ) {
+ $found = 'equals';
+ } elseif ( $curChar === '<' ) {
+ $found = 'angle';
+ } elseif ( $curChar === "\n" ) {
+ if ( $stackFlags['inHeading'] ) {
+ $found = 'line-end';
+ } else {
+ $found = 'line-start';
+ }
+ } elseif ( $curChar === $currentClosing ) {
+ $found = 'close';
+ } elseif ( isset( $rules[$curChar] ) ) {
+ $found = 'open';
+ $rule = $rules[$curChar];
+ } else {
+ # Some versions of PHP have a strcspn which stops on null characters
+ # Ignore and continue
+ ++$i;
+ continue;
+ }
+ }
+ }
+
+ if ( $found === 'angle' ) {
+ $matches = false;
+ // Handle </onlyinclude>
+ if ( $enableOnlyinclude
+ && substr( $text, $i, strlen( '</onlyinclude>' ) ) === '</onlyinclude>' )
+ {
+ $findOnlyinclude = true;
+ continue;
+ }
+
+ // Determine element name
+ if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
+ // Element name missing or not listed
+ $accum->addLiteral( '<' );
+ ++$i;
+ continue;
+ }
+ // Handle comments
+ if ( isset( $matches[2] ) && $matches[2] === '!--' ) {
+ // To avoid leaving blank lines, when a comment is both preceded
+ // and followed by a newline (ignoring spaces), trim leading and
+ // trailing spaces and one of the newlines.
+
+ // Find the end
+ $variantEndPos = strpos( $text, '-->', $i + 4 );
+ if ( $variantEndPos === false ) {
+ // Unclosed comment in input, runs to end
+ $inner = strval( substr( $text, $i ) );
+ $accum->addNodeWithText( 'comment', $inner );
+ $i = strlen( $text );
+ } else {
+ $endPos = intval( $variantEndPos );
+ // Search backwards for leading whitespace
+ if ( $i ) {
+ $wsStart = $i - intval( strspn( $revText, ' ', strlen( $text ) - $i ) );
+ } else {
+ $wsStart = 0;
+ }
+ // Search forwards for trailing whitespace
+ // $wsEnd will be the position of the last space (or the '>' if there's none)
+ $wsEnd = $endPos + 2 + intval( strspn( $text, ' ', $endPos + 3 ) );
+ // Eat the line if possible
+ // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
+ // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
+ // it's a possible beneficial b/c break.
+ if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) === "\n"
+ && substr( $text, $wsEnd + 1, 1 ) === "\n" )
+ {
+ $startPos2 = $wsStart;
+ $endPos = $wsEnd + 1;
+ // Remove leading whitespace from the end of the accumulator
+ // Sanity check first though
+ $wsLength = $i - $wsStart;
+ if ( $wsLength > 0
+ && $accum->lastNode instanceof PPNode_HipHop_Text
+ && substr( $accum->lastNode->value, -$wsLength ) === str_repeat( ' ', $wsLength ) )
+ {
+ $accum->lastNode->value = strval( substr( $accum->lastNode->value, 0, -$wsLength ) );
+ }
+ // Do a line-start run next time to look for headings after the comment
+ $fakeLineStart = true;
+ } else {
+ // No line to eat, just take the comment itself
+ $startPos2 = $i;
+ $endPos += 2;
+ }
+
+ if ( $stack->top ) {
+ $part = $stack->getTop()->getCurrentPart();
+ if ( ! (isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 )) {
+ $part->visualEnd = $wsStart;
+ }
+ // Else comments abutting, no change in visual end
+ $part->commentEnd = $endPos;
+ }
+ $i = $endPos + 1;
+ $inner = strval( substr( $text, $startPos2, $endPos - $startPos2 + 1 ) );
+ $accum->addNodeWithText( 'comment', $inner );
+ }
+ continue;
+ }
+ $name = strval( $matches[1] );
+ $lowerName = strtolower( $name );
+ $attrStart = $i + strlen( $name ) + 1;
+
+ // Find end of tag
+ $variantTagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
+ if ( $variantTagEndPos === false ) {
+ // Infinite backtrack
+ // Disable tag search to prevent worst-case O(N^2) performance
+ $noMoreGT = true;
+ $accum->addLiteral( '<' );
+ ++$i;
+ continue;
+ }
+ $tagEndPos = intval( $variantTagEndPos );
+
+ // Handle ignored tags
+ if ( in_array( $lowerName, $ignoredTags ) ) {
+ $accum->addNodeWithText( 'ignore', strval( substr( $text, $i, $tagEndPos - $i + 1 ) ) );
+ $i = $tagEndPos + 1;
+ continue;
+ }
+
+ $tagStartPos = $i;
+ $inner = $close = '';
+ if ( $text[$tagEndPos-1] === '/' ) {
+ // Short end tag
+ $attrEnd = $tagEndPos - 1;
+ $shortEnd = true;
+ $inner = '';
+ $i = $tagEndPos + 1;
+ $haveClose = false;
+ } else {
+ $attrEnd = $tagEndPos;
+ $shortEnd = false;
+ // Find closing tag
+ if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
+ $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
+ {
+ $inner = strval( substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ) );
+ $i = intval( $matches[0][1] ) + strlen( $matches[0][0] );
+ $close = strval( $matches[0][0] );
+ $haveClose = true;
+ } else {
+ // No end tag -- let it run out to the end of the text.
+ $inner = strval( substr( $text, $tagEndPos + 1 ) );
+ $i = strlen( $text );
+ $haveClose = false;
+ }
+ }
+ // <includeonly> and <noinclude> just become <ignore> tags
+ if ( in_array( $lowerName, $ignoredElements ) ) {
+ $accum->addNodeWithText( 'ignore', strval( substr( $text, $tagStartPos, $i - $tagStartPos ) ) );
+ continue;
+ }
+
+ if ( $attrEnd <= $attrStart ) {
+ $attr = '';
+ } else {
+ // Note that the attr element contains the whitespace between name and attribute,
+ // this is necessary for precise reconstruction during pre-save transform.
+ $attr = strval( substr( $text, $attrStart, $attrEnd - $attrStart ) );
+ }
+
+ $extNode = new PPNode_HipHop_Tree( 'ext' );
+ $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'name', $name ) );
+ $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'attr', $attr ) );
+ if ( !$shortEnd ) {
+ $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'inner', $inner ) );
+ }
+ if ( $haveClose ) {
+ $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'close', $close ) );
+ }
+ $accum->addNode( $extNode );
+ }
+
+ elseif ( $found === 'line-start' ) {
+ // Is this the start of a heading?
+ // Line break belongs before the heading element in any case
+ if ( $fakeLineStart ) {
+ $fakeLineStart = false;
+ } else {
+ $accum->addLiteral( $curChar );
+ $i++;
+ }
+
+ $count = intval( strspn( $text, '=', $i, 6 ) );
+ if ( $count == 1 && $stackFlags['findEquals'] ) {
+ // DWIM: This looks kind of like a name/value separator
+ // Let's let the equals handler have it and break the potential heading
+ // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex.
+ } elseif ( $count > 0 ) {
+ $partData = array(
+ 'open' => "\n",
+ 'close' => "\n",
+ 'parts' => array( new PPDPart_HipHop( str_repeat( '=', $count ) ) ),
+ 'startPos' => $i,
+ 'count' => $count );
+ $stack->push( $partData );
+ $accum = $stack->getAccum();
+ $stackFlags = $stack->getFlags();
+ $i += $count;
+ }
+ } elseif ( $found === 'line-end' ) {
+ $piece = $stack->getTop();
+ // A heading must be open, otherwise \n wouldn't have been in the search list
+ assert( $piece->open === "\n" );
+ $part = $piece->getCurrentPart();
+ // Search back through the input to see if it has a proper close
+ // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient
+ $wsLength = intval( strspn( $revText, " \t", strlen( $text ) - $i ) );
+ $searchStart = $i - $wsLength;
+ if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
+ // Comment found at line end
+ // Search for equals signs before the comment
+ $searchStart = intval( $part->visualEnd );
+ $searchStart -= intval( strspn( $revText, " \t", strlen( $text ) - $searchStart ) );
+ }
+ $count = intval( $piece->count );
+ $equalsLength = intval( strspn( $revText, '=', strlen( $text ) - $searchStart ) );
+ $isTreeNode = false;
+ $resultAccum = $accum;
+ if ( $equalsLength > 0 ) {
+ 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)
+ $count = $equalsLength;
+ if ( $count < 3 ) {
+ $count = 0;
+ } else {
+ $count = intval( ( $count - 1 ) / 2 );
+ if ( $count > 6 ) {
+ $count = 6;
+ }
+ }
+ } else {
+ if ( $count > $equalsLength ) {
+ $count = $equalsLength;
+ }
+ }
+ if ( $count > 0 ) {
+ // Normal match, output <h>
+ $tree = new PPNode_HipHop_Tree( 'possible-h' );
+ $tree->addChild( new PPNode_HipHop_Attr( 'level', $count ) );
+ $tree->addChild( new PPNode_HipHop_Attr( 'i', $headingIndex++ ) );
+ $tree->lastChild->nextSibling = $accum->firstNode;
+ $tree->lastChild = $accum->lastNode;
+ $isTreeNode = true;
+ } else {
+ // Single equals sign on its own line, count=0
+ // Output $resultAccum
+ }
+ } else {
+ // No match, no <h>, just pass down the inner text
+ // Output $resultAccum
+ }
+ // Unwind the stack
+ $stack->pop();
+ $accum = $stack->getAccum();
+ $stackFlags = $stack->getFlags();
+
+ // Append the result to the enclosing accumulator
+ if ( $isTreeNode ) {
+ $accum->addNode( $tree );
+ } else {
+ $accum->addAccum( $resultAccum );
+ }
+ // Note that we do NOT increment the input pointer.
+ // This is because the closing linebreak could be the opening linebreak of
+ // 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' ) {
+ # count opening brace characters
+ $count = intval( strspn( $text, $curChar, $i ) );
+
+ # we need to add to stack only if opening brace count is enough for one of the rules
+ if ( $count >= $rule['min'] ) {
+ # Add it to the stack
+ $partData = array(
+ 'open' => $curChar,
+ 'close' => $rule['end'],
+ 'count' => $count,
+ 'lineStart' => ($i == 0 || $text[$i-1] === "\n"),
+ );
+
+ $stack->push( $partData );
+ $accum = $stack->getAccum();
+ $stackFlags = $stack->getFlags();
+ } else {
+ # Add literal brace(s)
+ $accum->addLiteral( str_repeat( $curChar, $count ) );
+ }
+ $i += $count;
+ } elseif ( $found === 'close' ) {
+ $piece = $stack->getTop();
+ # lets check if there are enough characters for closing brace
+ $maxCount = intval( $piece->count );
+ $count = intval( strspn( $text, $curChar, $i, $maxCount ) );
+
+ # check for maximum matching characters (if there are 5 closing
+ # characters, we will probably need only 3 - depending on the rules)
+ $rule = $rules[$piece->open];
+ if ( $count > $rule['max'] ) {
+ # The specified maximum exists in the callback array, unless the caller
+ # has made an error
+ $matchingCount = intval( $rule['max'] );
+ } else {
+ # Count is less than the maximum
+ # Skip any gaps in the callback array to find the true largest match
+ # Need to use array_key_exists not isset because the callback can be null
+ $matchingCount = $count;
+ while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
+ --$matchingCount;
+ }
+ }
+
+ if ($matchingCount <= 0) {
+ # No matching element found in callback array
+ # Output a literal closing brace and continue
+ $accum->addLiteral( str_repeat( $curChar, $count ) );
+ $i += $count;
+ continue;
+ }
+ $name = strval( $rule['names'][$matchingCount] );
+ $isTreeNode = false;
+ if ( $name === 'LITERAL' ) {
+ // No element, just literal text
+ $resultAccum = $piece->breakSyntax( $matchingCount );
+ $resultAccum->addLiteral( str_repeat( $rule['end'], $matchingCount ) );
+ } else {
+ # Create XML element
+ # Note: $parts is already XML, does not need to be encoded further
+ $isTreeNode = true;
+ $parts = $piece->parts;
+ $titleAccum = PPDAccum_HipHop::cast( $parts[0]->out );
+ unset( $parts[0] );
+
+ $tree = new PPNode_HipHop_Tree( $name );
+
+ # The invocation is at the start of the line if lineStart is set in
+ # the stack, and all opening brackets are used up.
+ if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
+ $tree->addChild( new PPNode_HipHop_Attr( 'lineStart', 1 ) );
+ }
+ $titleNode = new PPNode_HipHop_Tree( 'title' );
+ $titleNode->firstChild = $titleAccum->firstNode;
+ $titleNode->lastChild = $titleAccum->lastNode;
+ $tree->addChild( $titleNode );
+ $argIndex = 1;
+ foreach ( $parts as $variantPart ) {
+ $part = PPDPart_HipHop::cast( $variantPart );
+ if ( isset( $part->eqpos ) ) {
+ // Find equals
+ $lastNode = false;
+ for ( $node = $part->out->firstNode; $node; $node = $node->nextSibling ) {
+ if ( $node === $part->eqpos ) {
+ break;
+ }
+ $lastNode = $node;
+ }
+ if ( !$node ) {
+ throw new MWException( __METHOD__. ': eqpos not found' );
+ }
+ if ( $node->name !== 'equals' ) {
+ throw new MWException( __METHOD__ .': eqpos is not equals' );
+ }
+ $equalsNode = $node;
+
+ // Construct name node
+ $nameNode = new PPNode_HipHop_Tree( 'name' );
+ if ( $lastNode !== false ) {
+ $lastNode->nextSibling = false;
+ $nameNode->firstChild = $part->out->firstNode;
+ $nameNode->lastChild = $lastNode;
+ }
+
+ // Construct value node
+ $valueNode = new PPNode_HipHop_Tree( 'value' );
+ if ( $equalsNode->nextSibling !== false ) {
+ $valueNode->firstChild = $equalsNode->nextSibling;
+ $valueNode->lastChild = $part->out->lastNode;
+ }
+ $partNode = new PPNode_HipHop_Tree( 'part' );
+ $partNode->addChild( $nameNode );
+ $partNode->addChild( $equalsNode->firstChild );
+ $partNode->addChild( $valueNode );
+ $tree->addChild( $partNode );
+ } else {
+ $partNode = new PPNode_HipHop_Tree( 'part' );
+ $nameNode = new PPNode_HipHop_Tree( 'name' );
+ $nameNode->addChild( new PPNode_HipHop_Attr( 'index', $argIndex++ ) );
+ $valueNode = new PPNode_HipHop_Tree( 'value' );
+ $valueNode->firstChild = $part->out->firstNode;
+ $valueNode->lastChild = $part->out->lastNode;
+ $partNode->addChild( $nameNode );
+ $partNode->addChild( $valueNode );
+ $tree->addChild( $partNode );
+ }
+ }
+ }
+
+ # Advance input pointer
+ $i += $matchingCount;
+
+ # Unwind the stack
+ $stack->pop();
+ $accum = $stack->getAccum();
+
+ # Re-add the old stack element if it still has unmatched opening characters remaining
+ if ($matchingCount < $piece->count) {
+ $piece->parts = array( new PPDPart_HipHop );
+ $piece->count -= $matchingCount;
+ # do we still qualify for any callback with remaining count?
+ $names = $rules[$piece->open]['names'];
+ $skippedBraces = 0;
+ $enclosingAccum = $accum;
+ while ( $piece->count ) {
+ if ( array_key_exists( $piece->count, $names ) ) {
+ $stack->push( $piece );
+ $accum = $stack->getAccum();
+ break;
+ }
+ --$piece->count;
+ $skippedBraces ++;
+ }
+ $enclosingAccum->addLiteral( str_repeat( $piece->open, $skippedBraces ) );
+ }
+
+ $stackFlags = $stack->getFlags();
+
+ # Add XML element to the enclosing accumulator
+ if ( $isTreeNode ) {
+ $accum->addNode( $tree );
+ } else {
+ $accum->addAccum( $resultAccum );
+ }
+ } elseif ( $found === 'pipe' ) {
+ $stackFlags['findEquals'] = true; // shortcut for getFlags()
+ $stack->addPart();
+ $accum = $stack->getAccum();
+ ++$i;
+ } elseif ( $found === 'equals' ) {
+ $stackFlags['findEquals'] = false; // shortcut for getFlags()
+ $accum->addNodeWithText( 'equals', '=' );
+ $stack->getCurrentPart()->eqpos = $accum->lastNode;
+ ++$i;
+ }
+ }
+
+ # Output any remaining unclosed brackets
+ foreach ( $stack->stack as $variantPiece ) {
+ $piece = PPDStackElement_HipHop::cast( $variantPiece );
+ $stack->rootAccum->addAccum( $piece->breakSyntax() );
+ }
+
+ # Enable top-level headings
+ for ( $node = $stack->rootAccum->firstNode; $node; $node = $node->nextSibling ) {
+ if ( isset( $node->name ) && $node->name === 'possible-h' ) {
+ $node->name = 'h';
+ }
+ }
+
+ $rootNode = new PPNode_HipHop_Tree( 'root' );
+ $rootNode->firstChild = $stack->rootAccum->firstNode;
+ $rootNode->lastChild = $stack->rootAccum->lastNode;
+
+ // Cache
+ if ($cacheable) {
+ $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );
+ $wgMemc->set( $cacheKey, $cacheValue, 86400 );
+ wfProfileOut( __METHOD__.'-cache-miss' );
+ wfProfileOut( __METHOD__.'-cacheable' );
+ wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $rootNode;
+ }
+}
+
+
+
+/**
+ * Stack class to help Preprocessor::preprocessToObj()
+ * @ingroup Parser
+ */
+class PPDStack_HipHop {
+ var $stack, $rootAccum;
+
+ /**
+ * @var PPDStack
+ */
+ var $top;
+ var $out;
+
+ static $false = false;
+
+ function __construct() {
+ $this->stack = array();
+ $this->top = false;
+ $this->rootAccum = new PPDAccum_HipHop;
+ $this->accum = $this->rootAccum;
+ }
+
+ /**
+ * @return int
+ */
+ function count() {
+ return count( $this->stack );
+ }
+
+ function getAccum() {
+ return PPDAccum_HipHop::cast( $this->accum );
+ }
+
+ function getCurrentPart() {
+ return $this->getTop()->getCurrentPart();
+ }
+
+ function getTop() {
+ return PPDStackElement_HipHop::cast( $this->top );
+ }
+
+ function push( $data ) {
+ if ( $data instanceof PPDStackElement_HipHop ) {
+ $this->stack[] = $data;
+ } else {
+ $this->stack[] = new PPDStackElement_HipHop( $data );
+ }
+ $this->top = $this->stack[ count( $this->stack ) - 1 ];
+ $this->accum = $this->top->getAccum();
+ }
+
+ function pop() {
+ if ( !count( $this->stack ) ) {
+ throw new MWException( __METHOD__.': no elements remaining' );
+ }
+ $temp = array_pop( $this->stack );
+
+ if ( count( $this->stack ) ) {
+ $this->top = $this->stack[ count( $this->stack ) - 1 ];
+ $this->accum = $this->top->getAccum();
+ } else {
+ $this->top = self::$false;
+ $this->accum = $this->rootAccum;
+ }
+ return $temp;
+ }
+
+ function addPart( $s = '' ) {
+ $this->top->addPart( $s );
+ $this->accum = $this->top->getAccum();
+ }
+
+ /**
+ * @return array
+ */
+ function getFlags() {
+ if ( !count( $this->stack ) ) {
+ return array(
+ 'findEquals' => false,
+ 'findPipe' => false,
+ 'inHeading' => false,
+ );
+ } else {
+ return $this->top->getFlags();
+ }
+ }
+}
+
+/**
+ * @ingroup Parser
+ */
+class PPDStackElement_HipHop {
+ var $open, // Opening character (\n for heading)
+ $close, // Matching closing character
+ $count, // Number of opening characters found (number of "=" for heading)
+ $parts, // Array of PPDPart objects describing pipe-separated parts.
+ $lineStart; // True if the open char appeared at the start of the input line. Not set for headings.
+
+ static function cast( PPDStackElement_HipHop $obj ) {
+ return $obj;
+ }
+
+ function __construct( $data = array() ) {
+ $this->parts = array( new PPDPart_HipHop );
+
+ foreach ( $data as $name => $value ) {
+ $this->$name = $value;
+ }
+ }
+
+ function getAccum() {
+ return PPDAccum_HipHop::cast( $this->parts[count($this->parts) - 1]->out );
+ }
+
+ function addPart( $s = '' ) {
+ $this->parts[] = new PPDPart_HipHop( $s );
+ }
+
+ function getCurrentPart() {
+ return PPDPart_HipHop::cast( $this->parts[count($this->parts) - 1] );
+ }
+
+ /**
+ * @return array
+ */
+ function getFlags() {
+ $partCount = count( $this->parts );
+ $findPipe = $this->open !== "\n" && $this->open !== '[';
+ return array(
+ 'findPipe' => $findPipe,
+ 'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
+ 'inHeading' => $this->open === "\n",
+ );
+ }
+
+ /**
+ * Get the accumulator that would result if the close is not found.
+ *
+ * @return PPDAccum_HipHop
+ */
+ function breakSyntax( $openingCount = false ) {
+ if ( $this->open === "\n" ) {
+ $accum = PPDAccum_HipHop::cast( $this->parts[0]->out );
+ } else {
+ if ( $openingCount === false ) {
+ $openingCount = $this->count;
+ }
+ $accum = new PPDAccum_HipHop;
+ $accum->addLiteral( str_repeat( $this->open, $openingCount ) );
+ $first = true;
+ foreach ( $this->parts as $part ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $accum->addLiteral( '|' );
+ }
+ $accum->addAccum( $part->out );
+ }
+ }
+ return $accum;
+ }
+}
+
+/**
+ * @ingroup Parser
+ */
+class PPDPart_HipHop {
+ var $out; // Output accumulator object
+
+ // Optional member variables:
+ // eqpos Position of equals sign in output accumulator
+ // commentEnd Past-the-end input pointer for the last comment encountered
+ // visualEnd Past-the-end input pointer for the end of the accumulator minus comments
+
+ function __construct( $out = '' ) {
+ $this->out = new PPDAccum_HipHop;
+ if ( $out !== '' ) {
+ $this->out->addLiteral( $out );
+ }
+ }
+
+ static function cast( PPDPart_HipHop $obj ) {
+ return $obj;
+ }
+}
+
+/**
+ * @ingroup Parser
+ */
+class PPDAccum_HipHop {
+ var $firstNode, $lastNode;
+
+ function __construct() {
+ $this->firstNode = $this->lastNode = false;
+ }
+
+ static function cast( PPDAccum_HipHop $obj ) {
+ return $obj;
+ }
+
+ /**
+ * Append a string literal
+ */
+ function addLiteral( string $s ) {
+ if ( $this->lastNode === false ) {
+ $this->firstNode = $this->lastNode = new PPNode_HipHop_Text( $s );
+ } elseif ( $this->lastNode instanceof PPNode_HipHop_Text ) {
+ $this->lastNode->value .= $s;
+ } else {
+ $this->lastNode->nextSibling = new PPNode_HipHop_Text( $s );
+ $this->lastNode = $this->lastNode->nextSibling;
+ }
+ }
+
+ /**
+ * Append a PPNode
+ */
+ function addNode( PPNode $node ) {
+ if ( $this->lastNode === false ) {
+ $this->firstNode = $this->lastNode = $node;
+ } else {
+ $this->lastNode->nextSibling = $node;
+ $this->lastNode = $node;
+ }
+ }
+
+ /**
+ * Append a tree node with text contents
+ */
+ function addNodeWithText( string $name, string $value ) {
+ $node = PPNode_HipHop_Tree::newWithText( $name, $value );
+ $this->addNode( $node );
+ }
+
+ /**
+ * Append a PPDAccum_HipHop
+ * Takes over ownership of the nodes in the source argument. These nodes may
+ * subsequently be modified, especially nextSibling.
+ */
+ function addAccum( PPDAccum_HipHop $accum ) {
+ if ( $accum->lastNode === false ) {
+ // nothing to add
+ } elseif ( $this->lastNode === false ) {
+ $this->firstNode = $accum->firstNode;
+ $this->lastNode = $accum->lastNode;
+ } else {
+ $this->lastNode->nextSibling = $accum->firstNode;
+ $this->lastNode = $accum->lastNode;
+ }
+ }
+}
+
+/**
+ * An expansion frame, used as a context to expand the result of preprocessToObj()
+ * @ingroup Parser
+ */
+class PPFrame_HipHop implements PPFrame {
+
+ /**
+ * @var Parser
+ */
+ var $parser;
+
+ /**
+ * @var Preprocessor
+ */
+ var $preprocessor;
+
+ /**
+ * @var Title
+ */
+ var $title;
+ var $titleCache;
+
+ /**
+ * Hashtable listing templates which are disallowed for expansion in this frame,
+ * having been encountered previously in parent frames.
+ */
+ var $loopCheckHash;
+
+ /**
+ * Recursion depth of this frame, top = 0
+ * Note that this is NOT the same as expansion depth in expand()
+ */
+ var $depth;
+
+
+ /**
+ * Construct a new preprocessor frame.
+ * @param $preprocessor Preprocessor: the parent preprocessor
+ */
+ function __construct( $preprocessor ) {
+ $this->preprocessor = $preprocessor;
+ $this->parser = $preprocessor->parser;
+ $this->title = $this->parser->mTitle;
+ $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false );
+ $this->loopCheckHash = array();
+ $this->depth = 0;
+ }
+
+ /**
+ * Create a new child frame
+ * $args is optionally a multi-root PPNode or array containing the template arguments
+ *
+ * @param $args PPNode_HipHop_Array|array
+ * @param $title Title|false
+ *
+ * @return PPTemplateFrame_HipHop
+ */
+ function newChild( $args = false, $title = false ) {
+ $namedArgs = array();
+ $numberedArgs = array();
+ if ( $title === false ) {
+ $title = $this->title;
+ }
+ if ( $args !== false ) {
+ if ( $args instanceof PPNode_HipHop_Array ) {
+ $args = $args->value;
+ } elseif ( !is_array( $args ) ) {
+ throw new MWException( __METHOD__ . ': $args must be array or PPNode_HipHop_Array' );
+ }
+ foreach ( $args as $arg ) {
+ $bits = $arg->splitArg();
+ if ( $bits['index'] !== '' ) {
+ // Numbered parameter
+ $numberedArgs[$bits['index']] = $bits['value'];
+ unset( $namedArgs[$bits['index']] );
+ } else {
+ // Named parameter
+ $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
+ $namedArgs[$name] = $bits['value'];
+ unset( $numberedArgs[$name] );
+ }
+ }
+ }
+ return new PPTemplateFrame_HipHop( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
+ }
+
+ /**
+ * @throws MWException
+ * @param $root
+ * @param $flags int
+ * @return string
+ */
+ function expand( $root, $flags = 0 ) {
+ static $expansionDepth = 0;
+ if ( is_string( $root ) ) {
+ return $root;
+ }
+
+ if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
+ return '<span class="error">Node-count limit exceeded</span>';
+ }
+ if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
+ return '<span class="error">Expansion depth limit exceeded</span>';
+ }
+ ++$expansionDepth;
+
+ $outStack = array( '', '' );
+ $iteratorStack = array( false, $root );
+ $indexStack = array( 0, 0 );
+
+ while ( count( $iteratorStack ) > 1 ) {
+ $level = count( $outStack ) - 1;
+ $iteratorNode =& $iteratorStack[ $level ];
+ $out =& $outStack[$level];
+ $index =& $indexStack[$level];
+
+ if ( is_array( $iteratorNode ) ) {
+ if ( $index >= count( $iteratorNode ) ) {
+ // All done with this iterator
+ $iteratorStack[$level] = false;
+ $contextNode = false;
+ } else {
+ $contextNode = $iteratorNode[$index];
+ $index++;
+ }
+ } elseif ( $iteratorNode instanceof PPNode_HipHop_Array ) {
+ if ( $index >= $iteratorNode->getLength() ) {
+ // All done with this iterator
+ $iteratorStack[$level] = false;
+ $contextNode = false;
+ } else {
+ $contextNode = $iteratorNode->item( $index );
+ $index++;
+ }
+ } else {
+ // Copy to $contextNode and then delete from iterator stack,
+ // because this is not an iterator but we do have to execute it once
+ $contextNode = $iteratorStack[$level];
+ $iteratorStack[$level] = false;
+ }
+
+ $newIterator = false;
+
+ if ( $contextNode === false ) {
+ // nothing to do
+ } elseif ( is_string( $contextNode ) ) {
+ $out .= $contextNode;
+ } elseif ( is_array( $contextNode ) || $contextNode instanceof PPNode_HipHop_Array ) {
+ $newIterator = $contextNode;
+ } elseif ( $contextNode instanceof PPNode_HipHop_Attr ) {
+ // No output
+ } elseif ( $contextNode instanceof PPNode_HipHop_Text ) {
+ $out .= $contextNode->value;
+ } elseif ( $contextNode instanceof PPNode_HipHop_Tree ) {
+ if ( $contextNode->name === 'template' ) {
+ # Double-brace expansion
+ $bits = $contextNode->splitTemplate();
+ if ( $flags & PPFrame::NO_TEMPLATES ) {
+ $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] );
+ } else {
+ $ret = $this->parser->braceSubstitution( $bits, $this );
+ if ( isset( $ret['object'] ) ) {
+ $newIterator = $ret['object'];
+ } else {
+ $out .= $ret['text'];
+ }
+ }
+ } elseif ( $contextNode->name === 'tplarg' ) {
+ # Triple-brace expansion
+ $bits = $contextNode->splitTemplate();
+ if ( $flags & PPFrame::NO_ARGS ) {
+ $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] );
+ } else {
+ $ret = $this->parser->argSubstitution( $bits, $this );
+ if ( isset( $ret['object'] ) ) {
+ $newIterator = $ret['object'];
+ } else {
+ $out .= $ret['text'];
+ }
+ }
+ } elseif ( $contextNode->name === 'comment' ) {
+ # HTML-style comment
+ # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
+ if ( $this->parser->ot['html']
+ || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
+ || ( $flags & PPFrame::STRIP_COMMENTS ) )
+ {
+ $out .= '';
+ }
+ # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
+ # Not in RECOVER_COMMENTS mode (extractSections) though
+ elseif ( $this->parser->ot['wiki'] && ! ( $flags & PPFrame::RECOVER_COMMENTS ) ) {
+ $out .= $this->parser->insertStripItem( $contextNode->firstChild->value );
+ }
+ # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
+ else {
+ $out .= $contextNode->firstChild->value;
+ }
+ } elseif ( $contextNode->name === 'ignore' ) {
+ # Output suppression used by <includeonly> etc.
+ # OT_WIKI will only respect <ignore> in substed templates.
+ # The other output types respect it unless NO_IGNORE is set.
+ # extractSections() sets NO_IGNORE and so never respects it.
+ if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) {
+ $out .= $contextNode->firstChild->value;
+ } else {
+ //$out .= '';
+ }
+ } elseif ( $contextNode->name === 'ext' ) {
+ # Extension tag
+ $bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null );
+ $out .= $this->parser->extensionSubstitution( $bits, $this );
+ } elseif ( $contextNode->name === 'h' ) {
+ # Heading
+ if ( $this->parser->ot['html'] ) {
+ # Expand immediately and insert heading index marker
+ $s = '';
+ for ( $node = $contextNode->firstChild; $node; $node = $node->nextSibling ) {
+ $s .= $this->expand( $node, $flags );
+ }
+
+ $bits = $contextNode->splitHeading();
+ $titleText = $this->title->getPrefixedDBkey();
+ $this->parser->mHeadings[] = array( $titleText, $bits['i'] );
+ $serial = count( $this->parser->mHeadings ) - 1;
+ $marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX;
+ $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
+ $this->parser->mStripState->addGeneral( $marker, '' );
+ $out .= $s;
+ } else {
+ # Expand in virtual stack
+ $newIterator = $contextNode->getChildren();
+ }
+ } else {
+ # Generic recursive expansion
+ $newIterator = $contextNode->getChildren();
+ }
+ } else {
+ throw new MWException( __METHOD__.': Invalid parameter type' );
+ }
+
+ if ( $newIterator !== false ) {
+ $outStack[] = '';
+ $iteratorStack[] = $newIterator;
+ $indexStack[] = 0;
+ } elseif ( $iteratorStack[$level] === false ) {
+ // Return accumulated value to parent
+ // With tail recursion
+ while ( $iteratorStack[$level] === false && $level > 0 ) {
+ $outStack[$level - 1] .= $out;
+ array_pop( $outStack );
+ array_pop( $iteratorStack );
+ array_pop( $indexStack );
+ $level--;
+ }
+ }
+ }
+ --$expansionDepth;
+ return $outStack[0];
+ }
+
+ /**
+ * @param $sep
+ * @param $flags
+ * @return string
+ */
+ function implodeWithFlags( $sep, $flags /*, ... */ ) {
+ $args = array_slice( func_get_args(), 2 );
+
+ $first = true;
+ $s = '';
+ foreach ( $args as $root ) {
+ if ( $root instanceof PPNode_HipHop_Array ) {
+ $root = $root->value;
+ }
+ if ( !is_array( $root ) ) {
+ $root = array( $root );
+ }
+ foreach ( $root as $node ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $s .= $sep;
+ }
+ $s .= $this->expand( $node, $flags );
+ }
+ }
+ return $s;
+ }
+
+ /**
+ * Implode with no flags specified
+ * This previously called implodeWithFlags but has now been inlined to reduce stack depth
+ * @return string
+ */
+ function implode( $sep /*, ... */ ) {
+ $args = array_slice( func_get_args(), 1 );
+
+ $first = true;
+ $s = '';
+ foreach ( $args as $root ) {
+ if ( $root instanceof PPNode_HipHop_Array ) {
+ $root = $root->value;
+ }
+ if ( !is_array( $root ) ) {
+ $root = array( $root );
+ }
+ foreach ( $root as $node ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $s .= $sep;
+ }
+ $s .= $this->expand( $node );
+ }
+ }
+ return $s;
+ }
+
+ /**
+ * Makes an object that, when expand()ed, will be the same as one obtained
+ * with implode()
+ *
+ * @return PPNode_HipHop_Array
+ */
+ function virtualImplode( $sep /*, ... */ ) {
+ $args = array_slice( func_get_args(), 1 );
+ $out = array();
+ $first = true;
+
+ foreach ( $args as $root ) {
+ if ( $root instanceof PPNode_HipHop_Array ) {
+ $root = $root->value;
+ }
+ if ( !is_array( $root ) ) {
+ $root = array( $root );
+ }
+ foreach ( $root as $node ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $out[] = $sep;
+ }
+ $out[] = $node;
+ }
+ }
+ return new PPNode_HipHop_Array( $out );
+ }
+
+ /**
+ * Virtual implode with brackets
+ *
+ * @return PPNode_HipHop_Array
+ */
+ function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
+ $args = array_slice( func_get_args(), 3 );
+ $out = array( $start );
+ $first = true;
+
+ foreach ( $args as $root ) {
+ if ( $root instanceof PPNode_HipHop_Array ) {
+ $root = $root->value;
+ }
+ if ( !is_array( $root ) ) {
+ $root = array( $root );
+ }
+ foreach ( $root as $node ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $out[] = $sep;
+ }
+ $out[] = $node;
+ }
+ }
+ $out[] = $end;
+ return new PPNode_HipHop_Array( $out );
+ }
+
+ function __toString() {
+ return 'frame{}';
+ }
+
+ /**
+ * @param $level bool
+ * @return array|bool|String
+ */
+ function getPDBK( $level = false ) {
+ if ( $level === false ) {
+ return $this->title->getPrefixedDBkey();
+ } else {
+ return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
+ }
+ }
+
+ /**
+ * @return array
+ */
+ function getArguments() {
+ return array();
+ }
+
+ /**
+ * @return array
+ */
+ function getNumberedArguments() {
+ return array();
+ }
+
+ /**
+ * @return array
+ */
+ function getNamedArguments() {
+ return array();
+ }
+
+ /**
+ * Returns true if there are no arguments in this frame
+ *
+ * @return bool
+ */
+ function isEmpty() {
+ return true;
+ }
+
+ /**
+ * @param $name
+ * @return bool
+ */
+ function getArgument( $name ) {
+ return false;
+ }
+
+ /**
+ * Returns true if the infinite loop check is OK, false if a loop is detected
+ *
+ * @param $title Title
+ *
+ * @return bool
+ */
+ function loopCheck( $title ) {
+ return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
+ }
+
+ /**
+ * Return true if the frame is a template frame
+ *
+ * @return bool
+ */
+ function isTemplate() {
+ return false;
+ }
+}
+
+/**
+ * Expansion frame with template arguments
+ * @ingroup Parser
+ */
+class PPTemplateFrame_HipHop extends PPFrame_HipHop {
+ var $numberedArgs, $namedArgs, $parent;
+ var $numberedExpansionCache, $namedExpansionCache;
+
+ /**
+ * @param $preprocessor
+ * @param $parent
+ * @param $numberedArgs array
+ * @param $namedArgs array
+ * @param $title Title
+ */
+ function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
+ parent::__construct( $preprocessor );
+
+ $this->parent = $parent;
+ $this->numberedArgs = $numberedArgs;
+ $this->namedArgs = $namedArgs;
+ $this->title = $title;
+ $pdbk = $title ? $title->getPrefixedDBkey() : false;
+ $this->titleCache = $parent->titleCache;
+ $this->titleCache[] = $pdbk;
+ $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
+ if ( $pdbk !== false ) {
+ $this->loopCheckHash[$pdbk] = true;
+ }
+ $this->depth = $parent->depth + 1;
+ $this->numberedExpansionCache = $this->namedExpansionCache = array();
+ }
+
+ function __toString() {
+ $s = 'tplframe{';
+ $first = true;
+ $args = $this->numberedArgs + $this->namedArgs;
+ foreach ( $args as $name => $value ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $s .= ', ';
+ }
+ $s .= "\"$name\":\"" .
+ str_replace( '"', '\\"', $value->__toString() ) . '"';
+ }
+ $s .= '}';
+ return $s;
+ }
+ /**
+ * Returns true if there are no arguments in this frame
+ *
+ * @return bool
+ */
+ function isEmpty() {
+ return !count( $this->numberedArgs ) && !count( $this->namedArgs );
+ }
+
+ /**
+ * @return array
+ */
+ function getArguments() {
+ $arguments = array();
+ foreach ( array_merge(
+ array_keys($this->numberedArgs),
+ array_keys($this->namedArgs)) as $key ) {
+ $arguments[$key] = $this->getArgument($key);
+ }
+ return $arguments;
+ }
+
+ /**
+ * @return array
+ */
+ function getNumberedArguments() {
+ $arguments = array();
+ foreach ( array_keys($this->numberedArgs) as $key ) {
+ $arguments[$key] = $this->getArgument($key);
+ }
+ return $arguments;
+ }
+
+ /**
+ * @return array
+ */
+ function getNamedArguments() {
+ $arguments = array();
+ foreach ( array_keys($this->namedArgs) as $key ) {
+ $arguments[$key] = $this->getArgument($key);
+ }
+ return $arguments;
+ }
+
+ /**
+ * @param $index
+ * @return array|bool
+ */
+ function getNumberedArgument( $index ) {
+ if ( !isset( $this->numberedArgs[$index] ) ) {
+ return false;
+ }
+ if ( !isset( $this->numberedExpansionCache[$index] ) ) {
+ # No trimming for unnamed arguments
+ $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS );
+ }
+ return $this->numberedExpansionCache[$index];
+ }
+
+ /**
+ * @param $name
+ * @return bool
+ */
+ function getNamedArgument( $name ) {
+ if ( !isset( $this->namedArgs[$name] ) ) {
+ return false;
+ }
+ if ( !isset( $this->namedExpansionCache[$name] ) ) {
+ # Trim named arguments post-expand, for backwards compatibility
+ $this->namedExpansionCache[$name] = trim(
+ $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
+ }
+ return $this->namedExpansionCache[$name];
+ }
+
+ /**
+ * @param $name
+ * @return array|bool
+ */
+ function getArgument( $name ) {
+ $text = $this->getNumberedArgument( $name );
+ if ( $text === false ) {
+ $text = $this->getNamedArgument( $name );
+ }
+ return $text;
+ }
+
+ /**
+ * Return true if the frame is a template frame
+ *
+ * @return bool
+ */
+ function isTemplate() {
+ return true;
+ }
+}
+
+/**
+ * Expansion frame with custom arguments
+ * @ingroup Parser
+ */
+class PPCustomFrame_HipHop extends PPFrame_HipHop {
+ var $args;
+
+ function __construct( $preprocessor, $args ) {
+ parent::__construct( $preprocessor );
+ $this->args = $args;
+ }
+
+ function __toString() {
+ $s = 'cstmframe{';
+ $first = true;
+ foreach ( $this->args as $name => $value ) {
+ if ( $first ) {
+ $first = false;
+ } else {
+ $s .= ', ';
+ }
+ $s .= "\"$name\":\"" .
+ str_replace( '"', '\\"', $value->__toString() ) . '"';
+ }
+ $s .= '}';
+ return $s;
+ }
+
+ /**
+ * @return bool
+ */
+ function isEmpty() {
+ return !count( $this->args );
+ }
+
+ /**
+ * @param $index
+ * @return bool
+ */
+ function getArgument( $index ) {
+ if ( !isset( $this->args[$index] ) ) {
+ return false;
+ }
+ return $this->args[$index];
+ }
+}
+
+/**
+ * @ingroup Parser
+ */
+class PPNode_HipHop_Tree implements PPNode {
+ var $name, $firstChild, $lastChild, $nextSibling;
+
+ function __construct( $name ) {
+ $this->name = $name;
+ $this->firstChild = $this->lastChild = $this->nextSibling = false;
+ }
+
+ function __toString() {
+ $inner = '';
+ $attribs = '';
+ for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) {
+ if ( $node instanceof PPNode_HipHop_Attr ) {
+ $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
+ } else {
+ $inner .= $node->__toString();
+ }
+ }
+ if ( $inner === '' ) {
+ return "<{$this->name}$attribs/>";
+ } else {
+ return "<{$this->name}$attribs>$inner</{$this->name}>";
+ }
+ }
+
+ /**
+ * @param $name
+ * @param $text
+ * @return PPNode_HipHop_Tree
+ */
+ static function newWithText( $name, $text ) {
+ $obj = new self( $name );
+ $obj->addChild( new PPNode_HipHop_Text( $text ) );
+ return $obj;
+ }
+
+ function addChild( $node ) {
+ if ( $this->lastChild === false ) {
+ $this->firstChild = $this->lastChild = $node;
+ } else {
+ $this->lastChild->nextSibling = $node;
+ $this->lastChild = $node;
+ }
+ }
+
+ /**
+ * @return PPNode_HipHop_Array
+ */
+ function getChildren() {
+ $children = array();
+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
+ $children[] = $child;
+ }
+ return new PPNode_HipHop_Array( $children );
+ }
+
+ function getFirstChild() {
+ return $this->firstChild;
+ }
+
+ function getNextSibling() {
+ return $this->nextSibling;
+ }
+
+ function getChildrenOfType( $name ) {
+ $children = array();
+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
+ if ( isset( $child->name ) && $child->name === $name ) {
+ $children[] = $name;
+ }
+ }
+ return $children;
+ }
+
+ /**
+ * @return bool
+ */
+ function getLength() {
+ return false;
+ }
+
+ /**
+ * @param $i
+ * @return bool
+ */
+ function item( $i ) {
+ return false;
+ }
+
+ /**
+ * @return string
+ */
+ function getName() {
+ return $this->name;
+ }
+
+ /**
+ * Split a <part> node into an associative array containing:
+ * name PPNode name
+ * index String index
+ * value PPNode value
+ *
+ * @return array
+ */
+ function splitArg() {
+ $bits = array();
+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
+ if ( !isset( $child->name ) ) {
+ continue;
+ }
+ if ( $child->name === 'name' ) {
+ $bits['name'] = $child;
+ if ( $child->firstChild instanceof PPNode_HipHop_Attr
+ && $child->firstChild->name === 'index' )
+ {
+ $bits['index'] = $child->firstChild->value;
+ }
+ } elseif ( $child->name === 'value' ) {
+ $bits['value'] = $child;
+ }
+ }
+
+ if ( !isset( $bits['name'] ) ) {
+ throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
+ }
+ if ( !isset( $bits['index'] ) ) {
+ $bits['index'] = '';
+ }
+ return $bits;
+ }
+
+ /**
+ * Split an <ext> node into an associative array containing name, attr, inner and close
+ * All values in the resulting array are PPNodes. Inner and close are optional.
+ *
+ * @return array
+ */
+ function splitExt() {
+ $bits = array();
+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
+ if ( !isset( $child->name ) ) {
+ continue;
+ }
+ if ( $child->name === 'name' ) {
+ $bits['name'] = $child;
+ } elseif ( $child->name === 'attr' ) {
+ $bits['attr'] = $child;
+ } elseif ( $child->name === 'inner' ) {
+ $bits['inner'] = $child;
+ } elseif ( $child->name === 'close' ) {
+ $bits['close'] = $child;
+ }
+ }
+ if ( !isset( $bits['name'] ) ) {
+ throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
+ }
+ return $bits;
+ }
+
+ /**
+ * Split an <h> node
+ *
+ * @return array
+ */
+ function splitHeading() {
+ if ( $this->name !== 'h' ) {
+ throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
+ }
+ $bits = array();
+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
+ if ( !isset( $child->name ) ) {
+ continue;
+ }
+ if ( $child->name === 'i' ) {
+ $bits['i'] = $child->value;
+ } elseif ( $child->name === 'level' ) {
+ $bits['level'] = $child->value;
+ }
+ }
+ if ( !isset( $bits['i'] ) ) {
+ throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
+ }
+ return $bits;
+ }
+
+ /**
+ * Split a <template> or <tplarg> node
+ *
+ * @return array
+ */
+ function splitTemplate() {
+ $parts = array();
+ $bits = array( 'lineStart' => '' );
+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
+ if ( !isset( $child->name ) ) {
+ continue;
+ }
+ if ( $child->name === 'title' ) {
+ $bits['title'] = $child;
+ }
+ if ( $child->name === 'part' ) {
+ $parts[] = $child;
+ }
+ if ( $child->name === 'lineStart' ) {
+ $bits['lineStart'] = '1';
+ }
+ }
+ if ( !isset( $bits['title'] ) ) {
+ throw new MWException( 'Invalid node passed to ' . __METHOD__ );
+ }
+ $bits['parts'] = new PPNode_HipHop_Array( $parts );
+ return $bits;
+ }
+}
+
+/**
+ * @ingroup Parser
+ */
+class PPNode_HipHop_Text implements PPNode {
+ var $value, $nextSibling;
+
+ function __construct( $value ) {
+ if ( is_object( $value ) ) {
+ throw new MWException( __CLASS__ . ' given object instead of string' );
+ }
+ $this->value = $value;
+ }
+
+ function __toString() {
+ return htmlspecialchars( $this->value );
+ }
+
+ function getNextSibling() {
+ return $this->nextSibling;
+ }
+
+ function getChildren() { return false; }
+ function getFirstChild() { return false; }
+ function getChildrenOfType( $name ) { return false; }
+ function getLength() { return false; }
+ function item( $i ) { return false; }
+ function getName() { return '#text'; }
+ function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
+ function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
+ function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
+}
+
+/**
+ * @ingroup Parser
+ */
+class PPNode_HipHop_Array implements PPNode {
+ var $value, $nextSibling;
+
+ function __construct( $value ) {
+ $this->value = $value;
+ }
+
+ function __toString() {
+ return var_export( $this, true );
+ }
+
+ function getLength() {
+ return count( $this->value );
+ }
+
+ function item( $i ) {
+ return $this->value[$i];
+ }
+
+ function getName() { return '#nodelist'; }
+
+ function getNextSibling() {
+ return $this->nextSibling;
+ }
+
+ function getChildren() { return false; }
+ function getFirstChild() { return false; }
+ function getChildrenOfType( $name ) { return false; }
+ function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
+ function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
+ function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
+}
+
+/**
+ * @ingroup Parser
+ */
+class PPNode_HipHop_Attr implements PPNode {
+ var $name, $value, $nextSibling;
+
+ function __construct( $name, $value ) {
+ $this->name = $name;
+ $this->value = $value;
+ }
+
+ function __toString() {
+ return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
+ }
+
+ function getName() {
+ return $this->name;
+ }
+
+ function getNextSibling() {
+ return $this->nextSibling;
+ }
+
+ function getChildren() { return false; }
+ function getFirstChild() { return false; }
+ function getChildrenOfType( $name ) { return false; }
+ function getLength() { return false; }
+ function item( $i ) { return false; }
+ function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
+ function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
+ function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
+}
diff --git a/includes/parser/StripState.php b/includes/parser/StripState.php
new file mode 100644
index 00000000..c7bd1e77
--- /dev/null
+++ b/includes/parser/StripState.php
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * @todo document, briefly.
+ * @ingroup Parser
+ */
+class StripState {
+ protected $prefix;
+ protected $data;
+ protected $regex;
+
+ protected $tempType, $tempMergePrefix;
+
+ function __construct( $prefix ) {
+ $this->prefix = $prefix;
+ $this->data = array(
+ 'nowiki' => array(),
+ 'general' => array()
+ );
+ $this->regex = "/{$this->prefix}([^\x7f]+)" . Parser::MARKER_SUFFIX . '/';
+ }
+
+ /**
+ * Add a nowiki strip item
+ * @param $marker
+ * @param $value
+ */
+ function addNoWiki( $marker, $value ) {
+ $this->addItem( 'nowiki', $marker, $value );
+ }
+
+ /**
+ * @param $marker
+ * @param $value
+ */
+ function addGeneral( $marker, $value ) {
+ $this->addItem( 'general', $marker, $value );
+ }
+
+ /**
+ * @throws MWException
+ * @param $type
+ * @param $marker
+ * @param $value
+ */
+ protected function addItem( $type, $marker, $value ) {
+ if ( !preg_match( $this->regex, $marker, $m ) ) {
+ throw new MWException( "Invalid marker: $marker" );
+ }
+
+ $this->data[$type][$m[1]] = $value;
+ }
+
+ /**
+ * @param $text
+ * @return mixed
+ */
+ function unstripGeneral( $text ) {
+ return $this->unstripType( 'general', $text );
+ }
+
+ /**
+ * @param $text
+ * @return mixed
+ */
+ function unstripNoWiki( $text ) {
+ return $this->unstripType( 'nowiki', $text );
+ }
+
+ /**
+ * @param $text
+ * @return mixed
+ */
+ function unstripBoth( $text ) {
+ $text = $this->unstripType( 'general', $text );
+ $text = $this->unstripType( 'nowiki', $text );
+ return $text;
+ }
+
+ /**
+ * @param $type
+ * @param $text
+ * @return mixed
+ */
+ protected function unstripType( $type, $text ) {
+ // Shortcut
+ if ( !count( $this->data[$type] ) ) {
+ return $text;
+ }
+
+ wfProfileIn( __METHOD__ );
+ $this->tempType = $type;
+ $out = preg_replace_callback( $this->regex, array( $this, 'unstripCallback' ), $text );
+ $this->tempType = null;
+ wfProfileOut( __METHOD__ );
+ return $out;
+ }
+
+ /**
+ * @param $m array
+ * @return array
+ */
+ protected function unstripCallback( $m ) {
+ if ( isset( $this->data[$this->tempType][$m[1]] ) ) {
+ return $this->data[$this->tempType][$m[1]];
+ } else {
+ return $m[0];
+ }
+ }
+
+ /**
+ * Get a StripState object which is sufficient to unstrip the given text.
+ * It will contain the minimum subset of strip items necessary.
+ *
+ * @param $text string
+ *
+ * @return StripState
+ */
+ function getSubState( $text ) {
+ $subState = new StripState( $this->prefix );
+ $pos = 0;
+ while ( true ) {
+ $startPos = strpos( $text, $this->prefix, $pos );
+ $endPos = strpos( $text, Parser::MARKER_SUFFIX, $pos );
+ if ( $startPos === false || $endPos === false ) {
+ break;
+ }
+
+ $endPos += strlen( Parser::MARKER_SUFFIX );
+ $marker = substr( $text, $startPos, $endPos - $startPos );
+ if ( !preg_match( $this->regex, $marker, $m ) ) {
+ continue;
+ }
+
+ $key = $m[1];
+ if ( isset( $this->data['nowiki'][$key] ) ) {
+ $subState->data['nowiki'][$key] = $this->data['nowiki'][$key];
+ } elseif ( isset( $this->data['general'][$key] ) ) {
+ $subState->data['general'][$key] = $this->data['general'][$key];
+ }
+ $pos = $endPos;
+ }
+ return $subState;
+ }
+
+ /**
+ * Merge another StripState object into this one. The strip marker keys
+ * will not be preserved. The strings in the $texts array will have their
+ * strip markers rewritten, the resulting array of strings will be returned.
+ *
+ * @param $otherState StripState
+ * @param $texts Array
+ * @return Array
+ */
+ function merge( $otherState, $texts ) {
+ $mergePrefix = Parser::getRandomString();
+
+ foreach ( $otherState->data as $type => $items ) {
+ foreach ( $items as $key => $value ) {
+ $this->data[$type]["$mergePrefix-$key"] = $value;
+ }
+ }
+
+ $this->tempMergePrefix = $mergePrefix;
+ $texts = preg_replace_callback( $otherState->regex, array( $this, 'mergeCallback' ), $texts );
+ $this->tempMergePrefix = null;
+ return $texts;
+ }
+
+ protected function mergeCallback( $m ) {
+ $key = $m[1];
+ return "{$this->prefix}{$this->tempMergePrefix}-$key" . Parser::MARKER_SUFFIX;
+ }
+}
+
diff --git a/includes/parser/Tidy.php b/includes/parser/Tidy.php
index 38f22fd8..3a6d3e9c 100644
--- a/includes/parser/Tidy.php
+++ b/includes/parser/Tidy.php
@@ -6,6 +6,74 @@
*/
/**
+ * Class used to hide mw:editsection tokens from Tidy so that it doesn't break them
+ * or break on them. This is a bit of a hack for now, but hopefully in the future
+ * we may create a real postprocessor or something that will replace this.
+ * It's called wrapper because for now it basically takes over MWTidy::tidy's task
+ * of wrapping the text in a xhtml block
+ *
+ * This re-uses some of the parser's UNIQ tricks, though some of it is private so it's
+ * duplicated. Perhaps we should create an abstract marker hiding class.
+ */
+class MWTidyWrapper {
+
+ /**
+ * @var ReplacementArray
+ */
+ protected $mTokens;
+
+ protected $mUniqPrefix;
+
+ protected $mMarkerIndex;
+
+ public function __construct() {
+ $this->mTokens = null;
+ $this->mUniqPrefix = null;
+ }
+
+ /**
+ * @param $text string
+ * @return string
+ */
+ public function getWrapped( $text ) {
+ $this->mTokens = new ReplacementArray;
+ $this->mUniqPrefix = "\x7fUNIQ" .
+ dechex( mt_rand( 0, 0x7fffffff ) ) . dechex( mt_rand( 0, 0x7fffffff ) );
+ $this->mMarkerIndex = 0;
+
+ $wrappedtext = preg_replace_callback( ParserOutput::EDITSECTION_REGEX,
+ array( &$this, 'replaceEditSectionLinksCallback' ), $text );
+
+ $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'.
+ ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>'.
+ '<head><title>test</title></head><body>'.$wrappedtext.'</body></html>';
+
+ return $wrappedtext;
+ }
+
+ /**
+ * @param $m array
+ *
+ * @return string
+ */
+ function replaceEditSectionLinksCallback( $m ) {
+ $marker = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}" . Parser::MARKER_SUFFIX;
+ $this->mMarkerIndex++;
+ $this->mTokens->setPair( $marker, $m[0] );
+ return $marker;
+ }
+
+ /**
+ * @param $text string
+ * @return string
+ */
+ public function postprocess( $text ) {
+ return $this->mTokens->replace( $text );
+ }
+
+}
+
+/**
* Class to interact with HTML tidy
*
* Either the external tidy program or the in-process tidy extension
@@ -15,7 +83,6 @@
* @ingroup Parser
*/
class MWTidy {
-
/**
* Interface with html tidy, used if $wgUseTidy = true.
* If tidy isn't able to correct the markup, the original will be
@@ -27,20 +94,26 @@ class MWTidy {
public static function tidy( $text ) {
global $wgTidyInternal;
- $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'.
-' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>'.
-'<head><title>test</title></head><body>'.$text.'</body></html>';
+ $wrapper = new MWTidyWrapper;
+ $wrappedtext = $wrapper->getWrapped( $text );
- if( $wgTidyInternal ) {
- $correctedtext = self::execInternalTidy( $wrappedtext );
+ $retVal = null;
+ if ( $wgTidyInternal ) {
+ $correctedtext = self::execInternalTidy( $wrappedtext, false, $retVal );
} else {
- $correctedtext = self::execExternalTidy( $wrappedtext );
+ $correctedtext = self::execExternalTidy( $wrappedtext, false, $retVal );
}
- if( is_null( $correctedtext ) ) {
+
+ if ( $retVal < 0 ) {
+ wfDebug( "Possible tidy configuration error!\n" );
+ return $text . "\n<!-- Tidy was unable to run -->\n";
+ } elseif ( is_null( $correctedtext ) ) {
wfDebug( "Tidy error detected!\n" );
return $text . "\n<!-- Tidy found serious XHTML errors -->\n";
}
+ $correctedtext = $wrapper->postprocess( $correctedtext ); // restore any hidden tokens
+
return $correctedtext;
}
@@ -60,6 +133,7 @@ class MWTidy {
} else {
$errorStr = self::execExternalTidy( $text, true, $retval );
}
+
return ( $retval < 0 && $errorStr == '' ) || $retval == 0;
}
@@ -68,7 +142,7 @@ class MWTidy {
* Also called in OutputHandler.php for full page validation
*
* @param $text String: HTML to check
- * @param $stderr Boolean: Whether to read from STDERR rather than STDOUT
+ * @param $stderr Boolean: Whether to read result from STDERR rather than STDOUT
* @param &$retval Exit code (-1 on internal error)
* @return mixed String or null
*/
@@ -79,7 +153,7 @@ class MWTidy {
$cleansource = '';
$opts = ' -utf8';
- if( $stderr ) {
+ if ( $stderr ) {
$descriptorspec = array(
0 => array( 'pipe', 'r' ),
1 => array( 'file', wfGetNull(), 'a' ),
@@ -96,73 +170,84 @@ class MWTidy {
$readpipe = $stderr ? 2 : 1;
$pipes = array();
- if( function_exists( 'proc_open' ) ) {
- $process = proc_open( "$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes );
- if ( is_resource( $process ) ) {
- // Theoretically, this style of communication could cause a deadlock
- // here. If the stdout buffer fills up, then writes to stdin could
- // block. This doesn't appear to happen with tidy, because tidy only
- // writes to stdout after it's finished reading from stdin. Search
- // for tidyParseStdin and tidySaveStdout in console/tidy.c
- fwrite( $pipes[0], $text );
- fclose( $pipes[0] );
- while ( !feof( $pipes[$readpipe] ) ) {
- $cleansource .= fgets( $pipes[$readpipe], 1024 );
- }
- fclose( $pipes[$readpipe] );
- $retval = proc_close( $process );
- } else {
- $retval = -1;
+ $process = proc_open(
+ "$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes );
+
+ if ( is_resource( $process ) ) {
+ // Theoretically, this style of communication could cause a deadlock
+ // here. If the stdout buffer fills up, then writes to stdin could
+ // block. This doesn't appear to happen with tidy, because tidy only
+ // writes to stdout after it's finished reading from stdin. Search
+ // for tidyParseStdin and tidySaveStdout in console/tidy.c
+ fwrite( $pipes[0], $text );
+ fclose( $pipes[0] );
+ while ( !feof( $pipes[$readpipe] ) ) {
+ $cleansource .= fgets( $pipes[$readpipe], 1024 );
}
+ fclose( $pipes[$readpipe] );
+ $retval = proc_close( $process );
} else {
- $retval = -1;
+ wfWarn( "Unable to start external tidy process" );
+ $retval = -1;
}
- if( !$stderr && $cleansource == '' && $text != '' ) {
+ 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.
$cleansource = null;
}
+
wfProfileOut( __METHOD__ );
return $cleansource;
}
/**
- * Use the HTML tidy PECL extension to use the tidy library in-process,
+ * Use the HTML tidy extension to use the tidy library in-process,
* saving the overhead of spawning a new process.
*
- * 'pear install tidy' should be able to compile the extension module.
+ * @param $text String: HTML to check
+ * @param $stderr Boolean: Whether to read result from error status instead of output
+ * @param &$retval Exit code (-1 on internal error)
+ * @return mixed String or null
*/
private static function execInternalTidy( $text, $stderr = false, &$retval = null ) {
global $wgTidyConf, $wgDebugTidy;
wfProfileIn( __METHOD__ );
+ if ( !MWInit::classExists( 'tidy' ) ) {
+ wfWarn( "Unable to load internal tidy class." );
+ $retval = -1;
+
+ wfProfileOut( __METHOD__ );
+ return null;
+ }
+
$tidy = new tidy;
$tidy->parseString( $text, $wgTidyConf, 'utf8' );
- if( $stderr ) {
+ if ( $stderr ) {
$retval = $tidy->getStatus();
+
wfProfileOut( __METHOD__ );
return $tidy->errorBuffer;
} else {
$tidy->cleanRepair();
$retval = $tidy->getStatus();
- if( $retval == 2 ) {
+ if ( $retval == 2 ) {
// 2 is magic number for fatal error
// http://www.php.net/manual/en/function.tidy-get-status.php
$cleansource = null;
} else {
$cleansource = tidy_get_output( $tidy );
- }
- if ( $wgDebugTidy && $retval > 0 ) {
- $cleansource .= "<!--\nTidy reports:\n" .
- str_replace( '-->', '--&gt;', $tidy->errorBuffer ) .
- "\n-->";
+ if ( $wgDebugTidy && $retval > 0 ) {
+ $cleansource .= "<!--\nTidy reports:\n" .
+ str_replace( '-->', '--&gt;', $tidy->errorBuffer ) .
+ "\n-->";
+ }
}
wfProfileOut( __METHOD__ );
return $cleansource;
}
}
-
}
diff --git a/includes/Profiler.php b/includes/profiler/Profiler.php
index 6deb742e..93b03f59 100644
--- a/includes/Profiler.php
+++ b/includes/profiler/Profiler.php
@@ -7,16 +7,15 @@
* This file is only included if profiling is enabled
*/
-/** backward compatibility */
-$wgProfiling = true;
-
/**
* Begin profiling of a function
* @param $functionname String: name of the function we will profile
*/
function wfProfileIn( $functionname ) {
global $wgProfiler;
- $wgProfiler->profileIn( $functionname );
+ if ( $wgProfiler instanceof Profiler || isset( $wgProfiler['class'] ) ) {
+ Profiler::instance()->profileIn( $functionname );
+ }
}
/**
@@ -25,32 +24,8 @@ function wfProfileIn( $functionname ) {
*/
function wfProfileOut( $functionname = 'missing' ) {
global $wgProfiler;
- $wgProfiler->profileOut( $functionname );
-}
-
-/**
- * Returns a profiling output to be stored in debug file
- *
- * @param $start Float
- * @param $elapsed Float: time elapsed since the beginning of the request
- */
-function wfGetProfilingOutput( $start, $elapsed ) {
- global $wgProfiler;
- return $wgProfiler->getOutput( $start, $elapsed );
-}
-
-/**
- * Close opened profiling sections
- */
-function wfProfileClose() {
- global $wgProfiler;
- $wgProfiler->close();
-}
-
-if (!function_exists('memory_get_usage')) {
- # Old PHP or --enable-memory-limit not compiled in
- function memory_get_usage() {
- return 0;
+ if ( $wgProfiler instanceof Profiler || isset( $wgProfiler['class'] ) ) {
+ Profiler::instance()->profileOut( $functionname );
}
}
@@ -62,8 +37,12 @@ class Profiler {
var $mStack = array (), $mWorkStack = array (), $mCollated = array ();
var $mCalls = array (), $mTotals = array ();
var $mTemplated = false;
+ var $mTimeMetric = 'wall';
+ private $mCollateDone = false;
+ protected $mProfileID = false;
+ private static $__instance = null;
- function __construct() {
+ function __construct( $params ) {
// Push an entry for the pre-profile setup time onto the stack
global $wgRequestTime;
if ( !empty( $wgRequestTime ) ) {
@@ -72,6 +51,57 @@ class Profiler {
} else {
$this->profileIn( '-total' );
}
+ if ( isset( $params['timeMetric'] ) ) {
+ $this->mTimeMetric = $params['timeMetric'];
+ }
+ }
+
+ /**
+ * Singleton
+ * @return Profiler
+ */
+ public static function instance() {
+ if( is_null( self::$__instance ) ) {
+ global $wgProfiler;
+ if( is_array( $wgProfiler ) ) {
+ $class = isset( $wgProfiler['class'] ) ? $wgProfiler['class'] : 'ProfilerStub';
+ self::$__instance = new $class( $wgProfiler );
+ } elseif( $wgProfiler instanceof Profiler ) {
+ self::$__instance = $wgProfiler; // back-compat
+ } else {
+ self::$__instance = new ProfilerStub( $wgProfiler );
+ }
+ }
+ return self::$__instance;
+ }
+
+ /**
+ * Set the profiler to a specific profiler instance. Mostly for dumpHTML
+ * @param $p Profiler object
+ */
+ public static function setInstance( Profiler $p ) {
+ self::$__instance = $p;
+ }
+
+ /**
+ * Return whether this a stub profiler
+ *
+ * @return Boolean
+ */
+ public function isStub() {
+ return false;
+ }
+
+ public function setProfileID( $id ) {
+ $this->mProfileID = $id;
+ }
+
+ public function getProfileID() {
+ if ( $this->mProfileID === false ) {
+ return wfWikiID();
+ } else {
+ return $this->mProfileID;
+ }
}
/**
@@ -79,9 +109,8 @@ class Profiler {
*
* @param $functionname String
*/
- function profileIn( $functionname ) {
- global $wgDebugFunctionEntry, $wgProfiling;
- if( !$wgProfiling ) return;
+ public function profileIn( $functionname ) {
+ global $wgDebugFunctionEntry;
if( $wgDebugFunctionEntry ){
$this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
}
@@ -94,9 +123,8 @@ class Profiler {
*
* @param $functionname String
*/
- function profileOut($functionname) {
- global $wgDebugFunctionEntry, $wgProfiling;
- if( !$wgProfiling ) return;
+ public function profileOut( $functionname ) {
+ global $wgDebugFunctionEntry;
$memory = memory_get_usage();
$time = $this->getTime();
@@ -128,15 +156,9 @@ class Profiler {
}
/**
- * called by wfProfileClose()
+ * Close opened profiling sections
*/
- function close() {
- global $wgProfiling;
-
- # Avoid infinite loop
- if( !$wgProfiling )
- return;
-
+ public function close() {
while( count( $this->mWorkStack ) ){
$this->profileOut( 'close' );
}
@@ -152,23 +174,19 @@ class Profiler {
}
/**
- * Called by wfGetProfilingOutput()
+ * Returns a profiling output to be stored in debug file
+ *
+ * @return String
*/
- function getOutput() {
+ public function getOutput() {
global $wgDebugFunctionEntry, $wgProfileCallTree;
$wgDebugFunctionEntry = false;
if( !count( $this->mStack ) && !count( $this->mCollated ) ){
return "No profiling output\n";
}
- $this->close();
if( $wgProfileCallTree ) {
- global $wgProfileToDatabase;
- # XXX: We must call $this->getFunctionReport() to log to the DB
- if( $wgProfileToDatabase ) {
- $this->getFunctionReport();
- }
return $this->getCallTree();
} else {
return $this->getFunctionReport();
@@ -235,8 +253,11 @@ class Profiler {
}
function getTime() {
- return microtime(true);
- #return $this->getUserTime();
+ if ( $this->mTimeMetric === 'user' ) {
+ return $this->getUserTime();
+ } else {
+ return microtime(true);
+ }
}
function getUserTime() {
@@ -244,31 +265,21 @@ class Profiler {
return $ru['ru_utime.tv_sec'].' '.$ru['ru_utime.tv_usec'] / 1e6;
}
- /**
- * Returns a list of profiled functions.
- * Also log it into the database if $wgProfileToDatabase is set to true.
- */
- function getFunctionReport() {
- global $wgProfileToDatabase;
+ protected function collateData() {
+ if ( $this->mCollateDone ) {
+ return;
+ }
+ $this->mCollateDone = true;
- $width = 140;
- $nameWidth = $width - 65;
- $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n";
- $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
- $prof = "\nProfiling data\n";
- $prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' );
- $this->mCollated = array ();
- $this->mCalls = array ();
- $this->mMemory = array ();
+ $this->close();
+
+ $this->mCollated = array();
+ $this->mCalls = array();
+ $this->mMemory = array();
# Estimate profiling overhead
$profileCount = count($this->mStack);
- wfProfileIn( '-overhead-total' );
- for( $i = 0; $i < $profileCount; $i ++ ){
- wfProfileIn( '-overhead-internal' );
- wfProfileOut( '-overhead-internal' );
- }
- wfProfileOut( '-overhead-total' );
+ self::calculateOverhead( $profileCount );
# First, subtract the overhead!
$overheadTotal = $overheadMemory = $overheadInternal = array();
@@ -327,20 +338,31 @@ class Profiler {
$this->mOverhead[$fname] += $subcalls;
}
- $total = @$this->mCollated['-total'];
$this->mCalls['-overhead-total'] = $profileCount;
-
- # Output
arsort( $this->mCollated, SORT_NUMERIC );
+ }
+
+ /**
+ * Returns a list of profiled functions.
+ * Also log it into the database if $wgProfileToDatabase is set to true.
+ */
+ function getFunctionReport() {
+ $this->collateData();
+
+ $width = 140;
+ $nameWidth = $width - 65;
+ $format = "%-{$nameWidth}s %6d %13.3f %13.3f %13.3f%% %9d (%13.3f -%13.3f) [%d]\n";
+ $titleFormat = "%-{$nameWidth}s %6s %13s %13s %13s %9s\n";
+ $prof = "\nProfiling data\n";
+ $prof .= sprintf( $titleFormat, 'Name', 'Calls', 'Total', 'Each', '%', 'Mem' );
+
+ $total = isset( $this->mCollated['-total'] ) ? $this->mCollated['-total'] : 0;
+
foreach( $this->mCollated as $fname => $elapsed ){
$calls = $this->mCalls[$fname];
$percent = $total ? 100. * $elapsed / $total : 0;
$memory = $this->mMemory[$fname];
$prof .= sprintf($format, substr($fname, 0, $nameWidth), $calls, (float) ($elapsed * 1000), (float) ($elapsed * 1000) / $calls, $percent, $memory, ($this->mMin[$fname] * 1000.0), ($this->mMax[$fname] * 1000.0), $this->mOverhead[$fname]);
- # Log to the DB
- if( $wgProfileToDatabase ) {
- self::logToDB($fname, (float) ($elapsed * 1000), $calls, (float) ($memory) );
- }
}
$prof .= "\nTotal: $total\n\n";
@@ -348,6 +370,18 @@ class Profiler {
}
/**
+ * Dummy calls to wfProfileIn/wfProfileOut to calculate its overhead
+ */
+ protected static function calculateOverhead( $profileCount ) {
+ wfProfileIn( '-overhead-total' );
+ for( $i = 0; $i < $profileCount; $i++ ){
+ wfProfileIn( '-overhead-internal' );
+ wfProfileOut( '-overhead-internal' );
+ }
+ wfProfileOut( '-overhead-total' );
+ }
+
+ /**
* Counts the number of profiled function calls sitting under
* the given point in the call graph. Not the most efficient algo.
*
@@ -366,60 +400,66 @@ class Profiler {
}
/**
- * Log a function into the database.
- *
- * @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
+ * Log the whole profiling data into the database.
*/
- static function logToDB( $name, $timeSum, $eventCount, $memorySum ){
- # Do not log anything if database is readonly (bug 5375)
- if( wfReadOnly() ) { return; }
+ public function logData(){
+ global $wgProfilePerHost, $wgProfileToDatabase;
- global $wgProfilePerHost;
+ # Do not log anything if database is readonly (bug 5375)
+ if( wfReadOnly() || !$wgProfileToDatabase ) {
+ return;
+ }
$dbw = wfGetDB( DB_MASTER );
- if( !is_object( $dbw ) )
- return false;
- $errorState = $dbw->ignoreErrors( true );
+ if( !is_object( $dbw ) ) {
+ return;
+ }
- $name = substr($name, 0, 255);
+ $errorState = $dbw->ignoreErrors( true );
if( $wgProfilePerHost ){
$pfhost = wfHostname();
} else {
$pfhost = '';
}
-
- // Kludge
- $timeSum = ($timeSum >= 0) ? $timeSum : 0;
- $memorySum = ($memorySum >= 0) ? $memorySum : 0;
-
- $dbw->update( 'profiling',
- array(
- "pf_count=pf_count+{$eventCount}",
- "pf_time=pf_time+{$timeSum}",
- "pf_memory=pf_memory+{$memorySum}",
- ),
- array(
- 'pf_name' => $name,
- 'pf_server' => $pfhost,
- ),
- __METHOD__ );
-
-
- $rc = $dbw->affectedRows();
- if ($rc == 0) {
- $dbw->insert('profiling', array ('pf_name' => $name, 'pf_count' => $eventCount,
- 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost ),
- __METHOD__, array ('IGNORE'));
+
+ $this->collateData();
+
+ foreach( $this->mCollated as $name => $elapsed ){
+ $eventCount = $this->mCalls[$name];
+ $timeSum = (float) ($elapsed * 1000);
+ $memorySum = (float)$this->mMemory[$name];
+ $name = substr($name, 0, 255);
+
+ // Kludge
+ $timeSum = ($timeSum >= 0) ? $timeSum : 0;
+ $memorySum = ($memorySum >= 0) ? $memorySum : 0;
+
+ $dbw->update( 'profiling',
+ array(
+ "pf_count=pf_count+{$eventCount}",
+ "pf_time=pf_time+{$timeSum}",
+ "pf_memory=pf_memory+{$memorySum}",
+ ),
+ array(
+ 'pf_name' => $name,
+ 'pf_server' => $pfhost,
+ ),
+ __METHOD__ );
+
+ $rc = $dbw->affectedRows();
+ if ( $rc == 0 ) {
+ $dbw->insert('profiling', array ('pf_name' => $name, 'pf_count' => $eventCount,
+ 'pf_time' => $timeSum, 'pf_memory' => $memorySum, 'pf_server' => $pfhost ),
+ __METHOD__, array ('IGNORE'));
+ }
+ // When we upgrade to mysql 4.1, the insert+update
+ // can be merged into just a insert with this construct added:
+ // "ON DUPLICATE KEY UPDATE ".
+ // "pf_count=pf_count + VALUES(pf_count), ".
+ // "pf_time=pf_time + VALUES(pf_time)";
}
- // When we upgrade to mysql 4.1, the insert+update
- // can be merged into just a insert with this construct added:
- // "ON DUPLICATE KEY UPDATE ".
- // "pf_count=pf_count + VALUES(pf_count), ".
- // "pf_time=pf_time + VALUES(pf_time)";
+
$dbw->ignoreErrors( $errorState );
}
@@ -432,31 +472,12 @@ class Profiler {
}
/**
- * Get function caller
- *
- * @param $level Integer
- */
- static function getCaller( $level ) {
- $backtrace = wfDebugBacktrace();
- if ( isset( $backtrace[$level] ) ) {
- if ( isset( $backtrace[$level]['class'] ) ) {
- $caller = $backtrace[$level]['class'] . '::' . $backtrace[$level]['function'];
- } else {
- $caller = $backtrace[$level]['function'];
- }
- } else {
- $caller = 'unknown';
- }
- return $caller;
- }
-
- /**
* Add an entry in the debug log file
*
* @param $s String to output
*/
function debug( $s ) {
- if( function_exists( 'wfDebug' ) ) {
+ if( defined( 'MW_COMPILED' ) || function_exists( 'wfDebug' ) ) {
wfDebug( $s );
}
}
diff --git a/includes/ProfilerSimple.php b/includes/profiler/ProfilerSimple.php
index 8aab1ecc..bbdbec8e 100644
--- a/includes/ProfilerSimple.php
+++ b/includes/profiler/ProfilerSimple.php
@@ -4,10 +4,6 @@
* @ingroup Profiler
*/
-if ( !class_exists( 'Profiler' ) ) {
- require_once(dirname(__FILE__).'/Profiler.php');
-}
-
/**
* Simple profiler base class.
* @todo document methods (?)
@@ -15,11 +11,21 @@ if ( !class_exists( 'Profiler' ) ) {
*/
class ProfilerSimple extends Profiler {
var $mMinimumTime = 0;
- var $mProfileID = false;
- function __construct() {
+ var $zeroEntry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0);
+ var $errorEntry;
+
+ function __construct( $params ) {
global $wgRequestTime, $wgRUstart;
+ parent::__construct( $params );
+
+ $this->errorEntry = $this->zeroEntry;
+ $this->errorEntry['count'] = 1;
+
if (!empty($wgRequestTime) && !empty($wgRUstart)) {
+ # Remove the -total entry from parent::__construct
+ $this->mWorkStack = array();
+
$this->mWorkStack[] = array( '-total', 0, $wgRequestTime,$this->getCpuTime($wgRUstart));
$elapsedcpu = $this->getCpuTime() - $this->getCpuTime($wgRUstart);
@@ -27,7 +33,7 @@ class ProfilerSimple extends Profiler {
$entry =& $this->mCollated["-setup"];
if (!is_array($entry)) {
- $entry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0);
+ $entry = $this->zeroEntry;
$this->mCollated["-setup"] =& $entry;
}
$entry['cpu'] += $elapsedcpu;
@@ -42,18 +48,6 @@ class ProfilerSimple extends Profiler {
$this->mMinimumTime = $min;
}
- function setProfileID( $id ) {
- $this->mProfileID = $id;
- }
-
- function getProfileID() {
- if ( $this->mProfileID === false ) {
- return wfWikiID();
- } else {
- return $this->mProfileID;
- }
- }
-
function profileIn($functionname) {
global $wgDebugFunctionEntry;
if ($wgDebugFunctionEntry) {
@@ -78,20 +72,18 @@ class ProfilerSimple extends Profiler {
$message = "Profile section ended by close(): {$ofname}";
$functionname = $ofname;
$this->debug( "$message\n" );
- $this->mCollated[$message] = array(
- 'real' => 0.0, 'count' => 1);
+ $this->mCollated[$message] = $this->errorEntry;
}
elseif ($ofname != $functionname) {
$message = "Profiling error: in({$ofname}), out($functionname)";
$this->debug( "$message\n" );
- $this->mCollated[$message] = array(
- 'real' => 0.0, 'count' => 1);
+ $this->mCollated[$message] = $this->errorEntry;
}
$entry =& $this->mCollated[$functionname];
$elapsedcpu = $this->getCpuTime() - $octime;
$elapsedreal = microtime(true) - $ortime;
if (!is_array($entry)) {
- $entry = array('cpu'=> 0.0, 'cpu_sq' => 0.0, 'real' => 0.0, 'real_sq' => 0.0, 'count' => 0);
+ $entry = $this->zeroEntry;
$this->mCollated[$functionname] =& $entry;
}
$entry['cpu'] += $elapsedcpu;
@@ -103,8 +95,13 @@ class ProfilerSimple extends Profiler {
}
}
- function getFunctionReport() {
+ public function getFunctionReport() {
/* Implement in output subclasses */
+ return '';
+ }
+
+ public function logData() {
+ /* Implement in subclasses */
}
function getCpuTime($ru=null) {
diff --git a/includes/profiler/ProfilerSimpleText.php b/includes/profiler/ProfilerSimpleText.php
new file mode 100644
index 00000000..ef9049fa
--- /dev/null
+++ b/includes/profiler/ProfilerSimpleText.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @file
+ * @ingroup Profiler
+ */
+
+/**
+ * The least sophisticated profiler output class possible, view your source! :)
+ *
+ * Put the following 2 lines in StartProfiler.php:
+ *
+ * $wgProfiler['class'] = 'ProfilerSimpleText';
+ * $wgProfiler['visible'] = true;
+ *
+ * @ingroup Profiler
+ */
+class ProfilerSimpleText extends ProfilerSimple {
+ public $visible = false; /* Show as <PRE> or <!-- ? */
+ static private $out;
+
+ public function __construct( $profileConfig ) {
+ if ( isset( $profileConfig['visible'] ) && $profileConfig['visible'] ) {
+ $this->visible = true;
+ }
+ parent::__construct( $profileConfig );
+ }
+
+ public function logData() {
+ if ( $this->mTemplated ) {
+ $this->close();
+ $totalReal = isset( $this->mCollated['-total'] )
+ ? $this->mCollated['-total']['real']
+ : 0; // profiling mismatch error?
+ uasort( $this->mCollated, array('self','sort') );
+ array_walk( $this->mCollated, array('self','format'), $totalReal );
+ if ( $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, $totalReal ) {
+ $perc = $totalReal ? $item['real']/$totalReal*100 : 0;
+ self::$out .= sprintf( "%6.2f%% %3.6f %6d - %s\n",
+ $perc, $item['real'], $item['count'], $key );
+ }
+}
diff --git a/includes/ProfilerSimpleTrace.php b/includes/profiler/ProfilerSimpleTrace.php
index 8f6a2a1c..ba41babc 100644
--- a/includes/ProfilerSimpleTrace.php
+++ b/includes/profiler/ProfilerSimpleTrace.php
@@ -4,10 +4,6 @@
* @ingroup Profiler
*/
-if ( !class_exists( 'ProfilerSimple' ) ) {
- require_once(dirname(__FILE__).'/ProfilerSimple.php');
-}
-
/**
* Execution trace
* @todo document methods (?)
@@ -15,12 +11,12 @@ if ( !class_exists( 'ProfilerSimple' ) ) {
*/
class ProfilerSimpleTrace extends ProfilerSimple {
var $mMinimumTime = 0;
- var $mProfileID = false;
var $trace = "";
var $memory = 0;
- function __construct() {
+ function __construct( $params ) {
global $wgRequestTime, $wgRUstart;
+ parent::__construct( $params );
if ( !empty( $wgRequestTime ) && !empty( $wgRUstart ) ) {
$this->mWorkStack[] = array( '-total', 0, $wgRequestTime, $this->getCpuTime( $wgRUstart ) );
}
@@ -65,7 +61,7 @@ class ProfilerSimpleTrace extends ProfilerSimple {
return $diff / 1024;
}
- function getOutput() {
+ function logData() {
print "<!-- \n {$this->trace} \n -->";
}
}
diff --git a/includes/ProfilerSimpleUDP.php b/includes/profiler/ProfilerSimpleUDP.php
index 67ad97f6..ed49d5a2 100644
--- a/includes/ProfilerSimpleUDP.php
+++ b/includes/profiler/ProfilerSimpleUDP.php
@@ -4,18 +4,18 @@
* @ingroup Profiler
*/
-require_once(dirname(__FILE__).'/ProfilerSimple.php');
-
/**
* ProfilerSimpleUDP class, that sends out messages for 'udpprofile' daemon
* (the one from mediawiki/trunk/udpprofile SVN )
* @ingroup Profiler
*/
class ProfilerSimpleUDP extends ProfilerSimple {
- function getFunctionReport() {
+ public function logData() {
global $wgUDPProfilerHost, $wgUDPProfilerPort;
- if ( $this->mCollated['-total']['real'] < $this->mMinimumTime ) {
+ $this->close();
+
+ if ( isset( $this->mCollated['-total'] ) && $this->mCollated['-total']['real'] < $this->mMinimumTime ) {
# Less than minimum, ignore
return;
}
diff --git a/includes/profiler/ProfilerStub.php b/includes/profiler/ProfilerStub.php
new file mode 100644
index 00000000..58c1975c
--- /dev/null
+++ b/includes/profiler/ProfilerStub.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Stub profiling functions
+ * @file
+ * @ingroup Profiler
+ */
+class ProfilerStub extends Profiler {
+ public function isStub() {
+ return true;
+ }
+ public function profileIn( $fn ) {}
+ public function profileOut( $fn ) {}
+ public function getOutput() {}
+ public function close() {}
+}
diff --git a/includes/proxy_check.php b/includes/proxy_check.php
deleted file mode 100644
index 2bc46c0d..00000000
--- a/includes/proxy_check.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-/**
- * Command line script to check for an open proxy at a specified location
- *
- * @file
- */
-
-if( php_sapi_name() != 'cli' ) {
- die( 1 );
-}
-
-/**
- *
- */
-$output = '';
-
-/**
- * Exit if there are not enough parameters, or if it's not command line mode
- */
-if ( ( isset( $_REQUEST ) && array_key_exists( 'argv', $_REQUEST ) ) || count( $argv ) < 4 ) {
- $output .= "Incorrect parameters\n";
-} else {
- /**
- * Get parameters
- */
- $ip = $argv[1];
- $port = $argv[2];
- $url = $argv[3];
- $host = trim(`hostname`);
- $output = "Connecting to $ip:$port, target $url, this hostname $host\n";
-
- # Open socket
- $sock = @fsockopen($ip, $port, $errno, $errstr, 5);
- if ($errno == 0 ) {
- $output .= "Connected\n";
- # Send payload
- $request = "GET $url HTTP/1.0\r\n";
-# $request .= "Proxy-Connection: Keep-Alive\r\n";
-# $request .= "Pragma: no-cache\r\n";
-# $request .= "Host: ".$url."\r\n";
-# $request .= "User-Agent: MediaWiki open proxy check\r\n";
- $request .= "\r\n";
- @fputs($sock, $request);
- $response = fgets($sock, 65536);
- $output .= $response;
- @fclose($sock);
- } else {
- $output .= "No connection\n";
- }
-}
-
-$output = escapeshellarg( $output );
-
-#`echo $output >> /home/tstarling/open/proxy.log`;
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
index 191bc9f0..2a2e2981 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -29,7 +29,7 @@
class ResourceLoader {
/* Protected Static Members */
- protected static $filterCacheVersion = 2;
+ protected static $filterCacheVersion = 4;
/** Array: List of module name/ResourceLoaderModule object pairs */
protected $modules = array();
@@ -40,15 +40,15 @@ class ResourceLoader {
/**
* Loads information stored in the database about modules.
- *
- * This method grabs modules dependencies from the database and updates 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
+ *
+ * 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
*/
@@ -59,11 +59,11 @@ class ResourceLoader {
$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()
+ 'md_skin' => $skin
), __METHOD__
);
@@ -80,7 +80,7 @@ class ResourceLoader {
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 ) {
@@ -96,11 +96,11 @@ class ResourceLoader {
), __METHOD__
);
foreach ( $res as $row ) {
- $this->getModule( $row->mr_resource )->setMsgBlobMtime( $lang,
+ $this->getModule( $row->mr_resource )->setMsgBlobMtime( $lang,
wfTimestamp( TS_UNIX, $row->mr_timestamp ) );
unset( $modulesWithoutMessages[$row->mr_resource] );
}
- }
+ }
foreach ( array_keys( $modulesWithoutMessages ) as $name ) {
$this->getModule( $name )->setMsgBlobMtime( $lang, 0 );
}
@@ -108,14 +108,14 @@ class ResourceLoader {
/**
* 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,
+ *
+ * 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
@@ -124,10 +124,10 @@ class ResourceLoader {
global $wgResourceLoaderMinifierStatementsOnOwnLine, $wgResourceLoaderMinifierMaxLineLength;
wfProfileIn( __METHOD__ );
- // For empty/whitespace-only data or for unknown filters, don't perform
+ // For empty/whitespace-only data or for unknown filters, don't perform
// any caching or processing
- if ( trim( $data ) === ''
- || !in_array( $filter, array( 'minify-js', 'minify-css' ) ) )
+ if ( trim( $data ) === ''
+ || !in_array( $filter, array( 'minify-js', 'minify-css' ) ) )
{
wfProfileOut( __METHOD__ );
return $data;
@@ -135,7 +135,7 @@ class ResourceLoader {
// Try for cache hit
// Use CACHE_ANYTHING since filtering is very slow compared to DB queries
- $key = wfMemcKey( 'resourceloader', 'filter', $filter, md5( $data ) );
+ $key = wfMemcKey( 'resourceloader', 'filter', $filter, self::$filterCacheVersion, md5( $data ) );
$cache = wfGetCache( CACHE_ANYTHING );
$cacheEntry = $cache->get( $key );
if ( is_string( $cacheEntry ) ) {
@@ -143,6 +143,7 @@ class ResourceLoader {
return $cacheEntry;
}
+ $result = '';
// Run the filter - we've already verified one of these will work
try {
switch ( $filter ) {
@@ -151,9 +152,11 @@ class ResourceLoader {
$wgResourceLoaderMinifierStatementsOnOwnLine,
$wgResourceLoaderMinifierMaxLineLength
);
+ $result .= "\n\n/* cache key: $key */\n";
break;
case 'minify-css':
$result = CSSMin::minify( $data );
+ $result .= "\n\n/* cache key: $key */\n";
break;
}
@@ -165,7 +168,7 @@ class ResourceLoader {
}
wfProfileOut( __METHOD__ );
-
+
return $result;
}
@@ -176,24 +179,24 @@ class ResourceLoader {
*/
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
+ * @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)
@@ -217,14 +220,14 @@ class ResourceLoader {
if ( isset( $this->moduleInfos[$name] ) ) {
// A module has already been registered by this name
throw new MWException(
- 'ResourceLoader duplicate registration error. ' .
+ '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 (,)" );
+ if ( preg_match( '/[|,!]/', $name ) ) {
+ throw new MWException( "ResourceLoader module name '$name' is invalid. Names may not contain pipes (|), commas (,) or exclamation marks (!)" );
}
// Attach module
@@ -232,7 +235,7 @@ class ResourceLoader {
// Old calling convention
// Validate the input
if ( !( $info instanceof ResourceLoaderModule ) ) {
- throw new MWException( 'ResourceLoader invalid module error. ' .
+ throw new MWException( 'ResourceLoader invalid module error. ' .
'Instances of ResourceLoaderModule expected.' );
}
@@ -260,7 +263,7 @@ class ResourceLoader {
* Get the ResourceLoaderModule object for a given module name.
*
* @param $name String: Module name
- * @return Mixed: ResourceLoaderModule if module has been registered, null otherwise
+ * @return ResourceLoaderModule if module has been registered, null otherwise
*/
public function getModule( $name ) {
if ( !isset( $this->modules[$name] ) ) {
@@ -295,7 +298,7 @@ class ResourceLoader {
*/
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
@@ -319,13 +322,13 @@ class ResourceLoader {
}
}
- // If a version wasn't specified we need a shorter expiry time for updates
+ // 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
+ // If a version was specified we can use a longer expiry time since changing
// version numbers causes cache misses
else {
$maxage = $wgResourceLoaderMaxage['versioned']['client'];
@@ -343,7 +346,7 @@ class ResourceLoader {
wfProfileIn( __METHOD__.'-getModifiedTime' );
$private = false;
- // To send Last-Modified and support If-Modified-Since, we need to detect
+ // 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 ) {
@@ -387,7 +390,8 @@ class ResourceLoader {
// 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 ) {
+ // Never send 304s in debug mode
+ if ( $ims !== false && !$context->getDebug() ) {
$imsTS = strtok( $ims, ';' );
if ( $mtime <= wfTimestamp( TS_UNIX, $imsTS ) ) {
// There's another bug in ob_gzhandler (see also the comment at
@@ -406,17 +410,17 @@ class ResourceLoader {
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;
@@ -435,21 +439,21 @@ class ResourceLoader {
/**
* 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() )
+ 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() ) {
@@ -467,18 +471,33 @@ class ResourceLoader {
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";
+ // If we are in debug mode, we'll want to return an array of URLs if possible
+ // However, we can't do this if the module doesn't support it
+ // We also can't do this if there is an only= parameter, because we have to give
+ // the module a way to return a load.php URL without causing an infinite loop
+ if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
+ $scripts = $module->getScriptURLsForDebug( $context );
+ } else {
+ $scripts = $module->getScript( $context );
+ if ( is_string( $scripts ) ) {
+ // bug 27054: Append semicolon to prevent weird bugs
+ // caused by files not terminating their statements right
+ $scripts .= ";\n";
+ }
+ }
}
-
// Styles
$styles = array();
if ( $context->shouldIncludeStyles() ) {
- $styles = $module->getStyles( $context );
+ // If we are in debug mode, we'll want to return an array of URLs
+ // See comment near shouldIncludeScripts() for more details
+ if ( $context->getDebug() && !$context->getOnly() && $module->supportsURLLoading() ) {
+ $styles = $module->getStyleURLsForDebug( $context );
+ } else {
+ $styles = $module->getStyles( $context );
+ }
}
// Messages
@@ -487,7 +506,11 @@ class ResourceLoader {
// Append output
switch ( $context->getOnly() ) {
case 'scripts':
- $out .= $scripts;
+ if ( is_string( $scripts ) ) {
+ $out .= $scripts;
+ } elseif ( is_array( $scripts ) ) {
+ $out .= self::makeLoaderImplementScript( $name, $scripts, array(), array() );
+ }
break;
case 'styles':
$out .= self::makeCombinedStyles( $styles );
@@ -496,11 +519,13 @@ class ResourceLoader {
$out .= self::makeMessageSetScript( new XmlJsCode( $messagesBlob ) );
break;
default:
- // Minify CSS before embedding in mediaWiki.loader.implement call
+ // Minify CSS before embedding in mw.loader.implement call
// (unless in debug mode)
if ( !$context->getDebug() ) {
foreach ( $styles as $media => $style ) {
- $styles[$media] = $this->filter( 'minify-css', $style );
+ if ( is_string( $style ) ) {
+ $styles[$media] = $this->filter( 'minify-css', $style );
+ }
}
}
$out .= self::makeLoaderImplementScript( $name, $scripts, $styles,
@@ -521,10 +546,10 @@ class ResourceLoader {
// 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'] ) )
+ if ( count( $modules ) && $context->getOnly() === 'scripts'
+ && !isset( $modules['startup'] ) )
{
- $out .= self::makeLoaderStateScript(
+ $out .= self::makeLoaderStateScript(
array_fill_keys( array_keys( $modules ), 'ready' ) );
}
// Set the state of modules which were requested but unavailable as missing
@@ -540,7 +565,7 @@ class ResourceLoader {
$out = $this->filter( 'minify-js', $out );
}
}
-
+
wfProfileOut( __METHOD__ );
return $exceptions . $out;
}
@@ -548,26 +573,30 @@ class ResourceLoader {
/* Static Methods */
/**
- * Returns JS code to call to mediaWiki.loader.implement for a module with
+ * Returns JS code to call to mw.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
+ * @param $scripts Mixed: List of URLs to JavaScript files or String of JavaScript code
+ * @param $styles Mixed: List of CSS strings keyed by media type, or list of lists of URLs to
+ * CSS files keyed by media type
+ * @param $messages Mixed: List of messages associated with this module. May either be an
* associative array mapping message key to value, or a JSON-encoded message blob containing
* the same data, wrapped in an XmlJsCode object.
+ *
+ * @return string
*/
public static function makeLoaderImplementScript( $name, $scripts, $styles, $messages ) {
- if ( is_array( $scripts ) ) {
- $scripts = implode( $scripts, "\n" );
+ if ( is_string( $scripts ) ) {
+ $scripts = new XmlJsCode( "function( $ ) {{$scripts}}" );
+ } elseif ( !is_array( $scripts ) ) {
+ throw new MWException( 'Invalid scripts error. Array of URLs or string of code expected.' );
}
- return Xml::encodeJsCall(
- 'mediaWiki.loader.implement',
+ return Xml::encodeJsCall(
+ 'mw.loader.implement',
array(
$name,
- new XmlJsCode( "function( $, mw ) {{$scripts}}" ),
+ $scripts,
(object)$styles,
(object)$messages
) );
@@ -578,16 +607,20 @@ class ResourceLoader {
*
* @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.
+ *
+ * @return string
*/
public static function makeMessageSetScript( $messages ) {
- return Xml::encodeJsCall( 'mediaWiki.messages.set', array( (object)$messages ) );
+ return Xml::encodeJsCall( 'mw.messages.set', array( (object)$messages ) );
}
/**
- * Combines an associative array mapping media type to CSS into a
+ * 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
+ *
+ * @return string
*/
public static function makeCombinedStyles( array $styles ) {
$out = '';
@@ -595,10 +628,10 @@ class ResourceLoader {
// 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' ) {
+ } elseif ( $media === '' || $media == 'all' ) {
// Don't output invalid or frivolous @media statements
$out .= "$style\n";
} else {
@@ -609,7 +642,7 @@ class ResourceLoader {
}
/**
- * Returns a JS call to mediaWiki.loader.state, which sets the state of a
+ * Returns a JS call to mw.loader.state, which sets the state of a
* module or modules to a given value. Has two calling conventions:
*
* - ResourceLoader::makeLoaderStateScript( $name, $state ):
@@ -617,36 +650,43 @@ class ResourceLoader {
*
* - ResourceLoader::makeLoaderStateScript( array( $name => $state, ... ) ):
* Set the state of modules with the given names to the given states
+ *
+ * @param $name string
+ * @param $state
+ *
+ * @return string
*/
public static function makeLoaderStateScript( $name, $state = null ) {
if ( is_array( $name ) ) {
- return Xml::encodeJsCall( 'mediaWiki.loader.state', array( $name ) );
+ return Xml::encodeJsCall( 'mw.loader.state', array( $name ) );
} else {
- return Xml::encodeJsCall( 'mediaWiki.loader.state', array( $name, $state ) );
+ return Xml::encodeJsCall( 'mw.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.
+ * 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
+ *
+ * @return string
*/
public static function makeCustomLoaderScript( $name, $version, $dependencies, $group, $script ) {
$script = str_replace( "\n", "\n\t", trim( $script ) );
- return Xml::encodeJsCall(
+ 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
+ * Returns JS code which calls mw.loader.register with the given
* parameters. Has three calling conventions:
*
* - ResourceLoader::makeLoaderRegisterScript( $name, $version, $dependencies, $group ):
@@ -666,43 +706,49 @@ class ResourceLoader {
* @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.
+ *
+ * @return string
*/
- public static function makeLoaderRegisterScript( $name, $version = null,
- $dependencies = null, $group = null )
+ public static function makeLoaderRegisterScript( $name, $version = null,
+ $dependencies = null, $group = null )
{
if ( is_array( $name ) ) {
- return Xml::encodeJsCall( 'mediaWiki.loader.register', array( $name ) );
+ return Xml::encodeJsCall( 'mw.loader.register', array( $name ) );
} else {
$version = (int) $version > 1 ? (int) $version : 1;
- return Xml::encodeJsCall( 'mediaWiki.loader.register',
+ return Xml::encodeJsCall( 'mw.loader.register',
array( $name, $version, $dependencies, $group ) );
}
}
/**
- * Returns JS code which runs given JS code if the client-side framework is
+ * Returns JS code which runs given JS code if the client-side framework is
* present.
*
* @param $script String: JavaScript code
+ *
+ * @return string
*/
public static function makeLoaderConditionalScript( $script ) {
$script = str_replace( "\n", "\n\t", trim( $script ) );
- return "if ( window.mediaWiki ) {\n\t$script\n}\n";
+ return "if(window.mw){\n\t$script\n}\n";
}
/**
- * Returns JS code which will set the MediaWiki configuration array to
+ * 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
+ *
+ * @return string
*/
public static function makeConfigSetScript( array $configuration ) {
- return Xml::encodeJsCall( 'mediaWiki.config.set', array( $configuration ) );
+ return Xml::encodeJsCall( 'mw.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)
@@ -716,15 +762,16 @@ class ResourceLoader {
$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 );
+ $str = implode( '|', $arr );
+ return $str;
}
-
+
/**
* Determine whether debug mode was requested
* Order of priority is 1) request param, 2) cookie, 3) $wg setting
@@ -733,9 +780,71 @@ class ResourceLoader {
public static function inDebugMode() {
global $wgRequest, $wgResourceLoaderDebug;
static $retval = null;
- if ( !is_null( $retval ) )
+ if ( !is_null( $retval ) ) {
return $retval;
+ }
return $retval = $wgRequest->getFuzzyBool( 'debug',
$wgRequest->getCookie( 'resourceLoaderDebug', '', $wgResourceLoaderDebug ) );
}
+
+ /**
+ * Build a load.php URL
+ * @param $modules array of module names (strings)
+ * @param $lang string Language code
+ * @param $skin string Skin name
+ * @param $user string|null User name. If null, the &user= parameter is omitted
+ * @param $version string|null Versioning timestamp
+ * @param $debug bool Whether the request should be in debug mode
+ * @param $only string|null &only= parameter
+ * @param $printable bool Printable mode
+ * @param $handheld bool Handheld mode
+ * @param $extraQuery array Extra query parameters to add
+ * @return string URL to load.php. May be protocol-relative (if $wgLoadScript is procol-relative)
+ */
+ public static function makeLoaderURL( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null,
+ $printable = false, $handheld = false, $extraQuery = array() ) {
+ global $wgLoadScript;
+ $query = self::makeLoaderQuery( $modules, $lang, $skin, $user, $version, $debug,
+ $only, $printable, $handheld, $extraQuery
+ );
+
+ // Prevent the IE6 extension check from being triggered (bug 28840)
+ // by appending a character that's invalid in Windows extensions ('*')
+ return wfExpandUrl( wfAppendQuery( $wgLoadScript, $query ) . '&*', PROTO_RELATIVE );
+ }
+
+ /**
+ * Build a query array (array representation of query string) for load.php. Helper
+ * function for makeLoaderURL().
+ * @return array
+ */
+ public static function makeLoaderQuery( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null,
+ $printable = false, $handheld = false, $extraQuery = array() ) {
+ $query = array(
+ 'modules' => self::makePackedModulesString( $modules ),
+ 'lang' => $lang,
+ 'skin' => $skin,
+ 'debug' => $debug ? 'true' : 'false',
+ );
+ if ( $user !== null ) {
+ $query['user'] = $user;
+ }
+ if ( $version !== null ) {
+ $query['version'] = $version;
+ }
+ if ( $only !== null ) {
+ $query['only'] = $only;
+ }
+ if ( $printable ) {
+ $query['printable'] = 1;
+ }
+ if ( $handheld ) {
+ $query['handheld'] = 1;
+ }
+ $query += $extraQuery;
+
+ // Make queries uniform in order
+ ksort( $query );
+ return $query;
+ }
}
diff --git a/includes/resourceloader/ResourceLoaderContext.php b/includes/resourceloader/ResourceLoaderContext.php
index bf059b46..326b7c4a 100644
--- a/includes/resourceloader/ResourceLoaderContext.php
+++ b/includes/resourceloader/ResourceLoaderContext.php
@@ -73,6 +73,8 @@ class ResourceLoaderContext {
*/
public static function expandModuleNames( $modules ) {
$retval = array();
+ // For backwards compatibility with an earlier hack, replace ! with .
+ $modules = str_replace( '!', '.', $modules );
$exploded = explode( '|', $modules );
foreach ( $exploded as $group ) {
if ( strpos( $group, ',' ) === false ) {
@@ -98,18 +100,30 @@ class ResourceLoaderContext {
return $retval;
}
+ /**
+ * @return ResourceLoader
+ */
public function getResourceLoader() {
return $this->resourceLoader;
}
-
+
+ /**
+ * @return WebRequest
+ */
public function getRequest() {
return $this->request;
}
+ /**
+ * @return array
+ */
public function getModules() {
return $this->modules;
}
+ /**
+ * @return string
+ */
public function getLanguage() {
if ( $this->language === null ) {
global $wgLang;
@@ -121,49 +135,79 @@ class ResourceLoaderContext {
return $this->language;
}
+ /**
+ * @return string
+ */
public function getDirection() {
if ( $this->direction === null ) {
$this->direction = $this->request->getVal( 'dir' );
if ( !$this->direction ) {
- global $wgContLang;
- $this->direction = $wgContLang->getDir();
+ # directionality based on user language (see bug 6100)
+ $this->direction = Language::factory( $this->language )->getDir();
}
}
return $this->direction;
}
+ /**
+ * @return string
+ */
public function getSkin() {
return $this->skin;
}
+ /**
+ * @return string
+ */
public function getUser() {
return $this->user;
}
+ /**
+ * @return bool
+ */
public function getDebug() {
return $this->debug;
}
+ /**
+ * @return String
+ */
public function getOnly() {
return $this->only;
}
+ /**
+ * @return String
+ */
public function getVersion() {
return $this->version;
}
+ /**
+ * @return bool
+ */
public function shouldIncludeScripts() {
return is_null( $this->only ) || $this->only === 'scripts';
}
+ /**
+ * @return bool
+ */
public function shouldIncludeStyles() {
return is_null( $this->only ) || $this->only === 'styles';
}
+ /**
+ * @return bool
+ */
public function shouldIncludeMessages() {
return is_null( $this->only ) || $this->only === 'messages';
}
+ /**
+ * @return string
+ */
public function getHash() {
if ( !isset( $this->hash ) ) {
$this->hash = implode( '|', array(
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php
index 1c37eb07..f38c60ae 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -78,6 +78,8 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected $messages = array();
/** String: Name of group to load this module in */
protected $group;
+ /** String: Position on the page to load this module at */
+ protected $position = 'bottom';
/** Boolean: Link to raw files in debug mode */
protected $debugRaw = true;
/**
@@ -95,15 +97,16 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* 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
+ *
+ * Below is a description for the $options array:
+ * @code
* array(
* // Base path to prepend to all local paths in $options. Defaults to $IP
* 'localBasePath' => [base path],
@@ -137,14 +140,21 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* 'messages' => [array of message key strings],
* // Group which this module should be loaded together with
* 'group' => [group name string],
+ * // Position on the page to load this module at
+ * 'position' => ['bottom' (default) or 'top']
* )
+ * @endcode
*/
- public function __construct( $options = array(), $localBasePath = null,
- $remoteBasePath = null )
+ public function __construct( $options = array(), $localBasePath = null,
+ $remoteBasePath = null )
{
- global $IP, $wgScriptPath;
+ global $IP, $wgScriptPath, $wgResourceBasePath;
$this->localBasePath = $localBasePath === null ? $IP : $localBasePath;
- $this->remoteBasePath = $remoteBasePath === null ? $wgScriptPath : $remoteBasePath;
+ if ( $remoteBasePath !== null ) {
+ $this->remoteBasePath = $remoteBasePath;
+ } else {
+ $this->remoteBasePath = $wgResourceBasePath === null ? $wgScriptPath : $wgResourceBasePath;
+ }
if ( isset( $options['remoteExtPath'] ) ) {
global $wgExtensionAssetsPath;
@@ -166,14 +176,14 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
case 'skinStyles':
if ( !is_array( $option ) ) {
throw new MWException(
- "Invalid collated file path list error. " .
+ "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. " .
+ "Invalid collated file path list key error. " .
"'$key' given, string expected."
);
}
@@ -187,6 +197,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
break;
// Single strings
case 'group':
+ case 'position':
case 'localBasePath':
case 'remoteBasePath':
$this->{$member} = (string) $option;
@@ -197,39 +208,37 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
break;
}
}
- // Make sure the remote base path is a complete valid url
- $this->remoteBasePath = wfExpandUrl( $this->remoteBasePath );
+ // Make sure the remote base path is a complete valid URL,
+ // but possibly protocol-relative to avoid cache pollution
+ $this->remoteBasePath = wfExpandUrl( $this->remoteBasePath, PROTO_RELATIVE );
}
/**
* 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;
- }
- }
+ $files = $this->getScriptFiles( $context );
return $this->readScriptFiles( $files );
}
+
+ public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
+ $urls = array();
+ foreach ( $this->getScriptFiles( $context ) as $file ) {
+ $urls[] = $this->getRemotePath( $file );
+ }
+ return $urls;
+ }
+
+ public function supportsURLLoading() {
+ return $this->debugRaw;
+ }
/**
* Gets loader script.
- *
+ *
* @return String: JavaScript code to be added to startup module
*/
public function getLoaderScript() {
@@ -241,29 +250,19 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* 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' ),
+ $styles = $this->readStyleFiles(
+ $this->getStyleFiles( $context ),
$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() ) ) {
+ if ( $this->localFileRefs !== $this->getFileDependencies( $context->getSkin() ) && !wfReadOnly() ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->replace( 'module_deps',
array( array( 'md_module', 'md_skin' ) ), array(
@@ -276,9 +275,20 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
return $styles;
}
+ public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
+ $urls = array();
+ foreach ( $this->getStyleFiles( $context ) as $mediaType => $list ) {
+ $urls[$mediaType] = array();
+ foreach ( $list as $file ) {
+ $urls[$mediaType][] = $this->getRemotePath( $file );
+ }
+ }
+ return $urls;
+ }
+
/**
* Gets list of message keys used by this module.
- *
+ *
* @return Array: List of message keys
*/
public function getMessages() {
@@ -287,7 +297,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Gets the name of the group this module should be loaded in.
- *
+ *
* @return String: Group name
*/
public function getGroup() {
@@ -295,8 +305,15 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
+ * @return string
+ */
+ public function getPosition() {
+ return $this->position;
+ }
+
+ /**
* Gets list of names of modules this module depends on.
- *
+ *
* @return Array: List of module names
*/
public function getDependencies() {
@@ -305,14 +322,14 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* 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
+ *
+ * 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
+ *
+ * @param $context ResourceLoaderContext: Context in which to calculate
* the modified time
* @return Integer: UNIX timestamp
* @see ResourceLoaderModule::getFileDependencies
@@ -322,23 +339,23 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
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(),
+ 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,
@@ -351,39 +368,47 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$files = array_map( array( $this, 'getLocalPath' ), $files );
// File deps need to be treated separately because they're already prefixed
$files = array_merge( $files, $this->getFileDependencies( $context->getSkin() ) );
-
- // If a module is nothing but a list of dependencies, we need to avoid
+
+ // 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( array( __CLASS__, 'safeFilemtime' ), $files ) );
+ $filesMtime = max( array_map( 'filemtime', $files ) );
wfProfileOut( __METHOD__.'-filemtime' );
- $this->modifiedTime[$context->getHash()] = max(
- $filesMtime,
+ $this->modifiedTime[$context->getHash()] = max(
+ $filesMtime,
$this->getMsgBlobMtime( $context->getLanguage() ) );
wfProfileOut( __METHOD__ );
return $this->modifiedTime[$context->getHash()];
}
- /* Protected Members */
+ /* Protected Methods */
+ /**
+ * @param $path string
+ * @return string
+ */
protected function getLocalPath( $path ) {
return "{$this->localBasePath}/$path";
}
-
+
+ /**
+ * @param $path string
+ * @return string
+ */
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
+ *
+ * @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
@@ -398,7 +423,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$collatedFiles[$default] = array();
}
$collatedFiles[$default][] = $value;
- } else if ( is_array( $value ) ) {
+ } elseif ( is_array( $value ) ) {
// File name as the key, options array as the value
$optionValue = isset( $value[$option] ) ? $value[$option] : $default;
if ( !isset( $collatedFiles[$optionValue] ) ) {
@@ -412,19 +437,19 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* 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,
+ * @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] ) )
+ } elseif ( is_string( $fallback )
+ && isset( $list[$fallback] )
+ && is_array( $list[$fallback] ) )
{
return $list[$fallback];
}
@@ -432,23 +457,56 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
+ * Gets a list of file paths for all scripts in this module, in order of propper execution.
+ *
+ * @param $context ResourceLoaderContext: Context
+ * @return Array: List of file paths
+ */
+ protected function getScriptFiles( 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 );
+ }
+ return $files;
+ }
+
+ /**
+ * Gets a list of file paths for all styles in this module, in order of propper inclusion.
+ *
+ * @param $context ResourceLoaderContext: Context
+ * @return Array: List of file paths
+ */
+ protected function getStyleFiles( ResourceLoaderContext $context ) {
+ return array_merge_recursive(
+ self::collateFilePathListByOption( $this->styles, 'media', 'all' ),
+ self::collateFilePathListByOption(
+ self::tryForKey( $this->skinStyles, $context->getSkin(), 'default' ), 'media', 'all'
+ )
+ );
+ }
+
+ /**
* 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 ) {
+ global $wgResourceLoaderValidateStaticJS;
if ( empty( $scripts ) ) {
return '';
}
- global $wgResourceLoaderValidateStaticJS;
$js = '';
foreach ( array_unique( $scripts ) as $fileName ) {
$localPath = $this->getLocalPath( $fileName );
- if ( !file_exists( $localPath ) ) {
+ $contents = file_get_contents( $localPath );
+ if ( $contents === false ) {
throw new MWException( __METHOD__.": script file not found: \"$localPath\"" );
}
- $contents = file_get_contents( $localPath );
if ( $wgResourceLoaderValidateStaticJS ) {
// Static files don't really need to be checked as often; unlike
// on-wiki module they shouldn't change unexpectedly without
@@ -462,16 +520,19 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* 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,
+ *
+ * @param $styles Array: List of media type/list of file paths pairs, to read, remap and
+ * concetenate
+ *
+ * @param $flip bool
+ *
+ * @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(
@@ -488,49 +549,38 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
/**
* Reads a style file.
- *
+ *
* This method can be used as a callback for array_map()
- *
- * @param $path String: File path of style file to read
+ *
+ * @param $path String: File path of script file to read
+ * @param $flip bool
+ *
* @return String: CSS data in script file
- * @throws MWException if the file doesn't exist
*/
- protected function readStyleFile( $path, $flip ) {
+ protected function readStyleFile( $path, $flip ) {
$localPath = $this->getLocalPath( $path );
- if ( !file_exists( $localPath ) ) {
+ $style = file_get_contents( $localPath );
+ if ( $style === false ) {
throw new MWException( __METHOD__.": style file not found: \"$localPath\"" );
}
- $style = file_get_contents( $localPath );
if ( $flip ) {
$style = CSSJanus::transform( $style, true, false );
}
- $dir = $this->getLocalPath( dirname( $path ) );
- $remoteDir = $this->getRemotePath( dirname( $path ) );
+ $dirname = dirname( $path );
+ if ( $dirname == '.' ) {
+ // If $path doesn't have a directory component, don't prepend a dot
+ $dirname = '';
+ }
+ $dir = $this->getLocalPath( $dirname );
+ $remoteDir = $this->getRemotePath( $dirname );
// Get and register local file references
- $this->localFileRefs = array_merge(
- $this->localFileRefs,
+ $this->localFileRefs = array_merge(
+ $this->localFileRefs,
CSSMin::getLocalFileReferences( $style, $dir ) );
return CSSMin::remap(
$style, $dir, $remoteDir, true
);
}
-
- /**
- * Safe version of filemtime(), which doesn't throw a PHP warning if the file doesn't exist
- * but returns 1 instead.
- * @param $filename string File name
- * @return int UNIX timestamp, or 1 if the file doesn't exist
- */
- protected static function safeFilemtime( $filename ) {
- if ( file_exists( $filename ) ) {
- return filemtime( $filename );
- } else {
- // We only ever map this function on an array if we're gonna call max() after,
- // so return our standard minimum timestamps here. This is 1, not 0, because
- // wfTimestamp(0) == NOW
- return 1;
- }
- }
/**
* Get whether CSS for this module should be flipped
diff --git a/includes/resourceloader/ResourceLoaderFilePageModule.php b/includes/resourceloader/ResourceLoaderFilePageModule.php
new file mode 100644
index 00000000..fc9aef1b
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderFilePageModule.php
@@ -0,0 +1,11 @@
+<?php
+/*
+ * ResourceLoader definition for MediaWiki:Filepage.css
+ */
+class ResourceLoaderFilePageModule extends ResourceLoaderWikiModule {
+ protected function getPages( ResourceLoaderContext $context ) {
+ return array(
+ 'MediaWiki:Filepage.css' => array( 'type' => 'style' ),
+ );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderModule.php b/includes/resourceloader/ResourceLoaderModule.php
index 77d230c9..ae1be5af 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -24,6 +24,34 @@
* Abstraction for resource loader modules, with name registration and maxage functionality.
*/
abstract class ResourceLoaderModule {
+
+ # Type of resource
+ const TYPE_SCRIPTS = 'scripts';
+ const TYPE_STYLES = 'styles';
+ const TYPE_MESSAGES = 'messages';
+ const TYPE_COMBINED = 'combined';
+
+ # sitewide core module like a skin file or jQuery component
+ const ORIGIN_CORE_SITEWIDE = 1;
+
+ # per-user module generated by the software
+ const ORIGIN_CORE_INDIVIDUAL = 2;
+
+ # sitewide module generated from user-editable files, like MediaWiki:Common.js, or
+ # modules accessible to multiple users, such as those generated by the Gadgets extension.
+ const ORIGIN_USER_SITEWIDE = 3;
+
+ # per-user module generated from user-editable files, like User:Me/vector.js
+ const ORIGIN_USER_INDIVIDUAL = 4;
+
+ # an access constant; make sure this is kept as the largest number in this group
+ const ORIGIN_ALL = 10;
+
+ # script and style modules form a hierarchy of trustworthiness, with core modules like
+ # skins and jQuery as most trustworthy, and user scripts as least trustworthy. We can
+ # limit the types of scripts and styles we allow to load on, say, sensitive special
+ # pages like Special:UserLogin and Special:Preferences
+ protected $origin = self::ORIGIN_CORE_SITEWIDE;
/* Protected Members */
@@ -57,10 +85,34 @@ abstract class ResourceLoaderModule {
}
/**
- * Get whether CSS for this module should be flipped
+ * Get this module's origin. This is set when the module is registered
+ * with ResourceLoader::register()
+ *
+ * @return Int ResourceLoaderModule class constant, the subclass default
+ * if not set manuall
+ */
+ public function getOrigin() {
+ return $this->origin;
+ }
+
+ /**
+ * Set this module's origin. This is called by ResourceLodaer::register()
+ * when registering the module. Other code should not call this.
+ *
+ * @param $origin Int origin
+ */
+ public function setOrigin( $origin ) {
+ $this->origin = $origin;
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return bool
*/
public function getFlip( $context ) {
- return $context->getDirection() === 'rtl';
+ global $wgContLang;
+
+ return $wgContLang->getDir() !== $context->getDirection();
}
/**
@@ -74,6 +126,44 @@ abstract class ResourceLoaderModule {
// Stub, override expected
return '';
}
+
+ /**
+ * Get the URL or URLs to load for this module's JS in debug mode.
+ * The default behavior is to return a load.php?only=scripts URL for
+ * the module, but file-based modules will want to override this to
+ * load the files directly.
+ *
+ * This function is called only when 1) we're in debug mode, 2) there
+ * is no only= parameter and 3) supportsURLLoading() returns true.
+ * #2 is important to prevent an infinite loop, therefore this function
+ * MUST return either an only= URL or a non-load.php URL.
+ *
+ * @param $context ResourceLoaderContext: Context object
+ * @return Array of URLs
+ */
+ public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
+ global $wgLoadScript; // TODO factor out to ResourceLoader static method and deduplicate from makeResourceLoaderLink()
+ $query = array(
+ 'modules' => $this->getName(),
+ 'only' => 'scripts',
+ 'skin' => $context->getSkin(),
+ 'user' => $context->getUser(),
+ 'debug' => 'true',
+ 'version' => $context->getVersion()
+ );
+ ksort( $query );
+ return array( wfAppendQuery( $wgLoadScript, $query ) . '&*' );
+ }
+
+ /**
+ * Whether this module supports URL loading. If this function returns false,
+ * getScript() will be used even in cases (debug mode, no only param) where
+ * getScriptURLsForDebug() would normally be used instead.
+ * @return bool
+ */
+ public function supportsURLLoading() {
+ return true;
+ }
/**
* Get all CSS for this module for a given skin.
@@ -83,7 +173,30 @@ abstract class ResourceLoaderModule {
*/
public function getStyles( ResourceLoaderContext $context ) {
// Stub, override expected
- return '';
+ return array();
+ }
+
+ /**
+ * Get the URL or URLs to load for this module's CSS in debug mode.
+ * The default behavior is to return a load.php?only=styles URL for
+ * the module, but file-based modules will want to override this to
+ * load the files directly. See also getScriptURLsForDebug()
+ *
+ * @param $context ResourceLoaderContext: Context object
+ * @return Array: array( mediaType => array( URL1, URL2, ... ), ... )
+ */
+ public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
+ global $wgLoadScript; // TODO factor out to ResourceLoader static method and deduplicate from makeResourceLoaderLink()
+ $query = array(
+ 'modules' => $this->getName(),
+ 'only' => 'styles',
+ 'skin' => $context->getSkin(),
+ 'user' => $context->getUser(),
+ 'debug' => 'true',
+ 'version' => $context->getVersion()
+ );
+ ksort( $query );
+ return array( 'all' => array( wfAppendQuery( $wgLoadScript, $query ) . '&*' ) );
}
/**
@@ -107,6 +220,17 @@ abstract class ResourceLoaderModule {
// Stub, override expected
return null;
}
+
+ /**
+ * Where on the HTML page should this module's JS be loaded?
+ * 'top': in the <head>
+ * 'bottom': at the bottom of the <body>
+ *
+ * @return string
+ */
+ public function getPosition() {
+ return 'bottom';
+ }
/**
* Get the loader JS for this module, if set.
@@ -179,7 +303,7 @@ abstract class ResourceLoaderModule {
* 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
+ * @return Integer: UNIX timestamp, or 0 if the module doesn't have messages
*/
public function getMsgBlobMtime( $lang ) {
if ( !isset( $this->msgBlobMtime[$lang] ) ) {
@@ -192,7 +316,12 @@ abstract class ResourceLoaderModule {
'mr_lang' => $lang
), __METHOD__
);
- $this->msgBlobMtime[$lang] = $msgBlobMtime ? wfTimestamp( TS_UNIX, $msgBlobMtime ) : 0;
+ // If no blob was found, but the module does have messages, that means we need
+ // to regenerate it. Return NOW
+ if ( $msgBlobMtime === false ) {
+ $msgBlobMtime = wfTimestampNow();
+ }
+ $this->msgBlobMtime[$lang] = wfTimestamp( TS_UNIX, $msgBlobMtime );
}
return $this->msgBlobMtime[$lang];
}
@@ -236,4 +365,54 @@ abstract class ResourceLoaderModule {
public function isKnownEmpty( ResourceLoaderContext $context ) {
return false;
}
+
+
+ /** @var JSParser lazy-initialized; use self::javaScriptParser() */
+ private static $jsParser;
+ private static $parseCacheVersion = 1;
+
+ /**
+ * Validate a given script file; if valid returns the original source.
+ * If invalid, returns replacement JS source that throws an exception.
+ *
+ * @param string $fileName
+ * @param string $contents
+ * @return string JS with the original, or a replacement error
+ */
+ protected function validateScriptFile( $fileName, $contents ) {
+ global $wgResourceLoaderValidateJS;
+ if ( $wgResourceLoaderValidateJS ) {
+ // Try for cache hit
+ // Use CACHE_ANYTHING since filtering is very slow compared to DB queries
+ $key = wfMemcKey( 'resourceloader', 'jsparse', self::$parseCacheVersion, md5( $contents ) );
+ $cache = wfGetCache( CACHE_ANYTHING );
+ $cacheEntry = $cache->get( $key );
+ if ( is_string( $cacheEntry ) ) {
+ return $cacheEntry;
+ }
+
+ $parser = self::javaScriptParser();
+ try {
+ $parser->parse( $contents, $fileName, 1 );
+ $result = $contents;
+ } catch (Exception $e) {
+ // We'll save this to cache to avoid having to validate broken JS over and over...
+ $err = $e->getMessage();
+ $result = "throw new Error(" . Xml::encodeJsVar("JavaScript parse error: $err") . ");";
+ }
+
+ $cache->set( $key, $result );
+ return $result;
+ } else {
+ return $contents;
+ }
+ }
+
+ protected static function javaScriptParser() {
+ if ( !self::$jsParser ) {
+ self::$jsParser = new JSParser();
+ }
+ return self::$jsParser;
+ }
+
}
diff --git a/includes/resourceloader/ResourceLoaderNoscriptModule.php b/includes/resourceloader/ResourceLoaderNoscriptModule.php
new file mode 100644
index 00000000..28f629a2
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderNoscriptModule.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+/**
+ * Module for site customizations
+ */
+class ResourceLoaderNoscriptModule extends ResourceLoaderWikiModule {
+
+ /* Protected Methods */
+
+ /**
+ * Gets list of pages used by this module. Obviously, it makes absolutely no
+ * sense to include JavaScript files here... :D
+ *
+ * @param $context ResourceLoaderContext
+ *
+ * @return Array: List of pages
+ */
+ protected function getPages( ResourceLoaderContext $context ) {
+ return array( 'MediaWiki:Noscript.css' => array( 'type' => 'style' ) );
+ }
+
+ /* Methods */
+
+ /**
+ * Gets group name
+ *
+ * @return String: Name of group
+ */
+ public function getGroup() {
+ return 'noscript';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderSiteModule.php b/includes/resourceloader/ResourceLoaderSiteModule.php
index 977d16bb..2527a0a3 100644
--- a/includes/resourceloader/ResourceLoaderSiteModule.php
+++ b/includes/resourceloader/ResourceLoaderSiteModule.php
@@ -29,7 +29,9 @@ class ResourceLoaderSiteModule extends ResourceLoaderWikiModule {
/**
* Gets list of pages used by this module
- *
+ *
+ * @param $context ResourceLoaderContext
+ *
* @return Array: List of pages
*/
protected function getPages( ResourceLoaderContext $context ) {
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php
index f3117378..43f1dbd2 100644
--- a/includes/resourceloader/ResourceLoaderStartUpModule.php
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -21,20 +21,24 @@
*/
class ResourceLoaderStartUpModule extends ResourceLoaderModule {
-
+
/* Protected Members */
protected $modifiedTime = array();
/* Protected Methods */
-
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
protected function getConfig( $context ) {
- global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
- $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang,
- $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
- $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
+ global $wgLoadScript, $wgScript, $wgStylePath, $wgScriptExtension,
+ $wgArticlePath, $wgScriptPath, $wgServer, $wgContLang,
+ $wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
+ $wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
$wgSitename, $wgFileExtensions, $wgExtensionAssetsPath,
- $wgResourceLoaderMaxQueryLength;
+ $wgCookiePrefix, $wgResourceLoaderMaxQueryLength, $wgLegacyJavaScriptGlobals;
// Pre-process information
$separatorTransTable = $wgContLang->separatorTransformTable();
@@ -50,7 +54,21 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
implode( "\t", $digitTransTable ),
);
$mainPage = Title::newMainPage();
-
+
+ /**
+ * Namespace related preparation
+ * - wgNamespaceIds: Key-value pairs of all localized, canonical and aliases for namespaces.
+ * - wgCaseSensitiveNamespaces: Array of namespaces that are case-sensitive.
+ */
+ $namespaceIds = $wgContLang->getNamespaceIds();
+ $caseSensitiveNamespaces = array();
+ foreach( MWNamespace::getCanonicalNamespaces() as $index => $name ) {
+ $namespaceIds[$wgContLang->lc( $name )] = $index;
+ if ( !MWNamespace::isCapitalized( $index ) ) {
+ $caseSensitiveNamespaces[] = $index;
+ }
+ }
+
// Build list of variables
$vars = array(
'wgLoadScript' => $wgLoadScript,
@@ -70,29 +88,37 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgVersion' => $wgVersion,
'wgEnableAPI' => $wgEnableAPI,
'wgEnableWriteAPI' => $wgEnableWriteAPI,
+ 'wgDefaultDateFormat' => $wgContLang->getDefaultDateFormat(),
+ 'wgMonthNames' => $wgContLang->getMonthNamesArray(),
+ 'wgMonthNamesShort' => $wgContLang->getMonthAbbreviationsArray(),
'wgSeparatorTransformTable' => $compactSeparatorTransTable,
'wgDigitTransformTable' => $compactDigitTransTable,
'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
- 'wgNamespaceIds' => $wgContLang->getNamespaceIds(),
+ 'wgNamespaceIds' => $namespaceIds,
'wgSiteName' => $wgSitename,
'wgFileExtensions' => array_values( $wgFileExtensions ),
'wgDBname' => $wgDBname,
+ // This sucks, it is only needed on Special:Upload, but I could
+ // not find a way to add vars only for a certain module
+ 'wgFileCanRotate' => BitmapHandler::canRotate(),
+ 'wgAvailableSkins' => Skin::getSkinNames(),
'wgExtensionAssetsPath' => $wgExtensionAssetsPath,
+ // MediaWiki sets cookies to have this prefix by default
+ 'wgCookiePrefix' => $wgCookiePrefix,
'wgResourceLoaderMaxQueryLength' => $wgResourceLoaderMaxQueryLength,
+ 'wgLegacyJavaScriptGlobals' => $wgLegacyJavaScriptGlobals,
+ 'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
);
- 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
*
@@ -102,7 +128,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
public static function getModuleRegistrations( ResourceLoaderContext $context ) {
global $wgCacheEpoch;
wfProfileIn( __METHOD__ );
-
+
$out = '';
$registrations = array();
$resourceLoader = $context->getResourceLoader();
@@ -113,8 +139,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
if ( $loader !== false ) {
$deps = $module->getDependencies();
$group = $module->getGroup();
- $version = wfTimestamp( TS_ISO_8601_BASIC,
- round( $module->getModifiedTime( $context ), -2 ) );
+ $version = wfTimestamp( TS_ISO_8601_BASIC,
+ $module->getModifiedTime( $context ) );
$out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $loader );
}
// Automatically register module
@@ -123,19 +149,19 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
// seem to do that, and custom implementations might forget. Coerce it to TS_UNIX
$moduleMtime = wfTimestamp( TS_UNIX, $module->getModifiedTime( $context ) );
$mtime = max( $moduleMtime, wfTimestamp( TS_UNIX, $wgCacheEpoch ) );
- // Modules without dependencies or a group pass two arguments (name, timestamp) to
- // mediaWiki.loader.register()
+ // Modules without dependencies or a group pass two arguments (name, timestamp) to
+ // mw.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 ) {
+ // Modules with dependencies but no group pass three arguments
+ // (name, timestamp, dependencies) to mw.loader.register()
+ elseif ( $module->getGroup() === null ) {
$registrations[] = array(
$name, $mtime, $module->getDependencies() );
}
- // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
- // to mediaWiki.loader.register()
+ // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
+ // to mw.loader.register()
else {
$registrations[] = array(
$name, $mtime, $module->getDependencies(), $module->getGroup() );
@@ -143,52 +169,74 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
}
}
$out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
-
+
wfProfileOut( __METHOD__ );
return $out;
}
/* Methods */
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string
+ */
public function getScript( ResourceLoaderContext $context ) {
- global $IP, $wgLoadScript;
+ global $IP, $wgLoadScript, $wgLegacyJavaScriptGlobals;
$out = file_get_contents( "$IP/resources/startup.js" );
if ( $context->getOnly() === 'scripts' ) {
- // Build load query for jquery and mediawiki modules
+
+ // The core modules:
+ $modules = array( 'jquery', 'mediawiki' );
+ wfRunHooks( 'ResourceLoaderGetStartupModules', array( &$modules ) );
+
+ // Get the latest version
+ $version = 0;
+ foreach ( $modules as $moduleName ) {
+ $version = max( $version,
+ $context->getResourceLoader()->getModule( $moduleName )->getModifiedTime( $context )
+ );
+ }
+ // Build load query for StartupModules
$query = array(
- 'modules' => implode( '|', array( 'jquery', 'mediawiki' ) ),
+ 'modules' => ResourceLoader::makePackedModulesString( $modules ),
'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 ) )
+ 'version' => wfTimestamp( TS_ISO_8601_BASIC, $version )
);
// 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 ) ) .
+ $out .= "var startUp = function() {\n" .
+ "\tmw.config = new " . Xml::encodeJsCall( 'mw.Map', array( $wgLegacyJavaScriptGlobals ) ) . "\n" .
+ "\t$registrations\n" .
+ "\t" . Xml::encodeJsCall( 'mw.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" .
+ $out .= "if ( isCompatible() ) {\n" .
+ "\t" . Xml::encodeJsCall( 'document.write', array( $scriptTag ) ) .
+ "}\n" .
"delete isCompatible;";
}
return $out;
}
+ public function supportsURLLoading() {
+ return false;
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array|mixed
+ */
public function getModifiedTime( ResourceLoaderContext $context ) {
global $IP, $wgCacheEpoch;
@@ -204,7 +252,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
$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
+ // infinite recursion - think carefully before making changes to this
// code!
$time = wfTimestamp( TS_UNIX, $wgCacheEpoch );
foreach ( $loader->getModuleNames() as $name ) {
@@ -214,14 +262,11 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
return $this->modifiedTime[$hash] = $time;
}
- public function getFlip( $context ) {
- global $wgContLang;
-
- return $wgContLang->getDir() !== $context->getDirection();
- }
-
/* Methods */
-
+
+ /**
+ * @return string
+ */
public function getGroup() {
return 'startup';
}
diff --git a/includes/resourceloader/ResourceLoaderUserGroupsModule.php b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
new file mode 100644
index 00000000..733dfa04
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderUserGroupsModule.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Module for user customizations
+ */
+class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
+
+ /* Protected Methods */
+ protected $origin = self::ORIGIN_USER_SITEWIDE;
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
+ protected function getPages( ResourceLoaderContext $context ) {
+ if ( $context->getUser() ) {
+ $user = User::newFromName( $context->getUser() );
+ if ( $user instanceof User ) {
+ $pages = array();
+ foreach( $user->getEffectiveGroups() as $group ) {
+ if ( in_array( $group, array( '*', 'user' ) ) ) {
+ continue;
+ }
+ $pages["MediaWiki:Group-$group.js"] = array( 'type' => 'script' );
+ $pages["MediaWiki:Group-$group.css"] = array( 'type' => 'style' );
+ }
+ return $pages;
+ }
+ }
+ return array();
+ }
+
+ /* Methods */
+
+ /**
+ * @return string
+ */
+ public function getGroup() {
+ return 'user';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php
index c7186653..892e8462 100644
--- a/includes/resourceloader/ResourceLoaderUserModule.php
+++ b/includes/resourceloader/ResourceLoaderUserModule.php
@@ -26,7 +26,12 @@
class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
/* Protected Methods */
+ protected $origin = self::ORIGIN_USER_INDIVIDUAL;
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
protected function getPages( ResourceLoaderContext $context ) {
if ( $context->getUser() ) {
$username = $context->getUser();
@@ -41,9 +46,12 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
}
return array();
}
-
+
/* Methods */
-
+
+ /**
+ * @return string
+ */
public function getGroup() {
return 'user';
}
diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
index 61c76940..8f28eb8d 100644
--- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
@@ -29,8 +29,14 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
protected $modifiedTime = array();
+ protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
+
/* Methods */
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array|int|Mixed
+ */
public function getModifiedTime( ResourceLoaderContext $context ) {
$hash = $context->getHash();
if ( isset( $this->modifiedTime[$hash] ) ) {
@@ -64,11 +70,19 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
}
}
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string
+ */
public function getScript( ResourceLoaderContext $context ) {
- return Xml::encodeJsCall( 'mediaWiki.user.options.set',
+ return Xml::encodeJsCall( 'mw.user.options.set',
array( $this->contextUserOptions( $context ) ) );
}
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
public function getStyles( ResourceLoaderContext $context ) {
global $wgAllowUserCssPrefs;
@@ -80,6 +94,10 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
if ( $options['underline'] < 2 ) {
$rules[] = "a { text-decoration: " .
( $options['underline'] ? 'underline' : 'none' ) . "; }";
+ } else {
+ # The scripts of these languages are very hard to read with underlines
+ $rules[] = 'a:lang(ar), a:lang(ckb), a:lang(fa),a:lang(kk-arab), ' .
+ 'a:lang(mzn), a:lang(ps), a:lang(ur) { text-decoration: none; }';
}
if ( $options['highlightbroken'] ) {
$rules[] = "a.new, #quickbar a.new { color: #ba0000; }\n";
@@ -109,12 +127,9 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
return array();
}
- public function getFlip( $context ) {
- global $wgContLang;
-
- return $wgContLang->getDir() !== $context->getDirection();
- }
-
+ /**
+ * @return string
+ */
public function getGroup() {
return 'private';
}
diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php
new file mode 100644
index 00000000..9403534c
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderUserTokensModule.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 Krinkle
+ */
+
+/**
+ * Module for user tokens
+ */
+class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
+
+ /* Protected Members */
+
+ protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
+
+ /* Methods */
+
+ /**
+ * Fetch the tokens for the current user.
+ *
+ * @param $context ResourceLoaderContext: Context object
+ * @return Array: List of tokens keyed by token type
+ */
+ protected function contextUserTokens( ResourceLoaderContext $context ) {
+ global $wgUser;
+
+ return array(
+ 'editToken' => $wgUser->edittoken(),
+ 'watchToken' => ApiQueryInfo::getWatchToken(null, null),
+ );
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string
+ */
+ public function getScript( ResourceLoaderContext $context ) {
+ return Xml::encodeJsCall( 'mw.user.tokens.set',
+ array( $this->contextUserTokens( $context ) ) );
+ }
+
+ /**
+ * @return string
+ */
+ public function getGroup() {
+ return 'private';
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
index 93e66eb0..bad61cb9 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -32,6 +32,9 @@ defined( 'MEDIAWIKI' ) || die( 1 );
abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
/* Protected Members */
+
+ # Origin is user-supplied code
+ protected $origin = self::ORIGIN_USER_SITEWIDE;
// In-object cache for title mtimes
protected $titleMtimes = array();
@@ -41,11 +44,15 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
abstract protected function getPages( ResourceLoaderContext $context );
/* Protected Methods */
-
+
+ /**
+ * @param $title Title
+ * @return null|string
+ */
protected function getContent( $title ) {
if ( $title->getNamespace() === NS_MEDIAWIKI ) {
- $dbkey = $title->getDBkey();
- return wfEmptyMsg( $dbkey ) ? '' : wfMsgExt( $dbkey, 'content' );
+ $message = wfMessage( $title->getDBkey() )->inContentLanguage();
+ return $message->exists() ? $message->plain() : '';
}
if ( !$title->isCssJsSubpage() ) {
return null;
@@ -59,6 +66,10 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
/* Methods */
+ /**
+ * @param $context ResourceLoaderContext
+ * @return string
+ */
public function getScript( ResourceLoaderContext $context ) {
$scripts = '';
foreach ( $this->getPages( $context ) as $titleText => $options ) {
@@ -66,11 +77,12 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
continue;
}
$title = Title::newFromText( $titleText );
- if ( !$title ) {
+ if ( !$title || $title->isRedirect() ) {
continue;
}
$script = $this->getContent( $title );
if ( strval( $script ) !== '' ) {
+ $script = $this->validateScriptFile( $titleText, $script );
if ( strpos( $titleText, '*/' ) === false ) {
$scripts .= "/* $titleText */\n";
}
@@ -80,6 +92,10 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
return $scripts;
}
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
public function getStyles( ResourceLoaderContext $context ) {
global $wgScriptPath;
@@ -89,7 +105,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
continue;
}
$title = Title::newFromText( $titleText );
- if ( !$title ) {
+ if ( !$title || $title->isRedirect() ) {
continue;
}
$media = isset( $options['media'] ) ? $options['media'] : 'all';
@@ -112,6 +128,10 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
return $styles;
}
+ /**
+ * @param $context ResourceLoaderContext
+ * @return int|mixed
+ */
public function getModifiedTime( ResourceLoaderContext $context ) {
$modifiedTime = 1; // wfTimestamp() interprets 0 as "now"
$mtimes = $this->getTitleMtimes( $context );
@@ -120,21 +140,15 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
}
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();
+ public function isKnownEmpty( ResourceLoaderContext $context ) {
+ return count( $this->getTitleMtimes( $context ) ) == 0;
}
-
+
/**
* Get the modification times of all titles that would be loaded for
* a given context.
diff --git a/includes/revisiondelete/RevisionDelete.php b/includes/revisiondelete/RevisionDelete.php
index 00afb053..b329fc4b 100644
--- a/includes/revisiondelete/RevisionDelete.php
+++ b/includes/revisiondelete/RevisionDelete.php
@@ -1,18 +1,31 @@
<?php
/**
* List for revision table items
+ *
+ * This will check both the 'revision' table for live revisions and the
+ * 'archive' table for traditionally-deleted revisions that have an
+ * ar_rev_id saved.
+ *
+ * See RevDel_RevisionItem and RevDel_ArchivedRevisionItem for items.
*/
class RevDel_RevisionList extends RevDel_List {
var $currentRevId;
- var $type = 'revision';
- var $idField = 'rev_id';
- var $dateField = 'rev_timestamp';
- var $authorIdField = 'rev_user';
- var $authorNameField = 'rev_user_text';
+ public function getType() {
+ return 'revision';
+ }
+
+ public static function getRelationType() {
+ return 'rev_id';
+ }
+
+ /**
+ * @param $db DatabaseBase
+ * @return mixed
+ */
public function doQuery( $db ) {
$ids = array_map( 'intval', $this->ids );
- return $db->select( array('revision','page'), '*',
+ $live = $db->select( array('revision','page'), '*',
array(
'rev_page' => $this->title->getArticleID(),
'rev_id' => $ids,
@@ -21,16 +34,54 @@ class RevDel_RevisionList extends RevDel_List {
__METHOD__,
array( 'ORDER BY' => 'rev_id DESC' )
);
+
+ if ( $live->numRows() >= count( $ids ) ) {
+ // All requested revisions are live, keeps things simple!
+ return $live;
+ }
+
+ // Check if any requested revisions are available fully deleted.
+ $archived = $db->select( array( 'archive' ), '*',
+ array(
+ 'ar_rev_id' => $ids
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'ar_rev_id DESC' )
+ );
+
+ if ( $archived->numRows() == 0 ) {
+ return $live;
+ } elseif ( $live->numRows() == 0 ) {
+ return $archived;
+ } else {
+ // Combine the two! Whee
+ $rows = array();
+ foreach ( $live as $row ) {
+ $rows[$row->rev_id] = $row;
+ }
+ foreach ( $archived as $row ) {
+ $rows[$row->ar_rev_id] = $row;
+ }
+ krsort( $rows );
+ return new FakeResultWrapper( array_values( $rows ) );
+ }
}
public function newItem( $row ) {
- return new RevDel_RevisionItem( $this, $row );
+ if ( isset( $row->rev_id ) ) {
+ return new RevDel_RevisionItem( $this, $row );
+ } elseif ( isset( $row->ar_rev_id ) ) {
+ return new RevDel_ArchivedRevisionItem( $this, $row );
+ } else {
+ // This shouldn't happen. :)
+ throw new MWException( 'Invalid row type in RevDel_RevisionList' );
+ }
}
public function getCurrent() {
if ( is_null( $this->currentRevId ) ) {
$dbw = wfGetDB( DB_MASTER );
- $this->currentRevId = $dbw->selectField(
+ $this->currentRevId = $dbw->selectField(
'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
}
return $this->currentRevId;
@@ -54,7 +105,7 @@ class RevDel_RevisionList extends RevDel_List {
}
/**
- * Item class for a revision table row
+ * Item class for a live revision table row
*/
class RevDel_RevisionItem extends RevDel_Item {
var $revision;
@@ -64,10 +115,26 @@ class RevDel_RevisionItem extends RevDel_Item {
$this->revision = new Revision( $row );
}
+ public function getIdField() {
+ return 'rev_id';
+ }
+
+ public function getTimestampField() {
+ return 'rev_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'rev_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'rev_user_text';
+ }
+
public function canView() {
return $this->revision->userCan( Revision::DELETED_RESTRICTED );
}
-
+
public function canViewContent() {
return $this->revision->userCan( Revision::DELETED_TEXT );
}
@@ -81,8 +148,8 @@ class RevDel_RevisionItem extends RevDel_Item {
// Update revision table
$dbw->update( 'revision',
array( 'rev_deleted' => $bits ),
- array(
- 'rev_id' => $this->revision->getId(),
+ array(
+ 'rev_id' => $this->revision->getId(),
'rev_page' => $this->revision->getPage(),
'rev_deleted' => $this->getBits()
),
@@ -94,7 +161,7 @@ class RevDel_RevisionItem extends RevDel_Item {
}
// Update recentchanges table
$dbw->update( 'recentchanges',
- array(
+ array(
'rc_deleted' => $bits,
'rc_patrolled' => 1
),
@@ -113,7 +180,7 @@ class RevDel_RevisionItem extends RevDel_Item {
}
public function isHideCurrentOp( $newBits ) {
- return ( $newBits & Revision::DELETED_TEXT )
+ return ( $newBits & Revision::DELETED_TEXT )
&& $this->list->getCurrent() == $this->getId();
}
@@ -122,14 +189,13 @@ class RevDel_RevisionItem extends RevDel_Item {
* Overridden by RevDel_ArchiveItem.
*/
protected function getRevisionLink() {
- global $wgLang;
- $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
+ $date = $this->list->getLang()->timeanddate( $this->revision->getTimestamp(), true );
if ( $this->isDeleted() && !$this->canViewContent() ) {
return $date;
}
- return $this->special->skin->link(
+ return Linker::link(
$this->list->title,
- $date,
+ $date,
array(),
array(
'oldid' => $this->revision->getId(),
@@ -146,9 +212,9 @@ class RevDel_RevisionItem extends RevDel_Item {
if ( $this->isDeleted() && !$this->canViewContent() ) {
return wfMsgHtml('diff');
} else {
- return
- $this->special->skin->link(
- $this->list->title,
+ return
+ Linker::link(
+ $this->list->title,
wfMsgHtml('diff'),
array(),
array(
@@ -167,8 +233,8 @@ class RevDel_RevisionItem extends RevDel_Item {
public function getHTML() {
$difflink = $this->getDiffLink();
$revlink = $this->getRevisionLink();
- $userlink = $this->special->skin->revUserLink( $this->revision );
- $comment = $this->special->skin->revComment( $this->revision );
+ $userlink = Linker::revUserLink( $this->revision );
+ $comment = Linker::revComment( $this->revision );
if ( $this->isDeleted() ) {
$revlink = "<span class=\"history-deleted\">$revlink</span>";
}
@@ -180,12 +246,18 @@ class RevDel_RevisionItem extends RevDel_Item {
* 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 getType() {
+ return 'archive';
+ }
+
+ public static function getRelationType() {
+ return 'ar_timestamp';
+ }
+ /**
+ * @param $db DatabaseBase
+ * @return mixed
+ */
public function doQuery( $db ) {
$timestamps = array();
foreach ( $this->ids as $id ) {
@@ -225,34 +297,50 @@ class RevDel_ArchiveItem extends RevDel_RevisionItem {
array( 'page' => $this->list->title->getArticleId() ) );
}
+ public function getIdField() {
+ return 'ar_timestamp';
+ }
+
+ public function getTimestampField() {
+ return 'ar_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'ar_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'ar_user_text';
+ }
+
public function getId() {
# Convert DB timestamp to MW timestamp
return $this->revision->getTimestamp();
}
-
+
public function setBits( $bits ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'archive',
array( 'ar_deleted' => $bits ),
- array( 'ar_namespace' => $this->list->title->getNamespace(),
- 'ar_title' => $this->list->title->getDBkey(),
+ 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()
+ '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 );
+ $date = $this->list->getLang()->timeanddate( $this->revision->getTimestamp(), true );
if ( $this->isDeleted() && !$this->canViewContent() ) {
return $date;
}
- return $this->special->skin->link( $undelete, $date, array(),
+ return Linker::link( $undelete, $date, array(),
array(
'target' => $this->list->title->getPrefixedText(),
'timestamp' => $this->revision->getTimestamp()
@@ -263,8 +351,8 @@ class RevDel_ArchiveItem extends RevDel_RevisionItem {
if ( $this->isDeleted() && !$this->canViewContent() ) {
return wfMsgHtml( 'diff' );
}
- $undelete = SpecialPage::getTitleFor( 'Undelete' );
- return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ return Linker::link( $undelete, wfMsgHtml('diff'), array(),
array(
'target' => $this->list->title->getPrefixedText(),
'diff' => 'prev',
@@ -273,17 +361,57 @@ class RevDel_ArchiveItem extends RevDel_RevisionItem {
}
}
+
+/**
+ * Item class for a archive table row by ar_rev_id -- actually
+ * used via RevDel_RevisionList.
+ */
+class RevDel_ArchivedRevisionItem extends RevDel_ArchiveItem {
+ public function __construct( $list, $row ) {
+ RevDel_Item::__construct( $list, $row );
+
+ $this->revision = Revision::newFromArchiveRow( $row,
+ array( 'page' => $this->list->title->getArticleId() ) );
+ }
+
+ public function getIdField() {
+ return 'ar_rev_id';
+ }
+
+ public function getId() {
+ return $this->revision->getId();
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'archive',
+ array( 'ar_deleted' => $bits ),
+ array( 'ar_rev_id' => $this->row->ar_rev_id,
+ 'ar_deleted' => $this->getBits()
+ ),
+ __METHOD__ );
+ return (bool)$dbw->affectedRows();
+ }
+}
+
/**
* List for oldimage table items
*/
class RevDel_FileList extends RevDel_List {
- var $type = 'oldimage';
- var $idField = 'oi_archive_name';
- var $dateField = 'oi_timestamp';
- var $authorIdField = 'oi_user';
- var $authorNameField = 'oi_user_text';
+ public function getType() {
+ return 'oldimage';
+ }
+
+ public static function getRelationType() {
+ return 'oi_archive_name';
+ }
+
var $storeBatch, $deleteBatch, $cleanupBatch;
+ /**
+ * @param $db DatabaseBase
+ * @return mixed
+ */
public function doQuery( $db ) {
$archiveNames = array();
foreach( $this->ids as $timestamp ) {
@@ -348,6 +476,10 @@ class RevDel_FileList extends RevDel_List {
* Item class for an oldimage table row
*/
class RevDel_FileItem extends RevDel_Item {
+
+ /**
+ * @var File
+ */
var $file;
public function __construct( $list, $row ) {
@@ -355,6 +487,22 @@ class RevDel_FileItem extends RevDel_Item {
$this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
}
+ public function getIdField() {
+ return 'oi_archive_name';
+ }
+
+ public function getTimestampField() {
+ return 'oi_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'oi_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'oi_user_text';
+ }
+
public function getId() {
$parts = explode( '!', $this->row->oi_archive_name );
return $parts[0];
@@ -363,7 +511,7 @@ class RevDel_FileItem extends RevDel_Item {
public function canView() {
return $this->file->userCan( File::DELETED_RESTRICTED );
}
-
+
public function canViewContent() {
return $this->file->userCan( File::DELETED_FILE );
}
@@ -374,7 +522,7 @@ class RevDel_FileItem extends RevDel_Item {
public function setBits( $bits ) {
# Queue the file op
- # FIXME: move to LocalFile.php
+ # @todo FIXME: Move to LocalFile.php
if ( $this->isDeleted() ) {
if ( $bits & File::DELETED_FILE ) {
# Still deleted
@@ -395,12 +543,12 @@ class RevDel_FileItem extends RevDel_Item {
$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(
+ array(
'oi_name' => $this->row->oi_name,
'oi_timestamp' => $this->row->oi_timestamp,
'oi_deleted' => $this->getBits()
@@ -415,24 +563,25 @@ class RevDel_FileItem extends RevDel_Item {
}
/**
- * Get the link to the file.
+ * Get the link to the file.
* Overridden by RevDel_ArchivedFileItem.
*/
protected function getLink() {
- global $wgLang, $wgUser;
- $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
+ $date = $this->list->getLang()->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(),
+ $revdelete = SpecialPage::getTitleFor( 'Revisiondelete' );
+ $link = Linker::link(
+ $revdelete,
+ $date, array(),
array(
'target' => $this->list->title->getPrefixedText(),
'file' => $this->file->getArchiveName(),
- 'token' => $wgUser->editToken( $this->file->getArchiveName() )
+ 'token' => $this->list->getUser()->editToken(
+ $this->file->getArchiveName() )
)
);
}
@@ -448,8 +597,8 @@ class RevDel_FileItem extends RevDel_Item {
*/
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 );
+ $link = Linker::userLink( $this->file->user, $this->file->user_text ) .
+ Linker::userToolLinks( $this->file->user, $this->file->user_text );
} else {
$link = wfMsgHtml( 'rev-deleted-user' );
}
@@ -467,7 +616,7 @@ class RevDel_FileItem extends RevDel_Item {
*/
protected function getComment() {
if( $this->file->userCan( File::DELETED_COMMENT ) ) {
- $block = $this->special->skin->commentBlock( $this->file->description );
+ $block = Linker::commentBlock( $this->file->description );
} else {
$block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
}
@@ -478,15 +627,14 @@ class RevDel_FileItem extends RevDel_Item {
}
public function getHTML() {
- global $wgLang;
- $data =
+ $data =
wfMsg(
- 'widthheight',
- $wgLang->formatNum( $this->file->getWidth() ),
- $wgLang->formatNum( $this->file->getHeight() )
+ 'widthheight',
+ $this->list->getLang()->formatNum( $this->file->getWidth() ),
+ $this->list->getLang()->formatNum( $this->file->getHeight() )
) .
- ' (' .
- wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
+ ' (' .
+ wfMsgExt( 'nbytes', 'parsemag', $this->list->getLang()->formatNum( $this->file->getSize() ) ) .
')';
return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
@@ -498,12 +646,18 @@ class RevDel_FileItem extends RevDel_Item {
* 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 getType() {
+ return 'filearchive';
+ }
+
+ public static function getRelationType() {
+ return 'fa_id';
+ }
+
+ /**
+ * @param $db DatabaseBase
+ * @return mixed
+ */
public function doQuery( $db ) {
$ids = array_map( 'intval', $this->ids );
return $db->select( 'filearchive', '*',
@@ -530,6 +684,22 @@ class RevDel_ArchivedFileItem extends RevDel_FileItem {
$this->file = ArchivedFile::newFromRow( $row );
}
+ public function getIdField() {
+ return 'fa_id';
+ }
+
+ public function getTimestampField() {
+ return 'fa_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'fa_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'fa_user_text';
+ }
+
public function getId() {
return $this->row->fa_id;
}
@@ -548,19 +718,18 @@ class RevDel_ArchivedFileItem extends RevDel_FileItem {
}
protected function getLink() {
- global $wgLang, $wgUser;
- $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
+ $date = $this->list->getLang()->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(),
+ $link = Linker::link( $undelete, $date, array(),
array(
'target' => $this->list->title->getPrefixedText(),
'file' => $key,
- 'token' => $wgUser->editToken( $key )
+ 'token' => $this->list->getUser()->editToken( $key )
)
);
}
@@ -575,12 +744,18 @@ class RevDel_ArchivedFileItem extends RevDel_FileItem {
* 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 getType() {
+ return 'logging';
+ }
+
+ public static function getRelationType() {
+ return 'log_id';
+ }
+ /**
+ * @param $db DatabaseBase
+ * @return mixed
+ */
public function doQuery( $db ) {
$ids = array_map( 'intval', $this->ids );
return $db->select( 'logging', '*',
@@ -615,10 +790,26 @@ class RevDel_LogList extends RevDel_List {
* Item class for a logging table row
*/
class RevDel_LogItem extends RevDel_Item {
+ public function getIdField() {
+ return 'log_id';
+ }
+
+ public function getTimestampField() {
+ return 'log_timestamp';
+ }
+
+ public function getAuthorIdField() {
+ return 'log_user';
+ }
+
+ public function getAuthorNameField() {
+ return 'log_user_text';
+ }
+
public function canView() {
return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
}
-
+
public function canViewContent() {
return true; // none
}
@@ -630,9 +821,9 @@ class RevDel_LogItem extends RevDel_Item {
public function setBits( $bits ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'recentchanges',
- array(
- 'rc_deleted' => $bits,
- 'rc_patrolled' => 1
+ array(
+ 'rc_deleted' => $bits,
+ 'rc_patrolled' => 1
),
array(
'rc_logid' => $this->row->log_id,
@@ -652,14 +843,12 @@ class RevDel_LogItem extends RevDel_Item {
}
public function getHTML() {
- global $wgLang;
-
- $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
+ $date = htmlspecialchars( $this->list->getLang()->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(
+ $loglink = Linker::link(
SpecialPage::getTitleFor( 'Log' ),
wfMsgHtml( 'log' ),
array(),
@@ -669,19 +858,20 @@ class RevDel_LogItem extends RevDel_Item {
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 );
+ $skin = $this->list->getUser()->getSkin();
+ $action = LogPage::actionText( $this->row->log_type, $this->row->log_action,
+ $title, $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,
+ $userLink = Linker::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 );
+ $comment = $this->list->getLang()->getDirMark() . Linker::commentBlock( $this->row->log_comment );
if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
$comment = '<span class="history-deleted">' . $comment . '</span>';
}
diff --git a/includes/revisiondelete/RevisionDeleteAbstracts.php b/includes/revisiondelete/RevisionDeleteAbstracts.php
index 073c25ba..73af1e5f 100644
--- a/includes/revisiondelete/RevisionDeleteAbstracts.php
+++ b/includes/revisiondelete/RevisionDeleteAbstracts.php
@@ -1,63 +1,28 @@
<?php
/**
- * Abstract base class for a list of deletable items
+ * Abstract base class for a list of deletable items. The list class
+ * needs to be able to make a query from a set of identifiers to pull
+ * relevant rows, to return RevDel_Item subclasses wrapping them, and
+ * to wrap bulk update operations.
*/
-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;
+abstract class RevDel_List extends Rev_List {
+ function __construct( IContextSource $context, Title $title, array $ids ) {
+ parent::__construct( $context, $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
+ * Get the DB field name associated with the ID list.
+ * This used to populate the log_search table for finding log entries.
+ * Override this function.
*/
- public function getTimestampField() {
- return $this->dateField;
+ public static function getRelationType() {
+ return null;
}
/**
- * 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
+ * Set the visibility for the revisions in this list. Logging and
* transactions are done here.
*
* @param $params Associative array of parameters. Members are:
@@ -118,7 +83,7 @@ abstract class RevDel_List {
$status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
$status->failCount++;
continue;
- }
+ }
// Update the revision
$ok = $item->setBits( $newBits );
@@ -128,7 +93,7 @@ abstract class RevDel_List {
$status->successCount++;
if( $item->getAuthorId() > 0 ) {
$authorIds[] = $item->getAuthorId();
- } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
+ } elseif( IP::isIPAddress( $item->getAuthorName() ) ) {
$authorIPs[] = $item->getAuthorName();
}
} else {
@@ -149,7 +114,7 @@ abstract class RevDel_List {
return $status;
}
- // Save success count
+ // Save success count
$successCount = $status->successCount;
// Move files, if there are any
@@ -162,9 +127,9 @@ abstract class RevDel_List {
// Log it
$this->updateLog( array(
- 'title' => $this->title,
- 'count' => $successCount,
- 'newBits' => $newBits,
+ 'title' => $this->title,
+ 'count' => $successCount,
+ 'newBits' => $newBits,
'oldBits' => $oldBits,
'comment' => $comment,
'ids' => $idsForLog,
@@ -191,7 +156,7 @@ abstract class RevDel_List {
* 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.
+ * oldBits: The old value of the *_deleted bitfield.
* title: The target title
* ids: The ID list
* comment: The log comment
@@ -244,66 +209,13 @@ abstract class RevDel_List {
}
/**
- * 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
@@ -313,27 +225,15 @@ abstract class RevDel_List {
}
/**
- * A hook for setVisibility(): do any necessary updates post-commit.
+ * A hook for setVisibility(): do any necessary updates post-commit.
* STUB
- * @return Status
+ * @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();
@@ -342,75 +242,8 @@ abstract class RevDel_List {
/**
* 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;
- }
-
+abstract class RevDel_Item extends Rev_Item {
/**
- * 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
@@ -420,32 +253,16 @@ abstract class RevDel_Item {
}
/**
- * 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
+ * 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
diff --git a/includes/revisiondelete/RevisionDeleteUser.php b/includes/revisiondelete/RevisionDeleteUser.php
new file mode 100644
index 00000000..c88b4d91
--- /dev/null
+++ b/includes/revisiondelete/RevisionDeleteUser.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Backend functions for suppressing and unsuppressing all references to a given user,
+ * used when blocking with HideUser enabled. This was spun out of SpecialBlockip.php
+ * in 1.18; at some point it needs to be rewritten to either use RevisionDelete abstraction,
+ * or at least schema abstraction.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup RevisionDelete
+ */
+class RevisionDeleteUser {
+
+ /**
+ * Update *_deleted bitfields in various tables to hide or unhide usernames
+ * @param $name String username
+ * @param $userId Int user id
+ * @param $op String operator '|' or '&'
+ * @param $dbw null|Database, if you happen to have one lying around
+ * @return bool
+ */
+ private static function setUsernameBitfields( $name, $userId, $op, $dbw ) {
+ if( $op !== '|' && $op !== '&' ){
+ return false; // sanity check
+ }
+ if( !$dbw instanceof DatabaseBase ){
+ $dbw = wfGetDB( DB_MASTER );
+ }
+
+ # To suppress, we OR the current bitfields with Revision::DELETED_USER
+ # to put a 1 in the username *_deleted bit. To unsuppress we AND the
+ # current bitfields with the inverse of Revision::DELETED_USER. The
+ # username bit is made to 0 (x & 0 = 0), while others are unchanged (x & 1 = x).
+ # The same goes for the sysop-restricted *_deleted bit.
+ $delUser = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
+ $delAction = LogPage::DELETED_ACTION | Revision::DELETED_RESTRICTED;
+ if( $op == '&' ) {
+ $delUser = "~{$delUser}";
+ $delAction = "~{$delAction}";
+ }
+
+ # Normalize user name
+ $userTitle = Title::makeTitleSafe( NS_USER, $name );
+ $userDbKey = $userTitle->getDBkey();
+
+ # Hide name from live edits
+ $dbw->update(
+ 'revision',
+ array( "rev_deleted = rev_deleted $op $delUser" ),
+ array( 'rev_user' => $userId ),
+ __METHOD__ );
+
+ # Hide name from deleted edits
+ $dbw->update(
+ 'archive',
+ array( "ar_deleted = ar_deleted $op $delUser" ),
+ array( 'ar_user_text' => $name ),
+ __METHOD__
+ );
+
+ # Hide name from logs
+ $dbw->update(
+ 'logging',
+ array( "log_deleted = log_deleted $op $delUser" ),
+ array( 'log_user' => $userId, "log_type != 'suppress'" ),
+ __METHOD__
+ );
+ $dbw->update(
+ 'logging',
+ array( "log_deleted = log_deleted $op $delAction" ),
+ array( 'log_namespace' => NS_USER, 'log_title' => $userDbKey,
+ "log_type != 'suppress'" ),
+ __METHOD__
+ );
+
+ # Hide name from RC
+ $dbw->update(
+ 'recentchanges',
+ array( "rc_deleted = rc_deleted $op $delUser" ),
+ array( 'rc_user_text' => $name ),
+ __METHOD__
+ );
+ $dbw->update(
+ 'recentchanges',
+ array( "rc_deleted = rc_deleted $op $delAction" ),
+ array( 'rc_namespace' => NS_USER, 'rc_title' => $userDbKey, 'rc_logid > 0' ),
+ __METHOD__
+ );
+
+ # Hide name from live images
+ $dbw->update(
+ 'oldimage',
+ array( "oi_deleted = oi_deleted $op $delUser" ),
+ array( 'oi_user_text' => $name ),
+ __METHOD__
+ );
+
+ # Hide name from deleted images
+ $dbw->update(
+ 'filearchive',
+ array( "fa_deleted = fa_deleted $op $delUser" ),
+ array( 'fa_user_text' => $name ),
+ __METHOD__
+ );
+ # Done!
+ return true;
+ }
+
+ public static function suppressUserName( $name, $userId, $dbw = null ) {
+ return self::setUsernameBitfields( $name, $userId, '|', $dbw );
+ }
+
+ public static function unsuppressUserName( $name, $userId, $dbw = null ) {
+ return self::setUsernameBitfields( $name, $userId, '&', $dbw );
+ }
+} \ No newline at end of file
diff --git a/includes/revisiondelete/RevisionDeleter.php b/includes/revisiondelete/RevisionDeleter.php
index d47fcecf..bde586c5 100644
--- a/includes/revisiondelete/RevisionDeleter.php
+++ b/includes/revisiondelete/RevisionDeleter.php
@@ -18,7 +18,7 @@ class RevisionDeleter {
* 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 $new Integer: the new bitfield
* @param $arr Array: the array to update.
*/
protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
@@ -29,10 +29,10 @@ class RevisionDeleter {
/**
* 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
+ * 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.
@@ -67,48 +67,39 @@ class RevisionDeleter {
* @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 $language Language object to use
* @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";
-
+ public static function getLogMessage( $count, $nbitfield, $obitfield, $language, $isForLog = false ) {
$changes = self::getChanges( $nbitfield, $obitfield );
- array_walk($changes, 'RevisionDeleter::expandMessageArray', $forContent);
-
+ array_walk( $changes, array( __CLASS__, 'expandMessageArray' ), $language );
+
$changesText = array();
-
+
if( count( $changes[0] ) ) {
- $changesText[] = $msgFunc( 'revdelete-hid', $lang->commaList( $changes[0] ) );
+ $changesText[] = wfMsgExt( 'revdelete-hid', array( 'parsemag', 'language' => $language ), $language->commaList( $changes[0] ) );
}
if( count( $changes[1] ) ) {
- $changesText[] = $msgFunc( 'revdelete-unhid', $lang->commaList( $changes[1] ) );
+ $changesText[] = wfMsgExt( 'revdelete-unhid', array( 'parsemag', 'language' => $language ), $language->commaList( $changes[1] ) );
}
-
- $s = $lang->semicolonList( $changesText );
+
+ $s = $language->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) );
+ return wfMsgExt( $msg, array( 'parsemag', 'language' => $language ), $s, $language->formatNum($count) );
}
-
- private static function expandMessageArray(& $msg, $key, $forContent) {
- if ( is_array ($msg) ) {
- array_walk($msg, 'RevisionDeleter::expandMessageArray', $forContent);
+
+ private static function expandMessageArray( &$msg, $key, $language ) {
+ if ( is_array ( $msg ) ) {
+ array_walk( $msg, array( __CLASS__, 'expandMessageArray' ), $language );
} else {
- if ( $forContent ) {
- $msg = wfMsgForContent($msg);
- } else {
- $msg = wfMsg($msg);
- }
+ $msg = wfMsgExt( $msg, array( 'parsemag', 'language' => $language ) );
}
}
-
+
// Get DB field name for URL param...
// Future code for other things may also track
// other types of revision-specific changes.
@@ -119,75 +110,62 @@ class RevisionDeleter {
}
if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
$class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
- $list = new $class( null, null, null );
- return $list->getIdField();
+ return call_user_func( array( $class, 'getRelationType' ) );
} 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.
+
+ /**
+ * Checks if a revision still exists in the revision table.
+ * If it doesn't, returns the corresponding ar_timestamp field
+ * so that this key can be used instead.
+ *
+ * @param $title Title
+ * @param $revid
+ * @return bool|mixed
+ */
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.
+
+ /**
+ * Creates utility links for log entries.
+ *
+ * @param $title Title
+ * @param $paramArray Array
+ * @param $skin Skin
+ * @param $messages
+ * @return String
+ */
public static function getLogLinks( $title, $paramArray, $skin, $messages ) {
global $wgLang;
-
- if( count($paramArray) >= 2 ) {
+
+ 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) ) {
+ if ( count( $Ids ) == 1 ) {
// Live revision diffs...
- if( in_array( $key, array( 'oldid', 'revision' ) ) ) {
+ if ( in_array( $key, array( 'oldid', 'revision' ) ) ) {
$revert[] = $skin->link(
$title,
$messages['diff'],
@@ -199,10 +177,10 @@ class RevisionDeleter {
array( 'known', 'noclasses' )
);
// Deleted revision diffs...
- } else if( in_array( $key, array( 'artimestamp','archive' ) ) ) {
+ } elseif ( in_array( $key, array( 'artimestamp','archive' ) ) ) {
$revert[] = $skin->link(
SpecialPage::getTitleFor( 'Undelete' ),
- $messages['diff'],
+ $messages['diff'],
array(),
array(
'target' => $title->getPrefixedDBKey(),
@@ -213,58 +191,23 @@ class RevisionDeleter {
);
}
}
-
+
// 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' )
- );
- }
-
+ $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 17482da2..40b992de 100644
--- a/includes/search/SearchEngine.php
+++ b/includes/search/SearchEngine.php
@@ -22,6 +22,14 @@ class SearchEngine {
var $namespaces = array( NS_MAIN );
var $showRedirects = false;
+ /// Feature values
+ protected $features = array();
+
+ /**
+ * @var DatabaseBase
+ */
+ protected $db;
+
function __construct($db = null) {
if ( $db ) {
$this->db = $db;
@@ -54,9 +62,38 @@ class SearchEngine {
return null;
}
- /** If this search backend can list/unlist redirects */
+ /**
+ * If this search backend can list/unlist redirects
+ * @deprecated since 1.18 Call supports( 'list-redirects' );
+ */
function acceptListRedirects() {
- return true;
+ return $this->supports( 'list-redirects' );
+ }
+
+ /**
+ * @since 1.18
+ * @param $feature String
+ * @return Boolean
+ */
+ public function supports( $feature ) {
+ switch( $feature ) {
+ case 'list-redirects':
+ return true;
+ case 'title-suffix-filter':
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Way to pass custom data for engines
+ * @since 1.18
+ * @param $feature String
+ * @param $data Mixed
+ * @return Noolean
+ */
+ public function setFeatureData( $feature, $data ) {
+ $this->features[$feature] = $data;
}
/**
@@ -95,11 +132,11 @@ class SearchEngine {
wfRunHooks( 'SearchGetNearMatchComplete', array( $searchterm, &$title ) );
return $title;
}
-
+
/**
- * Do a near match (see SearchEngine::getNearMatch) and wrap it into a
+ * Do a near match (see SearchEngine::getNearMatch) and wrap it into a
* SearchResultSet.
- *
+ *
* @param $searchterm string
* @return SearchResultSet
*/
@@ -124,19 +161,23 @@ class SearchEngine {
return $titleResult;
}
+ $context = new RequestContext;
+
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 );
+ $context->setTitle( $title );
+ $article = Article::newFromTitle( $title, $context );
if ( $article->hasViewableContent() ) {
return $title;
}
@@ -259,7 +300,7 @@ class SearchEngine {
if ( strncmp( $query, $allkeyword, strlen( $allkeyword ) ) == 0 ) {
$this->namespaces = null;
$parsed = substr( $query, strlen( $allkeyword ) );
- } else if ( strpos( $query, ':' ) !== false ) {
+ } elseif ( strpos( $query, ':' ) !== false ) {
$prefix = substr( $query, 0, strpos( $query, ':' ) );
$index = $wgContLang->getNsIndex( $prefix );
if ( $index !== false ) {
@@ -321,14 +362,11 @@ class SearchEngine {
}
/**
- * Find snippet highlight settings for a given user
+ * Find snippet highlight settings for all users
*
- * @param $user User
* @return Array contextlines, contextchars
*/
- public static function userHighlightPrefs( &$user ) {
- // $contextlines = $user->getOption( 'contextlines', 5 );
- // $contextchars = $user->getOption( 'contextchars', 50 );
+ public static function userHighlightPrefs() {
$contextlines = 2; // Hardcode this. Old defaults sucked. :)
$contextchars = 75; // same as above.... :P
return array( $contextlines, $contextchars );
@@ -434,13 +472,15 @@ class SearchEngine {
* @return String
*/
public static function getOpenSearchTemplate() {
- global $wgOpenSearchTemplate, $wgServer;
- if ( $wgOpenSearchTemplate ) {
+ global $wgOpenSearchTemplate, $wgCanonicalServer;
+ if ( $wgOpenSearchTemplate ) {
return $wgOpenSearchTemplate;
} else {
$ns = implode( '|', SearchEngine::defaultNamespaces() );
- if ( !$ns ) $ns = "0";
- return $wgServer . wfScript( 'api' ) . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
+ if ( !$ns ) {
+ $ns = "0";
+ }
+ return $wgCanonicalServer . wfScript( 'api' ) . '?action=opensearch&search={searchTerms}&namespace=' . $ns;
}
}
@@ -575,6 +615,9 @@ class SearchResultSet {
* This class is used for different SQL-based search engines shipped with MediaWiki
*/
class SqlSearchResultSet extends SearchResultSet {
+
+ protected $mResultSet;
+
function __construct( $resultSet, $terms ) {
$this->mResultSet = $resultSet;
$this->mTerms = $terms;
@@ -598,7 +641,7 @@ class SqlSearchResultSet extends SearchResultSet {
$row = $this->mResultSet->fetchObject();
if ( $row === false )
return false;
-
+
return SearchResult::newFromRow( $row );
}
@@ -619,19 +662,33 @@ class SearchResultTooMany {
/**
- * @todo Fixme: This class is horribly factored. It would probably be better to
+ * @todo FIXME: This class is horribly factored. It would probably be better to
* have a useful base class to which you pass some standard information, then
* let the fancy self-highlighters extend that.
* @ingroup Search
*/
class SearchResult {
+
+ /**
+ * @var Revision
+ */
var $mRevision = null;
var $mImage = null;
/**
+ * @var Title
+ */
+ var $mTitle;
+
+ /**
+ * @var String
+ */
+ var $mText;
+
+ /**
* Return a new SearchResult and initializes it with a title.
- *
- * @param $title Title
+ *
+ * @param $title Title
* @return SearchResult
*/
public static function newFromTitle( $title ) {
@@ -641,7 +698,7 @@ class SearchResult {
}
/**
* Return a new SearchResult and initializes it with a row.
- *
+ *
* @param $row object
* @return SearchResult
*/
@@ -650,28 +707,28 @@ class SearchResult {
$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 ) {
@@ -788,7 +845,7 @@ class SearchResult {
function getTimestamp() {
if ( $this->mRevision )
return $this->mRevision->getTimestamp();
- else if ( $this->mImage )
+ elseif ( $this->mImage )
return $this->mImage->getTimestamp();
return '';
}
@@ -886,7 +943,7 @@ class SearchHighlighter {
2 => '/(\[\[)|(\]\])/', // image
3 => "/(\n\\{\\|)|(\n\\|\\})/" ); // table
- // FIXME: this should prolly be a hook or something
+ // @todo FIXME: This should prolly be a hook or something
if ( function_exists( 'wfCite' ) ) {
$spat .= '|(<ref>)'; // references via cite extension
$endPatterns[4] = '/(<ref>)|(<\/ref>)/';
@@ -972,7 +1029,7 @@ class SearchHighlighter {
$anyterm = implode( '|', $terms );
$phrase = implode( "$wgSearchHighlightBoundaries+", $terms );
- // FIXME: a hack to scale contextchars, a correct solution
+ // @todo FIXME: A hack to scale contextchars, a correct solution
// would be to have contextchars actually be char and not byte
// length, and do proper utf-8 substrings and lengths everywhere,
// but PHP is making that very hard and unclean to implement :(
@@ -1318,12 +1375,13 @@ class SearchHighlighter {
continue;
}
--$contextlines;
- $pre = $wgContLang->truncate( $m[1], - $contextchars );
+ // truncate function changes ... to relevant i18n message.
+ $pre = $wgContLang->truncate( $m[1], - $contextchars, '...', false );
if ( count( $m ) < 3 ) {
$post = '';
} else {
- $post = $wgContLang->truncate( $m[3], $contextchars );
+ $post = $wgContLang->truncate( $m[3], $contextchars, '...', false );
}
$found = $m[2];
@@ -1344,7 +1402,7 @@ class SearchHighlighter {
/**
* Dummy class to be used when non-supported Database engine is present.
- * @todo Fixme: dummy class should probably try something at least mildly useful,
+ * @todo FIXME: Dummy class should probably try something at least mildly useful,
* such as a LIKE search through titles.
* @ingroup Search
*/
diff --git a/includes/search/SearchIBM_DB2.php b/includes/search/SearchIBM_DB2.php
index 8cedd6f2..c02a009d 100644
--- a/includes/search/SearchIBM_DB2.php
+++ b/includes/search/SearchIBM_DB2.php
@@ -91,7 +91,7 @@ class SearchIBM_DB2 extends SearchEngine {
* Return a LIMIT clause to limit results on the query.
* @return String
*/
- function queryLimit($sql) {
+ function queryLimit( $sql ) {
return $this->db->limitResult($sql, $this->limit, $this->offset);
}
@@ -151,7 +151,7 @@ class SearchIBM_DB2 extends SearchEngine {
$lc = SearchEngine::legalSearchChars();
$this->searchTerms = array();
- # FIXME: This doesn't handle parenthetical expressions.
+ # @todo FIXME: This doesn't handle parenthetical expressions.
$m = array();
$q = array();
diff --git a/includes/search/SearchMssql.php b/includes/search/SearchMssql.php
index 8b850fae..ebf19d3a 100644
--- a/includes/search/SearchMssql.php
+++ b/includes/search/SearchMssql.php
@@ -91,8 +91,9 @@ class SearchMssql extends SearchEngine {
/**
* Return a LIMIT clause to limit results on the query.
*
+ * @param $sql string
+ *
* @return String
- * @private
*/
function queryLimit( $sql ) {
return $this->db->limitResult( $sql, $this->limit, $this->offset );
@@ -103,7 +104,6 @@ class SearchMssql extends SearchEngine {
* subclasses may define this though
*
* @return String
- * @private
*/
function queryRanking( $filteredTerm, $fulltext ) {
return ' ORDER BY ftindex.[RANK] DESC'; // return ' ORDER BY score(1)';
@@ -115,7 +115,6 @@ class SearchMssql extends SearchEngine {
*
* @param $filteredTerm String
* @param $fulltext Boolean
- * @private
*/
function getQuery( $filteredTerm, $fulltext ) {
return $this->queryLimit( $this->queryMain( $filteredTerm, $fulltext ) . ' ' .
@@ -124,7 +123,6 @@ class SearchMssql extends SearchEngine {
$this->queryRanking( $filteredTerm, $fulltext ) . ' ' );
}
-
/**
* Picks which field to index on, depending on what type of query.
*
@@ -159,7 +157,7 @@ class SearchMssql extends SearchEngine {
$lc = SearchEngine::legalSearchChars();
$this->searchTerms = array();
- # FIXME: This doesn't handle parenthetical expressions.
+ # @todo FIXME: This doesn't handle parenthetical expressions.
$m = array();
$q = array();
diff --git a/includes/search/SearchMySQL.php b/includes/search/SearchMySQL.php
index b92682ad..c52c9e5b 100644
--- a/includes/search/SearchMySQL.php
+++ b/includes/search/SearchMySQL.php
@@ -40,9 +40,14 @@ class SearchMySQL extends SearchEngine {
parent::__construct( $db );
}
- /**
- * 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
+ *
+ * @param $filteredText string
+ * @param $fullText string
+ *
+ * @return string
*/
function parseQuery( $filteredText, $fulltext ) {
global $wgContLang;
@@ -50,13 +55,13 @@ class SearchMySQL extends SearchEngine {
$searchon = '';
$this->searchTerms = array();
- # FIXME: This doesn't handle parenthetical expressions.
+ # @todo FIXME: This doesn't handle parenthetical expressions.
$m = array();
if( preg_match_all( '/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
$filteredText, $m, PREG_SET_ORDER ) ) {
foreach( $m as $bits ) {
@list( /* all */, $modifier, $term, $nonQuoted, $wildcard ) = $bits;
-
+
if( $nonQuoted != '' ) {
$term = $nonQuoted;
$quote = '';
@@ -64,13 +69,13 @@ class SearchMySQL extends SearchEngine {
$term = str_replace( '"', '', $term );
$quote = '"';
}
-
+
if( $searchon !== '' ) $searchon .= ' ';
if( $this->strictMatching && ($modifier == '') ) {
// If we leave this out, boolean op defaults to OR which is rarely helpful.
$modifier = '+';
}
-
+
// Some languages such as Serbian store the input form in the search index,
// so we may need to search for matches in multiple writing system variants.
$convertedVariants = $wgContLang->autoConvertToAllVariants( $term );
@@ -79,7 +84,7 @@ class SearchMySQL 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.
@@ -87,12 +92,12 @@ class SearchMySQL 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 .= '(';
@@ -108,7 +113,7 @@ class SearchMySQL 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 );
@@ -124,10 +129,10 @@ class SearchMySQL extends SearchEngine {
$field = $this->getIndexField( $fulltext );
return " MATCH($field) AGAINST('$searchon' IN BOOLEAN MODE) ";
}
-
+
function regexTerm( $string, $wildcard ) {
global $wgContLang;
-
+
$regex = preg_quote( $string, '/' );
if( $wgContLang->hasWordBreaks() ) {
if( $wildcard ) {
@@ -167,85 +172,112 @@ class SearchMySQL extends SearchEngine {
function searchTitle( $term ) {
return $this->searchInternal( $term, false );
}
-
+
protected function searchInternal( $term, $fulltext ) {
global $wgCountTotalSearchHits;
-
+
+ // This seems out of place, why is this called with empty term?
+ if ( trim( $term ) === '' ) return null;
+
$filteredTerm = $this->filter( $term );
- $resultSet = $this->db->query( $this->getQuery( $filteredTerm, $fulltext ) );
-
+ $query = $this->getQuery( $filteredTerm, $fulltext );
+ $resultSet = $this->db->select(
+ $query['tables'], $query['fields'], $query['conds'],
+ __METHOD__, $query['options'], $query['joins']
+ );
+
$total = null;
if( $wgCountTotalSearchHits ) {
- $totalResult = $this->db->query( $this->getCountQuery( $filteredTerm, $fulltext ) );
+ $query = $this->getCountQuery( $filteredTerm, $fulltext );
+ $totalResult = $this->db->select(
+ $query['tables'], $query['fields'], $query['conds'],
+ __METHOD__, $query['options'], $query['joins']
+ );
+
$row = $totalResult->fetchObject();
if( $row ) {
$total = intval( $row->c );
}
$totalResult->free();
}
-
+
return new MySQLSearchResultSet( $resultSet, $this->searchTerms, $total );
}
-
- /**
- * Return a partial WHERE clause to exclude redirects, if so set
- * @return String
- */
- function queryRedirect() {
- if( $this->showRedirects ) {
- return '';
- } else {
- return 'AND page_is_redirect=0';
+ public function supports( $feature ) {
+ switch( $feature ) {
+ case 'list-redirects':
+ case 'title-suffix-filter':
+ return true;
+ default:
+ return false;
}
}
/**
- * Return a partial WHERE clause to limit the search to the given namespaces
- * @return String
+ * Add special conditions
+ * @param $query Array
+ * @since 1.18
*/
- function queryNamespaces() {
- if( is_null($this->namespaces) )
- return ''; # search all
- if ( !count( $this->namespaces ) ) {
- $namespaces = '0';
- } else {
- $namespaces = $this->db->makeList( $this->namespaces );
+ protected function queryFeatures( &$query ) {
+ foreach ( $this->features as $feature => $value ) {
+ if ( $feature === 'list-redirects' && !$value ) {
+ $query['conds']['page_is_redirect'] = 0;
+ } elseif( $feature === 'title-suffix-filter' && $value ) {
+ $query['conds'][] = 'page_title' . $this->db->buildLike( $this->db->anyString(), $value );
+ }
}
- return 'AND page_namespace IN (' . $namespaces . ')';
}
/**
- * Return a LIMIT clause to limit results on the query.
- * @return String
+ * Add namespace conditions
+ * @param $query Array
+ * @since 1.18 (changed)
*/
- function queryLimit() {
- return $this->db->limitResult( '', $this->limit, $this->offset );
+ function queryNamespaces( &$query ) {
+ if ( is_array( $this->namespaces ) ) {
+ if ( count( $this->namespaces ) === 0 ) {
+ $this->namespaces[] = '0';
+ }
+ $query['conds']['page_namespace'] = $this->namespaces;
+ }
}
/**
- * Does not do anything for generic search engine
- * subclasses may define this though
- * @return String
+ * Add limit options
+ * @param $query Array
+ * @since 1.18
*/
- function queryRanking( $filteredTerm, $fulltext ) {
- return '';
+ protected function limitResult( &$query ) {
+ $query['options']['LIMIT'] = $this->limit;
+ $query['options']['OFFSET'] = $this->offset;
}
/**
- * Construct the full SQL query to do the search.
+ * Construct the SQL query to do the search.
* The guts shoulds be constructed in queryMain()
* @param $filteredTerm String
* @param $fulltext Boolean
+ * @return Array
+ * @since 1.18 (changed)
*/
function getQuery( $filteredTerm, $fulltext ) {
- return $this->queryMain( $filteredTerm, $fulltext ) . ' ' .
- $this->queryRedirect() . ' ' .
- $this->queryNamespaces() . ' ' .
- $this->queryRanking( $filteredTerm, $fulltext ) . ' ' .
- $this->queryLimit();
+ $query = array(
+ 'tables' => array(),
+ 'fields' => array(),
+ 'conds' => array(),
+ 'options' => array(),
+ 'joins' => array(),
+ );
+
+ $this->queryMain( $query, $filteredTerm, $fulltext );
+ $this->queryFeatures( $query );
+ $this->queryNamespaces( $query );
+ $this->limitResult( $query );
+
+ return $query;
}
-
+
/**
* Picks which field to index on, depending on what type of query.
* @param $fulltext Boolean
@@ -257,32 +289,40 @@ class SearchMySQL extends SearchEngine {
/**
* Get the base part of the search query.
- * The actual match syntax will depend on the server
- * version; MySQL 3 and MySQL 4 have different capabilities
- * in their fulltext search indexes.
*
* @param $filteredTerm String
* @param $fulltext Boolean
- * @return String
+ * @since 1.18 (changed)
*/
- function queryMain( $filteredTerm, $fulltext ) {
+ function queryMain( &$query, $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 ' .
- "FROM $page,$searchindex " .
- 'WHERE page_id=si_page AND ' . $match;
+ $query['tables'][] = 'page';
+ $query['tables'][] = 'searchindex';
+ $query['fields'][] = 'page_id';
+ $query['fields'][] = 'page_namespace';
+ $query['fields'][] = 'page_title';
+ $query['conds'][] = 'page_id=si_page';
+ $query['conds'][] = $match;
}
+ /**
+ * @since 1.18 (changed)
+ */
function getCountQuery( $filteredTerm, $fulltext ) {
$match = $this->parseQuery( $filteredTerm, $fulltext );
- $page = $this->db->tableName( 'page' );
- $searchindex = $this->db->tableName( 'searchindex' );
- return "SELECT COUNT(*) AS c " .
- "FROM $page,$searchindex " .
- 'WHERE page_id=si_page AND ' . $match .
- $this->queryRedirect() . ' ' .
- $this->queryNamespaces();
+
+ $query = array(
+ 'tables' => array( 'page', 'searchindex' ),
+ 'fields' => array( 'COUNT(*) as c' ),
+ 'conds' => array( 'page_id=si_page', $match ),
+ 'options' => array(),
+ 'joins' => array(),
+ );
+
+ $this->queryFeatures( $query );
+ $this->queryNamespaces( $query );
+
+ return $query;
}
/**
@@ -311,7 +351,7 @@ class SearchMySQL extends SearchEngine {
* @param $id Integer
* @param $title String
*/
- function updateTitle( $id, $title ) {
+ function updateTitle( $id, $title ) {
$dbw = wfGetDB( DB_MASTER );
$dbw->update( 'searchindex',
@@ -329,7 +369,7 @@ class SearchMySQL extends SearchEngine {
global $wgContLang;
wfProfileIn( __METHOD__ );
-
+
$out = parent::normalizeText( $string );
// MySQL fulltext index doesn't grok utf-8, so we
@@ -363,7 +403,7 @@ class SearchMySQL extends SearchEngine {
$out );
wfProfileOut( __METHOD__ );
-
+
return $out;
}
@@ -379,7 +419,7 @@ class SearchMySQL extends SearchEngine {
/**
* Check MySQL server's ft_min_word_len setting so we know
* if we need to pad short words...
- *
+ *
* @return int
*/
protected function minSearchLength() {
diff --git a/includes/search/SearchOracle.php b/includes/search/SearchOracle.php
index 15c386ce..85337ca1 100644
--- a/includes/search/SearchOracle.php
+++ b/includes/search/SearchOracle.php
@@ -123,18 +123,22 @@ class SearchOracle extends SearchEngine {
/**
* Return a LIMIT clause to limit results on the query.
+ *
+ * @param string
+ *
* @return String
*/
- function queryLimit($sql) {
+ 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
*/
- function queryRanking($filteredTerm, $fulltext) {
+ function queryRanking( $filteredTerm, $fulltext ) {
return ' ORDER BY score(1)';
}
@@ -186,7 +190,7 @@ class SearchOracle extends SearchEngine {
$lc = SearchEngine::legalSearchChars();
$this->searchTerms = array();
- # FIXME: This doesn't handle parenthetical expressions.
+ # @todo FIXME: This doesn't handle parenthetical expressions.
$m = array();
$searchon = '';
if (preg_match_all('/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
@@ -253,9 +257,9 @@ class SearchOracle extends SearchEngine {
// 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->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_text_idx', false ) ) . ")" );
$dbw->query( "CALL ctx_ddl.sync_index(" .
- $dbw->addQuotes( $dbw->getDBname() . '.' . trim( $dbw->tableName( 'si_title_idx' ), '"' ) ) . ")" );
+ $dbw->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_title_idx', false ) ) . ")" );
}
/**
diff --git a/includes/search/SearchPostgres.php b/includes/search/SearchPostgres.php
index 9d6d1539..cfe283b2 100644
--- a/includes/search/SearchPostgres.php
+++ b/includes/search/SearchPostgres.php
@@ -29,6 +29,11 @@
* @ingroup Search
*/
class SearchPostgres extends SearchEngine {
+
+ /**
+ * @var DatabasePostgres
+ */
+ protected $db;
/**
* Creates an instance of this class
* @param $db DatabaseSqlite: database object
@@ -56,6 +61,7 @@ class SearchPostgres extends SearchEngine {
}
return new PostgresSearchResultSet( $resultSet, $this->searchTerms );
}
+
function searchText( $term ) {
$q = $this->searchQuery( $term, 'textvector', 'old_text' );
$olderror = error_reporting(E_ERROR);
@@ -67,11 +73,14 @@ class SearchPostgres extends SearchEngine {
return new PostgresSearchResultSet( $resultSet, $this->searchTerms );
}
-
- /*
+ /**
* Transform the user's search string into a better form for tsearch2
* Returns an SQL fragment consisting of quoted text to search for.
- */
+ *
+ * @param $term string
+ *
+ * @return string
+ */
function parseQuery( $term ) {
wfDebug( "parseQuery received: $term \n" );
@@ -96,10 +105,10 @@ class SearchPostgres extends SearchEngine {
if (strtolower($terms[2]) === 'and') {
$searchstring .= ' & ';
}
- else if (strtolower($terms[2]) === 'or' or $terms[2] === '|') {
+ elseif (strtolower($terms[2]) === 'or' or $terms[2] === '|') {
$searchstring .= ' | ';
}
- else if (strtolower($terms[2]) === 'not') {
+ elseif (strtolower($terms[2]) === 'not') {
$searchstring .= ' & !';
}
else {
@@ -139,21 +148,18 @@ class SearchPostgres extends SearchEngine {
* @param $colname
*/
function searchQuery( $term, $fulltext, $colname ) {
- $postgresVersion = $this->db->getServerVersion();
-
- $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)";
+ $SQL = "SELECT to_tsquery($searchstring)";
$res = $this->db->query($SQL);
if (!$res) {
## TODO: Better output (example to catch: one 'two)
die ("Sorry, that was not a valid search string. Please go back and try again");
}
- $top = pg_fetch_result($res,0,0);
+ $top = $res->fetchRow();
+ $top = $top[0];
if ($top === "") { ## e.g. if only stopwords are used XXX return something better
$query = "SELECT page_id, page_namespace, page_title, 0 AS score ".
@@ -168,12 +174,10 @@ class SearchPostgres extends SearchEngine {
}
}
- $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 ".
+ "ts_rank($fulltext, to_tsquery($searchstring), 5) AS score ".
"FROM page p, revision r, pagecontent c WHERE p.page_latest = r.rev_id " .
- "AND r.rev_text_id = c.old_id AND $fulltext @@ to_tsquery($prefix $searchstring)";
+ "AND r.rev_text_id = c.old_id AND $fulltext @@ to_tsquery($searchstring)";
}
## Redirects
@@ -204,7 +208,7 @@ class SearchPostgres extends SearchEngine {
function update( $pageid, $title, $text ) {
## We don't want to index older revisions
$SQL = "UPDATE pagecontent SET textvector = NULL WHERE old_id IN ".
- "(SELECT rev_text_id FROM revision WHERE rev_page = " . intval( $pageid ) .
+ "(SELECT rev_text_id FROM revision WHERE rev_page = " . intval( $pageid ) .
" ORDER BY rev_text_id DESC OFFSET 1)";
$this->db->query($SQL);
return true;
diff --git a/includes/search/SearchSqlite.php b/includes/search/SearchSqlite.php
index 6accc31b..cd59eea9 100644
--- a/includes/search/SearchSqlite.php
+++ b/includes/search/SearchSqlite.php
@@ -26,6 +26,12 @@
* @ingroup Search
*/
class SearchSqlite extends SearchEngine {
+
+ /**
+ * @var DatabaseSqlite
+ */
+ protected $db;
+
/**
* Creates an instance of this class
* @param $db DatabaseSqlite: database object
@@ -45,6 +51,8 @@ class SearchSqlite extends SearchEngine {
/**
* Parse the user's query and transform it into an SQL fragment which will
* become part of a WHERE clause
+ *
+ * @return string
*/
function parseQuery( $filteredText, $fulltext ) {
global $wgContLang;
@@ -66,7 +74,9 @@ class SearchSqlite extends SearchEngine {
$quote = '"';
}
- if( $searchon !== '' ) $searchon .= ' ';
+ if( $searchon !== '' ) {
+ $searchon .= ' ';
+ }
// Some languages such as Serbian store the input form in the search index,
// so we may need to search for matches in multiple writing system variants.
diff --git a/includes/search/SearchUpdate.php b/includes/search/SearchUpdate.php
index 5262faa4..77146ebb 100644
--- a/includes/search/SearchUpdate.php
+++ b/includes/search/SearchUpdate.php
@@ -15,8 +15,8 @@
*/
class SearchUpdate {
- /* private */ var $mId = 0, $mNamespace, $mTitle, $mText;
- /* private */ var $mTitleWords;
+ private $mId = 0, $mNamespace, $mTitle, $mText;
+ private $mTitleWords;
function __construct( $id, $title, $text = false ) {
$nt = Title::newFromText( $title );
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
index f016ab92..e4bf42d3 100644
--- a/includes/specials/SpecialActiveusers.php
+++ b/includes/specials/SpecialActiveusers.php
@@ -32,6 +32,16 @@
*/
class ActiveUsersPager extends UsersPager {
+ /**
+ * @var FormOptions
+ */
+ protected $opts;
+
+ /**
+ * @var Array
+ */
+ protected $groups;
+
function __construct( $group = null ) {
global $wgRequest, $wgActiveUserDays;
$this->RCMaxAge = $wgActiveUserDays;
@@ -49,6 +59,10 @@ class ActiveUsersPager extends UsersPager {
parent::__construct();
}
+ function getTitle() {
+ return SpecialPage::getTitleFor( 'Activeusers' );
+ }
+
public function setupOptions() {
global $wgRequest;
diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php
index 296c6f50..2214b4ab 100644
--- a/includes/specials/SpecialAllmessages.php
+++ b/includes/specials/SpecialAllmessages.php
@@ -30,6 +30,11 @@
class SpecialAllmessages extends SpecialPage {
/**
+ * @var AllmessagesTablePager
+ */
+ protected $table;
+
+ /**
* Constructor
*/
public function __construct() {
@@ -42,37 +47,102 @@ class SpecialAllmessages extends SpecialPage {
* @param $par Mixed: parameter passed to the page or null
*/
public function execute( $par ) {
- global $wgOut, $wgRequest;
+ $request = $this->getRequest();
+ $out = $this->getOutput();
$this->setHeaders();
global $wgUseDatabaseMessages;
if( !$wgUseDatabaseMessages ) {
- $wgOut->addWikiMsg( 'allmessagesnotsupportedDB' );
+ $out->addWikiMsg( 'allmessagesnotsupportedDB' );
return;
} else {
$this->outputHeader( 'allmessagestext' );
}
- $this->filter = $wgRequest->getVal( 'filter', 'all' );
- $this->prefix = $wgRequest->getVal( 'prefix', '' );
+ $out->addModuleStyles( 'mediawiki.special' );
+
+ $this->filter = $request->getVal( 'filter', 'all' );
+ $this->prefix = $request->getVal( 'prefix', '' );
$this->table = new AllmessagesTablePager(
$this,
- $conds = array(),
- wfGetLangObj( $wgRequest->getVal( 'lang', $par ) )
+ array(),
+ wfGetLangObj( $request->getVal( 'lang', $par ) )
);
- $this->langCode = $this->table->lang->getCode();
+ $this->langcode = $this->table->lang->getCode();
- $wgOut->addHTML( $this->buildForm() .
+ $out->addHTML( $this->table->buildForm() .
$this->table->getNavigationBar() .
- $this->table->getLimitForm() .
$this->table->getBody() .
$this->table->getNavigationBar() );
}
+}
+
+/**
+ * Use TablePager for prettified output. We have to pretend that we're
+ * getting data from a table when in fact not all of it comes from the database.
+ */
+class AllmessagesTablePager extends TablePager {
+
+ protected $filter, $prefix, $langcode, $displayPrefix;
+
+ public $mLimitsShown;
+
+ /**
+ * @var Language
+ */
+ public $lang;
+
+ /**
+ * @var null|bool
+ */
+ public $custom;
+
+ function __construct( $page, $conds, $langObj = null ) {
+ parent::__construct();
+ $this->mIndexField = 'am_title';
+ $this->mPage = $page;
+ $this->mConds = $conds;
+ $this->mDefaultDirection = true; // always sort ascending
+ $this->mLimitsShown = array( 20, 50, 100, 250, 500, 5000 );
+
+ global $wgLang, $wgContLang, $wgRequest;
+
+ $this->talk = htmlspecialchars( wfMsg( 'talkpagelinktext' ) );
+
+ $this->lang = ( $langObj ? $langObj : $wgContLang );
+ $this->langcode = $this->lang->getCode();
+ $this->foreign = $this->langcode != $wgContLang->getCode();
+
+ if( $wgRequest->getVal( 'filter', 'all' ) === 'all' ){
+ $this->custom = null; // So won't match in either case
+ } else {
+ $this->custom = ($wgRequest->getVal( 'filter' ) == 'unmodified');
+ }
+
+ $prefix = $wgLang->ucfirst( $wgRequest->getVal( 'prefix', '' ) );
+ $prefix = $prefix != '' ? Title::makeTitleSafe( NS_MEDIAWIKI, $wgRequest->getVal( 'prefix', null ) ) : null;
+ if( $prefix !== null ){
+ $this->displayPrefix = $prefix->getDBkey();
+ $this->prefix = '/^' . preg_quote( $this->displayPrefix ) . '/i';
+ } else {
+ $this->displayPrefix = false;
+ $this->prefix = false;
+ }
+
+ // The suffix that may be needed for message names if we're in a
+ // different language (eg [[MediaWiki:Foo/fr]]: $suffix = '/fr'
+ if( $this->foreign ) {
+ $this->suffix = '/' . $this->langcode;
+ } else {
+ $this->suffix = '';
+ }
+ }
+
function buildForm() {
global $wgScript;
@@ -88,7 +158,7 @@ class SpecialAllmessages extends SpecialPage {
Xml::label( wfMsg( 'allmessages-prefix' ), 'mw-allmessages-form-prefix' ) .
"</td>\n
<td class=\"mw-input\">" .
- Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->prefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) .
+ Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->displayPrefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) .
"</td>\n
</tr>
<tr>\n
@@ -124,72 +194,33 @@ class SpecialAllmessages extends SpecialPage {
Xml::openElement( 'select', array( 'id' => 'mw-allmessages-form-lang', 'name' => 'lang' ) );
foreach( $languages as $lang => $name ) {
- $selected = $lang == $this->langCode;
+ $selected = $lang == $this->langcode;
$out .= Xml::option( $lang . ' - ' . $name, $lang, $selected ) . "\n";
}
$out .= Xml::closeElement( 'select' ) .
"</td>\n
- </tr>
- <tr>\n
+ </tr>" .
+
+ '<tr>
+ <td class="mw-label">' .
+ Xml::label( wfMsg( 'table_pager_limit_label'), 'mw-table_pager_limit_label' ) .
+ '</td>
+ <td class="mw-input">' .
+ $this->getLimitSelect() .
+ '</td>
+ <tr>
<td></td>
- <td>" .
+ <td>' .
Xml::submitButton( wfMsg( 'allmessages-filter-submit' ) ) .
"</td>\n
</tr>" .
+
Xml::closeElement( 'table' ) .
- $this->table->getHiddenFields( array( 'title', 'prefix', 'filter', 'lang' ) ) .
+ $this->getHiddenFields( array( 'title', 'prefix', 'filter', 'lang', 'limit' ) ) .
Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' );
return $out;
}
-}
-
-/* use TablePager for prettified output. We have to pretend that we're
- * getting data from a table when in fact not all of it comes from the database.
- */
-class AllmessagesTablePager extends TablePager {
-
- public $mLimitsShown;
-
- function __construct( $page, $conds, $langObj = null ) {
- parent::__construct();
- $this->mIndexField = 'am_title';
- $this->mPage = $page;
- $this->mConds = $conds;
- $this->mDefaultDirection = true; // always sort ascending
- $this->mLimitsShown = array( 20, 50, 100, 250, 500, 5000 );
-
- global $wgLang, $wgContLang, $wgRequest;
-
- $this->talk = htmlspecialchars( wfMsg( 'talkpagelinktext' ) );
-
- $this->lang = ( $langObj ? $langObj : $wgContLang );
- $this->langcode = $this->lang->getCode();
- $this->foreign = $this->langcode != $wgContLang->getCode();
-
- if( $wgRequest->getVal( 'filter', 'all' ) === 'all' ){
- $this->custom = null; // So won't match in either case
- } else {
- $this->custom = ($wgRequest->getVal( 'filter' ) == 'unmodified');
- }
-
- $prefix = $wgLang->ucfirst( $wgRequest->getVal( 'prefix', '' ) );
- $prefix = $prefix != '' ? Title::makeTitleSafe( NS_MEDIAWIKI, $wgRequest->getVal( 'prefix', null ) ) : null;
- if( $prefix !== null ){
- $this->prefix = '/^' . preg_quote( $prefix->getDBkey() ) . '/i';
- } else {
- $this->prefix = false;
- }
- $this->getSkin();
-
- // The suffix that may be needed for message names if we're in a
- // different language (eg [[MediaWiki:Foo/fr]]: $suffix = '/fr'
- if( $this->foreign ) {
- $this->suffix = '/' . $this->langcode;
- } else {
- $this->suffix = '';
- }
- }
function getAllMessages( $descending ) {
wfProfileIn( __METHOD__ );
@@ -208,12 +239,16 @@ class AllmessagesTablePager extends TablePager {
}
/**
- * Determine which of the MediaWiki and MediaWiki_talk namespace pages exist.
- * Returns array( 'pages' => ..., 'talks' => ... ), where the subarrays have
- * an entry for each existing page, with the key being the message name and
+ * Determine which of the MediaWiki and MediaWiki_talk namespace pages exist.
+ * Returns array( 'pages' => ..., 'talks' => ... ), where the subarrays have
+ * an entry for each existing page, with the key being the message name and
* value arbitrary.
+ *
+ * @param array $messageNames
+ * @param string $langcode What language code
+ * @param bool $foreign Whether the $langcode is not the content language
*/
- function getCustomisedStatuses( $messageNames ) {
+ public static function getCustomisedStatuses( $messageNames, $langcode = 'en', $foreign = false ) {
wfProfileIn( __METHOD__ . '-db' );
$dbr = wfGetDB( DB_SLAVE );
@@ -226,20 +261,19 @@ class AllmessagesTablePager extends TablePager {
$xNames = array_flip( $messageNames );
$pageFlags = $talkFlags = array();
-
+
foreach ( $res as $s ) {
if( $s->page_namespace == NS_MEDIAWIKI ) {
- if( $this->foreign ) {
+ if( $foreign ) {
$title = explode( '/', $s->page_title );
- if( count( $title ) === 2 && $this->langcode == $title[1]
- && isset( $xNames[$title[0]] ) )
- {
+ if( count( $title ) === 2 && $langcode == $title[1]
+ && isset( $xNames[$title[0]] ) ) {
$pageFlags["{$title[0]}"] = true;
}
} elseif( isset( $xNames[$s->page_title] ) ) {
$pageFlags[$s->page_title] = true;
}
- } else if( $s->page_namespace == NS_MEDIAWIKI_TALK ){
+ } elseif( $s->page_namespace == NS_MEDIAWIKI_TALK ){
$talkFlags[$s->page_title] = true;
}
}
@@ -249,14 +283,15 @@ class AllmessagesTablePager extends TablePager {
return array( 'pages' => $pageFlags, 'talks' => $talkFlags );
}
- /* This function normally does a database query to get the results; we need
+ /**
+ * This function normally does a database query to get the results; we need
* to make a pretend result using a FakeResultWrapper.
*/
function reallyDoQuery( $offset, $limit, $descending ) {
$result = new FakeResultWrapper( array() );
$messageNames = $this->getAllMessages( $descending );
- $statuses = $this->getCustomisedStatuses( $messageNames );
+ $statuses = self::getCustomisedStatuses( $messageNames, $this->langcode, $this->foreign );
$count = 0;
foreach( $messageNames as $key ) {
@@ -264,17 +299,21 @@ class AllmessagesTablePager extends TablePager {
if( $customised !== $this->custom &&
( $descending && ( $key < $offset || !$offset ) || !$descending && $key > $offset ) &&
( ( $this->prefix && preg_match( $this->prefix, $key ) ) || $this->prefix === false )
- ){
+ ) {
+ $actual = wfMessage( $key )->inLanguage( $this->langcode )->plain();
+ $default = wfMessage( $key )->inLanguage( $this->langcode )->useDatabase( false )->plain();
$result->result[] = array(
'am_title' => $key,
- 'am_actual' => wfMsgGetKey( $key, /*useDB*/true, $this->langcode, false ),
- 'am_default' => wfMsgGetKey( $key, /*useDB*/false, $this->langcode, false ),
+ 'am_actual' => $actual,
+ 'am_default' => $default,
'am_customised' => $customised,
'am_talk_exists' => isset( $statuses['talks'][$key] )
);
$count++;
}
- if( $count == $limit ) break;
+ if( $count == $limit ) {
+ break;
+ }
}
return $result;
}
@@ -306,9 +345,9 @@ class AllmessagesTablePager extends TablePager {
$talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
if( $this->mCurrentRow->am_customised ){
- $title = $this->mSkin->linkKnown( $title, $wgLang->lcfirst( $value ) );
+ $title = Linker::linkKnown( $title, $wgLang->lcfirst( $value ) );
} else {
- $title = $this->mSkin->link(
+ $title = Linker::link(
$title,
$wgLang->lcfirst( $value ),
array(),
@@ -317,9 +356,9 @@ class AllmessagesTablePager extends TablePager {
);
}
if ( $this->mCurrentRow->am_talk_exists ) {
- $talk = $this->mSkin->linkKnown( $talk , $this->talk );
+ $talk = Linker::linkKnown( $talk , $this->talk );
} else {
- $talk = $this->mSkin->link(
+ $talk = Linker::link(
$talk,
$this->talk,
array(),
@@ -330,7 +369,6 @@ class AllmessagesTablePager extends TablePager {
return $title . ' (' . $talk . ')';
case 'am_default' :
- return Sanitizer::escapeHtmlAllowEntities( $value, ENT_QUOTES );
case 'am_actual' :
return Sanitizer::escapeHtmlAllowEntities( $value, ENT_QUOTES );
}
@@ -369,8 +407,10 @@ class AllmessagesTablePager extends TablePager {
function getCellAttrs( $field, $value ){
if( $this->mCurrentRow->am_customised && $field == 'am_title' ){
return array( 'rowspan' => '2', 'class' => $field );
- } else {
+ } else if( $field == 'am_title' ) {
return array( 'class' => $field );
+ } else {
+ return array( 'lang' => $this->langcode, 'dir' => $this->lang->getDir(), 'class' => $field );
}
}
@@ -381,15 +421,19 @@ class AllmessagesTablePager extends TablePager {
'am_default' => wfMsg( 'allmessagesdefault' )
);
}
+
function getTitle() {
return SpecialPage::getTitleFor( 'Allmessages', false );
}
+
function isFieldSortable( $x ){
return false;
}
+
function getDefaultSort(){
return '';
}
+
function getQueryInfo(){
return '';
}
diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php
index 5fa1aa47..a9cbf3ab 100644
--- a/includes/specials/SpecialAllpages.php
+++ b/includes/specials/SpecialAllpages.php
@@ -20,7 +20,7 @@
* @file
* @ingroup SpecialPage
*/
-
+
/**
* Implements Special:Allpages
*
@@ -58,24 +58,27 @@ class SpecialAllpages extends IncludableSpecialPage {
* @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL)
*/
function execute( $par ) {
- global $wgRequest, $wgOut, $wgContLang;
+ global $wgContLang;
+ $request = $this->getRequest();
+ $out = $this->getOutput();
$this->setHeaders();
$this->outputHeader();
- $wgOut->allowClickjacking();
+ $out->allowClickjacking();
# GET values
- $from = $wgRequest->getVal( 'from', null );
- $to = $wgRequest->getVal( 'to', null );
- $namespace = $wgRequest->getInt( 'namespace' );
+ $from = $request->getVal( 'from', null );
+ $to = $request->getVal( 'to', null );
+ $namespace = $request->getInt( 'namespace' );
$namespaces = $wgContLang->getNamespaces();
- $wgOut->setPagetitle(
+ $out->setPagetitle(
( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ?
wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
wfMsg( 'allarticles' )
);
+ $out->addModuleStyles( 'mediawiki.special' );
if( isset($par) ) {
$this->showChunk( $namespace, $par, $to );
@@ -96,7 +99,7 @@ class SpecialAllpages extends IncludableSpecialPage {
function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '' ) {
global $wgScript;
$t = $this->getTitle();
-
+
$out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
$out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
$out .= Html::hidden( 'title', $t->getPrefixedText() );
@@ -141,7 +144,7 @@ class SpecialAllpages extends IncludableSpecialPage {
* @param $to String: list all pages to this name
*/
function showToplevel( $namespace = NS_MAIN, $from = '', $to = '' ) {
- global $wgOut;
+ $output = $this->getOutput();
# TODO: Either make this *much* faster or cache the title index points
# in the querycache table.
@@ -220,7 +223,7 @@ class SpecialAllpages extends IncludableSpecialPage {
if( !empty($lines) ) {
$this->showChunk( $namespace, $from, $to );
} else {
- $wgOut->addHTML( $this->namespaceForm( $namespace, $from, $to ) );
+ $output->addHTML( $this->namespaceForm( $namespace, $from, $to ) );
}
return;
}
@@ -240,14 +243,13 @@ class SpecialAllpages extends IncludableSpecialPage {
$out2 = '';
} else {
if( isset($from) || isset($to) ) {
- global $wgUser;
$out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ).
'<tr>
<td>' .
$nsForm .
'</td>
<td class="mw-allpages-nav">' .
- $wgUser->getSkin()->link( $this->getTitle(), wfMsgHtml ( 'allpages' ),
+ $this->getSkin()->link( $this->getTitle(), wfMsgHtml ( 'allpages' ),
array(), array(), 'known' ) .
"</td>
</tr>" .
@@ -256,7 +258,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$out2 = $nsForm;
}
}
- $wgOut->addHTML( $out2 . $out );
+ $output->addHTML( $out2 . $out );
}
/**
@@ -291,9 +293,9 @@ class SpecialAllpages extends IncludableSpecialPage {
* @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;
-
- $sk = $wgUser->getSkin();
+ global $wgContLang, $wgLang;
+ $output = $this->getOutput();
+ $sk = $this->getSkin();
$fromList = $this->getNamespaceKeyAndText($namespace, $from);
$toList = $this->getNamespaceKeyAndText( $namespace, $to );
@@ -301,7 +303,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$n = 0;
if ( !$fromList || !$toList ) {
- $out = wfMsgWikiHtml( 'allpagesbadtitle' );
+ $out = wfMsgExt( 'allpagesbadtitle', 'parse' );
} elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
// Show errormessage and reset to NS_MAIN
$out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace );
@@ -320,7 +322,7 @@ class SpecialAllpages extends IncludableSpecialPage {
}
$res = $dbr->select( 'page',
- array( 'page_namespace', 'page_title', 'page_is_redirect' ),
+ array( 'page_namespace', 'page_title', 'page_is_redirect', 'page_id' ),
$conds,
__METHOD__,
array(
@@ -333,10 +335,10 @@ class SpecialAllpages extends IncludableSpecialPage {
if( $res->numRows() > 0 ) {
$out = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-chunk' ) );
while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
- $t = Title::makeTitle( $s->page_namespace, $s->page_title );
+ $t = Title::newFromRow( $s );
if( $t ) {
$link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
- $sk->linkKnown( $t, htmlspecialchars( $t->getText() ) ) .
+ $sk->link( $t ) .
($s->page_is_redirect ? '</div>' : '' );
} else {
$link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
@@ -373,7 +375,7 @@ class SpecialAllpages extends IncludableSpecialPage {
'page_title',
array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ),
__METHOD__,
- array( 'ORDER BY' => 'page_title DESC',
+ array( 'ORDER BY' => 'page_title DESC',
'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 )
)
);
@@ -409,7 +411,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$nsForm .
'</td>
<td class="mw-allpages-nav">' .
- $sk->link( $self, wfMsgHtml ( 'allpages' ), array(), array(), 'known' );
+ $sk->link( $self, wfMsgHtml ( 'allpages' ) );
# Do we put a previous link ?
if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) {
@@ -420,7 +422,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$prevLink = $sk->linkKnown(
$self,
- htmlspecialchars( wfMsg( 'prevpage', $pt ) ),
+ wfMessage( 'prevpage', $pt )->escaped(),
array(),
$query
);
@@ -437,7 +439,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$nextLink = $sk->linkKnown(
$self,
- htmlspecialchars( wfMsg( 'nextpage', $t->getText() ) ),
+ wfMessage( 'nextpage', $t->getText() )->escaped(),
array(),
$query
);
@@ -446,20 +448,18 @@ class SpecialAllpages extends IncludableSpecialPage {
$out2 .= "</td></tr></table>";
}
- $wgOut->addHTML( $out2 . $out );
- if( isset($prevLink) or isset($nextLink) ) {
- $wgOut->addHTML( '<hr /><p class="mw-allpages-nav">' );
- if( isset( $prevLink ) ) {
- $wgOut->addHTML( $prevLink );
- }
- if( isset( $prevLink ) && isset( $nextLink ) ) {
- $wgOut->addHTML( wfMsgExt( 'pipe-separator' , 'escapenoentities' ) );
- }
- if( isset( $nextLink ) ) {
- $wgOut->addHTML( $nextLink );
- }
- $wgOut->addHTML( '</p>' );
+ $output->addHTML( $out2 . $out );
+
+ $links = array();
+ if ( isset( $prevLink ) ) $links[] = $prevLink;
+ if ( isset( $nextLink ) ) $links[] = $nextLink;
+ if ( count( $links ) ) {
+ $output->addHTML(
+ Html::element( 'hr' ) .
+ Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ),
+ $wgLang->pipeList( $links )
+ ) );
}
}
@@ -468,17 +468,15 @@ class SpecialAllpages extends IncludableSpecialPage {
* @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
*/
- function getNamespaceKeyAndText($ns, $text) {
+ protected function getNamespaceKeyAndText($ns, $text) {
if ( $text == '' )
return array( $ns, '', '' ); # shortcut for common case
$t = Title::makeTitleSafe($ns, $text);
if ( $t && $t->isLocal() ) {
return array( $t->getNamespace(), $t->getDBkey(), $t->getText() );
- } else if ( $t ) {
+ } elseif ( $t ) {
return null;
}
diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php
index 2d5047d2..cbb5df80 100644
--- a/includes/specials/SpecialAncientpages.php
+++ b/includes/specials/SpecialAncientpages.php
@@ -28,8 +28,8 @@
*/
class AncientPagesPage extends QueryPage {
- function getName() {
- return "Ancientpages";
+ function __construct( $name = 'Ancientpages' ) {
+ parent::__construct( $name );
}
function isExpensive() {
@@ -38,20 +38,20 @@ class AncientPagesPage extends QueryPage {
function isSyndicated() { return false; }
- function getSQL() {
- $db = wfGetDB( DB_SLAVE );
- $page = $db->tableName( 'page' );
- $revision = $db->tableName( 'revision' );
- $epoch = $db->unixTimestamp( 'rev_timestamp' );
+ function getQueryInfo() {
+ return array(
+ 'tables' => array( 'page', 'revision' ),
+ 'fields' => array( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'rev_timestamp AS value' ),
+ 'conds' => array( 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0,
+ 'page_latest=rev_id' )
+ );
+ }
- return
- "SELECT 'Ancientpages' as type,
- page_namespace as namespace,
- page_title as title,
- $epoch as value
- FROM $page, $revision
- WHERE page_namespace=".NS_MAIN." AND page_is_redirect=0
- AND page_latest=rev_id";
+ function usesTimestamps() {
+ return true;
}
function sortDescending() {
@@ -67,14 +67,6 @@ class AncientPagesPage extends QueryPage {
$title,
htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) )
);
- return wfSpecialList($link, htmlspecialchars($d) );
+ return wfSpecialList( $link, htmlspecialchars($d) );
}
}
-
-function wfSpecialAncientpages() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $app = new AncientPagesPage();
-
- $app->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialBlankpage.php b/includes/specials/SpecialBlankpage.php
index aaa45a80..42d33779 100644
--- a/includes/specials/SpecialBlankpage.php
+++ b/includes/specials/SpecialBlankpage.php
@@ -32,8 +32,7 @@ class SpecialBlankpage extends UnlistedSpecialPage {
parent::__construct( 'Blankpage' );
}
public function execute( $par ) {
- global $wgOut;
$this->setHeaders();
- $wgOut->addWikiMsg('intentionallyblankpage');
+ $this->getOutput()->addWikiMsg('intentionallyblankpage');
}
}
diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php
new file mode 100644
index 00000000..f1fe8386
--- /dev/null
+++ b/includes/specials/SpecialBlock.php
@@ -0,0 +1,855 @@
+<?php
+/**
+ * Implements Special:Block
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 with 'block' right to block users from
+ * editing pages and other actions
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialBlock extends SpecialPage {
+
+ /** The maximum number of edits a user can have and still be hidden
+ * TODO: config setting? */
+ const HIDEUSER_CONTRIBLIMIT = 1000;
+
+ /** @var User user to be blocked, as passed either by parameter (url?wpTarget=Foo)
+ * or as subpage (Special:Block/Foo) */
+ protected $target;
+
+ /// @var Block::TYPE_ constant
+ protected $type;
+
+ /// @var User|String the previous block target
+ protected $previousTarget;
+
+ /// @var Bool whether the previous submission of the form asked for HideUser
+ protected $requestedHideUser;
+
+ /// @var Bool
+ protected $alreadyBlocked;
+
+ /// @var Array
+ protected $preErrors = array();
+
+ public function __construct() {
+ parent::__construct( 'Block', 'block' );
+ }
+
+ public function execute( $par ) {
+ global $wgUser, $wgOut, $wgRequest;
+
+ # Permission check
+ if( !$this->userCanExecute( $wgUser ) ) {
+ $this->displayRestrictionError();
+ return;
+ }
+
+ # Can't block when the database is locked
+ if( wfReadOnly() ) {
+ throw new ReadOnlyError;
+ }
+
+ # Extract variables from the request. Try not to get into a situation where we
+ # need to extract *every* variable from the form just for processing here, but
+ # there are legitimate uses for some variables
+ list( $this->target, $this->type ) = self::getTargetAndType( $par, $wgRequest );
+ if ( $this->target instanceof User ) {
+ # Set the 'relevant user' in the skin, so it displays links like Contributions,
+ # User logs, UserRights, etc.
+ $this->getSkin()->setRelevantUser( $this->target );
+ }
+
+ list( $this->previousTarget, /*...*/ ) = Block::parseTarget( $wgRequest->getVal( 'wpPreviousTarget' ) );
+ $this->requestedHideUser = $wgRequest->getBool( 'wpHideUser' );
+
+ # bug 15810: blocked admins should have limited access here
+ $status = self::checkUnblockSelf( $this->target );
+ if ( $status !== true ) {
+ throw new ErrorPageError( 'badaccess', $status );
+ }
+
+ $wgOut->setPageTitle( wfMsg( 'blockip-title' ) );
+ $wgOut->addModules( 'mediawiki.special', 'mediawiki.special.block' );
+
+ $out = $this->getOutput();
+ $out->setPageTitle( wfMsg( 'blockip-title' ) );
+ $out->addModules( array( 'mediawiki.special', 'mediawiki.special.block' ) );
+
+ $fields = $this->getFormFields();
+ $this->maybeAlterFormDefaults( $fields );
+
+ $form = new HTMLForm( $fields, $this->getContext() );
+ $form->setWrapperLegend( wfMsg( 'blockip-legend' ) );
+ $form->setSubmitCallback( array( __CLASS__, 'processForm' ) );
+
+ $t = $this->alreadyBlocked
+ ? wfMsg( 'ipb-change-block' )
+ : wfMsg( 'ipbsubmit' );
+ $form->setSubmitText( $t );
+
+ $this->doPreText( $form );
+ $this->doHeadertext( $form );
+ $this->doPostText( $form );
+
+ if( $form->show() ){
+ $wgOut->setPageTitle( wfMsg( 'blockipsuccesssub' ) );
+ $wgOut->addWikiMsg( 'blockipsuccesstext', $this->target );
+ }
+ }
+
+ /**
+ * Get the HTMLForm descriptor array for the block form
+ * @return Array
+ */
+ protected static function getFormFields(){
+ global $wgUser, $wgBlockAllowsUTEdit;
+
+ $a = array(
+ 'Target' => array(
+ 'type' => 'text',
+ 'label-message' => 'ipadressorusername',
+ 'tabindex' => '1',
+ 'id' => 'mw-bi-target',
+ 'size' => '45',
+ 'required' => true,
+ 'validation-callback' => array( __CLASS__, 'validateTargetField' ),
+ ),
+ 'Expiry' => array(
+ 'type' => !count( self::getSuggestedDurations() ) ? 'text' : 'selectorother',
+ 'label-message' => 'ipbexpiry',
+ 'required' => true,
+ 'tabindex' => '2',
+ 'options' => self::getSuggestedDurations(),
+ 'other' => wfMsg( 'ipbother' ),
+ ),
+ 'Reason' => array(
+ 'type' => 'selectandother',
+ 'label-message' => 'ipbreason',
+ 'options-message' => 'ipbreason-dropdown',
+ ),
+ 'CreateAccount' => array(
+ 'type' => 'check',
+ 'label-message' => 'ipbcreateaccount',
+ 'default' => true,
+ ),
+ );
+
+ if( self::canBlockEmail( $wgUser ) ) {
+ $a['DisableEmail'] = array(
+ 'type' => 'check',
+ 'label-message' => 'ipbemailban',
+ );
+ }
+
+ if( $wgBlockAllowsUTEdit ){
+ $a['DisableUTEdit'] = array(
+ 'type' => 'check',
+ 'label-message' => 'ipb-disableusertalk',
+ 'default' => false,
+ );
+ }
+
+ $a['AutoBlock'] = array(
+ 'type' => 'check',
+ 'label-message' => 'ipbenableautoblock',
+ 'default' => true,
+ );
+
+ # Allow some users to hide name from block log, blocklist and listusers
+ if( $wgUser->isAllowed( 'hideuser' ) ) {
+ $a['HideUser'] = array(
+ 'type' => 'check',
+ 'label-message' => 'ipbhidename',
+ 'cssclass' => 'mw-block-hideuser',
+ );
+ }
+
+ # Watchlist their user page? (Only if user is logged in)
+ if( $wgUser->isLoggedIn() ) {
+ $a['Watch'] = array(
+ 'type' => 'check',
+ 'label-message' => 'ipbwatchuser',
+ );
+ }
+
+ $a['HardBlock'] = array(
+ 'type' => 'check',
+ 'label-message' => 'ipb-hardblock',
+ 'default' => false,
+ );
+
+ # This is basically a copy of the Target field, but the user can't change it, so we
+ # can see if the warnings we maybe showed to the user before still apply
+ $a['PreviousTarget'] = array(
+ 'type' => 'hidden',
+ 'default' => false,
+ );
+
+ # We'll turn this into a checkbox if we need to
+ $a['Confirm'] = array(
+ 'type' => 'hidden',
+ 'default' => '',
+ 'label-message' => 'ipb-confirm',
+ );
+
+ return $a;
+ }
+
+ /**
+ * If the user has already been blocked with similar settings, load that block
+ * and change the defaults for the form fields to match the existing settings.
+ * @param &$fields Array HTMLForm descriptor array
+ * @return Bool whether fields were altered (that is, whether the target is
+ * already blocked)
+ */
+ protected function maybeAlterFormDefaults( &$fields ){
+ global $wgRequest, $wgUser;
+
+ # This will be overwritten by request data
+ $fields['Target']['default'] = (string)$this->target;
+
+ # This won't be
+ $fields['PreviousTarget']['default'] = (string)$this->target;
+
+ $block = Block::newFromTarget( $this->target );
+
+ if( $block instanceof Block && !$block->mAuto # The block exists and isn't an autoblock
+ && ( $this->type != Block::TYPE_RANGE # The block isn't a rangeblock
+ || $block->getTarget() == $this->target ) # or if it is, the range is what we're about to block
+ )
+ {
+ $fields['HardBlock']['default'] = $block->isHardblock();
+ $fields['CreateAccount']['default'] = $block->prevents( 'createaccount' );
+ $fields['AutoBlock']['default'] = $block->isAutoblocking();
+ if( isset( $fields['DisableEmail'] ) ){
+ $fields['DisableEmail']['default'] = $block->prevents( 'sendemail' );
+ }
+ if( isset( $fields['HideUser'] ) ){
+ $fields['HideUser']['default'] = $block->mHideName;
+ }
+ if( isset( $fields['DisableUTEdit'] ) ){
+ $fields['DisableUTEdit']['default'] = $block->prevents( 'editownusertalk' );
+ }
+ $fields['Reason']['default'] = $block->mReason;
+
+ if( $wgRequest->wasPosted() ){
+ # Ok, so we got a POST submission asking us to reblock a user. So show the
+ # confirm checkbox; the user will only see it if they haven't previously
+ $fields['Confirm']['type'] = 'check';
+ } else {
+ # We got a target, but it wasn't a POST request, so the user must have gone
+ # to a link like [[Special:Block/User]]. We don't need to show the checkbox
+ # as long as they go ahead and block *that* user
+ $fields['Confirm']['default'] = 1;
+ }
+
+ if( $block->mExpiry == 'infinity' ) {
+ $fields['Expiry']['default'] = 'indefinite';
+ } else {
+ $fields['Expiry']['default'] = wfTimestamp( TS_RFC2822, $block->mExpiry );
+ }
+
+ $this->alreadyBlocked = true;
+ $this->preErrors[] = array( 'ipb-needreblock', (string)$block->getTarget() );
+ }
+
+ # We always need confirmation to do HideUser
+ if( $this->requestedHideUser ){
+ $fields['Confirm']['type'] = 'check';
+ unset( $fields['Confirm']['default'] );
+ $this->preErrors[] = 'ipb-confirmhideuser';
+ }
+
+ # Or if the user is trying to block themselves
+ if( (string)$this->target === $wgUser->getName() ){
+ $fields['Confirm']['type'] = 'check';
+ unset( $fields['Confirm']['default'] );
+ $this->preErrors[] = 'ipb-blockingself';
+ }
+ }
+
+ /**
+ * Add header elements like block log entries, etc.
+ * @param $form HTMLForm
+ * @return void
+ */
+ protected function doPreText( HTMLForm &$form ){
+ $form->addPreText( wfMsgExt( 'blockiptext', 'parse' ) );
+
+ $otherBlockMessages = array();
+ if( $this->target !== null ) {
+ # Get other blocks, i.e. from GlobalBlocking or TorBlock extension
+ wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockMessages, $this->target ) );
+
+ if( count( $otherBlockMessages ) ) {
+ $s = Html::rawElement(
+ 'h2',
+ array(),
+ wfMsgExt( 'ipb-otherblocks-header', 'parseinline', count( $otherBlockMessages ) )
+ ) . "\n";
+ $list = '';
+ foreach( $otherBlockMessages as $link ) {
+ $list .= Html::rawElement( 'li', array(), $link ) . "\n";
+ }
+ $s .= Html::rawElement(
+ 'ul',
+ array( 'class' => 'mw-blockip-alreadyblocked' ),
+ $list
+ ) . "\n";
+ $form->addPreText( $s );
+ }
+ }
+ }
+
+ /**
+ * Add header text inside the form, just underneath where the errors would go
+ * @param $form HTMLForm
+ * @return void
+ */
+ protected function doHeaderText( HTMLForm &$form ){
+ global $wgRequest;
+ # Don't need to do anything if the form has been posted
+ if( !$wgRequest->wasPosted() && $this->preErrors ){
+ $s = HTMLForm::formatErrors( $this->preErrors );
+ if( $s ){
+ $form->addHeaderText( Html::rawElement(
+ 'div',
+ array( 'class' => 'error' ),
+ $s
+ ) );
+ }
+ }
+ }
+
+ /**
+ * Add footer elements to the form
+ * @param $form HTMLForm
+ * @return void
+ */
+ protected function doPostText( HTMLForm &$form ){
+ global $wgUser, $wgLang;
+
+ # Link to the user's contributions, if applicable
+ if( $this->target instanceof User ){
+ $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->target->getName() );
+ $links[] = Linker::link(
+ $contribsPage,
+ wfMsgExt( 'ipb-blocklist-contribs', 'escape', $this->target->getName() )
+ );
+ }
+
+ # Link to unblock the specified user, or to a blank unblock form
+ if( $this->target instanceof User ) {
+ $message = wfMsgExt( 'ipb-unblock-addr', array( 'parseinline' ), $this->target->getName() );
+ $list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
+ } else {
+ $message = wfMsgExt( 'ipb-unblock', array( 'parseinline' ) );
+ $list = SpecialPage::getTitleFor( 'Unblock' );
+ }
+ $links[] = Linker::linkKnown( $list, $message, array() );
+
+ # Link to the block list
+ $links[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'BlockList' ),
+ wfMsg( 'ipb-blocklist' )
+ );
+
+ # Link to edit the block dropdown reasons, if applicable
+ if ( $wgUser->isAllowed( 'editinterface' ) ) {
+ $links[] = Linker::link(
+ Title::makeTitle( NS_MEDIAWIKI, 'Ipbreason-dropdown' ),
+ wfMsgHtml( 'ipb-edit-dropdown' ),
+ array(),
+ array( 'action' => 'edit' )
+ );
+ }
+
+ $form->addPostText( Html::rawElement(
+ 'p',
+ array( 'class' => 'mw-ipb-conveniencelinks' ),
+ $wgLang->pipeList( $links )
+ ) );
+
+ if( $this->target instanceof User ){
+ # Get relevant extracts from the block and suppression logs, if possible
+ $userpage = $this->target->getUserPage();
+ $out = '';
+
+ LogEventsList::showLogExtract(
+ $out,
+ 'block',
+ $userpage->getPrefixedText(),
+ '',
+ array(
+ 'lim' => 10,
+ 'msgKey' => array( 'blocklog-showlog', $userpage->getText() ),
+ 'showIfEmpty' => false
+ )
+ );
+ $form->addPostText( $out );
+
+ # Add suppression block entries if allowed
+ if( $wgUser->isAllowed( 'suppressionlog' ) ) {
+ LogEventsList::showLogExtract(
+ $out,
+ 'suppress',
+ $userpage->getPrefixedText(),
+ '',
+ array(
+ 'lim' => 10,
+ 'conds' => array( 'log_action' => array( 'block', 'reblock', 'unblock' ) ),
+ 'msgKey' => array( 'blocklog-showsuppresslog', $userpage->getText() ),
+ 'showIfEmpty' => false
+ )
+ );
+ $form->addPostText( $out );
+ }
+ }
+ }
+
+ /**
+ * Determine the target of the block, and the type of target
+ * TODO: should be in Block.php?
+ * @param $par String subpage parameter passed to setup, or data value from
+ * the HTMLForm
+ * @param $request WebRequest optionally try and get data from a request too
+ * @return void
+ */
+ public static function getTargetAndType( $par, WebRequest $request = null ){
+ $i = 0;
+ $target = null;
+ while( true ){
+ switch( $i++ ){
+ case 0:
+ # The HTMLForm will check wpTarget first and only if it doesn't get
+ # a value use the default, which will be generated from the options
+ # below; so this has to have a higher precedence here than $par, or
+ # we could end up with different values in $this->target and the HTMLForm!
+ if( $request instanceof WebRequest ){
+ $target = $request->getText( 'wpTarget', null );
+ }
+ break;
+ case 1:
+ $target = $par;
+ break;
+ case 2:
+ if( $request instanceof WebRequest ){
+ $target = $request->getText( 'ip', null );
+ }
+ break;
+ case 3:
+ # B/C @since 1.18
+ if( $request instanceof WebRequest ){
+ $target = $request->getText( 'wpBlockAddress', null );
+ }
+ break;
+ case 4:
+ break 2;
+ }
+ list( $target, $type ) = Block::parseTarget( $target );
+ if( $type !== null ){
+ return array( $target, $type );
+ }
+ }
+ return array( null, null );
+ }
+
+ /**
+ * HTMLForm field validation-callback for Target field.
+ * @since 1.18
+ * @param $value String
+ * @param $alldata Array
+ * @return Message
+ */
+ public static function validateTargetField( $value, $alldata = null ) {
+ global $wgBlockCIDRLimit;
+
+ list( $target, $type ) = self::getTargetAndType( $value );
+
+ if( $type == Block::TYPE_USER ){
+ # TODO: why do we not have a User->exists() method?
+ if( !$target->getId() ){
+ return wfMessage( 'nosuchusershort',
+ wfEscapeWikiText( $target->getName() ) );
+ }
+
+ $status = self::checkUnblockSelf( $target );
+ if ( $status !== true ) {
+ return wfMessage( 'badaccess', $status );
+ }
+
+ } elseif( $type == Block::TYPE_RANGE ){
+ list( $ip, $range ) = explode( '/', $target, 2 );
+
+ if( ( IP::isIPv4( $ip ) && $wgBlockCIDRLimit['IPv4'] == 32 )
+ || ( IP::isIPv6( $ip ) && $wgBlockCIDRLimit['IPv6'] == 128 ) )
+ {
+ # Range block effectively disabled
+ return wfMessage( 'range_block_disabled' );
+ }
+
+ if( ( IP::isIPv4( $ip ) && $range > 32 )
+ || ( IP::isIPv6( $ip ) && $range > 128 ) )
+ {
+ # Dodgy range
+ return wfMessage( 'ip_range_invalid' );
+ }
+
+ if( IP::isIPv4( $ip ) && $range < $wgBlockCIDRLimit['IPv4'] ) {
+ return wfMessage( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] );
+ }
+
+ if( IP::isIPv6( $ip ) && $range < $wgBlockCIDRLimit['IPv6'] ) {
+ return wfMessage( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] );
+ }
+
+ } elseif( $type == Block::TYPE_IP ){
+ # All is well
+
+ } else {
+ return wfMessage( 'badipaddress' );
+ }
+
+ return true;
+ }
+
+ /**
+ * Given the form data, actually implement a block
+ * @param $data Array
+ * @return Bool|String
+ */
+ public static function processForm( array $data ){
+ global $wgUser, $wgBlockAllowsUTEdit;
+
+ // Handled by field validator callback
+ // self::validateTargetField( $data['Target'] );
+
+ # This might have been a hidden field or a checkbox, so interesting data
+ # can come from it
+ $data['Confirm'] = !in_array( $data['Confirm'], array( '', '0', null, false ), true );
+
+ list( $target, $type ) = self::getTargetAndType( $data['Target'] );
+ if( $type == Block::TYPE_USER ){
+ $user = $target;
+ $target = $user->getName();
+ $userId = $user->getId();
+
+ # Give admins a heads-up before they go and block themselves. Much messier
+ # to do this for IPs, but it's pretty unlikely they'd ever get the 'block'
+ # permission anyway, although the code does allow for it
+ if( $target === $wgUser->getName() &&
+ ( $data['PreviousTarget'] !== $data['Target'] || !$data['Confirm'] ) )
+ {
+ return array( 'ipb-blockingself' );
+ }
+
+ } elseif( $type == Block::TYPE_RANGE ){
+ $userId = 0;
+
+ } elseif( $type == Block::TYPE_IP ){
+ $target = $target->getName();
+ $userId = 0;
+
+ } else {
+ # This should have been caught in the form field validation
+ return array( 'badipaddress' );
+ }
+
+ if( ( strlen( $data['Expiry'] ) == 0) || ( strlen( $data['Expiry'] ) > 50 )
+ || !self::parseExpiryInput( $data['Expiry'] ) )
+ {
+ return array( 'ipb_expiry_invalid' );
+ }
+
+ if( !isset( $data['DisableEmail'] ) ){
+ $data['DisableEmail'] = false;
+ }
+
+ # If the user has done the form 'properly', they won't even have been given the
+ # option to suppress-block unless they have the 'hideuser' permission
+ if( !isset( $data['HideUser'] ) ){
+ $data['HideUser'] = false;
+ }
+ if( $data['HideUser'] ) {
+ if( !$wgUser->isAllowed('hideuser') ){
+ # this codepath is unreachable except by a malicious user spoofing forms,
+ # or by race conditions (user has oversight and sysop, loads block form,
+ # and is de-oversighted before submission); so need to fail completely
+ # rather than just silently disable hiding
+ return array( 'badaccess-group0' );
+ }
+
+ # Recheck params here...
+ if( $type != Block::TYPE_USER ) {
+ $data['HideUser'] = false; # IP users should not be hidden
+
+ } elseif( !in_array( $data['Expiry'], array( 'infinite', 'infinity', 'indefinite' ) ) ) {
+ # Bad expiry.
+ return array( 'ipb_expiry_temp' );
+
+ } elseif( $user->getEditCount() > self::HIDEUSER_CONTRIBLIMIT ) {
+ # Typically, the user should have a handful of edits.
+ # Disallow hiding users with many edits for performance.
+ return array( 'ipb_hide_invalid' );
+
+ } elseif( !$data['Confirm'] ){
+ return array( 'ipb-confirmhideuser' );
+ }
+ }
+
+ # Create block object.
+ $block = new Block();
+ $block->setTarget( $target );
+ $block->setBlocker( $wgUser );
+ $block->mReason = $data['Reason'][0];
+ $block->mExpiry = self::parseExpiryInput( $data['Expiry'] );
+ $block->prevents( 'createaccount', $data['CreateAccount'] );
+ $block->prevents( 'editownusertalk', ( !$wgBlockAllowsUTEdit || $data['DisableUTEdit'] ) );
+ $block->prevents( 'sendemail', $data['DisableEmail'] );
+ $block->isHardblock( $data['HardBlock'] );
+ $block->isAutoblocking( $data['AutoBlock'] );
+ $block->mHideName = $data['HideUser'];
+
+ if( !wfRunHooks( 'BlockIp', array( &$block, &$wgUser ) ) ) {
+ return array( 'hookaborted' );
+ }
+
+ # Try to insert block. Is there a conflicting block?
+ $status = $block->insert();
+ if( !$status ) {
+ # Show form unless the user is already aware of this...
+ if( !$data['Confirm'] || ( array_key_exists( 'PreviousTarget', $data )
+ && $data['PreviousTarget'] !== $target ) )
+ {
+ return array( array( 'ipb_already_blocked', $block->getTarget() ) );
+ # Otherwise, try to update the block...
+ } else {
+ # This returns direct blocks before autoblocks/rangeblocks, since we should
+ # be sure the user is blocked by now it should work for our purposes
+ $currentBlock = Block::newFromTarget( $target );
+
+ if( $block->equals( $currentBlock ) ) {
+ return array( array( 'ipb_already_blocked', $block->getTarget() ) );
+ }
+
+ # If the name was hidden and the blocking user cannot hide
+ # names, then don't allow any block changes...
+ if( $currentBlock->mHideName && !$wgUser->isAllowed( 'hideuser' ) ) {
+ return array( 'cant-see-hidden-user' );
+ }
+
+ $currentBlock->delete();
+ $status = $block->insert();
+ $logaction = 'reblock';
+
+ # Unset _deleted fields if requested
+ if( $currentBlock->mHideName && !$data['HideUser'] ) {
+ RevisionDeleteUser::unsuppressUserName( $target, $userId );
+ }
+
+ # If hiding/unhiding a name, this should go in the private logs
+ if( (bool)$currentBlock->mHideName ){
+ $data['HideUser'] = true;
+ }
+ }
+ } else {
+ $logaction = 'block';
+ }
+
+ wfRunHooks( 'BlockIpComplete', array( $block, $wgUser ) );
+
+ # Set *_deleted fields if requested
+ if( $data['HideUser'] ) {
+ RevisionDeleteUser::suppressUserName( $target, $userId );
+ }
+
+ # Can't watch a rangeblock
+ if( $type != Block::TYPE_RANGE && $data['Watch'] ) {
+ $wgUser->addWatch( Title::makeTitle( NS_USER, $target ) );
+ }
+
+ # Block constructor sanitizes certain block options on insert
+ $data['BlockEmail'] = $block->prevents( 'sendemail' );
+ $data['AutoBlock'] = $block->isAutoblocking();
+
+ # Prepare log parameters
+ $logParams = array();
+ $logParams[] = $data['Expiry'];
+ $logParams[] = self::blockLogFlags( $data, $type );
+
+ # Make log entry, if the name is hidden, put it in the oversight log
+ $log_type = $data['HideUser'] ? 'suppress' : 'block';
+ $log = new LogPage( $log_type );
+ $log_id = $log->addEntry(
+ $logaction,
+ Title::makeTitle( NS_USER, $target ),
+ $data['Reason'][0],
+ $logParams
+ );
+ # Relate log ID to block IDs (bug 25763)
+ $blockIds = array_merge( array( $status['id'] ), $status['autoIds'] );
+ $log->addRelations( 'ipb_id', $blockIds, $log_id );
+
+ # Report to the user
+ return true;
+ }
+
+ /**
+ * Get an array of suggested block durations from MediaWiki:Ipboptions
+ * @todo FIXME: This uses a rather odd syntax for the options, should it be converted
+ * to the standard "**<duration>|<displayname>" format?
+ * @param $lang Language|null the language to get the durations in, or null to use
+ * the wiki's content language
+ * @return Array
+ */
+ public static function getSuggestedDurations( $lang = null ){
+ $a = array();
+ $msg = $lang === null
+ ? wfMessage( 'ipboptions' )->inContentLanguage()->text()
+ : wfMessage( 'ipboptions' )->inLanguage( $lang )->text();
+
+ if( $msg == '-' ){
+ return array();
+ }
+
+ foreach( explode( ',', $msg ) as $option ) {
+ if( strpos( $option, ':' ) === false ){
+ $option = "$option:$option";
+ }
+ list( $show, $value ) = explode( ':', $option );
+ $a[htmlspecialchars( $show )] = htmlspecialchars( $value );
+ }
+ return $a;
+ }
+
+ /**
+ * Convert a submitted expiry time, which may be relative ("2 weeks", etc) or absolute
+ * ("24 May 2034", etc), into an absolute timestamp we can put into the database.
+ * @param $expiry String: whatever was typed into the form
+ * @return String: timestamp or "infinity" string for the DB implementation
+ */
+ public static function parseExpiryInput( $expiry ) {
+ static $infinity;
+ if( $infinity == null ){
+ $infinity = wfGetDB( DB_SLAVE )->getInfinity();
+ }
+ if ( $expiry == 'infinite' || $expiry == 'indefinite' ) {
+ $expiry = $infinity;
+ } else {
+ $expiry = strtotime( $expiry );
+ if ( $expiry < 0 || $expiry === false ) {
+ return false;
+ }
+ $expiry = wfTimestamp( TS_MW, $expiry );
+ }
+ return $expiry;
+ }
+
+ /**
+ * Can we do an email block?
+ * @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|String
+ * @return Bool|String true or error message key
+ */
+ public static function checkUnblockSelf( $user ) {
+ global $wgUser;
+ if ( is_int( $user ) ) {
+ $user = User::newFromId( $user );
+ } elseif ( is_string( $user ) ) {
+ $user = User::newFromName( $user );
+ }
+ if( $wgUser->isBlocked() ){
+ if( $user instanceof User && $user->getId() == $wgUser->getId() ) {
+ # User is trying to unblock themselves
+ if ( $wgUser->isAllowed( 'unblockself' ) ) {
+ return true;
+ # User blocked themselves and is now trying to reverse it
+ } elseif ( $wgUser->blockedBy() === $wgUser->getName() ) {
+ return true;
+ } else {
+ return 'ipbnounblockself';
+ }
+ } else {
+ # User is trying to block/unblock someone else
+ return 'ipbblocked';
+ }
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Return a comma-delimited list of "flags" to be passed to the log
+ * reader for this block, to provide more information in the logs
+ * @param $data Array from HTMLForm data
+ * @param $type Block::TYPE_ constant
+ * @return array
+ */
+ protected static function blockLogFlags( array $data, $type ) {
+ global $wgBlockAllowsUTEdit;
+ $flags = array();
+
+ # when blocking a user the option 'anononly' is not available/has no effect -> do not write this into log
+ if( !$data['HardBlock'] && $type != Block::TYPE_USER ){
+ $flags[] = 'anononly';
+ }
+
+ if( $data['CreateAccount'] ){
+ $flags[] = 'nocreate';
+ }
+
+ # Same as anononly, this is not displayed when blocking an IP address
+ if( !$data['AutoBlock'] && $type != Block::TYPE_IP ){
+ $flags[] = 'noautoblock';
+ }
+
+ if( $data['DisableEmail'] ){
+ $flags[] = 'noemail';
+ }
+
+ if( $wgBlockAllowsUTEdit && $data['DisableUTEdit'] ){
+ $flags[] = 'nousertalk';
+ }
+
+ if( $data['HideUser'] ){
+ $flags[] = 'hiddenname';
+ }
+
+ return implode( ',', $flags );
+ }
+}
+
+# BC @since 1.18
+class IPBlockForm extends SpecialBlock {}
diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php
new file mode 100644
index 00000000..ebeb5874
--- /dev/null
+++ b/includes/specials/SpecialBlockList.php
@@ -0,0 +1,437 @@
+<?php
+/**
+ * Implements Special:BlockList
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write 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 existing blocks
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialBlockList extends SpecialPage {
+
+ protected $target, $options;
+
+ function __construct() {
+ parent::__construct( 'BlockList' );
+ }
+
+ /**
+ * Main execution point
+ *
+ * @param $par String title fragment
+ */
+ public function execute( $par ) {
+ global $wgOut, $wgRequest, $wgLang;
+
+ $this->setHeaders();
+ $this->outputHeader();
+ $wgOut->setPageTitle( wfMsg( 'ipblocklist' ) );
+ $wgOut->addModuleStyles( 'mediawiki.special' );
+
+ $par = $wgRequest->getVal( 'ip', $par );
+ $this->target = trim( $wgRequest->getVal( 'wpTarget', $par ) );
+
+ $this->options = $wgRequest->getArray( 'wpOptions', array() );
+
+ $action = $wgRequest->getText( 'action' );
+
+ if( $action == 'unblock' || $action == 'submit' && $wgRequest->wasPosted() ) {
+ # B/C @since 1.18: Unblock interface is now at Special:Unblock
+ $title = SpecialPage::getTitleFor( 'Unblock', $this->target );
+ $wgOut->redirect( $title->getFullUrl() );
+ return;
+ }
+
+ # Just show the block list
+ $fields = array(
+ 'Target' => array(
+ 'type' => 'text',
+ 'label-message' => 'ipadressorusername',
+ 'tabindex' => '1',
+ 'size' => '45',
+ ),
+ 'Options' => array(
+ 'type' => 'multiselect',
+ 'options' => array(
+ wfMsg( 'blocklist-userblocks' ) => 'userblocks',
+ wfMsg( 'blocklist-tempblocks' ) => 'tempblocks',
+ wfMsg( 'blocklist-addressblocks' ) => 'addressblocks',
+ ),
+ 'flatlist' => true,
+ ),
+ 'Limit' => array(
+ 'class' => 'HTMLBlockedUsersItemSelect',
+ 'label-message' => 'table_pager_limit_label',
+ 'options' => array(
+ $wgLang->formatNum( 20 ) => 20,
+ $wgLang->formatNum( 50 ) => 50,
+ $wgLang->formatNum( 100 ) => 100,
+ $wgLang->formatNum( 250 ) => 250,
+ $wgLang->formatNum( 500 ) => 500,
+ ),
+ 'name' => 'limit',
+ 'default' => 50,
+ ),
+ );
+ $form = new HTMLForm( $fields, $this->getContext() );
+ $form->setMethod( 'get' );
+ $form->setWrapperLegend( wfMsg( 'ipblocklist-legend' ) );
+ $form->setSubmitText( wfMsg( 'ipblocklist-submit' ) );
+ $form->prepareForm();
+
+ $form->displayForm( '' );
+ $this->showList();
+ }
+
+ function showList() {
+ global $wgOut, $wgUser;
+
+ # Purge expired entries on one in every 10 queries
+ if ( !mt_rand( 0, 10 ) ) {
+ Block::purgeExpired();
+ }
+
+ $conds = array();
+ # Is the user allowed to see hidden blocks?
+ if ( !$wgUser->isAllowed( 'hideuser' ) ){
+ $conds['ipb_deleted'] = 0;
+ }
+
+ if ( $this->target !== '' ){
+ list( $target, $type ) = Block::parseTarget( $this->target );
+
+ switch( $type ){
+ case Block::TYPE_ID:
+ $conds['ipb_id'] = $target;
+ break;
+
+ case Block::TYPE_IP:
+ case Block::TYPE_RANGE:
+ list( $start, $end ) = IP::parseRange( $target );
+ $dbr = wfGetDB( DB_SLAVE );
+ $conds[] = $dbr->makeList(
+ array(
+ 'ipb_address' => $target,
+ Block::getRangeCond( $start, $end )
+ ),
+ LIST_OR
+ );
+ $conds['ipb_auto'] = 0;
+ break;
+
+ case Block::TYPE_USER:
+ $conds['ipb_address'] = (string)$this->target;
+ $conds['ipb_auto'] = 0;
+ break;
+ }
+ }
+
+ # Apply filters
+ if( in_array( 'userblocks', $this->options ) ) {
+ $conds['ipb_user'] = 0;
+ }
+ if( in_array( 'tempblocks', $this->options ) ) {
+ $conds['ipb_expiry'] = 'infinity';
+ }
+ if( in_array( 'addressblocks', $this->options ) ) {
+ $conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start";
+ }
+
+ # Check for other blocks, i.e. global/tor blocks
+ $otherBlockLink = array();
+ wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockLink, $this->target ) );
+
+ # Show additional header for the local block only when other blocks exists.
+ # Not necessary in a standard installation without such extensions enabled
+ if( count( $otherBlockLink ) ) {
+ $wgOut->addHTML(
+ Html::rawElement( 'h2', array(), wfMsg( 'ipblocklist-localblock' ) ) . "\n"
+ );
+ }
+
+ $pager = new BlockListPager( $this, $conds );
+ if ( $pager->getNumRows() ) {
+ $wgOut->addHTML(
+ $pager->getNavigationBar() .
+ $pager->getBody().
+ $pager->getNavigationBar()
+ );
+
+ } elseif ( $this->target ) {
+ $wgOut->addWikiMsg( 'ipblocklist-no-results' );
+
+ } else {
+ $wgOut->addWikiMsg( 'ipblocklist-empty' );
+ }
+
+ if( count( $otherBlockLink ) ) {
+ $wgOut->addHTML(
+ Html::rawElement(
+ 'h2',
+ array(),
+ wfMsgExt(
+ 'ipblocklist-otherblocks',
+ 'parseinline',
+ count( $otherBlockLink )
+ )
+ ) . "\n"
+ );
+ $list = '';
+ foreach( $otherBlockLink as $link ) {
+ $list .= Html::rawElement( 'li', array(), $link ) . "\n";
+ }
+ $wgOut->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-ipblocklist-otherblocks' ), $list ) . "\n" );
+ }
+ }
+}
+
+class BlockListPager extends TablePager {
+ protected $conds;
+ protected $page;
+
+ function __construct( $page, $conds ) {
+ $this->page = $page;
+ $this->conds = $conds;
+ $this->mDefaultDirection = true;
+ parent::__construct();
+ }
+
+ function getFieldNames() {
+ static $headers = null;
+
+ if ( $headers == array() ) {
+ $headers = array(
+ 'ipb_timestamp' => 'blocklist-timestamp',
+ 'ipb_target' => 'blocklist-target',
+ 'ipb_expiry' => 'blocklist-expiry',
+ 'ipb_by' => 'blocklist-by',
+ 'ipb_params' => 'blocklist-params',
+ 'ipb_reason' => 'blocklist-reason',
+ );
+ $headers = array_map( 'wfMsg', $headers );
+ }
+
+ return $headers;
+ }
+
+ function formatValue( $name, $value ) {
+ global $wgLang, $wgUser;
+
+ static $sk, $msg;
+ if ( empty( $sk ) ) {
+ $sk = $this->getSkin();
+ $msg = array(
+ 'anononlyblock',
+ 'createaccountblock',
+ 'noautoblockblock',
+ 'emailblock',
+ 'blocklist-nousertalk',
+ 'unblocklink',
+ 'change-blocklink',
+ 'infiniteblock',
+ );
+ $msg = array_combine( $msg, array_map( 'wfMessage', $msg ) );
+ }
+
+ $row = $this->mCurrentRow;
+ $formatted = '';
+
+ switch( $name ) {
+ case 'ipb_timestamp':
+ $formatted = $wgLang->timeanddate( $value, /* User preference timezone */ true );
+ break;
+
+ case 'ipb_target':
+ if( $row->ipb_auto ){
+ $formatted = wfMessage( 'autoblockid', $row->ipb_id )->parse();
+ } else {
+ list( $target, $type ) = Block::parseTarget( $row->ipb_address );
+ switch( $type ){
+ case Block::TYPE_USER:
+ case Block::TYPE_IP:
+ $formatted = $sk->userLink( $target->getId(), $target );
+ $formatted .= $sk->userToolLinks(
+ $target->getId(),
+ $target,
+ false,
+ Linker::TOOL_LINKS_NOBLOCK
+ );
+ break;
+ case Block::TYPE_RANGE:
+ $formatted = htmlspecialchars( $target );
+ }
+ }
+ break;
+
+ case 'ipb_expiry':
+ $formatted = $wgLang->formatExpiry( $value, /* User preference timezone */ true );
+ if( $wgUser->isAllowed( 'block' ) ){
+ if( $row->ipb_auto ){
+ $links[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Unblock' ),
+ $msg['unblocklink'],
+ array(),
+ array( 'wpTarget' => "#{$row->ipb_id}" )
+ );
+ } else {
+ $links[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Unblock', $row->ipb_address ),
+ $msg['unblocklink']
+ );
+ $links[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Block', $row->ipb_address ),
+ $msg['change-blocklink']
+ );
+ }
+ $formatted .= ' ' . Html::rawElement(
+ 'span',
+ array( 'class' => 'mw-blocklist-actions' ),
+ wfMsg( 'parentheses', $wgLang->pipeList( $links ) )
+ );
+ }
+ break;
+
+ case 'ipb_by':
+ $user = User::newFromId( $value );
+ if( $user instanceof User ){
+ $formatted = $sk->userLink( $user->getId(), $user->getName() );
+ $formatted .= $sk->userToolLinks( $user->getId(), $user->getName() );
+ }
+ break;
+
+ case 'ipb_reason':
+ $formatted = $sk->commentBlock( $value );
+ break;
+
+ case 'ipb_params':
+ $properties = array();
+ if ( $row->ipb_anon_only ) {
+ $properties[] = $msg['anononlyblock'];
+ }
+ if ( $row->ipb_create_account ) {
+ $properties[] = $msg['createaccountblock'];
+ }
+ if ( $row->ipb_user && !$row->ipb_enable_autoblock ) {
+ $properties[] = $msg['noautoblockblock'];
+ }
+
+ if ( $row->ipb_block_email ) {
+ $properties[] = $msg['emailblock'];
+ }
+
+ if ( !$row->ipb_allow_usertalk ) {
+ $properties[] = $msg['blocklist-nousertalk'];
+ }
+
+ $formatted = $wgLang->commaList( $properties );
+ break;
+
+ default:
+ $formatted = "Unable to format $name";
+ break;
+ }
+
+ return $formatted;
+ }
+
+ function getQueryInfo() {
+ $info = array(
+ 'tables' => array( 'ipblocks' ),
+ 'fields' => array(
+ 'ipb_id',
+ 'ipb_address',
+ 'ipb_user',
+ 'ipb_by',
+ 'ipb_reason',
+ 'ipb_timestamp',
+ 'ipb_auto',
+ 'ipb_anon_only',
+ 'ipb_create_account',
+ 'ipb_enable_autoblock',
+ 'ipb_expiry',
+ 'ipb_range_start',
+ 'ipb_range_end',
+ 'ipb_deleted',
+ 'ipb_block_email',
+ 'ipb_allow_usertalk',
+ ),
+ 'conds' => $this->conds,
+ );
+
+ global $wgUser;
+ # Is the user allowed to see hidden blocks?
+ if ( !$wgUser->isAllowed( 'hideuser' ) ){
+ $conds['ipb_deleted'] = 0;
+ }
+
+ return $info;
+ }
+
+ public function getTableClass(){
+ return 'TablePager mw-blocklist';
+ }
+
+ function getIndexField() {
+ return 'ipb_timestamp';
+ }
+
+ function getDefaultSort() {
+ return 'ipb_timestamp';
+ }
+
+ function isFieldSortable( $name ) {
+ return false;
+ }
+
+ function getTitle() {
+ return $this->page->getTitle();
+ }
+}
+
+/**
+ * Items per page dropdown. Essentially a crap workaround for bug 32603.
+ *
+ * @todo Do not release 1.19 with this.
+ */
+class HTMLBlockedUsersItemSelect extends HTMLSelectField {
+ /**
+ * Basically don't do any validation. If it's a number that's fine. Also,
+ * add it to the list if it's not there already
+ *
+ * @param $value
+ * @param $alldata
+ * @return bool
+ */
+ function validate( $value, $alldata ) {
+ if ( $value == '' ) {
+ return true;
+ }
+
+ if ( !in_array( $value, $this->mParams['options'] ) ) {
+ $this->mParams['options'][ $this->mParent->getLanguage()->formatNum( $value ) ] = intval($value);
+ asort( $this->mParams['options'] );
+ }
+
+ return true;
+ }
+
+}
diff --git a/includes/specials/SpecialBlockip.php b/includes/specials/SpecialBlockip.php
deleted file mode 100644
index 28a0f3f1..00000000
--- a/includes/specials/SpecialBlockip.php
+++ /dev/null
@@ -1,892 +0,0 @@
-<?php
-/**
- * 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
- */
-
-/**
- * A special page that allows users with 'block' right to block users from
- * editing pages and other actions
- *
- * @ingroup SpecialPage
- */
-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;
-
- public function __construct() {
- parent::__construct( 'Blockip', 'block' );
- }
-
- public function execute( $par ) {
- global $wgUser, $wgOut, $wgRequest;
-
- # Can't block when the database is locked
- if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- }
- # Permission check
- if( !$this->userCanExecute( $wgUser ) ) {
- $wgOut->permissionRequired( 'block' );
- return;
- }
-
- $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 );
- }
- }
-
- $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 ) );
- $this->BlockAddress = strtr( $this->BlockAddress, '_', ' ' );
- $this->BlockReason = $wgRequest->getText( 'wpBlockReason' );
- $this->BlockReasonList = $wgRequest->getText( 'wpBlockReasonList' );
- $this->BlockExpiry = $wgRequest->getVal( 'wpBlockExpiry', wfMsg( 'ipbotheroption' ) );
- $this->BlockOther = $wgRequest->getVal( 'wpBlockOther', '' );
-
- # Unchecked checkboxes are not included in the form data at all, so having one
- # that is true by default is a bit tricky
- $byDefault = !$wgRequest->wasPosted();
- $this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly', $byDefault );
- $this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', $byDefault );
- $this->BlockEnableAutoblock = $wgRequest->getBool( 'wpEnableAutoblock', $byDefault );
- $this->BlockEmail = false;
- if( self::canBlockEmail( $wgUser ) ) {
- $this->BlockEmail = $wgRequest->getBool( 'wpEmailBan', false );
- }
- $this->BlockWatchUser = $wgRequest->getBool( 'wpWatchUser', false ) && $wgUser->isLoggedIn();
- # Re-check user's rights to hide names, very serious, defaults to null
- if( $wgUser->isAllowed( 'hideuser' ) ) {
- $this->BlockHideName = $wgRequest->getBool( 'wpHideName', null );
- } else {
- $this->BlockHideName = false;
- }
- $this->BlockAllowUsertalk = ( $wgRequest->getBool( 'wpAllowUsertalk', $byDefault ) && $wgBlockAllowsUTEdit );
- $this->BlockReblock = $wgRequest->getBool( 'wpChangeBlock', false );
-
- $this->wasPosted = $wgRequest->wasPosted();
- }
-
- public function showForm( $err ) {
- global $wgOut, $wgUser, $wgSysopUserBans;
-
- $wgOut->setPageTitle( wfMsg( 'blockip-title' ) );
- $wgOut->addWikiMsg( 'blockiptext' );
-
- if( $wgSysopUserBans ) {
- $mIpaddress = Xml::label( wfMsg( 'ipadressorusername' ), 'mw-bi-target' );
- } else {
- $mIpaddress = Xml::label( wfMsg( 'ipaddress' ), 'mw-bi-target' );
- }
- $mIpbexpiry = Xml::label( wfMsg( 'ipbexpiry' ), 'wpBlockExpiry' );
- $mIpbother = Xml::label( wfMsg( 'ipbother' ), 'mw-bi-other' );
- $mIpbreasonother = Xml::label( wfMsg( 'ipbreason' ), 'wpBlockReasonList' );
- $mIpbreason = Xml::label( wfMsg( 'ipbotherreason' ), 'mw-bi-reason' );
-
- $titleObj = SpecialPage::getTitleFor( 'Blockip' );
- $user = User::newFromName( $this->BlockAddress );
-
- $alreadyBlocked = false;
- $otherBlockedMsgs = array();
- if( $err && $err[0] != 'ipb_already_blocked' ) {
- $key = array_shift( $err );
- $msg = wfMsgReal( $key, $err );
- $wgOut->setSubtitle( wfMsgHtml( 'formerror' ) );
- $wgOut->addHTML( Xml::tags( 'p', array( 'class' => 'error' ), $msg ) );
- } elseif( $this->BlockAddress !== null ) {
- # Get other blocks, i.e. from GlobalBlocking or TorBlock extension
- wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockedMsgs, $this->BlockAddress ) );
-
- $userId = is_object( $user ) ? $user->getId() : 0;
- $currentBlock = Block::newFromDB( $this->BlockAddress, $userId );
- if( !is_null( $currentBlock ) && !$currentBlock->mAuto && # The block exists and isn't an autoblock
- ( $currentBlock->mRangeStart == $currentBlock->mRangeEnd || # The block isn't a rangeblock
- # or if it is, the range is what we're about to block
- ( $currentBlock->mAddress == $this->BlockAddress ) )
- ) {
- $alreadyBlocked = true;
- # Set the block form settings to the existing block
- if( !$this->wasPosted ) {
- $this->BlockAnonOnly = $currentBlock->mAnonOnly;
- $this->BlockCreateAccount = $currentBlock->mCreateAccount;
- $this->BlockEnableAutoblock = $currentBlock->mEnableAutoblock;
- $this->BlockEmail = $currentBlock->mBlockEmail;
- $this->BlockHideName = $currentBlock->mHideName;
- $this->BlockAllowUsertalk = $currentBlock->mAllowUsertalk;
- if( $currentBlock->mExpiry == 'infinity' ) {
- $this->BlockOther = 'indefinite';
- } else {
- $this->BlockOther = wfTimestamp( TS_ISO_8601, $currentBlock->mExpiry );
- }
- $this->BlockReason = $currentBlock->mReason;
- }
- }
- }
-
- # Show other blocks from extensions, i.e. GlockBlocking and TorBlock
- if( count( $otherBlockedMsgs ) ) {
- $wgOut->addHTML(
- Html::rawElement( 'h2', array(), wfMsgExt( 'ipb-otherblocks-header', 'parseinline', count( $otherBlockedMsgs ) ) ) . "\n"
- );
- $list = '';
- foreach( $otherBlockedMsgs as $link ) {
- $list .= Html::rawElement( 'li', array(), $link ) . "\n";
- }
- $wgOut->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-blockip-alreadyblocked' ), $list ) . "\n" );
- }
-
- # Username/IP is blocked already locally
- if( $alreadyBlocked ) {
- $wgOut->wrapWikiMsg( "<div class='mw-ipb-needreblock'>\n$1\n</div>", array( 'ipb-needreblock', $this->BlockAddress ) );
- }
-
- $scBlockExpiryOptions = wfMsgForContent( 'ipboptions' );
-
- $showblockoptions = $scBlockExpiryOptions != '-';
- if( !$showblockoptions ) $mIpbother = $mIpbexpiry;
-
- $blockExpiryFormOptions = Xml::option( wfMsg( 'ipbotheroption' ), 'other' );
- foreach( explode( ',', $scBlockExpiryOptions ) as $option ) {
- if( strpos( $option, ':' ) === false ) $option = "$option:$option";
- list( $show, $value ) = explode( ':', $option );
- $show = htmlspecialchars( $show );
- $value = htmlspecialchars( $value );
- $blockExpiryFormOptions .= Xml::option( $show, $value, $this->BlockExpiry === $value ) . "\n";
- }
-
- $reasonDropDown = Xml::listDropDown( 'wpBlockReasonList',
- wfMsgForContent( 'ipbreason-dropdown' ),
- wfMsgForContent( 'ipbreasonotherlist' ), $this->BlockReasonList, 'wpBlockDropDown', 4 );
-
- $wgOut->addModules( 'mediawiki.legacy.block' );
- $wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ), 'id' => 'blockip' ) ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'blockip-legend' ) ) .
- Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-blockip-table' ) ) .
- "<tr>
- <td class='mw-label'>
- {$mIpaddress}
- </td>
- <td class='mw-input'>" .
- Html::input( 'wpBlockAddress', $this->BlockAddress, 'text', array(
- 'tabindex' => '1',
- 'id' => 'mw-bi-target',
- 'onchange' => 'updateBlockOptions()',
- 'size' => '45',
- 'required' => ''
- ) + ( $this->BlockAddress ? array() : array( 'autofocus' ) ) ). "
- </td>
- </tr>
- <tr>"
- );
- if( $showblockoptions ) {
- $wgOut->addHTML("
- <td class='mw-label'>
- {$mIpbexpiry}
- </td>
- <td class='mw-input'>" .
- Xml::tags( 'select',
- array(
- 'id' => 'wpBlockExpiry',
- 'name' => 'wpBlockExpiry',
- 'onchange' => 'considerChangingExpiryFocus()',
- 'tabindex' => '2' ),
- $blockExpiryFormOptions ) .
- "</td>"
- );
- }
- $wgOut->addHTML("
- </tr>
- <tr id='wpBlockOther'>
- <td class='mw-label'>
- {$mIpbother}
- </td>
- <td class='mw-input'>" .
- Xml::input( 'wpBlockOther', 45, $this->BlockOther,
- array( 'tabindex' => '3', 'id' => 'mw-bi-other' ) ) . "
- </td>
- </tr>
- <tr>
- <td class='mw-label'>
- {$mIpbreasonother}
- </td>
- <td class='mw-input'>
- {$reasonDropDown}
- </td>
- </tr>
- <tr id=\"wpBlockReason\">
- <td class='mw-label'>
- {$mIpbreason}
- </td>
- <td class='mw-input'>" .
- Html::input( 'wpBlockReason', $this->BlockReason, 'text', array(
- 'tabindex' => '5',
- 'id' => 'mw-bi-reason',
- 'maxlength' => '200',
- 'size' => '45'
- ) + ( $this->BlockAddress ? array( 'autofocus' ) : array() ) ) . "
- </td>
- </tr>
- <tr id='wpAnonOnlyRow'>
- <td>&#160;</td>
- <td class='mw-input'>" .
- Xml::checkLabel( wfMsg( 'ipbanononly' ),
- 'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly,
- array( 'tabindex' => '6' ) ) . "
- </td>
- </tr>
- <tr id='wpCreateAccountRow'>
- <td>&#160;</td>
- <td class='mw-input'>" .
- Xml::checkLabel( wfMsg( 'ipbcreateaccount' ),
- 'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount,
- array( 'tabindex' => '7' ) ) . "
- </td>
- </tr>
- <tr id='wpEnableAutoblockRow'>
- <td>&#160;</td>
- <td class='mw-input'>" .
- Xml::checkLabel( wfMsg( 'ipbenableautoblock' ),
- 'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock,
- array( 'tabindex' => '8' ) ) . "
- </td>
- </tr>"
- );
-
- if( self::canBlockEmail( $wgUser ) ) {
- $wgOut->addHTML("
- <tr id='wpEnableEmailBan'>
- <td>&#160;</td>
- <td class='mw-input'>" .
- Xml::checkLabel( wfMsg( 'ipbemailban' ),
- 'wpEmailBan', 'wpEmailBan', $this->BlockEmail,
- array( 'tabindex' => '9' ) ) . "
- </td>
- </tr>"
- );
- }
-
- // Allow some users to hide name from block log, blocklist and listusers
- if( $wgUser->isAllowed( 'hideuser' ) ) {
- $wgOut->addHTML("
- <tr id='wpEnableHideUser'>
- <td>&#160;</td>
- <td class='mw-input'><strong>" .
- Xml::checkLabel( wfMsg( 'ipbhidename' ),
- 'wpHideName', 'wpHideName', $this->BlockHideName,
- array( 'tabindex' => '10' )
- ) . "
- </strong></td>
- </tr>"
- );
- }
-
- # Watchlist their user page? (Only if user is logged in)
- if( $wgUser->isLoggedIn() ) {
- $wgOut->addHTML("
- <tr id='wpEnableWatchUser'>
- <td>&#160;</td>
- <td class='mw-input'>" .
- Xml::checkLabel( wfMsg( 'ipbwatchuser' ),
- 'wpWatchUser', 'wpWatchUser', $this->BlockWatchUser,
- array( 'tabindex' => '11' ) ) . "
- </td>
- </tr>"
- );
- }
-
- # Can we explicitly disallow the use of user_talk?
- global $wgBlockAllowsUTEdit;
- if( $wgBlockAllowsUTEdit ){
- $wgOut->addHTML("
- <tr id='wpAllowUsertalkRow'>
- <td>&#160;</td>
- <td class='mw-input'>" .
- Xml::checkLabel( wfMsg( 'ipballowusertalk' ),
- 'wpAllowUsertalk', 'wpAllowUsertalk', $this->BlockAllowUsertalk,
- array( 'tabindex' => '12' ) ) . "
- </td>
- </tr>"
- );
- }
-
- $wgOut->addHTML("
- <tr>
- <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' )
- + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'blockip-block' ) ). "
- </td>
- </tr>" .
- Xml::closeElement( 'table' ) .
- Html::hidden( 'wpEditToken', $wgUser->editToken() ) .
- ( $alreadyBlocked ? Html::hidden( 'wpChangeBlock', 1 ) : "" ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
- );
-
- $wgOut->addHTML( $this->getConvenienceLinks() );
-
- if( is_object( $user ) ) {
- $this->showLogFragment( $wgOut, $user->getUserPage() );
- } elseif( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->BlockAddress ) ) {
- $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) );
- } elseif( preg_match( '/^\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}/', $this->BlockAddress ) ) {
- $this->showLogFragment( $wgOut, Title::makeTitle( NS_USER, $this->BlockAddress ) );
- }
- }
-
- /**
- * Can we do an email block?
- * @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.
- * $userID and $expiry will be filled accordingly
- * @return array(message key, arguments) on failure, empty array on success
- */
- function doBlock( &$userId = null, &$expiry = null ) {
- global $wgUser, $wgSysopUserBans, $wgSysopRangeBans, $wgBlockAllowsUTEdit, $wgBlockCIDRLimit;
-
- $userId = 0;
- # Expand valid IPv6 addresses, usernames are left as is
- $this->BlockAddress = IP::sanitizeIP( $this->BlockAddress );
- # isIPv4() and IPv6() are used for final validation
- $rxIP4 = '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}';
- $rxIP6 = '\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}:\w{1,4}';
- $rxIP = "($rxIP4|$rxIP6)";
-
- # Check for invalid specifications
- if( !preg_match( "/^$rxIP$/", $this->BlockAddress ) ) {
- $matches = array();
- if( preg_match( "/^($rxIP4)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) {
- # IPv4
- if( $wgSysopRangeBans && $wgBlockCIDRLimit['IPv4'] != 32 ) {
- if( !IP::isIPv4( $this->BlockAddress ) || $matches[2] > 32 ) {
- return array( 'ip_range_invalid' );
- } elseif ( $matches[2] < $wgBlockCIDRLimit['IPv4'] ) {
- return array( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] );
- }
- $this->BlockAddress = Block::normaliseRange( $this->BlockAddress );
- } else {
- # Range block illegal
- return array( 'range_block_disabled' );
- }
- } elseif( preg_match( "/^($rxIP6)\\/(\\d{1,3})$/", $this->BlockAddress, $matches ) ) {
- # IPv6
- if( $wgSysopRangeBans && $wgBlockCIDRLimit['IPv6'] != 128 ) {
- if( !IP::isIPv6( $this->BlockAddress ) || $matches[2] > 128 ) {
- return array( 'ip_range_invalid' );
- } elseif( $matches[2] < $wgBlockCIDRLimit['IPv6'] ) {
- return array( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] );
- }
- $this->BlockAddress = Block::normaliseRange( $this->BlockAddress );
- } else {
- # Range block illegal
- return array('range_block_disabled');
- }
- } else {
- # Username block
- if( $wgSysopUserBans ) {
- $user = User::newFromName( $this->BlockAddress );
- if( $user instanceof User && $user->getId() ) {
- # Use canonical name
- $userId = $user->getId();
- $this->BlockAddress = $user->getName();
- } else {
- return array( 'nosuchusershort', htmlspecialchars( $user ? $user->getName() : $this->BlockAddress ) );
- }
- } else {
- return array( 'badipaddress' );
- }
- }
- }
-
- if( $wgUser->isBlocked() && ( $wgUser->getId() !== $userId ) ) {
- return array( 'cant-block-while-blocked' );
- }
-
- $reasonstr = $this->BlockReasonList;
- if( $reasonstr != 'other' && $this->BlockReason != '' ) {
- // Entry from drop down menu + additional comment
- $reasonstr .= wfMsgForContent( 'colon-separator' ) . $this->BlockReason;
- } elseif( $reasonstr == 'other' ) {
- $reasonstr = $this->BlockReason;
- }
-
- $expirestr = $this->BlockExpiry;
- if( $expirestr == 'other' )
- $expirestr = $this->BlockOther;
-
- if( ( strlen( $expirestr ) == 0) || ( strlen( $expirestr ) > 50 ) ) {
- return array( 'ipb_expiry_invalid' );
- }
-
- if( false === ( $expiry = Block::parseExpiryInput( $expirestr ) ) ) {
- // Bad expiry.
- return array( 'ipb_expiry_invalid' );
- }
-
- if( $this->BlockHideName ) {
- // Recheck params here...
- if( !$userId || !$wgUser->isAllowed('hideuser') ) {
- $this->BlockHideName = false; // IP users should not be hidden
- } elseif( $expiry !== 'infinity' ) {
- // Bad expiry.
- return array( 'ipb_expiry_temp' );
- } elseif( User::edits( $userId ) > self::HIDEUSER_CONTRIBLIMIT ) {
- // Typically, the user should have a handful of edits.
- // Disallow hiding users with many edits for performance.
- return array( 'ipb_hide_invalid' );
- }
- }
-
- # Create block object
- # Note: for a user block, ipb_address is only for display purposes
- $block = new Block( $this->BlockAddress, $userId, $wgUser->getId(),
- $reasonstr, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly,
- $this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName,
- $this->BlockEmail,
- isset( $this->BlockAllowUsertalk ) ? $this->BlockAllowUsertalk : $wgBlockAllowsUTEdit
- );
-
- # Should this be privately logged?
- $suppressLog = (bool)$this->BlockHideName;
- if( wfRunHooks( 'BlockIp', array( &$block, &$wgUser ) ) ) {
- # Try to insert block. Is there a conflicting block?
- if( !$block->insert() ) {
- # Show form unless the user is already aware of this...
- if( !$this->BlockReblock ) {
- return array( 'ipb_already_blocked' );
- # Otherwise, try to update the block...
- } else {
- # This returns direct blocks before autoblocks/rangeblocks, since we should
- # be sure the user is blocked by now it should work for our purposes
- $currentBlock = Block::newFromDB( $this->BlockAddress, $userId );
- if( $block->equals( $currentBlock ) ) {
- return array( 'ipb_already_blocked' );
- }
- # If the name was hidden and the blocking user cannot hide
- # names, then don't allow any block changes...
- if( $currentBlock->mHideName && !$wgUser->isAllowed( 'hideuser' ) ) {
- return array( 'cant-see-hidden-user' );
- }
- $currentBlock->delete();
- $block->insert();
- # If hiding/unhiding a name, this should go in the private logs
- $suppressLog = $suppressLog || (bool)$currentBlock->mHideName;
- $log_action = 'reblock';
- # Unset _deleted fields if requested
- if( $currentBlock->mHideName && !$this->BlockHideName ) {
- self::unsuppressUserName( $this->BlockAddress, $userId );
- }
- }
- } else {
- $log_action = 'block';
- }
- wfRunHooks( 'BlockIpComplete', array( $block, $wgUser ) );
-
- # Set *_deleted fields if requested
- if( $this->BlockHideName ) {
- self::suppressUserName( $this->BlockAddress, $userId );
- }
-
- # Only show watch link when this is no range block
- if( $this->BlockWatchUser && $block->mRangeStart == $block->mRangeEnd ) {
- $wgUser->addWatch( Title::makeTitle( NS_USER, $this->BlockAddress ) );
- }
-
- # Block constructor sanitizes certain block options on insert
- $this->BlockEmail = $block->mBlockEmail;
- $this->BlockEnableAutoblock = $block->mEnableAutoblock;
-
- # Prepare log parameters
- $logParams = array();
- $logParams[] = $expirestr;
- $logParams[] = $this->blockLogFlags();
-
- # Make log entry, if the name is hidden, put it in the oversight log
- $log_type = $suppressLog ? 'suppress' : 'block';
- $log = new LogPage( $log_type );
- $log->addEntry( $log_action, Title::makeTitle( NS_USER, $this->BlockAddress ),
- $reasonstr, $logParams );
-
- # Report to the user
- return array();
- } else {
- return array( 'hookaborted' );
- }
- }
-
- public static function suppressUserName( $name, $userId, $dbw = null ) {
- $op = '|'; // bitwise OR
- return self::setUsernameBitfields( $name, $userId, $op, $dbw );
- }
-
- public static function unsuppressUserName( $name, $userId, $dbw = null ) {
- $op = '&'; // bitwise AND
- return self::setUsernameBitfields( $name, $userId, $op, $dbw );
- }
-
- private static function setUsernameBitfields( $name, $userId, $op, $dbw ) {
- if( $op !== '|' && $op !== '&' ) return false; // sanity check
- if( !$dbw )
- $dbw = wfGetDB( DB_MASTER );
- $delUser = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
- $delAction = LogPage::DELETED_ACTION | Revision::DELETED_RESTRICTED;
- # Normalize user name
- $userTitle = Title::makeTitleSafe( NS_USER, $name );
- $userDbKey = $userTitle->getDBkey();
- # To suppress, we OR the current bitfields with Revision::DELETED_USER
- # to put a 1 in the username *_deleted bit. To unsuppress we AND the
- # current bitfields with the inverse of Revision::DELETED_USER. The
- # username bit is made to 0 (x & 0 = 0), while others are unchanged (x & 1 = x).
- # The same goes for the sysop-restricted *_deleted bit.
- if( $op == '&' ) {
- $delUser = "~{$delUser}";
- $delAction = "~{$delAction}";
- }
- # Hide name from live edits
- $dbw->update( 'revision', array( "rev_deleted = rev_deleted $op $delUser" ),
- array( 'rev_user' => $userId ), __METHOD__ );
- # Hide name from deleted edits
- $dbw->update( 'archive', array( "ar_deleted = ar_deleted $op $delUser" ),
- array( 'ar_user_text' => $name ), __METHOD__ );
- # Hide name from logs
- $dbw->update( 'logging', array( "log_deleted = log_deleted $op $delUser" ),
- array( 'log_user' => $userId, "log_type != 'suppress'" ), __METHOD__ );
- $dbw->update( 'logging', array( "log_deleted = log_deleted $op $delAction" ),
- array( 'log_namespace' => NS_USER, 'log_title' => $userDbKey,
- "log_type != 'suppress'" ), __METHOD__ );
- # Hide name from RC
- $dbw->update( 'recentchanges', array( "rc_deleted = rc_deleted $op $delUser" ),
- array( 'rc_user_text' => $name ), __METHOD__ );
- $dbw->update( 'recentchanges', array( "rc_deleted = rc_deleted $op $delAction" ),
- array( 'rc_namespace' => NS_USER, 'rc_title' => $userDbKey, 'rc_logid > 0' ), __METHOD__ );
- # Hide name from live images
- $dbw->update( 'oldimage', array( "oi_deleted = oi_deleted $op $delUser" ),
- array( 'oi_user_text' => $name ), __METHOD__ );
- # Hide name from deleted images
- # WMF - schema change pending
- # $dbw->update( 'filearchive', array( "fa_deleted = fa_deleted $op $delUser" ),
- # array( 'fa_user_text' => $name ), __METHOD__ );
- # Done!
- return true;
- }
-
- /**
- * UI entry point for blocking
- * Wraps around doBlock()
- */
- public function doSubmit() {
- global $wgOut;
- $retval = $this->doBlock();
- if( empty( $retval ) ) {
- $titleObj = SpecialPage::getTitleFor( 'Blockip' );
- $wgOut->redirect( $titleObj->getFullURL( 'action=success&ip=' .
- urlencode( $this->BlockAddress ) ) );
- return;
- }
- $this->showForm( $retval );
- }
-
- public function showSuccess() {
- global $wgOut;
-
- $wgOut->setPageTitle( wfMsg( 'blockip-title' ) );
- $wgOut->setSubtitle( wfMsg( 'blockipsuccesssub' ) );
- $text = wfMsgExt( 'blockipsuccesstext', array( 'parse' ), $this->BlockAddress );
- $wgOut->addHTML( $text );
- }
-
- private function showLogFragment( $out, $title ) {
- global $wgUser;
-
- // Used to support GENDER in 'blocklog-showlog' and 'blocklog-showsuppresslog'
- $userBlocked = $title->getText();
-
- LogEventsList::showLogExtract(
- $out,
- 'block',
- $title->getPrefixedText(),
- '',
- array(
- 'lim' => 10,
- 'msgKey' => array(
- 'blocklog-showlog',
- $userBlocked
- ),
- 'showIfEmpty' => false
- )
- );
-
- // Add suppression block entries if allowed
- if( $wgUser->isAllowed( 'suppressionlog' ) ) {
- LogEventsList::showLogExtract( $out, 'suppress', $title->getPrefixedText(), '',
- array(
- 'lim' => 10,
- 'conds' => array(
- 'log_action' => array(
- 'block',
- 'reblock',
- 'unblock'
- )
- ),
- 'msgKey' => array(
- 'blocklog-showsuppresslog',
- $userBlocked
- ),
- 'showIfEmpty' => false
- )
- );
- }
- }
-
- /**
- * Return a comma-delimited list of "flags" to be passed to the log
- * reader for this block, to provide more information in the logs
- *
- * @return array
- */
- private function blockLogFlags() {
- global $wgBlockAllowsUTEdit;
- $flags = array();
- if( $this->BlockAnonOnly && IP::isIPAddress( $this->BlockAddress ) )
- // when blocking a user the option 'anononly' is not available/has no effect -> do not write this into log
- $flags[] = 'anononly';
- if( $this->BlockCreateAccount )
- $flags[] = 'nocreate';
- if( !$this->BlockEnableAutoblock && !IP::isIPAddress( $this->BlockAddress ) )
- // Same as anononly, this is not displayed when blocking an IP address
- $flags[] = 'noautoblock';
- if( $this->BlockEmail )
- $flags[] = 'noemail';
- if( !$this->BlockAllowUsertalk && $wgBlockAllowsUTEdit )
- $flags[] = 'nousertalk';
- if( $this->BlockHideName )
- $flags[] = 'hiddenname';
- return implode( ',', $flags );
- }
-
- /**
- * Builds unblock and block list links
- *
- * @return string
- */
- private function getConvenienceLinks() {
- global $wgUser, $wgLang;
- $skin = $wgUser->getSkin();
- if( $this->BlockAddress )
- $links[] = $this->getContribsLink( $skin );
- $links[] = $this->getUnblockLink( $skin );
- $links[] = $this->getBlockListLink( $skin );
- if ( $wgUser->isAllowed( 'editinterface' ) ) {
- $title = Title::makeTitle( NS_MEDIAWIKI, 'Ipbreason-dropdown' );
- $links[] = $skin->link(
- $title,
- wfMsgHtml( 'ipb-edit-dropdown' ),
- array(),
- array( 'action' => 'edit' )
- );
- }
- return '<p class="mw-ipb-conveniencelinks">' . $wgLang->pipeList( $links ) . '</p>';
- }
-
- /**
- * Build a convenient link to a user or IP's contribs
- * form
- *
- * @param $skin Skin to use
- * @return string
- */
- private function getContribsLink( $skin ) {
- $contribsPage = SpecialPage::getTitleFor( 'Contributions', $this->BlockAddress );
- return $skin->link( $contribsPage, wfMsgExt( 'ipb-blocklist-contribs', 'escape', $this->BlockAddress ) );
- }
-
- /**
- * Build a convenient link to unblock the given username or IP
- * address, if available; otherwise link to a blank unblock
- * form
- *
- * @param $skin Skin to use
- * @return string
- */
- private function getUnblockLink( $skin ) {
- $list = SpecialPage::getTitleFor( 'Ipblocklist' );
- $query = array( 'action' => 'unblock' );
-
- if( $this->BlockAddress ) {
- $addr = strtr( $this->BlockAddress, '_', ' ' );
- $message = wfMsg( 'ipb-unblock-addr', $addr );
- $query['ip'] = $this->BlockAddress;
- } else {
- $message = wfMsg( 'ipb-unblock' );
- }
- return $skin->linkKnown(
- $list,
- htmlspecialchars( $message ),
- array(),
- $query
- );
- }
-
- /**
- * Build a convenience link to the block list
- *
- * @param $skin Skin to use
- * @return string
- */
- private function getBlockListLink( $skin ) {
- return $skin->linkKnown(
- SpecialPage::getTitleFor( 'Ipblocklist' ),
- wfMsg( 'ipb-blocklist' )
- );
- }
-
- /**
- * Block a list of selected users
- *
- * @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;
- $counter = $blockSize = 0;
- $safeUsers = array();
- $log = new LogPage( 'block' );
- foreach( $users as $name ) {
- # Enforce limits
- $counter++;
- $blockSize++;
- # Lets not go *too* fast
- if( $blockSize >= 20 ) {
- $blockSize = 0;
- wfWaitForSlaves( 5 );
- }
- $u = User::newFromName( $name, false );
- // If user doesn't exist, it ought to be an IP then
- if( is_null( $u ) || ( !$u->getId() && !IP::isIPAddress( $u->getName() ) ) ) {
- continue;
- }
- $userTitle = $u->getUserPage();
- $userTalkTitle = $u->getTalkPage();
- $userpage = new Article( $userTitle );
- $usertalk = new Article( $userTalkTitle );
- $safeUsers[] = '[[' . $userTitle->getPrefixedText() . '|' . $userTitle->getText() . ']]';
- $expirestr = $u->getId() ? 'indefinite' : '1 week';
- $expiry = Block::parseExpiryInput( $expirestr );
- $anonOnly = IP::isIPAddress( $u->getName() ) ? 1 : 0;
- // Create the block
- $block = new Block( $u->getName(), // victim
- $u->getId(), // uid
- $wgUser->getId(), // blocker
- $reason, // comment
- wfTimestampNow(), // block time
- 0, // auto ?
- $expiry, // duration
- $anonOnly, // anononly?
- 1, // block account creation?
- 1, // autoblocking?
- 0, // suppress name?
- 0 // block from sending email?
- );
- $oldblock = Block::newFromDB( $u->getName(), $u->getId() );
- if( !$oldblock ) {
- $block->insert();
- # Prepare log parameters
- $logParams = array();
- $logParams[] = $expirestr;
- if( $anonOnly ) {
- $logParams[] = 'anononly';
- }
- $logParams[] = 'nocreate';
- # Add log entry
- $log->addEntry( 'block', $userTitle, $reason, $logParams );
- }
- # Tag userpage! (check length to avoid mistakes)
- if( strlen( $tag ) > 2 ) {
- $userpage->doEdit( $tag, $reason, EDIT_MINOR );
- }
- if( strlen( $talkTag ) > 2 ) {
- $usertalk->doEdit( $talkTag, $reason, EDIT_MINOR );
- }
- }
- return $safeUsers;
- }
-}
diff --git a/includes/specials/SpecialBlockme.php b/includes/specials/SpecialBlockme.php
index f5131f5f..40747667 100644
--- a/includes/specials/SpecialBlockme.php
+++ b/includes/specials/SpecialBlockme.php
@@ -48,10 +48,12 @@ class SpecialBlockme extends UnlistedSpecialPage {
if ( !$user->isLoggedIn() ) {
$user->addToDatabase();
}
- $id = $user->getId();
- $reason = wfMsg( 'proxyblockreason' );
- $block = new Block( $ip, 0, $id, $reason, wfTimestampNow() );
+ $block = new Block();
+ $block->setTarget( $ip );
+ $block->setBlocker( $user );
+ $block->mReason = wfMsg( 'proxyblockreason' );
+
$block->insert();
$wgOut->addWikiMsg( 'proxyblocksuccess' );
diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php
index 67fb5404..20819329 100644
--- a/includes/specials/SpecialBooksources.php
+++ b/includes/specials/SpecialBooksources.php
@@ -49,14 +49,13 @@ class SpecialBookSources extends SpecialPage {
* @param $isbn ISBN passed as a subpage parameter
*/
public function execute( $isbn ) {
- global $wgOut, $wgRequest;
$this->setHeaders();
- $this->isbn = self::cleanIsbn( $isbn ? $isbn : $wgRequest->getText( 'isbn' ) );
- $wgOut->addWikiMsg( 'booksources-summary' );
- $wgOut->addHTML( $this->makeForm() );
+ $this->outputHeader();
+ $this->isbn = self::cleanIsbn( $isbn ? $isbn : $this->getRequest()->getText( 'isbn' ) );
+ $this->getOutput()->addHTML( $this->makeForm() );
if( strlen( $this->isbn ) > 0 ) {
if( !self::isValidISBN( $this->isbn ) ) {
- $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", 'booksources-invalid-isbn' );
+ $this->getOutput()->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", 'booksources-invalid-isbn' );
}
$this->showList();
}
@@ -72,26 +71,26 @@ class SpecialBookSources extends SpecialPage {
if( strlen( $isbn ) == 13 ) {
for( $i = 0; $i < 12; $i++ ) {
if($i % 2 == 0) {
- $sum += $isbn{$i};
+ $sum += $isbn[$i];
} else {
- $sum += 3 * $isbn{$i};
+ $sum += 3 * $isbn[$i];
}
}
-
+
$check = (10 - ($sum % 10)) % 10;
- if ($check == $isbn{12}) {
+ if ($check == $isbn[12]) {
return true;
}
} elseif( strlen( $isbn ) == 10 ) {
for($i = 0; $i < 9; $i++) {
- $sum += $isbn{$i} * ($i + 1);
+ $sum += $isbn[$i] * ($i + 1);
}
-
+
$check = $sum % 11;
if($check == 10) {
$check = "X";
}
- if($check == $isbn{9}) {
+ if($check == $isbn[9]) {
return true;
}
}
@@ -115,10 +114,10 @@ class SpecialBookSources extends SpecialPage {
*/
private function makeForm() {
global $wgScript;
- $title = self::getTitleFor( 'Booksources' );
+
$form = '<fieldset><legend>' . wfMsgHtml( 'booksources-search-legend' ) . '</legend>';
$form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
- $form .= Html::hidden( 'title', $title->getPrefixedText() );
+ $form .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
$form .= '<p>' . Xml::inputLabel( wfMsg( 'booksources-isbn' ), 'isbn', 'isbn', 20, $this->isbn );
$form .= '&#160;' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '</p>';
$form .= Xml::closeElement( 'form' );
@@ -133,27 +132,27 @@ class SpecialBookSources extends SpecialPage {
* @return string
*/
private function showList() {
- global $wgOut, $wgContLang;
+ global $wgContLang;
# Hook to allow extensions to insert additional HTML,
# e.g. for API-interacting plugins and so on
- wfRunHooks( 'BookInformation', array( $this->isbn, &$wgOut ) );
+ wfRunHooks( 'BookInformation', array( $this->isbn, $this->getOutput() ) );
# Check for a local page such as Project:Book_sources and use that if available
$title = Title::makeTitleSafe( NS_PROJECT, wfMsgForContent( 'booksources' ) ); # Show list in content language
if( is_object( $title ) && $title->exists() ) {
$rev = Revision::newFromTitle( $title );
- $wgOut->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) );
+ $this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) );
return true;
}
# Fall back to the defaults given in the language file
- $wgOut->addWikiMsg( 'booksources-text' );
- $wgOut->addHTML( '<ul>' );
+ $this->getOutput()->addWikiMsg( 'booksources-text' );
+ $this->getOutput()->addHTML( '<ul>' );
$items = $wgContLang->getBookstoreList();
foreach( $items as $label => $url )
- $wgOut->addHTML( $this->makeListItem( $label, $url ) );
- $wgOut->addHTML( '</ul>' );
+ $this->getOutput()->addHTML( $this->makeListItem( $label, $url ) );
+ $this->getOutput()->addHTML( '</ul>' );
return true;
}
diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php
index 98b02126..2330c896 100644
--- a/includes/specials/SpecialBrokenRedirects.php
+++ b/includes/specials/SpecialBrokenRedirects.php
@@ -28,42 +28,56 @@
* @ingroup SpecialPage
*/
class BrokenRedirectsPage extends PageQueryPage {
- var $targets = array();
- function getName() {
- return 'BrokenRedirects';
+ function __construct( $name = 'BrokenRedirects' ) {
+ parent::__construct( $name );
}
- function isExpensive( ) { return true; }
+ function isExpensive() { return true; }
function isSyndicated() { return false; }
+ function sortDescending() { return false; }
- function getPageHeader( ) {
+ function getPageHeader() {
return wfMsgExt( 'brokenredirectstext', array( 'parse' ) );
}
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $page, $redirect ) = $dbr->tableNamesN( 'page', 'redirect' );
-
- $sql = "SELECT 'BrokenRedirects' AS type,
- p1.page_namespace AS namespace,
- p1.page_title AS title,
- rd_namespace,
- rd_title
- FROM $redirect AS rd
- JOIN $page p1 ON (rd.rd_from=p1.page_id)
- LEFT JOIN $page AS p2 ON (rd_namespace=p2.page_namespace AND rd_title=p2.page_title )
- WHERE rd_namespace >= 0
- AND p2.page_namespace IS NULL";
- return $sql;
+ function getQueryInfo() {
+ return array(
+ 'tables' => array( 'redirect', 'p1' => 'page',
+ 'p2' => 'page' ),
+ 'fields' => array( 'p1.page_namespace AS namespace',
+ 'p1.page_title AS title',
+ 'rd_namespace',
+ 'rd_title'
+ ),
+ 'conds' => array( 'rd_namespace >= 0',
+ 'p2.page_namespace IS NULL'
+ ),
+ 'join_conds' => array( 'p1' => array( 'JOIN', array(
+ 'rd_from=p1.page_id',
+ ) ),
+ 'p2' => array( 'LEFT JOIN', array(
+ 'rd_namespace=p2.page_namespace',
+ 'rd_title=p2.page_title'
+ ) )
+ )
+ );
}
- function getOrder() {
- return '';
+ /**
+ * @return array
+ */
+ function getOrderFields() {
+ return array ( 'rd_namespace', 'rd_title', 'rd_from' );
}
+ /**
+ * @param $skin Skin
+ * @param $result
+ * @return String
+ */
function formatResult( $skin, $result ) {
- global $wgUser, $wgContLang, $wgLang;
+ global $wgUser, $wgLang;
$fromObj = Title::makeTitle( $result->namespace, $result->title );
if ( isset( $result->rd_title ) ) {
@@ -95,14 +109,14 @@ class BrokenRedirectsPage extends PageQueryPage {
array(),
array( 'action' => 'edit' )
);
- $to = $skin->link(
+ $to = $skin->link(
$toObj,
null,
array(),
array(),
array( 'broken' )
);
- $arr = $wgContLang->getArrow();
+ $arr = $wgLang->getArrow();
$out = $from . wfMsg( 'word-separator' );
@@ -120,14 +134,3 @@ class BrokenRedirectsPage extends PageQueryPage {
return $out;
}
}
-
-/**
- * constructor
- */
-function wfSpecialBrokenRedirects() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $sbr = new BrokenRedirectsPage();
-
- return $sbr->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php
index c2dd40cd..91d98b86 100644
--- a/includes/specials/SpecialCategories.php
+++ b/includes/specials/SpecialCategories.php
@@ -69,16 +69,20 @@ class CategoryPager extends AlphabeticPager {
$this->mOffset = $from;
}
}
-
+
function getQueryInfo() {
return array(
'tables' => array( 'category' ),
'fields' => array( 'cat_title','cat_pages' ),
- 'conds' => array( 'cat_pages > 0' ),
+ 'conds' => array( 'cat_pages > 0' ),
'options' => array( 'USE INDEX' => 'cat_title' ),
);
}
+ function getTitle() {
+ return SpecialPage::getTitleFor( 'Categories' );
+ }
+
function getIndexField() {
# return array( 'abc' => 'cat_title', 'count' => 'cat_pages' );
return 'cat_title';
@@ -116,19 +120,18 @@ class CategoryPager extends AlphabeticPager {
function formatRow($result) {
global $wgLang;
$title = Title::makeTitle( NS_CATEGORY, $result->cat_title );
- $titleText = $this->getSkin()->link( $title, htmlspecialchars( $title->getText() ) );
+ $titleText = Linker::link( $title, htmlspecialchars( $title->getText() ) );
$count = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ),
$wgLang->formatNum( $result->cat_pages ) );
- return Xml::tags('li', null, "$titleText ($count)" ) . "\n";
+ return Xml::tags('li', null, wfSpecialList( $titleText, $count ) ) . "\n";
}
-
+
public function getStartForm( $from ) {
global $wgScript;
- $t = SpecialPage::getTitleFor( 'Categories' );
-
+
return
Xml::tags( 'form', array( 'method' => 'get', 'action' => $wgScript ),
- Html::hidden( 'title', $t->getPrefixedText() ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::fieldset( wfMsg( 'categories' ),
Xml::inputLabel( wfMsg( 'categoriesfrom' ),
'from', 'from', 20, $from ) .
diff --git a/includes/specials/SpecialResetpass.php b/includes/specials/SpecialChangePassword.php
index 0af6fbf0..46562b36 100644
--- a/includes/specials/SpecialResetpass.php
+++ b/includes/specials/SpecialChangePassword.php
@@ -1,6 +1,6 @@
<?php
/**
- * Implements Special:Resetpass
+ * Implements Special:ChangePassword
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,9 +26,9 @@
*
* @ingroup SpecialPage
*/
-class SpecialResetpass extends SpecialPage {
+class SpecialChangePassword extends SpecialPage {
public function __construct() {
- parent::__construct( 'Resetpass' );
+ parent::__construct( 'ChangePassword' );
}
/**
@@ -46,16 +46,12 @@ class SpecialResetpass extends SpecialPage {
$this->mOldpass = $wgRequest->getVal( 'wpPassword' );
$this->mNewpass = $wgRequest->getVal( 'wpNewPassword' );
$this->mRetype = $wgRequest->getVal( 'wpRetype' );
-
+ $this->mDomain = $wgRequest->getVal( 'wpDomain' );
+
$this->setHeaders();
$this->outputHeader();
$wgOut->disallowUserJs();
- if( !$wgAuth->allowPasswordChange() ) {
- $this->error( wfMsg( 'resetpass_forbidden' ) );
- return;
- }
-
if( !$wgRequest->wasPosted() && !$wgUser->isLoggedIn() ) {
$this->error( wfMsg( 'resetpass-no-info' ) );
return;
@@ -66,22 +62,35 @@ class SpecialResetpass extends SpecialPage {
return;
}
- if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal('token') ) ) {
+ if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) {
try {
+ if ( isset( $_SESSION['wsDomain'] ) ) {
+ $this->mDomain = $_SESSION['wsDomain'];
+ }
+ $wgAuth->setDomain( $this->mDomain );
+ if( !$wgAuth->allowPasswordChange() ) {
+ $this->error( wfMsg( 'resetpass_forbidden' ) );
+ return;
+ }
+
$this->attemptReset( $this->mNewpass, $this->mRetype );
$wgOut->addWikiMsg( 'resetpass_success' );
if( !$wgUser->isLoggedIn() ) {
+ LoginForm::setLoginToken();
+ $token = LoginForm::getLoginToken();
$data = array(
- 'action' => 'submitlogin',
- 'wpName' => $this->mUserName,
- 'wpPassword' => $this->mNewpass,
- 'returnto' => $wgRequest->getVal( 'returnto' ),
+ 'action' => 'submitlogin',
+ 'wpName' => $this->mUserName,
+ 'wpDomain' => $this->mDomain,
+ 'wpLoginToken' => $token,
+ 'wpPassword' => $this->mNewpass,
+ 'returnto' => $wgRequest->getVal( 'returnto' ),
);
if( $wgRequest->getCheck( 'wpRemember' ) ) {
$data['wpRemember'] = 1;
}
$login = new LoginForm( new FauxRequest( $data, true ) );
- $login->execute();
+ $login->execute( null );
}
$this->doReturnTo();
} catch( PasswordError $e ) {
@@ -90,7 +99,7 @@ class SpecialResetpass extends SpecialPage {
}
$this->showForm();
}
-
+
function doReturnTo() {
global $wgRequest, $wgOut;
$titleObj = Title::newFromText( $wgRequest->getVal( 'returnto' ) );
@@ -118,7 +127,7 @@ class SpecialResetpass extends SpecialPage {
$rememberMe = '<tr>' .
'<td></td>' .
'<td class="mw-input">' .
- Xml::checkLabel(
+ Xml::checkLabel(
wfMsgExt( 'remembermypassword', 'parsemag', $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) ) ),
'wpRemember', 'wpRemember',
$wgRequest->getCheck( 'wpRemember' ) ) .
@@ -139,6 +148,7 @@ class SpecialResetpass extends SpecialPage {
'id' => 'mw-resetpass-form' ) ) . "\n" .
Html::hidden( 'token', $wgUser->editToken() ) . "\n" .
Html::hidden( 'wpName', $this->mUserName ) . "\n" .
+ Html::hidden( 'wpDomain', $this->mDomain ) . "\n" .
Html::hidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . "\n" .
wfMsgExt( 'resetpass_text', array( 'parse' ) ) . "\n" .
Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" .
@@ -183,7 +193,7 @@ class SpecialResetpass extends SpecialPage {
$out .= "\t<td class='mw-label'>";
if ( $type != 'text' )
$out .= Xml::label( wfMsg( $label ), $name );
- else
+ else
$out .= wfMsgHtml( $label );
$out .= "</td>\n";
$out .= "\t<td class='mw-input'>";
@@ -200,19 +210,29 @@ class SpecialResetpass extends SpecialPage {
protected function attemptReset( $newpass, $retype ) {
$user = User::newFromName( $this->mUserName );
if( !$user || $user->isAnon() ) {
- throw new PasswordError( 'no such user' );
+ throw new PasswordError( wfMsg( 'nosuchusershort', $this->mUserName ) );
}
-
+
if( $newpass !== $retype ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'badretype' ) );
throw new PasswordError( wfMsg( 'badretype' ) );
}
+ $throttleCount = LoginForm::incLoginThrottle( $this->mUserName );
+ if ( $throttleCount === true ) {
+ throw new PasswordError( wfMsg( 'login-throttled' ) );
+ }
+
if( !$user->checkTemporaryPassword($this->mOldpass) && !$user->checkPassword($this->mOldpass) ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) );
throw new PasswordError( wfMsg( 'resetpass-wrong-oldpass' ) );
}
-
+
+ // Please reset throttle for successful logins, thanks!
+ if ( $throttleCount ) {
+ LoginForm::clearLoginThrottle( $this->mUserName );
+ }
+
try {
$user->setPassword( $this->mNewpass );
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'success' ) );
@@ -221,7 +241,7 @@ class SpecialResetpass extends SpecialPage {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) );
throw new PasswordError( $e->getMessage() );
}
-
+
$user->setCookies();
$user->saveSettings();
}
diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php
index 4650fc94..6b9ef0a9 100644
--- a/includes/specials/SpecialComparePages.php
+++ b/includes/specials/SpecialComparePages.php
@@ -40,47 +40,6 @@ class SpecialComparePages extends SpecialPage {
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
*
@@ -91,80 +50,79 @@ class SpecialComparePages extends SpecialPage {
$this->setHeaders();
$this->outputHeader();
- $this->setup( $par );
+ $form = new HTMLForm( array(
+ 'Page1' => array(
+ 'type' => 'text',
+ 'name' => 'page1',
+ 'label-message' => 'compare-page1',
+ 'size' => '40',
+ 'section' => 'page1',
+ ),
+ 'Revision1' => array(
+ 'type' => 'int',
+ 'name' => 'rev1',
+ 'label-message' => 'compare-rev1',
+ 'size' => '8',
+ 'section' => 'page1',
+ ),
+ 'Page2' => array(
+ 'type' => 'text',
+ 'name' => 'page2',
+ 'label-message' => 'compare-page2',
+ 'size' => '40',
+ 'section' => 'page2',
+ ),
+ 'Revision2' => array(
+ 'type' => 'int',
+ 'name' => 'rev2',
+ 'label-message' => 'compare-rev2',
+ 'size' => '8',
+ 'section' => 'page2',
+ ),
+ 'Action' => array(
+ 'type' => 'hidden',
+ 'name' => 'action',
+ ),
+ 'Diffonly' => array(
+ 'type' => 'hidden',
+ 'name' => 'diffonly',
+ ),
+ ), 'compare' );
+ $form->setSubmitText( wfMsg( 'compare-submit' ) );
+ $form->suppressReset();
+ $form->setMethod( 'get' );
+ $form->setTitle( $this->getTitle() );
+
+ $form->loadData();
+ $form->displayForm( '' );
+
+ self::showDiff( $form->mFieldData );
+ }
- // Settings
- $this->form();
+ public static function showDiff( $data ){
+ $rev1 = self::revOrTitle( $data['Revision1'], $data['Page1'] );
+ $rev2 = self::revOrTitle( $data['Revision2'], $data['Page2'] );
- if( $this->opts->getValue( 'rev1' ) && $this->opts->getValue( 'rev2' ) ) {
+ if( $rev1 && $rev2 ) {
$de = new DifferenceEngine( null,
- $this->opts->getValue( 'rev1' ),
- $this->opts->getValue( 'rev2' ),
+ $rev1,
+ $rev2,
null, // rcid
- ( $this->opts->getValue( 'action' ) == 'purge' ),
+ ( $data["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 );
+ public static function revOrTitle( $revision, $title ) {
+ if( $revision ){
+ return $revision;
+ } elseif( $title ) {
+ $title = Title::newFromText( $title );
+ if( $title instanceof Title ){
+ return $title->getLatestRevID();
+ }
}
- $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 );
+ return null;
}
}
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
index e556a60b..70bbfe39 100644
--- a/includes/specials/SpecialConfirmemail.php
+++ b/includes/specials/SpecialConfirmemail.php
@@ -44,31 +44,27 @@ class EmailConfirmation extends UnlistedSpecialPage {
* @param $code Confirmation code passed to the page
*/
function execute( $code ) {
- global $wgUser, $wgOut;
$this->setHeaders();
-
+
if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
+ throw new ReadOnlyError;
}
-
+
if( empty( $code ) ) {
- if( $wgUser->isLoggedIn() ) {
- if( User::isValidEmailAddr( $wgUser->getEmail() ) ) {
+ if( $this->getUser()->isLoggedIn() ) {
+ if( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
$this->showRequestForm();
} else {
- $wgOut->addWikiMsg( 'confirmemail_noemail' );
+ $this->getOutput()->addWikiMsg( 'confirmemail_noemail' );
}
} else {
- $title = SpecialPage::getTitleFor( 'Userlogin' );
- $skin = $wgUser->getSkin();
- $llink = $skin->linkKnown(
- $title,
+ $llink = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Userlogin' ),
wfMsgHtml( 'loginreqlink' ),
array(),
array( 'returnto' => $this->getTitle()->getPrefixedText() )
);
- $wgOut->addHTML( wfMsgWikiHtml( 'confirmemail_needlogin', $llink ) );
+ $this->getOutput()->addHTML( wfMessage( 'confirmemail_needlogin' )->rawParams( $llink )->parse() );
}
} else {
$this->attemptConfirm( $code );
@@ -79,33 +75,34 @@ class EmailConfirmation extends UnlistedSpecialPage {
* Show a nice form for the user to request a confirmation mail
*/
function showRequestForm() {
- global $wgOut, $wgUser, $wgLang, $wgRequest;
- if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getText( 'token' ) ) ) {
- $status = $wgUser->sendConfirmationMail();
+ $user = $this->getUser();
+ $out = $this->getOutput();
+ if( $this->getRequest()->wasPosted() && $user->matchEditToken( $this->getRequest()->getText( 'token' ) ) ) {
+ $status = $user->sendConfirmationMail();
if ( $status->isGood() ) {
- $wgOut->addWikiMsg( 'confirmemail_sent' );
+ $out->addWikiMsg( 'confirmemail_sent' );
} else {
- $wgOut->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
+ $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
}
} else {
- if( $wgUser->isEmailConfirmed() ) {
+ if( $user->isEmailConfirmed() ) {
// date and time are separate parameters to facilitate localisation.
// $time is kept for backward compat reasons.
// 'emailauthenticated' is also used in SpecialPreferences.php
- $time = $wgLang->timeAndDate( $wgUser->mEmailAuthenticated, true );
- $d = $wgLang->date( $wgUser->mEmailAuthenticated, true );
- $t = $wgLang->time( $wgUser->mEmailAuthenticated, true );
- $wgOut->addWikiMsg( 'emailauthenticated', $time, $d, $t );
+ $time = $this->getLang()->timeAndDate( $user->mEmailAuthenticated, true );
+ $d = $this->getLang()->date( $user->mEmailAuthenticated, true );
+ $t = $this->getLang()->time( $user->mEmailAuthenticated, true );
+ $out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
}
- if( $wgUser->isEmailConfirmationPending() ) {
- $wgOut->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">\n$1\n</div>", 'confirmemail_pending' );
+ if( $user->isEmailConfirmationPending() ) {
+ $out->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">\n$1\n</div>", 'confirmemail_pending' );
}
- $wgOut->addWikiMsg( 'confirmemail_text' );
+ $out->addWikiMsg( 'confirmemail_text' );
$form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl() ) );
- $form .= Html::hidden( 'token', $wgUser->editToken() );
+ $form .= Html::hidden( 'token', $user->editToken() );
$form .= Xml::submitButton( wfMsg( 'confirmemail_send' ) );
$form .= Xml::closeElement( 'form' );
- $wgOut->addHTML( $form );
+ $out->addHTML( $form );
}
}
@@ -116,19 +113,18 @@ class EmailConfirmation extends UnlistedSpecialPage {
* @param $code Confirmation code
*/
function attemptConfirm( $code ) {
- global $wgUser, $wgOut;
$user = User::newFromConfirmationCode( $code );
if( is_object( $user ) ) {
$user->confirmEmail();
$user->saveSettings();
- $message = $wgUser->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
- $wgOut->addWikiMsg( $message );
- if( !$wgUser->isLoggedIn() ) {
+ $message = $this->getUser()->isLoggedIn() ? 'confirmemail_loggedin' : 'confirmemail_success';
+ $this->getOutput()->addWikiMsg( $message );
+ if( !$this->getUser()->isLoggedIn() ) {
$title = SpecialPage::getTitleFor( 'Userlogin' );
- $wgOut->returnToMain( true, $title );
+ $this->getOutput()->returnToMain( true, $title );
}
} else {
- $wgOut->addWikiMsg( 'confirmemail_invalid' );
+ $this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
}
}
@@ -150,11 +146,9 @@ class EmailInvalidation extends UnlistedSpecialPage {
$this->setHeaders();
if ( wfReadOnly() ) {
- global $wgOut;
- $wgOut->readOnlyPage();
- return;
+ throw new ReadOnlyError;
}
-
+
$this->attemptInvalidate( $code );
}
@@ -165,17 +159,16 @@ class EmailInvalidation extends UnlistedSpecialPage {
* @param $code Confirmation code
*/
function attemptInvalidate( $code ) {
- global $wgUser, $wgOut;
$user = User::newFromConfirmationCode( $code );
if( is_object( $user ) ) {
$user->invalidateEmail();
$user->saveSettings();
- $wgOut->addWikiMsg( 'confirmemail_invalidated' );
- if( !$wgUser->isLoggedIn() ) {
- $wgOut->returnToMain();
+ $this->getOutput()->addWikiMsg( 'confirmemail_invalidated' );
+ if( !$this->getUser()->isLoggedIn() ) {
+ $this->getOutput()->returnToMain();
}
} else {
- $wgOut->addWikiMsg( 'confirmemail_invalid' );
+ $this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
}
}
}
diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php
index cee01a7f..fea27bfd 100644
--- a/includes/specials/SpecialContributions.php
+++ b/includes/specials/SpecialContributions.php
@@ -29,6 +29,8 @@
class SpecialContributions extends SpecialPage {
+ protected $opts;
+
public function __construct() {
parent::__construct( 'Contributions' );
}
@@ -38,6 +40,7 @@ class SpecialContributions extends SpecialPage {
$this->setHeaders();
$this->outputHeader();
+ $wgOut->addModuleStyles( 'mediawiki.special' );
$this->opts = array();
@@ -78,6 +81,10 @@ class SpecialContributions extends SpecialPage {
$target = $nt->getText();
$wgOut->setSubtitle( $this->contributionsSub( $nt, $id ) );
$wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsgExt( 'contributions-title', array( 'parsemag' ),$target ) ) );
+ $user = User::newFromName( $target, false );
+ if ( is_object( $user ) ) {
+ $this->getSkin()->setRelevantUser( $user );
+ }
} else {
$wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') );
$wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'sp-contributions-newbies-title' ) ) );
@@ -107,13 +114,43 @@ class SpecialContributions extends SpecialPage {
$this->opts['month'] = $wgRequest->getIntOrNull( 'month' );
}
- // Add RSS/atom links
- $this->setSyndicated();
$feedType = $wgRequest->getVal( 'feed' );
if( $feedType ) {
- return $this->feed( $feedType );
+ // Maintain some level of backwards compatability
+ // If people request feeds using the old parameters, redirect to API
+ $apiParams = array(
+ 'action' => 'feedcontributions',
+ 'feedformat' => $feedType,
+ 'user' => $target,
+ );
+ if ( $this->opts['topOnly'] ) {
+ $apiParams['toponly'] = true;
+ }
+ if ( $this->opts['deletedOnly'] ) {
+ $apiParams['deletedonly'] = true;
+ }
+ if ( $this->opts['tagFilter'] !== '' ) {
+ $apiParams['tagfilter'] = $this->opts['tagFilter'];
+ }
+ if ( $this->opts['namespace'] !== '' ) {
+ $apiParams['namespace'] = $this->opts['namespace'];
+ }
+ if ( $this->opts['year'] !== null ) {
+ $apiParams['year'] = $this->opts['year'];
+ }
+ if ( $this->opts['month'] !== null ) {
+ $apiParams['month'] = $this->opts['month'];
+ }
+
+ $url = wfScript( 'api' ) . '?' . wfArrayToCGI( $apiParams );
+
+ $wgOut->redirect( $url, '301' );
+ return;
}
+ // Add RSS/atom links
+ $this->addFeedLinks( array( 'action' => 'feedcontributions', 'user' => $target ) );
+
if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id ) ) ) {
$wgOut->addHTML( $this->getForm() );
@@ -130,7 +167,8 @@ class SpecialContributions extends SpecialPage {
$wgOut->addWikiMsg( 'nocontribs', $target );
} else {
# Show a message about slave lag, if applicable
- if( ( $lag = $pager->getDatabase()->getLag() ) > 0 )
+ $lag = wfGetLB()->safeGetLag( $pager->getDatabase() );
+ if( $lag > 0 )
$wgOut->showLagWarning( $lag );
$wgOut->addHTML(
@@ -141,7 +179,6 @@ class SpecialContributions extends SpecialPage {
}
$wgOut->preventClickjacking( $pager->getPreventClickjacking() );
-
# Show the appropriate "footer" message - WHOIS tools, etc.
if( $target != 'newbies' ) {
$message = 'sp-contributions-footer';
@@ -155,8 +192,7 @@ class SpecialContributions extends SpecialPage {
}
}
- $text = wfMsgNoTrans( $message, $target );
- if( !wfEmptyMsg( $message, $text ) && $text != '-' ) {
+ if( !wfMessage( $message, $target )->isDisabled() ) {
$wgOut->wrapWikiMsg(
"<div class='mw-contributions-footer'>\n$1\n</div>",
array( $message, $target ) );
@@ -165,23 +201,17 @@ class SpecialContributions extends SpecialPage {
}
}
- protected function setSyndicated() {
- global $wgOut;
- $wgOut->setSyndicated( true );
- $wgOut->setFeedAppendQuery( wfArrayToCGI( $this->opts ) );
- }
-
/**
* Generates the subheading with links
* @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.
+ * @todo FIXME: Almost the same as getSubTitle in SpecialDeletedContributions.php. Could be combined.
*/
protected function contributionsSub( $nt, $id ) {
- global $wgSysopUserBans, $wgLang, $wgUser, $wgOut;
+ global $wgLang, $wgUser, $wgOut;
- $sk = $wgUser->getSkin();
+ $sk = $this->getSkin();
if ( $id === null ) {
$user = htmlspecialchars( $nt->getText() );
@@ -191,78 +221,7 @@ class SpecialContributions extends SpecialPage {
$userObj = User::newFromName( $nt->getText(), /* check for username validity not needed */ false );
$talk = $nt->getTalkPage();
if( $talk ) {
- # Talk page link
- $tools[] = $sk->link( $talk, wfMsgHtml( 'sp-contributions-talk' ) );
- if( ( $id !== null && $wgSysopUserBans ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) {
- if( $wgUser->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
- if ( $userObj->isBlocked() ) {
- $tools[] = $sk->linkKnown( # Change block link
- SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ),
- wfMsgHtml( 'change-blocklink' )
- );
- $tools[] = $sk->linkKnown( # Unblock link
- SpecialPage::getTitleFor( 'Ipblocklist' ),
- wfMsgHtml( 'unblocklink' ),
- array(),
- array(
- 'action' => 'unblock',
- 'ip' => $nt->getDBkey()
- )
- );
- }
- else { # User is not blocked
- $tools[] = $sk->linkKnown( # Block link
- SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ),
- wfMsgHtml( 'blocklink' )
- );
- }
- }
- # Block log link
- $tools[] = $sk->linkKnown(
- SpecialPage::getTitleFor( 'Log' ),
- wfMsgHtml( 'sp-contributions-blocklog' ),
- array(),
- array(
- 'type' => 'block',
- 'page' => $nt->getPrefixedText()
- )
- );
- }
- # 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' ),
- wfMsgHtml( 'sp-contributions-logs' ),
- array(),
- array( 'user' => $nt->getText() )
- );
-
- # Add link to deleted user contributions for priviledged users
- if( $wgUser->isAllowed( 'deletedhistory' ) ) {
- $tools[] = $sk->linkKnown(
- SpecialPage::getTitleFor( 'DeletedContributions', $nt->getDBkey() ),
- wfMsgHtml( 'sp-contributions-deleted' )
- );
- }
-
- # Add a link to change user rights for privileged users
- $userrightsPage = new UserrightsPage();
- if( $id !== null && $userrightsPage->userCanChangeRights( User::newFromId( $id ) ) ) {
- $tools[] = $sk->linkKnown(
- SpecialPage::getTitleFor( 'Userrights', $nt->getDBkey() ),
- wfMsgHtml( 'sp-contributions-userrights' )
- );
- }
-
- wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) );
-
+ $tools = self::getUserLinks( $nt, $talk, $userObj, $wgUser );
$links = $wgLang->pipeList( $tools );
// Show a note if the user is blocked and display the last block log entry.
@@ -291,7 +250,7 @@ class SpecialContributions extends SpecialPage {
// languages that want to put the "for" bit right after $user but before
// $links. If 'contribsub' is around, use it for reverse compatibility,
// otherwise use 'contribsub2'.
- if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) {
+ if( wfEmptyMsg( 'contribsub' ) ) {
return wfMsgHtml( 'contribsub2', $user, $links );
} else {
return wfMsgHtml( 'contribsub', "$user ($links)" );
@@ -299,6 +258,82 @@ class SpecialContributions extends SpecialPage {
}
/**
+ * Links to different places.
+ * @param $userpage Title: Target user page
+ * @param $talkpage Title: Talk page
+ * @param $target User: Target user object
+ * @param $subject User: The viewing user ($wgUser is still checked in some cases, like userrights page!!)
+ */
+ public static function getUserLinks( Title $userpage, Title $talkpage, User $target, User $subject ) {
+
+ $sk = $subject->getSkin();
+ $id = $target->getId();
+ $username = $target->getName();
+
+ $tools[] = $sk->link( $talkpage, wfMsgHtml( 'sp-contributions-talk' ) );
+
+ if( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) {
+ if( $subject->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
+ if ( $target->isBlocked() ) {
+ $tools[] = $sk->linkKnown( # Change block link
+ SpecialPage::getTitleFor( 'Block', $username ),
+ wfMsgHtml( 'change-blocklink' )
+ );
+ $tools[] = $sk->linkKnown( # Unblock link
+ SpecialPage::getTitleFor( 'Unblock', $username ),
+ wfMsgHtml( 'unblocklink' )
+ );
+ } else { # User is not blocked
+ $tools[] = $sk->linkKnown( # Block link
+ SpecialPage::getTitleFor( 'Block', $username ),
+ wfMsgHtml( 'blocklink' )
+ );
+ }
+ }
+ # Block log link
+ $tools[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Log', 'block' ),
+ wfMsgHtml( 'sp-contributions-blocklog' ),
+ array(),
+ array(
+ 'page' => $userpage->getPrefixedText()
+ )
+ );
+ }
+ # Uploads
+ $tools[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Listfiles', $username ),
+ wfMsgHtml( 'sp-contributions-uploads' )
+ );
+
+ # Other logs link
+ $tools[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Log', $username ),
+ wfMsgHtml( 'sp-contributions-logs' )
+ );
+
+ # Add link to deleted user contributions for priviledged users
+ if( $subject->isAllowed( 'deletedhistory' ) ) {
+ $tools[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'DeletedContributions', $username ),
+ wfMsgHtml( 'sp-contributions-deleted' )
+ );
+ }
+
+ # Add a link to change user rights for privileged users
+ $userrightsPage = new UserrightsPage();
+ if( $id !== null && $userrightsPage->userCanChangeRights( $target ) ) {
+ $tools[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Userrights', $username ),
+ wfMsgHtml( 'sp-contributions-userrights' )
+ );
+ }
+
+ wfRunHooks( 'ContributionsToolLinks', array( $id, $userpage, &$tools ) );
+ return $tools;
+ }
+
+ /**
* Generates the namespace selector form with hidden attributes.
* @return String: HTML fragment
*/
@@ -374,104 +409,15 @@ class SpecialContributions extends SpecialPage {
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 ) ) {
+ ) . ' ';
+ $explain = wfMessage( 'sp-contributions-explain' );
+ if ( $explain->exists() ) {
$f .= "<p id='mw-sp-contributions-explain'>{$explain}</p>";
}
$f .= Xml::closeElement('fieldset' ) .
Xml::closeElement( 'form' );
return $f;
}
-
- /**
- * Output a subscription feed listing recent edits to this page.
- * @param $type String
- */
- protected function feed( $type ) {
- global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgOut;
-
- if( !$wgFeed ) {
- $wgOut->addWikiMsg( 'feed-unavailable' );
- return;
- }
-
- if( !isset( $wgFeedClasses[$type] ) ) {
- $wgOut->addWikiMsg( 'feed-invalid' );
- return;
- }
-
- $feed = new $wgFeedClasses[$type](
- $this->feedTitle(),
- 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( 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 ) {
- foreach ( $pager->mResult as $row ) {
- $feed->outItem( $this->feedItem( $row ) );
- }
- }
- $feed->outFooter();
- }
-
- protected function feedTitle() {
- global $wgLanguageCode, $wgSitename;
- $page = SpecialPage::getPage( 'Contributions' );
- $desc = $page->getDescription();
- return "$wgSitename - $desc [$wgLanguageCode]";
- }
-
- protected function feedItem( $row ) {
- $title = Title::MakeTitle( intval( $row->page_namespace ), $row->page_title );
- if( $title ) {
- $date = $row->rev_timestamp;
- $comments = $title->getTalkPage()->getFullURL();
- $revision = Revision::newFromTitle( $title, $row->rev_id );
-
- return new FeedItem(
- $title->getPrefixedText(),
- $this->feedItemDesc( $revision ),
- $title->getFullURL(),
- $date,
- $this->feedItemAuthor( $revision ),
- $comments
- );
- } else {
- return null;
- }
- }
-
- protected function feedItemAuthor( $revision ) {
- return $revision->getUserText();
- }
-
- protected function feedItemDesc( $revision ) {
- if( $revision ) {
- return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) .
- htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) .
- "</p>\n<hr />\n<div>" .
- nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>";
- }
- return '';
- }
}
/**
@@ -513,6 +459,10 @@ class ContribsPager extends ReverseChronologicalPager {
return $query;
}
+ function getTitle() {
+ return SpecialPage::getTitleFor( 'Contributions' );
+ }
+
function getQueryInfo() {
global $wgUser;
list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond();
@@ -521,7 +471,7 @@ class ContribsPager extends ReverseChronologicalPager {
// Paranoia: avoid brute force searches (bug 17342)
if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
$conds[] = $this->mDb->bitAnd('rev_deleted',Revision::DELETED_USER) . ' = 0';
- } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ } elseif( !$wgUser->isAllowed( 'suppressrevision' ) ) {
$conds[] = $this->mDb->bitAnd('rev_deleted',Revision::SUPPRESSED_USER) .
' != ' . Revision::SUPPRESSED_USER;
}
@@ -561,7 +511,7 @@ class ContribsPager extends ReverseChronologicalPager {
$condition[] = 'rev_user >' . (int)($max - $max / 100);
$condition[] = 'ug_group IS NULL';
$index = 'user_timestamp';
- # FIXME: other groups may have 'bot' rights
+ # @todo FIXME: Other groups may have 'bot' rights
$join_conds['user_groups'] = array( 'LEFT JOIN', "ug_user = rev_user AND ug_group = 'bot'" );
} else {
$tables = array( 'page', 'revision' );
@@ -608,7 +558,7 @@ class ContribsPager extends ReverseChronologicalPager {
* @todo This would probably look a lot nicer in a table.
*/
function formatRow( $row ) {
- global $wgUser, $wgLang, $wgContLang;
+ global $wgUser, $wgLang;
wfProfileIn( __METHOD__ );
$sk = $this->getSkin();
@@ -655,7 +605,7 @@ class ContribsPager extends ReverseChronologicalPager {
array( 'action' => 'history' )
);
- $comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true );
+ $comment = $wgLang->getDirMark() . $sk->revComment( $rev, false, true );
$date = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true );
if( $rev->userCan( Revision::DELETED_TEXT ) ) {
$d = $sk->linkKnown(
@@ -734,7 +684,7 @@ class ContribsPager extends ReverseChronologicalPager {
/**
* Get the Database object in use
*
- * @return Database
+ * @return DatabaseBase
*/
public function getDatabase() {
return $this->mDb;
diff --git a/includes/specials/SpecialDeadendpages.php b/includes/specials/SpecialDeadendpages.php
index dfa053aa..f8ef4d44 100644
--- a/includes/specials/SpecialDeadendpages.php
+++ b/includes/specials/SpecialDeadendpages.php
@@ -28,8 +28,8 @@
*/
class DeadendPagesPage extends PageQueryPage {
- function getName( ) {
- return "Deadendpages";
+ function __construct( $name = 'Deadendpages' ) {
+ parent::__construct( $name );
}
function getPageHeader() {
@@ -41,11 +41,13 @@ class DeadendPagesPage extends PageQueryPage {
*
* @return true
*/
- function isExpensive( ) {
- return 1;
+ function isExpensive() {
+ return true;
}
- function isSyndicated() { return false; }
+ function isSyndicated() {
+ return false;
+ }
/**
* @return false
@@ -54,28 +56,30 @@ class DeadendPagesPage extends PageQueryPage {
return false;
}
- /**
- * @return string an sqlquery
- */
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $page, $pagelinks ) = $dbr->tableNamesN( 'page', 'pagelinks' );
- return "SELECT 'Deadendpages' as type, page_namespace AS namespace, page_title as title, page_title AS value " .
- "FROM $page LEFT JOIN $pagelinks ON page_id = pl_from " .
- "WHERE pl_from IS NULL " .
- "AND page_namespace = 0 " .
- "AND page_is_redirect = 0";
+ function getQueryInfo() {
+ return array(
+ 'tables' => array( 'page', 'pagelinks' ),
+ 'fields' => array( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'page_title AS value'
+ ),
+ 'conds' => array( 'pl_from IS NULL',
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0
+ ),
+ 'join_conds' => array( 'pagelinks' => array( 'LEFT JOIN', array(
+ 'page_id=pl_from'
+ ) ) )
+ );
}
-}
-/**
- * Constructor
- */
-function wfSpecialDeadendpages() {
-
- list( $limit, $offset ) = wfCheckLimits();
-
- $depp = new DeadendPagesPage();
-
- return $depp->doQuery( $offset, $limit );
+ function getOrderFields() {
+ // For some crazy reason ordering by a constant
+ // causes a filesort
+ if( count( MWNamespace::getContentNamespaces() ) > 1 ) {
+ return array( 'page_namespace', 'page_title' );
+ } else {
+ return array( 'page_title' );
+ }
+ }
}
diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php
index 92e22586..65858482 100644
--- a/includes/specials/SpecialDeletedContributions.php
+++ b/includes/specials/SpecialDeletedContributions.php
@@ -55,7 +55,7 @@ class DeletedContribsPager extends IndexPager {
// Paranoia: avoid brute force searches (bug 17792)
if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
$conds[] = $this->mDb->bitAnd('ar_deleted',Revision::DELETED_USER) . ' = 0';
- } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ } elseif( !$wgUser->isAllowed( 'suppressrevision' ) ) {
$conds[] = $this->mDb->bitAnd('ar_deleted',Revision::SUPPRESSED_USER) .
' != ' . Revision::SUPPRESSED_USER;
}
@@ -211,7 +211,7 @@ class DeletedContribsPager extends IndexPager {
} else {
$mflag = '';
}
-
+
// Revision delete link
$canHide = $wgUser->isAllowed( 'deleterevision' );
if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
@@ -234,9 +234,9 @@ class DeletedContribsPager extends IndexPager {
array( 'class' => 'mw-deletedcontribs-tools' ),
wfMsg( 'parentheses', $wgLang->pipeList( array( $last, $dellog, $reviewlink ) ) )
);
-
+
$ret = "{$del}{$link} {$tools} . . {$mflag} {$pagelink} {$comment}";
-
+
# Denote if username is redacted for this edit
if( $rev->isDeleted( Revision::DELETED_USER ) ) {
$ret .= " <strong>" . wfMsgHtml('rev-deleted-user-contribs') . "</strong>";
@@ -325,7 +325,8 @@ class DeletedContributionsPage extends SpecialPage {
}
# Show a message about slave lag, if applicable
- if( ( $lag = $pager->getDatabase()->getLag() ) > 0 )
+ $lag = wfGetLB()->safeGetLag( $pager->getDatabase() );
+ if( $lag > 0 )
$wgOut->showLagWarning( $lag );
$wgOut->addHTML(
@@ -340,9 +341,7 @@ class DeletedContributionsPage extends SpecialPage {
? 'sp-contributions-footer-anon'
: 'sp-contributions-footer';
-
- $text = wfMsgNoTrans( $message, $target );
- if( !wfEmptyMsg( $message, $text ) && $text != '-' ) {
+ if( !wfMessage( $message )->isDisabled() ) {
$wgOut->wrapWikiMsg( "<div class='mw-contributions-footer'>\n$1\n</div>", array( $message, $target ) );
}
}
@@ -353,12 +352,12 @@ class DeletedContributionsPage extends SpecialPage {
* @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.
+ * @todo FIXME: Almost the same as contributionsSub in SpecialContributions.php. Could be combined.
*/
function getSubTitle( $nt, $id ) {
- global $wgSysopUserBans, $wgLang, $wgUser, $wgOut;
+ global $wgLang, $wgUser, $wgOut;
- $sk = $wgUser->getSkin();
+ $sk = $this->getSkin();
if ( $id === null ) {
$user = htmlspecialchars( $nt->getText() );
@@ -370,11 +369,11 @@ class DeletedContributionsPage extends SpecialPage {
if( $talk ) {
# Talk page link
$tools[] = $sk->link( $talk, wfMsgHtml( 'sp-contributions-talk' ) );
- if( ( $id !== null && $wgSysopUserBans ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) {
+ if( ( $id !== null ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) {
if( $wgUser->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
if ( $userObj->isBlocked() ) {
$tools[] = $sk->linkKnown( # Change block link
- SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ),
+ SpecialPage::getTitleFor( 'Block', $nt->getDBkey() ),
wfMsgHtml( 'change-blocklink' )
);
$tools[] = $sk->linkKnown( # Unblock link
@@ -383,13 +382,13 @@ class DeletedContributionsPage extends SpecialPage {
array(),
array(
'action' => 'unblock',
- 'ip' => $nt->getDBkey()
+ 'ip' => $nt->getDBkey()
)
);
}
else { # User is not blocked
$tools[] = $sk->linkKnown( # Block link
- SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ),
+ SpecialPage::getTitleFor( 'Block', $nt->getDBkey() ),
wfMsgHtml( 'blocklink' )
);
}
@@ -455,7 +454,7 @@ class DeletedContributionsPage extends SpecialPage {
// languages that want to put the "for" bit right after $user but before
// $links. If 'contribsub' is around, use it for reverse compatibility,
// otherwise use 'contribsub2'.
- if( wfEmptyMsg( 'contribsub', wfMsg( 'contribsub' ) ) ) {
+ if( wfEmptyMsg( 'contribsub' ) ) {
return wfMsgHtml( 'contribsub2', $user, $links );
} else {
return wfMsgHtml( 'contribsub', "$user ($links)" );
diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php
index 3e706189..431dfe76 100644
--- a/includes/specials/SpecialDisambiguations.php
+++ b/includes/specials/SpecialDisambiguations.php
@@ -28,33 +28,28 @@
*/
class DisambiguationsPage extends PageQueryPage {
- function getName() {
- return 'Disambiguations';
+ function __construct( $name = 'Disambiguations' ) {
+ parent::__construct( $name );
}
- function isExpensive( ) { return true; }
+ function isExpensive() { return true; }
function isSyndicated() { return false; }
-
- function getPageHeader( ) {
+ function getPageHeader() {
return wfMsgExt( 'disambiguations-text', array( 'parse' ) );
}
- function getSQL() {
- global $wgContentNamespaces;
-
+ function getQueryInfo() {
$dbr = wfGetDB( DB_SLAVE );
-
- $dMsgText = wfMsgForContent('disambiguationspage');
-
+ $dMsgText = wfMsgForContent( 'disambiguationspage' );
$linkBatch = new LinkBatch;
# If the text can be treated as a title, use it verbatim.
# Otherwise, pull the titles from the links table
$dp = Title::newFromText($dMsgText);
if( $dp ) {
- if($dp->getNamespace() != NS_TEMPLATE) {
- # FIXME we assume the disambiguation message is a template but
+ if( $dp->getNamespace() != NS_TEMPLATE ) {
+ # @todo FIXME: We assume the disambiguation message is a template but
# the page can potentially be from another namespace :/
wfDebug("Mediawiki:disambiguationspage message does not refer to a template!\n");
}
@@ -65,69 +60,79 @@ class DisambiguationsPage extends PageQueryPage {
$res = $dbr->select(
array('pagelinks', 'page'),
'pl_title',
- array('page_id = pl_from', 'pl_namespace' => NS_TEMPLATE,
- 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()),
+ array('page_id = pl_from',
+ 'pl_namespace' => NS_TEMPLATE,
+ 'page_namespace' => $disPageObj->getNamespace(),
+ 'page_title' => $disPageObj->getDBkey()),
__METHOD__ );
foreach ( $res as $row ) {
$linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title ));
}
}
-
- $set = $linkBatch->constructSet( 'lb.tl', $dbr );
+ $set = $linkBatch->constructSet( 'tl', $dbr );
if( $set === false ) {
- # We must always return a valid sql query, but this way DB will always quicly return an empty result
+ # We must always return a valid SQL query, but this way
+ # the DB will always quickly return an empty result
$set = 'FALSE';
wfDebug("Mediawiki:disambiguationspage message does not link to any templates!\n");
}
- list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' );
+ // @todo FIXME: What are pagelinks and p2 doing here?
+ return array (
+ 'tables' => array( 'templatelinks', 'p1' => 'page', 'pagelinks', 'p2' => 'page' ),
+ 'fields' => array( 'p1.page_namespace AS namespace',
+ 'p1.page_title AS title',
+ 'pl_from AS value' ),
+ 'conds' => array( $set,
+ 'p1.page_id = tl_from',
+ 'pl_namespace = p1.page_namespace',
+ 'pl_title = p1.page_title',
+ 'p2.page_id = pl_from',
+ 'p2.page_namespace' => MWNamespace::getContentNamespaces() )
+ );
+ }
- if ( $wgContentNamespaces ) {
- $nsclause = 'IN (' . $dbr->makeList( $wgContentNamespaces ) . ')';
- } else {
- $nsclause = '= ' . NS_MAIN;
- }
+ function getOrderFields() {
+ return array( 'tl_namespace', 'tl_title', 'value' );
+ }
- $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 ' . $nsclause
- .' AND pb.page_id = lb.tl_from'
- .' AND pb.page_namespace = la.pl_namespace'
- .' AND pb.page_title = la.pl_title'
- .' ORDER BY lb.tl_namespace, lb.tl_title';
-
- return $sql;
+ function sortDescending() {
+ return false;
}
- function getOrder() {
- return '';
+ /**
+ * Fetch links and cache their existence
+ *
+ * @param $db DatabaseBase
+ * @param $res
+ */
+ function preprocessResults( $db, $res ) {
+ $batch = new LinkBatch;
+ foreach ( $res as $row ) {
+ $batch->add( $row->namespace, $row->title );
+ }
+ $batch->execute();
+
+ // Back to start for display
+ if ( $db->numRows( $res ) > 0 ) {
+ // If there are no rows we get an error seeking.
+ $db->dataSeek( $res, 0 );
+ }
}
function formatResult( $skin, $result ) {
- global $wgContLang;
+ global $wgLang;
+
$title = Title::newFromID( $result->value );
$dp = Title::makeTitle( $result->namespace, $result->title );
$from = $skin->link( $title );
- $edit = $skin->link( $title, wfMsgExt( 'parentheses', array( 'escape' ), wfMsg( 'editlink' ) ) , array(), array( 'redirect' => 'no', 'action' => 'edit' ) );
- $arr = $wgContLang->getArrow();
+ $edit = $skin->link( $title, wfMsgExt( 'parentheses', array( 'escape' ), wfMsg( 'editlink' ) ) ,
+ array(), array( 'redirect' => 'no', 'action' => 'edit' ) );
+ $arr = $wgLang->getArrow();
$to = $skin->link( $dp );
return "$from $edit $arr $to";
}
}
-
-/**
- * Constructor
- */
-function wfSpecialDisambiguations() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $sd = new DisambiguationsPage();
-
- return $sd->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php
index c7f63210..ec899d8a 100644
--- a/includes/specials/SpecialDoubleRedirects.php
+++ b/includes/specials/SpecialDoubleRedirects.php
@@ -29,63 +29,63 @@
*/
class DoubleRedirectsPage extends PageQueryPage {
- function getName() {
- return 'DoubleRedirects';
+ function __construct( $name = 'DoubleRedirects' ) {
+ parent::__construct( $name );
}
- function isExpensive( ) { return true; }
+ function isExpensive() { return true; }
function isSyndicated() { return false; }
+ function sortDescending() { return false; }
- function getPageHeader( ) {
+ function getPageHeader() {
return wfMsgExt( 'doubleredirectstext', array( 'parse' ) );
}
- function getSQLText( &$dbr, $namespace = null, $title = null ) {
-
- list( $page, $redirect ) = $dbr->tableNamesN( 'page', 'redirect' );
-
+ function reallyGetQueryInfo( $namespace = null, $title = null ) {
$limitToTitle = !( $namespace === null && $title === null );
- $sql = $limitToTitle ? "SELECT" : "SELECT 'DoubleRedirects' as type," ;
- $sql .=
- " pa.page_namespace as namespace, pa.page_title as title," .
- " pb.page_namespace as nsb, pb.page_title as tb," .
- " pc.page_namespace as nsc, pc.page_title as tc" .
- " FROM $redirect AS ra, $redirect AS rb, $page AS pa, $page AS pb, $page AS pc" .
- " WHERE ra.rd_from=pa.page_id" .
- " AND ra.rd_namespace=pb.page_namespace" .
- " AND ra.rd_title=pb.page_title" .
- " AND rb.rd_from=pb.page_id" .
- " AND rb.rd_namespace=pc.page_namespace" .
- " AND rb.rd_title=pc.page_title";
-
- if( $limitToTitle ) {
- $encTitle = $dbr->addQuotes( $title );
- $sql .= " AND pa.page_namespace=$namespace" .
- " AND pa.page_title=$encTitle";
+ $retval = array (
+ 'tables' => array ( 'ra' => 'redirect',
+ 'rb' => 'redirect', 'pa' => 'page',
+ 'pb' => 'page', 'pc' => 'page' ),
+ 'fields' => array ( 'pa.page_namespace AS namespace',
+ 'pa.page_title AS title',
+ 'pb.page_namespace AS nsb',
+ 'pb.page_title AS tb',
+ 'pc.page_namespace AS nsc',
+ 'pc.page_title AS tc' ),
+ 'conds' => array ( 'ra.rd_from = pa.page_id',
+ 'pb.page_namespace = ra.rd_namespace',
+ 'pb.page_title = ra.rd_title',
+ 'rb.rd_from = pb.page_id',
+ 'pc.page_namespace = rb.rd_namespace',
+ 'pc.page_title = rb.rd_title' )
+ );
+ if ( $limitToTitle ) {
+ $retval['conds']['pa.page_namespace'] = $namespace;
+ $retval['conds']['pa.page_title'] = $title;
}
-
- return $sql;
+ return $retval;
}
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- return $this->getSQLText( $dbr );
+ function getQueryInfo() {
+ return $this->reallyGetQueryInfo();
}
- function getOrder() {
- return '';
+ function getOrderFields() {
+ return array ( 'ra.rd_namespace', 'ra.rd_title' );
}
function formatResult( $skin, $result ) {
- global $wgContLang;
+ global $wgLang;
- $fname = 'DoubleRedirectsPage::formatResult';
$titleA = Title::makeTitle( $result->namespace, $result->title );
if ( $result && !isset( $result->nsb ) ) {
$dbr = wfGetDB( DB_SLAVE );
- $sql = $this->getSQLText( $dbr, $result->namespace, $result->title );
- $res = $dbr->query( $sql, $fname );
+ $qi = $this->reallyGetQueryInfo( $result->namespace,
+ $result->title );
+ $res = $dbr->select($qi['tables'], $qi['fields'],
+ $qi['conds'], __METHOD__ );
if ( $res ) {
$result = $dbr->fetchObject( $res );
}
@@ -119,20 +119,8 @@ class DoubleRedirectsPage extends PageQueryPage {
array( 'redirect' => 'no' )
);
$linkC = $skin->linkKnown( $titleC );
- $arr = $wgContLang->getArrow() . $wgContLang->getDirMark();
+ $arr = $wgLang->getArrow() . $wgLang->getDirMark();
return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
}
}
-
-/**
- * constructor
- */
-function wfSpecialDoubleRedirects() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $sdr = new DoubleRedirectsPage();
-
- return $sdr->doQuery( $offset, $limit );
-
-}
diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php
new file mode 100644
index 00000000..bb2ecd80
--- /dev/null
+++ b/includes/specials/SpecialEditWatchlist.php
@@ -0,0 +1,596 @@
+<?php
+
+/**
+ * Provides the UI through which users can perform editing
+ * operations on their watchlist
+ *
+ * @ingroup Watchlist
+ * @author Rob Church <robchur@gmail.com>
+ */
+class SpecialEditWatchlist extends UnlistedSpecialPage {
+
+ /**
+ * Editing modes
+ */
+ const EDIT_CLEAR = 1;
+ const EDIT_RAW = 2;
+ const EDIT_NORMAL = 3;
+
+ protected $successMessage;
+
+ protected $toc;
+
+ public function __construct(){
+ parent::__construct( 'EditWatchlist' );
+ }
+
+ /**
+ * Main execution point
+ *
+ * @param $mode int
+ */
+ public function execute( $mode ) {
+ if( wfReadOnly() ) {
+ throw new ReadOnlyError;
+ }
+
+ $out = $this->getOutput();
+
+ # Anons don't get a watchlist
+ if( $this->getUser()->isAnon() ) {
+ $out->setPageTitle( wfMsg( 'watchnologin' ) );
+ $llink = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Userlogin' ),
+ wfMsgHtml( 'loginreqlink' ),
+ array(),
+ array( 'returnto' => $this->getTitle()->getPrefixedText() )
+ );
+ $out->addHTML( wfMessage( 'watchlistanontext' )->rawParams( $llink )->parse() );
+ return;
+ }
+
+ $sub = wfMsgExt(
+ 'watchlistfor2',
+ array( 'parseinline', 'replaceafter' ),
+ $this->getUser()->getName(),
+ SpecialEditWatchlist::buildTools( null )
+ );
+ $out->setSubtitle( $sub );
+
+ # B/C: $mode used to be waaay down the parameter list, and the first parameter
+ # was $wgUser
+ if( $mode instanceof User ){
+ $args = func_get_args();
+ if( count( $args >= 4 ) ){
+ $mode = $args[3];
+ }
+ }
+ $mode = self::getMode( $this->getRequest(), $mode );
+
+ switch( $mode ) {
+ case self::EDIT_CLEAR:
+ // The "Clear" link scared people too much.
+ // Pass on to the raw editor, from which it's very easy to clear.
+
+ case self::EDIT_RAW:
+ $out->setPageTitle( wfMsg( 'watchlistedit-raw-title' ) );
+ $form = $this->getRawForm();
+ if( $form->show() ){
+ $out->addHTML( $this->successMessage );
+ $out->returnToMain();
+ }
+ break;
+
+ case self::EDIT_NORMAL:
+ default:
+ $out->setPageTitle( wfMsg( 'watchlistedit-normal-title' ) );
+ $form = $this->getNormalForm();
+ if( $form->show() ){
+ $out->addHTML( $this->successMessage );
+ $out->returnToMain();
+ } elseif ( $this->toc !== false ) {
+ $out->prependHTML( $this->toc );
+ }
+ break;
+ }
+ }
+
+ /**
+ * Extract a list of titles from a blob of text, returning
+ * (prefixed) strings; unwatchable titles are ignored
+ *
+ * @param $list String
+ * @return array
+ */
+ private function extractTitles( $list ) {
+ $titles = array();
+ $list = explode( "\n", trim( $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() ) {
+ $titles[] = $title->getPrefixedText();
+ }
+ }
+ }
+ return array_unique( $titles );
+ }
+
+ public function submitRaw( $data ){
+ $wanted = $this->extractTitles( $data['Titles'] );
+ $current = $this->getWatchlist();
+
+ if( count( $wanted ) > 0 ) {
+ $toWatch = array_diff( $wanted, $current );
+ $toUnwatch = array_diff( $current, $wanted );
+ $this->watchTitles( $toWatch );
+ $this->unwatchTitles( $toUnwatch );
+ $this->getUser()->invalidateCache();
+
+ if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ){
+ $this->successMessage = wfMessage( 'watchlistedit-raw-done' )->parse();
+ } else {
+ return false;
+ }
+
+ if( count( $toWatch ) > 0 ) {
+ $this->successMessage .= wfMessage(
+ 'watchlistedit-raw-added',
+ $this->getLang()->formatNum( count( $toWatch ) )
+ );
+ $this->showTitles( $toWatch, $this->successMessage );
+ }
+
+ if( count( $toUnwatch ) > 0 ) {
+ $this->successMessage .= wfMessage(
+ 'watchlistedit-raw-removed',
+ $this->getLang()->formatNum( count( $toUnwatch ) )
+ );
+ $this->showTitles( $toUnwatch, $this->successMessage );
+ }
+ } else {
+ $this->clearWatchlist();
+ $this->getUser()->invalidateCache();
+ $this->successMessage .= wfMessage(
+ 'watchlistedit-raw-removed',
+ $this->getLang()->formatNum( count( $current ) )
+ );
+ $this->showTitles( $current, $this->successMessage );
+ }
+ return true;
+ }
+
+ /**
+ * Print out a list of linked titles
+ *
+ * $titles can be an array of strings or Title objects; the former
+ * is preferred, since Titles are very memory-heavy
+ *
+ * @param $titles array of strings, or Title objects
+ * @param $output String
+ */
+ private function showTitles( $titles, &$output ) {
+ $talk = wfMsgHtml( 'talkpagelinktext' );
+ // Do a batch existence check
+ $batch = new LinkBatch();
+ foreach( $titles as $title ) {
+ if( !$title instanceof Title ) {
+ $title = Title::newFromText( $title );
+ }
+ if( $title instanceof Title ) {
+ $batch->addObj( $title );
+ $batch->addObj( $title->getTalkPage() );
+ }
+ }
+ $batch->execute();
+ // Print out the list
+ $output .= "<ul>\n";
+ foreach( $titles as $title ) {
+ if( !$title instanceof Title ) {
+ $title = Title::newFromText( $title );
+ }
+ if( $title instanceof Title ) {
+ $output .= "<li>"
+ . Linker::link( $title )
+ . ' (' . Linker::link( $title->getTalkPage(), $talk )
+ . ")</li>\n";
+ }
+ }
+ $output .= "</ul>\n";
+ }
+
+ /**
+ * Prepare a list of titles on a user's watchlist (excluding talk pages)
+ * and return an array of (prefixed) strings
+ *
+ * @return array
+ */
+ private function getWatchlist() {
+ $list = array();
+ $dbr = wfGetDB( DB_MASTER );
+ $res = $dbr->select(
+ 'watchlist',
+ '*',
+ array(
+ 'wl_user' => $this->getUser()->getId(),
+ ),
+ __METHOD__
+ );
+ if( $res->numRows() > 0 ) {
+ foreach ( $res as $row ) {
+ $title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
+ if( $title instanceof Title && !$title->isTalkPage() )
+ $list[] = $title->getPrefixedText();
+ }
+ $res->free();
+ }
+ return $list;
+ }
+
+ /**
+ * Get a list of titles on a user's watchlist, excluding talk pages,
+ * and return as a two-dimensional array with namespace, title and
+ * redirect status
+ *
+ * @return array
+ */
+ private function getWatchlistInfo() {
+ $titles = array();
+ $dbr = wfGetDB( DB_MASTER );
+
+ $res = $dbr->select(
+ array( 'watchlist', 'page' ),
+ array(
+ 'wl_namespace',
+ 'wl_title',
+ 'page_id',
+ 'page_len',
+ 'page_is_redirect',
+ 'page_latest'
+ ),
+ array( 'wl_user' => $this->getUser()->getId() ),
+ __METHOD__,
+ array( 'ORDER BY' => 'wl_namespace, wl_title' ),
+ array( 'page' => array(
+ 'LEFT JOIN',
+ 'wl_namespace = page_namespace AND wl_title = page_title'
+ ) )
+ );
+
+ if( $res && $dbr->numRows( $res ) > 0 ) {
+ $cache = LinkCache::singleton();
+ 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, $row->page_latest );
+ } else {
+ $cache->addBadLinkObj( $title );
+ }
+ // Ignore non-talk
+ if( !$title->isTalkPage() ) {
+ $titles[$row->wl_namespace][$row->wl_title] = $row->page_is_redirect;
+ }
+ }
+ }
+ }
+ return $titles;
+ }
+
+ /**
+ * Remove all titles from a user's watchlist
+ */
+ private function clearWatchlist() {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete(
+ 'watchlist',
+ array( 'wl_user' => $this->getUser()->getId() ),
+ __METHOD__
+ );
+ }
+
+ /**
+ * Add a list of titles to a user's watchlist
+ *
+ * $titles can be an array of strings or Title objects; the former
+ * is preferred, since Titles are very memory-heavy
+ *
+ * @param $titles Array of strings, or Title objects
+ */
+ private function watchTitles( $titles ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $rows = array();
+ foreach( $titles as $title ) {
+ if( !$title instanceof Title ) {
+ $title = Title::newFromText( $title );
+ }
+ if( $title instanceof Title ) {
+ $rows[] = array(
+ 'wl_user' => $this->getUser()->getId(),
+ 'wl_namespace' => ( $title->getNamespace() & ~1 ),
+ 'wl_title' => $title->getDBkey(),
+ 'wl_notificationtimestamp' => null,
+ );
+ $rows[] = array(
+ 'wl_user' => $this->getUser()->getId(),
+ 'wl_namespace' => ( $title->getNamespace() | 1 ),
+ 'wl_title' => $title->getDBkey(),
+ 'wl_notificationtimestamp' => null,
+ );
+ }
+ }
+ $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' );
+ }
+
+ /**
+ * Remove a list of titles from a user's watchlist
+ *
+ * $titles can be an array of strings or Title objects; the former
+ * is preferred, since Titles are very memory-heavy
+ *
+ * @param $titles Array of strings, or Title objects
+ */
+ private function unwatchTitles( $titles ) {
+ $dbw = wfGetDB( DB_MASTER );
+ foreach( $titles as $title ) {
+ if( !$title instanceof Title ) {
+ $title = Title::newFromText( $title );
+ }
+ if( $title instanceof Title ) {
+ $dbw->delete(
+ 'watchlist',
+ array(
+ 'wl_user' => $this->getUser()->getId(),
+ 'wl_namespace' => ( $title->getNamespace() & ~1 ),
+ 'wl_title' => $title->getDBkey(),
+ ),
+ __METHOD__
+ );
+ $dbw->delete(
+ 'watchlist',
+ array(
+ 'wl_user' => $this->getUser()->getId(),
+ 'wl_namespace' => ( $title->getNamespace() | 1 ),
+ 'wl_title' => $title->getDBkey(),
+ ),
+ __METHOD__
+ );
+ $article = new Article( $title, 0 );
+ wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$article ) );
+ }
+ }
+ }
+
+ public function submitNormal( $data ) {
+ $removed = array();
+
+ foreach( $data as $titles ) {
+ $this->unwatchTitles( $titles );
+ $removed += $titles;
+ }
+
+ if( count( $removed ) > 0 ) {
+ $this->successMessage = wfMessage(
+ 'watchlistedit-normal-done',
+ $this->getLang()->formatNum( count( $removed ) )
+ );
+ $this->showTitles( $removed, $this->successMessage );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get the standard watchlist editing form
+ *
+ * @return HTMLForm
+ */
+ protected function getNormalForm(){
+ global $wgContLang;
+
+ $fields = array();
+ $count = 0;
+
+ $haveInvalidNamespaces = false;
+ foreach( $this->getWatchlistInfo() as $namespace => $pages ){
+ if ( $namespace < 0 ) {
+ $haveInvalidNamespaces = true;
+ continue;
+ }
+
+ $fields['TitlesNs'.$namespace] = array(
+ 'class' => 'EditWatchlistCheckboxSeriesField',
+ 'options' => array(),
+ 'section' => "ns$namespace",
+ );
+
+ foreach( $pages as $dbkey => $redirect ){
+ $title = Title::makeTitleSafe( $namespace, $dbkey );
+ $text = $this->buildRemoveLine( $title, $redirect );
+ $fields['TitlesNs'.$namespace]['options'][$text] = $title->getEscapedText();
+ $count++;
+ }
+ }
+ if ( $haveInvalidNamespaces ) {
+ wfDebug( "User {$this->getContext()->getUser()->getId()} has invalid watchlist entries, clening up...\n" );
+ $this->getContext()->getUser()->cleanupWatchlist();
+ }
+
+ if ( count( $fields ) > 1 && $count > 30 ) {
+ $this->toc = Linker::tocIndent();
+ $tocLength = 0;
+ foreach( $fields as $key => $data ) {
+ $ns = substr( $data['section'], 2 );
+ $nsText = $ns == NS_MAIN
+ ? wfMsgHtml( 'blanknamespace' )
+ : htmlspecialchars( $wgContLang->getFormattedNsText( $ns ) );
+ $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText, ++$tocLength, 1 ) . Linker::tocLineEnd();
+ }
+ $this->toc = Linker::tocList( $this->toc );
+ } else {
+ $this->toc = false;
+ }
+
+ $form = new EditWatchlistNormalHTMLForm( $fields, $this->getContext() );
+ $form->setTitle( $this->getTitle() );
+ $form->setSubmitText( wfMessage( 'watchlistedit-normal-submit' )->text() );
+ $form->setWrapperLegend( wfMessage( 'watchlistedit-normal-legend' )->text() );
+ $form->addHeaderText( wfMessage( 'watchlistedit-normal-explain' )->parse() );
+ $form->setSubmitCallback( array( $this, 'submitNormal' ) );
+ return $form;
+ }
+
+ /**
+ * Build the label for a checkbox, with a link to the title, and various additional bits
+ *
+ * @param $title Title
+ * @param $redirect bool
+ * @return string
+ */
+ private function buildRemoveLine( $title, $redirect ) {
+ $link = Linker::link( $title );
+ if( $redirect ) {
+ $link = '<span class="watchlistredir">' . $link . '</span>';
+ }
+ $tools[] = Linker::link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) );
+ if( $title->exists() ) {
+ $tools[] = Linker::linkKnown(
+ $title,
+ wfMsgHtml( 'history_short' ),
+ array(),
+ array( 'action' => 'history' )
+ );
+ }
+ if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
+ $tools[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
+ wfMsgHtml( 'contributions' )
+ );
+ }
+
+ wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $redirect, $this->getSkin() ) );
+
+ return $link . " (" . $this->getLang()->pipeList( $tools ) . ")";
+ }
+
+ /**
+ * Get a form for editing the watchlist in "raw" mode
+ *
+ * @return HTMLForm
+ */
+ protected function getRawForm(){
+ $titles = implode( $this->getWatchlist(), "\n" );
+ $fields = array(
+ 'Titles' => array(
+ 'type' => 'textarea',
+ 'label-message' => 'watchlistedit-raw-titles',
+ 'default' => $titles,
+ ),
+ );
+ $form = new HTMLForm( $fields );
+ $form->setTitle( $this->getTitle( 'raw' ) );
+ $form->setSubmitText( wfMessage( 'watchlistedit-raw-submit' )->text() );
+ $form->setWrapperLegend( wfMessage( 'watchlistedit-raw-legend' )->text() );
+ $form->addHeaderText( wfMessage( 'watchlistedit-raw-explain' )->parse() );
+ $form->setSubmitCallback( array( $this, 'submitRaw' ) );
+ return $form;
+ }
+
+ /**
+ * Determine whether we are editing the watchlist, and if so, what
+ * kind of editing operation
+ *
+ * @param $request WebRequest
+ * @param $par mixed
+ * @return int
+ */
+ public static function getMode( $request, $par ) {
+ $mode = strtolower( $request->getVal( 'action', $par ) );
+ switch( $mode ) {
+ case 'clear':
+ case self::EDIT_CLEAR:
+ return self::EDIT_CLEAR;
+
+ case 'raw':
+ case self::EDIT_RAW:
+ return self::EDIT_RAW;
+
+ case 'edit':
+ case self::EDIT_NORMAL:
+ return self::EDIT_NORMAL;
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Build a set of links for convenient navigation
+ * between watchlist viewing and editing modes
+ *
+ * @param $unused Unused
+ * @return string
+ */
+ public static function buildTools( $unused ) {
+ global $wgLang;
+
+ $tools = array();
+ $modes = array(
+ 'view' => array( 'Watchlist', false ),
+ 'edit' => array( 'EditWatchlist', false ),
+ 'raw' => array( 'EditWatchlist', 'raw' ),
+ );
+ foreach( $modes as $mode => $arr ) {
+ // can use messages 'watchlisttools-view', 'watchlisttools-edit', 'watchlisttools-raw'
+ $tools[] = Linker::linkKnown(
+ SpecialPage::getTitleFor( $arr[0], $arr[1] ),
+ wfMsgHtml( "watchlisttools-{$mode}" )
+ );
+ }
+ return Html::rawElement( 'span',
+ array( 'class' => 'mw-watchlist-toollinks' ),
+ wfMsg( 'parentheses', $wgLang->pipeList( $tools ) ) );
+ }
+}
+
+# B/C since 1.18
+class WatchlistEditor extends SpecialEditWatchlist {}
+
+/**
+ * Extend HTMLForm purely so we can have a more sane way of getting the section headers
+ */
+class EditWatchlistNormalHTMLForm extends HTMLForm {
+ public function getLegend( $namespace ){
+ $namespace = substr( $namespace, 2 );
+ return $namespace == NS_MAIN
+ ? wfMsgHtml( 'blanknamespace' )
+ : htmlspecialchars( $this->getContext()->getLang()->getFormattedNsText( $namespace ) );
+ }
+ public function getBody() {
+ return $this->displaySection( $this->mFieldTree, '', 'editwatchlist-' );
+ }
+}
+
+class EditWatchlistCheckboxSeriesField extends HTMLMultiSelectField {
+ /**
+ * HTMLMultiSelectField throws validation errors if we get input data
+ * that doesn't match the data set in the form setup. This causes
+ * problems if something gets removed from the watchlist while the
+ * form is open (bug 32126), but we know that invalid items will
+ * be harmless so we can override it here.
+ *
+ * @param $value String the value the field was submitted with
+ * @param $alldata Array the data collected from the form
+ * @return Mixed Bool true on success, or String error to display.
+ */
+ function validate( $value, $alldata ) {
+ // Need to call into grandparent to be a good citizen. :)
+ return HTMLFormField::validate( $value, $alldata );
+ }
+}
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
index 61271227..7c2ba570 100644
--- a/includes/specials/SpecialEmailuser.php
+++ b/includes/specials/SpecialEmailuser.php
@@ -28,20 +28,20 @@
*/
class SpecialEmailUser extends UnlistedSpecialPage {
protected $mTarget;
-
- public function __construct(){
+
+ public function __construct() {
parent::__construct( 'Emailuser' );
}
-
- protected function getFormFields(){
+
+ protected function getFormFields() {
global $wgUser;
return array(
'From' => array(
'type' => 'info',
'raw' => 1,
- 'default' => $wgUser->getSkin()->link(
- $wgUser->getUserPage(),
- htmlspecialchars( $wgUser->getName() )
+ 'default' => $this->getSkin()->link(
+ $wgUser->getUserPage(),
+ htmlspecialchars( $wgUser->getName() )
),
'label-message' => 'emailfrom',
'id' => 'mw-emailuser-sender',
@@ -49,8 +49,8 @@ class SpecialEmailUser extends UnlistedSpecialPage {
'To' => array(
'type' => 'info',
'raw' => 1,
- 'default' => $wgUser->getSkin()->link(
- $this->mTargetObj->getUserPage(),
+ 'default' => $this->getSkin()->link(
+ $this->mTargetObj->getUserPage(),
htmlspecialchars( $this->mTargetObj->getName() )
),
'label-message' => 'emailto',
@@ -82,25 +82,17 @@ class SpecialEmailUser extends UnlistedSpecialPage {
),
);
}
-
+
public function execute( $par ) {
global $wgRequest, $wgOut, $wgUser;
$this->setHeaders();
$this->outputHeader();
-
+ $wgOut->addModuleStyles( 'mediawiki.special' );
$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 out if sending user cannot do this
$error = self::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) );
switch ( $error ) {
case null:
@@ -125,7 +117,19 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$wgOut->showErrorPage( $title, $msg, $params );
return;
}
-
+ // Got a valid target user name? Else ask for one.
+ $ret = self::getTarget( $this->mTarget );
+ if( !$ret instanceof User ) {
+ if( $this->mTarget != '' ) {
+ $ret = ( $ret == 'notarget' ) ? 'emailnotarget' : ( $ret . 'text' );
+ $wgOut->wrapWikiMsg( "<p class='error'>$1</p>", $ret );
+ }
+ $wgOut->addHTML( self::userForm( $this->mTarget ) );
+ return false;
+ }
+
+ $this->mTargetObj = $ret;
+
$form = new HTMLForm( $this->getFormFields() );
$form->addPreText( wfMsgExt( 'emailpagetext', 'parseinline' ) );
$form->setSubmitText( wfMsg( 'emailsend' ) );
@@ -133,16 +137,16 @@ class SpecialEmailUser extends UnlistedSpecialPage {
$form->setSubmitCallback( array( __CLASS__, 'submit' ) );
$form->setWrapperLegend( wfMsgExt( 'email-legend', 'parsemag' ) );
$form->loadData();
-
- if( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ){
+
+ if( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ) {
return false;
}
-
- $wgOut->setPagetitle( wfMsg( 'emailpage' ) );
+
+ $wgOut->setPageTitle( wfMsg( 'emailpage' ) );
$result = $form->show();
-
- if( $result === true || ( $result instanceof Status && $result->isGood() ) ){
- $wgOut->setPagetitle( wfMsg( 'emailsent' ) );
+
+ if( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
+ $wgOut->setPageTitle( wfMsg( 'emailsent' ) );
$wgOut->addWikiMsg( 'emailsenttext' );
$wgOut->returnToMain( false, $this->mTargetObj->getUserPage() );
}
@@ -159,15 +163,15 @@ class SpecialEmailUser extends UnlistedSpecialPage {
wfDebug( "Target is empty.\n" );
return 'notarget';
}
-
+
$nu = User::newFromName( $target );
if( !$nu instanceof User || !$nu->getId() ) {
wfDebug( "Target is invalid user.\n" );
return 'notarget';
- } else if ( !$nu->isEmailConfirmed() ) {
+ } elseif ( !$nu->isEmailConfirmed() ) {
wfDebug( "User has no valid email.\n" );
return 'noemail';
- } else if ( !$nu->canReceiveEmail() ) {
+ } elseif ( !$nu->canReceiveEmail() ) {
wfDebug( "User does not allow user emails.\n" );
return 'nowikiemail';
}
@@ -184,15 +188,15 @@ class SpecialEmailUser extends UnlistedSpecialPage {
*/
public static function getPermissionsError( $user, $editToken ) {
global $wgEnableEmail, $wgEnableUserEmail;
- if( !$wgEnableEmail || !$wgEnableUserEmail ){
+ if( !$wgEnableEmail || !$wgEnableUserEmail ) {
return 'usermaildisabled';
}
-
+
if( !$user->isAllowed( 'sendemail' ) ) {
return 'badaccess';
}
-
- if( !$user->isEmailConfirmed() ){
+
+ if( !$user->isEmailConfirmed() ) {
return 'mailnologin';
}
@@ -217,8 +221,28 @@ class SpecialEmailUser extends UnlistedSpecialPage {
}
/**
+ * Form to ask for target user name.
+ *
+ * @param $name String: user name submitted.
+ * @return String: form asking for user name.
+ */
+
+ function userForm( $name ) {
+ global $wgScript ;
+ $string = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'askusername' ) ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
+ Xml::openElement( 'fieldset' ) .
+ Html::rawElement( 'legend', null, wfMessage( 'emailtarget' )->parse() ) .
+ Xml::inputLabel( wfMessage( 'emailusername' )->text(), 'target', 'emailusertarget', 30, $name ) . ' ' .
+ Xml::submitButton( wfMessage( 'emailusernamesubmit' )->text() ) .
+ Xml::closeElement( 'fieldset' ) .
+ Xml::closeElement( 'form' ) . "\n";
+ return $string;
+ }
+
+ /**
* Really send a mail. Permissions should have been checked using
- * getPermissionsError(). It is probably also a good
+ * 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
@@ -228,7 +252,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
global $wgUser, $wgUserEmailUseReplyTo;
$target = self::getTarget( $data['Target'] );
- if( !$target instanceof User ){
+ if( !$target instanceof User ) {
return wfMsgExt( $target . 'text', 'parse' );
}
$to = new MailAddress( $target );
@@ -238,17 +262,17 @@ class SpecialEmailUser extends UnlistedSpecialPage {
// Add a standard footer and trim up trailing newlines
$text = rtrim( $text ) . "\n\n-- \n";
- $text .= wfMsgExt(
+ $text .= wfMsgExt(
'emailuserfooter',
- array( 'content', 'parsemag' ),
- array( $from->name, $to->name )
+ array( 'content', 'parsemag' ),
+ array( $from->name, $to->name )
);
$error = '';
if( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) {
return $error;
}
-
+
if( $wgUserEmailUseReplyTo ) {
// Put the generic wiki autogenerated address in the From:
// header and reserve the user for Reply-To.
@@ -283,12 +307,12 @@ class SpecialEmailUser extends UnlistedSpecialPage {
return $status;
} else {
// if the user requested a copy of this mail, do this now,
- // unless they are emailing themselves, in which case one
+ // 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(),
+ 'emailccsubject',
+ $target->getName(),
$subject
);
wfRunHooks( 'EmailUserCC', array( &$from, &$from, &$cc_subject, &$text ) );
diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php
index eaed2393..50754b6a 100644
--- a/includes/specials/SpecialExport.php
+++ b/includes/specials/SpecialExport.php
@@ -40,7 +40,7 @@ class SpecialExport extends SpecialPage {
public function execute( $par ) {
global $wgOut, $wgRequest, $wgSitename, $wgExportAllowListContributors;
global $wgExportAllowHistory, $wgExportMaxHistory, $wgExportMaxLinkDepth;
- global $wgExportFromNamespaces, $wgUser;
+ global $wgExportFromNamespaces;
$this->setHeaders();
$this->outputHeader();
@@ -63,16 +63,18 @@ class SpecialExport extends SpecialPage {
$t = Title::makeTitleSafe( NS_MAIN, $catname );
if ( $t ) {
/**
- * @todo Fixme: this can lead to hitting memory limit for very large
+ * @todo FIXME: This can lead to hitting memory limit for very large
* categories. Ideally we would do the lookup synchronously
* during the export in a single query.
*/
$catpages = $this->getPagesFromCategory( $t );
- if ( $catpages ) $page .= "\n" . implode( "\n", $catpages );
+ if ( $catpages ) {
+ $page .= "\n" . implode( "\n", $catpages );
+ }
}
}
}
- else if( $wgRequest->getCheck( 'addns' ) && $wgExportFromNamespaces ) {
+ elseif( $wgRequest->getCheck( 'addns' ) && $wgExportFromNamespaces ) {
$page = $wgRequest->getText( 'pages' );
$nsindex = $wgRequest->getText( 'nsindex', '' );
@@ -81,20 +83,22 @@ class SpecialExport extends SpecialPage {
* Same implementation as above, so same @todo
*/
$nspages = $this->getPagesFromNamespace( $nsindex );
- if ( $nspages ) $page .= "\n" . implode( "\n", $nspages );
+ if ( $nspages ) {
+ $page .= "\n" . implode( "\n", $nspages );
+ }
}
}
- else if( $wgRequest->wasPosted() && $par == '' ) {
+ elseif( $wgRequest->wasPosted() && $par == '' ) {
$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(
@@ -103,7 +107,7 @@ class SpecialExport extends SpecialPage {
'limit' => $wgExportMaxHistory,
);
$historyCheck = $wgRequest->getCheck( 'history' );
-
+
if ( $this->curonly ) {
$history = WikiExporter::CURRENT;
} elseif ( !$historyCheck ) {
@@ -118,19 +122,23 @@ class SpecialExport extends SpecialPage {
}
}
- if( $page != '' ) $this->doExport = true;
+ if( $page != '' ) {
+ $this->doExport = true;
+ }
} else {
// 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( $page != '' ) {
+ $this->doExport = true;
+ }
}
if( !$wgExportAllowHistory ) {
@@ -139,24 +147,26 @@ class SpecialExport extends SpecialPage {
}
$list_authors = $wgRequest->getCheck( 'listauthors' );
- if ( !$this->curonly || !$wgExportAllowListContributors ) $list_authors = false ;
+ 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();
$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;
}
@@ -176,23 +186,38 @@ class SpecialExport extends SpecialPage {
$form .= '<br />';
if( $wgExportAllowHistory ) {
- $form .= Xml::checkLabel( wfMsg( 'exportcuronly' ), 'curonly', 'curonly', true ) . '<br />';
+ $form .= Xml::checkLabel(
+ wfMsg( 'exportcuronly' ),
+ 'curonly',
+ 'curonly',
+ $wgRequest->wasPosted() ? $wgRequest->getCheck( 'curonly' ) : true
+ ) . '<br />';
} else {
$wgOut->addHTML( wfMsgExt( 'exportnohistory', 'parse' ) );
}
-
- $form .= Xml::checkLabel( wfMsg( 'export-templates' ), 'templates', 'wpExportTemplates', false ) . '<br />';
-
+
+ $form .= Xml::checkLabel(
+ wfMsg( 'export-templates' ),
+ 'templates',
+ 'wpExportTemplates',
+ $wgRequest->wasPosted() ? $wgRequest->getCheck( 'templates' ) : 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' ), $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'export' ) );
+ $form .= Xml::checkLabel(
+ wfMsg( 'export-download' ),
+ 'wpDownload',
+ 'wpDownload',
+ $wgRequest->wasPosted() ? $wgRequest->getCheck( 'wpDownload' ) : true
+ ) . '<br />';
+
+ $form .= Xml::submitButton( wfMsg( 'export-submit' ), Linker::tooltipAndAccesskeyAttribs( 'export' ) );
$form .= Xml::closeElement( 'form' );
-
+
$wgOut->addHTML( $form );
}
@@ -247,7 +272,7 @@ class SpecialExport extends SpecialPage {
foreach( $pages as $k => $v ) {
$pages[$k] = str_replace( " ", "_", $v );
}
-
+
$pages = array_unique( $pages );
/* Ok, let's get to it... */
@@ -266,11 +291,11 @@ class SpecialExport extends SpecialPage {
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 ) {
@@ -286,14 +311,18 @@ class SpecialExport extends SpecialPage {
}*/
#Bug 8824: Only export pages the user can read
$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.
+ 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();
}
@@ -314,7 +343,7 @@ class SpecialExport extends SpecialPage {
);
$pages = array();
-
+
foreach ( $res as $row ) {
$n = $row->page_title;
if ($row->page_namespace) {
@@ -340,10 +369,10 @@ class SpecialExport extends SpecialPage {
);
$pages = array();
-
+
foreach ( $res as $row ) {
$n = $row->page_title;
-
+
if ( $row->page_namespace ) {
$ns = $wgContLang->getNsText( $row->page_namespace );
$n = $ns . ':' . $n;
@@ -373,17 +402,17 @@ class SpecialExport extends SpecialPage {
*/
private function validateLinkDepth( $depth ) {
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
@@ -394,24 +423,24 @@ class SpecialExport extends SpecialPage {
/** Expand a list of pages to include pages linked to from that page. */
private function getPageLinks( $inputPages, $pageSet, $depth ) {
- for(; $depth > 0; --$depth ) {
+ for( ; $depth > 0; --$depth ) {
$pageSet = $this->getLinks(
$inputPages, $pageSet, 'pagelinks',
- array( 'pl_namespace AS namespace', 'pl_title AS title' ),
+ 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 ) {
@@ -429,13 +458,13 @@ class SpecialExport extends SpecialPage {
*/
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
+ /// @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 ),
@@ -449,15 +478,15 @@ class SpecialExport extends SpecialPage {
),
__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 c265ed38..6d621a2e 100644
--- a/includes/specials/SpecialFewestrevisions.php
+++ b/includes/specials/SpecialFewestrevisions.php
@@ -29,8 +29,8 @@
*/
class FewestrevisionsPage extends QueryPage {
- function getName() {
- return 'Fewestrevisions';
+ function __construct( $name = 'Fewestrevisions' ) {
+ parent::__construct( $name );
}
function isExpensive() {
@@ -41,31 +41,34 @@ class FewestrevisionsPage extends QueryPage {
return false;
}
- function getSql() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' );
-
- return "SELECT 'Fewestrevisions' as type,
- page_namespace as namespace,
- page_title as title,
- page_is_redirect as redirect,
- COUNT(*) as value
- FROM $revision
- JOIN $page ON page_id = rev_page
- WHERE page_namespace = " . NS_MAIN . "
- GROUP BY page_namespace, page_title, page_is_redirect
- HAVING COUNT(*) > 1";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'revision', 'page' ),
+ 'fields' => array ( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'COUNT(*) AS value',
+ 'page_is_redirect AS redirect' ),
+ 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_id = rev_page' ),
+ 'options' => array ( 'HAVING' => 'COUNT(*) > 1',
// ^^^ This was probably here to weed out redirects.
// Since we mark them as such now, it might be
// useful to remove this. People _do_ create pages
// and never revise them, they aren't necessarily
// redirects.
+ 'GROUP BY' => 'page_namespace, page_title, page_is_redirect' )
+ );
}
+
function sortDescending() {
return false;
}
+ /**
+ * @param $skin Skin object
+ * @param $result Object: database row
+ */
function formatResult( $skin, $result ) {
global $wgLang, $wgContLang;
@@ -94,9 +97,3 @@ class FewestrevisionsPage extends QueryPage {
return wfSpecialList( $plink, $nlink );
}
}
-
-function wfSpecialFewestrevisions() {
- list( $limit, $offset ) = wfCheckLimits();
- $frp = new FewestrevisionsPage();
- $frp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php
index 172e92ad..a296fd95 100644
--- a/includes/specials/SpecialFileDuplicateSearch.php
+++ b/includes/specials/SpecialFileDuplicateSearch.php
@@ -29,123 +29,161 @@
* @ingroup SpecialPage
*/
class FileDuplicateSearchPage extends QueryPage {
- var $hash, $filename;
+ protected $hash = '', $filename = '';
- function __construct( $hash, $filename ) {
- $this->hash = $hash;
- $this->filename = $filename;
+ /**
+ * @var File $file selected reference file, if present
+ */
+ protected $file = null;
+
+ function __construct( $name = 'FileDuplicateSearch' ) {
+ parent::__construct( $name );
}
- function getName() { return 'FileDuplicateSearch'; }
- function isExpensive() { return false; }
function isSyndicated() { return false; }
+ function isCacheable() { return false; }
+ function isCached() { return false; }
function linkParameters() {
return array( 'filename' => $this->filename );
}
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- $image = $dbr->tableName( 'image' );
- $hash = $dbr->addQuotes( $this->hash );
-
- return "SELECT 'FileDuplicateSearch' AS type,
- img_name AS title,
- img_sha1 AS value,
- img_user_text,
- img_timestamp
- FROM $image
- WHERE img_sha1 = $hash
- ";
+ /**
+ * Fetch dupes from all connected file repositories.
+ *
+ * @return Array of File objects
+ */
+ function getDupes() {
+ return RepoGroup::singleton()->findBySha1( $this->hash );
}
- function formatResult( $skin, $result ) {
- global $wgContLang, $wgLang;
+ /**
+ *
+ * @param $dupes Array of File objects
+ */
+ function showList( $dupes ) {
+ global $wgOut;
+ $skin = $this->getSkin();
- $nt = Title::makeTitle( NS_FILE, $result->title );
- $text = $wgContLang->convert( $nt->getText() );
- $plink = $skin->link(
- Title::newFromText( $nt->getPrefixedText() ),
- $text
- );
+ $html = array();
+ $html[] = $this->openList( 0 );
- $user = $skin->link( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text );
- $time = $wgLang->timeanddate( $result->img_timestamp );
+ foreach ( $dupes as $dupe ) {
+ $line = $this->formatResult( $skin, $dupe );
+ $html[] = "<li>" . $line . "</li>";
+ }
+ $html[] = $this->closeList();
- return "$plink . . $user . . $time";
+ $wgOut->addHtml( implode( "\n", $html ) );
}
-}
-/**
- * Output the HTML search form, and constructs the FileDuplicateSearch object.
- */
-function wfSpecialFileDuplicateSearch( $par = null ) {
- global $wgRequest, $wgOut, $wgLang, $wgContLang, $wgScript;
-
- $hash = '';
- $filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' );
-
- $title = Title::makeTitleSafe( NS_FILE, $filename );
- if( $title && $title->getText() != '' ) {
- $dbr = wfGetDB( DB_SLAVE );
- $image = $dbr->tableName( 'image' );
- $encFilename = $dbr->addQuotes( htmlspecialchars( $title->getDBkey() ) );
- $sql = "SELECT img_sha1 from $image where img_name = $encFilename";
- $res = $dbr->query( $sql );
- $row = $dbr->fetchRow( $res );
- if( $row !== false ) {
- $hash = $row[0];
- }
+ function getQueryInfo() {
+ return array(
+ 'tables' => array( 'image' ),
+ 'fields' => array(
+ 'img_name AS title',
+ 'img_sha1 AS value',
+ 'img_user_text',
+ 'img_timestamp'
+ ),
+ 'conds' => array( 'img_sha1' => $this->hash )
+ );
}
- # Create the input form
- $wgOut->addHTML(
- Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) .
- 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 ) . ' ' .
- Xml::submitButton( wfMsg( 'fileduplicatesearch-submit' ) ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
- );
-
- if( $hash != '' ) {
- $align = $wgContLang->alignEnd();
-
- # Show a thumbnail of the file
- $img = wfFindFile( $title );
- if ( $img ) {
- $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
- if( $thumb ) {
- $wgOut->addHTML( '<div style="float:' . $align . '" id="mw-fileduplicatesearch-icon">' .
- $thumb->toHtml( array( 'desc-link' => false ) ) . '<br />' .
- wfMsgExt( 'fileduplicatesearch-info', array( 'parse' ),
- $wgLang->formatNum( $img->getWidth() ),
- $wgLang->formatNum( $img->getHeight() ),
- $wgLang->formatSize( $img->getSize() ),
- $img->getMimeType()
- ) .
- '</div>' );
- }
+ function execute( $par ) {
+ global $wgRequest, $wgOut, $wgLang, $wgScript;
+
+ $this->setHeaders();
+ $this->outputHeader();
+
+ $this->filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' );
+ $this->file = null;
+ $this->hash = '';
+ $title = Title::newFromText( $this->filename, NS_FILE );
+ if( $title && $title->getText() != '' ) {
+ $this->file = wfFindFile( $title );
}
- # Do the query
- $wpp = new FileDuplicateSearchPage( $hash, $filename );
- list( $limit, $offset ) = wfCheckLimits();
- $count = $wpp->doQuery( $offset, $limit );
+ # Create the input form
+ $wgOut->addHTML(
+ Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
+ Xml::openElement( 'fieldset' ) .
+ Xml::element( 'legend', null, wfMsg( 'fileduplicatesearch-legend' ) ) .
+ Xml::inputLabel( wfMsg( 'fileduplicatesearch-filename' ), 'filename', 'filename', 50, $this->filename ) . ' ' .
+ Xml::submitButton( wfMsg( 'fileduplicatesearch-submit' ) ) .
+ Xml::closeElement( 'fieldset' ) .
+ Xml::closeElement( 'form' )
+ );
- # Show a short summary
- if( $count == 1 ) {
- $wgOut->wrapWikiMsg(
- "<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>",
- array( 'fileduplicatesearch-result-1', $filename )
- );
- } elseif ( $count > 1 ) {
+ if( $this->file ) {
+ $this->hash = $this->file->getSha1();
+ } elseif( $this->filename !== '' ) {
$wgOut->wrapWikiMsg(
- "<p class='mw-fileduplicatesearch-result-n'>\n$1\n</p>",
- array( 'fileduplicatesearch-result-n', $filename, $wgLang->formatNum( $count - 1 ) )
+ "<p class='mw-fileduplicatesearch-noresults'>\n$1\n</p>",
+ array( 'fileduplicatesearch-noresults', wfEscapeWikiText( $this->filename ) )
);
}
+
+ if( $this->hash != '' ) {
+ # Show a thumbnail of the file
+ $img = $this->file;
+ if ( $img ) {
+ $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
+ if( $thumb ) {
+ $wgOut->addHTML( '<div id="mw-fileduplicatesearch-icon">' .
+ $thumb->toHtml( array( 'desc-link' => false ) ) . '<br />' .
+ wfMsgExt( 'fileduplicatesearch-info', array( 'parse' ),
+ $wgLang->formatNum( $img->getWidth() ),
+ $wgLang->formatNum( $img->getHeight() ),
+ $wgLang->formatSize( $img->getSize() ),
+ $img->getMimeType()
+ ) .
+ '</div>' );
+ }
+ }
+
+ $dupes = $this->getDupes();
+ $numRows = count( $dupes );
+
+ # Show a short summary
+ if( $numRows == 1 ) {
+ $wgOut->wrapWikiMsg(
+ "<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>",
+ array( 'fileduplicatesearch-result-1', wfEscapeWikiText( $this->filename ) )
+ );
+ } elseif ( $numRows ) {
+ $wgOut->wrapWikiMsg(
+ "<p class='mw-fileduplicatesearch-result-n'>\n$1\n</p>",
+ array( 'fileduplicatesearch-result-n', wfEscapeWikiText( $this->filename ),
+ $wgLang->formatNum( $numRows - 1 ) )
+ );
+ }
+
+ $this->showList( $dupes );
+ }
+ }
+
+ /**
+ *
+ * @param Skin $skin
+ * @param File $result
+ * @return string
+ */
+ function formatResult( $skin, $result ) {
+ global $wgContLang, $wgLang;
+
+ $nt = $result->getTitle();
+ $text = $wgContLang->convert( $nt->getText() );
+ $plink = $skin->link(
+ Title::newFromText( $nt->getPrefixedText() ),
+ $text
+ );
+
+ $userText = $result->getUser( 'text' );
+ $user = $skin->link( Title::makeTitle( NS_USER, $userText ), $userText );
+ $time = $wgLang->timeanddate( $result->getTimestamp() );
+
+ return "$plink . . $user . . $time";
}
}
diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php
index 8bb0890c..08f90fd2 100644
--- a/includes/specials/SpecialFilepath.php
+++ b/includes/specials/SpecialFilepath.php
@@ -40,19 +40,25 @@ class SpecialFilepath extends SpecialPage {
$file = !is_null( $par ) ? $par : $wgRequest->getText( 'file' );
- $title = Title::makeTitleSafe( NS_FILE, $file );
+ $title = Title::newFromText( $file, NS_FILE );
if ( ! $title instanceof Title || $title->getNamespace() != NS_FILE ) {
$this->showForm( $title );
} else {
$file = wfFindFile( $title );
+
if ( $file && $file->exists() ) {
+ // Default behaviour: Use the direct link to the file.
$url = $file->getURL();
$width = $wgRequest->getInt( 'width', -1 );
$height = $wgRequest->getInt( 'height', -1 );
+
+ // If a width is requested...
if ( $width != -1 ) {
$mto = $file->transform( array( 'width' => $width, 'height' => $height ) );
+ // ... and we can
if ( $mto && !$mto->isError() ) {
+ // ... change the URL to point to a thumbnail.
$url = $mto->getURL();
}
}
@@ -64,6 +70,9 @@ class SpecialFilepath extends SpecialPage {
}
}
+ /**
+ * @param $title Title
+ */
function showForm( $title ) {
global $wgOut, $wgScript;
diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php
index 7d1cf0dd..fc904a23 100644
--- a/includes/specials/SpecialImport.php
+++ b/includes/specials/SpecialImport.php
@@ -30,14 +30,15 @@
* @ingroup SpecialPage
*/
class SpecialImport extends SpecialPage {
-
+
private $interwiki = false;
private $namespace;
private $frompage = '';
private $logcomment= false;
private $history = true;
private $includeTemplates = false;
-
+ private $pageLinkDepth;
+
/**
* Constructor
*/
@@ -46,26 +47,27 @@ class SpecialImport extends SpecialPage {
global $wgImportTargetNamespace;
$this->namespace = $wgImportTargetNamespace;
}
-
+
/**
* Execute
*/
function execute( $par ) {
global $wgRequest, $wgUser, $wgOut;
-
+
$this->setHeaders();
$this->outputHeader();
-
+
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
return;
}
-
- if( !$wgUser->isAllowed( 'import' ) && !$wgUser->isAllowed( 'importupload' ) )
+
+ if( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) {
return $wgOut->permissionRequired( 'import' );
+ }
- # TODO: allow Title::getUserPermissionsErrors() to take an array
- # FIXME: Title::checkSpecialsAndNSPermissions() has a very wierd expectation of what
+ # @todo Allow Title::getUserPermissionsErrors() to take an array
+ # @todo FIXME: Title::checkSpecialsAndNSPermissions() has a very wierd expectation of what
# getUserPermissionsErrors() might actually be used for, hence the 'ns-specialprotected'
$errors = wfMergeErrorArrays(
$this->getTitle()->getUserPermissionsErrors(
@@ -88,7 +90,7 @@ class SpecialImport extends SpecialPage {
}
$this->showForm();
}
-
+
/**
* Do the actual import
*/
@@ -162,7 +164,7 @@ class SpecialImport extends SpecialPage {
# Success!
$wgOut->addWikiMsg( 'importsuccess' );
}
- $wgOut->addWikiText( '<hr />' );
+ $wgOut->addHTML( '<hr />' );
}
}
@@ -290,7 +292,7 @@ class SpecialImport extends SpecialPage {
<td>
</td>
<td class='mw-submit'>" .
- Xml::submitButton( wfMsg( 'import-interwiki-submit' ), $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'import' ) ) .
+ Xml::submitButton( wfMsg( 'import-interwiki-submit' ), Linker::tooltipAndAccesskeyAttribs( 'import' ) ) .
"</td>
</tr>" .
Xml::closeElement( 'table' ).
@@ -312,8 +314,8 @@ class ImportReporter {
private $mLogItemCount = 0;
function __construct( $importer, $upload, $interwiki , $reason=false ) {
- $this->mOriginalPageOutCallback =
- $importer->setPageOutCallback( array( $this, 'reportPage' ) );
+ $this->mOriginalPageOutCallback =
+ $importer->setPageOutCallback( array( $this, 'reportPage' ) );
$this->mOriginalLogCallback =
$importer->setLogItemCallback( array( $this, 'reportLogItem' ) );
$this->mPageCount = 0;
@@ -326,7 +328,7 @@ class ImportReporter {
global $wgOut;
$wgOut->addHTML( "<ul>\n" );
}
-
+
function reportLogItem( /* ... */ ) {
$this->mLogItemCount++;
if ( is_callable( $this->mOriginalLogCallback ) ) {
@@ -334,21 +336,27 @@ class ImportReporter {
}
}
+ /**
+ * @param Title $title
+ * @param Title $origTitle
+ * @param int $revisionCount
+ * @param $successCount
+ * @param $pageInfo
+ * @return void
+ */
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();
-
$this->mPageCount++;
$localCount = $wgLang->formatNum( $successCount );
$contentCount = $wgContLang->formatNum( $successCount );
if( $successCount > 0 ) {
- $wgOut->addHTML( "<li>" . $skin->linkKnown( $title ) . " " .
+ $wgOut->addHTML( "<li>" . Linker::linkKnown( $title ) . " " .
wfMsgExt( 'import-revision-count', array( 'parsemag', 'escape' ), $localCount ) .
"</li>\n"
);
@@ -382,14 +390,14 @@ class ImportReporter {
$article->updateRevisionOn( $dbw, $nullRevision );
wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
} else {
- $wgOut->addHTML( "<li>" . $skin->linkKnown( $title ) . " " .
+ $wgOut->addHTML( "<li>" . Linker::linkKnown( $title ) . " " .
wfMsgHtml( 'import-nonewrevisions' ) . "</li>\n" );
}
}
function close() {
global $wgOut, $wgLang;
-
+
if ( $this->mLogItemCount > 0 ) {
$msg = wfMsgExt( 'imported-log-entries', 'parseinline',
$wgLang->formatNum( $this->mLogItemCount ) );
diff --git a/includes/specials/SpecialIpblocklist.php b/includes/specials/SpecialIpblocklist.php
deleted file mode 100644
index 24d7f008..00000000
--- a/includes/specials/SpecialIpblocklist.php
+++ /dev/null
@@ -1,581 +0,0 @@
-<?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
- */
-
-/**
- * A special page that lists existing blocks and allows users with the 'block'
- * permission to remove blocks
- *
- * @ingroup SpecialPage
- */
-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;
-
- $this->setHeaders();
- $this->outputHeader();
-
- $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->addWikiMsg( 'unblockiptext' );
-
- $action = $this->getTitle()->getLocalURL( 'action=submit' );
-
- if ( $err != '' ) {
- $wgOut->setSubtitle( wfMsg( 'formerror' ) );
- $wgOut->addWikiText( Html::rawElement( 'span', array( 'class' => 'error' ), $err ) . "\n" );
- }
-
- $addressPart = false;
- if ( $this->id ) {
- $block = Block::newFromID( $this->id );
- if ( $block ) {
- $encName = htmlspecialchars( $block->getRedactedName() );
- $encId = $this->id;
- $addressPart = $encName . Html::hidden( 'id', $encId );
- $ipa = wfMsgHtml( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' );
- }
- }
- if ( !$addressPart ) {
- $addressPart = Xml::input( 'wpUnblockAddress', 40, $this->ip, array( 'type' => 'text', 'tabindex' => '1' ) );
- $ipa = Xml::label( wfMsg( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' ), 'wpUnblockAddress' );
- }
-
- $wgOut->addHTML(
- 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}
- </td>
- <td class='mw-input'>
- {$addressPart}
- </td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( wfMsg( 'ipbreason' ), 'wpUnblockReason' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'wpUnblockReason', 40, $this->reason, array( 'type' => 'text', 'tabindex' => '2' ) ) .
- "</td>
- </tr>
- <tr>
- <td>&#160;</td>
- <td class='mw-submit'>" .
- Xml::submitButton( wfMsg( 'ipusubmit' ), array( 'name' => 'wpBlock', 'tabindex' => '3' ) ) .
- "</td>
- </tr>" .
- Html::closeElement( 'table' ) .
- Html::closeElement( 'fieldset' ) .
- Html::hidden( 'wpEditToken', $wgUser->editToken() ) .
- Html::closeElement( 'form' ) . "\n"
- );
-
- }
-
- const UNBLOCK_SUCCESS = 0; // Success
- const UNBLOCK_NO_SUCH_ID = 1; // No such block ID
- const UNBLOCK_USER_NOT_BLOCKED = 2; // IP wasn't blocked
- const UNBLOCK_BLOCKED_AS_RANGE = 3; // IP is part of a range block
- const UNBLOCK_UNKNOWNERR = 4; // Unknown error
-
- /**
- * Backend code for unblocking. doSubmit() wraps around this.
- * $range is only used when UNBLOCK_BLOCKED_AS_RANGE is returned, in which
- * case it contains the range $ip is part of.
- * @return array array(message key, parameters) on failure, empty array on success
- */
- 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 ) );
- }
- $ip = $block->getRedactedName();
- } else {
- $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 ) );
- }
- $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 ) );
- }
- 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 );
- }
- }
- }
- // Yes, this is really necessary
- $id = $block->mId;
-
- # 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 ) );
- }
-
- # Delete block
- if ( !$block->delete() ) {
- return array( 'ipb_cant_unblock', htmlspecialchars( $id ) );
- }
-
- # Unset _deleted fields as needed
- if( $block->mHideName ) {
- IPBlockForm::unsuppressUserName( $block->mAddress, $block->mUser );
- }
-
- # Make log entry
- $log = new LogPage( 'block' );
- $log->addEntry( 'unblock', Title::makeTitle( NS_USER, $ip ), $reason );
- return array();
- }
-
- 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 ) );
- return;
- }
-
- # Report to the user
- $success = $this->getTitle()->getFullURL( 'action=success&successip=' . urlencode( $this->ip ) );
- $wgOut->redirect( $success );
- }
-
- function showList( $msg ) {
- global $wgOut, $wgUser;
-
- if ( $msg != '' ) {
- $wgOut->setSubtitle( $msg );
- }
-
- // Purge expired entries on one in every 10 queries
- if ( !mt_rand( 0, 10 ) ) {
- Block::purgeExpired();
- }
-
- $conds = array();
- // Is user allowed to see all the blocks?
- if ( !$wgUser->isAllowed( 'hideuser' ) )
- $conds['ipb_deleted'] = 0;
- if ( $this->ip == '' ) {
- // No extra conditions
- } elseif ( substr( $this->ip, 0, 1 ) == '#' ) {
- $conds['ipb_id'] = substr( $this->ip, 1 );
- // Single IPs
- } 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 ) );
- $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_auto'] = 0;
- // IP range
- } elseif ( IP::isIPAddress( $this->ip ) ) {
- $conds['ipb_address'] = Block::normaliseRange( $this->ip );
- $conds['ipb_auto'] = 0;
- } else {
- $user = User::newFromName( $this->ip );
- if ( $user && ( $id = $user->getId() ) != 0 ) {
- $conds['ipb_user'] = $id;
- } else {
- // Uh...?
- $conds['ipb_address'] = $this->ip;
- $conds['ipb_auto'] = 0;
- }
- }
- // Apply filters
- if( $this->hideuserblocks ) {
- $conds['ipb_user'] = 0;
- }
- if( $this->hidetempblocks ) {
- $conds['ipb_expiry'] = 'infinity';
- }
- if( $this->hideaddressblocks ) {
- $conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start";
- }
-
- // Search form
- $wgOut->addHTML( $this->searchForm() );
-
- // Check for other blocks, i.e. global/tor blocks
- $otherBlockLink = array();
- wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockLink, $this->ip ) );
-
- // Show additional header for the local block only when other blocks exists.
- // Not necessary in a standard installation without such extensions enabled
- if( count( $otherBlockLink ) ) {
- $wgOut->addHTML(
- Html::rawElement( 'h2', array(), wfMsg( 'ipblocklist-localblock' ) ) . "\n"
- );
- }
- $pager = new IPBlocklistPager( $this, $conds );
- if ( $pager->getNumRows() ) {
- $wgOut->addHTML(
- $pager->getNavigationBar() .
- Html::rawElement( 'ul', null, $pager->getBody() ) .
- $pager->getNavigationBar()
- );
- } elseif ( $this->ip != '') {
- $wgOut->addWikiMsg( 'ipblocklist-no-results' );
- } else {
- $wgOut->addWikiMsg( 'ipblocklist-empty' );
- }
-
- if( count( $otherBlockLink ) ) {
- $wgOut->addHTML(
- Html::rawElement( 'h2', array(), wfMsgExt( 'ipblocklist-otherblocks', 'parseinline', count( $otherBlockLink ) ) ) . "\n"
- );
- $list = '';
- foreach( $otherBlockLink as $link ) {
- $list .= Html::rawElement( 'li', array(), $link ) . "\n";
- }
- $wgOut->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-ipblocklist-otherblocks' ), $list ) . "\n" );
- }
-
- }
-
- function searchForm() {
- global $wgScript, $wgLang;
-
- $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) );
- $nondefaults = array();
- if( $this->hideuserblocks ) {
- $nondefaults['hideuserblocks'] = $this->hideuserblocks;
- }
- if( $this->hidetempblocks ) {
- $nondefaults['hidetempblocks'] = $this->hidetempblocks;
- }
- if( $this->hideaddressblocks ) {
- $nondefaults['hideaddressblocks'] = $this->hideaddressblocks;
- }
- $ubLink = $this->makeOptionsLink( $showhide[1-$this->hideuserblocks],
- array( 'hideuserblocks' => 1-$this->hideuserblocks ), $nondefaults);
- $tbLink = $this->makeOptionsLink( $showhide[1-$this->hidetempblocks],
- array( 'hidetempblocks' => 1-$this->hidetempblocks ), $nondefaults);
- $sipbLink = $this->makeOptionsLink( $showhide[1-$this->hideaddressblocks],
- array( 'hideaddressblocks' => 1-$this->hideaddressblocks ), $nondefaults);
-
- $links = array();
- $links[] = wfMsgHtml( 'ipblocklist-sh-userblocks', $ubLink );
- $links[] = wfMsgHtml( 'ipblocklist-sh-tempblocks', $tbLink );
- $links[] = wfMsgHtml( 'ipblocklist-sh-addressblocks', $sipbLink );
- $hl = $wgLang->pipeList( $links );
-
- return
- 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 ) .
- '&#160;' .
- Xml::submitButton( wfMsg( 'ipblocklist-submit' ) ) . '<br />' .
- $hl .
- Html::closeElement( 'fieldset' )
- );
- }
-
- /**
- * Makes change an option link which carries all the other options
- *
- * @param $title see Title
- * @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;
- return $sk->link( $this->getTitle(), htmlspecialchars( $title ),
- ( $active ? array( 'style'=>'font-weight: bold;' ) : array() ), $params, array( 'known' ) );
- }
-
- /**
- * Callback function to output a block
- */
- function formatRow( $block ) {
- global $wgUser, $wgLang, $wgBlockAllowsUTEdit;
-
- wfProfileIn( __METHOD__ );
-
- static $sk=null, $msg=null;
-
- if( is_null( $sk ) )
- $sk = $wgUser->getSkin();
- if( is_null( $msg ) ) {
- $msg = array();
- $keys = array( 'infiniteblock', 'expiringblock', 'unblocklink', 'change-blocklink',
- 'anononlyblock', 'createaccountblock', 'noautoblockblock', 'emailblock', 'blocklist-nousertalk', 'blocklistline' );
- foreach( $keys as $key ) {
- $msg[$key] = wfMsgHtml( $key );
- }
- }
-
- # Prepare links to the blocker's user and talk pages
- $blocker_id = $block->getBy();
- $blocker_name = $block->getByName();
- $blocker = $sk->userLink( $blocker_id, $blocker_name );
- $blocker .= $sk->userToolLinks( $blocker_id, $blocker_name );
-
- # Prepare links to the block target's user and contribs. pages (as applicable, don't do it for autoblocks)
- if( $block->mAuto ) {
- $target = $block->getRedactedName(); # Hide the IP addresses of auto-blocks; privacy
- } else {
- $target = $sk->userLink( $block->mUser, $block->mAddress )
- . $sk->userToolLinks( $block->mUser, $block->mAddress, false, Linker::TOOL_LINKS_NOBLOCK );
- }
-
- $formattedTime = htmlspecialchars( $wgLang->timeanddate( $block->mTimestamp, true ) );
-
- $properties = array();
- $properties[] = Block::formatExpiry( $block->mExpiry );
- if ( $block->mAnonOnly ) {
- $properties[] = $msg['anononlyblock'];
- }
- if ( $block->mCreateAccount ) {
- $properties[] = $msg['createaccountblock'];
- }
- if (!$block->mEnableAutoblock && $block->mUser ) {
- $properties[] = $msg['noautoblockblock'];
- }
-
- if ( $block->mBlockEmail && $block->mUser ) {
- $properties[] = $msg['emailblock'];
- }
-
- if ( !$block->mAllowUsertalk && $wgBlockAllowsUTEdit ) {
- $properties[] = $msg['blocklist-nousertalk'];
- }
-
- $properties = $wgLang->commaList( $properties );
-
- $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) );
-
- $changeblocklink = '';
- $toolLinks = '';
- if ( $wgUser->isAllowed( 'block' ) ) {
- $unblocklink = $sk->link( $this->getTitle(),
- $msg['unblocklink'],
- array(),
- array( 'action' => 'unblock', 'id' => $block->mId ),
- 'known' );
-
- # Create changeblocklink for all blocks with exception of autoblocks
- if( !$block->mAuto ) {
- $changeblocklink = wfMsgExt( 'pipe-separator', 'escapenoentities' ) .
- $sk->link( SpecialPage::getTitleFor( 'Blockip', $block->mAddress ),
- $msg['change-blocklink'],
- array(), array(), 'known' );
- }
- $toolLinks = "($unblocklink$changeblocklink)";
- }
-
- $comment = $sk->commentBlock( htmlspecialchars($block->mReason) );
-
- $s = "{$line} $comment";
- if ( $block->mHideName )
- $s = '<span class="history-deleted">' . $s . '</span>';
-
- wfProfileOut( __METHOD__ );
- return "<li>$s $toolLinks</li>\n";
- }
-}
-
-/**
- * @todo document
- * @ingroup Pager
- */
-class IPBlocklistPager extends ReverseChronologicalPager {
- public $mForm, $mConds;
-
- function __construct( $form, $conds = array() ) {
- $this->mForm = $form;
- $this->mConds = $conds;
- parent::__construct();
- }
-
- function getStartBody() {
- wfProfileIn( __METHOD__ );
- # Do a link batch query
- $this->mResult->seek( 0 );
- $lb = new LinkBatch;
-
- /*
- while ( $row = $this->mResult->fetchObject() ) {
- $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
- $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
- $lb->addObj( Title::makeTitleSafe( NS_USER, $row->ipb_address ) );
- $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ipb_address ) );
- }*/
- # 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.
- foreach ( $this->mResult as $row ) {
- $name = str_replace( ' ', '_', $row->ipb_by_text );
- $lb->add( NS_USER, $name );
- $lb->add( NS_USER_TALK, $name );
- $name = str_replace( ' ', '_', $row->ipb_address );
- $lb->add( NS_USER, $name );
- $lb->add( NS_USER_TALK, $name );
- }
- $lb->execute();
- wfProfileOut( __METHOD__ );
- return '';
- }
-
- function formatRow( $row ) {
- $block = new Block;
- $block->initFromRow( $row );
- return $this->mForm->formatRow( $block );
- }
-
- function getQueryInfo() {
- $conds = $this->mConds;
- $conds[] = 'ipb_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() );
- return array(
- 'tables' => 'ipblocks',
- 'fields' => '*',
- 'conds' => $conds,
- );
- }
-
- function getIndexField() {
- return 'ipb_timestamp';
- }
-}
diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php
index 9dee9d5a..bef859a2 100644
--- a/includes/specials/SpecialLinkSearch.php
+++ b/includes/specials/SpecialLinkSearch.php
@@ -21,74 +21,10 @@
* @ingroup SpecialPage
* @author Brion Vibber
*/
-
-/**
- * Special:LinkSearch to search the external-links table.
- */
-function wfSpecialLinkSearch( $par ) {
-
- list( $limit, $offset ) = wfCheckLimits();
- global $wgOut, $wgUrlProtocols, $wgMiserMode, $wgLang;
- $target = $GLOBALS['wgRequest']->getVal( 'target', $par );
- $namespace = $GLOBALS['wgRequest']->getIntorNull( 'namespace', null );
-
- $protocols_list[] = '';
- foreach( $wgUrlProtocols as $prot ) {
- $protocols_list[] = $prot;
- }
-
- $target2 = $target;
- $protocol = '';
- $pr_sl = strpos($target2, '//' );
- $pr_cl = strpos($target2, ':' );
- if ( $pr_sl ) {
- // For protocols with '//'
- $protocol = substr( $target2, 0 , $pr_sl+2 );
- $target2 = substr( $target2, $pr_sl+2 );
- } elseif ( !$pr_sl && $pr_cl ) {
- // For protocols without '//' like 'mailto:'
- $protocol = substr( $target2, 0 , $pr_cl+1 );
- $target2 = substr( $target2, $pr_cl+1 );
- } elseif ( $protocol == '' && $target2 != '' ) {
- // default
- $protocol = 'http://';
- }
- if ( !in_array( $protocol, $protocols_list ) ) {
- // unsupported protocol, show original search request
- $target2 = $target;
- $protocol = '';
- }
-
- $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'] ) ) .
- 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, '' );
- }
- $s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) .
- '</fieldset>' .
- Xml::closeElement( 'form' );
- $wgOut->addHTML( $s );
-
- if( $target != '' ) {
- $searcher = new LinkSearchPage;
- $searcher->setParams( array(
- 'query' => $target2,
- 'namespace' => $namespace,
- 'protocol' => $protocol ) );
- $searcher->doQuery( $offset, $limit );
- }
-}
/**
+ * Special:LinkSearch to search the external-links table.
* @ingroup SpecialPage
*/
class LinkSearchPage extends QueryPage {
@@ -98,8 +34,75 @@ class LinkSearchPage extends QueryPage {
$this->mProt = $params['protocol'];
}
- function getName() {
- return 'LinkSearch';
+ function __construct( $name = 'LinkSearch' ) {
+ parent::__construct( $name );
+ }
+
+ function isCacheable() {
+ return false;
+ }
+
+ function execute( $par ) {
+ global $wgOut, $wgRequest, $wgUrlProtocols, $wgMiserMode, $wgLang;
+ $this->setHeaders();
+ $wgOut->allowClickjacking();
+
+ $target = $wgRequest->getVal( 'target', $par );
+ $namespace = $wgRequest->getIntorNull( 'namespace', null );
+
+ $protocols_list = array();
+ foreach( $wgUrlProtocols as $prot ) {
+ if ( $prot !== '//' ) {
+ $protocols_list[] = $prot;
+ }
+ }
+
+ $target2 = $target;
+ $protocol = '';
+ $pr_sl = strpos($target2, '//' );
+ $pr_cl = strpos($target2, ':' );
+ if ( $pr_sl ) {
+ // For protocols with '//'
+ $protocol = substr( $target2, 0 , $pr_sl+2 );
+ $target2 = substr( $target2, $pr_sl+2 );
+ } elseif ( !$pr_sl && $pr_cl ) {
+ // For protocols without '//' like 'mailto:'
+ $protocol = substr( $target2, 0 , $pr_cl+1 );
+ $target2 = substr( $target2, $pr_cl+1 );
+ } elseif ( $protocol == '' && $target2 != '' ) {
+ // default
+ $protocol = 'http://';
+ }
+ if ( $protocol != '' && !in_array( $protocol, $protocols_list ) ) {
+ // unsupported protocol, show original search request
+ $target2 = $target;
+ $protocol = '';
+ }
+
+ $out->addWikiMsg( 'linksearch-text', '<nowiki>' . $this->getLanguage()->commaList( $protocols_list ) . '</nowiki>' );
+ $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, '' );
+ }
+ $s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) .
+ '</fieldset>' .
+ Xml::closeElement( 'form' );
+ $wgOut->addHTML( $s );
+
+ if( $target != '' ) {
+ $this->setParams( array(
+ 'query' => $target2,
+ 'namespace' => $namespace,
+ 'protocol' => $protocol ) );
+ parent::execute( $par );
+ if( $this->mMungedQuery === false )
+ $wgOut->addWikiMsg( 'linksearch-error' );
+ }
}
/**
@@ -111,15 +114,17 @@ class LinkSearchPage extends QueryPage {
/**
* Return an appropriately formatted LIKE query and the clause
+ *
+ * @return array
*/
- static function mungeQuery( $query , $prot ) {
+ static function mungeQuery( $query, $prot ) {
$field = 'el_index';
$rv = LinkFilter::makeLikeArray( $query , $prot );
- if ($rv === false) {
+ if ( $rv === false ) {
// 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() );
+ $rv = array( $prot . rtrim( $query, " \t*" ), $dbr->anyString() );
$field = 'el_to';
}
}
@@ -136,35 +141,32 @@ class LinkSearchPage extends QueryPage {
return $params;
}
- function getSQL() {
+ function getQueryInfo() {
global $wgMiserMode;
$dbr = wfGetDB( DB_SLAVE );
- $page = $dbr->tableName( 'page' );
- $externallinks = $dbr->tableName( 'externallinks' );
-
- /* strip everything past first wildcard, so that index-based-only lookup would be done */
- list( $munged, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
- $stripped = LinkFilter::keepOneWildcard( $munged );
+ // strip everything past first wildcard, so that
+ // index-based-only lookup would be done
+ list( $this->mMungedQuery, $clause ) = self::mungeQuery(
+ $this->mQuery, $this->mProt );
+ if( $this->mMungedQuery === false )
+ // Invalid query; return no results
+ return array( 'tables' => 'page', 'fields' => 'page_id', 'conds' => '0=1' );
+
+ $stripped = LinkFilter::keepOneWildcard( $this->mMungedQuery );
$like = $dbr->buildLike( $stripped );
-
- $encSQL = '';
- if ( isset ($this->mNs) && !$wgMiserMode )
- $encSQL = 'AND page_namespace=' . $dbr->addQuotes( $this->mNs );
-
- $use_index = $dbr->useIndexClause( $clause );
- return
- "SELECT
- page_namespace AS namespace,
- page_title AS title,
- el_index AS value,
- el_to AS url
- FROM
- $page,
- $externallinks $use_index
- WHERE
- page_id=el_from
- AND $clause $like
- $encSQL";
+ $retval = array (
+ 'tables' => array ( 'page', 'externallinks' ),
+ 'fields' => array ( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'el_index AS value', 'el_to AS url' ),
+ 'conds' => array ( 'page_id = el_from',
+ "$clause $like" ),
+ 'options' => array( 'USE INDEX' => $clause )
+ );
+ if ( isset( $this->mNs ) && !$wgMiserMode ) {
+ $retval['conds']['page_namespace'] = $this->mNs;
+ }
+ return $retval;
}
function formatResult( $skin, $result ) {
@@ -179,7 +181,7 @@ class LinkSearchPage extends QueryPage {
/**
* Override to check query validity.
*/
- function doQuery( $offset, $limit, $shownavigation=true ) {
+ function doQuery( $offset = false, $limit = false ) {
global $wgOut;
list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt );
if( $this->mMungedQuery === false ) {
@@ -188,7 +190,7 @@ class LinkSearchPage extends QueryPage {
// For debugging
// Generates invalid xhtml with patterns that contain --
//$wgOut->addHTML( "\n<!-- " . htmlspecialchars( $this->mMungedQuery ) . " -->\n" );
- parent::doQuery( $offset, $limit, $shownavigation );
+ parent::doQuery( $offset, $limit );
}
}
@@ -198,7 +200,7 @@ class LinkSearchPage extends QueryPage {
* it as good enough for optimizing sort. The implicit ordering
* from the scan will usually do well enough for our needs.
*/
- function getOrder() {
- return '';
+ function getOrderFields() {
+ return array();
}
}
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
index 350e833b..427de167 100644
--- a/includes/specials/SpecialListfiles.php
+++ b/includes/specials/SpecialListfiles.php
@@ -20,16 +20,38 @@
* @file
* @ingroup SpecialPage
*/
-
-function wfSpecialListfiles( $par = null ) {
- global $wgOut;
- $pager = new ImageListPager( $par );
+class SpecialListFiles extends IncludableSpecialPage {
- $limit = $pager->getForm();
- $body = $pager->getBody();
- $nav = $pager->getNavigationBar();
- $wgOut->addHTML( "$limit<br />\n$body<br />\n$nav" );
+ public function __construct(){
+ parent::__construct( 'Listfiles' );
+ }
+
+ public function execute( $par ){
+ global $wgOut, $wgRequest;
+ $this->setHeaders();
+ $this->outputHeader();
+
+ if ( $this->including() ) {
+ $userName = $par;
+ $search = '';
+ } else {
+ $userName = $wgRequest->getText( 'user', $par );
+ $search = $wgRequest->getText( 'ilsearch', '' );
+ }
+
+ $pager = new ImageListPager( $userName, $search, $this->including() );
+
+ if ( $this->including() ) {
+ $html = $pager->getBody();
+ } else {
+ $form = $pager->getForm();
+ $body = $pager->getBody();
+ $nav = $pager->getNavigationBar();
+ $html = "$form<br />\n$body<br />\n$nav";
+ }
+ $wgOut->addHTML( $html );
+ }
}
/**
@@ -39,46 +61,62 @@ class ImageListPager extends TablePager {
var $mFieldNames = null;
var $mQueryConds = array();
var $mUserName = null;
-
- function __construct( $par = null ) {
+ var $mSearch = '';
+ var $mIncluding = false;
+
+ function __construct( $userName = null, $search = '', $including = false ) {
global $wgRequest, $wgMiserMode;
- if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) {
- $this->mDefaultDirection = true;
- } else {
- $this->mDefaultDirection = false;
- }
-
- $userName = $wgRequest->getText( 'user', $par );
+
+ $this->mIncluding = $including;
+
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 );
+ $this->mSearch = $search;
+ $nt = Title::newFromURL( $this->mSearch );
if ( $nt ) {
$dbr = wfGetDB( DB_SLAVE );
- $this->mQueryConds[] = '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() );
}
}
+ if ( !$including ) {
+ if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) {
+ $this->mDefaultDirection = true;
+ } else {
+ $this->mDefaultDirection = false;
+ }
+ } else {
+ $this->mDefaultDirection = true;
+ }
+
parent::__construct();
}
+ function getTitle() {
+ return SpecialPage::getTitleFor( 'Listfiles' );
+ }
+
+ /**
+ * @return Array
+ */
function getFieldNames() {
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' ),
+ 'thumb' => wfMsg( 'listfiles_thumb' ),
'img_size' => wfMsg( 'listfiles_size' ),
+ 'img_user_text' => wfMsg( 'listfiles_user' ),
'img_description' => wfMsg( 'listfiles_description' ),
);
if( !$wgMiserMode ) {
@@ -89,6 +127,9 @@ class ImageListPager extends TablePager {
}
function isFieldSortable( $field ) {
+ if ( $this->mIncluding ) {
+ return false;
+ }
static $sortable = array( 'img_timestamp', 'img_name' );
if ( $field == 'img_size' ) {
# No index for both img_size and img_user_text
@@ -101,7 +142,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';
+ $fields[array_search('thumb', $fields)] = 'img_name AS thumb';
$options = $join_conds = array();
# Depends on $wgMiserMode
@@ -111,7 +152,7 @@ class ImageListPager extends TablePager {
# Need to rewrite this one
foreach ( $fields as &$field ) {
if ( $field == 'count' ) {
- $field = 'COUNT(oi_archive_name) as count';
+ $field = 'COUNT(oi_archive_name) AS count';
}
}
unset( $field );
@@ -120,7 +161,8 @@ class ImageListPager extends TablePager {
if( $dbr->implicitGroupby() ) {
$options = array( 'GROUP BY' => 'img_name' );
} else {
- $columnlist = implode( ',', preg_grep( '/^img/', array_keys( $this->getFieldNames() ) ) );
+ $columnlist = implode( ',',
+ preg_grep( '/^img/', array_keys( $this->getFieldNames() ) ) );
$options = array( 'GROUP BY' => "img_user, $columnlist" );
}
$join_conds = array( 'oldimage' => array( 'LEFT JOIN', 'oi_name = img_name' ) );
@@ -159,7 +201,7 @@ class ImageListPager extends TablePager {
switch ( $field ) {
case 'thumb':
$file = wfLocalFile( $value );
- $thumb = $file->transform( array( 'width' => 180 ) );
+ $thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) );
return $thumb->toHtml( array( 'desc-link' => true ) );
case 'img_timestamp':
return htmlspecialchars( $wgLang->timeanddate( $value, true ) );
@@ -167,12 +209,18 @@ class ImageListPager extends TablePager {
static $imgfile = null;
if ( $imgfile === null ) $imgfile = wfMsg( 'imgfile' );
- $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 );
- return "$link ($download)";
+ // Weird files can maybe exist? Bug 22227
+ $filePage = Title::makeTitleSafe( NS_FILE, $value );
+ if( $filePage ) {
+ $link = $this->getSkin()->linkKnown( $filePage, htmlspecialchars( $filePage->getText() ) );
+ $download = Xml::element( 'a',
+ array( 'href' => wfLocalFile( $filePage )->getURL() ),
+ $imgfile
+ );
+ return "$link ($download)";
+ } else {
+ return htmlspecialchars( $value );
+ }
case 'img_user_text':
if ( $this->mCurrentRow->img_user ) {
$link = $this->getSkin()->link(
@@ -188,34 +236,34 @@ class ImageListPager extends TablePager {
case 'img_description':
return $this->getSkin()->commentBlock( $value );
case 'count':
- return intval($value)+1;
+ return intval( $value ) + 1;
}
}
function getForm() {
- global $wgRequest, $wgScript, $wgMiserMode;
- $search = $wgRequest->getText( 'ilsearch' );
+ global $wgScript, $wgMiserMode;
$inputForm = array();
$inputForm['table_pager_limit_label'] = $this->getLimitSelect();
if ( !$wgMiserMode ) {
- $inputForm['listfiles_search_for'] = Html::input( 'ilsearch', $search, 'text', array(
- 'size' => '40',
- 'maxlength' => '255',
- 'id' => 'mw-ilsearch',
+ $inputForm['listfiles_search_for'] = Html::input( 'ilsearch', $this->mSearch, 'text',
+ array(
+ 'size' => '40',
+ 'maxlength' => '255',
+ 'id' => 'mw-ilsearch',
) );
}
$inputForm['username'] = Html::input( 'user', $this->mUserName, 'text', array(
- 'size' => '40',
- 'maxlength' => '255',
- 'id' => 'mw-listfiles-user',
+ 'size' => '40',
+ 'maxlength' => '255',
+ 'id' => 'mw-listfiles-user',
) );
- $s = Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' ) ) .
+ return 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;
}
function getTableClass() {
@@ -229,7 +277,7 @@ class ImageListPager extends TablePager {
function getSortHeaderClass() {
return 'listfiles_sort ' . parent::getSortHeaderClass();
}
-
+
function getPagingQueries() {
$queries = parent::getPagingQueries();
if ( !is_null( $this->mUserName ) ) {
@@ -243,9 +291,7 @@ class ImageListPager extends TablePager {
function getDefaultQuery() {
$queries = parent::getDefaultQuery();
- if ( !isset( $queries['user'] )
- && !is_null( $this->mUserName ) )
- {
+ 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 910ffd08..07e08e77 100644
--- a/includes/specials/SpecialListgrouprights.php
+++ b/includes/specials/SpecialListgrouprights.php
@@ -30,29 +30,27 @@
*/
class SpecialListGroupRights extends SpecialPage {
- var $skin;
-
/**
* Constructor
*/
function __construct() {
- global $wgUser;
parent::__construct( 'Listgrouprights' );
- $this->skin = $wgUser->getSkin();
}
/**
* Show the special page
*/
public function execute( $par ) {
- global $wgOut, $wgImplicitGroups;
+ global $wgImplicitGroups;
global $wgGroupPermissions, $wgRevokePermissions, $wgAddGroups, $wgRemoveGroups;
global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
+ $out = $this->getOutput();
$this->setHeaders();
$this->outputHeader();
+ $out->addModuleStyles( 'mediawiki.special' );
- $wgOut->addHTML(
+ $out->addHTML(
Xml::openElement( 'table', array( 'class' => 'wikitable mw-listgrouprights-table' ) ) .
'<tr>' .
Xml::element( 'th', null, wfMsg( 'listgrouprights-group' ) ) .
@@ -60,7 +58,7 @@ class SpecialListGroupRights extends SpecialPage {
'</tr>'
);
- $allGroups = array_unique( array_merge(
+ $allGroups = array_unique( array_merge(
array_keys( $wgGroupPermissions ),
array_keys( $wgRevokePermissions ),
array_keys( $wgAddGroups ),
@@ -69,34 +67,28 @@ class SpecialListGroupRights extends SpecialPage {
array_keys( $wgGroupsRemoveFromSelf )
) );
asort( $allGroups );
-
+
foreach ( $allGroups as $group ) {
- $permissions = isset( $wgGroupPermissions[$group] )
- ? $wgGroupPermissions[$group]
+ $permissions = isset( $wgGroupPermissions[$group] )
+ ? $wgGroupPermissions[$group]
: array();
$groupname = ( $group == '*' ) // Replace * with a more descriptive groupname
- ? 'all'
- : $group;
+ ? 'all'
+ : $group;
- $msg = wfMsg( 'group-' . $groupname );
- if ( wfEmptyMsg( 'group-' . $groupname, $msg ) || $msg == '' ) {
- $groupnameLocalized = $groupname;
- } else {
- $groupnameLocalized = $msg;
- }
+ $msg = wfMessage( 'group-' . $groupname );
+ $groupnameLocalized = !$msg->isBlank() ? $msg->text() : $groupname;
- $msg = wfMsgForContent( 'grouppage-' . $groupname );
- if ( wfEmptyMsg( 'grouppage-' . $groupname, $msg ) || $msg == '' ) {
- $grouppageLocalized = MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname;
- } else {
- $grouppageLocalized = $msg;
- }
+ $msg = wfMessage( 'grouppage-' . $groupname )->inContentLanguage();
+ $grouppageLocalized = !$msg->isBlank() ?
+ $msg->text() :
+ MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname;
if( $group == '*' ) {
// Do not make a link for the generic * group
$grouppage = htmlspecialchars( $groupnameLocalized );
} else {
- $grouppage = $this->skin->link(
+ $grouppage = Linker::link(
Title::newFromText( $grouppageLocalized ),
htmlspecialchars( $groupnameLocalized )
);
@@ -104,7 +96,7 @@ class SpecialListGroupRights extends SpecialPage {
if ( $group === 'user' ) {
// Link to Special:listusers for implicit group 'user'
- $grouplink = '<br />' . $this->skin->link(
+ $grouplink = '<br />' . Linker::link(
SpecialPage::getTitleFor( 'Listusers' ),
wfMsgHtml( 'listgrouprights-members' ),
array(),
@@ -112,7 +104,7 @@ class SpecialListGroupRights extends SpecialPage {
array( 'known', 'noclasses' )
);
} elseif ( !in_array( $group, $wgImplicitGroups ) ) {
- $grouplink = '<br />' . $this->skin->link(
+ $grouplink = '<br />' . Linker::link(
SpecialPage::getTitleFor( 'Listusers' ),
wfMsgHtml( 'listgrouprights-members' ),
array(),
@@ -131,7 +123,7 @@ class SpecialListGroupRights extends SpecialPage {
$removegroupsSelf = isset( $wgGroupsRemoveFromSelf[$group] ) ? $wgGroupsRemoveFromSelf[$group] : array();
$id = $group == '*' ? false : Sanitizer::escapeId( $group );
- $wgOut->addHTML( Html::rawElement( 'tr', array( 'id' => $id ),
+ $out->addHTML( Html::rawElement( 'tr', array( 'id' => $id ),
"
<td>$grouppage$grouplink</td>
<td>" .
@@ -140,10 +132,10 @@ class SpecialListGroupRights extends SpecialPage {
'
) );
}
- $wgOut->addHTML(
+ $out->addHTML(
Xml::closeElement( 'table' ) . "\n<br /><hr />\n"
);
- $wgOut->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' );
+ $out->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' );
}
/**
@@ -158,7 +150,7 @@ class SpecialListGroupRights extends SpecialPage {
* @return string List of all granted permissions, separated by comma separator
*/
private static function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
- global $wgLang;
+ global $wgLang;
$r = array();
foreach( $permissions as $permission => $granted ) {
@@ -183,25 +175,25 @@ class SpecialListGroupRights extends SpecialPage {
sort( $r );
if( $add === true ){
$r[] = wfMsgExt( 'listgrouprights-addgroup-all', array( 'escape' ) );
- } else if( is_array( $add ) && count( $add ) ) {
+ } elseif( is_array( $add ) && count( $add ) ) {
$add = array_values( array_unique( $add ) );
$r[] = wfMsgExt( 'listgrouprights-addgroup', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $add ) ), count( $add ) );
}
if( $remove === true ){
$r[] = wfMsgExt( 'listgrouprights-removegroup-all', array( 'escape' ) );
- } else if( is_array( $remove ) && count( $remove ) ) {
+ } elseif( is_array( $remove ) && count( $remove ) ) {
$remove = array_values( array_unique( $remove ) );
$r[] = wfMsgExt( 'listgrouprights-removegroup', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $remove ) ), count( $remove ) );
}
if( $addSelf === true ){
$r[] = wfMsgExt( 'listgrouprights-addgroup-self-all', array( 'escape' ) );
- } else if( is_array( $addSelf ) && count( $addSelf ) ) {
+ } elseif( is_array( $addSelf ) && count( $addSelf ) ) {
$addSelf = array_values( array_unique( $addSelf ) );
$r[] = wfMsgExt( 'listgrouprights-addgroup-self', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $addSelf ) ), count( $addSelf ) );
}
if( $removeSelf === true ){
$r[] = wfMsgExt( 'listgrouprights-removegroup-self-all', array( 'escape' ) );
- } else if( is_array( $removeSelf ) && count( $removeSelf ) ) {
+ } elseif( is_array( $removeSelf ) && count( $removeSelf ) ) {
$removeSelf = array_values( array_unique( $removeSelf ) );
$r[] = wfMsgExt( 'listgrouprights-removegroup-self', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $removeSelf ) ), count( $removeSelf ) );
}
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
index 315047da..acf5fbd9 100644
--- a/includes/specials/SpecialListredirects.php
+++ b/includes/specials/SpecialListredirects.php
@@ -30,22 +30,72 @@
*/
class ListredirectsPage extends QueryPage {
- function getName() { return( 'Listredirects' ); }
- function isExpensive() { return( true ); }
- function isSyndicated() { return( false ); }
- function sortDescending() { return( false ); }
+ function __construct( $name = 'Listredirects' ) {
+ parent::__construct( $name );
+ }
+
+ function isExpensive() { return true; }
+ function isSyndicated() { return false; }
+ function sortDescending() { return false; }
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- $page = $dbr->tableName( 'page' );
- $sql = "SELECT 'Listredirects' AS type, page_title AS title, page_namespace AS namespace,
- 0 AS value FROM $page WHERE page_is_redirect = 1";
- return( $sql );
+ function getQueryInfo() {
+ return array(
+ 'tables' => array( 'p1' => 'page', 'redirect', 'p2' => 'page' ),
+ 'fields' => array( 'p1.page_namespace AS namespace',
+ 'p1.page_title AS title',
+ 'rd_namespace',
+ 'rd_title',
+ 'rd_fragment',
+ 'rd_interwiki',
+ 'p2.page_id AS redirid' ),
+ 'conds' => array( 'p1.page_is_redirect' => 1 ),
+ 'join_conds' => array( 'redirect' => array(
+ 'LEFT JOIN', 'rd_from=p1.page_id' ),
+ 'p2' => array( 'LEFT JOIN', array(
+ 'p2.page_namespace=rd_namespace',
+ 'p2.page_title=rd_title' ) ) )
+ );
}
- function formatResult( $skin, $result ) {
- global $wgContLang;
+ function getOrderFields() {
+ return array ( 'p1.page_namespace', 'p1.page_title' );
+ }
+ /**
+ * Cache page existence for performance
+ *
+ * @param $db DatabaseBase
+ * @param $res ResultWrapper
+ */
+ function preprocessResults( $db, $res ) {
+ $batch = new LinkBatch;
+ foreach ( $res as $row ) {
+ $batch->add( $row->namespace, $row->title );
+ $batch->addObj( $this->getRedirectTarget( $row ) );
+ }
+ $batch->execute();
+
+ // Back to start for display
+ if ( $db->numRows( $res ) > 0 ) {
+ // If there are no rows we get an error seeking.
+ $db->dataSeek( $res, 0 );
+ }
+ }
+
+ protected function getRedirectTarget( $row ) {
+ if ( isset( $row->rd_title ) ) {
+ return Title::makeTitle( $row->rd_namespace,
+ $row->rd_title, $row->rd_fragment,
+ $row->rd_interwiki
+ );
+ } else {
+ $title = Title::makeTitle( $row->namespace, $row->title );
+ $article = new Article( $title );
+ return $article->getRedirectTarget();
+ }
+ }
+
+ function formatResult( $skin, $result ) {
# Make a link to the redirect itself
$rd_title = Title::makeTitle( $result->namespace, $result->title );
$rd_link = $skin->link(
@@ -56,25 +106,15 @@ class ListredirectsPage extends QueryPage {
);
# Find out where the redirect leads
- $revision = Revision::newFromTitle( $rd_title );
- if( $revision ) {
+ $target = $this->getRedirectTarget( $result );
+ if( $target ) {
+ global $wgLang;
# Make a link to the destination page
- $target = Title::newFromRedirect( $revision->getText() );
- if( $target ) {
- $arr = $wgContLang->getArrow() . $wgContLang->getDirMark();
- $targetLink = $skin->link( $target );
- return "$rd_link $arr $targetLink";
- } else {
- return "<del>$rd_link</del>";
- }
+ $arr = $wgLang->getArrow() . $wgLang->getDirMark();
+ $targetLink = $skin->link( $target );
+ return "$rd_link $arr $targetLink";
} else {
return "<del>$rd_link</del>";
}
}
}
-
-function wfSpecialListredirects() {
- list( $limit, $offset ) = wfCheckLimits();
- $lrp = new ListredirectsPage();
- $lrp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
index abc0363a..0531444a 100644
--- a/includes/specials/SpecialListusers.php
+++ b/includes/specials/SpecialListusers.php
@@ -41,7 +41,7 @@ class UsersPager extends AlphabeticPager {
if ( $parms[0] != '' && ( in_array( $par, User::getAllGroups() ) || in_array( $par, $symsForAll ) ) ) {
$this->requestedGroup = $par;
$un = $wgRequest->getText( 'username' );
- } else if ( count( $parms ) == 2 ) {
+ } elseif ( count( $parms ) == 2 ) {
$this->requestedGroup = $parms[0];
$un = $parms[1];
} else {
@@ -64,6 +64,9 @@ class UsersPager extends AlphabeticPager {
parent::__construct();
}
+ function getTitle() {
+ return SpecialPage::getTitleFor( 'Listusers' );
+ }
function getIndexField() {
return $this->creationSort ? 'user_id' : 'user_name';
@@ -74,13 +77,16 @@ class UsersPager extends AlphabeticPager {
$dbr = wfGetDB( DB_SLAVE );
$conds = array();
// Don't show hidden names
- if( !$wgUser->isAllowed('hideuser') )
+ if( !$wgUser->isAllowed('hideuser') ) {
$conds[] = 'ipb_deleted IS NULL';
+ }
+
+ $options = array();
+
if( $this->requestedGroup != '' ) {
$conds['ug_group'] = $this->requestedGroup;
- $useIndex = '';
} else {
- $useIndex = $dbr->useIndexClause( $this->creationSort ? 'PRIMARY' : 'user_name');
+ //$options['USE INDEX'] = $this->creationSort ? 'PRIMARY' : 'user_name';
}
if( $this->requestedUser != '' ) {
# Sorted either by account creation or name
@@ -94,11 +100,10 @@ class UsersPager extends AlphabeticPager {
$conds[] = 'user_editcount > 0';
}
- list ($user,$user_groups,$ipblocks) = $dbr->tableNamesN('user','user_groups','ipblocks');
+ $options['GROUP BY'] = $this->creationSort ? 'user_id' : 'user_name';
$query = array(
- 'tables' => " $user $useIndex LEFT JOIN $user_groups ON user_id=ug_user
- LEFT JOIN $ipblocks ON user_id=ipb_user AND ipb_deleted=1 AND ipb_auto=0 ",
+ 'tables' => array( 'user', 'user_groups', 'ipblocks'),
'fields' => array(
$this->creationSort ? 'MAX(user_name) AS user_name' : 'user_name',
$this->creationSort ? 'user_id' : 'MAX(user_id) AS user_id',
@@ -108,7 +113,11 @@ class UsersPager extends AlphabeticPager {
'MIN(user_registration) AS creation',
'MAX(ipb_deleted) AS ipb_deleted' // block/hide status
),
- 'options' => array('GROUP BY' => $this->creationSort ? 'user_id' : 'user_name'),
+ 'options' => $options,
+ 'join_conds' => array(
+ 'user_groups' => array( 'LEFT JOIN', 'user_id=ug_user' ),
+ 'ipblocks' => array( 'LEFT JOIN', 'user_id=ipb_user AND ipb_deleted=1 AND ipb_auto=0' ),
+ ),
'conds' => $conds
);
@@ -123,7 +132,7 @@ class UsersPager extends AlphabeticPager {
return '';
$userPage = Title::makeTitle( NS_USER, $row->user_name );
- $name = $this->getSkin()->link( $userPage, htmlspecialchars( $userPage->getText() ) );
+ $name = Linker::link( $userPage, htmlspecialchars( $userPage->getText() ) );
$groups_list = self::getGroups( $row->user_id );
if( count( $groups_list ) > 0 ) {
@@ -247,7 +256,7 @@ class UsersPager extends AlphabeticPager {
*/
protected static function getGroups( $uid ) {
$user = User::newFromId( $uid );
- $groups = array_diff( $user->getEffectiveGroups(), $user->getImplicitGroups() );
+ $groups = array_diff( $user->getEffectiveGroups(), User::getImplicitGroups() );
return $groups;
}
@@ -266,25 +275,42 @@ class UsersPager extends AlphabeticPager {
}
/**
- * constructor
- * $par string (optional) A group to list users from
+ * @ingroup SpecialPage
*/
-function wfSpecialListusers( $par = null ) {
- global $wgOut;
-
- $up = new UsersPager($par);
-
- # getBody() first to check, if empty
- $usersbody = $up->getBody();
- $s = Xml::openElement( 'div', array('class' => 'mw-spcontent') );
- $s .= $up->getPageHeader();
- if( $usersbody ) {
- $s .= $up->getNavigationBar();
- $s .= '<ul>' . $usersbody . '</ul>';
- $s .= $up->getNavigationBar() ;
- } else {
- $s .= '<p>' . wfMsgHTML('listusers-noresult') . '</p>';
- };
- $s .= Xml::closeElement( 'div' );
- $wgOut->addHTML( $s );
+class SpecialListUsers extends SpecialPage {
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct( 'Listusers' );
+ }
+
+ /**
+ * Show the special page
+ *
+ * @param $par string (optional) A group to list users from
+ */
+ public function execute( $par ) {
+ global $wgOut;
+
+ $this->setHeaders();
+ $this->outputHeader();
+
+ $up = new UsersPager( $par );
+
+ # getBody() first to check, if empty
+ $usersbody = $up->getBody();
+
+ $s = $up->getPageHeader();
+ if( $usersbody ) {
+ $s .= $up->getNavigationBar();
+ $s .= Html::rawElement( 'ul', array(), $usersbody );
+ $s .= $up->getNavigationBar();
+ } else {
+ $s .= wfMessage( 'listusers-noresult' )->parseAsBlock();
+ }
+
+ $wgOut->addHTML( $s );
+ }
}
diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php
index aad3cea4..5c861b31 100644
--- a/includes/specials/SpecialLockdb.php
+++ b/includes/specials/SpecialLockdb.php
@@ -34,12 +34,13 @@ class SpecialLockdb extends SpecialPage {
}
public function execute( $par ) {
- global $wgUser, $wgOut, $wgRequest;
+ global $wgUser, $wgRequest;
$this->setHeaders();
- if( !$wgUser->isAllowed( 'siteadmin' ) ) {
- $wgOut->permissionRequired( 'siteadmin' );
+ # Permission check
+ if( !$this->userCanExecute( $wgUser ) ) {
+ $this->displayRestrictionError();
return;
}
@@ -57,7 +58,7 @@ class SpecialLockdb extends SpecialPage {
if ( $action == 'success' ) {
$this->showSuccess();
- } else if ( $action == 'submit' && $wgRequest->wasPosted() &&
+ } elseif ( $action == 'submit' && $wgRequest->wasPosted() &&
$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
$this->doSubmit();
} else {
@@ -109,7 +110,10 @@ class SpecialLockdb extends SpecialPage {
$this->showForm( wfMsg( 'locknoconfirm' ) );
return;
}
- $fp = @fopen( $wgReadOnlyFile, 'w' );
+
+ wfSuppressWarnings();
+ $fp = fopen( $wgReadOnlyFile, 'w' );
+ wfRestoreWarnings();
if ( false === $fp ) {
# This used to show a file not found error, but the likeliest reason for fopen()
@@ -119,8 +123,14 @@ class SpecialLockdb extends SpecialPage {
return;
}
fwrite( $fp, $this->reason );
- fwrite( $fp, "\n<p>(by " . $wgUser->getName() . " at " .
- $wgContLang->timeanddate( wfTimestampNow() ) . ")</p>\n" );
+ $timestamp = wfTimestampNow();
+ fwrite( $fp, "\n<p>" . wfMsgExt(
+ 'lockedbyandtime',
+ array( 'content', 'parsemag' ),
+ $wgUser->getName(),
+ $wgContLang->date( $timestamp ),
+ $wgContLang->time( $timestamp )
+ ) . "</p>\n" );
fclose( $fp );
$wgOut->redirect( $this->getTitle()->getFullURL( 'action=success' ) );
diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php
index a2af8de5..d8f6d8cf 100644
--- a/includes/specials/SpecialLog.php
+++ b/includes/specials/SpecialLog.php
@@ -95,10 +95,10 @@ class SpecialLog extends SpecialPage {
}
private function show( FormOptions $opts, array $extraConds ) {
- global $wgOut, $wgUser;
+ global $wgOut;
# Create a LogPager item to get the results and a LogEventsList item to format them...
- $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
+ $loglist = new LogEventsList( $this->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' ) );
@@ -106,6 +106,11 @@ class SpecialLog extends SpecialPage {
# Set title and add header
$loglist->showHeader( $pager->getType() );
+ # Set relevant user
+ if ( $pager->getUser() ) {
+ $this->getSkin()->setRelevantUser( User::newFromName( $pager->getUser() ) );
+ }
+
# Show form options
$loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(),
$pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $opts->getValue( 'tagfilter' ) );
diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php
index 0788037f..0800e43c 100644
--- a/includes/specials/SpecialLonelypages.php
+++ b/includes/specials/SpecialLonelypages.php
@@ -29,9 +29,10 @@
*/
class LonelyPagesPage extends PageQueryPage {
- function getName() {
- return "Lonelypages";
+ function __construct( $name = 'Lonelypages' ) {
+ parent::__construct( $name );
}
+
function getPageHeader() {
return wfMsgExt( 'lonelypagestext', array( 'parse' ) );
}
@@ -45,35 +46,36 @@ class LonelyPagesPage extends PageQueryPage {
}
function isSyndicated() { return false; }
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $page, $pagelinks, $templatelinks ) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' );
-
- return
- "SELECT 'Lonelypages' AS type,
- page_namespace AS namespace,
- page_title AS title,
- page_title AS value
- FROM $page
- LEFT JOIN $pagelinks
- ON page_namespace=pl_namespace AND page_title=pl_title
- LEFT JOIN $templatelinks
- ON page_namespace=tl_namespace AND page_title=tl_title
- WHERE pl_namespace IS NULL
- AND page_namespace=".NS_MAIN."
- AND page_is_redirect=0
- AND tl_namespace IS NULL";
-
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'page', 'pagelinks',
+ 'templatelinks' ),
+ 'fields' => array ( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'page_title AS value' ),
+ 'conds' => array ( 'pl_namespace IS NULL',
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0,
+ 'tl_namespace IS NULL' ),
+ 'join_conds' => array (
+ 'pagelinks' => array (
+ 'LEFT JOIN', array (
+ 'pl_namespace = page_namespace',
+ 'pl_title = page_title' ) ),
+ 'templatelinks' => array (
+ 'LEFT JOIN', array (
+ 'tl_namespace = page_namespace',
+ 'tl_title = page_title' ) ) )
+ );
}
-}
-/**
- * Constructor
- */
-function wfSpecialLonelypages() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $lpp = new LonelyPagesPage();
-
- return $lpp->doQuery( $offset, $limit );
+ function getOrderFields() {
+ // For some crazy reason ordering by a constant
+ // causes a filesort in MySQL 5
+ if( count( MWNamespace::getContentNamespaces() ) > 1 ) {
+ return array( 'page_namespace', 'page_title' );
+ } else {
+ return array( 'page_title' );
+ }
+ }
}
diff --git a/includes/specials/SpecialLongpages.php b/includes/specials/SpecialLongpages.php
index cd0f3090..dd60e37d 100644
--- a/includes/specials/SpecialLongpages.php
+++ b/includes/specials/SpecialLongpages.php
@@ -27,22 +27,11 @@
*/
class LongPagesPage extends ShortPagesPage {
- function getName() {
- return "Longpages";
+ function __construct( $name = 'Longpages' ) {
+ parent::__construct( $name );
}
function sortDescending() {
return true;
}
}
-
-/**
- * constructor
- */
-function wfSpecialLongpages() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $lpp = new LongPagesPage();
-
- $lpp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php
index 79683a35..aefe7bf5 100644
--- a/includes/specials/SpecialMIMEsearch.php
+++ b/includes/specials/SpecialMIMEsearch.php
@@ -28,50 +28,62 @@
* @ingroup SpecialPage
*/
class MIMEsearchPage extends QueryPage {
- var $major, $minor;
+ protected $major, $minor;
- function __construct( $major, $minor ) {
- $this->major = $major;
- $this->minor = $minor;
+ function __construct( $name = 'MIMEsearch' ) {
+ parent::__construct( $name );
}
- function getName() { return 'MIMEsearch'; }
-
- /**
- * Due to this page relying upon extra fields being passed in the SELECT it
- * will fail if it's set as expensive and misermode is on
- */
function isExpensive() { return true; }
function isSyndicated() { return false; }
+ function isCacheable() { return false; }
function linkParameters() {
- $arr = array( $this->major, $this->minor );
- $mime = implode( '/', $arr );
- return array( 'mime' => $mime );
+ return array( 'mime' => "{$this->major}/{$this->minor}" );
}
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- $image = $dbr->tableName( 'image' );
- $major = $dbr->addQuotes( $this->major );
- $minor = $dbr->addQuotes( $this->minor );
+ public function getQueryInfo() {
+ return array(
+ 'tables' => array( 'image' ),
+ 'fields' => array( "'" . NS_FILE . "' AS namespace",
+ 'img_name AS title',
+ 'img_major_mime AS value',
+ 'img_size',
+ 'img_width',
+ 'img_height',
+ 'img_user_text',
+ 'img_timestamp' ),
+ 'conds' => array( 'img_major_mime' => $this->major,
+ 'img_minor_mime' => $this->minor )
+ );
+ }
- return
- "SELECT 'MIMEsearch' AS type,
- " . NS_FILE . " AS namespace,
- img_name AS title,
- img_major_mime AS value,
+ function execute( $par ) {
+ global $wgRequest, $wgOut;
+ $mime = $par ? $par : $wgRequest->getText( 'mime' );
+
+ $this->setHeaders();
+ $this->outputHeader();
+ $wgOut->addHTML(
+ Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => SpecialPage::getTitleFor( 'MIMEsearch' )->getLocalUrl() ) ) .
+ Xml::openElement( 'fieldset' ) .
+ 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' ) ) .
+ Xml::closeElement( 'fieldset' ) .
+ Xml::closeElement( 'form' )
+ );
- img_size,
- img_width,
- img_height,
- img_user_text,
- img_timestamp
- FROM $image
- WHERE img_major_mime = $major AND img_minor_mime = $minor
- ";
+ list( $this->major, $this->minor ) = File::splitMime( $mime );
+ if ( $this->major == '' || $this->minor == '' || $this->minor == 'unknown' ||
+ !self::isValidType( $this->major ) ) {
+ return;
+ }
+ parent::execute( $par );
}
+
function formatResult( $skin, $result ) {
global $wgContLang, $wgLang;
@@ -83,7 +95,7 @@ class MIMEsearchPage extends QueryPage {
);
$download = $skin->makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) );
- $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
+ $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
$wgLang->formatNum( $result->img_size ) );
$dimensions = htmlspecialchars( wfMsg( 'widthheight',
$wgLang->formatNum( $result->img_width ),
@@ -94,63 +106,24 @@ class MIMEsearchPage extends QueryPage {
return "($download) $plink . . $dimensions . . $bytes . . $user . . $time";
}
-}
-
-/**
- * Output the HTML search form, and constructs the MIMEsearchPage object.
- */
-function wfSpecialMIMEsearch( $par = null ) {
- global $wgRequest, $wgOut;
-
- $mime = isset( $par ) ? $par : $wgRequest->getText( 'mime' );
- $wgOut->addHTML(
- Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => SpecialPage::getTitleFor( 'MIMEsearch' )->getLocalUrl() ) ) .
- Xml::openElement( 'fieldset' ) .
- 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' ) ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
- );
-
- list( $major, $minor ) = wfSpecialMIMEsearchParse( $mime );
- if ( $major == '' or $minor == '' or !wfSpecialMIMEsearchValidType( $major ) )
- return;
- $wpp = new MIMEsearchPage( $major, $minor );
-
- list( $limit, $offset ) = wfCheckLimits();
- $wpp->doQuery( $offset, $limit );
-}
-
-function wfSpecialMIMEsearchParse( $str ) {
- // searched for an invalid MIME type.
- if( strpos( $str, '/' ) === false) {
- return array ('', '');
+ /**
+ * @param $type string
+ * @return bool
+ */
+ protected static function isValidType( $type ) {
+ // From maintenance/tables.sql => img_major_mime
+ $types = array(
+ 'unknown',
+ 'application',
+ 'audio',
+ 'image',
+ 'text',
+ 'video',
+ 'message',
+ 'model',
+ 'multipart'
+ );
+ return in_array( $type, $types );
}
-
- list( $major, $minor ) = explode( '/', $str, 2 );
-
- return array(
- ltrim( $major, ' ' ),
- rtrim( $minor, ' ' )
- );
-}
-
-function wfSpecialMIMEsearchValidType( $type ) {
- // From maintenance/tables.sql => img_major_mime
- $types = array(
- 'unknown',
- 'application',
- 'audio',
- 'image',
- 'text',
- 'video',
- 'message',
- 'model',
- 'multipart'
- );
-
- return in_array( $type, $types );
}
diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php
index 43b4ef6a..88e90ee5 100644
--- a/includes/specials/SpecialMergeHistory.php
+++ b/includes/specials/SpecialMergeHistory.php
@@ -29,14 +29,23 @@
*/
class SpecialMergeHistory extends SpecialPage {
var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment;
+
+ /**
+ * @var Title
+ */
var $mTargetObj, $mDestObj;
public function __construct() {
parent::__construct( 'MergeHistory', 'mergehistory' );
}
+ /**
+ * @param $request WebRequest
+ * @return void
+ */
private function loadRequestParams( $request ) {
global $wgUser;
+
$this->mAction = $request->getVal( 'action' );
$this->mTarget = $request->getVal( 'target' );
$this->mDest = $request->getVal( 'dest' );
@@ -45,7 +54,7 @@ class SpecialMergeHistory extends SpecialPage {
$this->mTargetID = intval( $request->getVal( 'targetID' ) );
$this->mDestID = intval( $request->getVal( 'destID' ) );
$this->mTimestamp = $request->getVal( 'mergepoint' );
- if( !preg_match("/[0-9]{14}/",$this->mTimestamp) ) {
+ if( !preg_match( '/[0-9]{14}/', $this->mTimestamp ) ) {
$this->mTimestamp = '';
}
$this->mComment = $request->getText( 'wpComment' );
@@ -73,7 +82,7 @@ class SpecialMergeHistory extends SpecialPage {
}
}
- function execute( $par ) {
+ public function execute( $par ) {
global $wgOut, $wgRequest, $wgUser;
if ( wfReadOnly() ) {
@@ -91,7 +100,7 @@ class SpecialMergeHistory extends SpecialPage {
$this->setHeaders();
$this->outputHeader();
- if( $this->mTargetID && $this->mDestID && $this->mAction=="submit" && $this->mMerge ) {
+ if( $this->mTargetID && $this->mDestID && $this->mAction == 'submit' && $this->mMerge ) {
return $this->merge();
}
@@ -109,14 +118,14 @@ class SpecialMergeHistory extends SpecialPage {
);
}
- if ( !$this->mDestObj instanceof Title) {
+ if ( !$this->mDestObj instanceof Title ) {
$errors[] = wfMsgExt( 'mergehistory-invalid-destination', array( 'parse' ) );
} elseif( !$this->mDestObj->exists() ) {
$errors[] = wfMsgExt( 'mergehistory-no-destination', array( 'parse' ),
wfEscapeWikiText( $this->mDestObj->getPrefixedText() )
);
}
-
+
if ( $this->mTargetObj && $this->mDestObj && $this->mTargetObj->equals( $this->mDestObj ) ) {
$errors[] = wfMsgExt( 'mergehistory-same-destination', array( 'parse' ) );
}
@@ -146,37 +155,47 @@ class SpecialMergeHistory extends SpecialPage {
Html::hidden( 'submitted', '1' ) .
Html::hidden( 'mergepoint', $this->mTimestamp ) .
Xml::openElement( 'table' ) .
- "<tr>
- <td>".Xml::label( wfMsg( 'mergehistory-from' ), 'target' )."</td>
- <td>".Xml::input( 'target', 30, $this->mTarget, array('id'=>'target') )."</td>
+ '<tr>
+ <td>' . Xml::label( wfMsg( 'mergehistory-from' ), 'target' ) . '</td>
+ <td>' . Xml::input( 'target', 30, $this->mTarget, array( 'id' => 'target' ) ) . '</td>
</tr><tr>
- <td>".Xml::label( wfMsg( 'mergehistory-into' ), 'dest' )."</td>
- <td>".Xml::input( 'dest', 30, $this->mDest, array('id'=>'dest') )."</td>
- </tr><tr><td>" .
+ <td>' . Xml::label( wfMsg( 'mergehistory-into' ), 'dest' ) . '</td>
+ <td>' . Xml::input( 'dest', 30, $this->mDest, array( 'id' => 'dest' ) ) . '</td>
+ </tr><tr><td>' .
Xml::submitButton( wfMsg( 'mergehistory-go' ) ) .
- "</td></tr>" .
+ '</td></tr>' .
Xml::closeElement( 'table' ) .
'</fieldset>' .
- '</form>' );
+ '</form>'
+ );
}
private function showHistory() {
global $wgUser, $wgOut;
- $this->sk = $wgUser->getSkin();
+ $this->sk = $this->getSkin();
- $wgOut->setPagetitle( wfMsg( "mergehistory" ) );
+ $wgOut->setPageTitle( wfMsg( 'mergehistory' ) );
$this->showMergeForm();
# List all stored revisions
- $revisions = new MergeHistoryPager( $this, array(), $this->mTargetObj, $this->mDestObj );
+ $revisions = new MergeHistoryPager(
+ $this, array(), $this->mTargetObj, $this->mDestObj
+ );
$haveRevisions = $revisions && $revisions->getNumRows() > 0;
$titleObj = $this->getTitle();
$action = $titleObj->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
- $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) );
+ $top = Xml::openElement(
+ 'form',
+ array(
+ 'method' => 'post',
+ 'action' => $action,
+ 'id' => 'merge'
+ )
+ );
$wgOut->addHTML( $top );
if( $haveRevisions ) {
@@ -184,43 +203,46 @@ class SpecialMergeHistory extends SpecialPage {
# in a nice little table
$table =
Xml::openElement( 'fieldset' ) .
- wfMsgExt( 'mergehistory-merge', array('parseinline'),
+ wfMsgExt( 'mergehistory-merge', array( 'parseinline' ),
$this->mTargetObj->getPrefixedText(), $this->mDestObj->getPrefixedText() ) .
Xml::openElement( 'table', array( 'id' => 'mw-mergehistory-table' ) ) .
- "<tr>
- <td class='mw-label'>" .
+ '<tr>
+ <td class="mw-label">' .
Xml::label( wfMsg( 'mergehistory-reason' ), 'wpComment' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'wpComment', 50, $this->mComment, array('id' => 'wpComment') ) .
- "</td>
+ '</td>
+ <td class="mw-input">' .
+ Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) .
+ '</td>
</tr>
<tr>
<td>&#160;</td>
- <td class='mw-submit'>" .
+ <td class="mw-submit">' .
Xml::submitButton( wfMsg( 'mergehistory-submit' ), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) .
- "</td>
- </tr>" .
+ '</td>
+ </tr>' .
Xml::closeElement( 'table' ) .
Xml::closeElement( 'fieldset' );
$wgOut->addHTML( $table );
}
- $wgOut->addHTML( "<h2 id=\"mw-mergehistory\">" . wfMsgHtml( "mergehistory-list" ) . "</h2>\n" );
+ $wgOut->addHTML(
+ '<h2 id="mw-mergehistory">' .
+ wfMsgHtml( 'mergehistory-list' ) . "</h2>\n"
+ );
if( $haveRevisions ) {
$wgOut->addHTML( $revisions->getNavigationBar() );
- $wgOut->addHTML( "<ul>" );
+ $wgOut->addHTML( '<ul>' );
$wgOut->addHTML( $revisions->getBody() );
- $wgOut->addHTML( "</ul>" );
+ $wgOut->addHTML( '</ul>' );
$wgOut->addHTML( $revisions->getNavigationBar() );
} else {
- $wgOut->addWikiMsg( "mergehistory-empty" );
+ $wgOut->addWikiMsg( 'mergehistory-empty' );
}
# Show relevant lines from the deletion log:
- $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'merge' ) ) . "</h2>\n" );
+ $wgOut->addHTML( '<h2>' . htmlspecialchars( LogPage::logName( 'merge' ) ) . "</h2>\n" );
LogEventsList::showLogExtract( $wgOut, 'merge', $this->mTargetObj->getPrefixedText() );
# When we submit, go by page ID to avoid some nasty but unlikely collisions.
@@ -245,7 +267,7 @@ class SpecialMergeHistory extends SpecialPage {
$last = $this->message['last'];
$ts = wfTimestamp( TS_MW, $row->rev_timestamp );
- $checkBox = Xml::radio( "mergepoint", $ts, false );
+ $checkBox = Xml::radio( 'mergepoint', $ts, false );
$pageLink = $this->sk->linkKnown(
$rev->getTitle(),
@@ -258,9 +280,9 @@ class SpecialMergeHistory extends SpecialPage {
}
# Last link
- if( !$rev->userCan( Revision::DELETED_TEXT ) )
+ if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
$last = $this->message['last'];
- else if( isset($this->prevId[$row->rev_id]) )
+ } elseif( isset( $this->prevId[$row->rev_id] ) ) {
$last = $this->sk->linkKnown(
$rev->getTitle(),
$this->message['last'],
@@ -270,10 +292,12 @@ class SpecialMergeHistory extends SpecialPage {
'oldid' => $this->prevId[$row->rev_id]
)
);
+ }
$userLink = $this->sk->revUserTools( $rev );
- if(!is_null($size = $row->rev_len)) {
+ $size = $row->rev_len;
+ if( !is_null( $size ) ) {
$stxt = $this->sk->formatRevisionSize( $size );
}
$comment = $this->sk->revComment( $rev );
@@ -288,8 +312,9 @@ class SpecialMergeHistory extends SpecialPage {
function getPageLink( $row, $titleObj, $ts, $target ) {
global $wgLang;
- if( !$this->userCan($row, Revision::DELETED_TEXT) ) {
- return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';
+ if( !$this->userCan( $row, Revision::DELETED_TEXT ) ) {
+ return '<span class="history-deleted">' .
+ $wgLang->timeanddate( $ts, true ) . '</span>';
} else {
$link = $this->sk->linkKnown(
$titleObj,
@@ -300,8 +325,9 @@ class SpecialMergeHistory extends SpecialPage {
'timestamp' => $ts
)
);
- if( $this->isDeleted($row, Revision::DELETED_TEXT) )
+ if( $this->isDeleted( $row, Revision::DELETED_TEXT ) ) {
$link = '<span class="history-deleted">' . $link . '</span>';
+ }
return $link;
}
}
@@ -313,63 +339,80 @@ class SpecialMergeHistory extends SpecialPage {
# keep it consistent...
$targetTitle = Title::newFromID( $this->mTargetID );
$destTitle = Title::newFromID( $this->mDestID );
- if( is_null($targetTitle) || is_null($destTitle) )
+ if( is_null( $targetTitle ) || is_null( $destTitle ) ) {
return false; // validate these
- if( $targetTitle->getArticleId() == $destTitle->getArticleId() )
+ }
+ if( $targetTitle->getArticleId() == $destTitle->getArticleId() ) {
return false;
+ }
# Verify that this timestamp is valid
# Must be older than the destination page
$dbw = wfGetDB( DB_MASTER );
# Get timestamp into DB format
- $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp($this->mTimestamp) : '';
+ $this->mTimestamp = $this->mTimestamp ? $dbw->timestamp( $this->mTimestamp ) : '';
# Max timestamp should be min of destination page
- $maxtimestamp = $dbw->selectField( 'revision', 'MIN(rev_timestamp)',
- array('rev_page' => $this->mDestID ),
- __METHOD__ );
+ $maxtimestamp = $dbw->selectField(
+ 'revision',
+ 'MIN(rev_timestamp)',
+ array( 'rev_page' => $this->mDestID ),
+ __METHOD__
+ );
# Destination page must exist with revisions
if( !$maxtimestamp ) {
- $wgOut->addWikiMsg('mergehistory-fail');
+ $wgOut->addWikiMsg( 'mergehistory-fail' );
return false;
}
# Get the latest timestamp of the source
- $lasttimestamp = $dbw->selectField( array('page','revision'),
+ $lasttimestamp = $dbw->selectField(
+ array( 'page', 'revision' ),
'rev_timestamp',
- array('page_id' => $this->mTargetID, 'page_latest = rev_id' ),
- __METHOD__ );
+ array( 'page_id' => $this->mTargetID, 'page_latest = rev_id' ),
+ __METHOD__
+ );
# $this->mTimestamp must be older than $maxtimestamp
if( $this->mTimestamp >= $maxtimestamp ) {
- $wgOut->addWikiMsg('mergehistory-fail');
+ $wgOut->addWikiMsg( 'mergehistory-fail' );
return false;
}
# Update the revisions
if( $this->mTimestamp ) {
$timewhere = "rev_timestamp <= {$this->mTimestamp}";
- $TimestampLimit = wfTimestamp(TS_MW,$this->mTimestamp);
+ $timestampLimit = wfTimestamp( TS_MW, $this->mTimestamp );
} else {
$timewhere = "rev_timestamp <= {$maxtimestamp}";
- $TimestampLimit = wfTimestamp(TS_MW,$lasttimestamp);
+ $timestampLimit = wfTimestamp( TS_MW, $lasttimestamp );
}
# Do the moving...
- $dbw->update( 'revision',
+ $dbw->update(
+ 'revision',
array( 'rev_page' => $this->mDestID ),
- array( 'rev_page' => $this->mTargetID,
- $timewhere ),
- __METHOD__ );
+ array( 'rev_page' => $this->mTargetID, $timewhere ),
+ __METHOD__
+ );
$count = $dbw->affectedRows();
# Make the source page a redirect if no revisions are left
- $haveRevisions = $dbw->selectField( 'revision',
+ $haveRevisions = $dbw->selectField(
+ 'revision',
'rev_timestamp',
array( 'rev_page' => $this->mTargetID ),
__METHOD__,
- array( 'FOR UPDATE' ) );
+ array( 'FOR UPDATE' )
+ );
if( !$haveRevisions ) {
if( $this->mComment ) {
- $comment = wfMsgForContent( 'mergehistory-comment', $targetTitle->getPrefixedText(),
- $destTitle->getPrefixedText(), $this->mComment );
+ $comment = wfMsgForContent(
+ 'mergehistory-comment',
+ $targetTitle->getPrefixedText(),
+ $destTitle->getPrefixedText(),
+ $this->mComment
+ );
} else {
- $comment = wfMsgForContent( 'mergehistory-autocomment', $targetTitle->getPrefixedText(),
- $destTitle->getPrefixedText() );
+ $comment = wfMsgForContent(
+ 'mergehistory-autocomment',
+ $targetTitle->getPrefixedText(),
+ $destTitle->getPrefixedText()
+ );
}
$mwRedir = MagicWord::get( 'redirect' );
$redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destTitle->getPrefixedText() . "]]\n";
@@ -389,22 +432,26 @@ class SpecialMergeHistory extends SpecialPage {
'pl_from' => $this->mDestID,
'pl_namespace' => $destTitle->getNamespace(),
'pl_title' => $destTitle->getDBkey() ),
- __METHOD__ );
+ __METHOD__
+ );
} else {
$targetTitle->invalidateCache(); // update histories
}
$destTitle->invalidateCache(); // update histories
# Check if this did anything
if( !$count ) {
- $wgOut->addWikiMsg('mergehistory-fail');
+ $wgOut->addWikiMsg( 'mergehistory-fail' );
return false;
}
# Update our logs
$log = new LogPage( 'merge' );
- $log->addEntry( 'merge', $targetTitle, $this->mComment,
- array($destTitle->getPrefixedText(),$TimestampLimit) );
+ $log->addEntry(
+ 'merge', $targetTitle, $this->mComment,
+ array( $destTitle->getPrefixedText(), $timestampLimit )
+ );
- $wgOut->addHTML( wfMsgExt( 'mergehistory-success', array('parseinline'),
+ $wgOut->addHTML(
+ wfMsgExt( 'mergehistory-success', array('parseinline'),
$targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count ) );
wfRunHooks( 'ArticleMergeComplete', array( $targetTitle, $destTitle ) );
@@ -423,14 +470,21 @@ class MergeHistoryPager extends ReverseChronologicalPager {
$this->articleID = $source->getArticleID();
$dbr = wfGetDB( DB_SLAVE );
- $maxtimestamp = $dbr->selectField( 'revision', 'MIN(rev_timestamp)',
- array('rev_page' => $dest->getArticleID() ),
- __METHOD__ );
+ $maxtimestamp = $dbr->selectField(
+ 'revision',
+ 'MIN(rev_timestamp)',
+ array( 'rev_page' => $dest->getArticleID() ),
+ __METHOD__
+ );
$this->maxTimestamp = $maxtimestamp;
parent::__construct();
}
+ function getTitle() {
+ return SpecialPage::getTitleFor( 'Contributions' );
+ }
+
function getStartBody() {
wfProfileIn( __METHOD__ );
# Do a link batch query
@@ -442,11 +496,12 @@ class MergeHistoryPager extends ReverseChronologicalPager {
$batch->addObj( Title::makeTitleSafe( NS_USER, $row->rev_user_text ) );
$batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->rev_user_text ) );
- $rev_id = isset($rev_id) ? $rev_id : $row->rev_id;
- if( $rev_id > $row->rev_id )
+ $rev_id = isset( $rev_id ) ? $rev_id : $row->rev_id;
+ if( $rev_id > $row->rev_id ) {
$this->mForm->prevId[$rev_id] = $row->rev_id;
- else if( $rev_id < $row->rev_id )
+ } elseif( $rev_id < $row->rev_id ) {
$this->mForm->prevId[$row->rev_id] = $rev_id;
+ }
$rev_id = $row->rev_id;
}
@@ -468,9 +523,12 @@ class MergeHistoryPager extends ReverseChronologicalPager {
$conds[] = 'page_id = rev_page';
$conds[] = "rev_timestamp < {$this->maxTimestamp}";
return array(
- 'tables' => array('revision','page'),
- 'fields' => array( 'rev_minor_edit', 'rev_timestamp', 'rev_user', 'rev_user_text', 'rev_comment',
- 'rev_id', 'rev_page', 'rev_parent_id', 'rev_text_id', 'rev_len', 'rev_deleted' ),
+ 'tables' => array( 'revision', 'page' ),
+ 'fields' => array(
+ 'rev_minor_edit', 'rev_timestamp', 'rev_user', 'rev_user_text',
+ 'rev_comment', 'rev_id', 'rev_page', 'rev_parent_id',
+ 'rev_text_id', 'rev_len', 'rev_deleted'
+ ),
'conds' => $conds
);
}
diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php
index 124f0bd5..2e437196 100644
--- a/includes/specials/SpecialMostcategories.php
+++ b/includes/specials/SpecialMostcategories.php
@@ -31,28 +31,32 @@
*/
class MostcategoriesPage extends QueryPage {
- function getName() { return 'Mostcategories'; }
+ function __construct( $name = 'Mostcategories' ) {
+ parent::__construct( $name );
+ }
+
function isExpensive() { return true; }
function isSyndicated() { return false; }
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $categorylinks, $page) = $dbr->tableNamesN( 'categorylinks', 'page' );
- return
- "
- SELECT
- 'Mostcategories' as type,
- page_namespace as namespace,
- page_title as title,
- COUNT(*) as value
- FROM $categorylinks
- LEFT JOIN $page ON cl_from = page_id
- WHERE page_namespace = " . NS_MAIN . "
- GROUP BY page_namespace, page_title
- HAVING COUNT(*) > 1
- ";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'categorylinks', 'page' ),
+ 'fields' => array ( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'COUNT(*) AS value' ),
+ 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces() ),
+ 'options' => array ( 'HAVING' => 'COUNT(*) > 1',
+ 'GROUP BY' => 'page_namespace, page_title' ),
+ 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
+ 'page_id = cl_from' ) )
+ );
}
+ /**
+ * @param $skin Skin
+ * @param $result
+ * @return string
+ */
function formatResult( $skin, $result ) {
global $wgLang;
$title = Title::makeTitleSafe( $result->namespace, $result->title );
@@ -62,14 +66,3 @@ class MostcategoriesPage extends QueryPage {
return wfSpecialList( $link, $count );
}
}
-
-/**
- * constructor
- */
-function wfSpecialMostcategories() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $wpp = new MostcategoriesPage();
-
- $wpp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php
index 411a281b..ac2b5206 100644
--- a/includes/specials/SpecialMostimages.php
+++ b/includes/specials/SpecialMostimages.php
@@ -31,24 +31,22 @@
*/
class MostimagesPage extends ImageQueryPage {
- function getName() { return 'Mostimages'; }
+ function __construct( $name = 'Mostimages' ) {
+ parent::__construct( $name );
+ }
+
function isExpensive() { return true; }
function isSyndicated() { return false; }
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- $imagelinks = $dbr->tableName( 'imagelinks' );
- return
- "
- SELECT
- 'Mostimages' as type,
- " . NS_FILE . " as namespace,
- il_to as title,
- COUNT(*) as value
- FROM $imagelinks
- GROUP BY il_to
- HAVING COUNT(*) > 1
- ";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'imagelinks' ),
+ 'fields' => array ( "'" . NS_FILE . "' AS namespace",
+ 'il_to AS title',
+ 'COUNT(*) AS value' ),
+ 'options' => array ( 'GROUP BY' => 'il_to',
+ 'HAVING' => 'COUNT(*) > 1' )
+ );
}
function getCellHtml( $row ) {
@@ -58,14 +56,3 @@ class MostimagesPage extends ImageQueryPage {
}
}
-
-/**
- * Constructor
- */
-function wfSpecialMostimages() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $wpp = new MostimagesPage();
-
- $wpp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
index c731588a..58f686e8 100644
--- a/includes/specials/SpecialMostlinked.php
+++ b/includes/specials/SpecialMostlinked.php
@@ -32,43 +32,34 @@
*/
class MostlinkedPage extends QueryPage {
- function getName() { return 'Mostlinked'; }
+ function __construct( $name = 'Mostlinked' ) {
+ parent::__construct( $name );
+ }
+
function isExpensive() { return true; }
function isSyndicated() { return false; }
- function getSQL() {
- global $wgMiserMode;
-
- $dbr = wfGetDB( DB_SLAVE );
-
- # In miser mode, reduce the query cost by adding a threshold for large wikis
- if ( $wgMiserMode ) {
- $numPages = SiteStats::pages();
- if ( $numPages > 10000 ) {
- $cutoff = 100;
- } elseif ( $numPages > 100 ) {
- $cutoff = intval( sqrt( $numPages ) );
- } else {
- $cutoff = 1;
- }
- } else {
- $cutoff = 1;
- }
-
- list( $pagelinks, $page ) = $dbr->tableNamesN( 'pagelinks', 'page' );
- return
- "SELECT 'Mostlinked' AS type,
- pl_namespace AS namespace,
- pl_title AS title,
- COUNT(*) AS value
- FROM $pagelinks
- LEFT JOIN $page ON pl_namespace=page_namespace AND pl_title=page_title
- GROUP BY pl_namespace, pl_title
- HAVING COUNT(*) > $cutoff";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'pagelinks', 'page' ),
+ 'fields' => array ( 'pl_namespace AS namespace',
+ 'pl_title AS title',
+ 'COUNT(*) AS value',
+ 'page_namespace' ),
+ 'options' => array ( 'HAVING' => 'COUNT(*) > 1',
+ 'GROUP BY' => 'pl_namespace, pl_title, '.
+ 'page_namespace' ),
+ 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
+ array ( 'page_namespace = pl_namespace',
+ 'page_title = pl_title' ) ) )
+ );
}
/**
* Pre-fill the link cache
+ *
+ * @param $db DatabaseBase
+ * @param $res
*/
function preprocessResults( $db, $res ) {
if( $db->numRows( $res ) > 0 ) {
@@ -114,14 +105,3 @@ class MostlinkedPage extends QueryPage {
return wfSpecialList( $link, $wlh );
}
}
-
-/**
- * constructor
- */
-function wfSpecialMostlinked() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $wpp = new MostlinkedPage();
-
- $wpp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
index e1fc1d95..195282f7 100644
--- a/includes/specials/SpecialMostlinkedcategories.php
+++ b/includes/specials/SpecialMostlinkedcategories.php
@@ -31,65 +31,60 @@
*/
class MostlinkedCategoriesPage extends QueryPage {
- function getName() { return 'Mostlinkedcategories'; }
+ function __construct( $name = 'Mostlinkedcategories' ) {
+ parent::__construct( $name );
+ }
+
function isExpensive() { return true; }
function isSyndicated() { return false; }
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- $categorylinks = $dbr->tableName( 'categorylinks' );
- $name = $dbr->addQuotes( $this->getName() );
- return
- "
- SELECT
- $name as type,
- " . NS_CATEGORY . " as namespace,
- cl_to as title,
- COUNT(*) as value
- FROM $categorylinks
- GROUP BY cl_to
- ";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'categorylinks' ),
+ 'fields' => array ( 'cl_to AS title',
+ NS_CATEGORY . ' AS namespace',
+ 'COUNT(*) AS value' ),
+ 'options' => array ( 'GROUP BY' => 'cl_to' )
+ );
}
function sortDescending() { return true; }
/**
* Fetch user page links and cache their existence
+ *
+ * @param $db DatabaseBase
+ * @param $res DatabaseResult
*/
function preprocessResults( $db, $res ) {
$batch = new LinkBatch;
foreach ( $res as $row ) {
- $batch->add( $row->namespace, $row->title );
+ $batch->add( NS_CATEGORY, $row->title );
}
$batch->execute();
// Back to start for display
- if ( $db->numRows( $res ) > 0 )
+ if ( $db->numRows( $res ) > 0 ) {
// If there are no rows we get an error seeking.
$db->dataSeek( $res, 0 );
+ }
}
+ /**
+ * @param $skin Skin
+ * @param $result
+ * @return string
+ */
function formatResult( $skin, $result ) {
global $wgLang, $wgContLang;
- $nt = Title::makeTitle( $result->namespace, $result->title );
+ $nt = Title::makeTitle( NS_CATEGORY, $result->title );
$text = $wgContLang->convert( $nt->getText() );
$plink = $skin->link( $nt, htmlspecialchars( $text ) );
- $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'),
+ $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ),
$wgLang->formatNum( $result->value ) );
- return wfSpecialList($plink, $nlinks);
+ return wfSpecialList( $plink, $nlinks );
}
}
-
-/**
- * constructor
- */
-function wfSpecialMostlinkedCategories() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $wpp = new MostlinkedCategoriesPage();
-
- $wpp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php
index 822d6bc9..69771925 100644
--- a/includes/specials/SpecialMostlinkedtemplates.php
+++ b/includes/specials/SpecialMostlinkedtemplates.php
@@ -21,22 +21,17 @@
* @ingroup SpecialPage
* @author Rob Church <robchur@gmail.com>
*/
-
+
/**
* Special page lists templates with a large number of
* transclusion links, i.e. "most used" templates
*
* @ingroup SpecialPage
*/
-class SpecialMostlinkedtemplates extends QueryPage {
+class MostlinkedTemplatesPage extends QueryPage {
- /**
- * Name of the report
- *
- * @return String
- */
- public function getName() {
- return 'Mostlinkedtemplates';
+ function __construct( $name = 'Mostlinkedtemplates' ) {
+ parent::__construct( $name );
}
/**
@@ -66,22 +61,15 @@ class SpecialMostlinkedtemplates extends QueryPage {
return true;
}
- /**
- * Generate SQL for the report
- *
- * @return String
- */
- public function getSql() {
- $dbr = wfGetDB( DB_SLAVE );
- $templatelinks = $dbr->tableName( 'templatelinks' );
- $name = $dbr->addQuotes( $this->getName() );
- return "SELECT {$name} AS type,
- " . NS_TEMPLATE . " AS namespace,
- tl_title AS title,
- COUNT(*) AS value
- FROM {$templatelinks}
- WHERE tl_namespace = " . NS_TEMPLATE . "
- GROUP BY tl_title";
+ public function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'templatelinks' ),
+ 'fields' => array ( 'tl_namespace AS namespace',
+ 'tl_title AS title',
+ 'COUNT(*) AS value' ),
+ 'conds' => array ( 'tl_namespace' => NS_TEMPLATE ),
+ 'options' => array( 'GROUP BY' => 'tl_namespace, tl_title' )
+ );
}
/**
@@ -108,7 +96,7 @@ class SpecialMostlinkedtemplates extends QueryPage {
* @return String
*/
public function formatResult( $skin, $result ) {
- $title = Title::makeTitleSafe( $result->namespace, $result->title );
+ $title = Title::makeTitle( $result->namespace, $result->title );
return wfSpecialList(
$skin->link( $title ),
@@ -133,13 +121,3 @@ class SpecialMostlinkedtemplates extends QueryPage {
}
}
-/**
- * Execution function
- *
- * @param $par Mixed: parameters passed to the page
- */
-function wfSpecialMostlinkedtemplates( $par = false ) {
- list( $limit, $offset ) = wfCheckLimits();
- $mlt = new SpecialMostlinkedtemplates();
- $mlt->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php
index f9bafabc..b0253316 100644
--- a/includes/specials/SpecialMostrevisions.php
+++ b/includes/specials/SpecialMostrevisions.php
@@ -23,64 +23,12 @@
* @ingroup SpecialPage
* @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
*/
-
-/**
- * A special page to show pages with highest revision count
- *
- * @ingroup SpecialPage
- */
-class MostrevisionsPage extends QueryPage {
-
- function getName() { return 'Mostrevisions'; }
- function isExpensive() { return true; }
- function isSyndicated() { return false; }
-
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $revision, $page ) = $dbr->tableNamesN( 'revision', 'page' );
- return
- "
- SELECT
- 'Mostrevisions' as type,
- page_namespace as namespace,
- page_title as title,
- COUNT(*) as value
- FROM $revision
- JOIN $page ON page_id = rev_page
- WHERE page_namespace = " . NS_MAIN . "
- GROUP BY page_namespace, page_title
- HAVING COUNT(*) > 1
- ";
+class MostrevisionsPage extends FewestrevisionsPage {
+ function __construct( $name = 'Mostrevisions' ) {
+ parent::__construct( $name );
}
- function formatResult( $skin, $result ) {
- global $wgLang, $wgContLang;
-
- $nt = Title::makeTitle( $result->namespace, $result->title );
- $text = $wgContLang->convert( $nt->getPrefixedText() );
-
- $plink = $skin->linkKnown( $nt, $text );
-
- $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'),
- $wgLang->formatNum( $result->value ) );
- $nlink = $skin->linkKnown(
- $nt,
- $nl,
- array(),
- array( 'action' => 'history' )
- );
-
- return wfSpecialList($plink, $nlink);
+ function sortDescending() {
+ return true;
}
}
-
-/**
- * constructor
- */
-function wfSpecialMostrevisions() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $wpp = new MostrevisionsPage();
-
- $wpp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php
index 2f156c65..7ac7eba4 100644
--- a/includes/specials/SpecialMovepage.php
+++ b/includes/specials/SpecialMovepage.php
@@ -27,6 +27,10 @@
* @ingroup SpecialPage
*/
class MovePageForm extends UnlistedSpecialPage {
+
+ /**
+ * @var Title
+ */
var $oldTitle, $newTitle; # Objects
var $reason; # Text input
var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared; # Checks
@@ -70,7 +74,9 @@ class MovePageForm extends UnlistedSpecialPage {
# Check rights
$permErrors = $this->oldTitle->getUserPermissionsErrors( 'move', $wgUser );
if( !empty( $permErrors ) ) {
- $wgOut->showPermissionsErrorPage( $permErrors );
+ // Auto-block user's IP if the account was "hard" blocked
+ $user->spreadAnyEditBlock();
+ $this->getOutput()->showPermissionsErrorPage( $permErrors );
return;
}
@@ -103,12 +109,14 @@ class MovePageForm extends UnlistedSpecialPage {
function showForm( $err ) {
global $wgOut, $wgUser, $wgContLang, $wgFixDoubleRedirects;
- $skin = $wgUser->getSkin();
+ $skin = $this->getSkin();
$oldTitleLink = $skin->link( $this->oldTitle );
$wgOut->setPagetitle( wfMsg( 'move-page', $this->oldTitle->getPrefixedText() ) );
- $wgOut->setSubtitle( wfMsg( 'move-page-backlink', $oldTitleLink ) );
+ $skin->setRelevantTitle( $this->oldTitle );
+
+ $wgOut->addModules( 'mediawiki.special.movePage' );
$newTitle = $this->newTitle;
@@ -211,7 +219,7 @@ class MovePageForm extends UnlistedSpecialPage {
Xml::element( 'legend', null, wfMsg( 'move-page-legend' ) ) .
Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-movepage-table' ) ) .
"<tr>
- <td class='mw-label'>" .
+ <td class='mw-label'>" .
wfMsgHtml( 'movearticle' ) .
"</td>
<td class='mw-input'>
@@ -233,7 +241,7 @@ class MovePageForm extends UnlistedSpecialPage {
"</td>
<td class='mw-input'>" .
Html::element( 'textarea', array( 'name' => 'wpReason', 'id' => 'wpReason', 'cols' => 60, 'rows' => 2,
- 'maxlength' => 200 ), $this->reason ) .
+ 'maxlength' => 200 ), $this->reason ) . // maxlength byte limit is enforce in mediawiki.special.movePage.js
"</td>
</tr>"
);
@@ -398,7 +406,7 @@ class MovePageForm extends UnlistedSpecialPage {
# Do the actual move.
$error = $ot->moveTo( $nt, true, $this->reason, $createRedirect );
if ( $error !== true ) {
- # FIXME: show all the errors in a list, not just the first one
+ # @todo FIXME: Show all the errors in a list, not just the first one
$this->showForm( reset( $error ) );
return;
}
@@ -407,7 +415,7 @@ class MovePageForm extends UnlistedSpecialPage {
DoubleRedirectJob::fixRedirects( 'move', $ot, $nt );
}
- wfRunHooks( 'SpecialMovepageAfterMove', array( &$this , &$ot , &$nt ) ) ;
+ wfRunHooks( 'SpecialMovepageAfterMove', array( &$this, &$ot, &$nt ) );
$wgOut->setPagetitle( wfMsg( 'pagemovedsub' ) );
@@ -442,10 +450,10 @@ class MovePageForm extends UnlistedSpecialPage {
#
# If the target namespace doesn't allow subpages, moving with subpages
# 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.
+ # is bad.
+ # @todo FIXME: A specific error message should be given in this case.
- // FIXME: Use Title::moveSubpages() here
+ // @todo FIXME: Use Title::moveSubpages() here
$dbr = wfGetDB( DB_MASTER );
if( $this->moveSubpages && (
MWNamespace::hasSubpages( $nt->getNamespace() ) || (
@@ -486,7 +494,7 @@ class MovePageForm extends UnlistedSpecialPage {
}
$extraOutput = array();
- $skin = $wgUser->getSkin();
+ $skin = $this->getSkin();
$count = 1;
foreach( $extraPages as $oldSubpage ) {
if( $ot->equals( $oldSubpage ) ) {
@@ -561,7 +569,7 @@ class MovePageForm extends UnlistedSpecialPage {
# 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.
+ # @todo FIXME: Needs a more robust solution inside FileRepo.
if( $ot->getNamespace() == NS_FILE ) {
RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $ot );
}
@@ -573,7 +581,7 @@ class MovePageForm extends UnlistedSpecialPage {
}
function showSubpages( $title, $out ) {
- global $wgUser, $wgLang;
+ global $wgLang;
if( !MWNamespace::hasSubpages( $title->getNamespace() ) )
return;
@@ -590,7 +598,7 @@ class MovePageForm extends UnlistedSpecialPage {
}
$out->addWikiMsg( 'movesubpagetext', $wgLang->formatNum( $count ) );
- $skin = $wgUser->getSkin();
+ $skin = $this->getSkin();
$out->addHTML( "<ul>\n" );
foreach( $subpages as $subpage ) {
diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php
index cecd7dfd..ea20c2f7 100644
--- a/includes/specials/SpecialNewimages.php
+++ b/includes/specials/SpecialNewimages.php
@@ -20,244 +20,152 @@
* @file
* @ingroup SpecialPage
*/
+class SpecialNewFiles extends IncludableSpecialPage {
-/**
- * @todo FIXME: this code is crap, should use Pager and Database::select().
- */
-function wfSpecialNewimages( $par, $specialPage ) {
- global $wgUser, $wgOut, $wgLang, $wgRequest, $wgMiserMode;
-
- $wpIlMatch = $wgRequest->getText( 'wpIlMatch' );
- $dbr = wfGetDB( DB_SLAVE );
- $sk = $wgUser->getSkin();
- $shownav = !$specialPage->including();
- $hidebots = $wgRequest->getBool( 'hidebots' , 1 );
+ public function __construct(){
+ parent::__construct( 'Newimages' );
+ }
- $hidebotsql = '';
- if ( $hidebots ) {
- # Make a list of group names which have the 'bot' flag set.
- $botconds = array();
- foreach ( User::getGroupsWithPermission('bot') as $groupname ) {
- $botconds[] = 'ug_group = ' . $dbr->addQuotes( $groupname );
- }
+ public function execute( $par ){
+ $this->setHeaders();
+ $this->outputHeader();
- # If not bot groups, do not set $hidebotsql
- if ( $botconds ) {
- $isbotmember = $dbr->makeList( $botconds, LIST_OR );
+ $pager = new NewFilesPager( $par );
- # This join, in conjunction with WHERE ug_group IS NULL, returns
- # only those rows from IMAGE where the uploading user is not a mem-
- # ber of a group which has the 'bot' permission set.
- $ug = $dbr->tableName( 'user_groups' );
- $hidebotsql = " LEFT JOIN $ug ON img_user=ug_user AND ($isbotmember)";
+ if ( !$this->including() ) {
+ $form = $pager->getForm();
+ $form->prepareForm();
+ $form->displayForm( '' );
+ }
+ $this->getOutput()->addHTML( $pager->getBody() );
+ if ( !$this->including() ) {
+ $this->getOutput()->addHTML( $pager->getNavigationBar() );
}
}
+}
- $image = $dbr->tableName( 'image' );
- $sql = "SELECT img_timestamp from $image";
- if ($hidebotsql) {
- $sql .= "$hidebotsql WHERE ug_group IS NULL";
- }
- $sql .= ' ORDER BY img_timestamp DESC';
- $sql = $dbr->limitResult($sql, 1, false);
- $res = $dbr->query( $sql, __FUNCTION__ );
- $row = $dbr->fetchRow( $res );
- if( $row !== false ) {
- $ts = $row[0];
- } else {
- $ts = false;
- }
+/**
+ * @ingroup SpecialPage Pager
+ */
+class NewFilesPager extends ReverseChronologicalPager {
- # If we were clever, we'd use this to cache.
- $latestTimestamp = wfTimestamp( TS_MW, $ts );
+ function __construct( $par = null ) {
+ global $wgRequest;
- # Hardcode this for now.
- $limit = 48;
- $parval = intval( $par );
- if ( $parval ) {
- if ( $parval <= $limit && $parval > 0 ) {
- $limit = $parval;
- }
- }
+ $this->like = $wgRequest->getText( 'like' );
+ $this->showbots = $wgRequest->getBool( 'showbots' , 0 );
+ $this->skin = $this->getSkin();
- $where = array();
- $searchpar = array();
- if ( $wpIlMatch != '' && !$wgMiserMode) {
- $nt = Title::newFromURL( $wpIlMatch );
- if( $nt ) {
- $where[] = 'LOWER(img_name) ' . $dbr->buildLike( $dbr->anyString(), strtolower( $nt->getDBkey() ), $dbr->anyString() );
- $searchpar['wpIlMatch'] = $wpIlMatch;
- }
+ parent::__construct();
}
- $invertSort = false;
- $until = $wgRequest->getVal( 'until' );
- if( $until ) {
- $where[] = "img_timestamp < '" . $dbr->timestamp( $until ) . "'";
+ function getTitle() {
+ return SpecialPage::getTitleFor( 'Newimages' );
}
- $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,'.
- "img_description,img_timestamp FROM $image";
- if( $hidebotsql ) {
- $sql .= $hidebotsql;
- $where[] = 'ug_group IS NULL';
- }
- if( count( $where ) ) {
- $sql .= ' WHERE ' . $dbr->makeList( $where, LIST_AND );
- }
- $sql.=' ORDER BY img_timestamp '. ( $invertSort ? '' : ' DESC' );
- $sql = $dbr->limitResult($sql, ( $limit + 1 ), false);
- $res = $dbr->query( $sql, __FUNCTION__ );
+ function getQueryInfo() {
+ global $wgMiserMode;
+ $conds = $jconds = array();
+ $tables = array( 'image' );
- /**
- * We have to flip things around to get the last N after a certain date
- */
- $images = array();
- foreach ( $res as $s ) {
- if( $invertSort ) {
- array_unshift( $images, $s );
- } else {
- array_push( $images, $s );
+ if( !$this->showbots ) {
+ $tables[] = 'user_groups';
+ $conds[] = 'ug_group IS NULL';
+ $jconds['user_groups'] = array(
+ 'LEFT JOIN',
+ array(
+ 'ug_group' => User::getGroupsWithPermission( 'bot' ),
+ 'ug_user = img_user'
+ )
+ );
}
- }
- $gallery = new ImageGallery();
- $firstTimestamp = null;
- $lastTimestamp = null;
- $shownImages = 0;
- foreach( $images as $s ) {
- $shownImages++;
- if( $shownImages > $limit ) {
- # One extra just to test for whether to show a page link;
- # don't actually show it.
- break;
+ if( !$wgMiserMode && $this->like !== null ){
+ $dbr = wfGetDB( DB_SLAVE );
+ $likeObj = Title::newFromURL( $this->like );
+ if( $likeObj instanceof Title ){
+ $like = $dbr->buildLike( $dbr->anyString(), strtolower( $likeObj->getDBkey() ), $dbr->anyString() );
+ $conds[] = "LOWER(img_name) $like";
+ }
}
- $name = $s->img_name;
- $ut = $s->img_user_text;
-
- $nt = Title::newFromText( $name, NS_FILE );
- $ul = $sk->link( Title::makeTitle( NS_USER, $ut ), $ut );
-
- $gallery->add( $nt, "$ul<br />\n<i>".htmlspecialchars($wgLang->timeanddate( $s->img_timestamp, true ))."</i><br />\n" );
+ $query = array(
+ 'tables' => $tables,
+ 'fields' => '*',
+ 'join_conds' => $jconds,
+ 'conds' => $conds
+ );
- $timestamp = wfTimestamp( TS_MW, $s->img_timestamp );
- if( empty( $firstTimestamp ) ) {
- $firstTimestamp = $timestamp;
- }
- $lastTimestamp = $timestamp;
+ return $query;
}
- $titleObj = SpecialPage::getTitleFor( 'Newimages' );
- $action = $titleObj->getLocalURL( $hidebots ? '' : 'hidebots=0' );
- if ( $shownav && !$wgMiserMode ) {
- $wgOut->addHTML(
- Xml::openElement( 'form', array( 'action' => $action, 'method' => 'post', 'id' => 'imagesearch' ) ) .
- Xml::fieldset( wfMsg( 'newimages-legend' ) ) .
- Xml::inputLabel( wfMsg( 'newimages-label' ), 'wpIlMatch', 'wpIlMatch', 20, $wpIlMatch ) . ' ' .
- Xml::submitButton( wfMsg( 'ilsubmit' ), array( 'name' => 'wpIlSubmit' ) ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
- );
+ function getIndexField(){
+ return 'img_timestamp';
}
- $bydate = wfMsg( 'bydate' );
- $lt = $wgLang->formatNum( min( $shownImages, $limit ) );
- if ( $shownav ) {
- $text = wfMsgExt( 'imagelisttext', array('parse'), $lt, $bydate );
- $wgOut->addHTML( $text . "\n" );
+ function getStartBody(){
+ $this->gallery = new ImageGallery();
}
- /**
- * Paging controls...
- */
-
- # If we change bot visibility, this needs to be carried along.
- if( !$hidebots ) {
- $botpar = array( 'hidebots' => 0 );
- } else {
- $botpar = array();
+ function getEndBody(){
+ return $this->gallery->toHTML();
}
- $now = wfTimestampNow();
- $d = $wgLang->date( $now, true );
- $t = $wgLang->time( $now, true );
- $query = array_merge(
- array( 'from' => $now ),
- $botpar,
- $searchpar
- );
-
- $dateLink = $sk->linkKnown(
- $titleObj,
- htmlspecialchars( wfMsg( 'sp-newimages-showfrom', $d, $t ) ),
- array(),
- $query
- );
- $query = array_merge(
- array( 'hidebots' => ( $hidebots ? 0 : 1 ) ),
- $searchpar
- );
+ function formatRow( $row ) {
+ global $wgLang;
- $showhide = $hidebots ? wfMsg( 'show' ) : wfMsg( 'hide' );
+ $name = $row->img_name;
+ $user = User::newFromId( $row->img_user );
- $botLink = $sk->linkKnown(
- $titleObj,
- htmlspecialchars( wfMsg( 'showhidebots', $showhide ) ),
- array(),
- $query
- );
+ $title = Title::makeTitle( NS_FILE, $name );
+ $ul = $this->skin->link( $user->getUserpage(), $user->getName() );
- $opts = array( 'parsemag', 'escapenoentities' );
- $prevLink = wfMsgExt( 'pager-newer-n', $opts, $wgLang->formatNum( $limit ) );
- if( $firstTimestamp && $firstTimestamp != $latestTimestamp ) {
- $query = array_merge(
- array( 'from' => $firstTimestamp ),
- $botpar,
- $searchpar
- );
-
- $prevLink = $sk->linkKnown(
- $titleObj,
- $prevLink,
- array(),
- $query
+ $this->gallery->add(
+ $title,
+ "$ul<br />\n<i>"
+ . htmlspecialchars( $wgLang->timeanddate( $row->img_timestamp, true ) )
+ . "</i><br />\n"
);
}
- $nextLink = wfMsgExt( 'pager-older-n', $opts, $wgLang->formatNum( $limit ) );
- if( $invertSort || ( $shownImages > $limit && $lastTimestamp ) ) {
- $query = array_merge(
- array( 'until' => ( $lastTimestamp ? $lastTimestamp : "" ) ),
- $botpar,
- $searchpar
- );
-
- $nextLink = $sk->linkKnown(
- $titleObj,
- $nextLink,
- array(),
- $query
+ function getForm() {
+ global $wgRequest, $wgMiserMode;
+
+ $fields = array(
+ 'like' => array(
+ 'type' => 'text',
+ 'label-message' => 'newimages-label',
+ 'name' => 'like',
+ ),
+ 'showbots' => array(
+ 'type' => 'check',
+ 'label' => wfMessage( 'showhidebots', wfMsg( 'show' ) ),
+ 'name' => 'showbots',
+ # 'default' => $wgRequest->getBool( 'showbots', 0 ),
+ ),
+ 'limit' => array(
+ 'type' => 'hidden',
+ 'default' => $wgRequest->getText( 'limit' ),
+ 'name' => 'limit',
+ ),
+ 'offset' => array(
+ 'type' => 'hidden',
+ 'default' => $wgRequest->getText( 'offset' ),
+ 'name' => 'offset',
+ ),
);
- }
-
- $prevnext = '<p>' . $botLink . ' '. wfMsgHtml( 'viewprevnext', $prevLink, $nextLink, $dateLink ) .'</p>';
+ if( $wgMiserMode ){
+ unset( $fields['like'] );
+ }
- if ($shownav)
- $wgOut->addHTML( $prevnext );
+ $form = new HTMLForm( $fields );
+ $form->setTitle( $this->getTitle() );
+ $form->setSubmitText( wfMsg( 'ilsubmit' ) );
+ $form->setMethod( 'get' );
+ $form->setWrapperLegend( wfMsg( 'newimages-legend' ) );
- if( count( $images ) ) {
- $wgOut->addHTML( $gallery->toHTML() );
- if ($shownav)
- $wgOut->addHTML( $prevnext );
- } else {
- $wgOut->addWikiMsg( 'noimages' );
+ return $form;
}
}
diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php
index 3235436a..bf9fb9f7 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -29,7 +29,12 @@
class SpecialNewpages extends IncludableSpecialPage {
// Stored objects
- protected $opts, $skin;
+
+ /**
+ * @var FormOptions
+ */
+ protected $opts;
+ protected $customFilters;
// Some internal settings
protected $showNavigation = false;
@@ -39,24 +44,30 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function setup( $par ) {
- global $wgRequest, $wgUser, $wgEnableNewpagesUserFilter;
+ global $wgEnableNewpagesUserFilter;
// Options
$opts = new FormOptions();
$this->opts = $opts; // bind
$opts->add( 'hideliu', false );
- $opts->add( 'hidepatrolled', $wgUser->getBoolOption( 'newpageshidepatrolled' ) );
+ $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'newpageshidepatrolled' ) );
$opts->add( 'hidebots', false );
$opts->add( 'hideredirs', true );
- $opts->add( 'limit', (int)$wgUser->getOption( 'rclimit' ) );
+ $opts->add( 'limit', (int)$this->getUser()->getOption( 'rclimit' ) );
$opts->add( 'offset', '' );
$opts->add( 'namespace', '0' );
$opts->add( 'username', '' );
$opts->add( 'feed', '' );
$opts->add( 'tagfilter', '' );
+ $this->customFilters = array();
+ wfRunHooks( 'SpecialNewPagesFilters', array( $this, &$this->customFilters ) );
+ foreach( $this->customFilters as $key => $params ) {
+ $opts->add( $key, $params['default'] );
+ }
+
// Set values
- $opts->fetchValuesFromRequest( $wgRequest );
+ $opts->fetchValuesFromRequest( $this->getRequest() );
if ( $par ) $this->parseParams( $par );
// Validate
@@ -64,36 +75,42 @@ class SpecialNewpages extends IncludableSpecialPage {
if( !$wgEnableNewpagesUserFilter ) {
$opts->setValue( 'username', '' );
}
-
- // Store some objects
- $this->skin = $wgUser->getSkin();
}
protected function parseParams( $par ) {
global $wgLang;
$bits = preg_split( '/\s*,\s*/', trim( $par ) );
foreach ( $bits as $bit ) {
- if ( 'shownav' == $bit )
+ if ( 'shownav' == $bit ) {
$this->showNavigation = true;
- if ( 'hideliu' === $bit )
+ }
+ if ( 'hideliu' === $bit ) {
$this->opts->setValue( 'hideliu', true );
- if ( 'hidepatrolled' == $bit )
+ }
+ if ( 'hidepatrolled' == $bit ) {
$this->opts->setValue( 'hidepatrolled', true );
- if ( 'hidebots' == $bit )
+ }
+ if ( 'hidebots' == $bit ) {
$this->opts->setValue( 'hidebots', true );
- if ( 'showredirs' == $bit )
+ }
+ if ( 'showredirs' == $bit ) {
$this->opts->setValue( 'hideredirs', false );
- if ( is_numeric( $bit ) )
+ }
+ if ( is_numeric( $bit ) ) {
$this->opts->setValue( 'limit', intval( $bit ) );
+ }
$m = array();
- if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) )
- $this->opts->setValue( 'limit', intval($m[1]) );
+ if ( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
+ $this->opts->setValue( 'limit', intval( $m[1] ) );
+ }
// PG offsets not just digits!
- if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) )
- $this->opts->setValue( 'offset', intval($m[1]) );
- if ( preg_match( '/^username=(.*)$/', $bit, $m ) )
+ if ( preg_match( '/^offset=([^=]+)$/', $bit, $m ) ) {
+ $this->opts->setValue( 'offset', intval( $m[1] ) );
+ }
+ if ( preg_match( '/^username=(.*)$/', $bit, $m ) ) {
$this->opts->setValue( 'username', $m[1] );
+ }
if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
$ns = $wgLang->getNsIndex( $m[1] );
if( $ns !== false ) {
@@ -110,7 +127,7 @@ class SpecialNewpages extends IncludableSpecialPage {
* @return String
*/
public function execute( $par ) {
- global $wgOut;
+ $out = $this->getOutput();
$this->setHeaders();
$this->outputHeader();
@@ -135,15 +152,17 @@ class SpecialNewpages extends IncludableSpecialPage {
if( $pager->getNumRows() ) {
$navigation = '';
- if ( $this->showNavigation ) $navigation = $pager->getNavigationBar();
- $wgOut->addHTML( $navigation . $pager->getBody() . $navigation );
+ if ( $this->showNavigation ) {
+ $navigation = $pager->getNavigationBar();
+ }
+ $out->addHTML( $navigation . $pager->getBody() . $navigation );
} else {
- $wgOut->addWikiMsg( 'specialpage-empty' );
+ $out->addWikiMsg( 'specialpage-empty' );
}
}
protected function filterLinks() {
- global $wgGroupPermissions, $wgUser, $wgLang;
+ global $wgGroupPermissions, $wgLang;
// show/hide links
$showhide = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) );
@@ -155,24 +174,28 @@ class SpecialNewpages extends IncludableSpecialPage {
'hidebots' => 'rcshowhidebots',
'hideredirs' => 'whatlinkshere-hideredirs'
);
+ foreach ( $this->customFilters as $key => $params ) {
+ $filters[$key] = $params['msg'];
+ }
// Disable some if needed
- # FIXME: throws E_NOTICEs if not set; and doesn't obey hooks etc
- if ( $wgGroupPermissions['*']['createpage'] !== true )
- unset($filters['hideliu']);
-
- if ( !$wgUser->useNPPatrol() )
- unset($filters['hidepatrolled']);
+ # @todo FIXME: Throws E_NOTICEs if not set; and doesn't obey hooks etc.
+ if ( $wgGroupPermissions['*']['createpage'] !== true ) {
+ unset( $filters['hideliu'] );
+ }
+ if ( !$this->getUser()->useNPPatrol() ) {
+ unset( $filters['hidepatrolled'] );
+ }
$links = array();
$changed = $this->opts->getChangedValues();
- unset($changed['offset']); // Reset offset if query type changes
+ unset( $changed['offset'] ); // Reset offset if query type changes
$self = $this->getTitle();
foreach ( $filters as $key => $msg ) {
- $onoff = 1 - $this->opts->getValue($key);
- $link = $this->skin->link( $self, $showhide[$onoff], array(),
- array( $key => $onoff ) + $changed
+ $onoff = 1 - $this->opts->getValue( $key );
+ $link = $this->getSkin()->link( $self, $showhide[$onoff], array(),
+ array( $key => $onoff ) + $changed
);
$links[$key] = wfMsgHtml( $msg, $link );
}
@@ -181,7 +204,7 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function form() {
- global $wgOut, $wgEnableNewpagesUserFilter, $wgScript;
+ global $wgEnableNewpagesUserFilter, $wgScript;
// Consume values
$this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW
@@ -201,61 +224,62 @@ class SpecialNewpages extends IncludableSpecialPage {
$hidden = implode( "\n", $hidden );
$tagFilter = ChangeTags::buildTagFilterSelector( $tagFilterVal );
- if ($tagFilter)
+ if ( $tagFilter ) {
list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter;
+ }
$form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) .
Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
Xml::fieldset( wfMsg( 'newpages' ) ) .
Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) .
- "<tr>
- <td class='mw-label'>" .
+ '<tr>
+ <td class="mw-label">' .
Xml::label( wfMsg( 'namespace' ), 'namespace' ) .
- "</td>
- <td class='mw-input'>" .
+ '</td>
+ <td class="mw-input">' .
Xml::namespaceSelector( $namespace, 'all' ) .
- "</td>
- </tr>" . ( $tagFilter ? (
- "<tr>
- <td class='mw-label'>" .
+ '</td>
+ </tr>' . ( $tagFilter ? (
+ '<tr>
+ <td class="mw-label">' .
$tagFilterLabel .
- "</td>
- <td class='mw-input'>" .
+ '</td>
+ <td class="mw-input">' .
$tagFilterSelector .
- "</td>
- </tr>" ) : '' ) .
- ($wgEnableNewpagesUserFilter ?
- "<tr>
- <td class='mw-label'>" .
+ '</td>
+ </tr>' ) : '' ) .
+ ( $wgEnableNewpagesUserFilter ?
+ '<tr>
+ <td class="mw-label">' .
Xml::label( wfMsg( 'newpages-username' ), 'mw-np-username' ) .
- "</td>
- <td class='mw-input'>" .
+ '</td>
+ <td class="mw-input">' .
Xml::input( 'username', 30, $userText, array( 'id' => 'mw-np-username' ) ) .
- "</td>
- </tr>" : "" ) .
- "<tr> <td></td>
- <td class='mw-submit'>" .
+ '</td>
+ </tr>' : '' ) .
+ '<tr> <td></td>
+ <td class="mw-submit">' .
Xml::submitButton( wfMsg( 'allpagessubmit' ) ) .
- "</td>
- </tr>" .
- "<tr>
+ '</td>
+ </tr>' .
+ '<tr>
<td></td>
- <td class='mw-input'>" .
+ <td class="mw-input">' .
$this->filterLinks() .
- "</td>
- </tr>" .
+ '</td>
+ </tr>' .
Xml::closeElement( 'table' ) .
Xml::closeElement( 'fieldset' ) .
$hidden .
Xml::closeElement( 'form' );
- $wgOut->addHTML( $form );
+ $this->getOutput()->addHTML( $form );
}
protected function setSyndicated() {
- global $wgOut;
- $wgOut->setSyndicated( true );
- $wgOut->setFeedAppendQuery( wfArrayToCGI( $this->opts->getAllValues() ) );
+ $out = $this->getOutput();
+ $out->setSyndicated( true );
+ $out->setFeedAppendQuery( wfArrayToCGI( $this->opts->getAllValues() ) );
}
/**
@@ -265,11 +289,20 @@ class SpecialNewpages extends IncludableSpecialPage {
* @return String
*/
public function formatRow( $result ) {
- global $wgLang, $wgContLang;
+ global $wgLang;
+
+ # Revision deletion works on revisions, so we should cast one
+ $row = array(
+ 'comment' => $result->rc_comment,
+ 'deleted' => $result->rc_deleted,
+ 'user_text' => $result->rc_user_text,
+ 'user' => $result->rc_user,
+ );
+ $rev = new Revision( $row );
$classes = array();
-
- $dm = $wgContLang->getDirMark();
+
+ $dm = $wgLang->getDirMark();
$title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title );
$time = Html::element( 'span', array( 'class' => 'mw-newpages-time' ),
@@ -278,17 +311,18 @@ class SpecialNewpages extends IncludableSpecialPage {
$query = array( 'redirect' => 'no' );
- if( $this->patrollable( $result ) )
+ if( $this->patrollable( $result ) ) {
$query['rcid'] = $result->rc_id;
+ }
- $plink = $this->skin->linkKnown(
+ $plink = $this->getSkin()->linkKnown(
$title,
null,
array( 'class' => 'mw-newpages-pagename' ),
$query,
array( 'known' ) // Set explicitly to avoid the default of 'known','noclasses'. This breaks the colouration for stubs
);
- $histLink = $this->skin->linkKnown(
+ $histLink = $this->getSkin()->linkKnown(
$title,
wfMsgHtml( 'hist' ),
array(),
@@ -300,10 +334,10 @@ class SpecialNewpages extends IncludableSpecialPage {
'[' . 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 );
-
+
+ $ulink = $this->getSkin()->revUserTools( $rev );
+ $comment = $this->getSkin()->revComment( $rev );
+
if ( $this->patrollable( $result ) ) {
$classes[] = 'not-patrolled';
}
@@ -321,7 +355,7 @@ class SpecialNewpages extends IncludableSpecialPage {
$tagDisplay = '';
}
- $css = count($classes) ? ' class="'.implode( " ", $classes).'"' : '';
+ $css = count( $classes ) ? ' class="' . implode( ' ', $classes ) . '"' : '';
return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n";
}
@@ -333,8 +367,7 @@ class SpecialNewpages extends IncludableSpecialPage {
* @return Boolean
*/
protected function patrollable( $result ) {
- global $wgUser;
- return ( $wgUser->useNPPatrol() && !$result->rc_patrolled );
+ return ( $this->getUser()->useNPPatrol() && !$result->rc_patrolled );
}
/**
@@ -343,22 +376,23 @@ class SpecialNewpages extends IncludableSpecialPage {
* @param $type String
*/
protected function feed( $type ) {
- global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgOut;
+ global $wgFeed, $wgFeedClasses, $wgFeedLimit;
if ( !$wgFeed ) {
- $wgOut->addWikiMsg( 'feed-unavailable' );
+ $this->getOutput()->addWikiMsg( 'feed-unavailable' );
return;
}
if( !isset( $wgFeedClasses[$type] ) ) {
- $wgOut->addWikiMsg( 'feed-invalid' );
+ $this->getOutput()->addWikiMsg( 'feed-invalid' );
return;
}
$feed = new $wgFeedClasses[$type](
$this->feedTitle(),
wfMsgExt( 'tagline', 'parsemag' ),
- $this->getTitle()->getFullUrl() );
+ $this->getTitle()->getFullUrl()
+ );
$pager = new NewPagesPager( $this, $this->opts );
$limit = $this->opts->getValue( 'limit' );
@@ -375,8 +409,7 @@ class SpecialNewpages extends IncludableSpecialPage {
protected function feedTitle() {
global $wgLanguageCode, $wgSitename;
- $page = SpecialPage::getPage( 'Newpages' );
- $desc = $page->getDescription();
+ $desc = $this->getDescription();
return "$wgSitename - $desc [$wgLanguageCode]";
}
@@ -392,7 +425,8 @@ class SpecialNewpages extends IncludableSpecialPage {
$title->getFullURL(),
$date,
$this->feedItemAuthor( $row ),
- $comments);
+ $comments
+ );
} else {
return null;
}
@@ -406,7 +440,7 @@ class SpecialNewpages extends IncludableSpecialPage {
$revision = Revision::newFromId( $row->rev_id );
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>";
}
@@ -419,7 +453,12 @@ class SpecialNewpages extends IncludableSpecialPage {
*/
class NewPagesPager extends ReverseChronologicalPager {
// Stored opts
- protected $opts, $mForm;
+ protected $opts;
+
+ /**
+ * @var HtmlForm
+ */
+ protected $mForm;
function __construct( $form, FormOptions $opts ) {
parent::__construct();
@@ -427,15 +466,30 @@ class NewPagesPager extends ReverseChronologicalPager {
$this->opts = $opts;
}
+ /**
+ * @return Title
+ */
function getTitle() {
static $title = null;
- if ( $title === null )
+ if ( $title === null ) {
$title = $this->mForm->getTitle();
+ }
return $title;
}
+ /**
+ * @return User
+ */
+ function getUser() {
+ static $user = null;
+ if ( $user === null ) {
+ $user = $this->mForm->getUser();
+ }
+ return $user;
+ }
+
function getQueryInfo() {
- global $wgEnableNewpagesUserFilter, $wgGroupPermissions, $wgUser;
+ global $wgEnableNewpagesUserFilter, $wgGroupPermissions;
$conds = array();
$conds['rc_new'] = 1;
@@ -461,7 +515,7 @@ class NewPagesPager extends ReverseChronologicalPager {
$conds['rc_user'] = 0;
}
# If this user cannot see patrolled edits or they are off, don't do dumb queries!
- if( $this->opts->getValue( 'hidepatrolled' ) && $wgUser->useNPPatrol() ) {
+ if( $this->opts->getValue( 'hidepatrolled' ) && $this->getUser()->useNPPatrol() ) {
$conds['rc_patrolled'] = 0;
}
if( $this->opts->getValue( 'hidebots' ) ) {
@@ -471,31 +525,39 @@ 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));
+ $tables = array( 'recentchanges', 'page' );
+ $fields = array(
+ 'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_user', 'rc_user_text',
+ 'rc_comment', 'rc_timestamp', 'rc_patrolled','rc_id', 'rc_deleted',
+ 'page_len AS length', 'page_latest AS rev_id', 'ts_tags'
+ );
+ $join_conds = array( 'page' => array( 'INNER JOIN', 'page_id=rc_cur_id' ) );
+
+ wfRunHooks( 'SpecialNewpagesConditions',
+ array( &$this, $this->opts, &$conds, &$tables, &$fields, &$join_conds ) );
$info = array(
- 'tables' => array( 'recentchanges', 'page' ),
- 'fields' => 'rc_namespace,rc_title, rc_cur_id, rc_user,rc_user_text,rc_comment,
- rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id, ts_tags',
- 'conds' => $conds,
- 'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) ),
- 'join_conds' => array(
- 'page' => array('INNER JOIN', 'page_id=rc_cur_id'),
- ),
+ 'tables' => $tables,
+ 'fields' => $fields,
+ 'conds' => $conds,
+ 'options' => array( 'USE INDEX' => array( 'recentchanges' => $rcIndexes ) ),
+ 'join_conds' => $join_conds
);
- ## Empty array for fields, it'll be set by us anyway.
+ // Empty array for fields, it'll be set by us anyway.
$fields = array();
- ## Modify query for tags
- ChangeTags::modifyDisplayQuery( $info['tables'],
- $fields,
- $info['conds'],
- $info['join_conds'],
- $info['options'],
- $this->opts['tagfilter'] );
+ // Modify query for tags
+ ChangeTags::modifyDisplayQuery(
+ $info['tables'],
+ $fields,
+ $info['conds'],
+ $info['join_conds'],
+ $info['options'],
+ $this->opts['tagfilter']
+ );
return $info;
}
@@ -517,10 +579,10 @@ class NewPagesPager extends ReverseChronologicalPager {
$linkBatch->add( $row->rc_namespace, $row->rc_title );
}
$linkBatch->execute();
- return "<ul>";
+ return '<ul>';
}
function getEndBody() {
- return "</ul>";
+ return '</ul>';
}
}
diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php
new file mode 100644
index 00000000..1caa2c51
--- /dev/null
+++ b/includes/specials/SpecialPasswordReset.php
@@ -0,0 +1,273 @@
+<?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 for requesting a password reset email
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialPasswordReset extends FormSpecialPage {
+
+ public function __construct() {
+ parent::__construct( 'PasswordReset' );
+ }
+
+ public function userCanExecute( User $user ) {
+ $error = $this->canChangePassword( $user );
+ if ( is_string( $error ) ) {
+ throw new ErrorPageError( 'internalerror', $error );
+ } else if ( !$error ) {
+ throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' );
+ }
+
+ return parent::userCanExecute( $user );
+ }
+
+ protected function getFormFields() {
+ global $wgPasswordResetRoutes, $wgAuth;
+ $a = array();
+ if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
+ $a['Username'] = array(
+ 'type' => 'text',
+ 'label-message' => 'passwordreset-username',
+ );
+ }
+
+ if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) {
+ $a['Email'] = array(
+ 'type' => 'email',
+ 'label-message' => 'passwordreset-email',
+ );
+ }
+
+ if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) {
+ $domains = $wgAuth->domainList();
+ $a['Domain'] = array(
+ 'type' => 'select',
+ 'options' => $domains,
+ 'label-message' => 'passwordreset-domain',
+ );
+ }
+
+ return $a;
+ }
+
+ public function alterForm( HTMLForm $form ) {
+ $form->setSubmitText( wfMessage( "mailmypassword" ) );
+ }
+
+ protected function preText() {
+ global $wgPasswordResetRoutes;
+ $i = 0;
+ if ( isset( $wgPasswordResetRoutes['username'] ) && $wgPasswordResetRoutes['username'] ) {
+ $i++;
+ }
+ if ( isset( $wgPasswordResetRoutes['email'] ) && $wgPasswordResetRoutes['email'] ) {
+ $i++;
+ }
+ if ( isset( $wgPasswordResetRoutes['domain'] ) && $wgPasswordResetRoutes['domain'] ) {
+ $i++;
+ }
+ return wfMessage( 'passwordreset-pretext', $i )->parseAsBlock();
+ }
+
+ /**
+ * Process the form. At this point we know that the user passes all the criteria in
+ * userCanExecute(), and if the data array contains 'Username', etc, then Username
+ * resets are allowed.
+ * @param $data array
+ * @return Bool|Array
+ */
+ public function onSubmit( array $data ) {
+ global $wgAuth;
+
+ if ( isset( $data['Domain'] ) ) {
+ if ( $wgAuth->validDomain( $data['Domain'] ) ) {
+ $wgAuth->setDomain( $data['Domain'] );
+ } else {
+ $wgAuth->setDomain( 'invaliddomain' );
+ }
+ }
+
+ if ( isset( $data['Username'] ) && $data['Username'] !== '' ) {
+ $method = 'username';
+ $users = array( User::newFromName( $data['Username'] ) );
+ } elseif ( isset( $data['Email'] )
+ && $data['Email'] !== ''
+ && Sanitizer::validateEmail( $data['Email'] ) )
+ {
+ $method = 'email';
+ $res = wfGetDB( DB_SLAVE )->select(
+ 'user',
+ '*',
+ array( 'user_email' => $data['Email'] ),
+ __METHOD__
+ );
+ if ( $res ) {
+ $users = array();
+ foreach( $res as $row ){
+ $users[] = User::newFromRow( $row );
+ }
+ } else {
+ // Some sort of database error, probably unreachable
+ throw new MWException( 'Unknown database error in ' . __METHOD__ );
+ }
+ } else {
+ // The user didn't supply any data
+ return false;
+ }
+
+ // Check for hooks (captcha etc), and allow them to modify the users list
+ $error = array();
+ if ( !wfRunHooks( 'SpecialPasswordResetOnSubmit', array( &$users, $data, &$error ) ) ) {
+ return array( $error );
+ }
+
+ if( count( $users ) == 0 ){
+ if( $method == 'email' ){
+ // Don't reveal whether or not an email address is in use
+ return true;
+ } else {
+ return array( 'noname' );
+ }
+ }
+
+ $firstUser = $users[0];
+
+ if ( !$firstUser instanceof User || !$firstUser->getID() ) {
+ return array( array( 'nosuchuser', $data['Username'] ) );
+ }
+
+ // Check against the rate limiter
+ if ( $this->getUser()->pingLimiter( 'mailpassword' ) ) {
+ throw new ThrottledError;
+ }
+
+ // Check against password throttle
+ foreach ( $users as $user ) {
+ if ( $user->isPasswordReminderThrottled() ) {
+ global $wgPasswordReminderResendTime;
+ # Round the time in hours to 3 d.p., in case someone is specifying
+ # minutes or seconds.
+ return array( array( 'throttled-mailpassword', round( $wgPasswordReminderResendTime, 3 ) ) );
+ }
+ }
+
+ global $wgNewPasswordExpiry;
+
+ // All the users will have the same email address
+ if ( $firstUser->getEmail() == '' ) {
+ // This won't be reachable from the email route, so safe to expose the username
+ return array( array( 'noemail', $firstUser->getName() ) );
+ }
+
+ // We need to have a valid IP address for the hook, but per bug 18347, we should
+ // send the user's name if they're logged in.
+ $ip = wfGetIP();
+ if ( !$ip ) {
+ return array( 'badipaddress' );
+ }
+ $caller = $this->getUser();
+ wfRunHooks( 'User::mailPasswordInternal', array( &$caller, &$ip, &$firstUser ) );
+ $username = $caller->getName();
+ $msg = IP::isValid( $username )
+ ? 'passwordreset-emailtext-ip'
+ : 'passwordreset-emailtext-user';
+
+ $passwords = array();
+ foreach ( $users as $user ) {
+ $password = $user->randomPassword();
+ $user->setNewpassword( $password );
+ $user->saveSettings();
+ $passwords[] = wfMessage( 'passwordreset-emailelement', $user->getName(), $password );
+ }
+ $passwordBlock = implode( "\n\n", $passwords );
+
+ // Send in the user's language; which should hopefully be the same
+ $userLanguage = $firstUser->getOption( 'language' );
+
+ $body = wfMessage( $msg )->inLanguage( $userLanguage );
+ $body->params(
+ $username,
+ $passwordBlock,
+ count( $passwords ),
+ Title::newMainPage()->getCanonicalUrl(),
+ round( $wgNewPasswordExpiry / 86400 )
+ );
+
+ $title = wfMessage( 'passwordreset-emailtitle' );
+
+ $result = $firstUser->sendMail( $title->text(), $body->text() );
+
+ if ( $result->isGood() ) {
+ return true;
+ } else {
+ // @todo FIXME: The email didn't send, but we have already set the password throttle
+ // timestamp, so they won't be able to try again until it expires... :(
+ return array( array( 'mailerror', $result->getMessage() ) );
+ }
+ }
+
+ public function onSuccess() {
+ $this->getOutput()->addWikiMsg( 'passwordreset-emailsent' );
+ $this->getOutput()->returnToMain();
+ }
+
+ function canChangePassword(User $user) {
+ global $wgPasswordResetRoutes, $wgAuth;
+
+ // Maybe password resets are disabled, or there are no allowable routes
+ if ( !is_array( $wgPasswordResetRoutes ) ||
+ !in_array( true, array_values( $wgPasswordResetRoutes ) ) ) {
+ return 'passwordreset-disabled';
+ }
+
+ // Maybe the external auth plugin won't allow local password changes
+ if ( !$wgAuth->allowPasswordChange() ) {
+ return 'resetpass_forbidden';
+ }
+
+ // Maybe the user is blocked (check this here rather than relying on the parent
+ // method as we have a more specific error message to use here
+ if ( $user->isBlocked() ) {
+ return 'blocked-mailpassword';
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Hide the password reset page if resets are disabled.
+ * @return Bool
+ */
+ function isListed() {
+ global $wgUser;
+
+ if ( $this->canChangePassword( $wgUser ) === true ) {
+ return parent::isListed();
+ }
+
+ return false;
+ }
+} \ No newline at end of file
diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php
index 375cefdf..7c7190ad 100644
--- a/includes/specials/SpecialPopularpages.php
+++ b/includes/specials/SpecialPopularpages.php
@@ -28,8 +28,8 @@
*/
class PopularPagesPage extends QueryPage {
- function getName() {
- return "Popularpages";
+ function __construct( $name = 'Popularpages' ) {
+ parent::__construct( $name );
}
function isExpensive() {
@@ -38,31 +38,21 @@ class PopularPagesPage extends QueryPage {
}
function isSyndicated() { return false; }
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- $page = $dbr->tableName( 'page' );
-
- $query =
- "SELECT 'Popularpages' as type,
- page_namespace as namespace,
- page_title as title,
- page_counter as value
- FROM $page ";
- $where =
- "WHERE page_is_redirect=0 AND page_namespace";
-
- global $wgContentNamespaces;
- if( empty( $wgContentNamespaces ) ) {
- $where .= '='.NS_MAIN;
- } else if( count( $wgContentNamespaces ) > 1 ) {
- $where .= ' in (' . implode( ', ', $wgContentNamespaces ) . ')';
- } else {
- $where .= '='.$wgContentNamespaces[0];
- }
-
- return $query . $where;
+ function getQueryInfo() {
+ return array (
+ 'tables' => array( 'page' ),
+ 'fields' => array( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'page_counter AS value'),
+ 'conds' => array( 'page_is_redirect' => 0,
+ 'page_namespace' => MWNamespace::getContentNamespaces() ) );
}
+ /**
+ * @param $skin Skin
+ * @param $result
+ * @return string
+ */
function formatResult( $skin, $result ) {
global $wgLang, $wgContLang;
$title = Title::makeTitle( $result->namespace, $result->title );
@@ -78,14 +68,3 @@ class PopularPagesPage extends QueryPage {
return wfSpecialList($link, $nv);
}
}
-
-/**
- * Constructor
- */
-function wfSpecialPopularpages() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $ppp = new PopularPagesPage();
-
- return $ppp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php
index e63aeee6..edc26bc1 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -52,7 +52,6 @@ class SpecialPreferences extends SpecialPage {
return;
}
- $wgOut->addModules( 'mediawiki.legacy.prefs' );
$wgOut->addModules( 'mediawiki.special.preferences' );
if ( $wgRequest->getCheck( 'success' ) ) {
@@ -61,7 +60,7 @@ class SpecialPreferences extends SpecialPage {
'savedprefs'
);
}
-
+
if ( $wgRequest->getCheck( 'eauth' ) ) {
$wgOut->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>",
'eauthentsent', $wgUser->getName() );
@@ -78,7 +77,7 @@ class SpecialPreferences extends SpecialPage {
$wgOut->addWikiMsg( 'prefs-reset-intro' );
- $htmlForm = new HTMLForm( array(), 'prefs-restore' );
+ $htmlForm = new HTMLForm( array(), $this->getContext(), 'prefs-restore' );
$htmlForm->setSubmitText( wfMsg( 'restoreprefs' ) );
$htmlForm->setTitle( $this->getTitle( 'reset' ) );
diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php
index 09e7734c..28be4daf 100644
--- a/includes/specials/SpecialPrefixindex.php
+++ b/includes/specials/SpecialPrefixindex.php
@@ -28,11 +28,11 @@
*/
class SpecialPrefixindex extends SpecialAllpages {
// Inherit $maxPerPage
-
+
function __construct(){
parent::__construct( 'Prefixindex' );
}
-
+
/**
* Entry point : initialise variables and call subfunctions.
* @param $par String: becomes "FOO" when called like Special:Prefixindex/FOO (default null)
@@ -42,34 +42,39 @@ class SpecialPrefixindex extends SpecialAllpages {
$this->setHeaders();
$this->outputHeader();
+ $wgOut->addModuleStyles( 'mediawiki.special' );
# GET values
$from = $wgRequest->getVal( 'from', '' );
$prefix = $wgRequest->getVal( 'prefix', '' );
- $namespace = $wgRequest->getInt( 'namespace' );
- $namespaces = $wgContLang->getNamespaces();
+ $ns = $wgRequest->getIntOrNull( 'namespace' );
+ $namespace = (int)$ns; // if no namespace given, use 0 (NS_MAIN).
- $wgOut->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) )
- ? wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) )
- : wfMsg( 'prefixindex' )
+ $namespaces = $wgContLang->getNamespaces();
+ $wgOut->setPagetitle(
+ ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) )
+ ? wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) )
+ : wfMsg( 'prefixindex' )
);
$showme = '';
- if( isset( $par ) ){
+ if( isset( $par ) ) {
$showme = $par;
- } elseif( $prefix != '' ){
+ } elseif( $prefix != '' ) {
$showme = $prefix;
- } elseif( $from != '' ){
+ } elseif( $from != '' ) {
// For back-compat with Special:Allpages
$showme = $from;
}
- if ($showme != '' || $namespace) {
+
+ // Bug 27864: if transcluded, show all pages instead of the form.
+ if ( $this->including() || $showme != '' || $ns !== null ) {
$this->showPrefixChunk( $namespace, $showme, $from );
} else {
$wgOut->addHTML( $this->namespacePrefixForm( $namespace, null ) );
}
}
-
+
/**
* HTML for the top form
* @param $namespace Integer: a namespace constant (default NS_MAIN).
@@ -115,9 +120,9 @@ class SpecialPrefixindex extends SpecialAllpages {
* @param $from String: list all pages from this name (default FALSE)
*/
function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null ) {
- global $wgOut, $wgUser, $wgContLang, $wgLang;
+ global $wgOut, $wgContLang, $wgLang;
- $sk = $wgUser->getSkin();
+ $sk = $this->getSkin();
if (!isset($from)) $from = $prefix;
@@ -126,7 +131,7 @@ class SpecialPrefixindex extends SpecialAllpages {
$namespaces = $wgContLang->getNamespaces();
if ( !$prefixList || !$fromList ) {
- $out = wfMsgWikiHtml( 'allpagesbadtitle' );
+ $out = wfMsgExt( 'allpagesbadtitle', 'parse' );
} elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
// Show errormessage and reset to NS_MAIN
$out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace );
@@ -135,7 +140,7 @@ class SpecialPrefixindex extends SpecialAllpages {
list( $namespace, $prefixKey, $prefix ) = $prefixList;
list( /* $fromNS */, $fromKey, ) = $fromList;
- ### FIXME: should complain if $fromNs != $namespace
+ ### @todo FIXME: Should complain if $fromNs != $namespace
$dbr = wfGetDB( DB_SLAVE );
@@ -154,12 +159,12 @@ class SpecialPrefixindex extends SpecialAllpages {
)
);
- ### FIXME: side link to previous
+ ### @todo FIXME: Side link to previous
$n = 0;
if( $res->numRows() > 0 ) {
$out = Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-prefixindex-list-table' ) );
-
+
while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
$t = Title::makeTitle( $s->page_namespace, $s->page_title );
if( $t ) {
@@ -190,6 +195,7 @@ class SpecialPrefixindex extends SpecialAllpages {
}
}
+ $footer = '';
if ( $this->including() ) {
$out2 = '';
} else {
@@ -200,8 +206,7 @@ class SpecialPrefixindex extends SpecialAllpages {
<td>' .
$nsForm .
'</td>
- <td id="mw-prefixindex-nav-form">' .
- $sk->linkKnown( $self, wfMsgHtml( 'allpages' ) );
+ <td id="mw-prefixindex-nav-form" class="mw-prefixindex-nav">';
if( isset( $res ) && $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
$query = array(
@@ -213,20 +218,21 @@ class SpecialPrefixindex extends SpecialAllpages {
$query['namespace'] = $namespace;
}
- $out2 = $wgLang->pipeList( array(
- $out2,
- $sk->linkKnown(
+ $nextLink = Linker::linkKnown(
$self,
wfMsgHtml( 'nextpage', str_replace( '_',' ', htmlspecialchars( $s->page_title ) ) ),
array(),
$query
- )
- ) );
+ );
+ $out2 .= $nextLink;
+
+ $footer = "\n" . Html::element( "hr" )
+ . Html::rawElement( "div", array( "class" => "mw-prefixindex-nav" ), $nextLink );
}
$out2 .= "</td></tr>" .
Xml::closeElement( 'table' );
}
- $wgOut->addHTML( $out2 . $out );
+ $this->getOutput()->addHTML( $out2 . $out . $footer );
}
}
diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php
index c676aa00..b1f61f09 100644
--- a/includes/specials/SpecialProtectedpages.php
+++ b/includes/specials/SpecialProtectedpages.php
@@ -76,14 +76,16 @@ class SpecialProtectedpages extends SpecialPage {
* @return string Formatted <li> element
*/
public function formatRow( $row ) {
- global $wgUser, $wgLang, $wgContLang;
+ global $wgUser, $wgLang;
wfProfileIn( __METHOD__ );
- static $skin=null;
+ static $skin = null, $infinity = null;
- if( is_null( $skin ) )
+ if( is_null( $skin ) ){
$skin = $wgUser->getSkin();
+ $infinity = wfGetDB( DB_SLAVE )->getInfinity();
+ }
$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
$link = $skin->link( $title );
@@ -100,17 +102,21 @@ class SpecialProtectedpages extends SpecialPage {
$stxt = '';
- if( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) {
- $expiry = Block::decodeExpiry( $row->pr_expiry );
+ $expiry = $wgLang->formatExpiry( $row->pr_expiry, TS_MW );
+ if( $expiry != $infinity ) {
- $expiry_description = wfMsg( 'protect-expiring' , $wgLang->timeanddate( $expiry ) ,
- $wgLang->date( $expiry ) , $wgLang->time( $expiry ) );
+ $expiry_description = wfMsg(
+ 'protect-expiring',
+ $wgLang->timeanddate( $expiry ),
+ $wgLang->date( $expiry ),
+ $wgLang->time( $expiry )
+ );
$description_items[] = htmlspecialchars($expiry_description);
}
if(!is_null($size = $row->page_len)) {
- $stxt = $wgContLang->getDirMark() . ' ' . $skin->formatRevisionSize( $size );
+ $stxt = $wgLang->getDirMark() . ' ' . $skin->formatRevisionSize( $size );
}
# Show a link to the change protection form for allowed users otherwise a link to the protection log
@@ -139,7 +145,7 @@ class SpecialProtectedpages extends SpecialPage {
return Html::rawElement(
'li',
array(),
- wfSpecialList( $link . $stxt, $wgLang->commaList( $description_items ) ) . $changeProtection ) . "\n";
+ wfSpecialList( $link . $stxt, $wgLang->commaList( $description_items ), false ) . $changeProtection ) . "\n";
}
/**
@@ -193,7 +199,7 @@ class SpecialProtectedpages extends SpecialPage {
return
Xml::checkLabel( wfMsg('protectedpages-indef'), 'indefonly', 'indefonly', $indefOnly ) . "\n";
}
-
+
/**
* @return string Formatted HTML
*/
@@ -288,7 +294,7 @@ class ProtectedPagesPager extends AlphabeticPager {
public $mForm, $mConds;
private $type, $level, $namespace, $sizetype, $size, $indefonly;
- function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0,
+ function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0,
$indefonly = false, $cascadeonly = false )
{
$this->mForm = $form;
@@ -313,6 +319,10 @@ class ProtectedPagesPager extends AlphabeticPager {
return '';
}
+ function getTitle() {
+ return SpecialPage::getTitleFor( 'Protectedpages' );
+ }
+
function formatRow( $row ) {
return $this->mForm->formatRow( $row );
}
@@ -323,15 +333,16 @@ class ProtectedPagesPager extends AlphabeticPager {
'OR pr_expiry IS NULL)';
$conds[] = 'page_id=pr_page';
$conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type );
-
+
if( $this->sizetype=='min' ) {
$conds[] = 'page_len>=' . $this->size;
- } else if( $this->sizetype=='max' ) {
+ } elseif( $this->sizetype=='max' ) {
$conds[] = 'page_len<=' . $this->size;
}
if( $this->indefonly ) {
- $conds[] = "pr_expiry = 'infinity' OR pr_expiry IS NULL";
+ $db = wfGetDB( DB_SLAVE );
+ $conds[] = "pr_expiry = {$db->addQuotes( $db->getInfinity() )} OR pr_expiry IS NULL";
}
if( $this->cascadeonly ) {
$conds[] = "pr_cascade = '1'";
diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php
index 5b18d87f..5fb91af7 100644
--- a/includes/specials/SpecialProtectedtitles.php
+++ b/includes/specials/SpecialProtectedtitles.php
@@ -70,16 +70,20 @@ class SpecialProtectedtitles extends SpecialPage {
/**
* Callback function to output a restriction
+ *
+ * @return string
*/
function formatRow( $row ) {
- global $wgUser, $wgLang;
+ global $wgLang;
wfProfileIn( __METHOD__ );
- static $skin=null;
+ static $skin = null, $infinity = null;
- if( is_null( $skin ) )
- $skin = $wgUser->getSkin();
+ if( is_null( $skin ) ){
+ $skin = $this->getSkin();
+ $infinity = wfGetDB( DB_SLAVE )->getInfinity();
+ }
$title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title );
$link = $skin->link( $title );
@@ -90,19 +94,17 @@ class SpecialProtectedtitles extends SpecialPage {
$description_items[] = $protType;
- $stxt = '';
-
- if ( $row->pt_expiry != 'infinity' && strlen($row->pt_expiry) ) {
- $expiry = Block::decodeExpiry( $row->pt_expiry );
+ $expiry = strlen( $row->pt_expiry ) ? $wgLang->formatExpiry( $row->pt_expiry, TS_MW ) : $infinity;
+ if( $expiry != $infinity ) {
$expiry_description = wfMsg( 'protect-expiring', $wgLang->timeanddate( $expiry ) , $wgLang->date( $expiry ) , $wgLang->time( $expiry ) );
- $description_items[] = $expiry_description;
+ $description_items[] = htmlspecialchars($expiry_description);
}
wfProfileOut( __METHOD__ );
- return '<li>' . wfSpecialList( $link . $stxt, implode( $description_items, ', ' ) ) . "</li>\n";
+ return '<li>' . wfSpecialList( $link, implode( $description_items, ', ' ) ) . "</li>\n";
}
/**
@@ -205,6 +207,10 @@ class ProtectedTitlesPager extends AlphabeticPager {
return '';
}
+ function getTitle() {
+ return SpecialPage::getTitleFor( 'Protectedtitles' );
+ }
+
function formatRow( $row ) {
return $this->mForm->formatRow( $row );
}
diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php
index 6299f384..e299dc77 100644
--- a/includes/specials/SpecialRandompage.php
+++ b/includes/specials/SpecialRandompage.php
@@ -43,7 +43,9 @@ class RandomPage extends SpecialPage {
}
public function setNamespace ( $ns ) {
- if( !$ns || $ns < NS_MAIN ) $ns = NS_MAIN;
+ if( !$ns || $ns < NS_MAIN ) {
+ $ns = NS_MAIN;
+ }
$this->namespaces = array( $ns );
}
@@ -63,7 +65,7 @@ class RandomPage extends SpecialPage {
if( is_null( $title ) ) {
$this->setHeaders();
- $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages',
+ $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages',
$this->getNsList(), count( $this->namespaces ) );
return;
}
@@ -83,15 +85,15 @@ class RandomPage extends SpecialPage {
global $wgContLang;
$nsNames = array();
foreach( $this->namespaces as $n ) {
- if( $n === NS_MAIN )
- $nsNames[] = wfMsgForContent( 'blanknamespace' );
- else
+ if( $n === NS_MAIN ) {
+ $nsNames[] = wfMsgNoTrans( 'blanknamespace' );
+ } else {
$nsNames[] = $wgContLang->getNsText( $n );
+ }
}
return $wgContLang->commaList( $nsNames );
}
-
/**
* Choose a random title.
* @return Title object (or null if nothing to choose from)
@@ -99,7 +101,8 @@ class RandomPage extends SpecialPage {
public function getRandomTitle() {
$randstr = wfRandom();
$title = null;
- if ( !wfRunHooks( 'SpecialRandomGetRandomTitle', array( &$randstr, &$this->isRedir, &$this->namespaces, &$this->extra, &$title ) ) ) {
+ if ( !wfRunHooks( 'SpecialRandomGetRandomTitle', array( &$randstr, &$this->isRedir, &$this->namespaces,
+ &$this->extra, &$title ) ) ) {
return $title;
}
$row = $this->selectRandomPageFromDB( $randstr );
@@ -111,53 +114,50 @@ class RandomPage extends SpecialPage {
* any more bias than what the page_random scheme
* causes anyway. Trust me, I'm a mathematician. :)
*/
- if( !$row )
+ if( !$row ) {
$row = $this->selectRandomPageFromDB( "0" );
+ }
- if( $row )
+ if( $row ) {
return Title::makeTitleSafe( $row->page_namespace, $row->page_title );
- else
+ } else {
return null;
+ }
+ }
+
+ protected function getQueryInfo( $randstr ) {
+ $redirect = $this->isRedirect() ? 1 : 0;
+
+ return array(
+ 'tables' => array( 'page' ),
+ 'fields' => array( 'page_title', 'page_namespace' ),
+ 'conds' => array_merge( array(
+ 'page_namespace' => $this->namespaces,
+ 'page_is_redirect' => $redirect,
+ 'page_random >= ' . $randstr
+ ), $this->extra ),
+ 'options' => array(
+ 'ORDER BY' => 'page_random',
+ 'USE INDEX' => 'page_random',
+ 'LIMIT' => 1,
+ ),
+ 'join_conds' => array()
+ );
}
- private function selectRandomPageFromDB( $randstr ) {
- global $wgExtraRandompageSQL;
+ private function selectRandomPageFromDB( $randstr, $fname = __METHOD__ ) {
$dbr = wfGetDB( DB_SLAVE );
- $use_index = $dbr->useIndexClause( 'page_random' );
- $page = $dbr->tableName( 'page' );
+ $query = $this->getQueryInfo( $randstr );
+ $res = $dbr->select(
+ $query['tables'],
+ $query['fields'],
+ $query['conds'],
+ $fname,
+ $query['options'],
+ $query['join_conds']
+ );
- $ns = implode( ",", $this->namespaces );
- $redirect = $this->isRedirect() ? 1 : 0;
-
- if ( $wgExtraRandompageSQL ) {
- $this->extra[] = $wgExtraRandompageSQL;
- }
- if ( $this->addExtraSQL() ) {
- $this->extra[] = $this->addExtraSQL();
- }
- $extra = '';
- if ( $this->extra ) {
- $extra = 'AND (' . implode( ') AND (', $this->extra ) . ')';
- }
- $sql = "SELECT page_title, page_namespace
- FROM $page $use_index
- WHERE page_namespace IN ( $ns )
- AND page_is_redirect = $redirect
- AND page_random >= $randstr
- $extra
- ORDER BY page_random";
-
- $sql = $dbr->limitResult( $sql, 1, 0 );
- $res = $dbr->query( $sql, __METHOD__ );
return $dbr->fetchObject( $res );
}
-
- /* an alternative to $wgExtraRandompageSQL so subclasses
- * can add their own SQL by overriding this function
- * @deprecated, append to $this->extra instead
- */
- public function addExtraSQL() {
- return '';
- }
}
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
index c012beca..6c78ced0 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -28,6 +28,7 @@
*/
class SpecialRecentChanges extends IncludableSpecialPage {
var $rcOptions, $rcSubpage;
+ protected $customFilters;
public function __construct( $name = 'Recentchanges' ) {
parent::__construct( $name );
@@ -39,22 +40,22 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return FormOptions
*/
public function getDefaultOptions() {
- global $wgUser;
$opts = new FormOptions();
- $opts->add( 'days', (int)$wgUser->getOption( 'rcdays' ) );
- $opts->add( 'limit', (int)$wgUser->getOption( 'rclimit' ) );
+ $opts->add( 'days', (int)$this->getUser()->getOption( 'rcdays' ) );
+ $opts->add( 'limit', (int)$this->getUser()->getOption( 'rclimit' ) );
$opts->add( 'from', '' );
- $opts->add( 'hideminor', $wgUser->getBoolOption( 'hideminor' ) );
+ $opts->add( 'hideminor', $this->getUser()->getBoolOption( 'hideminor' ) );
$opts->add( 'hidebots', true );
$opts->add( 'hideanons', false );
$opts->add( 'hideliu', false );
- $opts->add( 'hidepatrolled', $wgUser->getBoolOption( 'hidepatrolled' ) );
+ $opts->add( 'hidepatrolled', $this->getUser()->getBoolOption( 'hidepatrolled' ) );
$opts->add( 'hidemyself', false );
$opts->add( 'namespace', '', FormOptions::INTNULL );
$opts->add( 'invert', false );
+ $opts->add( 'associated', false );
$opts->add( 'categories', '' );
$opts->add( 'categories_any', false );
@@ -65,13 +66,20 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Create a FormOptions object with options as specified by the user
*
+ * @param $parameters array
+ *
* @return FormOptions
*/
public function setup( $parameters ) {
- global $wgRequest;
-
$opts = $this->getDefaultOptions();
- $opts->fetchValuesFromRequest( $wgRequest );
+
+ $this->customFilters = array();
+ wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ) );
+ foreach( $this->customFilters as $key => $params ) {
+ $opts->add( $key, $params['default'] );
+ }
+
+ $opts->fetchValuesFromRequest( $this->getRequest() );
// Give precedence to subpage syntax
if( $parameters !== null ) {
@@ -88,10 +96,10 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return FormOptions
*/
public function feedSetup() {
- global $wgFeedLimit, $wgRequest;
+ global $wgFeedLimit;
$opts = $this->getDefaultOptions();
# Feed is cached on limit,hideminor,namespace; other params would randomly not work
- $opts->fetchValuesFromRequest( $wgRequest, array( 'limit', 'hideminor', 'namespace' ) );
+ $opts->fetchValuesFromRequest( $this->getRequest(), array( 'limit', 'hideminor', 'namespace' ) );
$opts->validateIntBounds( 'limit', 0, $wgFeedLimit );
return $opts;
}
@@ -101,9 +109,12 @@ class SpecialRecentChanges extends IncludableSpecialPage {
*/
public function getOptions() {
if ( $this->rcOptions === null ) {
- global $wgRequest;
- $feedFormat = $wgRequest->getVal( 'feed' );
- $this->rcOptions = $feedFormat ? $this->feedSetup() : $this->setup( $this->rcSubpage );
+ if ( $this->including() ) {
+ $isFeed = false;
+ } else {
+ $isFeed = (bool)$this->getRequest()->getVal( 'feed' );
+ }
+ $this->rcOptions = $isFeed ? $this->feedSetup() : $this->setup( $this->rcSubpage );
}
return $this->rcOptions;
}
@@ -115,12 +126,11 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @param $subpage String
*/
public function execute( $subpage ) {
- global $wgRequest, $wgOut;
$this->rcSubpage = $subpage;
- $feedFormat = $wgRequest->getVal( 'feed' );
+ $feedFormat = $this->including() ? null : $this->getRequest()->getVal( 'feed' );
# 10 seconds server-side caching max
- $wgOut->setSquidMaxage( 10 );
+ $this->getOutput()->setSquidMaxage( 10 );
# Check if the client has a cached version
$lastmod = $this->checkLastModified( $feedFormat );
if( $lastmod === false ) {
@@ -130,6 +140,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$opts = $this->getOptions();
$this->setHeaders();
$this->outputHeader();
+ $this->addRecentChangesJS();
// Fetch results, prepare a batch link existence check query
$conds = $this->buildMainQueryConds( $opts );
@@ -144,8 +155,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
if( !$feedFormat ) {
$batch = new LinkBatch;
foreach( $rows as $row ) {
- $batch->add( NS_USER, $row->rc_user_text );
- $batch->add( NS_USER_TALK, $row->rc_user_text );
+ $batch->add( NS_USER, $row->rc_user_text );
+ $batch->add( NS_USER_TALK, $row->rc_user_text );
$batch->add( $row->rc_namespace, $row->rc_title );
}
$batch->execute();
@@ -169,7 +180,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' );
$formatter = $changesFeed->getFeedObject(
wfMsgForContent( 'recentchanges' ),
- wfMsgForContent( 'recentchanges-feed-description' )
+ wfMsgForContent( 'recentchanges-feed-description' ),
+ $this->getTitle()->getFullURL()
);
return array( $changesFeed, $formatter );
}
@@ -184,20 +196,42 @@ class SpecialRecentChanges extends IncludableSpecialPage {
public function parseParameters( $par, FormOptions $opts ) {
$bits = preg_split( '/\s*,\s*/', trim( $par ) );
foreach( $bits as $bit ) {
- if( 'hidebots' === $bit ) $opts['hidebots'] = true;
- if( 'bots' === $bit ) $opts['hidebots'] = false;
- if( 'hideminor' === $bit ) $opts['hideminor'] = true;
- if( 'minor' === $bit ) $opts['hideminor'] = false;
- if( 'hideliu' === $bit ) $opts['hideliu'] = true;
- if( 'hidepatrolled' === $bit ) $opts['hidepatrolled'] = true;
- if( 'hideanons' === $bit ) $opts['hideanons'] = true;
- if( 'hidemyself' === $bit ) $opts['hidemyself'] = true;
+ if( 'hidebots' === $bit ) {
+ $opts['hidebots'] = true;
+ }
+ if( 'bots' === $bit ) {
+ $opts['hidebots'] = false;
+ }
+ if( 'hideminor' === $bit ) {
+ $opts['hideminor'] = true;
+ }
+ if( 'minor' === $bit ) {
+ $opts['hideminor'] = false;
+ }
+ if( 'hideliu' === $bit ) {
+ $opts['hideliu'] = true;
+ }
+ if( 'hidepatrolled' === $bit ) {
+ $opts['hidepatrolled'] = true;
+ }
+ if( 'hideanons' === $bit ) {
+ $opts['hideanons'] = true;
+ }
+ if( 'hidemyself' === $bit ) {
+ $opts['hidemyself'] = true;
+ }
- if( is_numeric( $bit ) ) $opts['limit'] = $bit;
+ if( is_numeric( $bit ) ) {
+ $opts['limit'] = $bit;
+ }
$m = array();
- if( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) $opts['limit'] = $m[1];
- if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) $opts['days'] = $m[1];
+ if( preg_match( '/^limit=(\d+)$/', $bit, $m ) ) {
+ $opts['limit'] = $m[1];
+ }
+ if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) {
+ $opts['days'] = $m[1];
+ }
}
}
@@ -210,11 +244,10 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return String or false
*/
public function checkLastModified( $feedFormat ) {
- global $wgUseRCPatrol, $wgOut;
$dbr = wfGetDB( DB_SLAVE );
$lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ );
- if( $feedFormat || !$wgUseRCPatrol ) {
- if( $lastmod && $wgOut->checkLastModified( $lastmod ) ) {
+ if( $feedFormat || !$this->getUser()->useRCPatrol() ) {
+ if( $lastmod && $this->getOutput()->checkLastModified( $lastmod ) ) {
# Client cache fresh and headers sent, nothing more to do.
return false;
}
@@ -229,8 +262,6 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return array
*/
public function buildMainQueryConds( FormOptions $opts ) {
- global $wgUser;
-
$dbr = wfGetDB( DB_SLAVE );
$conds = array();
@@ -261,35 +292,58 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$conds[] = 'rc_timestamp >= ' . $dbr->addQuotes( $cutoff );
-
- $hidePatrol = $wgUser->useRCPatrol() && $opts['hidepatrolled'];
+ $hidePatrol = $this->getUser()->useRCPatrol() && $opts['hidepatrolled'];
$hideLoggedInUsers = $opts['hideliu'] && !$forcebot;
$hideAnonymousUsers = $opts['hideanons'] && !$forcebot;
- if( $opts['hideminor'] ) $conds['rc_minor'] = 0;
- if( $opts['hidebots'] ) $conds['rc_bot'] = 0;
- if( $hidePatrol ) $conds['rc_patrolled'] = 0;
- if( $forcebot ) $conds['rc_bot'] = 1;
- if( $hideLoggedInUsers ) $conds[] = 'rc_user = 0';
- if( $hideAnonymousUsers ) $conds[] = 'rc_user != 0';
+ if( $opts['hideminor'] ) {
+ $conds['rc_minor'] = 0;
+ }
+ if( $opts['hidebots'] ) {
+ $conds['rc_bot'] = 0;
+ }
+ if( $hidePatrol ) {
+ $conds['rc_patrolled'] = 0;
+ }
+ if( $forcebot ) {
+ $conds['rc_bot'] = 1;
+ }
+ if( $hideLoggedInUsers ) {
+ $conds[] = 'rc_user = 0';
+ }
+ if( $hideAnonymousUsers ) {
+ $conds[] = 'rc_user != 0';
+ }
if( $opts['hidemyself'] ) {
- if( $wgUser->getId() ) {
- $conds[] = 'rc_user != ' . $dbr->addQuotes( $wgUser->getId() );
+ if( $this->getUser()->getId() ) {
+ $conds[] = 'rc_user != ' . $dbr->addQuotes( $this->getUser()->getId() );
} else {
- $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $wgUser->getName() );
+ $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $this->getUser()->getName() );
}
}
# Namespace filtering
if( $opts['namespace'] !== '' ) {
- if( !$opts['invert'] ) {
- $conds[] = 'rc_namespace = ' . $dbr->addQuotes( $opts['namespace'] );
+ $selectedNS = $dbr->addQuotes( $opts['namespace'] );
+ $operator = $opts['invert'] ? '!=' : '=';
+ $boolean = $opts['invert'] ? 'AND' : 'OR';
+
+ # namespace association (bug 2429)
+ if( !$opts['associated'] ) {
+ $condition = "rc_namespace $operator $selectedNS";
} else {
- $conds[] = 'rc_namespace != ' . $dbr->addQuotes( $opts['namespace'] );
+ # Also add the associated namespace
+ $associatedNS = $dbr->addQuotes(
+ MWNamespace::getAssociated( $opts['namespace'] )
+ );
+ $condition = "(rc_namespace $operator $selectedNS "
+ . $boolean
+ . " rc_namespace $operator $associatedNS)";
}
- }
+ $conds[] = $condition;
+ }
return $conds;
}
@@ -301,75 +355,94 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return database result or false (for Recentchangeslinked only)
*/
public function doMainQuery( $conds, $opts ) {
- global $wgUser;
-
$tables = array( 'recentchanges' );
$join_conds = array();
- $query_options = array( 'USE INDEX' => array('recentchanges' => 'rc_timestamp') );
+ $query_options = array(
+ 'USE INDEX' => array( 'recentchanges' => 'rc_timestamp' )
+ );
- $uid = $wgUser->getId();
+ $uid = $this->getUser()->getId();
$dbr = wfGetDB( DB_SLAVE );
$limit = $opts['limit'];
$namespace = $opts['namespace'];
- $select = '*';
$invert = $opts['invert'];
+ $associated = $opts['associated'];
+ $fields = array( $dbr->tableName( 'recentchanges' ) . '.*' ); // all rc columns
// JOIN on watchlist for users
- if( $uid ) {
+ if ( $uid ) {
$tables[] = 'watchlist';
+ $fields[] = 'wl_user';
+ $fields[] = 'wl_notificationtimestamp';
$join_conds['watchlist'] = array('LEFT JOIN',
"wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace");
}
- if ($wgUser->isAllowed("rollback")) {
+ if ( $this->getUser()->isAllowed( 'rollback' ) ) {
$tables[] = 'page';
+ $fields[] = 'page_latest';
$join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id');
}
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']
+ $tables, $fields, $conds, $join_conds, $query_options,
+ $opts['tagfilter']
);
}
- if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$select ) ) )
+ if ( !wfRunHooks( 'SpecialRecentChangesQuery',
+ array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$fields ) ) )
+ {
return false;
+ }
// Don't use the new_namespace_time timestamp index if:
// (a) "All namespaces" selected
- // (b) We want all pages NOT in a certain namespaces (inverted)
+ // (b) We want pages in more than one namespace (inverted/associated)
// (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 && !is_null( $namespace ) )
+ if( $namespace === ''
+ || ( $invert || $associated )
|| $opts['tagfilter'] != ''
|| !$dbr->unionSupportsOrderAndLimit() )
{
- $res = $dbr->select( $tables, '*', $conds, __METHOD__,
+ $res = $dbr->select( $tables, $fields, $conds, __METHOD__,
array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) +
$query_options,
$join_conds );
// We have a new_namespace_time index! UNION over new=(0,1) and sort result set!
} else {
// New pages
- $sqlNew = $dbr->selectSQLText( $tables, $select,
+ $sqlNew = $dbr->selectSQLText(
+ $tables,
+ $fields,
array( 'rc_new' => 1 ) + $conds,
__METHOD__,
- array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
- 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ),
- $join_conds );
+ array(
+ 'ORDER BY' => 'rc_timestamp DESC',
+ 'LIMIT' => $limit,
+ 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' )
+ ),
+ $join_conds
+ );
// Old pages
- $sqlOld = $dbr->selectSQLText( $tables, '*',
+ $sqlOld = $dbr->selectSQLText(
+ $tables,
+ $fields,
array( 'rc_new' => 0 ) + $conds,
__METHOD__,
- array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
- 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ),
- $join_conds );
+ array(
+ 'ORDER BY' => 'rc_timestamp DESC',
+ 'LIMIT' => $limit,
+ 'USE INDEX' => array( 'recentchanges' => 'new_name_timestamp' )
+ ),
+ $join_conds
+ );
# Join the two fast queries, and sort the result set
- $sql = $dbr->unionQueries(array($sqlNew, $sqlOld), false).' ORDER BY rc_timestamp DESC';
- $sql = $dbr->limitResult($sql, $limit, false);
+ $sql = $dbr->unionQueries( array( $sqlNew, $sqlOld ), false ) .
+ ' ORDER BY rc_timestamp DESC';
+ $sql = $dbr->limitResult( $sql, $limit, false );
$res = $dbr->query( $sql, __METHOD__ );
}
@@ -377,14 +450,13 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
- * Send output to $wgOut, only called if not used feeds
+ * Send output to the OutputPage object, only called if not used feeds
*
* @param $rows Array of database rows
* @param $opts FormOptions
*/
public function webOutput( $rows, $opts ) {
- global $wgOut, $wgUser, $wgRCShowWatchingUsers, $wgShowUpdatedMarker;
- global $wgAllowCategorizedRecentChanges;
+ global $wgRCShowWatchingUsers, $wgShowUpdatedMarker, $wgAllowCategorizedRecentChanges;
$limit = $opts['limit'];
@@ -394,43 +466,47 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
// And now for the content
- $wgOut->setFeedAppendQuery( $this->getFeedQuery() );
+ $this->getOutput()->setFeedAppendQuery( $this->getFeedQuery() );
if( $wgAllowCategorizedRecentChanges ) {
$this->filterByCategories( $rows, $opts );
}
- $showWatcherCount = $wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' );
+ $showWatcherCount = $wgRCShowWatchingUsers && $this->getUser()->getOption( 'shownumberswatching' );
$watcherCache = array();
$dbr = wfGetDB( DB_SLAVE );
$counter = 1;
- $list = ChangesList::newFromUser( $wgUser );
+ $list = ChangesList::newFromContext( $this->getContext() );
$s = $list->beginRecentChangesList();
foreach( $rows as $obj ) {
- if( $limit == 0 ) break;
+ if( $limit == 0 ) {
+ break;
+ }
$rc = RecentChange::newFromRow( $obj );
$rc->counter = $counter++;
# Check if the page has been updated since the last visit
- if( $wgShowUpdatedMarker && !empty($obj->wl_notificationtimestamp) ) {
- $rc->notificationtimestamp = ($obj->rc_timestamp >= $obj->wl_notificationtimestamp);
+ if( $wgShowUpdatedMarker && !empty( $obj->wl_notificationtimestamp ) ) {
+ $rc->notificationtimestamp = ( $obj->rc_timestamp >= $obj->wl_notificationtimestamp );
} else {
$rc->notificationtimestamp = false; // Default
}
# Check the number of users watching the page
$rc->numberofWatchingusers = 0; // Default
if( $showWatcherCount && $obj->rc_namespace >= 0 ) {
- if( !isset($watcherCache[$obj->rc_namespace][$obj->rc_title]) ) {
+ if( !isset( $watcherCache[$obj->rc_namespace][$obj->rc_title] ) ) {
$watcherCache[$obj->rc_namespace][$obj->rc_title] =
- $dbr->selectField( 'watchlist',
+ $dbr->selectField(
+ 'watchlist',
'COUNT(*)',
array(
'wl_namespace' => $obj->rc_namespace,
'wl_title' => $obj->rc_title,
),
- __METHOD__ . '-watchers' );
+ __METHOD__ . '-watchers'
+ );
}
$rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title];
}
@@ -438,7 +514,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
--$limit;
}
$s .= $list->endRecentChangesList();
- $wgOut->addHTML( $s );
+ $this->getOutput()->addHTML( $s );
}
/**
@@ -456,14 +532,16 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return String: XHTML
*/
public function doHeader( $opts ) {
- global $wgScript, $wgOut;
+ global $wgScript;
- $this->setTopText( $wgOut, $opts );
+ $this->setTopText( $opts );
$defaults = $opts->getAllValues();
$nondefaults = $opts->getChangedValues();
- $opts->consumeValues( array( 'namespace', 'invert', 'tagfilter',
- 'categories', 'categories_any' ) );
+ $opts->consumeValues( array(
+ 'namespace', 'invert', 'associated', 'tagfilter',
+ 'categories', 'categories_any'
+ ) );
$panel = array();
$panel[] = $this->optionsPanel( $defaults, $nondefaults );
@@ -502,11 +580,11 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$panel[] = $form;
$panelString = implode( "\n", $panel );
- $wgOut->addHTML(
+ $this->getOutput()->addHTML(
Xml::fieldset( wfMsg( 'recentchanges-legend' ), $panelString, array( 'class' => 'rcoptions' ) )
);
- $this->setBottomText( $wgOut, $opts );
+ $this->setBottomText( $opts );
}
/**
@@ -515,7 +593,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @param $opts FormOptions
* @return Array
*/
- function getExtraOptions( $opts ){
+ function getExtraOptions( $opts ) {
$extraOpts = array();
$extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
@@ -525,8 +603,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
$tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] );
- if ( count($tagFilter) )
+ if ( count( $tagFilter ) ) {
$extraOpts['tagfilter'] = $tagFilter;
+ }
wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) );
return $extraOpts;
@@ -535,33 +614,41 @@ class SpecialRecentChanges extends IncludableSpecialPage {
/**
* Send the text to be displayed above the options
*
- * @param $out OutputPage
* @param $opts FormOptions
*/
- function setTopText( OutputPage $out, FormOptions $opts ){
- $out->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) );
+ function setTopText( FormOptions $opts ) {
+ $this->getOutput()->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) );
}
/**
* Send the text to be displayed after the options, for use in
* Recentchangeslinked
*
- * @param $out OutputPage
* @param $opts FormOptions
*/
- function setBottomText( OutputPage $out, FormOptions $opts ){}
+ function setBottomText( FormOptions $opts ) {}
/**
* Creates the choose namespace selection
*
+ * @todo Uses radio buttons (HASHAR)
* @param $opts FormOptions
* @return String
*/
protected function namespaceFilterForm( FormOptions $opts ) {
$nsSelect = Xml::namespaceSelector( $opts['namespace'], '' );
- $nsLabel = Xml::label( wfMsg('namespace'), 'namespace' );
- $invert = Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $opts['invert'] );
- return array( $nsLabel, "$nsSelect $invert" );
+ $nsLabel = Xml::label( wfMsg( 'namespace' ), 'namespace' );
+ $invert = Xml::checkLabel(
+ wfMsg( 'invert' ), 'invert', 'nsinvert',
+ $opts['invert'],
+ array( 'title' => wfMsg( 'tooltip-invert' ) )
+ );
+ $associated = Xml::checkLabel(
+ wfMsg( 'namespace_association' ), 'associated', 'nsassociated',
+ $opts['associated'],
+ array( 'title' => wfMsg( 'tooltip-namespace_association' ) )
+ );
+ return array( $nsLabel, "$nsSelect $invert $associated" );
}
/**
@@ -571,10 +658,10 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return Array
*/
protected function categoryFilterForm( FormOptions $opts ) {
- list( $label, $input ) = Xml::inputLabelSep( wfMsg('rc_categories'),
+ list( $label, $input ) = Xml::inputLabelSep( wfMsg( 'rc_categories' ),
'categories', 'mw-categories', false, $opts['categories'] );
- $input .= ' ' . Xml::checkLabel( wfMsg('rc_categories_any'),
+ $input .= ' ' . Xml::checkLabel( wfMsg( 'rc_categories_any' ),
'categories_any', 'mw-categories_any', $opts['categories_any'] );
return array( $label, $input );
@@ -597,7 +684,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$cats = array();
foreach( $categories as $cat ) {
$cat = trim( $cat );
- if( $cat == '' ) continue;
+ if( $cat == '' ) {
+ continue;
+ }
$cats[] = $cat;
}
@@ -605,10 +694,12 @@ class SpecialRecentChanges extends IncludableSpecialPage {
$articles = array();
$a2r = array();
$rowsarr = array();
- foreach( $rows AS $k => $r ) {
+ 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( $id == 0 ) {
+ continue; # Page might have been deleted...
+ }
if( !in_array( $id, $articles ) ) {
$articles[] = $id;
}
@@ -620,18 +711,19 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
# Shortcut?
- if( !count( $articles ) || !count( $cats ) )
- return ;
+ if( !count( $articles ) || !count( $cats ) ) {
+ return;
+ }
# Look up
$c = new Categoryfinder;
- $c->seed( $articles, $cats, $opts['categories_any'] ? "OR" : "AND" ) ;
+ $c->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' );
$match = $c->run();
# Filter
$newrows = array();
- foreach( $match AS $id ) {
- foreach( $a2r[$id] AS $rev ) {
+ foreach( $match as $id ) {
+ foreach( $a2r[$id] as $rev ) {
$k = $rev;
$newrows[$k] = $rowsarr[$k];
}
@@ -648,15 +740,12 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @param $active Boolean: whether to show the link in bold
*/
function makeOptionsLink( $title, $override, $options, $active = false ) {
- global $wgUser;
- $sk = $wgUser->getSkin();
$params = $override + $options;
+ $text = htmlspecialchars( $title );
if ( $active ) {
- return $sk->link( $this->getTitle(), '<strong>' . htmlspecialchars( $title ) . '</strong>',
- array(), $params, array( 'known' ) );
- } else {
- return $sk->link( $this->getTitle(), htmlspecialchars( $title ), array() , $params, array( 'known' ) );
+ $text = '<strong>' . $text . '</strong>';
}
+ return Linker::linkKnown( $this->getTitle(), $text, array(), $params );
}
/**
@@ -666,20 +755,21 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @param $nondefaults Array
*/
function optionsPanel( $defaults, $nondefaults ) {
- global $wgLang, $wgUser, $wgRCLinkLimits, $wgRCLinkDays;
+ global $wgRCLinkLimits, $wgRCLinkDays;
$options = $nondefaults + $defaults;
$note = '';
- if( !wfEmptyMsg( 'rclegend', wfMsg('rclegend') ) ) {
- $note .= '<div class="mw-rclegend">' . wfMsgExt( 'rclegend', array('parseinline') ) . "</div>\n";
+ if( !wfEmptyMsg( 'rclegend' ) ) {
+ $note .= '<div class="mw-rclegend">' .
+ wfMsgExt( 'rclegend', array( 'parseinline' ) ) . "</div>\n";
}
if( $options['from'] ) {
$note .= wfMsgExt( 'rcnotefrom', array( 'parseinline' ),
- $wgLang->formatNum( $options['limit'] ),
- $wgLang->timeanddate( $options['from'], true ),
- $wgLang->date( $options['from'], true ),
- $wgLang->time( $options['from'], true ) ) . '<br />';
+ $this->getLang()->formatNum( $options['limit'] ),
+ $this->getLang()->timeanddate( $options['from'], true ),
+ $this->getLang()->date( $options['from'], true ),
+ $this->getLang()->time( $options['from'], true ) ) . '<br />';
}
# Sort data for display and make sure it's unique after we've added user data.
@@ -692,50 +782,63 @@ class SpecialRecentChanges extends IncludableSpecialPage {
// limit links
foreach( $wgRCLinkLimits as $value ) {
- $cl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ),
- array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ) ;
+ $cl[] = $this->makeOptionsLink( $this->getLang()->formatNum( $value ),
+ array( 'limit' => $value ), $nondefaults, $value == $options['limit'] );
}
- $cl = $wgLang->pipeList( $cl );
+ $cl = $this->getLang()->pipeList( $cl );
// day links, reset 'from' to none
foreach( $wgRCLinkDays as $value ) {
- $dl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ),
- array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ) ;
+ $dl[] = $this->makeOptionsLink( $this->getLang()->formatNum( $value ),
+ array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] );
}
- $dl = $wgLang->pipeList( $dl );
+ $dl = $this->getLang()->pipeList( $dl );
// show/hide links
$showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) );
- $minorLink = $this->makeOptionsLink( $showhide[1-$options['hideminor']],
- array( 'hideminor' => 1-$options['hideminor'] ), $nondefaults);
- $botLink = $this->makeOptionsLink( $showhide[1-$options['hidebots']],
- array( 'hidebots' => 1-$options['hidebots'] ), $nondefaults);
- $anonsLink = $this->makeOptionsLink( $showhide[ 1 - $options['hideanons'] ],
- array( 'hideanons' => 1 - $options['hideanons'] ), $nondefaults );
- $liuLink = $this->makeOptionsLink( $showhide[1-$options['hideliu']],
- array( 'hideliu' => 1-$options['hideliu'] ), $nondefaults);
- $patrLink = $this->makeOptionsLink( $showhide[1-$options['hidepatrolled']],
- array( 'hidepatrolled' => 1-$options['hidepatrolled'] ), $nondefaults);
- $myselfLink = $this->makeOptionsLink( $showhide[1-$options['hidemyself']],
- array( 'hidemyself' => 1-$options['hidemyself'] ), $nondefaults);
-
- $links[] = wfMsgHtml( 'rcshowhideminor', $minorLink );
- $links[] = wfMsgHtml( 'rcshowhidebots', $botLink );
- $links[] = wfMsgHtml( 'rcshowhideanons', $anonsLink );
- $links[] = wfMsgHtml( 'rcshowhideliu', $liuLink );
- if( $wgUser->useRCPatrol() )
- $links[] = wfMsgHtml( 'rcshowhidepatr', $patrLink );
- $links[] = wfMsgHtml( 'rcshowhidemine', $myselfLink );
- $hl = $wgLang->pipeList( $links );
+ $filters = array(
+ 'hideminor' => 'rcshowhideminor',
+ 'hidebots' => 'rcshowhidebots',
+ 'hideanons' => 'rcshowhideanons',
+ 'hideliu' => 'rcshowhideliu',
+ 'hidepatrolled' => 'rcshowhidepatr',
+ 'hidemyself' => 'rcshowhidemine'
+ );
+ foreach ( $this->customFilters as $key => $params ) {
+ $filters[$key] = $params['msg'];
+ }
+ // Disable some if needed
+ if ( !$this->getUser()->useRCPatrol() ) {
+ unset( $filters['hidepatrolled'] );
+ }
+
+ $links = array();
+ foreach ( $filters as $key => $msg ) {
+ $link = $this->makeOptionsLink( $showhide[1 - $options[$key]],
+ array( $key => 1-$options[$key] ), $nondefaults );
+ $links[] = wfMsgHtml( $msg, $link );
+ }
// show from this onward link
- $now = $wgLang->timeanddate( wfTimestampNow(), true );
- $tl = $this->makeOptionsLink( $now, array( 'from' => wfTimestampNow() ), $nondefaults );
+ $timestamp = wfTimestampNow();
+ $now = $this->getLang()->timeanddate( $timestamp, true );
+ $tl = $this->makeOptionsLink(
+ $now, array( 'from' => $timestamp ), $nondefaults
+ );
$rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter' ),
- $cl, $dl, $hl );
+ $cl, $dl, $this->getLang()->pipeList( $links ) );
$rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter' ), $tl );
return "{$note}$rclinks<br />$rclistfrom";
}
+
+ /**
+ * add javascript specific to the [[Special:RecentChanges]] page
+ */
+ function addRecentChangesJS() {
+ $this->getOutput()->addModules( array(
+ 'mediawiki.special.recentchanges',
+ ) );
+ }
}
diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php
index db0f554d..8b8369b5 100644
--- a/includes/specials/SpecialRecentchangeslinked.php
+++ b/includes/specials/SpecialRecentchangeslinked.php
@@ -46,9 +46,8 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
public function feedSetup() {
- global $wgRequest;
$opts = parent::feedSetup();
- $opts['target'] = $wgRequest->getVal( 'target' );
+ $opts['target'] = $this->getRequest()->getVal( 'target' );
return $opts;
}
@@ -56,14 +55,13 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$feed = new ChangesFeed( $feedFormat, false );
$feedObj = $feed->getFeedObject(
wfMsgForContent( 'recentchangeslinked-title', $this->getTargetTitle()->getPrefixedText() ),
- wfMsgForContent( 'recentchangeslinked-feed' )
+ wfMsgForContent( 'recentchangeslinked-feed' ),
+ $this->getTitle()->getFullUrl()
);
return array( $feed, $feedObj );
}
public function doMainQuery( $conds, $opts ) {
- global $wgUser, $wgOut;
-
$target = $opts['target'];
$showlinkedto = $opts['showlinkedto'];
$limit = $opts['limit'];
@@ -73,11 +71,11 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
$title = Title::newFromURL( $target );
if( !$title || $title->getInterwiki() != '' ){
- $wgOut->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' );
+ $this->getOutput()->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' );
return false;
}
- $wgOut->setPageTitle( wfMsg( 'recentchangeslinked-title', $title->getPrefixedText() ) );
+ $this->getOutput()->setPageTitle( wfMsg( 'recentchangeslinked-title', $title->getPrefixedText() ) );
/*
* Ordinary links are in the pagelinks table, while transclusions are
@@ -99,13 +97,13 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$query_options = array();
// left join with watchlist table to highlight watched rows
- $uid = $wgUser->getId();
+ $uid = $this->getUser()->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" );
}
- if ( $wgUser->isAllowed( 'rollback' ) ) {
+ if ( $this->getUser()->isAllowed( 'rollback' ) ) {
$tables[] = 'page';
$join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id');
$select[] = 'page_latest';
@@ -143,7 +141,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
// imagelinks and categorylinks tables have no xx_namespace field, and have xx_to instead of xx_title
if( $link_table == 'imagelinks' ) $link_ns = NS_FILE;
- else if( $link_table == 'categorylinks' ) $link_ns = NS_CATEGORY;
+ elseif( $link_table == 'categorylinks' ) $link_ns = NS_CATEGORY;
else $link_ns = 0;
if( $showlinkedto ) {
@@ -171,16 +169,16 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
else
$order = array();
-
- $query = $dbr->selectSQLText(
- array_merge( $tables, array( $link_table ) ),
- $select,
+
+ $query = $dbr->selectSQLText(
+ array_merge( $tables, array( $link_table ) ),
+ $select,
$conds + $subconds,
- __METHOD__,
+ __METHOD__,
$order + $query_options,
$join_conds + array( $link_table => array( 'INNER JOIN', $subjoin ) )
);
-
+
if( $dbr->unionSupportsOrderAndLimit())
$query = $dbr->limitResult( $query, $limit );
@@ -196,7 +194,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$sql = $dbr->unionQueries($subsql, false).' ORDER BY rc_timestamp DESC';
$sql = $dbr->limitResult($sql, $limit, false);
}
-
+
$res = $dbr->query( $sql, __METHOD__ );
if( $res->numRows() == 0 )
@@ -204,7 +202,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
return $res;
}
-
+
function getExtraOptions( $opts ){
$opts->consumeValues( array( 'showlinkedto', 'target', 'tagfilter' ) );
$extraOpts = array();
@@ -219,6 +217,9 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
return $extraOpts;
}
+ /**
+ * @return Title
+ */
function getTargetTitle() {
if ( $this->rclTargetTitle === null ) {
$opts = $this->getOptions();
@@ -231,13 +232,12 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
return $this->rclTargetTitle;
}
- function setTopText( OutputPage $out, FormOptions $opts ) {
- global $wgUser;
- $skin = $wgUser->getSkin();
+ function setTopText( FormOptions $opts ) {
$target = $this->getTargetTitle();
- if( $target )
- $out->setSubtitle( wfMsg( 'recentchangeslinked-backlink', $skin->link( $target,
+ if( $target ) {
+ $this->getOutput()->setSubtitle( wfMsg( 'recentchangeslinked-backlink', Linker::link( $target,
$target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) );
+ }
}
public function getFeedQuery() {
@@ -249,9 +249,9 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
}
- function setBottomText( OutputPage $out, FormOptions $opts ) {
- if( isset( $this->mResultEmpty ) && $this->mResultEmpty ){
- $out->addWikiMsg( 'recentchangeslinked-noresult' );
+ function setBottomText( FormOptions $opts ) {
+ if( isset( $this->mResultEmpty ) && $this->mResultEmpty ) {
+ $this->getOutput()->addWikiMsg( 'recentchangeslinked-noresult' );
}
}
}
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
index f77fc347..3c643253 100644
--- a/includes/specials/SpecialRevisiondelete.php
+++ b/includes/specials/SpecialRevisiondelete.php
@@ -28,9 +28,6 @@
* @ingroup SpecialPage
*/
class SpecialRevisionDelete extends UnlistedSpecialPage {
- /** Skin object */
- var $skin;
-
/** True if the submit button was clicked, and the form was posted */
var $submitClicked;
@@ -64,39 +61,39 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
*/
static $allowedTypes = array(
'revision' => array(
- 'check-label' => 'revdelete-hide-text',
+ 'check-label' => 'revdelete-hide-text',
'deletion-bits' => Revision::DELETED_TEXT,
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
- 'list-class' => 'RevDel_RevisionList',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'list-class' => 'RevDel_RevisionList',
),
'archive' => array(
- 'check-label' => 'revdelete-hide-text',
+ 'check-label' => 'revdelete-hide-text',
'deletion-bits' => Revision::DELETED_TEXT,
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
- 'list-class' => 'RevDel_ArchiveList',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'list-class' => 'RevDel_ArchiveList',
),
'oldimage'=> array(
- 'check-label' => 'revdelete-hide-image',
+ 'check-label' => 'revdelete-hide-image',
'deletion-bits' => File::DELETED_FILE,
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
- 'list-class' => 'RevDel_FileList',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'list-class' => 'RevDel_FileList',
),
'filearchive' => array(
- 'check-label' => 'revdelete-hide-image',
+ 'check-label' => 'revdelete-hide-image',
'deletion-bits' => File::DELETED_FILE,
- 'success' => 'revdelete-success',
- 'failure' => 'revdelete-failure',
- 'list-class' => 'RevDel_ArchivedFileList',
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'list-class' => 'RevDel_ArchivedFileList',
),
'logging' => array(
- 'check-label' => 'revdelete-hide-name',
+ 'check-label' => 'revdelete-hide-name',
'deletion-bits' => LogPage::DELETED_ACTION,
- 'success' => 'logdelete-success',
- 'failure' => 'logdelete-failure',
- 'list-class' => 'RevDel_LogList',
+ 'success' => 'logdelete-success',
+ 'failure' => 'logdelete-failure',
+ 'list-class' => 'RevDel_LogList',
),
);
@@ -114,43 +111,46 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
public function execute( $par ) {
- global $wgOut, $wgUser, $wgRequest;
- if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
- $wgOut->permissionRequired( 'deletedhistory' );
+ $output = $this->getOutput();
+ $user = $this->getUser();
+ if( !$user->isAllowed( 'deletedhistory' ) ) {
+ $output->permissionRequired( 'deletedhistory' );
return;
- } else if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
+ } elseif( wfReadOnly() ) {
+ $output->readOnlyPage();
return;
}
- $this->mIsAllowed = $wgUser->isAllowed('deleterevision'); // for changes
- $this->skin = $wgUser->getSkin();
+ $this->mIsAllowed = $user->isAllowed('deleterevision'); // for changes
$this->setHeaders();
$this->outputHeader();
- $this->submitClicked = $wgRequest->wasPosted() && $wgRequest->getBool( 'wpSubmit' );
+ $request = $this->getRequest();
+ $this->submitClicked = $request->wasPosted() && $request->getBool( 'wpSubmit' );
# Handle our many different possible input types.
- $ids = $wgRequest->getVal( 'ids' );
+ $ids = $request->getVal( 'ids' );
if ( !is_null( $ids ) ) {
# Allow CSV, for backwards compatibility, or a single ID for show/hide links
$this->ids = explode( ',', $ids );
} else {
# Array input
- $this->ids = array_keys( $wgRequest->getArray('ids',array()) );
+ $this->ids = array_keys( $request->getArray('ids',array()) );
}
// $this->ids = array_map( 'intval', $this->ids );
$this->ids = array_unique( array_filter( $this->ids ) );
- if ( $wgRequest->getVal( 'action' ) == 'historysubmit' ) {
- # For show/hide form submission from history page
- $this->targetObj = $GLOBALS['wgTitle'];
+ if ( $request->getVal( 'action' ) == 'historysubmit' ) {
+ // For show/hide form submission from history page
+ // Since we are access through index.php?title=XXX&action=historysubmit
+ // getFullTitle() will contain the target title and not our title
+ $this->targetObj = $this->getFullTitle();
$this->typeName = 'revision';
} else {
- $this->typeName = $wgRequest->getVal( 'type' );
- $this->targetObj = Title::newFromText( $wgRequest->getText( 'target' ) );
+ $this->typeName = $request->getVal( 'type' );
+ $this->targetObj = Title::newFromText( $request->getText( 'target' ) );
}
# For reviewing deleted files...
- $this->archiveName = $wgRequest->getVal( 'file' );
- $this->token = $wgRequest->getVal( 'token' );
+ $this->archiveName = $request->getVal( 'file' );
+ $this->token = $request->getVal( 'token' );
if ( $this->archiveName && $this->targetObj ) {
$this->tryShowFile( $this->archiveName );
return;
@@ -162,23 +162,23 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# No targets?
if( !isset( self::$allowedTypes[$this->typeName] ) || count( $this->ids ) == 0 ) {
- $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+ $output->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
return;
}
$this->typeInfo = self::$allowedTypes[$this->typeName];
# If we have revisions, get the title from the first one
- # since they should all be from the same page. This allows
+ # since they should all be from the same page. This allows
# for more flexibility with page moves...
if( $this->typeName == 'revision' ) {
$rev = Revision::newFromId( $this->ids[0] );
$this->targetObj = $rev ? $rev->getTitle() : $this->targetObj;
}
-
- $this->otherReason = $wgRequest->getVal( 'wpReason' );
+
+ $this->otherReason = $request->getVal( 'wpReason' );
# We need a target page!
if( is_null($this->targetObj) ) {
- $wgOut->addWikiMsg( 'undelete-header' );
+ $output->addWikiMsg( 'undelete-header' );
return;
}
# Give a link to the logs/hist for this page
@@ -190,27 +190,27 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ),
array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER )
);
- if( $wgUser->isAllowed('suppressrevision') ) {
+ if( $user->isAllowed('suppressrevision') ) {
$this->checks[] = array( 'revdelete-hide-restricted',
'wpHideRestricted', Revision::DELETED_RESTRICTED );
}
# Either submit or create our form
if( $this->mIsAllowed && $this->submitClicked ) {
- $this->submit( $wgRequest );
+ $this->submit( $request );
} else {
$this->showForm();
}
-
+
$qc = $this->getLogQueryCond();
# Show relevant lines from the deletion log
- $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
- LogEventsList::showLogExtract( $wgOut, 'delete',
+ $output->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
+ LogEventsList::showLogExtract( $output, 'delete',
$this->targetObj->getPrefixedText(), '', array( 'lim' => 25, 'conds' => $qc ) );
# Show relevant lines from the suppression log
- if( $wgUser->isAllowed( 'suppressionlog' ) ) {
- $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "</h2>\n" );
- LogEventsList::showLogExtract( $wgOut, 'suppress',
+ if( $user->isAllowed( 'suppressionlog' ) ) {
+ $output->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "</h2>\n" );
+ LogEventsList::showLogExtract( $output, 'suppress',
$this->targetObj->getPrefixedText(), '', array( 'lim' => 25, 'conds' => $qc ) );
}
}
@@ -219,11 +219,10 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* Show some useful links in the subtitle
*/
protected function showConvenienceLinks() {
- global $wgOut, $wgUser, $wgLang;
# Give a link to the logs/hist for this page
if( $this->targetObj ) {
$links = array();
- $links[] = $this->skin->linkKnown(
+ $links[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Log' ),
wfMsgHtml( 'viewpagelogs' ),
array(),
@@ -231,16 +230,16 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
);
if ( $this->targetObj->getNamespace() != NS_SPECIAL ) {
# Give a link to the page history
- $links[] = $this->skin->linkKnown(
+ $links[] = Linker::linkKnown(
$this->targetObj,
wfMsgHtml( 'pagehist' ),
array(),
array( 'action' => 'history' )
);
# Link to deleted edits
- if( $wgUser->isAllowed('undelete') ) {
+ if( $this->getUser()->isAllowed('undelete') ) {
$undelete = SpecialPage::getTitleFor( 'Undelete' );
- $links[] = $this->skin->linkKnown(
+ $links[] = Linker::linkKnown(
$undelete,
wfMsgHtml( 'deletedhist' ),
array(),
@@ -249,7 +248,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
}
# Logs themselves don't have histories or archived revisions
- $wgOut->setSubtitle( '<p>' . $wgLang->pipeList( $links ) . '</p>' );
+ $this->getOutput()->setSubtitle( '<p>' . $this->getLang()->pipeList( $links ) . '</p>' );
}
}
@@ -259,7 +258,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
protected function getLogQueryCond() {
$conds = array();
// Revision delete logs for these item
- $conds['log_type'] = array('delete','suppress');
+ $conds['log_type'] = array( 'delete', 'suppress' );
$conds['log_action'] = $this->getList()->getLogAction();
$conds['ls_field'] = RevisionDeleter::getRelationType( $this->typeName );
$conds['ls_value'] = $this->ids;
@@ -271,36 +270,34 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* TODO Mostly copied from Special:Undelete. Refactor.
*/
protected function tryShowFile( $archiveName ) {
- global $wgOut, $wgRequest, $wgUser, $wgLang;
-
$repo = RepoGroup::singleton()->getLocalRepo();
$oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
$oimage->load();
// Check if user is allowed to see this file
if ( !$oimage->exists() ) {
- $wgOut->addWikiMsg( 'revdelete-no-file' );
+ $this->getOutput()->addWikiMsg( 'revdelete-no-file' );
return;
}
if( !$oimage->userCan(File::DELETED_FILE) ) {
if( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
- $wgOut->permissionRequired( 'suppressrevision' );
+ $this->getOutput()->permissionRequired( 'suppressrevision' );
} else {
- $wgOut->permissionRequired( 'deletedtext' );
+ $this->getOutput()->permissionRequired( 'deletedtext' );
}
return;
}
- if ( !$wgUser->matchEditToken( $this->token, $archiveName ) ) {
- $wgOut->addWikiMsg( 'revdelete-show-file-confirm',
+ if ( !$this->getUser()->matchEditToken( $this->token, $archiveName ) ) {
+ $this->getOutput()->addWikiMsg( 'revdelete-show-file-confirm',
$this->targetObj->getText(),
- $wgLang->date( $oimage->getTimestamp() ),
- $wgLang->time( $oimage->getTimestamp() ) );
- $wgOut->addHTML(
+ $this->getLang()->date( $oimage->getTimestamp() ),
+ $this->getLang()->time( $oimage->getTimestamp() ) );
+ $this->getOutput()->addHTML(
Xml::openElement( 'form', array(
'method' => 'POST',
'action' => $this->getTitle()->getLocalUrl(
'target=' . urlencode( $oimage->getName() ) .
'&file=' . urlencode( $archiveName ) .
- '&token=' . urlencode( $wgUser->editToken( $archiveName ) ) )
+ '&token=' . urlencode( $this->getUser()->editToken( $archiveName ) ) )
)
) .
Xml::submitButton( wfMsg( 'revdelete-show-file-submit' ) ) .
@@ -308,14 +305,14 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
);
return;
}
- $wgOut->disable();
+ $this->getOutput()->disable();
# We mustn't allow the output to be Squid cached, otherwise
# if an admin previews a deleted image, and it's cached, then
# a user without appropriate permissions can toddle off and
# nab the image, and Squid will serve it
- $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
- $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
- $wgRequest->response()->header( 'Pragma: no-cache' );
+ $this->getRequest()->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
+ $this->getRequest()->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
+ $this->getRequest()->response()->header( 'Pragma: no-cache' );
# Stream the file to the client
global $IP;
@@ -331,27 +328,26 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
protected function getList() {
if ( is_null( $this->list ) ) {
$class = $this->typeInfo['list-class'];
- $this->list = new $class( $this, $this->targetObj, $this->ids );
+ $this->list = new $class( $this->getContext(), $this->targetObj, $this->ids );
}
return $this->list;
}
/**
- * Show a list of items that we will operate on, and show a form with checkboxes
+ * Show a list of items that we will operate on, and show a form with checkboxes
* which will allow the user to choose new visibility settings.
*/
protected function showForm() {
- global $wgOut, $wgUser, $wgLang;
$UserAllowed = true;
if ( $this->typeName == 'logging' ) {
- $wgOut->addWikiMsg( 'logdelete-selected', $wgLang->formatNum( count($this->ids) ) );
+ $this->getOutput()->addWikiMsg( 'logdelete-selected', $this->getLang()->formatNum( count($this->ids) ) );
} else {
- $wgOut->addWikiMsg( 'revdelete-selected',
+ $this->getOutput()->addWikiMsg( 'revdelete-selected',
$this->targetObj->getPrefixedText(), count( $this->ids ) );
}
- $wgOut->addHTML( "<ul>" );
+ $this->getOutput()->addHTML( "<ul>" );
$numRevisions = 0;
// Live revisions...
@@ -360,21 +356,21 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$item = $list->current();
if ( !$item->canView() ) {
if( !$this->submitClicked ) {
- $wgOut->permissionRequired( 'suppressrevision' );
+ $this->getOutput()->permissionRequired( 'suppressrevision' );
return;
}
$UserAllowed = false;
}
$numRevisions++;
- $wgOut->addHTML( $item->getHTML() );
+ $this->getOutput()->addHTML( $item->getHTML() );
}
if( !$numRevisions ) {
- $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
+ $this->getOutput()->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
return;
}
-
- $wgOut->addHTML( "</ul>" );
+
+ $this->getOutput()->addHTML( "</ul>" );
// Explanation text
$this->addUsageText();
@@ -384,7 +380,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
// Show form if the user can submit
if( $this->mIsAllowed ) {
$out = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ),
+ 'action' => $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ),
'id' => 'mw-revdel-form-revisions' ) ) .
Xml::fieldset( wfMsg( 'revdelete-legend' ) ) .
$this->buildCheckBoxes() .
@@ -404,7 +400,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
Xml::label( wfMsg( 'revdelete-otherreason' ), 'wpReason' ) .
'</td>' .
'<td class="mw-input">' .
- Xml::input( 'wpReason', 60, $this->otherReason, array( 'id' => 'wpReason' ) ) .
+ Xml::input( 'wpReason', 60, $this->otherReason, array( 'id' => 'wpReason', 'maxlength' => 100 ) ) .
'</td>' .
"</tr><tr>\n" .
'<td></td>' .
@@ -414,7 +410,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
'</td>' .
"</tr>\n" .
Xml::closeElement( 'table' ) .
- Html::hidden( 'wpEditToken', $wgUser->editToken() ) .
+ Html::hidden( 'wpEditToken', $this->getUser()->editToken() ) .
Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
Html::hidden( 'type', $this->typeName ) .
Html::hidden( 'ids', implode( ',', $this->ids ) ) .
@@ -425,9 +421,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
if( $this->mIsAllowed ) {
$out .= Xml::closeElement( 'form' ) . "\n";
// Show link to edit the dropdown reasons
- if( $wgUser->isAllowed( 'editinterface' ) ) {
+ if( $this->getUser()->isAllowed( 'editinterface' ) ) {
$title = Title::makeTitle( NS_MEDIAWIKI, 'revdelete-reason-dropdown' );
- $link = $wgUser->getSkin()->link(
+ $link = Linker::link(
$title,
wfMsgHtml( 'revdelete-edit-reasonlist' ),
array(),
@@ -436,30 +432,27 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$out .= Xml::tags( 'p', array( 'class' => 'mw-revdel-editreasons' ), $link ) . "\n";
}
}
- $wgOut->addHTML( $out );
+ $this->getOutput()->addHTML( $out );
}
/**
* Show some introductory text
- * FIXME Wikimedia-specific policy text
+ * @todo FIXME: Wikimedia-specific policy text
*/
protected function addUsageText() {
- global $wgOut, $wgUser;
- $wgOut->addWikiMsg( 'revdelete-text' );
- if( $wgUser->isAllowed( 'suppressrevision' ) ) {
- $wgOut->addWikiMsg( 'revdelete-suppress-text' );
+ $this->getOutput()->addWikiMsg( 'revdelete-text' );
+ if( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
+ $this->getOutput()->addWikiMsg( 'revdelete-suppress-text' );
}
if( $this->mIsAllowed ) {
- $wgOut->addWikiMsg( 'revdelete-confirm' );
+ $this->getOutput()->addWikiMsg( 'revdelete-confirm' );
}
}
-
+
/**
* @return String: HTML
*/
protected function buildCheckBoxes() {
- global $wgRequest;
-
$html = '<table>';
// If there is just one item, use checkboxes
$list = $this->getList();
@@ -467,7 +460,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$list->reset();
$bitfield = $list->current()->getBits(); // existing field
if( $this->submitClicked ) {
- $bitfield = $this->extractBitfield( $this->extractBitParams($wgRequest), $bitfield );
+ $bitfield = $this->extractBitfield( $this->extractBitParams(), $bitfield );
}
foreach( $this->checks as $item ) {
list( $message, $name, $field ) = $item;
@@ -488,7 +481,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
list( $message, $name, $field ) = $item;
// If there are several items, use third state by default...
if( $this->submitClicked ) {
- $selected = $wgRequest->getInt( $name, 0 /* unchecked */ );
+ $selected = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
} else {
$selected = -1; // use existing field
}
@@ -503,24 +496,23 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$html .= "<tr>$line</tr>\n";
}
}
-
+
$html .= '</table>';
return $html;
}
/**
* UI entry point for form submission.
- * @param $request WebRequest
*/
- protected function submit( $request ) {
- global $wgUser, $wgOut;
+ protected function submit() {
# Check edit token on submission
- if( $this->submitClicked && !$wgUser->matchEditToken( $request->getVal('wpEditToken') ) ) {
- $wgOut->addWikiMsg( 'sessionfailure' );
+ $token = $this->getRequest()->getVal('wpEditToken');
+ if( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
+ $this->getOutput()->addWikiMsg( 'sessionfailure' );
return false;
}
- $bitParams = $this->extractBitParams( $request );
- $listReason = $request->getText( 'wpRevDeleteReasonList', 'other' ); // from dropdown
+ $bitParams = $this->extractBitParams();
+ $listReason = $this->getRequest()->getText( 'wpRevDeleteReasonList', 'other' ); // from dropdown
$comment = $listReason;
if( $comment != 'other' && $this->otherReason != '' ) {
// Entry from drop down menu + additional comment
@@ -529,8 +521,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$comment = $this->otherReason;
}
# Can the user set this field?
- if( $bitParams[Revision::DELETED_RESTRICTED]==1 && !$wgUser->isAllowed('suppressrevision') ) {
- $wgOut->permissionRequired( 'suppressrevision' );
+ if( $bitParams[Revision::DELETED_RESTRICTED]==1 && !$this->getUser()->isAllowed('suppressrevision') ) {
+ $this->getOutput()->permissionRequired( 'suppressrevision' );
return false;
}
# If the save went through, go to success message...
@@ -549,9 +541,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* Report that the submit operation succeeded
*/
protected function success() {
- global $wgOut;
- $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
- $wgOut->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeInfo['success'] );
+ $this->getOutput()->setPagetitle( wfMsg( 'actioncomplete' ) );
+ $this->getOutput()->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeInfo['success'] );
$this->list->reloadFromMaster();
$this->showForm();
}
@@ -560,22 +551,21 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* Report that the submit operation failed
*/
protected function failure( $status ) {
- global $wgOut;
- $wgOut->setPagetitle( wfMsg( 'actionfailed' ) );
- $wgOut->addWikiText( $status->getWikiText( $this->typeInfo['failure'] ) );
+ $this->getOutput()->setPagetitle( wfMsg( 'actionfailed' ) );
+ $this->getOutput()->addWikiText( $status->getWikiText( $this->typeInfo['failure'] ) );
$this->showForm();
}
/**
* Put together an array that contains -1, 0, or the *_deleted const for each bit
- * @param $request WebRequest
+ *
* @return array
*/
- protected function extractBitParams( $request ) {
+ protected function extractBitParams() {
$bitfield = array();
foreach( $this->checks as $item ) {
list( /* message */ , $name, $field ) = $item;
- $val = $request->getInt( $name, 0 /* unchecked */ );
+ $val = $this->getRequest()->getInt( $name, 0 /* unchecked */ );
if( $val < -1 || $val > 1) {
$val = -1; // -1 for existing value
}
@@ -586,7 +576,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
return $bitfield;
}
-
+
/**
* Put together a rev_deleted bitfield
* @param $bitPars array extractBitParams() params
@@ -599,7 +589,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
foreach( $bitPars as $const => $val ) {
if( $val == 1 ) {
$newBits |= $const; // $const is the *_deleted const
- } else if( $val == -1 ) {
+ } elseif( $val == -1 ) {
$newBits |= ($oldfield & $const); // use existing
}
}
diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php
index fd6e858e..ba9d378a 100644
--- a/includes/specials/SpecialSearch.php
+++ b/includes/specials/SpecialSearch.php
@@ -24,35 +24,66 @@
*/
/**
- * Entry point
- *
- * @param $par String: (default '')
- */
-function wfSpecialSearch( $par = '' ) {
- 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 );
- // Fetch the search term
- $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $titleParam ) );
- $searchPage = new SpecialSearch( $wgRequest, $wgUser );
- if( $wgRequest->getVal( 'fulltext' )
- || !is_null( $wgRequest->getVal( 'offset' ))
- || !is_null( $wgRequest->getVal( 'searchx' )) )
- {
- $searchPage->showResults( $search );
- } else {
- $searchPage->goResult( $search );
- }
-}
-
-/**
* implements Special:Search - Run text & title search and display the output
* @ingroup SpecialPage
*/
-class SpecialSearch {
+class SpecialSearch extends SpecialPage {
+ /**
+ * Current search profile. Search profile is just a name that identifies
+ * the active search tab on the search page (content, help, discussions...)
+ * For users tt replaces the set of enabled namespaces from the query
+ * string when applicable. Extensions can add new profiles with hooks
+ * with custom search options just for that profile.
+ * null|string
+ */
+ protected $profile;
+
+ /// Search engine
+ protected $searchEngine;
+
+ /// For links
+ protected $extraParams = array();
+
+ /// No idea, apparently used by some other classes
+ protected $mPrefix;
+
+ const NAMESPACES_CURRENT = 'sense';
+
+ public function __construct() {
+ parent::__construct( 'Search' );
+ }
+
+ /**
+ * Entry point
+ *
+ * @param $par String or null
+ */
+ public function execute( $par ) {
+ global $wgRequest, $wgUser, $wgOut;
+
+ $this->setHeaders();
+ $this->outputHeader();
+ $wgOut->allowClickjacking();
+ $wgOut->addModuleStyles( 'mediawiki.special' );
+
+ // 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 );
+
+ // Fetch the search term
+ $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $titleParam ) );
+
+ $this->load( $wgRequest, $wgUser );
+
+ if ( $wgRequest->getVal( 'fulltext' )
+ || !is_null( $wgRequest->getVal( 'offset' ) )
+ || !is_null( $wgRequest->getVal( 'searchx' ) ) )
+ {
+ $this->showResults( $search );
+ } else {
+ $this->goResult( $search );
+ }
+ }
/**
* Set up basic search parameters from the request and user settings.
@@ -61,18 +92,44 @@ class SpecialSearch {
* @param $request WebRequest
* @param $user User
*/
- public function __construct( &$request, &$user ) {
+ public function load( &$request, &$user ) {
list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
- $this->mPrefix = $request->getVal('prefix', '');
- # Extract requested namespaces
- $this->namespaces = $this->powerSearch( $request );
- if( empty( $this->namespaces ) ) {
- $this->namespaces = SearchEngine::userNamespaces( $user );
+ $this->mPrefix = $request->getVal( 'prefix', '' );
+
+
+ # Extract manually requested namespaces
+ $nslist = $this->powerSearch( $request );
+ $this->profile = $profile = $request->getVal( 'profile', null );
+ $profiles = $this->getSearchProfiles();
+ if ( $profile === null) {
+ // BC with old request format
+ $this->profile = 'advanced';
+ if ( count( $nslist ) ) {
+ foreach( $profiles as $key => $data ) {
+ if ( $nslist === $data['namespaces'] && $key !== 'advanced') {
+ $this->profile = $key;
+ }
+ }
+ $this->namespaces = $nslist;
+ } else {
+ $this->namespaces = SearchEngine::userNamespaces( $user );
+ }
+ } elseif ( $profile === 'advanced' ) {
+ $this->namespaces = $nslist;
+ } else {
+ if ( isset( $profiles[$profile]['namespaces'] ) ) {
+ $this->namespaces = $profiles[$profile]['namespaces'];
+ } else {
+ // Unknown profile requested
+ $this->profile = 'default';
+ $this->namespaces = $profiles['default']['namespaces'];
+ }
}
- $this->searchRedirects = $request->getCheck( 'redirs' );
- $this->searchAdvanced = $request->getVal( 'advanced' );
- $this->active = 'advanced';
- $this->sk = $user->getSkin();
+
+ // Redirects defaults to true, but we don't know whether it was ticked of or just missing
+ $default = $request->getBool( 'profile' ) ? 0 : 1;
+ $this->searchRedirects = $request->getBool( 'redirs', $default ) ? 1 : 0;
+ $this->sk = $this->getSkin();
$this->didYouMeanHtml = ''; # html of did you mean... link
$this->fulltext = $request->getVal('fulltext');
}
@@ -93,12 +150,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;
@@ -123,19 +180,21 @@ class SpecialSearch {
* @param $term String
*/
public function showResults( $term ) {
- global $wgOut, $wgUser, $wgDisableTextSearch, $wgContLang, $wgScript;
+ global $wgOut, $wgDisableTextSearch, $wgContLang, $wgScript;
wfProfileIn( __METHOD__ );
- $sk = $wgUser->getSkin();
+ $sk = $this->getSkin();
- $this->searchEngine = SearchEngine::create();
- $search =& $this->searchEngine;
+ $search = $this->getSearchEngine();
$search->setLimitOffset( $this->limit, $this->offset );
$search->setNamespaces( $this->namespaces );
- $search->showRedirects = $this->searchRedirects;
+ $search->showRedirects = $this->searchRedirects; // BC
+ $search->setFeatureData( 'list-redirects', $this->searchRedirects );
$search->prefix = $this->mPrefix;
$term = $search->transformSearchTerm($term);
+ wfRunHooks( 'SpecialSearchSetupEngine', array( $this, $this->profile, $search ) );
+
$this->setupPage( $term );
if( $wgDisableTextSearch ) {
@@ -146,14 +205,13 @@ class SpecialSearch {
wfProfileOut( __METHOD__ );
return;
}
- global $wgInputEncoding;
$wgOut->addHTML(
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'search-external' ) ) .
Xml::element( 'p', array( 'class' => 'mw-searchdisabled' ), wfMsg( 'searchdisabled' ) ) .
wfMsg( 'googlesearch',
htmlspecialchars( $term ),
- htmlspecialchars( $wgInputEncoding ),
+ htmlspecialchars( 'UTF-8' ),
htmlspecialchars( wfMsg( 'searchbutton' ) )
) .
Xml::closeElement( 'fieldset' )
@@ -205,7 +263,7 @@ class SpecialSearch {
Xml::openElement(
'form',
array(
- 'id' => ( $this->searchAdvanced ? 'powersearch' : 'search' ),
+ 'id' => ( $this->profile === 'advanced' ? 'powersearch' : 'search' ),
'method' => 'get',
'action' => $wgScript
)
@@ -214,7 +272,7 @@ class SpecialSearch {
$wgOut->addHtml(
Xml::openElement( 'table', array( 'id'=>'mw-search-top-table', 'border'=>0, 'cellpadding'=>0, 'cellspacing'=>0 ) ) .
Xml::openElement( 'tr' ) .
- Xml::openElement( 'td' ) . "\n" .
+ Xml::openElement( 'td' ) . "\n" .
$this->shortDialog( $term ) .
Xml::closeElement('td') .
Xml::closeElement('tr') .
@@ -223,17 +281,15 @@ class SpecialSearch {
// Sometimes the search engine knows there are too many hits
if( $titleMatches instanceof SearchResultTooMany ) {
- $wgOut->addWikiText( '==' . wfMsg( 'toomanymatches' ) . "==\n" );
+ $wgOut->wrapWikiMsg( "==$1==\n", 'toomanymatches' );
wfProfileOut( __METHOD__ );
return;
}
$filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':';
if( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
- $wgOut->addHTML( $this->formHeader($term, 0, 0));
- if( $this->searchAdvanced ) {
- $wgOut->addHTML( $this->powerSearchBox( $term ) );
- }
+ $wgOut->addHTML( $this->formHeader( $term, 0, 0 ) );
+ $wgOut->addHtml( $this->getProfileForm( $this->profile, $term ) );
$wgOut->addHTML( '</form>' );
// Empty query -- straight view of search form
wfProfileOut( __METHOD__ );
@@ -245,7 +301,7 @@ class SpecialSearch {
$textMatchesNum = $textMatches ? $textMatches->numRows() : 0;
// Total initial query matches (possible false positives)
$num = $titleMatchesNum + $textMatchesNum;
-
+
// Get total actual results (after second filtering, if any)
$numTitleMatches = $titleMatches && !is_null( $titleMatches->getTotalHits() ) ?
$titleMatches->getTotalHits() : $titleMatchesNum;
@@ -258,13 +314,12 @@ class SpecialSearch {
$totalRes += $titleMatches->getTotalHits();
if($textMatches && !is_null( $textMatches->getTotalHits() ))
$totalRes += $textMatches->getTotalHits();
-
+
// show number of results and current offset
- $wgOut->addHTML( $this->formHeader($term, $num, $totalRes));
- if( $this->searchAdvanced ) {
- $wgOut->addHTML( $this->powerSearchBox( $term ) );
- }
-
+ $wgOut->addHTML( $this->formHeader( $term, $num, $totalRes ) );
+ $wgOut->addHtml( $this->getProfileForm( $this->profile, $term ) );
+
+
$wgOut->addHtml( Xml::closeElement( 'form' ) );
$wgOut->addHtml( "<div class='searchresults'>" );
@@ -281,8 +336,9 @@ class SpecialSearch {
wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
} else {
wfRunHooks( 'SpecialSearchNoResults', array( $term ) );
- }
+ }
+ $wgOut->parserOptions()->setEditSection( false );
if( $titleMatches ) {
if( $numTitleMatches > 0 ) {
$wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' );
@@ -321,23 +377,33 @@ class SpecialSearch {
}
wfProfileOut( __METHOD__ );
}
-
+
protected function showCreateLink( $t ) {
global $wgOut;
-
+
// show direct page/create link if applicable
- $messageName = null;
- if( !is_null($t) ) {
- if( $t->isKnown() ) {
- $messageName = 'searchmenu-exists';
- } elseif( $t->userCan( 'create' ) ) {
- $messageName = 'searchmenu-new';
- } else {
- $messageName = 'searchmenu-new-nocreate';
- }
- }
+
+ // Check DBkey !== '' in case of fragment link only.
+ if( is_null( $t ) || $t->getDBkey() === '' ) {
+ // invalid title
+ // preserve the paragraph for margins etc...
+ $this->getOutput()->addHtml( '<p></p>' );
+ return;
+ }
+ $messageName = '';
+ if( $t->isKnown() ) {
+ $messageName = 'searchmenu-exists';
+ } elseif( $t->userCan( 'create' ) ) {
+ $messageName = 'searchmenu-new';
+ } else {
+ $messageName = 'searchmenu-new-nocreate';
+ }
+ $params = array( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) );
+ wfRunHooks( 'SpecialSearchCreateLink', array( $t, &$params ) );
+
+ // Extensions using the hook might still return an empty $messageName
if( $messageName ) {
- $wgOut->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", array( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) ) );
+ $this->getOutput()->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", $params );
} else {
// preserve the paragraph for margins etc...
$wgOut->addHtml( '<p></p>' );
@@ -349,28 +415,14 @@ class SpecialSearch {
*/
protected function setupPage( $term ) {
global $wgOut;
- // Figure out the active search profile header
- if( $this->searchAdvanced ) {
- $this->active = 'advanced';
- } else {
- $profiles = $this->getSearchProfiles();
-
- foreach( $profiles as $key => $data ) {
- if ( $this->namespaces == $data['namespaces'] && $key != 'advanced')
- $this->active = $key;
- }
-
- }
+
# Should advanced UI be used?
- $this->searchAdvanced = ($this->active === 'advanced');
- if( !empty( $term ) ) {
+ $this->searchAdvanced = ($this->profile === 'advanced');
+ if( strval( $term ) !== '' ) {
$wgOut->setPageTitle( wfMsg( 'searchresults') );
$wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'searchresults-title', $term ) ) );
}
- $wgOut->setArticleRelated( false );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
// add javascript specific to special:search
- $wgOut->addModules( 'mediawiki.legacy.search' );
$wgOut->addModules( 'mediawiki.special.search' );
}
@@ -388,6 +440,7 @@ class SpecialSearch {
$arr[] = $ns;
}
}
+
return $arr;
}
@@ -398,14 +451,15 @@ class SpecialSearch {
*/
protected function powerSearchOptions() {
$opt = array();
- foreach( $this->namespaces as $n ) {
- $opt['ns' . $n] = 1;
- }
$opt['redirs'] = $this->searchRedirects ? 1 : 0;
- if( $this->searchAdvanced ) {
- $opt['advanced'] = $this->searchAdvanced;
+ if( $this->profile !== 'advanced' ) {
+ $opt['profile'] = $this->profile;
+ } else {
+ foreach( $this->namespaces as $n ) {
+ $opt['ns' . $n] = 1;
+ }
}
- return $opt;
+ return $opt + $this->extraParams;
}
/**
@@ -443,7 +497,7 @@ class SpecialSearch {
* @param $terms Array: terms to highlight
*/
protected function showHit( $result, $terms ) {
- global $wgLang, $wgUser;
+ global $wgLang;
wfProfileIn( __METHOD__ );
if( $result->isBrokenTitle() ) {
@@ -451,16 +505,16 @@ class SpecialSearch {
return "<!-- Broken link in search result -->\n";
}
- $sk = $wgUser->getSkin();
+ $sk = $this->getSkin();
$t = $result->getTitle();
$titleSnippet = $result->getTitleSnippet($terms);
if( $titleSnippet == '' )
$titleSnippet = null;
-
+
$link_t = clone $t;
-
+
wfRunHooks( 'ShowSearchHitTitle',
array( &$link_t, &$titleSnippet, $result, $terms, $this ) );
@@ -509,7 +563,6 @@ class SpecialSearch {
$section = '';
-
if( !is_null($sectionTitle) ) {
if( $sectionText == '' )
$sectionText = null;
@@ -544,7 +597,7 @@ class SpecialSearch {
$size = wfMsgExt(
'search-result-size',
array( 'parsemag', 'escape' ),
- $this->sk->formatSize( $byteSize ),
+ $wgLang->formatSize( $byteSize ),
$wgLang->formatNum( $wordCount )
);
@@ -706,13 +759,13 @@ class SpecialSearch {
$out = "";
// display project name
if(is_null($lastInterwiki) || $lastInterwiki != $t->getInterwiki()) {
- if( key_exists($t->getInterwiki(),$customCaptions) )
+ if( array_key_exists($t->getInterwiki(),$customCaptions) ) {
// captions from 'search-interwiki-custom'
$caption = $customCaptions[$t->getInterwiki()];
- else{
+ } else {
// default is to show the hostname of the other wiki which might suck
// if there are many wikis on one hostname
- $parsed = parse_url($t->getFullURL());
+ $parsed = wfParseUrl( $t->getFullURL() );
$caption = wfMsg('search-interwiki-default', $parsed['host']);
}
// "more results" link (special page stuff could be localized, but we might not know target lang)
@@ -735,14 +788,28 @@ class SpecialSearch {
return $out;
}
+ protected function getProfileForm( $profile, $term ) {
+ // Hidden stuff
+ $opts = array();
+ $opts['redirs'] = $this->searchRedirects;
+ $opts['profile'] = $this->profile;
+
+ if ( $profile === 'advanced' ) {
+ return $this->powerSearchBox( $term, $opts );
+ } else {
+ $form = '';
+ wfRunHooks( 'SpecialSearchProfileForm', array( $this, &$form, $profile, $term, $opts ) );
+ return $form;
+ }
+ }
/**
- * Generates the power search box at bottom of [[Special:Search]]
+ * Generates the power search box at [[Special:Search]]
*
* @param $term String: search term
* @return String: HTML form
*/
- protected function powerSearchBox( $term ) {
+ protected function powerSearchBox( $term, $opts ) {
// Groups namespaces into rows according to subject
$rows = array();
foreach( SearchEngine::searchableNamespaces() as $namespace => $name ) {
@@ -768,7 +835,7 @@ class SpecialSearch {
}
$rows = array_values( $rows );
$numRows = count( $rows );
-
+
// Lays out namespaces in multiple floating two-column tables so they'll
// be arranged nicely while still accommodating different screen widths
$namespaceTables = '';
@@ -782,15 +849,21 @@ class SpecialSearch {
}
$namespaceTables .= Xml::closeElement( 'table' );
}
+
+ $showSections = array( 'namespaceTables' => $namespaceTables );
+
// Show redirects check only if backend supports it
- $redirects = '';
- if( $this->searchEngine->acceptListRedirects() ) {
- $redirects =
- Xml::check(
- 'redirs', $this->searchRedirects, array( 'value' => '1', 'id' => 'redirs' )
- ) .
- ' ' .
- Xml::label( wfMsg( 'powersearch-redir' ), 'redirs' );
+ if( $this->getSearchEngine()->supports( 'list-redirects' ) ) {
+ $showSections['redirects'] =
+ Xml::checkLabel( wfMsg( 'powersearch-redir' ), 'redirs', 'redirs', $this->searchRedirects );
+ }
+
+ wfRunHooks( 'SpecialSearchPowerBox', array( &$showSections, $term, $opts ) );
+
+ $hidden = '';
+ unset( $opts['redirs'] );
+ foreach( $opts as $key => $value ) {
+ $hidden .= Html::hidden( $key, $value );
}
// Return final output
return
@@ -809,7 +882,6 @@ class SpecialSearch {
array(
'type'=>'button',
'id' => 'mw-search-toggleall',
- 'onclick' => 'mwToggleSearchCheckboxes("all");',
'value' => wfMsg( 'powersearch-toggleall' )
)
) .
@@ -818,25 +890,20 @@ class SpecialSearch {
array(
'type'=>'button',
'id' => 'mw-search-togglenone',
- 'onclick' => 'mwToggleSearchCheckboxes("none");',
'value' => wfMsg( 'powersearch-togglenone' )
)
)
) .
Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
- $namespaceTables .
- Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
- $redirects .
- Html::hidden( 'title', SpecialPage::getTitleFor( 'Search' )->getPrefixedText() ) .
- Html::hidden( 'advanced', $this->searchAdvanced ) .
- Html::hidden( 'fulltext', 'Advanced search' ) .
+ implode( Xml::element( 'div', array( 'class' => 'divider' ), '', false ), $showSections ) .
+ $hidden .
Xml::closeElement( 'fieldset' );
}
-
+
protected function getSearchProfiles() {
// Builds list of Search Types (profiles)
$nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
-
+
$profiles = array(
'default' => array(
'message' => 'searchprofile-articles',
@@ -867,25 +934,25 @@ class SpecialSearch {
'advanced' => array(
'message' => 'searchprofile-advanced',
'tooltip' => 'searchprofile-advanced-tooltip',
- 'namespaces' => $this->namespaces,
- 'parameters' => array( 'advanced' => 1 ),
+ 'namespaces' => self::NAMESPACES_CURRENT,
)
);
-
+
wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) );
foreach( $profiles as &$data ) {
- sort($data['namespaces']);
+ if ( !is_array( $data['namespaces'] ) ) continue;
+ sort( $data['namespaces'] );
}
-
+
return $profiles;
}
protected function formHeader( $term, $resultsShown, $totalNum ) {
global $wgLang;
-
+
$out = Xml::openElement('div', array( 'class' => 'mw-search-formheader' ) );
-
+
$bareterm = $term;
if( $this->startsWithImage( $term ) ) {
// Deletes prefixes
@@ -893,24 +960,29 @@ class SpecialSearch {
}
$profiles = $this->getSearchProfiles();
-
+
// Outputs XML for Search Types
$out .= Xml::openElement( 'div', array( 'class' => 'search-types' ) );
$out .= Xml::openElement( 'ul' );
foreach ( $profiles as $id => $profile ) {
+ if ( !isset( $profile['parameters'] ) ) {
+ $profile['parameters'] = array();
+ }
+ $profile['parameters']['profile'] = $id;
+
$tooltipParam = isset( $profile['namespace-messages'] ) ?
$wgLang->commaList( $profile['namespace-messages'] ) : null;
$out .= Xml::tags(
'li',
array(
- 'class' => $this->active == $id ? 'current' : 'normal'
+ 'class' => $this->profile === $id ? 'current' : 'normal'
),
$this->makeSearchLink(
$bareterm,
- $profile['namespaces'],
+ array(),
wfMsg( $profile['message'] ),
wfMsg( $profile['tooltip'], $tooltipParam ),
- isset( $profile['parameters'] ) ? $profile['parameters'] : array()
+ $profile['parameters']
)
);
}
@@ -930,60 +1002,54 @@ class SpecialSearch {
} elseif ( $resultsShown >= $this->limit ) {
$top = wfShowingResults( $this->offset, $this->limit );
} else {
- $top = wfShowingResultsNum( $this->offset, $this->limit, $resultsShown );
+ $top = wfMsgExt( 'showingresultsnum', array( 'parseinline' ),
+ $wgLang->formatNum( $this->limit ),
+ $wgLang->formatNum( $this->offset + 1 ),
+ $wgLang->formatNum( $resultsShown )
+ );
}
$out .= Xml::tags( 'div', array( 'class' => 'results-info' ),
Xml::tags( 'ul', null, Xml::tags( 'li', null, $top ) )
);
}
-
+
$out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
$out .= Xml::closeElement('div');
-
- // Adds hidden namespace fields
- if ( !$this->searchAdvanced ) {
- foreach( $this->namespaces as $ns ) {
- $out .= Html::hidden( "ns{$ns}", '1' );
- }
- }
-
+
return $out;
}
protected function shortDialog( $term ) {
- $searchTitle = SpecialPage::getTitleFor( 'Search' );
- $out = Html::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n";
- // Keep redirect setting
- $out .= Html::hidden( "redirs", (int)$this->searchRedirects ) . "\n";
+ $out = Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
+ $out .= Html::hidden( 'profile', $this->profile ) . "\n";
// Term box
$out .= Html::input( 'search', $term, 'search', array(
- 'id' => $this->searchAdvanced ? 'powerSearchText' : 'searchText',
+ 'id' => $this->profile === 'advanced' ? 'powerSearchText' : 'searchText',
'size' => '50',
'autofocus'
) ) . "\n";
$out .= Html::hidden( 'fulltext', 'Search' ) . "\n";
$out .= Xml::submitButton( wfMsg( 'searchbutton' ) ) . "\n";
- return $out . $this->didYouMeanHtml;
+ return $out . $this->didYouMeanHtml;
}
/**
* Make a search link with some target namespaces
*
* @param $term String
- * @param $namespaces Array
+ * @param $namespaces Array ignored
* @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() ) {
+ protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params = array() ) {
$opt = $params;
foreach( $namespaces as $n ) {
$opt['ns' . $n] = 1;
}
- $opt['redirs'] = $this->searchRedirects ? 1 : 0;
+ $opt['redirs'] = $this->searchRedirects;
- $st = SpecialPage::getTitleFor( 'Search' );
$stParams = array_merge(
array(
'search' => $term,
@@ -995,10 +1061,8 @@ class SpecialSearch {
return Xml::element(
'a',
array(
- 'href' => $st->getLocalURL( $stParams ),
- 'title' => $tooltip,
- 'onmousedown' => 'mwSearchHeaderClick(this);',
- 'onkeydown' => 'mwSearchHeaderClick(this);'),
+ 'href' => $this->getTitle()->getLocalURL( $stParams ),
+ 'title' => $tooltip),
$label
);
}
@@ -1018,7 +1082,7 @@ class SpecialSearch {
}
return false;
}
-
+
/**
* Check if query starts with all: prefix
*
@@ -1028,12 +1092,32 @@ class SpecialSearch {
protected function startsWithAll( $term ) {
$allkeyword = wfMsgForContent('searchall');
-
+
$p = explode( ':', $term );
if( count( $p ) > 1 ) {
return $p[0] == $allkeyword;
}
return false;
}
-}
+ /**
+ * @since 1.18
+ */
+ public function getSearchEngine() {
+ if ( $this->searchEngine === null ) {
+ $this->searchEngine = SearchEngine::create();
+ }
+ return $this->searchEngine;
+ }
+
+ /**
+ * Users of hook SpecialSearchSetupEngine can use this to
+ * add more params to links to not lose selection when
+ * user navigates search results.
+ * @since 1.18
+ */
+ public function setExtraParam( $key, $value ) {
+ $this->extraParams[$key] = $value;
+ }
+
+}
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
index 989e4c07..3b785018 100644
--- a/includes/specials/SpecialShortpages.php
+++ b/includes/specials/SpecialShortpages.php
@@ -29,10 +29,11 @@
*/
class ShortPagesPage extends QueryPage {
- function getName() {
- return 'Shortpages';
+ function __construct( $name = 'Shortpages' ) {
+ parent::__construct( $name );
}
+ // inexpensive?
/**
* This query is indexed as of 1.5
*/
@@ -44,29 +45,27 @@ class ShortPagesPage extends QueryPage {
return false;
}
- function getSQL() {
- global $wgContentNamespaces;
-
- $dbr = wfGetDB( DB_SLAVE );
- $page = $dbr->tableName( 'page' );
- $name = $dbr->addQuotes( $this->getName() );
-
- $forceindex = $dbr->useIndexClause("page_len");
-
- if ($wgContentNamespaces)
- $nsclause = "page_namespace IN (" . $dbr->makeList($wgContentNamespaces) . ")";
- else
- $nsclause = "page_namespace = " . NS_MAIN;
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'page' ),
+ 'fields' => array ( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'page_len AS value' ),
+ 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'page_is_redirect' => 0 ),
+ 'options' => array ( 'USE INDEX' => 'page_len' )
+ );
+ }
- return
- "SELECT $name as type,
- page_namespace as namespace,
- page_title as title,
- page_len AS value
- FROM $page $forceindex
- WHERE $nsclause AND page_is_redirect=0";
+ function getOrderFields() {
+ return array( 'page_len' );
}
+ /**
+ * @param $db DatabaseBase
+ * @param $res
+ * @return void
+ */
function preprocessResults( $db, $res ) {
# There's no point doing a batch check if we aren't caching results;
# the page must exist for it to have been pulled out of the table
@@ -87,10 +86,10 @@ class ShortPagesPage extends QueryPage {
}
function formatResult( $skin, $result ) {
- global $wgLang, $wgContLang;
- $dm = $wgContLang->getDirMark();
+ global $wgLang;
+ $dm = $wgLang->getDirMark();
- $title = Title::makeTitleSafe( $result->namespace, $result->title );
+ $title = Title::makeTitle( $result->namespace, $result->title );
if ( !$title ) {
return '<!-- Invalid title ' . htmlspecialchars( "{$result->namespace}:{$result->title}" ). '-->';
}
@@ -110,14 +109,3 @@ class ShortPagesPage extends QueryPage {
: "<del>({$hlink}) {$dm}{$plink} {$dm}[{$size}]</del>";
}
}
-
-/**
- * constructor
- */
-function wfSpecialShortpages() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $spp = new ShortPagesPage();
-
- return $spp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php
index 19bc6b00..13bc4c2b 100644
--- a/includes/specials/SpecialSpecialpages.php
+++ b/includes/specials/SpecialSpecialpages.php
@@ -33,10 +33,11 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
function execute( $par ) {
- global $wgOut;
+ $out = $this->getOutput();
$this->setHeaders();
$this->outputHeader();
- $wgOut->allowClickjacking();
+ $out->allowClickjacking();
+ $out->addModuleStyles( 'mediawiki.special' );
$groups = $this->getPageGroups();
@@ -50,7 +51,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
private function getPageGroups() {
global $wgSortSpecialPages;
- $pages = SpecialPage::getUsablePages();
+ $pages = SpecialPageFactory::getUsablePages();
if( !count( $pages ) ) {
# Yeah, that was pointless. Thanks for coming.
@@ -61,11 +62,11 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
$groups = array();
foreach ( $pages as $page ) {
if ( $page->isListed() ) {
- $group = SpecialPage::getGroup( $page );
+ $group = SpecialPageFactory::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(), $page->isExpensive() );
}
}
@@ -87,52 +88,59 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
}
private function outputPageList( $groups ) {
- global $wgUser, $wgOut;
+ global $wgMiserMode;
+ $out = $this->getOutput();
- $sk = $wgUser->getSkin();
$includesRestrictedPages = false;
+ $includesCachedPages = 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(
+ $out->wrapWikiMsg( "<h2 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h2>\n", "specialpages-group-$group" );
+ $out->addHTML(
Html::openElement( 'table', array( 'style' => 'width:100%;', 'class' => 'mw-specialpages-table' ) ) ."\n" .
Html::openElement( '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 ) );
+ list( $title, $restricted, $expensive) = $specialpage;
+
+ $pageClasses = array();
+ if ( $expensive && $wgMiserMode ){
+ $includesCachedPages = true;
+ $pageClasses[] = 'mw-specialpagecached';
+ }
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" );
+ $pageClasses[] = 'mw-specialpagerestricted';
}
+ $link = Linker::linkKnown( $title , htmlspecialchars( $desc ) );
+ $out->addHTML( Html::rawElement( 'li', array( 'class' => implode( ' ', $pageClasses ) ), $link ) . "\n" );
+
# Split up the larger groups
$count++;
if( $total > 3 && $count == $middle ) {
- $wgOut->addHTML(
+ $out->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(
+ $out->addHTML(
Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) .
Html::element( 'td', array( 'style' => 'width:30%' ), '' ) .
Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n"
);
}
- if ( $includesRestrictedPages ) {
- $wgOut->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' );
+ if ( $includesRestrictedPages || $includesCachedPages ) {
+ $out->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' );
}
}
}
diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php
index b0d0246e..5def4da5 100644
--- a/includes/specials/SpecialStatistics.php
+++ b/includes/specials/SpecialStatistics.php
@@ -28,20 +28,20 @@
* @ingroup SpecialPage
*/
class SpecialStatistics extends SpecialPage {
-
+
private $views, $edits, $good, $images, $total, $users,
- $activeUsers, $admins = 0;
-
+ $activeUsers = 0;
+
public function __construct() {
parent::__construct( 'Statistics' );
}
-
+
public function execute( $par ) {
- global $wgOut, $wgMemc;
- global $wgDisableCounters, $wgMiserMode;
-
+ global $wgMemc, $wgDisableCounters, $wgMiserMode;
+
$this->setHeaders();
-
+ $this->getOutput()->addModuleStyles( 'mediawiki.special' );
+
$this->views = SiteStats::views();
$this->edits = SiteStats::edits();
$this->good = SiteStats::articles();
@@ -49,15 +49,14 @@ class SpecialStatistics extends SpecialPage {
$this->total = SiteStats::pages();
$this->users = SiteStats::users();
$this->activeUsers = SiteStats::activeUsers();
- $this->admins = SiteStats::numberingroup('sysop');
$this->hook = '';
-
+
# Staticic - views
$viewsStats = '';
if( !$wgDisableCounters ) {
$viewsStats = $this->getViewsStats();
}
-
+
# Set active user count
if( !$wgMiserMode ) {
$key = wfMemcKey( 'sitestats', 'activeusers-updated' );
@@ -88,7 +87,7 @@ class SpecialStatistics extends SpecialPage {
if( !$wgDisableCounters && !$wgMiserMode ) {
$text .= $this->getMostViewedPages();
}
-
+
# Statistic - other
$extraStats = array();
if( wfRunHooks( 'SpecialStatsAddExtra', array( &$extraStats ) ) ) {
@@ -98,12 +97,12 @@ class SpecialStatistics extends SpecialPage {
$text .= Xml::closeElement( 'table' );
# Customizable footer
- $footer = wfMsgExt( 'statistics-footer', array('parseinline') );
- if( !wfEmptyMsg( 'statistics-footer', $footer ) && $footer != '' ) {
- $text .= "\n" . $footer;
+ $footer = wfMessage( 'statistics-footer' );
+ if ( !$footer->isBlank() ) {
+ $text .= "\n" . $footer->parse();
}
- $wgOut->addHTML( $text );
+ $this->getOutput()->addHTML( $text );
}
/**
@@ -117,80 +116,72 @@ class SpecialStatistics extends SpecialPage {
*/
private function formatRow( $text, $number, $trExtraParams = array(), $descMsg = '', $descMsgParam = '' ) {
if( $descMsg ) {
- $descriptionText = wfMsgExt( $descMsg, array( 'parseinline' ), $descMsgParam );
- if ( !wfEmptyMsg( $descMsg, $descriptionText ) ) {
- $descriptionText = " ($descriptionText)";
- $text .= "<br />" . Xml::element( 'small', array( 'class' => 'mw-statistic-desc'),
- $descriptionText );
+ $msg = wfMessage( $descMsg, $descMsgParam );
+ if ( $msg->exists() ) {
+ $descriptionText = $msg->parse();
+ $text .= "<br />" . Xml::element( 'small', array( 'class' => 'mw-statistic-desc'),
+ " ($descriptionText)" );
}
}
- return
- Html::rawElement( 'tr', $trExtraParams,
+ return Html::rawElement( 'tr', $trExtraParams,
Html::rawElement( 'td', array(), $text ) .
Html::rawElement( 'td', array( 'class' => 'mw-statistics-numbers' ), $number )
);
}
-
+
/**
* Each of these methods is pretty self-explanatory, get a particular
* row for the table of statistics
* @return string
*/
private function getPageStats() {
- global $wgLang;
return Xml::openElement( 'tr' ) .
Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-pages', array( 'parseinline' ) ) ) .
Xml::closeElement( 'tr' ) .
$this->formatRow( wfMsgExt( 'statistics-articles', array( 'parseinline' ) ),
- $wgLang->formatNum( $this->good ),
+ $this->getLang()->formatNum( $this->good ),
array( 'class' => 'mw-statistics-articles' ) ) .
$this->formatRow( wfMsgExt( 'statistics-pages', array( 'parseinline' ) ),
- $wgLang->formatNum( $this->total ),
+ $this->getLang()->formatNum( $this->total ),
array( 'class' => 'mw-statistics-pages' ),
'statistics-pages-desc' ) .
$this->formatRow( wfMsgExt( 'statistics-files', array( 'parseinline' ) ),
- $wgLang->formatNum( $this->images ),
+ $this->getLang()->formatNum( $this->images ),
array( 'class' => 'mw-statistics-files' ) );
}
private function getEditStats() {
- global $wgLang;
return Xml::openElement( 'tr' ) .
Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-edits', array( 'parseinline' ) ) ) .
Xml::closeElement( 'tr' ) .
$this->formatRow( wfMsgExt( 'statistics-edits', array( 'parseinline' ) ),
- $wgLang->formatNum( $this->edits ),
+ $this->getLang()->formatNum( $this->edits ),
array( 'class' => 'mw-statistics-edits' ) ) .
$this->formatRow( wfMsgExt( 'statistics-edits-average', array( 'parseinline' ) ),
- $wgLang->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ),
+ $this->getLang()->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ),
array( 'class' => 'mw-statistics-edits-average' ) );
}
private function getUserStats() {
- global $wgLang, $wgUser, $wgActiveUserDays;
- $sk = $wgUser->getSkin();
+ global $wgActiveUserDays;
return Xml::openElement( 'tr' ) .
Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-users', array( 'parseinline' ) ) ) .
Xml::closeElement( 'tr' ) .
$this->formatRow( wfMsgExt( 'statistics-users', array( 'parseinline' ) ),
- $wgLang->formatNum( $this->users ),
+ $this->getLang()->formatNum( $this->users ),
array( 'class' => 'mw-statistics-users' ) ) .
$this->formatRow( wfMsgExt( 'statistics-users-active', array( 'parseinline' ) ) . ' ' .
- $sk->link(
+ Linker::linkKnown(
SpecialPage::getTitleFor( 'Activeusers' ),
- wfMsgHtml( 'listgrouprights-members' ),
- array(),
- array(),
- 'known'
+ wfMsgHtml( 'listgrouprights-members' )
),
- $wgLang->formatNum( $this->activeUsers ),
+ $this->getLang()->formatNum( $this->activeUsers ),
array( 'class' => 'mw-statistics-users-active' ),
'statistics-users-active-desc',
- $wgLang->formatNum( $wgActiveUserDays ) );
+ $this->getLang()->formatNum( $wgActiveUserDays ) );
}
private function getGroupStats() {
- global $wgGroupPermissions, $wgImplicitGroups, $wgLang, $wgUser;
- $sk = $wgUser->getSkin();
+ global $wgGroupPermissions, $wgImplicitGroups;
$text = '';
foreach( $wgGroupPermissions as $group => $permissions ) {
# Skip generic * and implicit groups
@@ -198,29 +189,28 @@ class SpecialStatistics extends SpecialPage {
continue;
}
$groupname = htmlspecialchars( $group );
- $msg = wfMsg( 'group-' . $groupname );
- if ( wfEmptyMsg( 'group-' . $groupname, $msg ) || $msg == '' ) {
+ $msg = wfMessage( 'group-' . $groupname );
+ if ( $msg->isBlank() ) {
$groupnameLocalized = $groupname;
} else {
- $groupnameLocalized = $msg;
+ $groupnameLocalized = $msg->text();
}
- $msg = wfMsgForContent( 'grouppage-' . $groupname );
- if ( wfEmptyMsg( 'grouppage-' . $groupname, $msg ) || $msg == '' ) {
+ $msg = wfMessage( 'grouppage-' . $groupname )->inContentLanguage();
+ if ( $msg->isBlank() ) {
$grouppageLocalized = MWNamespace::getCanonicalName( NS_PROJECT ) . ':' . $groupname;
} else {
- $grouppageLocalized = $msg;
+ $grouppageLocalized = $msg->text();
}
$linkTarget = Title::newFromText( $grouppageLocalized );
- $grouppage = $sk->link(
+ $grouppage = Linker::link(
$linkTarget,
htmlspecialchars( $groupnameLocalized )
);
- $grouplink = $sk->link(
+ $grouplink = Linker::linkKnown(
SpecialPage::getTitleFor( 'Listusers' ),
wfMsgHtml( 'listgrouprights-members' ),
array(),
- array( 'group' => $group ),
- 'known'
+ array( 'group' => $group )
);
# Add a class when a usergroup contains no members to allow hiding these rows
$classZero = '';
@@ -229,31 +219,28 @@ class SpecialStatistics extends SpecialPage {
$classZero = ' statistics-group-zero';
}
$text .= $this->formatRow( $grouppage . ' ' . $grouplink,
- $wgLang->formatNum( $countUsers ),
+ $this->getLang()->formatNum( $countUsers ),
array( 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) . $classZero ) );
}
return $text;
}
private function getViewsStats() {
- global $wgLang;
return Xml::openElement( 'tr' ) .
Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-views', array( 'parseinline' ) ) ) .
Xml::closeElement( 'tr' ) .
$this->formatRow( wfMsgExt( 'statistics-views-total', array( 'parseinline' ) ),
- $wgLang->formatNum( $this->views ),
+ $this->getLang()->formatNum( $this->views ),
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->getLang()->formatNum( sprintf( '%.2f', $this->edits ?
$this->views / $this->edits : 0 ) ),
array ( 'class' => 'mw-statistics-views-peredit' ) );
}
private function getMostViewedPages() {
- global $wgLang, $wgUser;
$text = '';
$dbr = wfGetDB( DB_SLAVE );
- $sk = $wgUser->getSkin();
$res = $dbr->select(
'page',
array(
@@ -278,9 +265,9 @@ class SpecialStatistics extends SpecialPage {
foreach ( $res as $row ) {
$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
if( $title instanceof Title ) {
- $text .= $this->formatRow( $sk->link( $title ),
- $wgLang->formatNum( $row->page_counter ) );
-
+ $text .= $this->formatRow( Linker::link( $title ),
+ $this->getLang()->formatNum( $row->page_counter ) );
+
}
}
$res->free();
@@ -289,22 +276,20 @@ class SpecialStatistics extends SpecialPage {
}
private function getOtherStats( $stats ) {
- global $wgLang;
-
if ( !count( $stats ) )
return '';
$return = Xml::openElement( 'tr' ) .
Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-hooks', array( 'parseinline' ) ) ) .
Xml::closeElement( 'tr' );
-
+
foreach( $stats as $name => $number ) {
$name = htmlspecialchars( $name );
$number = htmlspecialchars( $number );
-
- $return .= $this->formatRow( $name, $wgLang->formatNum( $number ), array( 'class' => 'mw-statistics-hook' ) );
+
+ $return .= $this->formatRow( $name, $this->getLang()->formatNum( $number ), array( 'class' => 'mw-statistics-hook' ) );
}
-
+
return $return;
}
}
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index c2aecf47..66a89e94 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -48,7 +48,8 @@ class SpecialTags extends SpecialPage {
Xml::tags( 'th', null, wfMsgExt( 'tags-hitcount-header', 'parseinline' ) )
);
$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' ) );
+ $res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) AS hitcount' ),
+ array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) );
foreach ( $res as $row ) {
$html .= $this->doTagRow( $row->ct_tag, $row->hitcount );
@@ -62,10 +63,9 @@ class SpecialTags extends SpecialPage {
}
function doTagRow( $tag, $hitcount ) {
- static $sk=null, $doneTags=array();
- if (!$sk) {
- global $wgUser;
- $sk = $wgUser->getSkin();
+ static $sk = null, $doneTags = array();
+ if ( !$sk ) {
+ $sk = $this->getSkin();
}
if ( in_array( $tag, $doneTags ) ) {
@@ -73,7 +73,7 @@ class SpecialTags extends SpecialPage {
}
global $wgLang;
-
+
$newRow = '';
$newRow .= Xml::tags( 'td', null, Xml::element( 'tt', null, $tag ) );
@@ -81,8 +81,8 @@ class SpecialTags extends SpecialPage {
$disp .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), wfMsgHtml( 'tags-edit' ) ) . ')';
$newRow .= Xml::tags( 'td', null, $disp );
- $desc = wfMsgExt( "tag-$tag-description", 'parseinline' );
- $desc = wfEmptyMsg( "tag-$tag-description", $desc ) ? '' : $desc;
+ $msg = wfMessage( "tag-$tag-description" );
+ $desc = !$msg->exists() ? '' : $msg->parse();
$desc .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), wfMsgHtml( 'tags-edit' ) ) . ')';
$newRow .= Xml::tags( 'td', null, $desc );
diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php
new file mode 100644
index 00000000..521c1775
--- /dev/null
+++ b/includes/specials/SpecialUnblock.php
@@ -0,0 +1,209 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * A special page for unblocking users
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialUnblock extends SpecialPage {
+
+ protected $target;
+ protected $type;
+ protected $block;
+
+ public function __construct(){
+ parent::__construct( 'Unblock', 'block' );
+ }
+
+ public function execute( $par ){
+ global $wgUser, $wgOut, $wgRequest;
+
+ # Check permissions
+ if( !$this->userCanExecute( $wgUser ) ) {
+ $this->displayRestrictionError();
+ return;
+ }
+
+ # Check for database lock
+ if( wfReadOnly() ) {
+ throw new ReadOnlyError;
+ }
+
+ list( $this->target, $this->type ) = SpecialBlock::getTargetAndType( $par, $wgRequest );
+ $this->block = Block::newFromTarget( $this->target );
+
+ # bug 15810: blocked admins should have limited access here. This won't allow sysops
+ # to remove autoblocks on themselves, but they should have ipblock-exempt anyway
+ $status = SpecialBlock::checkUnblockSelf( $this->target );
+ if ( $status !== true ) {
+ throw new ErrorPageError( 'badaccess', $status );
+ }
+
+ $wgOut->setPageTitle( wfMsg( 'unblockip' ) );
+ $wgOut->addModules( 'mediawiki.special' );
+
+ $form = new HTMLForm( $this->getFields(), $this->getContext() );
+ $form->setWrapperLegend( wfMsg( 'unblockip' ) );
+ $form->setSubmitCallback( array( __CLASS__, 'processUnblock' ) );
+ $form->setSubmitText( wfMsg( 'ipusubmit' ) );
+ $form->addPreText( wfMsgExt( 'unblockiptext', 'parse' ) );
+
+ if( $form->show() ){
+ switch( $this->type ){
+ case Block::TYPE_USER:
+ case Block::TYPE_IP:
+ $wgOut->addWikiMsg( 'unblocked', $this->target );
+ break;
+ case Block::TYPE_RANGE:
+ $wgOut->addWikiMsg( 'unblocked-range', $this->target );
+ break;
+ case Block::TYPE_ID:
+ case Block::TYPE_AUTO:
+ $wgOut->addWikiMsg( 'unblocked-id', $this->target );
+ break;
+ }
+ }
+ }
+
+ protected function getFields(){
+ $fields = array(
+ 'Target' => array(
+ 'type' => 'text',
+ 'label-message' => 'ipadressorusername',
+ 'tabindex' => '1',
+ 'size' => '45',
+ 'required' => true,
+ ),
+ 'Name' => array(
+ 'type' => 'info',
+ 'label-message' => 'ipadressorusername',
+ ),
+ 'Reason' => array(
+ 'type' => 'text',
+ 'label-message' => 'ipbreason',
+ )
+ );
+
+ if( $this->block instanceof Block ){
+ list( $target, $type ) = $this->block->getTargetAndType();
+
+ # Autoblocks are logged as "autoblock #123 because the IP was recently used by
+ # User:Foo, and we've just got any block, auto or not, that applies to a target
+ # the user has specified. Someone could be fishing to connect IPs to autoblocks,
+ # so don't show any distinction between unblocked IPs and autoblocked IPs
+ if( $type == Block::TYPE_AUTO && $this->type == Block::TYPE_IP ){
+ $fields['Target']['default'] = $this->target;
+ unset( $fields['Name'] );
+
+ } else {
+ $fields['Target']['default'] = $target;
+ $fields['Target']['type'] = 'hidden';
+ switch( $type ){
+ case Block::TYPE_USER:
+ case Block::TYPE_IP:
+ $skin = $this->getSkin();
+ $fields['Name']['default'] = $skin->link(
+ $target->getUserPage(),
+ $target->getName()
+ );
+ $fields['Name']['raw'] = true;
+ break;
+
+ case Block::TYPE_RANGE:
+ $fields['Name']['default'] = $target;
+ break;
+
+ case Block::TYPE_AUTO:
+ $fields['Name']['default'] = $this->block->getRedactedName();
+ $fields['Name']['raw'] = true;
+ # Don't expose the real target of the autoblock
+ $fields['Target']['default'] = "#{$this->target}";
+ break;
+ }
+ }
+
+ } else {
+ $fields['Target']['default'] = $this->target;
+ unset( $fields['Name'] );
+ }
+ return $fields;
+ }
+
+ /**
+ * Process the form
+ * @return Array( Array(message key, parameters) ) on failure, True on success
+ */
+ public static function processUnblock( array $data ){
+ global $wgUser;
+
+ $target = $data['Target'];
+ $block = Block::newFromTarget( $data['Target'] );
+
+ if( !$block instanceof Block ){
+ return array( array( 'ipb_cant_unblock', $target ) );
+ }
+
+ # If the specified IP is a single address, and the block is a range block, don't
+ # unblock the whole range.
+ list( $target, $type ) = SpecialBlock::getTargetAndType( $target );
+ if( $block->getType() == Block::TYPE_RANGE && $type == Block::TYPE_IP ) {
+ $range = $block->getTarget();
+ return array( array( 'ipb_blocked_as_range', $target, $range ) );
+ }
+
+ # If the name was hidden and the blocking user cannot hide
+ # names, then don't allow any block removals...
+ if( !$wgUser->isAllowed( 'hideuser' ) && $block->mHideName ) {
+ return array( 'unblock-hideuser' );
+ }
+
+ # Delete block
+ if ( !$block->delete() ) {
+ return array( 'ipb_cant_unblock', htmlspecialchars( $block->getTarget() ) );
+ }
+
+ # Unset _deleted fields as needed
+ if( $block->mHideName ) {
+ # Something is deeply FUBAR if this is not a User object, but who knows?
+ $id = $block->getTarget() instanceof User
+ ? $block->getTarget()->getID()
+ : User::idFromName( $block->getTarget() );
+
+ RevisionDeleteUser::unsuppressUserName( $block->getTarget(), $id );
+ }
+
+ # Redact the name (IP address) for autoblocks
+ if ( $block->getType() == Block::TYPE_AUTO ) {
+ $page = Title::makeTitle( NS_USER, '#' . $block->getId() );
+ } else {
+ $page = $block->getTarget() instanceof User
+ ? $block->getTarget()->getUserpage()
+ : Title::makeTitle( NS_USER, $block->getTarget() );
+ }
+
+ # Make log entry
+ $log = new LogPage( 'block' );
+ $log->addEntry( 'unblock', $page, $data['Reason'] );
+
+ return true;
+ }
+}
diff --git a/includes/specials/SpecialUncategorizedcategories.php b/includes/specials/SpecialUncategorizedcategories.php
index 9574af70..70d98df9 100644
--- a/includes/specials/SpecialUncategorizedcategories.php
+++ b/includes/specials/SpecialUncategorizedcategories.php
@@ -27,22 +27,8 @@
* @ingroup SpecialPage
*/
class UncategorizedCategoriesPage extends UncategorizedPagesPage {
- function __construct() {
+ function __construct( $name = 'Uncategorizedcategories' ) {
+ parent::__construct( $name );
$this->requestedNamespace = NS_CATEGORY;
}
-
- function getName() {
- return "Uncategorizedcategories";
- }
-}
-
-/**
- * constructor
- */
-function wfSpecialUncategorizedcategories() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $lpp = new UncategorizedCategoriesPage();
-
- return $lpp->doQuery( $offset, $limit );
}
diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php
index c4254039..3efed747 100644
--- a/includes/specials/SpecialUncategorizedimages.php
+++ b/includes/specials/SpecialUncategorizedimages.php
@@ -27,10 +27,11 @@
*
* @ingroup SpecialPage
*/
+// @todo FIXME: Use an instance of UncategorizedPagesPage or something
class UncategorizedImagesPage extends ImageQueryPage {
- function getName() {
- return 'Uncategorizedimages';
+ function __construct( $name = 'Uncategorizedimages' ) {
+ parent::__construct( $name );
}
function sortDescending() {
@@ -45,21 +46,18 @@ class UncategorizedImagesPage extends ImageQueryPage {
return false;
}
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' );
- $ns = NS_FILE;
-
- return "SELECT 'Uncategorizedimages' AS type, page_namespace AS namespace,
- page_title AS title, page_title AS value
- FROM {$page} LEFT JOIN {$categorylinks} ON page_id = cl_from
- WHERE cl_from IS NULL AND page_namespace = {$ns} AND page_is_redirect = 0";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array( 'page', 'categorylinks' ),
+ 'fields' => array( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'page_title AS value' ),
+ 'conds' => array( 'cl_from IS NULL',
+ 'page_namespace' => NS_FILE,
+ 'page_is_redirect' => 0 ),
+ 'join_conds' => array( 'categorylinks' => array(
+ 'LEFT JOIN', 'cl_from=page_id' ) )
+ );
}
}
-
-function wfSpecialUncategorizedimages() {
- $uip = new UncategorizedImagesPage();
- list( $limit, $offset ) = wfCheckLimits();
- return $uip->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php
index c7fef5d2..08a69448 100644
--- a/includes/specials/SpecialUncategorizedpages.php
+++ b/includes/specials/SpecialUncategorizedpages.php
@@ -26,11 +26,12 @@
*
* @ingroup SpecialPage
*/
+// @todo FIXME: Make $requestedNamespace selectable, unify all subclasses into one
class UncategorizedPagesPage extends PageQueryPage {
- var $requestedNamespace = NS_MAIN;
+ protected $requestedNamespace = false;
- function getName() {
- return "Uncategorizedpages";
+ function __construct( $name = 'Uncategorizedpages' ) {
+ parent::__construct( $name );
}
function sortDescending() {
@@ -42,32 +43,27 @@ class UncategorizedPagesPage extends PageQueryPage {
}
function isSyndicated() { return false; }
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' );
- $name = $dbr->addQuotes( $this->getName() );
-
- return
- "
- SELECT
- $name as type,
- page_namespace AS namespace,
- page_title AS title,
- page_title AS value
- FROM $page
- LEFT JOIN $categorylinks ON page_id=cl_from
- WHERE cl_from IS NULL AND page_namespace={$this->requestedNamespace} AND page_is_redirect=0
- ";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'page', 'categorylinks' ),
+ 'fields' => array ( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'page_title AS value' ),
+ // default for page_namespace is all content namespaces (if requestedNamespace is false)
+ // otherwise, page_namespace is requestedNamespace
+ 'conds' => array ( 'cl_from IS NULL',
+ 'page_namespace' => ( $this->requestedNamespace!==false ? $this->requestedNamespace : MWNamespace::getContentNamespaces() ),
+ 'page_is_redirect' => 0 ),
+ 'join_conds' => array ( 'categorylinks' => array (
+ 'LEFT JOIN', 'cl_from = page_id' ) )
+ );
}
-}
-/**
- * constructor
- */
-function wfSpecialUncategorizedpages() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $lpp = new UncategorizedPagesPage();
-
- return $lpp->doQuery( $offset, $limit );
+ function getOrderFields() {
+ // For some crazy reason ordering by a constant
+ // causes a filesort
+ if( $this->requestedNamespace === false && count( MWNamespace::getContentNamespaces() ) > 1 )
+ return array( 'page_namespace', 'page_title' );
+ return array( 'page_title' );
+ }
}
diff --git a/includes/specials/SpecialUncategorizedtemplates.php b/includes/specials/SpecialUncategorizedtemplates.php
index aa4e979d..af038fa8 100644
--- a/includes/specials/SpecialUncategorizedtemplates.php
+++ b/includes/specials/SpecialUncategorizedtemplates.php
@@ -29,20 +29,8 @@
* @ingroup SpecialPage
*/
class UncategorizedTemplatesPage extends UncategorizedPagesPage {
-
- var $requestedNamespace = NS_TEMPLATE;
-
- public function getName() {
- return 'Uncategorizedtemplates';
+ public function __construct( $name = 'Uncategorizedtemplates' ) {
+ parent::__construct( $name );
+ $this->requestedNamespace = NS_TEMPLATE;
}
-
-}
-
-/**
- * Main execution point
- */
-function wfSpecialUncategorizedtemplates() {
- list( $limit, $offset ) = wfCheckLimits();
- $utp = new UncategorizedTemplatesPage();
- $utp->doQuery( $offset, $limit );
}
diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php
index 1cf61d26..d4636e74 100644
--- a/includes/specials/SpecialUndelete.php
+++ b/includes/specials/SpecialUndelete.php
@@ -27,6 +27,10 @@
* @ingroup SpecialPage
*/
class PageArchive {
+
+ /**
+ * @var Title
+ */
protected $title;
var $fileStatus;
@@ -76,6 +80,11 @@ class PageArchive {
return self::listPages( $dbr, $conds );
}
+ /**
+ * @param $dbr DatabaseBase
+ * @param $condition
+ * @return bool|ResultWrapper
+ */
protected static function listPages( $dbr, $condition ) {
return $dbr->resultObject(
$dbr->select(
@@ -105,9 +114,12 @@ class PageArchive {
function listRevisions() {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'archive',
- array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_len', 'ar_deleted' ),
+ array(
+ 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
+ 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id'
+ ),
array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey() ),
+ 'ar_title' => $this->title->getDBkey() ),
'PageArchive::listRevisions',
array( 'ORDER BY' => 'ar_timestamp DESC' ) );
$ret = $dbr->resultObject( $res );
@@ -155,19 +167,6 @@ class PageArchive {
}
/**
- * Fetch (and decompress if necessary) the stored text for the deleted
- * revision of the page with the given timestamp.
- *
- * @param $timestamp String
- * @return String
- * @deprecated Use getRevision() for more flexible information
- */
- function getRevisionText( $timestamp ) {
- $rev = $this->getRevision( $timestamp );
- return $rev ? $rev->getText() : null;
- }
-
- /**
* Return a Revision object containing data for the deleted revision.
* Note that the result *may* or *may not* have a null page ID.
*
@@ -190,8 +189,8 @@ class PageArchive {
'ar_deleted',
'ar_len' ),
array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- 'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
+ 'ar_title' => $this->title->getDBkey(),
+ 'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
__METHOD__ );
if( $row ) {
return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleId() ) );
@@ -217,8 +216,8 @@ class PageArchive {
$row = $dbr->selectRow( 'archive',
'ar_timestamp',
array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey(),
- 'ar_timestamp < ' .
+ 'ar_title' => $this->title->getDBkey(),
+ 'ar_timestamp < ' .
$dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
__METHOD__,
array(
@@ -263,7 +262,7 @@ class PageArchive {
if( is_null( $row->ar_text_id ) ) {
// An old row from MediaWiki 1.4 or previous.
// Text is embedded in this row in classic compression format.
- return Revision::getRevisionText( $row, "ar_" );
+ return Revision::getRevisionText( $row, 'ar_' );
} else {
// New-style: keyed to the text storage backend.
$dbr = wfGetDB( DB_SLAVE );
@@ -275,7 +274,6 @@ class PageArchive {
}
}
-
/**
* Fetch (and decompress if necessary) the stored text of the most
* recently edited deleted revision of the page.
@@ -289,7 +287,7 @@ class PageArchive {
$row = $dbr->selectRow( 'archive',
array( 'ar_text', 'ar_flags', 'ar_text_id' ),
array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey() ),
+ 'ar_title' => $this->title->getDBkey() ),
__METHOD__,
array( 'ORDER BY' => 'ar_timestamp DESC' ) );
if( $row ) {
@@ -308,8 +306,8 @@ class PageArchive {
$dbr = wfGetDB( DB_SLAVE );
$n = $dbr->selectField( 'archive', 'COUNT(ar_title)',
array( 'ar_namespace' => $this->title->getNamespace(),
- 'ar_title' => $this->title->getDBkey() ) );
- return ($n > 0);
+ 'ar_title' => $this->title->getDBkey() ) );
+ return ( $n > 0 );
}
/**
@@ -336,6 +334,9 @@ class PageArchive {
if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
$img = wfLocalFile( $this->title );
$this->fileStatus = $img->restore( $fileVersions, $unsuppress );
+ if ( !$this->fileStatus->isOk() ) {
+ return false;
+ }
$filesRestored = $this->fileStatus->successCount;
} else {
$filesRestored = 0;
@@ -343,8 +344,9 @@ class PageArchive {
if( $restoreText ) {
$textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
- if($textRestored === false) // It must be one of UNDELETE_*
+ if( $textRestored === false ) { // It must be one of UNDELETE_*
return false;
+ }
} else {
$textRestored = 0;
}
@@ -368,11 +370,12 @@ class PageArchive {
return false;
}
- if( trim( $comment ) != '' )
+ if( trim( $comment ) != '' ) {
$reason .= wfMsgForContent( 'colon-separator' ) . $comment;
+ }
$log->addEntry( 'restore', $this->title, $reason );
- return array($textRestored, $filesRestored, $reason);
+ return array( $textRestored, $filesRestored, $reason );
}
/**
@@ -387,19 +390,24 @@ class PageArchive {
* @return Mixed: number of revisions restored or false on failure
*/
private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
- if ( wfReadOnly() )
+ if ( wfReadOnly() ) {
return false;
+ }
$restoreAll = empty( $timestamps );
$dbw = wfGetDB( DB_MASTER );
# Does this page already exist? We'll have to update it...
$article = new Article( $this->title );
+ # Load latest data for the current page (bug 31179)
+ $article->loadPageData( 'fromdbmaster' );
+ $oldcountable = $article->isCountable();
+
$options = 'FOR UPDATE'; // lock page
$page = $dbw->selectRow( 'page',
array( 'page_id', 'page_latest' ),
array( 'page_namespace' => $this->title->getNamespace(),
- 'page_title' => $this->title->getDBkey() ),
+ 'page_title' => $this->title->getDBkey() ),
__METHOD__,
$options
);
@@ -463,7 +471,7 @@ class PageArchive {
$ret = $dbw->resultObject( $result );
$rev_count = $dbw->numRows( $result );
if( !$rev_count ) {
- wfDebug( __METHOD__.": no revisions to restore\n" );
+ wfDebug( __METHOD__ . ": no revisions to restore\n" );
return false; // ???
}
@@ -473,7 +481,7 @@ class PageArchive {
if( $makepage ) {
// Check the state of the newest to-be version...
- if( !$unsuppress && ($row->ar_deleted & Revision::DELETED_TEXT) ) {
+ if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
return false; // we can't leave the current revision like this!
}
// Safe to insert now...
@@ -483,7 +491,7 @@ class PageArchive {
// Check if a deleted revision will become the current revision...
if( $row->ar_timestamp > $previousTimestamp ) {
// Check the state of the newest to-be version...
- if( !$unsuppress && ($row->ar_deleted & Revision::DELETED_TEXT) ) {
+ if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
return false; // we can't leave the current revision like this!
}
}
@@ -495,8 +503,11 @@ class PageArchive {
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__ );
- if( $exists ) continue; // don't throw DB errors
+ $exists = $dbw->selectField( 'revision', '1',
+ array( 'rev_id' => $row->ar_rev_id ), __METHOD__ );
+ if( $exists ) {
+ continue; // don't throw DB errors
+ }
}
// Insert one revision at a time...maintaining deletion status
// unless we are specifically removing all restrictions...
@@ -520,38 +531,33 @@ class PageArchive {
__METHOD__ );
// Was anything restored at all?
- if( $restored == 0 )
+ if ( $restored == 0 ) {
return 0;
+ }
- if( $revision ) {
- // Attach the latest revision to the page...
- $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId );
- if( $newid || $wasnew ) {
- // Update site stats, link tables, etc
- $article->createUpdates( $revision );
- }
+ $created = (bool)$newid;
- if( $newid ) {
- wfRunHooks( 'ArticleUndelete', array( &$this->title, true, $comment ) );
- Article::onArticleCreate( $this->title );
- } else {
- wfRunHooks( 'ArticleUndelete', array( &$this->title, false, $comment ) );
- Article::onArticleEdit( $this->title );
- }
+ // Attach the latest revision to the page...
+ $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId );
+ if ( $created || $wasnew ) {
+ // Update site stats, link tables, etc
+ $user = User::newFromName( $revision->getRawUserText(), false );
+ $article->doEditUpdates( $revision, $user, array( 'created' => $created, 'oldcountable' => $oldcountable ) );
+ }
- if( $this->title->getNamespace() == NS_FILE ) {
- $update = new HTMLCacheUpdate( $this->title, 'imagelinks' );
- $update->doUpdate();
- }
- } else {
- // Revision couldn't be created. This is very weird
- wfDebug( "Undelete: unknown error...\n" );
- return false;
+ wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment ) );
+
+ if( $this->title->getNamespace() == NS_FILE ) {
+ $update = new HTMLCacheUpdate( $this->title, 'imagelinks' );
+ $update->doUpdate();
}
return $restored;
}
+ /**
+ * @return Status
+ */
function getFileStatus() { return $this->fileStatus; }
}
@@ -561,47 +567,47 @@ class PageArchive {
*
* @ingroup SpecialPage
*/
-class UndeleteForm extends SpecialPage {
- var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mTargetObj;
- var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken, $mRequest;
+class SpecialUndelete extends SpecialPage {
+ var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mFilename;
+ var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken;
- function __construct( $request = null ) {
- parent::__construct( 'Undelete', 'deletedhistory' );
+ /**
+ * @var Title
+ */
+ var $mTargetObj;
- if ( $request === null ) {
- global $wgRequest;
- $this->mRequest = $wgRequest;
- } else {
- $this->mRequest = $request;
- }
+ function __construct() {
+ parent::__construct( 'Undelete', 'deletedhistory' );
}
function loadRequest() {
- global $wgUser;
- $this->mAction = $this->mRequest->getVal( 'action' );
- $this->mTarget = $this->mRequest->getVal( 'target' );
- $this->mSearchPrefix = $this->mRequest->getText( 'prefix' );
- $time = $this->mRequest->getVal( 'timestamp' );
+ $request = $this->getRequest();
+ $user = $this->getUser();
+
+ $this->mAction = $request->getVal( 'action' );
+ $this->mTarget = $request->getVal( 'target' );
+ $this->mSearchPrefix = $request->getText( 'prefix' );
+ $time = $request->getVal( 'timestamp' );
$this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
- $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->mFilename = $request->getVal( 'file' );
+
+ $posted = $request->wasPosted() &&
+ $user->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' ) && $user->isAllowed( 'suppressrevision' );
+ $this->mToken = $request->getVal( 'token' );
+
+ if ( $user->isAllowed( 'undelete' ) && !$user->isBlocked() ) {
$this->mAllowed = true; // user can restore
$this->mCanView = true; // user can view content
- } elseif ( $wgUser->isAllowed( 'deletedtext' ) ) {
+ } elseif ( $user->isAllowed( 'deletedtext' ) ) {
$this->mAllowed = false; // user cannot restore
$this->mCanView = true; // user can view content
- } else { // user can only view the list of revisions
+ } else { // user can only view the list of revisions
$this->mAllowed = false;
$this->mCanView = false;
$this->mTimestamp = '';
@@ -611,7 +617,7 @@ class UndeleteForm extends SpecialPage {
if( $this->mRestore || $this->mInvert ) {
$timestamps = array();
$this->mFileVersions = array();
- foreach( $_REQUEST as $key => $val ) {
+ foreach( $request->getValues() as $key => $val ) {
$matches = array();
if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
array_push( $timestamps, $matches[1] );
@@ -627,10 +633,8 @@ class UndeleteForm extends SpecialPage {
}
function execute( $par ) {
- global $wgOut, $wgUser;
-
$this->setHeaders();
- if ( !$this->userCanExecute( $wgUser ) ) {
+ if ( !$this->userCanExecute( $this->getUser() ) ) {
$this->displayRestrictionError();
return;
}
@@ -638,10 +642,12 @@ class UndeleteForm extends SpecialPage {
$this->loadRequest();
+ $out = $this->getOutput();
+
if ( $this->mAllowed ) {
- $wgOut->setPagetitle( wfMsg( "undeletepage" ) );
+ $out->setPageTitle( wfMsg( 'undeletepage' ) );
} else {
- $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) );
+ $out->setPageTitle( wfMsg( 'viewdeletedpage' ) );
}
if( $par != '' ) {
@@ -649,13 +655,14 @@ class UndeleteForm extends SpecialPage {
}
if ( $this->mTarget !== '' ) {
$this->mTargetObj = Title::newFromURL( $this->mTarget );
+ $this->getSkin()->setRelevantTitle( $this->mTargetObj );
} 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' ) ) {
+ # Not all users can just browse every deleted page from the list
+ if( $this->getUser()->isAllowed( 'browsearchive' ) ) {
$this->showSearchForm();
# List undeletable articles
@@ -664,52 +671,53 @@ class UndeleteForm extends SpecialPage {
$this->showList( $result );
}
} else {
- $wgOut->addWikiMsg( 'undelete-header' );
+ $out->addWikiMsg( 'undelete-header' );
}
return;
}
if( $this->mTimestamp !== '' ) {
return $this->showRevision( $this->mTimestamp );
}
- if( $this->mFile !== null ) {
- $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile );
+ if( $this->mFilename !== null ) {
+ $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
// Check if user is allowed to see this file
if ( !$file->exists() ) {
- $wgOut->addWikiMsg( 'filedelete-nofile', $this->mFile );
+ $out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
return;
- } else if( !$file->userCan( File::DELETED_FILE ) ) {
+ } elseif( !$file->userCan( File::DELETED_FILE ) ) {
if( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
- $wgOut->permissionRequired( 'suppressrevision' );
+ $out->permissionRequired( 'suppressrevision' );
} else {
- $wgOut->permissionRequired( 'deletedtext' );
+ $out->permissionRequired( 'deletedtext' );
}
return false;
- } elseif ( !$wgUser->matchEditToken( $this->mToken, $this->mFile ) ) {
- $this->showFileConfirmationForm( $this->mFile );
+ } elseif ( !$this->getUser()->matchEditToken( $this->mToken, $this->mFilename ) ) {
+ $this->showFileConfirmationForm( $this->mFilename );
return false;
} else {
- return $this->showFile( $this->mFile );
+ return $this->showFile( $this->mFilename );
}
}
- if( $this->mRestore && $this->mAction == "submit" ) {
+ if( $this->mRestore && $this->mAction == 'submit' ) {
global $wgUploadMaintenance;
if( $wgUploadMaintenance && $this->mTargetObj && $this->mTargetObj->getNamespace() == NS_FILE ) {
- $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", array( 'filedelete-maintenance' ) );
+ $out->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", array( 'filedelete-maintenance' ) );
return;
}
return $this->undelete();
}
- if( $this->mInvert && $this->mAction == "submit" ) {
- return $this->showHistory( );
+ if( $this->mInvert && $this->mAction == 'submit' ) {
+ return $this->showHistory();
}
return $this->showHistory();
}
function showSearchForm() {
- global $wgOut, $wgScript;
- $wgOut->addWikiMsg( 'undelete-header' );
+ global $wgScript;
+
+ $this->getOutput()->addWikiMsg( 'undelete-header' );
- $wgOut->addHTML(
+ $this->getOutput()->addHTML(
Xml::openElement( 'form', array(
'method' => 'get',
'action' => $wgScript ) ) .
@@ -725,23 +733,27 @@ class UndeleteForm extends SpecialPage {
);
}
- // Generic list of deleted pages
+ /**
+ * Generic list of deleted pages
+ *
+ * @param $result ResultWrapper
+ * @return bool
+ */
private function showList( $result ) {
- global $wgLang, $wgUser, $wgOut;
+ $out = $this->getOutput();
if( $result->numRows() == 0 ) {
- $wgOut->addWikiMsg( 'undelete-no-results' );
+ $out->addWikiMsg( 'undelete-no-results' );
return;
}
- $wgOut->addWikiMsg( 'undeletepagetext', $wgLang->formatNum( $result->numRows() ) );
+ $out->addWikiMsg( 'undeletepagetext', $this->getLang()->formatNum( $result->numRows() ) );
- $sk = $wgUser->getSkin();
$undelete = $this->getTitle();
- $wgOut->addHTML( "<ul>\n" );
+ $out->addHTML( "<ul>\n" );
foreach ( $result as $row ) {
$title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
- $link = $sk->linkKnown(
+ $link = Linker::linkKnown(
$undelete,
htmlspecialchars( $title->getPrefixedText() ),
array(),
@@ -749,111 +761,102 @@ class UndeleteForm extends SpecialPage {
);
$revs = wfMsgExt( 'undeleterevisions',
array( 'parseinline' ),
- $wgLang->formatNum( $row->count ) );
- $wgOut->addHTML( "<li>{$link} ({$revs})</li>\n" );
+ $this->getLang()->formatNum( $row->count ) );
+ $out->addHTML( "<li>{$link} ({$revs})</li>\n" );
}
$result->free();
- $wgOut->addHTML( "</ul>\n" );
+ $out->addHTML( "</ul>\n" );
return true;
}
private function showRevision( $timestamp ) {
- global $wgLang, $wgUser, $wgOut;
-
- $skin = $wgUser->getSkin();
+ $out = $this->getOutput();
- if(!preg_match("/[0-9]{14}/",$timestamp)) return 0;
+ if( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
+ return 0;
+ }
$archive = new PageArchive( $this->mTargetObj );
+ wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) );
$rev = $archive->getRevision( $timestamp );
if( !$rev ) {
- $wgOut->addWikiMsg( 'undeleterevision-missing' );
+ $out->addWikiMsg( 'undeleterevision-missing' );
return;
}
- if( $rev->isDeleted(Revision::DELETED_TEXT) ) {
- if( !$rev->userCan(Revision::DELETED_TEXT) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
+ if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ $out->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\n</div>\n", 'rev-deleted-text-view' );
- $wgOut->addHTML( '<br />' );
+ $out->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
+ $out->addHTML( '<br />' );
// and we are allowed to see...
}
}
- $wgOut->setPageTitle( wfMsg( 'undeletepage' ) );
-
- $link = $skin->linkKnown(
- $this->getTitle( $this->mTargetObj->getPrefixedDBkey() ),
- htmlspecialchars( $this->mTargetObj->getPrefixedText() )
- );
+ $out->setPageTitle( wfMsg( 'undeletepage' ) );
if( $this->mDiff ) {
$previousRev = $archive->getPreviousRevision( $timestamp );
if( $previousRev ) {
$this->showDiff( $previousRev, $rev );
- if( $wgUser->getOption( 'diffonly' ) ) {
+ if( $this->getUser()->getOption( 'diffonly' ) ) {
return;
} else {
- $wgOut->addHTML( '<hr />' );
+ $out->addHTML( '<hr />' );
}
} else {
- $wgOut->addWikiMsg( 'undelete-nodiff' );
+ $out->addWikiMsg( 'undelete-nodiff' );
}
}
+ $link = Linker::linkKnown(
+ $this->getTitle( $this->mTargetObj->getPrefixedDBkey() ),
+ htmlspecialchars( $this->mTargetObj->getPrefixedText() )
+ );
+
// date and time are separate parameters to facilitate localisation.
// $time is kept for backward compat reasons.
- $time = htmlspecialchars( $wgLang->timeAndDate( $timestamp, true ) );
- $d = htmlspecialchars( $wgLang->date( $timestamp, true ) );
- $t = htmlspecialchars( $wgLang->time( $timestamp, true ) );
- $user = $skin->revUserTools( $rev );
+ $time = $this->getLang()->timeAndDate( $timestamp, true );
+ $d = $this->getLang()->date( $timestamp, true );
+ $t = $this->getLang()->time( $timestamp, true );
+ $user = Linker::revUserTools( $rev );
if( $this->mPreview ) {
$openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
} else {
$openDiv = '<div id="mw-undelete-revision">';
}
+ $out->addHTML( $openDiv );
// Revision delete links
- $canHide = $wgUser->isAllowed( 'deleterevision' );
- if( $this->mDiff ) {
- $revdlink = ''; // diffs already have revision delete links
- } else if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
- if( !$rev->userCan(Revision::DELETED_RESTRICTED ) ) {
- $revdlink = $skin->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
- } else {
- $query = array(
- 'type' => 'archive',
- 'target' => $this->mTargetObj->getPrefixedDBkey(),
- 'ids' => $rev->getTimestamp()
- );
- $revdlink = $skin->revDeleteLink( $query,
- $rev->isDeleted( File::DELETED_RESTRICTED ), $canHide );
+ if ( !$this->mDiff ) {
+ $revdel = $this->revDeleteLink( $rev );
+ if ( $revdel ) {
+ $out->addHTML( $revdel );
}
- } else {
- $revdlink = '';
}
- $wgOut->addHTML( $openDiv . $revdlink . wfMsgWikiHtml( 'undelete-revision', $link, $time, $user, $d, $t ) . '</div>' );
+ $out->addHTML( wfMessage( 'undelete-revision' )->rawParams( $link )->params(
+ $time )->rawParams( $user )->params( $d, $t )->parse() . '</div>' );
wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) );
if( $this->mPreview ) {
- //Hide [edit]s
- $popts = $wgOut->parserOptions();
+ // Hide [edit]s
+ $popts = $out->parserOptions();
$popts->setEditSection( false );
- $wgOut->parserOptions( $popts );
- $wgOut->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER ), $this->mTargetObj, true );
+ $out->parserOptions( $popts );
+ $out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER ), $this->mTargetObj, true );
}
- $wgOut->addHTML(
+ $out->addHTML(
Xml::element( 'textarea', array(
'readonly' => 'readonly',
- 'cols' => intval( $wgUser->getOption( 'cols' ) ),
- 'rows' => intval( $wgUser->getOption( 'rows' ) ) ),
+ 'cols' => intval( $this->getUser()->getOption( 'cols' ) ),
+ 'rows' => intval( $this->getUser()->getOption( 'rows' ) ) ),
$rev->getText( Revision::FOR_THIS_USER ) . "\n" ) .
Xml::openElement( 'div' ) .
Xml::openElement( 'form', array(
@@ -870,7 +873,7 @@ class UndeleteForm extends SpecialPage {
Xml::element( 'input', array(
'type' => 'hidden',
'name' => 'wpEditToken',
- 'value' => $wgUser->editToken() ) ) .
+ 'value' => $this->getUser()->editToken() ) ) .
Xml::element( 'input', array(
'type' => 'submit',
'name' => 'preview',
@@ -884,6 +887,48 @@ class UndeleteForm extends SpecialPage {
}
/**
+ * Get a revision-deletion link, or disabled link, or nothing, depending
+ * on user permissions & the settings on the revision.
+ *
+ * Will use forward-compatible revision ID in the Special:RevDelete link
+ * if possible, otherwise the timestamp-based ID which may break after
+ * undeletion.
+ *
+ * @param Revision $rev
+ * @return string HTML fragment
+ */
+ function revDeleteLink( $rev ) {
+ $canHide = $this->getUser()->isAllowed( 'deleterevision' );
+ if( $canHide || ( $rev->getVisibility() && $this->getUser()->isAllowed( 'deletedhistory' ) ) ) {
+ if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ $revdlink = Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
+ } else {
+ if ( $rev->getId() ) {
+ // RevDelete links using revision ID are stable across
+ // page deletion and undeletion; use when possible.
+ $query = array(
+ 'type' => 'revision',
+ 'target' => $this->mTargetObj->getPrefixedDBkey(),
+ 'ids' => $rev->getId()
+ );
+ } else {
+ // Older deleted entries didn't save a revision ID.
+ // We have to refer to these by timestamp, ick!
+ $query = array(
+ 'type' => 'archive',
+ 'target' => $this->mTargetObj->getPrefixedDBkey(),
+ 'ids' => $rev->getTimestamp()
+ );
+ }
+ return Linker::revDeleteLink( $query,
+ $rev->isDeleted( File::DELETED_RESTRICTED ), $canHide );
+ }
+ } else {
+ return '';
+ }
+ }
+
+ /**
* Build a diff display between this and the previous either deleted
* or non-deleted edit.
*
@@ -892,11 +937,9 @@ class UndeleteForm extends SpecialPage {
* @return String: HTML
*/
function showDiff( $previousRev, $currentRev ) {
- global $wgOut;
-
$diffEngine = new DifferenceEngine( $previousRev->getTitle() );
$diffEngine->showDiffStyle();
- $wgOut->addHTML(
+ $this->getOutput()->addHTML(
"<div>" .
"<table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>" .
"<col class='diff-marker' />" .
@@ -918,59 +961,47 @@ class UndeleteForm extends SpecialPage {
);
}
+ /**
+ * @param $rev Revision
+ * @param $prefix
+ * @return string
+ */
private function diffHeader( $rev, $prefix ) {
- global $wgUser, $wgLang;
- $sk = $wgUser->getSkin();
$isDeleted = !( $rev->getId() && $rev->getTitle() );
if( $isDeleted ) {
- /// @todo Fixme: $rev->getTitle() is null for deleted revs...?
+ /// @todo FIXME: $rev->getTitle() is null for deleted revs...?
$targetPage = $this->getTitle();
$targetQuery = array(
'target' => $this->mTargetObj->getPrefixedText(),
'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
);
} else {
- /// @todo Fixme getId() may return non-zero for deleted revs...
+ /// @todo FIXME: getId() may return non-zero for deleted revs...
$targetPage = $rev->getTitle();
$targetQuery = array( 'oldid' => $rev->getId() );
}
// Add show/hide deletion links if available
- $canHide = $wgUser->isAllowed( 'deleterevision' );
- if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
- $del = ' ';
- if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
- $del .= $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
- } else {
- $query = array(
- 'type' => 'archive',
- 'target' => $this->mTargetObj->getPrefixedDbkey(),
- 'ids' => $rev->getTimestamp()
- );
- $del .= $sk->revDeleteLink( $query,
- $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
- }
- } else {
- $del = '';
- }
+ $del = $this->revDeleteLink( $rev );
return
- '<div id="mw-diff-'.$prefix.'title1"><strong>' .
- $sk->link(
+ '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
+ Linker::link(
$targetPage,
- wfMsgHtml(
+ wfMsgExt(
'revisionasof',
- htmlspecialchars( $wgLang->timeanddate( $rev->getTimestamp(), true ) ),
- htmlspecialchars( $wgLang->date( $rev->getTimestamp(), true ) ),
- htmlspecialchars( $wgLang->time( $rev->getTimestamp(), true ) )
+ array( 'escape' ),
+ $this->getLang()->timeanddate( $rev->getTimestamp(), true ),
+ $this->getLang()->date( $rev->getTimestamp(), true ),
+ $this->getLang()->time( $rev->getTimestamp(), true )
),
array(),
$targetQuery
) .
'</strong></div>' .
'<div id="mw-diff-'.$prefix.'title2">' .
- $sk->revUserTools( $rev ) . '<br />' .
+ Linker::revUserTools( $rev ) . '<br />' .
'</div>' .
'<div id="mw-diff-'.$prefix.'title3">' .
- $sk->revComment( $rev ) . $del . '<br />' .
+ Linker::revComment( $rev ) . $del . '<br />' .
'</div>';
}
@@ -978,19 +1009,18 @@ class UndeleteForm extends SpecialPage {
* Show a form confirming whether a tokenless user really wants to see a file
*/
private function showFileConfirmationForm( $key ) {
- global $wgOut, $wgUser, $wgLang;
- $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile );
- $wgOut->addWikiMsg( 'undelete-show-file-confirm',
+ $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
+ $this->getOutput()->addWikiMsg( 'undelete-show-file-confirm',
$this->mTargetObj->getText(),
- $wgLang->date( $file->getTimestamp() ),
- $wgLang->time( $file->getTimestamp() ) );
- $wgOut->addHTML(
+ $this->getLang()->date( $file->getTimestamp() ),
+ $this->getLang()->time( $file->getTimestamp() ) );
+ $this->getOutput()->addHTML(
Xml::openElement( 'form', array(
'method' => 'POST',
- 'action' => $this->getTitle()->getLocalUrl(
+ 'action' => $this->getTitle()->getLocalURL(
'target=' . urlencode( $this->mTarget ) .
'&file=' . urlencode( $key ) .
- '&token=' . urlencode( $wgUser->editToken( $key ) ) )
+ '&token=' . urlencode( $this->getUser()->editToken( $key ) ) )
)
) .
Xml::submitButton( wfMsg( 'undelete-show-file-submit' ) ) .
@@ -1002,16 +1032,16 @@ class UndeleteForm extends SpecialPage {
* Show a deleted file version requested by the visitor.
*/
private function showFile( $key ) {
- global $wgOut, $wgRequest;
- $wgOut->disable();
+ $this->getOutput()->disable();
# We mustn't allow the output to be Squid cached, otherwise
# if an admin previews a deleted image, and it's cached, then
# a user without appropriate permissions can toddle off and
# nab the image, and Squid will serve it
- $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
- $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
- $wgRequest->response()->header( 'Pragma: no-cache' );
+ $response = $this->getRequest()->response();
+ $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
+ $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
+ $response->header( 'Pragma: no-cache' );
global $IP;
require_once( "$IP/includes/StreamFile.php" );
@@ -1020,34 +1050,36 @@ class UndeleteForm extends SpecialPage {
wfStreamFile( $path );
}
- private function showHistory( ) {
- global $wgUser, $wgOut;
-
- $sk = $wgUser->getSkin();
+ private function showHistory() {
+ $out = $this->getOutput();
if( $this->mAllowed ) {
- $wgOut->setPagetitle( wfMsg( "undeletepage" ) );
+ $out->addModules( 'mediawiki.special.undelete' );
+ $out->setPageTitle( wfMsg( 'undeletepage' ) );
} else {
- $wgOut->setPagetitle( wfMsg( 'viewdeletedpage' ) );
+ $out->setPageTitle( wfMsg( 'viewdeletedpage' ) );
}
-
- $wgOut->wrapWikiMsg( "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n", array ( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() ) );
+ $out->wrapWikiMsg(
+ "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
+ array( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() )
+ );
$archive = new PageArchive( $this->mTargetObj );
+ wfRunHooks( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) );
/*
$text = $archive->getLastRevisionText();
if( is_null( $text ) ) {
- $wgOut->addWikiMsg( "nohistory" );
+ $out->addWikiMsg( 'nohistory' );
return;
}
*/
- $wgOut->addHTML( '<div class="mw-undelete-history">' );
+ $out->addHTML( '<div class="mw-undelete-history">' );
if ( $this->mAllowed ) {
- $wgOut->addWikiMsg( "undeletehistory" );
- $wgOut->addWikiMsg( "undeleterevdel" );
+ $out->addWikiMsg( 'undeletehistory' );
+ $out->addWikiMsg( 'undeleterevdel' );
} else {
- $wgOut->addWikiMsg( "undeletehistorynoadmin" );
+ $out->addWikiMsg( 'undeletehistorynoadmin' );
}
- $wgOut->addHTML( '</div>' );
+ $out->addHTML( '</div>' );
# List all stored revisions
$revisions = $archive->listRevisions();
@@ -1080,39 +1112,39 @@ class UndeleteForm extends SpecialPage {
$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 );
+ $out->addHTML( $top );
}
# Show relevant lines from the deletion log:
- $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) . "\n" );
- LogEventsList::showLogExtract( $wgOut, 'delete', $this->mTargetObj->getPrefixedText() );
+ $out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) . "\n" );
+ LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj->getPrefixedText() );
# Show relevant lines from the suppression log:
- if( $wgUser->isAllowed( 'suppressionlog' ) ) {
- $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'suppress' ) ) . "\n" );
- LogEventsList::showLogExtract( $wgOut, 'suppress', $this->mTargetObj->getPrefixedText() );
+ if( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
+ $out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'suppress' ) ) . "\n" );
+ LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj->getPrefixedText() );
}
if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
# Format the user-visible controls (comment field, submission button)
# in a nice little table
- if( $wgUser->isAllowed( 'suppressrevision' ) ) {
+ if( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
$unsuppressBox =
"<tr>
<td>&#160;</td>
<td class='mw-input'>" .
- Xml::checkLabel( wfMsg('revdelete-unsuppress'), 'wpUnsuppress',
+ Xml::checkLabel( wfMsg( 'revdelete-unsuppress' ), 'wpUnsuppress',
'mw-undelete-unsuppress', $this->mUnsuppress ).
"</td>
</tr>";
} else {
- $unsuppressBox = "";
+ $unsuppressBox = '';
}
$table =
Xml::fieldset( wfMsg( 'undelete-fieldset-title' ) ) .
Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
"<tr>
<td colspan='2' class='mw-undelete-extrahelp'>" .
- wfMsgWikiHtml( 'undeleteextrahelp' ) .
+ wfMsgExt( 'undeleteextrahelp', 'parse' ) .
"</td>
</tr>
<tr>
@@ -1127,7 +1159,6 @@ class UndeleteForm extends SpecialPage {
<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' ) ) . ' ' .
Xml::submitButton( wfMsg( 'undeleteinvert' ), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) .
"</td>
</tr>" .
@@ -1135,51 +1166,49 @@ class UndeleteForm extends SpecialPage {
Xml::closeElement( 'table' ) .
Xml::closeElement( 'fieldset' );
- $wgOut->addHTML( $table );
+ $out->addHTML( $table );
}
- $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'history' ) ) . "\n" );
+ $out->addHTML( Xml::element( 'h2', null, wfMsg( 'history' ) ) . "\n" );
if( $haveRevisions ) {
# The page's stored (deleted) history:
- $wgOut->addHTML("<ul>");
+ $out->addHTML( '<ul>' );
$remaining = $revisions->numRows();
$earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
foreach ( $revisions as $row ) {
$remaining--;
- $wgOut->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) );
+ $out->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining ) );
}
$revisions->free();
- $wgOut->addHTML("</ul>");
+ $out->addHTML( '</ul>' );
} else {
- $wgOut->addWikiMsg( "nohistory" );
+ $out->addWikiMsg( 'nohistory' );
}
if( $haveFiles ) {
- $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" );
- $wgOut->addHTML( "<ul>" );
+ $out->addHTML( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" );
+ $out->addHTML( '<ul>' );
foreach ( $files as $row ) {
- $wgOut->addHTML( $this->formatFileRow( $row, $sk ) );
+ $out->addHTML( $this->formatFileRow( $row ) );
}
$files->free();
- $wgOut->addHTML( "</ul>" );
+ $out->addHTML( '</ul>' );
}
if ( $this->mAllowed ) {
# Slip in the hidden controls here
$misc = Html::hidden( 'target', $this->mTarget );
- $misc .= Html::hidden( 'wpEditToken', $wgUser->editToken() );
+ $misc .= Html::hidden( 'wpEditToken', $this->getUser()->editToken() );
$misc .= Xml::closeElement( 'form' );
- $wgOut->addHTML( $misc );
+ $out->addHTML( $misc );
}
return true;
}
- private function formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) {
- global $wgUser, $wgLang;
-
+ private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
$rev = Revision::newFromArchiveRow( $row,
array( 'page' => $this->mTargetObj->getArticleId() ) );
$stxt = '';
@@ -1188,7 +1217,7 @@ class UndeleteForm extends SpecialPage {
if( $this->mAllowed ) {
if( $this->mInvert ) {
if( in_array( $ts, $this->mTargetTimestamp ) ) {
- $checkBox = Xml::check( "ts$ts");
+ $checkBox = Xml::check( "ts$ts" );
} else {
$checkBox = Xml::check( "ts$ts", true );
}
@@ -1203,13 +1232,13 @@ class UndeleteForm extends SpecialPage {
$titleObj = $this->getTitle();
# Last link
if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
- $pageLink = htmlspecialchars( $wgLang->timeanddate( $ts, true ) );
- $last = wfMsgHtml('diff');
- } else if( $remaining > 0 || ($earliestLiveTime && $ts > $earliestLiveTime) ) {
- $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk );
- $last = $sk->linkKnown(
+ $pageLink = htmlspecialchars( $this->getLang()->timeanddate( $ts, true ) );
+ $last = wfMsgHtml( 'diff' );
+ } elseif( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
+ $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
+ $last = Linker::linkKnown(
$titleObj,
- wfMsgHtml('diff'),
+ wfMsgHtml( 'diff' ),
array(),
array(
'target' => $this->mTargetObj->getPrefixedText(),
@@ -1218,77 +1247,61 @@ class UndeleteForm extends SpecialPage {
)
);
} else {
- $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk );
- $last = wfMsgHtml('diff');
+ $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
+ $last = wfMsgHtml( 'diff' );
}
} else {
- $pageLink = htmlspecialchars( $wgLang->timeanddate( $ts, true ) );
- $last = wfMsgHtml('diff');
+ $pageLink = htmlspecialchars( $this->getLang()->timeanddate( $ts, true ) );
+ $last = wfMsgHtml( 'diff' );
}
// User links
- $userLink = $sk->revUserTools( $rev );
+ $userLink = Linker::revUserTools( $rev );
// Revision text size
- if( !is_null($size = $row->ar_len) ) {
- $stxt = $sk->formatRevisionSize( $size );
+ $size = $row->ar_len;
+ if( !is_null( $size ) ) {
+ $stxt = Linker::formatRevisionSize( $size );
}
// Edit summary
- $comment = $sk->revComment( $rev );
+ $comment = Linker::revComment( $rev );
// Revision delete links
- $canHide = $wgUser->isAllowed( 'deleterevision' );
- if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
- if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
- $revdlink = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
- } else {
- $query = array(
- 'type' => 'archive',
- 'target' => $this->mTargetObj->getPrefixedDBkey(),
- 'ids' => $ts
- );
- $revdlink = $sk->revDeleteLink( $query,
- $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
- }
- } else {
- $revdlink = '';
- }
+ $revdlink = $this->revDeleteLink( $rev );
return "<li>$checkBox $revdlink ($last) $pageLink . . $userLink $stxt $comment</li>";
}
- private function formatFileRow( $row, $sk ) {
- global $wgUser, $wgLang;
-
+ private function formatFileRow( $row ) {
$file = ArchivedFile::newFromRow( $row );
$ts = wfTimestamp( TS_MW, $row->fa_timestamp );
if( $this->mAllowed && $row->fa_storage_key ) {
- $checkBox = Xml::check( "fileid" . $row->fa_id );
+ $checkBox = Xml::check( 'fileid' . $row->fa_id );
$key = urlencode( $row->fa_storage_key );
- $pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key, $sk );
+ $pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key );
} else {
$checkBox = '';
- $pageLink = $wgLang->timeanddate( $ts, true );
+ $pageLink = $this->getLang()->timeanddate( $ts, true );
}
- $userLink = $this->getFileUser( $file, $sk );
+ $userLink = $this->getFileUser( $file );
$data =
wfMsg( 'widthheight',
- $wgLang->formatNum( $row->fa_width ),
- $wgLang->formatNum( $row->fa_height ) ) .
+ $this->getLang()->formatNum( $row->fa_width ),
+ $this->getLang()->formatNum( $row->fa_height ) ) .
' (' .
- wfMsg( 'nbytes', $wgLang->formatNum( $row->fa_size ) ) .
+ wfMsg( 'nbytes', $this->getLang()->formatNum( $row->fa_size ) ) .
')';
$data = htmlspecialchars( $data );
- $comment = $this->getFileComment( $file, $sk );
+ $comment = $this->getFileComment( $file );
// Add show/hide deletion links if available
- $canHide = $wgUser->isAllowed( 'deleterevision' );
- if( $canHide || ($file->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
- if( !$file->userCan(File::DELETED_RESTRICTED ) ) {
- $revdlink = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
+ $canHide = $this->getUser()->isAllowed( 'deleterevision' );
+ if( $canHide || ( $file->getVisibility() && $this->getUser()->isAllowed( 'deletedhistory' ) ) ) {
+ if( !$file->userCan( File::DELETED_RESTRICTED ) ) {
+ $revdlink = Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
} else {
$query = array(
'type' => 'filearchive',
'target' => $this->mTargetObj->getPrefixedDBkey(),
'ids' => $row->fa_id
);
- $revdlink = $sk->revDeleteLink( $query,
+ $revdlink = Linker::revDeleteLink( $query,
$file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
}
} else {
@@ -1299,17 +1312,17 @@ class UndeleteForm extends SpecialPage {
/**
* Fetch revision text link if it's available to all users
+ *
+ * @param $rev Revision
* @return string
*/
- function getPageLink( $rev, $titleObj, $ts, $sk ) {
- global $wgLang;
-
- $time = htmlspecialchars( $wgLang->timeanddate( $ts, true ) );
+ function getPageLink( $rev, $titleObj, $ts ) {
+ $time = htmlspecialchars( $this->getLang()->timeanddate( $ts, true ) );
- if( !$rev->userCan(Revision::DELETED_TEXT) ) {
+ if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
return '<span class="history-deleted">' . $time . '</span>';
} else {
- $link = $sk->linkKnown(
+ $link = Linker::linkKnown(
$titleObj,
$time,
array(),
@@ -1318,8 +1331,9 @@ class UndeleteForm extends SpecialPage {
'timestamp' => $ts
)
);
- if( $rev->isDeleted(Revision::DELETED_TEXT) )
+ if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
$link = '<span class="history-deleted">' . $link . '</span>';
+ }
return $link;
}
}
@@ -1327,26 +1341,26 @@ class UndeleteForm extends SpecialPage {
/**
* Fetch image view link if it's available to all users
*
+ * @param $file File
* @return String: HTML fragment
*/
- function getFileLink( $file, $titleObj, $ts, $key, $sk ) {
- global $wgLang, $wgUser;
-
- if( !$file->userCan(File::DELETED_FILE) ) {
- return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';
+ function getFileLink( $file, $titleObj, $ts, $key ) {
+ if( !$file->userCan( File::DELETED_FILE ) ) {
+ return '<span class="history-deleted">' . $this->getLang()->timeanddate( $ts, true ) . '</span>';
} else {
- $link = $sk->linkKnown(
+ $link = Linker::linkKnown(
$titleObj,
- $wgLang->timeanddate( $ts, true ),
+ $this->getLang()->timeanddate( $ts, true ),
array(),
array(
'target' => $this->mTargetObj->getPrefixedText(),
'file' => $key,
- 'token' => $wgUser->editToken( $key )
+ 'token' => $this->getUser()->editToken( $key )
)
);
- if( $file->isDeleted(File::DELETED_FILE) )
+ if( $file->isDeleted( File::DELETED_FILE ) ) {
$link = '<span class="history-deleted">' . $link . '</span>';
+ }
return $link;
}
}
@@ -1354,16 +1368,18 @@ class UndeleteForm extends SpecialPage {
/**
* Fetch file's user id if it's available to this user
*
+ * @param $file File
* @return String: HTML fragment
*/
- function getFileUser( $file, $sk ) {
- if( !$file->userCan(File::DELETED_USER) ) {
+ function getFileUser( $file ) {
+ if( !$file->userCan( File::DELETED_USER ) ) {
return '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
} else {
- $link = $sk->userLink( $file->getRawUser(), $file->getRawUserText() ) .
- $sk->userToolLinks( $file->getRawUser(), $file->getRawUserText() );
- if( $file->isDeleted(File::DELETED_USER) )
+ $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) .
+ Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() );
+ if( $file->isDeleted( File::DELETED_USER ) ) {
$link = '<span class="history-deleted">' . $link . '</span>';
+ }
return $link;
}
}
@@ -1371,54 +1387,57 @@ class UndeleteForm extends SpecialPage {
/**
* Fetch file upload comment if it's available to this user
*
+ * @param $file File
* @return String: HTML fragment
*/
- function getFileComment( $file, $sk ) {
- if( !$file->userCan(File::DELETED_COMMENT) ) {
- return '<span class="history-deleted"><span class="comment">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span></span>';
+ function getFileComment( $file ) {
+ if( !$file->userCan( File::DELETED_COMMENT ) ) {
+ return '<span class="history-deleted"><span class="comment">' .
+ wfMsgHtml( 'rev-deleted-comment' ) . '</span></span>';
} else {
- $link = $sk->commentBlock( $file->getRawDescription() );
- if( $file->isDeleted(File::DELETED_COMMENT) )
+ $link = Linker::commentBlock( $file->getRawDescription() );
+ if( $file->isDeleted( File::DELETED_COMMENT ) ) {
$link = '<span class="history-deleted">' . $link . '</span>';
+ }
return $link;
}
}
function undelete() {
- global $wgOut, $wgUser;
if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
+ throw new ReadOnlyError;
}
+
if( !is_null( $this->mTargetObj ) ) {
$archive = new PageArchive( $this->mTargetObj );
+ wfRunHooks( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) );
$ok = $archive->undelete(
$this->mTargetTimestamp,
$this->mComment,
$this->mFileVersions,
$this->mUnsuppress );
- if( is_array($ok) ) {
- if ( $ok[1] ) // Undeleted file count
+ if( is_array( $ok ) ) {
+ if ( $ok[1] ) { // Undeleted file count
wfRunHooks( 'FileUndeleteComplete', array(
$this->mTargetObj, $this->mFileVersions,
- $wgUser, $this->mComment) );
+ $this->getUser(), $this->mComment ) );
+ }
- $skin = $wgUser->getSkin();
- $link = $skin->linkKnown( $this->mTargetObj );
- $wgOut->addHTML( wfMsgWikiHtml( 'undeletedpage', $link ) );
+ $link = Linker::linkKnown( $this->mTargetObj );
+ $this->getOutput()->addHTML( wfMessage( 'undeletedpage' )->rawParams( $link )->parse() );
} else {
- $wgOut->showFatalError( wfMsg( "cannotundelete" ) );
- $wgOut->addHTML( '<p>' . wfMsgHtml( "undeleterevdel" ) . '</p>' );
+ $this->getOutput()->showFatalError( wfMsg( 'cannotundelete' ) );
+ $this->getOutput()->addWikiMsg( 'undeleterevdel' );
}
// Show file deletion warnings and errors
$status = $archive->getFileStatus();
if( $status && !$status->isGood() ) {
- $wgOut->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) );
+ $this->getOutput()->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) );
}
} else {
- $wgOut->showFatalError( wfMsg( "cannotundelete" ) );
+ $this->getOutput()->showFatalError( wfMsg( 'cannotundelete' ) );
}
return false;
}
diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php
index c71b554b..95ad0bf5 100644
--- a/includes/specials/SpecialUnlockdb.php
+++ b/includes/specials/SpecialUnlockdb.php
@@ -33,12 +33,13 @@ class SpecialUnlockdb extends SpecialPage {
}
public function execute( $par ) {
- global $wgUser, $wgOut, $wgRequest;
+ global $wgUser, $wgRequest;
$this->setHeaders();
- if( !$wgUser->isAllowed( 'siteadmin' ) ) {
- $wgOut->permissionRequired( 'siteadmin' );
+ # Permission check
+ if( !$this->userCanExecute( $wgUser ) ) {
+ $this->displayRestrictionError();
return;
}
@@ -48,7 +49,7 @@ class SpecialUnlockdb extends SpecialPage {
if ( $action == 'success' ) {
$this->showSuccess();
- } else if ( $action == 'submit' && $wgRequest->wasPosted() &&
+ } elseif ( $action == 'submit' && $wgRequest->wasPosted() &&
$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
$this->doSubmit();
} else {
@@ -104,7 +105,12 @@ class SpecialUnlockdb extends SpecialPage {
$this->showForm( wfMsg( 'locknoconfirm' ) );
return;
}
- if ( @!unlink( $wgReadOnlyFile ) ) {
+
+ wfSuppressWarnings();
+ $res = unlink( $wgReadOnlyFile );
+ wfRestoreWarnings();
+
+ if ( !$res ) {
$wgOut->showFileDeleteError( $wgReadOnlyFile );
return;
}
diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php
index ff2a66e1..e4b8e544 100644
--- a/includes/specials/SpecialUnusedcategories.php
+++ b/includes/specials/SpecialUnusedcategories.php
@@ -28,25 +28,26 @@ class UnusedCategoriesPage extends QueryPage {
function isExpensive() { return true; }
- function getName() {
- return 'Unusedcategories';
+ function __construct( $name = 'Unusedcategories' ) {
+ parent::__construct( $name );
}
function getPageHeader() {
return wfMsgExt( 'unusedcategoriestext', array( 'parse' ) );
}
- function getSQL() {
- $NScat = NS_CATEGORY;
- $dbr = wfGetDB( DB_SLAVE );
- list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' );
- return "SELECT 'Unusedcategories' as type,
- {$NScat} as namespace, page_title as title, page_title as value
- FROM $page
- LEFT JOIN $categorylinks ON page_title=cl_to
- WHERE cl_from IS NULL
- AND page_namespace = {$NScat}
- AND page_is_redirect = 0";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'page', 'categorylinks' ),
+ 'fields' => array ( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'page_title AS value' ),
+ 'conds' => array ( 'cl_from IS NULL',
+ 'page_namespace' => NS_CATEGORY,
+ 'page_is_redirect' => 0 ),
+ 'join_conds' => array ( 'categorylinks' => array (
+ 'LEFT JOIN', 'cl_to = page_title' ) )
+ );
}
/**
@@ -61,10 +62,3 @@ class UnusedCategoriesPage extends QueryPage {
return $skin->link( $title, $title->getText() );
}
}
-
-/** constructor */
-function wfSpecialUnusedCategories() {
- list( $limit, $offset ) = wfCheckLimits();
- $uc = new UnusedCategoriesPage();
- return $uc->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php
index 091ec3a3..6407de44 100644
--- a/includes/specials/SpecialUnusedimages.php
+++ b/includes/specials/SpecialUnusedimages.php
@@ -27,41 +27,53 @@
* @ingroup SpecialPage
*/
class UnusedimagesPage extends ImageQueryPage {
+ function __construct( $name = 'Unusedimages' ) {
+ parent::__construct( $name );
+ }
- function isExpensive() { return true; }
-
- function getName() {
- return 'Unusedimages';
+ function isExpensive() {
+ return true;
}
function sortDescending() {
return false;
}
- function isSyndicated() { return false; }
-
- function getSQL() {
- global $wgCountCategorizedImagesAsUsed;
- $dbr = wfGetDB( DB_SLAVE );
+ function isSyndicated() {
+ return false;
+ }
- $epoch = $dbr->unixTimestamp( 'img_timestamp' );
+ function getQueryInfo() {
+ global $wgCountCategorizedImagesAsUsed;
+ $retval = array (
+ 'tables' => array ( 'image', 'imagelinks' ),
+ 'fields' => array ( "'" . NS_FILE . "' AS namespace",
+ 'img_name AS title',
+ 'img_timestamp AS value',
+ 'img_user', 'img_user_text',
+ 'img_description' ),
+ 'conds' => array ( 'il_to IS NULL' ),
+ 'join_conds' => array ( 'imagelinks' => array (
+ 'LEFT JOIN', 'il_to = img_name' ) )
+ );
if ( $wgCountCategorizedImagesAsUsed ) {
- list( $page, $image, $imagelinks, $categorylinks ) = $dbr->tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' );
-
- return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, $epoch as value,
- img_user, img_user_text, img_description
- FROM ((($page AS I LEFT JOIN $categorylinks AS L ON I.page_id = L.cl_from)
- LEFT JOIN $imagelinks AS P ON I.page_title = P.il_to)
- INNER JOIN $image AS G ON I.page_title = G.img_name)
- WHERE I.page_namespace = ".NS_FILE." AND L.cl_from IS NULL AND P.il_to IS NULL";
- } else {
- list( $image, $imagelinks ) = $dbr->tableNamesN( 'image','imagelinks' );
-
- return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, $epoch as value,
- img_user, img_user_text, img_description
- FROM $image LEFT JOIN $imagelinks ON img_name=il_to WHERE il_to IS NULL ";
+ // Order is significant
+ $retval['tables'] = array ( 'image', 'page', 'categorylinks',
+ 'imagelinks' );
+ $retval['conds']['page_namespace'] = NS_FILE;
+ $retval['conds'][] = 'cl_from IS NULL';
+ $retval['conds'][] = 'img_name = page_title';
+ $retval['join_conds']['categorylinks'] = array (
+ 'LEFT JOIN', 'cl_from = page_id' );
+ $retval['join_conds']['imagelinks'] = array (
+ 'LEFT JOIN', 'il_to = page_title' );
}
+ return $retval;
+ }
+
+ function usesTimestamps() {
+ return true;
}
function getPageHeader() {
@@ -69,13 +81,3 @@ class UnusedimagesPage extends ImageQueryPage {
}
}
-
-/**
- * Entry point
- */
-function wfSpecialUnusedimages() {
- list( $limit, $offset ) = wfCheckLimits();
- $uip = new UnusedimagesPage();
-
- return $uip->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php
index 68bf95a2..da501605 100644
--- a/includes/specials/SpecialUnusedtemplates.php
+++ b/includes/specials/SpecialUnusedtemplates.php
@@ -31,24 +31,34 @@
*/
class UnusedtemplatesPage extends QueryPage {
- function getName() { return( 'Unusedtemplates' ); }
+ function __construct( $name = 'Unusedtemplates' ) {
+ parent::__construct( $name );
+ }
+
function isExpensive() { return true; }
function isSyndicated() { return false; }
function sortDescending() { return false; }
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $page, $templatelinks) = $dbr->tableNamesN( 'page', 'templatelinks' );
- $sql = "SELECT 'Unusedtemplates' AS type, page_title AS title,
- page_namespace AS namespace, 0 AS value
- FROM $page
- LEFT JOIN $templatelinks
- ON page_namespace = tl_namespace AND page_title = tl_title
- WHERE page_namespace = 10 AND tl_from IS NULL
- AND page_is_redirect = 0";
- return $sql;
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'page', 'templatelinks' ),
+ 'fields' => array ( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'page_title AS value' ),
+ 'conds' => array ( 'page_namespace' => NS_TEMPLATE,
+ 'tl_from IS NULL',
+ 'page_is_redirect' => 0 ),
+ 'join_conds' => array ( 'templatelinks' => array (
+ 'LEFT JOIN', array ( 'tl_title = page_title',
+ 'tl_namespace = page_namespace' ) ) )
+ );
}
+ /**
+ * @param $skin Skin
+ * @param $result
+ * @return string
+ */
function formatResult( $skin, $result ) {
$title = Title::makeTitle( NS_TEMPLATE, $result->title );
$pageLink = $skin->linkKnown(
@@ -72,8 +82,3 @@ class UnusedtemplatesPage extends QueryPage {
}
-function wfSpecialUnusedtemplates() {
- list( $limit, $offset ) = wfCheckLimits();
- $utp = new UnusedtemplatesPage();
- $utp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php
index ecd62cb7..0f11140b 100644
--- a/includes/specials/SpecialUnwatchedpages.php
+++ b/includes/specials/SpecialUnwatchedpages.php
@@ -31,62 +31,58 @@
*/
class UnwatchedpagesPage extends QueryPage {
- function getName() { return 'Unwatchedpages'; }
+ function __construct( $name = 'Unwatchedpages' ) {
+ parent::__construct( $name, 'unwatchedpages' );
+ }
+
function isExpensive() { return true; }
function isSyndicated() { return false; }
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $page, $watchlist ) = $dbr->tableNamesN( 'page', 'watchlist' );
- $mwns = NS_MEDIAWIKI;
- return
- "
- SELECT
- 'Unwatchedpages' as type,
- page_namespace as namespace,
- page_title as title,
- page_namespace as value
- FROM $page
- LEFT JOIN $watchlist ON wl_namespace = page_namespace AND page_title = wl_title
- WHERE wl_title IS NULL AND page_is_redirect = 0 AND page_namespace<>$mwns
- ";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'page', 'watchlist' ),
+ 'fields' => array ( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'page_namespace AS value' ),
+ 'conds' => array ( 'wl_title IS NULL',
+ 'page_is_redirect' => 0,
+ "page_namespace != '" . NS_MEDIAWIKI .
+ "'" ),
+ 'join_conds' => array ( 'watchlist' => array (
+ 'LEFT JOIN', array ( 'wl_title = page_title',
+ 'wl_namespace = page_namespace' ) ) )
+ );
}
function sortDescending() { return false; }
+ function getOrderFields() {
+ return array( 'page_namespace', 'page_title' );
+ }
+
+ /**
+ * @param $skin Skin
+ * @param $result
+ * @return string
+ */
function formatResult( $skin, $result ) {
global $wgContLang;
$nt = Title::makeTitle( $result->namespace, $result->title );
$text = $wgContLang->convert( $nt->getPrefixedText() );
- $plink = $skin->linkKnown(
+ $plink = Linker::linkKnown(
$nt,
htmlspecialchars( $text )
);
- $wlink = $skin->linkKnown(
+ $token = WatchAction::getWatchToken( $nt, $this->getUser() );
+ $wlink = Linker::linkKnown(
$nt,
wfMsgHtml( 'watch' ),
array(),
- array( 'action' => 'watch' )
+ array( 'action' => 'watch', 'token' => $token )
);
return wfSpecialList( $plink, $wlink );
}
}
-
-/**
- * constructor
- */
-function wfSpecialUnwatchedpages() {
- global $wgUser, $wgOut;
-
- if ( ! $wgUser->isAllowed( 'unwatchedpages' ) )
- return $wgOut->permissionRequired( 'unwatchedpages' );
-
- list( $limit, $offset ) = wfCheckLimits();
-
- $wpp = new UnwatchedpagesPage();
-
- $wpp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 893e4be2..8eeca5d5 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -45,7 +45,15 @@ class SpecialUpload extends SpecialPage {
/** Misc variables **/
public $mRequest; // The WebRequest or FauxRequest this form is supposed to handle
public $mSourceType;
+
+ /**
+ * @var UploadBase
+ */
public $mUpload;
+
+ /**
+ * @var LocalFile
+ */
public $mLocalFile;
public $mUploadClicked;
@@ -71,6 +79,8 @@ class SpecialUpload extends SpecialPage {
public $uploadFormTextTop;
public $uploadFormTextAfterSummary;
+ public $mWatchthis;
+
/**
* Initialize instance variables from request and create an Upload handler
*
@@ -105,7 +115,7 @@ class SpecialUpload extends SpecialPage {
$this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file
$this->mCancelUpload = $request->getCheck( 'wpCancelUpload' )
- || $request->getCheck( 'wpReUpload' ); // b/w compat
+ || $request->getCheck( 'wpReUpload' ); // b/w compat
// If it was posted check for the token (no remote POST'ing with user credentials)
$token = $request->getVal( 'wpEditToken' );
@@ -130,7 +140,7 @@ class SpecialUpload extends SpecialPage {
* @param $user User object
* @return Boolean
*/
- public function userCanExecute( $user ) {
+ public function userCanExecute( User $user ) {
return UploadBase::isEnabled() && parent::userCanExecute( $user );
}
@@ -196,7 +206,7 @@ class SpecialUpload extends SpecialPage {
wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
return;
}
-
+
$this->showUploadForm( $this->getUploadForm() );
}
@@ -214,7 +224,7 @@ class SpecialUpload extends SpecialPage {
*/
protected function showUploadForm( $form ) {
# Add links if file was previously deleted
- if ( !$this->mDesiredDestName ) {
+ if ( $this->mDesiredDestName ) {
$this->showViewDeletedLinks();
}
@@ -267,7 +277,7 @@ class SpecialUpload extends SpecialPage {
$desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
$delNotice = ''; // empty by default
if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) {
- LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ),
+ LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ),
$desiredTitleObj->getPrefixedText(),
'', array( 'lim' => 10,
'conds' => array( "log_action != 'revision'" ),
@@ -278,17 +288,17 @@ class SpecialUpload extends SpecialPage {
$form->addPreText( $delNotice );
# Add text to form
- $form->addPreText( '<div id="uploadtext">' .
- wfMsgExt( 'uploadtext', 'parse', array( $this->mDesiredDestName ) ) .
+ $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 ) ) {
+ $uploadFooter = wfMessage( 'uploadfooter' );
+ if ( !$uploadFooter->isDisabled() ) {
$form->addPostText( '<div id="mw-upload-footer-message">'
- . $wgOut->parse( $uploadFooter ) . "</div>\n" );
+ . $wgOut->parse( $uploadFooter->plain() ) . "</div>\n" );
}
return $form;
@@ -309,7 +319,7 @@ class SpecialUpload extends SpecialPage {
$link = wfMsgExt(
$wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted',
array( 'parse', 'replaceafter' ),
- $wgUser->getSkin()->linkKnown(
+ $this->getSkin()->linkKnown(
SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count )
)
@@ -317,11 +327,6 @@ class SpecialUpload extends SpecialPage {
$wgOut->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
}
}
-
- // Show the relevant lines from deletion log (for still deleted files only)
- if( $title instanceof Title && $title->isDeletedQuick() && !$title->exists() ) {
- $this->showDeletionLog( $wgOut, $title->getPrefixedText() );
- }
}
/**
@@ -337,7 +342,7 @@ class SpecialUpload extends SpecialPage {
*/
protected function showRecoverableUploadError( $message ) {
$sessionKey = $this->mUpload->stashSession();
- $message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" .
+ $message = '<h2>' . wfMsgHtml( 'uploaderror' ) . "</h2>\n" .
'<div class="error">' . $message . "</div>\n";
$form = $this->getUploadForm( $message, $sessionKey );
@@ -357,8 +362,8 @@ class SpecialUpload extends SpecialPage {
# mDestWarningAck is set when some javascript has shown the warning
# to the user. mForReUpload is set when the user clicks the "upload a
# new version" link.
- if ( !$warnings || ( count( $warnings ) == 1 &&
- isset( $warnings['exists'] ) &&
+ if ( !$warnings || ( count( $warnings ) == 1 &&
+ isset( $warnings['exists'] ) &&
( $this->mDestWarningAck || $this->mForReUpload ) ) )
{
return false;
@@ -436,16 +441,15 @@ class SpecialUpload extends SpecialPage {
return;
}
-
// Upload verification
$details = $this->mUpload->verifyUpload();
if ( $details['status'] != UploadBase::OK ) {
$this->processVerificationError( $details );
return;
}
-
+
// Verify permissions for this title
- $permErrors = $this->mUpload->verifyPermissions( $wgUser );
+ $permErrors = $this->mUpload->verifyTitlePermissions( $wgUser );
if( $permErrors !== true ) {
$code = array_shift( $permErrors[0] );
$this->showRecoverableUploadError( wfMsgExt( $code,
@@ -574,6 +578,10 @@ class SpecialUpload extends SpecialPage {
$this->showRecoverableUploadError( wfMsgExt( 'filetype-missing',
'parseinline' ) );
break;
+ case UploadBase::WINDOWS_NONASCII_FILENAME:
+ $this->showRecoverableUploadError( wfMsgExt( 'windows-nonascii-filename',
+ 'parseinline' ) );
+ break;
/** Statuses that require reuploading **/
case UploadBase::EMPTY_FILE:
@@ -583,18 +591,25 @@ class SpecialUpload extends SpecialPage {
$this->showUploadError( wfMsgHtml( 'largefileserver' ) );
break;
case UploadBase::FILETYPE_BADTYPE:
- $finalExt = $details['finalExt'];
- $this->showUploadError(
- wfMsgExt( 'filetype-banned-type',
- array( 'parseinline' ),
- htmlspecialchars( $finalExt ),
- implode(
- wfMsgExt( 'comma-separator', array( 'escapenoentities' ) ),
- $wgFileExtensions
- ),
- $wgLang->formatNum( count( $wgFileExtensions ) )
- )
- );
+ $msg = wfMessage( 'filetype-banned-type' );
+ if ( isset( $details['blacklistedExt'] ) ) {
+ $msg->params( $wgLang->commaList( $details['blacklistedExt'] ) );
+ } else {
+ $msg->params( $details['finalExt'] );
+ }
+ $msg->params( $wgLang->commaList( $wgFileExtensions ),
+ count( $wgFileExtensions ) );
+
+ // Add PLURAL support for the first parameter. This results
+ // in a bit unlogical parameter sequence, but does not break
+ // old translations
+ if ( isset( $details['blacklistedExt'] ) ) {
+ $msg->params( count( $details['blacklistedExt'] ) );
+ } else {
+ $msg->params( 1 );
+ }
+
+ $this->showUploadError( $msg->parse() );
break;
case UploadBase::VERIFICATION_ERROR:
unset( $details['status'] );
@@ -690,7 +705,7 @@ class SpecialUpload extends SpecialPage {
'page' => $filename
)
);
- $warning = wfMsgWikiHtml( 'filewasdeleted', $llink );
+ $warning = wfMsgExt( 'filewasdeleted', array( 'parse', 'replaceafter' ), $llink );
}
return $warning;
@@ -761,6 +776,8 @@ class UploadForm extends HTMLForm {
protected $mSourceIds;
+ protected $mMaxFileSize = array();
+
public function __construct( $options = array() ) {
$this->mWatch = !empty( $options['watch'] );
$this->mForReUpload = !empty( $options['forreupload'] );
@@ -777,7 +794,7 @@ class UploadForm extends HTMLForm {
? $options['texttop'] : '';
$this->mTextAfterSummary = isset( $options['textaftersummary'] )
- ? $options['textaftersummary'] : '';
+ ? $options['textaftersummary'] : '';
$sourceDescriptor = $this->getSourceSection();
$descriptor = $sourceDescriptor
@@ -812,7 +829,6 @@ class UploadForm extends HTMLForm {
*/
protected function getSourceSection() {
global $wgLang, $wgUser, $wgRequest;
- global $wgMaxUploadSize;
if ( $this->mSessionKey ) {
return array(
@@ -841,6 +857,14 @@ class UploadForm extends HTMLForm {
);
}
+ $this->mMaxUploadSize['file'] = UploadBase::getMaxUploadSize( 'file' );
+ # Limit to upload_max_filesize unless we are running under HipHop and
+ # that setting doesn't exist
+ if ( !wfIsHipHop() ) {
+ $this->mMaxUploadSize['file'] = min( $this->mMaxUploadSize['file'],
+ wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ) );
+ }
+
$descriptor['UploadFile'] = array(
'class' => 'UploadSourceField',
'section' => 'source',
@@ -851,17 +875,12 @@ class UploadForm extends HTMLForm {
'radio' => &$radio,
'help' => wfMsgExt( 'upload-maxfilesize',
array( 'parseinline', 'escapenoentities' ),
- $wgLang->formatSize(
- wfShorthandToInteger( min(
- wfShorthandToInteger(
- ini_get( 'upload_max_filesize' )
- ), $wgMaxUploadSize
- ) )
- )
+ $wgLang->formatSize( $this->mMaxUploadSize['file'] )
) . ' ' . wfMsgHtml( 'upload_source_file' ),
'checked' => $selectedSourceType == 'file',
);
if ( $canUploadByUrl ) {
+ $this->mMaxUploadSize['url'] = UploadBase::getMaxUploadSize( 'url' );
$descriptor['UploadFileURL'] = array(
'class' => 'UploadSourceField',
'section' => 'source',
@@ -871,7 +890,7 @@ class UploadForm extends HTMLForm {
'radio' => &$radio,
'help' => wfMsgExt( 'upload-maxfilesize',
array( 'parseinline', 'escapenoentities' ),
- $wgLang->formatSize( $wgMaxUploadSize )
+ $wgLang->formatSize( $this->mMaxUploadSize['url'] )
) . ' ' . wfMsgHtml( 'upload_source_url' ),
'checked' => $selectedSourceType == 'url',
);
@@ -903,16 +922,16 @@ class UploadForm extends HTMLForm {
# Everything not permitted is banned
$extensionsList =
'<div id="mw-upload-permitted">' .
- wfMsgWikiHtml( 'upload-permitted', $wgLang->commaList( $wgFileExtensions ) ) .
+ wfMsgExt( 'upload-permitted', 'parse', $wgLang->commaList( $wgFileExtensions ) ) .
"</div>\n";
} else {
# We have to list both preferred and prohibited
$extensionsList =
'<div id="mw-upload-preferred">' .
- wfMsgWikiHtml( 'upload-preferred', $wgLang->commaList( $wgFileExtensions ) ) .
+ wfMsgExt( 'upload-preferred', 'parse', $wgLang->commaList( $wgFileExtensions ) ) .
"</div>\n" .
'<div id="mw-upload-prohibited">' .
- wfMsgWikiHtml( 'upload-prohibited', $wgLang->commaList( $wgFileBlacklist ) ) .
+ wfMsgExt( 'upload-prohibited', 'parse', $wgLang->commaList( $wgFileBlacklist ) ) .
"</div>\n";
}
} else {
@@ -931,6 +950,26 @@ class UploadForm extends HTMLForm {
protected function getDescriptionSection() {
global $wgUser;
+ if ( $this->mSessionKey ) {
+ $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
+ try {
+ $file = $stash->getFile( $this->mSessionKey );
+ } catch ( MWException $e ) {
+ $file = null;
+ }
+ if ( $file ) {
+ global $wgContLang;
+
+ $mto = $file->transform( array( 'width' => 120 ) );
+ $this->addHeaderText(
+ '<div class="thumb t' . $wgContLang->alignEnd() . '">' .
+ Html::element( 'img', array(
+ 'src' => $mto->getUrl(),
+ 'class' => 'thumbimage',
+ ) ) . '</div>', 'description' );
+ }
+ }
+
$descriptor = array(
'DestFile' => array(
'type' => 'text',
@@ -939,7 +978,7 @@ class UploadForm extends HTMLForm {
'label-message' => 'destfilename',
'size' => 60,
'default' => $this->mDestFile,
- # FIXME: hack to work around poor handling of the 'default' option in HTMLForm
+ # @todo FIXME: Hack to work around poor handling of the 'default' option in HTMLForm
'nodata' => strval( $this->mDestFile ) !== '',
),
'UploadDescription' => array(
@@ -967,6 +1006,7 @@ class UploadForm extends HTMLForm {
'EditTools' => array(
'type' => 'edittools',
'section' => 'description',
+ 'message' => 'edittools-upload',
)
);
@@ -1035,7 +1075,7 @@ class UploadForm extends HTMLForm {
'id' => 'wpDestFileWarningAck',
'default' => $this->mDestWarningAck ? '1' : '',
);
-
+
if ( $this->mForReUpload ) {
$descriptor['ForReUpload'] = array(
'type' => 'hidden',
@@ -1064,6 +1104,7 @@ class UploadForm extends HTMLForm {
$useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
$useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview && $wgEnableAPI;
+ $this->mMaxUploadSize['*'] = UploadBase::getMaxUploadSize();
$scriptVars = array(
'wgAjaxUploadDestCheck' => $useAjaxDestCheck,
@@ -1075,12 +1116,17 @@ class UploadForm extends HTMLForm {
'wgUploadSourceIds' => $this->mSourceIds,
'wgStrictFileExtensions' => $wgStrictFileExtensions,
'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ),
+ 'wgMaxUploadSize' => $this->mMaxUploadSize,
);
$wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
- // For <charinsert> support
- $wgOut->addModules( array( 'mediawiki.legacy.edit', 'mediawiki.legacy.upload' ) );
+
+ $wgOut->addModules( array(
+ 'mediawiki.action.edit', // For <charinsert> support
+ 'mediawiki.legacy.upload', // Old form stuff...
+ 'mediawiki.special.upload', // Newer extras for thumbnail preview.
+ ) );
}
/**
diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php
index 48a41a5e..20a37f0b 100644
--- a/includes/specials/SpecialUploadStash.php
+++ b/includes/specials/SpecialUploadStash.php
@@ -20,13 +20,7 @@ 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,
+ // 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
@@ -34,19 +28,15 @@ class SpecialUploadStash extends UnlistedSpecialPage {
//
// 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;
+ const MAX_SERVE_BYTES = 1048576; // 1MB
+ public function __construct() {
parent::__construct( 'UploadStash', 'upload' );
try {
$this->stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
} catch ( UploadStashNotAvailableException $e ) {
return null;
}
-
- $this->loadRequest( is_null( $request ) ? $wgRequest : $request );
}
/**
@@ -91,7 +81,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
return $this->outputLocalFile( $params['file'] );
}
} catch( UploadStashFileNotFoundException $e ) {
- $code = 404;
+ $code = 404;
$message = $e->getMessage();
} catch( UploadStashZeroLengthFileException $e ) {
$code = 500;
@@ -107,15 +97,15 @@ class SpecialUploadStash extends UnlistedSpecialPage {
$message = $e->getMessage();
}
- wfHttpError( $code, OutputPage::getStatusMessage( $code ), $message );
+ wfHttpError( $code, HttpStatus::getMessage( $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
+ * 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
*/
@@ -132,28 +122,27 @@ class SpecialUploadStash extends UnlistedSpecialPage {
$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 );
+ $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
- */
+ *
+ * @param $file
+ * @param $params array
+ *
+ * @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
@@ -165,16 +154,13 @@ class SpecialUploadStash extends UnlistedSpecialPage {
} 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 $file: File object
* @param $params: scaling parameters ( e.g. array( width => '50' ) );
- * @param $flags: scaling flags ( see File:: constants )
+ * @param $flags: scaling flags ( see File:: constants )
* @throws MWException
* @return boolean success
*/
@@ -182,7 +168,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// 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 );
@@ -203,41 +189,47 @@ class SpecialUploadStash extends UnlistedSpecialPage {
}
return $this->outputLocalFile( $thumbFile );
-
+
}
-
+
/**
* Scale a file with a remote "scaler", as exists on the Wikimedia Foundation cluster, and output it to STDOUT.
- * Note: unlike the usual thumbnail process, the web client never sees the cluster URL; we do the whole HTTP transaction to the scaler ourselves
+ * 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: 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 $file: File object
* @param $params: scaling parameters ( e.g. array( width => '50' ) );
- * @param $flags: scaling flags ( see File:: constants )
+ * @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;
-
+ // We need to use generateThumbName() instead of thumbName(), because
+ // the suffix needs to match the file name for the remote thumbnailer
+ // to work
+ $scalerThumbName = $file->generateThumbName( $file->getName(), $params );
+ $scalerThumbUrl = $wgUploadStashScalerBaseUrl . '/' . $file->getUrlRel() .
+ '/' . rawurlencode( $scalerThumbName );
+
// make a curl call to the scaler to create a thumbnail
- $httpOptions = array(
+ $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 ) );
+ $errors = $status->getErrorsArray();
+ $errorStr = "Fetching thumbnail failed: " . print_r( $errors, 1 );
+ $errorStr .= "\nurl = $scalerThumbUrl\n";
+ throw new MWException( $errorStr );
}
$contentType = $req->getResponseHeader( "content-type" );
if ( ! $contentType ) {
@@ -257,13 +249,13 @@ class SpecialUploadStash extends UnlistedSpecialPage {
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
@@ -275,11 +267,11 @@ class SpecialUploadStash extends UnlistedSpecialPage {
throw new SpecialUploadStashTooLargeException();
}
self::outputFileHeaders( $contentType, $size );
- print $content;
+ 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.
@@ -290,38 +282,20 @@ class SpecialUploadStash extends UnlistedSpecialPage {
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' );
-
- }
+ header( "Content-Length: $size", true );
}
/**
- * Static callback for the HTMLForm in showUploads, to process
+ * 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 ( isset( $formData['Clear'] ) ) {
+ $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
+ wfDebug( "stash has: " . print_r( $stash->listFiles(), true ) );
if ( ! $stash->clear() ) {
return Status::newFatal( 'uploadstash-errclear' );
}
@@ -333,7 +307,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* 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 ) {
@@ -344,49 +318,49 @@ class SpecialUploadStash extends UnlistedSpecialPage {
$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',
+ $form = new HTMLForm( array(
+ 'Clear' => array(
+ 'type' => 'hidden',
'default' => true,
'name' => 'clear',
- )
+ )
), 'clearStashedUploads' );
- $form->setSubmitCallback( array( __CLASS__, 'tryClearStashedUploads' ) );
+ $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();
-
+ $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' ) );
+ $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 ) {
+ // TODO: Use Linker::link or even construct the list in plain wikitext
$fileListItemsHtml .= Html::rawElement( 'li', array(),
- Html::element( 'a', array( 'href' =>
+ Html::element( 'a', array( 'href' =>
$this->getTitle( "file/$file" )->getLocalURL() ), $file )
);
}
$wgOut->addHtml( Html::rawElement( 'ul', array(), $fileListItemsHtml ) );
- $form->displayForm( $formResult );
+ $form->displayForm( $formResult );
$wgOut->addHtml( Html::rawElement( 'p', array(), $refreshHtml ) );
} else {
- $wgOut->addHtml( Html::rawElement( 'p', array(),
+ $wgOut->addHtml( Html::rawElement( 'p', array(),
Html::element( 'span', array(), wfMsg( 'uploadstash-nofiles' ) )
- . ' '
+ . ' '
. $refreshHtml
) );
}
-
+
return true;
}
}
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index ccace79d..01dc9a1c 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -22,24 +22,11 @@
*/
/**
- * Constructor
- */
-function wfSpecialUserlogin( $par = '' ) {
- global $wgRequest;
- if( session_id() == '' ) {
- wfSetupSession();
- }
-
- $form = new LoginForm( $wgRequest, $par );
- $form->execute();
-}
-
-/**
* Implements Special:UserLogin
*
* @ingroup SpecialPage
*/
-class LoginForm {
+class LoginForm extends SpecialPage {
const SUCCESS = 0;
const NO_NAME = 1;
@@ -56,23 +43,42 @@ class LoginForm {
const NEED_TOKEN = 12;
const WRONG_TOKEN = 13;
- var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted;
- var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword;
+ var $mUsername, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted;
+ var $mAction, $mCreateaccount, $mCreateaccountMail;
var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS;
+ var $mType, $mReason, $mRealName;
+ var $mAbortLoginErrorMsg = 'login-abort-generic';
+ /**
+ * @var ExternalUser
+ */
private $mExtUser = null;
/**
- * Constructor
- * @param $request WebRequest: a WebRequest object passed by reference
- * @param $par String: subpage parameter
+ * @param WebRequest $request
+ */
+ public function __construct( $request = null ) {
+ parent::__construct( 'Userlogin' );
+
+ if ( $request === null ) {
+ global $wgRequest;
+ $this->load( $wgRequest );
+ } else {
+ $this->load( $request );
+ }
+ }
+
+ /**
+ * Loader
+ *
+ * @param $request WebRequest object
*/
- function __construct( &$request, $par = '' ) {
+ function load( $request ) {
global $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
- $this->mType = ( $par == 'signup' ) ? $par : $request->getText( 'type' ); # Check for [[Special:Userlogin/signup]]
- $this->mName = $request->getText( 'wpName' );
+ $this->mType = $request->getText( 'type' );
+ $this->mUsername = $request->getText( 'wpName' );
$this->mPassword = $request->getText( 'wpPassword' );
$this->mRetype = $request->getText( 'wpRetype' );
$this->mDomain = $request->getText( 'wpDomain' );
@@ -83,9 +89,7 @@ class LoginForm {
$this->mPosted = $request->wasPosted();
$this->mCreateaccount = $request->getCheck( 'wpCreateaccount' );
$this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' )
- && $wgEnableEmail;
- $this->mMailmypassword = $request->getCheck( 'wpMailmypassword' )
- && $wgEnableEmail;
+ && $wgEnableEmail;
$this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
$this->mAction = $request->getVal( 'action' );
$this->mRemember = $request->getCheck( 'wpRemember' );
@@ -105,9 +109,9 @@ class LoginForm {
$this->mEmail = '';
}
if( !in_array( 'realname', $wgHiddenPrefs ) ) {
- $this->mRealName = $request->getText( 'wpRealName' );
+ $this->mRealName = $request->getText( 'wpRealName' );
} else {
- $this->mRealName = '';
+ $this->mRealName = '';
}
if( !$wgAuth->validDomain( $this->mDomain ) ) {
@@ -123,7 +127,15 @@ class LoginForm {
}
}
- function execute() {
+ public function execute( $par ) {
+ if ( session_id() == '' ) {
+ wfSetupSession();
+ }
+
+ if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]]
+ $this->mType = 'signup';
+ }
+
if ( !is_null( $this->mCookieCheck ) ) {
$this->onCookieRedirectCheck( $this->mCookieCheck );
return;
@@ -132,8 +144,6 @@ class LoginForm {
return $this->addNewAccount();
} elseif ( $this->mCreateaccountMail ) {
return $this->addNewAccountMailPassword();
- } elseif ( $this->mMailmypassword ) {
- return $this->mailPassword();
} elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
return $this->processLogin();
}
@@ -167,8 +177,6 @@ class LoginForm {
$u->addNewUserLogEntry( true, $this->mReason );
$wgOut->setPageTitle( wfMsg( 'accmailtitle' ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setArticleRelated( false );
if( !$result->isGood() ) {
$this->mainLoginForm( wfMsg( 'mailerror', $result->getWikiText() ) );
@@ -198,7 +206,7 @@ class LoginForm {
}
# Send out an email authentication message if needed
- if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) ) {
+ if( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) {
$status = $u->sendConfirmationMail();
if( $status->isGood() ) {
$wgOut->addWikiMsg( 'confirmemail_oncreate' );
@@ -216,6 +224,10 @@ class LoginForm {
if( $wgUser->isAnon() ) {
$wgUser = $u;
$wgUser->setCookies();
+ // This should set it for OutputPage and the Skin
+ // which is needed or the personal links will be
+ // wrong.
+ RequestContext::getMain()->setUser( $u );
wfRunHooks( 'AddNewAccount', array( $wgUser, false ) );
$wgUser->addNewUserLogEntry();
if( $this->hasSessionCookie() ) {
@@ -227,9 +239,7 @@ class LoginForm {
# Confirm that the account was created
$self = SpecialPage::getTitleFor( 'Userlogin' );
$wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) );
- $wgOut->setArticleRelated( false );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->addHTML( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) );
+ $wgOut->addWikiMsg( 'accountcreatedtext', $u->getName() );
$wgOut->returnToMain( false, $self );
wfRunHooks( 'AddNewAccount', array( $u, false ) );
$u->addNewUserLogEntry( false, $this->mReason );
@@ -258,7 +268,8 @@ class LoginForm {
// create a local account and login as any domain user). We only need
// to check this for domains that aren't local.
if( 'local' != $this->mDomain && $this->mDomain != '' ) {
- if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mName ) || !$wgAuth->authenticate( $this->mName, $this->mPassword ) ) ) {
+ if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mUsername )
+ || !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) ) ) {
$this->mainLoginForm( wfMsg( 'wrongpassword' ) );
return false;
}
@@ -272,7 +283,7 @@ class LoginForm {
# Request forgery checks.
if ( !self::getCreateaccountToken() ) {
self::setCreateaccountToken();
- $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) );
+ $this->mainLoginForm( wfMsgExt( 'nocookiesfornew', array( 'parseinline' ) ) );
return false;
}
@@ -293,7 +304,7 @@ class LoginForm {
$wgOut->permissionRequired( 'createaccount' );
return false;
} elseif ( $wgUser->isBlockedFromCreateAccount() ) {
- $this->userBlockedMessage();
+ $this->userBlockedMessage( $wgUser->isBlockedFromCreateAccount() );
return false;
}
@@ -304,7 +315,7 @@ class LoginForm {
}
# Now create a dummy user ($u) and check if it is valid
- $name = trim( $this->mName );
+ $name = trim( $this->mUsername );
$u = User::newFromName( $name, 'creatable' );
if ( !is_object( $u ) ) {
$this->mainLoginForm( wfMsg( 'noname' ) );
@@ -325,7 +336,14 @@ class LoginForm {
$valid = $u->getPasswordValidity( $this->mPassword );
if ( $valid !== true ) {
if ( !$this->mCreateaccountMail ) {
- $this->mainLoginForm( wfMsgExt( $valid, array( 'parsemag' ), $wgMinimalPasswordLength ) );
+ if ( is_array( $valid ) ) {
+ $message = array_shift( $valid );
+ $params = $valid;
+ } else {
+ $message = $valid;
+ $params = array( $wgMinimalPasswordLength );
+ }
+ $this->mainLoginForm( wfMsgExt( $message, array( 'parsemag' ), $params ) );
return false;
} else {
# do not force a password for account creation by email
@@ -341,7 +359,7 @@ class LoginForm {
return false;
}
- if( !empty( $this->mEmail ) && !User::isValidEmailAddr( $this->mEmail ) ) {
+ if( !empty( $this->mEmail ) && !Sanitizer::validateEmail( $this->mEmail ) ) {
$this->mainLoginForm( wfMsg( 'invalidemailaddress' ) );
return false;
}
@@ -431,9 +449,9 @@ class LoginForm {
* creation.
*/
public function authenticateUserData() {
- global $wgUser, $wgAuth, $wgMemc;
+ global $wgUser, $wgAuth;
- if ( $this->mName == '' ) {
+ if ( $this->mUsername == '' ) {
return self::NO_NAME;
}
@@ -452,22 +470,9 @@ class LoginForm {
return self::NEED_TOKEN;
}
- global $wgPasswordAttemptThrottle;
-
- $throttleCount = 0;
- if ( is_array( $wgPasswordAttemptThrottle ) ) {
- $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) );
- $count = $wgPasswordAttemptThrottle['count'];
- $period = $wgPasswordAttemptThrottle['seconds'];
-
- $throttleCount = $wgMemc->get( $throttleKey );
- if ( !$throttleCount ) {
- $wgMemc->add( $throttleKey, 1, $period ); // start counter
- } elseif ( $throttleCount < $count ) {
- $wgMemc->incr( $throttleKey );
- } elseif ( $throttleCount >= $count ) {
- return self::THROTTLED;
- }
+ $throttleCount = self::incLoginThrottle( $this->mUsername );
+ if ( $throttleCount === true ) {
+ return self::THROTTLED;
}
// Validate the login token
@@ -481,16 +486,16 @@ class LoginForm {
// creates the user in the database. Until we load $wgUser, checking
// 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" );
+ if ( $wgUser->getName() === $this->mUsername ) {
+ wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" );
return self::SUCCESS;
}
- $this->mExtUser = ExternalUser::newFromName( $this->mName );
+ $this->mExtUser = ExternalUser::newFromName( $this->mUsername );
# TODO: Allow some magic here for invalid external names, e.g., let the
# user choose a different wiki name.
- $u = User::newFromName( $this->mName );
+ $u = User::newFromName( $this->mUsername );
if( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) {
return self::ILLEGAL;
}
@@ -518,7 +523,7 @@ class LoginForm {
// Give general extensions, such as a captcha, a chance to abort logins
$abort = self::ABORTED;
- if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort ) ) ) {
+ if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$this->mAbortLoginErrorMsg ) ) ) {
return $abort;
}
@@ -559,10 +564,14 @@ class LoginForm {
} else {
$wgAuth->updateUser( $u );
$wgUser = $u;
+ // This should set it for OutputPage and the Skin
+ // which is needed or the personal links will be
+ // wrong.
+ RequestContext::getMain()->setUser( $u );
// Please reset throttle for successful logins, thanks!
- if( $throttleCount ) {
- $wgMemc->delete( $throttleKey );
+ if ( $throttleCount ) {
+ self::clearLoginThrottle( $this->mUsername );
}
if ( $isAutoCreated ) {
@@ -576,9 +585,52 @@ class LoginForm {
return $retval;
}
+ /*
+ * Increment the login attempt throttle hit count for the (username,current IP)
+ * tuple unless the throttle was already reached.
+ * @param $username string The user name
+ * @return Bool|Integer The integer hit count or True if it is already at the limit
+ */
+ public static function incLoginThrottle( $username ) {
+ global $wgPasswordAttemptThrottle, $wgMemc;
+
+ $throttleCount = 0;
+ if ( is_array( $wgPasswordAttemptThrottle ) ) {
+ $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $username ) );
+ $count = $wgPasswordAttemptThrottle['count'];
+ $period = $wgPasswordAttemptThrottle['seconds'];
+
+ $throttleCount = $wgMemc->get( $throttleKey );
+ if ( !$throttleCount ) {
+ $wgMemc->add( $throttleKey, 1, $period ); // start counter
+ } elseif ( $throttleCount < $count ) {
+ $wgMemc->incr( $throttleKey );
+ } elseif ( $throttleCount >= $count ) {
+ return true;
+ }
+ }
+
+ return $throttleCount;
+ }
+
+ /*
+ * Clear the login attempt throttle hit count for the (username,current IP) tuple.
+ * @param $username string The user name
+ * @return void
+ */
+ public static function clearLoginThrottle( $username ) {
+ global $wgMemc;
+
+ $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $username ) );
+ $wgMemc->delete( $throttleKey );
+ }
+
/**
* Attempt to automatically create a user on login. Only succeeds if there
* is an external authentication method which allows it.
+ *
+ * @param $user User
+ *
* @return integer Status code
*/
function attemptAutoCreate( $user ) {
@@ -618,6 +670,14 @@ class LoginForm {
}
}
+ $abortError = '';
+ if( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) {
+ // Hook point to add extra creation throttles and blocks
+ wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" );
+ $this->mAbortLoginErrorMsg = $abortError;
+ return self::ABORTED;
+ }
+
wfDebug( __METHOD__ . ": creating account\n" );
$this->initUser( $user, true );
return self::SUCCESS;
@@ -639,7 +699,7 @@ class LoginForm {
self::clearLoginToken();
// Reset the throttle
- $key = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) );
+ $key = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mUsername ) );
global $wgMemc;
$wgMemc->delete( $key );
@@ -657,7 +717,7 @@ class LoginForm {
break;
case self::NEED_TOKEN:
- $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
+ $this->mainLoginForm( wfMsgExt( 'nocookiesforlogin', array( 'parseinline' ) ) );
break;
case self::WRONG_TOKEN:
$this->mainLoginForm( wfMsg( 'sessionfailure' ) );
@@ -671,9 +731,11 @@ class LoginForm {
break;
case self::NOT_EXISTS:
if( $wgUser->isAllowed( 'createaccount' ) ) {
- $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mName ) ) );
+ $this->mainLoginForm( wfMsgExt( 'nosuchuser', 'parseinline',
+ wfEscapeWikiText( $this->mUsername ) ) );
} else {
- $this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName ) ) );
+ $this->mainLoginForm( wfMsg( 'nosuchusershort',
+ wfEscapeWikiText( $this->mUsername ) ) );
}
break;
case self::WRONG_PASS:
@@ -686,14 +748,17 @@ class LoginForm {
$this->resetLoginForm( wfMsg( 'resetpass_announce' ) );
break;
case self::CREATE_BLOCKED:
- $this->userBlockedMessage();
+ $this->userBlockedMessage( $wgUser->mBlock );
break;
case self::THROTTLED:
$this->mainLoginForm( wfMsg( 'login-throttled' ) );
break;
case self::USER_BLOCKED:
$this->mainLoginForm( wfMsgExt( 'login-userblocked',
- array( 'parsemag', 'escape' ), $this->mName ) );
+ array( 'parsemag', 'escape' ), $this->mUsername ) );
+ break;
+ case self::ABORTED:
+ $this->mainLoginForm( wfMsg( $this->mAbortLoginErrorMsg ) );
break;
default:
throw new MWException( 'Unhandled case value' );
@@ -703,100 +768,11 @@ class LoginForm {
function resetLoginForm( $error ) {
global $wgOut;
$wgOut->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) );
- $reset = new SpecialResetpass();
+ $reset = new SpecialChangePassword();
$reset->execute( null );
}
/**
- * @private
- */
- 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
- # with password resets
- if( $wgUser->isBlocked() ) {
- $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) );
- return;
- }
-
- # Check for hooks
- $error = null;
- if ( !wfRunHooks( 'UserLoginMailPassword', array( $this->mName, &$error ) ) ) {
- $this->mainLoginForm( $error );
- return;
- }
-
- # If the user doesn't have a login token yet, set one.
- if ( !self::getLoginToken() ) {
- self::setLoginToken();
- $this->mainLoginForm( wfMsg( 'sessionfailure' ) );
- return;
- }
-
- # If the user didn't pass a login token, tell them we need one
- if ( !$this->mToken ) {
- $this->mainLoginForm( wfMsg( 'sessionfailure' ) );
- return;
- }
-
- # Check against the rate limiter
- if( $wgUser->pingLimiter( 'mailpassword' ) ) {
- $wgOut->rateLimited();
- return;
- }
-
- if ( $this->mName == '' ) {
- $this->mainLoginForm( wfMsg( 'noname' ) );
- return;
- }
- $u = User::newFromName( $this->mName );
- if( !$u instanceof User ) {
- $this->mainLoginForm( wfMsg( 'noname' ) );
- return;
- }
- if ( 0 == $u->getID() ) {
- $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $u->getName() ) ) );
- return;
- }
-
- # Validate the login token
- if ( $this->mToken !== self::getLoginToken() ) {
- $this->mainLoginForm( wfMsg( 'sessionfailure' ) );
- return;
- }
-
- # Check against password throttle
- if ( $u->isPasswordReminderThrottled() ) {
- global $wgPasswordReminderResendTime;
- # Round the time in hours to 3 d.p., in case someone is specifying
- # minutes or seconds.
- $this->mainLoginForm( wfMsgExt( 'throttled-mailpassword', array( 'parsemag' ),
- round( $wgPasswordReminderResendTime, 3 ) ) );
- return;
- }
-
- $result = $this->mailPasswordInternal( $u, true, 'passwordremindertitle', 'passwordremindertext' );
- if( $result->isGood() ) {
- $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ), 'success' );
- self::clearLoginToken();
- } else {
- $this->mainLoginForm( $result->getWikiText( 'mailerror' ) );
- }
- }
-
-
- /**
* @param $u User object
* @param $throttle Boolean
* @param $emailTitle String: message name of email title
@@ -872,9 +848,14 @@ class LoginForm {
global $wgUser;
# Run any hooks; display injected HTML
$injected_html = '';
+ $welcome_creation_msg = 'welcomecreation';
+
wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$injected_html ) );
- $this->displaySuccessfulLogin( 'welcomecreation', $injected_html );
+ //let any extensions change what message is shown
+ wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) );
+
+ $this->displaySuccessfulLogin( $welcome_creation_msg, $injected_html );
}
/**
@@ -884,9 +865,10 @@ class LoginForm {
global $wgOut, $wgUser;
$wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setArticleRelated( false );
- $wgOut->addWikiMsg( $msgname, $wgUser->getName() );
+ if( $msgname ){
+ $wgOut->addWikiMsg( $msgname, wfEscapeWikiText( $wgUser->getName() ) );
+ }
+
$wgOut->addHTML( $injected_html );
if ( !empty( $this->mReturnTo ) ) {
@@ -896,9 +878,15 @@ class LoginForm {
}
}
- /** */
- function userBlockedMessage() {
- global $wgOut, $wgUser;
+ /**
+ * Output a message that informs the user that they cannot create an account because
+ * there is a block on them or their IP which prevents account creation. Note that
+ * User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock'
+ * setting on blocks (bug 13611).
+ * @param $block Block the block causing this error
+ */
+ function userBlockedMessage( Block $block ) {
+ global $wgOut;
# Let's be nice about this, it's likely that this feature will be used
# for blocking large numbers of innocent people, e.g. range blocks on
@@ -909,17 +897,19 @@ class LoginForm {
# out.
$wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setArticleRelated( false );
-
- $ip = wfGetIP();
- $blocker = User::whoIs( $wgUser->mBlock->mBy );
- $block_reason = $wgUser->mBlock->mReason;
+ $block_reason = $block->mReason;
if ( strval( $block_reason ) === '' ) {
$block_reason = wfMsg( 'blockednoreason' );
}
- $wgOut->addWikiMsg( 'cantcreateaccount-text', $ip, $block_reason, $blocker );
+
+ $wgOut->addWikiMsg(
+ 'cantcreateaccount-text',
+ $block->getTarget(),
+ $block_reason,
+ $block->getBlocker()->getName()
+ );
+
$wgOut->returnToMain( false );
}
@@ -927,10 +917,11 @@ class LoginForm {
* @private
*/
function mainLoginForm( $msg, $msgtype = 'error' ) {
- global $wgUser, $wgOut, $wgHiddenPrefs, $wgEnableEmail;
+ global $wgUser, $wgOut, $wgHiddenPrefs;
+ global $wgEnableEmail, $wgEnableUserEmail;
global $wgRequest, $wgLoginLanguageSelector;
global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
- global $wgSecureLogin;
+ global $wgSecureLogin, $wgPasswordResetRoutes;
$titleObj = SpecialPage::getTitleFor( 'Userlogin' );
@@ -942,7 +933,7 @@ class LoginForm {
$wgOut->readOnlyPage();
return;
} elseif ( $wgUser->isBlockedFromCreateAccount() ) {
- $this->userBlockedMessage();
+ $this->userBlockedMessage( $wgUser->isBlockedFromCreateAccount() );
return;
} elseif ( count( $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) {
$wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' );
@@ -950,11 +941,11 @@ class LoginForm {
}
}
- if ( $this->mName == '' ) {
+ if ( $this->mUsername == '' ) {
if ( $wgUser->isLoggedIn() ) {
- $this->mName = $wgUser->getName();
+ $this->mUsername = $wgUser->getName();
} else {
- $this->mName = $wgRequest->getCookie( 'UserName' );
+ $this->mUsername = $wgRequest->getCookie( 'UserName' );
}
}
@@ -996,8 +987,12 @@ class LoginForm {
$template->set( 'link', '' );
}
+ $resetLink = $this->mType == 'signup'
+ ? null
+ : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) );
+
$template->set( 'header', '' );
- $template->set( 'name', $this->mName );
+ $template->set( 'name', $this->mUsername );
$template->set( 'password', $this->mPassword );
$template->set( 'retype', $this->mRetype );
$template->set( 'email', $this->mEmail );
@@ -1012,7 +1007,9 @@ class LoginForm {
$template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) );
$template->set( 'useemail', $wgEnableEmail );
$template->set( 'emailrequired', $wgEmailConfirmToEdit );
+ $template->set( 'emailothers', $wgEnableUserEmail );
$template->set( 'canreset', $wgAuth->allowPasswordChange() );
+ $template->set( 'resetlink', $resetLink );
$template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
$template->set( 'usereason', $wgUser->isLoggedIn() );
$template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) || $this->mRemember );
@@ -1037,6 +1034,22 @@ class LoginForm {
if( $this->mLanguage )
$template->set( 'uselang', $this->mLanguage );
}
+
+ // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise
+ // Ditto for signupend
+ $usingHTTPS = WebRequest::detectProtocol() == 'https';
+ $loginendHTTPS = wfMessage( 'loginend-https' );
+ $signupendHTTPS = wfMessage( 'signupend-https' );
+ if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) {
+ $template->set( 'loginend', $loginendHTTPS->parse() );
+ } else {
+ $template->set( 'loginend', wfMessage( 'loginend' )->parse() );
+ }
+ if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) {
+ $template->set( 'signupend', $signupendHTTPS->parse() );
+ } else {
+ $template->set( 'signupend', wfMessage( 'signupend' )->parse() );
+ }
// Give authentication and captcha plugins a chance to modify the form
$wgAuth->modifyUITemplate( $template, $this->mType );
@@ -1053,22 +1066,24 @@ class LoginForm {
$wgOut->setPageTitle( wfMsg( 'userloginnocreate' ) );
}
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setArticleRelated( false );
$wgOut->disallowUserJs(); // just in case...
$wgOut->addTemplate( $template );
}
/**
* @private
+ *
+ * @param $user User
+ *
+ * @return Boolean
*/
function showCreateOrLoginLink( &$user ) {
if( $this->mType == 'signup' ) {
- return( true );
+ return true;
} elseif( $user->isAllowed( 'createaccount' ) ) {
- return( true );
+ return true;
} else {
- return( false );
+ return false;
}
}
@@ -1186,15 +1201,15 @@ class LoginForm {
function makeLanguageSelector() {
global $wgLang;
- $msg = wfMsgForContent( 'loginlanguagelinks' );
- if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) {
- $langs = explode( "\n", $msg );
+ $msg = wfMessage( 'loginlanguagelinks' )->inContentLanguage();
+ if( !$msg->isBlank() ) {
+ $langs = explode( "\n", $msg->text() );
$links = array();
foreach( $langs as $lang ) {
$lang = trim( $lang, '* ' );
$parts = explode( '|', $lang );
if ( count( $parts ) >= 2 ) {
- $links[] = $this->makeLanguageSelectorLink( $parts[0], $parts[1] );
+ $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
}
}
return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : '';
@@ -1211,7 +1226,6 @@ class LoginForm {
* @param $lang Language code
*/
function makeLanguageSelectorLink( $text, $lang ) {
- global $wgUser;
$self = SpecialPage::getTitleFor( 'Userlogin' );
$attr = array( 'uselang' => $lang );
if( $this->mType == 'signup' ) {
@@ -1220,8 +1234,7 @@ class LoginForm {
if( $this->mReturnTo ) {
$attr['returnto'] = $this->mReturnTo;
}
- $skin = $wgUser->getSkin();
- return $skin->linkKnown(
+ return Linker::linkKnown(
$self,
htmlspecialchars( $text ),
array(),
diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php
index 6ea8668b..4de048c0 100644
--- a/includes/specials/SpecialUserrights.php
+++ b/includes/specials/SpecialUserrights.php
@@ -41,7 +41,7 @@ class UserrightsPage extends SpecialPage {
return true;
}
- public function userCanExecute( $user ) {
+ public function userCanExecute( User $user ) {
return $this->userCanChangeRights( $user, false );
}
@@ -98,7 +98,7 @@ class UserrightsPage extends SpecialPage {
}
if( !$this->userCanChangeRights( $wgUser, true ) ) {
- // fixme... there may be intermediate groups we can mention.
+ // @todo FIXME: There may be intermediate groups we can mention.
$wgOut->showPermissionsErrorPage( array( array(
$wgUser->isAnon()
? 'userrights-nologin'
@@ -112,7 +112,7 @@ class UserrightsPage extends SpecialPage {
}
$this->outputHeader();
-
+ $wgOut->addModuleStyles( 'mediawiki.special' );
$this->setHeaders();
// show the general form
@@ -222,7 +222,7 @@ class UserrightsPage extends SpecialPage {
$user->removeGroup( $group );
}
}
- if( $add ) {
+ if( $add ) {
$newGroups = array_merge( $newGroups, $add );
foreach( $add as $group ) {
$user->addGroup( $group );
@@ -317,7 +317,7 @@ class UserrightsPage extends SpecialPage {
return Status::newFatal( 'nouserspecified' );
}
- if( $name{0} == '#' ) {
+ if( $name[0] == '#' ) {
// Numeric ID can be specified...
// We'll do a lookup for the name internally.
$id = intval( substr( $name, 1 ) );
@@ -414,7 +414,7 @@ class UserrightsPage extends SpecialPage {
* @param $groups Array: Array of groups the user is in
*/
protected function showEditUserGroupsForm( $user, $groups ) {
- global $wgOut, $wgUser, $wgLang;
+ global $wgOut, $wgUser, $wgLang, $wgRequest;
$list = array();
foreach( $groups as $group ) {
@@ -429,12 +429,14 @@ class UserrightsPage extends SpecialPage {
}
$grouplist = '';
- if( count( $list ) > 0 ) {
- $grouplist = wfMsgHtml( 'userrights-groupsmember' );
+ $count = count( $list );
+ if( $count > 0 ) {
+ $grouplist = wfMessage( 'userrights-groupsmember', $count)->parse();
$grouplist = '<p>' . $grouplist . ' ' . $wgLang->listToText( $list ) . "</p>\n";
}
- if( count( $autolist ) > 0 ) {
- $autogrouplistintro = wfMsgHtml( 'userrights-groupsmember-auto' );
+ $count = count( $autolist );
+ if( $count > 0 ) {
+ $autogrouplistintro = wfMessage( 'userrights-groupsmember-auto', $count)->parse();
$grouplist .= '<p>' . $autogrouplistintro . ' ' . $wgLang->listToText( $autolist ) . "</p>\n";
}
$wgOut->addHTML(
@@ -453,14 +455,15 @@ class UserrightsPage extends SpecialPage {
Xml::label( wfMsg( 'userrights-reason' ), 'wpReason' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'user-reason', 60, false, array( 'id' => 'wpReason', 'maxlength' => 255 ) ) .
+ Xml::input( 'user-reason', 60, $wgRequest->getVal( 'user-reason', false ),
+ array( 'id' => 'wpReason', 'maxlength' => 255 ) ) .
"</td>
</tr>
<tr>
<td></td>
<td class='mw-submit'>" .
Xml::submitButton( wfMsg( 'saveusergroups' ),
- array( 'name' => 'saveusergroups' ) + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'userrights-set' ) ) .
+ array( 'name' => 'saveusergroups' ) + Linker::tooltipAndAccesskeyAttribs( 'userrights-set' ) ) .
"</td>
</tr>" .
Xml::closeElement( 'table' ) . "\n" .
@@ -534,7 +537,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, wfMessage( 'userrights-' . $name . '-col', count( $column ) )->text() );
}
$ret.= "</tr>\n<tr>\n";
foreach( $columns as $column ) {
diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php
index 101823db..0331f056 100644
--- a/includes/specials/SpecialVersion.php
+++ b/includes/specials/SpecialVersion.php
@@ -29,15 +29,15 @@
* @ingroup SpecialPage
*/
class SpecialVersion extends SpecialPage {
-
+
protected $firstExtOpened = false;
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:
+ # Doesn't work at the time of writing but maybe some day:
'https://svn.wikimedia.org/viewvc/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
);
@@ -49,30 +49,33 @@ class SpecialVersion extends SpecialPage {
* main()
*/
public function execute( $par ) {
- global $wgOut, $wgSpecialVersionShowHooks, $wgContLang;
-
+ global $wgOut, $wgSpecialVersionShowHooks, $wgRequest;
+
$this->setHeaders();
$this->outputHeader();
$wgOut->allowClickjacking();
- $wgOut->addHTML( Xml::openElement( 'div',
- array( 'dir' => $wgContLang->getDir() ) ) );
- $text =
+ $text =
$this->getMediaWikiCredits() .
$this->softwareInformation() .
$this->getExtensionCredits();
if ( $wgSpecialVersionShowHooks ) {
$text .= $this->getWgHooks();
}
-
+
$wgOut->addWikiText( $text );
$wgOut->addHTML( $this->IPInfo() );
- $wgOut->addHTML( '</div>' );
+
+ if ( $wgRequest->getVal( 'easteregg' ) ) {
+ if ( $this->showEasterEgg() ) {
+ // TODO: put something interesting here
+ }
+ }
}
/**
* Returns wiki text showing the license information.
- *
+ *
* @return string
*/
private static function getMediaWikiCredits() {
@@ -113,7 +116,7 @@ class SpecialVersion extends SpecialPage {
/**
* Returns wiki text showing the third party software versions (apache, php, mysql).
- *
+ *
* @return string
*/
static function softwareInformation() {
@@ -136,14 +139,14 @@ 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>
+ <td class=\"ltr\">" . $version . "</td>
</tr>\n";
}
-
+
return $out . Xml::closeElement( 'table' );
}
@@ -163,8 +166,8 @@ class SpecialVersion extends SpecialPage {
$version = "$wgVersion (r{$info['checkout-rev']})";
} else {
$version = $wgVersion . ' ' .
- wfMsg(
- 'version-svn-revision',
+ wfMsg(
+ 'version-svn-revision',
isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
$info['checkout-rev']
);
@@ -173,7 +176,7 @@ class SpecialVersion extends SpecialPage {
wfProfileOut( __METHOD__ );
return $version;
}
-
+
/**
* Return a wikitext-formatted string of the MediaWiki version with a link to
* the SVN revision if available.
@@ -183,16 +186,16 @@ class SpecialVersion extends SpecialPage {
public static function getVersionLinked() {
global $wgVersion, $IP;
wfProfileIn( __METHOD__ );
-
+
$info = self::getSvnInfo( $IP );
-
+
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 {
@@ -201,7 +204,7 @@ class SpecialVersion extends SpecialPage {
} else {
$version = $wgVersion;
}
-
+
wfProfileOut( __METHOD__ );
return $version;
}
@@ -209,13 +212,13 @@ class SpecialVersion extends SpecialPage {
/**
* Returns an array with the base extension types.
* Type is stored as array key, the message as array value.
- *
+ *
* TODO: ideally this would return all extension types, including
* those added by SpecialVersionExtensionTypes. This is not possible
* since this hook is passing along $this though.
- *
+ *
* @since 1.17
- *
+ *
* @return array
*/
public static function getExtensionTypes() {
@@ -225,44 +228,46 @@ class SpecialVersion extends SpecialPage {
'parserhook' => wfMsg( 'version-parserhooks' ),
'variable' => wfMsg( 'version-variables' ),
'media' => wfMsg( 'version-mediahandlers' ),
+ 'antispam' => wfMsg( 'version-antispam' ),
'skin' => wfMsg( 'version-skins' ),
+ 'api' => wfMsg( 'version-api' ),
'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;
+ global $wgExtensionCredits, $wgExtensionFunctions, $wgParser;
- if ( !count( $wgExtensionCredits ) && !count( $wgExtensionFunctions ) && !count( $wgSkinExtensionFunctions ) ) {
+ if ( !count( $wgExtensionCredits ) && !count( $wgExtensionFunctions ) ) {
return '';
}
$extensionTypes = self::getExtensionTypes();
-
+
/**
* @deprecated as of 1.17, use hook ExtensionTypes instead.
*/
@@ -271,25 +276,25 @@ class SpecialVersion extends SpecialPage {
$out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) .
Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-ext' ) );
- // Make sure the 'other' type is set to an array.
+ // 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'] );
@@ -309,36 +314,32 @@ class SpecialVersion extends SpecialPage {
$out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n";
}
- if( count( $fhooks = $wgParser->getFunctionHooks() ) ) {
+ $fhooks = $wgParser->getFunctionHooks();
+ if( count( $fhooks ) ) {
$out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ), 'parser-function-hooks' );
$out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n";
}
- if ( count( $wgSkinExtensionFunctions ) ) {
- $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;
-
+ global $wgExtensionCredits;
+
$out = '';
-
+
if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) {
$out .= $this->openExtType( $message, 'credits-' . $type );
@@ -350,7 +351,7 @@ class SpecialVersion extends SpecialPage {
}
return $out;
- }
+ }
/**
* Callback to sort extensions by type.
@@ -368,14 +369,14 @@ class SpecialVersion extends SpecialPage {
/**
* 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;
@@ -393,10 +394,10 @@ class SpecialVersion extends SpecialPage {
} else {
$mainLink = $name;
}
-
+
if ( isset( $extension['version'] ) ) {
- $versionText = '<span class="mw-version-ext-version">' .
- wfMsg( 'version-version', $extension['version'] ) .
+ $versionText = '<span class="mw-version-ext-version">' .
+ wfMsg( 'version-version', $extension['version'] ) .
'</span>';
} else {
$versionText = '';
@@ -412,22 +413,19 @@ class SpecialVersion extends SpecialPage {
# Make description text.
$description = isset ( $extension['description'] ) ? $extension['description'] : '';
-
+
if( isset ( $extension['descriptionmsg'] ) ) {
# 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
array_map( "htmlspecialchars", $descriptionMsg ); // For sanity
- $msg = wfMsg( $descriptionMsgKey, $descriptionMsg );
+ $description = wfMsg( $descriptionMsgKey, $descriptionMsg );
} else {
- $msg = wfMsg( $descriptionMsg );
+ $description = wfMsg( $descriptionMsg );
}
- if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) {
- $description = $msg;
- }
}
if ( $svnText !== false ) {
@@ -438,12 +436,12 @@ 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>
+ <td>" . $this->listAuthors( $author, false ) . "</td>
</tr>\n";
-
+
return $extNameVer . $extDescAuthor;
}
@@ -466,11 +464,12 @@ class SpecialVersion extends SpecialPage {
<th>" . wfMsg( 'version-hook-subscribedby' ) . "</th>
</tr>\n";
- foreach ( $myWgHooks as $hook => $hooks )
+ foreach ( $myWgHooks as $hook => $hooks ) {
$ret .= "<tr>
<td>$hook</td>
<td>" . $this->listToText( $hooks ) . "</td>
</tr>\n";
+ }
$ret .= Xml::closeElement( 'table' );
return $ret;
@@ -487,13 +486,13 @@ class SpecialVersion extends SpecialPage {
$out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n";
}
$this->firstExtOpened = true;
-
+
if( $name ) {
$opt['id'] = "sv-$name";
}
$out .= "<tr>" . Xml::element( 'th', $opt, $text ) . "</tr>\n";
-
+
return $out;
}
@@ -509,11 +508,29 @@ class SpecialVersion extends SpecialPage {
}
/**
+ * Return a formatted unsorted list of authors
+ *
+ * @param $authors mixed: string or array of strings
+ * @return String: HTML fragment
+ */
+ function listAuthors( $authors ) {
+ $list = array();
+ foreach( (array)$authors as $item ) {
+ if( $item == '...' ) {
+ $list[] = wfMsg( 'version-poweredby-others' );
+ } else {
+ $list[] = $item;
+ }
+ }
+ return $this->listToText( $list, false );
+ }
+
+ /**
* 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 ) {
@@ -538,29 +555,31 @@ class SpecialVersion extends SpecialPage {
*
* @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 )
+ public static function arrayToString( $list ) {
+ if( is_array( $list ) && count( $list ) == 1 ) {
$list = $list[0];
+ }
if( is_object( $list ) ) {
$class = get_class( $list );
return "($class)";
} elseif ( !is_array( $list ) ) {
return $list;
} else {
- if( is_object( $list[0] ) )
+ if( is_object( $list[0] ) ) {
$class = get_class( $list[0] );
- else
+ } else {
$class = $list[0];
+ }
return "($class, {$list[1]})";
}
}
/**
- * Get an associative array of information about a given path, from its .svn
- * subdirectory. Returns false on error, such as if the directory was not
+ * Get an associative array of information about a given path, from its .svn
+ * subdirectory. Returns false on error, such as if the directory was not
* checked out with subversion.
*
* Returned keys are:
@@ -608,7 +627,7 @@ class SpecialVersion extends SpecialPage {
}
}
}
-
+
return false;
}
@@ -616,26 +635,26 @@ class SpecialVersion extends SpecialPage {
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'],
+ $viewvc = str_replace(
+ $info['repo-url'],
self::$viewvcUrls[$info['repo-url']],
$info['url']
);
-
+
$viewvc .= '/?pathrev=';
$viewvc .= urlencode( $info['checkout-rev'] );
$info['viewvc-url'] = $viewvc;
}
-
+
return $info;
}
@@ -643,12 +662,12 @@ class SpecialVersion extends SpecialPage {
* Retrieve the revision number of a Subversion working directory.
*
* @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'] ) ) {
@@ -658,4 +677,108 @@ class SpecialVersion extends SpecialPage {
}
}
+ function showEasterEgg() {
+ $rx = $rp = $xe = '';
+ $alpha = array("", "kbQW", "\$\n()");
+ $beta = implode( "', '", $alpha);
+ $juliet = 'echo $delta + strrev($foxtrot) - $alfa + $wgVersion . base64_decode($bravo) * $charlie';
+ for ( $i = 1; $i <= 4; $i++ ) {
+ $rx .= '([^j]*)J';
+ $rp .= "+(\\$i)";
+ }
+
+ $rx = "/$rx/Sei";
+ $O = substr("$alpha')", 1);
+ for ( $i = 1; $i <= strlen( $rx ) / 3; $i++ ) {
+ $rx[$i-1] = strtolower( $rx[$i-1] );
+ }
+ $ry = ".*?(.((.)(.))).{1,3}(.)(.{1,$i})(\\4.\\3)(.).*";
+ $ry = "/$ry/Sei";
+ $O = substr("$beta')", 1);
+ preg_match_all('/(?<=\$)[[:alnum:]]*/',substr($juliet, 0, $i<<1), $charlie);
+ foreach( $charlie[0] as $bravo ) {
+ $$bravo =& $xe;
+ }
+ $xe = 'xe=<<<mo/./hfromowoxv=<<<m
+쵍潅旅𞗎왎캎𐺆ߨ趥䲀쫥𒯡𚦄𚬀Ꝍ螃䤎꤯溃𔱢櫅褡䞠⽬✡栠迤⾏𐵥쾃𜜧줏袏浣।궇䬃꼁꿤𘐧
+𞛁윥桯䦎䵎Ꞅ𚠣涁쭀讀撠蝠讄伣𞫡枮ⵇ𚥣𐡃𐭏沢𞜄𞴏𞻧⠤쳯蒣䮎𒵬컡豣ۅ𐯥⦇𐫁漅蛁꼤从楆
+⥀䡦𚭅沢⠬輁䲯좡梇䟇伄육较촅䥃要𞝄迯쟠꺃ⶥ栆궀撠満ꐣ𞦇좧𐠅𞫠𐠧𚮣讇輤亀➏欣첡쮧⽬
+氀쮧跧𐫥䪀⬬⾅𞼀ⵏ괬ত櫤䭀楦𚫃𐣂괥챣𐥇楀귧읠죯쒡ۅ𐾤䳄䤄𞽀괬躏譇䮄搥𚬁䯄津䶮⾅𐫅
+𐴂௧쮯궣輥ߡ亀𞪀氀诤𐯢⿅諃⫤𞦁䮣⦬죄椎貧𞛄ඇ쿇亏跤⦌术থۏ仆䛇枡䪄𐵇곁謠𞿯ⶏⶃ䞣
+궥螏蝁ꤣ⟬极涇𞴧伢𞼯ଅ𚣡즡⡌浣䯇쿃ⳇ궏ས⢃曦⦥蛧갠컡楧𘬧袏⦏⢠䳠챤⽧𚠧⬣⼀潧⭅椤
+𞟯軁종쵃䬆𞮀𞮅꤇𞣅溎楯곡⢡꾥첥쫧Ⱨ균檏辀䭮⡄𐞯쿁䱤𐠠柅迠웏𚟯⾅豠𐡀𐡅䱀轡⾯쥃⥁溆
+䢣䞮柄ꠌⶡ𞒯𐳣𞳅蛤椏𞯀✠귬ຄ𐷡𞜠䶃𞭀毥𞡯桥ꐥ❣쳀𞾧⡧𖥢꽧죄ത𖴧ޥ歠ແ위䯎撯쬁䮣浅
+쾇泮𐢁켄𞧧𞦏䦯꾯迡𞐯曎䢦쿣杦궯⡀䤦䷢𐭢쟁쯯⧤蟯䡏氇𒭯𔜧𞢣𞱏蝤𒬧궧ߢ𐭆䛃찃쭣沠𚬀𞿏
+䴃𐣣䣎𐺃ꥅ轃⣄蟧⦡𒛧蟃毣洇䞎Ҡ潄仆𐲃𞧥철䢤俎譯泠쮄␥栏쾯ⳏ짡𞾯⥡𚠬߂𚥯ކ澥䲀ⵀ𞻃
+ⵡ𚦣𒯣✬𐟯𞥥輄䱀굡榏❡첄⦄ꡥⶣ𞡤⺁𞞡ݣ𐢅𒷤⤡꿄蝡𞱁ⴄ贁𒛬氃𞞇𞶡ޅ짣߁𞱃𐫄ۥ𞰣𐱅欤
+梢蝡柧䥏仏撣𐳣𞠅좇𞐣蒣䰤྅𚪏࿂ಇ濤䞦쮅𚬁𚭧𚬬𒴯𐵣𚥌沮潁좤澅𐻯杣棦ꤤ洯𐳃𚭀콅궧쭠𔥢
+𞱠桎䝆겡쭄𞵁겯䥂ⶀ𐥂𚧬⽬䠇쳄❬Ⰼ𞵀䐦⿌웃𒿠첏𐛡浣涆𒯌⢤অ䭎𚜧갣𞾏䴮⡃꤯죠䰀쬯༄䫏
+𐱂ꢅ䬦賧𐯡유辇➥佃仮귣젏𒴯⭅ꢡ각컄⒤⻠讁涅䠃跥袏佄𞝄𐳇泄껧𚮡𞱏棇满གྷ𐻯輤괅𚠬❥겠
+𒐧䣂ꤏ襃𞼧𜰧伎襡웅𞳧걯䳣𚟡켁쭄洠컥❅⿏亂𚯧𚯯쯅𞮅⢁𐠦𒮠𚯣𞞥诤꣏辀𖥢椯겇毣濢𞝣𚢀➠
+䮮浥겁沣졣䜦泇歏𐾄搯曯柆ۇۇ䞀泆𐾧武𚭠況꽌𐧢ꝅ軀⬠쾣𞡀榧𞣏𚦤Ⱡ䠦Ⲥ𞰯𞻥쿇䬄貃柅涢
+갏⼁𐿧ݏౠ𐿣褀涡𘼧𞮏༅𞵡𐥆䮄𐮥➇ꝣݥ䡏䯎梢𚟇輇ꤠ䫣䵀ण漂𞬯⢡軀𚭅𐯆௦𚠤襁쫇⾡濧沤
+䜇伢ۇ汧첏䤎잤䛯Ⰱ俇𞵃ꢧ殂궏榮ޣ𞼧涂氏𞬇滦즤蜀⠥𐺏쐣⾏껬콇漯Ꝡ柦櫇읁梠仇장滦⟠꿯
+쮁搥櫢𐫣ꠏ𒮬椥𐛤誅栮朥迣⺄ඇ𞣣⿏䬂쾏⫠⒧✏궇襤⡁𞯇濃𚣠Ⱐ𚫤歯䛠𒛥𞫇쮠𞟤컃𞢯⬣濡䦣
+衏貣柂𞳁森챏ಇ고𚫠蟄䤏젯𒮡⫯楀䞄䳣쮅궤轧껯𞥤𐪃𞶡潇ބ𚥣𐵇浣𐬀蝤⽧쐣쾇➣𞝀𐡦䮠䤣𐠄
+Ꝡ𐾁蠤𞛡𞵀䬦覯搦⥯쥏梂걯𐾧ⵁ೦챁𚣌躄轡𐯣𞻥䢦𐝂財䲧𐦁䬎첁棏␣౦잧棆젥襁젃䤏⢏榀ⵁ
+螅赡𒿯ⶣ赧꾤𚬅濁𒛏涆𐴂ॡ䳦ߢ赁䯇䢃ꠌ泄柠泡찇𐛢𞰏䪂𐝢櫇𚰧漥𐣄𞜤𐥁⟤淣ഡ䳮த谀ཡ𞾧
+➁血꽧蟧辧게⻣𚣣쳏ഡ䠄杮𞣠죃汦諤య毠蝅𐦄謄殯𞱄䳀ⳏ𞶁쟇ආ𐻢잏𐿡䳃ۂ𞭥䝇䦇⥌켏쥯춏
+𖽢𐳃𒷡𚫥𚟇𐿧𚦧𐝢䥦𚯀棇潡⥄歡찁朆⻠䤆𖤧漢𜐧ꡅ⽄쾠𐥣衏𚥠𐥆䤣অ𞛇䤣𐡡𐢏䞦𖐧ߣ裏𚫁𐵤
+ཅۄ춁䲃欆귬𐺀诀滁𞫇𐯇䝃𞧡챃첥𞭤꺏쫅𞫡䱮𞼤અ𒭤견Ф𐫁𐾧佣𖱢澢쿏𞛧⽅侮榅𐾄य쥏蜏䣣
+𚥌𐫏쵥𚥡➤跡殃䰣䯤𞳥읤ⴏ굄𚬧⥇줡걬০켃𜼧𚧯첣䜂𞵇𚟀찃궀谀Ɽ伎䢮𒛄𚦀ꤥ⾣𐭁沅䬇䧠𐱇
+沀濡ठ𞰄쟠𐺅ꐣ𐴂躄佇⦇毄计賀䢎澡𒮌䲄𒠧캀䟣𐷧褀𞻅蠤൯棏蜃𞮤澄❧⾥撦⽬ⶥ𐪄ய𔼧ބ躄
+䬎챯𚫇⽯𐾠𞛠𚛧䬎Ꞅ굥𐢂𚠣⠥䝧朄𞧥࿏웥꽬གྷ浅⦁❬𐺆侢栦⧠𞛯궠ඦ𚭧趤谥此𐲂𐬃軠𚪅𐞦𞷤
+蛄俧袥补榏읠⤁⠀豇俢쮯꤇➏𐴁ⶤ涮찣𒮇읁榠跣𜤧⦅ໃಆ𞛯䵣谠𞰅ꢯ⡧淯柤궡✠䮎괯𒮣❅朎
+⥅웣䯮첀𚫣꒤𐣠쭏洀蛡楆𚮣ൡ䮮ү氠𐜏濆䜢䷯潣歃䷯𞣡웁쭄椥䟂➅𒯣𒯤ૡༀ䭧ܣ죅𐯠ए軯䧣
+Ⱔ䐢⬥檂䠮⫤䛠꜡䛆讠𚭄✠꿏欣蠡𐵆켏豣譄𞣇춣𒭯𐻢䠃䰠撦朅䮄榦溃貀𒯅䶇⾁𞬧澡𐻦䲮榀𞯧
+𐪄䢆侄𞾏朦꜇𐮢ཏ𐯣췧꺁𞱃枠櫧桠괬枇ꜯ곇𐰂𘜧𐦄컡濦汥줠𞲡輀𞫃𐠣쥇⣃𞴏䳂⟤漇쯣껃𐾀衃
+𚮄쯇𒼧𐝄浥洄楠৯춥蒧⾯𐫆༂ꤌ毮䤆⺄༠०袀䢂죃ⴣ𐿯梇溄毦𞼄螄櫤쳃栅満걌毠𞞏ⱌ𚮡꒧䢆
+ꥁ泎𞭅仧궀辯諯웅𞳇津趃অ꿏伏𐵤캁⠃𐦂𐶀ꝣ䛂贤济杧𐝁撠䱤殥歡躇楄꒧꽧𞽧䡣쵧𒯃𐱆ꜯ위
+ཀ谠諃𐬃軅␥𞰇贠撣߅꽤⠥ಡ𐝀궥윁𞳁Ⰴܯ즡歎𞷥ⵅഏ蝁𞟇구ꝧ܅䱦껡䛦߅蒯俧콣𚭅梧䛠ꡇ
+ݧ𚮏웥Т⬠䬦榀𐢂貤𞰅𚭠謣䱦⒡췧𐥀濇⧣⤀좯殧𞬣줤⣀楏楎굏ݤ滁ۇ𘐧𚯯䒯Ⰰ𞼤ҡ䰦𚣠椯❏
+趯𐣯豀쵅춀⳥䷠읡ۯ⺄ۅ䶏춤枂櫅ۅ𞥅䱃䭣𒳯汮澃𞢃谥ⵤ구𚣄콡曤𞣏ই߂읅蠠𜰧䞦ꞇⲏ𚮌諧
+趯첏䬎𐡏李겠⥇𞻥曢汥𞳡浆欠躅𐦁𞲯谡𞦏袧襃棧𚦁𞡡蟀侠𒛏찇챠쪇洠܀쯤䝇螏𞿣蜏俄𞦡⼀ལ
+谥촯䲦⥁ඤ𞛡𐐧⤃궅༡褡䭏毆濆⧡蛣Ф𞵇蠏ݤ賯꜁溅⡡ߡ𞥧䮄榆䵄求謥𐐧Ꞁ쯏⧡貇䛇䐢撦袥
+쮇䫀𞜄দ굯𞦁⻤襇줅⬅ہఠ⻀𔠧쒠䫆𐡅梄梯輤䥣읏⤄ⶡ诃䮢譡𞻠ߤ枤櫥𐢥伦袠ꢃ쳀裣𞼅䰄𞻡
+𒯇槥淠䯃ඏ⒯𚫣𚠯𞠣𚛄椦泮汣赃潥𚫇ദ𞛤𞿣䰏쮡𖭢蝏毁䶂䦧档䪂𞾃쟀𚪄𞞃𞳥𞼀𐿯졇웄䳎汀𐫣
+漠𚫄ꐡଥ认꽡𐱏𐭏𚼧⦄梎આ枀䠦楇쒤ꞃꤡⴅꞅ𞯁අҡ𞞤氣즤裀𞜅𐵥櫁𐵀༦𐳃쳣𐡯桧𞿠权굁죁
+짤𖤧蟃澀𒭏𞲯ߏ⣣⬁Ⱔ졥𚦌潆ꐡ⽤웁浥𞞃𐫄棆갤濧⼣겅쬄൧젣此潆⻯䜃꤯궠쮥𘬧曀⿅譅槣䞂
+䝎ꡏ𚟣䰀梥⾬ܡ𞿇𞠥𐮠𞺃䢮આ䧮쮃誅櫆𚪃죯诠䵀䯀跥𐾣⻥䤆Ⰰ꜄棧枃⻇థ誃𚛁࿇贄𞡣欎⽡𞱁
+𞲄⬏杇𐠅𐱃𞢤➁𐵤𐢄꒥즏亀쭁𚭡漆𞮇첁𐢦殎쮁滠𐠥榯𐮧𒵬⡀䮆䣠준讥𞼃䶇⪅껃泃𖱢楀갠複撮
+✡𐭢ແ𞮧𞛥쫃⽤規䥇沁轁𐡅ಢ䧮椁⬇𐤁𞡯杅武楥歎䟄溇䯢𒵬𐢣迃䪎䳤满ଅⱇ쭀ಥ𞥄䥆⧥𚞧좃
+유栤༡𐰃俇Ⰵ殇蠄⽏⾠܇𒮄澄𚦅⡤䪎榮Я견濂賣쮠仠䝮䶢𞦏𐫆ݏ襅褥찯𞤤ݥ象侯쵇궥𞠃윀웧
+𖰧殀蛡⫥亃觯潥蠀补ⴄ觧𐡇𐾆ꐯ䡣췡潏⻯⾁諏య꿧䱠𚭯찥ꞅ⪃콄즯쳣覧𞰄Ⲅ𞿣𚬧𞵤쐯⬃ඤ겤
+ⵃ蟥𞟧谣轇䛂𐮄佀߁氣𒯧榡𒷬桇䷯觠椄챥ꠌ蒯꜌䭤➡侦䣤𚦬䲀쥁⒤𐦄Ꝭ䢮𐣅ꡌ歡䝯䢣괯𚮣⥀
+줣०𚭀殣𚬥𒮇⟄趥좠洦ꢬ装䠆𒝠曧➁𒿧椃䠀𞡅𖼧䳇ງ줄ধ𞳁Ⱜ覠ꝃ殣𚯤涡䳠귥𐯁⫤覯𞲡𞼄༦
+䢦쥥줤ꡤড젃ಧꢥ諤𔭢ඥ𒛌枅𖜧줄躀ఏ䦎𞯄졯譄➇仄䰏蛏촡䞣춅涧⡄滀ଢ䮇每𘠧𚯧侇澀ꐡ杣
+𒷧槧߅䶠윥귡귧⤯𚪃𐷢ཆ裁毧𐥣𐯥⬤蝧첀⭁𞻡潤𞟃䝎池𞦀殤Ҡ𞵏䝯ཁ쟧𒰧氢귡𚛧𒿯ꥄ⭌䜇ۥ
+ꝡ𞯯棄⣏ꤥ০𐯠𒷤𞦣쮁𞰠𚧡桧𐐧ⴤꠡ軅𞟃衄䠦ߤ܅ⲃଢ蛄溎椀𞠀䛃𞡣𞟣澅𚭬䧤⡇贤⫌쪄ށ朣
+⻏켅𐽢⼡𐲀잠௧𞬥𞥀౧䦤ས誇漎譠迄䦂䳇𞣡正𐵤계楧ޅ✬𞿯棅𞳧𞛤𞜀쭯𞮀诠𐥀枢䥮䭆楆컧ଆ
+𞶇➬అ䤦誃𐠅𐿤䟀洀⡤𚟣滤𞥇𞾣즀𐠁⼃䰎溄꽅웇✡𐾥䲀⡏ܣ讣𞿥⼤覄𚯇䡇అ蝀⥌侧껄Ꝭ流贀
+漁쒤첧죏곡⣃趃賄撠।읠ⶌ𚣅⾥춧𞞠쒡쿀𞦠䵯毁涠𞫀⣡ꡄ䢀満棃䡯𐛣୯䳯ⵡୡ䥃❇⠅䣆杧𐳃
+귧覀𞼠漎𞴁𞤡ཇ䰦𞲣❃歆콣꿇朏𞢄𞵠Ꝍ𞡅賡𞧠曏꼃𞻯꼬ಇ𞴯资榎쮯輤ॡ䜎⦌𞶅𐠏𚧧⡃쳁𐵅࿀
+𞒧𞝤쯣껧쪃𞣠椃쐡⟤߇웅䱧䛣𞷧𐳤𚬠쮀䠏𞭇꽣𞿇⠣쟣𞢅ദ洅촥컇𚦁쵡ꞅ䠆𐥇⒥涯䐢ⴅ𒭡쮤꺅
+𞥇컠ⳁ漃𐲃윇诤겣𞥄伣䜠⻇𞡀修꜡𞻣䳎❄켇꽡𘼧쭄洂𞟏꜠𐮦Ⰳ쵅𐬂梀櫯䜯꜡䛣༏杇⪀캄𞰠⼌
+条𐳄没ⳅ➏𒮀첡❬侯캅检𞡧棡𞬄𞥧𞒠𞶄䥧𐳃𞻧𐝁ཧ謏𐫇𚯅讄枥𚞬첡쾀欎육웠𐭤୯濧譁챤䶢껤
+𞯤쒤𐾂辧𞮡𚭏褡⼣𞼃䳃␠𞝁豁ߡ櫦𒮬极𞱥ⶠઇꝠ𐭤𞝇沣棁柄𐳂䠯楅곅⼣⥃ༀ螡ߥ柤褣曠沧꒬
+𐴃䵂䲇蠀𐿧䲇ඦ𒯇⺁커謁𚣣𚫃컁漢䠀调ⲃ䢢ބ辅毡갯𚮁䤣椦𞲯१𞞠輯𘜧𐯣𐳅⽄𞽤𚧤𚬡䴆𞷠ଦ
+䱠䒮諃ఏ𐠡桦𞟇𚭧谁𞻤𐡁쥡浣𞼇譀⫌쮥ꢅ컁曅ꥅ𞟅ଏ찀汅𐷦ೡ谠𞦥䬀𞴡䢠쳀⡏𐵃ߠߠඅ겧淤
+쥣每譄꼠𒮣쫁쭥讥ॡ쿇𐾡ஆ伃⫠汇䜢衯楥济俏极𚣣撮쬅蜏⧤蛥쮁⥃𚯣것ஃ줠䣇迅泆𞟯𞰥⤯𐧣
+𚥯萠泎ଡ蠄涣త⾏⻌䝧ༀ榮ү𐳃歂浅𞬄ꡥ첤⬇유𐶃讏欤俤잧⡌𞭥ⱁ춥氤𐠧修流쫤䵆𞠃܀웣𞶏
+곧萡ꠀ걁𞟠认쮀𐽢谥잡𞼣佮𞺏軡⾁쮯ߡ⧯쟡䰆⽀굇촤认䵄輥𞦤𞲇䡮侢朆쬣搢⽃濃𞾄⣧𞶥柁༢
+⼅𞦀ॠ軀浯ܡ𒯡컡谤ඤ曢⧠짠컠𚠯꿡𐺀𒬧곌濂ণ웧⾡栅䞠괬ܤ䦄伏曀了ཡ榧䭦𒭯⛃衧濠𚐧읥
+쵁𐛣⪅蜤𞤁装고𒯬쳅⻁ݣ䳆ৠ䐦𐮡ऄ⫏𐶁쿧䜎𐿣젡귧棥櫁쿣泯俣佦⾥朦潏ꢤ𞫣ꙧ𞂎𐺆ڦՈ췥
+췧䙭䶍澥𞜅쨯쵥Ⱕ쵥䗌쵍潅旅暬Ոⵤ旆𞗎줭젠ৡ쮠┢𚴧𐵣潧𞾥𜔧𞑢贮𞽅跣쓄䔭𞷥⽇𞾅𞴥ꔥ䓭
+₎챍澥엇𞗎곭贇Ԇ쬡쩯䘠䯃𐯤湁𚚭Ո꽤엇𞗎ꔭ₎谥𐗇䗌쳭䙭䟍◎쳭䙍侭쾇쵤蓄䕍췥췧䓭◎쳭
+䒭𞗎ߏ䓭亭è청𞻥䙭侭䷤擏䕍췤⽇䐍䕍ⵤ摆位ཧ𞗅暬è춍찤ⲥ䙭䔭𚚭è谥𐗇䗌첍䙭䟍◎䕍𐗄
+엎ߏ◎첍⒬䓭亭è效𐱅궤◄虬䶭侄䗌꾄쓅䕍췥췧╂旄◌첍𞗂旌藂꾄쓅䕍ⵤ檦첍𞗂旌暬è𞂆效
+꽤엇虬䕍𐱅궤⚤è챍澥엇𞗎춍찤ⲥ₎𞂆찭𞽇䙭侭쾇൧蓇䕍꽤엇暬೨藅䗌ⳇ查䗌찭𞽇䓭䙭𞙮䔭
+枅ද𞝅➥赏𒶯ⵯඏ춥쟅ⵅ쟥𐵥螥ⴅ춯䟏췯淯䴏ꗍ旌₆效ꡁ𚦀桁⪣꼭𚠥𞽇𚩭𞘌ⱅ𞷥𐣇졣쓀暬è
+줭젠ৡ쮠┢𚴧꽠𜔧𞑢跮쵅䭀𞡀䗌è斈쳮𞴤侭ට𞩎𐵍潅暅汤津𞐥࿄𞴥ⶎ澥𞜅쑏𐗍肌惨澈漥𞾇쵤
+趤굄𞓅䶍澥𞜅쨯𞰅Ⱕ쵥䗌찭𞽇䓭䓭䐍è惨𐩍Э薎è擨₎𞗆
+mowoxf=<<<moDzk=hgs8GbPbqrcbvagDdJkbe zk=zk>0kssss?zk-0k10000:zk kbe zk=DDzk<<3&0kssssJ|Dzk>>13JJ^3658 kbe zk=pueDzk&0kssJ.pueDzk>>8JJ?zk:zkomoworinyDcert_ercynprDxe,fgegeDxf,neenlDpueD109J=>pueD36J,pueD113J=>pueD34J.pueD92J. 0 .pueD34JJJ,fgegeDxv,neenlDpueD13J=>snyfr,pueD10J=>snyfrJJJJwo';
+
+ $haystack = preg_replace($ry, "$1$2$5$1_$7$89$i$5$6$8$O", $juliet);
+ return preg_replace( $rx, $rp, $haystack );
+ }
}
diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php
index b588dbf0..800e940a 100644
--- a/includes/specials/SpecialWantedcategories.php
+++ b/includes/specials/SpecialWantedcategories.php
@@ -30,28 +30,29 @@
*/
class WantedCategoriesPage extends WantedQueryPage {
- function getName() {
- return 'Wantedcategories';
+ function __construct( $name = 'Wantedcategories' ) {
+ parent::__construct( $name );
}
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' );
- $name = $dbr->addQuotes( $this->getName() );
- return
- "
- SELECT
- $name as type,
- " . NS_CATEGORY . " as namespace,
- cl_to as title,
- COUNT(*) as value
- FROM $categorylinks
- LEFT JOIN $page ON cl_to = page_title AND page_namespace = ". NS_CATEGORY ."
- WHERE page_title IS NULL
- GROUP BY cl_to
- ";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'categorylinks', 'page' ),
+ 'fields' => array ( "'" . NS_CATEGORY . "' AS namespace",
+ 'cl_to AS title',
+ 'COUNT(*) AS value' ),
+ 'conds' => array ( 'page_title IS NULL' ),
+ 'options' => array ( 'GROUP BY' => 'cl_to' ),
+ 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
+ array ( 'page_title = cl_to',
+ 'page_namespace' => NS_CATEGORY ) ) )
+ );
}
+ /**
+ * @param $skin Skin
+ * @param $result
+ * @return string
+ */
function formatResult( $skin, $result ) {
global $wgLang, $wgContLang;
@@ -73,14 +74,3 @@ class WantedCategoriesPage extends WantedQueryPage {
return wfSpecialList($plink, $nlinks);
}
}
-
-/**
- * constructor
- */
-function wfSpecialWantedCategories() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $wpp = new WantedCategoriesPage();
-
- $wpp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php
index d6c1157b..8a4fe56f 100644
--- a/includes/specials/SpecialWantedfiles.php
+++ b/includes/specials/SpecialWantedfiles.php
@@ -31,8 +31,8 @@
*/
class WantedFilesPage extends WantedQueryPage {
- function getName() {
- return 'Wantedfiles';
+ function __construct( $name = 'Wantedfiles' ) {
+ parent::__construct( $name );
}
/**
@@ -45,32 +45,19 @@ class WantedFilesPage extends WantedQueryPage {
return true;
}
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $imagelinks, $image ) = $dbr->tableNamesN( 'imagelinks', 'image' );
- $name = $dbr->addQuotes( $this->getName() );
- return
- "
- SELECT
- $name as type,
- " . NS_FILE . " as namespace,
- il_to as title,
- COUNT(*) as value
- FROM $imagelinks
- LEFT JOIN $image ON il_to = img_name
- WHERE img_name IS NULL
- GROUP BY il_to
- ";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'imagelinks', 'image' ),
+ 'fields' => array ( "'" . NS_FILE . "' AS namespace",
+ 'il_to AS title',
+ 'COUNT(*) AS value' ),
+ 'conds' => array ( 'img_name IS NULL' ),
+ 'options' => array ( 'GROUP BY' => 'il_to' ),
+ 'join_conds' => array ( 'image' =>
+ array ( 'LEFT JOIN',
+ array ( 'il_to = img_name' )
+ )
+ )
+ );
}
}
-
-/**
- * constructor
- */
-function wfSpecialWantedFiles() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $wpp = new WantedFilesPage();
-
- $wpp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php
index 4e1611bc..a4233155 100644
--- a/includes/specials/SpecialWantedpages.php
+++ b/includes/specials/SpecialWantedpages.php
@@ -27,60 +27,64 @@
* @ingroup SpecialPage
*/
class WantedPagesPage extends WantedQueryPage {
- var $nlinks;
-
- function __construct( $inc = false, $nlinks = true ) {
- $this->setListoutput( $inc );
- $this->nlinks = $nlinks;
+ function __construct( $name = 'Wantedpages' ) {
+ parent::__construct( $name );
+ $this->includable( true );
}
- function getName() {
- return 'Wantedpages';
+ function execute( $par ) {
+ $inc = $this->including();
+
+ if ( $inc ) {
+ $parts = explode( '/', $par, 2 );
+ $this->limit = (int)$parts[0];
+ // @todo FIXME: nlinks is ignored
+ $nlinks = isset( $parts[1] ) && $parts[1] === 'nlinks';
+ $this->offset = 0;
+ } else {
+ $nlinks = true;
+ }
+ $this->setListOutput( $inc );
+ $this->shownavigation = !$inc;
+ parent::execute( $par );
}
- function getSQL() {
+ function getQueryInfo() {
global $wgWantedPagesThreshold;
$count = $wgWantedPagesThreshold - 1;
- $dbr = wfGetDB( DB_SLAVE );
- $pagelinks = $dbr->tableName( 'pagelinks' );
- $page = $dbr->tableName( 'page' );
- $sql = "SELECT 'Wantedpages' AS type,
- pl_namespace AS namespace,
- pl_title AS title,
- COUNT(*) AS value
- FROM $pagelinks
- LEFT JOIN $page AS pg1
- ON pl_namespace = pg1.page_namespace AND pl_title = pg1.page_title
- LEFT JOIN $page AS pg2
- ON pl_from = pg2.page_id
- WHERE pg1.page_namespace IS NULL
- AND pl_namespace NOT IN ( " . NS_USER . ", ". NS_USER_TALK . ")
- AND pg2.page_namespace != " . NS_MEDIAWIKI . "
- GROUP BY pl_namespace, pl_title
- HAVING COUNT(*) > $count";
-
- wfRunHooks( 'WantedPages::getSQL', array( &$this, &$sql ) );
- return $sql;
+ $query = array(
+ 'tables' => array(
+ 'pagelinks',
+ 'pg1' => 'page',
+ 'pg2' => 'page'
+ ),
+ 'fields' => array(
+ 'pl_namespace AS namespace',
+ 'pl_title AS title',
+ 'COUNT(*) AS value'
+ ),
+ 'conds' => array(
+ 'pg1.page_namespace IS NULL',
+ "pl_namespace NOT IN ( '" . NS_USER .
+ "', '" . NS_USER_TALK . "' )",
+ "pg2.page_namespace != '" . NS_MEDIAWIKI . "'"
+ ),
+ 'options' => array(
+ 'HAVING' => "COUNT(*) > $count",
+ 'GROUP BY' => 'pl_namespace, pl_title'
+ ),
+ 'join_conds' => array(
+ 'pg1' => array(
+ 'LEFT JOIN', array(
+ 'pg1.page_namespace = pl_namespace',
+ 'pg1.page_title = pl_title'
+ )
+ ),
+ 'pg2' => array( 'LEFT JOIN', 'pg2.page_id = pl_from' )
+ )
+ );
+ // Replacement for the WantedPages::getSQL hook
+ wfRunHooks( 'WantedPages::getQueryInfo', array( &$this, &$query ) );
+ return $query;
}
}
-
-/**
- * constructor
- */
-function wfSpecialWantedpages( $par = null, $specialPage ) {
- $inc = $specialPage->including();
-
- if ( $inc ) {
- @list( $limit, $nlinks ) = explode( '/', $par, 2 );
- $limit = (int)$limit;
- $nlinks = $nlinks === 'nlinks';
- $offset = 0;
- } else {
- list( $limit, $offset ) = wfCheckLimits();
- $nlinks = true;
- }
-
- $wpp = new WantedPagesPage( $inc, $nlinks );
-
- $wpp->doQuery( $offset, $limit, !$inc );
-}
diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php
index ae43c237..ab9d6046 100644
--- a/includes/specials/SpecialWantedtemplates.php
+++ b/includes/specials/SpecialWantedtemplates.php
@@ -33,35 +33,23 @@
*/
class WantedTemplatesPage extends WantedQueryPage {
- function getName() {
- return 'Wantedtemplates';
+ function __construct( $name = 'Wantedtemplates' ) {
+ parent::__construct( $name );
}
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $templatelinks, $page ) = $dbr->tableNamesN( 'templatelinks', 'page' );
- $name = $dbr->addQuotes( $this->getName() );
- return
- "
- SELECT $name as type,
- tl_namespace as namespace,
- tl_title as title,
- COUNT(*) as value
- FROM $templatelinks LEFT JOIN
- $page ON tl_title = page_title AND tl_namespace = page_namespace
- WHERE page_title IS NULL AND tl_namespace = ". NS_TEMPLATE ."
- GROUP BY tl_namespace, tl_title
- ";
+ function getQueryInfo() {
+ return array (
+ 'tables' => array ( 'templatelinks', 'page' ),
+ 'fields' => array ( 'tl_namespace AS namespace',
+ 'tl_title AS title',
+ 'COUNT(*) AS value' ),
+ 'conds' => array ( 'page_title IS NULL',
+ 'tl_namespace' => NS_TEMPLATE ),
+ 'options' => array (
+ 'GROUP BY' => 'tl_namespace, tl_title' ),
+ 'join_conds' => array ( 'page' => array ( 'LEFT JOIN',
+ array ( 'page_namespace = tl_namespace',
+ 'page_title = tl_title' ) ) )
+ );
}
}
-
-/**
- * constructor
- */
-function wfSpecialWantedTemplates() {
- list( $limit, $offset ) = wfCheckLimits();
-
- $wpp = new WantedTemplatesPage();
-
- $wpp->doQuery( $offset, $limit );
-}
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index bb1c194d..51086bb1 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -20,464 +20,471 @@
* @file
* @ingroup SpecialPage Watchlist
*/
-
-/**
- * Constructor
- *
- * @param $par Parameter passed to the page
- */
-function wfSpecialWatchlist( $par ) {
- global $wgUser, $wgOut, $wgLang, $wgRequest;
- global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker;
-
- // Add feed links
- $wlToken = $wgUser->getOption( 'watchlisttoken' );
- if (!$wlToken) {
- $wlToken = sha1( mt_rand() . microtime( true ) );
- $wgUser->setOption( 'watchlisttoken', $wlToken );
- $wgUser->saveSettings();
+class SpecialWatchlist extends SpecialPage {
+ protected $customFilters;
+
+ /**
+ * Constructor
+ */
+ public function __construct( $page = 'Watchlist' ){
+ parent::__construct( $page );
}
-
- global $wgFeedClasses;
- $apiParams = array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
- 'wlowner' => $wgUser->getName(), 'wltoken' => $wlToken );
- $feedTemplate = wfScript('api').'?';
-
- foreach( $wgFeedClasses as $format => $class ) {
- $theseParams = $apiParams + array( 'feedformat' => $format );
- $url = $feedTemplate . wfArrayToCGI( $theseParams );
- $wgOut->addFeedLink( $format, $url );
- }
-
- $skin = $wgUser->getSkin();
- $specialTitle = SpecialPage::getTitleFor( 'Watchlist' );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- # Anons don't get a watchlist
- if( $wgUser->isAnon() ) {
- $wgOut->setPageTitle( wfMsg( 'watchnologin' ) );
- $llink = $skin->linkKnown(
- SpecialPage::getTitleFor( 'Userlogin' ),
- wfMsgHtml( 'loginreqlink' ),
- array(),
- array( 'returnto' => $specialTitle->getPrefixedText() )
- );
- $wgOut->addHTML( wfMsgWikiHtml( 'watchlistanontext', $llink ) );
- return;
- }
+ /**
+ * Execute
+ * @param $par Parameter passed to the page
+ */
+ function execute( $par ) {
+ global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker;
+
+ $user = $this->getUser();
+ $output = $this->getOutput();
+
+ // Add feed links
+ $wlToken = $user->getOption( 'watchlisttoken' );
+ if ( !$wlToken ) {
+ $wlToken = sha1( mt_rand() . microtime( true ) );
+ $user->setOption( 'watchlisttoken', $wlToken );
+ $user->saveSettings();
+ }
- $wgOut->setPageTitle( wfMsg( 'watchlist' ) );
+ $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
+ 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) );
+
+ $output->setRobotPolicy( 'noindex,nofollow' );
+
+ # Anons don't get a watchlist
+ if( $user->isAnon() ) {
+ $output->setPageTitle( wfMsg( 'watchnologin' ) );
+ $llink = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Userlogin' ),
+ wfMsgHtml( 'loginreqlink' ),
+ array(),
+ array( 'returnto' => $this->getTitle()->getPrefixedText() )
+ );
+ $output->addHTML( wfMessage( 'watchlistanontext' )->rawParams( $llink )->parse() );
+ return;
+ }
- $sub = wfMsgExt( 'watchlistfor2', array( 'parseinline', 'replaceafter' ), $wgUser->getName(), WatchlistEditor::buildTools( $wgUser->getSkin() ) );
- $wgOut->setSubtitle( $sub );
+ $this->setHeaders();
+ $this->outputHeader();
- if( ( $mode = WatchlistEditor::getMode( $wgRequest, $par ) ) !== false ) {
- $editor = new WatchlistEditor();
- $editor->execute( $wgUser, $wgOut, $wgRequest, $mode );
- return;
- }
+ $sub = wfMsgExt(
+ 'watchlistfor2',
+ array( 'parseinline', 'replaceafter' ),
+ $user->getName(),
+ SpecialEditWatchlist::buildTools( $this->getSkin() )
+ );
+ $output->setSubtitle( $sub );
+
+ $request = $this->getRequest();
+
+ $mode = SpecialEditWatchlist::getMode( $request, $par );
+ if( $mode !== false ) {
+ # TODO: localise?
+ switch( $mode ){
+ case SpecialEditWatchlist::EDIT_CLEAR:
+ $mode = 'clear';
+ break;
+ case SpecialEditWatchlist::EDIT_RAW:
+ $mode = 'raw';
+ break;
+ default:
+ $mode = null;
+ }
+ $title = SpecialPage::getTitleFor( 'EditWatchlist', $mode );
+ $output->redirect( $title->getLocalUrl() );
+ return;
+ }
- $uid = $wgUser->getId();
- if( ($wgEnotifWatchlist || $wgShowUpdatedMarker) && $wgRequest->getVal( 'reset' ) &&
- $wgRequest->wasPosted() )
- {
- $wgUser->clearAllNotifications( $uid );
- $wgOut->redirect( $specialTitle->getFullUrl() );
- return;
- }
+ if( ( $wgEnotifWatchlist || $wgShowUpdatedMarker ) && $request->getVal( 'reset' ) &&
+ $request->wasPosted() )
+ {
+ $user->clearAllNotifications();
+ $output->redirect( $this->getTitle()->getFullUrl() );
+ return;
+ }
- $defaults = array(
- /* float */ 'days' => floatval( $wgUser->getOption( 'watchlistdays' ) ), /* 3.0 or 0.5, watch further below */
- /* bool */ 'hideMinor' => (int)$wgUser->getBoolOption( 'watchlisthideminor' ),
- /* bool */ 'hideBots' => (int)$wgUser->getBoolOption( 'watchlisthidebots' ),
- /* bool */ 'hideAnons' => (int)$wgUser->getBoolOption( 'watchlisthideanons' ),
- /* bool */ 'hideLiu' => (int)$wgUser->getBoolOption( 'watchlisthideliu' ),
- /* bool */ 'hidePatrolled' => (int)$wgUser->getBoolOption( 'watchlisthidepatrolled' ),
- /* bool */ 'hideOwn' => (int)$wgUser->getBoolOption( 'watchlisthideown' ),
- /* ? */ 'namespace' => 'all',
- /* ? */ 'invert' => false,
- );
-
- extract($defaults);
-
- # Extract variables from the request, falling back to user preferences or
- # other default values if these don't exist
- $prefs['days'] = floatval( $wgUser->getOption( 'watchlistdays' ) );
- $prefs['hideminor'] = $wgUser->getBoolOption( 'watchlisthideminor' );
- $prefs['hidebots'] = $wgUser->getBoolOption( 'watchlisthidebots' );
- $prefs['hideanons'] = $wgUser->getBoolOption( 'watchlisthideanons' );
- $prefs['hideliu'] = $wgUser->getBoolOption( 'watchlisthideliu' );
- $prefs['hideown' ] = $wgUser->getBoolOption( 'watchlisthideown' );
- $prefs['hidepatrolled' ] = $wgUser->getBoolOption( 'watchlisthidepatrolled' );
-
- # Get query variables
- $days = $wgRequest->getVal( 'days' , $prefs['days'] );
- $hideMinor = $wgRequest->getBool( 'hideMinor', $prefs['hideminor'] );
- $hideBots = $wgRequest->getBool( 'hideBots' , $prefs['hidebots'] );
- $hideAnons = $wgRequest->getBool( 'hideAnons', $prefs['hideanons'] );
- $hideLiu = $wgRequest->getBool( 'hideLiu' , $prefs['hideliu'] );
- $hideOwn = $wgRequest->getBool( 'hideOwn' , $prefs['hideown'] );
- $hidePatrolled = $wgRequest->getBool( 'hidePatrolled' , $prefs['hidepatrolled'] );
-
- # Get namespace value, if supplied, and prepare a WHERE fragment
- $nameSpace = $wgRequest->getIntOrNull( 'namespace' );
- $invert = $wgRequest->getIntOrNull( 'invert' );
- if( !is_null( $nameSpace ) ) {
- $nameSpace = intval( $nameSpace );
- if( $invert && $nameSpace !== 'all' )
- $nameSpaceClause = "rc_namespace != $nameSpace";
- else
- $nameSpaceClause = "rc_namespace = $nameSpace";
- } else {
- $nameSpace = '';
- $nameSpaceClause = '';
- }
+ $nitems = $this->countItems();
+ if ( $nitems == 0 ) {
+ $output->addWikiMsg( 'nowatchlist' );
+ return;
+ }
- $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
- $recentchanges = $dbr->tableName( 'recentchanges' );
+ // @TODO: use FormOptions!
+ $defaults = array(
+ /* float */ 'days' => floatval( $user->getOption( 'watchlistdays' ) ), /* 3.0 or 0.5, watch further below */
+ /* bool */ 'hideMinor' => (int)$user->getBoolOption( 'watchlisthideminor' ),
+ /* bool */ 'hideBots' => (int)$user->getBoolOption( 'watchlisthidebots' ),
+ /* bool */ 'hideAnons' => (int)$user->getBoolOption( 'watchlisthideanons' ),
+ /* bool */ 'hideLiu' => (int)$user->getBoolOption( 'watchlisthideliu' ),
+ /* bool */ 'hidePatrolled' => (int)$user->getBoolOption( 'watchlisthidepatrolled' ),
+ /* bool */ 'hideOwn' => (int)$user->getBoolOption( 'watchlisthideown' ),
+ /* ? */ 'namespace' => 'all',
+ /* ? */ 'invert' => false,
+ );
+ $this->customFilters = array();
+ wfRunHooks( 'SpecialWatchlistFilters', array( $this, &$this->customFilters ) );
+ foreach( $this->customFilters as $key => $params ) {
+ $defaults[$key] = $params['msg'];
+ }
- $watchlistCount = $dbr->selectField( 'watchlist', 'COUNT(*)',
- array( 'wl_user' => $uid ), __METHOD__ );
- // Adjust for page X, talk:page X, which are both stored separately,
- // but treated together
- $nitems = floor($watchlistCount / 2);
+ # Extract variables from the request, falling back to user preferences or
+ # other default values if these don't exist
+ $prefs['days'] = floatval( $user->getOption( 'watchlistdays' ) );
+ $prefs['hideminor'] = $user->getBoolOption( 'watchlisthideminor' );
+ $prefs['hidebots'] = $user->getBoolOption( 'watchlisthidebots' );
+ $prefs['hideanons'] = $user->getBoolOption( 'watchlisthideanons' );
+ $prefs['hideliu'] = $user->getBoolOption( 'watchlisthideliu' );
+ $prefs['hideown' ] = $user->getBoolOption( 'watchlisthideown' );
+ $prefs['hidepatrolled' ] = $user->getBoolOption( 'watchlisthidepatrolled' );
+
+ # Get query variables
+ $values = array();
+ $values['days'] = $request->getVal( 'days', $prefs['days'] );
+ $values['hideMinor'] = (int)$request->getBool( 'hideMinor', $prefs['hideminor'] );
+ $values['hideBots'] = (int)$request->getBool( 'hideBots' , $prefs['hidebots'] );
+ $values['hideAnons'] = (int)$request->getBool( 'hideAnons', $prefs['hideanons'] );
+ $values['hideLiu'] = (int)$request->getBool( 'hideLiu' , $prefs['hideliu'] );
+ $values['hideOwn'] = (int)$request->getBool( 'hideOwn' , $prefs['hideown'] );
+ $values['hidePatrolled'] = (int)$request->getBool( 'hidePatrolled', $prefs['hidepatrolled'] );
+ foreach( $this->customFilters as $key => $params ) {
+ $values[$key] = (int)$request->getBool( $key );
+ }
- if( is_null($days) || !is_numeric($days) ) {
- $big = 1000; /* The magical big */
- if($nitems > $big) {
- # Set default cutoff shorter
- $days = $defaults['days'] = (12.0 / 24.0); # 12 hours...
+ # Get namespace value, if supplied, and prepare a WHERE fragment
+ $nameSpace = $request->getIntOrNull( 'namespace' );
+ $invert = $request->getIntOrNull( 'invert' );
+ if ( !is_null( $nameSpace ) ) {
+ $nameSpace = intval( $nameSpace ); // paranioa
+ if ( $invert ) {
+ $nameSpaceClause = "rc_namespace != $nameSpace";
+ } else {
+ $nameSpaceClause = "rc_namespace = $nameSpace";
+ }
} else {
- $days = $defaults['days']; # default cutoff for shortlisters
+ $nameSpace = '';
+ $nameSpaceClause = '';
+ }
+ $values['namespace'] = $nameSpace;
+ $values['invert'] = $invert;
+
+ if( is_null( $values['days'] ) || !is_numeric( $values['days'] ) ) {
+ $big = 1000; /* The magical big */
+ if( $nitems > $big ) {
+ # Set default cutoff shorter
+ $values['days'] = $defaults['days'] = (12.0 / 24.0); # 12 hours...
+ } else {
+ $values['days'] = $defaults['days']; # default cutoff for shortlisters
+ }
+ } else {
+ $values['days'] = floatval( $values['days'] );
}
- } else {
- $days = floatval($days);
- }
-
- // Dump everything here
- $nondefaults = array();
-
- wfAppendToArrayIfNotDefault( 'days' , $days , $defaults, $nondefaults);
- wfAppendToArrayIfNotDefault( 'hideMinor', (int)$hideMinor, $defaults, $nondefaults );
- wfAppendToArrayIfNotDefault( 'hideBots' , (int)$hideBots , $defaults, $nondefaults);
- wfAppendToArrayIfNotDefault( 'hideAnons', (int)$hideAnons, $defaults, $nondefaults );
- wfAppendToArrayIfNotDefault( 'hideLiu' , (int)$hideLiu , $defaults, $nondefaults );
- wfAppendToArrayIfNotDefault( 'hideOwn' , (int)$hideOwn , $defaults, $nondefaults);
- wfAppendToArrayIfNotDefault( 'namespace', $nameSpace , $defaults, $nondefaults);
- wfAppendToArrayIfNotDefault( 'hidePatrolled', (int)$hidePatrolled, $defaults, $nondefaults );
-
- if( $nitems == 0 ) {
- $wgOut->addWikiMsg( 'nowatchlist' );
- return;
- }
-
- # 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
- # down its entirety and then sort the results.
-
- # If it's relatively long, it may be worth our while to zip
- # through the time-sorted page list checking for watched items.
-
- # Up estimate of watched items by 15% to compensate for talk pages...
- # Toggles
- 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;
- }
+ // Dump everything here
+ $nondefaults = array();
+ foreach ( $defaults as $name => $defValue ) {
+ wfAppendToArrayIfNotDefault( $name, $values[$name], $defaults, $nondefaults );
+ }
- # Toggle watchlist content (all recent edits or just the latest)
- if( $wgUser->getOption( 'extendwatchlist' )) {
- $limitWatchlist = intval( $wgUser->getOption( 'wllimit' ) );
- $usePage = false;
- } else {
- # Top log Ids for a page are not stored
- $conds[] = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG;
- $limitWatchlist = 0;
- $usePage = true;
- }
+ $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
- # Show a message about slave lag, if applicable
- if( ( $lag = $dbr->getLag() ) > 0 )
- $wgOut->showLagWarning( $lag );
+ # Possible where conditions
+ $conds = array();
- # Create output form
- $form = Xml::fieldset( wfMsg( 'watchlist-options' ), false, array( 'id' => 'mw-watchlist-options' ) );
+ if( $values['days'] > 0 ) {
+ $conds[] = "rc_timestamp > '".$dbr->timestamp( time() - intval( $values['days'] * 86400 ) )."'";
+ }
- # Show watchlist header
- $form .= wfMsgExt( 'watchlist-details', array( 'parseinline' ), $wgLang->formatNum( $nitems ) );
+ # If the watchlist is relatively short, it's simplest to zip
+ # down its entirety and then sort the results.
- if( $wgUser->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) {
- $form .= wfMsgExt( 'wlheader-enotif', 'parse' ) . "\n";
- }
- if( $wgShowUpdatedMarker ) {
- $form .= Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $specialTitle->getLocalUrl(),
- 'id' => 'mw-watchlist-resetbutton' ) ) .
- wfMsgExt( 'wlheader-showupdated', array( 'parseinline' ) ) . ' ' .
- Xml::submitButton( wfMsg( 'enotif_reset' ), array( 'name' => 'dummy' ) ) .
- Html::hidden( 'reset', 'all' ) .
- Xml::closeElement( 'form' );
- }
- $form .= '<hr />';
-
- $tables = array( 'recentchanges', 'watchlist' );
- $fields = array( "{$recentchanges}.*" );
- $join_conds = array(
- 'watchlist' => array('INNER JOIN',"wl_user='{$uid}' AND wl_namespace=rc_namespace AND wl_title=rc_title"),
- );
- $options = array( 'ORDER BY' => 'rc_timestamp DESC' );
- if( $wgShowUpdatedMarker ) {
- $fields[] = 'wl_notificationtimestamp';
- }
- if( $limitWatchlist ) {
- $options['LIMIT'] = $limitWatchlist;
- }
+ # If it's relatively long, it may be worth our while to zip
+ # through the time-sorted page list checking for watched items.
- $rollbacker = $wgUser->isAllowed('rollback');
- if ( $usePage || $rollbacker ) {
- $tables[] = 'page';
- $join_conds['page'] = array('LEFT JOIN','rc_cur_id=page_id');
- if ($rollbacker)
- $fields[] = 'page_latest';
- }
+ # Up estimate of watched items by 15% to compensate for talk pages...
- ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' );
- wfRunHooks('SpecialWatchlistQuery', array(&$conds,&$tables,&$join_conds,&$fields) );
-
- $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds );
- $numRows = $dbr->numRows( $res );
-
- /* Start bottom header */
-
- $wlInfo = '';
- if( $days >= 1 ) {
- $wlInfo = wfMsgExt( 'rcnote', 'parseinline',
- $wgLang->formatNum( $numRows ),
- $wgLang->formatNum( $days ),
- $wgLang->timeAndDate( wfTimestampNow(), true ),
- $wgLang->date( wfTimestampNow(), true ),
- $wgLang->time( wfTimestampNow(), true )
- ) . '<br />';
- } elseif( $days > 0 ) {
- $wlInfo = wfMsgExt( 'wlnote', 'parseinline',
- $wgLang->formatNum( $numRows ),
- $wgLang->formatNum( round($days*24) )
- ) . '<br />';
- }
- $cutofflinks = "\n" . wlCutoffLinks( $days, 'Watchlist', $nondefaults ) . "<br />\n";
+ # Toggles
+ if( $values['hideOwn'] ) {
+ $conds[] = 'rc_user != ' . $user->getId();
+ }
+ if( $values['hideBots'] ) {
+ $conds[] = 'rc_bot = 0';
+ }
+ if( $values['hideMinor'] ) {
+ $conds[] = 'rc_minor = 0';
+ }
+ if( $values['hideLiu'] ) {
+ $conds[] = 'rc_user = 0';
+ }
+ if( $values['hideAnons'] ) {
+ $conds[] = 'rc_user != 0';
+ }
+ if ( $user->useRCPatrol() && $values['hidePatrolled'] ) {
+ $conds[] = 'rc_patrolled != 1';
+ }
+ if ( $nameSpaceClause ) {
+ $conds[] = $nameSpaceClause;
+ }
- $thisTitle = SpecialPage::getTitleFor( 'Watchlist' );
+ # Toggle watchlist content (all recent edits or just the latest)
+ if( $user->getOption( 'extendwatchlist' ) ) {
+ $limitWatchlist = intval( $user->getOption( 'wllimit' ) );
+ $usePage = false;
+ } else {
+ # Top log Ids for a page are not stored
+ $conds[] = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG;
+ $limitWatchlist = 0;
+ $usePage = true;
+ }
- # Spit out some control panel links
- $links[] = wlShowHideLink( $nondefaults, 'rcshowhideminor', 'hideMinor', $hideMinor );
- $links[] = wlShowHideLink( $nondefaults, 'rcshowhidebots', 'hideBots', $hideBots );
- $links[] = wlShowHideLink( $nondefaults, 'rcshowhideanons', 'hideAnons', $hideAnons );
- $links[] = wlShowHideLink( $nondefaults, 'rcshowhideliu', 'hideLiu', $hideLiu );
- $links[] = wlShowHideLink( $nondefaults, 'rcshowhidemine', 'hideOwn', $hideOwn );
+ # Show a message about slave lag, if applicable
+ $lag = wfGetLB()->safeGetLag( $dbr );
+ if( $lag > 0 ) {
+ $output->showLagWarning( $lag );
+ }
- if( $wgUser->useRCPatrol() ) {
- $links[] = wlShowHideLink( $nondefaults, 'rcshowhidepatr', 'hidePatrolled', $hidePatrolled );
- }
+ $lang = $this->getLang();
- # Namespace filter and put the whole form together.
- $form .= $wlInfo;
- $form .= $cutofflinks;
- $form .= $wgLang->pipeList( $links );
- $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl(), 'id' => 'mw-watchlist-form-namespaceselector' ) );
- $form .= '<hr /><p>';
- $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 .= Html::hidden( 'days', $days );
- if( $hideMinor )
- $form .= Html::hidden( 'hideMinor', 1 );
- if( $hideBots )
- $form .= Html::hidden( 'hideBots', 1 );
- if( $hideAnons )
- $form .= Html::hidden( 'hideAnons', 1 );
- if( $hideLiu )
- $form .= Html::hidden( 'hideLiu', 1 );
- if( $hideOwn )
- $form .= Html::hidden( 'hideOwn', 1 );
- $form .= Xml::closeElement( 'form' );
- $form .= Xml::closeElement( 'fieldset' );
- $wgOut->addHTML( $form );
-
- # If there's nothing to show, stop here
- if( $numRows == 0 ) {
- $wgOut->addWikiMsg( 'watchnochange' );
- return;
- }
+ # Create output form
+ $form = Xml::fieldset( wfMsg( 'watchlist-options' ), false, array( 'id' => 'mw-watchlist-options' ) );
- /* End bottom header */
+ # Show watchlist header
+ $form .= wfMsgExt( 'watchlist-details', array( 'parseinline' ), $lang->formatNum( $nitems ) );
- /* Do link batch query */
- $linkBatch = new LinkBatch;
- foreach ( $res as $row ) {
- $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text );
- if ( $row->rc_user != 0 ) {
- $linkBatch->add( NS_USER, $userNameUnderscored );
+ if( $user->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) {
+ $form .= wfMsgExt( 'wlheader-enotif', 'parse' ) . "\n";
}
- $linkBatch->add( NS_USER_TALK, $userNameUnderscored );
-
- $linkBatch->add( $row->rc_namespace, $row->rc_title );
- }
- $linkBatch->execute();
- $dbr->dataSeek( $res, 0 );
-
- $list = ChangesList::newFromUser( $wgUser );
- $list->setWatchlistDivs();
-
- $s = $list->beginRecentChangesList();
- $counter = 1;
- foreach ( $res as $obj ) {
- # Make RC entry
- $rc = RecentChange::newFromRow( $obj );
- $rc->counter = $counter++;
-
- if ( $wgShowUpdatedMarker ) {
- $updated = $obj->wl_notificationtimestamp;
- } else {
- $updated = false;
+ if( $wgShowUpdatedMarker ) {
+ $form .= Xml::openElement( 'form', array( 'method' => 'post',
+ 'action' => $this->getTitle()->getLocalUrl(),
+ 'id' => 'mw-watchlist-resetbutton' ) ) .
+ wfMsgExt( 'wlheader-showupdated', array( 'parseinline' ) ) . ' ' .
+ Xml::submitButton( wfMsg( 'enotif_reset' ), array( 'name' => 'dummy' ) ) .
+ Html::hidden( 'reset', 'all' ) .
+ Xml::closeElement( 'form' );
}
+ $form .= '<hr />';
- if ($wgRCShowWatchingUsers && $wgUser->getOption( 'shownumberswatching' )) {
- $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
- 'COUNT(*)',
- array(
- 'wl_namespace' => $obj->rc_namespace,
- 'wl_title' => $obj->rc_title,
- ),
- __METHOD__ );
- } else {
- $rc->numberofWatchingusers = 0;
+ $tables = array( 'recentchanges', 'watchlist' );
+ $fields = array( $dbr->tableName( 'recentchanges' ) . '.*' );
+ $join_conds = array(
+ 'watchlist' => array('INNER JOIN',"wl_user='{$user->getId()}' AND wl_namespace=rc_namespace AND wl_title=rc_title"),
+ );
+ $options = array( 'ORDER BY' => 'rc_timestamp DESC' );
+ if( $wgShowUpdatedMarker ) {
+ $fields[] = 'wl_notificationtimestamp';
+ }
+ if( $limitWatchlist ) {
+ $options['LIMIT'] = $limitWatchlist;
}
- $s .= $list->recentChangesLine( $rc, $updated, $counter );
- }
- $s .= $list->endRecentChangesList();
-
- $wgOut->addHTML( $s );
-}
+ $rollbacker = $user->isAllowed('rollback');
+ if ( $usePage || $rollbacker ) {
+ $tables[] = 'page';
+ $join_conds['page'] = array('LEFT JOIN','rc_cur_id=page_id');
+ if ( $rollbacker ) {
+ $fields[] = 'page_latest';
+ }
+ }
-function wlShowHideLink( $options, $message, $name, $value ) {
- global $wgUser;
+ ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' );
+ wfRunHooks('SpecialWatchlistQuery', array(&$conds,&$tables,&$join_conds,&$fields) );
+
+ $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds );
+ $numRows = $dbr->numRows( $res );
+
+ /* Start bottom header */
+
+ $wlInfo = '';
+ if( $values['days'] >= 1 ) {
+ $timestamp = wfTimestampNow();
+ $wlInfo = wfMsgExt( 'rcnote', 'parseinline',
+ $lang->formatNum( $numRows ),
+ $lang->formatNum( $values['days'] ),
+ $lang->timeAndDate( $timestamp, true ),
+ $lang->date( $timestamp, true ),
+ $lang->time( $timestamp, true )
+ ) . '<br />';
+ } elseif( $values['days'] > 0 ) {
+ $wlInfo = wfMsgExt( 'wlnote', 'parseinline',
+ $lang->formatNum( $numRows ),
+ $lang->formatNum( round( $values['days'] * 24 ) )
+ ) . '<br />';
+ }
- $showLinktext = wfMsgHtml( 'show' );
- $hideLinktext = wfMsgHtml( 'hide' );
- $title = SpecialPage::getTitleFor( 'Watchlist' );
- $skin = $wgUser->getSkin();
+ $cutofflinks = "\n" . $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n";
- $label = $value ? $showLinktext : $hideLinktext;
- $options[$name] = 1 - (int) $value;
+ # Spit out some control panel links
+ $filters = array(
+ 'hideMinor' => 'rcshowhideminor',
+ 'hideBots' => 'rcshowhidebots',
+ 'hideAnons' => 'rcshowhideanons',
+ 'hideLiu' => 'rcshowhideliu',
+ 'hideOwn' => 'rcshowhidemine',
+ 'hidePatrolled' => 'rcshowhidepatr'
+ );
+ foreach ( $this->customFilters as $key => $params ) {
+ $filters[$key] = $params['msg'];
+ }
+ // Disable some if needed
+ if ( !$user->useNPPatrol() ) {
+ unset( $filters['hidePatrolled'] );
+ }
- return wfMsgHtml( $message, $skin->linkKnown( $title, $label, array(), $options ) );
-}
+ $links = array();
+ foreach( $filters as $name => $msg ) {
+ $links[] = $this->showHideLink( $nondefaults, $msg, $name, $values[$name] );
+ }
+ # Namespace filter and put the whole form together.
+ $form .= $wlInfo;
+ $form .= $cutofflinks;
+ $form .= $lang->pipeList( $links );
+ $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl(), 'id' => 'mw-watchlist-form-namespaceselector' ) );
+ $form .= '<hr /><p>';
+ $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 .= Html::hidden( 'days', $values['days'] );
+ foreach ( $filters as $key => $msg ) {
+ if ( $values[$key] ) {
+ $form .= Html::hidden( $key, 1 );
+ }
+ }
+ $form .= Xml::closeElement( 'form' );
+ $form .= Xml::closeElement( 'fieldset' );
+ $output->addHTML( $form );
+
+ # If there's nothing to show, stop here
+ if( $numRows == 0 ) {
+ $output->addWikiMsg( 'watchnochange' );
+ return;
+ }
-function wlHoursLink( $h, $page, $options = array() ) {
- global $wgUser, $wgLang, $wgContLang;
+ /* End bottom header */
- $sk = $wgUser->getSkin();
- $title = Title::newFromText( $wgContLang->specialPage( $page ) );
- $options['days'] = ($h / 24.0);
+ /* Do link batch query */
+ $linkBatch = new LinkBatch;
+ foreach ( $res as $row ) {
+ $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text );
+ if ( $row->rc_user != 0 ) {
+ $linkBatch->add( NS_USER, $userNameUnderscored );
+ }
+ $linkBatch->add( NS_USER_TALK, $userNameUnderscored );
- $s = $sk->linkKnown(
- $title,
- $wgLang->formatNum( $h ),
- array(),
- $options
- );
+ $linkBatch->add( $row->rc_namespace, $row->rc_title );
+ }
+ $linkBatch->execute();
+ $dbr->dataSeek( $res, 0 );
+
+ $list = ChangesList::newFromContext( $this->getContext() );
+ $list->setWatchlistDivs();
+
+ $s = $list->beginRecentChangesList();
+ $counter = 1;
+ foreach ( $res as $obj ) {
+ # Make RC entry
+ $rc = RecentChange::newFromRow( $obj );
+ $rc->counter = $counter++;
+
+ if ( $wgShowUpdatedMarker ) {
+ $updated = $obj->wl_notificationtimestamp;
+ } else {
+ $updated = false;
+ }
+
+ if ( $wgRCShowWatchingUsers && $user->getOption( 'shownumberswatching' ) ) {
+ $rc->numberofWatchingusers = $dbr->selectField( 'watchlist',
+ 'COUNT(*)',
+ array(
+ 'wl_namespace' => $obj->rc_namespace,
+ 'wl_title' => $obj->rc_title,
+ ),
+ __METHOD__ );
+ } else {
+ $rc->numberofWatchingusers = 0;
+ }
+
+ $s .= $list->recentChangesLine( $rc, $updated, $counter );
+ }
+ $s .= $list->endRecentChangesList();
- return $s;
-}
+ $output->addHTML( $s );
+ }
-function wlDaysLink( $d, $page, $options = array() ) {
- global $wgUser, $wgLang, $wgContLang;
+ protected function showHideLink( $options, $message, $name, $value ) {
+ $showLinktext = wfMsgHtml( 'show' );
+ $hideLinktext = wfMsgHtml( 'hide' );
- $sk = $wgUser->getSkin();
- $title = Title::newFromText( $wgContLang->specialPage( $page ) );
- $options['days'] = $d;
- $message = ($d ? $wgLang->formatNum( $d ) : wfMsgHtml( 'watchlistall2' ) );
+ $label = $value ? $showLinktext : $hideLinktext;
+ $options[$name] = 1 - (int) $value;
- $s = $sk->linkKnown(
- $title,
- $message,
- array(),
- $options
- );
+ return wfMsgHtml( $message, Linker::linkKnown( $this->getTitle(), $label, array(), $options ) );
+ }
- return $s;
-}
+ protected function hoursLink( $h, $options = array() ) {
+ $options['days'] = ( $h / 24.0 );
-/**
- * Returns html
- */
-function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) {
- global $wgLang;
-
- $hours = array( 1, 2, 6, 12 );
- $days = array( 1, 3, 7 );
- $i = 0;
- foreach( $hours as $h ) {
- $hours[$i++] = wlHoursLink( $h, $page, $options );
- }
- $i = 0;
- foreach( $days as $d ) {
- $days[$i++] = wlDaysLink( $d, $page, $options );
+ return Linker::linkKnown(
+ $this->getTitle(),
+ $this->getLang()->formatNum( $h ),
+ array(),
+ $options
+ );
}
- return wfMsgExt('wlshowlast',
- array('parseinline', 'replaceafter'),
- $wgLang->pipeList( $hours ),
- $wgLang->pipeList( $days ),
- wlDaysLink( 0, $page, $options ) );
-}
-/**
- * Count the number of items on a user's watchlist
- *
- * @param $user User object
- * @param $talk Boolean: include talk pages
- * @return Integer
- */
-function wlCountItems( &$user, $talk = true ) {
- $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
+ protected function daysLink( $d, $options = array() ) {
+ $options['days'] = $d;
+ $message = ( $d ? $this->getLang()->formatNum( $d ) : wfMsgHtml( 'watchlistall2' ) );
- # Fetch the raw count
- $res = $dbr->select( 'watchlist', 'COUNT(*) AS count',
- array( 'wl_user' => $user->mId ), 'wlCountItems' );
- $row = $dbr->fetchObject( $res );
- $count = $row->count;
+ return Linker::linkKnown(
+ $this->getTitle(),
+ $message,
+ array(),
+ $options
+ );
+ }
- # Halve to remove talk pages if needed
- if( !$talk )
- $count = floor( $count / 2 );
+ /**
+ * Returns html
+ *
+ * @return string
+ */
+ protected function cutoffLinks( $days, $options = array() ) {
+ $hours = array( 1, 2, 6, 12 );
+ $days = array( 1, 3, 7 );
+ $i = 0;
+ foreach( $hours as $h ) {
+ $hours[$i++] = $this->hoursLink( $h, $options );
+ }
+ $i = 0;
+ foreach( $days as $d ) {
+ $days[$i++] = $this->daysLink( $d, $options );
+ }
+ return wfMsgExt('wlshowlast',
+ array('parseinline', 'replaceafter'),
+ $this->getLang()->pipeList( $hours ),
+ $this->getLang()->pipeList( $days ),
+ $this->daysLink( 0, $options ) );
+ }
- return( $count );
+ /**
+ * Count the number of items on a user's watchlist
+ *
+ * @return Integer
+ */
+ protected function countItems() {
+ $dbr = wfGetDB( DB_SLAVE, 'watchlist' );
+
+ # Fetch the raw count
+ $res = $dbr->select( 'watchlist', 'COUNT(*) AS count',
+ array( 'wl_user' => $this->getUser()->getId() ), __METHOD__ );
+ $row = $dbr->fetchObject( $res );
+ $count = $row->count;
+
+ return floor( $count / 2 );
+ }
}
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index 360f3f68..5cdaad6a 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -28,23 +28,27 @@
*/
class SpecialWhatLinksHere extends SpecialPage {
- // Stored objects
- protected $opts, $target, $selfTitle;
+ /**
+ * @var FormOptions
+ */
+ protected $opts;
- // Stored globals
- protected $skin;
+ protected $selfTitle;
+
+ /**
+ * @var Title
+ */
+ protected $target;
protected $limits = array( 20, 50, 100, 250, 500 );
public function __construct() {
parent::__construct( 'Whatlinkshere' );
- global $wgUser;
- $this->skin = $wgUser->getSkin();
}
function execute( $par ) {
- global $wgOut, $wgRequest;
-
+ $out = $this->getOutput();
+
$this->setHeaders();
$opts = new FormOptions();
@@ -59,7 +63,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$opts->add( 'hidelinks', false );
$opts->add( 'hideimages', false );
- $opts->fetchValuesFromRequest( $wgRequest );
+ $opts->fetchValuesFromRequest( $this->getRequest() );
$opts->validateIntBounds( 'limit', 0, 5000 );
// Give precedence to subpage syntax
@@ -72,29 +76,32 @@ class SpecialWhatLinksHere extends SpecialPage {
$this->target = Title::newFromURL( $opts->getValue( 'target' ) );
if( !$this->target ) {
- $wgOut->addHTML( $this->whatlinkshereForm() );
+ $out->addHTML( $this->whatlinkshereForm() );
return;
}
+ $this->getSkin()->setRelevantTitle( $this->target );
+
+
$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' ) ) ) );
+ $out->setPageTitle( wfMsg( 'whatlinkshere-title', $this->target->getPrefixedText() ) );
+ $out->setSubtitle( wfMsg( 'whatlinkshere-backlink', Linker::link( $this->target, $this->target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) );
$this->showIndirectLinks( 0, $this->target, $opts->getValue( 'limit' ),
$opts->getValue( 'from' ), $opts->getValue( 'back' ) );
}
/**
- * @param $level int Recursion level
+ * @param $level int Recursion level
* @param $target Title Target title
- * @param $limit int Number of entries to display
- * @param $from Title Display from this article ID
- * @param $back Title Display from this article ID at backwards scrolling
- * @private
+ * @param $limit int Number of entries to display
+ * @param $from Title Display from this article ID
+ * @param $back Title Display from this article ID at backwards scrolling
*/
function showIndirectLinks( $level, $target, $limit, $from = 0, $back = 0 ) {
- global $wgOut, $wgMaxRedirectLinksRetrieved;
+ global $wgMaxRedirectLinksRetrieved;
+ $out = $this->getOutput();
$dbr = wfGetDB( DB_SLAVE );
$options = array();
@@ -171,14 +178,14 @@ class SpecialWhatLinksHere extends SpecialPage {
if( ( !$fetchlinks || !$dbr->numRows($plRes) ) && ( $hidetrans || !$dbr->numRows($tlRes) ) && ( $hideimages || !$dbr->numRows($ilRes) ) ) {
if ( 0 == $level ) {
- $wgOut->addHTML( $this->whatlinkshereForm() );
+ $out->addHTML( $this->whatlinkshereForm() );
// Show filters only if there are links
if( $hidelinks || $hidetrans || $hideredirs || $hideimages )
- $wgOut->addHTML( $this->getFilterPanel() );
+ $out->addHTML( $this->getFilterPanel() );
$errMsg = is_int($namespace) ? 'nolinkshere-ns' : 'nolinkshere';
- $wgOut->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
+ $out->addWikiMsg( $errMsg, $this->target->getPrefixedText() );
}
return;
}
@@ -228,31 +235,31 @@ class SpecialWhatLinksHere extends SpecialPage {
$prevId = $from;
if ( $level == 0 ) {
- $wgOut->addHTML( $this->whatlinkshereForm() );
- $wgOut->addHTML( $this->getFilterPanel() );
- $wgOut->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
+ $out->addHTML( $this->whatlinkshereForm() );
+ $out->addHTML( $this->getFilterPanel() );
+ $out->addWikiMsg( 'linkshere', $this->target->getPrefixedText() );
$prevnext = $this->getPrevNext( $prevId, $nextId );
- $wgOut->addHTML( $prevnext );
+ $out->addHTML( $prevnext );
}
- $wgOut->addHTML( $this->listStart( $level ) );
+ $out->addHTML( $this->listStart( $level ) );
foreach ( $rows as $row ) {
$nt = Title::makeTitle( $row->page_namespace, $row->page_title );
if ( $row->page_is_redirect && $level < 2 ) {
- $wgOut->addHTML( $this->listItem( $row, $nt, true ) );
+ $out->addHTML( $this->listItem( $row, $nt, true ) );
$this->showIndirectLinks( $level + 1, $nt, $wgMaxRedirectLinksRetrieved );
- $wgOut->addHTML( Xml::closeElement( 'li' ) );
+ $out->addHTML( Xml::closeElement( 'li' ) );
} else {
- $wgOut->addHTML( $this->listItem( $row, $nt ) );
+ $out->addHTML( $this->listItem( $row, $nt ) );
}
}
- $wgOut->addHTML( $this->listEnd() );
+ $out->addHTML( $this->listEnd() );
if( $level == 0 ) {
- $wgOut->addHTML( $prevnext );
+ $out->addHTML( $prevnext );
}
}
@@ -261,6 +268,9 @@ class SpecialWhatLinksHere extends SpecialPage {
}
protected function listItem( $row, $nt, $notClose = false ) {
+ global $wgLang;
+ $dirmark = $wgLang->getDirMark();
+
# local message cache
static $msgcache = null;
if ( $msgcache === null ) {
@@ -278,7 +288,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$query = array();
}
- $link = $this->skin->linkKnown(
+ $link = Linker::linkKnown(
$nt,
null,
array(),
@@ -304,8 +314,8 @@ class SpecialWhatLinksHere extends SpecialPage {
$wlh = Xml::wrapClass( "($wlhLink)", 'mw-whatlinkshere-tools' );
return $notClose ?
- Xml::openElement( 'li' ) . "$link $propsText $wlh\n" :
- Xml::tags( 'li', null, "$link $propsText $wlh" ) . "\n";
+ Xml::openElement( 'li' ) . "$link $propsText $dirmark $wlh\n" :
+ Xml::tags( 'li', null, "$link $propsText $dirmark $wlh" ) . "\n";
}
protected function listEnd() {
@@ -317,7 +327,7 @@ class SpecialWhatLinksHere extends SpecialPage {
if ( $title === null )
$title = $this->getTitle();
- return $this->skin->linkKnown(
+ return Linker::linkKnown(
$title,
$text,
array(),
@@ -326,7 +336,7 @@ class SpecialWhatLinksHere extends SpecialPage {
}
function makeSelfLink( $text, $query ) {
- return $this->skin->linkKnown(
+ return Linker::linkKnown(
$this->selfTitle,
$text,
array(),
@@ -378,7 +388,7 @@ class SpecialWhatLinksHere extends SpecialPage {
# Build up the form
$f = Xml::openElement( 'form', array( 'action' => $wgScript ) );
-
+
# Values that should not be forgotten
$f .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
@@ -410,7 +420,7 @@ class SpecialWhatLinksHere extends SpecialPage {
/**
* Create filter panel
- *
+ *
* @return string HTML fieldset and filter panel with the show/hide links
*/
function getFilterPanel() {
diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php
index 90c1f441..9d91b833 100644
--- a/includes/specials/SpecialWithoutinterwiki.php
+++ b/includes/specials/SpecialWithoutinterwiki.php
@@ -30,8 +30,14 @@
class WithoutInterwikiPage extends PageQueryPage {
private $prefix = '';
- function getName() {
- return 'Withoutinterwiki';
+ function __construct( $name = 'Withoutinterwiki' ) {
+ parent::__construct( $name );
+ }
+
+ function execute( $par ) {
+ global $wgRequest;
+ $this->prefix = Title::capitalize( $wgRequest->getVal( 'prefix', $par ), NS_MAIN );
+ parent::execute( $par );
}
function getPageHeader() {
@@ -43,9 +49,9 @@ class WithoutInterwikiPage extends PageQueryPage {
}
$prefix = $this->prefix;
- $t = SpecialPage::getTitleFor( $this->getName() );
+ $t = $this->getTitle();
- return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
+ return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'withoutinterwiki-legend' ) ) .
Html::hidden( 'title', $t->getPrefixedText() ) .
@@ -59,6 +65,10 @@ class WithoutInterwikiPage extends PageQueryPage {
return false;
}
+ function getOrderFields() {
+ return array( 'page_namespace', 'page_title' );
+ }
+
function isExpensive() {
return true;
}
@@ -67,36 +77,22 @@ class WithoutInterwikiPage extends PageQueryPage {
return false;
}
- function getSQL() {
- $dbr = wfGetDB( DB_SLAVE );
- list( $page, $langlinks ) = $dbr->tableNamesN( 'page', 'langlinks' );
- $prefix = $this->prefix ? 'AND page_title' . $dbr->buildLike( $this->prefix , $dbr->anyString() ) : '';
- return
- "SELECT 'Withoutinterwiki' AS type,
- page_namespace AS namespace,
- page_title AS title,
- page_title AS value
- FROM $page
- LEFT JOIN $langlinks
- ON ll_from = page_id
- WHERE ll_title IS NULL
- AND page_namespace=" . NS_MAIN . "
- AND page_is_redirect = 0
- {$prefix}";
- }
-
- function setPrefix( $prefix = '' ) {
- $this->prefix = $prefix;
+ function getQueryInfo() {
+ $query = array (
+ 'tables' => array ( 'page', 'langlinks' ),
+ 'fields' => array ( 'page_namespace AS namespace',
+ 'page_title AS title',
+ 'page_title AS value' ),
+ 'conds' => array ( 'll_title IS NULL',
+ 'page_namespace' => NS_MAIN,
+ 'page_is_redirect' => 0 ),
+ 'join_conds' => array ( 'langlinks' => array (
+ 'LEFT JOIN', 'll_from = page_id' ) )
+ );
+ if ( $this->prefix ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $query['conds'][] = 'page_title ' . $dbr->buildLike( $this->prefix, $dbr->anyString() );
+ }
+ return $query;
}
-
-}
-
-function wfSpecialWithoutinterwiki() {
- global $wgRequest;
- list( $limit, $offset ) = wfCheckLimits();
- // Only searching the mainspace anyway
- $prefix = Title::capitalize( $wgRequest->getVal( 'prefix' ), NS_MAIN );
- $wip = new WithoutInterwikiPage();
- $wip->setPrefix( $prefix );
- $wip->doQuery( $offset, $limit );
}
diff --git a/includes/templates/PHP4.php b/includes/templates/PHP4.php
deleted file mode 100644
index 69f7d55d..00000000
--- a/includes/templates/PHP4.php
+++ /dev/null
@@ -1,102 +0,0 @@
-<?php
-/**
- * Template used when the installer detects that this is PHP 4
- *
- * @file
- * @ingroup Templates
- */
-
-if( !defined( 'MW_PHP4' ) ) {
- die( "Not an entry point.");
-}
-
-if( isset( $_SERVER['SCRIPT_NAME'] ) ) {
- // Probably IIS; doesn't set REQUEST_URI
- $scriptUrl = $_SERVER['SCRIPT_NAME'];
-} elseif( isset( $_SERVER['REQUEST_URI'] ) ) {
- // We're trying SCRIPT_NAME first because it won't include PATH_INFO... hopefully
- $scriptUrl = $_SERVER['REQUEST_URI'];
-} else {
- $scriptUrl = '';
-}
-if ( preg_match( '!^(.*)/(mw-)?config/[^/]*.php$!', $scriptUrl, $m ) ) {
- $baseUrl = $m[1];
-} elseif ( preg_match( '!^(.*)/[^/]*.php$!', $scriptUrl, $m ) ) {
- $baseUrl = $m[1];
-} else {
- $baseUrl = dirname( $scriptUrl );
-}
-
-?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html xmlns='http://www.w3.org/1999/xhtml' lang='en'>
- <head>
- <title>MediaWiki <?php echo htmlspecialchars( $wgVersion ); ?></title>
- <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
- <style type='text/css' media='screen'>
- html, body {
- color: #000;
- background-color: #fff;
- font-family: sans-serif;
- text-align: center;
- }
-
- p {
- text-align: left;
- margin-left: 2em;
- margin-right: 2em;
- }
-
- h1 {
- font-size: 150%;
- }
- </style>
- </head>
- <body>
- <img src="<?php echo htmlspecialchars( $baseUrl ) ?>/skins/common/images/mediawiki.png" alt='The MediaWiki logo' />
-
- <h1>MediaWiki <?php echo htmlspecialchars( $wgVersion ); ?></h1>
- <div class='error'>
-<p>
- MediaWiki requires PHP 5.1.x or higher. You are running PHP
- <?php echo htmlspecialchars( phpversion() ); ?>.
-</p>
-<?php
-flush();
-/**
- * Test the *.php5 extension
- */
-$downloadOther = true;
-if ( $baseUrl ) {
- $testUrl = "$wgServer$baseUrl/php5.php5";
- if( function_exists( 'file_get_contents' ) ) {
- $errorLevel = error_reporting();
- error_reporting( $errorLevel & !E_WARNING );
-
- ini_set( 'allow_url_fopen', '1' );
- $s = file_get_contents( $testUrl );
-
- error_reporting( $errorLevel );
- }
-
- if ( strpos( $s, 'yes' ) !== false ) {
- $encUrl = htmlspecialchars( str_replace( '.php', '.php5', $scriptUrl ) );
- echo "<p>You may be able to use MediaWiki using a <a href=\"$encUrl\">.php5</a> file extension.</p>";
- $downloadOther = false;
- }
-}
-if ( $downloadOther ) {
-?>
-<p>Please consider
-<a href="http://www.php.net/downloads.php">upgrading your copy of PHP</a>.
-PHP 4 is at the end of its lifecycle and will not receive further security updates.</p>
-<p>If for some reason you really really need to run MediaWiki on PHP 4, you will need to
-<a href="http://www.mediawiki.org/wiki/Download">download version 1.6.x</a>
-from our website. </p>
-<?php
-}
-?>
-
- </div>
- </body>
-</html>
diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php
index 99ab2d8e..0bfd9737 100644
--- a/includes/templates/Userlogin.php
+++ b/includes/templates/Userlogin.php
@@ -36,7 +36,7 @@ class UserloginTemplate extends QuickTemplate {
<p id="userloginlink"><?php $this->html('link') ?></p>
<?php $this->html('header'); /* pre-table point for form plugins... */ ?>
<div id="userloginprompt"><?php $this->msgWiki('loginprompt') ?></div>
- <?php if( @$this->haveData( 'languages' ) ) { ?><div id="languagelinks"><p><?php $this->html( 'languages' ); ?></p></div><?php } ?>
+ <?php if( $this->haveData( 'languages' ) ) { ?><div id="languagelinks"><p><?php $this->html( 'languages' ); ?></p></div><?php } ?>
<table>
<tr>
<td class="mw-label"><label for='wpName1'><?php $this->msg('yourname') ?></label></td>
@@ -130,21 +130,29 @@ class UserloginTemplate extends QuickTemplate {
'tabindex' => '9'
) );
if ( $this->data['useemail'] && $this->data['canreset'] ) {
- echo '&#160;';
- echo Html::input( 'wpMailmypassword', wfMsg( 'mailmypassword' ), 'submit', array(
- 'id' => 'wpMailmypassword',
- 'tabindex' => '10'
- ) );
+ if( $this->data['resetlink'] === true ){
+ echo '&#160;';
+ echo Linker::link(
+ SpecialPage::getTitleFor( 'PasswordReset' ),
+ wfMessage( 'userlogin-resetlink' )
+ );
+ } elseif( $this->data['resetlink'] === null ) {
+ echo '&#160;';
+ echo Html::input( 'wpMailmypassword', wfMsg( 'mailmypassword' ), 'submit', array(
+ 'id' => 'wpMailmypassword',
+ 'tabindex' => '10'
+ ) );
+ }
} ?>
</td>
</tr>
</table>
-<?php if( @$this->haveData( 'uselang' ) ) { ?><input type="hidden" name="uselang" value="<?php $this->text( 'uselang' ); ?>" /><?php } ?>
-<?php if( @$this->haveData( 'token' ) ) { ?><input type="hidden" name="wpLoginToken" value="<?php $this->text( 'token' ); ?>" /><?php } ?>
+<?php if( $this->haveData( 'uselang' ) ) { ?><input type="hidden" name="uselang" value="<?php $this->text( 'uselang' ); ?>" /><?php } ?>
+<?php if( $this->haveData( 'token' ) ) { ?><input type="hidden" name="wpLoginToken" value="<?php $this->text( 'token' ); ?>" /><?php } ?>
</form>
</div>
-<div id="loginend"><?php $this->msgWiki( 'loginend' ); ?></div>
+<div id="loginend"><?php $this->html( 'loginend' ); ?></div>
<?php
}
@@ -183,7 +191,7 @@ class UsercreateTemplate extends QuickTemplate {
<h2><?php $this->msg('createaccount') ?></h2>
<p id="userloginlink"><?php $this->html('link') ?></p>
<?php $this->html('header'); /* pre-table point for form plugins... */ ?>
- <?php if( @$this->haveData( 'languages' ) ) { ?><div id="languagelinks"><p><?php $this->html( 'languages' ); ?></p></div><?php } ?>
+ <?php if( $this->haveData( 'languages' ) ) { ?><div id="languagelinks"><p><?php $this->html( 'languages' ); ?></p></div><?php } ?>
<table>
<tr>
<td class="mw-label"><label for='wpName2'><?php $this->msg('yourname') ?></label></td>
@@ -251,11 +259,15 @@ class UsercreateTemplate extends QuickTemplate {
'size' => '20'
) ); ?>
<div class="prefsectiontip">
- <?php if( $this->data['emailrequired'] ) {
- $this->msgWiki('prefs-help-email-required');
- } else {
- $this->msgWiki('prefs-help-email');
- } ?>
+ <?php // duplicated in Preferences.php profilePreferences()
+ if( $this->data['emailrequired'] ) {
+ $this->msgWiki('prefs-help-email-required');
+ } else {
+ $this->msgWiki('prefs-help-email');
+ }
+ if( $this->data['emailothers'] ) {
+ $this->msgWiki('prefs-help-email-others');
+ } ?>
</div>
</td>
<?php } ?>
@@ -361,11 +373,11 @@ class UsercreateTemplate extends QuickTemplate {
</td>
</tr>
</table>
-<?php if( @$this->haveData( 'uselang' ) ) { ?><input type="hidden" name="uselang" value="<?php $this->text( 'uselang' ); ?>" /><?php } ?>
-<?php if( @$this->haveData( 'token' ) ) { ?><input type="hidden" name="wpCreateaccountToken" value="<?php $this->text( 'token' ); ?>" /><?php } ?>
+<?php if( $this->haveData( 'uselang' ) ) { ?><input type="hidden" name="uselang" value="<?php $this->text( 'uselang' ); ?>" /><?php } ?>
+<?php if( $this->haveData( 'token' ) ) { ?><input type="hidden" name="wpCreateaccountToken" value="<?php $this->text( 'token' ); ?>" /><?php } ?>
</form>
</div>
-<div id="signupend"><?php $this->msgWiki( 'signupend' ); ?></div>
+<div id="signupend"><?php $this->html( 'signupend' ); ?></div>
<?php
}
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php
index 546b9db8..a97edbc7 100644
--- a/includes/upload/UploadBase.php
+++ b/includes/upload/UploadBase.php
@@ -18,14 +18,16 @@ abstract class UploadBase {
protected $mDesiredDestName, $mDestName, $mRemoveTempFile, $mSourceType;
protected $mTitle = false, $mTitleError = 0;
protected $mFilteredName, $mFinalExtension;
- protected $mLocalFile;
+ protected $mLocalFile, $mFileSize, $mFileProps;
+ protected $mBlackListedExtensions;
+ protected $mJavaDetected;
const SUCCESS = 0;
const OK = 0;
const EMPTY_FILE = 3;
const MIN_LENGTH_PARTNAME = 4;
const ILLEGAL_FILENAME = 5;
- const OVERWRITE_EXISTING_FILE = 7; # Not used anymore; handled by verifyPermissions()
+ const OVERWRITE_EXISTING_FILE = 7; # Not used anymore; handled by verifyTitlePermissions()
const FILETYPE_MISSING = 8;
const FILETYPE_BADTYPE = 9;
const VERIFICATION_ERROR = 10;
@@ -34,13 +36,7 @@ abstract class UploadBase {
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;
- }
+ const WINDOWS_NONASCII_FILENAME = 13;
public function getVerificationErrorCode( $error ) {
$code_to_status = array(self::EMPTY_FILE => 'empty-file',
@@ -52,6 +48,7 @@ abstract class UploadBase {
self::OVERWRITE_EXISTING_FILE => 'overwrite',
self::VERIFICATION_ERROR => 'verification-error',
self::HOOK_ABORTED => 'hookaborted',
+ self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
);
if( isset( $code_to_status[$error] ) ) {
return $code_to_status[$error];
@@ -66,21 +63,21 @@ abstract class UploadBase {
*/
public static function isEnabled() {
global $wgEnableUploads;
+
if ( !$wgEnableUploads ) {
return false;
}
# Check php's file_uploads setting
- if( !wfIniGetBool( 'file_uploads' ) ) {
- return false;
- }
- return true;
+ return wfIsHipHop() || wfIniGetBool( 'file_uploads' );
}
/**
* Returns true if the user can use this upload module or else a string
* identifying the missing permission.
* Can be overriden by subclasses.
+ *
+ * @param $user User
*/
public static function isAllowed( $user ) {
foreach ( array( 'upload', 'edit' ) as $permission ) {
@@ -96,6 +93,9 @@ abstract class UploadBase {
/**
* Create a form of UploadBase depending on wpSourceType and initializes it
+ *
+ * @param $request WebRequest
+ * @param $type
*/
public static function createFromRequest( &$request, $type = null ) {
$type = $type ? $type : $request->getVal( 'wpSourceType', 'File' );
@@ -144,6 +144,14 @@ abstract class UploadBase {
public function __construct() {}
/**
+ * Returns the upload type. Should be overridden by child classes
+ *
+ * @since 1.18
+ * @return string
+ */
+ public function getSourceType() { return null; }
+
+ /**
* Initialize the path information
* @param $name string the desired destination name
* @param $tempPath string the temporary path
@@ -200,6 +208,19 @@ abstract class UploadBase {
}
/**
+ * Finish appending to the Repo file
+ *
+ * @param $toAppendPath String: path to the Repo file that will be appended to.
+ * @return Status Status
+ */
+ protected function appendFinish( $toAppendPath ) {
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ $status = $repo->appendFinish( $toAppendPath );
+ return $status;
+ }
+
+
+ /**
* @param $srcPath String: the source path
* @return the real path if it was a virtual URL
*/
@@ -226,11 +247,11 @@ abstract class UploadBase {
/**
* Honor $wgMaxUploadSize
*/
- global $wgMaxUploadSize;
- if( $this->mFileSize > $wgMaxUploadSize ) {
- return array(
+ $maxSize = self::getMaxUploadSize( $this->getSourceType() );
+ if( $this->mFileSize > $maxSize ) {
+ return array(
'status' => self::FILE_TOO_LARGE,
- 'max' => $wgMaxUploadSize,
+ 'max' => $maxSize,
);
}
@@ -279,6 +300,9 @@ abstract class UploadBase {
}
if ( $this->mTitleError == self::FILETYPE_BADTYPE ) {
$result['finalExt'] = $this->mFinalExtension;
+ if ( count( $this->mBlackListedExtensions ) ) {
+ $result['blacklistedExt'] = $this->mBlackListedExtensions;
+ }
}
return $result;
}
@@ -297,15 +321,16 @@ abstract class UploadBase {
global $wgVerifyMimeType;
if ( $wgVerifyMimeType ) {
wfDebug ( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n");
- if ( !$this->verifyExtension( $mime, $this->mFinalExtension ) ) {
- return array( 'filetype-mime-mismatch' );
- }
-
global $wgMimeTypeBlacklist;
if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
return array( 'filetype-badmime', $mime );
}
+ # XXX: Missing extension will be caught by validateName() via getTitle()
+ if ( $this->mFinalExtension != '' && !$this->verifyExtension( $mime, $this->mFinalExtension ) ) {
+ return array( 'filetype-mime-mismatch', $this->mFinalExtension, $mime );
+ }
+
# Check IE type
$fp = fopen( $this->mTempPath, 'rb' );
$chunk = fread( $fp, 256 );
@@ -330,12 +355,12 @@ abstract class UploadBase {
* @return mixed true of the file is verified, array otherwise.
*/
protected function verifyFile() {
+ global $wgAllowJavaUploads;
# get the title, even though we are doing nothing with it, because
- # we need to populate mFinalExtension
+ # 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' ];
@@ -354,9 +379,25 @@ abstract class UploadBase {
}
}
- /**
- * Scan the uploaded file for viruses
- */
+ # Check for Java applets, which if uploaded can bypass cross-site
+ # restrictions.
+ if ( !$wgAllowJavaUploads ) {
+ $this->mJavaDetected = false;
+ $zipStatus = ZipDirectoryReader::read( $this->mTempPath,
+ array( $this, 'zipEntryCallback' ) );
+ if ( !$zipStatus->isOK() ) {
+ $errors = $zipStatus->getErrorsArray();
+ $error = reset( $errors );
+ if ( $error[0] !== 'zip-wrong-format' ) {
+ return $error;
+ }
+ }
+ if ( $this->mJavaDetected ) {
+ return array( 'uploadjava' );
+ }
+ }
+
+ # Scan the uploaded file for viruses
$virus = $this->detectVirus( $this->mTempPath );
if ( $virus ) {
return array( 'uploadvirus', $virus );
@@ -381,17 +422,51 @@ abstract class UploadBase {
}
/**
+ * Callback for ZipDirectoryReader to detect Java class files.
+ */
+ function zipEntryCallback( $entry ) {
+ $names = array( $entry['name'] );
+
+ // If there is a null character, cut off the name at it, because JDK's
+ // ZIP_GetEntry() uses strcmp() if the name hashes match. If a file name
+ // were constructed which had ".class\0" followed by a string chosen to
+ // make the hash collide with the truncated name, that file could be
+ // returned in response to a request for the .class file.
+ $nullPos = strpos( $entry['name'], "\000" );
+ if ( $nullPos !== false ) {
+ $names[] = substr( $entry['name'], 0, $nullPos );
+ }
+
+ // If there is a trailing slash in the file name, we have to strip it,
+ // because that's what ZIP_GetEntry() does.
+ if ( preg_grep( '!\.class/?$!', $names ) ) {
+ $this->mJavaDetected = true;
+ }
+ }
+
+ /**
+ * Alias for verifyTitlePermissions. The function was originally 'verifyPermissions'
+ * but that suggests it's checking the user, when it's really checking the title + user combination.
+ * @param $user User object to verify the permissions against
+ * @return mixed An array as returned by getUserPermissionsErrors or true
+ * in case the user has proper permissions.
+ */
+ public function verifyPermissions( $user ) {
+ return $this->verifyTitlePermissions( $user );
+ }
+
+ /**
* 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 the User object to verify the permissions against
+ * @param $user User object to verify the permissions against
* @return mixed An array as returned by getUserPermissionsErrors or true
* in case the user has proper permissions.
*/
- public function verifyPermissions( $user ) {
+ public function verifyTitlePermissions( $user ) {
/**
* If the image is protected, non-sysop users won't be able
* to modify it by uploading a new revision.
@@ -412,12 +487,12 @@ abstract class UploadBase {
$permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
return $permErrors;
}
-
+
$overwriteError = $this->checkOverwrite( $user );
if ( $overwriteError !== true ) {
return array( $overwriteError );
}
-
+
return true;
}
@@ -427,11 +502,12 @@ abstract class UploadBase {
* @return Array of warnings
*/
public function checkWarnings() {
+ global $wgLang;
+
$warnings = array();
$localFile = $this->getLocalFile();
$filename = $localFile->getName();
- $n = strrpos( $filename, '.' );
/**
* Check whether the resulting filename is different from the desired one,
@@ -448,7 +524,8 @@ abstract class UploadBase {
global $wgCheckFileExtensions, $wgFileExtensions;
if ( $wgCheckFileExtensions ) {
if ( !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) {
- $warnings['filetype-unwanted-type'] = $this->mFinalExtension;
+ $warnings['filetype-unwanted-type'] = array( $this->mFinalExtension,
+ $wgLang->commaList( $wgFileExtensions ), count( $wgFileExtensions ) );
}
}
@@ -493,24 +570,26 @@ abstract class UploadBase {
* Really perform the upload. Stores the file in the local repo, watches
* if necessary and runs the UploadComplete hook.
*
- * @return mixed Status indicating the whether the upload succeeded.
+ * @param $user User
+ *
+ * @return Status indicating the whether the upload succeeded.
*/
public function performUpload( $comment, $pageText, $watch, $user ) {
- $status = $this->getLocalFile()->upload(
- $this->mTempPath,
- $comment,
+ $status = $this->getLocalFile()->upload(
+ $this->mTempPath,
+ $comment,
$pageText,
File::DELETE_SOURCE,
- $this->mFileProps,
- false,
- $user
+ $this->mFileProps,
+ false,
+ $user
);
if( $status->isGood() ) {
if ( $watch ) {
$user->addWatch( $this->getLocalFile()->getTitle() );
}
-
+
wfRunHooks( 'UploadComplete', array( &$this ) );
}
@@ -527,13 +606,23 @@ abstract class UploadBase {
if ( $this->mTitle !== false ) {
return $this->mTitle;
}
+
+ /* Assume that if a user specified File:Something.jpg, this is an error
+ * and that the namespace prefix needs to be stripped of.
+ */
+ $title = Title::newFromText( $this->mDesiredDestName );
+ if ( $title && $title->getNamespace() == NS_FILE ) {
+ $this->mFilteredName = $title->getDBkey();
+ } else {
+ $this->mFilteredName = $this->mDesiredDestName;
+ }
/**
* Chop off any directories in the given filename. Then
* filter out illegal characters, and try to make a legible name
* out of it. We'll strip some silently that Title would die on.
*/
- $this->mFilteredName = wfStripIllegalFilenameChars( $this->mDesiredDestName );
+ $this->mFilteredName = wfStripIllegalFilenameChars( $this->mFilteredName );
/* Normalize to title form before we do any further processing */
$nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
if( is_null( $nt ) ) {
@@ -552,20 +641,48 @@ abstract class UploadBase {
$this->mFinalExtension = trim( $ext[count( $ext ) - 1] );
} else {
$this->mFinalExtension = '';
+
+ # No extension, try guessing one
+ $magic = MimeMagic::singleton();
+ $mime = $magic->guessMimeType( $this->mTempPath );
+ if ( $mime !== 'unknown/unknown' ) {
+ # Get a space separated list of extensions
+ $extList = $magic->getExtensionsForType( $mime );
+ if ( $extList ) {
+ # Set the extension to the canonical extension
+ $this->mFinalExtension = strtok( $extList, ' ' );
+
+ # Fix up the other variables
+ $this->mFilteredName .= ".{$this->mFinalExtension}";
+ $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
+ $ext = array( $this->mFinalExtension );
+ }
+ }
+
}
/* Don't allow users to override the blacklist (check file extension) */
global $wgCheckFileExtensions, $wgStrictFileExtensions;
global $wgFileExtensions, $wgFileBlacklist;
+
+ $blackListedExtensions = $this->checkFileExtensionList( $ext, $wgFileBlacklist );
+
if ( $this->mFinalExtension == '' ) {
$this->mTitleError = self::FILETYPE_MISSING;
return $this->mTitle = null;
- } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) ||
+ } elseif ( $blackListedExtensions ||
( $wgCheckFileExtensions && $wgStrictFileExtensions &&
- !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) ) {
+ !$this->checkFileExtensionList( $ext, $wgFileExtensions ) ) ) {
+ $this->mBlackListedExtensions = $blackListedExtensions;
$this->mTitleError = self::FILETYPE_BADTYPE;
return $this->mTitle = null;
}
+
+ // Windows may be broken with special characters, see bug XXX
+ if ( wfIsWindows() && !preg_match( '/^[\x0-\x7f]*$/', $nt->getText() ) ) {
+ $this->mTitleError = self::WINDOWS_NONASCII_FILENAME;
+ return $this->mTitle = null;
+ }
# If there was more than one "extension", reassemble the base
# filename to prevent bogus complaints about length
@@ -585,6 +702,8 @@ abstract class UploadBase {
/**
* Return the local file and initializes if necessary.
+ *
+ * @return LocalFile
*/
public function getLocalFile() {
if( is_null( $this->mLocalFile ) ) {
@@ -619,31 +738,40 @@ abstract class UploadBase {
* 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
+ * into the database.
+ * This method returns the file object, which also has a 'fileKey' property which can be passed through a form or
* API request to find this stashed file again.
*
- * @param $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
+ * @param $key String: (optional) the file key used to find the file info again. If not supplied, a key will be autogenerated.
+ * @return UploadStashFile stashed file
*/
- public function stashSessionFile( $key = null ) {
+ public function stashFile( $key = null ) {
+ // was stashSessionFile
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
- $data = array(
- 'mFileProps' => $this->mFileProps
- );
- $file = $stash->stashFile( $this->mTempPath, $data, $key );
+
+ $file = $stash->stashFile( $this->mTempPath, $this->getSourceType(), $key );
$this->mLocalFile = $file;
return $file;
}
/**
- * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashSessionFile().
+ * Stash a file in a temporary directory, returning a key which can be used to find the file again. See stashFile().
*
- * @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
+ * @param $key String: (optional) the file key used to find the file info again. If not supplied, a key will be autogenerated.
+ * @return String: file key
+ */
+ public function stashFileGetKey( $key = null ) {
+ return $this->stashFile( $key )->getFileKey();
+ }
+
+ /**
+ * alias for stashFileGetKey, for backwards compatibility
+ *
+ * @param $key String: (optional) the file key used to find the file info again. If not supplied, a key will be autogenerated.
+ * @return String: file key
*/
public function stashSession( $key = null ) {
- return $this->stashSessionFile( $key )->getSessionKey();
+ return $this->stashFileGetKey( $key );
}
/**
@@ -689,19 +817,14 @@ abstract class UploadBase {
/**
* Perform case-insensitive match against a list of file extensions.
- * Returns true if any of the extensions are in the list.
+ * Returns an array of matching extensions.
*
* @param $ext Array
* @param $list Array
* @return Boolean
*/
public static function checkFileExtensionList( $ext, $list ) {
- foreach( $ext as $e ) {
- if( in_array( strtolower( $e ), $list ) ) {
- return true;
- }
- }
- return false;
+ return array_intersect( array_map( 'strtolower', $ext ), $list );
}
/**
@@ -788,7 +911,7 @@ abstract class UploadBase {
$chunk = trim( $chunk );
- # FIXME: convert from UTF-16 if necessarry!
+ # @todo FIXME: Convert from UTF-16 if necessarry!
wfDebug( __METHOD__ . ": checking for embedded scripts and HTML stuff\n" );
# check for HTML doctype
@@ -828,6 +951,7 @@ abstract class UploadBase {
foreach( $tags as $tag ) {
if( false !== strpos( $chunk, $tag ) ) {
+ wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag\n" );
return true;
}
}
@@ -841,16 +965,19 @@ abstract class UploadBase {
# look for script-types
if( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) {
+ wfDebug( __METHOD__ . ": found script types\n" );
return true;
}
# look for html-style script-urls
if( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
+ wfDebug( __METHOD__ . ": found html-style script urls\n" );
return true;
}
# look for css-style script-urls
if( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) {
+ wfDebug( __METHOD__ . ": found css-style script urls\n" );
return true;
}
@@ -989,33 +1116,11 @@ abstract class UploadBase {
}
/**
- * Check if the temporary file is MacBinary-encoded, as some uploads
- * from Internet Explorer on Mac OS Classic and Mac OS X will be.
- * If so, the data fork will be extracted to a second temporary file,
- * which will then be checked for validity and either kept or discarded.
- */
- private function checkMacBinary() {
- $macbin = new MacBinary( $this->mTempPath );
- if( $macbin->isValid() ) {
- $dataFile = tempnam( wfTempDir(), 'WikiMacBinary' );
- $dataHandle = fopen( $dataFile, 'wb' );
-
- wfDebug( __METHOD__ . ": Extracting MacBinary data fork to $dataFile\n" );
- $macbin->extractData( $dataHandle );
-
- $this->mTempPath = $dataFile;
- $this->mFileSize = $macbin->dataForkLength();
-
- // We'll have to manually remove the new file if it's not kept.
- $this->mRemoveTempFile = true;
- }
- $macbin->close();
- }
-
- /**
* Check if there's an overwrite conflict and, if so, if restrictions
* forbid this user from performing the upload.
*
+ * @param $user User
+ *
* @return mixed true on success, array on failure
*/
private function checkOverwrite( $user ) {
@@ -1072,7 +1177,7 @@ abstract class UploadBase {
* - File exists with normalized extension
* - The file looks like a thumbnail and the original exists
*
- * @param $file The File object to check
+ * @param $file File The File object to check
* @return mixed False if the file does not exists, else an array
*/
public static function getExistsWarning( $file ) {
@@ -1170,9 +1275,9 @@ abstract class UploadBase {
*/
public static function getFilenamePrefixBlacklist() {
$blacklist = array();
- $message = wfMsgForContent( 'filename-prefix-blacklist' );
- if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) {
- $lines = explode( "\n", $message );
+ $message = wfMessage( 'filename-prefix-blacklist' )->inContentLanguage();
+ if( !$message->isDisabled() ) {
+ $lines = explode( "\n", $message->plain() );
foreach( $lines as $line ) {
// Remove comment lines
$comment = substr( trim( $line ), 0, 1 );
@@ -1191,18 +1296,18 @@ abstract class UploadBase {
}
/**
- * Gets image info about the file just uploaded.
+ * 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
+ * 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.
+ * with the appropriate format, presumably.
*
* @param $result ApiResult:
* @return Array: image info
*/
public function getImageInfo( $result ) {
$file = $this->getLocalFile();
- // TODO This cries out for refactoring. We really want to say $file->getAllInfo(); here.
+ // 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();
@@ -1220,4 +1325,19 @@ abstract class UploadBase {
unset( $code['status'] );
return Status::newFatal( $this->getVerificationErrorCode( $code ), $error );
}
+
+ public static function getMaxUploadSize( $forType = null ) {
+ global $wgMaxUploadSize;
+
+ if ( is_array( $wgMaxUploadSize ) ) {
+ if ( !is_null( $forType ) && isset( $wgMaxUploadSize[$forType] ) ) {
+ return $wgMaxUploadSize[$forType];
+ } else {
+ return $wgMaxUploadSize['*'];
+ }
+ } else {
+ return intval( $wgMaxUploadSize );
+ }
+
+ }
}
diff --git a/includes/upload/UploadFromFile.php b/includes/upload/UploadFromFile.php
index e67ec191..c2ab6467 100644
--- a/includes/upload/UploadFromFile.php
+++ b/includes/upload/UploadFromFile.php
@@ -8,8 +8,15 @@
*/
class UploadFromFile extends UploadBase {
+
+ /**
+ * @var WebRequestUpload
+ */
protected $mUpload = null;
+ /**
+ * @param $request WebRequest
+ */
function initializeFromRequest( &$request ) {
$upload = $request->getUpload( 'wpUploadFile' );
$desiredDestName = $request->getText( 'wpDestFile' );
@@ -18,31 +25,47 @@ class UploadFromFile extends UploadBase {
return $this->initialize( $desiredDestName, $upload );
}
-
+
/**
* Initialize from a filename and a WebRequestUpload
+ * @param $name
+ * @param $webRequestUpload
*/
function initialize( $name, $webRequestUpload ) {
$this->mUpload = $webRequestUpload;
return $this->initializePathInfo( $name,
$this->mUpload->getTempName(), $this->mUpload->getSize() );
}
+
+ /**
+ * @param $request
+ * @return bool
+ */
static function isValidRequest( $request ) {
# 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;
}
-
+
+ /**
+ * @return string
+ */
+ public function getSourceType() {
+ return 'file';
+ }
+
+ /**
+ * @return array
+ */
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,
+ self::getMaxUploadSize( $this->getSourceType() ),
wfShorthandToInteger( ini_get( 'upload_max_filesize' ) ),
wfShorthandToInteger( ini_get( 'post_max_size' ) )
),
@@ -60,6 +83,4 @@ class UploadFromFile extends UploadBase {
public function getFileTempname() {
return $this->mUpload->getTempname();
}
-
-
}
diff --git a/includes/upload/UploadFromStash.php b/includes/upload/UploadFromStash.php
index 156781e9..feb14a87 100644
--- a/includes/upload/UploadFromStash.php
+++ b/includes/upload/UploadFromStash.php
@@ -8,66 +8,109 @@
*/
class UploadFromStash extends UploadBase {
- public static function isValidSessionKey( $key, $sessionData ) {
- return !empty( $key ) &&
- is_array( $sessionData ) &&
- isset( $sessionData[$key] ) &&
- isset( $sessionData[$key]['version'] ) &&
- $sessionData[$key]['version'] == UploadBase::SESSION_VERSION;
+ protected $mFileKey, $mVirtualTempPath, $mFileProps, $mSourceType;
+
+ // an instance of UploadStash
+ private $stash;
+
+ //LocalFile repo
+ private $repo;
+
+ public function __construct( $user = false, $stash = false, $repo = false ) {
+ // user object. sometimes this won't exist, as when running from cron.
+ $this->user = $user;
+
+ if( $repo ) {
+ $this->repo = $repo;
+ } else {
+ $this->repo = RepoGroup::singleton()->getLocalRepo();
+ }
+
+ if( $stash ) {
+ $this->stash = $stash;
+ } else {
+ wfDebug( __METHOD__ . " creating new UploadStash instance for " . $user->getId() . "\n" );
+ $this->stash = new UploadStash( $this->repo, $this->user );
+ }
+
+ return true;
+ }
+
+ public static function isValidKey( $key ) {
+ // this is checked in more detail in UploadStash
+ return preg_match( UploadStash::KEY_FORMAT_REGEX, $key );
}
+ /**
+ * @param $request WebRequest
+ *
+ * @return Boolean
+ */
public static function isValidRequest( $request ) {
- $sessionData = $request->getSessionData( UploadBase::SESSION_KEYNAME );
- return self::isValidSessionKey(
- $request->getText( 'wpSessionKey' ),
- $sessionData
- );
+ return self::isValidKey( $request->getText( 'wpFileKey' ) || $request->getText( 'wpSessionKey' ) );
}
- public function initialize( $name, $sessionKey, $sessionData ) {
- /**
- * Confirming a temporarily stashed upload.
- * We don't want path names to be forged, so we keep
- * them in the session on the server and just give
- * an opaque key to the user agent.
- */
-
- $this->initializePathInfo( $name,
- $this->getRealPath ( $sessionData['mTempPath'] ),
- $sessionData['mFileSize'],
- false
- );
-
- $this->mSessionKey = $sessionKey;
- $this->mVirtualTempPath = $sessionData['mTempPath'];
- $this->mFileProps = $sessionData['mFileProps'];
+ public function initialize( $key, $name = 'upload_file' ) {
+ /**
+ * Confirming a temporarily stashed upload.
+ * We don't want path names to be forged, so we keep
+ * them in the session on the server and just give
+ * an opaque key to the user agent.
+ */
+ $metadata = $this->stash->getMetadata( $key );
+ $this->initializePathInfo( $name,
+ $this->getRealPath ( $metadata['us_path'] ),
+ $metadata['us_size'],
+ false
+ );
+
+ $this->mFileKey = $key;
+ $this->mVirtualTempPath = $metadata['us_path'];
+ $this->mFileProps = $this->stash->getFileProps( $key );
+ $this->mSourceType = $metadata['us_source_type'];
}
+ /**
+ * @param $request WebRequest
+ */
public function initializeFromRequest( &$request ) {
- $sessionKey = $request->getText( 'wpSessionKey' );
- $sessionData = $request->getSessionData( UploadBase::SESSION_KEYNAME );
+ $fileKey = $request->getText( 'wpFileKey' ) || $request->getText( 'wpSessionKey' );
$desiredDestName = $request->getText( 'wpDestFile' );
- if( !$desiredDestName )
- $desiredDestName = $request->getText( 'wpUploadFile' );
- return $this->initialize( $desiredDestName, $sessionKey, $sessionData[$sessionKey] );
+ if( !$desiredDestName ) {
+ $desiredDestName = $request->getText( 'wpUploadFile' ) || $request->getText( 'filename' );
+ }
+ return $this->initialize( $fileKey, $desiredDestName );
+ }
+
+ public function getSourceType() {
+ return $this->mSourceType;
}
/**
* File has been previously verified so no need to do so again.
+ *
+ * @return bool
*/
protected function verifyFile() {
return true;
}
-
/**
* There is no need to stash the image twice
*/
+ public function stashFile( $key = null ) {
+ if ( !empty( $this->mLocalFile ) ) {
+ return $this->mLocalFile;
+ }
+ return parent::stashFile( $key );
+ }
+
+ /**
+ * Alias for stashFile
+ */
public function stashSession( $key = null ) {
- if ( !empty( $this->mSessionKey ) )
- return $this->mSessionKey;
- return parent::stashSession();
+ return $this->stashFile( $key );
}
/**
@@ -75,9 +118,16 @@ class UploadFromStash extends UploadBase {
* @return success
*/
public function unsaveUploadedFile() {
- $repo = RepoGroup::singleton()->getLocalRepo();
- $success = $repo->freeTemp( $this->mVirtualTempPath );
- return $success;
+ return $this->stash->removeFile( $this->mFileKey );
+ }
+
+ /**
+ * Perform the upload, then remove the database record afterward.
+ */
+ public function performUpload( $comment, $pageText, $watch, $user ) {
+ $rv = parent::performUpload( $comment, $pageText, $watch, $user );
+ $this->unsaveUploadedFile();
+ return $rv;
}
} \ No newline at end of file
diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php
index c28fd7da..8178988f 100644
--- a/includes/upload/UploadFromUrl.php
+++ b/includes/upload/UploadFromUrl.php
@@ -12,9 +12,13 @@ class UploadFromUrl extends UploadBase {
protected $mAsync, $mUrl;
protected $mIgnoreWarnings = true;
+ protected $mTempPath;
+
/**
* 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.
+ *
+ * @param $user User
*/
public static function isAllowed( $user ) {
if ( !$user->isAllowed( 'upload_by_url' ) )
@@ -45,6 +49,9 @@ class UploadFromUrl extends UploadBase {
$this->mUrl = $url;
$this->mAsync = $wgAllowAsyncCopyUploads ? $async : false;
+ if ( $async ) {
+ throw new MWException( 'Asynchronous copy uploads are no longer possible as of r81612.' );
+ }
$tempPath = $this->mAsync ? null : $this->makeTemporaryFile();
# File size and removeTempFile will be filled in later
@@ -53,7 +60,7 @@ class UploadFromUrl extends UploadBase {
/**
* Entry point for SpecialUpload
- * @param $request Object: WebRequest object
+ * @param $request WebRequest object
*/
public function initializeFromRequest( &$request ) {
$desiredDestName = $request->getText( 'wpDestFile' );
@@ -61,13 +68,13 @@ class UploadFromUrl extends UploadBase {
$desiredDestName = $request->getText( 'wpUploadFileURL' );
return $this->initialize(
$desiredDestName,
- $request->getVal( 'wpUploadFileURL' ),
+ trim( $request->getVal( 'wpUploadFileURL' ) ),
false
);
}
/**
- * @param $request Object: WebRequest object
+ * @param $request WebRequest object
*/
public static function isValidRequest( $request ) {
global $wgUser;
@@ -78,6 +85,7 @@ class UploadFromUrl extends UploadBase {
&& $wgUser->isAllowed( 'upload_by_url' );
}
+ public function getSourceType() { return 'url'; }
public function fetchFile() {
if ( !Http::isValidURI( $this->mUrl ) ) {
@@ -137,7 +145,9 @@ class UploadFromUrl extends UploadBase {
$this->mRemoveTempFile = true;
$this->mFileSize = 0;
- $req = MWHttpRequest::factory( $this->mUrl );
+ $req = MWHttpRequest::factory( $this->mUrl, array(
+ 'followRedirects' => true
+ ) );
$req->setCallback( array( $this, 'saveTempFileChunk' ) );
$status = $req->execute();
@@ -184,11 +194,11 @@ class UploadFromUrl extends UploadBase {
* 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 ) {
+ public function verifyTitlePermissions( $user ) {
if ( $this->mAsync ) {
return true;
}
- return parent::verifyPermissions( $user );
+ return parent::verifyTitlePermissions( $user );
}
/**
@@ -207,7 +217,13 @@ class UploadFromUrl extends UploadBase {
return parent::performUpload( $comment, $pageText, $watch, $user );
}
-
+ /**
+ * @param $comment
+ * @param $pageText
+ * @param $watch
+ * @param $user User
+ * @return
+ */
protected function insertJob( $comment, $pageText, $watch, $user ) {
$sessionKey = $this->stashSession();
$job = new UploadFromUrlJob( $this->getTitle(), array(
@@ -226,5 +242,4 @@ class UploadFromUrl extends UploadBase {
return $sessionKey;
}
-
}
diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php
index 1765925d..9304ce5f 100644
--- a/includes/upload/UploadStash.php
+++ b/includes/upload/UploadStash.php
@@ -1,110 +1,190 @@
<?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 applications to find said files later, as long as the db table or temp files haven't been purged.
* - enable the uploading user (and *ONLY* the uploading user) to access said files, and thumbnails of said files, via a URL.
- * We accomplish this 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.
+ * We accomplish this using a database table, with ownership checking as you might expect. See SpecialUploadStash, which
+ * implements a web interface to some files stored this way.
*
+ * UploadStash represents the entire stash of temporary files.
+ * UploadStashFile is a filestore for the actual physical disk files.
+ * UploadFromStash extends UploadBase, and represents a single stashed file as it is moved from the stash to the regular file repository
*/
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*$/';
+ 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();
+ // When a given stashed file can't be loaded, wait for the slaves to catch up. If they're more than MAX_LAG
+ // behind, throw an exception instead. (at what point is broken better than slow?)
+ const MAX_LAG = 30;
- // TODO: Once UploadBase starts using this, switch to use these constants rather than UploadBase::SESSION*
- // const SESSION_VERSION = 2;
- // const SESSION_KEYNAME = 'wsUploadData';
+ // Age of the repository in hours. That is, after how long will files be assumed abandoned and deleted?
+ const REPO_AGE = 6;
/**
- * Represents the session which contains temporarily stored files.
- * Designed to be compatible with the session stashing code in UploadBase (should replace it eventually)
+ * repository that this uses to store temp files
+ * public because we sometimes need to get a LocalFile within the same repo.
*
- * @param $repo FileRepo: optional -- repo in which to store files. Will choose LocalRepo if not supplied.
+ * @var LocalRepo
*/
- public function __construct( $repo ) {
+ public $repo;
+
+ // array of initialized repo objects
+ protected $files = array();
+
+ // cache of the file metadata that's stored in the database
+ protected $fileMetadata = array();
+
+ // fileprops cache
+ protected $fileProps = array();
+
+ // current user
+ protected $user, $userId, $isLoggedIn;
+ /**
+ * Represents a temporary filestore, with metadata in the database.
+ * Designed to be compatible with the session stashing code in UploadBase (should replace it eventually)
+ *
+ * @param $repo FileRepo
+ */
+ public function __construct( $repo, $user = null ) {
// this might change based on wiki's configuration.
$this->repo = $repo;
- if ( ! isset( $_SESSION ) ) {
- throw new UploadStashNotAvailableException( 'no session variable' );
+ // if a user was passed, use it. otherwise, attempt to use the global.
+ // this keeps FileRepo from breaking when it creates an UploadStash object
+ if ( $user ) {
+ $this->user = $user;
+ } else {
+ global $wgUser;
+ $this->user = $wgUser;
}
- if ( !isset( $_SESSION[UploadBase::SESSION_KEYNAME] ) ) {
- $_SESSION[UploadBase::SESSION_KEYNAME] = array();
+ if ( is_object( $this->user ) ) {
+ $this->userId = $this->user->getId();
+ $this->isLoggedIn = $this->user->isLoggedIn();
}
-
}
/**
* 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
+ * @param $key String: key under which file information is stored
+ * @param $noAuth Boolean (optional) Don't check authentication. Used by maintenance scripts.
* @throws UploadStashFileNotFoundException
- * @throws UploadStashBadVersionException
+ * @throws UploadStashNotLoggedInException
+ * @throws UploadStashWrongOwnerException
+ * @throws UploadStashBadPathException
* @return UploadStashFile
*/
- public function getFile( $key ) {
+ public function getFile( $key, $noAuth = false ) {
+
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" );
+ }
+
+ if ( !$noAuth ) {
+ if ( !$this->isLoggedIn ) {
+ throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
+ }
+ }
+
+ $dbr = $this->repo->getSlaveDb();
+
+ if ( !isset( $this->fileMetadata[$key] ) ) {
+ // try this first. if it fails to find the row, check for lag, wait, try again. if its still missing, throw an exception.
+ // this more complex solution keeps things moving for page loads with many requests
+ // (ie. validating image ownership) when replag is high
+ if ( !$this->fetchFileMetadata( $key ) ) {
+ $lag = $dbr->getLag();
+ if ( $lag > 0 && $lag <= self::MAX_LAG ) {
+ // if there's not too much replication lag, just wait for the slave to catch up to our last insert.
+ sleep( ceil( $lag ) );
+ } elseif ( $lag > self::MAX_LAG ) {
+ // that's a lot of lag to introduce into the middle of the UI.
+ throw new UploadStashMaxLagExceededException(
+ 'Couldn\'t load stashed file metadata, and replication lag is above threshold: (MAX_LAG=' . self::MAX_LAG . ')'
+ );
+ }
+
+ // now that the waiting has happened, try again
+ $this->fetchFileMetadata( $key );
}
- $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 );
+ if ( !isset( $this->fileMetadata[$key] ) ) {
+ throw new UploadStashFileNotFoundException( "key '$key' not found in stash" );
}
-
- // 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" );
+
+ // create $this->files[$key]
+ $this->initFile( $key );
+
+ // fetch fileprops
+ $path = $this->fileMetadata[$key]['us_path'];
+ if ( $this->repo->isVirtualUrl( $path ) ) {
+ $path = $this->repo->resolveVirtualUrl( $path );
}
- $this->files[$key] = $file;
+ $this->fileProps[$key] = File::getPropsFromPath( $path );
+ }
+
+ if ( ! $this->files[$key]->exists() ) {
+ wfDebug( __METHOD__ . " tried to get file at $key, but it doesn't exist\n" );
+ throw new UploadStashBadPathException( "path doesn't exist" );
+ }
+ if ( !$noAuth ) {
+ if ( $this->fileMetadata[$key]['us_user'] != $this->userId ) {
+ throw new UploadStashWrongOwnerException( "This file ($key) doesn't belong to the current user." );
+ }
}
+
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.
+ * Getter for file metadata.
+ *
+ * @param key String: key under which file information is stored
+ * @return Array
+ */
+ public function getMetadata ( $key ) {
+ $this->getFile( $key );
+ return $this->fileMetadata[$key];
+ }
+
+ /**
+ * Getter for fileProps
+ *
+ * @param key String: key under which file information is stored
+ * @return Array
+ */
+ public function getFileProps ( $key ) {
+ $this->getFile( $key );
+ return $this->fileProps[$key];
+ }
+
+ /**
+ * Stash a file in a temp directory and record that we did this in the database, along with other metadata.
*
* @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
+ * @param $sourceType String: the type of upload that generated this file (currently, I believe, 'file' or null)
+ * @param $key String: optional, unique key for this file. Used for directory hashing when storing, otherwise not important
* @throws UploadStashBadPathException
* @throws UploadStashFileException
+ * @throws UploadStashNotLoggedInException
* @return UploadStashFile: file, or null on failure
*/
- public function stashFile( $path, $data = array(), $key = null ) {
+ public function stashFile( $path, $sourceType = null, $key = null ) {
if ( ! file_exists( $path ) ) {
- wfDebug( "UploadStash: tried to stash file at '$path', but it doesn't exist\n" );
+ wfDebug( __METHOD__ . " tried to stash file at '$path', but it doesn't exist\n" );
throw new UploadStashBadPathException( "path doesn't exist" );
}
- $fileProps = File::getPropsFromPath( $path );
+ $fileProps = File::getPropsFromPath( $path );
+ wfDebug( __METHOD__ . " stashing file at '$path'\n" );
// 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.
@@ -115,65 +195,251 @@ class UploadStash {
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 no key was supplied, make one. a mysql insertid would be totally reasonable here, except
+ // that some users of this function might expect to supply the key instead of using the generated one.
if ( is_null( $key ) ) {
- $key = $fileProps['sha1'] . "." . $extension;
+ // some things that when combined will make a suitably unique key.
+ // see: http://www.jwz.org/doc/mid.html
+ list ($usec, $sec) = explode( ' ', microtime() );
+ $usec = substr($usec, 2);
+ $key = wfBaseConvert( $sec . $usec, 10, 36 ) . '.' .
+ wfBaseConvert( mt_rand(), 10, 36 ) . '.'.
+ $this->userId . '.' .
+ $extension;
}
+ $this->fileProps[$key] = $fileProps;
+
if ( ! preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
throw new UploadStashBadPathException( "key '$key' is not in a proper format" );
- }
+ }
+ wfDebug( __METHOD__ . " key for '$path': $key\n" );
// if not already in a temporary area, put it there
- $status = $this->repo->storeTemp( basename( $path ), $path );
+ $storeStatus = $this->repo->storeTemp( basename( $path ), $path );
- if( ! $status->isOK() ) {
+ if ( ! $storeStatus->isOK() ) {
// It is a convention in MediaWiki to only return one error per API exception, even if multiple errors
// are available. We use reset() to pick the "first" thing that was wrong, preferring errors to warnings.
- // This is a bit lame, as we may have more info in the $status and we're throwing it away, but to fix it means
+ // This is a bit lame, as we may have more info in the $storeStatus and we're throwing it away, but to fix it means
// redesigning API errors significantly.
- // $status->value just contains the virtual URL (if anything) which is probably useless to the caller
- $error = reset( $status->getErrorsArray() );
+ // $storeStatus->value just contains the virtual URL (if anything) which is probably useless to the caller
+ $error = $storeStatus->getErrorsArray();
+ $error = reset( $error );
if ( ! count( $error ) ) {
- $error = reset( $status->getWarningsArray() );
+ $error = $storeStatus->getWarningsArray();
+ $error = reset( $error );
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
+ $stashPath = $storeStatus->value;
+
+ // fetch the current user ID
+ if ( !$this->isLoggedIn ) {
+ throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
+ }
+
+ // insert the file metadata into the db.
+ wfDebug( __METHOD__ . " inserting $stashPath under $key\n" );
+ $dbw = $this->repo->getMasterDb();
+
+ // select happens on the master so this can all be in a transaction, which
+ // avoids a race condition that's likely with multiple people uploading from the same
+ // set of files
+ $dbw->begin();
+ // first, check to see if it's already there.
+ $row = $dbw->selectRow(
+ 'uploadstash',
+ 'us_user, us_timestamp',
+ array( 'us_key' => $key ),
+ __METHOD__
+ );
+
+ // The current user can't have this key if:
+ // - the key is owned by someone else and
+ // - the age of the key is less than REPO_AGE
+ if ( is_object( $row ) ) {
+ if ( $row->us_user != $this->userId &&
+ $row->wfTimestamp( TS_UNIX, $row->us_timestamp ) > time() - UploadStash::REPO_AGE * 3600
+ ) {
+ $dbw->rollback();
+ throw new UploadStashWrongOwnerException( "Attempting to upload a duplicate of a file that someone else has stashed" );
+ }
+ }
+
+ $this->fileMetadata[$key] = array(
+ 'us_user' => $this->userId,
+ 'us_key' => $key,
+ 'us_orig_path' => $path,
+ 'us_path' => $stashPath,
+ 'us_size' => $fileProps['size'],
+ 'us_sha1' => $fileProps['sha1'],
+ 'us_mime' => $fileProps['mime'],
+ 'us_media_type' => $fileProps['media_type'],
+ 'us_image_width' => $fileProps['width'],
+ 'us_image_height' => $fileProps['height'],
+ 'us_image_bits' => $fileProps['bits'],
+ 'us_source_type' => $sourceType,
+ 'us_timestamp' => $dbw->timestamp(),
+ 'us_status' => 'finished'
);
- // 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 );
-
+ // if a row exists but previous checks on it passed, let the current user take over this key.
+ $dbw->replace(
+ 'uploadstash',
+ 'us_key',
+ $this->fileMetadata[$key],
+ __METHOD__
+ );
+ $dbw->commit();
+
+ // store the insertid in the class variable so immediate retrieval (possibly laggy) isn't necesary.
+ $this->fileMetadata[$key]['us_id'] = $dbw->insertId();
+
+ # create the UploadStashFile object for this file.
+ $this->initFile( $key );
+
return $this->getFile( $key );
}
/**
+ * Remove all files from the stash.
+ * Does not clean up files in the repo, just the record of them.
+ *
+ * @throws UploadStashNotLoggedInException
+ * @return boolean: success
+ */
+ public function clear() {
+ if ( !$this->isLoggedIn ) {
+ throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
+ }
+
+ wfDebug( __METHOD__ . " clearing all rows for user $userId\n" );
+ $dbw = $this->repo->getMasterDb();
+ $dbw->delete(
+ 'uploadstash',
+ array( 'us_user' => $this->userId ),
+ __METHOD__
+ );
+
+ # destroy objects.
+ $this->files = array();
+ $this->fileMetadata = array();
+
+ return true;
+ }
+
+ /**
+ * Remove a particular file from the stash. Also removes it from the repo.
+ *
+ * @throws UploadStashNotLoggedInException
+ * @throws UploadStashWrongOwnerException
+ * @return boolean: success
+ */
+ public function removeFile( $key ) {
+ if ( !$this->isLoggedIn ) {
+ throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
+ }
+
+ $dbw = $this->repo->getMasterDb();
+
+ // this is a cheap query. it runs on the master so that this function still works when there's lag.
+ // it won't be called all that often.
+ $row = $dbw->selectRow(
+ 'uploadstash',
+ 'us_user',
+ array( 'us_key' => $key ),
+ __METHOD__
+ );
+
+ if( !$row ) {
+ throw new UploadStashNoSuchKeyException( "No such key ($key), cannot remove" );
+ }
+
+ if ( $row->us_user != $this->userId ) {
+ throw new UploadStashWrongOwnerException( "Can't delete: the file ($key) doesn't belong to this user." );
+ }
+
+ return $this->removeFileNoAuth( $key );
+ }
+
+
+ /**
+ * Remove a file (see removeFile), but doesn't check ownership first.
+ *
+ * @return boolean: success
+ */
+ public function removeFileNoAuth( $key ) {
+ wfDebug( __METHOD__ . " clearing row $key\n" );
+
+ $dbw = $this->repo->getMasterDb();
+
+ // this gets its own transaction since it's called serially by the cleanupUploadStash maintenance script
+ $dbw->begin();
+ $dbw->delete(
+ 'uploadstash',
+ array( 'us_key' => $key ),
+ __METHOD__
+ );
+ $dbw->commit();
+
+ // TODO: look into UnregisteredLocalFile and find out why the rv here is sometimes wrong (false when file was removed)
+ // for now, ignore.
+ $this->files[$key]->remove();
+
+ unset( $this->files[$key] );
+ unset( $this->fileMetadata[$key] );
+
+ return true;
+ }
+
+ /**
+ * List all files in the stash.
+ *
+ * @throws UploadStashNotLoggedInException
+ * @return Array
+ */
+ public function listFiles() {
+ if ( !$this->isLoggedIn ) {
+ throw new UploadStashNotLoggedInException( __METHOD__ . ' No user is logged in, files must belong to users' );
+ }
+
+ $dbr = $this->repo->getSlaveDb();
+ $res = $dbr->select(
+ 'uploadstash',
+ 'us_key',
+ array( 'us_key' => $key ),
+ __METHOD__
+ );
+
+ if ( !is_object( $res ) || $res->numRows() == 0 ) {
+ // nothing to do.
+ return false;
+ }
+
+ // finish the read before starting writes.
+ $keys = array();
+ foreach ( $res as $row ) {
+ array_push( $keys, $row->us_key );
+ }
+
+ return $keys;
+ }
+
+ /**
* 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
+ * 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
+ * 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 ) {
+ public static function getExtensionForPath( $path ) {
// Does this have an extension?
$n = strrpos( $path, '.' );
$extension = null;
@@ -184,8 +450,8 @@ class UploadStash {
$magic = MimeMagic::singleton();
$mimeType = $magic->guessMimeType( $path );
$extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) );
- if ( count( $extensions ) ) {
- $extension = $extensions[0];
+ if ( count( $extensions ) ) {
+ $extension = $extensions[0];
}
}
@@ -196,52 +462,103 @@ class UploadStash {
return File::normalizeExtension( $extension );
}
+ /**
+ * Helper function: do the actual database query to fetch file metadata.
+ *
+ * @param $key String: key
+ * @return boolean
+ */
+ protected function fetchFileMetadata( $key ) {
+ // populate $fileMetadata[$key]
+ $dbr = $this->repo->getSlaveDb();
+ $row = $dbr->selectRow(
+ 'uploadstash',
+ '*',
+ array( 'us_key' => $key ),
+ __METHOD__
+ );
+
+ if ( !is_object( $row ) ) {
+ // key wasn't present in the database. this will happen sometimes.
+ return false;
+ }
+
+ $this->fileMetadata[$key] = array(
+ 'us_user' => $row->us_user,
+ 'us_key' => $row->us_key,
+ 'us_orig_path' => $row->us_orig_path,
+ 'us_path' => $row->us_path,
+ 'us_size' => $row->us_size,
+ 'us_sha1' => $row->us_sha1,
+ 'us_mime' => $row->us_mime,
+ 'us_media_type' => $row->us_media_type,
+ 'us_image_width' => $row->us_image_width,
+ 'us_image_height' => $row->us_image_height,
+ 'us_image_bits' => $row->us_image_bits,
+ 'us_source_type' => $row->us_source_type,
+ 'us_timestamp' => $row->us_timestamp,
+ 'us_status' => $row->us_status
+ );
+
+ return true;
+ }
+
+ /**
+ * Helper function: Initialize the UploadStashFile for a given file.
+ *
+ * @param $path String: path to file
+ * @param $key String: key under which to store the object
+ * @throws UploadStashZeroLengthFileException
+ * @return bool
+ */
+ protected function initFile( $key ) {
+ $file = new UploadStashFile( $this->repo, $this->fileMetadata[$key]['us_path'], $key );
+ if ( $file->getSize() === 0 ) {
+ throw new UploadStashZeroLengthFileException( "File is zero length" );
+ }
+ $this->files[$key] = $file;
+ return true;
+ }
}
class UploadStashFile extends UnregisteredLocalFile {
- private $sessionStash;
- private $sessionKey;
- private $sessionData;
+ private $fileKey;
private $urlName;
+ protected $url;
/**
* A LocalFile wrapper around a file that has been temporarily stashed, so we can do things like create thumbnails for it
* Arguably UnregisteredLocalFile should be handling its own file repo but that class is a bit retarded currently
*
- * @param $stash UploadStash: useful for obtaining config, stashing transformed files
- * @param $repo FileRepo: repository where we should find the path
+ * @param $repo FSRepo: 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;
+ public function __construct( $repo, $path, $key ) {
+ $this->fileKey = $key;
// resolve mwrepo:// urls
if ( $repo->isVirtualUrl( $path ) ) {
- $path = $repo->resolveVirtualUrl( $path );
- }
+ $path = $repo->resolveVirtualUrl( $path );
+ } else {
- // check if path appears to be sane, no parent traversals, and is in this repo's temp zone.
- $repoTempPath = $repo->getZonePath( 'temp' );
- if ( ( ! $repo->validateFilename( $path ) ) ||
- ( strpos( $path, $repoTempPath ) !== 0 ) ) {
- wfDebug( "UploadStash: tried to construct an UploadStashFile from a file that should already exist at '$path', but path is not valid\n" );
- throw new UploadStashBadPathException( 'path is not valid' );
- }
+ // 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' );
+ // 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 );
@@ -261,13 +578,13 @@ class UploadStashFile extends UnregisteredLocalFile {
/**
* 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
+ * 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 ) {
+ public function getThumbPath( $thumbName = false ) {
$path = dirname( $this->path );
if ( $thumbName !== false ) {
$path .= "/$thumbName";
@@ -276,71 +593,49 @@ class UploadStashFile extends UnregisteredLocalFile {
}
/**
- * Return the file/url base name of a thumbnail with the specified parameters
+ * Return the file/url base name of a thumbnail with the specified parameters.
+ * We override this because we want to use the pretty url name instead of the
+ * ugly file name.
*
* @param $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;
+ return $this->generateThumbName( $this->getUrlName(), $params );
}
/**
* Helper function -- given a 'subpage', return the local URL e.g. /wiki/Special:UploadStash/subpage
* @param {String} $subPage
- * @return {String} local URL for this subpage in the Special:UploadStash space.
+ * @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
+ /**
+ * 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)
+ * (that's hidden in the db)
*
* @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 ) {
+ 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() {
+ public function getUrlName() {
if ( ! $this->urlName ) {
- $this->urlName = $this->sessionKey;
+ $this->urlName = $this->fileKey;
}
return $this->urlName;
}
@@ -359,23 +654,22 @@ class UploadStashFile extends UnregisteredLocalFile {
}
/**
- * Parent classes use this method, for no obvious reason, to return the path (relative to wiki root, I assume).
+ * 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() {
+ 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)
+ * Getter for file key (the unique id by which this file's location & metadata is stored in the db)
*
- * @return String: session key
+ * @return String: file key
*/
- public function getSessionKey() {
- return $this->sessionKey;
+ public function getFileKey() {
+ return $this->fileKey;
}
/**
@@ -383,15 +677,26 @@ class UploadStashFile extends UnregisteredLocalFile {
* @return Status: success
*/
public function remove() {
+ if ( !$this->repo->fileExists( $this->path, FileRepo::FILES_ONLY ) ) {
+ // Maybe the file's already been removed? This could totally happen in UploadBase.
+ return true;
+ }
+
return $this->repo->freeTemp( $this->path );
}
+ public function exists() {
+ return $this->repo->fileExists( $this->path, FileRepo::FILES_ONLY );
+ }
+
}
class UploadStashNotAvailableException extends MWException {};
class UploadStashFileNotFoundException extends MWException {};
class UploadStashBadPathException extends MWException {};
-class UploadStashBadVersionException extends MWException {};
class UploadStashFileException extends MWException {};
class UploadStashZeroLengthFileException extends MWException {};
-
+class UploadStashNotLoggedInException extends MWException {};
+class UploadStashWrongOwnerException extends MWException {};
+class UploadStashMaxLagExceededException extends MWException {};
+class UploadStashNoSuchKeyException extends MWException {};
diff --git a/includes/zhtable/Makefile.py b/includes/zhtable/Makefile.py
index a7822b0b..305422bd 100644
--- a/includes/zhtable/Makefile.py
+++ b/includes/zhtable/Makefile.py
@@ -1,5 +1,5 @@
#!/usr/bin/python
-# -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
# @author Philip
import tarfile as tf
import zipfile as zf
@@ -30,8 +30,9 @@ def unichr3( *args ):
return [unichr( int( i[2:7], 16 ) ) for i in args if i[2:7]]
# DEFINE
-SF_MIRROR = 'easynews'
-SCIM_TABLES_VER = '0.5.9'
+UNIHAN_VER = '5.2.0'
+SF_MIRROR = 'cdnetworks-kr-2'
+SCIM_TABLES_VER = '0.5.10'
SCIM_PINYIN_VER = '0.5.91'
LIBTABE_VER = '0.2.3'
# END OF DEFINE
@@ -44,7 +45,7 @@ def download( url, dest ):
if islinux:
# we use wget instead urlretrieve under Linux,
# because wget could display details like download progress
- os.system('wget %s' % url)
+ os.system( 'wget %s -O %s' % ( url, dest ) )
else:
print( 'Downloading from [%s] ...' % url )
urllib_request.urlretrieve( url, dest )
@@ -81,7 +82,8 @@ def parserCore( fp, pos, beginmark = None, endmark = None ):
elems = line.split()
if len( elems ) < 2:
continue
- elif len( elems[0] ) > 1:
+ elif len( elems[0] ) > 1 and \
+ len( elems[pos] ) > 1: # words only
mlist.add( elems[pos] )
return mlist
@@ -258,7 +260,7 @@ def PHPArray( table ):
def main():
#Get Unihan.zip:
- url = 'http://www.unicode.org/Public/UNIDATA/Unihan.zip'
+ url = 'http://www.unicode.org/Public/%s/ucd/Unihan.zip' % UNIHAN_VER
han_dest = 'Unihan.zip'
download( url, han_dest )
diff --git a/includes/zhtable/simp2trad_noconvert.manual b/includes/zhtable/simp2trad_noconvert.manual
index 5ad656b3..a46560a7 100644
--- a/includes/zhtable/simp2trad_noconvert.manual
+++ b/includes/zhtable/simp2trad_noconvert.manual
@@ -1,4 +1,139 @@
-余
+=>"余"
+=>"𫗭"
+=>"𪨧"
+=>"𫚭"
+=>"𫔀"
+=>"𫊻"
+=>"𫋌"
+=>"蚃"
+=>"𩾂"
+=>"𫚜"
+=>"𫚢"
+=>"𧉰"
+=>"䙌"
+=>"𫊮"
+=>"𫋇"
+=>"𫉄"
+=>"𫘛"
+=>"𫘜"
+=>"𫘝"
+=>"𫘟"
+=>"𩧨"
+=>"𩧫"
+=>"𫘞"
+=>"𫘠"
+=>"𩧲"
+=>"𩧴"
+=>"𫘡"
+=>"𩧺"
+=>"𫘣"
+=>"𫘤"
+=>"𫘧"
+=>"𫘥"
+=>"𫘦"
+=>"𩨀"
+=>"𩨊"
+=>"𫘩"
+=>"𩨃"
+=>"𫘪"
+=>"𫘪"
+=>"𫘫"
+=>"𫘬"
+=>"𩨈"
+=>"𫘨"
+=>"𩨄"
+=>"𫘭"
+=>"𩧯"
+=>"𫘯"
+=>"𫘰"
+=>"𫘱"
+=>"𫘽"
+=>"𫚉"
+=>"𩽹"
+=>"𫚌"
+=>"𫚍"
+=>"𫚒"
+=>"𫚑"
+=>"𫚖"
+=>"𩽾"
+=>"䲟"
+=>"𫚓"
+=>"𫚗"
+=>"𫚔"
+=>"𫚛"
+=>"𩾃"
+=>"𫚚"
+=>"𩾁"
+=>"𫚙"
+=>"𫚡"
+=>"𫚞"
+=>"𩾇"
+=>"𩽼"
+=>"𫚣"
+=>"䲠"
+=>"䲡"
+=>"𫚊"
+=>"𫚥"
+=>"𫚕"
+=>"𫚤"
+=>"䲢"
+=>"𫚦"
+=>"𫚧"
+=>"𫚋"
+=>"𩾌"
+=>"𫚪"
+=>"𫚫"
+=>"𫚈"
+=>"𫚭"
+=>"𫛛"
+=>"𪉃"
+=>"𫛚"
+=>"𫛜"
+=>"𫛞"
+=>"𫛝"
+=>"𫛤"
+=>"𫛡"
+=>"𫁡"
+=>"𪉈"
+=>"𫛣"
+=>"𫛦"
+=>"𪉆"
+=>"𫛩"
+=>"𫛪"
+=>"𫛥"
+=>"𪉍"
+=>"𫛭"
+=>"𫛨"
+=>"𫛳"
+=>"𫛱"
+=>"𫛲"
+=>"𫛵"
+=>"𫛶"
+=>"𫛸"
+=>"𫛷"
+=>"𫛯"
+=>"𫛫"
+=>"𫛽"
+=>"𫜀"
+=>"𪉑"
+=>"𫜃"
+=>"𫛴"
+=>"𪉊"
+=>"𫜁"
+=>"𫜄"
+=>"𫛢"
+=>"𫛟"
+=>"𪎊"
+=>"𤿲"
+=>"𪎉"
+=>"𪎌"
+=>"𫜑"
+=>"𫜩"
+=>"𫜪"
+=>"𫜭"
+=>"𫜬"
+=>"𫜮"
+=>"𫜰"
diff --git a/includes/zhtable/simpphrases.manual b/includes/zhtable/simpphrases.manual
index 4b699e26..d8602fec 100644
--- a/includes/zhtable/simpphrases.manual
+++ b/includes/zhtable/simpphrases.manual
@@ -2169,6 +2169,7 @@
嗅得着
嗅不着
嗅着
+警戒着
於乎
於戏
魏徵
@@ -2232,4 +2233,7 @@
苧烯
近角聪信
米泽瑠美
-峯岸南 \ No newline at end of file
+峯岸南
+僧伽吒
+王道乾
+後姓
diff --git a/includes/zhtable/toCN.manual b/includes/zhtable/toCN.manual
index 54e95765..41680d1f 100644
--- a/includes/zhtable/toCN.manual
+++ b/includes/zhtable/toCN.manual
@@ -54,6 +54,7 @@
軟碟機 软驱
快閃記憶體 快闪存储器
滑鼠 鼠标
+滑鼠蛇 滑鼠蛇
二進位 二进制
滿二進位 满二进位
六進位 六进制
@@ -77,7 +78,6 @@
資訊理論 信息论
迴圈 循环
防寫 写保护
-分散式 分布式
解析度 分辨率
伺服器 服务器
等於 等于
@@ -86,14 +86,11 @@
掃瞄器 扫瞄仪
寬頻 宽带
資料庫 数据库
-鉅賈 巨商
萬曆 万历
永曆 永历
辭彙 词汇
母音 元音
-自由球 任意球
-自由球员 自由球员
-自由球員 自由球员
+字母 字母
頭槌 头球
進球 入球
顆進球 粒入球
@@ -232,8 +229,6 @@
华乐街 华乐街
屋价 房价
計程車 出租车
-公車 公共汽车
-公車上書 公车上书
單車 自行车
節慶 节日
芝士 乾酪
diff --git a/includes/zhtable/toHK.manual b/includes/zhtable/toHK.manual
index 53b354c7..2ebb7504 100644
--- a/includes/zhtable/toHK.manual
+++ b/includes/zhtable/toHK.manual
@@ -133,7 +133,6 @@
马自达 萬事得
馬自達 萬事得
寶獅 標致
-拿破崙 拿破侖
布什 布殊
布希 布殊
布希亞 布希亞
@@ -2123,6 +2122,7 @@
嗅得著 嗅得着
嗅不著 嗅不着
嗅著 嗅着
+警戒著 警戒着
榴莲 榴槤
榴蓮 榴槤
发布 發佈
@@ -2239,4 +2239,4 @@
分布 分佈
分布于 分佈於
分布於 分佈於
-想象 想像 \ No newline at end of file
+想象 想像
diff --git a/includes/zhtable/toSimp.manual b/includes/zhtable/toSimp.manual
index da04b82e..739d04c3 100644
--- a/includes/zhtable/toSimp.manual
+++ b/includes/zhtable/toSimp.manual
@@ -159,4 +159,7 @@
近角聪信 近角聪信
修鍊 修炼
米泽瑠美 米泽瑠美
-太閤 太阁
+太閤 太阁
+候覆 候复
+待覆 待复
+批覆 批复
diff --git a/includes/zhtable/toTW.manual b/includes/zhtable/toTW.manual
index a638e86b..35b62689 100644
--- a/includes/zhtable/toTW.manual
+++ b/includes/zhtable/toTW.manual
@@ -86,6 +86,7 @@
远程控制 遠程控制
遠程控制 遠程控制
行程控制 行程控制
+流程控制 流程控制
端口 埠
算子 運算元
算法 演算法
@@ -105,7 +106,6 @@
便携式 攜帶型
信息论 資訊理論
写保护 防寫
-分布式 分散式
分辨率 解析度
服务器 伺服器
等于 等於
@@ -114,7 +114,6 @@
宽带 寬頻
数据库 資料庫
奶酪 乳酪
-巨商 鉅賈
手电 手電筒
手电筒 手電筒
万历 萬曆
@@ -125,9 +124,6 @@
新纪元 新紀元
新紀元 新紀元
宋元 宋元
-任意球 自由球
-任意球员 任意球員
-任意球員 任意球員
头球 頭槌
入球 進球
粒入球 顆進球
@@ -410,3 +406,5 @@
村子裏 村子裡
青霉素 青黴素
想象 想像
+锎 鉲
+信道 信道
diff --git a/includes/zhtable/toTrad.manual b/includes/zhtable/toTrad.manual
index 0c79178f..b0efd28e 100644
--- a/includes/zhtable/toTrad.manual
+++ b/includes/zhtable/toTrad.manual
@@ -179,4 +179,8 @@
黎吉雲 黎吉雲
于飛島 于飛島
鄉愿 鄉愿
-奇迹 奇蹟
+奇迹 奇蹟
+候复 候覆
+待复 待覆
+批复 批覆
+划槳 划槳
diff --git a/includes/zhtable/trad2simp.manual b/includes/zhtable/trad2simp.manual
index 6cbc3ee5..692c74b5 100644
--- a/includes/zhtable/trad2simp.manual
+++ b/includes/zhtable/trad2simp.manual
@@ -1,26 +1,3 @@
-U+03473㑳|U+03447㑇|
-U+04308䌈|U+26216𦈖|
-U+04A8F䪏|U+293FC𩏼|
-U+04A97䪗|U+29400𩐀|
-U+04A98䪘|U+293FF𩏿|
-U+04AF4䫴|U+29597𩖗|
-U+04B18䬘|U+2966E𩙮|
-U+04B1D䬝|U+2966F𩙯|
-U+04B40䭀|U+29807𩠇|
-U+04B43䭃|U+29808𩠈|
-U+04B7F䭿|U+299ED𩧭|
-U+04B9D䮝|U+299F0𩧰|
-U+04B9E䮞|U+29A01𩨁|
-U+04BA0䮠|U+299FF𩧿|
-U+04BB3䮳|U+29A0F𩨏|
-U+04BBE䮾|U+299EA𩧪|
-U+04C59䱙|U+29F88𩾈|
-U+04C6C䱬|U+29F8A𩾊|
-U+04C70䱰|U+29F8B𩾋|
-U+04C77䱷|U+04CA3䲣|
-U+04CB0䲰|U+2A242𪉂|
-U+04D2C䴬|U+2A388𪎈|
-U+04D34䴴|U+2A38B𪎋|
U+04E99亙|U+04E98亘|
U+04F48佈|U+05E03布|
U+04F48佈|U+05E03布|
@@ -35,7 +12,6 @@ U+052F3勳|U+052CB勋|
U+0537D卽|U+05373即|
U+053A4厤|U+05386历|
U+055AB喫|U+05403吃|
-U+055F0嗰|U+20BB6𠮶|
U+05641噁|U+06076恶|
U+05690嚐|U+05C1D尝|
U+056A5嚥|U+054BD咽|
@@ -140,36 +116,12 @@ U+0904A遊|U+06E38游|
U+09061遡|U+06EAF溯|
U+091A3醣|U+07CD6糖|
U+091AF醯|U+09170酰|
-U+091F3釳|U+28C3F𨰿|
-U+091FE釾|U+0497A䥺|
-U+0920B鈋|U+28C42𨱂|
-U+09220鈠|U+28C41𨱁|
-U+0922F鈯|U+28C44𨱄|
-U+09232鈲|U+28C43𨱃|
-U+09241鉁|U+28C45𨱅|
-U+092B6銶|U+28C47𨱇|
-U+092C9鋉|U+28C48𨱈|
-U+09302錂|U+28C4B𨱋|
-U+09344鍄|U+28C49𨱉|
U+0934A鍊|U+070BC炼|U+094FE链|
-U+0936E鍮|U+28C4E𨱎|
U+0938C鎌|U+09570镰|
-U+0939D鎝|U+28C4F𨱏|
U+093AD鎭|U+093AE镇|
-U+093AF鎯|U+28C4D𨱍|
-U+093B7鎷|U+28C3E𨰾|
-U+093C6鏆|U+28C4C𨱌|
-U+093C9鏉|U+28C52𨱒|
U+093DA鏚|U+0621A戚|
-U+093FA鏺|U+0497D䥽|
-U+0940E鐎|U+28C53𨱓|
-U+0940F鐏|U+28C54𨱔|
-U+09425鐥|U+04985䦅|
-U+0942F鐯|U+04983䦃|
U+09451鑑|U+09274鉴|
U+0955F镟|U+065CB旋|
-U+0958D閍|U+28E02𨸂|
-U+09590閐|U+28E03𨸃|
U+09592閒|U+095F2闲|
U+095A4閤|U+05408合|
U+095E2闢|U+08F9F辟|
@@ -178,53 +130,17 @@ U+0965E陞|U+05347升|
U+097A6鞦|U+079CB秋|U+097A7鞧|
U+097C6韆|U+05343千|
U+097DD韝|U+097B2鞲|
-U+09843顃|U+29596𩖖|
-U+098B0颰|U+29665𩙥|
-U+098B7颷|U+2966A𩙪|
-U+098BE颾|U+2966B𩙫|
+U+09858願|U+0613F愿|
U+098F1飱|U+098E7飧|
U+09918餘|U+04F59余|U+09980馀|
+U+09931餱|U+07CC7糇|
U+09935餵|U+05582喂|
-U+09938餸|U+2980C𩠌|
-U+099CE駎|U+299E8𩧨|
-U+099DA駚|U+299EB𩧫|
-U+099E7駧|U+299F2𩧲|
-U+099E9駩|U+299F4𩧴|
-U+099F6駶|U+299FA𩧺|
-U+09A14騔|U+29A00𩨀|
-U+09A1A騚|U+29A0A𩨊|
-U+09A1D騝|U+29A03𩨃|
-U+09A1F騟|U+29A08𩨈|
-U+09A2A騪|U+29A04𩨄|
-U+09A4B驋|U+299EF𩧯|
U+09B28鬨|U+054C4哄|
-U+09B65魥|U+29F79𩽹|
-U+09B95鮕|U+29F80𩾀|
-U+09B9F鮟|U+29F7E𩽾|
-U+09BA3鮣|U+04C9F䲟|
-U+09BB8鮸|U+29F83𩾃|
-U+09BC4鯄|U+29F81𩾁|
-U+09BF1鯱|U+29F87𩾇|
-U+09BF6鯶|U+29F7C𩽼|
-U+09C06鰆|U+04CA0䲠|
-U+09C0C鰌|U+04CA1䲡|
-U+09C27鰧|U+04CA2䲢|
-U+09C47鱇|U+29F8C𩾌|
-U+09C47鱇|U+29F8C𩾌|
-U+09CFC鳼|U+2A243𪉃|
-U+09D1C鴜|U+2A248𪉈|
-U+09D32鴲|U+2A246𪉆|
-U+09D5A鵚|U+2A24D𪉍|
U+09D70鵰|U+096D5雕|U+05F6B彫|
-U+09DD4鷔|U+2A251𪉑|
-U+09DE8鷨|U+2A24A𪉊|
U+09E7C鹼|U+078B1碱|U+07877硷|
-U+09EA8麨|U+2A38A𪎊|
U+09EAA麪|U+09762面|
U+09EAB麫|U+09762面|
U+09EAF麯|U+066F2曲|
-U+09EB2麲|U+2A389𪎉|
-U+09EB3麳|U+2A38C𪎌|
U+09EB4麴|U+066F2曲|U+09EB4麴|
U+09EF4黴|U+09709霉|
U+09F15鼕|U+051AC冬|
@@ -232,60 +148,4 @@ U+09F47齇|U+09F44齄|
U+09F63齣|U+051FA出|
U+09F91龑|U+04DAE䶮|
U+21ED5𡻕|U+05C81岁|
-U+26A99𦪙|U+0447D䑽|
-U+2895B𨥛|U+28C40𨱀|
-U+289F1𨧱|U+28C4A𨱊|
-U+28AD2𨫒|U+28C50𨱐|
-U+28B82𨮂|U+28C55𨱕|
-U+293A2𩎢|U+293FE𩏾|
-U+293EA𩏪|U+293FD𩏽|
-U+294E3𩓣|U+29595𩖕|
-U+295C0𩗀|U+29666𩙦|
-U+295E1𩗡|U+29667𩙧|
-U+29600𩘀|U+29669𩙩|
-U+2961D𩘝|U+2966D𩙭|
-U+29639𩘹|U+29668𩙨|
-U+2963A𩘺|U+2966C𩙬|
-U+29648𩙈|U+29670𩙰|
-U+29726𩜦|U+29806𩠆|
-U+29754𩝔|U+2980B𩠋|
-U+297AF𩞯|U+04B6A䭪|
-U+297D0𩟐|U+29805𩠅|
-U+2987A𩡺|U+299E6𩧦|
-U+298A1𩢡|U+299EC𩧬|
-U+298B4𩢴|U+299F5𩧵|
-U+298B8𩢸|U+299F3𩧳|
-U+298BE𩢾|U+299EE𩧮|
-U+298CF𩣏|U+299F6𩧶|
U+298F5𩣵|U+299FB𩧻|
-U+298FA𩣺|U+299FC𩧼|
-U+2990A𩤊|U+299E9𩧩|
-U+29919𩤙|U+29A06𩨆|
-U+29932𩤲|U+29A09𩨉|
-U+29938𩤸|U+29A05𩨅|
-U+29944𩥄|U+29A0B𩨋|
-U+29947𩥇|U+29A0D𩨍|
-U+29949𩥉|U+299F1𩧱|
-U+29951𩥑|U+29A0C𩨌|
-U+299C6𩧆|U+29A10𩨐|
-U+29D69𩵩|U+29F7A𩽺|
-U+29D79𩵹|U+29F7B𩽻|
-U+29DB0𩶰|U+29F7F𩽿|
-U+29DB1𩶱|U+29F7D𩽽|
-U+29DF0𩷰|U+29F84𩾄|
-U+29E03𩸃|U+29F85𩾅|
-U+29E26𩸦|U+29F86𩾆|
-U+29F47𩽇|U+29F8E𩾎|
-U+29FEA𩿪|U+2A244𪉄|
-U+2A026𪀦|U+2A245𪉅|
-U+2A03E𪀾|U+2A24B𪉋|
-U+2A048𪁈|U+2A249𪉉|
-U+2A056𪁖|U+2A24C𪉌|
-U+2A086𪂆|U+2A24E𪉎|
-U+2A0CD𪃍|U+2A250𪉐|
-U+2A0CF𪃏|U+2A24F𪉏|
-U+2A106𪄆|U+2A254𪉔|
-U+2A115𪄕|U+2A252𪉒|
-U+2A1F3𪇳|U+2A255𪉕|
-U+2A600𪘀|U+2A68F𪚏|
-U+2A62F𪘯|U+2A690𪚐| \ No newline at end of file
diff --git a/includes/zhtable/trad2simp_noconvert.manual b/includes/zhtable/trad2simp_noconvert.manual
index 6efab3d4..052bab69 100644
--- a/includes/zhtable/trad2simp_noconvert.manual
+++ b/includes/zhtable/trad2simp_noconvert.manual
@@ -2,3 +2,4 @@
=>"獃"
+𫚭
diff --git a/includes/zhtable/tradphrases.manual b/includes/zhtable/tradphrases.manual
index 5a832a60..ee3bc69f 100644
--- a/includes/zhtable/tradphrases.manual
+++ b/includes/zhtable/tradphrases.manual
@@ -66,6 +66,20 @@
千多隻
萬多隻
億多隻
+這只能
+這只可
+這只在
+這只是
+這只需
+這只會
+這只用
+那只能
+那只可
+那只在
+那只是
+那只需
+那只會
+那只用
多只能
多只可
多只在
@@ -917,6 +931,7 @@
國歷任
國歷屆
國歷經
+國歷來
新歷史
夏歷史
百花曆
@@ -1299,6 +1314,7 @@
府干卿
一干人
未乾
+未干涉
抹乾
餅乾
拭乾
@@ -2294,6 +2310,7 @@
多餘
剩餘
餘生
+餘歡
有餘
一餘
二餘
@@ -3381,7 +3398,6 @@
球杆
腊之以為餌
腊毒
-草广
蜡月
蜡祭
言云
@@ -3776,6 +3792,7 @@
于吉
于堅
于姓
+于氏
于娜
于娟
于山
@@ -4010,6 +4027,7 @@
染殿后
准三后
風后
+后母戊
風後,
人如風後入江雲
中風後
@@ -4024,6 +4042,7 @@
順風後
大風後
馬格里布
+伊里布
劃入
中庄子
埔裏社撫墾局
@@ -4257,3 +4276,32 @@
一頭長髮
的長髮
黑色長髮
+前天
+昨天
+今天
+明天
+後天
+數學家
+科學家
+物理學家
+化學家
+生物學家
+天文學家
+游離
+子晳
+紅后假說
+書面
+不只
+高涌泉
+請求
+考試
+測試
+筆試
+口試
+冰冷
+王田里
+后姓
+台州
+田庄英雄
+計劃
+抑制劑
diff --git a/includes/zhtable/tradphrases_exclude.manual b/includes/zhtable/tradphrases_exclude.manual
index 6ed245c3..e6abb4e1 100644
--- a/includes/zhtable/tradphrases_exclude.manual
+++ b/includes/zhtable/tradphrases_exclude.manual
@@ -327,3 +327,4 @@
好家伙
姦污
併發
+衚衕