summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/Action.php140
-rw-r--r--includes/AjaxResponse.php14
-rw-r--r--includes/Article.php1329
-rw-r--r--includes/AuthPlugin.php2
-rw-r--r--includes/AutoLoader.php158
-rw-r--r--includes/Autopromote.php4
-rw-r--r--includes/BacklinkCache.php17
-rw-r--r--includes/Block.php83
-rw-r--r--includes/CategoryPage.php684
-rw-r--r--includes/CategoryViewer.php677
-rw-r--r--includes/Categoryfinder.php4
-rw-r--r--includes/Cdb.php7
-rw-r--r--includes/Cdb_PHP.php69
-rw-r--r--includes/ChangeTags.php63
-rw-r--r--includes/ChangesFeed.php5
-rw-r--r--includes/ChangesList.php291
-rw-r--r--includes/Cookie.php23
-rw-r--r--includes/DefaultSettings.php636
-rw-r--r--includes/DeferredUpdates.php89
-rw-r--r--includes/Defines.php9
-rw-r--r--includes/EditPage.php2088
-rw-r--r--includes/Exception.php172
-rw-r--r--includes/Export.php37
-rw-r--r--includes/ExternalEdit.php104
-rw-r--r--includes/ExternalStoreDB.php6
-rw-r--r--includes/FakeTitle.php17
-rw-r--r--includes/Fallback.php53
-rw-r--r--includes/Feed.php92
-rw-r--r--includes/FeedUtils.php151
-rw-r--r--includes/FileDeleteForm.php134
-rw-r--r--includes/ForkController.php6
-rw-r--r--includes/FormOptions.php4
-rw-r--r--includes/GlobalFunctions.php1071
-rw-r--r--includes/HTMLForm.php289
-rw-r--r--includes/Hooks.php6
-rw-r--r--includes/Html.php194
-rw-r--r--includes/HttpFunctions.old.php1
-rw-r--r--includes/HttpFunctions.php89
-rw-r--r--includes/IP.php30
-rw-r--r--includes/ImageFunctions.php37
-rw-r--r--includes/ImageGallery.php15
-rw-r--r--includes/ImagePage.php133
-rw-r--r--includes/Import.php365
-rw-r--r--includes/Init.php49
-rw-r--r--includes/Licenses.php4
-rw-r--r--includes/Linker.php262
-rw-r--r--includes/LinksUpdate.php180
-rw-r--r--includes/LocalisationCache.php251
-rw-r--r--includes/MWFunction.php19
-rw-r--r--includes/MagicWord.php54
-rw-r--r--includes/Message.php205
-rw-r--r--includes/Metadata.php13
-rw-r--r--includes/MimeMagic.php4
-rw-r--r--includes/Namespace.php66
-rw-r--r--includes/OutputPage.php847
-rw-r--r--includes/PHPVersionError.php8
-rw-r--r--includes/PageQueryPage.php2
-rw-r--r--includes/Pager.php211
-rw-r--r--includes/PathRouter.php351
-rw-r--r--includes/PatrolLog.php88
-rw-r--r--includes/PoolCounter.php60
-rw-r--r--includes/Preferences.php568
-rw-r--r--includes/ProtectionForm.php93
-rw-r--r--includes/ProxyTools.php117
-rw-r--r--includes/QueryPage.php106
-rw-r--r--includes/RawPage.php228
-rw-r--r--includes/RecentChange.php180
-rw-r--r--includes/RequestContext.php424
-rw-r--r--includes/Revision.php278
-rw-r--r--includes/RevisionList.php70
-rw-r--r--includes/Sanitizer.php149
-rw-r--r--includes/SeleniumWebSettings.php18
-rw-r--r--includes/Setup.php69
-rw-r--r--includes/SiteConfiguration.php10
-rw-r--r--includes/SiteStats.php16
-rw-r--r--includes/Skin.php423
-rw-r--r--includes/SkinLegacy.php282
-rw-r--r--includes/SkinTemplate.php821
-rw-r--r--includes/SpecialPage.php220
-rw-r--r--includes/SpecialPageFactory.php83
-rw-r--r--includes/SquidPurgeClient.php2
-rw-r--r--includes/Status.php10
-rw-r--r--includes/StreamFile.php235
-rw-r--r--includes/StubObject.php15
-rw-r--r--includes/Title.php2238
-rw-r--r--includes/User.php183
-rw-r--r--includes/UserArray.php3
-rw-r--r--includes/UserMailer.php334
-rw-r--r--includes/UserRightsProxy.php17
-rw-r--r--includes/ViewCountUpdate.php2
-rw-r--r--includes/WebRequest.php277
-rw-r--r--includes/WebResponse.php7
-rw-r--r--includes/WebStart.php2
-rw-r--r--includes/Wiki.php277
-rw-r--r--includes/WikiCategoryPage.php12
-rw-r--r--includes/WikiError.php8
-rw-r--r--includes/WikiFilePage.php41
-rw-r--r--includes/WikiMap.php60
-rw-r--r--includes/WikiPage.php2130
-rw-r--r--includes/Xml.php179
-rw-r--r--includes/XmlTypeCheck.php31
-rw-r--r--includes/ZhClient.php11
-rw-r--r--includes/ZipDirectoryReader.php124
-rw-r--r--includes/actions/CreditsAction.php67
-rw-r--r--includes/actions/DeleteAction.php (renamed from includes/actions/DeletetrackbackAction.php)30
-rw-r--r--includes/actions/EditAction.php74
-rw-r--r--includes/actions/HistoryAction.php (renamed from includes/HistoryPage.php)363
-rw-r--r--includes/actions/InfoAction.php44
-rw-r--r--includes/actions/MarkpatrolledAction.php32
-rw-r--r--includes/actions/ProtectAction.php56
-rw-r--r--includes/actions/PurgeAction.php12
-rw-r--r--includes/actions/RawAction.php239
-rw-r--r--includes/actions/RenderAction.php42
-rw-r--r--includes/actions/RevertAction.php22
-rw-r--r--includes/actions/RevisiondeleteAction.php4
-rw-r--r--includes/actions/RollbackAction.php14
-rw-r--r--includes/actions/ViewAction.php43
-rw-r--r--includes/actions/WatchAction.php16
-rw-r--r--includes/api/ApiBase.php177
-rw-r--r--includes/api/ApiBlock.php25
-rw-r--r--includes/api/ApiComparePages.php6
-rw-r--r--includes/api/ApiDelete.php116
-rw-r--r--includes/api/ApiDisabled.php9
-rw-r--r--includes/api/ApiEditPage.php120
-rw-r--r--includes/api/ApiEmailUser.php13
-rw-r--r--includes/api/ApiExpandTemplates.php17
-rw-r--r--includes/api/ApiFeedContributions.php11
-rw-r--r--includes/api/ApiFeedWatchlist.php24
-rw-r--r--includes/api/ApiFileRevert.php15
-rw-r--r--includes/api/ApiFormatBase.php20
-rw-r--r--includes/api/ApiFormatDbg.php5
-rw-r--r--includes/api/ApiFormatDump.php5
-rw-r--r--includes/api/ApiFormatJson.php5
-rw-r--r--includes/api/ApiFormatPhp.php5
-rw-r--r--includes/api/ApiFormatRaw.php5
-rw-r--r--includes/api/ApiFormatTxt.php5
-rw-r--r--includes/api/ApiFormatWddx.php7
-rw-r--r--includes/api/ApiFormatXml.php17
-rw-r--r--includes/api/ApiFormatYaml.php5
-rw-r--r--includes/api/ApiHelp.php22
-rw-r--r--includes/api/ApiImport.php18
-rw-r--r--includes/api/ApiLogin.php47
-rw-r--r--includes/api/ApiLogout.php17
-rw-r--r--includes/api/ApiMain.php92
-rw-r--r--includes/api/ApiMove.php15
-rw-r--r--includes/api/ApiOpenSearch.php9
-rw-r--r--includes/api/ApiPageSet.php17
-rw-r--r--includes/api/ApiParamInfo.php137
-rw-r--r--includes/api/ApiParse.php71
-rw-r--r--includes/api/ApiPatrol.php10
-rw-r--r--includes/api/ApiProtect.php27
-rw-r--r--includes/api/ApiPurge.php82
-rw-r--r--includes/api/ApiQuery.php27
-rw-r--r--includes/api/ApiQueryAllCategories.php8
-rw-r--r--includes/api/ApiQueryAllLinks.php7
-rw-r--r--includes/api/ApiQueryAllUsers.php32
-rw-r--r--includes/api/ApiQueryAllimages.php21
-rw-r--r--includes/api/ApiQueryAllmessages.php32
-rw-r--r--includes/api/ApiQueryAllpages.php26
-rw-r--r--includes/api/ApiQueryBacklinks.php7
-rw-r--r--includes/api/ApiQueryBase.php31
-rw-r--r--includes/api/ApiQueryBlocks.php58
-rw-r--r--includes/api/ApiQueryCategories.php32
-rw-r--r--includes/api/ApiQueryCategoryInfo.php7
-rw-r--r--includes/api/ApiQueryCategoryMembers.php15
-rw-r--r--includes/api/ApiQueryDeletedrevs.php48
-rw-r--r--includes/api/ApiQueryDisabled.php9
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php18
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php7
-rw-r--r--includes/api/ApiQueryExternalLinks.php10
-rw-r--r--includes/api/ApiQueryFilearchive.php22
-rw-r--r--includes/api/ApiQueryIWBacklinks.php7
-rw-r--r--includes/api/ApiQueryIWLinks.php37
-rw-r--r--includes/api/ApiQueryImageInfo.php10
-rw-r--r--includes/api/ApiQueryImages.php33
-rw-r--r--includes/api/ApiQueryInfo.php33
-rw-r--r--includes/api/ApiQueryLangBacklinks.php7
-rw-r--r--includes/api/ApiQueryLangLinks.php33
-rw-r--r--includes/api/ApiQueryLinks.php50
-rw-r--r--includes/api/ApiQueryLogEvents.php50
-rw-r--r--includes/api/ApiQueryPageProps.php7
-rw-r--r--includes/api/ApiQueryProtectedTitles.php10
-rw-r--r--includes/api/ApiQueryQueryPage.php14
-rw-r--r--includes/api/ApiQueryRandom.php7
-rw-r--r--includes/api/ApiQueryRecentChanges.php20
-rw-r--r--includes/api/ApiQueryRevisions.php57
-rw-r--r--includes/api/ApiQuerySearch.php10
-rw-r--r--includes/api/ApiQuerySiteinfo.php79
-rw-r--r--includes/api/ApiQueryStashImageInfo.php7
-rw-r--r--includes/api/ApiQueryTags.php7
-rw-r--r--includes/api/ApiQueryUserContributions.php21
-rw-r--r--includes/api/ApiQueryUserInfo.php61
-rw-r--r--includes/api/ApiQueryUsers.php9
-rw-r--r--includes/api/ApiQueryWatchlist.php11
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php7
-rw-r--r--includes/api/ApiResult.php15
-rw-r--r--includes/api/ApiRollback.php19
-rw-r--r--includes/api/ApiRsd.php6
-rw-r--r--includes/api/ApiUnblock.php19
-rw-r--r--includes/api/ApiUndelete.php14
-rw-r--r--includes/api/ApiUpload.php217
-rw-r--r--includes/api/ApiUserrights.php13
-rw-r--r--includes/api/ApiWatch.php24
-rw-r--r--includes/cache/CacheDependency.php16
-rw-r--r--includes/cache/FileCacheBase.php249
-rw-r--r--includes/cache/GenderCache.php (renamed from includes/GenderCache.php)2
-rw-r--r--includes/cache/HTMLCacheUpdate.php17
-rw-r--r--includes/cache/HTMLFileCache.php288
-rw-r--r--includes/cache/LinkBatch.php20
-rw-r--r--includes/cache/LinkCache.php55
-rw-r--r--includes/cache/MessageCache.php54
-rw-r--r--includes/cache/ObjectFileCache.php30
-rw-r--r--includes/cache/ResourceFileCache.php87
-rw-r--r--includes/context/ContextSource.php170
-rw-r--r--includes/context/DerivativeContext.php286
-rw-r--r--includes/context/IContextSource.php110
-rw-r--r--includes/context/RequestContext.php398
-rw-r--r--includes/db/CloneDatabase.php6
-rw-r--r--includes/db/Database.php401
-rw-r--r--includes/db/DatabaseError.php54
-rw-r--r--includes/db/DatabaseIbm_db2.php225
-rw-r--r--includes/db/DatabaseMssql.php2
-rw-r--r--includes/db/DatabaseMysql.php329
-rw-r--r--includes/db/DatabaseOracle.php31
-rw-r--r--includes/db/DatabasePostgres.php57
-rw-r--r--includes/db/DatabaseSqlite.php8
-rw-r--r--includes/db/DatabaseUtility.php15
-rw-r--r--includes/db/LBFactory.php11
-rw-r--r--includes/db/LBFactory_Multi.php27
-rw-r--r--includes/db/LBFactory_Single.php8
-rw-r--r--includes/db/LoadBalancer.php85
-rw-r--r--includes/db/LoadMonitor.php6
-rw-r--r--includes/debug/Debug.php285
-rw-r--r--includes/diff/DairikiDiff.php187
-rw-r--r--includes/diff/DifferenceEngine.php913
-rw-r--r--includes/diff/WikiDiff3.php5
-rw-r--r--includes/extauth/vB.php15
-rw-r--r--includes/filerepo/FSRepo.php736
-rw-r--r--includes/filerepo/FileRepo.php1058
-rw-r--r--includes/filerepo/ForeignAPIRepo.php47
-rw-r--r--includes/filerepo/ForeignDBViaLBRepo.php1
-rw-r--r--includes/filerepo/LocalRepo.php111
-rw-r--r--includes/filerepo/NullRepo.php3
-rw-r--r--includes/filerepo/README23
-rw-r--r--includes/filerepo/RepoGroup.php49
-rw-r--r--includes/filerepo/backend/FSFile.php233
-rw-r--r--includes/filerepo/backend/FSFileBackend.php600
-rw-r--r--includes/filerepo/backend/FileBackend.php1739
-rw-r--r--includes/filerepo/backend/FileBackendGroup.php156
-rw-r--r--includes/filerepo/backend/FileBackendMultiWrite.php420
-rw-r--r--includes/filerepo/backend/FileOp.php697
-rw-r--r--includes/filerepo/backend/SwiftFileBackend.php877
-rw-r--r--includes/filerepo/backend/TempFSFile.php92
-rw-r--r--includes/filerepo/backend/lockmanager/DBLockManager.php469
-rw-r--r--includes/filerepo/backend/lockmanager/FSLockManager.php202
-rw-r--r--includes/filerepo/backend/lockmanager/LSLockManager.php295
-rw-r--r--includes/filerepo/backend/lockmanager/LockManager.php182
-rw-r--r--includes/filerepo/backend/lockmanager/LockManagerGroup.php89
-rw-r--r--includes/filerepo/file/ArchivedFile.php (renamed from includes/filerepo/ArchivedFile.php)52
-rw-r--r--includes/filerepo/file/File.php (renamed from includes/filerepo/File.php)519
-rw-r--r--includes/filerepo/file/ForeignAPIFile.php (renamed from includes/filerepo/ForeignAPIFile.php)135
-rw-r--r--includes/filerepo/file/ForeignDBFile.php (renamed from includes/filerepo/ForeignDBFile.php)4
-rw-r--r--includes/filerepo/file/LocalFile.php (renamed from includes/filerepo/LocalFile.php)319
-rw-r--r--includes/filerepo/file/OldLocalFile.php (renamed from includes/filerepo/OldLocalFile.php)11
-rw-r--r--includes/filerepo/file/UnregisteredLocalFile.php (renamed from includes/filerepo/UnregisteredLocalFile.php)55
-rw-r--r--includes/installer/CliInstaller.php14
-rw-r--r--includes/installer/DatabaseInstaller.php33
-rw-r--r--includes/installer/DatabaseUpdater.php196
-rw-r--r--includes/installer/Ibm_db2Installer.php16
-rw-r--r--includes/installer/Ibm_db2Updater.php31
-rw-r--r--includes/installer/Installer.i18n.php2288
-rw-r--r--includes/installer/Installer.php120
-rw-r--r--includes/installer/LocalSettingsGenerator.php5
-rw-r--r--includes/installer/MysqlInstaller.php49
-rw-r--r--includes/installer/MysqlUpdater.php78
-rw-r--r--includes/installer/OracleInstaller.php4
-rw-r--r--includes/installer/OracleUpdater.php28
-rw-r--r--includes/installer/PhpBugTests.php3
-rw-r--r--includes/installer/PostgresInstaller.php49
-rw-r--r--includes/installer/PostgresUpdater.php65
-rw-r--r--includes/installer/SqliteInstaller.php21
-rw-r--r--includes/installer/SqliteUpdater.php22
-rw-r--r--includes/installer/WebInstaller.php41
-rw-r--r--includes/installer/WebInstallerOutput.php13
-rw-r--r--includes/installer/WebInstallerPage.php57
-rw-r--r--includes/interwiki/Interwiki.php130
-rw-r--r--includes/job/DoubleRedirectJob.php31
-rw-r--r--includes/job/EnotifNotifyJob.php3
-rw-r--r--includes/job/JobQueue.php21
-rw-r--r--includes/job/RefreshLinksJob.php12
-rw-r--r--includes/job/UploadFromUrlJob.php16
-rw-r--r--includes/json/FormatJson.php21
-rw-r--r--includes/json/Services_JSON.php4
-rw-r--r--includes/libs/CSSJanus.php32
-rw-r--r--includes/libs/CSSMin.php12
-rw-r--r--includes/libs/IEContentAnalyzer.php79
-rw-r--r--includes/libs/IEUrlExtension.php58
-rw-r--r--includes/libs/JavaScriptMinifier.php49
-rw-r--r--includes/libs/jsminplus.php223
-rw-r--r--includes/logging/LogEntry.php518
-rw-r--r--includes/logging/LogEventsList.php (renamed from includes/LogEventsList.php)521
-rw-r--r--includes/logging/LogFormatter.php673
-rw-r--r--includes/logging/LogPage.php (renamed from includes/LogPage.php)166
-rw-r--r--includes/logging/LogPager.php356
-rw-r--r--includes/logging/PatrolLog.php58
-rw-r--r--includes/media/Bitmap.php85
-rw-r--r--includes/media/BitmapMetadataHandler.php19
-rw-r--r--includes/media/Bitmap_ClientOnly.php2
-rw-r--r--includes/media/DjVu.php9
-rw-r--r--includes/media/DjVuImage.php (renamed from includes/DjVuImage.php)0
-rw-r--r--includes/media/Exif.php5
-rw-r--r--includes/media/ExifBitmap.php10
-rw-r--r--includes/media/FormatMetadata.php11
-rw-r--r--includes/media/GIF.php23
-rw-r--r--includes/media/Generic.php122
-rw-r--r--includes/media/JpegMetadataExtractor.php10
-rw-r--r--includes/media/MediaTransformOutput.php86
-rw-r--r--includes/media/SVG.php12
-rw-r--r--includes/media/SVGMetadataExtractor.php8
-rw-r--r--includes/media/XCF.php137
-rw-r--r--includes/media/XMP.php6
-rw-r--r--includes/media/XMPInfo.php23
-rw-r--r--includes/media/XMPValidate.php53
-rw-r--r--includes/mime.info4
-rw-r--r--includes/mime.types22
-rw-r--r--includes/normal/RandomTest.php2
-rw-r--r--includes/normal/UtfNormal.php32
-rw-r--r--includes/objectcache/BagOStuff.php8
-rw-r--r--includes/objectcache/DBABagOStuff.php4
-rw-r--r--includes/objectcache/EmptyBagOStuff.php2
-rw-r--r--includes/objectcache/MemcachedClient.php20
-rw-r--r--includes/objectcache/MultiWriteBagOStuff.php4
-rw-r--r--includes/objectcache/ObjectCache.php10
-rw-r--r--includes/objectcache/SqlBagOStuff.php139
-rw-r--r--includes/objectcache/eAccelBagOStuff.php46
-rw-r--r--includes/parser/CoreParserFunctions.php42
-rw-r--r--includes/parser/LinkHolderArray.php6
-rw-r--r--includes/parser/Parser.php549
-rw-r--r--includes/parser/ParserCache.php23
-rw-r--r--includes/parser/ParserOptions.php327
-rw-r--r--includes/parser/ParserOutput.php87
-rw-r--r--includes/parser/Preprocessor.php11
-rw-r--r--includes/parser/Preprocessor_DOM.php16
-rw-r--r--includes/parser/Preprocessor_Hash.php11
-rw-r--r--includes/parser/Preprocessor_HipHop.hphp16
-rw-r--r--includes/parser/StripState.php7
-rw-r--r--includes/parser/Tidy.php12
-rw-r--r--includes/profiler/Profiler.php67
-rw-r--r--includes/profiler/ProfilerSimple.php9
-rw-r--r--includes/profiler/ProfilerSimpleText.php1
-rw-r--r--includes/profiler/ProfilerSimpleTrace.php3
-rw-r--r--includes/profiler/ProfilerSimpleUDP.php38
-rw-r--r--includes/profiler/ProfilerStub.php2
-rw-r--r--includes/resourceloader/ResourceLoader.php390
-rw-r--r--includes/resourceloader/ResourceLoaderContext.php32
-rw-r--r--includes/resourceloader/ResourceLoaderFileModule.php99
-rw-r--r--includes/resourceloader/ResourceLoaderFilePageModule.php7
-rw-r--r--includes/resourceloader/ResourceLoaderModule.php99
-rw-r--r--includes/resourceloader/ResourceLoaderStartUpModule.php51
-rw-r--r--includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php113
-rw-r--r--includes/resourceloader/ResourceLoaderUserModule.php25
-rw-r--r--includes/resourceloader/ResourceLoaderUserOptionsModule.php53
-rw-r--r--includes/resourceloader/ResourceLoaderUserTokensModule.php6
-rw-r--r--includes/resourceloader/ResourceLoaderWikiModule.php57
-rw-r--r--includes/revisiondelete/RevisionDelete.php74
-rw-r--r--includes/revisiondelete/RevisionDeleteAbstracts.php4
-rw-r--r--includes/revisiondelete/RevisionDeleter.php88
-rw-r--r--includes/search/SearchEngine.php191
-rw-r--r--includes/search/SearchMySQL.php3
-rw-r--r--includes/search/SearchOracle.php4
-rw-r--r--includes/search/SearchUpdate.php4
-rw-r--r--includes/specials/SpecialActiveusers.php90
-rw-r--r--includes/specials/SpecialAllmessages.php78
-rw-r--r--includes/specials/SpecialAllpages.php58
-rw-r--r--includes/specials/SpecialAncientpages.php8
-rw-r--r--includes/specials/SpecialBlock.php317
-rw-r--r--includes/specials/SpecialBlockList.php171
-rw-r--r--includes/specials/SpecialBlockme.php15
-rw-r--r--includes/specials/SpecialBooksources.php9
-rw-r--r--includes/specials/SpecialBrokenRedirects.php29
-rw-r--r--includes/specials/SpecialCategories.php40
-rw-r--r--includes/specials/SpecialChangeEmail.php213
-rw-r--r--includes/specials/SpecialChangePassword.php91
-rw-r--r--includes/specials/SpecialComparePages.php51
-rw-r--r--includes/specials/SpecialConfirmemail.php22
-rw-r--r--includes/specials/SpecialContributions.php600
-rw-r--r--includes/specials/SpecialDeadendpages.php2
-rw-r--r--includes/specials/SpecialDeletedContributions.php200
-rw-r--r--includes/specials/SpecialDisambiguations.php14
-rw-r--r--includes/specials/SpecialDoubleRedirects.php24
-rw-r--r--includes/specials/SpecialEditWatchlist.php250
-rw-r--r--includes/specials/SpecialEmailuser.php70
-rw-r--r--includes/specials/SpecialExport.php209
-rw-r--r--includes/specials/SpecialFewestrevisions.php20
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php49
-rw-r--r--includes/specials/SpecialFilepath.php17
-rw-r--r--includes/specials/SpecialImport.php141
-rw-r--r--includes/specials/SpecialJavaScriptTest.php142
-rw-r--r--includes/specials/SpecialLinkSearch.php4
-rw-r--r--includes/specials/SpecialListfiles.php32
-rw-r--r--includes/specials/SpecialListgrouprights.php43
-rw-r--r--includes/specials/SpecialListredirects.php11
-rw-r--r--includes/specials/SpecialListusers.php68
-rw-r--r--includes/specials/SpecialLockdb.php120
-rw-r--r--includes/specials/SpecialLog.php70
-rw-r--r--includes/specials/SpecialMIMEsearch.php23
-rw-r--r--includes/specials/SpecialMergeHistory.php144
-rw-r--r--includes/specials/SpecialMostcategories.php7
-rw-r--r--includes/specials/SpecialMostimages.php4
-rw-r--r--includes/specials/SpecialMostlinked.php13
-rw-r--r--includes/specials/SpecialMostlinkedcategories.php19
-rw-r--r--includes/specials/SpecialMostlinkedtemplates.php17
-rw-r--r--includes/specials/SpecialMovepage.php364
-rw-r--r--includes/specials/SpecialNewimages.php41
-rw-r--r--includes/specials/SpecialNewpages.php85
-rw-r--r--includes/specials/SpecialPasswordReset.php80
-rw-r--r--includes/specials/SpecialPopularpages.php13
-rw-r--r--includes/specials/SpecialPreferences.php48
-rw-r--r--includes/specials/SpecialPrefixindex.php42
-rw-r--r--includes/specials/SpecialProtectedpages.php59
-rw-r--r--includes/specials/SpecialProtectedtitles.php49
-rw-r--r--includes/specials/SpecialRandompage.php13
-rw-r--r--includes/specials/SpecialRecentchanges.php67
-rw-r--r--includes/specials/SpecialRecentchangeslinked.php55
-rw-r--r--includes/specials/SpecialRevisiondelete.php42
-rw-r--r--includes/specials/SpecialSearch.php302
-rw-r--r--includes/specials/SpecialShortpages.php23
-rw-r--r--includes/specials/SpecialSpecialpages.php2
-rw-r--r--includes/specials/SpecialStatistics.php32
-rw-r--r--includes/specials/SpecialTags.php25
-rw-r--r--includes/specials/SpecialUnblock.php65
-rw-r--r--includes/specials/SpecialUndelete.php415
-rw-r--r--includes/specials/SpecialUnlockdb.php104
-rw-r--r--includes/specials/SpecialUnusedcategories.php4
-rw-r--r--includes/specials/SpecialUnusedtemplates.php6
-rw-r--r--includes/specials/SpecialUnwatchedpages.php2
-rw-r--r--includes/specials/SpecialUpload.php176
-rw-r--r--includes/specials/SpecialUploadStash.php58
-rw-r--r--includes/specials/SpecialUserlogin.php414
-rw-r--r--includes/specials/SpecialUserlogout.php21
-rw-r--r--includes/specials/SpecialUserrights.php126
-rw-r--r--includes/specials/SpecialVersion.php31
-rw-r--r--includes/specials/SpecialWantedcategories.php11
-rw-r--r--includes/specials/SpecialWantedfiles.php26
-rw-r--r--includes/specials/SpecialWantedpages.php9
-rw-r--r--includes/specials/SpecialWatchlist.php93
-rw-r--r--includes/specials/SpecialWhatlinkshere.php61
-rw-r--r--includes/specials/SpecialWithoutinterwiki.php6
-rw-r--r--includes/templates/NoLocalSettings.php1
-rw-r--r--includes/templates/Usercreate.php238
-rw-r--r--includes/templates/Userlogin.php227
-rw-r--r--includes/upload/UploadBase.php100
-rw-r--r--includes/upload/UploadFromChunks.php276
-rw-r--r--includes/upload/UploadFromFile.php12
-rw-r--r--includes/upload/UploadFromStash.php4
-rw-r--r--includes/upload/UploadFromUrl.php26
-rw-r--r--includes/upload/UploadStash.php22
457 files changed, 37248 insertions, 19306 deletions
diff --git a/includes/Action.php b/includes/Action.php
index d5432b23..37c48488 100644
--- a/includes/Action.php
+++ b/includes/Action.php
@@ -1,5 +1,9 @@
<?php
/**
+ * @defgroup Actions Action done on pages
+ */
+
+/**
* 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.
*
@@ -27,7 +31,7 @@ abstract class Action {
/**
* Page on which we're performing the action
- * @var Article
+ * @var Page
*/
protected $page;
@@ -72,34 +76,82 @@ abstract class Action {
/**
* Get an appropriate Action subclass for the given action
* @param $action String
- * @param $page Article
+ * @param $page Page
+ * @param $context IContextSource
* @return Action|false|null false if the action is disabled, null
* if it is not recognised
*/
- public final static function factory( $action, Page $page ) {
+ public final static function factory( $action, Page $page, IContextSource $context = null ) {
$class = self::getClass( $action, $page->getActionOverrides() );
if ( $class ) {
- $obj = new $class( $page );
+ $obj = new $class( $page, $context );
return $obj;
}
return $class;
}
/**
+ * Get the action that will be executed, not necessarily the one passed
+ * passed through the "action" request parameter. Actions disabled in
+ * $wgActions will be replaced by "nosuchaction".
+ *
+ * @since 1.19
+ * @param $context IContextSource
+ * @return string: action name
+ */
+ public final static function getActionName( IContextSource $context ) {
+ global $wgActions;
+
+ $request = $context->getRequest();
+ $actionName = $request->getVal( 'action', 'view' );
+
+ // Check for disabled actions
+ if ( isset( $wgActions[$actionName] ) && $wgActions[$actionName] === false ) {
+ $actionName = 'nosuchaction';
+ }
+
+ // Workaround for bug #20966: inability of IE to provide an action dependent
+ // on which submit button is clicked.
+ if ( $actionName === 'historysubmit' ) {
+ if ( $request->getBool( 'revisiondelete' ) ) {
+ $actionName = 'revisiondelete';
+ } else {
+ $actionName = 'view';
+ }
+ } elseif ( $actionName == 'editredlink' ) {
+ $actionName = 'edit';
+ }
+
+ // Trying to get a WikiPage for NS_SPECIAL etc. will result
+ // in WikiPage::factory throwing "Invalid or virtual namespace -1 given."
+ // For SpecialPages et al, default to action=view.
+ if ( !$context->canUseWikiPage() ) {
+ return 'view';
+ }
+
+ $action = Action::factory( $actionName, $context->getWikiPage() );
+ if ( $action instanceof Action ) {
+ return $action->getName();
+ }
+
+ return 'nosuchaction';
+ }
+
+ /**
* 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;
+ return self::getClass( $name, array() ) !== null;
}
/**
* Get the IContextSource in use here
* @return IContextSource
*/
- protected final function getContext() {
+ public final function getContext() {
if ( $this->context instanceof IContextSource ) {
return $this->context;
}
@@ -111,7 +163,7 @@ abstract class Action {
*
* @return WebRequest
*/
- protected final function getRequest() {
+ public final function getRequest() {
return $this->getContext()->getRequest();
}
@@ -120,7 +172,7 @@ abstract class Action {
*
* @return OutputPage
*/
- protected final function getOutput() {
+ public final function getOutput() {
return $this->getContext()->getOutput();
}
@@ -129,7 +181,7 @@ abstract class Action {
*
* @return User
*/
- protected final function getUser() {
+ public final function getUser() {
return $this->getContext()->getUser();
}
@@ -138,34 +190,58 @@ abstract class Action {
*
* @return Skin
*/
- protected final function getSkin() {
+ public final function getSkin() {
return $this->getContext()->getSkin();
}
/**
* Shortcut to get the user Language being used for this instance
*
- * @return Skin
+ * @return Language
+ */
+ public final function getLanguage() {
+ return $this->getContext()->getLanguage();
+ }
+
+ /**
+ * Shortcut to get the user Language being used for this instance
+ *
+ * @deprecated 1.19 Use getLanguage instead
+ * @return Language
*/
- protected final function getLang() {
- return $this->getContext()->getLang();
+ public final function getLang() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->getLanguage();
}
/**
* Shortcut to get the Title object from the page
* @return Title
*/
- protected final function getTitle() {
+ public final function getTitle() {
return $this->page->getTitle();
}
/**
+ * Get a Message object with context set
+ * Parameters are the same as wfMessage()
+ *
+ * @return Message object
+ */
+ public final function msg() {
+ $params = func_get_args();
+ return call_user_func_array( array( $this->getContext(), 'msg' ), $params );
+ }
+
+ /**
* Protected constructor: use Action::factory( $action, $page ) to actually build
* these things in the real world
- * @param Page $page
+ * @param $page Page
+ * @param $context IContextSource
*/
- protected function __construct( Page $page ) {
+ protected function __construct( Page $page, IContextSource $context = null ) {
$this->page = $page;
+ $this->context = $context;
}
/**
@@ -177,8 +253,11 @@ abstract class Action {
/**
* Get the permission required to perform this action. Often, but not always,
* the same as the action name
+ * @return String|null
*/
- public abstract function getRestriction();
+ public function getRestriction() {
+ return null;
+ }
/**
* Checks if the given user (identified by an object) can perform this action. Can be
@@ -189,18 +268,25 @@ abstract class Action {
* @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() );
+ $right = $this->getRestriction();
+ if ( $right !== null ) {
+ $errors = $this->getTitle()->getUserPermissionsErrors( $right, $user );
+ if ( count( $errors ) ) {
+ throw new PermissionsError( $right, $errors );
+ }
}
if ( $this->requiresUnblock() && $user->isBlocked() ) {
$block = $user->mBlock;
throw new UserBlockedError( $block );
}
+
+ // This should be checked at the end so that the user won't think the
+ // error is only temporary when he also don't have the rights to execute
+ // this action
+ if ( $this->requiresWrite() && wfReadOnly() ) {
+ throw new ReadOnlyError();
+ }
}
/**
@@ -246,7 +332,7 @@ abstract class Action {
* @return String
*/
protected function getDescription() {
- return wfMsg( strtolower( $this->getName() ) );
+ return wfMsgHtml( strtolower( $this->getName() ) );
}
/**
@@ -259,8 +345,6 @@ abstract class Action {
/**
* 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();
@@ -279,6 +363,10 @@ abstract class FormAction extends Action {
* @return String HTML which will be sent to $form->addPreText()
*/
protected function preText() { return ''; }
+
+ /**
+ * @return string
+ */
protected function postText() { return ''; }
/**
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php
index b9f80855..e60ca23c 100644
--- a/includes/AjaxResponse.php
+++ b/includes/AjaxResponse.php
@@ -180,11 +180,11 @@ class AjaxResponse {
$this->disable();
$this->mLastModified = $lastmod;
- wfDebug( "$fname: CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
+ wfDebug( "$fname: CACHED client: $ismodsince ; user: {$wgUser->getTouched()} ; page: $timestamp ; site $wgCacheEpoch\n", false );
return true;
} else {
- wfDebug( "$fname: READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp ; site $wgCacheEpoch\n", false );
+ wfDebug( "$fname: READY client: $ismodsince ; user: {$wgUser->getTouched()} ; page: $timestamp ; site $wgCacheEpoch\n", false );
$this->mLastModified = $lastmod;
}
} else {
@@ -193,6 +193,11 @@ class AjaxResponse {
}
}
+ /**
+ * @param $mckey
+ * @param $touched
+ * @return bool
+ */
function loadFromMemcached( $mckey, $touched ) {
global $wgMemc;
@@ -216,6 +221,11 @@ class AjaxResponse {
return false;
}
+ /**
+ * @param $mckey
+ * @param $expiry int
+ * @return bool
+ */
function storeInMemcached( $mckey, $expiry = 86400 ) {
global $wgMemc;
diff --git a/includes/Article.php b/includes/Article.php
index a0cc6a95..b07f309c 100644
--- a/includes/Article.php
+++ b/includes/Article.php
@@ -32,6 +32,11 @@ class Article extends Page {
*/
protected $mPage;
+ /**
+ * @var ParserOptions: ParserOptions object for $wgUser articles
+ */
+ public $mParserOptions;
+
var $mContent; // !<
var $mContentLoaded = false; // !<
var $mOldId; // !<
@@ -69,6 +74,10 @@ class Article extends Page {
$this->mPage = $this->newPage( $title );
}
+ /**
+ * @param $title Title
+ * @return WikiPage
+ */
protected function newPage( Title $title ) {
return new WikiPage( $title );
}
@@ -76,6 +85,7 @@ class Article extends Page {
/**
* Constructor from a page id
* @param $id Int article ID to load
+ * @return Article|null
*/
public static function newFromID( $id ) {
$t = Title::newFromID( $id );
@@ -140,6 +150,7 @@ class Article extends Page {
/**
* Get the title object of the article
+ *
* @return Title object of this page
*/
public function getTitle() {
@@ -147,9 +158,17 @@ class Article extends Page {
}
/**
+ * Get the WikiPage object of this instance
+ *
+ * @since 1.19
+ * @return WikiPage
+ */
+ public function getPage() {
+ return $this->mPage;
+ }
+
+ /**
* Clear the object
- * @todo FIXME: Shouldn't this be public?
- * @private
*/
public function clear() {
$this->mContentLoaded = false;
@@ -164,7 +183,7 @@ class Article extends Page {
/**
* Note that getContent/loadContent do not follow redirects anymore.
* If you need to fetch redirectable content easily, try
- * the shortcut in Article::followRedirect()
+ * the shortcut in WikiPage::getRedirectTarget()
*
* This function has side effects! Do not use this function if you
* only want the real revision text if any.
@@ -191,7 +210,7 @@ class Article extends Page {
return $text;
} else {
- $this->loadContent();
+ $this->fetchContent();
wfProfileOut( __METHOD__ );
return $this->mContent;
@@ -220,27 +239,39 @@ class Article extends Page {
$this->mRedirectUrl = false;
- $oldid = $wgRequest->getVal( 'oldid' );
+ $oldid = $wgRequest->getIntOrNull( 'oldid' );
- if ( isset( $oldid ) ) {
- $oldid = intval( $oldid );
- if ( $wgRequest->getVal( 'direction' ) == 'next' ) {
- $nextid = $this->getTitle()->getNextRevisionID( $oldid );
- if ( $nextid ) {
- $oldid = $nextid;
- } else {
- $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
- }
- } elseif ( $wgRequest->getVal( 'direction' ) == 'prev' ) {
- $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
- if ( $previd ) {
- $oldid = $previd;
+ if ( $oldid === null ) {
+ return 0;
+ }
+
+ if ( $oldid !== 0 ) {
+ # Load the given revision and check whether the page is another one.
+ # In that case, update this instance to reflect the change.
+ $this->mRevision = Revision::newFromId( $oldid );
+ if ( $this->mRevision !== null ) {
+ // Revision title doesn't match the page title given?
+ if ( $this->mPage->getID() != $this->mRevision->getPage() ) {
+ $function = array( get_class( $this->mPage ), 'newFromID' );
+ $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
}
}
}
- if ( !$oldid ) {
- $oldid = 0;
+ if ( $wgRequest->getVal( 'direction' ) == 'next' ) {
+ $nextid = $this->getTitle()->getNextRevisionID( $oldid );
+ if ( $nextid ) {
+ $oldid = $nextid;
+ $this->mRevision = null;
+ } else {
+ $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
+ }
+ } elseif ( $wgRequest->getVal( 'direction' ) == 'prev' ) {
+ $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
+ if ( $previd ) {
+ $oldid = $previd;
+ $this->mRevision = null;
+ }
}
return $oldid;
@@ -248,31 +279,31 @@ class Article extends Page {
/**
* Load the revision (including text) into this object
+ *
+ * @deprecated in 1.19; use fetchContent()
*/
function loadContent() {
- if ( $this->mContentLoaded ) {
- return;
- }
-
- wfProfileIn( __METHOD__ );
-
- $this->fetchContent( $this->getOldID() );
-
- wfProfileOut( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.19' );
+ $this->fetchContent();
}
/**
* Get text of an article from database
* Does *NOT* follow redirects.
*
- * @param $oldid Int: 0 for whatever the latest revision is
* @return mixed string containing article contents, or false if null
*/
- function fetchContent( $oldid = 0 ) {
+ function fetchContent() {
if ( $this->mContentLoaded ) {
return $this->mContent;
}
+ wfProfileIn( __METHOD__ );
+
+ $this->mContentLoaded = true;
+
+ $oldid = $this->getOldID();
+
# Pre-fill content with error message so that if something
# fails we'll have something telling us what we intended.
$t = $this->getTitle()->getPrefixedText();
@@ -280,43 +311,39 @@ class Article extends Page {
$this->mContent = wfMsgNoTrans( 'missing-article', $t, $d ) ;
if ( $oldid ) {
- $revision = Revision::newFromId( $oldid );
- if ( !$revision ) {
- wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
- return false;
- }
- // 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" );
+ # $this->mRevision might already be fetched by getOldIDFromRequest()
+ if ( !$this->mRevision ) {
+ $this->mRevision = Revision::newFromId( $oldid );
+ if ( !$this->mRevision ) {
+ wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
+ wfProfileOut( __METHOD__ );
return false;
}
}
} else {
if ( !$this->mPage->getLatest() ) {
wfDebug( __METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n" );
+ wfProfileOut( __METHOD__ );
return false;
}
- $revision = $this->mPage->getRevision();
- if ( !$revision ) {
+ $this->mRevision = $this->mPage->getRevision();
+ if ( !$this->mRevision ) {
wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n" );
+ wfProfileOut( __METHOD__ );
return false;
}
}
// @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->mRevIdFetched = $revision->getId();
- $this->mContentLoaded = true;
- $this->mRevision =& $revision;
+ $this->mContent = $this->mRevision->getText( Revision::FOR_THIS_USER ); // Loads if user is allowed
+ $this->mRevIdFetched = $this->mRevision->getId();
wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
+ wfProfileOut( __METHOD__ );
+
return $this->mContent;
}
@@ -325,7 +352,7 @@ class Article extends Page {
* @deprecated since 1.18
*/
public function forUpdate() {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
}
/**
@@ -343,6 +370,19 @@ class Article extends Page {
}
/**
+ * Get the fetched Revision object depending on request parameters or null
+ * on failure.
+ *
+ * @since 1.19
+ * @return Revision|null
+ */
+ public function getRevisionFetched() {
+ $this->fetchContent();
+
+ return $this->mRevision;
+ }
+
+ /**
* Use this to fetch the rev ID used on page views
*
* @return int revision ID of last article revision
@@ -361,14 +401,25 @@ class Article extends Page {
*/
public function view() {
global $wgUser, $wgOut, $wgRequest, $wgParser;
- global $wgUseFileCache, $wgUseETag;
+ global $wgUseFileCache, $wgUseETag, $wgDebugToolbar;
wfProfileIn( __METHOD__ );
# Get variables from query string
+ # As side effect this will load the revision and update the title
+ # in a revision ID is passed in the request, so this should remain
+ # the first call of this method even if $oldid is used way below.
$oldid = $this->getOldID();
- # getOldID may want us to redirect somewhere else
+ # Another whitelist check in case getOldID() is altering the title
+ $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $wgUser );
+ if ( count( $permErrors ) ) {
+ wfDebug( __METHOD__ . ": denied on secondary read check\n" );
+ wfProfileOut( __METHOD__ );
+ throw new PermissionsError( 'read', $permErrors );
+ }
+
+ # getOldID() may as well want us to redirect somewhere else
if ( $this->mRedirectUrl ) {
$wgOut->redirect( $this->mRedirectUrl );
wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
@@ -377,10 +428,6 @@ class Article extends Page {
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" );
@@ -390,22 +437,26 @@ class Article extends Page {
return;
}
+ # Set page title (may be overridden by DISPLAYTITLE)
+ $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
+
+ $wgOut->setArticleFlag( true );
# Allow frames by default
$wgOut->allowClickjacking();
$parserCache = ParserCache::singleton();
- $parserOptions = $this->mPage->getParserOptions();
+ $parserOptions = $this->getParserOptions();
# Render printable version, use printable version cache
if ( $wgOut->isPrintable() ) {
$parserOptions->setIsPrintable( true );
$parserOptions->setEditSection( false );
- } elseif ( $wgUseETag && !$this->getTitle()->quickUserCan( 'edit' ) ) {
+ } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit' ) ) {
$parserOptions->setEditSection( false );
}
# Try client and file cache
- if ( $oldid === 0 && $this->mPage->checkTouched() ) {
+ if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
if ( $wgUseETag ) {
$wgOut->setETag( $parserCache->getETag( $this, $parserOptions ) );
}
@@ -421,25 +472,21 @@ class Article extends Page {
wfDebug( __METHOD__ . ": done file cache\n" );
# tell wgOut that output is taken care of
$wgOut->disable();
- $this->mPage->viewUpdates();
+ $this->mPage->doViewUpdates( $wgUser );
wfProfileOut( __METHOD__ );
return;
}
}
- if ( !$wgUseETag && !$this->getTitle()->quickUserCan( 'edit' ) ) {
- $parserOptions->setEditSection( false );
- }
-
# Should the parser cache be used?
- $useParserCache = $this->useParserCache( $oldid );
+ $useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $wgUser->getStubThreshold() ) {
wfIncrStats( 'pcache_miss_stub' );
}
- $wasRedirected = $this->showRedirectedFromHeader();
+ $this->showRedirectedFromHeader();
$this->showNamespaceHeader();
# Iterate through the possible ways of constructing the output text.
@@ -454,45 +501,45 @@ class Article extends Page {
wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$useParserCache ) );
break;
case 2:
+ # Early abort if the page doesn't exist
+ if ( !$this->mPage->exists() ) {
+ wfDebug( __METHOD__ . ": showing missing article\n" );
+ $this->showMissingArticle();
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
# Try the parser cache
if ( $useParserCache ) {
$this->mParserOutput = $parserCache->get( $this, $parserOptions );
if ( $this->mParserOutput !== false ) {
- wfDebug( __METHOD__ . ": showing parser cache contents\n" );
+ if ( $oldid ) {
+ wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
+ $this->setOldSubtitle( $oldid );
+ } else {
+ wfDebug( __METHOD__ . ": showing parser cache contents\n" );
+ }
$wgOut->addParserOutput( $this->mParserOutput );
# Ensure that UI elements requiring revision ID have
# the correct version information.
$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 );
+ $cachedTimestamp = $this->mParserOutput->getTimestamp();
+ if ( $cachedTimestamp !== null ) {
+ $wgOut->setRevisionTimestamp( $cachedTimestamp );
+ $this->mPage->setTimestamp( $cachedTimestamp );
}
+ $outputDone = true;
}
}
break;
case 3:
- $text = $this->getContent();
- if ( $text === false || $this->mPage->getID() == 0 ) {
- wfDebug( __METHOD__ . ": showing missing article\n" );
- $this->showMissingArticle();
- wfProfileOut( __METHOD__ );
- return;
- }
-
- # Another whitelist check in case oldid is altering the title
- if ( !$this->getTitle()->userCanRead() ) {
- wfDebug( __METHOD__ . ": denied on secondary read check\n" );
- $wgOut->loginToUse();
- $wgOut->output();
- $wgOut->disable();
- wfProfileOut( __METHOD__ );
- return;
- }
+ # This will set $this->mRevision if needed
+ $this->fetchContent();
# Are we looking at an old revision
- if ( $oldid && !is_null( $this->mRevision ) ) {
+ if ( $oldid && $this->mRevision ) {
$this->setOldSubtitle( $oldid );
if ( !$this->showDeletedRevisionHeader() ) {
@@ -500,36 +547,29 @@ class Article extends Page {
wfProfileOut( __METHOD__ );
return;
}
-
- # If this "old" version is the current, then try the parser cache...
- 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" );
- $wgOut->addParserOutput( $this->mParserOutput );
- $wgOut->setRevisionId( $this->mPage->getLatest() );
- $outputDone = true;
- break;
- }
- }
}
# Ensure that UI elements requiring revision ID have
# the correct version information.
$wgOut->setRevisionId( $this->getRevIdFetched() );
+ # Preload timestamp to avoid a DB hit
+ $wgOut->setRevisionTimestamp( $this->getTimestamp() );
# Pages containing custom CSS or JavaScript get special treatment
if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
$this->showCssOrJsPage();
$outputDone = true;
+ } elseif( !wfRunHooks( 'ArticleViewCustom', array( $this->mContent, $this->getTitle(), $wgOut ) ) ) {
+ # Allow extensions do their own custom view for certain pages
+ $outputDone = true;
} else {
+ $text = $this->getContent();
$rt = Title::newFromRedirectArray( $text );
if ( $rt ) {
wfDebug( __METHOD__ . ": showing redirect=no page\n" );
# Viewing a redirect page (e.g. with parameter redirect=no)
- # Don't append the subtitle if this was an old revision
- $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) );
+ $wgOut->addHTML( $this->viewRedirect( $rt ) );
# Parse just to get categories, displaytitle, etc.
$this->mParserOutput = $wgParser->parse( $text, $this->getTitle(), $parserOptions );
$wgOut->addParserOutputNoText( $this->mParserOutput );
@@ -541,16 +581,34 @@ class Article extends Page {
# Run the parse, protected by a pool counter
wfDebug( __METHOD__ . ": doing uncached parse\n" );
- $key = $parserCache->getKey( $this, $parserOptions );
- $poolArticleView = new PoolWorkArticleView( $this, $key, $useParserCache, $parserOptions );
+ $poolArticleView = new PoolWorkArticleView( $this, $parserOptions,
+ $this->getRevIdFetched(), $useParserCache, $this->getContent() );
if ( !$poolArticleView->execute() ) {
+ $error = $poolArticleView->getError();
+ if ( $error ) {
+ $wgOut->clearHTML(); // for release() errors
+ $wgOut->enableClientCache( false );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
+
+ $errortext = $error->getWikiText( false, 'view-pool-error' );
+ $wgOut->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
+ }
# Connection or timeout error
wfProfileOut( __METHOD__ );
return;
- } else {
- $outputDone = true;
}
+
+ $this->mParserOutput = $poolArticleView->getParserOutput();
+ $wgOut->addParserOutput( $this->mParserOutput );
+
+ # Don't cache a dirty ParserOutput object
+ if ( $poolArticleView->getIsDirty() ) {
+ $wgOut->setSquidMaxage( 0 );
+ $wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
+ }
+
+ $outputDone = true;
break;
# Should be unreachable, but just in case...
default:
@@ -558,38 +616,53 @@ class Article extends Page {
}
}
- # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
- if ( $this->mParserOutput ) {
- $titleText = $this->mParserOutput->getTitleText();
+ # Get the ParserOutput actually *displayed* here.
+ # Note that $this->mParserOutput is the *current* version output.
+ $pOutput = ( $outputDone instanceof ParserOutput )
+ ? $outputDone // object fetched by hook
+ : $this->mParserOutput;
- if ( strval( $titleText ) !== '' ) {
- $wgOut->setPageTitle( $titleText );
- }
+ # Adjust title for main page & pages with displaytitle
+ if ( $pOutput ) {
+ $this->adjustDisplayTitle( $pOutput );
}
# For the main page, overwrite the <title> element with the con-
# tents of 'pagetitle-view-mainpage' instead of the default (if
# that's not empty).
# This message always exists because it is in the i18n files
- if ( $this->getTitle()->equals( Title::newMainPage() ) ) {
+ if ( $this->getTitle()->isMainPage() ) {
$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
- # there are any __NOINDEX__ tags on the page
- $policy = $this->getRobotPolicy( 'view' );
+ # Check for any __NOINDEX__ tags on the page using $pOutput
+ $policy = $this->getRobotPolicy( 'view', $pOutput );
$wgOut->setIndexPolicy( $policy['index'] );
$wgOut->setFollowPolicy( $policy['follow'] );
$this->showViewFooter();
- $this->mPage->viewUpdates();
+ $this->mPage->doViewUpdates( $wgUser );
+
wfProfileOut( __METHOD__ );
}
/**
+ * Adjust title for pages with displaytitle, -{T|}- or language conversion
+ * @param $pOutput ParserOutput
+ */
+ public function adjustDisplayTitle( ParserOutput $pOutput ) {
+ global $wgOut;
+ # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
+ $titleText = $pOutput->getTitleText();
+ if ( strval( $titleText ) !== '' ) {
+ $wgOut->setPageTitle( $titleText );
+ }
+ }
+
+ /**
* Show a diff page according to current request variables. For use within
* Article::view() only, other callers should use the DifferenceEngine class.
*/
@@ -603,14 +676,14 @@ class Article extends Page {
$unhide = $wgRequest->getInt( 'unhide' ) == 1;
$oldid = $this->getOldID();
- $de = new DifferenceEngine( $this->getTitle(), $oldid, $diff, $rcid, $purge, $unhide );
+ $de = new DifferenceEngine( $this->getContext(), $oldid, $diff, $rcid, $purge, $unhide );
// DifferenceEngine directly fetched the revision:
$this->mRevIdFetched = $de->mNewid;
$de->showDiffPage( $diffOnly );
if ( $diff == 0 || $diff == $this->mPage->getLatest() ) {
# Run view updates for current revision only
- $this->mPage->viewUpdates();
+ $this->mPage->doViewUpdates( $wgUser );
}
}
@@ -622,10 +695,10 @@ class Article extends Page {
* page views.
*/
protected function showCssOrJsPage() {
- global $wgOut, $wgLang;
+ global $wgOut;
- $dir = $wgLang->getDir();
- $lang = $wgLang->getCode();
+ $dir = $this->getContext()->getLanguage()->getDir();
+ $lang = $this->getContext()->getLanguage()->getCode();
$wgOut->wrapWikiMsg( "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
'clearyourcache' );
@@ -644,10 +717,11 @@ class Article extends Page {
/**
* Get the robot policy to be used for the current view
* @param $action String the action= GET parameter
+ * @param $pOutput ParserOutput
* @return Array the policy that should be set
* TODO: actions other than 'view'
*/
- public function getRobotPolicy( $action ) {
+ public function getRobotPolicy( $action, $pOutput ) {
global $wgOut, $wgArticleRobotPolicies, $wgNamespaceRobotPolicies;
global $wgDefaultRobotPolicy, $wgRequest;
@@ -695,12 +769,12 @@ class Article extends Page {
self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
);
}
- if ( $this->getTitle()->canUseNoindex() && is_object( $this->mParserOutput ) && $this->mParserOutput->getIndexPolicy() ) {
+ if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
# __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
# a final sanity check that we have really got the parser output.
$policy = array_merge(
$policy,
- array( 'index' => $this->mParserOutput->getIndexPolicy() )
+ array( 'index' => $pOutput->getIndexPolicy() )
);
}
@@ -760,16 +834,14 @@ class Article extends Page {
// This is an internally redirected page view.
// We'll need a backlink to the source page for navigation.
if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) {
- $redir = Linker::link(
+ $redir = Linker::linkKnown(
$this->mRedirectedFrom,
null,
array(),
- array( 'redirect' => 'no' ),
- array( 'known', 'noclasses' )
+ array( 'redirect' => 'no' )
);
- $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir );
- $wgOut->setSubtitle( $s );
+ $wgOut->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
// Set the fragment if one was specified in the redirect
if ( strval( $this->getTitle()->getFragment() ) != '' ) {
@@ -782,6 +854,9 @@ class Article extends Page {
'href' => $this->getTitle()->getLocalURL() )
);
+ // Tell $wgOut the user arrived at this article through a redirect
+ $wgOut->setRedirectedFrom( $this->mRedirectedFrom );
+
return true;
}
} elseif ( $rdfrom ) {
@@ -789,8 +864,7 @@ class Article extends Page {
// If it was reported from a trusted site, supply a backlink.
if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
$redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
- $s = wfMsgExt( 'redirectedfrom', array( 'parseinline', 'replaceafter' ), $redir );
- $wgOut->setSubtitle( $s );
+ $wgOut->addSubtitle( wfMessage( 'redirectedfrom' )->rawParams( $redir ) );
return true;
}
@@ -817,7 +891,7 @@ class Article extends Page {
* Show the footer section of an ordinary page view
*/
public function showViewFooter() {
- global $wgOut, $wgUseTrackbacks;
+ global $wgOut;
# check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
if ( $this->getTitle()->getNamespace() == NS_USER_TALK && IP::isValid( $this->getTitle()->getText() ) ) {
@@ -828,11 +902,6 @@ class Article extends Page {
# chance to mark this new article as patrolled.
$this->showPatrolFooter();
- # Trackbacks
- if ( $wgUseTrackbacks ) {
- $this->addTrackbacks();
- }
-
wfRunHooks( 'ArticleViewFooter', array( $this ) );
}
@@ -851,7 +920,7 @@ class Article extends Page {
return;
}
- $token = $wgUser->editToken( $rcid );
+ $token = $wgUser->getEditToken( $rcid );
$wgOut->preventClickjacking();
$wgOut->addHTML(
@@ -879,7 +948,7 @@ class Article extends Page {
* namespace, show the default message text. To be called from Article::view().
*/
public function showMissingArticle() {
- global $wgOut, $wgRequest, $wgUser;
+ global $wgOut, $wgRequest, $wgUser, $wgSend404Code;
# Show info in user (talk) namespace. Does the user exist? Is he blocked?
if ( $this->getTitle()->getNamespace() == NS_USER || $this->getTitle()->getNamespace() == NS_USER_TALK ) {
@@ -888,7 +957,7 @@ class Article extends Page {
$user = User::newFromName( $rootPart, false /* allow IP users*/ );
$ip = User::isIP( $rootPart );
- if ( !$user->isLoggedIn() && !$ip ) { # User does not exist
+ if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist
$wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
array( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ) );
} elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
@@ -919,6 +988,18 @@ class Article extends Page {
'msgKey' => array( 'moveddeleted-notice' ) )
);
+ if ( !$this->mPage->hasViewableContent() && $wgSend404Code ) {
+ // 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" );
+ }
+
+ $hookResult = wfRunHooks( 'BeforeDisplayNoArticleText', array( $this ) );
+
+ if ( ! $hookResult ) {
+ return;
+ }
+
# Show error message
$oldid = $this->getOldID();
if ( $oldid ) {
@@ -941,12 +1022,6 @@ class Article extends Page {
}
$text = "<div class='noarticletext'>\n$text\n</div>";
- 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" );
- }
-
$wgOut->addWikiText( $text );
}
@@ -992,63 +1067,126 @@ class Article extends Page {
}
/**
- * Execute the uncached parse for action=view
+ * Generate the navigation links when browsing through an article revisions
+ * It shows the information as:
+ * Revision as of \<date\>; view current revision
+ * \<- Previous version | Next Version -\>
+ *
+ * @param $oldid String: revision ID of this article revision
*/
- public function doViewParse() {
- global $wgOut;
+ public function setOldSubtitle( $oldid = 0 ) {
+ global $wgLang, $wgOut, $wgUser, $wgRequest;
- $oldid = $this->getOldID();
- $parserOptions = $this->mPage->getParserOptions();
+ if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
+ return;
+ }
- # Render printable version, use printable version cache
- $parserOptions->setIsPrintable( $wgOut->isPrintable() );
+ $unhide = $wgRequest->getInt( 'unhide' ) == 1;
- # Don't show section-edit links on old revisions... this way lies madness.
- if ( !$this->isCurrent() || $wgOut->isPrintable() || !$this->getTitle()->quickUserCan( 'edit' ) ) {
- $parserOptions->setEditSection( false );
+ # Cascade unhide param in links for easy deletion browsing
+ $extraParams = array();
+ if ( $wgRequest->getVal( 'unhide' ) ) {
+ $extraParams['unhide'] = 1;
}
- $useParserCache = $this->useParserCache( $oldid );
- $this->outputWikiText( $this->getContent(), $useParserCache, $parserOptions );
-
- return true;
- }
+ $revision = Revision::newFromId( $oldid );
+ $timestamp = $revision->getTimestamp();
- /**
- * Try to fetch an expired entry from the parser cache. If it is present,
- * output it and return true. If it is not present, output nothing and
- * return false. This is used as a callback function for
- * PoolCounter::executeProtected().
- *
- * @return boolean
- */
- public function tryDirtyCache() {
- global $wgOut;
- $parserCache = ParserCache::singleton();
- $options = $this->mPage->getParserOptions();
+ $current = ( $oldid == $this->mPage->getLatest() );
+ $td = $wgLang->timeanddate( $timestamp, true );
+ $tddate = $wgLang->date( $timestamp, true );
+ $tdtime = $wgLang->time( $timestamp, true );
- if ( $wgOut->isPrintable() ) {
- $options->setIsPrintable( true );
- $options->setEditSection( false );
- }
+ # Show user links if allowed to see them. If hidden, then show them only if requested...
+ $userlinks = Linker::revUserTools( $revision, !$unhide );
- $output = $parserCache->getDirty( $this, $options );
+ $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
+ ? 'revision-info-current'
+ : 'revision-info';
- if ( $output ) {
- wfDebug( __METHOD__ . ": sending dirty output\n" );
- wfDebugLog( 'dirty', "dirty output " . $parserCache->getKey( $this, $options ) . "\n" );
- $wgOut->setSquidMaxage( 0 );
- $this->mParserOutput = $output;
- $wgOut->addParserOutput( $output );
- $wgOut->addHTML( "<!-- parser cache is expired, sending anyway due to pool overload-->\n" );
+ $wgOut->addSubtitle( "<div id=\"mw-{$infomsg}\">" . wfMessage( $infomsg,
+ $td )->rawParams( $userlinks )->params( $revision->getID(), $tddate,
+ $tdtime, $revision->getUser() )->parse() . "</div>" );
- return true;
- } else {
- wfDebugLog( 'dirty', "dirty missing\n" );
- wfDebug( __METHOD__ . ": no dirty cache\n" );
+ $lnk = $current
+ ? wfMsgHtml( 'currentrevisionlink' )
+ : Linker::link(
+ $this->getTitle(),
+ wfMsgHtml( 'currentrevisionlink' ),
+ array(),
+ $extraParams,
+ array( 'known', 'noclasses' )
+ );
+ $curdiff = $current
+ ? wfMsgHtml( 'diff' )
+ : Linker::link(
+ $this->getTitle(),
+ wfMsgHtml( 'diff' ),
+ array(),
+ array(
+ 'diff' => 'cur',
+ 'oldid' => $oldid
+ ) + $extraParams,
+ array( 'known', 'noclasses' )
+ );
+ $prev = $this->getTitle()->getPreviousRevisionID( $oldid ) ;
+ $prevlink = $prev
+ ? Linker::link(
+ $this->getTitle(),
+ wfMsgHtml( 'previousrevision' ),
+ array(),
+ array(
+ 'direction' => 'prev',
+ 'oldid' => $oldid
+ ) + $extraParams,
+ array( 'known', 'noclasses' )
+ )
+ : wfMsgHtml( 'previousrevision' );
+ $prevdiff = $prev
+ ? Linker::link(
+ $this->getTitle(),
+ wfMsgHtml( 'diff' ),
+ array(),
+ array(
+ 'diff' => 'prev',
+ 'oldid' => $oldid
+ ) + $extraParams,
+ array( 'known', 'noclasses' )
+ )
+ : wfMsgHtml( 'diff' );
+ $nextlink = $current
+ ? wfMsgHtml( 'nextrevision' )
+ : Linker::link(
+ $this->getTitle(),
+ wfMsgHtml( 'nextrevision' ),
+ array(),
+ array(
+ 'direction' => 'next',
+ 'oldid' => $oldid
+ ) + $extraParams,
+ array( 'known', 'noclasses' )
+ );
+ $nextdiff = $current
+ ? wfMsgHtml( 'diff' )
+ : Linker::link(
+ $this->getTitle(),
+ wfMsgHtml( 'diff' ),
+ array(),
+ array(
+ 'diff' => 'next',
+ 'oldid' => $oldid
+ ) + $extraParams,
+ array( 'known', 'noclasses' )
+ );
- return false;
+ $cdel = Linker::getRevDeleteLink( $wgUser, $revision, $this->getTitle() );
+ if ( $cdel !== '' ) {
+ $cdel .= ' ';
}
+
+ $wgOut->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
+ wfMsgExt( 'revision-nav', array( 'escapenoentities', 'parsemag', 'replaceafter' ),
+ $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>" );
}
/**
@@ -1060,19 +1198,24 @@ class Article extends Page {
* @return string containing HMTL with redirect link
*/
public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
- global $wgOut, $wgLang, $wgStylePath;
+ global $wgOut, $wgStylePath;
if ( !is_array( $target ) ) {
$target = array( $target );
}
- $imageDir = $wgLang->getDir();
+ $lang = $this->getTitle()->getPageLanguage();
+ $imageDir = $lang->getDir();
if ( $appendSubtitle ) {
$wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) );
}
// the loop prepends the arrow image before the link, so the first case needs to be outside
+
+ /**
+ * @var $title Title
+ */
$title = array_shift( $target );
if ( $forceKnown ) {
@@ -1082,7 +1225,7 @@ class Article extends Page {
}
$nextRedirect = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
- $alt = $wgLang->isRTL() ? 'â†' : '→';
+ $alt = $lang->isRTL() ? 'â†' : '→';
// Automatically append redirect=no to each link, since most of them are redirect pages themselves.
foreach ( $target as $rt ) {
$link .= Html::element( 'img', array( 'src' => $nextRedirect, 'alt' => $alt ) );
@@ -1100,57 +1243,8 @@ class Article extends Page {
}
/**
- * Builds trackback links for article display if $wgUseTrackbacks is set to true
- */
- public function addTrackbacks() {
- 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->mPage->getID() )
- );
-
- if ( !$dbr->numRows( $tbs ) ) {
- return;
- }
-
- $wgOut->preventClickjacking();
-
- $tbtext = "";
- foreach ( $tbs as $o ) {
- $rmvtxt = "";
-
- 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 ) );
- }
-
- $tbtext .= "\n";
- $tbtext .= wfMsgNoTrans( strlen( $o->tb_ex ) ? 'trackbackexcerpt' : 'trackback',
- $o->tb_title,
- $o->tb_url,
- $o->tb_ex,
- $o->tb_name,
- $rmvtxt );
- }
-
- $wgOut->wrapWikiMsg( "<div id='mw_trackbacks'>\n$1\n</div>\n", array( 'trackbackbox', $tbtext ) );
- }
-
- /**
- * Removes trackback record for current article from trackbacks table
- * @deprecated since 1.18
- */
- public function deletetrackback() {
- return Action::factory( 'deletetrackback', $this )->show();
- }
-
- /**
* Handle action=render
*/
-
public function render() {
global $wgOut;
@@ -1159,62 +1253,6 @@ class Article extends Page {
}
/**
- * Handle action=purge
- */
- public function purge() {
- return Action::factory( 'purge', $this )->show();
- }
-
- /**
- * Mark this particular edit/page as patrolled
- * @deprecated since 1.18
- */
- public function markpatrolled() {
- Action::factory( 'markpatrolled', $this )->show();
- }
-
- /**
- * User-interface handler for the "watch" action.
- * Requires Request to pass a token as of 1.18.
- * @deprecated since 1.18
- */
- public function watch() {
- Action::factory( 'watch', $this )->show();
- }
-
- /**
- * Add this page to $wgUser's watchlist
- *
- * This is safe to be called multiple times
- *
- * @return bool true on successful watch operation
- * @deprecated since 1.18
- */
- public function doWatch() {
- global $wgUser;
- 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() {
- Action::factory( 'unwatch', $this )->show();
- }
-
- /**
- * Stop watching a page
- * @return bool true on successful unwatch
- * @deprecated since 1.18
- */
- public function doUnwatch() {
- global $wgUser;
- return WatchAction::doUnwatch( $this->getTitle(), $wgUser );
- }
-
- /**
* action=protect handler
*/
public function protect() {
@@ -1230,136 +1268,69 @@ class Article extends Page {
}
/**
- * Info about this page
- * Called for ?action=info when $wgAllowPageInfo is on.
- */
- public function info() {
- Action::factory( 'info', $this )->show();
- }
-
- /**
- * Overriden by ImagePage class, only present here to avoid a fatal error
- * Called for ?action=revert
- */
- public function revert() {
- Action::factory( 'revert', $this )->show();
- }
-
- /**
- * User interface for rollback operations
- */
- public function rollback() {
- Action::factory( 'rollback', $this )->show();
- }
-
- /**
- * Output a redirect back to the article.
- * This is typically used after an edit.
- *
- * @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 doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
- wfDeprecated( __METHOD__ );
- global $wgOut;
-
- if ( $noRedir ) {
- $query = 'redirect=no';
- if ( $extraQuery )
- $query .= "&$extraQuery";
- } else {
- $query = $extraQuery;
- }
-
- $wgOut->redirect( $this->getTitle()->getFullURL( $query ) . $sectionAnchor );
- }
-
- /**
* UI entry point for page deletion
*/
public function delete() {
- global $wgOut, $wgRequest;
+ global $wgOut, $wgRequest, $wgLang;
- $confirm = $wgRequest->wasPosted() &&
- $this->getContext()->getUser()->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
-
- $this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
- $this->DeleteReason = $wgRequest->getText( 'wpReason' );
+ # This code desperately needs to be totally rewritten
- $reason = $this->DeleteReasonList;
+ $title = $this->getTitle();
+ $user = $this->getContext()->getUser();
- if ( $reason != 'other' && $this->DeleteReason != '' ) {
- // Entry from drop down menu + additional comment
- $reason .= wfMsgForContent( 'colon-separator' ) . $this->DeleteReason;
- } elseif ( $reason == 'other' ) {
- $reason = $this->DeleteReason;
+ # Check permissions
+ $permission_errors = $title->getUserPermissionsErrors( 'delete', $user );
+ if ( count( $permission_errors ) ) {
+ throw new PermissionsError( 'delete', $permission_errors );
}
- # Flag to hide all contents of the archived revisions
- $suppress = $wgRequest->getVal( 'wpSuppress' ) && $this->getContext()->getUser()->isAllowed( 'suppressrevision' );
-
- # This code desperately needs to be totally rewritten
-
# Read-only check...
if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
-
- return;
+ throw new ReadOnlyError;
}
- # Check permissions
- $permission_errors = $this->getTitle()->getUserPermissionsErrors( 'delete', $this->getContext()->getUser() );
-
- if ( count( $permission_errors ) > 0 ) {
- $wgOut->showPermissionsErrorPage( $permission_errors );
-
- return;
- }
-
- $wgOut->setPagetitle( wfMsg( 'delete-confirm', $this->getTitle()->getPrefixedText() ) );
-
# Better double-check that it hasn't been deleted yet!
$dbw = wfGetDB( DB_MASTER );
- $conds = $this->getTitle()->pageCond();
+ $conds = $title->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' ),
- wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
- )
- );
+ $wgOut->setPageTitle( wfMessage( 'cannotdelete-title', $title->getPrefixedText() ) );
+ $wgOut->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
+ array( 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) )
+ );
$wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
LogEventsList::showLogExtract(
$wgOut,
'delete',
- $this->getTitle()->getPrefixedText()
+ $title->getPrefixedText()
);
return;
}
- # Hack for big sites
- $bigHistory = $this->mPage->isBigDeletion();
- if ( $bigHistory && !$this->getTitle()->userCan( 'bigdelete' ) ) {
- global $wgLang, $wgDeleteRevisionsLimit;
-
- $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
- array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
+ $deleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
+ $deleteReason = $wgRequest->getText( 'wpReason' );
- return;
+ if ( $deleteReasonList == 'other' ) {
+ $reason = $deleteReason;
+ } elseif ( $deleteReason != '' ) {
+ // Entry from drop down menu + additional comment
+ $reason = $deleteReasonList . wfMsgForContent( 'colon-separator' ) . $deleteReason;
+ } else {
+ $reason = $deleteReasonList;
}
- if ( $confirm ) {
+ if ( $wgRequest->wasPosted() && $user->matchEditToken( $wgRequest->getVal( 'wpEditToken' ),
+ array( 'delete', $this->getTitle()->getPrefixedText() ) ) )
+ {
+ # Flag to hide all contents of the archived revisions
+ $suppress = $wgRequest->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
+
$this->doDelete( $reason, $suppress );
- if ( $wgRequest->getCheck( 'wpWatch' ) && $this->getContext()->getUser()->isLoggedIn() ) {
+ if ( $wgRequest->getCheck( 'wpWatch' ) && $user->isLoggedIn() ) {
$this->doWatch();
- } elseif ( $this->getTitle()->userIsWatching() ) {
+ } elseif ( $title->userIsWatching() ) {
$this->doUnwatch();
}
@@ -1373,21 +1344,19 @@ class Article extends Page {
}
// If the page has a history, insert a warning
- if ( $hasHistory && !$confirm ) {
- global $wgLang;
-
- $revisions = $this->mPage->estimateRevisionCount();
+ if ( $hasHistory ) {
+ $revisions = $this->mTitle->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' ) . Linker::link( $this->getTitle(),
+ wfMsgHtml( 'word-separator' ) . Linker::link( $title,
wfMsgHtml( 'history' ),
array( 'rel' => 'archives' ),
array( 'action' => 'history' ) ) .
'</strong>'
);
- if ( $bigHistory ) {
+ if ( $this->mTitle->isBigDeletion() ) {
global $wgDeleteRevisionsLimit;
$wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
@@ -1407,14 +1376,16 @@ class Article extends Page {
wfDebug( "Article::confirmDelete\n" );
- $deleteBackLink = Linker::linkKnown( $this->getTitle() );
- $wgOut->setSubtitle( wfMsgHtml( 'delete-backlink', $deleteBackLink ) );
+ $wgOut->setPageTitle( wfMessage( 'delete-confirm', $this->getTitle()->getPrefixedText() ) );
+ $wgOut->addBacklinkSubtitle( $this->getTitle() );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->addWikiMsg( 'confirmdeletetext' );
wfRunHooks( 'ArticleConfirmDelete', array( $this, $wgOut, &$reason ) );
- if ( $this->getContext()->getUser()->isAllowed( 'suppressrevision' ) ) {
+ $user = $this->getContext()->getUser();
+
+ if ( $user->isAllowed( 'suppressrevision' ) ) {
$suppress = "<tr id=\"wpDeleteSuppressRow\">
<td></td>
<td class='mw-input'><strong>" .
@@ -1425,7 +1396,7 @@ class Article extends Page {
} else {
$suppress = '';
}
- $checkWatch = $this->getContext()->getUser()->getBoolOption( 'watchdeletion' ) || $this->getTitle()->userIsWatching();
+ $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $this->getTitle()->userIsWatching();
$form = Xml::openElement( 'form', array( 'method' => 'post',
'action' => $this->getTitle()->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
@@ -1458,7 +1429,7 @@ class Article extends Page {
</tr>";
# Disallow watching if user is not logged in
- if ( $this->getContext()->getUser()->isLoggedIn() ) {
+ if ( $user->isLoggedIn() ) {
$form .= "
<tr>
<td></td>
@@ -1480,10 +1451,10 @@ class Article extends Page {
</tr>" .
Xml::closeElement( 'table' ) .
Xml::closeElement( 'fieldset' ) .
- Html::hidden( 'wpEditToken', $this->getContext()->getUser()->editToken() ) .
+ Html::hidden( 'wpEditToken', $user->getEditToken( array( 'delete', $this->getTitle()->getPrefixedText() ) ) ) .
Xml::closeElement( 'form' );
- if ( $this->getContext()->getUser()->isAllowed( 'editinterface' ) ) {
+ if ( $user->isAllowed( 'editinterface' ) ) {
$title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
$link = Linker::link(
$title,
@@ -1503,17 +1474,17 @@ class Article extends Page {
/**
* Perform a deletion and output success or failure messages
+ * @param $reason
+ * @param $suppress bool
*/
public function doDelete( $reason, $suppress = false ) {
global $wgOut;
- $id = $this->getTitle()->getArticleID( Title::GAID_FOR_UPDATE );
-
$error = '';
- if ( $this->mPage->doDeleteArticle( $reason, $suppress, $id, $error ) ) {
+ if ( $this->mPage->doDeleteArticle( $reason, $suppress, 0, true, $error ) ) {
$deleted = $this->getTitle()->getPrefixedText();
- $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
+ $wgOut->setPageTitle( wfMessage( 'actioncomplete' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$loglink = '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]';
@@ -1521,16 +1492,11 @@ class Article extends Page {
$wgOut->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
$wgOut->returnToMain( false );
} else {
+ $wgOut->setPageTitle( wfMessage( 'cannotdelete-title', $this->getTitle()->getPrefixedText() ) );
if ( $error == '' ) {
- $wgOut->showFatalError(
- Html::rawElement(
- 'div',
- array( 'class' => 'error mw-error-cannotdelete' ),
- wfMsgExt( 'cannotdelete', array( 'parse' ),
- wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
- )
+ $wgOut->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
+ array( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) )
);
-
$wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
LogEventsList::showLogExtract(
@@ -1539,156 +1505,11 @@ class Article extends Page {
$this->getTitle()->getPrefixedText()
);
} else {
- $wgOut->showFatalError( $error );
+ $wgOut->addHTML( $error );
}
}
}
- /**
- * Generate the navigation links when browsing through an article revisions
- * It shows the information as:
- * Revision as of \<date\>; view current revision
- * \<- Previous version | Next Version -\>
- *
- * @param $oldid String: revision ID of this article revision
- */
- public function setOldSubtitle( $oldid = 0 ) {
- global $wgLang, $wgOut, $wgUser, $wgRequest;
-
- if ( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
- return;
- }
-
- $unhide = $wgRequest->getInt( 'unhide' ) == 1;
-
- # Cascade unhide param in links for easy deletion browsing
- $extraParams = array();
- if ( $wgRequest->getVal( 'unhide' ) ) {
- $extraParams['unhide'] = 1;
- }
-
- $revision = Revision::newFromId( $oldid );
- $timestamp = $revision->getTimestamp();
-
- $current = ( $oldid == $this->mPage->getLatest() );
- $td = $wgLang->timeanddate( $timestamp, true );
- $tddate = $wgLang->date( $timestamp, true );
- $tdtime = $wgLang->time( $timestamp, true );
-
- $lnk = $current
- ? wfMsgHtml( 'currentrevisionlink' )
- : Linker::link(
- $this->getTitle(),
- wfMsgHtml( 'currentrevisionlink' ),
- array(),
- $extraParams,
- array( 'known', 'noclasses' )
- );
- $curdiff = $current
- ? wfMsgHtml( 'diff' )
- : Linker::link(
- $this->getTitle(),
- wfMsgHtml( 'diff' ),
- array(),
- array(
- 'diff' => 'cur',
- 'oldid' => $oldid
- ) + $extraParams,
- array( 'known', 'noclasses' )
- );
- $prev = $this->getTitle()->getPreviousRevisionID( $oldid ) ;
- $prevlink = $prev
- ? Linker::link(
- $this->getTitle(),
- wfMsgHtml( 'previousrevision' ),
- array(),
- array(
- 'direction' => 'prev',
- 'oldid' => $oldid
- ) + $extraParams,
- array( 'known', 'noclasses' )
- )
- : wfMsgHtml( 'previousrevision' );
- $prevdiff = $prev
- ? Linker::link(
- $this->getTitle(),
- wfMsgHtml( 'diff' ),
- array(),
- array(
- 'diff' => 'prev',
- 'oldid' => $oldid
- ) + $extraParams,
- array( 'known', 'noclasses' )
- )
- : wfMsgHtml( 'diff' );
- $nextlink = $current
- ? wfMsgHtml( 'nextrevision' )
- : Linker::link(
- $this->getTitle(),
- wfMsgHtml( 'nextrevision' ),
- array(),
- array(
- 'direction' => 'next',
- 'oldid' => $oldid
- ) + $extraParams,
- array( 'known', 'noclasses' )
- );
- $nextdiff = $current
- ? wfMsgHtml( 'diff' )
- : Linker::link(
- $this->getTitle(),
- wfMsgHtml( 'diff' ),
- array(),
- array(
- 'diff' => 'next',
- 'oldid' => $oldid
- ) + $extraParams,
- array( 'known', 'noclasses' )
- );
-
- $cdel = '';
-
- // User can delete revisions or view deleted revisions...
- $canHide = $wgUser->isAllowed( 'deleterevision' );
- if ( $canHide || ( $revision->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) ) {
- if ( !$revision->userCan( Revision::DELETED_RESTRICTED ) ) {
- $cdel = Linker::revDeleteLinkDisabled( $canHide ); // rev was hidden from Sysops
- } else {
- $query = array(
- 'type' => 'revision',
- 'target' => $this->getTitle()->getPrefixedDbkey(),
- 'ids' => $oldid
- );
- $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 = Linker::revUserTools( $revision, !$unhide );
-
- $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
- ? 'revision-info-current'
- : 'revision-info';
-
- $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" .
- wfMsgExt(
- $infomsg,
- array( 'parseinline', 'replaceafter' ),
- $td,
- $userlinks,
- $revision->getID(),
- $tddate,
- $tdtime,
- $revision->getUser()
- ) .
- "</div>\n" .
- "\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsgExt( 'revision-nav', array( 'escapenoentities', 'parsemag', 'replaceafter' ),
- $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t";
-
- $wgOut->setSubtitle( $r );
- }
-
/* Caching functions */
/**
@@ -1708,10 +1529,10 @@ class Article extends Page {
$called = true;
if ( $this->isFileCacheable() ) {
- $cache = new HTMLFileCache( $this->getTitle() );
- if ( $cache->isFileCacheGood( $this->mPage->getTouched() ) ) {
+ $cache = HTMLFileCache::newFromTitle( $this->getTitle(), 'view' );
+ if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
wfDebug( "Article::tryFileCache(): about to load file\n" );
- $cache->loadFromFileCache();
+ $cache->loadFromFileCache( $this->getContext() );
return true;
} else {
wfDebug( "Article::tryFileCache(): starting buffer\n" );
@@ -1731,8 +1552,9 @@ class Article extends Page {
public function isFileCacheable() {
$cacheable = false;
- if ( HTMLFileCache::useFileCache() ) {
- $cacheable = $this->mPage->getID() && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
+ if ( HTMLFileCache::useFileCache( $this->getContext() ) ) {
+ $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 ) );
@@ -1745,25 +1567,6 @@ class Article extends Page {
/**#@-*/
/**
- * 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 );
-
- $this->doCascadeProtectionUpdates( $this->mParserOutput );
-
- $wgOut->addParserOutput( $this->mParserOutput );
- }
-
- /**
* 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.
@@ -1775,91 +1578,25 @@ class Article extends Page {
* @return ParserOutput or false if the given revsion ID is not found
*/
public function getParserOutput( $oldid = null, User $user = null ) {
- global $wgEnableParserCache, $wgUser;
- $user = is_null( $user ) ? $wgUser : $user;
-
- wfProfileIn( __METHOD__ );
- // Should the parser cache be used?
- $useParserCache = $wgEnableParserCache &&
- $user->getStubThreshold() == 0 &&
- $this->mPage->exists() &&
- $oldid === null;
-
- wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
-
- if ( $user->getStubThreshold() ) {
- wfIncrStats( 'pcache_miss_stub' );
- }
-
- if ( $useParserCache ) {
- $parserOutput = ParserCache::singleton()->get( $this, $this->mPage->getParserOptions() );
- if ( $parserOutput !== false ) {
- wfProfileOut( __METHOD__ );
- return $parserOutput;
- }
- }
+ global $wgUser;
- // 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();
- }
+ $user = is_null( $user ) ? $wgUser : $user;
+ $parserOptions = $this->mPage->makeParserOptions( $user );
- wfProfileOut( __METHOD__ );
- return $this->getOutputFromWikitext( $text, $useParserCache );
+ return $this->mPage->getParserOutput( $parserOptions, $oldid );
}
/**
- * This does all the heavy lifting for outputWikitext, except it returns the parser
- * output instead of sending it straight to $wgOut. Makes things nice and simple for,
- * say, embedding thread pages within a discussion system (LiquidThreads)
- *
- * @param $text string
- * @param $cache boolean
- * @param $parserOptions parsing options, defaults to false
- * @return ParserOutput
+ * Get parser options suitable for rendering the primary article wikitext
+ * @return ParserOptions|false
*/
- public function getOutputFromWikitext( $text, $cache = true, $parserOptions = false ) {
- global $wgParser, $wgEnableParserCache, $wgUseFileCache;
-
- if ( !$parserOptions ) {
- $parserOptions = $this->mPage->getParserOptions();
- }
-
- $time = - wfTime();
- $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->getTitle()->getPrefixedDBkey() ) );
- }
-
- if ( $wgEnableParserCache && $cache && $this->mParserOutput->isCacheable() ) {
- $parserCache = ParserCache::singleton();
- $parserCache->save( $this->mParserOutput, $this, $parserOptions );
- }
-
- // Make sure file cache is not used on uncacheable content.
- // Output that has magic words in it can still use the parser cache
- // (if enabled), though it will generally expire sooner.
- if ( !$this->mParserOutput->isCacheable() || $this->mParserOutput->containsOldMagic() ) {
- $wgUseFileCache = false;
- }
-
- if ( $this->isCurrent() ) {
- $this->mPage->doCascadeProtectionUpdates( $this->mParserOutput );
+ public function getParserOptions() {
+ global $wgUser;
+ if ( !$this->mParserOptions ) {
+ $this->mParserOptions = $this->mPage->makeParserOptions( $wgUser );
}
-
- return $this->mParserOutput;
+ // Clone to allow modifications of the return value without affecting cache
+ return clone $this->mParserOptions;
}
/**
@@ -1888,6 +1625,119 @@ class Article extends Page {
}
/**
+ * Info about this page
+ * @deprecated since 1.19
+ */
+ public function info() {
+ wfDeprecated( __METHOD__, '1.19' );
+ Action::factory( 'info', $this )->show();
+ }
+
+ /**
+ * Mark this particular edit/page as patrolled
+ * @deprecated since 1.18
+ */
+ public function markpatrolled() {
+ wfDeprecated( __METHOD__, '1.18' );
+ Action::factory( 'markpatrolled', $this )->show();
+ }
+
+ /**
+ * Handle action=purge
+ * @deprecated since 1.19
+ */
+ public function purge() {
+ return Action::factory( 'purge', $this )->show();
+ }
+
+ /**
+ * Handle action=revert
+ * @deprecated since 1.19
+ */
+ public function revert() {
+ wfDeprecated( __METHOD__, '1.19' );
+ Action::factory( 'revert', $this )->show();
+ }
+
+ /**
+ * Handle action=rollback
+ * @deprecated since 1.19
+ */
+ public function rollback() {
+ wfDeprecated( __METHOD__, '1.19' );
+ Action::factory( 'rollback', $this )->show();
+ }
+
+ /**
+ * User-interface handler for the "watch" action.
+ * Requires Request to pass a token as of 1.18.
+ * @deprecated since 1.18
+ */
+ public function watch() {
+ wfDeprecated( __METHOD__, '1.18' );
+ Action::factory( 'watch', $this )->show();
+ }
+
+ /**
+ * Add this page to $wgUser's watchlist
+ *
+ * This is safe to be called multiple times
+ *
+ * @return bool true on successful watch operation
+ * @deprecated since 1.18
+ */
+ public function doWatch() {
+ global $wgUser;
+ wfDeprecated( __METHOD__, '1.18' );
+ 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() {
+ wfDeprecated( __METHOD__, '1.18' );
+ Action::factory( 'unwatch', $this )->show();
+ }
+
+ /**
+ * Stop watching a page
+ * @return bool true on successful unwatch
+ * @deprecated since 1.18
+ */
+ public function doUnwatch() {
+ global $wgUser;
+ wfDeprecated( __METHOD__, '1.18' );
+ return WatchAction::doUnwatch( $this->getTitle(), $wgUser );
+ }
+
+ /**
+ * Output a redirect back to the article.
+ * This is typically used after an edit.
+ *
+ * @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 doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ global $wgOut;
+
+ if ( $noRedir ) {
+ $query = 'redirect=no';
+ if ( $extraQuery )
+ $query .= "&$extraQuery";
+ } else {
+ $query = $extraQuery;
+ }
+
+ $wgOut->redirect( $this->getTitle()->getFullURL( $query ) . $sectionAnchor );
+ }
+
+ /**
* Use PHP's magic __get handler to handle accessing of
* raw WikiPage fields for backwards compatibility.
*
@@ -1898,7 +1748,7 @@ class Article extends Page {
#wfWarn( "Access to raw $fname field " . __CLASS__ );
return $this->mPage->$fname;
}
- trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
+ trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
}
/**
@@ -1907,7 +1757,6 @@ class Article extends Page {
*
* @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 ) ) {
@@ -1917,7 +1766,7 @@ class Article extends Page {
} elseif ( !in_array( $fname, array( 'mContext', 'mPage' ) ) ) {
$this->mPage->$fname = $fvalue;
} else {
- trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
+ trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
}
}
@@ -1933,109 +1782,121 @@ class Article extends Page {
#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 );
+ trigger_error( 'Inaccessible function via __call(): ' . $fname, E_USER_ERROR );
}
// ****** B/C functions to work-around PHP silliness with __call and references ****** //
+
+ /**
+ * @param $limit array
+ * @param $expiry array
+ * @param $cascade bool
+ * @param $reason string
+ * @param $user User
+ * @return Status
+ */
+ public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
+ return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
+ }
+
+ /**
+ * @param $limit array
+ * @param $reason string
+ * @param $cascade int
+ * @param $expiry array
+ * @return bool
+ */
public function updateRestrictions( $limit = array(), $reason = '', &$cascade = 0, $expiry = array() ) {
return $this->mPage->updateRestrictions( $limit, $reason, $cascade, $expiry );
}
+ /**
+ * @param $reason string
+ * @param $suppress bool
+ * @param $id int
+ * @param $commit bool
+ * @param $error string
+ * @return bool
+ */
public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) {
return $this->mPage->doDeleteArticle( $reason, $suppress, $id, $commit, $error );
}
+ /**
+ * @param $fromP
+ * @param $summary
+ * @param $token
+ * @param $bot
+ * @param $resultDetails
+ * @param $user User
+ * @return array
+ */
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 );
}
+ /**
+ * @param $fromP
+ * @param $summary
+ * @param $bot
+ * @param $resultDetails
+ * @param $guser User
+ * @return array
+ */
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 );
}
+ /**
+ * @param $hasHistory bool
+ * @return mixed
+ */
public function generateReason( &$hasHistory ) {
return $this->mPage->getAutoDeleteReason( $hasHistory );
}
// ****** B/C functions for static methods ( __callStatic is PHP>=5.3 ) ****** //
+
+ /**
+ * @return array
+ */
public static function selectFields() {
return WikiPage::selectFields();
}
+ /**
+ * @param $title Title
+ */
public static function onArticleCreate( $title ) {
- return WikiPage::onArticleCreate( $title );
+ WikiPage::onArticleCreate( $title );
}
+ /**
+ * @param $title Title
+ */
public static function onArticleDelete( $title ) {
- return WikiPage::onArticleDelete( $title );
- }
-
- public static function onArticleEdit( $title ) {
- return WikiPage::onArticleEdit( $title );
+ WikiPage::onArticleDelete( $title );
}
- public static function getAutosummary( $oldtext, $newtext, $flags ) {
- return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
- }
- // ******
-}
-
-class PoolWorkArticleView extends PoolCounterWork {
-
/**
- * @var Article
+ * @param $title Title
*/
- private $mArticle;
-
- function __construct( $article, $key, $useParserCache, $parserOptions ) {
- parent::__construct( 'ArticleView', $key );
- $this->mArticle = $article;
- $this->cacheable = $useParserCache;
- $this->parserOptions = $parserOptions;
- }
-
- function doWork() {
- return $this->mArticle->doViewParse();
- }
-
- function getCachedWork() {
- global $wgOut;
-
- $parserCache = ParserCache::singleton();
- $this->mArticle->mParserOutput = $parserCache->get( $this->mArticle, $this->parserOptions );
-
- if ( $this->mArticle->mParserOutput !== false ) {
- wfDebug( __METHOD__ . ": showing contents parsed by someone else\n" );
- $wgOut->addParserOutput( $this->mArticle->mParserOutput );
- # Ensure that UI elements requiring revision ID have
- # the correct version information.
- $wgOut->setRevisionId( $this->mArticle->getLatest() );
- return true;
- }
- return false;
- }
-
- function fallback() {
- return $this->mArticle->tryDirtyCache();
+ public static function onArticleEdit( $title ) {
+ WikiPage::onArticleEdit( $title );
}
/**
- * @param $status Status
+ * @param $oldtext
+ * @param $newtext
+ * @param $flags
+ * @return string
*/
- 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;
+ public static function getAutosummary( $oldtext, $newtext, $flags ) {
+ return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
}
+ // ******
}
diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php
index eebb52d6..2fdba797 100644
--- a/includes/AuthPlugin.php
+++ b/includes/AuthPlugin.php
@@ -67,7 +67,7 @@ class AuthPlugin {
* Modify options in the login template.
*
* @param $template UserLoginTemplate object.
- * @param $type String 'signup' or 'login'.
+ * @param $type String 'signup' or 'login'. Added in 1.16.
*/
public function modifyUITemplate( &$template, &$type ) {
# Override this!
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index d8263ba9..93fac45f 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -24,12 +24,13 @@ $wgAutoloadLocalClasses = array(
'AuthPluginUser' => 'includes/AuthPlugin.php',
'Autopromote' => 'includes/Autopromote.php',
'BacklinkCache' => 'includes/BacklinkCache.php',
+ 'BadTitleError' => 'includes/Exception.php',
'BaseTemplate' => 'includes/SkinTemplate.php',
'Block' => 'includes/Block.php',
'Category' => 'includes/Category.php',
'Categoryfinder' => 'includes/Categoryfinder.php',
'CategoryPage' => 'includes/CategoryPage.php',
- 'CategoryViewer' => 'includes/CategoryPage.php',
+ 'CategoryViewer' => 'includes/CategoryViewer.php',
'CdbFunctions' => 'includes/Cdb_PHP.php',
'CdbReader' => 'includes/Cdb.php',
'CdbReader_DBA' => 'includes/Cdb.php',
@@ -46,11 +47,15 @@ $wgAutoloadLocalClasses = array(
'ConfEditor' => 'includes/ConfEditor.php',
'ConfEditorParseError' => 'includes/ConfEditor.php',
'ConfEditorToken' => 'includes/ConfEditor.php',
- 'ContextSource' => 'includes/RequestContext.php',
'Cookie' => 'includes/Cookie.php',
'CookieJar' => 'includes/Cookie.php',
+ 'MWCryptRand' => 'includes/CryptRand.php',
+ 'CurlHttpRequest' => 'includes/HttpFunctions.php',
+ 'DeferrableUpdate' => 'includes/DeferredUpdates.php',
+ 'DeferredUpdates' => 'includes/DeferredUpdates.php',
+ 'DerivativeRequest' => 'includes/WebRequest.php',
'DiffHistoryBlob' => 'includes/HistoryBlob.php',
- 'DjVuImage' => 'includes/DjVuImage.php',
+
'DoubleReplacer' => 'includes/StringUtils.php',
'DummyLinker' => 'includes/Linker.php',
'Dump7ZipOutput' => 'includes/Export.php',
@@ -87,13 +92,10 @@ $wgAutoloadLocalClasses = array(
'FormAction' => 'includes/Action.php',
'FormOptions' => 'includes/FormOptions.php',
'FormSpecialPage' => 'includes/SpecialPage.php',
- 'GenderCache' => 'includes/GenderCache.php',
'HashtableReplacer' => 'includes/StringUtils.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',
'HTMLCheckField' => 'includes/HTMLForm.php',
@@ -113,8 +115,8 @@ $wgAutoloadLocalClasses = array(
'HTMLTextAreaField' => 'includes/HTMLForm.php',
'HTMLTextField' => 'includes/HTMLForm.php',
'Http' => 'includes/HttpFunctions.php',
+ 'HttpError' => 'includes/Exception.php',
'HttpRequest' => 'includes/HttpFunctions.old.php',
- 'IContextSource' => 'includes/RequestContext.php',
'IcuCollation' => 'includes/Collation.php',
'IdentityCollation' => 'includes/Collation.php',
'ImageGallery' => 'includes/ImageGallery.php',
@@ -128,6 +130,7 @@ $wgAutoloadLocalClasses = array(
'IndexPager' => 'includes/Pager.php',
'Interwiki' => 'includes/interwiki/Interwiki.php',
'IP' => 'includes/IP.php',
+ 'LCStore_Accel' => 'includes/LocalisationCache.php',
'LCStore_CDB' => 'includes/LocalisationCache.php',
'LCStore_DB' => 'includes/LocalisationCache.php',
'LCStore_Null' => 'includes/LocalisationCache.php',
@@ -139,9 +142,6 @@ $wgAutoloadLocalClasses = array(
'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',
'MagicWord' => 'includes/MagicWord.php',
'MagicWordArray' => 'includes/MagicWord.php',
'MailAddress' => 'includes/UserMailer.php',
@@ -150,7 +150,6 @@ $wgAutoloadLocalClasses = array(
'Message' => 'includes/Message.php',
'MessageBlobStore' => 'includes/MessageBlobStore.php',
'MimeMagic' => 'includes/MimeMagic.php',
- 'MWCryptRand' => 'includes/CryptRand.php',
'MWException' => 'includes/Exception.php',
'MWExceptionHandler' => 'includes/Exception.php',
'MWFunction' => 'includes/MWFunction.php',
@@ -160,24 +159,23 @@ $wgAutoloadLocalClasses = array(
'OldChangesList' => 'includes/ChangesList.php',
'OutputPage' => 'includes/OutputPage.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',
+ 'PathRouter' => 'includes/PathRouter.php',
+ 'PathRouterPatternReplacer' => 'includes/PathRouter.php',
'PermissionsError' => 'includes/Exception.php',
'PhpHttpRequest' => 'includes/HttpFunctions.php',
'PoolCounter' => 'includes/PoolCounter.php',
'PoolCounter_Stub' => 'includes/PoolCounter.php',
'PoolCounterWork' => 'includes/PoolCounter.php',
+ 'PoolWorkArticleView' => 'includes/WikiPage.php',
'Preferences' => 'includes/Preferences.php',
'PreferencesForm' => 'includes/Preferences.php',
'PrefixSearch' => 'includes/PrefixSearch.php',
'ProtectionForm' => 'includes/ProtectionForm.php',
'QueryPage' => 'includes/QueryPage.php',
'QuickTemplate' => 'includes/SkinTemplate.php',
- 'RawPage' => 'includes/RawPage.php',
'RCCacheEntry' => 'includes/ChangesList.php',
'RdfMetaData' => 'includes/Metadata.php',
'ReadOnlyError' => 'includes/Exception.php',
@@ -186,10 +184,9 @@ $wgAutoloadLocalClasses = array(
'RegexlikeReplacer' => 'includes/StringUtils.php',
'ReplacementArray' => 'includes/StringUtils.php',
'Replacer' => 'includes/StringUtils.php',
- 'RequestContext' => 'includes/RequestContext.php',
'ReverseChronologicalPager' => 'includes/Pager.php',
- 'Rev_Item' => 'includes/RevisionList.php',
- 'Rev_List' => 'includes/RevisionList.php',
+ 'RevisionItemBase' => 'includes/RevisionList.php',
+ 'RevisionListBase' => 'includes/RevisionList.php',
'Revision' => 'includes/Revision.php',
'RevisionList' => 'includes/RevisionList.php',
'RSSFeed' => 'includes/Feed.php',
@@ -214,6 +211,7 @@ $wgAutoloadLocalClasses = array(
'SquidPurgeClient' => 'includes/SquidPurgeClient.php',
'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php',
'Status' => 'includes/Status.php',
+ 'StreamFile' => 'includes/StreamFile.php',
'StringUtils' => 'includes/StringUtils.php',
'StubContLang' => 'includes/StubObject.php',
'StubObject' => 'includes/StubObject.php',
@@ -258,15 +256,26 @@ $wgAutoloadLocalClasses = array(
# includes/actions
'CreditsAction' => 'includes/actions/CreditsAction.php',
- 'DeletetrackbackAction' => 'includes/actions/DeletetrackbackAction.php',
+ 'DeleteAction' => 'includes/actions/DeleteAction.php',
+ 'EditAction' => 'includes/actions/EditAction.php',
+ 'HistoryAction' => 'includes/actions/HistoryAction.php',
+ 'HistoryPage' => 'includes/actions/HistoryAction.php',
+ 'HistoryPager' => 'includes/actions/HistoryAction.php',
'InfoAction' => 'includes/actions/InfoAction.php',
'MarkpatrolledAction' => 'includes/actions/MarkpatrolledAction.php',
+ 'ProtectAction' => 'includes/actions/ProtectAction.php',
'PurgeAction' => 'includes/actions/PurgeAction.php',
+ 'RawAction' => 'includes/actions/RawAction.php',
+ 'RawPage' => 'includes/actions/RawAction.php',
+ 'RenderAction' => 'includes/actions/RenderAction.php',
'RevertAction' => 'includes/actions/RevertAction.php',
'RevertFileAction' => 'includes/actions/RevertAction.php',
'RevisiondeleteAction' => 'includes/actions/RevisiondeleteAction.php',
'RollbackAction' => 'includes/actions/RollbackAction.php',
+ 'SubmitAction' => 'includes/actions/EditAction.php',
+ 'UnprotectAction' => 'includes/actions/ProtectAction.php',
'UnwatchAction' => 'includes/actions/WatchAction.php',
+ 'ViewAction' => 'includes/actions/ViewAction.php',
'WatchAction' => 'includes/actions/WatchAction.php',
# includes/api
@@ -359,13 +368,14 @@ $wgAutoloadLocalClasses = array(
'ApiUpload' => 'includes/api/ApiUpload.php',
'ApiUserrights' => 'includes/api/ApiUserrights.php',
'ApiWatch' => 'includes/api/ApiWatch.php',
- 'UsageException' => 'includes/api/ApiMain.php',
# includes/cache
'CacheDependency' => 'includes/cache/CacheDependency.php',
'ConstantDependency' => 'includes/cache/CacheDependency.php',
'DependencyWrapper' => 'includes/cache/CacheDependency.php',
+ 'FileCacheBase' => 'includes/cache/FileCacheBase.php',
'FileDependency' => 'includes/cache/CacheDependency.php',
+ 'GenderCache' => 'includes/cache/GenderCache.php',
'GlobalDependency' => 'includes/cache/CacheDependency.php',
'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php',
'HTMLCacheUpdateJob' => 'includes/cache/HTMLCacheUpdate.php',
@@ -373,10 +383,20 @@ $wgAutoloadLocalClasses = array(
'LinkBatch' => 'includes/cache/LinkBatch.php',
'LinkCache' => 'includes/cache/LinkCache.php',
'MessageCache' => 'includes/cache/MessageCache.php',
+ 'ObjectFileCache' => 'includes/cache/ObjectFileCache.php',
+ 'ResourceFileCache' => 'includes/cache/ResourceFileCache.php',
'SquidUpdate' => 'includes/cache/SquidUpdate.php',
'TitleDependency' => 'includes/cache/CacheDependency.php',
'TitleListDependency' => 'includes/cache/CacheDependency.php',
+ 'UsageException' => 'includes/api/ApiMain.php',
+
+ # includes/context
+ 'ContextSource' => 'includes/context/ContextSource.php',
+ 'DerivativeContext' => 'includes/context/DerivativeContext.php',
+ 'IContextSource' => 'includes/context/IContextSource.php',
+ 'RequestContext' => 'includes/context/RequestContext.php',
+
# includes/db
'Blob' => 'includes/db/DatabaseUtility.php',
'ChronologyProtector' => 'includes/db/LBFactory.php',
@@ -419,6 +439,9 @@ $wgAutoloadLocalClasses = array(
'ResultWrapper' => 'includes/db/DatabaseUtility.php',
'SQLiteField' => 'includes/db/DatabaseSqlite.php',
+ # includes/debug
+ 'MWDebug' => 'includes/debug/Debug.php',
+
# includes/diff
'_DiffEngine' => 'includes/diff/DairikiDiff.php',
'_DiffOp' => 'includes/diff/DairikiDiff.php',
@@ -444,24 +467,57 @@ $wgAutoloadLocalClasses = array(
'ExternalUser_vB' => 'includes/extauth/vB.php',
# includes/filerepo
- 'ArchivedFile' => 'includes/filerepo/ArchivedFile.php',
- 'File' => 'includes/filerepo/File.php',
'FileRepo' => 'includes/filerepo/FileRepo.php',
'FileRepoStatus' => 'includes/filerepo/FileRepoStatus.php',
- 'ForeignAPIFile' => 'includes/filerepo/ForeignAPIFile.php',
'ForeignAPIRepo' => 'includes/filerepo/ForeignAPIRepo.php',
- 'ForeignDBFile' => 'includes/filerepo/ForeignDBFile.php',
'ForeignDBRepo' => 'includes/filerepo/ForeignDBRepo.php',
'ForeignDBViaLBRepo' => 'includes/filerepo/ForeignDBViaLBRepo.php',
'FSRepo' => 'includes/filerepo/FSRepo.php',
- 'LocalFile' => 'includes/filerepo/LocalFile.php',
- 'LocalFileDeleteBatch' => 'includes/filerepo/LocalFile.php',
- 'LocalFileMoveBatch' => 'includes/filerepo/LocalFile.php',
- 'LocalFileRestoreBatch' => 'includes/filerepo/LocalFile.php',
'LocalRepo' => 'includes/filerepo/LocalRepo.php',
- 'OldLocalFile' => 'includes/filerepo/OldLocalFile.php',
+ 'NullRepo' => 'includes/filerepo/NullRepo.php',
'RepoGroup' => 'includes/filerepo/RepoGroup.php',
- 'UnregisteredLocalFile' => 'includes/filerepo/UnregisteredLocalFile.php',
+
+ # includes/filerepo/file
+ 'ArchivedFile' => 'includes/filerepo/file/ArchivedFile.php',
+ 'File' => 'includes/filerepo/file/File.php',
+ 'ForeignAPIFile' => 'includes/filerepo/file/ForeignAPIFile.php',
+ 'ForeignDBFile' => 'includes/filerepo/file/ForeignDBFile.php',
+ 'LocalFile' => 'includes/filerepo/file/LocalFile.php',
+ 'LocalFileDeleteBatch' => 'includes/filerepo/file/LocalFile.php',
+ 'LocalFileMoveBatch' => 'includes/filerepo/file/LocalFile.php',
+ 'LocalFileRestoreBatch' => 'includes/filerepo/file/LocalFile.php',
+ 'OldLocalFile' => 'includes/filerepo/file/OldLocalFile.php',
+ 'UnregisteredLocalFile' => 'includes/filerepo/file/UnregisteredLocalFile.php',
+ 'FSFile' => 'includes/filerepo/backend/FSFile.php',
+ 'TempFSFile' => 'includes/filerepo/backend/TempFSFile.php',
+
+ # includes/filerepo/backend
+ 'FileBackendGroup' => 'includes/filerepo/backend/FileBackendGroup.php',
+ 'FileBackend' => 'includes/filerepo/backend/FileBackend.php',
+ 'FileBackendStore' => 'includes/filerepo/backend/FileBackend.php',
+ 'FileBackendMultiWrite' => 'includes/filerepo/backend/FileBackendMultiWrite.php',
+ 'FileBackendStoreShardListIterator' => 'includes/filerepo/backend/FileBackend.php',
+ 'FSFileBackend' => 'includes/filerepo/backend/FSFileBackend.php',
+ 'FSFileBackendFileList' => 'includes/filerepo/backend/FSFileBackend.php',
+ 'SwiftFileBackend' => 'includes/filerepo/backend/SwiftFileBackend.php',
+ 'SwiftFileBackendFileList' => 'includes/filerepo/backend/SwiftFileBackend.php',
+ 'LockManagerGroup' => 'includes/filerepo/backend/lockmanager/LockManagerGroup.php',
+ 'LockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
+ 'ScopedLock' => 'includes/filerepo/backend/lockmanager/LockManager.php',
+ 'FSLockManager' => 'includes/filerepo/backend/lockmanager/FSLockManager.php',
+ 'DBLockManager' => 'includes/filerepo/backend/lockmanager/DBLockManager.php',
+ 'LSLockManager' => 'includes/filerepo/backend/lockmanager/LSLockManager.php',
+ 'MySqlLockManager'=> 'includes/filerepo/backend/lockmanager/DBLockManager.php',
+ 'NullLockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
+ 'FileOp' => 'includes/filerepo/backend/FileOp.php',
+ 'FileOpScopedPHPTimeout' => 'includes/filerepo/backend/FileOp.php',
+ 'StoreFileOp' => 'includes/filerepo/backend/FileOp.php',
+ 'CopyFileOp' => 'includes/filerepo/backend/FileOp.php',
+ 'MoveFileOp' => 'includes/filerepo/backend/FileOp.php',
+ 'DeleteFileOp' => 'includes/filerepo/backend/FileOp.php',
+ 'ConcatenateFileOp' => 'includes/filerepo/backend/FileOp.php',
+ 'CreateFileOp' => 'includes/filerepo/backend/FileOp.php',
+ 'NullFileOp' => 'includes/filerepo/backend/FileOp.php',
# includes/installer
'CliInstaller' => 'includes/installer/CliInstaller.php',
@@ -527,13 +583,32 @@ $wgAutoloadLocalClasses = array(
'JSMinPlus' => 'includes/libs/jsminplus.php',
'JSParser' => 'includes/libs/jsminplus.php',
+ # includes/logging
+ 'DatabaseLogEntry' => 'includes/logging/LogEntry.php',
+ 'DeleteLogFormatter' => 'includes/logging/LogFormatter.php',
+ 'LegacyLogFormatter' => 'includes/logging/LogFormatter.php',
+ 'LogEntry' => 'includes/logging/LogEntry.php',
+ 'LogEventsList' => 'includes/logging/LogEventsList.php',
+ 'LogEntryBase' => 'includes/logging/LogEntry.php',
+ 'LogFormatter' => 'includes/logging/LogFormatter.php',
+ 'LogPage' => 'includes/logging/LogPage.php',
+ 'LogPager' => 'includes/logging/LogPager.php',
+ 'ManualLogEntry' => 'includes/logging/LogEntry.php',
+ 'MoveLogFormatter' => 'includes/logging/LogFormatter.php',
+ 'NewUsersLogFormatter' => 'includes/logging/LogFormatter.php',
+ 'PatrolLog' => 'includes/logging/PatrolLog.php',
+ 'PatrolLogFormatter' => 'includes/logging/LogFormatter.php',
+ 'RCDatabaseLogEntry' => 'includes/logging/LogEntry.php',
+
# includes/media
'BitmapHandler' => 'includes/media/Bitmap.php',
'BitmapHandler_ClientOnly' => 'includes/media/Bitmap_ClientOnly.php',
'BitmapMetadataHandler' => 'includes/media/BitmapMetadataHandler.php',
'BmpHandler' => 'includes/media/BMP.php',
'DjVuHandler' => 'includes/media/DjVu.php',
+ 'DjVuImage' => 'includes/media/DjVuImage.php',
'Exif' => 'includes/media/Exif.php',
+ 'ExifBitmapHandler' => 'includes/media/ExifBitmap.php',
'FormatExif' => 'includes/media/FormatMetadata.php',
'FormatMetadata' => 'includes/media/FormatMetadata.php',
'GIFHandler' => 'includes/media/GIF.php',
@@ -542,7 +617,6 @@ $wgAutoloadLocalClasses = array(
'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',
@@ -553,6 +627,7 @@ $wgAutoloadLocalClasses = array(
'ThumbnailImage' => 'includes/media/MediaTransformOutput.php',
'TiffHandler' => 'includes/media/Tiff.php',
'TransformParameterError' => 'includes/media/MediaTransformOutput.php',
+ 'XCFHandler' => 'includes/media/XCF.php',
'XMPInfo' => 'includes/media/XMPInfo.php',
'XMPReader' => 'includes/media/XMP.php',
'XMPValidate' => 'includes/media/XMPValidate.php',
@@ -564,7 +639,6 @@ $wgAutoloadLocalClasses = array(
'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',
@@ -648,6 +722,7 @@ $wgAutoloadLocalClasses = array(
'ResourceLoaderNoscriptModule' => 'includes/resourceloader/ResourceLoaderNoscriptModule.php',
'ResourceLoaderSiteModule' => 'includes/resourceloader/ResourceLoaderSiteModule.php',
'ResourceLoaderStartUpModule' => 'includes/resourceloader/ResourceLoaderStartUpModule.php',
+ 'ResourceLoaderUserCSSPrefsModule' => 'includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php',
'ResourceLoaderUserGroupsModule' => 'includes/resourceloader/ResourceLoaderUserGroupsModule.php',
'ResourceLoaderUserModule' => 'includes/resourceloader/ResourceLoaderUserModule.php',
'ResourceLoaderUserOptionsModule' => 'includes/resourceloader/ResourceLoaderUserOptionsModule.php',
@@ -740,6 +815,7 @@ $wgAutoloadLocalClasses = array(
'SpecialBlockme' => 'includes/specials/SpecialBlockme.php',
'SpecialBookSources' => 'includes/specials/SpecialBooksources.php',
'SpecialCategories' => 'includes/specials/SpecialCategories.php',
+ 'SpecialChangeEmail' => 'includes/specials/SpecialChangeEmail.php',
'SpecialChangePassword' => 'includes/specials/SpecialChangePassword.php',
'SpecialComparePages' => 'includes/specials/SpecialComparePages.php',
'SpecialContributions' => 'includes/specials/SpecialContributions.php',
@@ -748,6 +824,7 @@ $wgAutoloadLocalClasses = array(
'SpecialExport' => 'includes/specials/SpecialExport.php',
'SpecialFilepath' => 'includes/specials/SpecialFilepath.php',
'SpecialImport' => 'includes/specials/SpecialImport.php',
+ 'SpecialJavaScriptTest' => 'includes/specials/SpecialJavaScriptTest.php',
'SpecialListFiles' => 'includes/specials/SpecialListfiles.php',
'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php',
'SpecialListUsers' => 'includes/specials/SpecialListusers.php',
@@ -800,12 +877,13 @@ $wgAutoloadLocalClasses = array(
'WithoutInterwikiPage' => 'includes/specials/SpecialWithoutinterwiki.php',
# includes/templates
- 'UsercreateTemplate' => 'includes/templates/Userlogin.php',
'UserloginTemplate' => 'includes/templates/Userlogin.php',
+ 'UsercreateTemplate' => 'includes/templates/Usercreate.php',
# includes/upload
'UploadBase' => 'includes/upload/UploadBase.php',
'UploadFromFile' => 'includes/upload/UploadFromFile.php',
+ 'UploadFromChunks' => 'includes/upload/UploadFromChunks.php',
'UploadFromStash' => 'includes/upload/UploadFromStash.php',
'UploadFromUrl' => 'includes/upload/UploadFromUrl.php',
'UploadStash' => 'includes/upload/UploadStash.php',
@@ -816,6 +894,9 @@ $wgAutoloadLocalClasses = array(
'UploadStashFileNotFoundException' => 'includes/upload/UploadStash.php',
'UploadStashNotAvailableException' => 'includes/upload/UploadStash.php',
'UploadStashZeroLengthFileException' => 'includes/upload/UploadStash.php',
+ 'UploadStashNotLoggedInException' => 'includes/upload/UploadStash.php',
+ 'UploadStashWrongOwnerException' => 'includes/upload/UploadStash.php',
+ 'UploadStashNoSuchKeyException' => 'includes/upload/UploadStash.php',
# languages
'FakeConverter' => 'languages/Language.php',
@@ -832,10 +913,13 @@ $wgAutoloadLocalClasses = array(
'Maintenance' => 'maintenance/Maintenance.php',
'FixExtLinksProtocolRelative' => 'maintenance/fixExtLinksProtocolRelative.php',
'PopulateCategory' => 'maintenance/populateCategory.php',
+ 'PopulateImageSha1' => 'maintenance/populateImageSha1.php',
'PopulateLogSearch' => 'maintenance/populateLogSearch.php',
'PopulateLogUsertext' => 'maintenance/populateLogUsertext.php',
'PopulateParentId' => 'maintenance/populateParentId.php',
'PopulateRevisionLength' => 'maintenance/populateRevisionLength.php',
+ 'PopulateRevisionSha1' => 'maintenance/populateRevisionSha1.php',
+ 'RefreshLinks' => 'maintenance/refreshLinks.php',
'SevenZipStream' => 'maintenance/7zip.inc',
'Sqlite' => 'maintenance/sqlite.inc',
'UpdateCollation' => 'maintenance/updateCollation.php',
@@ -844,15 +928,19 @@ $wgAutoloadLocalClasses = array(
# maintenance/language
'csvStatsOutput' => 'maintenance/language/StatOutputs.php',
+ 'languages' => 'maintenance/language/languages.inc',
+ 'MessageWriter' => 'maintenance/language/writeMessagesArray.inc',
'statsOutput' => 'maintenance/language/StatOutputs.php',
'textStatsOutput' => 'maintenance/language/StatOutputs.php',
'wikiStatsOutput' => 'maintenance/language/StatOutputs.php',
+ # maintenance/term
+ 'AnsiTermColorer' => 'maintenance/term/MWTerm.php',
+ 'DummyTermColorer' => 'maintenance/term/MWTerm.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',
diff --git a/includes/Autopromote.php b/includes/Autopromote.php
index 83f3c20b..a2336030 100644
--- a/includes/Autopromote.php
+++ b/includes/Autopromote.php
@@ -166,9 +166,9 @@ class Autopromote {
$groups = array_slice( $cond, 1 );
return count( array_intersect( $groups, $user->getGroups() ) ) == count( $groups );
case APCOND_ISIP:
- return $cond[1] == wfGetIP();
+ return $cond[1] == $user->getRequest()->getIP();
case APCOND_IPINRANGE:
- return IP::isInRange( wfGetIP(), $cond[1] );
+ return IP::isInRange( $user->getRequest()->getIP(), $cond[1] );
case APCOND_BLOCKED:
return $user->isBlocked();
case APCOND_ISBOT:
diff --git a/includes/BacklinkCache.php b/includes/BacklinkCache.php
index 8d1571ec..d17104f8 100644
--- a/includes/BacklinkCache.php
+++ b/includes/BacklinkCache.php
@@ -22,7 +22,7 @@
* @author Tim Starling
* @copyright © 2009, Tim Starling, Domas Mituzas
* @copyright © 2010, Max Sem
- * @copyright © 2011, Ashar Voultoiz
+ * @copyright © 2011, Antoine Musso
*/
class BacklinkCache {
@@ -75,6 +75,8 @@ class BacklinkCache {
* Serialization handler, diasallows to serialize the database to prevent
* failures after this class is deserialized from cache with dead DB
* connection.
+ *
+ * @return array
*/
function __sleep() {
return array( 'partitionCache', 'fullResultCache', 'title' );
@@ -190,7 +192,13 @@ class BacklinkCache {
if ( isset( $prefixes[$table] ) ) {
return $prefixes[$table];
} else {
- throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
+ $prefix = null;
+ wfRunHooks( 'BacklinkCacheGetPrefix', array( $table, &$prefix ) );
+ if( $prefix ) {
+ return $prefix;
+ } else {
+ throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
+ }
}
}
@@ -237,7 +245,10 @@ class BacklinkCache {
);
break;
default:
- throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
+ $conds = null;
+ wfRunHooks( 'BacklinkCacheGetConditions', array( $table, $this->title, &$conds ) );
+ if( !$conds )
+ throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
}
return $conds;
diff --git a/includes/Block.php b/includes/Block.php
index 27181d86..d80edb5e 100644
--- a/includes/Block.php
+++ b/includes/Block.php
@@ -59,7 +59,7 @@ class Block {
*/
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 )
+ $hideName = 0, $blockEmail = 0, $allowUsertalk = 0, $byText = '' )
{
if( $timestamp === 0 ){
$timestamp = wfTimestampNow();
@@ -71,13 +71,20 @@ class Block {
}
$this->setTarget( $address );
- $this->setBlocker( User::newFromID( $by ) );
+ if ( $this->target instanceof User && $user ) {
+ $this->target->setId( $user ); // needed for foreign users
+ }
+ if ( $by ) { // local user
+ $this->setBlocker( User::newFromID( $by ) );
+ } else { // foreign user
+ $this->setBlocker( $byText );
+ }
$this->mReason = $reason;
$this->mTimestamp = wfTimestamp( TS_MW, $timestamp );
$this->mAuto = $auto;
$this->isHardblock( !$anonOnly );
$this->prevents( 'createaccount', $createAccount );
- if ( $expiry == 'infinity' || $expiry == Block::infinity() ) {
+ if ( $expiry == 'infinity' || $expiry == wfGetDB( DB_SLAVE )->getInfinity() ) {
$this->mExpiry = 'infinity';
} else {
$this->mExpiry = wfTimestamp( TS_MW, $expiry );
@@ -101,6 +108,7 @@ class Block {
* @deprecated since 1.18
*/
public static function newFromDB( $address, $user = 0 ) {
+ wfDeprecated( __METHOD__, '1.18' );
return self::newFromTarget( User::whoIs( $user ), $address );
}
@@ -155,6 +163,7 @@ class Block {
* @deprecated since 1.18
*/
public function clear() {
+ wfDeprecated( __METHOD__, '1.18' );
# Noop
}
@@ -167,7 +176,7 @@ class Block {
* @deprecated since 1.18
*/
public function load( $address = '', $user = 0 ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
if( $user ){
$username = User::whoIs( $user );
$block = self::newFromTarget( $username, $address );
@@ -345,7 +354,11 @@ class Block {
*/
protected function initFromRow( $row ) {
$this->setTarget( $row->ipb_address );
- $this->setBlocker( User::newFromId( $row->ipb_by ) );
+ if ( $row->ipb_by ) { // local user
+ $this->setBlocker( User::newFromID( $row->ipb_by ) );
+ } else { // foreign user
+ $this->setBlocker( $row->ipb_by_text );
+ }
$this->mReason = $row->ipb_reason;
$this->mTimestamp = wfTimestamp( TS_MW, $row->ipb_timestamp );
@@ -418,10 +431,12 @@ class Block {
# Don't collide with expired blocks
Block::purgeExpired();
- $ipb_id = $dbw->nextSequenceValue( 'ipblocks_ipb_id_seq' );
+ $row = $this->getDatabaseArray();
+ $row['ipb_id'] = $dbw->nextSequenceValue("ipblocks_ipb_id_seq");
+
$dbw->insert(
'ipblocks',
- $this->getDatabaseArray(),
+ $row,
__METHOD__,
array( 'IGNORE' )
);
@@ -471,8 +486,8 @@ class Block {
$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_by' => $this->getBy(),
+ 'ipb_by_text' => $this->getByName(),
'ipb_reason' => $this->mReason,
'ipb_timestamp' => $db->timestamp( $this->mTimestamp ),
'ipb_auto' => $this->mAuto,
@@ -761,11 +776,12 @@ class Block {
/**
* Get the user id of the blocking sysop
*
- * @return Integer
+ * @return Integer (0 for foreign users)
*/
public function getBy() {
- return $this->getBlocker() instanceof User
- ? $this->getBlocker()->getId()
+ $blocker = $this->getBlocker();
+ return ( $blocker instanceof User )
+ ? $blocker->getId()
: 0;
}
@@ -775,9 +791,10 @@ class Block {
* @return String
*/
public function getByName() {
- return $this->getBlocker() instanceof User
- ? $this->getBlocker()->getName()
- : null;
+ $blocker = $this->getBlocker();
+ return ( $blocker instanceof User )
+ ? $blocker->getName()
+ : (string)$blocker; // username
}
/**
@@ -795,6 +812,7 @@ class Block {
* @param $x Bool
*/
public function forUpdate( $x = null ) {
+ wfDeprecated( __METHOD__, '1.18' );
# noop
}
@@ -883,6 +901,7 @@ class Block {
* @deprecated since 1.18; use $dbw->encodeExpiry() instead
*/
public static function encodeExpiry( $expiry, $db ) {
+ wfDeprecated( __METHOD__, '1.18' );
return $db->encodeExpiry( $expiry );
}
@@ -892,9 +911,10 @@ class Block {
* @param $expiry String: Database expiry format
* @param $timestampType Int Requested timestamp format
* @return String
- * @deprecated since 1.18; use $wgLang->decodeExpiry() instead
+ * @deprecated since 1.18; use $wgLang->formatExpiry() instead
*/
public static function decodeExpiry( $expiry, $timestampType = TS_MW ) {
+ wfDeprecated( __METHOD__, '1.18' );
global $wgContLang;
return $wgContLang->formatExpiry( $expiry, $timestampType );
}
@@ -919,6 +939,7 @@ class Block {
* @deprecated since 1.18, call IP::sanitizeRange() directly
*/
public static function normaliseRange( $range ) {
+ wfDeprecated( __METHOD__, '1.18' );
return IP::sanitizeRange( $range );
}
@@ -927,7 +948,8 @@ class Block {
*/
public static function purgeExpired() {
$dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'ipblocks', array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
+ $dbw->delete( 'ipblocks',
+ array( 'ipb_expiry < ' . $dbw->addQuotes( $dbw->timestamp() ) ), __METHOD__ );
}
/**
@@ -937,6 +959,7 @@ class Block {
* @return String
*/
public static function infinity() {
+ wfDeprecated( __METHOD__, '1.18' );
return wfGetDB( DB_SLAVE )->getInfinity();
}
@@ -948,6 +971,8 @@ class Block {
* @deprecated since 1.18; use $wgLang->formatExpiry() instead
*/
public static function formatExpiry( $encoded_expiry ) {
+ wfDeprecated( __METHOD__, '1.18' );
+
global $wgContLang;
static $msg = null;
@@ -981,7 +1006,7 @@ class Block {
* @deprecated since 1.18 moved to SpecialBlock::parseExpiryInput()
*/
public static function parseExpiryInput( $expiry ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialBlock::parseExpiryInput( $expiry );
}
@@ -1017,7 +1042,7 @@ class Block {
# passed by some callers (bug 29116)
return null;
- } elseif( in_array( $type, array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE, null ) ) ) {
+ } elseif( in_array( $type, array( Block::TYPE_USER, Block::TYPE_IP, Block::TYPE_RANGE ) ) ) {
$block = new Block();
$block->fromMaster( $fromMaster );
@@ -1027,12 +1052,9 @@ class Block {
if( $block->newLoad( $vagueTarget ) ){
return $block;
- } else {
- return null;
}
- } else {
- return null;
}
+ return null;
}
/**
@@ -1127,6 +1149,15 @@ class Block {
}
/**
+ * @since 1.19
+ *
+ * @return Mixed|string
+ */
+ public function getExpiry() {
+ return $this->mExpiry;
+ }
+
+ /**
* Set the target for this block, and update $this->type accordingly
* @param $target Mixed
*/
@@ -1136,7 +1167,7 @@ class Block {
/**
* Get the user who implemented this block
- * @return User
+ * @return User|string Local User object or string for a foreign user
*/
public function getBlocker(){
return $this->blocker;
@@ -1144,9 +1175,9 @@ class Block {
/**
* Set the user who implemented (or will implement) this block
- * @param $user User
+ * @param $user User|string Local User object or username string for foriegn users
*/
- public function setBlocker( User $user ){
+ public function setBlocker( $user ){
$this->blocker = $user;
}
}
diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php
index 6a0f6132..eab7a356 100644
--- a/includes/CategoryPage.php
+++ b/includes/CategoryPage.php
@@ -17,6 +17,10 @@ class CategoryPage extends Article {
# Subclasses can change this to override the viewer class.
protected $mCategoryViewerClass = 'CategoryViewer';
+ /**
+ * @param $title Title
+ * @return WikiCategoryPage
+ */
protected function newPage( Title $title ) {
// Overload mPage with a category-specific page
return new WikiCategoryPage( $title );
@@ -34,26 +38,28 @@ class CategoryPage extends Article {
}
function view() {
- global $wgRequest, $wgUser;
-
- $diff = $wgRequest->getVal( 'diff' );
- $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
+ $request = $this->getContext()->getRequest();
+ $diff = $request->getVal( 'diff' );
+ $diffOnly = $request->getBool( 'diffonly',
+ $this->getContext()->getUser()->getOption( 'diffonly' ) );
if ( isset( $diff ) && $diffOnly ) {
- return parent::view();
+ parent::view();
+ return;
}
if ( !wfRunHooks( 'CategoryPageView', array( &$this ) ) ) {
return;
}
- if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
+ $title = $this->getTitle();
+ if ( NS_CATEGORY == $title->getNamespace() ) {
$this->openShowCategory();
}
parent::view();
- if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
+ if ( NS_CATEGORY == $title->getNamespace() ) {
$this->closeShowCategory();
}
}
@@ -63,18 +69,17 @@ 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' );
+ $request = $this->getContext()->getRequest();
+ $oldFrom = $request->getVal( 'from' );
+ $oldUntil = $request->getVal( 'until' );
+
+ $reqArray = $request->getValues();
- $reqArray = $wgRequest->getValues();
-
$from = $until = array();
foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
- $from[$type] = $wgRequest->getVal( "{$type}from", $oldFrom );
- $until[$type] = $wgRequest->getVal( "{$type}until", $oldUntil );
+ $from[$type] = $request->getVal( "{$type}from", $oldFrom );
+ $until[$type] = $request->getVal( "{$type}until", $oldUntil );
// Do not want old-style from/until propagating in nav links.
if ( !isset( $reqArray["{$type}from"] ) && isset( $reqArray["from"] ) ) {
@@ -88,652 +93,7 @@ class CategoryPage extends Article {
unset( $reqArray["from"] );
unset( $reqArray["to"] );
- $viewer = new $this->mCategoryViewerClass( $this->mTitle, $from, $until, $reqArray );
- $wgOut->addHTML( $viewer->getHTML() );
- }
-}
-
-class CategoryViewer {
- var $limit, $from, $until,
- $articles, $articles_start_char,
- $children, $children_start_char,
- $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.
- * @var array
- */
- private $query;
-
- function __construct( $title, $from = '', $until = '', $query = array() ) {
- global $wgCategoryPagingLimit;
- $this->title = $title;
- $this->from = $from;
- $this->until = $until;
- $this->limit = $wgCategoryPagingLimit;
- $this->cat = Category::newFromTitle( $title );
- $this->query = $query;
- $this->collation = Collation::singleton();
- unset( $this->query['title'] );
- }
-
- /**
- * Format the category data list.
- *
- * @return string HTML output
- */
- public function getHTML() {
- global $wgOut, $wgCategoryMagicGallery, $wgLang, $wgContLang;
- wfProfileIn( __METHOD__ );
-
- $this->showGallery = $wgCategoryMagicGallery && !$wgOut->mNoGallery;
-
- $this->clearCategoryState();
- $this->doCategoryQuery();
- $this->finaliseCategoryState();
-
- $r = $this->getSubcategorySection() .
- $this->getPagesSection() .
- $this->getImageSection();
-
- if ( $r == '' ) {
- // If there is no category content to display, only
- // show the top part of the navigation links.
- // @todo FIXME: Cannot be completely suppressed because it
- // is unknown if 'until' or 'from' makes this
- // give 0 results.
- $r = $r . $this->getCategoryTop();
- } else {
- $r = $this->getCategoryTop() .
- $r .
- $this->getCategoryBottom();
- }
-
- // Give a proper message if category is empty
- if ( $r == '' ) {
- $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 );
- }
-
- function clearCategoryState() {
- $this->articles = array();
- $this->articles_start_char = array();
- $this->children = array();
- $this->children_start_char = array();
- if ( $this->showGallery ) {
- $this->gallery = new ImageGallery();
- $this->gallery->setHideBadImages();
- } else {
- $this->imgsNoGallery = array();
- $this->imgsNoGallery_start_char = array();
- }
- }
-
- /**
- * Add a subcategory to the internal lists, using a Category object
- */
- function addSubcategoryObject( Category $cat, $sortkey, $pageLength ) {
- // Subcategory; strip the 'Category' namespace from the link text.
- $title = $cat->getTitle();
-
- $link = Linker::link( $title, htmlspecialchars( $title->getText() ) );
- if ( $title->isRedirect() ) {
- // This didn't used to add redirect-in-category, but might
- // as well be consistent with the rest of the sections
- // on a category page.
- $link = '<span class="redirect-in-category">' . $link . '</span>';
- }
- $this->children[] = $link;
-
- $this->children_start_char[] =
- $this->getSubcategorySortChar( $cat->getTitle(), $sortkey );
- }
-
- /**
- * Add a subcategory to the internal lists, using a title object
- * @deprecated since 1.17 kept for compatibility, please use addSubcategoryObject instead
- */
- function addSubcategory( Title $title, $sortkey, $pageLength ) {
- $this->addSubcategoryObject( Category::newFromTitle( $title ), $sortkey, $pageLength );
- }
-
- /**
- * Get the character to be used for sorting subcategories.
- * If there's a link from Category:A to Category:B, the sortkey of the resulting
- * entry in the categorylinks table is Category:A, not A, which it SHOULD be.
- * Workaround: If sortkey == "Category:".$title, than use $title for sorting,
- * else use sortkey...
- *
- * @param Title $title
- * @param string $sortkey The human-readable sortkey (before transforming to icu or whatever).
- */
- function getSubcategorySortChar( $title, $sortkey ) {
- global $wgContLang;
-
- if ( $title->getPrefixedText() == $sortkey ) {
- $word = $title->getDBkey();
- } else {
- $word = $sortkey;
- }
-
- $firstChar = $this->collation->getFirstLetter( $word );
-
- return $wgContLang->convert( $firstChar );
- }
-
- /**
- * Add a page in the image namespace
- */
- function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) {
- global $wgContLang;
- if ( $this->showGallery ) {
- $flip = $this->flip['file'];
- if ( $flip ) {
- $this->gallery->insert( $title );
- } else {
- $this->gallery->add( $title );
- }
- } else {
- $link = Linker::link( $title );
- if ( $isRedirect ) {
- // This seems kind of pointless given 'mw-redirect' class,
- // but keeping for back-compatibility with user css.
- $link = '<span class="redirect-in-category">' . $link . '</span>';
- }
- $this->imgsNoGallery[] = $link;
-
- $this->imgsNoGallery_start_char[] = $wgContLang->convert(
- $this->collation->getFirstLetter( $sortkey ) );
- }
- }
-
- /**
- * Add a miscellaneous page
- */
- function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) {
- global $wgContLang;
-
- $link = Linker::link( $title );
- if ( $isRedirect ) {
- // This seems kind of pointless given 'mw-redirect' class,
- // but keeping for back-compatiability with user css.
- $link = '<span class="redirect-in-category">' . $link . '</span>';
- }
- $this->articles[] = $link;
-
- $this->articles_start_char[] = $wgContLang->convert(
- $this->collation->getFirstLetter( $sortkey ) );
- }
-
- function finaliseCategoryState() {
- if ( $this->flip['subcat'] ) {
- $this->children = array_reverse( $this->children );
- $this->children_start_char = array_reverse( $this->children_start_char );
- }
- if ( $this->flip['page'] ) {
- $this->articles = array_reverse( $this->articles );
- $this->articles_start_char = array_reverse( $this->articles_start_char );
- }
- if ( !$this->showGallery && $this->flip['file'] ) {
- $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
- $this->imgsNoGallery_start_char = array_reverse( $this->imgsNoGallery_start_char );
- }
- }
-
- function doCategoryQuery() {
- $dbr = wfGetDB( DB_SLAVE, 'category' );
-
- $this->nextPage = array(
- 'page' => null,
- 'subcat' => null,
- 'file' => null,
- );
- $this->flip = array( 'page' => false, 'subcat' => false, 'file' => false );
-
- foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
- # Get the sortkeys for start/end, if applicable. Note that if
- # the collation in the database differs from the one
- # set in $wgCategoryCollation, pagination might go totally haywire.
- $extraConds = array( 'cl_type' => $type );
- if ( $this->from[$type] !== null ) {
- $extraConds[] = 'cl_sortkey >= '
- . $dbr->addQuotes( $this->collation->getSortKey( $this->from[$type] ) );
- } elseif ( $this->until[$type] !== null ) {
- $extraConds[] = 'cl_sortkey < '
- . $dbr->addQuotes( $this->collation->getSortKey( $this->until[$type] ) );
- $this->flip[$type] = true;
- }
-
- $res = $dbr->select(
- array( 'page', 'categorylinks', 'category' ),
- array( 'page_id', 'page_title', 'page_namespace', 'page_len',
- 'page_is_redirect', 'cl_sortkey', 'cat_id', 'cat_title',
- 'cat_subcats', 'cat_pages', 'cat_files',
- 'cl_sortkey_prefix', 'cl_collation' ),
- array_merge( array( 'cl_to' => $this->title->getDBkey() ), $extraConds ),
- __METHOD__,
- array(
- 'USE INDEX' => array( 'categorylinks' => 'cl_sortkey' ),
- 'LIMIT' => $this->limit + 1,
- 'ORDER BY' => $this->flip[$type] ? 'cl_sortkey DESC' : 'cl_sortkey',
- ),
- array(
- 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ),
- 'category' => array( 'LEFT JOIN', 'cat_title = page_title AND page_namespace = ' . NS_CATEGORY )
- )
- );
-
- $count = 0;
- foreach ( $res as $row ) {
- $title = Title::newFromRow( $row );
- if ( $row->cl_collation === '' ) {
- // Hack to make sure that while updating from 1.16 schema
- // and db is inconsistent, that the sky doesn't fall.
- // See r83544. Could perhaps be removed in a couple decades...
- $humanSortkey = $row->cl_sortkey;
- } else {
- $humanSortkey = $title->getCategorySortkey( $row->cl_sortkey_prefix );
- }
-
- if ( ++$count > $this->limit ) {
- # We've reached the one extra which shows that there
- # are additional pages to be had. Stop here...
- $this->nextPage[$type] = $humanSortkey;
- break;
- }
-
- if ( $title->getNamespace() == NS_CATEGORY ) {
- $cat = Category::newFromRow( $row, $title );
- $this->addSubcategoryObject( $cat, $humanSortkey, $row->page_len );
- } elseif ( $title->getNamespace() == NS_FILE ) {
- $this->addImage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
- } else {
- $this->addPage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
- }
- }
- }
- }
-
- function getCategoryTop() {
- $r = $this->getCategoryBottom();
- return $r === ''
- ? $r
- : "<br style=\"clear:both;\"/>\n" . $r;
- }
-
- function getSubcategorySection() {
- # Don't show subcategories section if there are none.
- $r = '';
- $rescnt = count( $this->children );
- $dbcnt = $this->cat->getSubcatCount();
- $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'subcat' );
-
- if ( $rescnt > 0 ) {
- # Showing subcategories
- $r .= "<div id=\"mw-subcategories\">\n";
- $r .= '<h2>' . wfMsg( 'subcategories' ) . "</h2>\n";
- $r .= $countmsg;
- $r .= $this->getSectionPagingLinks( 'subcat' );
- $r .= $this->formatList( $this->children, $this->children_start_char );
- $r .= $this->getSectionPagingLinks( 'subcat' );
- $r .= "\n</div>";
- }
- return $r;
- }
-
- function getPagesSection() {
- $ti = htmlspecialchars( $this->title->getText() );
- # Don't show articles section if there are none.
- $r = '';
-
- # @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.
- $dbcnt = $this->cat->getPageCount() - $this->cat->getSubcatCount()
- - $this->cat->getFileCount();
- $rescnt = count( $this->articles );
- $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'article' );
-
- if ( $rescnt > 0 ) {
- $r = "<div id=\"mw-pages\">\n";
- $r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n";
- $r .= $countmsg;
- $r .= $this->getSectionPagingLinks( 'page' );
- $r .= $this->formatList( $this->articles, $this->articles_start_char );
- $r .= $this->getSectionPagingLinks( 'page' );
- $r .= "\n</div>";
- }
- return $r;
- }
-
- function getImageSection() {
- $r = '';
- $rescnt = $this->showGallery ? $this->gallery->count() : count( $this->imgsNoGallery );
- if ( $rescnt > 0 ) {
- $dbcnt = $this->cat->getFileCount();
- $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' );
-
- $r .= "<div id=\"mw-category-media\">\n";
- $r .= '<h2>' . wfMsg( 'category-media-header', htmlspecialchars( $this->title->getText() ) ) . "</h2>\n";
- $r .= $countmsg;
- $r .= $this->getSectionPagingLinks( 'file' );
- if ( $this->showGallery ) {
- $r .= $this->gallery->toHTML();
- } else {
- $r .= $this->formatList( $this->imgsNoGallery, $this->imgsNoGallery_start_char );
- }
- $r .= $this->getSectionPagingLinks( 'file' );
- $r .= "\n</div>";
- }
- return $r;
- }
-
- /**
- * Get the paging links for a section (subcats/pages/files), to go at the top and bottom
- * of the output.
- *
- * @param $type String: 'page', 'subcat', or 'file'
- * @return String: HTML output, possibly empty if there are no other pages
- */
- private function getSectionPagingLinks( $type ) {
- if ( $this->until[$type] !== null ) {
- return $this->pagingLinks( $this->nextPage[$type], $this->until[$type], $type );
- } elseif ( $this->nextPage[$type] !== null || $this->from[$type] !== null ) {
- return $this->pagingLinks( $this->from[$type], $this->nextPage[$type], $type );
- } else {
- return '';
- }
- }
-
- function getCategoryBottom() {
- return '';
- }
-
- /**
- * Format a list of articles chunked by letter, either as a
- * bullet list or a columnar format, depending on the length.
- *
- * @param $articles Array
- * @param $articles_start_char Array
- * @param $cutoff Int
- * @return String
- * @private
- */
- function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
- $list = '';
- if ( count ( $articles ) > $cutoff ) {
- $list = self::columnList( $articles, $articles_start_char );
- } elseif ( count( $articles ) > 0 ) {
- // for short lists of articles in categories.
- $list = self::shortList( $articles, $articles_start_char );
- }
-
- $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;
- }
-
- /**
- * Format a list of articles chunked by letter in a three-column
- * list, ordered vertically.
- *
- * TODO: Take the headers into account when creating columns, so they're
- * more visually equal.
- *
- * More distant TODO: Scrap this and use CSS columns, whenever IE finally
- * supports those.
- *
- * @param $articles Array
- * @param $articles_start_char Array
- * @return String
- * @private
- */
- static function columnList( $articles, $articles_start_char ) {
- $columns = array_combine( $articles, $articles_start_char );
- # Split into three columns
- $columns = array_chunk( $columns, ceil( count( $columns ) / 3 ), true /* preserve keys */ );
-
- $ret = '<table width="100%"><tr valign="top"><td>';
- $prevchar = null;
-
- foreach ( $columns as $column ) {
- $colContents = array();
-
- # Kind of like array_flip() here, but we keep duplicates in an
- # array instead of dropping them.
- foreach ( $column as $article => $char ) {
- if ( !isset( $colContents[$char] ) ) {
- $colContents[$char] = array();
- }
- $colContents[$char][] = $article;
- }
-
- $first = true;
- foreach ( $colContents as $char => $articles ) {
- $ret .= '<h3>' . htmlspecialchars( $char );
- if ( $first && $char === $prevchar ) {
- # We're continuing a previous chunk at the top of a new
- # column, so add " cont." after the letter.
- $ret .= ' ' . wfMsgHtml( 'listingcontinuesabbrev' );
- }
- $ret .= "</h3>\n";
-
- $ret .= '<ul><li>';
- $ret .= implode( "</li>\n<li>", $articles );
- $ret .= '</li></ul>';
-
- $first = false;
- $prevchar = $char;
- }
-
- $ret .= "</td>\n<td>";
- }
-
- $ret .= '</td></tr></table>';
- return $ret;
- }
-
- /**
- * Format a list of articles chunked by letter in a bullet list.
- * @param $articles Array
- * @param $articles_start_char Array
- * @return String
- * @private
- */
- static function shortList( $articles, $articles_start_char ) {
- $r = '<h3>' . htmlspecialchars( $articles_start_char[0] ) . "</h3>\n";
- $r .= '<ul><li>' . $articles[0] . '</li>';
- for ( $index = 1; $index < count( $articles ); $index++ ) {
- if ( $articles_start_char[$index] != $articles_start_char[$index - 1] ) {
- $r .= "</ul><h3>" . htmlspecialchars( $articles_start_char[$index] ) . "</h3>\n<ul>";
- }
-
- $r .= "<li>{$articles[$index]}</li>";
- }
- $r .= '</ul>';
- return $r;
- }
-
- /**
- * Create paging links, as a helper method to getSectionPagingLinks().
- *
- * @param $first String The 'until' parameter for the generated URL
- * @param $last String The 'from' parameter for the genererated URL
- * @param $type String A prefix for parameters, 'page' or 'subcat' or
- * 'file'
- * @return String HTML
- */
- private function pagingLinks( $first, $last, $type = '' ) {
- global $wgLang;
-
- $limitText = $wgLang->formatNum( $this->limit );
-
- $prevLink = wfMsgExt( 'prevn', array( 'escape', 'parsemag' ), $limitText );
-
- if ( $first != '' ) {
- $prevQuery = $this->query;
- $prevQuery["{$type}until"] = $first;
- unset( $prevQuery["{$type}from"] );
- $prevLink = Linker::linkKnown(
- $this->addFragmentToTitle( $this->title, $type ),
- $prevLink,
- array(),
- $prevQuery
- );
- }
-
- $nextLink = wfMsgExt( 'nextn', array( 'escape', 'parsemag' ), $limitText );
-
- if ( $last != '' ) {
- $lastQuery = $this->query;
- $lastQuery["{$type}from"] = $last;
- unset( $lastQuery["{$type}until"] );
- $nextLink = Linker::linkKnown(
- $this->addFragmentToTitle( $this->title, $type ),
- $nextLink,
- array(),
- $lastQuery
- );
- }
-
- return "($prevLink) ($nextLink)";
- }
-
- /**
- * 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 independently
- * of the other types.
- *
- * Note for grepping: uses the messages category-article-count,
- * category-article-count-limited, category-subcat-count,
- * category-subcat-count-limited, category-file-count,
- * category-file-count-limited.
- *
- * @param $rescnt Int: The number of items returned by our database query.
- * @param $dbcnt Int: The number of items according to the category table.
- * @param $type String: 'subcat', 'article', or 'file'
- * @return String: A message giving the number of items, to output to HTML.
- */
- private function getCountMessage( $rescnt, $dbcnt, $type ) {
- global $wgLang;
- # There are three cases:
- # 1) The category table figure seems sane. It might be wrong, but
- # we can't do anything about it if we don't recalculate it on ev-
- # ery category view.
- # 2) The category table figure isn't sane, like it's smaller than the
- # number of actual results, *but* the number of results is less
- # than $this->limit and there's no offset. In this case we still
- # know the right figure.
- # 3) We have no idea.
-
- # Check if there's a "from" or "until" for anything
-
- // This is a little ugly, but we seem to use different names
- // for the paging types then for the messages.
- if ( $type === 'article' ) {
- $pagingType = 'page';
- } else {
- $pagingType = $type;
- }
-
- $fromOrUntil = false;
- if ( $this->from[$pagingType] !== null || $this->until[$pagingType] !== null ) {
- $fromOrUntil = true;
- }
-
- if ( $dbcnt == $rescnt || ( ( $rescnt == $this->limit || $fromOrUntil )
- && $dbcnt > $rescnt ) ) {
- # Case 1: seems sane.
- $totalcnt = $dbcnt;
- } elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
- # Case 2: not sane, but salvageable. Use the number of results.
- # Since there are fewer than 200, we can also take this opportunity
- # to refresh the incorrect category table entry -- which should be
- # quick due to the small number of entries.
- $totalcnt = $rescnt;
- $this->cat->refreshCounts();
- } else {
- # Case 3: hopeless. Don't give a total count at all.
- return wfMsgExt( "category-$type-count-limited", 'parse',
- $wgLang->formatNum( $rescnt ) );
- }
- return wfMsgExt(
- "category-$type-count",
- 'parse',
- $wgLang->formatNum( $rescnt ),
- $wgLang->formatNum( $totalcnt )
- );
+ $viewer = new $this->mCategoryViewerClass( $this->getContext()->getTitle(), $this->getContext(), $from, $until, $reqArray );
+ $this->getContext()->getOutput()->addHTML( $viewer->getHTML() );
}
}
diff --git a/includes/CategoryViewer.php b/includes/CategoryViewer.php
new file mode 100644
index 00000000..e8e91423
--- /dev/null
+++ b/includes/CategoryViewer.php
@@ -0,0 +1,677 @@
+<?php
+
+if ( !defined( 'MEDIAWIKI' ) )
+ die( 1 );
+
+class CategoryViewer extends ContextSource {
+ var $limit, $from, $until,
+ $articles, $articles_start_char,
+ $children, $children_start_char,
+ $showGallery, $imgsNoGalley,
+ $imgsNoGallery_start_char,
+ $imgsNoGallery;
+
+ /**
+ * @var Array
+ */
+ 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.
+ * @var array
+ */
+ private $query;
+
+ /**
+ * Constructor
+ *
+ * @since 1.19 $context is a second, required parameter
+ * @param $title Title
+ * @param $context IContextSource
+ * @param $from String
+ * @param $until String
+ * @param $query Array
+ */
+ function __construct( $title, IContextSource $context, $from = '', $until = '', $query = array() ) {
+ global $wgCategoryPagingLimit;
+ $this->title = $title;
+ $this->setContext( $context );
+ $this->from = $from;
+ $this->until = $until;
+ $this->limit = $wgCategoryPagingLimit;
+ $this->cat = Category::newFromTitle( $title );
+ $this->query = $query;
+ $this->collation = Collation::singleton();
+ unset( $this->query['title'] );
+ }
+
+ /**
+ * Format the category data list.
+ *
+ * @return string HTML output
+ */
+ public function getHTML() {
+ global $wgCategoryMagicGallery;
+ wfProfileIn( __METHOD__ );
+
+ $this->showGallery = $wgCategoryMagicGallery && !$this->getOutput()->mNoGallery;
+
+ $this->clearCategoryState();
+ $this->doCategoryQuery();
+ $this->finaliseCategoryState();
+
+ $r = $this->getSubcategorySection() .
+ $this->getPagesSection() .
+ $this->getImageSection();
+
+ if ( $r == '' ) {
+ // If there is no category content to display, only
+ // show the top part of the navigation links.
+ // @todo FIXME: Cannot be completely suppressed because it
+ // is unknown if 'until' or 'from' makes this
+ // give 0 results.
+ $r = $r . $this->getCategoryTop();
+ } else {
+ $r = $this->getCategoryTop() .
+ $r .
+ $this->getCategoryBottom();
+ }
+
+ // Give a proper message if category is empty
+ if ( $r == '' ) {
+ $r = wfMsgExt( 'category-empty', array( 'parse' ) );
+ }
+
+ $lang = $this->getLanguage();
+ $langAttribs = array( 'lang' => $lang->getCode(), 'dir' => $lang->getDir() );
+ # put a div around the headings which are in the user language
+ $r = Html::openElement( 'div', $langAttribs ) . $r . '</div>';
+
+ wfProfileOut( __METHOD__ );
+ return $r;
+ }
+
+ function clearCategoryState() {
+ $this->articles = array();
+ $this->articles_start_char = array();
+ $this->children = array();
+ $this->children_start_char = array();
+ if ( $this->showGallery ) {
+ $this->gallery = new ImageGallery();
+ $this->gallery->setHideBadImages();
+ } else {
+ $this->imgsNoGallery = array();
+ $this->imgsNoGallery_start_char = array();
+ }
+ }
+
+ /**
+ * Add a subcategory to the internal lists, using a Category object
+ * @param $cat Category
+ * @param $sortkey
+ * @param $pageLength
+ */
+ function addSubcategoryObject( Category $cat, $sortkey, $pageLength ) {
+ // Subcategory; strip the 'Category' namespace from the link text.
+ $title = $cat->getTitle();
+
+ $link = Linker::link( $title, htmlspecialchars( $title->getText() ) );
+ if ( $title->isRedirect() ) {
+ // This didn't used to add redirect-in-category, but might
+ // as well be consistent with the rest of the sections
+ // on a category page.
+ $link = '<span class="redirect-in-category">' . $link . '</span>';
+ }
+ $this->children[] = $link;
+
+ $this->children_start_char[] =
+ $this->getSubcategorySortChar( $cat->getTitle(), $sortkey );
+ }
+
+ /**
+ * Add a subcategory to the internal lists, using a title object
+ * @deprecated since 1.17 kept for compatibility, please use addSubcategoryObject instead
+ */
+ function addSubcategory( Title $title, $sortkey, $pageLength ) {
+ wfDeprecated( __METHOD__, '1.17' );
+ $this->addSubcategoryObject( Category::newFromTitle( $title ), $sortkey, $pageLength );
+ }
+
+ /**
+ * Get the character to be used for sorting subcategories.
+ * If there's a link from Category:A to Category:B, the sortkey of the resulting
+ * entry in the categorylinks table is Category:A, not A, which it SHOULD be.
+ * Workaround: If sortkey == "Category:".$title, than use $title for sorting,
+ * else use sortkey...
+ *
+ * @param Title $title
+ * @param string $sortkey The human-readable sortkey (before transforming to icu or whatever).
+ */
+ function getSubcategorySortChar( $title, $sortkey ) {
+ global $wgContLang;
+
+ if ( $title->getPrefixedText() == $sortkey ) {
+ $word = $title->getDBkey();
+ } else {
+ $word = $sortkey;
+ }
+
+ $firstChar = $this->collation->getFirstLetter( $word );
+
+ return $wgContLang->convert( $firstChar );
+ }
+
+ /**
+ * Add a page in the image namespace
+ * @param $title Title
+ * @param $sortkey
+ * @param $pageLength
+ * @param $isRedirect bool
+ */
+ function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) {
+ global $wgContLang;
+ if ( $this->showGallery ) {
+ $flip = $this->flip['file'];
+ if ( $flip ) {
+ $this->gallery->insert( $title );
+ } else {
+ $this->gallery->add( $title );
+ }
+ } else {
+ $link = Linker::link( $title );
+ if ( $isRedirect ) {
+ // This seems kind of pointless given 'mw-redirect' class,
+ // but keeping for back-compatibility with user css.
+ $link = '<span class="redirect-in-category">' . $link . '</span>';
+ }
+ $this->imgsNoGallery[] = $link;
+
+ $this->imgsNoGallery_start_char[] = $wgContLang->convert(
+ $this->collation->getFirstLetter( $sortkey ) );
+ }
+ }
+
+ /**
+ * Add a miscellaneous page
+ * @param $title
+ * @param $sortkey
+ * @param $pageLength
+ * @param $isRedirect bool
+ */
+ function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) {
+ global $wgContLang;
+
+ $link = Linker::link( $title );
+ if ( $isRedirect ) {
+ // This seems kind of pointless given 'mw-redirect' class,
+ // but keeping for back-compatiability with user css.
+ $link = '<span class="redirect-in-category">' . $link . '</span>';
+ }
+ $this->articles[] = $link;
+
+ $this->articles_start_char[] = $wgContLang->convert(
+ $this->collation->getFirstLetter( $sortkey ) );
+ }
+
+ function finaliseCategoryState() {
+ if ( $this->flip['subcat'] ) {
+ $this->children = array_reverse( $this->children );
+ $this->children_start_char = array_reverse( $this->children_start_char );
+ }
+ if ( $this->flip['page'] ) {
+ $this->articles = array_reverse( $this->articles );
+ $this->articles_start_char = array_reverse( $this->articles_start_char );
+ }
+ if ( !$this->showGallery && $this->flip['file'] ) {
+ $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
+ $this->imgsNoGallery_start_char = array_reverse( $this->imgsNoGallery_start_char );
+ }
+ }
+
+ function doCategoryQuery() {
+ $dbr = wfGetDB( DB_SLAVE, 'category' );
+
+ $this->nextPage = array(
+ 'page' => null,
+ 'subcat' => null,
+ 'file' => null,
+ );
+ $this->flip = array( 'page' => false, 'subcat' => false, 'file' => false );
+
+ foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
+ # Get the sortkeys for start/end, if applicable. Note that if
+ # the collation in the database differs from the one
+ # set in $wgCategoryCollation, pagination might go totally haywire.
+ $extraConds = array( 'cl_type' => $type );
+ if ( $this->from[$type] !== null ) {
+ $extraConds[] = 'cl_sortkey >= '
+ . $dbr->addQuotes( $this->collation->getSortKey( $this->from[$type] ) );
+ } elseif ( $this->until[$type] !== null ) {
+ $extraConds[] = 'cl_sortkey < '
+ . $dbr->addQuotes( $this->collation->getSortKey( $this->until[$type] ) );
+ $this->flip[$type] = true;
+ }
+
+ $res = $dbr->select(
+ array( 'page', 'categorylinks', 'category' ),
+ array( 'page_id', 'page_title', 'page_namespace', 'page_len',
+ 'page_is_redirect', 'cl_sortkey', 'cat_id', 'cat_title',
+ 'cat_subcats', 'cat_pages', 'cat_files',
+ 'cl_sortkey_prefix', 'cl_collation' ),
+ array_merge( array( 'cl_to' => $this->title->getDBkey() ), $extraConds ),
+ __METHOD__,
+ array(
+ 'USE INDEX' => array( 'categorylinks' => 'cl_sortkey' ),
+ 'LIMIT' => $this->limit + 1,
+ 'ORDER BY' => $this->flip[$type] ? 'cl_sortkey DESC' : 'cl_sortkey',
+ ),
+ array(
+ 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ),
+ 'category' => array( 'LEFT JOIN', 'cat_title = page_title AND page_namespace = ' . NS_CATEGORY )
+ )
+ );
+
+ $count = 0;
+ foreach ( $res as $row ) {
+ $title = Title::newFromRow( $row );
+ if ( $row->cl_collation === '' ) {
+ // Hack to make sure that while updating from 1.16 schema
+ // and db is inconsistent, that the sky doesn't fall.
+ // See r83544. Could perhaps be removed in a couple decades...
+ $humanSortkey = $row->cl_sortkey;
+ } else {
+ $humanSortkey = $title->getCategorySortkey( $row->cl_sortkey_prefix );
+ }
+
+ if ( ++$count > $this->limit ) {
+ # We've reached the one extra which shows that there
+ # are additional pages to be had. Stop here...
+ $this->nextPage[$type] = $humanSortkey;
+ break;
+ }
+
+ if ( $title->getNamespace() == NS_CATEGORY ) {
+ $cat = Category::newFromRow( $row, $title );
+ $this->addSubcategoryObject( $cat, $humanSortkey, $row->page_len );
+ } elseif ( $title->getNamespace() == NS_FILE ) {
+ $this->addImage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
+ } else {
+ $this->addPage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
+ }
+ }
+ }
+ }
+
+ /**
+ * @return string
+ */
+ function getCategoryTop() {
+ $r = $this->getCategoryBottom();
+ return $r === ''
+ ? $r
+ : "<br style=\"clear:both;\"/>\n" . $r;
+ }
+
+ /**
+ * @return string
+ */
+ function getSubcategorySection() {
+ # Don't show subcategories section if there are none.
+ $r = '';
+ $rescnt = count( $this->children );
+ $dbcnt = $this->cat->getSubcatCount();
+ $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'subcat' );
+
+ if ( $rescnt > 0 ) {
+ # Showing subcategories
+ $r .= "<div id=\"mw-subcategories\">\n";
+ $r .= '<h2>' . wfMsg( 'subcategories' ) . "</h2>\n";
+ $r .= $countmsg;
+ $r .= $this->getSectionPagingLinks( 'subcat' );
+ $r .= $this->formatList( $this->children, $this->children_start_char );
+ $r .= $this->getSectionPagingLinks( 'subcat' );
+ $r .= "\n</div>";
+ }
+ return $r;
+ }
+
+ /**
+ * @return string
+ */
+ function getPagesSection() {
+ $ti = htmlspecialchars( $this->title->getText() );
+ # Don't show articles section if there are none.
+ $r = '';
+
+ # @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.
+ $dbcnt = $this->cat->getPageCount() - $this->cat->getSubcatCount()
+ - $this->cat->getFileCount();
+ $rescnt = count( $this->articles );
+ $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'article' );
+
+ if ( $rescnt > 0 ) {
+ $r = "<div id=\"mw-pages\">\n";
+ $r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n";
+ $r .= $countmsg;
+ $r .= $this->getSectionPagingLinks( 'page' );
+ $r .= $this->formatList( $this->articles, $this->articles_start_char );
+ $r .= $this->getSectionPagingLinks( 'page' );
+ $r .= "\n</div>";
+ }
+ return $r;
+ }
+
+ /**
+ * @return string
+ */
+ function getImageSection() {
+ $r = '';
+ $rescnt = $this->showGallery ? $this->gallery->count() : count( $this->imgsNoGallery );
+ if ( $rescnt > 0 ) {
+ $dbcnt = $this->cat->getFileCount();
+ $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' );
+
+ $r .= "<div id=\"mw-category-media\">\n";
+ $r .= '<h2>' . wfMsg( 'category-media-header', htmlspecialchars( $this->title->getText() ) ) . "</h2>\n";
+ $r .= $countmsg;
+ $r .= $this->getSectionPagingLinks( 'file' );
+ if ( $this->showGallery ) {
+ $r .= $this->gallery->toHTML();
+ } else {
+ $r .= $this->formatList( $this->imgsNoGallery, $this->imgsNoGallery_start_char );
+ }
+ $r .= $this->getSectionPagingLinks( 'file' );
+ $r .= "\n</div>";
+ }
+ return $r;
+ }
+
+ /**
+ * Get the paging links for a section (subcats/pages/files), to go at the top and bottom
+ * of the output.
+ *
+ * @param $type String: 'page', 'subcat', or 'file'
+ * @return String: HTML output, possibly empty if there are no other pages
+ */
+ private function getSectionPagingLinks( $type ) {
+ if ( $this->until[$type] !== null ) {
+ return $this->pagingLinks( $this->nextPage[$type], $this->until[$type], $type );
+ } elseif ( $this->nextPage[$type] !== null || $this->from[$type] !== null ) {
+ return $this->pagingLinks( $this->from[$type], $this->nextPage[$type], $type );
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * @return string
+ */
+ function getCategoryBottom() {
+ return '';
+ }
+
+ /**
+ * Format a list of articles chunked by letter, either as a
+ * bullet list or a columnar format, depending on the length.
+ *
+ * @param $articles Array
+ * @param $articles_start_char Array
+ * @param $cutoff Int
+ * @return String
+ * @private
+ */
+ function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
+ $list = '';
+ if ( count ( $articles ) > $cutoff ) {
+ $list = self::columnList( $articles, $articles_start_char );
+ } elseif ( count( $articles ) > 0 ) {
+ // for short lists of articles in categories.
+ $list = self::shortList( $articles, $articles_start_char );
+ }
+
+ $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;
+ }
+
+ /**
+ * Format a list of articles chunked by letter in a three-column
+ * list, ordered vertically.
+ *
+ * TODO: Take the headers into account when creating columns, so they're
+ * more visually equal.
+ *
+ * More distant TODO: Scrap this and use CSS columns, whenever IE finally
+ * supports those.
+ *
+ * @param $articles Array
+ * @param $articles_start_char Array
+ * @return String
+ * @private
+ */
+ static function columnList( $articles, $articles_start_char ) {
+ $columns = array_combine( $articles, $articles_start_char );
+ # Split into three columns
+ $columns = array_chunk( $columns, ceil( count( $columns ) / 3 ), true /* preserve keys */ );
+
+ $ret = '<table width="100%"><tr valign="top">';
+ $prevchar = null;
+
+ foreach ( $columns as $column ) {
+ $ret .= '<td width="33.3%">';
+ $colContents = array();
+
+ # Kind of like array_flip() here, but we keep duplicates in an
+ # array instead of dropping them.
+ foreach ( $column as $article => $char ) {
+ if ( !isset( $colContents[$char] ) ) {
+ $colContents[$char] = array();
+ }
+ $colContents[$char][] = $article;
+ }
+
+ $first = true;
+ foreach ( $colContents as $char => $articles ) {
+ $ret .= '<h3>' . htmlspecialchars( $char );
+ if ( $first && $char === $prevchar ) {
+ # We're continuing a previous chunk at the top of a new
+ # column, so add " cont." after the letter.
+ $ret .= ' ' . wfMsgHtml( 'listingcontinuesabbrev' );
+ }
+ $ret .= "</h3>\n";
+
+ $ret .= '<ul><li>';
+ $ret .= implode( "</li>\n<li>", $articles );
+ $ret .= '</li></ul>';
+
+ $first = false;
+ $prevchar = $char;
+ }
+
+ $ret .= "</td>\n";
+ }
+
+ $ret .= '</tr></table>';
+ return $ret;
+ }
+
+ /**
+ * Format a list of articles chunked by letter in a bullet list.
+ * @param $articles Array
+ * @param $articles_start_char Array
+ * @return String
+ * @private
+ */
+ static function shortList( $articles, $articles_start_char ) {
+ $r = '<h3>' . htmlspecialchars( $articles_start_char[0] ) . "</h3>\n";
+ $r .= '<ul><li>' . $articles[0] . '</li>';
+ for ( $index = 1; $index < count( $articles ); $index++ ) {
+ if ( $articles_start_char[$index] != $articles_start_char[$index - 1] ) {
+ $r .= "</ul><h3>" . htmlspecialchars( $articles_start_char[$index] ) . "</h3>\n<ul>";
+ }
+
+ $r .= "<li>{$articles[$index]}</li>";
+ }
+ $r .= '</ul>';
+ return $r;
+ }
+
+ /**
+ * Create paging links, as a helper method to getSectionPagingLinks().
+ *
+ * @param $first String The 'until' parameter for the generated URL
+ * @param $last String The 'from' parameter for the genererated URL
+ * @param $type String A prefix for parameters, 'page' or 'subcat' or
+ * 'file'
+ * @return String HTML
+ */
+ private function pagingLinks( $first, $last, $type = '' ) {
+ $prevLink = wfMessage( 'prevn' )->numParams( $this->limit )->escaped();
+
+ if ( $first != '' ) {
+ $prevQuery = $this->query;
+ $prevQuery["{$type}until"] = $first;
+ unset( $prevQuery["{$type}from"] );
+ $prevLink = Linker::linkKnown(
+ $this->addFragmentToTitle( $this->title, $type ),
+ $prevLink,
+ array(),
+ $prevQuery
+ );
+ }
+
+ $nextLink = wfMessage( 'nextn' )->numParams( $this->limit )->escaped();
+
+ if ( $last != '' ) {
+ $lastQuery = $this->query;
+ $lastQuery["{$type}from"] = $last;
+ unset( $lastQuery["{$type}until"] );
+ $nextLink = Linker::linkKnown(
+ $this->addFragmentToTitle( $this->title, $type ),
+ $nextLink,
+ array(),
+ $lastQuery
+ );
+ }
+
+ return "($prevLink) ($nextLink)";
+ }
+
+ /**
+ * 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
+ * @return Title
+ */
+ 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 independently
+ * of the other types.
+ *
+ * Note for grepping: uses the messages category-article-count,
+ * category-article-count-limited, category-subcat-count,
+ * category-subcat-count-limited, category-file-count,
+ * category-file-count-limited.
+ *
+ * @param $rescnt Int: The number of items returned by our database query.
+ * @param $dbcnt Int: The number of items according to the category table.
+ * @param $type String: 'subcat', 'article', or 'file'
+ * @return String: A message giving the number of items, to output to HTML.
+ */
+ private function getCountMessage( $rescnt, $dbcnt, $type ) {
+ # There are three cases:
+ # 1) The category table figure seems sane. It might be wrong, but
+ # we can't do anything about it if we don't recalculate it on ev-
+ # ery category view.
+ # 2) The category table figure isn't sane, like it's smaller than the
+ # number of actual results, *but* the number of results is less
+ # than $this->limit and there's no offset. In this case we still
+ # know the right figure.
+ # 3) We have no idea.
+
+ # Check if there's a "from" or "until" for anything
+
+ // This is a little ugly, but we seem to use different names
+ // for the paging types then for the messages.
+ if ( $type === 'article' ) {
+ $pagingType = 'page';
+ } else {
+ $pagingType = $type;
+ }
+
+ $fromOrUntil = false;
+ if ( $this->from[$pagingType] !== null || $this->until[$pagingType] !== null ) {
+ $fromOrUntil = true;
+ }
+
+ if ( $dbcnt == $rescnt || ( ( $rescnt == $this->limit || $fromOrUntil )
+ && $dbcnt > $rescnt ) ) {
+ # Case 1: seems sane.
+ $totalcnt = $dbcnt;
+ } elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
+ # Case 2: not sane, but salvageable. Use the number of results.
+ # Since there are fewer than 200, we can also take this opportunity
+ # to refresh the incorrect category table entry -- which should be
+ # quick due to the small number of entries.
+ $totalcnt = $rescnt;
+ $this->cat->refreshCounts();
+ } else {
+ # Case 3: hopeless. Don't give a total count at all.
+ return wfMessage( "category-$type-count-limited" )->numParams( $rescnt )->parseAsBlock();
+ }
+ return wfMessage( "category-$type-count" )->numParams( $rescnt, $totalcnt )->parseAsBlock();
+ }
+}
diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php
index 2567de0d..4a8ed709 100644
--- a/includes/Categoryfinder.php
+++ b/includes/Categoryfinder.php
@@ -29,6 +29,10 @@ class Categoryfinder {
var $targets = array(); # Array of DBKEY category names
var $name2id = array();
var $mode; # "AND" or "OR"
+
+ /**
+ * @var DatabaseBase
+ */
var $dbr; # Read-DB slave
/**
diff --git a/includes/Cdb.php b/includes/Cdb.php
index d7a2bca5..94aa1925 100644
--- a/includes/Cdb.php
+++ b/includes/Cdb.php
@@ -72,7 +72,7 @@ abstract class CdbWriter {
*
* @param $fileName string
*
- * @return bool
+ * @return CdbWriter_DBA|CdbWriter_PHP
*/
public static function open( $fileName ) {
if ( CdbReader::haveExtension() ) {
@@ -85,11 +85,15 @@ abstract class CdbWriter {
/**
* Create the object and open the file
+ *
+ * @param $fileName string
*/
abstract function __construct( $fileName );
/**
* Set a key to a given value. The value will be converted to string.
+ * @param $key string
+ * @param $value string
*/
abstract public function set( $key, $value );
@@ -100,7 +104,6 @@ abstract class CdbWriter {
abstract public function close();
}
-
/**
* Reader class which uses the DBA extension
*/
diff --git a/includes/Cdb_PHP.php b/includes/Cdb_PHP.php
index f4029ba5..53175272 100644
--- a/includes/Cdb_PHP.php
+++ b/includes/Cdb_PHP.php
@@ -1,6 +1,6 @@
<?php
/**
- * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
+ * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
* appears in PHP 5.3. Changes are:
* * Error returns replaced with exceptions
* * Exception thrown if sizes or offsets are between 2GB and 4GB
@@ -50,7 +50,7 @@ class CdbFunctions {
/**
* The CDB hash function.
- *
+ *
* @param $s
*
* @return
@@ -62,7 +62,7 @@ class CdbFunctions {
// Do a 32-bit sum
// Inlined here for speed
$sum = ($h & 0x3fffffff) + ($h5 & 0x3fffffff);
- $h =
+ $h =
(
( $sum & 0x40000000 ? 1 : 0 )
+ ( $h & 0x80000000 ? 2 : 0 )
@@ -82,6 +82,9 @@ class CdbFunctions {
* CDB reader class
*/
class CdbReader_PHP extends CdbReader {
+ /** The filename */
+ var $fileName;
+
/** The file handle */
var $handle;
@@ -104,12 +107,16 @@ class CdbReader_PHP extends CdbReader {
var $dpos;
/* initialized if cdb_findnext() returns 1 */
- var $dlen;
+ var $dlen;
+ /**
+ * @param $fileName string
+ */
function __construct( $fileName ) {
+ $this->fileName = $fileName;
$this->handle = fopen( $fileName, 'rb' );
if ( !$this->handle ) {
- throw new MWException( 'Unable to open CDB file "' . $fileName . '"' );
+ throw new MWException( 'Unable to open CDB file "' . $this->fileName . '".' );
}
$this->findStart();
}
@@ -157,7 +164,8 @@ class CdbReader_PHP extends CdbReader {
protected function read( $length, $pos ) {
if ( fseek( $this->handle, $pos ) == -1 ) {
// This can easily happen if the internal pointers are incorrect
- throw new MWException( __METHOD__.': seek failed, file may be corrupted.' );
+ throw new MWException(
+ 'Seek failed, file "' . $this->fileName . '" may be corrupted.' );
}
if ( $length == 0 ) {
@@ -166,7 +174,8 @@ class CdbReader_PHP extends CdbReader {
$buf = fread( $this->handle, $length );
if ( $buf === false || strlen( $buf ) !== $length ) {
- throw new MWException( __METHOD__.': read from CDB file failed, file may be corrupted' );
+ throw new MWException(
+ 'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' );
}
return $buf;
}
@@ -179,7 +188,8 @@ class CdbReader_PHP extends CdbReader {
protected function unpack31( $s ) {
$data = unpack( 'V', $s );
if ( $data[1] > 0x7fffffff ) {
- throw new MWException( __METHOD__.': error in CDB file, integer too big' );
+ throw new MWException(
+ 'Error in CDB file "' . $this->fileName . '", integer too big.' );
}
return $data[1];
}
@@ -257,20 +267,24 @@ class CdbWriter_PHP extends CdbWriter {
var $handle, $realFileName, $tmpFileName;
var $hplist;
- var $numEntries, $pos;
+ var $numentries, $pos;
+ /**
+ * @param $fileName string
+ */
function __construct( $fileName ) {
$this->realFileName = $fileName;
$this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
$this->handle = fopen( $this->tmpFileName, 'wb' );
if ( !$this->handle ) {
- throw new MWException( 'Unable to open CDB file for write "' . $fileName . '"' );
+ $this->throwException(
+ 'Unable to open CDB file "' . $this->tmpFileName . '" for write.' );
}
$this->hplist = array();
$this->numentries = 0;
$this->pos = 2048; // leaving space for the pointer array, 256 * 8
if ( fseek( $this->handle, $this->pos ) == -1 ) {
- throw new MWException( __METHOD__.': fseek failed' );
+ $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' );
}
}
@@ -308,7 +322,7 @@ class CdbWriter_PHP extends CdbWriter {
unlink( $this->realFileName );
}
if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
- throw new MWException( 'Unable to move the new CDB file into place.' );
+ $this->throwException( 'Unable to move the new CDB file into place.' );
}
unset( $this->handle );
}
@@ -320,7 +334,7 @@ class CdbWriter_PHP extends CdbWriter {
protected function write( $buf ) {
$len = fwrite( $this->handle, $buf );
if ( $len !== strlen( $buf ) ) {
- throw new MWException( 'Error writing to CDB file.' );
+ $this->throwException( 'Error writing to CDB file "'.$this->tmpFileName.'".' );
}
}
@@ -331,7 +345,8 @@ class CdbWriter_PHP extends CdbWriter {
protected function posplus( $len ) {
$newpos = $this->pos + $len;
if ( $newpos > 0x7fffffff ) {
- throw new MWException( 'A value in the CDB file is too large' );
+ $this->throwException(
+ 'A value in the CDB file "'.$this->tmpFileName.'" is too large.' );
}
$this->pos = $newpos;
}
@@ -360,10 +375,10 @@ class CdbWriter_PHP extends CdbWriter {
*/
protected function addbegin( $keylen, $datalen ) {
if ( $keylen > 0x7fffffff ) {
- throw new MWException( __METHOD__.': key length too long' );
+ $this->throwException( 'Key length too long in file "'.$this->tmpFileName.'".' );
}
if ( $datalen > 0x7fffffff ) {
- throw new MWException( __METHOD__.': data length too long' );
+ $this->throwException( 'Data length too long in file "'.$this->tmpFileName.'".' );
}
$buf = pack( 'VV', $keylen, $datalen );
$this->write( $buf );
@@ -391,7 +406,7 @@ class CdbWriter_PHP extends CdbWriter {
}
// Excessively clever and indulgent code to simultaneously fill $packedTables
- // with the packed hashtables, and adjust the elements of $starts
+ // with the packed hashtables, and adjust the elements of $starts
// to actually point to the starts instead of the ends.
$packedTables = array_fill( 0, $this->numentries, false );
foreach ( $this->hplist as $item ) {
@@ -416,7 +431,7 @@ class CdbWriter_PHP extends CdbWriter {
// is taken.
for ( $u = 0; $u < $count; ++$u ) {
$hp = $packedTables[$starts[$i] + $u];
- $where = CdbFunctions::unsignedMod(
+ $where = CdbFunctions::unsignedMod(
CdbFunctions::unsignedShiftRight( $hp['h'], 8 ), $len );
while ( $hashtable[$where]['p'] )
if ( ++$where == $len )
@@ -426,7 +441,7 @@ class CdbWriter_PHP extends CdbWriter {
// Write the hashtable
for ( $u = 0; $u < $len; ++$u ) {
- $buf = pack( 'vvV',
+ $buf = pack( 'vvV',
$hashtable[$u]['h'] & 0xffff,
CdbFunctions::unsignedShiftRight( $hashtable[$u]['h'], 16 ),
$hashtable[$u]['p'] );
@@ -438,8 +453,22 @@ class CdbWriter_PHP extends CdbWriter {
// Write the pointer array at the start of the file
rewind( $this->handle );
if ( ftell( $this->handle ) != 0 ) {
- throw new MWException( __METHOD__.': Error rewinding to start of file' );
+ $this->throwException( 'Error rewinding to start of file "'.$this->tmpFileName.'".' );
}
$this->write( $final );
}
+
+ /**
+ * Clean up the temp file and throw an exception
+ *
+ * @param $msg string
+ * @throws MWException
+ */
+ protected function throwException( $msg ) {
+ if ( $this->handle ) {
+ fclose( $this->handle );
+ unlink( $this->tmpFileName );
+ }
+ throw new MWException( $msg );
+ }
}
diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php
index c8e522df..63d37327 100644
--- a/includes/ChangeTags.php
+++ b/includes/ChangeTags.php
@@ -1,6 +1,23 @@
<?php
-
+/**
+ * Functions related to change tags.
+ *
+ * @file
+ */
class ChangeTags {
+
+ /**
+ * Creates HTML for the given tags
+ *
+ * @param $tags String: Comma-separated list of tags
+ * @param $page String: A label for the type of action which is being displayed,
+ * for example: 'history', 'contributions' or 'newpages'
+ *
+ * @return Array with two items: (html, classes)
+ * - html: String: HTML for displaying the tags (empty string when param $tags is empty)
+ * - classes: Array of strings: CSS classes used in the generated html, one class for each tag
+ *
+ */
static function formatSummaryRow( $tags, $page ) {
if( !$tags )
return array( '', array() );
@@ -18,18 +35,38 @@ class ChangeTags {
);
$classes[] = Sanitizer::escapeClass( "mw-tag-$tag" );
}
-
$markers = '(' . implode( ', ', $displayTags ) . ')';
$markers = Xml::tags( 'span', array( 'class' => 'mw-tag-markers' ), $markers );
+
return array( $markers, $classes );
}
+ /**
+ * Get a short description for a tag
+ *
+ * @param $tag String: tag
+ *
+ * @return String: Short description of the tag from "mediawiki:tag-$tag" if this message exists,
+ * html-escaped version of $tag otherwise
+ */
static function tagDescription( $tag ) {
$msg = wfMessage( "tag-$tag" );
- return $msg->exists() ? $msg->parse() : htmlspecialchars( $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.
+ /**
+ * Add tags to a change given its rc_id, rev_id and/or log_id
+ *
+ * @param $tags String|Array: Tags to add to the change
+ * @param $rc_id int: rc_id of the change to add the tags to
+ * @param $rev_id int: rev_id of the change to add the tags to
+ * @param $log_id int: log_id of the change to add the tags to
+ * @param $params String: params to put in the ct_params field of tabel 'change_tag'
+ *
+ * @return bool: false if no changes are made, otherwise true
+ *
+ * @exception MWException when $rc_id, $rev_id and $log_id are all null
+ */
static function addTags( $tags, $rc_id = null, $rev_id = null, $log_id = null, $params = null ) {
if ( !is_array( $tags ) ) {
$tags = array( $tags );
@@ -103,6 +140,16 @@ class ChangeTags {
* Applies all tags-related changes to a query.
* Handles selecting tags, and filtering.
* Needs $tables to be set up properly, so we can figure out which join conditions to use.
+ *
+ * @param $tables String|Array: Tabel names, see DatabaseBase::select
+ * @param $fields String|Array: Fields used in query, see DatabaseBase::select
+ * @param $conds String|Array: conditions used in query, see DatabaseBase::select
+ * @param $join_conds Array: join conditions, see DatabaseBase::select
+ * @param $options Array: options, see Database::select
+ * @param $filter_tag String: tag to select on
+ *
+ * @exception MWException when unable to determine appropriate JOIN condition for tagging
+ *
*/
static function modifyDisplayQuery( &$tables, &$fields, &$conds,
&$join_conds, &$options, $filter_tag = false ) {
@@ -178,9 +225,13 @@ class ChangeTags {
}
/**
- *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.
+ * Tags on items in table 'change_tag' which are not (or no longer) in table 'valid_tag'
+ * are not included.
+ *
+ * Tries memcached first.
*
- * @return array
+ * @return Array of strings: tags
*/
static function listDefinedTags() {
// Caching...
diff --git a/includes/ChangesFeed.php b/includes/ChangesFeed.php
index c4c4a8a1..bcedf2f3 100644
--- a/includes/ChangesFeed.php
+++ b/includes/ChangesFeed.php
@@ -34,6 +34,11 @@ class ChangesFeed {
return false;
}
+ if( !array_key_exists( $this->format, $wgFeedClasses ) ) {
+ // falling back to atom
+ $this->format = 'atom';
+ }
+
$feedTitle = "$wgSitename - {$title} [$wgLanguageCode]";
return new $wgFeedClasses[$this->format](
$feedTitle, htmlspecialchars( $description ), $url );
diff --git a/includes/ChangesList.php b/includes/ChangesList.php
index 1858dc3a..fd97e0cb 100644
--- a/includes/ChangesList.php
+++ b/includes/ChangesList.php
@@ -67,6 +67,7 @@ class ChangesList extends ContextSource {
* @return ChangesList|EnhancedChangesList|OldChangesList derivative
*/
public static function newFromUser( $unused ) {
+ wfDeprecated( __METHOD__, '1.18' );
return self::newFromContext( RequestContext::getMain() );
}
@@ -193,10 +194,10 @@ class ChangesList extends ContextSource {
$fastCharDiff[$code] = $wgMiserMode || wfMsgNoTrans( 'rc-change-size' ) === '$1';
}
- $formatedSize = $wgLang->formatNum($szdiff);
+ $formattedSize = $wgLang->formatNum($szdiff);
if ( !$fastCharDiff[$code] ) {
- $formatedSize = wfMsgExt( 'rc-change-size', array( 'parsemag', 'escape' ), $formatedSize );
+ $formattedSize = wfMsgExt( 'rc-change-size', array( 'parsemag' ), $formattedSize );
}
if( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) {
@@ -204,13 +205,23 @@ class ChangesList extends ContextSource {
} else {
$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 {
- return "<$tag class='mw-plusminus-neg'>($formatedSize)</$tag>";
+
+ if ( $szdiff === 0 ) {
+ $formattedSizeClass = 'mw-plusminus-null';
+ }
+ if ( $szdiff > 0 ) {
+ $formattedSize = '+' . $formattedSize;
+ $formattedSizeClass = 'mw-plusminus-pos';
}
+ if ( $szdiff < 0 ) {
+ $formattedSizeClass = 'mw-plusminus-neg';
+ }
+
+ $formattedTotalSize = wfMsgExt( 'rc-change-size-new', 'parsemag', $wgLang->formatNum( $new ) );
+
+ return Html::element( $tag,
+ array( 'dir' => 'ltr', 'class' => $formattedSizeClass, 'title' => $formattedTotalSize ),
+ wfMessage( 'parentheses', $formattedSize )->plain() ) . $wgLang->getDirMark();
}
/**
@@ -225,38 +236,9 @@ class ChangesList extends ContextSource {
}
}
- /**
- * @param $s
- * @param $rc RecentChange
- * @return void
- */
- public function insertMove( &$s, $rc ) {
- # Diff
- $s .= '(' . $this->message['diff'] . ') (';
- # Hist
- $s .= Linker::linkKnown(
- $rc->getMovedToTitle(),
- $this->message['hist'],
- array(),
- array( 'action' => 'history' )
- ) . ') . . ';
- # "[[x]] moved to [[y]]"
- $msg = ( $rc->mAttribs['rc_type'] == RC_MOVE ) ? '1movedto2' : '1movedto2_redir';
- $s .= wfMsgHtml(
- $msg,
- Linker::linkKnown(
- $rc->getTitle(),
- null,
- array(),
- array( 'redirect' => 'no' )
- ),
- Linker::linkKnown( $rc->getMovedToTitle() )
- );
- }
-
public function insertDateHeader( &$s, $rc_timestamp ) {
# Make date header if necessary
- $date = $this->getLang()->date( $rc_timestamp, true, true );
+ $date = $this->getLanguage()->date( $rc_timestamp, true, true );
if( $date != $this->lastdate ) {
if( $this->lastdate != '' ) {
$s .= "</ul>\n";
@@ -268,21 +250,21 @@ class ChangesList extends ContextSource {
}
public function insertLog( &$s, $title, $logtype ) {
- $logname = LogPage::logName( $logtype );
- $s .= '(' . Linker::linkKnown( $title, htmlspecialchars( $logname ) ) . ')';
+ $page = new LogPage( $logtype );
+ $logname = $page->getName()->escaped();
+ $s .= '(' . Linker::linkKnown( $title, $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'];
- } elseif( !self::userCan($rc,Revision::DELETED_TEXT) ) {
+ } elseif ( !self::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) {
$diffLink = $this->message['diff'];
} else {
$query = array(
@@ -321,7 +303,6 @@ class ChangesList extends ContextSource {
* @param $rc RecentChange
* @param $unpatrolled
* @param $watched
- * @return void
*/
public function insertArticleLink( &$s, &$rc, $unpatrolled, $watched ) {
# If it's a new article, there is no diff link, but if it hasn't been
@@ -332,28 +313,21 @@ class ChangesList extends ContextSource {
$params['rcid'] = $rc->mAttribs['rc_id'];
}
+ $articlelink = Linker::linkKnown(
+ $rc->getTitle(),
+ null,
+ array(),
+ $params
+ );
if( $this->isDeleted($rc,Revision::DELETED_TEXT) ) {
- $articlelink = Linker::linkKnown(
- $rc->getTitle(),
- null,
- array(),
- $params
- );
$articlelink = '<span class="history-deleted">' . $articlelink . '</span>';
- } else {
- $articlelink = ' '. Linker::linkKnown(
- $rc->getTitle(),
- null,
- array(),
- $params
- );
}
# Bolden pages watched by this user
if( $watched ) {
$articlelink = "<strong class=\"mw-watched\">{$articlelink}</strong>";
}
# RTL/LTR marker
- $articlelink .= $this->getLang()->getDirMark();
+ $articlelink .= $this->getLanguage()->getDirMark();
wfRunHooks( 'ChangesListInsertArticleLink',
array(&$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched) );
@@ -364,51 +338,50 @@ class ChangesList extends ContextSource {
/**
* @param $s
* @param $rc RecentChange
- * @return void
*/
public function insertTimestamp( &$s, $rc ) {
$s .= $this->message['semicolon-separator'] .
- $this->getLang()->time( $rc->mAttribs['rc_timestamp'], true, true ) . ' . . ';
+ $this->getLanguage()->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
+ * @param &$s String HTML to update
+ * @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>';
} else {
- $s .= Linker::userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+ $s .= $this->getLanguage()->getDirMark() . 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->getSkin(), LogPage::extractParams( $rc->mAttribs['rc_params'] ), true, true );
- }
- }
+ public function insertLogEntry( $rc ) {
+ $formatter = LogFormatter::newFromRow( $rc->mAttribs );
+ $formatter->setShowUserToolLinks( true );
+ $mark = $this->getLanguage()->getDirMark();
+ return $formatter->getActionText() . " $mark" . $formatter->getComment();
}
- /** insert a formatted comment
- *
+ /**
+ * Insert a formatted comment
* @param $rc RecentChange
*/
- public function insertComment( &$s, &$rc ) {
+ public function insertComment( $rc ) {
if( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) {
if( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) {
- $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
+ return ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
} else {
- $s .= Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
+ return Linker::commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
}
}
}
@@ -430,7 +403,7 @@ class ChangesList extends ContextSource {
if( $count > 0 ) {
if( !isset( $cache[$count] ) ) {
$cache[$count] = wfMsgExt( 'number_of_watching_users_RCview',
- array('parsemag', 'escape' ), $this->getLang()->formatNum( $count ) );
+ array('parsemag', 'escape' ), $this->getLanguage()->formatNum( $count ) );
}
return $cache[$count];
} else {
@@ -453,16 +426,22 @@ class ChangesList extends ContextSource {
* field of this revision, if it's marked as deleted.
* @param $rc RCCacheEntry
* @param $field Integer
+ * @param $user User object to check, or null to use $wgUser
* @return Boolean
*/
- public static function userCan( $rc, $field ) {
+ public static function userCan( $rc, $field, User $user = null ) {
if( $rc->mAttribs['rc_type'] == RC_LOG ) {
- return LogEventsList::userCanBitfield( $rc->mAttribs['rc_deleted'], $field );
+ return LogEventsList::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
} else {
- return Revision::userCanBitfield( $rc->mAttribs['rc_deleted'], $field );
+ return Revision::userCanBitfield( $rc->mAttribs['rc_deleted'], $field, $user );
}
}
+ /**
+ * @param $link string
+ * @param $watched bool
+ * @return string
+ */
protected function maybeWatchedLink( $link, $watched = false ) {
if( $watched ) {
return '<strong class="mw-watched">' . $link . '</strong>';
@@ -473,7 +452,7 @@ class ChangesList extends ContextSource {
/** Inserts a rollback link
*
- * @param $s
+ * @param $s string
* @param $rc RecentChange
*/
public function insertRollback( &$s, &$rc ) {
@@ -496,10 +475,9 @@ class ChangesList extends ContextSource {
}
/**
- * @param $s
+ * @param $s string
* @param $rc RecentChange
* @param $classes
- * @return
*/
public function insertTags( &$s, &$rc, &$classes ) {
if ( empty($rc->mAttribs['ts_tags']) )
@@ -513,6 +491,18 @@ class ChangesList extends ContextSource {
public function insertExtra( &$s, &$rc, &$classes ) {
## Empty, used for subclassers to add anything special.
}
+
+ protected function showAsUnpatrolled( RecentChange $rc ) {
+ $unpatrolled = false;
+ if ( !$rc->mAttribs['rc_patrolled'] ) {
+ if ( $this->getUser()->useRCPatrol() ) {
+ $unpatrolled = true;
+ } elseif ( $this->getUser()->useNPPatrol() && $rc->mAttribs['rc_new'] ) {
+ $unpatrolled = true;
+ }
+ }
+ return $unpatrolled;
+ }
}
@@ -528,8 +518,9 @@ class OldChangesList extends ChangesList {
public function recentChangesLine( &$rc, $watched = false, $linenumber = null ) {
global $wgRCShowChangedSize;
wfProfileIn( __METHOD__ );
+
# Should patrol-related stuff be shown?
- $unpatrolled = $this->getUser()->useRCPatrol() && !$rc->mAttribs['rc_patrolled'];
+ $unpatrolled = $this->showAsUnpatrolled( $rc );
$dateheader = ''; // $s now contains only <li>...</li>, for hooks' convenience.
$this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
@@ -546,9 +537,8 @@ class OldChangesList extends ChangesList {
}
}
- // Moved pages
+ // Moved pages (very very old, not supported anymore)
if( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) {
- $this->insertMove( $s, $rc );
// Log entries
} elseif( $rc->mAttribs['rc_log_type'] ) {
$logtitle = Title::newFromText( 'Log/'.$rc->mAttribs['rc_log_type'], NS_SPECIAL );
@@ -583,14 +573,17 @@ class OldChangesList extends ChangesList {
$s .= "$cd . . ";
}
}
- # 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
- $this->insertComment( $s, $rc );
+
+ if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
+ $s .= $this->insertLogEntry( $rc );
+ } else {
+ # User tool links
+ $this->insertUserRelatedLinks( $s, $rc );
+ # LTR/RTL direction mark
+ $s .= $this->getLanguage()->getDirMark();
+ $s .= $this->insertComment( $rc );
+ }
+
# Tags
$this->insertTags( $s, $rc, $classes );
# Rollback
@@ -601,7 +594,7 @@ class OldChangesList extends ChangesList {
# How many users watch this page
if( $rc->numberofWatchingusers > 0 ) {
$s .= ' ' . wfMsgExt( 'number_of_watching_users_RCview',
- array( 'parsemag', 'escape' ), $this->getLang()->formatNum( $rc->numberofWatchingusers ) );
+ array( 'parsemag', 'escape' ), $this->getLanguage()->formatNum( $rc->numberofWatchingusers ) );
}
if( $this->watchlist ) {
@@ -620,6 +613,9 @@ class OldChangesList extends ChangesList {
* Generate a list of changes using an Enhanced system (uses javascript).
*/
class EnhancedChangesList extends ChangesList {
+
+ protected $rc_cache;
+
/**
* Add the JavaScript file for enhanced changeslist
* @return String
@@ -650,7 +646,7 @@ class EnhancedChangesList extends ChangesList {
$curIdEq = array( 'curid' => $rc->mAttribs['rc_cur_id'] );
# If it's a new day, add the headline and flush the cache
- $date = $this->getLang()->date( $rc->mAttribs['rc_timestamp'], true );
+ $date = $this->getLanguage()->date( $rc->mAttribs['rc_timestamp'], true );
$ret = '';
if( $date != $this->lastdate ) {
# Process current cache
@@ -661,22 +657,14 @@ class EnhancedChangesList extends ChangesList {
}
# Should patrol-related stuff be shown?
- if( $this->getUser()->useRCPatrol() ) {
- $rc->unpatrolled = !$rc->mAttribs['rc_patrolled'];
- } else {
- $rc->unpatrolled = false;
- }
+ $rc->unpatrolled = $this->showAsUnpatrolled( $rc );
$showdifflinks = true;
# Make article link
$type = $rc->mAttribs['rc_type'];
$logType = $rc->mAttribs['rc_log_type'];
- // Page moves
+ // Page moves, very old style, not supported anymore
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' ) ),
- Linker::linkKnown( $rc->getMovedToTitle() ) );
// New unpatrolled pages
} elseif( $rc->unpatrolled && $type == RC_NEW ) {
$clink = Linker::linkKnown( $rc->getTitle(), null, array(),
@@ -685,34 +673,28 @@ class EnhancedChangesList extends ChangesList {
} elseif( $type == RC_LOG ) {
if( $logType ) {
$logtitle = SpecialPage::getTitleFor( 'Log', $logType );
- $clink = '(' . Linker::linkKnown( $logtitle,
- LogPage::logName( $logType ) ) . ')';
+ $logpage = new LogPage( $logType );
+ $logname = $logpage->getName()->escaped();
+ $clink = '(' . Linker::linkKnown( $logtitle, $logname ) . ')';
} else {
$clink = Linker::link( $rc->getTitle() );
}
$watched = false;
// Log entries (old format) and special pages
} 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 = '(' . Linker::linkKnown( $rc->getTitle(), $logname ) . ')';
- } else {
- wfDebug( "Unexpected special page in recentchanges\n" );
- $clink = '';
- }
+ wfDebug( "Unexpected special page in recentchanges\n" );
+ $clink = '';
// Edits
} else {
$clink = Linker::linkKnown( $rc->getTitle() );
}
# Don't show unusable diff links
- if ( !ChangesList::userCan($rc,Revision::DELETED_TEXT) ) {
+ if ( !ChangesList::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) {
$showdifflinks = false;
}
- $time = $this->getLang()->time( $rc->mAttribs['rc_timestamp'], true, true );
+ $time = $this->getLanguage()->time( $rc->mAttribs['rc_timestamp'], true, true );
$rc->watched = $watched;
$rc->link = $clink;
$rc->timestamp = $time;
@@ -738,13 +720,13 @@ class EnhancedChangesList extends ChangesList {
if ( $type != RC_NEW ) {
$curLink = $this->message['cur'];
} else {
- $curUrl = htmlspecialchars( $rc->getTitle()->getLinkUrl( $querycur ) );
+ $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) );
$curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
}
$diffLink = $this->message['diff'];
} else {
- $diffUrl = htmlspecialchars( $rc->getTitle()->getLinkUrl( $querydiff ) );
- $curUrl = htmlspecialchars( $rc->getTitle()->getLinkUrl( $querycur ) );
+ $diffUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querydiff ) );
+ $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) );
$diffLink = "<a href=\"$diffUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['diff']}</a>";
$curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
}
@@ -806,9 +788,11 @@ class EnhancedChangesList extends ChangesList {
# Add the namespace and title of the block as part of the class
if ( $block[0]->mAttribs['rc_log_type'] ) {
# Log entry
- $classes = 'mw-collapsible mw-collapsed 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-collapsible mw-collapsed 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' );
@@ -861,9 +845,9 @@ class EnhancedChangesList extends ChangesList {
$users = array();
foreach( $userlinks as $userlink => $count) {
$text = $userlink;
- $text .= $this->getLang()->getDirMark();
+ $text .= $this->getLanguage()->getDirMark();
if( $count > 1 ) {
- $text .= ' (' . $this->getLang()->formatNum( $count ) . '×)';
+ $text .= ' (' . $this->getLanguage()->formatNum( $count ) . '×)';
}
array_push( $users, $text );
}
@@ -903,20 +887,20 @@ class EnhancedChangesList extends ChangesList {
$this->insertArticleLink( $r, $block[0], $block[0]->unpatrolled, $block[0]->watched );
}
- $r .= $this->getLang()->getDirMark();
+ $r .= $this->getLanguage()->getDirMark();
$queryParams['curid'] = $curId;
# Changes message
$n = count($block);
static $nchanges = array();
if ( !isset( $nchanges[$n] ) ) {
- $nchanges[$n] = wfMsgExt( 'nchanges', array( 'parsemag', 'escape' ), $this->getLang()->formatNum( $n ) );
+ $nchanges[$n] = wfMsgExt( 'nchanges', array( 'parsemag', 'escape' ), $this->getLanguage()->formatNum( $n ) );
}
# Total change link
$r .= ' ';
if( !$allLogs ) {
$r .= '(';
- if( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT ) ) {
+ if( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
$r .= $nchanges[$n];
} elseif( $isnew ) {
$r .= $nchanges[$n];
@@ -1005,7 +989,7 @@ class EnhancedChangesList extends ChangesList {
if( $type == RC_LOG ) {
$link = $rcObj->timestamp;
# Revision link
- } elseif( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) {
+ } elseif( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
$link = '<span class="history-deleted">'.$rcObj->timestamp.'</span> ';
} else {
if ( $rcObj->unpatrolled && $type == RC_NEW) {
@@ -1037,13 +1021,15 @@ class EnhancedChangesList extends ChangesList {
$r .= $rcObj->getCharacterDifference() . ' . . ' ;
}
- # User links
- $r .= $rcObj->userlink;
- $r .= $rcObj->usertalklink;
- // log action
- $this->insertAction( $r, $rcObj );
- // log comment
- $this->insertComment( $r, $rcObj );
+ if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) {
+ $r .= $this->insertLogEntry( $rcObj );
+ } else {
+ # User links
+ $r .= $rcObj->userlink;
+ $r .= $rcObj->usertalklink;
+ $r .= $this->insertComment( $rcObj );
+ }
+
# Rollback
$this->insertRollback( $r, $rcObj );
# Tags
@@ -1107,7 +1093,7 @@ class EnhancedChangesList extends ChangesList {
* Enhanced RC ungrouped line.
*
* @param $rcObj RecentChange
- * @return String: a HTML formated line (generated using $r)
+ * @return String: a HTML formatted line (generated using $r)
*/
protected function recentChangesBlockLine( $rcObj ) {
global $wgRCShowChangedSize;
@@ -1119,9 +1105,11 @@ class EnhancedChangesList extends ChangesList {
$logType = $rcObj->mAttribs['rc_log_type'];
if( $logType ) {
# Log entry
- $classes = 'mw-enhanced-rc ' . Sanitizer::escapeClass( 'mw-changeslist-log-' . $logType . '-' . $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'] );
+ $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' );
@@ -1163,19 +1151,15 @@ class EnhancedChangesList extends ChangesList {
if( $wgRCShowChangedSize && ($cd = $rcObj->getCharacterDifference()) ) {
$r .= "$cd . . ";
}
- # User/talk
- $r .= ' '.$rcObj->userlink . $rcObj->usertalklink;
- # Log action (if any)
- if( $logType ) {
- if( $this->isDeleted($rcObj,LogPage::DELETED_ACTION) ) {
- $r .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
- } else {
- $r .= ' ' . LogPage::actionText( $logType, $rcObj->mAttribs['rc_log_action'], $rcObj->getTitle(),
- $this->getSkin(), LogPage::extractParams( $rcObj->mAttribs['rc_params'] ), true, true );
- }
+
+ if ( $type == RC_LOG ) {
+ $r .= $this->insertLogEntry( $rcObj );
+ } else {
+ $r .= ' '.$rcObj->userlink . $rcObj->usertalklink;
+ $r .= $this->insertComment( $rcObj );
+ $r .= $this->insertRollback( $r, $rcObj );
}
- $this->insertComment( $r, $rcObj );
- $this->insertRollback( $r, $rcObj );
+
# Tags
$classes = explode( ' ', $classes );
$this->insertTags( $r, $rcObj, $classes );
@@ -1219,6 +1203,7 @@ class EnhancedChangesList extends ChangesList {
/**
* Returns text for the end of RC
* If enhanced RC is in use, returns pretty much all the text
+ * @return string
*/
public function endRecentChangesList() {
return $this->recentChangesBlock() . parent::endRecentChangesList();
diff --git a/includes/Cookie.php b/includes/Cookie.php
index 95a4599f..76739ccc 100644
--- a/includes/Cookie.php
+++ b/includes/Cookie.php
@@ -139,6 +139,10 @@ class Cookie {
return $ret;
}
+ /**
+ * @param $domain
+ * @return bool
+ */
protected function canServeDomain( $domain ) {
if ( $domain == $this->domain
|| ( strlen( $domain ) > strlen( $this->domain )
@@ -151,20 +155,19 @@ class Cookie {
return false;
}
+ /**
+ * @param $path
+ * @return bool
+ */
protected function canServePath( $path ) {
- if ( $this->path && substr_compare( $this->path, $path, 0, strlen( $this->path ) ) == 0 ) {
- return true;
- }
-
- return false;
+ return ( $this->path && substr_compare( $this->path, $path, 0, strlen( $this->path ) ) == 0 );
}
+ /**
+ * @return bool
+ */
protected function isUnExpired() {
- if ( $this->isSessionKey || $this->expires > time() ) {
- return true;
- }
-
- return false;
+ return $this->isSessionKey || $this->expires > time();
}
}
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 29d98d58..04450348 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -8,10 +8,10 @@
* To customize your installation, edit "LocalSettings.php". If you make
* changes here, they will be lost on next upgrade of MediaWiki!
*
- * Note that since all these string interpolations are expanded
- * before LocalSettings is included, if you localize something
- * like $wgScriptPath, you must also localize everything that
- * depends on it.
+ * In this file, variables whose default values depend on other
+ * variables are set to false. The actual default value of these variables
+ * will only be set in Setup.php, taking into account any custom settings
+ * performed in LocalSettings.php.
*
* Documentation is in the source and on:
* http://www.mediawiki.org/wiki/Manual:Configuration_settings
@@ -33,10 +33,10 @@ $wgConf = new SiteConfiguration;
/** @endcond */
/** MediaWiki version number */
-$wgVersion = '1.18.3';
+$wgVersion = '1.19.0';
/** Name of the site. It must be changed in LocalSettings.php */
-$wgSitename = 'MediaWiki';
+$wgSitename = 'MediaWiki';
/**
* URL of the server.
@@ -49,7 +49,7 @@ $wgSitename = 'MediaWiki';
* 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.
@@ -57,9 +57,9 @@ $wgSitename = 'MediaWiki';
$wgServer = WebRequest::detectServer();
/**
- * Canonical URL of the server, to use in IRC feeds and notification e-mails.
+ * 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;
@@ -78,7 +78,7 @@ $wgCanonicalServer = false;
* Other paths will be set to defaults based on it unless they are directly
* set in LocalSettings.php
*/
-$wgScriptPath = '/wiki';
+$wgScriptPath = '/wiki';
/**
* Whether to support URLs like index.php/Page_title These often break when PHP
@@ -113,24 +113,24 @@ $wgScriptExtension = '.php';
/**
* The URL path to index.php.
*
- * Defaults to "{$wgScriptPath}/index{$wgScriptExtension}".
+ * Will default to "{$wgScriptPath}/index{$wgScriptExtension}" in Setup.php
*/
-$wgScript = false;
+$wgScript = false;
/**
* The URL path to redirect.php. This is a script that is used by the Nostalgia
* skin.
*
- * Defaults to "{$wgScriptPath}/redirect{$wgScriptExtension}".
+ * Will default to "{$wgScriptPath}/redirect{$wgScriptExtension}" in Setup.php
*/
-$wgRedirectScript = false; ///< defaults to
+$wgRedirectScript = false;
/**
* The URL path to load.php.
*
* Defaults to "{$wgScriptPath}/load{$wgScriptExtension}".
*/
-$wgLoadScript = false;
+$wgLoadScript = false;
/**@}*/
@@ -140,7 +140,6 @@ $wgLoadScript = false;
*
* These various web and file path variables are set to their defaults
* in Setup.php if they are not explicitly set from LocalSettings.php.
- * If you do override them, be sure to set them all!
*
* These will relatively rarely need to be set manually, unless you are
* splitting style sheets or images outside the main document root.
@@ -155,16 +154,16 @@ $wgLoadScript = false;
*/
/**
- * The URL path of the skins directory. Defaults to "{$wgScriptPath}/skins"
+ * The URL path of the skins directory. Will default to "{$wgScriptPath}/skins" in Setup.php
*/
-$wgStylePath = false;
+$wgStylePath = false;
$wgStyleSheetPath = &$wgStylePath;
/**
* The URL path of the skins directory. Should not point to an external domain.
* Defaults to "{$wgScriptPath}/skins".
*/
-$wgLocalStylePath = false;
+$wgLocalStylePath = false;
/**
* The URL path of the extensions directory.
@@ -174,7 +173,7 @@ $wgLocalStylePath = false;
$wgExtensionAssetsPath = false;
/**
- * Filesystem stylesheets directory. Defaults to "{$IP}/skins"
+ * Filesystem stylesheets directory. Will default to "{$IP}/skins" in Setup.php
*/
$wgStyleDirectory = false;
@@ -182,51 +181,56 @@ $wgStyleDirectory = false;
* The URL path for primary article page views. This path should contain $1,
* which is replaced by the article title.
*
- * Defaults to "{$wgScript}/$1" or "{$wgScript}?title=$1", depending on
- * $wgUsePathInfo.
+ * Will default to "{$wgScript}/$1" or "{$wgScript}?title=$1" in Setup.php,
+ * depending on $wgUsePathInfo.
*/
-$wgArticlePath = false;
+$wgArticlePath = false;
/**
- * The URL path for the images directory. Defaults to "{$wgScriptPath}/images"
+ * The URL path for the images directory. Will default to "{$wgScriptPath}/images" in Setup.php
*/
-$wgUploadPath = false;
+$wgUploadPath = false;
+
+/**
+ * The maximum age of temporary (incomplete) uploaded files
+ */
+$wgUploadStashMaxAge = 6 * 3600; // 6 hours
/**
* The filesystem path of the images directory. Defaults to "{$IP}/images".
*/
-$wgUploadDirectory = false;
+$wgUploadDirectory = false;
/**
* The URL path of the wiki logo. The logo size should be 135x135 pixels.
- * Defaults to "{$wgStylePath}/common/images/wiki.png".
+ * Will default to "{$wgStylePath}/common/images/wiki.png" in Setup.php
*/
-$wgLogo = false;
+$wgLogo = false;
/**
* The URL path of the shortcut icon.
*/
-$wgFavicon = '/favicon.ico';
+$wgFavicon = '/favicon.ico';
/**
* The URL path of the icon for iPhone and iPod Touch web app bookmarks.
* Defaults to no icon.
*/
-$wgAppleTouchIcon = false;
+$wgAppleTouchIcon = false;
/**
* The local filesystem path to a temporary directory. This is not required to
* be web accessible.
*
- * Defaults to "{$wgUploadDirectory}/tmp".
+ * Will default to "{$wgUploadDirectory}/tmp" in Setup.php
*/
-$wgTmpDirectory = false;
+$wgTmpDirectory = false;
/**
* If set, this URL is added to the start of $wgUploadPath to form a complete
* upload URL.
*/
-$wgUploadBaseUrl = "";
+$wgUploadBaseUrl = '';
/**
* To enable remote on-demand scaling, set this to the thumbnail base URL.
@@ -277,7 +281,7 @@ $wgDeletedDirectory = false; // Defaults to $wgUploadDirectory/deleted
/**
* Set this to true if you use img_auth and want the user to see details on why access failed.
*/
-$wgImgAuthDetails = false;
+$wgImgAuthDetails = false;
/**
* If this is enabled, img_auth.php will not allow image access unless the wiki
@@ -295,11 +299,19 @@ $wgImgAuthPublicTest = true;
*
* Properties required for all repos:
* - class The class name for the repository. May come from the core or an extension.
- * The core repository classes are LocalRepo, ForeignDBRepo, FSRepo.
+ * The core repository classes are FileRepo, LocalRepo, ForeignDBRepo.
+ * FSRepo is also supported for backwards compatibility.
*
- * - name A unique name for the repository (but $wgLocalFileRepo should be 'local').
+ * - name A unique name for the repository (but $wgLocalFileRepo should be 'local').
+ * The name should consist of alpha-numberic characters.
+ * - backend A file backend name (see $wgFileBackends).
*
* For most core repos:
+ * - zones Associative array of zone names that each map to an array with:
+ * container : backend container name the zone is in
+ * directory : root path within container for the zone
+ * Zones default to using <repo name>-<zone> as the
+ * container name and the container root as the zone directory.
* - url Base public URL
* - hashLevels The number of directory levels for hash-based division of files
* - thumbScriptUrl The URL for thumb.php (optional, not recommended)
@@ -349,6 +361,8 @@ $wgImgAuthPublicTest = true;
* 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.
+ *
+ * @see Setup.php for an example usage and default initialization.
*/
$wgLocalFileRepo = false;
@@ -363,6 +377,27 @@ $wgForeignFileRepos = array();
$wgUseInstantCommons = false;
/**
+ * File backend structure configuration.
+ * This is an array of file backend configuration arrays.
+ * Each backend configuration has the following parameters:
+ * 'name' : A unique name for the backend
+ * 'class' : The file backend class to use
+ * 'wikiId' : A unique string that identifies the wiki (container prefix)
+ * 'lockManager' : The name of a lock manager (see $wgLockManagers)
+ * Additional parameters are specific to the class used.
+ */
+$wgFileBackends = array();
+
+/**
+ * Array of configuration arrays for each lock manager.
+ * Each backend configuration has the following parameters:
+ * 'name' : A unique name for the lock manger
+ * 'class' : The lock manger class to use
+ * Additional parameters are specific to the class used.
+ */
+$wgLockManagers = array();
+
+/**
* Show EXIF data, on by default if available.
* Requires PHP's EXIF extension: http://www.php.net/manual/en/ref.exif.php
*
@@ -426,10 +461,10 @@ $wgAllowAsyncCopyUploads = false;
* for non-specified types.
*
* For example:
- * $wgMaxUploadSize = array(
- * '*' => 250 * 1024,
- * 'url' => 500 * 1024,
- * );
+ * $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.
*
@@ -474,7 +509,7 @@ $wgSharedThumbnailScriptPath = false;
*
* Note that this variable may be ignored if $wgLocalFileRepo is set.
*/
-$wgHashedUploadDirectory = true;
+$wgHashedUploadDirectory = true;
/**
* Set the following to false especially if you have a set of files that need to
@@ -553,6 +588,13 @@ $wgCheckFileExtensions = true;
*/
$wgStrictFileExtensions = true;
+/**
+ * Setting this to true will disable the upload system's checks for HTML/JavaScript.
+ * THIS IS VERY DANGEROUS on a publicly editable site, so USE wgGroupPermissions
+ * TO RESTRICT UPLOADING to only those that you trust
+ */
+$wgDisableUploadScriptChecks = false;
+
/** Warn if uploaded files are larger than this (in bytes), or false to disable*/
$wgUploadSizeWarning = false;
@@ -586,6 +628,7 @@ $wgMediaHandlers = array(
'image/tiff' => 'TiffHandler',
'image/x-ms-bmp' => 'BmpHandler',
'image/x-bmp' => 'BmpHandler',
+ 'image/x-xcf' => 'XCFHandler',
'image/svg+xml' => 'SvgHandler', // official
'image/svg' => 'SvgHandler', // compat
'image/vnd.djvu' => 'DjVuHandler', // official
@@ -601,9 +644,11 @@ $wgMediaHandlers = array(
*
* Use Image Magick instead of PHP builtin functions.
*/
-$wgUseImageMagick = false;
+$wgUseImageMagick = false;
/** The convert command shipped with ImageMagick */
-$wgImageMagickConvertCommand = '/usr/bin/convert';
+$wgImageMagickConvertCommand = '/usr/bin/convert';
+/** The identify command shipped with ImageMagick */
+$wgImageMagickIdentifyCommand = '/usr/bin/identify';
/** Sharpening parameter to ImageMagick */
$wgSharpenParameter = '0x0.4';
@@ -674,9 +719,17 @@ $wgSVGMetadataCutoff = 262144;
$wgAllowTitlesInSVG = false;
/**
- * Don't thumbnail an image if it will use too much working memory.
- * Default is 50 MB if decompressed to RGBA form, which corresponds to
- * 12.5 million pixels or 3500x3500
+ * The maximum number of pixels a source image can have if it is to be scaled
+ * down by a scaler that requires the full source image to be decompressed
+ * and stored in decompressed form, before the thumbnail is generated.
+ *
+ * This provides a limit on memory usage for the decompression side of the
+ * image scaler. The limit is used when scaling PNGs with any of the
+ * built-in image scalers, such as ImageMagick or GD. It is ignored for
+ * JPEGs with ImageMagick, and when using the VipsScaler extension.
+ *
+ * The default is 50 MB if decompressed to RGBA form, which corresponds to
+ * 12.5 million pixels or 3500x3500.
*/
$wgMaxImageArea = 1.25e7;
/**
@@ -751,7 +804,7 @@ $wgEnableAutoRotation = null;
* $wgAntivirusSetup array. Set this to NULL to disable virus scanning. If not
* null, every file uploaded will be scanned for viruses.
*/
-$wgAntivirus= null;
+$wgAntivirus = null;
/**
* Configuration for different virus scanners. This an associative array of
@@ -864,11 +917,11 @@ $wgTrivialMimeDetection = false;
* array = ( 'rootElement' => 'associatedMimeType' )
*/
$wgXMLMimeTypes = array(
- 'http://www.w3.org/2000/svg:svg' => 'image/svg+xml',
- 'svg' => 'image/svg+xml',
- 'http://www.lysator.liu.se/~alla/dia/:diagram' => 'application/x-dia-diagram',
- 'http://www.w3.org/1999/xhtml:html' => 'text/html', // application/xhtml+xml?
- 'html' => 'text/html', // application/xhtml+xml?
+ 'http://www.w3.org/2000/svg:svg' => 'image/svg+xml',
+ 'svg' => 'image/svg+xml',
+ 'http://www.lysator.liu.se/~alla/dia/:diagram' => 'application/x-dia-diagram',
+ 'http://www.w3.org/1999/xhtml:html' => 'text/html', // application/xhtml+xml?
+ 'html' => 'text/html', // application/xhtml+xml?
);
/**
@@ -879,13 +932,14 @@ $wgXMLMimeTypes = array(
* change it if you alter the array (see bug 8858).
* This is the list of settings the user can choose from:
*/
-$wgImageLimits = array (
- array(320,240),
- array(640,480),
- array(800,600),
- array(1024,768),
- array(1280,1024),
- array(10000,10000) );
+$wgImageLimits = array(
+ array( 320, 240 ),
+ array( 640, 480 ),
+ array( 800, 600 ),
+ array( 1024, 768 ),
+ array( 1280, 1024 ),
+ array( 10000, 10000 )
+);
/**
* Adjust thumbnails on image pages according to a user setting. In order to
@@ -1037,7 +1091,7 @@ $wgPasswordReminderResendTime = 24;
/**
* The time, in seconds, when an emailed temporary password expires.
*/
-$wgNewPasswordExpiry = 3600 * 24 * 7;
+$wgNewPasswordExpiry = 3600 * 24 * 7;
/**
* The time, in seconds, when an email confirmation email expires
@@ -1057,7 +1111,7 @@ $wgUserEmailConfirmationTokenExpiry = 7 * 24 * 60 * 60;
* "password" => password
* </code>
*/
-$wgSMTP = false;
+$wgSMTP = false;
/**
* Additional email parameters, will be passed as the last argument to mail() call.
@@ -1069,7 +1123,7 @@ $wgAdditionalMailParams = null;
* True: from page editor if s/he opted-in. False: Enotif mails appear to come
* from $wgEmergencyContact
*/
-$wgEnotifFromEditor = false;
+$wgEnotifFromEditor = false;
// TODO move UPO to preferences probably ?
# If set to true, users get a corresponding option in their preferences and can choose to enable or disable at their discretion
@@ -1081,30 +1135,30 @@ $wgEnotifFromEditor = false;
* highly recommended. It prevents MediaWiki from being used as an open spam
* relay.
*/
-$wgEmailAuthentication = true;
+$wgEmailAuthentication = true;
/**
* Allow users to enable email notification ("enotif") on watchlist changes.
*/
-$wgEnotifWatchlist = false;
+$wgEnotifWatchlist = false;
/**
* Allow users to enable email notification ("enotif") when someone edits their
* user talk page.
*/
-$wgEnotifUserTalk = false;
+$wgEnotifUserTalk = false;
/**
* Set the Reply-to address in notifications to the editor's address, if user
* allowed this in the preferences.
*/
-$wgEnotifRevealEditorAddress = false;
+$wgEnotifRevealEditorAddress = false;
/**
* Send notification mails on minor edits to watchlist pages. This is enabled
* by default. Does not affect user talk notifications.
*/
-$wgEnotifMinorEdits = true;
+$wgEnotifMinorEdits = true;
/**
* Send a generic mail instead of a personalised mail for each user. This
@@ -1134,7 +1188,7 @@ $wgEnotifUseRealName = false;
/**
* Array of usernames who will be sent a notification email for every change
- * which occurs on a wiki.
+ * which occurs on a wiki. Users will not be notified of their own changes.
*/
$wgUsersNotifiedOnAllChanges = array();
@@ -1146,17 +1200,17 @@ $wgUsersNotifiedOnAllChanges = array();
* @{
*/
/** Database host name or IP address */
-$wgDBserver = 'localhost';
+$wgDBserver = 'localhost';
/** Database port number (for PostgreSQL) */
-$wgDBport = 5432;
+$wgDBport = 5432;
/** Name of the database */
-$wgDBname = 'my_wiki';
+$wgDBname = 'my_wiki';
/** Database username */
-$wgDBuser = 'wikiuser';
+$wgDBuser = 'wikiuser';
/** Database user's password */
-$wgDBpassword = '';
+$wgDBpassword = '';
/** Database type */
-$wgDBtype = 'mysql';
+$wgDBtype = 'mysql';
/** Separate username for maintenance tasks. Leave as null to use the default. */
$wgDBadminuser = null;
@@ -1169,12 +1223,12 @@ $wgDBadminpassword = null;
* selected database type (eg SearchMySQL), or set to a class
* name to override to a custom search engine.
*/
-$wgSearchType = null;
+$wgSearchType = null;
/** Table name prefix */
-$wgDBprefix = '';
+$wgDBprefix = '';
/** MySQL table options to use during installation or update */
-$wgDBTableOptions = 'ENGINE=InnoDB';
+$wgDBTableOptions = 'ENGINE=InnoDB';
/**
* SQL Mode - default is turning off all modes, including strict, if set.
@@ -1185,10 +1239,10 @@ $wgDBTableOptions = 'ENGINE=InnoDB';
$wgSQLMode = '';
/** Mediawiki schema */
-$wgDBmwschema = 'mediawiki';
+$wgDBmwschema = 'mediawiki';
/** To override default SQLite data directory ($docroot/../data) */
-$wgSQLiteDataDir = '';
+$wgSQLiteDataDir = '';
/**
* Make all database connections secretly go to localhost. Fool the load balancer
@@ -1215,7 +1269,7 @@ $wgAllDBsAreLocalhost = false;
* $wgSharedPrefix is the table prefix for the shared database. It defaults to
* $wgDBprefix.
*/
-$wgSharedDB = null;
+$wgSharedDB = null;
/** @see $wgSharedDB */
$wgSharedPrefix = false;
@@ -1265,7 +1319,7 @@ $wgSharedTables = array( 'user', 'user_properties' );
* up, we at Wikimedia set read_only=1 in my.cnf on all our DB servers, even
* our masters, and then set read_only=0 on masters at runtime.
*/
-$wgDBservers = false;
+$wgDBservers = false;
/**
* Load balancer factory configuration
@@ -1277,13 +1331,13 @@ $wgDBservers = false;
* The LBFactory_Multi class is provided for this purpose, please see
* includes/db/LBFactory_Multi.php for configuration information.
*/
-$wgLBFactoryConf = array( 'class' => 'LBFactory_Simple' );
+$wgLBFactoryConf = array( 'class' => 'LBFactory_Simple' );
/** How long to wait for a slave to catch up to the master */
$wgMasterWaitTimeout = 10;
/** File to log database errors to */
-$wgDBerrorLog = false;
+$wgDBerrorLog = false;
/** When to give an error message */
$wgDBClusterTimeout = 10;
@@ -1295,10 +1349,7 @@ $wgDBClusterTimeout = 10;
$wgDBAvgStatusPoll = 2000;
/** Set to true if using InnoDB tables */
-$wgDBtransactions = false;
-/** Set to true for compatibility with extensions that might be checking.
- * MySQL 3.23.x is no longer supported. */
-$wgDBmysql4 = true;
+$wgDBtransactions = false;
/**
* Set to true to engage MySQL 4.1/5.0 charset-related features;
@@ -1316,7 +1367,7 @@ $wgDBmysql4 = true;
* characters (those not in the Basic Multilingual Plane) unless MySQL
* has enhanced their Unicode support.
*/
-$wgDBmysql5 = false;
+$wgDBmysql5 = false;
/**
* Other wikis on this site, can be administered from a single developer
@@ -1437,7 +1488,7 @@ $wgAntiLockFlags = 0;
/**
* Maximum article size in kilobytes
*/
-$wgMaxArticleSize = 2048;
+$wgMaxArticleSize = 2048;
/**
* The minimum amount of memory that MediaWiki "needs"; MediaWiki will try to
@@ -1473,7 +1524,7 @@ $wgCacheDirectory = false;
* - CACHE_NONE: Do not cache
* - CACHE_DB: Store cache objects in the DB
* - CACHE_MEMCACHED: MemCached, must specify servers in $wgMemCachedServers
- * - CACHE_ACCEL: eAccelerator, APC, XCache or WinCache
+ * - CACHE_ACCEL: APC, XCache or WinCache
* - CACHE_DBA: Use PHP's DBA extension to store in a DBM-style
* database. This is slow, and is not recommended for
* anything other than debugging.
@@ -1525,7 +1576,6 @@ $wgObjectCaches = array(
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' ),
@@ -1560,7 +1610,7 @@ $wgSessionsInMemcached = false;
$wgSessionHandler = null;
/** If enabled, will send MemCached debugging information to $wgDebugLogFile */
-$wgMemCachedDebug = false;
+$wgMemCachedDebug = false;
/** The list of MemCached servers and port numbers */
$wgMemCachedServers = array( '127.0.0.1:11000' );
@@ -1625,7 +1675,7 @@ $wgLocalisationCacheConf = array(
);
/** Allow client-side caching of pages */
-$wgCachePages = true;
+$wgCachePages = true;
/**
* Set this to current time to invalidate all prior cached pages. Affects both
@@ -1647,12 +1697,14 @@ $wgStyleVersion = '303';
* This will cache static pages for non-logged-in users to reduce
* database traffic on public sites.
* Must set $wgShowIPinHeader = false
+ * ResourceLoader requests to default language and skins are cached
+ * as well as single module requests.
*/
$wgUseFileCache = false;
/**
* Directory where the cached page will be saved.
- * Defaults to "$wgCacheDirectory/html".
+ * Will default to "{$wgUploadDirectory}/cache" in Setup.php
*/
$wgFileCacheDirectory = false;
@@ -1861,20 +1913,24 @@ $wgExtraLanguageNames = array();
/**
* List of language codes that don't correspond to an actual language.
- * These codes are leftoffs from renames, or other legacy things.
- * Also, qqq is a dummy "language" for documenting messages.
+ * These codes are mostly leftoffs from renames, or other legacy things.
+ * This array makes them not appear as a selectable language on the installer,
+ * and excludes them when running the transstat.php script.
*/
$wgDummyLanguageCodes = array(
- 'als',
- 'bat-smg',
- 'be-x-old',
- 'fiu-vro',
- 'iu',
- 'nb',
- 'qqq',
- 'qqx',
- 'roa-rup',
- 'simple',
+ 'als' => 'gsw',
+ 'bat-smg' => 'sgs',
+ 'be-x-old' => 'be-tarask',
+ 'bh' => 'bho',
+ 'fiu-vro' => 'vro',
+ 'no' => 'nb',
+ 'qqq' => 'qqq', # Used for message documentation.
+ 'qqx' => 'qqx', # Used for viewing message keys.
+ 'roa-rup' => 'rup',
+ 'simple' => 'en',
+ 'zh-classical' => 'lzh',
+ 'zh-min-nan' => 'nan',
+ 'zh-yue' => 'yue',
);
/**
@@ -1884,7 +1940,7 @@ $wgDummyLanguageCodes = array(
* This historic feature is one of the first that was added by former MediaWiki
* team leader Brion Vibber, and is used to support the Esperanto x-system.
*/
-$wgEditEncoding = '';
+$wgEditEncoding = '';
/**
* Set this to true to replace Arabic presentation forms with their standard
@@ -1929,7 +1985,7 @@ $wgAllUnicodeFixes = false;
* user names, etc still must be converted en masse in the database before
* continuing as a UTF-8 wiki.
*/
-$wgLegacyEncoding = false;
+$wgLegacyEncoding = false;
/**
* Browser Blacklist for unicode non compliant browsers. Contains a list of
@@ -2014,7 +2070,7 @@ $wgUseDatabaseMessages = true;
/**
* Expiry time for the message cache key
*/
-$wgMsgCacheExpiry = 86400;
+$wgMsgCacheExpiry = 86400;
/**
* Maximum entry size in the message cache, in bytes
@@ -2120,6 +2176,17 @@ $wgLocaltimezone = null;
*/
$wgLocalTZoffset = null;
+/**
+ * If set to true, this will roll back a few bug fixes introduced in 1.19,
+ * emulating the 1.18 behaviour, to avoid introducing bug 34832. In 1.19,
+ * language variant conversion is disabled in interface messages. Setting this
+ * to true re-enables it.
+ *
+ * This variable should be removed (implicitly false) in 1.20 or earlier.
+ */
+$wgBug34832TransitionalRollback = true;
+
+
/** @} */ # End of language/charset settings
/*************************************************************************//**
@@ -2188,6 +2255,11 @@ $wgAllowRdfaAttributes = false;
$wgAllowMicrodataAttributes = false;
/**
+ * Cleanup as much presentational html like valign -> css vertical-align as we can
+ */
+$wgCleanupPresentationalAttributes = true;
+
+/**
* Should we try to make our HTML output well-formed XML? If set to false,
* output will be a few bytes shorter, and the HTML will arguably be more
* readable. If set to true, life will be much easier for the authors of
@@ -2221,8 +2293,9 @@ $wgXhtmlNamespaces = array();
/**
* Show IP address, for non-logged in users. It's necessary to switch this off
* for some forms of caching.
+ * Will disable file cache.
*/
-$wgShowIPinHeader = true;
+$wgShowIPinHeader = true;
/**
* Site notice shown at the top of each page
@@ -2320,13 +2393,6 @@ $wgUseSiteJs = true;
$wgUseSiteCss = true;
/**
- * Set to false to disable application of access keys and tooltips,
- * eg to avoid keyboard conflicts with system keys or as a low-level
- * optimization.
- */
-$wgEnableTooltipsAndAccesskeys = true;
-
-/**
* Break out of framesets. This can be used to prevent clickjacking attacks,
* or to prevent external sites from framing your site with ads.
*/
@@ -2403,7 +2469,7 @@ $wgFooterIcons = array(
"poweredby" => array(
"mediawiki" => array(
"src" => null, // Defaults to "$wgStylePath/common/images/poweredby_mediawiki_88x31.png"
- "url" => "http://www.mediawiki.org/",
+ "url" => "//www.mediawiki.org/",
"alt" => "Powered by MediaWiki",
)
),
@@ -2431,11 +2497,6 @@ $wgVectorUseSimpleSearch = false;
$wgVectorUseIconWatch = false;
/**
- * Show the name of the current variant as a label in the variants drop-down menu
- */
-$wgVectorShowVariantName = false;
-
-/**
* Display user edit counts in various prominent places.
*/
$wgEdititis = false;
@@ -2448,6 +2509,18 @@ $wgEdititis = false;
*/
$wgBetterDirectionality = true;
+/**
+ * Some web hosts attempt to rewrite all responses with a 404 (not found)
+ * status code, mangling or hiding MediaWiki's output. If you are using such a
+ * host, you should start looking for a better one. While you're doing that,
+ * set this to false to convert some of MediaWiki's 404 responses to 200 so
+ * that the generated error pages can be seen.
+ *
+ * In cases where for technical reasons it is more important for MediaWiki to
+ * send the correct status code than for the body to be transmitted intact,
+ * this configuration variable is ignored.
+ */
+$wgSend404Code = true;
/** @} */ # End of output format settings }
@@ -2471,7 +2544,20 @@ $wgBetterDirectionality = true;
*/
$wgResourceModules = array();
-/*
+/**
+ * Extensions should register foreign module sources here. 'local' is a
+ * built-in source that is not in this array, but defined by
+ * ResourceLoader::__construct() so that it cannot be unset.
+ *
+ * Example:
+ * $wgResourceLoaderSources['foo'] = array(
+ * 'loadScript' => 'http://example.org/w/load.php',
+ * 'apiScript' => 'http://example.org/w/api.php'
+ * );
+ */
+$wgResourceLoaderSources = array();
+
+/**
* Default 'remoteBasePath' value for resource loader modules.
* If not set, then $wgScriptPath will be used as a fallback.
*/
@@ -2526,6 +2612,19 @@ $wgResourceLoaderMinifierMaxLineLength = 1000;
$wgIncludeLegacyJavaScript = true;
/**
+ * Whether to preload the mediawiki.util module as blocking module in the top queue.
+ * Before MediaWiki 1.19, modules used to load slower/less asynchronous which allowed
+ * modules to lack dependencies on 'popular' modules that were likely loaded already.
+ * This setting is to aid scripts during migration by providing mediawiki.util
+ * unconditionally (which was the most commonly missed dependency).
+ * It doesn't cover all missing dependencies obviously but should fix most of them.
+ * This should be removed at some point after site/user scripts have been fixed.
+ * Enable this if your wiki has a large amount of user/site scripts that are lacking
+ * dependencies.
+ */
+$wgPreloadJavaScriptMwUtil = false;
+
+/**
* 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 ) ..."
@@ -2570,6 +2669,13 @@ $wgResourceLoaderValidateJS = true;
*/
$wgResourceLoaderValidateStaticJS = false;
+/**
+ * If set to true, asynchronous loading of bottom-queue scripts in the <head>
+ * will be enabled. This is an experimental feature that's supposed to make
+ * JavaScript load faster.
+ */
+$wgResourceLoaderExperimentalAsyncLoading = false;
+
/** @} */ # End of resource loader settings }
@@ -2582,7 +2688,7 @@ $wgResourceLoaderValidateStaticJS = false;
* Name of the project namespace. If left set to false, $wgSitename will be
* used instead.
*/
-$wgMetaNamespace = false;
+$wgMetaNamespace = false;
/**
* Name of the project talk namespace.
@@ -2607,12 +2713,12 @@ $wgMetaNamespaceTalk = false;
* Custom namespaces should start at 100 to avoid conflicting with standard
* namespaces, and should always follow the even/odd main/talk pattern.
*/
-#$wgExtraNamespaces =
-# array(100 => "Hilfe",
-# 101 => "Hilfe_Diskussion",
-# 102 => "Aide",
-# 103 => "Discussion_Aide"
-# );
+# $wgExtraNamespaces = array(
+# 100 => "Hilfe",
+# 101 => "Hilfe_Diskussion",
+# 102 => "Aide",
+# 103 => "Discussion_Aide"
+# );
$wgExtraNamespaces = array();
/**
@@ -2670,7 +2776,7 @@ $wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+";
/**
* The interwiki prefix of the current wiki, or false if it doesn't have one.
*/
-$wgLocalInterwiki = false;
+$wgLocalInterwiki = false;
/**
* Expiry time for cache of interwiki table
@@ -2922,7 +3028,7 @@ $wgTidyInternal = extension_loaded( 'tidy' );
$wgDebugTidy = false;
/** Allow raw, unchecked HTML in <html>...</html> sections.
- * THIS IS VERY DANGEROUS on a publically editable site, so USE wgGroupPermissions
+ * THIS IS VERY DANGEROUS on a publicly editable site, so USE wgGroupPermissions
* TO RESTRICT EDITING to only those that you trust
*/
$wgRawHtml = false;
@@ -2988,7 +3094,7 @@ $wgPreprocessorCacheThreshold = 1000;
$wgEnableScaryTranscluding = false;
/**
- * Expiry time for interwiki transclusion
+ * (see next option $wgGlobalDatabase).
*/
$wgTranscludeCacheExpiry = 3600;
@@ -3074,13 +3180,13 @@ $wgPasswordResetRoutes = array(
/**
* Maximum number of Unicode characters in signature
*/
-$wgMaxSigChars = 255;
+$wgMaxSigChars = 255;
/**
* Maximum number of bytes in username. You want to run the maintenance
* script ./maintenance/checkUsernames.php once you have changed this value.
*/
-$wgMaxNameChars = 255;
+$wgMaxNameChars = 255;
/**
* Array of usernames which may not be registered or logged in from
@@ -3091,6 +3197,7 @@ $wgReservedUsernames = array(
'Conversion script', // Used for the old Wikipedia software upgrade
'Maintenance script', // Maintenance scripts which perform editing, image import script
'Template namespace initialisation script', // Used in 1.2->1.3 upgrade
+ 'ScriptImporter', // Default user name used by maintenance/importSiteScripts.php
'msg:double-redirect-fixer', // Automatic double redirect fix
'msg:usermessage-editor', // Default user for leaving user messages
'msg:proxyblocker', // For Special:Blockme
@@ -3260,7 +3367,7 @@ $wgAllowPrefChange = array();
* http://lists.wikimedia.org/pipermail/wikitech-l/2010-October/050065.html
* @since 1.17
*/
-$wgSecureLogin = false;
+$wgSecureLogin = false;
/** @} */ # end user accounts }
@@ -3272,15 +3379,15 @@ $wgSecureLogin = false;
/**
* Number of seconds before autoblock entries expire. Default 86400 = 1 day.
*/
-$wgAutoblockExpiry = 86400;
+$wgAutoblockExpiry = 86400;
/**
* Set this to true to allow blocked users to edit their own user talk page.
*/
-$wgBlockAllowsUTEdit = false;
+$wgBlockAllowsUTEdit = false;
/** Allow sysops to ban users from accessing Emailuser */
-$wgSysopEmailBans = true;
+$wgSysopEmailBans = true;
/**
* Limits on the possible sizes of range blocks.
@@ -3329,6 +3436,12 @@ $wgEmailConfirmToEdit = false;
/**
* Permission keys given to users in each group.
+ * This is an array where the keys are all groups and each value is an
+ * array of the format (right => boolean).
+ *
+ * The second format is used to support per-namespace permissions.
+ * Note that this feature does not fully work for all permission types.
+ *
* All users are implicitly in the '*' group including anonymous visitors;
* logged-in users are all implicitly in the 'user' group. These will be
* combined with the permissions of all groups that a given user is listed
@@ -3342,7 +3455,7 @@ $wgEmailConfirmToEdit = false;
* Functionality to make pages inaccessible has not been extensively tested
* for security. Use at your own risk!
*
- * This replaces wgWhitelistAccount and wgWhitelistEdit
+ * This replaces $wgWhitelistAccount and $wgWhitelistEdit
*/
$wgGroupPermissions = array();
@@ -3360,7 +3473,7 @@ $wgGroupPermissions['*']['writeapi'] = true;
$wgGroupPermissions['user']['move'] = true;
$wgGroupPermissions['user']['move-subpages'] = true;
$wgGroupPermissions['user']['move-rootuserpages'] = true; // can move root userpages
-//$wgGroupPermissions['user']['movefile'] = true; // Disabled for now due to possible bugs and security concerns
+$wgGroupPermissions['user']['movefile'] = true;
$wgGroupPermissions['user']['read'] = true;
$wgGroupPermissions['user']['edit'] = true;
$wgGroupPermissions['user']['createpage'] = true;
@@ -3424,7 +3537,6 @@ $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;
@@ -3602,7 +3714,7 @@ $wgAutopromoteOnce = array(
'onView' => array()
);
-/*
+/**
* Put user rights log entries for autopromotion in recent changes?
* @since 1.18
*/
@@ -3669,6 +3781,7 @@ $wgSummarySpamRegex = array();
* - false : let it through
*
* @deprecated since 1.17 Use hooks. See SpamBlacklist extension.
+ * @var $wgFilterCallback bool|string|Closure
*/
$wgFilterCallback = false;
@@ -3685,7 +3798,20 @@ $wgEnableDnsBlacklist = false;
$wgEnableSorbs = false;
/**
- * List of DNS blacklists to use, if $wgEnableDnsBlacklist is true
+ * List of DNS blacklists to use, if $wgEnableDnsBlacklist is true. This is an
+ * array of either a URL or an array with the URL and a key (should the blacklist
+ * require a key). For example:
+ * @code
+ * $wgDnsBlacklistUrls = array(
+ * // String containing URL
+ * 'http.dnsbl.sorbs.net',
+ * // Array with URL and key, for services that require a key
+ * array( 'dnsbl.httpbl.net', 'mykey' ),
+ * // Array with just the URL. While this works, it is recommended that you
+ * // just use a string as shown above
+ * array( 'opm.tornevall.org' )
+ * );
+ * @endcode
* @since 1.16
*/
$wgDnsBlacklistUrls = array( 'http.dnsbl.sorbs.net.' );
@@ -3751,6 +3877,12 @@ $wgRateLimitsExcludedIPs = array();
$wgPutIPinRC = true;
/**
+ * Integer defining default number of entries to show on
+ * special pages which are query-pages such as Special:Whatlinkshere.
+ */
+$wgQueryPageDefaultLimit = 50;
+
+/**
* Limit password attempts to X attempts per Y seconds per IP per account.
* Requires memcached.
*/
@@ -3796,7 +3928,7 @@ $wgProxyKey = false;
/**
* Default cookie expiration time. Setting to 0 makes all cookies session-only.
*/
-$wgCookieExpiration = 30*86400;
+$wgCookieExpiration = 180*86400;
/**
* Set to set an explicit domain on the login cookies eg, "justthis.domain.org"
@@ -3888,26 +4020,26 @@ $wgUseTeX = false;
* The debug log file should be not be publicly accessible if it is used, as it
* may contain private data.
*/
-$wgDebugLogFile = '';
+$wgDebugLogFile = '';
/**
* Prefix for debug log lines
*/
-$wgDebugLogPrefix = '';
+$wgDebugLogPrefix = '';
/**
* If true, instead of redirecting, show a page with a link to the redirect
* destination. This allows for the inspection of PHP error messages, and easy
* resubmission of form data. For developer use only.
*/
-$wgDebugRedirects = false;
+$wgDebugRedirects = false;
/**
* 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.
*/
-$wgDebugRawPage = false;
+$wgDebugRawPage = false;
/**
* Send debug data to an HTML comment in the output.
@@ -3917,12 +4049,12 @@ $wgDebugRawPage = false;
* contains private data for the current user. But it's not ideal for development
* use since data is lost on fatal errors and redirects.
*/
-$wgDebugComments = false;
+$wgDebugComments = false;
/**
* Write SQL queries to the debug log
*/
-$wgDebugDumpSql = false;
+$wgDebugDumpSql = false;
/**
* Set to an array of log group keys to filenames.
@@ -3930,17 +4062,18 @@ $wgDebugDumpSql = false;
* of the regular $wgDebugLogFile. Useful for enabling selective logging
* in production.
*/
-$wgDebugLogGroups = array();
+$wgDebugLogGroups = array();
/**
* Display debug data at the bottom of the main content area.
*
* Useful for developers and technical users trying to working on a closed wiki.
*/
-$wgShowDebug = false;
+$wgShowDebug = false;
/**
* Prefix debug messages with relative timestamp. Very-poor man's profiler.
+ * Since 1.19 also includes memory usage.
*/
$wgDebugTimestamps = false;
@@ -3952,14 +4085,14 @@ $wgDebugPrintHttpHeaders = true;
/**
* Show the contents of $wgHooks in Special:Version
*/
-$wgSpecialVersionShowHooks = false;
+$wgSpecialVersionShowHooks = false;
/**
* Whether to show "we're sorry, but there has been a database error" pages.
* Displaying errors aids in debugging, but may display information useful
* to an attacker.
*/
-$wgShowSQLErrors = false;
+$wgShowSQLErrors = false;
/**
* If set to true, uncaught exceptions will print a complete stack trace
@@ -3985,6 +4118,13 @@ $wgShowHostnames = false;
*/
$wgDevelopmentWarnings = false;
+/**
+ * Release limitation to wfDeprecated warnings, if set to a release number
+ * development warnings will not be generated for deprecations added in releases
+ * after the limit.
+ */
+$wgDeprecationReleaseLimit = '1.17';
+
/** Only record profiling info for pages that took longer than this */
$wgProfileLimit = 0.0;
@@ -4025,7 +4165,7 @@ $wgUDPProfilerPort = '3811';
$wgDebugProfiling = false;
/** Output debug message on every wfProfileIn/wfProfileOut */
-$wgDebugFunctionEntry = 0;
+$wgDebugFunctionEntry = false;
/**
* Destination for wfIncrStats() data...
@@ -4049,14 +4189,6 @@ $wgAggregateStatsID = false;
$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;
-
-/**
* Parser test suite files to be run by parserTests.php when no specific
* filename is passed to it.
*
@@ -4084,6 +4216,36 @@ $wgParserTestFiles = array(
* );
*/
$wgParserTestRemote = false;
+
+/**
+ * Allow running of javascript test suites via [[Special:JavaScriptTest]] (such as QUnit).
+ */
+$wgEnableJavaScriptTest = false;
+
+/**
+ * Configuration for javascript testing.
+ */
+$wgJavaScriptTestConfig = array(
+ 'qunit' => array(
+ 'documentation' => '//www.mediawiki.org/wiki/Manual:JavaScript_unit_testing',
+ ),
+);
+
+
+/**
+ * Overwrite the caching key prefix with custom value.
+ * @since 1.19
+ */
+$wgCachePrefix = false;
+
+/**
+ * Display the new debugging toolbar. This also enables profiling on database
+ * queries and other useful output.
+ * Will disable file cache.
+ *
+ * @since 1.19
+ */
+$wgDebugToolbar = false;
/** @} */ # end of profiling, testing and debugging }
@@ -4174,7 +4336,7 @@ $wgDisableSearchUpdate = false;
* </code>
*/
$wgNamespacesToBeSearchedDefault = array(
- NS_MAIN => true,
+ NS_MAIN => true,
);
/**
@@ -4184,8 +4346,8 @@ $wgNamespacesToBeSearchedDefault = array(
* Same format as $wgNamespacesToBeSearchedDefault
*/
$wgNamespacesToBeSearchedHelp = array(
- NS_PROJECT => true,
- NS_HELP => true,
+ NS_PROJECT => true,
+ NS_HELP => true,
);
/**
@@ -4228,6 +4390,27 @@ $wgUseTwoButtonsSearchForm = true;
*/
$wgSitemapNamespaces = false;
+/**
+ * Custom namespace priorities for sitemaps. Setting this will allow you to
+ * set custom priorities to namsepaces when sitemaps are generated using the
+ * maintenance/generateSitemap.php script.
+ *
+ * This should be a map of namespace IDs to priority
+ * Example:
+ * $wgSitemapNamespacesPriorities = array(
+ * NS_USER => '0.9',
+ * NS_HELP => '0.0',
+ * );
+ */
+$wgSitemapNamespacesPriorities = false;
+
+/**
+ * If true, searches for IP addresses will be redirected to that IP's
+ * contributions page. E.g. searching for "1.2.3.4" will redirect to
+ * [[Special:Contributions/1.2.3.4]]
+ */
+$wgEnableSearchContributorsByIP = true;
+
/** @} */ # end of search settings
/************************************************************************//**
@@ -4252,7 +4435,7 @@ $wgDiff = '/usr/bin/diff';
* can specify namespaces of pages they have special treatment for
*/
$wgPreviewOnOpenNamespaces = array(
- NS_CATEGORY => true
+ NS_CATEGORY => true
);
/**
@@ -4313,16 +4496,16 @@ $wgMaintenanceScripts = array();
* still be possible. To prevent database writes completely, use the read_only
* option in MySQL.
*/
-$wgReadOnly = null;
+$wgReadOnly = null;
/**
* If this lock file exists (size > 0), the wiki will be forced into read-only mode.
* Its contents will be shown to users as part of the read-only warning
* message.
*
- * Defaults to "{$wgUploadDirectory}/lock_yBgMBwiR".
+ * Will default to "{$wgUploadDirectory}/lock_yBgMBwiR" in Setup.php
*/
-$wgReadOnlyFile = false;
+$wgReadOnlyFile = false;
/**
* When you run the web-based upgrade utility, it will tell you what to set
@@ -4363,7 +4546,7 @@ $wgRCFilterByAge = false;
* Special:Recentchangeslinked pages.
*/
$wgRCLinkLimits = array( 50, 100, 250, 500 );
-$wgRCLinkDays = array( 1, 3, 7, 14, 30 );
+$wgRCLinkDays = array( 1, 3, 7, 14, 30 );
/**
* Send recent changes updates via UDP. The updates will be formatted for IRC.
@@ -4454,23 +4637,23 @@ $wgFeedClasses = array(
$wgAdvertisedFeedTypes = array( 'atom' );
/** Show watching users in recent changes, watchlist and page history views */
-$wgRCShowWatchingUsers = false; # UPO
+$wgRCShowWatchingUsers = false; # UPO
/** Show watching users in Page views */
-$wgPageShowWatchingUsers = false;
+$wgPageShowWatchingUsers = false;
/** Show the amount of changed characters in recent changes */
-$wgRCShowChangedSize = true;
+$wgRCShowChangedSize = true;
/**
* If the difference between the character counts of the text
* before and after the edit is below that value, the value will be
* highlighted on the RC page.
*/
-$wgRCChangedSizeThreshold = 500;
+$wgRCChangedSizeThreshold = 500;
/**
* Show "Updated (since my last visit)" marker in RC view, watchlist and history
* view for watched pages with new changes */
-$wgShowUpdatedMarker = true;
+$wgShowUpdatedMarker = true;
/**
* Disable links to talk pages of anonymous users (IPs) in listings on special
@@ -4593,7 +4776,7 @@ $wgExportMaxHistory = 0;
/**
* Return distinct author list (when not returning full history)
*/
-$wgExportAllowListContributors = false ;
+$wgExportAllowListContributors = false;
/**
* If non-zero, Special:Export accepts a "pagelink-depth" parameter
@@ -4613,6 +4796,11 @@ $wgExportMaxLinkDepth = 0;
*/
$wgExportFromNamespaces = false;
+/**
+* Whether to allow exporting the entire wiki into a single file
+*/
+$wgExportAllowAll = false;
+
/** @} */ # end of import/export }
/*************************************************************************//**
@@ -4643,12 +4831,6 @@ $wgExtensionFunctions = array();
$wgExtensionMessagesFiles = array();
/**
- * Aliases for special pages provided by extensions.
- * @deprecated since 1.16 Use $specialPageAliases in a file referred to by $wgExtensionMessagesFiles
- */
-$wgExtensionAliasesFiles = array();
-
-/**
* Parser output hooks.
* This is an associative array where the key is an extension-defined tag
* (typically the extension name), and the value is a PHP callback.
@@ -4691,13 +4873,13 @@ $wgAutoloadClasses = array();
*
* <code>
* $wgExtensionCredits[$type][] = array(
- * 'name' => 'Example extension',
- * 'version' => 1.9,
- * 'path' => __FILE__,
- * 'author' => 'Foo Barstein',
- * 'url' => 'http://wwww.example.com/Example%20Extension/',
- * 'description' => 'An example extension',
- * 'descriptionmsg' => 'exampleextension-desc',
+ * 'name' => 'Example extension',
+ * 'version' => 1.9,
+ * 'path' => __FILE__,
+ * 'author' => 'Foo Barstein',
+ * 'url' => 'http://wwww.example.com/Example%20Extension/',
+ * 'description' => 'An example extension',
+ * 'descriptionmsg' => 'exampleextension-desc',
* );
* </code>
*
@@ -4709,7 +4891,7 @@ $wgExtensionCredits = array();
/**
* Authentication plugin.
- * @var AuthPlugin
+ * @var $wgAuth AuthPlugin
*/
$wgAuth = null;
@@ -4740,6 +4922,7 @@ $wgJobClasses = array(
);
/**
+
* Jobs that must be explicitly requested, i.e. aren't run by job runners unless special flags are set.
*
* These can be:
@@ -4755,7 +4938,7 @@ $wgJobTypesExcludedFromDefaultQueue = array();
* Expensive Querypages are already updated.
*/
$wgSpecialPageCacheUpdates = array(
- 'Statistics' => array('SiteStatsUpdate','cacheUpdate')
+ 'Statistics' => array( 'SiteStatsUpdate', 'cacheUpdate' )
);
/**
@@ -4767,7 +4950,6 @@ $wgSpecialPageCacheUpdates = array(
*/
$wgExceptionHooks = array();
-
/**
* Page property link table invalidation lists. When a page property
* changes, this may require other link tables to be updated (eg
@@ -4789,7 +4971,7 @@ $wgPagePropLinkInvalidations = array(
/**
* Use experimental, DMOZ-like category browser
*/
-$wgUseCategoryBrowser = false;
+$wgUseCategoryBrowser = false;
/**
* On category pages, show thumbnail gallery for images belonging to that
@@ -4842,7 +5024,8 @@ $wgCategoryCollation = 'uppercase';
* an action, which is a specific kind of event that can exist in that
* log type.
*/
-$wgLogTypes = array( '',
+$wgLogTypes = array(
+ '',
'block',
'protect',
'rights',
@@ -4895,6 +5078,9 @@ $wgFilterLogTypes = array(
* will be listed in the user interface.
*
* Extensions with custom log types may add to this array.
+ *
+ * Since 1.19, if you follow the naming convention log-name-TYPE,
+ * where TYPE is your log type, yoy don't need to use this array.
*/
$wgLogNames = array(
'' => 'all-logs-page',
@@ -4915,6 +5101,9 @@ $wgLogNames = array(
* top of each log type.
*
* Extensions with custom log types may add to this array.
+ *
+ * Since 1.19, if you follow the naming convention log-description-TYPE,
+ * where TYPE is your log type, yoy don't need to use this array.
*/
$wgLogHeaders = array(
'' => 'alllogstext',
@@ -4946,33 +5135,32 @@ $wgLogActions = array(
'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',
);
/**
* The same as above, but here values are names of functions,
* not messages.
* @see LogPage::actionText
- */
-$wgLogActionsHandlers = array();
+ * @see LogFormatter
+ */
+$wgLogActionsHandlers = array(
+ // move, move_redir
+ 'move/*' => 'MoveLogFormatter',
+ // delete, restore, revision, event
+ 'delete/*' => 'DeleteLogFormatter',
+ 'suppress/revision' => 'DeleteLogFormatter',
+ 'suppress/event' => 'DeleteLogFormatter',
+ 'suppress/delete' => 'DeleteLogFormatter',
+ 'patrol/patrol' => 'PatrolLogFormatter',
+);
/**
* Maintain a log of newusers at Log/newusers?
@@ -5056,6 +5244,7 @@ $wgSpecialPageGroups = array(
'Block' => 'users',
'Unblock' => 'users',
'Preferences' => 'users',
+ 'ChangeEmail' => 'users',
'ChangePassword' => 'users',
'DeletedContributions' => 'users',
'PasswordReset' => 'users',
@@ -5100,6 +5289,7 @@ $wgSpecialPageGroups = array(
'Specialpages' => 'other',
'Blockme' => 'other',
'Booksources' => 'other',
+ 'JavaScriptTest' => 'other',
);
/** Whether or not to sort special pages in Special:Specialpages */
@@ -5136,16 +5326,24 @@ $wgMaxRedirectLinksRetrieved = 500;
* Unsetting core actions will probably cause things to complain loudly.
*/
$wgActions = array(
- 'credits' => true,
- 'deletetrackback' => true,
- 'info' => true,
- 'markpatrolled' => true,
- 'purge' => true,
- 'revert' => true,
+ 'credits' => true,
+ 'delete' => true,
+ 'edit' => true,
+ 'history' => true,
+ 'info' => true,
+ 'markpatrolled' => true,
+ 'protect' => true,
+ 'purge' => true,
+ 'raw' => true,
+ 'render' => true,
+ 'revert' => true,
'revisiondelete' => true,
- 'rollback' => true,
- 'unwatch' => true,
- 'watch' => true,
+ 'rollback' => true,
+ 'submit' => true,
+ 'unprotect' => true,
+ 'unwatch' => true,
+ 'view' => true,
+ 'watch' => true,
);
/**
diff --git a/includes/DeferredUpdates.php b/includes/DeferredUpdates.php
new file mode 100644
index 00000000..262994e3
--- /dev/null
+++ b/includes/DeferredUpdates.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Interface that deferrable updates should implement. Basically required so we
+ * can validate input on DeferredUpdates::addUpdate()
+ *
+ * @since 1.19
+ */
+interface DeferrableUpdate {
+ /**
+ * Perform the actual work
+ */
+ function doUpdate();
+}
+
+/**
+ * Class for mananging the deferred updates.
+ *
+ * @since 1.19
+ */
+class DeferredUpdates {
+ /**
+ * Store of updates to be deferred until the end of the request.
+ */
+ private static $updates = array();
+
+ /**
+ * Add an update to the deferred list
+ * @param $update DeferrableUpdate Some object that implements doUpdate()
+ */
+ public static function addUpdate( DeferrableUpdate $update ) {
+ array_push( self::$updates, $update );
+ }
+
+ /**
+ * HTMLCacheUpdates are the most common deferred update people use. This
+ * is a shortcut method for that.
+ * @see HTMLCacheUpdate::__construct()
+ * @param $title
+ * @param $table
+ */
+ public static function addHTMLCacheUpdate( $title, $table ) {
+ self::addUpdate( new HTMLCacheUpdate( $title, $table ) );
+ }
+
+ /**
+ * Do any deferred updates and clear the list
+ *
+ * @param $commit String: set to 'commit' to commit after every update to
+ * prevent lock contention
+ */
+ public static function doUpdates( $commit = '' ) {
+ global $wgDeferredUpdateList;
+
+ wfProfileIn( __METHOD__ );
+
+ $updates = array_merge( $wgDeferredUpdateList, self::$updates );
+
+ // No need to get master connections in case of empty updates array
+ if ( !count( $updates ) ) {
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
+ $doCommit = $commit == 'commit';
+ if ( $doCommit ) {
+ $dbw = wfGetDB( DB_MASTER );
+ }
+
+ foreach ( $updates as $update ) {
+ $update->doUpdate();
+
+ if ( $doCommit && $dbw->trxLevel() ) {
+ $dbw->commit( __METHOD__ );
+ }
+ }
+
+ self::clearPendingUpdates();
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Clear all pending updates without performing them. Generally, you don't
+ * want or need to call this. Unit tests need it though.
+ */
+ public static function clearPendingUpdates() {
+ global $wgDeferredUpdateList;
+ $wgDeferredUpdateList = self::$updates = array();
+ }
+}
diff --git a/includes/Defines.php b/includes/Defines.php
index ff7d7980..26deb2ba 100644
--- a/includes/Defines.php
+++ b/includes/Defines.php
@@ -2,7 +2,7 @@
/**
* A few constants that might be needed during LocalSettings.php.
*
- * Note: these constants must all be resolvable at compile time by HipHop,
+ * 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.
*
@@ -92,7 +92,7 @@ define( 'CACHE_ANYTHING', -1 ); // Use anything, as long as it works
define( 'CACHE_NONE', 0 ); // Do not cache
define( 'CACHE_DB', 1 ); // Store cache objects in the DB
define( 'CACHE_MEMCACHED', 2 ); // MemCached, must specify servers in $wgMemCacheServers
-define( 'CACHE_ACCEL', 3 ); // eAccelerator
+define( 'CACHE_ACCEL', 3 ); // APC, XCache or WinCache
define( 'CACHE_DBA', 4 ); // Use PHP's DBA extension to store in a DBM-style database
/**@}*/
@@ -149,13 +149,12 @@ define( 'MW_DATE_ISO', 'ISO 8601' );
/**@{
* RecentChange type identifiers
- * This may be obsolete; log items are now used for moves?
*/
define( 'RC_EDIT', 0);
define( 'RC_NEW', 1);
-define( 'RC_MOVE', 2);
+define( 'RC_MOVE', 2); // obsolete
define( 'RC_LOG', 3);
-define( 'RC_MOVE_OVER_REDIRECT', 4);
+define( 'RC_MOVE_OVER_REDIRECT', 4); // obsolete
/**@}*/
/**@{
diff --git a/includes/EditPage.php b/includes/EditPage.php
index e6e7111d..d00d9114 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -15,31 +15,133 @@
* 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.
+ *
+ * Surgeon General's Warning: prolonged exposure to this class is known to cause
+ * headaches, which may be fatal.
*/
class EditPage {
+
+ /**
+ * Status: Article successfully updated
+ */
const AS_SUCCESS_UPDATE = 200;
+
+ /**
+ * Status: Article successfully created
+ */
const AS_SUCCESS_NEW_ARTICLE = 201;
+
+ /**
+ * Status: Article update aborted by a hook function
+ */
const AS_HOOK_ERROR = 210;
+
+ /**
+ * Status: The filter function set in $wgFilterCallback returned true (= block it)
+ */
const AS_FILTERING = 211;
+
+ /**
+ * Status: A hook function returned an error
+ */
const AS_HOOK_ERROR_EXPECTED = 212;
+
+ /**
+ * Status: User is blocked from editting this page
+ */
const AS_BLOCKED_PAGE_FOR_USER = 215;
+
+ /**
+ * Status: Content too big (> $wgMaxArticleSize)
+ */
const AS_CONTENT_TOO_BIG = 216;
+
+ /**
+ * Status: User cannot edit? (not used)
+ */
const AS_USER_CANNOT_EDIT = 217;
+
+ /**
+ * Status: this anonymous user is not allowed to edit this page
+ */
const AS_READ_ONLY_PAGE_ANON = 218;
+
+ /**
+ * Status: this logged in user is not allowed to edit this page
+ */
const AS_READ_ONLY_PAGE_LOGGED = 219;
+
+ /**
+ * Status: wiki is in readonly mode (wfReadOnly() == true)
+ */
const AS_READ_ONLY_PAGE = 220;
+
+ /**
+ * Status: rate limiter for action 'edit' was tripped
+ */
const AS_RATE_LIMITED = 221;
+
+ /**
+ * Status: article was deleted while editting and param wpRecreate == false or form
+ * was not posted
+ */
const AS_ARTICLE_WAS_DELETED = 222;
+
+ /**
+ * Status: user tried to create this page, but is not allowed to do that
+ * ( Title->usercan('create') == false )
+ */
const AS_NO_CREATE_PERMISSION = 223;
+
+ /**
+ * Status: user tried to create a blank page
+ */
const AS_BLANK_ARTICLE = 224;
+
+ /**
+ * Status: (non-resolvable) edit conflict
+ */
const AS_CONFLICT_DETECTED = 225;
+
+ /**
+ * Status: no edit summary given and the user has forceeditsummary set and the user is not
+ * editting in his own userspace or talkspace and wpIgnoreBlankSummary == false
+ */
const AS_SUMMARY_NEEDED = 226;
+
+ /**
+ * Status: user tried to create a new section without content
+ */
const AS_TEXTBOX_EMPTY = 228;
+
+ /**
+ * Status: article is too big (> $wgMaxArticleSize), after merging in the new section
+ */
const AS_MAX_ARTICLE_SIZE_EXCEEDED = 229;
+
+ /**
+ * not used
+ */
const AS_OK = 230;
+
+ /**
+ * Status: WikiPage::doEdit() was unsuccessfull
+ */
const AS_END = 231;
+
+ /**
+ * Status: summary contained spam according to one of the regexes in $wgSummarySpamRegex
+ */
const AS_SPAM_ERROR = 232;
+
+ /**
+ * Status: anonymous user is not allowed to upload (User::isAllowed('upload') == false)
+ */
const AS_IMAGE_REDIRECT_ANON = 233;
+
+ /**
+ * Status: logged in user is not allowed to upload (User::isAllowed('upload') == false)
+ */
const AS_IMAGE_REDIRECT_LOGGED = 234;
/**
@@ -52,7 +154,7 @@ class EditPage {
*/
var $mTitle;
private $mContextTitle = null;
- var $action;
+ var $action = 'submit';
var $isConflict = false;
var $isCssJsSubpage = false;
var $isCssSubpage = false;
@@ -88,20 +190,20 @@ class EditPage {
var $save = false, $preview = false, $diff = false;
var $minoredit = false, $watchthis = false, $recreate = false;
var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
- var $edittime = '', $section = '', $starttime = '';
+ var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
# Placeholders for text injection by hooks (must be HTML)
# extensions should take care to _append_ to the present value
- public $editFormPageTop; // Before even the preview
- public $editFormTextTop;
- public $editFormTextBeforeContent;
- public $editFormTextAfterWarn;
- public $editFormTextAfterTools;
- public $editFormTextBottom;
- public $editFormTextAfterContent;
- public $previewTextAfterContent;
- public $mPreloadText;
+ public $editFormPageTop = ''; // Before even the preview
+ public $editFormTextTop = '';
+ public $editFormTextBeforeContent = '';
+ public $editFormTextAfterWarn = '';
+ public $editFormTextAfterTools = '';
+ public $editFormTextBottom = '';
+ public $editFormTextAfterContent = '';
+ public $previewTextAfterContent = '';
+ public $mPreloadText = '';
/* $didSave should be set to true whenever an article was succesfully altered. */
public $didSave = false;
@@ -110,34 +212,29 @@ class EditPage {
public $suppressIntro = false;
/**
- * @todo document
* @param $article Article
*/
- function __construct( $article ) {
- $this->mArticle =& $article;
+ public function __construct( Article $article ) {
+ $this->mArticle = $article;
$this->mTitle = $article->getTitle();
- $this->action = 'submit';
-
- # Placeholders for text injection by hooks (empty per default)
- $this->editFormPageTop =
- $this->editFormTextTop =
- $this->editFormTextBeforeContent =
- $this->editFormTextAfterWarn =
- $this->editFormTextAfterTools =
- $this->editFormTextBottom =
- $this->editFormTextAfterContent =
- $this->previewTextAfterContent =
- $this->mPreloadText = "";
}
/**
* @return Article
*/
- function getArticle() {
+ public function getArticle() {
return $this->mArticle;
}
/**
+ * @since 1.19
+ * @return Title
+ */
+ public function getTitle() {
+ return $this->mTitle;
+ }
+
+ /**
* Set the context Title object
*
* @param $title Title object or null
@@ -162,193 +259,6 @@ class EditPage {
}
}
- /**
- * 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;
-
- wfProfileIn( __METHOD__ );
- # Get variables from query string :P
- $section = $wgRequest->getVal( 'section' );
-
- $preload = $wgRequest->getVal( 'preload',
- // Custom preload text for new sections
- $section === 'new' ? 'MediaWiki:addsection-preload' : '' );
- $undoafter = $wgRequest->getVal( 'undoafter' );
- $undo = $wgRequest->getVal( 'undo' );
-
- // For message page not locally set, use the i18n message.
- // For other non-existent articles, use preload text if any.
- if ( !$this->mTitle->exists() ) {
- if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- # If this is a system message, get the default text.
- $text = $this->mTitle->getDefaultMessageText();
- if( $text === false ) {
- $text = $this->getPreloadedText( $preload );
- }
- } else {
- # If requested, preload some text.
- $text = $this->getPreloadedText( $preload );
- }
- // For existing pages, get text based on "undo" or section parameters.
- } else {
- $text = $this->mArticle->getContent();
- if ( $undo > 0 && $undoafter > 0 && $undo < $undoafter ) {
- # If they got undoafter and undo round the wrong way, switch them
- list( $undo, $undoafter ) = array( $undoafter, $undo );
- }
- if ( $undo > 0 && $undo > $undoafter ) {
- # Undoing a specific edit overrides section editing; section-editing
- # doesn't work with undoing.
- if ( $undoafter ) {
- $undorev = Revision::newFromId( $undo );
- $oldrev = Revision::newFromId( $undoafter );
- } else {
- $undorev = Revision::newFromId( $undo );
- $oldrev = $undorev ? $undorev->getPrevious() : null;
- }
-
- # Sanity check, make sure it's the right page,
- # the revisions exist and they were not deleted.
- # Otherwise, $text will be left as-is.
- if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
- $undorev->getPage() == $oldrev->getPage() &&
- $undorev->getPage() == $this->mArticle->getID() &&
- !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
- !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
-
- $undotext = $this->mArticle->getUndoText( $undorev, $oldrev );
- if ( $undotext === false ) {
- # Warn the user that something went wrong
- $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-failure">' . wfMsgNoTrans( 'undo-failure' ) . '</div>' );
- } else {
- $text = $undotext;
- # Inform the user of our success and set an automatic edit summary
- $this->editFormPageTop .= $wgOut->parse( '<div class="mw-undo-success">' . wfMsgNoTrans( 'undo-success' ) . '</div>' );
- $firstrev = $oldrev->getNext();
- # If we just undid one rev, use an autosummary
- if ( $firstrev->mId == $undo ) {
- $this->summary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() );
- $this->undidRev = $undo;
- }
- $this->formtype = 'diff';
- }
- } else {
- // Failed basic sanity checks.
- // Older revisions may have been removed since the link
- // was created, or we may simply have got bogus input.
- $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-norev">' . wfMsgNoTrans( 'undo-norev' ) . '</div>' );
- }
- } elseif ( $section != '' ) {
- if ( $section == 'new' ) {
- $text = $this->getPreloadedText( $preload );
- } else {
- // Get section edit text (returns $def_text for invalid sections)
- $text = $wgParser->getSection( $text, $section, $def_text );
- }
- }
- }
-
- wfProfileOut( __METHOD__ );
- return $text;
- }
-
- /**
- * Use this method before edit() to preload some text into the edit box
- *
- * @param $text string
- */
- public function setPreloadedText( $text ) {
- $this->mPreloadText = $text;
- }
-
- /**
- * Get the contents to be preloaded into the box, either set by
- * an earlier setPreloadText() or by loading the given page.
- *
- * @param $preload String: representing the title to preload from.
- * @return String
- */
- protected function getPreloadedText( $preload ) {
- global $wgUser, $wgParser;
- if ( !empty( $this->mPreloadText ) ) {
- return $this->mPreloadText;
- } elseif ( $preload !== '' ) {
- $title = Title::newFromText( $preload );
- # Check for existence to avoid getting MediaWiki:Noarticletext
- if ( isset( $title ) && $title->exists() && $title->userCanRead() ) {
- $article = new Article( $title );
-
- if ( $article->isRedirect() ) {
- $title = Title::newFromRedirectRecurse( $article->getContent() );
- # Redirects to missing titles are displayed, to hidden pages are followed
- # Copying observed behaviour from ?action=view
- if ( $title->exists() ) {
- if ($title->userCanRead() ) {
- $article = new Article( $title );
- } else {
- return "";
- }
- }
- }
- $parserOptions = ParserOptions::newFromUser( $wgUser );
- return $wgParser->getPreloadText( $article->getContent(), $title, $parserOptions );
- }
- }
- return '';
- }
-
- /**
- * 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 !== null ) {
- return $this->deletedSinceEdit;
- }
-
- $this->deletedSinceEdit = false;
-
- if ( $this->mTitle->isDeletedQuick() ) {
- $this->lastDelete = $this->getLastDelete();
- if ( $this->lastDelete ) {
- $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
- if ( $deleteTime > $this->starttime ) {
- $this->deletedSinceEdit = true;
- }
- }
- }
-
- 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() ) {
- $name = $this->mTitle->getSkinFromCssJsSubpage();
- $skins = array_merge(
- array_keys( Skin::getSkinNames() ),
- array( 'common' )
- );
- return !in_array( $name, $skins )
- && in_array( strtolower( $name ), $skins );
- } else {
- return false;
- }
- }
-
function submit() {
$this->edit();
}
@@ -374,8 +284,12 @@ class EditPage {
wfProfileIn( __METHOD__ );
wfDebug( __METHOD__.": enter\n" );
- // This is not an article
- $wgOut->setArticleFlag( false );
+ // If they used redlink=1 and the page exists, redirect to the main article
+ if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
+ $wgOut->redirect( $this->mTitle->getFullURL() );
+ wfProfileOut( __METHOD__ );
+ return;
+ }
$this->importFormData( $wgRequest );
$this->firsttime = false;
@@ -392,45 +306,31 @@ class EditPage {
$this->preview = true;
}
- $wgOut->addModules( array( 'mediawiki.action.edit' ) );
-
- if ( $wgUser->getOption( 'uselivepreview', false ) ) {
- $wgOut->addModules( 'mediawiki.legacy.preview' );
+ if ( $this->save ) {
+ $this->formtype = 'save';
+ } elseif ( $this->preview ) {
+ $this->formtype = 'preview';
+ } elseif ( $this->diff ) {
+ $this->formtype = 'diff';
+ } else { # First time through
+ $this->firsttime = true;
+ if ( $this->previewOnOpen() ) {
+ $this->formtype = 'preview';
+ } else {
+ $this->formtype = 'initial';
+ }
}
- // Bug #19334: textarea jumps when editing articles in IE8
- $wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
$permErrors = $this->getEditPermissionErrors();
if ( $permErrors ) {
+ wfDebug( __METHOD__ . ": User can't edit\n" );
// 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;
- $this->readOnlyPage( $content, true, $permErrors, 'edit' );
+ $this->displayPermissionsError( $permErrors );
+
wfProfileOut( __METHOD__ );
return;
- } else {
- if ( $this->save ) {
- $this->formtype = 'save';
- } elseif ( $this->preview ) {
- $this->formtype = 'preview';
- } elseif ( $this->diff ) {
- $this->formtype = 'diff';
- } else { # First time through
- $this->firsttime = true;
- if ( $this->previewOnOpen() ) {
- $this->formtype = 'preview';
- } else {
- $this->formtype = 'initial';
- }
- }
- }
-
- // If they used redlink=1 and the page exists, redirect to the main article
- if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) {
- $wgOut->redirect( $this->mTitle->getFullURL() );
}
wfProfileIn( __METHOD__."-business-end" );
@@ -444,29 +344,8 @@ class EditPage {
$this->isNew = !$this->mTitle->exists() || $this->section == 'new';
# Show applicable editing introductions
- if ( $this->formtype == 'initial' || $this->firsttime )
+ if ( $this->formtype == 'initial' || $this->firsttime ) {
$this->showIntro();
-
- if ( $this->mTitle->isTalkPage() ) {
- $wgOut->addWikiMsg( 'talkpagetext' );
- }
-
- # Optional notices on a per-namespace and per-page basis
- $editnotice_ns = 'editnotice-'.$this->mTitle->getNamespace();
- $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 );
- $editnotice_base_msg = wfMessage( $editnotice_base )->inContentLanguage();
- if ( $editnotice_base_msg->exists() ) {
- $wgOut->addWikiText( $editnotice_base_msg->plain() );
- }
- }
}
# Attempt submission here. This will check for edit conflicts,
@@ -527,11 +406,70 @@ class EditPage {
}
/**
+ * Display a permissions error page, like OutputPage::showPermissionsErrorPage(),
+ * but with the following differences:
+ * - If redlink=1, the user will be redirected to the page
+ * - If there is content to display or the error occurs while either saving,
+ * previewing or showing the difference, it will be a
+ * "View source for ..." page displaying the source code after the error message.
+ *
+ * @since 1.19
+ * @param $permErrors Array of permissions errors, as returned by
+ * Title::getUserPermissionsErrors().
+ */
+ protected function displayPermissionsError( array $permErrors ) {
+ global $wgRequest, $wgOut;
+
+ if ( $wgRequest->getBool( 'redlink' ) ) {
+ // The edit page was reached via a red link.
+ // Redirect to the article page and let them click the edit tab if
+ // they really want a permission error.
+ $wgOut->redirect( $this->mTitle->getFullUrl() );
+ return;
+ }
+
+ $content = $this->getContent();
+
+ # Use the normal message if there's nothing to display
+ if ( $this->firsttime && $content === '' ) {
+ $action = $this->mTitle->exists() ? 'edit' :
+ ( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
+ throw new PermissionsError( $action, $permErrors );
+ }
+
+ $wgOut->setPageTitle( wfMessage( 'viewsource-title', $this->getContextTitle()->getPrefixedText() ) );
+ $wgOut->addBacklinkSubtitle( $this->getContextTitle() );
+ $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
+ $wgOut->addHTML( "<hr />\n" );
+
+ # If the user made changes, preserve them when showing the markup
+ # (This happens when a user is blocked during edit, for instance)
+ if ( !$this->firsttime ) {
+ $content = $this->textbox1;
+ $wgOut->addWikiMsg( 'viewyourtext' );
+ } else {
+ $wgOut->addWikiMsg( 'viewsourcetext' );
+ }
+
+ $this->showTextbox( $content, 'wpTextbox1', array( 'readonly' ) );
+
+ $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
+ Linker::formatTemplates( $this->getTemplates() ) ) );
+
+ if ( $this->mTitle->exists() ) {
+ $wgOut->returnToMain( null, $this->mTitle );
+ }
+ }
+
+ /**
* Show a read-only error
* Parameters are the same as OutputPage:readOnlyPage()
* Redirect to the article page if redlink=1
+ * @deprecated in 1.19; use displayPermissionsError() instead
*/
function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
+ wfDeprecated( __METHOD__, '1.19' );
+
global $wgRequest, $wgOut;
if ( $wgRequest->getBool( 'redlink' ) ) {
// The edit page was reached via a red link.
@@ -574,30 +512,37 @@ class EditPage {
}
/**
- * Does this EditPage class support section editing?
- * This is used by EditPage subclasses to indicate their ui cannot handle section edits
+ * Checks whether the user entered a skin name in uppercase,
+ * e.g. "User:Example/Monobook.css" instead of "monobook.css"
*
* @return bool
*/
- protected function isSectionEditSupported() {
- return true;
+ protected function isWrongCaseCssJsPage() {
+ if( $this->mTitle->isCssJsSubpage() ) {
+ $name = $this->mTitle->getSkinFromCssJsSubpage();
+ $skins = array_merge(
+ array_keys( Skin::getSkinNames() ),
+ array( 'common' )
+ );
+ return !in_array( $name, $skins )
+ && in_array( strtolower( $name ), $skins );
+ } else {
+ return false;
+ }
}
/**
- * Returns the URL to use in the form's action attribute.
- * This is used by EditPage subclasses when simply customizing the action
- * variable in the constructor is not enough. This can be used when the
- * EditPage lives inside of a Special page rather than a custom page action.
+ * Does this EditPage class support section editing?
+ * This is used by EditPage subclasses to indicate their ui cannot handle section edits
*
- * @param $title Title object for which is being edited (where we go to for &action= links)
- * @return string
+ * @return bool
*/
- protected function getActionURL( Title $title ) {
- return $title->getLocalURL( array( 'action' => $this->action ) );
+ protected function isSectionEditSupported() {
+ return true;
}
/**
- * @todo document
+ * This function collects the form data and uses it to populate various member variables.
* @param $request WebRequest
*/
function importFormData( &$request ) {
@@ -627,15 +572,25 @@ class EditPage {
# Truncate for whole multibyte characters. +5 bytes for ellipsis
$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);
+ # If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
+ # header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
+ # section titles.
+ $this->summary = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary );
+
+ # Treat sectiontitle the same way as summary.
+ # Note that wpSectionTitle is not yet a part of the actual edit form, as wpSummary is
+ # currently doing double duty as both edit summary and section title. Right now this
+ # is just to allow API edits to work around this limitation, but this should be
+ # incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
+ $this->sectiontitle = $wgLang->truncate( $request->getText( 'wpSectionTitle' ), 250 );
+ $this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
$this->edittime = $request->getVal( 'wpEdittime' );
$this->starttime = $request->getVal( 'wpStarttime' );
$this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
- if ($this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null) {
+ 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)
@@ -702,19 +657,24 @@ class EditPage {
} else {
# Not a posted form? Start with nothing.
wfDebug( __METHOD__ . ": Not a posted form.\n" );
- $this->textbox1 = '';
- $this->summary = '';
- $this->edittime = '';
- $this->starttime = wfTimestampNow();
- $this->edit = false;
- $this->preview = false;
- $this->save = false;
- $this->diff = false;
- $this->minoredit = false;
- $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overriden by request parameters
- $this->recreate = false;
-
+ $this->textbox1 = '';
+ $this->summary = '';
+ $this->sectiontitle = '';
+ $this->edittime = '';
+ $this->starttime = wfTimestampNow();
+ $this->edit = false;
+ $this->preview = false;
+ $this->save = false;
+ $this->diff = false;
+ $this->minoredit = false;
+ $this->watchthis = $request->getBool( 'watchthis', false ); // Watch may be overriden by request parameters
+ $this->recreate = false;
+
+ // When creating a new section, we can preload a section title by passing it as the
+ // preloadtitle parameter in the URL (Bug 13100)
if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
+ $this->sectiontitle = $request->getVal( 'preloadtitle' );
+ // Once wpSummary isn't being use for setting section titles, we should delete this.
$this->summary = $request->getVal( 'preloadtitle' );
}
elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
@@ -729,7 +689,6 @@ class EditPage {
$this->bot = $request->getBool( 'bot', true );
$this->nosummary = $request->getBool( 'nosummary' );
- // @todo FIXME: Unused variable?
$this->oldid = $request->getInt( 'oldid' );
$this->live = $request->getCheck( 'live' );
@@ -756,101 +715,324 @@ class EditPage {
}
/**
- * Make sure the form isn't faking a user's credentials.
+ * Initialise form fields in the object
+ * Called on the first invocation, e.g. when a user clicks an edit link
+ * @return bool -- if the requested section is valid
+ */
+ function initialiseForm() {
+ global $wgUser;
+ $this->edittime = $this->mArticle->getTimestamp();
+ $this->textbox1 = $this->getContent( false );
+ // activate checkboxes if user wants them to be always active
+ # Sort out the "watch" checkbox
+ if ( $wgUser->getOption( 'watchdefault' ) ) {
+ # Watch all edits
+ $this->watchthis = true;
+ } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
+ # Watch creations
+ $this->watchthis = true;
+ } elseif ( $this->mTitle->userIsWatching() ) {
+ # Already watched
+ $this->watchthis = true;
+ }
+ if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
+ $this->minoredit = true;
+ }
+ if ( $this->textbox1 === false ) {
+ return false;
+ }
+ wfProxyCheck();
+ return true;
+ }
+
+ /**
+ * Fetch initial editing page content.
*
- * @param $request WebRequest
- * @return bool
+ * @param $def_text string
+ * @return mixed string on success, $def_text for invalid sections
* @private
*/
- function tokenOk( &$request ) {
- global $wgUser;
- $token = $request->getVal( 'wpEditToken' );
- $this->mTokenOk = $wgUser->matchEditToken( $token );
- $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
- return $this->mTokenOk;
+ function getContent( $def_text = '' ) {
+ global $wgOut, $wgRequest, $wgParser;
+
+ wfProfileIn( __METHOD__ );
+
+ $text = false;
+
+ // For message page not locally set, use the i18n message.
+ // For other non-existent articles, use preload text if any.
+ if ( !$this->mTitle->exists() || $this->section == 'new' ) {
+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
+ # If this is a system message, get the default text.
+ $text = $this->mTitle->getDefaultMessageText();
+ }
+ if ( $text === false ) {
+ # If requested, preload some text.
+ $preload = $wgRequest->getVal( 'preload',
+ // Custom preload text for new sections
+ $this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
+ $text = $this->getPreloadedText( $preload );
+ }
+ // For existing pages, get text based on "undo" or section parameters.
+ } else {
+ if ( $this->section != '' ) {
+ // Get section edit text (returns $def_text for invalid sections)
+ $text = $wgParser->getSection( $this->getOriginalContent(), $this->section, $def_text );
+ } else {
+ $undoafter = $wgRequest->getInt( 'undoafter' );
+ $undo = $wgRequest->getInt( 'undo' );
+
+ if ( $undo > 0 && $undoafter > 0 ) {
+ if ( $undo < $undoafter ) {
+ # If they got undoafter and undo round the wrong way, switch them
+ list( $undo, $undoafter ) = array( $undoafter, $undo );
+ }
+
+ $undorev = Revision::newFromId( $undo );
+ $oldrev = Revision::newFromId( $undoafter );
+
+ # Sanity check, make sure it's the right page,
+ # the revisions exist and they were not deleted.
+ # Otherwise, $text will be left as-is.
+ if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
+ $undorev->getPage() == $oldrev->getPage() &&
+ $undorev->getPage() == $this->mTitle->getArticleId() &&
+ !$undorev->isDeleted( Revision::DELETED_TEXT ) &&
+ !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
+
+ $text = $this->mArticle->getUndoText( $undorev, $oldrev );
+ if ( $text === false ) {
+ # Warn the user that something went wrong
+ $undoMsg = 'failure';
+ } else {
+ # Inform the user of our success and set an automatic edit summary
+ $undoMsg = 'success';
+
+ # If we just undid one rev, use an autosummary
+ $firstrev = $oldrev->getNext();
+ if ( $firstrev->getId() == $undo ) {
+ $undoSummary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() );
+ if ( $this->summary === '' ) {
+ $this->summary = $undoSummary;
+ } else {
+ $this->summary = $undoSummary . wfMsgForContent( 'colon-separator' ) . $this->summary;
+ }
+ $this->undidRev = $undo;
+ }
+ $this->formtype = 'diff';
+ }
+ } else {
+ // Failed basic sanity checks.
+ // Older revisions may have been removed since the link
+ // was created, or we may simply have got bogus input.
+ $undoMsg = 'norev';
+ }
+
+ $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
+ $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
+ wfMsgNoTrans( 'undo-' . $undoMsg ) . '</div>', true, /* interface */true );
+ }
+
+ if ( $text === false ) {
+ $text = $this->getOriginalContent();
+ }
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $text;
}
/**
- * Show all applicable editing introductions
+ * Get the content of the wanted revision, without section extraction.
+ *
+ * The result of this function can be used to compare user's input with
+ * section replaced in its context (using WikiPage::replaceSection())
+ * to the original text of the edit.
+ *
+ * This difers from Article::getContent() that when a missing revision is
+ * encountered the result will be an empty string and not the
+ * 'missing-article' message.
+ *
+ * @since 1.19
+ * @return string
*/
- protected function showIntro() {
- global $wgOut, $wgUser;
- if ( $this->suppressIntro ) {
- return;
+ private function getOriginalContent() {
+ if ( $this->section == 'new' ) {
+ return $this->getCurrentText();
+ }
+ $revision = $this->mArticle->getRevisionFetched();
+ if ( $revision === null ) {
+ return '';
}
+ return $this->mArticle->getContent();
+ }
- $namespace = $this->mTitle->getNamespace();
+ /**
+ * Get the actual text of the page. This is basically similar to
+ * WikiPage::getRawText() except that when the page doesn't exist an empty
+ * string is returned instead of false.
+ *
+ * @since 1.19
+ * @return string
+ */
+ private function getCurrentText() {
+ $text = $this->mArticle->getRawText();
+ if ( $text === false ) {
+ return '';
+ } else {
+ return $text;
+ }
+ }
- if ( $namespace == NS_MEDIAWIKI ) {
- # Show a warning if editing an interface message
- $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
+ /**
+ * Use this method before edit() to preload some text into the edit box
+ *
+ * @param $text string
+ */
+ public function setPreloadedText( $text ) {
+ $this->mPreloadText = $text;
+ }
+
+ /**
+ * Get the contents to be preloaded into the box, either set by
+ * an earlier setPreloadText() or by loading the given page.
+ *
+ * @param $preload String: representing the title to preload from.
+ * @return String
+ */
+ protected function getPreloadedText( $preload ) {
+ global $wgUser, $wgParser;
+
+ if ( !empty( $this->mPreloadText ) ) {
+ return $this->mPreloadText;
+ }
+
+ if ( $preload === '' ) {
+ return '';
}
- # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
- # Show log extract when the user is currently blocked
- if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
- $parts = explode( '/', $this->mTitle->getText(), 2 );
- $username = $parts[0];
- $user = User::newFromName( $username, false /* allow IP users*/ );
- $ip = User::isIP( $username );
- if ( !$user->isLoggedIn() && !$ip ) { # User does not exist
- $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
- array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
- } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
- LogEventsList::showLogExtract(
- $wgOut,
- 'block',
- $user->getUserPage()->getPrefixedText(),
- '',
- array(
- 'lim' => 1,
- 'showIfEmpty' => false,
- 'msgKey' => array(
- 'blocked-notice-logextract',
- $user->getName() # Support GENDER in notice
- )
- )
- );
- }
+ $title = Title::newFromText( $preload );
+ # Check for existence to avoid getting MediaWiki:Noarticletext
+ if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
+ return '';
}
- # Try to add a custom edit intro, or use the standard one if this is not possible.
- if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
- if ( $wgUser->isLoggedIn() ) {
- $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletext\">\n$1\n</div>", 'newarticletext' );
- } else {
- $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletextanon\">\n$1\n</div>", 'newarticletextanon' );
+
+ $page = WikiPage::factory( $title );
+ if ( $page->isRedirect() ) {
+ $title = $page->getRedirectTarget();
+ # Same as before
+ if ( $title === null || !$title->exists() || !$title->userCan( 'read' ) ) {
+ return '';
}
+ $page = WikiPage::factory( $title );
}
- # Give a notice if the user is editing a deleted/moved page...
- if ( !$this->mTitle->exists() ) {
- LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle->getPrefixedText(),
- '', array( 'lim' => 10,
- 'conds' => array( "log_action != 'revision'" ),
- 'showIfEmpty' => false,
- 'msgKey' => array( 'recreate-moveddeleted-warn') )
- );
- }
+
+ $parserOptions = ParserOptions::newFromUser( $wgUser );
+ return $wgParser->getPreloadText( $page->getRawText(), $title, $parserOptions );
}
/**
- * Attempt to show a custom editing introduction, if supplied
+ * Make sure the form isn't faking a user's credentials.
*
+ * @param $request WebRequest
* @return bool
+ * @private
*/
- protected function showCustomIntro() {
- if ( $this->editintro ) {
- $title = Title::newFromText( $this->editintro );
- if ( $title instanceof Title && $title->exists() && $title->userCanRead() ) {
- global $wgOut;
- // Added using template syntax, to take <noinclude>'s into account.
- $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
+ function tokenOk( &$request ) {
+ global $wgUser;
+ $token = $request->getVal( 'wpEditToken' );
+ $this->mTokenOk = $wgUser->matchEditToken( $token );
+ $this->mTokenOkExceptSuffix = $wgUser->matchEditTokenNoSuffix( $token );
+ return $this->mTokenOk;
+ }
+
+ /**
+ * Attempt submission
+ * @return bool false if output is done, true if the rest of the form should be displayed
+ */
+ function attemptSave() {
+ global $wgUser, $wgOut;
+
+ $resultDetails = false;
+ # Allow bots to exempt some edits from bot flagging
+ $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
+ $status = $this->internalAttemptSave( $resultDetails, $bot );
+ // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
+
+ if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
+ $this->didSave = true;
+ }
+
+ switch ( $status->value ) {
+ case self::AS_HOOK_ERROR_EXPECTED:
+ case self::AS_CONTENT_TOO_BIG:
+ case self::AS_ARTICLE_WAS_DELETED:
+ case self::AS_CONFLICT_DETECTED:
+ case self::AS_SUMMARY_NEEDED:
+ case self::AS_TEXTBOX_EMPTY:
+ case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
+ case self::AS_END:
return true;
- } else {
+
+ case self::AS_HOOK_ERROR:
+ case self::AS_FILTERING:
return false;
- }
- } else {
- return false;
+
+ case self::AS_SUCCESS_NEW_ARTICLE:
+ $query = $resultDetails['redirect'] ? 'redirect=no' : '';
+ $anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
+ $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $anchor );
+ 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_BLANK_ARTICLE:
+ $wgOut->redirect( $this->getContextTitle()->getFullURL() );
+ return false;
+
+ case self::AS_SPAM_ERROR:
+ $this->spamPageWithContent( $resultDetails['spam'] );
+ return false;
+
+ case self::AS_BLOCKED_PAGE_FOR_USER:
+ throw new UserBlockedError( $wgUser->mBlock );
+
+ case self::AS_IMAGE_REDIRECT_ANON:
+ case self::AS_IMAGE_REDIRECT_LOGGED:
+ throw new PermissionsError( 'upload' );
+
+ case self::AS_READ_ONLY_PAGE_ANON:
+ case self::AS_READ_ONLY_PAGE_LOGGED:
+ throw new PermissionsError( 'edit' );
+
+ case self::AS_READ_ONLY_PAGE:
+ throw new ReadOnlyError;
+
+ case self::AS_RATE_LIMITED:
+ throw new ThrottledError();
+
+ case self::AS_NO_CREATE_PERMISSION:
+ $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
+ throw new PermissionsError( $permission );
+
}
+ return false;
}
/**
@@ -866,9 +1048,9 @@ class EditPage {
* 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 $wgFilterCallback, $wgUser, $wgRequest, $wgParser;
global $wgMaxArticleSize;
-
+
$status = Status::newGood();
wfProfileIn( __METHOD__ );
@@ -891,7 +1073,6 @@ class EditPage {
$status->setResult( false, $code );
wfProfileOut( __METHOD__ . '-checks' );
-
wfProfileOut( __METHOD__ );
return $status;
@@ -904,7 +1085,7 @@ class EditPage {
}
if ( $match !== false ) {
$result['spam'] = $match;
- $ip = wfGetIP();
+ $ip = $wgRequest->getIP();
$pdbk = $this->mTitle->getPrefixedDBkey();
$match = str_replace( "\n", '', $match );
wfDebugLog( 'SpamRegex', "$ip spam regex hit [[$pdbk]]: \"$match\"" );
@@ -914,7 +1095,7 @@ class EditPage {
wfProfileOut( __METHOD__ );
return $status;
}
- if ( $wgFilterCallback && $wgFilterCallback( $this->mTitle, $this->textbox1, $this->section, $this->hookError, $this->summary ) ) {
+ if ( $wgFilterCallback && is_callable( $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' );
@@ -936,6 +1117,7 @@ class EditPage {
wfProfileOut( __METHOD__ );
return $status;
}
+
if ( $wgUser->isBlockedFrom( $this->mTitle, false ) ) {
// Auto-block user's IP if the account was "hard" blocked
$wgUser->spreadAnyEditBlock();
@@ -945,6 +1127,7 @@ class EditPage {
wfProfileOut( __METHOD__ );
return $status;
}
+
$this->kblength = (int)( strlen( $this->textbox1 ) / 1024 );
if ( $this->kblength > $wgMaxArticleSize ) {
// Error will be displayed by showEditForm()
@@ -1044,8 +1227,33 @@ class EditPage {
}
$text = $this->textbox1;
- if ( $this->section == 'new' && $this->summary != '' ) {
- $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
+ $result['sectionanchor'] = '';
+ if ( $this->section == 'new' ) {
+ if ( $this->sectiontitle !== '' ) {
+ // Insert the section title above the content.
+ $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->sectiontitle ) . "\n\n" . $text;
+
+ // Jump to the new section
+ $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
+
+ // If no edit summary was specified, create one automatically from the section
+ // title and have it link to the new section. Otherwise, respect the summary as
+ // passed.
+ if ( $this->summary === '' ) {
+ $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
+ }
+ } elseif ( $this->summary !== '' ) {
+ // Insert the section title above the content.
+ $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
+
+ // Jump to the new section
+ $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
+
+ // Create a link to the new section from the edit summary.
+ $cleanSummary = $wgParser->stripSectionName( $this->summary );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
+ }
}
$status->value = self::AS_SUCCESS_NEW_ARTICLE;
@@ -1055,10 +1263,11 @@ class EditPage {
# Article exists. Check for edit conflict.
$this->mArticle->clear(); # Force reload of dates, etc.
+ $timestamp = $this->mArticle->getTimestamp();
- wfDebug( "timestamp: {$this->mArticle->getTimestamp()}, edittime: {$this->edittime}\n" );
+ wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
- if ( $this->mArticle->getTimestamp() != $this->edittime ) {
+ if ( $timestamp != $this->edittime ) {
$this->isConflict = true;
if ( $this->section == 'new' ) {
if ( $this->mArticle->getUserText() == $wgUser->getName() &&
@@ -1072,23 +1281,27 @@ class EditPage {
$this->isConflict = false;
wfDebug( __METHOD__ .": conflict suppressed; new section\n" );
}
+ } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) {
+ # Suppress edit conflict with self, except for section edits where merging is required.
+ wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
+ $this->isConflict = false;
}
}
- $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;
+
+ // If sectiontitle is set, use it, otherwise use the summary as the section title (for
+ // backwards compatibility with old forms/bots).
+ if ( $this->sectiontitle !== '' ) {
+ $sectionTitle = $this->sectiontitle;
+ } else {
+ $sectionTitle = $this->summary;
}
-
+
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 );
+ wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
+ $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
} else {
wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary );
+ $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
}
if ( is_null( $text ) ) {
wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
@@ -1113,8 +1326,6 @@ class EditPage {
return $status;
}
- $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...
@@ -1131,7 +1342,8 @@ class EditPage {
}
# Handle the user preference to force summaries here, but not for null edits
- if ( $this->section != 'new' && !$this->allowBlankSummary && 0 != strcmp( $oldtext, $text )
+ if ( $this->section != 'new' && !$this->allowBlankSummary
+ && $this->getOriginalContent() != $text
&& !Title::newFromRedirect( $text ) ) # check if it's not a redirect
{
if ( md5( $this->summary ) == $this->autoSumm ) {
@@ -1166,7 +1378,16 @@ class EditPage {
wfProfileOut( __METHOD__ );
return $status;
}
- if ( $this->summary != '' ) {
+ if ( $this->sectiontitle !== '' ) {
+ $sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
+ // If no edit summary was specified, create one automatically from the section
+ // title and have it link to the new section. Otherwise, respect the summary as
+ // passed.
+ if ( $this->summary === '' ) {
+ $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
+ }
+ } elseif ( $this->summary !== '' ) {
$sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
# This is a new section, so create a link to the new section
# in the revision summary.
@@ -1259,7 +1480,7 @@ class EditPage {
$res = $dbw->select( 'revision',
'rev_user',
array(
- 'rev_page' => $this->mArticle->getId(),
+ 'rev_page' => $this->mTitle->getArticleId(),
'rev_timestamp > '.$dbw->addQuotes( $dbw->timestamp($edittime) )
),
__METHOD__,
@@ -1273,6 +1494,60 @@ class EditPage {
}
/**
+ * @private
+ * @todo document
+ *
+ * @parma $editText string
+ *
+ * @return bool
+ */
+ function mergeChangesInto( &$editText ){
+ wfProfileIn( __METHOD__ );
+
+ $db = wfGetDB( DB_MASTER );
+
+ // This is the revision the editor started from
+ $baseRevision = $this->getBaseRevision();
+ if ( is_null( $baseRevision ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $baseText = $baseRevision->getText();
+
+ // The current state, we want to merge updates into it
+ $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
+ if ( is_null( $currentRevision ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ $currentText = $currentRevision->getText();
+
+ $result = '';
+ if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
+ $editText = $result;
+ wfProfileOut( __METHOD__ );
+ return true;
+ } else {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ }
+
+ /**
+ * @return Revision
+ */
+ function getBaseRevision() {
+ if ( !$this->mBaseRevision ) {
+ $db = wfGetDB( DB_MASTER );
+ $baseRevision = Revision::loadFromTimestamp(
+ $db, $this->mTitle, $this->edittime );
+ return $this->mBaseRevision = $baseRevision;
+ } else {
+ return $this->mBaseRevision;
+ }
+ }
+
+ /**
* Check given input text against $wgSpamRegex, and return the text of the first match.
*
* @param $text string
@@ -1314,48 +1589,27 @@ class EditPage {
return false;
}
- /**
- * Initialise form fields in the object
- * Called on the first invocation, e.g. when a user clicks an edit link
- * @return bool -- if the requested section is valid
- */
- function initialiseForm() {
- global $wgUser;
- $this->edittime = $this->mArticle->getTimestamp();
- $this->textbox1 = $this->getContent( false );
- // activate checkboxes if user wants them to be always active
- # Sort out the "watch" checkbox
- if ( $wgUser->getOption( 'watchdefault' ) ) {
- # Watch all edits
- $this->watchthis = true;
- } elseif ( $wgUser->getOption( 'watchcreations' ) && !$this->mTitle->exists() ) {
- # Watch creations
- $this->watchthis = true;
- } elseif ( $this->mTitle->userIsWatching() ) {
- # Already watched
- $this->watchthis = true;
- }
- if ( $wgUser->getOption( 'minordefault' ) && !$this->isNew ) {
- $this->minoredit = true;
- }
- if ( $this->textbox1 === false ) {
- return false;
+ function setHeaders() {
+ global $wgOut, $wgUser;
+
+ $wgOut->addModules( 'mediawiki.action.edit' );
+
+ if ( $wgUser->getOption( 'uselivepreview', false ) ) {
+ $wgOut->addModules( 'mediawiki.legacy.preview' );
}
- wfProxyCheck();
- return true;
- }
+ // Bug #19334: textarea jumps when editing articles in IE8
+ $wgOut->addStyle( 'common/IE80Fixes.css', 'screen', 'IE 8' );
- function setHeaders() {
- global $wgOut;
$wgOut->setRobotPolicy( 'noindex,nofollow' );
- if ( $this->formtype == 'preview' ) {
- $wgOut->setPageTitleActionText( wfMsg( 'preview' ) );
- }
+
+ # Enabled article-related sidebar, toplinks, etc.
+ $wgOut->setArticleRelated( true );
+
if ( $this->isConflict ) {
- $wgOut->setPageTitle( wfMsg( 'editconflict', $this->getContextTitle()->getPrefixedText() ) );
+ $wgOut->setPageTitle( wfMessage( 'editconflict', $this->getContextTitle()->getPrefixedText() ) );
} elseif ( $this->section != '' ) {
$msg = $this->section == 'new' ? 'editingcomment' : 'editingsection';
- $wgOut->setPageTitle( wfMsg( $msg, $this->getContextTitle()->getPrefixedText() ) );
+ $wgOut->setPageTitle( wfMessage( $msg, $this->getContextTitle()->getPrefixedText() ) );
} else {
# Use the title defined by DISPLAYTITLE magic word when present
if ( isset( $this->mParserOutput )
@@ -1364,7 +1618,90 @@ class EditPage {
} else {
$title = $this->getContextTitle()->getPrefixedText();
}
- $wgOut->setPageTitle( wfMsg( 'editing', $title ) );
+ $wgOut->setPageTitle( wfMessage( 'editing', $title ) );
+ }
+ }
+
+ /**
+ * Show all applicable editing introductions
+ */
+ protected function showIntro() {
+ global $wgOut, $wgUser;
+ if ( $this->suppressIntro ) {
+ return;
+ }
+
+ $namespace = $this->mTitle->getNamespace();
+
+ if ( $namespace == NS_MEDIAWIKI ) {
+ # Show a warning if editing an interface message
+ $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1\n</div>", 'editinginterface' );
+ }
+
+ # Show a warning message when someone creates/edits a user (talk) page but the user does not exist
+ # Show log extract when the user is currently blocked
+ if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
+ $parts = explode( '/', $this->mTitle->getText(), 2 );
+ $username = $parts[0];
+ $user = User::newFromName( $username, false /* allow IP users*/ );
+ $ip = User::isIP( $username );
+ if ( !($user && $user->isLoggedIn()) && !$ip ) { # User does not exist
+ $wgOut->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n$1\n</div>",
+ array( 'userpage-userdoesnotexist', wfEscapeWikiText( $username ) ) );
+ } elseif ( $user->isBlocked() ) { # Show log extract if the user is currently blocked
+ LogEventsList::showLogExtract(
+ $wgOut,
+ 'block',
+ $user->getUserPage(),
+ '',
+ array(
+ 'lim' => 1,
+ 'showIfEmpty' => false,
+ 'msgKey' => array(
+ 'blocked-notice-logextract',
+ $user->getName() # Support GENDER in notice
+ )
+ )
+ );
+ }
+ }
+ # Try to add a custom edit intro, or use the standard one if this is not possible.
+ if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
+ if ( $wgUser->isLoggedIn() ) {
+ $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletext\">\n$1\n</div>", 'newarticletext' );
+ } else {
+ $wgOut->wrapWikiMsg( "<div class=\"mw-newarticletextanon\">\n$1\n</div>", 'newarticletextanon' );
+ }
+ }
+ # Give a notice if the user is editing a deleted/moved page...
+ if ( !$this->mTitle->exists() ) {
+ LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
+ '', array( 'lim' => 10,
+ 'conds' => array( "log_action != 'revision'" ),
+ 'showIfEmpty' => false,
+ 'msgKey' => array( 'recreate-moveddeleted-warn') )
+ );
+ }
+ }
+
+ /**
+ * Attempt to show a custom editing introduction, if supplied
+ *
+ * @return bool
+ */
+ protected function showCustomIntro() {
+ if ( $this->editintro ) {
+ $title = Title::newFromText( $this->editintro );
+ if ( $title instanceof Title && $title->exists() && $title->userCan( 'read' ) ) {
+ global $wgOut;
+ // Added using template syntax, to take <noinclude>'s into account.
+ $wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
}
}
@@ -1391,24 +1728,11 @@ class EditPage {
$this->setHeaders();
- # Enabled article-related sidebar, toplinks, etc.
- $wgOut->setArticleRelated( true );
-
if ( $this->showHeader() === false ) {
wfProfileOut( __METHOD__ );
return;
}
- $action = htmlspecialchars( $this->getActionURL( $this->getContextTitle() ) );
-
- if ( $wgUser->getOption( 'showtoolbar' ) and !$this->isCssJsSubpage ) {
- # prepare toolbar for edit buttons
- $toolbar = EditPage::getEditToolbar();
- } else {
- $toolbar = '';
- }
-
-
$wgOut->addHTML( $this->editFormPageTop );
if ( $wgUser->getOption( 'previewontop' ) ) {
@@ -1417,26 +1741,21 @@ class EditPage {
$wgOut->addHTML( $this->editFormTextTop );
- $templates = $this->getTemplates();
- $formattedtemplates = Linker::formatTemplates( $templates, $this->preview, $this->section != '');
-
- $hiddencats = $this->mArticle->getHiddenCategories();
- $formattedhiddencats = Linker::formatHiddenCategories( $hiddencats );
-
- if ( $this->wasDeletedSinceLastEdit() && 'save' != $this->formtype ) {
- $wgOut->wrapWikiMsg(
- "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
- 'deletedwhileediting' );
- } elseif ( $this->wasDeletedSinceLastEdit() ) {
- // Hide the toolbar and edit area, user can click preview to get it back
- // Add an confirmation checkbox and explanation.
- $toolbar = '';
- // @todo move this to a cleaner conditional instead of blanking a variable
+ $showToolbar = true;
+ if ( $this->wasDeletedSinceLastEdit() ) {
+ if ( $this->formtype == 'save' ) {
+ // Hide the toolbar and edit area, user can click preview to get it back
+ // Add an confirmation checkbox and explanation.
+ $showToolbar = false;
+ } else {
+ $wgOut->wrapWikiMsg( "<div class='error mw-deleted-while-editing'>\n$1\n</div>",
+ 'deletedwhileediting' );
+ }
}
- $wgOut->addHTML( <<<HTML
-<form id="editform" name="editform" method="post" action="$action" enctype="multipart/form-data">
-HTML
-);
+
+ $wgOut->addHTML( Html::openElement( 'form', array( 'id' => 'editform', 'name' => 'editform',
+ 'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
+ 'enctype' => 'multipart/form-data' ) ) );
if ( is_callable( $formCallback ) ) {
call_user_func_array( $formCallback, array( &$wgOut ) );
@@ -1473,13 +1792,14 @@ HTML
#####
# For a bit more sophisticated detection of blank summaries, hash the
# automatic one and pass that in the hidden field wpAutoSummary.
- if ( $this->missingSummary ||
- ( $this->section == 'new' && $this->nosummary ) )
- $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
+ if ( $this->missingSummary || ( $this->section == 'new' && $this->nosummary ) ) {
+ $wgOut->addHTML( Html::hidden( 'wpIgnoreBlankSummary', true ) );
+ }
+
$autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
$wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
- $wgOut->addHTML( Html::hidden( 'oldid', $this->mArticle->getOldID() ) );
+ $wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
if ( $this->section == 'new' ) {
$this->showSummaryInput( true, $this->summary );
@@ -1488,14 +1808,19 @@ HTML
$wgOut->addHTML( $this->editFormTextBeforeContent );
- $wgOut->addHTML( $toolbar );
+ if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
+ $wgOut->addHTML( EditPage::getEditToolbar() );
+ }
if ( $this->isConflict ) {
// In an edit conflict bypass the overrideable content form method
// and fallback to the raw wpTextbox1 since editconflicts can't be
// resolved between page source edits and custom ui edits using the
// custom edit ui.
- $this->showTextbox1( null, $this->getContent() );
+ $this->textbox2 = $this->textbox1;
+ $this->textbox1 = $this->getCurrentText();
+
+ $this->showTextbox1();
} else {
$this->showContentForm();
}
@@ -1503,32 +1828,31 @@ HTML
$wgOut->addHTML( $this->editFormTextAfterContent );
$wgOut->addWikiText( $this->getCopywarn() );
- if ( isset($this->editFormTextAfterWarn) && $this->editFormTextAfterWarn !== '' )
- $wgOut->addHTML( $this->editFormTextAfterWarn );
+
+ $wgOut->addHTML( $this->editFormTextAfterWarn );
$this->showStandardInputs();
$this->showFormAfterText();
$this->showTosSummary();
+
$this->showEditTools();
- $wgOut->addHTML( <<<HTML
-{$this->editFormTextAfterTools}
-<div class='templatesUsed'>
-{$formattedtemplates}
-</div>
-<div class='hiddencats'>
-{$formattedhiddencats}
-</div>
-HTML
-);
+ $wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
+
+ $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
+ Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
+
+ $wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'hiddencats' ),
+ Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
- if ( $this->isConflict )
+ if ( $this->isConflict ) {
$this->showConflict();
+ }
+
+ $wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
- $wgOut->addHTML( $this->editFormTextBottom );
- $wgOut->addHTML( "</form>\n" );
if ( !$wgUser->getOption( 'previewontop' ) ) {
$this->displayPreviewArea( $previewOutput, false );
}
@@ -1536,8 +1860,54 @@ HTML
wfProfileOut( __METHOD__ );
}
+ /**
+ * Extract the section title from current section text, if any.
+ *
+ * @param string $text
+ * @return Mixed|string or false
+ */
+ public static function extractSectionTitle( $text ) {
+ preg_match( "/^(=+)(.+)\\1\\s*(\n|$)/i", $text, $matches );
+ if ( !empty( $matches[2] ) ) {
+ global $wgParser;
+ return $wgParser->stripSectionName(trim($matches[2]));
+ } else {
+ return false;
+ }
+ }
+
protected function showHeader() {
global $wgOut, $wgUser, $wgMaxArticleSize, $wgLang;
+
+ if ( $this->mTitle->isTalkPage() ) {
+ $wgOut->addWikiMsg( 'talkpagetext' );
+ }
+
+ # Optional notices on a per-namespace and per-page basis
+ $editnotice_ns = 'editnotice-'.$this->mTitle->getNamespace();
+ $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 );
+ $editnotice_base_msg = wfMessage( $editnotice_base )->inContentLanguage();
+ if ( $editnotice_base_msg->exists() ) {
+ $wgOut->addWikiText( $editnotice_base_msg->plain() );
+ }
+ }
+ } else {
+ # Even if there are no subpages in namespace, we still don't want / in MW ns.
+ $editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->mTitle->getDBkey() );
+ $editnoticeMsg = wfMessage( $editnoticeText )->inContentLanguage();
+ if ( $editnoticeMsg->exists() ) {
+ $wgOut->addWikiText( $editnoticeMsg->plain() );
+ }
+ }
+
if ( $this->isConflict ) {
$wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1\n</div>", 'explainconflict' );
$this->edittime = $this->mArticle->getTimestamp();
@@ -1551,14 +1921,10 @@ HTML
}
if ( $this->section != '' && $this->section != 'new' ) {
- $matches = array();
if ( !$this->summary && !$this->preview && !$this->diff ) {
- preg_match( "/^(=+)(.+)\\1/mi", $this->textbox1, $matches );
- if ( !empty( $matches[2] ) ) {
- global $wgParser;
- $this->summary = "/* " .
- $wgParser->stripSectionName(trim($matches[2])) .
- " */ ";
+ $sectionTitle = self::extractSectionTitle( $this->textbox1 );
+ if ( $sectionTitle !== false ) {
+ $this->summary = "/* $sectionTitle */ ";
}
}
}
@@ -1583,18 +1949,27 @@ HTML
$wgOut->addWikiMsg( 'nonunicodebrowser' );
}
- if ( isset( $this->mArticle ) && isset( $this->mArticle->mRevision ) ) {
- // Let sysop know that this will make private content public if saved
+ if ( $this->section != 'new' ) {
+ $revision = $this->mArticle->getRevisionFetched();
+ if ( $revision ) {
+ // Let sysop know that this will make private content public if saved
- if ( !$this->mArticle->mRevision->userCan( Revision::DELETED_TEXT ) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
- } elseif ( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
- $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
- }
+ if ( !$revision->userCan( Revision::DELETED_TEXT ) ) {
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
+ } elseif ( $revision->isDeleted( Revision::DELETED_TEXT ) ) {
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
+ }
+
+ if ( !$revision->isCurrent() ) {
+ $this->mArticle->setOldSubtitle( $revision->getId() );
+ $wgOut->addWikiMsg( 'editingold' );
+ }
+ } elseif ( $this->mTitle->exists() ) {
+ // Something went wrong
- if ( !$this->mArticle->mRevision->isCurrent() ) {
- $this->mArticle->setOldSubtitle( $this->mArticle->mRevision->getId() );
- $wgOut->addWikiMsg( 'editingold' );
+ $wgOut->wrapWikiMsg( "<div class='errorbox'>\n$1\n</div>\n",
+ array( 'missing-article', $this->mTitle->getPrefixedText(),
+ wfMsgNoTrans( 'missingarticle-rev', $this->oldid ) ) );
}
}
}
@@ -1611,7 +1986,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', $this->getContextTitle()->getSkinFromCssJsSubpage() ) );
+ $wgOut->wrapWikiMsg( "<div class='error' id='mw-userinvalidcssjstitle'>\n$1\n</div>", array( 'userinvalidcssjstitle', $this->mTitle->getSkinFromCssJsSubpage() ) );
}
if ( $this->formtype !== 'preview' ) {
if ( $this->isCssSubpage )
@@ -1630,7 +2005,7 @@ HTML
# Then it must be protected based on static groups (regular)
$noticeMsg = 'protectedpagewarning';
}
- LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle->getPrefixedText(), '',
+ LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
array( 'lim' => 1, 'msgKey' => array( $noticeMsg ) ) );
}
if ( $this->mTitle->isCascadeProtected() ) {
@@ -1648,7 +2023,7 @@ HTML
$wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
}
if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
- LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle->getPrefixedText(), '',
+ LogEventsList::showLogExtract( $wgOut, 'protect', $this->mTitle, '',
array( 'lim' => 1,
'showIfEmpty' => false,
'msgKey' => array( 'titleprotectedwarning' ),
@@ -1788,7 +2163,7 @@ HTML
* include the constant suffix to prevent editing from
* broken text-mangling proxies.
*/
- $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->editToken() ) . "\n" );
+ $wgOut->addHTML( "\n" . Html::hidden( "wpEditToken", $wgUser->getEditToken() ) . "\n" );
}
/**
@@ -1811,34 +2186,40 @@ HTML
* @param $customAttribs An array of html attributes to use in the textarea
* @param $textoverride String: optional text to override $this->textarea1 with
*/
- protected function showTextbox1($customAttribs = null, $textoverride = null) {
- $classes = array(); // Textarea CSS
- if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
- # Is the title semi-protected?
- if ( $this->mTitle->isSemiProtected() ) {
- $classes[] = 'mw-textarea-sprotected';
- } else {
- # Then it must be protected based on static groups (regular)
- $classes[] = 'mw-textarea-protected';
+ protected function showTextbox1( $customAttribs = null, $textoverride = null ) {
+ if ( $this->wasDeletedSinceLastEdit() && $this->formtype == 'save' ) {
+ $attribs = array( 'style' => 'display:none;' );
+ } else {
+ $classes = array(); // Textarea CSS
+ if ( $this->mTitle->getNamespace() != NS_MEDIAWIKI && $this->mTitle->isProtected( 'edit' ) ) {
+ # Is the title semi-protected?
+ if ( $this->mTitle->isSemiProtected() ) {
+ $classes[] = 'mw-textarea-sprotected';
+ } else {
+ # 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';
+ }
}
- # Is the title cascade-protected?
- if ( $this->mTitle->isCascadeProtected() ) {
- $classes[] = 'mw-textarea-cprotected';
+
+ $attribs = array( 'tabindex' => 1 );
+
+ if ( is_array( $customAttribs ) ) {
+ $attribs += $customAttribs;
}
- }
- $attribs = array( 'tabindex' => 1 );
- if ( is_array($customAttribs) )
- $attribs += $customAttribs;
- if ( $this->wasDeletedSinceLastEdit() )
- $attribs['type'] = 'hidden';
- if ( !empty( $classes ) ) {
- if ( isset($attribs['class']) )
- $classes[] = $attribs['class'];
- $attribs['class'] = implode( ' ', $classes );
+ if ( count( $classes ) ) {
+ if ( isset( $attribs['class'] ) ) {
+ $classes[] = $attribs['class'];
+ }
+ $attribs['class'] = implode( ' ', $classes );
+ }
}
- $this->showTextbox( isset($textoverride) ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
+ $this->showTextbox( $textoverride !== null ? $textoverride : $this->textbox1, 'wpTextbox1', $attribs );
}
protected function showTextbox2() {
@@ -1849,7 +2230,7 @@ HTML
global $wgOut, $wgUser;
$wikitext = $this->safeUnicodeOutput( $content );
- if ( $wikitext !== '' ) {
+ if ( strval($wikitext) !== '' ) {
// Ensure there's a newline at the end, otherwise adding lines
// is awkward.
// But don't add a newline if the ext is empty, or Firefox in XHTML
@@ -1917,6 +2298,40 @@ HTML
}
/**
+ * Get a diff between the current contents of the edit box and the
+ * version of the page we're editing from.
+ *
+ * If this is a section edit, we'll replace the section as for final
+ * save and then make a comparison.
+ */
+ function showDiff() {
+ global $wgUser, $wgContLang, $wgParser, $wgOut;
+
+ $oldtext = $this->mArticle->getRawText();
+ $newtext = $this->mArticle->replaceSection(
+ $this->section, $this->textbox1, $this->summary, $this->edittime );
+
+ wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
+
+ $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
+ $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
+
+ if ( $oldtext !== false || $newtext != '' ) {
+ $oldtitle = wfMsgExt( 'currentrev', array( 'parseinline' ) );
+ $newtitle = wfMsgExt( 'yourtext', array( 'parseinline' ) );
+
+ $de = new DifferenceEngine( $this->mArticle->getContext() );
+ $de->setText( $oldtext, $newtext );
+ $difftext = $de->getDiff( $oldtitle, $newtitle );
+ $de->showDiffStyle();
+ } else {
+ $difftext = '';
+ }
+
+ $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
+ }
+
+ /**
* Give a chance for site and per-namespace customizations of
* terms of service summary link that might exist separately
* from the copyright notice.
@@ -1992,20 +2407,75 @@ HTML
*/
protected function showConflict() {
global $wgOut;
- $this->textbox2 = $this->textbox1;
- $this->textbox1 = $this->getContent();
+
if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
- $de = new DifferenceEngine( $this->mTitle );
+ $de = new DifferenceEngine( $this->mArticle->getContext() );
$de->setText( $this->textbox2, $this->textbox1 );
- $de->showDiff( wfMsg( "yourtext" ), wfMsg( "storedversion" ) );
+ $de->showDiff( wfMsgExt( 'yourtext', 'parseinline' ), wfMsg( 'storedversion' ) );
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
$this->showTextbox2();
}
}
+ /**
+ * @return string
+ */
+ public function getCancelLink() {
+ $cancelParams = array();
+ if ( !$this->isConflict && $this->oldid > 0 ) {
+ $cancelParams['oldid'] = $this->oldid;
+ }
+
+ return Linker::linkKnown(
+ $this->getContextTitle(),
+ wfMsgExt( 'cancel', array( 'parseinline' ) ),
+ array( 'id' => 'mw-editform-cancel' ),
+ $cancelParams
+ );
+ }
+
+ /**
+ * Returns the URL to use in the form's action attribute.
+ * This is used by EditPage subclasses when simply customizing the action
+ * variable in the constructor is not enough. This can be used when the
+ * EditPage lives inside of a Special page rather than a custom page action.
+ *
+ * @param $title Title object for which is being edited (where we go to for &action= links)
+ * @return string
+ */
+ protected function getActionURL( Title $title ) {
+ return $title->getLocalURL( array( 'action' => $this->action ) );
+ }
+
+ /**
+ * 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 !== null ) {
+ return $this->deletedSinceEdit;
+ }
+
+ $this->deletedSinceEdit = false;
+
+ if ( $this->mTitle->isDeletedQuick() ) {
+ $this->lastDelete = $this->getLastDelete();
+ if ( $this->lastDelete ) {
+ $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
+ if ( $deleteTime > $this->starttime ) {
+ $this->deletedSinceEdit = true;
+ }
+ }
+ }
+
+ return $this->deletedSinceEdit;
+ }
+
protected function getLastDelete() {
$dbr = wfGetDB( DB_SLAVE );
$data = $dbr->selectRow(
@@ -2043,10 +2513,25 @@ HTML
* @return string
*/
function getPreviewText() {
- global $wgOut, $wgUser, $wgParser;
+ global $wgOut, $wgUser, $wgParser, $wgRawHtml;
wfProfileIn( __METHOD__ );
+ if ( $wgRawHtml && !$this->mTokenOk ) {
+ // Could be an offsite preview attempt. This is very unsafe if
+ // HTML is enabled, as it could be an attack.
+ $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>", true, /* interface */true );
+ }
+ wfProfileOut( __METHOD__ );
+ return $parsedNote;
+ }
+
if ( $this->mTriedSave && !$this->mTokenOk ) {
if ( $this->mTokenOkExceptSuffix ) {
$note = wfMsg( 'token_suffix_mismatch' );
@@ -2061,47 +2546,36 @@ HTML
$parserOptions = ParserOptions::newFromUser( $wgUser );
$parserOptions->setEditSection( false );
+ $parserOptions->setTidy( true );
$parserOptions->setIsPreview( true );
$parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
- global $wgRawHtml;
- 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 = '';
- 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
+ # don't parse non-wikitext pages, show message about preview
# 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';
- if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ if ( $this->isCssJsSubpage || !$this->mTitle->isWikitextPage() ) {
+ if( $this->mTitle->isCssJsSubpage() ) {
+ $level = 'user';
+ } elseif( $this->mTitle->isCssOrJsPage() ) {
$level = 'site';
+ } else {
+ $level = false;
}
# Used messages to make sure grep find them:
# Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
- if (preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>";
- $class = "mw-code mw-css";
- } elseif (preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMsg( "{$level}jspreview" ) . "\n</div>";
- $class = "mw-code mw-js";
- } else {
- throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+ if( $level ) {
+ if (preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
+ $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMsg( "{$level}csspreview" ) . "\n</div>";
+ $class = "mw-code mw-css";
+ } elseif (preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
+ $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMsg( "{$level}jspreview" ) . "\n</div>";
+ $class = "mw-code mw-js";
+ } else {
+ throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+ }
}
- $parserOptions->setTidy( true );
$parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
$previewHTML = $parserOutput->mText;
$previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
@@ -2115,15 +2589,15 @@ HTML
# If we're adding a comment, we need to show the
# summary as the headline
if ( $this->section == "new" && $this->summary != "" ) {
- $toparse = "== {$this->summary} ==\n\n" . $toparse;
+ $toparse = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $toparse;
}
wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
- $parserOptions->setTidy( true );
$parserOptions->enableLimitReport();
- $parserOutput = $wgParser->parse( $this->mArticle->preSaveTransform( $toparse ),
- $this->mTitle, $parserOptions );
+
+ $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
+ $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
$previewHTML = $parserOutput->getText();
$this->mParserOutput = $parserOutput;
@@ -2143,7 +2617,7 @@ HTML
$previewhead = "<div class='previewnote'>\n" .
'<h2 id="mw-previewheader">' . htmlspecialchars( wfMsg( 'preview' ) ) . "</h2>" .
- $wgOut->parse( $note ) . $conflict . "</div>\n";
+ $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
$pageLang = $this->mTitle->getPageLanguage();
$attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
@@ -2170,203 +2644,11 @@ HTML
}
return $templates;
} else {
- return $this->mArticle->getUsedTemplates();
+ return $this->mTitle->getTemplateLinksFrom();
}
}
/**
- * Call the stock "user is blocked" page
- */
- function blockedPage() {
- global $wgOut;
- $wgOut->blockedPage( false ); # Standard block notice on the top, don't 'return'
-
- # If the user made changes, preserve them when showing the markup
- # (This happens when a user is blocked during edit, for instance)
- $first = $this->firsttime || ( !$this->save && $this->textbox1 == '' );
- if ( $first ) {
- $source = $this->mTitle->exists() ? $this->getContent() : false;
- } else {
- $source = $this->textbox1;
- }
-
- # Spit out the source or the user's modified version
- if ( $source !== false ) {
- $wgOut->addHTML( '<hr />' );
- $wgOut->addWikiMsg( $first ? 'blockedoriginalsource' : 'blockededitsource', $this->mTitle->getPrefixedText() );
- $this->showTextbox1( array( 'readonly' ), $source );
- }
- }
-
- /**
- * Produce the stock "please login to edit pages" page
- */
- function userNotLoggedInPage() {
- global $wgOut;
-
- $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
- $loginLink = Linker::linkKnown(
- $loginTitle,
- wfMsgHtml( 'loginreqlink' ),
- array(),
- array( 'returnto' => $this->getContextTitle()->getPrefixedText() )
- );
-
- $wgOut->setPageTitle( wfMsg( 'whitelistedittitle' ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setArticleRelated( false );
-
- $wgOut->addHTML( wfMessage( 'whitelistedittext' )->rawParams( $loginLink )->parse() );
- $wgOut->returnToMain( false, $this->getContextTitle() );
- }
-
- /**
- * Creates a basic error page which informs the user that
- * they have attempted to edit a nonexistent section.
- */
- function noSuchSectionPage() {
- global $wgOut;
-
- $wgOut->setPageTitle( wfMsg( 'nosuchsectiontitle' ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setArticleRelated( false );
-
- $res = wfMsgExt( 'nosuchsectiontext', 'parse', $this->section );
- wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
- $wgOut->addHTML( $res );
-
- $wgOut->returnToMain( false, $this->mTitle );
- }
-
- /**
- * Produce the stock "your edit contains spam" page
- *
- * @param $match Text which triggered one or more filters
- * @deprecated since 1.17 Use method spamPageWithContent() instead
- */
- static function spamPage( $match = false ) {
- global $wgOut, $wgTitle;
-
- $wgOut->setPageTitle( wfMsg( 'spamprotectiontitle' ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setArticleRelated( false );
-
- $wgOut->addHTML( '<div id="spamprotected">' );
- $wgOut->addWikiMsg( 'spamprotectiontext' );
- if ( $match ) {
- $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
- }
- $wgOut->addHTML( '</div>' );
-
- $wgOut->returnToMain( false, $wgTitle );
- }
-
- /**
- * Show "your edit contains spam" page with your diff and text
- *
- * @param $match Text which triggered one or more filters
- */
- public function spamPageWithContent( $match = false ) {
- global $wgOut;
- $this->textbox2 = $this->textbox1;
-
- $wgOut->setPageTitle( wfMsg( 'spamprotectiontitle' ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setArticleRelated( false );
-
- $wgOut->addHTML( '<div id="spamprotected">' );
- $wgOut->addWikiMsg( 'spamprotectiontext' );
- if ( $match ) {
- $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
- }
- $wgOut->addHTML( '</div>' );
-
- $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
- $de = new DifferenceEngine( $this->mTitle );
- $de->setText( $this->getContent(), $this->textbox2 );
- $de->showDiff( wfMsg( "storedversion" ), wfMsg( "yourtext" ) );
-
- $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
- $this->showTextbox2();
-
- $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
- }
-
-
- /**
- * @private
- * @todo document
- *
- * @parma $editText string
- *
- * @return bool
- */
- function mergeChangesInto( &$editText ){
- wfProfileIn( __METHOD__ );
-
- $db = wfGetDB( DB_MASTER );
-
- // This is the revision the editor started from
- $baseRevision = $this->getBaseRevision();
- if ( is_null( $baseRevision ) ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- $baseText = $baseRevision->getText();
-
- // The current state, we want to merge updates into it
- $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
- if ( is_null( $currentRevision ) ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- $currentText = $currentRevision->getText();
-
- $result = '';
- if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
- $editText = $result;
- wfProfileOut( __METHOD__ );
- return true;
- } else {
- wfProfileOut( __METHOD__ );
- return false;
- }
- }
-
- /**
- * Check if the browser is on a blacklist of user-agents known to
- * mangle UTF-8 data on form submission. Returns true if Unicode
- * should make it through, false if it's known to be a problem.
- * @return bool
- * @private
- */
- function checkUnicodeCompliantBrowser() {
- global $wgBrowserBlackList;
- if ( empty( $_SERVER["HTTP_USER_AGENT"] ) ) {
- // No User-Agent header sent? Trust it by default...
- return true;
- }
- $currentbrowser = $_SERVER["HTTP_USER_AGENT"];
- foreach ( $wgBrowserBlackList as $browser ) {
- if ( preg_match($browser, $currentbrowser) ) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Format an anchor fragment as it would appear for a given section name
- * @param $text String
- * @return String
- * @private
- */
- function sectionAnchor( $text ) {
- global $wgParser;
- return $wgParser->guessSectionNameFromWikiText( $text );
- }
-
- /**
* Shows a bulletin board style toolbar for common editing functions.
* It can be disabled in the user preferences.
* The necessary JavaScript code can be found in skins/common/edit.js.
@@ -2384,10 +2666,8 @@ HTML
* 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.
+ * and. The tip text is shown when the user moves the mouse
+ * over the button.
*
* Also here: accesskeys (key), which are not used yet until
* someone can figure out a way to make them work in
@@ -2448,7 +2728,6 @@ HTML
'sample' => wfMsg( 'image_sample' ),
'tip' => wfMsg( 'image_tip' ),
'key' => 'D',
- 'select' => true
) : false,
$imagesAvailable ? array(
'image' => $wgLang->getImageFile( 'button-media' ),
@@ -2497,16 +2776,12 @@ HTML
)
);
- $script = '';
+ $script = 'mw.loader.using("mediawiki.action.edit", function() {';
foreach ( $toolarray as $tool ) {
if ( !$tool ) {
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.
@@ -2522,6 +2797,14 @@ HTML
$script .= Xml::encodeJsCall( 'mw.toolbar.addButton', $params );
}
+
+ // This used to be called on DOMReady from mediawiki.action.edit, which
+ // ended up causing race conditions with the setup code above.
+ $script .= "\n" .
+ "// Create button bar\n" .
+ "$(function() { mw.toolbar.init(); } );\n";
+
+ $script .= '});';
$wgOut->addScript( Html::inlineScript( ResourceLoader::makeLoaderConditionalScript( $script ) ) );
$toolbar = '<div id="toolbar"></div>';
@@ -2663,50 +2946,138 @@ HTML
}
/**
- * @return string
+ * Call the stock "user is blocked" page
+ *
+ * @deprecated in 1.19; throw an exception directly instead
*/
- public function getCancelLink() {
- $cancelParams = array();
- if ( !$this->isConflict && $this->mArticle->getOldID() > 0 ) {
- $cancelParams['oldid'] = $this->mArticle->getOldID();
- }
+ function blockedPage() {
+ wfDeprecated( __METHOD__, '1.19' );
+ global $wgUser;
- return Linker::linkKnown(
- $this->getContextTitle(),
- wfMsgExt( 'cancel', array( 'parseinline' ) ),
- array( 'id' => 'mw-editform-cancel' ),
- $cancelParams
- );
+ throw new UserBlockedError( $wgUser->mBlock );
}
/**
- * Get a diff between the current contents of the edit box and the
- * version of the page we're editing from.
+ * Produce the stock "please login to edit pages" page
*
- * If this is a section edit, we'll replace the section as for final
- * save and then make a comparison.
+ * @deprecated in 1.19; throw an exception directly instead
*/
- function showDiff() {
- $oldtext = $this->mArticle->fetchContent();
- $newtext = $this->mArticle->replaceSection(
- $this->section, $this->textbox1, $this->summary, $this->edittime );
+ function userNotLoggedInPage() {
+ wfDeprecated( __METHOD__, '1.19' );
+ throw new PermissionsError( 'edit' );
+ }
- wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
+ /**
+ * Show an error page saying to the user that he has insufficient permissions
+ * to create a new page
+ *
+ * @deprecated in 1.19; throw an exception directly instead
+ */
+ function noCreatePermission() {
+ wfDeprecated( __METHOD__, '1.19' );
+ $permission = $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage';
+ throw new PermissionsError( $permission );
+ }
- $newtext = $this->mArticle->preSaveTransform( $newtext );
- $oldtitle = wfMsgExt( 'currentrev', array( 'parseinline' ) );
- $newtitle = wfMsgExt( 'yourtext', array( 'parseinline' ) );
- if ( $oldtext !== false || $newtext != '' ) {
- $de = new DifferenceEngine( $this->mTitle );
- $de->setText( $oldtext, $newtext );
- $difftext = $de->getDiff( $oldtitle, $newtitle );
- $de->showDiffStyle();
- } else {
- $difftext = '';
+ /**
+ * Creates a basic error page which informs the user that
+ * they have attempted to edit a nonexistent section.
+ */
+ function noSuchSectionPage() {
+ global $wgOut;
+
+ $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
+
+ $res = wfMsgExt( 'nosuchsectiontext', 'parse', $this->section );
+ wfRunHooks( 'EditPageNoSuchSection', array( &$this, &$res ) );
+ $wgOut->addHTML( $res );
+
+ $wgOut->returnToMain( false, $this->mTitle );
+ }
+
+ /**
+ * Produce the stock "your edit contains spam" page
+ *
+ * @param $match Text which triggered one or more filters
+ * @deprecated since 1.17 Use method spamPageWithContent() instead
+ */
+ static function spamPage( $match = false ) {
+ wfDeprecated( __METHOD__, '1.17' );
+
+ global $wgOut, $wgTitle;
+
+ $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
+
+ $wgOut->addHTML( '<div id="spamprotected">' );
+ $wgOut->addWikiMsg( 'spamprotectiontext' );
+ if ( $match ) {
+ $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
}
+ $wgOut->addHTML( '</div>' );
+ $wgOut->returnToMain( false, $wgTitle );
+ }
+
+ /**
+ * Show "your edit contains spam" page with your diff and text
+ *
+ * @param $match Text which triggered one or more filters
+ */
+ public function spamPageWithContent( $match = false ) {
global $wgOut;
- $wgOut->addHTML( '<div id="wikiDiff">' . $difftext . '</div>' );
+ $this->textbox2 = $this->textbox1;
+
+ $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
+
+ $wgOut->addHTML( '<div id="spamprotected">' );
+ $wgOut->addWikiMsg( 'spamprotectiontext' );
+ if ( $match ) {
+ $wgOut->addWikiMsg( 'spamprotectionmatch', wfEscapeWikiText( $match ) );
+ }
+ $wgOut->addHTML( '</div>' );
+
+ $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
+ $de = new DifferenceEngine( $this->mArticle->getContext() );
+ $de->setText( $this->getCurrentText(), $this->textbox2 );
+ $de->showDiff( wfMsg( "storedversion" ), wfMsgExt( 'yourtext', 'parseinline' ) );
+
+ $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
+ $this->showTextbox2();
+
+ $wgOut->addReturnTo( $this->getContextTitle(), array( 'action' => 'edit' ) );
+ }
+
+ /**
+ * Format an anchor fragment as it would appear for a given section name
+ * @param $text String
+ * @return String
+ * @private
+ */
+ function sectionAnchor( $text ) {
+ global $wgParser;
+ return $wgParser->guessSectionNameFromWikiText( $text );
+ }
+
+ /**
+ * Check if the browser is on a blacklist of user-agents known to
+ * mangle UTF-8 data on form submission. Returns true if Unicode
+ * should make it through, false if it's known to be a problem.
+ * @return bool
+ * @private
+ */
+ function checkUnicodeCompliantBrowser() {
+ global $wgBrowserBlackList;
+ if ( empty( $_SERVER["HTTP_USER_AGENT"] ) ) {
+ // No User-Agent header sent? Trust it by default...
+ return true;
+ }
+ $currentbrowser = $_SERVER["HTTP_USER_AGENT"];
+ foreach ( $wgBrowserBlackList as $browser ) {
+ if ( preg_match($browser, $currentbrowser) ) {
+ return false;
+ }
+ }
+ return true;
}
/**
@@ -2835,117 +3206,4 @@ HTML
// reverse the transform that we made for reversability reasons.
return strtr( $result, array( "&#x0" => "&#x" ) );
}
-
- function noCreatePermission() {
- global $wgOut;
- $wgOut->setPageTitle( wfMsg( 'nocreatetitle' ) );
- $wgOut->addWikiMsg( 'nocreatetext' );
- }
-
- /**
- * Attempt submission
- * @return bool false if output is done, true if the rest of the form should be displayed
- */
- function attemptSave() {
- global $wgUser, $wgOut;
-
- $resultDetails = false;
- # Allow bots to exempt some edits from bot flagging
- $bot = $wgUser->isAllowed( 'bot' ) && $this->bot;
- $status = $this->internalAttemptSave( $resultDetails, $bot );
- // FIXME: once the interface for internalAttemptSave() is made nicer, this should use the message in $status
-
- if ( $status->value == self::AS_SUCCESS_UPDATE || $status->value == self::AS_SUCCESS_NEW_ARTICLE ) {
- $this->didSave = true;
- }
-
- switch ( $status->value ) {
- case self::AS_HOOK_ERROR_EXPECTED:
- case self::AS_CONTENT_TOO_BIG:
- case self::AS_ARTICLE_WAS_DELETED:
- case self::AS_CONFLICT_DETECTED:
- case self::AS_SUMMARY_NEEDED:
- case self::AS_TEXTBOX_EMPTY:
- case self::AS_MAX_ARTICLE_SIZE_EXCEEDED:
- case self::AS_END:
- return true;
-
- 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:
- $this->spamPageWithContent( $resultDetails['spam'] );
- return false;
-
- case self::AS_BLOCKED_PAGE_FOR_USER:
- $this->blockedPage();
- return false;
-
- case self::AS_IMAGE_REDIRECT_ANON:
- $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
- return false;
-
- case self::AS_READ_ONLY_PAGE_ANON:
- $this->userNotLoggedInPage();
- 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_NO_CREATE_PERMISSION:
- $this->noCreatePermission();
- return false;
-
- case self::AS_BLANK_ARTICLE:
- $wgOut->redirect( $this->getContextTitle()->getFullURL() );
- return false;
-
- case self::AS_IMAGE_REDIRECT_LOGGED:
- $wgOut->permissionRequired( 'upload' );
- return false;
- }
- }
-
- /**
- * @return Revision
- */
- function getBaseRevision() {
- if ( !$this->mBaseRevision ) {
- $db = wfGetDB( DB_MASTER );
- $baseRevision = Revision::loadFromTimestamp(
- $db, $this->mTitle, $this->edittime );
- return $this->mBaseRevision = $baseRevision;
- } else {
- return $this->mBaseRevision;
- }
- }
}
diff --git a/includes/Exception.php b/includes/Exception.php
index 1f599d66..3bd89b6e 100644
--- a/includes/Exception.php
+++ b/includes/Exception.php
@@ -53,7 +53,7 @@ class MWException extends Exception {
global $wgExceptionHooks;
if ( !isset( $wgExceptionHooks ) || !is_array( $wgExceptionHooks ) ) {
- return; // Just silently ignore
+ return; // Just silently ignore
}
if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[ $name ] ) ) {
@@ -70,8 +70,9 @@ class MWException extends Exception {
$result = null;
}
- if ( is_string( $result ) )
+ if ( is_string( $result ) ) {
return $result;
+ }
}
}
@@ -118,6 +119,7 @@ class MWException extends Exception {
/**
* If $wgShowExceptionDetails is true, return a text message with a
* backtrace to the error.
+ * @return string
*/
function getText() {
global $wgShowExceptionDetails;
@@ -131,10 +133,12 @@ class MWException extends Exception {
}
}
- /* Return titles of this error page */
+ /**
+ * Return titles of this error page
+ * @return String
+ */
function getPageTitle() {
- global $wgSitename;
- return $this->msg( 'internalerror', "$wgSitename error" );
+ return $this->msg( 'internalerror', "Internal error" );
}
/**
@@ -166,12 +170,7 @@ class MWException extends Exception {
function reportHTML() {
global $wgOut;
if ( $this->useOutputPage() ) {
- $wgOut->setPageTitle( $this->getPageTitle() );
- $wgOut->setRobotPolicy( "noindex,nofollow" );
- $wgOut->setArticleRelated( false );
- $wgOut->enableClientCache( false );
- $wgOut->redirect( '' );
- $wgOut->clearHTML();
+ $wgOut->prepareErrorPage( $this->getPageTitle() );
$hookResult = $this->runHooks( get_class( $this ) );
if ( $hookResult ) {
@@ -182,6 +181,7 @@ class MWException extends Exception {
$wgOut->output();
} else {
+ header( "Content-Type: text/html; charset=utf-8" );
$hookResult = $this->runHooks( get_class( $this ) . "Raw" );
if ( $hookResult ) {
die( $hookResult );
@@ -210,6 +210,10 @@ class MWException extends Exception {
}
}
+ /**
+ * @static
+ * @return bool
+ */
static function isCommandLine() {
return !empty( $GLOBALS['wgCommandLineMode'] );
}
@@ -221,10 +225,17 @@ class MWException extends Exception {
* @ingroup Exception
*/
class FatalError extends MWException {
+
+ /**
+ * @return string
+ */
function getHTML() {
return $this->getMessage();
}
+ /**
+ * @return string
+ */
function getText() {
return $this->getMessage();
}
@@ -255,44 +266,76 @@ class ErrorPageError extends MWException {
function report() {
global $wgOut;
+
$wgOut->showErrorPage( $this->title, $this->msg, $this->params );
$wgOut->output();
}
}
/**
+ * Show an error page on a badtitle.
+ * Similar to ErrorPage, but emit a 400 HTTP error code to let mobile
+ * browser it is not really a valid content.
+ */
+class BadTitleError extends ErrorPageError {
+
+ /**
+ * @param $msg string A message key (default: 'badtitletext')
+ * @param $params Array parameter to wfMsg()
+ */
+ function __construct( $msg = 'badtitletext', $params = null ) {
+ parent::__construct( 'badtitle', $msg, $params );
+ }
+
+ /**
+ * Just like ErrorPageError::report() but additionally set
+ * a 400 HTTP status code (bug 33646).
+ */
+ function report() {
+ global $wgOut;
+
+ // bug 33646: a badtitle error page need to return an error code
+ // to let mobile browser now that it is not a normal page.
+ $wgOut->setStatusCode( 400 );
+ parent::report();
+ }
+
+}
+
+/**
* Show an error when a user tries to do something they do not have the necessary
* permissions for.
* @ingroup Exception
*/
class PermissionsError extends ErrorPageError {
- public $permission;
+ public $permission, $errors;
- function __construct( $permission ) {
+ function __construct( $permission, $errors = array() ) {
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'
+ if ( !count( $errors ) ) {
+ $groups = array_map(
+ array( 'User', 'makeGroupLinkWiki' ),
+ User::getGroupsWithPermission( $this->permission )
);
+
+ if ( $groups ) {
+ $errors[] = array( 'badaccess-groups', $wgLang->commaList( $groups ), count( $groups ) );
+ } else {
+ $errors[] = array( 'badaccess-group0' );
+ }
}
+
+ $this->errors = $errors;
+ }
+
+ function report() {
+ global $wgOut;
+
+ $wgOut->showPermissionsErrorPage( $this->errors, $this->permission );
+ $wgOut->output();
}
}
@@ -322,6 +365,7 @@ class ThrottledError extends ErrorPageError {
'actionthrottledtext'
);
}
+
public function report(){
global $wgOut;
$wgOut->setStatusCode( 503 );
@@ -335,10 +379,15 @@ class ThrottledError extends ErrorPageError {
*/
class UserBlockedError extends ErrorPageError {
public function __construct( Block $block ){
- global $wgLang;
-
- $blockerUserpage = $block->getBlocker()->getUserPage();
- $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
+ global $wgLang, $wgRequest;
+
+ $blocker = $block->getBlocker();
+ if ( $blocker instanceof User ) { // local user
+ $blockerUserpage = $block->getBlocker()->getUserPage();
+ $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
+ } else { // foreign user
+ $link = $blocker;
+ }
$reason = $block->mReason;
if( $reason == '' ) {
@@ -355,8 +404,8 @@ class UserBlockedError extends ErrorPageError {
array(
$link,
$reason,
- wfGetIP(),
- $block->getBlocker()->getName(),
+ $wgRequest->getIP(),
+ $block->getByName(),
$block->getId(),
$wgLang->formatExpiry( $block->mExpiry ),
$intended,
@@ -367,6 +416,55 @@ class UserBlockedError extends ErrorPageError {
}
/**
+ * Show an error that looks like an HTTP server error.
+ * Replacement for wfHttpError().
+ *
+ * @ingroup Exception
+ */
+class HttpError extends MWException {
+ private $httpCode, $header, $content;
+
+ /**
+ * Constructor
+ *
+ * @param $httpCode Integer: HTTP status code to send to the client
+ * @param $content String|Message: content of the message
+ * @param $header String|Message: content of the header (\<title\> and \<h1\>)
+ */
+ public function __construct( $httpCode, $content, $header = null ){
+ parent::__construct( $content );
+ $this->httpCode = (int)$httpCode;
+ $this->header = $header;
+ $this->content = $content;
+ }
+
+ public function reportHTML() {
+ $httpMessage = HttpStatus::getMessage( $this->httpCode );
+
+ header( "Status: {$this->httpCode} {$httpMessage}" );
+ header( 'Content-type: text/html; charset=utf-8' );
+
+ if ( $this->header === null ) {
+ $header = $httpMessage;
+ } elseif ( $this->header instanceof Message ) {
+ $header = $this->header->escaped();
+ } else {
+ $header = htmlspecialchars( $this->header );
+ }
+
+ if ( $this->content instanceof Message ) {
+ $content = $this->content->escaped();
+ } else {
+ $content = htmlspecialchars( $this->content );
+ }
+
+ print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n".
+ "<html><head><title>$header</title></head>\n" .
+ "<body><h1>$header</h1><p>$content</p></body></html>\n";
+ }
+}
+
+/**
* Handler class for MWExceptions
* @ingroup Exception
*/
diff --git a/includes/Export.php b/includes/Export.php
index 87c735c1..7773d03c 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -56,7 +56,7 @@ class WikiExporter {
* make additional queries to pull source data while the
* main query is still running.
*
- * @param $db Database
+ * @param $db DatabaseBase
* @param $history Mixed: one of WikiExporter::FULL, WikiExporter::CURRENT,
* WikiExporter::RANGE or WikiExporter::STABLE,
* or an associative array:
@@ -380,7 +380,7 @@ class XmlDumpWriter {
* @return string
*/
function schemaVersion() {
- return "0.5";
+ return "0.6";
}
/**
@@ -477,10 +477,22 @@ class XmlDumpWriter {
$out = " <page>\n";
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
$out .= ' ' . Xml::elementClean( 'title', array(), self::canonicalTitle( $title ) ) . "\n";
+ $out .= ' ' . Xml::element( 'ns', array(), strval( $row->page_namespace) ) . "\n";
$out .= ' ' . Xml::element( 'id', array(), strval( $row->page_id ) ) . "\n";
if ( $row->page_is_redirect ) {
- $out .= ' ' . Xml::element( 'redirect', array() ) . "\n";
+ $page = WikiPage::factory( $title );
+ $redirect = $page->getRedirectTarget();
+ if ( $redirect instanceOf Title && $redirect->isValidRedirectTarget() ) {
+ $out .= ' ' . Xml::element( 'redirect', array( 'title' => self::canonicalTitle( $redirect ) ) ) . "\n";
+ }
+ }
+
+ if ( $row->rev_sha1 ) {
+ $out .= " " . Xml::element('sha1', null, strval($row->rev_sha1) ) . "\n";
+ } else {
+ $out .= " <sha1/>\n";
}
+
if ( $row->page_restrictions != '' ) {
$out .= ' ' . Xml::element( 'restrictions', array(),
strval( $row->page_restrictions ) ) . "\n";
@@ -538,12 +550,12 @@ class XmlDumpWriter {
// Raw text from the database may have invalid chars
$text = strval( Revision::getRevisionText( $row ) );
$out .= " " . Xml::elementClean( 'text',
- array( 'xml:space' => 'preserve', 'bytes' => $row->rev_len ),
+ array( 'xml:space' => 'preserve', 'bytes' => intval( $row->rev_len ) ),
strval( $text ) ) . "\n";
} else {
// Stub output
$out .= " " . Xml::element( 'text',
- array( 'id' => $row->rev_text_id, 'bytes' => $row->rev_len ),
+ array( 'id' => $row->rev_text_id, 'bytes' => intval( $row->rev_len ) ),
"" ) . "\n";
}
@@ -609,7 +621,7 @@ class XmlDumpWriter {
function writeContributor( $id, $text ) {
$out = " <contributor>\n";
- if ( $id ) {
+ if ( $id || !IP::isValid( $text ) ) {
$out .= " " . Xml::elementClean( 'username', null, strval( $text ) ) . "\n";
$out .= " " . Xml::element( 'id', null, strval( $id ) ) . "\n";
} else {
@@ -677,9 +689,10 @@ class XmlDumpWriter {
* 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
+ * @since 1.18
*/
public static function canonicalTitle( Title $title ) {
if ( $title->getInterwiki() ) {
@@ -689,7 +702,7 @@ class XmlDumpWriter {
global $wgContLang;
$prefix = str_replace( '_', ' ', $wgContLang->getNsText( $title->getNamespace() ) );
- if ($prefix !== '') {
+ if ( $prefix !== '' ) {
$prefix .= ':';
}
@@ -892,8 +905,6 @@ class DumpBZip2Output extends DumpPipeOutput {
* @ingroup Dump
*/
class Dump7ZipOutput extends DumpPipeOutput {
- protected $filename;
-
function __construct( $file ) {
$command = $this->setup7zCommand( $file );
parent::__construct( $command );
@@ -908,10 +919,6 @@ class Dump7ZipOutput extends DumpPipeOutput {
return( $command );
}
- function closeRenameAndReopen( $newname ) {
- $this->closeAndRename( $newname, true );
- }
-
function closeAndRename( $newname, $open = false ) {
$newname = $this->checkRenameArgCount( $newname );
if ( $newname ) {
@@ -919,7 +926,7 @@ class Dump7ZipOutput extends DumpPipeOutput {
proc_close( $this->procOpenResource );
$this->renameOrException( $newname );
if ( $open ) {
- $command = $this->setup7zCommand( $file );
+ $command = $this->setup7zCommand( $this->filename );
$this->startCommand( $command );
}
}
diff --git a/includes/ExternalEdit.php b/includes/ExternalEdit.php
index bf97c1a5..b8704758 100644
--- a/includes/ExternalEdit.php
+++ b/includes/ExternalEdit.php
@@ -18,60 +18,99 @@
* and save the modified data back to the server.
*
*/
-class ExternalEdit {
+class ExternalEdit extends ContextSource {
+
/**
- * Title to perform the edit on
- * @var Title
+ * Array of URLs to link to
+ * @var Array
*/
- private $title;
+ private $urls;
/**
- * Mode of editing
- * @var String
+ * Constructor
+ * @param $context IContextSource context to use
+ * @param $urls array
*/
- private $mode;
+ public function __construct( IContextSource $context, array $urls = array() ) {
+ $this->setContext( $context );
+ $this->urls = $urls;
+ }
/**
- * Constructor
- * @param $title Title object we're performing the edit on
- * @param $mode String What mode we're using. Only 'file' has any effect
+ * Check whether external edit or diff should be used.
+ *
+ * @param $context IContextSource context to use
+ * @param $type String can be either 'edit' or 'diff'
+ * @return Bool
*/
- public function __construct( $title, $mode ) {
- $this->title = $title;
- $this->mode = $mode;
+ public static function useExternalEngine( IContextSource $context, $type ) {
+ global $wgUseExternalEditor;
+
+ if ( !$wgUseExternalEditor ) {
+ return false;
+ }
+
+ $pref = $type == 'diff' ? 'externaldiff' : 'externaleditor';
+ $request = $context->getRequest();
+
+ return !$request->getVal( 'internaledit' ) &&
+ ( $context->getUser()->getOption( $pref ) || $request->getVal( 'externaledit' ) );
}
/**
* Output the information for the external editor
*/
- public function edit() {
- global $wgOut, $wgScript, $wgScriptPath, $wgCanonicalServer, $wgLang;
- $wgOut->disable();
- header( 'Content-type: application/x-external-editor; charset=utf-8' );
- header( 'Cache-control: no-cache' );
+ public function execute() {
+ global $wgContLang, $wgScript, $wgScriptPath, $wgCanonicalServer;
+
+ $this->getOutput()->disable();
+
+ $response = $this->getRequest()->response();
+ $response->header( 'Content-type: application/x-external-editor; charset=utf-8' );
+ $response->header( 'Cache-control: no-cache' );
+
+ $special = $wgContLang->getNsText( NS_SPECIAL );
# $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( $this->mode == "file" ) {
+ if ( count( $this->urls ) ) {
+ $urls = $this->urls;
+ $type = "Diff text";
+ } elseif ( $this->getRequest()->getVal( 'mode' ) == 'file' ) {
$type = "Edit file";
- $image = wfLocalFile( $this->title );
- $url = $image->getCanonicalURL();
- $extension = $image->getExtension();
+ $image = wfLocalFile( $this->getTitle() );
+ $urls = array( 'File' => array(
+ 'Extension' => $image->getExtension(),
+ 'URL' => $image->getCanonicalURL()
+ ) );
} 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";
+ $urls = array( 'File' => array(
+ 'Extension' => 'wiki',
+ 'URL' => $this->getTitle()->getCanonicalURL(
+ array( 'action' => 'edit', 'internaledit' => 'true' ) )
+ ) );
+ }
+
+ $files = '';
+ foreach( $urls as $key => $vars ) {
+ $files .= "\n[$key]\n";
+ foreach( $vars as $varname => $varval ) {
+ $files .= "$varname=$varval\n";
+ }
}
- $special = $wgLang->getNsText( NS_SPECIAL );
+
+ $url = $this->getTitle()->getFullURL(
+ $this->getRequest()->appendQueryValue( 'internaledit', 1, true ) );
+
$control = <<<CONTROL
-; You're seeing this file because you're using Mediawiki's External Editor
-; feature. This is probably because you selected use external editor
-; in your preferences. To edit normally, either disable that preference
-; or go to the URL $url .
+; You're seeing this file because you're using Mediawiki's External Editor feature.
+; This is probably because you selected use external editor in your preferences.
+; To edit normally, either disable that preference or go to the URL:
+; $url
; See http://www.mediawiki.org/wiki/Manual:External_editors for details.
[Process]
Type=$type
@@ -80,10 +119,7 @@ Script={$wgCanonicalServer}{$wgScript}
Server={$wgCanonicalServer}
Path={$wgScriptPath}
Special namespace=$special
-
-[File]
-Extension=$extension
-URL=$url
+$files
CONTROL;
echo $control;
}
diff --git a/includes/ExternalStoreDB.php b/includes/ExternalStoreDB.php
index 552c3109..4920a91c 100644
--- a/includes/ExternalStoreDB.php
+++ b/includes/ExternalStoreDB.php
@@ -34,7 +34,7 @@ class ExternalStoreDB {
$wiki = isset($this->mParams['wiki']) ? $this->mParams['wiki'] : false;
$lb =& $this->getLoadBalancer( $cluster );
- if ( !in_array( "DB://" . $cluster, $wgDefaultExternalStore ) ) {
+ if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
wfDebug( "read only external store" );
$lb->allowLagged(true);
} else {
@@ -120,12 +120,12 @@ class ExternalStoreDB {
wfDebug( "ExternalStoreDB::fetchBlob cache miss on $cacheID\n" );
$dbr =& $this->getSlave( $cluster );
- $ret = $dbr->selectField( $this->getTable( $dbr ), 'blob_text', array( 'blob_id' => $id ) );
+ $ret = $dbr->selectField( $this->getTable( $dbr ), 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
if ( $ret === false ) {
wfDebugLog( 'ExternalStoreDB', "ExternalStoreDB::fetchBlob master fallback on $cacheID\n" );
// Try the master
$dbw =& $this->getMaster( $cluster );
- $ret = $dbw->selectField( $this->getTable( $dbw ), 'blob_text', array( 'blob_id' => $id ) );
+ $ret = $dbw->selectField( $this->getTable( $dbw ), 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
if( $ret === false) {
wfDebugLog( 'ExternalStoreDB', "ExternalStoreDB::fetchBlob master failed to find $cacheID\n" );
}
diff --git a/includes/FakeTitle.php b/includes/FakeTitle.php
index 515ff387..8415ec08 100644
--- a/includes/FakeTitle.php
+++ b/includes/FakeTitle.php
@@ -31,9 +31,9 @@ class FakeTitle extends Title {
function getPrefixedURL() { $this->error(); }
function getFullURL( $query = '', $variant = false ) { $this->error(); }
function getLocalURL( $query = '', $variant = false ) { $this->error(); }
- function getLinkUrl( $query = array(), $variant = false ) { $this->error(); }
- function escapeLocalURL( $query = '' ) { $this->error(); }
- function escapeFullURL( $query = '' ) { $this->error(); }
+ function getLinkURL( $query = array(), $variant = false ) { $this->error(); }
+ function escapeLocalURL( $query = '', $query2 = false ) { $this->error(); }
+ function escapeFullURL( $query = '', $query2 = false ) { $this->error(); }
function getInternalURL( $query = '', $variant = false ) { $this->error(); }
function getEditURL() { $this->error(); }
function getEscapedText() { $this->error(); }
@@ -42,9 +42,9 @@ class FakeTitle extends Title {
function isProtected( $action = '' ) { $this->error(); }
function isConversionTable() { $this->error(); }
function userIsWatching() { $this->error(); }
- function quickUserCan( $action ) { $this->error(); }
- function isNamespaceProtected() { $this->error(); }
- function userCan( $action, $doExpensiveQueries = true ) { $this->error(); }
+ function quickUserCan( $action, $user = null ) { $this->error(); }
+ function isNamespaceProtected( User $user ) { $this->error(); }
+ function userCan( $action, $user = null, $doExpensiveQueries = true ) { $this->error(); }
function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true, $ignoreErrors = array() ) { $this->error(); }
function updateTitleProtection( $create_perm, $reason, $expiry ) { $this->error(); }
function deleteTitleProtection() { $this->error(); }
@@ -56,7 +56,6 @@ class FakeTitle extends Title {
function getSubpages( $limit = -1 ) { $this->error(); }
function isCssJsSubpage() { $this->error(); }
function isCssOrJsPage() { $this->error(); }
- function isValidCssJsSubpage() { $this->error(); }
function getSkinFromCssJsSubpage() { $this->error(); }
function isCssSubpage() { $this->error(); }
function isJsSubpage() { $this->error(); }
@@ -102,7 +101,7 @@ class FakeTitle extends Title {
function getNextRevisionID( $revId, $flags=0 ) { $this->error(); }
function getFirstRevision( $flags=0 ) { $this->error(); }
function isNewPage() { $this->error(); }
- function getEarliestRevTime() { $this->error(); }
+ function getEarliestRevTime( $flags = 0 ) { $this->error(); }
function countRevisionsBetween( $old, $new ) { $this->error(); }
function equals( Title $title ) { $this->error(); }
function exists() { $this->error(); }
@@ -112,8 +111,6 @@ class FakeTitle extends Title {
function touchLinks() { $this->error(); }
function getTouched( $db = null ) { $this->error(); }
function getNotificationTimestamp( $user = null ) { $this->error(); }
- function trackbackURL() { $this->error(); }
- function trackbackRDF() { $this->error(); }
function getNamespaceKey( $prepend = 'nstab-' ) { $this->error(); }
function isSpecialPage() { $this->error(); }
function isSpecial( $name ) { $this->error(); }
diff --git a/includes/Fallback.php b/includes/Fallback.php
index 2cca1e81..b517cd16 100644
--- a/includes/Fallback.php
+++ b/includes/Fallback.php
@@ -22,7 +22,13 @@
* Fallback functions for PHP installed without mbstring support
*/
class Fallback {
-
+
+ /**
+ * @param $from
+ * @param $to
+ * @param $string
+ * @return string
+ */
public static function iconv( $from, $to, $string ) {
if ( substr( $to, -8 ) == '//IGNORE' ) {
$to = substr( $to, 0, strlen( $to ) - 8 );
@@ -48,7 +54,7 @@ class Fallback {
* 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
@@ -60,22 +66,27 @@ class Fallback {
$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;
}
-
+
+ /**
+ * @param $str
+ * @param $splitPos
+ * @return int
+ */
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.
@@ -90,7 +101,7 @@ class Fallback {
$charPos = 0;
$bytePos = 0;
}
-
+
while( $charPos++ < $splitPos ) {
++$bytePos;
// Move past any tail bytes
@@ -110,10 +121,10 @@ class Fallback {
}
}
}
-
+
return $bytePos;
}
-
+
/**
* Fallback implementation of mb_strlen, hardcoded to UTF-8.
* @param string $str
@@ -123,20 +134,20 @@ class Fallback {
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
@@ -147,17 +158,17 @@ class Fallback {
*/
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
@@ -168,10 +179,10 @@ class Fallback {
*/
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];
@@ -196,5 +207,5 @@ class Fallback {
}
return false;
}
-
+
}
diff --git a/includes/Feed.php b/includes/Feed.php
index ef33c78f..351f3572 100644
--- a/includes/Feed.php
+++ b/includes/Feed.php
@@ -39,42 +39,34 @@ class FeedItem {
/**
* @var Title
*/
- var $Title = 'Wiki';
- var $Description = '';
- var $Url = '';
- var $Date = '';
- var $Author = '';
- var $UniqueId = '';
- var $RSSIsPermalink;
+ var $title;
- /**
- * Constructor
- *
- * @param $Title String|Title Item's title
- * @param $Description String
- * @param $Url String: URL uniquely designating the item.
- * @param $Date String: Item's date
- * @param $Author String: Author's user name
- * @param $Comments String
- */
- function __construct( $Title, $Description, $Url, $Date = '', $Author = '', $Comments = '' ) {
- $this->Title = $Title;
- $this->Description = $Description;
- $this->Url = $Url;
- $this->UniqueId = $Url;
- $this->RSSIsPermalink = false;
- $this->Date = $Date;
- $this->Author = $Author;
- $this->Comments = $Comments;
- }
+ var $description;
+ var $url;
+ var $date;
+ var $author;
+ var $uniqueId;
+ var $comments;
+ var $rssIsPermalink = false;
/**
- * Get the last touched timestamp
+ * Constructor
*
- * @return String last-touched timestamp
- */
- public function getLastMod() {
- return $this->Title->getTouched();
+ * @param $title String|Title Item's title
+ * @param $description String
+ * @param $url String: URL uniquely designating the item.
+ * @param $date String: Item's date
+ * @param $author String: Author's user name
+ * @param $comments String
+ */
+ function __construct( $title, $description, $url, $date = '', $author = '', $comments = '' ) {
+ $this->title = $title;
+ $this->description = $description;
+ $this->url = $url;
+ $this->uniqueId = $url;
+ $this->date = $date;
+ $this->author = $author;
+ $this->comments = $comments;
}
/**
@@ -95,8 +87,8 @@ class FeedItem {
* @return String
*/
public function getUniqueId() {
- if ( $this->UniqueId ) {
- return $this->xmlEncode( $this->UniqueId );
+ if ( $this->uniqueId ) {
+ return $this->xmlEncode( $this->uniqueId );
}
}
@@ -104,11 +96,11 @@ class FeedItem {
* set the unique id of an item
*
* @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)
+ * @param $rssIsPermalink Boolean: set to true if the guid (unique id) is a permalink (RSS feeds only)
*/
- public function setUniqueId($uniqueId, $RSSisPermalink = false) {
- $this->UniqueId = $uniqueId;
- $this->RSSIsPermalink = $RSSisPermalink;
+ public function setUniqueId( $uniqueId, $rssIsPermalink = false ) {
+ $this->uniqueId = $uniqueId;
+ $this->rssIsPermalink = $rssIsPermalink;
}
/**
@@ -117,17 +109,7 @@ class FeedItem {
* @return String
*/
public function getTitle() {
- return $this->xmlEncode( $this->Title );
- }
-
- /**
- * 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();
+ return $this->xmlEncode( $this->title );
}
/**
@@ -136,7 +118,7 @@ class FeedItem {
* @return String
*/
public function getUrl() {
- return $this->xmlEncode( $this->Url );
+ return $this->xmlEncode( $this->url );
}
/**
@@ -145,7 +127,7 @@ class FeedItem {
* @return String
*/
public function getDescription() {
- return $this->xmlEncode( $this->Description );
+ return $this->xmlEncode( $this->description );
}
/**
@@ -164,7 +146,7 @@ class FeedItem {
* @return String
*/
public function getDate() {
- return $this->Date;
+ return $this->date;
}
/**
@@ -173,7 +155,7 @@ class FeedItem {
* @return String
*/
public function getAuthor() {
- return $this->xmlEncode( $this->Author );
+ return $this->xmlEncode( $this->author );
}
/**
@@ -182,7 +164,7 @@ class FeedItem {
* @return String
*/
public function getComments() {
- return $this->xmlEncode( $this->Comments );
+ return $this->xmlEncode( $this->comments );
}
/**
@@ -325,7 +307,7 @@ class RSSFeed extends ChannelFeed {
<item>
<title><?php print $item->getTitle() ?></title>
<link><?php print wfExpandUrl( $item->getUrl(), PROTO_CURRENT ) ?></link>
- <guid<?php if( !$item->RSSIsPermalink ) print ' isPermaLink="false"' ?>><?php print $item->getUniqueId() ?></guid>
+ <guid<?php if( !$item->rssIsPermalink ) print ' isPermaLink="false"' ?>><?php print $item->getUniqueId() ?></guid>
<description><?php print $item->getDescription() ?></description>
<?php if( $item->getDate() ) { ?><pubDate><?php print $this->formatTime( $item->getDate() ) ?></pubDate><?php } ?>
<?php if( $item->getAuthor() ) { ?><dc:creator><?php print $item->getAuthor() ?></dc:creator><?php }?>
diff --git a/includes/FeedUtils.php b/includes/FeedUtils.php
index 4502c3a8..cf42329b 100644
--- a/includes/FeedUtils.php
+++ b/includes/FeedUtils.php
@@ -31,16 +31,15 @@ class FeedUtils {
* @return Boolean
*/
public static function checkFeedOutput( $type ) {
- global $wgFeed, $wgFeedClasses;
+ global $wgOut, $wgFeed, $wgFeedClasses;
if ( !$wgFeed ) {
- global $wgOut;
$wgOut->addWikiMsg( 'feed-unavailable' );
return false;
}
if( !isset( $wgFeedClasses[$type] ) ) {
- wfHttpError( 500, "Internal Server Error", "Unsupported feed type." );
+ $wgOut->addWikiMsg( 'feed-invalid' );
return false;
}
@@ -54,24 +53,21 @@ class FeedUtils {
* @return String
*/
public static function formatDiff( $row ) {
- global $wgUser;
-
$titleObj = Title::makeTitle( $row->rc_namespace, $row->rc_title );
$timestamp = wfTimestamp( TS_MW, $row->rc_timestamp );
$actiontext = '';
if( $row->rc_type == RC_LOG ) {
- if( $row->rc_deleted & LogPage::DELETED_ACTION ) {
- $actiontext = wfMsgHtml('rev-deleted-event');
- } else {
- $actiontext = LogPage::actionText( $row->rc_log_type, $row->rc_log_action,
- $titleObj, $wgUser->getSkin(), LogPage::extractParams($row->rc_params,true,true) );
- }
+ $rcRow = (array)$row; // newFromRow() only accepts arrays for RC rows
+ $actiontext = LogFormatter::newFromRow( $rcRow )->getActionText();
}
return self::formatDiffRow( $titleObj,
$row->rc_last_oldid, $row->rc_this_oldid,
$timestamp,
- ($row->rc_deleted & Revision::DELETED_COMMENT) ? wfMsgHtml('rev-deleted-comment') : $row->rc_comment,
- $actiontext );
+ ($row->rc_deleted & Revision::DELETED_COMMENT)
+ ? wfMsgHtml('rev-deleted-comment')
+ : $row->rc_comment,
+ $actiontext
+ );
}
/**
@@ -86,88 +82,109 @@ class FeedUtils {
* @return String
*/
public static function formatDiffRow( $title, $oldid, $newid, $timestamp, $comment, $actiontext='' ) {
- global $wgFeedDiffCutoff, $wgLang, $wgUser;
+ global $wgFeedDiffCutoff, $wgLang;
wfProfileIn( __METHOD__ );
- $skin = $wgUser->getSkin();
# log enties
$completeText = '<p>' . implode( ' ',
array_filter(
array(
$actiontext,
- $skin->formatComment( $comment ) ) ) ) . "</p>\n";
+ Linker::formatComment( $comment ) ) ) ) . "</p>\n";
- //NOTE: Check permissions for anonymous users, not current user.
- // No "privileged" version should end up in the cache.
- // Most feed readers will not log in anway.
+ // NOTE: Check permissions for anonymous users, not current user.
+ // No "privileged" version should end up in the cache.
+ // Most feed readers will not log in anway.
$anon = new User();
$accErrors = $title->getUserPermissionsErrors( 'read', $anon, true );
- if( $title->getNamespace() >= 0 && !$accErrors && $newid ) {
- if( $oldid ) {
- wfProfileIn( __METHOD__."-dodiff" );
-
- #$diffText = $de->getDiff( wfMsg( 'revisionasof',
- # $wgLang->timeanddate( $timestamp ),
- # $wgLang->date( $timestamp ),
- # $wgLang->time( $timestamp ) ),
- # wfMsg( 'currentrev' ) );
-
- // Don't bother generating the diff if we won't be able to show it
- if ( $wgFeedDiffCutoff > 0 ) {
- $de = new DifferenceEngine( $title, $oldid, $newid );
- $diffText = $de->getDiff(
- wfMsg( 'previousrevision' ), // hack
- wfMsg( 'revisionasof',
- $wgLang->timeanddate( $timestamp ),
- $wgLang->date( $timestamp ),
- $wgLang->time( $timestamp ) ) );
- }
-
- if ( $wgFeedDiffCutoff <= 0 || ( strlen( $diffText ) > $wgFeedDiffCutoff ) ) {
- // Omit large diffs
- $diffLink = $title->escapeFullUrl(
- 'diff=' . $newid .
- '&oldid=' . $oldid );
- $diffText = '<a href="' .
- $diffLink .
- '">' .
- htmlspecialchars( wfMsgForContent( 'showdiff' ) ) .
- '</a>';
- } elseif ( $diffText === false ) {
- // Error in diff engine, probably a missing revision
- $diffText = "<p>Can't load revision $newid</p>";
- } else {
- // Diff output fine, clean up any illegal UTF-8
- $diffText = UtfNormal::cleanUp( $diffText );
- $diffText = self::applyDiffStyle( $diffText );
- }
- wfProfileOut( __METHOD__."-dodiff" );
+ // Can't diff special pages, unreadable pages or pages with no new revision
+ // to compare against: just return the text.
+ if( $title->getNamespace() < 0 || $accErrors || !$newid ) {
+ wfProfileOut( __METHOD__ );
+ return $completeText;
+ }
+
+ if( $oldid ) {
+ wfProfileIn( __METHOD__."-dodiff" );
+
+ #$diffText = $de->getDiff( wfMsg( 'revisionasof',
+ # $wgLang->timeanddate( $timestamp ),
+ # $wgLang->date( $timestamp ),
+ # $wgLang->time( $timestamp ) ),
+ # wfMsg( 'currentrev' ) );
+
+ // Don't bother generating the diff if we won't be able to show it
+ if ( $wgFeedDiffCutoff > 0 ) {
+ $de = new DifferenceEngine( $title, $oldid, $newid );
+ $diffText = $de->getDiff(
+ wfMsg( 'previousrevision' ), // hack
+ wfMsg( 'revisionasof',
+ $wgLang->timeanddate( $timestamp ),
+ $wgLang->date( $timestamp ),
+ $wgLang->time( $timestamp ) ) );
+ }
+
+ if ( $wgFeedDiffCutoff <= 0 || ( strlen( $diffText ) > $wgFeedDiffCutoff ) ) {
+ // Omit large diffs
+ $diffText = self::getDiffLink( $title, $newid, $oldid );
+ } elseif ( $diffText === false ) {
+ // Error in diff engine, probably a missing revision
+ $diffText = "<p>Can't load revision $newid</p>";
+ } else {
+ // Diff output fine, clean up any illegal UTF-8
+ $diffText = UtfNormal::cleanUp( $diffText );
+ $diffText = self::applyDiffStyle( $diffText );
+ }
+ wfProfileOut( __METHOD__."-dodiff" );
+ } else {
+ $rev = Revision::newFromId( $newid );
+ if( $wgFeedDiffCutoff <= 0 || is_null( $rev ) ) {
+ $newtext = '';
+ } else {
+ $newtext = $rev->getText();
+ }
+ if ( $wgFeedDiffCutoff <= 0 || strlen( $newtext ) > $wgFeedDiffCutoff ) {
+ // Omit large new page diffs, bug 29110
+ $diffText = self::getDiffLink( $title, $newid );
} else {
- $rev = Revision::newFromId( $newid );
- if( is_null( $rev ) ) {
- $newtext = '';
- } else {
- $newtext = $rev->getText();
- }
$diffText = '<p><b>' . wfMsg( 'newpage' ) . '</b></p>' .
'<div>' . nl2br( htmlspecialchars( $newtext ) ) . '</div>';
}
- $completeText .= $diffText;
}
+ $completeText .= $diffText;
wfProfileOut( __METHOD__ );
return $completeText;
}
/**
+ * Generates a diff link. Used when the full diff is not wanted for example
+ * when $wgFeedDiffCutoff is 0.
+ *
+ * @param $title Title object: used to generate the diff URL
+ * @param $newid Integer newid for this diff
+ * @param $oldid Integer|null oldid for the diff. Null means it is a new article
+ */
+ protected static function getDiffLink( Title $title, $newid, $oldid = null ) {
+ $queryParameters = ($oldid == null)
+ ? "diff={$newid}"
+ : "diff={$newid}&oldid={$oldid}" ;
+ $diffUrl = $title->getFullUrl( $queryParameters );
+
+ $diffLink = Html::element( 'a', array( 'href' => $diffUrl ),
+ wfMsgForContent( 'showdiff' ) );
+
+ return $diffLink;
+ }
+
+ /**
* Hacky application of diff styles for the feeds.
* Might be 'cleaner' to use DOM or XSLT or something,
* but *gack* it's a pain in the ass.
*
* @param $text String: diff's HTML output
* @return String: modified HTML
- * @private
*/
public static function applyDiffStyle( $text ) {
$styles = array(
diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php
index 515768ff..11f9aea5 100644
--- a/includes/FileDeleteForm.php
+++ b/includes/FileDeleteForm.php
@@ -8,9 +8,19 @@
*/
class FileDeleteForm {
+ /**
+ * @var Title
+ */
private $title = null;
+
+ /**
+ * @var File
+ */
private $file = null;
+ /**
+ * @var File
+ */
private $oldfile = null;
private $oldimage = '';
@@ -29,30 +39,31 @@ class FileDeleteForm {
* pending authentication, confirmation, etc.
*/
public function execute() {
- global $wgOut, $wgRequest, $wgUser;
- $this->setHeaders();
+ global $wgOut, $wgRequest, $wgUser, $wgUploadMaintenance;
- if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
+ $permissionErrors = $this->title->getUserPermissionsErrors( 'delete', $wgUser );
+ if ( count( $permissionErrors ) ) {
+ throw new PermissionsError( 'delete', $permissionErrors );
}
- $permission_errors = $this->title->getUserPermissionsErrors('delete', $wgUser);
- if (count($permission_errors)>0) {
- $wgOut->showPermissionsErrorPage( $permission_errors );
- return;
+
+ if ( wfReadOnly() ) {
+ throw new ReadOnlyError;
}
+ if ( $wgUploadMaintenance ) {
+ throw new ErrorPageError( 'filedelete-maintenance-title', 'filedelete-maintenance' );
+ }
+
+ $this->setHeaders();
+
$this->oldimage = $wgRequest->getText( 'oldimage', false );
$token = $wgRequest->getText( 'wpEditToken' );
# Flag to hide all contents of the archived revisions
$suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('suppressrevision');
- if( $this->oldimage && !self::isValidOldSpec($this->oldimage) ) {
- $wgOut->showUnexpectedValueError( 'oldimage', htmlspecialchars( $this->oldimage ) );
- return;
- }
- if( $this->oldimage )
+ if( $this->oldimage ) {
$this->oldfile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $this->title, $this->oldimage );
+ }
if( !self::haveDeletableFile($this->file, $this->oldfile, $this->oldimage) ) {
$wgOut->addHTML( $this->prepareMessage( 'filedelete-nofile' ) );
@@ -62,26 +73,38 @@ class FileDeleteForm {
// Perform the deletion if appropriate
if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $token, $this->oldimage ) ) {
- $this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList' );
- $this->DeleteReason = $wgRequest->getText( 'wpReason' );
- $reason = $this->DeleteReasonList;
- if ( $reason != 'other' && $this->DeleteReason != '') {
+ $deleteReasonList = $wgRequest->getText( 'wpDeleteReasonList' );
+ $deleteReason = $wgRequest->getText( 'wpReason' );
+
+ if ( $deleteReasonList == 'other' ) {
+ $reason = $deleteReason;
+ } elseif ( $deleteReason != '' ) {
// Entry from drop down menu + additional comment
- $reason .= wfMsgForContent( 'colon-separator' ) . $this->DeleteReason;
- } elseif ( $reason == 'other' ) {
- $reason = $this->DeleteReason;
+ $reason = $deleteReasonList . wfMsgForContent( 'colon-separator' ) . $deleteReason;
+ } else {
+ $reason = $deleteReasonList;
}
- $status = self::doDelete( $this->title, $this->file, $this->oldimage, $reason, $suppress );
+ $status = self::doDelete( $this->title, $this->file, $this->oldimage, $reason, $suppress, $wgUser );
- if( !$status->isGood() )
+ if( !$status->isGood() ) {
+ $wgOut->addHTML( '<h2>' . $this->prepareMessage( 'filedeleteerror-short' ) . "</h2>\n" );
+ $wgOut->addHTML( '<span class="error">' );
$wgOut->addWikiText( $status->getWikiText( 'filedeleteerror-short', 'filedeleteerror-long' ) );
+ $wgOut->addHTML( '</span>' );
+ }
if( $status->ok ) {
- $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
+ $wgOut->setPageTitle( wfMessage( 'actioncomplete' ) );
$wgOut->addHTML( $this->prepareMessage( 'filedelete-success' ) );
// Return to the main page if we just deleted all versions of the
// file, otherwise go back to the description page
$wgOut->addReturnTo( $this->oldimage ? $this->title : Title::newMainPage() );
+
+ if ( $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn() ) {
+ WatchAction::doWatch( $this->title, $wgUser );
+ } elseif ( $this->title->userIsWatching() ) {
+ WatchAction::doUnwatch( $this->title, $wgUser );
+ }
}
return;
}
@@ -94,17 +117,20 @@ class FileDeleteForm {
* Really delete the file
*
* @param $title Title object
- * @param $file File object
+ * @param File $file: file object
* @param $oldimage String: archive name
* @param $reason String: reason of the deletion
* @param $suppress Boolean: whether to mark all deleted versions as restricted
+ * @param $user User object performing the request
*/
- public static function doDelete( &$title, &$file, &$oldimage, $reason, $suppress ) {
- global $wgUser;
- $article = null;
- $status = Status::newFatal( 'error' );
+ public static function doDelete( &$title, &$file, &$oldimage, $reason, $suppress, User $user = null ) {
+ if ( $user === null ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
if( $oldimage ) {
+ $page = null;
$status = $file->deleteOld( $oldimage, $reason, $suppress );
if( $status->ok ) {
// Need to do a log item
@@ -116,20 +142,17 @@ class FileDeleteForm {
$log->addEntry( 'delete', $title, $logComment );
}
} else {
- $id = $title->getArticleID( Title::GAID_FOR_UPDATE );
- $article = new Article( $title );
+ $status = Status::newFatal( 'cannotdelete',
+ wfEscapeWikiText( $title->getPrefixedText() )
+ );
+ $page = WikiPage::factory( $title );
$dbw = wfGetDB( DB_MASTER );
try {
// 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 );
- }
+ $error = '';
+ if ( $page->doDeleteArticleReal( $reason, $suppress, 0, false, $error, $user ) >= WikiPage::DELETE_SUCCESS ) {
$status = $file->delete( $reason, $suppress );
- if( $status->ok ) {
+ if( $status->isOK() ) {
$dbw->commit();
} else {
$dbw->rollback();
@@ -141,8 +164,10 @@ class FileDeleteForm {
throw $e;
}
}
- if( $status->isGood() )
- wfRunHooks('FileDeleteComplete', array( &$file, &$oldimage, &$article, &$wgUser, &$reason));
+
+ if ( $status->isOK() ) {
+ wfRunHooks( 'FileDeleteComplete', array( &$file, &$oldimage, &$page, &$user, &$reason ) );
+ }
return $status;
}
@@ -170,7 +195,7 @@ class FileDeleteForm {
'id' => 'mw-img-deleteconfirm' ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'filedelete-legend' ) ) .
- Html::hidden( 'wpEditToken', $wgUser->editToken( $this->oldimage ) ) .
+ Html::hidden( 'wpEditToken', $wgUser->getEditToken( $this->oldimage ) ) .
$this->prepareMessage( 'filedelete-intro' ) .
Xml::openElement( 'table', array( 'id' => 'mw-img-deleteconfirm-table' ) ) .
"<tr>
@@ -193,7 +218,7 @@ class FileDeleteForm {
"</td>
</tr>
{$suppress}";
- if( $wgUser->isLoggedIn() ) {
+ if( $wgUser->isLoggedIn() ) {
$form .= "
<tr>
<td></td>
@@ -216,9 +241,8 @@ class FileDeleteForm {
Xml::closeElement( 'form' );
if ( $wgUser->isAllowed( 'editinterface' ) ) {
- $skin = $wgUser->getSkin();
$title = Title::makeTitle( NS_MEDIAWIKI, 'Filedelete-reason-dropdown' );
- $link = $skin->link(
+ $link = Linker::link(
$title,
wfMsgHtml( 'filedelete-edit-reasonlist' ),
array(),
@@ -236,7 +260,7 @@ class FileDeleteForm {
private function showLogEntries() {
global $wgOut;
$wgOut->addHTML( '<h2>' . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
- LogEventsList::showLogExtract( $wgOut, 'delete', $this->title->getPrefixedText() );
+ LogEventsList::showLogExtract( $wgOut, 'delete', $this->title );
}
/**
@@ -270,19 +294,10 @@ class FileDeleteForm {
* Set headers, titles and other bits
*/
private function setHeaders() {
- global $wgOut, $wgUser;
- $wgOut->setPageTitle( wfMsg( 'filedelete', $this->title->getText() ) );
+ global $wgOut;
+ $wgOut->setPageTitle( wfMessage( 'filedelete', $this->title->getText() ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setSubtitle( wfMsg(
- 'filedelete-backlink',
- $wgUser->getSkin()->link(
- $this->title,
- null,
- array(),
- array(),
- array( 'known', 'noclasses' )
- )
- ) );
+ $wgOut->addBacklinkSubtitle( $this->title );
}
/**
@@ -301,6 +316,9 @@ class FileDeleteForm {
* value was provided, does it correspond to an
* existing, local, old version of this file?
*
+ * @param $file File
+ * @param $oldfile File
+ * @param $oldimage File
* @return bool
*/
public static function haveDeletableFile(&$file, &$oldfile, $oldimage) {
diff --git a/includes/ForkController.php b/includes/ForkController.php
index d87dfb1e..9cacef54 100644
--- a/includes/ForkController.php
+++ b/includes/ForkController.php
@@ -34,7 +34,7 @@ class ForkController {
public function __construct( $numProcs, $flags = 0 ) {
if ( php_sapi_name() != 'cli' ) {
- throw new MWException( "MultiProcess cannot be used from the web." );
+ throw new MWException( "ForkController cannot be used from the web." );
}
$this->procsToStart = $numProcs;
$this->flags = $flags;
@@ -119,13 +119,13 @@ class ForkController {
// Don't share DB or memcached connections
wfGetLBFactory()->destroyInstance();
ObjectCache::clear();
- unset( $wgMemc );
+ $wgMemc = null;
}
/**
* Fork a number of worker processes.
*
- * return string
+ * @return string
*/
protected function forkWorkers( $numProcs ) {
$this->prepareEnvironment();
diff --git a/includes/FormOptions.php b/includes/FormOptions.php
index b668ff46..ccc87d8a 100644
--- a/includes/FormOptions.php
+++ b/includes/FormOptions.php
@@ -5,10 +5,10 @@
*
* Copyright © 2008, Niklas Laxstiröm
*
- * Copyright © 2011, Ashar Voultoiz
+ * Copyright © 2011, Antoine Musso
*
* @author Niklas Laxström
- * @author Ashar Voultoiz
+ * @author Antoine Musso
*/
class FormOptions implements ArrayAccess {
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index 8ed79c40..52cd46a5 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -81,6 +81,9 @@ if ( !function_exists( 'istainted' ) ) {
/**
* Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
+ * @param $a array
+ * @param $b array
+ * @return array
*/
function wfArrayDiff2( $a, $b ) {
return array_udiff( $a, $b, 'wfArrayDiff2_cmp' );
@@ -164,7 +167,7 @@ function wfArrayMerge( $array1/* ... */ ) {
* array( array( 'x' ) ),
* array( array( 'x', '2' ) ),
* array( array( 'x' ) ),
- * array( array( 'y') )
+ * array( array( 'y' ) )
* );
* returns:
* array(
@@ -296,9 +299,9 @@ function wfUrlencode( $s ) {
static $needle;
if ( is_null( $s ) ) {
$needle = null;
- return;
+ 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 ) ) {
@@ -319,7 +322,7 @@ 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.
+ * Options set to null or false will not be output.
*
* @param $array1 Array ( String|Array )
* @param $array2 Array ( String|Array )
@@ -333,7 +336,7 @@ function wfArrayToCGI( $array1, $array2 = null, $prefix = '' ) {
$cgi = '';
foreach ( $array1 as $key => $value ) {
- if ( $value !== '' ) {
+ if ( !is_null($value) && $value !== false ) {
if ( $cgi != '' ) {
$cgi .= '&';
}
@@ -366,8 +369,7 @@ function wfArrayToCGI( $array1, $array2 = null, $prefix = '' ) {
* 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.
+ * arrays. Of course, keys and values are urldecode()d.
*
* @param $query String: query string
* @return array Array version of input
@@ -382,7 +384,13 @@ function wfCgiToArray( $query ) {
if ( $bit === '' ) {
continue;
}
- list( $key, $value ) = explode( '=', $bit );
+ if ( strpos( $bit, '=' ) === false ) {
+ // Pieces like &qwerty become 'qwerty' => '' (at least this is what php does)
+ $key = $bit;
+ $value = '';
+ } else {
+ list( $key, $value ) = explode( '=', $bit );
+ }
$key = urldecode( $key );
$value = urldecode( $value );
if ( strpos( $key, '[' ) !== false ) {
@@ -444,8 +452,11 @@ function wfAppendQuery( $url, $query ) {
* 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
+ * @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, current-path-relative URL or false if
+ * no valid URL can be constructed
*/
function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
global $wgServer, $wgCanonicalServer, $wgInternalServer;
@@ -477,21 +488,170 @@ function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
$defaultProtoWithoutSlashes = substr( $defaultProto, 0, -2 );
- if( substr( $url, 0, 2 ) == '//' ) {
- return $defaultProtoWithoutSlashes . $url;
- } elseif( substr( $url, 0, 1 ) == '/' ) {
+ if ( substr( $url, 0, 2 ) == '//' ) {
+ $url = $defaultProtoWithoutSlashes . $url;
+ } elseif ( substr( $url, 0, 1 ) == '/' ) {
// If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes, otherwise leave it alone
- return ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url;
- } else {
+ $url = ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url;
+ }
+
+ $bits = wfParseUrl( $url );
+ if ( $bits && isset( $bits['path'] ) ) {
+ $bits['path'] = wfRemoveDotSegments( $bits['path'] );
+ return wfAssembleUrl( $bits );
+ } elseif ( $bits ) {
+ # No path to expand
return $url;
+ } elseif ( substr( $url, 0, 1 ) != '/' ) {
+ # URL is a relative path
+ return wfRemoveDotSegments( $url );
+ }
+
+ # Expanded URL is not valid.
+ return false;
+}
+
+/**
+ * This function will reassemble a URL parsed with wfParseURL. This is useful
+ * if you need to edit part of a URL and put it back together.
+ *
+ * This is the basic structure used (brackets contain keys for $urlParts):
+ * [scheme][delimiter][user]:[pass]@[host]:[port][path]?[query]#[fragment]
+ *
+ * @todo Need to integrate this into wfExpandUrl (bug 32168)
+ *
+ * @since 1.19
+ * @param $urlParts Array URL parts, as output from wfParseUrl
+ * @return string URL assembled from its component parts
+ */
+function wfAssembleUrl( $urlParts ) {
+ $result = '';
+
+ if ( isset( $urlParts['delimiter'] ) ) {
+ if ( isset( $urlParts['scheme'] ) ) {
+ $result .= $urlParts['scheme'];
+ }
+
+ $result .= $urlParts['delimiter'];
+ }
+
+ if ( isset( $urlParts['host'] ) ) {
+ if ( isset( $urlParts['user'] ) ) {
+ $result .= $urlParts['user'];
+ if ( isset( $urlParts['pass'] ) ) {
+ $result .= ':' . $urlParts['pass'];
+ }
+ $result .= '@';
+ }
+
+ $result .= $urlParts['host'];
+
+ if ( isset( $urlParts['port'] ) ) {
+ $result .= ':' . $urlParts['port'];
+ }
+ }
+
+ if ( isset( $urlParts['path'] ) ) {
+ $result .= $urlParts['path'];
+ }
+
+ if ( isset( $urlParts['query'] ) ) {
+ $result .= '?' . $urlParts['query'];
+ }
+
+ if ( isset( $urlParts['fragment'] ) ) {
+ $result .= '#' . $urlParts['fragment'];
+ }
+
+ return $result;
+}
+
+/**
+ * Remove all dot-segments in the provided URL path. For example,
+ * '/a/./b/../c/' becomes '/a/c/'. For details on the algorithm, please see
+ * RFC3986 section 5.2.4.
+ *
+ * @todo Need to integrate this into wfExpandUrl (bug 32168)
+ *
+ * @param $urlPath String URL path, potentially containing dot-segments
+ * @return string URL path with all dot-segments removed
+ */
+function wfRemoveDotSegments( $urlPath ) {
+ $output = '';
+ $inputOffset = 0;
+ $inputLength = strlen( $urlPath );
+
+ while ( $inputOffset < $inputLength ) {
+ $prefixLengthOne = substr( $urlPath, $inputOffset, 1 );
+ $prefixLengthTwo = substr( $urlPath, $inputOffset, 2 );
+ $prefixLengthThree = substr( $urlPath, $inputOffset, 3 );
+ $prefixLengthFour = substr( $urlPath, $inputOffset, 4 );
+ $trimOutput = false;
+
+ if ( $prefixLengthTwo == './' ) {
+ # Step A, remove leading "./"
+ $inputOffset += 2;
+ } elseif ( $prefixLengthThree == '../' ) {
+ # Step A, remove leading "../"
+ $inputOffset += 3;
+ } elseif ( ( $prefixLengthTwo == '/.' ) && ( $inputOffset + 2 == $inputLength ) ) {
+ # Step B, replace leading "/.$" with "/"
+ $inputOffset += 1;
+ $urlPath[$inputOffset] = '/';
+ } elseif ( $prefixLengthThree == '/./' ) {
+ # Step B, replace leading "/./" with "/"
+ $inputOffset += 2;
+ } elseif ( $prefixLengthThree == '/..' && ( $inputOffset + 3 == $inputLength ) ) {
+ # Step C, replace leading "/..$" with "/" and
+ # remove last path component in output
+ $inputOffset += 2;
+ $urlPath[$inputOffset] = '/';
+ $trimOutput = true;
+ } elseif ( $prefixLengthFour == '/../' ) {
+ # Step C, replace leading "/../" with "/" and
+ # remove last path component in output
+ $inputOffset += 3;
+ $trimOutput = true;
+ } elseif ( ( $prefixLengthOne == '.' ) && ( $inputOffset + 1 == $inputLength ) ) {
+ # Step D, remove "^.$"
+ $inputOffset += 1;
+ } elseif ( ( $prefixLengthTwo == '..' ) && ( $inputOffset + 2 == $inputLength ) ) {
+ # Step D, remove "^..$"
+ $inputOffset += 2;
+ } else {
+ # Step E, move leading path segment to output
+ if ( $prefixLengthOne == '/' ) {
+ $slashPos = strpos( $urlPath, '/', $inputOffset + 1 );
+ } else {
+ $slashPos = strpos( $urlPath, '/', $inputOffset );
+ }
+ if ( $slashPos === false ) {
+ $output .= substr( $urlPath, $inputOffset );
+ $inputOffset = $inputLength;
+ } else {
+ $output .= substr( $urlPath, $inputOffset, $slashPos - $inputOffset );
+ $inputOffset += $slashPos - $inputOffset;
+ }
+ }
+
+ if ( $trimOutput ) {
+ $slashPos = strrpos( $output, '/' );
+ if ( $slashPos === false ) {
+ $output = '';
+ } else {
+ $output = substr( $output, 0, $slashPos );
+ }
+ }
}
+
+ return $output;
}
/**
* 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
+ * DO NOT USE this directly, use wfUrlProtocolsWithoutProtRel() instead
* @return String
*/
function wfUrlProtocols( $includeProtocolRelative = true ) {
@@ -537,6 +697,7 @@ function wfUrlProtocols( $includeProtocolRelative = true ) {
* Like wfUrlProtocols(), but excludes '//' from the protocol list. Use this if
* you need a regex that matches all URL protocols but does not match protocol-
* relative URLs
+ * @return String
*/
function wfUrlProtocolsWithoutProtRel() {
return wfUrlProtocols( false );
@@ -554,7 +715,7 @@ function wfUrlProtocolsWithoutProtRel() {
*/
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 ) == '//';
@@ -564,7 +725,9 @@ function wfParseUrl( $url ) {
wfSuppressWarnings();
$bits = parse_url( $url );
wfRestoreWarnings();
- if ( !$bits ) {
+ // parse_url() returns an array without scheme for some invalid URLs, e.g.
+ // parse_url("%0Ahttp://example.com") == array( 'host' => '%0Ahttp', 'path' => 'example.com' )
+ if ( !$bits || !isset( $bits['scheme'] ) ) {
return false;
}
@@ -592,7 +755,7 @@ function wfParseUrl( $url ) {
$bits['path'] = '/' . $bits['path'];
}
}
-
+
// If the URL was protocol-relative, fix scheme and delimiter
if ( $wasRelative ) {
$bits['scheme'] = '';
@@ -717,6 +880,8 @@ function wfDebug( $text, $logonly = false ) {
wfErrorLog( $text, $wgDebugLogFile );
}
}
+
+ MWDebug::debugMsg( $text );
}
/**
@@ -747,20 +912,15 @@ function wfIsDebugRawPage() {
* @return string
*/
function wfDebugTimer() {
- global $wgDebugTimestamps;
+ global $wgDebugTimestamps, $wgRequestTime;
+
if ( !$wgDebugTimestamps ) {
return '';
}
- static $start = null;
- if ( $start === null ) {
- $start = microtime( true );
- $prefix = "\n$start";
- } else {
- $prefix = sprintf( "%6.4f", microtime( true ) - $start );
- }
-
- return $prefix . ' ';
+ $prefix = sprintf( "%6.4f", microtime( true ) - $wgRequestTime );
+ $mem = sprintf( "%5.1fM", ( memory_get_usage( true ) / ( 1024 * 1024 ) ) );
+ return "$prefix $mem ";
}
/**
@@ -788,16 +948,12 @@ function wfDebugMem( $exact = false ) {
* log file is specified, (default true)
*/
function wfDebugLog( $logGroup, $text, $public = true ) {
- global $wgDebugLogGroups, $wgShowHostnames;
+ global $wgDebugLogGroups;
$text = trim( $text ) . "\n";
if( isset( $wgDebugLogGroups[$logGroup] ) ) {
$time = wfTimestamp( TS_DB );
$wiki = wfWikiID();
- if ( $wgShowHostnames ) {
- $host = wfHostname();
- } else {
- $host = '';
- }
+ $host = wfHostname();
if ( wfRunHooks( 'Debug', array( $text, $logGroup ) ) ) {
wfErrorLog( "$time $host $wiki: $text", $wgDebugLogGroups[$logGroup] );
}
@@ -812,15 +968,98 @@ function wfDebugLog( $logGroup, $text, $public = true ) {
* @param $text String: database error message.
*/
function wfLogDBError( $text ) {
- global $wgDBerrorLog, $wgDBname;
+ global $wgDBerrorLog;
if ( $wgDBerrorLog ) {
- $host = trim(`hostname`);
- $text = date( 'D M j G:i:s T Y' ) . "\t$host\t$wgDBname\t$text";
+ $host = wfHostname();
+ $wiki = wfWikiID();
+ $text = date( 'D M j G:i:s T Y' ) . "\t$host\t$wiki\t$text";
wfErrorLog( $text, $wgDBerrorLog );
}
}
/**
+ * Throws a warning that $function is deprecated
+ *
+ * @param $function String
+ * @param $version String|false: Added in 1.19.
+ * @param $component String|false: Added in 1.19.
+ *
+ * @return null
+ */
+function wfDeprecated( $function, $version = false, $component = false ) {
+ static $functionsWarned = array();
+
+ MWDebug::deprecated( $function, $version, $component );
+
+ if ( !isset( $functionsWarned[$function] ) ) {
+ $functionsWarned[$function] = true;
+
+ if ( $version ) {
+ global $wgDeprecationReleaseLimit;
+
+ if ( $wgDeprecationReleaseLimit && $component === false ) {
+ # Strip -* off the end of $version so that branches can use the
+ # format #.##-branchname to avoid issues if the branch is merged into
+ # a version of MediaWiki later than what it was branched from
+ $comparableVersion = preg_replace( '/-.*$/', '', $version );
+
+ # If the comparableVersion is larger than our release limit then
+ # skip the warning message for the deprecation
+ if ( version_compare( $wgDeprecationReleaseLimit, $comparableVersion, '<' ) ) {
+ return;
+ }
+ }
+
+ $component = $component === false ? 'MediaWiki' : $component;
+ wfWarn( "Use of $function was deprecated in $component $version.", 2 );
+ } else {
+ wfWarn( "Use of $function is deprecated.", 2 );
+ }
+ }
+}
+
+/**
+ * Send a warning either to the debug log or in a PHP error depending on
+ * $wgDevelopmentWarnings
+ *
+ * @param $msg String: message to send
+ * @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;
+
+ MWDebug::warning( $msg, $callerOffset + 2 );
+
+ $callers = wfDebugBacktrace();
+ if ( isset( $callers[$callerOffset + 1] ) ) {
+ $callerfunc = $callers[$callerOffset + 1];
+ $callerfile = $callers[$callerOffset];
+ if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
+ $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
+ } else {
+ $file = '(internal function)';
+ }
+ $func = '';
+ if ( isset( $callerfunc['class'] ) ) {
+ $func .= $callerfunc['class'] . '::';
+ }
+ if ( isset( $callerfunc['function'] ) ) {
+ $func .= $callerfunc['function'];
+ }
+ $msg .= " [Called from $func in $file]";
+ }
+
+ if ( $wgDevelopmentWarnings ) {
+ trigger_error( $msg, $level );
+ } else {
+ wfDebug( "$msg\n" );
+ }
+}
+
+/**
* Log to a file without getting "file size exceeded" signals.
*
* Can also log to TCP or UDP with the syntax udp://host:port/prefix. This will
@@ -855,21 +1094,22 @@ function wfErrorLog( $text, $file ) {
$text = preg_replace( '/^/m', $prefix . ' ', $text );
// Limit to 64KB
- if ( strlen( $text ) > 65534 ) {
- $text = substr( $text, 0, 65534 );
+ if ( strlen( $text ) > 65506 ) {
+ $text = substr( $text, 0, 65506 );
}
if ( substr( $text, -1 ) != "\n" ) {
$text .= "\n";
}
- } elseif ( strlen( $text ) > 65535 ) {
- $text = substr( $text, 0, 65535 );
+ } elseif ( strlen( $text ) > 65507 ) {
+ $text = substr( $text, 0, 65507 );
}
$sock = socket_create( $domain, SOCK_DGRAM, SOL_UDP );
if ( !$sock ) {
return;
}
+
socket_sendto( $sock, $text, strlen( $text ), 0, $host, $port );
socket_close( $sock );
} else {
@@ -899,8 +1139,7 @@ function wfLogProfilingData() {
// Get total page request time and only show pages that longer than
// $wgProfileLimit time (default is 0)
- $now = wfTime();
- $elapsed = $now - $wgRequestTime;
+ $elapsed = microtime( true ) - $wgRequestTime;
if ( $elapsed <= $wgProfileLimit ) {
return;
}
@@ -962,6 +1201,9 @@ function wfReadOnly() {
return (bool)$wgReadOnly;
}
+/**
+ * @return bool
+ */
function wfReadOnlyReason() {
global $wgReadOnly;
wfReadOnly();
@@ -977,9 +1219,10 @@ function wfReadOnlyReason() {
* a valid code create a language for that language, if
* it is a string but not a valid code then make a basic
* language object
- * - a boolean: if it's false then use the current users
- * language (as a fallback for the old parameter
- * functionality), or if it is true then use the wikis
+ * - a boolean: if it's false then use the global object for
+ * the current user's language (as a fallback for the old parameter
+ * functionality), or if it is true then use global object
+ * for the wiki's content language.
* @return Language object
*/
function wfGetLangObj( $langcode = false ) {
@@ -1023,6 +1266,7 @@ function wfGetLangObj( $langcode = false ) {
* @return Language
*/
function wfUILang() {
+ wfDeprecated( __METHOD__, '1.18' );
global $wgLang;
return $wgLang;
}
@@ -1249,7 +1493,8 @@ function wfMsgWikiHtml( $key ) {
$args = func_get_args();
array_shift( $args );
return wfMsgReplaceArgs(
- MessageCache::singleton()->parse( wfMsgGetKey( $key ), null, /* can't be set to false */ true )->getText(),
+ MessageCache::singleton()->parse( wfMsgGetKey( $key ), null,
+ /* can't be set to false */ true, /* interface */ true )->getText(),
$args );
}
@@ -1311,13 +1556,18 @@ function wfMsgExt( $key, $options ) {
}
$messageCache = MessageCache::singleton();
- if( in_array( 'parse', $options, true ) ) {
- $string = $messageCache->parse( $string, null, true, !$forContent, $langCodeObj )->getText();
- } elseif ( in_array( 'parseinline', $options, true ) ) {
- $string = $messageCache->parse( $string, null, true, !$forContent, $langCodeObj )->getText();
- $m = array();
- if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
- $string = $m[1];
+ $parseInline = in_array( 'parseinline', $options, true );
+ if( in_array( 'parse', $options, true ) || $parseInline ) {
+ $string = $messageCache->parse( $string, null, true, !$forContent, $langCodeObj );
+ if ( $string instanceof ParserOutput ) {
+ $string = $string->getText();
+ }
+
+ if ( $parseInline ) {
+ $m = array();
+ if( preg_match( '/^<p>(.*)\n?<\/p>\n?$/sU', $string, $m ) ) {
+ $string = $m[1];
+ }
}
} elseif ( in_array( 'parsemag', $options, true ) ) {
$string = $messageCache->transform( $string,
@@ -1353,7 +1603,7 @@ function wfEmptyMsg( $key ) {
* Throw a debugging exception. This function previously once exited the process,
* but now throws an exception instead, with similar results.
*
- * @param $msg String: message shown when dieing.
+ * @param $msg String: message shown when dying.
*/
function wfDebugDieBacktrace( $msg = '' ) {
throw new MWException( $msg );
@@ -1397,8 +1647,7 @@ function wfHostname() {
function wfReportTime() {
global $wgRequestTime, $wgShowHostnames;
- $now = wfTime();
- $elapsed = $now - $wgRequestTime;
+ $elapsed = microtime( true ) - $wgRequestTime;
return $wgShowHostnames
? sprintf( '<!-- Served by %s in %01.3f secs. -->', wfHostname(), $elapsed )
@@ -1575,80 +1824,40 @@ function wfShowingResults( $offset, $limit ) {
* @param $query String: optional URL query parameter string
* @param $atend Bool: optional param for specified if this is the last page
* @return String
+ * @deprecated in 1.19; use Language::viewPrevNext() instead
*/
function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
+ wfDeprecated( __METHOD__, '1.19' );
+
global $wgLang;
- $fmtLimit = $wgLang->formatNum( $limit );
- // @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 );
- # Get prev/next link title text
- $pTitle = wfMsgExt( 'prevn-title', array( 'parsemag', 'escape' ), $fmtLimit );
- $nTitle = wfMsgExt( 'nextn-title', array( 'parsemag', 'escape' ), $fmtLimit );
- # Fetch the title object
+
+ $query = wfCgiToArray( $query );
+
if( is_object( $link ) ) {
- $title =& $link;
+ $title = $link;
} else {
$title = Title::newFromText( $link );
if( is_null( $title ) ) {
return false;
}
}
- # Make 'previous' link
- if( 0 != $offset ) {
- $po = $offset - $limit;
- $po = max( $po, 0 );
- $q = "limit={$limit}&offset={$po}";
- if( $query != '' ) {
- $q .= '&' . $query;
- }
- $plink = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$pTitle}\" class=\"mw-prevlink\">{$prev}</a>";
- } else {
- $plink = $prev;
- }
- # Make 'next' link
- $no = $offset + $limit;
- $q = "limit={$limit}&offset={$no}";
- if( $query != '' ) {
- $q .= '&' . $query;
- }
- if( $atend ) {
- $nlink = $next;
- } else {
- $nlink = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$nTitle}\" class=\"mw-nextlink\">{$next}</a>";
- }
- # Make links to set number of items per page
- $nums = $wgLang->pipeList( array(
- wfNumLink( $offset, 20, $title, $query ),
- wfNumLink( $offset, 50, $title, $query ),
- wfNumLink( $offset, 100, $title, $query ),
- wfNumLink( $offset, 250, $title, $query ),
- wfNumLink( $offset, 500, $title, $query )
- ) );
- return wfMsgHtml( 'viewprevnext', $plink, $nlink, $nums );
+
+ return $wgLang->viewPrevNext( $title, $offset, $limit, $query, $atend );
}
/**
- * Generate links for (20|50|100...) items-per-page links
+ * Make a list item, used by various special pages
*
- * @param $offset String
- * @param $limit Integer
- * @param $title Title
- * @param $query String: optional URL query parameter string
+ * @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
+ * @deprecated since 1.19; use Language::specialList() instead
*/
-function wfNumLink( $offset, $limit, $title, $query = '' ) {
+function wfSpecialList( $page, $details, $oppositedm = true ) {
global $wgLang;
- if( $query == '' ) {
- $q = '';
- } else {
- $q = $query.'&';
- }
- $q .= "limit={$limit}&offset={$offset}";
- $fmtLimit = $wgLang->formatNum( $limit );
- $lTitle = wfMsgExt( 'shown-title', array( 'parsemag', 'escape' ), $limit );
- $s = '<a href="' . $title->escapeLocalURL( $q ) . "\" title=\"{$lTitle}\" class=\"mw-numlink\">{$fmtLimit}</a>";
- return $s;
+ return $wgLang->specialList( $page, $details, $oppositedm );
}
/**
@@ -1675,7 +1884,7 @@ function wfClientAcceptsGzip( $force = false ) {
$result = false;
return $result;
}
- wfDebug( " accepts gzip\n" );
+ wfDebug( "wfClientAcceptsGzip: client accepts gzip.\n" );
$result = true;
}
}
@@ -1750,6 +1959,8 @@ function wfSetVar( &$dest, $source, $force = false ) {
* @param $dest Int
* @param $bit Int
* @param $state Bool
+ *
+ * @return bool
*/
function wfSetBit( &$dest, $bit, $state = true ) {
$temp = (bool)( $dest & $bit );
@@ -1764,217 +1975,6 @@ function wfSetBit( &$dest, $bit, $state = true ) {
}
/**
- * 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();
-
- $args = func_get_args();
- $first = true;
- $retVal = '';
- foreach ( $args as $arg ) {
- if ( !$first ) {
- $retVal .= ' ';
- } else {
- $first = false;
- }
-
- if ( wfIsWindows() ) {
- // 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 = '';
- $iteration = 0;
- foreach ( $tokens as $token ) {
- if ( $iteration % 2 == 1 ) {
- // Delimiter, a double quote preceded by zero or more slashes
- $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
- } elseif ( $iteration % 4 == 2 ) {
- // ^ in $token will be outside quotes, need to be escaped
- $arg .= str_replace( '^', '^^', $token );
- } else { // $iteration % 4 == 0
- // ^ in $token will appear inside double quotes, so leave as is
- $arg .= $token;
- }
- $iteration++;
- }
- // Double the backslashes before the end of the string, because
- // we will soon add a quote
- $m = array();
- if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
- $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
- }
-
- // Add surrounding quotes
- $retVal .= '"' . $arg . '"';
- } else {
- $retVal .= escapeshellarg( $arg );
- }
- }
- return $retVal;
-}
-
-/**
- * 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;
-
- # This check may also protect against code injection in
- # case of broken installations.
- wfSuppressWarnings();
- $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
- wfRestoreWarnings();
-
- if( !$haveDiff3 ) {
- wfDebug( "diff3 not found\n" );
- return false;
- }
-
- # Make temporary files
- $td = wfTempDir();
- $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
- $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
- $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
-
- fwrite( $oldtextFile, $old );
- fclose( $oldtextFile );
- fwrite( $mytextFile, $mine );
- fclose( $mytextFile );
- fwrite( $yourtextFile, $yours );
- fclose( $yourtextFile );
-
- # Check for a conflict
- $cmd = $wgDiff3 . ' -a --overlap-only ' .
- wfEscapeShellArg( $mytextName ) . ' ' .
- wfEscapeShellArg( $oldtextName ) . ' ' .
- wfEscapeShellArg( $yourtextName );
- $handle = popen( $cmd, 'r' );
-
- if( fgets( $handle, 1024 ) ) {
- $conflict = true;
- } else {
- $conflict = false;
- }
- pclose( $handle );
-
- # Merge differences
- $cmd = $wgDiff3 . ' -a -e --merge ' .
- wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
- $handle = popen( $cmd, 'r' );
- $result = '';
- do {
- $data = fread( $handle, 8192 );
- if ( strlen( $data ) == 0 ) {
- break;
- }
- $result .= $data;
- } while ( true );
- pclose( $handle );
- unlink( $mytextName );
- unlink( $oldtextName );
- unlink( $yourtextName );
-
- if ( $result === '' && $old !== '' && !$conflict ) {
- wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
- $conflict = true;
- }
- return !$conflict;
-}
-
-/**
- * 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.
- * @return String: unified diff of $before and $after
- */
-function wfDiff( $before, $after, $params = '-u' ) {
- if ( $before == $after ) {
- return '';
- }
-
- global $wgDiff;
- wfSuppressWarnings();
- $haveDiff = $wgDiff && file_exists( $wgDiff );
- wfRestoreWarnings();
-
- # This check may also protect against code injection in
- # case of broken installations.
- if( !$haveDiff ) {
- wfDebug( "diff executable not found\n" );
- $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
- $format = new UnifiedDiffFormatter();
- return $format->format( $diffs );
- }
-
- # Make temporary files
- $td = wfTempDir();
- $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
- $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
-
- fwrite( $oldtextFile, $before );
- fclose( $oldtextFile );
- fwrite( $newtextFile, $after );
- fclose( $newtextFile );
-
- // Get the diff of the two files
- $cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
-
- $h = popen( $cmd, 'r' );
-
- $diff = '';
-
- do {
- $data = fread( $h, 8192 );
- if ( strlen( $data ) == 0 ) {
- break;
- }
- $diff .= $data;
- } while ( true );
-
- // Clean up
- pclose( $h );
- unlink( $oldtextName );
- unlink( $newtextName );
-
- // Kill the --- and +++ lines. They're not useful.
- $diff_lines = explode( "\n", $diff );
- if ( strpos( $diff_lines[0], '---' ) === 0 ) {
- unset( $diff_lines[0] );
- }
- if ( strpos( $diff_lines[1], '+++' ) === 0 ) {
- unset( $diff_lines[1] );
- }
-
- $diff = implode( "\n", $diff_lines );
-
- return $diff;
-}
-
-/**
* A wrapper around the PHP function var_export().
* Either print it or add it to the regular output ($wgOut).
*
@@ -2005,7 +2005,7 @@ function wfHttpError( $code, $label, $desc ) {
$wgOut->sendCacheControl();
header( 'Content-type: text/html; charset=utf-8' );
- print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">".
+ print "<!doctype html>" .
'<html><head><title>' .
htmlspecialchars( $label ) .
'</title></head><body><h1>' .
@@ -2161,7 +2161,7 @@ function mimeTypeMatch( $type, $avail ) {
function wfNegotiateType( $cprefs, $sprefs ) {
$combine = array();
- foreach( array_keys($sprefs) as $type ) {
+ foreach( array_keys( $sprefs ) as $type ) {
$parts = explode( '/', $type );
if( $parts[1] != '*' ) {
$ckey = mimeTypeMatch( $type, $cprefs );
@@ -2213,7 +2213,7 @@ function wfSuppressWarnings( $end = false ) {
} else {
if ( !$suppressCount ) {
// E_DEPRECATED is undefined in PHP 5.2
- if( !defined( 'E_DEPRECATED' ) ){
+ 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 ) );
@@ -2313,7 +2313,7 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
} elseif ( preg_match( '/^-?\d{1,13}$/D', $ts ) ) {
# TS_UNIX
$uts = $ts;
- $strtime = "@$ts"; // Undocumented?
+ $strtime = "@$ts"; // http://php.net/manual/en/datetime.formats.compound.php
} elseif ( preg_match( '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}.\d{6}$/', $ts ) ) {
# TS_ORACLE // session altered to DD-MM-YYYY HH24:MI:SS.FF6
$strtime = preg_replace( '/(\d\d)\.(\d\d)\.(\d\d)(\.(\d+))?/', "$1:$2:$3",
@@ -2326,7 +2326,7 @@ function wfTimestamp( $outputtype = TS_UNIX, $ts = 0 ) {
# TS_POSTGRES
} elseif ( preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.*\d* GMT$/', $ts, $da ) ) {
# TS_POSTGRES
- } elseif (preg_match('/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.\d\d\d$/',$ts,$da)) {
+ } elseif (preg_match( '/^(\d{4})\-(\d\d)\-(\d\d) (\d\d):(\d\d):(\d\d)\.\d\d\d$/', $ts, $da ) ) {
# TS_DB2
} elseif ( preg_match( '/^[ \t\r\n]*([A-Z][a-z]{2},[ \t\r\n]*)?' . # Day of week
'\d\d?[ \t\r\n]*[A-Z][a-z]{2}[ \t\r\n]*\d{2}(?:\d{2})?' . # dd Mon yyyy
@@ -2508,8 +2508,12 @@ function wfTempDir() {
function wfMkdirParents( $dir, $mode = null, $caller = null ) {
global $wgDirectoryMode;
+ if ( FileBackend::isStoragePath( $dir ) ) { // sanity
+ throw new MWException( __FUNCTION__ . " given storage path `$dir`.");
+ }
+
if ( !is_null( $caller ) ) {
- wfDebug( "$caller: called wfMkdirParents($dir)" );
+ wfDebug( "$caller: called wfMkdirParents($dir)\n" );
}
if( strval( $dir ) === '' || file_exists( $dir ) ) {
@@ -2586,6 +2590,29 @@ function wfIncrStats( $key, $count = 1 ) {
}
/**
+ * Remove a directory and all its content.
+ * Does not hide error.
+ */
+function wfRecursiveRemoveDir( $dir ) {
+ wfDebug( __FUNCTION__ . "( $dir )\n" );
+ // 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 );
+ }
+}
+
+/**
* @param $nr Mixed: the number to format
* @param $acc Integer: the number of digits after the decimal point, default 2
* @param $round Boolean: whether or not to round the value, default true
@@ -2612,23 +2639,6 @@ function in_string( $needle, $str, $insensitive = false ) {
}
/**
- * 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 wfSpecialList( $page, $details, $oppositedm = true ) {
- global $wgLang;
- $dirmark = ( $oppositedm ? $wgLang->getDirMark( true ) : '' ) .
- $wgLang->getDirMark();
- $details = $details ? $dirmark . " ($details)" : '';
- return $page . $details;
-}
-
-/**
* Safety wrapper around ini_get() for boolean settings.
* The values returned from ini_get() are pre-normalized for settings
* set via php.ini or php_flag/php_admin_flag... but *not*
@@ -2696,6 +2706,70 @@ function wfDl( $extension, $fileName = null ) {
}
/**
+ * 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();
+
+ $args = func_get_args();
+ $first = true;
+ $retVal = '';
+ foreach ( $args as $arg ) {
+ if ( !$first ) {
+ $retVal .= ' ';
+ } else {
+ $first = false;
+ }
+
+ if ( wfIsWindows() ) {
+ // 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 = '';
+ $iteration = 0;
+ foreach ( $tokens as $token ) {
+ if ( $iteration % 2 == 1 ) {
+ // Delimiter, a double quote preceded by zero or more slashes
+ $arg .= str_replace( '\\', '\\\\', substr( $token, 0, -1 ) ) . '\\"';
+ } elseif ( $iteration % 4 == 2 ) {
+ // ^ in $token will be outside quotes, need to be escaped
+ $arg .= str_replace( '^', '^^', $token );
+ } else { // $iteration % 4 == 0
+ // ^ in $token will appear inside double quotes, so leave as is
+ $arg .= $token;
+ }
+ $iteration++;
+ }
+ // Double the backslashes before the end of the string, because
+ // we will soon add a quote
+ $m = array();
+ if ( preg_match( '/^(.*?)(\\\\+)$/', $arg, $m ) ) {
+ $arg = $m[1] . str_replace( '\\', '\\\\', $m[2] );
+ }
+
+ // Add surrounding quotes
+ $retVal .= '"' . $arg . '"';
+ } else {
+ $retVal .= escapeshellarg( $arg );
+ }
+ }
+ return $retVal;
+}
+
+/**
* Execute a shell command, with time and memory limits mirrored from the PHP
* configuration if supported.
* @param $cmd String Command line, properly escaped for shell.
@@ -2805,6 +2879,178 @@ function wfInitShellLocale() {
}
/**
+ * Generate a shell-escaped command line string to run a maintenance script.
+ * Note that $parameters should be a flat array and an option with an argument
+ * should consist of two consecutive items in the array (do not use "--option value").
+ * @param $script string MediaWiki maintenance script path
+ * @param $parameters Array Arguments and options to the script
+ * @param $options Array Associative array of options:
+ * 'php': The path to the php executable
+ * 'wrapper': Path to a PHP wrapper to handle the maintenance script
+ * @return Array
+ */
+function wfShellMaintenanceCmd( $script, array $parameters = array(), array $options = array() ) {
+ global $wgPhpCli;
+ // Give site config file a chance to run the script in a wrapper.
+ // The caller may likely want to call wfBasename() on $script.
+ wfRunHooks( 'wfShellMaintenanceCmd', array( &$script, &$parameters, &$options ) );
+ $cmd = isset( $options['php'] ) ? array( $options['php'] ) : array( $wgPhpCli );
+ if ( isset( $options['wrapper'] ) ) {
+ $cmd[] = $options['wrapper'];
+ }
+ $cmd[] = $script;
+ // Escape each parameter for shell
+ return implode( " ", array_map( 'wfEscapeShellArg', array_merge( $cmd, $parameters ) ) );
+}
+
+/**
+ * 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;
+
+ # This check may also protect against code injection in
+ # case of broken installations.
+ wfSuppressWarnings();
+ $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
+ wfRestoreWarnings();
+
+ if( !$haveDiff3 ) {
+ wfDebug( "diff3 not found\n" );
+ return false;
+ }
+
+ # Make temporary files
+ $td = wfTempDir();
+ $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
+ $mytextFile = fopen( $mytextName = tempnam( $td, 'merge-mine-' ), 'w' );
+ $yourtextFile = fopen( $yourtextName = tempnam( $td, 'merge-your-' ), 'w' );
+
+ fwrite( $oldtextFile, $old );
+ fclose( $oldtextFile );
+ fwrite( $mytextFile, $mine );
+ fclose( $mytextFile );
+ fwrite( $yourtextFile, $yours );
+ fclose( $yourtextFile );
+
+ # Check for a conflict
+ $cmd = $wgDiff3 . ' -a --overlap-only ' .
+ wfEscapeShellArg( $mytextName ) . ' ' .
+ wfEscapeShellArg( $oldtextName ) . ' ' .
+ wfEscapeShellArg( $yourtextName );
+ $handle = popen( $cmd, 'r' );
+
+ if( fgets( $handle, 1024 ) ) {
+ $conflict = true;
+ } else {
+ $conflict = false;
+ }
+ pclose( $handle );
+
+ # Merge differences
+ $cmd = $wgDiff3 . ' -a -e --merge ' .
+ wfEscapeShellArg( $mytextName, $oldtextName, $yourtextName );
+ $handle = popen( $cmd, 'r' );
+ $result = '';
+ do {
+ $data = fread( $handle, 8192 );
+ if ( strlen( $data ) == 0 ) {
+ break;
+ }
+ $result .= $data;
+ } while ( true );
+ pclose( $handle );
+ unlink( $mytextName );
+ unlink( $oldtextName );
+ unlink( $yourtextName );
+
+ if ( $result === '' && $old !== '' && !$conflict ) {
+ wfDebug( "Unexpected null result from diff3. Command: $cmd\n" );
+ $conflict = true;
+ }
+ return !$conflict;
+}
+
+/**
+ * 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.
+ * @return String: unified diff of $before and $after
+ */
+function wfDiff( $before, $after, $params = '-u' ) {
+ if ( $before == $after ) {
+ return '';
+ }
+
+ global $wgDiff;
+ wfSuppressWarnings();
+ $haveDiff = $wgDiff && file_exists( $wgDiff );
+ wfRestoreWarnings();
+
+ # This check may also protect against code injection in
+ # case of broken installations.
+ if( !$haveDiff ) {
+ wfDebug( "diff executable not found\n" );
+ $diffs = new Diff( explode( "\n", $before ), explode( "\n", $after ) );
+ $format = new UnifiedDiffFormatter();
+ return $format->format( $diffs );
+ }
+
+ # Make temporary files
+ $td = wfTempDir();
+ $oldtextFile = fopen( $oldtextName = tempnam( $td, 'merge-old-' ), 'w' );
+ $newtextFile = fopen( $newtextName = tempnam( $td, 'merge-your-' ), 'w' );
+
+ fwrite( $oldtextFile, $before );
+ fclose( $oldtextFile );
+ fwrite( $newtextFile, $after );
+ fclose( $newtextFile );
+
+ // Get the diff of the two files
+ $cmd = "$wgDiff " . $params . ' ' . wfEscapeShellArg( $oldtextName, $newtextName );
+
+ $h = popen( $cmd, 'r' );
+
+ $diff = '';
+
+ do {
+ $data = fread( $h, 8192 );
+ if ( strlen( $data ) == 0 ) {
+ break;
+ }
+ $diff .= $data;
+ } while ( true );
+
+ // Clean up
+ pclose( $h );
+ unlink( $oldtextName );
+ unlink( $newtextName );
+
+ // Kill the --- and +++ lines. They're not useful.
+ $diff_lines = explode( "\n", $diff );
+ if ( strpos( $diff_lines[0], '---' ) === 0 ) {
+ unset( $diff_lines[0] );
+ }
+ if ( strpos( $diff_lines[1], '+++' ) === 0 ) {
+ unset( $diff_lines[1] );
+ }
+
+ $diff = implode( "\n", $diff_lines );
+
+ return $diff;
+}
+
+/**
* This function works like "use VERSION" in Perl, the program will die with a
* backtrace if the current version of PHP is less than the version provided
*
@@ -2920,35 +3166,13 @@ function wfRelativePath( $path, $from ) {
/**
* Do any deferred updates and clear the list
*
- * @param $commit String: set to 'commit' to commit after every update to
- * prevent lock contention
+ * @deprecated since 1.19
+ * @see DeferredUpdates::doUpdate()
+ * @param $commit string
*/
function wfDoUpdates( $commit = '' ) {
- global $wgDeferredUpdateList;
-
- wfProfileIn( __METHOD__ );
-
- // No need to get master connections in case of empty updates array
- if ( !count( $wgDeferredUpdateList ) ) {
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $doCommit = $commit == 'commit';
- if ( $doCommit ) {
- $dbw = wfGetDB( DB_MASTER );
- }
-
- foreach ( $wgDeferredUpdateList as $update ) {
- $update->doUpdate();
-
- if ( $doCommit && $dbw->trxLevel() ) {
- $dbw->commit();
- }
- }
-
- $wgDeferredUpdateList = array();
- wfProfileOut( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.19' );
+ DeferredUpdates::doUpdates( $commit );
}
/**
@@ -3043,13 +3267,17 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
*
* @param $name String
* @param $p Array: parameters
+ * @return object
* @deprecated since 1.18, warnings in 1.18, removal in 1.20
*/
function wfCreateObject( $name, $p ) {
- wfDeprecated( __FUNCTION__ );
+ wfDeprecated( __FUNCTION__, '1.18' );
return MWFunction::newObj( $name, $p );
}
+/**
+ * @return bool
+ */
function wfHttpOnlySafe() {
global $wgHttpOnlyBlacklist;
@@ -3164,8 +3392,10 @@ function wfGetPrecompiledData( $name ) {
* @return String
*/
function wfMemcKey( /*... */ ) {
+ global $wgCachePrefix;
+ $prefix = $wgCachePrefix === false ? wfWikiID() : $wgCachePrefix;
$args = func_get_args();
- $key = wfWikiID() . ':' . implode( ':', $args );
+ $key = $prefix . ':' . implode( ':', $args );
$key = str_replace( ' ', '_', $key );
return $key;
}
@@ -3207,7 +3437,8 @@ function wfWikiID() {
* Split a wiki ID into DB name and table prefix
*
* @param $wiki String
- * @param $bits String
+ *
+ * @return array
*/
function wfSplitWikiID( $wiki ) {
$bits = explode( '-', $wiki, 2 );
@@ -3290,14 +3521,23 @@ 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
+ * @param $title Title|String
+ * @return File|null A File, or null if passed an invalid Title
*/
function wfLocalFile( $title ) {
return RepoGroup::singleton()->getLocalRepo()->newFile( $title );
}
/**
+ * Stream a file to the browser. Back-compat alias for StreamFile::stream()
+ * @deprecated since 1.19
+ */
+function wfStreamFile( $fname, $headers = array() ) {
+ wfDeprecated( __FUNCTION__, '1.19' );
+ StreamFile::stream( $fname, $headers );
+}
+
+/**
* Should low-performance queries be disabled?
*
* @return Boolean
@@ -3364,7 +3604,7 @@ function wfBoolToStr( $value ) {
* @codeCoverageIgnore
*/
function wfLoadExtensionMessages() {
- wfDeprecated( __FUNCTION__ );
+ wfDeprecated( __FUNCTION__, '1.16' );
}
/**
@@ -3379,59 +3619,6 @@ function wfGetNull() {
}
/**
- * Throws a warning that $function is deprecated
- *
- * @param $function String
- * @return null
- */
-function wfDeprecated( $function ) {
- static $functionsWarned = array();
- if ( !isset( $functionsWarned[$function] ) ) {
- $functionsWarned[$function] = true;
- wfWarn( "Use of $function is deprecated.", 2 );
- }
-}
-
-/**
- * Send a warning either to the debug log or in a PHP error depending on
- * $wgDevelopmentWarnings
- *
- * @param $msg String: message to send
- * @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] ) ) {
- $callerfunc = $callers[$callerOffset + 1];
- $callerfile = $callers[$callerOffset];
- if ( isset( $callerfile['file'] ) && isset( $callerfile['line'] ) ) {
- $file = $callerfile['file'] . ' at line ' . $callerfile['line'];
- } else {
- $file = '(internal function)';
- }
- $func = '';
- if ( isset( $callerfunc['class'] ) ) {
- $func .= $callerfunc['class'] . '::';
- }
- if ( isset( $callerfunc['function'] ) ) {
- $func .= $callerfunc['function'];
- }
- $msg .= " [Called from $func in $file]";
- }
-
- if ( $wgDevelopmentWarnings ) {
- trigger_error( $msg, $level );
- } else {
- wfDebug( "$msg\n" );
- }
-}
-
-/**
* 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
@@ -3440,7 +3627,6 @@ function wfWarn( $msg, $callerOffset = 1, $level = E_USER_NOTICE ) {
*
* @param $maxLag Integer (deprecated)
* @param $wiki mixed Wiki identifier accepted by wfGetLB
- * @return null
*/
function wfWaitForSlaves( $maxLag = false, $wiki = false ) {
$lb = wfGetLB( $wiki );
@@ -3458,7 +3644,7 @@ function wfWaitForSlaves( $maxLag = false, $wiki = false ) {
* @deprecated since 1.18, warnings in 1.18, remove in 1.20
*/
function wfOut( $s ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __FUNCTION__, '1.18' );
global $wgCommandLineMode;
if ( $wgCommandLineMode ) {
echo $s;
@@ -3472,6 +3658,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
+ * @param $n int
*/
function wfCountDown( $n ) {
for ( $i = $n; $i >= 0; $i-- ) {
@@ -3580,7 +3767,7 @@ function wfShorthandToInteger( $string = '' ) {
* See unit test for examples.
*
* @param $code String: The language code.
- * @return $langCode String: The language code which complying with BCP 47 standards.
+ * @return String: The language code which complying with BCP 47 standards.
*/
function wfBCP47( $code ) {
$codeSegment = explode( '-', $code );
@@ -3588,7 +3775,7 @@ function wfBCP47( $code ) {
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') {
+ if( $segNo > 0 && strtolower( $codeSegment[( $segNo - 1 )] ) == 'x' ) {
$codeBCP[$segNo] = strtolower( $seg );
// ISO 3166 country code
} elseif ( ( strlen( $seg ) == 2 ) && ( $segNo > 0 ) ) {
@@ -3654,7 +3841,7 @@ function wfGetParserCacheStorage() {
*
* @param $event String: event name
* @param $args Array: parameters passed to hook functions
- * @return Boolean
+ * @return Boolean True if no handler aborted the hook
*/
function wfRunHooks( $event, $args = array() ) {
return Hooks::run( $event, $args );
diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php
index 948de61f..7326bf5c 100644
--- a/includes/HTMLForm.php
+++ b/includes/HTMLForm.php
@@ -33,10 +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'.
+ * 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
@@ -53,9 +53,9 @@
*
* TODO: Document 'section' / 'subsection' stuff
*/
-class HTMLForm {
+class HTMLForm extends ContextSource {
- # A mapping of 'type' inputs onto standard HTMLFormField subclasses
+ // A mapping of 'type' inputs onto standard HTMLFormField subclasses
static $typeMappings = array(
'text' => 'HTMLTextField',
'textarea' => 'HTMLTextAreaField',
@@ -73,15 +73,18 @@ class HTMLForm {
'hidden' => 'HTMLHiddenField',
'edittools' => 'HTMLEditTools',
- # HTMLTextField will output the correct type="" attribute automagically.
- # There are about four zillion other HTML5 input types, like url, but
- # we don't use those at the moment, so no point in adding all of them.
+ // HTMLTextField will output the correct type="" attribute automagically.
+ // There are about four zillion other HTML5 input types, like url, but
+ // we don't use those at the moment, so no point in adding all of them.
'email' => 'HTMLTextField',
'password' => 'HTMLTextField',
);
protected $mMessagePrefix;
+
+ /** @var HTMLFormField[] */
protected $mFlatFields;
+
protected $mFieldTree;
protected $mShowReset = false;
public $mFieldData;
@@ -102,15 +105,30 @@ class HTMLForm {
protected $mSubmitText;
protected $mSubmitTooltip;
- protected $mContext; // <! IContextSource
protected $mTitle;
protected $mMethod = 'post';
+ /**
+ * Form action URL. false means we will use the URL to set Title
+ * @since 1.19
+ * @var false|string
+ */
+ protected $mAction = false;
+
protected $mUseMultipart = false;
protected $mHiddenFields = array();
protected $mButtons = array();
protected $mWrapperLegend = false;
+
+ /**
+ * If true, sections that contain both fields and subsections will
+ * render their subsections before their fields.
+ *
+ * Subclasses may set this to false to render subsections after fields
+ * instead.
+ */
+ protected $mSubSectionBeforeFields = true;
/**
* Build a new HTMLForm from an array of field attributes
@@ -121,7 +139,7 @@ class HTMLForm {
*/
public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) {
if( $context instanceof IContextSource ){
- $this->mContext = $context;
+ $this->setContext( $context );
$this->mTitle = false; // We don't need them to set a title
$this->mMessagePrefix = $messagePrefix;
} else {
@@ -175,11 +193,12 @@ class HTMLForm {
* done already.
* @deprecated since 1.18 load modules with ResourceLoader instead
*/
- static function addJS() { }
+ static function addJS() { wfDeprecated( __METHOD__, '1.18' ); }
/**
* Initialise a new Object for the field
- * @param $descriptor input Descriptor, as described above
+ * @param $fieldname string
+ * @param $descriptor string input Descriptor, as described above
* @return HTMLFormField subclass
*/
static function loadInputFromParameters( $fieldname, $descriptor ) {
@@ -221,12 +240,27 @@ class HTMLForm {
* @return Status|boolean
*/
function tryAuthorizedSubmit() {
- $editToken = $this->getRequest()->getVal( 'wpEditToken' );
-
$result = false;
- if ( $this->getMethod() != 'post' || $this->getUser()->matchEditToken( $editToken ) ) {
+
+ $submit = false;
+ if ( $this->getMethod() != 'post' ) {
+ $submit = true; // no session check needed
+ } elseif ( $this->getRequest()->wasPosted() ) {
+ $editToken = $this->getRequest()->getVal( 'wpEditToken' );
+ if ( $this->getUser()->isLoggedIn() || $editToken != null ) {
+ // Session tokens for logged-out users have no security value.
+ // However, if the user gave one, check it in order to give a nice
+ // "session expired" error instead of "permission denied" or such.
+ $submit = $this->getUser()->matchEditToken( $editToken );
+ } else {
+ $submit = true;
+ }
+ }
+
+ if ( $submit ) {
$result = $this->trySubmit();
}
+
return $result;
}
@@ -276,7 +310,7 @@ class HTMLForm {
$data = $this->filterDataForSubmit( $this->mFieldData );
- $res = call_user_func( $callback, $data );
+ $res = call_user_func( $callback, $data, $this );
return $res;
}
@@ -306,7 +340,16 @@ class HTMLForm {
* Set the introductory message, overwriting any existing message.
* @param $msg String complete text of message to display
*/
- function setIntro( $msg ) { $this->mPre = $msg; }
+ function setIntro( $msg ) {
+ $this->setPreText( $msg );
+ }
+
+ /**
+ * Set the introductory message, overwriting any existing message.
+ * @since 1.19
+ * @param $msg String complete text of message to display
+ */
+ function setPreText( $msg ) { $this->mPre = $msg; }
/**
* Add introductory text.
@@ -317,7 +360,7 @@ 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
+ * @param $section string The section to add the header to
*/
function addHeaderText( $msg, $section = null ) {
if ( is_null( $section ) ) {
@@ -331,6 +374,20 @@ class HTMLForm {
}
/**
+ * Set header text, inside the form.
+ * @since 1.19
+ * @param $msg String complete text of message to display
+ * @param $section The section to add the header to
+ */
+ function setHeaderText( $msg, $section = null ) {
+ if ( is_null( $section ) ) {
+ $this->mHeader = $msg;
+ } else {
+ $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
@@ -347,12 +404,32 @@ class HTMLForm {
}
/**
+ * Set footer text, inside the form.
+ * @since 1.19
+ * @param $msg String complete text of message to display
+ * @param $section string The section to add the footer text to
+ */
+ function setFooterText( $msg, $section = null ) {
+ if ( is_null( $section ) ) {
+ $this->mFooter = $msg;
+ } else {
+ $this->mSectionFooters[$section] = $msg;
+ }
+ }
+
+ /**
* Add text to the end of the display.
* @param $msg String complete text of message to display
*/
function addPostText( $msg ) { $this->mPost .= $msg; }
/**
+ * Set text at the end of the display.
+ * @param $msg String complete text of message to display
+ */
+ function setPostText( $msg ) { $this->mPost = $msg; }
+
+ /**
* Add a hidden field to the output
* @param $name String field name. This will be used exactly as entered
* @param $value String field value
@@ -368,11 +445,20 @@ class HTMLForm {
}
/**
- * Display the form (sending to wgOut), with an appropriate error
+ * Display the form (sending to $wgOut), with an appropriate error
* message or stack of messages, and any validation errors, etc.
* @param $submitResult Mixed output from HTMLForm::trySubmit()
*/
function displayForm( $submitResult ) {
+ $this->getOutput()->addHTML( $this->getHTML( $submitResult ) );
+ }
+
+ /**
+ * Returns the raw HTML generated by the form
+ * @param $submitResult Mixed output from HTMLForm::trySubmit()
+ * @return string
+ */
+ function getHTML( $submitResult ) {
# For good measure (it is the default)
$this->getOutput()->preventClickjacking();
$this->getOutput()->addModules( 'mediawiki.htmlform' );
@@ -388,11 +474,7 @@ class HTMLForm {
$html = $this->wrapForm( $html );
- $this->getOutput()->addHTML( ''
- . $this->mPre
- . $html
- . $this->mPost
- );
+ return '' . $this->mPre . $html . $this->mPost;
}
/**
@@ -412,7 +494,7 @@ class HTMLForm {
: 'application/x-www-form-urlencoded';
# Attributes
$attribs = array(
- 'action' => $this->getTitle()->getFullURL(),
+ 'action' => $this->mAction === false ? $this->getTitle()->getFullURL() : $this->mAction,
'method' => $this->mMethod,
'class' => 'visualClear',
'enctype' => $encType,
@@ -433,7 +515,7 @@ class HTMLForm {
$html = '';
if( $this->getMethod() == 'post' ){
- $html .= Html::hidden( 'wpEditToken', $this->getUser()->editToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
+ $html .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
$html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
}
@@ -506,6 +588,7 @@ class HTMLForm {
/**
* Get the whole body of the form.
+ * @return String
*/
function getBody() {
return $this->displaySection( $this->mFieldTree );
@@ -571,6 +654,15 @@ class HTMLForm {
}
/**
+ * Set the text for the submit button to a message
+ * @since 1.19
+ * @param $msg String message key
+ */
+ public function setSubmitTextMsg( $msg ) {
+ return $this->setSubmitText( $this->msg( $msg )->escaped() );
+ }
+
+ /**
* Get the text for the submit button, either customised or a default.
* @return unknown_type
*/
@@ -609,6 +701,16 @@ class HTMLForm {
public function setWrapperLegend( $legend ) { $this->mWrapperLegend = $legend; }
/**
+ * Prompt the whole form to be wrapped in a <fieldset>, with
+ * this message as its <legend> element.
+ * @since 1.19
+ * @param $msg String message key
+ */
+ public function setWrapperLegendMsg( $msg ) {
+ return $this->setWrapperLegend( $this->msg( $msg )->escaped() );
+ }
+
+ /**
* Set the prefix for various default messages
* TODO: currently only used for the <fieldset> legend on forms
* with multiple sections; should be used elsewhre?
@@ -637,36 +739,6 @@ class HTMLForm {
}
/**
- * @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
*/
@@ -680,9 +752,10 @@ class HTMLForm {
/**
* TODO: Document
- * @param $fields array of fields (either arrays or objects)
+ * @param $fields array[]|HTMLFormField[] 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
+ * @return String
*/
function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) {
$tableHtml = '';
@@ -696,8 +769,9 @@ class HTMLForm {
: $value->getDefault();
$tableHtml .= $value->getTableRow( $v );
- if ( $value->getLabel() != '&#160;' )
+ if ( $value->getLabel() != '&#160;' ) {
$hasLeftColumn = true;
+ }
} elseif ( is_array( $value ) ) {
$section = $this->displaySection( $value, $key );
$legend = $this->getLegend( $key );
@@ -732,7 +806,11 @@ class HTMLForm {
$tableHtml = Html::rawElement( 'table', $attribs,
Html::rawElement( 'tbody', array(), "\n$tableHtml\n" ) ) . "\n";
- return $subsectionHtml . "\n" . $tableHtml;
+ if ( $this->mSubSectionBeforeFields ) {
+ return $subsectionHtml . "\n" . $tableHtml;
+ } else {
+ return $tableHtml . "\n" . $subsectionHtml;
+ }
}
/**
@@ -789,6 +867,19 @@ class HTMLForm {
public function getLegend( $key ) {
return wfMsg( "{$this->mMessagePrefix}-$key" );
}
+
+ /**
+ * Set the value for the action attribute of the form.
+ * When set to false (which is the default state), the set title is used.
+ *
+ * @since 1.19
+ *
+ * @param string|false $action
+ */
+ public function setAction( $action ) {
+ $this->mAction = $action;
+ }
+
}
/**
@@ -830,20 +921,20 @@ abstract class HTMLFormField {
* @return Mixed Bool true on success, or String error to display.
*/
function validate( $value, $alldata ) {
- if ( isset( $this->mValidationCallback ) ) {
- return call_user_func( $this->mValidationCallback, $value, $alldata );
- }
-
if ( isset( $this->mParams['required'] ) && $value === '' ) {
return wfMsgExt( 'htmlform-required', 'parseinline' );
}
+ if ( isset( $this->mValidationCallback ) ) {
+ return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent );
+ }
+
return true;
}
function filter( $value, $alldata ) {
if ( isset( $this->mFilterCallback ) ) {
- $value = call_user_func( $this->mFilterCallback, $value, $alldata );
+ $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent );
}
return $value;
@@ -934,6 +1025,10 @@ abstract class HTMLFormField {
if ( isset( $params['filter-callback'] ) ) {
$this->mFilterCallback = $params['filter-callback'];
}
+
+ if ( isset( $params['flatlist'] ) ){
+ $this->mClass .= ' mw-htmlform-flatlist';
+ }
}
/**
@@ -1055,7 +1150,7 @@ abstract class HTMLFormField {
/**
* flatten an array of options to a single array, for instance,
* a set of <options> inside <optgroups>.
- * @param $options Associative Array with values either Strings
+ * @param $options array Associative Array with values either Strings
* or Arrays
* @return Array flattened input
*/
@@ -1118,6 +1213,10 @@ class HTMLTextField extends HTMLFormField {
'value' => $value,
) + $this->getTooltipAndAccessKey();
+ if ( $this->mClass !== '' ) {
+ $attribs['class'] = $this->mClass;
+ }
+
if ( isset( $this->mParams['maxlength'] ) ) {
$attribs['maxlength'] = $this->mParams['maxlength'];
}
@@ -1188,7 +1287,10 @@ class HTMLTextAreaField extends HTMLFormField {
'rows' => $this->getRows(),
) + $this->getTooltipAndAccessKey();
-
+ if ( $this->mClass !== '' ) {
+ $attribs['class'] = $this->mClass;
+ }
+
if ( !empty( $this->mParams['disabled'] ) ) {
$attribs['disabled'] = 'disabled';
}
@@ -1295,6 +1397,10 @@ class HTMLCheckField extends HTMLFormField {
if ( !empty( $this->mParams['disabled'] ) ) {
$attr['disabled'] = 'disabled';
}
+
+ if ( $this->mClass !== '' ) {
+ $attr['class'] = $this->mClass;
+ }
return Xml::check( $this->mName, $value, $attr ) . '&#160;' .
Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
@@ -1303,6 +1409,7 @@ class HTMLCheckField extends HTMLFormField {
/**
* For a checkbox, the label goes on the right hand side, and is
* added in getInputHTML(), rather than HTMLFormField::getRow()
+ * @return String
*/
function getLabel() {
return '&#160;';
@@ -1371,6 +1478,10 @@ class HTMLSelectField extends HTMLFormField {
if ( !empty( $this->mParams['disabled'] ) ) {
$select->setAttribute( 'disabled', 'disabled' );
}
+
+ if ( $this->mClass !== '' ) {
+ $select->setAttribute( 'class', $this->mClass );
+ }
$select->addOptions( $this->mParams['options'] );
@@ -1432,6 +1543,10 @@ class HTMLSelectOrOtherField extends HTMLTextField {
if ( isset( $this->mParams['maxlength'] ) ) {
$tbAttribs['maxlength'] = $this->mParams['maxlength'];
}
+
+ if ( $this->mClass !== '' ) {
+ $tbAttribs['class'] = $this->mClass;
+ }
$textbox = Html::input(
$this->mName . '-other',
@@ -1467,13 +1582,6 @@ class HTMLSelectOrOtherField extends HTMLTextField {
*/
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 );
@@ -1525,7 +1633,7 @@ class HTMLMultiSelectField extends HTMLFormField {
$attribs + $thisAttribs );
$checkbox .= '&#160;' . Html::rawElement( 'label', array( 'for' => "{$this->mID}-$info" ), $label );
- $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-multiselect-item' ), $checkbox );
+ $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $checkbox );
}
}
@@ -1655,6 +1763,10 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
'id' => $this->mID . '-other',
'size' => $this->getSize(),
);
+
+ if ( $this->mClass !== '' ) {
+ $textAttribs['class'] = $this->mClass;
+ }
foreach ( array( 'required', 'autofocus', 'multiple', 'disabled' ) as $param ) {
if ( isset( $this->mParams[$param] ) ) {
@@ -1696,7 +1808,17 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
} else {
$final = $this->getDefault();
- $list = $text = '';
+
+ $list = 'other';
+ $text = $final;
+ foreach ( $this->mFlatOptions as $option ) {
+ $match = $option . wfMsgForContent( 'colon-separator' );
+ if( strpos( $text, $match ) === 0 ) {
+ $list = $option;
+ $text = substr( $text, strlen( $match ) );
+ break;
+ }
+ }
}
return array( $final, $list, $text );
}
@@ -1729,6 +1851,8 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
* Radio checkbox fields.
*/
class HTMLRadioField extends HTMLFormField {
+
+
function validate( $value, $alldata ) {
$p = parent::validate( $value, $alldata );
@@ -1752,6 +1876,8 @@ class HTMLRadioField extends HTMLFormField {
/**
* This returns a block of all the radio options, in one cell.
* @see includes/HTMLFormField#getInputHTML()
+ * @param $value String
+ * @return String
*/
function getInputHTML( $value ) {
$html = $this->formatOptions( $this->mParams['options'], $value );
@@ -1774,16 +1900,16 @@ class HTMLRadioField extends HTMLFormField {
$html .= $this->formatOptions( $info, $value );
} else {
$id = Sanitizer::escapeId( $this->mID . "-$info" );
- $html .= Xml::radio(
+ $radio = Xml::radio(
$this->mName,
$info,
$info == $value,
$attribs + array( 'id' => $id )
);
- $html .= '&#160;' .
+ $radio .= '&#160;' .
Html::rawElement( 'label', array( 'for' => $id ), $label );
- $html .= "<br />\n";
+ $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $radio );
}
}
@@ -1864,7 +1990,7 @@ class HTMLSubmitField extends HTMLFormField {
return Xml::submitButton(
$value,
array(
- 'class' => 'mw-htmlform-submit',
+ 'class' => 'mw-htmlform-submit ' . $this->mClass,
'name' => $this->mName,
'id' => $this->mID,
)
@@ -1877,6 +2003,9 @@ class HTMLSubmitField extends HTMLFormField {
/**
* Button cannot be invalid
+ * @param $value String
+ * @param $alldata Array
+ * @return Bool
*/
public function validate( $value, $alldata ){
return true;
diff --git a/includes/Hooks.php b/includes/Hooks.php
index dd08d03b..e1c1d50b 100644
--- a/includes/Hooks.php
+++ b/includes/Hooks.php
@@ -86,7 +86,7 @@ class Hooks {
*
* @param $event String: event name
* @param $args Array: parameters passed to hook functions
- * @return Boolean
+ * @return Boolean True if no handler aborted the hook
*/
public static function run( $event, $args = array() ) {
global $wgHooks;
@@ -222,9 +222,7 @@ class Hooks {
/* String return is an error; false return means stop processing. */
if ( is_string( $retval ) ) {
- global $wgOut;
- $wgOut->showFatalError( $retval );
- return false;
+ throw new FatalError( $retval );
} elseif( $retval === null ) {
if ( $closure ) {
$prettyFunc = "$event closure";
diff --git a/includes/Html.php b/includes/Html.php
index be9a1e1b..c61a1baf 100644
--- a/includes/Html.php
+++ b/includes/Html.php
@@ -48,7 +48,7 @@
* @since 1.16
*/
class Html {
- # List of void elements from HTML5, section 9.1.2 as of 2009-08-10
+ # List of void elements from HTML5, section 8.1.2 as of 2011-08-12
private static $voidElements = array(
'area',
'base',
@@ -64,16 +64,19 @@ class Html {
'meta',
'param',
'source',
+ 'track',
+ 'wbr',
);
# Boolean attributes, which may have the value omitted entirely. Manually
- # collected from the HTML5 spec as of 2010-06-07.
+ # collected from the HTML5 spec as of 2011-08-12.
private static $boolAttribs = array(
'async',
'autofocus',
'autoplay',
'checked',
'controls',
+ 'default',
'defer',
'disabled',
'formnovalidate',
@@ -82,6 +85,7 @@ class Html {
'itemscope',
'loop',
'multiple',
+ 'muted',
'novalidate',
'open',
'pubdate',
@@ -91,25 +95,40 @@ class Html {
'scoped',
'seamless',
'selected',
+ 'truespeed',
+ 'typemustmatch',
+ # HTML5 Microdata
+ 'itemscope',
+ );
+
+ private static $HTMLFiveOnlyAttribs = array(
+ 'autocomplete',
+ 'autofocus',
+ 'max',
+ 'min',
+ 'multiple',
+ 'pattern',
+ 'placeholder',
+ 'required',
+ 'step',
+ 'spellcheck',
);
/**
* Returns an HTML element in a string. The major advantage here over
* manually typing out the HTML is that it will escape all attribute
* values. If you're hardcoding all the attributes, or there are none, you
- * should probably type out the string yourself.
+ * should probably just type out the html element yourself.
*
* This is quite similar to Xml::tags(), but it implements some useful
* HTML-specific logic. For instance, there is no $allowShortTag
* parameter: the closing tag is magically omitted if $element has an empty
* content model. If $wgWellFormedXml is false, then a few bytes will be
- * shaved off the HTML output as well. In the future, other HTML-specific
- * features might be added, like allowing arrays for the values of
- * attributes like class= and media=.
+ * shaved off the HTML output as well.
*
* @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
+ * 'href' => 'http://www.mediawiki.org/' ). See expandAttributes() for
* further documentation.
* @param $contents string The raw HTML contents of the element: *not*
* escaped!
@@ -352,6 +371,28 @@ class Html {
* For instance, it will omit quotation marks if $wgWellFormedXml is false,
* and will treat boolean attributes specially.
*
+ * Attributes that should contain space-separated lists (such as 'class') array
+ * values are allowed as well, which will automagically be normalized
+ * and converted to a space-separated string. In addition to a numerical
+ * array, the attribute value may also be an associative array. See the
+ * example below for how that works.
+ *
+ * @par Numerical array
+ * @code
+ * Html::element( 'em', array(
+ * 'class' => array( 'foo', 'bar' )
+ * ) );
+ * // gives '<em class="foo bar"></em>'
+ * @endcode
+ *
+ * @par Associative array
+ * @code
+ * Html::element( 'em', array(
+ * 'class' => array( 'foo', 'bar', 'foo' => false, 'quux' => true )
+ * ) );
+ * // gives '<em class="bar quux"></em>'
+ * @endcode
+ *
* @param $attribs array Associative array of attributes, e.g., array(
* 'href' => 'http://www.mediawiki.org/' ). Values will be HTML-escaped.
* A value of false means to omit the attribute. For boolean attributes,
@@ -381,6 +422,12 @@ class Html {
# and we'd like consistency and better compression anyway.
$key = strtolower( $key );
+ # Here we're blacklisting some HTML5-only attributes...
+ if ( !$wgHtml5 && in_array( $key, self::$HTMLFiveOnlyAttribs )
+ ) {
+ continue;
+ }
+
# Bug 23769: Blacklist all form validation attributes for now. Current
# (June 2010) WebKit has no UI, so the form just refuses to submit
# without telling the user why, which is much worse than failing
@@ -391,20 +438,53 @@ class Html {
continue;
}
- # Here we're blacklisting some HTML5-only attributes...
- if ( !$wgHtml5 && in_array( $key, array(
- 'autocomplete',
- 'autofocus',
- 'max',
- 'min',
- 'multiple',
- 'pattern',
- 'placeholder',
- 'required',
- 'step',
- 'spellcheck',
- ) ) ) {
- continue;
+ // http://www.w3.org/TR/html401/index/attributes.html ("space-separated")
+ // http://www.w3.org/TR/html5/index.html#attributes-1 ("space-separated")
+ $spaceSeparatedListAttributes = array(
+ 'class', // html4, html5
+ 'accesskey', // as of html5, multiple space-separated values allowed
+ // html4-spec doesn't document rel= as space-separated
+ // but has been used like that and is now documented as such
+ // in the html5-spec.
+ 'rel',
+ );
+
+ # Specific features for attributes that allow a list of space-separated values
+ if ( in_array( $key, $spaceSeparatedListAttributes ) ) {
+ // Apply some normalization and remove duplicates
+
+ // Convert into correct array. Array can contain space-seperated
+ // values. Implode/explode to get those into the main array as well.
+ if ( is_array( $value ) ) {
+ // If input wasn't an array, we can skip this step
+
+ $newValue = array();
+ foreach ( $value as $k => $v ) {
+ if ( is_string( $v ) ) {
+ // String values should be normal `array( 'foo' )`
+ // Just append them
+ if ( !isset( $value[$v] ) ) {
+ // As a special case don't set 'foo' if a
+ // separate 'foo' => true/false exists in the array
+ // keys should be authoritive
+ $newValue[] = $v;
+ }
+ } elseif ( $v ) {
+ // If the value is truthy but not a string this is likely
+ // an array( 'foo' => true ), falsy values don't add strings
+ $newValue[] = $k;
+ }
+ }
+ $value = implode( ' ', $newValue );
+ }
+ $value = explode( ' ', $value );
+
+ // Normalize spacing by fixing up cases where people used
+ // more than 1 space and/or a trailing/leading space
+ $value = array_diff( $value, array( '', ' ' ) );
+
+ // Remove duplicates and create the string
+ $value = implode( ' ', array_unique( $value ) );
}
# See the "Attributes" section in the HTML syntax part of HTML5,
@@ -460,6 +540,7 @@ class Html {
# @todo FIXME: Is this really true?
$map['<'] = '&lt;';
}
+
$ret .= " $key=$quote" . strtr( $value, $map ) . $quote;
}
}
@@ -620,6 +701,77 @@ class Html {
}
return self::element( 'textarea', $attribs, $spacedValue );
}
+ /**
+ * Build a drop-down box for selecting a namespace
+ *
+ * @param $params array:
+ * - selected: [optional] Id of namespace which should be pre-selected
+ * - all: [optional] Value of item for "all namespaces". If null or unset, no <option> is generated to select all namespaces
+ * - label: text for label to add before the field
+ * @param $selectAttribs array HTML attributes for the generated select element.
+ * - id: [optional], default: 'namespace'
+ * - name: [optional], default: 'namespace'
+ * @return string HTML code to select a namespace.
+ */
+ public static function namespaceSelector( Array $params = array(), Array $selectAttribs = array() ) {
+ global $wgContLang;
+
+ // Default 'id' & 'name' <select> attributes
+ $selectAttribs = $selectAttribs + array(
+ 'id' => 'namespace',
+ 'name' => 'namespace',
+ );
+ ksort( $selectAttribs );
+
+ // Is a namespace selected?
+ if ( isset( $params['selected'] ) ) {
+ // If string only contains digits, convert to clean int. Selected could also
+ // be "all" or "" etc. which needs to be left untouched.
+ // PHP is_numeric() has issues with large strings, PHP ctype_digit has other issues
+ // and returns false for already clean ints. Use regex instead..
+ if ( preg_match( '/^\d+$/', $params['selected'] ) ) {
+ $params['selected'] = intval( $params['selected'] );
+ }
+ // else: leaves it untouched for later processing
+ } else {
+ $params['selected'] = '';
+ }
+
+ // Array holding the <option> elements
+ $options = array();
+
+ if ( isset( $params['all'] ) ) {
+ // add an <option> that would let the user select all namespaces.
+ // Value is provided by user, the name shown is localized.
+ $options[$params['all']] = wfMsg( 'namespacesall' );
+ }
+ // Add defaults <option> according to content language
+ $options += $wgContLang->getFormattedNamespaces();
+
+ // Convert $options to HTML
+ $optionsHtml = array();
+ foreach ( $options as $nsId => $nsName ) {
+ if ( $nsId < NS_MAIN ) {
+ continue;
+ }
+ if ( $nsId === 0 ) {
+ $nsName = wfMsg( 'blanknamespace' );
+ }
+ $optionsHtml[] = Xml::option( $nsName, $nsId, $nsId === $params['selected'] );
+ }
+
+ // Forge a <select> element and returns it
+ $ret = '';
+ if ( isset( $params['label'] ) ) {
+ $ret .= Xml::label( $params['label'], $selectAttribs['id'] ) . '&#160;';
+ }
+ $ret .= Html::openElement( 'select', $selectAttribs )
+ . "\n"
+ . implode( "\n", $optionsHtml )
+ . "\n"
+ . Html::closeElement( 'select' );
+ return $ret;
+ }
/**
* Constructs the opening html-tag with necessary doctypes depending on
diff --git a/includes/HttpFunctions.old.php b/includes/HttpFunctions.old.php
index ddfa608e..479b4d23 100644
--- a/includes/HttpFunctions.old.php
+++ b/includes/HttpFunctions.old.php
@@ -9,5 +9,4 @@
* This is for backwards compatibility.
* @since 1.17
*/
-
class HttpRequest extends MWHttpRequest { }
diff --git a/includes/HttpFunctions.php b/includes/HttpFunctions.php
index a80fec17..147823fe 100644
--- a/includes/HttpFunctions.php
+++ b/includes/HttpFunctions.php
@@ -29,6 +29,8 @@ class Http {
* - followRedirects Whether to follow redirects (defaults to false).
* Note: this should only be used when the target URL is trusted,
* to avoid attacks on intranet services accessible by HTTP.
+ * - userAgent A user agent, if you want to override the default
+ * MediaWiki/$wgVersion
* @return Mixed: (bool)false on failure or a string on success
*/
public static function request( $method, $url, $options = array() ) {
@@ -40,6 +42,9 @@ class Http {
}
$req = MWHttpRequest::factory( $url, $options );
+ if( isset( $options['userAgent'] ) ) {
+ $req->setUserAgent( $options['userAgent'] );
+ }
$status = $req->execute();
if ( $status->isOK() ) {
@@ -53,6 +58,9 @@ class Http {
* Simple wrapper for Http::request( 'GET' )
* @see Http::request()
*
+ * @param $url
+ * @param $timeout string
+ * @param $options array
* @return string
*/
public static function get( $url, $timeout = 'default', $options = array() ) {
@@ -64,6 +72,8 @@ class Http {
* Simple wrapper for Http::request( 'POST' )
* @see Http::request()
*
+ * @param $url
+ * @param $options array
* @return string
*/
public static function post( $url, $options = array() ) {
@@ -124,10 +134,12 @@ class Http {
* protocols, because we only want protocols that both cURL
* and php support.
*
+ * file:// should not be allowed here for security purpose (r67684)
+ *
* @fixme this is wildly inaccurate and fails to actually check most stuff
*
* @param $uri Mixed: URI to check for validity
- * @returns Boolean
+ * @return Boolean
*/
public static function isValidURI( $uri ) {
return preg_match(
@@ -184,9 +196,9 @@ class MWHttpRequest {
global $wgHTTPTimeout;
$this->url = wfExpandUrl( $url, PROTO_HTTP );
- $this->parsedUrl = parse_url( $this->url );
+ $this->parsedUrl = wfParseUrl( $this->url );
- if ( !Http::isValidURI( $this->url ) ) {
+ if ( !$this->parsedUrl || !Http::isValidURI( $this->url ) ) {
$this->status = Status::newFatal( 'http-invalid-url' );
} else {
$this->status = Status::newGood( 100 ); // continue
@@ -221,6 +233,7 @@ class MWHttpRequest {
* Generate a new request object
* @param $url String: url to use
* @param $options Array: (optional) extra params to pass (see Http::request())
+ * @return CurlHttpRequest|PhpHttpRequest
* @see MWHttpRequest::__construct
*/
public static function factory( $url, $options = null ) {
@@ -278,7 +291,7 @@ class MWHttpRequest {
}
if ( Http::isLocalURL( $this->url ) ) {
- $this->proxy = 'http://localhost:80/';
+ $this->proxy = '';
} elseif ( $wgHTTPProxy ) {
$this->proxy = $wgHTTPProxy ;
} elseif ( getenv( "http_proxy" ) ) {
@@ -295,6 +308,7 @@ class MWHttpRequest {
/**
* Set the user agent
+ * @param $UA string
*/
public function setUserAgent( $UA ) {
$this->setHeader( 'User-Agent', $UA );
@@ -302,6 +316,8 @@ class MWHttpRequest {
/**
* Set an arbitrary header
+ * @param $name
+ * @param $value
*/
public function setHeader( $name, $value ) {
// I feel like I should normalize the case here...
@@ -310,6 +326,7 @@ class MWHttpRequest {
/**
* Get an array of the headers
+ * @return array
*/
public function getHeaderList() {
$list = array();
@@ -525,7 +542,7 @@ class MWHttpRequest {
/**
* Returns the cookie jar in use.
*
- * @returns CookieJar
+ * @return CookieJar
*/
public function getCookieJar() {
if ( !$this->respHeaders ) {
@@ -540,6 +557,9 @@ class MWHttpRequest {
* cookies. Used internally after a request to parse the
* Set-Cookie headers.
* @see Cookie::set
+ * @param $name
+ * @param $value null
+ * @param $attr null
*/
public function setCookie( $name, $value = null, $attr = null ) {
if ( !$this->cookieJar ) {
@@ -568,13 +588,48 @@ class MWHttpRequest {
/**
* Returns the final URL after all redirections.
*
- * @return String
+ * Relative values of the "Location" header are incorrect as stated in RFC, however they do happen and modern browsers support them.
+ * This function loops backwards through all locations in order to build the proper absolute URI - Marooned at wikia-inc.com
+ *
+ * Note that the multiple Location: headers are an artifact of CURL -- they
+ * shouldn't actually get returned this way. Rewrite this when bug 29232 is
+ * taken care of (high-level redirect handling rewrite).
+ *
+ * @return string
*/
public function getFinalUrl() {
- $location = $this->getResponseHeader( "Location" );
+ $headers = $this->getResponseHeaders();
+
+ //return full url (fix for incorrect but handled relative location)
+ if ( isset( $headers[ 'location' ] ) ) {
+ $locations = $headers[ 'location' ];
+ $domain = '';
+ $foundRelativeURI = false;
+ $countLocations = count($locations);
+
+ for ( $i = $countLocations - 1; $i >= 0; $i-- ) {
+ $url = parse_url( $locations[ $i ] );
+
+ if ( isset($url[ 'host' ]) ) {
+ $domain = $url[ 'scheme' ] . '://' . $url[ 'host' ];
+ break; //found correct URI (with host)
+ } else {
+ $foundRelativeURI = true;
+ }
+ }
- if ( $location ) {
- return $location;
+ if ( $foundRelativeURI ) {
+ if ( $domain ) {
+ return $domain . $locations[ $countLocations - 1 ];
+ } else {
+ $url = parse_url( $this->url );
+ if ( isset($url[ 'host' ]) ) {
+ return $url[ 'scheme' ] . '://' . $url[ 'host' ] . $locations[ $countLocations - 1 ];
+ }
+ }
+ } else {
+ return $locations[ $countLocations - 1 ];
+ }
}
return $this->url;
@@ -583,6 +638,7 @@ class MWHttpRequest {
/**
* Returns true if the backend can follow redirects. Overridden by the
* child classes.
+ * @return bool
*/
public function canFollowRedirects() {
return true;
@@ -603,6 +659,11 @@ class CurlHttpRequest extends MWHttpRequest {
protected $curlOptions = array();
protected $headerText = "";
+ /**
+ * @param $fh
+ * @param $content
+ * @return int
+ */
protected function readHeader( $fh, $content ) {
$this->headerText .= $content;
return strlen( $content );
@@ -694,6 +755,9 @@ class CurlHttpRequest extends MWHttpRequest {
return $this->status;
}
+ /**
+ * @return bool
+ */
public function canFollowRedirects() {
if ( strval( ini_get( 'open_basedir' ) ) !== '' || wfIniGetBool( 'safe_mode' ) ) {
wfDebug( "Cannot follow redirects in safe mode\n" );
@@ -710,6 +774,11 @@ class CurlHttpRequest extends MWHttpRequest {
}
class PhpHttpRequest extends MWHttpRequest {
+
+ /**
+ * @param $url string
+ * @return string
+ */
protected function urlToTcp( $url ) {
$parsedUrl = parse_url( $url );
@@ -797,7 +866,7 @@ class PhpHttpRequest extends MWHttpRequest {
# Check security of URL
$url = $this->getResponseHeader( "Location" );
- if ( substr( $url, 0, 7 ) !== 'http://' ) {
+ if ( !Http::isValidURI( $url ) ) {
wfDebug( __METHOD__ . ": insecure redirection\n" );
break;
}
diff --git a/includes/IP.php b/includes/IP.php
index 1da7cd07..e3f61214 100644
--- a/includes/IP.php
+++ b/includes/IP.php
@@ -18,7 +18,7 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @author Ashar Voultoiz <hashar at free dot fr>, Aaron Schulz
+ * @author Antoine Musso <hashar at free dot fr>, Aaron Schulz
*/
// Some regex definition to "play" with IP address and IP address blocks
@@ -186,14 +186,14 @@ 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,
+ * 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
+ * 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.
@@ -241,8 +241,13 @@ class IP {
/**
* 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
+ * brackets like in RFC 2732. If the port matches the default port, omit
* the port specification
+ *
+ * @param $host string
+ * @param $port int
+ * @param $defaultPort bool|int
+ * @return string
*/
public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
if ( strpos( $host, ':' ) !== false ) {
@@ -449,6 +454,10 @@ class IP {
return $n;
}
+ /**
+ * @param $ip
+ * @return String
+ */
private static function toUnsigned6( $ip ) {
return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
}
@@ -548,6 +557,8 @@ class IP {
* Convert a network specification in IPv6 CIDR notation to an
* integer network and a number of bits
*
+ * @param $range
+ *
* @return array(string, int)
*/
private static function parseCIDR6( $range ) {
@@ -585,6 +596,9 @@ class IP {
* 2001:0db8:85a3::7344/96 CIDR
* 2001:0db8:85a3::7344 - 2001:0db8:85a3::7344 Explicit range
* 2001:0db8:85a3::7344/96 Single IP
+ *
+ * @param $range
+ *
* @return array(string, string)
*/
private static function parseRange6( $range ) {
diff --git a/includes/ImageFunctions.php b/includes/ImageFunctions.php
index d048a9dd..4b90e24a 100644
--- a/includes/ImageFunctions.php
+++ b/includes/ImageFunctions.php
@@ -16,10 +16,11 @@
*
* @param $name string the image name to check
* @param $contextTitle Title|bool the page on which the image occurs, if known
+ * @param $blacklist string wikitext of a file blacklist
* @return bool
*/
-function wfIsBadImage( $name, $contextTitle = false ) {
- static $badImages = false;
+function wfIsBadImage( $name, $contextTitle = false, $blacklist = null ) {
+ static $badImageCache = null; // based on bad_image_list msg
wfProfileIn( __METHOD__ );
# Handle redirects
@@ -34,11 +35,17 @@ function wfIsBadImage( $name, $contextTitle = false ) {
wfProfileOut( __METHOD__ );
return $bad;
}
-
- if( !$badImages ) {
+
+ $cacheable = ( $blacklist === null );
+ if( $cacheable && $badImageCache !== null ) {
+ $badImages = $badImageCache;
+ } else { // cache miss
+ if ( $blacklist === null ) {
+ $blacklist = wfMsgForContentNoTrans( 'bad_image_list' ); // site list
+ }
# Build the list now
$badImages = array();
- $lines = explode( "\n", wfMsgForContentNoTrans( 'bad_image_list' ) );
+ $lines = explode( "\n", $blacklist );
foreach( $lines as $line ) {
# List items only
if ( substr( $line, 0, 1 ) !== '*' ) {
@@ -68,6 +75,9 @@ function wfIsBadImage( $name, $contextTitle = false ) {
$badImages[$imageDBkey] = $exceptions;
}
}
+ if ( $cacheable ) {
+ $badImageCache = $badImages;
+ }
}
$contextKey = $contextTitle ? $contextTitle->getPrefixedDBkey() : false;
@@ -75,20 +85,3 @@ function wfIsBadImage( $name, $contextTitle = false ) {
wfProfileOut( __METHOD__ );
return $bad;
}
-
-/**
- * Calculate the largest thumbnail width for a given original file size
- * such that the thumbnail's height is at most $maxHeight.
- * @param $boxWidth Integer Width of the thumbnail box.
- * @param $boxHeight Integer Height of the thumbnail box.
- * @param $maxHeight Integer Maximum height expected for the thumbnail.
- * @return Integer.
- */
-function wfFitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
- $idealWidth = $boxWidth * $maxHeight / $boxHeight;
- $roundedUp = ceil( $idealWidth );
- if( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight )
- return floor( $idealWidth );
- else
- return $roundedUp;
-}
diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php
index 4d5f067c..1106124a 100644
--- a/includes/ImageGallery.php
+++ b/includes/ImageGallery.php
@@ -28,9 +28,9 @@ class ImageGallery {
* Contextual title, used when images are being screened
* against the bad image list
*/
- private $contextTitle = false;
+ protected $contextTitle = false;
- private $mAttribs = array();
+ protected $mAttribs = array();
/**
* Fixed margins
@@ -131,7 +131,7 @@ class ImageGallery {
* @deprecated since 1.18 Not used anymore
*/
function useSkin( $skin ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
/* no op */
}
@@ -249,11 +249,11 @@ class ImageGallery {
# Get the file...
if ( $this->mParser instanceof Parser ) {
# Give extensions a chance to select the file revision for us
- $time = $sha1 = false;
+ $options = array();
wfRunHooks( 'BeforeParserFetchFileAndTitle',
- array( $this->mParser, $nt, &$time, &$sha1, &$descQuery ) );
+ array( $this->mParser, $nt, &$options, &$descQuery ) );
# Fetch and register the file (file title may be different via hooks)
- list( $img, $nt ) = $this->mParser->fetchFileAndTitle( $nt, $time, $sha1 );
+ list( $img, $nt ) = $this->mParser->fetchFileAndTitle( $nt, $options );
} else {
$img = wfFindFile( $nt );
}
@@ -314,8 +314,7 @@ class ImageGallery {
if( $this->mShowBytes ) {
if( $img ) {
- $fileSize = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
- $wgLang->formatNum( $img->getSize() ) );
+ $fileSize = htmlspecialchars( $wgLang->formatSize( $img->getSize() ) );
} else {
$fileSize = wfMsgHtml( 'filemissing' );
}
diff --git a/includes/ImagePage.php b/includes/ImagePage.php
index 956977e0..dcb09a41 100644
--- a/includes/ImagePage.php
+++ b/includes/ImagePage.php
@@ -18,6 +18,10 @@ class ImagePage extends Article {
var $mExtraDescription = false;
+ /**
+ * @param $title Title
+ * @return WikiFilePage
+ */
protected function newPage( Title $title ) {
// Overload mPage with a file-specific page
return new WikiFilePage( $title );
@@ -99,13 +103,11 @@ class ImagePage extends Article {
$wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
$wgOut->addHTML( $this->viewRedirect( Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ),
/* $appendSubtitle */ true, /* $forceKnown */ true ) );
- $this->mPage->viewUpdates();
+ $this->mPage->doViewUpdates( $this->getContext()->getUser() );
return;
}
}
- $this->showRedirectedFromHeader();
-
if ( $wgShowEXIF && $this->displayImg->exists() ) {
// @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
$formattedMetadata = $this->displayImg->formatMetadata();
@@ -136,7 +138,7 @@ class ImagePage extends Article {
# Just need to set the right headers
$wgOut->setArticleFlag( true );
$wgOut->setPageTitle( $this->getTitle()->getPrefixedText() );
- $this->mPage->viewUpdates();
+ $this->mPage->doViewUpdates( $this->getContext()->getUser() );
}
# Show shared description, if needed
@@ -184,6 +186,9 @@ class ImagePage extends Article {
$wgOut->addModuleStyles( 'filepage' );
}
+ /**
+ * @return File
+ */
public function getDisplayedFile() {
$this->loadFile();
return $this->displayImg;
@@ -243,6 +248,7 @@ class ImagePage extends Article {
*
* Omit noarticletext if sharedupload; text will be fetched from the
* shared upload server if possible.
+ * @return string
*/
public function getContent() {
$this->loadFile();
@@ -254,7 +260,7 @@ class ImagePage extends Article {
protected function openShowImage() {
global $wgOut, $wgUser, $wgImageLimits, $wgRequest,
- $wgLang, $wgEnableUploads;
+ $wgLang, $wgEnableUploads, $wgSend404Code;
$this->loadFile();
@@ -322,12 +328,21 @@ class ImagePage extends Article {
}
$msgsmall = wfMessage( 'show-big-image-preview' )->
rawParams( $this->makeSizeLink( $params, $width, $height ) )->
- parse() . ' ' .
- wfMessage( 'show-big-image-other' )->
- rawParams( $wgLang->pipeList( $otherSizes ) )->parse();
+ parse();
+ if ( count( $otherSizes ) && $this->displayImg->getRepo()->canTransformVia404() ) {
+ $msgsmall .= ' ' .
+ Html::rawElement( 'span', array( 'class' => 'mw-filepage-other-resolutions' ),
+ wfMessage( 'show-big-image-other' )->rawParams( $wgLang->pipeList( $otherSizes ) )->
+ params( count( $otherSizes ) )->parse()
+ );
+ }
+ } elseif ( $width == 0 && $height == 0 ){
+ # Some sort of audio file that doesn't have dimensions
+ # Don't output a no hi res message for such a file
+ $msgsmall = '';
} else {
# Image is small enough to show full size on image page
- $msgsmall = wfMsgExt( 'file-nohires', array( 'parseinline' ) );
+ $msgsmall = wfMessage( 'file-nohires' )->parse();
}
$params['width'] = $width;
@@ -335,7 +350,7 @@ class ImagePage extends Article {
$thumbnail = $this->displayImg->transform( $params );
$showLink = true;
- $anchorclose = '<br />' . $msgsmall;
+ $anchorclose = Html::rawElement( 'div', array( 'class' => 'mw-filepage-resolutioninfo' ), $msgsmall );
$isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
if ( $isMulti ) {
@@ -392,7 +407,7 @@ class ImagePage extends Article {
'action' => $wgScript,
'onchange' => 'document.pageselector.submit();',
);
-
+ $options = array();
for ( $i = 1; $i <= $count; $i++ ) {
$options[] = Xml::option( $wgLang->formatNum( $i ), $i, $i == $page );
}
@@ -407,7 +422,7 @@ class ImagePage extends Article {
wfMsgExt( 'imgmultigoto', array( 'parseinline', 'replaceafter' ), $select ) .
Xml::submitButton( wfMsg( 'imgmultigo' ) ) .
Xml::closeElement( 'form' ) .
- "<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>"
+ "<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>"
);
}
} else {
@@ -467,7 +482,7 @@ EOT
// 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 ( !$this->getID() && $wgSend404Code ) {
// 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' );
@@ -478,9 +493,10 @@ EOT
/**
* Creates an thumbnail of specified size and returns an HTML link to it
- * @param array $params Scaler parameters
- * @param int $width
- * @param int $height
+ * @param $params array Scaler parameters
+ * @param $width int
+ * @param $height int
+ * @return string
*/
private function makeSizeLink( $params, $width, $height ) {
$params['width'] = $width;
@@ -609,6 +625,11 @@ EOT
}
}
+ /**
+ * @param $target
+ * @param $limit
+ * @return ResultWrapper
+ */
protected function queryImageLinks( $target, $limit ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -741,6 +762,9 @@ EOT
);
$wgOut->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
+ /**
+ * @var $file File
+ */
foreach ( $dupes as $file ) {
$fromSrc = '';
if ( $file->isLocal() ) {
@@ -765,29 +789,25 @@ EOT
* Delete the file, or an earlier version of it
*/
public function delete() {
- global $wgUploadMaintenance;
- 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->mPage->getFile()->exists() || !$this->mPage->getFile()->isLocal() || $this->mPage->getFile()->getRedirected() ) {
+ $file = $this->mPage->getFile();
+ if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
// Standard article deletion
parent::delete();
return;
}
- $deleter = new FileDeleteForm( $this->mPage->getFile() );
+
+ $deleter = new FileDeleteForm( $file );
$deleter->execute();
}
/**
* Display an error with a wikitext description
+ *
+ * @param $description String
*/
function showError( $description ) {
global $wgOut;
- $wgOut->setPageTitle( wfMsg( 'internalerror' ) );
+ $wgOut->setPageTitle( wfMessage( 'internalerror' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setArticleRelated( false );
$wgOut->enableClientCache( false );
@@ -833,6 +853,11 @@ class ImageHistoryList {
*/
protected $imagePage;
+ /**
+ * @var File
+ */
+ protected $current;
+
protected $repo, $showThumb;
protected $preventClickjacking = false;
@@ -848,14 +873,24 @@ class ImageHistoryList {
$this->showThumb = $wgShowArchiveThumbnails && $this->img->canRender();
}
+ /**
+ * @return ImagePage
+ */
public function getImagePage() {
return $this->imagePage;
}
+ /**
+ * @return File
+ */
public function getFile() {
return $this->img;
}
+ /**
+ * @param $navLinks string
+ * @return string
+ */
public function beginImageHistoryList( $navLinks = '' ) {
global $wgOut, $wgUser;
return Xml::element( 'h2', array( 'id' => 'filehistory' ), wfMsg( 'filehist' ) ) . "\n"
@@ -873,6 +908,10 @@ class ImageHistoryList {
. "</tr>\n";
}
+ /**
+ * @param $navLinks string
+ * @return string
+ */
public function endImageHistoryList( $navLinks = '' ) {
return "</table>\n$navLinks\n</div>\n";
}
@@ -948,7 +987,7 @@ class ImageHistoryList {
array(
'action' => 'revert',
'oldimage' => $img,
- 'wpEditToken' => $wgUser->editToken( $img )
+ 'wpEditToken' => $wgUser->getEditToken( $img )
),
array( 'known', 'noclasses' )
);
@@ -963,7 +1002,7 @@ class ImageHistoryList {
$row .= "<td $selected style='white-space: nowrap;'>";
if ( !$file->userCan( File::DELETED_FILE ) ) {
# Don't link to unviewable files
- $row .= '<span class="history-deleted">' . $wgLang->timeAndDate( $timestamp, true ) . '</span>';
+ $row .= '<span class="history-deleted">' . $wgLang->timeanddate( $timestamp, true ) . '</span>';
} elseif ( $file->isDeleted( File::DELETED_FILE ) ) {
if ( $local ) {
$this->preventClickjacking();
@@ -971,22 +1010,22 @@ class ImageHistoryList {
# Make a link to review the image
$url = Linker::link(
$revdel,
- $wgLang->timeAndDate( $timestamp, true ),
+ $wgLang->timeanddate( $timestamp, true ),
array(),
array(
'target' => $this->title->getPrefixedText(),
'file' => $img,
- 'token' => $wgUser->editToken( $img )
+ 'token' => $wgUser->getEditToken( $img )
),
array( 'known', 'noclasses' )
);
} else {
- $url = $wgLang->timeAndDate( $timestamp, true );
+ $url = $wgLang->timeanddate( $timestamp, true );
}
$row .= '<span class="history-deleted">' . $url . '</span>';
} else {
$url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
- $row .= Xml::element( 'a', array( 'href' => $url ), $wgLang->timeAndDate( $timestamp, true ) );
+ $row .= Xml::element( 'a', array( 'href' => $url ), $wgLang->timeanddate( $timestamp, true ) );
}
$row .= "</td>";
@@ -1020,7 +1059,7 @@ class ImageHistoryList {
if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
$row .= '<td><span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span></td>';
} else {
- $row .= '<td dir="' . $wgContLang->getDir() . '">' . Linker::commentBlock( $description, $this->title ) . '</td>';
+ $row .= '<td dir="' . $wgContLang->getDir() . '">' . Linker::formatComment( $description, $this->title ) . '</td>';
}
$rowClass = null;
@@ -1047,7 +1086,7 @@ class ImageHistoryList {
$thumbnail = $file->transform( $params );
$options = array(
'alt' => wfMsg( 'filehist-thumbtext',
- $wgLang->timeAndDate( $timestamp, true ),
+ $wgLang->timeanddate( $timestamp, true ),
$wgLang->date( $timestamp, true ),
$wgLang->time( $timestamp, true ) ),
'file-link' => true,
@@ -1063,10 +1102,16 @@ class ImageHistoryList {
}
}
+ /**
+ * @param $enable bool
+ */
protected function preventClickjacking( $enable = true ) {
$this->preventClickjacking = $enable;
}
+ /**
+ * @return bool
+ */
public function getPreventClickjacking() {
return $this->preventClickjacking;
}
@@ -1098,6 +1143,9 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
$this->mRange = array( 0, 0 ); // display range
}
+ /**
+ * @return Title
+ */
function getTitle() {
return $this->mTitle;
}
@@ -1106,14 +1154,23 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
return false;
}
+ /**
+ * @return string
+ */
function getIndexField() {
return '';
}
+ /**
+ * @return string
+ */
function formatRow( $row ) {
return '';
}
+ /**
+ * @return string
+ */
function getBody() {
$s = '';
$this->doQuery();
@@ -1217,10 +1274,16 @@ class ImageHistoryPseudoPager extends ReverseChronologicalPager {
$this->mQueryDone = true;
}
+ /**
+ * @param $enable bool
+ */
protected function preventClickjacking( $enable = true ) {
$this->preventClickjacking = $enable;
}
+ /**
+ * @return bool
+ */
public function getPreventClickjacking() {
return $this->preventClickjacking;
}
diff --git a/includes/Import.php b/includes/Import.php
index b874462e..e906c7f0 100644
--- a/includes/Import.php
+++ b/includes/Import.php
@@ -34,11 +34,13 @@ class WikiImporter {
private $reader = null;
private $mLogItemCallback, $mUploadCallback, $mRevisionCallback, $mPageCallback;
private $mSiteInfoCallback, $mTargetNamespace, $mPageOutCallback;
- private $mDebug;
+ private $mNoticeCallback, $mDebug;
private $mImportUploads, $mImageBasePath;
+ private $mNoUpdates = false;
/**
* Creates an ImportXMLReader drawing from the source provided
+ * @param $source
*/
function __construct( $source ) {
$this->reader = new XMLReader();
@@ -47,8 +49,7 @@ class WikiImporter {
$id = UploadSourceAdapter::registerSource( $source );
if (defined( 'LIBXML_PARSEHUGE' ) ) {
$this->reader->open( "uploadsource://$id", null, LIBXML_PARSEHUGE );
- }
- else {
+ } else {
$this->reader->open( "uploadsource://$id" );
}
@@ -74,24 +75,44 @@ class WikiImporter {
wfDebug( "IMPORT: $data\n" );
}
- private function notice( $data ) {
- global $wgCommandLineMode;
- if( $wgCommandLineMode ) {
- print "$data\n";
- } else {
- global $wgOut;
- $wgOut->addHTML( "<li>" . htmlspecialchars( $data ) . "</li>\n" );
+ private function notice( $msg /*, $param, ...*/ ) {
+ $params = func_get_args();
+ array_shift( $params );
+
+ if ( is_callable( $this->mNoticeCallback ) ) {
+ call_user_func( $this->mNoticeCallback, $msg, $params );
+ } else { # No ImportReporter -> CLI
+ echo wfMessage( $msg, $params )->text() . "\n";
}
}
/**
* Set debug mode...
+ * @param $debug bool
*/
function setDebug( $debug ) {
$this->mDebug = $debug;
}
/**
+ * Set 'no updates' mode. In this mode, the link tables will not be updated by the importer
+ * @param $noupdates bool
+ */
+ function setNoUpdates( $noupdates ) {
+ $this->mNoUpdates = $noupdates;
+ }
+
+ /**
+ * Set a callback that displays notice messages
+ *
+ * @param $callback callback
+ * @return callback
+ */
+ public function setNoticeCallback( $callback ) {
+ return wfSetVar( $this->mNoticeCallback, $callback );
+ }
+
+ /**
* Sets the action to perform as each new page in the stream is reached.
* @param $callback callback
* @return callback
@@ -163,6 +184,8 @@ class WikiImporter {
/**
* Set a target namespace to override the defaults
+ * @param $namespace
+ * @return bool
*/
public function setTargetNamespace( $namespace ) {
if( is_null( $namespace ) ) {
@@ -175,13 +198,17 @@ class WikiImporter {
return false;
}
}
-
+
/**
- *
+ * @param $dir
*/
public function setImageBasePath( $dir ) {
$this->mImageBasePath = $dir;
}
+
+ /**
+ * @param $import
+ */
public function setImportUploads( $import ) {
$this->mImportUploads = $import;
}
@@ -189,6 +216,7 @@ class WikiImporter {
/**
* Default per-revision callback, performs the import.
* @param $revision WikiRevision
+ * @return bool
*/
public function importRevision( $revision ) {
$dbw = wfGetDB( DB_MASTER );
@@ -198,6 +226,7 @@ class WikiImporter {
/**
* Default per-revision callback, performs the import.
* @param $rev WikiRevision
+ * @return bool
*/
public function importLogItem( $rev ) {
$dbw = wfGetDB( DB_MASTER );
@@ -206,6 +235,8 @@ class WikiImporter {
/**
* Dummy for now...
+ * @param $revision
+ * @return bool
*/
public function importUpload( $revision ) {
$dbw = wfGetDB( DB_MASTER );
@@ -214,6 +245,12 @@ class WikiImporter {
/**
* Mostly for hook use
+ * @param $title
+ * @param $origTitle
+ * @param $revCount
+ * @param $sRevCount
+ * @param $pageInfo
+ * @return
*/
public function finishImportPage( $title, $origTitle, $revCount, $sRevCount, $pageInfo ) {
$args = func_get_args();
@@ -403,6 +440,10 @@ class WikiImporter {
return true;
}
+ /**
+ * @return bool
+ * @throws MWException
+ */
private function handleSiteInfo() {
// Site info is useful, but not actually used for dump imports.
// Includes a quick short-circuit to save performance.
@@ -444,6 +485,10 @@ class WikiImporter {
$this->processLogItem( $logInfo );
}
+ /**
+ * @param $logInfo
+ * @return bool|mixed
+ */
private function processLogItem( $logInfo ) {
$revision = new WikiRevision;
@@ -453,6 +498,7 @@ class WikiImporter {
$revision->setTimestamp( $logInfo['timestamp'] );
$revision->setParams( $logInfo['params'] );
$revision->setTitle( Title::newFromText( $logInfo['logtitle'] ) );
+ $revision->setNoUpdates( $this->mNoUpdates );
if ( isset( $logInfo['comment'] ) ) {
$revision->setComment( $logInfo['comment'] );
@@ -522,6 +568,9 @@ class WikiImporter {
$pageInfo );
}
+ /**
+ * @param $pageInfo array
+ */
private function handleRevision( &$pageInfo ) {
$this->debug( "Enter revision handler" );
$revisionInfo = array();
@@ -557,6 +606,11 @@ class WikiImporter {
}
}
+ /**
+ * @param $pageInfo
+ * @param $revisionInfo
+ * @return bool|mixed
+ */
private function processRevision( $pageInfo, $revisionInfo ) {
$revision = new WikiRevision;
@@ -587,10 +641,15 @@ class WikiImporter {
if ( isset( $revisionInfo['contributor']['username'] ) ) {
$revision->setUserName( $revisionInfo['contributor']['username'] );
}
+ $revision->setNoUpdates( $this->mNoUpdates );
return $this->revisionCallback( $revision );
}
+ /**
+ * @param $pageInfo
+ * @return mixed
+ */
private function handleUpload( &$pageInfo ) {
$this->debug( "Enter upload handler" );
$uploadInfo = array();
@@ -627,7 +686,7 @@ class WikiImporter {
$skip = true;
}
}
-
+
if ( $this->mImageBasePath && isset( $uploadInfo['rel'] ) ) {
$path = "{$this->mImageBasePath}/{$uploadInfo['rel']}";
if ( file_exists( $path ) ) {
@@ -640,14 +699,22 @@ class WikiImporter {
return $this->processUpload( $pageInfo, $uploadInfo );
}
}
-
+
+ /**
+ * @param $contents
+ * @return string
+ */
private function dumpTemp( $contents ) {
$filename = tempnam( wfTempDir(), 'importupload' );
file_put_contents( $filename, $contents );
return $filename;
}
-
+ /**
+ * @param $pageInfo
+ * @param $uploadInfo
+ * @return mixed
+ */
private function processUpload( $pageInfo, $uploadInfo ) {
$revision = new WikiRevision;
$text = isset( $uploadInfo['text'] ) ? $uploadInfo['text'] : '';
@@ -677,10 +744,14 @@ class WikiImporter {
if ( isset( $uploadInfo['contributor']['username'] ) ) {
$revision->setUserName( $uploadInfo['contributor']['username'] );
}
+ $revision->setNoUpdates( $this->mNoUpdates );
return call_user_func( $this->mUploadCallback, $revision );
}
+ /**
+ * @return array
+ */
private function handleContributor() {
$fields = array( 'id', 'ip', 'username' );
$info = array();
@@ -701,7 +772,13 @@ class WikiImporter {
return $info;
}
+ /**
+ * @param $text string
+ * @return Array or false
+ */
private function processTitle( $text ) {
+ global $wgCommandLineMode;
+
$workTitle = $text;
$origTitle = Title::newFromText( $workTitle );
@@ -713,11 +790,22 @@ class WikiImporter {
}
if( is_null( $title ) ) {
- // Invalid page title? Ignore the page
- $this->notice( "Skipping invalid page title '$workTitle'" );
+ # Invalid page title? Ignore the page
+ $this->notice( 'import-error-invalid', $workTitle );
+ return false;
+ } elseif( $title->isExternal() ) {
+ $this->notice( 'import-error-interwiki', $title->getPrefixedText() );
+ return false;
+ } elseif( !$title->canExist() ) {
+ $this->notice( 'import-error-special', $title->getPrefixedText() );
+ return false;
+ } elseif( !$title->userCan( 'edit' ) && !$wgCommandLineMode ) {
+ # Do not import if the importing wiki user cannot edit this page
+ $this->notice( 'import-error-edit', $title->getPrefixedText() );
return false;
- } elseif( $title->getInterwiki() != '' ) {
- $this->notice( "Skipping interwiki page title '$workTitle'" );
+ } elseif( !$title->exists() && !$title->userCan( 'create' ) && !$wgCommandLineMode ) {
+ # Do not import if the importing wiki user cannot create this page
+ $this->notice( 'import-error-create', $title->getPrefixedText() );
return false;
}
@@ -733,6 +821,10 @@ class UploadSourceAdapter {
private $mBuffer;
private $mPosition;
+ /**
+ * @param $source
+ * @return string
+ */
static function registerSource( $source ) {
$id = wfGenerateToken();
@@ -741,6 +833,13 @@ class UploadSourceAdapter {
return $id;
}
+ /**
+ * @param $path
+ * @param $mode
+ * @param $options
+ * @param $opened_path
+ * @return bool
+ */
function stream_open( $path, $mode, $options, &$opened_path ) {
$url = parse_url($path);
$id = $url['host'];
@@ -754,6 +853,10 @@ class UploadSourceAdapter {
return true;
}
+ /**
+ * @param $count
+ * @return string
+ */
function stream_read( $count ) {
$return = '';
$leave = false;
@@ -779,18 +882,31 @@ class UploadSourceAdapter {
return $return;
}
+ /**
+ * @param $data
+ * @return bool
+ */
function stream_write( $data ) {
return false;
}
+ /**
+ * @return mixed
+ */
function stream_tell() {
return $this->mPosition;
}
+ /**
+ * @return bool
+ */
function stream_eof() {
return $this->mSource->atEnd();
}
+ /**
+ * @return array
+ */
function url_stat() {
$result = array();
@@ -813,6 +929,10 @@ class UploadSourceAdapter {
}
class XMLReader2 extends XMLReader {
+
+ /**
+ * @return bool|string
+ */
function nodeContents() {
if( $this->isEmptyElement ) {
return "";
@@ -838,6 +958,10 @@ class XMLReader2 extends XMLReader {
*/
class WikiRevision {
var $importer = null;
+
+ /**
+ * @var Title
+ */
var $title = null;
var $id = 0;
var $timestamp = "20010115000000";
@@ -853,7 +977,13 @@ class WikiRevision {
var $sha1base36 = false;
var $isTemp = false;
var $archiveName = '';
+ var $fileIsTemp;
+ private $mNoUpdates = false;
+ /**
+ * @param $title
+ * @throws MWException
+ */
function setTitle( $title ) {
if( is_object( $title ) ) {
$this->title = $title;
@@ -864,139 +994,253 @@ class WikiRevision {
}
}
+ /**
+ * @param $id
+ */
function setID( $id ) {
$this->id = $id;
}
+ /**
+ * @param $ts
+ */
function setTimestamp( $ts ) {
# 2003-08-05T18:30:02Z
$this->timestamp = wfTimestamp( TS_MW, $ts );
}
+ /**
+ * @param $user
+ */
function setUsername( $user ) {
$this->user_text = $user;
}
+ /**
+ * @param $ip
+ */
function setUserIP( $ip ) {
$this->user_text = $ip;
}
+ /**
+ * @param $text
+ */
function setText( $text ) {
$this->text = $text;
}
+ /**
+ * @param $text
+ */
function setComment( $text ) {
$this->comment = $text;
}
+ /**
+ * @param $minor
+ */
function setMinor( $minor ) {
$this->minor = (bool)$minor;
}
+ /**
+ * @param $src
+ */
function setSrc( $src ) {
$this->src = $src;
}
+
+ /**
+ * @param $src
+ * @param $isTemp
+ */
function setFileSrc( $src, $isTemp ) {
$this->fileSrc = $src;
$this->fileIsTemp = $isTemp;
}
- function setSha1Base36( $sha1base36 ) {
+
+ /**
+ * @param $sha1base36
+ */
+ function setSha1Base36( $sha1base36 ) {
$this->sha1base36 = $sha1base36;
}
+ /**
+ * @param $filename
+ */
function setFilename( $filename ) {
$this->filename = $filename;
}
+
+ /**
+ * @param $archiveName
+ */
function setArchiveName( $archiveName ) {
$this->archiveName = $archiveName;
}
+ /**
+ * @param $size
+ */
function setSize( $size ) {
$this->size = intval( $size );
}
+ /**
+ * @param $type
+ */
function setType( $type ) {
$this->type = $type;
}
+ /**
+ * @param $action
+ */
function setAction( $action ) {
$this->action = $action;
}
+ /**
+ * @param $params
+ */
function setParams( $params ) {
$this->params = $params;
}
/**
+ * @param $noupdates
+ */
+ public function setNoUpdates( $noupdates ) {
+ $this->mNoUpdates = $noupdates;
+ }
+
+ /**
* @return Title
*/
function getTitle() {
return $this->title;
}
+ /**
+ * @return int
+ */
function getID() {
return $this->id;
}
+ /**
+ * @return string
+ */
function getTimestamp() {
return $this->timestamp;
}
+ /**
+ * @return string
+ */
function getUser() {
return $this->user_text;
}
+ /**
+ * @return string
+ */
function getText() {
return $this->text;
}
+ /**
+ * @return string
+ */
function getComment() {
return $this->comment;
}
+ /**
+ * @return bool
+ */
function getMinor() {
return $this->minor;
}
+ /**
+ * @return mixed
+ */
function getSrc() {
return $this->src;
}
+
+ /**
+ * @return bool|String
+ */
function getSha1() {
if ( $this->sha1base36 ) {
return wfBaseConvert( $this->sha1base36, 36, 16 );
}
return false;
}
+
+ /**
+ * @return string
+ */
function getFileSrc() {
return $this->fileSrc;
}
+
+ /**
+ * @return bool
+ */
function isTempSrc() {
return $this->isTemp;
}
+ /**
+ * @return mixed
+ */
function getFilename() {
return $this->filename;
}
+
+ /**
+ * @return string
+ */
function getArchiveName() {
return $this->archiveName;
}
+ /**
+ * @return mixed
+ */
function getSize() {
return $this->size;
}
+ /**
+ * @return string
+ */
function getType() {
return $this->type;
}
+ /**
+ * @return string
+ */
function getAction() {
return $this->action;
}
+ /**
+ * @return string
+ */
function getParams() {
return $this->params;
}
+ /**
+ * @return bool
+ */
function importOldRevision() {
$dbw = wfGetDB( DB_MASTER );
@@ -1016,14 +1260,14 @@ class WikiRevision {
$linkCache = LinkCache::singleton();
$linkCache->clear();
- $article = new Article( $this->title );
- $pageId = $article->getId();
- if( $pageId == 0 ) {
+ $page = WikiPage::factory( $this->title );
+ if( !$page->exists() ) {
# must create the page...
- $pageId = $article->insertOn( $dbw );
+ $pageId = $page->insertOn( $dbw );
$created = true;
$oldcountable = null;
} else {
+ $pageId = $page->getId();
$created = false;
$prior = $dbw->selectField( 'revision', '1',
@@ -1039,7 +1283,7 @@ class WikiRevision {
$this->title->getPrefixedText() . "]], timestamp " . $this->timestamp . "\n" );
return false;
}
- $oldcountable = $article->isCountable();
+ $oldcountable = $page->isCountable();
}
# @todo FIXME: Use original rev_id optionally (better for backups)
@@ -1054,16 +1298,19 @@ class WikiRevision {
'minor_edit' => $this->minor,
) );
$revision->insertOn( $dbw );
- $changed = $article->updateIfNewerOn( $dbw, $revision );
+ $changed = $page->updateIfNewerOn( $dbw, $revision );
- if ( $changed !== false ) {
+ if ( $changed !== false && !$this->mNoUpdates ) {
wfDebug( __METHOD__ . ": running updates\n" );
- $article->doEditUpdates( $revision, $userObj, array( 'created' => $created, 'oldcountable' => $oldcountable ) );
+ $page->doEditUpdates( $revision, $userObj, array( 'created' => $created, 'oldcountable' => $oldcountable ) );
}
return true;
}
+ /**
+ * @return mixed
+ */
function importLogItem() {
$dbw = wfGetDB( DB_MASTER );
# @todo FIXME: This will not record autoblocks
@@ -1089,7 +1336,7 @@ class WikiRevision {
if( $prior ) {
wfDebug( __METHOD__ . ": skipping existing item for Log:{$this->type}/{$this->action}, timestamp " .
$this->timestamp . "\n" );
- return false;
+ return;
}
$log_id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
$data = array(
@@ -1107,19 +1354,22 @@ class WikiRevision {
$dbw->insert( 'logging', $data, __METHOD__ );
}
+ /**
+ * @return bool
+ */
function importUpload() {
# 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 );
+ $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(),
+ $file = OldLocalFile::newFromArchiveName( $this->getTitle(),
RepoGroup::singleton()->getLocalRepo(), $archiveName );
wfDebug( __METHOD__ . "File already exists; importing as $archiveName\n" );
}
@@ -1128,7 +1378,7 @@ class WikiRevision {
wfDebug( __METHOD__ . ': Bad file for ' . $this->getTitle() . "\n" );
return false;
}
-
+
# Get the file source or download if necessary
$source = $this->getFileSrc();
$flags = $this->isTempSrc() ? File::DELETE_SOURCE : 0;
@@ -1151,16 +1401,16 @@ class WikiRevision {
}
$user = User::newFromName( $this->user_text );
-
+
# Do the actual upload
if ( $archiveName ) {
- $status = $file->uploadOld( $source, $archiveName,
+ $status = $file->uploadOld( $source, $archiveName,
$this->getTimestamp(), $this->getComment(), $user, $flags );
} else {
- $status = $file->upload( $source, $this->getComment(), $this->getComment(),
+ $status = $file->upload( $source, $this->getComment(), $this->getComment(),
$flags, false, $this->getTimestamp(), $user );
}
-
+
if ( $status->isGood() ) {
wfDebug( __METHOD__ . ": Succesful\n" );
return true;
@@ -1170,6 +1420,9 @@ class WikiRevision {
}
}
+ /**
+ * @return bool|string
+ */
function downloadSource() {
global $wgEnableUploads;
if( !$wgEnableUploads ) {
@@ -1211,17 +1464,22 @@ class ImportStringSource {
$this->mRead = false;
}
+ /**
+ * @return bool
+ */
function atEnd() {
return $this->mRead;
}
+ /**
+ * @return bool|string
+ */
function readChunk() {
if( $this->atEnd() ) {
return false;
- } else {
- $this->mRead = true;
- return $this->mString;
}
+ $this->mRead = true;
+ return $this->mString;
}
}
@@ -1234,14 +1492,24 @@ class ImportStreamSource {
$this->mHandle = $handle;
}
+ /**
+ * @return bool
+ */
function atEnd() {
return feof( $this->mHandle );
}
+ /**
+ * @return string
+ */
function readChunk() {
return fread( $this->mHandle, 32768 );
}
+ /**
+ * @param $filename string
+ * @return Status
+ */
static function newFromFile( $filename ) {
wfSuppressWarnings();
$file = fopen( $filename, 'rt' );
@@ -1252,6 +1520,10 @@ class ImportStreamSource {
return Status::newGood( new ImportStreamSource( $file ) );
}
+ /**
+ * @param $fieldname string
+ * @return Status
+ */
static function newFromUpload( $fieldname = "xmlimport" ) {
$upload =& $_FILES[$fieldname];
@@ -1280,6 +1552,11 @@ class ImportStreamSource {
}
}
+ /**
+ * @param $url
+ * @param $method string
+ * @return Status
+ */
static function newFromURL( $url, $method = 'GET' ) {
wfDebug( __METHOD__ . ": opening $url\n" );
# Use the standard HTTP fetch function; it times out
@@ -1298,6 +1575,14 @@ class ImportStreamSource {
}
}
+ /**
+ * @param $interwiki
+ * @param $page
+ * @param $history bool
+ * @param $templates bool
+ * @param $pageLinkDepth int
+ * @return Status
+ */
public static function newFromInterwiki( $interwiki, $page, $history = false, $templates = false, $pageLinkDepth = 0 ) {
if( $page == '' ) {
return Status::newFatal( 'import-noarticle' );
diff --git a/includes/Init.php b/includes/Init.php
index de867282..72c10543 100644
--- a/includes/Init.php
+++ b/includes/Init.php
@@ -23,7 +23,7 @@ class MWInit {
}
/**
- * Returns true if we are running under HipHop, whether in compiled or
+ * Returns true if we are running under HipHop, whether in compiled or
* interpreted mode.
*
* @return bool
@@ -47,10 +47,10 @@ class MWInit {
}
/**
- * 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
+ * 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
@@ -94,7 +94,7 @@ class MWInit {
}
/**
- * Register an extension setup file and return its path for compiled
+ * 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:
*
@@ -130,13 +130,13 @@ class MWInit {
/**
* 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.
+ * 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
+ * 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
@@ -153,11 +153,32 @@ class MWInit {
}
/**
- * Determine whether a function exists, using a method which works under
+ * Determine wether a method exists within a class, using a method which works
+ * under HipHop.
+ *
+ * Note that under HipHop when method_exists is given a string for it's class
+ * such as to test for a static method has the same issues as class_exists does.
+ *
+ * @param $class string
+ * @param $method string
+ *
+ * @return bool
+ */
+ static function methodExists( $class, $method ) {
+ try {
+ $r = new ReflectionMethod( $class, $method );
+ } 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 ) {
diff --git a/includes/Licenses.php b/includes/Licenses.php
index 09fa8db3..8a06c6fc 100644
--- a/includes/Licenses.php
+++ b/includes/Licenses.php
@@ -134,6 +134,10 @@ class Licenses extends HTMLFormField {
return str_repeat( "\t", $depth ) . Xml::element( 'option', $attribs, $val ) . "\n";
}
+ /**
+ * @param $str string
+ * @return String
+ */
protected function msg( $str ) {
$msg = wfMessage( $str );
return $msg->exists() ? $msg->text() : $str;
diff --git a/includes/Linker.php b/includes/Linker.php
index e1e554c3..3691d040 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -12,6 +12,7 @@ class Linker {
* Flags for userToolLinks()
*/
const TOOL_LINKS_NOBLOCK = 1;
+ const TOOL_LINKS_EMAIL = 2;
/**
* Get the appropriate HTML attributes to add to the "a" element of an ex-
@@ -22,7 +23,7 @@ class Linker {
* @deprecated since 1.18 Just pass the external class directly to something using Html::expandAttributes
*/
static function getExternalLinkAttributes( $class = 'external' ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
return self::getLinkAttributesInternal( '', $class );
}
@@ -108,7 +109,7 @@ class Linker {
* @param $threshold Integer: user defined threshold
* @return String: CSS class
*/
- static function getLinkColour( $t, $threshold ) {
+ public static function getLinkColour( $t, $threshold ) {
$colour = '';
if ( $t->isRedirect() ) {
# Page is a redirect
@@ -134,9 +135,14 @@ class Linker {
* name of the target).
* link() replaces the old functions in the makeLink() family.
*
+ * @since 1.18 Method exists since 1.16 as non-static, made static in 1.18.
+ * You can call it using this if you want to keep compat with these:
+ * $linker = class_exists( 'DummyLinker' ) ? new DummyLinker() : new Linker();
+ * $linker->link( ... );
+ *
* @param $target Title Can currently only be a Title, but this may
* change to support Images, literal URLs, etc.
- * @param $text string The HTML contents of the <a> element, i.e.,
+ * @param $html string The HTML contents of the <a> element, i.e.,
* the link text. This is raw HTML and will not be escaped. If null,
* defaults to the prefixed text of the Title; or if the Title is just a
* fragment, the contents of the fragment.
@@ -225,16 +231,18 @@ class Linker {
* Identical to link(), except $options defaults to 'known'.
*/
public static function linkKnown(
- $target, $text = null, $customAttribs = array(),
+ $target, $html = null, $customAttribs = array(),
$query = array(), $options = array( 'known', 'noclasses' ) )
{
- return self::link( $target, $text, $customAttribs, $query, $options );
+ return self::link( $target, $html, $customAttribs, $query, $options );
}
/**
* Returns the Url used to link to a Title
*
* @param $target Title
+ * @param $query Array: query parameters
+ * @param $options Array
*/
private static function linkUrl( $target, $query, $options ) {
wfProfileIn( __METHOD__ );
@@ -249,11 +257,11 @@ class Linker {
# there's already an action specified, or unless 'edit' makes no sense
# (i.e., for a nonexistent special page).
if ( in_array( 'broken', $options ) && empty( $query['action'] )
- && $target->getNamespace() != NS_SPECIAL ) {
+ && !$target->isSpecialPage() ) {
$query['action'] = 'edit';
$query['redlink'] = '1';
}
- $ret = $target->getLinkUrl( $query );
+ $ret = $target->getLinkURL( $query );
wfProfileOut( __METHOD__ );
return $ret;
}
@@ -358,7 +366,7 @@ class Linker {
*/
static function makeSizeLinkObj( $size, $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
global $wgUser;
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.17' );
$threshold = $wgUser->getStubThreshold();
$colour = ( $size < $threshold ) ? 'stub' : '';
@@ -375,12 +383,12 @@ class Linker {
*
* @return string
*/
- static function makeSelfLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
- if ( $text == '' ) {
- $text = htmlspecialchars( $nt->getPrefixedText() );
+ public static function makeSelfLinkObj( $nt, $html = '', $query = '', $trail = '', $prefix = '' ) {
+ if ( $html == '' ) {
+ $html = htmlspecialchars( $nt->getPrefixedText() );
}
list( $inside, $trail ) = self::splitTrail( $trail );
- return "<strong class=\"selflink\">{$prefix}{$text}{$inside}</strong>{$trail}";
+ return "<strong class=\"selflink\">{$prefix}{$html}{$inside}</strong>{$trail}";
}
/**
@@ -388,7 +396,7 @@ class Linker {
* @return Title
*/
static function normaliseSpecialPage( Title $title ) {
- if ( $title->getNamespace() == NS_SPECIAL ) {
+ if ( $title->isSpecialPage() ) {
list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
if ( !$name ) {
return $title;
@@ -409,7 +417,7 @@ class Linker {
*
* @return string
*/
- static function fnamePart( $url ) {
+ private static function fnamePart( $url ) {
$basename = strrchr( $url, '/' );
if ( false === $basename ) {
$basename = $url;
@@ -428,7 +436,7 @@ class Linker {
*
* @return string
*/
- static function makeExternalImage( $url, $alt = '' ) {
+ public static function makeExternalImage( $url, $alt = '' ) {
if ( $alt == '' ) {
$alt = self::fnamePart( $url );
}
@@ -477,7 +485,7 @@ class Linker {
* @param $widthOption: Used by the parser to remember the user preference thumbnailsize
* @return String: HTML for an image, with links, wrappers, etc.
*/
- static function makeImageLink2( Title $title, $file, $frameParams = array(),
+ public static function makeImageLink2( Title $title, $file, $frameParams = array(),
$handlerParams = array(), $time = false, $query = "", $widthOption = null )
{
$res = null;
@@ -604,7 +612,7 @@ class Linker {
* @param $frameParams The frame parameters
* @param $query An optional query string to add to description page links
*/
- static function getImageLinkMTOParams( $frameParams, $query = '' ) {
+ private static function getImageLinkMTOParams( $frameParams, $query = '' ) {
$mtoParams = array();
if ( isset( $frameParams['link-url'] ) && $frameParams['link-url'] !== '' ) {
$mtoParams['custom-url-link'] = $frameParams['link-url'];
@@ -633,7 +641,7 @@ class Linker {
* @param $framed Boolean
* @param $manualthumb String
*/
- static function makeThumbLinkObj( Title $title, $file, $label = '', $alt,
+ public static function makeThumbLinkObj( Title $title, $file, $label = '', $alt,
$align = 'right', $params = array(), $framed = false , $manualthumb = "" )
{
$frameParams = array(
@@ -659,7 +667,7 @@ class Linker {
* @param string $query
* @return mixed
*/
- static function makeThumbLink2( Title $title, $file, $frameParams = array(),
+ public static function makeThumbLink2( Title $title, $file, $frameParams = array(),
$handlerParams = array(), $time = false, $query = "" )
{
global $wgStylePath, $wgContLang;
@@ -760,31 +768,31 @@ class Linker {
* Make a "broken" link to an image
*
* @param $title Title object
- * @param $text String: link label in unescaped text form
+ * @param $html String: link label in htmlescaped text form
* @param $query String: query string
* @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 static function makeBrokenImageLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '', $time = false ) {
- global $wgEnableUploads, $wgUploadMissingFileUrl;
+ public static function makeBrokenImageLinkObj( $title, $html = '', $query = '', $trail = '', $prefix = '', $time = false ) {
+ global $wgEnableUploads, $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
if ( ! $title instanceof Title ) {
- return "<!-- ERROR -->{$prefix}{$text}{$trail}";
+ return "<!-- ERROR -->{$prefix}{$html}{$trail}";
}
wfProfileIn( __METHOD__ );
$currentExists = $time ? ( wfFindFile( $title ) != false ) : false;
list( $inside, $trail ) = self::splitTrail( $trail );
- if ( $text == '' )
- $text = htmlspecialchars( $title->getPrefixedText() );
+ if ( $html == '' )
+ $html = htmlspecialchars( $title->getPrefixedText() );
- if ( ( $wgUploadMissingFileUrl || $wgEnableUploads ) && !$currentExists ) {
+ if ( ( $wgUploadMissingFileUrl || $wgUploadNavigationUrl || $wgEnableUploads ) && !$currentExists ) {
$redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
if ( $redir ) {
wfProfileOut( __METHOD__ );
- return self::linkKnown( $title, "$prefix$text$inside", array(), $query ) . $trail;
+ return self::linkKnown( $title, "$prefix$html$inside", array(), $query ) . $trail;
}
$href = self::getUploadUrl( $title, $query );
@@ -792,10 +800,10 @@ class Linker {
wfProfileOut( __METHOD__ );
return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
- "$prefix$text$inside</a>$trail";
+ "$prefix$html$inside</a>$trail";
} else {
wfProfileOut( __METHOD__ );
- return self::linkKnown( $title, "$prefix$text$inside", array(), $query ) . $trail;
+ return self::linkKnown( $title, "$prefix$html$inside", array(), $query ) . $trail;
}
}
@@ -807,13 +815,15 @@ class Linker {
* @return String: urlencoded URL
*/
protected static function getUploadUrl( $destFile, $query = '' ) {
- global $wgUploadMissingFileUrl;
+ global $wgUploadMissingFileUrl, $wgUploadNavigationUrl;
$q = 'wpDestFile=' . $destFile->getPartialUrl();
if ( $query != '' )
$q .= '&' . $query;
if ( $wgUploadMissingFileUrl ) {
return wfAppendQuery( $wgUploadMissingFileUrl, $q );
+ } elseif( $wgUploadNavigationUrl ) {
+ return wfAppendQuery( $wgUploadNavigationUrl, $q );
} else {
$upload = SpecialPage::getTitleFor( 'Upload' );
return $upload->getLocalUrl( $q );
@@ -824,13 +834,13 @@ class Linker {
* Create a direct link to a given uploaded file.
*
* @param $title Title object.
- * @param $text String: pre-sanitized HTML
+ * @param $html String: pre-sanitized HTML
* @param $time string: MW timestamp of file creation time
* @return String: HTML
*/
- public static function makeMediaLinkObj( $title, $text = '', $time = false ) {
+ public static function makeMediaLinkObj( $title, $html = '', $time = false ) {
$img = wfFindFile( $title, array( 'time' => $time ) );
- return self::makeMediaLinkFile( $title, $img, $text );
+ return self::makeMediaLinkFile( $title, $img, $html );
}
/**
@@ -839,12 +849,12 @@ class Linker {
*
* @param $title Title object.
* @param $file File|false mixed File object or false
- * @param $text String: pre-sanitized HTML
+ * @param $html String: pre-sanitized HTML
* @return String: HTML
*
* @todo Handle invalid or missing images better.
*/
- public static function makeMediaLinkFile( Title $title, $file, $text = '' ) {
+ public static function makeMediaLinkFile( Title $title, $file, $html = '' ) {
if ( $file && $file->exists() ) {
$url = $file->getURL();
$class = 'internal';
@@ -853,21 +863,21 @@ class Linker {
$class = 'new';
}
$alt = htmlspecialchars( $title->getText(), ENT_QUOTES );
- if ( $text == '' ) {
- $text = $alt;
+ if ( $html == '' ) {
+ $html = $alt;
}
$u = htmlspecialchars( $url );
- return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$text}</a>";
+ return "<a href=\"{$u}\" class=\"$class\" title=\"{$alt}\">{$html}</a>";
}
/**
* Make a link to a special page given its name and, optionally,
* a message key from the link text.
- * Usage example: $skin->specialLink( 'recentchanges' )
+ * Usage example: Linker::specialLink( 'Recentchanges' )
*
- * @return bool
+ * @return string
*/
- static function specialLink( $name, $key = '' ) {
+ public static function specialLink( $name, $key = '' ) {
if ( $key == '' ) {
$key = strtolower( $name );
}
@@ -883,12 +893,12 @@ class Linker {
* @param $linktype String: type of external link. Gets added to the classes
* @param $attribs Array of extra attributes to <a>
*/
- static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) {
+ public static function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) {
$class = "external";
- if ( isset($linktype) && $linktype ) {
+ if ( $linktype ) {
$class .= " $linktype";
}
- if ( isset($attribs['class']) && $attribs['class'] ) {
+ if ( isset( $attribs['class'] ) && $attribs['class'] ) {
$class .= " {$attribs['class']}";
}
$attribs['class'] = $class;
@@ -910,17 +920,23 @@ class Linker {
/**
* Make user link (or user contributions for unregistered users)
* @param $userId Integer: user id in database.
- * @param $userText String: user name in database
+ * @param $userName String: user name in database.
+ * @param $altUserName String: text to display instead of the user name (optional)
* @return String: HTML fragment
- * @private
+ * @since 1.19 Method exists for a long time. $displayText was added in 1.19.
*/
- static function userLink( $userId, $userText ) {
+ public static function userLink( $userId, $userName, $altUserName = false ) {
if ( $userId == 0 ) {
- $page = SpecialPage::getTitleFor( 'Contributions', $userText );
+ $page = SpecialPage::getTitleFor( 'Contributions', $userName );
} else {
- $page = Title::makeTitle( NS_USER, $userText );
+ $page = Title::makeTitle( NS_USER, $userName );
}
- return self::link( $page, htmlspecialchars( $userText ), array( 'class' => 'mw-userlink' ) );
+
+ return self::link(
+ $page,
+ htmlspecialchars( $altUserName !== false ? $altUserName : $userName ),
+ array( 'class' => 'mw-userlink' )
+ );
}
/**
@@ -930,7 +946,7 @@ 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. Linker::TOOL_LINKS_NOBLOCK)
+ * @param $flags Integer: customisation flags (e.g. Linker::TOOL_LINKS_NOBLOCK and Linker::TOOL_LINKS_EMAIL)
* @param $edits Integer: user edit count (optional, for performance)
* @return String: HTML fragment
*/
@@ -939,7 +955,8 @@ class Linker {
) {
global $wgUser, $wgDisableAnonTalk, $wgLang;
$talkable = !( $wgDisableAnonTalk && 0 == $userId );
- $blockable = !$flags & self::TOOL_LINKS_NOBLOCK;
+ $blockable = !( $flags & self::TOOL_LINKS_NOBLOCK );
+ $addEmailLink = $flags & self::TOOL_LINKS_EMAIL && $userId;
$items = array();
if ( $talkable ) {
@@ -962,6 +979,12 @@ class Linker {
$items[] = self::blockLink( $userId, $userText );
}
+ if ( $addEmailLink && $wgUser->canSendEmail() ) {
+ $items[] = self::emailLink( $userId, $userText );
+ }
+
+ wfRunHooks( 'UserToolLinksEdit', array( $userId, $userText, &$items ) );
+
if ( $items ) {
return ' <span class="mw-usertoollinks">(' . $wgLang->pipeList( $items ) . ')</span>';
} else {
@@ -984,9 +1007,8 @@ class Linker {
* @param $userId Integer: user id in database.
* @param $userText String: user name in database.
* @return String: HTML fragment with user talk link
- * @private
*/
- static function userTalkLink( $userId, $userText ) {
+ public static function userTalkLink( $userId, $userText ) {
$userTalkPage = Title::makeTitle( NS_USER_TALK, $userText );
$userTalkLink = self::link( $userTalkPage, wfMsgHtml( 'talkpagelinktext' ) );
return $userTalkLink;
@@ -996,21 +1018,31 @@ class Linker {
* @param $userId Integer: userid
* @param $userText String: user name in database.
* @return String: HTML fragment with block link
- * @private
*/
- static function blockLink( $userId, $userText ) {
+ public static function blockLink( $userId, $userText ) {
$blockPage = SpecialPage::getTitleFor( 'Block', $userText );
$blockLink = self::link( $blockPage, wfMsgHtml( 'blocklink' ) );
return $blockLink;
}
/**
+ * @param $userId Integer: userid
+ * @param $userText String: user name in database.
+ * @return String: HTML fragment with e-mail user link
+ */
+ public static function emailLink( $userId, $userText ) {
+ $emailPage = SpecialPage::getTitleFor( 'Emailuser', $userText );
+ $emailLink = self::link( $emailPage, wfMsgHtml( 'emaillink' ) );
+ return $emailLink;
+ }
+
+ /**
* Generate a user link if the current user is allowed to view it
* @param $rev Revision object.
* @param $isPublic Boolean: show only if all users can see it
* @return String: HTML fragment
*/
- static function revUserLink( $rev, $isPublic = false ) {
+ public static function revUserLink( $rev, $isPublic = false ) {
if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
$link = wfMsgHtml( 'rev-deleted-user' );
} elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
@@ -1031,7 +1063,7 @@ class Linker {
* @param $isPublic Boolean: show only if all users can see it
* @return string HTML
*/
- static function revUserTools( $rev, $isPublic = false ) {
+ public static function revUserTools( $rev, $isPublic = false ) {
if ( $rev->isDeleted( Revision::DELETED_USER ) && $isPublic ) {
$link = wfMsgHtml( 'rev-deleted-user' );
} elseif ( $rev->userCan( Revision::DELETED_USER ) ) {
@@ -1064,7 +1096,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
*/
- static function formatComment( $comment, $title = null, $local = false ) {
+ public static function formatComment( $comment, $title = null, $local = false ) {
wfProfileIn( __METHOD__ );
# Sanitize text a bit:
@@ -1116,6 +1148,7 @@ class Linker {
* @return string
*/
private static function formatAutocommentsCallback( $match ) {
+ global $wgLang;
$title = self::$autocommentTitle;
$local = self::$autocommentLocal;
@@ -1141,23 +1174,22 @@ class Linker {
}
if ( $sectionTitle ) {
$link = self::link( $sectionTitle,
- htmlspecialchars( wfMsgForContent( 'sectionlink' ) ), array(), array(),
+ $wgLang->getArrow(), array(), array(),
'noclasses' );
} else {
$link = '';
}
}
- $auto = "$link$auto";
if ( $pre ) {
# written summary $presep autocomment (summary /* section */)
- $auto = wfMsgExt( 'autocomment-prefix', array( 'escapenoentities', 'content' ) ) . $auto;
+ $pre .= wfMsgExt( 'autocomment-prefix', array( 'escapenoentities', 'content' ) );
}
if ( $post ) {
# autocomment $postsep written summary (/* section */ summary)
$auto .= wfMsgExt( 'colon-separator', array( 'escapenoentities', 'content' ) );
}
$auto = '<span class="autocomment">' . $auto . '</span>';
- $comment = $pre . $auto . $post;
+ $comment = $pre . $link . $wgLang->getDirMark() . '<span dir="auto">' . $auto . $post . '</span>';
return $comment;
}
@@ -1266,7 +1298,7 @@ class Linker {
* @param $text
* @return string
*/
- static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
+ public static function normalizeSubpageLink( $contextTitle, $target, &$text ) {
# Valid link forms:
# Foobar -- normal
# :Foobar -- override special treatment of prefix (images, language links)
@@ -1291,7 +1323,7 @@ class Linker {
# bug 7425
$target = trim( $target );
# Look at the first character
- if ( $target != '' && $target { 0 } === '/' ) {
+ if ( $target != '' && $target[0] === '/' ) {
# / at end means we don't want the slash to be shown
$m = array();
$trailingSlashes = preg_match_all( '%(/+)$%', $target, $m );
@@ -1348,7 +1380,7 @@ class Linker {
*
* @return string
*/
- static function commentBlock( $comment, $title = null, $local = false ) {
+ public 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
@@ -1356,7 +1388,7 @@ class Linker {
return '';
} else {
$formatted = self::formatComment( $comment, $title, $local );
- return " <span class=\"comment\">($formatted)</span>";
+ return " <span class=\"comment\" dir=\"auto\">($formatted)</span>";
}
}
@@ -1369,7 +1401,7 @@ class Linker {
* @param $isPublic Boolean: show only if all users can see it
* @return String: HTML fragment
*/
- static function revComment( Revision $rev, $local = false, $isPublic = false ) {
+ public static function revComment( Revision $rev, $local = false, $isPublic = false ) {
if ( $rev->getRawComment() == "" ) {
return "";
}
@@ -1408,7 +1440,7 @@ class Linker {
*
* @return string
*/
- static function tocIndent() {
+ public static function tocIndent() {
return "\n<ul>";
}
@@ -1417,7 +1449,7 @@ class Linker {
*
* @return string
*/
- static function tocUnindent( $level ) {
+ public static function tocUnindent( $level ) {
return "</li>\n" . str_repeat( "</ul>\n</li>\n", $level > 0 ? $level : 0 );
}
@@ -1426,7 +1458,7 @@ class Linker {
*
* @return string
*/
- static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
+ public static function tocLine( $anchor, $tocline, $tocnumber, $level, $sectionIndex = false ) {
$classes = "toclevel-$level";
if ( $sectionIndex !== false ) {
$classes .= " tocsection-$sectionIndex";
@@ -1442,7 +1474,7 @@ class Linker {
* tocUnindent() will be used instead if we're ending a line below
* the new level.
*/
- static function tocLineEnd() {
+ public static function tocLineEnd() {
return "</li>\n";
}
@@ -1453,7 +1485,7 @@ class Linker {
* @param $lang mixed: Language code for the toc title
* @return String: full html of the TOC
*/
- static function tocList( $toc, $lang = false ) {
+ public static function tocList( $toc, $lang = false ) {
$title = wfMsgExt( 'toc', array( 'language' => $lang, 'escape' ) );
return
'<table id="toc" class="toc"><tr><td>'
@@ -1498,17 +1530,17 @@ class Linker {
* a space and ending with '>'
* This *must* be at least '>' for no attribs
* @param $anchor String: the anchor to give the headline (the bit after the #)
- * @param $text String: the text of the header
+ * @param $html String: html for the text of the header
* @param $link String: HTML to add for the section edit link
* @param $legacyAnchor Mixed: a second, optional anchor to give for
* backward compatibility (false to omit)
*
* @return String: HTML headline
*/
- public static function makeHeadline( $level, $attribs, $anchor, $text, $link, $legacyAnchor = false ) {
+ public static function makeHeadline( $level, $attribs, $anchor, $html, $link, $legacyAnchor = false ) {
$ret = "<h$level$attribs"
. $link
- . " <span class=\"mw-headline\" id=\"$anchor\">$text</span>"
+ . " <span class=\"mw-headline\" id=\"$anchor\">$html</span>"
. "</h$level>";
if ( $legacyAnchor !== false ) {
$ret = "<div id=\"$legacyAnchor\"></div>$ret";
@@ -1547,7 +1579,7 @@ class Linker {
*
* @param $rev Revision object
*/
- static function generateRollback( $rev ) {
+ public static function generateRollback( $rev ) {
return '<span class="mw-rollback-link">['
. self::buildRollbackLink( $rev )
. ']</span>';
@@ -1565,7 +1597,7 @@ class Linker {
$query = array(
'action' => 'rollback',
'from' => $rev->getUserText(),
- 'token' => $wgUser->editToken( array( $title->getPrefixedText(), $rev->getUserText() ) ),
+ 'token' => $wgUser->getEditToken( array( $title->getPrefixedText(), $rev->getUserText() ) ),
);
if ( $wgRequest->getBool( 'bot' ) ) {
$query['bot'] = '1';
@@ -1697,10 +1729,6 @@ class Linker {
* escape), or false for no title attribute
*/
public static function titleAttrib( $name, $options = null ) {
- global $wgEnableTooltipsAndAccesskeys;
- if ( !$wgEnableTooltipsAndAccesskeys )
- return false;
-
wfProfileIn( __METHOD__ );
$message = wfMessage( "tooltip-$name" );
@@ -1769,6 +1797,50 @@ class Linker {
}
/**
+ * 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 User $user
+ * @param Revision $rev
+ * @param Revision $title
+ * @return string HTML fragment
+ */
+ public static function getRevDeleteLink( User $user, Revision $rev, Title $title ) {
+ $canHide = $user->isAllowed( 'deleterevision' );
+ if ( !$canHide && !( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
+ return '';
+ }
+
+ if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
+ return 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' => $title->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' => $title->getPrefixedDBkey(),
+ 'ids' => $rev->getTimestamp()
+ );
+ }
+ return Linker::revDeleteLink( $query,
+ $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
+ }
+ }
+
+ /**
* Creates a (show/hide) link for deleting revisions/log entries
*
* @param $query Array: query parameters to be passed to link()
@@ -1780,9 +1852,9 @@ class Linker {
*/
public static function revDeleteLink( $query = array(), $restricted = false, $delete = true ) {
$sp = SpecialPage::getTitleFor( 'Revisiondelete' );
- $text = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' );
+ $html = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' );
$tag = $restricted ? 'strong' : 'span';
- $link = self::link( $sp, $text, array(), $query, array( 'known', 'noclasses' ) );
+ $link = self::link( $sp, $html, array(), $query, array( 'known', 'noclasses' ) );
return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), "($link)" );
}
@@ -1795,8 +1867,8 @@ class Linker {
* of appearance with CSS
*/
public static function revDeleteLinkDisabled( $delete = true ) {
- $text = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' );
- return Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), "($text)" );
+ $html = $delete ? wfMsgHtml( 'rev-delundel' ) : wfMsgHtml( 'rev-showdeleted' );
+ return Xml::tags( 'span', array( 'class' => 'mw-revdelundel-link' ), "($html)" );
}
/* Deprecated methods */
@@ -1815,6 +1887,8 @@ class Linker {
* the end of the link.
*/
static function makeBrokenLink( $title, $text = '', $query = '', $trail = '' ) {
+ wfDeprecated( __METHOD__, '1.16' );
+
$nt = Title::newFromText( $title );
if ( $nt instanceof Title ) {
return self::makeBrokenLinkObj( $nt, $text, $query, $trail );
@@ -1841,6 +1915,8 @@ class Linker {
* @param $prefix String: optional prefix. As trail, only before instead of after.
*/
static function makeLinkObj( $nt, $text = '', $query = '', $trail = '', $prefix = '' ) {
+ # wfDeprecated( __METHOD__, '1.16' ); // See r105985 and it's revert. Somewhere still used.
+
wfProfileIn( __METHOD__ );
$query = wfCgiToArray( $query );
list( $inside, $trail ) = self::splitTrail( $trail );
@@ -1873,6 +1949,8 @@ class Linker {
static function makeKnownLinkObj(
$title, $text = '', $query = '', $trail = '', $prefix = '' , $aprops = '', $style = ''
) {
+ # wfDeprecated( __METHOD__, '1.16' ); // See r105985 and it's revert. Somewhere still used.
+
wfProfileIn( __METHOD__ );
if ( $text == '' ) {
@@ -1906,6 +1984,8 @@ class Linker {
* @param $prefix String: Optional prefix
*/
static function makeBrokenLinkObj( $title, $text = '', $query = '', $trail = '', $prefix = '' ) {
+ wfDeprecated( __METHOD__, '1.16' );
+
wfProfileIn( __METHOD__ );
list( $inside, $trail ) = self::splitTrail( $trail );
@@ -1935,6 +2015,8 @@ class Linker {
* @param $prefix String: Optional prefix
*/
static function makeColouredLinkObj( $nt, $colour, $text = '', $query = '', $trail = '', $prefix = '' ) {
+ wfDeprecated( __METHOD__, '1.16' );
+
if ( $colour != '' ) {
$style = self::getInternalLinkAttributesObj( $nt, $text, $colour );
} else {
@@ -1947,9 +2029,6 @@ class Linker {
* Returns the attributes for the tooltip and access key.
*/
public static function tooltipAndAccesskeyAttribs( $name ) {
- global $wgEnableTooltipsAndAccesskeys;
- if ( !$wgEnableTooltipsAndAccesskeys )
- return array();
# @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
# no attribute" instead of "output '' as value for attribute", this
# would be three lines.
@@ -1967,13 +2046,9 @@ class Linker {
}
/**
- * @deprecated since 1.14
* Returns raw bits of HTML, use titleAttrib()
*/
public static function tooltip( $name, $options = null ) {
- global $wgEnableTooltipsAndAccesskeys;
- if ( !$wgEnableTooltipsAndAccesskeys )
- return '';
# @todo FIXME: If Sanitizer::expandAttributes() treated "false" as "output
# no attribute" instead of "output '' as value for attribute", this
# would be two lines.
@@ -2003,4 +2078,3 @@ class DummyLinker {
return call_user_func_array( array( 'Linker', $fname ), $args );
}
}
-
diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php
index 1fe6118a..27d1dfd2 100644
--- a/includes/LinksUpdate.php
+++ b/includes/LinksUpdate.php
@@ -26,6 +26,7 @@ class LinksUpdate {
*/
var $mId, //!< Page ID of the article linked from
$mTitle, //!< Title object of the article linked from
+ $mParserOutput, //!< Parser output
$mLinks, //!< Map of title strings to IDs for the links in the document
$mImages, //!< DB keys of the images used, in the array key only
$mTemplates, //!< Map of title strings to IDs for the template references, including broken ones
@@ -135,7 +136,7 @@ class LinksUpdate {
# External links
$existing = $this->getExistingExternals();
$this->incrTableUpdate( 'externallinks', 'el', $this->getExternalDeletions( $existing ),
- $this->getExternalInsertions( $existing ) );
+ $this->getExternalInsertions( $existing ) );
# Language links
$existing = $this->getExistingInterlangs();
@@ -241,6 +242,7 @@ class LinksUpdate {
foreach ( $batches as $batch ) {
list( $start, $end ) = $batch;
$params = array(
+ 'table' => 'templatelinks',
'start' => $start,
'end' => $end,
);
@@ -296,27 +298,38 @@ class LinksUpdate {
);
}
+ /**
+ * @param $cats
+ */
function invalidateCategories( $cats ) {
$this->invalidatePages( NS_CATEGORY, array_keys( $cats ) );
}
/**
* Update all the appropriate counts in the category table.
- * @param $added associative array of category name => sort key
- * @param $deleted associative array of category name => sort key
+ * @param $added array associative array of category name => sort key
+ * @param $deleted array associative array of category name => sort key
*/
function updateCategoryCounts( $added, $deleted ) {
- $a = new Article($this->mTitle);
+ $a = WikiPage::factory( $this->mTitle );
$a->updateCategoryCounts(
array_keys( $added ), array_keys( $deleted )
);
}
+ /**
+ * @param $images
+ */
function invalidateImageDescriptions( $images ) {
$this->invalidatePages( NS_FILE, array_keys( $images ) );
}
- function dumbTableUpdate( $table, $insertions, $fromField ) {
+ /**
+ * @param $table
+ * @param $insertions
+ * @param $fromField
+ */
+ private function dumbTableUpdate( $table, $insertions, $fromField ) {
$this->mDb->delete( $table, array( $fromField => $this->mId ), __METHOD__ );
if ( count( $insertions ) ) {
# The link array was constructed without FOR UPDATE, so there may
@@ -328,7 +341,10 @@ class LinksUpdate {
/**
* Update a table by doing a delete query then an insert query
- * @private
+ * @param $table
+ * @param $prefix
+ * @param $deletions
+ * @param $insertions
*/
function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
if ( $table == 'page_props' ) {
@@ -371,13 +387,13 @@ class LinksUpdate {
}
}
-
/**
* Get an array of pagelinks insertions for passing to the DB
* Skips the titles specified by the 2-D array $existing
- * @private
+ * @param $existing array
+ * @return array
*/
- function getLinkInsertions( $existing = array() ) {
+ private function getLinkInsertions( $existing = array() ) {
$arr = array();
foreach( $this->mLinks as $ns => $dbkeys ) {
$diffs = isset( $existing[$ns] )
@@ -396,9 +412,10 @@ class LinksUpdate {
/**
* Get an array of template insertions. Like getLinkInsertions()
- * @private
+ * @param $existing array
+ * @return array
*/
- function getTemplateInsertions( $existing = array() ) {
+ private function getTemplateInsertions( $existing = array() ) {
$arr = array();
foreach( $this->mTemplates as $ns => $dbkeys ) {
$diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
@@ -416,9 +433,10 @@ class LinksUpdate {
/**
* Get an array of image insertions
* Skips the names specified in $existing
- * @private
+ * @param $existing array
+ * @return array
*/
- function getImageInsertions( $existing = array() ) {
+ private function getImageInsertions( $existing = array() ) {
$arr = array();
$diffs = array_diff_key( $this->mImages, $existing );
foreach( $diffs as $iname => $dummy ) {
@@ -432,9 +450,10 @@ class LinksUpdate {
/**
* Get an array of externallinks insertions. Skips the names specified in $existing
- * @private
+ * @param $existing array
+ * @return array
*/
- function getExternalInsertions( $existing = array() ) {
+ private function getExternalInsertions( $existing = array() ) {
$arr = array();
$diffs = array_diff_key( $this->mExternals, $existing );
foreach( $diffs as $url => $dummy ) {
@@ -452,11 +471,12 @@ class LinksUpdate {
/**
* Get an array of category insertions
*
- * @param $existing Array mapping existing category names to sort keys. If both
+ * @param $existing array mapping existing category names to sort keys. If both
* match a link in $this, the link will be omitted from the output
- * @private
+ *
+ * @return array
*/
- function getCategoryInsertions( $existing = array() ) {
+ private function getCategoryInsertions( $existing = array() ) {
global $wgContLang, $wgCategoryCollation;
$diffs = array_diff_assoc( $this->mCategories, $existing );
$arr = array();
@@ -496,23 +516,26 @@ class LinksUpdate {
* Get an array of interlanguage link insertions
*
* @param $existing Array mapping existing language codes to titles
- * @private
+ *
+ * @return array
*/
- function getInterlangInsertions( $existing = array() ) {
- $diffs = array_diff_assoc( $this->mInterlangs, $existing );
- $arr = array();
- foreach( $diffs as $lang => $title ) {
- $arr[] = array(
- 'll_from' => $this->mId,
- 'll_lang' => $lang,
- 'll_title' => $title
- );
- }
- return $arr;
+ private function getInterlangInsertions( $existing = array() ) {
+ $diffs = array_diff_assoc( $this->mInterlangs, $existing );
+ $arr = array();
+ foreach( $diffs as $lang => $title ) {
+ $arr[] = array(
+ 'll_from' => $this->mId,
+ 'll_lang' => $lang,
+ 'll_title' => $title
+ );
+ }
+ return $arr;
}
/**
* Get an array of page property insertions
+ * @param $existing array
+ * @return array
*/
function getPropertyInsertions( $existing = array() ) {
$diffs = array_diff_assoc( $this->mProperties, $existing );
@@ -530,9 +553,10 @@ class LinksUpdate {
/**
* Get an array of interwiki insertions for passing to the DB
* Skips the titles specified by the 2-D array $existing
- * @private
+ * @param $existing array
+ * @return array
*/
- function getInterwikiInsertions( $existing = array() ) {
+ private function getInterwikiInsertions( $existing = array() ) {
$arr = array();
foreach( $this->mInterwikis as $prefix => $dbkeys ) {
$diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys;
@@ -550,9 +574,10 @@ class LinksUpdate {
/**
* Given an array of existing links, returns those links which are not in $this
* and thus should be deleted.
- * @private
+ * @param $existing array
+ * @return array
*/
- function getLinkDeletions( $existing ) {
+ private function getLinkDeletions( $existing ) {
$del = array();
foreach ( $existing as $ns => $dbkeys ) {
if ( isset( $this->mLinks[$ns] ) ) {
@@ -567,9 +592,10 @@ class LinksUpdate {
/**
* Given an array of existing templates, returns those templates which are not in $this
* and thus should be deleted.
- * @private
+ * @param $existing array
+ * @return array
*/
- function getTemplateDeletions( $existing ) {
+ private function getTemplateDeletions( $existing ) {
$del = array();
foreach ( $existing as $ns => $dbkeys ) {
if ( isset( $this->mTemplates[$ns] ) ) {
@@ -584,42 +610,47 @@ class LinksUpdate {
/**
* Given an array of existing images, returns those images which are not in $this
* and thus should be deleted.
- * @private
+ * @param $existing array
+ * @return array
*/
- function getImageDeletions( $existing ) {
+ private function getImageDeletions( $existing ) {
return array_diff_key( $existing, $this->mImages );
}
/**
* Given an array of existing external links, returns those links which are not
* in $this and thus should be deleted.
- * @private
+ * @param $existing array
+ * @return array
*/
- function getExternalDeletions( $existing ) {
+ private function getExternalDeletions( $existing ) {
return array_diff_key( $existing, $this->mExternals );
}
/**
* Given an array of existing categories, returns those categories which are not in $this
* and thus should be deleted.
- * @private
+ * @param $existing array
+ * @return array
*/
- function getCategoryDeletions( $existing ) {
+ private function getCategoryDeletions( $existing ) {
return array_diff_assoc( $existing, $this->mCategories );
}
/**
* Given an array of existing interlanguage links, returns those links which are not
* in $this and thus should be deleted.
- * @private
+ * @param $existing array
+ * @return array
*/
- function getInterlangDeletions( $existing ) {
- return array_diff_assoc( $existing, $this->mInterlangs );
+ private function getInterlangDeletions( $existing ) {
+ return array_diff_assoc( $existing, $this->mInterlangs );
}
/**
* Get array of properties which should be deleted.
- * @private
+ * @param $existing array
+ * @return array
*/
function getPropertyDeletions( $existing ) {
return array_diff_assoc( $existing, $this->mProperties );
@@ -628,9 +659,10 @@ class LinksUpdate {
/**
* Given an array of existing interwiki links, returns those links which are not in $this
* and thus should be deleted.
- * @private
+ * @param $existing array
+ * @return array
*/
- function getInterwikiDeletions( $existing ) {
+ private function getInterwikiDeletions( $existing ) {
$del = array();
foreach ( $existing as $prefix => $dbkeys ) {
if ( isset( $this->mInterwikis[$prefix] ) ) {
@@ -644,9 +676,10 @@ class LinksUpdate {
/**
* Get an array of existing links, as a 2-D array
- * @private
+ *
+ * @return array
*/
- function getExistingLinks() {
+ private function getExistingLinks() {
$res = $this->mDb->select( 'pagelinks', array( 'pl_namespace', 'pl_title' ),
array( 'pl_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
@@ -661,9 +694,10 @@ class LinksUpdate {
/**
* Get an array of existing templates, as a 2-D array
- * @private
+ *
+ * @return array
*/
- function getExistingTemplates() {
+ private function getExistingTemplates() {
$res = $this->mDb->select( 'templatelinks', array( 'tl_namespace', 'tl_title' ),
array( 'tl_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
@@ -678,9 +712,10 @@ class LinksUpdate {
/**
* Get an array of existing images, image names in the keys
- * @private
+ *
+ * @return array
*/
- function getExistingImages() {
+ private function getExistingImages() {
$res = $this->mDb->select( 'imagelinks', array( 'il_to' ),
array( 'il_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
@@ -692,9 +727,10 @@ class LinksUpdate {
/**
* Get an array of existing external links, URLs in the keys
- * @private
+ *
+ * @return array
*/
- function getExistingExternals() {
+ private function getExistingExternals() {
$res = $this->mDb->select( 'externallinks', array( 'el_to' ),
array( 'el_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
@@ -706,9 +742,10 @@ class LinksUpdate {
/**
* Get an array of existing categories, with the name in the key and sort key in the value.
- * @private
+ *
+ * @return array
*/
- function getExistingCategories() {
+ private function getExistingCategories() {
$res = $this->mDb->select( 'categorylinks', array( 'cl_to', 'cl_sortkey_prefix' ),
array( 'cl_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
@@ -721,9 +758,10 @@ class LinksUpdate {
/**
* Get an array of existing interlanguage links, with the language code in the key and the
* title in the value.
- * @private
+ *
+ * @return array
*/
- function getExistingInterlangs() {
+ private function getExistingInterlangs() {
$res = $this->mDb->select( 'langlinks', array( 'll_lang', 'll_title' ),
array( 'll_from' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
@@ -752,9 +790,10 @@ class LinksUpdate {
/**
* Get an array of existing categories, with the name in the key and sort key in the value.
- * @private
+ *
+ * @return array
*/
- function getExistingProperties() {
+ private function getExistingProperties() {
$res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ),
array( 'pp_page' => $this->mId ), __METHOD__, $this->mOptions );
$arr = array();
@@ -764,16 +803,26 @@ class LinksUpdate {
return $arr;
}
-
/**
* Return the title object of the page being updated
+ * @return Title
*/
- function getTitle() {
+ public function getTitle() {
return $this->mTitle;
}
/**
+ * Returns parser output
+ * @since 1.19
+ * @return ParserOutput
+ */
+ public function getParserOutput() {
+ return $this->mParserOutput;
+ }
+
+ /**
* Return the list of images used as generated by the parser
+ * @return array
*/
public function getImages() {
return $this->mImages;
@@ -781,8 +830,9 @@ class LinksUpdate {
/**
* Invalidate any necessary link lists related to page property changes
+ * @param $changed
*/
- function invalidateProperties( $changed ) {
+ private function invalidateProperties( $changed ) {
global $wgPagePropLinkInvalidations;
foreach ( $changed as $name => $value ) {
diff --git a/includes/LocalisationCache.php b/includes/LocalisationCache.php
index 6d5882d9..3b1f45cc 100644
--- a/includes/LocalisationCache.php
+++ b/includes/LocalisationCache.php
@@ -1,6 +1,6 @@
<?php
-define( 'MW_LC_VERSION', 1 );
+define( 'MW_LC_VERSION', 2 );
/**
* Class for caching the contents of localisation files, Messages*.php
@@ -40,6 +40,8 @@ class LocalisationCache {
/**
* The persistent store object. An instance of LCStore.
+ *
+ * @var LCStore
*/
var $store;
@@ -88,6 +90,7 @@ class LocalisationCache {
'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
+ 'digitGroupingPattern'
);
/**
@@ -131,6 +134,8 @@ class LocalisationCache {
*/
static public $preloadedKeys = array( 'dateFormats', 'namespaceNames' );
+ var $mergeableKeys = null;
+
/**
* Constructor.
* For constructor parameters, see the documentation in DefaultSettings.php
@@ -154,6 +159,9 @@ class LocalisationCache {
case 'db':
$storeClass = 'LCStore_DB';
break;
+ case 'accel':
+ $storeClass = 'LCStore_Accel';
+ break;
case 'detect':
$storeClass = $wgCacheDirectory ? 'LCStore_CDB' : 'LCStore_DB';
break;
@@ -179,9 +187,11 @@ class LocalisationCache {
/**
* Returns true if the given key is mergeable, that is, if it is an associative
* array which can be merged through a fallback sequence.
+ * @param $key
+ * @return bool
*/
public function isMergeableKey( $key ) {
- if ( !isset( $this->mergeableKeys ) ) {
+ if ( $this->mergeableKeys === null ) {
$this->mergeableKeys = array_flip( array_merge(
self::$mergeableMapKeys,
self::$mergeableListKeys,
@@ -198,6 +208,9 @@ class LocalisationCache {
*
* Warning: this may be slow for split items (messages), since it will
* need to fetch all of the subitems from the cache individually.
+ * @param $code
+ * @param $key
+ * @return mixed
*/
public function getItem( $code, $key ) {
if ( !isset( $this->loadedItems[$code][$key] ) ) {
@@ -205,23 +218,29 @@ class LocalisationCache {
$this->loadItem( $code, $key );
wfProfileOut( __METHOD__.'-load' );
}
+
if ( $key === 'fallback' && isset( $this->shallowFallbacks[$code] ) ) {
return $this->shallowFallbacks[$code];
}
+
return $this->data[$code][$key];
}
/**
* Get a subitem, for instance a single message for a given language.
+ * @param $code
+ * @param $key
+ * @param $subkey
+ * @return null
*/
public function getSubitem( $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 );
wfProfileOut( __METHOD__.'-load' );
}
+
if ( isset( $this->data[$code][$key][$subkey] ) ) {
return $this->data[$code][$key][$subkey];
} else {
@@ -237,6 +256,9 @@ class LocalisationCache {
*
* Will return null if the item is not found, or false if the item is not an
* array.
+ * @param $code
+ * @param $key
+ * @return bool|null|string
*/
public function getSubitemList( $code, $key ) {
if ( in_array( $key, self::$splitKeys ) ) {
@@ -253,19 +275,24 @@ class LocalisationCache {
/**
* Load an item into the cache.
+ * @param $code
+ * @param $key
*/
protected function loadItem( $code, $key ) {
if ( !isset( $this->initialisedLangs[$code] ) ) {
$this->initLanguage( $code );
}
+
// Check to see if initLanguage() loaded it for us
if ( isset( $this->loadedItems[$code][$key] ) ) {
return;
}
+
if ( isset( $this->shallowFallbacks[$code] ) ) {
$this->loadItem( $this->shallowFallbacks[$code], $key );
return;
}
+
if ( in_array( $key, self::$splitKeys ) ) {
$subkeyList = $this->getSubitem( $code, 'list', $key );
foreach ( $subkeyList as $subkey ) {
@@ -277,30 +304,38 @@ class LocalisationCache {
} else {
$this->data[$code][$key] = $this->store->get( $code, $key );
}
+
$this->loadedItems[$code][$key] = true;
}
/**
* Load a subitem into the cache
+ * @param $code
+ * @param $key
+ * @param $subkey
+ * @return
*/
protected function loadSubitem( $code, $key, $subkey ) {
if ( !in_array( $key, self::$splitKeys ) ) {
$this->loadItem( $code, $key );
return;
}
+
if ( !isset( $this->initialisedLangs[$code] ) ) {
$this->initLanguage( $code );
}
+
// Check to see if initLanguage() loaded it for us
- if ( isset( $this->loadedItems[$code][$key] )
- || isset( $this->loadedSubitems[$code][$key][$subkey] ) )
- {
+ if ( isset( $this->loadedItems[$code][$key] ) ||
+ isset( $this->loadedSubitems[$code][$key][$subkey] ) ) {
return;
}
+
if ( isset( $this->shallowFallbacks[$code] ) ) {
$this->loadSubitem( $this->shallowFallbacks[$code], $key, $subkey );
return;
}
+
$value = $this->store->get( $code, "$key:$subkey" );
$this->data[$code][$key][$subkey] = $value;
$this->loadedSubitems[$code][$key][$subkey] = true;
@@ -316,10 +351,14 @@ class LocalisationCache {
}
$deps = $this->store->get( $code, 'deps' );
- if ( $deps === null ) {
+ $keys = $this->store->get( $code, 'list', 'messages' );
+ $preload = $this->store->get( $code, 'preload' );
+ // Different keys may expire separately, at least in LCStore_Accel
+ if ( $deps === null || $keys === null || $preload === null ) {
wfDebug( __METHOD__."($code): cache missing, need to make one\n" );
return true;
}
+
foreach ( $deps as $dep ) {
// Because we're unserializing stuff from cache, we
// could receive objects of classes that don't exist
@@ -331,16 +370,19 @@ class LocalisationCache {
return true;
}
}
+
return false;
}
/**
* Initialise a language in this object. Rebuild the cache if necessary.
+ * @param $code
*/
protected function initLanguage( $code ) {
if ( isset( $this->initialisedLangs[$code] ) ) {
return;
}
+
$this->initialisedLangs[$code] = true;
# If the code is of the wrong form for a Messages*.php file, do a shallow fallback
@@ -391,6 +433,8 @@ class LocalisationCache {
/**
* Create a fallback from one language to another, without creating a
* complete persistent cache.
+ * @param $primaryCode
+ * @param $fallbackCode
*/
public function initShallowFallback( $primaryCode, $fallbackCode ) {
$this->data[$primaryCode] =& $this->data[$fallbackCode];
@@ -401,6 +445,9 @@ class LocalisationCache {
/**
* Read a PHP file containing localisation data.
+ * @param $_fileName
+ * @param $_fileType
+ * @return array
*/
protected function readPHPFile( $_fileName, $_fileType ) {
// Disable APC caching
@@ -415,12 +462,16 @@ class LocalisationCache {
} else {
throw new MWException( __METHOD__.": Invalid file type: $_fileType" );
}
+
return $data;
}
/**
* Merge two localisation values, a primary and a fallback, overwriting the
* primary value in place.
+ * @param $key
+ * @param $value
+ * @param $fallbackValue
*/
protected function mergeItem( $key, &$value, $fallbackValue ) {
if ( !is_null( $value ) ) {
@@ -435,6 +486,7 @@ class LocalisationCache {
if ( !empty( $value['inherit'] ) ) {
$value = array_merge( $fallbackValue, $value );
}
+
if ( isset( $value['inherit'] ) ) {
unset( $value['inherit'] );
}
@@ -447,6 +499,10 @@ class LocalisationCache {
}
}
+ /**
+ * @param $value
+ * @param $fallbackValue
+ */
protected function mergeMagicWords( &$value, $fallbackValue ) {
foreach ( $fallbackValue as $magicName => $fallbackInfo ) {
if ( !isset( $value[$magicName] ) ) {
@@ -468,6 +524,11 @@ class LocalisationCache {
*
* Returns true if any data from the extension array was used, false
* otherwise.
+ * @param $codeSequence
+ * @param $key
+ * @param $value
+ * @param $fallbackValue
+ * @return bool
*/
protected function mergeExtensionItem( $codeSequence, $key, &$value, $fallbackValue ) {
$used = false;
@@ -477,16 +538,17 @@ class LocalisationCache {
$used = true;
}
}
+
return $used;
}
/**
* Load localisation data for a given language for both core and extensions
* and save it to the persistent cache store and the process cache
+ * @param $code
*/
public function recache( $code ) {
- static $recursionGuard = array();
- global $wgExtensionMessagesFiles, $wgExtensionAliasesFiles;
+ global $wgExtensionMessagesFiles;
wfProfileIn( __METHOD__ );
if ( !$code ) {
@@ -515,6 +577,7 @@ class LocalisationCache {
foreach ( $data as $key => $value ) {
$this->mergeItem( $key, $coreData[$key], $value );
}
+
}
# Fill in the fallback if it's not there already
@@ -522,28 +585,42 @@ class LocalisationCache {
$coreData['fallback'] = $code === 'en' ? false : 'en';
}
- if ( $coreData['fallback'] !== false ) {
- # Guard against circular references
- if ( isset( $recursionGuard[$code] ) ) {
- throw new MWException( "Error: Circular fallback reference in language code $code" );
+ if ( $coreData['fallback'] === false ) {
+ $coreData['fallbackSequence'] = array();
+ } else {
+ $coreData['fallbackSequence'] = array_map( 'trim', explode( ',', $coreData['fallback'] ) );
+ $len = count( $coreData['fallbackSequence'] );
+
+ # Ensure that the sequence ends at en
+ if ( $coreData['fallbackSequence'][$len - 1] !== 'en' ) {
+ $coreData['fallbackSequence'][] = 'en';
}
- $recursionGuard[$code] = true;
# Load the fallback localisation item by item and merge it
- $deps = array_merge( $deps, $this->getItem( $coreData['fallback'], 'deps' ) );
- foreach ( self::$allKeys as $key ) {
- if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
- $fallbackValue = $this->getItem( $coreData['fallback'], $key );
- $this->mergeItem( $key, $coreData[$key], $fallbackValue );
+ foreach ( $coreData['fallbackSequence'] as $fbCode ) {
+ # Load the secondary localisation from the source file to
+ # avoid infinite cycles on cyclic fallbacks
+ $fbFilename = Language::getMessagesFileName( $fbCode );
+
+ if ( !file_exists( $fbFilename ) ) {
+ continue;
+ }
+
+ $deps[] = new FileDependency( $fbFilename );
+ $fbData = $this->readPHPFile( $fbFilename, 'core' );
+
+ foreach ( self::$allKeys as $key ) {
+ if ( !isset( $fbData[$key] ) ) {
+ continue;
+ }
+
+ if ( is_null( $coreData[$key] ) || $this->isMergeableKey( $key ) ) {
+ $this->mergeItem( $key, $coreData[$key], $fbData[$key] );
+ }
}
}
- $fallbackSequence = $this->getItem( $coreData['fallback'], 'fallbackSequence' );
- array_unshift( $fallbackSequence, $coreData['fallback'] );
- $coreData['fallbackSequence'] = $fallbackSequence;
- unset( $recursionGuard[$code] );
- } else {
- $coreData['fallbackSequence'] = array();
}
+
$codeSequence = array_merge( array( $code ), $coreData['fallbackSequence'] );
# Load the extension localisations
@@ -554,24 +631,13 @@ class LocalisationCache {
foreach ( $wgExtensionMessagesFiles as $fileName ) {
$data = $this->readPHPFile( $fileName, 'extension' );
$used = false;
+
foreach ( $data as $key => $item ) {
if( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) {
$used = true;
}
}
- if ( $used ) {
- $deps[] = new FileDependency( $fileName );
- }
- }
- # Load deprecated $wgExtensionAliasesFiles
- foreach ( $wgExtensionAliasesFiles as $fileName ) {
- $data = $this->readPHPFile( $fileName, 'aliases' );
- if ( !isset( $data['aliases'] ) ) {
- continue;
- }
- $used = $this->mergeExtensionItem( $codeSequence, 'specialPageAliases',
- $allData['specialPageAliases'], $data['aliases'] );
if ( $used ) {
$deps[] = new FileDependency( $fileName );
}
@@ -584,7 +650,6 @@ class LocalisationCache {
# Add cache dependencies for any referenced globals
$deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
- $deps['wgExtensionAliasesFiles'] = new GlobalDependency( 'wgExtensionAliasesFiles' );
$deps['version'] = new ConstantDependency( 'MW_LC_VERSION' );
# Add dependencies to the cache entry
@@ -651,12 +716,15 @@ class LocalisationCache {
*
* The preload item will be loaded automatically, improving performance
* for the commonly-requested items it contains.
+ * @param $data
+ * @return array
*/
protected function buildPreload( $data ) {
$preload = array( 'messages' => array() );
foreach ( self::$preloadedKeys as $key ) {
$preload[$key] = $data[$key];
}
+
foreach ( $data['preloadedMessages'] as $subkey ) {
if ( isset( $data['messages'][$subkey] ) ) {
$subitem = $data['messages'][$subkey];
@@ -665,18 +733,21 @@ class LocalisationCache {
}
$preload['messages'][$subkey] = $subitem;
}
+
return $preload;
}
/**
* Unload the data for a given language from the object cache.
* Reduces memory usage.
+ * @param $code
*/
public function unload( $code ) {
unset( $this->data[$code] );
unset( $this->loadedItems[$code] );
unset( $this->loadedSubitems[$code] );
unset( $this->initialisedLangs[$code] );
+
foreach ( $this->shallowFallbacks as $shallowCode => $fbCode ) {
if ( $fbCode === $code ) {
$this->unload( $shallowCode );
@@ -741,9 +812,59 @@ interface LCStore {
/**
* Set a key to a given value. startWrite() must be called before this
* is called, and finishWrite() must be called afterwards.
+ * @param $key
+ * @param $value
*/
function set( $key, $value );
+}
+
+/**
+ * LCStore implementation which uses PHP accelerator to store data.
+ * This will work if one of XCache, WinCache or APC cacher is configured.
+ * (See ObjectCache.php)
+ */
+class LCStore_Accel implements LCStore {
+ var $currentLang;
+ var $keys;
+ public function __construct() {
+ $this->cache = wfGetCache( CACHE_ACCEL );
+ }
+
+ public function get( $code, $key ) {
+ $k = wfMemcKey( 'l10n', $code, 'k', $key );
+ $r = $this->cache->get( $k );
+ return $r === false ? null : $r;
+ }
+
+ public function startWrite( $code ) {
+ $k = wfMemcKey( 'l10n', $code, 'l' );
+ $keys = $this->cache->get( $k );
+ if ( $keys ) {
+ foreach ( $keys as $k ) {
+ $this->cache->delete( $k );
+ }
+ }
+ $this->currentLang = $code;
+ $this->keys = array();
+ }
+
+ public function finishWrite() {
+ if ( $this->currentLang ) {
+ $k = wfMemcKey( 'l10n', $this->currentLang, 'l' );
+ $this->cache->set( $k, array_keys( $this->keys ) );
+ }
+ $this->currentLang = null;
+ $this->keys = array();
+ }
+
+ public function set( $key, $value ) {
+ if ( $this->currentLang ) {
+ $k = wfMemcKey( 'l10n', $this->currentLang, 'k', $key );
+ $this->keys[$k] = true;
+ $this->cache->set( $k, $value );
+ }
+ }
}
/**
@@ -753,7 +874,12 @@ interface LCStore {
class LCStore_DB implements LCStore {
var $currentLang;
var $writesDone = false;
- var $dbw, $batch;
+
+ /**
+ * @var DatabaseBase
+ */
+ var $dbw;
+ var $batch;
var $readOnly = false;
public function get( $code, $key ) {
@@ -775,9 +901,11 @@ class LCStore_DB implements LCStore {
if ( $this->readOnly ) {
return;
}
+
if ( !$code ) {
throw new MWException( __METHOD__.": Invalid language \"$code\"" );
}
+
$this->dbw = wfGetDB( DB_MASTER );
try {
$this->dbw->begin();
@@ -792,6 +920,7 @@ class LCStore_DB implements LCStore {
throw $e;
}
}
+
$this->currentLang = $code;
$this->batch = array();
}
@@ -800,9 +929,11 @@ class LCStore_DB implements LCStore {
if ( $this->readOnly ) {
return;
}
+
if ( $this->batch ) {
$this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
}
+
$this->dbw->commit();
$this->currentLang = null;
$this->dbw = null;
@@ -814,13 +945,16 @@ class LCStore_DB implements LCStore {
if ( $this->readOnly ) {
return;
}
+
if ( is_null( $this->currentLang ) ) {
throw new MWException( __CLASS__.': must call startWrite() before calling set()' );
}
+
$this->batch[] = array(
'lc_lang' => $this->currentLang,
'lc_key' => $key,
'lc_value' => serialize( $value ) );
+
if ( count( $this->batch ) >= 100 ) {
$this->dbw->insert( 'l10n_cache', $this->batch, __METHOD__ );
$this->batch = array();
@@ -845,6 +979,7 @@ class LCStore_CDB implements LCStore {
function __construct( $conf = array() ) {
global $wgCacheDirectory;
+
if ( isset( $conf['directory'] ) ) {
$this->directory = $conf['directory'];
} else {
@@ -855,16 +990,19 @@ class LCStore_CDB implements LCStore {
public function get( $code, $key ) {
if ( !isset( $this->readers[$code] ) ) {
$fileName = $this->getFileName( $code );
+
if ( !file_exists( $fileName ) ) {
$this->readers[$code] = false;
} else {
$this->readers[$code] = CdbReader::open( $fileName );
}
}
+
if ( !$this->readers[$code] ) {
return null;
} else {
$value = $this->readers[$code]->get( $key );
+
if ( $value === false ) {
return null;
}
@@ -874,15 +1012,17 @@ class LCStore_CDB implements LCStore {
public function startWrite( $code ) {
if ( !file_exists( $this->directory ) ) {
- if ( !wfMkdirParents( $this->directory ) ) {
+ if ( !wfMkdirParents( $this->directory, null, __METHOD__ ) ) {
throw new MWException( "Unable to create the localisation store " .
"directory \"{$this->directory}\"" );
}
}
+
// Close reader to stop permission errors on write
if( !empty($this->readers[$code]) ) {
$this->readers[$code]->close();
}
+
$this->writer = CdbWriter::open( $this->getFileName( $code ) );
$this->currentLang = $code;
}
@@ -946,16 +1086,24 @@ class LocalisationCache_BulkLoad extends LocalisationCache {
*/
var $maxLoadedLangs = 10;
+ /**
+ * @param $fileName
+ * @param $fileType
+ * @return array|mixed
+ */
protected function readPHPFile( $fileName, $fileType ) {
$serialize = $fileType === 'core';
if ( !isset( $this->fileCache[$fileName][$fileType] ) ) {
$data = parent::readPHPFile( $fileName, $fileType );
+
if ( $serialize ) {
$encData = serialize( $data );
} else {
$encData = $data;
}
+
$this->fileCache[$fileName][$fileType] = $encData;
+
return $data;
} elseif ( $serialize ) {
return unserialize( $this->fileCache[$fileName][$fileType] );
@@ -964,18 +1112,32 @@ class LocalisationCache_BulkLoad extends LocalisationCache {
}
}
+ /**
+ * @param $code
+ * @param $key
+ * @return mixed
+ */
public function getItem( $code, $key ) {
unset( $this->mruLangs[$code] );
$this->mruLangs[$code] = true;
return parent::getItem( $code, $key );
}
+ /**
+ * @param $code
+ * @param $key
+ * @param $subkey
+ * @return
+ */
public function getSubitem( $code, $key, $subkey ) {
unset( $this->mruLangs[$code] );
$this->mruLangs[$code] = true;
return parent::getSubitem( $code, $key, $subkey );
}
+ /**
+ * @param $code
+ */
public function recache( $code ) {
parent::recache( $code );
unset( $this->mruLangs[$code] );
@@ -983,6 +1145,9 @@ class LocalisationCache_BulkLoad extends LocalisationCache {
$this->trimCache();
}
+ /**
+ * @param $code
+ */
public function unload( $code ) {
unset( $this->mruLangs[$code] );
parent::unload( $code );
@@ -999,4 +1164,4 @@ class LocalisationCache_BulkLoad extends LocalisationCache {
$this->unload( $code );
}
}
-}
+} \ No newline at end of file
diff --git a/includes/MWFunction.php b/includes/MWFunction.php
index 53ce446e..0113f917 100644
--- a/includes/MWFunction.php
+++ b/includes/MWFunction.php
@@ -20,6 +20,11 @@
class MWFunction {
+ /**
+ * @param $callback
+ * @return array
+ * @throws MWException
+ */
protected static function cleanCallback( $callback ) {
if( is_string( $callback ) ) {
if ( strpos( $callback, '::' ) !== false ) {
@@ -39,6 +44,10 @@ class MWFunction {
return $callback;
}
+ /**
+ * @param $callback
+ * @return mixed
+ */
public static function call( $callback ) {
$callback = self::cleanCallback( $callback );
@@ -47,11 +56,21 @@ class MWFunction {
return call_user_func_array( 'call_user_func', $args );
}
+ /**
+ * @param $callback
+ * @param $argsarams
+ * @return mixed
+ */
public static function callArray( $callback, $argsarams ) {
$callback = self::cleanCallback( $callback );
return call_user_func_array( $callback, $argsarams );
}
+ /**
+ * @param $class
+ * @param $args array
+ * @return object
+ */
public static function newObj( $class, $args = array() ) {
if( !count( $args ) ) {
return new $class;
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index d1579380..1ba46701 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -10,8 +10,13 @@
/**
* This class encapsulates "magic words" such as #redirect, __NOTOC__, etc.
- * Usage:
- * if (MagicWord::get( 'redirect' )->match( $text ) )
+ *
+ * @par Usage:
+ * @code
+ * if (MagicWord::get( 'redirect' )->match( $text ) ) {
+ * // some code
+ * }
+ * @endcode
*
* Possible future improvements:
* * Simultaneous searching for a number of magic words
@@ -20,8 +25,20 @@
* Please avoid reading the data out of one of these objects and then writing
* special case code. If possible, add another match()-like function here.
*
- * To add magic words in an extension, use the LanguageGetMagic hook. For
- * magic words which are also Parser variables, add a MagicWordwgVariableIDs
+ * To add magic words in an extension, use $magicWords in a file listed in
+ * $wgExtensionMessagesFiles[].
+ *
+ * @par Example:
+ * @code
+ * $magicWords = array();
+ *
+ * $magicWords['en'] = array(
+ * 'magicwordkey' => array( 0, 'case_insensitive_magic_word' ),
+ * 'magicwordkey2' => array( 1, 'CASE_sensitive_magic_word2' ),
+ * );
+ * @endcode
+ *
+ * For magic words which are also Parser variables, add a MagicWordwgVariableIDs
* hook. Use string keys.
*
* @ingroup Parser
@@ -212,13 +229,6 @@ class MagicWord {
*/
static function getVariableIDs() {
if ( !self::$mVariableIDsInitialised ) {
- # Deprecated constant definition hook, available for extensions that need it
- $magicWords = array();
- wfRunHooks( 'MagicWordMagicWords', array( &$magicWords ) );
- foreach ( $magicWords as $word ) {
- define( $word, $word );
- }
-
# Get variable IDs
wfRunHooks( 'MagicWordwgVariableIDs', array( &self::$mVariableIDs ) );
self::$mVariableIDsInitialised = true;
@@ -228,17 +238,19 @@ class MagicWord {
/**
* Get an array of parser substitution modifier IDs
+ * @return array
*/
static function getSubstIDs() {
- return self::$mSubstIDs;
+ return self::$mSubstIDs;
}
/**
* Allow external reads of TTL array
*
+ * @param $id int
* @return array
*/
- static function getCacheTTL($id) {
+ static function getCacheTTL( $id ) {
if ( array_key_exists( $id, self::$mCacheTTLs ) ) {
return self::$mCacheTTLs[$id];
} else {
@@ -309,8 +321,8 @@ 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
+ * 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
@@ -383,7 +395,7 @@ class MagicWord {
/**
* Returns true if the text contains the word
*
- * @paran $text string
+ * @param $text string
*
* @return bool
*/
@@ -633,7 +645,7 @@ class MagicWordArray {
/**
* Add a number of magic words by name
*
- * $param $names array
+ * @param $names array
*/
public function addArray( $names ) {
$this->names = array_merge( $this->names, array_values( $names ) );
@@ -712,7 +724,7 @@ class MagicWordArray {
/**
* Get a regex anchored to the start of the string that does not match parameters
*
- * @return string
+ * @return array
*/
function getRegexStart() {
$base = $this->getBaseRegex();
@@ -721,7 +733,7 @@ class MagicWordArray {
$newRegex[0] = "/^(?:{$base[0]})/iuS";
}
if ( $base[1] !== '' ) {
- $newRegex[1] = "/^(?:{$base[1]})/S";
+ $newRegex[1] = "/^(?:{$base[1]})/S";
}
return $newRegex;
}
@@ -729,7 +741,7 @@ class MagicWordArray {
/**
* Get an anchored regex for matching variables with parameters
*
- * @return string
+ * @return array
*/
function getVariableStartToEndRegex() {
$base = $this->getBaseRegex();
@@ -748,7 +760,7 @@ class MagicWordArray {
* Returns array(magic word ID, parameter value)
* If there is no parameter value, that element will be false.
*
- * @param $m arrray
+ * @param $m array
*
* @return array
*/
diff --git a/includes/Message.php b/includes/Message.php
index 87349ff5..3c5d5d7d 100644
--- a/includes/Message.php
+++ b/includes/Message.php
@@ -1,55 +1,137 @@
<?php
/**
- * This class provides methods for fetching interface messages and
- * processing them into variety of formats that are needed in MediaWiki.
- *
- * It is intented to replace the old wfMsg* functions that over time grew
- * unusable.
- *
- * Examples:
- * Fetching a message text for interface message
- * $button = Xml::button( wfMessage( 'submit' )->text() );
- * </pre>
- * Messages can have parameters:
- * wfMessage( 'welcome-to' )->params( $wgSitename )->text();
- * {{GRAMMAR}} and friends work correctly
- * wfMessage( 'are-friends', $user, $friend );
- * wfMessage( 'bad-message' )->rawParams( '<script>...</script>' )->escaped();
- * </pre>
- * Sometimes the message text ends up in the database, so content language is needed.
- * wfMessage( 'file-log', $user, $filename )->inContentLanguage()->text()
- * </pre>
- * Checking if message exists:
+ * The Message class provides methods which fullfil two basic services:
+ * - fetching interface messages
+ * - processing messages into a variety of formats
+ *
+ * First implemented with MediaWiki 1.17, the Message class is intented to
+ * replace the old wfMsg* functions that over time grew unusable.
+ * @see https://www.mediawiki.org/wiki/New_messages_API for equivalences
+ * between old and new functions.
+ *
+ * You should use the wfMessage() global function which acts as a wrapper for
+ * the Message class. The wrapper let you pass parameters as arguments.
+ *
+ * The most basic usage cases would be:
+ *
+ * @code
+ * // Initialize a Message object using the 'some_key' message key
+ * $message = wfMessage( 'some_key' );
+ *
+ * // Using two parameters those values are strings 'value1' and 'value2':
+ * $message = wfMessage( 'some_key',
+ * 'value1', 'value2'
+ * );
+ * @endcode
+ *
+ * @section message_global_fn Global function wrapper:
+ *
+ * Since wfMessage() returns a Message instance, you can chain its call with
+ * a method. Some of them return a Message instance too so you can chain them.
+ * You will find below several examples of wfMessage() usage.
+ *
+ * Fetching a message text for interface message:
+ *
+ * @code
+ * $button = Xml::button(
+ * wfMessage( 'submit' )->text()
+ * );
+ * @endcode
+ *
+ * A Message instance can be passed parameters after it has been constructed,
+ * use the params() method to do so:
+ *
+ * @code
+ * wfMessage( 'welcome-to' )
+ * ->params( $wgSitename )
+ * ->text();
+ * @endcode
+ *
+ * {{GRAMMAR}} and friends work correctly:
+ *
+ * @code
+ * wfMessage( 'are-friends',
+ * $user, $friend
+ * );
+ * wfMessage( 'bad-message' )
+ * ->rawParams( '<script>...</script>' )
+ * ->escaped();
+ * @endcode
+ *
+ * @section message_language Changing language:
+ *
+ * Messages can be requested in a different language or in whatever current
+ * content language is being used. The methods are:
+ * - Message->inContentLanguage()
+ * - Message->inLanguage()
+ *
+ * Sometimes the message text ends up in the database, so content language is
+ * needed:
+ *
+ * @code
+ * wfMessage( 'file-log',
+ * $user, $filename
+ * )->inContentLanguage()->text();
+ * @endcode
+ *
+ * Checking whether a message exists:
+ *
+ * @code
* wfMessage( 'mysterious-message' )->exists()
- * </pre>
+ * // returns a boolean whether the 'mysterious-message' key exist.
+ * @endcode
+ *
* If you want to use a different language:
- * wfMessage( 'email-header' )->inLanguage( $user->getOption( 'language' ) )->plain()
- * Note that you cannot parse the text except in the content or interface
- * languages
- * </pre>
*
+ * @code
+ * $userLanguage = $user->getOption( 'language' );
+ * wfMessage( 'email-header' )
+ * ->inLanguage( $userLanguage )
+ * ->plain();
+ * @endcode
+ *
+ * @note You cannot parse the text except in the content or interface
+ * @note languages
*
- * Comparison with old wfMsg* functions:
+ * @section message_compare_old Comparison with old wfMsg* functions:
*
- * Use full parsing.
+ * Use full parsing:
+ *
+ * @code
+ * // old style:
* wfMsgExt( 'key', array( 'parseinline' ), 'apple' );
- * === wfMessage( 'key', 'apple' )->parse();
- * </pre>
- * Parseinline is used because it is more useful when pre-building html.
+ * // new style:
+ * wfMessage( 'key', 'apple' )->parse();
+ * @endcode
+ *
+ * Parseinline is used because it is more useful when pre-building HTML.
* In normal use it is better to use OutputPage::(add|wrap)WikiMsg.
*
- * Places where html cannot be used. {{-transformation is done.
+ * Places where HTML cannot be used. {{-transformation is done.
+ * @code
+ * // old style:
* wfMsgExt( 'key', array( 'parsemag' ), 'apple', 'pear' );
- * === wfMessage( 'key', 'apple', 'pear' )->text();
- * </pre>
+ * // new style:
+ * wfMessage( 'key', 'apple', 'pear' )->text();
+ * @endcode
*
- * Shortcut for escaping the message too, similar to wfMsgHTML, but
+ * Shortcut for escaping the message too, similar to wfMsgHTML(), but
* parameters are not replaced after escaping by default.
- * $escaped = wfMessage( 'key' )->rawParams( 'apple' )->escaped();
- * </pre>
+ * @code
+ * $escaped = wfMessage( 'key' )
+ * ->rawParams( 'apple' )
+ * ->escaped();
+ * @endcode
+ *
+ * @section message_appendix Appendix:
*
* @todo
* - test, can we have tests?
+ * - this documentation needs to be extended
+ *
+ * @see https://www.mediawiki.org/wiki/WfMessage()
+ * @see https://www.mediawiki.org/wiki/New_messages_API
+ * @see https://www.mediawiki.org/wiki/Localisation
*
* @since 1.17
* @author Niklas Laxström
@@ -60,7 +142,7 @@ class Message {
* means the current interface language, false content language.
*/
protected $interface = true;
-
+
/**
* In which language to get this message. Overrides the $interface
* variable.
@@ -68,7 +150,7 @@ class Message {
* @var Language
*/
protected $language = null;
-
+
/**
* The message key.
*/
@@ -101,6 +183,11 @@ class Message {
protected $title = null;
/**
+ * @var string
+ */
+ protected $message;
+
+ /**
* Constructor.
* @param $key: message key, or array of message keys to try and use the first non-empty message for
* @param $params Array message parameters
@@ -131,7 +218,7 @@ 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
+ * @param Varargs: message keys (or first arg as an array of all the message keys)
* @return Message: $this
*/
public static function newFallbackSequence( /*...*/ ) {
@@ -150,7 +237,7 @@ class Message {
/**
* Adds parameters to the parameter list of this message.
- * @param Varargs: parameters as Strings
+ * @param Varargs: parameters as Strings, or a single argument that is an array of Strings
* @return Message: $this
*/
public function params( /*...*/ ) {
@@ -168,7 +255,7 @@ class Message {
* In other words the parsing process cannot access the contents
* of this type of parameter, and you need to make sure it is
* sanitized beforehand. The parser will see "$n", instead.
- * @param Varargs: raw parameters as Strings
+ * @param Varargs: raw parameters as Strings (or single argument that is an array of raw parameters)
* @return Message: $this
*/
public function rawParams( /*...*/ ) {
@@ -181,11 +268,11 @@ class Message {
}
return $this;
}
-
+
/**
* Add parameters that are numeric and will be passed through
* Language::formatNum before substitution
- * @param Varargs: numeric parameters
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
* @return Message: $this
*/
public function numParams( /*...*/ ) {
@@ -198,7 +285,20 @@ class Message {
}
return $this;
}
-
+
+ /**
+ * Set the language and the title from a context object
+ *
+ * @param $context IContextSource
+ * @return Message: $this
+ */
+ public function setContext( IContextSource $context ) {
+ $this->inLanguage( $context->getLanguage() );
+ $this->title( $context->getTitle() );
+
+ return $this;
+ }
+
/**
* Request the message in any language that is supported.
* As a side effect interface message status is unconditionally
@@ -216,7 +316,7 @@ class Message {
} else {
$type = gettype( $lang );
throw new MWException( __METHOD__ . " must be "
- . "passed a String or Language object; $type given"
+ . "passed a String or Language object; $type given"
);
}
$this->interface = false;
@@ -268,10 +368,10 @@ class Message {
*/
public function toString() {
$string = $this->getMessageText();
-
+
# Replace parameters before text parsing
$string = $this->replaceParameters( $string, 'before' );
-
+
# Maybe transform using the full parser
if( $this->format === 'parse' ) {
$string = $this->parseText( $string );
@@ -287,10 +387,10 @@ class Message {
$string = $this->transformText( $string );
$string = htmlspecialchars( $string, ENT_QUOTES, 'UTF-8', false );
}
-
+
# Raw parameter replacement
$string = $this->replaceParameters( $string, 'after' );
-
+
return $string;
}
@@ -303,7 +403,7 @@ class Message {
public function __toString() {
return $this->toString();
}
-
+
/**
* Fully parse the text from wikitext to HTML
* @return String parsed HTML
@@ -470,7 +570,10 @@ class Message {
protected function fetchMessage() {
if ( !isset( $this->message ) ) {
$cache = MessageCache::singleton();
- if ( is_array($this->key) ) {
+ if ( is_array( $this->key ) ) {
+ if ( !count( $this->key ) ) {
+ throw new MWException( "Given empty message key array." );
+ }
foreach ( $this->key as $key ) {
$message = $cache->get( $key, $this->useDatabase, $this->language );
if ( $message !== false && $message !== '' ) {
diff --git a/includes/Metadata.php b/includes/Metadata.php
index 2e4ab94c..e5e3296b 100644
--- a/includes/Metadata.php
+++ b/includes/Metadata.php
@@ -41,14 +41,13 @@ abstract class RdfMetaData {
$rdftype = wfNegotiateType( wfAcceptToPrefs( $httpaccept ), wfAcceptToPrefs( self::RDF_TYPE_PREFS ) );
if( !$rdftype ){
- wfHttpError( 406, 'Not Acceptable', wfMsg( 'notacceptable' ) );
- return false;
- } else {
- $wgOut->disable();
- $wgRequest->response()->header( "Content-type: {$rdftype}; charset=utf-8" );
- $wgOut->sendCacheControl();
- return true;
+ throw new HttpError( 406, wfMessage( 'notacceptable' ) );
}
+
+ $wgOut->disable();
+ $wgRequest->response()->header( "Content-type: {$rdftype}; charset=utf-8" );
+ $wgOut->sendCacheControl();
+ return true;
}
protected function reallyFullUrl() {
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index 62325238..f86b6051 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -29,6 +29,7 @@ application/ogg ogx ogg ogm ogv oga spx
application/pdf pdf
application/vnd.oasis.opendocument.chart odc
application/vnd.oasis.opendocument.chart-template otc
+application/vnd.oasis.opendocument.database odb
application/vnd.oasis.opendocument.formula odf
application/vnd.oasis.opendocument.formula-template otf
application/vnd.oasis.opendocument.graphics odg
@@ -40,8 +41,8 @@ application/vnd.oasis.opendocument.presentation-template otp
application/vnd.oasis.opendocument.spreadsheet ods
application/vnd.oasis.opendocument.spreadsheet-template ots
application/vnd.oasis.opendocument.text odt
-application/vnd.oasis.opendocument.text-template ott
application/vnd.oasis.opendocument.text-master otm
+application/vnd.oasis.opendocument.text-template ott
application/vnd.oasis.opendocument.text-web oth
application/x-javascript js
application/x-shockwave-flash swf
@@ -79,6 +80,7 @@ define('MM_WELL_KNOWN_MIME_INFO', <<<END_STRING
application/pdf [OFFICE]
application/vnd.oasis.opendocument.chart [OFFICE]
application/vnd.oasis.opendocument.chart-template [OFFICE]
+application/vnd.oasis.opendocument.database [OFFICE]
application/vnd.oasis.opendocument.formula [OFFICE]
application/vnd.oasis.opendocument.formula-template [OFFICE]
application/vnd.oasis.opendocument.graphics [OFFICE]
diff --git a/includes/Namespace.php b/includes/Namespace.php
index 95c167b2..292559d0 100644
--- a/includes/Namespace.php
+++ b/includes/Namespace.php
@@ -27,7 +27,7 @@ class MWNamespace {
/**
* 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
+ * Special namespaces are defined in includes/Defines.php and have
* a value below 0 (ex: NS_SPECIAL = -1 , NS_MEDIA = -2)
*
* @param $index
@@ -36,7 +36,7 @@ class MWNamespace {
* @return true
*/
private static function isMethodValidFor( $index, $method ) {
- if( $index < NS_MAIN ) {
+ if ( $index < NS_MAIN ) {
throw new MWException( "$method does not make any sense for given namespace $index" );
}
return true;
@@ -50,7 +50,7 @@ class MWNamespace {
*/
public static function isMovable( $index ) {
global $wgAllowImageMoving;
- return !( $index < NS_MAIN || ($index == NS_FILE && !$wgAllowImageMoving) || $index == NS_CATEGORY );
+ return !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving ) || $index == NS_CATEGORY );
}
/**
@@ -58,12 +58,22 @@ class MWNamespace {
*
* @param $index Int: namespace index
* @return bool
+ * @since 1.19
*/
- public static function isMain( $index ) {
+ public static function isSubject( $index ) {
return !self::isTalk( $index );
}
/**
+ * @see self::isSubject
+ * @deprecated Please use the more consistently named isSubject (since 1.19)
+ */
+ public static function isMain( $index ) {
+ wfDeprecated( __METHOD__, '1.19' );
+ return self::isSubject( $index );
+ }
+
+ /**
* Is the given namespace a talk namespace?
*
* @param $index Int: namespace index
@@ -96,7 +106,7 @@ class MWNamespace {
*/
public static function getSubject( $index ) {
# Handle special namespaces
- if( $index < NS_MAIN ) {
+ if ( $index < NS_MAIN ) {
return $index;
}
@@ -116,9 +126,9 @@ class MWNamespace {
public static function getAssociated( $index ) {
self::isMethodValidFor( $index, __METHOD__ );
- if( self::isMain( $index ) ) {
+ if ( self::isSubject( $index ) ) {
return self::getTalk( $index );
- } elseif( self::isTalk( $index ) ) {
+ } elseif ( self::isTalk( $index ) ) {
return self::getSubject( $index );
} else {
return null;
@@ -129,8 +139,9 @@ class MWNamespace {
* Returns whether the specified namespace exists
*
* @param $index
- *
+ *
* @return bool
+ * @since 1.19
*/
public static function exists( $index ) {
$nslist = self::getCanonicalNamespaces();
@@ -138,6 +149,39 @@ class MWNamespace {
}
/**
+ * Returns whether the specified namespaces are the same namespace
+ *
+ * @note It's possible that in the future we may start using something
+ * other than just namespace indexes. Under that circumstance making use
+ * of this function rather than directly doing comparison will make
+ * sure that code will not potentially break.
+ *
+ * @param $ns1 int The first namespace index
+ * @param $ns2 int The second namespae index
+ *
+ * @return bool
+ * @since 1.19
+ */
+ public static function equals( $ns1, $ns2 ) {
+ return $ns1 == $ns2;
+ }
+
+ /**
+ * Returns whether the specified namespaces share the same subject.
+ * eg: NS_USER and NS_USER wil return true, as well
+ * NS_USER and NS_USER_TALK will return true.
+ *
+ * @param $ns1 int The first namespace index
+ * @param $ns2 int The second namespae index
+ *
+ * @return bool
+ * @since 1.19
+ */
+ public static function subjectEquals( $ns1, $ns2 ) {
+ return self::getSubject( $ns1 ) == self::getSubject( $ns2 );
+ }
+
+ /**
* Returns array of all defined namespaces with their canonical
* (English) names.
*
@@ -165,7 +209,7 @@ class MWNamespace {
*/
public static function getCanonicalName( $index ) {
$nslist = self::getCanonicalNamespaces();
- if( isset( $nslist[$index] ) ) {
+ if ( isset( $nslist[$index] ) ) {
return $nslist[$index];
} else {
return false;
@@ -184,7 +228,7 @@ class MWNamespace {
if ( $xNamespaces === false ) {
$xNamespaces = array();
foreach ( self::getCanonicalNamespaces() as $i => $text ) {
- $xNamespaces[strtolower($text)] = $i;
+ $xNamespaces[strtolower( $text )] = $i;
}
}
if ( array_key_exists( $name, $xNamespaces ) ) {
@@ -262,7 +306,7 @@ class MWNamespace {
*/
public static function getContentNamespaces() {
global $wgContentNamespaces;
- if( !is_array( $wgContentNamespaces ) || $wgContentNamespaces === array() ) {
+ 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)
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 68c771bc..a91d5465 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -13,7 +13,7 @@ if ( !defined( 'MEDIAWIKI' ) ) {
*
* @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
+ * Some comments comes from a pairing session between Zak Greant and Antoine Musso
* in November 2010.
*
* @todo document
@@ -47,7 +47,13 @@ class OutputPage extends ContextSource {
var $mHTMLtitle = '';
/// Should be private. Is the displayed content related to the source of the corresponding wiki article.
- var $mIsarticle = true;
+ var $mIsarticle = false;
+
+ /**
+ * Should be private. Has get/set methods properly documented.
+ * Stores "article flag" toggle.
+ */
+ var $mIsArticleRelated = true;
/**
* Should be private. We have to set isPrintable(). Some pages should
@@ -61,7 +67,7 @@ class OutputPage extends ContextSource {
* Contains the page subtitle. Special pages usually have some links here.
* Don't confuse with site subtitle added by skins.
*/
- var $mSubtitle = '';
+ private $mSubtitle = array();
var $mRedirect = '';
var $mStatusCode;
@@ -126,9 +132,9 @@ class OutputPage extends ContextSource {
var $mTemplateIds = array();
var $mImageTimeKeys = array();
- var $mRedirectCode = '';
+ var $mRedirectCode = '';
- var $mFeedLinksAppendQuery = null;
+ var $mFeedLinksAppendQuery = null;
# What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page?
# @see ResourceLoaderModule::$origin
@@ -147,12 +153,9 @@ class OutputPage extends ContextSource {
var $mContainsOldMagic = 0, $mContainsNewMagic = 0;
/**
- * Should be private. Has get/set methods properly documented.
- * Stores "article flag" toggle.
+ * lazy initialised, use parserOptions()
+ * @var ParserOptions
*/
- var $mIsArticleRelated = true;
-
- /// lazy initialised, use parserOptions()
protected $mParserOptions = null;
/**
@@ -194,6 +197,7 @@ class OutputPage extends ContextSource {
/// should be private. To include the variable {{REVISIONID}}
var $mRevisionId = null;
+ private $mRevisionTimestamp = null;
var $mFileVersion = null;
@@ -220,12 +224,25 @@ class OutputPage extends ContextSource {
);
/**
+ * If the current page was reached through a redirect, $mRedirectedFrom contains the Title
+ * of the redirect.
+ *
+ * @var Title
+ */
+ private $mRedirectedFrom = null;
+
+ /**
* 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( IContextSource $context = null ) {
- $this->setContext( $context );
+ if ( $context === null ) {
+ # Extensions should use `new RequestContext` instead of `new OutputPage` now.
+ wfDeprecated( __METHOD__ );
+ } else {
+ $this->setContext( $context );
+ }
}
/**
@@ -464,7 +481,7 @@ class OutputPage extends ContextSource {
*
* @param $filter
* @param $position
- *
+ *
* @return Array of module names
*/
public function getModuleStyles( $filter = false, $position = null ) {
@@ -506,6 +523,15 @@ class OutputPage extends ContextSource {
}
/**
+ * Get an array of head items
+ *
+ * @return Array
+ */
+ function getHeadItemsArray() {
+ return $this->mHeadItems;
+ }
+
+ /**
* Get all header items in a string
*
* @return String
@@ -743,7 +769,11 @@ class OutputPage extends ContextSource {
* @param $name string
*/
public function setHTMLTitle( $name ) {
- $this->mHTMLtitle = $name;
+ if ( $name instanceof Message ) {
+ $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
+ } else {
+ $this->mHTMLtitle = $name;
+ }
}
/**
@@ -756,21 +786,34 @@ class OutputPage extends ContextSource {
}
/**
+ * Set $mRedirectedFrom, the Title of the page which redirected us to the current page.
+ *
+ * param @t Title
+ */
+ public function setRedirectedFrom( $t ) {
+ $this->mRedirectedFrom = $t;
+ }
+
+ /**
* "Page title" means the contents of \<h1\>. It is stored as a valid HTML fragment.
* This function allows good tags like \<sup\> in the \<h1\> tag, but not bad tags like \<script\>.
* This function automatically sets \<title\> to the same content as \<h1\> but with all tags removed.
* Bad tags that were escaped in \<h1\> will still be escaped in \<title\>, and good tags like \<i\> will be dropped entirely.
*
- * @param $name string
+ * @param $name string|Message
*/
public function setPageTitle( $name ) {
+ if ( $name instanceof Message ) {
+ $name = $name->setContext( $this->getContext() )->text();
+ }
+
# change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
# but leave "<i>foobar</i>" alone
$nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
$this->mPagetitle = $nameWithTags;
# change "<i>foo&amp;bar</i>" to "foo&bar"
- $this->setHTMLTitle( wfMsg( 'pagetitle', Sanitizer::stripAllTags( $nameWithTags ) ) );
+ $this->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) );
}
/**
@@ -787,7 +830,7 @@ class OutputPage extends ContextSource {
*
* @param $t Title object
*/
- public function setTitle( $t ) {
+ public function setTitle( Title $t ) {
$this->getContext()->setTitle( $t );
}
@@ -795,19 +838,54 @@ class OutputPage extends ContextSource {
/**
* Replace the subtile with $str
*
- * @param $str String: new value of the subtitle
+ * @param $str String|Message: new value of the subtitle
*/
public function setSubtitle( $str ) {
- $this->mSubtitle = /*$this->parse(*/ $str /*)*/; // @bug 2514
+ $this->clearSubtitle();
+ $this->addSubtitle( $str );
}
/**
* Add $str to the subtitle
*
- * @param $str String to add to the subtitle
+ * @deprecated in 1.19; use addSubtitle() instead
+ * @param $str String|Message to add to the subtitle
*/
public function appendSubtitle( $str ) {
- $this->mSubtitle .= /*$this->parse(*/ $str /*)*/; // @bug 2514
+ $this->addSubtitle( $str );
+ }
+
+ /**
+ * Add $str to the subtitle
+ *
+ * @param $str String|Message to add to the subtitle
+ */
+ public function addSubtitle( $str ) {
+ if ( $str instanceof Message ) {
+ $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
+ } else {
+ $this->mSubtitle[] = $str;
+ }
+ }
+
+ /**
+ * Add a subtitle containing a backlink to a page
+ *
+ * @param $title Title to link to
+ */
+ public function addBacklinkSubtitle( Title $title ) {
+ $query = array();
+ if ( $title->isRedirect() ) {
+ $query['redirect'] = 'no';
+ }
+ $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) );
+ }
+
+ /**
+ * Clear the subtitles
+ */
+ public function clearSubtitle() {
+ $this->mSubtitle = array();
}
/**
@@ -816,7 +894,7 @@ class OutputPage extends ContextSource {
* @return String
*/
public function getSubtitle() {
- return $this->mSubtitle;
+ return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
}
/**
@@ -1130,9 +1208,11 @@ class OutputPage extends ContextSource {
* Return whether user JavaScript is allowed for this page
* @deprecated since 1.18 Load modules with ResourceLoader, and origin and
* trustworthiness is identified and enforced automagically.
+ * Will be removed in 1.20.
* @return Boolean
*/
public function isUserJsAllowed() {
+ wfDeprecated( __METHOD__, '1.18' );
return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL;
}
@@ -1189,6 +1269,19 @@ class OutputPage extends ContextSource {
}
/**
+ * Shortcut for adding an Html::element via addHTML.
+ *
+ * @since 1.19
+ *
+ * @param $element string
+ * @param $attribs array
+ * @param $contents string
+ */
+ public function addElement( $element, $attribs = array(), $contents = '' ) {
+ $this->addHTML( Html::element( $element, $attribs, $contents ) );
+ }
+
+ /**
* Clear the body HTML
*/
public function clearHTML() {
@@ -1222,7 +1315,7 @@ class OutputPage extends ContextSource {
*/
public function parserOptions( $options = null ) {
if ( !$this->mParserOptions ) {
- $this->mParserOptions = new ParserOptions;
+ $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
$this->mParserOptions->setEditSection( false );
}
return wfSetVar( $this->mParserOptions, $options );
@@ -1250,6 +1343,27 @@ class OutputPage extends ContextSource {
}
/**
+ * Set the timestamp of the revision which will be displayed. This is used
+ * to avoid a extra DB call in Skin::lastModified().
+ *
+ * @param $revid Mixed: string, or null
+ * @return Mixed: previous value
+ */
+ public function setRevisionTimestamp( $timestmap ) {
+ return wfSetVar( $this->mRevisionTimestamp, $timestmap );
+ }
+
+ /**
+ * Get the timestamp of displayed revision.
+ * This will be null if not filled by setRevisionTimestamp().
+ *
+ * @return String or null
+ */
+ public function getRevisionTimestamp() {
+ return $this->mRevisionTimestamp;
+ }
+
+ /**
* Set the displayed file version
*
* @param $file File|false
@@ -1288,7 +1402,7 @@ class OutputPage extends ContextSource {
* @return Array (dbKey => array('time' => MW timestamp or null, 'sha1' => sha1 or ''))
* @since 1.18
*/
- public function getImageTimeKeys() {
+ public function getFileSearchOptions() {
return $this->mImageTimeKeys;
}
@@ -1298,10 +1412,11 @@ class OutputPage extends ContextSource {
*
* @param $text String
* @param $linestart Boolean: is this the start of a line?
+ * @param $interface Boolean: is this text in the user interface language?
*/
- public function addWikiText( $text, $linestart = true ) {
+ public function addWikiText( $text, $linestart = true, $interface = true ) {
$title = $this->getTitle(); // Work arround E_STRICT
- $this->addWikiTextTitle( $text, $title, $linestart );
+ $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
}
/**
@@ -1344,16 +1459,17 @@ class OutputPage extends ContextSource {
* @param $title Title object
* @param $linestart Boolean: is this the start of a line?
* @param $tidy Boolean: whether to use tidy
+ * @param $interface Boolean: whether it is an interface message
+ * (for example disables conversion)
*/
- public function addWikiTextTitle( $text, &$title, $linestart, $tidy = false ) {
+ public function addWikiTextTitle( $text, &$title, $linestart, $tidy = false, $interface = false ) {
global $wgParser;
wfProfileIn( __METHOD__ );
- wfIncrStats( 'pcache_not_possible' );
-
$popts = $this->parserOptions();
$oldTidy = $popts->setTidy( $tidy );
+ $popts->setInterfaceMessage( (bool) $interface );
$parserOutput = $wgParser->parse(
$text, $title, $popts,
@@ -1398,7 +1514,7 @@ class OutputPage extends ContextSource {
}
}
// File versioning...
- foreach ( (array)$parserOutput->getImageTimeKeys() as $dbk => $data ) {
+ foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
$this->mImageTimeKeys[$dbk] = $data;
}
@@ -1446,7 +1562,8 @@ class OutputPage extends ContextSource {
* @param $linestart Boolean: is this the start of a line?
* @param $interface Boolean: use interface language ($wgLang instead of
* $wgContLang) while parsing language sensitive magic
- * words like GRAMMAR and PLURAL
+ * words like GRAMMAR and PLURAL. This also disables
+ * LanguageConverter.
* @param $language Language object: target language object, will override
* $interface
* @return String: HTML
@@ -1636,12 +1753,12 @@ class OutputPage extends ContextSource {
* /w/index.php?title=Main_page&variant=zh-cn should never be served.
*/
function addAcceptLanguage() {
- global $wgContLang;
- if( !$this->getRequest()->getCheck( 'variant' ) && $wgContLang->hasVariants() ) {
- $variants = $wgContLang->getVariants();
+ $lang = $this->getTitle()->getPageLanguage();
+ if( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
+ $variants = $lang->getVariants();
$aloption = array();
foreach ( $variants as $variant ) {
- if( $variant === $wgContLang->getCode() ) {
+ if( $variant === $lang->getCode() ) {
continue;
} else {
$aloption[] = 'string-contains=' . $variant;
@@ -1698,6 +1815,7 @@ class OutputPage extends ContextSource {
} elseif ( $this->mPreventClickjacking && $wgEditPageFrameOptions ) {
return $wgEditPageFrameOptions;
}
+ return false;
}
/**
@@ -1773,7 +1891,7 @@ class OutputPage extends ContextSource {
*
* @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.
*/
@@ -1861,7 +1979,7 @@ class OutputPage extends ContextSource {
wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) );
wfProfileIn( 'Output-skin' );
- $sk->outputPage( $this );
+ $sk->outputPage();
wfProfileOut( 'Output-skin' );
}
@@ -1888,26 +2006,44 @@ class OutputPage extends ContextSource {
}
/**
- * Output a standard error page
+ * Prepare this object to display an error page; disable caching and
+ * indexing, clear the current text and redirect, set the page's title
+ * and optionally an custom HTML title (content of the <title> tag).
*
- * showErrorPage( 'titlemsg', 'pagetextmsg', array( 'param1', 'param2' ) );
- * showErrorPage( 'titlemsg', $messageObject );
- *
- * @param $title String: message key for page title
- * @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
+ * @param $pageTitle String|Message will be passed directly to setPageTitle()
+ * @param $htmlTitle String|Message will be passed directly to setHTMLTitle();
+ * optional, if not passed the <title> attribute will be
+ * based on $pageTitle
*/
- public function showErrorPage( $title, $msg, $params = array() ) {
+ public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
if ( $this->getTitle() ) {
$this->mDebugtext .= 'Original title: ' . $this->getTitle()->getPrefixedText() . "\n";
}
- $this->setPageTitle( wfMsg( $title ) );
- $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
+
+ $this->setPageTitle( $pageTitle );
+ if ( $htmlTitle !== false ) {
+ $this->setHTMLTitle( $htmlTitle );
+ }
$this->setRobotPolicy( 'noindex,nofollow' );
$this->setArticleRelated( false );
$this->enableClientCache( false );
$this->mRedirect = '';
- $this->mBodytext = '';
+ $this->clearSubtitle();
+ $this->clearHTML();
+ }
+
+ /**
+ * Output a standard error page
+ *
+ * showErrorPage( 'titlemsg', 'pagetextmsg', array( 'param1', 'param2' ) );
+ * showErrorPage( 'titlemsg', $messageObject );
+ *
+ * @param $title String: message key for page title
+ * @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() ) {
+ $this->prepareErrorPage( $this->msg( $title ), $this->msg( 'errorpagetitle' ) );
if ( $msg instanceof Message ){
$this->addHTML( $msg->parse() );
@@ -1925,16 +2061,71 @@ class OutputPage extends ContextSource {
* @param $action String: action that was denied or null if unknown
*/
public function showPermissionsErrorPage( $errors, $action = null ) {
- $this->mDebugtext .= 'Original title: ' .
- $this->getTitle()->getPrefixedText() . "\n";
- $this->setPageTitle( wfMsg( 'permissionserrors' ) );
- $this->setHTMLTitle( wfMsg( 'permissionserrors' ) );
- $this->setRobotPolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
- $this->enableClientCache( false );
- $this->mRedirect = '';
- $this->mBodytext = '';
- $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
+ global $wgGroupPermissions;
+
+ // For some action (read, edit, create and upload), display a "login to do this action"
+ // error if all of the following conditions are met:
+ // 1. the user is not logged in
+ // 2. the only error is insufficient permissions (i.e. no block or something else)
+ // 3. the error can be avoided simply by logging in
+ if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) )
+ && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
+ && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
+ && ( ( isset( $wgGroupPermissions['user'][$action] ) && $wgGroupPermissions['user'][$action] )
+ || ( isset( $wgGroupPermissions['autoconfirmed'][$action] ) && $wgGroupPermissions['autoconfirmed'][$action] ) )
+ ) {
+ $displayReturnto = null;
+
+ # Due to bug 32276, if a user does not have read permissions,
+ # $this->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.
+ $request = $this->getRequest();
+ $returnto = Title::newFromURL( $request->getVal( 'title', '' ) );
+ if ( $action == 'edit' ) {
+ $msg = 'whitelistedittext';
+ $displayReturnto = $returnto;
+ } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
+ $msg = 'nocreatetext';
+ } elseif ( $action == 'upload' ) {
+ $msg = 'uploadnologintext';
+ } else { # Read
+ $msg = 'loginreqpagetext';
+ $displayReturnto = Title::newMainPage();
+ }
+
+ $query = array();
+
+ if ( $returnto ) {
+ $query['returnto'] = $returnto->getPrefixedText();
+
+ if ( !$request->wasPosted() ) {
+ $returntoquery = $request->getValues();
+ unset( $returntoquery['title'] );
+ unset( $returntoquery['returnto'] );
+ unset( $returntoquery['returntoquery'] );
+ $query['returntoquery'] = wfArrayToCGI( $returntoquery );
+ }
+ }
+ $loginLink = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Userlogin' ),
+ $this->msg( 'loginreqlink' )->escaped(),
+ array(),
+ $query
+ );
+
+ $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
+ $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
+
+ # Don't return to a page the user can't read otherwise
+ # we'll end up in a pointless loop
+ if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
+ $this->returnToMain( null, $displayReturnto );
+ }
+ } else {
+ $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
+ $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
+ }
}
/**
@@ -1944,11 +2135,7 @@ class OutputPage extends ContextSource {
* @param $version Mixed: the version of MediaWiki needed to use the page
*/
public function versionRequired( $version ) {
- $this->setPageTitle( wfMsg( 'versionrequired', $version ) );
- $this->setHTMLTitle( wfMsg( 'versionrequired', $version ) );
- $this->setRobotPolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
- $this->mBodytext = '';
+ $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
$this->addWikiMsg( 'versionrequiredtext', $version );
$this->returnToMain();
@@ -1965,41 +2152,11 @@ class OutputPage extends ContextSource {
/**
* Produce the stock "please login to use the wiki" page
+ *
+ * @deprecated in 1.19; throw the exception directly
*/
public function loginToUse() {
- global $wgRequest;
-
- if( $this->getUser()->isLoggedIn() ) {
- throw new PermissionsError( 'read' );
- }
-
- $this->setPageTitle( wfMsg( 'loginreqtitle' ) );
- $this->setHtmlTitle( wfMsg( 'errorpagetitle' ) );
- $this->setRobotPolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
-
- $returnto = Title::newFromURL( $wgRequest->getVal( 'title', '' ) );
- $returntoquery = array();
- if( $returnto ) {
- $returntoquery = array( 'returnto' => $returnto->getPrefixedText() );
- }
-
- $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
- $loginLink = Linker::linkKnown(
- $loginTitle,
- wfMsgHtml( 'loginreqlink' ),
- array(),
- $returntoquery
- );
- $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
- $mainPage = Title::newMainPage();
- if( $mainPage->userCanRead() ) {
- $this->returnToMain( null, $mainPage );
- }
+ throw new PermissionsError( 'read' );
}
/**
@@ -2011,14 +2168,14 @@ class OutputPage extends ContextSource {
*/
public function formatPermissionsErrorMessage( $errors, $action = null ) {
if ( $action == null ) {
- $text = wfMsgNoTrans( 'permissionserrorstext', count( $errors ) ) . "\n\n";
+ $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
} else {
- $action_desc = wfMsgNoTrans( "action-$action" );
- $text = wfMsgNoTrans(
+ $action_desc = $this->msg( "action-$action" )->plain();
+ $text = $this->msg(
'permissionserrorstext-withaction',
count( $errors ),
$action_desc
- ) . "\n\n";
+ )->plain() . "\n\n";
}
if ( count( $errors ) > 1 ) {
@@ -2026,13 +2183,13 @@ class OutputPage extends ContextSource {
foreach( $errors as $error ) {
$text .= '<li>';
- $text .= call_user_func_array( 'wfMsgNoTrans', $error );
+ $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain();
$text .= "</li>\n";
}
$text .= '</ul>';
} else {
$text .= "<div class=\"permissions-errors\">\n" .
- call_user_func_array( 'wfMsgNoTrans', reset( $errors ) ) .
+ call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() .
"\n</div>";
}
@@ -2072,12 +2229,10 @@ class OutputPage extends ContextSource {
if ( !empty( $reasons ) ) {
// Permissions error
if( $source ) {
- $this->setPageTitle( wfMsg( 'viewsource' ) );
- $this->setSubtitle(
- wfMsg( 'viewsourcefor', Linker::linkKnown( $this->getTitle() ) )
- );
+ $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) );
+ $this->addBacklinkSubtitle( $this->getTitle() );
} else {
- $this->setPageTitle( wfMsg( 'badaccess' ) );
+ $this->setPageTitle( $this->msg( 'badaccess' ) );
}
$this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
} else {
@@ -2089,18 +2244,20 @@ class OutputPage extends ContextSource {
if( is_string( $source ) ) {
$this->addWikiMsg( 'viewsourcetext' );
+ $pageLang = $this->getTitle()->getPageLanguage();
$params = array(
'id' => 'wpTextbox1',
'name' => 'wpTextbox1',
'cols' => $this->getUser()->getOption( 'cols' ),
'rows' => $this->getUser()->getOption( 'rows' ),
- 'readonly' => 'readonly'
+ 'readonly' => 'readonly',
+ 'lang' => $pageLang->getHtmlCode(),
+ 'dir' => $pageLang->getDir(),
);
$this->addHTML( Html::element( 'textarea', $params, $source ) );
// Show templates used by this article
- $article = new Article( $this->getTitle() );
- $templates = Linker::formatTemplates( $article->getUsedTemplates() );
+ $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() );
$this->addHTML( "<div class='templatesUsed'>
$templates
</div>
@@ -2139,37 +2296,34 @@ $templates
? '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 ) ) );
+ $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) );
}
}
public function showFatalError( $message ) {
- $this->setPageTitle( wfMsg( 'internalerror' ) );
- $this->setRobotPolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
- $this->enableClientCache( false );
- $this->mRedirect = '';
- $this->mBodytext = $message;
+ $this->prepareErrorPage( $this->msg( 'internalerror' ) );
+
+ $this->addHTML( $message );
}
public function showUnexpectedValueError( $name, $val ) {
- $this->showFatalError( wfMsg( 'unexpected', $name, $val ) );
+ $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
}
public function showFileCopyError( $old, $new ) {
- $this->showFatalError( wfMsg( 'filecopyerror', $old, $new ) );
+ $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
}
public function showFileRenameError( $old, $new ) {
- $this->showFatalError( wfMsg( 'filerenameerror', $old, $new ) );
+ $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
}
public function showFileDeleteError( $name ) {
- $this->showFatalError( wfMsg( 'filedeleteerror', $name ) );
+ $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
}
public function showFileNotFoundError( $name ) {
- $this->showFatalError( wfMsg( 'filenotfound', $name ) );
+ $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
}
/**
@@ -2181,10 +2335,8 @@ $templates
*/
public function addReturnTo( $title, $query = array(), $text = null ) {
$this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL() ) );
- $link = wfMsgHtml(
- 'returnto',
- Linker::link( $title, $text, array(), $query )
- );
+ $link = $this->msg( 'returnto' )->rawParams(
+ Linker::link( $title, $text, array(), $query ) )->escaped();
$this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
}
@@ -2227,19 +2379,19 @@ $templates
* @return String: The doctype, opening <html>, and head element.
*/
public function headElement( Skin $sk, $includeStyle = true ) {
- global $wgContLang, $wgUseTrackbacks;
- $userdir = $this->getLang()->getDir();
+ global $wgContLang;
+
+ $userdir = $this->getLanguage()->getDir();
$sitedir = $wgContLang->getDir();
if ( $sk->commonPrintStylesheet() ) {
$this->addModuleStyles( 'mediawiki.legacy.wikiprintable' );
}
- $sk->setupUserCss( $this );
- $ret = Html::htmlHeader( array( 'lang' => $this->getLang()->getCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
+ $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
if ( $this->getHTMLTitle() == '' ) {
- $this->setHTMLTitle( wfMsg( 'pagetitle', $this->getPageTitle() ) );
+ $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) );
}
$openHead = Html::openElement( 'head' );
@@ -2251,16 +2403,12 @@ $templates
$ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
$ret .= implode( "\n", array(
- $this->getHeadLinks( $sk, true ),
- $this->buildCssLinks( $sk ),
- $this->getHeadScripts( $sk ),
+ $this->getHeadLinks( null, true ),
+ $this->buildCssLinks(),
+ $this->getHeadScripts(),
$this->getHeadItems()
) );
- if ( $wgUseTrackbacks && $this->isArticleRelated() ) {
- $ret .= $this->getTitle()->trackbackRDF();
- }
-
$closeHead = Html::closeElement( 'head' );
if ( $closeHead ) {
$ret .= "$closeHead\n";
@@ -2268,29 +2416,16 @@ $templates
$bodyAttrs = array();
- # Crazy edit-on-double-click stuff
- $action = $this->getRequest()->getVal( 'action', 'view' );
-
- if (
- $this->getTitle()->getNamespace() != NS_SPECIAL &&
- in_array( $action, array( 'view', 'purge' ) ) &&
- $this->getUser()->getOption( 'editondblclick' )
- )
- {
- $editUrl = $this->getTitle()->getLocalUrl( $sk->editUrlOptions() );
- $bodyAttrs['ondblclick'] = "document.location = '" .
- Xml::escapeJsString( $editUrl ) . "'";
- }
-
# Classes for LTR/RTL directionality support
$bodyAttrs['class'] = "mediawiki $userdir sitedir-$sitedir";
- if ( $this->getContext()->getLang()->capitalizeAllNouns() ) {
+ if ( $this->getLanguage()->capitalizeAllNouns() ) {
# A <body> class is probably not the best way to do this . . .
$bodyAttrs['class'] .= ' capitalize-all-nouns';
}
$bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() );
$bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
+ $bodyAttrs['class'] .= ' action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
$sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need
wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
@@ -2304,13 +2439,12 @@ $templates
* Add the default ResourceLoader modules to this object
*/
private function addDefaultModules() {
- global $wgIncludeLegacyJavaScript, $wgUseAjax,
+ global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
$wgAjaxWatch, $wgEnableMWSuggest;
// Add base resources
$this->addModules( array(
'mediawiki.user',
- 'mediawiki.util',
'mediawiki.page.startup',
'mediawiki.page.ready',
) );
@@ -2318,6 +2452,12 @@ $templates
$this->addModules( 'mediawiki.legacy.wikibits' );
}
+ if ( $wgPreloadJavaScriptMwUtil ) {
+ $this->addModules( 'mediawiki.util' );
+ }
+
+ MWDebug::addModules( $this );
+
// Add various resources if required
if ( $wgUseAjax ) {
$this->addModules( 'mediawiki.legacy.ajax' );
@@ -2336,6 +2476,11 @@ $templates
if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) {
$this->addModules( 'mediawiki.action.view.rightClickEdit' );
}
+
+ # Crazy edit-on-double-click stuff
+ if ( $this->isArticle() && $this->getUser()->getOption( 'editondblclick' ) ) {
+ $this->addModules( 'mediawiki.action.view.dblClickEdit' );
+ }
}
/**
@@ -2352,29 +2497,15 @@ $templates
/**
* TODO: Document
- * @param $skin Skin
- * @param $modules Array/string with the module name
+ * @param $modules Array/string with the module name(s)
* @param $only String ResourceLoaderModule TYPE_ class constant
* @param $useESI boolean
+ * @param $extraQuery Array with extra query parameters to add to each request. array( param => value )
+ * @param $loadCall boolean If true, output an (asynchronous) mw.loader.load() call rather than a <script src="..."> tag
* @return string html <script> and <style> tags
*/
- protected function makeResourceLoaderLink( Skin $skin, $modules, $only, $useESI = false ) {
- global $wgLoadScript, $wgResourceLoaderUseESI;
- // Lazy-load ResourceLoader
- // TODO: Should this be a static function of ResourceLoader instead?
- $baseQuery = array(
- 'lang' => $this->getContext()->getLang()->getCode(),
- 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
- 'skin' => $skin->getSkinName(),
- 'only' => $only,
- );
- // Propagate printable and handheld parameters if present
- if ( $this->isPrintable() ) {
- $baseQuery['printable'] = 1;
- }
- if ( $this->getRequest()->getBool( 'handheld' ) ) {
- $baseQuery['handheld'] = 1;
- }
+ protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) {
+ global $wgResourceLoaderUseESI;
if ( !count( $modules ) ) {
return '';
@@ -2390,7 +2521,7 @@ $templates
// Recursively call us for every item
$links = '';
foreach ( $modules as $name ) {
- $links .= $this->makeResourceLoaderLink( $skin, $name, $only, $useESI );
+ $links .= $this->makeResourceLoaderLink( $name, $only, $useESI );
}
return $links;
}
@@ -2421,14 +2552,26 @@ $templates
$links = '';
foreach ( $groups as $group => $modules ) {
- $query = $baseQuery;
// Special handling for user-specific groups
+ $user = null;
if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
- $query['user'] = $this->getUser()->getName();
+ $user = $this->getUser()->getName();
}
// Create a fake request based on the one we are about to make so modules return
// correct timestamp and emptiness data
+ $query = ResourceLoader::makeLoaderQuery(
+ array(), // modules; not determined yet
+ $this->getLanguage()->getCode(),
+ $this->getSkin()->getSkinName(),
+ $user,
+ null, // version; not determined yet
+ ResourceLoader::inDebugMode(),
+ $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
+ $this->isPrintable(),
+ $this->getRequest()->getBool( 'handheld' ),
+ $extraQuery
+ );
$context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
// Drop modules that know they're empty
foreach ( $modules as $key => $module ) {
@@ -2441,8 +2584,6 @@ $templates
continue;
}
- $query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $modules ) );
-
// Inline private modules. These can't be loaded through load.php for security
// reasons, see bug 34907. Note that these modules should be loaded from
// getHeadScripts() before the first loader call. Otherwise other modules can't
@@ -2459,6 +2600,7 @@ $templates
)
);
}
+ $links .= "\n";
continue;
}
// Special handling for the user group; because users might change their stuff
@@ -2466,6 +2608,7 @@ $templates
// timestamp of these user-changable modules so we can ensure cache misses on change
// This should NOT be done for the site group (bug 27564) because anons get that too
// and we shouldn't be putting timestamps in Squid-cached HTML
+ $version = null;
if ( $group === 'user' ) {
// Get the maximum timestamp
$timestamp = 1;
@@ -2473,15 +2616,21 @@ $templates
$timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
}
// Add a version parameter so cache will break when things change
- $query['version'] = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
+ $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
}
- // Make queries uniform in order
- ksort( $query );
- $url = wfAppendQuery( $wgLoadScript, $query );
- // Prevent the IE6 extension check from being triggered (bug 28840)
- // by appending a character that's invalid in Windows extensions ('*')
- $url .= '&*';
+ $url = ResourceLoader::makeLoaderURL(
+ array_keys( $modules ),
+ $this->getLanguage()->getCode(),
+ $this->getSkin()->getSkinName(),
+ $user,
+ $version,
+ ResourceLoader::inDebugMode(),
+ $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
+ $this->isPrintable(),
+ $this->getRequest()->getBool( 'handheld' ),
+ $extraQuery
+ );
if ( $useESI && $wgResourceLoaderUseESI ) {
$esi = Xml::element( 'esi:include', array( 'src' => $url ) );
if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
@@ -2493,6 +2642,12 @@ $templates
// Automatically select style/script elements
if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
$link = Html::linkedStyle( $url );
+ } else if ( $loadCall ) {
+ $link = Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
+ )
+ );
} else {
$link = Html::linkedScript( $url );
}
@@ -2511,12 +2666,13 @@ $templates
* 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 ) {
+ function getHeadScripts() {
+ global $wgResourceLoaderExperimentalAsyncLoading;
+
// Startup - this will immediately load jquery and mediawiki modules
- $scripts = $this->makeResourceLoaderLink( $sk, 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
+ $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
// Load config before anything else
$scripts .= Html::inlineScript(
@@ -2525,10 +2681,16 @@ $templates
)
);
+ // Load embeddable private modules before any loader links
+ // This needs to be TYPE_COMBINED so these modules are properly wrapped
+ // in mw.loader.implement() calls and deferred until mw.user is available
+ $embedScripts = array( 'user.options', 'user.tokens' );
+ $scripts .= $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
+
// 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 );
+ $scripts .= $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES );
+ $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS );
// Modules requests - let the client calculate dependencies and batch requests as it likes
// Only load modules that have marked themselves for loading at the top
@@ -2540,25 +2702,37 @@ $templates
)
);
}
+
+ if ( $wgResourceLoaderExperimentalAsyncLoading ) {
+ $scripts .= $this->getScriptsForBottomQueue( true );
+ }
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
+ * JS stuff to put at the 'bottom', which can either be the bottom of the <body>
+ * or the bottom of the <head> depending on $wgResourceLoaderExperimentalAsyncLoading:
+ * modules marked with position 'bottom', legacy scripts ($this->mScripts),
+ * user preferences, site JS and user JS
*
+ * @param $inHead boolean If true, this HTML goes into the <head>, if false it goes into the <body>
* @return string
*/
- function getBottomScripts( Skin $sk ) {
+ function getScriptsForBottomQueue( $inHead ) {
global $wgUseSiteJs, $wgAllowUserJs;
// Script and Messages "only" requests marked for bottom inclusion
+ // If we're in the <head>, use load() calls rather than <script src="..."> tags
// Messages should go first
- $scripts = $this->makeResourceLoaderLink( $sk, $this->getModuleMessages( true, 'bottom' ), ResourceLoaderModule::TYPE_MESSAGES );
- $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleScripts( true, 'bottom' ), ResourceLoaderModule::TYPE_SCRIPTS );
+ $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
+ ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(),
+ /* $loadCall = */ $inHead
+ );
+ $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
+ ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(),
+ /* $loadCall = */ $inHead
+ );
// 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
@@ -2566,7 +2740,7 @@ $templates
if ( $modules ) {
$scripts .= Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
)
);
}
@@ -2574,11 +2748,13 @@ $templates
// Legacy Scripts
$scripts .= "\n" . $this->mScripts;
- $userScripts = array( 'user.options', 'user.tokens' );
+ $userScripts = array();
// Add site JS if enabled
if ( $wgUseSiteJs ) {
- $scripts .= $this->makeResourceLoaderLink( $sk, 'site', ResourceLoaderModule::TYPE_SCRIPTS );
+ $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
if( $this->getUser()->isLoggedIn() ){
$userScripts[] = 'user.groups';
}
@@ -2586,28 +2762,50 @@ $templates
// 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 ) ) {
+ if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
# XXX: additional security check/prompt?
+ // We're on a preview of a JS subpage
+ // Exclude this page from the user module in case it's in there (bug 26283)
+ $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
+ array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
+ );
+ // Load the previewed JS
$scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
} else {
- # @todo FIXME: This means that User:Me/Common.js doesn't load when previewing
- # User:Me/Vector.js, and vice versa (bug26283)
- $userScripts[] = 'user';
+ // Include the user module normally
+ // We can't do $userScripts[] = 'user'; because the user module would end up
+ // being wrapped in a closure, so load it raw like 'site'
+ $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
}
}
- $scripts .= $this->makeResourceLoaderLink( $sk, $userScripts, ResourceLoaderModule::TYPE_SCRIPTS );
+ $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
return $scripts;
}
/**
+ * JS stuff to put at the bottom of the <body>
+ */
+ function getBottomScripts() {
+ global $wgResourceLoaderExperimentalAsyncLoading;
+ if ( !$wgResourceLoaderExperimentalAsyncLoading ) {
+ return $this->getScriptsForBottomQueue( false );
+ } else {
+ return '';
+ }
+ }
+
+ /**
* 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.
+ * @param $value {Mixed} [optional] Value of the configuration variable.
*/
- public function addJsConfigVars( $keys, $value ) {
+ public function addJsConfigVars( $keys, $value = null ) {
if ( is_array( $keys ) ) {
foreach ( $keys as $key => $value ) {
$this->mJsConfigVars[$key] = $value;
@@ -2626,40 +2824,72 @@ $templates
* This is only public until that function is removed. You have been warned.
*
* Do not add things here which can be evaluated in ResourceLoaderStartupScript
- * - in other words, page-indendent/site-wide variables (without state).
+ * - in other words, page-independent/site-wide variables (without state).
* You will only be adding bloat to the html page and causing page caches to
* have to be purged on configuration changes.
+ * @return array
*/
public function getJSVars() {
- global $wgUseAjax, $wgEnableMWSuggest, $wgContLang;
+ global $wgUseAjax, $wgEnableMWSuggest;
+
+ $latestRevID = 0;
+ $pageID = 0;
+ $canonicalName = false; # bug 21115
$title = $this->getTitle();
$ns = $title->getNamespace();
$nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
+
+ // Get the relevant title so that AJAX features can use the correct page name
+ // when making API requests from certain special pages (bug 34972).
+ $relevantTitle = $this->getSkin()->getRelevantTitle();
+
if ( $ns == NS_SPECIAL ) {
list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
- } else {
- $canonicalName = false; # bug 21115
+ } elseif ( $this->canUseWikiPage() ) {
+ $wikiPage = $this->getWikiPage();
+ $latestRevID = $wikiPage->getLatest();
+ $pageID = $wikiPage->getId();
}
+ $lang = $title->getPageLanguage();
+
+ // Pre-process information
+ $separatorTransTable = $lang->separatorTransformTable();
+ $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
+ $compactSeparatorTransTable = array(
+ implode( "\t", array_keys( $separatorTransTable ) ),
+ implode( "\t", $separatorTransTable ),
+ );
+ $digitTransTable = $lang->digitTransformTable();
+ $digitTransTable = $digitTransTable ? $digitTransTable : array();
+ $compactDigitTransTable = array(
+ implode( "\t", array_keys( $digitTransTable ) ),
+ implode( "\t", $digitTransTable ),
+ );
+
$vars = array(
'wgCanonicalNamespace' => $nsname,
'wgCanonicalSpecialPageName' => $canonicalName,
'wgNamespaceNumber' => $title->getNamespace(),
'wgPageName' => $title->getPrefixedDBKey(),
'wgTitle' => $title->getText(),
- 'wgCurRevisionId' => $title->getLatestRevID(),
- 'wgArticleId' => $title->getArticleId(),
+ 'wgCurRevisionId' => $latestRevID,
+ 'wgArticleId' => $pageID,
'wgIsArticle' => $this->isArticle(),
- 'wgAction' => $this->getRequest()->getText( 'action', 'view' ),
+ 'wgAction' => Action::getActionName( $this->getContext() ),
'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(),
'wgUserGroups' => $this->getUser()->getEffectiveGroups(),
'wgCategories' => $this->getCategories(),
'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
+ 'wgPageContentLanguage' => $lang->getCode(),
+ 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
+ 'wgDigitTransformTable' => $compactDigitTransTable,
+ 'wgRelevantPageName' => $relevantTitle->getPrefixedDBKey(),
);
- if ( $wgContLang->hasVariants() ) {
- $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
- }
+ if ( $lang->hasVariants() ) {
+ $vars['wgUserVariant'] = $lang->getPreferredVariant();
+ }
foreach ( $title->getRestrictionTypes() as $type ) {
$vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
}
@@ -2669,28 +2899,55 @@ $templates
if ( $title->isMainPage() ) {
$vars['wgIsMainPage'] = true;
}
+ if ( $this->mRedirectedFrom ) {
+ $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBKey();
+ }
// 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 ) );
+ wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) );
// Merge in variables from addJsConfigVars last
return array_merge( $vars, $this->mJsConfigVars );
}
/**
- * @param $sk Skin
+ * To make it harder for someone to slip a user a fake
+ * user-JavaScript or user-CSS preview, a random token
+ * is associated with the login session. If it's not
+ * passed back with the preview request, we won't render
+ * the code.
+ *
+ * @return bool
+ */
+ public function userCanPreview() {
+ if ( $this->getRequest()->getVal( 'action' ) != 'submit'
+ || !$this->getRequest()->wasPosted()
+ || !$this->getUser()->matchEditToken(
+ $this->getRequest()->getVal( 'wpEditToken' ) )
+ ) {
+ return false;
+ }
+ if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) {
+ return false;
+ }
+
+ return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) );
+ }
+
+ /**
+ * @param $unused Unused
* @param $addContentType bool
*
* @return string HTML tag links to be put in the header.
*/
- public function getHeadLinks( Skin $sk, $addContentType = false ) {
+ public function getHeadLinks( $unused = null, $addContentType = false ) {
global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
$wgSitename, $wgVersion, $wgHtml5, $wgMimeType,
$wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
- $wgDisableLangConversion, $wgCanonicalLanguageLinks, $wgContLang,
+ $wgDisableLangConversion, $wgCanonicalLanguageLinks,
$wgRightsPage, $wgRightsUrl;
$tags = array();
@@ -2762,11 +3019,12 @@ $templates
}
# Universal edit button
- if ( $wgUniversalEditButton ) {
- if ( $this->isArticleRelated() && $this->getTitle() && $this->getTitle()->quickUserCan( 'edit' )
- && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create' ) ) ) {
+ if ( $wgUniversalEditButton && $this->isArticleRelated() ) {
+ $user = $this->getUser();
+ if ( $this->getTitle()->quickUserCan( 'edit', $user )
+ && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
// Original UniversalEditButton
- $msg = wfMsg( 'edit' );
+ $msg = $this->msg( 'edit' )->text();
$tags[] = Html::element( 'link', array(
'rel' => 'alternate',
'type' => 'application/x-wiki',
@@ -2799,7 +3057,7 @@ $templates
'rel' => 'search',
'type' => 'application/opensearchdescription+xml',
'href' => wfScript( 'opensearch_desc' ),
- 'title' => wfMsgForContent( 'opensearch-desc' ),
+ 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
) );
if ( $wgEnableAPI ) {
@@ -2816,26 +3074,29 @@ $templates
) );
}
- # Language variants
- if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks
- && $wgContLang->hasVariants() ) {
-
- $urlvar = $wgContLang->getURLVariant();
- if ( !$urlvar ) {
- $variants = $wgContLang->getVariants();
- foreach ( $variants as $_v ) {
+ # Language variants
+ if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) {
+ $lang = $this->getTitle()->getPageLanguage();
+ if ( $lang->hasVariants() ) {
+
+ $urlvar = $lang->getURLVariant();
+
+ if ( !$urlvar ) {
+ $variants = $lang->getVariants();
+ foreach ( $variants as $_v ) {
+ $tags[] = Html::element( 'link', array(
+ 'rel' => 'alternate',
+ 'hreflang' => $_v,
+ 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
+ );
+ }
+ } else {
$tags[] = Html::element( 'link', array(
- 'rel' => 'alternate',
- 'hreflang' => $_v,
- 'href' => $this->getTitle()->getLocalURL( '', $_v ) )
- );
+ 'rel' => 'canonical',
+ 'href' => $this->getTitle()->getCanonicalUrl()
+ ) );
}
- } else {
- $tags[] = Html::element( 'link', array(
- 'rel' => 'canonical',
- 'href' => $this->getTitle()->getCanonicalUrl()
- ) );
}
}
@@ -2872,7 +3133,7 @@ $templates
$format,
$link,
# Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
- wfMsg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )
+ $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text()
);
}
@@ -2883,24 +3144,22 @@ $templates
# like to promote instead of the RC feed (maybe like a "Recent New Articles"
# or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
# If so, use it instead.
-
- $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
-
if ( $wgOverrideSiteFeed ) {
foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
// Note, this->feedLink escapes the url.
$tags[] = $this->feedLink(
$type,
$feedUrl,
- wfMsg( "site-{$type}-feed", $wgSitename )
+ $this->msg( "site-{$type}-feed", $wgSitename )->text()
);
}
- } elseif ( $this->getTitle()->getPrefixedText() != $rctitle->getPrefixedText() ) {
+ } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
+ $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
foreach ( $wgAdvertisedFeedTypes as $format ) {
$tags[] = $this->feedLink(
$format,
$rctitle->getLocalURL( "feed={$format}" ),
- wfMsg( "site-{$format}-feed", $wgSitename ) # For grep: 'site-rss-feed', 'site-atom-feed'.
+ $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'.
);
}
}
@@ -2953,10 +3212,10 @@ $templates
/**
* Adds inline CSS styles
* @param $style_css Mixed: inline CSS
- * @param $flip False or String: Set to 'flip' to flip the CSS if needed
+ * @param $flip String: Set to 'flip' to flip the CSS if needed
*/
- public function addInlineStyle( $style_css, $flip = false ) {
- if( $flip === 'flip' && $this->getLang()->isRTL() ) {
+ public function addInlineStyle( $style_css, $flip = 'noflip' ) {
+ if( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
# If wanted, and the interface is right-to-left, flip the CSS
$style_css = CSSJanus::transform( $style_css, true, false );
}
@@ -2966,17 +3225,61 @@ $templates
/**
* 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 = '';
+ public function buildCssLinks() {
+ global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs,
+ $wgLang, $wgContLang;
+
+ $this->getSkin()->setupSkinUserCss( $this );
+
// Add ResourceLoader styles
// Split the styles into four groups
$styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() );
+ $otherTags = ''; // Tags to append after the normal <link> tags
$resourceLoader = $this->getResourceLoader();
- foreach ( $this->getModuleStyles() as $name ) {
+
+ $moduleStyles = $this->getModuleStyles();
+
+ // Per-site custom styles
+ if ( $wgUseSiteCss ) {
+ $moduleStyles[] = 'site';
+ $moduleStyles[] = 'noscript';
+ if( $this->getUser()->isLoggedIn() ){
+ $moduleStyles[] = 'user.groups';
+ }
+ }
+
+ // Per-user custom styles
+ if ( $wgAllowUserCss ) {
+ if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) {
+ // We're on a preview of a CSS subpage
+ // Exclude this page from the user module in case it's in there (bug 26283)
+ $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
+ array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
+ );
+
+ // Load the previewed CSS
+ // If needed, Janus it first. This is user-supplied CSS, so it's
+ // assumed to be right for the content language directionality.
+ $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
+ if ( $wgLang->getDir() !== $wgContLang->getDir() ) {
+ $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
+ }
+ $otherTags .= Html::inlineStyle( $previewedCSS );
+ } else {
+ // Load the user styles normally
+ $moduleStyles[] = 'user';
+ }
+ }
+
+ // Per-user preference styles
+ if ( $wgAllowUserCssPrefs ) {
+ $moduleStyles[] = 'user.cssprefs';
+ }
+
+ foreach ( $moduleStyles as $name ) {
$module = $resourceLoader->getModule( $name );
if ( !$module ) {
continue;
@@ -2991,7 +3294,7 @@ $templates
// 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'], ResourceLoaderModule::TYPE_STYLES );
+ $ret = $this->makeResourceLoaderLink( $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
@@ -3002,15 +3305,28 @@ $templates
// 'private' at present only contains user.options, so put that before 'user'
// Any future private modules will likely have a similar user-specific character
foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) {
- $ret .= $this->makeResourceLoaderLink( $sk, $styles[$group],
+ $ret .= $this->makeResourceLoaderLink( $styles[$group],
ResourceLoaderModule::TYPE_STYLES
);
}
+
+ // Add stuff in $otherTags (previewed user CSS if applicable)
+ $ret .= $otherTags;
return $ret;
}
+ /**
+ * @return Array
+ */
public function buildCssLinksArray() {
$links = array();
+
+ // Add any extension CSS
+ foreach ( $this->mExtStyles as $url ) {
+ $this->addStyle( $url );
+ }
+ $this->mExtStyles = array();
+
foreach( $this->styles as $file => $options ) {
$link = $this->styleLink( $file, $options );
if( $link ) {
@@ -3030,7 +3346,7 @@ $templates
*/
protected function styleLink( $style, $options ) {
if( isset( $options['dir'] ) ) {
- if( $this->getLang()->getDir() != $options['dir'] ) {
+ if( $this->getLanguage()->getDir() != $options['dir'] ) {
return '';
}
}
@@ -3118,16 +3434,11 @@ $templates
* Like addWikiMsg() except the parameters are taken as an array
* 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';
- $text = wfMsgExt( $name, $options, $args );
- $this->addHTML( $text );
+ public function addWikiMsgArray( $name, $args ) {
+ $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
}
/**
diff --git a/includes/PHPVersionError.php b/includes/PHPVersionError.php
index 91502a4c..ec6490a8 100644
--- a/includes/PHPVersionError.php
+++ b/includes/PHPVersionError.php
@@ -7,7 +7,7 @@
*
* Calling this function kills execution immediately.
*
- * @param $entryPoint String Which entry point we're protecting. One of:
+ * @param $type String Which entry point we are protecting. One of:
* - index.php
* - load.php
* - api.php
@@ -17,7 +17,7 @@
* version are hardcoded here
*/
function wfPHPVersionError( $type ){
- $mwVersion = '1.18';
+ $mwVersion = '1.19';
$phpVersion = PHP_VERSION;
$message = "MediaWiki $mwVersion requires at least PHP version 5.2.3, you are using PHP $phpVersion.";
if( $type == 'index.php' ) {
@@ -32,7 +32,7 @@ function wfPHPVersionError( $type ){
header( 'Cache-control: none' );
header( 'Pragma: nocache' );
-$finalOutput = <<<HTML
+ $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>
@@ -88,4 +88,4 @@ HTML;
}
echo( "$finalOutput\n" );
die( 1 );
-} \ No newline at end of file
+}
diff --git a/includes/PageQueryPage.php b/includes/PageQueryPage.php
index 8390241f..dc5e971d 100644
--- a/includes/PageQueryPage.php
+++ b/includes/PageQueryPage.php
@@ -21,6 +21,6 @@ abstract class PageQueryPage extends QueryPage {
if ( $title instanceof Title ) {
$text = $wgContLang->convert( $title->getPrefixedText() );
}
- return $skin->link( $title, htmlspecialchars( $text ), array(), array(), array('known', 'noclasses') );
+ return Linker::linkKnown( $title, htmlspecialchars( $text ) );
}
}
diff --git a/includes/Pager.php b/includes/Pager.php
index 81d95593..faae3d2d 100644
--- a/includes/Pager.php
+++ b/includes/Pager.php
@@ -57,7 +57,7 @@ interface Pager {
*
* @ingroup Pager
*/
-abstract class IndexPager implements Pager {
+abstract class IndexPager extends ContextSource implements Pager {
public $mRequest;
public $mLimitsShown = array( 20, 50, 100, 250, 500 );
public $mDefaultLimit = 50;
@@ -67,10 +67,15 @@ abstract class IndexPager implements Pager {
public $mPastTheEndRow;
/**
- * The index to actually be used for ordering. This is a single string
- * even if multiple orderings are supported.
+ * The index to actually be used for ordering. This is a single column,
+ * for one ordering, even if multiple orderings are supported.
*/
protected $mIndexField;
+ /**
+ * An array of secondary columns to order by. These fields are not part of the offset.
+ * This is a column list for one ordering, even if multiple orderings are supported.
+ */
+ protected $mExtraSortFields;
/** For pages that support multiple types of ordering, which one to use.
*/
protected $mOrderType;
@@ -90,6 +95,9 @@ abstract class IndexPager implements Pager {
/** True if the current result set is the first one */
public $mIsFirst;
+ public $mIsLast;
+
+ protected $mLastShown, $mFirstShown, $mPastTheEndIndex, $mDefaultQuery, $mNavigationBar;
/**
* Result object for the query. Warning: seek before use.
@@ -98,9 +106,12 @@ abstract class IndexPager implements Pager {
*/
public $mResult;
- public function __construct() {
- global $wgRequest, $wgUser;
- $this->mRequest = $wgRequest;
+ public function __construct( IContextSource $context = null ) {
+ if ( $context ) {
+ $this->setContext( $context );
+ }
+
+ $this->mRequest = $this->getRequest();
# NB: the offset is quoted, not validated. It is treated as an
# arbitrary string to support the widest variety of index types. Be
@@ -108,25 +119,33 @@ abstract class IndexPager implements Pager {
$this->mOffset = $this->mRequest->getText( 'offset' );
# Use consistent behavior for the limit options
- $this->mDefaultLimit = intval( $wgUser->getOption( 'rclimit' ) );
+ $this->mDefaultLimit = intval( $this->getUser()->getOption( 'rclimit' ) );
list( $this->mLimit, /* $offset */ ) = $this->mRequest->getLimitOffset();
$this->mIsBackwards = ( $this->mRequest->getVal( 'dir' ) == 'prev' );
$this->mDb = wfGetDB( DB_SLAVE );
- $index = $this->getIndexField();
+ $index = $this->getIndexField(); // column to sort on
+ $extraSort = $this->getExtraSortFields(); // extra columns to sort on for query planning
$order = $this->mRequest->getVal( 'order' );
if( is_array( $index ) && isset( $index[$order] ) ) {
$this->mOrderType = $order;
$this->mIndexField = $index[$order];
+ $this->mExtraSortFields = isset( $extraSort[$order] )
+ ? (array)$extraSort[$order]
+ : array();
} elseif( is_array( $index ) ) {
# First element is the default
reset( $index );
list( $this->mOrderType, $this->mIndexField ) = each( $index );
+ $this->mExtraSortFields = isset( $extraSort[$this->mOrderType] )
+ ? (array)$extraSort[$this->mOrderType]
+ : array();
} else {
# $index is not an array
$this->mOrderType = null;
$this->mIndexField = $index;
+ $this->mExtraSortFields = (array)$extraSort;
}
if( !isset( $this->mDefaultDirection ) ) {
@@ -173,13 +192,17 @@ abstract class IndexPager implements Pager {
}
/**
- * Set the offset from an other source than $wgRequest
+ * Set the offset from an other source than the request
+ *
+ * @param $offset Int|String
*/
function setOffset( $offset ) {
$this->mOffset = $offset;
}
/**
- * Set the limit from an other source than $wgRequest
+ * Set the limit from an other source than the request
+ *
+ * @param $limit Int|String
*/
function setLimit( $limit ) {
$this->mLimit = $limit;
@@ -269,11 +292,16 @@ abstract class IndexPager implements Pager {
$conds = isset( $info['conds'] ) ? $info['conds'] : array();
$options = isset( $info['options'] ) ? $info['options'] : array();
$join_conds = isset( $info['join_conds'] ) ? $info['join_conds'] : array();
+ $sortColumns = array_merge( array( $this->mIndexField ), $this->mExtraSortFields );
if ( $descending ) {
- $options['ORDER BY'] = $this->mIndexField;
+ $options['ORDER BY'] = implode( ',', $sortColumns );
$operator = '>';
} else {
- $options['ORDER BY'] = $this->mIndexField . ' DESC';
+ $orderBy = array();
+ foreach ( $sortColumns as $col ) {
+ $orderBy[] = $col . ' DESC';
+ }
+ $options['ORDER BY'] = implode( ',', $orderBy );
$operator = '<';
}
if ( $offset != '' ) {
@@ -297,10 +325,16 @@ abstract class IndexPager implements Pager {
*
* @return String
*/
- function getBody() {
+ public function getBody() {
if ( !$this->mQueryDone ) {
$this->doQuery();
}
+
+ if ( $this->mResult->numRows() ) {
+ # Do any special query batches before display
+ $this->doBatchLookups();
+ }
+
# Don't use any extra rows returned by the query
$numRows = min( $this->mResult->numRows(), $this->mLimit );
@@ -348,22 +382,30 @@ abstract class IndexPager implements Pager {
if( $type ) {
$attrs['class'] = "mw-{$type}link";
}
- return $this->getSkin()->link(
+ return Linker::linkKnown(
$this->getTitle(),
$text,
$attrs,
- $query + $this->getDefaultQuery(),
- array( 'noclasses', 'known' )
+ $query + $this->getDefaultQuery()
);
}
/**
+ * Called from getBody(), before getStartBody() is called and
+ * after doQuery() was called. This will be called only if there
+ * are rows in the result set.
+ *
+ * @return void
+ */
+ protected function doBatchLookups() {}
+
+ /**
* Hook into getBody(), allows text to be inserted at the start. This
* will be called even if there are no rows in the result set.
*
* @return String
*/
- function getStartBody() {
+ protected function getStartBody() {
return '';
}
@@ -372,7 +414,7 @@ abstract class IndexPager implements Pager {
*
* @return String
*/
- function getEndBody() {
+ protected function getEndBody() {
return '';
}
@@ -382,34 +424,11 @@ abstract class IndexPager implements Pager {
*
* @return String
*/
- function getEmptyBody() {
+ protected function getEmptyBody() {
return '';
}
/**
- * Title used for self-links. Override this if you want to be able to
- * use a title other than $wgTitle
- *
- * @return Title object
- */
- function getTitle() {
- return $GLOBALS['wgTitle'];
- }
-
- /**
- * Get the current skin. This can be overridden if necessary.
- *
- * @return Skin object
- */
- function getSkin() {
- if ( !isset( $this->mSkin ) ) {
- global $wgUser;
- $this->mSkin = $wgUser->getSkin();
- }
- return $this->mSkin;
- }
-
- /**
* Get an array of query parameters that should be put into self-links.
* By default, all parameters passed in the URL are used, except for a
* short blacklist.
@@ -417,10 +436,8 @@ abstract class IndexPager implements Pager {
* @return Associative array
*/
function getDefaultQuery() {
- global $wgRequest;
-
if ( !isset( $this->mDefaultQuery ) ) {
- $this->mDefaultQuery = $wgRequest->getQueryValues();
+ $this->mDefaultQuery = $this->getRequest()->getQueryValues();
unset( $this->mDefaultQuery['title'] );
unset( $this->mDefaultQuery['dir'] );
unset( $this->mDefaultQuery['offset'] );
@@ -455,7 +472,7 @@ abstract class IndexPager implements Pager {
}
# Don't announce the limit everywhere if it's the default
- $urlLimit = $this->mLimit == $this->mDefaultLimit ? '' : $this->mLimit;
+ $urlLimit = $this->mLimit == $this->mDefaultLimit ? null : $this->mLimit;
if ( $this->mIsFirst ) {
$prev = false;
@@ -502,6 +519,8 @@ abstract class IndexPager implements Pager {
* $linkTexts will be used. Both $linkTexts and $disabledTexts are arrays
* of HTML.
*
+ * @param $linkTexts Array
+ * @param $disabledTexts Array
* @return Array
*/
function getPagingLinks( $linkTexts, $disabledTexts = array() ) {
@@ -524,7 +543,6 @@ abstract class IndexPager implements Pager {
}
function getLimitLinks() {
- global $wgLang;
$links = array();
if ( $this->mIsBackwards ) {
$offset = $this->mPastTheEndIndex;
@@ -533,7 +551,7 @@ abstract class IndexPager implements Pager {
}
foreach ( $this->mLimitsShown as $limit ) {
$links[] = $this->makeLink(
- $wgLang->formatNum( $limit ),
+ $this->getLanguage()->formatNum( $limit ),
array( 'offset' => $offset, 'limit' => $limit ),
'num'
);
@@ -574,10 +592,30 @@ abstract class IndexPager implements Pager {
*
* Needless to say, it's really not a good idea to use a non-unique index
* for this! That won't page right.
+ *
+ * @return string|Array
*/
abstract function getIndexField();
/**
+ * This function should be overridden to return the names of secondary columns
+ * to order by in addition to the column in getIndexField(). These fields will
+ * not be used in the pager offset or in any links for users.
+ *
+ * If getIndexField() returns an array of 'querykey' => 'indexfield' pairs then
+ * this must return a corresponding array of 'querykey' => array( fields...) pairs
+ * in order for a request with &count=querykey to use array( fields...) to sort.
+ *
+ * This is useful for pagers that GROUP BY a unique column (say page_id)
+ * and ORDER BY another (say page_len). Using GROUP BY and ORDER BY both on
+ * page_len,page_id avoids temp tables (given a page_len index). This would
+ * also work if page_id was non-unique but we had a page_len,page_id index.
+ *
+ * @return Array
+ */
+ protected function getExtraSortFields() { return array(); }
+
+ /**
* Return the default sorting direction: false for ascending, true for de-
* scending. You can also have an associative array of ordertype => dir,
* if multiple order types are supported. In this case getIndexField()
@@ -605,30 +643,33 @@ abstract class IndexPager implements Pager {
* @ingroup Pager
*/
abstract class AlphabeticPager extends IndexPager {
+
/**
* Shamelessly stolen bits from ReverseChronologicalPager,
* didn't want to do class magic as may be still revamped
+ *
+ * @return String HTML
*/
function getNavigationBar() {
- global $wgLang;
-
if ( !$this->isNavigationBarShown() ) return '';
if( isset( $this->mNavigationBar ) ) {
return $this->mNavigationBar;
}
+ $lang = $this->getLanguage();
+
$opts = array( 'parsemag', 'escapenoentities' );
$linkTexts = array(
'prev' => wfMsgExt(
'prevn',
$opts,
- $wgLang->formatNum( $this->mLimit )
+ $lang->formatNum( $this->mLimit )
),
'next' => wfMsgExt(
'nextn',
$opts,
- $wgLang->formatNum($this->mLimit )
+ $lang->formatNum($this->mLimit )
),
'first' => wfMsgExt( 'page_first', $opts ),
'last' => wfMsgExt( 'page_last', $opts )
@@ -636,10 +677,10 @@ abstract class AlphabeticPager extends IndexPager {
$pagingLinks = $this->getPagingLinks( $linkTexts );
$limitLinks = $this->getLimitLinks();
- $limits = $wgLang->pipeList( $limitLinks );
+ $limits = $lang->pipeList( $limitLinks );
$this->mNavigationBar =
- "(" . $wgLang->pipeList(
+ "(" . $lang->pipeList(
array( $pagingLinks['first'],
$pagingLinks['last'] )
) . ") " .
@@ -700,13 +741,7 @@ abstract class ReverseChronologicalPager extends IndexPager {
public $mYear;
public $mMonth;
- function __construct() {
- parent::__construct();
- }
-
function getNavigationBar() {
- global $wgLang;
-
if ( !$this->isNavigationBarShown() ) {
return '';
}
@@ -714,7 +749,8 @@ abstract class ReverseChronologicalPager extends IndexPager {
if ( isset( $this->mNavigationBar ) ) {
return $this->mNavigationBar;
}
- $nicenumber = $wgLang->formatNum( $this->mLimit );
+
+ $nicenumber = $this->getLanguage()->formatNum( $this->mLimit );
$linkTexts = array(
'prev' => wfMsgExt(
'pager-newer-n',
@@ -732,7 +768,7 @@ abstract class ReverseChronologicalPager extends IndexPager {
$pagingLinks = $this->getPagingLinks( $linkTexts );
$limitLinks = $this->getLimitLinks();
- $limits = $wgLang->pipeList( $limitLinks );
+ $limits = $this->getLanguage()->pipeList( $limitLinks );
$this->mNavigationBar = "({$pagingLinks['first']}" .
wfMsgExt( 'pipe-separator' , 'escapenoentities' ) .
@@ -746,11 +782,11 @@ abstract class ReverseChronologicalPager extends IndexPager {
}
function getDateCond( $year, $month ) {
- $year = intval($year);
- $month = intval($month);
+ $year = intval( $year );
+ $month = intval( $month );
// Basic validity checks
$this->mYear = $year > 0 ? $year : false;
- $this->mMonth = ($month > 0 && $month < 13) ? $month : false;
+ $this->mMonth = ( $month > 0 && $month < 13 ) ? $month : false;
// Given an optional year and month, we need to generate a timestamp
// to use as "WHERE rev_timestamp <= result"
// Examples: year = 2006 equals < 20070101 (+000000)
@@ -801,15 +837,18 @@ abstract class TablePager extends IndexPager {
var $mSort;
var $mCurrentRow;
- function __construct() {
- global $wgRequest;
- $this->mSort = $wgRequest->getText( 'sort' );
+ function __construct( IContextSource $context = null ) {
+ if ( $context ) {
+ $this->setContext( $context );
+ }
+
+ $this->mSort = $this->getRequest()->getText( 'sort' );
if ( !array_key_exists( $this->mSort, $this->getFieldNames() ) ) {
$this->mSort = $this->getDefaultSort();
}
- if ( $wgRequest->getBool( 'asc' ) ) {
+ if ( $this->getRequest()->getBool( 'asc' ) ) {
$this->mDefaultDirection = false;
- } elseif ( $wgRequest->getBool( 'desc' ) ) {
+ } elseif ( $this->getRequest()->getBool( 'desc' ) ) {
$this->mDefaultDirection = true;
} /* Else leave it at whatever the class default is */
@@ -821,7 +860,7 @@ abstract class TablePager extends IndexPager {
$tableClass = htmlspecialchars( $this->getTableClass() );
$sortClass = htmlspecialchars( $this->getSortHeaderClass() );
- $s = "<table style='border:1;' class=\"$tableClass\"><thead><tr>\n";
+ $s = "<table style='border:1;' class=\"mw-datatable $tableClass\"><thead><tr>\n";
$fields = $this->getFieldNames();
# Make table header
@@ -872,9 +911,13 @@ abstract class TablePager extends IndexPager {
return "<tr><td colspan=\"$colspan\">$msgEmpty</td></tr>\n";
}
+ /**
+ * @param $row Array
+ * @return String HTML
+ */
function formatRow( $row ) {
$this->mCurrentRow = $row; # In case formatValue etc need to know
- $s = Xml::openElement( 'tr', $this->getRowAttrs($row) );
+ $s = Xml::openElement( 'tr', $this->getRowAttrs( $row ) );
$fieldNames = $this->getFieldNames();
foreach ( $fieldNames as $field => $name ) {
$value = isset( $row->$field ) ? $row->$field : null;
@@ -902,7 +945,7 @@ abstract class TablePager extends IndexPager {
* Get attributes to be applied to the given row.
*
* @param $row Object: the database result row
- * @return Associative array
+ * @return Array of <attr> => <value>
*/
function getRowAttrs( $row ) {
$class = $this->getRowClass( $row );
@@ -919,9 +962,9 @@ abstract class TablePager extends IndexPager {
* take this as an excuse to hardcode styles; use classes and
* CSS instead. Row context is available in $this->mCurrentRow
*
- * @param $field The column
- * @param $value The cell contents
- * @return Associative array
+ * @param $field String The column
+ * @param $value String The cell contents
+ * @return Array of attr => value
*/
function getCellAttrs( $field, $value ) {
return array( 'class' => 'TablePager_col_' . $field );
@@ -945,9 +988,10 @@ abstract class TablePager extends IndexPager {
/**
* A navigation bar with images
+ * @return String HTML
*/
function getNavigationBar() {
- global $wgStylePath, $wgLang;
+ global $wgStylePath;
if ( !$this->isNavigationBarShown() ) {
return '';
@@ -972,7 +1016,7 @@ abstract class TablePager extends IndexPager {
'next' => 'arrow_disabled_right_25.png',
'last' => 'arrow_disabled_last_25.png',
);
- if( $wgLang->isRTL() ) {
+ if( $this->getLanguage()->isRTL() ) {
$keys = array_keys( $labels );
$images = array_combine( $keys, array_reverse( $images ) );
$disabledImages = array_combine( $keys, array_reverse( $disabledImages ) );
@@ -1003,8 +1047,6 @@ abstract class TablePager extends IndexPager {
* @return String: HTML fragment
*/
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 ) ) {
@@ -1018,7 +1060,7 @@ abstract class TablePager extends IndexPager {
# will be a string.
if( is_int( $value ) ){
$limit = $value;
- $text = $wgLang->formatNum( $limit );
+ $text = $this->getLanguage()->formatNum( $limit );
} else {
$limit = $key;
$text = $value;
@@ -1034,13 +1076,12 @@ abstract class TablePager extends IndexPager {
* Resubmits all defined elements of the query string, except for a
* blacklist, passed in the $blacklist parameter.
*
+ * @param $blacklist Array parameters from the request query which should not be resubmitted
* @return String: HTML fragment
*/
function getHiddenFields( $blacklist = array() ) {
- global $wgRequest;
-
$blacklist = (array)$blacklist;
- $query = $wgRequest->getQueryValues();
+ $query = $this->getRequest()->getQueryValues();
foreach ( $blacklist as $name ) {
unset( $query[$name] );
}
@@ -1112,6 +1153,8 @@ abstract class TablePager extends IndexPager {
* An array mapping database field names to a textual description of the
* field name, for use in the table header. The description should be plain
* text, it will be HTML-escaped later.
+ *
+ * @return Array
*/
abstract function getFieldNames();
}
diff --git a/includes/PathRouter.php b/includes/PathRouter.php
new file mode 100644
index 00000000..3e298a58
--- /dev/null
+++ b/includes/PathRouter.php
@@ -0,0 +1,351 @@
+<?php
+/**
+ * PathRouter class.
+ * This class can take patterns such as /wiki/$1 and use them to
+ * parse query parameters out of REQUEST_URI paths.
+ *
+ * $router->add( "/wiki/$1" );
+ * - Matches /wiki/Foo style urls and extracts the title
+ * $router->add( array( 'edit' => "/edit/$1" ), array( 'action' => '$key' ) );
+ * - Matches /edit/Foo style urls and sets action=edit
+ * $router->add( '/$2/$1',
+ * array( 'variant' => '$2' ),
+ * array( '$2' => array( 'zh-hant', 'zh-hans' )
+ * );
+ * - Matches /zh-hant/Foo or /zh-hans/Foo
+ * $router->addStrict( "/foo/Bar", array( 'title' => 'Baz' ) );
+ * - Matches /foo/Bar explicitly and uses "Baz" as the title
+ * $router->add( '/help/$1', array( 'title' => 'Help:$1' ) );
+ * - Matches /help/Foo with "Help:Foo" as the title
+ * $router->add( '/$1', array( 'foo' => array( 'value' => 'bar$2' ) );
+ * - Matches /Foo and sets 'foo' to 'bar$2' without $2 being replaced
+ * $router->add( '/$1', array( 'data:foo' => 'bar' ), array( 'callback' => 'functionname' ) );
+ * - Matches /Foo, adds the key 'foo' with the value 'bar' to the data array
+ * and calls functionname( &$matches, $data );
+ *
+ * Path patterns:
+ * - Paths may contain $# patterns such as $1, $2, etc...
+ * - $1 will match 0 or more while the rest will match 1 or more
+ * - Unless you use addStrict "/wiki" and "/wiki/" will be expanded to "/wiki/$1"
+ *
+ * Params:
+ * - In a pattern $1, $2, etc... will be replaced with the relevant contents
+ * - If you used a keyed array as a path pattern, $key will be replaced with
+ * the relevant contents
+ * - The default behavior is equivalent to `array( 'title' => '$1' )`,
+ * if you don't want the title parameter you can explicitly use `array( 'title' => false )`
+ * - You can specify a value that won't have replacements in it
+ * using `'foo' => array( 'value' => 'bar' );`
+ *
+ * Options:
+ * - The option keys $1, $2, etc... can be specified to restrict the possible values
+ * of that variable. A string can be used for a single value, or an array for multiple.
+ * - When the option key 'strict' is set (Using addStrict is simpler than doing this directly)
+ * the path won't have $1 implicitly added to it.
+ * - The option key 'callback' can specify a callback that will be run when a path is matched.
+ * The callback will have the arguments ( &$matches, $data ) and the matches array can
+ * be modified.
+ *
+ * @since 1.19
+ * @author Daniel Friesen
+ */
+class PathRouter {
+
+ /**
+ * Protected helper to do the actual bulk work of adding a single pattern.
+ * This is in a separate method so that add() can handle the difference between
+ * a single string $path and an array() $path that contains multiple path
+ * patterns each with an associated $key to pass on.
+ */
+ protected function doAdd( $path, $params, $options, $key = null ) {
+ // Make sure all paths start with a /
+ if ( $path[0] !== '/' ) {
+ $path = '/' . $path;
+ }
+
+ if ( !isset( $options['strict'] ) || !$options['strict'] ) {
+ // Unless this is a strict path make sure that the path has a $1
+ if ( strpos( $path, '$1' ) === false ) {
+ if ( substr( $path, -1 ) !== '/' ) {
+ $path .= '/';
+ }
+ $path .= '$1';
+ }
+ }
+
+ // If 'title' is not specified and our path pattern contains a $1
+ // Add a default 'title' => '$1' rule to the parameters.
+ if ( !isset( $params['title'] ) && strpos( $path, '$1' ) !== false ) {
+ $params['title'] = '$1';
+ }
+ // If the user explicitly marked 'title' as false then omit it from the matches
+ if ( isset( $params['title'] ) && $params['title'] === false ) {
+ unset( $params['title'] );
+ }
+
+ // Loop over our parameters and convert basic key => string
+ // patterns into fully descriptive array form
+ foreach ( $params as $paramName => $paramData ) {
+ if ( is_string( $paramData ) ) {
+ if ( preg_match( '/\$(\d+|key)/u', $paramData ) ) {
+ $paramArrKey = 'pattern';
+ } else {
+ // If there's no replacement use a value instead
+ // of a pattern for a little more efficiency
+ $paramArrKey = 'value';
+ }
+ $params[$paramName] = array(
+ $paramArrKey => $paramData
+ );
+ }
+ }
+
+ // Loop over our options and convert any single value $# restrictions
+ // into an array so we only have to do in_array tests.
+ foreach ( $options as $optionName => $optionData ) {
+ if ( preg_match( '/^\$\d+$/u', $optionName ) ) {
+ if ( !is_array( $optionData ) ) {
+ $options[$optionName] = array( $optionData );
+ }
+ }
+ }
+
+ $pattern = (object)array(
+ 'path' => $path,
+ 'params' => $params,
+ 'options' => $options,
+ 'key' => $key,
+ );
+ $pattern->weight = self::makeWeight( $pattern );
+ $this->patterns[] = $pattern;
+ }
+
+ /**
+ * Add a new path pattern to the path router
+ *
+ * @param $path The path pattern to add
+ * @param $params The params for this path pattern
+ * @param $options The options for this path pattern
+ */
+ public function add( $path, $params = array(), $options = array() ) {
+ if ( is_array( $path ) ) {
+ foreach ( $path as $key => $onePath ) {
+ $this->doAdd( $onePath, $params, $options, $key );
+ }
+ } else {
+ $this->doAdd( $path, $params, $options );
+ }
+ }
+
+ /**
+ * Add a new path pattern to the path router with the strict option on
+ * @see self::add
+ */
+ public function addStrict( $path, $params = array(), $options = array() ) {
+ $options['strict'] = true;
+ $this->add( $path, $params, $options );
+ }
+
+ /**
+ * Protected helper to re-sort our patterns so that the most specific
+ * (most heavily weighted) patterns are at the start of the array.
+ */
+ protected function sortByWeight() {
+ $weights = array();
+ foreach( $this->patterns as $key => $pattern ) {
+ $weights[$key] = $pattern->weight;
+ }
+ array_multisort( $weights, SORT_DESC, SORT_NUMERIC, $this->patterns );
+ }
+
+ protected static function makeWeight( $pattern ) {
+ # Start with a weight of 0
+ $weight = 0;
+
+ // Explode the path to work with
+ $path = explode( '/', $pattern->path );
+
+ # For each level of the path
+ foreach( $path as $piece ) {
+ if ( preg_match( '/^\$(\d+|key)$/u', $piece ) ) {
+ # For a piece that is only a $1 variable add 1 points of weight
+ $weight += 1;
+ } elseif ( preg_match( '/\$(\d+|key)/u', $piece ) ) {
+ # For a piece that simply contains a $1 variable add 2 points of weight
+ $weight += 2;
+ } else {
+ # For a solid piece add a full 3 points of weight
+ $weight += 3;
+ }
+ }
+
+ foreach ( $pattern->options as $key => $option ) {
+ if ( preg_match( '/^\$\d+$/u', $key ) ) {
+ # Add 0.5 for restrictions to values
+ # This way given two separate "/$2/$1" patterns the
+ # one with a limited set of $2 values will dominate
+ # the one that'll match more loosely
+ $weight += 0.5;
+ }
+ }
+
+ return $weight;
+ }
+
+ /**
+ * Parse a path and return the query matches for the path
+ *
+ * @param $path The path to parse
+ * @return Array The array of matches for the path
+ */
+ public function parse( $path ) {
+ // Make sure our patterns are sorted by weight so the most specific
+ // matches are tested first
+ $this->sortByWeight();
+
+ $matches = null;
+
+ foreach ( $this->patterns as $pattern ) {
+ $matches = self::extractTitle( $path, $pattern );
+ if ( !is_null( $matches ) ) {
+ break;
+ }
+ }
+
+ // We know the difference between null (no matches) and
+ // array() (a match with no data) but our WebRequest caller
+ // expects array() even when we have no matches so return
+ // a array() when we have null
+ return is_null( $matches ) ? array() : $matches;
+ }
+
+ protected static function extractTitle( $path, $pattern ) {
+ // Convert the path pattern into a regexp we can match with
+ $regexp = preg_quote( $pattern->path, '#' );
+ // .* for the $1
+ $regexp = preg_replace( '#\\\\\$1#u', '(?P<par1>.*)', $regexp );
+ // .+ for the rest of the parameter numbers
+ $regexp = preg_replace( '#\\\\\$(\d+)#u', '(?P<par$1>.+?)', $regexp );
+ $regexp = "#^{$regexp}$#";
+
+ $matches = array();
+ $data = array();
+
+ // Try to match the path we were asked to parse with our regexp
+ if ( preg_match( $regexp, $path, $m ) ) {
+ // Ensure that any $# restriction we have set in our {$option}s
+ // matches properly here.
+ foreach ( $pattern->options as $key => $option ) {
+ if ( preg_match( '/^\$\d+$/u', $key ) ) {
+ $n = intval( substr( $key, 1 ) );
+ $value = rawurldecode( $m["par{$n}"] );
+ if ( !in_array( $value, $option ) ) {
+ // If any restriction does not match return null
+ // to signify that this rule did not match.
+ return null;
+ }
+ }
+ }
+
+ // Give our $data array a copy of every $# that was matched
+ foreach ( $m as $matchKey => $matchValue ) {
+ if ( preg_match( '/^par\d+$/u', $matchKey ) ) {
+ $n = intval( substr( $matchKey, 3 ) );
+ $data['$'.$n] = rawurldecode( $matchValue );
+ }
+ }
+ // If present give our $data array a $key as well
+ if ( isset( $pattern->key ) ) {
+ $data['$key'] = $pattern->key;
+ }
+
+ // Go through our parameters for this match and add data to our matches and data arrays
+ foreach ( $pattern->params as $paramName => $paramData ) {
+ $value = null;
+ // Differentiate data: from normal parameters and keep the correct
+ // array key around (ie: foo for data:foo)
+ if ( preg_match( '/^data:/u', $paramName ) ) {
+ $isData = true;
+ $key = substr( $paramName, 5 );
+ } else {
+ $isData = false;
+ $key = $paramName;
+ }
+
+ if ( isset( $paramData['value'] ) ) {
+ // For basic values just set the raw data as the value
+ $value = $paramData['value'];
+ } elseif ( isset( $paramData['pattern'] ) ) {
+ // For patterns we have to make value replacements on the string
+ $value = $paramData['pattern'];
+ $replacer = new PathRouterPatternReplacer;
+ $replacer->params = $m;
+ if ( isset( $pattern->key ) ) {
+ $replacer->key = $pattern->key;
+ }
+ $value = $replacer->replace( $value );
+ if ( $value === false ) {
+ // Pattern required data that wasn't available, abort
+ return null;
+ }
+ }
+
+ // Send things that start with data: to $data, the rest to $matches
+ if ( $isData ) {
+ $data[$key] = $value;
+ } else {
+ $matches[$key] = $value;
+ }
+ }
+
+ // If this match includes a callback, execute it
+ if ( isset( $pattern->options['callback'] ) ) {
+ call_user_func_array( $pattern->options['callback'], array( &$matches, $data ) );
+ }
+ } else {
+ // Our regexp didn't match, return null to signify no match.
+ return null;
+ }
+ // Fall through, everything went ok, return our matches array
+ return $matches;
+ }
+
+}
+
+class PathRouterPatternReplacer {
+
+ public $key, $params, $error;
+
+ /**
+ * Replace keys inside path router patterns with text.
+ * We do this inside of a replacement callback because after replacement we can't tell the
+ * difference between a $1 that was not replaced and a $1 that was part of
+ * the content a $1 was replaced with.
+ */
+ public function replace( $value ) {
+ $this->error = false;
+ $value = preg_replace_callback( '/\$(\d+|key)/u', array( $this, 'callback' ), $value );
+ if ( $this->error ) {
+ return false;
+ }
+ return $value;
+ }
+
+ protected function callback( $m ) {
+ if ( $m[1] == "key" ) {
+ if ( is_null( $this->key ) ) {
+ $this->error = true;
+ return '';
+ }
+ return $this->key;
+ } else {
+ $d = $m[1];
+ if ( !isset( $this->params["par$d"] ) ) {
+ $this->error = true;
+ return '';
+ }
+ return rawurldecode( $this->params["par$d"] );
+ }
+ }
+
+} \ No newline at end of file
diff --git a/includes/PatrolLog.php b/includes/PatrolLog.php
deleted file mode 100644
index 0df48a85..00000000
--- a/includes/PatrolLog.php
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-
-/**
- * Class containing static functions for working with
- * logs of patrol events
- *
- * @author Rob Church <robchur@gmail.com>
- */
-class PatrolLog {
-
- /**
- * Record a log event for a change being patrolled
- *
- * @param $rc Mixed: change identifier or RecentChange object
- * @param $auto Boolean: was this patrol event automatic?
- */
- public static function record( $rc, $auto = false ) {
- if( !( $rc instanceof RecentChange ) ) {
- $rc = RecentChange::newFromId( $rc );
- if( !is_object( $rc ) )
- return false;
- }
- $title = Title::makeTitleSafe( $rc->getAttribute( 'rc_namespace' ), $rc->getAttribute( 'rc_title' ) );
- if( is_object( $title ) ) {
- $params = self::buildParams( $rc, $auto );
- $log = new LogPage( 'patrol', false, $auto ? "skipUDP" : "UDP" ); # False suppresses RC entries
- $log->addEntry( 'patrol', $title, '', $params );
- return true;
- }
- return false;
- }
-
- /**
- * Generate the log action text corresponding to a patrol log item
- *
- * @param $title Title of the page that was patrolled
- * @param $params Array: log parameters (from logging.log_params)
- * @param $lang Language object to use, or false
- * @return String
- */
- public static function makeActionText( $title, $params, $lang ) {
- list( $cur, /* $prev */, $auto ) = $params;
- if( is_object( $lang ) ) {
- # Standard link to the page in question
- $link = Linker::link( $title );
- if( $title->exists() ) {
- # Generate a diff link
- $query = array(
- 'oldid' => $cur,
- 'diff' => 'prev'
- );
-
- $diff = Linker::link(
- $title,
- htmlspecialchars( wfMsg( 'patrol-log-diff', $lang->formatNum( $cur, true ) ) ),
- array(),
- $query,
- array( 'known', 'noclasses' )
- );
- } else {
- # Don't bother with a diff link, it's useless
- $diff = htmlspecialchars( wfMsg( 'patrol-log-diff', $cur ) );
- }
- # Indicate whether or not the patrolling was automatic
- $auto = $auto ? wfMsgHtml( 'patrol-log-auto' ) : '';
- # Put it all together
- return wfMsgHtml( 'patrol-log-line', $diff, $link, $auto );
- } else {
- $text = $title->getPrefixedText();
- return wfMsgForContent( 'patrol-log-line', wfMsgHtml('patrol-log-diff',$cur), "[[$text]]", '' );
- }
- }
-
- /**
- * Prepare log parameters for a patrolled change
- *
- * @param $change RecentChange to represent
- * @param $auto Boolean: whether the patrol event was automatic
- * @return Array
- */
- private static function buildParams( $change, $auto ) {
- return array(
- $change->getAttribute( 'rc_this_oldid' ),
- $change->getAttribute( 'rc_last_oldid' ),
- (int)$auto
- );
- }
-}
diff --git a/includes/PoolCounter.php b/includes/PoolCounter.php
index 182520e1..83ae0abe 100644
--- a/includes/PoolCounter.php
+++ b/includes/PoolCounter.php
@@ -1,11 +1,11 @@
<?php
/**
- * When you have many workers (threads/servers) giving service, and a
+ * 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,
+ * Given enough requests and the item expiring fast (non-cacheable,
* lots of edits...) that single work can end up unfairly using most (all)
* of the cpu of the pool. This is also known as 'Michael Jackson effect'
* since this effect triggered on the english wikipedia on the day Michael
@@ -15,16 +15,16 @@
* The PoolCounter provides semaphore semantics for restricting the number
* of workers that may be concurrently performing such single task.
*
- * By default PoolCounter_Stub is used, which provides no locking. You
+ * By default PoolCounter_Stub is used, which provides no locking. You
* can get a useful one in the PoolCounter extension.
*/
abstract class PoolCounter {
-
+
/* Return codes */
const LOCKED = 1; /* Lock acquired */
const RELEASED = 2; /* Lock released */
const DONE = 3; /* Another worker did the work for you */
-
+
const ERROR = -1; /* Indeterminate error */
const NOT_LOCKED = -2; /* Called release() with no lock held */
const QUEUE_FULL = -3; /* There are already maxqueue workers on this lock */
@@ -33,38 +33,38 @@ abstract class PoolCounter {
/**
* I want to do this task and I need to do it myself.
- *
+ *
* @return Locked/Error
*/
abstract function acquireForMe();
/**
- * I want to do this task, but if anyone else does it
+ * I want to do this task, but if anyone else does it
* instead, it's also fine for me. I will read its cached data.
- *
+ *
* @return Locked/Done/Error
*/
abstract function acquireForAnyone();
/**
* I have successfully finished my task.
- * Lets another one grab the lock, and returns the workers
+ * Lets another one grab the lock, and returns the workers
* waiting on acquireForAnyone()
- *
+ *
* @return Released/NotLocked/Error
*/
abstract function release();
-
+
/**
* $key: All workers with the same key share the lock.
- * $workers: It wouldn't be a good idea to have more than this number of
+ * $workers: It wouldn't be a good idea to have more than this number of
* workers doing the task simultaneously.
- * $maxqueue: If this number of workers are already working/waiting,
+ * $maxqueue: If this number of workers are already working/waiting,
* fail instead of wait.
* $timeout: Maximum time in seconds to wait for the lock.
*/
protected $key, $workers, $maxqueue, $timeout;
-
+
/**
* Create a Pool counter. This should only be called from the PoolWorks.
*
@@ -80,10 +80,10 @@ abstract class PoolCounter {
}
$conf = $wgPoolCounterConf[$type];
$class = $conf['class'];
-
+
return new $class( $conf, $type, $key );
}
-
+
protected function __construct( $conf, $type, $key ) {
$this->key = $key;
$this->workers = $conf['workers'];
@@ -125,7 +125,7 @@ class PoolCounter_Stub extends PoolCounter {
*/
abstract class PoolCounterWork {
protected $cacheable = false; //Does this override getCachedWork() ?
-
+
/**
* Actually perform the work, caching it if needed.
*/
@@ -140,30 +140,34 @@ abstract class PoolCounterWork {
}
/**
- * A work not so good (eg. expired one) but better than an error
+ * A work not so good (eg. expired one) but better than an error
* message.
* @return mixed work result or false
*/
function fallback() {
return false;
}
-
+
/**
* Do something with the error, like showing it to the user.
*/
- function error( $status ) {
+ function error( $status ) {
return false;
}
/**
* Log an error
+ *
+ * @param $status Status
*/
function logError( $status ) {
wfDebugLog( 'poolcounter', $status->getWikiText() );
}
-
+
/**
* Get the result of the work (whatever it is), or false.
+ * @param $skipcache bool
+ * @return bool|mixed
*/
function execute( $skipcache = false ) {
if ( $this->cacheable && !$skipcache ) {
@@ -183,7 +187,7 @@ abstract class PoolCounterWork {
$result = $this->doWork();
$this->poolCounter->release();
return $result;
-
+
case PoolCounter::DONE:
$result = $this->getCachedWork();
if ( $result === false ) {
@@ -193,27 +197,27 @@ abstract class PoolCounterWork {
return $this->execute( true );
}
return $result;
-
+
case PoolCounter::QUEUE_FULL:
case PoolCounter::TIMEOUT:
$result = $this->fallback();
-
+
if ( $result !== false ) {
return $result;
}
/* no break */
-
+
/* These two cases should never be hit... */
case PoolCounter::ERROR:
default:
$errors = array( PoolCounter::QUEUE_FULL => 'pool-queuefull', PoolCounter::TIMEOUT => 'pool-timeout' );
-
- $status = Status::newFatal( isset($errors[$status->value]) ? $errors[$status->value] : 'pool-errorunknown' );
+
+ $status = Status::newFatal( isset( $errors[$status->value] ) ? $errors[$status->value] : 'pool-errorunknown' );
$this->logError( $status );
return $this->error( $status );
}
}
-
+
function __construct( $type, $key ) {
$this->poolCounter = PoolCounter::factory( $type, $key );
}
diff --git a/includes/Preferences.php b/includes/Preferences.php
index 91b3326b..ea1efa18 100644
--- a/includes/Preferences.php
+++ b/includes/Preferences.php
@@ -39,25 +39,26 @@ class Preferences {
/**
* @throws MWException
* @param $user User
+ * @param $context IContextSource
* @return array|null
*/
- static function getPreferences( $user ) {
+ static function getPreferences( $user, IContextSource $context ) {
if ( self::$defaultPreferences ) {
return self::$defaultPreferences;
}
$defaultPreferences = array();
- self::profilePreferences( $user, $defaultPreferences );
- self::skinPreferences( $user, $defaultPreferences );
- self::filesPreferences( $user, $defaultPreferences );
- self::datetimePreferences( $user, $defaultPreferences );
- self::renderingPreferences( $user, $defaultPreferences );
- self::editingPreferences( $user, $defaultPreferences );
- self::rcPreferences( $user, $defaultPreferences );
- self::watchlistPreferences( $user, $defaultPreferences );
- self::searchPreferences( $user, $defaultPreferences );
- self::miscPreferences( $user, $defaultPreferences );
+ self::profilePreferences( $user, $context, $defaultPreferences );
+ self::skinPreferences( $user, $context, $defaultPreferences );
+ self::filesPreferences( $user, $context, $defaultPreferences );
+ self::datetimePreferences( $user, $context, $defaultPreferences );
+ self::renderingPreferences( $user, $context, $defaultPreferences );
+ self::editingPreferences( $user, $context, $defaultPreferences );
+ self::rcPreferences( $user, $context, $defaultPreferences );
+ self::watchlistPreferences( $user, $context, $defaultPreferences );
+ self::searchPreferences( $user, $context, $defaultPreferences );
+ self::miscPreferences( $user, $context, $defaultPreferences );
wfRunHooks( 'GetPreferences', array( $user, &$defaultPreferences ) );
@@ -127,11 +128,16 @@ class Preferences {
/**
* @param $user User
+ * @param $context IContextSource
* @param $defaultPreferences
* @return void
*/
- static function profilePreferences( $user, &$defaultPreferences ) {
- global $wgLang, $wgUser;
+ static function profilePreferences( $user, IContextSource $context, &$defaultPreferences ) {
+ global $wgAuth, $wgContLang, $wgParser, $wgCookieExpiration, $wgLanguageCode,
+ $wgDisableTitleConversion, $wgDisableLangConversion, $wgMaxSigChars,
+ $wgEnableEmail, $wgEmailConfirmToEdit, $wgEnableUserEmail, $wgEmailAuthentication,
+ $wgEnotifWatchlist, $wgEnotifUserTalk, $wgEnotifRevealEditorAddress;
+
## User info #####################################
// Information panel
$defaultPreferences['username'] = array(
@@ -159,23 +165,22 @@ class Preferences {
$groupName = User::getGroupName( $ueg );
$userGroups[] = User::makeGroupLinkHTML( $ueg, $groupName );
- $memberName = User::getGroupMember( $ueg );
+ $memberName = User::getGroupMember( $ueg, $user->getName() );
$userMembers[] = User::makeGroupLinkHTML( $ueg, $memberName );
}
asort( $userGroups );
asort( $userMembers );
+ $lang = $context->getLanguage();
+
$defaultPreferences['usergroups'] = array(
'type' => 'info',
- 'label' => wfMsgExt(
- 'prefs-memberingroups', 'parseinline',
- $wgLang->formatNum( count( $userGroups ) )
- ),
- 'default' => wfMsgExt(
- 'prefs-memberingroups-type', array(),
- $wgLang->commaList( $userGroups ),
- $wgLang->commaList( $userMembers )
- ),
+ 'label' => $context->msg( 'prefs-memberingroups' )->numParams(
+ count( $userGroups ) )->parse(),
+ 'default' => $context->msg( 'prefs-memberingroups-type',
+ $lang->commaList( $userGroups ),
+ $lang->commaList( $userMembers )
+ )->plain(),
'raw' => true,
'section' => 'personal/info',
);
@@ -183,26 +188,27 @@ class Preferences {
$defaultPreferences['editcount'] = array(
'type' => 'info',
'label-message' => 'prefs-edits',
- 'default' => $wgLang->formatNum( $user->getEditCount() ),
+ 'default' => $lang->formatNum( $user->getEditCount() ),
'section' => 'personal/info',
);
if ( $user->getRegistration() ) {
+ $displayUser = $context->getUser();
+ $userRegistration = $user->getRegistration();
$defaultPreferences['registrationdate'] = array(
'type' => 'info',
'label-message' => 'prefs-registration',
- 'default' => wfMsgExt(
- 'prefs-registration-date-time', 'parsemag',
- $wgLang->timeanddate( $user->getRegistration(), true ),
- $wgLang->date( $user->getRegistration(), true ),
- $wgLang->time( $user->getRegistration(), true )
- ),
+ 'default' => $context->msg(
+ 'prefs-registration-date-time',
+ $lang->userTimeAndDate( $userRegistration, $displayUser ),
+ $lang->userDate( $userRegistration, $displayUser ),
+ $lang->userTime( $userRegistration, $displayUser )
+ )->parse(),
'section' => 'personal/info',
);
}
// Actually changeable stuff
- global $wgAuth;
$defaultPreferences['realname'] = array(
'type' => $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
'default' => $user->getRealName(),
@@ -215,17 +221,17 @@ class Preferences {
'type' => 'select',
'section' => 'personal/info',
'options' => array(
- wfMsg( 'gender-male' ) => 'male',
- wfMsg( 'gender-female' ) => 'female',
- wfMsg( 'gender-unknown' ) => 'unknown',
+ $context->msg( 'gender-male' )->text() => 'male',
+ $context->msg( 'gender-female' )->text() => 'female',
+ $context->msg( 'gender-unknown' )->text() => 'unknown',
),
'label-message' => 'yourgender',
'help-message' => 'prefs-help-gender',
);
if ( $wgAuth->allowPasswordChange() ) {
- $link = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'ChangePassword' ),
- wfMsgHtml( 'prefs-resetpass' ), array(),
+ $link = Linker::link( SpecialPage::getTitleFor( 'ChangePassword' ),
+ $context->msg( 'prefs-resetpass' )->escaped(), array(),
array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' ) ) );
$defaultPreferences['password'] = array(
@@ -236,21 +242,16 @@ class Preferences {
'section' => 'personal/info',
);
}
- global $wgCookieExpiration;
if ( $wgCookieExpiration > 0 ) {
$defaultPreferences['rememberpassword'] = array(
'type' => 'toggle',
- 'label' => wfMsgExt(
- 'tog-rememberpassword',
- array( 'parsemag' ),
- $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )
- ),
+ 'label' => $context->msg( 'tog-rememberpassword' )->numParams(
+ ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )->text(),
'section' => 'personal/info',
);
}
// Language
- global $wgLanguageCode;
$languages = Language::getLanguageNames( false );
if ( !array_key_exists( $wgLanguageCode, $languages ) ) {
$languages[$wgLanguageCode] = $wgLanguageCode;
@@ -269,20 +270,14 @@ class Preferences {
'label-message' => 'yourlanguage',
);
- global $wgContLang, $wgDisableLangConversion;
- global $wgDisableTitleConversion;
/* see if there are multiple language variants to choose from*/
$variantArray = array();
if ( !$wgDisableLangConversion ) {
$variants = $wgContLang->getVariants();
- $languages = Language::getLanguageNames( true );
foreach ( $variants as $v ) {
$v = str_replace( '_', '-', strtolower( $v ) );
- if ( array_key_exists( $v, $languages ) ) {
- // If it doesn't have a name, we'll pretend it doesn't exist
- $variantArray[$v] = $languages[$v];
- }
+ $variantArray[$v] = $wgContLang->getVariantname( $v, false );
}
$options = array();
@@ -297,24 +292,23 @@ class Preferences {
'type' => 'select',
'options' => $options,
'section' => 'personal/i18n',
+ 'help-message' => 'prefs-help-variant',
);
}
}
if ( count( $variantArray ) > 1 && !$wgDisableLangConversion && !$wgDisableTitleConversion ) {
$defaultPreferences['noconvertlink'] =
- array(
+ array(
'type' => 'toggle',
'section' => 'personal/i18n',
'label-message' => 'tog-noconvertlink',
);
}
- global $wgMaxSigChars, $wgOut, $wgParser;
-
// show a preview of the old signature first
- $oldsigWikiText = $wgParser->preSaveTransform( "~~~", new Title, $user, new ParserOptions );
- $oldsigHTML = $wgOut->parseInline( $oldsigWikiText );
+ $oldsigWikiText = $wgParser->preSaveTransform( "~~~", $context->getTitle(), $user, ParserOptions::newFromContext( $context ) );
+ $oldsigHTML = $context->getOutput()->parseInline( $oldsigWikiText, true, true );
$defaultPreferences['oldsig'] = array(
'type' => 'info',
'raw' => true,
@@ -339,11 +333,7 @@ class Preferences {
## Email stuff
- global $wgEnableEmail;
if ( $wgEnableEmail ) {
- global $wgEmailConfirmToEdit;
- global $wgEnableUserEmail;
-
$helpMessages[] = $wgEmailConfirmToEdit
? 'prefs-help-email-required'
: 'prefs-help-email' ;
@@ -353,17 +343,26 @@ class Preferences {
$helpMessages[] = 'prefs-help-email-others';
}
+ $link = Linker::link(
+ SpecialPage::getTitleFor( 'ChangeEmail' ),
+ $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->escaped(),
+ array(),
+ array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' ) ) );
+
+ $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : '';
+ if ( $wgAuth->allowPropChange( 'emailaddress' ) ) {
+ $emailAddress .= $emailAddress == '' ? $link : " ($link)";
+ }
+
$defaultPreferences['emailaddress'] = array(
- 'type' => $wgAuth->allowPropChange( 'emailaddress' ) ? 'email' : 'info',
- 'default' => $user->getEmail(),
- 'section' => 'personal/email',
+ 'type' => 'info',
+ 'raw' => true,
+ 'default' => $emailAddress,
'label-message' => 'youremail',
+ 'section' => 'personal/email',
'help-messages' => $helpMessages,
- 'validation-callback' => array( 'Preferences', 'validateEmail' ),
);
- global $wgEmailAuthentication;
-
$disableEmailPrefs = false;
if ( $wgEmailAuthentication ) {
@@ -372,29 +371,25 @@ class Preferences {
// date and time are separate parameters to facilitate localisation.
// $time is kept for backward compat reasons.
// 'emailauthenticated' is also used in SpecialConfirmemail.php
- $time = $wgLang->timeAndDate( $user->getEmailAuthenticationTimestamp(), true );
- $d = $wgLang->date( $user->getEmailAuthenticationTimestamp(), true );
- $t = $wgLang->time( $user->getEmailAuthenticationTimestamp(), true );
- $emailauthenticated = wfMsgExt(
- 'emailauthenticated', 'parseinline',
- array( $time, $d, $t )
- ) . '<br />';
+ $displayUser = $context->getUser();
+ $emailTimestamp = $user->getEmailAuthenticationTimestamp();
+ $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser );
+ $d = $lang->userDate( $emailTimestamp, $displayUser );
+ $t = $lang->userTime( $emailTimestamp, $displayUser );
+ $emailauthenticated = $context->msg( 'emailauthenticated',
+ $time, $d, $t )->parse() . '<br />';
$disableEmailPrefs = false;
} else {
$disableEmailPrefs = true;
- $skin = $wgUser->getSkin();
- $emailauthenticated = wfMsgExt( 'emailnotauthenticated', 'parseinline' ) . '<br />' .
- $skin->link(
+ $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' .
+ Linker::linkKnown(
SpecialPage::getTitleFor( 'Confirmemail' ),
- wfMsg( 'emailconfirmlink' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ $context->msg( 'emailconfirmlink' )->escaped()
) . '<br />';
}
} else {
$disableEmailPrefs = true;
- $emailauthenticated = wfMsgHtml( 'noemailprefs' );
+ $emailauthenticated = $context->msg( 'noemailprefs' )->escaped();
}
$defaultPreferences['emailauthentication'] = array(
@@ -423,7 +418,6 @@ class Preferences {
);
}
- global $wgEnotifWatchlist;
if ( $wgEnotifWatchlist ) {
$defaultPreferences['enotifwatchlistpages'] = array(
'type' => 'toggle',
@@ -432,7 +426,6 @@ class Preferences {
'disabled' => $disableEmailPrefs,
);
}
- global $wgEnotifUserTalk;
if ( $wgEnotifUserTalk ) {
$defaultPreferences['enotifusertalkpages'] = array(
'type' => 'toggle',
@@ -449,7 +442,6 @@ class Preferences {
'disabled' => $disableEmailPrefs,
);
- global $wgEnotifRevealEditorAddress;
if ( $wgEnotifRevealEditorAddress ) {
$defaultPreferences['enotifrevealaddr'] = array(
'type' => 'toggle',
@@ -464,16 +456,17 @@ class Preferences {
/**
* @param $user User
+ * @param $context IContextSource
* @param $defaultPreferences
* @return void
*/
- static function skinPreferences( $user, &$defaultPreferences ) {
+ static function skinPreferences( $user, IContextSource $context, &$defaultPreferences ) {
## Skin #####################################
- global $wgLang, $wgAllowUserCss, $wgAllowUserJs;
+ global $wgAllowUserCss, $wgAllowUserJs;
$defaultPreferences['skin'] = array(
'type' => 'radio',
- 'options' => self::generateSkinOptions( $user ),
+ 'options' => self::generateSkinOptions( $user, $context ),
'label' => '&#160;',
'section' => 'rendering/skin',
);
@@ -482,23 +475,22 @@ class Preferences {
# This code is basically copied from generateSkinOptions(). It'd
# be nice to somehow merge this back in there to avoid redundancy.
if ( $wgAllowUserCss || $wgAllowUserJs ) {
- $sk = $user->getSkin();
$linkTools = array();
if ( $wgAllowUserCss ) {
$cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/common.css' );
- $linkTools[] = $sk->link( $cssPage, wfMsgHtml( 'prefs-custom-css' ) );
+ $linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() );
}
if ( $wgAllowUserJs ) {
$jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/common.js' );
- $linkTools[] = $sk->link( $jsPage, wfMsgHtml( 'prefs-custom-js' ) );
+ $linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
}
$defaultPreferences['commoncssjs'] = array(
'type' => 'info',
'raw' => true,
- 'default' => $wgLang->pipeList( $linkTools ),
+ 'default' => $context->getLanguage()->pipeList( $linkTools ),
'label-message' => 'prefs-common-css-js',
'section' => 'rendering/skin',
);
@@ -506,7 +498,7 @@ class Preferences {
$selectedSkin = $user->getOption( 'skin' );
if ( in_array( $selectedSkin, array( 'cologneblue', 'standard' ) ) ) {
- $settings = array_flip( $wgLang->getQuickbarSettings() );
+ $settings = array_flip( $context->getLanguage()->getQuickbarSettings() );
$defaultPreferences['quickbar'] = array(
'type' => 'radio',
@@ -519,19 +511,20 @@ class Preferences {
/**
* @param $user User
+ * @param $context IContextSource
* @param $defaultPreferences Array
*/
- static function filesPreferences( $user, &$defaultPreferences ) {
+ static function filesPreferences( $user, IContextSource $context, &$defaultPreferences ) {
## Files #####################################
$defaultPreferences['imagesize'] = array(
'type' => 'select',
- 'options' => self::getImageSizes(),
+ 'options' => self::getImageSizes( $context ),
'label-message' => 'imagemaxsize',
'section' => 'rendering/files',
);
$defaultPreferences['thumbsize'] = array(
'type' => 'select',
- 'options' => self::getThumbSizes(),
+ 'options' => self::getThumbSizes( $context ),
'label-message' => 'thumbsize',
'section' => 'rendering/files',
);
@@ -539,14 +532,13 @@ class Preferences {
/**
* @param $user User
+ * @param $context IContextSource
* @param $defaultPreferences
* @return void
*/
- static function datetimePreferences( $user, &$defaultPreferences ) {
- global $wgLang;
-
+ static function datetimePreferences( $user, IContextSource $context, &$defaultPreferences ) {
## Date and time #####################################
- $dateOptions = self::getDateOptions();
+ $dateOptions = self::getDateOptions( $context );
if ( $dateOptions ) {
$defaultPreferences['date'] = array(
'type' => 'radio',
@@ -558,9 +550,10 @@ class Preferences {
// Info
$now = wfTimestampNow();
+ $lang = $context->getLanguage();
$nowlocal = Xml::element( 'span', array( 'id' => 'wpLocalTime' ),
- $wgLang->time( $now, true ) );
- $nowserver = $wgLang->time( $now, false ) .
+ $lang->time( $now, true ) );
+ $nowserver = $lang->time( $now, false ) .
Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) );
$defaultPreferences['nowserver'] = array(
@@ -581,18 +574,29 @@ class Preferences {
// Grab existing pref.
$tzOffset = $user->getOption( 'timecorrection' );
- $tz = explode( '|', $tzOffset, 2 );
+ $tz = explode( '|', $tzOffset, 3 );
+
+ $tzOptions = self::getTimezoneOptions( $context );
$tzSetting = $tzOffset;
if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
$minDiff = $tz[1];
$tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 );
+ } elseif ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' &&
+ !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) ) )
+ {
+ # Timezone offset can vary with DST
+ $userTZ = timezone_open( $tz[2] );
+ if ( $userTZ !== false ) {
+ $minDiff = floor( timezone_offset_get( $userTZ, date_create( 'now' ) ) / 60 );
+ $tzSetting = "ZoneInfo|$minDiff|{$tz[2]}";
+ }
}
$defaultPreferences['timecorrection'] = array(
'class' => 'HTMLSelectOrOtherField',
'label-message' => 'timezonelegend',
- 'options' => self::getTimezoneOptions(),
+ 'options' => $tzOptions,
'default' => $tzSetting,
'size' => 20,
'section' => 'datetime/timeoffset',
@@ -601,18 +605,19 @@ class Preferences {
/**
* @param $user User
+ * @param $context IContextSource
* @param $defaultPreferences Array
*/
- static function renderingPreferences( $user, &$defaultPreferences ) {
+ static function renderingPreferences( $user, IContextSource $context, &$defaultPreferences ) {
## Page Rendering ##############################
global $wgAllowUserCssPrefs;
if ( $wgAllowUserCssPrefs ) {
$defaultPreferences['underline'] = array(
'type' => 'select',
'options' => array(
- wfMsg( 'underline-never' ) => 0,
- wfMsg( 'underline-always' ) => 1,
- wfMsg( 'underline-default' ) => 2,
+ $context->msg( 'underline-never' )->text() => 0,
+ $context->msg( 'underline-always' )->text() => 1,
+ $context->msg( 'underline-default' )->text() => 2,
),
'label-message' => 'tog-underline',
'section' => 'rendering/advancedrendering',
@@ -620,9 +625,9 @@ class Preferences {
}
$stubThresholdValues = array( 50, 100, 500, 1000, 2000, 5000, 10000 );
- $stubThresholdOptions = array( wfMsg( 'stub-threshold-disabled' ) => 0 );
+ $stubThresholdOptions = array( $context->msg( 'stub-threshold-disabled' )->text() => 0 );
foreach ( $stubThresholdValues as $value ) {
- $stubThresholdOptions[wfMsg( 'size-bytes', $value )] = $value;
+ $stubThresholdOptions[$context->msg( 'size-bytes', $value )->text()] = $value;
}
$defaultPreferences['stubthreshold'] = array(
@@ -630,14 +635,14 @@ class Preferences {
'section' => 'rendering/advancedrendering',
'options' => $stubThresholdOptions,
'size' => 20,
- 'label' => wfMsg( 'stub-threshold' ), // Raw HTML message. Yay?
+ 'label' => $context->msg( 'stub-threshold' )->text(), // Raw HTML message. Yay?
);
if ( $wgAllowUserCssPrefs ) {
$defaultPreferences['highlightbroken'] = array(
'type' => 'toggle',
'section' => 'rendering/advancedrendering',
- 'label' => wfMsg( 'tog-highlightbroken' ), // Raw HTML
+ 'label' => $context->msg( 'tog-highlightbroken' )->text(), // Raw HTML
);
$defaultPreferences['showtoc'] = array(
'type' => 'toggle',
@@ -678,9 +683,10 @@ class Preferences {
/**
* @param $user User
+ * @param $context IContextSource
* @param $defaultPreferences Array
*/
- static function editingPreferences( $user, &$defaultPreferences ) {
+ static function editingPreferences( $user, IContextSource $context, &$defaultPreferences ) {
global $wgUseExternalEditor, $wgAllowUserCssPrefs;
## Editing #####################################
@@ -705,10 +711,10 @@ class Preferences {
'section' => 'editing/advancedediting',
'label-message' => 'editfont-style',
'options' => array(
- wfMsg( 'editfont-default' ) => 'default',
- wfMsg( 'editfont-monospace' ) => 'monospace',
- wfMsg( 'editfont-sansserif' ) => 'sans-serif',
- wfMsg( 'editfont-serif' ) => 'serif',
+ $context->msg( 'editfont-default' )->text() => 'default',
+ $context->msg( 'editfont-monospace' )->text() => 'monospace',
+ $context->msg( 'editfont-sansserif' )->text() => 'sans-serif',
+ $context->msg( 'editfont-serif' )->text() => 'serif',
)
);
}
@@ -783,10 +789,11 @@ class Preferences {
/**
* @param $user User
+ * @param $context IContextSource
* @param $defaultPreferences Array
*/
- static function rcPreferences( $user, &$defaultPreferences ) {
- global $wgRCMaxAge, $wgLang;
+ static function rcPreferences( $user, IContextSource $context, &$defaultPreferences ) {
+ global $wgRCMaxAge, $wgRCShowWatchingUsers;
## RecentChanges #####################################
$defaultPreferences['rcdays'] = array(
@@ -795,11 +802,8 @@ class Preferences {
'section' => 'rc/displayrc',
'min' => 1,
'max' => ceil( $wgRCMaxAge / ( 3600 * 24 ) ),
- 'help' => wfMsgExt(
- 'recentchangesdays-max',
- array( 'parsemag' ),
- $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600 * 24 ) ) )
- )
+ 'help' => $context->msg( 'recentchangesdays-max' )->numParams(
+ ceil( $wgRCMaxAge / ( 3600 * 24 ) ) )->text()
);
$defaultPreferences['rclimit'] = array(
'type' => 'int',
@@ -831,7 +835,6 @@ class Preferences {
);
}
- global $wgRCShowWatchingUsers;
if ( $wgRCShowWatchingUsers ) {
$defaultPreferences['shownumberswatching'] = array(
'type' => 'toggle',
@@ -843,18 +846,22 @@ class Preferences {
/**
* @param $user User
+ * @param $context IContextSource
* @param $defaultPreferences
*/
- static function watchlistPreferences( $user, &$defaultPreferences ) {
- global $wgUseRCPatrol, $wgEnableAPI;
+ static function watchlistPreferences( $user, IContextSource $context, &$defaultPreferences ) {
+ global $wgUseRCPatrol, $wgEnableAPI, $wgRCMaxAge;
+ $watchlistdaysMax = ceil( $wgRCMaxAge / ( 3600 * 24 ) );
+
## Watchlist #####################################
$defaultPreferences['watchlistdays'] = array(
'type' => 'float',
'min' => 0,
- 'max' => 7,
+ 'max' => $watchlistdaysMax,
'section' => 'watchlist/displaywatchlist',
- 'help' => wfMsgHtml( 'prefs-watchlist-days-max' ),
+ 'help' => $context->msg( 'prefs-watchlist-days-max' )->numParams(
+ $watchlistdaysMax )->text(),
'label-message' => 'prefs-watchlist-days',
);
$defaultPreferences['wllimit'] = array(
@@ -862,7 +869,7 @@ class Preferences {
'min' => 0,
'max' => 1000,
'label-message' => 'prefs-watchlist-edits',
- 'help' => wfMsgHtml( 'prefs-watchlist-edits-max' ),
+ 'help' => $context->msg( 'prefs-watchlist-edits-max' )->escaped(),
'section' => 'watchlist/displaywatchlist',
);
$defaultPreferences['extendwatchlist'] = array(
@@ -912,7 +919,7 @@ class Preferences {
'type' => 'text',
'section' => 'watchlist/advancedwatchlist',
'label-message' => 'prefs-watchlist-token',
- 'help' => wfMsgHtml( 'prefs-help-watchlist-token', $hash )
+ 'help' => $context->msg( 'prefs-help-watchlist-token', $hash )->escaped()
);
}
@@ -940,10 +947,11 @@ class Preferences {
/**
* @param $user User
+ * @param $context IContextSource
* @param $defaultPreferences Array
*/
- static function searchPreferences( $user, &$defaultPreferences ) {
- global $wgContLang;
+ static function searchPreferences( $user, IContextSource $context, &$defaultPreferences ) {
+ global $wgContLang, $wgEnableMWSuggest, $wgVectorUseSimpleSearch;
## Search #####################################
$defaultPreferences['searchlimit'] = array(
@@ -953,7 +961,6 @@ class Preferences {
'min' => 0,
);
- global $wgEnableMWSuggest;
if ( $wgEnableMWSuggest ) {
$defaultPreferences['disablesuggest'] = array(
'type' => 'toggle',
@@ -962,7 +969,6 @@ class Preferences {
);
}
- global $wgVectorUseSimpleSearch;
if ( $wgVectorUseSimpleSearch ) {
$defaultPreferences['vector-simplesearch'] = array(
'type' => 'toggle',
@@ -987,7 +993,7 @@ class Preferences {
$displayNs = str_replace( '_', ' ', $name );
if ( !$displayNs ) {
- $displayNs = wfMsg( 'blanknamespace' );
+ $displayNs = $context->msg( 'blanknamespace' )->text();
}
$displayNs = htmlspecialchars( $displayNs );
@@ -1005,9 +1011,12 @@ class Preferences {
/**
* @param $user User
+ * @param $context IContextSource
* @param $defaultPreferences Array
*/
- static function miscPreferences( $user, &$defaultPreferences ) {
+ static function miscPreferences( $user, IContextSource $context, &$defaultPreferences ) {
+ global $wgContLang;
+
## Misc #####################################
$defaultPreferences['diffonly'] = array(
'type' => 'toggle',
@@ -1021,8 +1030,6 @@ class Preferences {
);
// Stuff from Language::getExtraUserToggles()
- global $wgContLang;
-
$toggles = $wgContLang->getExtraUserToggles();
foreach ( $toggles as $toggle ) {
@@ -1036,14 +1043,15 @@ class Preferences {
/**
* @param $user User The User object
+ * @param $context IContextSource
* @return Array: text/links to display as key; $skinkey as value
*/
- static function generateSkinOptions( $user ) {
- global $wgDefaultSkin, $wgLang, $wgAllowUserCss, $wgAllowUserJs;
+ static function generateSkinOptions( $user, IContextSource $context ) {
+ global $wgDefaultSkin, $wgAllowUserCss, $wgAllowUserJs;
$ret = array();
$mptitle = Title::newMainPage();
- $previewtext = wfMsgHtml( 'skin-preview' );
+ $previewtext = $context->msg( 'skin-preview' )->text();
# Only show members of Skin::getSkinNames() rather than
# $skinNames (skins is all skin names from Language.php)
@@ -1052,20 +1060,19 @@ 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 ) {
- $msg = wfMessage( "skinname-{$skinkey}" );
+ $msg = $context->msg( "skinname-{$skinkey}" );
if ( $msg->exists() ) {
$skinname = htmlspecialchars( $msg->text() );
}
}
asort( $validSkinNames );
- $sk = $user->getSkin();
foreach ( $validSkinNames as $skinkey => $sn ) {
$linkTools = array();
# Mark the default skin
if ( $skinkey == $wgDefaultSkin ) {
- $linkTools[] = wfMsgHtml( 'default' );
+ $linkTools[] = $context->msg( 'default' )->escaped();
}
# Create preview link
@@ -1075,15 +1082,15 @@ class Preferences {
# Create links to user CSS/JS pages
if ( $wgAllowUserCss ) {
$cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
- $linkTools[] = $sk->link( $cssPage, wfMsgHtml( 'prefs-custom-css' ) );
+ $linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() );
}
if ( $wgAllowUserJs ) {
$jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
- $linkTools[] = $sk->link( $jsPage, wfMsgHtml( 'prefs-custom-js' ) );
+ $linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() );
}
- $display = $sn . ' ' . wfMsg( 'parentheses', $wgLang->pipeList( $linkTools ) );
+ $display = $sn . ' ' . $context->msg( 'parentheses', $context->getLanguage()->pipeList( $linkTools ) )->text();
$ret[$display] = $skinkey;
}
@@ -1091,11 +1098,12 @@ class Preferences {
}
/**
+ * @param $context IContextSource
* @return array
*/
- static function getDateOptions() {
- global $wgLang;
- $dateopts = $wgLang->getDatePreferences();
+ static function getDateOptions( IContextSource $context ) {
+ $lang = $context->getLanguage();
+ $dateopts = $lang->getDatePreferences();
$ret = array();
@@ -1114,9 +1122,9 @@ class Preferences {
$epoch = wfTimestampNow();
foreach ( $dateopts as $key ) {
if ( $key == 'default' ) {
- $formatted = wfMsgHtml( 'datedefault' );
+ $formatted = $context->msg( 'datedefault' )->escaped();
} else {
- $formatted = htmlspecialchars( $wgLang->timeanddate( $epoch, false, $key ) );
+ $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) );
}
$ret[$formatted] = $key;
}
@@ -1125,15 +1133,17 @@ class Preferences {
}
/**
+ * @param $context IContextSource
* @return array
*/
- static function getImageSizes() {
+ static function getImageSizes( IContextSource $context ) {
global $wgImageLimits;
$ret = array();
+ $pixels = $context->msg( 'unit-pixel' )->text();
foreach ( $wgImageLimits as $index => $limits ) {
- $display = "{$limits[0]}×{$limits[1]}" . wfMsg( 'unit-pixel' );
+ $display = "{$limits[0]}×{$limits[1]}" . $pixels;
$ret[$display] = $index;
}
@@ -1141,15 +1151,17 @@ class Preferences {
}
/**
+ * @param $context IContextSource
* @return array
*/
- static function getThumbSizes() {
+ static function getThumbSizes( IContextSource $context ) {
global $wgThumbLimits;
$ret = array();
+ $pixels = $context->msg( 'unit-pixel' )->text();
foreach ( $wgThumbLimits as $index => $size ) {
- $display = $size . wfMsg( 'unit-pixel' );
+ $display = $size . $pixels;
$ret[$display] = $index;
}
@@ -1157,22 +1169,20 @@ class Preferences {
}
/**
- * @param $signature
- * @param $alldata
+ * @param $signature string
+ * @param $alldata array
+ * @param $form HTMLForm
* @return bool|string
*/
- static function validateSignature( $signature, $alldata ) {
- global $wgParser, $wgMaxSigChars, $wgLang;
+ static function validateSignature( $signature, $alldata, $form ) {
+ global $wgParser, $wgMaxSigChars;
if ( mb_strlen( $signature ) > $wgMaxSigChars ) {
return Xml::element( 'span', array( 'class' => 'error' ),
- wfMsgExt( 'badsiglength', 'parsemag',
- $wgLang->formatNum( $wgMaxSigChars )
- )
- );
+ $form->msg( 'badsiglength' )->numParams( $wgMaxSigChars )->text() );
} elseif ( isset( $alldata['fancysig'] ) &&
$alldata['fancysig'] &&
false === $wgParser->validateSig( $signature ) ) {
- return Xml::element( 'span', array( 'class' => 'error' ), wfMsg( 'badsig' ) );
+ return Xml::element( 'span', array( 'class' => 'error' ), $form->msg( 'badsig' )->text() );
} else {
return true;
}
@@ -1181,51 +1191,45 @@ class Preferences {
/**
* @param $signature string
* @param $alldata array
+ * @param $form HTMLForm
* @return string
*/
- static function cleanSignature( $signature, $alldata ) {
- global $wgParser;
+ static function cleanSignature( $signature, $alldata, $form ) {
if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) {
+ global $wgParser;
$signature = $wgParser->cleanSig( $signature );
} else {
// When no fancy sig used, make sure ~{3,5} get removed.
- $signature = $wgParser->cleanSigInSig( $signature );
+ $signature = Parser::cleanSigInSig( $signature );
}
return $signature;
}
/**
- * @param $email
- * @param $alldata
- * @return bool|String
- */
- static function validateEmail( $email, $alldata ) {
- if ( $email && !Sanitizer::validateEmail( $email ) ) {
- return wfMsgExt( 'invalidemailaddress', 'parseinline' );
- }
-
- global $wgEmailConfirmToEdit;
- if ( $wgEmailConfirmToEdit && !$email ) {
- return wfMsgExt( 'noemailtitle', 'parseinline' );
- }
- return true;
- }
-
- /**
* @param $user User
+ * @param $context IContextSource
* @param $formClass string
+ * @param $remove Array: array of items to remove
* @return HtmlForm
*/
- static function getFormObject( $user, $formClass = 'PreferencesForm' ) {
- $formDescriptor = Preferences::getPreferences( $user );
- $htmlForm = new $formClass( $formDescriptor, 'prefs' );
+ static function getFormObject( $user, IContextSource $context, $formClass = 'PreferencesForm', array $remove = array() ) {
+ $formDescriptor = Preferences::getPreferences( $user, $context );
+ if ( count( $remove ) ) {
+ $removeKeys = array_flip( $remove );
+ $formDescriptor = array_diff_key( $formDescriptor, $removeKeys );
+ }
+
+ /**
+ * @var $htmlForm PreferencesForm
+ */
+ $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' );
+ $htmlForm->setModifiedUser( $user );
$htmlForm->setId( 'mw-prefs-form' );
- $htmlForm->setSubmitText( wfMsg( 'saveprefs' ) );
+ $htmlForm->setSubmitText( $context->msg( 'saveprefs' )->text() );
# Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save'
$htmlForm->setSubmitTooltip( 'preferences-save' );
- $htmlForm->setTitle( SpecialPage::getTitleFor( 'Preferences' ) );
$htmlForm->setSubmitID( 'prefsubmit' );
$htmlForm->setSubmitCallback( array( 'Preferences', 'tryFormSubmit' ) );
@@ -1235,20 +1239,20 @@ class Preferences {
/**
* @return array
*/
- static function getTimezoneOptions() {
+ static function getTimezoneOptions( IContextSource $context ) {
$opt = array();
global $wgLocalTZoffset, $wgLocaltimezone;
// Check that $wgLocalTZoffset is the same as $wgLocaltimezone
if ( $wgLocalTZoffset == date( 'Z' ) / 60 ) {
- $server_tz_msg = wfMsg( 'timezoneuseserverdefault', $wgLocaltimezone );
+ $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $wgLocaltimezone )->text();
} else {
- $tzstring = sprintf( '%+03d:%02d', floor( $wgLocalTZoffset / 60 ), abs( $wgLocalTZoffset ) % 60 );
- $server_tz_msg = wfMsg( 'timezoneuseserverdefault', $tzstring );
+ $tzstring = sprintf( '%+03d:%02d', floor( $wgLocalTZoffset / 60 ), abs( $wgLocalTZoffset ) % 60 );
+ $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text();
}
$opt[$server_tz_msg] = "System|$wgLocalTZoffset";
- $opt[wfMsg( 'timezoneuseoffset' )] = 'other';
- $opt[wfMsg( 'guesstimezone' )] = 'guess';
+ $opt[$context->msg( 'timezoneuseoffset' )->text()] = 'other';
+ $opt[$context->msg( 'guesstimezone' )->text()] = 'guess';
if ( function_exists( 'timezone_identifiers_list' ) ) {
# Read timezone list
@@ -1256,16 +1260,16 @@ class Preferences {
sort( $tzs );
$tzRegions = array();
- $tzRegions['Africa'] = wfMsg( 'timezoneregion-africa' );
- $tzRegions['America'] = wfMsg( 'timezoneregion-america' );
- $tzRegions['Antarctica'] = wfMsg( 'timezoneregion-antarctica' );
- $tzRegions['Arctic'] = wfMsg( 'timezoneregion-arctic' );
- $tzRegions['Asia'] = wfMsg( 'timezoneregion-asia' );
- $tzRegions['Atlantic'] = wfMsg( 'timezoneregion-atlantic' );
- $tzRegions['Australia'] = wfMsg( 'timezoneregion-australia' );
- $tzRegions['Europe'] = wfMsg( 'timezoneregion-europe' );
- $tzRegions['Indian'] = wfMsg( 'timezoneregion-indian' );
- $tzRegions['Pacific'] = wfMsg( 'timezoneregion-pacific' );
+ $tzRegions['Africa'] = $context->msg( 'timezoneregion-africa' )->text();
+ $tzRegions['America'] = $context->msg( 'timezoneregion-america' )->text();
+ $tzRegions['Antarctica'] = $context->msg( 'timezoneregion-antarctica' )->text();
+ $tzRegions['Arctic'] = $context->msg( 'timezoneregion-arctic' )->text();
+ $tzRegions['Asia'] = $context->msg( 'timezoneregion-asia' )->text();
+ $tzRegions['Atlantic'] = $context->msg( 'timezoneregion-atlantic' )->text();
+ $tzRegions['Australia'] = $context->msg( 'timezoneregion-australia' )->text();
+ $tzRegions['Europe'] = $context->msg( 'timezoneregion-europe' )->text();
+ $tzRegions['Indian'] = $context->msg( 'timezoneregion-indian' )->text();
+ $tzRegions['Pacific'] = $context->msg( 'timezoneregion-pacific' )->text();
asort( $tzRegions );
$prefill = array_fill_keys( array_values( $tzRegions ), array() );
@@ -1338,12 +1342,14 @@ class Preferences {
/**
* @param $formData
+ * @param $form PreferencesForm
* @param $entryPoint string
* @return bool|Status|string
*/
- static function tryFormSubmit( $formData, $entryPoint = 'internal' ) {
- global $wgUser, $wgEmailAuthentication, $wgEnableEmail;
+ static function tryFormSubmit( $formData, $form, $entryPoint = 'internal' ) {
+ global $wgHiddenPrefs;
+ $user = $form->getModifiedUser();
$result = true;
// Filter input
@@ -1360,39 +1366,10 @@ class Preferences {
'emailaddress',
);
- if ( $wgEnableEmail ) {
- $newaddr = $formData['emailaddress'];
- $oldaddr = $wgUser->getEmail();
- if ( ( $newaddr != '' ) && ( $newaddr != $oldaddr ) ) {
- # the user has supplied a new email address on the login page
- # new behaviour: set this new emailaddr from login-page into user database record
- $wgUser->setEmail( $newaddr );
- # but flag as "dirty" = unauthenticated
- $wgUser->invalidateEmail();
- if ( $wgEmailAuthentication ) {
- # Mail a temporary password to the dirty address.
- # User can come back through the confirmation URL to re-enable email.
- $type = $oldaddr != '' ? 'changed' : 'set';
- $result = $wgUser->sendConfirmationMail( $type );
- if ( !$result->isGood() ) {
- return htmlspecialchars( $result->getWikiText( 'mailerror' ) );
- } elseif ( $entryPoint == 'ui' ) {
- $result = 'eauth';
- }
- }
- } else {
- $wgUser->setEmail( $newaddr );
- }
- if ( $oldaddr != $newaddr ) {
- wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldaddr, $newaddr ) );
- }
- }
-
// Fortunately, the realname field is MUCH simpler
- global $wgHiddenPrefs;
if ( !in_array( 'realname', $wgHiddenPrefs ) ) {
$realName = $formData['realname'];
- $wgUser->setRealName( $realName );
+ $user->setRealName( $realName );
}
foreach ( $saveBlacklist as $b ) {
@@ -1406,52 +1383,94 @@ class Preferences {
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 );
+ $formData[$pref] = $user->getOption( $pref, null, true );
}
// Keeps old preferences from interfering due to back-compat
// code, etc.
- $wgUser->resetOptions();
+ $user->resetOptions();
foreach ( $formData as $key => $value ) {
- $wgUser->setOption( $key, $value );
+ $user->setOption( $key, $value );
}
- $wgUser->saveSettings();
+ $user->saveSettings();
return $result;
}
/**
* @param $formData
+ * @param $form PreferencesForm
* @return Status
*/
- public static function tryUISubmit( $formData ) {
- $res = self::tryFormSubmit( $formData, 'ui' );
+ public static function tryUISubmit( $formData, $form ) {
+ $res = self::tryFormSubmit( $formData, $form, 'ui' );
if ( $res ) {
- $urlOptions = array( 'success' );
+ $urlOptions = array( 'success' => 1 );
if ( $res === 'eauth' ) {
- $urlOptions[] = 'eauth';
+ $urlOptions['eauth'] = 1;
}
- $queryString = implode( '&', $urlOptions );
+ $urlOptions += $form->getExtraSuccessRedirectParameters();
- $url = SpecialPage::getTitleFor( 'Preferences' )->getFullURL( $queryString );
+ $url = $form->getTitle()->getFullURL( $urlOptions );
- global $wgOut;
- $wgOut->redirect( $url );
+ $form->getContext()->getOutput()->redirect( $url );
}
return Status::newGood();
}
/**
+ * Try to set a user's email address.
+ * This does *not* try to validate the address.
+ * Caller is responsible for checking $wgAuth.
+ * @param $user User
+ * @param $newaddr string New email address
+ * @return Array (true on success or Status on failure, info string)
+ */
+ public static function trySetUserEmail( User $user, $newaddr ) {
+ global $wgEnableEmail, $wgEmailAuthentication;
+ $info = ''; // none
+
+ if ( $wgEnableEmail ) {
+ $oldaddr = $user->getEmail();
+ if ( ( $newaddr != '' ) && ( $newaddr != $oldaddr ) ) {
+ # The user has supplied a new email address on the login page
+ # new behaviour: set this new emailaddr from login-page into user database record
+ $user->setEmail( $newaddr );
+ if ( $wgEmailAuthentication ) {
+ # Mail a temporary password to the dirty address.
+ # User can come back through the confirmation URL to re-enable email.
+ $type = $oldaddr != '' ? 'changed' : 'set';
+ $result = $user->sendConfirmationMail( $type );
+ if ( !$result->isGood() ) {
+ return array( $result, 'mailerror' );
+ }
+ $info = 'eauth';
+ }
+ } elseif ( $newaddr != $oldaddr ) { // if the address is the same, don't change it
+ $user->setEmail( $newaddr );
+ }
+ if ( $oldaddr != $newaddr ) {
+ wfRunHooks( 'PrefsEmailAudit', array( $user, $oldaddr, $newaddr ) );
+ }
+ }
+
+ return array( true, $info );
+ }
+
+ /**
+ * @deprecated in 1.19; will be removed in 1.20.
* @param $user User
* @return array
*/
public static function loadOldSearchNs( $user ) {
+ wfDeprecated( __METHOD__, '1.19' );
+
$searchableNamespaces = SearchEngine::searchableNamespaces();
// Back compat with old format
$arr = array();
@@ -1468,6 +1487,38 @@ class Preferences {
/** Some tweaks to allow js prefs to work */
class PreferencesForm extends HTMLForm {
+ // Override default value from HTMLForm
+ protected $mSubSectionBeforeFields = false;
+
+ private $modifiedUser;
+
+ /**
+ * @param $user User
+ */
+ public function setModifiedUser( $user ) {
+ $this->modifiedUser = $user;
+ }
+
+ /**
+ * @return User
+ */
+ public function getModifiedUser() {
+ if ( $this->modifiedUser === null ) {
+ return $this->getUser();
+ } else {
+ return $this->modifiedUser;
+ }
+ }
+
+ /**
+ * Get extra parameters for the query string when redirecting after
+ * successful save.
+ *
+ * @return array()
+ */
+ public function getExtraSuccessRedirectParameters() {
+ return array();
+ }
/**
* @param $html string
@@ -1485,12 +1536,9 @@ class PreferencesForm extends HTMLForm {
function getButtons() {
$html = parent::getButtons();
- global $wgUser;
-
- $sk = $wgUser->getSkin();
$t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
- $html .= "\n" . $sk->link( $t, wfMsgHtml( 'restoreprefs' ) );
+ $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped() );
$html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
@@ -1520,10 +1568,24 @@ class PreferencesForm extends HTMLForm {
return $data;
}
+
/**
* Get the whole body of the form.
+ * @return string
*/
function getBody() {
return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' );
}
+
+ /**
+ * Get the <legend> for a given section key. Normally this is the
+ * prefs-$key message but we'll allow extensions to override it.
+ * @param $key string
+ * @return string
+ */
+ function getLegend( $key ) {
+ $legend = parent::getLegend( $key );
+ wfRunHooks( 'PreferencesGetLegend', array( $this, $key, &$legend ) );
+ return $legend;
+ }
}
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index 71703eb2..dbe06d49 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -66,8 +66,11 @@ class ProtectionForm {
// Check if the form should be disabled.
// If it is, the form will be available in read-only to show levels.
- $this->mPermErrors = $this->mTitle->getUserPermissionsErrors('protect',$wgUser);
- $this->disabled = wfReadOnly() || $this->mPermErrors != array();
+ $this->mPermErrors = $this->mTitle->getUserPermissionsErrors( 'protect', $wgUser );
+ if ( wfReadOnly() ) {
+ $this->mPermErrors[] = array( 'readonlytext', wfReadOnlyReason() );
+ }
+ $this->disabled = $this->mPermErrors != array();
$this->disabledAttrib = $this->disabled
? array( 'disabled' => 'disabled' )
: array();
@@ -180,6 +183,11 @@ class ProtectionForm {
*/
function execute() {
global $wgRequest, $wgOut;
+
+ if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
+ throw new ErrorPageError( 'protect-badnamespace-title', 'protect-badnamespace-text' );
+ }
+
if( $wgRequest->wasPosted() ) {
if( $this->save() ) {
$q = $this->mArticle->isRedirect() ? 'redirect=no' : '';
@@ -196,23 +204,17 @@ class ProtectionForm {
* @param $err String: error message or null if there's no error
*/
function show( $err = null ) {
- global $wgOut, $wgUser;
+ global $wgOut;
$wgOut->setRobotPolicy( 'noindex,nofollow' );
- if( is_null( $this->mTitle ) ||
- $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- $wgOut->showFatalError( wfMsg( 'badarticleerror' ) );
- return;
- }
-
- list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
-
- if ( $err != "" ) {
- $wgOut->setSubtitle( wfMsgHtml( 'formerror' ) );
+ if ( is_array( $err ) ) {
+ $wgOut->wrapWikiMsg( "<p class='error'>\n$1\n</p>\n", $err );
+ } elseif ( is_string( $err ) ) {
$wgOut->addHTML( "<p class='error'>{$err}</p>\n" );
}
+ list( $cascadeSources, /* $restrictions */ ) = $this->mTitle->getCascadeProtectionSources();
if ( $cascadeSources && count($cascadeSources) > 0 ) {
$titles = '';
@@ -223,26 +225,19 @@ class ProtectionForm {
$wgOut->wrapWikiMsg( "<div id=\"mw-protect-cascadeon\">\n$1\n" . $titles . "</div>", array( 'protect-cascadeon', count($cascadeSources) ) );
}
- $sk = $wgUser->getSkin();
- $titleLink = $sk->link( $this->mTitle );
- $wgOut->setPageTitle( wfMsg( 'protect-title', $this->mTitle->getPrefixedText() ) );
- $wgOut->setSubtitle( wfMsg( 'protect-backlink', $titleLink ) );
-
# Show an appropriate message if the user isn't allowed or able to change
# the protection settings at this time
- if( $this->disabled ) {
- if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- } elseif( $this->mPermErrors ) {
- $wgOut->showPermissionsErrorPage( $this->mPermErrors );
- }
+ if ( $this->disabled ) {
+ $wgOut->setPageTitle( wfMessage( 'protect-title-notallowed', $this->mTitle->getPrefixedText() ) );
+ $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $this->mPermErrors, 'protect' ) );
} else {
+ $wgOut->setPageTitle( wfMessage( 'protect-title', $this->mTitle->getPrefixedText() ) );
$wgOut->addWikiMsg( 'protect-text',
wfEscapeWikiText( $this->mTitle->getPrefixedText() ) );
}
+ $wgOut->addBacklinkSubtitle( $this->mTitle );
$wgOut->addHTML( $this->buildForm() );
-
$this->showLogExtract( $wgOut );
}
@@ -252,7 +247,7 @@ class ProtectionForm {
* @return Boolean: success
*/
function save() {
- global $wgRequest, $wgUser;
+ global $wgRequest, $wgUser, $wgOut;
# Permission check!
if ( $this->disabled ) {
@@ -261,8 +256,8 @@ class ProtectionForm {
}
$token = $wgRequest->getVal( 'wpEditToken' );
- if ( !$wgUser->matchEditToken( $token ) ) {
- $this->show( wfMsg( 'sessionfailure' ) );
+ if ( !$wgUser->matchEditToken( $token, array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) ) {
+ $this->show( array( 'sessionfailure' ) );
return false;
}
@@ -280,11 +275,11 @@ class ProtectionForm {
if( empty($this->mRestrictions[$action]) )
continue; // unprotected
if ( !$expiry[$action] ) {
- $this->show( wfMsg( 'protect_expiry_invalid' ) );
+ $this->show( array( 'protect_expiry_invalid' ) );
return false;
}
if ( $expiry[$action] < wfTimestampNow() ) {
- $this->show( wfMsg( 'protect_expiry_old' ) );
+ $this->show( array( 'protect_expiry_old' ) );
return false;
}
}
@@ -299,20 +294,24 @@ class ProtectionForm {
!(isset($wgGroupPermissions[$edit_restriction]['protect']) && $wgGroupPermissions[$edit_restriction]['protect'] ) )
$this->mCascade = false;
- if ($this->mTitle->exists()) {
- $ok = $this->mArticle->updateRestrictions( $this->mRestrictions, $reasonstr, $this->mCascade, $expiry );
- } else {
- $ok = $this->mTitle->updateTitleProtection( $this->mRestrictions['create'], $reasonstr, $expiry['create'] );
- }
+ $status = $this->mArticle->doUpdateRestrictions( $this->mRestrictions, $expiry, $this->mCascade, $reasonstr, $wgUser );
- if( !$ok ) {
- throw new FatalError( "Unknown error at restriction save time." );
+ if ( !$status->isOK() ) {
+ $this->show( $wgOut->parseInline( $status->getWikiText() ) );
+ return false;
}
+ /**
+ * Give extensions a change to handle added form items
+ *
+ * @since 1.19 you can (and you should) return false to abort saving;
+ * you can also return an array of message name and its parameters
+ */
$errorMsg = '';
- # Give extensions a change to handle added form items
- if( !wfRunHooks( 'ProtectionForm::save', array($this->mArticle,&$errorMsg) ) ) {
- throw new FatalError( "Unknown hook error at restriction save time." );
+ if( !wfRunHooks( 'ProtectionForm::save', array( $this->mArticle, &$errorMsg ) ) ) {
+ if ( $errorMsg == '' ) {
+ $errorMsg = array( 'hookaborted' );
+ }
}
if( $errorMsg != '' ) {
$this->show( $errorMsg );
@@ -324,7 +323,7 @@ class ProtectionForm {
} elseif ( $this->mTitle->userIsWatching() ) {
WatchAction::doUnwatch( $this->mTitle, $wgUser );
}
- return $ok;
+ return true;
}
/**
@@ -344,7 +343,6 @@ class ProtectionForm {
$out .= Xml::openElement( 'form', array( 'method' => 'post',
'action' => $this->mTitle->getLocalUrl( 'action=protect' ),
'id' => 'mw-Protect-Form', 'onsubmit' => 'ProtectionForm.enableUnchainedInputs(true)' ) );
- $out .= Html::hidden( 'wpEditToken',$wgUser->editToken() );
}
$out .= Xml::openElement( 'fieldset' ) .
@@ -375,9 +373,9 @@ class ProtectionForm {
$expiryFormOptions = '';
if ( $this->mExistingExpiry[$action] && $this->mExistingExpiry[$action] != 'infinity' ) {
- $timestamp = $wgLang->timeanddate( $this->mExistingExpiry[$action] );
- $d = $wgLang->date( $this->mExistingExpiry[$action] );
- $t = $wgLang->time( $this->mExistingExpiry[$action] );
+ $timestamp = $wgLang->timeanddate( $this->mExistingExpiry[$action], true );
+ $d = $wgLang->date( $this->mExistingExpiry[$action], true );
+ $t = $wgLang->time( $this->mExistingExpiry[$action], true );
$expiryFormOptions .=
Xml::option(
wfMsg( 'protect-existing-expiry', $timestamp, $d, $t ),
@@ -501,7 +499,7 @@ class ProtectionForm {
if ( $wgUser->isAllowed( 'editinterface' ) ) {
$title = Title::makeTitle( NS_MEDIAWIKI, 'Protect-dropdown' );
- $link = $wgUser->getSkin()->link(
+ $link = Linker::link(
$title,
wfMsgHtml( 'protect-edit-reasonlist' ),
array(),
@@ -511,6 +509,7 @@ class ProtectionForm {
}
if ( !$this->disabled ) {
+ $out .= Html::hidden( 'wpEditToken', $wgUser->getEditToken( array( 'protect', $this->mTitle->getPrefixedDBkey() ) ) );
$out .= Xml::closeElement( 'form' );
$wgOut->addScript( $this->buildCleanupScript() );
}
@@ -608,7 +607,7 @@ class ProtectionForm {
function showLogExtract( &$out ) {
# Show relevant lines from the protection log:
$out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'protect' ) ) );
- LogEventsList::showLogExtract( $out, 'protect', $this->mTitle->getPrefixedText() );
+ LogEventsList::showLogExtract( $out, 'protect', $this->mTitle );
# Let extensions add other relevant log extracts
wfRunHooks( 'ProtectionForm::showLogExtract', array($this->mArticle,$out) );
}
diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php
index 99cd65c5..bdab3be2 100644
--- a/includes/ProxyTools.php
+++ b/includes/ProxyTools.php
@@ -7,35 +7,15 @@
/**
* Extracts the XFF string from the request header
- * Checks first for "X-Forwarded-For", then "Client-ip"
* Note: headers are spoofable
+ *
+ * @deprecated in 1.19; use $wgRequest->getHeader( 'X-Forwarded-For' ) instead.
* @return string
*/
function wfGetForwardedFor() {
- $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 ( $apacheHeaders as $tempName => $tempValue ) {
- $set[ strtoupper( $tempName ) ] = $tempValue;
- }
- $index = strtoupper ( 'X-Forwarded-For' );
- $index2 = strtoupper ( 'Client-ip' );
- } else {
- // Subject to spoofing with headers like X_Forwarded_For
- $set = $_SERVER;
- $index = 'HTTP_X_FORWARDED_FOR';
- $index2 = 'CLIENT-IP';
- }
-
- #Try a couple of headers
- if( isset( $set[$index] ) ) {
- return $set[$index];
- } elseif( isset( $set[$index2] ) ) {
- return $set[$index2];
- } else {
- return null;
- }
+ wfDeprecated( __METHOD__, '1.19' );
+ global $wgRequest;
+ return $wgRequest->getHeader( 'X-Forwarded-For' );
}
/**
@@ -46,88 +26,29 @@ function wfGetForwardedFor() {
* @return string
*/
function wfGetAgent() {
- wfDeprecated( __FUNCTION__ );
- if( function_exists( 'apache_request_headers' ) ) {
- // More reliable than $_SERVER due to case and -/_ folding
- $set = array();
- foreach ( apache_request_headers() as $tempName => $tempValue ) {
- $set[ strtoupper( $tempName ) ] = $tempValue;
- }
- $index = strtoupper ( 'User-Agent' );
- } else {
- // Subject to spoofing with headers like X_Forwarded_For
- $set = $_SERVER;
- $index = 'HTTP_USER_AGENT';
- }
- if( isset( $set[$index] ) ) {
- return $set[$index];
- } else {
- return '';
- }
+ wfDeprecated( __METHOD__, '1.18' );
+ global $wgRequest;
+ return $wgRequest->getHeader( 'User-Agent' );
}
/**
* Work out the IP address based on various globals
* For trusted proxies, use the XFF client IP (first of the chain)
+ *
+ * @deprecated in 1.19; call $wgRequest->getIP() directly.
* @return string
*/
function wfGetIP() {
- global $wgUsePrivateIPs, $wgCommandLineMode;
- static $ip = false;
-
- # Return cached result
- if ( !empty( $ip ) ) {
- return $ip;
- }
-
- /* collect the originating ips */
- # Client connecting to this webserver
- if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
- $ip = IP::canonicalize( $_SERVER['REMOTE_ADDR'] );
- } elseif( $wgCommandLineMode ) {
- $ip = '127.0.0.1';
- }
-
- # Append XFF
- $forwardedFor = wfGetForwardedFor();
- 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];
- }
- }
- } else {
- break;
- }
- }
- }
-
- # Allow extensions to improve our guess
- wfRunHooks( 'GetIP', array( &$ip ) );
-
- if( !$ip ) {
- throw new MWException( "Unable to determine IP" );
- }
-
- wfDebug( "IP: $ip\n" );
- return $ip;
+ wfDeprecated( __METHOD__, '1.19' );
+ global $wgRequest;
+ return $wgRequest->getIP();
}
/**
- * Checks if an IP is a trusted proxy providor
- * Useful to tell if X-Fowarded-For data is possibly bogus
- * Squid cache servers for the site and AOL are whitelisted
+ * Checks if an IP is a trusted proxy providor.
+ * Useful to tell if X-Fowarded-For data is possibly bogus.
+ * Squid cache servers for the site are whitelisted.
+ *
* @param $ip String
* @return bool
*/
@@ -146,14 +67,14 @@ function wfIsTrustedProxy( $ip ) {
*/
function wfProxyCheck() {
global $wgBlockOpenProxies, $wgProxyPorts, $wgProxyScriptPath;
- global $wgMemc, $wgProxyMemcExpiry;
+ global $wgMemc, $wgProxyMemcExpiry, $wgRequest;
global $wgProxyKey;
if ( !$wgBlockOpenProxies ) {
return;
}
- $ip = wfGetIP();
+ $ip = $wgRequest->getIP();
# Get MemCached key
$mcKey = wfMemcKey( 'proxy', 'ip', $ip );
diff --git a/includes/QueryPage.php b/includes/QueryPage.php
index b3bb974b..69912cbf 100644
--- a/includes/QueryPage.php
+++ b/includes/QueryPage.php
@@ -437,29 +437,31 @@ abstract class QueryPage extends SpecialPage {
* real, honest-to-gosh query page.
*/
function execute( $par ) {
- global $wgUser, $wgOut, $wgLang, $wgRequest;
+ global $wgQueryCacheLimit, $wgDisableQueryPageUpdate;
- if ( !$this->userCanExecute( $wgUser ) ) {
+ $user = $this->getUser();
+ if ( !$this->userCanExecute( $user ) ) {
$this->displayRestrictionError();
return;
}
- if ( $this->limit == 0 && $this->offset == 0 ) {
- list( $this->limit, $this->offset ) = $wgRequest->getLimitOffset();
- }
- $dbr = wfGetDB( DB_SLAVE );
-
$this->setHeaders();
- $wgOut->setSyndicated( $this->isSyndicated() );
+ $this->outputHeader();
+
+ $out = $this->getOutput();
if ( $this->isCached() && !$this->isCacheable() ) {
- $wgOut->setSyndicated( false );
- $wgOut->addWikiMsg( 'querypage-disabled' );
+ $out->addWikiMsg( 'querypage-disabled' );
return 0;
}
+ $out->setSyndicated( $this->isSyndicated() );
+
+ if ( $this->limit == 0 && $this->offset == 0 ) {
+ list( $this->limit, $this->offset ) = $this->getRequest()->getLimitOffset();
+ }
+
// TODO: Use doQuery()
- // $res = null;
if ( !$this->isCached() ) {
$res = $this->reallyDoQuery( $this->limit, $this->offset );
} else {
@@ -469,50 +471,50 @@ abstract class QueryPage extends SpecialPage {
# Fetch the timestamp of this update
$ts = $this->getCachedTimestamp();
+ $lang = $this->getLanguage();
+ $maxResults = $lang->formatNum( $wgQueryCacheLimit );
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 );
+ $updated = $lang->userTimeAndDate( $ts, $user );
+ $updateddate = $lang->userDate( $ts, $user );
+ $updatedtime = $lang->userTime( $ts, $user );
+ $out->addMeta( 'Data-Cache-Time', $ts );
+ $out->addInlineScript( "var dataCacheTime = '$ts';" );
+ $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults );
} else {
- $wgOut->addWikiMsg( 'perfcached' );
+ $out->addWikiMsg( 'perfcached', $maxResults );
}
# 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 ) ) {
- $wgOut->addWikiMsg( 'querypage-no-updates' );
+ $out->addWikiMsg( 'querypage-no-updates' );
}
-
}
-
}
- $this->numRows = $dbr->numRows( $res );
+ $this->numRows = $res->numRows();
+ $dbr = wfGetDB( DB_SLAVE );
$this->preprocessResults( $dbr, $res );
- $wgOut->addHTML( Xml::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) );
+ $out->addHTML( Xml::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) );
# Top header and navigation
if ( $this->shownavigation ) {
- $wgOut->addHTML( $this->getPageHeader() );
+ $out->addHTML( $this->getPageHeader() );
if ( $this->numRows > 0 ) {
- $wgOut->addHTML( '<p>' . wfShowingResults( $this->offset, $this->numRows ) . '</p>' );
+ $out->addHTML( $this->msg( 'showingresults' )->numParams(
+ $this->numRows, $this->offset + 1 )->parseAsBlock() );
# Disable the "next" link when we reach the end
- $paging = wfViewPrevNext( $this->offset, $this->limit,
- $this->getTitle( $par ),
- wfArrayToCGI( $this->linkParameters() ), ( $this->numRows < $this->limit ) );
- $wgOut->addHTML( '<p>' . $paging . '</p>' );
+ $paging = $this->getLanguage()->viewPrevNext( $this->getTitle( $par ), $this->offset,
+ $this->limit, $this->linkParameters(), ( $this->numRows < $this->limit ) );
+ $out->addHTML( '<p>' . $paging . '</p>' );
} else {
# No results to show, so don't bother with "showing X of Y" etc.
# -- just let the user know and give up now
- $wgOut->addHTML( '<p>' . wfMsgHtml( 'specialpage-empty' ) . '</p>' );
- $wgOut->addHTML( Xml::closeElement( 'div' ) );
+ $out->addWikiMsg( 'specialpage-empty' );
+ $out->addHTML( Xml::closeElement( 'div' ) );
return;
}
}
@@ -520,7 +522,7 @@ abstract class QueryPage extends SpecialPage {
# The actual results; specialist subclasses will want to handle this
# 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,
+ $this->outputResults( $out,
$this->getSkin(),
$dbr, # Should use a ResultWrapper for this
$res,
@@ -529,10 +531,10 @@ abstract class QueryPage extends SpecialPage {
# Repeat the paging links at the bottom
if ( $this->shownavigation ) {
- $wgOut->addHTML( '<p>' . $paging . '</p>' );
+ $out->addHTML( '<p>' . $paging . '</p>' );
}
- $wgOut->addHTML( Xml::closeElement( 'div' ) );
+ $out->addHTML( Xml::closeElement( 'div' ) );
return $this->numRows;
}
@@ -597,10 +599,17 @@ abstract class QueryPage extends SpecialPage {
}
}
+ /**
+ * @param $offset
+ * @return string
+ */
function openList( $offset ) {
return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n";
}
+ /**
+ * @return string
+ */
function closeList() {
return "</ol>\n";
}
@@ -617,8 +626,7 @@ abstract class QueryPage extends SpecialPage {
global $wgFeed, $wgFeedClasses;
if ( !$wgFeed ) {
- global $wgOut;
- $wgOut->addWikiMsg( 'feed-unavailable' );
+ $this->getOutput()->addWikiMsg( 'feed-unavailable' );
return;
}
@@ -693,7 +701,7 @@ abstract class QueryPage extends SpecialPage {
}
function feedDesc() {
- return wfMsgExt( 'tagline', 'parsemag' );
+ return $this->msg( 'tagline' )->text();
}
function feedUrl() {
@@ -754,8 +762,8 @@ abstract class WantedQueryPage extends QueryPage {
if ( $title instanceof Title ) {
if ( $this->isCached() || $this->forceExistenceCheck() ) {
$pageLink = $title->isKnown()
- ? '<del>' . $skin->link( $title ) . '</del>'
- : $skin->link(
+ ? '<del>' . Linker::link( $title ) . '</del>'
+ : Linker::link(
$title,
null,
array(),
@@ -763,7 +771,7 @@ abstract class WantedQueryPage extends QueryPage {
array( 'broken' )
);
} else {
- $pageLink = $skin->link(
+ $pageLink = Linker::link(
$title,
null,
array(),
@@ -771,10 +779,9 @@ abstract class WantedQueryPage extends QueryPage {
array( 'broken' )
);
}
- return wfSpecialList( $pageLink, $this->makeWlhLink( $title, $skin, $result ) );
+ return $this->getLanguage()->specialList( $pageLink, $this->makeWlhLink( $title, $result ) );
} else {
- $tsafe = htmlspecialchars( $result->title );
- return wfMsgHtml( 'wantedpages-badtitle', $tsafe );
+ return $this->msg( 'wantedpages-badtitle', $result->title )->escaped();
}
}
@@ -782,15 +789,12 @@ abstract class WantedQueryPage extends QueryPage {
* Make a "what links here" link for a given title
*
* @param $title Title to make the link for
- * @param $skin Skin object to use
* @param $result Object: result row
* @return string
*/
- private function makeWlhLink( $title, $skin, $result ) {
- global $wgLang;
- $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' );
- $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->value ) );
- return $skin->link( $wlh, $label, array(), array( 'target' => $title->getPrefixedText() ) );
+ private function makeWlhLink( $title, $result ) {
+ $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
+ $label = $this->msg( 'nlinks' )->numParams( $result->value )->escaped();
+ return Linker::link( $wlh, $label );
}
}
diff --git a/includes/RawPage.php b/includes/RawPage.php
deleted file mode 100644
index 6a552a50..00000000
--- a/includes/RawPage.php
+++ /dev/null
@@ -1,228 +0,0 @@
-<?php
-/**
- * Raw page text accessor
- *
- * Copyright © 2004 Gabriel Wicke <wicke@wikidev.net>
- * http://wikidev.net/
- *
- * Based on HistoryPage and SpecialExport
- *
- * License: GPL (http://www.gnu.org/copyleft/gpl.html)
- *
- * @author Gabriel Wicke <wicke@wikidev.net>
- * @file
- */
-
-/**
- * A simple method to retrieve the plain source of an article,
- * using "action=raw" in the GET request string.
- */
-class RawPage {
- var $mArticle, $mTitle, $mRequest;
- var $mOldId, $mGen, $mCharset, $mSection;
- var $mSmaxage, $mMaxage;
- var $mContentType, $mExpandTemplates;
-
- 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->getTitle();
-
- if( $request === false ) {
- $this->mRequest = $wgRequest;
- } else {
- $this->mRequest = $request;
- }
-
- $ctype = $this->mRequest->getVal( 'ctype' );
- $smaxage = $this->mRequest->getIntOrNull( 'smaxage' );
- $maxage = $this->mRequest->getInt( 'maxage', $wgSquidMaxage );
-
- $this->mExpandTemplates = $this->mRequest->getVal( 'templates' ) === 'expand';
- $this->mUseMessageCache = $this->mRequest->getBool( 'usemsgcache' );
-
- $this->mSection = $this->mRequest->getIntOrNull( 'section' );
-
- $oldid = $this->mRequest->getInt( 'oldid' );
-
- switch( $wgRequest->getText( 'direction' ) ) {
- case 'next':
- # output next revision, or nothing if there isn't one
- if( $oldid ) {
- $oldid = $this->mTitle->getNextRevisionId( $oldid );
- }
- $oldid = $oldid ? $oldid : -1;
- break;
- case 'prev':
- # output previous revision, or nothing if there isn't one
- if( !$oldid ) {
- # get the current revision so we can get the penultimate one
- $this->mArticle->getTouched();
- $oldid = $this->mArticle->getLatest();
- }
- $prev = $this->mTitle->getPreviousRevisionId( $oldid );
- $oldid = $prev ? $prev : -1 ;
- break;
- case 'cur':
- $oldid = 0;
- break;
- }
- $this->mOldId = $oldid;
-
- # special case for 'generated' raw things: user css/js
- $gen = $this->mRequest->getVal( 'gen' );
-
- if( $gen == 'css' ) {
- $this->mGen = $gen;
- if( is_null( $smaxage ) ) {
- $smaxage = $wgSquidMaxage;
- }
- if( $ctype == '' ) {
- $ctype = 'text/css';
- }
- } elseif( $gen == 'js' ) {
- $this->mGen = $gen;
- if( is_null( $smaxage ) ) $smaxage = $wgSquidMaxage;
- if($ctype == '') $ctype = $wgJsMimeType;
- } else {
- $this->mGen = false;
- }
- $this->mCharset = 'UTF-8';
-
- # Force caching for CSS and JS raw content, default: 5 minutes
- if( is_null( $smaxage ) && ( $ctype == 'text/css' || $ctype == $wgJsMimeType ) ) {
- global $wgForcedRawSMaxage;
- $this->mSmaxage = intval( $wgForcedRawSMaxage );
- } else {
- $this->mSmaxage = intval( $smaxage );
- }
- $this->mMaxage = $maxage;
-
- # Output may contain user-specific data;
- # vary generated content for open sessions and private wikis
- if( $this->mGen || !$wgGroupPermissions['*']['read'] ) {
- $this->mPrivateCache = $this->mSmaxage == 0 || session_id() != '';
- } else {
- $this->mPrivateCache = false;
- }
-
- if( $ctype == '' || !in_array( $ctype, $allowedCTypes ) ) {
- $this->mContentType = 'text/x-wiki';
- } else {
- $this->mContentType = $ctype;
- }
- }
-
- function view() {
- global $wgOut, $wgRequest;
-
- if( !$wgRequest->checkUrlExtension() ) {
- $wgOut->disable();
- return;
- }
-
- header( 'Content-type: ' . $this->mContentType . '; charset=' . $this->mCharset );
- # allow the client to cache this for 24 hours
- $mode = $this->mPrivateCache ? 'private' : 'public';
- header( 'Cache-Control: ' . $mode . ', s-maxage=' . $this->mSmaxage . ', max-age=' . $this->mMaxage );
-
- global $wgUseFileCache;
- if( $wgUseFileCache && HTMLFileCache::useFileCache() ) {
- $cache = new HTMLFileCache( $this->mTitle, 'raw' );
- if( $cache->isFileCacheGood( /* Assume up to date */ ) ) {
- $cache->loadFromFileCache();
- $wgOut->disable();
- return;
- } else {
- ob_start( array( &$cache, 'saveToFileCache' ) );
- }
- }
-
- $text = $this->getRawText();
-
- if( !wfRunHooks( 'RawPageViewBeforeOutput', array( &$this, &$text ) ) ) {
- wfDebug( __METHOD__ . ": RawPageViewBeforeOutput hook broke raw page output.\n" );
- }
-
- echo $text;
- $wgOut->disable();
- }
-
- function getRawText() {
- global $wgOut, $wgUser;
- if( $this->mGen ) {
- $sk = $wgUser->getSkin();
- if( !StubObject::isRealObject( $wgOut ) ) {
- $wgOut->_unstub( 2 );
- }
- $sk->initPage( $wgOut );
- if( $this->mGen == 'css' ) {
- return $sk->generateUserStylesheet();
- } elseif( $this->mGen == 'js' ) {
- return $sk->generateUserJs();
- }
- } else {
- return $this->getArticleText();
- }
- }
-
- function getArticleText() {
- $found = false;
- $text = '';
- if( $this->mTitle ) {
- // 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();
- $msg = wfMessage( $key )->inContentLanguage();
- # If the message doesn't exist, return a blank
- $text = !$msg->exists() ? '' : $msg->plain();
- $found = true;
- } else {
- // Get it from the DB
- $rev = Revision::newFromTitle( $this->mTitle, $this->mOldId );
- if( $rev ) {
- $lastmod = wfTimestamp( TS_RFC2822, $rev->getTimestamp() );
- header( "Last-modified: $lastmod" );
-
- if( !is_null( $this->mSection ) ) {
- global $wgParser;
- $text = $wgParser->getSection( $rev->getText(), $this->mSection );
- } else {
- $text = $rev->getText();
- }
- $found = true;
- }
- }
- }
-
- # Bad title or page does not exist
- if( !$found && $this->mContentType == 'text/x-wiki' ) {
- # Don't return a 404 response for CSS or JavaScript;
- # 404s aren't generally cached and it would create
- # extra hits when user CSS/JS are on and the user doesn't
- # have the pages.
- header( 'HTTP/1.0 404 Not Found' );
- }
-
- return $this->parseArticleText( $text );
- }
-
- /**
- * @param $text
- * @return string
- */
- function parseArticleText( $text ) {
- if( $text === '' ) {
- return '';
- } else {
- if( $this->mExpandTemplates ) {
- global $wgParser;
- return $wgParser->preprocess( $text, $this->mTitle, new ParserOptions() );
- } else {
- return $text;
- }
- }
- }
-}
diff --git a/includes/RecentChange.php b/includes/RecentChange.php
index 955986e9..ca0ed955 100644
--- a/includes/RecentChange.php
+++ b/includes/RecentChange.php
@@ -55,15 +55,24 @@ class RecentChange {
*/
var $mMovedToTitle = false;
var $numberofWatchingusers = 0 ; # Dummy to prevent error message in SpecialRecentchangeslinked
+ var $notificationtimestamp;
# Factory methods
+ /**
+ * @param $row
+ * @return RecentChange
+ */
public static function newFromRow( $row ) {
$rc = new RecentChange;
$rc->loadFromRow( $row );
return $rc;
}
+ /**
+ * @param $row
+ * @return RecentChange
+ */
public static function newFromCurRow( $row ) {
$rc = new RecentChange;
$rc->loadFromCurRow( $row );
@@ -96,9 +105,7 @@ class RecentChange {
* @param $fname Mixed: override the method name in profiling/logs
* @return RecentChange
*/
- public static function newFromConds( $conds, $fname = false ) {
- if( $fname === false )
- $fname = __METHOD__;
+ public static function newFromConds( $conds, $fname = __METHOD__ ) {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select(
'recentchanges',
@@ -116,10 +123,16 @@ class RecentChange {
# Accessors
+ /**
+ * @param $attribs array
+ */
public function setAttribs( $attribs ) {
$this->mAttribs = $attribs;
}
+ /**
+ * @param $extra array
+ */
public function setExtra( $extra ) {
$this->mExtra = $extra;
}
@@ -137,6 +150,9 @@ class RecentChange {
return $this->mTitle;
}
+ /**
+ * @return bool|\Title
+ */
public function getMovedToTitle() {
if( $this->mMovedToTitle === false ) {
$this->mMovedToTitle = Title::makeTitle( $this->mAttribs['rc_moved_to_ns'],
@@ -145,10 +161,12 @@ class RecentChange {
return $this->mMovedToTitle;
}
- # Writes the data in this object to the database
- public function save() {
- global $wgLocalInterwiki, $wgPutIPinRC, $wgRC2UDPAddress, $wgRC2UDPOmitBots;
- $fname = 'RecentChange::save';
+ /**
+ * Writes the data in this object to the database
+ * @param $noudp bool
+ */
+ public function save( $noudp = false ) {
+ global $wgLocalInterwiki, $wgPutIPinRC, $wgContLang;
$dbw = wfGetDB( DB_MASTER );
if( !is_array($this->mExtra) ) {
@@ -165,6 +183,9 @@ class RecentChange {
unset( $this->mAttribs['rc_ip'] );
}
+ # Make sure summary is truncated (whole multibyte characters)
+ $this->mAttribs['rc_comment'] = $wgContLang->truncate( $this->mAttribs['rc_comment'], 255 );
+
# Fixup database timestamps
$this->mAttribs['rc_timestamp'] = $dbw->timestamp($this->mAttribs['rc_timestamp']);
$this->mAttribs['rc_cur_time'] = $dbw->timestamp($this->mAttribs['rc_cur_time']);
@@ -176,17 +197,17 @@ class RecentChange {
}
# Insert new row
- $dbw->insert( 'recentchanges', $this->mAttribs, $fname );
+ $dbw->insert( 'recentchanges', $this->mAttribs, __METHOD__ );
# Set the ID
$this->mAttribs['rc_id'] = $dbw->insertId();
-
+
# Notify extensions
wfRunHooks( 'RecentChange_save', array( &$this ) );
# Notify external application via UDP
- if( $wgRC2UDPAddress && ( !$this->mAttribs['rc_bot'] || !$wgRC2UDPOmitBots ) ) {
- self::sendToUDP( $this->getIRCLine() );
+ if ( !$noudp ) {
+ $this->notifyRC2UDP();
}
# E-mail notifications
@@ -194,24 +215,25 @@ class RecentChange {
if( $wgUseEnotif || $wgShowUpdatedMarker ) {
// Users
if( $this->mAttribs['rc_user'] ) {
- $editor = ($wgUser->getId() == $this->mAttribs['rc_user']) ?
+ $editor = ($wgUser->getId() == $this->mAttribs['rc_user']) ?
$wgUser : User::newFromID( $this->mAttribs['rc_user'] );
// Anons
} else {
- $editor = ($wgUser->getName() == $this->mAttribs['rc_user_text']) ?
+ $editor = ($wgUser->getName() == $this->mAttribs['rc_user_text']) ?
$wgUser : User::newFromName( $this->mAttribs['rc_user_text'], false );
}
+ $title = Title::makeTitle( $this->mAttribs['rc_namespace'], $this->mAttribs['rc_title'] );
+
# @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,
+ $status = $enotif->notifyOnPageChange( $editor, $title,
$this->mAttribs['rc_timestamp'],
$this->mAttribs['rc_comment'],
$this->mAttribs['rc_minor'],
$this->mAttribs['rc_last_oldid'] );
}
}
-
+
public function notifyRC2UDP() {
global $wgRC2UDPAddress, $wgRC2UDPOmitBots;
# Notify external application via UDP
@@ -250,7 +272,7 @@ class RecentChange {
}
return false;
}
-
+
/**
* Remove newlines, carriage returns and decode html entites
* @param $text String
@@ -279,7 +301,7 @@ class RecentChange {
}
return $change->doMarkPatrolled( $wgUser, $auto );
}
-
+
/**
* Mark this RecentChange as patrolled
*
@@ -321,7 +343,7 @@ class RecentChange {
wfRunHooks( 'MarkPatrolledComplete', array($this->getAttribute('rc_id'), &$user, false) );
return array();
}
-
+
/**
* Mark this RecentChange patrolled, without error checking
* @return Integer: number of affected rows
@@ -361,8 +383,9 @@ class RecentChange {
*/
public static function notifyEdit( $timestamp, &$title, $minor, &$user, $comment, $oldId,
$lastTimestamp, $bot, $ip='', $oldSize=0, $newSize=0, $newId=0, $patrol=0 ) {
+ global $wgRequest;
if( !$ip ) {
- $ip = wfGetIP();
+ $ip = $wgRequest->getIP();
if( !$ip ) $ip = '';
}
@@ -424,9 +447,12 @@ class RecentChange {
*/
public static function notifyNew( $timestamp, &$title, $minor, &$user, $comment, $bot,
$ip='', $size=0, $newId=0, $patrol=0 ) {
+ global $wgRequest;
if( !$ip ) {
- $ip = wfGetIP();
- if( !$ip ) $ip = '';
+ $ip = $wgRequest->getIP();
+ if( !$ip ) {
+ $ip = '';
+ }
}
$rc = new RecentChange;
@@ -465,75 +491,24 @@ class RecentChange {
'newSize' => $size
);
$rc->save();
- return $rc;
+ return $rc;
}
- # Makes an entry in the database corresponding to a rename
-
/**
* @param $timestamp
- * @param $oldTitle Title
- * @param $newTitle Title
- * @param $user User
- * @param $comment
+ * @param $title
+ * @param $user
+ * @param $actionComment
* @param $ip string
- * @param $overRedir bool
- * @return void
+ * @param $type
+ * @param $action
+ * @param $target
+ * @param $logComment
+ * @param $params
+ * @param $newId int
+ * @return bool
*/
- public static function notifyMove( $timestamp, &$oldTitle, &$newTitle, &$user, $comment, $ip='',
- $overRedir = false ) {
- global $wgRequest;
- if( !$ip ) {
- $ip = wfGetIP();
- if( !$ip ) $ip = '';
- }
-
- $rc = new RecentChange;
- $rc->mAttribs = array(
- 'rc_timestamp' => $timestamp,
- 'rc_cur_time' => $timestamp,
- 'rc_namespace' => $oldTitle->getNamespace(),
- 'rc_title' => $oldTitle->getDBkey(),
- 'rc_type' => $overRedir ? RC_MOVE_OVER_REDIRECT : RC_MOVE,
- 'rc_minor' => 0,
- 'rc_cur_id' => $oldTitle->getArticleID(),
- 'rc_user' => $user->getId(),
- 'rc_user_text' => $user->getName(),
- 'rc_comment' => $comment,
- 'rc_this_oldid' => 0,
- 'rc_last_oldid' => 0,
- 'rc_bot' => $user->isAllowed( 'bot' ) ? $wgRequest->getBool( 'bot' , true ) : 0,
- 'rc_moved_to_ns' => $newTitle->getNamespace(),
- 'rc_moved_to_title' => $newTitle->getDBkey(),
- 'rc_ip' => $ip,
- 'rc_new' => 0, # obsolete
- 'rc_patrolled' => 1,
- 'rc_old_len' => null,
- 'rc_new_len' => null,
- 'rc_deleted' => 0,
- 'rc_logid' => 0, # notifyMove not used anymore
- 'rc_log_type' => null,
- 'rc_log_action' => '',
- 'rc_params' => ''
- );
-
- $rc->mExtra = array(
- 'prefixedDBkey' => $oldTitle->getPrefixedDBkey(),
- 'lastTimestamp' => 0,
- 'prefixedMoveTo' => $newTitle->getPrefixedDBkey()
- );
- $rc->save();
- }
-
- public static function notifyMoveToNew( $timestamp, &$oldTitle, &$newTitle, &$user, $comment, $ip='' ) {
- RecentChange::notifyMove( $timestamp, $oldTitle, $newTitle, $user, $comment, $ip, false );
- }
-
- public static function notifyMoveOverRedirect( $timestamp, &$oldTitle, &$newTitle, &$user, $comment, $ip='' ) {
- RecentChange::notifyMove( $timestamp, $oldTitle, $newTitle, $user, $comment, $ip, true );
- }
-
- public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip='', $type,
+ public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip='', $type,
$action, $target, $logComment, $params, $newId=0 )
{
global $wgLogRestrictions;
@@ -565,7 +540,7 @@ class RecentChange {
$type, $action, $target, $logComment, $params, $newId=0 ) {
global $wgRequest;
if( !$ip ) {
- $ip = wfGetIP();
+ $ip = $wgRequest->getIP();
if( !$ip ) {
$ip = '';
}
@@ -607,14 +582,22 @@ class RecentChange {
return $rc;
}
- # Initialises the members of this object from a mysql row object
+ /**
+ * Initialises the members of this object from a mysql row object
+ *
+ * @param $row
+ */
public function loadFromRow( $row ) {
$this->mAttribs = get_object_vars( $row );
$this->mAttribs['rc_timestamp'] = wfTimestamp(TS_MW, $this->mAttribs['rc_timestamp']);
$this->mAttribs['rc_deleted'] = $row->rc_deleted; // MUST be set
}
- # Makes a pseudo-RC entry from a cur row
+ /**
+ * Makes a pseudo-RC entry from a cur row
+ *
+ * @param $row
+ */
public function loadFromCurRow( $row ) {
$this->mAttribs = array(
'rc_timestamp' => wfTimestamp(TS_MW, $row->rev_timestamp),
@@ -656,6 +639,9 @@ class RecentChange {
return isset( $this->mAttribs[$name] ) ? $this->mAttribs[$name] : null;
}
+ /**
+ * @return array
+ */
public function getAttributes() {
return $this->mAttribs;
}
@@ -663,6 +649,8 @@ class RecentChange {
/**
* Gets the end part of the diff URL associated with this object
* Blank if no diff link should be displayed
+ * @param $forceCur
+ * @return string
*/
public function diffLinkTrail( $forceCur ) {
if( $this->mAttribs['rc_type'] == RC_EDIT ) {
@@ -679,6 +667,9 @@ class RecentChange {
return $trail;
}
+ /**
+ * @return string
+ */
public function getIRCLine() {
global $wgUseRCPatrol, $wgUseNPPatrol, $wgRC2UDPInterwikiPrefix, $wgLocalInterwiki,
$wgCanonicalServer, $wgScript;
@@ -708,8 +699,8 @@ class RecentChange {
$url .= $query;
}
- if( isset( $this->mExtra['oldSize'] ) && isset( $this->mExtra['newSize'] ) ) {
- $szdiff = $this->mExtra['newSize'] - $this->mExtra['oldSize'];
+ if( $this->mAttribs['rc_old_len'] !== null && $this->mAttribs['rc_new_len'] !== null ) {
+ $szdiff = $this->mAttribs['rc_new_len'] - $this->mAttribs['rc_old_len'];
if($szdiff < -500) {
$szdiff = "\002$szdiff\002";
} elseif($szdiff >= 0) {
@@ -747,18 +738,21 @@ class RecentChange {
} else {
$titleString = "\00314[[\00307$title\00314]]";
}
-
+
# see http://www.irssi.org/documentation/formats for some colour codes. prefix is \003,
# no colour (\003) switches back to the term default
$fullString = "$titleString\0034 $flag\00310 " .
- "\00302$url\003 \0035*\003 \00303$user\003 \0035*\003 $szdiff \00310$comment\003\n";
-
+ "\00302$url\003 \0035*\003 \00303$user\003 \0035*\003 $szdiff \00310$comment\003\n";
+
return $fullString;
}
/**
* Returns the change size (HTML).
* The lengths can be given optionally.
+ * @param $old int
+ * @param $new int
+ * @return string
*/
public function getCharacterDifference( $old = 0, $new = 0 ) {
if( $old === 0 ) {
diff --git a/includes/RequestContext.php b/includes/RequestContext.php
deleted file mode 100644
index 441a9aec..00000000
--- a/includes/RequestContext.php
+++ /dev/null
@@ -1,424 +0,0 @@
-<?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 Language object
- *
- * @return Language
- */
- public function getLanguage();
-
- /**
- * 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;
- }
-
- /**
- * Get the Language object
- *
- * @return Language
- */
- public function getLanguage() {
- return $this->getLang();
- }
-
- /**
- * 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 Language object
- *
- * @return Language
- */
- public function getLanguage() {
- 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 b5d776bd..445211c9 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -4,6 +4,23 @@
* @todo document
*/
class Revision {
+ protected $mId;
+ protected $mPage;
+ protected $mUserText;
+ protected $mOrigUserText;
+ protected $mUser;
+ protected $mMinorEdit;
+ protected $mTimestamp;
+ protected $mDeleted;
+ protected $mSize;
+ protected $mSha1;
+ protected $mParentId;
+ protected $mComment;
+ protected $mText;
+ protected $mTextRow;
+ protected $mTitle;
+ protected $mCurrent;
+
const DELETED_TEXT = 1;
const DELETED_COMMENT = 2;
const DELETED_USER = 4;
@@ -23,9 +40,7 @@ class Revision {
* @return Revision or null
*/
public static function newFromId( $id ) {
- return Revision::newFromConds(
- array( 'page_id=rev_page',
- 'rev_id' => intval( $id ) ) );
+ return Revision::newFromConds( array( 'rev_id' => intval( $id ) ) );
}
/**
@@ -57,7 +72,6 @@ class Revision {
// Use a join to get the latest revision
$conds[] = 'rev_id=page_latest';
}
- $conds[] = 'page_id=rev_page';
return Revision::newFromConds( $conds );
}
@@ -85,7 +99,6 @@ class Revision {
} else {
$conds[] = 'rev_id = page_latest';
}
- $conds[] = 'page_id=rev_page';
return Revision::newFromConds( $conds );
}
@@ -101,7 +114,7 @@ class Revision {
*/
public static function newFromArchiveRow( $row, $overrides = array() ) {
$attribs = $overrides + array(
- 'page' => isset( $row->page_id ) ? $row->page_id : null,
+ 'page' => isset( $row->ar_page_id ) ? $row->ar_page_id : null,
'id' => isset( $row->ar_rev_id ) ? $row->ar_rev_id : null,
'comment' => $row->ar_comment,
'user' => $row->ar_user,
@@ -110,7 +123,9 @@ class Revision {
'minor_edit' => $row->ar_minor_edit,
'text_id' => isset( $row->ar_text_id ) ? $row->ar_text_id : null,
'deleted' => $row->ar_deleted,
- 'len' => $row->ar_len);
+ 'len' => $row->ar_len,
+ 'sha1' => isset( $row->ar_sha1 ) ? $row->ar_sha1 : null,
+ );
if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
// Pre-1.5 ar_text row
$attribs['text'] = self::getRevisionText( $row, 'ar_' );
@@ -122,6 +137,8 @@ class Revision {
}
/**
+ * @since 1.19
+ *
* @param $row
* @return Revision
*/
@@ -138,9 +155,7 @@ class Revision {
* @return Revision or null
*/
public static function loadFromId( $db, $id ) {
- return Revision::loadFromConds( $db,
- array( 'page_id=rev_page',
- 'rev_id' => intval( $id ) ) );
+ return Revision::loadFromConds( $db, array( 'rev_id' => intval( $id ) ) );
}
/**
@@ -154,7 +169,7 @@ class Revision {
* @return Revision or null
*/
public static function loadFromPageId( $db, $pageid, $id = 0 ) {
- $conds = array( 'page_id=rev_page','rev_page' => intval( $pageid ), 'page_id'=>intval( $pageid ) );
+ $conds = array( 'rev_page' => intval( $pageid ), 'page_id' => intval( $pageid ) );
if( $id ) {
$conds['rev_id'] = intval( $id );
} else {
@@ -179,12 +194,11 @@ class Revision {
} else {
$matchId = 'page_latest';
}
- return Revision::loadFromConds(
- $db,
+ return Revision::loadFromConds( $db,
array( "rev_id=$matchId",
- 'page_id=rev_page',
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey() ) );
+ 'page_title' => $title->getDBkey() )
+ );
}
/**
@@ -198,12 +212,11 @@ class Revision {
* @return Revision or null
*/
public static function loadFromTimestamp( $db, $title, $timestamp ) {
- return Revision::loadFromConds(
- $db,
+ return Revision::loadFromConds( $db,
array( 'rev_timestamp' => $db->timestamp( $timestamp ),
- 'page_id=rev_page',
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey() ) );
+ 'page_title' => $title->getDBkey() )
+ );
}
/**
@@ -214,12 +227,12 @@ class Revision {
*/
public static function newFromConds( $conditions ) {
$db = wfGetDB( DB_SLAVE );
- $row = Revision::loadFromConds( $db, $conditions );
- if( is_null( $row ) && wfGetLB()->getServerCount() > 1 ) {
+ $rev = Revision::loadFromConds( $db, $conditions );
+ if( is_null( $rev ) && wfGetLB()->getServerCount() > 1 ) {
$dbw = wfGetDB( DB_MASTER );
- $row = Revision::loadFromConds( $dbw, $conditions );
+ $rev = Revision::loadFromConds( $dbw, $conditions );
}
- return $row;
+ return $rev;
}
/**
@@ -234,7 +247,6 @@ class Revision {
$res = Revision::fetchFromConds( $db, $conditions );
if( $res ) {
$row = $res->fetchObject();
- $res->free();
if( $row ) {
$ret = new Revision( $row );
return $ret;
@@ -257,8 +269,8 @@ class Revision {
wfGetDB( DB_SLAVE ),
array( 'rev_id=page_latest',
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBkey(),
- 'page_id=rev_page' ) );
+ 'page_title' => $title->getDBkey() )
+ );
}
/**
@@ -271,16 +283,39 @@ class Revision {
* @return ResultWrapper
*/
private static function fetchFromConds( $db, $conditions ) {
- $fields = self::selectFields();
- $fields[] = 'page_namespace';
- $fields[] = 'page_title';
- $fields[] = 'page_latest';
+ $fields = array_merge(
+ self::selectFields(),
+ self::selectPageFields(),
+ self::selectUserFields()
+ );
return $db->select(
- array( 'page', 'revision' ),
+ array( 'revision', 'page', 'user' ),
$fields,
$conditions,
__METHOD__,
- array( 'LIMIT' => 1 ) );
+ array( 'LIMIT' => 1 ),
+ array( 'page' => self::pageJoinCond(), 'user' => self::userJoinCond() )
+ );
+ }
+
+ /**
+ * Return the value of a select() JOIN conds array for the user table.
+ * This will get user table rows for logged-in users.
+ * @since 1.19
+ * @return Array
+ */
+ public static function userJoinCond() {
+ return array( 'LEFT JOIN', array( 'rev_user != 0', 'user_id = rev_user' ) );
+ }
+
+ /**
+ * Return the value of a select() page conds array for the paeg table.
+ * This will assure that the revision(s) are not orphaned from live pages.
+ * @since 1.19
+ * @return Array
+ */
+ public static function pageJoinCond() {
+ return array( 'INNER JOIN', array( 'page_id = rev_page' ) );
}
/**
@@ -294,12 +329,13 @@ class Revision {
'rev_text_id',
'rev_timestamp',
'rev_comment',
- 'rev_user_text,'.
+ 'rev_user_text',
'rev_user',
'rev_minor_edit',
'rev_deleted',
'rev_len',
- 'rev_parent_id'
+ 'rev_parent_id',
+ 'rev_sha1'
);
}
@@ -307,7 +343,7 @@ class Revision {
* Return the list of text fields that should be selected to read the
* revision text
*/
- static function selectTextFields() {
+ public static function selectTextFields() {
return array(
'old_text',
'old_flags'
@@ -317,15 +353,23 @@ class Revision {
/**
* Return the list of page fields that should be selected from page table
*/
- static function selectPageFields() {
+ public static function selectPageFields() {
return array(
'page_namespace',
'page_title',
+ 'page_id',
'page_latest'
);
}
/**
+ * Return the list of user fields that should be selected from user table
+ */
+ public static function selectUserFields() {
+ return array( 'user_name' );
+ }
+
+ /**
* Constructor
*
* @param $row Mixed: either a database row or an array
@@ -337,21 +381,28 @@ class Revision {
$this->mPage = intval( $row->rev_page );
$this->mTextId = intval( $row->rev_text_id );
$this->mComment = $row->rev_comment;
- $this->mUserText = $row->rev_user_text;
$this->mUser = intval( $row->rev_user );
$this->mMinorEdit = intval( $row->rev_minor_edit );
$this->mTimestamp = $row->rev_timestamp;
$this->mDeleted = intval( $row->rev_deleted );
- if( !isset( $row->rev_parent_id ) )
- $this->mParentId = is_null($row->rev_parent_id) ? null : 0;
- else
+ if( !isset( $row->rev_parent_id ) ) {
+ $this->mParentId = is_null( $row->rev_parent_id ) ? null : 0;
+ } else {
$this->mParentId = intval( $row->rev_parent_id );
+ }
- if( !isset( $row->rev_len ) || is_null( $row->rev_len ) )
+ if( !isset( $row->rev_len ) || is_null( $row->rev_len ) ) {
$this->mSize = null;
- else
+ } else {
$this->mSize = intval( $row->rev_len );
+ }
+
+ if ( !isset( $row->rev_sha1 ) ) {
+ $this->mSha1 = null;
+ } else {
+ $this->mSha1 = $row->rev_sha1;
+ }
if( isset( $row->page_latest ) ) {
$this->mCurrent = ( $row->rev_id == $row->page_latest );
@@ -369,9 +420,18 @@ class Revision {
// 'text' table row entry will be lazy-loaded
$this->mTextRow = null;
}
+
+ // Use user_name for users and rev_user_text for IPs...
+ $this->mUserText = null; // lazy load if left null
+ if ( $this->mUser == 0 ) {
+ $this->mUserText = $row->rev_user_text; // IP user
+ } elseif ( isset( $row->user_name ) ) {
+ $this->mUserText = $row->user_name; // logged-in user
+ }
+ $this->mOrigUserText = $row->rev_user_text;
} elseif( is_array( $row ) ) {
// Build a new revision to be saved...
- global $wgUser;
+ global $wgUser; // ugh
$this->mId = isset( $row['id'] ) ? intval( $row['id'] ) : null;
$this->mPage = isset( $row['page'] ) ? intval( $row['page'] ) : null;
@@ -379,10 +439,11 @@ class Revision {
$this->mUserText = isset( $row['user_text'] ) ? strval( $row['user_text'] ) : $wgUser->getName();
$this->mUser = isset( $row['user'] ) ? intval( $row['user'] ) : $wgUser->getId();
$this->mMinorEdit = isset( $row['minor_edit'] ) ? intval( $row['minor_edit'] ) : 0;
- $this->mTimestamp = isset( $row['timestamp'] ) ? strval( $row['timestamp'] ) : wfTimestamp( TS_MW );
+ $this->mTimestamp = isset( $row['timestamp'] ) ? strval( $row['timestamp'] ) : wfTimestampNow();
$this->mDeleted = isset( $row['deleted'] ) ? intval( $row['deleted'] ) : 0;
$this->mSize = isset( $row['len'] ) ? intval( $row['len'] ) : null;
$this->mParentId = isset( $row['parent_id'] ) ? intval( $row['parent_id'] ) : null;
+ $this->mSha1 = isset( $row['sha1'] ) ? strval( $row['sha1'] ) : null;
// Enforce spacing trimming on supplied text
$this->mComment = isset( $row['comment'] ) ? trim( strval( $row['comment'] ) ) : null;
@@ -391,9 +452,14 @@ class Revision {
$this->mTitle = null; # Load on demand if needed
$this->mCurrent = false;
- # If we still have no len_size, see it we have the text to figure it out
- if ( !$this->mSize )
- $this->mSize = is_null( $this->mText ) ? null : strlen( $this->mText );
+ # If we still have no length, see it we have the text to figure it out
+ if ( !$this->mSize ) {
+ $this->mSize = is_null( $this->mText ) ? null : strlen( $this->mText );
+ }
+ # Same for sha1
+ if ( $this->mSha1 === null ) {
+ $this->mSha1 = is_null( $this->mText ) ? null : self::base36Sha1( $this->mText );
+ }
} else {
throw new MWException( 'Revision constructor passed invalid row format.' );
}
@@ -410,6 +476,16 @@ class Revision {
}
/**
+ * Set the revision ID
+ *
+ * @since 1.19
+ * @param $id Integer
+ */
+ public function setId( $id ) {
+ $this->mId = $id;
+ }
+
+ /**
* Get text row ID
*
* @return Integer
@@ -437,6 +513,15 @@ class Revision {
}
/**
+ * Returns the base36 sha1 of the text in this revision, or null if unknown.
+ *
+ * @return String
+ */
+ public function getSha1() {
+ return $this->mSha1;
+ }
+
+ /**
* Returns the title of the page associated with this entry.
*
* @return Title
@@ -448,13 +533,12 @@ class Revision {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
array( 'page', 'revision' ),
- array( 'page_namespace', 'page_title' ),
+ self::selectPageFields(),
array( 'page_id=rev_page',
'rev_id' => $this->mId ),
- 'Revision::getTitle' );
- if( $row ) {
- $this->mTitle = Title::makeTitle( $row->page_namespace,
- $row->page_title );
+ __METHOD__ );
+ if ( $row ) {
+ $this->mTitle = Title::newFromRow( $row );
}
return $this->mTitle;
}
@@ -486,14 +570,14 @@ class Revision {
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to $wgUser
* Revision::RAW get the ID regardless of permissions
- *
- *
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return Integer
*/
- public function getUser( $audience = self::FOR_PUBLIC ) {
+ public function getUser( $audience = self::FOR_PUBLIC, User $user = null ) {
if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
return 0;
- } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER ) ) {
+ } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
return 0;
} else {
return $this->mUser;
@@ -518,16 +602,17 @@ class Revision {
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to $wgUser
* Revision::RAW get the text regardless of permissions
- *
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return string
*/
- public function getUserText( $audience = self::FOR_PUBLIC ) {
+ public function getUserText( $audience = self::FOR_PUBLIC, User $user = null ) {
if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_USER ) ) {
return '';
- } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER ) ) {
+ } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_USER, $user ) ) {
return '';
} else {
- return $this->mUserText;
+ return $this->getRawUserText();
}
}
@@ -537,6 +622,14 @@ class Revision {
* @return String
*/
public function getRawUserText() {
+ if ( $this->mUserText === null ) {
+ $this->mUserText = User::whoIs( $this->mUser ); // load on demand
+ if ( $this->mUserText === false ) {
+ # This shouldn't happen, but it can if the wiki was recovered
+ # via importing revs and there is no user table entry yet.
+ $this->mUserText = $this->mOrigUserText;
+ }
+ }
return $this->mUserText;
}
@@ -549,13 +642,14 @@ class Revision {
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to $wgUser
* Revision::RAW get the text regardless of permissions
- *
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return String
*/
- function getComment( $audience = self::FOR_PUBLIC ) {
+ function getComment( $audience = self::FOR_PUBLIC, User $user = null ) {
if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_COMMENT ) ) {
return '';
- } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT ) ) {
+ } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_COMMENT, $user ) ) {
return '';
} else {
return $this->mComment;
@@ -600,7 +694,7 @@ class Revision {
}
/**
- * int $field one of DELETED_* bitfield constants
+ * @param $field int one of DELETED_* bitfield constants
*
* @return Boolean
*/
@@ -610,6 +704,8 @@ class Revision {
/**
* Get the deletion bitfield of the revision
+ *
+ * @return int
*/
public function getVisibility() {
return (int)$this->mDeleted;
@@ -624,13 +720,14 @@ class Revision {
* Revision::FOR_PUBLIC to be displayed to all users
* Revision::FOR_THIS_USER to be displayed to $wgUser
* Revision::RAW get the text regardless of permissions
- *
+ * @param $user User object to check for, only if FOR_THIS_USER is passed
+ * to the $audience parameter
* @return String
*/
- public function getText( $audience = self::FOR_PUBLIC ) {
+ public function getText( $audience = self::FOR_PUBLIC, User $user = null ) {
if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) {
return '';
- } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT ) ) {
+ } elseif( $audience == self::FOR_THIS_USER && !$this->userCan( self::DELETED_TEXT, $user ) ) {
return '';
} else {
return $this->getRawText();
@@ -644,7 +741,7 @@ class Revision {
* @return String
*/
public function revText() {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.17' );
return $this->getText( self::FOR_THIS_USER );
}
@@ -894,8 +991,12 @@ class Revision {
'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
'rev_deleted' => $this->mDeleted,
'rev_len' => $this->mSize,
- 'rev_parent_id' => is_null($this->mParentId) ?
- $this->getPreviousRevisionId( $dbw ) : $this->mParentId
+ 'rev_parent_id' => is_null( $this->mParentId )
+ ? $this->getPreviousRevisionId( $dbw )
+ : $this->mParentId,
+ 'rev_sha1' => is_null( $this->mSha1 )
+ ? Revision::base36Sha1( $this->mText )
+ : $this->mSha1
), __METHOD__
);
@@ -908,6 +1009,15 @@ class Revision {
}
/**
+ * Get the base 36 SHA-1 value for a string of text
+ * @param $text String
+ * @return String
+ */
+ public static function base36Sha1( $text ) {
+ return wfBaseConvert( sha1( $text ), 16, 36, 31 );
+ }
+
+ /**
* Lazy-load the revision's text.
* Currently hardcoded to the 'text' table storage engine.
*
@@ -986,7 +1096,7 @@ class Revision {
$current = $dbw->selectRow(
array( 'page', 'revision' ),
- array( 'page_latest', 'rev_text_id', 'rev_len' ),
+ array( 'page_latest', 'rev_text_id', 'rev_len', 'rev_sha1' ),
array(
'page_id' => $pageId,
'page_latest=rev_id',
@@ -1000,7 +1110,8 @@ class Revision {
'minor_edit' => $minor,
'text_id' => $current->rev_text_id,
'parent_id' => $current->page_latest,
- 'len' => $current->rev_len
+ 'len' => $current->rev_len,
+ 'sha1' => $current->rev_sha1
) );
} else {
$revision = null;
@@ -1017,10 +1128,11 @@ class Revision {
* @param $field Integer:one of self::DELETED_TEXT,
* self::DELETED_COMMENT,
* self::DELETED_USER
+ * @param $user User object to check, or null to use $wgUser
* @return Boolean
*/
- public function userCan( $field ) {
- return self::userCanBitfield( $this->mDeleted, $field );
+ public function userCan( $field, User $user = null ) {
+ return self::userCanBitfield( $this->mDeleted, $field, $user );
}
/**
@@ -1032,11 +1144,11 @@ class Revision {
* @param $field Integer: one of self::DELETED_TEXT = File::DELETED_FILE,
* self::DELETED_COMMENT = File::DELETED_COMMENT,
* self::DELETED_USER = File::DELETED_USER
+ * @param $user User object to check, or null to use $wgUser
* @return Boolean
*/
- public static function userCanBitfield( $bitfield, $field ) {
+ public static function userCanBitfield( $bitfield, $field, User $user = null ) {
if( $bitfield & $field ) { // aspect is deleted
- global $wgUser;
if ( $bitfield & self::DELETED_RESTRICTED ) {
$permission = 'suppressrevision';
} elseif ( $field & self::DELETED_TEXT ) {
@@ -1045,7 +1157,11 @@ class Revision {
$permission = 'deletedhistory';
}
wfDebug( "Checking for $permission due to $field match on $bitfield\n" );
- return $wgUser->isAllowed( $permission );
+ if ( $user === null ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
+ return $user->isAllowed( $permission );
} else {
return true;
}
@@ -1106,11 +1222,3 @@ class Revision {
return 0;
}
}
-
-/**
- * Aliases for backwards compatibility with 1.6
- */
-define( 'MW_REV_DELETED_TEXT', Revision::DELETED_TEXT );
-define( 'MW_REV_DELETED_COMMENT', Revision::DELETED_COMMENT );
-define( 'MW_REV_DELETED_USER', Revision::DELETED_USER );
-define( 'MW_REV_DELETED_RESTRICTED', Revision::DELETED_RESTRICTED );
diff --git a/includes/RevisionList.php b/includes/RevisionList.php
index ae067ead..814e2dfa 100644
--- a/includes/RevisionList.php
+++ b/includes/RevisionList.php
@@ -2,15 +2,11 @@
/**
* List for revision table items for a single page
*/
-abstract class Rev_List {
+abstract class RevisionListBase extends ContextSource {
/**
* @var Title
*/
var $title;
- /**
- * @var IContextSource
- */
- var $context;
var $ids, $res, $current;
@@ -20,7 +16,7 @@ abstract class Rev_List {
* @param $title Title
*/
function __construct( IContextSource $context, Title $title ) {
- $this->context = $context;
+ $this->setContext( $context );
$this->title = $title;
}
@@ -104,38 +100,20 @@ abstract class Rev_List {
* @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 */
+abstract class RevisionItemBase {
+ /** The parent RevisionListBase */
var $list;
/** The DB result row */
var $row;
/**
- * @param $list Rev_List
+ * @param $list RevisionListBase
* @param $row DB result row
*/
public function __construct( $list, $row ) {
@@ -184,19 +162,19 @@ abstract class Rev_Item {
}
/**
- * Get the date, formatted with $wgLang
+ * Get the date, formatted in user's languae
*/
public function formatDate() {
- global $wgLang;
- return $wgLang->date( $this->getTimestamp() );
+ return $this->list->getLanguage()->userDate( $this->getTimestamp(),
+ $this->list->getUser() );
}
/**
- * Get the time, formatted with $wgLang
+ * Get the time, formatted in user's languae
*/
public function formatTime() {
- global $wgLang;
- return $wgLang->time( $this->getTimestamp() );
+ return $this->list->getLanguage()->userTime( $this->getTimestamp(),
+ $this->list->getUser() );
}
/**
@@ -240,7 +218,7 @@ abstract class Rev_Item {
abstract public function getHTML();
}
-class RevisionList extends Rev_List {
+class RevisionList extends RevisionListBase {
public function getType() {
return 'revision';
}
@@ -250,19 +228,19 @@ class RevisionList extends Rev_List {
* @return mixed
*/
public function doQuery( $db ) {
- $conds = array(
- 'rev_page' => $this->title->getArticleID(),
- 'rev_page = page_id'
- );
+ $conds = array( 'rev_page' => $this->title->getArticleID() );
if ( $this->ids !== null ) {
$conds['rev_id'] = array_map( 'intval', $this->ids );
}
return $db->select(
- array( 'revision', 'page' ),
- '*',
+ array( 'revision', 'page', 'user' ),
+ array_merge( Revision::selectFields(), Revision::selectUserFields() ),
$conds,
__METHOD__,
- array( 'ORDER BY' => 'rev_id DESC' )
+ array( 'ORDER BY' => 'rev_id DESC' ),
+ array(
+ 'page' => Revision::pageJoinCond(),
+ 'user' => Revision::userJoinCond() )
);
}
@@ -274,7 +252,7 @@ class RevisionList extends Rev_List {
/**
* Item class for a live revision table row
*/
-class RevisionItem extends Rev_Item {
+class RevisionItem extends RevisionItemBase {
var $revision, $context;
public function __construct( $list, $row ) {
@@ -296,15 +274,15 @@ class RevisionItem extends Rev_Item {
}
public function getAuthorNameField() {
- return 'rev_user_text';
+ return 'user_name'; // see Revision::selectUserFields()
}
public function canView() {
- return $this->revision->userCan( Revision::DELETED_RESTRICTED );
+ return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->context->getUser() );
}
public function canViewContent() {
- return $this->revision->userCan( Revision::DELETED_TEXT );
+ return $this->revision->userCan( Revision::DELETED_TEXT, $this->context->getUser() );
}
public function isDeleted() {
@@ -316,7 +294,7 @@ class RevisionItem extends Rev_Item {
* Overridden by RevDel_ArchiveItem.
*/
protected function getRevisionLink() {
- $date = $this->list->getLang()->timeanddate( $this->revision->getTimestamp(), true );
+ $date = $this->list->getLanguage()->timeanddate( $this->revision->getTimestamp(), true );
if ( $this->isDeleted() && !$this->canViewContent() ) {
return $date;
}
diff --git a/includes/Sanitizer.php b/includes/Sanitizer.php
index d0e46f91..196abd9f 100644
--- a/includes/Sanitizer.php
+++ b/includes/Sanitizer.php
@@ -33,12 +33,20 @@ class Sanitizer {
* Regular expression to match various types of character references in
* Sanitizer::normalizeCharReferences and Sanitizer::decodeCharReferences
*/
- const CHAR_REFS_REGEX =
+ const CHAR_REFS_REGEX =
'/&([A-Za-z0-9\x80-\xff]+);
|&\#([0-9]+);
|&\#[xX]([0-9A-Fa-f]+);
|(&)/x';
+ /**
+ * Blacklist for evil uris like javascript:
+ * WARNING: DO NOT use this in any place that actually requires blacklisting
+ * for security reasons. There are NUMEROUS[1] ways to bypass blacklisting, the
+ * only way to be secure from javascript: uri based xss vectors is to whitelist
+ * things that you know are safe and deny everything else.
+ * [1]: http://ha.ckers.org/xss.html
+ */
const EVIL_URI_PATTERN = '!(^|\s|\*/\s*)(javascript|vbscript)([^\w]|$)!i';
const XMLNS_ATTRIBUTE_PATTERN = "/^xmlns:[:A-Z_a-z-.0-9]+$/";
@@ -327,7 +335,7 @@ class Sanitizer {
$attribFirst = '[:A-Z_a-z0-9]';
$attrib = '[:A-Z_a-z-.0-9]';
$space = '[\x09\x0a\x0d\x20]';
- self::$attribsRegex =
+ self::$attribsRegex =
"/(?:^|$space)({$attribFirst}{$attrib}*)
($space*=$space*
(?:
@@ -449,16 +457,26 @@ class Sanitizer {
# and see if we find a match below them
$optstack = array();
array_push( $optstack, $ot );
- $ot = @array_pop( $tagstack );
+ wfSuppressWarnings();
+ $ot = array_pop( $tagstack );
+ wfRestoreWarnings();
while ( $ot != $t && isset( $htmlsingleallowed[$ot] ) ) {
array_push( $optstack, $ot );
- $ot = @array_pop( $tagstack );
+ wfSuppressWarnings();
+ $ot = array_pop( $tagstack );
+ wfRestoreWarnings();
}
if ( $t != $ot ) {
# No match. Push the optional elements back again
$badtag = true;
- while ( $ot = @array_pop( $optstack ) ) {
+ wfSuppressWarnings();
+ $ot = array_pop( $optstack );
+ wfRestoreWarnings();
+ while ( $ot ) {
array_push( $tagstack, $ot );
+ wfSuppressWarnings();
+ $ot = array_pop( $optstack );
+ wfRestoreWarnings();
}
}
} else {
@@ -595,6 +613,102 @@ class Sanitizer {
}
/**
+ * Take an array of attribute names and values and fix some deprecated values
+ * for the given element type.
+ * This does not validate properties, so you should ensure that you call
+ * validateTagAttributes AFTER this to ensure that the resulting style rule
+ * this may add is safe.
+ *
+ * - Converts most presentational attributes like align into inline css
+ *
+ * @param $attribs Array
+ * @param $element String
+ * @return Array
+ */
+ static function fixDeprecatedAttributes( $attribs, $element ) {
+ global $wgHtml5, $wgCleanupPresentationalAttributes;
+
+ // presentational attributes were removed from html5, we can leave them
+ // in when html5 is turned off
+ if ( !$wgHtml5 || !$wgCleanupPresentationalAttributes ) {
+ return $attribs;
+ }
+
+ $table = array( 'table' );
+ $cells = array( 'td', 'th' );
+ $colls = array( 'col', 'colgroup' );
+ $tblocks = array( 'tbody', 'tfoot', 'thead' );
+ $h = array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' );
+
+ $presentationalAttribs = array(
+ 'align' => array( 'text-align', array_merge( array( 'caption', 'hr', 'div', 'p', 'tr' ), $table, $cells, $colls, $tblocks, $h ) ),
+ 'clear' => array( 'clear', array( 'br' ) ),
+ 'height' => array( 'height', $cells ),
+ 'nowrap' => array( 'white-space', $cells ),
+ 'size' => array( 'height', array( 'hr' ) ),
+ 'type' => array( 'list-style-type', array( 'li', 'ol', 'ul' ) ),
+ 'valign' => array( 'vertical-align', array_merge( $cells, $colls, $tblocks ) ),
+ 'width' => array( 'width', array_merge( array( 'hr', 'pre' ), $table, $cells, $colls ) ),
+ );
+
+ // Ensure that any upper case or mixed case attributes are converted to lowercase
+ foreach ( $attribs as $attribute => $value ) {
+ if ( $attribute !== strtolower( $attribute ) && array_key_exists( strtolower( $attribute ), $presentationalAttribs ) ) {
+ $attribs[strtolower( $attribute )] = $value;
+ unset( $attribs[$attribute] );
+ }
+ }
+
+ $style = "";
+ foreach ( $presentationalAttribs as $attribute => $info ) {
+ list( $property, $elements ) = $info;
+
+ // Skip if this attribute is not relevant to this element
+ if ( !in_array( $element, $elements ) ) {
+ continue;
+ }
+
+ // Skip if the attribute is not used
+ if ( !array_key_exists( $attribute, $attribs ) ) {
+ continue;
+ }
+
+ $value = $attribs[$attribute];
+
+ // For nowrap the value should be nowrap instead of whatever text is in the value
+ if ( $attribute === 'nowrap' ) {
+ $value = 'nowrap';
+ }
+
+ // clear="all" is clear: both; in css
+ if ( $attribute === 'clear' && strtolower( $value ) === 'all' ) {
+ $value = 'both';
+ }
+
+ // Size based properties should have px applied to them if they have no unit
+ if ( in_array( $attribute, array( 'height', 'width', 'size' ) ) ) {
+ if ( preg_match( '/^[\d.]+$/', $value ) ) {
+ $value = "{$value}px";
+ }
+ }
+
+ $style .= " $property: $value;";
+
+ unset( $attribs[$attribute] );
+ }
+
+ if ( $style ) {
+ // Prepend our style rules so that they can be overridden by user css
+ if ( isset($attribs['style']) ) {
+ $style .= " " . $attribs['style'];
+ }
+ $attribs['style'] = trim($style);
+ }
+
+ return $attribs;
+ }
+
+ /**
* Take an array of attribute names and values and normalize or discard
* illegal values for the given element type.
*
@@ -662,7 +776,7 @@ class Sanitizer {
}
//RDFa and microdata properties allow URLs, URIs and/or CURIs. check them for sanity
- if ( $attribute === 'rel' || $attribute === 'rev' ||
+ if ( $attribute === 'rel' || $attribute === 'rev' ||
$attribute === 'about' || $attribute === 'property' || $attribute === 'resource' || #RDFa
$attribute === 'datatype' || $attribute === 'typeof' || #RDFa
$attribute === 'itemid' || $attribute === 'itemprop' || $attribute === 'itemref' || #HTML5 microdata
@@ -670,7 +784,7 @@ class Sanitizer {
//Paranoia. Allow "simple" values but suppress javascript
if ( preg_match( self::EVIL_URI_PATTERN, $value ) ) {
- continue;
+ continue;
}
}
@@ -735,7 +849,7 @@ class Sanitizer {
* returned string may contain character references given certain
* clever input strings. These character references must
* be escaped before the return value is embedded in HTML.
- *
+ *
* @param $value String
* @return String
*/
@@ -757,7 +871,7 @@ class Sanitizer {
$space = '[\\x20\\t\\r\\n\\f]';
$nl = '(?:\\n|\\r\\n|\\r|\\f)';
$backslash = '\\\\';
- $decodeRegex = "/ $backslash
+ $decodeRegex = "/ $backslash
(?:
($nl) | # 1. Line continuation
([0-9A-Fa-f]{1,6})$space? | # 2. character number
@@ -767,7 +881,7 @@ class Sanitizer {
}
$value = preg_replace_callback( $decodeRegex,
array( __CLASS__, 'cssDecodeCallback' ), $value );
-
+
// Remove any comments; IE gets token splitting wrong
// This must be done AFTER decoding character references and
// escape sequences, because those steps can introduce comments
@@ -841,8 +955,9 @@ class Sanitizer {
return '';
}
- $stripped = Sanitizer::validateTagAttributes(
- Sanitizer::decodeTagAttributes( $text ), $element );
+ $decoded = Sanitizer::decodeTagAttributes( $text );
+ $decoded = Sanitizer::fixDeprecatedAttributes( $decoded, $element );
+ $stripped = Sanitizer::validateTagAttributes( $decoded, $element );
$attribs = array();
foreach( $stripped as $attribute => $value ) {
@@ -1341,7 +1456,7 @@ class Sanitizer {
if ( $wgAllowRdfaAttributes ) {
#RDFa attributes as specified in section 9 of http://www.w3.org/TR/2008/REC-rdfa-syntax-20081014
$common = array_merge( $common, array(
- 'about', 'property', 'resource', 'datatype', 'typeof',
+ 'about', 'property', 'resource', 'datatype', 'typeof',
) );
}
@@ -1458,7 +1573,7 @@ class Sanitizer {
'th' => array_merge( $common, $tablecell, $tablealign ),
# 12.2 # NOTE: <a> is not allowed directly, but the attrib whitelist is used from the Parser object
- 'a' => array_merge( $common, array( 'href', 'rel', 'rev' ) ), # rel/rev esp. for RDFa
+ 'a' => array_merge( $common, array( 'href', 'rel', 'rev' ) ), # rel/rev esp. for RDFa
# 13.2
# Not usually allowed, but may be used for extension-style hooks
@@ -1549,7 +1664,7 @@ class Sanitizer {
$url = Sanitizer::decodeCharReferences( $url );
# Escape any control characters introduced by the above step
- $url = preg_replace_callback( '/[\][<>"\\x00-\\x20\\x7F\|]/',
+ $url = preg_replace_callback( '/[\][<>"\\x00-\\x20\\x7F\|]/',
array( __CLASS__, 'cleanUrlCallback' ), $url );
# Validate hostname portion
@@ -1573,7 +1688,7 @@ class Sanitizer {
\xe1\xa0\x8d| # 180d MONGOLIAN FREE VARIATION SELECTOR THREE
\xe2\x80\x8c| # 200c ZERO WIDTH NON-JOINER
\xe2\x80\x8d| # 200d ZERO WIDTH JOINER
- [\xef\xb8\x80-\xef\xb8\x8f] # fe00-fe00f VARIATION SELECTOR-1-16
+ [\xef\xb8\x80-\xef\xb8\x8f] # fe00-fe0f VARIATION SELECTOR-1-16
/xuD";
$host = preg_replace( $strip, '', $host );
@@ -1617,6 +1732,8 @@ class Sanitizer {
* to be liberal enough for wide use. Some invalid addresses will still
* pass validation here.
*
+ * @since 1.18
+ *
* @param $addr String E-mail address
* @return Bool
*/
diff --git a/includes/SeleniumWebSettings.php b/includes/SeleniumWebSettings.php
index 56afa929..34d829ca 100644
--- a/includes/SeleniumWebSettings.php
+++ b/includes/SeleniumWebSettings.php
@@ -201,21 +201,3 @@ function switchToTestResources( $testResourceName, $switchDB = true ) {
$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 815d24eb..3955937c 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -115,6 +115,19 @@ $wgNamespaceAliases['Image'] = NS_FILE;
$wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
/**
+ * Initialise $wgLockManagers to include basic FS version
+ */
+$wgLockManagers[] = array(
+ 'name' => 'fsLockManager',
+ 'class' => 'FSLockManager',
+ 'lockDirectory' => "{$wgUploadDirectory}/lockdir",
+);
+$wgLockManagers[] = array(
+ 'name' => 'nullLockManager',
+ 'class' => 'NullLockManager',
+);
+
+/**
* Initialise $wgLocalFileRepo from backwards-compatible settings
*/
if ( !$wgLocalFileRepo ) {
@@ -163,7 +176,7 @@ if ( $wgUseSharedUploads ) {
);
} else {
$wgForeignFileRepos[] = array(
- 'class' => 'FSRepo',
+ 'class' => 'FileRepo',
'name' => 'shared',
'directory' => $wgSharedUploadDirectory,
'url' => $wgSharedUploadPath,
@@ -177,15 +190,33 @@ if ( $wgUseSharedUploads ) {
}
if ( $wgUseInstantCommons ) {
$wgForeignFileRepos[] = array(
- 'class' => 'ForeignAPIRepo',
- 'name' => 'wikimediacommons',
- 'apibase' => 'http://commons.wikimedia.org/w/api.php',
- 'hashLevels' => 2,
- 'fetchDescription' => true,
- 'descriptionCacheExpiry' => 43200,
- 'apiThumbCacheExpiry' => 86400,
+ 'class' => 'ForeignAPIRepo',
+ 'name' => 'wikimediacommons',
+ 'apibase' => WebRequest::detectProtocol() === 'https' ?
+ 'https://commons.wikimedia.org/w/api.php' :
+ 'http://commons.wikimedia.org/w/api.php',
+ 'hashLevels' => 2,
+ 'fetchDescription' => true,
+ 'descriptionCacheExpiry' => 43200,
+ 'apiThumbCacheExpiry' => 86400,
);
}
+/*
+ * Add on default file backend config for file repos.
+ * FileBackendGroup will handle initializing the backends.
+ */
+if ( !isset( $wgLocalFileRepo['backend'] ) ) {
+ $wgLocalFileRepo['backend'] = $wgLocalFileRepo['name'] . '-backend';
+}
+foreach ( $wgForeignFileRepos as &$repo ) {
+ if ( !isset( $repo['directory'] ) && $repo['class'] === 'ForeignAPIRepo' ) {
+ $repo['directory'] = $wgUploadDirectory; // b/c
+ }
+ if ( !isset( $repo['backend'] ) ) {
+ $repo['backend'] = $repo['name'] . '-backend';
+ }
+}
+unset( $repo ); // no global pollution; destroy reference
if ( is_null( $wgEnableAutoRotation ) ) {
// Only enable auto-rotation when the bitmap handler can rotate
@@ -268,8 +299,10 @@ $wgContLanguageCode = $wgLanguageCode;
# Easy to forget to falsify $wgShowIPinHeader for static caches.
# If file cache or squid cache is on, just disable this (DWIMD).
+# Do the same for $wgDebugToolbar.
if ( $wgUseFileCache || $wgUseSquid ) {
$wgShowIPinHeader = false;
+ $wgDebugToolbar = false;
}
# $wgAllowRealName and $wgAllowUserSkin were removed in 1.16
@@ -320,16 +353,21 @@ if ( $wgNewUserLog ) {
$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';
+ # newusers, create, create2, autocreate
+ $wgLogActionsHandlers['newusers/*'] = 'NewUsersLogFormatter';
}
if ( $wgCookieSecure === 'detect' ) {
$wgCookieSecure = ( substr( $wgServer, 0, 6 ) === 'https:' );
}
+// Disable MWDebug for command line mode, this prevents MWDebug from eating up
+// all the memory from logging SQL queries on maintenance scripts
+global $wgCommandLineMode;
+if ( $wgDebugToolbar && !$wgCommandLineMode ) {
+ MWDebug::init();
+}
+
if ( !defined( 'MW_COMPILED' ) ) {
if ( !MWInit::classExists( 'AutoLoader' ) ) {
require_once( "$IP/includes/AutoLoader.php" );
@@ -375,7 +413,6 @@ if( is_null( $wgLocalTZoffset ) ) {
}
# Useful debug output
-global $wgCommandLineMode;
if ( $wgCommandLineMode ) {
$wgRequest = new FauxRequest( array() );
@@ -466,12 +503,6 @@ $wgTitle = null;
$wgDeferredUpdateList = array();
-// 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 . '-globals' );
wfProfileIn( $fname . '-extensions' );
diff --git a/includes/SiteConfiguration.php b/includes/SiteConfiguration.php
index ebdb08fe..8a977fb3 100644
--- a/includes/SiteConfiguration.php
+++ b/includes/SiteConfiguration.php
@@ -23,12 +23,12 @@ class SiteConfiguration {
* Array of domains that are local and can be handled by the same server
*/
public $localVHosts = array();
-
+
/**
* Optional callback to load full configuration data.
*/
public $fullLoadCallback = null;
-
+
/** Whether or not all data has been loaded */
public $fullLoadDone = false;
@@ -205,10 +205,6 @@ class SiteConfiguration {
return $this->wikis;
}
- /** A no-op */
- function initialise() {
- }
-
/**
* Retrieves the value of a given setting, and places it in a variable passed by reference.
* @param $setting String ID of the setting name to retrieve
@@ -308,7 +304,7 @@ class SiteConfiguration {
}
/**
- * Merge params beetween the ones passed to the function and the ones given
+ * Merge params between the ones passed to the function and the ones given
* by self::$siteParamsCallback for backward compatibility
* Values returned by self::getWikiParams() have the priority.
*
diff --git a/includes/SiteStats.php b/includes/SiteStats.php
index d61d4fc7..abb11306 100644
--- a/includes/SiteStats.php
+++ b/includes/SiteStats.php
@@ -5,7 +5,7 @@
*/
class SiteStats {
static $row, $loaded = false;
- static $admins, $jobs;
+ static $jobs;
static $pageCount = array();
static $groupMemberCounts = array();
@@ -207,7 +207,7 @@ class SiteStats {
}
// Now check for underflow/overflow
foreach( array( 'total_views', 'total_edits', 'good_articles',
- 'total_pages', 'users', 'admins', 'images' ) as $member ) {
+ 'total_pages', 'users', 'images' ) as $member ) {
if(
$row->{"ss_$member"} > 2000000000
|| $row->{"ss_$member"} < 0
@@ -220,9 +220,9 @@ class SiteStats {
}
/**
- *
+ * Class for handling updates to the site_stats table
*/
-class SiteStatsUpdate {
+class SiteStatsUpdate implements DeferrableUpdate {
var $mViews, $mEdits, $mGood, $mPages, $mUsers;
@@ -268,9 +268,9 @@ class SiteStatsUpdate {
$sql = "UPDATE $site_stats SET $updates";
# Need a separate transaction because this a global lock
- $dbw->begin();
+ $dbw->begin( __METHOD__ );
$dbw->query( $sql, __METHOD__ );
- $dbw->commit();
+ $dbw->commit( __METHOD__ );
}
}
@@ -407,9 +407,9 @@ class SiteStatsInit {
/**
* Do all updates and commit them. More or less a replacement
- * for the original initStats, but without the calls to wfOut()
+ * for the original initStats, but without output.
*
- * @param $database Boolean or DatabaseBase:
+ * @param $database DatabaseBase|bool
* - Boolean: whether to use the master DB
* - DatabaseBase: database connection to use
* @param $options Array of options, may contain the following values
diff --git a/includes/Skin.php b/includes/Skin.php
index 83e9ee98..430537d4 100644
--- a/includes/Skin.php
+++ b/includes/Skin.php
@@ -22,7 +22,7 @@ abstract class Skin extends ContextSource {
/**
* Fetch the set of available skins.
- * @return array of strings
+ * @return associative array of strings
*/
static function getSkinNames() {
global $wgValidSkinNames;
@@ -55,6 +55,18 @@ abstract class Skin extends ContextSource {
}
return $wgValidSkinNames;
}
+
+ /**
+ * Fetch the skinname messages for available skins.
+ * @return array of strings
+ */
+ static function getSkinNameMessages() {
+ $messages = array();
+ foreach( self::getSkinNames() as $skinKey => $skinName ) {
+ $messages[] = "skinname-$skinKey";
+ }
+ return $messages;
+ }
/**
* Fetch the list of usable skins in regards to $wgSkipSkins.
@@ -156,25 +168,22 @@ abstract class Skin extends ContextSource {
}
}
}
- $skin = new $className;
+ $skin = new $className( $key );
return $skin;
}
- /** @return string path to the skin stylesheet */
- function getStylesheet() {
- return 'common/wikistandard.css';
- }
-
/** @return string skin name */
public function getSkinName() {
return $this->skinname;
}
+ /**
+ * @param $out OutputPage
+ */
function initPage( OutputPage $out ) {
wfProfileIn( __METHOD__ );
$this->preloadExistence();
- $this->setMembers();
wfProfileOut( __METHOD__ );
}
@@ -189,7 +198,7 @@ abstract class Skin extends ContextSource {
$titles = array( $user->getUserPage(), $user->getTalkPage() );
// Other tab link
- if ( $this->getTitle()->getNamespace() == NS_SPECIAL ) {
+ if ( $this->getTitle()->isSpecialPage() ) {
// nothing
} elseif ( $this->getTitle()->isTalkPage() ) {
$titles[] = $this->getTitle()->getSubjectPage();
@@ -203,13 +212,6 @@ abstract class Skin extends ContextSource {
}
/**
- * Set some local variables
- */
- protected function setMembers() {
- $this->userpage = $this->getUser()->getUserPage()->getPrefixedText();
- }
-
- /**
* Get the current revision ID
*
* @return Integer
@@ -282,7 +284,7 @@ abstract class Skin extends ContextSource {
$this->mRelevantUser = User::newFromName( $rootUser, false );
} else {
$user = User::newFromName( $rootUser, false );
- if ( $user->isLoggedIn() ) {
+ if ( $user && $user->isLoggedIn() ) {
$this->mRelevantUser = $user;
}
}
@@ -295,8 +297,12 @@ abstract class Skin extends ContextSource {
* Outputs the HTML generated by other functions.
* @param $out OutputPage
*/
- abstract function outputPage( OutputPage $out );
+ abstract function outputPage( OutputPage $out = null );
+ /**
+ * @param $data array
+ * @return string
+ */
static function makeVariablesScript( $data ) {
if ( $data ) {
return Html::inlineScript(
@@ -308,103 +314,9 @@ abstract class Skin extends ContextSource {
}
/**
- * To make it harder for someone to slip a user a fake
- * user-JavaScript or user-CSS preview, a random token
- * is associated with the login session. If it's not
- * passed back with the preview request, we won't render
- * the code.
- *
- * @param $action String: 'edit', 'submit' etc.
- * @return bool
- */
- public function userCanPreview( $action ) {
- if ( $action != 'submit' ) {
- return false;
- }
- if ( !$this->getRequest()->wasPosted() ) {
- return false;
- }
- if ( !$this->getTitle()->userCanEditCssSubpage() ) {
- return false;
- }
- if ( !$this->getTitle()->userCanEditJsSubpage() ) {
- return false;
- }
-
- return $this->getUser()->matchEditToken(
- $this->getRequest()->getVal( 'wpEditToken' ) );
- }
-
- /**
- * Generated JavaScript action=raw&gen=js
- * 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
- */
- public function generateUserJs( $skinName = null ) {
- 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() {
- return '';
- }
-
- /**
- * @private
- * @todo document
- * @param $out OutputPage
- */
- function setupUserCss( OutputPage $out ) {
- global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs;
-
- wfProfileIn( __METHOD__ );
-
- $this->setupSkinUserCss( $out );
- // Add any extension CSS
- foreach ( $out->getExtStyle() as $url ) {
- $out->addStyle( $url );
- }
-
- // Per-site custom styles
- if ( $wgUseSiteCss ) {
- $out->addModuleStyles( array( 'site', 'noscript' ) );
- if( $this->getUser()->isLoggedIn() ){
- $out->addModuleStyles( 'user.groups' );
- }
- }
-
- // Per-user custom styles
- if ( $wgAllowUserCss ) {
- 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' );
- }
- }
-
- // Per-user preference styles
- if ( $wgAllowUserCssPrefs ) {
- $out->addModuleStyles( 'user.options' );
- }
-
- wfProfileOut( __METHOD__ );
- }
-
- /**
* Make a <script> tag containing global variables
*
+ * @deprecated in 1.19
* @param $unused Unused
* @return string HTML fragment
*/
@@ -449,10 +361,9 @@ abstract class Skin extends ContextSource {
* @return String
*/
function getPageClasses( $title ) {
- global $wgRequest;
$numeric = 'ns-' . $title->getNamespace();
- if ( $title->getNamespace() == NS_SPECIAL ) {
+ if ( $title->isSpecialPage() ) {
$type = 'ns-special';
// bug 23315: provide a class based on the canonical special page name without subpages
list( $canonicalName ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
@@ -468,13 +379,8 @@ abstract class Skin extends ContextSource {
}
$name = Sanitizer::escapeClass( 'page-' . $title->getPrefixedText() );
-
- if ( $wgRequest->getVal('action') ) {
- $action = 'action-' . $wgRequest->getVal('action');
- } else {
- $action = 'action-view';
- }
- return "$numeric $type $name $action";
+
+ return "$numeric $type $name";
}
/**
@@ -497,43 +403,47 @@ abstract class Skin extends ContextSource {
return $wgLogo;
}
+ /**
+ * @return string
+ */
function getCategoryLinks() {
global $wgUseCategoryBrowser;
$out = $this->getOutput();
+ $allCats = $out->getCategoryLinks();
- if ( count( $out->mCategoryLinks ) == 0 ) {
+ if ( !count( $allCats ) ) {
return '';
}
$embed = "<li>";
$pop = "</li>";
- $allCats = $out->getCategoryLinks();
$s = '';
- $colon = wfMsgExt( 'colon-separator', 'escapenoentities' );
+ $colon = $this->msg( 'colon-separator' )->escaped();
if ( !empty( $allCats['normal'] ) ) {
$t = $embed . implode( "{$pop}{$embed}" , $allCats['normal'] ) . $pop;
- $msg = wfMsgExt( 'pagecategories', array( 'parsemag', 'escapenoentities' ), count( $allCats['normal'] ) );
- $s .= '<div id="mw-normal-catlinks">' .
- Linker::link( Title::newFromText( wfMsgForContent( 'pagecategorieslink' ) ), $msg )
+ $msg = $this->msg( 'pagecategories', count( $allCats['normal'] ) )->escaped();
+ $linkPage = wfMessage( 'pagecategorieslink' )->inContentLanguage()->text();
+ $s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
+ Linker::link( Title::newFromText( $linkPage ), $msg )
. $colon . '<ul>' . $t . '</ul>' . '</div>';
}
# Hidden categories
if ( isset( $allCats['hidden'] ) ) {
if ( $this->getUser()->getBoolOption( 'showhiddencats' ) ) {
- $class = 'mw-hidden-cats-user-shown';
+ $class = ' mw-hidden-cats-user-shown';
} elseif ( $this->getTitle()->getNamespace() == NS_CATEGORY ) {
- $class = 'mw-hidden-cats-ns-shown';
+ $class = ' mw-hidden-cats-ns-shown';
} else {
- $class = 'mw-hidden-cats-hidden';
+ $class = ' mw-hidden-cats-hidden';
}
- $s .= "<div id=\"mw-hidden-catlinks\" class=\"$class\">" .
- wfMsgExt( 'hidden-categories', array( 'parsemag', 'escapenoentities' ), count( $allCats['hidden'] ) ) .
+ $s .= "<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" .
+ $this->msg( 'hidden-categories', count( $allCats['hidden'] ) )->escaped() .
$colon . '<ul>' . $embed . implode( "{$pop}{$embed}" , $allCats['hidden'] ) . $pop . '</ul>' .
'</div>';
}
@@ -577,12 +487,15 @@ abstract class Skin extends ContextSource {
# add our current element to the list
$eltitle = Title::newFromText( $element );
- $return .= Linker::link( $eltitle, $eltitle->getText() );
+ $return .= Linker::link( $eltitle, htmlspecialchars( $eltitle->getText() ) );
}
return $return;
}
+ /**
+ * @return string
+ */
function getCategories() {
$out = $this->getOutput();
@@ -645,15 +558,21 @@ abstract class Skin extends ContextSource {
protected function generateDebugHTML() {
global $wgShowDebug;
+ $html = MWDebug::getDebugHTML( $this->getContext() );
+
if ( $wgShowDebug ) {
$listInternals = $this->formatDebugHTML( $this->getOutput()->mDebugtext );
- return "\n<hr />\n<strong>Debug data:</strong><ul id=\"mw-debug-html\">" .
+ $html .= "\n<hr />\n<strong>Debug data:</strong><ul id=\"mw-debug-html\">" .
$listInternals . "</ul>\n";
}
- return '';
+ return $html;
}
+ /**
+ * @param $debugText string
+ * @return string
+ */
private function formatDebugHTML( $debugText ) {
global $wgDebugTimestamps;
@@ -665,7 +584,7 @@ abstract class Skin extends ContextSource {
$pre = '';
if ( $wgDebugTimestamps ) {
$matches = array();
- if ( preg_match( '/^(\d+\.\d+\s{2})/', $line, $matches ) ) {
+ if ( preg_match( '/^(\d+\.\d+ {1,3}\d+.\dM\s{2})/', $line, $matches ) ) {
$pre = $matches[1];
$line = substr( $line, strlen( $pre ) );
}
@@ -706,14 +625,14 @@ abstract class Skin extends ContextSource {
/**
* 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 ) {
+ function bottomScripts() {
// 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 );
+ $bottomScriptText = $this->getOutput()->getBottomScripts();
wfRunHooks( 'SkinAfterBottomScripts', array( $this, &$bottomScriptText ) );
return $bottomScriptText;
@@ -733,9 +652,12 @@ abstract class Skin extends ContextSource {
// oldid not available for non existing pages
$url = htmlspecialchars( $this->getTitle()->getCanonicalURL() );
}
- return wfMsg( 'retrievedfrom', '<a href="' . $url . '">' . $url . '</a>' );
+ return $this->msg( 'retrievedfrom', '<a href="' . $url . '">' . $url . '</a>' )->text();
}
+ /**
+ * @return String
+ */
function getUndeleteLink() {
$action = $this->getRequest()->getVal( 'action', 'view' );
@@ -751,22 +673,20 @@ abstract class Skin extends ContextSource {
$msg = 'viewdeleted';
}
- return wfMsg(
- $msg,
- Linker::link(
+ return $this->msg( $msg )->rawParams(
+ Linker::linkKnown(
SpecialPage::getTitleFor( 'Undelete', $this->getTitle()->getPrefixedDBkey() ),
- wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $this->getLang()->formatNum( $n ) ),
- array(),
- array(),
- array( 'known', 'noclasses' )
- )
- );
+ $this->msg( 'restorelink' )->numParams( $n )->escaped() )
+ )->text();
}
}
return '';
}
+ /**
+ * @return string
+ */
function subPageSubtitle() {
$out = $this->getOutput();
$subpages = '';
@@ -790,18 +710,15 @@ abstract class Skin extends ContextSource {
$linkObj = Title::newFromText( $growinglink );
if ( is_object( $linkObj ) && $linkObj->exists() ) {
- $getlink = $this->link(
+ $getlink = Linker::linkKnown(
$linkObj,
- htmlspecialchars( $display ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ htmlspecialchars( $display )
);
$c++;
if ( $c > 1 ) {
- $subpages .= wfMsgExt( 'pipe-separator', 'escapenoentities' );
+ $subpages .= $this->msg( 'pipe-separator' )->escaped();
} else {
$subpages .= '&lt; ';
}
@@ -828,22 +745,30 @@ abstract class Skin extends ContextSource {
return $wgShowIPinHeader && session_id() != '';
}
+ /**
+ * @return String
+ */
function getSearchLink() {
$searchPage = SpecialPage::getTitleFor( 'Search' );
return $searchPage->getLocalURL();
}
+ /**
+ * @return string
+ */
function escapeSearchLink() {
return htmlspecialchars( $this->getSearchLink() );
}
+ /**
+ * @param $type string
+ * @return string
+ */
function getCopyright( $type = 'detect' ) {
- global $wgRightsPage, $wgRightsUrl, $wgRightsText;
+ global $wgRightsPage, $wgRightsUrl, $wgRightsText, $wgContLang;
if ( $type == 'detect' ) {
- $diff = $this->getRequest()->getVal( 'diff' );
-
- if ( is_null( $diff ) && !$this->isRevisionCurrent() && wfMsgForContent( 'history_copyright' ) !== '-' ) {
+ if ( !$this->isRevisionCurrent() && !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled() ) {
$type = 'history';
} else {
$type = 'normal';
@@ -856,8 +781,6 @@ abstract class Skin extends ContextSource {
$msg = 'copyright';
}
- $out = '';
-
if ( $wgRightsPage ) {
$title = Title::newFromText( $wgRightsPage );
$link = Linker::linkKnown( $title, $wgRightsText );
@@ -867,7 +790,7 @@ abstract class Skin extends ContextSource {
$link = $wgRightsText;
} else {
# Give up now
- return $out;
+ return '';
}
// Allow for site and per-namespace customization of copyright notice.
@@ -875,15 +798,21 @@ abstract class Skin extends ContextSource {
wfRunHooks( 'SkinCopyrightFooter', array( $this->getTitle(), $type, &$msg, &$link, &$forContent ) );
+ $msgObj = $this->msg( $msg )->rawParams( $link );
if ( $forContent ) {
- $out .= wfMsgForContent( $msg, $link );
+ $msg = $msgObj->inContentLanguage()->text();
+ if ( $this->getLanguage()->getCode() !== $wgContLang->getCode() ) {
+ $msg = Html::rawElement( 'span', array( 'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ), $msg );
+ }
+ return $msg;
} else {
- $out .= wfMsg( $msg, $link );
+ return $msgObj->text();
}
-
- return $out;
}
+ /**
+ * @return null|string
+ */
function getCopyrightIcon() {
global $wgRightsUrl, $wgRightsText, $wgRightsIcon, $wgCopyrightIcon;
@@ -918,7 +847,7 @@ abstract class Skin extends ContextSource {
global $wgStylePath;
$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>';
+ $text = '<a href="//www.mediawiki.org/"><img src="' . $url . '" height="31" width="88" alt="Powered by MediaWiki" /></a>';
wfRunHooks( 'SkinGetPoweredBy', array( &$text, $this ) );
return $text;
}
@@ -926,31 +855,35 @@ abstract class Skin extends ContextSource {
/**
* 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() ) {
+ protected function lastModified() {
+ $timestamp = $this->getOutput()->getRevisionTimestamp();
+
+ # No cached timestamp, load it from the database
+ if ( $timestamp === null ) {
$timestamp = Revision::getTimestampFromId( $this->getTitle(), $this->getRevisionId() );
- } else {
- $timestamp = $article->getTimestamp();
}
if ( $timestamp ) {
- $d = $this->getLang()->date( $timestamp, true );
- $t = $this->getLang()->time( $timestamp, true );
- $s = ' ' . wfMsg( 'lastmodifiedat', $d, $t );
+ $d = $this->getLanguage()->userDate( $timestamp, $this->getUser() );
+ $t = $this->getLanguage()->userTime( $timestamp, $this->getUser() );
+ $s = ' ' . $this->msg( 'lastmodifiedat', $d, $t )->text();
} else {
$s = '';
}
if ( wfGetLB()->getLaggedSlaveMode() ) {
- $s .= ' <strong>' . wfMsg( 'laggedslavemode' ) . '</strong>';
+ $s .= ' <strong>' . $this->msg( 'laggedslavemode' )->text() . '</strong>';
}
return $s;
}
+ /**
+ * @param $align string
+ * @return string
+ */
function logoText( $align = '' ) {
if ( $align != '' ) {
$a = " align='{$align}'";
@@ -958,9 +891,9 @@ abstract class Skin extends ContextSource {
$a = '';
}
- $mp = wfMsg( 'mainpage' );
+ $mp = $this->msg( 'mainpage' )->escaped();
$mptitle = Title::newMainPage();
- $url = ( is_object( $mptitle ) ? $mptitle->escapeLocalURL() : '' );
+ $url = ( is_object( $mptitle ) ? htmlspecialchars( $mptitle->getLocalURL() ) : '' );
$logourl = $this->getLogo();
$s = "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
@@ -997,31 +930,33 @@ abstract class Skin extends ContextSource {
* @return string
*/
function mainPageLink() {
- $s = Linker::link(
+ $s = Linker::linkKnown(
Title::newMainPage(),
- wfMsg( 'mainpage' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ $this->msg( 'mainpage' )->escaped()
);
return $s;
}
+ /**
+ * @param $desc
+ * @param $page
+ * @return string
+ */
public function footerLink( $desc, $page ) {
// if the link description has been set to "-" in the default language,
- if ( wfMsgForContent( $desc ) == '-' ) {
+ if ( $this->msg( $desc )->inContentLanguage()->isDisabled() ) {
// then it is disabled, for all languages.
return '';
} else {
// Otherwise, we display the link for the user, described in their
// language (which may or may not be the same as the default language),
// but we make the link target be the one site-wide page.
- $title = Title::newFromText( wfMsgForContent( $page ) );
+ $title = Title::newFromText( $this->msg( $page )->inContentLanguage()->text() );
return Linker::linkKnown(
$title,
- wfMsgExt( $desc, array( 'parsemag', 'escapenoentities' ) )
+ $this->msg( $desc )->escaped()
);
}
}
@@ -1067,8 +1002,16 @@ abstract class Skin extends ContextSource {
return $options;
}
+ /**
+ * @param $id User|int
+ * @return bool
+ */
function showEmailUser( $id ) {
- $targetUser = User::newFromId( $id );
+ if ( $id instanceof User ) {
+ $targetUser = $id;
+ } else {
+ $targetUser = User::newFromId( $id );
+ }
return $this->getUser()->canSendEmail() && # the sending user must have a confirmed email address
$targetUser->canReceiveEmail(); # the target user must have a confirmed email address and allow emails from users
}
@@ -1098,6 +1041,11 @@ abstract class Skin extends ContextSource {
}
/* these are used extensively in SkinTemplate, but also some other places */
+
+ /**
+ * @param $urlaction string
+ * @return String
+ */
static function makeMainPageUrl( $urlaction = '' ) {
$title = Title::newMainPage();
self::checkTitle( $title, '' );
@@ -1105,22 +1053,43 @@ abstract class Skin extends ContextSource {
return $title->getLocalURL( $urlaction );
}
+ /**
+ * @param $name string
+ * @param $urlaction string
+ * @return String
+ */
static function makeSpecialUrl( $name, $urlaction = '' ) {
$title = SpecialPage::getSafeTitleFor( $name );
return $title->getLocalURL( $urlaction );
}
+ /**
+ * @param $name string
+ * @param $subpage string
+ * @param $urlaction string
+ * @return String
+ */
static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) {
$title = SpecialPage::getSafeTitleFor( $name, $subpage );
return $title->getLocalURL( $urlaction );
}
+ /**
+ * @param $name string
+ * @param $urlaction string
+ * @return String
+ */
static function makeI18nUrl( $name, $urlaction = '' ) {
$title = Title::newFromText( wfMsgForContent( $name ) );
self::checkTitle( $title, $name );
return $title->getLocalURL( $urlaction );
}
+ /**
+ * @param $name string
+ * @param $urlaction string
+ * @return String
+ */
static function makeUrl( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
self::checkTitle( $title, $name );
@@ -1142,7 +1111,13 @@ abstract class Skin extends ContextSource {
}
}
- # this can be passed the NS number as defined in Language.php
+ /**
+ * this can be passed the NS number as defined in Language.php
+ * @param $name
+ * @param $urlaction string
+ * @param $namespace int
+ * @return String
+ */
static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) {
$title = Title::makeTitleSafe( $namespace, $name );
self::checkTitle( $title, $name );
@@ -1150,7 +1125,12 @@ abstract class Skin extends ContextSource {
return $title->getLocalURL( $urlaction );
}
- /* these return an array with the 'href' and boolean 'exists' */
+ /**
+ * these return an array with the 'href' and boolean 'exists'
+ * @param $name
+ * @param $urlaction string
+ * @return array
+ */
static function makeUrlDetails( $name, $urlaction = '' ) {
$title = Title::newFromText( $name );
self::checkTitle( $title, $name );
@@ -1177,7 +1157,12 @@ abstract class Skin extends ContextSource {
);
}
- # make sure we have some title to operate on
+ /**
+ * make sure we have some title to operate on
+ *
+ * @param $title Title
+ * @param $name string
+ */
static function checkTitle( &$title, $name ) {
if ( !is_object( $title ) ) {
$title = Title::newFromText( $name );
@@ -1193,13 +1178,13 @@ abstract class Skin extends ContextSource {
* @return array
*/
function buildSidebar() {
- global $parserMemc, $wgEnableSidebarCache, $wgSidebarCacheExpiry;
+ global $wgMemc, $wgEnableSidebarCache, $wgSidebarCacheExpiry;
wfProfileIn( __METHOD__ );
- $key = wfMemcKey( 'sidebar', $this->getLang()->getCode() );
+ $key = wfMemcKey( 'sidebar', $this->getLanguage()->getCode() );
if ( $wgEnableSidebarCache ) {
- $cachedsidebar = $parserMemc->get( $key );
+ $cachedsidebar = $wgMemc->get( $key );
if ( $cachedsidebar ) {
wfProfileOut( __METHOD__ );
return $cachedsidebar;
@@ -1211,7 +1196,7 @@ abstract class Skin extends ContextSource {
wfRunHooks( 'SkinBuildSidebar', array( $this, &$bar ) );
if ( $wgEnableSidebarCache ) {
- $parserMemc->set( $key, $bar, $wgSidebarCacheExpiry );
+ $wgMemc->set( $key, $bar, $wgSidebarCacheExpiry );
}
wfProfileOut( __METHOD__ );
@@ -1223,7 +1208,7 @@ abstract class Skin extends ContextSource {
*
* This is just a wrapper around addToSidebarPlain() for backwards compatibility
*
- * @param &$bar array
+ * @param $bar array
* @param $message String
*/
function addToSidebar( &$bar, $message ) {
@@ -1233,7 +1218,7 @@ abstract class Skin extends ContextSource {
/**
* Add content from plain text
* @since 1.17
- * @param &$bar array
+ * @param $bar array
* @param $text string
* @return Array
*/
@@ -1246,6 +1231,7 @@ abstract class Skin extends ContextSource {
if ( strpos( $line, '*' ) !== 0 ) {
continue;
}
+ $line = rtrim( $line, "\r" ); // for Windows compat
if ( strpos( $line, '**' ) !== 0 ) {
$heading = trim( $line, '* ' );
@@ -1260,13 +1246,13 @@ abstract class Skin extends ContextSource {
$line = array_map( 'trim', explode( '|', $line, 2 ) );
if ( count( $line ) !== 2 ) {
// Second sanity check, could be hit by people doing
- // funky stuff with parserfuncs... (bug 3321)
+ // funky stuff with parserfuncs... (bug 33321)
continue;
}
$extraAttribs = array();
- $msgLink = wfMessage( $line[0] )->inContentLanguage();
+ $msgLink = $this->msg( $line[0] )->inContentLanguage();
if ( $msgLink->exists() ) {
$link = $msgLink->text();
if ( $link == '-' ) {
@@ -1275,7 +1261,7 @@ abstract class Skin extends ContextSource {
} else {
$link = $line[0];
}
- $msgText = wfMessage( $line[1] );
+ $msgText = $this->msg( $line[1] );
if ( $msgText->exists() ) {
$text = $msgText->text();
} else {
@@ -1284,13 +1270,13 @@ abstract class Skin extends ContextSource {
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;
@@ -1300,7 +1286,7 @@ abstract class Skin extends ContextSource {
if ( $title ) {
$title = $title->fixSpecialName();
- $href = $title->getLocalURL();
+ $href = $title->getLinkURL();
} else {
$href = 'INVALID-TITLE';
}
@@ -1322,10 +1308,9 @@ abstract class Skin extends ContextSource {
}
/**
- * Should we include common/wikiprintable.css? Skins that have their own
+ * Should we load mediawiki.legacy.wikiprintable? Skins that have their own
* print stylesheet should override this and return false. (This is an
- * ugly hack to get Monobook to play nicely with
- * OutputPage::headElement().)
+ * ugly hack to get Monobook to play nicely with OutputPage::headElement().)
*
* @return bool
*/
@@ -1348,33 +1333,31 @@ abstract class Skin extends ContextSource {
$userTalkTitle = $userTitle->getTalkPage();
if ( !$userTalkTitle->equals( $out->getTitle() ) ) {
- $newMessagesLink = $this->link(
+ $newMessagesLink = Linker::linkKnown(
$userTalkTitle,
- wfMsgHtml( 'newmessageslink' ),
+ $this->msg( 'newmessageslink' )->escaped(),
array(),
- array( 'redirect' => 'no' ),
- array( 'known', 'noclasses' )
+ array( 'redirect' => 'no' )
);
- $newMessagesDiffLink = $this->link(
+ $newMessagesDiffLink = Linker::linkKnown(
$userTalkTitle,
- wfMsgHtml( 'newmessagesdifflink' ),
+ $this->msg( 'newmessagesdifflink' )->escaped(),
array(),
- array( 'diff' => 'cur' ),
- array( 'known', 'noclasses' )
+ array( 'diff' => 'cur' )
);
- $ntl = wfMsg(
+ $ntl = $this->msg(
'youhavenewmessages',
$newMessagesLink,
$newMessagesDiffLink
- );
+ )->text();
# Disable Squid cache
$out->setSquidMaxage( 0 );
}
} elseif ( count( $newtalks ) ) {
// _>" " for BC <= 1.16
- $sep = str_replace( '_', ' ', wfMsgHtml( 'newtalkseparator' ) );
+ $sep = str_replace( '_', ' ', $this->msg( 'newtalkseparator' )->escaped() );
$msgs = array();
foreach ( $newtalks as $newtalk ) {
@@ -1384,7 +1367,7 @@ abstract class Skin extends ContextSource {
);
}
$parts = implode( $sep, $msgs );
- $ntl = wfMsgHtml( 'youhavenewmessagesmulti', $parts );
+ $ntl = $this->msg( 'youhavenewmessagesmulti' )->rawParams( $parts )->escaped();
$out->setSquidMaxage( 0 );
}
@@ -1413,7 +1396,7 @@ abstract class Skin extends ContextSource {
return false;
}
} else {
- $msg = wfMessage( $name )->inContentLanguage();
+ $msg = $this->msg( $name )->inContentLanguage();
if( $msg->isDisabled() ) {
wfProfileOut( __METHOD__ );
return false;
@@ -1441,7 +1424,7 @@ abstract class Skin extends ContextSource {
}
$notice = Html::rawElement( 'div', array( 'id' => 'localNotice',
- 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ), $notice );
+ 'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ), $notice );
wfProfileOut( __METHOD__ );
return $notice;
}
diff --git a/includes/SkinLegacy.php b/includes/SkinLegacy.php
index 53ce6741..77c85a88 100644
--- a/includes/SkinLegacy.php
+++ b/includes/SkinLegacy.php
@@ -183,54 +183,16 @@ class LegacyTemplate extends BaseTemplate {
}
function pageStats() {
- global $wgOut, $wgLang, $wgRequest, $wgUser;
- global $wgDisableCounters, $wgMaxCredits, $wgShowCreditsIfMax, $wgPageShowWatchingUsers;
+ $ret = array();
+ $items = array( 'viewcount', 'credits', 'lastmod', 'numberofwatchingusers', 'copyright' );
- 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 );
+ foreach( $items as $item ) {
+ if ( $this->data[$item] !== false ) {
+ $ret[] = $this->data[$item];
}
}
- 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();
+ return implode( ' ', $ret );
}
function topLinks() {
@@ -269,20 +231,23 @@ class LegacyTemplate extends BaseTemplate {
$s = '';
/* show links to different language variants */
- global $wgDisableLangConversion, $wgLang, $wgContLang;
+ global $wgDisableLangConversion, $wgLang;
- $variants = $wgContLang->getVariants();
+ $title = $this->getSkin()->getTitle();
+ $lang = $title->getPageLanguage();
+ $variants = $lang->getVariants();
- if ( !$wgDisableLangConversion && sizeof( $variants ) > 1 ) {
+ if ( !$wgDisableLangConversion && sizeof( $variants ) > 1
+ && !$title->isSpecialPage() ) {
foreach ( $variants as $code ) {
- $varname = $wgContLang->getVariantname( $code );
+ $varname = $lang->getVariantname( $code );
if ( $varname == 'disable' ) {
continue;
}
$s = $wgLang->pipeList( array(
$s,
- '<a href="' . $this->getSkin()->getTitle()->escapeLocalURL( 'variant=' . $code ) . '">' . htmlspecialchars( $varname ) . '</a>'
+ '<a href="' . htmlspecialchars( $title->getLocalURL( 'variant=' . $code ) ) . '">' . htmlspecialchars( $varname ) . '</a>'
) );
}
}
@@ -319,7 +284,7 @@ class LegacyTemplate extends BaseTemplate {
}
function bottomLinks() {
- global $wgOut, $wgUser, $wgUseTrackbacks;
+ global $wgOut, $wgUser;
$sep = wfMsgExt( 'pipe-separator', 'escapenoentities' ) . "\n";
$s = '';
@@ -335,16 +300,14 @@ class LegacyTemplate extends BaseTemplate {
$element[] = $this->whatLinksHere();
$element[] = $this->watchPageLinksLink();
- if ( $wgUseTrackbacks ) {
- $element[] = $this->trackbackLink();
- }
+ $title = $this->getSkin()->getTitle();
if (
- $this->getSkin()->getTitle()->getNamespace() == NS_USER ||
- $this->getSkin()->getTitle()->getNamespace() == NS_USER_TALK
+ $title->getNamespace() == NS_USER ||
+ $title->getNamespace() == NS_USER_TALK
) {
- $id = User::idFromName( $this->getSkin()->getTitle()->getText() );
- $ip = User::isIP( $this->getSkin()->getTitle()->getText() );
+ $id = User::idFromName( $title->getText() );
+ $ip = User::isIP( $title->getText() );
# Both anons and non-anons have contributions list
if ( $id || $ip ) {
@@ -358,7 +321,7 @@ class LegacyTemplate extends BaseTemplate {
$s = implode( $element, $sep );
- if ( $this->getSkin()->getTitle()->getArticleId() ) {
+ if ( $title->getArticleId() ) {
$s .= "\n<br />";
// Delete/protect/move links for privileged users
@@ -382,7 +345,7 @@ class LegacyTemplate extends BaseTemplate {
}
function otherLanguages() {
- global $wgOut, $wgContLang, $wgHideInterlanguageLinks;
+ global $wgOut, $wgLang, $wgContLang, $wgHideInterlanguageLinks;
if ( $wgHideInterlanguageLinks ) {
return '';
@@ -397,8 +360,8 @@ class LegacyTemplate extends BaseTemplate {
$s = wfMsg( 'otherlanguages' ) . wfMsg( 'colon-separator' );
$first = true;
- if ( $wgContLang->isRTL() ) {
- $s .= '<span dir="LTR">';
+ if ( $wgLang->isRTL() ) {
+ $s .= '<span dir="ltr">';
}
foreach ( $a as $l ) {
@@ -416,7 +379,7 @@ class LegacyTemplate extends BaseTemplate {
$text == '' ? $l : $text );
}
- if ( $wgContLang->isRTL() ) {
+ if ( $wgLang->isRTL() ) {
$s .= '</span>';
}
@@ -427,34 +390,18 @@ class LegacyTemplate extends BaseTemplate {
* Show a drop-down box of special pages
*/
function specialPagesList() {
- global $wgContLang, $wgServer, $wgRedirectScript;
+ global $wgScript;
+ $select = new XmlSelect( 'title' );
$pages = SpecialPageFactory::getUsablePages();
-
- foreach ( $pages as $name => $page ) {
- $pages[$name] = $page->getDescription();
+ array_unshift( $pages, SpecialPageFactory::getPage( 'SpecialPages' ) );
+ foreach ( $pages as $obj ) {
+ $select->addOption( $obj->getDescription(),
+ $obj->getTitle()->getPrefixedDBkey() );
}
- $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;
+ return Html::rawElement( 'form', array( 'id' => 'specialpages', 'method' => 'get',
+ 'action' => $wgScript ), $select->getHTML() . Xml::submitButton( wfMsg( 'go' ) ) );
}
function pageTitleLinks() {
@@ -464,23 +411,26 @@ class LegacyTemplate extends BaseTemplate {
$diff = $wgRequest->getVal( 'diff' );
$action = $wgRequest->getText( 'action' );
+ $skin = $this->getSkin();
+ $title = $skin->getTitle();
+
$s[] = $this->printableLink();
- $disclaimer = $this->getSkin()->disclaimerLink(); # may be empty
+ $disclaimer = $skin->disclaimerLink(); # may be empty
if ( $disclaimer ) {
$s[] = $disclaimer;
}
- $privacy = $this->getSkin()->privacyLink(); # may be empty too
+ $privacy = $skin->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 ( $title->getNamespace() == NS_FILE ) {
+ $name = $title->getDBkey();
+ $image = wfFindFile( $title );
if ( $image ) {
$link = htmlspecialchars( $image->getURL() );
@@ -491,33 +441,28 @@ class LegacyTemplate extends BaseTemplate {
}
if ( 'history' == $action || isset( $diff ) || isset( $oldid ) ) {
- $s[] .= Linker::link(
- $this->getSkin()->getTitle(),
- wfMsg( 'currentrev' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ $s[] .= Linker::linkKnown(
+ $title,
+ wfMsg( 'currentrev' )
);
}
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(
+ if ( !$title->equals( $wgUser->getTalkPage() ) ) {
+ $tl = Linker::linkKnown(
$wgUser->getTalkPage(),
wfMsgHtml( 'newmessageslink' ),
array(),
- array( 'redirect' => 'no' ),
- array( 'known', 'noclasses' )
+ array( 'redirect' => 'no' )
);
- $dl = Linker::link(
+ $dl = Linker::linkKnown(
$wgUser->getTalkPage(),
wfMsgHtml( 'newmessagesdifflink' ),
array(),
- array( 'diff' => 'cur' ),
- array( 'known', 'noclasses' )
+ array( 'diff' => 'cur' )
);
$s[] = '<strong>' . wfMsg( 'youhavenewmessages', $tl, $dl ) . '</strong>';
# disable caching
@@ -526,7 +471,7 @@ class LegacyTemplate extends BaseTemplate {
}
}
- $undelete = $this->getSkin()->getUndeleteLink();
+ $undelete = $skin->getUndeleteLink();
if ( !empty( $undelete ) ) {
$s[] = $undelete;
@@ -541,7 +486,7 @@ class LegacyTemplate extends BaseTemplate {
*/
function pageTitle() {
global $wgOut;
- $s = '<h1 class="pagetitle">' . $wgOut->getPageTitle() . '</h1>';
+ $s = '<h1 class="pagetitle"><span dir="auto">' . $wgOut->getPageTitle() . '</span></h1>';
return $s;
}
@@ -587,6 +532,7 @@ class LegacyTemplate extends BaseTemplate {
* @deprecated in 1.19
*/
function getQuickbarCompensator( $rows = 1 ) {
+ wfDeprecated( __METHOD__, '1.19' );
return "<td width='152' rowspan='{$rows}'>&#160;</td>";
}
@@ -596,20 +542,20 @@ class LegacyTemplate extends BaseTemplate {
if ( !$wgOut->isArticleRelated() ) {
$s = wfMsg( 'protectedpage' );
} else {
- if ( $this->getSkin()->getTitle()->quickUserCan( 'edit' ) && $this->getSkin()->getTitle()->exists() ) {
+ $title = $this->getSkin()->getTitle();
+ if ( $title->quickUserCan( 'edit' ) && $title->exists() ) {
$t = wfMsg( 'editthispage' );
- } elseif ( $this->getSkin()->getTitle()->quickUserCan( 'create' ) && !$this->getSkin()->getTitle()->exists() ) {
+ } elseif ( $title->quickUserCan( 'create' ) && !$title->exists() ) {
$t = wfMsg( 'create-this-page' );
} else {
$t = wfMsg( 'viewsource' );
}
- $s = Linker::link(
- $this->getSkin()->getTitle(),
+ $s = Linker::linkKnown(
+ $title,
$t,
array(),
- $this->getSkin()->editUrlOptions(),
- array( 'known', 'noclasses' )
+ $this->getSkin()->editUrlOptions()
);
}
@@ -620,16 +566,16 @@ class LegacyTemplate extends BaseTemplate {
global $wgUser, $wgRequest;
$diff = $wgRequest->getVal( 'diff' );
+ $title = $this->getSkin()->getTitle();
- if ( $this->getSkin()->getTitle()->getArticleId() && ( !$diff ) && $wgUser->isAllowed( 'delete' ) ) {
+ if ( $title->getArticleId() && ( !$diff ) && $wgUser->isAllowed( 'delete' ) ) {
$t = wfMsg( 'deletethispage' );
- $s = Linker::link(
- $this->getSkin()->getTitle(),
+ $s = Linker::linkKnown(
+ $title,
$t,
array(),
- array( 'action' => 'delete' ),
- array( 'known', 'noclasses' )
+ array( 'action' => 'delete' )
);
} else {
$s = '';
@@ -642,9 +588,10 @@ class LegacyTemplate extends BaseTemplate {
global $wgUser, $wgRequest;
$diff = $wgRequest->getVal( 'diff' );
+ $title = $this->getSkin()->getTitle();
- if ( $this->getSkin()->getTitle()->getArticleId() && ( ! $diff ) && $wgUser->isAllowed( 'protect' ) ) {
- if ( $this->getSkin()->getTitle()->isProtected() ) {
+ if ( $title->getArticleId() && ( ! $diff ) && $wgUser->isAllowed( 'protect' ) ) {
+ if ( $title->isProtected() ) {
$text = wfMsg( 'unprotectthispage' );
$query = array( 'action' => 'unprotect' );
} else {
@@ -652,12 +599,11 @@ class LegacyTemplate extends BaseTemplate {
$query = array( 'action' => 'protect' );
}
- $s = Linker::link(
- $this->getSkin()->getTitle(),
+ $s = Linker::linkKnown(
+ $title,
$text,
array(),
- $query,
- array( 'known', 'noclasses' )
+ $query
);
} else {
$s = '';
@@ -690,12 +636,11 @@ class LegacyTemplate extends BaseTemplate {
$id = 'mw-watch-link' . $this->mWatchLinkNum;
}
- $s = Linker::link(
+ $s = Linker::linkKnown(
$title,
$text,
array( 'id' => $id ),
- $query,
- array( 'known', 'noclasses' )
+ $query
);
} else {
$s = wfMsg( 'notanarticle' );
@@ -706,12 +651,11 @@ class LegacyTemplate extends BaseTemplate {
function moveThisPage() {
if ( $this->getSkin()->getTitle()->quickUserCan( 'move' ) ) {
- return Linker::link(
+ return Linker::linkKnown(
SpecialPage::getTitleFor( 'Movepage' ),
wfMsg( 'movethispage' ),
array(),
- array( 'target' => $this->getSkin()->getTitle()->getPrefixedDBkey() ),
- array( 'known', 'noclasses' )
+ array( 'target' => $this->getSkin()->getTitle()->getPrefixedDBkey() )
);
} else {
// no message if page is protected - would be redundant
@@ -729,32 +673,23 @@ class LegacyTemplate extends BaseTemplate {
}
function whatLinksHere() {
- return Linker::link(
+ return Linker::linkKnown(
SpecialPage::getTitleFor( 'Whatlinkshere', $this->getSkin()->getTitle()->getPrefixedDBkey() ),
- wfMsgHtml( 'whatlinkshere' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ wfMsgHtml( 'whatlinkshere' )
);
}
function userContribsLink() {
- return Linker::link(
+ return Linker::linkKnown(
SpecialPage::getTitleFor( 'Contributions', $this->getSkin()->getTitle()->getDBkey() ),
- wfMsgHtml( 'contributions' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ wfMsgHtml( 'contributions' )
);
}
function emailUserLink() {
- return Linker::link(
+ return Linker::linkKnown(
SpecialPage::getTitleFor( 'Emailuser', $this->getSkin()->getTitle()->getDBkey() ),
- wfMsg( 'emailuser' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ wfMsgHtml( 'emailuser' )
);
}
@@ -764,31 +699,24 @@ class LegacyTemplate extends BaseTemplate {
if ( !$wgOut->isArticleRelated() ) {
return '(' . wfMsg( 'notanarticle' ) . ')';
} else {
- return Linker::link(
+ return Linker::linkKnown(
SpecialPage::getTitleFor( 'Recentchangeslinked', $this->getSkin()->getTitle()->getPrefixedDBkey() ),
- wfMsg( 'recentchangeslinked-toolbox' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ wfMsgHtml( 'recentchangeslinked-toolbox' )
);
}
}
- function trackbackLink() {
- return '<a href="' . $this->getSkin()->getTitle()->trackbackURL() . '">'
- . wfMsg( 'trackbacklink' ) . '</a>';
- }
-
function talkLink() {
- if ( NS_SPECIAL == $this->getSkin()->getTitle()->getNamespace() ) {
+ $title = $this->getSkin()->getTitle();
+ if ( NS_SPECIAL == $title->getNamespace() ) {
# No discussion links for special pages
return '';
}
$linkOptions = array();
- if ( $this->getSkin()->getTitle()->isTalkPage() ) {
- $link = $this->getSkin()->getTitle()->getSubjectPage();
+ if ( $title->isTalkPage() ) {
+ $link = $title->getSubjectPage();
switch( $link->getNamespace() ) {
case NS_MAIN:
$text = wfMsg( 'articlepage' );
@@ -821,7 +749,7 @@ class LegacyTemplate extends BaseTemplate {
$text = wfMsg( 'articlepage' );
}
} else {
- $link = $this->getSkin()->getTitle()->getTalkPage();
+ $link = $title->getTalkPage();
$text = wfMsg( 'talkpage' );
}
@@ -833,30 +761,26 @@ class LegacyTemplate extends BaseTemplate {
function commentLink() {
global $wgOut;
- if ( $this->getSkin()->getTitle()->getNamespace() == NS_SPECIAL ) {
+ $title = $this->getSkin()->getTitle();
+ if ( $title->isSpecialPage() ) {
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();
+ if ( !$title->isTalkPage() && !$wgOut->showNewSectionLink() ) {
+ $title = $title->getTalkPage();
}
- return Linker::link(
+ return Linker::linkKnown(
$title,
wfMsg( 'postcomment' ),
array(),
array(
'action' => 'edit',
'section' => 'new'
- ),
- array( 'known', 'noclasses' )
+ )
);
}
@@ -867,26 +791,22 @@ class LegacyTemplate extends BaseTemplate {
# 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(
+ return Linker::linkKnown(
SpecialPage::getTitleFor( 'Upload' ),
- wfMsgHtml( 'upload' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ wfMsgHtml( 'upload' )
);
}
}
function nameAndLogin() {
- global $wgUser, $wgLang, $wgContLang;
-
- $logoutPage = $wgContLang->specialPage( 'Userlogout' );
+ global $wgUser, $wgLang, $wgRequest;
+ $returnTo = $this->getSkin()->getTitle();
$ret = '';
if ( $wgUser->isAnon() ) {
if ( $this->getSkin()->showIPinHeader() ) {
- $name = wfGetIP();
+ $name = $wgRequest->getIP();
$talkLink = Linker::link( $wgUser->getTalkPage(),
$wgLang->getNsText( NS_TALK ) );
@@ -896,11 +816,10 @@ class LegacyTemplate extends BaseTemplate {
$ret .= wfMsg( 'notloggedin' );
}
- $returnTo = $this->getSkin()->getTitle()->getPrefixedDBkey();
$query = array();
- if ( $logoutPage != $returnTo ) {
- $query['returnto'] = $returnTo;
+ if ( !$returnTo->isSpecial( 'Userlogout' ) ) {
+ $query['returnto'] = $returnTo->getPrefixedDBkey();
}
$loginlink = $wgUser->isAllowed( 'createaccount' )
@@ -911,7 +830,6 @@ class LegacyTemplate extends BaseTemplate {
wfMsg( $loginlink ), array(), $query
);
} else {
- $returnTo = $this->getSkin()->getTitle()->getPrefixedDBkey();
$talkLink = Linker::link( $wgUser->getTalkPage(),
$wgLang->getNsText( NS_TALK ) );
@@ -921,7 +839,7 @@ class LegacyTemplate extends BaseTemplate {
$ret .= $wgLang->pipeList( array(
Linker::link(
SpecialPage::getTitleFor( 'Userlogout' ), wfMsg( 'logout' ),
- array(), array( 'returnto' => $returnTo )
+ array(), array( 'returnto' => $returnTo->getPrefixedDBkey() )
),
Linker::specialLink( 'Preferences' ),
) );
diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php
index ef104bd7..e41b5e7d 100644
--- a/includes/SkinTemplate.php
+++ b/includes/SkinTemplate.php
@@ -131,23 +131,31 @@ class SkinTemplate extends Skin {
*
* @param $out OutputPage
*/
- function outputPage( OutputPage $out ) {
- global $wgUser, $wgLang, $wgContLang;
+ function outputPage( OutputPage $out=null ) {
+ global $wgContLang;
global $wgScript, $wgStylePath;
- global $wgMimeType, $wgJsMimeType, $wgRequest;
+ global $wgMimeType, $wgJsMimeType;
global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces, $wgHtml5Version;
- global $wgDisableCounters, $wgLogo, $wgHideInterlanguageLinks;
+ global $wgDisableCounters, $wgSitename, $wgLogo, $wgHideInterlanguageLinks;
global $wgMaxCredits, $wgShowCreditsIfMax;
global $wgPageShowWatchingUsers;
- global $wgUseTrackbacks, $wgUseSiteJs, $wgDebugComments;
+ global $wgDebugComments;
global $wgArticlePath, $wgScriptPath, $wgServer;
wfProfileIn( __METHOD__ );
Profiler::instance()->setTemplated( true );
- $oldid = $wgRequest->getVal( 'oldid' );
- $diff = $wgRequest->getVal( 'diff' );
- $action = $wgRequest->getVal( 'action', 'view' );
+ $oldContext = null;
+ if ( $out !== null ) {
+ // @todo Add wfDeprecated in 1.20
+ $oldContext = $this->getContext();
+ $this->setContext( $out->getContext() );
+ }
+
+ $out = $this->getOutput();
+ $request = $this->getRequest();
+ $user = $this->getUser();
+ $title = $this->getTitle();
wfProfileIn( __METHOD__ . '-init' );
$this->initPage( $out );
@@ -156,24 +164,21 @@ class SkinTemplate extends Skin {
wfProfileOut( __METHOD__ . '-init' );
wfProfileIn( __METHOD__ . '-stuff' );
- $this->thispage = $this->getTitle()->getPrefixedDBkey();
- $this->userpage = $wgUser->getUserPage()->getPrefixedText();
-
+ $this->thispage = $title->getPrefixedDBkey();
+ $this->titletxt = $title->getPrefixedText();
+ $this->userpage = $user->getUserPage()->getPrefixedText();
$query = array();
- if ( !$wgRequest->wasPosted() ) {
- $query = $wgRequest->getValues();
+ if ( !$request->wasPosted() ) {
+ $query = $request->getValues();
unset( $query['title'] );
unset( $query['returnto'] );
unset( $query['returntoquery'] );
}
$this->thisquery = wfArrayToCGI( $query );
- $this->loggedin = $wgUser->isLoggedIn();
- $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->loggedin = $user->isLoggedIn();
+ $this->username = $user->getName();
+ if ( $this->loggedin || $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
@@ -181,48 +186,25 @@ class SkinTemplate extends Skin {
$this->userpageUrlDetails = self::makeKnownUrlDetails( $this->userpage );
}
- $this->titletxt = $this->getTitle()->getPrefixedText();
wfProfileOut( __METHOD__ . '-stuff' );
wfProfileIn( __METHOD__ . '-stuff-head' );
- if ( $this->useHeadElement ) {
- $pagecss = $this->setupPageCss();
- if( $pagecss )
- $out->addInlineStyle( $pagecss );
- } else {
- $this->setupUserCss( $out );
-
- $tpl->set( 'pagecss', $this->setupPageCss() );
+ if ( !$this->useHeadElement ) {
+ $tpl->set( 'pagecss', false );
$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 );
-
- if( $wgUseSiteJs ) {
- $jsCache = $this->loggedin ? '&smaxage=0' : '';
- $tpl->set( 'jsvarurl',
- self::makeUrl( '-',
- "action=raw$jsCache&gen=js&useskin=" .
- urlencode( $this->getSkinName() ) ) );
- } else {
- $tpl->set( 'jsvarurl', false );
- }
+ $tpl->set( 'userjs', false );
+ $tpl->set( 'userjsprev', false );
+
+ $tpl->set( 'jsvarurl', false );
$tpl->setRef( 'xhtmldefaultnamespace', $wgXhtmlDefaultNamespace );
$tpl->set( 'xhtmlnamespaces', $wgXhtmlNamespaces );
$tpl->set( 'html5version', $wgHtml5Version );
- $tpl->set( 'headlinks', $out->getHeadLinks( $this ) );
- $tpl->set( 'csslinks', $out->buildCssLinks( $this ) );
-
- if( $wgUseTrackbacks && $out->isArticleRelated() ) {
- $tpl->set( 'trackbackhtml', $out->getTitle()->trackbackRDF() );
- } else {
- $tpl->set( 'trackbackhtml', null );
- }
+ $tpl->set( 'headlinks', $out->getHeadLinks() );
+ $tpl->set( 'csslinks', $out->buildCssLinks() );
+ $tpl->set( 'pageclass', $this->getPageClasses( $title ) );
+ $tpl->set( 'skinnameclass', ( 'skin-' . Sanitizer::escapeClass( $this->getSkinName() ) ) );
}
wfProfileOut( __METHOD__ . '-stuff-head' );
@@ -230,42 +212,33 @@ 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->getTitle() ) );
- $tpl->set( 'skinnameclass', ( 'skin-' . Sanitizer::escapeClass( $this->getSkinName() ) ) );
- $nsname = MWNamespace::exists( $this->getTitle()->getNamespace() ) ?
- MWNamespace::getCanonicalName( $this->getTitle()->getNamespace() ) :
- $this->getTitle()->getNsText();
-
- $tpl->set( 'nscanonical', $nsname );
- $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->setRef( 'thispage', $this->thispage );
+ $tpl->setRef( 'titleprefixeddbkey', $this->thispage );
+ $tpl->set( 'titletext', $title->getText() );
+ $tpl->set( 'articleid', $title->getArticleId() );
$tpl->set( 'isarticle', $out->isArticle() );
- $tpl->setRef( 'thispage', $this->thispage );
$subpagestr = $this->subPageSubtitle();
- $tpl->set(
- 'subtitle', !empty( $subpagestr ) ?
- '<span class="subpages">' . $subpagestr . '</span>' . $out->getSubtitle() :
- $out->getSubtitle()
- );
+ if ( $subpagestr !== '' ) {
+ $subpagestr = '<span class="subpages">' . $subpagestr . '</span>';
+ }
+ $tpl->set( 'subtitle', $subpagestr . $out->getSubtitle() );
+
$undelete = $this->getUndeleteLink();
- $tpl->set(
- 'undelete', !empty( $undelete ) ?
- '<span class="subpages">' . $undelete . '</span>' :
- ''
- );
+ if ( $undelete === '' ) {
+ $tpl->set( 'undelete', '' );
+ } else {
+ $tpl->set( 'undelete', '<span class="subpages">' . $undelete . '</span>' );
+ }
$tpl->set( 'catlinks', $this->getCategories() );
if( $out->isSyndicated() ) {
$feeds = array();
foreach( $out->getSyndicationLinks() as $format => $link ) {
$feeds[$format] = array(
- 'text' => wfMsg( "feed-$format" ),
+ 'text' => $this->msg( "feed-$format" )->text(),
'href' => $link
);
}
@@ -280,39 +253,40 @@ class SkinTemplate extends Skin {
$tpl->setRef( 'wgScript', $wgScript );
$tpl->setRef( 'skinname', $this->skinname );
$tpl->set( 'skinclass', get_class( $this ) );
+ $tpl->setRef( 'skin', $this );
$tpl->setRef( 'stylename', $this->stylename );
$tpl->set( 'printable', $out->isPrintable() );
- $tpl->set( 'handheld', $wgRequest->getBool( 'handheld' ) );
+ $tpl->set( 'handheld', $request->getBool( 'handheld' ) );
$tpl->setRef( 'loggedin', $this->loggedin );
- $tpl->set( 'notspecialpage', $this->getTitle()->getNamespace() != NS_SPECIAL );
+ $tpl->set( 'notspecialpage', !$title->isSpecialPage() );
/* XXX currently unused, might get useful later
- $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( 'editable', ( !$title->isSpecialPage() ) );
+ $tpl->set( 'exists', $title->getArticleID() != 0 );
+ $tpl->set( 'watch', $title->userIsWatching() ? 'unwatch' : 'watch' );
+ $tpl->set( 'protect', count( $title->isProtected() ) ? 'unprotect' : 'protect' );
+ $tpl->set( 'helppage', $this->msg( 'helppage' )->text() );
*/
$tpl->set( 'searchaction', $this->escapeSearchLink() );
$tpl->set( 'searchtitle', SpecialPage::getTitleFor( 'Search' )->getPrefixedDBKey() );
- $tpl->set( 'search', trim( $wgRequest->getVal( 'search' ) ) );
+ $tpl->set( 'search', trim( $request->getVal( 'search' ) ) );
$tpl->setRef( 'stylepath', $wgStylePath );
$tpl->setRef( 'articlepath', $wgArticlePath );
$tpl->setRef( 'scriptpath', $wgScriptPath );
$tpl->setRef( 'serverurl', $wgServer );
$tpl->setRef( 'logopath', $wgLogo );
+ $tpl->setRef( 'sitename', $wgSitename );
- $contentlang = $wgContLang->getCode();
- $contentdir = $wgContLang->getDir();
- $userlang = $wgLang->getCode();
- $userdir = $wgLang->getDir();
+ $lang = $this->getLanguage();
+ $userlang = $lang->getHtmlCode();
+ $userdir = $lang->getDir();
$tpl->set( 'lang', $userlang );
$tpl->set( 'dir', $userdir );
- $tpl->set( 'rtl', $wgLang->isRTL() );
+ $tpl->set( 'rtl', $lang->isRTL() );
- $tpl->set( 'capitalizeallnouns', $wgLang->capitalizeAllNouns() ? ' capitalize-all-nouns' : '' );
- $tpl->set( 'showjumplinks', $wgUser->getOption( 'showjumplinks' ) );
- $tpl->set( 'username', $wgUser->isAnon() ? null : $this->username );
+ $tpl->set( 'capitalizeallnouns', $lang->capitalizeAllNouns() ? ' capitalize-all-nouns' : '' );
+ $tpl->set( 'showjumplinks', $user->getOption( 'showjumplinks' ) );
+ $tpl->set( 'username', $this->loggedin ? $this->username : null );
$tpl->setRef( 'userpage', $this->userpage );
$tpl->setRef( 'userpageurl', $this->userpageUrlDetails['href'] );
$tpl->set( 'userlang', $userlang );
@@ -323,79 +297,52 @@ class SkinTemplate extends Skin {
$tpl->set( 'userlangattributes', '' );
$tpl->set( 'specialpageattributes', '' ); # obsolete
- if ( $userlang !== $contentlang || $userdir !== $contentdir ) {
+ if ( $userlang !== $wgContLang->getHtmlCode() || $userdir !== $wgContLang->getDir() ) {
$attrs = " lang='$userlang' dir='$userdir'";
$tpl->set( 'userlangattributes', $attrs );
}
- $newtalks = $this->getNewtalks( $out );
-
wfProfileOut( __METHOD__ . '-stuff2' );
wfProfileIn( __METHOD__ . '-stuff3' );
- $tpl->setRef( 'newtalk', $newtalks );
- $tpl->setRef( 'skin', $this );
+ $tpl->set( 'newtalk', $this->getNewtalks() );
$tpl->set( 'logo', $this->logoText() );
- if ( $out->isArticle() && ( !isset( $oldid ) || isset( $diff ) ) &&
- $this->getTitle()->exists() )
- {
- $article = new Article( $this->getTitle(), 0 );
- if ( !$wgDisableCounters ) {
- $viewcount = $wgLang->formatNum( $article->getCount() );
- if ( $viewcount ) {
- $tpl->set( 'viewcount', wfMsgExt( 'viewcount', array( 'parseinline' ), $viewcount ) );
- } else {
- $tpl->set( 'viewcount', false );
+
+ $tpl->set( 'copyright', false );
+ $tpl->set( 'viewcount', false );
+ $tpl->set( 'lastmod', false );
+ $tpl->set( 'credits', false );
+ $tpl->set( 'numberofwatchingusers', false );
+ if ( $out->isArticle() && $title->exists() ) {
+ if ( $this->isRevisionCurrent() ) {
+ if ( !$wgDisableCounters ) {
+ $viewcount = $this->getWikiPage()->getCount();
+ if ( $viewcount ) {
+ $tpl->set( 'viewcount', $this->msg( 'viewcount' )->numParams( $viewcount )->parse() );
+ }
}
- } else {
- $tpl->set( 'viewcount', false );
- }
- if( $wgPageShowWatchingUsers ) {
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'watchlist',
- array( 'COUNT(*) AS n' ),
- array( 'wl_title' => $dbr->strencode( $this->getTitle()->getDBkey() ), 'wl_namespace' => $this->getTitle()->getNamespace() ),
- __METHOD__
- );
- $x = $dbr->fetchObject( $res );
- $numberofwatchingusers = $x->n;
- if( $numberofwatchingusers > 0 ) {
- $tpl->set( 'numberofwatchingusers',
- wfMsgExt( 'number_of_watching_users_pageview', array( 'parseinline' ),
- $wgLang->formatNum( $numberofwatchingusers ) )
+ if( $wgPageShowWatchingUsers ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $num = $dbr->selectField( 'watchlist', 'COUNT(*)',
+ array( 'wl_title' => $title->getDBkey(), 'wl_namespace' => $title->getNamespace() ),
+ __METHOD__
);
- } else {
- $tpl->set( 'numberofwatchingusers', false );
+ if( $num > 0 ) {
+ $tpl->set( 'numberofwatchingusers',
+ $this->msg( 'number_of_watching_users_pageview' )->numParams( $num )->parse()
+ );
+ }
}
- } else {
- $tpl->set( 'numberofwatchingusers', false );
- }
- $tpl->set( 'copyright', $this->getCopyright() );
-
- $this->credits = false;
-
- if( $wgMaxCredits != 0 ){
- $this->credits = Action::factory( 'credits', $article )->getCredits( $wgMaxCredits, $wgShowCreditsIfMax );
- } else {
- $tpl->set( 'lastmod', $this->lastModified( $article ) );
+ if ( $wgMaxCredits != 0 ) {
+ $tpl->set( 'credits', Action::factory( 'credits', $this->getWikiPage(),
+ $this->getContext() )->getCredits( $wgMaxCredits, $wgShowCreditsIfMax ) );
+ } else {
+ $tpl->set( 'lastmod', $this->lastModified() );
+ }
}
-
- $tpl->setRef( 'credits', $this->credits );
-
- } elseif ( isset( $oldid ) && !isset( $diff ) ) {
$tpl->set( 'copyright', $this->getCopyright() );
- $tpl->set( 'viewcount', false );
- $tpl->set( 'lastmod', false );
- $tpl->set( 'credits', false );
- $tpl->set( 'numberofwatchingusers', false );
- } else {
- $tpl->set( 'copyright', false );
- $tpl->set( 'viewcount', false );
- $tpl->set( 'lastmod', false );
- $tpl->set( 'credits', false );
- $tpl->set( 'numberofwatchingusers', false );
}
wfProfileOut( __METHOD__ . '-stuff3' );
@@ -446,23 +393,27 @@ class SkinTemplate extends Skin {
$tpl->set( 'debug', '' );
}
- $tpl->set( 'reporttime', wfReportTime() );
$tpl->set( 'sitenotice', $this->getSiteNotice() );
- $tpl->set( 'bottomscripts', $this->bottomScripts( $out ) );
+ $tpl->set( 'bottomscripts', $this->bottomScripts() );
$tpl->set( 'printfooter', $this->printSource() );
- # Add a <div class="mw-content-ltr/rtl"> around the body text
+ # An ID that includes the actual body text; without categories, contentSub, ...
+ $realBodyAttribs = array( 'id' => 'mw-content-text' );
+
+ # Add a mw-content-ltr/rtl class to be able to style based on text direction
+ # when the content is different from the UI language, i.e.:
# 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 );
+ if( !in_array( $title->getNamespace(), array( NS_SPECIAL, NS_FILE ) ) &&
+ in_array( $request->getVal( 'action', 'view' ), array( 'view', 'historysubmit' ) ) &&
+ ( $title->exists() || $title->getNamespace() == NS_MEDIAWIKI ) ) {
+ $pageLang = $title->getPageLanguage();
+ $realBodyAttribs['lang'] = $pageLang->getHtmlCode();
+ $realBodyAttribs['dir'] = $pageLang->getDir();
+ $realBodyAttribs['class'] = 'mw-content-'.$pageLang->getDir();
}
+ $out->mBodytext = Html::rawElement( 'div', $realBodyAttribs, $out->mBodytext );
$tpl->setRef( 'bodytext', $out->mBodytext );
# Language links
@@ -496,23 +447,24 @@ class SkinTemplate extends Skin {
wfProfileIn( __METHOD__ . '-stuff5' );
# Personal toolbar
- $tpl->set( 'personal_urls', $this->buildPersonalUrls( $out ) );
- $content_navigation = $this->buildContentNavigationUrls( $out );
+ $tpl->set( 'personal_urls', $this->buildPersonalUrls() );
+ $content_navigation = $this->buildContentNavigationUrls();
$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( $out ) );
+ $tpl->set( 'nav_urls', $this->buildNavUrls() );
// Set the head scripts near the end, in case the above actions resulted in added scripts
if ( $this->useHeadElement ) {
$tpl->set( 'headelement', $out->headElement( $this ) );
} else {
- $tpl->set( 'headscripts', $out->getScript() );
+ $tpl->set( 'headscripts', $out->getHeadScripts() . $out->getHeadItems() );
}
$tpl->set( 'debughtml', $this->generateDebugHTML() );
+ $tpl->set( 'reporttime', wfReportTime() );
// original version by hansm
if( !wfRunHooks( 'SkinTemplateOutputPageBeforeExec', array( &$this, &$tpl ) ) ) {
@@ -540,6 +492,10 @@ class SkinTemplate extends Skin {
// result may be an error
$this->printOrError( $res );
+
+ if ( $oldContext ) {
+ $this->setContext( $oldContext );
+ }
wfProfileOut( __METHOD__ );
}
@@ -572,26 +528,25 @@ class SkinTemplate extends Skin {
* build array of urls for personal toolbar
* @return array
*/
- protected function buildPersonalUrls( OutputPage $out ) {
- global $wgRequest;
-
- $title = $out->getTitle();
+ protected function buildPersonalUrls() {
+ $title = $this->getTitle();
+ $request = $this->getRequest();
$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,
- # $this->getTitle() will just give Special:Badtitle, which is
- # not especially useful as a returnto parameter. Use the title
+
+ # Due to bug 32276, if a user does not have read permissions,
+ # $this->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 );
+ $page = Title::newFromURL( $request->getVal( 'title', '' ) );
+ $page = $request->getVal( 'returnto', $page );
$a = array();
if ( strval( $page ) !== '' ) {
$a['returnto'] = $page;
- $query = $wgRequest->getVal( 'returntoquery', $this->thisquery );
+ $query = $request->getVal( 'returntoquery', $this->thisquery );
if( $query != '' ) {
$a['returntoquery'] = $query;
}
@@ -606,20 +561,20 @@ class SkinTemplate extends Skin {
);
$usertalkUrlDetails = $this->makeTalkUrlDetails( $this->userpage );
$personal_urls['mytalk'] = array(
- 'text' => wfMsg( 'mytalk' ),
+ 'text' => $this->msg( 'mytalk' )->text(),
'href' => &$usertalkUrlDetails['href'],
'class' => $usertalkUrlDetails['exists'] ? false : 'new',
'active' => ( $usertalkUrlDetails['href'] == $pageurl )
);
$href = self::makeSpecialUrl( 'Preferences' );
$personal_urls['preferences'] = array(
- 'text' => wfMsg( 'mypreferences' ),
+ 'text' => $this->msg( 'mypreferences' )->text(),
'href' => $href,
'active' => ( $href == $pageurl )
);
$href = self::makeSpecialUrl( 'Watchlist' );
$personal_urls['watchlist'] = array(
- 'text' => wfMsg( 'mywatchlist' ),
+ 'text' => $this->msg( 'mywatchlist' )->text(),
'href' => $href,
'active' => ( $href == $pageurl )
);
@@ -627,26 +582,26 @@ class SkinTemplate extends Skin {
# We need to do an explicit check for Special:Contributions, as we
# have to match both the title, and the target (which could come
# from request values or be specified in "sub page" form. The plot
- # thickens, because $wgTitle is altered for special pages, so doesn't
- # contain the original alias-with-subpage.
- $origTitle = Title::newFromText( $wgRequest->getText( 'title' ) );
- if( $origTitle instanceof Title && $origTitle->getNamespace() == NS_SPECIAL ) {
+ # thickens, because the Title object is altered for special pages,
+ # so doesn't contain the original alias-with-subpage.
+ $origTitle = Title::newFromText( $request->getText( 'title' ) );
+ if( $origTitle instanceof Title && $origTitle->isSpecialPage() ) {
list( $spName, $spPar ) = SpecialPageFactory::resolveAlias( $origTitle->getText() );
$active = $spName == 'Contributions'
&& ( ( $spPar && $spPar == $this->username )
- || $wgRequest->getText( 'target' ) == $this->username );
+ || $request->getText( 'target' ) == $this->username );
} else {
$active = false;
}
$href = self::makeSpecialUrlSubpage( 'Contributions', $this->username );
$personal_urls['mycontris'] = array(
- 'text' => wfMsg( 'mycontris' ),
+ 'text' => $this->msg( 'mycontris' )->text(),
'href' => $href,
'active' => $active
);
$personal_urls['logout'] = array(
- 'text' => wfMsg( 'userlogout' ),
+ 'text' => $this->msg( 'userlogout' )->text(),
'href' => self::makeSpecialUrl( 'Userlogout',
// userlogout link must always contain an & character, otherwise we might not be able
// to detect a buggy precaching proxy (bug 17790)
@@ -655,22 +610,21 @@ class SkinTemplate extends Skin {
'active' => false
);
} else {
- global $wgUser;
$useCombinedLoginLink = $this->useCombinedLoginLink();
- $loginlink = $wgUser->isAllowed( 'createaccount' ) && $useCombinedLoginLink
+ $loginlink = $this->getUser()->isAllowed( 'createaccount' ) && $useCombinedLoginLink
? 'nav-login-createaccount'
: 'login';
- $is_signup = $wgRequest->getText('type') == "signup";
+ $is_signup = $request->getText('type') == "signup";
# anonlogin & login are the same
$login_url = array(
- 'text' => wfMsg( $loginlink ),
+ 'text' => $this->msg( $loginlink )->text(),
'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
'active' => $title->isSpecial( 'Userlogin' ) && ( $loginlink == "nav-login-createaccount" || !$is_signup )
);
- if ( $wgUser->isAllowed( 'createaccount' ) && !$useCombinedLoginLink ) {
+ if ( $this->getUser()->isAllowed( 'createaccount' ) && !$useCombinedLoginLink ) {
$createaccount_url = array(
- 'text' => wfMsg( 'createaccount' ),
+ 'text' => $this->msg( 'createaccount' )->text(),
'href' => self::makeSpecialUrl( 'Userlogin', "$returnto&type=signup" ),
'active' => $title->isSpecial( 'Userlogin' ) && $is_signup
);
@@ -703,7 +657,7 @@ class SkinTemplate extends Skin {
$usertalkUrlDetails = $this->makeTalkUrlDetails( $this->userpage );
$href = &$usertalkUrlDetails['href'];
$personal_urls['anontalk'] = array(
- 'text' => wfMsg( 'anontalk' ),
+ 'text' => $this->msg( 'anontalk' )->text(),
'href' => $href,
'class' => $usertalkUrlDetails['exists'] ? false : 'new',
'active' => ( $pageurl == $href )
@@ -743,7 +697,7 @@ class SkinTemplate extends Skin {
// wfMessageFallback will nicely accept $message as an array of fallbacks
// or just a single key
- $msg = wfMessageFallback( $message );
+ $msg = wfMessageFallback( $message )->setContext( $this->getContext() );
if ( is_array($message) ) {
// for hook compatibility just keep the last message name
$message = end($message);
@@ -826,8 +780,7 @@ class SkinTemplate extends Skin {
*
* @return array
*/
- protected function buildContentNavigationUrls( OutputPage $out ) {
- global $wgContLang, $wgLang, $wgUser, $wgRequest;
+ protected function buildContentNavigationUrls() {
global $wgDisableLangConversion;
wfProfileIn( __METHOD__ );
@@ -835,6 +788,10 @@ class SkinTemplate extends Skin {
$title = $this->getRelevantTitle(); // Display tabs for the relevant title rather than always the title itself
$onPage = $title->equals($this->getTitle());
+ $out = $this->getOutput();
+ $request = $this->getRequest();
+ $user = $this->getUser();
+
$content_navigation = array(
'namespaces' => array(),
'views' => array(),
@@ -843,11 +800,9 @@ class SkinTemplate extends Skin {
);
// parameters
- $action = $wgRequest->getVal( 'action', 'view' );
- $section = $wgRequest->getVal( 'section' );
+ $action = $request->getVal( 'action', 'view' );
- $userCanRead = $title->userCanRead();
- $skname = $this->skinname;
+ $userCanRead = $title->quickUserCan( 'read', $user );
$preventActiveTabs = false;
wfRunHooks( 'SkinTemplatePreventOtherActiveTabs', array( &$this, &$preventActiveTabs ) );
@@ -870,6 +825,8 @@ class SkinTemplate extends Skin {
$talkId = "{$subjectId}_talk";
}
+ $skname = $this->skinname;
+
// Adds namespace links
$subjectMsg = array( "nstab-$subjectId" );
if ( $subjectPage->isMainPage() ) {
@@ -884,163 +841,175 @@ class SkinTemplate extends Skin {
);
$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
- }
+ if ( $userCanRead ) {
+ // Adds view view link
+ if ( $title->exists() ) {
+ $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' );
-
- // 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() ) {
+ wfProfileIn( __METHOD__ . '-edit' );
+
+ // Checks if user can edit the current page if it exists or create it otherwise
+ if ( $title->quickUserCan( 'edit', $user ) && ( $title->exists() || $title->quickUserCan( 'create', $user ) ) ) {
+ // Builds CSS class for talk page links
+ $isTalkClass = $isTalk ? ' istalk' : '';
+ // Whether the user is editing the page
+ $isEditing = $onPage && ( $action == 'edit' || $action == 'submit' );
+ // Whether to show the "Add a new section" tab
+ // Checks if this is a current rev of talk page and is not forced to be hidden
+ $showNewSection = !$out->forceHideNewSectionLink()
+ && ( ( $isTalk && $this->isRevisionCurrent() ) || $out->showNewSectionLink() );
+ $section = $request->getVal( 'section' );
+
+ $msgKey = $title->exists() || ( $title->getNamespace() == NS_MEDIAWIKI && $title->getDefaultMessageText() !== false ) ?
+ "edit" : "create";
+ $content_navigation['views']['edit'] = array(
+ 'class' => ( $isEditing && ( $section !== 'new' || !$showNewSection ) ? 'selected' : '' ) . $isTalkClass,
+ 'text' => wfMessageFallback( "$skname-view-$msgKey", $msgKey )->setContext( $this->getContext() )->text(),
+ 'href' => $title->getLocalURL( $this->editUrlOptions() ),
+ 'primary' => true, // don't collapse this in vector
+ );
+
+ // section link
+ if ( $showNewSection ) {
// Adds new section link
//$content_navigation['actions']['addsection']
$content_navigation['views']['addsection'] = array(
- 'class' => $section == 'new' ? 'selected' : false,
- 'text' => wfMessageFallback( "$skname-action-addsection", 'addsection' )->text(),
+ 'class' => ( $isEditing && $section == 'new' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-addsection", 'addsection' )->setContext( $this->getContext() )->text(),
'href' => $title->getLocalURL( 'action=edit&section=new' )
);
}
- }
- // 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' );
-
- // 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_navigation['actions']['delete'] = array(
- 'class' => ( $onPage && $action == 'delete' ) ? 'selected' : false,
- 'text' => wfMessageFallback( "$skname-action-delete", 'delete' )->text(),
- 'href' => $title->getLocalURL( 'action=delete' )
+ // Checks if the page has some kind of viewable content
+ } elseif ( $title->hasSourceText() ) {
+ // Adds view source view link
+ $content_navigation['views']['viewsource'] = array(
+ 'class' => ( $onPage && $action == 'edit' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-viewsource", 'viewsource' )->setContext( $this->getContext() )->text(),
+ 'href' => $title->getLocalURL( $this->editUrlOptions() ),
+ 'primary' => true, // don't collapse this in vector
);
}
- 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()
+ wfProfileOut( __METHOD__ . '-edit' );
+
+ wfProfileIn( __METHOD__ . '-live' );
+ // Checks if the page exists
+ if ( $title->exists() ) {
+ // Adds history view link
+ $content_navigation['views']['history'] = array(
+ 'class' => ( $onPage && $action == 'history' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-view-history", 'history_short' )->setContext( $this->getContext() )->text(),
+ 'href' => $title->getLocalURL( 'action=history' ),
+ 'rel' => 'archives',
);
+
+ if ( $title->quickUserCan( 'delete', $user ) ) {
+ $content_navigation['actions']['delete'] = array(
+ 'class' => ( $onPage && $action == 'delete' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-delete", 'delete' )->setContext( $this->getContext() )->text(),
+ 'href' => $title->getLocalURL( 'action=delete' )
+ );
+ }
+
+ if ( $title->quickUserCan( 'move', $user ) ) {
+ $moveTitle = SpecialPage::getTitleFor( 'Movepage', $title->getPrefixedDBkey() );
+ $content_navigation['actions']['move'] = array(
+ 'class' => $this->getTitle()->isSpecial( 'Movepage' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-move", 'move' )->setContext( $this->getContext() )->text(),
+ 'href' => $moveTitle->getLocalURL()
+ );
+ }
+ } else {
+ // article doesn't exist or is deleted
+ if ( $user->isAllowed( 'deletedhistory' ) ) {
+ $n = $title->isDeleted();
+ if( $n ) {
+ $undelTitle = SpecialPage::getTitleFor( 'Undelete' );
+ // If the user can't undelete but can view deleted history show them a "View .. deleted" tab instead
+ $msgKey = $user->isAllowed( 'undelete' ) ? 'undelete' : 'viewdeleted';
+ $content_navigation['actions']['undelete'] = array(
+ 'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false,
+ 'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" )
+ ->setContext( $this->getContext() )->numParams( $n )->text(),
+ 'href' => $undelTitle->getLocalURL( array( 'target' => $title->getPrefixedDBkey() ) )
+ );
+ }
+ }
}
- if ( $title->getNamespace() !== NS_MEDIAWIKI && $wgUser->isAllowed( 'protect' ) ) {
- $mode = !$title->isProtected() ? 'protect' : 'unprotect';
+ if ( $title->getNamespace() !== NS_MEDIAWIKI && $title->quickUserCan( 'protect', $user ) ) {
+ $mode = $title->isProtected() ? 'unprotect' : 'protect';
$content_navigation['actions'][$mode] = array(
'class' => ( $onPage && $action == $mode ) ? 'selected' : false,
- 'text' => wfMessageFallback( "$skname-action-$mode", $mode )->text(),
+ 'text' => wfMessageFallback( "$skname-action-$mode", $mode )->setContext( $this->getContext() )->text(),
'href' => $title->getLocalURL( "action=$mode" )
);
}
- } else {
- // article doesn't exist or is deleted
- if ( $wgUser->isAllowed( 'deletedhistory' ) ) {
- $n = $title->isDeleted();
- if( $n ) {
- $undelTitle = SpecialPage::getTitleFor( 'Undelete' );
- // If the user can't undelete but can view deleted history show them a "View .. deleted" tab instead
- $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 ( $title->getNamespace() !== NS_MEDIAWIKI && $wgUser->isAllowed( 'protect' ) ) {
- $mode = !$title->getRestrictions( 'create' ) ? 'protect' : 'unprotect';
+ wfProfileOut( __METHOD__ . '-live' );
+
+ // 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, $user, $mode );
$content_navigation['actions'][$mode] = array(
- 'class' => ( $onPage && $action == $mode ) ? 'selected' : false,
- 'text' => wfMessageFallback( "$skname-action-$mode", $mode )->text(),
- 'href' => $title->getLocalURL( "action=$mode" )
+ 'class' => $onPage && ( $action == 'watch' || $action == 'unwatch' ) ? 'selected' : false,
+ 'text' => $this->msg( $mode )->text(), // uses 'watch' or 'unwatch' message
+ 'href' => $title->getLocalURL( array( 'action' => $mode, 'token' => $token ) )
);
}
}
- wfProfileOut( __METHOD__ . '-live' );
-
- // 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( 'SkinTemplateNavigation', array( &$this, &$content_navigation ) );
+
+ if ( $userCanRead && !$wgDisableLangConversion ) {
+ $pageLang = $title->getPageLanguage();
+ // Gets list of language variants
+ $variants = $pageLang->getVariants();
+ // Checks that language conversion is enabled and variants exist
+ // And if it is not in the special namespace
+ if( count( $variants ) > 1 ) {
+ // Gets preferred variant (note that user preference is
+ // only possible for wiki content language variant)
+ $preferred = $pageLang->getPreferredVariant();
+ // Loops over each variant
+ foreach( $variants as $code ) {
+ // Gets variant name from language code
+ $varname = $pageLang->getVariantname( $code );
+ // Checks if the variant is marked as disabled
+ if( $varname == 'disable' ) {
+ // Skips this variant
+ continue;
+ }
+ // Appends variant link
+ $content_navigation['variants'][] = array(
+ 'class' => ( $code == $preferred ) ? 'selected' : false,
+ 'text' => $varname,
+ 'href' => $title->getLocalURL( array( 'variant' => $code ) )
+ );
+ }
+ }
+ }
} else {
// If it's not content, it's got to be a special page
$content_navigation['namespaces']['special'] = array(
'class' => 'selected',
- 'text' => wfMsg( 'nstab-special' ),
- 'href' => $wgRequest->getRequestURL(), // @bug 2457, 2510
+ 'text' => $this->msg( 'nstab-special' )->text(),
+ 'href' => $request->getRequestURL(), // @bug 2457, 2510
'context' => 'subject'
);
@@ -1048,30 +1017,6 @@ class SkinTemplate extends Skin {
array( &$this, &$content_navigation ) );
}
- // Gets list of language variants
- $variants = $wgContLang->getVariants();
- // Checks that language conversion is enabled and variants exist
- if( !$wgDisableLangConversion && count( $variants ) > 1 ) {
- // Gets preferred variant
- $preferred = $wgContLang->getPreferredVariant();
- // Loops over each variant
- foreach( $variants as $code ) {
- // Gets variant name from language code
- $varname = $wgContLang->getVariantname( $code );
- // Checks if the variant is marked as disabled
- if( $varname == 'disable' ) {
- // Skips this variant
- continue;
- }
- // Appends variant link
- $content_navigation['variants'][] = array(
- 'class' => ( $code == $preferred ) ? 'selected' : false,
- 'text' => $varname,
- 'href' => $title->getLocalURL( '', $code )
- );
- }
- }
-
// Equiv to SkinTemplateContentActions
wfRunHooks( 'SkinTemplateNavigation::Universal', array( &$this, &$content_navigation ) );
@@ -1168,36 +1113,42 @@ class SkinTemplate extends Skin {
* @return array
* @private
*/
- protected function buildNavUrls( OutputPage $out ) {
- global $wgUseTrackbacks, $wgUser, $wgRequest;
+ protected function buildNavUrls() {
global $wgUploadNavigationUrl;
wfProfileIn( __METHOD__ );
- $action = $wgRequest->getVal( 'action', 'view' );
+ $out = $this->getOutput();
+ $request = $this->getRequest();
$nav_urls = array();
$nav_urls['mainpage'] = array( 'href' => self::makeMainPageUrl() );
if( $wgUploadNavigationUrl ) {
$nav_urls['upload'] = array( 'href' => $wgUploadNavigationUrl );
- } elseif( UploadBase::isEnabled() && UploadBase::isAllowed( $wgUser ) === true ) {
+ } elseif( UploadBase::isEnabled() && UploadBase::isAllowed( $this->getUser() ) === true ) {
$nav_urls['upload'] = array( 'href' => self::makeSpecialUrl( 'Upload' ) );
} else {
$nav_urls['upload'] = false;
}
$nav_urls['specialpages'] = array( 'href' => self::makeSpecialUrl( 'Specialpages' ) );
- // default permalink to being off, will override it as required below.
+ $nav_urls['print'] = false;
$nav_urls['permalink'] = false;
+ $nav_urls['whatlinkshere'] = false;
+ $nav_urls['recentchangeslinked'] = false;
+ $nav_urls['contributions'] = false;
+ $nav_urls['log'] = false;
+ $nav_urls['blockip'] = false;
+ $nav_urls['emailuser'] = false;
// 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( $out->isArticle() ) {
if ( !$out->isPrintable() ) {
$nav_urls['print'] = array(
- 'text' => wfMsg( 'printableversion' ),
+ 'text' => $this->msg( 'printableversion' )->text(),
'href' => $this->getTitle()->getLocalURL(
- $wgRequest->appendQueryValue( 'printable', 'yes', true ) )
+ $request->appendQueryValue( 'printable', 'yes', true ) )
);
}
@@ -1205,7 +1156,7 @@ class SkinTemplate extends Skin {
$revid = $this->getRevisionId();
if ( $revid ) {
$nav_urls['permalink'] = array(
- 'text' => wfMsg( 'permalink' ),
+ 'text' => $this->msg( 'permalink' )->text(),
'href' => $out->getTitle()->getLocalURL( "oldid=$revid" )
);
}
@@ -1215,72 +1166,45 @@ class SkinTemplate extends Skin {
array( &$this, &$nav_urls, &$revid, &$revid ) );
}
- if( $this->getTitle()->getNamespace() != NS_SPECIAL ) {
- $wlhTitle = SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage );
+ if ( $out->isArticleRelated() ) {
$nav_urls['whatlinkshere'] = array(
- 'href' => $wlhTitle->getLocalUrl()
+ 'href' => SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage )->getLocalUrl()
);
- if( $this->getTitle()->getArticleId() ) {
- $rclTitle = SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage );
+ if ( $this->getTitle()->getArticleId() ) {
$nav_urls['recentchangeslinked'] = array(
- 'href' => $rclTitle->getLocalUrl()
+ 'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage )->getLocalUrl()
);
- } else {
- $nav_urls['recentchangeslinked'] = false;
}
- if( $wgUseTrackbacks )
- $nav_urls['trackbacklink'] = array(
- 'href' => $out->getTitle()->trackbackURL()
- );
}
$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
$nav_urls['contributions'] = array(
'href' => self::makeSpecialUrlSubpage( 'Contributions', $rootUser )
);
- if( $id ) {
+ if ( $user->isLoggedIn() ) {
$logPage = SpecialPage::getTitleFor( 'Log' );
$nav_urls['log'] = array(
- 'href' => $logPage->getLocalUrl(
- array(
- 'user' => $rootUser
- )
- )
+ 'href' => $logPage->getLocalUrl( array( 'user' => $rootUser ) )
);
- } else {
- $nav_urls['log'] = false;
}
- if ( $wgUser->isAllowed( 'block' ) ) {
+ if ( $this->getUser()->isAllowed( 'block' ) ) {
$nav_urls['blockip'] = array(
'href' => self::makeSpecialUrlSubpage( 'Block', $rootUser )
);
- } else {
- $nav_urls['blockip'] = false;
}
- } else {
- $nav_urls['contributions'] = false;
- $nav_urls['log'] = false;
- $nav_urls['blockip'] = false;
- }
- $nav_urls['emailuser'] = false;
- if( $this->showEmailUser( $id ) ) {
- $nav_urls['emailuser'] = array(
- 'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser )
- );
+
+ if ( $this->showEmailUser( $user ) ) {
+ $nav_urls['emailuser'] = array(
+ 'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser )
+ );
+ }
}
+
wfProfileOut( __METHOD__ );
return $nav_urls;
}
@@ -1294,41 +1218,6 @@ class SkinTemplate extends Skin {
return $this->getTitle()->getNamespaceKey();
}
- /**
- * @private
- * @todo FIXME: Why is this duplicated in/from OutputPage::getHeadScripts()??
- */
- function setupUserJs( $allowUserJs ) {
- global $wgRequest, $wgJsMimeType;
- wfProfileIn( __METHOD__ );
-
- $action = $wgRequest->getVal( 'action', 'view' );
-
- if( $allowUserJs && $this->loggedin ) {
- if( $this->getTitle()->isJsSubpage() and $this->userCanPreview( $action ) ) {
- # XXX: additional security check/prompt?
- $this->userjsprev = '/*<![CDATA[*/ ' . $wgRequest->getText( 'wpTextbox1' ) . ' /*]]>*/';
- } else {
- $this->userjs = self::makeUrl( $this->userpage . '/' . $this->skinname . '.js', 'action=raw&ctype=' . $wgJsMimeType );
- }
- }
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Code for extensions to hook into to provide per-page CSS, see
- * extensions/PageCSS/PageCSS.php for an implementation of this.
- *
- * @private
- */
- function setupPageCss() {
- wfProfileIn( __METHOD__ );
- $out = false;
- wfRunHooks( 'SkinTemplateSetupPageCss', array( &$out ) );
- wfProfileOut( __METHOD__ );
- return $out;
- }
-
public function commonPrintStylesheet() {
return false;
}
@@ -1418,12 +1307,10 @@ abstract class QuickTemplate {
* @private
*/
function msgWiki( $str ) {
- global $wgParser, $wgOut;
+ global $wgOut;
$text = $this->translator->translate( $str );
- $parserOutput = $wgParser->parse( $text, $wgOut->getTitle(),
- $wgOut->parserOptions(), true );
- echo $parserOutput->getText();
+ echo $wgOut->parse( $text );
}
/**
@@ -1461,6 +1348,28 @@ abstract class QuickTemplate {
abstract class BaseTemplate extends QuickTemplate {
/**
+ * Get a Message object with its context set
+ *
+ * @param $name Str message name
+ * @return Message
+ */
+ public function getMsg( $name ) {
+ return $this->getSkin()->msg( $name );
+ }
+
+ function msg( $str ) {
+ echo $this->getMsg( $str )->escaped();
+ }
+
+ function msgHtml( $str ) {
+ echo $this->getMsg( $str )->text();
+ }
+
+ function msgWiki( $str ) {
+ echo $this->getMsg( $str )->parseAsBlock();
+ }
+
+ /**
* Create an array of common toolbox items from the data in the quicktemplate
* stored by SkinTemplate.
* The resulting array is built acording to a format intended to be passed
@@ -1470,20 +1379,16 @@ abstract class BaseTemplate extends QuickTemplate {
wfProfileIn( __METHOD__ );
$toolbox = array();
- if ( $this->data['notspecialpage'] ) {
+ if ( isset( $this->data['nav_urls']['whatlinkshere'] ) && $this->data['nav_urls']['whatlinkshere'] ) {
$toolbox['whatlinkshere'] = $this->data['nav_urls']['whatlinkshere'];
$toolbox['whatlinkshere']['id'] = 't-whatlinkshere';
- if ( $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 ( isset( $this->data['nav_urls']['recentchangeslinked'] ) && $this->data['nav_urls']['recentchangeslinked'] ) {
+ $toolbox['recentchangeslinked'] = $this->data['nav_urls']['recentchangeslinked'];
+ $toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
+ $toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
}
- if ( $this->data['feeds'] ) {
+ if ( isset( $this->data['feeds'] ) && $this->data['feeds'] ) {
$toolbox['feeds']['id'] = 'feedlinks';
$toolbox['feeds']['links'] = array();
foreach ( $this->data['feeds'] as $key => $feed ) {
@@ -1495,17 +1400,17 @@ abstract class BaseTemplate extends QuickTemplate {
}
}
foreach ( array( 'contributions', 'log', 'blockip', 'emailuser', 'upload', 'specialpages' ) as $special ) {
- if ( $this->data['nav_urls'][$special] ) {
+ if ( isset( $this->data['nav_urls'][$special] ) && $this->data['nav_urls'][$special] ) {
$toolbox[$special] = $this->data['nav_urls'][$special];
$toolbox[$special]['id'] = "t-$special";
}
}
- if ( !empty( $this->data['nav_urls']['print']['href'] ) ) {
+ if ( isset( $this->data['nav_urls']['print'] ) && $this->data['nav_urls']['print'] ) {
$toolbox['print'] = $this->data['nav_urls']['print'];
$toolbox['print']['rel'] = 'alternate';
$toolbox['print']['msg'] = 'printableversion';
}
- if( $this->data['nav_urls']['permalink'] ) {
+ if ( isset( $this->data['nav_urls']['permalink'] ) && $this->data['nav_urls']['permalink'] ) {
$toolbox['permalink'] = $this->data['nav_urls']['permalink'];
if( $toolbox['permalink']['href'] === '' ) {
unset( $toolbox['permalink']['href'] );
@@ -1583,13 +1488,13 @@ abstract class BaseTemplate extends QuickTemplate {
// Search is a special case, skins should custom implement this
$boxes[$boxName] = array(
'id' => "p-search",
- 'header' => wfMessage( 'search' )->text(),
+ 'header' => $this->getMsg( 'search' )->text(),
'generated' => false,
'content' => true,
);
break;
case 'TOOLBOX':
- $msgObj = wfMessage( 'toolbox' );
+ $msgObj = $this->getMsg( 'toolbox' );
$boxes[$boxName] = array(
'id' => "p-tb",
'header' => $msgObj->exists() ? $msgObj->text() : 'toolbox',
@@ -1599,7 +1504,7 @@ abstract class BaseTemplate extends QuickTemplate {
break;
case 'LANGUAGES':
if ( $this->data['language_urls'] ) {
- $msgObj = wfMessage( 'otherlanguages' );
+ $msgObj = $this->getMsg( 'otherlanguages' );
$boxes[$boxName] = array(
'id' => "p-lang",
'header' => $msgObj->exists() ? $msgObj->text() : 'otherlanguages',
@@ -1609,7 +1514,7 @@ abstract class BaseTemplate extends QuickTemplate {
}
break;
default:
- $msgObj = wfMessage( $boxName );
+ $msgObj = $this->getMsg( $boxName );
$boxes[$boxName] = array(
'id' => "p-$boxName",
'header' => $msgObj->exists() ? $msgObj->text() : $boxName,
diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php
index 022ef824..17a36ecf 100644
--- a/includes/SpecialPage.php
+++ b/includes/SpecialPage.php
@@ -31,7 +31,7 @@ class SpecialPage {
// The canonical name of this special page
// Also used for the default <h1> heading, @see getDescription()
- /*private*/ var $mName;
+ protected $mName;
// The local name of this special page
private $mLocalName;
@@ -67,6 +67,7 @@ class SpecialPage {
* @deprecated since 1.18
*/
static function initList() {
+ wfDeprecated( __METHOD__, '1.18' );
// Noop
}
@@ -74,6 +75,7 @@ class SpecialPage {
* @deprecated since 1.18
*/
static function initAliasList() {
+ wfDeprecated( __METHOD__, '1.18' );
// Noop
}
@@ -86,6 +88,7 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function resolveAlias( $alias ) {
+ wfDeprecated( __METHOD__, '1.18' );
list( $name, /*...*/ ) = SpecialPageFactory::resolveAlias( $alias );
return $name;
}
@@ -112,7 +115,7 @@ class SpecialPage {
* @deprecated since 1.7, warnings in 1.17, might be removed in 1.20
*/
static function addPage( &$page ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.7' );
SpecialPageFactory::getList()->{$page->mName} = $page;
}
@@ -125,6 +128,7 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function setGroup( $page, $group ) {
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialPageFactory::setGroup( $page, $group );
}
@@ -136,6 +140,7 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getGroup( &$page ) {
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialPageFactory::getGroup( $page );
}
@@ -148,6 +153,7 @@ class SpecialPage {
* @param $name String the page to remove
*/
static function removePage( $name ) {
+ wfDeprecated( __METHOD__, '1.18' );
unset( SpecialPageFactory::getList()->$name );
}
@@ -159,6 +165,7 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function exists( $name ) {
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialPageFactory::exists( $name );
}
@@ -170,6 +177,7 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getPage( $name ) {
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialPageFactory::getPage( $name );
}
@@ -182,6 +190,7 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getPageByAlias( $alias ) {
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialPageFactory::getPage( $alias );
}
@@ -189,11 +198,14 @@ class SpecialPage {
* Return categorised listable special pages which are available
* for the current user, and everyone.
*
+ * @param $user User object to check permissions, $wgUser will be used
+ * if not provided
* @return Associative array mapping page's name to its SpecialPage object
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
- static function getUsablePages() {
- return SpecialPageFactory::getUsablePages();
+ static function getUsablePages( User $user = null ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ return SpecialPageFactory::getUsablePages( $user );
}
/**
@@ -203,6 +215,7 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getRegularPages() {
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialPageFactory::getRegularPages();
}
@@ -214,6 +227,7 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getRestrictedPages() {
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialPageFactory::getRestrictedPages();
}
@@ -232,23 +246,11 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
public static function executePath( &$title, IContextSource &$context, $including = false ) {
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialPageFactory::executePath( $title, $context, $including );
}
/**
- * 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.
- *
- * @param $title Title
- * @return String: HTML fragment
- * @deprecated since 1.18 call SpecialPageFactory method directly
- */
- static function capturePath( &$title ) {
- return SpecialPageFactory::capturePath( $title );
- }
-
- /**
* Get the local name for a specified canonical name
*
* @param $name String
@@ -258,6 +260,7 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getLocalNameFor( $name, $subpage = false ) {
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialPageFactory::getLocalNameFor( $name, $subpage );
}
@@ -301,6 +304,7 @@ class SpecialPage {
* @deprecated since 1.18 call SpecialPageFactory method directly
*/
static function getTitleForAlias( $alias ) {
+ wfDeprecated( __METHOD__, '1.18' );
return SpecialPageFactory::getTitleForAlias( $alias );
}
@@ -344,12 +348,12 @@ class SpecialPage {
$this->mListed = $listed;
$this->mIncludable = $includable;
if ( !$function ) {
- $this->mFunction = 'wfSpecial'.$name;
+ $this->mFunction = 'wfSpecial' . $name;
} else {
$this->mFunction = $function;
}
if ( $file === 'default' ) {
- $this->mFile = dirname(__FILE__) . "/specials/Special$name.php";
+ $this->mFile = dirname( __FILE__ ) . "/specials/Special$name.php";
} else {
$this->mFile = $file;
}
@@ -364,11 +368,11 @@ class SpecialPage {
* @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' ) {
- // Deprecated messages now, remove in 1.18 or 1.20?
- wfDeprecated( __METHOD__ );
+ // Deprecated messages now, remove in 1.19 or 1.20?
+ wfDeprecated( __METHOD__, '1.17' );
+ // Sometimes $fName is SpecialPage, sometimes it's specialpage. <3 PHP
+ if ( strtolower( $fName ) == 'specialpage' ) {
$name = isset( $a[0] ) ? $a[0] : '';
$restriction = isset( $a[1] ) ? $a[1] : '';
$listed = isset( $a[2] ) ? $a[2] : true;
@@ -406,6 +410,7 @@ class SpecialPage {
* @deprecated since 1.18
*/
function getFile() {
+ wfDeprecated( __METHOD__, '1.18' );
return $this->mFile;
}
@@ -433,7 +438,7 @@ class SpecialPage {
* @param $x Bool
* @return Bool
*/
- function listed( $x = null) {
+ function listed( $x = null ) {
return wfSetVar( $this->mListed, $x );
}
@@ -441,7 +446,7 @@ class SpecialPage {
* Whether it's allowed to transclude the special page via {{Special:Foo/params}}
* @return Bool
*/
- public function isIncludable(){
+ public function isIncludable() {
return $this->mIncludable;
}
@@ -452,11 +457,43 @@ class SpecialPage {
* @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 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 name( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mName, $x ); }
+
+ /**
+ * These mutators are very evil, as the relevant variables should not mutate. So
+ * don't use them.
+ * @param $x Mixed
+ * @return Mixed
+ * @deprecated since 1.18
+ */
+ function restriction( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mRestriction, $x ); }
+
+ /**
+ * These mutators are very evil, as the relevant variables should not mutate. So
+ * don't use them.
+ * @param $x Mixed
+ * @return Mixed
+ * @deprecated since 1.18
+ */
+ function func( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mFunction, $x ); }
+
+ /**
+ * These mutators are very evil, as the relevant variables should not mutate. So
+ * don't use them.
+ * @param $x Mixed
+ * @return Mixed
+ * @deprecated since 1.18
+ */
+ function file( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mFile, $x ); }
+
+ /**
+ * These mutators are very evil, as the relevant variables should not mutate. So
+ * don't use them.
+ * @param $x Mixed
+ * @return Mixed
+ * @deprecated since 1.18
+ */
+ function includable( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mIncludable, $x ); }
/**
* Whether the special page is being evaluated via transclusion
@@ -499,7 +536,7 @@ class SpecialPage {
public function isRestricted() {
global $wgGroupPermissions;
// DWIM: If all anons can do something, then it is not restricted
- return $this->mRestriction != '' && empty($wgGroupPermissions['*'][$this->mRestriction]);
+ return $this->mRestriction != '' && empty( $wgGroupPermissions['*'][$this->mRestriction] );
}
/**
@@ -522,6 +559,29 @@ class SpecialPage {
}
/**
+ * Checks if userCanExecute, and if not throws a PermissionsError
+ *
+ * @since 1.19
+ */
+ public function checkPermissions() {
+ if ( !$this->userCanExecute( $this->getUser() ) ) {
+ $this->displayRestrictionError();
+ }
+ }
+
+ /**
+ * If the wiki is currently in readonly mode, throws a ReadOnlyError
+ *
+ * @since 1.19
+ * @throws ReadOnlyError
+ */
+ public function checkReadOnly() {
+ if ( wfReadOnly() ) {
+ throw new ReadOnlyError;
+ }
+ }
+
+ /**
* Sets headers - this should be called from the execute() method of all derived classes!
*/
function setHeaders() {
@@ -541,18 +601,15 @@ class SpecialPage {
*/
function execute( $par ) {
$this->setHeaders();
+ $this->checkPermissions();
- if ( $this->userCanExecute( $this->getUser() ) ) {
- $func = $this->mFunction;
- // only load file if the function does not exist
- if( !is_callable($func) && $this->mFile ) {
- require_once( $this->mFile );
- }
- $this->outputHeader();
- call_user_func( $func, $par, $this );
- } else {
- $this->displayRestrictionError();
+ $func = $this->mFunction;
+ // only load file if the function does not exist
+ if ( !is_callable( $func ) && $this->mFile ) {
+ require_once( $this->mFile );
}
+ $this->outputHeader();
+ call_user_func( $func, $par, $this );
}
/**
@@ -566,12 +623,12 @@ class SpecialPage {
function outputHeader( $summaryMessageKey = '' ) {
global $wgContLang;
- if( $summaryMessageKey == '' ) {
+ if ( $summaryMessageKey == '' ) {
$msg = $wgContLang->lc( $this->getName() ) . '-summary';
} else {
$msg = $summaryMessageKey;
}
- if ( !wfMessage( $msg )->isBlank() and ! $this->including() ) {
+ if ( !$this->msg( $msg )->isBlank() && !$this->including() ) {
$this->getOutput()->wrapWikiMsg(
"<div class='mw-specialpage-summary'>\n$1\n</div>", $msg );
}
@@ -589,7 +646,7 @@ class SpecialPage {
* @return String
*/
function getDescription() {
- return wfMsg( strtolower( $this->mName ) );
+ return $this->msg( strtolower( $this->mName ) )->text();
}
/**
@@ -615,7 +672,7 @@ class SpecialPage {
/**
* Gets the context this SpecialPage is executed in
*
- * @return IContextSource
+ * @return IContextSource|RequestContext
* @since 1.18
*/
public function getContext() {
@@ -670,20 +727,23 @@ class SpecialPage {
/**
* Shortcut to get user's language
*
+ * @deprecated 1.19 Use getLanguage instead
* @return Language
* @since 1.18
*/
public function getLang() {
- return $this->getContext()->getLang();
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->getLanguage();
}
/**
* Shortcut to get user's language
*
* @return Language
+ * @since 1.19
*/
public function getLanguage() {
- return $this->getContext()->getLang();
+ return $this->getContext()->getLanguage();
}
/**
@@ -717,14 +777,14 @@ class SpecialPage {
* @param $params array
*/
protected function addFeedLinks( $params ) {
- global $wgFeedClasses, $wgOut;
+ global $wgFeedClasses;
$feedTemplate = wfScript( 'api' ) . '?';
- foreach( $wgFeedClasses as $format => $class ) {
+ foreach ( $wgFeedClasses as $format => $class ) {
$theseParams = $params + array( 'feedformat' => $format );
$url = $feedTemplate . wfArrayToCGI( $theseParams );
- $wgOut->addFeedLink( $format, $url );
+ $this->getOutput()->addFeedLink( $format, $url );
}
}
}
@@ -764,9 +824,9 @@ abstract class FormSpecialPage extends SpecialPage {
$form = new HTMLForm( $this->fields, $this->getContext() );
$form->setSubmitCallback( array( $this, 'onSubmit' ) );
- $form->setWrapperLegend( wfMessage( strtolower( $this->getName() ) . '-legend' ) );
+ $form->setWrapperLegend( $this->msg( strtolower( $this->getName() ) . '-legend' ) );
$form->addHeaderText(
- wfMessage( strtolower( $this->getName() ) . '-text' )->parseAsBlock() );
+ $this->msg( strtolower( $this->getName() ) . '-text' )->parseAsBlock() );
// Retain query parameters (uselang etc)
$params = array_diff_key(
@@ -806,7 +866,7 @@ abstract class FormSpecialPage extends SpecialPage {
$this->setHeaders();
// This will throw exceptions if there's a problem
- $this->userCanExecute( $this->getUser() );
+ $this->checkExecutePermissions( $this->getUser() );
$form = $this->getForm();
if ( $form->show() ) {
@@ -818,31 +878,27 @@ abstract class FormSpecialPage extends SpecialPage {
* Maybe do something interesting with the subpage parameter
* @param $par String
*/
- protected function setParameter( $par ){}
+ 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
+ * Called from execute() to check if the given user can perform this action.
+ * Failures here must throw subclasses of ErrorPageError.
+ * @param $user 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() );
- }
+ protected function checkExecutePermissions( User $user ) {
+ $this->checkPermissions();
if ( $this->requiresUnblock() && $user->isBlocked() ) {
$block = $user->mBlock;
throw new UserBlockedError( $block );
}
+ if ( $this->requiresWrite() ) {
+ $this->checkReadOnly();
+ }
+
return true;
}
@@ -872,7 +928,7 @@ class UnlistedSpecialPage extends SpecialPage {
parent::__construct( $name, $restriction, false, $function, $file );
}
- public function isListed(){
+ public function isListed() {
return false;
}
}
@@ -888,7 +944,7 @@ class IncludableSpecialPage extends SpecialPage {
parent::__construct( $name, $restriction, $listed, $function, $file, true );
}
- public function isIncludable(){
+ public function isIncludable() {
return true;
}
}
@@ -905,7 +961,7 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage {
// Query parameteres added by redirects
protected $mAddedRedirectParams = array();
- public function execute( $par ){
+ public function execute( $par ) {
$redirect = $this->getRedirect( $par );
$query = $this->getRedirectQuery();
// Redirect to a page title with possible query parameters
@@ -945,13 +1001,13 @@ abstract class RedirectSpecialPage extends UnlistedSpecialPage {
public function getRedirectQuery() {
$params = array();
- foreach( $this->mAllowedRedirectParams as $arg ) {
- if( $this->getRequest()->getVal( $arg, null ) !== null ){
+ foreach ( $this->mAllowedRedirectParams as $arg ) {
+ if ( $this->getRequest()->getVal( $arg, null ) !== null ) {
$params[$arg] = $this->getRequest()->getVal( $arg );
}
}
- foreach( $this->mAddedRedirectParams as $arg => $val ) {
+ foreach ( $this->mAddedRedirectParams as $arg => $val ) {
$params[$arg] = $val;
}
@@ -985,20 +1041,20 @@ abstract class SpecialRedirectToSpecial extends RedirectSpecialPage {
}
/**
- * ListAdmins --> ListUsers/admin
+ * ListAdmins --> ListUsers/sysop
*/
class SpecialListAdmins extends SpecialRedirectToSpecial {
- function __construct(){
- parent::__construct( 'ListAdmins', 'ListUsers', 'sysop' );
+ function __construct() {
+ parent::__construct( 'Listadmins', 'Listusers', 'sysop' );
}
}
/**
- * ListBots --> ListUsers/admin
+ * ListBots --> ListUsers/bot
*/
class SpecialListBots extends SpecialRedirectToSpecial {
- function __construct(){
- parent::__construct( 'ListAdmins', 'ListUsers', 'bot' );
+ function __construct() {
+ parent::__construct( 'Listbots', 'Listusers', 'bot' );
}
}
@@ -1007,7 +1063,7 @@ class SpecialListBots extends SpecialRedirectToSpecial {
* @todo FIXME: This (and the rest of the login frontend) needs to die a horrible painful death
*/
class SpecialCreateAccount extends SpecialRedirectToSpecial {
- function __construct(){
+ function __construct() {
parent::__construct( 'CreateAccount', 'Userlogin', 'signup', array( 'uselang' ) );
}
}
@@ -1102,6 +1158,10 @@ class SpecialPermanentLink extends RedirectSpecialPage {
function getRedirect( $subpage ) {
$subpage = intval( $subpage );
+ if ( $subpage === 0 ) {
+ # throw an error page when no subpage was given
+ throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
+ }
$this->mAddedRedirectParams['oldid'] = $subpage;
return true;
}
diff --git a/includes/SpecialPageFactory.php b/includes/SpecialPageFactory.php
index 2a2e6a4c..0a1631b0 100644
--- a/includes/SpecialPageFactory.php
+++ b/includes/SpecialPageFactory.php
@@ -138,6 +138,7 @@ class SpecialPageFactory {
'Blankpage' => 'SpecialBlankpage',
'Blockme' => 'SpecialBlockme',
'Emailuser' => 'SpecialEmailUser',
+ 'JavaScriptTest' => 'SpecialJavaScriptTest',
'Movepage' => 'MovePageForm',
'Mycontributions' => 'SpecialMycontributions',
'Mypage' => 'SpecialMypage',
@@ -160,6 +161,7 @@ class SpecialPageFactory {
static function getList() {
global $wgSpecialPages;
global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication;
+ global $wgEnableEmail;
if ( !is_object( self::$mList ) ) {
wfProfileIn( __METHOD__ );
@@ -177,6 +179,10 @@ class SpecialPageFactory {
self::$mList['Invalidateemail'] = 'EmailInvalidation';
}
+ if ( $wgEnableEmail ) {
+ self::$mList['ChangeEmail'] = 'SpecialChangeEmail';
+ }
+
// Add extension special pages
self::$mList = array_merge( self::$mList, $wgSpecialPages );
@@ -262,7 +268,7 @@ class SpecialPageFactory {
*/
public static function setGroup( $page, $group ) {
global $wgSpecialPageGroups;
- $name = is_object( $page ) ? $page->mName : $page;
+ $name = is_object( $page ) ? $page->getName() : $page;
$wgSpecialPageGroups[$name] = $group;
}
@@ -272,23 +278,25 @@ class SpecialPageFactory {
* @param $page SpecialPage
*/
public static function getGroup( &$page ) {
+ $name = $page->getName();
+
global $wgSpecialPageGroups;
static $specialPageGroupsCache = array();
- if ( isset( $specialPageGroupsCache[$page->mName] ) ) {
- return $specialPageGroupsCache[$page->mName];
+ if ( isset( $specialPageGroupsCache[$name] ) ) {
+ return $specialPageGroupsCache[$name];
}
- $msg = wfMessage( 'specialpages-specialpagegroup-' . strtolower( $page->mName ) );
+ $msg = wfMessage( 'specialpages-specialpagegroup-' . strtolower( $name ) );
if ( !$msg->isBlank() ) {
$group = $msg->text();
} else {
- $group = isset( $wgSpecialPageGroups[$page->mName] )
- ? $wgSpecialPageGroups[$page->mName]
+ $group = isset( $wgSpecialPageGroups[$name] )
+ ? $wgSpecialPageGroups[$name]
: '-';
}
if ( $group == '-' ) {
$group = 'other';
}
- $specialPageGroupsCache[$page->mName] = $group;
+ $specialPageGroupsCache[$name] = $group;
return $group;
}
@@ -332,16 +340,21 @@ class SpecialPageFactory {
* Return categorised listable special pages which are available
* for the current user, and everyone.
*
+ * @param $user User object to check permissions, $wgUser will be used
+ * if not provided
* @return Array( String => Specialpage )
*/
- public static function getUsablePages() {
- global $wgUser;
+ public static function getUsablePages( User $user = null ) {
$pages = array();
+ if ( $user === null ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
foreach ( self::getList() as $name => $rec ) {
$page = self::getPage( $name );
if ( $page // not null
&& $page->isListed()
- && ( !$page->isRestricted() || $page->userCanExecute( $wgUser ) )
+ && ( !$page->isRestricted() || $page->userCanExecute( $user ) )
) {
$pages[$name] = $page;
}
@@ -417,7 +430,12 @@ class SpecialPageFactory {
if ( !$page ) {
$context->getOutput()->setArticleRelated( false );
$context->getOutput()->setRobotPolicy( 'noindex,nofollow' );
- $context->getOutput()->setStatusCode( 404 );
+
+ global $wgSend404Code;
+ if ( $wgSend404Code ) {
+ $context->getOutput()->setStatusCode( 404 );
+ }
+
$context->getOutput()->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
wfProfileOut( __METHOD__ );
return false;
@@ -462,40 +480,47 @@ class SpecialPageFactory {
}
/**
- * 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.
+ * Just like executePath() but will override global variables and execute
+ * the page in "inclusion" mode. Returns true if the execution was
+ * successful or false if there was no such special page, or a title object
+ * if it was a redirect.
*
- * Also saves the current $wgTitle, $wgOut, 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.
+ * Also saves the current $wgTitle, $wgOut, $wgRequest, $wgUser and $wgLang
+ * variables so that the special page will get the context it'd expect on a
+ * normal request, and then restores them to their previous values after.
*
* @param $title Title
+ * @param $context IContextSource
*
* @return String: HTML fragment
*/
- static function capturePath( &$title ) {
- global $wgOut, $wgTitle, $wgRequest;
+ static function capturePath( Title $title, IContextSource $context ) {
+ global $wgOut, $wgTitle, $wgRequest, $wgUser, $wgLang;
+ // Save current globals
$oldTitle = $wgTitle;
$oldOut = $wgOut;
$oldRequest = $wgRequest;
+ $oldUser = $wgUser;
+ $oldLang = $wgLang;
- // Don't want special pages interpreting ?feed=atom parameters.
- $wgRequest = new FauxRequest( array() );
-
- $context = new RequestContext;
- $context->setTitle( $title );
- $context->setRequest( $wgRequest );
+ // Set the globals to the current context
+ $wgTitle = $title;
$wgOut = $context->getOutput();
+ $wgRequest = $context->getRequest();
+ $wgUser = $context->getUser();
+ $wgLang = $context->getLanguage();
+ // The useful part
$ret = self::executePath( $title, $context, true );
- if ( $ret === true ) {
- $ret = $wgOut->getHTML();
- }
+
+ // And restore the old globals
$wgTitle = $oldTitle;
$wgOut = $oldOut;
$wgRequest = $oldRequest;
+ $wgUser = $oldUser;
+ $wgLang = $oldLang;
+
return $ret;
}
@@ -510,7 +535,7 @@ class SpecialPageFactory {
static function getLocalNameFor( $name, $subpage = false ) {
global $wgContLang;
$aliases = $wgContLang->getSpecialPageAliases();
-
+
if ( isset( $aliases[$name][0] ) ) {
$name = $aliases[$name][0];
} else {
diff --git a/includes/SquidPurgeClient.php b/includes/SquidPurgeClient.php
index 3de68578..506ada96 100644
--- a/includes/SquidPurgeClient.php
+++ b/includes/SquidPurgeClient.php
@@ -147,7 +147,7 @@ class SquidPurgeClient {
* @param $url string
*/
public function queuePurge( $url ) {
- $url = str_replace( "\n", '', $url );
+ $url = SquidUpdate::expand( str_replace( "\n", '', $url ) );
$this->requests[] = "PURGE $url HTTP/1.0\r\n" .
"Connection: Keep-Alive\r\n" .
"Proxy-Connection: Keep-Alive\r\n" .
diff --git a/includes/Status.php b/includes/Status.php
index 6bd94564..e9f3fb91 100644
--- a/includes/Status.php
+++ b/includes/Status.php
@@ -51,7 +51,7 @@ class Status {
/**
* Change operation result
*
- * @param $ok Boolean: whether to operation completed
+ * @param $ok Boolean: whether the operation completed
* @param $value Mixed
*/
function setResult( $ok, $value = null ) {
@@ -294,11 +294,11 @@ class Status {
}
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
@@ -308,7 +308,7 @@ class Status {
foreach ( $this->errors as $error ) {
if ( $error['type'] === $type ) {
$result[] = $error;
- }
+ }
}
return $result;
}
@@ -329,7 +329,7 @@ class Status {
}
/**
- * If the specified source message exists, replace it with the specified
+ * If the specified source message exists, replace it with the specified
* destination message, but keep the same parameters as in the original error.
*
* Return true if the replacement was done, false otherwise.
diff --git a/includes/StreamFile.php b/includes/StreamFile.php
index d08cfec6..0de03c83 100644
--- a/includes/StreamFile.php
+++ b/includes/StreamFile.php
@@ -4,119 +4,168 @@
*
* @file
*/
+class StreamFile {
+ const READY_STREAM = 1;
+ const NOT_MODIFIED = 2;
-/**
- * @param $fname string
- * @param $headers array
- */
-function wfStreamFile( $fname, $headers = array() ) {
- wfSuppressWarnings();
- $stat = stat( $fname );
- wfRestoreWarnings();
- if ( !$stat ) {
- header( 'HTTP/1.0 404 Not Found' );
- header( 'Cache-Control: no-cache' );
- header( 'Content-Type: text/html; charset=utf-8' );
- $encFile = htmlspecialchars( $fname );
- $encScript = htmlspecialchars( $_SERVER['SCRIPT_NAME'] );
- echo "<html><body>
-<h1>File not found</h1>
-<p>Although this PHP script ($encScript) exists, the file requested for output
-($encFile) does not.</p>
-</body></html>
-";
- return;
- }
+ /**
+ * Stream a file to the browser, adding all the headings and fun stuff.
+ * Headers sent include: Content-type, Content-Length, Last-Modified,
+ * and Content-Disposition.
+ *
+ * @param $fname string Full name and path of the file to stream
+ * @param $headers array Any additional headers to send
+ * @param $sendErrors bool Send error messages if errors occur (like 404)
+ * @return bool Success
+ */
+ public static function stream( $fname, $headers = array(), $sendErrors = true ) {
+ wfSuppressWarnings();
+ $stat = stat( $fname );
+ wfRestoreWarnings();
- header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', $stat['mtime'] ) . ' GMT' );
+ $res = self::prepareForStream( $fname, $stat, $headers, $sendErrors );
+ if ( $res == self::NOT_MODIFIED ) {
+ return true; // use client cache
+ } elseif ( $res == self::READY_STREAM ) {
+ return readfile( $fname );
+ } else {
+ return false; // failed
+ }
+ }
- // Cancel output buffering and gzipping if set
- wfResetOutputBuffers();
+ /**
+ * Call this function used in preparation before streaming a file.
+ * This function does the following:
+ * (a) sends Last-Modified, Content-type, and Content-Disposition headers
+ * (b) cancels any PHP output buffering and automatic gzipping of output
+ * (c) sends Content-Length header based on HTTP_IF_MODIFIED_SINCE check
+ *
+ * @param $path string Storage path or file system path
+ * @param $info Array|false File stat info with 'mtime' and 'size' fields
+ * @param $headers Array Additional headers to send
+ * @param $sendErrors bool Send error messages if errors occur (like 404)
+ * @return int|false READY_STREAM, NOT_MODIFIED, or false on failure
+ */
+ public static function prepareForStream(
+ $path, $info, $headers = array(), $sendErrors = true
+ ) {
+ global $wgLanguageCode;
- $type = wfGetType( $fname );
- if ( $type and $type!="unknown/unknown") {
- header("Content-type: $type");
- } else {
- header('Content-type: application/x-wiki');
- }
+ if ( !is_array( $info ) ) {
+ if ( $sendErrors ) {
+ header( 'HTTP/1.0 404 Not Found' );
+ header( 'Cache-Control: no-cache' );
+ header( 'Content-Type: text/html; charset=utf-8' );
+ $encFile = htmlspecialchars( $path );
+ $encScript = htmlspecialchars( $_SERVER['SCRIPT_NAME'] );
+ echo "<html><body>
+ <h1>File not found</h1>
+ <p>Although this PHP script ($encScript) exists, the file requested for output
+ ($encFile) does not.</p>
+ </body></html>
+ ";
+ }
+ return false;
+ }
- // Don't stream it out as text/html if there was a PHP error
- if ( headers_sent() ) {
- echo "Headers already sent, terminating.\n";
- return;
- }
+ // Sent Last-Modified HTTP header for client-side caching
+ header( 'Last-Modified: ' . wfTimestamp( TS_RFC2822, $info['mtime'] ) );
- global $wgLanguageCode;
- header( "Content-Disposition: inline;filename*=utf-8'$wgLanguageCode'" . urlencode( basename( $fname ) ) );
+ // Cancel output buffering and gzipping if set
+ wfResetOutputBuffers();
- foreach ( $headers as $header ) {
- header( $header );
- }
+ $type = self::contentTypeFromPath( $path );
+ if ( $type && $type != 'unknown/unknown' ) {
+ header( "Content-type: $type" );
+ } else {
+ // Send a content type which is not known to Internet Explorer, to
+ // avoid triggering IE's content type detection. Sending a standard
+ // unknown content type here essentially gives IE license to apply
+ // whatever content type it likes.
+ header( 'Content-type: application/x-wiki' );
+ }
- if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
- $modsince = preg_replace( '/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
- $sinceTime = strtotime( $modsince );
- if ( $stat['mtime'] <= $sinceTime ) {
- ini_set('zlib.output_compression', 0);
- header( "HTTP/1.0 304 Not Modified" );
- return;
+ // Don't stream it out as text/html if there was a PHP error
+ if ( headers_sent() ) {
+ echo "Headers already sent, terminating.\n";
+ return false;
}
- }
- header( 'Content-Length: ' . $stat['size'] );
+ header( "Content-Disposition: inline;filename*=utf-8'$wgLanguageCode'" .
+ urlencode( basename( $path ) ) );
- readfile( $fname );
-}
+ // Send additional headers
+ foreach ( $headers as $header ) {
+ header( $header );
+ }
-/**
- * @param $filename string
- * @param $safe bool
- * @return null|string
- */
-function wfGetType( $filename, $safe = true ) {
- global $wgTrivialMimeDetection;
-
- $ext = strrchr($filename, '.');
- $ext = $ext === false ? '' : strtolower( substr( $ext, 1 ) );
-
- # trivial detection by file extension,
- # used for thumbnails (thumb.php)
- if ($wgTrivialMimeDetection) {
- switch ($ext) {
- case 'gif': return 'image/gif';
- case 'png': return 'image/png';
- case 'jpg': return 'image/jpeg';
- case 'jpeg': return 'image/jpeg';
+ // Don't send if client has up to date cache
+ if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
+ $modsince = preg_replace( '/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
+ if ( wfTimestamp( TS_UNIX, $info['mtime'] ) <= strtotime( $modsince ) ) {
+ ini_set( 'zlib.output_compression', 0 );
+ header( "HTTP/1.0 304 Not Modified" );
+ return self::NOT_MODIFIED; // ok
+ }
}
- return 'unknown/unknown';
+ header( 'Content-Length: ' . $info['size'] );
+
+ return self::READY_STREAM; // ok
}
-
- $magic = MimeMagic::singleton();
- // Use the extension only, rather than magic numbers, to avoid opening
- // up vulnerabilities due to uploads of files with allowed extensions
- // but disallowed types.
- $type = $magic->guessTypesForExtension( $ext );
/**
- * Double-check some security settings that were done on upload but might
- * have changed since.
+ * Determine the file type of a file based on the path
+ *
+ * @param $filename string Storage path or file system path
+ * @param $safe bool Whether to do retroactive upload blacklist checks
+ * @return null|string
*/
- if ( $safe ) {
- global $wgFileBlacklist, $wgCheckFileExtensions, $wgStrictFileExtensions,
- $wgFileExtensions, $wgVerifyMimeType, $wgMimeTypeBlacklist;
- list( , $extList ) = UploadBase::splitExtensions( $filename );
- if ( UploadBase::checkFileExtensionList( $extList, $wgFileBlacklist ) ) {
- return 'unknown/unknown';
- }
- if ( $wgCheckFileExtensions && $wgStrictFileExtensions
- && !UploadBase::checkFileExtensionList( $extList, $wgFileExtensions ) )
- {
+ public static function contentTypeFromPath( $filename, $safe = true ) {
+ global $wgTrivialMimeDetection;
+
+ $ext = strrchr( $filename, '.' );
+ $ext = $ext === false ? '' : strtolower( substr( $ext, 1 ) );
+
+ # trivial detection by file extension,
+ # used for thumbnails (thumb.php)
+ if ( $wgTrivialMimeDetection ) {
+ switch ( $ext ) {
+ case 'gif': return 'image/gif';
+ case 'png': return 'image/png';
+ case 'jpg': return 'image/jpeg';
+ case 'jpeg': return 'image/jpeg';
+ }
+
return 'unknown/unknown';
}
- if ( $wgVerifyMimeType && in_array( strtolower( $type ), $wgMimeTypeBlacklist ) ) {
- return 'unknown/unknown';
+
+ $magic = MimeMagic::singleton();
+ // Use the extension only, rather than magic numbers, to avoid opening
+ // up vulnerabilities due to uploads of files with allowed extensions
+ // but disallowed types.
+ $type = $magic->guessTypesForExtension( $ext );
+
+ /**
+ * Double-check some security settings that were done on upload but might
+ * have changed since.
+ */
+ if ( $safe ) {
+ global $wgFileBlacklist, $wgCheckFileExtensions, $wgStrictFileExtensions,
+ $wgFileExtensions, $wgVerifyMimeType, $wgMimeTypeBlacklist;
+ list( , $extList ) = UploadBase::splitExtensions( $filename );
+ if ( UploadBase::checkFileExtensionList( $extList, $wgFileBlacklist ) ) {
+ return 'unknown/unknown';
+ }
+ if ( $wgCheckFileExtensions && $wgStrictFileExtensions
+ && !UploadBase::checkFileExtensionList( $extList, $wgFileExtensions ) )
+ {
+ return 'unknown/unknown';
+ }
+ if ( $wgVerifyMimeType && in_array( strtolower( $type ), $wgMimeTypeBlacklist ) ) {
+ return 'unknown/unknown';
+ }
}
+ return $type;
}
- return $type;
}
diff --git a/includes/StubObject.php b/includes/StubObject.php
index 951cbaea..647ad929 100644
--- a/includes/StubObject.php
+++ b/includes/StubObject.php
@@ -60,6 +60,7 @@ class StubObject {
/**
* Create a new object to replace this stub object.
+ * @return object
*/
function _newObject() {
return MWFunction::newObj( $this->mClass, $this->mParams );
@@ -89,9 +90,10 @@ class StubObject {
function _unstub( $name = '_unstub', $level = 2 ) {
static $recursionLevel = 0;
- if ( !($GLOBALS[$this->mGlobal] instanceof StubObject) )
+ if ( !($GLOBALS[$this->mGlobal] instanceof StubObject) ) {
return $GLOBALS[$this->mGlobal]; // already unstubbed.
-
+ }
+
if ( get_class( $GLOBALS[$this->mGlobal] ) != $this->mClass ) {
$fname = __METHOD__.'-'.$this->mGlobal;
wfProfileIn( $fname );
@@ -116,6 +118,7 @@ class StubObject {
class StubContLang extends StubObject {
function __construct() {
+ wfDeprecated( __CLASS__, '1.18' );
parent::__construct( 'wgContLang' );
}
@@ -123,6 +126,9 @@ class StubContLang extends StubObject {
return $this->_call( $name, $args );
}
+ /**
+ * @return Language
+ */
function _newObject() {
global $wgLanguageCode;
$obj = Language::factory( $wgLanguageCode );
@@ -147,7 +153,10 @@ class StubUserLang extends StubObject {
return $this->_call( $name, $args );
}
+ /**
+ * @return Language
+ */
function _newObject() {
- return RequestContext::getMain()->getLang();
+ return RequestContext::getMain()->getLanguage();
}
}
diff --git a/includes/Title.php b/includes/Title.php
index 02607496..f3cf79d4 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -47,7 +47,6 @@ class Title {
*/
const GAID_FOR_UPDATE = 1;
-
/**
* @name Private member variables
* Please use the accessor functions instead.
@@ -64,6 +63,7 @@ class Title {
var $mFragment; // /< Title fragment (i.e. the bit after the #)
var $mArticleID = -1; // /< Article ID, fetched from the link cache on demand
var $mLatestID = false; // /< ID of most recent revision
+ private $mEstimateRevisions; // /< Estimated number of revisions; null of not loaded
var $mRestrictions = array(); // /< Array of groups allowed to edit this article
var $mOldRestrictions = false;
var $mCascadeRestriction; ///< Cascade restrictions on this page to included templates and images?
@@ -388,8 +388,8 @@ class Title {
$titles = array( $title );
while ( --$recurse > 0 ) {
if ( $title->isRedirect() ) {
- $article = new Article( $title, 0 );
- $newtitle = $article->getRedirectTarget();
+ $page = WikiPage::factory( $title );
+ $newtitle = $page->getRedirectTarget();
} else {
break;
}
@@ -443,10 +443,6 @@ class Title {
return null;
}
-# ----------------------------------------------------------------------------
-# Static functions
-# ----------------------------------------------------------------------------
-
/**
* Get the prefixed DB key associated with an ID
*
@@ -481,6 +477,33 @@ class Title {
}
/**
+ * Returns a simple regex that will match on characters and sequences invalid in titles.
+ * Note that this doesn't pick up many things that could be wrong with titles, but that
+ * replacing this regex with something valid will make many titles valid.
+ *
+ * @return String regex string
+ */
+ static function getTitleInvalidRegex() {
+ static $rxTc = false;
+ if ( !$rxTc ) {
+ # Matching titles will be held as illegal.
+ $rxTc = '/' .
+ # Any character not allowed is forbidden...
+ '[^' . self::legalChars() . ']' .
+ # URL percent encoding sequences interfere with the ability
+ # to round-trip titles -- you can't link to them consistently.
+ '|%[0-9A-Fa-f]{2}' .
+ # XML/HTML character references produce similar issues.
+ '|&[A-Za-z0-9\x80-\xff]+;' .
+ '|&#[0-9]+;' .
+ '|&#x[0-9A-Fa-f]+;' .
+ '/S';
+ }
+
+ return $rxTc;
+ }
+
+ /**
* Get a string representation of a title suitable for
* including in a search index
*
@@ -532,6 +555,36 @@ class Title {
}
/**
+ * Escape a text fragment, say from a link, for a URL
+ *
+ * @param $fragment string containing a URL or link fragment (after the "#")
+ * @return String: escaped string
+ */
+ static function escapeFragmentForURL( $fragment ) {
+ # Note that we don't urlencode the fragment. urlencoded Unicode
+ # fragments appear not to work in IE (at least up to 7) or in at least
+ # one version of Opera 9.x. The W3C validator, for one, doesn't seem
+ # to care if they aren't encoded.
+ return Sanitizer::escapeId( $fragment, 'noninitial' );
+ }
+
+ /**
+ * 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 ) {
+ if ( $a->getNamespace() == $b->getNamespace() ) {
+ return strcmp( $a->getText(), $b->getText() );
+ } else {
+ return $a->getNamespace() - $b->getNamespace();
+ }
+ }
+
+ /**
* Determine whether the object refers to a page within
* this project.
*
@@ -546,6 +599,24 @@ class Title {
}
/**
+ * Is this Title interwiki?
+ *
+ * @return Bool
+ */
+ public function isExternal() {
+ return ( $this->mInterwiki != '' );
+ }
+
+ /**
+ * Get the interwiki prefix (or null string)
+ *
+ * @return String Interwiki prefix
+ */
+ public function getInterwiki() {
+ return $this->mInterwiki;
+ }
+
+ /**
* Determine whether the object refers to a page within
* this project and is transcludable.
*
@@ -573,51 +644,49 @@ class Title {
}
/**
- * Escape a text fragment, say from a link, for a URL
- *
- * @param $fragment string containing a URL or link fragment (after the "#")
- * @return String: escaped string
- */
- static function escapeFragmentForURL( $fragment ) {
- # Note that we don't urlencode the fragment. urlencoded Unicode
- # fragments appear not to work in IE (at least up to 7) or in at least
- # one version of Opera 9.x. The W3C validator, for one, doesn't seem
- # to care if they aren't encoded.
- return Sanitizer::escapeId( $fragment, 'noninitial' );
- }
-
-# ----------------------------------------------------------------------------
-# Other stuff
-# ----------------------------------------------------------------------------
-
- /** Simple accessors */
- /**
* Get the text form (spaces not underscores) of the main part
*
* @return String Main part of the title
*/
- public function getText() { return $this->mTextform; }
+ public function getText() {
+ return $this->mTextform;
+ }
/**
* Get the URL-encoded form of the main part
*
* @return String Main part of the title, URL-encoded
*/
- public function getPartialURL() { return $this->mUrlform; }
+ public function getPartialURL() {
+ return $this->mUrlform;
+ }
/**
* Get the main part with underscores
*
* @return String: Main part of the title, with underscores
*/
- public function getDBkey() { return $this->mDbkeyform; }
+ public function getDBkey() {
+ return $this->mDbkeyform;
+ }
+
+ /**
+ * Get the DB key with the initial letter case as specified by the user
+ *
+ * @return String DB key
+ */
+ function getUserCaseDBKey() {
+ return $this->mUserCaseDBKey;
+ }
/**
* Get the namespace index, i.e. one of the NS_xxxx constants.
*
* @return Integer: Namespace index
*/
- public function getNamespace() { return $this->mNamespace; }
+ public function getNamespace() {
+ return $this->mNamespace;
+ }
/**
* Get the namespace text
@@ -657,15 +726,6 @@ class Title {
}
/**
- * Get the DB key with the initial letter case as specified by the user
- *
- * @return String DB key
- */
- function getUserCaseDBKey() {
- return $this->mUserCaseDBKey;
- }
-
- /**
* Get the namespace text of the subject (rather than talk) page
*
* @return String Namespace text
@@ -695,18 +755,306 @@ class Title {
}
/**
- * Get the interwiki prefix (or null string)
+ * Is this in a namespace that allows actual pages?
*
- * @return String Interwiki prefix
+ * @return Bool
+ * @internal note -- uses hardcoded namespace index instead of constants
+ */
+ public function canExist() {
+ return $this->mNamespace >= NS_MAIN;
+ }
+
+ /**
+ * Can this title be added to a user's watchlist?
+ *
+ * @return Bool TRUE or FALSE
+ */
+ public function isWatchable() {
+ return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
+ }
+
+ /**
+ * Returns true if this is a special page.
+ *
+ * @return boolean
+ */
+ public function isSpecialPage() {
+ return $this->getNamespace() == NS_SPECIAL;
+ }
+
+ /**
+ * Returns true if this title resolves to the named special page
+ *
+ * @param $name String The special page name
+ * @return boolean
*/
- public function getInterwiki() { return $this->mInterwiki; }
+ public function isSpecial( $name ) {
+ if ( $this->isSpecialPage() ) {
+ list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
+ if ( $name == $thisName ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 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 Title
+ */
+ public function fixSpecialName() {
+ if ( $this->isSpecialPage() ) {
+ list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
+ if ( $canonicalName ) {
+ $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
+ if ( $localName != $this->mDbkeyform ) {
+ return Title::makeTitle( NS_SPECIAL, $localName );
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Returns true if the title is inside the specified namespace.
+ *
+ * Please make use of this instead of comparing to getNamespace()
+ * This function is much more resistant to changes we may make
+ * to namespaces than code that makes direct comparisons.
+ * @param $ns int The namespace
+ * @return bool
+ * @since 1.19
+ */
+ public function inNamespace( $ns ) {
+ return MWNamespace::equals( $this->getNamespace(), $ns );
+ }
+
+ /**
+ * Returns true if the title is inside one of the specified namespaces.
+ *
+ * @param ...$namespaces The namespaces to check for
+ * @return bool
+ * @since 1.19
+ */
+ public function inNamespaces( /* ... */ ) {
+ $namespaces = func_get_args();
+ if ( count( $namespaces ) > 0 && is_array( $namespaces[0] ) ) {
+ $namespaces = $namespaces[0];
+ }
+
+ foreach ( $namespaces as $ns ) {
+ if ( $this->inNamespace( $ns ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if the title has the same subject namespace as the
+ * namespace specified.
+ * For example this method will take NS_USER and return true if namespace
+ * is either NS_USER or NS_USER_TALK since both of them have NS_USER
+ * as their subject namespace.
+ *
+ * This is MUCH simpler than individually testing for equivilance
+ * against both NS_USER and NS_USER_TALK, and is also forward compatible.
+ * @since 1.19
+ */
+ public function hasSubjectNamespace( $ns ) {
+ return MWNamespace::subjectEquals( $this->getNamespace(), $ns );
+ }
+
+ /**
+ * Is this Title in a namespace which contains content?
+ * In other words, is this a content page, for the purposes of calculating
+ * statistics, etc?
+ *
+ * @return Boolean
+ */
+ public function isContentPage() {
+ return MWNamespace::isContent( $this->getNamespace() );
+ }
+
+ /**
+ * Would anybody with sufficient privileges be able to move this page?
+ * Some pages just aren't movable.
+ *
+ * @return Bool TRUE or FALSE
+ */
+ public function isMovable() {
+ if ( !MWNamespace::isMovable( $this->getNamespace() ) || $this->getInterwiki() != '' ) {
+ // Interwiki title or immovable namespace. Hooks don't get to override here
+ return false;
+ }
+
+ $result = true;
+ wfRunHooks( 'TitleIsMovable', array( $this, &$result ) );
+ return $result;
+ }
+
+ /**
+ * 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();
+ *
+ * @since 1.18
+ * @return Bool
+ */
+ public function isMainPage() {
+ return $this->equals( Title::newMainPage() );
+ }
+
+ /**
+ * Is this a subpage?
+ *
+ * @return Bool
+ */
+ public function isSubpage() {
+ return MWNamespace::hasSubpages( $this->mNamespace )
+ ? strpos( $this->getText(), '/' ) !== false
+ : false;
+ }
+
+ /**
+ * Is this a conversion table for the LanguageConverter?
+ *
+ * @return Bool
+ */
+ public function isConversionTable() {
+ return $this->getNamespace() == NS_MEDIAWIKI &&
+ strpos( $this->getText(), 'Conversiontable' ) !== false;
+ }
+
+ /**
+ * Does that page contain wikitext, or it is JS, CSS or whatever?
+ *
+ * @return Bool
+ */
+ public function isWikitextPage() {
+ $retval = !$this->isCssOrJsPage() && !$this->isCssJsSubpage();
+ wfRunHooks( 'TitleIsWikitextPage', array( $this, &$retval ) );
+ return $retval;
+ }
+
+ /**
+ * Could this page contain custom CSS or JavaScript, based
+ * on the title?
+ *
+ * @return Bool
+ */
+ public function isCssOrJsPage() {
+ $retval = $this->mNamespace == NS_MEDIAWIKI
+ && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
+ wfRunHooks( 'TitleIsCssOrJsPage', array( $this, &$retval ) );
+ return $retval;
+ }
+
+ /**
+ * Is this a .css or .js subpage of a user page?
+ * @return Bool
+ */
+ public function isCssJsSubpage() {
+ return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
+ }
+
+ /**
+ * Trim down a .css or .js subpage title to get the corresponding skin name
+ *
+ * @return string containing skin name from .css or .js subpage title
+ */
+ public function getSkinFromCssJsSubpage() {
+ $subpage = explode( '/', $this->mTextform );
+ $subpage = $subpage[ count( $subpage ) - 1 ];
+ $lastdot = strrpos( $subpage, '.' );
+ if ( $lastdot === false )
+ return $subpage; # Never happens: only called for names ending in '.css' or '.js'
+ return substr( $subpage, 0, $lastdot );
+ }
+
+ /**
+ * Is this a .css subpage of a user page?
+ *
+ * @return Bool
+ */
+ public function isCssSubpage() {
+ return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
+ }
+
+ /**
+ * Is this a .js subpage of a user page?
+ *
+ * @return Bool
+ */
+ public function isJsSubpage() {
+ return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
+ }
+
+ /**
+ * Is this a talk page of some sort?
+ *
+ * @return Bool
+ */
+ public function isTalkPage() {
+ return MWNamespace::isTalk( $this->getNamespace() );
+ }
+
+ /**
+ * Get a Title object associated with the talk page of this article
+ *
+ * @return Title the object for the talk page
+ */
+ public function getTalkPage() {
+ return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
+ }
+
+ /**
+ * Get a title object associated with the subject page of this
+ * talk page
+ *
+ * @return Title the object for the subject page
+ */
+ public function getSubjectPage() {
+ // Is this the same title?
+ $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
+ if ( $this->getNamespace() == $subjectNS ) {
+ return $this;
+ }
+ return Title::makeTitle( $subjectNS, $this->getDBkey() );
+ }
+
+ /**
+ * Get the default namespace index, for when there is no namespace
+ *
+ * @return Int Default namespace index
+ */
+ public function getDefaultNamespace() {
+ return $this->mDefaultNamespace;
+ }
+
+ /**
+ * Get title for search index
+ *
+ * @return String a stripped-down title string ready for the
+ * search index
+ */
+ public function getIndexTitle() {
+ return Title::indexTitle( $this->mNamespace, $this->mTextform );
+ }
/**
* Get the Title fragment (i.e.\ the bit after the #) in text form
*
* @return String Title fragment
*/
- public function getFragment() { return $this->mFragment; }
+ public function getFragment() {
+ return $this->mFragment;
+ }
/**
* Get the fragment in URL form, including the "#" character if there is one
@@ -721,20 +1069,37 @@ class Title {
}
/**
- * Get the default namespace index, for when there is no namespace
+ * Set the fragment for this title. Removes the first character from the
+ * specified fragment before setting, so it assumes you're passing it with
+ * an initial "#".
*
- * @return Int Default namespace index
+ * Deprecated for public use, use Title::makeTitle() with fragment parameter.
+ * Still in active use privately.
+ *
+ * @param $fragment String text
*/
- public function getDefaultNamespace() { return $this->mDefaultNamespace; }
+ public function setFragment( $fragment ) {
+ $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
+ }
/**
- * Get title for search index
+ * Prefix some arbitrary text with the namespace or interwiki prefix
+ * of this object
*
- * @return String a stripped-down title string ready for the
- * search index
+ * @param $name String the text
+ * @return String the prefixed text
+ * @private
*/
- public function getIndexTitle() {
- return Title::indexTitle( $this->mNamespace, $this->mTextform );
+ private function prefix( $name ) {
+ $p = '';
+ if ( $this->mInterwiki != '' ) {
+ $p = $this->mInterwiki . ':';
+ }
+
+ if ( 0 != $this->mNamespace ) {
+ $p .= $this->getNsText() . ':';
+ }
+ return $p . $name;
}
/**
@@ -766,6 +1131,15 @@ class Title {
}
/**
+ * Return a string representation of this title
+ *
+ * @return String representation of this title
+ */
+ public function __toString() {
+ return $this->getPrefixedText();
+ }
+
+ /**
* Get the prefixed title with spaces, plus any fragment
* (part beginning with '#')
*
@@ -811,6 +1185,17 @@ class Title {
}
/**
+ * Get the HTML-escaped displayable text form.
+ * Used for the title field in <a> tags.
+ *
+ * @return String the text, including any prefixes
+ */
+ public function getEscapedText() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return htmlspecialchars( $this->getPrefixedText() );
+ }
+
+ /**
* Get a URL-encoded form of the subpage text
*
* @return String URL-encoded subpage name
@@ -833,48 +1218,64 @@ class Title {
}
/**
- * Get a real URL referring to this title, with interwiki link and
- * fragment
+ * Helper to fix up the get{Local,Full,Link,Canonical}URL args
+ * get{Canonical,Full,Link,Local}URL methods accepted an optional
+ * second argument named variant. This was deprecated in favor
+ * of passing an array of option with a "variant" key
+ * Once $query2 is removed for good, this helper can be dropped
+ * andthe wfArrayToCGI moved to getLocalURL();
*
- * @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 String language variant of url (for sr, zh..)
- * @return String the URL
+ * @since 1.19 (r105919)
*/
- public function getFullURL( $query = '', $variant = false ) {
- global $wgServer, $wgRequest;
-
+ private static function fixUrlQueryArgs( $query, $query2 = false ) {
+ if( $query2 !== false ) {
+ wfDeprecated( "Title::get{Canonical,Full,Link,Local} method called with a second parameter is deprecated. Add your parameter to an array passed as the first parameter.", "1.19" );
+ }
if ( is_array( $query ) ) {
$query = wfArrayToCGI( $query );
}
-
- $interwiki = Interwiki::fetch( $this->mInterwiki );
- if ( !$interwiki ) {
- $url = $this->getLocalURL( $query, $variant );
-
- // Ugly quick hack to avoid duplicate prefixes (bug 4571 etc)
- // Correct fix would be to move the prepending elsewhere.
- if ( $wgRequest->getVal( 'action' ) != 'render' ) {
- $url = $wgServer . $url;
+ if ( $query2 ) {
+ if ( is_string( $query2 ) ) {
+ // $query2 is a string, we will consider this to be
+ // a deprecated $variant argument and add it to the query
+ $query2 = wfArrayToCGI( array( 'variant' => $query2 ) );
+ } else {
+ $query2 = wfArrayToCGI( $query2 );
}
- } else {
- $baseUrl = $interwiki->getURL();
-
- $namespace = wfUrlencode( $this->getNsText() );
- if ( $namespace != '' ) {
- # Can this actually happen? Interwikis shouldn't be parsed.
- # Yes! It can in interwiki transclusion. But... it probably shouldn't.
- $namespace .= ':';
+ // If we have $query content add a & to it first
+ if ( $query ) {
+ $query .= '&';
}
- $url = str_replace( '$1', $namespace . $this->mUrlform, $baseUrl );
- $url = wfAppendQuery( $url, $query );
+ // Now append the queries together
+ $query .= $query2;
}
+ return $query;
+ }
+
+ /**
+ * Get a real URL referring to this title, with interwiki link and
+ * fragment
+ *
+ * See getLocalURL for the arguments.
+ *
+ * @see self::getLocalURL
+ * @return String the URL
+ */
+ public function getFullURL( $query = '', $query2 = false ) {
+ $query = self::fixUrlQueryArgs( $query, $query2 );
+
+ # Hand off all the decisions on urls to getLocalURL
+ $url = $this->getLocalURL( $query );
+
+ # Expand the url to make it a full url. Note that getLocalURL has the
+ # potential to output full urls for a variety of reasons, so we use
+ # wfExpandUrl instead of simply prepending $wgServer
+ $url = wfExpandUrl( $url, PROTO_RELATIVE );
# Finally, add the fragment.
$url .= $this->getFragmentForURL();
- wfRunHooks( 'GetFullURL', array( &$this, &$url, $query, $variant ) );
+ wfRunHooks( 'GetFullURL', array( &$this, &$url, $query ) );
return $url;
}
@@ -882,48 +1283,45 @@ class Title {
* Get a URL with no fragment or server name. If this page is generated
* with action=render, $wgServer is prepended.
*
- * @param $query Mixed: an optional query string; if not specified,
- * $wgArticlePath will be used. Can be specified as an associative array
- * as well, e.g., array( 'action' => 'edit' ) (keys and values will be
- * URL-escaped).
- * @param $variant String language variant of url (for sr, zh..)
+
+ * @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).
+ * Some query patterns will trigger various shorturl path replacements.
+ * @param $query2 Mixed: An optional secondary query array. This one MUST
+ * be an array. If a string is passed it will be interpreted as a deprecated
+ * variant argument and urlencoded into a variant= argument.
+ * This second query argument will be added to the $query
+ * The second parameter is deprecated since 1.19. Pass it as a key,value
+ * pair in the first parameter array instead.
+ *
* @return String the URL
*/
- public function getLocalURL( $query = '', $variant = false ) {
+ public function getLocalURL( $query = '', $query2 = false ) {
global $wgArticlePath, $wgScript, $wgServer, $wgRequest;
- global $wgVariantArticlePath, $wgContLang;
- if ( is_array( $query ) ) {
- $query = wfArrayToCGI( $query );
- }
+ $query = self::fixUrlQueryArgs( $query, $query2 );
- if ( $this->isExternal() ) {
- $url = $this->getFullURL();
- if ( $query ) {
- // This is currently only used for edit section links in the
- // context of interwiki transclusion. In theory we should
- // append the query to the end of any existing query string,
- // but interwiki transclusion is already broken in that case.
- $url .= "?$query";
+ $interwiki = Interwiki::fetch( $this->mInterwiki );
+ if ( $interwiki ) {
+ $namespace = $this->getNsText();
+ if ( $namespace != '' ) {
+ # Can this actually happen? Interwikis shouldn't be parsed.
+ # Yes! It can in interwiki transclusion. But... it probably shouldn't.
+ $namespace .= ':';
}
+ $url = $interwiki->getURL( $namespace . $this->getDBkey() );
+ $url = wfAppendQuery( $url, $query );
} else {
$dbkey = wfUrlencode( $this->getPrefixedDBkey() );
if ( $query == '' ) {
- if ( $variant != false && $wgContLang->hasVariants() ) {
- if ( !$wgVariantArticlePath ) {
- $variantArticlePath = "$wgScript?title=$1&variant=$2"; // default
- } else {
- $variantArticlePath = $wgVariantArticlePath;
- }
- $url = str_replace( '$2', urlencode( $variant ), $variantArticlePath );
- $url = str_replace( '$1', $dbkey, $url );
- } else {
- $url = str_replace( '$1', $dbkey, $wgArticlePath );
- }
+ $url = str_replace( '$1', $dbkey, $wgArticlePath );
+ wfRunHooks( 'GetLocalURL::Article', array( &$this, &$url ) );
} else {
- global $wgActionPaths;
+ global $wgVariantArticlePath, $wgActionPaths;
$url = false;
$matches = array();
+
if ( !empty( $wgActionPaths ) &&
preg_match( '/^(.*&|)action=([^&]*)(&(.*)|)$/', $query, $matches ) )
{
@@ -940,6 +1338,20 @@ class Title {
}
}
+ if ( $url === false &&
+ $wgVariantArticlePath &&
+ $this->getPageLanguage()->hasVariants() &&
+ preg_match( '/^variant=([^&]*)$/', $query, $matches ) )
+ {
+ $variant = urldecode( $matches[1] );
+ if ( $this->getPageLanguage()->hasVariant( $variant ) ) {
+ // Only do the variant replacement if the given variant is a valid
+ // variant for the page's language.
+ $url = str_replace( '$2', urlencode( $variant ), $wgVariantArticlePath );
+ $url = str_replace( '$1', $dbkey, $url );
+ }
+ }
+
if ( $url === false ) {
if ( $query == '-' ) {
$query = '';
@@ -948,6 +1360,8 @@ class Title {
}
}
+ wfRunHooks( 'GetLocalURL::Internal', array( &$this, &$url, $query ) );
+
// @todo FIXME: This causes breakage in various places when we
// actually expected a local URL and end up with dupe prefixes.
if ( $wgRequest->getVal( 'action' ) == 'render' ) {
@@ -968,21 +1382,19 @@ 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 Array of Strings An associative array of key => value pairs for the
- * query string. Keys and values will be escaped.
- * @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).
+ * See getLocalURL for the arguments.
+ *
+ * @see self::getLocalURL
* @return String the URL
*/
- public function getLinkUrl( $query = array(), $variant = false ) {
+ public function getLinkURL( $query = '', $query2 = false ) {
wfProfileIn( __METHOD__ );
if ( $this->isExternal() ) {
- $ret = $this->getFullURL( $query );
+ $ret = $this->getFullURL( $query, $query2 );
} elseif ( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
$ret = $this->getFragmentForURL();
} else {
- $ret = $this->getLocalURL( $query, $variant ) . $this->getFragmentForURL();
+ $ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
}
wfProfileOut( __METHOD__ );
return $ret;
@@ -992,49 +1404,50 @@ 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 String an optional query string
+ * See getLocalURL for the arguments.
+ *
+ * @see self::getLocalURL
* @return String the URL
*/
- public function escapeLocalURL( $query = '' ) {
- return htmlspecialchars( $this->getLocalURL( $query ) );
+ public function escapeLocalURL( $query = '', $query2 = false ) {
+ wfDeprecated( __METHOD__, '1.19' );
+ return htmlspecialchars( $this->getLocalURL( $query, $query2 ) );
}
/**
* Get an HTML-escaped version of the URL form, suitable for
* using in a link, including the server name and fragment
*
- * @param $query String an optional query string
+ * See getLocalURL for the arguments.
+ *
+ * @see self::getLocalURL
* @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 ) );
+ public function escapeFullURL( $query = '', $query2 = false ) {
+ wfDeprecated( __METHOD__, '1.19' );
+ return htmlspecialchars( $this->getFullURL( $query, $query2 ) );
}
/**
* Get the URL form for an internal link.
* - Used in various Squid-related code, in case we have a different
* internal hostname for the server from the exposed one.
- *
+ *
* 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 String an optional query string
- * @param $variant String language variant of url (for sr, zh..)
+ * See getLocalURL for the arguments.
+ *
+ * @see self::getLocalURL
* @return String the URL
*/
- public function getInternalURL( $query = '', $variant = false ) {
+ public function getInternalURL( $query = '', $query2 = false ) {
global $wgInternalServer, $wgServer;
+ $query = self::fixUrlQueryArgs( $query, $query2 );
$server = $wgInternalServer !== false ? $wgInternalServer : $wgServer;
- $url = wfExpandUrl( $server . $this->getLocalURL( $query, $variant ), PROTO_HTTP );
- wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query, $variant ) );
+ $url = wfExpandUrl( $server . $this->getLocalURL( $query ), PROTO_HTTP );
+ wfRunHooks( 'GetInternalURL', array( &$this, &$url, $query ) );
return $url;
}
@@ -1042,21 +1455,36 @@ class Title {
* 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, ...)
+ *
+ * See getLocalURL for the arguments.
+ *
+ * @see self::getLocalURL
* @return string The URL
+ * @since 1.18
*/
- 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 ) );
+ public function getCanonicalURL( $query = '', $query2 = false ) {
+ $query = self::fixUrlQueryArgs( $query, $query2 );
+ $url = wfExpandUrl( $this->getLocalURL( $query ) . $this->getFragmentForURL(), PROTO_CANONICAL );
+ wfRunHooks( 'GetCanonicalURL', array( &$this, &$url, $query ) );
return $url;
}
/**
+ * HTML-escaped version of getCanonicalURL()
+ *
+ * See getLocalURL for the arguments.
+ *
+ * @see self::getLocalURL
+ * @since 1.18
+ */
+ public function escapeCanonicalURL( $query = '', $query2 = false ) {
+ wfDeprecated( __METHOD__, '1.19' );
+ return htmlspecialchars( $this->getCanonicalURL( $query, $query2 ) );
+ }
+
+ /**
* Get the edit URL for this Title
*
* @return String the URL, or a null string if this is an
@@ -1072,100 +1500,6 @@ class Title {
}
/**
- * Get the HTML-escaped displayable text form.
- * Used for the title field in <a> tags.
- *
- * @return String the text, including any prefixes
- */
- public function getEscapedText() {
- return htmlspecialchars( $this->getPrefixedText() );
- }
-
- /**
- * Is this Title interwiki?
- *
- * @return Bool
- */
- public function isExternal() {
- return ( $this->mInterwiki != '' );
- }
-
- /**
- * Is this page "semi-protected" - the *only* protection is autoconfirm?
- *
- * @param $action String Action to check (default: edit)
- * @return Bool
- */
- public function isSemiProtected( $action = 'edit' ) {
- if ( $this->exists() ) {
- $restrictions = $this->getRestrictions( $action );
- if ( count( $restrictions ) > 0 ) {
- foreach ( $restrictions as $restriction ) {
- if ( strtolower( $restriction ) != 'autoconfirmed' ) {
- return false;
- }
- }
- } else {
- # Not protected
- return false;
- }
- return true;
- } else {
- # If it doesn't exist, it can't be protected
- return false;
- }
- }
-
- /**
- * Does the title correspond to a protected article?
- *
- * @param $action String the action the page is protected from,
- * by default checks all actions.
- * @return Bool
- */
- public function isProtected( $action = '' ) {
- global $wgRestrictionLevels;
-
- $restrictionTypes = $this->getRestrictionTypes();
-
- # Special pages have inherent protection
- if( $this->getNamespace() == NS_SPECIAL ) {
- return true;
- }
-
- # Check regular protection levels
- foreach ( $restrictionTypes as $type ) {
- if ( $action == $type || $action == '' ) {
- $r = $this->getRestrictions( $type );
- foreach ( $wgRestrictionLevels as $level ) {
- if ( in_array( $level, $r ) && $level != '' ) {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- /**
- * Is this a conversion table for the LanguageConverter?
- *
- * @return Bool
- */
- public function isConversionTable() {
- if(
- $this->getNamespace() == NS_MEDIAWIKI &&
- strpos( $this->getText(), 'Conversiontable' ) !== false
- )
- {
- return true;
- }
-
- return false;
- }
-
- /**
* Is $wgUser watching this page?
*
* @return Bool
@@ -1184,7 +1518,19 @@ class Title {
}
/**
- * Can $wgUser perform $action on this page?
+ * Can $wgUser read this page?
+ *
+ * @deprecated in 1.19; use userCan(), quickUserCan() or getUserPermissionsErrors() instead
+ * @return Bool
+ * @todo fold these checks into userCan()
+ */
+ public function userCanRead() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->userCan( 'read' );
+ }
+
+ /**
+ * Can $user perform $action on this page?
* This skips potentially expensive cascading permission checks
* as well as avoids expensive error formatting
*
@@ -1194,47 +1540,30 @@ class Title {
* May provide false positives, but should never provide a false negative.
*
* @param $action String action that permission needs to be checked for
+ * @param $user User to check (since 1.19); $wgUser will be used if not
+ * provided.
* @return Bool
*/
- public function quickUserCan( $action ) {
- return $this->userCan( $action, false );
+ public function quickUserCan( $action, $user = null ) {
+ return $this->userCan( $action, $user, false );
}
/**
- * Determines if $user is unable to edit this page because it has been protected
- * by $wgNamespaceProtection.
+ * Can $user perform $action on this page?
*
- * @param $user User object, $wgUser will be used if not passed
+ * @param $action String action that permission needs to be checked for
+ * @param $user User to check (since 1.19); $wgUser will be used if not
+ * provided.
+ * @param $doExpensiveQueries Bool Set this to false to avoid doing
+ * unnecessary queries.
* @return Bool
*/
- public function isNamespaceProtected( User $user = null ) {
- global $wgNamespaceProtection;
-
- if ( $user === null ) {
+ public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
+ if ( !$user instanceof User ) {
global $wgUser;
$user = $wgUser;
}
-
- if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
- foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
- if ( $right != '' && !$user->isAllowed( $right ) ) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Can $wgUser perform $action on this page?
- *
- * @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;
- return ( $this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries, true ) === array() );
+ return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries, true ) );
}
/**
@@ -1244,9 +1573,10 @@ class Title {
*
* @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.
+ * @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() ) {
@@ -1321,24 +1651,7 @@ class Title {
$errors[] = array( 'cant-move-to-user-page' );
}
} elseif ( !$user->isAllowed( $action ) ) {
- // We avoid expensive display logic for quickUserCan's and such
- $groups = false;
- if ( !$short ) {
- $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
- User::getGroupsWithPermission( $action ) );
- }
-
- if ( $groups ) {
- global $wgLang;
- $return = array(
- 'badaccess-groups',
- $wgLang->commaList( $groups ),
- count( $groups )
- );
- } else {
- $return = array( 'badaccess-group0' );
- }
- $errors[] = $return;
+ $errors[] = $this->missingPermissionError( $action, $short );
}
return $errors;
@@ -1413,7 +1726,7 @@ class Title {
private function checkSpecialsAndNSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
# Only 'createaccount' and 'execute' can be performed on
# special pages, which don't actually exist in the DB.
- $specialOKActions = array( 'createaccount', 'execute' );
+ $specialOKActions = array( 'createaccount', 'execute', 'read' );
if ( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions ) ) {
$errors[] = array( 'ns-specialprotected' );
}
@@ -1443,8 +1756,6 @@ class Title {
private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
# Protect css/js subpages of user pages
# XXX: this might be better using restrictions
- # XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssSubpage()
- # and $this->userCanEditJsSubpage() from working
# XXX: right 'editusercssjs' is deprecated, for backward compatibility only
if ( $action != 'patrol' && !$user->isAllowed( 'editusercssjs' )
&& !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $this->mTextform ) ) {
@@ -1479,13 +1790,10 @@ class Title {
}
if ( $right != '' && !$user->isAllowed( $right ) ) {
// Users with 'editprotected' permission can edit protected pages
- if ( $action == 'edit' && $user->isAllowed( 'editprotected' ) ) {
- // Users with 'editprotected' permission cannot edit protected pages
- // with cascading option turned on.
- if ( $this->mCascadeRestriction ) {
- $errors[] = array( 'protectedpagetext', $right );
- }
- } else {
+ // without cascading option turned on.
+ if ( $action != 'edit' || !$user->isAllowed( 'editprotected' )
+ || $this->mCascadeRestriction )
+ {
$errors[] = array( 'protectedpagetext', $right );
}
}
@@ -1545,8 +1853,10 @@ class Title {
* @return Array list of errors
*/
private function checkActionPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ global $wgDeleteRevisionsLimit, $wgLang;
+
if ( $action == 'protect' ) {
- if ( $this->getUserPermissionsErrors( 'edit', $user ) != array() ) {
+ if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $doExpensiveQueries, true ) ) ) {
// If they can't edit, they shouldn't protect.
$errors[] = array( 'protect-cantedit' );
}
@@ -1556,7 +1866,9 @@ class Title {
if( $title_protection['pt_create_perm'] == 'sysop' ) {
$title_protection['pt_create_perm'] = 'protect'; // B/C
}
- if( $title_protection['pt_create_perm'] == '' || !$user->isAllowed( $title_protection['pt_create_perm'] ) ) {
+ if( $title_protection['pt_create_perm'] == '' ||
+ !$user->isAllowed( $title_protection['pt_create_perm'] ) )
+ {
$errors[] = array( 'titleprotected', User::whoIs( $title_protection['pt_user'] ), $title_protection['pt_reason'] );
}
}
@@ -1567,7 +1879,7 @@ class Title {
$errors[] = array( 'immobile-source-namespace', $this->getNsText() );
} elseif ( !$this->isMovable() ) {
// Less specific message for rarer cases
- $errors[] = array( 'immobile-page' );
+ $errors[] = array( 'immobile-source-page' );
}
} elseif ( $action == 'move-target' ) {
if ( !MWNamespace::isMovable( $this->mNamespace ) ) {
@@ -1575,6 +1887,12 @@ class Title {
} elseif ( !$this->isMovable() ) {
$errors[] = array( 'immobile-target-page' );
}
+ } elseif ( $action == 'delete' ) {
+ if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
+ && !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion() )
+ {
+ $errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
+ }
}
return $errors;
}
@@ -1591,21 +1909,19 @@ class Title {
* @return Array list of errors
*/
private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
- if( !$doExpensiveQueries ) {
+ // Account creation blocks handled at userlogin.
+ // Unblocking handled in SpecialUnblock
+ if( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
return $errors;
}
global $wgContLang, $wgLang, $wgEmailConfirmToEdit;
- if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount' ) {
+ if ( $wgEmailConfirmToEdit && !$user->isEmailConfirmed() ) {
$errors[] = array( 'confirmedittext' );
}
- 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 ) ){
+ if ( ( $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 ) {
@@ -1619,7 +1935,7 @@ class Title {
if ( $reason == '' ) {
$reason = wfMsg( 'blockednoreason' );
}
- $ip = wfGetIP();
+ $ip = $user->getRequest()->getIP();
if ( is_numeric( $id ) ) {
$name = User::whoIs( $id );
@@ -1647,6 +1963,127 @@ class Title {
}
/**
+ * Check that the user is allowed to read this page.
+ *
+ * @param $action String the action 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
+ *
+ * @return Array list of errors
+ */
+ private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ global $wgWhitelistRead, $wgGroupPermissions, $wgRevokePermissions;
+ static $useShortcut = null;
+
+ # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
+ if ( is_null( $useShortcut ) ) {
+ $useShortcut = true;
+ if ( empty( $wgGroupPermissions['*']['read'] ) ) {
+ # 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
+ * reading is allowed.
+ */
+ foreach ( $wgRevokePermissions as $perms ) {
+ if ( !empty( $perms['read'] ) ) {
+ # We might be removing the read right from the user, so no shortcut
+ $useShortcut = false;
+ break;
+ }
+ }
+ }
+ }
+
+ $whitelisted = false;
+ if ( $useShortcut ) {
+ # Shortcut for public wikis, allows skipping quite a bit of code
+ $whitelisted = true;
+ } elseif ( $user->isAllowed( 'read' ) ) {
+ # If the user is allowed to read pages, he is allowed to read all pages
+ $whitelisted = true;
+ } elseif ( $this->isSpecial( 'Userlogin' )
+ || $this->isSpecial( 'ChangePassword' )
+ || $this->isSpecial( 'PasswordReset' )
+ ) {
+ # Always grant access to the login page.
+ # Even anons need to be able to log in.
+ $whitelisted = true;
+ } elseif ( is_array( $wgWhitelistRead ) && count( $wgWhitelistRead ) ) {
+ # Time to check the whitelist
+ # Only do these checks is there's something to check against
+ $name = $this->getPrefixedText();
+ $dbName = $this->getPrefixedDBKey();
+
+ // Check with and without underscores
+ if ( in_array( $name, $wgWhitelistRead, true ) || in_array( $dbName, $wgWhitelistRead, true ) ) {
+ # Check for explicit whitelisting
+ $whitelisted = true;
+ } elseif ( $this->getNamespace() == NS_MAIN ) {
+ # Old settings might have the title prefixed with
+ # a colon for main-namespace pages
+ if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
+ $whitelisted = true;
+ }
+ } elseif ( $this->isSpecialPage() ) {
+ # If it's a special page, ditch the subpage bit and check again
+ $name = $this->getDBkey();
+ list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
+ if ( $name !== false ) {
+ $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
+ if ( in_array( $pure, $wgWhitelistRead, true ) ) {
+ $whitelisted = true;
+ }
+ }
+ }
+ }
+
+ if ( !$whitelisted ) {
+ # If the title is not whitelisted, give extensions a chance to do so...
+ wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );
+ if ( !$whitelisted ) {
+ $errors[] = $this->missingPermissionError( $action, $short );
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Get a description array when the user doesn't have the right to perform
+ * $action (i.e. when User::isAllowed() returns false)
+ *
+ * @param $action String the action to check
+ * @param $short Boolean short circuit on first error
+ * @return Array list of errors
+ */
+ private function missingPermissionError( $action, $short ) {
+ // We avoid expensive display logic for quickUserCan's and such
+ if ( $short ) {
+ return array( 'badaccess-group0' );
+ }
+
+ $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
+ User::getGroupsWithPermission( $action ) );
+
+ if ( count( $groups ) ) {
+ global $wgLang;
+ return array(
+ 'badaccess-groups',
+ $wgLang->commaList( $groups ),
+ count( $groups )
+ );
+ } else {
+ return array( 'badaccess-group0' );
+ }
+ }
+
+ /**
* Can $user perform $action on this page? This is an internal function,
* which checks ONLY that previously checked by userCan (i.e. it leaves out
* checks on wfReadOnly() and blocks)
@@ -1660,20 +2097,28 @@ class Title {
protected function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true, $short = false ) {
wfProfileIn( __METHOD__ );
- $errors = array();
- $checks = array(
- 'checkQuickPermissions',
- 'checkPermissionHooks',
- 'checkSpecialsAndNSPermissions',
- 'checkCSSandJSPermissions',
- 'checkPageRestrictions',
- 'checkCascadingSourcesRestrictions',
- 'checkActionPermissions',
- 'checkUserBlock'
- );
+ # Read has special handling
+ if ( $action == 'read' ) {
+ $checks = array(
+ 'checkPermissionHooks',
+ 'checkReadPermissions',
+ );
+ } else {
+ $checks = array(
+ 'checkQuickPermissions',
+ 'checkPermissionHooks',
+ 'checkSpecialsAndNSPermissions',
+ 'checkCSSandJSPermissions',
+ 'checkPageRestrictions',
+ 'checkCascadingSourcesRestrictions',
+ 'checkActionPermissions',
+ 'checkUserBlock'
+ );
+ }
+ $errors = array();
while( count( $checks ) > 0 &&
- !( $short && count( $errors ) > 0 ) ) {
+ !( $short && count( $errors ) > 0 ) ) {
$method = array_shift( $checks );
$errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
}
@@ -1683,6 +2128,79 @@ class Title {
}
/**
+ * Protect css subpages of user pages: can $wgUser edit
+ * this page?
+ *
+ * @deprecated in 1.19; will be removed in 1.20. Use getUserPermissionsErrors() instead.
+ * @return Bool
+ */
+ public function userCanEditCssSubpage() {
+ global $wgUser;
+ wfDeprecated( __METHOD__, '1.19' );
+ return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
+ || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
+ }
+
+ /**
+ * Protect js subpages of user pages: can $wgUser edit
+ * this page?
+ *
+ * @deprecated in 1.19; will be removed in 1.20. Use getUserPermissionsErrors() instead.
+ * @return Bool
+ */
+ public function userCanEditJsSubpage() {
+ global $wgUser;
+ wfDeprecated( __METHOD__, '1.19' );
+ return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
+ || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
+ }
+
+ /**
+ * Get a filtered list of all restriction types supported by this wiki.
+ * @param bool $exists True to get all restriction types that apply to
+ * titles that do exist, False for all restriction types that apply to
+ * titles that do not exist
+ * @return array
+ */
+ public static function getFilteredRestrictionTypes( $exists = true ) {
+ global $wgRestrictionTypes;
+ $types = $wgRestrictionTypes;
+ if ( $exists ) {
+ # Remove the create restriction for existing titles
+ $types = array_diff( $types, array( 'create' ) );
+ } else {
+ # Only the create and upload restrictions apply to non-existing titles
+ $types = array_intersect( $types, array( 'create', 'upload' ) );
+ }
+ return $types;
+ }
+
+ /**
+ * Returns restriction types for the current Title
+ *
+ * @return array applicable restriction types
+ */
+ public function getRestrictionTypes() {
+ if ( $this->isSpecialPage() ) {
+ 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 restrictions to [[' .
+ $this->getPrefixedText() . ']] are {' . implode( ',', $types ) . "}\n" );
+
+ return $types;
+ }
+
+ /**
* Is this title subject to title protection?
* Title protection is the one applied against creation of such title.
*
@@ -1715,66 +2233,24 @@ class Title {
/**
* Update the title protection status
*
+ * @deprecated in 1.19; will be removed in 1.20. Use WikiPage::doUpdateRestrictions() instead.
* @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 ) {
- global $wgUser, $wgContLang;
-
- if ( $create_perm == implode( ',', $this->getRestrictions( 'create' ) )
- && $expiry == $this->mRestrictionsExpiry['create'] ) {
- // No change
- return true;
- }
-
- list ( $namespace, $title ) = array( $this->getNamespace(), $this->getDBkey() );
-
- $dbw = wfGetDB( DB_MASTER );
-
- $encodedExpiry = $dbw->encodeExpiry( $expiry );
+ wfDeprecated( __METHOD__, '1.19' );
- $expiry_description = '';
- if ( $encodedExpiry != $dbw->getInfinity() ) {
- $expiry_description = ' (' . wfMsgForContent( 'protect-expiring', $wgContLang->timeanddate( $expiry ),
- $wgContLang->date( $expiry ) , $wgContLang->time( $expiry ) ) . ')';
- } else {
- $expiry_description .= ' (' . wfMsgForContent( 'protect-expiry-indefinite' ) . ')';
- }
-
- # Update protection table
- if ( $create_perm != '' ) {
- $this->mTitleProtection = array(
- 'pt_namespace' => $namespace,
- 'pt_title' => $title,
- 'pt_create_perm' => $create_perm,
- 'pt_timestamp' => $dbw->encodeExpiry( wfTimestampNow() ),
- 'pt_expiry' => $encodedExpiry,
- 'pt_user' => $wgUser->getId(),
- 'pt_reason' => $reason,
- );
- $dbw->replace( 'protected_titles', array( array( 'pt_namespace', 'pt_title' ) ),
- $this->mTitleProtection, __METHOD__ );
- } else {
- $dbw->delete( 'protected_titles', array( 'pt_namespace' => $namespace,
- 'pt_title' => $title ), __METHOD__ );
- $this->mTitleProtection = false;
- }
+ global $wgUser;
- # Update the protection log
- if ( $dbw->affectedRows() ) {
- $log = new LogPage( 'protect' );
+ $limit = array( 'create' => $create_perm );
+ $expiry = array( 'create' => $expiry );
- if ( $create_perm ) {
- $params = array( "[create=$create_perm] $expiry_description", '' );
- $log->addEntry( ( isset( $this->mRestrictions['create'] ) && $this->mRestrictions['create'] ) ? 'modify' : 'protect', $this, trim( $reason ), $params );
- } else {
- $log->addEntry( 'unprotect', $this, $reason );
- }
- }
+ $page = WikiPage::factory( $this );
+ $status = $page->doUpdateRestrictions( $limit, $expiry, false, $reason, $wgUser );
- return true;
+ return $status->isOK();
}
/**
@@ -1792,284 +2268,81 @@ class Title {
}
/**
- * Would anybody with sufficient privileges be able to move this page?
- * Some pages just aren't movable.
- *
- * @return Bool TRUE or FALSE
- */
- public function isMovable() {
- return MWNamespace::isMovable( $this->getNamespace() ) && $this->getInterwiki() == '';
- }
-
- /**
- * Can $wgUser read this page?
+ * Is this page "semi-protected" - the *only* protection is autoconfirm?
*
+ * @param $action String Action to check (default: edit)
* @return Bool
- * @todo fold these checks into userCan()
*/
- public function userCanRead() {
- global $wgUser, $wgGroupPermissions;
-
- static $useShortcut = null;
-
- # Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
- if ( is_null( $useShortcut ) ) {
- global $wgRevokePermissions;
- $useShortcut = true;
- if ( empty( $wgGroupPermissions['*']['read'] ) ) {
- # 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
- * reading is allowed.
- */
- foreach ( $wgRevokePermissions as $perms ) {
- if ( !empty( $perms['read'] ) ) {
- # We might be removing the read right from the user, so no shortcut
- $useShortcut = false;
- break;
+ public function isSemiProtected( $action = 'edit' ) {
+ if ( $this->exists() ) {
+ $restrictions = $this->getRestrictions( $action );
+ if ( count( $restrictions ) > 0 ) {
+ foreach ( $restrictions as $restriction ) {
+ if ( strtolower( $restriction ) != 'autoconfirmed' ) {
+ return false;
}
}
+ } else {
+ # Not protected
+ return false;
}
- }
-
- $result = null;
- wfRunHooks( 'userCan', array( &$this, &$wgUser, 'read', &$result ) );
- if ( $result !== null ) {
- return $result;
- }
-
- # Shortcut for public wikis, allows skipping quite a bit of code
- if ( $useShortcut ) {
- return true;
- }
-
- if ( $wgUser->isAllowed( 'read' ) ) {
return true;
} 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( 'ChangePassword' )
- || $this->isSpecial( 'PasswordReset' )
- ) {
- return true;
- }
-
- # Bail out if there isn't whitelist
- if ( !is_array( $wgWhitelistRead ) ) {
- return false;
- }
-
- # 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
- if ( $this->getNamespace() == NS_MAIN ) {
- if ( in_array( ':' . $name, $wgWhitelistRead ) ) {
- return true;
- }
- }
-
- # If it's a special page, ditch the subpage bit and check again
- if ( $this->getNamespace() == NS_SPECIAL ) {
- $name = $this->getDBkey();
- list( $name, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $name );
- if ( $name === false ) {
- # Invalid special page, but we show standard login required message
- return false;
- }
-
- $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
- if ( in_array( $pure, $wgWhitelistRead, true ) ) {
- return true;
- }
- }
-
+ # If it doesn't exist, it can't be protected
+ return false;
}
- return false;
}
/**
- * 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 Bool
- */
- public function isTalkPage() {
- return MWNamespace::isTalk( $this->getNamespace() );
- }
-
- /**
- * Is this a subpage?
- *
- * @return Bool
- */
- public function isSubpage() {
- return MWNamespace::hasSubpages( $this->mNamespace )
- ? strpos( $this->getText(), '/' ) !== false
- : false;
- }
-
- /**
- * Does this have subpages? (Warning, usually requires an extra DB query.)
+ * Does the title correspond to a protected article?
*
+ * @param $action String the action the page is protected from,
+ * by default checks all actions.
* @return Bool
*/
- public function hasSubpages() {
- if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
- # Duh
- return false;
- }
-
- # We dynamically add a member variable for the purpose of this method
- # alone to cache the result. There's no point in having it hanging
- # around uninitialized in every Title object; therefore we only add it
- # if needed and don't declare it statically.
- if ( isset( $this->mHasSubpages ) ) {
- return $this->mHasSubpages;
- }
+ public function isProtected( $action = '' ) {
+ global $wgRestrictionLevels;
- $subpages = $this->getSubpages( 1 );
- if ( $subpages instanceof TitleArray ) {
- return $this->mHasSubpages = (bool)$subpages->count();
- }
- return $this->mHasSubpages = false;
- }
+ $restrictionTypes = $this->getRestrictionTypes();
- /**
- * Get all subpages of this page.
- *
- * @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
- */
- public function getSubpages( $limit = -1 ) {
- if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
- return array();
+ # Special pages have inherent protection
+ if( $this->isSpecialPage() ) {
+ return true;
}
- $dbr = wfGetDB( DB_SLAVE );
- $conds['page_namespace'] = $this->getNamespace();
- $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
- $options = array();
- if ( $limit > -1 ) {
- $options['LIMIT'] = $limit;
+ # Check regular protection levels
+ foreach ( $restrictionTypes as $type ) {
+ if ( $action == $type || $action == '' ) {
+ $r = $this->getRestrictions( $type );
+ foreach ( $wgRestrictionLevels as $level ) {
+ if ( in_array( $level, $r ) && $level != '' ) {
+ return true;
+ }
+ }
+ }
}
- return $this->mSubpages = TitleArray::newFromResult(
- $dbr->select( 'page',
- array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
- $conds,
- __METHOD__,
- $options
- )
- );
- }
-
- /**
- * Could this page contain custom CSS or JavaScript, based
- * on the title?
- *
- * @return Bool
- */
- public function isCssOrJsPage() {
- return $this->mNamespace == NS_MEDIAWIKI
- && preg_match( '!\.(?:css|js)$!u', $this->mTextform ) > 0;
- }
-
- /**
- * Is this a .css or .js subpage of a user page?
- * @return Bool
- */
- public function isCssJsSubpage() {
- return ( NS_USER == $this->mNamespace and preg_match( "/\\/.*\\.(?:css|js)$/", $this->mTextform ) );
- }
-
- /**
- * Is this a *valid* .css or .js subpage of a user page?
- *
- * @return Bool
- * @deprecated since 1.17
- */
- public function isValidCssJsSubpage() {
- return $this->isCssJsSubpage();
- }
-
- /**
- * Trim down a .css or .js subpage title to get the corresponding skin name
- *
- * @return string containing skin name from .css or .js subpage title
- */
- public function getSkinFromCssJsSubpage() {
- $subpage = explode( '/', $this->mTextform );
- $subpage = $subpage[ count( $subpage ) - 1 ];
- return( str_replace( array( '.css', '.js' ), array( '', '' ), $subpage ) );
- }
-
- /**
- * Is this a .css subpage of a user page?
- *
- * @return Bool
- */
- public function isCssSubpage() {
- return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.css$/", $this->mTextform ) );
- }
- /**
- * Is this a .js subpage of a user page?
- *
- * @return Bool
- */
- public function isJsSubpage() {
- return ( NS_USER == $this->mNamespace && preg_match( "/\\/.*\\.js$/", $this->mTextform ) );
+ return false;
}
/**
- * Protect css subpages of user pages: can $wgUser edit
- * this page?
+ * Determines if $user is unable to edit this page because it has been protected
+ * by $wgNamespaceProtection.
*
+ * @param $user User object to check permissions
* @return Bool
- * @todo XXX: this might be better using restrictions
*/
- public function userCanEditCssSubpage() {
- global $wgUser;
- return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'editusercss' ) )
- || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
- }
+ public function isNamespaceProtected( User $user ) {
+ global $wgNamespaceProtection;
- /**
- * Protect js subpages of user pages: can $wgUser edit
- * this page?
- *
- * @return Bool
- * @todo XXX: this might be better using restrictions
- */
- public function userCanEditJsSubpage() {
- global $wgUser;
- return ( ( $wgUser->isAllowedAll( 'editusercssjs', 'edituserjs' ) )
- || preg_match( '/^' . preg_quote( $wgUser->getName(), '/' ) . '\//', $this->mTextform ) );
+ if ( isset( $wgNamespaceProtection[$this->mNamespace] ) ) {
+ foreach ( (array)$wgNamespaceProtection[$this->mNamespace] as $right ) {
+ if ( $right != '' && !$user->isAllowed( $right ) ) {
+ return true;
+ }
+ }
+ }
+ return false;
}
/**
@@ -2181,6 +2454,34 @@ class Title {
}
/**
+ * Accessor/initialisation for mRestrictions
+ *
+ * @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 ) {
+ $this->loadRestrictions();
+ }
+ return isset( $this->mRestrictions[$action] )
+ ? $this->mRestrictions[$action]
+ : array();
+ }
+
+ /**
+ * Get the expiry time for the restriction against a given action
+ *
+ * @return String|Bool 14-char timestamp, or 'infinity' if the page is protected forever
+ * or not protected at all, or false if the action is not recognised.
+ */
+ public function getRestrictionExpiry( $action ) {
+ if ( !$this->mRestrictionsLoaded ) {
+ $this->loadRestrictions();
+ }
+ return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
+ }
+
+ /**
* Returns cascading restrictions for the current article
*
* @return Boolean
@@ -2336,6 +2637,15 @@ class Title {
}
/**
+ * Flush the protection cache in this object and force reload from the database.
+ * This is used when updating protection from WikiPage::doUpdateRestrictions().
+ */
+ public function flushRestrictions() {
+ $this->mRestrictionsLoaded = false;
+ $this->mTitleProtection = null;
+ }
+
+ /**
* Purge expired restrictions from the page_restrictions table
*/
static function purgeExpiredRestrictions() {
@@ -2354,31 +2664,58 @@ class Title {
}
/**
- * Accessor/initialisation for mRestrictions
+ * Does this have subpages? (Warning, usually requires an extra DB query.)
*
- * @param $action String action that permission needs to be checked for
- * @return Array of Strings the array of groups allowed to edit this article
+ * @return Bool
*/
- public function getRestrictions( $action ) {
- if ( !$this->mRestrictionsLoaded ) {
- $this->loadRestrictions();
+ public function hasSubpages() {
+ if ( !MWNamespace::hasSubpages( $this->mNamespace ) ) {
+ # Duh
+ return false;
}
- return isset( $this->mRestrictions[$action] )
- ? $this->mRestrictions[$action]
- : array();
+
+ # We dynamically add a member variable for the purpose of this method
+ # alone to cache the result. There's no point in having it hanging
+ # around uninitialized in every Title object; therefore we only add it
+ # if needed and don't declare it statically.
+ if ( isset( $this->mHasSubpages ) ) {
+ return $this->mHasSubpages;
+ }
+
+ $subpages = $this->getSubpages( 1 );
+ if ( $subpages instanceof TitleArray ) {
+ return $this->mHasSubpages = (bool)$subpages->count();
+ }
+ return $this->mHasSubpages = false;
}
/**
- * Get the expiry time for the restriction against a given action
+ * Get all subpages of this page.
*
- * @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.
+ * @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
*/
- public function getRestrictionExpiry( $action ) {
- if ( !$this->mRestrictionsLoaded ) {
- $this->loadRestrictions();
+ public function getSubpages( $limit = -1 ) {
+ if ( !MWNamespace::hasSubpages( $this->getNamespace() ) ) {
+ return array();
}
- return isset( $this->mRestrictionsExpiry[$action] ) ? $this->mRestrictionsExpiry[$action] : false;
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $conds['page_namespace'] = $this->getNamespace();
+ $conds[] = 'page_title ' . $dbr->buildLike( $this->getDBkey() . '/', $dbr->anyString() );
+ $options = array();
+ if ( $limit > -1 ) {
+ $options['LIMIT'] = $limit;
+ }
+ return $this->mSubpages = TitleArray::newFromResult(
+ $dbr->select( 'page',
+ array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
+ $conds,
+ __METHOD__,
+ $options
+ )
+ );
}
/**
@@ -2521,15 +2858,15 @@ 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::doEdit() and Article::insertOn() to allow
+ * - This is called from WikiPage::doEdit() and WikiPage::insertOn() to allow
* loading of the new page_id. It's also called from
- * Article::doDeleteArticle()
+ * WikiPage::doDeleteArticle()
*
* @param $newid Int the new Article ID
*/
public function resetArticleID( $newid ) {
$linkCache = LinkCache::singleton();
- $linkCache->clearBadLink( $this->getPrefixedDBkey() );
+ $linkCache->clearLink( $this );
if ( $newid === false ) {
$this->mArticleID = -1;
@@ -2541,73 +2878,7 @@ class Title {
$this->mRedirect = null;
$this->mLength = -1;
$this->mLatestID = false;
- }
-
- /**
- * Updates page_touched for this page; called from LinksUpdate.php
- *
- * @return Bool true if the update succeded
- */
- public function invalidateCache() {
- if ( wfReadOnly() ) {
- return;
- }
- $dbw = wfGetDB( DB_MASTER );
- $success = $dbw->update(
- 'page',
- array( 'page_touched' => $dbw->timestamp() ),
- $this->pageCond(),
- __METHOD__
- );
- HTMLFileCache::clearFileCache( $this );
- return $success;
- }
-
- /**
- * Prefix some arbitrary text with the namespace or interwiki prefix
- * of this object
- *
- * @param $name String the text
- * @return String the prefixed text
- * @private
- */
- private function prefix( $name ) {
- $p = '';
- if ( $this->mInterwiki != '' ) {
- $p = $this->mInterwiki . ':';
- }
-
- if ( 0 != $this->mNamespace ) {
- $p .= $this->getNsText() . ':';
- }
- return $p . $name;
- }
-
- /**
- * Returns a simple regex that will match on characters and sequences invalid in titles.
- * Note that this doesn't pick up many things that could be wrong with titles, but that
- * replacing this regex with something valid will make many titles valid.
- *
- * @return String regex string
- */
- static function getTitleInvalidRegex() {
- static $rxTc = false;
- if ( !$rxTc ) {
- # Matching titles will be held as illegal.
- $rxTc = '/' .
- # Any character not allowed is forbidden...
- '[^' . Title::legalChars() . ']' .
- # URL percent encoding sequences interfere with the ability
- # to round-trip titles -- you can't link to them consistently.
- '|%[0-9A-Fa-f]{2}' .
- # XML/HTML character references produce similar issues.
- '|&[A-Za-z0-9\x80-\xff]+;' .
- '|&#[0-9]+;' .
- '|&#x[0-9A-Fa-f]+;' .
- '/S';
- }
-
- return $rxTc;
+ $this->mEstimateRevisions = null;
}
/**
@@ -2811,7 +3082,7 @@ class Title {
: $dbkey;
// Any remaining initial :s are illegal.
- if ( $dbkey !== '' && ':' == $dbkey { 0 } ) {
+ if ( $dbkey !== '' && ':' == $dbkey[0] ) {
return false;
}
@@ -2825,44 +3096,6 @@ class Title {
}
/**
- * Set the fragment for this title. Removes the first character from the
- * specified fragment before setting, so it assumes you're passing it with
- * an initial "#".
- *
- * Deprecated for public use, use Title::makeTitle() with fragment parameter.
- * Still in active use privately.
- *
- * @param $fragment String text
- */
- public function setFragment( $fragment ) {
- $this->mFragment = str_replace( '_', ' ', substr( $fragment, 1 ) );
- }
-
- /**
- * Get a Title object associated with the talk page of this article
- *
- * @return Title the object for the talk page
- */
- public function getTalkPage() {
- return Title::makeTitle( MWNamespace::getTalk( $this->getNamespace() ), $this->getDBkey() );
- }
-
- /**
- * Get a title object associated with the subject page of this
- * talk page
- *
- * @return Title the object for the subject page
- */
- public function getSubjectPage() {
- // Is this the same title?
- $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
- if ( $this->getNamespace() == $subjectNS ) {
- return $this;
- }
- return Title::makeTitle( $subjectNS, $this->getDBkey() );
- }
-
- /**
* Get an array of Title objects linking to this Title
* Also stores the IDs in the link cache.
*
@@ -2875,8 +3108,6 @@ class Title {
* @return Array of Title objects linking here
*/
public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
- $linkCache = LinkCache::singleton();
-
if ( count( $options ) > 0 ) {
$db = wfGetDB( DB_MASTER );
} else {
@@ -2895,11 +3126,12 @@ class Title {
);
$retVal = array();
- if ( $db->numRows( $res ) ) {
+ if ( $res->numRows() ) {
+ $linkCache = LinkCache::singleton();
foreach ( $res as $row ) {
$titleObj = Title::makeTitle( $row->page_namespace, $row->page_title );
if ( $titleObj ) {
- $linkCache->addGoodLinkObj( $row->page_id, $titleObj, $row->page_len, $row->page_is_redirect, $row->page_latest );
+ $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
$retVal[] = $titleObj;
}
}
@@ -2922,6 +3154,76 @@ class Title {
}
/**
+ * Get an array of Title objects linked from this Title
+ * Also stores the IDs in the link cache.
+ *
+ * WARNING: do not use this function on arbitrary user-supplied titles!
+ * On heavily-used templates it will max out the memory.
+ *
+ * @param $options Array: may be FOR UPDATE
+ * @param $table String: table name
+ * @param $prefix String: fields prefix
+ * @return Array of Title objects linking here
+ */
+ public function getLinksFrom( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
+ $id = $this->getArticleId();
+
+ # If the page doesn't exist; there can't be any link from this page
+ if ( !$id ) {
+ return array();
+ }
+
+ if ( count( $options ) > 0 ) {
+ $db = wfGetDB( DB_MASTER );
+ } else {
+ $db = wfGetDB( DB_SLAVE );
+ }
+
+ $namespaceFiled = "{$prefix}_namespace";
+ $titleField = "{$prefix}_title";
+
+ $res = $db->select(
+ array( $table, 'page' ),
+ array( $namespaceFiled, $titleField, 'page_id', 'page_len', 'page_is_redirect', 'page_latest' ),
+ array( "{$prefix}_from" => $id ),
+ __METHOD__,
+ $options,
+ array( 'page' => array( 'LEFT JOIN', array( "page_namespace=$namespaceFiled", "page_title=$titleField" ) ) )
+ );
+
+ $retVal = array();
+ if ( $res->numRows() ) {
+ $linkCache = LinkCache::singleton();
+ foreach ( $res as $row ) {
+ $titleObj = Title::makeTitle( $row->$namespaceFiled, $row->$titleField );
+ if ( $titleObj ) {
+ if ( $row->page_id ) {
+ $linkCache->addGoodLinkObjFromRow( $titleObj, $row );
+ } else {
+ $linkCache->addBadLinkObj( $titleObj );
+ }
+ $retVal[] = $titleObj;
+ }
+ }
+ }
+ return $retVal;
+ }
+
+ /**
+ * Get an array of Title objects used on this Title as a template
+ * Also stores the IDs in the link cache.
+ *
+ * WARNING: do not use this function on arbitrary user-supplied titles!
+ * On heavily-used templates it will max out the memory.
+ *
+ * @param $options Array: may be FOR UPDATE
+ * @return Array of Title the Title objects used here
+ */
+ public function getTemplateLinksFrom( $options = array() ) {
+ return $this->getLinksFrom( $options, 'templatelinks', 'tl' );
+ }
+
+ /**
* Get an array of Title objects referring to non-existent articles linked from this page
*
* @todo check if needed (used only in SpecialBrokenRedirects.php, and should use redirect table in this case)
@@ -3109,7 +3411,7 @@ class Title {
$errors = array();
// wfFindFile( $nt ) / wfLocalFile( $nt ) is not allowed until below
-
+
$file = wfLocalFile( $this );
if ( $file->exists() ) {
if ( $nt->getText() != wfStripIllegalFilenameChars( $nt->getText() ) ) {
@@ -3119,14 +3421,14 @@ class Title {
$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
+ // 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 );
@@ -3157,8 +3459,8 @@ class Title {
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 );
@@ -3168,12 +3470,14 @@ class Title {
return $status->getErrorsArray();
}
}
+ // Clear RepoGroup process cache
+ RepoGroup::singleton()->clearCache( $this );
+ RepoGroup::singleton()->clearCache( $nt ); # clear false negative cache
}
$dbw->begin(); # If $file was a LocalFile, its transaction would have closed our own.
$pageid = $this->getArticleID( self::GAID_FOR_UPDATE );
$protected = $this->isProtected();
- $pageCountChange = ( $createRedirect ? 1 : 0 ) - ( $nt->exists() ? 1 : 0 );
// Do the actual move
$err = $this->moveToInternal( $nt, $reason, $createRedirect );
@@ -3183,8 +3487,6 @@ class Title {
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.
$prefixes = $dbw->select(
@@ -3208,6 +3510,8 @@ class Title {
);
}
+ $redirid = $this->getArticleID();
+
if ( $protected ) {
# Protect the redirect title as the title used to be...
$dbw->insertSelect( 'page_restrictions', 'page_restrictions',
@@ -3243,51 +3547,8 @@ class Title {
WatchedItem::duplicateEntries( $this, $nt );
}
- # Update search engine
- $u = new SearchUpdate( $pageid, $nt->getPrefixedDBkey() );
- $u->doUpdate();
- $u = new SearchUpdate( $redirid, $this->getPrefixedDBkey(), '' );
- $u->doUpdate();
-
$dbw->commit();
- # Update site_stats
- if ( $this->isContentPage() && !$nt->isContentPage() ) {
- # No longer a content page
- # Not viewed, edited, removing
- $u = new SiteStatsUpdate( 0, 1, -1, $pageCountChange );
- } elseif ( !$this->isContentPage() && $nt->isContentPage() ) {
- # Now a content page
- # Not viewed, edited, adding
- $u = new SiteStatsUpdate( 0, 1, + 1, $pageCountChange );
- } elseif ( $pageCountChange ) {
- # Redirect added
- $u = new SiteStatsUpdate( 0, 0, 0, 1 );
- } else {
- # Nothing special
- $u = false;
- }
- if ( $u ) {
- $u->doUpdate();
- }
-
- # Update message cache for interface messages
- 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 ) {
- MessageCache::singleton()->replace( $this->getDBkey(), false );
- } else {
- $oldarticle = new Article( $this );
- MessageCache::singleton()->replace( $this->getDBkey(), $oldarticle->getContent() );
- }
- }
- if ( $nt->getNamespace() == NS_MEDIAWIKI ) {
- $newarticle = new Article( $nt );
- MessageCache::singleton()->replace( $nt->getDBkey(), $newarticle->getContent() );
- }
-
- global $wgUser;
wfRunHooks( 'TitleMoveComplete', array( &$this, &$nt, &$wgUser, $pageid, $redirid ) );
return true;
}
@@ -3304,11 +3565,28 @@ class Title {
private function moveToInternal( &$nt, $reason = '', $createRedirect = true ) {
global $wgUser, $wgContLang;
- $moveOverRedirect = $nt->exists();
+ if ( $nt->exists() ) {
+ $moveOverRedirect = true;
+ $logType = 'move_redir';
+ } else {
+ $moveOverRedirect = false;
+ $logType = 'move';
+ }
+
+ $redirectSuppressed = !$createRedirect && $wgUser->isAllowed( 'suppressredirect' );
- $commentMsg = ( $moveOverRedirect ? '1movedto2_redir' : '1movedto2' );
- $comment = wfMsgForContent( $commentMsg, $this->getPrefixedText(), $nt->getPrefixedText() );
+ $logEntry = new ManualLogEntry( 'move', $logType );
+ $logEntry->setPerformer( $wgUser );
+ $logEntry->setTarget( $this );
+ $logEntry->setComment( $reason );
+ $logEntry->setParameters( array(
+ '4::target' => $nt->getPrefixedText(),
+ '5::noredir' => $redirectSuppressed ? '1': '0',
+ ) );
+ $formatter = LogFormatter::newFromEntry( $logEntry );
+ $formatter->setContext( RequestContext::newExtraneousContext( $this ) );
+ $comment = $formatter->getPlainActionText();
if ( $reason ) {
$comment .= wfMsgForContent( 'colon-separator' ) . $reason;
}
@@ -3320,38 +3598,18 @@ class Title {
$dbw = wfGetDB( DB_MASTER );
- if ( $moveOverRedirect ) {
- $rcts = $dbw->timestamp( $nt->getEarliestRevTime() );
+ $newpage = WikiPage::factory( $nt );
+ if ( $moveOverRedirect ) {
$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__ );
- }
- // 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__
- );
+
+ $newpage->doDeleteUpdates( $newid );
}
# Save a null revision in the page's history notifying of the move
@@ -3361,68 +3619,56 @@ class Title {
}
$nullRevId = $nullRevision->insertOn( $dbw );
- $now = wfTimestampNow();
# Change the name of the target page:
$dbw->update( 'page',
/* SET */ array(
- 'page_touched' => $dbw->timestamp( $now ),
'page_namespace' => $nt->getNamespace(),
'page_title' => $nt->getDBkey(),
- 'page_latest' => $nullRevId,
),
/* WHERE */ array( 'page_id' => $oldid ),
__METHOD__
);
+
+ $this->resetArticleID( 0 );
$nt->resetArticleID( $oldid );
- $article = new Article( $nt );
- wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $wgUser ) );
- $article->setCachedLastEditTime( $now );
+ $newpage->updateRevisionOn( $dbw, $nullRevision );
+
+ wfRunHooks( 'NewRevisionFromEditComplete',
+ array( $newpage, $nullRevision, $latest, $wgUser ) );
+
+ $newpage->doEditUpdates( $nullRevision, $wgUser, array( 'changed' => false ) );
+
+ if ( !$moveOverRedirect ) {
+ WikiPage::onArticleCreate( $nt );
+ }
# Recreate the redirect, this time in the other direction.
- if ( $createRedirect || !$wgUser->isAllowed( 'suppressredirect' ) ) {
+ if ( $redirectSuppressed ) {
+ WikiPage::onArticleDelete( $this );
+ } else {
$mwRedir = MagicWord::get( 'redirect' );
$redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $nt->getPrefixedText() . "]]\n";
- $redirectArticle = new Article( $this );
+ $redirectArticle = WikiPage::factory( $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 ) );
-
- # Now, we record the link from the redirect to the new title.
- # It should have no other outgoing links...
- $dbw->delete( 'pagelinks', array( 'pl_from' => $newid ), __METHOD__ );
- $dbw->insert( 'pagelinks',
- array(
- 'pl_from' => $newid,
- 'pl_namespace' => $nt->getNamespace(),
- 'pl_title' => $nt->getDBkey() ),
- __METHOD__ );
- $redirectSuppressed = false;
- } else {
- $this->resetArticleID( 0 );
- $redirectSuppressed = true;
+ if ( $newid ) { // sanity
+ $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 ) );
+
+ $redirectArticle->doEditUpdates( $redirectRevision, $wgUser, array( 'created' => true ) );
+ }
}
# Log the move
- $log = new LogPage( 'move' );
- $logType = ( $moveOverRedirect ? 'move_redir' : 'move' );
- $log->addEntry( $logType, $this, $reason, array( 1 => $nt->getPrefixedText(), 2 => $redirectSuppressed ) );
-
- # Purge caches for old and new titles
- if ( $moveOverRedirect ) {
- # A simple purge is enough when moving over a redirect
- $nt->purgeSquid();
- } else {
- # Purge caches as per article creation, including any pages that link to this title
- Article::onArticleCreate( $nt );
- }
- $this->purgeSquid();
+ $logid = $logEntry->insert();
+ $logEntry->publish( $logid );
}
/**
@@ -3558,6 +3804,9 @@ class Title {
}
# Get the article text
$rev = Revision::newFromTitle( $nt );
+ if( !is_object( $rev ) ){
+ return false;
+ }
$text = $rev->getText();
# Does the redirect point to the source?
# Or is it a broken self-redirect, usually caused by namespace collisions?
@@ -3579,15 +3828,6 @@ class Title {
}
/**
- * Can this title be added to a user's watchlist?
- *
- * @return Bool TRUE or FALSE
- */
- public function isWatchable() {
- return !$this->isExternal() && MWNamespace::isWatchable( $this->getNamespace() );
- }
-
- /**
* Get categories to which this Title belongs and return an array of
* categories' names.
*
@@ -3748,6 +3988,41 @@ class Title {
}
/**
+ * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit
+ *
+ * @return bool
+ */
+ public function isBigDeletion() {
+ global $wgDeleteRevisionsLimit;
+
+ if ( !$wgDeleteRevisionsLimit ) {
+ return false;
+ }
+
+ $revCount = $this->estimateRevisionCount();
+ return $revCount > $wgDeleteRevisionsLimit;
+ }
+
+ /**
+ * Get the approximate revision count of this page.
+ *
+ * @return int
+ */
+ public function estimateRevisionCount() {
+ if ( !$this->exists() ) {
+ return 0;
+ }
+
+ if ( $this->mEstimateRevisions === null ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ $this->mEstimateRevisions = $dbr->estimateRowCount( 'revision', '*',
+ array( 'rev_page' => $this->getArticleId() ), __METHOD__ );
+ }
+
+ return $this->mEstimateRevisions;
+ }
+
+ /**
* Get the number of revisions between the given revision.
* Used for diffs and other things that really need it.
*
@@ -3821,28 +4096,15 @@ 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 ) {
- if ( $a->getNamespace() == $b->getNamespace() ) {
- return strcmp( $a->getText(), $b->getText() );
- } else {
- return $a->getNamespace() - $b->getNamespace();
- }
- }
-
- /**
- * Return a string representation of this title
+ * Check if this title is a subpage of another title
*
- * @return String representation of this title
+ * @param $title Title
+ * @return Bool
*/
- public function __toString() {
- return $this->getPrefixedText();
+ public function isSubpageOf( Title $title ) {
+ return $this->getInterwiki() === $title->getInterwiki()
+ && $this->getNamespace() == $title->getNamespace()
+ && strpos( $this->getDBkey(), $title->getDBkey() . '/' ) === 0;
}
/**
@@ -3956,13 +4218,23 @@ class Title {
}
/**
- * Is this in a namespace that allows actual pages?
+ * Updates page_touched for this page; called from LinksUpdate.php
*
- * @return Bool
- * @internal note -- uses hardcoded namespace index instead of constants
+ * @return Bool true if the update succeded
*/
- public function canExist() {
- return $this->mNamespace >= 0 && $this->mNamespace != NS_MEDIA;
+ public function invalidateCache() {
+ if ( wfReadOnly() ) {
+ return false;
+ }
+ $dbw = wfGetDB( DB_MASTER );
+ $success = $dbw->update(
+ 'page',
+ array( 'page_touched' => $dbw->timestamp() ),
+ $this->pageCond(),
+ __METHOD__
+ );
+ HTMLFileCache::clearFileCache( $this );
+ return $success;
}
/**
@@ -4030,46 +4302,6 @@ class Title {
}
/**
- * Get the trackback URL for this page
- *
- * @return String Trackback URL
- */
- public function trackbackURL() {
- global $wgScriptPath, $wgServer, $wgScriptExtension;
-
- return "$wgServer$wgScriptPath/trackback$wgScriptExtension?article="
- . htmlspecialchars( urlencode( $this->getPrefixedDBkey() ) );
- }
-
- /**
- * Get the trackback RDF for this page
- *
- * @return String Trackback RDF
- */
- public function trackbackRDF() {
- $url = htmlspecialchars( $this->getFullURL() );
- $title = htmlspecialchars( $this->getText() );
- $tburl = $this->trackbackURL();
-
- // Autodiscovery RDF is placed in comments so HTML validator
- // won't barf. This is a rather icky workaround, but seems
- // frequently used by this kind of RDF thingy.
- //
- // 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/\">
-<rdf:Description
- rdf:about=\"$url\"
- dc:identifier=\"$url\"
- dc:title=\"$title\"
- trackback:ping=\"$tburl\" />
-</rdf:RDF>
--->";
- }
-
- /**
* Generate strings used for xml 'id' names in monobook tabs
*
* @param $prepend string defaults to 'nstab-'
@@ -4101,61 +4333,6 @@ class Title {
}
/**
- * Returns true if this is a special page.
- *
- * @return boolean
- */
- public function isSpecialPage() {
- return $this->getNamespace() == NS_SPECIAL;
- }
-
- /**
- * Returns true if this title resolves to the named special page
- *
- * @param $name String The special page name
- * @return boolean
- */
- public function isSpecial( $name ) {
- if ( $this->getNamespace() == NS_SPECIAL ) {
- list( $thisName, /* $subpage */ ) = SpecialPageFactory::resolveAlias( $this->getDBkey() );
- if ( $name == $thisName ) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * 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 Title
- */
- public function fixSpecialName() {
- if ( $this->getNamespace() == NS_SPECIAL ) {
- list( $canonicalName, $par ) = SpecialPageFactory::resolveAlias( $this->mDbkeyform );
- if ( $canonicalName ) {
- $localName = SpecialPageFactory::getLocalNameFor( $canonicalName, $par );
- if ( $localName != $this->mDbkeyform ) {
- return Title::makeTitle( NS_SPECIAL, $localName );
- }
- }
- }
- return $this;
- }
-
- /**
- * Is this Title in a namespace which contains content?
- * In other words, is this a content page, for the purposes of calculating
- * statistics, etc?
- *
- * @return Boolean
- */
- public function isContentPage() {
- return MWNamespace::isContent( $this->getNamespace() );
- }
-
- /**
* Get all extant redirects to this Title
*
* @param $ns Int|Null Single namespace to consider; NULL to consider all namespaces
@@ -4212,7 +4389,7 @@ class Title {
/**
* Get a backlink cache object
*
- * @return object BacklinkCache
+ * @return BacklinkCache
*/
function getBacklinkCache() {
if ( is_null( $this->mBacklinkCache ) ) {
@@ -4238,50 +4415,6 @@ class Title {
}
/**
- * Returns restriction types for the current 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 ) . "\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
- * titles that do exist, False for all restriction types that apply to
- * titles that do not exist
- * @return array
- */
- public static function getFilteredRestrictionTypes( $exists = true ) {
- global $wgRestrictionTypes;
- $types = $wgRestrictionTypes;
- if ( $exists ) {
- # Remove the create restriction for existing titles
- $types = array_diff( $types, array( 'create' ) );
- } else {
- # Only the create and upload restrictions apply to non-existing titles
- $types = array_intersect( $types, array( 'create', 'upload' ) );
- }
- return $types;
- }
-
- /**
* Returns the raw sort key to be used for categories, with the specified
* prefix. This will be fed to Collation::getSortKey() to get a
* binary sortkey that can be used for actual sorting.
@@ -4320,12 +4453,9 @@ class Title {
*/
public function getPageLanguage() {
global $wgLang;
- if ( $this->getNamespace() == NS_SPECIAL ) {
+ if ( $this->isSpecialPage() ) {
// 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' );
diff --git a/includes/User.php b/includes/User.php
index af05926c..cfba748f 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -142,13 +142,11 @@ class User {
'reupload',
'reupload-shared',
'rollback',
- 'selenium',
'sendemail',
'siteadmin',
'suppressionlog',
'suppressredirect',
'suppressrevision',
- 'trackback',
'unblockself',
'undelete',
'unwatchedpages',
@@ -393,7 +391,7 @@ class User {
* If the code is invalid or has expired, returns NULL.
*
* @param $code String Confirmation code
- * @return User
+ * @return User object, or null
*/
public static function newFromConfirmationCode( $code ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -414,7 +412,7 @@ class User {
*
* @param $request WebRequest object to use; $wgRequest will be used if
* ommited.
- * @return User
+ * @return User object
*/
public static function newFromSession( WebRequest $request = null ) {
$user = new User;
@@ -447,9 +445,9 @@ class User {
/**
* Get the username corresponding to a given user ID
* @param $id Int User ID
- * @return String The corresponding username
+ * @return String|false The corresponding username
*/
- static function whoIs( $id ) {
+ public static function whoIs( $id ) {
$dbr = wfGetDB( DB_SLAVE );
return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ), __METHOD__ );
}
@@ -458,7 +456,7 @@ 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
+ * @return String|false The corresponding user's real name
*/
public static function whoIsReal( $id ) {
$dbr = wfGetDB( DB_SLAVE );
@@ -550,6 +548,7 @@ class User {
return false;
}
+
// Ensure that the name can't be misresolved as a different title,
// such as with extra namespace keys at the start.
$parsed = Title::newFromText( $name );
@@ -731,6 +730,7 @@ class User {
* @deprecated since 1.18 call Sanitizer::isValidEmail() directly
*/
public static function isValidEmailAddr( $addr ) {
+ wfDeprecated( __METHOD__, '1.18' );
return Sanitizer::validateEmail( $addr );
}
@@ -1049,6 +1049,8 @@ class User {
public function loadFromRow( $row ) {
$all = true;
+ $this->mGroups = null; // deferred
+
if ( isset( $row->user_name ) ) {
$this->mName = $row->user_name;
$this->mFrom = 'name';
@@ -1072,13 +1074,21 @@ class User {
$all = false;
}
+ if ( isset( $row->user_editcount ) ) {
+ $this->mEditCount = $row->user_editcount;
+ } 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);
+ if ( isset( $row->user_options ) ) {
+ $this->decodeOptions( $row->user_options );
+ }
+ $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
$this->mToken = $row->user_token;
if ( $this->mToken == '' ) {
$this->mToken = null;
@@ -1087,7 +1097,6 @@ class User {
$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;
}
@@ -1159,6 +1168,7 @@ class User {
$log->addEntry( 'autopromote',
$this->getUserPage(),
'', // no comment
+ // These group names are "list to texted"-ed in class LogPage.
array( implode( ', ', $oldGroups ), implode( ', ', $newGroups ) )
);
}
@@ -1207,6 +1217,12 @@ class User {
}
$defOpt['skin'] = $wgDefaultSkin;
+ // FIXME: Ideally we'd cache the results of this function so the hook is only run once,
+ // but that breaks the parser tests because they rely on being able to change $wgContLang
+ // mid-request and see that change reflected in the return value of this function.
+ // Which is insane and would never happen during normal MW operation, but is also not
+ // likely to get fixed unless and until we context-ify everything.
+ // See also https://www.mediawiki.org/wiki/Special:Code/MediaWiki/101488#c25275
wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) );
return $defOpt;
@@ -1252,44 +1268,47 @@ class User {
// overwriting mBlockedby, surely?
$this->load();
- $this->mBlockedby = 0;
- $this->mHideName = 0;
- $this->mAllowUsertalk = 0;
-
# 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();
+ $ip = $this->getRequest()->getIP();
} else {
$ip = null;
}
# User/IP blocking
- $this->mBlock = Block::newFromTarget( $this->getName(), $ip, !$bFromSlave );
- if ( $this->mBlock instanceof Block ) {
- wfDebug( __METHOD__ . ": Found block.\n" );
- $this->mBlockedby = $this->mBlock->getByName();
- $this->mBlockreason = $this->mBlock->mReason;
- $this->mHideName = $this->mBlock->mHideName;
- $this->mAllowUsertalk = !$this->mBlock->prevents( 'editownusertalk' );
- }
+ $block = Block::newFromTarget( $this->getName(), $ip, !$bFromSlave );
# Proxy blocking
- if ( $ip !== null && !$this->isAllowed( 'proxyunbannable' ) && !in_array( $ip, $wgProxyWhitelist ) ) {
+ if ( !$block instanceof Block && $ip !== null && !$this->isAllowed( 'proxyunbannable' )
+ && !in_array( $ip, $wgProxyWhitelist ) )
+ {
# Local list
if ( self::isLocallyBlockedProxy( $ip ) ) {
- $this->mBlockedby = wfMsg( 'proxyblocker' );
- $this->mBlockreason = wfMsg( 'proxyblockreason' );
+ $block = new Block;
+ $block->setBlocker( wfMsg( 'proxyblocker' ) );
+ $block->mReason = wfMsg( 'proxyblockreason' );
+ $block->setTarget( $ip );
+ } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
+ $block = new Block;
+ $block->setBlocker( wfMsg( 'sorbs' ) );
+ $block->mReason = wfMsg( 'sorbsreason' );
+ $block->setTarget( $ip );
}
+ }
- # DNSBL
- if ( !$this->mBlockedby && !$this->getID() ) {
- if ( $this->isDnsBlacklisted( $ip ) ) {
- $this->mBlockedby = wfMsg( 'sorbs' );
- $this->mBlockreason = wfMsg( 'sorbsreason' );
- }
- }
+ if ( $block instanceof Block ) {
+ wfDebug( __METHOD__ . ": Found block.\n" );
+ $this->mBlock = $block;
+ $this->mBlockedby = $block->getByName();
+ $this->mBlockreason = $block->mReason;
+ $this->mHideName = $block->mHideName;
+ $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
+ } else {
+ $this->mBlockedby = '';
+ $this->mHideName = 0;
+ $this->mAllowUsertalk = false;
}
# Extensions
@@ -1337,7 +1356,17 @@ class User {
foreach( (array)$bases as $base ) {
# Make hostname
- $host = "$ipReversed.$base";
+ # If we have an access key, use that too (ProjectHoneypot, etc.)
+ if( is_array( $base ) ) {
+ if( count( $base ) >= 2 ) {
+ # Access key is 1, base URL is 0
+ $host = "{$base[1]}.$ipReversed.{$base[0]}";
+ } else {
+ $host = "$ipReversed.{$base[0]}";
+ }
+ } else {
+ $host = "$ipReversed.$base";
+ }
# Send query
$ipList = gethostbynamel( $host );
@@ -1397,7 +1426,7 @@ class User {
*/
public function isPingLimitable() {
global $wgRateLimitsExcludedIPs;
- if( in_array( wfGetIP(), $wgRateLimitsExcludedIPs ) ) {
+ if( in_array( $this->getRequest()->getIP(), $wgRateLimitsExcludedIPs ) ) {
// No other good way currently to disable rate limits
// for specific IPs. :P
// But this is a crappy hack and should die.
@@ -1438,7 +1467,7 @@ class User {
$limits = $wgRateLimits[$action];
$keys = array();
$id = $this->getId();
- $ip = wfGetIP();
+ $ip = $this->getRequest()->getIP();
$userLimit = false;
if( isset( $limits['anon'] ) && $id == 0 ) {
@@ -1595,7 +1624,7 @@ class User {
if( IP::isIPAddress( $this->getName() ) ) {
$ip = $this->getName();
} elseif( !$ip ) {
- $ip = wfGetIP();
+ $ip = $this->getRequest()->getIP();
}
$blocked = false;
wfRunHooks( 'UserIsBlockedGlobally', array( &$this, $ip, &$blocked ) );
@@ -1673,7 +1702,7 @@ class User {
$this->load();
if ( $this->mName === false ) {
# Clean up IPs
- $this->mName = IP::sanitizeIP( wfGetIP() );
+ $this->mName = IP::sanitizeIP( $this->getRequest()->getIP() );
}
return $this->mName;
}
@@ -2081,7 +2110,11 @@ class User {
*/
public function setEmail( $str ) {
$this->load();
+ if( $str == $this->mEmail ) {
+ return;
+ }
$this->mEmail = $str;
+ $this->invalidateEmail();
wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) );
}
@@ -2259,7 +2292,7 @@ class User {
* Get the permissions this user has.
* @return Array of String permission names
*/
- function getRights() {
+ public function getRights() {
if ( is_null( $this->mRights ) ) {
$this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
wfRunHooks( 'UserGetRights', array( $this, &$this->mRights ) );
@@ -2276,6 +2309,7 @@ class User {
*/
public function getGroups() {
$this->load();
+ $this->loadGroups();
return $this->mGroups;
}
@@ -2533,6 +2567,7 @@ class User {
* @deprecated since 1.18 Use ->getSkin() in the most relevant outputting context you have
*/
public function getSkin() {
+ wfDeprecated( __METHOD__, '1.18' );
return RequestContext::getMain()->getSkin();
}
@@ -2567,14 +2602,6 @@ class User {
}
/**
- * 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.
@@ -2661,8 +2688,11 @@ class User {
/**
* Set this user's options from an encoded string
* @param $str String Encoded options to import
+ *
+ * @deprecated in 1.19 due to removal of user_options from the user table
*/
private function decodeOptions( $str ) {
+ wfDeprecated( __METHOD__, '1.19' );
if( !$str )
return;
@@ -2798,7 +2828,6 @@ class User {
'user_real_name' => $this->mRealName,
'user_email' => $this->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
- 'user_options' => '',
'user_touched' => $dbw->timestamp( $this->mTouched ),
'user_token' => strval( $this->mToken ),
'user_email_token' => $this->mEmailToken,
@@ -2866,7 +2895,6 @@ class User {
'user_email' => $user->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
'user_real_name' => $user->mRealName,
- 'user_options' => '',
'user_token' => strval( $user->mToken ),
'user_registration' => $dbw->timestamp( $user->mRegistration ),
'user_editcount' => 0,
@@ -2900,7 +2928,6 @@ class User {
'user_email' => $this->mEmail,
'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_real_name' => $this->mRealName,
- 'user_options' => '',
'user_token' => strval( $this->mToken ),
'user_registration' => $dbw->timestamp( $this->mRegistration ),
'user_editcount' => 0,
@@ -2943,7 +2970,7 @@ class User {
return false;
}
- return (bool)$userblock->doAutoblock( wfGetIP() );
+ return (bool)$userblock->doAutoblock( $this->getRequest()->getIP() );
}
/**
@@ -2961,11 +2988,12 @@ class User {
* @return String Page rendering hash
*/
public function getPageRenderingHash() {
+ wfDeprecated( __METHOD__, '1.17' );
+
global $wgUseDynamicDates, $wgRenderHashAppend, $wgLang, $wgContLang;
if( $this->mHash ){
return $this->mHash;
}
- wfDeprecated( __METHOD__ );
// stubthreshold is only included below for completeness,
// since it disables the parser cache, its value will always
@@ -3012,7 +3040,7 @@ class User {
# 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() );
+ $this->mBlockedFromCreateAccount = Block::newFromTarget( null, $this->getRequest()->getIP() );
}
return $this->mBlockedFromCreateAccount instanceof Block && $this->mBlockedFromCreateAccount->prevents( 'createaccount' )
? $this->mBlockedFromCreateAccount
@@ -3130,16 +3158,31 @@ class User {
}
/**
+ * Alias for getEditToken.
+ * @deprecated since 1.19, use getEditToken instead.
+ *
+ * @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
+ */
+ public function editToken( $salt = '', $request = null ) {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->getEditToken( $salt, $request );
+ }
+
+ /**
* Initialize (if necessary) and return a session token value
* which can be used in edit forms to show that the user's
* login credentials aren't being hijacked with a foreign form
* submission.
*
+ * @since 1.19
+ *
* @param $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
*/
- public function editToken( $salt = '', $request = null ) {
+ public function getEditToken( $salt = '', $request = null ) {
if ( $request == null ) {
$request = $this->getRequest();
}
@@ -3181,7 +3224,7 @@ class User {
* @return Boolean: Whether the token matches
*/
public function matchEditToken( $val, $salt = '', $request = null ) {
- $sessionToken = $this->editToken( $salt, $request );
+ $sessionToken = $this->getEditToken( $salt, $request );
if ( $val != $sessionToken ) {
wfDebug( "User::matchEditToken: broken session data\n" );
}
@@ -3198,7 +3241,7 @@ class User {
* @return Boolean: Whether the token matches
*/
public function matchEditTokenNoSuffix( $val, $salt = '', $request = null ) {
- $sessionToken = $this->editToken( $salt, $request );
+ $sessionToken = $this->getEditToken( $salt, $request );
return substr( $sessionToken, 0, 32 ) == substr( $val, 0, 32 );
}
@@ -3227,7 +3270,7 @@ class User {
return $this->sendMail( wfMsg( 'confirmemail_subject' ),
wfMsg( $message,
- wfGetIP(),
+ $this->getRequest()->getIP(),
$this->getName(),
$url,
$wgLang->timeanddate( $expiration, false ),
@@ -3464,15 +3507,12 @@ class User {
* Get the permissions associated with a given list of groups
*
* @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
*/
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,
@@ -3480,8 +3520,7 @@ class User {
array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
}
}
-
- // Revoke the revoked permissions
+ // now revoke the revoked permissions
foreach( $groups as $group ) {
if( isset( $wgRevokePermissions[$group] ) ) {
$rights = array_diff( $rights,
@@ -3495,9 +3534,6 @@ class User {
* Get all the groups who have a given permission
*
* @param $role String Role to check
- * @param $ns int
- *
- *
* @return Array of Strings List of internal group names with the given permission
*/
public static function getGroupsWithPermission( $role ) {
@@ -3526,10 +3562,11 @@ class User {
* Get the localized descriptive name for a member of a group, if it exists
*
* @param $group String Internal group name
+ * @param $username String Username for gender (since 1.19)
* @return String Localized name for group member
*/
- public static function getGroupMember( $group ) {
- $msg = wfMessage( "group-$group-member" );
+ public static function getGroupMember( $group, $username = '#' ) {
+ $msg = wfMessage( "group-$group-member", $username );
return $msg->isBlank() ? $group : $msg->text();
}
@@ -3872,12 +3909,12 @@ class User {
}
/**
- * Add a newuser log entry for this user
+ * Add a newuser log entry for this user. Before 1.19 the return value was always true.
*
* @param $byEmail Boolean: account made by email?
* @param $reason String: user supplied reason
*
- * @return true
+ * @return int|bool True if not $wgNewUserLog; otherwise ID of log item or 0 on failure
*/
public function addNewUserLogEntry( $byEmail = false, $reason = '' ) {
global $wgUser, $wgContLang, $wgNewUserLog;
@@ -3899,13 +3936,12 @@ class User {
}
}
$log = new LogPage( 'newusers' );
- $log->addEntry(
+ return (int)$log->addEntry(
$action,
$this->getUserPage(),
$reason,
array( $this->getId() )
);
- return true;
}
/**
@@ -3952,6 +3988,7 @@ class User {
__METHOD__
);
+ $this->mOptionOverrides = array();
foreach ( $res as $row ) {
$this->mOptionOverrides[$row->up_property] = $row->up_value;
$this->mOptions[$row->up_property] = $row->up_value;
@@ -4007,10 +4044,8 @@ class User {
}
}
- $dbw->begin();
$dbw->delete( 'user_properties', array( 'up_user' => $this->getId() ), __METHOD__ );
$dbw->insert( 'user_properties', $insert_rows, __METHOD__ );
- $dbw->commit();
}
/**
diff --git a/includes/UserArray.php b/includes/UserArray.php
index 6cce48c0..c5ba0b2b 100644
--- a/includes/UserArray.php
+++ b/includes/UserArray.php
@@ -77,6 +77,9 @@ class UserArrayFromResult extends UserArray {
return $this->res->numRows();
}
+ /**
+ * @return User
+ */
function current() {
return $this->current;
}
diff --git a/includes/UserMailer.php b/includes/UserMailer.php
index 63f5bf2e..5d98d9d2 100644
--- a/includes/UserMailer.php
+++ b/includes/UserMailer.php
@@ -68,7 +68,7 @@ class MailAddress {
return $this->address;
}
} else {
- return '';
+ return "";
}
}
@@ -107,6 +107,39 @@ class UserMailer {
}
/**
+ * Creates a single string from an associative array
+ *
+ * @param $headers Associative Array: keys are header field names,
+ * values are ... values.
+ * @param $endl String: The end of line character. Defaults to "\n"
+ * @return String
+ */
+ static function arrayToHeaderString( $headers, $endl = "\n" ) {
+ foreach( $headers as $name => $value ) {
+ $string[] = "$name: $value";
+ }
+ return implode( $endl, $string );
+ }
+
+ /**
+ * Create a value suitable for the MessageId Header
+ *
+ * @return String
+ */
+ static function makeMsgId() {
+ global $wgSMTP, $wgServer;
+
+ $msgid = uniqid( wfWikiID() . ".", true ); /* true required for cygwin */
+ if ( is_array($wgSMTP) && isset($wgSMTP['IDHost']) && $wgSMTP['IDHost'] ) {
+ $domain = $wgSMTP['IDHost'];
+ } else {
+ $url = wfParseUrl($wgServer);
+ $domain = $url['host'];
+ }
+ return "<$msgid@$domain>";
+ }
+
+ /**
* This function will perform a direct (authenticated) login to
* a SMTP Server to use for mail relaying if 'wgSMTP' specifies an
* array of parameters. It requires PEAR:Mail to do that.
@@ -120,7 +153,7 @@ class UserMailer {
* @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 = 'text/plain; charset=UTF-8') {
+ public static function send( $to, $from, $subject, $body, $replyto = null, $contentType = 'text/plain; charset=UTF-8' ) {
global $wgSMTP, $wgEnotifMaxRecips, $wgAdditionalMailParams;
if ( !is_array( $to ) ) {
@@ -129,7 +162,73 @@ class UserMailer {
wfDebug( __METHOD__ . ': sending mail to ' . implode( ', ', $to ) . "\n" );
+ # Make sure we have at least one address
+ $has_address = false;
+ foreach ( $to as $u ) {
+ if ( $u->address ) {
+ $has_address = true;
+ break;
+ }
+ }
+ if ( !$has_address ) {
+ return Status::newFatal( 'user-mail-no-addy' );
+ }
+
+ # Forge email headers
+ # -------------------
+ #
+ # WARNING
+ #
+ # DO NOT add To: or Subject: headers at this step. They need to be
+ # handled differently depending upon the mailer we are going to use.
+ #
+ # To:
+ # PHP mail() first argument is the mail receiver. The argument is
+ # used as a recipient destination and as a To header.
+ #
+ # PEAR mailer has a recipient argument which is only used to
+ # send the mail. If no To header is given, PEAR will set it to
+ # to 'undisclosed-recipients:'.
+ #
+ # NOTE: To: is for presentation, the actual recipient is specified
+ # by the mailer using the Rcpt-To: header.
+ #
+ # Subject:
+ # PHP mail() second argument to pass the subject, passing a Subject
+ # as an additional header will result in a duplicate header.
+ #
+ # PEAR mailer should be passed a Subject header.
+ #
+ # -- hashar 20120218
+
+ $headers['From'] = $from->toString();
+ $headers['Return-Path'] = $from->address;
+
+ if ( $replyto ) {
+ $headers['Reply-To'] = $replyto->toString();
+ }
+
+ $headers['Date'] = date( 'r' );
+ $headers['MIME-Version'] = '1.0';
+ $headers['Content-type'] = ( is_null( $contentType ) ?
+ 'text/plain; charset=UTF-8' : $contentType );
+ $headers['Content-transfer-encoding'] = '8bit';
+
+ $headers['Message-ID'] = self::makeMsgId();
+ $headers['X-Mailer'] = 'MediaWiki mailer';
+
+ $ret = wfRunHooks( 'AlternateUserMailer', array( $headers, $to, $from, $subject, $body ) );
+ if ( $ret === false ) {
+ return Status::newGood();
+ } elseif ( $ret !== true ) {
+ return Status::newFatal( 'php-mail-error', $ret );
+ }
+
if ( is_array( $wgSMTP ) ) {
+ #
+ # PEAR MAILER
+ #
+
if ( function_exists( 'stream_resolve_include_path' ) ) {
$found = stream_resolve_include_path( 'Mail.php' );
} else {
@@ -140,43 +239,6 @@ class UserMailer {
}
require_once( 'Mail.php' );
- $msgid = str_replace( " ", "_", microtime() );
- if ( function_exists( 'posix_getpid' ) ) {
- $msgid .= '.' . posix_getpid();
- }
-
- if ( is_array( $to ) ) {
- $dest = array();
- foreach ( $to as $u ) {
- $dest[] = $u->address;
- }
- } else {
- $dest = $to->address;
- }
-
- $headers['From'] = $from->toString();
- $headers['Return-Path'] = $from->address;
-
- if ( count( $to ) > 1 ) {
- $headers['To'] = 'undisclosed-recipients:;';
- }
- else {
- $headers['To'] = $to[0]->toString();
- }
-
- if ( $replyto ) {
- $headers['Reply-To'] = $replyto->toString();
- }
- $headers['Subject'] = self::quotedPrintable( $subject );
- $headers['Date'] = date( 'r' );
- $headers['MIME-Version'] = '1.0';
- $headers['Content-type'] = ( is_null( $contentType ) ?
- 'text/plain; charset=UTF-8' : $contentType );
- $headers['Content-transfer-encoding'] = '8bit';
- // @todo FIXME
- $headers['Message-ID'] = "<$msgid@" . $wgSMTP['IDHost'] . '>';
- $headers['X-Mailer'] = 'MediaWiki mailer';
-
wfSuppressWarnings();
// Create the mail object using the Mail::factory method
@@ -187,10 +249,21 @@ class UserMailer {
return Status::newFatal( 'pear-mail-error', $mail_object->getMessage() );
}
- wfDebug( "Sending mail via PEAR::Mail to $dest\n" );
- $chunks = array_chunk( (array)$dest, $wgEnotifMaxRecips );
+ wfDebug( "Sending mail via PEAR::Mail\n" );
+
+ $headers['Subject'] = self::quotedPrintable( $subject );
+
+ # When sending only to one recipient, shows it its email using To:
+ if ( count( $to ) == 1 ) {
+ $headers['To'] = $to[0]->toString();
+ }
+
+ # Split jobs since SMTP servers tends to limit the maximum
+ # number of possible recipients.
+ $chunks = array_chunk( $to, $wgEnotifMaxRecips );
foreach ( $chunks as $chunk ) {
$status = self::sendWithPear( $mail_object, $chunk, $headers, $body );
+ # FIXME : some chunks might be sent while others are not!
if ( !$status->isOK() ) {
wfRestoreWarnings();
return $status;
@@ -199,6 +272,10 @@ class UserMailer {
wfRestoreWarnings();
return Status::newGood();
} else {
+ #
+ # PHP mail()
+ #
+
# Line endings need to be different on Unix and Windows due to
# the bug described at http://trac.wordpress.org/ticket/2603
if ( wfIsWindows() ) {
@@ -208,18 +285,10 @@ class UserMailer {
$endl = "\n";
}
- $headers = array(
- "MIME-Version: 1.0",
- "Content-type: $contentType",
- "Content-Transfer-Encoding: 8bit",
- "X-Mailer: MediaWiki mailer",
- "From: " . $from->toString(),
- );
- if ( $replyto ) {
- $headers[] = "Reply-To: " . $replyto->toString();
+ if( count($to) > 1 ) {
+ $headers['To'] = 'undisclosed-recipients:;';
}
-
- $headers = implode( $endl, $headers );
+ $headers = self::arrayToHeaderString( $headers, $endl );
wfDebug( "Sending mail via internal mail() function\n" );
@@ -228,11 +297,13 @@ class UserMailer {
ini_set( 'html_errors', '0' );
set_error_handler( 'UserMailer::errorHandler' );
- if ( !is_array( $to ) ) {
- $to = array( $to );
- }
+ $safeMode = wfIniGetBool( 'safe_mode' );
foreach ( $to as $recip ) {
- $sent = mail( $recip->toString(), self::quotedPrintable( $subject ), $body, $headers, $wgAdditionalMailParams );
+ if ( $safeMode ) {
+ $sent = mail( $recip, self::quotedPrintable( $subject ), $body, $headers );
+ } else {
+ $sent = mail( $recip, self::quotedPrintable( $subject ), $body, $headers, $wgAdditionalMailParams );
+ }
}
restore_error_handler();
@@ -243,7 +314,7 @@ class UserMailer {
return Status::newFatal( 'php-mail-error', self::$mErrorString );
} elseif ( ! $sent ) {
// mail function only tells if there's an error
- wfDebug( "Error sending mail\n" );
+ wfDebug( "Unknown error sending mail\n" );
return Status::newFatal( 'php-mail-error-unknown' );
} else {
return Status::newGood();
@@ -321,11 +392,21 @@ class UserMailer {
*
*/
class EmailNotification {
- protected $to, $subject, $body, $replyto, $from;
- protected $user, $title, $timestamp, $summary, $minorEdit, $oldid, $composed_common, $editor;
+ protected $subject, $body, $replyto, $from;
+ protected $timestamp, $summary, $minorEdit, $oldid, $composed_common;
protected $mailTargets = array();
/**
+ * @var Title
+ */
+ protected $title;
+
+ /**
+ * @var User
+ */
+ protected $editor;
+
+ /**
* Send emails corresponding to the user $editor editing the page $title.
* Also updates wl_notificationtimestamp.
*
@@ -339,7 +420,8 @@ class EmailNotification {
* @param $oldid (default: false)
*/
public function notifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid = false ) {
- global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker;
+ global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker, $wgEnotifMinorEdits,
+ $wgUsersNotifiedOnAllChanges, $wgEnotifUserTalk;
if ( $title->getNamespace() < 0 ) {
return;
@@ -378,21 +460,39 @@ class EmailNotification {
}
}
+ $sendEmail = true;
+ // If nobody is watching the page, and there are no users notified on all changes
+ // don't bother creating a job/trying to send emails
+ // $watchers deals with $wgEnotifWatchlist
+ if ( !count( $watchers ) && !count( $wgUsersNotifiedOnAllChanges ) ) {
+ $sendEmail = false;
+ // Only send notification for non minor edits, unless $wgEnotifMinorEdits
+ if ( !$minorEdit || ( $wgEnotifMinorEdits && !$editor->isAllowed( 'nominornewtalk' ) ) ) {
+ $isUserTalkPage = ( $title->getNamespace() == NS_USER_TALK );
+ if ( $wgEnotifUserTalk && $isUserTalkPage && $this->canSendUserTalkEmail( $editor, $title, $minorEdit ) ) {
+ $sendEmail = true;
+ }
+ }
+ }
+
+ if ( !$sendEmail ) {
+ return;
+ }
if ( $wgEnotifUseJobQ ) {
$params = array(
- "editor" => $editor->getName(),
- "editorID" => $editor->getID(),
- "timestamp" => $timestamp,
- "summary" => $summary,
- "minorEdit" => $minorEdit,
- "oldid" => $oldid,
- "watchers" => $watchers );
+ 'editor' => $editor->getName(),
+ 'editorID' => $editor->getID(),
+ 'timestamp' => $timestamp,
+ 'summary' => $summary,
+ 'minorEdit' => $minorEdit,
+ 'oldid' => $oldid,
+ 'watchers' => $watchers
+ );
$job = new EnotifNotifyJob( $title, $params );
$job->insert();
} else {
$this->actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers );
}
-
}
/**
@@ -433,25 +533,11 @@ class EmailNotification {
$userTalkId = false;
if ( !$minorEdit || ( $wgEnotifMinorEdits && !$editor->isAllowed( 'nominornewtalk' ) ) ) {
- if ( $wgEnotifUserTalk && $isUserTalkPage ) {
+
+ if ( $wgEnotifUserTalk && $isUserTalkPage && $this->canSendUserTalkEmail( $editor, $title, $minorEdit ) ) {
$targetUser = User::newFromName( $title->getText() );
- if ( !$targetUser || $targetUser->isAnon() ) {
- wfDebug( __METHOD__ . ": user talk page edited, but user does not exist\n" );
- } elseif ( $targetUser->getId() == $editor->getId() ) {
- wfDebug( __METHOD__ . ": user edited their own talk page, no notification sent\n" );
- } elseif ( $targetUser->getOption( 'enotifusertalkpages' ) &&
- ( !$minorEdit || $targetUser->getOption( 'enotifminoredits' ) ) )
- {
- if ( $targetUser->isEmailConfirmed() ) {
- wfDebug( __METHOD__ . ": sending talk page update notification\n" );
- $this->compose( $targetUser );
- $userTalkId = $targetUser->getId();
- } else {
- wfDebug( __METHOD__ . ": talk page owner doesn't have validated email\n" );
- }
- } else {
- wfDebug( __METHOD__ . ": talk page owner doesn't want notifications\n" );
- }
+ $this->compose( $targetUser );
+ $userTalkId = $targetUser->getId();
}
if ( $wgEnotifWatchlist ) {
@@ -471,6 +557,10 @@ class EmailNotification {
global $wgUsersNotifiedOnAllChanges;
foreach ( $wgUsersNotifiedOnAllChanges as $name ) {
+ if ( $editor->getName() == $name ) {
+ // No point notifying the user that actually made the change!
+ continue;
+ }
$user = User::newFromName( $name );
$this->compose( $user );
}
@@ -480,6 +570,39 @@ class EmailNotification {
}
/**
+ * @param $editor User
+ * @param $title Title bool
+ * @param $minorEdit
+ * @return bool
+ */
+ private function canSendUserTalkEmail( $editor, $title, $minorEdit ) {
+ global $wgEnotifUserTalk;
+ $isUserTalkPage = ( $title->getNamespace() == NS_USER_TALK );
+
+ if ( $wgEnotifUserTalk && $isUserTalkPage ) {
+ $targetUser = User::newFromName( $title->getText() );
+
+ if ( !$targetUser || $targetUser->isAnon() ) {
+ wfDebug( __METHOD__ . ": user talk page edited, but user does not exist\n" );
+ } elseif ( $targetUser->getId() == $editor->getId() ) {
+ wfDebug( __METHOD__ . ": user edited their own talk page, no notification sent\n" );
+ } elseif ( $targetUser->getOption( 'enotifusertalkpages' ) &&
+ ( !$minorEdit || $targetUser->getOption( 'enotifminoredits' ) ) )
+ {
+ if ( $targetUser->isEmailConfirmed() ) {
+ wfDebug( __METHOD__ . ": sending talk page update notification\n" );
+ return true;
+ } else {
+ wfDebug( __METHOD__ . ": talk page owner doesn't have validated email\n" );
+ }
+ } else {
+ wfDebug( __METHOD__ . ": talk page owner doesn't want notifications\n" );
+ }
+ }
+ return false;
+ }
+
+ /**
* Generate the generic "this page has been changed" e-mail text.
*/
private function composeCommonMailtext() {
@@ -494,10 +617,17 @@ class EmailNotification {
# simply editing the Meta pages
$keys = array();
+ $postTransformKeys = array();
if ( $this->oldid ) {
- $difflink = $this->title->getCanonicalUrl( 'diff=0&oldid=' . $this->oldid );
- $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastvisited', $difflink );
+ if ( $wgEnotifImpersonal ) {
+ // For impersonal mail, show a diff link to the last revision.
+ $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastdiff',
+ $this->title->getCanonicalUrl( 'diff=next&oldid=' . $this->oldid ) );
+ } else {
+ $keys['$NEWPAGE'] = wfMsgForContent( 'enotif_lastvisited',
+ $this->title->getCanonicalUrl( 'diff=0&oldid=' . $this->oldid ) );
+ }
$keys['$OLDID'] = $this->oldid;
$keys['$CHANGEDORCREATED'] = wfMsgForContent( 'changed' );
} else {
@@ -510,7 +640,6 @@ class EmailNotification {
$keys['$PAGETITLE'] = $this->title->getPrefixedText();
$keys['$PAGETITLE_URL'] = $this->title->getCanonicalUrl();
$keys['$PAGEMINOREDIT'] = $this->minorEdit ? wfMsgForContent( 'minoredit' ) : '';
- $keys['$PAGESUMMARY'] = $this->summary == '' ? ' - ' : $this->summary;
$keys['$UNWATCHURL'] = $this->title->getCanonicalUrl( 'action=unwatch' );
if ( $this->editor->isAnon() ) {
@@ -525,16 +654,20 @@ class EmailNotification {
$keys['$PAGEEDITOR_WIKI'] = $this->editor->getUserPage()->getCanonicalUrl();
+ # Replace this after transforming the message, bug 35019
+ $postTransformKeys['$PAGESUMMARY'] = $this->summary == '' ? ' - ' : $this->summary;
+
# Now build message's subject and body
$subject = wfMsgExt( 'enotif_subject', 'content' );
$subject = strtr( $subject, $keys );
- $this->subject = MessageCache::singleton()->transform( $subject, false, null, $this->title );
+ $subject = MessageCache::singleton()->transform( $subject, false, null, $this->title );
+ $this->subject = strtr( $subject, $postTransformKeys );
$body = wfMsgExt( 'enotif_body', 'content' );
$body = strtr( $body, $keys );
$body = MessageCache::singleton()->transform( $body, false, null, $this->title );
- $this->body = wordwrap( $body, 72 );
+ $this->body = wordwrap( strtr( $body, $postTransformKeys ), 72 );
# Reveal the page editor's address as REPLY-TO address only if
# the user has not opted-out and the option is enabled at the
@@ -562,6 +695,7 @@ class EmailNotification {
* depending on settings.
*
* Call sendMails() to send any mails that were queued.
+ * @param $user User
*/
function compose( $user ) {
global $wgEnotifImpersonal;
@@ -601,22 +735,18 @@ class EmailNotification {
// Note: The to parameter cannot be an address in the form of "Something <someone@example.com>".
// The mail command will not parse this properly while talking with the MTA.
$to = new MailAddress( $watchingUser );
- $name = $wgEnotifUseRealName ? $watchingUser->getRealName() : $watchingUser->getName();
- $body = str_replace( '$WATCHINGUSERNAME', $name, $this->body );
-
- $timecorrection = $watchingUser->getOption( 'timecorrection' );
# $PAGEEDITDATE is the time and date of the page change
# expressed in terms of individual local time of the notification
# recipient, i.e. watching user
$body = str_replace(
- array( '$PAGEEDITDATEANDTIME',
+ array( '$WATCHINGUSERNAME',
'$PAGEEDITDATE',
'$PAGEEDITTIME' ),
- array( $wgContLang->timeanddate( $this->timestamp, true, false, $timecorrection ),
- $wgContLang->date( $this->timestamp, true, false, $timecorrection ),
- $wgContLang->time( $this->timestamp, true, false, $timecorrection ) ),
- $body );
+ array( $wgEnotifUseRealName ? $watchingUser->getRealName() : $watchingUser->getName(),
+ $wgContLang->userDate( $this->timestamp, $watchingUser ),
+ $wgContLang->userTime( $this->timestamp, $watchingUser ) ),
+ $this->body );
return UserMailer::send( $to, $this->from, $this->subject, $body, $this->replyto );
}
@@ -636,8 +766,8 @@ class EmailNotification {
'$PAGEEDITDATE',
'$PAGEEDITTIME' ),
array( wfMsgForContent( 'enotif_impersonal_salutation' ),
- $wgContLang->date( $this->timestamp, true, false, false ),
- $wgContLang->time( $this->timestamp, true, false, false ) ),
+ $wgContLang->date( $this->timestamp, false, false ),
+ $wgContLang->time( $this->timestamp, false, false ) ),
$this->body );
return UserMailer::send( $addresses, $this->from, $this->subject, $body, $this->replyto );
diff --git a/includes/UserRightsProxy.php b/includes/UserRightsProxy.php
index 6c2a5f12..dfce8adf 100644
--- a/includes/UserRightsProxy.php
+++ b/includes/UserRightsProxy.php
@@ -85,6 +85,13 @@ class UserRightsProxy {
return self::newFromLookup( $database, 'user_name', $name, $ignoreInvalidDB );
}
+ /**
+ * @param $database
+ * @param $field
+ * @param $value
+ * @param $ignoreInvalidDB bool
+ * @return null|UserRightsProxy
+ */
private static function newFromLookup( $database, $field, $value, $ignoreInvalidDB = false ) {
$db = self::getDB( $database, $ignoreInvalidDB );
if( $db ) {
@@ -122,10 +129,16 @@ class UserRightsProxy {
return null;
}
+ /**
+ * @return int
+ */
public function getId() {
return $this->id;
}
+ /**
+ * @return bool
+ */
public function isAnon() {
return $this->getId() == 0;
}
@@ -187,14 +200,14 @@ class UserRightsProxy {
),
__METHOD__ );
}
-
+
/**
* Replaces User::setOption()
*/
public function setOption( $option, $value ) {
$this->newOptions[$option] = $value;
}
-
+
public function saveSettings() {
$rows = array();
foreach ( $this->newOptions as $option => $value ) {
diff --git a/includes/ViewCountUpdate.php b/includes/ViewCountUpdate.php
index 0642b630..a30b0f79 100644
--- a/includes/ViewCountUpdate.php
+++ b/includes/ViewCountUpdate.php
@@ -27,7 +27,7 @@
* '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 {
+class ViewCountUpdate implements DeferrableUpdate {
protected $id;
/**
diff --git a/includes/WebRequest.php b/includes/WebRequest.php
index 44fe6610..39f9cb8e 100644
--- a/includes/WebRequest.php
+++ b/includes/WebRequest.php
@@ -44,6 +44,12 @@ class WebRequest {
*/
private $response;
+ /**
+ * Cached client IP address
+ * @var String
+ */
+ private $ip;
+
public function __construct() {
/// @todo FIXME: This preemptive de-quoting can interfere with other web libraries
/// and increases our memory footprint. It would be cleaner to do on
@@ -56,15 +62,19 @@ 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.
+ * Extract relevant query arguments from the http request uri's path
+ * to be merged with the normal php provided query arguments.
+ * Tries to use the REQUEST_URI data if available and parses it
+ * according to the wiki's configuration looking for any known pattern.
+ *
+ * If the REQUEST_URI is not provided we'll fall back on the PATH_INFO
+ * provided by the server if any and use that to set a 'title' parameter.
*
* @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.
+ * @return Array: Any query arguments found in path matches.
*/
static public function getPathInfo( $want = 'all' ) {
// PATH_INFO is mangled due to http://bugs.php.net/bug.php?id=31892
@@ -87,28 +97,42 @@ class WebRequest {
// Abort to keep from breaking...
return $matches;
}
+
+ $router = new PathRouter;
+
// Raw PATH_INFO style
- $matches = self::extractTitle( $path, "$wgScript/$1" );
+ $router->add( "$wgScript/$1" );
+
+ if( isset( $_SERVER['SCRIPT_NAME'] )
+ && preg_match( '/\.php5?/', $_SERVER['SCRIPT_NAME'] ) )
+ {
+ # Check for SCRIPT_NAME, we handle index.php explicitly
+ # But we do have some other .php files such as img_auth.php
+ # Don't let root article paths clober the parsing for them
+ $router->add( $_SERVER['SCRIPT_NAME'] . "/$1" );
+ }
global $wgArticlePath;
- if( !$matches && $wgArticlePath ) {
- $matches = self::extractTitle( $path, $wgArticlePath );
+ if( $wgArticlePath ) {
+ $router->add( $wgArticlePath );
}
global $wgActionPaths;
- if( !$matches && $wgActionPaths ) {
- $matches = self::extractTitle( $path, $wgActionPaths, 'action' );
+ if( $wgActionPaths ) {
+ $router->add( $wgActionPaths, array( 'action' => '$key' ) );
}
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' );
+ if( $wgVariantArticlePath ) {
+ $router->add( $wgVariantArticlePath,
+ array( 'variant' => '$2'),
+ array( '$2' => $wgContLang->getVariants() )
+ );
}
+
+ wfRunHooks( 'WebRequestPathInfoRouter', array( $router ) );
+
+ $matches = $router->parse( $path );
}
} elseif ( isset( $_SERVER['ORIG_PATH_INFO'] ) && $_SERVER['ORIG_PATH_INFO'] != '' ) {
// Mangled PATH_INFO
@@ -158,11 +182,17 @@ class WebRequest {
return $proto . '://' . IP::combineHostAndPort( $host, $port, $stdPort );
}
-
+
+ /**
+ * @return array
+ */
public static function detectProtocolAndStdPort() {
return ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] == 'on' ) ? array( 'https', 443 ) : array( 'http', 80 );
}
-
+
+ /**
+ * @return string
+ */
public static function detectProtocol() {
list( $proto, $stdPort ) = self::detectProtocolAndStdPort();
return $proto;
@@ -192,7 +222,7 @@ class WebRequest {
}
/**
- * Internal URL rewriting function; tries to extract page title and,
+ * URL rewriting function; tries to extract page title and,
* optionally, one other fixed parameter value from a URL path.
*
* @param $path string: the URL path given from the client
@@ -201,7 +231,7 @@ class WebRequest {
* passed on as the value of this URL parameter
* @return array of URL variables to interpolate; empty if no match
*/
- private static function extractTitle( $path, $bases, $key = false ) {
+ static function extractTitle( $path, $bases, $key = false ) {
foreach( (array)$bases as $keyValue => $base ) {
// Find the part after $wgArticlePath
$base = str_replace( '$1', '', $base );
@@ -225,16 +255,24 @@ class WebRequest {
* used for undoing the evil that is magic_quotes_gpc.
*
* @param $arr array: will be modified
+ * @param $topLevel bool Specifies if the array passed is from the top
+ * level of the source. In PHP5 magic_quotes only escapes the first level
+ * of keys that belong to an array.
* @return array the original array
+ * @see http://www.php.net/manual/en/function.get-magic-quotes-gpc.php#49612
*/
- private function &fix_magic_quotes( &$arr ) {
+ private function &fix_magic_quotes( &$arr, $topLevel = true ) {
+ $clean = array();
foreach( $arr as $key => $val ) {
if( is_array( $val ) ) {
- $this->fix_magic_quotes( $arr[$key] );
+ $cleanKey = $topLevel ? stripslashes( $key ) : $key;
+ $clean[$cleanKey] = $this->fix_magic_quotes( $arr[$key], false );
} else {
- $arr[$key] = stripslashes( $val );
+ $cleanKey = stripslashes( $key );
+ $clean[$cleanKey] = stripslashes( $val );
}
}
+ $arr = $clean;
return $arr;
}
@@ -505,7 +543,7 @@ class WebRequest {
* @return Array
*/
public function getQueryValues() {
- return $_GET;
+ return $_GET;
}
/**
@@ -518,7 +556,7 @@ class WebRequest {
* @return Boolean
*/
public function wasPosted() {
- return $_SERVER['REQUEST_METHOD'] == 'POST';
+ return isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] == 'POST';
}
/**
@@ -595,7 +633,7 @@ class WebRequest {
* 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.
*
@@ -705,6 +743,7 @@ class WebRequest {
* @return integer
*/
public function getFileSize( $key ) {
+ wfDeprecated( __METHOD__, '1.17' );
$file = new WebRequestUpload( $this, $key );
return $file->getSize();
}
@@ -855,10 +894,8 @@ class WebRequest {
return false;
}
}
- wfHttpError( 403, 'Forbidden',
+ throw new HttpError( 403,
'Invalid file extension found in the path info or query string.' );
-
- return false;
}
return true;
}
@@ -913,8 +950,13 @@ HTML;
* if there was no dot before the question mark (bug 28235).
*
* @deprecated Use checkUrlExtension().
+ *
+ * @param $extWhitelist array
+ *
+ * @return bool
*/
public function isPathInfoBad( $extWhitelist = array() ) {
+ wfDeprecated( __METHOD__, '1.17' );
global $wgScriptExtension;
$extWhitelist[] = ltrim( $wgScriptExtension, '.' );
return IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist );
@@ -922,7 +964,7 @@ HTML;
/**
* Parse the Accept-Language header sent by the client into an array
- * @return array( languageCode => q-value ) sorted by q-value in descending order
+ * @return array array( languageCode => q-value ) sorted by q-value in descending order
* May contain the "language" '*', which applies to languages other than those explicitly listed.
* This is aligned with rfc2616 section 14.4
*/
@@ -938,7 +980,7 @@ HTML;
// Break up string into pieces (languages and q factors)
$lang_parse = null;
- preg_match_all( '/([a-z]{1,8}(-[a-z]{1,8})?|\*)\s*(;\s*q\s*=\s*(1|0(\.[0-9]+)?)?)?/',
+ preg_match_all( '/([a-z]{1,8}(-[a-z]{1,8})*|\*)\s*(;\s*q\s*=\s*(1(\.0{0,3})?|0(\.[0-9]{0,3})?)?)?/',
$acceptLang, $lang_parse );
if ( !count( $lang_parse[1] ) ) {
@@ -960,6 +1002,77 @@ HTML;
arsort( $langs, SORT_NUMERIC );
return $langs;
}
+
+ /**
+ * Fetch the raw IP from the request
+ *
+ * @since 1.19
+ *
+ * @return String
+ */
+ protected function getRawIP() {
+ if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
+ return IP::canonicalize( $_SERVER['REMOTE_ADDR'] );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Work out the IP address based on various globals
+ * For trusted proxies, use the XFF client IP (first of the chain)
+ *
+ * @since 1.19
+ *
+ * @return string
+ */
+ public function getIP() {
+ global $wgUsePrivateIPs;
+
+ # Return cached result
+ if ( $this->ip !== null ) {
+ return $this->ip;
+ }
+
+ # collect the originating ips
+ $ip = $this->getRawIP();
+
+ # Append XFF
+ $forwardedFor = $this->getHeader( 'X-Forwarded-For' );
+ if ( $forwardedFor !== false ) {
+ $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];
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ # Allow extensions to improve our guess
+ wfRunHooks( 'GetIP', array( &$ip ) );
+
+ if ( !$ip ) {
+ throw new MWException( "Unable to determine IP" );
+ }
+
+ wfDebug( "IP: $ip\n" );
+ $this->ip = $ip;
+ return $ip;
+ }
}
/**
@@ -1091,7 +1204,7 @@ class FauxRequest extends WebRequest {
* @param $wasPosted Bool: whether to treat the data as POST
* @param $session Mixed: session array or null
*/
- public function __construct( $data, $wasPosted = false, $session = null ) {
+ public function __construct( $data = array(), $wasPosted = false, $session = null ) {
if( is_array( $data ) ) {
$this->data = $data;
} else {
@@ -1102,19 +1215,34 @@ class FauxRequest extends WebRequest {
$this->session = $session;
}
+ /**
+ * @param $method string
+ * @throws MWException
+ */
private function notImplemented( $method ) {
throw new MWException( "{$method}() not implemented" );
}
+ /**
+ * @param $name string
+ * @param $default string
+ * @return string
+ */
public function getText( $name, $default = '' ) {
# Override; don't recode since we're using internal data
return (string)$this->getVal( $name, $default );
}
+ /**
+ * @return Array
+ */
public function getValues() {
return $this->data;
}
+ /**
+ * @return array
+ */
public function getQueryValues() {
if ( $this->wasPosted ) {
return array();
@@ -1123,6 +1251,9 @@ class FauxRequest extends WebRequest {
}
}
+ /**
+ * @return bool
+ */
public function wasPosted() {
return $this->wasPosted;
}
@@ -1135,32 +1266,114 @@ class FauxRequest extends WebRequest {
$this->notImplemented( __METHOD__ );
}
+ /**
+ * @param $name
+ * @return bool|string
+ */
public function getHeader( $name ) {
return isset( $this->headers[$name] ) ? $this->headers[$name] : false;
}
+ /**
+ * @param $name string
+ * @param $val string
+ */
public function setHeader( $name, $val ) {
$this->headers[$name] = $val;
}
+ /**
+ * @param $key
+ * @return mixed
+ */
public function getSessionData( $key ) {
if( isset( $this->session[$key] ) )
return $this->session[$key];
}
+ /**
+ * @param $key
+ * @param $data
+ */
public function setSessionData( $key, $data ) {
$this->session[$key] = $data;
}
+ /**
+ * @return array|Mixed|null
+ */
public function getSessionArray() {
return $this->session;
}
+ /**
+ * @param array $extWhitelist
+ * @return bool
+ */
public function isPathInfoBad( $extWhitelist = array() ) {
return false;
}
+ /**
+ * @param array $extWhitelist
+ * @return bool
+ */
public function checkUrlExtension( $extWhitelist = array() ) {
return true;
}
+
+ /**
+ * @return string
+ */
+ protected function getRawIP() {
+ return '127.0.0.1';
+ }
+}
+
+/**
+ * Similar to FauxRequest, but only fakes URL parameters and method
+ * (POST or GET) and use the base request for the remaining stuff
+ * (cookies, session and headers).
+ *
+ * @ingroup HTTP
+ */
+class DerivativeRequest extends FauxRequest {
+ private $base;
+
+ public function __construct( WebRequest $base, $data, $wasPosted = false ) {
+ $this->base = $base;
+ parent::__construct( $data, $wasPosted );
+ }
+
+ public function getCookie( $key, $prefix = null, $default = null ) {
+ return $this->base->getCookie( $key, $prefix, $default );
+ }
+
+ public function checkSessionCookie() {
+ return $this->base->checkSessionCookie();
+ }
+
+ public function getHeader( $name ) {
+ return $this->base->getHeader( $name );
+ }
+
+ public function getAllHeaders() {
+ return $this->base->getAllHeaders();
+ }
+
+ public function getSessionData( $key ) {
+ return $this->base->getSessionData( $key );
+ }
+
+ public function setSessionData( $key, $data ) {
+ return $this->base->setSessionData( $key, $data );
+ }
+
+ public function getAcceptLang() {
+ return $this->base->getAcceptLang();
+ }
+
+ public function getIP() {
+ return $this->base->getIP();
+ }
}
diff --git a/includes/WebResponse.php b/includes/WebResponse.php
index 1101d75d..193101b1 100644
--- a/includes/WebResponse.php
+++ b/includes/WebResponse.php
@@ -131,9 +131,14 @@ class FauxResponse extends WebResponse {
}
/**
+ * @todo document. It just ignore optional parameters.
+ *
* @param $name String: name of cookie
* @param $value String: value to give cookie
- * @param $expire Int: number of seconds til cookie expires
+ * @param $expire Int: number of seconds til cookie expires (Default: 0)
+ * @param $prefix TODO DOCUMENT (Default: null)
+ * @param $domain TODO DOCUMENT (Default: null)
+ *
*/
public function setcookie( $name, $value, $expire = 0, $prefix = null, $domain = null ) {
$this->cookies[$name] = $value;
diff --git a/includes/WebStart.php b/includes/WebStart.php
index 6cfb4722..17f8216b 100644
--- a/includes/WebStart.php
+++ b/includes/WebStart.php
@@ -26,7 +26,7 @@
# Protect against register_globals
# This must be done before any globals are set by the code
if ( ini_get( 'register_globals' ) ) {
- if ( isset( $_REQUEST['GLOBALS'] ) ) {
+ if ( isset( $_REQUEST['GLOBALS'] ) || isset( $_FILES['GLOBALS'] ) ) {
die( '<a href="http://www.hardened-php.net/globals-problem">$GLOBALS overwrite vulnerability</a>');
}
$verboten = array(
diff --git a/includes/Wiki.php b/includes/Wiki.php
index f535b981..6ead57c4 100644
--- a/includes/Wiki.php
+++ b/includes/Wiki.php
@@ -33,18 +33,29 @@ class MediaWiki {
*/
private $context;
- public function request( WebRequest $x = null ){
+ /**
+ * @param $x null|WebRequest
+ * @return WebRequest
+ */
+ public function request( WebRequest $x = null ) {
$old = $this->context->getRequest();
$this->context->setRequest( $x );
return $old;
}
- public function output( OutputPage $x = null ){
+ /**
+ * @param $x null|OutputPage
+ * @return OutputPage
+ */
+ public function output( OutputPage $x = null ) {
$old = $this->context->getOutput();
$this->context->setOutput( $x );
return $old;
}
+ /**
+ * @param IContextSource|null $context
+ */
public function __construct( IContextSource $context = null ) {
if ( !$context ) {
$context = RequestContext::getMain();
@@ -65,6 +76,7 @@ class MediaWiki {
$request = $this->context->getRequest();
$curid = $request->getInt( 'curid' );
$title = $request->getVal( 'title' );
+ $action = $request->getVal( 'action', 'view' );
if ( $request->getCheck( 'search' ) ) {
// Compatibility with old search URLs which didn't use Special:Search
@@ -74,7 +86,7 @@ class MediaWiki {
} elseif ( $curid ) {
// URLs like this are generated by RC, because rc_title isn't always accurate
$ret = Title::newFromID( $curid );
- } elseif ( $title == '' && $this->getAction() != 'delete' ) {
+ } elseif ( $title == '' && $action != 'delete' ) {
$ret = Title::newMainPage();
} else {
$ret = Title::newFromURL( $title );
@@ -92,7 +104,7 @@ class MediaWiki {
}
}
// For non-special titles, check for implicit titles
- if ( is_null( $ret ) || $ret->getNamespace() != NS_SPECIAL ) {
+ if ( is_null( $ret ) || !$ret->isSpecialPage() ) {
// We can have urls with just ?diff=,?oldid= or even just ?diff=
$oldid = $request->getInt( 'oldid' );
$oldid = $oldid ? $oldid : $request->getInt( 'diff' );
@@ -114,7 +126,7 @@ class MediaWiki {
* Get the Title object that we'll be acting on, as specified in the WebRequest
* @return Title
*/
- public function getTitle(){
+ public function getTitle() {
if( $this->context->getTitle() === null ){
$this->context->setTitle( $this->parseTitle() );
}
@@ -146,34 +158,45 @@ class MediaWiki {
$output->setPrintable();
}
- $pageView = false; // was an article or special page viewed?
+ $unused = null; // To pass it by reference
+ wfRunHooks( 'BeforeInitialize', array( &$title, &$unused, &$output, &$user, $request, $this ) );
- 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->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() ) {
- // 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
+ wfProfileOut( __METHOD__ );
+ throw new BadTitleError();
+ }
+
+ // Check user's permissions to read this page.
+ // We have to check here to catch special pages etc.
+ // We will check again in Article::view().
+ $permErrors = $title->getUserPermissionsErrors( 'read', $user );
+ if ( count( $permErrors ) ) {
+ // Bug 32276: allowing the skin to generate output with $wgTitle or
+ // $this->context->title set to the input title would allow anonymous users to
+ // 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;
+ //
+ // The skin mostly uses $this->context->getTitle() these days, but some extensions
+ // still use $wgTitle.
+
+ $badTitle = SpecialPage::getTitleFor( 'Badtitle' );
+ $this->context->setTitle( $badTitle );
+ $wgTitle = $badTitle;
+
+ wfProfileOut( __METHOD__ );
+ throw new PermissionsError( 'read', $permErrors );
+ }
+
+ $pageView = false; // was an article or special page viewed?
- $output->loginToUse();
// Interwiki redirects
- } elseif ( $title->getInterwiki() != '' ) {
+ if ( $title->getInterwiki() != '' ) {
$rdfrom = $request->getVal( 'rdfrom' );
if ( $rdfrom ) {
$url = $title->getFullURL( 'rdfrom=' . urlencode( $rdfrom ) );
@@ -191,7 +214,7 @@ class MediaWiki {
} else {
$this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
wfProfileOut( __METHOD__ );
- throw new ErrorPageError( 'badtitle', 'badtitletext' );
+ throw new BadTitleError();
}
// Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
} elseif ( $request->getVal( 'action', 'view' ) == 'view' && !$request->wasPosted()
@@ -200,7 +223,7 @@ class MediaWiki {
&& !count( $request->getValueNames( array( 'action', 'title' ) ) )
&& wfRunHooks( 'TestCanonicalRedirect', array( $request, $title, $output ) ) )
{
- if ( $title->getNamespace() == NS_SPECIAL ) {
+ if ( $title->isSpecialPage() ) {
list( $name, $subpage ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
if ( $name ) {
$title = SpecialPage::getTitleFor( $name, $subpage );
@@ -228,7 +251,7 @@ class MediaWiki {
"\$wgArticlePath setting and/or toggle \$wgUsePathInfo " .
"to true.";
}
- wfHttpError( 500, "Internal error", $message );
+ throw new HttpError( 500, $message );
} else {
$output->setSquidMaxage( 1200 );
$output->redirect( $targetUrl, '301' );
@@ -245,8 +268,8 @@ class MediaWiki {
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
+ * $wgArticle is deprecated, do not use it.
+ * This will be removed entirely in 1.20.
* @deprecated since 1.18
*/
global $wgArticle;
@@ -278,39 +301,22 @@ class MediaWiki {
* @return Article object
*/
public static function articleFromTitle( $title, IContextSource $context ) {
+ wfDeprecated( __METHOD__, '1.18' );
return Article::newFromTitle( $title, $context );
}
/**
- * 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"
+ * Returns the name of the action that will be executed.
*
- * @return String: action
+ * @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';
+ static $action = null;
+
+ if ( $action === null ) {
+ $action = Action::getActionName( $this->context );
}
- // 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;
}
@@ -325,19 +331,21 @@ class MediaWiki {
wfProfileIn( __METHOD__ );
- $request = $this->context->getRequest();
$title = $this->context->getTitle();
-
- $action = $request->getVal( 'action', 'view' );
$article = Article::newFromTitle( $title, $this->context );
+ $this->context->setWikiPage( $article->getPage() );
// NS_MEDIAWIKI has no redirects.
// It is also used for CSS/JS, so performance matters here...
if ( $title->getNamespace() == NS_MEDIAWIKI ) {
wfProfileOut( __METHOD__ );
return $article;
}
+
+ $request = $this->context->getRequest();
+
// Namespace might change when using redirects
// Check for redirects ...
+ $action = $request->getVal( 'action', 'view' );
$file = ( $title->getNamespace() == NS_FILE ) ? $article->getFile() : null;
if ( ( $action == 'view' || $action == 'render' ) // ... for actions that show content
&& !$request->getVal( 'oldid' ) && // ... and are not old revisions
@@ -372,10 +380,12 @@ class MediaWiki {
$rarticle->setRedirectedFrom( $title );
$article = $rarticle;
$this->context->setTitle( $target );
+ $this->context->setWikiPage( $article->getPage() );
}
}
} else {
$this->context->setTitle( $article->getTitle() );
+ $this->context->setWikiPage( $article->getPage() );
}
}
@@ -395,7 +405,7 @@ class MediaWiki {
// Output everything!
$this->context->getOutput()->output();
// Do any deferred jobs
- wfDoUpdates( 'commit' );
+ DeferredUpdates::doUpdates( 'commit' );
$this->doJobs();
wfProfileOut( __METHOD__ );
}
@@ -421,9 +431,9 @@ class MediaWiki {
while ( $n-- && false != ( $job = Job::pop() ) ) {
$output = $job->toString() . "\n";
- $t = -wfTime();
+ $t = - microtime( true );
$success = $job->run();
- $t += wfTime();
+ $t += microtime( true );
$t = round( $t * 1000 );
if ( !$success ) {
$output .= "Error: " . $job->getLastError() . ", Time: $t ms\n";
@@ -450,11 +460,9 @@ class MediaWiki {
/**
* Perform one of the "standard" actions
*
- * @param $article Article
+ * @param $page Page
*/
- private function performAction( Page $article ) {
- global $wgSquidMaxage, $wgUseExternalEditor;
-
+ private function performAction( Page $page ) {
wfProfileIn( __METHOD__ );
$request = $this->context->getRequest();
@@ -463,7 +471,7 @@ class MediaWiki {
$user = $this->context->getUser();
if ( !wfRunHooks( 'MediaWikiPerformAction',
- array( $output, $article, $title, $user, $request, $this ) ) )
+ array( $output, $page, $title, $user, $request, $this ) ) )
{
wfProfileOut( __METHOD__ );
return;
@@ -471,69 +479,17 @@ class MediaWiki {
$act = $this->getAction();
- $action = Action::factory( $act, $article );
+ $action = Action::factory( $act, $page );
if ( $action instanceof Action ) {
$action->show();
wfProfileOut( __METHOD__ );
return;
}
- switch( $act ) {
- case 'view':
- $output->setSquidMaxage( $wgSquidMaxage );
- $article->view();
- break;
- case 'raw': // includes JS/CSS
- wfProfileIn( __METHOD__ . '-raw' );
- $raw = new RawPage( $article );
- $raw->view();
- wfProfileOut( __METHOD__ . '-raw' );
- break;
- case 'delete':
- case 'protect':
- case 'unprotect':
- case 'render':
- $article->$act();
- break;
- case 'submit':
- if ( session_id() == '' ) {
- // Send a cookie so anons get talk message notifications
- wfSetupSession();
- }
- // Continue...
- case 'edit':
- if ( wfRunHooks( 'CustomEditor', array( $article, $user ) ) ) {
- $internal = $request->getVal( 'internaledit' );
- $external = $request->getVal( 'externaledit' );
- $section = $request->getVal( 'section' );
- $oldid = $request->getVal( 'oldid' );
- if ( !$wgUseExternalEditor || $act == 'submit' || $internal ||
- $section || $oldid ||
- ( !$user->getOption( 'externaleditor' ) && !$external ) )
- {
- $editor = new EditPage( $article );
- $editor->submit();
- } elseif ( $wgUseExternalEditor
- && ( $external || $user->getOption( 'externaleditor' ) ) )
- {
- $mode = $request->getVal( 'mode' );
- $extedit = new ExternalEdit( $article->getTitle(), $mode );
- $extedit->edit();
- }
- }
- break;
- case 'history':
- if ( $request->getFullRequestURL() == $title->getInternalURL( 'action=history' ) ) {
- $output->setSquidMaxage( $wgSquidMaxage );
- }
- $history = new HistoryPage( $article );
- $history->history();
- break;
- default:
- if ( wfRunHooks( 'UnknownAction', array( $act, $article ) ) ) {
- $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
- }
+ if ( wfRunHooks( 'UnknownAction', array( $request->getVal( 'action', 'view' ), $page ) ) ) {
+ $output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
}
+
wfProfileOut( __METHOD__ );
}
@@ -543,7 +499,7 @@ class MediaWiki {
*/
public function run() {
try {
- $this->checkMaxLag( true );
+ $this->checkMaxLag();
$this->main();
$this->restInPeace();
} catch ( Exception $e ) {
@@ -554,38 +510,29 @@ class MediaWiki {
/**
* 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
+ * @return bool
*/
- private function checkMaxLag( $abort ) {
+ private function checkMaxLag() {
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();
+ list( $host, $lag ) = wfGetLB()->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";
- }
+ $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;
}
}
@@ -598,36 +545,42 @@ class MediaWiki {
wfProfileIn( __METHOD__ );
- # Set title from request parameters
- $wgTitle = $this->getTitle();
- $action = $this->getAction();
- $user = $this->context->getUser();
+ $request = $this->context->getRequest();
+
+ // Send Ajax requests to the Ajax dispatcher.
+ if ( $wgUseAjax && $request->getVal( 'action', 'view' ) == 'ajax' ) {
+
+ // Set a dummy title, because $wgTitle == null might break things
+ $title = Title::makeTitle( NS_MAIN, 'AJAX' );
+ $this->context->setTitle( $title );
+ $wgTitle = $title;
- # 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 ) {
+ // Get title from request parameters,
+ // is set on the fly by parseTitle the first time.
+ $title = $this->getTitle();
+ $action = $this->getAction();
+ $wgTitle = $title;
+
+ if ( $wgUseFileCache && $title->getNamespace() >= 0 ) {
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 ( HTMLFileCache::useFileCache( $this->context ) ) {
+ // Try low-level file cache hit
+ $cache = HTMLFileCache::newFromTitle( $title, $action );
+ if ( $cache->isCacheGood( /* Assume up to date */ ) ) {
+ // Check incoming headers to see if client has this cached
+ $timestamp = $cache->cacheTimestamp();
if ( !$this->context->getOutput()->checkLastModified( $timestamp ) ) {
- $cache->loadFromFileCache();
+ $cache->loadFromFileCache( $this->context );
}
- # Do any stats increment/watchlist stuff
- $article = WikiPage::factory( $wgTitle );
- $article->doViewUpdates( $user );
- # Tell OutputPage that output is taken care of
+ // Do any stats increment/watchlist stuff
+ $this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
+ // Tell OutputPage that output is taken care of
$this->context->getOutput()->disable();
wfProfileOut( 'main-try-filecache' );
wfProfileOut( __METHOD__ );
diff --git a/includes/WikiCategoryPage.php b/includes/WikiCategoryPage.php
index 42c2a6ce..01938cd9 100644
--- a/includes/WikiCategoryPage.php
+++ b/includes/WikiCategoryPage.php
@@ -3,21 +3,13 @@
* 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.
+ *
+ * @return bool
*/
public function hasViewableContent() {
if ( parent::hasViewableContent() ) {
diff --git a/includes/WikiError.php b/includes/WikiError.php
index a634dff6..7c167f61 100644
--- a/includes/WikiError.php
+++ b/includes/WikiError.php
@@ -35,7 +35,7 @@ class WikiError {
* @deprecated since 1.17
*/
function __construct( $message ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.17' );
$this->mMessage = $message;
}
@@ -65,7 +65,7 @@ class WikiError {
* @deprecated since 1.17
*/
public static function isError( $object ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.17' );
if ( $object instanceof WikiError ) {
return true;
} elseif ( $object instanceof Status ) {
@@ -88,7 +88,7 @@ class WikiErrorMsg extends WikiError {
* @deprecated since 1.17
*/
function __construct( $message/*, ... */ ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.17' );
$args = func_get_args();
array_shift( $args );
$this->mMessage = wfMsgReal( $message, $args, true );
@@ -120,7 +120,7 @@ class WikiXmlError extends WikiError {
* @deprecated since 1.17
*/
function __construct( $parser, $message = 'XML parsing error', $context = null, $offset = 0 ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.17' );
$this->mXmlError = xml_get_error_code( $parser );
$this->mColumn = xml_get_current_column_number( $parser );
$this->mLine = xml_get_current_line_number( $parser );
diff --git a/includes/WikiFilePage.php b/includes/WikiFilePage.php
index 6727a8cd..8aeaa243 100644
--- a/includes/WikiFilePage.php
+++ b/includes/WikiFilePage.php
@@ -5,12 +5,15 @@
* @ingroup Media
*/
class WikiFilePage extends WikiPage {
+ /**
+ * @var File
+ */
protected $mFile = false; // !< File object
protected $mRepo = null; // !<
protected $mFileLoaded = false; // !<
protected $mDupes = null; // !<
- function __construct( $title ) {
+ public function __construct( $title ) {
parent::__construct( $title );
$this->mDupes = null;
$this->mRepo = null;
@@ -22,13 +25,15 @@ class WikiFilePage extends WikiPage {
/**
* @param $file File:
- * @return void
*/
public function setFile( $file ) {
$this->mFile = $file;
$this->mFileLoaded = true;
}
+ /**
+ * @return bool
+ */
protected function loadFile() {
if ( $this->mFileLoaded ) {
return true;
@@ -43,8 +48,12 @@ class WikiFilePage extends WikiPage {
}
}
$this->mRepo = $this->mFile->getRepo();
+ return true;
}
+ /**
+ * @return mixed|null|Title
+ */
public function getRedirectTarget() {
$this->loadFile();
if ( $this->mFile->isLocal() ) {
@@ -59,6 +68,9 @@ class WikiFilePage extends WikiPage {
return $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
}
+ /**
+ * @return bool|mixed|Title
+ */
public function followRedirect() {
$this->loadFile();
if ( $this->mFile->isLocal() ) {
@@ -72,25 +84,38 @@ class WikiFilePage extends WikiPage {
return Title::makeTitle( NS_FILE, $to );
}
+ /**
+ * @param bool $text
+ * @return bool
+ */
public function isRedirect( $text = false ) {
$this->loadFile();
if ( $this->mFile->isLocal() ) {
return parent::isRedirect( $text );
}
-
+
return (bool)$this->mFile->getRedirected();
}
+ /**
+ * @return bool
+ */
public function isLocal() {
$this->loadFile();
return $this->mFile->isLocal();
}
+ /**
+ * @return bool|File
+ */
public function getFile() {
$this->loadFile();
return $this->mFile;
}
+ /**
+ * @return array|null
+ */
public function getDuplicates() {
$this->loadFile();
if ( !is_null( $this->mDupes ) ) {
@@ -104,6 +129,10 @@ class WikiFilePage extends WikiPage {
// Remove duplicates with self and non matching file sizes
$self = $this->mFile->getRepoName() . ':' . $this->mFile->getName();
$size = $this->mFile->getSize();
+
+ /**
+ * @var $file File
+ */
foreach ( $dupes as $index => $file ) {
$key = $file->getRepoName() . ':' . $file->getName();
if ( $key == $self ) {
@@ -127,13 +156,13 @@ class WikiFilePage extends WikiPage {
$update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
$update->doUpdate();
$this->mFile->upgradeRow();
- $this->mFile->purgeCache();
+ $this->mFile->purgeCache( array( 'forThumbRefresh' => true ) );
} 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();
+ $this->mFile->purgeCache( array( 'forThumbRefresh' => true ) );
}
- parent::doPurge();
+ return parent::doPurge();
}
}
diff --git a/includes/WikiMap.php b/includes/WikiMap.php
index 458718ee..6c7f23b5 100644
--- a/includes/WikiMap.php
+++ b/includes/WikiMap.php
@@ -17,17 +17,18 @@ class WikiMap {
$wgConf->loadFullData();
list( $major, $minor ) = $wgConf->siteFromDB( $wikiID );
- if( isset( $major ) ) {
- $server = $wgConf->get( 'wgCanonicalServer', $wikiID, $major,
- array( 'lang' => $minor, 'site' => $major ) );
- $path = $wgConf->get( 'wgArticlePath', $wikiID, $major,
- array( 'lang' => $minor, 'site' => $major ) );
- return new WikiReference( $major, $minor, $server, $path );
- } else {
+ if( $major === null ) {
return null;
}
+ $canonicalServer = $wgConf->get( 'wgCanonicalServer', $wikiID, $major,
+ array( 'lang' => $minor, 'site' => $major ) );
+ $server = $wgConf->get( 'wgServer', $wikiID, $major,
+ array( 'lang' => $minor, 'site' => $major ) );
+ $path = $wgConf->get( 'wgArticlePath', $wikiID, $major,
+ array( 'lang' => $minor, 'site' => $major ) );
+ return new WikiReference( $major, $minor, $canonicalServer, $path, $server );
}
-
+
/**
* Convenience to get the wiki's display name
*
@@ -86,11 +87,11 @@ class WikiMap {
*/
public static function getForeignURL( $wikiID, $page ) {
$wiki = WikiMap::getWiki( $wikiID );
-
+
if ( $wiki ) {
return $wiki->getUrl( $page );
}
-
+
return false;
}
}
@@ -101,21 +102,27 @@ class WikiMap {
class WikiReference {
private $mMinor; ///< 'en', 'meta', 'mediawiki', etc
private $mMajor; ///< 'wiki', 'wiktionary', etc
- private $mServer; ///< server override, 'www.mediawiki.org'
- private $mPath; ///< path override, '/wiki/$1'
+ private $mCanonicalServer; ///< canonical server URL, e.g. 'http://www.mediawiki.org'
+ private $mServer; ///< server URL, may be protocol-relative, e.g. '//www.mediawiki.org'
+ private $mPath; ///< path, '/wiki/$1'
- public function __construct( $major, $minor, $server, $path ) {
+ public function __construct( $major, $minor, $canonicalServer, $path, $server = null ) {
$this->mMajor = $major;
$this->mMinor = $minor;
- $this->mServer = $server;
+ $this->mCanonicalServer = $canonicalServer;
$this->mPath = $path;
+ $this->mServer = $server === null ? $canonicalServer : $server;
}
+ /**
+ * @return string
+ * @throws MWException
+ */
public function getHostname() {
$prefixes = array( 'http://', 'https://' );
foreach ( $prefixes as $prefix ) {
- if ( substr( $this->mServer, 0, strlen( $prefix ) ) ) {
- return substr( $this->mServer, strlen( $prefix ) );
+ if ( substr( $this->mCanonicalServer, 0, strlen( $prefix ) ) ) {
+ return substr( $this->mCanonicalServer, strlen( $prefix ) );
}
}
throw new MWException( "Invalid hostname for wiki {$this->mMinor}.{$this->mMajor}" );
@@ -150,12 +157,31 @@ class WikiReference {
}
/**
- * Get a URL to a page on this foreign wiki
+ * Get a canonical (i.e. based on $wgCanonicalServer) URL to a page on this foreign wiki
*
* @param $page String: page name (must be normalised before calling this function!)
* @return String: Url
*/
+ public function getCanonicalUrl( $page ) {
+ return $this->mCanonicalServer . $this->getLocalUrl( $page );
+ }
+
+ /**
+ * Alias for getCanonicalUrl(), for backwards compatibility.
+ * @return String
+ */
public function getUrl( $page ) {
+ return $this->getCanonicalUrl( $page );
+ }
+
+ /**
+ * Get a URL based on $wgServer, like Title::getFullUrl() would produce
+ * when called locally on the wiki.
+ *
+ * @param $page String: page name (must be normalized before calling this function!)
+ * @return String: URL
+ */
+ public function getFullUrl( $page ) {
return
$this->mServer .
$this->getLocalUrl( $page );
diff --git a/includes/WikiPage.php b/includes/WikiPage.php
index ae6e4408..acc9831a 100644
--- a/includes/WikiPage.php
+++ b/includes/WikiPage.php
@@ -13,31 +13,68 @@ abstract class Page {}
* @internal documentation reviewed 15 Mar 2010
*/
class WikiPage extends Page {
+ // doDeleteArticleReal() return values. Values less than zero indicate fatal errors,
+ // values greater than zero indicate that there were problems not resulting in page
+ // not being deleted
+
+ /**
+ * Delete operation aborted by hook
+ */
+ const DELETE_HOOK_ABORTED = -1;
+
+ /**
+ * Deletion successful
+ */
+ const DELETE_SUCCESS = 0;
+
+ /**
+ * Page not found
+ */
+ const DELETE_NO_PAGE = 1;
+
+ /**
+ * No revisions found to delete
+ */
+ const DELETE_NO_REVISIONS = 2;
+
/**
* @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
+ public $mLatest = false; // !< Integer (false means "not loaded")
+ public $mPreparedEdit = false; // !< Array
/**@}}*/
/**
- * @protected
- * @var ParserOptions: ParserOptions object for $wgUser articles
+ * @var Title
+ */
+ protected $mRedirectTarget = null;
+
+ /**
+ * @var Revision
+ */
+ protected $mLastRevision = null;
+
+ /**
+ * @var string; timestamp of the current revision or empty string if not loaded
+ */
+ protected $mTimestamp = '';
+
+ /**
+ * @var string
+ */
+ protected $mTouched = '19700101000000';
+
+ /**
+ * @var int|null
*/
- public $mParserOptions;
+ protected $mCounter = null;
/**
* Constructor and clear the article
@@ -79,17 +116,16 @@ class WikiPage extends 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
+ if ( $t ) {
+ return self::factory( $t );
+ }
+ return null;
}
/**
@@ -98,6 +134,8 @@ class WikiPage extends Page {
* (and only when) $wgActions[$action] === true. This allows subclasses
* to override the default behavior.
*
+ * @todo: move this UI stuff somewhere else
+ *
* @return Array
*/
public function getActionOverrides() {
@@ -105,125 +143,6 @@ class WikiPage extends Page {
}
/**
- * 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
*/
@@ -237,47 +156,17 @@ class WikiPage extends Page {
public function clear() {
$this->mDataLoaded = false;
- $this->mCounter = -1; # Not loaded
+ $this->mCounter = null;
$this->mRedirectTarget = null; # Title object if set
$this->mLastRevision = null; # Latest revision
- $this->mTimestamp = '';
$this->mTouched = '19700101000000';
+ $this->mTimestamp = '';
$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.
*
@@ -370,8 +259,7 @@ class WikiPage extends Page {
$lc = LinkCache::singleton();
if ( $data ) {
- $lc->addGoodLinkObj( $data->page_id, $this->mTitle,
- $data->page_len, $data->page_is_redirect, $data->page_latest );
+ $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
$this->mTitle->loadFromRow( $data );
@@ -402,7 +290,7 @@ class WikiPage extends Page {
* @return bool Whether or not the page exists in the database
*/
public function exists() {
- return $this->getId() > 0;
+ return $this->mTitle->exists();
}
/**
@@ -414,75 +302,21 @@ class WikiPage extends Page {
* @return bool
*/
public function hasViewableContent() {
- return $this->exists() || $this->mTitle->isAlwaysKnown();
+ return $this->mTitle->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__
- );
- }
+ if ( !$this->mDataLoaded ) {
+ $this->loadPageData();
}
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
@@ -501,6 +335,39 @@ class WikiPage extends Page {
}
/**
+ * 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;
+ }
+
+ /**
* Loads everything except the text
* This isn't necessary for all uses, so it's only done if needed.
*/
@@ -653,6 +520,194 @@ class WikiPage extends Page {
}
/**
+ * 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 );
+ }
+
+ /**
+ * 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__ );
+ }
+ }
+ }
+
+ /**
+ * 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 ) {
+ return false;
+ }
+
+ if ( $rt->isExternal() ) {
+ 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 {
+ // External pages pages without "local" bit set are not valid
+ // redirect targets
+ return false;
+ }
+ }
+
+ if ( $rt->isSpecialPage() ) {
+ // Gotta handle redirects to special pages differently:
+ // Fill the HTTP response "Location" header and ignore
+ // the rest of the page we're on.
+ //
+ // Some pages are not valid targets
+ if ( $rt->isValidRedirectTarget() ) {
+ return $rt->getFullURL();
+ } else {
+ return false;
+ }
+ }
+
+ return $rt;
+ }
+
+ /**
* 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
@@ -704,20 +759,131 @@ class WikiPage extends Page {
}
/**
+ * 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;
+ }
+
+ /**
* Should the parser cache be used?
*
- * @param $user User The relevant user
+ * @param $parserOptions ParserOptions to check
+ * @param $oldid int
* @return boolean
*/
- public function isParserCacheUsed( User $user, $oldid ) {
+ public function isParserCacheUsed( ParserOptions $parserOptions, $oldid ) {
global $wgEnableParserCache;
return $wgEnableParserCache
- && $user->getStubThreshold() == 0
- && $this->exists()
- && empty( $oldid )
- && !$this->mTitle->isCssOrJsPage()
- && !$this->mTitle->isCssJsSubpage();
+ && $parserOptions->getStubThreshold() == 0
+ && $this->mTitle->exists()
+ && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
+ && $this->mTitle->isWikitextPage();
+ }
+
+ /**
+ * Get a ParserOutput for the given ParserOptions and revision ID.
+ * The parser cache will be used if possible.
+ *
+ * @since 1.19
+ * @param $parserOptions ParserOptions to use for the parse operation
+ * @param $oldid Revision ID to get the text from, passing null or 0 will
+ * get the current revision (default value)
+ * @return ParserOutput or false if the revision was not found
+ */
+ public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
+ wfProfileIn( __METHOD__ );
+
+ $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
+ wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
+ if ( $parserOptions->getStubThreshold() ) {
+ wfIncrStats( 'pcache_miss_stub' );
+ }
+
+ if ( $useParserCache ) {
+ $parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
+ if ( $parserOutput !== false ) {
+ wfProfileOut( __METHOD__ );
+ return $parserOutput;
+ }
+ }
+
+ if ( $oldid === null || $oldid === 0 ) {
+ $oldid = $this->getLatest();
+ }
+
+ $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
+ $pool->execute();
+
+ wfProfileOut( __METHOD__ );
+
+ return $pool->getParserOutput();
+ }
+
+ /**
+ * Do standard deferred updates after page view
+ * @param $user User The relevant user
+ */
+ public function doViewUpdates( User $user ) {
+ global $wgDisableCounters;
+ if ( wfReadOnly() ) {
+ return;
+ }
+
+ # Don't update page view counters on views from bot users (bug 14044)
+ if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->mTitle->exists() ) {
+ DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
+ }
+
+ # Update newtalk / watchlist notification status
+ $user->clearNotification( $this->mTitle );
}
/**
@@ -745,14 +911,15 @@ class WikiPage extends Page {
}
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- if ( $this->getId() == 0 ) {
- $text = false;
- } else {
+ if ( $this->mTitle->exists() ) {
$text = $this->getRawText();
+ } else {
+ $text = false;
}
MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
}
+ return true;
}
/**
@@ -764,7 +931,6 @@ class WikiPage extends Page {
*
* @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__ );
@@ -800,7 +966,7 @@ class WikiPage extends Page {
*
* @param $dbw DatabaseBase: object
* @param $revision Revision: For ID number, and text used to set
- length and redirect status fields
+ * 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
@@ -814,6 +980,7 @@ class WikiPage extends Page {
wfProfileIn( __METHOD__ );
$text = $revision->getText();
+ $len = strlen( $text );
$rt = Title::newFromRedirectRecurse( $text );
$conditions = array( 'page_id' => $this->getId() );
@@ -830,7 +997,7 @@ class WikiPage extends Page {
'page_touched' => $dbw->timestamp( $now ),
'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
'page_is_redirect' => $rt !== null ? 1 : 0,
- 'page_len' => strlen( $text ),
+ 'page_len' => $len,
),
$conditions,
__METHOD__ );
@@ -838,7 +1005,12 @@ class WikiPage extends Page {
$result = $dbw->affectedRows() != 0;
if ( $result ) {
$this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
+ $this->setLastEdit( $revision );
$this->setCachedLastEditTime( $now );
+ $this->mLatest = $revision->getId();
+ $this->mIsRedirect = (bool)$rt;
+ # Update the LinkCache.
+ LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect, $this->mLatest );
}
wfProfileOut( __METHOD__ );
@@ -846,29 +1018,6 @@ class WikiPage extends Page {
}
/**
- * 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
@@ -885,7 +1034,7 @@ class WikiPage extends Page {
// Delete if changing from redirect to non-redirect
$isRedirect = !is_null( $redirectTitle );
- if ( !$isRedirect && !is_null( $lastRevIsRedirect ) && $lastRevIsRedirect === $isRedirect ) {
+ if ( !$isRedirect && $lastRevIsRedirect === false ) {
return true;
}
@@ -945,40 +1094,78 @@ class WikiPage extends Page {
}
/**
+ * 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;
+ }
+
+ /**
* @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 $sectionTitle 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 ) {
+ public function replaceSection( $section, $text, $sectionTitle = '', $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 );
+ // Bug 30711: always use current version when adding a new section
+ if ( is_null( $edittime ) || $section == 'new' ) {
+ $oldtext = $this->getRawText();
+ if ( $oldtext === false ) {
+ wfDebug( __METHOD__ . ": no page text\n" );
+ wfProfileOut( __METHOD__ );
+ return null;
+ }
} 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;
- }
+ if ( !$rev ) {
+ wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
+ $this->getId() . "; section: $section; edittime: $edittime)\n" );
+ wfProfileOut( __METHOD__ );
+ return null;
+ }
- $oldtext = $rev->getText();
+ $oldtext = $rev->getText();
+ }
if ( $section == 'new' ) {
# Inserting a new section
- $subject = $summary ? wfMsgForContent( 'newsectionheaderdefaultlevel', $summary ) . "\n\n" : '';
- $text = strlen( trim( $oldtext ) ) > 0
+ $subject = $sectionTitle ? wfMsgForContent( 'newsectionheaderdefaultlevel', $sectionTitle ) . "\n\n" : '';
+ if ( wfRunHooks( 'PlaceNewSection', array( $this, $oldtext, $subject, &$text ) ) ) {
+ $text = strlen( trim( $oldtext ) ) > 0
? "{$oldtext}\n\n{$subject}{$text}"
: "{$subject}{$text}";
+ }
} else {
# Replacing an existing section; roll out the big guns
global $wgParser;
@@ -1031,7 +1218,7 @@ class WikiPage extends Page {
* 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
+ * If EDIT_UPDATE is specified and the article doesn't exist, the function will return an
* edit-gone-missing error. If EDIT_NEW is specified and the article does exist, an
* edit-already-exists error will be returned. These two conditions are also possible with
* auto-detection due to MediaWiki's performance-optimised locking strategy.
@@ -1091,6 +1278,8 @@ class WikiPage extends Page {
$oldtext = $this->getRawText(); // current revision
$oldsize = strlen( $oldtext );
+ $oldid = $this->getLatest();
+ $oldIsRedirect = $this->isRedirect();
$oldcountable = $this->isCountable();
# Provide autosummaries if one is not provided and autosummaries are enabled.
@@ -1110,6 +1299,15 @@ class WikiPage extends Page {
# Update article, but only if changed.
$status->value['new'] = false;
+ if ( !$oldid ) {
+ # Article gone missing
+ wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
+ $status->fatal( 'edit-gone-missing' );
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
# Make sure the revision is either completely inserted or not inserted at all
if ( !$wgDBtransactions ) {
$userAbort = ignore_user_abort( true );
@@ -1120,7 +1318,7 @@ class WikiPage extends Page {
'comment' => $summary,
'minor_edit' => $isminor,
'text' => $text,
- 'parent_id' => $this->mLatest,
+ 'parent_id' => $oldid,
'user' => $user->getId(),
'user_text' => $user->getName(),
'timestamp' => $now
@@ -1129,15 +1327,6 @@ class WikiPage extends Page {
$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 );
@@ -1148,7 +1337,7 @@ class WikiPage extends Page {
# 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 );
+ $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
if ( !$ok ) {
/* Belated edit conflict! Run away!! */
@@ -1171,7 +1360,7 @@ class WikiPage extends Page {
$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,
+ $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
$revisionId, $patrolled
);
@@ -1183,6 +1372,10 @@ class WikiPage extends Page {
$user->incEditCount();
$dbw->commit();
}
+ } else {
+ // Bug 32948: revision ID must be set to page {{REVISIONID}} and
+ // related variables correctly
+ $revision->setId( $this->getLatest() );
}
if ( !$wgDBtransactions ) {
@@ -1202,8 +1395,6 @@ class WikiPage extends Page {
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();
@@ -1238,11 +1429,6 @@ class WikiPage extends Page {
) );
$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 );
@@ -1276,7 +1462,7 @@ class WikiPage extends Page {
# Do updates right now unless deferral was requested
if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
- wfDoUpdates();
+ DeferredUpdates::doUpdates();
}
// Return the new revision (or null) to the caller
@@ -1293,7 +1479,210 @@ class WikiPage extends Page {
}
/**
+ * Get parser options suitable for rendering the primary article wikitext
+ * @param User|string $user User object or 'canonical'
+ * @return ParserOptions
+ */
+ public function makeParserOptions( $user ) {
+ global $wgContLang;
+ if ( $user instanceof User ) { // settings per user (even anons)
+ $options = ParserOptions::newFromUser( $user );
+ } else { // canonical settings
+ $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
+ }
+ $options->enableLimitReport(); // show inclusion/loop reports
+ $options->setTidy( true ); // fix bad HTML
+ return $options;
+ }
+
+ /**
+ * 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, $wgContLang, $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::newFromUserAndLang( $user, $wgContLang );
+ wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
+
+ $edit = (object)array();
+ $edit->revid = $revid;
+ $edit->newText = $text;
+ $edit->pst = $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+ $edit->popts = $this->makeParserOptions( 'canonical' );
+ $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 $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__
+ );
+ }
+ }
+
+ if ( !$this->mTitle->exists() ) {
+ wfProfileOut( __METHOD__ );
+ return;
+ }
+
+ $id = $this->getId();
+ $title = $this->mTitle->getPrefixedDBkey();
+ $shortTitle = $this->mTitle->getDBkey();
+
+ 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;
+ }
+
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
+ DeferredUpdates::addUpdate( 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__ );
+ }
+
+ /**
+ * 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__ );
+ }
+
+ /**
* Update the article's restriction field, and leave a log entry.
+ * This works for protection both existing and non-existing pages.
*
* @param $limit Array: set of restriction keys
* @param $reason String
@@ -1302,30 +1691,16 @@ class WikiPage extends Page {
* @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;
- }
+ public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
+ global $wgContLang;
if ( wfReadOnly() ) {
- wfDebug( "updateRestrictions failed: read-only\n" );
- return false;
+ return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
}
- if ( count( $this->mTitle->getUserPermissionsErrors( 'protect', $user ) ) ) {
- wfDebug( "updateRestrictions failed: insufficient permissions\n" );
- return false;
- }
+ $restrictionTypes = $this->mTitle->getRestrictionTypes();
+
+ $id = $this->mTitle->getArticleID();
if ( !$cascade ) {
$cascade = false;
@@ -1336,151 +1711,182 @@ class WikiPage extends Page {
# @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 );
+ $isProtected = false;
+ $protect = false;
$changed = false;
+ $dbw = wfGetDB( DB_MASTER );
+
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] )
- {
+ if ( !isset( $expiry[$action] ) ) {
+ $expiry[$action] = $dbw->getInfinity();
+ }
+ if ( !isset( $limit[$action] ) ) {
+ $limit[$action] = '';
+ } elseif ( $limit[$action] != '' ) {
+ $protect = true;
+ }
+
+ # Get current restrictions on $action
+ $current = implode( '', $this->mTitle->getRestrictions( $action ) );
+ if ( $current != '' ) {
+ $isProtected = true;
+ }
+
+ if ( $limit[$action] != $current ) {
+ $changed = true;
+ } elseif ( $limit[$action] != '' ) {
+ # Only check expiry change if the action is actually being
+ # protected, since expiry does nothing on an not-protected
+ # action.
+ if ( $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 ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) {
+ $changed = true;
+ }
# If nothing's changed, do nothing
- if ( $changed ) {
- if ( wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
- $dbw = wfGetDB( DB_MASTER );
+ if ( !$changed ) {
+ return Status::newGood();
+ }
- # Prepare a null revision to be added to the history
- $modified = $current != '' && $protect;
+ if ( !$protect ) { # No protection at all means unprotection
+ $revCommentMsg = 'unprotectedarticle';
+ $logAction = 'unprotect';
+ } elseif ( $isProtected ) {
+ $revCommentMsg = 'modifiedarticleprotection';
+ $logAction = 'modify';
+ } else {
+ $revCommentMsg = 'protectedarticle';
+ $logAction = 'protect';
+ }
- if ( $protect ) {
- $comment_type = $modified ? 'modifiedarticleprotection' : 'protectedarticle';
+ $encodedExpiry = array();
+ $protectDescription = '';
+ foreach ( $limit as $action => $restrictions ) {
+ $encodedExpiry[$action] = $dbw->encodeExpiry( $expiry[$action] );
+ if ( $restrictions != '' ) {
+ $protectDescription .= $wgContLang->getDirMark() . "[$action=$restrictions] (";
+ if ( $encodedExpiry[$action] != 'infinity' ) {
+ $protectDescription .= wfMsgForContent( 'protect-expiring',
+ $wgContLang->timeanddate( $expiry[$action], false, false ) ,
+ $wgContLang->date( $expiry[$action], false, false ) ,
+ $wgContLang->time( $expiry[$action], false, false ) );
} else {
- $comment_type = 'unprotectedarticle';
+ $protectDescription .= wfMsgForContent( 'protect-expiry-indefinite' );
}
- $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 = '';
+ $protectDescription .= ') ';
+ }
+ }
+ $protectDescription = trim( $protectDescription );
- if ( $cascade ) {
- $cascade_description = ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']';
- }
+ if ( $id ) { # Protection of existing page
+ if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
+ return Status::newGood();
+ }
- if ( $reason ) {
- $comment .= ": $reason";
- }
+ # 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' );
- $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' );
- }
+ # The schema allows multiple restrictions
+ if ( !in_array( 'protect', $editrestriction ) && !in_array( 'sysop', $editrestriction ) ) {
+ $cascade = false;
+ }
- $protect_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__ );
}
- $protect_description = trim( $protect_description );
+ }
- if ( $protect_description && $protect ) {
- $editComment .= " ($protect_description)";
- }
+ # Prepare a null revision to be added to the history
+ $editComment = $wgContLang->ucfirst( wfMsgForContent( $revCommentMsg, $this->mTitle->getPrefixedText() ) );
+ if ( $reason ) {
+ $editComment .= ": $reason";
+ }
+ if ( $protectDescription ) {
+ $editComment .= " ($protectDescription)";
+ }
+ if ( $cascade ) {
+ $editComment .= ' [' . wfMsgForContent( 'protect-summary-cascade' ) . ']';
+ }
- if ( $cascade ) {
- $editComment .= "$cascade_description";
- }
+ # 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__
+ );
- # 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__ );
- }
- }
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
+ wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
+ } else { # Protection of non-existing page (also known as "title protection")
+ # Cascade protection is meaningless in this case
+ $cascade = false;
- # 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
+ if ( $limit['create'] != '' ) {
+ $dbw->replace( 'protected_titles',
+ array( array( 'pt_namespace', 'pt_title' ) ),
+ array(
+ 'pt_namespace' => $this->mTitle->getNamespace(),
+ 'pt_title' => $this->mTitle->getDBkey(),
+ 'pt_create_perm' => $limit['create'],
+ 'pt_timestamp' => $dbw->encodeExpiry( wfTimestampNow() ),
+ 'pt_expiry' => $encodedExpiry['create'],
+ 'pt_user' => $user->getId(),
+ 'pt_reason' => $reason,
+ ), __METHOD__
+ );
+ } else {
+ $dbw->delete( 'protected_titles',
+ array(
+ 'pt_namespace' => $this->mTitle->getNamespace(),
+ 'pt_title' => $this->mTitle->getDBkey()
), __METHOD__
);
+ }
+ }
- wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
- wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
+ $this->mTitle->flushRestrictions();
- # 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
+ if ( $logAction == 'unprotect' ) {
+ $logParams = array();
+ } else {
+ $logParams = array( $protectDescription, $cascade ? 'cascade' : '' );
+ }
- return true;
+ # Update the protection log
+ $log = new LogPage( 'protect' );
+ $log->addEntry( $logAction, $this->mTitle, trim( $reason ), $logParams, $user );
+
+ return Status::newGood();
}
/**
@@ -1507,83 +1913,26 @@ class WikiPage extends Page {
}
/**
- * @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
+ * Same as doDeleteArticleReal(), but returns more detailed success/failure status
+ * 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 &$error Array of errors to append to
+ * @param $user User The deleting user
+ * @return boolean true if successful
*/
- 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;
+ public function doDeleteArticle(
+ $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
+ ) {
+ return $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user )
+ == WikiPage::DELETE_SUCCESS;
}
/**
@@ -1591,39 +1940,36 @@ class WikiPage extends Page {
* Deletes the article with database consistency, writes logs, purges caches
*
* @param $reason string delete reason for deletion log
- * @param suppress bitfield
+ * @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
+ * @param &$error Array of errors to append to
+ * @param $user User The deleting user
+ * @return int: One of WikiPage::DELETE_* constants
*/
- public function doDeleteArticle(
+ public function doDeleteArticleReal(
$reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
) {
- global $wgDeferredUpdateList, $wgUseTrackbacks, $wgUser;
+ global $wgUser;
$user = is_null( $user ) ? $wgUser : $user;
wfDebug( __METHOD__ . "\n" );
if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error ) ) ) {
- return false;
+ return WikiPage::DELETE_HOOK_ABORTED;
}
$dbw = wfGetDB( DB_MASTER );
$t = $this->mTitle->getDBkey();
$id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
if ( $t === '' || $id == 0 ) {
- return false;
+ return WikiPage::DELETE_NO_PAGE;
}
- $u = new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 );
- array_push( $wgDeferredUpdateList, $u );
-
// Bitfields to further suppress the content
if ( $suppress ) {
$bitfield = 0;
@@ -1657,30 +2003,62 @@ class WikiPage extends Page {
'ar_timestamp' => 'rev_timestamp',
'ar_minor_edit' => 'rev_minor_edit',
'ar_rev_id' => 'rev_id',
+ 'ar_parent_id' => 'rev_parent_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
+ 'ar_deleted' => $bitfield,
+ 'ar_sha1' => 'rev_sha1'
), 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;
+ return WikiPage::DELETE_NO_REVISIONS;
}
+ $this->doDeleteUpdates( $id );
+
+ # Log the deletion, if the page was suppressed, log it at Oversight instead
+ $logtype = $suppress ? 'suppress' : 'delete';
+
+ $logEntry = new ManualLogEntry( $logtype, 'delete' );
+ $logEntry->setPerformer( $user );
+ $logEntry->setTarget( $this->mTitle );
+ $logEntry->setComment( $reason );
+ $logid = $logEntry->insert();
+ $logEntry->publish( $logid );
+
+ if ( $commit ) {
+ $dbw->commit();
+ }
+
+ wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id ) );
+ return WikiPage::DELETE_SUCCESS;
+ }
+
+ /**
+ * Do some database updates after deletion
+ *
+ * @param $id Int: page_id value of the page being deleted
+ */
+ public function doDeleteUpdates( $id ) {
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ # Delete restrictions for it
+ $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
+
# Fix category table counts
$cats = array();
$res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
@@ -1695,18 +2073,16 @@ class WikiPage extends Page {
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 ) );
+ $dbw->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ );
+ $dbw->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ );
+ $dbw->delete( 'categorylinks', array( 'cl_from' => $id ), __METHOD__ );
+ $dbw->delete( 'templatelinks', array( 'tl_from' => $id ), __METHOD__ );
+ $dbw->delete( 'externallinks', array( 'el_from' => $id ), __METHOD__ );
+ $dbw->delete( 'langlinks', array( 'll_from' => $id ), __METHOD__ );
+ $dbw->delete( 'iwlinks', array( 'iwl_from' => $id ), __METHOD__ );
+ $dbw->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ );
+ $dbw->delete( 'page_props', array( 'pp_page' => $id ), __METHOD__ );
}
# If using cleanup triggers, we can skip some manual deletes
@@ -1727,20 +2103,6 @@ class WikiPage extends Page {
# 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;
}
/**
@@ -1749,6 +2111,8 @@ class WikiPage extends Page {
* 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
+ *
+ * @todo: seperate the business/permission stuff out from backend code
*
* @param $fromP String: Name of the user whose edits to rollback.
* @param $summary String: Custom summary. Set to default summary if empty.
@@ -1796,7 +2160,7 @@ class WikiPage extends Page {
* 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-
+ * 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.
@@ -1816,7 +2180,7 @@ class WikiPage extends Page {
}
# Get the last editor
- $current = Revision::newFromTitle( $this->mTitle );
+ $current = $this->getRevision();
if ( is_null( $current ) ) {
# Something wrong... no page?
return array( array( 'notanarticle' ) );
@@ -1905,7 +2269,7 @@ class WikiPage extends Page {
}
# Actually store the edit
- $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId() );
+ $status = $this->doEdit( $target->getText(), $summary, $flags, $target->getId(), $guser );
if ( !empty( $status->value['revision'] ) ) {
$revId = $status->value['revision']->getId();
} else {
@@ -1925,281 +2289,6 @@ class WikiPage extends Page {
}
/**
- * 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.
@@ -2262,7 +2351,9 @@ class WikiPage extends Page {
# User talk pages
if ( $title->getNamespace() == NS_USER_TALK ) {
$user = User::newFromName( $title->getText(), false );
- $user->setNewtalk( false );
+ if ( $user ) {
+ $user->setNewtalk( false );
+ }
}
# Image redirects
@@ -2276,13 +2367,12 @@ class WikiPage extends Page {
* @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' );
+ DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
+
// Invalidate the caches of all pages which redirect here
- $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
+ DeferredUpdates::addHTMLCacheUpdate( $title, 'redirect' );
# Purge squid for this page only
$title->purgeSquid();
@@ -2294,35 +2384,6 @@ class WikiPage extends Page {
/**#@-*/
/**
- * 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.
*
@@ -2417,9 +2478,8 @@ class WikiPage extends Page {
public function getAutoDeleteReason( &$hasHistory ) {
global $wgContLang;
- $dbw = wfGetDB( DB_MASTER );
// Get the last revision
- $rev = Revision::newFromTitle( $this->getTitle() );
+ $rev = $this->getRevision();
if ( is_null( $rev ) ) {
return false;
@@ -2440,6 +2500,8 @@ class WikiPage extends Page {
}
}
+ $dbw = wfGetDB( DB_MASTER );
+
// Find out if there was only one contributor
// Only scan the last 20 revisions
$res = $dbw->select( 'revision', 'rev_user_text',
@@ -2502,42 +2564,6 @@ class WikiPage extends Page {
}
/**
- * 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.
*
@@ -2604,7 +2630,7 @@ class WikiPage extends Page {
* Updates cascading protections
*
* @param $parserOutput ParserOutput object for the current version
- **/
+ */
public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
return;
@@ -2651,27 +2677,305 @@ class WikiPage extends Page {
}
}
- /*
- * @deprecated since 1.18
- */
+ /**
+ * Return a list of templates used by this article.
+ * Uses the templatelinks table
+ *
+ * @deprecated in 1.19; use Title::getTemplateLinksFrom()
+ * @return Array of Title objects
+ */
+ public function getUsedTemplates() {
+ return $this->mTitle->getTemplateLinksFrom();
+ }
+
+ /**
+ * Perform article updates on a special page creation.
+ *
+ * @param $rev Revision object
+ *
+ * @todo This is a shitty interface function. Kill it and replace the
+ * other shitty functions like doEditUpdates and such so it's not needed
+ * anymore.
+ * @deprecated since 1.18, use doEditUpdates()
+ */
+ public function createUpdates( $rev ) {
+ wfDeprecated( __METHOD__, '1.18' );
+ global $wgUser;
+ $this->doEditUpdates( $rev, $wgUser, array( 'created' => true ) );
+ }
+
+ /**
+ * This function is called right before saving the wikitext,
+ * so we can do things like signatures and links-in-context.
+ *
+ * @deprecated in 1.19; use Parser::preSaveTransform() instead
+ * @param $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;
+
+ wfDeprecated( __METHOD__, '1.19' );
+
+ $user = is_null( $user ) ? $wgUser : $user;
+
+ if ( $popts === null ) {
+ $popts = ParserOptions::newFromUser( $user );
+ }
+
+ return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
+ }
+
+ /**
+ * Check whether the number of revisions of this page surpasses $wgDeleteRevisionsLimit
+ *
+ * @deprecated in 1.19; use Title::isBigDeletion() instead.
+ * @return bool
+ */
+ public function isBigDeletion() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->mTitle->isBigDeletion();
+ }
+
+ /**
+ * Get the approximate revision count of this page.
+ *
+ * @deprecated in 1.19; use Title::estimateRevisionCount() instead.
+ * @return int
+ */
+ public function estimateRevisionCount() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->mTitle->estimateRevisionCount();
+ }
+
+ /**
+ * Update the article's restriction field, and leave a log entry.
+ *
+ * @deprecated since 1.19
+ * @param $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;
+
+ $user = is_null( $user ) ? $wgUser : $user;
+
+ return $this->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user )->isOK();
+ }
+
+ /**
+ * @deprecated since 1.18
+ */
public function quickEdit( $text, $comment = '', $minor = 0 ) {
+ wfDeprecated( __METHOD__, '1.18' );
global $wgUser;
return $this->doQuickEdit( $text, $wgUser, $comment, $minor );
}
- /*
- * @deprecated since 1.18
- */
+ /**
+ * @deprecated since 1.18
+ */
public function viewUpdates() {
+ wfDeprecated( __METHOD__, '1.18' );
global $wgUser;
return $this->doViewUpdates( $wgUser );
}
- /*
- * @deprecated since 1.18
- */
+ /**
+ * @deprecated since 1.18
+ */
public function useParserCache( $oldid ) {
+ wfDeprecated( __METHOD__, '1.18' );
global $wgUser;
- return $this->isParserCacheUsed( $wgUser, $oldid );
+ return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
+ }
+}
+
+class PoolWorkArticleView extends PoolCounterWork {
+
+ /**
+ * @var Page
+ */
+ private $page;
+
+ /**
+ * @var string
+ */
+ private $cacheKey;
+
+ /**
+ * @var integer
+ */
+ private $revid;
+
+ /**
+ * @var ParserOptions
+ */
+ private $parserOptions;
+
+ /**
+ * @var string|null
+ */
+ private $text;
+
+ /**
+ * @var ParserOutput|false
+ */
+ private $parserOutput = false;
+
+ /**
+ * @var bool
+ */
+ private $isDirty = false;
+
+ /**
+ * @var Status|false
+ */
+ private $error = false;
+
+ /**
+ * Constructor
+ *
+ * @param $page Page
+ * @param $revid Integer: ID of the revision being parsed
+ * @param $useParserCache Boolean: whether to use the parser cache
+ * @param $parserOptions parserOptions to use for the parse operation
+ * @param $text String: text to parse or null to load it
+ */
+ function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $text = null ) {
+ $this->page = $page;
+ $this->revid = $revid;
+ $this->cacheable = $useParserCache;
+ $this->parserOptions = $parserOptions;
+ $this->text = $text;
+ $this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
+ parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
+ }
+
+ /**
+ * Get the ParserOutput from this object, or false in case of failure
+ *
+ * @return ParserOutput
+ */
+ public function getParserOutput() {
+ return $this->parserOutput;
+ }
+
+ /**
+ * Get whether the ParserOutput is a dirty one (i.e. expired)
+ *
+ * @return bool
+ */
+ public function getIsDirty() {
+ return $this->isDirty;
+ }
+
+ /**
+ * Get a Status object in case of error or false otherwise
+ *
+ * @return Status|false
+ */
+ public function getError() {
+ return $this->error;
+ }
+
+ /**
+ * @return bool
+ */
+ function doWork() {
+ global $wgParser, $wgUseFileCache;
+
+ $isCurrent = $this->revid === $this->page->getLatest();
+
+ if ( $this->text !== null ) {
+ $text = $this->text;
+ } elseif ( $isCurrent ) {
+ $text = $this->page->getRawText();
+ } else {
+ $rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
+ if ( $rev === null ) {
+ return false;
+ }
+ $text = $rev->getText();
+ }
+
+ $time = - microtime( true );
+ $this->parserOutput = $wgParser->parse( $text, $this->page->getTitle(),
+ $this->parserOptions, true, true, $this->revid );
+ $time += microtime( true );
+
+ # Timing hack
+ if ( $time > 3 ) {
+ wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
+ $this->page->getTitle()->getPrefixedDBkey() ) );
+ }
+
+ if ( $this->cacheable && $this->parserOutput->isCacheable() ) {
+ ParserCache::singleton()->save( $this->parserOutput, $this->page, $this->parserOptions );
+ }
+
+ // Make sure file cache is not used on uncacheable content.
+ // Output that has magic words in it can still use the parser cache
+ // (if enabled), though it will generally expire sooner.
+ if ( !$this->parserOutput->isCacheable() || $this->parserOutput->containsOldMagic() ) {
+ $wgUseFileCache = false;
+ }
+
+ if ( $isCurrent ) {
+ $this->page->doCascadeProtectionUpdates( $this->parserOutput );
+ }
+
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
+ function getCachedWork() {
+ $this->parserOutput = ParserCache::singleton()->get( $this->page, $this->parserOptions );
+
+ if ( $this->parserOutput === false ) {
+ wfDebug( __METHOD__ . ": parser cache miss\n" );
+ return false;
+ } else {
+ wfDebug( __METHOD__ . ": parser cache hit\n" );
+ return true;
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ function fallback() {
+ $this->parserOutput = ParserCache::singleton()->getDirty( $this->page, $this->parserOptions );
+
+ if ( $this->parserOutput === false ) {
+ wfDebugLog( 'dirty', "dirty missing\n" );
+ wfDebug( __METHOD__ . ": no dirty cache\n" );
+ return false;
+ } else {
+ wfDebug( __METHOD__ . ": sending dirty output\n" );
+ wfDebugLog( 'dirty', "dirty output {$this->cacheKey}\n" );
+ $this->isDirty = true;
+ return true;
+ }
+ }
+
+ /**
+ * @param $status Status
+ */
+ function error( $status ) {
+ $this->error = $status;
+ return false;
}
}
diff --git a/includes/Xml.php b/includes/Xml.php
index 45ee8720..7e5b3cdb 100644
--- a/includes/Xml.php
+++ b/includes/Xml.php
@@ -117,42 +117,19 @@ class Xml {
* @param $element_name String: value of the "name" attribute of the select tag
* @param $label String: optional label to add to the field
* @return string
+ * @deprecated since 1.19
*/
public static function namespaceSelector( $selected = '', $all = null, $element_name = 'namespace', $label = null ) {
- global $wgContLang;
- $namespaces = $wgContLang->getFormattedNamespaces();
- $options = array();
-
- // Godawful hack... we'll be frequently passed selected namespaces
- // as strings since PHP is such a shithole.
- // But we also don't want blanks and nulls and "all"s matching 0,
- // so let's convert *just* string ints to clean ints.
- if( preg_match( '/^\d+$/', $selected ) ) {
- $selected = intval( $selected );
- }
-
- if( !is_null( $all ) )
- $namespaces = array( $all => wfMsg( 'namespacesall' ) ) + $namespaces;
- foreach( $namespaces as $index => $name ) {
- if( $index < NS_MAIN ) {
- continue;
- }
- if( $index === 0 ) {
- $name = wfMsg( 'blanknamespace' );
- }
- $options[] = self::option( $name, $index, $index === $selected );
- }
-
- $ret = Xml::openElement( 'select', array( 'id' => 'namespace', 'name' => $element_name,
- 'class' => 'namespaceselector' ) )
- . "\n"
- . implode( "\n", $options )
- . "\n"
- . Xml::closeElement( 'select' );
- if ( !is_null( $label ) ) {
- $ret = Xml::label( $label, $element_name ) . '&#160;' . $ret;
- }
- return $ret;
+ wfDeprecated( __METHOD__, '1.19' );
+ return Html::namespaceSelector( array(
+ 'selected' => $selected,
+ 'all' => $all,
+ 'label' => $label,
+ ), array(
+ 'name' => $element_name,
+ 'id' => 'namespace',
+ 'class' => 'namespaceselector',
+ ) );
}
/**
@@ -208,21 +185,39 @@ class Xml {
}
/**
- *
- * @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
+ * Construct a language selector appropriate for use in a form or preferences
+ *
+ * @param string $selected The language code of the selected language
+ * @param boolean $customisedOnly If true only languages which have some content are listed
+ * @param string $language The ISO code of the language to display the select list in (optional)
+ * @return array containing 2 items: label HTML and select list HTML
*/
- public static function languageSelector( $selected, $customisedOnly = true ) {
+ public static function languageSelector( $selected, $customisedOnly = true, $language = null ) {
global $wgLanguageCode;
- /**
- * Make sure the site language is in the list; a custom language code
- * might not have a defined name...
- */
- $languages = Language::getLanguageNames( $customisedOnly );
+
+ // If a specific language was requested and CLDR is installed, use it
+ if ( $language && is_callable( array( 'LanguageNames', 'getNames' ) ) ) {
+ if ( $customisedOnly ) {
+ $listType = LanguageNames::LIST_MW_SUPPORTED; // Only pull names that have localisation in MediaWiki
+ } else {
+ $listType = LanguageNames::LIST_MW; // Pull all languages that are in Names.php
+ }
+ // Retrieve the list of languages in the requested language (via CLDR)
+ $languages = LanguageNames::getNames(
+ $language, // Code of the requested language
+ LanguageNames::FALLBACK_NORMAL, // Use fallback chain
+ $listType
+ );
+ } else {
+ $languages = Language::getLanguageNames( $customisedOnly );
+ }
+
+ // Make sure the site language is in the list; a custom language code might not have a
+ // defined name...
if( !array_key_exists( $wgLanguageCode, $languages ) ) {
$languages[$wgLanguageCode] = $wgLanguageCode;
}
+
ksort( $languages );
/**
@@ -334,10 +329,10 @@ class Xml {
/**
* Convenience function to build an HTML radio button
- * @param $name value of the name attribute
- * @param $value value of the value attribute
- * @param $checked Whether the checkbox is checked or not
- * @param $attribs other attributes
+ * @param $name String value of the name attribute
+ * @param $value String value of the value attribute
+ * @param $checked Bool Whether the checkbox is checked or not
+ * @param $attribs Array other attributes
* @return string HTML
*/
public static function radio( $name, $value, $checked = false, $attribs = array() ) {
@@ -360,7 +355,7 @@ class Xml {
public static function label( $label, $id, $attribs = array() ) {
$a = array( 'for' => $id );
- # FIXME avoid copy pasting below:
+ # FIXME avoid copy pasting below:
if( isset( $attribs['class'] ) ){
$a['class'] = $attribs['class'];
}
@@ -376,8 +371,8 @@ 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 int value of the size attribute
- * @param $value value of the value attribute
+ * @param $size Int|Bool value of the size attribute
+ * @param $value String|Bool value of the value attribute
* @param $attribs array other attributes
* @return string HTML
*/
@@ -389,11 +384,11 @@ class Xml {
/**
* Same as Xml::inputLabel() but return input and label in an array
*
- * @param $label
- * @param $name
- * @param $id
- * @param $size
- * @param $value
+ * @param $label String
+ * @param $name String
+ * @param $id String
+ * @param $size Int|Bool
+ * @param $value String|Bool
* @param $attribs array
*
* @return array
@@ -466,7 +461,7 @@ class Xml {
if( $selected ) {
$attribs['selected'] = 'selected';
}
- return self::element( 'option', $attribs, $text );
+ return Html::element( 'option', $attribs, $text );
}
/**
@@ -506,19 +501,24 @@ class Xml {
$optgroup = false;
}
}
+
if( $optgroup ) $options .= self::closeElement('optgroup');
$attribs = array();
+
if( $name ) {
$attribs['id'] = $name;
$attribs['name'] = $name;
}
+
if( $class ) {
$attribs['class'] = $class;
}
+
if( $tabindex ) {
$attribs['tabindex'] = $tabindex;
}
+
return Xml::openElement( 'select', $attribs )
. "\n"
. $options
@@ -537,9 +537,11 @@ class Xml {
*/
public static function fieldset( $legend = false, $content = false, $attribs = array() ) {
$s = Xml::openElement( 'fieldset', $attribs ) . "\n";
+
if ( $legend ) {
$s .= Xml::element( 'legend', null, $legend ) . "\n";
}
+
if ( $content !== false ) {
$s .= $content . "\n";
$s .= Xml::closeElement( 'fieldset' ) . "\n";
@@ -600,6 +602,7 @@ class Xml {
"\xe2\x80\x8c" => "\\u200c", // ZERO WIDTH NON-JOINER
"\xe2\x80\x8d" => "\\u200d", // ZERO WIDTH JOINER
);
+
return strtr( $string, $pairs );
}
@@ -627,7 +630,7 @@ class Xml {
$s = '[';
foreach ( $value as $elt ) {
if ( $s != '[' ) {
- $s .= ', ';
+ $s .= ',';
}
$s .= self::encodeJsVar( $elt );
}
@@ -639,9 +642,10 @@ class Xml {
$s = '{';
foreach ( (array)$value as $name => $elt ) {
if ( $s != '{' ) {
- $s .= ', ';
+ $s .= ',';
}
- $s .= '"' . self::escapeJsString( $name ) . '": ' .
+
+ $s .= '"' . self::escapeJsString( $name ) . '":' .
self::encodeJsVar( $elt );
}
$s .= '}';
@@ -666,19 +670,22 @@ class Xml {
public static function encodeJsCall( $name, $args ) {
$s = "$name(";
$first = true;
+
foreach ( $args as $arg ) {
if ( $first ) {
$first = false;
} else {
$s .= ', ';
}
+
$s .= Xml::encodeJsVar( $arg );
}
+
$s .= ");\n";
+
return $s;
}
-
/**
* Check if a string is well-formed XML.
* Must include the surrounding tag.
@@ -702,7 +709,9 @@ class Xml {
xml_parser_free( $parser );
return false;
}
+
xml_parser_free( $parser );
+
return true;
}
@@ -720,6 +729,7 @@ class Xml {
'<html>' .
$text .
'</html>';
+
return Xml::isWellFormed( $html );
}
@@ -765,7 +775,6 @@ class Xml {
$form .= "</tbody></table>";
-
return $form;
}
@@ -778,36 +787,59 @@ class Xml {
*/
public static function buildTable( $rows, $attribs = array(), $headers = null ) {
$s = Xml::openElement( 'table', $attribs );
+
if ( is_array( $headers ) ) {
+ $s .= Xml::openElement( 'thead', $attribs );
+
foreach( $headers as $id => $header ) {
$attribs = array();
- if ( is_string( $id ) ) $attribs['id'] = $id;
+
+ if ( is_string( $id ) ) {
+ $attribs['id'] = $id;
+ }
+
$s .= Xml::element( 'th', $attribs, $header );
}
+ $s .= Xml::closeElement( 'thead' );
}
+
foreach( $rows as $id => $row ) {
$attribs = array();
- if ( is_string( $id ) ) $attribs['id'] = $id;
+
+ if ( is_string( $id ) ) {
+ $attribs['id'] = $id;
+ }
+
$s .= Xml::buildTableRow( $attribs, $row );
}
+
$s .= Xml::closeElement( 'table' );
+
return $s;
}
/**
* Build a row for a table
- * @param $attribs An array of attributes to apply to the tr tag
- * @param $cells An array of strings to put in <td>
+ * @param $attribs array An array of attributes to apply to the tr tag
+ * @param $cells array An array of strings to put in <td>
* @return string
*/
public static function buildTableRow( $attribs, $cells ) {
$s = Xml::openElement( 'tr', $attribs );
+
foreach( $cells as $id => $cell ) {
+
$attribs = array();
- if ( is_string( $id ) ) $attribs['id'] = $id;
+
+ if ( is_string( $id ) ) {
+ $attribs['id'] = $id;
+ }
+
$s .= Xml::element( 'td', $attribs, $cell );
}
+
$s .= Xml::closeElement( 'tr' );
+
return $s;
}
}
@@ -821,9 +853,11 @@ class XmlSelect {
if ( $name ) {
$this->setAttribute( 'name', $name );
}
+
if ( $id ) {
$this->setAttribute( 'id', $id );
}
+
if ( $default !== false ) {
$this->default = $default;
}
@@ -849,7 +883,7 @@ class XmlSelect {
* @return array|null
*/
public function getAttribute( $name ) {
- if ( isset($this->attributes[$name]) ) {
+ if ( isset( $this->attributes[$name] ) ) {
return $this->attributes[$name];
} else {
return null;
@@ -863,6 +897,7 @@ class XmlSelect {
public function addOption( $name, $value = false ) {
// Stab stab stab
$value = ($value !== false) ? $value : $name;
+
$this->options[] = array( $name => $value );
}
@@ -888,10 +923,11 @@ class XmlSelect {
*/
static function formatOptions( $options, $default = false ) {
$data = '';
+
foreach( $options as $label => $value ) {
if ( is_array( $value ) ) {
$contents = self::formatOptions( $value, $default );
- $data .= Xml::tags( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
+ $data .= Html::rawElement( 'optgroup', array( 'label' => $label ), $contents ) . "\n";
} else {
$data .= Xml::option( $label, $value, $value === $default ) . "\n";
}
@@ -905,12 +941,13 @@ class XmlSelect {
*/
public function getHTML() {
$contents = '';
+
foreach ( $this->options as $options ) {
$contents .= self::formatOptions( $options, $this->default );
}
- return Xml::tags( 'select', $this->attributes, rtrim( $contents ) );
- }
+ return Html::rawElement( 'select', $this->attributes, rtrim( $contents ) );
+ }
}
/**
diff --git a/includes/XmlTypeCheck.php b/includes/XmlTypeCheck.php
index 78dd259d..be286f8e 100644
--- a/includes/XmlTypeCheck.php
+++ b/includes/XmlTypeCheck.php
@@ -6,7 +6,7 @@ class XmlTypeCheck {
* well-formed XML. Note that this doesn't check schema validity.
*/
public $wellFormed = false;
-
+
/**
* Will be set to true if the optional element filter returned
* a match at some point.
@@ -31,7 +31,7 @@ class XmlTypeCheck {
$this->filterCallback = $filterCallback;
$this->run( $file );
}
-
+
/**
* Get the root element. Simple accessor to $rootElement
*
@@ -52,21 +52,26 @@ class XmlTypeCheck {
xml_set_element_handler( $parser, array( $this, 'rootElementOpen' ), false );
- $file = fopen( $fname, "rb" );
- do {
- $chunk = fread( $file, 32768 );
- $ret = xml_parse( $parser, $chunk, feof( $file ) );
- if( $ret == 0 ) {
- // XML isn't well-formed!
+ if ( file_exists( $fname ) ) {
+ $file = fopen( $fname, "rb" );
+ if ( $file ) {
+ do {
+ $chunk = fread( $file, 32768 );
+ $ret = xml_parse( $parser, $chunk, feof( $file ) );
+ if( $ret == 0 ) {
+ // XML isn't well-formed!
+ fclose( $file );
+ xml_parser_free( $parser );
+ return;
+ }
+ } while( !feof( $file ) );
+
fclose( $file );
- xml_parser_free( $parser );
- return;
}
- } while( !feof( $file ) );
+ }
$this->wellFormed = true;
- fclose( $file );
xml_parser_free( $parser );
}
@@ -77,7 +82,7 @@ class XmlTypeCheck {
*/
private function rootElementOpen( $parser, $name, $attribs ) {
$this->rootElement = $name;
-
+
if( is_callable( $this->filterCallback ) ) {
xml_set_element_handler( $parser, array( $this, 'elementOpen' ), false );
$this->elementOpen( $parser, $name, $attribs );
diff --git a/includes/ZhClient.php b/includes/ZhClient.php
index 8bb36b23..d3d79165 100644
--- a/includes/ZhClient.php
+++ b/includes/ZhClient.php
@@ -41,10 +41,7 @@ class ZhClient {
$errno = $errstr = '';
$this->mFP = fsockopen( $this->mHost, $this->mPort, $errno, $errstr, 30 );
wfRestoreWarnings();
- if ( !$this->mFP ) {
- return false;
- }
- return true;
+ return !$this->mFP;
}
/**
@@ -77,10 +74,7 @@ class ZhClient {
$data .= $str;
}
// data should be of length $len. otherwise something is wrong
- if ( strlen( $data ) != $len ) {
- return false;
- }
- return $data;
+ return strlen( $data ) == $len;
}
/**
@@ -124,6 +118,7 @@ class ZhClient {
}
return $ret;
}
+
/**
* Perform word segmentation
*
diff --git a/includes/ZipDirectoryReader.php b/includes/ZipDirectoryReader.php
index d21cf3b0..37934aea 100644
--- a/includes/ZipDirectoryReader.php
+++ b/includes/ZipDirectoryReader.php
@@ -1,8 +1,8 @@
<?php
/**
- * A class for reading ZIP file directories, for the purposes of upload
- * verification.
+ * 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.
@@ -12,7 +12,7 @@ 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
+ * Because this class is aimed at verification, an error is raised on
* suspicious or ambiguous input, instead of emulating some standard
* behaviour.
*
@@ -20,7 +20,7 @@ class ZipDirectoryReader {
* @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
+ * - name: The file name. Directories conventionally have a trailing
* slash.
*
* - mtime: The file modification time, in MediaWiki 14-char format
@@ -30,18 +30,18 @@ class ZipDirectoryReader {
* @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: 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
+ * 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:
@@ -50,20 +50,20 @@ class ZipDirectoryReader {
*
* - zip-wrong-format: The file does not appear to be a ZIP file.
*
- * - zip-bad: There was something wrong or ambiguous about the file
+ * - zip-bad: There was something wrong or ambiguous about the file
* data.
*
- * - zip-unsupported: The ZIP file uses features which
+ * - 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
+ * 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
+ * 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
+ * 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() ) {
@@ -92,6 +92,8 @@ class ZipDirectoryReader {
/** Stored headers */
var $eocdr, $eocdr64, $eocdr64Locator;
+ var $data;
+
/** The "extra field" ID for ZIP64 central directory entries */
const ZIP64_EXTRA_HEADER = 0x0001;
@@ -104,7 +106,6 @@ class ZipDirectoryReader {
/** The index of the "general field" bit for central directory encryption */
const GENERAL_CD_ENCRYPTED = 13;
-
/**
* Private constructor
*/
@@ -165,8 +166,8 @@ class ZipDirectoryReader {
}
/**
- * Read the header which is at the end of the central directory,
- * unimaginatively called the "end of central directory record" by the ZIP
+ * 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() {
@@ -189,7 +190,7 @@ class ZipDirectoryReader {
$block = $this->getBlock( $startPos );
$sigPos = strrpos( $block, "PK\x05\x06" );
if ( $sigPos === false ) {
- $this->error( 'zip-wrong-format',
+ $this->error( 'zip-wrong-format',
"zip file lacks EOCDR signature. It probably isn't a zip file." );
}
@@ -212,7 +213,7 @@ class ZipDirectoryReader {
}
/**
- * Read the header called the "ZIP64 end of central directory locator". An
+ * Read the header called the "ZIP64 end of central directory locator". An
* error will be raised if it does not exist.
*/
function readZip64EndOfCentralDirectoryLocator() {
@@ -224,20 +225,20 @@ class ZipDirectoryReader {
);
$structSize = $this->getStructSize( $info );
- $block = $this->getBlock( $this->getFileLength() - $this->eocdr['EOCDR size']
+ $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
+ // 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
+ * 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() {
@@ -266,14 +267,14 @@ class ZipDirectoryReader {
$this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory record' );
}
if ( $data['disk'] !== 0
- || $data['CD start disk'] !== 0 )
+ || $data['CD start disk'] !== 0 )
{
$this->error( 'zip-unsupported', 'more than one disk (in EOCDR64)' );
}
}
/**
- * Find the location of the central directory, as would be seen by a
+ * Find the location of the central directory, as would be seen by a
* non-ZIP64 reader.
*
* @return List containing offset, size and end position.
@@ -286,27 +287,27 @@ class ZipDirectoryReader {
// 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 ' .
+ $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
+ * 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
+ // 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
+ if ( $size == 0xffffffff
|| $offset == 0xffffffff
|| $numEntries == 0xffff )
{
@@ -324,7 +325,7 @@ class ZipDirectoryReader {
// 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 ' .
+ $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
'of central directory record' );
}
return array( $offset, $size );
@@ -390,7 +391,7 @@ class ZipDirectoryReader {
}
// Convert the timestamp into MediaWiki format
- // For the format, please see the MS-DOS 2.0 Programmer's Reference,
+ // 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'];
@@ -405,8 +406,8 @@ class ZipDirectoryReader {
$year, $month, $day, $hour, $minute, $second );
// Convert the character set in the file name
- if ( !function_exists( 'iconv' )
- || $this->testBit( $data['general bits'], self::GENERAL_UTF8 ) )
+ if ( !function_exists( 'iconv' )
+ || $this->testBit( $data['general bits'], self::GENERAL_UTF8 ) )
{
$name = $data['name'];
} else {
@@ -444,7 +445,7 @@ class ZipDirectoryReader {
while ( $extraPos < strlen( $extraField ) ) {
$extra = $this->unpack( $extraField, $extraHeaderInfo, $extraPos );
$extraPos += $extraHeaderSize;
- $extra += $this->unpack( $extraField,
+ $extra += $this->unpack( $extraField,
array( 'data' => array( 'string', $extra['size'] ) ),
$extraPos );
$extraPos += $extra['size'];
@@ -473,7 +474,7 @@ class ZipDirectoryReader {
* 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
+ * @param $length The number of bytes to return. If omitted, the remainder
* of the file will be returned.
*
* @return string
@@ -500,10 +501,10 @@ class ZipDirectoryReader {
$block .= $this->getSegment( $segIndex );
}
- $block = substr( $block,
+ $block = substr( $block,
$start - $startSeg * self::SEGSIZE,
$length );
-
+
if ( strlen( $block ) < $length ) {
$this->error( 'zip-bad', 'getBlock() returned an unexpectedly small amount of data' );
}
@@ -512,12 +513,12 @@ class ZipDirectoryReader {
}
/**
- * 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
+ * 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
+ * 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 ) {
@@ -556,25 +557,25 @@ class ZipDirectoryReader {
}
/**
- * Unpack a binary structure. This is like the built-in unpack() function
+ * 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
+ * @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.
+ * - "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.
+ * @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 );
@@ -600,8 +601,8 @@ class ZipDirectoryReader {
$length = intval( $type );
$bytes = substr( $string, $pos, $length );
- // Calculate the value. Use an algorithm which automatically
- // upgrades the value to floating point if necessary.
+ // 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;
@@ -623,7 +624,7 @@ class ZipDirectoryReader {
}
/**
- * Returns a bit from a given position in an integer value, converted to
+ * Returns a bit from a given position in an integer value, converted to
* boolean.
*
* @param $value integer
@@ -678,6 +679,9 @@ class ZipDirectoryReaderError extends Exception {
parent::__construct( "ZipDirectoryReader error: $code" );
}
+ /**
+ * @return mixed
+ */
function getErrorCode() {
return $this->code;
}
diff --git a/includes/actions/CreditsAction.php b/includes/actions/CreditsAction.php
index 1040085b..cd083c30 100644
--- a/includes/actions/CreditsAction.php
+++ b/includes/actions/CreditsAction.php
@@ -29,12 +29,8 @@ class CreditsAction extends FormlessAction {
return 'credits';
}
- public function getRestriction() {
- return null;
- }
-
protected function getDescription() {
- return wfMsg( 'creditspage' );
+ return wfMsgHtml( 'creditspage' );
}
/**
@@ -46,7 +42,7 @@ class CreditsAction extends FormlessAction {
wfProfileIn( __METHOD__ );
if ( $this->page->getID() == 0 ) {
- $s = wfMsg( 'nocredits' );
+ $s = $this->msg( 'nocredits' )->parse();
} else {
$s = $this->getCredits( -1 );
}
@@ -67,8 +63,8 @@ class CreditsAction extends FormlessAction {
wfProfileIn( __METHOD__ );
$s = '';
- if ( isset( $cnt ) && $cnt != 0 ) {
- $s = self::getAuthor( $this->page );
+ if ( $cnt != 0 ) {
+ $s = $this->getAuthor( $this->page );
if ( $cnt > 1 || $cnt < 0 ) {
$s .= ' ' . $this->getContributors( $cnt - 1, $showIfMax );
}
@@ -83,20 +79,20 @@ class CreditsAction extends FormlessAction {
* @param $article Article object
* @return String HTML
*/
- protected static function getAuthor( Page $article ) {
- global $wgLang;
-
- $user = User::newFromId( $article->getUser() );
+ protected function getAuthor( Page $article ) {
+ $user = User::newFromName( $article->getUserText(), false );
$timestamp = $article->getTimestamp();
if ( $timestamp ) {
- $d = $wgLang->date( $article->getTimestamp(), true );
- $t = $wgLang->time( $article->getTimestamp(), true );
+ $lang = $this->getLanguage();
+ $d = $lang->date( $article->getTimestamp(), true );
+ $t = $lang->time( $article->getTimestamp(), true );
} else {
$d = '';
$t = '';
}
- return wfMessage( 'lastmodifiedatby', $d, $t )->rawParams( self::userLink( $user ) )->params( $user->getName() )->escaped();
+ return $this->msg( 'lastmodifiedatby', $d, $t )->rawParams(
+ $this->userLink( $user ) )->params( $user->getName() )->escaped();
}
/**
@@ -106,7 +102,7 @@ class CreditsAction extends FormlessAction {
* @return String: html
*/
protected function getContributors( $cnt, $showIfMax ) {
- global $wgLang, $wgHiddenPrefs;
+ global $wgHiddenPrefs;
$contributors = $this->page->getContributors();
@@ -116,7 +112,8 @@ class CreditsAction extends FormlessAction {
if ( $cnt > 0 && $contributors->count() > $cnt ) {
$others_link = $this->othersLink();
if ( !$showIfMax )
- return wfMessage( 'othercontribs' )->rawParams( $others_link )->params( $contributors->count() )->escaped();
+ return $this->msg( 'othercontribs' )->rawParams(
+ $others_link )->params( $contributors->count() )->escaped();
}
$real_names = array();
@@ -127,14 +124,14 @@ class CreditsAction extends FormlessAction {
foreach ( $contributors as $user ) {
$cnt--;
if ( $user->isLoggedIn() ) {
- $link = self::link( $user );
+ $link = $this->link( $user );
if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
$real_names[] = $link;
} else {
$user_names[] = $link;
}
} else {
- $anon_ips[] = self::link( $user );
+ $anon_ips[] = $this->link( $user );
}
if ( $cnt == 0 ) {
@@ -142,22 +139,24 @@ class CreditsAction extends FormlessAction {
}
}
+ $lang = $this->getLanguage();
+
if ( count( $real_names ) ) {
- $real = $wgLang->listToText( $real_names );
+ $real = $lang->listToText( $real_names );
} else {
$real = false;
}
# "ThisSite user(s) A, B and C"
if ( count( $user_names ) ) {
- $user = wfMessage( 'siteusers' )->rawParams( $wgLang->listToText( $user_names ) )->params(
+ $user = $this->msg( 'siteusers' )->rawParams( $lang->listToText( $user_names ) )->params(
count( $user_names ) )->escaped();
} else {
$user = false;
}
if ( count( $anon_ips ) ) {
- $anon = wfMessage( 'anonusers' )->rawParams( $wgLang->listToText( $anon_ips ) )->params(
+ $anon = $this->msg( 'anonusers' )->rawParams( $lang->listToText( $anon_ips ) )->params(
count( $anon_ips ) )->escaped();
} else {
$anon = false;
@@ -174,8 +173,8 @@ class CreditsAction extends FormlessAction {
$count = count( $fulllist );
# "Based on work by ..."
return $count
- ? wfMessage( 'othercontribs' )->rawParams(
- $wgLang->listToText( $fulllist ) )->params( $count )->escaped()
+ ? $this->msg( 'othercontribs' )->rawParams(
+ $lang->listToText( $fulllist ) )->params( $count )->escaped()
: '';
}
@@ -184,7 +183,7 @@ class CreditsAction extends FormlessAction {
* @param $user User object
* @return String: html
*/
- protected static function link( User $user ) {
+ protected function link( User $user ) {
global $wgHiddenPrefs;
if ( !in_array( 'realname', $wgHiddenPrefs ) && !$user->isAnon() ) {
$real = $user->getRealName();
@@ -204,32 +203,30 @@ class CreditsAction extends FormlessAction {
* @param $user User object
* @return String: html
*/
- protected static function userLink( User $user ) {
- $link = self::link( $user );
+ protected function userLink( User $user ) {
+ $link = $this->link( $user );
if ( $user->isAnon() ) {
- return wfMsgExt( 'anonuser', array( 'parseinline', 'replaceafter' ), $link );
+ return $this->msg( 'anonuser' )->rawParams( $link )->parse();
} else {
global $wgHiddenPrefs;
if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
return $link;
} else {
- return wfMessage( 'siteuser' )->rawParams( $link )->params( $user->getName() )->escaped();
+ return $this->msg( 'siteuser' )->rawParams( $link )->params( $user->getName() )->escaped();
}
}
}
/**
* Get a link to action=credits of $article page
- * @param $article Article object
- * @return String: html
+ * @return String: HTML link
*/
protected function othersLink() {
- return Linker::link(
+ return Linker::linkKnown(
$this->getTitle(),
- wfMsgHtml( 'others' ),
+ $this->msg( 'others' )->escaped(),
array(),
- array( 'action' => 'credits' ),
- array( 'known' )
+ array( 'action' => 'credits' )
);
}
}
diff --git a/includes/actions/DeletetrackbackAction.php b/includes/actions/DeleteAction.php
index 0efebdf5..5a5a382b 100644
--- a/includes/actions/DeletetrackbackAction.php
+++ b/includes/actions/DeleteAction.php
@@ -1,8 +1,8 @@
<?php
/**
- * Delete a trackback on a page
+ * Handle page deletion
*
- * Copyright © 2011 Alexandre Emsenhuber
+ * Copyright © 2012 Timo Tijhof
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,35 +20,23 @@
*
* @file
* @ingroup Actions
+ * @author Timo Tijhof
*/
-class DeletetrackbackAction extends FormlessAction {
+class DeleteAction extends FormlessAction {
public function getName() {
- return 'deletetrackback';
- }
-
- public function getRestriction() {
return 'delete';
}
- protected function getDescription() {
- return '';
+ public function onView(){
+ return null;
}
- protected function checkCanExecute( User $user ) {
- if ( !$user->matchEditToken( $this->getRequest()->getVal( 'token' ) ) ) {
- throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
- }
+ public function show(){
- return parent::checkCanExecute( $user );
- }
+ $this->page->delete();
- 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/EditAction.php b/includes/actions/EditAction.php
new file mode 100644
index 00000000..08a33f4c
--- /dev/null
+++ b/includes/actions/EditAction.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * action=edit / action=submit handler
+ *
+ * Copyright © 2012 Timo Tijhof
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ * @author Timo Tijhof
+ */
+
+class EditAction extends FormlessAction {
+
+ public function getName() {
+ return 'edit';
+ }
+
+ public function onView(){
+ return null;
+ }
+
+ public function show(){
+ $page = $this->page;
+ $request = $this->getRequest();
+ $user = $this->getUser();
+ $context = $this->getContext();
+
+ if ( wfRunHooks( 'CustomEditor', array( $page, $user ) ) ) {
+ if ( ExternalEdit::useExternalEngine( $context, 'edit' )
+ && $this->getName() == 'edit' && !$request->getVal( 'section' )
+ && !$request->getVal( 'oldid' ) )
+ {
+ $extedit = new ExternalEdit( $context );
+ $extedit->execute();
+ } else {
+ $editor = new EditPage( $page );
+ $editor->edit();
+ }
+ }
+
+ }
+
+}
+
+class SubmitAction extends EditAction {
+
+ public function getName() {
+ return 'submit';
+ }
+
+ public function show(){
+ if ( session_id() == '' ) {
+ // Send a cookie so anons get talk message notifications
+ wfSetupSession();
+ }
+
+ parent::show();
+ }
+
+}
diff --git a/includes/HistoryPage.php b/includes/actions/HistoryAction.php
index dd5ecd43..457f67ff 100644
--- a/includes/HistoryPage.php
+++ b/includes/actions/HistoryAction.php
@@ -15,38 +15,42 @@
* history.
*
*/
-class HistoryPage {
+class HistoryAction extends FormlessAction {
const DIR_PREV = 0;
const DIR_NEXT = 1;
- /** Contains the Article object. Passed on construction. */
- private $article;
- /** The $article title object. Found on construction. */
- private $title;
- /** Shortcut to the user Skin object. */
- private $skin;
+ public function getName() {
+ return 'history';
+ }
- /**
- * Construct a new HistoryPage.
- *
- * @param $article Article
- */
- function __construct( $article ) {
- global $wgUser;
- $this->article = $article;
- $this->title = $article->getTitle();
- $this->skin = $wgUser->getSkin();
- $this->preCacheMessages();
+ public function requiresWrite() {
+ return false;
}
- /** Get the Article object we are working on. */
- public function getArticle() {
- return $this->article;
+ public function requiresUnblock() {
+ return false;
}
- /** Get the Title object. */
- public function getTitle() {
- return $this->title;
+ protected function getPageTitle() {
+ return $this->msg( 'history-title', $this->getTitle()->getPrefixedText() )->text();
+ }
+
+ protected function getDescription() {
+ // Creation of a subtitle link pointing to [[Special:Log]]
+ return Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Log' ),
+ $this->msg( 'viewpagelogs' )->escaped(),
+ array(),
+ array( 'page' => $this->getTitle()->getPrefixedText() )
+ );
+ }
+
+ /**
+ * Get the Article object we are working on.
+ * @return Page
+ */
+ public function getArticle() {
+ return $this->page;
}
/**
@@ -58,7 +62,7 @@ class HistoryPage {
if ( !isset( $this->message ) ) {
$msgs = array( 'cur', 'last', 'pipe-separator' );
foreach ( $msgs as $msg ) {
- $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
+ $this->message[$msg] = $this->msg( $msg )->escaped();
}
}
}
@@ -67,53 +71,54 @@ class HistoryPage {
* Print the history page for an article.
* @return nothing
*/
- function history() {
- global $wgOut, $wgRequest, $wgScript;
+ function onView() {
+ global $wgScript, $wgUseFileCache, $wgSquidMaxage;
+
+ $out = $this->getOutput();
+ $request = $this->getRequest();
/**
* Allow client caching.
*/
- if ( $wgOut->checkLastModified( $this->article->getTouched() ) )
+ if ( $out->checkLastModified( $this->page->getTouched() ) ) {
return; // Client cache fresh and headers sent, nothing more to do.
+ }
wfProfileIn( __METHOD__ );
- // Setup page variables.
- $wgOut->setPageTitle( wfMsg( 'history-title', $this->title->getPrefixedText() ) );
- $wgOut->setPageTitleActionText( wfMsg( 'history_short' ) );
- $wgOut->setArticleFlag( false );
- $wgOut->setArticleRelated( true );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->setSyndicated( true );
- $wgOut->setFeedAppendQuery( 'action=history' );
- $wgOut->addModules( array( 'mediawiki.legacy.history', 'mediawiki.action.history' ) );
+ if ( $request->getFullRequestURL() == $this->getTitle()->getInternalURL( 'action=history' ) ) {
+ $out->setSquidMaxage( $wgSquidMaxage );
+ }
- // Creation of a subtitle link pointing to [[Special:Log]]
- $logPage = SpecialPage::getTitleFor( 'Log' );
- $logLink = $this->skin->link(
- $logPage,
- wfMsgHtml( 'viewpagelogs' ),
- array(),
- array( 'page' => $this->title->getPrefixedText() ),
- array( 'known', 'noclasses' )
- );
- $wgOut->setSubtitle( $logLink );
+ $this->preCacheMessages();
+
+ # Fill in the file cache if not set already
+ if ( $wgUseFileCache && HTMLFileCache::useFileCache( $this->getContext() ) ) {
+ $cache = HTMLFileCache::newFromTitle( $this->getTitle(), 'history' );
+ if ( !$cache->isCacheGood( /* Assume up to date */ ) ) {
+ ob_start( array( &$cache, 'saveToFileCache' ) );
+ }
+ }
+
+ // Setup page variables.
+ $out->setFeedAppendQuery( 'action=history' );
+ $out->addModules( array( 'mediawiki.legacy.history', 'mediawiki.action.history' ) );
// Handle atom/RSS feeds.
- $feedType = $wgRequest->getVal( 'feed' );
+ $feedType = $request->getVal( 'feed' );
if ( $feedType ) {
wfProfileOut( __METHOD__ );
return $this->feed( $feedType );
}
// Fail nicely if article doesn't exist.
- if ( !$this->title->exists() ) {
- $wgOut->addWikiMsg( 'nohistory' );
+ if ( !$this->page->exists() ) {
+ $out->addWikiMsg( 'nohistory' );
# show deletion/move log if there is an entry
LogEventsList::showLogExtract(
- $wgOut,
+ $out,
array( 'delete', 'move' ),
- $this->title->getPrefixedText(),
+ $this->getTitle(),
'',
array( 'lim' => 10,
'conds' => array( "log_action != 'revision'" ),
@@ -128,50 +133,50 @@ class HistoryPage {
/**
* Add date selector to quickly get to a certain time
*/
- $year = $wgRequest->getInt( 'year' );
- $month = $wgRequest->getInt( 'month' );
- $tagFilter = $wgRequest->getVal( 'tagfilter' );
+ $year = $request->getInt( 'year' );
+ $month = $request->getInt( 'month' );
+ $tagFilter = $request->getVal( 'tagfilter' );
$tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
/**
* Option to show only revisions that have been (partially) hidden via RevisionDelete
*/
- if ( $wgRequest->getBool( 'deleted' ) ) {
+ if ( $request->getBool( 'deleted' ) ) {
$conds = array( "rev_deleted != '0'" );
} else {
$conds = array();
}
- $checkDeleted = Xml::checkLabel( wfMsg( 'history-show-deleted' ),
- 'deleted', 'mw-show-deleted-only', $wgRequest->getBool( 'deleted' ) ) . "\n";
+ $checkDeleted = Xml::checkLabel( $this->msg( 'history-show-deleted' )->text(),
+ 'deleted', 'mw-show-deleted-only', $request->getBool( 'deleted' ) ) . "\n";
// Add the general form
$action = htmlspecialchars( $wgScript );
- $wgOut->addHTML(
+ $out->addHTML(
"<form action=\"$action\" method=\"get\" id=\"mw-history-searchform\">" .
Xml::fieldset(
- wfMsg( 'history-fieldset-title' ),
+ $this->msg( 'history-fieldset-title' )->text(),
false,
array( 'id' => 'mw-history-search' )
) .
- Html::hidden( 'title', $this->title->getPrefixedDBKey() ) . "\n" .
+ Html::hidden( 'title', $this->getTitle()->getPrefixedDBKey() ) . "\n" .
Html::hidden( 'action', 'history' ) . "\n" .
Xml::dateMenu( $year, $month ) . '&#160;' .
( $tagSelector ? ( implode( '&#160;', $tagSelector ) . '&#160;' ) : '' ) .
$checkDeleted .
- Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
+ Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n" .
'</fieldset></form>'
);
- wfRunHooks( 'PageHistoryBeforeList', array( &$this->article ) );
+ wfRunHooks( 'PageHistoryBeforeList', array( &$this->page ) );
// Create and output the list.
$pager = new HistoryPager( $this, $year, $month, $tagFilter, $conds );
- $wgOut->addHTML(
+ $out->addHTML(
$pager->getNavigationBar() .
$pager->getBody() .
$pager->getNavigationBar()
);
- $wgOut->preventClickjacking( $pager->getPreventClickjacking() );
+ $out->preventClickjacking( $pager->getPreventClickjacking() );
wfProfileOut( __METHOD__ );
}
@@ -201,7 +206,7 @@ class HistoryPage {
$offsets = array();
}
- $page_id = $this->title->getArticleID();
+ $page_id = $this->page->getId();
return $dbr->select( 'revision',
Revision::selectFields(),
@@ -218,21 +223,22 @@ class HistoryPage {
* @param $type String: feed type
*/
function feed( $type ) {
- global $wgFeedClasses, $wgRequest, $wgFeedLimit;
+ global $wgFeedClasses, $wgFeedLimit;
if ( !FeedUtils::checkFeedOutput( $type ) ) {
return;
}
+ $request = $this->getRequest();
$feed = new $wgFeedClasses[$type](
- $this->title->getPrefixedText() . ' - ' .
+ $this->getTitle()->getPrefixedText() . ' - ' .
wfMsgForContent( 'history-feed-title' ),
wfMsgForContent( 'history-feed-description' ),
- $this->title->getFullUrl( 'action=history' )
+ $this->getTitle()->getFullUrl( 'action=history' )
);
// Get a limit on number of feed entries. Provide a sane default
// of 10 if none is defined (but limit to $wgFeedLimit max)
- $limit = $wgRequest->getInt( 'limit', 10 );
+ $limit = $request->getInt( 'limit', 10 );
if ( $limit > $wgFeedLimit || $limit < 1 ) {
$limit = 10;
}
@@ -240,7 +246,7 @@ class HistoryPage {
// Generate feed elements enclosed between header and footer.
$feed->outHeader();
- if ( $items ) {
+ if ( $items->numRows() ) {
foreach ( $items as $row ) {
$feed->outItem( $this->feedItem( $row ) );
}
@@ -251,14 +257,13 @@ class HistoryPage {
}
function feedEmpty() {
- global $wgOut;
return new FeedItem(
wfMsgForContent( 'nohistory' ),
- $wgOut->parse( wfMsgForContent( 'history-feed-empty' ) ),
- $this->title->getFullUrl(),
+ $this->getOutput()->parse( wfMsgForContent( 'history-feed-empty' ) ),
+ $this->getTitle()->getFullUrl(),
wfTimestamp( TS_MW ),
'',
- $this->title->getTalkPage()->getFullUrl()
+ $this->getTitle()->getTalkPage()->getFullUrl()
);
}
@@ -272,10 +277,10 @@ class HistoryPage {
*/
function feedItem( $row ) {
$rev = new Revision( $row );
- $rev->setTitle( $this->title );
+ $rev->setTitle( $this->getTitle() );
$text = FeedUtils::formatDiffRow(
- $this->title,
- $this->title->getPreviousRevisionID( $rev->getId() ),
+ $this->getTitle(),
+ $this->getTitle()->getPreviousRevisionID( $rev->getId() ),
$rev->getId(),
$rev->getTimestamp(),
$rev->getComment()
@@ -296,10 +301,10 @@ class HistoryPage {
return new FeedItem(
$title,
$text,
- $this->title->getFullUrl( 'diff=' . $rev->getId() . '&oldid=prev' ),
+ $this->getTitle()->getFullUrl( 'diff=' . $rev->getId() . '&oldid=prev' ),
$rev->getTimestamp(),
$rev->getUserText(),
- $this->title->getTalkPage()->getFullUrl()
+ $this->getTitle()->getTalkPage()->getFullUrl()
);
}
}
@@ -308,14 +313,13 @@ class HistoryPage {
* @ingroup Pager
*/
class HistoryPager extends ReverseChronologicalPager {
- public $lastRow = false, $counter, $historyPage, $title, $buttons, $conds;
+ public $lastRow = false, $counter, $historyPage, $buttons, $conds;
protected $oldIdChecked;
protected $preventClickjacking = false;
function __construct( $historyPage, $year = '', $month = '', $tagFilter = '', $conds = array() ) {
- parent::__construct();
+ parent::__construct( $historyPage->getContext() );
$this->historyPage = $historyPage;
- $this->title = $this->historyPage->getTitle();
$this->tagFilter = $tagFilter;
$this->getDateCond( $year, $month );
$this->conds = $conds;
@@ -326,10 +330,6 @@ 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
@@ -340,13 +340,15 @@ class HistoryPager extends ReverseChronologicalPager {
function getQueryInfo() {
$queryInfo = array(
- 'tables' => array( 'revision' ),
- 'fields' => Revision::selectFields(),
+ 'tables' => array( 'revision', 'user' ),
+ 'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ),
'conds' => array_merge(
- array( 'rev_page' => $this->title->getArticleID() ),
+ array( 'rev_page' => $this->getWikiPage()->getId() ),
$this->conds ),
'options' => array( 'USE INDEX' => array( 'revision' => 'page_timestamp' ) ),
- 'join_conds' => array( 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ),
+ 'join_conds' => array(
+ 'user' => Revision::userJoinCond(),
+ 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ),
);
ChangeTags::modifyDisplayQuery(
$queryInfo['tables'],
@@ -370,7 +372,7 @@ class HistoryPager extends ReverseChronologicalPager {
$firstInList = $this->counter == 1;
$this->counter++;
$s = $this->historyLine( $this->lastRow, $row,
- $this->title->getNotificationTimestamp(), $latest, $firstInList );
+ $this->getTitle()->getNotificationTimestamp( $this->getUser() ), $latest, $firstInList );
} else {
$s = '';
}
@@ -378,37 +380,54 @@ class HistoryPager extends ReverseChronologicalPager {
return $s;
}
+ function doBatchLookups() {
+ # Do a link batch query
+ $this->mResult->seek( 0 );
+ $batch = new LinkBatch();
+ foreach ( $this->mResult as $row ) {
+ if( !is_null( $row->user_name ) ) {
+ $batch->add( NS_USER, $row->user_name );
+ $batch->add( NS_USER_TALK, $row->user_name );
+ } else { # for anons or usernames of imported revisions
+ $batch->add( NS_USER, $row->rev_user_text );
+ $batch->add( NS_USER_TALK, $row->rev_user_text );
+ }
+ }
+ $batch->execute();
+ $this->mResult->seek( 0 );
+ }
+
/**
* Creates begin of history list with a submit button
*
* @return string HTML output
*/
function getStartBody() {
- global $wgScript, $wgUser, $wgOut;
+ global $wgScript;
$this->lastRow = false;
$this->counter = 1;
$this->oldIdChecked = 0;
- $wgOut->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' );
+ $this->getOutput()->wrapWikiMsg( "<div class='mw-history-legend'>\n$1\n</div>", 'histlegend' );
$s = Html::openElement( 'form', array( 'action' => $wgScript,
'id' => 'mw-history-compare' ) ) . "\n";
- $s .= Html::hidden( 'title', $this->title->getPrefixedDbKey() ) . "\n";
+ $s .= Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . "\n";
$s .= Html::hidden( 'action', 'historysubmit' ) . "\n";
- $s .= '<div>' . $this->submitButton( wfMsg( 'compareselectedversions' ),
- array( 'class' => 'historysubmit' ) ) . "\n";
-
+ // Button container stored in $this->buttons for re-use in getEndBody()
$this->buttons = '<div>';
- $this->buttons .= $this->submitButton( wfMsg( 'compareselectedversions' ),
- array( 'class' => 'historysubmit' )
+ $this->buttons .= $this->submitButton( $this->msg( 'compareselectedversions' )->text(),
+ array( 'class' => 'historysubmit mw-history-compareselectedversions-button' )
+ Linker::tooltipAndAccesskeyAttribs( 'compareselectedversions' )
) . "\n";
- if ( $wgUser->isAllowed( 'deleterevision' ) ) {
- $s .= $this->getRevisionButton( 'revisiondelete', 'showhideselectedversions' );
+ if ( $this->getUser()->isAllowed( 'deleterevision' ) ) {
+ $this->buttons .= $this->getRevisionButton( 'revisiondelete', 'showhideselectedversions' );
}
$this->buttons .= '</div>';
- $s .= '</div><ul id="pagehistory">' . "\n";
+
+ $s .= $this->buttons;
+ $s .= '<ul id="pagehistory">' . "\n";
return $s;
}
@@ -420,11 +439,10 @@ class HistoryPager extends ReverseChronologicalPager {
'type' => 'submit',
'name' => $name,
'value' => '1',
- 'class' => "mw-history-$name-button",
+ 'class' => "historysubmit mw-history-$name-button",
),
- wfMsg( $msg )
+ $this->msg( $msg )->text()
) . "\n";
- $this->buttons .= $element;
return $element;
}
@@ -445,7 +463,7 @@ class HistoryPager extends ReverseChronologicalPager {
}
$this->counter++;
$s = $this->historyLine( $this->lastRow, $next,
- $this->title->getNotificationTimestamp(), $latest, $firstInList );
+ $this->getTitle()->getNotificationTimestamp( $this->getUser() ), $latest, $firstInList );
} else {
$s = '';
}
@@ -480,7 +498,7 @@ class HistoryPager extends ReverseChronologicalPager {
* @todo document some more, and maybe clean up the code (some params redundant?)
*
* @param $row Object: the database row corresponding to the previous line.
- * @param $next Mixed: the database row corresponding to the next line.
+ * @param $next Mixed: the database row corresponding to the next line. (chronologically previous)
* @param $notificationtimestamp
* @param $latest Boolean: whether this row corresponds to the page's latest revision.
* @param $firstInList Boolean: whether this row corresponds to the first displayed on this history page.
@@ -489,9 +507,15 @@ class HistoryPager extends ReverseChronologicalPager {
function historyLine( $row, $next, $notificationtimestamp = false,
$latest = false, $firstInList = false )
{
- global $wgUser, $wgLang;
$rev = new Revision( $row );
- $rev->setTitle( $this->title );
+ $rev->setTitle( $this->getTitle() );
+
+ if ( is_object( $next ) ) {
+ $prevRev = new Revision( $next );
+ $prevRev->setTitle( $this->getTitle() );
+ } else {
+ $prevRev = null;
+ }
$curlink = $this->curLink( $rev, $latest );
$lastlink = $this->lastLink( $rev, $next );
@@ -507,11 +531,12 @@ class HistoryPager extends ReverseChronologicalPager {
$classes = array();
$del = '';
+ $user = $this->getUser();
// Show checkboxes for each revision
- if ( $wgUser->isAllowed( 'deleterevision' ) ) {
+ if ( $user->isAllowed( 'deleterevision' ) ) {
$this->preventClickjacking();
// If revision was hidden from sysops, disable the checkbox
- if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
$del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) );
// Otherwise, enable the checkbox...
} else {
@@ -519,15 +544,15 @@ class HistoryPager extends ReverseChronologicalPager {
array( 'name' => 'ids[' . $rev->getId() . ']' ) );
}
// User can only view deleted revisions...
- } elseif ( $rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) {
+ } elseif ( $rev->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) {
// If revision was hidden from sysops, disable the link
- if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
- $cdel = $this->getSkin()->revDeleteLinkDisabled( false );
+ if ( !$rev->userCan( Revision::DELETED_RESTRICTED, $user ) ) {
+ $cdel = Linker::revDeleteLinkDisabled( false );
// Otherwise, show the link...
} else {
$query = array( 'type' => 'revision',
- 'target' => $this->title->getPrefixedDbkey(), 'ids' => $rev->getId() );
- $del .= $this->getSkin()->revDeleteLink( $query,
+ 'target' => $this->getTitle()->getPrefixedDbkey(), 'ids' => $rev->getId() );
+ $del .= Linker::revDeleteLink( $query,
$rev->isDeleted( Revision::DELETED_RESTRICTED ), false );
}
}
@@ -535,63 +560,65 @@ class HistoryPager extends ReverseChronologicalPager {
$s .= " $del ";
}
- $dirmark = $wgLang->getDirMark();
+ $lang = $this->getLanguage();
+ $dirmark = $lang->getDirMark();
$s .= " $link";
$s .= $dirmark;
$s .= " <span class='history-user'>" .
- $this->getSkin()->revUserTools( $rev, true ) . "</span>";
+ Linker::revUserTools( $rev, true ) . "</span>";
$s .= $dirmark;
if ( $rev->isMinor() ) {
$s .= ' ' . ChangesList::flag( 'minor' );
}
- if ( !is_null( $size = $rev->getSize() ) && !$rev->isDeleted( Revision::DELETED_TEXT ) ) {
- $s .= ' ' . $this->getSkin()->formatRevisionSize( $size );
- }
+ # Size is always public data
+ $prevSize = $prevRev ? $prevRev->getSize() : 0;
+ $sDiff = ChangesList::showCharacterDifference( $prevSize, $rev->getSize() );
+ $s .= ' . . ' . $sDiff . ' . . ';
- $s .= $this->getSkin()->revComment( $rev, false, true );
+ $s .= Linker::revComment( $rev, false, true );
if ( $notificationtimestamp && ( $row->rev_timestamp >= $notificationtimestamp ) ) {
- $s .= ' <span class="updatedmarker">' . wfMsgHtml( 'updatedmarker' ) . '</span>';
+ $s .= ' <span class="updatedmarker">' . $this->msg( 'updatedmarker' )->escaped() . '</span>';
}
$tools = array();
# Rollback and undo links
- if ( !is_null( $next ) && is_object( $next ) ) {
- if ( $latest && $this->title->userCan( 'rollback' ) && $this->title->userCan( 'edit' ) ) {
+ if ( $prevRev &&
+ !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) ) )
+ {
+ if ( $latest && !count( $this->getTitle()->getUserPermissionsErrors( 'rollback', $this->getUser() ) ) ) {
$this->preventClickjacking();
$tools[] = '<span class="mw-rollback-link">' .
- $this->getSkin()->buildRollbackLink( $rev ) . '</span>';
+ Linker::buildRollbackLink( $rev ) . '</span>';
}
- if ( $this->title->quickUserCan( 'edit' )
- && !$rev->isDeleted( Revision::DELETED_TEXT )
- && !$next->rev_deleted & Revision::DELETED_TEXT )
+ if ( !$rev->isDeleted( Revision::DELETED_TEXT )
+ && !$prevRev->isDeleted( Revision::DELETED_TEXT ) )
{
# Create undo tooltip for the first (=latest) line only
$undoTooltip = $latest
- ? array( 'title' => wfMsg( 'tooltip-undo' ) )
+ ? array( 'title' => $this->msg( 'tooltip-undo' )->text() )
: array();
- $undolink = $this->getSkin()->link(
- $this->title,
- wfMsgHtml( 'editundo' ),
+ $undolink = Linker::linkKnown(
+ $this->getTitle(),
+ $this->msg( 'editundo' )->escaped(),
$undoTooltip,
array(
- 'action' => 'edit',
- 'undoafter' => $next->rev_id,
- 'undo' => $rev->getId()
- ),
- array( 'known', 'noclasses' )
+ 'action' => 'edit',
+ 'undoafter' => $prevRev->getId(),
+ 'undo' => $rev->getId()
+ )
);
$tools[] = "<span class=\"mw-history-undo\">{$undolink}</span>";
}
}
if ( $tools ) {
- $s .= ' (' . $wgLang->pipeList( $tools ) . ')';
+ $s .= ' (' . $lang->pipeList( $tools ) . ')';
}
# Tags
@@ -616,16 +643,14 @@ class HistoryPager extends ReverseChronologicalPager {
* @return String
*/
function revLink( $rev ) {
- global $wgLang;
- $date = $wgLang->timeanddate( wfTimestamp( TS_MW, $rev->getTimestamp() ), true );
+ $date = $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $this->getUser() );
$date = htmlspecialchars( $date );
- if ( $rev->userCan( Revision::DELETED_TEXT ) ) {
- $link = $this->getSkin()->link(
- $this->title,
+ if ( $rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
+ $link = Linker::linkKnown(
+ $this->getTitle(),
$date,
array(),
- array( 'oldid' => $rev->getId() ),
- array( 'known', 'noclasses' )
+ array( 'oldid' => $rev->getId() )
);
} else {
$link = $date;
@@ -645,18 +670,17 @@ class HistoryPager extends ReverseChronologicalPager {
*/
function curLink( $rev, $latest ) {
$cur = $this->historyPage->message['cur'];
- if ( $latest || !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ if ( $latest || !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
return $cur;
} else {
- return $this->getSkin()->link(
- $this->title,
+ return Linker::linkKnown(
+ $this->getTitle(),
$cur,
array(),
array(
- 'diff' => $this->title->getLatestRevID(),
+ 'diff' => $this->getWikiPage()->getLatest(),
'oldid' => $rev->getId()
- ),
- array( 'known', 'noclasses' )
+ )
);
}
}
@@ -677,30 +701,28 @@ class HistoryPager extends ReverseChronologicalPager {
return $last;
} elseif ( $next === 'unknown' ) {
# Next row probably exists but is unknown, use an oldid=prev link
- return $this->getSkin()->link(
- $this->title,
+ return Linker::linkKnown(
+ $this->getTitle(),
$last,
array(),
array(
'diff' => $prevRev->getId(),
'oldid' => 'prev'
- ),
- array( 'known', 'noclasses' )
+ )
);
- } elseif ( !$prevRev->userCan( Revision::DELETED_TEXT )
- || !$nextRev->userCan( Revision::DELETED_TEXT ) )
+ } elseif ( !$prevRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+ || !$nextRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) )
{
return $last;
} else {
- return $this->getSkin()->link(
- $this->title,
+ return Linker::linkKnown(
+ $this->getTitle(),
$last,
array(),
array(
'diff' => $prevRev->getId(),
'oldid' => $next->rev_id
- ),
- array( 'known', 'noclasses' )
+ )
);
}
}
@@ -728,7 +750,7 @@ class HistoryPager extends ReverseChronologicalPager {
$checkmark = array( 'checked' => 'checked' );
} else {
# Check visibility of old revisions
- if ( !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
$radio['disabled'] = 'disabled';
$checkmark = array(); // We will check the next possible one
} elseif ( !$this->oldIdChecked ) {
@@ -769,7 +791,14 @@ class HistoryPager extends ReverseChronologicalPager {
}
/**
- * Backwards-compatibility aliases
+ * Backwards-compatibility alias
*/
-class PageHistory extends HistoryPage {}
-class PageHistoryPager extends HistoryPager {}
+class HistoryPage extends HistoryAction {
+ public function __construct( Page $article ) { # Just to make it public
+ parent::__construct( $article );
+ }
+
+ public function history() {
+ $this->onView();
+ }
+}
diff --git a/includes/actions/InfoAction.php b/includes/actions/InfoAction.php
index b0b5f259..70edabce 100644
--- a/includes/actions/InfoAction.php
+++ b/includes/actions/InfoAction.php
@@ -29,10 +29,6 @@ class InfoAction extends FormlessAction {
return 'info';
}
- public function getRestriction() {
- return 'read';
- }
-
protected function getDescription() {
return '';
}
@@ -46,7 +42,7 @@ class InfoAction extends FormlessAction {
}
protected function getPageTitle() {
- return wfMsg( 'pageinfo-title', $this->getTitle()->getSubjectPage()->getPrefixedText() );
+ return $this->msg( 'pageinfo-title', $this->getTitle()->getSubjectPage()->getPrefixedText() )->text();
}
public function onView() {
@@ -60,44 +56,44 @@ class InfoAction extends FormlessAction {
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::element( 'th', array(), $this->msg( 'pageinfo-subjectpage' )->text() ) .
+ Html::element( 'th', array(), $this->msg( 'pageinfo-talkpage' )->text() )
) .
Html::rawElement( 'tr', array(),
- Html::element( 'th', array( 'colspan' => 3 ), wfMsg( 'pageinfo-header-edits' ) )
+ Html::element( 'th', array( 'colspan' => 3 ), $this->msg( 'pageinfo-header-edits' )->text() )
) .
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::element( 'td', array(), $this->msg( 'pageinfo-edits' )->text() ) .
+ Html::element( 'td', array(), $this->getLanguage()->formatNum( $pageInfo['edits'] ) ) .
+ Html::element( 'td', array(), $this->getLanguage()->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'] ) )
+ Html::element( 'td', array(), $this->msg( 'pageinfo-authors' )->text() ) .
+ Html::element( 'td', array(), $this->getLanguage()->formatNum( $pageInfo['authors'] ) ) .
+ Html::element( 'td', array(), $this->getLanguage()->formatNum( $talkInfo['authors'] ) )
) .
( !$this->getUser()->isAllowed( 'unwatchedpages' ) ? '' :
Html::rawElement( 'tr', array(),
- Html::element( 'th', array( 'colspan' => 3 ), wfMsg( 'pageinfo-header-watchlist' ) )
+ Html::element( 'th', array( 'colspan' => 3 ), $this->msg( 'pageinfo-header-watchlist' )->text() )
) .
Html::rawElement( 'tr', array(),
- Html::element( 'td', array(), wfMsg( 'pageinfo-watchers' ) ) .
- Html::element( 'td', array( 'colspan' => 2 ), $this->getLang()->formatNum( $pageInfo['watchers'] ) )
+ Html::element( 'td', array(), $this->msg( 'pageinfo-watchers' )->text() ) .
+ Html::element( 'td', array( 'colspan' => 2 ), $this->getLanguage()->formatNum( $pageInfo['watchers'] ) )
)
).
( $wgDisableCounters ? '' :
Html::rawElement( 'tr', array(),
- Html::element( 'th', array( 'colspan' => 3 ), wfMsg( 'pageinfo-header-views' ) )
+ Html::element( 'th', array( 'colspan' => 3 ), $this->msg( 'pageinfo-header-views' )->text() )
) .
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::element( 'td', array(), $this->msg( 'pageinfo-views' )->text() ) .
+ Html::element( 'td', array(), $this->getLanguage()->formatNum( $pageInfo['views'] ) ) .
+ Html::element( 'td', array(), $this->getLanguage()->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 ) ) )
+ Html::element( 'td', array(), $this->msg( 'pageinfo-viewsperedit' )->text() ) .
+ Html::element( 'td', array(), $this->getLanguage()->formatNum( sprintf( '%.2f', $pageInfo['edits'] ? $pageInfo['views'] / $pageInfo['edits'] : 0 ) ) ) .
+ Html::element( 'td', array(), $this->getLanguage()->formatNum( sprintf( '%.2f', $talkInfo['edits'] ? $talkInfo['views'] / $talkInfo['edits'] : 0 ) ) )
)
)
);
diff --git a/includes/actions/MarkpatrolledAction.php b/includes/actions/MarkpatrolledAction.php
index a5d76627..ae9223f4 100644
--- a/includes/actions/MarkpatrolledAction.php
+++ b/includes/actions/MarkpatrolledAction.php
@@ -28,30 +28,25 @@ class MarkpatrolledAction extends FormlessAction {
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' ) );
+ $request = $this->getRequest();
+ $rcId = $request->getInt( 'rcid' );
+ $rc = RecentChange::newFromId( $rcId );
if ( is_null( $rc ) ) {
throw new ErrorPageError( 'markedaspatrollederror', 'markedaspatrollederrortext' );
}
- $errors = $rc->doMarkPatrolled( $this->getUser() );
+ $user = $this->getUser();
+ if ( !$user->matchEditToken( $request->getVal( 'token' ), $rcId ) ) {
+ throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
+ }
+
+ $errors = $rc->doMarkPatrolled( $user );
if ( in_array( array( 'rcpatroldisabled' ), $errors ) ) {
throw new ErrorPageError( 'rcpatroldisabled', 'rcpatroldisabledtext' );
@@ -67,19 +62,18 @@ class MarkpatrolledAction extends FormlessAction {
$return = SpecialPage::getTitleFor( $returnto );
if ( in_array( array( 'markedaspatrollederror-noautopatrol' ), $errors ) ) {
- $this->getOutput()->setPageTitle( wfMsg( 'markedaspatrollederror' ) );
+ $this->getOutput()->setPageTitle( $this->msg( 'markedaspatrollederror' ) );
$this->getOutput()->addWikiMsg( 'markedaspatrollederror-noautopatrol' );
$this->getOutput()->returnToMain( null, $return );
return;
}
- if ( !empty( $errors ) ) {
- $this->getOutput()->showPermissionsErrorPage( $errors );
- return;
+ if ( count( $errors ) ) {
+ throw new PermissionsError( 'patrol', $errors );
}
# Inform the user
- $this->getOutput()->setPageTitle( wfMsg( 'markedaspatrolled' ) );
+ $this->getOutput()->setPageTitle( $this->msg( 'markedaspatrolled' ) );
$this->getOutput()->addWikiMsg( 'markedaspatrolledtext', $rc->getTitle()->getPrefixedText() );
$this->getOutput()->returnToMain( null, $return );
}
diff --git a/includes/actions/ProtectAction.php b/includes/actions/ProtectAction.php
new file mode 100644
index 00000000..f053ede7
--- /dev/null
+++ b/includes/actions/ProtectAction.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * action=protect handler
+ *
+ * Copyright © 2012 Timo Tijhof
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ * @author Timo Tijhof
+ */
+
+class ProtectAction extends FormlessAction {
+
+ public function getName() {
+ return 'protect';
+ }
+
+ public function onView(){
+ return null;
+ }
+
+ public function show(){
+
+ $this->page->protect();
+
+ }
+
+}
+
+class UnprotectAction extends ProtectAction {
+
+ public function getName() {
+ return 'unprotect';
+ }
+
+ public function show(){
+
+ $this->page->unprotect();
+
+ }
+
+}
diff --git a/includes/actions/PurgeAction.php b/includes/actions/PurgeAction.php
index 29cbf3ae..21a6d904 100644
--- a/includes/actions/PurgeAction.php
+++ b/includes/actions/PurgeAction.php
@@ -31,10 +31,6 @@ class PurgeAction extends FormAction {
return 'purge';
}
- public function getRestriction() {
- return null;
- }
-
public function requiresUnblock() {
return false;
}
@@ -52,8 +48,7 @@ class PurgeAction extends FormAction {
}
public function onSubmit( $data ) {
- $this->page->doPurge();
- return true;
+ return $this->page->doPurge();
}
/**
@@ -71,8 +66,9 @@ class PurgeAction extends FormAction {
$this->getRequest()->getQueryValues(),
array( 'title' => null, 'action' => null )
) );
- $this->onSubmit( array() );
- $this->onSuccess();
+ if( $this->onSubmit( array() ) ) {
+ $this->onSuccess();
+ }
} else {
$this->redirectParams = $this->getRequest()->getVal( 'redirectparams', '' );
$form = $this->getForm();
diff --git a/includes/actions/RawAction.php b/includes/actions/RawAction.php
new file mode 100644
index 00000000..e4c6b3e0
--- /dev/null
+++ b/includes/actions/RawAction.php
@@ -0,0 +1,239 @@
+<?php
+/**
+ * Raw page text accessor
+ *
+ * Copyright © 2004 Gabriel Wicke <wicke@wikidev.net>
+ * http://wikidev.net/
+ *
+ * Based on HistoryPage and SpecialExport
+ *
+ * License: GPL (http://www.gnu.org/copyleft/gpl.html)
+ *
+ * @author Gabriel Wicke <wicke@wikidev.net>
+ * @file
+ */
+
+/**
+ * A simple method to retrieve the plain source of an article,
+ * using "action=raw" in the GET request string.
+ */
+class RawAction extends FormlessAction {
+ private $mGen;
+
+ public function getName() {
+ return 'raw';
+ }
+
+ public function requiresWrite() {
+ return false;
+ }
+
+ public function requiresUnblock() {
+ return false;
+ }
+
+ function onView() {
+ global $wgGroupPermissions, $wgSquidMaxage, $wgForcedRawSMaxage, $wgJsMimeType;
+
+ $this->getOutput()->disable();
+ $request = $this->getRequest();
+
+ if ( !$request->checkUrlExtension() ) {
+ return;
+ }
+
+ if ( $this->getOutput()->checkLastModified( $this->page->getTouched() ) ) {
+ return; // Client cache fresh and headers sent, nothing more to do.
+ }
+
+ # special case for 'generated' raw things: user css/js
+ # This is deprecated and will only return empty content
+ $gen = $request->getVal( 'gen' );
+ $smaxage = $request->getIntOrNull( 'smaxage' );
+
+ if ( $gen == 'css' || $gen == 'js' ) {
+ $this->mGen = $gen;
+ if ( $smaxage === null ) {
+ $smaxage = $wgSquidMaxage;
+ }
+ } else {
+ $this->mGen = false;
+ }
+
+ $contentType = $this->getContentType();
+
+ # Force caching for CSS and JS raw content, default: 5 minutes
+ if ( $smaxage === null ) {
+ if ( $contentType == 'text/css' || $contentType == $wgJsMimeType ) {
+ $smaxage = intval( $wgForcedRawSMaxage );
+ } else {
+ $smaxage = 0;
+ }
+ }
+
+ $maxage = $request->getInt( 'maxage', $wgSquidMaxage );
+
+ $response = $request->response();
+
+ $response->header( 'Content-type: ' . $contentType . '; charset=UTF-8' );
+ # Output may contain user-specific data;
+ # vary generated content for open sessions on private wikis
+ $privateCache = !$wgGroupPermissions['*']['read'] && ( $smaxage == 0 || session_id() != '' );
+ # allow the client to cache this for 24 hours
+ $mode = $privateCache ? 'private' : 'public';
+ $response->header( 'Cache-Control: ' . $mode . ', s-maxage=' . $smaxage . ', max-age=' . $maxage );
+
+ $text = $this->getRawText();
+
+ if ( $text === false && $contentType == 'text/x-wiki' ) {
+ # Don't return a 404 response for CSS or JavaScript;
+ # 404s aren't generally cached and it would create
+ # extra hits when user CSS/JS are on and the user doesn't
+ # have the pages.
+ $response->header( 'HTTP/1.x 404 Not Found' );
+ }
+
+ if ( !wfRunHooks( 'RawPageViewBeforeOutput', array( &$this, &$text ) ) ) {
+ wfDebug( __METHOD__ . ": RawPageViewBeforeOutput hook broke raw page output.\n" );
+ }
+
+ echo $text;
+ }
+
+ /**
+ * Get the text that should be returned, or false if the page or revision
+ * was not found.
+ *
+ * @return String|Bool
+ */
+ public function getRawText() {
+ global $wgParser;
+
+ # No longer used
+ if( $this->mGen ) {
+ return '';
+ }
+
+ $text = false;
+ $title = $this->getTitle();
+ $request = $this->getRequest();
+
+ // If it's a MediaWiki message we can just hit the message cache
+ if ( $request->getBool( 'usemsgcache' ) && $title->getNamespace() == NS_MEDIAWIKI ) {
+ $key = $title->getDBkey();
+ $msg = wfMessage( $key )->inContentLanguage();
+ # If the message doesn't exist, return a blank
+ $text = !$msg->exists() ? '' : $msg->plain();
+ } else {
+ // Get it from the DB
+ $rev = Revision::newFromTitle( $title, $this->getOldId() );
+ if ( $rev ) {
+ $lastmod = wfTimestamp( TS_RFC2822, $rev->getTimestamp() );
+ $request->response()->header( "Last-modified: $lastmod" );
+
+ // Public-only due to cache headers
+ $text = $rev->getText();
+ $section = $request->getIntOrNull( 'section' );
+ if ( $section !== null ) {
+ $text = $wgParser->getSection( $text, $section );
+ }
+ }
+ }
+
+ if ( $text !== false && $text !== '' && $request->getVal( 'templates' ) === 'expand' ) {
+ $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
+ }
+
+ return $text;
+ }
+
+ /**
+ * Get the ID of the revision that should used to get the text.
+ *
+ * @return Integer
+ */
+ public function getOldId() {
+ $oldid = $this->getRequest()->getInt( 'oldid' );
+ switch ( $this->getRequest()->getText( 'direction' ) ) {
+ case 'next':
+ # output next revision, or nothing if there isn't one
+ if( $oldid ) {
+ $oldid = $this->getTitle()->getNextRevisionId( $oldid );
+ }
+ $oldid = $oldid ? $oldid : -1;
+ break;
+ case 'prev':
+ # output previous revision, or nothing if there isn't one
+ if( !$oldid ) {
+ # get the current revision so we can get the penultimate one
+ $oldid = $this->page->getLatest();
+ }
+ $prev = $this->getTitle()->getPreviousRevisionId( $oldid );
+ $oldid = $prev ? $prev : -1 ;
+ break;
+ case 'cur':
+ $oldid = 0;
+ break;
+ }
+ return $oldid;
+ }
+
+ /**
+ * Get the content type to use for the response
+ *
+ * @return String
+ */
+ public function getContentType() {
+ global $wgJsMimeType;
+
+ $ctype = $this->getRequest()->getVal( 'ctype' );
+
+ if ( $ctype == '' ) {
+ $gen = $this->getRequest()->getVal( 'gen' );
+ if ( $gen == 'js' ) {
+ $ctype = $wgJsMimeType;
+ } elseif ( $gen == 'css' ) {
+ $ctype = 'text/css';
+ }
+ }
+
+ $allowedCTypes = array( 'text/x-wiki', $wgJsMimeType, 'text/css', 'application/x-zope-edit' );
+ if ( $ctype == '' || !in_array( $ctype, $allowedCTypes ) ) {
+ $ctype = 'text/x-wiki';
+ }
+
+ return $ctype;
+ }
+}
+
+/**
+ * Backward compatibility for extensions
+ *
+ * @deprecated in 1.19
+ */
+class RawPage extends RawAction {
+ public $mOldId;
+
+ function __construct( Page $page, $request = false ) {
+ wfDeprecated( __CLASS__, '1.19' );
+ parent::__construct( $page );
+
+ if ( $request !== false ) {
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setRequest( $request );
+ $this->context = $context;
+ }
+ }
+
+ public function view() {
+ $this->onView();
+ }
+
+ public function getOldId() {
+ # Some extensions like to set $mOldId
+ if ( $this->mOldId !== null ) {
+ return $this->mOldId;
+ }
+ return parent::getOldId();
+ }
+}
diff --git a/includes/actions/RenderAction.php b/includes/actions/RenderAction.php
new file mode 100644
index 00000000..80af79cc
--- /dev/null
+++ b/includes/actions/RenderAction.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Handle action=render
+ *
+ * Copyright © 2012 Timo Tijhof
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ * @author Timo Tijhof
+ */
+
+class RenderAction extends FormlessAction {
+
+ public function getName() {
+ return 'render';
+ }
+
+ public function onView(){
+ return null;
+ }
+
+ public function show(){
+
+ $this->page->render();
+
+ }
+
+}
diff --git a/includes/actions/RevertAction.php b/includes/actions/RevertAction.php
index bcb8cd8b..f9497f4b 100644
--- a/includes/actions/RevertAction.php
+++ b/includes/actions/RevertAction.php
@@ -17,7 +17,7 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* @file
- * @ingroup Action
+ * @ingroup Actions
* @ingroup Media
* @author Alexandre Emsenhuber
* @author Rob Church <robchur@gmail.com>
@@ -26,7 +26,7 @@
/**
* Dummy class for pages not in NS_FILE
*
- * @ingroup Action
+ * @ingroup Actions
*/
class RevertAction extends Action {
@@ -34,10 +34,6 @@ class RevertAction extends Action {
return 'revert';
}
- public function getRestriction() {
- return 'read';
- }
-
public function show() {
$this->getOutput()->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
}
@@ -48,7 +44,7 @@ class RevertAction extends Action {
/**
* Class for pages in NS_FILE
*
- * @ingroup Action
+ * @ingroup Actions
*/
class RevertFileAction extends FormAction {
protected $oldFile;
@@ -95,7 +91,7 @@ class RevertFileAction extends FormAction {
'vertical-label' => true,
'raw' => true,
'default' => wfMsgExt( 'filerevert-intro', 'parse', $this->getTitle()->getText(),
- $this->getLang()->date( $timestamp, true ), $this->getLang()->time( $timestamp, true ),
+ $this->getLanguage()->date( $timestamp, true ), $this->getLanguage()->time( $timestamp, true ),
wfExpandUrl( $this->page->getFile()->getArchiveUrl( $this->getRequest()->getText( 'oldimage' ) ),
PROTO_CURRENT
) )
@@ -119,8 +115,8 @@ class RevertFileAction extends FormAction {
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 ),
+ $this->getLanguage()->date( $timestamp, true ),
+ $this->getLanguage()->time( $timestamp, true ),
wfExpandUrl( $this->page->getFile()->getArchiveUrl( $this->getRequest()->getText( 'oldimage' ) ),
PROTO_CURRENT
) ) );
@@ -132,9 +128,7 @@ class RevertFileAction extends FormAction {
}
protected function getDescription() {
- return wfMsg(
- 'filerevert-backlink',
- Linker::linkKnown( $this->getTitle() )
- );
+ $this->getOutput()->addBacklinkSubtitle( $this->getTitle() );
+ return '';
}
}
diff --git a/includes/actions/RevisiondeleteAction.php b/includes/actions/RevisiondeleteAction.php
index 2ac03d18..f07e493d 100644
--- a/includes/actions/RevisiondeleteAction.php
+++ b/includes/actions/RevisiondeleteAction.php
@@ -29,10 +29,6 @@ class RevisiondeleteAction extends FormlessAction {
return 'revisiondelete';
}
- public function getRestriction() {
- return null;
- }
-
public function requiresUnblock() {
return false;
}
diff --git a/includes/actions/RollbackAction.php b/includes/actions/RollbackAction.php
index 9036ebf5..ebb34c78 100644
--- a/includes/actions/RollbackAction.php
+++ b/includes/actions/RollbackAction.php
@@ -17,13 +17,13 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* @file
- * @ingroup Action
+ * @ingroup Actions
*/
/**
* User interface for the rollback action
*
- * @ingroup Action
+ * @ingroup Actions
*/
class RollbackAction extends FormlessAction {
@@ -54,7 +54,7 @@ class RollbackAction extends FormlessAction {
}
if ( isset( $result[0][0] ) && ( $result[0][0] == 'alreadyrolled' || $result[0][0] == 'cantrollback' ) ) {
- $this->getOutput()->setPageTitle( wfMsg( 'rollbackfailed' ) );
+ $this->getOutput()->setPageTitle( $this->msg( 'rollbackfailed' ) );
$errArray = $result[0];
$errMsg = array_shift( $errArray );
$this->getOutput()->addWikiMsgArray( $errMsg, $errArray );
@@ -83,9 +83,7 @@ class RollbackAction extends FormlessAction {
$out [] = $error;
}
}
- $this->getOutput()->showPermissionsErrorPage( $out );
-
- return;
+ throw new PermissionsError( 'rollback', $out );
}
if ( $result == array( array( 'readonlytext' ) ) ) {
@@ -95,7 +93,7 @@ class RollbackAction extends FormlessAction {
$current = $details['current'];
$target = $details['target'];
$newId = $details['newid'];
- $this->getOutput()->setPageTitle( wfMsg( 'actioncomplete' ) );
+ $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
$this->getOutput()->setRobotPolicy( 'noindex,nofollow' );
if ( $current->getUserText() === '' ) {
@@ -111,7 +109,7 @@ class RollbackAction extends FormlessAction {
$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 = new DifferenceEngine( $this->getContext(), $current->getId(), $newId, false, true );
$de->showDiff( '', '' );
}
}
diff --git a/includes/actions/ViewAction.php b/includes/actions/ViewAction.php
new file mode 100644
index 00000000..4e37381b
--- /dev/null
+++ b/includes/actions/ViewAction.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * An action that views article content
+ *
+ * Copyright © 2012 Timo Tijhof
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ * @author Timo Tijhof
+ */
+
+class ViewAction extends FormlessAction {
+
+ public function getName() {
+ return 'view';
+ }
+
+ public function onView(){
+ return null;
+ }
+
+ public function show(){
+ global $wgSquidMaxage;
+
+ $this->getOutput()->setSquidMaxage( $wgSquidMaxage );
+ $this->page->view();
+ }
+
+}
diff --git a/includes/actions/WatchAction.php b/includes/actions/WatchAction.php
index 52e66754..63d9b151 100644
--- a/includes/actions/WatchAction.php
+++ b/includes/actions/WatchAction.php
@@ -26,16 +26,12 @@ class WatchAction extends FormAction {
return 'watch';
}
- public function getRestriction() {
- return 'read';
- }
-
public function requiresUnblock() {
return false;
}
protected function getDescription() {
- return wfMsg( 'addwatch' );
+ return wfMsgHtml( 'addwatch' );
}
/**
@@ -87,7 +83,7 @@ class WatchAction extends FormAction {
}
public static function doWatch( Title $title, User $user ) {
- $page = new Article( $title, 0 );
+ $page = WikiPage::factory( $title );
if ( wfRunHooks( 'WatchArticle', array( &$user, &$page ) ) ) {
$user->addWatch( $title );
@@ -97,7 +93,7 @@ class WatchAction extends FormAction {
}
public static function doUnwatch( Title $title, User $user ) {
- $page = new Article( $title, 0 );
+ $page = WikiPage::factory( $title );
if ( wfRunHooks( 'UnwatchArticle', array( &$user, &$page ) ) ) {
$user->removeWatch( $title );
@@ -110,7 +106,7 @@ class WatchAction extends FormAction {
* 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 User $user 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
@@ -123,14 +119,14 @@ class WatchAction extends FormAction {
// 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 );
+ return $user->getEditToken( $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 User $user 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
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php
index 9fe96199..a586f688 100644
--- a/includes/api/ApiBase.php
+++ b/includes/api/ApiBase.php
@@ -39,7 +39,7 @@
*
* @ingroup API
*/
-abstract class ApiBase {
+abstract class ApiBase extends ContextSource {
// These constants allow modules to specify exactly how to treat incoming parameters.
@@ -72,6 +72,10 @@ abstract class ApiBase {
$this->mMainModule = $mainModule;
$this->mModuleName = $moduleName;
$this->mModulePrefix = $modulePrefix;
+
+ if ( !$this->isMain() ) {
+ $this->setContext( $mainModule->getContext() );
+ }
}
/*****************************************************************************
@@ -179,16 +183,12 @@ abstract class ApiBase {
* The object will have the WebRequest and the User object set to the ones
* used in this instance.
*
- * @return RequestContext
+ * @deprecated since 1.19 use getContext to get the current context
+ * @return DerivativeContext
*/
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;
+ wfDeprecated( __METHOD__, '1.19' );
+ return new DerivativeContext( $this->getContext() );
}
/**
@@ -236,7 +236,7 @@ abstract class ApiBase {
public function makeHelpMsg() {
static $lnPrfx = "\n ";
- $msg = $this->getDescription();
+ $msg = $this->getFinalDescription();
if ( $msg !== false ) {
@@ -267,7 +267,30 @@ abstract class ApiBase {
$msg .= "Parameters:\n$paramsMsg";
}
- $msg .= $this->makeHelpArrayToString( $lnPrfx, "Example", $this->getExamples() );
+ $examples = $this->getExamples();
+ if ( $examples !== false && $examples !== '' ) {
+ if ( !is_array( $examples ) ) {
+ $examples = array(
+ $examples
+ );
+ }
+ $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n";
+ foreach( $examples as $k => $v ) {
+
+ if ( is_numeric( $k ) ) {
+ $msg .= " $v\n";
+ } else {
+ $v .= ":";
+ if ( is_array( $v ) ) {
+ $msgExample = implode( "\n", array_map( array( $this, 'indentExampleText' ), $v ) );
+ } else {
+ $msgExample = " $v";
+ }
+ $msg .= wordwrap( $msgExample, 100, "\n" ) . "\n $k\n";
+ }
+ }
+ }
+
$msg .= $this->makeHelpArrayToString( $lnPrfx, "Help page", $this->getHelpUrls() );
if ( $this->getMain()->getShowVersions() ) {
@@ -292,6 +315,14 @@ abstract class ApiBase {
}
/**
+ * @param $item string
+ * @return string
+ */
+ private function indentExampleText( $item ) {
+ return " " . $item;
+ }
+
+ /**
* @param $prefix string Text to split output items
* @param $title string What is being output
* @param $input string|array
@@ -341,20 +372,20 @@ abstract class ApiBase {
}
$deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] ) ?
- $paramSettings[self::PARAM_DEPRECATED] : false;
+ $paramSettings[self::PARAM_DEPRECATED] : false;
if ( $deprecated ) {
$desc = "DEPRECATED! $desc";
}
$required = isset( $paramSettings[self::PARAM_REQUIRED] ) ?
- $paramSettings[self::PARAM_REQUIRED] : false;
+ $paramSettings[self::PARAM_REQUIRED] : false;
if ( $required ) {
$desc .= $paramPrefix . "This parameter is required";
}
$type = isset( $paramSettings[self::PARAM_TYPE] ) ? $paramSettings[self::PARAM_TYPE] : null;
if ( isset( $type ) ) {
- if ( isset( $paramSettings[self::PARAM_ISMULTI] ) ) {
+ if ( isset( $paramSettings[self::PARAM_ISMULTI] ) && $paramSettings[self::PARAM_ISMULTI] ) {
$prompt = 'Values (separate with \'|\'): ';
} else {
$prompt = 'One value: ';
@@ -411,7 +442,7 @@ abstract class ApiBase {
if ( !$isArray
|| $isArray && count( $paramSettings[self::PARAM_TYPE] ) > self::LIMIT_SML1 ) {
$desc .= $paramPrefix . "Maximum number of values " .
- self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
+ self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)";
}
}
}
@@ -437,6 +468,7 @@ abstract class ApiBase {
* Callback for preg_replace_callback() call in makeHelpMsg().
* Replaces a source file name with a link to ViewVC
*
+ * @param $matches array
* @return string
*/
public function makeHelpMsg_callback( $matches ) {
@@ -464,8 +496,8 @@ abstract class ApiBase {
// returning the version string for ApiBase work
if ( $path ) {
return "{$matches[0]}\n https://svn.wikimedia.org/" .
- "viewvc/mediawiki/trunk/" . dirname( $path ) .
- "/{$matches[2]}";
+ "viewvc/mediawiki/trunk/" . dirname( $path ) .
+ "/{$matches[2]}";
}
return $matches[0];
}
@@ -510,6 +542,7 @@ abstract class ApiBase {
/**
* Get final list of parameters, after hooks have had a chance to
* tweak it as needed.
+ *
* @return array or false
*/
public function getFinalParams() {
@@ -519,8 +552,9 @@ abstract class ApiBase {
}
/**
- * Get final description, after hooks have had a chance to tweak it as
+ * Get final parameter descriptions, after hooks have had a chance to tweak it as
* needed.
+ *
* @return array
*/
public function getFinalParamDescription() {
@@ -530,6 +564,18 @@ abstract class ApiBase {
}
/**
+ * Get final module description, after hooks have had a chance to tweak it as
+ * needed.
+ *
+ * @return array
+ */
+ public function getFinalDescription() {
+ $desc = $this->getDescription();
+ wfRunHooks( 'APIGetDescription', array( &$this, &$desc ) );
+ return $desc;
+ }
+
+ /**
* This method mangles parameter name based on the prefix supplied to the constructor.
* Override this method to change parameter name during runtime
* @param $paramName string Parameter name
@@ -586,7 +632,7 @@ abstract class ApiBase {
array_shift( $required );
$intersection = array_intersect( array_keys( array_filter( $params,
- array( $this, "parameterNotEmpty" ) ) ), $required );
+ array( $this, "parameterNotEmpty" ) ) ), $required );
if ( count( $intersection ) > 1 ) {
$this->dieUsage( 'The parameters ' . implode( ', ', $intersection ) . ' can not be used together', 'invalidparammix' );
@@ -621,7 +667,7 @@ abstract class ApiBase {
array_shift( $required );
$intersection = array_intersect( array_keys( array_filter( $params,
- array( $this, "parameterNotEmpty" ) ) ), $required );
+ array( $this, "parameterNotEmpty" ) ) ), $required );
if ( count( $intersection ) > 1 ) {
$this->dieUsage( 'The parameters ' . implode( ', ', $intersection ) . ' can not be used together', 'invalidparammix' );
@@ -655,8 +701,11 @@ abstract class ApiBase {
/**
* @deprecated since 1.17 use MWNamespace::getValidNamespaces()
+ *
+ * @return array
*/
public static function getValidNamespaces() {
+ wfDeprecated( __METHOD__, '1.17' );
return MWNamespace::getValidNamespaces();
}
@@ -672,7 +721,6 @@ abstract class ApiBase {
$userWatching = $titleObj->userIsWatching();
- global $wgUser;
switch ( $watchlist ) {
case 'watch':
return true;
@@ -688,10 +736,10 @@ abstract class ApiBase {
# If no user option was passed, use watchdefault or watchcreation
if ( is_null( $userOption ) ) {
$userOption = $titleObj->exists()
- ? 'watchdefault' : 'watchcreations';
+ ? 'watchdefault' : 'watchcreations';
}
# Watch the article based on the user preference
- return (bool)$wgUser->getOption( $userOption );
+ return (bool)$this->getUser()->getOption( $userOption );
case 'nochange':
return $userWatching;
@@ -713,11 +761,11 @@ abstract class ApiBase {
return;
}
- global $wgUser;
+ $user = $this->getUser();
if ( $value ) {
- WatchAction::doWatch( $titleObj, $wgUser );
+ WatchAction::doWatch( $titleObj, $user );
} else {
- WatchAction::doUnwatch( $titleObj, $wgUser );
+ WatchAction::doUnwatch( $titleObj, $user );
}
}
@@ -765,9 +813,9 @@ abstract class ApiBase {
ApiBase::dieDebug( __METHOD__, "Boolean param $encParamName's default is set to '$default'" );
}
- $value = $this->getMain()->getRequest()->getCheck( $encParamName );
+ $value = $this->getRequest()->getCheck( $encParamName );
} else {
- $value = $this->getMain()->getRequest()->getVal( $encParamName, $default );
+ $value = $this->getRequest()->getVal( $encParamName, $default );
if ( isset( $value ) && $type == 'namespace' ) {
$type = MWNamespace::getValidNamespaces();
@@ -904,13 +952,18 @@ abstract class ApiBase {
// This is a bit awkward, but we want to avoid calling canApiHighLimits() because it unstubs $wgUser
$valuesList = explode( '|', $value, self::LIMIT_SML2 + 1 );
$sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits() ?
- self::LIMIT_SML2 : self::LIMIT_SML1;
+ self::LIMIT_SML2 : self::LIMIT_SML1;
if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
$this->setWarning( "Too many values supplied for parameter '$valueName': the limit is $sizeLimit" );
}
if ( !$allowMultiple && count( $valuesList ) != 1 ) {
+ // Bug 33482 - Allow entries with | in them for non-multiple values
+ if ( in_array( $value, $allowedValues ) ) {
+ return $value;
+ }
+
$possibleValues = is_array( $allowedValues ) ? "of '" . implode( "', '", $allowedValues ) . "'" : '';
$this->dieUsage( "Only one $possibleValues is allowed for parameter '$valueName'", "multival_$valueName" );
}
@@ -1037,17 +1090,17 @@ abstract class ApiBase {
*/
public static $messageMap = array(
// This one MUST be present, or dieUsageMsg() will recurse infinitely
- 'unknownerror' => array( 'code' => 'unknownerror', 'info' => "Unknown error: ``\$1''" ),
+ 'unknownerror' => array( 'code' => 'unknownerror', 'info' => "Unknown error: \"\$1\"" ),
'unknownerror-nocode' => array( 'code' => 'unknownerror', 'info' => 'Unknown error' ),
// Messages from Title::getUserPermissionsErrors()
'ns-specialprotected' => array( 'code' => 'unsupportednamespace', 'info' => "Pages in the Special namespace can't be edited" ),
'protectedinterface' => array( 'code' => 'protectednamespace-interface', 'info' => "You're not allowed to edit interface messages" ),
- 'namespaceprotected' => array( 'code' => 'protectednamespace', 'info' => "You're not allowed to edit pages in the ``\$1'' namespace" ),
+ 'namespaceprotected' => array( 'code' => 'protectednamespace', 'info' => "You're not allowed to edit pages in the \"\$1\" namespace" ),
'customcssprotected' => array( 'code' => 'customcssprotected', 'info' => "You're not allowed to edit custom CSS pages" ),
'customjsprotected' => array( 'code' => 'customjsprotected', 'info' => "You're not allowed to edit custom JavaScript pages" ),
'cascadeprotected' => array( 'code' => 'cascadeprotected', 'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page" ),
- 'protectedpagetext' => array( 'code' => 'protectedpage', 'info' => "The ``\$1'' right is required to edit this page" ),
+ 'protectedpagetext' => array( 'code' => 'protectedpage', 'info' => "The \"\$1\" right is required to edit this page" ),
'protect-cantedit' => array( 'code' => 'cantedit', 'info' => "You can't protect this page because you can't edit it" ),
'badaccess-group0' => array( 'code' => 'permissiondenied', 'info' => "Permission denied" ), // Generic permission denied message
'badaccess-groups' => array( 'code' => 'permissiondenied', 'info' => "Permission denied" ),
@@ -1066,7 +1119,7 @@ abstract class ApiBase {
'cantrollback' => array( 'code' => 'onlyauthor', 'info' => "The page you tried to rollback only has one author" ),
'readonlytext' => array( 'code' => 'readonly', 'info' => "The wiki is currently in read-only mode" ),
'sessionfailure' => array( 'code' => 'badtoken', 'info' => "Invalid token" ),
- 'cannotdelete' => array( 'code' => 'cantdelete', 'info' => "Couldn't delete ``\$1''. Maybe it was deleted already by someone else" ),
+ 'cannotdelete' => array( 'code' => 'cantdelete', 'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else" ),
'notanarticle' => array( 'code' => 'missingtitle', 'info' => "The page you requested doesn't exist" ),
'selfmove' => array( 'code' => 'selfmove', 'info' => "Can't move a page to itself" ),
'immobile_namespace' => array( 'code' => 'immobilenamespace', 'info' => "You tried to move pages from or to a namespace that is protected from moving" ),
@@ -1084,7 +1137,7 @@ abstract class ApiBase {
'badipaddress' => array( 'code' => 'invalidip', 'info' => "Invalid IP address specified" ),
'ipb_expiry_invalid' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time" ),
'ipb_already_blocked' => array( 'code' => 'alreadyblocked', 'info' => "The user you tried to block was already blocked" ),
- 'ipb_blocked_as_range' => array( 'code' => 'blockedasrange', 'info' => "IP address ``\$1'' was blocked as part of range ``\$2''. You can't unblock the IP invidually, but you can unblock the range as a whole." ),
+ 'ipb_blocked_as_range' => array( 'code' => 'blockedasrange', 'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP invidually, but you can unblock the range as a whole." ),
'ipb_cant_unblock' => array( 'code' => 'cantunblock', 'info' => "The block you specified was not found. It may have been unblocked already" ),
'mailnologin' => array( 'code' => 'cantsend', 'info' => "You are not logged in, you do not have a confirmed e-mail address, or you are not allowed to send e-mail to other users, so you cannot send e-mail" ),
'ipbblocked' => array( 'code' => 'ipbblocked', 'info' => 'You cannot block or unblock users while you are yourself blocked' ),
@@ -1098,9 +1151,9 @@ abstract class ApiBase {
'delete-toobig' => array( 'code' => 'bigdelete', 'info' => "You can't delete this page because it has more than \$1 revisions" ),
'movenotallowedfile' => array( 'code' => 'cantmovefile', 'info' => "You don't have permission to move files" ),
'userrights-no-interwiki' => array( 'code' => 'nointerwikiuserrights', 'info' => "You don't have permission to change user rights on other wikis" ),
- 'userrights-nodatabase' => array( 'code' => 'nosuchdatabase', 'info' => "Database ``\$1'' does not exist or is not local" ),
- 'nouserspecified' => array( 'code' => 'invaliduser', 'info' => "Invalid username ``\$1''" ),
- 'noname' => array( 'code' => 'invaliduser', 'info' => "Invalid username ``\$1''" ),
+ 'userrights-nodatabase' => array( 'code' => 'nosuchdatabase', 'info' => "Database \"\$1\" does not exist or is not local" ),
+ 'nouserspecified' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
+ 'noname' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
'summaryrequired' => array( 'code' => 'summaryrequired', 'info' => 'Summary required' ),
// API-specific messages
@@ -1108,13 +1161,13 @@ abstract class ApiBase {
'writedisabled' => array( 'code' => 'noapiwrite', 'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file" ),
'writerequired' => array( 'code' => 'writeapidenied', 'info' => "You're not allowed to edit this wiki through the API" ),
'missingparam' => array( 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ),
- 'invalidtitle' => array( 'code' => 'invalidtitle', 'info' => "Bad title ``\$1''" ),
+ 'invalidtitle' => array( 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ),
'nosuchpageid' => array( 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ),
'nosuchrevid' => array( 'code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1" ),
- 'nosuchuser' => array( 'code' => 'nosuchuser', 'info' => "User ``\$1'' doesn't exist" ),
- 'invaliduser' => array( 'code' => 'invaliduser', 'info' => "Invalid username ``\$1''" ),
- 'invalidexpiry' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time ``\$1''" ),
- 'pastexpiry' => array( 'code' => 'pastexpiry', 'info' => "Expiry time ``\$1'' is in the past" ),
+ 'nosuchuser' => array( 'code' => 'nosuchuser', 'info' => "User \"\$1\" doesn't exist" ),
+ 'invaliduser' => array( 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ),
+ 'invalidexpiry' => array( 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ),
+ 'pastexpiry' => array( 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ),
'create-titleexists' => array( 'code' => 'create-titleexists', 'info' => "Existing titles can't be protected with 'create'" ),
'missingtitle-createonly' => array( 'code' => 'missingtitle-createonly', 'info' => "Missing titles can only be protected with 'create'" ),
'cantblock' => array( 'code' => 'cantblock', 'info' => "You don't have permission to block users" ),
@@ -1127,9 +1180,9 @@ abstract class ApiBase {
'permdenied-undelete' => array( 'code' => 'permissiondenied', 'info' => "You don't have permission to restore deleted revisions" ),
'createonly-exists' => array( 'code' => 'articleexists', 'info' => "The article you tried to create has been created already" ),
'nocreate-missing' => array( 'code' => 'missingtitle', 'info' => "The article you tried to edit doesn't exist" ),
- 'nosuchrcid' => array( 'code' => 'nosuchrcid', 'info' => "There is no change with rcid ``\$1''" ),
- 'protect-invalidaction' => array( 'code' => 'protect-invalidaction', 'info' => "Invalid protection type ``\$1''" ),
- 'protect-invalidlevel' => array( 'code' => 'protect-invalidlevel', 'info' => "Invalid protection level ``\$1''" ),
+ 'nosuchrcid' => array( 'code' => 'nosuchrcid', 'info' => "There is no change with rcid \"\$1\"" ),
+ 'protect-invalidaction' => array( 'code' => 'protect-invalidaction', 'info' => "Invalid protection type \"\$1\"" ),
+ 'protect-invalidlevel' => array( 'code' => 'protect-invalidlevel', 'info' => "Invalid protection level \"\$1\"" ),
'toofewexpiries' => array( 'code' => 'toofewexpiries', 'info' => "\$1 expiry timestamps were provided where \$2 were needed" ),
'cantimport' => array( 'code' => 'cantimport', 'info' => "You don't have permission to import pages" ),
'cantimport-upload' => array( 'code' => 'cantimport-upload', 'info' => "You don't have permission to import uploaded pages" ),
@@ -1141,17 +1194,19 @@ abstract class ApiBase {
'importcantopen' => array( 'code' => 'cantopenfile', 'info' => "Couldn't open the uploaded file" ),
'import-noarticle' => array( 'code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified' ),
'importbadinterwiki' => array( 'code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified' ),
- 'import-unknownerror' => array( 'code' => 'import-unknownerror', 'info' => "Unknown error on import: ``\$1''" ),
+ 'import-unknownerror' => array( 'code' => 'import-unknownerror', 'info' => "Unknown error on import: \"\$1\"" ),
'cantoverwrite-sharedfile' => array( 'code' => 'cantoverwrite-sharedfile', 'info' => 'The target file exists on a shared repository and you do not have permission to override it' ),
'sharedfile-exists' => array( 'code' => 'fileexists-sharedrepo-perm', 'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.' ),
'mustbeposted' => array( 'code' => 'mustbeposted', 'info' => "The \$1 module requires a POST request" ),
'show' => array( 'code' => 'show', 'info' => 'Incorrect parameter - mutually exclusive values may not be supplied' ),
'specialpage-cantexecute' => array( 'code' => 'specialpage-cantexecute', 'info' => "You don't have permission to view the results of this special page" ),
+ 'invalidoldimage' => array( 'code' => 'invalidoldimage', 'info' => 'The oldimage parameter has invalid format' ),
+ 'nodeleteablefile' => array( 'code' => 'nodeleteablefile', 'info' => 'No such old version of the file' ),
// ApiEditPage messages
'noimageredirect-anon' => array( 'code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects" ),
'noimageredirect-logged' => array( 'code' => 'noimageredirect', 'info' => "You don't have permission to create image redirects" ),
- 'spamdetected' => array( 'code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: ``\$1''" ),
+ 'spamdetected' => array( 'code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: \"\$1\"" ),
'filtered' => array( 'code' => 'filtered', 'info' => "The filter callback function refused your edit" ),
'contenttoobig' => array( 'code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes" ),
'noedit-anon' => array( 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ),
@@ -1162,9 +1217,9 @@ abstract class ApiBase {
'hashcheckfailed' => array( 'code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect" ),
'missingtext' => array( 'code' => 'notext', 'info' => "One of the text, appendtext, prependtext and undo parameters must be set" ),
'emptynewsection' => array( 'code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.' ),
- 'revwrongpage' => array( 'code' => 'revwrongpage', 'info' => "r\$1 is not a revision of ``\$2''" ),
+ '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" ),
@@ -1178,8 +1233,11 @@ abstract class ApiBase {
'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' ),
+ 'filename-toolong' => array( 'code' => 'filename-toolong', 'info' => 'The filename is too long' ),
'illegal-filename' => array( 'code' => 'illegal-filename', 'info' => 'The filename is not allowed' ),
'filetype-missing' => array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ),
+
+ 'mustbeloggedin' => array( 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' )
);
/**
@@ -1211,6 +1269,7 @@ abstract class ApiBase {
* @return array('code' => code, 'info' => info)
*/
public function parseMsg( $error ) {
+ $error = (array)$error; // It seems strings sometimes make their way in here
$key = array_shift( $error );
// Check whether the error array was nested
@@ -1222,8 +1281,8 @@ abstract class ApiBase {
if ( isset( self::$messageMap[$key] ) ) {
return array( 'code' =>
- wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ),
- 'info' =>
+ wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ),
+ 'info' =>
wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error )
);
}
@@ -1282,7 +1341,7 @@ 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
- * @return bool
+ * @return bool|string
*/
public function getTokenSalt() {
return false;
@@ -1295,10 +1354,9 @@ abstract class ApiBase {
* @return User
*/
public function getWatchlistUser( $params ) {
- global $wgUser;
if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
$user = User::newFromName( $params['owner'], false );
- if ( !$user->getId() ) {
+ if ( !($user && $user->getId()) ) {
$this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
}
$token = $user->getOption( 'watchlisttoken' );
@@ -1306,10 +1364,10 @@ abstract class ApiBase {
$this->dieUsage( 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences', 'bad_wltoken' );
}
} else {
- if ( !$wgUser->isLoggedIn() ) {
+ if ( !$this->getUser()->isLoggedIn() ) {
$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
}
- $user = $wgUser;
+ $user = $this->getUser();
}
return $user;
}
@@ -1482,6 +1540,13 @@ abstract class ApiBase {
}
/**
+ * @return DatabaseBase
+ */
+ protected function getDB() {
+ return wfGetDB( DB_SLAVE, 'api' );
+ }
+
+ /**
* Debugging function that prints a value and an optional backtrace
* @param $value mixed Value to print
* @param $name string Description of the printed value
diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php
index bb2bb253..351ac6b7 100644
--- a/includes/api/ApiBlock.php
+++ b/includes/api/ApiBlock.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* API module that facilitates the blocking of users. Requires API write mode
* to be enabled.
@@ -48,29 +43,29 @@ class ApiBlock extends ApiBase {
* of success. If it fails, the result will specify the nature of the error.
*/
public function execute() {
- global $wgUser;
+ $user = $this->getUser();
$params = $this->extractRequestParams();
if ( $params['gettoken'] ) {
- $res['blocktoken'] = $wgUser->editToken( '', $this->getMain()->getRequest() );
+ $res['blocktoken'] = $user->getEditToken( '', $this->getMain()->getRequest() );
$this->getResult()->addValue( null, $this->getModuleName(), $res );
return;
}
- if ( !$wgUser->isAllowed( 'block' ) ) {
+ if ( !$user->isAllowed( 'block' ) ) {
$this->dieUsageMsg( 'cantblock' );
}
# bug 15810: blocked admins should have limited access here
- if ( $wgUser->isBlocked() ) {
- $status = SpecialBlock::checkUnblockSelf( $params['user'] );
+ if ( $user->isBlocked() ) {
+ $status = SpecialBlock::checkUnblockSelf( $params['user'], $user );
if ( $status !== true ) {
$this->dieUsageMsg( array( $status ) );
}
}
- if ( $params['hidename'] && !$wgUser->isAllowed( 'hideuser' ) ) {
+ if ( $params['hidename'] && !$user->isAllowed( 'hideuser' ) ) {
$this->dieUsageMsg( 'canthide' );
}
- if ( $params['noemail'] && !SpecialBlock::canBlockEmail( $wgUser ) ) {
+ if ( $params['noemail'] && !SpecialBlock::canBlockEmail( $user ) ) {
$this->dieUsageMsg( 'cantblock-email' );
}
@@ -87,13 +82,13 @@ class ApiBlock extends ApiBase {
'AutoBlock' => $params['autoblock'],
'DisableEmail' => $params['noemail'],
'HideUser' => $params['hidename'],
- 'DisableUTEdit' => $params['allowusertalk'],
+ 'DisableUTEdit' => !$params['allowusertalk'],
'AlreadyBlocked' => $params['reblock'],
'Watch' => $params['watchuser'],
'Confirm' => true,
);
- $retval = SpecialBlock::processForm( $data );
+ $retval = SpecialBlock::processForm( $data, $this->getContext() );
if ( $retval !== true ) {
// We don't care about multiple errors, just report one of them
$this->dieUsageMsg( $retval );
@@ -208,7 +203,7 @@ class ApiBlock extends ApiBase {
return '';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike',
'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail='
diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php
index 59f8555b..4bb94c4a 100644
--- a/includes/api/ApiComparePages.php
+++ b/includes/api/ApiComparePages.php
@@ -35,7 +35,7 @@ class ApiComparePages extends ApiBase {
$rev1 = $this->revisionOrTitle( $params['fromrev'], $params['fromtitle'] );
$rev2 = $this->revisionOrTitle( $params['torev'], $params['totitle'] );
- $de = new DifferenceEngine( null,
+ $de = new DifferenceEngine( $this->getContext(),
$rev1,
$rev2,
null, // rcid
@@ -118,9 +118,9 @@ class ApiComparePages extends ApiBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'api.php?action=compare&fromrev=1&torev=2',
+ 'api.php?action=compare&fromrev=1&torev=2' => 'Create a diff between revision 1 and 2',
);
}
diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php
index d6cc21b3..cfaf6cc1 100644
--- a/includes/api/ApiDelete.php
+++ b/includes/api/ApiDelete.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* API module that facilitates deleting pages. The API equivalent of action=delete.
* Requires API write mode to be enabled.
@@ -69,67 +64,56 @@ class ApiDelete extends ApiBase {
}
$reason = ( isset( $params['reason'] ) ? $params['reason'] : null );
+ $pageObj = WikiPage::factory( $titleObj );
+ $user = $this->getUser();
+
if ( $titleObj->getNamespace() == NS_FILE ) {
- $retval = self::deleteFile( $params['token'], $titleObj, $params['oldimage'], $reason, false );
- if ( count( $retval ) ) {
- $this->dieUsageMsg( reset( $retval ) ); // We don't care about multiple errors, just report one of them
- }
+ $retval = self::deleteFile( $pageObj, $user, $params['token'], $params['oldimage'], $reason, false );
} else {
- $articleObj = new Article( $titleObj );
- $retval = self::delete( $articleObj, $params['token'], $reason );
+ $retval = self::delete( $pageObj, $user, $params['token'], $reason );
+ }
- if ( count( $retval ) ) {
- $this->dieUsageMsg( reset( $retval ) ); // We don't care about multiple errors, just report one of them
- }
+ if ( count( $retval ) ) {
+ $this->dieUsageMsg( reset( $retval ) ); // We don't care about multiple errors, just report one of them
+ }
- // Deprecated parameters
- if ( $params['watch'] ) {
- $watch = 'watch';
- } elseif ( $params['unwatch'] ) {
- $watch = 'unwatch';
- } else {
- $watch = $params['watchlist'];
- }
- $this->setWatch( $watch, $titleObj, 'watchdeletion' );
+ // Deprecated parameters
+ if ( $params['watch'] ) {
+ $watch = 'watch';
+ } elseif ( $params['unwatch'] ) {
+ $watch = 'unwatch';
+ } else {
+ $watch = $params['watchlist'];
}
+ $this->setWatch( $watch, $titleObj, 'watchdeletion' );
$r = array( 'title' => $titleObj->getPrefixedText(), 'reason' => $reason );
$this->getResult()->addValue( null, $this->getModuleName(), $r );
}
/**
- *
- * @param &$title Title
+ * @param $title Title
+ * @param $user User doing the action
* @param $token String
+ * @return array
*/
- private static function getPermissionsError( &$title, $token ) {
- global $wgUser;
-
+ private static function getPermissionsError( $title, $user, $token ) {
// Check permissions
- $errors = $title->getUserPermissionsErrors( 'delete', $wgUser );
- if ( count( $errors ) > 0 ) {
- return $errors;
- }
-
- return array();
+ return $title->getUserPermissionsErrors( 'delete', $user );
}
/**
* We have our own delete() function, since Article.php's implementation is split in two phases
*
- * @param $article Article object to work on
+ * @param $page WikiPage object to work on
+ * @param $user User doing the action
* @param $token String: delete token (same as edit token)
* @param $reason String: reason for the deletion. Autogenerated if NULL
* @return Title::getUserPermissionsErrors()-like array
*/
- public static function delete( &$article, $token, &$reason = null ) {
- global $wgUser;
- if ( $article->isBigDeletion() && !$wgUser->isAllowed( 'bigdelete' ) ) {
- global $wgDeleteRevisionsLimit;
- return array( array( 'delete-toobig', $wgDeleteRevisionsLimit ) );
- }
- $title = $article->getTitle();
- $errors = self::getPermissionsError( $title, $token );
+ public static function delete( Page $page, User $user, $token, &$reason = null ) {
+ $title = $page->getTitle();
+ $errors = self::getPermissionsError( $title, $user, $token );
if ( count( $errors ) ) {
return $errors;
}
@@ -139,54 +123,58 @@ class ApiDelete extends ApiBase {
// Need to pass a throwaway variable because generateReason expects
// a reference
$hasHistory = false;
- $reason = $article->generateReason( $hasHistory );
+ $reason = $page->getAutoDeleteReason( $hasHistory );
if ( $reason === false ) {
- return array( array( 'cannotdelete' ) );
+ return array( array( 'cannotdelete', $title->getPrefixedText() ) );
}
}
$error = '';
// Luckily, Article.php provides a reusable delete function that does the hard work for us
- if ( $article->doDeleteArticle( $reason, false, 0, true, $error ) ) {
+ if ( $page->doDeleteArticle( $reason, false, 0, true, $error ) ) {
return array();
} else {
- return array( array( 'cannotdelete', $article->getTitle()->getPrefixedText() ) );
+ return array( array( 'cannotdelete', $title->getPrefixedText() ) );
}
}
/**
+ * @param $page WikiPage object to work on
+ * @param $user User doing the action
* @param $token
- * @param $title Title
* @param $oldimage
* @param $reason
* @param $suppress bool
* @return \type|array|Title
*/
- public static function deleteFile( $token, &$title, $oldimage, &$reason = null, $suppress = false ) {
- $errors = self::getPermissionsError( $title, $token );
+ public static function deleteFile( Page $page, User $user, $token, $oldimage, &$reason = null, $suppress = false ) {
+ $title = $page->getTitle();
+ $errors = self::getPermissionsError( $title, $user, $token );
if ( count( $errors ) ) {
return $errors;
}
- if ( $oldimage && !FileDeleteForm::isValidOldSpec( $oldimage ) ) {
- return array( array( 'invalidoldimage' ) );
+ $file = $page->getFile();
+ if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
+ return self::delete( $page, $user, $token, $reason );
}
- $file = wfFindFile( $title, array( 'ignoreRedirect' => true ) );
- $oldfile = false;
-
if ( $oldimage ) {
+ if ( !FileDeleteForm::isValidOldSpec( $oldimage ) ) {
+ return array( array( 'invalidoldimage' ) );
+ }
$oldfile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $oldimage );
+ if ( !$oldfile->exists() || !$oldfile->isLocal() || $oldfile->getRedirected() ) {
+ return array( array( 'nodeleteablefile' ) );
+ }
+ } else {
+ $oldfile = false;
}
- if ( !FileDeleteForm::haveDeletableFile( $file, $oldfile, $oldimage ) ) {
- return self::delete( new Article( $title ), $token, $reason );
- }
if ( is_null( $reason ) ) { // Log and RC don't like null reasons
$reason = '';
}
$status = FileDeleteForm::doDelete( $title, $file, $oldimage, $reason, $suppress );
-
if ( !$status->isGood() ) {
return array( array( 'cannotdelete', $title->getPrefixedText() ) );
}
@@ -257,6 +245,10 @@ class ApiDelete extends ApiBase {
array( 'nosuchpageid', 'pageid' ),
array( 'notanarticle' ),
array( 'hookaborted', 'error' ),
+ array( 'delete-toobig', 'limit' ),
+ array( 'cannotdelete', 'title' ),
+ array( 'invalidoldimage' ),
+ array( 'nodeleteablefile' ),
)
);
}
@@ -269,10 +261,10 @@ class ApiDelete extends ApiBase {
return '';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'api.php?action=delete&title=Main%20Page&token=123ABC',
- 'api.php?action=delete&title=Main%20Page&token=123ABC&reason=Preparing%20for%20move'
+ 'api.php?action=delete&title=Main%20Page&token=123ABC' => 'Delete the Main Page',
+ 'api.php?action=delete&title=Main%20Page&token=123ABC&reason=Preparing%20for%20move' => 'Delete the Main Page with the reason "Preparing for move"',
);
}
diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php
index ad731bb4..55754896 100644
--- a/includes/api/ApiDisabled.php
+++ b/includes/api/ApiDisabled.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* API module that dies with an error immediately.
*
@@ -46,7 +41,7 @@ class ApiDisabled extends ApiBase {
}
public function execute() {
- $this->dieUsage( "The ``{$this->getModuleName()}'' module has been disabled.", 'moduledisabled' );
+ $this->dieUsage( "The \"{$this->getModuleName()}\" module has been disabled.", 'moduledisabled' );
}
public function isReadMode() {
@@ -65,7 +60,7 @@ class ApiDisabled extends ApiBase {
return 'This module has been disabled';
}
- protected function getExamples() {
+ public function getExamples() {
return array();
}
diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php
index 2b949ba9..9ed6d08d 100644
--- a/includes/api/ApiEditPage.php
+++ b/includes/api/ApiEditPage.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* A module that allows for editing and creating pages.
*
@@ -43,7 +38,7 @@ class ApiEditPage extends ApiBase {
}
public function execute() {
- global $wgUser;
+ $user = $this->getUser();
$params = $this->extractRequestParams();
if ( is_null( $params['text'] ) && is_null( $params['appendtext'] ) &&
@@ -87,10 +82,6 @@ class ApiEditPage extends ApiBase {
}
}
- // Some functions depend on $wgTitle == $ep->mTitle
- global $wgTitle;
- $wgTitle = $titleObj;
-
if ( $params['createonly'] && $titleObj->exists() ) {
$this->dieUsageMsg( 'createonly-exists' );
}
@@ -99,15 +90,16 @@ class ApiEditPage extends ApiBase {
}
// Now let's check whether we're even allowed to do this
- $errors = $titleObj->getUserPermissionsErrors( 'edit', $wgUser );
+ $errors = $titleObj->getUserPermissionsErrors( 'edit', $user );
if ( !$titleObj->exists() ) {
- $errors = array_merge( $errors, $titleObj->getUserPermissionsErrors( 'create', $wgUser ) );
+ $errors = array_merge( $errors, $titleObj->getUserPermissionsErrors( 'create', $user ) );
}
if ( count( $errors ) ) {
$this->dieUsageMsg( $errors[0] );
}
- $articleObj = new Article( $titleObj );
+ $articleObj = Article::newFromTitle( $titleObj, $this->getContext() );
+
$toMD5 = $params['text'];
if ( !is_null( $params['appendtext'] ) || !is_null( $params['prependtext'] ) )
{
@@ -178,41 +170,42 @@ class ApiEditPage extends ApiBase {
$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(
+ $requestArray = array(
'wpTextbox1' => $params['text'],
'wpEditToken' => $params['token'],
'wpIgnoreBlankSummary' => ''
);
if ( !is_null( $params['summary'] ) ) {
- $reqArr['wpSummary'] = $params['summary'];
+ $requestArray['wpSummary'] = $params['summary'];
+ }
+
+ if ( !is_null( $params['sectiontitle'] ) ) {
+ $requestArray['wpSectionTitle'] = $params['sectiontitle'];
}
// Watch out for basetimestamp == ''
// wfTimestamp() treats it as NOW, almost certainly causing an edit conflict
if ( !is_null( $params['basetimestamp'] ) && $params['basetimestamp'] != '' ) {
- $reqArr['wpEdittime'] = wfTimestamp( TS_MW, $params['basetimestamp'] );
+ $requestArray['wpEdittime'] = wfTimestamp( TS_MW, $params['basetimestamp'] );
} else {
- $reqArr['wpEdittime'] = $articleObj->getTimestamp();
+ $requestArray['wpEdittime'] = $articleObj->getTimestamp();
}
if ( !is_null( $params['starttimestamp'] ) && $params['starttimestamp'] != '' ) {
- $reqArr['wpStarttime'] = wfTimestamp( TS_MW, $params['starttimestamp'] );
+ $requestArray['wpStarttime'] = wfTimestamp( TS_MW, $params['starttimestamp'] );
} else {
- $reqArr['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
+ $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime
}
- if ( $params['minor'] || ( !$params['notminor'] && $wgUser->getOption( 'minordefault' ) ) ) {
- $reqArr['wpMinoredit'] = '';
+ if ( $params['minor'] || ( !$params['notminor'] && $user->getOption( 'minordefault' ) ) ) {
+ $requestArray['wpMinoredit'] = '';
}
if ( $params['recreate'] ) {
- $reqArr['wpRecreate'] = '';
+ $requestArray['wpRecreate'] = '';
}
if ( !is_null( $params['section'] ) ) {
@@ -220,9 +213,9 @@ class ApiEditPage extends ApiBase {
if ( $section == 0 && $params['section'] != '0' && $params['section'] != 'new' ) {
$this->dieUsage( "The section parameter must be set to an integer or 'new'", "invalidsection" );
}
- $reqArr['wpSection'] = $params['section'];
+ $requestArray['wpSection'] = $params['section'];
} else {
- $reqArr['wpSection'] = '';
+ $requestArray['wpSection'] = '';
}
$watch = $this->getWatchlistValue( $params['watchlist'], $titleObj );
@@ -235,22 +228,23 @@ class ApiEditPage extends ApiBase {
}
if ( $watch ) {
- $reqArr['wpWatchthis'] = '';
+ $requestArray['wpWatchthis'] = '';
}
- $req = new FauxRequest( $reqArr, true );
+ global $wgTitle, $wgRequest;
+
+ $req = new DerivativeRequest( $this->getRequest(), $requestArray, true );
+
+ // Some functions depend on $wgTitle == $ep->mTitle
+ // TODO: Make them not or check if they still do
+ $wgTitle = $titleObj;
+
+ $ep = new EditPage( $articleObj );
+ $ep->setContextTitle( $titleObj );
$ep->importFormData( $req );
// Run hooks
- // Handle CAPTCHA parameters
- global $wgRequest;
- if ( !is_null( $params['captchaid'] ) ) {
- $wgRequest->setVal( 'wpCaptchaId', $params['captchaid'] );
- }
- if ( !is_null( $params['captchaword'] ) ) {
- $wgRequest->setVal( 'wpCaptchaWord', $params['captchaword'] );
- }
-
+ // Handle APIEditBeforeSave parameters
$r = array();
if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $ep->textbox1, &$r ) ) ) {
if ( count( $r ) ) {
@@ -270,7 +264,7 @@ class ApiEditPage extends ApiBase {
$oldRequest = $wgRequest;
$wgRequest = $req;
- $status = $ep->internalAttemptSave( $result, $wgUser->isAllowed( 'bot' ) && $params['bot'] );
+ $status = $ep->internalAttemptSave( $result, $user->isAllowed( 'bot' ) && $params['bot'] );
$wgRequest = $oldRequest;
global $wgMaxArticleSize;
@@ -333,19 +327,14 @@ class ApiEditPage extends ApiBase {
$r['result'] = 'Success';
$r['pageid'] = intval( $titleObj->getArticleID() );
$r['title'] = $titleObj->getPrefixedText();
- // HACK: We create a new Article object here because getRevIdFetched()
- // refuses to be run twice, and because Title::getLatestRevId()
- // won't fetch from the master unless we select for update, which we
- // don't want to do.
- $newArticle = new Article( $titleObj );
- $newRevId = $newArticle->getRevIdFetched();
+ $newRevId = $articleObj->getLatest();
if ( $newRevId == $oldRevId ) {
$r['nochange'] = '';
} else {
$r['oldrevid'] = intval( $oldRevId );
$r['newrevid'] = intval( $newRevId );
$r['newtimestamp'] = wfTimestamp( TS_ISO_8601,
- $newArticle->getTimestamp() );
+ $articleObj->getTimestamp() );
}
break;
@@ -358,7 +347,11 @@ class ApiEditPage extends ApiBase {
$this->dieUsageMsg( $errors[0] ); // TODO: Add new errors to message map
break;
default:
- $this->dieUsageMsg( array( 'unknownerror', $status->value ) );
+ if ( is_string( $status->value ) && strlen( $status->value ) ) {
+ $this->dieUsage( "An unknown return value was returned by Editpage. The code returned was \"{$status->value}\"" , $status->value );
+ } else {
+ $this->dieUsageMsg( array( 'unknownerror', $status->value ) );
+ }
}
$apiResult->addValue( null, $this->getModuleName(), $r );
}
@@ -371,7 +364,7 @@ class ApiEditPage extends ApiBase {
return true;
}
- protected function getDescription() {
+ public function getDescription() {
return 'Create and edit pages.';
}
@@ -412,13 +405,17 @@ class ApiEditPage extends ApiBase {
) );
}
- protected function getAllowedParams() {
+ public function getAllowedParams() {
return array(
'title' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'section' => null,
+ 'sectiontitle' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => false,
+ ),
'text' => null,
'token' => null,
'summary' => null,
@@ -430,8 +427,6 @@ class ApiEditPage extends ApiBase {
'recreate' => false,
'createonly' => false,
'nocreate' => false,
- 'captchaword' => null,
- 'captchaid' => null,
'watch' => array(
ApiBase::PARAM_DFLT => false,
ApiBase::PARAM_DEPRECATED => true,
@@ -465,11 +460,12 @@ class ApiEditPage extends ApiBase {
);
}
- protected function getParamDescription() {
+ public function getParamDescription() {
$p = $this->getModulePrefix();
return array(
'title' => 'Page title',
'section' => 'Section number. 0 for the top section, \'new\' for a new section',
+ 'sectiontitle' => 'The title for a new section',
'text' => 'Page content',
'token' => array( 'Edit token. You can get one of these through prop=info.',
'The token should always be sent as the last parameter, or at least, after the text parameter'
@@ -490,10 +486,8 @@ class ApiEditPage extends ApiBase {
'watch' => 'Add the page to your watchlist',
'unwatch' => 'Remove the page from your watchlist',
'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
- 'captchaid' => 'CAPTCHA ID from previous request',
- 'captchaword' => 'Answer to the CAPTCHA',
'md5' => array( "The MD5 hash of the {$p}text parameter, or the {$p}prependtext and {$p}appendtext parameters concatenated.",
- 'If set, the edit won\'t be done unless the hash is correct' ),
+ 'If set, the edit won\'t be done unless the hash is correct' ),
'prependtext' => "Add this text to the beginning of the page. Overrides {$p}text",
'appendtext' => "Add this text to the end of the page. Overrides {$p}text",
'undo' => "Undo this revision. Overrides {$p}text, {$p}prependtext and {$p}appendtext",
@@ -510,14 +504,16 @@ class ApiEditPage extends ApiBase {
return '';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Edit a page (anonymous user):',
- ' api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\',
- 'Prepend __NOTOC__ to a page (anonymous user):',
- ' api.php?action=edit&title=Test&summary=NOTOC&minor=&prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\',
- 'Undo r13579 through r13585 with autosummary (anonymous user):',
- ' api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\',
+
+ 'api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\'
+ => 'Edit a page (anonymous user)',
+
+ 'api.php?action=edit&title=Test&summary=NOTOC&minor=&prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\'
+ => 'Prepend __NOTOC__ to a page (anonymous user)',
+ 'api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\'
+ => 'Undo r13579 through r13585 with autosummary (anonymous user)',
);
}
diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php
index 46e8d523..d9eed60c 100644
--- a/includes/api/ApiEmailUser.php
+++ b/includes/api/ApiEmailUser.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* API Module to facilitate sending of emails to users
* @ingroup API
@@ -40,8 +35,6 @@ class ApiEmailUser extends ApiBase {
}
public function execute() {
- global $wgUser;
-
$params = $this->extractRequestParams();
// Validate target
@@ -51,7 +44,7 @@ class ApiEmailUser extends ApiBase {
}
// Check permissions and errors
- $error = SpecialEmailUser::getPermissionsError( $wgUser, $params['token'] );
+ $error = SpecialEmailUser::getPermissionsError( $this->getUser(), $params['token'] );
if ( $error ) {
$this->dieUsageMsg( array( $error ) );
}
@@ -138,9 +131,9 @@ class ApiEmailUser extends ApiBase {
return '';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'api.php?action=emailuser&target=WikiSysop&text=Content'
+ 'api.php?action=emailuser&target=WikiSysop&text=Content' => 'Send an email to the User "WikiSysop" with the text "Content"',
);
}
diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php
index dfa520a2..d570534d 100644
--- a/includes/api/ApiExpandTemplates.php
+++ b/includes/api/ApiExpandTemplates.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* API module that functions as a shortcut to the wikitext preprocessor. Expands
* any templates in a provided string, and returns the result of this expansion
@@ -52,14 +47,14 @@ class ApiExpandTemplates extends ApiBase {
// Create title for parser
$title_obj = Title::newFromText( $params['title'] );
if ( !$title_obj ) {
- $title_obj = Title::newFromText( 'API' ); // default
+ $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
$result = $this->getResult();
// Parse text
global $wgParser;
- $options = new ParserOptions();
+ $options = ParserOptions::newFromContext( $this->getContext() );
if ( $params['includecomments'] ) {
$options->setRemoveComments( false );
@@ -112,7 +107,13 @@ class ApiExpandTemplates extends ApiBase {
return 'Expands all templates in wikitext';
}
- protected function getExamples() {
+ public function getPossibleErrors() {
+ return array_merge( parent::getPossibleErrors(), array(
+ array( 'invalidtitle', 'title' ),
+ ) );
+ }
+
+ public function getExamples() {
return array(
'api.php?action=expandtemplates&text={{Project:Sandbox}}'
);
diff --git a/includes/api/ApiFeedContributions.php b/includes/api/ApiFeedContributions.php
index c1e6ff6e..4e70bde2 100644
--- a/includes/api/ApiFeedContributions.php
+++ b/includes/api/ApiFeedContributions.php
@@ -1,5 +1,4 @@
<?php
-
/**
*
*
@@ -36,6 +35,8 @@ class ApiFeedContributions extends ApiBase {
/**
* This module uses a custom feed wrapper printer.
+ *
+ * @return ApiFormatFeedWrapper
*/
public function getCustomPrinter() {
return new ApiFormatFeedWrapper( $this->getMain() );
@@ -73,7 +74,7 @@ class ApiFeedContributions extends ApiBase {
$feedUrl
);
- $pager = new ContribsPager( array(
+ $pager = new ContribsPager( $this->getContext(), array(
'target' => $target,
'namespace' => $params['namespace'],
'year' => $params['year'],
@@ -99,7 +100,7 @@ class ApiFeedContributions extends ApiBase {
if( $title ) {
$date = $row->rev_timestamp;
$comments = $title->getTalkPage()->getFullURL();
- $revision = Revision::newFromRow( $row);
+ $revision = Revision::newFromRow( $row );
return new FeedItem(
$title->getPrefixedText(),
@@ -195,7 +196,7 @@ class ApiFeedContributions extends ApiBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=feedcontributions&user=Reedy',
);
@@ -204,4 +205,4 @@ class ApiFeedContributions extends ApiBase {
public function getVersion() {
return __CLASS__ . ': $Id$';
}
-} \ No newline at end of file
+}
diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php
index dd7e3d8f..eee8fa19 100644
--- a/includes/api/ApiFeedWatchlist.php
+++ b/includes/api/ApiFeedWatchlist.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* This action allows users to get their watchlist items in RSS/Atom formats.
* When executed, it performs a nested call to the API to get the needed data,
@@ -44,6 +39,8 @@ class ApiFeedWatchlist extends ApiBase {
/**
* This module uses a custom feed wrapper printer.
+ *
+ * @return ApiFormatFeedWrapper
*/
public function getCustomPrinter() {
return new ApiFormatFeedWrapper( $this->getMain() );
@@ -68,6 +65,9 @@ class ApiFeedWatchlist extends ApiBase {
if( !isset( $wgFeedClasses[ $params['feedformat'] ] ) ) {
$this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' );
}
+ if ( !is_null( $params['wlexcludeuser'] ) ) {
+ $fauxReqArr['wlexcludeuser'] = $params['wlexcludeuser'];
+ }
// limit to the number of hours going from now back
$endTime = wfTimestamp( TS_MW, time() - intval( $params['hours'] * 60 * 60 ) );
@@ -150,6 +150,10 @@ class ApiFeedWatchlist extends ApiBase {
}
}
+ /**
+ * @param $info array
+ * @return FeedItem
+ */
private function createFeedItem( $info ) {
$titleStr = $info['title'];
$title = Title::newFromText( $titleStr );
@@ -188,6 +192,9 @@ class ApiFeedWatchlist extends ApiBase {
'wltoken' => array(
ApiBase::PARAM_TYPE => 'string'
),
+ 'wlexcludeuser' => array(
+ ApiBase::PARAM_TYPE => 'user'
+ ),
'linktodiffs' => false,
);
}
@@ -197,9 +204,10 @@ class ApiFeedWatchlist extends ApiBase {
'feedformat' => 'The format of the feed',
'hours' => 'List pages modified within this many hours from now',
'allrev' => 'Include multiple revisions of the same page within given timeframe',
- 'wlowner' => "The user whose watchlist you want (must be accompanied by {$this->getModulePrefix()}token if it's not you)",
+ 'wlowner' => "The user whose watchlist you want (must be accompanied by {$this->getModulePrefix()}wltoken if it's not you)",
'wltoken' => 'Security token that requested user set in their preferences',
- 'linktodiffs' => 'Link to change differences instead of article pages'
+ 'wlexcludeuser' => 'A user whose edits should not be shown in the watchlist',
+ 'linktodiffs' => 'Link to change differences instead of article pages',
);
}
@@ -214,7 +222,7 @@ class ApiFeedWatchlist extends ApiBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=feedwatchlist',
'api.php?action=feedwatchlist&allrev=&linktodiffs=&hours=6'
diff --git a/includes/api/ApiFileRevert.php b/includes/api/ApiFileRevert.php
index 5ff50512..7ef1da0a 100644
--- a/includes/api/ApiFileRevert.php
+++ b/includes/api/ApiFileRevert.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* @ingroup API
*/
@@ -47,14 +42,12 @@ class ApiFileRevert extends ApiBase {
}
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 );
+ $this->checkPermissions( $this->getUser() );
$sourceUrl = $this->file->getArchiveVirtualUrl( $this->archiveName );
$status = $this->file->upload( $sourceUrl, $this->params['comment'], $this->params['comment'] );
@@ -176,10 +169,10 @@ class ApiFileRevert extends ApiBase {
return '';
}
- protected function getExamples() {
+ public 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=+\\',
+ 'api.php?action=filerevert&filename=Wiki.png&comment=Revert&archivename=20110305152740!Wiki.png&token=+\\'
+ => 'Revert Wiki.png to the version of 20110305152740',
);
}
diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php
index 8c8235b8..543c90ce 100644
--- a/includes/api/ApiFormatBase.php
+++ b/includes/api/ApiFormatBase.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
-
/**
* This is the abstract base class for API formatters.
*
@@ -235,8 +230,10 @@ See <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>, or
public function getBuffer() {
return $this->mBuffer;
}
+
/**
* Set the flag to buffer the result instead of printing it.
+ * @param $value bool
*/
public function setBufferResult( $value ) {
$this->mBufferResult = $value;
@@ -267,7 +264,7 @@ See <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>, or
// 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
- $text = preg_replace( "#api\\.php\\?[^ \\()<\n\t]+#", '<a href="\\0">\\0</a>', $text );
+ $text = preg_replace( "#api\\.php\\?[^ <\n\t]+#", '<a href="\\0">\\0</a>', $text );
if ( $this->mHelp ) {
// make strings inside * bold
$text = preg_replace( "#\\*[^<>\n]+\\*#", '<b>\\0</b>', $text );
@@ -288,8 +285,11 @@ See <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>, or
return $text;
}
- protected function getExamples() {
- return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName();
+ public function getExamples() {
+ return array(
+ 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName()
+ => "Format the query result in the {$this->getModuleName()} format",
+ );
}
public function getHelpUrls() {
@@ -335,6 +335,8 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
/**
* Feed does its own headers
+ *
+ * @return null
*/
public function getMimeType() {
return null;
@@ -342,6 +344,8 @@ class ApiFormatFeedWrapper extends ApiFormatBase {
/**
* Optimization - no need to sanitize data that will not be needed
+ *
+ * @return bool
*/
public function getNeedsRawData() {
return true;
diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php
index 32f223d7..92619f76 100644
--- a/includes/api/ApiFormatDbg.php
+++ b/includes/api/ApiFormatDbg.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiFormatBase.php' );
-}
-
/**
* API PHP's var_export() output formatter
* @ingroup API
diff --git a/includes/api/ApiFormatDump.php b/includes/api/ApiFormatDump.php
index bde3e56d..0f055e13 100644
--- a/includes/api/ApiFormatDump.php
+++ b/includes/api/ApiFormatDump.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiFormatBase.php' );
-}
-
/**
* API PHP's var_dump() output formatter
* @ingroup API
diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php
index e3755d73..e728d057 100644
--- a/includes/api/ApiFormatJson.php
+++ b/includes/api/ApiFormatJson.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiFormatBase.php' );
-}
-
/**
* API JSON output formatter
* @ingroup API
diff --git a/includes/api/ApiFormatPhp.php b/includes/api/ApiFormatPhp.php
index cfcc2a03..60552c40 100644
--- a/includes/api/ApiFormatPhp.php
+++ b/includes/api/ApiFormatPhp.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiFormatBase.php' );
-}
-
/**
* API Serialized PHP output formatter
* @ingroup API
diff --git a/includes/api/ApiFormatRaw.php b/includes/api/ApiFormatRaw.php
index 75912871..db81aacd 100644
--- a/includes/api/ApiFormatRaw.php
+++ b/includes/api/ApiFormatRaw.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiFormatBase.php' );
-}
-
/**
* Formatter that spits out anything you like with any desired MIME type
* @ingroup API
diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php
index 54a620fc..e26b82b0 100644
--- a/includes/api/ApiFormatTxt.php
+++ b/includes/api/ApiFormatTxt.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiFormatBase.php' );
-}
-
/**
* API Text output formatter
* @ingroup API
diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php
index f5cace21..1bc9d025 100644
--- a/includes/api/ApiFormatWddx.php
+++ b/includes/api/ApiFormatWddx.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiFormatBase.php' );
-}
-
/**
* API WDDX output formatter
* @ingroup API
@@ -69,6 +64,8 @@ class ApiFormatWddx extends ApiFormatBase {
/**
* Recursively go through the object and output its data in WDDX format.
+ * @param $elemValue
+ * @param $indent int
*/
function slowWddxPrinter( $elemValue, $indent = 0 ) {
$indstr = ( $this->getIsHtml() ? '' : str_repeat( ' ', $indent ) );
diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php
index 06bd9f33..8f4abc15 100644
--- a/includes/api/ApiFormatXml.php
+++ b/includes/api/ApiFormatXml.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiFormatBase.php' );
-}
-
/**
* API XML output formatter
* @ingroup API
@@ -68,11 +63,15 @@ class ApiFormatXml extends ApiFormatBase {
$this->addXslt();
}
if ( $this->mIncludeNamespace ) {
- $data = array( 'xmlns' => self::$namespace ) + $this->getResultData();
+ // If the result data already contains an 'xmlns' namespace added
+ // for custom XML output types, it will override the one for the
+ // generic API results.
+ // This allows API output of other XML types like Atom, RSS, RSD.
+ $data = $this->getResultData() + array( 'xmlns' => self::$namespace );
} else {
$data = $this->getResultData();
}
-
+
$this->printText(
self::recXmlPrint( $this->mRootElemName,
$data,
@@ -94,7 +93,7 @@ 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
@@ -202,7 +201,7 @@ class ApiFormatXml extends ApiFormatBase {
$this->setWarning( 'Stylesheet should have .xsl extension.' );
return;
}
- $this->printText( '<?xml-stylesheet href="' . $nt->escapeLocalURL( 'action=raw' ) . '" type="text/xsl" ?>' );
+ $this->printText( '<?xml-stylesheet href="' . htmlspecialchars( $nt->getLocalURL( 'action=raw' ) ) . '" type="text/xsl" ?>' );
}
public function getAllowedParams() {
diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php
index ecf35900..dbcdb21c 100644
--- a/includes/api/ApiFormatYaml.php
+++ b/includes/api/ApiFormatYaml.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiFormatBase.php' );
-}
-
/**
* API YAML output formatter
* @ingroup API
diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php
index f2af822a..97da786b 100644
--- a/includes/api/ApiHelp.php
+++ b/includes/api/ApiHelp.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
-
/**
* This is a simple class to handle action=help
*
@@ -139,18 +134,13 @@ class ApiHelp extends ApiBase {
return 'Display this help screen. Or the help screen for the specified module';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Whole help page:',
- ' api.php?action=help',
- 'Module (action) help page:',
- ' api.php?action=help&modules=protect',
- 'Query (list) modules help page:',
- ' api.php?action=help&querymodules=categorymembers',
- 'Query (prop) modules help page:',
- ' api.php?action=help&querymodules=info',
- 'Query (meta) modules help page:',
- ' api.php?action=help&querymodules=siteinfo',
+ 'api.php?action=help' => 'Whole help page',
+ 'api.php?action=help&modules=protect' => 'Module (action) help page',
+ 'api.php?action=help&querymodules=categorymembers' => 'Query (list) modules help page',
+ 'api.php?action=help&querymodules=info' => 'Query (prop) modules help page',
+ 'api.php?action=help&querymodules=siteinfo' => 'Query (meta) modules help page',
);
}
diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php
index ce740efc..ade9f1f3 100644
--- a/includes/api/ApiImport.php
+++ b/includes/api/ApiImport.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
-
/**
* API module that imports an XML file like Special:Import does
*
@@ -41,13 +36,12 @@ class ApiImport extends ApiBase {
}
public function execute() {
- global $wgUser;
-
+ $user = $this->getUser();
$params = $this->extractRequestParams();
$isUpload = false;
if ( isset( $params['interwikisource'] ) ) {
- if ( !$wgUser->isAllowed( 'import' ) ) {
+ if ( !$user->isAllowed( 'import' ) ) {
$this->dieUsageMsg( 'cantimport' );
}
if ( !isset( $params['interwikipage'] ) ) {
@@ -61,7 +55,7 @@ class ApiImport extends ApiBase {
);
} else {
$isUpload = true;
- if ( !$wgUser->isAllowed( 'importupload' ) ) {
+ if ( !$user->isAllowed( 'importupload' ) ) {
$this->dieUsageMsg( 'cantimport-upload' );
}
$source = ImportStreamSource::newFromUpload( 'xml' );
@@ -158,10 +152,10 @@ class ApiImport extends ApiBase {
return '';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Import [[meta:Help:Parserfunctions]] to namespace 100 with full history:',
- ' api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&namespace=100&fullhistory=&token=123ABC',
+ 'api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&namespace=100&fullhistory=&token=123ABC'
+ => 'Import [[meta:Help:Parserfunctions]] to namespace 100 with full history',
);
}
diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php
index 1b17e43b..aa570cbc 100644
--- a/includes/api/ApiLogin.php
+++ b/includes/api/ApiLogin.php
@@ -25,11 +25,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
-
/**
* Unit to authenticate log-in attempts to the current wiki.
*
@@ -55,39 +50,45 @@ class ApiLogin extends ApiBase {
$result = array();
- $req = new FauxRequest( array(
- 'wpName' => $params['name'],
- 'wpPassword' => $params['password'],
- 'wpDomain' => $params['domain'],
- 'wpLoginToken' => $params['token'],
- 'wpRemember' => ''
- ) );
-
// Init session if necessary
if ( session_id() == '' ) {
wfSetupSession();
}
- $loginForm = new LoginForm( $req );
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setRequest( new DerivativeRequest(
+ $this->getContext()->getRequest(),
+ array(
+ 'wpName' => $params['name'],
+ 'wpPassword' => $params['password'],
+ 'wpDomain' => $params['domain'],
+ 'wpLoginToken' => $params['token'],
+ 'wpRemember' => ''
+ )
+ ) );
+ $loginForm = new LoginForm();
+ $loginForm->setContext( $context );
- global $wgCookiePrefix, $wgUser, $wgPasswordAttemptThrottle;
+ global $wgCookiePrefix, $wgPasswordAttemptThrottle;
$authRes = $loginForm->authenticateUserData();
switch ( $authRes ) {
case LoginForm::SUCCESS:
- $wgUser->setOption( 'rememberpassword', 1 );
- $wgUser->setCookies( $this->getMain()->getRequest() );
+ $user = $context->getUser();
+ $this->getContext()->setUser( $user );
+ $user->setOption( 'rememberpassword', 1 );
+ $user->setCookies( $this->getRequest() );
// 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 ) );
+ wfRunHooks( 'UserLoginComplete', array( &$user, &$injected_html ) );
$result['result'] = 'Success';
- $result['lguserid'] = intval( $wgUser->getId() );
- $result['lgusername'] = $wgUser->getName();
- $result['lgtoken'] = $wgUser->getToken();
+ $result['lguserid'] = intval( $user->getId() );
+ $result['lgusername'] = $user->getName();
+ $result['lgtoken'] = $user->getToken();
$result['cookieprefix'] = $wgCookiePrefix;
$result['sessionid'] = session_id();
break;
@@ -199,14 +200,14 @@ class ApiLogin extends ApiBase {
array( 'code' => 'NotExists', 'info' => ' The username you provided doesn\'t exist' ),
array( 'code' => 'EmptyPass', 'info' => ' You didn\'t set the lgpassword parameter or you left it empty' ),
array( 'code' => 'WrongPass', 'info' => ' The password you provided is incorrect' ),
- array( 'code' => 'WrongPluginPass', 'info' => 'Same as `WrongPass", returned when an authentication plugin rather than MediaWiki itself rejected the password' ),
+ array( 'code' => 'WrongPluginPass', 'info' => 'Same as "WrongPass", returned when an authentication plugin rather than MediaWiki itself rejected the password' ),
array( 'code' => 'CreateBlocked', 'info' => 'The wiki tried to automatically create a new account for you, but your IP address has been blocked from account creation' ),
array( 'code' => 'Throttled', 'info' => 'You\'ve logged in too many times in a short time' ),
array( 'code' => 'Blocked', 'info' => 'User is blocked' ),
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=login&lgname=user&lgpassword=password'
);
diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php
index b5dd7ac9..81a054a6 100644
--- a/includes/api/ApiLogout.php
+++ b/includes/api/ApiLogout.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
-
/**
* API module to allow users to log out of the wiki. API equivalent of
* Special:Userlogout.
@@ -42,13 +37,13 @@ class ApiLogout extends ApiBase {
}
public function execute() {
- global $wgUser;
- $oldName = $wgUser->getName();
- $wgUser->logout();
+ $user = $this->getUser();
+ $oldName = $user->getName();
+ $user->logout();
// Give extensions to do something after user logout
$injected_html = '';
- wfRunHooks( 'UserLogoutComplete', array( &$wgUser, &$injected_html, $oldName ) );
+ wfRunHooks( 'UserLogoutComplete', array( &$user, &$injected_html, $oldName ) );
}
public function isReadMode() {
@@ -67,9 +62,9 @@ class ApiLogout extends ApiBase {
return 'Log out and clear session data';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'api.php?action=logout'
+ 'api.php?action=logout' => 'Log the current user out',
);
}
diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php
index b7e118cf..fa95cfca 100644
--- a/includes/api/ApiMain.php
+++ b/includes/api/ApiMain.php
@@ -25,11 +25,6 @@
* @defgroup API API
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
-
/**
* This is the main API class, used for both external and internal processing.
* When executed, it will create the requested formatter object,
@@ -132,7 +127,7 @@ class ApiMain extends ApiBase {
private $mPrinter;
private $mModules, $mModuleNames, $mFormats, $mFormatNames;
- private $mResult, $mAction, $mShowVersions, $mEnableWrite, $mRequest;
+ private $mResult, $mAction, $mShowVersions, $mEnableWrite;
private $mInternalMode, $mSquidMaxage, $mModule;
private $mCacheMode = 'private';
@@ -141,11 +136,25 @@ class ApiMain extends ApiBase {
/**
* Constructs an instance of ApiMain that utilizes the module and format specified by $request.
*
- * @param $request WebRequest - if this is an instance of FauxRequest, errors are thrown and no printing occurs
+ * @param $context IContextSource|WebRequest - if this is an instance of FauxRequest, errors are thrown and no printing occurs
* @param $enableWrite bool should be set to true if the api may modify data
*/
- public function __construct( $request, $enableWrite = false ) {
- $this->mInternalMode = ( $request instanceof FauxRequest );
+ public function __construct( $context = null, $enableWrite = false ) {
+ if ( $context === null ) {
+ $context = RequestContext::getMain();
+ } elseif ( $context instanceof WebRequest ) {
+ // BC for pre-1.19
+ $request = $context;
+ $context = RequestContext::getMain();
+ }
+ // We set a derivative context so we can change stuff later
+ $this->setContext( new DerivativeContext( $context ) );
+
+ if ( isset( $request ) ) {
+ $this->getContext()->setRequest( $request );
+ }
+
+ $this->mInternalMode = ( $this->getRequest() instanceof FauxRequest );
// Special handling for the main module: $parent === $this
parent::__construct( $this, $this->mInternalMode ? 'main_int' : 'main' );
@@ -156,11 +165,12 @@ class ApiMain extends ApiBase {
// Remove all modules other than login
global $wgUser;
- if ( $request->getVal( 'callback' ) !== null ) {
+ if ( $this->getRequest()->getVal( 'callback' ) !== null ) {
// JSON callback allows cross-site reads.
// For safety, strip user credentials.
wfDebug( "API: stripping user credentials for JSON callback\n" );
$wgUser = new User();
+ $this->getContext()->setUser( $wgUser );
}
}
@@ -175,8 +185,6 @@ class ApiMain extends ApiBase {
$this->mShowVersions = false;
$this->mEnableWrite = $enableWrite;
- $this->mRequest = &$request;
-
$this->mSquidMaxage = - 1; // flag for executeActionWithErrorHandling()
$this->mCommit = false;
}
@@ -190,14 +198,6 @@ class ApiMain extends ApiBase {
}
/**
- * Return the request object that contains client's request
- * @return WebRequest
- */
- public function getRequest() {
- return $this->mRequest;
- }
-
- /**
* Get the ApiResult object associated with current request
*
* @return ApiResult
@@ -286,6 +286,7 @@ class ApiMain extends ApiBase {
* $this->setCacheMode('private')
*/
public function setCachePrivate() {
+ wfDeprecated( __METHOD__, '1.17' );
$this->setCacheMode( 'private' );
}
@@ -314,6 +315,7 @@ class ApiMain extends ApiBase {
* @deprecated since 1.17 Use setCacheMode( 'anon-public-user-private' )
*/
public function setVaryCookie() {
+ wfDeprecated( __METHOD__, '1.17' );
$this->setCacheMode( 'anon-public-user-private' );
}
@@ -399,7 +401,7 @@ class ApiMain extends ApiBase {
}
protected function sendCacheHeaders() {
- global $wgUseXVO, $wgOut, $wgVaryOnXFP;
+ global $wgUseXVO, $wgVaryOnXFP;
$response = $this->getRequest()->response();
if ( $this->mCacheMode == 'private' ) {
@@ -411,11 +413,12 @@ class ApiMain extends ApiBase {
$xfp = $wgVaryOnXFP ? ', X-Forwarded-Proto' : '';
$response->header( 'Vary: Accept-Encoding, Cookie' . $xfp );
if ( $wgUseXVO ) {
+ $out = $this->getOutput();
if ( $wgVaryOnXFP ) {
- $wgOut->addVaryHeader( 'X-Forwarded-Proto' );
+ $out->addVaryHeader( 'X-Forwarded-Proto' );
}
- $response->header( $wgOut->getXVO() );
- if ( $wgOut->haveCacheVaryCookies() ) {
+ $response->header( $out->getXVO() );
+ if ( $out->haveCacheVaryCookies() ) {
// Logged in, mark this request private
$response->header( 'Cache-Control: private' );
return;
@@ -428,7 +431,7 @@ class ApiMain extends ApiBase {
return;
} // else no XVO and anonymous, send public headers below
}
-
+
// Send public headers
if ( $wgVaryOnXFP ) {
$response->header( 'Vary: Accept-Encoding, X-Forwarded-Proto' );
@@ -486,6 +489,8 @@ class ApiMain extends ApiBase {
* @return string
*/
protected function substituteResultWithError( $e ) {
+ global $wgShowHostnames;
+
$result = $this->getResult();
// Printer may not be initialized if the extractRequestParams() fails for the main module
if ( !isset ( $this->mPrinter ) ) {
@@ -533,8 +538,12 @@ class ApiMain extends ApiBase {
if ( !is_null( $requestid ) ) {
$result->addValue( null, 'requestid', $requestid );
}
- // servedby is especially useful when debugging errors
- $result->addValue( null, 'servedby', wfHostName() );
+
+ if ( $wgShowHostnames ) {
+ // servedby is especially useful when debugging errors
+ $result->addValue( null, 'servedby', wfHostName() );
+ }
+
$result->addValue( null, 'error', $errMessage );
return $errMessage['code'];
@@ -545,15 +554,20 @@ class ApiMain extends ApiBase {
* @return array
*/
protected function setupExecuteAction() {
+ global $wgShowHostnames;
+
// First add the id to the top element
$result = $this->getResult();
$requestid = $this->getParameter( 'requestid' );
if ( !is_null( $requestid ) ) {
$result->addValue( null, 'requestid', $requestid );
}
- $servedby = $this->getParameter( 'servedby' );
- if ( $servedby ) {
- $result->addValue( null, 'servedby', wfHostName() );
+
+ if ( $wgShowHostnames ) {
+ $servedby = $this->getParameter( 'servedby' );
+ if ( $servedby ) {
+ $result->addValue( null, 'servedby', wfHostName() );
+ }
}
$params = $this->extractRequestParams();
@@ -591,8 +605,7 @@ class ApiMain extends ApiBase {
if ( !isset( $moduleParams['token'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'token' ) );
} else {
- global $wgUser;
- if ( !$wgUser->matchEditToken( $moduleParams['token'], $salt, $this->getRequest() ) ) {
+ if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt, $this->getRequest() ) ) {
$this->dieUsageMsg( 'sessionfailure' );
}
}
@@ -634,9 +647,9 @@ class ApiMain extends ApiBase {
* @param $module ApiBase An Api module
*/
protected function checkExecutePermissions( $module ) {
- global $wgUser;
+ $user = $this->getUser();
if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) &&
- !$wgUser->isAllowed( 'read' ) )
+ !$user->isAllowed( 'read' ) )
{
$this->dieUsageMsg( 'readrequired' );
}
@@ -644,7 +657,7 @@ class ApiMain extends ApiBase {
if ( !$this->mEnableWrite ) {
$this->dieUsageMsg( 'writedisabled' );
}
- if ( !$wgUser->isAllowed( 'writeapi' ) ) {
+ if ( !$user->isAllowed( 'writeapi' ) ) {
$this->dieUsageMsg( 'writerequired' );
}
if ( wfReadOnly() ) {
@@ -660,7 +673,7 @@ class ApiMain extends ApiBase {
*/
protected function setupExternalResponse( $module, $params ) {
// Ignore mustBePosted() for internal calls
- if ( $module->mustBePosted() && !$this->mRequest->wasPosted() ) {
+ if ( $module->mustBePosted() && !$this->getRequest()->wasPosted() ) {
$this->dieUsageMsg( array( 'mustbeposted', $this->mAction ) );
}
@@ -807,8 +820,8 @@ class ApiMain extends ApiBase {
'** **',
'** This is an auto-generated MediaWiki API documentation page **',
'** **',
- '** Documentation and Examples: **',
- '** https://www.mediawiki.org/wiki/API **',
+ '** Documentation and Examples: **',
+ '** https://www.mediawiki.org/wiki/API **',
'** **',
'**********************************************************************************************************',
'',
@@ -970,8 +983,7 @@ class ApiMain extends ApiBase {
*/
public function canApiHighLimits() {
if ( !isset( $this->mCanApiHighLimits ) ) {
- global $wgUser;
- $this->mCanApiHighLimits = $wgUser->isAllowed( 'apihighlimits' );
+ $this->mCanApiHighLimits = $this->getUser()->isAllowed( 'apihighlimits' );
}
return $this->mCanApiHighLimits;
diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php
index a0b7bcbe..f0a25e4a 100644
--- a/includes/api/ApiMove.php
+++ b/includes/api/ApiMove.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* API Module to move pages
* @ingroup API
@@ -40,7 +35,7 @@ class ApiMove extends ApiBase {
}
public function execute() {
- global $wgUser;
+ $user = $this->getUser();
$params = $this->extractRequestParams();
if ( is_null( $params['reason'] ) ) {
$params['reason'] = '';
@@ -75,9 +70,9 @@ class ApiMove extends ApiBase {
&& !RepoGroup::singleton()->getLocalRepo()->findFile( $toTitle )
&& wfFindFile( $toTitle ) )
{
- if ( !$params['ignorewarnings'] && $wgUser->isAllowed( 'reupload-shared' ) ) {
+ if ( !$params['ignorewarnings'] && $user->isAllowed( 'reupload-shared' ) ) {
$this->dieUsageMsg( 'sharedfile-exists' );
- } elseif ( !$wgUser->isAllowed( 'reupload-shared' ) ) {
+ } elseif ( !$user->isAllowed( 'reupload-shared' ) ) {
$this->dieUsageMsg( 'cantoverwrite-sharedfile' );
}
}
@@ -89,7 +84,7 @@ class ApiMove extends ApiBase {
}
$r = array( 'from' => $fromTitle->getPrefixedText(), 'to' => $toTitle->getPrefixedText(), 'reason' => $params['reason'] );
- if ( !$params['noredirect'] || !$wgUser->isAllowed( 'suppressredirect' ) ) {
+ if ( !$params['noredirect'] || !$user->isAllowed( 'suppressredirect' ) ) {
$r['redirectcreated'] = '';
}
@@ -254,7 +249,7 @@ class ApiMove extends ApiBase {
return '';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=move&from=Exampel&to=Example&token=123ABC&reason=Misspelled%20title&movetalk=&noredirect='
);
diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php
index 65ee0db9..0727cffd 100644
--- a/includes/api/ApiOpenSearch.php
+++ b/includes/api/ApiOpenSearch.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* @ingroup API
*/
@@ -116,10 +111,10 @@ class ApiOpenSearch extends ApiBase {
}
public function getDescription() {
- return 'Searches the wiki using the OpenSearch protocol';
+ return 'Search the wiki using the OpenSearch protocol';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=opensearch&search=Te'
);
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
index 4718221d..7b84c473 100644
--- a/includes/api/ApiPageSet.php
+++ b/includes/api/ApiPageSet.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* This class contains a list of pages that the client has requested.
* Initially, when the client passes in titles=, pageids=, or revisions=
@@ -57,7 +52,7 @@ class ApiPageSet extends ApiQueryBase {
/**
* Constructor
- * @param $query ApiQuery
+ * @param $query ApiQueryBase
* @param $resolveRedirects bool Whether redirects should be resolved
* @param $convertTitles bool
*/
@@ -464,7 +459,7 @@ class ApiPageSet extends ApiQueryBase {
__METHOD__ );
$this->profileDBOut();
}
-
+
$this->initFromQueryResult( $res, $remaining, false ); // process PageIDs
// Resolve any found redirects
@@ -645,8 +640,8 @@ class ApiPageSet extends ApiQueryBase {
// We found pages that aren't in the redirect table
// Add them
foreach ( $this->mPendingRedirectIDs as $id => $title ) {
- $article = new Article( $title );
- $rt = $article->insertRedirect();
+ $page = WikiPage::factory( $title );
+ $rt = $page->insertRedirect();
if ( !$rt ) {
// What the hell. Let's just ignore this
continue;
@@ -743,7 +738,7 @@ class ApiPageSet extends ApiQueryBase {
return $array;
}
- protected function getAllowedParams() {
+ public function getAllowedParams() {
return array(
'titles' => array(
ApiBase::PARAM_ISMULTI => true
@@ -759,7 +754,7 @@ class ApiPageSet extends ApiQueryBase {
);
}
- protected function getParamDescription() {
+ public function getParamDescription() {
return array(
'titles' => 'A list of titles to work on',
'pageids' => 'A list of page IDs to work on',
diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php
index ad098920..f2263476 100644
--- a/includes/api/ApiParamInfo.php
+++ b/includes/api/ApiParamInfo.php
@@ -24,65 +24,86 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* @ingroup API
*/
class ApiParamInfo extends ApiBase {
+ /**
+ * @var ApiQuery
+ */
+ protected $queryObj;
+
public function __construct( $main, $action ) {
parent::__construct( $main, $action );
+ $this->queryObj = new ApiQuery( $this->getMain(), 'query' );
}
public function execute() {
// Get parameters
$params = $this->extractRequestParams();
$result = $this->getResult();
- $queryObj = new ApiQuery( $this->getMain(), 'query' );
- $r = array();
+
+ $res = array();
if ( is_array( $params['modules'] ) ) {
- $modArr = $this->getMain()->getModules();
- $r['modules'] = array();
- foreach ( $params['modules'] as $m ) {
- if ( !isset( $modArr[$m] ) ) {
- $r['modules'][] = array( 'name' => $m, 'missing' => '' );
+ $modules = $this->getMain()->getModules();
+ $res['modules'] = array();
+ foreach ( $params['modules'] as $mod ) {
+ if ( !isset( $modules[$mod] ) ) {
+ $res['modules'][] = array( 'name' => $mod, 'missing' => '' );
continue;
}
- $obj = new $modArr[$m]( $this->getMain(), $m );
- $a = $this->getClassInfo( $obj );
- $a['name'] = $m;
- $r['modules'][] = $a;
+ $obj = new $modules[$mod]( $this->getMain(), $mod );
+
+ $item = $this->getClassInfo( $obj );
+ $item['name'] = $mod;
+ $res['modules'][] = $item;
}
- $result->setIndexedTagName( $r['modules'], 'module' );
+ $result->setIndexedTagName( $res['modules'], 'module' );
}
+
if ( is_array( $params['querymodules'] ) ) {
- $qmodArr = $queryObj->getModules();
- $r['querymodules'] = array();
+ $queryModules = $this->queryObj->getModules();
+ $res['querymodules'] = array();
foreach ( $params['querymodules'] as $qm ) {
- if ( !isset( $qmodArr[$qm] ) ) {
- $r['querymodules'][] = array( 'name' => $qm, 'missing' => '' );
+ if ( !isset( $queryModules[$qm] ) ) {
+ $res['querymodules'][] = array( 'name' => $qm, 'missing' => '' );
continue;
}
- $obj = new $qmodArr[$qm]( $this, $qm );
- $a = $this->getClassInfo( $obj );
- $a['name'] = $qm;
- $a['querytype'] = $queryObj->getModuleType( $qm );
- $r['querymodules'][] = $a;
+ $obj = new $queryModules[$qm]( $this, $qm );
+ $item = $this->getClassInfo( $obj );
+ $item['name'] = $qm;
+ $item['querytype'] = $this->queryObj->getModuleType( $qm );
+ $res['querymodules'][] = $item;
}
- $result->setIndexedTagName( $r['querymodules'], 'module' );
+ $result->setIndexedTagName( $res['querymodules'], 'module' );
}
+
if ( $params['mainmodule'] ) {
- $r['mainmodule'] = $this->getClassInfo( $this->getMain() );
+ $res['mainmodule'] = $this->getClassInfo( $this->getMain() );
}
+
if ( $params['pagesetmodule'] ) {
- $pageSet = new ApiPageSet( $queryObj );
- $r['pagesetmodule'] = $this->getClassInfo( $pageSet );
+ $pageSet = new ApiPageSet( $this->queryObj );
+ $res['pagesetmodule'] = $this->getClassInfo( $pageSet );
+ }
+
+ if ( is_array( $params['formatmodules'] ) ) {
+ $formats = $this->getMain()->getFormats();
+ $res['formatmodules'] = array();
+ foreach ( $params['formatmodules'] as $f ) {
+ if ( !isset( $formats[$f] ) ) {
+ $res['formatmodules'][] = array( 'name' => $f, 'missing' => '' );
+ continue;
+ }
+ $obj = new $formats[$f]( $this, $f );
+ $item = $this->getClassInfo( $obj );
+ $item['name'] = $f;
+ $res['formatmodules'][] = $item;
+ }
+ $result->setIndexedTagName( $res['formatmodules'], 'module' );
}
- $result->addValue( null, $this->getModuleName(), $r );
+ $result->addValue( null, $this->getModuleName(), $res );
}
/**
@@ -92,9 +113,10 @@ class ApiParamInfo extends ApiBase {
function getClassInfo( $obj ) {
$result = $this->getResult();
$retval['classname'] = get_class( $obj );
- $retval['description'] = implode( "\n", (array)$obj->getDescription() );
- $examples = (array)$obj->getExamples();
- $retval['examples'] = implode( "\n", $examples );
+ $retval['description'] = implode( "\n", (array)$obj->getFinalDescription() );
+
+ $retval['examples'] = '';
+
$retval['version'] = implode( "\n", (array)$obj->getVersion() );
$retval['prefix'] = $obj->getModulePrefix();
@@ -122,9 +144,31 @@ class ApiParamInfo extends ApiBase {
}
$result->setIndexedTagName( $retval['helpurls'], 'helpurl' );
- $retval['allexamples'] = $examples;
- if ( isset( $retval['allexamples'][0] ) && $retval['allexamples'][0] === false ) {
- $retval['allexamples'] = array();
+ $examples = $obj->getExamples();
+ $retval['allexamples'] = array();
+ if ( $examples !== false ) {
+ if ( is_string( $examples ) ) {
+ $examples = array( $examples );
+ }
+ foreach( $examples as $k => $v ) {
+ if ( strlen( $retval['examples'] ) ) {
+ $retval['examples'] .= ' ';
+ }
+ $item = array();
+ if ( is_numeric( $k ) ) {
+ $retval['examples'] .= $v;
+ $result->setContent( $item, $v );
+ } else {
+ if ( !is_array( $v ) ) {
+ $item['description'] = $v;
+ } else {
+ $item['description'] = implode( $v, "\n" );
+ }
+ $retval['examples'] .= $item['description'] . ' ' . $k;
+ $result->setContent( $item, $k );
+ }
+ $retval['allexamples'][] = $item;
+ }
}
$result->setIndexedTagName( $retval['allexamples'], 'example' );
@@ -219,15 +263,27 @@ class ApiParamInfo extends ApiBase {
}
public function getAllowedParams() {
+ $modules = array_keys( $this->getMain()->getModules() );
+ sort( $modules );
+ $querymodules = array_keys( $this->queryObj->getModules() );
+ sort( $querymodules );
+ $formatmodules = array_keys( $this->getMain()->getFormats() );
+ sort( $formatmodules );
return array(
'modules' => array(
- ApiBase::PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => $modules,
),
'querymodules' => array(
- ApiBase::PARAM_ISMULTI => true
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => $querymodules,
),
'mainmodule' => false,
'pagesetmodule' => false,
+ 'formatmodules' => array(
+ ApiBase::PARAM_ISMULTI => true,
+ ApiBase::PARAM_TYPE => $formatmodules,
+ )
);
}
@@ -237,6 +293,7 @@ class ApiParamInfo extends ApiBase {
'querymodules' => 'List of query module names (value of prop=, meta= or list= parameter)',
'mainmodule' => 'Get information about the main (top-level) module as well',
'pagesetmodule' => 'Get information about the pageset module (providing titles= and friends) as well',
+ 'formatmodules' => 'List of format module names (value of format= parameter)',
);
}
@@ -244,7 +301,7 @@ class ApiParamInfo extends ApiBase {
return 'Obtain information about certain API parameters and errors';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=paraminfo&modules=parse&querymodules=allpages|siteinfo'
);
diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php
index a3159186..893491b9 100644
--- a/includes/api/ApiParse.php
+++ b/includes/api/ApiParse.php
@@ -22,11 +22,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* @ingroup API
*/
@@ -63,7 +58,8 @@ class ApiParse extends ApiBase {
// The parser needs $wgTitle to be set, apparently the
// $title parameter in Parser::parse isn't enough *sigh*
- global $wgParser, $wgUser, $wgTitle, $wgLang;
+ // TODO: Does this still need $wgTitle?
+ global $wgParser, $wgTitle, $wgLang;
// Currently unnecessary, code to act as a safeguard against any change in current behaviour of uselang breaks
$oldLang = null;
@@ -72,7 +68,7 @@ class ApiParse extends ApiBase {
$wgLang = Language::factory( $params['uselang'] );
}
- $popts = new ParserOptions();
+ $popts = ParserOptions::newFromContext( $this->getContext() );
$popts->setTidy( true );
$popts->enableLimitReport( !$params['disablepp'] );
@@ -88,7 +84,7 @@ class ApiParse extends ApiBase {
if ( !$rev ) {
$this->dieUsage( "There is no revision ID $oldid", 'missingrev' );
}
- if ( !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
$this->dieUsage( "You don't have permission to view deleted revisions", 'permissiondenied' );
}
@@ -98,22 +94,20 @@ class ApiParse extends ApiBase {
// 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,
+ // May get from/save to parser cache
+ $p_result = $this->getParsedSectionOrText( $titleObj, $popts, $pageid,
isset( $prop['wikitext'] ) ) ;
} else { // This is an old revision, so get the text differently
- $this->text = $rev->getText( Revision::FOR_THIS_USER );
-
- $wgTitle = $titleObj;
+ $this->text = $rev->getText( Revision::FOR_THIS_USER, $this->getUser() );
if ( $this->section !== false ) {
$this->text = $this->getSectionText( $this->text, 'r' . $rev->getId() );
}
+ // Should we save old revision parses to the parser cache?
$p_result = $wgParser->parse( $this->text, $titleObj, $popts );
}
- } else { // Not $oldid
+ } else { // Not $oldid, but $pageid or $page
if ( $params['redirects'] ) {
$reqParams = array(
'action' => 'query',
@@ -155,12 +149,12 @@ class ApiParse extends ApiBase {
}
$wgTitle = $titleObj;
- $articleObj = new Article( $titleObj, 0 );
if ( isset( $prop['revid'] ) ) {
- $oldid = $articleObj->getRevIdFetched();
+ $oldid = $titleObj->getLatestRevID();
}
- $p_result = $this->getParsedSectionOrText( $articleObj, $titleObj, $popts, $pageid,
+ // Potentially cached
+ $p_result = $this->getParsedSectionOrText( $titleObj, $popts, $pageid,
isset( $prop['wikitext'] ) ) ;
}
} else { // Not $oldid, $pageid, $page. Hence based on $text
@@ -180,10 +174,11 @@ class ApiParse extends ApiBase {
}
if ( $params['pst'] || $params['onlypst'] ) {
- $this->pstText = $wgParser->preSaveTransform( $this->text, $titleObj, $wgUser, $popts );
+ $this->pstText = $wgParser->preSaveTransform( $this->text, $titleObj, $this->getUser(), $popts );
}
if ( $params['onlypst'] ) {
// Build a result and bail out
+ $result_array = array();
$result_array['text'] = array();
$result->setContent( $result_array['text'], $this->pstText );
if ( isset( $prop['wikitext'] ) ) {
@@ -193,6 +188,7 @@ class ApiParse extends ApiBase {
$result->addValue( null, $this->getModuleName(), $result_array );
return;
}
+ // Not cached (save or load)
$p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts );
}
@@ -215,7 +211,7 @@ class ApiParse extends ApiBase {
if ( !is_null( $params['summary'] ) ) {
$result_array['parsedsummary'] = array();
- $result->setContent( $result_array['parsedsummary'], $wgUser->getSkin()->formatComment( $params['summary'], $titleObj ) );
+ $result->setContent( $result_array['parsedsummary'], Linker::formatComment( $params['summary'], $titleObj ) );
}
if ( isset( $prop['langlinks'] ) ) {
@@ -257,16 +253,16 @@ class ApiParse extends ApiBase {
}
if ( isset( $prop['headitems'] ) || isset( $prop['headhtml'] ) ) {
- $context = new RequestContext;
+ $context = $this->getContext();
+ $context->setTitle( $titleObj );
$context->getOutput()->addParserOutputNoText( $p_result );
if ( isset( $prop['headitems'] ) ) {
$headItems = $this->formatHeadItems( $p_result->getHeadItems() );
- $context->getSkin()->setupUserCss( $context->getOutput() );
$css = $this->formatCss( $context->getOutput()->buildCssLinksArray() );
- $scripts = array( $context->getOutput()->getHeadScripts( $context->getSkin() ) );
+ $scripts = array( $context->getOutput()->getHeadScripts() );
$result_array['headitems'] = array_merge( $headItems, $css, $scripts );
}
@@ -311,29 +307,29 @@ class ApiParse extends ApiBase {
}
/**
- * @param $articleObj Article
* @param $titleObj Title
* @param $popts ParserOptions
* @param $pageId Int
* @param $getWikitext Bool
* @return ParserOutput
*/
- private function getParsedSectionOrText( $articleObj, $titleObj, $popts, $pageId = null, $getWikitext = false ) {
- if ( $this->section !== false ) {
- global $wgParser;
+ private function getParsedSectionOrText( $titleObj, $popts, $pageId = null, $getWikitext = false ) {
+ global $wgParser;
+
+ $page = WikiPage::factory( $titleObj );
- $this->text = $this->getSectionText( $articleObj->getRawText(), !is_null ( $pageId )
+ if ( $this->section !== false ) {
+ $this->text = $this->getSectionText( $page->getRawText(), !is_null( $pageId )
? 'page id ' . $pageId : $titleObj->getText() );
+ // Not cached (save or load)
return $wgParser->parse( $this->text, $titleObj, $popts );
} else {
// Try the parser cache first
- $pout = $articleObj->getParserOutput();
+ // getParserOutput will save to Parser cache if able
+ $pout = $page->getParserOutput( $popts );
if ( $getWikitext ) {
- $rev = Revision::newFromTitle( $titleObj );
- if ( $rev ) {
- $this->text = $rev->getText();
- }
+ $this->text = $page->getRawText();
}
return $pout;
}
@@ -341,6 +337,7 @@ class ApiParse extends ApiBase {
private function getSectionText( $text, $what ) {
global $wgParser;
+ // Not cached (save or load)
$text = $wgParser->getSection( $text, $this->section, false );
if ( $text === false ) {
$this->dieUsage( "There is no section {$this->section} in " . $what, 'nosuchsection' );
@@ -377,7 +374,7 @@ class ApiParse extends ApiBase {
}
private function categoriesHtml( $categories ) {
- $context = $this->createContext();
+ $context = $this->getContext();
$context->getOutput()->addCategoryLinks( $categories );
return $context->getSkin()->getCategories();
}
@@ -385,8 +382,12 @@ class ApiParse extends ApiBase {
/**
* @deprecated since 1.18 No modern skin generates language links this way, please use language links
* data to generate your own HTML.
+ * @param $languages array
+ * @return string
*/
private function languagesHtml( $languages ) {
+ wfDeprecated( __METHOD__, '1.18' );
+
global $wgContLang, $wgHideInterlanguageLinks;
if ( $wgHideInterlanguageLinks || count( $languages ) == 0 ) {
@@ -584,7 +585,7 @@ class ApiParse extends ApiBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=parse&text={{Project:Sandbox}}'
);
diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php
index 8e6e8738..1332f263 100644
--- a/includes/api/ApiPatrol.php
+++ b/includes/api/ApiPatrol.php
@@ -24,10 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- require_once ( 'ApiBase.php' );
-}
-
/**
* Allows user to patrol pages
* @ingroup API
@@ -42,15 +38,13 @@ 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 = $rc->doMarkPatrolled( $wgUser );
+ $retval = $rc->doMarkPatrolled( $this->getUser() );
if ( $retval ) {
$this->dieUsageMsg( reset( $retval ) );
@@ -104,7 +98,7 @@ class ApiPatrol extends ApiBase {
return 'patrol';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=patrol&token=123abc&rcid=230672766'
);
diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php
index ac1e0736..fb225d86 100644
--- a/includes/api/ApiProtect.php
+++ b/includes/api/ApiProtect.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* @ingroup API
*/
@@ -39,7 +34,7 @@ class ApiProtect extends ApiBase {
}
public function execute() {
- global $wgUser, $wgRestrictionLevels;
+ global $wgRestrictionLevels;
$params = $this->extractRequestParams();
$titleObj = Title::newFromText( $params['title'] );
@@ -47,7 +42,7 @@ class ApiProtect extends ApiBase {
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
- $errors = $titleObj->getUserPermissionsErrors( 'protect', $wgUser );
+ $errors = $titleObj->getUserPermissionsErrors( 'protect', $this->getUser() );
if ( $errors ) {
// We don't care about multiple errors, just report one of them
$this->dieUsageMsg( reset( $errors ) );
@@ -107,20 +102,16 @@ class ApiProtect extends ApiBase {
}
$cascade = $params['cascade'];
- $articleObj = new Article( $titleObj );
$watch = $params['watch'] ? 'watch' : $params['watchlist'];
$this->setWatch( $watch, $titleObj );
- if ( $titleObj->exists() ) {
- $ok = $articleObj->updateRestrictions( $protections, $params['reason'], $cascade, $expiryarray );
- } else {
- $ok = $titleObj->updateTitleProtection( $protections['create'], $params['reason'], $expiryarray['create'] );
- }
- if ( !$ok ) {
- // This is very weird. Maybe the article was deleted or the user was blocked/desysopped in the meantime?
- // Just throw an unknown error in this case, as it's very likely to be a race condition
- $this->dieUsageMsg( array() );
+ $pageObj = WikiPage::factory( $titleObj );
+ $status = $pageObj->doUpdateRestrictions( $protections, $expiryarray, $cascade, $params['reason'], $this->getUser() );
+
+ if ( !$status->isOK() ) {
+ $errors = $status->getErrorsArray();
+ $this->dieUsageMsg( $errors[0] );
}
$res = array(
'title' => $titleObj->getPrefixedText(),
@@ -217,7 +208,7 @@ class ApiProtect extends ApiBase {
return '';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=sysop|move=sysop&cascade=&expiry=20070901163000|never',
'api.php?action=protect&title=Main%20Page&token=123ABC&protections=edit=all|move=all&reason=Lifting%20restrictions'
diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php
index ac5f0207..9e9320fb 100644
--- a/includes/api/ApiPurge.php
+++ b/includes/api/ApiPurge.php
@@ -25,10 +25,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- require_once( 'ApiBase.php' );
-}
-
/**
* API interface for page purging
* @ingroup API
@@ -43,42 +39,58 @@ class ApiPurge extends ApiBase {
* Purges the cache of a page
*/
public function execute() {
- global $wgUser;
+ $user = $this->getUser();
$params = $this->extractRequestParams();
- if ( !$wgUser->isAllowed( 'purge' ) && !$this->getMain()->isInternalMode() &&
- !$this->getMain()->getRequest()->wasPosted() ) {
+ if ( !$user->isAllowed( 'purge' ) && !$this->getMain()->isInternalMode() &&
+ !$this->getRequest()->wasPosted() ) {
$this->dieUsageMsg( array( 'mustbeposted', $this->getModuleName() ) );
}
$forceLinkUpdate = $params['forcelinkupdate'];
+ $pageSet = new ApiPageSet( $this );
+ $pageSet->execute();
$result = array();
- foreach ( $params['titles'] as $t ) {
+ foreach( $pageSet->getInvalidTitles() as $title ) {
$r = array();
- $title = Title::newFromText( $t );
- if ( !$title instanceof Title ) {
- $r['title'] = $t;
- $r['invalid'] = '';
- $result[] = $r;
- continue;
- }
+ $r['title'] = $title;
+ $r['invalid'] = '';
+ $result[] = $r;
+ }
+ foreach( $pageSet->getMissingPageIDs() as $p ) {
+ $page = array();
+ $page['pageid'] = $p;
+ $page['missing'] = '';
+ $result[] = $page;
+ }
+ foreach( $pageSet->getMissingRevisionIDs() as $r ) {
+ $rev = array();
+ $rev['revid'] = $r;
+ $rev['missing'] = '';
+ $result[] = $rev;
+ }
+
+ foreach ( $pageSet->getTitles() as $title ) {
+ $r = array();
+
ApiQueryBase::addTitleInfo( $r, $title );
if ( !$title->exists() ) {
$r['missing'] = '';
$result[] = $r;
continue;
}
- $context = $this->createContext();
- $context->setTitle( $title );
- $article = Article::newFromTitle( $title, $context );
- $article->doPurge(); // Directly purge and skip the UI part of purge().
+
+ $page = WikiPage::factory( $title );
+ $page->doPurge(); // Directly purge and skip the UI part of purge().
$r['purged'] = '';
if( $forceLinkUpdate ) {
- if ( !$wgUser->pingLimiter() ) {
+ if ( !$user->pingLimiter() ) {
global $wgParser, $wgEnableParserCache;
- $popts = new ParserOptions();
- $p_result = $wgParser->parse( $article->getContent(), $title, $popts );
+
+ $popts = ParserOptions::newFromContext( $this->getContext() );
+ $p_result = $wgParser->parse( $page->getRawText(), $title, $popts,
+ true, true, $page->getLatest() );
# Update the links tables
$u = new LinksUpdate( $title, $p_result );
@@ -88,7 +100,7 @@ class ApiPurge extends ApiBase {
if ( $wgEnableParserCache ) {
$pcache = ParserCache::singleton();
- $pcache->save( $p_result, $article, $popts );
+ $pcache->save( $p_result, $page, $popts );
}
} else {
$this->setWarning( $this->parseMsg( array( 'actionthrottledtext' ) ) );
@@ -108,18 +120,15 @@ class ApiPurge extends ApiBase {
}
public function getAllowedParams() {
- return array(
- 'titles' => array(
- ApiBase::PARAM_ISMULTI => true,
- ApiBase::PARAM_REQUIRED => true
- ),
+ $psModule = new ApiPageSet( $this );
+ return $psModule->getAllowedParams() + array(
'forcelinkupdate' => false,
);
}
public function getParamDescription() {
- return array(
- 'titles' => 'A list of titles',
+ $psModule = new ApiPageSet( $this );
+ return $psModule->getParamDescription() + array(
'forcelinkupdate' => 'Update the links tables',
);
}
@@ -131,14 +140,17 @@ class ApiPurge extends ApiBase {
}
public function getPossibleErrors() {
- return array_merge( parent::getPossibleErrors(), array(
- array( 'cantpurge' ),
- ) );
+ $psModule = new ApiPageSet( $this );
+ return array_merge(
+ parent::getPossibleErrors(),
+ array( array( 'cantpurge' ), ),
+ $psModule->getPossibleErrors()
+ );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'api.php?action=purge&titles=Main_Page|API'
+ 'api.php?action=purge&titles=Main_Page|API' => 'Purge the "Main Page" and the "API" page',
);
}
diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php
index 717b43b4..cd54a7da 100644
--- a/includes/api/ApiQuery.php
+++ b/includes/api/ApiQuery.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
-
/**
* This is the main query class. It behaves similar to ApiMain: based on the
* parameters given, it will create a list of titles to work on (an ApiPageSet
@@ -106,6 +101,8 @@ class ApiQuery extends ApiBase {
private $mSlaveDB = null;
private $mNamedDB = array();
+ protected $mAllowedGenerators = array();
+
public function __construct( $main, $action ) {
parent::__construct( $main, $action );
@@ -119,9 +116,8 @@ class ApiQuery extends ApiBase {
$this->mListModuleNames = array_keys( $this->mQueryListModules );
$this->mMetaModuleNames = array_keys( $this->mQueryMetaModules );
- // Allow the entire list of modules at first,
- // but during module instantiation check if it can be used as a generator.
- $this->mAllowedGenerators = array_merge( $this->mListModuleNames, $this->mPropModuleNames );
+ $this->makeHelpMsgHelper( $this->mQueryPropModules, 'prop' );
+ $this->makeHelpMsgHelper( $this->mQueryListModules, 'list' );
}
/**
@@ -179,7 +175,7 @@ class ApiQuery extends ApiBase {
/**
* Get the array mapping module names to class names
- * @return array(modulename => classname)
+ * @return array array(modulename => classname)
*/
function getModules() {
return array_merge( $this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules );
@@ -281,6 +277,8 @@ class ApiQuery extends ApiBase {
* The cache mode may increase in the level of privacy, but public modules
* added to private data do not decrease the level of privacy.
*
+ * @param $cacheMode string
+ * @param $modCacheMode string
* @return string
*/
protected function mergeCacheMode( $cacheMode, $modCacheMode ) {
@@ -441,7 +439,7 @@ class ApiQuery extends ApiBase {
$vals = array();
ApiQueryBase::addTitleInfo( $vals, $title );
$vals['special'] = '';
- if ( $title->getNamespace() == NS_SPECIAL &&
+ if ( $title->isSpecialPage() &&
!SpecialPageFactory::exists( $title->getDbKey() ) ) {
$vals['missing'] = '';
} elseif ( $title->getNamespace() == NS_MEDIA &&
@@ -485,7 +483,7 @@ class ApiQuery extends ApiBase {
$titles = $pageSet->getGoodTitles();
if ( count( $titles ) ) {
foreach ( $titles as $title ) {
- if ( $title->userCanRead() ) {
+ if ( $title->userCan( 'read' ) ) {
$exportTitles[] = $title;
}
}
@@ -634,6 +632,9 @@ class ApiQuery extends ApiBase {
$moduleDescriptions = array();
foreach ( $moduleList as $moduleName => $moduleClass ) {
+ /**
+ * @var $module ApiQueryBase
+ */
$module = new $moduleClass( $this, $moduleName, null );
$msg = ApiMain::makeHelpMsgHeader( $module, $paramName );
@@ -673,7 +674,7 @@ class ApiQuery extends ApiBase {
'NOTE: generator parameter names must be prefixed with a \'g\', see examples' ),
'redirects' => 'Automatically resolve redirects',
'converttitles' => array( "Convert titles to other variants if necessary. Only works if the wiki's content language supports variant conversion.",
- 'Languages that support variant conversion include kk, ku, gan, tg, sr, zh' ),
+ 'Languages that support variant conversion include gan, iu, kk, ku, shi, sr, tg, zh' ),
'indexpageids' => 'Include an additional pageids section listing all returned page IDs',
'export' => 'Export the current revisions of all given or generated pages',
'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export',
@@ -695,7 +696,7 @@ class ApiQuery extends ApiBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment',
'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions',
diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php
index fc56965e..78367a45 100644
--- a/includes/api/ApiQueryAllCategories.php
+++ b/includes/api/ApiQueryAllCategories.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to enumerate all categories, even the ones that don't have
* category pages.
@@ -55,7 +50,6 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
/**
* @param $resultPageSet ApiPageSet
- * @return void
*/
private function run( $resultPageSet = null ) {
$db = $this->getDB();
@@ -196,7 +190,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase {
return 'Enumerate all categories';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=allcategories&acprop=size',
'api.php?action=query&generator=allcategories&gacprefix=List&prop=info',
diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php
index 822d0136..903f144f 100644
--- a/includes/api/ApiQueryAllLinks.php
+++ b/includes/api/ApiQueryAllLinks.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to enumerate links from all pages together.
*
@@ -221,7 +216,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=alllinks&alunique=&alfrom=B',
);
diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php
index 0443269e..ac112ef9 100644
--- a/includes/api/ApiQueryAllUsers.php
+++ b/includes/api/ApiQueryAllUsers.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to enumerate all registered users.
*
@@ -65,10 +60,15 @@ class ApiQueryAllUsers extends ApiQueryBase {
$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 );
+ # MySQL doesn't seem to use 'equality propagation' here, so like the
+ # ActiveUsers special page, we have to use rc_user_text for some cases.
+ $userFieldToSort = $params['activeusers'] ? 'rc_user_text' : 'user_name';
+
+ $this->addWhereRange( $userFieldToSort, $dir, $from, $to );
if ( !is_null( $params['prefix'] ) ) {
- $this->addWhere( 'user_name' . $db->buildLike( $this->keyToTitle( $params['prefix'] ), $db->anyString() ) );
+ $this->addWhere( $userFieldToSort .
+ $db->buildLike( $this->keyToTitle( $params['prefix'] ), $db->anyString() ) );
}
if ( !is_null( $params['rights'] ) ) {
@@ -148,7 +148,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
$timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays*24*3600 );
$this->addWhere( "rc_timestamp >= {$db->addQuotes( $timestamp )}" );
- $this->addOption( 'GROUP BY', 'user_name' );
+ $this->addOption( 'GROUP BY', $userFieldToSort );
}
$this->addOption( 'LIMIT', $sqlLimit );
@@ -235,10 +235,12 @@ class ApiQueryAllUsers extends ApiQueryBase {
'MediaWiki configuration error: the database contains more user groups than known to User::getAllGroups() function' );
}
+ $lastUserObj = User::newFromName( $lastUser );
+
// Add user's group info
if ( $fld_groups ) {
- if ( !isset( $lastUserData['groups'] ) ) {
- $lastUserData['groups'] = ApiQueryUsers::getAutoGroups( User::newFromName( $lastUser ) );
+ if ( !isset( $lastUserData['groups'] ) && $lastUserObj ) {
+ $lastUserData['groups'] = ApiQueryUsers::getAutoGroups( $lastUserObj );
}
if ( !is_null( $row->ug_group2 ) ) {
@@ -247,13 +249,13 @@ class ApiQueryAllUsers extends ApiQueryBase {
$result->setIndexedTagName( $lastUserData['groups'], 'g' );
}
- if ( $fld_implicitgroups && !isset( $lastUserData['implicitgroups'] ) ) {
- $lastUserData['implicitgroups'] = ApiQueryUsers::getAutoGroups( User::newFromName( $lastUser ) );
+ if ( $fld_implicitgroups && !isset( $lastUserData['implicitgroups'] ) && $lastUserObj ) {
+ $lastUserData['implicitgroups'] = ApiQueryUsers::getAutoGroups( $lastUserObj );
$result->setIndexedTagName( $lastUserData['implicitgroups'], 'g' );
}
if ( $fld_rights ) {
- if ( !isset( $lastUserData['rights'] ) ) {
- $lastUserData['rights'] = User::getGroupPermissions( User::newFromName( $lastUser )->getAutomaticGroups() );
+ if ( !isset( $lastUserData['rights'] ) && $lastUserObj ) {
+ $lastUserData['rights'] = User::getGroupPermissions( $lastUserObj->getAutomaticGroups() );
}
if ( !is_null( $row->ug_group2 ) ) {
$lastUserData['rights'] = array_unique( array_merge( $lastUserData['rights'],
@@ -362,7 +364,7 @@ class ApiQueryAllUsers extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=allusers&aufrom=Y',
);
diff --git a/includes/api/ApiQueryAllimages.php b/includes/api/ApiQueryAllimages.php
index f0fc39e3..ca344f73 100644
--- a/includes/api/ApiQueryAllimages.php
+++ b/includes/api/ApiQueryAllimages.php
@@ -26,11 +26,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to enumerate all available pages.
*
@@ -249,14 +244,16 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Simple Use',
- ' Show a list of images starting at the letter "B"',
- ' api.php?action=query&list=allimages&aifrom=B',
- 'Using as Generator',
- ' Show info about 4 images starting at the letter "T"',
- ' api.php?action=query&generator=allimages&gailimit=4&gaifrom=T&prop=imageinfo',
+ 'api.php?action=query&list=allimages&aifrom=B' => array(
+ 'Simple Use',
+ 'Show a list of images starting at the letter "B"',
+ ),
+ 'api.php?action=query&generator=allimages&gailimit=4&gaifrom=T&prop=imageinfo' => array(
+ 'Using as Generator',
+ 'Show info about 4 images starting at the letter "T"',
+ ),
);
}
diff --git a/includes/api/ApiQueryAllmessages.php b/includes/api/ApiQueryAllmessages.php
index d636c613..44774927 100644
--- a/includes/api/ApiQueryAllmessages.php
+++ b/includes/api/ApiQueryAllmessages.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* A query action to return messages from site message cache
*
@@ -65,7 +60,18 @@ class ApiQueryAllmessages extends ApiQueryBase {
// Determine which messages should we print
if ( in_array( '*', $params['messages'] ) ) {
- $message_names = array_keys( Language::getMessagesFor( 'en' ) );
+ $message_names = Language::getMessageKeysFor( $langObj->getCode() );
+ if ( $params['includelocal'] ) {
+ global $wgLanguageCode;
+ $message_names = array_unique( array_merge(
+ $message_names,
+ // Pass in the content language code so we get local messages that have a
+ // MediaWiki:msgkey page. We might theoretically miss messages that have no
+ // MediaWiki:msgkey page but do have a MediaWiki:msgkey/lang page, but that's
+ // just a stupid case.
+ MessageCache::singleton()->getAllMessageKeys( $wgLanguageCode )
+ ) );
+ }
sort( $message_names );
$messages_target = $message_names;
} else {
@@ -158,7 +164,9 @@ class ApiQueryAllmessages extends ApiQueryBase {
} else {
$msgString = $msg->plain();
}
- ApiResult::setContent( $a, $msgString );
+ if ( !$params['nocontent'] ) {
+ ApiResult::setContent( $a, $msgString );
+ }
if ( isset( $prop['default'] ) ) {
$default = wfMessage( $message )->inLanguage( $langObj )->useDatabase( false );
if ( !$default->exists() ) {
@@ -204,6 +212,8 @@ class ApiQueryAllmessages extends ApiQueryBase {
)
),
'enableparser' => false,
+ 'nocontent' => false,
+ 'includelocal' => false,
'args' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_ALLOW_DUPLICATES => true,
@@ -230,7 +240,11 @@ class ApiQueryAllmessages extends ApiQueryBase {
'messages' => 'Which messages to output. "*" (default) means all messages',
'prop' => 'Which properties to get',
'enableparser' => array( 'Set to enable parser, will preprocess the wikitext of message',
- 'Will substitute magic words, handle templates etc.' ),
+ 'Will substitute magic words, handle templates etc.' ),
+ 'nocontent' => 'If set, do not include the content of the messages in the output.',
+ 'includelocal' => array( "Also include local messages, i.e. messages that don't exist in the software but do exist as a MediaWiki: page.",
+ "This lists all MediaWiki: pages, so it will also list those that aren't 'really' messages such as Common.js",
+ ),
'title' => 'Page name to use as context when parsing message (for enableparser option)',
'args' => 'Arguments to be substituted into message',
'prefix' => 'Return messages with this prefix',
@@ -246,7 +260,7 @@ class ApiQueryAllmessages extends ApiQueryBase {
return 'Return messages from this site';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&meta=allmessages&amprefix=ipb-',
'api.php?action=query&meta=allmessages&ammessages=august|mainpage&amlang=de',
diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php
index 4a216670..e003ee91 100644
--- a/includes/api/ApiQueryAllpages.php
+++ b/includes/api/ApiQueryAllpages.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to enumerate all available pages.
*
@@ -312,16 +307,19 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Simple Use',
- ' Show a list of pages starting at the letter "B"',
- ' api.php?action=query&list=allpages&apfrom=B',
- 'Using as Generator',
- ' Show info about 4 pages starting at the letter "T"',
- ' api.php?action=query&generator=allpages&gaplimit=4&gapfrom=T&prop=info',
- ' Show content of first 2 non-redirect pages begining at "Re"',
- ' api.php?action=query&generator=allpages&gaplimit=2&gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content'
+ 'api.php?action=query&list=allpages&apfrom=B' => array(
+ 'Simple Use',
+ 'Show a list of pages starting at the letter "B"',
+ ),
+ 'api.php?action=query&generator=allpages&gaplimit=4&gapfrom=T&prop=info' => array(
+ 'Using as Generator',
+ 'Show info about 4 pages starting at the letter "T"',
+ ),
+ 'api.php?action=query&generator=allpages&gaplimit=2&gapfilterredir=nonredirects&gapfrom=Re&prop=revisions&rvprop=content' => array(
+ 'Show content of first 2 non-redirect pages begining at "Re"',
+ )
);
}
diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php
index 8e2639f3..381ef550 100644
--- a/includes/api/ApiQueryBacklinks.php
+++ b/includes/api/ApiQueryBacklinks.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiQueryBase.php" );
-}
-
/**
* This is a three-in-one module to query:
* * backlinks - links pointing to the given page,
@@ -511,7 +506,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
static $examples = array(
'backlinks' => array(
'api.php?action=query&list=backlinks&bltitle=Main%20Page',
diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php
index 130d0403..4fe82de0 100644
--- a/includes/api/ApiQueryBase.php
+++ b/includes/api/ApiQueryBase.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
-
/**
* This is a base class for all Query modules.
* It provides some common functionality such as constructing various SQL
@@ -40,6 +35,11 @@ abstract class ApiQueryBase extends ApiBase {
private $mQueryModule, $mDb, $tables, $where, $fields, $options, $join_conds;
+ /**
+ * @param $query ApiBase
+ * @param $moduleName string
+ * @param $paramPrefix string
+ */
public function __construct( ApiBase $query, $moduleName, $paramPrefix = '' ) {
parent::__construct( $query->getMain(), $moduleName, $paramPrefix );
$this->mQueryModule = $query;
@@ -55,6 +55,7 @@ abstract class ApiQueryBase extends ApiBase {
* Public caching will only be allowed if *all* the modules that supply
* data for a given request return a cache mode of public.
*
+ * @param $params
* @return string
*/
public function getCacheMode( $params ) {
@@ -213,21 +214,26 @@ abstract class ApiQueryBase extends ApiBase {
if ( $sort ) {
$order = $field . ( $isDirNewer ? '' : ' DESC' );
- if ( !isset( $this->options['ORDER BY'] ) ) {
- $this->addOption( 'ORDER BY', $order );
- } else {
- $this->addOption( 'ORDER BY', $this->options['ORDER BY'] . ', ' . $order );
- }
+ // Append ORDER BY
+ $optionOrderBy = isset( $this->options['ORDER BY'] ) ? (array)$this->options['ORDER BY'] : array();
+ $optionOrderBy[] = $order;
+ $this->addOption( 'ORDER BY', $optionOrderBy );
}
}
+
/**
* Add a WHERE clause corresponding to a range, similar to addWhereRange,
* but converts $start and $end to database timestamps.
* @see addWhereRange
+ * @param $field
+ * @param $dir
+ * @param $start
+ * @param $end
+ * @param $sort bool
*/
protected function addTimestampWhereRange( $field, $dir, $start, $end, $sort = true ) {
$db = $this->getDb();
- return $this->addWhereRange( $field, $dir,
+ return $this->addWhereRange( $field, $dir,
$db->timestampOrNull( $start ), $db->timestampOrNull( $end ), $sort );
}
@@ -502,8 +508,7 @@ abstract class ApiQueryBase extends ApiBase {
* @return void
*/
public function showHiddenUsersAddBlockInfo( $showBlockInfo ) {
- global $wgUser;
- $userCanViewHiddenUsers = $wgUser->isAllowed( 'hideuser' );
+ $userCanViewHiddenUsers = $this->getUser()->isAllowed( 'hideuser' );
if ( $showBlockInfo || !$userCanViewHiddenUsers ) {
$this->addTables( 'ipblocks' );
diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php
index cfcaf0b3..bebb5a7d 100644
--- a/includes/api/ApiQueryBlocks.php
+++ b/includes/api/ApiQueryBlocks.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to enumerate all user blocks
*
@@ -46,7 +41,7 @@ class ApiQueryBlocks extends ApiQueryBase {
}
public function execute() {
- global $wgUser, $wgContLang;
+ global $wgContLang;
$params = $this->extractRequestParams();
$this->requireMaxOneParameter( $params, 'users', 'ip' );
@@ -92,6 +87,7 @@ class ApiQueryBlocks extends ApiQueryBase {
$this->addWhereFld( 'ipb_address', $this->usernames );
$this->addWhereFld( 'ipb_auto', 0 );
}
+ $db = $this->getDB();
if ( isset( $params['ip'] ) ) {
list( $ip, $range ) = IP::parseCIDR( $params['ip'] );
if ( $ip && $range ) {
@@ -105,7 +101,6 @@ class ApiQueryBlocks extends ApiQueryBase {
}
$prefix = substr( $lower, 0, 4 );
- $db = $this->getDB();
$this->addWhere( array(
'ipb_range_start' . $db->buildLike( $prefix, $db->anyString() ),
"ipb_range_start <= '$lower'",
@@ -114,7 +109,29 @@ class ApiQueryBlocks extends ApiQueryBase {
) );
}
- if ( !$wgUser->isAllowed( 'hideuser' ) ) {
+ if ( !is_null( $params['show'] ) ) {
+ $show = array_flip( $params['show'] );
+
+ /* Check for conflicting parameters. */
+ if ( ( isset ( $show['account'] ) && isset ( $show['!account'] ) )
+ || ( isset ( $show['ip'] ) && isset ( $show['!ip'] ) )
+ || ( isset ( $show['range'] ) && isset ( $show['!range'] ) )
+ || ( isset ( $show['temp'] ) && isset ( $show['!temp'] ) )
+ ) {
+ $this->dieUsageMsg( 'show' );
+ }
+
+ $this->addWhereIf( 'ipb_user = 0', isset( $show['!account'] ) );
+ $this->addWhereIf( 'ipb_user != 0', isset( $show['account'] ) );
+ $this->addWhereIf( 'ipb_user != 0 OR ipb_range_end > ipb_range_start', isset( $show['!ip'] ) );
+ $this->addWhereIf( 'ipb_user = 0 AND ipb_range_end = ipb_range_start', isset( $show['ip'] ) );
+ $this->addWhereIf( 'ipb_expiry = '.$db->addQuotes($db->getInfinity()), isset( $show['!temp'] ) );
+ $this->addWhereIf( 'ipb_expiry != '.$db->addQuotes($db->getInfinity()), isset( $show['temp'] ) );
+ $this->addWhereIf( "ipb_range_end = ipb_range_start", isset( $show['!range'] ) );
+ $this->addWhereIf( "ipb_range_end > ipb_range_start", isset( $show['range'] ) );
+ }
+
+ if ( !$this->getUser()->isAllowed( 'hideuser' ) ) {
$this->addWhereFld( 'ipb_deleted', 0 );
}
@@ -252,15 +269,29 @@ class ApiQueryBlocks extends ApiQueryBase {
'flags'
),
ApiBase::PARAM_ISMULTI => true
- )
+ ),
+ 'show' => array(
+ ApiBase::PARAM_TYPE => array(
+ 'account',
+ '!account',
+ 'temp',
+ '!temp',
+ 'ip',
+ '!ip',
+ 'range',
+ '!range',
+ ),
+ ApiBase::PARAM_ISMULTI => true
+ ),
);
}
public function getParamDescription() {
+ $p = $this->getModulePrefix();
return array(
'start' => 'The timestamp to start enumerating from',
'end' => 'The timestamp to stop enumerating at',
- 'dir' => $this->getDirectionDescription( $this->getModulePrefix() ),
+ 'dir' => $this->getDirectionDescription( $p ),
'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.',
@@ -279,6 +310,10 @@ class ApiQueryBlocks extends ApiQueryBase {
' range - Adds the range of IPs affected by the block',
' flags - Tags the ban with (autoblock, anononly, etc)',
),
+ 'show' => array(
+ 'Show only items that meet this criteria.',
+ "For example, to see only indefinite blocks on IPs, set {$p}show=ip|!temp"
+ ),
);
}
@@ -292,10 +327,11 @@ class ApiQueryBlocks extends ApiQueryBase {
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' ),
+ array( 'show' ),
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=blocks',
'api.php?action=query&list=blocks&bkusers=Alice|Bob'
diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php
index a6bca698..1c1f1550 100644
--- a/includes/api/ApiQueryCategories.php
+++ b/includes/api/ApiQueryCategories.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiQueryBase.php" );
-}
-
/**
* A query module to enumerate categories the set of pages belong to.
*
@@ -80,7 +75,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
foreach ( $params['categories'] as $cat ) {
$title = Title::newFromText( $cat );
if ( !$title || $title->getNamespace() != NS_CATEGORY ) {
- $this->setWarning( "``$cat'' is not a category" );
+ $this->setWarning( "\"$cat\" is not a category" );
} else {
$cats[] = $title->getDBkey();
}
@@ -127,11 +122,16 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
}
$this->addOption( 'USE INDEX', array( 'categorylinks' => 'cl_from' ) );
+
+ $dir = ( $params['dir'] == 'descending' ? ' DESC' : '' );
// Don't order by cl_from if it's constant in the WHERE clause
if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
- $this->addOption( 'ORDER BY', 'cl_to' );
+ $this->addOption( 'ORDER BY', 'cl_to' . $dir );
} else {
- $this->addOption( 'ORDER BY', "cl_from, cl_to" );
+ $this->addOption( 'ORDER BY', array(
+ 'cl_from' . $dir,
+ 'cl_to' . $dir
+ ));
}
$res = $this->select( __METHOD__ );
@@ -213,6 +213,13 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
'categories' => array(
ApiBase::PARAM_ISMULTI => true,
),
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending'
+ )
+ ),
);
}
@@ -228,6 +235,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
'show' => 'Which kind of categories to show',
'continue' => 'When more results are available, use this to continue',
'categories' => 'Only list these categories. Useful for checking whether a certain page is in a certain category',
+ 'dir' => 'The direction in which to list',
);
}
@@ -241,12 +249,10 @@ class ApiQueryCategories extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Get a list of categories [[Albert Einstein]] belongs to:',
- ' api.php?action=query&prop=categories&titles=Albert%20Einstein',
- 'Get information about all categories used in the [[Albert Einstein]]:',
- ' api.php?action=query&generator=categories&titles=Albert%20Einstein&prop=info'
+ 'api.php?action=query&prop=categories&titles=Albert%20Einstein' => 'Get a list of categories [[Albert Einstein]] belongs to',
+ 'api.php?action=query&generator=categories&titles=Albert%20Einstein&prop=info' => 'Get information about all categories used in the [[Albert Einstein]]',
);
}
diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php
index 3130140f..c5070e87 100644
--- a/includes/api/ApiQueryCategoryInfo.php
+++ b/includes/api/ApiQueryCategoryInfo.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiQueryBase.php" );
-}
-
/**
* This query adds the <categories> subelement to all pages with the list of categories the page is in
*
@@ -115,7 +110,7 @@ class ApiQueryCategoryInfo extends ApiQueryBase {
return 'Returns information about the given categories';
}
- protected function getExamples() {
+ public function getExamples() {
return 'api.php?action=query&prop=categoryinfo&titles=Category:Foo|Category:Bar';
}
diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php
index c916f5c1..4b19b7e8 100644
--- a/includes/api/ApiQueryCategoryMembers.php
+++ b/includes/api/ApiQueryCategoryMembers.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiQueryBase.php" );
-}
-
/**
* A query module to enumerate pages that belong to a category.
*
@@ -153,7 +148,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
$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,
@@ -392,12 +387,10 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase {
);
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Get first 10 pages in [[Category:Physics]]:',
- ' api.php?action=query&list=categorymembers&cmtitle=Category:Physics',
- 'Get page info about first 10 pages in [[Category:Physics]]:',
- ' api.php?action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info',
+ 'api.php?action=query&list=categorymembers&cmtitle=Category:Physics' => 'Get first 10 pages in [[Category:Physics]]',
+ 'api.php?action=query&generator=categorymembers&gcmtitle=Category:Physics&prop=info' => 'Get page info about first 10 pages in [[Category:Physics]]',
);
}
diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php
index e226070c..0a0cc93d 100644
--- a/includes/api/ApiQueryDeletedrevs.php
+++ b/includes/api/ApiQueryDeletedrevs.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to enumerate all deleted revisions.
*
@@ -41,9 +36,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
}
public function execute() {
- global $wgUser;
+ $user = $this->getUser();
// Before doing anything at all, let's check permissions
- if ( !$wgUser->isAllowed( 'deletedhistory' ) ) {
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
$this->dieUsage( 'You don\'t have permission to view deleted revision information', 'permissiondenied' );
}
@@ -58,6 +53,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$fld_parsedcomment = isset ( $prop['parsedcomment'] );
$fld_minor = isset( $prop['minor'] );
$fld_len = isset( $prop['len'] );
+ $fld_sha1 = isset( $prop['sha1'] );
$fld_content = isset( $prop['content'] );
$fld_token = isset( $prop['token'] );
@@ -106,6 +102,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->addFieldsIf( 'ar_comment', $fld_comment || $fld_parsedcomment );
$this->addFieldsIf( 'ar_minor_edit', $fld_minor );
$this->addFieldsIf( 'ar_len', $fld_len );
+ $this->addFieldsIf( 'ar_sha1', $fld_sha1 );
if ( $fld_content ) {
$this->addTables( 'text' );
@@ -113,7 +110,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$this->addWhere( 'ar_text_id = old_id' );
// This also means stricter restrictions
- if ( !$wgUser->isAllowed( 'undelete' ) ) {
+ if ( !$user->isAllowed( 'undelete' ) ) {
$this->dieUsage( 'You don\'t have permission to view deleted revision content', 'permissiondenied' );
}
}
@@ -132,7 +129,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $fld_token ) {
// Undelete tokens are identical for all pages, so we cache one here
- $token = $wgUser->editToken( '', $this->getMain()->getRequest() );
+ $token = $user->getEditToken( '', $this->getMain()->getRequest() );
}
$dir = $params['dir'];
@@ -214,7 +211,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $fld_revid ) {
$rev['revid'] = intval( $row->ar_rev_id );
}
- if ( $fld_parentid ) {
+ if ( $fld_parentid && !is_null( $row->ar_parent_id ) ) {
$rev['parentid'] = intval( $row->ar_parent_id );
}
if ( $fld_user ) {
@@ -230,7 +227,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$title = Title::makeTitle( $row->ar_namespace, $row->ar_title );
if ( $fld_parsedcomment ) {
- $rev['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->ar_comment, $title );
+ $rev['parsedcomment'] = Linker::formatComment( $row->ar_comment, $title );
}
if ( $fld_minor && $row->ar_minor_edit == 1 ) {
$rev['minor'] = '';
@@ -238,6 +235,13 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $fld_len ) {
$rev['len'] = $row->ar_len;
}
+ if ( $fld_sha1 ) {
+ if ( $row->ar_sha1 != '' ) {
+ $rev['sha1'] = wfBaseConvert( $row->ar_sha1, 36, 16, 40 );
+ } else {
+ $rev['sha1'] = '';
+ }
+ }
if ( $fld_content ) {
ApiResult::setContent( $rev, Revision::getRevisionText( $row ) );
}
@@ -319,6 +323,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
'parsedcomment',
'minor',
'len',
+ 'sha1',
'content',
'token'
),
@@ -345,7 +350,8 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
' comment - Adds the comment of the revision',
' parsedcomment - Adds the parsed comment of the revision',
' minor - Tags if the revision is minor',
- ' len - Adds the length of the revision',
+ ' len - Adds the length (bytes) of the revision',
+ ' sha1 - Adds the SHA-1 (base 16) of the revision',
' content - Adds the content of the revision',
' token - Gives the edit token',
),
@@ -385,16 +391,16 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'List the last deleted revisions of Main Page and Talk:Main Page, with content (mode 1):',
- ' api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&drprop=user|comment|content',
- 'List the last 50 deleted contributions by Bob (mode 2):',
- ' api.php?action=query&list=deletedrevs&druser=Bob&drlimit=50',
- 'List the first 50 deleted revisions in the main namespace (mode 3):',
- ' api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50',
- 'List the first 50 deleted pages in the Talk namespace (mode 3):',
- ' api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=1&drunique=',
+ 'api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&drprop=user|comment|content'
+ => 'List the last deleted revisions of Main Page and Talk:Main Page, with content (mode 1)',
+ 'api.php?action=query&list=deletedrevs&druser=Bob&drlimit=50'
+ => 'List the last 50 deleted contributions by Bob (mode 2)',
+ 'api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50'
+ => 'List the first 50 deleted revisions in the main namespace (mode 3)',
+ 'api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=1&drunique='
+ => 'List the first 50 deleted pages in the Talk namespace (mode 3):',
);
}
diff --git a/includes/api/ApiQueryDisabled.php b/includes/api/ApiQueryDisabled.php
index ab08042a..d68480c3 100644
--- a/includes/api/ApiQueryDisabled.php
+++ b/includes/api/ApiQueryDisabled.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* API module that does nothing
*
@@ -46,7 +41,7 @@ class ApiQueryDisabled extends ApiQueryBase {
}
public function execute() {
- $this->setWarning( "The ``{$this->getModuleName()}'' module has been disabled." );
+ $this->setWarning( "The \"{$this->getModuleName()}\" module has been disabled." );
}
public function getAllowedParams() {
@@ -63,7 +58,7 @@ class ApiQueryDisabled extends ApiQueryBase {
);
}
- protected function getExamples() {
+ public function getExamples() {
return array();
}
diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php
index a68e178d..beca5879 100644
--- a/includes/api/ApiQueryDuplicateFiles.php
+++ b/includes/api/ApiQueryDuplicateFiles.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiQueryBase.php" );
-}
-
/**
* A query module to list duplicates of the given file(s)
*
@@ -94,7 +89,8 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
);
}
- $this->addOption( 'ORDER BY', 'i1.img_name' );
+ $dir = ( $params['dir'] == 'descending' ? ' DESC' : '' );
+ $this->addOption( 'ORDER BY', 'i1.img_name' . $dir );
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$res = $this->select( __METHOD__ );
@@ -141,6 +137,13 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
),
'continue' => null,
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending'
+ )
+ ),
);
}
@@ -148,6 +151,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
return array(
'limit' => 'How many files to return',
'continue' => 'When more results are available, use this to continue',
+ 'dir' => 'The direction in which to list',
);
}
@@ -161,7 +165,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles',
'api.php?action=query&generator=allimages&prop=duplicatefiles',
diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php
index a2b2e318..93c71e2f 100644
--- a/includes/api/ApiQueryExtLinksUsage.php
+++ b/includes/api/ApiQueryExtLinksUsage.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* @ingroup API
*/
@@ -247,7 +242,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=exturlusage&euquery=www.mediawiki.org'
);
diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php
index 95297628..a9fbc839 100644
--- a/includes/api/ApiQueryExternalLinks.php
+++ b/includes/api/ApiQueryExternalLinks.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiQueryBase.php" );
-}
-
/**
* A query module to list all external URLs found on a given set of pages.
*
@@ -148,10 +143,9 @@ class ApiQueryExternalLinks extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Get a list of external links on the [[Main Page]]:',
- ' api.php?action=query&prop=extlinks&titles=Main%20Page',
+ 'api.php?action=query&prop=extlinks&titles=Main%20Page' => 'Get a list of external links on the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php
index e746a6c4..be995f30 100644
--- a/includes/api/ApiQueryFilearchive.php
+++ b/includes/api/ApiQueryFilearchive.php
@@ -26,11 +26,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to enumerate all deleted files.
*
@@ -43,9 +38,9 @@ class ApiQueryFilearchive extends ApiQueryBase {
}
public function execute() {
- global $wgUser;
+ $user = $this->getUser();
// Before doing anything at all, let's check permissions
- if ( !$wgUser->isAllowed( 'deletedhistory' ) ) {
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
$this->dieUsage( 'You don\'t have permission to view deleted file information', 'permissiondenied' );
}
@@ -110,7 +105,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
}
}
- if ( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ if ( !$user->isAllowed( 'suppressrevision' ) ) {
// Filter out revisions that the user is not allowed to see. There
// is no way to indicate that we have skipped stuff because the
// continuation parameter is fa_name
@@ -166,7 +161,7 @@ class ApiQueryFilearchive extends ApiQueryBase {
if ( $fld_description ) {
$file['description'] = $row->fa_description;
if ( isset( $prop['parseddescription'] ) ) {
- $file['parseddescription'] = $wgUser->getSkin()->formatComment(
+ $file['parseddescription'] = Linker::formatComment(
$row->fa_description, $title );
}
}
@@ -285,11 +280,12 @@ class ApiQueryFilearchive extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Simple Use',
- ' Show a list of all deleted files',
- ' api.php?action=query&list=filearchive',
+ 'api.php?action=query&list=filearchive' => array(
+ 'Simple Use',
+ 'Show a list of all deleted files',
+ ),
);
}
diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php
index 8b3c8af1..feda1779 100644
--- a/includes/api/ApiQueryIWBacklinks.php
+++ b/includes/api/ApiQueryIWBacklinks.php
@@ -25,11 +25,6 @@
* @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
@@ -207,7 +202,7 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=iwbacklinks&iwbltitle=Test&iwblprefix=wikibooks',
'api.php?action=query&generator=iwbacklinks&giwbltitle=Test&iwblprefix=wikibooks&prop=info'
diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php
index 30e44ae4..13256ad8 100644
--- a/includes/api/ApiQueryIWLinks.php
+++ b/includes/api/ApiQueryIWLinks.php
@@ -25,11 +25,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiQueryBase.php" );
-}
-
/**
* A query module to list all interwiki links on a page
*
@@ -79,20 +74,27 @@ class ApiQueryIWLinks extends ApiQueryBase {
);
}
+ $dir = ( $params['dir'] == 'descending' ? ' DESC' : '' );
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' );
+ $this->addOption( 'ORDER BY', 'iwl_from' . $dir );
} else {
- $this->addOption( 'ORDER BY', 'iwl_title, iwl_from' );
+ $this->addOption( 'ORDER BY', array(
+ 'iwl_title' . $dir,
+ 'iwl_from' . $dir
+ ));
}
} else {
// 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' );
+ $this->addOption( 'ORDER BY', 'iwl_prefix' . $dir );
} else {
- $this->addOption( 'ORDER BY', 'iwl_from, iwl_prefix' );
+ $this->addOption( 'ORDER BY', array (
+ 'iwl_from' . $dir,
+ 'iwl_prefix' . $dir
+ ));
}
}
@@ -109,7 +111,7 @@ class ApiQueryIWLinks extends ApiQueryBase {
}
$entry = array( 'prefix' => $row->iwl_prefix );
- if ( !is_null( $params['url'] ) ) {
+ if ( $params['url'] ) {
$title = Title::newFromText( "{$row->iwl_prefix}:{$row->iwl_title}" );
if ( $title ) {
$entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
@@ -131,7 +133,7 @@ class ApiQueryIWLinks extends ApiQueryBase {
public function getAllowedParams() {
return array(
- 'url' => null,
+ 'url' => false,
'limit' => array(
ApiBase::PARAM_DFLT => 10,
ApiBase::PARAM_TYPE => 'limit',
@@ -142,6 +144,13 @@ class ApiQueryIWLinks extends ApiQueryBase {
'continue' => null,
'prefix' => null,
'title' => null,
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending'
+ )
+ ),
);
}
@@ -152,6 +161,7 @@ class ApiQueryIWLinks extends ApiQueryBase {
'continue' => 'When more results are available, use this to continue',
'prefix' => 'Prefix for the interwiki',
'title' => "Interwiki link to search for. Must be used with {$this->getModulePrefix()}prefix",
+ 'dir' => 'The direction in which to list',
);
}
@@ -166,10 +176,9 @@ class ApiQueryIWLinks extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Get interwiki links from the [[Main Page]]:',
- ' api.php?action=query&prop=iwlinks&titles=Main%20Page',
+ 'api.php?action=query&prop=iwlinks&titles=Main%20Page' => 'Get interwiki links from the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php
index ab179b9f..03a24821 100644
--- a/includes/api/ApiQueryImageInfo.php
+++ b/includes/api/ApiQueryImageInfo.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* A query action to get image information and upload history.
*
@@ -318,8 +313,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
$vals['commenthidden'] = '';
} else {
if ( $pcomment ) {
- global $wgUser;
- $vals['parsedcomment'] = $wgUser->getSkin()->formatComment(
+ $vals['parsedcomment'] = Linker::formatComment(
$file->getDescription(), $file->getTitle() );
}
if ( $comment ) {
@@ -568,7 +562,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo',
'api.php?action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url',
diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php
index 5fbdc895..f03b2874 100644
--- a/includes/api/ApiQueryImages.php
+++ b/includes/api/ApiQueryImages.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiQueryBase.php" );
-}
-
/**
* This query adds an <images> subelement to all pages with the list of images embedded into those pages.
*
@@ -79,11 +74,15 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
);
}
+ $dir = ( $params['dir'] == 'descending' ? ' DESC' : '' );
// Don't order by il_from if it's constant in the WHERE clause
if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) {
- $this->addOption( 'ORDER BY', 'il_to' );
+ $this->addOption( 'ORDER BY', 'il_to' . $dir );
} else {
- $this->addOption( 'ORDER BY', 'il_from, il_to' );
+ $this->addOption( 'ORDER BY', array(
+ 'il_from' . $dir,
+ 'il_to' . $dir
+ ));
}
$this->addOption( 'LIMIT', $params['limit'] + 1 );
@@ -92,7 +91,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
foreach ( $params['images'] as $img ) {
$title = Title::newFromText( $img );
if ( !$title || $title->getNamespace() != NS_FILE ) {
- $this->setWarning( "``$img'' is not a file" );
+ $this->setWarning( "\"$img\" is not a file" );
} else {
$images[] = $title->getDBkey();
}
@@ -154,7 +153,14 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
'continue' => null,
'images' => array(
ApiBase::PARAM_ISMULTI => true,
- )
+ ),
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending'
+ )
+ ),
);
}
@@ -163,6 +169,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
'limit' => 'How many images to return',
'continue' => 'When more results are available, use this to continue',
'images' => 'Only list these images. Useful for checking whether a certain page has a certain Image.',
+ 'dir' => 'The direction in which to list',
);
}
@@ -176,12 +183,10 @@ class ApiQueryImages extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Get a list of images used in the [[Main Page]]:',
- ' api.php?action=query&prop=images&titles=Main%20Page',
- 'Get information about all images used in the [[Main Page]]:',
- ' api.php?action=query&generator=images&titles=Main%20Page&prop=info'
+ 'api.php?action=query&prop=images&titles=Main%20Page' => 'Get a list of images used in the [[Main Page]]',
+ 'api.php?action=query&generator=images&titles=Main%20Page&prop=info' => 'Get information about all images used in the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php
index c2f4dc92..f0d0faa3 100644
--- a/includes/api/ApiQueryInfo.php
+++ b/includes/api/ApiQueryInfo.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* A query module to show basic page information.
*
@@ -76,7 +71,7 @@ class ApiQueryInfo extends ApiQueryBase {
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title)
* it should return a token or false (permission denied)
- * @return array(tokenname => function)
+ * @return array array(tokenname => function)
*/
protected function getTokenFunctions() {
// Don't call the hooks twice
@@ -119,7 +114,7 @@ class ApiQueryInfo extends ApiQueryBase {
return $cachedEditToken;
}
- $cachedEditToken = $wgUser->editToken();
+ $cachedEditToken = $wgUser->getEditToken();
return $cachedEditToken;
}
@@ -134,7 +129,7 @@ class ApiQueryInfo extends ApiQueryBase {
return $cachedDeleteToken;
}
- $cachedDeleteToken = $wgUser->editToken();
+ $cachedDeleteToken = $wgUser->getEditToken();
return $cachedDeleteToken;
}
@@ -149,7 +144,7 @@ class ApiQueryInfo extends ApiQueryBase {
return $cachedProtectToken;
}
- $cachedProtectToken = $wgUser->editToken();
+ $cachedProtectToken = $wgUser->getEditToken();
return $cachedProtectToken;
}
@@ -164,7 +159,7 @@ class ApiQueryInfo extends ApiQueryBase {
return $cachedMoveToken;
}
- $cachedMoveToken = $wgUser->editToken();
+ $cachedMoveToken = $wgUser->getEditToken();
return $cachedMoveToken;
}
@@ -179,7 +174,7 @@ class ApiQueryInfo extends ApiQueryBase {
return $cachedBlockToken;
}
- $cachedBlockToken = $wgUser->editToken();
+ $cachedBlockToken = $wgUser->getEditToken();
return $cachedBlockToken;
}
@@ -199,7 +194,7 @@ class ApiQueryInfo extends ApiQueryBase {
return $cachedEmailToken;
}
- $cachedEmailToken = $wgUser->editToken();
+ $cachedEmailToken = $wgUser->getEditToken();
return $cachedEmailToken;
}
@@ -214,7 +209,7 @@ class ApiQueryInfo extends ApiQueryBase {
return $cachedImportToken;
}
- $cachedImportToken = $wgUser->editToken();
+ $cachedImportToken = $wgUser->getEditToken();
return $cachedImportToken;
}
@@ -229,7 +224,7 @@ class ApiQueryInfo extends ApiQueryBase {
return $cachedWatchToken;
}
- $cachedWatchToken = $wgUser->editToken( 'watch' );
+ $cachedWatchToken = $wgUser->getEditToken( 'watch' );
return $cachedWatchToken;
}
@@ -383,7 +378,7 @@ class ApiQueryInfo extends ApiQueryBase {
$pageInfo['fullurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
$pageInfo['editurl'] = wfExpandUrl( $title->getFullURL( 'action=edit' ), PROTO_CURRENT );
}
- if ( $this->fld_readable && $title->userCanRead() ) {
+ if ( $this->fld_readable && $title->userCan( 'read' ) ) {
$pageInfo['readable'] = '';
}
@@ -619,9 +614,9 @@ class ApiQueryInfo extends ApiQueryBase {
* Get information about watched status and put it in $this->watched
*/
private function getWatchedInfo() {
- global $wgUser;
+ $user = $this->getUser();
- if ( $wgUser->isAnon() || count( $this->everything ) == 0 ) {
+ if ( $user->isAnon() || count( $this->everything ) == 0 ) {
return;
}
@@ -635,7 +630,7 @@ class ApiQueryInfo extends ApiQueryBase {
$this->addFields( array( 'wl_title', 'wl_namespace' ) );
$this->addWhere( array(
$lb->constructSet( 'wl', $db ),
- 'wl_user' => $wgUser->getID()
+ 'wl_user' => $user->getID()
) );
$res = $this->select( __METHOD__ );
@@ -721,7 +716,7 @@ class ApiQueryInfo extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&prop=info&titles=Main%20Page',
'api.php?action=query&prop=info&inprop=protection&titles=Main%20Page'
diff --git a/includes/api/ApiQueryLangBacklinks.php b/includes/api/ApiQueryLangBacklinks.php
index 959ee789..15734944 100644
--- a/includes/api/ApiQueryLangBacklinks.php
+++ b/includes/api/ApiQueryLangBacklinks.php
@@ -25,11 +25,6 @@
* @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
@@ -207,7 +202,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public 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'
diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php
index 942655f4..fdba8465 100644
--- a/includes/api/ApiQueryLangLinks.php
+++ b/includes/api/ApiQueryLangLinks.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiQueryBase.php" );
-}
-
/**
* A query module to list all langlinks (links to correspanding foreign language pages).
*
@@ -74,20 +69,27 @@ class ApiQueryLangLinks extends ApiQueryBase {
);
}
+ $dir = ( $params['dir'] == 'descending' ? ' DESC' : '' );
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' );
+ $this->addOption( 'ORDER BY', 'll_from' . $dir );
} else {
- $this->addOption( 'ORDER BY', 'll_title, ll_from' );
+ $this->addOption( 'ORDER BY', array(
+ 'll_title' . $dir,
+ 'll_from' . $dir
+ ));
}
} else {
// 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' );
+ $this->addOption( 'ORDER BY', 'll_lang' . $dir );
} else {
- $this->addOption( 'ORDER BY', 'll_from, ll_lang' );
+ $this->addOption( 'ORDER BY', array(
+ 'll_from' . $dir,
+ 'll_lang' . $dir
+ ));
}
}
@@ -135,6 +137,13 @@ class ApiQueryLangLinks extends ApiQueryBase {
'url' => false,
'lang' => null,
'title' => null,
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending'
+ )
+ ),
);
}
@@ -145,6 +154,7 @@ class ApiQueryLangLinks extends ApiQueryBase {
'url' => 'Whether to get the full URL',
'lang' => 'Language code',
'title' => "Link to search for. Must be used with {$this->getModulePrefix()}lang",
+ 'dir' => 'The direction in which to list',
);
}
@@ -159,10 +169,9 @@ class ApiQueryLangLinks extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Get interlanguage links from the [[Main Page]]:',
- ' api.php?action=query&prop=langlinks&titles=Main%20Page&redirects=',
+ 'api.php?action=query&prop=langlinks&titles=Main%20Page&redirects=' => 'Get interlanguage links from the [[Main Page]]',
);
}
diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php
index 55217e2f..0377eddb 100644
--- a/includes/api/ApiQueryLinks.php
+++ b/includes/api/ApiQueryLinks.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiQueryBase.php" );
-}
-
/**
* A query module to list all wiki links on a given set of pages.
*
@@ -48,6 +43,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->prefix = 'pl';
$this->description = 'link';
$this->titlesParam = 'titles';
+ $this->titlesParamDescription = 'Only list links to these titles. Useful for checking whether a certain page links to a certain title.';
$this->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#links_.2F_pl';
break;
case self::TEMPLATES:
@@ -55,6 +51,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->prefix = 'tl';
$this->description = 'template';
$this->titlesParam = 'templates';
+ $this->titlesParamDescription = 'Only list these templates. Useful for checking whether a certain page uses a certain template.';
$this->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#templates_.2F_tl';
break;
default:
@@ -102,7 +99,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
foreach ( $params[$this->titlesParam] as $t ) {
$title = Title::newFromText( $t );
if ( !$title ) {
- $this->setWarning( "``$t'' is not a valid title" );
+ $this->setWarning( "\"$t\" is not a valid title" );
} else {
$lb->addObj( $title );
}
@@ -131,6 +128,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
);
}
+ $dir = ( $params['dir'] == 'descending' ? ' DESC' : '' );
// Here's some MySQL craziness going on: if you use WHERE foo='bar'
// and later ORDER BY foo MySQL doesn't notice the ORDER BY is pointless
// but instead goes and filesorts, because the index for foo was used
@@ -138,15 +136,15 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
// clause from the ORDER BY clause
$order = array();
if ( count( $this->getPageSet()->getGoodTitles() ) != 1 ) {
- $order[] = "{$this->prefix}_from";
+ $order[] = $this->prefix . '_from' . $dir;
}
if ( count( $params['namespace'] ) != 1 ) {
- $order[] = "{$this->prefix}_namespace";
+ $order[] = $this->prefix . '_namespace' . $dir;
}
- $order[] = "{$this->prefix}_title";
- $this->addOption( 'ORDER BY', implode( ', ', $order ) );
- $this->addOption( 'USE INDEX', "{$this->prefix}_from" );
+ $order[] = $this->prefix . "_title" . $dir;
+ $this->addOption( 'ORDER BY', $order );
+ $this->addOption( 'USE INDEX', $this->prefix . '_from' );
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$res = $this->select( __METHOD__ );
@@ -207,36 +205,38 @@ class ApiQueryLinks extends ApiQueryGeneratorBase {
$this->titlesParam => array(
ApiBase::PARAM_ISMULTI => true,
),
+ 'dir' => array(
+ ApiBase::PARAM_DFLT => 'ascending',
+ ApiBase::PARAM_TYPE => array(
+ 'ascending',
+ 'descending'
+ )
+ ),
);
}
public function getParamDescription() {
$desc = $this->description;
- $arr = array(
+ return array(
'namespace' => "Show {$desc}s in this namespace(s) only",
'limit' => "How many {$desc}s to return",
'continue' => 'When more results are available, use this to continue',
+ $this->titlesParam => $this->titlesParamDescription,
+ 'dir' => 'The direction in which to list',
);
- 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.';
- } 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;
}
public function getDescription() {
return "Returns all {$this->description}s from the given page(s)";
}
- protected function getExamples() {
+ public function getExamples() {
+ $desc = $this->description;
+ $name = $this->getModuleName();
return array(
- "Get {$this->description}s from the [[Main Page]]:",
- " api.php?action=query&prop={$this->getModuleName()}&titles=Main%20Page",
- "Get information about the {$this->description} pages in the [[Main Page]]:",
- " api.php?action=query&generator={$this->getModuleName()}&titles=Main%20Page&prop=info",
- "Get {$this->description}s from the Main Page in the User and Template namespaces:",
- " api.php?action=query&prop={$this->getModuleName()}&titles=Main%20Page&{$this->prefix}namespace=2|10"
+ "api.php?action=query&prop={$name}&titles=Main%20Page" => "Get {$desc}s from the [[Main Page]]:",
+ "api.php?action=query&generator={$name}&titles=Main%20Page&prop=info" => "Get information about the {$desc} pages in the [[Main Page]]:",
+ "api.php?action=query&prop={$name}&titles=Main%20Page&{$this->prefix}namespace=2|10" => "Get {$desc}s from the Main Page in the User and Template namespaces:",
);
}
diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php
index 669ab71f..0d07a254 100644
--- a/includes/api/ApiQueryLogEvents.php
+++ b/includes/api/ApiQueryLogEvents.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query action to List the log events, with optional filtering by various parameters.
*
@@ -202,26 +197,44 @@ class ApiQueryLogEvents extends ApiQueryBase {
* @param $ts
* @return array
*/
- public static function addLogParams( $result, &$vals, $params, $type, $action, $ts ) {
- $params = explode( "\n", $params );
+ public static function addLogParams( $result, &$vals, $params, $type, $action, $ts, $legacy = false ) {
switch ( $type ) {
case 'move':
- if ( isset( $params[0] ) ) {
- $title = Title::newFromText( $params[0] );
+ if ( $legacy ){
+ $targetKey = 0;
+ $noredirKey = 1;
+ } else {
+ $targetKey = '4::target';
+ $noredirKey = '5::noredir';
+ }
+
+ if ( isset( $params[ $targetKey ] ) ) {
+ $title = Title::newFromText( $params[ $targetKey ] );
if ( $title ) {
$vals2 = array();
ApiQueryBase::addTitleInfo( $vals2, $title, 'new_' );
$vals[$type] = $vals2;
}
}
- if ( isset( $params[1] ) && $params[1] ) {
+ if ( isset( $params[ $noredirKey ] ) && $params[ $noredirKey ] ) {
$vals[$type]['suppressedredirect'] = '';
}
$params = null;
break;
case 'patrol':
+ if ( $legacy ){
+ $cur = 0;
+ $prev = 1;
+ $auto = 2;
+ } else {
+ $cur = '4::curid';
+ $prev = '5::previd';
+ $auto = '6::auto';
+ }
$vals2 = array();
- list( $vals2['cur'], $vals2['prev'], $vals2['auto'] ) = $params;
+ $vals2['cur'] = $params[$cur];
+ $vals2['prev'] = $params[$prev];
+ $vals2['auto'] = $params[$auto];
$vals[$type] = $vals2;
$params = null;
break;
@@ -255,6 +268,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
private function extractRowInfo( $row ) {
+ $logEntry = DatabaseLogEntry::newFromRow( $row );
$vals = array();
if ( $this->fld_ids ) {
@@ -286,10 +300,11 @@ class ApiQueryLogEvents extends ApiQueryBase {
self::addLogParams(
$this->getResult(),
$vals,
- $row->log_params,
- $row->log_type,
- $row->log_action,
- $row->log_timestamp
+ $logEntry->getParameters(),
+ $logEntry->getType(),
+ $logEntry->getSubtype(),
+ $logEntry->getTimestamp(),
+ $logEntry->isLegacy()
);
}
}
@@ -323,8 +338,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
}
if ( $this->fld_parsedcomment ) {
- global $wgUser;
- $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->log_comment, $title );
+ $vals['parsedcomment'] = Linker::formatComment( $row->log_comment, $title );
}
}
}
@@ -445,7 +459,7 @@ class ApiQueryLogEvents extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=logevents'
);
diff --git a/includes/api/ApiQueryPageProps.php b/includes/api/ApiQueryPageProps.php
index 56213fa3..1eef67e6 100644
--- a/includes/api/ApiQueryPageProps.php
+++ b/includes/api/ApiQueryPageProps.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* A query module to show basic page information.
*
@@ -142,7 +137,7 @@ class ApiQueryPageProps extends ApiQueryBase {
return 'Get various properties defined in the page content';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&prop=pageprops&titles=Category:Foo',
);
diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php
index ff703cda..44cc1d32 100644
--- a/includes/api/ApiQueryProtectedTitles.php
+++ b/includes/api/ApiQueryProtectedTitles.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to enumerate all create-protected pages.
*
@@ -112,8 +107,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
}
if ( isset( $prop['parsedcomment'] ) ) {
- global $wgUser;
- $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->pt_reason, $title );
+ $vals['parsedcomment'] = Linker::formatComment( $row->pt_reason, $title );
}
if ( isset( $prop['expiry'] ) ) {
@@ -224,7 +218,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase {
return 'List all titles protected from creation';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=protectedtitles',
);
diff --git a/includes/api/ApiQueryQueryPage.php b/includes/api/ApiQueryQueryPage.php
index b38df6b6..5eba0de6 100644
--- a/includes/api/ApiQueryQueryPage.php
+++ b/includes/api/ApiQueryQueryPage.php
@@ -24,11 +24,6 @@
* @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
*
@@ -73,15 +68,13 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
/**
* @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 ) ) {
+ if ( !$qp->userCanExecute( $this->getUser() ) ) {
$this->dieUsageMsg( 'specialpage-cantexecute' );
}
@@ -98,7 +91,7 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
}
}
$result->addValue( array( 'query' ), $this->getModuleName(), $r );
-
+
if ( $qp->isCached() && !$qp->isCacheable() ) {
// Disabled query page, don't run the query
return;
@@ -183,10 +176,11 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase {
public function getPossibleErrors() {
return array_merge( parent::getPossibleErrors(), array(
+ array( 'specialpage-cantexecute' )
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=querypage&qppage=Ancientpages'
);
diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php
index dea0b0f5..2e9e2dd5 100644
--- a/includes/api/ApiQueryRandom.php
+++ b/includes/api/ApiQueryRandom.php
@@ -25,11 +25,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to get list of random pages
*
@@ -175,7 +170,7 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
);
}
- protected function getExamples() {
+ public function getExamples() {
return 'api.php?action=query&list=random&rnnamespace=0&rnlimit=2';
}
diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php
index 9ce6688e..bf5bbd9b 100644
--- a/includes/api/ApiQueryRecentChanges.php
+++ b/includes/api/ApiQueryRecentChanges.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* A query action to enumerate the recent changes that were done to the wiki.
* Various filters are supported.
@@ -52,7 +47,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title, $rc)
* it should return a token or false (permission denied)
- * @return array(tokenname => function)
+ * @return array array(tokenname => function)
*/
protected function getTokenFunctions() {
// Don't call the hooks twice
@@ -89,7 +84,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
// The patrol token is always the same, let's exploit that
static $cachedPatrolToken = null;
if ( is_null( $cachedPatrolToken ) ) {
- $cachedPatrolToken = $wgUser->editToken( 'patrol' );
+ $cachedPatrolToken = $wgUser->getEditToken( 'patrol' );
}
return $cachedPatrolToken;
@@ -129,7 +124,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
* @param $resultPageSet ApiPageSet
*/
public function run( $resultPageSet = null ) {
- global $wgUser;
+ $user = $this->getUser();
/* Get the parameters of the request. */
$params = $this->extractRequestParams();
@@ -163,7 +158,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
// Check permissions
if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
- if ( !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
+ if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
$this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
}
}
@@ -219,7 +214,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
/* Set up internal members based upon params. */
$this->initProperties( $prop );
- if ( $this->fld_patrolled && !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
+ if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
$this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
}
@@ -410,8 +405,7 @@ class ApiQueryRecentChanges 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_redirect ) {
@@ -633,7 +627,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=recentchanges'
);
diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php
index 401406bb..fa58bdf0 100644
--- a/includes/api/ApiQueryRevisions.php
+++ b/includes/api/ApiQueryRevisions.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* A query action to enumerate revisions of a given page, or show top revisions of multiple pages.
* Various pieces of information may be shown - flags, comments, and the actual wiki markup of the rev.
@@ -84,8 +79,8 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( !$wgUser->isAllowed( 'rollback' ) ) {
return false;
}
- return $wgUser->editToken( array( $title->getPrefixedText(),
- $rev->getUserText() ) );
+ return $wgUser->getEditToken(
+ array( $title->getPrefixedText(), $rev->getUserText() ) );
}
public function execute() {
@@ -159,6 +154,7 @@ class ApiQueryRevisions extends ApiQueryBase {
$this->fld_comment = isset ( $prop['comment'] );
$this->fld_parsedcomment = isset ( $prop['parsedcomment'] );
$this->fld_size = isset ( $prop['size'] );
+ $this->fld_sha1 = isset ( $prop['sha1'] );
$this->fld_userid = isset( $prop['userid'] );
$this->fld_user = isset ( $prop['user'] );
$this->token = $params['token'];
@@ -196,7 +192,7 @@ class ApiQueryRevisions extends ApiQueryBase {
if ( isset( $prop['content'] ) || !is_null( $this->difftotext ) ) {
// For each page we will request, the user must have read rights for that page
foreach ( $pageSet->getGoodTitles() as $title ) {
- if ( !$title->userCanRead() ) {
+ if ( !$title->userCan( 'read' ) ) {
$this->dieUsage(
'The current user is not allowed to read ' . $title->getPrefixedText(),
'accessdenied' );
@@ -409,8 +405,20 @@ class ApiQueryRevisions extends ApiQueryBase {
$vals['timestamp'] = wfTimestamp( TS_ISO_8601, $revision->getTimestamp() );
}
- if ( $this->fld_size && !is_null( $revision->getSize() ) ) {
- $vals['size'] = intval( $revision->getSize() );
+ if ( $this->fld_size ) {
+ if ( !is_null( $revision->getSize() ) ) {
+ $vals['size'] = intval( $revision->getSize() );
+ } else {
+ $vals['size'] = 0;
+ }
+ }
+
+ if ( $this->fld_sha1 ) {
+ if ( $revision->getSha1() != '' ) {
+ $vals['sha1'] = wfBaseConvert( $revision->getSha1(), 36, 16, 40 );
+ } else {
+ $vals['sha1'] = '';
+ }
}
if ( $this->fld_comment || $this->fld_parsedcomment ) {
@@ -424,8 +432,7 @@ class ApiQueryRevisions extends ApiQueryBase {
}
if ( $this->fld_parsedcomment ) {
- global $wgUser;
- $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $comment, $title );
+ $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
}
}
}
@@ -468,7 +475,7 @@ class ApiQueryRevisions extends ApiQueryBase {
}
if ( $this->fld_content && !$revision->isDeleted( Revision::DELETED_TEXT ) ) {
if ( $this->generateXML ) {
- $wgParser->startExternalParse( $title, new ParserOptions(), OT_PREPROCESS );
+ $wgParser->startExternalParse( $title, ParserOptions::newFromContext( $this->getContext() ), OT_PREPROCESS );
$dom = $wgParser->preprocessToDom( $text );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
$xml = $dom->saveXML();
@@ -479,10 +486,10 @@ class ApiQueryRevisions extends ApiQueryBase {
}
if ( $this->expandTemplates && !$this->parseContent ) {
- $text = $wgParser->preprocess( $text, $title, new ParserOptions() );
+ $text = $wgParser->preprocess( $text, $title, ParserOptions::newFromContext( $this->getContext() ) );
}
if ( $this->parseContent ) {
- $text = $wgParser->parse( $text, $title, new ParserOptions() )->getText();
+ $text = $wgParser->parse( $text, $title, ParserOptions::newFromContext( $this->getContext() ) )->getText();
}
ApiResult::setContent( $vals, $text );
} elseif ( $this->fld_content ) {
@@ -494,11 +501,13 @@ class ApiQueryRevisions extends ApiQueryBase {
static $n = 0; // Number of uncached diffs we've had
if ( $n < $wgAPIMaxUncachedDiffs ) {
$vals['diff'] = array();
+ $context = new DerivativeContext( $this->getContext() );
+ $context->setTitle( $title );
if ( !is_null( $this->difftotext ) ) {
- $engine = new DifferenceEngine( $title );
+ $engine = new DifferenceEngine( $context );
$engine->setText( $text, $this->difftotext );
} else {
- $engine = new DifferenceEngine( $title, $revision->getID(), $this->diffto );
+ $engine = new DifferenceEngine( $context, $revision->getID(), $this->diffto );
$vals['diff']['from'] = $engine->getOldid();
$vals['diff']['to'] = $engine->getNewid();
}
@@ -537,6 +546,7 @@ class ApiQueryRevisions extends ApiQueryBase {
'user',
'userid',
'size',
+ 'sha1',
'comment',
'parsedcomment',
'content',
@@ -599,7 +609,8 @@ class ApiQueryRevisions extends ApiQueryBase {
' timestamp - The timestamp of the revision',
' user - User that made the revision',
' userid - User id of revision creator',
- ' size - Length of the revision',
+ ' size - Length (bytes) of the revision',
+ ' sha1 - SHA-1 (base 16) of the revision',
' comment - Comment by the user for revision',
' parsedcomment - Parsed comment by the user for the revision',
' content - Text of the revision',
@@ -651,15 +662,15 @@ class ApiQueryRevisions extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'Get data with content for the last revision of titles "API" and "Main Page":',
+ 'Get data with content for the last revision of titles "API" and "Main Page"',
' api.php?action=query&prop=revisions&titles=API|Main%20Page&rvprop=timestamp|user|comment|content',
- 'Get last 5 revisions of the "Main Page":',
+ 'Get last 5 revisions of the "Main Page"',
' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment',
- 'Get first 5 revisions of the "Main Page":',
+ 'Get first 5 revisions of the "Main Page"',
' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer',
- 'Get first 5 revisions of the "Main Page" made after 2006-05-01:',
+ 'Get first 5 revisions of the "Main Page" made after 2006-05-01',
' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvdir=newer&rvstart=20060501000000',
'Get first 5 revisions of the "Main Page" that were not made made by anonymous user "127.0.0.1"',
' api.php?action=query&prop=revisions&titles=Main%20Page&rvlimit=5&rvprop=timestamp|user|comment&rvexcludeuser=127.0.0.1',
diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php
index 5c133b7d..40aac050 100644
--- a/includes/api/ApiQuerySearch.php
+++ b/includes/api/ApiQuerySearch.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to perform full text search within wiki titles and content
*
@@ -69,6 +64,9 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
$search->setNamespaces( $params['namespace'] );
$search->showRedirects = $params['redirects'];
+ $query = $search->transformSearchTerm( $query );
+ $query = $search->replacePrefixes( $query );
+
// Perform the actual search
if ( $what == 'text' ) {
$matches = $search->searchText( $query );
@@ -293,7 +291,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=search&srsearch=meaning',
'api.php?action=query&list=search&srwhat=text&srsearch=meaning',
diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php
index 56743189..e2580ac6 100644
--- a/includes/api/ApiQuerySiteinfo.php
+++ b/includes/api/ApiQuerySiteinfo.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* A query action to return meta information about the wiki site.
*
@@ -43,6 +38,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
public function execute() {
$params = $this->extractRequestParams();
$done = array();
+ $fit = false;
foreach ( $params['prop'] as $p ) {
switch ( $p ) {
case 'general':
@@ -138,6 +134,14 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
$data['rights'] = $GLOBALS['wgRightsText'];
$data['lang'] = $GLOBALS['wgLanguageCode'];
+
+ $fallbacks = array();
+ foreach( $wgContLang->getFallbackLanguages() as $code ) {
+ $fallbacks[] = array( 'code' => $code );
+ }
+ $data['fallback'] = $fallbacks;
+ $this->getResult()->setIndexedTagName( $data['fallback'], 'lang' );
+
if ( $wgContLang->isRTL() ) {
$data['rtl'] = '';
}
@@ -256,40 +260,44 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
protected function appendInterwikiMap( $property, $filter ) {
- $this->resetQueryParams();
- $this->addTables( 'interwiki' );
- $this->addFields( array( 'iw_prefix', 'iw_local', 'iw_url', 'iw_wikiid', 'iw_api' ) );
-
+ $local = null;
if ( $filter === 'local' ) {
- $this->addWhere( 'iw_local = 1' );
+ $local = 1;
} elseif ( $filter === '!local' ) {
- $this->addWhere( 'iw_local = 0' );
+ $local = 0;
} elseif ( $filter ) {
ApiBase::dieDebug( __METHOD__, "Unknown filter=$filter" );
}
- $this->addOption( 'ORDER BY', 'iw_prefix' );
+ $params = $this->extractRequestParams();
+ $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
- $res = $this->select( __METHOD__ );
+ if( $langCode ) {
+ $langNames = Language::getTranslatedLanguageNames( $langCode );
+ } else {
+ $langNames = Language::getLanguageNames();
+ }
+ $getPrefixes = Interwiki::getAllPrefixes( $local );
$data = array();
- $langNames = Language::getLanguageNames();
- foreach ( $res as $row ) {
+
+ foreach ( $getPrefixes as $row ) {
+ $prefix = $row['iw_prefix'];
$val = array();
- $val['prefix'] = $row->iw_prefix;
- if ( $row->iw_local == '1' ) {
+ $val['prefix'] = $prefix;
+ if ( $row['iw_local'] == '1' ) {
$val['local'] = '';
}
- // $val['trans'] = intval( $row->iw_trans ); // should this be exposed?
- if ( isset( $langNames[$row->iw_prefix] ) ) {
- $val['language'] = $langNames[$row->iw_prefix];
+ // $val['trans'] = intval( $row['iw_trans'] ); // should this be exposed?
+ if ( isset( $langNames[$prefix] ) ) {
+ $val['language'] = $langNames[$prefix];
}
- $val['url'] = wfExpandUrl( $row->iw_url, PROTO_CURRENT );
- if( isset( $row->iw_wikiid ) ) {
- $val['wikiid'] = $row->iw_wikiid;
+ $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;
+ if( isset( $row['iw_api'] ) ) {
+ $val['api'] = $row['iw_api'];
}
$data[] = $val;
@@ -467,8 +475,18 @@ class ApiQuerySiteinfo extends ApiQueryBase {
}
public function appendLanguages( $property ) {
+ $params = $this->extractRequestParams();
+ $langCode = isset( $params['inlanguagecode'] ) ? $params['inlanguagecode'] : '';
+
+ if( $langCode ) {
+ $langNames = Language::getTranslatedLanguageNames( $langCode );
+ } else {
+ $langNames = Language::getLanguageNames();
+ }
+
$data = array();
- foreach ( Language::getLanguageNames() as $code => $name ) {
+
+ foreach ( $langNames as $code => $name ) {
$lang = array( 'code' => $code );
ApiResult::setContent( $lang, $name );
$data[] = $lang;
@@ -565,10 +583,12 @@ class ApiQuerySiteinfo extends ApiQueryBase {
),
'showalldb' => false,
'numberingroup' => false,
+ 'inlanguagecode' => null,
);
}
public function getParamDescription() {
+ $p = $this->getModulePrefix();
return array(
'prop' => array(
'Which sysinfo properties to get:',
@@ -578,13 +598,13 @@ class ApiQuerySiteinfo extends ApiQueryBase {
' specialpagealiases - List of special page aliases',
' magicwords - List of magic words and their aliases',
' statistics - Returns site statistics',
- ' interwikimap - Returns interwiki map (optionally filtered)',
+ " interwikimap - Returns interwiki map (optionally filtered, (optionally localised by using {$p}inlanguagecode))",
' dbrepllag - Returns database server with the highest replication lag',
' usergroups - Returns user groups and the associated permissions',
' extensions - Returns extensions installed on the wiki',
' fileextensions - Returns list of file extensions allowed to be uploaded',
' rightsinfo - Returns wiki rights (license) information if available',
- ' languages - Returns a list of languages MediaWiki supports',
+ " languages - Returns a list of languages MediaWiki supports (optionally localised by using {$p}inlanguagecode)",
' skins - Returns a list of all enabled skins',
' extensiontags - Returns a list of parser extension tags',
' functionhooks - Returns a list of parser function hooks',
@@ -593,6 +613,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
'filteriw' => 'Return only local or only nonlocal entries of the interwiki map',
'showalldb' => 'List all database servers, not just the one lagging the most',
'numberingroup' => 'Lists the number of users in user groups',
+ 'inlanguagecode' => 'Language code for localised language names (best effort, use CLDR extension)',
);
}
@@ -606,7 +627,7 @@ class ApiQuerySiteinfo extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&meta=siteinfo&siprop=general|namespaces|namespacealiases|statistics',
'api.php?action=query&meta=siteinfo&siprop=interwikimap&sifilteriw=local',
diff --git a/includes/api/ApiQueryStashImageInfo.php b/includes/api/ApiQueryStashImageInfo.php
index b1903025..4501ec58 100644
--- a/includes/api/ApiQueryStashImageInfo.php
+++ b/includes/api/ApiQueryStashImageInfo.php
@@ -45,6 +45,11 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
$this->dieUsage( "One of filekey or sessionkey must be supplied", 'nofilekey');
}
+ // Alias sessionkey to filekey, but give an existing filekey precedence.
+ if ( !$params['filekey'] && $params['sessionkey'] ) {
+ $params['filekey'] = $params['sessionkey'];
+ }
+
try {
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
@@ -122,7 +127,7 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo {
return 'Returns image information for stashed images';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&prop=stashimageinfo&siifilekey=124sd34rsdf567',
'api.php?action=query&prop=stashimageinfo&siifilekey=b34edoe3|bceffd4&siiurlwidth=120&siiprop=url',
diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php
index 4ab0c3d1..12cea1d7 100644
--- a/includes/api/ApiQueryTags.php
+++ b/includes/api/ApiQueryTags.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to enumerate change tags.
*
@@ -178,7 +173,7 @@ class ApiQueryTags extends ApiQueryBase {
return 'List change tags';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=tags&tgprop=displayname|description|hitcount'
);
diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php
index f6a9fe46..8e2f20db 100644
--- a/includes/api/ApiQueryUserContributions.php
+++ b/includes/api/ApiQueryUserContributions.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* This query action adds a list of a specified user's contributions to the output.
*
@@ -146,7 +141,7 @@ class ApiQueryContributions extends ApiQueryBase {
// We're after the revision table, and the corresponding page
// row for anything we retrieve. We may also need the
// recentchanges row and/or tag summary row.
- global $wgUser;
+ $user = $this->getUser();
$tables = array( 'page', 'revision' ); // Order may change
$this->addWhere( 'page_id=rev_page' );
@@ -167,7 +162,7 @@ class ApiQueryContributions extends ApiQueryBase {
);
}
- if ( !$wgUser->isAllowed( 'hideuser' ) ) {
+ if ( !$user->isAllowed( 'hideuser' ) ) {
$this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
}
// We only want pages by the specified users.
@@ -216,7 +211,7 @@ class ApiQueryContributions extends ApiQueryBase {
if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
$this->fld_patrolled ) {
- if ( !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
+ if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
$this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
}
@@ -275,6 +270,9 @@ class ApiQueryContributions extends ApiQueryBase {
/**
* Extract fields from the database row and append them to a result array
+ *
+ * @param $row
+ * @return array
*/
private function extractRowInfo( $row ) {
$vals = array();
@@ -321,8 +319,7 @@ class ApiQueryContributions extends ApiQueryBase {
}
if ( $this->fld_parsedcomment ) {
- global $wgUser;
- $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->rev_comment, $title );
+ $vals['parsedcomment'] = Linker::formatComment( $row->rev_comment, $title );
}
}
}
@@ -444,7 +441,7 @@ class ApiQueryContributions extends ApiQueryBase {
' tags - Lists tags for the edit',
),
'show' => array( "Show only items that meet this criteria, e.g. non minor edits only: {$p}show=!minor",
- "NOTE: if {$p}show=patrolled or {$p}show=!patrolled is set, revisions older than $wgRCMaxAge won\'t be shown", ),
+ "NOTE: if {$p}show=patrolled or {$p}show=!patrolled is set, revisions older than \$wgRCMaxAge ($wgRCMaxAge) won't be shown", ),
'tag' => 'Only list revisions tagged with this tag',
'toponly' => 'Only list changes which are the latest revision',
);
@@ -463,7 +460,7 @@ class ApiQueryContributions extends ApiQueryBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=usercontribs&ucuser=YurikBot',
'api.php?action=query&list=usercontribs&ucuserprefix=217.121.114.',
diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php
index 2411bee9..a0ee227f 100644
--- a/includes/api/ApiQueryUserInfo.php
+++ b/includes/api/ApiQueryUserInfo.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to get information about the currently logged-in user
*
@@ -55,47 +50,48 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
protected function getCurrentUserInfo() {
- global $wgUser, $wgRequest, $wgHiddenPrefs;
+ global $wgRequest, $wgHiddenPrefs;
+ $user = $this->getUser();
$result = $this->getResult();
$vals = array();
- $vals['id'] = intval( $wgUser->getId() );
- $vals['name'] = $wgUser->getName();
+ $vals['id'] = intval( $user->getId() );
+ $vals['name'] = $user->getName();
- if ( $wgUser->isAnon() ) {
+ if ( $user->isAnon() ) {
$vals['anon'] = '';
}
if ( isset( $this->prop['blockinfo'] ) ) {
- if ( $wgUser->isBlocked() ) {
- $vals['blockedby'] = User::whoIs( $wgUser->blockedBy() );
- $vals['blockreason'] = $wgUser->blockedFor();
+ if ( $user->isBlocked() ) {
+ $vals['blockedby'] = User::whoIs( $user->blockedBy() );
+ $vals['blockreason'] = $user->blockedFor();
}
}
- if ( isset( $this->prop['hasmsg'] ) && $wgUser->getNewtalk() ) {
+ if ( isset( $this->prop['hasmsg'] ) && $user->getNewtalk() ) {
$vals['messages'] = '';
}
if ( isset( $this->prop['groups'] ) ) {
- $autolist = ApiQueryUsers::getAutoGroups( $wgUser );
+ $autolist = ApiQueryUsers::getAutoGroups( $user );
- $vals['groups'] = array_merge( $autolist, $wgUser->getGroups() );
+ $vals['groups'] = array_merge( $autolist, $user->getGroups() );
$result->setIndexedTagName( $vals['groups'], 'g' ); // even if empty
}
if ( isset( $this->prop['implicitgroups'] ) ) {
- $vals['implicitgroups'] = ApiQueryUsers::getAutoGroups( $wgUser );
+ $vals['implicitgroups'] = ApiQueryUsers::getAutoGroups( $user );
$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() ) );
+ $vals['rights'] = array_values( array_unique( $user->getRights() ) );
$result->setIndexedTagName( $vals['rights'], 'r' ); // even if empty
}
if ( isset( $this->prop['changeablegroups'] ) ) {
- $vals['changeablegroups'] = $wgUser->changeableGroups();
+ $vals['changeablegroups'] = $user->changeableGroups();
$result->setIndexedTagName( $vals['changeablegroups']['add'], 'g' );
$result->setIndexedTagName( $vals['changeablegroups']['remove'], 'g' );
$result->setIndexedTagName( $vals['changeablegroups']['add-self'], 'g' );
@@ -103,17 +99,17 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
if ( isset( $this->prop['options'] ) ) {
- $vals['options'] = $wgUser->getOptions();
+ $vals['options'] = $user->getOptions();
}
if ( isset( $this->prop['preferencestoken'] ) &&
is_null( $this->getMain()->getRequest()->getVal( 'callback' ) )
) {
- $vals['preferencestoken'] = $wgUser->editToken( '', $this->getMain()->getRequest() );
+ $vals['preferencestoken'] = $user->getEditToken( '', $this->getMain()->getRequest() );
}
if ( isset( $this->prop['editcount'] ) ) {
- $vals['editcount'] = intval( $wgUser->getEditCount() );
+ $vals['editcount'] = intval( $user->getEditCount() );
}
if ( isset( $this->prop['ratelimits'] ) ) {
@@ -121,19 +117,19 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
if ( isset( $this->prop['realname'] ) && !in_array( 'realname', $wgHiddenPrefs ) ) {
- $vals['realname'] = $wgUser->getRealName();
+ $vals['realname'] = $user->getRealName();
}
if ( isset( $this->prop['email'] ) ) {
- $vals['email'] = $wgUser->getEmail();
- $auth = $wgUser->getEmailAuthenticationTimestamp();
+ $vals['email'] = $user->getEmail();
+ $auth = $user->getEmailAuthenticationTimestamp();
if ( !is_null( $auth ) ) {
$vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth );
}
}
if ( isset( $this->prop['registrationdate'] ) ) {
- $regDate = $wgUser->getRegistration();
+ $regDate = $user->getRegistration();
if ( $regDate !== false ) {
$vals['registrationdate'] = wfTimestamp( TS_ISO_8601, $regDate );
}
@@ -154,25 +150,26 @@ class ApiQueryUserInfo extends ApiQueryBase {
}
protected function getRateLimits() {
- global $wgUser, $wgRateLimits;
- if ( !$wgUser->isPingLimitable() ) {
+ global $wgRateLimits;
+ $user = $this->getUser();
+ if ( !$user->isPingLimitable() ) {
return array(); // No limits
}
// Find out which categories we belong to
$categories = array();
- if ( $wgUser->isAnon() ) {
+ if ( $user->isAnon() ) {
$categories[] = 'anon';
} else {
$categories[] = 'user';
}
- if ( $wgUser->isNewbie() ) {
+ if ( $user->isNewbie() ) {
$categories[] = 'ip';
$categories[] = 'subnet';
- if ( !$wgUser->isAnon() )
+ if ( !$user->isAnon() )
$categories[] = 'newbie';
}
- $categories = array_merge( $categories, $wgUser->getGroups() );
+ $categories = array_merge( $categories, $user->getGroups() );
// Now get the actual limits
$retval = array();
@@ -238,7 +235,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
return 'Get information about the current user';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&meta=userinfo',
'api.php?action=query&meta=userinfo&uiprop=blockinfo|groups|rights|hasmsg',
diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php
index 31437827..31624bdf 100644
--- a/includes/api/ApiQueryUsers.php
+++ b/includes/api/ApiQueryUsers.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* Query module to get information about a list of users
*
@@ -74,7 +69,7 @@ class ApiQueryUsers extends ApiQueryBase {
global $wgUser;
// Since the permissions check for userrights is non-trivial,
// don't bother with it here
- return $wgUser->editToken( $user->getName() );
+ return $wgUser->getEditToken( $user->getName() );
}
public function execute() {
@@ -322,7 +317,7 @@ class ApiQueryUsers extends ApiQueryBase {
return 'Get information about a list of users';
}
- protected function getExamples() {
+ public function getExamples() {
return 'api.php?action=query&list=users&ususers=brion|TimStarling&usprop=groups|editcount|gender';
}
diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php
index 77ecb90a..ea56fcd9 100644
--- a/includes/api/ApiQueryWatchlist.php
+++ b/includes/api/ApiQueryWatchlist.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* This query action allows clients to retrieve a list of recently modified pages
* that are part of the logged-in user's watchlist.
@@ -159,8 +154,8 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
// Check permissions.
if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
- global $wgUser;
- if ( !$wgUser->useRCPatrol() && !$wgUser->useNPPatrol() ) {
+ $user = $this->getUser();
+ if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
$this->dieUsage( 'You need the patrol right to request the patrolled flag', 'permissiondenied' );
}
}
@@ -438,7 +433,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=watchlist',
'api.php?action=query&list=watchlist&wlprop=ids|title|timestamp|user|comment',
diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php
index 126f6d89..506944f0 100644
--- a/includes/api/ApiQueryWatchlistRaw.php
+++ b/includes/api/ApiQueryWatchlistRaw.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiQueryBase.php' );
-}
-
/**
* This query action allows clients to retrieve a list of pages
* on the logged-in user's watchlist.
@@ -197,7 +192,7 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=query&list=watchlistraw',
'api.php?action=query&generator=watchlistraw&gwrshow=changed&prop=revisions',
diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php
index a8ca6046..798b2275 100644
--- a/includes/api/ApiResult.php
+++ b/includes/api/ApiResult.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
-
/**
* This class represents the result of the API operations.
* It simply wraps a nested array() structure, adding some functions to simplify array's modifications.
@@ -246,11 +241,12 @@ class ApiResult extends ApiBase {
/**
* Add value to the output data at the given path.
- * 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
+ * Path can be an indexed array, each element specifying the branch at which to add the new
+ * value. Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value.
+ * If $path is null, the value will be inserted at the data root.
+ * If $name is empty, the $value is added as a next list element data[] = $value.
*
- * @param $path
+ * @param $path array|string|null
* @param $name string
* @param $value mixed
* @param $overwrite bool
@@ -259,6 +255,7 @@ class ApiResult extends ApiBase {
*/
public function addValue( $path, $name, $value, $overwrite = false ) {
global $wgAPIMaxResultSize;
+
$data = &$this->mData;
if ( $this->mCheckingSize ) {
$newsize = $this->mSize + self::size( $value );
diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php
index 154e5dfb..436c392b 100644
--- a/includes/api/ApiRollback.php
+++ b/includes/api/ApiRollback.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* @ingroup API
*/
@@ -52,11 +47,11 @@ class ApiRollback extends ApiBase {
$params = $this->extractRequestParams();
// User and title already validated in call to getTokenSalt from Main
- $titleObj = $this->getTitle();
- $articleObj = new Article( $titleObj );
+ $titleObj = $this->getRbTitle();
+ $pageObj = WikiPage::factory( $titleObj );
$summary = ( isset( $params['summary'] ) ? $params['summary'] : '' );
$details = array();
- $retval = $articleObj->doRollback( $this->getUser(), $summary, $params['token'], $params['markbot'], $details );
+ $retval = $pageObj->doRollback( $this->getRbUser(), $summary, $params['token'], $params['markbot'], $details, $this->getUser() );
if ( $retval ) {
// We don't care about multiple errors, just report one of them
@@ -141,10 +136,10 @@ class ApiRollback extends ApiBase {
}
public function getTokenSalt() {
- return array( $this->getTitle()->getPrefixedText(), $this->getUser() );
+ return array( $this->getRbTitle()->getPrefixedText(), $this->getRbUser() );
}
- private function getUser() {
+ private function getRbUser() {
if ( $this->mUser !== null ) {
return $this->mUser;
}
@@ -165,7 +160,7 @@ class ApiRollback extends ApiBase {
/**
* @return Title
*/
- private function getTitle() {
+ private function getRbTitle() {
if ( $this->mTitleObj !== null ) {
return $this->mTitleObj;
}
@@ -184,7 +179,7 @@ class ApiRollback extends ApiBase {
return $this->mTitleObj;
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=rollback&title=Main%20Page&user=Catrope&token=123ABC',
'api.php?action=rollback&title=Main%20Page&user=217.121.114.116&token=123ABC&summary=Reverting%20vandalism&markbot=1'
diff --git a/includes/api/ApiRsd.php b/includes/api/ApiRsd.php
index e4410379..f0e1fad6 100644
--- a/includes/api/ApiRsd.php
+++ b/includes/api/ApiRsd.php
@@ -25,10 +25,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- require_once( 'ApiBase.php' );
-}
-
/**
* API module for sending out RSD information
* @ingroup API
@@ -71,7 +67,7 @@ class ApiRsd extends ApiBase {
return 'Export an RSD (Really Simple Discovery) schema';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=rsd'
);
diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php
index 9c3bcf69..db94fd5b 100644
--- a/includes/api/ApiUnblock.php
+++ b/includes/api/ApiUnblock.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* API module that facilitates the unblocking of users. Requires API write mode
* to be enabled.
@@ -45,11 +40,11 @@ class ApiUnblock extends ApiBase {
* Unblocks the specified user or provides the reason the unblock failed.
*/
public function execute() {
- global $wgUser;
+ $user = $this->getUser();
$params = $this->extractRequestParams();
if ( $params['gettoken'] ) {
- $res['unblocktoken'] = $wgUser->editToken( '', $this->getMain()->getRequest() );
+ $res['unblocktoken'] = $user->getEditToken( '', $this->getMain()->getRequest() );
$this->getResult()->addValue( null, $this->getModuleName(), $res );
return;
}
@@ -61,12 +56,12 @@ class ApiUnblock extends ApiBase {
$this->dieUsageMsg( 'unblock-idanduser' );
}
- if ( !$wgUser->isAllowed( 'block' ) ) {
+ if ( !$user->isAllowed( 'block' ) ) {
$this->dieUsageMsg( 'cantunblock' );
}
# bug 15810: blocked admins should have limited access here
- if ( $wgUser->isBlocked() ) {
- $status = SpecialBlock::checkUnblockSelf( $params['user'] );
+ if ( $user->isBlocked() ) {
+ $status = SpecialBlock::checkUnblockSelf( $params['user'], $user );
if ( $status !== true ) {
$this->dieUsageMsg( $status );
}
@@ -77,7 +72,7 @@ class ApiUnblock extends ApiBase {
'Reason' => is_null( $params['reason'] ) ? '' : $params['reason']
);
$block = Block::newFromTarget( $data['Target'] );
- $retval = SpecialUnblock::processUnblock( $data );
+ $retval = SpecialUnblock::processUnblock( $data, $this->getContext() );
if ( $retval !== true ) {
$this->dieUsageMsg( $retval[0] );
}
@@ -141,7 +136,7 @@ class ApiUnblock extends ApiBase {
return '';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=unblock&id=105',
'api.php?action=unblock&user=Bob&reason=Sorry%20Bob'
diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php
index 2be70108..d3429972 100644
--- a/includes/api/ApiUndelete.php
+++ b/includes/api/ApiUndelete.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* @ingroup API
*/
@@ -39,14 +34,13 @@ class ApiUndelete extends ApiBase {
}
public function execute() {
- global $wgUser;
$params = $this->extractRequestParams();
- if ( !$wgUser->isAllowed( 'undelete' ) ) {
+ if ( !$this->getUser()->isAllowed( 'undelete' ) ) {
$this->dieUsageMsg( 'permdenied-undelete' );
}
- if ( $wgUser->isBlocked() ) {
+ if ( $this->getUser()->isBlocked() ) {
$this->dieUsageMsg( 'blockedtext' );
}
@@ -74,7 +68,7 @@ class ApiUndelete extends ApiBase {
if ( $retval[1] ) {
wfRunHooks( 'FileUndeleteComplete',
- array( $titleObj, array(), $wgUser, $params['reason'] ) );
+ array( $titleObj, array(), $this->getUser(), $params['reason'] ) );
}
$this->setWatch( $params['watchlist'], $titleObj );
@@ -152,7 +146,7 @@ class ApiUndelete extends ApiBase {
return '';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=undelete&title=Main%20Page&token=123ABC&reason=Restoring%20main%20page',
'api.php?action=undelete&title=Main%20Page&token=123ABC&timestamps=20070703220045|20070702194856'
diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php
index e9598378..fdc1eff0 100644
--- a/includes/api/ApiUpload.php
+++ b/includes/api/ApiUpload.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* @ingroup API
*/
@@ -46,18 +41,19 @@ class ApiUpload extends ApiBase {
}
public function execute() {
- global $wgUser;
-
// Check whether upload is enabled
if ( !UploadBase::isEnabled() ) {
$this->dieUsageMsg( 'uploaddisabled' );
}
+ $user = $this->getUser();
+
// Parameter handling
$this->mParams = $this->extractRequestParams();
$request = $this->getMain()->getRequest();
// Add the uploaded file to the params array
$this->mParams['file'] = $request->getFileName( 'file' );
+ $this->mParams['chunk'] = $request->getFileName( 'chunk' );
// Copy the session key to the file key, for backward compatibility.
if( !$this->mParams['filekey'] && $this->mParams['sessionkey'] ) {
@@ -74,7 +70,7 @@ class ApiUpload extends ApiBase {
}
// First check permission to upload
- $this->checkPermissions( $wgUser );
+ $this->checkPermissions( $user );
// Fetch the file
$status = $this->mUpload->fetchFile();
@@ -85,49 +81,26 @@ class ApiUpload extends ApiBase {
}
// Check if the uploaded file is sane
- $this->verifyUpload();
-
-
+ if ( $this->mParams['chunk'] ) {
+ $maxSize = $this->mUpload->getMaxUploadSize( );
+ if( $this->mParams['filesize'] > $maxSize ) {
+ $this->dieUsage( 'The file you submitted was too large', 'file-too-large' );
+ }
+ } else {
+ $this->verifyUpload();
+ }
+
// 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 );
+ $permErrors = $this->mUpload->verifyTitlePermissions( $user );
if ( $permErrors !== true ) {
$this->dieRecoverableError( $permErrors[0], 'filename' );
}
}
-
- // Prepare the API result
- $result = array();
-
- $warnings = $this->getApiWarnings();
- if ( $warnings ) {
- $result['result'] = 'Warning';
- $result['warnings'] = $warnings;
- // in case the warnings can be fixed with some further user action, let's stash this upload
- // and return a key they can use to restart it
- try {
- $result['filekey'] = $this->performStash();
- $result['sessionkey'] = $result['filekey']; // backwards compatibility
- } catch ( MWException $e ) {
- $result['warnings']['stashfailed'] = $e->getMessage();
- }
- } elseif ( $this->mParams['stash'] ) {
- // Some uploads can request they be stashed, so as not to publish them immediately.
- // In this case, a failure to stash ought to be fatal
- try {
- $result['result'] = 'Success';
- $result['filekey'] = $this->performStash();
- $result['sessionkey'] = $result['filekey']; // backwards compatibility
- } catch ( MWException $e ) {
- $this->dieUsage( $e->getMessage(), 'stashfailed' );
- }
- } else {
- // This is the most common case -- a normal upload with no warnings
- // $result will be formatted properly for the API already, with a status
- $result = $this->performUpload();
- }
+ // Get the result based on the current upload context:
+ $result = $this->getContextResult();
if ( $result['result'] === 'Success' ) {
$result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() );
@@ -138,7 +111,93 @@ class ApiUpload extends ApiBase {
// Cleanup any temporary mess
$this->mUpload->cleanupTempFile();
}
-
+ /**
+ * Get an uplaod result based on upload context
+ */
+ private function getContextResult(){
+ $warnings = $this->getApiWarnings();
+ if ( $warnings ) {
+ // Get warnings formated in result array format
+ return $this->getWarningsResult( $warnings );
+ } elseif ( $this->mParams['chunk'] ) {
+ // Add chunk, and get result
+ return $this->getChunkResult();
+ } elseif ( $this->mParams['stash'] ) {
+ // Stash the file and get stash result
+ return $this->getStashResult();
+ }
+ // This is the most common case -- a normal upload with no warnings
+ // performUpload will return a formatted properly for the API with status
+ return $this->performUpload();
+ }
+ /**
+ * Get Stash Result, throws an expetion if the file could not be stashed.
+ */
+ private function getStashResult(){
+ $result = array ();
+ // 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['filekey'] = $this->performStash();
+ $result['sessionkey'] = $result['filekey']; // backwards compatibility
+ } catch ( MWException $e ) {
+ $this->dieUsage( $e->getMessage(), 'stashfailed' );
+ }
+ return $result;
+ }
+ /**
+ * Get Warnings Result
+ * @param $warnings Array of Api upload warnings
+ */
+ private function getWarningsResult( $warnings ){
+ $result = array();
+ $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['filekey'] = $this->performStash();
+ $result['sessionkey'] = $result['filekey']; // backwards compatibility
+ } catch ( MWException $e ) {
+ $result['warnings']['stashfailed'] = $e->getMessage();
+ }
+ return $result;
+ }
+ /**
+ * Get the result of a chunk upload.
+ */
+ private function getChunkResult(){
+ $result = array();
+
+ $result['result'] = 'Continue';
+ $request = $this->getMain()->getRequest();
+ $chunkPath = $request->getFileTempname( 'chunk' );
+ $chunkSize = $request->getUpload( 'chunk' )->getSize();
+ if ($this->mParams['offset'] == 0) {
+ $result['filekey'] = $this->performStash();
+ } else {
+ $status = $this->mUpload->addChunk($chunkPath, $chunkSize,
+ $this->mParams['offset']);
+ if ( !$status->isGood() ) {
+ $this->dieUsage( $status->getWikiText(), 'stashfailed' );
+ return ;
+ }
+ $result['filekey'] = $this->mParams['filekey'];
+ // Check we added the last chunk:
+ if( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) {
+ $status = $this->mUpload->concatenateChunks();
+ if ( !$status->isGood() ) {
+ $this->dieUsage( $status->getWikiText(), 'stashfailed' );
+ return ;
+ }
+ $result['result'] = 'Success';
+ }
+ }
+ $result['offset'] = $this->mParams['offset'] + $chunkSize;
+ return $result;
+ }
+
/**
* Stash the file and return the file key
* Also re-raises exceptions with slightly more informative message strings (useful for API)
@@ -147,7 +206,12 @@ class ApiUpload extends ApiBase {
*/
function performStash() {
try {
- $fileKey = $this->mUpload->stashFile()->getFileKey();
+ $stashFile = $this->mUpload->stashFile();
+
+ if ( !$stashFile ) {
+ throw new MWException( 'Invalid stashed file' );
+ }
+ $fileKey = $stashFile->getFileKey();
} catch ( MWException $e ) {
$message = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage();
wfDebug( __METHOD__ . ' ' . $message . "\n");
@@ -188,9 +252,11 @@ class ApiUpload extends ApiBase {
protected function selectUploadModule() {
$request = $this->getMain()->getRequest();
- // One and only one of the following parameters is needed
- $this->requireOnlyOneParameter( $this->mParams,
- 'filekey', 'file', 'url', 'statuskey' );
+ // chunk or one and only one of the following parameters is needed
+ if( !$this->mParams['chunk'] ) {
+ $this->requireOnlyOneParameter( $this->mParams,
+ 'filekey', 'file', 'url', 'statuskey' );
+ }
if ( $this->mParams['statuskey'] ) {
$this->checkAsyncDownloadEnabled();
@@ -214,17 +280,32 @@ class ApiUpload extends ApiBase {
$this->dieUsageMsg( array( 'missingparam', 'filename' ) );
}
- if ( $this->mParams['filekey'] ) {
+ if ( $this->mParams['chunk'] ) {
+ // Chunk upload
+ $this->mUpload = new UploadFromChunks();
+ if( isset( $this->mParams['filekey'] ) ){
+ // handle new chunk
+ $this->mUpload->continueChunks(
+ $this->mParams['filename'],
+ $this->mParams['filekey'],
+ $request->getUpload( 'chunk' )
+ );
+ } else {
+ // handle first chunk
+ $this->mUpload->initialize(
+ $this->mParams['filename'],
+ $request->getUpload( 'chunk' )
+ );
+ }
+ } elseif ( isset( $this->mParams['filekey'] ) ) {
// Upload stashed in a previous request
if ( !UploadFromStash::isValidKey( $this->mParams['filekey'] ) ) {
$this->dieUsageMsg( 'invalid-file-key' );
}
- // 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'] );
+ $this->mUpload = new UploadFromStash( $this->getUser() );
+ $this->mUpload->initialize( $this->mParams['filekey'], $this->mParams['filename'] );
} elseif ( isset( $this->mParams['file'] ) ) {
$this->mUpload = new UploadFromFile();
$this->mUpload->initialize(
@@ -255,7 +336,6 @@ class ApiUpload extends ApiBase {
$this->mUpload = new UploadFromUrl;
$this->mUpload->initialize( $this->mParams['filename'],
$this->mParams['url'], $async );
-
}
return true;
@@ -300,6 +380,9 @@ class ApiUpload extends ApiBase {
$this->dieRecoverableError( 'illegal-filename', 'filename',
array( 'filename' => $verification['filtered'] ) );
break;
+ case UploadBase::FILENAME_TOO_LONG:
+ $this->dieRecoverableError( 'filename-toolong', 'filename' );
+ break;
case UploadBase::FILETYPE_MISSING:
$this->dieRecoverableError( 'filetype-missing', 'filename' );
break;
@@ -383,10 +466,10 @@ class ApiUpload extends ApiBase {
/**
* Perform the actual upload. Returns a suitable result array on success;
* dies on failure.
+ *
+ * @return array
*/
protected function performUpload() {
- global $wgUser;
-
// Use comment as initial page text by default
if ( is_null( $this->mParams['text'] ) ) {
$this->mParams['text'] = $this->mParams['comment'];
@@ -402,7 +485,7 @@ class ApiUpload extends ApiBase {
// No errors, no warnings: do the upload
$status = $this->mUpload->performUpload( $this->mParams['comment'],
- $this->mParams['text'], $watch, $wgUser );
+ $this->mParams['text'], $watch, $this->getUser() );
if ( !$status->isGood() ) {
$error = $status->getErrorsArray();
@@ -479,6 +562,10 @@ class ApiUpload extends ApiBase {
),
'stash' => false,
+ 'filesize' => null,
+ 'offset' => null,
+ 'chunk' => null,
+
'asyncdownload' => false,
'leavemessage' => false,
'statuskey' => null,
@@ -497,11 +584,15 @@ class ApiUpload extends ApiBase {
'watchlist' => 'Unconditionally add or remove the page from your watchlist, use preferences or do not change watch',
'ignorewarnings' => 'Ignore any warnings',
'file' => 'File contents',
- 'url' => 'Url to fetch the file from',
+ 'url' => 'URL to fetch the file from',
'filekey' => 'Key that identifies a previous upload that was stashed temporarily.',
'sessionkey' => 'Same as filekey, maintained for backward compatibility.',
'stash' => 'If set, the server will not add the file to the repository and stash it temporarily.',
+ 'chunk' => 'Chunk contents',
+ 'offset' => 'Offset of chunk in bytes',
+ 'filesize' => 'Filesize of entire upload',
+
'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',
@@ -552,12 +643,12 @@ class ApiUpload extends ApiBase {
return '';
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- '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&filekey=filekey&ignorewarnings=1',
+ 'api.php?action=upload&filename=Wiki.png&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png'
+ => 'Upload from a URL',
+ 'api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1'
+ => 'Complete an upload that failed due to warnings',
);
}
diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php
index 74948ce0..191dd3ec 100644
--- a/includes/api/ApiUserrights.php
+++ b/includes/api/ApiUserrights.php
@@ -25,11 +25,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( "ApiBase.php" );
-}
-
/**
* @ingroup API
*/
@@ -44,7 +39,7 @@ class ApiUserrights extends ApiBase {
public function execute() {
$params = $this->extractRequestParams();
- $user = $this->getUser();
+ $user = $this->getUrUser();
$form = new UserrightsPage;
$r['user'] = $user->getName();
@@ -62,7 +57,7 @@ class ApiUserrights extends ApiBase {
/**
* @return User
*/
- private function getUser() {
+ private function getUrUser() {
if ( $this->mUser !== null ) {
return $this->mUser;
}
@@ -130,10 +125,10 @@ class ApiUserrights extends ApiBase {
}
public function getTokenSalt() {
- return $this->getUser()->getName();
+ return $this->getUrUser()->getName();
}
- protected function getExamples() {
+ public function getExamples() {
return array(
'api.php?action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
);
diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php
index 27846ab7..fa382b3b 100644
--- a/includes/api/ApiWatch.php
+++ b/includes/api/ApiWatch.php
@@ -24,11 +24,6 @@
* @file
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- // Eclipse helper - will be ignored in production
- require_once( 'ApiBase.php' );
-}
-
/**
* API module to allow users to watch a page
*
@@ -41,8 +36,8 @@ class ApiWatch extends ApiBase {
}
public function execute() {
- global $wgUser;
- if ( !$wgUser->isLoggedIn() ) {
+ $user = $this->getUser();
+ if ( !$user->isLoggedIn() ) {
$this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
}
@@ -53,17 +48,16 @@ class ApiWatch extends ApiBase {
$this->dieUsageMsg( array( 'invalidtitle', $params['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 = WatchAction::doUnwatch( $title, $wgUser );
+ $res['message'] = $this->msg( 'removedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock();
+ $success = UnwatchAction::doUnwatch( $title, $user );
} else {
$res['watched'] = '';
- $res['message'] = wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() );
- $success = UnwatchAction::doWatch( $title, $wgUser );
+ $res['message'] = $this->msg( 'addedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock();
+ $success = WatchAction::doWatch( $title, $user );
}
if ( !$success ) {
$this->dieUsageMsg( 'hookaborted' );
@@ -118,10 +112,10 @@ class ApiWatch extends ApiBase {
) );
}
- protected function getExamples() {
+ public function getExamples() {
return array(
- 'api.php?action=watch&title=Main_Page',
- 'api.php?action=watch&title=Main_Page&unwatch=',
+ 'api.php?action=watch&title=Main_Page' => 'Watch the page "Main Page"',
+ 'api.php?action=watch&title=Main_Page&unwatch=' => 'Unwatch the page "Main Page"',
);
}
diff --git a/includes/cache/CacheDependency.php b/includes/cache/CacheDependency.php
index aa020664..0df0cd89 100644
--- a/includes/cache/CacheDependency.php
+++ b/includes/cache/CacheDependency.php
@@ -28,6 +28,8 @@ class DependencyWrapper {
/**
* Returns true if any of the dependencies have expired
+ *
+ * @return bool
*/
function isExpired() {
foreach ( $this->deps as $dep ) {
@@ -51,6 +53,7 @@ class DependencyWrapper {
/**
* Get the user-defined value
+ * @return bool|\Mixed
*/
function getValue() {
return $this->value;
@@ -143,6 +146,9 @@ class FileDependency extends CacheDependency {
$this->timestamp = $timestamp;
}
+ /**
+ * @return array
+ */
function __sleep() {
$this->loadDependencyValues();
return array( 'filename', 'timestamp' );
@@ -265,11 +271,15 @@ class TitleListDependency extends CacheDependency {
/**
* Construct a dependency on a list of titles
+ * @param $linkBatch LinkBatch
*/
function __construct( LinkBatch $linkBatch ) {
$this->linkBatch = $linkBatch;
}
+ /**
+ * @return array
+ */
function calculateTimestamps() {
# Initialise values to false
$timestamps = array();
@@ -314,6 +324,9 @@ class TitleListDependency extends CacheDependency {
return array( 'timestamps' );
}
+ /**
+ * @return LinkBatch
+ */
function getLinkBatch() {
if ( !isset( $this->linkBatch ) ) {
$this->linkBatch = new LinkBatch;
@@ -370,6 +383,9 @@ class GlobalDependency extends CacheDependency {
* @return bool
*/
function isExpired() {
+ if( !isset($GLOBALS[$this->name]) ) {
+ return true;
+ }
return $GLOBALS[$this->name] != $this->value;
}
}
diff --git a/includes/cache/FileCacheBase.php b/includes/cache/FileCacheBase.php
new file mode 100644
index 00000000..37401655
--- /dev/null
+++ b/includes/cache/FileCacheBase.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * Contain the FileCacheBase class
+ * @file
+ * @ingroup Cache
+ */
+abstract class FileCacheBase {
+ protected $mKey;
+ protected $mType = 'object';
+ protected $mExt = 'cache';
+ protected $mFilePath;
+ protected $mUseGzip;
+ /* lazy loaded */
+ protected $mCached;
+
+ /* @TODO: configurable? */
+ const MISS_FACTOR = 15; // log 1 every MISS_FACTOR cache misses
+ const MISS_TTL_SEC = 3600; // how many seconds ago is "recent"
+
+ protected function __construct() {
+ global $wgUseGzip;
+
+ $this->mUseGzip = (bool)$wgUseGzip;
+ }
+
+ /**
+ * Get the base file cache directory
+ * @return string
+ */
+ final protected function baseCacheDirectory() {
+ global $wgFileCacheDirectory;
+ return $wgFileCacheDirectory;
+ }
+
+ /**
+ * Get the base cache directory (not specific to this file)
+ * @return string
+ */
+ abstract protected function cacheDirectory();
+
+ /**
+ * Get the path to the cache file
+ * @return string
+ */
+ protected function cachePath() {
+ if ( $this->mFilePath !== null ) {
+ return $this->mFilePath;
+ }
+
+ $dir = $this->cacheDirectory();
+ # Build directories (methods include the trailing "/")
+ $subDirs = $this->typeSubdirectory() . $this->hashSubdirectory();
+ # Avoid extension confusion
+ $key = str_replace( '.', '%2E', urlencode( $this->mKey ) );
+ # Build the full file path
+ $this->mFilePath = "{$dir}/{$subDirs}{$key}.{$this->mExt}";
+ if ( $this->useGzip() ) {
+ $this->mFilePath .= '.gz';
+ }
+
+ return $this->mFilePath;
+ }
+
+ /**
+ * Check if the cache file exists
+ * @return bool
+ */
+ public function isCached() {
+ if ( $this->mCached === null ) {
+ $this->mCached = file_exists( $this->cachePath() );
+ }
+ return $this->mCached;
+ }
+
+ /**
+ * Get the last-modified timestamp of the cache file
+ * @return string|false TS_MW timestamp
+ */
+ public function cacheTimestamp() {
+ $timestamp = filemtime( $this->cachePath() );
+ return ( $timestamp !== false )
+ ? wfTimestamp( TS_MW, $timestamp )
+ : false;
+ }
+
+ /**
+ * Check if up to date cache file exists
+ * @param $timestamp string MW_TS timestamp
+ *
+ * @return bool
+ */
+ public function isCacheGood( $timestamp = '' ) {
+ global $wgCacheEpoch;
+
+ if ( !$this->isCached() ) {
+ return false;
+ }
+
+ $cachetime = $this->cacheTimestamp();
+ $good = ( $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime );
+ wfDebug( __METHOD__ . ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n" );
+
+ return $good;
+ }
+
+ /**
+ * Check if the cache is gzipped
+ * @return bool
+ */
+ protected function useGzip() {
+ return $this->mUseGzip;
+ }
+
+ /**
+ * Get the uncompressed text from the cache
+ * @return string
+ */
+ public function fetchText() {
+ // gzopen can transparently read from gziped or plain text
+ $fh = gzopen( $this->cachePath(), 'rb' );
+ return stream_get_contents( $fh );
+ }
+
+ /**
+ * Save and compress text to the cache
+ * @return string compressed text
+ */
+ public function saveText( $text ) {
+ global $wgUseFileCache;
+
+ if ( !$wgUseFileCache ) {
+ return false;
+ }
+
+ if ( $this->useGzip() ) {
+ $text = gzencode( $text );
+ }
+
+ $this->checkCacheDirs(); // build parent dir
+ if ( !file_put_contents( $this->cachePath(), $text, LOCK_EX ) ) {
+ wfDebug( __METHOD__ . "() failed saving ". $this->cachePath() . "\n");
+ $this->mCached = null;
+ return false;
+ }
+
+ $this->mCached = true;
+ return $text;
+ }
+
+ /**
+ * Clear the cache for this page
+ * @return void
+ */
+ public function clearCache() {
+ wfSuppressWarnings();
+ unlink( $this->cachePath() );
+ wfRestoreWarnings();
+ $this->mCached = false;
+ }
+
+ /**
+ * Create parent directors of $this->cachePath()
+ * @return void
+ */
+ protected function checkCacheDirs() {
+ wfMkdirParents( dirname( $this->cachePath() ), null, __METHOD__ );
+ }
+
+ /**
+ * Get the cache type subdirectory (with trailing slash)
+ * An extending class could use that method to alter the type -> directory
+ * mapping. @see HTMLFileCache::typeSubdirectory() for an example.
+ *
+ * @return string
+ */
+ protected function typeSubdirectory() {
+ return $this->mType . '/';
+ }
+
+ /**
+ * Return relative multi-level hash subdirectory (with trailing slash)
+ * or the empty string if not $wgFileCacheDepth
+ * @return string
+ */
+ protected function hashSubdirectory() {
+ global $wgFileCacheDepth;
+
+ $subdir = '';
+ if ( $wgFileCacheDepth > 0 ) {
+ $hash = md5( $this->mKey );
+ for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) {
+ $subdir .= substr( $hash, 0, $i ) . '/';
+ }
+ }
+
+ return $subdir;
+ }
+
+ /**
+ * Roughly increments the cache misses in the last hour by unique visitors
+ * @param $request WebRequest
+ * @return void
+ */
+ public function incrMissesRecent( WebRequest $request ) {
+ global $wgMemc;
+ if ( mt_rand( 0, self::MISS_FACTOR - 1 ) == 0 ) {
+ # Get a large IP range that should include the user even if that
+ # person's IP address changes
+ $ip = $request->getIP();
+ if ( !IP::isValid( $ip ) ) {
+ return;
+ }
+ $ip = IP::isIPv6( $ip )
+ ? IP::sanitizeRange( "$ip/32" )
+ : IP::sanitizeRange( "$ip/16" );
+
+ # Bail out if a request already came from this range...
+ $key = wfMemcKey( get_class( $this ), 'attempt', $this->mType, $this->mKey, $ip );
+ if ( $wgMemc->get( $key ) ) {
+ return; // possibly the same user
+ }
+ $wgMemc->set( $key, 1, self::MISS_TTL_SEC );
+
+ # Increment the number of cache misses...
+ $key = $this->cacheMissKey();
+ if ( $wgMemc->get( $key ) === false ) {
+ $wgMemc->set( $key, 1, self::MISS_TTL_SEC );
+ } else {
+ $wgMemc->incr( $key );
+ }
+ }
+ }
+
+ /**
+ * Roughly gets the cache misses in the last hour by unique visitors
+ * @return int
+ */
+ public function getMissesRecent() {
+ global $wgMemc;
+ return self::MISS_FACTOR * $wgMemc->get( $this->cacheMissKey() );
+ }
+
+ /**
+ * @return string
+ */
+ protected function cacheMissKey() {
+ return wfMemcKey( get_class( $this ), 'misses', $this->mType, $this->mKey );
+ }
+}
diff --git a/includes/GenderCache.php b/includes/cache/GenderCache.php
index a17ac024..342f8dba 100644
--- a/includes/GenderCache.php
+++ b/includes/cache/GenderCache.php
@@ -111,7 +111,7 @@ class GenderCache {
}
if ( count( $users ) === 0 ) {
- return false;
+ return;
}
$dbr = wfGetDB( DB_SLAVE );
diff --git a/includes/cache/HTMLCacheUpdate.php b/includes/cache/HTMLCacheUpdate.php
index d542800d..11e2ae74 100644
--- a/includes/cache/HTMLCacheUpdate.php
+++ b/includes/cache/HTMLCacheUpdate.php
@@ -23,8 +23,7 @@
*
* @ingroup Cache
*/
-class HTMLCacheUpdate
-{
+class HTMLCacheUpdate implements DeferrableUpdate {
/**
* @var Title
*/
@@ -33,6 +32,12 @@ class HTMLCacheUpdate
public $mTable, $mPrefix, $mStart, $mEnd;
public $mRowsPerJob, $mRowsPerQuery;
+ /**
+ * @param $titleTo
+ * @param $table
+ * @param $start bool
+ * @param $end bool
+ */
function __construct( $titleTo, $table, $start = false, $end = false ) {
global $wgUpdateRowsPerJob, $wgUpdateRowsPerQuery;
@@ -138,6 +143,9 @@ class HTMLCacheUpdate
Job::batchInsert( $jobs );
}
+ /**
+ * @return mixed
+ */
protected function insertJobs() {
$batches = $this->mCache->partition( $this->mTable, $this->mRowsPerJob );
if ( !$batches ) {
@@ -157,6 +165,7 @@ class HTMLCacheUpdate
/**
* Invalidate an array (or iterator) of Title objects, right now
+ * @param $titleArray array
*/
protected function invalidateTitles( $titleArray ) {
global $wgUseFileCache, $wgUseSquid;
@@ -179,7 +188,7 @@ class HTMLCacheUpdate
foreach ( $batches as $batch ) {
$dbw->update( 'page',
array( 'page_touched' => $timestamp ),
- array( 'page_id IN (' . $dbw->makeList( $batch ) . ')' ),
+ array( 'page_id' => $batch ),
__METHOD__
);
}
@@ -197,9 +206,9 @@ class HTMLCacheUpdate
}
}
}
-
}
+
/**
* Job wrapper for HTMLCacheUpdate. Gets run whenever a related
* job gets called from the queue.
diff --git a/includes/cache/HTMLFileCache.php b/includes/cache/HTMLFileCache.php
index 1095da2c..92130f69 100644
--- a/includes/cache/HTMLFileCache.php
+++ b/includes/cache/HTMLFileCache.php
@@ -4,170 +4,112 @@
* @file
* @ingroup Cache
*/
-
-/**
- * Handles talking to the file cache, putting stuff in and taking it back out.
- * Mostly called from Article.php for the emergency abort/fallback to cache.
- *
- * Global options that affect this module:
- * - $wgCachePages
- * - $wgCacheEpoch
- * - $wgUseFileCache
- * - $wgCacheDirectory
- * - $wgFileCacheDirectory
- * - $wgUseGzip
- *
- * @ingroup Cache
- */
-class HTMLFileCache {
-
+class HTMLFileCache extends FileCacheBase {
/**
- * @var Title
+ * Construct an ObjectFileCache from a Title and an action
+ * @param $title Title|string Title object or prefixed DB key string
+ * @param $action string
+ * @return HTMLFileCache
*/
- var $mTitle;
- var $mFileCache, $mType;
+ public static function newFromTitle( $title, $action ) {
+ $cache = new self();
- public function __construct( $title, $type = 'view' ) {
- $this->mTitle = $title;
- $this->mType = ($type == 'raw' || $type == 'view' ) ? $type : false;
- $this->fileCacheName(); // init name
- }
-
- public function fileCacheName() {
- if( !$this->mFileCache ) {
- global $wgCacheDirectory, $wgFileCacheDirectory, $wgFileCacheDepth;
-
- if ( $wgFileCacheDirectory ) {
- $dir = $wgFileCacheDirectory;
- } elseif ( $wgCacheDirectory ) {
- $dir = "$wgCacheDirectory/html";
- } else {
- throw new MWException( 'Please set $wgCacheDirectory in LocalSettings.php if you wish to use the HTML file cache' );
- }
-
- # Store raw pages (like CSS hits) elsewhere
- $subdir = ($this->mType === 'raw') ? 'raw/' : '';
-
- $key = $this->mTitle->getPrefixedDbkey();
- if ( $wgFileCacheDepth > 0 ) {
- $hash = md5( $key );
- for ( $i = 1; $i <= $wgFileCacheDepth; $i++ ) {
- $subdir .= substr( $hash, 0, $i ) . '/';
- }
- }
- # Avoid extension confusion
- $key = str_replace( '.', '%2E', urlencode( $key ) );
- $this->mFileCache = "{$dir}/{$subdir}{$key}.html";
+ $allowedTypes = self::cacheablePageActions();
+ if ( !in_array( $action, $allowedTypes ) ) {
+ throw new MWException( "Invalid filecache type given." );
+ }
+ $cache->mKey = ( $title instanceof Title )
+ ? $title->getPrefixedDBkey()
+ : (string)$title;
+ $cache->mType = (string)$action;
+ $cache->mExt = 'html';
- if( $this->useGzip() ) {
- $this->mFileCache .= '.gz';
- }
+ return $cache;
+ }
- wfDebug( __METHOD__ . ": {$this->mFileCache}\n" );
- }
- return $this->mFileCache;
+ /**
+ * Cacheable actions
+ * @return array
+ */
+ protected static function cacheablePageActions() {
+ return array( 'view', 'history' );
}
- public function isFileCached() {
- if( $this->mType === false ) {
- return false;
- }
- return file_exists( $this->fileCacheName() );
+ /**
+ * Get the base file cache directory
+ * @return string
+ */
+ protected function cacheDirectory() {
+ return $this->baseCacheDirectory(); // no subdir for b/c with old cache files
}
- public function fileCacheTime() {
- return wfTimestamp( TS_MW, filemtime( $this->fileCacheName() ) );
+ /**
+ * Get the cache type subdirectory (with the trailing slash) or the empty string
+ * Alter the type -> directory mapping to put action=view cache at the root.
+ *
+ * @return string
+ */
+ protected function typeSubdirectory() {
+ if ( $this->mType === 'view' ) {
+ return ''; // b/c to not skip existing cache
+ } else {
+ return $this->mType . '/';
+ }
}
/**
* Check if pages can be cached for this request/user
+ * @param $context IContextSource
* @return bool
*/
- public static function useFileCache() {
- global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest, $wgLang, $wgContLang;
- if( !$wgUseFileCache ) {
+ public static function useFileCache( IContextSource $context ) {
+ global $wgUseFileCache, $wgShowIPinHeader, $wgDebugToolbar, $wgContLang;
+ if ( !$wgUseFileCache ) {
return false;
}
+ if ( $wgShowIPinHeader || $wgDebugToolbar ) {
+ wfDebug( "HTML file cache skipped. Either \$wgShowIPinHeader and/or \$wgDebugToolbar on\n" );
+ return false;
+ }
+
// Get all query values
- $queryVals = $wgRequest->getValues();
- foreach( $queryVals as $query => $val ) {
- if( $query == 'title' || $query == 'curid' ) {
- continue;
+ $queryVals = $context->getRequest()->getValues();
+ foreach ( $queryVals as $query => $val ) {
+ if ( $query === 'title' || $query === 'curid' ) {
+ continue; // note: curid sets title
// Normal page view in query form can have action=view.
- // Raw hits for pages also stored, like .css pages for example.
- } elseif( $query == 'action' && $val == 'view' ) {
- continue;
- } elseif( $query == 'usemsgcache' && $val == 'yes' ) {
+ } elseif ( $query === 'action' && in_array( $val, self::cacheablePageActions() ) ) {
continue;
// Below are header setting params
- } elseif( $query == 'maxage' || $query == 'smaxage' || $query == 'ctype' || $query == 'gen' ) {
+ } elseif ( $query === 'maxage' || $query === 'smaxage' ) {
continue;
- } else {
- return false;
}
+ return false;
}
+ $user = $context->getUser();
// Check for non-standard user language; this covers uselang,
// and extensions for auto-detecting user language.
- $ulang = $wgLang->getCode();
+ $ulang = $context->getLanguage()->getCode();
$clang = $wgContLang->getCode();
// Check that there are no other sources of variation
- return !$wgShowIPinHeader && !$wgUser->getId() && !$wgUser->getNewtalk() && $ulang == $clang;
+ return !$user->getId() && !$user->getNewtalk() && $ulang == $clang;
}
/**
- * Check if up to date cache file exists
- * @param $timestamp string
- *
- * @return bool
+ * Read from cache to context output
+ * @param $context IContextSource
+ * @return void
*/
- public function isFileCacheGood( $timestamp = '' ) {
- global $wgCacheEpoch;
+ public function loadFromFileCache( IContextSource $context ) {
+ global $wgMimeType, $wgLanguageCode;
- if( !$this->isFileCached() ) {
- return false;
- }
-
- $cachetime = $this->fileCacheTime();
- $good = $timestamp <= $cachetime && $wgCacheEpoch <= $cachetime;
-
- wfDebug( __METHOD__ . ": cachetime $cachetime, touched '{$timestamp}' epoch {$wgCacheEpoch}, good $good\n");
- return $good;
- }
-
- public function useGzip() {
- global $wgUseGzip;
- return $wgUseGzip;
- }
-
- /* In handy string packages */
- public function fetchRawText() {
- return file_get_contents( $this->fileCacheName() );
- }
-
- public function fetchPageText() {
- if( $this->useGzip() ) {
- /* Why is there no gzfile_get_contents() or gzdecode()? */
- return implode( '', gzfile( $this->fileCacheName() ) );
- } else {
- return $this->fetchRawText();
- }
- }
-
- /* Working directory to/from output */
- public function loadFromFileCache() {
- 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=UTF-8" );
- header( "Content-Language: $wgLanguageCode" );
- }
-
- if( $this->useGzip() ) {
- if( wfClientAcceptsGzip() ) {
+ $filename = $this->cachePath();
+ $context->getOutput()->sendCacheControl();
+ header( "Content-Type: $wgMimeType; charset=UTF-8" );
+ header( "Content-Language: $wgLanguageCode" );
+ if ( $this->useGzip() ) {
+ if ( wfClientAcceptsGzip() ) {
header( 'Content-Encoding: gzip' );
} else {
/* Send uncompressed */
@@ -176,74 +118,70 @@ class HTMLFileCache {
}
}
readfile( $filename );
- $wgOut->disable(); // tell $wgOut that output is taken care of
- }
-
- protected function checkCacheDirs() {
- $filename = $this->fileCacheName();
- $mydir2 = substr($filename,0,strrpos($filename,'/')); # subdirectory level 2
- $mydir1 = substr($mydir2,0,strrpos($mydir2,'/')); # subdirectory level 1
-
- wfMkdirParents( $mydir1 );
- wfMkdirParents( $mydir2 );
+ $context->getOutput()->disable(); // tell $wgOut that output is taken care of
}
+ /**
+ * Save this cache object with the given text.
+ * Use this as an ob_start() handler.
+ * @param $text string
+ * @return bool Whether $wgUseFileCache is enabled
+ */
public function saveToFileCache( $text ) {
global $wgUseFileCache;
- if( !$wgUseFileCache || strlen( $text ) < 512 ) {
+
+ if ( !$wgUseFileCache || strlen( $text ) < 512 ) {
// Disabled or empty/broken output (OOM and PHP errors)
return $text;
}
wfDebug( __METHOD__ . "()\n", false);
- $this->checkCacheDirs();
+ $now = wfTimestampNow();
+ if ( $this->useGzip() ) {
+ $text = str_replace(
+ '</html>', '<!-- Cached/compressed '.$now." -->\n</html>", $text );
+ } else {
+ $text = str_replace(
+ '</html>', '<!-- Cached '.$now." -->\n</html>", $text );
+ }
- $f = fopen( $this->fileCacheName(), 'w' );
- if($f) {
- $now = wfTimestampNow();
- if( $this->useGzip() ) {
- $rawtext = str_replace( '</html>',
- '<!-- Cached/compressed '.$now." -->\n</html>",
- $text );
- $text = gzencode( $rawtext );
- } else {
- $text = str_replace( '</html>',
- '<!-- Cached '.$now." -->\n</html>",
- $text );
- }
- fwrite( $f, $text );
- fclose( $f );
- if( $this->useGzip() ) {
- if( wfClientAcceptsGzip() ) {
- header( 'Content-Encoding: gzip' );
- return $text;
- } else {
- return $rawtext;
- }
+ // Store text to FS...
+ $compressed = $this->saveText( $text );
+ if ( $compressed === false ) {
+ return $text; // error
+ }
+
+ // gzip output to buffer as needed and set headers...
+ if ( $this->useGzip() ) {
+ // @TODO: ugly wfClientAcceptsGzip() function - use context!
+ if ( wfClientAcceptsGzip() ) {
+ header( 'Content-Encoding: gzip' );
+ return $compressed;
} else {
return $text;
}
+ } else {
+ return $text;
}
- return $text;
}
- public static function clearFileCache( $title ) {
+ /**
+ * Clear the file caches for a page for all actions
+ * @param $title Title
+ * @return bool Whether $wgUseFileCache is enabled
+ */
+ public static function clearFileCache( Title $title ) {
global $wgUseFileCache;
if ( !$wgUseFileCache ) {
return false;
}
- wfSuppressWarnings();
-
- $fc = new self( $title, 'view' );
- unlink( $fc->fileCacheName() );
-
- $fc = new self( $title, 'raw' );
- unlink( $fc->fileCacheName() );
-
- wfRestoreWarnings();
+ foreach ( self::cacheablePageActions() as $type ) {
+ $fc = self::newFromTitle( $title, $type );
+ $fc->clearCache();
+ }
return true;
}
diff --git a/includes/cache/LinkBatch.php b/includes/cache/LinkBatch.php
index 0bd869fc..17e8739b 100644
--- a/includes/cache/LinkBatch.php
+++ b/includes/cache/LinkBatch.php
@@ -86,7 +86,8 @@ class LinkBatch {
/**
* Do the query and add the results to the LinkCache object
- * Return an array mapping PDBK to ID
+ *
+ * @return Array mapping PDBK to ID
*/
public function execute() {
$linkCache = LinkCache::singleton();
@@ -96,12 +97,15 @@ class LinkBatch {
/**
* Do the query and add the results to a given LinkCache object
* Return an array mapping PDBK to ID
+ *
+ * @param $cache LinkCache
+ * @return Array remaining IDs
*/
protected function executeInto( &$cache ) {
wfProfileIn( __METHOD__ );
$res = $this->doQuery();
- $ids = $this->addResultToCache( $cache, $res );
$this->doGenderQuery();
+ $ids = $this->addResultToCache( $cache, $res );
wfProfileOut( __METHOD__ );
return $ids;
}
@@ -112,8 +116,9 @@ class LinkBatch {
* This function *also* stores extra fields of the title used for link
* parsing to avoid extra DB queries.
*
- * @param $cache
+ * @param $cache LinkCache
* @param $res
+ * @return Array of remaining titles
*/
public function addResultToCache( $cache, $res ) {
if ( !$res ) {
@@ -126,7 +131,7 @@ class LinkBatch {
$remaining = $this->data;
foreach ( $res as $row ) {
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
- $cache->addGoodLinkObj( $row->page_id, $title, $row->page_len, $row->page_is_redirect, $row->page_latest );
+ $cache->addGoodLinkObjFromRow( $title, $row );
$ids[$title->getPrefixedDBkey()] = $row->page_id;
unset( $remaining[$row->page_namespace][$row->page_title] );
}
@@ -144,6 +149,7 @@ class LinkBatch {
/**
* Perform the existence test query, return a ResultWrapper with page_id fields
+ * @return Bool|ResultWrapper
*/
public function doQuery() {
if ( $this->isEmpty() ) {
@@ -168,6 +174,11 @@ class LinkBatch {
return $res;
}
+ /**
+ * Do (and cache) {{GENDER:...}} information for userpages in this LinkBatch
+ *
+ * @return bool whether the query was successful
+ */
public function doGenderQuery() {
if ( $this->isEmpty() ) {
return false;
@@ -180,6 +191,7 @@ class LinkBatch {
$genderCache = GenderCache::singleton();
$genderCache->dolinkBatch( $this->data, $this->caller );
+ return true;
}
/**
diff --git a/includes/cache/LinkCache.php b/includes/cache/LinkCache.php
index aeb10eb0..a73eaaa4 100644
--- a/includes/cache/LinkCache.php
+++ b/includes/cache/LinkCache.php
@@ -9,8 +9,10 @@ class LinkCache {
// becomes incompatible with the new version.
private $mClassVer = 4;
- private $mGoodLinks, $mBadLinks;
- private $mForUpdate;
+ private $mGoodLinks = array();
+ private $mGoodLinkFields = array();
+ private $mBadLinks = array();
+ private $mForUpdate = false;
/**
* Get an instance of this class
@@ -25,13 +27,6 @@ class LinkCache {
return $instance;
}
- function __construct() {
- $this->mForUpdate = false;
- $this->mGoodLinks = array();
- $this->mGoodLinkFields = array();
- $this->mBadLinks = array();
- }
-
/**
* General accessor to get/set whether SELECT FOR UPDATE should be used
*
@@ -96,6 +91,23 @@ class LinkCache {
}
/**
+ * Same as above with better interface.
+ * @since 1.19
+ * @param $title Title
+ * @param $row object which has the fields page_id, page_is_redirect,
+ * page_latest
+ */
+ public function addGoodLinkObjFromRow( $title, $row ) {
+ $dbkey = $title->getPrefixedDbKey();
+ $this->mGoodLinks[$dbkey] = intval( $row->page_id );
+ $this->mGoodLinkFields[$dbkey] = array(
+ 'length' => intval( $row->page_len ),
+ 'redirect' => intval( $row->page_is_redirect ),
+ 'revision' => intval( $row->page_latest ),
+ );
+ }
+
+ /**
* @param $title Title
*/
public function addBadLinkObj( $title ) {
@@ -114,15 +126,9 @@ class LinkCache {
*/
public function clearLink( $title ) {
$dbkey = $title->getPrefixedDbKey();
- if( isset($this->mBadLinks[$dbkey]) ) {
- unset($this->mBadLinks[$dbkey]);
- }
- if( isset($this->mGoodLinks[$dbkey]) ) {
- unset($this->mGoodLinks[$dbkey]);
- }
- if( isset($this->mGoodLinkFields[$dbkey]) ) {
- unset($this->mGoodLinkFields[$dbkey]);
- }
+ unset( $this->mBadLinks[$dbkey] );
+ unset( $this->mGoodLinks[$dbkey] );
+ unset( $this->mGoodLinkFields[$dbkey] );
}
public function getGoodLinks() { return $this->mGoodLinks; }
@@ -188,22 +194,13 @@ class LinkCache {
__METHOD__, $options );
# Set fields...
if ( $s !== false ) {
+ $this->addGoodLinkObjFromRow( $nt, $s );
$id = intval( $s->page_id );
- $len = intval( $s->page_len );
- $redirect = intval( $s->page_is_redirect );
- $revision = intval( $s->page_latest );
} else {
+ $this->addBadLinkObj( $nt );
$id = 0;
- $len = -1;
- $redirect = 0;
- $revision = 0;
}
- if ( $id == 0 ) {
- $this->addBadLinkObj( $nt );
- } else {
- $this->addGoodLinkObj( $id, $nt, $len, $redirect, $revision );
- }
wfProfileOut( __METHOD__ );
return $id;
}
diff --git a/includes/cache/MessageCache.php b/includes/cache/MessageCache.php
index 79883844..146edd28 100644
--- a/includes/cache/MessageCache.php
+++ b/includes/cache/MessageCache.php
@@ -176,7 +176,7 @@ class MessageCache {
global $wgCacheDirectory;
$filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
- wfMkdirParents( $wgCacheDirectory ); // might fail
+ wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
wfSuppressWarnings();
$file = fopen( $filename, 'w' );
@@ -199,7 +199,7 @@ class MessageCache {
$filename = "$wgCacheDirectory/messages-" . wfWikiID() . "-$code";
$tempFilename = $filename . '.tmp';
- wfMkdirParents( $wgCacheDirectory ); // might fail
+ wfMkdirParents( $wgCacheDirectory, null, __METHOD__ ); // might fail
wfSuppressWarnings();
$file = fopen( $tempFilename, 'w' );
@@ -499,10 +499,10 @@ class MessageCache {
$codes = array_keys( Language::getLanguageNames() );
}
- global $parserMemc;
+ global $wgMemc;
foreach ( $codes as $code ) {
$sidebarKey = wfMemcKey( 'sidebar', $code );
- $parserMemc->delete( $sidebarKey );
+ $wgMemc->delete( $sidebarKey );
}
// Update the message in the message blob store
@@ -553,6 +553,8 @@ class MessageCache {
/**
* Represents a write lock on the messages key
*
+ * @param $key string
+ *
* @return Boolean: success
*/
function lock( $key ) {
@@ -585,6 +587,8 @@ class MessageCache {
* fallback).
* @param $isFullKey Boolean: specifies whether $key is a two part key
* "msg/lang".
+ *
+ * @return string|false
*/
function get( $key, $useDB = true, $langcode = true, $isFullKey = false ) {
global $wgLanguageCode, $wgContLang;
@@ -691,6 +695,8 @@ class MessageCache {
*
* @param $title String: Message cache key with initial uppercase letter.
* @param $code String: code denoting the language to try.
+ *
+ * @return string|false
*/
function getMsgFromNamespace( $title, $code ) {
global $wgAdaptiveMessageCache;
@@ -814,10 +820,9 @@ class MessageCache {
/**
* @param $text string
- * @param $string Title|string
* @param $title Title
- * @param $interface bool
* @param $linestart bool
+ * @param $interface bool
* @param $language
* @return ParserOutput
*/
@@ -828,13 +833,8 @@ class MessageCache {
$parser = $this->getParser();
$popts = $this->getParserOptions();
-
- if ( $interface ) {
- $popts->setInterfaceMessage( true );
- }
- if ( $language !== null ) {
- $popts->setTargetLanguage( $language );
- }
+ $popts->setInterfaceMessage( $interface );
+ $popts->setTargetLanguage( $language );
wfProfileIn( __METHOD__ );
if ( !$title || !$title instanceof Title ) {
@@ -878,6 +878,10 @@ class MessageCache {
$this->mLoadedLanguages = array();
}
+ /**
+ * @param $key
+ * @return array
+ */
public function figureMessage( $key ) {
global $wgLanguageCode;
$pieces = explode( '/', $key );
@@ -935,6 +939,9 @@ class MessageCache {
wfProfileOut( __METHOD__ );
}
+ /**
+ * @return array
+ */
public function getMostUsedMessages() {
wfProfileIn( __METHOD__ );
$cachekey = wfMemcKey( 'message-profiling' );
@@ -968,4 +975,25 @@ class MessageCache {
return array_keys( $list );
}
+ /**
+ * Get all message keys stored in the message cache for a given language.
+ * If $code is the content language code, this will return all message keys
+ * for which MediaWiki:msgkey exists. If $code is another language code, this
+ * will ONLY return message keys for which MediaWiki:msgkey/$code exists.
+ * @param $code string
+ * @return array of message keys (strings)
+ */
+ public function getAllMessageKeys( $code ) {
+ global $wgContLang;
+ $this->load( $code );
+ if ( !isset( $this->mCache[$code] ) ) {
+ // Apparently load() failed
+ return null;
+ }
+ $cache = $this->mCache[$code]; // Copy the cache
+ unset( $cache['VERSION'] ); // Remove the VERSION key
+ $cache = array_diff( $cache, array( '!NONEXISTENT' ) ); // Remove any !NONEXISTENT keys
+ // Keys may appear with a capital first letter. lcfirst them.
+ return array_map( array( $wgContLang, 'lcfirst' ), array_keys( $cache ) );
+ }
}
diff --git a/includes/cache/ObjectFileCache.php b/includes/cache/ObjectFileCache.php
new file mode 100644
index 00000000..3356f1fc
--- /dev/null
+++ b/includes/cache/ObjectFileCache.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Contain the ObjectFileCache class
+ * @file
+ * @ingroup Cache
+ */
+class ObjectFileCache extends FileCacheBase {
+ /**
+ * Construct an ObjectFileCache from a key and a type
+ * @param $key string
+ * @param $type string
+ * @return ObjectFileCache
+ */
+ public static function newFromKey( $key, $type ) {
+ $cache = new self();
+
+ $cache->mKey = (string)$key;
+ $cache->mType = (string)$type;
+
+ return $cache;
+ }
+
+ /**
+ * Get the base file cache directory
+ * @return string
+ */
+ protected function cacheDirectory() {
+ return $this->baseCacheDirectory() . '/object';
+ }
+}
diff --git a/includes/cache/ResourceFileCache.php b/includes/cache/ResourceFileCache.php
new file mode 100644
index 00000000..e73fc2d7
--- /dev/null
+++ b/includes/cache/ResourceFileCache.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Contain the ResourceFileCache class
+ * @file
+ * @ingroup Cache
+ */
+class ResourceFileCache extends FileCacheBase {
+ protected $mCacheWorthy;
+
+ /* @TODO: configurable? */
+ const MISS_THRESHOLD = 360; // 6/min * 60 min
+
+ /**
+ * Construct an ResourceFileCache from a context
+ * @param $context ResourceLoaderContext
+ * @return ResourceFileCache
+ */
+ public static function newFromContext( ResourceLoaderContext $context ) {
+ $cache = new self();
+
+ if ( $context->getOnly() === 'styles' ) {
+ $cache->mType = 'css';
+ } else {
+ $cache->mType = 'js';
+ }
+ $modules = array_unique( $context->getModules() ); // remove duplicates
+ sort( $modules ); // normalize the order (permutation => combination)
+ $cache->mKey = sha1( $context->getHash() . implode( '|', $modules ) );
+ if ( count( $modules ) == 1 ) {
+ $cache->mCacheWorthy = true; // won't take up much space
+ }
+
+ return $cache;
+ }
+
+ /**
+ * Check if an RL request can be cached.
+ * Caller is responsible for checking if any modules are private.
+ * @param $context ResourceLoaderContext
+ * @return bool
+ */
+ public static function useFileCache( ResourceLoaderContext $context ) {
+ global $wgUseFileCache, $wgDefaultSkin, $wgLanguageCode;
+ if ( !$wgUseFileCache ) {
+ return false;
+ }
+ // Get all query values
+ $queryVals = $context->getRequest()->getValues();
+ foreach ( $queryVals as $query => $val ) {
+ if ( $query === 'modules' || $query === 'version' || $query === '*' ) {
+ continue; // note: &* added as IE fix
+ } elseif ( $query === 'skin' && $val === $wgDefaultSkin ) {
+ continue;
+ } elseif ( $query === 'lang' && $val === $wgLanguageCode ) {
+ continue;
+ } elseif ( $query === 'only' && in_array( $val, array( 'styles', 'scripts' ) ) ) {
+ continue;
+ } elseif ( $query === 'debug' && $val === 'false' ) {
+ continue;
+ }
+ return false;
+ }
+ return true; // cacheable
+ }
+
+ /**
+ * Get the base file cache directory
+ * @return string
+ */
+ protected function cacheDirectory() {
+ return $this->baseCacheDirectory() . '/resources';
+ }
+
+ /**
+ * Item has many recent cache misses
+ * @return bool
+ */
+ public function isCacheWorthy() {
+ if ( $this->mCacheWorthy === null ) {
+ $this->mCacheWorthy = (
+ $this->isCached() || // even stale cache indicates it was cache worthy
+ $this->getMissesRecent() >= self::MISS_THRESHOLD // many misses
+ );
+ }
+ return $this->mCacheWorthy;
+ }
+}
diff --git a/includes/context/ContextSource.php b/includes/context/ContextSource.php
new file mode 100644
index 00000000..45bd6fff
--- /dev/null
+++ b/includes/context/ContextSource.php
@@ -0,0 +1,170 @@
+<?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 Happy-melon
+ * @file
+ */
+
+/**
+ * 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 RequestContext object
+ * @since 1.18
+ * @return RequestContext
+ */
+ 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
+ *
+ * @since 1.18
+ * @param $context IContextSource
+ */
+ public function setContext( IContextSource $context ) {
+ $this->context = $context;
+ }
+
+ /**
+ * Get the WebRequest object
+ *
+ * @since 1.18
+ * @return WebRequest
+ */
+ public function getRequest() {
+ return $this->getContext()->getRequest();
+ }
+
+ /**
+ * Get the Title object
+ *
+ * @since 1.18
+ * @return Title
+ */
+ public function getTitle() {
+ return $this->getContext()->getTitle();
+ }
+
+ /**
+ * Check whether a WikiPage object can be get with getWikiPage().
+ * Callers should expect that an exception is thrown from getWikiPage()
+ * if this method returns false.
+ *
+ * @since 1.19
+ * @return bool
+ */
+ public function canUseWikiPage() {
+ return $this->getContext()->canUseWikiPage();
+ }
+
+ /**
+ * Get the WikiPage object.
+ * May throw an exception if there's no Title object set or the Title object
+ * belongs to a special namespace that doesn't have WikiPage, so use first
+ * canUseWikiPage() to check whether this method can be called safely.
+ *
+ * @since 1.19
+ * @return WikiPage
+ */
+ public function getWikiPage() {
+ return $this->getContext()->getWikiPage();
+ }
+
+ /**
+ * Get the OutputPage object
+ *
+ * @since 1.18
+ * @return OutputPage object
+ */
+ public function getOutput() {
+ return $this->getContext()->getOutput();
+ }
+
+ /**
+ * Get the User object
+ *
+ * @since 1.18
+ * @return User
+ */
+ public function getUser() {
+ return $this->getContext()->getUser();
+ }
+
+ /**
+ * Get the Language object
+ *
+ * @deprecated 1.19 Use getLanguage instead
+ * @return Language
+ */
+ public function getLang() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->getLanguage();
+ }
+
+ /**
+ * Get the Language object
+ *
+ * @since 1.19
+ * @return Language
+ */
+ public function getLanguage() {
+ return $this->getContext()->getLanguage();
+ }
+
+ /**
+ * Get the Skin object
+ *
+ * @since 1.18
+ * @return Skin
+ */
+ public function getSkin() {
+ return $this->getContext()->getSkin();
+ }
+
+ /**
+ * Get a Message object with context set
+ * Parameters are the same as wfMessage()
+ *
+ * @since 1.18
+ * @return Message object
+ */
+ public function msg( /* $args */ ) {
+ $args = func_get_args();
+ return call_user_func_array( array( $this->getContext(), 'msg' ), $args );
+ }
+
+}
+
diff --git a/includes/context/DerivativeContext.php b/includes/context/DerivativeContext.php
new file mode 100644
index 00000000..5adf3621
--- /dev/null
+++ b/includes/context/DerivativeContext.php
@@ -0,0 +1,286 @@
+<?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.19
+ *
+ * @author Daniel Friesen
+ * @file
+ */
+
+/**
+ * An IContextSource implementation which will inherit context from another source
+ * but allow individual pieces of context to be changed locally
+ * eg: A ContextSource that can inherit from the main RequestContext but have
+ * a different Title instance set on it.
+ */
+class DerivativeContext extends ContextSource {
+
+ /**
+ * @var WebRequest
+ */
+ private $request;
+
+ /**
+ * @var Title
+ */
+ private $title;
+
+ /**
+ * @var WikiPage
+ */
+ private $wikipage;
+
+ /**
+ * @var OutputPage
+ */
+ private $output;
+
+ /**
+ * @var User
+ */
+ private $user;
+
+ /**
+ * @var Language
+ */
+ private $lang;
+
+ /**
+ * @var Skin
+ */
+ private $skin;
+
+ /**
+ * Constructor
+ * @param $context IContextSource Context to inherit from
+ */
+ public function __construct( IContextSource $context ) {
+ $this->setContext( $context );
+ }
+
+ /**
+ * 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 ( !is_null( $this->request ) ) {
+ return $this->request;
+ } else {
+ return $this->getContext()->getRequest();
+ }
+ }
+
+ /**
+ * 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 ( !is_null( $this->title ) ) {
+ return $this->title;
+ } else {
+ return $this->getContext()->getTitle();
+ }
+ }
+
+ /**
+ * Check whether a WikiPage object can be get with getWikiPage().
+ * Callers should expect that an exception is thrown from getWikiPage()
+ * if this method returns false.
+ *
+ * @since 1.19
+ * @return bool
+ */
+ public function canUseWikiPage() {
+ if ( $this->wikipage !== null ) {
+ return true;
+ } elseif ( $this->title !== null ) {
+ return $this->title->canExist();
+ } else {
+ return $this->getContext()->canUseWikiPage();
+ }
+ }
+
+ /**
+ * Set the WikiPage object
+ *
+ * @since 1.19
+ * @param $p WikiPage object
+ */
+ public function setWikiPage( WikiPage $p ) {
+ $this->wikipage = $p;
+ }
+
+ /**
+ * Get the WikiPage object.
+ * May throw an exception if there's no Title object set or the Title object
+ * belongs to a special namespace that doesn't have WikiPage, so use first
+ * canUseWikiPage() to check whether this method can be called safely.
+ *
+ * @since 1.19
+ * @return WikiPage
+ */
+ public function getWikiPage() {
+ if ( !is_null( $this->wikipage ) ) {
+ return $this->wikipage;
+ } else {
+ return $this->getContext()->getWikiPage();
+ }
+ }
+
+ /**
+ * Set the OutputPage object
+ *
+ * @param $o OutputPage
+ */
+ public function setOutput( OutputPage $o ) {
+ $this->output = $o;
+ }
+
+ /**
+ * Get the OutputPage object
+ *
+ * @return OutputPage object
+ */
+ public function getOutput() {
+ if ( !is_null( $this->output ) ) {
+ return $this->output;
+ } else {
+ return $this->getContext()->getOutput();
+ }
+ }
+
+ /**
+ * 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 ( !is_null( $this->user ) ) {
+ return $this->user;
+ } else {
+ return $this->getContext()->getUser();
+ }
+ }
+
+ /**
+ * Set the Language object
+ *
+ * @deprecated 1.19 Use setLanguage instead
+ * @param $l Mixed Language instance or language code
+ */
+ public function setLang( $l ) {
+ wfDeprecated( __METHOD__, '1.19' );
+ $this->setLanguage( $l );
+ }
+
+ /**
+ * Set the Language object
+ *
+ * @param $l Mixed Language instance or language code
+ * @since 1.19
+ */
+ public function setLanguage( $l ) {
+ if ( $l instanceof Language ) {
+ $this->lang = $l;
+ } elseif ( is_string( $l ) ) {
+ $l = RequestContext::sanitizeLangCode( $l );
+ $obj = Language::factory( $l );
+ $this->lang = $obj;
+ } else {
+ throw new MWException( __METHOD__ . " was passed an invalid type of data." );
+ }
+ }
+
+ /**
+ * @deprecated 1.19 Use getLanguage instead
+ * @return Language
+ */
+ public function getLang() {
+ wfDeprecated( __METHOD__, '1.19' );
+ $this->getLanguage();
+ }
+
+ /**
+ * Get the Language object
+ *
+ * @return Language
+ * @since 1.19
+ */
+ public function getLanguage() {
+ if ( !is_null( $this->lang ) ) {
+ return $this->lang;
+ } else {
+ return $this->getContext()->getLanguage();
+ }
+ }
+
+ /**
+ * 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 ( !is_null( $this->skin ) ) {
+ return $this->skin;
+ } else {
+ return $this->getContext()->getSkin();
+ }
+ }
+
+}
+
diff --git a/includes/context/IContextSource.php b/includes/context/IContextSource.php
new file mode 100644
index 00000000..476035b5
--- /dev/null
+++ b/includes/context/IContextSource.php
@@ -0,0 +1,110 @@
+<?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 Happy-melon
+ * @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();
+
+ /**
+ * Check whether a WikiPage object can be get with getWikiPage().
+ * Callers should expect that an exception is thrown from getWikiPage()
+ * if this method returns false.
+ *
+ * @since 1.19
+ * @return bool
+ */
+ public function canUseWikiPage();
+
+ /**
+ * Get the WikiPage object.
+ * May throw an exception if there's no Title object set or the Title object
+ * belongs to a special namespace that doesn't have WikiPage, so use first
+ * canUseWikiPage() to check whether this method can be called safely.
+ *
+ * @since 1.19
+ * @return WikiPage
+ */
+ public function getWikiPage();
+
+ /**
+ * Get the OutputPage object
+ *
+ * @return OutputPage object
+ */
+ public function getOutput();
+
+ /**
+ * Get the User object
+ *
+ * @return User
+ */
+ public function getUser();
+
+ /**
+ * Get the Language object
+ *
+ * @deprecated 1.19 Use getLanguage instead
+ * @return Language
+ */
+ public function getLang();
+
+ /**
+ * Get the Language object
+ *
+ * @return Language
+ * @since 1.19
+ */
+ public function getLanguage();
+
+ /**
+ * Get the Skin object
+ *
+ * @return Skin
+ */
+ public function getSkin();
+
+ /**
+ * Get a Message object with context set
+ *
+ * @return Message object
+ */
+ public function msg();
+}
+
diff --git a/includes/context/RequestContext.php b/includes/context/RequestContext.php
new file mode 100644
index 00000000..1ffbc08c
--- /dev/null
+++ b/includes/context/RequestContext.php
@@ -0,0 +1,398 @@
+<?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
+ */
+
+/**
+ * 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 WikiPage
+ */
+ private $wikipage;
+
+ /**
+ * @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;
+ }
+
+ /**
+ * Check whether a WikiPage object can be get with getWikiPage().
+ * Callers should expect that an exception is thrown from getWikiPage()
+ * if this method returns false.
+ *
+ * @since 1.19
+ * @return bool
+ */
+ public function canUseWikiPage() {
+ if ( $this->wikipage !== null ) {
+ # If there's a WikiPage object set, we can for sure get it
+ return true;
+ }
+ $title = $this->getTitle();
+ if ( $title === null ) {
+ # No Title, no WikiPage
+ return false;
+ } else {
+ # Only namespaces whose pages are stored in the database can have WikiPage
+ return $title->canExist();
+ }
+ }
+
+ /**
+ * Set the WikiPage object
+ *
+ * @since 1.19
+ * @param $p WikiPage object
+ */
+ public function setWikiPage( WikiPage $p ) {
+ $this->wikipage = $p;
+ }
+
+ /**
+ * Get the WikiPage object.
+ * May throw an exception if there's no Title object set or the Title object
+ * belongs to a special namespace that doesn't have WikiPage, so use first
+ * canUseWikiPage() to check whether this method can be called safely.
+ *
+ * @since 1.19
+ * @return WikiPage
+ */
+ public function getWikiPage() {
+ if ( $this->wikipage === null ) {
+ $title = $this->getTitle();
+ if ( $title === null ) {
+ throw new MWException( __METHOD__ . ' called without Title object set' );
+ }
+ $this->wikipage = WikiPage::factory( $title );
+ }
+ return $this->wikipage;
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * Accepts a language code and ensures it's sane. Outputs a cleaned up language
+ * code and replaces with $wgLanguageCode if not sane.
+ * @param $code string
+ * @return string
+ */
+ public static function sanitizeLangCode( $code ) {
+ global $wgLanguageCode;
+
+ // 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;
+ }
+
+ return $code;
+ }
+
+ /**
+ * Set the Language object
+ *
+ * @deprecated 1.19 Use setLanguage instead
+ * @param $l Mixed Language instance or language code
+ */
+ public function setLang( $l ) {
+ wfDeprecated( __METHOD__, '1.19' );
+ $this->setLanguage( $l );
+ }
+
+ /**
+ * Set the Language object
+ *
+ * @param $l Mixed Language instance or language code
+ * @since 1.19
+ */
+ public function setLanguage( $l ) {
+ if ( $l instanceof Language ) {
+ $this->lang = $l;
+ } elseif ( is_string( $l ) ) {
+ $l = self::sanitizeLangCode( $l );
+ $obj = Language::factory( $l );
+ $this->lang = $obj;
+ } else {
+ throw new MWException( __METHOD__ . " was passed an invalid type of data." );
+ }
+ }
+
+ /**
+ * @deprecated 1.19 Use getLanguage instead
+ * @return Language
+ */
+ public function getLang() {
+ wfDeprecated( __METHOD__, '1.19' );
+ return $this->getLanguage();
+ }
+
+ /**
+ * Get the Language object
+ *
+ * @return Language
+ * @since 1.19
+ */
+ public function getLanguage() {
+ if ( $this->lang === null ) {
+ global $wgLanguageCode, $wgContLang;
+ $code = $this->getRequest()->getVal(
+ 'uselang',
+ $this->getUser()->getOption( 'language' )
+ );
+ $code = self::sanitizeLangCode( $code );
+
+ 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' );
+
+ $skin = null;
+ wfRunHooks( 'RequestContextCreateSkin', array( $this, &$skin ) );
+
+ // If the hook worked try to set a skin from it
+ if ( $skin instanceof Skin ) {
+ $this->skin = $skin;
+ } elseif ( is_string($skin) ) {
+ $this->skin = Skin::newFromKey( $skin );
+ }
+
+ // If this is still null (the hook didn't run or didn't work)
+ // then go through the normal processing to load a skin
+ if ( $this->skin === null ) {
+ global $wgHiddenPrefs;
+ if( !in_array( 'skin', $wgHiddenPrefs ) ) {
+ # 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 );
+ }
+
+ // After all that set a context on whatever skin got created
+ $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 )->setContext( $this );
+ }
+
+ /** 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;
+ }
+
+ /**
+ * Create a new extraneous context. The context is filled with information
+ * external to the current session.
+ * - Title is specified by argument
+ * - Request is a FauxRequest, or a FauxRequest can be specified by argument
+ * - User is an anonymous user, for separation IPv4 localhost is used
+ * - Language will be based on the anonymous user and request, may be content
+ * language or a uselang param in the fauxrequest data may change the lang
+ * - Skin will be based on the anonymous user, should be the wiki's default skin
+ *
+ * @param $title Title Title to use for the extraneous request
+ * @param $request Mixed A WebRequest or data to use for a FauxRequest
+ * @return RequestContext
+ */
+ public static function newExtraneousContext( Title $title, $request=array() ) {
+ $context = new self;
+ $context->setTitle( $title );
+ if ( $request instanceof WebRequest ) {
+ $context->setRequest( $request );
+ } else {
+ $context->setRequest( new FauxRequest( $request ) );
+ }
+ $context->user = User::newFromName( '127.0.0.1', false );
+ return $context;
+ }
+
+}
+
diff --git a/includes/db/CloneDatabase.php b/includes/db/CloneDatabase.php
index 3357268d..bd0895cf 100644
--- a/includes/db/CloneDatabase.php
+++ b/includes/db/CloneDatabase.php
@@ -93,12 +93,12 @@ class CloneDatabase {
# fix back and forth so tableName() works right.
self::changePrefix( $this->oldTablePrefix );
- $oldTableName = $this->db->tableName( $tbl, false );
+ $oldTableName = $this->db->tableName( $tbl, 'raw' );
self::changePrefix( $this->newTablePrefix );
- $newTableName = $this->db->tableName( $tbl, false );
+ $newTableName = $this->db->tableName( $tbl, 'raw' );
- if( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres' ) ) ) {
+ if( $this->dropCurrentTables && !in_array( $this->db->getType(), array( 'postgres', 'oracle' ) ) ) {
$this->db->dropTable( $tbl, __METHOD__ );
wfDebug( __METHOD__." dropping {$newTableName}\n", true);
//Dropping the oldTable because the prefix was changed
diff --git a/includes/db/Database.php b/includes/db/Database.php
index d1a3b2bd..9f34f812 100644
--- a/includes/db/Database.php
+++ b/includes/db/Database.php
@@ -224,6 +224,12 @@ abstract class DatabaseBase implements DatabaseType {
protected $mDefaultBigSelects = null;
protected $mSchemaVars = false;
+ protected $preparedArgs;
+
+ protected $htmlErrors;
+
+ protected $delimiter = ';';
+
# ------------------------------------------------------------------------------
# Accessors
# ------------------------------------------------------------------------------
@@ -242,7 +248,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Boolean, controls output of large amounts of debug information.
- * @param $debug:
+ * @param $debug bool|null
* - true to enable debugging
* - false to disable debugging
* - omitted or null to do nothing
@@ -290,7 +296,9 @@ abstract class DatabaseBase implements DatabaseType {
* code should use lastErrno() and lastError() to handle the
* situation as appropriate.
*
- * @return The previous value of the flag.
+ * @param $ignoreErrors bool|null
+ *
+ * @return bool The previous value of the flag.
*/
function ignoreErrors( $ignoreErrors = null ) {
return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
@@ -324,15 +332,17 @@ abstract class DatabaseBase implements DatabaseType {
* @return The previous table prefix.
*/
function tablePrefix( $prefix = null ) {
- return wfSetVar( $this->mTablePrefix, $prefix, true );
+ return wfSetVar( $this->mTablePrefix, $prefix );
}
/**
* 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
+ * @param $name string The entry of the info array to get, or null to get the
* whole array
+ *
+ * @return LoadBalancer|null
*/
function getLBInfo( $name = null ) {
if ( is_null( $name ) ) {
@@ -353,7 +363,6 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $name
* @param $value
- * @return void
*/
function setLBInfo( $name, $value = null ) {
if ( is_null( $value ) ) {
@@ -365,6 +374,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Set lag time in seconds for a fake slave
+ *
+ * @param $lag int
*/
function setFakeSlaveLag( $lag ) {
$this->mFakeSlaveLag = $lag;
@@ -372,6 +383,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Make this connection a fake master
+ *
+ * @param $enabled bool
*/
function setFakeMaster( $enabled = true ) {
$this->mFakeMaster = $enabled;
@@ -379,6 +392,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if this database supports (and uses) cascading deletes
+ *
+ * @return bool
*/
function cascadingDeletes() {
return false;
@@ -386,6 +401,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if this database supports (and uses) triggers (e.g. on the page table)
+ *
+ * @return bool
*/
function cleanupTriggers() {
return false;
@@ -394,6 +411,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if this database is strict about what can be put into an IP field.
* Specifically, it uses a NULL value instead of an empty string.
+ *
+ * @return bool
*/
function strictIPs() {
return false;
@@ -401,6 +420,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if this database uses timestamps rather than integers
+ *
+ * @return bool
*/
function realTimestamps() {
return false;
@@ -408,6 +429,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if this database does an implicit sort when doing GROUP BY
+ *
+ * @return bool
*/
function implicitGroupby() {
return true;
@@ -416,6 +439,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if this database does an implicit order by when the column has an index
* For example: SELECT page_title FROM page LIMIT 1
+ *
+ * @return bool
*/
function implicitOrderby() {
return true;
@@ -424,6 +449,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if this database requires that SELECT DISTINCT queries require that all
ORDER BY expressions occur in the SELECT list per the SQL92 standard
+ *
+ * @return bool
*/
function standardSelectDistinct() {
return true;
@@ -432,6 +459,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if this database can do a native search on IP columns
* e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
+ *
+ * @return bool
*/
function searchableIPs() {
return false;
@@ -439,6 +468,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns true if this database can use functional indexes
+ *
+ * @return bool
*/
function functionalIndexes() {
return false;
@@ -452,7 +483,6 @@ abstract class DatabaseBase implements DatabaseType {
return $this->mLastQuery;
}
-
/**
* Returns true if the connection may have been used for write queries.
* Should return true if unsure.
@@ -508,6 +538,10 @@ abstract class DatabaseBase implements DatabaseType {
/**
* General read-only accessor
+ *
+ * @param $name string
+ *
+ * @return string
*/
function getProperty( $name ) {
return $this->$name;
@@ -579,13 +613,27 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Called by serialize. Throw an exception when DB connection is serialized.
+ * This causes problems on some database engines because the connection is
+ * not restored on unserialize.
+ */
+ public function __sleep() {
+ throw new MWException( 'Database serialization may cause problems, since the connection is not restored on wakeup.' );
+ }
+
+ /**
* Same as new DatabaseMysql( ... ), kept for backward compatibility
* @deprecated since 1.17
*
+ * @param $server
+ * @param $user
+ * @param $password
+ * @param $dbName
+ * @param $flags int
* @return DatabaseMysql
*/
static function newFromParams( $server, $user, $password, $dbName, $flags = 0 ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.17' );
return new DatabaseMysql( $server, $user, $password, $dbName, $flags );
}
@@ -595,7 +643,7 @@ abstract class DatabaseBase implements DatabaseType {
* @see Database::factory()
*/
public final static function newFromType( $dbType, $p = array() ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
if ( isset( $p['tableprefix'] ) ) {
$p['tablePrefix'] = $p['tableprefix'];
}
@@ -627,9 +675,9 @@ abstract class DatabaseBase implements DatabaseType {
'mysql', 'postgres', 'sqlite', 'oracle', 'mssql', 'ibm_db2'
);
$dbType = strtolower( $dbType );
+ $class = 'Database' . ucfirst( $dbType );
- if( in_array( $dbType, $canonicalDBTypes ) ) {
- $class = 'Database' . ucfirst( $dbType );
+ if( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
return new $class(
isset( $p['host'] ) ? $p['host'] : false,
isset( $p['user'] ) ? $p['user'] : false,
@@ -649,6 +697,9 @@ abstract class DatabaseBase implements DatabaseType {
set_error_handler( array( $this, 'connectionErrorHandler' ) );
}
+ /**
+ * @return bool|string
+ */
protected function restoreErrorHandler() {
restore_error_handler();
if ( $this->htmlErrors !== false ) {
@@ -663,6 +714,10 @@ abstract class DatabaseBase implements DatabaseType {
}
}
+ /**
+ * @param $errno
+ * @param $errstr
+ */
protected function connectionErrorHandler( $errno, $errstr ) {
$this->mPHPError = $errstr;
}
@@ -695,7 +750,7 @@ 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
+ * @return ResultWrapper Result object to feed to fetchObject, fetchRow, ...; or false on failure
*/
protected abstract function doQuery( $sql );
@@ -703,6 +758,8 @@ abstract class DatabaseBase implements DatabaseType {
* Determine whether a query writes to the DB.
* Should return true if unsure.
*
+ * @param $sql string
+ *
* @return bool
*/
function isWriteQuery( $sql ) {
@@ -776,8 +833,8 @@ abstract class DatabaseBase implements DatabaseType {
# 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();
+ if ( strpos( $sqlstart, "SHOW " ) !== 0 && strpos( $sqlstart, "SET " ) !== 0 )
+ $this->begin( __METHOD__ . " ($fname)" );
}
if ( $this->debug() ) {
@@ -787,20 +844,21 @@ abstract class DatabaseBase implements DatabaseType {
$sqlx = substr( $commentedSql, 0, 500 );
$sqlx = strtr( $sqlx, "\t\n", ' ' );
- if ( $isMaster ) {
- wfDebug( "Query $cnt (master): $sqlx\n" );
- } else {
- wfDebug( "Query $cnt (slave): $sqlx\n" );
- }
+ $master = $isMaster ? 'master' : 'slave';
+ wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
}
if ( istainted( $sql ) & TC_MYSQL ) {
throw new MWException( 'Tainted query found' );
}
+ $queryId = MWDebug::query( $sql, $fname, $isMaster );
+
# Do the query and handle errors
$ret = $this->doQuery( $commentedSql );
+ MWDebug::queryTime( $queryId );
+
# Try reconnecting if the connection was lost
if ( false === $ret && $this->wasErrorReissuable() ) {
# Transaction is gone, like it or not
@@ -874,6 +932,9 @@ abstract class DatabaseBase implements DatabaseType {
* database classes. The query wrapper functions (select() etc.) should be
* used instead.
*
+ * @param $sql string
+ * @param $func string
+ *
* @return array
*/
function prepare( $sql, $func = 'DatabaseBase::prepare' ) {
@@ -885,6 +946,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Free a prepared query, generated by prepare().
+ * @param $prepared
*/
function freePrepared( $prepared ) {
/* No-op by default */
@@ -959,7 +1021,6 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $matches Array
* @return String
- * @private
*/
function fillPreparedArg( $matches ) {
switch( $matches[1] ) {
@@ -999,6 +1060,12 @@ abstract class DatabaseBase implements DatabaseType {
* This function exists for historical reasons, DatabaseBase::update() has a more standard
* calling convention and feature set
*
+ * @param $table string
+ * @param $var
+ * @param $value
+ * @param $cond
+ * @param $fname string
+ *
* @return bool
*/
function set( $table, $var, $value, $cond, $fname = 'DatabaseBase::set' ) {
@@ -1160,7 +1227,6 @@ abstract class DatabaseBase implements DatabaseType {
* @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
@@ -1324,7 +1390,7 @@ abstract class DatabaseBase implements DatabaseType {
$from = ' FROM ' . implode( ',', $this->tableNamesWithAlias( $table ) );
}
} elseif ( $table != '' ) {
- if ( $table { 0 } == ' ' ) {
+ if ( $table[0] == ' ' ) {
$from = ' FROM ' . $table;
} else {
$from = ' FROM ' . $this->tableName( $table );
@@ -1364,7 +1430,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $table string|array Table name
* @param $vars string|array Field names
- * @param $conds|array Conditions
+ * @param $conds array Conditions
* @param $fname string Caller function name
* @param $options string|array Query options
* @param $join_conds array|string Join conditions
@@ -1404,8 +1470,8 @@ abstract class DatabaseBase implements DatabaseType {
* Takes the same arguments as DatabaseBase::select().
*
* @param $table String: table name
- * @param $vars Array: unused
- * @param $conds Array: filters on the table
+ * @param Array|string $vars : unused
+ * @param Array|string $conds : filters on the table
* @param $fname String: function name for profiling
* @param $options Array: options for select
* @return Integer: row count
@@ -1428,7 +1494,9 @@ abstract class DatabaseBase implements DatabaseType {
* Removes most variables from an SQL query and replaces them with X or N for numbers.
* It's only slightly flawed. Don't use for anything important.
*
- * @param $sql String: A SQL Query
+ * @param $sql String A SQL Query
+ *
+ * @return string
*/
static function generalizeSQL( $sql ) {
# This does the same as the regexp below would do, but in such a way
@@ -1469,6 +1537,10 @@ abstract class DatabaseBase implements DatabaseType {
* Usually throws a DBQueryError on failure
* If errors are explicitly ignored, returns NULL on failure
*
+ * @param $table
+ * @param $index
+ * @param $fname string
+ *
* @return bool|null
*/
function indexExists( $table, $index, $fname = 'DatabaseBase::indexExists' ) {
@@ -1483,14 +1555,15 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Query whether a given table exists
*
- * @string table
+ * @param $table string
+ * @param $fname string
*
* @return bool
*/
- function tableExists( $table ) {
+ function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$old = $this->ignoreErrors( true );
- $res = $this->query( "SELECT 1 FROM $table LIMIT 1", __METHOD__ );
+ $res = $this->query( "SELECT 1 FROM $table LIMIT 1", $fname );
$this->ignoreErrors( $old );
return (bool)$res;
@@ -1498,6 +1571,9 @@ abstract class DatabaseBase implements DatabaseType {
/**
* mysql_field_type() wrapper
+ * @param $res
+ * @param $index
+ * @return string
*/
function fieldType( $res, $index ) {
if ( $res instanceof ResultWrapper ) {
@@ -1562,7 +1638,7 @@ abstract class DatabaseBase implements DatabaseType {
*
* @param $table String Table name. This will be passed through
* DatabaseBase::tableName().
- * @param $a Array of rows to insert
+ * @param $a Array of rows to insert
* @param $fname String Calling function name (use __METHOD__) for logs/profiling
* @param $options Array of options
*
@@ -1662,7 +1738,7 @@ abstract class DatabaseBase implements DatabaseType {
$opts = $this->makeUpdateOptions( $options );
$sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
- if ( $conds != '*' ) {
+ if ( $conds !== array() && $conds !== '*' ) {
$sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
}
@@ -1672,7 +1748,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Makes an encoded list of strings from an array
* @param $a Array containing the data
- * @param $mode:
+ * @param $mode int Constant
* - LIST_COMMA: comma separated, no field names
* - LIST_AND: ANDed WHERE clause (without the WHERE). See
* the documentation for $conds in DatabaseBase::select().
@@ -1800,6 +1876,9 @@ abstract class DatabaseBase implements DatabaseType {
* Change the current database
*
* @todo Explain what exactly will fail if this is not overridden.
+ *
+ * @param $db
+ *
* @return bool Success or failure
*/
function selectDB( $db ) {
@@ -1835,11 +1914,13 @@ 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.
+ * @param $format String One of:
+ * quoted - Automatically pass the table name through addIdentifierQuotes()
+ * so that it can be used in a query.
+ * raw - Do not add identifier quotes to the table name
* @return String: full database name
*/
- function tableName( $name, $quoted = true ) {
+ function tableName( $name, $format = 'quoted' ) {
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
@@ -1871,7 +1952,7 @@ abstract class DatabaseBase implements DatabaseType {
}
$prefix = $this->mTablePrefix; # Default prefix
- # A database name has been specified in input. We don't want any
+ # A database name has been specified in input. We don't want any
# prefixes added.
if ( isset( $database ) ) {
$prefix = '';
@@ -1891,11 +1972,11 @@ abstract class DatabaseBase implements DatabaseType {
# Quote the $database and $table and apply the prefix if not quoted.
if ( isset( $database ) ) {
- $database = ( !$quoted || $this->isQuotedIdentifier( $database ) ? $database : $this->addIdentifierQuotes( $database ) );
+ $database = ( $format == 'quoted' || $this->isQuotedIdentifier( $database ) ? $database : $this->addIdentifierQuotes( $database ) );
}
$table = "{$prefix}{$table}";
- if ( $quoted && !$this->isQuotedIdentifier( $table ) ) {
+ if ( $format == 'quoted' && !$this->isQuotedIdentifier( $table ) ) {
$table = $this->addIdentifierQuotes( "{$table}" );
}
@@ -1913,6 +1994,8 @@ abstract class DatabaseBase implements DatabaseType {
* extract($dbr->tableNames('user','watchlist'));
* $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
* WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
+ *
+ * @return array
*/
public function tableNames() {
$inArray = func_get_args();
@@ -1933,6 +2016,8 @@ abstract class DatabaseBase implements DatabaseType {
* list( $user, $watchlist ) = $dbr->tableNamesN('user','watchlist');
* $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
* WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
+ *
+ * @return array
*/
public function tableNamesN() {
$inArray = func_get_args();
@@ -1950,7 +2035,7 @@ abstract class DatabaseBase implements DatabaseType {
* e.g. tableName AS newTableName
*
* @param $name string Table name, see tableName()
- * @param $alias string Alias (optional)
+ * @param $alias string|bool Alias (optional)
* @return string SQL name for aliased table. Will not alias a table to its own name
*/
public function tableNameWithAlias( $name, $alias = false ) {
@@ -1982,9 +2067,9 @@ abstract class DatabaseBase implements DatabaseType {
* 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()
+ * @param $tables array ( [alias] => table )
+ * @param $use_index array Same as for select()
+ * @param $join_conds array Same as for select()
* @return string
*/
protected function tableNamesWithUseIndexOrJOIN(
@@ -2087,6 +2172,8 @@ abstract class DatabaseBase implements DatabaseType {
* Since MySQL is the odd one out here the double quotes are our generic
* and we implement backticks in DatabaseMysql.
*
+ * @param $s string
+ *
* @return string
*/
public function addIdentifierQuotes( $s ) {
@@ -2111,10 +2198,12 @@ abstract class DatabaseBase implements DatabaseType {
* was renamed to addIdentifierQuotes.
* @deprecated since 1.18 use addIdentifierQuotes
*
+ * @param $s string
+ *
* @return string
*/
function quote_ident( $s ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
return $this->addIdentifierQuotes( $s );
}
@@ -2123,12 +2212,20 @@ abstract class DatabaseBase implements DatabaseType {
* WARNING: you should almost never use this function directly,
* instead use buildLike() that escapes everything automatically
* @deprecated since 1.17, warnings in 1.17, removed in ???
+ *
+ * @param $s string
+ *
+ * @return string
*/
public function escapeLike( $s ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.17' );
return $this->escapeLikeInternal( $s );
}
+ /**
+ * @param $s string
+ * @return string
+ */
protected function escapeLikeInternal( $s ) {
$s = str_replace( '\\', '\\\\', $s );
$s = $this->strencode( $s );
@@ -2181,7 +2278,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns a token for buildLike() that denotes a '%' to be used in a LIKE query
*
- * @rerturn LikeMatch
+ * @return LikeMatch
*/
function anyString() {
return new LikeMatch( '%' );
@@ -2191,6 +2288,12 @@ abstract class DatabaseBase implements DatabaseType {
* Returns an appropriately quoted sequence value for inserting a new row.
* MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
* subclass will return an integer, and save the value for insertId()
+ *
+ * Any implementation of this function should *not* involve reusing
+ * sequence numbers created for rolled-back transactions.
+ * See http://bugs.mysql.com/bug.php?id=30767 for details.
+ * @param $seqName string
+ * @return null
*/
function nextSequenceValue( $seqName ) {
return null;
@@ -2203,6 +2306,8 @@ abstract class DatabaseBase implements DatabaseType {
* which index to pick. Anyway, other databases might have different
* indexes on a given table. So don't bother overriding this unless you're
* MySQL.
+ * @param $index
+ * @return string
*/
function useIndexClause( $index ) {
return '';
@@ -2223,14 +2328,11 @@ abstract class DatabaseBase implements DatabaseType {
* 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,
+ * @param $table String: The table to replace the row(s) in.
+ * @param $rows array 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
+ * @param $uniqueIndexes array 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
- * @param $rows Array: Array of rows to replace
* @param $fname String: Calling function name (use __METHOD__) for logs/profiling
*/
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) {
@@ -2284,9 +2386,11 @@ abstract class DatabaseBase implements DatabaseType {
* 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
+ * @param $table string Table name
+ * @param $rows array Rows to insert
+ * @param $fname string Caller function name
+ *
+ * @return ResultWrapper
*/
protected function nativeReplace( $table, $rows, $fname ) {
$table = $this->tableName( $table );
@@ -2353,6 +2457,11 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Returns the size of a text field, or -1 for "unlimited"
+ *
+ * @param $table string
+ * @param $field string
+ *
+ * @return int
*/
function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
@@ -2412,24 +2521,24 @@ abstract class DatabaseBase implements DatabaseType {
* 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
+ * @param $destTable string The table name to insert into
+ * @param $srcTable string|array 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
+ * @param $varMap array 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
+ * @param $conds array 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 $fname string The function name of the caller, from __METHOD__
*
- * @param $insertOptions Options for the INSERT part of the query, see
+ * @param $insertOptions array Options for the INSERT part of the query, see
* DatabaseBase::insert() for details.
- * @param $selectOptions Options for the SELECT part of the query, see
+ * @param $selectOptions array Options for the SELECT part of the query, see
* DatabaseBase::select() for details.
*
* @return ResultWrapper
@@ -2461,7 +2570,10 @@ abstract class DatabaseBase implements DatabaseType {
" FROM $srcTable $useIndex ";
if ( $conds != '*' ) {
- $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
+ if ( is_array( $conds ) ) {
+ $conds = $this->makeList( $conds, LIST_AND );
+ }
+ $sql .= " WHERE $conds";
}
$sql .= " $tailOpts";
@@ -2483,9 +2595,9 @@ abstract class DatabaseBase implements DatabaseType {
* The version provided by default works in MySQL and SQLite. It will very
* likely need to be overridden for most other DBMSes.
*
- * @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)
+ * @param $sql String SQL query we will append the limit too
+ * @param $limit Integer the SQL limit
+ * @param $offset Integer|false the SQL offset (default false)
*
* @return string
*/
@@ -2499,6 +2611,11 @@ abstract class DatabaseBase implements DatabaseType {
. "{$limit} ";
}
+ /**
+ * @param $sql
+ * @param $num
+ * @return string
+ */
function limitResultForUpdate( $sql, $num ) {
return $this->limitResult( $sql, $num, 0 );
}
@@ -2553,6 +2670,16 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Determines how long the server has been up
+ * STUB
+ *
+ * @return int
+ */
+ function getServerUptime() {
+ return 0;
+ }
+
+ /**
* Determines if the last failure was due to a deadlock
* STUB
*
@@ -2563,6 +2690,16 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Determines if the last failure was due to a lock timeout
+ * STUB
+ *
+ * @return bool
+ */
+ function wasLockTimeout() {
+ return false;
+ }
+
+ /**
* Determines if the last query error was something that should be dealt
* with by pinging the connection and reissuing the query.
* STUB
@@ -2598,11 +2735,12 @@ abstract class DatabaseBase implements DatabaseType {
* Returns whatever the callback function returned on its successful,
* iteration, or false on error, for example if the retry limit was
* reached.
+ *
+ * @return bool
*/
function deadlockLoop() {
- $myFname = 'DatabaseBase::deadlockLoop';
- $this->begin();
+ $this->begin( __METHOD__ );
$args = func_get_args();
$function = array_shift( $args );
$oldIgnore = $this->ignoreErrors( true );
@@ -2633,11 +2771,11 @@ abstract class DatabaseBase implements DatabaseType {
$this->ignoreErrors( $oldIgnore );
if ( $tries <= 0 ) {
- $this->rollback( $myFname );
+ $this->rollback( __METHOD__ );
$this->reportQueryError( $error, $errno, $sql, $fname );
return false;
} else {
- $this->commit( $myFname );
+ $this->commit( __METHOD__ );
return $retVal;
}
}
@@ -2654,29 +2792,28 @@ abstract class DatabaseBase implements DatabaseType {
* zero if we timed out.
*/
function masterPosWait( DBMasterPos $pos, $timeout ) {
- $fname = 'DatabaseBase::masterPosWait';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
if ( !is_null( $this->mFakeSlaveLag ) ) {
$wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 );
if ( $wait > $timeout * 1e6 ) {
wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return -1;
} elseif ( $wait > 0 ) {
wfDebug( "Fake slave waiting $wait us\n" );
usleep( $wait );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return 1;
} else {
wfDebug( "Fake slave up to date ($wait us)\n" );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return 0;
}
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
# Real waits are implemented in the subclass.
return 0;
@@ -2713,6 +2850,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Begin a transaction, committing any previously open transaction
+ *
+ * @param $fname string
*/
function begin( $fname = 'DatabaseBase::begin' ) {
$this->query( 'BEGIN', $fname );
@@ -2721,6 +2860,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* End a transaction
+ *
+ * @param $fname string
*/
function commit( $fname = 'DatabaseBase::commit' ) {
if ( $this->mTrxLevel ) {
@@ -2732,6 +2873,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Rollback a transaction.
* No-op on non-transactional databases.
+ *
+ * @param $fname string
*/
function rollback( $fname = 'DatabaseBase::rollback' ) {
if ( $this->mTrxLevel ) {
@@ -2778,6 +2921,8 @@ abstract class DatabaseBase implements DatabaseType {
* The result is unquoted, and needs to be passed through addQuotes()
* before it can be included in raw SQL.
*
+ * @param $ts string|int
+ *
* @return string
*/
function timestamp( $ts = 0 ) {
@@ -2793,6 +2938,8 @@ abstract class DatabaseBase implements DatabaseType {
* The result is unquoted, and needs to be passed through addQuotes()
* before it can be included in raw SQL.
*
+ * @param $ts string|int
+ *
* @return string
*/
function timestampOrNull( $ts = null ) {
@@ -2813,6 +2960,10 @@ abstract class DatabaseBase implements DatabaseType {
* 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.
+ *
+ * @param $result bool|ResultWrapper
+ *
+ * @return bool|ResultWrapper
*/
function resultObject( $result ) {
if ( empty( $result ) ) {
@@ -2829,6 +2980,11 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Return aggregated value alias
+ *
+ * @param $valuedata
+ * @param $valuename string
+ *
+ * @return string
*/
function aggregateValue ( $valuedata, $valuename = 'value' ) {
return $valuename;
@@ -2860,7 +3016,7 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Return the maximum number of items allowed in a list, or 0 for unlimited.
*
- * return int
+ * @return int
*/
function maxListLen() {
return 0;
@@ -2871,6 +3027,8 @@ abstract class DatabaseBase implements DatabaseType {
* don't allow simple quoted strings to be inserted. To insert into such
* a field, pass the data through this function before passing it to
* DatabaseBase::insert().
+ * @param $b string
+ * @return string
*/
function encodeBlob( $b ) {
return $b;
@@ -2880,20 +3038,36 @@ abstract class DatabaseBase implements DatabaseType {
* Some DBMSs return a special placeholder object representing blob fields
* in result objects. Pass the object through this function to return the
* original string.
+ * @param $b string
+ * @return string
*/
function decodeBlob( $b ) {
return $b;
}
/**
- * Override database's default connection timeout. May be useful for very
- * long batch queries such as full-wiki dumps, where a single query reads
- * out over hours or days. May or may not be necessary for non-MySQL
- * databases. For most purposes, leaving it as a no-op should be fine.
+ * Override database's default connection timeout
*
* @param $timeout Integer in seconds
+ * @return void
+ * @deprecated since 1.19; use setSessionOptions()
+ */
+ public function setTimeout( $timeout ) {
+ wfDeprecated( __METHOD__, '1.19' );
+ $this->setSessionOptions( array( 'connTimeout' => $timeout ) );
+ }
+
+ /**
+ * Override database's default behavior. $options include:
+ * 'connTimeout' : Set the connection timeout value in seconds.
+ * May be useful for very long batch queries such as
+ * full-wiki dumps, where a single query reads out over
+ * hours or days.
+ *
+ * @param $options Array
+ * @return void
*/
- public function setTimeout( $timeout ) {}
+ public function setSessionOptions( array $options ) {}
/**
* Read and execute SQL commands from a file.
@@ -2906,6 +3080,7 @@ abstract class DatabaseBase implements DatabaseType {
* @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
+ * @return bool|string
*/
function sourceFile( $filename, $lineCallback = false, $resultCallback = false, $fname = false ) {
wfSuppressWarnings();
@@ -2973,56 +3148,42 @@ abstract class DatabaseBase implements DatabaseType {
* @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
+ * @param $inputCallback Callback: Optional function called for each complete line (ended with ;) sent
+ * @return bool|string
*/
- function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
- $fname = 'DatabaseBase::sourceStream' )
+ public function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
+ $fname = 'DatabaseBase::sourceStream', $inputCallback = false )
{
- $cmd = "";
- $done = false;
- $dollarquote = false;
+ $cmd = '';
- while ( ! feof( $fp ) ) {
+ while ( !feof( $fp ) ) {
if ( $lineCallback ) {
call_user_func( $lineCallback );
}
$line = trim( fgets( $fp ) );
- $sl = strlen( $line ) - 1;
- if ( $sl < 0 ) {
+ if ( $line == '' ) {
continue;
}
- if ( '-' == $line { 0 } && '-' == $line { 1 } ) {
+ if ( '-' == $line[0] && '-' == $line[1] ) {
continue;
}
- # # Allow dollar quoting for function declarations
- if ( substr( $line, 0, 4 ) == '$mw$' ) {
- if ( $dollarquote ) {
- $dollarquote = false;
- $done = true;
- }
- else {
- $dollarquote = true;
- }
- }
- elseif ( !$dollarquote ) {
- if ( ';' == $line { $sl } && ( $sl < 2 || ';' != $line { $sl - 1 } ) ) {
- $done = true;
- $line = substr( $line, 0, $sl );
- }
- }
-
if ( $cmd != '' ) {
$cmd .= ' ';
}
+ $done = $this->streamStatementEnd( $cmd, $line );
+
$cmd .= "$line\n";
- if ( $done ) {
- $cmd = str_replace( ';;', ";", $cmd );
+ if ( $done || feof( $fp ) ) {
$cmd = $this->replaceVars( $cmd );
+ if ( $inputCallback ) {
+ call_user_func( $inputCallback, $cmd );
+ }
$res = $this->query( $cmd, $fname );
if ( $resultCallback ) {
@@ -3035,7 +3196,6 @@ abstract class DatabaseBase implements DatabaseType {
}
$cmd = '';
- $done = false;
}
}
@@ -3043,6 +3203,24 @@ abstract class DatabaseBase implements DatabaseType {
}
/**
+ * Called by sourceStream() to check if we've reached a statement end
+ *
+ * @param $sql String SQL assembled so far
+ * @param $newLine String New line about to be added to $sql
+ * @return Bool Whether $newLine contains end of the statement
+ */
+ public function streamStatementEnd( &$sql, &$newLine ) {
+ if ( $this->delimiter ) {
+ $prev = $newLine;
+ $newLine = preg_replace( '/' . preg_quote( $this->delimiter, '/' ) . '$/', '', $newLine );
+ if ( $newLine != $prev ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Database independent variable replacement. Replaces a set of variables
* in an SQL statement with their contents as given by $this->getSchemaVars().
*
@@ -3096,6 +3274,8 @@ abstract class DatabaseBase implements DatabaseType {
/**
* Get schema variables. If none have been set via setSchemaVars(), then
* use some defaults from the current object.
+ *
+ * @return array
*/
protected function getSchemaVars() {
if ( $this->mSchemaVars ) {
@@ -3207,9 +3387,10 @@ abstract class DatabaseBase implements DatabaseType {
* @param $tableName string
* @param $fName string
* @return bool|ResultWrapper
+ * @since 1.18
*/
public function dropTable( $tableName, $fName = 'DatabaseBase::dropTable' ) {
- if( !$this->tableExists( $tableName ) ) {
+ if( !$this->tableExists( $tableName, $fName ) ) {
return false;
}
$sql = "DROP TABLE " . $this->tableName( $tableName );
diff --git a/includes/db/DatabaseError.php b/includes/db/DatabaseError.php
index b7fb1b22..836d7814 100644
--- a/includes/db/DatabaseError.php
+++ b/includes/db/DatabaseError.php
@@ -82,11 +82,19 @@ class DBConnectionError extends DBError {
parent::__construct( $db, $msg );
}
+ /**
+ * @return bool
+ */
function useOutputPage() {
// Not likely to work
return false;
}
+ /**
+ * @param $key
+ * @param $fallback
+ * @return string
+ */
function msg( $key, $fallback /*[, params...] */ ) {
global $wgLang;
@@ -100,6 +108,9 @@ class DBConnectionError extends DBError {
return wfMsgReplaceArgs( $message, $args );
}
+ /**
+ * @return bool
+ */
function getLogMessage() {
# Don't send to the exception log
return false;
@@ -171,7 +182,7 @@ class DBConnectionError extends DBError {
}
# We can't, cough and die in the usual fashion
- return parent::reportHTML();
+ parent::reportHTML();
}
/**
@@ -193,7 +204,7 @@ class DBConnectionError extends DBError {
<div style="margin: 1.5em">$usegoogle<br />
<small>$outofdate</small></div>
<!-- SiteSearch Google -->
-<form method="get" action="http://www.google.com/search" id="googlesearch">
+<form method="get" action="//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" />
@@ -215,21 +226,28 @@ EOT;
* @return string
*/
private function fileCachedPage() {
- global $wgTitle, $wgOut;
+ global $wgTitle, $wgOut, $wgRequest;
if ( $wgOut->isDisabled() ) {
- return; // Done already?
+ return ''; // Done already?
}
- if ( $wgTitle ) {
- $t =& $wgTitle;
+ if ( $wgTitle ) { // use $wgTitle if we managed to set it
+ $t = $wgTitle->getPrefixedDBkey();
} else {
- $t = Title::newFromText( $this->msg( 'mainpage', 'Main Page' ) );
+ # Fallback to the raw title URL param. We can't use the Title
+ # class is it may hit the interwiki table and give a DB error.
+ # We may get a cache miss due to not sanitizing the title though.
+ $t = str_replace( ' ', '_', $wgRequest->getVal( 'title' ) );
+ if ( $t == '' ) { // fallback to main page
+ $t = Title::newFromText(
+ $this->msg( 'mainpage', 'Main Page' ) )->getPrefixedDBkey();
+ }
}
- $cache = new HTMLFileCache( $t );
- if ( $cache->isFileCached() ) {
- return $cache->fetchPageText();
+ $cache = HTMLFileCache::newFromTitle( $t, 'view' );
+ if ( $cache->isCached() ) {
+ return $cache->fetchText();
} else {
return '';
}
@@ -242,15 +260,18 @@ EOT;
class DBQueryError extends DBError {
public $error, $errno, $sql, $fname;
+ /**
+ * @param $db DatabaseBase
+ * @param $error string
+ * @param $errno int|string
+ * @param $sql string
+ * @param $fname string
+ */
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" .
+ $message = "A database error has occurred. Did you forget to run maintenance/update.php after upgrading? See: https://www.mediawiki.org/wiki/Manual:Upgrading#Run_the_update_script\n" .
"Query: $sql\n" .
"Function: $fname\n" .
"Error: $errno $error\n";
- global $wgShowDBErrorBacktrace;
- if( $wgShowDBErrorBacktrace ) {
- $message .= $this->getTraceAsString();
- }
parent::__construct( $db, $message );
$this->error = $error;
@@ -295,6 +316,9 @@ class DBQueryError extends DBError {
}
}
+ /**
+ * @return bool
+ */
function getLogMessage() {
# Don't send to the exception log
return false;
diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php
index 147b9d59..fed3b12e 100644
--- a/includes/db/DatabaseIbm_db2.php
+++ b/includes/db/DatabaseIbm_db2.php
@@ -104,6 +104,147 @@ class IBM_DB2Blob {
}
/**
+ * Wrapper to address lack of certain operations in the DB2 driver
+ * ( seek, num_rows )
+ * @ingroup Database
+ * @since 1.19
+ */
+class IBM_DB2Result{
+ private $db;
+ private $result;
+ private $num_rows;
+ private $current_pos;
+ private $columns = array();
+ private $sql;
+
+ private $resultSet = array();
+ private $loadedLines = 0;
+
+ /**
+ * Construct and initialize a wrapper for DB2 query results
+ * @param $db Database
+ * @param $result Object
+ * @param $num_rows Integer
+ * @param $sql String
+ * @param $columns Array
+ */
+ public function __construct( $db, $result, $num_rows, $sql, $columns ){
+ $this->db = $db;
+
+ if( $result instanceof ResultWrapper ){
+ $this->result = $result->result;
+ }
+ else{
+ $this->result = $result;
+ }
+
+ $this->num_rows = $num_rows;
+ $this->current_pos = 0;
+ if ( $this->num_rows > 0 ) {
+ // Make a lower-case list of the column names
+ // By default, DB2 column names are capitalized
+ // while MySQL column names are lowercase
+
+ // Is there a reasonable maximum value for $i?
+ // Setting to 2048 to prevent an infinite loop
+ for( $i = 0; $i < 2048; $i++ ) {
+ $name = db2_field_name( $this->result, $i );
+ if ( $name != false ) {
+ continue;
+ }
+ else {
+ return false;
+ }
+
+ $this->columns[$i] = strtolower( $name );
+ }
+ }
+
+ $this->sql = $sql;
+ }
+
+ /**
+ * Unwrap the DB2 query results
+ * @return mixed Object on success, false on failure
+ */
+ public function getResult() {
+ if ( $this->result ) {
+ return $this->result;
+ }
+ else return false;
+ }
+
+ /**
+ * Get the number of rows in the result set
+ * @return integer
+ */
+ public function getNum_rows() {
+ return $this->num_rows;
+ }
+
+ /**
+ * Return a row from the result set in object format
+ * @return mixed Object on success, false on failure.
+ */
+ public function fetchObject() {
+ if ( $this->result
+ && $this->num_rows > 0
+ && $this->current_pos >= 0
+ && $this->current_pos < $this->num_rows )
+ {
+ $row = $this->fetchRow();
+ $ret = new stdClass();
+
+ foreach ( $row as $k => $v ) {
+ $lc = $this->columns[$k];
+ $ret->$lc = $v;
+ }
+ return $ret;
+ }
+ return false;
+ }
+
+ /**
+ * Return a row form the result set in array format
+ * @return mixed Array on success, false on failure
+ * @throws DBUnexpectedError
+ */
+ public function fetchRow(){
+ if ( $this->result
+ && $this->num_rows > 0
+ && $this->current_pos >= 0
+ && $this->current_pos < $this->num_rows )
+ {
+ if ( $this->loadedLines <= $this->current_pos ) {
+ $row = db2_fetch_array( $this->result );
+ $this->resultSet[$this->loadedLines++] = $row;
+ if ( $this->db->lastErrno() ) {
+ throw new DBUnexpectedError( $this->db, 'Error in fetchRow(): '
+ . htmlspecialchars( $this->db->lastError() ) );
+ }
+ }
+
+ if ( $this->loadedLines > $this->current_pos ){
+ return $this->resultSet[$this->current_pos++];
+ }
+
+ }
+ return false;
+ }
+
+ /**
+ * Free a DB2 result object
+ * @throws DBUnexpectedError
+ */
+ public function freeResult(){
+ unset( $this->resultSet );
+ if ( !@db2_free_result( $this->result ) ) {
+ throw new DBUnexpectedError( $this, "Unable to free DB2 result\n" );
+ }
+ }
+}
+
+/**
* Primary database interface
* @ingroup Database
*/
@@ -137,6 +278,8 @@ class DatabaseIbm_db2 extends DatabaseBase {
protected $mAffectedRows = null;
/** Number of rows returned by last SELECT */
protected $mNumRows = null;
+ /** Current row number on the cursor of the last SELECT */
+ protected $currentRow = 0;
/** Connection config options - see constructor */
public $mConnOptions = array();
@@ -233,7 +376,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
/**
* Returns a unique string representing the wiki on the server
*/
- function getWikiID() {
+ public function getWikiID() {
if( $this->mSchema ) {
return "{$this->mDBname}-{$this->mSchema}";
} else {
@@ -241,10 +384,22 @@ class DatabaseIbm_db2 extends DatabaseBase {
}
}
- function getType() {
+ /**
+ * Returns the database software identifieir
+ * @return string
+ */
+ public function getType() {
return 'ibm_db2';
}
+ /**
+ * Returns the database connection object
+ * @return Object
+ */
+ public function getDb(){
+ return $this->mConn;
+ }
+
/**
*
* @param $server String: hostname of database server
@@ -268,17 +423,12 @@ 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',
self::STMT_OPTION );
$this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON',
self::STMT_OPTION );
-
parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags );
}
@@ -361,8 +511,6 @@ class DatabaseIbm_db2 extends DatabaseBase {
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.
@@ -391,7 +539,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
{
$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());
+ $this->mConn = db2_pconnect( $dsn, "", "", array() );
wfRestoreWarnings();
}
@@ -460,16 +608,16 @@ class DatabaseIbm_db2 extends DatabaseBase {
*/
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);
-
+ $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: '
@@ -492,10 +640,10 @@ class DatabaseIbm_db2 extends DatabaseBase {
* Queries whether a given table exists
* @return boolean
*/
- public function tableExists( $table ) {
+ public function tableExists( $table, $fname = __METHOD__ ) {
$schema = $this->mSchema;
-
- $sql = "SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST WHERE ST.NAME = '" .
+
+ $sql = "SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST WHERE ST.NAME = '" .
strtoupper( $table ) .
"' AND ST.CREATOR = '" .
strtoupper( $schema ) . "'";
@@ -745,10 +893,10 @@ class DatabaseIbm_db2 extends DatabaseBase {
* Handle reserved keyword replacement in table names
*
* @param $name Object
- * @param $name Boolean
+ * @param $format String Ignored parameter Default 'quoted'Boolean
* @return String
*/
- public function tableName( $name, $quoted = true ) {
+ public function tableName( $name, $format = 'quoted' ) {
// we want maximum compatibility with MySQL schema
return $name;
}
@@ -939,14 +1087,14 @@ class DatabaseIbm_db2 extends DatabaseBase {
*/
private function removeNullPrimaryKeys( $table, $args ) {
$schema = $this->mSchema;
-
+
// find out the primary keys
- $keyres = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '"
- . strtoupper( $table )
- . "' AND TBCREATOR = '"
- . strtoupper( $schema )
+ $keyres = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '"
+ . strtoupper( $table )
+ . "' AND TBCREATOR = '"
+ . strtoupper( $schema )
. "' AND KEYSEQ > 0" );
-
+
$keys = array();
for (
$row = $this->fetchRow( $keyres );
@@ -1046,7 +1194,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
-
+
if ( $this->mNumRows ) {
return $this->mNumRows;
} else {
@@ -1062,9 +1210,13 @@ class DatabaseIbm_db2 extends DatabaseBase {
*/
public function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
+ return $res = $res->result;
+ }
+ if ( $res instanceof IBM_DB2Result ) {
+ return $res->dataSeek( $row );
}
- return db2_fetch_row( $res, $row );
+ wfDebug( "dataSeek operation in DB2 database\n" );
+ return false;
}
###
@@ -1097,6 +1249,9 @@ class DatabaseIbm_db2 extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+ if ( $res instanceof IBM_DB2Result ) {
+ $res = $res->getResult();
+ }
return db2_num_fields( $res );
}
@@ -1110,6 +1265,9 @@ class DatabaseIbm_db2 extends DatabaseBase {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+ if ( $res instanceof IBM_DB2Result ) {
+ $res = $res->getResult();
+ }
return db2_field_name( $res, $n );
}
@@ -1122,7 +1280,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
* @param $fname String: calling function name (use __METHOD__)
* for logs/profiling
* @param $options Associative array of options
- * (e.g. array('GROUP BY' => 'page_title')),
+ * (e.g. array( 'GROUP BY' => 'page_title' )),
* see Database::makeSelectOptions code for list of
* supported stuff
* @param $join_conds Associative array of table join conditions (optional)
@@ -1135,6 +1293,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
{
$res = parent::select( $table, $vars, $conds, $fname, $options,
$join_conds );
+ $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
// We must adjust for offset
if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) {
@@ -1161,10 +1320,11 @@ class DatabaseIbm_db2 extends DatabaseBase {
$res2 = parent::select( $table, $vars2, $conds, $fname, $options2,
$join_conds );
+
$obj = $this->fetchObject( $res2 );
$this->mNumRows = $obj->num_rows;
-
- return $res;
+
+ return new ResultWrapper( $this, new IBM_DB2Result( $this, $res, $obj->num_rows, $vars, $sql ) );
}
/**
@@ -1332,6 +1492,9 @@ SQL;
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
+ if ( $res instanceof IBM_DB2Result ) {
+ $res = $res->getResult();
+ }
return db2_field_type( $res, $index );
}
diff --git a/includes/db/DatabaseMssql.php b/includes/db/DatabaseMssql.php
index cbdf89ca..38be4cbb 100644
--- a/includes/db/DatabaseMssql.php
+++ b/includes/db/DatabaseMssql.php
@@ -631,7 +631,7 @@ class DatabaseMssql extends DatabaseBase {
return $version;
}
- function tableExists ( $table, $schema = false ) {
+ function tableExists ( $table, $fname = __METHOD__, $schema = false ) {
$res = sqlsrv_query( $this->mConn, "SELECT * FROM information_schema.tables
WHERE table_type='BASE TABLE' AND table_name = '$table'" );
if ( $res === false ) {
diff --git a/includes/db/DatabaseMysql.php b/includes/db/DatabaseMysql.php
index 6a81f234..c179b724 100644
--- a/includes/db/DatabaseMysql.php
+++ b/includes/db/DatabaseMysql.php
@@ -14,10 +14,18 @@
* @see Database
*/
class DatabaseMysql extends DatabaseBase {
+
+ /**
+ * @return string
+ */
function getType() {
return 'mysql';
}
+ /**
+ * @param $sql string
+ * @return resource
+ */
protected function doQuery( $sql ) {
if( $this->bufferResults() ) {
$ret = mysql_query( $sql, $this->mConn );
@@ -27,6 +35,14 @@ class DatabaseMysql extends DatabaseBase {
return $ret;
}
+ /**
+ * @param $server string
+ * @param $user string
+ * @param $password string
+ * @param $dbName string
+ * @return bool
+ * @throws DBConnectionError
+ */
function open( $server, $user, $password, $dbName ) {
global $wgAllDBsAreLocalhost;
wfProfileIn( __METHOD__ );
@@ -110,22 +126,19 @@ class DatabaseMysql extends DatabaseBase {
}
if ( $success ) {
- $version = $this->getServerVersion();
- if ( version_compare( $version, '4.1' ) >= 0 ) {
- // Tell the server we're communicating with it in UTF-8.
- // This may engage various charset conversions.
- global $wgDBmysql5;
- if( $wgDBmysql5 ) {
- $this->query( 'SET NAMES utf8', __METHOD__ );
- } else {
- $this->query( 'SET NAMES binary', __METHOD__ );
- }
- // Set SQL mode, default is turning them all off, can be overridden or skipped with null
- global $wgSQLMode;
- if ( is_string( $wgSQLMode ) ) {
- $mode = $this->addQuotes( $wgSQLMode );
- $this->query( "SET sql_mode = $mode", __METHOD__ );
- }
+ // Tell the server we're communicating with it in UTF-8.
+ // This may engage various charset conversions.
+ global $wgDBmysql5;
+ if( $wgDBmysql5 ) {
+ $this->query( 'SET NAMES utf8', __METHOD__ );
+ } else {
+ $this->query( 'SET NAMES binary', __METHOD__ );
+ }
+ // Set SQL mode, default is turning them all off, can be overridden or skipped with null
+ global $wgSQLMode;
+ if ( is_string( $wgSQLMode ) ) {
+ $mode = $this->addQuotes( $wgSQLMode );
+ $this->query( "SET sql_mode = $mode", __METHOD__ );
}
// Turn off strict mode if it is on
@@ -138,6 +151,9 @@ class DatabaseMysql extends DatabaseBase {
return $success;
}
+ /**
+ * @return bool
+ */
function close() {
$this->mOpened = false;
if ( $this->mConn ) {
@@ -150,6 +166,10 @@ class DatabaseMysql extends DatabaseBase {
}
}
+ /**
+ * @param $res ResultWrapper
+ * @throws DBUnexpectedError
+ */
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -162,6 +182,11 @@ class DatabaseMysql extends DatabaseBase {
}
}
+ /**
+ * @param $res ResultWrapper
+ * @return object|stdClass
+ * @throws DBUnexpectedError
+ */
function fetchObject( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -175,7 +200,12 @@ class DatabaseMysql extends DatabaseBase {
return $row;
}
- function fetchRow( $res ) {
+ /**
+ * @param $res ResultWrapper
+ * @return array
+ * @throws DBUnexpectedError
+ */
+ function fetchRow( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
}
@@ -188,6 +218,11 @@ class DatabaseMysql extends DatabaseBase {
return $row;
}
+ /**
+ * @throws DBUnexpectedError
+ * @param $res ResultWrapper
+ * @return int
+ */
function numRows( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -201,6 +236,10 @@ class DatabaseMysql extends DatabaseBase {
return $n;
}
+ /**
+ * @param $res ResultWrapper
+ * @return int
+ */
function numFields( $res ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -208,6 +247,11 @@ class DatabaseMysql extends DatabaseBase {
return mysql_num_fields( $res );
}
+ /**
+ * @param $res ResultWrapper
+ * @param $n string
+ * @return string
+ */
function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -215,8 +259,18 @@ class DatabaseMysql extends DatabaseBase {
return mysql_field_name( $res, $n );
}
- function insertId() { return mysql_insert_id( $this->mConn ); }
+ /**
+ * @return int
+ */
+ function insertId() {
+ return mysql_insert_id( $this->mConn );
+ }
+ /**
+ * @param $res ResultWrapper
+ * @param $row
+ * @return bool
+ */
function dataSeek( $res, $row ) {
if ( $res instanceof ResultWrapper ) {
$res = $res->result;
@@ -224,6 +278,9 @@ class DatabaseMysql extends DatabaseBase {
return mysql_data_seek( $res, $row );
}
+ /**
+ * @return int
+ */
function lastErrno() {
if ( $this->mConn ) {
return mysql_errno( $this->mConn );
@@ -232,6 +289,9 @@ class DatabaseMysql extends DatabaseBase {
}
}
+ /**
+ * @return string
+ */
function lastError() {
if ( $this->mConn ) {
# Even if it's non-zero, it can still be invalid
@@ -250,8 +310,20 @@ class DatabaseMysql extends DatabaseBase {
return $error;
}
- function affectedRows() { return mysql_affected_rows( $this->mConn ); }
+ /**
+ * @return int
+ */
+ function affectedRows() {
+ return mysql_affected_rows( $this->mConn );
+ }
+ /**
+ * @param $table string
+ * @param $uniqueIndexes
+ * @param $rows array
+ * @param $fname string
+ * @return ResultWrapper
+ */
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMysql::replace' ) {
return $this->nativeReplace( $table, $rows, $fname );
}
@@ -260,6 +332,13 @@ class DatabaseMysql extends DatabaseBase {
* Estimate rows in dataset
* Returns estimated count, based on EXPLAIN output
* Takes same arguments as Database::select()
+ *
+ * @param $table string|array
+ * @param $vars string|array
+ * @param $conds string|array
+ * @param $fname string
+ * @param $options string|array
+ * @return int
*/
public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) {
$options['EXPLAIN'] = true;
@@ -278,6 +357,11 @@ class DatabaseMysql extends DatabaseBase {
return $rows;
}
+ /**
+ * @param $table string
+ * @param $field string
+ * @return bool|MySQLField
+ */
function fieldInfo( $table, $field ) {
$table = $this->tableName( $table );
$res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
@@ -297,6 +381,11 @@ class DatabaseMysql extends DatabaseBase {
/**
* Get information about an index into an object
* Returns false if the index does not exist
+ *
+ * @param $table string
+ * @param $index string
+ * @param $fname string
+ * @return false|array
*/
function indexInfo( $table, $index, $fname = 'DatabaseMysql::indexInfo' ) {
# SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
@@ -322,11 +411,20 @@ class DatabaseMysql extends DatabaseBase {
return empty( $result ) ? false : $result;
}
+ /**
+ * @param $db
+ * @return bool
+ */
function selectDB( $db ) {
$this->mDBname = $db;
return mysql_select_db( $db, $this->mConn );
}
+ /**
+ * @param $s string
+ *
+ * @return string
+ */
function strencode( $s ) {
$sQuoted = mysql_real_escape_string( $s, $this->mConn );
@@ -339,15 +437,26 @@ class DatabaseMysql extends DatabaseBase {
/**
* MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
+ *
+ * @param $s string
+ *
+ * @return string
*/
public function addIdentifierQuotes( $s ) {
return "`" . $this->strencode( $s ) . "`";
}
+ /**
+ * @param $name string
+ * @return bool
+ */
public function isQuotedIdentifier( $name ) {
- return strlen($name) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
+ return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
}
+ /**
+ * @return bool
+ */
function ping() {
$ping = mysql_ping( $this->mConn );
if ( $ping ) {
@@ -364,11 +473,9 @@ class DatabaseMysql extends DatabaseBase {
/**
* Returns slave lag.
*
- * 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.
+ * This will do a SHOW SLAVE STATUS
*
- * @result int
+ * @return int
*/
function getLag() {
if ( !is_null( $this->mFakeSlaveLag ) ) {
@@ -376,13 +483,12 @@ class DatabaseMysql extends DatabaseBase {
return $this->mFakeSlaveLag;
}
- if ( version_compare( $this->getServerVersion(), '4.1.9', '>=' ) ) {
- return $this->getLagFromSlaveStatus();
- } else {
- return $this->getLagFromProcesslist();
- }
+ return $this->getLagFromSlaveStatus();
}
+ /**
+ * @return bool|int
+ */
function getLagFromSlaveStatus() {
$res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
if ( !$res ) {
@@ -399,7 +505,13 @@ class DatabaseMysql extends DatabaseBase {
}
}
+ /**
+ * @deprecated in 1.19, use getLagFromSlaveStatus
+ *
+ * @return bool|int
+ */
function getLagFromProcesslist() {
+ wfDeprecated( __METHOD__, '1.19' );
$res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
if( !$res ) {
return false;
@@ -432,12 +544,13 @@ 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
+ * @return bool|string
*/
function masterPosWait( DBMasterPos $pos, $timeout ) {
$fname = 'DatabaseBase::masterPosWait';
@@ -510,31 +623,68 @@ class DatabaseMysql extends DatabaseBase {
}
}
+ /**
+ * @return string
+ */
function getServerVersion() {
return mysql_get_server_info( $this->mConn );
}
+ /**
+ * @param $index
+ * @return string
+ */
function useIndexClause( $index ) {
return "FORCE INDEX (" . $this->indexName( $index ) . ")";
}
+ /**
+ * @return string
+ */
function lowPriorityOption() {
return 'LOW_PRIORITY';
}
+ /**
+ * @return string
+ */
public static function getSoftwareLink() {
return '[http://www.mysql.com/ MySQL]';
}
+ /**
+ * @return bool
+ */
function standardSelectDistinct() {
return false;
}
- public function setTimeout( $timeout ) {
- $this->query( "SET net_read_timeout=$timeout" );
- $this->query( "SET net_write_timeout=$timeout" );
+ /**
+ * @param $options array
+ */
+ public function setSessionOptions( array $options ) {
+ if ( isset( $options['connTimeout'] ) ) {
+ $timeout = (int)$options['connTimeout'];
+ $this->query( "SET net_read_timeout=$timeout" );
+ $this->query( "SET net_write_timeout=$timeout" );
+ }
}
+ public function streamStatementEnd( &$sql, &$newLine ) {
+ if ( strtoupper( substr( $newLine, 0, 9 ) ) == 'DELIMITER' ) {
+ preg_match( '/^DELIMITER\s+(\S+)/' , $newLine, $m );
+ $this->delimiter = $m[1];
+ $newLine = '';
+ }
+ return parent::streamStatementEnd( $sql, $newLine );
+ }
+
+ /**
+ * @param $lockName string
+ * @param $method string
+ * @param $timeout int
+ * @return bool
+ */
public function lock( $lockName, $method, $timeout = 5 ) {
$lockName = $this->addQuotes( $lockName );
$result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
@@ -550,6 +700,9 @@ class DatabaseMysql extends DatabaseBase {
/**
* FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
+ * @param $lockName string
+ * @param $method string
+ * @return bool
*/
public function unlock( $lockName, $method ) {
$lockName = $this->addQuotes( $lockName );
@@ -558,6 +711,12 @@ class DatabaseMysql extends DatabaseBase {
return $row->lockstatus;
}
+ /**
+ * @param $read array
+ * @param $write array
+ * @param $method string
+ * @param $lowPriority bool
+ */
public function lockTables( $read, $write, $method, $lowPriority = true ) {
$items = array();
@@ -574,6 +733,9 @@ class DatabaseMysql extends DatabaseBase {
$this->query( $sql, $method );
}
+ /**
+ * @param $method string
+ */
public function unlockTables( $method ) {
$this->query( "UNLOCK TABLES", $method );
}
@@ -588,6 +750,10 @@ class DatabaseMysql extends DatabaseBase {
return 'SearchMySQL';
}
+ /**
+ * @param bool $value
+ * @return mixed
+ */
public function setBigSelects( $value = true ) {
if ( $value === 'default' ) {
if ( $this->mDefaultBigSelects === null ) {
@@ -605,6 +771,13 @@ class DatabaseMysql extends DatabaseBase {
/**
* DELETE where the condition is a join. MySql uses multi-table deletes.
+ * @param $delTable string
+ * @param $joinTable string
+ * @param $delVar string
+ * @param $joinVar string
+ * @param $conds array|string
+ * @param $fname bool
+ * @return bool|ResultWrapper
*/
function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
if ( !$conds ) {
@@ -623,15 +796,38 @@ class DatabaseMysql extends DatabaseBase {
}
/**
+ * Determines how long the server has been up
+ *
+ * @return int
+ */
+ function getServerUptime() {
+ $vars = $this->getMysqlStatus( 'Uptime' );
+ return (int)$vars['Uptime'];
+ }
+
+ /**
* Determines if the last failure was due to a deadlock
+ *
+ * @return bool
*/
function wasDeadlock() {
return $this->lastErrno() == 1213;
}
/**
+ * Determines if the last failure was due to a lock timeout
+ *
+ * @return bool
+ */
+ function wasLockTimeout() {
+ return $this->lastErrno() == 1205;
+ }
+
+ /**
* Determines if the last query error was something that should be dealt
* with by pinging the connection and reissuing the query
+ *
+ * @return bool
*/
function wasErrorReissuable() {
return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
@@ -639,45 +835,34 @@ class DatabaseMysql extends DatabaseBase {
/**
* Determines if the last failure was due to the database being read-only.
+ *
+ * @return bool
*/
function wasReadOnlyError() {
return $this->lastErrno() == 1223 ||
( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
}
+ /**
+ * @param $oldName
+ * @param $newName
+ * @param $temporary bool
+ * @param $fname string
+ */
function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseMysql::duplicateTableStructure' ) {
$tmp = $temporary ? 'TEMPORARY ' : '';
- if ( strcmp( $this->getServerVersion(), '4.1' ) < 0 ) {
- # Hack for MySQL versions < 4.1, which don't support
- # "CREATE TABLE ... LIKE". Note that
- # "CREATE TEMPORARY TABLE ... SELECT * FROM ... LIMIT 0"
- # would not create the indexes we need....
- #
- # 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 ' . $this->addIdentifierQuotes( $oldName ) );
- $row = $this->fetchRow( $res );
- $oldQuery = $row[1];
- $query = preg_replace( '/CREATE TABLE `(.*?)`/',
- "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)";
- }
+ $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
+ * @return array
*/
function listTables( $prefix = null, $fname = 'DatabaseMysql::listTables' ) {
$result = $this->query( "SHOW TABLES", $fname);
@@ -692,12 +877,17 @@ class DatabaseMysql extends DatabaseBase {
$endArray[] = $table;
}
}
-
+
return $endArray;
}
+ /**
+ * @param $tableName
+ * @param $fName string
+ * @return bool|ResultWrapper
+ */
public function dropTable( $tableName, $fName = 'DatabaseMysql::dropTable' ) {
- if( !$this->tableExists( $tableName ) ) {
+ if( !$this->tableExists( $tableName, $fName ) ) {
return false;
}
return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
@@ -716,6 +906,7 @@ class DatabaseMysql extends DatabaseBase {
/**
* Get status information from SHOW STATUS in an associative array
*
+ * @param $which string
* @return array
*/
function getMysqlStatus( $which = "%" ) {
@@ -733,6 +924,8 @@ class DatabaseMysql extends DatabaseBase {
/**
* Legacy support: Database == DatabaseMysql
+ *
+ * @deprecated in 1.16
*/
class Database extends DatabaseMysql {}
@@ -757,18 +950,30 @@ class MySQLField implements Field {
$this->type = $info->type;
}
+ /**
+ * @return string
+ */
function name() {
return $this->name;
}
+ /**
+ * @return string
+ */
function tableName() {
return $this->tableName;
}
+ /**
+ * @return string
+ */
function type() {
return $this->type;
}
+ /**
+ * @return bool
+ */
function isNullable() {
return $this->nullable;
}
@@ -777,10 +982,16 @@ class MySQLField implements Field {
return $this->default;
}
+ /**
+ * @return bool
+ */
function isKey() {
return $this->is_key;
}
+ /**
+ * @return bool
+ */
function isMultipleKey() {
return $this->is_multiple;
}
diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php
index 9d51cf07..855fc831 100644
--- a/includes/db/DatabaseOracle.php
+++ b/includes/db/DatabaseOracle.php
@@ -548,8 +548,9 @@ class DatabaseOracle extends DatabaseBase {
$val = $val->fetch();
}
+ // backward compatibility
if ( preg_match( '/^timestamp.*/i', $col_type ) == 1 && strtolower( $val ) == 'infinity' ) {
- $val = '31-12-2030 12:00:00.000000';
+ $val = $this->getInfinity();
}
$val = ( $wgContLang != null ) ? $wgContLang->checkTitleEncoding( $val ) : $val;
@@ -654,7 +655,7 @@ class DatabaseOracle extends DatabaseBase {
return $retval;
}
- function tableName( $name, $quoted = true ) {
+ function tableName( $name, $format = 'quoted' ) {
/*
Replace reserved words with better ones
Using uppercase because that's the only way Oracle can handle
@@ -669,7 +670,7 @@ class DatabaseOracle extends DatabaseBase {
break;
}
- return parent::tableName( strtoupper( $name ), $quoted );
+ return parent::tableName( strtoupper( $name ), $format );
}
function tableNameInternal( $name ) {
@@ -768,9 +769,9 @@ class DatabaseOracle extends DatabaseBase {
// dirty code ... i know
$endArray = array();
- $endArray[] = $prefix.'MWUSER';
- $endArray[] = $prefix.'PAGE';
- $endArray[] = $prefix.'IMAGE';
+ $endArray[] = strtoupper($prefix.'MWUSER');
+ $endArray[] = strtoupper($prefix.'PAGE');
+ $endArray[] = strtoupper($prefix.'IMAGE');
$fixedOrderTabs = $endArray;
while (($row = $result->fetchRow()) !== false) {
if (!in_array($row['table_name'], $fixedOrderTabs))
@@ -855,7 +856,7 @@ class DatabaseOracle extends DatabaseBase {
/**
* Query whether a given table exists (in the given schema, or the default mw one if not given)
*/
- function tableExists( $table ) {
+ function tableExists( $table, $fname = __METHOD__ ) {
$table = $this->tableName( $table );
$table = $this->addQuotes( strtoupper( $this->removeIdentifierQuotes( $table ) ) );
$owner = $this->addQuotes( strtoupper( $this->mDBname ) );
@@ -969,7 +970,8 @@ class DatabaseOracle extends DatabaseBase {
}
/* defines must comply with ^define\s*([^\s=]*)\s*=\s?'\{\$([^\}]*)\}'; */
- function sourceStream( $fp, $lineCallback = false, $resultCallback = false, $fname = 'DatabaseOracle::sourceStream' ) {
+ function sourceStream( $fp, $lineCallback = false, $resultCallback = false,
+ $fname = 'DatabaseOracle::sourceStream', $inputCallback = false ) {
$cmd = '';
$done = false;
$dollarquote = false;
@@ -1023,6 +1025,9 @@ class DatabaseOracle extends DatabaseBase {
}
$cmd = $this->replaceVars( $cmd );
+ if ( $inputCallback ) {
+ call_user_func( $inputCallback, $cmd );
+ }
$res = $this->doQuery( $cmd );
if ( $resultCallback ) {
call_user_func( $resultCallback, $res, $this );
@@ -1210,7 +1215,7 @@ class DatabaseOracle extends DatabaseBase {
$sql .= $sqlSet;
}
- if ( $conds != '*' ) {
+ if ( $conds !== array() && $conds !== '*' ) {
$conds = $this->wrapConditionsForWhere( $table, $conds );
$sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
}
@@ -1299,7 +1304,8 @@ class DatabaseOracle extends DatabaseBase {
return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')';
}
- function setFakeMaster( $enabled = true ) { }
+ function setFakeMaster( $enabled = true ) {
+ }
function getDBname() {
return $this->mDBname;
@@ -1312,4 +1318,9 @@ class DatabaseOracle extends DatabaseBase {
public function getSearchEngine() {
return 'SearchOracle';
}
+
+ public function getInfinity() {
+ return '31-12-2030 12:00:00.000000';
+ }
+
} // end DatabaseOracle class
diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php
index 742a8b51..98cf3c75 100644
--- a/includes/db/DatabasePostgres.php
+++ b/includes/db/DatabasePostgres.php
@@ -39,7 +39,7 @@ AND relname=%s
AND attname=%s;
SQL;
- $table = $db->tableName( $table, false );
+ $table = $db->tableName( $table, 'raw' );
$res = $db->query(
sprintf( $q,
$db->addQuotes( $wgDBmwschema ),
@@ -200,6 +200,7 @@ class DatabasePostgres extends DatabaseBase {
$this->query( "SET client_encoding='UTF8'", __METHOD__ );
$this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
$this->query( "SET timezone = 'GMT'", __METHOD__ );
+ $this->query( "SET standard_conforming_strings = on", __METHOD__ );
global $wgDBmwschema;
if ( $this->schemaExists( $wgDBmwschema ) ) {
@@ -564,7 +565,7 @@ class DatabasePostgres extends DatabaseBase {
$ignore = in_array( 'IGNORE', $insertOptions ) ? 'mw' : '';
if( is_array( $insertOptions ) ) {
- $insertOptions = implode( ' ', $insertOptions );
+ $insertOptions = implode( ' ', $insertOptions ); // FIXME: This is unused
}
if( !is_array( $selectOptions ) ) {
$selectOptions = array( $selectOptions );
@@ -622,18 +623,23 @@ class DatabasePostgres extends DatabaseBase {
return $res;
}
- function tableName( $name, $quoted = true ) {
+ function tableName( $name, $format = 'quoted' ) {
# Replace reserved words with better ones
switch( $name ) {
case 'user':
- return 'mwuser';
+ return $this->realTableName( 'mwuser', $format );
case 'text':
- return 'pagecontent';
+ return $this->realTableName( 'pagecontent', $format );
default:
- return parent::tableName( $name, $quoted );
+ return $this->realTableName( $name, $format );
}
}
+ /* Don't cheat on installer */
+ function realTableName( $name, $format = 'quoted' ) {
+ return parent::tableName( $name, $format );
+ }
+
/**
* Return the next in a sequence, save the value for retrieval via insertId()
*/
@@ -687,6 +693,24 @@ class DatabasePostgres extends DatabaseBase {
return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName (LIKE $oldName INCLUDING DEFAULTS)", $fname );
}
+ function listTables( $prefix = null, $fname = 'DatabasePostgres::listTables' ) {
+ global $wgDBmwschema;
+ $eschema = $this->addQuotes( $wgDBmwschema );
+ $result = $this->query( "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $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;
+ }
+
function timestamp( $ts = 0 ) {
return wfTimestamp( TS_POSTGRES, $ts );
}
@@ -737,7 +761,7 @@ class DatabasePostgres extends DatabaseBase {
if ( !$schema ) {
$schema = $wgDBmwschema;
}
- $table = $this->tableName( $table, false );
+ $table = $this->realTableName( $table, 'raw' );
$etable = $this->addQuotes( $table );
$eschema = $this->addQuotes( $schema );
$SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
@@ -752,7 +776,7 @@ class DatabasePostgres extends DatabaseBase {
* For backward compatibility, this function checks both tables and
* views.
*/
- function tableExists( $table, $schema = false ) {
+ function tableExists( $table, $fname = __METHOD__, $schema = false ) {
return $this->relationExists( $table, array( 'r', 'v' ), $schema );
}
@@ -766,8 +790,8 @@ class DatabasePostgres extends DatabaseBase {
$q = <<<SQL
SELECT 1 FROM pg_class, pg_namespace, pg_trigger
WHERE relnamespace=pg_namespace.oid AND relkind='r'
- AND tgrelid=pg_class.oid
- AND nspname=%s AND relname=%s AND tgname=%s
+ AND tgrelid=pg_class.oid
+ AND nspname=%s AND relname=%s AND tgname=%s
SQL;
$res = $this->query(
sprintf(
@@ -982,4 +1006,17 @@ SQL;
public function getSearchEngine() {
return 'SearchPostgres';
}
+
+ public function streamStatementEnd( &$sql, &$newLine ) {
+ # Allow dollar quoting for function declarations
+ if ( substr( $newLine, 0, 4 ) == '$mw$' ) {
+ if ( $this->delimiter ) {
+ $this->delimiter = false;
+ }
+ else {
+ $this->delimiter = ';';
+ }
+ }
+ return parent::streamStatementEnd( $sql, $newLine );
+ }
} // end DatabasePostgres class
diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php
index e298175d..b2eb1c6b 100644
--- a/includes/db/DatabaseSqlite.php
+++ b/includes/db/DatabaseSqlite.php
@@ -37,7 +37,7 @@ class DatabaseSqlite extends DatabaseBase {
$this->mName = $dbName;
parent::__construct( $server, $user, $password, $dbName, $flags );
// parent doesn't open when $user is false, but we can work with $dbName
- if( !$user && $dbName ) {
+ if( $dbName ) {
global $wgSharedDB;
if( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) {
$this->attachDatabase( $wgSharedDB );
@@ -319,15 +319,15 @@ class DatabaseSqlite extends DatabaseBase {
* Use MySQL's naming (accounts for prefix etc) but remove surrounding backticks
*
* @param $name
- * @param bool $quoted
+ * @param $format String
* @return string
*/
- function tableName( $name, $quoted = true ) {
+ function tableName( $name, $format = 'quoted' ) {
// table names starting with sqlite_ are reserved
if ( strpos( $name, 'sqlite_' ) === 0 ) {
return $name;
}
- return str_replace( '"', '', parent::tableName( $name, $quoted ) );
+ return str_replace( '"', '', parent::tableName( $name, $format ) );
}
/**
diff --git a/includes/db/DatabaseUtility.php b/includes/db/DatabaseUtility.php
index d1bced6b..0ea713c8 100644
--- a/includes/db/DatabaseUtility.php
+++ b/includes/db/DatabaseUtility.php
@@ -10,6 +10,9 @@ class DBObject {
$this->mData = $data;
}
+ /**
+ * @return bool
+ */
function isLOB() {
return false;
}
@@ -155,6 +158,9 @@ class ResultWrapper implements Iterator {
$this->currentRow = null;
}
+ /**
+ * @return int
+ */
function current() {
if ( is_null( $this->currentRow ) ) {
$this->next();
@@ -162,16 +168,25 @@ class ResultWrapper implements Iterator {
return $this->currentRow;
}
+ /**
+ * @return int
+ */
function key() {
return $this->pos;
}
+ /**
+ * @return int
+ */
function next() {
$this->pos++;
$this->currentRow = $this->fetchObject();
return $this->currentRow;
}
+ /**
+ * @return bool
+ */
function valid() {
return $this->current() !== false;
}
diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php
index 22a84960..dec6ae16 100644
--- a/includes/db/LBFactory.php
+++ b/includes/db/LBFactory.php
@@ -42,7 +42,6 @@ abstract class LBFactory {
/**
* Shut down, close connections and destroy the cached instance.
- *
*/
static function destroyInstance() {
if ( self::$instance ) {
@@ -64,6 +63,7 @@ abstract class LBFactory {
/**
* Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
+ * @param $conf
*/
abstract function __construct( $conf );
@@ -110,6 +110,8 @@ abstract class LBFactory {
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
+ * @param $callback string|array
+ * @param array $params
*/
abstract function forEachLB( $callback, $params = array() );
@@ -121,6 +123,8 @@ abstract class LBFactory {
/**
* Call a method of each tracked load balancer
+ * @param $methodName string
+ * @param $args array
*/
function forEachLBCallMethod( $methodName, $args = array() ) {
$this->forEachLB( array( $this, 'callMethod' ), array( $methodName, $args ) );
@@ -128,6 +132,9 @@ abstract class LBFactory {
/**
* Private helper for forEachLBCallMethod
+ * @param $loadBalancer
+ * @param $methodName string
+ * @param $args
*/
function callMethod( $loadBalancer, $methodName, $args ) {
call_user_func_array( array( $loadBalancer, $methodName ), $args );
@@ -232,6 +239,8 @@ class LBFactory_Simple extends LBFactory {
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
+ * @param $callback
+ * @param $params array
*/
function forEachLB( $callback, $params = array() ) {
if ( isset( $this->mainLB ) ) {
diff --git a/includes/db/LBFactory_Multi.php b/includes/db/LBFactory_Multi.php
index 61e56e78..b7977a21 100644
--- a/includes/db/LBFactory_Multi.php
+++ b/includes/db/LBFactory_Multi.php
@@ -53,13 +53,16 @@ class LBFactory_Multi extends LBFactory {
var $conf, $mainLBs = array(), $extLBs = array();
var $lastWiki, $lastSection;
+ /**
+ * @param $conf array
+ */
function __construct( $conf ) {
$this->chronProt = new ChronologyProtector;
$this->conf = $conf;
$required = array( 'sectionsByDB', 'sectionLoads', 'serverTemplate' );
$optional = array( 'groupLoadsBySection', 'groupLoadsByDB', 'hostsByName',
'externalLoads', 'externalTemplateOverrides', 'templateOverridesByServer',
- 'templateOverridesByCluster', 'masterTemplateOverrides',
+ 'templateOverridesByCluster', 'masterTemplateOverrides',
'readOnlyBySection' );
foreach ( $required as $key ) {
@@ -83,6 +86,10 @@ class LBFactory_Multi extends LBFactory {
}
}
+ /**
+ * @param $wiki bool|string
+ * @return string
+ */
function getSectionForWiki( $wiki = false ) {
if ( $this->lastWiki === $wiki ) {
return $this->lastSection;
@@ -99,7 +106,7 @@ class LBFactory_Multi extends LBFactory {
}
/**
- * @param $wiki string
+ * @param $wiki bool|string
* @return LoadBalancer
*/
function newMainLB( $wiki = false ) {
@@ -116,7 +123,7 @@ class LBFactory_Multi extends LBFactory {
}
/**
- * @param $wiki
+ * @param $wiki bool|string
* @return LoadBalancer
*/
function getMainLB( $wiki = false ) {
@@ -165,6 +172,9 @@ class LBFactory_Multi extends LBFactory {
/**
* Make a new load balancer object based on template and load array
*
+ * @param $template
+ * @param $loads array
+ * @param $groupLoads
* @return LoadBalancer
*/
function newLoadBalancer( $template, $loads, $groupLoads ) {
@@ -172,7 +182,7 @@ class LBFactory_Multi extends LBFactory {
$servers = $this->makeServerArray( $template, $loads, $groupLoads );
$lb = new LoadBalancer( array(
'servers' => $servers,
- 'masterWaitTimeout' => $wgMasterWaitTimeout
+ 'masterWaitTimeout' => $wgMasterWaitTimeout
));
return $lb;
}
@@ -180,6 +190,9 @@ class LBFactory_Multi extends LBFactory {
/**
* Make a server array as expected by LoadBalancer::__construct, using a template and load array
*
+ * @param $template
+ * @param $loads array
+ * @param $groupLoads
* @return array
*/
function makeServerArray( $template, $loads, $groupLoads ) {
@@ -220,6 +233,8 @@ class LBFactory_Multi extends LBFactory {
/**
* Take a group load array indexed by group then server, and reindex it by server then group
+ * @param $groupLoads
+ * @return array
*/
function reindexGroupLoads( $groupLoads ) {
$reindexed = array();
@@ -233,6 +248,8 @@ class LBFactory_Multi extends LBFactory {
/**
* Get the database name and prefix based on the wiki ID
+ * @param $wiki bool
+ * @return array
*/
function getDBNameAndPrefix( $wiki = false ) {
if ( $wiki === false ) {
@@ -247,6 +264,8 @@ class LBFactory_Multi extends LBFactory {
* Execute a function for each tracked load balancer
* The callback is called with the load balancer as the first parameter,
* and $params passed as the subsequent parameters.
+ * @param $callback
+ * @param $params array
*/
function forEachLB( $callback, $params = array() ) {
foreach ( $this->mainLBs as $lb ) {
diff --git a/includes/db/LBFactory_Single.php b/includes/db/LBFactory_Single.php
index 89b41321..f80aa4bc 100644
--- a/includes/db/LBFactory_Single.php
+++ b/includes/db/LBFactory_Single.php
@@ -15,7 +15,7 @@ class LBFactory_Single extends LBFactory {
}
/**
- * @param $wiki
+ * @param $wiki bool|string
*
* @return LoadBalancer_Single
*/
@@ -24,7 +24,7 @@ class LBFactory_Single extends LBFactory {
}
/**
- * @param $wiki
+ * @param $wiki bool|string
*
* @return LoadBalancer_Single
*/
@@ -34,7 +34,7 @@ class LBFactory_Single extends LBFactory {
/**
* @param $cluster
- * @param $wiki
+ * @param $wiki bool|string
*
* @return LoadBalancer_Single
*/
@@ -44,7 +44,7 @@ class LBFactory_Single extends LBFactory {
/**
* @param $cluster
- * @param $wiki
+ * @param $wiki bool|string
*
* @return LoadBalancer_Single
*/
diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php
index c7210c4c..e96c6720 100644
--- a/includes/db/LoadBalancer.php
+++ b/includes/db/LoadBalancer.php
@@ -90,6 +90,8 @@ class LoadBalancer {
/**
* Get or set arbitrary data used by the parent object, usually an LBFactory
+ * @param $x
+ * @return \Mixed
*/
function parentInfo( $x = null ) {
return wfSetVar( $this->mParentInfo, $x );
@@ -99,7 +101,7 @@ class LoadBalancer {
* Given an array of non-normalised probabilities, this function will select
* an element and return the appropriate key
*
- * @param $weights
+ * @param $weights array
*
* @return int
*/
@@ -117,7 +119,7 @@ class LoadBalancer {
return false;
}
$max = mt_getrandmax();
- $rand = mt_rand(0, $max) / $max * $sum;
+ $rand = mt_rand( 0, $max ) / $max * $sum;
$sum = 0;
foreach ( $weights as $i => $w ) {
@@ -130,7 +132,7 @@ class LoadBalancer {
}
/**
- * @param $loads
+ * @param $loads array
* @param $wiki bool
* @return bool|int|string
*/
@@ -140,10 +142,10 @@ class LoadBalancer {
foreach ( $lags as $i => $lag ) {
if ( $i != 0 ) {
if ( $lag === false ) {
- wfDebug( "Server #$i is not replicating\n" );
+ wfDebugLog( 'replication', "Server #$i is not replicating\n" );
unset( $loads[$i] );
} elseif ( isset( $this->mServers[$i]['max lag'] ) && $lag > $this->mServers[$i]['max lag'] ) {
- wfDebug( "Server #$i is excessively lagged ($lag seconds)\n" );
+ wfDebugLog( 'replication', "Server #$i is excessively lagged ($lag seconds)\n" );
unset( $loads[$i] );
}
}
@@ -241,6 +243,7 @@ class LoadBalancer {
$i = $this->getRandomNonLagged( $currentLoads, $wiki );
if ( $i === false && count( $currentLoads ) != 0 ) {
# All slaves lagged. Switch to read-only mode
+ wfDebugLog( 'replication', "All slaves lagged. Switch to read-only mode\n" );
$wgReadOnly = 'The database has been automatically locked ' .
'while the slave database servers catch up to the master';
$i = $this->pickRandom( $currentLoads );
@@ -333,6 +336,8 @@ class LoadBalancer {
/**
* Wait for a specified number of microseconds, and return the period waited
+ * @param $t int
+ * @return int
*/
function sleep( $t ) {
wfProfileIn( __METHOD__ );
@@ -346,6 +351,7 @@ class LoadBalancer {
* Set the master wait position
* If a DB_SLAVE connection has been opened already, waits
* Otherwise sets a variable telling it to wait if such a connection is opened
+ * @param $pos int
*/
public function waitFor( $pos ) {
wfProfileIn( __METHOD__ );
@@ -363,6 +369,7 @@ class LoadBalancer {
/**
* Set the master wait position and wait for ALL slaves to catch up to it
+ * @param $pos int
*/
public function waitForAll( $pos ) {
wfProfileIn( __METHOD__ );
@@ -377,7 +384,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
+ * @param $i int
+ * @return DatabaseBase|false
*/
function getAnyOpenConnection( $i ) {
foreach ( $this->mConns as $conns ) {
@@ -390,6 +398,9 @@ class LoadBalancer {
/**
* Wait for a given slave to catch up to the master pos stored in $this
+ * @param $index
+ * @param $open bool
+ * @return bool
*/
function doWait( $index, $open = false ) {
# Find a connection to wait on
@@ -662,12 +673,14 @@ class LoadBalancer {
* Returns a Database object whether or not the connection was successful.
* @access private
*
+ * @param $server
+ * @param $dbNameOverride bool
* @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.' );
+ 'See DefaultSettings.php entry for $wgDBservers.' );
}
$host = $server['host'];
@@ -702,6 +715,10 @@ class LoadBalancer {
return $db;
}
+ /**
+ * @param $conn
+ * @throws DBConnectionError
+ */
function reportConnectionError( &$conn ) {
wfProfileIn( __METHOD__ );
@@ -719,6 +736,9 @@ class LoadBalancer {
wfProfileOut( __METHOD__ );
}
+ /**
+ * @return int
+ */
function getWriterIndex() {
return 0;
}
@@ -726,6 +746,7 @@ class LoadBalancer {
/**
* Returns true if the specified index is a valid server index
*
+ * @param $i
* @return bool
*/
function haveIndex( $i ) {
@@ -735,6 +756,7 @@ class LoadBalancer {
/**
* Returns true if the specified index is valid and has non-zero load
*
+ * @param $i
* @return bool
*/
function isNonZeroLoad( $i ) {
@@ -753,6 +775,8 @@ class LoadBalancer {
/**
* Get the host name or IP address of the server with the specified index
* Prefer a readable name if available.
+ * @param $i
+ * @return string
*/
function getServerName( $i ) {
if ( isset( $this->mServers[$i]['hostName'] ) ) {
@@ -766,6 +790,8 @@ class LoadBalancer {
/**
* Return the server info structure for a given index, or false if the index is invalid.
+ * @param $i
+ * @return bool
*/
function getServerInfo( $i ) {
if ( isset( $this->mServers[$i] ) ) {
@@ -777,6 +803,8 @@ class LoadBalancer {
/**
* Sets the server info structure for the given index. Entry at index $i is created if it doesn't exist
+ * @param $i
+ * @param $serverInfo
*/
function setServerInfo( $i, $serverInfo ) {
$this->mServers[$i] = $serverInfo;
@@ -827,8 +855,10 @@ class LoadBalancer {
* Deprecated function, typo in function name
*
* @deprecated in 1.18
+ * @param $conn
*/
function closeConnecton( $conn ) {
+ wfDeprecated( __METHOD__, '1.18' );
$this->closeConnection( $conn );
}
@@ -836,8 +866,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
- * @return void
+ * @param $conn DatabaseBase
*/
function closeConnection( $conn ) {
$done = false;
@@ -883,21 +912,32 @@ class LoadBalancer {
}
foreach ( $conns2[$masterIndex] as $conn ) {
if ( $conn->doneWrites() ) {
- $conn->commit();
+ $conn->commit( __METHOD__ );
}
}
}
}
+ /**
+ * @param $value null
+ * @return Mixed
+ */
function waitTimeout( $value = null ) {
return wfSetVar( $this->mWaitTimeout, $value );
}
+ /**
+ * @return bool
+ */
function getLaggedSlaveMode() {
return $this->mLaggedSlaveMode;
}
- /* Disables/enables lag checks */
+ /**
+ * Disables/enables lag checks
+ * @param $mode null
+ * @return bool
+ */
function allowLagged( $mode = null ) {
if ( $mode === null) {
return $this->mAllowLagged;
@@ -905,6 +945,9 @@ class LoadBalancer {
$this->mAllowLagged = $mode;
}
+ /**
+ * @return bool
+ */
function pingAll() {
$success = true;
foreach ( $this->mConns as $conns2 ) {
@@ -921,6 +964,8 @@ class LoadBalancer {
/**
* Call a function with each open connection object
+ * @param $callback
+ * @param array $params
*/
function forEachOpenConnection( $callback, $params = array() ) {
foreach ( $this->mConns as $conns2 ) {
@@ -936,7 +981,7 @@ 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. If there is
+ * 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
@@ -988,22 +1033,26 @@ class LoadBalancer {
$this->mLagTimes = array( 0 => 0 );
} else {
# Send the request to the load monitor
- $this->mLagTimes = $this->getLoadMonitor()->getLagTimes(
+ $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.
+ * 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
+ * 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.
+ *
+ * @param $conn DatabaseBase
+ *
+ * @return int
*/
function safeGetLag( $conn ) {
if ( $this->getServerCount() == 1 ) {
diff --git a/includes/db/LoadMonitor.php b/includes/db/LoadMonitor.php
index a6370c9e..16a0343f 100644
--- a/includes/db/LoadMonitor.php
+++ b/includes/db/LoadMonitor.php
@@ -66,12 +66,16 @@ class LoadMonitor_Null implements LoadMonitor {
function postConnectionBackoff( $conn, $threshold ) {
}
+ /**
+ * @param $serverIndexes
+ * @param $wiki
+ * @return array
+ */
function getLagTimes( $serverIndexes, $wiki ) {
return array_fill_keys( $serverIndexes, 0 );
}
}
-
/**
* Basic MySQL load monitor with no external dependencies
* Uses memcached to cache the replication lag for a short time
diff --git a/includes/debug/Debug.php b/includes/debug/Debug.php
new file mode 100644
index 00000000..de50ccac
--- /dev/null
+++ b/includes/debug/Debug.php
@@ -0,0 +1,285 @@
+<?php
+
+/**
+ * New debugger system that outputs a toolbar on page view
+ *
+ * By default, most methods do nothing ( self::$enabled = false ). You have
+ * to explicitly call MWDebug::init() to enabled them.
+ *
+ * @todo Profiler support
+ */
+class MWDebug {
+
+ /**
+ * Log lines
+ *
+ * @var array
+ */
+ protected static $log = array();
+
+ /**
+ * Debug messages from wfDebug()
+ *
+ * @var array
+ */
+ protected static $debug = array();
+
+ /**
+ * Queries
+ *
+ * @var array
+ */
+ protected static $query = array();
+
+ /**
+ * Is the debugger enabled?
+ *
+ * @var bool
+ */
+ protected static $enabled = false;
+
+ /**
+ * Array of functions that have already been warned, formatted
+ * function-caller to prevent a buttload of warnings
+ *
+ * @var array
+ */
+ protected static $deprecationWarnings = array();
+
+ /**
+ * Enabled the debugger and load resource module.
+ * This is called by Setup.php when $wgDebugToolbar is true.
+ */
+ public static function init() {
+ self::$enabled = true;
+ }
+
+ /**
+ * Add ResourceLoader modules to the OutputPage object if debugging is
+ * enabled.
+ *
+ * @param $out OutputPage
+ */
+ public static function addModules( OutputPage $out ) {
+ if ( self::$enabled ) {
+ $out->addModules( 'mediawiki.debug.init' );
+ }
+ }
+
+ /**
+ * Adds a line to the log
+ *
+ * @todo Add support for passing objects
+ *
+ * @param $str string
+ */
+ public static function log( $str ) {
+ if ( !self::$enabled ) {
+ return;
+ }
+
+ self::$log[] = array(
+ 'msg' => htmlspecialchars( $str ),
+ 'type' => 'log',
+ 'caller' => wfGetCaller(),
+ );
+ }
+
+ /**
+ * Returns internal log array
+ */
+ public static function getLog() {
+ return self::$log;
+ }
+
+ /**
+ * Clears internal log array and deprecation tracking
+ */
+ public static function clearLog() {
+ self::$log = array();
+ self::$deprecationWarnings = array();
+ }
+
+ /**
+ * Adds a warning entry to the log
+ *
+ * @param $msg
+ * @param int $callerOffset
+ * @return mixed
+ */
+ public static function warning( $msg, $callerOffset = 1 ) {
+ if ( !self::$enabled ) {
+ return;
+ }
+
+ // Check to see if there was already a deprecation notice, so not to
+ // get a duplicate warning
+ $logCount = count( self::$log );
+ if ( $logCount ) {
+ $lastLog = self::$log[ $logCount - 1 ];
+ if ( $lastLog['type'] == 'deprecated' && $lastLog['caller'] == wfGetCaller( $callerOffset + 1 ) ) {
+ return;
+ }
+ }
+
+ self::$log[] = array(
+ 'msg' => htmlspecialchars( $msg ),
+ 'type' => 'warn',
+ 'caller' => wfGetCaller( $callerOffset ),
+ );
+ }
+
+ /**
+ * Adds a depreciation entry to the log, along with a backtrace
+ *
+ * @param $function
+ * @param $version
+ * @param $component
+ * @return mixed
+ */
+ public static function deprecated( $function, $version, $component ) {
+ if ( !self::$enabled ) {
+ return;
+ }
+
+ // Chain: This function -> wfDeprecated -> deprecatedFunction -> caller
+ $caller = wfGetCaller( 4 );
+
+ // Check to see if there already was a warning about this function
+ $functionString = "$function-$caller";
+ if ( in_array( $functionString, self::$deprecationWarnings ) ) {
+ return;
+ }
+
+ $version = $version === false ? '(unknown version)' : $version;
+ $component = $component === false ? 'MediaWiki' : $component;
+ $msg = htmlspecialchars( "Use of function $function was deprecated in $component $version" );
+ $msg .= Html::rawElement( 'div', array( 'class' => 'mw-debug-backtrace' ),
+ Html::element( 'span', array(), 'Backtrace:' )
+ . wfBacktrace()
+ );
+
+ self::$deprecationWarnings[] = $functionString;
+ self::$log[] = array(
+ 'msg' => $msg,
+ 'type' => 'deprecated',
+ 'caller' => $caller,
+ );
+ }
+
+ /**
+ * This is a method to pass messages from wfDebug to the pretty debugger.
+ * Do NOT use this method, use MWDebug::log or wfDebug()
+ *
+ * @param $str string
+ */
+ public static function debugMsg( $str ) {
+ if ( !self::$enabled ) {
+ return;
+ }
+
+ self::$debug[] = trim( $str );
+ }
+
+ /**
+ * Begins profiling on a database query
+ *
+ * @param $sql string
+ * @param $function string
+ * @param $isMaster bool
+ * @return int ID number of the query to pass to queryTime or -1 if the
+ * debugger is disabled
+ */
+ public static function query( $sql, $function, $isMaster ) {
+ if ( !self::$enabled ) {
+ return -1;
+ }
+
+ self::$query[] = array(
+ 'sql' => $sql,
+ 'function' => $function,
+ 'master' => (bool) $isMaster,
+ 'time' => 0.0,
+ '_start' => microtime( true ),
+ );
+
+ return count( self::$query ) - 1;
+ }
+
+ /**
+ * Calculates how long a query took.
+ *
+ * @param $id int
+ */
+ public static function queryTime( $id ) {
+ if ( $id === -1 || !self::$enabled ) {
+ return;
+ }
+
+ self::$query[$id]['time'] = microtime( true ) - self::$query[$id]['_start'];
+ unset( self::$query[$id]['_start'] );
+ }
+
+ /**
+ * Returns a list of files included, along with their size
+ *
+ * @param $context IContextSource
+ * @return array
+ */
+ protected static function getFilesIncluded( IContextSource $context ) {
+ $files = get_included_files();
+ $fileList = array();
+ foreach ( $files as $file ) {
+ $size = filesize( $file );
+ $fileList[] = array(
+ 'name' => $file,
+ 'size' => $context->getLanguage()->formatSize( $size ),
+ );
+ }
+
+ return $fileList;
+ }
+
+ /**
+ * Returns the HTML to add to the page for the toolbar
+ *
+ * @param $context IContextSource
+ * @return string
+ */
+ public static function getDebugHTML( IContextSource $context ) {
+ if ( !self::$enabled ) {
+ return '';
+ }
+
+ global $wgVersion, $wgRequestTime;
+ MWDebug::log( 'MWDebug output complete' );
+ $request = $context->getRequest();
+ $debugInfo = array(
+ 'mwVersion' => $wgVersion,
+ 'phpVersion' => PHP_VERSION,
+ 'time' => microtime( true ) - $wgRequestTime,
+ 'log' => self::$log,
+ 'debugLog' => self::$debug,
+ 'queries' => self::$query,
+ 'request' => array(
+ 'method' => $_SERVER['REQUEST_METHOD'],
+ 'url' => $request->getRequestURL(),
+ 'headers' => $request->getAllHeaders(),
+ 'params' => $request->getValues(),
+ ),
+ 'memory' => $context->getLanguage()->formatSize( memory_get_usage() ),
+ 'memoryPeak' => $context->getLanguage()->formatSize( memory_get_peak_usage() ),
+ 'includes' => self::getFilesIncluded( $context ),
+ );
+
+ // Cannot use OutputPage::addJsConfigVars because those are already outputted
+ // by the time this method is called.
+ $html = Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ ResourceLoader::makeConfigSetScript( array( 'debugInfo' => $debugInfo ) )
+ )
+ );
+
+ return $html;
+ }
+}
diff --git a/includes/diff/DairikiDiff.php b/includes/diff/DairikiDiff.php
index 8f19712b..c935eee2 100644
--- a/includes/diff/DairikiDiff.php
+++ b/includes/diff/DairikiDiff.php
@@ -24,10 +24,16 @@ class _DiffOp {
trigger_error( 'pure virtual', E_USER_ERROR );
}
+ /**
+ * @return int
+ */
function norig() {
return $this->orig ? sizeof( $this->orig ) : 0;
}
+ /**
+ * @return int
+ */
function nclosing() {
return $this->closing ? sizeof( $this->closing ) : 0;
}
@@ -49,6 +55,9 @@ class _DiffOp_Copy extends _DiffOp {
$this->closing = $closing;
}
+ /**
+ * @return _DiffOp_Copy
+ */
function reverse() {
return new _DiffOp_Copy( $this->closing, $this->orig );
}
@@ -67,6 +76,9 @@ class _DiffOp_Delete extends _DiffOp {
$this->closing = false;
}
+ /**
+ * @return _DiffOp_Add
+ */
function reverse() {
return new _DiffOp_Add( $this->orig );
}
@@ -85,6 +97,9 @@ class _DiffOp_Add extends _DiffOp {
$this->orig = false;
}
+ /**
+ * @return _DiffOp_Delete
+ */
function reverse() {
return new _DiffOp_Delete( $this->closing );
}
@@ -103,6 +118,9 @@ class _DiffOp_Change extends _DiffOp {
$this->closing = $closing;
}
+ /**
+ * @return _DiffOp_Change
+ */
function reverse() {
return new _DiffOp_Change( $this->closing, $this->orig );
}
@@ -145,6 +163,11 @@ class _DiffEngine {
protected $lcs = 0;
+ /**
+ * @param $from_lines
+ * @param $to_lines
+ * @return array
+ */
function diff ( $from_lines, $to_lines ) {
wfProfileIn( __METHOD__ );
@@ -199,6 +222,10 @@ class _DiffEngine {
return $edits;
}
+ /**
+ * @param $from_lines
+ * @param $to_lines
+ */
function diff_local ( $from_lines, $to_lines ) {
global $wgExternalDiffEngine;
wfProfileIn( __METHOD__ );
@@ -268,6 +295,8 @@ class _DiffEngine {
/**
* Returns the whole line if it's small enough, or the MD5 hash otherwise
+ * @param $line string
+ * @return string
*/
function _line_hash( $line ) {
if ( strlen( $line ) > self::MAX_XREF_LENGTH ) {
@@ -293,6 +322,12 @@ class _DiffEngine {
* of the two files do not match, and likewise that the last lines do not
* match. The caller must trim matching lines from the beginning and end
* of the portions it is going to specify.
+ * @param $xoff
+ * @param $xlim
+ * @param $yoff
+ * @param $ylim
+ * @param $nchunks
+ * @return array
*/
function _diag( $xoff, $xlim, $yoff, $ylim, $nchunks ) {
$flip = false;
@@ -373,6 +408,10 @@ class _DiffEngine {
return array( $this->lcs, $seps );
}
+ /**
+ * @param $ypos
+ * @return int
+ */
function _lcs_pos( $ypos ) {
$end = $this->lcs;
if ( $end == 0 || $ypos > $this->seq[$end] ) {
@@ -410,6 +449,10 @@ class _DiffEngine {
*
* Note that XLIM, YLIM are exclusive bounds.
* All line numbers are origin-0 and discarded lines are not counted.
+ * @param $xoff
+ * @param $xlim
+ * @param $yoff
+ * @param $ylim
*/
function _compareseq ( $xoff, $xlim, $yoff, $ylim ) {
// Slide down the bottom initial diagonal.
@@ -703,6 +746,8 @@ class Diff {
* Check a Diff for validity.
*
* This is here only for debugging purposes.
+ * @param $from_lines
+ * @param $to_lines
*/
function _check( $from_lines, $to_lines ) {
wfProfileIn( __METHOD__ );
@@ -886,6 +931,13 @@ class DiffFormatter {
return $end;
}
+ /**
+ * @param $xbeg
+ * @param $xlen
+ * @param $ybeg
+ * @param $ylen
+ * @param $edits
+ */
function _block( $xbeg, $xlen, $ybeg, $ylen, &$edits ) {
wfProfileIn( __METHOD__ );
$this->_start_block( $this->_block_header( $xbeg, $xlen, $ybeg, $ylen ) );
@@ -910,12 +962,22 @@ class DiffFormatter {
ob_start();
}
+ /**
+ * @return string
+ */
function _end_diff() {
$val = ob_get_contents();
ob_end_clean();
return $val;
}
+ /**
+ * @param $xbeg
+ * @param $xlen
+ * @param $ybeg
+ * @param $ylen
+ * @return string
+ */
function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
if ( $xlen > 1 ) {
$xbeg .= ',' . ( $xbeg + $xlen - 1 );
@@ -934,23 +996,41 @@ class DiffFormatter {
function _end_block() {
}
+ /**
+ * @param $lines
+ * @param $prefix string
+ */
function _lines( $lines, $prefix = ' ' ) {
foreach ( $lines as $line ) {
echo "$prefix $line\n";
}
}
+ /**
+ * @param $lines
+ */
function _context( $lines ) {
$this->_lines( $lines );
}
+ /**
+ * @param $lines
+ */
function _added( $lines ) {
$this->_lines( $lines, '>' );
}
+
+ /**
+ * @param $lines
+ */
function _deleted( $lines ) {
$this->_lines( $lines, '<' );
}
+ /**
+ * @param $orig
+ * @param $closing
+ */
function _changed( $orig, $closing ) {
$this->_deleted( $orig );
echo "---\n";
@@ -966,16 +1046,36 @@ class UnifiedDiffFormatter extends DiffFormatter {
var $leading_context_lines = 2;
var $trailing_context_lines = 2;
+ /**
+ * @param $lines
+ */
function _added( $lines ) {
$this->_lines( $lines, '+' );
}
+
+ /**
+ * @param $lines
+ */
function _deleted( $lines ) {
$this->_lines( $lines, '-' );
}
+
+ /**
+ * @param $orig
+ * @param $closing
+ */
function _changed( $orig, $closing ) {
$this->_deleted( $orig );
$this->_added( $closing );
}
+
+ /**
+ * @param $xbeg
+ * @param $xlen
+ * @param $ybeg
+ * @param $ylen
+ * @return string
+ */
function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
return "@@ -$xbeg,$xlen +$ybeg,$ylen @@";
}
@@ -986,6 +1086,11 @@ class UnifiedDiffFormatter extends DiffFormatter {
* @ingroup DifferenceEngine
*/
class ArrayDiffFormatter extends DiffFormatter {
+
+ /**
+ * @param $diff
+ * @return array
+ */
function format( $diff ) {
$oldline = 1;
$newline = 1;
@@ -1049,6 +1154,9 @@ class _HWLDF_WordAccumulator {
$this->_tag = '';
}
+ /**
+ * @param $new_tag
+ */
function _flushGroup( $new_tag ) {
if ( $this->_group !== '' ) {
if ( $this->_tag == 'ins' ) {
@@ -1065,6 +1173,9 @@ class _HWLDF_WordAccumulator {
$this->_tag = $new_tag;
}
+ /**
+ * @param $new_tag
+ */
function _flushLine( $new_tag ) {
$this->_flushGroup( $new_tag );
if ( $this->_line != '' ) {
@@ -1076,6 +1187,10 @@ class _HWLDF_WordAccumulator {
$this->_line = '';
}
+ /**
+ * @param $words
+ * @param $tag string
+ */
function addWords ( $words, $tag = '' ) {
if ( $tag != $this->_tag ) {
$this->_flushGroup( $tag );
@@ -1095,6 +1210,9 @@ class _HWLDF_WordAccumulator {
}
}
+ /**
+ * @return array
+ */
function getLines() {
$this->_flushLine( '~done' );
return $this->_lines;
@@ -1109,6 +1227,10 @@ class _HWLDF_WordAccumulator {
class WordLevelDiff extends MappedDiff {
const MAX_LINE_LENGTH = 10000;
+ /**
+ * @param $orig_lines
+ * @param $closing_lines
+ */
function __construct ( $orig_lines, $closing_lines ) {
wfProfileIn( __METHOD__ );
@@ -1120,6 +1242,10 @@ class WordLevelDiff extends MappedDiff {
wfProfileOut( __METHOD__ );
}
+ /**
+ * @param $lines
+ * @return array
+ */
function _split( $lines ) {
wfProfileIn( __METHOD__ );
@@ -1152,6 +1278,9 @@ class WordLevelDiff extends MappedDiff {
return array( $words, $stripped );
}
+ /**
+ * @return array
+ */
function orig() {
wfProfileIn( __METHOD__ );
$orig = new _HWLDF_WordAccumulator;
@@ -1168,6 +1297,9 @@ class WordLevelDiff extends MappedDiff {
return $lines;
}
+ /**
+ * @return array
+ */
function closing() {
wfProfileIn( __METHOD__ );
$closing = new _HWLDF_WordAccumulator;
@@ -1197,6 +1329,11 @@ class TableDiffFormatter extends DiffFormatter {
$this->trailing_context_lines = 2;
}
+ /**
+ * @static
+ * @param $msg
+ * @return mixed
+ */
public static function escapeWhiteSpace( $msg ) {
$msg = preg_replace( '/^ /m', '&#160; ', $msg );
$msg = preg_replace( '/ $/m', ' &#160;', $msg );
@@ -1204,12 +1341,22 @@ class TableDiffFormatter extends DiffFormatter {
return $msg;
}
+ /**
+ * @param $xbeg
+ * @param $xlen
+ * @param $ybeg
+ * @param $ylen
+ * @return string
+ */
function _block_header( $xbeg, $xlen, $ybeg, $ylen ) {
$r = '<tr><td colspan="2" class="diff-lineno"><!--LINE ' . $xbeg . "--></td>\n" .
'<td colspan="2" class="diff-lineno"><!--LINE ' . $ybeg . "--></td></tr>\n";
return $r;
}
+ /**
+ * @param $header
+ */
function _start_block( $header ) {
echo $header;
}
@@ -1220,21 +1367,39 @@ class TableDiffFormatter extends DiffFormatter {
function _lines( $lines, $prefix = ' ', $color = 'white' ) {
}
- # HTML-escape parameter before calling this
+ /**
+ * HTML-escape parameter before calling this
+ * @param $line
+ * @return string
+ */
function addedLine( $line ) {
return $this->wrapLine( '+', 'diff-addedline', $line );
}
- # HTML-escape parameter before calling this
+ /**
+ * HTML-escape parameter before calling this
+ * @param $line
+ * @return string
+ */
function deletedLine( $line ) {
return $this->wrapLine( '−', 'diff-deletedline', $line );
}
- # HTML-escape parameter before calling this
+ /**
+ * HTML-escape parameter before calling this
+ * @param $line
+ * @return string
+ */
function contextLine( $line ) {
return $this->wrapLine( '&#160;', 'diff-context', $line );
}
+ /**
+ * @param $marker
+ * @param $class
+ * @param $line
+ * @return string
+ */
private function wrapLine( $marker, $class, $line ) {
if ( $line !== '' ) {
// The <div> wrapper is needed for 'overflow: auto' style to scroll properly
@@ -1243,10 +1408,16 @@ class TableDiffFormatter extends DiffFormatter {
return "<td class='diff-marker'>$marker</td><td class='$class'>$line</td>";
}
+ /**
+ * @return string
+ */
function emptyLine() {
return '<td colspan="2">&#160;</td>';
}
+ /**
+ * @param $lines array
+ */
function _added( $lines ) {
foreach ( $lines as $line ) {
echo '<tr>' . $this->emptyLine() .
@@ -1255,6 +1426,9 @@ class TableDiffFormatter extends DiffFormatter {
}
}
+ /**
+ * @param $lines
+ */
function _deleted( $lines ) {
foreach ( $lines as $line ) {
echo '<tr>' . $this->deletedLine( '<del class="diffchange">' .
@@ -1263,6 +1437,9 @@ class TableDiffFormatter extends DiffFormatter {
}
}
+ /**
+ * @param $lines
+ */
function _context( $lines ) {
foreach ( $lines as $line ) {
echo '<tr>' .
@@ -1271,6 +1448,10 @@ class TableDiffFormatter extends DiffFormatter {
}
}
+ /**
+ * @param $orig
+ * @param $closing
+ */
function _changed( $orig, $closing ) {
wfProfileIn( __METHOD__ );
diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php
index 5902461b..439e3204 100644
--- a/includes/diff/DifferenceEngine.php
+++ b/includes/diff/DifferenceEngine.php
@@ -18,24 +18,25 @@ define( 'MW_DIFF_VERSION', '1.11a' );
* @todo document
* @ingroup DifferenceEngine
*/
-class DifferenceEngine {
+class DifferenceEngine extends ContextSource {
/**#@+
* @private
*/
var $mOldid, $mNewid;
- var $mOldtitle, $mNewtitle, $mPagetitle;
var $mOldtext, $mNewtext;
+ protected $mDiffLang;
/**
* @var Title
*/
- var $mOldPage, $mNewPage, $mTitle;
+ var $mOldPage, $mNewPage;
var $mRcidMarkPatrolled;
/**
* @var Revision
*/
var $mOldRev, $mNewRev;
+ private $mRevisionsIdsLoaded = false; // Have the revisions IDs been loaded
var $mRevisionsLoaded = false; // Have the revisions been loaded
var $mTextLoaded = 0; // How many text blobs have been loaded, 0, 1 or 2?
var $mCacheHit = false; // Was the diff fetched from cache?
@@ -51,49 +52,32 @@ class DifferenceEngine {
// readability and conserve space with many small diffs.
protected $mReducedLineNumbers = false;
+ // Link to action=markpatrolled
+ protected $mMarkPatrolledLink = null;
+
protected $unhide = false; # show rev_deleted content if allowed
/**#@-*/
/**
* Constructor
- * @param $titleObj Title object that the diff is associated with
+ * @param $context IContextSource context to use, anything else will be ignored
* @param $old Integer old ID we want to show and diff with.
* @param $new String either 'prev' or 'next'.
* @param $rcid Integer ??? FIXME (default 0)
* @param $refreshCache boolean If set, refreshes the diff cache
* @param $unhide boolean If set, allow viewing deleted revs
*/
- function __construct( $titleObj = null, $old = 0, $new = 0, $rcid = 0,
+ function __construct( $context = null, $old = 0, $new = 0, $rcid = 0,
$refreshCache = false, $unhide = false )
{
- if ( $titleObj ) {
- $this->mTitle = $titleObj;
- } else {
- global $wgTitle;
- $this->mTitle = $wgTitle; // @TODO: get rid of this
+ if ( $context instanceof IContextSource ) {
+ $this->setContext( $context );
}
+
wfDebug( "DifferenceEngine old '$old' new '$new' rcid '$rcid'\n" );
- if ( 'prev' === $new ) {
- # Show diff between revision $old and the previous one.
- # Get previous one from DB.
- $this->mNewid = intval( $old );
- $this->mOldid = $this->mTitle->getPreviousRevisionID( $this->mNewid );
- } elseif ( 'next' === $new ) {
- # Show diff between revision $old and the next one.
- # Get next one from DB.
- $this->mOldid = intval( $old );
- $this->mNewid = $this->mTitle->getNextRevisionID( $this->mOldid );
- if ( false === $this->mNewid ) {
- # if no result, NewId points to the newest old revision. The only newer
- # revision is cur, which is "0".
- $this->mNewid = 0;
- }
- } else {
- $this->mOldid = intval( $old );
- $this->mNewid = intval( $new );
- wfRunHooks( 'NewDifferenceEngine', array( &$titleObj, &$this->mOldid, &$this->mNewid, $old, $new ) );
- }
+ $this->mOldid = $old;
+ $this->mNewid = $new;
$this->mRcidMarkPatrolled = intval( $rcid ); # force it to be an integer
$this->mRefreshCache = $refreshCache;
$this->unhide = $unhide;
@@ -107,10 +91,14 @@ class DifferenceEngine {
}
/**
- * @return Title
+ * @return Language
*/
- function getTitle() {
- return $this->mTitle;
+ function getDiffLang() {
+ if ( $this->mDiffLang === null ) {
+ # Default language in which the diff text is written.
+ $this->mDiffLang = $this->getTitle()->getPageLanguage();
+ }
+ return $this->mDiffLang;
}
/**
@@ -124,6 +112,7 @@ class DifferenceEngine {
* @return int
*/
function getOldid() {
+ $this->loadRevisionIds();
return $this->mOldid;
}
@@ -131,6 +120,7 @@ class DifferenceEngine {
* @return Bool|int
*/
function getNewid() {
+ $this->loadRevisionIds();
return $this->mNewid;
}
@@ -142,8 +132,7 @@ class DifferenceEngine {
* @return mixed URL or false
*/
function deletedLink( $id ) {
- global $wgUser;
- if ( $wgUser->isAllowed( 'deletedhistory' ) ) {
+ if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow('archive', '*',
array( 'ar_rev_id' => $id ),
@@ -176,281 +165,214 @@ class DifferenceEngine {
}
function showDiffPage( $diffOnly = false ) {
- global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol;
wfProfileIn( __METHOD__ );
# Allow frames except in certain special cases
- $wgOut->allowClickjacking();
-
- # If external diffs are enabled both globally and for the user,
- # we'll use the application/x-external-editor interface to call
- # an external diff tool like kompare, kdiff3, etc.
- if ( $wgUseExternalEditor && $wgUser->getOption( 'externaldiff' ) ) {
- global $wgCanonicalServer, $wgScript, $wgLang;
- $wgOut->disable();
- header ( "Content-type: application/x-external-editor; charset=UTF-8" );
- $url1 = $this->mTitle->getCanonical( array(
- 'action' => 'raw',
- 'oldid' => $this->mOldid
- ) );
- $url2 = $this->mTitle->getCanonical( array(
- 'action' => 'raw',
- 'oldid' => $this->mNewid
- ) );
- $special = $wgLang->getNsText( NS_SPECIAL );
- $control = <<<CONTROL
- [Process]
- Type=Diff text
- Engine=MediaWiki
- Script={$wgCanonicalServer}{$wgScript}
- Special namespace={$special}
-
- [File]
- Extension=wiki
- URL=$url1
-
- [File 2]
- Extension=wiki
- URL=$url2
-CONTROL;
- echo( $control );
-
- wfProfileOut( __METHOD__ );
- return;
- }
+ $out = $this->getOutput();
+ $out->allowClickjacking();
+ $out->setRobotPolicy( 'noindex,nofollow' );
- $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' ),
+ $t = $this->getTitle()->getPrefixedText();
+ $d = $this->msg( 'missingarticle-diff',
$this->deletedIdMarker( $this->mOldid ),
- $this->deletedIdMarker( $this->mNewid ) );
- $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
- $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", "<span class='plainlinks'>$d</span>" );
+ $this->deletedIdMarker( $this->mNewid ) )->escaped();
+ $out->setPageTitle( $this->msg( 'errorpagetitle' ) );
+ $out->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", "<span class='plainlinks'>$d</span>" );
wfProfileOut( __METHOD__ );
return;
}
- wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
-
- if ( $this->mNewRev->isCurrent() ) {
- $wgOut->setArticleFlag( true );
+ $user = $this->getUser();
+ $permErrors = $this->mNewPage->getUserPermissionsErrors( 'read', $user );
+ if ( $this->mOldPage ) { # mOldPage might not be set, see below.
+ $permErrors = wfMergeErrorArrays( $permErrors,
+ $this->mOldPage->getUserPermissionsErrors( 'read', $user ) );
}
-
- # mOldid is false if the difference engine is called with a "vague" query for
- # a diff between a version V and its previous version V' AND the version V
- # is the first version of that article. In that case, V' does not exist.
- if ( $this->mOldid === false ) {
- $this->showFirstRevision();
- $this->renderNewRevision(); // should we respect $diffOnly here or not?
+ if ( count( $permErrors ) ) {
wfProfileOut( __METHOD__ );
- return;
+ throw new PermissionsError( 'read', $permErrors );
}
- $oldTitle = $this->mOldPage->getPrefixedText();
- $newTitle = $this->mNewPage->getPrefixedText();
- if ( $oldTitle == $newTitle ) {
- $wgOut->setPageTitle( $newTitle );
- } else {
- $wgOut->setPageTitle( $oldTitle . ', ' . $newTitle );
- }
- if ( $this->mNewPage->equals( $this->mOldPage ) ) {
- $wgOut->setSubtitle( wfMsgExt( 'difference', array( 'parseinline' ) ) );
- } else {
- $wgOut->setSubtitle( wfMsgExt( 'difference-multipage', array( 'parseinline' ) ) );
- }
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
+ # If external diffs are enabled both globally and for the user,
+ # we'll use the application/x-external-editor interface to call
+ # an external diff tool like kompare, kdiff3, etc.
+ if ( ExternalEdit::useExternalEngine( $this->getContext(), 'diff' ) ) {
+ $urls = array(
+ 'File' => array( 'Extension' => 'wiki', 'URL' =>
+ # This should be mOldPage, but it may not be set, see below.
+ $this->mNewPage->getCanonicalURL( array(
+ 'action' => 'raw', 'oldid' => $this->mOldid ) )
+ ),
+ 'File2' => array( 'Extension' => 'wiki', 'URL' =>
+ $this->mNewPage->getCanonicalURL( array(
+ 'action' => 'raw', 'oldid' => $this->mNewid ) )
+ ),
+ );
+
+ $externalEditor = new ExternalEdit( $this->getContext(), $urls );
+ $externalEditor->execute();
- if ( !$this->mOldPage->userCanRead() || !$this->mNewPage->userCanRead() ) {
- $wgOut->loginToUse();
- $wgOut->output();
- $wgOut->disable();
wfProfileOut( __METHOD__ );
return;
}
- $sk = $wgUser->getSkin();
- if ( method_exists( $sk, 'suppressQuickbar' ) ) {
- $sk->suppressQuickbar();
+ $rollback = '';
+ $undoLink = '';
+
+ $query = array();
+ # Carry over 'diffonly' param via navigation links
+ if ( $diffOnly != $user->getBoolOption( 'diffonly' ) ) {
+ $query['diffonly'] = $diffOnly;
}
+ # Cascade unhide param in links for easy deletion browsing
+ if ( $this->unhide ) {
+ $query['unhide'] = 1;
+ }
+
+ # Check if one of the revisions is deleted/suppressed
+ $deleted = $suppressed = false;
+ $allowed = $this->mNewRev->userCan( Revision::DELETED_TEXT, $user );
- // Check if page is editable
- $editable = $this->mNewRev->getTitle()->userCan( 'edit' );
- if ( $editable && $this->mNewRev->isCurrent() && $wgUser->isAllowed( 'rollback' ) ) {
- $wgOut->preventClickjacking();
- $rollback = '&#160;&#160;&#160;' . $sk->generateRollback( $this->mNewRev );
+ # mOldRev is false if the difference engine is called with a "vague" query for
+ # a diff between a version V and its previous version V' AND the version V
+ # is the first version of that article. In that case, V' does not exist.
+ if ( $this->mOldRev === false ) {
+ $out->setPageTitle( $this->mNewPage->getPrefixedText() );
+ $out->addSubtitle( $this->msg( 'difference' ) );
+ $samePage = true;
+ $oldHeader = '';
} else {
- $rollback = '';
- }
+ wfRunHooks( 'DiffViewHeader', array( $this, $this->mOldRev, $this->mNewRev ) );
- // Prepare a change patrol link, if applicable
- if ( $wgUseRCPatrol && $this->mTitle->userCan( 'patrol' ) ) {
- // If we've been given an explicit change identifier, use it; saves time
- if ( $this->mRcidMarkPatrolled ) {
- $rcid = $this->mRcidMarkPatrolled;
- $rc = RecentChange::newFromId( $rcid );
- // Already patrolled?
- $rcid = is_object( $rc ) && !$rc->getAttribute( 'rc_patrolled' ) ? $rcid : 0;
+ $sk = $this->getSkin();
+ if ( method_exists( $sk, 'suppressQuickbar' ) ) {
+ $sk->suppressQuickbar();
+ }
+
+ if ( $this->mNewPage->equals( $this->mOldPage ) ) {
+ $out->setPageTitle( $this->mNewPage->getPrefixedText() );
+ $out->addSubtitle( $this->msg( 'difference' ) );
+ $samePage = true;
} else {
- // Look for an unpatrolled change corresponding to this diff
- $db = wfGetDB( DB_SLAVE );
- $change = RecentChange::newFromConds(
- array(
- // Redundant user,timestamp condition so we can use the existing index
- 'rc_user_text' => $this->mNewRev->getRawUserText(),
- 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
- 'rc_this_oldid' => $this->mNewid,
- 'rc_last_oldid' => $this->mOldid,
- 'rc_patrolled' => 0
- ),
- __METHOD__
- );
- if ( $change instanceof RecentChange ) {
- $rcid = $change->mAttribs['rc_id'];
- $this->mRcidMarkPatrolled = $rcid;
- } else {
- // None found
- $rcid = 0;
+ $out->setPageTitle( $this->mOldPage->getPrefixedText() . ', ' . $this->mNewPage->getPrefixedText() );
+ $out->addSubtitle( $this->msg( 'difference-multipage' ) );
+ $samePage = false;
+ }
+
+ if ( $samePage && $this->mNewPage->userCan( 'edit', $user ) ) {
+ if ( $this->mNewRev->isCurrent() && $this->mNewPage->userCan( 'rollback', $user ) ) {
+ $out->preventClickjacking();
+ $rollback = '&#160;&#160;&#160;' . Linker::generateRollback( $this->mNewRev );
+ }
+ if ( !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $undoLink = ' ' . $this->msg( 'parentheses' )->rawParams(
+ Html::element( 'a', array(
+ 'href' => $this->mNewPage->getLocalUrl( array(
+ 'action' => 'edit',
+ 'undoafter' => $this->mOldid,
+ 'undo' => $this->mNewid ) ),
+ 'title' => Linker::titleAttrib( 'undo' )
+ ),
+ $this->msg( 'editundo' )->text()
+ ) )->escaped();
}
}
- // Build the link
- if ( $rcid ) {
- $wgOut->preventClickjacking();
- $token = $wgUser->editToken( $rcid );
- $patrol = ' <span class="patrollink">[' . $sk->link(
- $this->mTitle,
- wfMsgHtml( 'markaspatrolleddiff' ),
- array(),
- array(
- 'action' => 'markpatrolled',
- 'rcid' => $rcid,
- 'token' => $token,
- ),
- array(
- 'known',
- 'noclasses'
- )
- ) . ']</span>';
+
+ # Make "previous revision link"
+ if ( $samePage && $this->mOldRev->getPrevious() ) {
+ $prevlink = Linker::linkKnown(
+ $this->mOldPage,
+ $this->msg( 'previousdiff' )->escaped(),
+ array( 'id' => 'differences-prevlink' ),
+ array( 'diff' => 'prev', 'oldid' => $this->mOldid ) + $query
+ );
} else {
- $patrol = '';
+ $prevlink = '&#160;';
}
- } else {
- $patrol = '';
- }
- # Carry over 'diffonly' param via navigation links
- if ( $diffOnly != $wgUser->getBoolOption( 'diffonly' ) ) {
- $query['diffonly'] = $diffOnly;
- }
+ if ( $this->mOldRev->isMinor() ) {
+ $oldminor = ChangesList::flag( 'minor' );
+ } else {
+ $oldminor = '';
+ }
- # Make "previous revision link"
- $query['diff'] = 'prev';
- $query['oldid'] = $this->mOldid;
- # Cascade unhide param in links for easy deletion browsing
- if ( $this->unhide ) {
- $query['unhide'] = 1;
- }
- if ( !$this->mOldRev->getPrevious() ) {
- $prevlink = '&#160;';
- } else {
- $prevlink = $sk->link(
- $this->mTitle,
- wfMsgHtml( 'previousdiff' ),
- array(
- 'id' => 'differences-prevlink'
- ),
- $query,
- array(
- 'known',
- 'noclasses'
- )
- );
+ $ldel = $this->revisionDeleteLink( $this->mOldRev );
+ $oldRevisionHeader = $this->getRevisionHeader( $this->mOldRev, 'complete' );
+
+ $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $oldRevisionHeader . '</strong></div>' .
+ '<div id="mw-diff-otitle2">' .
+ Linker::revUserTools( $this->mOldRev, !$this->unhide ) . '</div>' .
+ '<div id="mw-diff-otitle3">' . $oldminor .
+ Linker::revComment( $this->mOldRev, !$diffOnly, !$this->unhide ) . $ldel . '</div>' .
+ '<div id="mw-diff-otitle4">' . $prevlink . '</div>';
+
+ if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $deleted = true; // old revisions text is hidden
+ if ( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) ) {
+ $suppressed = true; // also suppressed
+ }
+ }
+
+ # Check if this user can see the revisions
+ if ( !$this->mOldRev->userCan( Revision::DELETED_TEXT, $user ) ) {
+ $allowed = false;
+ }
}
# Make "next revision link"
- $query['diff'] = 'next';
- $query['oldid'] = $this->mNewid;
# Skip next link on the top revision
- if ( $this->mNewRev->isCurrent() ) {
- $nextlink = '&#160;';
- } else {
- $nextlink = $sk->link(
- $this->mTitle,
- wfMsgHtml( 'nextdiff' ),
- array(
- 'id' => 'differences-nextlink'
- ),
- $query,
- array(
- 'known',
- 'noclasses'
- )
+ if ( $samePage && !$this->mNewRev->isCurrent() ) {
+ $nextlink = Linker::linkKnown(
+ $this->mNewPage,
+ $this->msg( 'nextdiff' )->escaped(),
+ array( 'id' => 'differences-nextlink' ),
+ array( 'diff' => 'next', 'oldid' => $this->mNewid ) + $query
);
+ } else {
+ $nextlink = '&#160;';
}
- $oldminor = '';
- $newminor = '';
-
- if ( $this->mOldRev->isMinor() ) {
- $oldminor = ChangesList::flag( 'minor' );
- }
if ( $this->mNewRev->isMinor() ) {
$newminor = ChangesList::flag( 'minor' );
+ } else {
+ $newminor = '';
}
# Handle RevisionDelete links...
- $ldel = $this->revisionDeleteLink( $this->mOldRev );
$rdel = $this->revisionDeleteLink( $this->mNewRev );
+ $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) . $undoLink;
- $oldHeader = '<div id="mw-diff-otitle1"><strong>' . $this->mOldtitle . '</strong></div>' .
- '<div id="mw-diff-otitle2">' .
- $sk->revUserTools( $this->mOldRev, !$this->unhide ) . '</div>' .
- '<div id="mw-diff-otitle3">' . $oldminor .
- $sk->revComment( $this->mOldRev, !$diffOnly, !$this->unhide ) . $ldel . '</div>' .
- '<div id="mw-diff-otitle4">' . $prevlink . '</div>';
- $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $this->mNewtitle . '</strong></div>' .
- '<div id="mw-diff-ntitle2">' . $sk->revUserTools( $this->mNewRev, !$this->unhide ) .
+ $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader . '</strong></div>' .
+ '<div id="mw-diff-ntitle2">' . Linker::revUserTools( $this->mNewRev, !$this->unhide ) .
" $rollback</div>" .
'<div id="mw-diff-ntitle3">' . $newminor .
- $sk->revComment( $this->mNewRev, !$diffOnly, !$this->unhide ) . $rdel . '</div>' .
- '<div id="mw-diff-ntitle4">' . $nextlink . $patrol . '</div>';
+ Linker::revComment( $this->mNewRev, !$diffOnly, !$this->unhide ) . $rdel . '</div>' .
+ '<div id="mw-diff-ntitle4">' . $nextlink . $this->markPatrolledLink() . '</div>';
- # Check if this user can see the revisions
- $allowed = $this->mOldRev->userCan( Revision::DELETED_TEXT )
- && $this->mNewRev->userCan( Revision::DELETED_TEXT );
- # Check if one of the revisions is deleted/suppressed
- $deleted = $suppressed = false;
- if ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
- $deleted = true; // old revisions text is hidden
- if ( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) )
- $suppressed = true; // also suppressed
- }
if ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
$deleted = true; // new revisions text is hidden
if ( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) )
$suppressed = true; // also suppressed
}
+
# If the diff cannot be shown due to a deleted revision, then output
# the diff header and links to unhide (if available)...
if ( $deleted && ( !$this->unhide || !$allowed ) ) {
$this->showDiffStyle();
$multi = $this->getMultiNotice();
- $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) );
+ $out->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) );
if ( !$allowed ) {
$msg = $suppressed ? 'rev-suppressed-no-diff' : 'rev-deleted-no-diff';
# Give explanation for why revision is not visible
- $wgOut->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
+ $out->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...
- $link = $this->mTitle->getFullUrl( array(
- 'diff' => $this->mNewid,
- 'oldid' => $this->mOldid,
- 'unhide' => 1
- ) );
+ $link = $this->getTitle()->getFullUrl( $this->getRequest()->appendQueryValue( 'unhide', '1', true ) );
$msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff';
- $wgOut->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n", array( $msg, $link ) );
+ $out->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n", array( $msg, $link ) );
}
# Otherwise, output a regular diff...
} else {
@@ -458,7 +380,7 @@ CONTROL;
$notice = '';
if ( $deleted ) {
$msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view';
- $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" . wfMsgExt( $msg, 'parseinline' ) . "</div>\n";
+ $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" . $this->msg( $msg )->parse() . "</div>\n";
}
$this->showDiff( $oldHeader, $newHeader, $notice );
if ( !$diffOnly ) {
@@ -469,29 +391,79 @@ CONTROL;
}
/**
- * @param $rev Revision
+ * Get a link to mark the change as patrolled, or '' if there's either no
+ * revision to patrol or the user is not allowed to to it.
+ * Side effect: this method will call OutputPage::preventClickjacking()
+ * when a link is builded.
+ *
* @return String
*/
- protected function revisionDeleteLink( $rev ) {
- global $wgUser;
- $link = '';
- $canHide = $wgUser->isAllowed( 'deleterevision' );
- // Show del/undel link if:
- // (a) the user can delete revisions, or
- // (b) the user can view deleted revision *and* this one is deleted
- if ( $canHide || ( $rev->getVisibility() && $wgUser->isAllowed( 'deletedhistory' ) ) ) {
- $sk = $wgUser->getSkin();
- if ( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
- $link = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
+ protected function markPatrolledLink() {
+ global $wgUseRCPatrol;
+
+ if ( $this->mMarkPatrolledLink === null ) {
+ // Prepare a change patrol link, if applicable
+ if ( $wgUseRCPatrol && $this->mNewPage->userCan( 'patrol', $this->getUser() ) ) {
+ // If we've been given an explicit change identifier, use it; saves time
+ if ( $this->mRcidMarkPatrolled ) {
+ $rcid = $this->mRcidMarkPatrolled;
+ $rc = RecentChange::newFromId( $rcid );
+ // Already patrolled?
+ $rcid = is_object( $rc ) && !$rc->getAttribute( 'rc_patrolled' ) ? $rcid : 0;
+ } else {
+ // Look for an unpatrolled change corresponding to this diff
+ $db = wfGetDB( DB_SLAVE );
+ $change = RecentChange::newFromConds(
+ array(
+ // Redundant user,timestamp condition so we can use the existing index
+ 'rc_user_text' => $this->mNewRev->getRawUserText(),
+ 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
+ 'rc_this_oldid' => $this->mNewid,
+ 'rc_last_oldid' => $this->mOldid,
+ 'rc_patrolled' => 0
+ ),
+ __METHOD__
+ );
+ if ( $change instanceof RecentChange ) {
+ $rcid = $change->mAttribs['rc_id'];
+ $this->mRcidMarkPatrolled = $rcid;
+ } else {
+ // None found
+ $rcid = 0;
+ }
+ }
+ // Build the link
+ if ( $rcid ) {
+ $this->getOutput()->preventClickjacking();
+ $token = $this->getUser()->getEditToken( $rcid );
+ $this->mMarkPatrolledLink = ' <span class="patrollink">[' . Linker::linkKnown(
+ $this->mNewPage,
+ $this->msg( 'markaspatrolleddiff' )->escaped(),
+ array(),
+ array(
+ 'action' => 'markpatrolled',
+ 'rcid' => $rcid,
+ 'token' => $token,
+ )
+ ) . ']</span>';
+ } else {
+ $this->mMarkPatrolledLink = '';
+ }
} else {
- $query = array(
- 'type' => 'revision',
- 'target' => $rev->mTitle->getPrefixedDbkey(),
- 'ids' => $rev->getId()
- );
- $link = $sk->revDeleteLink( $query,
- $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
+ $this->mMarkPatrolledLink = '';
}
+ }
+
+ return $this->mMarkPatrolledLink;
+ }
+
+ /**
+ * @param $rev Revision
+ * @return String
+ */
+ protected function revisionDeleteLink( $rev ) {
+ $link = Linker::getRevDeleteLink( $this->getUser(), $rev, $rev->getTitle() );
+ if ( $link !== '' ) {
$link = '&#160;&#160;&#160;' . $link . ' ';
}
return $link;
@@ -501,156 +473,81 @@ CONTROL;
* Show the new revision of the page.
*/
function renderNewRevision() {
- global $wgOut, $wgUser;
wfProfileIn( __METHOD__ );
+ $out = $this->getOutput();
+ $revHeader = $this->getRevisionHeader( $this->mNewRev );
# Add "current version as of X" title
- $wgOut->addHTML( "<hr class='diff-hr' />
- <h2 class='diff-currentversion-title'>{$this->mPagetitle}</h2>\n" );
+ $out->addHTML( "<hr class='diff-hr' />
+ <h2 class='diff-currentversion-title'>{$revHeader}</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;
- }
-
+ if ( wfRunHooks( 'ArticleContentOnDiff', array( $this, $out ) ) ) {
$this->loadNewText();
- $wgOut->setRevisionId( $this->mNewRev->getId() );
+ $out->setRevisionId( $this->mNewid );
+ $out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
+ $out->setArticleFlag( true );
- if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
+ if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->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 ) ) ) {
+ if ( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
// 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" );
+ preg_match( '!\.(css|js)$!u', $this->mNewPage->getText(), $m );
+ $out->addHTML( "<pre class=\"mw-code mw-{$m[1]}\" dir=\"ltr\">\n" );
+ $out->addHTML( htmlspecialchars( $this->mNewtext ) );
+ $out->addHTML( "\n</pre>\n" );
}
- } elseif ( $pCache ) {
- $article = new Article( $this->mTitle, 0 );
- $pOutput = ParserCache::singleton()->get( $article, $wgOut->parserOptions() );
- if( $pOutput ) {
- $wgOut->addParserOutput( $pOutput );
+ } elseif ( !wfRunHooks( 'ArticleViewCustom', array( $this->mNewtext, $this->mNewPage, $out ) ) ) {
+ // Handled by extension
+ } else {
+ // Normal page
+ if ( $this->getTitle()->equals( $this->mNewPage ) ) {
+ // If the Title stored in the context is the same as the one
+ // of the new revision, we can use its associated WikiPage
+ // object.
+ $wikiPage = $this->getWikiPage();
} else {
- $article->doViewParse();
+ // Otherwise we need to create our own WikiPage object
+ $wikiPage = WikiPage::factory( $this->mNewPage );
}
- } else {
- $wgOut->addWikiTextTidy( $this->mNewtext );
- }
- if ( !$this->mNewRev->isCurrent() ) {
- $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting );
- }
- }
- # Add redundant patrol link on bottom...
- if ( $this->mRcidMarkPatrolled && $this->mTitle->quickUserCan( 'patrol' ) ) {
- $sk = $wgUser->getSkin();
- $token = $wgUser->editToken( $this->mRcidMarkPatrolled );
- $wgOut->preventClickjacking();
- $wgOut->addHTML(
- "<div class='patrollink'>[" . $sk->link(
- $this->mTitle,
- wfMsgHtml( 'markaspatrolleddiff' ),
- array(),
- array(
- 'action' => 'markpatrolled',
- 'rcid' => $this->mRcidMarkPatrolled,
- 'token' => $token,
- )
- ) . ']</div>'
- );
- }
+ $parserOptions = ParserOptions::newFromContext( $this->getContext() );
+ $parserOptions->enableLimitReport();
+ $parserOptions->setTidy( true );
- wfProfileOut( __METHOD__ );
- }
-
- /**
- * Show the first revision of an article. Uses normal diff headers in
- * contrast to normal "old revision" display style.
- */
- function showFirstRevision() {
- global $wgOut, $wgUser;
- wfProfileIn( __METHOD__ );
-
- # Get article text from the DB
- #
- if ( ! $this->loadNewText() ) {
- $t = $this->mTitle->getPrefixedText();
- $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ),
- $this->deletedIdMarker( $this->mOldid ),
- $this->deletedIdMarker( $this->mNewid ) );
- $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) );
- $wgOut->addWikiMsg( 'missing-article', "<nowiki>$t</nowiki>", "<span class='plainlinks'>$d</span>" );
- wfProfileOut( __METHOD__ );
- return;
- }
- if ( $this->mNewRev->isCurrent() ) {
- $wgOut->setArticleFlag( true );
- }
-
- # Check if user is allowed to look at this page. If not, bail out.
- #
- if ( !$this->mTitle->userCanRead() ) {
- $wgOut->loginToUse();
- $wgOut->output();
- wfProfileOut( __METHOD__ );
- throw new MWException( "Permission Error: you do not have access to view this page" );
- }
+ if ( !$this->mNewRev->isCurrent() ) {
+ $parserOptions->setEditSection( false );
+ }
- # Prepare the header box
- #
- $sk = $wgUser->getSkin();
+ $parserOutput = $wikiPage->getParserOutput( $parserOptions, $this->mNewid );
- $next = $this->mTitle->getNextRevisionID( $this->mNewid );
- if ( !$next ) {
- $nextlink = '';
- } else {
- $nextlink = '<br />' . $sk->link(
- $this->mTitle,
- wfMsgHtml( 'nextdiff' ),
- array(
- 'id' => 'differences-nextlink'
- ),
- array(
- 'diff' => 'next',
- 'oldid' => $this->mNewid,
- ),
- array(
- 'known',
- 'noclasses'
- )
- );
+ # WikiPage::getParserOutput() should not return false, but just in case
+ if( $parserOutput ) {
+ $out->addParserOutput( $parserOutput );
+ }
+ }
}
- $header = "<div class=\"firstrevisionheader\" style=\"text-align: center\">" .
- $sk->revUserTools( $this->mNewRev ) . "<br />" . $sk->revComment( $this->mNewRev ) . $nextlink . "</div>\n";
-
- $wgOut->addHTML( $header );
-
- $wgOut->setSubtitle( wfMsgExt( 'difference', array( 'parseinline' ) ) );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
+ # Add redundant patrol link on bottom...
+ $out->addHTML( $this->markPatrolledLink() );
wfProfileOut( __METHOD__ );
}
/**
- * Get the diff text, send it to $wgOut
+ * Get the diff text, send it to the OutputPage object
* Returns false if the diff could not be generated, otherwise returns true
*
* @return bool
*/
function showDiff( $otitle, $ntitle, $notice = '' ) {
- global $wgOut;
$diff = $this->getDiff( $otitle, $ntitle, $notice );
if ( $diff === false ) {
- $wgOut->addWikiMsg( 'missing-article', "<nowiki>(fixme, bug)</nowiki>", '' );
+ $this->getOutput()->addWikiMsg( 'missing-article', "<nowiki>(fixme, bug)</nowiki>", '' );
return false;
} else {
$this->showDiffStyle();
- $wgOut->addHTML( $diff );
+ $this->getOutput()->addHTML( $diff );
return true;
}
}
@@ -659,8 +556,7 @@ CONTROL;
* Add style sheets and supporting JS for diff display.
*/
function showDiffStyle() {
- global $wgOut;
- $wgOut->addModuleStyles( 'mediawiki.action.history.diff' );
+ $this->getOutput()->addModuleStyles( 'mediawiki.action.history.diff' );
}
/**
@@ -694,16 +590,17 @@ CONTROL;
if ( !$this->loadRevisionData() ) {
wfProfileOut( __METHOD__ );
return false;
- } elseif ( $this->mOldRev && !$this->mOldRev->userCan( Revision::DELETED_TEXT ) ) {
+ } elseif ( $this->mOldRev && !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
wfProfileOut( __METHOD__ );
return false;
- } elseif ( $this->mNewRev && !$this->mNewRev->userCan( Revision::DELETED_TEXT ) ) {
+ } elseif ( $this->mNewRev && !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
wfProfileOut( __METHOD__ );
return false;
}
// Short-circuit
- if ( $this->mOldRev && $this->mNewRev
- && $this->mOldRev->getID() == $this->mNewRev->getID() )
+ // If mOldRev is false, it means that the
+ if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev
+ && $this->mOldRev->getID() == $this->mNewRev->getID() ) )
{
wfProfileOut( __METHOD__ );
return '';
@@ -878,9 +775,8 @@ CONTROL;
}
function localiseLineNumbersCb( $matches ) {
- global $wgLang;
if ( $matches[1] === '1' && $this->mReducedLineNumbers ) return '';
- return wfMsgExt( 'lineno', 'escape', $wgLang->formatNum( $matches[1] ) );
+ return $this->msg( 'lineno' )->numParams( $matches[1] )->escaped();
}
@@ -904,10 +800,10 @@ CONTROL;
$newRev = $this->mNewRev;
}
- $nEdits = $this->mTitle->countRevisionsBetween( $oldRev, $newRev );
+ $nEdits = $this->mNewPage->countRevisionsBetween( $oldRev, $newRev );
if ( $nEdits > 0 ) {
$limit = 100; // use diff-multi-manyusers if too many users
- $numUsers = $this->mTitle->countAuthorsBetween( $oldRev, $newRev, $limit );
+ $numUsers = $this->mNewPage->countAuthorsBetween( $oldRev, $newRev, $limit );
return self::intermediateEditsMsg( $nEdits, $numUsers, $limit );
}
return ''; // nothing
@@ -921,15 +817,63 @@ CONTROL;
* @return string
*/
public static function intermediateEditsMsg( $numEdits, $numUsers, $limit ) {
- global $wgLang;
if ( $numUsers > $limit ) {
$msg = 'diff-multi-manyusers';
$numUsers = $limit;
} else {
$msg = 'diff-multi';
}
- return wfMsgExt( $msg, 'parseinline',
- $wgLang->formatnum( $numEdits ), $wgLang->formatnum( $numUsers ) );
+ return wfMessage( $msg )->numParams( $numEdits, $numUsers )->parse();
+ }
+
+ /**
+ * Get a header for a specified revision.
+ *
+ * @param $rev Revision
+ * @param $complete String: 'complete' to get the header wrapped depending
+ * the visibility of the revision and a link to edit the page.
+ * @return String HTML fragment
+ */
+ private function getRevisionHeader( Revision $rev, $complete = '' ) {
+ $lang = $this->getLanguage();
+ $user = $this->getUser();
+ $revtimestamp = $rev->getTimestamp();
+ $timestamp = $lang->userTimeAndDate( $revtimestamp, $user );
+ $dateofrev = $lang->userDate( $revtimestamp, $user );
+ $timeofrev = $lang->userTime( $revtimestamp, $user );
+
+ $header = $this->msg(
+ $rev->isCurrent() ? 'currentrev-asof' : 'revisionasof',
+ $timestamp,
+ $dateofrev,
+ $timeofrev
+ )->escaped();
+
+ if ( $complete !== 'complete' ) {
+ return $header;
+ }
+
+ $title = $rev->getTitle();
+
+ $header = Linker::linkKnown( $title, $header, array(),
+ array( 'oldid' => $rev->getID() ) );
+
+ if ( $rev->userCan( Revision::DELETED_TEXT, $user ) ) {
+ $editQuery = array( 'action' => 'edit' );
+ if ( !$rev->isCurrent() ) {
+ $editQuery['oldid'] = $rev->getID();
+ }
+
+ $msg = $this->msg( $title->userCan( 'edit', $user ) ? 'editold' : 'viewsourceold' )->escaped();
+ $header .= ' (' . Linker::linkKnown( $title, $msg, array(), $editQuery ) . ')';
+ if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
+ }
+ } else {
+ $header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
+ }
+
+ return $header;
}
/**
@@ -938,28 +882,36 @@ CONTROL;
* @return string
*/
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() );
+ // shared.css sets diff in interface language/dir, but the actual content
+ // is often in a different language, mostly the page content language/dir
+ $tableClass = 'diff diff-contentalign-' . htmlspecialchars( $this->getDiffLang()->alignStart() );
$header = "<table class='$tableClass'>";
- if ( $diff ) { // Safari/Chrome show broken output if cols not used
+
+ if ( !$diff && !$otitle ) {
$header .= "
- <col class='diff-marker' />
- <col class='diff-content' />
- <col class='diff-marker' />
- <col class='diff-content' />";
- $colspan = 2;
- $multiColspan = 4;
+ <tr valign='top'>
+ <td class='diff-ntitle'>{$ntitle}</td>
+ </tr>";
+ $multiColspan = 1;
} else {
- $colspan = 1;
- $multiColspan = 2;
+ if ( $diff ) { // Safari/Chrome show broken output if cols not used
+ $header .= "
+ <col class='diff-marker' />
+ <col class='diff-content' />
+ <col class='diff-marker' />
+ <col class='diff-content' />";
+ $colspan = 2;
+ $multiColspan = 4;
+ } else {
+ $colspan = 1;
+ $multiColspan = 2;
+ }
+ $header .= "
+ <tr valign='top'>
+ <td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
+ <td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
+ </tr>";
}
- $header .= "
- <tr valign='top'>
- <td colspan='$colspan' class='diff-otitle'>{$otitle}</td>
- <td colspan='$colspan' class='diff-ntitle'>{$ntitle}</td>
- </tr>";
if ( $multi != '' ) {
$header .= "<tr><td colspan='{$multiColspan}' align='center' class='diff-multi'>{$multi}</td></tr>";
@@ -982,6 +934,50 @@ CONTROL;
}
/**
+ * Set the language in which the diff text is written
+ * (Defaults to page content language).
+ * @since 1.19
+ */
+ function setTextLanguage( $lang ) {
+ $this->mDiffLang = wfGetLangObj( $lang );
+ }
+
+ /**
+ * Load revision IDs
+ */
+ private function loadRevisionIds() {
+ if ( $this->mRevisionsIdsLoaded ) {
+ return;
+ }
+
+ $this->mRevisionsIdsLoaded = true;
+
+ $old = $this->mOldid;
+ $new = $this->mNewid;
+
+ if ( $new === 'prev' ) {
+ # Show diff between revision $old and the previous one.
+ # Get previous one from DB.
+ $this->mNewid = intval( $old );
+ $this->mOldid = $this->getTitle()->getPreviousRevisionID( $this->mNewid );
+ } elseif ( $new === 'next' ) {
+ # Show diff between revision $old and the next one.
+ # Get next one from DB.
+ $this->mOldid = intval( $old );
+ $this->mNewid = $this->getTitle()->getNextRevisionID( $this->mOldid );
+ if ( $this->mNewid === false ) {
+ # if no result, NewId points to the newest old revision. The only newer
+ # revision is cur, which is "0".
+ $this->mNewid = 0;
+ }
+ } else {
+ $this->mOldid = intval( $old );
+ $this->mNewid = intval( $new );
+ wfRunHooks( 'NewDifferenceEngine', array( $this->getTitle(), &$this->mOldid, &$this->mNewid, $old, $new ) );
+ }
+ }
+
+ /**
* Load revision metadata for the specified articles. If newid is 0, then compare
* the old article in oldid to the current article; if oldid is 0, then
* compare the current article to the immediately previous one (ignoring the
@@ -994,72 +990,27 @@ CONTROL;
* @return bool
*/
function loadRevisionData() {
- global $wgLang, $wgUser;
if ( $this->mRevisionsLoaded ) {
return true;
- } else {
- // Whether it succeeds or fails, we don't want to try again
- $this->mRevisionsLoaded = true;
}
+ // Whether it succeeds or fails, we don't want to try again
+ $this->mRevisionsLoaded = true;
+
+ $this->loadRevisionIds();
+
// Load the new revision object
$this->mNewRev = $this->mNewid
? Revision::newFromId( $this->mNewid )
- : Revision::newFromTitle( $this->mTitle );
+ : Revision::newFromTitle( $this->getTitle() );
+
if ( !$this->mNewRev instanceof Revision ) {
return false;
}
// Update the new revision ID in case it was 0 (makes life easier doing UI stuff)
$this->mNewid = $this->mNewRev->getId();
-
- // Check if page is editable
- $editable = $this->mNewRev->getTitle()->userCan( 'edit' );
-
- // Set assorted variables
- $timestamp = $wgLang->timeanddate( $this->mNewRev->getTimestamp(), true );
- $dateofrev = $wgLang->date( $this->mNewRev->getTimestamp(), true );
- $timeofrev = $wgLang->time( $this->mNewRev->getTimestamp(), true );
$this->mNewPage = $this->mNewRev->getTitle();
- if ( $this->mNewRev->isCurrent() ) {
- $newLink = $this->mNewPage->escapeLocalUrl( array(
- 'oldid' => $this->mNewid
- ) );
- $this->mPagetitle = htmlspecialchars( wfMsg(
- 'currentrev-asof',
- $timestamp,
- $dateofrev,
- $timeofrev
- ) );
- $newEdit = $this->mNewPage->escapeLocalUrl( array(
- 'action' => 'edit'
- ) );
-
- $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>";
- $this->mNewtitle .= " (<a href='$newEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
- } else {
- $newLink = $this->mNewPage->escapeLocalUrl( array(
- 'oldid' => $this->mNewid
- ) );
- $newEdit = $this->mNewPage->escapeLocalUrl( array(
- 'action' => 'edit',
- 'oldid' => $this->mNewid
- ) );
- $this->mPagetitle = htmlspecialchars( wfMsg(
- 'revisionasof',
- $timestamp,
- $dateofrev,
- $timeofrev
- ) );
-
- $this->mNewtitle = "<a href='$newLink'>{$this->mPagetitle}</a>";
- $this->mNewtitle .= " (<a href='$newEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
- }
- if ( !$this->mNewRev->userCan( Revision::DELETED_TEXT ) ) {
- $this->mNewtitle = "<span class='history-deleted'>{$this->mPagetitle}</span>";
- } elseif ( $this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
- $this->mNewtitle = "<span class='history-deleted'>{$this->mNewtitle}</span>";
- }
// Load the old revision object
$this->mOldRev = false;
@@ -1083,38 +1034,6 @@ CONTROL;
if ( $this->mOldRev ) {
$this->mOldPage = $this->mOldRev->getTitle();
-
- $t = $wgLang->timeanddate( $this->mOldRev->getTimestamp(), true );
- $dateofrev = $wgLang->date( $this->mOldRev->getTimestamp(), true );
- $timeofrev = $wgLang->time( $this->mOldRev->getTimestamp(), true );
- $oldLink = $this->mOldPage->escapeLocalUrl( array(
- 'oldid' => $this->mOldid
- ) );
- $oldEdit = $this->mOldPage->escapeLocalUrl( array(
- 'action' => 'edit',
- 'oldid' => $this->mOldid
- ) );
- $this->mOldPagetitle = htmlspecialchars( wfMsg( 'revisionasof', $t, $dateofrev, $timeofrev ) );
-
- $this->mOldtitle = "<a href='$oldLink'>{$this->mOldPagetitle}</a>"
- . " (<a href='$oldEdit'>" . wfMsgHtml( $editable ? 'editold' : 'viewsourceold' ) . "</a>)";
- // Add an "undo" link
- if ( $editable && !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
- $undoLink = Html::element( 'a', array(
- 'href' => $this->mNewPage->getLocalUrl( array(
- 'action' => 'edit',
- 'undoafter' => $this->mOldid,
- 'undo' => $this->mNewid ) ),
- 'title' => $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>';
- } elseif ( $this->mOldRev->isDeleted( Revision::DELETED_TEXT ) ) {
- $this->mOldtitle = '<span class="history-deleted">' . $this->mOldtitle . '</span>';
- }
}
return true;
diff --git a/includes/diff/WikiDiff3.php b/includes/diff/WikiDiff3.php
index 27d3d5b8..66727445 100644
--- a/includes/diff/WikiDiff3.php
+++ b/includes/diff/WikiDiff3.php
@@ -546,9 +546,12 @@ class WikiDiff3 {
}
// return the middle diagonal with maximal progress.
- return $max_progress[floor( $num_progress / 2 )];
+ return $max_progress[(int)floor( $num_progress / 2 )];
}
+ /**
+ * @return mixed
+ */
public function getLcsLength() {
if ( $this->heuristicUsed && !$this->lcsLengthCorrectedForHeuristic ) {
$this->lcsLengthCorrectedForHeuristic = true;
diff --git a/includes/extauth/vB.php b/includes/extauth/vB.php
index f516c423..0565a2e3 100644
--- a/includes/extauth/vB.php
+++ b/includes/extauth/vB.php
@@ -103,13 +103,14 @@ class ExternalUser_vB extends ExternalUser {
private function getDb() {
global $wgExternalAuthConf;
- return new Database(
- $wgExternalAuthConf['server'],
- $wgExternalAuthConf['username'],
- $wgExternalAuthConf['password'],
- $wgExternalAuthConf['dbname'],
- 0,
- $wgExternalAuthConf['tablePrefix']
+ return DatabaseBase::factory( 'mysql',
+ array(
+ 'host' => $wgExternalAuthConf['server'],
+ 'user' => $wgExternalAuthConf['username'],
+ 'password' => $wgExternalAuthConf['password'],
+ 'dbname' => $wgExternalAuthConf['dbname'],
+ 'tablePrefix' => $wgExternalAuthConf['tablePrefix'],
+ )
);
}
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php
index 2610ac6e..22dbdefc 100644
--- a/includes/filerepo/FSRepo.php
+++ b/includes/filerepo/FSRepo.php
@@ -7,710 +7,50 @@
*/
/**
- * A repository for files accessible via the local filesystem. Does not support
- * database access or registration.
+ * A repository for files accessible via the local filesystem.
+ * Does not support database access or registration.
+ *
+ * This is a mostly a legacy class. New uses should not be added.
+ *
* @ingroup FileRepo
+ * @deprecated since 1.19
*/
class FSRepo extends FileRepo {
- var $directory, $deletedDir, $deletedHashLevels, $fileMode;
- var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
- var $oldFileFactory = false;
- var $pathDisclosureProtection = 'simple';
-
- function __construct( $info ) {
- parent::__construct( $info );
-
- // Required settings
- $this->directory = $info['directory'];
- $this->url = $info['url'];
-
- // Optional settings
- $this->hashLevels = isset( $info['hashLevels'] ) ? $info['hashLevels'] : 2;
- $this->deletedHashLevels = isset( $info['deletedHashLevels'] ) ?
- $info['deletedHashLevels'] : $this->hashLevels;
- $this->deletedDir = isset( $info['deletedDir'] ) ? $info['deletedDir'] : false;
- $this->fileMode = isset( $info['fileMode'] ) ? $info['fileMode'] : 0644;
- if ( isset( $info['thumbDir'] ) ) {
- $this->thumbDir = $info['thumbDir'];
- } else {
- $this->thumbDir = "{$this->directory}/thumb";
- }
- if ( isset( $info['thumbUrl'] ) ) {
- $this->thumbUrl = $info['thumbUrl'];
- } else {
- $this->thumbUrl = "{$this->url}/thumb";
- }
- }
-
- /**
- * Get the public root directory of the repository.
- */
- function getRootDirectory() {
- return $this->directory;
- }
-
- /**
- * Get the public root URL of the repository
- */
- function getRootUrl() {
- return $this->url;
- }
-
- /**
- * Returns true if the repository uses a multi-level directory structure
- */
- function isHashed() {
- return (bool)$this->hashLevels;
- }
-
- /**
- * Get the local directory corresponding to one of the three basic zones
- *
- * @param $zone string
- *
- * @return string
- */
- function getZonePath( $zone ) {
- switch ( $zone ) {
- case 'public':
- return $this->directory;
- case 'temp':
- return "{$this->directory}/temp";
- case 'deleted':
- return $this->deletedDir;
- case 'thumb':
- return $this->thumbDir;
- default:
- return false;
- }
- }
-
- /**
- * @see FileRepo::getZoneUrl()
- *
- * @param $zone string
- *
- * @return url
- */
- function getZoneUrl( $zone ) {
- switch ( $zone ) {
- case 'public':
- return $this->url;
- case 'temp':
- return "{$this->url}/temp";
- case 'deleted':
- return parent::getZoneUrl( $zone ); // no public URL
- case 'thumb':
- return $this->thumbUrl;
- default:
- return parent::getZoneUrl( $zone );
- }
- }
-
- /**
- * 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;
- if ( $suffix !== false ) {
- $path .= '/' . rawurlencode( $suffix );
- }
- return $path;
- }
-
- /**
- * 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 protocol' );
- }
-
- $bits = explode( '/', substr( $url, 9 ), 3 );
- if ( count( $bits ) != 3 ) {
- throw new MWException( __METHOD__.": invalid mwrepo URL: $url" );
- }
- list( $repo, $zone, $rel ) = $bits;
- if ( $repo !== $this->name ) {
- throw new MWException( __METHOD__.": fetching from a foreign repo is not supported" );
- }
- $base = $this->getZonePath( $zone );
- if ( !$base ) {
- throw new MWException( __METHOD__.": invalid zone: $zone" );
- }
- return $base . '/' . rawurldecode( $rel );
- }
-
- /**
- * Store a batch of files
- *
- * @param $triplets Array: (src,zone,dest) triplets as per store()
- * @param $flags Integer: bitwise combination of the following flags:
- * self::DELETE_SOURCE Delete the source file after upload
- * self::OVERWRITE Overwrite an existing destination file instead of failing
- * self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
- * 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" );
- }
- if ( !$this->validateFilename( $dstRel ) ) {
- throw new MWException( 'Validation error in $dstRel' );
- }
- $dstPath = "$root/$dstRel";
- $dstDir = dirname( $dstPath );
-
- // Create destination directories for this triplet
- if ( !is_dir( $dstDir ) ) {
- if ( !wfMkdirParents( $dstDir ) ) {
- return $this->newFatal( 'directorycreateerror', $dstDir );
- }
- if ( $dstZone == 'deleted' ) {
- $this->initDeletedDir( $dstDir );
- }
- }
-
- // Resolve source
- if ( self::isVirtualUrl( $srcPath ) ) {
- $srcPath = $triplets[$i][0] = $this->resolveVirtualUrl( $srcPath );
- }
- if ( !is_file( $srcPath ) ) {
- // Make a list of files that don't exist for return to the caller
- $status->fatal( 'filenotfound', $srcPath );
- continue;
- }
-
- // Check overwriting
- if ( !( $flags & self::OVERWRITE ) && file_exists( $dstPath ) ) {
- if ( $flags & self::OVERWRITE_SAME ) {
- $hashSource = sha1_file( $srcPath );
- $hashDest = sha1_file( $dstPath );
- if ( $hashSource != $hashDest ) {
- $status->fatal( 'fileexistserror', $dstPath );
- }
- } else {
- $status->fatal( 'fileexistserror', $dstPath );
- }
- }
- }
-
- // Windows does not support moving over existing files, so explicitly delete them
- $deleteDest = wfIsWindows() && ( $flags & self::OVERWRITE );
-
- // Abort now on failure
- if ( !$status->ok ) {
- return $status;
- }
-
- // Execute the store operation for each triplet
- foreach ( $triplets as $i => $triplet ) {
- list( $srcPath, $dstZone, $dstRel ) = $triplet;
- $root = $this->getZonePath( $dstZone );
- $dstPath = "$root/$dstRel";
- $good = true;
-
- if ( $flags & self::DELETE_SOURCE ) {
- if ( $deleteDest ) {
- unlink( $dstPath );
- }
- if ( !rename( $srcPath, $dstPath ) ) {
- $status->error( 'filerenameerror', $srcPath, $dstPath );
- $good = false;
- }
- } else {
- if ( !copy( $srcPath, $dstPath ) ) {
- $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 );
- $status->successCount++;
- } 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( $toAppendPath ) ) {
- $toAppendPath = $this->resolveVirtualUrl( $toAppendPath );
- }
- // Make sure the files are there
- 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( $srcPath );
- if( $chunk === false ) {
- $status->fatal( 'fileappenderrorread', $srcPath );
- }
-
- if( $status->isOk() ) {
- if ( file_put_contents( $toAppendPath, $chunk, FILE_APPEND ) ) {
- $status->value = $toAppendPath;
- } else {
- $status->fatal( 'fileappenderror', $srcPath, $toAppendPath);
- }
- }
-
- if ( $flags & self::DELETE_SOURCE ) {
- unlink( $srcPath );
+ function __construct( array $info ) {
+ if ( !isset( $info['backend'] ) ) {
+ // B/C settings...
+ $directory = $info['directory'];
+ $deletedDir = isset( $info['deletedDir'] )
+ ? $info['deletedDir']
+ : false;
+ $thumbDir = isset( $info['thumbDir'] )
+ ? $info['thumbDir']
+ : "{$directory}/thumb";
+ $fileMode = isset( $info['fileMode'] )
+ ? $info['fileMode']
+ : 0644;
+
+ $repoName = $info['name'];
+ // Get the FS backend configuration
+ $backend = new FSFileBackend( array(
+ 'name' => $info['name'] . '-backend',
+ 'lockManager' => 'fsLockManager',
+ 'containerPaths' => array(
+ "{$repoName}-public" => "{$directory}",
+ "{$repoName}-temp" => "{$directory}/temp",
+ "{$repoName}-thumb" => $thumbDir,
+ "{$repoName}-deleted" => $deletedDir
+ ),
+ 'fileMode' => $fileMode,
+ ) );
+ // Update repo config to use this backend
+ $info['backend'] = $backend;
}
- return $status;
- }
-
- /* We can actually append to the files, so no-op needed here. */
- function appendFinish( $toAppendPath ) {}
-
- /**
- * Checks existence of specified array of files.
- *
- * @param $files Array: URLs of files to check
- * @param $flags Integer: bitwise combination of the following flags:
- * self::FILES_ONLY Mark file as existing only if it is a file (not directory)
- * @return Either array of files and existence flags, or false
- */
- function fileExistsBatch( $files, $flags = 0 ) {
- if ( !file_exists( $this->directory ) || !is_readable( $this->directory ) ) {
- return false;
- }
- $result = array();
- foreach ( $files as $key => $file ) {
- if ( self::isVirtualUrl( $file ) ) {
- $file = $this->resolveVirtualUrl( $file );
- }
- if( $flags & self::FILES_ONLY ) {
- $result[$key] = is_file( $file );
- } else {
- $result[$key] = file_exists( $file );
- }
- }
-
- return $result;
- }
-
- /**
- * Take all available measures to prevent web accessibility of new deleted
- * directories, in case the user has not configured offline storage
- */
- protected function initDeletedDir( $dir ) {
- // Add a .htaccess file to the root of the deleted zone
- $root = $this->getZonePath( 'deleted' );
- if ( !file_exists( "$root/.htaccess" ) ) {
- file_put_contents( "$root/.htaccess", "Deny from all\n" );
- }
- // Seed new directories with a blank index.html, to prevent crawling
- file_put_contents( "$dir/index.html", '' );
- }
-
- /**
- * Pick a random name in the temp zone and store a file to it.
- * @param $originalName String: the base name of the file as specified
- * by the user. The file extension will be maintained.
- * @param $srcPath String: the current location of the file.
- * @return FileRepoStatus object with the URL in the value.
- */
- function storeTemp( $originalName, $srcPath ) {
- $date = gmdate( "YmdHis" );
- $hashPath = $this->getHashPath( $originalName );
- $dstRel = "$hashPath$date!$originalName";
- $dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName );
-
- $result = $this->store( $srcPath, 'temp', $dstRel );
- $result->value = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
- return $result;
- }
-
- /**
- * 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
- */
- function freeTemp( $virtualUrl ) {
- $temp = "mwrepo://{$this->name}/temp";
- if ( substr( $virtualUrl, 0, strlen( $temp ) ) != $temp ) {
- wfDebug( __METHOD__.": Invalid virtual URL\n" );
- return false;
- }
- $path = $this->resolveVirtualUrl( $virtualUrl );
- wfSuppressWarnings();
- $success = unlink( $path );
- wfRestoreWarnings();
- return $success;
- }
-
- /**
- * Publish a batch of files
- * @param $triplets Array: (source,dest,archive) triplets as per publish()
- * @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
- * that the source files should be deleted if possible
- */
- function publishBatch( $triplets, $flags = 0 ) {
- // Perform initial checks
- 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 );
- }
- $status = $this->newGood( array() );
- foreach ( $triplets as $i => $triplet ) {
- list( $srcPath, $dstRel, $archiveRel ) = $triplet;
-
- if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) {
- $triplets[$i][0] = $srcPath = $this->resolveVirtualUrl( $srcPath );
- }
- if ( !$this->validateFilename( $dstRel ) ) {
- throw new MWException( 'Validation error in $dstRel' );
- }
- if ( !$this->validateFilename( $archiveRel ) ) {
- throw new MWException( 'Validation error in $archiveRel' );
- }
- $dstPath = "{$this->directory}/$dstRel";
- $archivePath = "{$this->directory}/$archiveRel";
-
- $dstDir = dirname( $dstPath );
- $archiveDir = dirname( $archivePath );
- // Abort immediately on directory creation errors since they're likely to be repetitive
- if ( !is_dir( $dstDir ) && !wfMkdirParents( $dstDir ) ) {
- return $this->newFatal( 'directorycreateerror', $dstDir );
- }
- if ( !is_dir( $archiveDir ) && !wfMkdirParents( $archiveDir ) ) {
- return $this->newFatal( 'directorycreateerror', $archiveDir );
- }
- if ( !is_file( $srcPath ) ) {
- // Make a list of files that don't exist for return to the caller
- $status->fatal( 'filenotfound', $srcPath );
- }
- }
-
- if ( !$status->ok ) {
- return $status;
- }
-
- foreach ( $triplets as $i => $triplet ) {
- list( $srcPath, $dstRel, $archiveRel ) = $triplet;
- $dstPath = "{$this->directory}/$dstRel";
- $archivePath = "{$this->directory}/$archiveRel";
-
- // Archive destination file if it exists
- if( is_file( $dstPath ) ) {
- // Check if the archive file exists
- // This is a sanity check to avoid data loss. In UNIX, the rename primitive
- // unlinks the destination file if it exists. DB-based synchronisation in
- // publishBatch's caller should prevent races. In Windows there's no
- // problem because the rename primitive fails if the destination exists.
- if ( is_file( $archivePath ) ) {
- $success = false;
- } else {
- wfSuppressWarnings();
- $success = rename( $dstPath, $archivePath );
- wfRestoreWarnings();
- }
-
- if( !$success ) {
- $status->error( 'filerenameerror',$dstPath, $archivePath );
- $status->failCount++;
- continue;
- } else {
- wfDebug(__METHOD__.": moved file $dstPath to $archivePath\n");
- }
- $status->value[$i] = 'archived';
- } else {
- $status->value[$i] = 'new';
- }
-
- $good = true;
- wfSuppressWarnings();
- if ( $flags & self::DELETE_SOURCE ) {
- if ( !rename( $srcPath, $dstPath ) ) {
- $status->error( 'filerenameerror', $srcPath, $dstPath );
- $good = false;
- }
- } else {
- if ( !copy( $srcPath, $dstPath ) ) {
- $status->error( 'filecopyerror', $srcPath, $dstPath );
- $good = false;
- }
- }
- wfRestoreWarnings();
-
- if ( $good ) {
- $status->successCount++;
- wfDebug(__METHOD__.": wrote tempfile $srcPath to $dstPath\n");
- // Thread-safe override for umask
- $this->chmod( $dstPath );
- } else {
- $status->failCount++;
- }
- }
- return $status;
- }
-
- /**
- * Move a group of files to the deletion archive.
- * If no valid deletion archive is configured, this may either delete the
- * file or throw an exception, depending on the preference of the repository.
- *
- * @param $sourceDestPairs Array of source/destination pairs. Each element
- * is a two-element array containing the source file path relative to the
- * public root in the first element, and the archive file path relative
- * to the deleted zone root in the second element.
- * @return FileRepoStatus
- */
- function deleteBatch( $sourceDestPairs ) {
- $status = $this->newGood();
- if ( !$this->deletedDir ) {
- throw new MWException( __METHOD__.': no valid deletion archive directory' );
- }
-
- /**
- * Validate filenames and create archive directories
- */
- foreach ( $sourceDestPairs as $pair ) {
- list( $srcRel, $archiveRel ) = $pair;
- if ( !$this->validateFilename( $srcRel ) ) {
- throw new MWException( __METHOD__.':Validation error in $srcRel' );
- }
- if ( !$this->validateFilename( $archiveRel ) ) {
- throw new MWException( __METHOD__.':Validation error in $archiveRel' );
- }
- $archivePath = "{$this->deletedDir}/$archiveRel";
- $archiveDir = dirname( $archivePath );
- if ( !is_dir( $archiveDir ) ) {
- if ( !wfMkdirParents( $archiveDir ) ) {
- $status->fatal( 'directorycreateerror', $archiveDir );
- continue;
- }
- $this->initDeletedDir( $archiveDir );
- }
- // Check if the archive directory is writable
- // This doesn't appear to work on NTFS
- if ( !is_writable( $archiveDir ) ) {
- $status->fatal( 'filedelete-archive-read-only', $archiveDir );
- }
- }
- if ( !$status->ok ) {
- // Abort early
- return $status;
- }
-
- /**
- * Move the files
- * We're now committed to returning an OK result, which will lead to
- * the files being moved in the DB also.
- */
- foreach ( $sourceDestPairs as $pair ) {
- list( $srcRel, $archiveRel ) = $pair;
- $srcPath = "{$this->directory}/$srcRel";
- $archivePath = "{$this->deletedDir}/$archiveRel";
- $good = true;
- if ( file_exists( $archivePath ) ) {
- # A file with this content hash is already archived
- wfSuppressWarnings();
- $good = unlink( $srcPath );
- wfRestoreWarnings();
- if ( !$good ) {
- $status->error( 'filedeleteerror', $srcPath );
- }
- } else{
- wfSuppressWarnings();
- $good = rename( $srcPath, $archivePath );
- wfRestoreWarnings();
- if ( !$good ) {
- $status->error( 'filerenameerror', $srcPath, $archivePath );
- } else {
- $this->chmod( $archivePath );
- }
- }
- if ( $good ) {
- $status->successCount++;
- } else {
- $status->failCount++;
- }
- }
- return $status;
- }
-
- /**
- * Get a relative path for a deletion archive key,
- * e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg
- */
- function getDeletedHashPath( $key ) {
- $path = '';
- for ( $i = 0; $i < $this->deletedHashLevels; $i++ ) {
- $path .= $key[$i] . '/';
- }
- return $path;
- }
-
- /**
- * Call a callback function for every file in the repository.
- * Uses the filesystem even in child classes.
- */
- function enumFilesInFS( $callback ) {
- $numDirs = 1 << ( $this->hashLevels * 4 );
- for ( $flatIndex = 0; $flatIndex < $numDirs; $flatIndex++ ) {
- $hexString = sprintf( "%0{$this->hashLevels}x", $flatIndex );
- $path = $this->directory;
- for ( $hexPos = 0; $hexPos < $this->hashLevels; $hexPos++ ) {
- $path .= '/' . substr( $hexString, 0, $hexPos + 1 );
- }
- if ( !file_exists( $path ) || !is_dir( $path ) ) {
- continue;
- }
- $dir = opendir( $path );
- if ($dir) {
- while ( false !== ( $name = readdir( $dir ) ) ) {
- call_user_func( $callback, $path . '/' . $name );
- }
- closedir( $dir );
- }
- }
- }
-
- /**
- * Call a callback function for every file in the repository
- * May use either the database or the filesystem
- */
- function enumFiles( $callback ) {
- $this->enumFilesInFS( $callback );
- }
-
- /**
- * Get properties of a file with a given virtual URL
- * The virtual URL must refer to this repo
- */
- function getFileProps( $virtualUrl ) {
- $path = $this->resolveVirtualUrl( $virtualUrl );
- return File::getPropsFromPath( $path );
- }
-
- /**
- * Path disclosure protection functions
- *
- * Get a callback function to use for cleaning error message parameters
- */
- function getErrorCleanupFunction() {
- switch ( $this->pathDisclosureProtection ) {
- case 'simple':
- $callback = array( $this, 'simpleClean' );
- break;
- default:
- $callback = parent::getErrorCleanupFunction();
- }
- return $callback;
- }
+ parent::__construct( $info );
- function simpleClean( $param ) {
- if ( !isset( $this->simpleCleanPairs ) ) {
- global $IP;
- $this->simpleCleanPairs = array(
- $this->directory => 'public',
- "{$this->directory}/temp" => 'temp',
- $IP => '$IP',
- dirname( __FILE__ ) => '$IP/extensions/WebStore',
- );
- if ( $this->deletedDir ) {
- $this->simpleCleanPairs[$this->deletedDir] = 'deleted';
- }
+ if ( !( $this->backend instanceof FSFileBackend ) ) {
+ throw new MWException( "FSRepo only supports FSFileBackend." );
}
- return strtr( $param, $this->simpleCleanPairs );
}
-
- /**
- * Chmod a file, supressing the warnings.
- * @param $path String: the path to change
- */
- protected function chmod( $path ) {
- wfSuppressWarnings();
- chmod( $path, $this->fileMode );
- wfRestoreWarnings();
- }
-
}
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index 843f09a9..8d4f2bd9 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -1,5 +1,13 @@
<?php
/**
+ * @defgroup FileRepo File Repository
+ *
+ * @brief This module handles how MediaWiki interacts with filesystems.
+ *
+ * @details
+ */
+
+/**
* Base code for file repositories.
*
* @file
@@ -7,61 +15,244 @@
*/
/**
- * Base class for file repositories.
- * Do not instantiate, use a derived class.
+ * Base class for file repositories
*
* @ingroup FileRepo
*/
-abstract class FileRepo {
+class FileRepo {
const FILES_ONLY = 1;
+
const DELETE_SOURCE = 1;
const OVERWRITE = 2;
const OVERWRITE_SAME = 4;
- const SKIP_VALIDATION = 8;
+ const SKIP_LOCKING = 8;
+
+ /** @var FileBackend */
+ protected $backend;
+ /** @var Array Map of zones to config */
+ protected $zones = array();
var $thumbScriptUrl, $transformVia404;
var $descBaseUrl, $scriptDirUrl, $scriptExtension, $articleUrl;
var $fetchDescription, $initialCapital;
- var $pathDisclosureProtection = 'paranoid';
- var $descriptionCacheExpiry, $hashLevels, $url, $thumbUrl;
+ var $pathDisclosureProtection = 'simple'; // 'paranoid'
+ var $descriptionCacheExpiry, $url, $thumbUrl;
+ var $hashLevels, $deletedHashLevels;
/**
* Factory functions for creating new files
* Override these in the base class
*/
- var $fileFactory = false, $oldFileFactory = false;
+ var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
+ var $oldFileFactory = false;
var $fileFactoryKey = false, $oldFileFactoryKey = false;
- function __construct( $info ) {
+ function __construct( Array $info = null ) {
+ // Verify required settings presence
+ if(
+ $info === null
+ || !array_key_exists( 'name', $info )
+ || !array_key_exists( 'backend', $info )
+ ) {
+ throw new MWException( __CLASS__ . " requires an array of options having both 'name' and 'backend' keys.\n" );
+ }
+
// Required settings
$this->name = $info['name'];
+ if ( $info['backend'] instanceof FileBackend ) {
+ $this->backend = $info['backend']; // useful for testing
+ } else {
+ $this->backend = FileBackendGroup::singleton()->get( $info['backend'] );
+ }
- // Optional settings
- $this->initialCapital = MWNamespace::isCapitalized( NS_FILE );
- foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
- 'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
- 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl', 'scriptExtension' )
- as $var )
- {
+ // Optional settings that can have no value
+ $optionalSettings = array(
+ 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
+ 'thumbScriptUrl', 'pathDisclosureProtection', 'descriptionCacheExpiry',
+ 'scriptExtension'
+ );
+ foreach ( $optionalSettings as $var ) {
if ( isset( $info[$var] ) ) {
$this->$var = $info[$var];
}
}
+
+ // Optional settings that have a default
+ $this->initialCapital = isset( $info['initialCapital'] )
+ ? $info['initialCapital']
+ : MWNamespace::isCapitalized( NS_FILE );
+ $this->url = isset( $info['url'] )
+ ? $info['url']
+ : false; // a subclass may set the URL (e.g. ForeignAPIRepo)
+ if ( isset( $info['thumbUrl'] ) ) {
+ $this->thumbUrl = $info['thumbUrl'];
+ } else {
+ $this->thumbUrl = $this->url ? "{$this->url}/thumb" : false;
+ }
+ $this->hashLevels = isset( $info['hashLevels'] )
+ ? $info['hashLevels']
+ : 2;
+ $this->deletedHashLevels = isset( $info['deletedHashLevels'] )
+ ? $info['deletedHashLevels']
+ : $this->hashLevels;
$this->transformVia404 = !empty( $info['transformVia404'] );
+ $this->zones = isset( $info['zones'] )
+ ? $info['zones']
+ : array();
+ // Give defaults for the basic zones...
+ foreach ( array( 'public', 'thumb', 'temp', 'deleted' ) as $zone ) {
+ if ( !isset( $this->zones[$zone] ) ) {
+ $this->zones[$zone] = array(
+ 'container' => "{$this->name}-{$zone}",
+ 'directory' => '' // container root
+ );
+ }
+ }
+ }
+
+ /**
+ * Get the file backend instance
+ *
+ * @return FileBackend
+ */
+ public function getBackend() {
+ return $this->backend;
+ }
+
+ /**
+ * Prepare a single zone or list of zones for usage.
+ * See initDeletedDir() for additional setup needed for the 'deleted' zone.
+ *
+ * @param $doZones Array Only do a particular zones
+ * @return Status
+ */
+ protected function initZones( $doZones = array() ) {
+ $status = $this->newGood();
+ foreach ( (array)$doZones as $zone ) {
+ $root = $this->getZonePath( $zone );
+ if ( $root === null ) {
+ throw new MWException( "No '$zone' zone defined in the {$this->name} repo." );
+ }
+ }
+ return $status;
+ }
+
+ /**
+ * Take all available measures to prevent web accessibility of new deleted
+ * directories, in case the user has not configured offline storage
+ *
+ * @param $dir string
+ * @return void
+ */
+ protected function initDeletedDir( $dir ) {
+ $this->backend->secure( // prevent web access & dir listings
+ array( 'dir' => $dir, 'noAccess' => true, 'noListing' => true ) );
}
/**
* Determine if a string is an mwrepo:// URL
*
* @param $url string
- *
* @return bool
*/
- static function isVirtualUrl( $url ) {
+ public static function isVirtualUrl( $url ) {
return substr( $url, 0, 9 ) == 'mwrepo://';
}
/**
+ * 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
+ */
+ public function getVirtualUrl( $suffix = false ) {
+ $path = 'mwrepo://' . $this->name;
+ if ( $suffix !== false ) {
+ $path .= '/' . rawurlencode( $suffix );
+ }
+ return $path;
+ }
+
+ /**
+ * Get the URL corresponding to one of the four basic zones
+ *
+ * @param $zone String: one of: public, deleted, temp, thumb
+ * @return String or false
+ */
+ public function getZoneUrl( $zone ) {
+ switch ( $zone ) {
+ case 'public':
+ return $this->url;
+ case 'temp':
+ return "{$this->url}/temp";
+ case 'deleted':
+ return false; // no public URL
+ case 'thumb':
+ return $this->thumbUrl;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Get the backend storage 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 protocol' );
+ }
+ $bits = explode( '/', substr( $url, 9 ), 3 );
+ if ( count( $bits ) != 3 ) {
+ throw new MWException( __METHOD__.": invalid mwrepo URL: $url" );
+ }
+ list( $repo, $zone, $rel ) = $bits;
+ if ( $repo !== $this->name ) {
+ throw new MWException( __METHOD__.": fetching from a foreign repo is not supported" );
+ }
+ $base = $this->getZonePath( $zone );
+ if ( !$base ) {
+ throw new MWException( __METHOD__.": invalid zone: $zone" );
+ }
+ return $base . '/' . rawurldecode( $rel );
+ }
+
+ /**
+ * The the storage container and base path of a zone
+ *
+ * @param $zone string
+ * @return Array (container, base path) or (null, null)
+ */
+ protected function getZoneLocation( $zone ) {
+ if ( !isset( $this->zones[$zone] ) ) {
+ return array( null, null ); // bogus
+ }
+ return array( $this->zones[$zone]['container'], $this->zones[$zone]['directory'] );
+ }
+
+ /**
+ * Get the storage path corresponding to one of the zones
+ *
+ * @param $zone string
+ * @return string|null
+ */
+ public function getZonePath( $zone ) {
+ list( $container, $base ) = $this->getZoneLocation( $zone );
+ if ( $container === null || $base === null ) {
+ return null;
+ }
+ $backendName = $this->backend->getName();
+ if ( $base != '' ) { // may not be set
+ $base = "/{$base}";
+ }
+ return "mwstore://$backendName/{$container}{$base}";
+ }
+
+ /**
* Create a new File object from the local repository
*
* @param $title Mixed: Title object or string
@@ -70,15 +261,12 @@ 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
+ * @return File|null A File, or null if passed an invalid Title
*/
- function newFile( $title, $time = false ) {
- if ( !( $title instanceof Title ) ) {
- $title = Title::makeTitleSafe( NS_FILE, $title );
- if ( !is_object( $title ) ) {
- return null;
- }
+ public function newFile( $title, $time = false ) {
+ $title = File::normalizeTitle( $title );
+ if ( !$title ) {
+ return null;
}
if ( $time ) {
if ( $this->oldFileFactory ) {
@@ -107,17 +295,14 @@ 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() ) {
- $time = isset( $options['time'] ) ? $options['time'] : false;
- if ( !($title instanceof Title) ) {
- $title = Title::makeTitleSafe( NS_FILE, $title );
- if ( !is_object( $title ) ) {
- return false;
- }
+ public function findFile( $title, $options = array() ) {
+ $title = File::normalizeTitle( $title );
+ if ( !$title ) {
+ return 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->newFile( $title );
if ( !$img ) {
@@ -143,12 +328,12 @@ abstract class FileRepo {
return false;
}
$redir = $this->checkRedirect( $title );
- if( $redir && $title->getNamespace() == NS_FILE) {
+ if ( $redir && $title->getNamespace() == NS_FILE) {
$img = $this->newFile( $redir );
- if( !$img ) {
+ if ( !$img ) {
return false;
}
- if( $img->exists() ) {
+ if ( $img->exists() ) {
$img->redirectedFrom( $title->getDBkey() );
return $img;
}
@@ -158,14 +343,16 @@ abstract class FileRepo {
/**
* 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:
*
* $findItem = array( 'title' => $title, 'private' => true );
* $findBatch = array( $findItem );
* $repo->findFiles( $findBatch );
+ * @return array
*/
- function findFiles( $items ) {
+ public function findFiles( $items ) {
$result = array();
foreach ( $items as $item ) {
if ( is_array( $item ) ) {
@@ -191,8 +378,9 @@ abstract class FileRepo {
*
* @param $sha1 String base 36 SHA-1 hash
* @param $options Option array, same as findFile().
+ * @return File|false
*/
- function findFileFromKey( $sha1, $options = array() ) {
+ public function findFileFromKey( $sha1, $options = array() ) {
$time = isset( $options['time'] ) ? $options['time'] : false;
# First try to find a matching current version of a file...
@@ -219,35 +407,59 @@ abstract class FileRepo {
}
/**
- * Get the URL of thumb.php
+ * Get an array or iterator of file objects for files that have a given
+ * SHA-1 content hash.
+ *
+ * STUB
*/
- function getThumbScriptUrl() {
- return $this->thumbScriptUrl;
+ public function findBySha1( $hash ) {
+ return array();
}
/**
- * Get the URL corresponding to one of the four basic zones
- * @param $zone String: one of: public, deleted, temp, thumb
- * @return String or false
+ * Get the public root URL of the repository
+ *
+ * @return string|false
*/
- function getZoneUrl( $zone ) {
- return false;
+ public function getRootUrl() {
+ return $this->url;
+ }
+
+ /**
+ * Returns true if the repository uses a multi-level directory structure
+ *
+ * @return string
+ */
+ public function isHashed() {
+ return (bool)$this->hashLevels;
+ }
+
+ /**
+ * Get the URL of thumb.php
+ *
+ * @return string
+ */
+ public function getThumbScriptUrl() {
+ return $this->thumbScriptUrl;
}
/**
* Returns true if the repository can transform files via a 404 handler
+ *
+ * @return bool
*/
- function canTransformVia404() {
+ public function canTransformVia404() {
return $this->transformVia404;
}
/**
* Get the name of an image from its title object
+ *
* @param $title Title
*/
- function getNameFromTitle( $title ) {
+ public function getNameFromTitle( Title $title ) {
+ global $wgContLang;
if ( $this->initialCapital != MWNamespace::isCapitalized( NS_FILE ) ) {
- global $wgContLang;
$name = $title->getUserCaseDBKey();
if ( $this->initialCapital ) {
$name = $wgContLang->ucfirst( $name );
@@ -258,6 +470,31 @@ abstract class FileRepo {
return $name;
}
+ /**
+ * Get the public zone root storage directory of the repository
+ *
+ * @return string
+ */
+ public function getRootDirectory() {
+ return $this->getZonePath( 'public' );
+ }
+
+ /**
+ * Get a relative path including trailing slash, e.g. f/fa/
+ * If the repo is not hashed, returns an empty string
+ *
+ * @param $name string
+ * @return string
+ */
+ public function getHashPath( $name ) {
+ return self::getHashPathForLevel( $name, $this->hashLevels );
+ }
+
+ /**
+ * @param $name
+ * @param $levels
+ * @return string
+ */
static function getHashPathForLevel( $name, $levels ) {
if ( $levels == 0 ) {
return '';
@@ -272,17 +509,20 @@ abstract class FileRepo {
}
/**
- * Get a relative path including trailing slash, e.g. f/fa/
- * If the repo is not hashed, returns an empty string
+ * Get the number of hash directory levels
+ *
+ * @return integer
*/
- function getHashPath( $name ) {
- return self::getHashPathForLevel( $name, $this->hashLevels );
+ public function getHashLevels() {
+ return $this->hashLevels;
}
/**
* Get the name of this repository, as specified by $info['name]' to the constructor
+ *
+ * @return string
*/
- function getName() {
+ public function getName() {
return $this->name;
}
@@ -291,11 +531,14 @@ abstract class FileRepo {
*
* @param $query mixed Query string to append
* @param $entry string Entry point; defaults to index
- * @return string
+ * @return string|false
*/
- function makeUrl( $query = '', $entry = 'index' ) {
- $ext = isset( $this->scriptExtension ) ? $this->scriptExtension : '.php';
- return wfAppendQuery( "{$this->scriptDirUrl}/{$entry}{$ext}", $query );
+ public function makeUrl( $query = '', $entry = 'index' ) {
+ if ( isset( $this->scriptDirUrl ) ) {
+ $ext = isset( $this->scriptExtension ) ? $this->scriptExtension : '.php';
+ return wfAppendQuery( "{$this->scriptDirUrl}/{$entry}{$ext}", $query );
+ }
+ return false;
}
/**
@@ -306,8 +549,11 @@ abstract class FileRepo {
*
* In particular, it uses the article paths as specified to the repository
* constructor, whereas local repositories use the local Title functions.
+ *
+ * @param $name string
+ * @return string
*/
- function getDescriptionUrl( $name ) {
+ public function getDescriptionUrl( $name ) {
$encName = wfUrlencode( $name );
if ( !is_null( $this->descBaseUrl ) ) {
# "http://example.com/wiki/Image:"
@@ -337,10 +583,12 @@ abstract class FileRepo {
* MediaWiki this means action=render. This should only be called by the
* repository's file class, since it may return invalid results. User code
* should use File::getDescriptionText().
+ *
* @param $name String: name of image to fetch
* @param $lang String: language to fetch it in, if any.
+ * @return string
*/
- function getDescriptionRenderUrl( $name, $lang = null ) {
+ public function getDescriptionRenderUrl( $name, $lang = null ) {
$query = 'action=render';
if ( !is_null( $lang ) ) {
$query .= '&uselang=' . $lang;
@@ -362,19 +610,21 @@ abstract class FileRepo {
/**
* Get the URL of the stylesheet to apply to description pages
- * @return string
+ *
+ * @return string|false
*/
- function getDescriptionStylesheetUrl() {
- if ( $this->scriptDirUrl ) {
+ public function getDescriptionStylesheetUrl() {
+ if ( isset( $this->scriptDirUrl ) ) {
return $this->makeUrl( 'title=MediaWiki:Filepage.css&' .
wfArrayToCGI( Skin::getDynamicStylesheetQuery() ) );
}
+ return false;
}
/**
* Store a file to a given destination.
*
- * @param $srcPath String: source path or virtual URL
+ * @param $srcPath String: source FS path, storage path, or virtual URL
* @param $dstZone String: destination zone
* @param $dstRel String: destination relative path
* @param $flags Integer: bitwise combination of the following flags:
@@ -382,9 +632,10 @@ abstract class FileRepo {
* self::OVERWRITE Overwrite an existing destination file instead of failing
* self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
* same contents as the source
+ * self::SKIP_LOCKING Skip any file locking when doing the store
* @return FileRepoStatus
*/
- function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
+ public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
$status = $this->storeBatch( array( array( $srcPath, $dstZone, $dstRel ) ), $flags );
if ( $status->successCount == 0 ) {
$status->ok = false;
@@ -395,65 +646,236 @@ abstract class FileRepo {
/**
* Store a batch of files
*
- * @param $triplets Array: (src,zone,dest) triplets as per store()
- * @param $flags Integer: flags as per store
+ * @param $triplets Array: (src, dest zone, dest rel) triplets as per store()
+ * @param $flags Integer: bitwise combination of the following flags:
+ * self::DELETE_SOURCE Delete the source file after upload
+ * self::OVERWRITE Overwrite an existing destination file instead of failing
+ * self::OVERWRITE_SAME Overwrite the file if the destination exists and has the
+ * same contents as the source
+ * self::SKIP_LOCKING Skip any file locking when doing the store
+ * @return FileRepoStatus
*/
- abstract function storeBatch( $triplets, $flags = 0 );
+ public function storeBatch( $triplets, $flags = 0 ) {
+ $backend = $this->backend; // convenience
+
+ $status = $this->newGood();
+
+ $operations = array();
+ $sourceFSFilesToDelete = array(); // cleanup for disk source files
+ // Validate each triplet and get the store operation...
+ foreach ( $triplets as $triplet ) {
+ list( $srcPath, $dstZone, $dstRel ) = $triplet;
+ wfDebug( __METHOD__
+ . "( \$src='$srcPath', \$dstZone='$dstZone', \$dstRel='$dstRel' )\n"
+ );
+
+ // Resolve destination path
+ $root = $this->getZonePath( $dstZone );
+ if ( !$root ) {
+ throw new MWException( "Invalid zone: $dstZone" );
+ }
+ if ( !$this->validateFilename( $dstRel ) ) {
+ throw new MWException( 'Validation error in $dstRel' );
+ }
+ $dstPath = "$root/$dstRel";
+ $dstDir = dirname( $dstPath );
+ // Create destination directories for this triplet
+ if ( !$backend->prepare( array( 'dir' => $dstDir ) )->isOK() ) {
+ return $this->newFatal( 'directorycreateerror', $dstDir );
+ }
+
+ if ( $dstZone == 'deleted' ) {
+ $this->initDeletedDir( $dstDir );
+ }
+
+ // Resolve source to a storage path if virtual
+ if ( self::isVirtualUrl( $srcPath ) ) {
+ $srcPath = $this->resolveVirtualUrl( $srcPath );
+ }
+
+ // Get the appropriate file operation
+ if ( FileBackend::isStoragePath( $srcPath ) ) {
+ $opName = ( $flags & self::DELETE_SOURCE ) ? 'move' : 'copy';
+ } else {
+ $opName = 'store';
+ if ( $flags & self::DELETE_SOURCE ) {
+ $sourceFSFilesToDelete[] = $srcPath;
+ }
+ }
+ $operations[] = array(
+ 'op' => $opName,
+ 'src' => $srcPath,
+ 'dst' => $dstPath,
+ 'overwrite' => $flags & self::OVERWRITE,
+ 'overwriteSame' => $flags & self::OVERWRITE_SAME,
+ );
+ }
+
+ // Execute the store operation for each triplet
+ $opts = array( 'force' => true );
+ if ( $flags & self::SKIP_LOCKING ) {
+ $opts['nonLocking'] = true;
+ }
+ $status->merge( $backend->doOperations( $operations, $opts ) );
+ // Cleanup for disk source files...
+ foreach ( $sourceFSFilesToDelete as $file ) {
+ wfSuppressWarnings();
+ unlink( $file ); // FS cleanup
+ wfRestoreWarnings();
+ }
+
+ return $status;
+ }
+
+ /**
+ * Deletes a batch of files.
+ * Each file can be a (zone, rel) pair, virtual url, storage path, or FS path.
+ * It will try to delete each file, but ignores any errors that may occur.
+ *
+ * @param $pairs array List of files to delete
+ * @param $flags Integer: bitwise combination of the following flags:
+ * self::SKIP_LOCKING Skip any file locking when doing the deletions
+ * @return void
+ */
+ public function cleanupBatch( $files, $flags = 0 ) {
+ $operations = array();
+ $sourceFSFilesToDelete = array(); // cleanup for disk source 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;
+ }
+ }
+ // Get a file operation if needed
+ if ( FileBackend::isStoragePath( $path ) ) {
+ $operations[] = array(
+ 'op' => 'delete',
+ 'src' => $path,
+ );
+ } else {
+ $sourceFSFilesToDelete[] = $path;
+ }
+ }
+ // Actually delete files from storage...
+ $opts = array( 'force' => true );
+ if ( $flags & self::SKIP_LOCKING ) {
+ $opts['nonLocking'] = true;
+ }
+ $this->backend->doOperations( $operations, $opts );
+ // Cleanup for disk source files...
+ foreach ( $sourceFSFilesToDelete as $file ) {
+ wfSuppressWarnings();
+ unlink( $file ); // FS cleanup
+ wfRestoreWarnings();
+ }
+ }
/**
* Pick a random name in the temp zone and store a file to it.
- * Returns a FileRepoStatus object with the URL in the value.
+ * Returns a FileRepoStatus object with the file Virtual URL in the value,
+ * file can later be disposed using FileRepo::freeTemp().
+ *
*
* @param $originalName String: the base name of the file as specified
* by the user. The file extension will be maintained.
* @param $srcPath String: the current location of the file.
+ * @return FileRepoStatus object with the URL in the value.
*/
- abstract function storeTemp( $originalName, $srcPath );
+ public function storeTemp( $originalName, $srcPath ) {
+ $date = gmdate( "YmdHis" );
+ $hashPath = $this->getHashPath( $originalName );
+ $dstRel = "{$hashPath}{$date}!{$originalName}";
+ $dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName );
+ $result = $this->store( $srcPath, 'temp', $dstRel, self::SKIP_LOCKING );
+ $result->value = $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
+ return $result;
+ }
/**
- * 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
- * that the source file should be deleted if possible
- * @return mixed Status or false
+ * Concatenate a list of files into a target file location.
+ *
+ * @param $srcPaths Array Ordered list of source virtual URLs/storage paths
+ * @param $dstPath String Target file system path
+ * @param $flags Integer: bitwise combination of the following flags:
+ * self::DELETE_SOURCE Delete the source files
+ * @return FileRepoStatus
*/
- abstract function append( $srcPath, $toAppendPath, $flags = 0 );
+ function concatenate( $srcPaths, $dstPath, $flags = 0 ) {
+ $status = $this->newGood();
- /**
- * Finish the append operation.
- * @param $toAppendPath String: path to append to.
- * @return mixed Status or false
- */
- abstract function appendFinish( $toAppendPath );
+ $sources = array();
+ $deleteOperations = array(); // post-concatenate ops
+ foreach ( $srcPaths as $srcPath ) {
+ // Resolve source to a storage path if virtual
+ $source = $this->resolveToStoragePath( $srcPath );
+ $sources[] = $source; // chunk to merge
+ if ( $flags & self::DELETE_SOURCE ) {
+ $deleteOperations[] = array( 'op' => 'delete', 'src' => $source );
+ }
+ }
+
+ // Concatenate the chunks into one FS file
+ $params = array( 'srcs' => $sources, 'dst' => $dstPath );
+ $status->merge( $this->backend->concatenate( $params ) );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ // Delete the sources if required
+ if ( $deleteOperations ) {
+ $opts = array( 'force' => true );
+ $status->merge( $this->backend->doOperations( $deleteOperations, $opts ) );
+ }
+
+ // Make sure status is OK, despite any $deleteOperations fatals
+ $status->setResult( true );
+
+ return $status;
+ }
/**
* Remove a temporary file or mark it for garbage collection
- * @param $virtualUrl String: the virtual URL returned by storeTemp
+ *
+ * @param $virtualUrl String: the virtual URL returned by FileRepo::storeTemp()
* @return Boolean: true on success, false on failure
- * STUB
*/
- function freeTemp( $virtualUrl ) {
- return true;
+ public function freeTemp( $virtualUrl ) {
+ $temp = "mwrepo://{$this->name}/temp";
+ if ( substr( $virtualUrl, 0, strlen( $temp ) ) != $temp ) {
+ wfDebug( __METHOD__.": Invalid temp virtual URL\n" );
+ return false;
+ }
+ $path = $this->resolveVirtualUrl( $virtualUrl );
+ $op = array( 'op' => 'delete', 'src' => $path );
+ $status = $this->backend->doOperation( $op );
+ return $status->isOK();
}
/**
- * Copy or move a file either from the local filesystem or from an mwrepo://
- * virtual URL, into this repository at the specified destination location.
+ * Copy or move a file either from a storage path, virtual URL,
+ * or FS path, into this repository at the specified destination location.
*
* Returns a FileRepoStatus object. On success, the value contains "new" or
* "archived", to indicate whether the file was new with that name.
*
- * @param $srcPath String: the source path or URL
+ * @param $srcPath String: the source FS path, storage path, or URL
* @param $dstRel String: the destination relative path
- * @param $archiveRel String: rhe relative path where the existing file is to
+ * @param $archiveRel String: the relative path where the existing file is to
* be archived, if there is one. Relative to the public zone root.
* @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source file should be deleted if possible
*/
- function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) {
+ public function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) {
$status = $this->publishBatch( array( array( $srcPath, $dstRel, $archiveRel ) ), $flags );
if ( $status->successCount == 0 ) {
$status->ok = false;
@@ -468,13 +890,123 @@ abstract class FileRepo {
/**
* Publish a batch of files
- * @param $triplets Array: (source,dest,archive) triplets as per publish()
+ *
+ * @param $triplets Array: (source, dest, archive) triplets as per publish()
* @param $flags Integer: bitfield, may be FileRepo::DELETE_SOURCE to indicate
* that the source files should be deleted if possible
+ * @return FileRepoStatus
*/
- abstract function publishBatch( $triplets, $flags = 0 );
+ public function publishBatch( $triplets, $flags = 0 ) {
+ $backend = $this->backend; // convenience
+
+ // Try creating directories
+ $status = $this->initZones( 'public' );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ $status = $this->newGood( array() );
+
+ $operations = array();
+ $sourceFSFilesToDelete = array(); // cleanup for disk source files
+ // Validate each triplet and get the store operation...
+ foreach ( $triplets as $i => $triplet ) {
+ list( $srcPath, $dstRel, $archiveRel ) = $triplet;
+ // Resolve source to a storage path if virtual
+ if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) {
+ $srcPath = $this->resolveVirtualUrl( $srcPath );
+ }
+ if ( !$this->validateFilename( $dstRel ) ) {
+ throw new MWException( 'Validation error in $dstRel' );
+ }
+ if ( !$this->validateFilename( $archiveRel ) ) {
+ throw new MWException( 'Validation error in $archiveRel' );
+ }
+
+ $publicRoot = $this->getZonePath( 'public' );
+ $dstPath = "$publicRoot/$dstRel";
+ $archivePath = "$publicRoot/$archiveRel";
+
+ $dstDir = dirname( $dstPath );
+ $archiveDir = dirname( $archivePath );
+ // Abort immediately on directory creation errors since they're likely to be repetitive
+ if ( !$backend->prepare( array( 'dir' => $dstDir ) )->isOK() ) {
+ return $this->newFatal( 'directorycreateerror', $dstDir );
+ }
+ if ( !$backend->prepare( array( 'dir' => $archiveDir ) )->isOK() ) {
+ return $this->newFatal( 'directorycreateerror', $archiveDir );
+ }
+
+ // Archive destination file if it exists
+ if ( $backend->fileExists( array( 'src' => $dstPath ) ) ) {
+ // Check if the archive file exists
+ // This is a sanity check to avoid data loss. In UNIX, the rename primitive
+ // unlinks the destination file if it exists. DB-based synchronisation in
+ // publishBatch's caller should prevent races. In Windows there's no
+ // problem because the rename primitive fails if the destination exists.
+ if ( $backend->fileExists( array( 'src' => $archivePath ) ) ) {
+ $operations[] = array( 'op' => 'null' );
+ continue;
+ } else {
+ $operations[] = array(
+ 'op' => 'move',
+ 'src' => $dstPath,
+ 'dst' => $archivePath
+ );
+ }
+ $status->value[$i] = 'archived';
+ } else {
+ $status->value[$i] = 'new';
+ }
+ // Copy (or move) the source file to the destination
+ if ( FileBackend::isStoragePath( $srcPath ) ) {
+ if ( $flags & self::DELETE_SOURCE ) {
+ $operations[] = array(
+ 'op' => 'move',
+ 'src' => $srcPath,
+ 'dst' => $dstPath
+ );
+ } else {
+ $operations[] = array(
+ 'op' => 'copy',
+ 'src' => $srcPath,
+ 'dst' => $dstPath
+ );
+ }
+ } else { // FS source path
+ $operations[] = array(
+ 'op' => 'store',
+ 'src' => $srcPath,
+ 'dst' => $dstPath
+ );
+ if ( $flags & self::DELETE_SOURCE ) {
+ $sourceFSFilesToDelete[] = $srcPath;
+ }
+ }
+ }
+
+ // Execute the operations for each triplet
+ $opts = array( 'force' => true );
+ $status->merge( $backend->doOperations( $operations, $opts ) );
+ // Cleanup for disk source files...
+ foreach ( $sourceFSFilesToDelete as $file ) {
+ wfSuppressWarnings();
+ unlink( $file ); // FS cleanup
+ wfRestoreWarnings();
+ }
- function fileExists( $file, $flags = 0 ) {
+ return $status;
+ }
+
+ /**
+ * Checks existence of a a file
+ *
+ * @param $file Virtual URL (or storage path) of file to check
+ * @param $flags Integer: bitwise combination of the following flags:
+ * self::FILES_ONLY Mark file as existing only if it is a file (not directory)
+ * @return bool
+ */
+ public function fileExists( $file, $flags = 0 ) {
$result = $this->fileExistsBatch( array( $file ), $flags );
return $result[0];
}
@@ -482,12 +1014,44 @@ abstract class FileRepo {
/**
* Checks existence of an array of files.
*
- * @param $files Array: URLs (or paths) of files to check
+ * @param $files Array: Virtual URLs (or storage paths) of files to check
* @param $flags Integer: bitwise combination of the following flags:
* self::FILES_ONLY Mark file as existing only if it is a file (not directory)
* @return Either array of files and existence flags, or false
*/
- abstract function fileExistsBatch( $files, $flags = 0 );
+ public function fileExistsBatch( $files, $flags = 0 ) {
+ $result = array();
+ foreach ( $files as $key => $file ) {
+ if ( self::isVirtualUrl( $file ) ) {
+ $file = $this->resolveVirtualUrl( $file );
+ }
+ if ( FileBackend::isStoragePath( $file ) ) {
+ $result[$key] = $this->backend->fileExists( array( 'src' => $file ) );
+ } else {
+ if ( $flags & self::FILES_ONLY ) {
+ $result[$key] = is_file( $file ); // FS only
+ } else {
+ $result[$key] = file_exists( $file ); // FS only
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Move a file to the deletion archive.
+ * If no valid deletion archive exists, this may either delete the file
+ * or throw an exception, depending on the preference of the repository
+ *
+ * @param $srcRel Mixed: relative path for the file to be deleted
+ * @param $archiveRel Mixed: relative path for the archive location.
+ * Relative to a private archive directory.
+ * @return FileRepoStatus object
+ */
+ public function delete( $srcRel, $archiveRel ) {
+ return $this->deleteBatch( array( array( $srcRel, $archiveRel ) ) );
+ }
/**
* Move a group of files to the deletion archive.
@@ -495,7 +1059,7 @@ abstract class FileRepo {
* If no valid deletion archive is configured, this may either delete the
* file or throw an exception, depending on the preference of the repository.
*
- * The overwrite policy is determined by the repository -- currently FSRepo
+ * The overwrite policy is determined by the repository -- currently LocalRepo
* assumes a naming scheme in the deleted zone based on content hash, as
* opposed to the public zone which is assumed to be unique.
*
@@ -505,41 +1069,210 @@ abstract class FileRepo {
* to the deleted zone root in the second element.
* @return FileRepoStatus
*/
- abstract function deleteBatch( $sourceDestPairs );
+ public function deleteBatch( $sourceDestPairs ) {
+ $backend = $this->backend; // convenience
+
+ // Try creating directories
+ $status = $this->initZones( array( 'public', 'deleted' ) );
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+
+ $status = $this->newGood();
+
+ $operations = array();
+ // Validate filenames and create archive directories
+ foreach ( $sourceDestPairs as $pair ) {
+ list( $srcRel, $archiveRel ) = $pair;
+ if ( !$this->validateFilename( $srcRel ) ) {
+ throw new MWException( __METHOD__.':Validation error in $srcRel' );
+ }
+ if ( !$this->validateFilename( $archiveRel ) ) {
+ throw new MWException( __METHOD__.':Validation error in $archiveRel' );
+ }
+
+ $publicRoot = $this->getZonePath( 'public' );
+ $srcPath = "{$publicRoot}/$srcRel";
+
+ $deletedRoot = $this->getZonePath( 'deleted' );
+ $archivePath = "{$deletedRoot}/{$archiveRel}";
+ $archiveDir = dirname( $archivePath ); // does not touch FS
+
+ // Create destination directories
+ if ( !$backend->prepare( array( 'dir' => $archiveDir ) )->isOK() ) {
+ return $this->newFatal( 'directorycreateerror', $archiveDir );
+ }
+ $this->initDeletedDir( $archiveDir );
+
+ $operations[] = array(
+ 'op' => 'move',
+ 'src' => $srcPath,
+ 'dst' => $archivePath,
+ // We may have 2+ identical files being deleted,
+ // all of which will map to the same destination file
+ 'overwriteSame' => true // also see bug 31792
+ );
+ }
+
+ // Move the files by execute the operations for each pair.
+ // We're now committed to returning an OK result, which will
+ // lead to the files being moved in the DB also.
+ $opts = array( 'force' => true );
+ $status->merge( $backend->doOperations( $operations, $opts ) );
+
+ return $status;
+ }
/**
- * Move a file to the deletion archive.
- * If no valid deletion archive exists, this may either delete the file
- * or throw an exception, depending on the preference of the repository
- * @param $srcRel Mixed: relative path for the file to be deleted
- * @param $archiveRel Mixed: relative path for the archive location.
- * Relative to a private archive directory.
- * @return FileRepoStatus object
+ * Get a relative path for a deletion archive key,
+ * e.g. s/z/a/ for sza251lrxrc1jad41h5mgilp8nysje52.jpg
+ *
+ * @return string
*/
- function delete( $srcRel, $archiveRel ) {
- return $this->deleteBatch( array( array( $srcRel, $archiveRel ) ) );
+ public function getDeletedHashPath( $key ) {
+ $path = '';
+ for ( $i = 0; $i < $this->deletedHashLevels; $i++ ) {
+ $path .= $key[$i] . '/';
+ }
+ return $path;
+ }
+
+ /**
+ * If a path is a virtual URL, resolve it to a storage path.
+ * Otherwise, just return the path as it is.
+ *
+ * @param $path string
+ * @return string
+ * @throws MWException
+ */
+ protected function resolveToStoragePath( $path ) {
+ if ( $this->isVirtualUrl( $path ) ) {
+ return $this->resolveVirtualUrl( $path );
+ }
+ return $path;
+ }
+
+ /**
+ * Get a local FS copy of a file with a given virtual URL/storage path.
+ * Temporary files may be purged when the file object falls out of scope.
+ *
+ * @param $virtualUrl string
+ * @return TempFSFile|null Returns null on failure
+ */
+ public function getLocalCopy( $virtualUrl ) {
+ $path = $this->resolveToStoragePath( $virtualUrl );
+ return $this->backend->getLocalCopy( array( 'src' => $path ) );
}
/**
- * Get properties of a file with a given virtual URL
- * The virtual URL must refer to this repo
- * Properties should ultimately be obtained via File::getPropsFromPath()
+ * Get a local FS file with a given virtual URL/storage path.
+ * The file is either an original or a copy. It should not be changed.
+ * Temporary files may be purged when the file object falls out of scope.
+ *
+ * @param $virtualUrl string
+ * @return FSFile|null Returns null on failure.
*/
- abstract function getFileProps( $virtualUrl );
+ public function getLocalReference( $virtualUrl ) {
+ $path = $this->resolveToStoragePath( $virtualUrl );
+ return $this->backend->getLocalReference( array( 'src' => $path ) );
+ }
/**
- * Call a callback function for every file in the repository
- * May use either the database or the filesystem
- * STUB
+ * Get properties of a file with a given virtual URL/storage path.
+ * Properties should ultimately be obtained via FSFile::getProps().
+ *
+ * @param $virtualUrl string
+ * @return Array
*/
- function enumFiles( $callback ) {
- throw new MWException( 'enumFiles is not supported by ' . get_class( $this ) );
+ public function getFileProps( $virtualUrl ) {
+ $path = $this->resolveToStoragePath( $virtualUrl );
+ return $this->backend->getFileProps( array( 'src' => $path ) );
+ }
+
+ /**
+ * Get the timestamp of a file with a given virtual URL/storage path
+ *
+ * @param $virtualUrl string
+ * @return string|false
+ */
+ public function getFileTimestamp( $virtualUrl ) {
+ $path = $this->resolveToStoragePath( $virtualUrl );
+ return $this->backend->getFileTimestamp( array( 'src' => $path ) );
+ }
+
+ /**
+ * Get the sha1 of a file with a given virtual URL/storage path
+ *
+ * @param $virtualUrl string
+ * @return string|false
+ */
+ public function getFileSha1( $virtualUrl ) {
+ $path = $this->resolveToStoragePath( $virtualUrl );
+ $tmpFile = $this->backend->getLocalReference( array( 'src' => $path ) );
+ if ( !$tmpFile ) {
+ return false;
+ }
+ return $tmpFile->getSha1Base36();
+ }
+
+ /**
+ * Attempt to stream a file with the given virtual URL/storage path
+ *
+ * @param $virtualUrl string
+ * @param $headers Array Additional HTTP headers to send on success
+ * @return bool Success
+ */
+ public function streamFile( $virtualUrl, $headers = array() ) {
+ $path = $this->resolveToStoragePath( $virtualUrl );
+ $params = array( 'src' => $path, 'headers' => $headers );
+ return $this->backend->streamFile( $params )->isOK();
+ }
+
+ /**
+ * Call a callback function for every public regular file in the repository.
+ * This only acts on the current version of files, not any old versions.
+ * May use either the database or the filesystem.
+ *
+ * @param $callback Array|string
+ * @return void
+ */
+ public function enumFiles( $callback ) {
+ $this->enumFilesInStorage( $callback );
+ }
+
+ /**
+ * Call a callback function for every public file in the repository.
+ * May use either the database or the filesystem.
+ *
+ * @param $callback Array|string
+ * @return void
+ */
+ protected function enumFilesInStorage( $callback ) {
+ $publicRoot = $this->getZonePath( 'public' );
+ $numDirs = 1 << ( $this->hashLevels * 4 );
+ // Use a priori assumptions about directory structure
+ // to reduce the tree height of the scanning process.
+ for ( $flatIndex = 0; $flatIndex < $numDirs; $flatIndex++ ) {
+ $hexString = sprintf( "%0{$this->hashLevels}x", $flatIndex );
+ $path = $publicRoot;
+ for ( $hexPos = 0; $hexPos < $this->hashLevels; $hexPos++ ) {
+ $path .= '/' . substr( $hexString, 0, $hexPos + 1 );
+ }
+ $iterator = $this->backend->getFileList( array( 'dir' => $path ) );
+ foreach ( $iterator as $name ) {
+ // Each item returned is a public file
+ call_user_func( $callback, "{$path}/{$name}" );
+ }
+ }
}
/**
* Determine if a relative path is valid, i.e. not blank or involving directory traveral
+ *
+ * @param $filename string
+ * @return bool
*/
- function validateFilename( $filename ) {
+ public function validateFilename( $filename ) {
if ( strval( $filename ) == '' ) {
return false;
}
@@ -550,11 +1283,11 @@ abstract class FileRepo {
* Use the same traversal protection as Title::secureAndSplit()
*/
if ( strpos( $filename, '.' ) !== false &&
- ( $filename === '.' || $filename === '..' ||
- strpos( $filename, './' ) === 0 ||
- strpos( $filename, '../' ) === 0 ||
- strpos( $filename, '/./' ) !== false ||
- strpos( $filename, '/../' ) !== false ) )
+ ( $filename === '.' || $filename === '..' ||
+ strpos( $filename, './' ) === 0 ||
+ strpos( $filename, '../' ) === 0 ||
+ strpos( $filename, '/./' ) !== false ||
+ strpos( $filename, '/../' ) !== false ) )
{
return false;
} else {
@@ -562,29 +1295,65 @@ abstract class FileRepo {
}
}
- /**#@+
- * Path disclosure protection functions
- */
- function paranoidClean( $param ) { return '[hidden]'; }
- function passThrough( $param ) { return $param; }
-
/**
* Get a callback function to use for cleaning error message parameters
+ *
+ * @return Array
*/
function getErrorCleanupFunction() {
switch ( $this->pathDisclosureProtection ) {
case 'none':
$callback = array( $this, 'passThrough' );
break;
+ case 'simple':
+ $callback = array( $this, 'simpleClean' );
+ break;
default: // 'paranoid'
$callback = array( $this, 'paranoidClean' );
}
return $callback;
}
- /**#@-*/
+
+ /**
+ * Path disclosure protection function
+ *
+ * @param $param string
+ * @return string
+ */
+ function paranoidClean( $param ) {
+ return '[hidden]';
+ }
+
+ /**
+ * Path disclosure protection function
+ *
+ * @param $param string
+ * @return string
+ */
+ function simpleClean( $param ) {
+ global $IP;
+ if ( !isset( $this->simpleCleanPairs ) ) {
+ $this->simpleCleanPairs = array(
+ $IP => '$IP', // sanity
+ );
+ }
+ return strtr( $param, $this->simpleCleanPairs );
+ }
+
+ /**
+ * Path disclosure protection function
+ *
+ * @param $param string
+ * @return string
+ */
+ function passThrough( $param ) {
+ return $param;
+ }
/**
* Create a new fatal error
+ *
+ * @return FileRepoStatus
*/
function newFatal( $message /*, parameters...*/ ) {
$params = func_get_args();
@@ -594,6 +1363,8 @@ abstract class FileRepo {
/**
* Create a new good result
+ *
+ * @return FileRepoStatus
*/
function newGood( $value = null ) {
return FileRepoStatus::newGood( $this, $value );
@@ -601,9 +1372,10 @@ abstract class FileRepo {
/**
* Delete files in the deleted directory if they are not referenced in the filearchive table
+ *
* STUB
*/
- function cleanupDeletedBatch( $storageKeys ) {}
+ public function cleanupDeletedBatch( $storageKeys ) {}
/**
* Checks if there is a redirect named as $title. If there is, return the
@@ -613,7 +1385,7 @@ abstract class FileRepo {
* @param $title Title of image
* @return Bool
*/
- function checkRedirect( $title ) {
+ public function checkRedirect( Title $title ) {
return false;
}
@@ -624,20 +1396,11 @@ abstract class FileRepo {
* STUB
* @param $title Title of image
*/
- function invalidateImageRedirect( $title ) {}
+ public function invalidateImageRedirect( Title $title ) {}
/**
- * Get an array or iterator of file objects for files that have a given
- * SHA-1 content hash.
+ * Get the human-readable name of the repo
*
- * STUB
- */
- function findBySha1( $hash ) {
- return array();
- }
-
- /**
- * Get the human-readable name of the repo.
* @return string
*/
public function getDisplayName() {
@@ -654,11 +1417,10 @@ abstract class FileRepo {
*
* @return bool
*/
- function isLocal() {
+ public function isLocal() {
return $this->getName() == 'local';
}
-
/**
* Get a key on the primary cache for this repository.
* Returns false if the repository's cache is not accessible at this site.
@@ -674,6 +1436,8 @@ abstract class FileRepo {
* Get a key for this repo in the local cache domain. These cache keys are
* not shared with remote instances of the repo.
* The parameters are the parts of the key, as for wfMemcKey().
+ *
+ * @return string
*/
function getLocalCacheKey( /*...*/ ) {
$args = func_get_args();
@@ -686,7 +1450,7 @@ abstract class FileRepo {
*
* @return UploadStash
*/
- function getUploadStash() {
+ public function getUploadStash() {
return new UploadStash( $this );
}
}
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index 502b8c1d..e544defb 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -32,20 +32,16 @@ class ForeignAPIRepo extends FileRepo {
var $apiThumbCacheExpiry = 86400; /* 24*60*60 */
/* Redownload thumbnail files after a month */
var $fileCacheExpiry = 2592000; /* 86400*30 */
- /* Local image directory */
- var $directory;
- var $thumbDir;
protected $mQueryCache = array();
protected $mFileExists = array();
function __construct( $info ) {
+ global $wgLocalFileRepo;
parent::__construct( $info );
- global $wgUploadDirectory;
// http://commons.wikimedia.org/w/api.php
$this->mApiBase = isset( $info['apibase'] ) ? $info['apibase'] : null;
- $this->directory = isset( $info['directory'] ) ? $info['directory'] : $wgUploadDirectory;
if( isset( $info['apiThumbCacheExpiry'] ) ) {
$this->apiThumbCacheExpiry = $info['apiThumbCacheExpiry'];
@@ -59,17 +55,11 @@ class ForeignAPIRepo extends FileRepo {
}
// If we can cache thumbs we can guess sane defaults for these
if( $this->canCacheThumbs() && !$this->url ) {
- global $wgLocalFileRepo;
$this->url = $wgLocalFileRepo['url'];
}
if( $this->canCacheThumbs() && !$this->thumbUrl ) {
$this->thumbUrl = $this->url . '/thumb';
}
- if ( isset( $info['thumbDir'] ) ) {
- $this->thumbDir = $info['thumbDir'];
- } else {
- $this->thumbDir = "{$this->directory}/thumb";
- }
}
/**
@@ -97,6 +87,10 @@ class ForeignAPIRepo extends FileRepo {
return false;
}
+ function concatenate( $fileList, $targetPath, $flags = 0 ){
+ return false;
+ }
+
function append( $srcPath, $toAppendPath, $flags = 0 ){
return false;
}
@@ -280,9 +274,9 @@ class ForeignAPIRepo extends FileRepo {
$localFilename = $localPath . "/" . $fileName;
$localUrl = $this->getZoneUrl( 'thumb' ) . "/" . $this->getHashPath( $name ) . rawurlencode( $name ) . "/" . rawurlencode( $fileName );
- if( file_exists( $localFilename ) && isset( $metadata['timestamp'] ) ) {
+ if( $this->fileExists( $localFilename ) && isset( $metadata['timestamp'] ) ) {
wfDebug( __METHOD__ . " Thumbnail was already downloaded before\n" );
- $modified = filemtime( $localFilename );
+ $modified = $this->getFileTimestamp( $localFilename );
$remoteModified = strtotime( $metadata['timestamp'] );
$current = time();
$diff = abs( $modified - $current );
@@ -299,16 +293,12 @@ class ForeignAPIRepo extends FileRepo {
wfDebug( __METHOD__ . " Could not download thumb\n" );
return false;
}
- if ( !is_dir($localPath) ) {
- if( !wfMkdirParents($localPath) ) {
- wfDebug( __METHOD__ . " could not create directory $localPath for thumb\n" );
- return $foreignUrl;
- }
- }
# @todo FIXME: Delete old thumbs that aren't being used. Maintenance script?
wfSuppressWarnings();
- if( !file_put_contents( $localFilename, $thumb ) ) {
+ $backend = $this->getBackend();
+ $op = array( 'op' => 'create', 'dst' => $localFilename, 'content' => $thumb );
+ if( !$backend->doOperation( $op )->isOK() ) {
wfRestoreWarnings();
wfDebug( __METHOD__ . " could not write to thumb path\n" );
return $foreignUrl;
@@ -335,17 +325,14 @@ class ForeignAPIRepo extends FileRepo {
}
/**
- * Get the local directory corresponding to one of the three basic zones
+ * Get the local directory corresponding to one of the basic zones
*/
function getZonePath( $zone ) {
- switch ( $zone ) {
- case 'public':
- return $this->directory;
- case 'thumb':
- return $this->thumbDir;
- default:
- return false;
+ $supported = array( 'public', 'thumb' );
+ if ( in_array( $zone, $supported ) ) {
+ return parent::getZonePath( $zone );
}
+ return false;
}
/**
@@ -388,4 +375,8 @@ class ForeignAPIRepo extends FileRepo {
return false;
}
}
+
+ function enumFiles( $callback ) {
+ throw new MWException( 'enumFiles is not supported by ' . get_class( $this ) );
+ }
}
diff --git a/includes/filerepo/ForeignDBViaLBRepo.php b/includes/filerepo/ForeignDBViaLBRepo.php
index 4c530b51..28b48b5e 100644
--- a/includes/filerepo/ForeignDBViaLBRepo.php
+++ b/includes/filerepo/ForeignDBViaLBRepo.php
@@ -30,6 +30,7 @@ class ForeignDBViaLBRepo extends LocalRepo {
function getSlaveDB() {
return wfGetDB( DB_SLAVE, array(), $this->wiki );
}
+
function hasSharedCache() {
return $this->hasSharedCache;
}
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
index 9089f4d7..cc23fa31 100644
--- a/includes/filerepo/LocalRepo.php
+++ b/includes/filerepo/LocalRepo.php
@@ -10,19 +10,20 @@
/**
* A repository that stores files in the local filesystem and registers them
* in the wiki's own database. This is the most commonly used repository class.
+ *
* @ingroup FileRepo
*/
-class LocalRepo extends FSRepo {
- var $fileFactory = array( 'LocalFile', 'newFromTitle' );
- var $fileFactoryKey = array( 'LocalFile', 'newFromKey' );
- var $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
- var $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' );
- var $fileFromRowFactory = array( 'LocalFile', 'newFromRow' );
- var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
+class LocalRepo extends FileRepo {
+ var $fileFactory = array( 'LocalFile' , 'newFromTitle' );
+ var $fileFactoryKey = array( 'LocalFile' , 'newFromKey' );
+ var $fileFromRowFactory = array( 'LocalFile' , 'newFromRow' );
+ var $oldFileFactory = array( 'OldLocalFile', 'newFromTitle' );
+ var $oldFileFactoryKey = array( 'OldLocalFile', 'newFromKey' );
+ var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' );
/**
* @throws MWException
- * @param $row
+ * @param $row
* @return File
*/
function newFileFromRow( $row ) {
@@ -55,6 +56,7 @@ class LocalRepo extends FSRepo {
* @return FileRepoStatus
*/
function cleanupDeletedBatch( $storageKeys ) {
+ $backend = $this->backend; // convenience
$root = $this->getZonePath( 'deleted' );
$dbw = $this->getMasterDB();
$status = $this->newGood();
@@ -63,25 +65,14 @@ class LocalRepo extends FSRepo {
$hashPath = $this->getDeletedHashPath( $key );
$path = "$root/$hashPath$key";
$dbw->begin();
- $inuse = $dbw->selectField( 'filearchive', '1',
- array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
- __METHOD__, array( 'FOR UPDATE' ) );
- if( !$inuse ) {
- $sha1 = self::getHashFromKey( $key );
- $ext = substr( $key, strcspn( $key, '.' ) + 1 );
- $ext = File::normalizeExtension($ext);
- $inuse = $dbw->selectField( 'oldimage', '1',
- array( 'oi_sha1' => $sha1,
- 'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
- $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE ),
- __METHOD__, array( 'FOR UPDATE' ) );
- }
- if ( !$inuse ) {
+ // Check for usage in deleted/hidden files and pre-emptively
+ // lock the key to avoid any future use until we are finished.
+ $deleted = $this->deletedFileHasKey( $key, 'lock' );
+ $hidden = $this->hiddenFileHasKey( $key, 'lock' );
+ if ( !$deleted && !$hidden ) { // not in use now
wfDebug( __METHOD__ . ": deleting $key\n" );
- wfSuppressWarnings();
- $unlink = unlink( $path );
- wfRestoreWarnings();
- if ( !$unlink ) {
+ $op = array( 'op' => 'delete', 'src' => $path );
+ if ( !$backend->doOperation( $op )->isOK() ) {
$status->error( 'undelete-cleanup-error', $path );
$status->failCount++;
}
@@ -95,6 +86,45 @@ class LocalRepo extends FSRepo {
}
/**
+ * Check if a deleted (filearchive) file has this sha1 key
+ *
+ * @param $key String File storage key (base-36 sha1 key with file extension)
+ * @param $lock String|null Use "lock" to lock the row via FOR UPDATE
+ * @return bool File with this key is in use
+ */
+ protected function deletedFileHasKey( $key, $lock = null ) {
+ $options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array();
+
+ $dbw = $this->getMasterDB();
+ return (bool)$dbw->selectField( 'filearchive', '1',
+ array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
+ __METHOD__, $options
+ );
+ }
+
+ /**
+ * Check if a hidden (revision delete) file has this sha1 key
+ *
+ * @param $key String File storage key (base-36 sha1 key with file extension)
+ * @param $lock String|null Use "lock" to lock the row via FOR UPDATE
+ * @return bool File with this key is in use
+ */
+ protected function hiddenFileHasKey( $key, $lock = null ) {
+ $options = ( $lock === 'lock' ) ? array( 'FOR UPDATE' ) : array();
+
+ $sha1 = self::getHashFromKey( $key );
+ $ext = File::normalizeExtension( substr( $key, strcspn( $key, '.' ) + 1 ) );
+
+ $dbw = $this->getMasterDB();
+ return (bool)$dbw->selectField( 'oldimage', '1',
+ array( 'oi_sha1' => $sha1,
+ 'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
+ $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE ),
+ __METHOD__, $options
+ );
+ }
+
+ /**
* Gets the SHA1 hash from a storage key
*
* @param string $key
@@ -108,16 +138,12 @@ class LocalRepo extends FSRepo {
* Checks if there is a redirect named as $title
*
* @param $title Title of file
+ * @return bool
*/
- function checkRedirect( $title ) {
+ function checkRedirect( Title $title ) {
global $wgMemc;
- if( is_string( $title ) ) {
- $title = Title::newFromText( $title );
- }
- if( $title instanceof Title && $title->getNamespace() == NS_MEDIA ) {
- $title = Title::makeTitle( NS_FILE, $title->getText() );
- }
+ $title = File::normalizeTitle( $title, 'exception' );
$memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
if ( $memcKey === false ) {
@@ -161,6 +187,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 ) {
@@ -169,20 +196,23 @@ class LocalRepo extends FSRepo {
}
$dbr = $this->getSlaveDB();
$id = $dbr->selectField(
- 'page', // Table
- 'page_id', //Field
- array( //Conditions
+ 'page', // Table
+ 'page_id', //Field
+ array( //Conditions
'page_namespace' => $title->getNamespace(),
'page_title' => $title->getDBkey(),
),
- __METHOD__ //Function name
+ __METHOD__ //Function name
);
return $id;
}
/**
- * Get an array or iterator of file objects for files that have a given
+ * Get an array or iterator of file objects for files that have a given
* SHA-1 content hash.
+ *
+ * @param $hash String a sha1 hash to look for
+ * @return Array
*/
function findBySha1( $hash ) {
$dbr = $this->getSlaveDB();
@@ -219,6 +249,8 @@ class LocalRepo extends FSRepo {
* Get a key on the primary cache for this repository.
* Returns false if the repository's cache is not accessible at this site.
* The parameters are the parts of the key, as for wfMemcKey().
+ *
+ * @return string
*/
function getSharedCacheKey( /*...*/ ) {
$args = func_get_args();
@@ -229,8 +261,9 @@ class LocalRepo extends FSRepo {
* Invalidates image redirect cache related to that image
*
* @param $title Title of page
+ * @return void
*/
- function invalidateImageRedirect( $title ) {
+ function invalidateImageRedirect( Title $title ) {
global $wgMemc;
$memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
if ( $memcKey ) {
diff --git a/includes/filerepo/NullRepo.php b/includes/filerepo/NullRepo.php
index cac3e5d8..65318f40 100644
--- a/includes/filerepo/NullRepo.php
+++ b/includes/filerepo/NullRepo.php
@@ -44,4 +44,7 @@ class NullRepo extends FileRepo {
function findFile( $title, $options = array() ) {
return false;
}
+ function concatenate( $fileList, $targetPath, $flags = 0 ) {
+ return false;
+ }
}
diff --git a/includes/filerepo/README b/includes/filerepo/README
index db46ff8a..885a1ded 100644
--- a/includes/filerepo/README
+++ b/includes/filerepo/README
@@ -42,18 +42,19 @@ 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
+File defines an abstract class File.
+ ForeignAPIFile extends File.
+ LocalFile extends File.
+ ForeignDBFile extends LocalFile
+ Image extends LocalFile
+ UnregisteredLocalFile extends File.
+ UploadStashFile extends UnregisteredLocalFile.
+FileRepo defines an abstract class FileRepo.
+ ForeignAPIRepo extends FileRepo
FSRepo extends FileRepo
- LocalRepo.php extends FSRepo
- ForeignDBRepo.php extends LocalRepo
- ForeignDBViaLBRepo.php extends LocalRepo
+ LocalRepo extends FSRepo
+ ForeignDBRepo extends LocalRepo
+ ForeignDBViaLBRepo extends LocalRepo
NullRepo extends FileRepo
Russ Nelson, March 2011
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index d4875908..334ef2b8 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -7,10 +7,6 @@
*/
/**
- * @defgroup FileRepo FileRepo
- */
-
-/**
* Prioritized list of file repositories
*
* @ingroup FileRepo
@@ -56,6 +52,9 @@ class RepoGroup {
/**
* Set the singleton instance to a given object
+ * Used by extensions which hook into the Repo chain.
+ * It's not enough to just create a superclass ... you have
+ * to get people to call into it even though all they know is RepoGroup::singleton()
*
* @param $instance RepoGroup
*/
@@ -105,22 +104,15 @@ class RepoGroup {
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
- if ( !($title instanceof Title) ) {
- $title = Title::makeTitleSafe( NS_FILE, $title );
- if ( !is_object( $title ) ) {
- return false;
- }
- }
-
- if ( $title->getNamespace() != NS_MEDIA && $title->getNamespace() != NS_FILE ) {
- throw new MWException( __METHOD__ . ' received an Title object with incorrect namespace' );
+ $title = File::normalizeTitle( $title );
+ if ( !$title ) {
+ return false;
}
# Check the cache
if ( empty( $options['ignoreRedirect'] )
&& empty( $options['private'] )
- && empty( $options['bypassCache'] )
- && $title->getNamespace() == NS_FILE )
+ && empty( $options['bypassCache'] ) )
{
$useCache = true;
$time = isset( $options['time'] ) ? $options['time'] : '';
@@ -176,10 +168,10 @@ class RepoGroup {
if ( !is_array( $item ) ) {
$item = array( 'title' => $item );
}
- if ( !( $item['title'] instanceof Title ) )
- $item['title'] = Title::makeTitleSafe( NS_FILE, $item['title'] );
- if ( $item['title'] )
+ $item['title'] = File::normalizeTitle( $item['title'] );
+ if ( $item['title'] ) {
$items[$item['title']->getDBkey()] = $item;
+ }
}
$images = $this->localRepo->findFiles( $items );
@@ -198,7 +190,7 @@ class RepoGroup {
/**
* Interface for FileRepo::checkRedirect()
*/
- function checkRedirect( $title ) {
+ function checkRedirect( Title $title ) {
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
@@ -370,14 +362,14 @@ class RepoGroup {
$repo = $this->getRepo( $repoName );
return $repo->getFileProps( $fileName );
} else {
- return File::getPropsFromPath( $fileName );
+ return FSFile::getPropsFromPath( $fileName );
}
}
/**
* Limit cache memory
*/
- function trimCache() {
+ protected function trimCache() {
while ( count( $this->cache ) >= self::MAX_CACHE_SIZE ) {
reset( $this->cache );
$key = key( $this->cache );
@@ -385,4 +377,19 @@ class RepoGroup {
unset( $this->cache[$key] );
}
}
+
+ /**
+ * Clear RepoGroup process cache used for finding a file
+ * @param $title Title|null Title of the file or null to clear all files
+ */
+ public function clearCache( Title $title = null ) {
+ if ( $title == null ) {
+ $this->cache = array();
+ } else {
+ $dbKey = $title->getDBkey();
+ if ( isset( $this->cache[$dbKey] ) ) {
+ unset( $this->cache[$dbKey] );
+ }
+ }
+ }
}
diff --git a/includes/filerepo/backend/FSFile.php b/includes/filerepo/backend/FSFile.php
new file mode 100644
index 00000000..54dd1359
--- /dev/null
+++ b/includes/filerepo/backend/FSFile.php
@@ -0,0 +1,233 @@
+<?php
+/**
+ * @file
+ * @ingroup FileBackend
+ */
+
+/**
+ * Class representing a non-directory file on the file system
+ *
+ * @ingroup FileBackend
+ */
+class FSFile {
+ protected $path; // path to file
+
+ /**
+ * Sets up the file object
+ *
+ * @param String $path Path to temporary file on local disk
+ */
+ public function __construct( $path ) {
+ if ( FileBackend::isStoragePath( $path ) ) {
+ throw new MWException( __METHOD__ . " given storage path `$path`." );
+ }
+ $this->path = $path;
+ }
+
+ /**
+ * Returns the file system path
+ *
+ * @return String
+ */
+ public function getPath() {
+ return $this->path;
+ }
+
+ /**
+ * Checks if the file exists
+ *
+ * @return bool
+ */
+ public function exists() {
+ return is_file( $this->path );
+ }
+
+ /**
+ * Get the file size in bytes
+ *
+ * @return int|false
+ */
+ public function getSize() {
+ return filesize( $this->path );
+ }
+
+ /**
+ * Get the file's last-modified timestamp
+ *
+ * @return string|false TS_MW timestamp or false on failure
+ */
+ public function getTimestamp() {
+ wfSuppressWarnings();
+ $timestamp = filemtime( $this->path );
+ wfRestoreWarnings();
+ if ( $timestamp !== false ) {
+ $timestamp = wfTimestamp( TS_MW, $timestamp );
+ }
+ return $timestamp;
+ }
+
+ /**
+ * Guess the MIME type from the file contents alone
+ *
+ * @return string
+ */
+ public function getMimeType() {
+ return MimeMagic::singleton()->guessMimeType( $this->path, false );
+ }
+
+ /**
+ * Get an associative array containing information about
+ * a file with the given storage 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
+ */
+ public function getProps( $ext = true ) {
+ wfProfileIn( __METHOD__ );
+ wfDebug( __METHOD__.": Getting file info for $this->path\n" );
+
+ $info = self::placeholderProps();
+ $info['fileExists'] = $this->exists();
+
+ if ( $info['fileExists'] ) {
+ $magic = MimeMagic::singleton();
+
+ # get the file extension
+ if ( $ext === true ) {
+ $ext = self::extensionFromPath( $this->path );
+ }
+
+ # mime type according to file contents
+ $info['file-mime'] = $this->getMimeType();
+ # logical mime type
+ $info['mime'] = $magic->improveTypeFromExtension( $info['file-mime'], $ext );
+
+ list( $info['major_mime'], $info['minor_mime'] ) = File::splitMime( $info['mime'] );
+ $info['media_type'] = $magic->getMediaType( $this->path, $info['mime'] );
+
+ # Get size in bytes
+ $info['size'] = $this->getSize();
+
+ # Height, width and metadata
+ $handler = MediaHandler::getHandler( $info['mime'] );
+ if ( $handler ) {
+ $tempImage = (object)array();
+ $info['metadata'] = $handler->getMetadata( $tempImage, $this->path );
+ $gis = $handler->getImageSize( $tempImage, $this->path, $info['metadata'] );
+ if ( is_array( $gis ) ) {
+ $info = $this->extractImageSizeInfo( $gis ) + $info;
+ }
+ }
+ $info['sha1'] = $this->getSha1Base36();
+
+ wfDebug(__METHOD__.": $this->path loaded, {$info['size']} bytes, {$info['mime']}.\n");
+ } else {
+ wfDebug(__METHOD__.": $this->path NOT FOUND!\n");
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $info;
+ }
+
+ /**
+ * Placeholder file properties to use for files that don't exist
+ *
+ * @return Array
+ */
+ public static function placeholderProps() {
+ $info = array();
+ $info['fileExists'] = false;
+ $info['mime'] = null;
+ $info['media_type'] = MEDIATYPE_UNKNOWN;
+ $info['metadata'] = '';
+ $info['sha1'] = '';
+ $info['width'] = 0;
+ $info['height'] = 0;
+ $info['bits'] = 0;
+ return $info;
+ }
+
+ /**
+ * Exract image size information
+ *
+ * @return Array
+ */
+ protected function extractImageSizeInfo( array $gis ) {
+ $info = array();
+ # NOTE: $gis[2] contains a code for the image type. This is no longer used.
+ $info['width'] = $gis[0];
+ $info['height'] = $gis[1];
+ if ( isset( $gis['bits'] ) ) {
+ $info['bits'] = $gis['bits'];
+ } else {
+ $info['bits'] = 0;
+ }
+ return $info;
+ }
+
+ /**
+ * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
+ * encoding, zero padded to 31 digits.
+ *
+ * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
+ * fairly neatly.
+ *
+ * @return false|string False on failure
+ */
+ public function getSha1Base36() {
+ wfProfileIn( __METHOD__ );
+
+ wfSuppressWarnings();
+ $hash = sha1_file( $this->path );
+ wfRestoreWarnings();
+ if ( $hash !== false ) {
+ $hash = wfBaseConvert( $hash, 16, 36, 31 );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $hash;
+ }
+
+ /**
+ * Get the final file extension from a file system path
+ *
+ * @param $path string
+ * @return string
+ */
+ public static function extensionFromPath( $path ) {
+ $i = strrpos( $path, '.' );
+ return strtolower( $i ? substr( $path, $i + 1 ) : '' );
+ }
+
+ /**
+ * Get an associative array containing information about a file in the local filesystem.
+ *
+ * @param $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
+ */
+ public static function getPropsFromPath( $path, $ext = true ) {
+ $fsFile = new self( $path );
+ return $fsFile->getProps( $ext );
+ }
+
+ /**
+ * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case
+ * encoding, zero padded to 31 digits.
+ *
+ * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36
+ * fairly neatly.
+ *
+ * @param $path string
+ *
+ * @return false|string False on failure
+ */
+ public static function getSha1Base36FromPath( $path ) {
+ $fsFile = new self( $path );
+ return $fsFile->getSha1Base36();
+ }
+}
diff --git a/includes/filerepo/backend/FSFileBackend.php b/includes/filerepo/backend/FSFileBackend.php
new file mode 100644
index 00000000..1a4c44ad
--- /dev/null
+++ b/includes/filerepo/backend/FSFileBackend.php
@@ -0,0 +1,600 @@
+<?php
+/**
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class for a file system (FS) based file backend.
+ *
+ * All "containers" each map to a directory under the backend's base directory.
+ * For backwards-compatibility, some container paths can be set to custom paths.
+ * The wiki ID will not be used in any custom paths, so this should be avoided.
+ *
+ * Having directories with thousands of files will diminish performance.
+ * Sharding can be accomplished by using FileRepo-style hash paths.
+ *
+ * Status messages should avoid mentioning the internal FS paths.
+ * PHP warnings are assumed to be logged rather than output.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+class FSFileBackend extends FileBackendStore {
+ protected $basePath; // string; directory holding the container directories
+ /** @var Array Map of container names to root paths */
+ protected $containerPaths = array(); // for custom container paths
+ protected $fileMode; // integer; file permission mode
+
+ protected $hadWarningErrors = array();
+
+ /**
+ * @see FileBackendStore::__construct()
+ * Additional $config params include:
+ * basePath : File system directory that holds containers.
+ * containerPaths : Map of container names to custom file system directories.
+ * This should only be used for backwards-compatibility.
+ * fileMode : Octal UNIX file permissions to use on files stored.
+ */
+ public function __construct( array $config ) {
+ parent::__construct( $config );
+
+ // Remove any possible trailing slash from directories
+ if ( isset( $config['basePath'] ) ) {
+ $this->basePath = rtrim( $config['basePath'], '/' ); // remove trailing slash
+ } else {
+ $this->basePath = null; // none; containers must have explicit paths
+ }
+
+ if ( isset( $config['containerPaths'] ) ) {
+ $this->containerPaths = (array)$config['containerPaths'];
+ foreach ( $this->containerPaths as &$path ) {
+ $path = rtrim( $path, '/' ); // remove trailing slash
+ }
+ }
+
+ $this->fileMode = isset( $config['fileMode'] )
+ ? $config['fileMode']
+ : 0644;
+ }
+
+ /**
+ * @see FileBackendStore::resolveContainerPath()
+ */
+ protected function resolveContainerPath( $container, $relStoragePath ) {
+ // Check that container has a root directory
+ if ( isset( $this->containerPaths[$container] ) || isset( $this->basePath ) ) {
+ // Check for sane relative paths (assume the base paths are OK)
+ if ( $this->isLegalRelPath( $relStoragePath ) ) {
+ return $relStoragePath;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sanity check a relative file system path for validity
+ *
+ * @param $path string Normalized relative path
+ * @return bool
+ */
+ protected function isLegalRelPath( $path ) {
+ // Check for file names longer than 255 chars
+ if ( preg_match( '![^/]{256}!', $path ) ) { // ext3/NTFS
+ return false;
+ }
+ if ( wfIsWindows() ) { // NTFS
+ return !preg_match( '![:*?"<>|]!', $path );
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Given the short (unresolved) and full (resolved) name of
+ * a container, return the file system path of the container.
+ *
+ * @param $shortCont string
+ * @param $fullCont string
+ * @return string|null
+ */
+ protected function containerFSRoot( $shortCont, $fullCont ) {
+ if ( isset( $this->containerPaths[$shortCont] ) ) {
+ return $this->containerPaths[$shortCont];
+ } elseif ( isset( $this->basePath ) ) {
+ return "{$this->basePath}/{$fullCont}";
+ }
+ return null; // no container base path defined
+ }
+
+ /**
+ * Get the absolute file system path for a storage path
+ *
+ * @param $storagePath string Storage path
+ * @return string|null
+ */
+ protected function resolveToFSPath( $storagePath ) {
+ list( $fullCont, $relPath ) = $this->resolveStoragePathReal( $storagePath );
+ if ( $relPath === null ) {
+ return null; // invalid
+ }
+ list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $storagePath );
+ $fsPath = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+ if ( $relPath != '' ) {
+ $fsPath .= "/{$relPath}";
+ }
+ return $fsPath;
+ }
+
+ /**
+ * @see FileBackendStore::isPathUsableInternal()
+ */
+ public function isPathUsableInternal( $storagePath ) {
+ $fsPath = $this->resolveToFSPath( $storagePath );
+ if ( $fsPath === null ) {
+ return false; // invalid
+ }
+ $parentDir = dirname( $fsPath );
+
+ if ( file_exists( $fsPath ) ) {
+ $ok = is_file( $fsPath ) && is_writable( $fsPath );
+ } else {
+ $ok = is_dir( $parentDir ) && is_writable( $parentDir );
+ }
+
+ return $ok;
+ }
+
+ /**
+ * @see FileBackendStore::doStoreInternal()
+ */
+ protected function doStoreInternal( array $params ) {
+ $status = Status::newGood();
+
+ $dest = $this->resolveToFSPath( $params['dst'] );
+ if ( $dest === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+ return $status;
+ }
+
+ if ( file_exists( $dest ) ) {
+ if ( !empty( $params['overwrite'] ) ) {
+ $ok = unlink( $dest );
+ if ( !$ok ) {
+ $status->fatal( 'backend-fail-delete', $params['dst'] );
+ return $status;
+ }
+ } else {
+ $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+ return $status;
+ }
+ }
+
+ $ok = copy( $params['src'], $dest );
+ if ( !$ok ) {
+ $status->fatal( 'backend-fail-store', $params['src'], $params['dst'] );
+ return $status;
+ }
+
+ $this->chmod( $dest );
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doCopyInternal()
+ */
+ protected function doCopyInternal( array $params ) {
+ $status = Status::newGood();
+
+ $source = $this->resolveToFSPath( $params['src'] );
+ if ( $source === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+ return $status;
+ }
+
+ $dest = $this->resolveToFSPath( $params['dst'] );
+ if ( $dest === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+ return $status;
+ }
+
+ if ( file_exists( $dest ) ) {
+ if ( !empty( $params['overwrite'] ) ) {
+ $ok = unlink( $dest );
+ if ( !$ok ) {
+ $status->fatal( 'backend-fail-delete', $params['dst'] );
+ return $status;
+ }
+ } else {
+ $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+ return $status;
+ }
+ }
+
+ $ok = copy( $source, $dest );
+ if ( !$ok ) {
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ return $status;
+ }
+
+ $this->chmod( $dest );
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doMoveInternal()
+ */
+ protected function doMoveInternal( array $params ) {
+ $status = Status::newGood();
+
+ $source = $this->resolveToFSPath( $params['src'] );
+ if ( $source === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+ return $status;
+ }
+
+ $dest = $this->resolveToFSPath( $params['dst'] );
+ if ( $dest === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+ return $status;
+ }
+
+ if ( file_exists( $dest ) ) {
+ if ( !empty( $params['overwrite'] ) ) {
+ // Windows does not support moving over existing files
+ if ( wfIsWindows() ) {
+ $ok = unlink( $dest );
+ if ( !$ok ) {
+ $status->fatal( 'backend-fail-delete', $params['dst'] );
+ return $status;
+ }
+ }
+ } else {
+ $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+ return $status;
+ }
+ }
+
+ $ok = rename( $source, $dest );
+ clearstatcache(); // file no longer at source
+ if ( !$ok ) {
+ $status->fatal( 'backend-fail-move', $params['src'], $params['dst'] );
+ return $status;
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doDeleteInternal()
+ */
+ protected function doDeleteInternal( array $params ) {
+ $status = Status::newGood();
+
+ $source = $this->resolveToFSPath( $params['src'] );
+ if ( $source === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+ return $status;
+ }
+
+ if ( !is_file( $source ) ) {
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-delete', $params['src'] );
+ }
+ return $status; // do nothing; either OK or bad status
+ }
+
+ $ok = unlink( $source );
+ if ( !$ok ) {
+ $status->fatal( 'backend-fail-delete', $params['src'] );
+ return $status;
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doCreateInternal()
+ */
+ protected function doCreateInternal( array $params ) {
+ $status = Status::newGood();
+
+ $dest = $this->resolveToFSPath( $params['dst'] );
+ if ( $dest === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+ return $status;
+ }
+
+ if ( file_exists( $dest ) ) {
+ if ( !empty( $params['overwrite'] ) ) {
+ $ok = unlink( $dest );
+ if ( !$ok ) {
+ $status->fatal( 'backend-fail-delete', $params['dst'] );
+ return $status;
+ }
+ } else {
+ $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+ return $status;
+ }
+ }
+
+ $bytes = file_put_contents( $dest, $params['content'] );
+ if ( $bytes === false ) {
+ $status->fatal( 'backend-fail-create', $params['dst'] );
+ return $status;
+ }
+
+ $this->chmod( $dest );
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doPrepareInternal()
+ */
+ protected function doPrepareInternal( $fullCont, $dirRel, array $params ) {
+ $status = Status::newGood();
+ list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+ $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+ if ( !wfMkdirParents( $dir ) ) { // make directory and its parents
+ $status->fatal( 'directorycreateerror', $params['dir'] );
+ } elseif ( !is_writable( $dir ) ) {
+ $status->fatal( 'directoryreadonlyerror', $params['dir'] );
+ } elseif ( !is_readable( $dir ) ) {
+ $status->fatal( 'directorynotreadableerror', $params['dir'] );
+ }
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doSecureInternal()
+ */
+ protected function doSecureInternal( $fullCont, $dirRel, array $params ) {
+ $status = Status::newGood();
+ list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+ $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+ // Seed new directories with a blank index.html, to prevent crawling...
+ if ( !empty( $params['noListing'] ) && !file_exists( "{$dir}/index.html" ) ) {
+ $bytes = file_put_contents( "{$dir}/index.html", '' );
+ if ( !$bytes ) {
+ $status->fatal( 'backend-fail-create', $params['dir'] . '/index.html' );
+ return $status;
+ }
+ }
+ // Add a .htaccess file to the root of the container...
+ if ( !empty( $params['noAccess'] ) ) {
+ if ( !file_exists( "{$contRoot}/.htaccess" ) ) {
+ $bytes = file_put_contents( "{$contRoot}/.htaccess", "Deny from all\n" );
+ if ( !$bytes ) {
+ $storeDir = "mwstore://{$this->name}/{$shortCont}";
+ $status->fatal( 'backend-fail-create', "{$storeDir}/.htaccess" );
+ return $status;
+ }
+ }
+ }
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doCleanInternal()
+ */
+ protected function doCleanInternal( $fullCont, $dirRel, array $params ) {
+ $status = Status::newGood();
+ list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+ $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+ wfSuppressWarnings();
+ if ( is_dir( $dir ) ) {
+ rmdir( $dir ); // remove directory if empty
+ }
+ wfRestoreWarnings();
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doFileExists()
+ */
+ protected function doGetFileStat( array $params ) {
+ $source = $this->resolveToFSPath( $params['src'] );
+ if ( $source === null ) {
+ return false; // invalid storage path
+ }
+
+ $this->trapWarnings(); // don't trust 'false' if there were errors
+ $stat = is_file( $source ) ? stat( $source ) : false; // regular files only
+ $hadError = $this->untrapWarnings();
+
+ if ( $stat ) {
+ return array(
+ 'mtime' => wfTimestamp( TS_MW, $stat['mtime'] ),
+ 'size' => $stat['size']
+ );
+ } elseif ( !$hadError ) {
+ return false; // file does not exist
+ } else {
+ return null; // failure
+ }
+ }
+
+ /**
+ * @see FileBackendStore::doClearCache()
+ */
+ protected function doClearCache( array $paths = null ) {
+ clearstatcache(); // clear the PHP file stat cache
+ }
+
+ /**
+ * @see FileBackendStore::getFileListInternal()
+ */
+ public function getFileListInternal( $fullCont, $dirRel, array $params ) {
+ list( $b, $shortCont, $r ) = FileBackend::splitStoragePath( $params['dir'] );
+ $contRoot = $this->containerFSRoot( $shortCont, $fullCont ); // must be valid
+ $dir = ( $dirRel != '' ) ? "{$contRoot}/{$dirRel}" : $contRoot;
+ $exists = is_dir( $dir );
+ if ( !$exists ) {
+ wfDebug( __METHOD__ . "() given directory does not exist: '$dir'\n" );
+ return array(); // nothing under this dir
+ }
+ $readable = is_readable( $dir );
+ if ( !$readable ) {
+ wfDebug( __METHOD__ . "() given directory is unreadable: '$dir'\n" );
+ return null; // bad permissions?
+ }
+ return new FSFileBackendFileList( $dir );
+ }
+
+ /**
+ * @see FileBackendStore::getLocalReference()
+ */
+ public function getLocalReference( array $params ) {
+ $source = $this->resolveToFSPath( $params['src'] );
+ if ( $source === null ) {
+ return null;
+ }
+ return new FSFile( $source );
+ }
+
+ /**
+ * @see FileBackendStore::getLocalCopy()
+ */
+ public function getLocalCopy( array $params ) {
+ $source = $this->resolveToFSPath( $params['src'] );
+ if ( $source === null ) {
+ return null;
+ }
+
+ // Create a new temporary file with the same extension...
+ $ext = FileBackend::extensionFromPath( $params['src'] );
+ $tmpFile = TempFSFile::factory( wfBaseName( $source ) . '_', $ext );
+ if ( !$tmpFile ) {
+ return null;
+ }
+ $tmpPath = $tmpFile->getPath();
+
+ // Copy the source file over the temp file
+ $ok = copy( $source, $tmpPath );
+ if ( !$ok ) {
+ return null;
+ }
+
+ $this->chmod( $tmpPath );
+
+ return $tmpFile;
+ }
+
+ /**
+ * Chmod a file, suppressing the warnings
+ *
+ * @param $path string Absolute file system path
+ * @return bool Success
+ */
+ protected function chmod( $path ) {
+ wfSuppressWarnings();
+ $ok = chmod( $path, $this->fileMode );
+ wfRestoreWarnings();
+
+ return $ok;
+ }
+
+ /**
+ * Listen for E_WARNING errors and track whether any happen
+ *
+ * @return bool
+ */
+ protected function trapWarnings() {
+ $this->hadWarningErrors[] = false; // push to stack
+ set_error_handler( array( $this, 'handleWarning' ), E_WARNING );
+ return false; // invoke normal PHP error handler
+ }
+
+ /**
+ * Stop listening for E_WARNING errors and return true if any happened
+ *
+ * @return bool
+ */
+ protected function untrapWarnings() {
+ restore_error_handler(); // restore previous handler
+ return array_pop( $this->hadWarningErrors ); // pop from stack
+ }
+
+ private function handleWarning() {
+ $this->hadWarningErrors[count( $this->hadWarningErrors ) - 1] = true;
+ return true; // suppress from PHP handler
+ }
+}
+
+/**
+ * Wrapper around RecursiveDirectoryIterator that catches
+ * exception or does any custom behavoir that we may want.
+ * Do not use this class from places outside FSFileBackend.
+ *
+ * @ingroup FileBackend
+ */
+class FSFileBackendFileList implements Iterator {
+ /** @var RecursiveIteratorIterator */
+ protected $iter;
+ protected $suffixStart; // integer
+ protected $pos = 0; // integer
+
+ /**
+ * @param $dir string file system directory
+ */
+ public function __construct( $dir ) {
+ $dir = realpath( $dir ); // normalize
+ $this->suffixStart = strlen( $dir ) + 1; // size of "path/to/dir/"
+ try {
+ # Get an iterator that will return leaf nodes (non-directories)
+ if ( MWInit::classExists( 'FilesystemIterator' ) ) { // PHP >= 5.3
+ # RecursiveDirectoryIterator extends FilesystemIterator.
+ # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
+ $flags = FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS;
+ $this->iter = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator( $dir, $flags ) );
+ } else { // PHP < 5.3
+ # RecursiveDirectoryIterator extends DirectoryIterator
+ $this->iter = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator( $dir ) );
+ }
+ } catch ( UnexpectedValueException $e ) {
+ $this->iter = null; // bad permissions? deleted?
+ }
+ }
+
+ public function current() {
+ // Return only the relative path and normalize slashes to FileBackend-style
+ // Make sure to use the realpath since the suffix is based upon that
+ return str_replace( '\\', '/',
+ substr( realpath( $this->iter->current() ), $this->suffixStart ) );
+ }
+
+ public function key() {
+ return $this->pos;
+ }
+
+ public function next() {
+ try {
+ $this->iter->next();
+ } catch ( UnexpectedValueException $e ) {
+ $this->iter = null;
+ }
+ ++$this->pos;
+ }
+
+ public function rewind() {
+ $this->pos = 0;
+ try {
+ $this->iter->rewind();
+ } catch ( UnexpectedValueException $e ) {
+ $this->iter = null;
+ }
+ }
+
+ public function valid() {
+ return $this->iter && $this->iter->valid();
+ }
+}
diff --git a/includes/filerepo/backend/FileBackend.php b/includes/filerepo/backend/FileBackend.php
new file mode 100644
index 00000000..9433bcb4
--- /dev/null
+++ b/includes/filerepo/backend/FileBackend.php
@@ -0,0 +1,1739 @@
+<?php
+/**
+ * @defgroup FileBackend File backend
+ * @ingroup FileRepo
+ *
+ * This module regroup classes meant for MediaWiki to interacts with
+ */
+
+/**
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * Base class for all file backend classes (including multi-write backends).
+ *
+ * This class defines the methods as abstract that subclasses must implement.
+ * Outside callers can assume that all backends will have these functions.
+ *
+ * All "storage paths" are of the format "mwstore://backend/container/path".
+ * The paths use UNIX file system (FS) notation, though any particular backend may
+ * not actually be using a local filesystem. Therefore, the paths are only virtual.
+ *
+ * Backend contents are stored under wiki-specific container names by default.
+ * For legacy reasons, this has no effect for the FS backend class, and per-wiki
+ * segregation must be done by setting the container paths appropriately.
+ *
+ * FS-based backends are somewhat more restrictive due to the existence of real
+ * directory files; a regular file cannot have the same name as a directory. Other
+ * backends with virtual directories may not have this limitation. Callers should
+ * store files in such a way that no files and directories are under the same path.
+ *
+ * Methods should avoid throwing exceptions at all costs.
+ * As a corollary, external dependencies should be kept to a minimum.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+abstract class FileBackend {
+ protected $name; // string; unique backend name
+ protected $wikiId; // string; unique wiki name
+ protected $readOnly; // string; read-only explanation message
+ /** @var LockManager */
+ protected $lockManager;
+
+ /**
+ * Create a new backend instance from configuration.
+ * This should only be called from within FileBackendGroup.
+ *
+ * $config includes:
+ * 'name' : The unique name of this backend.
+ * This should consist of alphanumberic, '-', and '_' characters.
+ * This name should not be changed after use.
+ * 'wikiId' : Prefix to container names that is unique to this wiki.
+ * This should consist of alphanumberic, '-', and '_' characters.
+ * 'lockManager' : Registered name of a file lock manager to use.
+ * 'readOnly' : Write operations are disallowed if this is a non-empty string.
+ * It should be an explanation for the backend being read-only.
+ *
+ * @param $config Array
+ */
+ public function __construct( array $config ) {
+ $this->name = $config['name'];
+ if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
+ throw new MWException( "Backend name `{$this->name}` is invalid." );
+ }
+ $this->wikiId = isset( $config['wikiId'] )
+ ? $config['wikiId']
+ : wfWikiID(); // e.g. "my_wiki-en_"
+ $this->lockManager = ( $config['lockManager'] instanceof LockManager )
+ ? $config['lockManager']
+ : LockManagerGroup::singleton()->get( $config['lockManager'] );
+ $this->readOnly = isset( $config['readOnly'] )
+ ? (string)$config['readOnly']
+ : '';
+ }
+
+ /**
+ * Get the unique backend name.
+ * We may have multiple different backends of the same type.
+ * For example, we can have two Swift backends using different proxies.
+ *
+ * @return string
+ */
+ final public function getName() {
+ return $this->name;
+ }
+
+ /**
+ * Check if this backend is read-only
+ *
+ * @return bool
+ */
+ final public function isReadOnly() {
+ return ( $this->readOnly != '' );
+ }
+
+ /**
+ * Get an explanatory message if this backend is read-only
+ *
+ * @return string|false Returns falls if the backend is not read-only
+ */
+ final public function getReadOnlyReason() {
+ return ( $this->readOnly != '' ) ? $this->readOnly : false;
+ }
+
+ /**
+ * This is the main entry point into the backend for write operations.
+ * Callers supply an ordered list of operations to perform as a transaction.
+ * Files will be locked, the stat cache cleared, and then the operations attempted.
+ * If any serious errors occur, all attempted operations will be rolled back.
+ *
+ * $ops is an array of arrays. The outer array holds a list of operations.
+ * Each inner array is a set of key value pairs that specify an operation.
+ *
+ * Supported operations and their parameters:
+ * a) Create a new file in storage with the contents of a string
+ * array(
+ * 'op' => 'create',
+ * 'dst' => <storage path>,
+ * 'content' => <string of new file contents>,
+ * 'overwrite' => <boolean>,
+ * 'overwriteSame' => <boolean>
+ * )
+ * b) Copy a file system file into storage
+ * array(
+ * 'op' => 'store',
+ * 'src' => <file system path>,
+ * 'dst' => <storage path>,
+ * 'overwrite' => <boolean>,
+ * 'overwriteSame' => <boolean>
+ * )
+ * c) Copy a file within storage
+ * array(
+ * 'op' => 'copy',
+ * 'src' => <storage path>,
+ * 'dst' => <storage path>,
+ * 'overwrite' => <boolean>,
+ * 'overwriteSame' => <boolean>
+ * )
+ * d) Move a file within storage
+ * array(
+ * 'op' => 'move',
+ * 'src' => <storage path>,
+ * 'dst' => <storage path>,
+ * 'overwrite' => <boolean>,
+ * 'overwriteSame' => <boolean>
+ * )
+ * e) Delete a file within storage
+ * array(
+ * 'op' => 'delete',
+ * 'src' => <storage path>,
+ * 'ignoreMissingSource' => <boolean>
+ * )
+ * f) Do nothing (no-op)
+ * array(
+ * 'op' => 'null',
+ * )
+ *
+ * Boolean flags for operations (operation-specific):
+ * 'ignoreMissingSource' : The operation will simply succeed and do
+ * nothing if the source file does not exist.
+ * 'overwrite' : Any destination file will be overwritten.
+ * 'overwriteSame' : An error will not be given if a file already
+ * exists at the destination that has the same
+ * contents as the new contents to be written there.
+ *
+ * $opts is an associative of boolean flags, including:
+ * 'force' : Errors that would normally cause a rollback do not.
+ * The remaining operations are still attempted if any fail.
+ * 'nonLocking' : No locks are acquired for the operations.
+ * This can increase performance for non-critical writes.
+ * This has no effect unless the 'force' flag is set.
+ * 'allowStale' : Don't require the latest available data.
+ * This can increase performance for non-critical writes.
+ * This has no effect unless the 'force' flag is set.
+ *
+ * Remarks on locking:
+ * File system paths given to operations should refer to files that are
+ * already locked or otherwise safe from modification from other processes.
+ * Normally these files will be new temp files, which should be adequate.
+ *
+ * Return value:
+ * This returns a Status, which contains all warnings and fatals that occured
+ * during the operation. The 'failCount', 'successCount', and 'success' members
+ * will reflect each operation attempted. The status will be "OK" unless:
+ * a) unexpected operation errors occurred (network partitions, disk full...)
+ * b) significant operation errors occured and 'force' was not set
+ *
+ * @param $ops Array List of operations to execute in order
+ * @param $opts Array Batch operation options
+ * @return Status
+ */
+ final public function doOperations( array $ops, array $opts = array() ) {
+ if ( $this->isReadOnly() ) {
+ return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+ }
+ if ( empty( $opts['force'] ) ) { // sanity
+ unset( $opts['nonLocking'] );
+ unset( $opts['allowStale'] );
+ }
+ return $this->doOperationsInternal( $ops, $opts );
+ }
+
+ /**
+ * @see FileBackend::doOperations()
+ */
+ abstract protected function doOperationsInternal( array $ops, array $opts );
+
+ /**
+ * Same as doOperations() except it takes a single operation.
+ * If you are doing a batch of operations that should either
+ * all succeed or all fail, then use that function instead.
+ *
+ * @see FileBackend::doOperations()
+ *
+ * @param $op Array Operation
+ * @param $opts Array Operation options
+ * @return Status
+ */
+ final public function doOperation( array $op, array $opts = array() ) {
+ return $this->doOperations( array( $op ), $opts );
+ }
+
+ /**
+ * Performs a single create operation.
+ * This sets $params['op'] to 'create' and passes it to doOperation().
+ *
+ * @see FileBackend::doOperation()
+ *
+ * @param $params Array Operation parameters
+ * @param $opts Array Operation options
+ * @return Status
+ */
+ final public function create( array $params, array $opts = array() ) {
+ $params['op'] = 'create';
+ return $this->doOperation( $params, $opts );
+ }
+
+ /**
+ * Performs a single store operation.
+ * This sets $params['op'] to 'store' and passes it to doOperation().
+ *
+ * @see FileBackend::doOperation()
+ *
+ * @param $params Array Operation parameters
+ * @param $opts Array Operation options
+ * @return Status
+ */
+ final public function store( array $params, array $opts = array() ) {
+ $params['op'] = 'store';
+ return $this->doOperation( $params, $opts );
+ }
+
+ /**
+ * Performs a single copy operation.
+ * This sets $params['op'] to 'copy' and passes it to doOperation().
+ *
+ * @see FileBackend::doOperation()
+ *
+ * @param $params Array Operation parameters
+ * @param $opts Array Operation options
+ * @return Status
+ */
+ final public function copy( array $params, array $opts = array() ) {
+ $params['op'] = 'copy';
+ return $this->doOperation( $params, $opts );
+ }
+
+ /**
+ * Performs a single move operation.
+ * This sets $params['op'] to 'move' and passes it to doOperation().
+ *
+ * @see FileBackend::doOperation()
+ *
+ * @param $params Array Operation parameters
+ * @param $opts Array Operation options
+ * @return Status
+ */
+ final public function move( array $params, array $opts = array() ) {
+ $params['op'] = 'move';
+ return $this->doOperation( $params, $opts );
+ }
+
+ /**
+ * Performs a single delete operation.
+ * This sets $params['op'] to 'delete' and passes it to doOperation().
+ *
+ * @see FileBackend::doOperation()
+ *
+ * @param $params Array Operation parameters
+ * @param $opts Array Operation options
+ * @return Status
+ */
+ final public function delete( array $params, array $opts = array() ) {
+ $params['op'] = 'delete';
+ return $this->doOperation( $params, $opts );
+ }
+
+ /**
+ * Concatenate a list of storage files into a single file system file.
+ * The target path should refer to a file that is already locked or
+ * otherwise safe from modification from other processes. Normally,
+ * the file will be a new temp file, which should be adequate.
+ * $params include:
+ * srcs : ordered source storage paths (e.g. chunk1, chunk2, ...)
+ * dst : file system path to 0-byte temp file
+ *
+ * @param $params Array Operation parameters
+ * @return Status
+ */
+ abstract public function concatenate( array $params );
+
+ /**
+ * Prepare a storage directory for usage.
+ * This will create any required containers and parent directories.
+ * Backends using key/value stores only need to create the container.
+ *
+ * $params include:
+ * dir : storage directory
+ *
+ * @param $params Array
+ * @return Status
+ */
+ final public function prepare( array $params ) {
+ if ( $this->isReadOnly() ) {
+ return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+ }
+ return $this->doPrepare( $params );
+ }
+
+ /**
+ * @see FileBackend::prepare()
+ */
+ abstract protected function doPrepare( array $params );
+
+ /**
+ * Take measures to block web access to a storage directory and
+ * the container it belongs to. FS backends might add .htaccess
+ * files whereas key/value store backends might restrict container
+ * access to the auth user that represents end-users in web request.
+ * This is not guaranteed to actually do anything.
+ *
+ * $params include:
+ * dir : storage directory
+ * noAccess : try to deny file access
+ * noListing : try to deny file listing
+ *
+ * @param $params Array
+ * @return Status
+ */
+ final public function secure( array $params ) {
+ if ( $this->isReadOnly() ) {
+ return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+ }
+ $status = $this->doPrepare( $params ); // dir must exist to restrict it
+ if ( $status->isOK() ) {
+ $status->merge( $this->doSecure( $params ) );
+ }
+ return $status;
+ }
+
+ /**
+ * @see FileBackend::secure()
+ */
+ abstract protected function doSecure( array $params );
+
+ /**
+ * Delete a storage directory if it is empty.
+ * Backends using key/value stores may do nothing unless the directory
+ * is that of an empty container, in which case it should be deleted.
+ *
+ * $params include:
+ * dir : storage directory
+ *
+ * @param $params Array
+ * @return Status
+ */
+ final public function clean( array $params ) {
+ if ( $this->isReadOnly() ) {
+ return Status::newFatal( 'backend-fail-readonly', $this->name, $this->readOnly );
+ }
+ return $this->doClean( $params );
+ }
+
+ /**
+ * @see FileBackend::clean()
+ */
+ abstract protected function doClean( array $params );
+
+ /**
+ * Check if a file exists at a storage path in the backend.
+ * This returns false if only a directory exists at the path.
+ *
+ * $params include:
+ * src : source storage path
+ * latest : use the latest available data
+ *
+ * @param $params Array
+ * @return bool|null Returns null on failure
+ */
+ abstract public function fileExists( array $params );
+
+ /**
+ * Get the last-modified timestamp of the file at a storage path.
+ *
+ * $params include:
+ * src : source storage path
+ * latest : use the latest available data
+ *
+ * @param $params Array
+ * @return string|false TS_MW timestamp or false on failure
+ */
+ abstract public function getFileTimestamp( array $params );
+
+ /**
+ * Get the contents of a file at a storage path in the backend.
+ * This should be avoided for potentially large files.
+ *
+ * $params include:
+ * src : source storage path
+ * latest : use the latest available data
+ *
+ * @param $params Array
+ * @return string|false Returns false on failure
+ */
+ abstract public function getFileContents( array $params );
+
+ /**
+ * Get the size (bytes) of a file at a storage path in the backend.
+ *
+ * $params include:
+ * src : source storage path
+ * latest : use the latest available data
+ *
+ * @param $params Array
+ * @return integer|false Returns false on failure
+ */
+ abstract public function getFileSize( array $params );
+
+ /**
+ * Get quick information about a file at a storage path in the backend.
+ * If the file does not exist, then this returns false.
+ * Otherwise, the result is an associative array that includes:
+ * mtime : the last-modified timestamp (TS_MW)
+ * size : the file size (bytes)
+ * Additional values may be included for internal use only.
+ *
+ * $params include:
+ * src : source storage path
+ * latest : use the latest available data
+ *
+ * @param $params Array
+ * @return Array|false|null Returns null on failure
+ */
+ abstract public function getFileStat( array $params );
+
+ /**
+ * Get a SHA-1 hash of the file at a storage path in the backend.
+ *
+ * $params include:
+ * src : source storage path
+ * latest : use the latest available data
+ *
+ * @param $params Array
+ * @return string|false Hash string or false on failure
+ */
+ abstract public function getFileSha1Base36( array $params );
+
+ /**
+ * Get the properties of the file at a storage path in the backend.
+ * Returns FSFile::placeholderProps() on failure.
+ *
+ * $params include:
+ * src : source storage path
+ * latest : use the latest available data
+ *
+ * @param $params Array
+ * @return Array
+ */
+ abstract public function getFileProps( array $params );
+
+ /**
+ * Stream the file at a storage path in the backend.
+ * If the file does not exists, a 404 error will be given.
+ * Appropriate HTTP headers (Status, Content-Type, Content-Length)
+ * must be sent if streaming began, while none should be sent otherwise.
+ * Implementations should flush the output buffer before sending data.
+ *
+ * $params include:
+ * src : source storage path
+ * headers : additional HTTP headers to send on success
+ * latest : use the latest available data
+ *
+ * @param $params Array
+ * @return Status
+ */
+ abstract public function streamFile( array $params );
+
+ /**
+ * Returns a file system file, identical to the file at a storage path.
+ * The file returned is either:
+ * a) A local copy of the file at a storage path in the backend.
+ * The temporary copy will have the same extension as the source.
+ * b) An original of the file at a storage path in the backend.
+ * Temporary files may be purged when the file object falls out of scope.
+ *
+ * Write operations should *never* be done on this file as some backends
+ * may do internal tracking or may be instances of FileBackendMultiWrite.
+ * In that later case, there are copies of the file that must stay in sync.
+ * Additionally, further calls to this function may return the same file.
+ *
+ * $params include:
+ * src : source storage path
+ * latest : use the latest available data
+ *
+ * @param $params Array
+ * @return FSFile|null Returns null on failure
+ */
+ abstract public function getLocalReference( array $params );
+
+ /**
+ * Get a local copy on disk of the file at a storage path in the backend.
+ * The temporary copy will have the same file extension as the source.
+ * Temporary files may be purged when the file object falls out of scope.
+ *
+ * $params include:
+ * src : source storage path
+ * latest : use the latest available data
+ *
+ * @param $params Array
+ * @return TempFSFile|null Returns null on failure
+ */
+ abstract public function getLocalCopy( array $params );
+
+ /**
+ * Get an iterator to list out all stored files under a storage directory.
+ * If the directory is of the form "mwstore://backend/container",
+ * then all files in the container should be listed.
+ * If the directory is of form "mwstore://backend/container/dir",
+ * then all files under that container directory should be listed.
+ * Results should be storage paths relative to the given directory.
+ *
+ * Storage backends with eventual consistency might return stale data.
+ *
+ * $params include:
+ * dir : storage path directory
+ *
+ * @return Traversable|Array|null Returns null on failure
+ */
+ abstract public function getFileList( array $params );
+
+ /**
+ * Invalidate any in-process file existence and property cache.
+ * If $paths is given, then only the cache for those files will be cleared.
+ *
+ * @param $paths Array Storage paths (optional)
+ * @return void
+ */
+ public function clearCache( array $paths = null ) {}
+
+ /**
+ * Lock the files at the given storage paths in the backend.
+ * This will either lock all the files or none (on failure).
+ *
+ * Callers should consider using getScopedFileLocks() instead.
+ *
+ * @param $paths Array Storage paths
+ * @param $type integer LockManager::LOCK_* constant
+ * @return Status
+ */
+ final public function lockFiles( array $paths, $type ) {
+ return $this->lockManager->lock( $paths, $type );
+ }
+
+ /**
+ * Unlock the files at the given storage paths in the backend.
+ *
+ * @param $paths Array Storage paths
+ * @param $type integer LockManager::LOCK_* constant
+ * @return Status
+ */
+ final public function unlockFiles( array $paths, $type ) {
+ return $this->lockManager->unlock( $paths, $type );
+ }
+
+ /**
+ * Lock the files at the given storage paths in the backend.
+ * This will either lock all the files or none (on failure).
+ * On failure, the status object will be updated with errors.
+ *
+ * Once the return value goes out scope, the locks will be released and
+ * the status updated. Unlock fatals will not change the status "OK" value.
+ *
+ * @param $paths Array Storage paths
+ * @param $type integer LockManager::LOCK_* constant
+ * @param $status Status Status to update on lock/unlock
+ * @return ScopedLock|null Returns null on failure
+ */
+ final public function getScopedFileLocks( array $paths, $type, Status $status ) {
+ return ScopedLock::factory( $this->lockManager, $paths, $type, $status );
+ }
+
+ /**
+ * Check if a given path is a "mwstore://" path.
+ * This does not do any further validation or any existence checks.
+ *
+ * @param $path string
+ * @return bool
+ */
+ final public static function isStoragePath( $path ) {
+ return ( strpos( $path, 'mwstore://' ) === 0 );
+ }
+
+ /**
+ * Split a storage path into a backend name, a container name,
+ * and a relative file path. The relative path may be the empty string.
+ * This does not do any path normalization or traversal checks.
+ *
+ * @param $storagePath string
+ * @return Array (backend, container, rel object) or (null, null, null)
+ */
+ final public static function splitStoragePath( $storagePath ) {
+ if ( self::isStoragePath( $storagePath ) ) {
+ // Remove the "mwstore://" prefix and split the path
+ $parts = explode( '/', substr( $storagePath, 10 ), 3 );
+ if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
+ if ( count( $parts ) == 3 ) {
+ return $parts; // e.g. "backend/container/path"
+ } else {
+ return array( $parts[0], $parts[1], '' ); // e.g. "backend/container"
+ }
+ }
+ }
+ return array( null, null, null );
+ }
+
+ /**
+ * Normalize a storage path by cleaning up directory separators.
+ * Returns null if the path is not of the format of a valid storage path.
+ *
+ * @param $storagePath string
+ * @return string|null
+ */
+ final public static function normalizeStoragePath( $storagePath ) {
+ list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
+ if ( $relPath !== null ) { // must be for this backend
+ $relPath = self::normalizeContainerPath( $relPath );
+ if ( $relPath !== null ) {
+ return ( $relPath != '' )
+ ? "mwstore://{$backend}/{$container}/{$relPath}"
+ : "mwstore://{$backend}/{$container}";
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Validate and normalize a relative storage path.
+ * Null is returned if the path involves directory traversal.
+ * Traversal is insecure for FS backends and broken for others.
+ *
+ * @param $path string Storage path relative to a container
+ * @return string|null
+ */
+ final protected static function normalizeContainerPath( $path ) {
+ // Normalize directory separators
+ $path = strtr( $path, '\\', '/' );
+ // Collapse any consecutive directory separators
+ $path = preg_replace( '![/]{2,}!', '/', $path );
+ // Remove any leading directory separator
+ $path = ltrim( $path, '/' );
+ // Use the same traversal protection as Title::secureAndSplit()
+ if ( strpos( $path, '.' ) !== false ) {
+ if (
+ $path === '.' ||
+ $path === '..' ||
+ strpos( $path, './' ) === 0 ||
+ strpos( $path, '../' ) === 0 ||
+ strpos( $path, '/./' ) !== false ||
+ strpos( $path, '/../' ) !== false
+ ) {
+ return null;
+ }
+ }
+ return $path;
+ }
+
+ /**
+ * Get the parent storage directory of a storage path.
+ * This returns a path like "mwstore://backend/container",
+ * "mwstore://backend/container/...", or null if there is no parent.
+ *
+ * @param $storagePath string
+ * @return string|null
+ */
+ final public static function parentStoragePath( $storagePath ) {
+ $storagePath = dirname( $storagePath );
+ list( $b, $cont, $rel ) = self::splitStoragePath( $storagePath );
+ return ( $rel === null ) ? null : $storagePath;
+ }
+
+ /**
+ * Get the final extension from a storage or FS path
+ *
+ * @param $path string
+ * @return string
+ */
+ final public static function extensionFromPath( $path ) {
+ $i = strrpos( $path, '.' );
+ return strtolower( $i ? substr( $path, $i + 1 ) : '' );
+ }
+}
+
+/**
+ * @brief Base class for all backends associated with a particular storage medium.
+ *
+ * This class defines the methods as abstract that subclasses must implement.
+ * Outside callers should *not* use functions with "Internal" in the name.
+ *
+ * The FileBackend operations are implemented using basic functions
+ * such as storeInternal(), copyInternal(), deleteInternal() and the like.
+ * This class is also responsible for path resolution and sanitization.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+abstract class FileBackendStore extends FileBackend {
+ /** @var Array Map of paths to small (RAM/disk) cache items */
+ protected $cache = array(); // (storage path => key => value)
+ protected $maxCacheSize = 100; // integer; max paths with entries
+ /** @var Array Map of paths to large (RAM/disk) cache items */
+ protected $expensiveCache = array(); // (storage path => key => value)
+ protected $maxExpensiveCacheSize = 10; // integer; max paths with entries
+
+ /** @var Array Map of container names to sharding settings */
+ protected $shardViaHashLevels = array(); // (container name => config array)
+
+ protected $maxFileSize = 1000000000; // integer bytes (1GB)
+
+ /**
+ * Get the maximum allowable file size given backend
+ * medium restrictions and basic performance constraints.
+ * Do not call this function from places outside FileBackend and FileOp.
+ *
+ * @return integer Bytes
+ */
+ final public function maxFileSizeInternal() {
+ return $this->maxFileSize;
+ }
+
+ /**
+ * Check if a file can be created at a given storage path.
+ * FS backends should check if the parent directory exists and the file is writable.
+ * Backends using key/value stores should check if the container exists.
+ *
+ * @param $storagePath string
+ * @return bool
+ */
+ abstract public function isPathUsableInternal( $storagePath );
+
+ /**
+ * Create a file in the backend with the given contents.
+ * Do not call this function from places outside FileBackend and FileOp.
+ *
+ * $params include:
+ * content : the raw file contents
+ * dst : destination storage path
+ * overwrite : overwrite any file that exists at the destination
+ *
+ * @param $params Array
+ * @return Status
+ */
+ final public function createInternal( array $params ) {
+ wfProfileIn( __METHOD__ );
+ if ( strlen( $params['content'] ) > $this->maxFileSizeInternal() ) {
+ $status = Status::newFatal( 'backend-fail-create', $params['dst'] );
+ } else {
+ $status = $this->doCreateInternal( $params );
+ $this->clearCache( array( $params['dst'] ) );
+ }
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::createInternal()
+ */
+ abstract protected function doCreateInternal( array $params );
+
+ /**
+ * Store a file into the backend from a file on disk.
+ * Do not call this function from places outside FileBackend and FileOp.
+ *
+ * $params include:
+ * src : source path on disk
+ * dst : destination storage path
+ * overwrite : overwrite any file that exists at the destination
+ *
+ * @param $params Array
+ * @return Status
+ */
+ final public function storeInternal( array $params ) {
+ wfProfileIn( __METHOD__ );
+ if ( filesize( $params['src'] ) > $this->maxFileSizeInternal() ) {
+ $status = Status::newFatal( 'backend-fail-store', $params['dst'] );
+ } else {
+ $status = $this->doStoreInternal( $params );
+ $this->clearCache( array( $params['dst'] ) );
+ }
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::storeInternal()
+ */
+ abstract protected function doStoreInternal( array $params );
+
+ /**
+ * Copy a file from one storage path to another in the backend.
+ * Do not call this function from places outside FileBackend and FileOp.
+ *
+ * $params include:
+ * src : source storage path
+ * dst : destination storage path
+ * overwrite : overwrite any file that exists at the destination
+ *
+ * @param $params Array
+ * @return Status
+ */
+ final public function copyInternal( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $status = $this->doCopyInternal( $params );
+ $this->clearCache( array( $params['dst'] ) );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::copyInternal()
+ */
+ abstract protected function doCopyInternal( array $params );
+
+ /**
+ * Delete a file at the storage path.
+ * Do not call this function from places outside FileBackend and FileOp.
+ *
+ * $params include:
+ * src : source storage path
+ * ignoreMissingSource : do nothing if the source file does not exist
+ *
+ * @param $params Array
+ * @return Status
+ */
+ final public function deleteInternal( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $status = $this->doDeleteInternal( $params );
+ $this->clearCache( array( $params['src'] ) );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::deleteInternal()
+ */
+ abstract protected function doDeleteInternal( array $params );
+
+ /**
+ * Move a file from one storage path to another in the backend.
+ * Do not call this function from places outside FileBackend and FileOp.
+ *
+ * $params include:
+ * src : source storage path
+ * dst : destination storage path
+ * overwrite : overwrite any file that exists at the destination
+ *
+ * @param $params Array
+ * @return Status
+ */
+ final public function moveInternal( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $status = $this->doMoveInternal( $params );
+ $this->clearCache( array( $params['src'], $params['dst'] ) );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::moveInternal()
+ */
+ protected function doMoveInternal( array $params ) {
+ // Copy source to dest
+ $status = $this->copyInternal( $params );
+ if ( $status->isOK() ) {
+ // Delete source (only fails due to races or medium going down)
+ $status->merge( $this->deleteInternal( array( 'src' => $params['src'] ) ) );
+ $status->setResult( true, $status->value ); // ignore delete() errors
+ }
+ return $status;
+ }
+
+ /**
+ * @see FileBackend::concatenate()
+ */
+ final public function concatenate( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $status = Status::newGood();
+
+ // Try to lock the source files for the scope of this function
+ $scopeLockS = $this->getScopedFileLocks( $params['srcs'], LockManager::LOCK_UW, $status );
+ if ( $status->isOK() ) {
+ // Actually do the concatenation
+ $status->merge( $this->doConcatenate( $params ) );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::concatenate()
+ */
+ protected function doConcatenate( array $params ) {
+ $status = Status::newGood();
+ $tmpPath = $params['dst']; // convenience
+
+ // Check that the specified temp file is valid...
+ wfSuppressWarnings();
+ $ok = ( is_file( $tmpPath ) && !filesize( $tmpPath ) );
+ wfRestoreWarnings();
+ if ( !$ok ) { // not present or not empty
+ $status->fatal( 'backend-fail-opentemp', $tmpPath );
+ return $status;
+ }
+
+ // Build up the temp file using the source chunks (in order)...
+ $tmpHandle = fopen( $tmpPath, 'ab' );
+ if ( $tmpHandle === false ) {
+ $status->fatal( 'backend-fail-opentemp', $tmpPath );
+ return $status;
+ }
+ foreach ( $params['srcs'] as $virtualSource ) {
+ // Get a local FS version of the chunk
+ $tmpFile = $this->getLocalReference( array( 'src' => $virtualSource ) );
+ if ( !$tmpFile ) {
+ $status->fatal( 'backend-fail-read', $virtualSource );
+ return $status;
+ }
+ // Get a handle to the local FS version
+ $sourceHandle = fopen( $tmpFile->getPath(), 'r' );
+ if ( $sourceHandle === false ) {
+ fclose( $tmpHandle );
+ $status->fatal( 'backend-fail-read', $virtualSource );
+ return $status;
+ }
+ // Append chunk to file (pass chunk size to avoid magic quotes)
+ if ( !stream_copy_to_stream( $sourceHandle, $tmpHandle ) ) {
+ fclose( $sourceHandle );
+ fclose( $tmpHandle );
+ $status->fatal( 'backend-fail-writetemp', $tmpPath );
+ return $status;
+ }
+ fclose( $sourceHandle );
+ }
+ if ( !fclose( $tmpHandle ) ) {
+ $status->fatal( 'backend-fail-closetemp', $tmpPath );
+ return $status;
+ }
+
+ clearstatcache(); // temp file changed
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackend::doPrepare()
+ */
+ final protected function doPrepare( array $params ) {
+ wfProfileIn( __METHOD__ );
+
+ $status = Status::newGood();
+ list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+ if ( $dir === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+ wfProfileOut( __METHOD__ );
+ return $status; // invalid storage path
+ }
+
+ if ( $shard !== null ) { // confined to a single container/shard
+ $status->merge( $this->doPrepareInternal( $fullCont, $dir, $params ) );
+ } else { // directory is on several shards
+ wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+ list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
+ $status->merge( $this->doPrepareInternal( "{$fullCont}{$suffix}", $dir, $params ) );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doPrepare()
+ */
+ protected function doPrepareInternal( $container, $dir, array $params ) {
+ return Status::newGood();
+ }
+
+ /**
+ * @see FileBackend::doSecure()
+ */
+ final protected function doSecure( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $status = Status::newGood();
+
+ list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+ if ( $dir === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+ wfProfileOut( __METHOD__ );
+ return $status; // invalid storage path
+ }
+
+ if ( $shard !== null ) { // confined to a single container/shard
+ $status->merge( $this->doSecureInternal( $fullCont, $dir, $params ) );
+ } else { // directory is on several shards
+ wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+ list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
+ $status->merge( $this->doSecureInternal( "{$fullCont}{$suffix}", $dir, $params ) );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doSecure()
+ */
+ protected function doSecureInternal( $container, $dir, array $params ) {
+ return Status::newGood();
+ }
+
+ /**
+ * @see FileBackend::doClean()
+ */
+ final protected function doClean( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $status = Status::newGood();
+
+ list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+ if ( $dir === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
+ wfProfileOut( __METHOD__ );
+ return $status; // invalid storage path
+ }
+
+ // Attempt to lock this directory...
+ $filesLockEx = array( $params['dir'] );
+ $scopedLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
+ if ( !$status->isOK() ) {
+ wfProfileOut( __METHOD__ );
+ return $status; // abort
+ }
+
+ if ( $shard !== null ) { // confined to a single container/shard
+ $status->merge( $this->doCleanInternal( $fullCont, $dir, $params ) );
+ } else { // directory is on several shards
+ wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+ list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ foreach ( $this->getContainerSuffixes( $shortCont ) as $suffix ) {
+ $status->merge( $this->doCleanInternal( "{$fullCont}{$suffix}", $dir, $params ) );
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doClean()
+ */
+ protected function doCleanInternal( $container, $dir, array $params ) {
+ return Status::newGood();
+ }
+
+ /**
+ * @see FileBackend::fileExists()
+ */
+ final public function fileExists( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $stat = $this->getFileStat( $params );
+ wfProfileOut( __METHOD__ );
+ return ( $stat === null ) ? null : (bool)$stat; // null => failure
+ }
+
+ /**
+ * @see FileBackend::getFileTimestamp()
+ */
+ final public function getFileTimestamp( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $stat = $this->getFileStat( $params );
+ wfProfileOut( __METHOD__ );
+ return $stat ? $stat['mtime'] : false;
+ }
+
+ /**
+ * @see FileBackend::getFileSize()
+ */
+ final public function getFileSize( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $stat = $this->getFileStat( $params );
+ wfProfileOut( __METHOD__ );
+ return $stat ? $stat['size'] : false;
+ }
+
+ /**
+ * @see FileBackend::getFileStat()
+ */
+ final public function getFileStat( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $path = self::normalizeStoragePath( $params['src'] );
+ if ( $path === null ) {
+ return false; // invalid storage path
+ }
+ $latest = !empty( $params['latest'] );
+ if ( isset( $this->cache[$path]['stat'] ) ) {
+ // If we want the latest data, check that this cached
+ // value was in fact fetched with the latest available data.
+ if ( !$latest || $this->cache[$path]['stat']['latest'] ) {
+ wfProfileOut( __METHOD__ );
+ return $this->cache[$path]['stat'];
+ }
+ }
+ $stat = $this->doGetFileStat( $params );
+ if ( is_array( $stat ) ) { // don't cache negatives
+ $this->trimCache(); // limit memory
+ $this->cache[$path]['stat'] = $stat;
+ $this->cache[$path]['stat']['latest'] = $latest;
+ }
+ wfProfileOut( __METHOD__ );
+ return $stat;
+ }
+
+ /**
+ * @see FileBackendStore::getFileStat()
+ */
+ abstract protected function doGetFileStat( array $params );
+
+ /**
+ * @see FileBackend::getFileContents()
+ */
+ public function getFileContents( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $tmpFile = $this->getLocalReference( $params );
+ if ( !$tmpFile ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ wfSuppressWarnings();
+ $data = file_get_contents( $tmpFile->getPath() );
+ wfRestoreWarnings();
+ wfProfileOut( __METHOD__ );
+ return $data;
+ }
+
+ /**
+ * @see FileBackend::getFileSha1Base36()
+ */
+ final public function getFileSha1Base36( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $path = $params['src'];
+ if ( isset( $this->cache[$path]['sha1'] ) ) {
+ wfProfileOut( __METHOD__ );
+ return $this->cache[$path]['sha1'];
+ }
+ $hash = $this->doGetFileSha1Base36( $params );
+ if ( $hash ) { // don't cache negatives
+ $this->trimCache(); // limit memory
+ $this->cache[$path]['sha1'] = $hash;
+ }
+ wfProfileOut( __METHOD__ );
+ return $hash;
+ }
+
+ /**
+ * @see FileBackendStore::getFileSha1Base36()
+ */
+ protected function doGetFileSha1Base36( array $params ) {
+ $fsFile = $this->getLocalReference( $params );
+ if ( !$fsFile ) {
+ return false;
+ } else {
+ return $fsFile->getSha1Base36();
+ }
+ }
+
+ /**
+ * @see FileBackend::getFileProps()
+ */
+ final public function getFileProps( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $fsFile = $this->getLocalReference( $params );
+ $props = $fsFile ? $fsFile->getProps() : FSFile::placeholderProps();
+ wfProfileOut( __METHOD__ );
+ return $props;
+ }
+
+ /**
+ * @see FileBackend::getLocalReference()
+ */
+ public function getLocalReference( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $path = $params['src'];
+ if ( isset( $this->expensiveCache[$path]['localRef'] ) ) {
+ wfProfileOut( __METHOD__ );
+ return $this->expensiveCache[$path]['localRef'];
+ }
+ $tmpFile = $this->getLocalCopy( $params );
+ if ( $tmpFile ) { // don't cache negatives
+ $this->trimExpensiveCache(); // limit memory
+ $this->expensiveCache[$path]['localRef'] = $tmpFile;
+ }
+ wfProfileOut( __METHOD__ );
+ return $tmpFile;
+ }
+
+ /**
+ * @see FileBackend::streamFile()
+ */
+ final public function streamFile( array $params ) {
+ wfProfileIn( __METHOD__ );
+ $status = Status::newGood();
+
+ $info = $this->getFileStat( $params );
+ if ( !$info ) { // let StreamFile handle the 404
+ $status->fatal( 'backend-fail-notexists', $params['src'] );
+ }
+
+ // Set output buffer and HTTP headers for stream
+ $extraHeaders = isset( $params['headers'] ) ? $params['headers'] : array();
+ $res = StreamFile::prepareForStream( $params['src'], $info, $extraHeaders );
+ if ( $res == StreamFile::NOT_MODIFIED ) {
+ // do nothing; client cache is up to date
+ } elseif ( $res == StreamFile::READY_STREAM ) {
+ $status = $this->doStreamFile( $params );
+ } else {
+ $status->fatal( 'backend-fail-stream', $params['src'] );
+ }
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::streamFile()
+ */
+ protected function doStreamFile( array $params ) {
+ $status = Status::newGood();
+
+ $fsFile = $this->getLocalReference( $params );
+ if ( !$fsFile ) {
+ $status->fatal( 'backend-fail-stream', $params['src'] );
+ } elseif ( !readfile( $fsFile->getPath() ) ) {
+ $status->fatal( 'backend-fail-stream', $params['src'] );
+ }
+
+ return $status;
+ }
+
+ /**
+ * @copydoc FileBackend::getFileList()
+ */
+ final public function getFileList( array $params ) {
+ list( $fullCont, $dir, $shard ) = $this->resolveStoragePath( $params['dir'] );
+ if ( $dir === null ) { // invalid storage path
+ return null;
+ }
+ if ( $shard !== null ) {
+ // File listing is confined to a single container/shard
+ return $this->getFileListInternal( $fullCont, $dir, $params );
+ } else {
+ wfDebug( __METHOD__ . ": iterating over all container shards.\n" );
+ // File listing spans multiple containers/shards
+ list( $b, $shortCont, $r ) = self::splitStoragePath( $params['dir'] );
+ return new FileBackendStoreShardListIterator( $this,
+ $fullCont, $dir, $this->getContainerSuffixes( $shortCont ), $params );
+ }
+ }
+
+ /**
+ * Do not call this function from places outside FileBackend
+ *
+ * @see FileBackendStore::getFileList()
+ *
+ * @param $container string Resolved container name
+ * @param $dir string Resolved path relative to container
+ * @param $params Array
+ * @return Traversable|Array|null
+ */
+ abstract public function getFileListInternal( $container, $dir, array $params );
+
+ /**
+ * Get the list of supported operations and their corresponding FileOp classes.
+ *
+ * @return Array
+ */
+ protected function supportedOperations() {
+ return array(
+ 'store' => 'StoreFileOp',
+ 'copy' => 'CopyFileOp',
+ 'move' => 'MoveFileOp',
+ 'delete' => 'DeleteFileOp',
+ 'create' => 'CreateFileOp',
+ 'null' => 'NullFileOp'
+ );
+ }
+
+ /**
+ * Return a list of FileOp objects from a list of operations.
+ * Do not call this function from places outside FileBackend.
+ *
+ * The result must have the same number of items as the input.
+ * An exception is thrown if an unsupported operation is requested.
+ *
+ * @param $ops Array Same format as doOperations()
+ * @return Array List of FileOp objects
+ * @throws MWException
+ */
+ final public function getOperations( array $ops ) {
+ $supportedOps = $this->supportedOperations();
+
+ $performOps = array(); // array of FileOp objects
+ // Build up ordered array of FileOps...
+ foreach ( $ops as $operation ) {
+ $opName = $operation['op'];
+ if ( isset( $supportedOps[$opName] ) ) {
+ $class = $supportedOps[$opName];
+ // Get params for this operation
+ $params = $operation;
+ // Append the FileOp class
+ $performOps[] = new $class( $this, $params );
+ } else {
+ throw new MWException( "Operation `$opName` is not supported." );
+ }
+ }
+
+ return $performOps;
+ }
+
+ /**
+ * @see FileBackend::doOperationsInternal()
+ */
+ protected function doOperationsInternal( array $ops, array $opts ) {
+ wfProfileIn( __METHOD__ );
+ $status = Status::newGood();
+
+ // Build up a list of FileOps...
+ $performOps = $this->getOperations( $ops );
+
+ // Acquire any locks as needed...
+ if ( empty( $opts['nonLocking'] ) ) {
+ // Build up a list of files to lock...
+ $filesLockEx = $filesLockSh = array();
+ foreach ( $performOps as $fileOp ) {
+ $filesLockSh = array_merge( $filesLockSh, $fileOp->storagePathsRead() );
+ $filesLockEx = array_merge( $filesLockEx, $fileOp->storagePathsChanged() );
+ }
+ // Optimization: if doing an EX lock anyway, don't also set an SH one
+ $filesLockSh = array_diff( $filesLockSh, $filesLockEx );
+ // Get a shared lock on the parent directory of each path changed
+ $filesLockSh = array_merge( $filesLockSh, array_map( 'dirname', $filesLockEx ) );
+ // Try to lock those files for the scope of this function...
+ $scopeLockS = $this->getScopedFileLocks( $filesLockSh, LockManager::LOCK_UW, $status );
+ $scopeLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
+ if ( !$status->isOK() ) {
+ wfProfileOut( __METHOD__ );
+ return $status; // abort
+ }
+ }
+
+ // Clear any cache entries (after locks acquired)
+ $this->clearCache();
+
+ // Actually attempt the operation batch...
+ $subStatus = FileOp::attemptBatch( $performOps, $opts );
+
+ // Merge errors into status fields
+ $status->merge( $subStatus );
+ $status->success = $subStatus->success; // not done in merge()
+
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
+ /**
+ * @see FileBackend::clearCache()
+ */
+ final public function clearCache( array $paths = null ) {
+ if ( is_array( $paths ) ) {
+ $paths = array_map( 'FileBackend::normalizeStoragePath', $paths );
+ $paths = array_filter( $paths, 'strlen' ); // remove nulls
+ }
+ if ( $paths === null ) {
+ $this->cache = array();
+ $this->expensiveCache = array();
+ } else {
+ foreach ( $paths as $path ) {
+ unset( $this->cache[$path] );
+ unset( $this->expensiveCache[$path] );
+ }
+ }
+ $this->doClearCache( $paths );
+ }
+
+ /**
+ * Clears any additional stat caches for storage paths
+ *
+ * @see FileBackend::clearCache()
+ *
+ * @param $paths Array Storage paths (optional)
+ * @return void
+ */
+ protected function doClearCache( array $paths = null ) {}
+
+ /**
+ * Prune the inexpensive cache if it is too big to add an item
+ *
+ * @return void
+ */
+ protected function trimCache() {
+ if ( count( $this->cache ) >= $this->maxCacheSize ) {
+ reset( $this->cache );
+ unset( $this->cache[key( $this->cache )] );
+ }
+ }
+
+ /**
+ * Prune the expensive cache if it is too big to add an item
+ *
+ * @return void
+ */
+ protected function trimExpensiveCache() {
+ if ( count( $this->expensiveCache ) >= $this->maxExpensiveCacheSize ) {
+ reset( $this->expensiveCache );
+ unset( $this->expensiveCache[key( $this->expensiveCache )] );
+ }
+ }
+
+ /**
+ * Check if a container name is valid.
+ * This checks for for length and illegal characters.
+ *
+ * @param $container string
+ * @return bool
+ */
+ final protected static function isValidContainerName( $container ) {
+ // This accounts for Swift and S3 restrictions while leaving room
+ // for things like '.xxx' (hex shard chars) or '.seg' (segments).
+ // This disallows directory separators or traversal characters.
+ // Note that matching strings URL encode to the same string;
+ // in Swift, the length restriction is *after* URL encoding.
+ return preg_match( '/^[a-z0-9][a-z0-9-_]{0,199}$/i', $container );
+ }
+
+ /**
+ * Splits a storage path into an internal container name,
+ * an internal relative file name, and a container shard suffix.
+ * Any shard suffix is already appended to the internal container name.
+ * This also checks that the storage path is valid and within this backend.
+ *
+ * If the container is sharded but a suffix could not be determined,
+ * this means that the path can only refer to a directory and can only
+ * be scanned by looking in all the container shards.
+ *
+ * @param $storagePath string
+ * @return Array (container, path, container suffix) or (null, null, null) if invalid
+ */
+ final protected function resolveStoragePath( $storagePath ) {
+ list( $backend, $container, $relPath ) = self::splitStoragePath( $storagePath );
+ if ( $backend === $this->name ) { // must be for this backend
+ $relPath = self::normalizeContainerPath( $relPath );
+ if ( $relPath !== null ) {
+ // Get shard for the normalized path if this container is sharded
+ $cShard = $this->getContainerShard( $container, $relPath );
+ // Validate and sanitize the relative path (backend-specific)
+ $relPath = $this->resolveContainerPath( $container, $relPath );
+ if ( $relPath !== null ) {
+ // Prepend any wiki ID prefix to the container name
+ $container = $this->fullContainerName( $container );
+ if ( self::isValidContainerName( $container ) ) {
+ // Validate and sanitize the container name (backend-specific)
+ $container = $this->resolveContainerName( "{$container}{$cShard}" );
+ if ( $container !== null ) {
+ return array( $container, $relPath, $cShard );
+ }
+ }
+ }
+ }
+ }
+ return array( null, null, null );
+ }
+
+ /**
+ * Like resolveStoragePath() except null values are returned if
+ * the container is sharded and the shard could not be determined.
+ *
+ * @see FileBackendStore::resolveStoragePath()
+ *
+ * @param $storagePath string
+ * @return Array (container, path) or (null, null) if invalid
+ */
+ final protected function resolveStoragePathReal( $storagePath ) {
+ list( $container, $relPath, $cShard ) = $this->resolveStoragePath( $storagePath );
+ if ( $cShard !== null ) {
+ return array( $container, $relPath );
+ }
+ return array( null, null );
+ }
+
+ /**
+ * Get the container name shard suffix for a given path.
+ * Any empty suffix means the container is not sharded.
+ *
+ * @param $container string Container name
+ * @param $relStoragePath string Storage path relative to the container
+ * @return string|null Returns null if shard could not be determined
+ */
+ final protected function getContainerShard( $container, $relPath ) {
+ list( $levels, $base, $repeat ) = $this->getContainerHashLevels( $container );
+ if ( $levels == 1 || $levels == 2 ) {
+ // Hash characters are either base 16 or 36
+ $char = ( $base == 36 ) ? '[0-9a-z]' : '[0-9a-f]';
+ // Get a regex that represents the shard portion of paths.
+ // The concatenation of the captures gives us the shard.
+ if ( $levels === 1 ) { // 16 or 36 shards per container
+ $hashDirRegex = '(' . $char . ')';
+ } else { // 256 or 1296 shards per container
+ if ( $repeat ) { // verbose hash dir format (e.g. "a/ab/abc")
+ $hashDirRegex = $char . '/(' . $char . '{2})';
+ } else { // short hash dir format (e.g. "a/b/c")
+ $hashDirRegex = '(' . $char . ')/(' . $char . ')';
+ }
+ }
+ // Allow certain directories to be above the hash dirs so as
+ // to work with FileRepo (e.g. "archive/a/ab" or "temp/a/ab").
+ // They must be 2+ chars to avoid any hash directory ambiguity.
+ $m = array();
+ if ( preg_match( "!^(?:[^/]{2,}/)*$hashDirRegex(?:/|$)!", $relPath, $m ) ) {
+ return '.' . implode( '', array_slice( $m, 1 ) );
+ }
+ return null; // failed to match
+ }
+ return ''; // no sharding
+ }
+
+ /**
+ * Get the sharding config for a container.
+ * If greater than 0, then all file storage paths within
+ * the container are required to be hashed accordingly.
+ *
+ * @param $container string
+ * @return Array (integer levels, integer base, repeat flag) or (0, 0, false)
+ */
+ final protected function getContainerHashLevels( $container ) {
+ if ( isset( $this->shardViaHashLevels[$container] ) ) {
+ $config = $this->shardViaHashLevels[$container];
+ $hashLevels = (int)$config['levels'];
+ if ( $hashLevels == 1 || $hashLevels == 2 ) {
+ $hashBase = (int)$config['base'];
+ if ( $hashBase == 16 || $hashBase == 36 ) {
+ return array( $hashLevels, $hashBase, $config['repeat'] );
+ }
+ }
+ }
+ return array( 0, 0, false ); // no sharding
+ }
+
+ /**
+ * Get a list of full container shard suffixes for a container
+ *
+ * @param $container string
+ * @return Array
+ */
+ final protected function getContainerSuffixes( $container ) {
+ $shards = array();
+ list( $digits, $base ) = $this->getContainerHashLevels( $container );
+ if ( $digits > 0 ) {
+ $numShards = pow( $base, $digits );
+ for ( $index = 0; $index < $numShards; $index++ ) {
+ $shards[] = '.' . wfBaseConvert( $index, 10, $base, $digits );
+ }
+ }
+ return $shards;
+ }
+
+ /**
+ * Get the full container name, including the wiki ID prefix
+ *
+ * @param $container string
+ * @return string
+ */
+ final protected function fullContainerName( $container ) {
+ if ( $this->wikiId != '' ) {
+ return "{$this->wikiId}-$container";
+ } else {
+ return $container;
+ }
+ }
+
+ /**
+ * Resolve a container name, checking if it's allowed by the backend.
+ * This is intended for internal use, such as encoding illegal chars.
+ * Subclasses can override this to be more restrictive.
+ *
+ * @param $container string
+ * @return string|null
+ */
+ protected function resolveContainerName( $container ) {
+ return $container;
+ }
+
+ /**
+ * Resolve a relative storage path, checking if it's allowed by the backend.
+ * This is intended for internal use, such as encoding illegal chars or perhaps
+ * getting absolute paths (e.g. FS based backends). Note that the relative path
+ * may be the empty string (e.g. the path is simply to the container).
+ *
+ * @param $container string Container name
+ * @param $relStoragePath string Storage path relative to the container
+ * @return string|null Path or null if not valid
+ */
+ protected function resolveContainerPath( $container, $relStoragePath ) {
+ return $relStoragePath;
+ }
+}
+
+/**
+ * FileBackendStore helper function to handle file listings that span container shards.
+ * Do not use this class from places outside of FileBackendStore.
+ *
+ * @ingroup FileBackend
+ */
+class FileBackendStoreShardListIterator implements Iterator {
+ /* @var FileBackendStore */
+ protected $backend;
+ /* @var Array */
+ protected $params;
+ /* @var Array */
+ protected $shardSuffixes;
+ protected $container; // string
+ protected $directory; // string
+
+ /* @var Traversable */
+ protected $iter;
+ protected $curShard = 0; // integer
+ protected $pos = 0; // integer
+
+ /**
+ * @param $backend FileBackendStore
+ * @param $container string Full storage container name
+ * @param $dir string Storage directory relative to container
+ * @param $suffixes Array List of container shard suffixes
+ * @param $params Array
+ */
+ public function __construct(
+ FileBackendStore $backend, $container, $dir, array $suffixes, array $params
+ ) {
+ $this->backend = $backend;
+ $this->container = $container;
+ $this->directory = $dir;
+ $this->shardSuffixes = $suffixes;
+ $this->params = $params;
+ }
+
+ public function current() {
+ if ( is_array( $this->iter ) ) {
+ return current( $this->iter );
+ } else {
+ return $this->iter->current();
+ }
+ }
+
+ public function key() {
+ return $this->pos;
+ }
+
+ public function next() {
+ ++$this->pos;
+ if ( is_array( $this->iter ) ) {
+ next( $this->iter );
+ } else {
+ $this->iter->next();
+ }
+ // Find the next non-empty shard if no elements are left
+ $this->nextShardIteratorIfNotValid();
+ }
+
+ /**
+ * If the iterator for this container shard is out of items,
+ * then move on to the next container that has items.
+ * If there are none, then it advances to the last container.
+ */
+ protected function nextShardIteratorIfNotValid() {
+ while ( !$this->valid() ) {
+ if ( ++$this->curShard >= count( $this->shardSuffixes ) ) {
+ break; // no more container shards
+ }
+ $this->setIteratorFromCurrentShard();
+ }
+ }
+
+ protected function setIteratorFromCurrentShard() {
+ $suffix = $this->shardSuffixes[$this->curShard];
+ $this->iter = $this->backend->getFileListInternal(
+ "{$this->container}{$suffix}", $this->directory, $this->params );
+ }
+
+ public function rewind() {
+ $this->pos = 0;
+ $this->curShard = 0;
+ $this->setIteratorFromCurrentShard();
+ // Find the next non-empty shard if this one has no elements
+ $this->nextShardIteratorIfNotValid();
+ }
+
+ public function valid() {
+ if ( $this->iter == null ) {
+ return false; // some failure?
+ } elseif ( is_array( $this->iter ) ) {
+ return ( current( $this->iter ) !== false ); // no paths can have this value
+ } else {
+ return $this->iter->valid();
+ }
+ }
+}
diff --git a/includes/filerepo/backend/FileBackendGroup.php b/includes/filerepo/backend/FileBackendGroup.php
new file mode 100644
index 00000000..73815cfb
--- /dev/null
+++ b/includes/filerepo/backend/FileBackendGroup.php
@@ -0,0 +1,156 @@
+<?php
+/**
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class to handle file backend registration
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+class FileBackendGroup {
+ /**
+ * @var FileBackendGroup
+ */
+ protected static $instance = null;
+
+ /** @var Array (name => ('class' => string, 'config' => array, 'instance' => object)) */
+ protected $backends = array();
+
+ protected function __construct() {}
+ protected function __clone() {}
+
+ /**
+ * @return FileBackendGroup
+ */
+ public static function singleton() {
+ if ( self::$instance == null ) {
+ self::$instance = new self();
+ self::$instance->initFromGlobals();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Destroy the singleton instance
+ *
+ * @return void
+ */
+ public static function destroySingleton() {
+ self::$instance = null;
+ }
+
+ /**
+ * Register file backends from the global variables
+ *
+ * @return void
+ */
+ protected function initFromGlobals() {
+ global $wgLocalFileRepo, $wgForeignFileRepos, $wgFileBackends;
+
+ // Register explicitly defined backends
+ $this->register( $wgFileBackends );
+
+ $autoBackends = array();
+ // Automatically create b/c backends for file repos...
+ $repos = array_merge( $wgForeignFileRepos, array( $wgLocalFileRepo ) );
+ foreach ( $repos as $info ) {
+ $backendName = $info['backend'];
+ if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
+ continue; // already defined (or set to the object for some reason)
+ }
+ $repoName = $info['name'];
+ // Local vars that used to be FSRepo members...
+ $directory = $info['directory'];
+ $deletedDir = isset( $info['deletedDir'] )
+ ? $info['deletedDir']
+ : false; // deletion disabled
+ $thumbDir = isset( $info['thumbDir'] )
+ ? $info['thumbDir']
+ : "{$directory}/thumb";
+ $fileMode = isset( $info['fileMode'] )
+ ? $info['fileMode']
+ : 0644;
+ // Get the FS backend configuration
+ $autoBackends[] = array(
+ 'name' => $backendName,
+ 'class' => 'FSFileBackend',
+ 'lockManager' => 'fsLockManager',
+ 'containerPaths' => array(
+ "{$repoName}-public" => "{$directory}",
+ "{$repoName}-thumb" => $thumbDir,
+ "{$repoName}-deleted" => $deletedDir,
+ "{$repoName}-temp" => "{$directory}/temp"
+ ),
+ 'fileMode' => $fileMode,
+ );
+ }
+
+ // Register implicitly defined backends
+ $this->register( $autoBackends );
+ }
+
+ /**
+ * Register an array of file backend configurations
+ *
+ * @param $configs Array
+ * @return void
+ * @throws MWException
+ */
+ protected function register( array $configs ) {
+ foreach ( $configs as $config ) {
+ if ( !isset( $config['name'] ) ) {
+ throw new MWException( "Cannot register a backend with no name." );
+ }
+ $name = $config['name'];
+ if ( !isset( $config['class'] ) ) {
+ throw new MWException( "Cannot register backend `{$name}` with no class." );
+ }
+ $class = $config['class'];
+
+ unset( $config['class'] ); // backend won't need this
+ $this->backends[$name] = array(
+ 'class' => $class,
+ 'config' => $config,
+ 'instance' => null
+ );
+ }
+ }
+
+ /**
+ * Get the backend object with a given name
+ *
+ * @param $name string
+ * @return FileBackend
+ * @throws MWException
+ */
+ public function get( $name ) {
+ if ( !isset( $this->backends[$name] ) ) {
+ throw new MWException( "No backend defined with the name `$name`." );
+ }
+ // Lazy-load the actual backend instance
+ if ( !isset( $this->backends[$name]['instance'] ) ) {
+ $class = $this->backends[$name]['class'];
+ $config = $this->backends[$name]['config'];
+ $this->backends[$name]['instance'] = new $class( $config );
+ }
+ return $this->backends[$name]['instance'];
+ }
+
+ /**
+ * Get an appropriate backend object from a storage path
+ *
+ * @param $storagePath string
+ * @return FileBackend|null Backend or null on failure
+ */
+ public function backendFromPath( $storagePath ) {
+ list( $backend, $c, $p ) = FileBackend::splitStoragePath( $storagePath );
+ if ( $backend !== null && isset( $this->backends[$backend] ) ) {
+ return $this->get( $backend );
+ }
+ return null;
+ }
+}
diff --git a/includes/filerepo/backend/FileBackendMultiWrite.php b/includes/filerepo/backend/FileBackendMultiWrite.php
new file mode 100644
index 00000000..c0f1ac57
--- /dev/null
+++ b/includes/filerepo/backend/FileBackendMultiWrite.php
@@ -0,0 +1,420 @@
+<?php
+/**
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * This class defines a multi-write backend. Multiple backends can be
+ * registered to this proxy backend and it will act as a single backend.
+ * Use this when all access to those backends is through this proxy backend.
+ * At least one of the backends must be declared the "master" backend.
+ *
+ * Only use this class when transitioning from one storage system to another.
+ *
+ * Read operations are only done on the 'master' backend for consistency.
+ * Write operations are performed on all backends, in the order defined.
+ * If an operation fails on one backend it will be rolled back from the others.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+class FileBackendMultiWrite extends FileBackend {
+ /** @var Array Prioritized list of FileBackendStore objects */
+ protected $backends = array(); // array of (backend index => backends)
+ protected $masterIndex = -1; // integer; index of master backend
+ protected $syncChecks = 0; // integer bitfield
+
+ /* Possible internal backend consistency checks */
+ const CHECK_SIZE = 1;
+ const CHECK_TIME = 2;
+
+ /**
+ * Construct a proxy backend that consists of several internal backends.
+ * Additional $config params include:
+ * 'backends' : Array of backend config and multi-backend settings.
+ * Each value is the config used in the constructor of a
+ * FileBackendStore class, but with these additional settings:
+ * 'class' : The name of the backend class
+ * 'isMultiMaster' : This must be set for one backend.
+ * 'syncChecks' : Integer bitfield of internal backend sync checks to perform.
+ * Possible bits include self::CHECK_SIZE and self::CHECK_TIME.
+ * The checks are done before allowing any file operations.
+ * @param $config Array
+ */
+ public function __construct( array $config ) {
+ parent::__construct( $config );
+ $namesUsed = array();
+ // Construct backends here rather than via registration
+ // to keep these backends hidden from outside the proxy.
+ foreach ( $config['backends'] as $index => $config ) {
+ $name = $config['name'];
+ if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
+ throw new MWException( "Two or more backends defined with the name $name." );
+ }
+ $namesUsed[$name] = 1;
+ if ( !isset( $config['class'] ) ) {
+ throw new MWException( 'No class given for a backend config.' );
+ }
+ $class = $config['class'];
+ $this->backends[$index] = new $class( $config );
+ if ( !empty( $config['isMultiMaster'] ) ) {
+ if ( $this->masterIndex >= 0 ) {
+ throw new MWException( 'More than one master backend defined.' );
+ }
+ $this->masterIndex = $index;
+ }
+ }
+ if ( $this->masterIndex < 0 ) { // need backends and must have a master
+ throw new MWException( 'No master backend defined.' );
+ }
+ $this->syncChecks = isset( $config['syncChecks'] )
+ ? $config['syncChecks']
+ : self::CHECK_SIZE;
+ }
+
+ /**
+ * @see FileBackend::doOperationsInternal()
+ */
+ final protected function doOperationsInternal( array $ops, array $opts ) {
+ $status = Status::newGood();
+
+ $performOps = array(); // list of FileOp objects
+ $filesRead = $filesChanged = array(); // storage paths used
+ // Build up a list of FileOps. The list will have all the ops
+ // for one backend, then all the ops for the next, and so on.
+ // These batches of ops are all part of a continuous array.
+ // Also build up a list of files read/changed...
+ foreach ( $this->backends as $index => $backend ) {
+ $backendOps = $this->substOpBatchPaths( $ops, $backend );
+ // Add on the operation batch for this backend
+ $performOps = array_merge( $performOps, $backend->getOperations( $backendOps ) );
+ if ( $index == 0 ) { // first batch
+ // Get the files used for these operations. Each backend has a batch of
+ // the same operations, so we only need to get them from the first batch.
+ foreach ( $performOps as $fileOp ) {
+ $filesRead = array_merge( $filesRead, $fileOp->storagePathsRead() );
+ $filesChanged = array_merge( $filesChanged, $fileOp->storagePathsChanged() );
+ }
+ // Get the paths under the proxy backend's name
+ $filesRead = $this->unsubstPaths( $filesRead );
+ $filesChanged = $this->unsubstPaths( $filesChanged );
+ }
+ }
+
+ // Try to lock those files for the scope of this function...
+ if ( empty( $opts['nonLocking'] ) ) {
+ $filesLockSh = array_diff( $filesRead, $filesChanged ); // optimization
+ $filesLockEx = $filesChanged;
+ // Get a shared lock on the parent directory of each path changed
+ $filesLockSh = array_merge( $filesLockSh, array_map( 'dirname', $filesLockEx ) );
+ // Try to lock those files for the scope of this function...
+ $scopeLockS = $this->getScopedFileLocks( $filesLockSh, LockManager::LOCK_UW, $status );
+ $scopeLockE = $this->getScopedFileLocks( $filesLockEx, LockManager::LOCK_EX, $status );
+ if ( !$status->isOK() ) {
+ return $status; // abort
+ }
+ }
+
+ // Clear any cache entries (after locks acquired)
+ $this->clearCache();
+
+ // Do a consistency check to see if the backends agree
+ if ( count( $this->backends ) > 1 ) {
+ $status->merge( $this->consistencyCheck( array_merge( $filesRead, $filesChanged ) ) );
+ if ( !$status->isOK() ) {
+ return $status; // abort
+ }
+ }
+
+ // Actually attempt the operation batch...
+ $subStatus = FileOp::attemptBatch( $performOps, $opts );
+
+ $success = array();
+ $failCount = $successCount = 0;
+ // Make 'success', 'successCount', and 'failCount' fields reflect
+ // the overall operation, rather than all the batches for each backend.
+ // Do this by only using success values from the master backend's batch.
+ $batchStart = $this->masterIndex * count( $ops );
+ $batchEnd = $batchStart + count( $ops ) - 1;
+ for ( $i = $batchStart; $i <= $batchEnd; $i++ ) {
+ if ( !isset( $subStatus->success[$i] ) ) {
+ break; // failed out before trying this op
+ } elseif ( $subStatus->success[$i] ) {
+ ++$successCount;
+ } else {
+ ++$failCount;
+ }
+ $success[] = $subStatus->success[$i];
+ }
+ $subStatus->success = $success;
+ $subStatus->successCount = $successCount;
+ $subStatus->failCount = $failCount;
+
+ // Merge errors into status fields
+ $status->merge( $subStatus );
+ $status->success = $subStatus->success; // not done in merge()
+
+ return $status;
+ }
+
+ /**
+ * Check that a set of files are consistent across all internal backends
+ *
+ * @param $paths Array
+ * @return Status
+ */
+ public function consistencyCheck( array $paths ) {
+ $status = Status::newGood();
+ if ( $this->syncChecks == 0 ) {
+ return $status; // skip checks
+ }
+
+ $mBackend = $this->backends[$this->masterIndex];
+ foreach ( array_unique( $paths ) as $path ) {
+ $params = array( 'src' => $path, 'latest' => true );
+ // Stat the file on the 'master' backend
+ $mStat = $mBackend->getFileStat( $this->substOpPaths( $params, $mBackend ) );
+ // Check of all clone backends agree with the master...
+ foreach ( $this->backends as $index => $cBackend ) {
+ if ( $index === $this->masterIndex ) {
+ continue; // master
+ }
+ $cStat = $cBackend->getFileStat( $this->substOpPaths( $params, $cBackend ) );
+ if ( $mStat ) { // file is in master
+ if ( !$cStat ) { // file should exist
+ $status->fatal( 'backend-fail-synced', $path );
+ continue;
+ }
+ if ( $this->syncChecks & self::CHECK_SIZE ) {
+ if ( $cStat['size'] != $mStat['size'] ) { // wrong size
+ $status->fatal( 'backend-fail-synced', $path );
+ continue;
+ }
+ }
+ if ( $this->syncChecks & self::CHECK_TIME ) {
+ $mTs = wfTimestamp( TS_UNIX, $mStat['mtime'] );
+ $cTs = wfTimestamp( TS_UNIX, $cStat['mtime'] );
+ if ( abs( $mTs - $cTs ) > 30 ) { // outdated file somewhere
+ $status->fatal( 'backend-fail-synced', $path );
+ continue;
+ }
+ }
+ } else { // file is not in master
+ if ( $cStat ) { // file should not exist
+ $status->fatal( 'backend-fail-synced', $path );
+ }
+ }
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * Substitute the backend name in storage path parameters
+ * for a set of operations with that of a given internal backend.
+ *
+ * @param $ops Array List of file operation arrays
+ * @param $backend FileBackendStore
+ * @return Array
+ */
+ protected function substOpBatchPaths( array $ops, FileBackendStore $backend ) {
+ $newOps = array(); // operations
+ foreach ( $ops as $op ) {
+ $newOp = $op; // operation
+ foreach ( array( 'src', 'srcs', 'dst', 'dir' ) as $par ) {
+ if ( isset( $newOp[$par] ) ) { // string or array
+ $newOp[$par] = $this->substPaths( $newOp[$par], $backend );
+ }
+ }
+ $newOps[] = $newOp;
+ }
+ return $newOps;
+ }
+
+ /**
+ * Same as substOpBatchPaths() but for a single operation
+ *
+ * @param $op File operation array
+ * @param $backend FileBackendStore
+ * @return Array
+ */
+ protected function substOpPaths( array $ops, FileBackendStore $backend ) {
+ $newOps = $this->substOpBatchPaths( array( $ops ), $backend );
+ return $newOps[0];
+ }
+
+ /**
+ * Substitute the backend of storage paths with an internal backend's name
+ *
+ * @param $paths Array|string List of paths or single string path
+ * @param $backend FileBackendStore
+ * @return Array|string
+ */
+ protected function substPaths( $paths, FileBackendStore $backend ) {
+ return preg_replace(
+ '!^mwstore://' . preg_quote( $this->name ) . '/!',
+ StringUtils::escapeRegexReplacement( "mwstore://{$backend->getName()}/" ),
+ $paths // string or array
+ );
+ }
+
+ /**
+ * Substitute the backend of internal storage paths with the proxy backend's name
+ *
+ * @param $paths Array|string List of paths or single string path
+ * @return Array|string
+ */
+ protected function unsubstPaths( $paths ) {
+ return preg_replace(
+ '!^mwstore://([^/]+)!',
+ StringUtils::escapeRegexReplacement( "mwstore://{$this->name}" ),
+ $paths // string or array
+ );
+ }
+
+ /**
+ * @see FileBackend::doPrepare()
+ */
+ public function doPrepare( array $params ) {
+ $status = Status::newGood();
+ foreach ( $this->backends as $backend ) {
+ $realParams = $this->substOpPaths( $params, $backend );
+ $status->merge( $backend->doPrepare( $realParams ) );
+ }
+ return $status;
+ }
+
+ /**
+ * @see FileBackend::doSecure()
+ */
+ public function doSecure( array $params ) {
+ $status = Status::newGood();
+ foreach ( $this->backends as $backend ) {
+ $realParams = $this->substOpPaths( $params, $backend );
+ $status->merge( $backend->doSecure( $realParams ) );
+ }
+ return $status;
+ }
+
+ /**
+ * @see FileBackend::doClean()
+ */
+ public function doClean( array $params ) {
+ $status = Status::newGood();
+ foreach ( $this->backends as $backend ) {
+ $realParams = $this->substOpPaths( $params, $backend );
+ $status->merge( $backend->doClean( $realParams ) );
+ }
+ return $status;
+ }
+
+ /**
+ * @see FileBackend::getFileList()
+ */
+ public function concatenate( array $params ) {
+ // We are writing to an FS file, so we don't need to do this per-backend
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->concatenate( $realParams );
+ }
+
+ /**
+ * @see FileBackend::fileExists()
+ */
+ public function fileExists( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->fileExists( $realParams );
+ }
+
+ /**
+ * @see FileBackend::getFileTimestamp()
+ */
+ public function getFileTimestamp( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->getFileTimestamp( $realParams );
+ }
+
+ /**
+ * @see FileBackend::getFileSize()
+ */
+ public function getFileSize( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->getFileSize( $realParams );
+ }
+
+ /**
+ * @see FileBackend::getFileStat()
+ */
+ public function getFileStat( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->getFileStat( $realParams );
+ }
+
+ /**
+ * @see FileBackend::getFileContents()
+ */
+ public function getFileContents( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->getFileContents( $realParams );
+ }
+
+ /**
+ * @see FileBackend::getFileSha1Base36()
+ */
+ public function getFileSha1Base36( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->getFileSha1Base36( $realParams );
+ }
+
+ /**
+ * @see FileBackend::getFileProps()
+ */
+ public function getFileProps( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->getFileProps( $realParams );
+ }
+
+ /**
+ * @see FileBackend::streamFile()
+ */
+ public function streamFile( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->streamFile( $realParams );
+ }
+
+ /**
+ * @see FileBackend::getLocalReference()
+ */
+ public function getLocalReference( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->getLocalReference( $realParams );
+ }
+
+ /**
+ * @see FileBackend::getLocalCopy()
+ */
+ public function getLocalCopy( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->getLocalCopy( $realParams );
+ }
+
+ /**
+ * @see FileBackend::getFileList()
+ */
+ public function getFileList( array $params ) {
+ $realParams = $this->substOpPaths( $params, $this->backends[$this->masterIndex] );
+ return $this->backends[$this->masterIndex]->getFileList( $realParams );
+ }
+
+ /**
+ * @see FileBackend::clearCache()
+ */
+ public function clearCache( array $paths = null ) {
+ foreach ( $this->backends as $backend ) {
+ $realPaths = is_array( $paths ) ? $this->substPaths( $paths, $backend ) : null;
+ $backend->clearCache( $realPaths );
+ }
+ }
+}
diff --git a/includes/filerepo/backend/FileOp.php b/includes/filerepo/backend/FileOp.php
new file mode 100644
index 00000000..5844c9f2
--- /dev/null
+++ b/includes/filerepo/backend/FileOp.php
@@ -0,0 +1,697 @@
+<?php
+/**
+ * @file
+ * @ingroup FileBackend
+ * @author Aaron Schulz
+ */
+
+/**
+ * Helper class for representing operations with transaction support.
+ * Do not use this class from places outside FileBackend.
+ *
+ * Methods called from attemptBatch() should avoid throwing exceptions at all costs.
+ * FileOp objects should be lightweight in order to support large arrays in memory.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+abstract class FileOp {
+ /** @var Array */
+ protected $params = array();
+ /** @var FileBackendStore */
+ protected $backend;
+
+ protected $state = self::STATE_NEW; // integer
+ protected $failed = false; // boolean
+ protected $useLatest = true; // boolean
+
+ protected $sourceSha1; // string
+ protected $destSameAsSource; // boolean
+
+ /* Object life-cycle */
+ const STATE_NEW = 1;
+ const STATE_CHECKED = 2;
+ const STATE_ATTEMPTED = 3;
+
+ /* Timeout related parameters */
+ const MAX_BATCH_SIZE = 1000;
+ const TIME_LIMIT_SEC = 300; // 5 minutes
+
+ /**
+ * Build a new file operation transaction
+ *
+ * @params $backend FileBackendStore
+ * @params $params Array
+ * @throws MWException
+ */
+ final public function __construct( FileBackendStore $backend, array $params ) {
+ $this->backend = $backend;
+ list( $required, $optional ) = $this->allowedParams();
+ foreach ( $required as $name ) {
+ if ( isset( $params[$name] ) ) {
+ $this->params[$name] = $params[$name];
+ } else {
+ throw new MWException( "File operation missing parameter '$name'." );
+ }
+ }
+ foreach ( $optional as $name ) {
+ if ( isset( $params[$name] ) ) {
+ $this->params[$name] = $params[$name];
+ }
+ }
+ $this->params = $params;
+ }
+
+ /**
+ * Allow stale data for file reads and existence checks
+ *
+ * @return void
+ */
+ final protected function allowStaleReads() {
+ $this->useLatest = false;
+ }
+
+ /**
+ * Attempt a series of file operations.
+ * Callers are responsible for handling file locking.
+ *
+ * $opts is an array of options, including:
+ * 'force' : Errors that would normally cause a rollback do not.
+ * The remaining operations are still attempted if any fail.
+ * 'allowStale' : Don't require the latest available data.
+ * This can increase performance for non-critical writes.
+ * This has no effect unless the 'force' flag is set.
+ *
+ * The resulting Status will be "OK" unless:
+ * a) unexpected operation errors occurred (network partitions, disk full...)
+ * b) significant operation errors occured and 'force' was not set
+ *
+ * @param $performOps Array List of FileOp operations
+ * @param $opts Array Batch operation options
+ * @return Status
+ */
+ final public static function attemptBatch( array $performOps, array $opts ) {
+ $status = Status::newGood();
+
+ $allowStale = !empty( $opts['allowStale'] );
+ $ignoreErrors = !empty( $opts['force'] );
+
+ $n = count( $performOps );
+ if ( $n > self::MAX_BATCH_SIZE ) {
+ $status->fatal( 'backend-fail-batchsize', $n, self::MAX_BATCH_SIZE );
+ return $status;
+ }
+
+ $predicates = FileOp::newPredicates(); // account for previous op in prechecks
+ // Do pre-checks for each operation; abort on failure...
+ foreach ( $performOps as $index => $fileOp ) {
+ if ( $allowStale ) {
+ $fileOp->allowStaleReads(); // allow potentially stale reads
+ }
+ $subStatus = $fileOp->precheck( $predicates );
+ $status->merge( $subStatus );
+ if ( !$subStatus->isOK() ) { // operation failed?
+ $status->success[$index] = false;
+ ++$status->failCount;
+ if ( !$ignoreErrors ) {
+ return $status; // abort
+ }
+ }
+ }
+
+ if ( $ignoreErrors ) {
+ # Treat all precheck() fatals as merely warnings
+ $status->setResult( true, $status->value );
+ }
+
+ // Restart PHP's execution timer and set the timeout to safe amount.
+ // This handles cases where the operations take a long time or where we are
+ // already running low on time left. The old timeout is restored afterwards.
+ # @TODO: re-enable this for when the number of batches is high.
+ #$scopedTimeLimit = new FileOpScopedPHPTimeout( self::TIME_LIMIT_SEC );
+
+ // Attempt each operation...
+ foreach ( $performOps as $index => $fileOp ) {
+ if ( $fileOp->failed() ) {
+ continue; // nothing to do
+ }
+ $subStatus = $fileOp->attempt();
+ $status->merge( $subStatus );
+ if ( $subStatus->isOK() ) {
+ $status->success[$index] = true;
+ ++$status->successCount;
+ } else {
+ $status->success[$index] = false;
+ ++$status->failCount;
+ // We can't continue (even with $ignoreErrors) as $predicates is wrong.
+ // Log the remaining ops as failed for recovery...
+ for ( $i = ($index + 1); $i < count( $performOps ); $i++ ) {
+ $performOps[$i]->logFailure( 'attempt_aborted' );
+ }
+ return $status; // bail out
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * Get the value of the parameter with the given name
+ *
+ * @param $name string
+ * @return mixed Returns null if the parameter is not set
+ */
+ final public function getParam( $name ) {
+ return isset( $this->params[$name] ) ? $this->params[$name] : null;
+ }
+
+ /**
+ * Check if this operation failed precheck() or attempt()
+ *
+ * @return bool
+ */
+ final public function failed() {
+ return $this->failed;
+ }
+
+ /**
+ * Get a new empty predicates array for precheck()
+ *
+ * @return Array
+ */
+ final public static function newPredicates() {
+ return array( 'exists' => array(), 'sha1' => array() );
+ }
+
+ /**
+ * Check preconditions of the operation without writing anything
+ *
+ * @param $predicates Array
+ * @return Status
+ */
+ final public function precheck( array &$predicates ) {
+ if ( $this->state !== self::STATE_NEW ) {
+ return Status::newFatal( 'fileop-fail-state', self::STATE_NEW, $this->state );
+ }
+ $this->state = self::STATE_CHECKED;
+ $status = $this->doPrecheck( $predicates );
+ if ( !$status->isOK() ) {
+ $this->failed = true;
+ }
+ return $status;
+ }
+
+ /**
+ * Attempt the operation, backing up files as needed; this must be reversible
+ *
+ * @return Status
+ */
+ final public function attempt() {
+ if ( $this->state !== self::STATE_CHECKED ) {
+ return Status::newFatal( 'fileop-fail-state', self::STATE_CHECKED, $this->state );
+ } elseif ( $this->failed ) { // failed precheck
+ return Status::newFatal( 'fileop-fail-attempt-precheck' );
+ }
+ $this->state = self::STATE_ATTEMPTED;
+ $status = $this->doAttempt();
+ if ( !$status->isOK() ) {
+ $this->failed = true;
+ $this->logFailure( 'attempt' );
+ }
+ return $status;
+ }
+
+ /**
+ * Get the file operation parameters
+ *
+ * @return Array (required params list, optional params list)
+ */
+ protected function allowedParams() {
+ return array( array(), array() );
+ }
+
+ /**
+ * Get a list of storage paths read from for this operation
+ *
+ * @return Array
+ */
+ public function storagePathsRead() {
+ return array();
+ }
+
+ /**
+ * Get a list of storage paths written to for this operation
+ *
+ * @return Array
+ */
+ public function storagePathsChanged() {
+ return array();
+ }
+
+ /**
+ * @return Status
+ */
+ protected function doPrecheck( array &$predicates ) {
+ return Status::newGood();
+ }
+
+ /**
+ * @return Status
+ */
+ protected function doAttempt() {
+ return Status::newGood();
+ }
+
+ /**
+ * Check for errors with regards to the destination file already existing.
+ * This also updates the destSameAsSource and sourceSha1 member variables.
+ * A bad status will be returned if there is no chance it can be overwritten.
+ *
+ * @param $predicates Array
+ * @return Status
+ */
+ protected function precheckDestExistence( array $predicates ) {
+ $status = Status::newGood();
+ // Get hash of source file/string and the destination file
+ $this->sourceSha1 = $this->getSourceSha1Base36(); // FS file or data string
+ if ( $this->sourceSha1 === null ) { // file in storage?
+ $this->sourceSha1 = $this->fileSha1( $this->params['src'], $predicates );
+ }
+ $this->destSameAsSource = false;
+ if ( $this->fileExists( $this->params['dst'], $predicates ) ) {
+ if ( $this->getParam( 'overwrite' ) ) {
+ return $status; // OK
+ } elseif ( $this->getParam( 'overwriteSame' ) ) {
+ $dhash = $this->fileSha1( $this->params['dst'], $predicates );
+ // Check if hashes are valid and match each other...
+ if ( !strlen( $this->sourceSha1 ) || !strlen( $dhash ) ) {
+ $status->fatal( 'backend-fail-hashes' );
+ } elseif ( $this->sourceSha1 !== $dhash ) {
+ // Give an error if the files are not identical
+ $status->fatal( 'backend-fail-notsame', $this->params['dst'] );
+ } else {
+ $this->destSameAsSource = true; // OK
+ }
+ return $status; // do nothing; either OK or bad status
+ } else {
+ $status->fatal( 'backend-fail-alreadyexists', $this->params['dst'] );
+ return $status;
+ }
+ }
+ return $status;
+ }
+
+ /**
+ * precheckDestExistence() helper function to get the source file SHA-1.
+ * Subclasses should overwride this iff the source is not in storage.
+ *
+ * @return string|false Returns false on failure
+ */
+ protected function getSourceSha1Base36() {
+ return null; // N/A
+ }
+
+ /**
+ * Check if a file will exist in storage when this operation is attempted
+ *
+ * @param $source string Storage path
+ * @param $predicates Array
+ * @return bool
+ */
+ final protected function fileExists( $source, array $predicates ) {
+ if ( isset( $predicates['exists'][$source] ) ) {
+ return $predicates['exists'][$source]; // previous op assures this
+ } else {
+ $params = array( 'src' => $source, 'latest' => $this->useLatest );
+ return $this->backend->fileExists( $params );
+ }
+ }
+
+ /**
+ * Get the SHA-1 of a file in storage when this operation is attempted
+ *
+ * @param $source string Storage path
+ * @param $predicates Array
+ * @return string|false
+ */
+ final protected function fileSha1( $source, array $predicates ) {
+ if ( isset( $predicates['sha1'][$source] ) ) {
+ return $predicates['sha1'][$source]; // previous op assures this
+ } else {
+ $params = array( 'src' => $source, 'latest' => $this->useLatest );
+ return $this->backend->getFileSha1Base36( $params );
+ }
+ }
+
+ /**
+ * Log a file operation failure and preserve any temp files
+ *
+ * @param $action string
+ * @return void
+ */
+ final protected function logFailure( $action ) {
+ $params = $this->params;
+ $params['failedAction'] = $action;
+ try {
+ wfDebugLog( 'FileOperation',
+ get_class( $this ) . ' failed:' . serialize( $params ) );
+ } catch ( Exception $e ) {
+ // bad config? debug log error?
+ }
+ }
+}
+
+/**
+ * FileOp helper class to expand PHP execution time for a function.
+ * On construction, set_time_limit() is called and set to $seconds.
+ * When the object goes out of scope, the timer is restarted, with
+ * the original time limit minus the time the object existed.
+ */
+class FileOpScopedPHPTimeout {
+ protected $startTime; // float; seconds
+ protected $oldTimeout; // integer; seconds
+
+ protected static $stackDepth = 0; // integer
+ protected static $totalCalls = 0; // integer
+ protected static $totalElapsed = 0; // float; seconds
+
+ /* Prevent callers in infinite loops from running forever */
+ const MAX_TOTAL_CALLS = 1000000;
+ const MAX_TOTAL_TIME = 300; // seconds
+
+ /**
+ * @param $seconds integer
+ */
+ public function __construct( $seconds ) {
+ if ( ini_get( 'max_execution_time' ) > 0 ) { // CLI uses 0
+ if ( self::$totalCalls >= self::MAX_TOTAL_CALLS ) {
+ trigger_error( "Maximum invocations of " . __CLASS__ . " exceeded." );
+ } elseif ( self::$totalElapsed >= self::MAX_TOTAL_TIME ) {
+ trigger_error( "Time limit within invocations of " . __CLASS__ . " exceeded." );
+ } elseif ( self::$stackDepth > 0 ) { // recursion guard
+ trigger_error( "Resursive invocation of " . __CLASS__ . " attempted." );
+ } else {
+ $this->oldTimeout = ini_set( 'max_execution_time', $seconds );
+ $this->startTime = microtime( true );
+ ++self::$stackDepth;
+ ++self::$totalCalls; // proof against < 1us scopes
+ }
+ }
+ }
+
+ /**
+ * Restore the original timeout.
+ * This does not account for the timer value on __construct().
+ */
+ public function __destruct() {
+ if ( $this->oldTimeout ) {
+ $elapsed = microtime( true ) - $this->startTime;
+ // Note: a limit of 0 is treated as "forever"
+ set_time_limit( max( 1, $this->oldTimeout - (int)$elapsed ) );
+ // If each scoped timeout is for less than one second, we end up
+ // restoring the original timeout without any decrease in value.
+ // Thus web scripts in an infinite loop can run forever unless we
+ // take some measures to prevent this. Track total time and calls.
+ self::$totalElapsed += $elapsed;
+ --self::$stackDepth;
+ }
+ }
+}
+
+/**
+ * Store a file into the backend from a file on the file system.
+ * Parameters similar to FileBackendStore::storeInternal(), which include:
+ * src : source path on file system
+ * dst : destination storage path
+ * overwrite : do nothing and pass if an identical file exists at destination
+ * overwriteSame : override any existing file at destination
+ */
+class StoreFileOp extends FileOp {
+ protected function allowedParams() {
+ return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
+ }
+
+ protected function doPrecheck( array &$predicates ) {
+ $status = Status::newGood();
+ // Check if the source file exists on the file system
+ if ( !is_file( $this->params['src'] ) ) {
+ $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+ return $status;
+ // Check if the source file is too big
+ } elseif ( filesize( $this->params['src'] ) > $this->backend->maxFileSizeInternal() ) {
+ $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
+ return $status;
+ // Check if a file can be placed at the destination
+ } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+ $status->fatal( 'backend-fail-store', $this->params['src'], $this->params['dst'] );
+ return $status;
+ }
+ // Check if destination file exists
+ $status->merge( $this->precheckDestExistence( $predicates ) );
+ if ( $status->isOK() ) {
+ // Update file existence predicates
+ $predicates['exists'][$this->params['dst']] = true;
+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
+ }
+ return $status; // safe to call attempt()
+ }
+
+ protected function doAttempt() {
+ $status = Status::newGood();
+ // Store the file at the destination
+ if ( !$this->destSameAsSource ) {
+ $status->merge( $this->backend->storeInternal( $this->params ) );
+ }
+ return $status;
+ }
+
+ protected function getSourceSha1Base36() {
+ wfSuppressWarnings();
+ $hash = sha1_file( $this->params['src'] );
+ wfRestoreWarnings();
+ if ( $hash !== false ) {
+ $hash = wfBaseConvert( $hash, 16, 36, 31 );
+ }
+ return $hash;
+ }
+
+ public function storagePathsChanged() {
+ return array( $this->params['dst'] );
+ }
+}
+
+/**
+ * Create a file in the backend with the given content.
+ * Parameters similar to FileBackendStore::createInternal(), which include:
+ * content : the raw file contents
+ * dst : destination storage path
+ * overwrite : do nothing and pass if an identical file exists at destination
+ * overwriteSame : override any existing file at destination
+ */
+class CreateFileOp extends FileOp {
+ protected function allowedParams() {
+ return array( array( 'content', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
+ }
+
+ protected function doPrecheck( array &$predicates ) {
+ $status = Status::newGood();
+ // Check if the source data is too big
+ if ( strlen( $this->getParam( 'content' ) ) > $this->backend->maxFileSizeInternal() ) {
+ $status->fatal( 'backend-fail-create', $this->params['dst'] );
+ return $status;
+ // Check if a file can be placed at the destination
+ } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+ $status->fatal( 'backend-fail-create', $this->params['dst'] );
+ return $status;
+ }
+ // Check if destination file exists
+ $status->merge( $this->precheckDestExistence( $predicates ) );
+ if ( $status->isOK() ) {
+ // Update file existence predicates
+ $predicates['exists'][$this->params['dst']] = true;
+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
+ }
+ return $status; // safe to call attempt()
+ }
+
+ protected function doAttempt() {
+ $status = Status::newGood();
+ // Create the file at the destination
+ if ( !$this->destSameAsSource ) {
+ $status->merge( $this->backend->createInternal( $this->params ) );
+ }
+ return $status;
+ }
+
+ protected function getSourceSha1Base36() {
+ return wfBaseConvert( sha1( $this->params['content'] ), 16, 36, 31 );
+ }
+
+ public function storagePathsChanged() {
+ return array( $this->params['dst'] );
+ }
+}
+
+/**
+ * Copy a file from one storage path to another in the backend.
+ * Parameters similar to FileBackendStore::copyInternal(), which include:
+ * src : source storage path
+ * dst : destination storage path
+ * overwrite : do nothing and pass if an identical file exists at destination
+ * overwriteSame : override any existing file at destination
+ */
+class CopyFileOp extends FileOp {
+ protected function allowedParams() {
+ return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
+ }
+
+ protected function doPrecheck( array &$predicates ) {
+ $status = Status::newGood();
+ // Check if the source file exists
+ if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+ $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+ return $status;
+ // Check if a file can be placed at the destination
+ } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+ $status->fatal( 'backend-fail-copy', $this->params['src'], $this->params['dst'] );
+ return $status;
+ }
+ // Check if destination file exists
+ $status->merge( $this->precheckDestExistence( $predicates ) );
+ if ( $status->isOK() ) {
+ // Update file existence predicates
+ $predicates['exists'][$this->params['dst']] = true;
+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
+ }
+ return $status; // safe to call attempt()
+ }
+
+ protected function doAttempt() {
+ $status = Status::newGood();
+ // Do nothing if the src/dst paths are the same
+ if ( $this->params['src'] !== $this->params['dst'] ) {
+ // Copy the file into the destination
+ if ( !$this->destSameAsSource ) {
+ $status->merge( $this->backend->copyInternal( $this->params ) );
+ }
+ }
+ return $status;
+ }
+
+ public function storagePathsRead() {
+ return array( $this->params['src'] );
+ }
+
+ public function storagePathsChanged() {
+ return array( $this->params['dst'] );
+ }
+}
+
+/**
+ * Move a file from one storage path to another in the backend.
+ * Parameters similar to FileBackendStore::moveInternal(), which include:
+ * src : source storage path
+ * dst : destination storage path
+ * overwrite : do nothing and pass if an identical file exists at destination
+ * overwriteSame : override any existing file at destination
+ */
+class MoveFileOp extends FileOp {
+ protected function allowedParams() {
+ return array( array( 'src', 'dst' ), array( 'overwrite', 'overwriteSame' ) );
+ }
+
+ protected function doPrecheck( array &$predicates ) {
+ $status = Status::newGood();
+ // Check if the source file exists
+ if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+ $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+ return $status;
+ // Check if a file can be placed at the destination
+ } elseif ( !$this->backend->isPathUsableInternal( $this->params['dst'] ) ) {
+ $status->fatal( 'backend-fail-move', $this->params['src'], $this->params['dst'] );
+ return $status;
+ }
+ // Check if destination file exists
+ $status->merge( $this->precheckDestExistence( $predicates ) );
+ if ( $status->isOK() ) {
+ // Update file existence predicates
+ $predicates['exists'][$this->params['src']] = false;
+ $predicates['sha1'][$this->params['src']] = false;
+ $predicates['exists'][$this->params['dst']] = true;
+ $predicates['sha1'][$this->params['dst']] = $this->sourceSha1;
+ }
+ return $status; // safe to call attempt()
+ }
+
+ protected function doAttempt() {
+ $status = Status::newGood();
+ // Do nothing if the src/dst paths are the same
+ if ( $this->params['src'] !== $this->params['dst'] ) {
+ if ( !$this->destSameAsSource ) {
+ // Move the file into the destination
+ $status->merge( $this->backend->moveInternal( $this->params ) );
+ } else {
+ // Just delete source as the destination needs no changes
+ $params = array( 'src' => $this->params['src'] );
+ $status->merge( $this->backend->deleteInternal( $params ) );
+ }
+ }
+ return $status;
+ }
+
+ public function storagePathsRead() {
+ return array( $this->params['src'] );
+ }
+
+ public function storagePathsChanged() {
+ return array( $this->params['dst'] );
+ }
+}
+
+/**
+ * Delete a file at the given storage path from the backend.
+ * Parameters similar to FileBackendStore::deleteInternal(), which include:
+ * src : source storage path
+ * ignoreMissingSource : don't return an error if the file does not exist
+ */
+class DeleteFileOp extends FileOp {
+ protected function allowedParams() {
+ return array( array( 'src' ), array( 'ignoreMissingSource' ) );
+ }
+
+ protected $needsDelete = true;
+
+ protected function doPrecheck( array &$predicates ) {
+ $status = Status::newGood();
+ // Check if the source file exists
+ if ( !$this->fileExists( $this->params['src'], $predicates ) ) {
+ if ( !$this->getParam( 'ignoreMissingSource' ) ) {
+ $status->fatal( 'backend-fail-notexists', $this->params['src'] );
+ return $status;
+ }
+ $this->needsDelete = false;
+ }
+ // Update file existence predicates
+ $predicates['exists'][$this->params['src']] = false;
+ $predicates['sha1'][$this->params['src']] = false;
+ return $status; // safe to call attempt()
+ }
+
+ protected function doAttempt() {
+ $status = Status::newGood();
+ if ( $this->needsDelete ) {
+ // Delete the source file
+ $status->merge( $this->backend->deleteInternal( $this->params ) );
+ }
+ return $status;
+ }
+
+ public function storagePathsChanged() {
+ return array( $this->params['src'] );
+ }
+}
+
+/**
+ * Placeholder operation that has no params and does nothing
+ */
+class NullFileOp extends FileOp {}
diff --git a/includes/filerepo/backend/SwiftFileBackend.php b/includes/filerepo/backend/SwiftFileBackend.php
new file mode 100644
index 00000000..a287f488
--- /dev/null
+++ b/includes/filerepo/backend/SwiftFileBackend.php
@@ -0,0 +1,877 @@
+<?php
+/**
+ * @file
+ * @ingroup FileBackend
+ * @author Russ Nelson
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class for an OpenStack Swift based file backend.
+ *
+ * This requires the SwiftCloudFiles MediaWiki extension, which includes
+ * the php-cloudfiles library (https://github.com/rackspace/php-cloudfiles).
+ * php-cloudfiles requires the curl, fileinfo, and mb_string PHP extensions.
+ *
+ * Status messages should avoid mentioning the Swift account name.
+ * Likewise, error suppression should be used to avoid path disclosure.
+ *
+ * @ingroup FileBackend
+ * @since 1.19
+ */
+class SwiftFileBackend extends FileBackendStore {
+ /** @var CF_Authentication */
+ protected $auth; // Swift authentication handler
+ protected $authTTL; // integer seconds
+ protected $swiftAnonUser; // string; username to handle unauthenticated requests
+ protected $maxContCacheSize = 100; // integer; max containers with entries
+
+ /** @var CF_Connection */
+ protected $conn; // Swift connection handle
+ protected $connStarted = 0; // integer UNIX timestamp
+ protected $connContainers = array(); // container object cache
+
+ /**
+ * @see FileBackendStore::__construct()
+ * Additional $config params include:
+ * swiftAuthUrl : Swift authentication server URL
+ * swiftUser : Swift user used by MediaWiki (account:username)
+ * swiftKey : Swift authentication key for the above user
+ * swiftAuthTTL : Swift authentication TTL (seconds)
+ * swiftAnonUser : Swift user used for end-user requests (account:username)
+ * shardViaHashLevels : Map of container names to sharding config with:
+ * 'base' : base of hash characters, 16 or 36
+ * 'levels' : the number of hash levels (and digits)
+ * 'repeat' : hash subdirectories are prefixed with all the
+ * parent hash directory names (e.g. "a/ab/abc")
+ */
+ public function __construct( array $config ) {
+ parent::__construct( $config );
+ // Required settings
+ $this->auth = new CF_Authentication(
+ $config['swiftUser'],
+ $config['swiftKey'],
+ null, // account; unused
+ $config['swiftAuthUrl']
+ );
+ // Optional settings
+ $this->authTTL = isset( $config['swiftAuthTTL'] )
+ ? $config['swiftAuthTTL']
+ : 120; // some sane number
+ $this->swiftAnonUser = isset( $config['swiftAnonUser'] )
+ ? $config['swiftAnonUser']
+ : '';
+ $this->shardViaHashLevels = isset( $config['shardViaHashLevels'] )
+ ? $config['shardViaHashLevels']
+ : '';
+ }
+
+ /**
+ * @see FileBackendStore::resolveContainerPath()
+ */
+ protected function resolveContainerPath( $container, $relStoragePath ) {
+ if ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
+ return null; // too long for Swift
+ }
+ return $relStoragePath;
+ }
+
+ /**
+ * @see FileBackendStore::isPathUsableInternal()
+ */
+ public function isPathUsableInternal( $storagePath ) {
+ list( $container, $rel ) = $this->resolveStoragePathReal( $storagePath );
+ if ( $rel === null ) {
+ return false; // invalid
+ }
+
+ try {
+ $this->getContainer( $container );
+ return true; // container exists
+ } catch ( NoSuchContainerException $e ) {
+ } catch ( InvalidResponseException $e ) {
+ } catch ( Exception $e ) { // some other exception?
+ $this->logException( $e, __METHOD__, array( 'path' => $storagePath ) );
+ }
+
+ return false;
+ }
+
+ /**
+ * @see FileBackendStore::doCreateInternal()
+ */
+ protected function doCreateInternal( array $params ) {
+ $status = Status::newGood();
+
+ list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
+ if ( $dstRel === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+ return $status;
+ }
+
+ // (a) Check the destination container and object
+ try {
+ $dContObj = $this->getContainer( $dstCont );
+ if ( empty( $params['overwrite'] ) &&
+ $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
+ {
+ $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+ return $status;
+ }
+ } catch ( NoSuchContainerException $e ) {
+ $status->fatal( 'backend-fail-create', $params['dst'] );
+ return $status;
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ return $status;
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ return $status;
+ }
+
+ // (b) Get a SHA-1 hash of the object
+ $sha1Hash = wfBaseConvert( sha1( $params['content'] ), 16, 36, 31 );
+
+ // (c) Actually create the object
+ try {
+ // Create a fresh CF_Object with no fields preloaded.
+ // We don't want to preserve headers, metadata, and such.
+ $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
+ // Note: metadata keys stored as [Upper case char][[Lower case char]...]
+ $obj->metadata = array( 'Sha1base36' => $sha1Hash );
+ // Manually set the ETag (https://github.com/rackspace/php-cloudfiles/issues/59).
+ // The MD5 here will be checked within Swift against its own MD5.
+ $obj->set_etag( md5( $params['content'] ) );
+ // Use the same content type as StreamFile for security
+ $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
+ // Actually write the object in Swift
+ $obj->write( $params['content'] );
+ } catch ( BadContentTypeException $e ) {
+ $status->fatal( 'backend-fail-contenttype', $params['dst'] );
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doStoreInternal()
+ */
+ protected function doStoreInternal( array $params ) {
+ $status = Status::newGood();
+
+ list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
+ if ( $dstRel === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+ return $status;
+ }
+
+ // (a) Check the destination container and object
+ try {
+ $dContObj = $this->getContainer( $dstCont );
+ if ( empty( $params['overwrite'] ) &&
+ $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
+ {
+ $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+ return $status;
+ }
+ } catch ( NoSuchContainerException $e ) {
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ return $status;
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ return $status;
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ return $status;
+ }
+
+ // (b) Get a SHA-1 hash of the object
+ $sha1Hash = sha1_file( $params['src'] );
+ if ( $sha1Hash === false ) { // source doesn't exist?
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ return $status;
+ }
+ $sha1Hash = wfBaseConvert( $sha1Hash, 16, 36, 31 );
+
+ // (c) Actually store the object
+ try {
+ // Create a fresh CF_Object with no fields preloaded.
+ // We don't want to preserve headers, metadata, and such.
+ $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD
+ // Note: metadata keys stored as [Upper case char][[Lower case char]...]
+ $obj->metadata = array( 'Sha1base36' => $sha1Hash );
+ // The MD5 here will be checked within Swift against its own MD5.
+ $obj->set_etag( md5_file( $params['src'] ) );
+ // Use the same content type as StreamFile for security
+ $obj->content_type = StreamFile::contentTypeFromPath( $params['dst'] );
+ // Actually write the object in Swift
+ $obj->load_from_filename( $params['src'], True ); // calls $obj->write()
+ } catch ( BadContentTypeException $e ) {
+ $status->fatal( 'backend-fail-contenttype', $params['dst'] );
+ } catch ( IOException $e ) {
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doCopyInternal()
+ */
+ protected function doCopyInternal( array $params ) {
+ $status = Status::newGood();
+
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+ if ( $srcRel === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+ return $status;
+ }
+
+ list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] );
+ if ( $dstRel === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
+ return $status;
+ }
+
+ // (a) Check the source/destination containers and destination object
+ try {
+ $sContObj = $this->getContainer( $srcCont );
+ $dContObj = $this->getContainer( $dstCont );
+ if ( empty( $params['overwrite'] ) &&
+ $this->fileExists( array( 'src' => $params['dst'], 'latest' => 1 ) ) )
+ {
+ $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
+ return $status;
+ }
+ } catch ( NoSuchContainerException $e ) {
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ return $status;
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ return $status;
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ return $status;
+ }
+
+ // (b) Actually copy the file to the destination
+ try {
+ $sContObj->copy_object_to( $srcRel, $dContObj, $dstRel );
+ } catch ( NoSuchObjectException $e ) { // source object does not exist
+ $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] );
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doDeleteInternal()
+ */
+ protected function doDeleteInternal( array $params ) {
+ $status = Status::newGood();
+
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+ if ( $srcRel === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+ return $status;
+ }
+
+ try {
+ $sContObj = $this->getContainer( $srcCont );
+ $sContObj->delete_object( $srcRel );
+ } catch ( NoSuchContainerException $e ) {
+ $status->fatal( 'backend-fail-delete', $params['src'] );
+ } catch ( NoSuchObjectException $e ) {
+ if ( empty( $params['ignoreMissingSource'] ) ) {
+ $status->fatal( 'backend-fail-delete', $params['src'] );
+ }
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doPrepareInternal()
+ */
+ protected function doPrepareInternal( $fullCont, $dir, array $params ) {
+ $status = Status::newGood();
+
+ // (a) Check if container already exists
+ try {
+ $contObj = $this->getContainer( $fullCont );
+ // NoSuchContainerException not thrown: container must exist
+ return $status; // already exists
+ } catch ( NoSuchContainerException $e ) {
+ // NoSuchContainerException thrown: container does not exist
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ return $status;
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ return $status;
+ }
+
+ // (b) Create container as needed
+ try {
+ $contObj = $this->createContainer( $fullCont );
+ if ( $this->swiftAnonUser != '' ) {
+ // Make container public to end-users...
+ $status->merge( $this->setContainerAccess(
+ $contObj,
+ array( $this->auth->username, $this->swiftAnonUser ), // read
+ array( $this->auth->username ) // write
+ ) );
+ }
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ return $status;
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ return $status;
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doSecureInternal()
+ */
+ protected function doSecureInternal( $fullCont, $dir, array $params ) {
+ $status = Status::newGood();
+
+ if ( $this->swiftAnonUser != '' ) {
+ // Restrict container from end-users...
+ try {
+ // doPrepareInternal() should have been called,
+ // so the Swift container should already exist...
+ $contObj = $this->getContainer( $fullCont ); // normally a cache hit
+ // NoSuchContainerException not thrown: container must exist
+ if ( !isset( $contObj->mw_wasSecured ) ) {
+ $status->merge( $this->setContainerAccess(
+ $contObj,
+ array( $this->auth->username ), // read
+ array( $this->auth->username ) // write
+ ) );
+ // @TODO: when php-cloudfiles supports container
+ // metadata, we can make use of that to avoid RTTs
+ $contObj->mw_wasSecured = true; // avoid useless RTTs
+ }
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doCleanInternal()
+ */
+ protected function doCleanInternal( $fullCont, $dir, array $params ) {
+ $status = Status::newGood();
+
+ // Only containers themselves can be removed, all else is virtual
+ if ( $dir != '' ) {
+ return $status; // nothing to do
+ }
+
+ // (a) Check the container
+ try {
+ $contObj = $this->getContainer( $fullCont, true );
+ } catch ( NoSuchContainerException $e ) {
+ return $status; // ok, nothing to do
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ return $status;
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ return $status;
+ }
+
+ // (b) Delete the container if empty
+ if ( $contObj->object_count == 0 ) {
+ try {
+ $this->deleteContainer( $fullCont );
+ } catch ( NoSuchContainerException $e ) {
+ return $status; // race?
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ return $status;
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-internal', $this->name );
+ $this->logException( $e, __METHOD__, $params );
+ return $status;
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::doFileExists()
+ */
+ protected function doGetFileStat( array $params ) {
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+ if ( $srcRel === null ) {
+ return false; // invalid storage path
+ }
+
+ $stat = false;
+ try {
+ $contObj = $this->getContainer( $srcCont );
+ $srcObj = $contObj->get_object( $srcRel, $this->headersFromParams( $params ) );
+ $this->addMissingMetadata( $srcObj, $params['src'] );
+ $stat = array(
+ // Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW
+ 'mtime' => wfTimestamp( TS_MW, $srcObj->last_modified ),
+ 'size' => $srcObj->content_length,
+ 'sha1' => $srcObj->metadata['Sha1base36']
+ );
+ } catch ( NoSuchContainerException $e ) {
+ } catch ( NoSuchObjectException $e ) {
+ } catch ( InvalidResponseException $e ) {
+ $stat = null;
+ } catch ( Exception $e ) { // some other exception?
+ $stat = null;
+ $this->logException( $e, __METHOD__, $params );
+ }
+
+ return $stat;
+ }
+
+ /**
+ * Fill in any missing object metadata and save it to Swift
+ *
+ * @param $obj CF_Object
+ * @param $path string Storage path to object
+ * @return bool Success
+ * @throws Exception cloudfiles exceptions
+ */
+ protected function addMissingMetadata( CF_Object $obj, $path ) {
+ if ( isset( $obj->metadata['Sha1base36'] ) ) {
+ return true; // nothing to do
+ }
+ $status = Status::newGood();
+ $scopeLockS = $this->getScopedFileLocks( array( $path ), LockManager::LOCK_UW, $status );
+ if ( $status->isOK() ) {
+ $tmpFile = $this->getLocalCopy( array( 'src' => $path, 'latest' => 1 ) );
+ if ( $tmpFile ) {
+ $hash = $tmpFile->getSha1Base36();
+ if ( $hash !== false ) {
+ $obj->metadata['Sha1base36'] = $hash;
+ $obj->sync_metadata(); // save to Swift
+ return true; // success
+ }
+ }
+ }
+ $obj->metadata['Sha1base36'] = false;
+ return false; // failed
+ }
+
+ /**
+ * @see FileBackend::getFileContents()
+ */
+ public function getFileContents( array $params ) {
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+ if ( $srcRel === null ) {
+ return false; // invalid storage path
+ }
+
+ if ( !$this->fileExists( $params ) ) {
+ return null;
+ }
+
+ $data = false;
+ try {
+ $sContObj = $this->getContainer( $srcCont );
+ $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD request
+ $data = $obj->read( $this->headersFromParams( $params ) );
+ } catch ( NoSuchContainerException $e ) {
+ } catch ( InvalidResponseException $e ) {
+ } catch ( Exception $e ) { // some other exception?
+ $this->logException( $e, __METHOD__, $params );
+ }
+
+ return $data;
+ }
+
+ /**
+ * @see FileBackendStore::getFileListInternal()
+ */
+ public function getFileListInternal( $fullCont, $dir, array $params ) {
+ return new SwiftFileBackendFileList( $this, $fullCont, $dir );
+ }
+
+ /**
+ * Do not call this function outside of SwiftFileBackendFileList
+ *
+ * @param $fullCont string Resolved container name
+ * @param $dir string Resolved storage directory with no trailing slash
+ * @param $after string Storage path of file to list items after
+ * @param $limit integer Max number of items to list
+ * @return Array
+ */
+ public function getFileListPageInternal( $fullCont, $dir, $after, $limit ) {
+ $files = array();
+
+ try {
+ $container = $this->getContainer( $fullCont );
+ $prefix = ( $dir == '' ) ? null : "{$dir}/";
+ $files = $container->list_objects( $limit, $after, $prefix );
+ } catch ( NoSuchContainerException $e ) {
+ } catch ( NoSuchObjectException $e ) {
+ } catch ( InvalidResponseException $e ) {
+ } catch ( Exception $e ) { // some other exception?
+ $this->logException( $e, __METHOD__, array( 'cont' => $fullCont, 'dir' => $dir ) );
+ }
+
+ return $files;
+ }
+
+ /**
+ * @see FileBackendStore::doGetFileSha1base36()
+ */
+ public function doGetFileSha1base36( array $params ) {
+ $stat = $this->getFileStat( $params );
+ if ( $stat ) {
+ return $stat['sha1'];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @see FileBackendStore::doStreamFile()
+ */
+ protected function doStreamFile( array $params ) {
+ $status = Status::newGood();
+
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+ if ( $srcRel === null ) {
+ $status->fatal( 'backend-fail-invalidpath', $params['src'] );
+ }
+
+ try {
+ $cont = $this->getContainer( $srcCont );
+ } catch ( NoSuchContainerException $e ) {
+ $status->fatal( 'backend-fail-stream', $params['src'] );
+ return $status;
+ } catch ( InvalidResponseException $e ) {
+ $status->fatal( 'backend-fail-connect', $this->name );
+ return $status;
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-stream', $params['src'] );
+ $this->logException( $e, __METHOD__, $params );
+ return $status;
+ }
+
+ try {
+ $output = fopen( 'php://output', 'wb' );
+ $obj = new CF_Object( $cont, $srcRel, false, false ); // skip HEAD request
+ $obj->stream( $output, $this->headersFromParams( $params ) );
+ } catch ( InvalidResponseException $e ) { // 404? connection problem?
+ $status->fatal( 'backend-fail-stream', $params['src'] );
+ } catch ( Exception $e ) { // some other exception?
+ $status->fatal( 'backend-fail-stream', $params['src'] );
+ $this->logException( $e, __METHOD__, $params );
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see FileBackendStore::getLocalCopy()
+ */
+ public function getLocalCopy( array $params ) {
+ list( $srcCont, $srcRel ) = $this->resolveStoragePathReal( $params['src'] );
+ if ( $srcRel === null ) {
+ return null;
+ }
+
+ if ( !$this->fileExists( $params ) ) {
+ return null;
+ }
+
+ $tmpFile = null;
+ try {
+ $sContObj = $this->getContainer( $srcCont );
+ $obj = new CF_Object( $sContObj, $srcRel, false, false ); // skip HEAD
+ // Get source file extension
+ $ext = FileBackend::extensionFromPath( $srcRel );
+ // Create a new temporary file...
+ $tmpFile = TempFSFile::factory( wfBaseName( $srcRel ) . '_', $ext );
+ if ( $tmpFile ) {
+ $handle = fopen( $tmpFile->getPath(), 'wb' );
+ if ( $handle ) {
+ $obj->stream( $handle, $this->headersFromParams( $params ) );
+ fclose( $handle );
+ } else {
+ $tmpFile = null; // couldn't open temp file
+ }
+ }
+ } catch ( NoSuchContainerException $e ) {
+ $tmpFile = null;
+ } catch ( InvalidResponseException $e ) {
+ $tmpFile = null;
+ } catch ( Exception $e ) { // some other exception?
+ $tmpFile = null;
+ $this->logException( $e, __METHOD__, $params );
+ }
+
+ return $tmpFile;
+ }
+
+ /**
+ * Get headers to send to Swift when reading a file based
+ * on a FileBackend params array, e.g. that of getLocalCopy().
+ * $params is currently only checked for a 'latest' flag.
+ *
+ * @param $params Array
+ * @return Array
+ */
+ protected function headersFromParams( array $params ) {
+ $hdrs = array();
+ if ( !empty( $params['latest'] ) ) {
+ $hdrs[] = 'X-Newest: true';
+ }
+ return $hdrs;
+ }
+
+ /**
+ * Set read/write permissions for a Swift container
+ *
+ * @param $contObj CF_Container Swift container
+ * @param $readGrps Array Swift users who can read (account:user)
+ * @param $writeGrps Array Swift users who can write (account:user)
+ * @return Status
+ */
+ protected function setContainerAccess(
+ CF_Container $contObj, array $readGrps, array $writeGrps
+ ) {
+ $creds = $contObj->cfs_auth->export_credentials();
+
+ $url = $creds['storage_url'] . '/' . rawurlencode( $contObj->name );
+
+ // Note: 10 second timeout consistent with php-cloudfiles
+ $req = new CurlHttpRequest( $url, array( 'method' => 'POST', 'timeout' => 10 ) );
+ $req->setHeader( 'X-Auth-Token', $creds['auth_token'] );
+ $req->setHeader( 'X-Container-Read', implode( ',', $readGrps ) );
+ $req->setHeader( 'X-Container-Write', implode( ',', $writeGrps ) );
+
+ return $req->execute(); // should return 204
+ }
+
+ /**
+ * Get a connection to the Swift proxy
+ *
+ * @return CF_Connection|false
+ * @throws InvalidResponseException
+ */
+ protected function getConnection() {
+ if ( $this->conn === false ) {
+ throw new InvalidResponseException; // failed last attempt
+ }
+ // Session keys expire after a while, so we renew them periodically
+ if ( $this->conn && ( time() - $this->connStarted ) > $this->authTTL ) {
+ $this->conn->close(); // close active cURL connections
+ $this->conn = null;
+ }
+ // Authenticate with proxy and get a session key...
+ if ( $this->conn === null ) {
+ $this->connContainers = array();
+ try {
+ $this->auth->authenticate();
+ $this->conn = new CF_Connection( $this->auth );
+ $this->connStarted = time();
+ } catch ( AuthenticationException $e ) {
+ $this->conn = false; // don't keep re-trying
+ } catch ( InvalidResponseException $e ) {
+ $this->conn = false; // don't keep re-trying
+ }
+ }
+ if ( !$this->conn ) {
+ throw new InvalidResponseException; // auth/connection problem
+ }
+ return $this->conn;
+ }
+
+ /**
+ * @see FileBackendStore::doClearCache()
+ */
+ protected function doClearCache( array $paths = null ) {
+ $this->connContainers = array(); // clear container object cache
+ }
+
+ /**
+ * Get a Swift container object, possibly from process cache.
+ * Use $reCache if the file count or byte count is needed.
+ *
+ * @param $container string Container name
+ * @param $reCache bool Refresh the process cache
+ * @return CF_Container
+ */
+ protected function getContainer( $container, $reCache = false ) {
+ $conn = $this->getConnection(); // Swift proxy connection
+ if ( $reCache ) {
+ unset( $this->connContainers[$container] ); // purge cache
+ }
+ if ( !isset( $this->connContainers[$container] ) ) {
+ $contObj = $conn->get_container( $container );
+ // NoSuchContainerException not thrown: container must exist
+ if ( count( $this->connContainers ) >= $this->maxContCacheSize ) { // trim cache?
+ reset( $this->connContainers );
+ $key = key( $this->connContainers );
+ unset( $this->connContainers[$key] );
+ }
+ $this->connContainers[$container] = $contObj; // cache it
+ }
+ return $this->connContainers[$container];
+ }
+
+ /**
+ * Create a Swift container
+ *
+ * @param $container string Container name
+ * @return CF_Container
+ */
+ protected function createContainer( $container ) {
+ $conn = $this->getConnection(); // Swift proxy connection
+ $contObj = $conn->create_container( $container );
+ $this->connContainers[$container] = $contObj; // cache it
+ return $contObj;
+ }
+
+ /**
+ * Delete a Swift container
+ *
+ * @param $container string Container name
+ * @return void
+ */
+ protected function deleteContainer( $container ) {
+ $conn = $this->getConnection(); // Swift proxy connection
+ $conn->delete_container( $container );
+ unset( $this->connContainers[$container] ); // purge cache
+ }
+
+ /**
+ * Log an unexpected exception for this backend
+ *
+ * @param $e Exception
+ * @param $func string
+ * @param $params Array
+ * @return void
+ */
+ protected function logException( Exception $e, $func, array $params ) {
+ wfDebugLog( 'SwiftBackend',
+ get_class( $e ) . " in '{$func}' (given '" . serialize( $params ) . "')" .
+ ( $e instanceof InvalidResponseException
+ ? ": {$e->getMessage()}"
+ : ""
+ )
+ );
+ }
+}
+
+/**
+ * SwiftFileBackend helper class to page through object listings.
+ * Swift also has a listing limit of 10,000 objects for sanity.
+ * Do not use this class from places outside SwiftFileBackend.
+ *
+ * @ingroup FileBackend
+ */
+class SwiftFileBackendFileList implements Iterator {
+ /** @var Array */
+ protected $bufferIter = array();
+ protected $bufferAfter = null; // string; list items *after* this path
+ protected $pos = 0; // integer
+
+ /** @var SwiftFileBackend */
+ protected $backend;
+ protected $container; //
+ protected $dir; // string storage directory
+ protected $suffixStart; // integer
+
+ const PAGE_SIZE = 5000; // file listing buffer size
+
+ /**
+ * @param $backend SwiftFileBackend
+ * @param $fullCont string Resolved container name
+ * @param $dir string Resolved directory relative to container
+ */
+ public function __construct( SwiftFileBackend $backend, $fullCont, $dir ) {
+ $this->backend = $backend;
+ $this->container = $fullCont;
+ $this->dir = $dir;
+ if ( substr( $this->dir, -1 ) === '/' ) {
+ $this->dir = substr( $this->dir, 0, -1 ); // remove trailing slash
+ }
+ if ( $this->dir == '' ) { // whole container
+ $this->suffixStart = 0;
+ } else { // dir within container
+ $this->suffixStart = strlen( $this->dir ) + 1; // size of "path/to/dir/"
+ }
+ }
+
+ public function current() {
+ return substr( current( $this->bufferIter ), $this->suffixStart );
+ }
+
+ public function key() {
+ return $this->pos;
+ }
+
+ public function next() {
+ // Advance to the next file in the page
+ next( $this->bufferIter );
+ ++$this->pos;
+ // Check if there are no files left in this page and
+ // advance to the next page if this page was not empty.
+ if ( !$this->valid() && count( $this->bufferIter ) ) {
+ $this->bufferAfter = end( $this->bufferIter );
+ $this->bufferIter = $this->backend->getFileListPageInternal(
+ $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE
+ );
+ }
+ }
+
+ public function rewind() {
+ $this->pos = 0;
+ $this->bufferAfter = null;
+ $this->bufferIter = $this->backend->getFileListPageInternal(
+ $this->container, $this->dir, $this->bufferAfter, self::PAGE_SIZE
+ );
+ }
+
+ public function valid() {
+ return ( current( $this->bufferIter ) !== false ); // no paths can have this value
+ }
+}
diff --git a/includes/filerepo/backend/TempFSFile.php b/includes/filerepo/backend/TempFSFile.php
new file mode 100644
index 00000000..7843d6cd
--- /dev/null
+++ b/includes/filerepo/backend/TempFSFile.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * @file
+ * @ingroup FileBackend
+ */
+
+/**
+ * This class is used to hold the location and do limited manipulation
+ * of files stored temporarily (usually this will be $wgTmpDirectory)
+ *
+ * @ingroup FileBackend
+ */
+class TempFSFile extends FSFile {
+ protected $canDelete = false; // bool; garbage collect the temp file
+
+ /** @var Array of active temp files to purge on shutdown */
+ protected static $instances = array();
+
+ /**
+ * Make a new temporary file on the file system.
+ * Temporary files may be purged when the file object falls out of scope.
+ *
+ * @param $prefix string
+ * @param $extension string
+ * @return TempFSFile|null
+ */
+ public static function factory( $prefix, $extension = '' ) {
+ $base = wfTempDir() . '/' . $prefix . dechex( mt_rand( 0, 99999999 ) );
+ $ext = ( $extension != '' ) ? ".{$extension}" : "";
+ for ( $attempt = 1; true; $attempt++ ) {
+ $path = "{$base}-{$attempt}{$ext}";
+ wfSuppressWarnings();
+ $newFileHandle = fopen( $path, 'x' );
+ wfRestoreWarnings();
+ if ( $newFileHandle ) {
+ fclose( $newFileHandle );
+ break; // got it
+ }
+ if ( $attempt >= 15 ) {
+ return null; // give up
+ }
+ }
+ $tmpFile = new self( $path );
+ $tmpFile->canDelete = true; // safely instantiated
+ return $tmpFile;
+ }
+
+ /**
+ * Purge this file off the file system
+ *
+ * @return bool Success
+ */
+ public function purge() {
+ $this->canDelete = false; // done
+ wfSuppressWarnings();
+ $ok = unlink( $this->path );
+ wfRestoreWarnings();
+ return $ok;
+ }
+
+ /**
+ * Clean up the temporary file only after an object goes out of scope
+ *
+ * @param $object Object
+ * @return void
+ */
+ public function bind( $object ) {
+ if ( is_object( $object ) ) {
+ $object->tempFSFileReferences[] = $this;
+ }
+ }
+
+ /**
+ * Set flag to not clean up after the temporary file
+ *
+ * @return void
+ */
+ public function preserve() {
+ $this->canDelete = false;
+ }
+
+ /**
+ * Cleans up after the temporary file by deleting it
+ */
+ function __destruct() {
+ if ( $this->canDelete ) {
+ wfSuppressWarnings();
+ unlink( $this->path );
+ wfRestoreWarnings();
+ }
+ }
+}
diff --git a/includes/filerepo/backend/lockmanager/DBLockManager.php b/includes/filerepo/backend/lockmanager/DBLockManager.php
new file mode 100644
index 00000000..045056ea
--- /dev/null
+++ b/includes/filerepo/backend/lockmanager/DBLockManager.php
@@ -0,0 +1,469 @@
+<?php
+
+/**
+ * Version of LockManager based on using DB table locks.
+ * This is meant for multi-wiki systems that may share files.
+ * All locks are blocking, so it might be useful to set a small
+ * lock-wait timeout via server config to curtail deadlocks.
+ *
+ * All lock requests for a resource, identified by a hash string, will map
+ * to one bucket. Each bucket maps to one or several peer DBs, each on their
+ * own server, all having the filelocks.sql tables (with row-level locking).
+ * A majority of peer DBs must agree for a lock to be acquired.
+ *
+ * Caching is used to avoid hitting servers that are down.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class DBLockManager extends LockManager {
+ /** @var Array Map of DB names to server config */
+ protected $dbServers; // (DB name => server config array)
+ /** @var Array Map of bucket indexes to peer DB lists */
+ protected $dbsByBucket; // (bucket index => (ldb1, ldb2, ...))
+ /** @var BagOStuff */
+ protected $statusCache;
+
+ protected $lockExpiry; // integer number of seconds
+ protected $safeDelay; // integer number of seconds
+
+ protected $session = 0; // random integer
+ /** @var Array Map Database connections (DB name => Database) */
+ protected $conns = array();
+
+ /**
+ * Construct a new instance from configuration.
+ *
+ * $config paramaters include:
+ * 'dbServers' : Associative array of DB names to server configuration.
+ * Configuration is an associative array that includes:
+ * 'host' - DB server name
+ * 'dbname' - DB name
+ * 'type' - DB type (mysql,postgres,...)
+ * 'user' - DB user
+ * 'password' - DB user password
+ * 'tablePrefix' - DB table prefix
+ * 'flags' - DB flags (see DatabaseBase)
+ * 'dbsByBucket' : Array of 1-16 consecutive integer keys, starting from 0,
+ * each having an odd-numbered list of DB names (peers) as values.
+ * Any DB named 'localDBMaster' will automatically use the DB master
+ * settings for this wiki (without the need for a dbServers entry).
+ * 'lockExpiry' : Lock timeout (seconds) for dropped connections. [optional]
+ * This tells the DB server how long to wait before assuming
+ * connection failure and releasing all the locks for a session.
+ *
+ * @param Array $config
+ */
+ public function __construct( array $config ) {
+ $this->dbServers = isset( $config['dbServers'] )
+ ? $config['dbServers']
+ : array(); // likely just using 'localDBMaster'
+ // Sanitize dbsByBucket config to prevent PHP errors
+ $this->dbsByBucket = array_filter( $config['dbsByBucket'], 'is_array' );
+ $this->dbsByBucket = array_values( $this->dbsByBucket ); // consecutive
+
+ if ( isset( $config['lockExpiry'] ) ) {
+ $this->lockExpiry = $config['lockExpiry'];
+ } else {
+ $met = ini_get( 'max_execution_time' );
+ $this->lockExpiry = $met ? $met : 60; // use some sane amount if 0
+ }
+ $this->safeDelay = ( $this->lockExpiry <= 0 )
+ ? 60 // pick a safe-ish number to match DB timeout default
+ : $this->lockExpiry; // cover worst case
+
+ foreach ( $this->dbsByBucket as $bucket ) {
+ if ( count( $bucket ) > 1 ) {
+ // Tracks peers that couldn't be queried recently to avoid lengthy
+ // connection timeouts. This is useless if each bucket has one peer.
+ $this->statusCache = wfGetMainCache();
+ break;
+ }
+ }
+
+ $this->session = '';
+ for ( $i = 0; $i < 5; $i++ ) {
+ $this->session .= mt_rand( 0, 2147483647 );
+ }
+ $this->session = wfBaseConvert( sha1( $this->session ), 16, 36, 31 );
+ }
+
+ /**
+ * @see LockManager::doLock()
+ */
+ protected function doLock( array $paths, $type ) {
+ $status = Status::newGood();
+
+ $pathsToLock = array();
+ // Get locks that need to be acquired (buckets => locks)...
+ foreach ( $paths as $path ) {
+ if ( isset( $this->locksHeld[$path][$type] ) ) {
+ ++$this->locksHeld[$path][$type];
+ } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+ $this->locksHeld[$path][$type] = 1;
+ } else {
+ $bucket = $this->getBucketFromKey( $path );
+ $pathsToLock[$bucket][] = $path;
+ }
+ }
+
+ $lockedPaths = array(); // files locked in this attempt
+ // Attempt to acquire these locks...
+ foreach ( $pathsToLock as $bucket => $paths ) {
+ // Try to acquire the locks for this bucket
+ $res = $this->doLockingQueryAll( $bucket, $paths, $type );
+ if ( $res === 'cantacquire' ) {
+ // Resources already locked by another process.
+ // Abort and unlock everything we just locked.
+ foreach ( $paths as $path ) {
+ $status->fatal( 'lockmanager-fail-acquirelock', $path );
+ }
+ $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+ return $status;
+ } elseif ( $res !== true ) {
+ // Couldn't contact any DBs for this bucket.
+ // Abort and unlock everything we just locked.
+ $status->fatal( 'lockmanager-fail-db-bucket', $bucket );
+ $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+ return $status;
+ }
+ // Record these locks as active
+ foreach ( $paths as $path ) {
+ $this->locksHeld[$path][$type] = 1; // locked
+ }
+ // Keep track of what locks were made in this attempt
+ $lockedPaths = array_merge( $lockedPaths, $paths );
+ }
+
+ return $status;
+ }
+
+ /**
+ * @see LockManager::doUnlock()
+ */
+ protected function doUnlock( array $paths, $type ) {
+ $status = Status::newGood();
+
+ foreach ( $paths as $path ) {
+ if ( !isset( $this->locksHeld[$path] ) ) {
+ $status->warning( 'lockmanager-notlocked', $path );
+ } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
+ $status->warning( 'lockmanager-notlocked', $path );
+ } else {
+ --$this->locksHeld[$path][$type];
+ if ( $this->locksHeld[$path][$type] <= 0 ) {
+ unset( $this->locksHeld[$path][$type] );
+ }
+ if ( !count( $this->locksHeld[$path] ) ) {
+ unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
+ }
+ }
+ }
+
+ // Reference count the locks held and COMMIT when zero
+ if ( !count( $this->locksHeld ) ) {
+ $status->merge( $this->finishLockTransactions() );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Get a connection to a lock DB and acquire locks on $paths.
+ * This does not use GET_LOCK() per http://bugs.mysql.com/bug.php?id=1118.
+ *
+ * @param $lockDb string
+ * @param $paths Array
+ * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
+ * @return bool Resources able to be locked
+ * @throws DBError
+ */
+ protected function doLockingQuery( $lockDb, array $paths, $type ) {
+ if ( $type == self::LOCK_EX ) { // writer locks
+ $db = $this->getConnection( $lockDb );
+ if ( !$db ) {
+ return false; // bad config
+ }
+ $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+ # Build up values for INSERT clause
+ $data = array();
+ foreach ( $keys as $key ) {
+ $data[] = array( 'fle_key' => $key );
+ }
+ # Wait on any existing writers and block new ones if we get in
+ $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
+ }
+ return true;
+ }
+
+ /**
+ * Attempt to acquire locks with the peers for a bucket.
+ * This should avoid throwing any exceptions.
+ *
+ * @param $bucket integer
+ * @param $paths Array List of resource keys to lock
+ * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
+ * @return bool|string One of (true, 'cantacquire', 'dberrors')
+ */
+ protected function doLockingQueryAll( $bucket, array $paths, $type ) {
+ $yesVotes = 0; // locks made on trustable DBs
+ $votesLeft = count( $this->dbsByBucket[$bucket] ); // remaining DBs
+ $quorum = floor( $votesLeft/2 + 1 ); // simple majority
+ // Get votes for each DB, in order, until we have enough...
+ foreach ( $this->dbsByBucket[$bucket] as $lockDb ) {
+ // Check that DB is not *known* to be down
+ if ( $this->cacheCheckFailures( $lockDb ) ) {
+ try {
+ // Attempt to acquire the lock on this DB
+ if ( !$this->doLockingQuery( $lockDb, $paths, $type ) ) {
+ return 'cantacquire'; // vetoed; resource locked
+ }
+ ++$yesVotes; // success for this peer
+ if ( $yesVotes >= $quorum ) {
+ return true; // lock obtained
+ }
+ } catch ( DBConnectionError $e ) {
+ $this->cacheRecordFailure( $lockDb );
+ } catch ( DBError $e ) {
+ if ( $this->lastErrorIndicatesLocked( $lockDb ) ) {
+ return 'cantacquire'; // vetoed; resource locked
+ }
+ }
+ }
+ --$votesLeft;
+ $votesNeeded = $quorum - $yesVotes;
+ if ( $votesNeeded > $votesLeft ) {
+ // In "trust cache" mode we don't have to meet the quorum
+ break; // short-circuit
+ }
+ }
+ // At this point, we must not have meet the quorum
+ return 'dberrors'; // not enough votes to ensure correctness
+ }
+
+ /**
+ * Get (or reuse) a connection to a lock DB
+ *
+ * @param $lockDb string
+ * @return Database
+ * @throws DBError
+ */
+ protected function getConnection( $lockDb ) {
+ if ( !isset( $this->conns[$lockDb] ) ) {
+ $db = null;
+ if ( $lockDb === 'localDBMaster' ) {
+ $lb = wfGetLBFactory()->newMainLB();
+ $db = $lb->getConnection( DB_MASTER );
+ } elseif ( isset( $this->dbServers[$lockDb] ) ) {
+ $config = $this->dbServers[$lockDb];
+ $db = DatabaseBase::factory( $config['type'], $config );
+ }
+ if ( !$db ) {
+ return null; // config error?
+ }
+ $this->conns[$lockDb] = $db;
+ $this->conns[$lockDb]->clearFlag( DBO_TRX );
+ # If the connection drops, try to avoid letting the DB rollback
+ # and release the locks before the file operations are finished.
+ # This won't handle the case of DB server restarts however.
+ $options = array();
+ if ( $this->lockExpiry > 0 ) {
+ $options['connTimeout'] = $this->lockExpiry;
+ }
+ $this->conns[$lockDb]->setSessionOptions( $options );
+ $this->initConnection( $lockDb, $this->conns[$lockDb] );
+ }
+ if ( !$this->conns[$lockDb]->trxLevel() ) {
+ $this->conns[$lockDb]->begin(); // start transaction
+ }
+ return $this->conns[$lockDb];
+ }
+
+ /**
+ * Do additional initialization for new lock DB connection
+ *
+ * @param $lockDb string
+ * @param $db DatabaseBase
+ * @return void
+ * @throws DBError
+ */
+ protected function initConnection( $lockDb, DatabaseBase $db ) {}
+
+ /**
+ * Commit all changes to lock-active databases.
+ * This should avoid throwing any exceptions.
+ *
+ * @return Status
+ */
+ protected function finishLockTransactions() {
+ $status = Status::newGood();
+ foreach ( $this->conns as $lockDb => $db ) {
+ if ( $db->trxLevel() ) { // in transaction
+ try {
+ $db->rollback(); // finish transaction and kill any rows
+ } catch ( DBError $e ) {
+ $status->fatal( 'lockmanager-fail-db-release', $lockDb );
+ }
+ }
+ }
+ return $status;
+ }
+
+ /**
+ * Check if the last DB error for $lockDb indicates
+ * that a requested resource was locked by another process.
+ * This should avoid throwing any exceptions.
+ *
+ * @param $lockDb string
+ * @return bool
+ */
+ protected function lastErrorIndicatesLocked( $lockDb ) {
+ if ( isset( $this->conns[$lockDb] ) ) { // sanity
+ $db = $this->conns[$lockDb];
+ return ( $db->wasDeadlock() || $db->wasLockTimeout() );
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the DB has not recently had connection/query errors.
+ * This just avoids wasting time on doomed connection attempts.
+ *
+ * @param $lockDb string
+ * @return bool
+ */
+ protected function cacheCheckFailures( $lockDb ) {
+ if ( $this->statusCache && $this->safeDelay > 0 ) {
+ $path = $this->getMissKey( $lockDb );
+ $misses = $this->statusCache->get( $path );
+ return !$misses;
+ }
+ return true;
+ }
+
+ /**
+ * Log a lock request failure to the cache
+ *
+ * @param $lockDb string
+ * @return bool Success
+ */
+ protected function cacheRecordFailure( $lockDb ) {
+ if ( $this->statusCache && $this->safeDelay > 0 ) {
+ $path = $this->getMissKey( $lockDb );
+ $misses = $this->statusCache->get( $path );
+ if ( $misses ) {
+ return $this->statusCache->incr( $path );
+ } else {
+ return $this->statusCache->add( $path, 1, $this->safeDelay );
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get a cache key for recent query misses for a DB
+ *
+ * @param $lockDb string
+ * @return string
+ */
+ protected function getMissKey( $lockDb ) {
+ return 'lockmanager:querymisses:' . str_replace( ' ', '_', $lockDb );
+ }
+
+ /**
+ * Get the bucket for resource path.
+ * This should avoid throwing any exceptions.
+ *
+ * @param $path string
+ * @return integer
+ */
+ protected function getBucketFromKey( $path ) {
+ $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
+ return intval( base_convert( $prefix, 16, 10 ) ) % count( $this->dbsByBucket );
+ }
+
+ /**
+ * Make sure remaining locks get cleared for sanity
+ */
+ function __destruct() {
+ foreach ( $this->conns as $lockDb => $db ) {
+ if ( $db->trxLevel() ) { // in transaction
+ try {
+ $db->rollback(); // finish transaction and kill any rows
+ } catch ( DBError $e ) {
+ // oh well
+ }
+ }
+ $db->close();
+ }
+ }
+}
+
+/**
+ * MySQL version of DBLockManager that supports shared locks.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * @ingroup LockManager
+ */
+class MySqlLockManager extends DBLockManager {
+ /** @var Array Mapping of lock types to the type actually used */
+ protected $lockTypeMap = array(
+ self::LOCK_SH => self::LOCK_SH,
+ self::LOCK_UW => self::LOCK_SH,
+ self::LOCK_EX => self::LOCK_EX
+ );
+
+ protected function initConnection( $lockDb, DatabaseBase $db ) {
+ # Let this transaction see lock rows from other transactions
+ $db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
+ }
+
+ protected function doLockingQuery( $lockDb, array $paths, $type ) {
+ $db = $this->getConnection( $lockDb );
+ if ( !$db ) {
+ return false;
+ }
+ $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+ # Build up values for INSERT clause
+ $data = array();
+ foreach ( $keys as $key ) {
+ $data[] = array( 'fls_key' => $key, 'fls_session' => $this->session );
+ }
+ # Block new writers...
+ $db->insert( 'filelocks_shared', $data, __METHOD__, array( 'IGNORE' ) );
+ # Actually do the locking queries...
+ if ( $type == self::LOCK_SH ) { // reader locks
+ # Bail if there are any existing writers...
+ $blocked = $db->selectField( 'filelocks_exclusive', '1',
+ array( 'fle_key' => $keys ),
+ __METHOD__
+ );
+ # Prospective writers that haven't yet updated filelocks_exclusive
+ # will recheck filelocks_shared after doing so and bail due to our entry.
+ } else { // writer locks
+ $encSession = $db->addQuotes( $this->session );
+ # Bail if there are any existing writers...
+ # The may detect readers, but the safe check for them is below.
+ # Note: if two writers come at the same time, both bail :)
+ $blocked = $db->selectField( 'filelocks_shared', '1',
+ array( 'fls_key' => $keys, "fls_session != $encSession" ),
+ __METHOD__
+ );
+ if ( !$blocked ) {
+ # Build up values for INSERT clause
+ $data = array();
+ foreach ( $keys as $key ) {
+ $data[] = array( 'fle_key' => $key );
+ }
+ # Block new readers/writers...
+ $db->insert( 'filelocks_exclusive', $data, __METHOD__ );
+ # Bail if there are any existing readers...
+ $blocked = $db->selectField( 'filelocks_shared', '1',
+ array( 'fls_key' => $keys, "fls_session != $encSession" ),
+ __METHOD__
+ );
+ }
+ }
+ return !$blocked;
+ }
+}
diff --git a/includes/filerepo/backend/lockmanager/FSLockManager.php b/includes/filerepo/backend/lockmanager/FSLockManager.php
new file mode 100644
index 00000000..42074fd3
--- /dev/null
+++ b/includes/filerepo/backend/lockmanager/FSLockManager.php
@@ -0,0 +1,202 @@
+<?php
+
+/**
+ * Simple version of LockManager based on using FS lock files.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * This should work fine for small sites running off one server.
+ * Do not use this with 'lockDirectory' set to an NFS mount unless the
+ * NFS client is at least version 2.6.12. Otherwise, the BSD flock()
+ * locks will be ignored; see http://nfs.sourceforge.net/#section_d.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class FSLockManager extends LockManager {
+ /** @var Array Mapping of lock types to the type actually used */
+ protected $lockTypeMap = array(
+ self::LOCK_SH => self::LOCK_SH,
+ self::LOCK_UW => self::LOCK_SH,
+ self::LOCK_EX => self::LOCK_EX
+ );
+
+ protected $lockDir; // global dir for all servers
+
+ /** @var Array Map of (locked key => lock type => lock file handle) */
+ protected $handles = array();
+
+ /**
+ * Construct a new instance from configuration.
+ *
+ * $config includes:
+ * 'lockDirectory' : Directory containing the lock files
+ *
+ * @param array $config
+ */
+ function __construct( array $config ) {
+ parent::__construct( $config );
+ $this->lockDir = $config['lockDirectory'];
+ }
+
+ protected function doLock( array $paths, $type ) {
+ $status = Status::newGood();
+
+ $lockedPaths = array(); // files locked in this attempt
+ foreach ( $paths as $path ) {
+ $status->merge( $this->doSingleLock( $path, $type ) );
+ if ( $status->isOK() ) {
+ $lockedPaths[] = $path;
+ } else {
+ // Abort and unlock everything
+ $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+ return $status;
+ }
+ }
+
+ return $status;
+ }
+
+ protected function doUnlock( array $paths, $type ) {
+ $status = Status::newGood();
+
+ foreach ( $paths as $path ) {
+ $status->merge( $this->doSingleUnlock( $path, $type ) );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Lock a single resource key
+ *
+ * @param $path string
+ * @param $type integer
+ * @return Status
+ */
+ protected function doSingleLock( $path, $type ) {
+ $status = Status::newGood();
+
+ if ( isset( $this->locksHeld[$path][$type] ) ) {
+ ++$this->locksHeld[$path][$type];
+ } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+ $this->locksHeld[$path][$type] = 1;
+ } else {
+ wfSuppressWarnings();
+ $handle = fopen( $this->getLockPath( $path ), 'a+' );
+ wfRestoreWarnings();
+ if ( !$handle ) { // lock dir missing?
+ wfMkdirParents( $this->lockDir );
+ $handle = fopen( $this->getLockPath( $path ), 'a+' ); // try again
+ }
+ if ( $handle ) {
+ // Either a shared or exclusive lock
+ $lock = ( $type == self::LOCK_SH ) ? LOCK_SH : LOCK_EX;
+ if ( flock( $handle, $lock | LOCK_NB ) ) {
+ // Record this lock as active
+ $this->locksHeld[$path][$type] = 1;
+ $this->handles[$path][$type] = $handle;
+ } else {
+ fclose( $handle );
+ $status->fatal( 'lockmanager-fail-acquirelock', $path );
+ }
+ } else {
+ $status->fatal( 'lockmanager-fail-openlock', $path );
+ }
+ }
+
+ return $status;
+ }
+
+ /**
+ * Unlock a single resource key
+ *
+ * @param $path string
+ * @param $type integer
+ * @return Status
+ */
+ protected function doSingleUnlock( $path, $type ) {
+ $status = Status::newGood();
+
+ if ( !isset( $this->locksHeld[$path] ) ) {
+ $status->warning( 'lockmanager-notlocked', $path );
+ } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
+ $status->warning( 'lockmanager-notlocked', $path );
+ } else {
+ $handlesToClose = array();
+ --$this->locksHeld[$path][$type];
+ if ( $this->locksHeld[$path][$type] <= 0 ) {
+ unset( $this->locksHeld[$path][$type] );
+ // If a LOCK_SH comes in while we have a LOCK_EX, we don't
+ // actually add a handler, so check for handler existence.
+ if ( isset( $this->handles[$path][$type] ) ) {
+ // Mark this handle to be unlocked and closed
+ $handlesToClose[] = $this->handles[$path][$type];
+ unset( $this->handles[$path][$type] );
+ }
+ }
+ // Unlock handles to release locks and delete
+ // any lock files that end up with no locks on them...
+ if ( wfIsWindows() ) {
+ // Windows: for any process, including this one,
+ // calling unlink() on a locked file will fail
+ $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
+ $status->merge( $this->pruneKeyLockFiles( $path ) );
+ } else {
+ // Unix: unlink() can be used on files currently open by this
+ // process and we must do so in order to avoid race conditions
+ $status->merge( $this->pruneKeyLockFiles( $path ) );
+ $status->merge( $this->closeLockHandles( $path, $handlesToClose ) );
+ }
+ }
+
+ return $status;
+ }
+
+ private function closeLockHandles( $path, array $handlesToClose ) {
+ $status = Status::newGood();
+ foreach ( $handlesToClose as $handle ) {
+ wfSuppressWarnings();
+ if ( !flock( $handle, LOCK_UN ) ) {
+ $status->fatal( 'lockmanager-fail-releaselock', $path );
+ }
+ if ( !fclose( $handle ) ) {
+ $status->warning( 'lockmanager-fail-closelock', $path );
+ }
+ wfRestoreWarnings();
+ }
+ return $status;
+ }
+
+ private function pruneKeyLockFiles( $path ) {
+ $status = Status::newGood();
+ if ( !count( $this->locksHeld[$path] ) ) {
+ wfSuppressWarnings();
+ # No locks are held for the lock file anymore
+ if ( !unlink( $this->getLockPath( $path ) ) ) {
+ $status->warning( 'lockmanager-fail-deletelock', $path );
+ }
+ wfRestoreWarnings();
+ unset( $this->locksHeld[$path] );
+ unset( $this->handles[$path] );
+ }
+ return $status;
+ }
+
+ /**
+ * Get the path to the lock file for a key
+ * @param $path string
+ * @return string
+ */
+ protected function getLockPath( $path ) {
+ $hash = self::sha1Base36( $path );
+ return "{$this->lockDir}/{$hash}.lock";
+ }
+
+ function __destruct() {
+ // Make sure remaining locks get cleared for sanity
+ foreach ( $this->locksHeld as $path => $locks ) {
+ $this->doSingleUnlock( $path, self::LOCK_EX );
+ $this->doSingleUnlock( $path, self::LOCK_SH );
+ }
+ }
+}
diff --git a/includes/filerepo/backend/lockmanager/LSLockManager.php b/includes/filerepo/backend/lockmanager/LSLockManager.php
new file mode 100644
index 00000000..b7ac743c
--- /dev/null
+++ b/includes/filerepo/backend/lockmanager/LSLockManager.php
@@ -0,0 +1,295 @@
+<?php
+
+/**
+ * Manage locks using a lock daemon server.
+ *
+ * Version of LockManager based on using lock daemon servers.
+ * This is meant for multi-wiki systems that may share files.
+ * All locks are non-blocking, which avoids deadlocks.
+ *
+ * All lock requests for a resource, identified by a hash string, will map
+ * to one bucket. Each bucket maps to one or several peer servers, each
+ * running LockServerDaemon.php, listening on a designated TCP port.
+ * A majority of peers must agree for a lock to be acquired.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class LSLockManager extends LockManager {
+ /** @var Array Mapping of lock types to the type actually used */
+ protected $lockTypeMap = array(
+ self::LOCK_SH => self::LOCK_SH,
+ self::LOCK_UW => self::LOCK_SH,
+ self::LOCK_EX => self::LOCK_EX
+ );
+
+ /** @var Array Map of server names to server config */
+ protected $lockServers; // (server name => server config array)
+ /** @var Array Map of bucket indexes to peer server lists */
+ protected $srvsByBucket; // (bucket index => (lsrv1, lsrv2, ...))
+
+ /** @var Array Map Server connections (server name => resource) */
+ protected $conns = array();
+
+ protected $connTimeout; // float number of seconds
+ protected $session = ''; // random SHA-1 string
+
+ /**
+ * Construct a new instance from configuration.
+ *
+ * $config paramaters include:
+ * 'lockServers' : Associative array of server names to configuration.
+ * Configuration is an associative array that includes:
+ * 'host' - IP address/hostname
+ * 'port' - TCP port
+ * 'authKey' - Secret string the lock server uses
+ * 'srvsByBucket' : Array of 1-16 consecutive integer keys, starting from 0,
+ * each having an odd-numbered list of server names (peers) as values.
+ * 'connTimeout' : Lock server connection attempt timeout. [optional]
+ *
+ * @param Array $config
+ */
+ public function __construct( array $config ) {
+ $this->lockServers = $config['lockServers'];
+ // Sanitize srvsByBucket config to prevent PHP errors
+ $this->srvsByBucket = array_filter( $config['srvsByBucket'], 'is_array' );
+ $this->srvsByBucket = array_values( $this->srvsByBucket ); // consecutive
+
+ if ( isset( $config['connTimeout'] ) ) {
+ $this->connTimeout = $config['connTimeout'];
+ } else {
+ $this->connTimeout = 3; // use some sane amount
+ }
+
+ $this->session = '';
+ for ( $i = 0; $i < 5; $i++ ) {
+ $this->session .= mt_rand( 0, 2147483647 );
+ }
+ $this->session = wfBaseConvert( sha1( $this->session ), 16, 36, 31 );
+ }
+
+ protected function doLock( array $paths, $type ) {
+ $status = Status::newGood();
+
+ $pathsToLock = array();
+ // Get locks that need to be acquired (buckets => locks)...
+ foreach ( $paths as $path ) {
+ if ( isset( $this->locksHeld[$path][$type] ) ) {
+ ++$this->locksHeld[$path][$type];
+ } elseif ( isset( $this->locksHeld[$path][self::LOCK_EX] ) ) {
+ $this->locksHeld[$path][$type] = 1;
+ } else {
+ $bucket = $this->getBucketFromKey( $path );
+ $pathsToLock[$bucket][] = $path;
+ }
+ }
+
+ $lockedPaths = array(); // files locked in this attempt
+ // Attempt to acquire these locks...
+ foreach ( $pathsToLock as $bucket => $paths ) {
+ // Try to acquire the locks for this bucket
+ $res = $this->doLockingRequestAll( $bucket, $paths, $type );
+ if ( $res === 'cantacquire' ) {
+ // Resources already locked by another process.
+ // Abort and unlock everything we just locked.
+ foreach ( $paths as $path ) {
+ $status->fatal( 'lockmanager-fail-acquirelock', $path );
+ }
+ $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+ return $status;
+ } elseif ( $res !== true ) {
+ // Couldn't contact any servers for this bucket.
+ // Abort and unlock everything we just locked.
+ foreach ( $paths as $path ) {
+ $status->fatal( 'lockmanager-fail-acquirelock', $path );
+ }
+ $status->merge( $this->doUnlock( $lockedPaths, $type ) );
+ return $status;
+ }
+ // Record these locks as active
+ foreach ( $paths as $path ) {
+ $this->locksHeld[$path][$type] = 1; // locked
+ }
+ // Keep track of what locks were made in this attempt
+ $lockedPaths = array_merge( $lockedPaths, $paths );
+ }
+
+ return $status;
+ }
+
+ protected function doUnlock( array $paths, $type ) {
+ $status = Status::newGood();
+
+ foreach ( $paths as $path ) {
+ if ( !isset( $this->locksHeld[$path] ) ) {
+ $status->warning( 'lockmanager-notlocked', $path );
+ } elseif ( !isset( $this->locksHeld[$path][$type] ) ) {
+ $status->warning( 'lockmanager-notlocked', $path );
+ } else {
+ --$this->locksHeld[$path][$type];
+ if ( $this->locksHeld[$path][$type] <= 0 ) {
+ unset( $this->locksHeld[$path][$type] );
+ }
+ if ( !count( $this->locksHeld[$path] ) ) {
+ unset( $this->locksHeld[$path] ); // no SH or EX locks left for key
+ }
+ }
+ }
+
+ // Reference count the locks held and release locks when zero
+ if ( !count( $this->locksHeld ) ) {
+ $status->merge( $this->releaseLocks() );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Get a connection to a lock server and acquire locks on $paths
+ *
+ * @param $lockSrv string
+ * @param $paths Array
+ * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
+ * @return bool Resources able to be locked
+ */
+ protected function doLockingRequest( $lockSrv, array $paths, $type ) {
+ if ( $type == self::LOCK_SH ) { // reader locks
+ $type = 'SH';
+ } elseif ( $type == self::LOCK_EX ) { // writer locks
+ $type = 'EX';
+ } else {
+ return true; // ok...
+ }
+
+ // Send out the command and get the response...
+ $keys = array_unique( array_map( 'LockManager::sha1Base36', $paths ) );
+ $response = $this->sendCommand( $lockSrv, 'ACQUIRE', $type, $keys );
+
+ return ( $response === 'ACQUIRED' );
+ }
+
+ /**
+ * Send a command and get back the response
+ *
+ * @param $lockSrv string
+ * @param $action string
+ * @param $type string
+ * @param $values Array
+ * @return string|false
+ */
+ protected function sendCommand( $lockSrv, $action, $type, $values ) {
+ $conn = $this->getConnection( $lockSrv );
+ if ( !$conn ) {
+ return false; // no connection
+ }
+ $authKey = $this->lockServers[$lockSrv]['authKey'];
+ // Build of the command as a flat string...
+ $values = implode( '|', $values );
+ $key = sha1( $this->session . $action . $type . $values . $authKey );
+ // Send out the command...
+ if ( fwrite( $conn, "{$this->session}:$key:$action:$type:$values\n" ) === false ) {
+ return false;
+ }
+ // Get the response...
+ $response = fgets( $conn );
+ if ( $response === false ) {
+ return false;
+ }
+ return trim( $response );
+ }
+
+ /**
+ * Attempt to acquire locks with the peers for a bucket
+ *
+ * @param $bucket integer
+ * @param $paths Array List of resource keys to lock
+ * @param $type integer LockManager::LOCK_EX or LockManager::LOCK_SH
+ * @return bool|string One of (true, 'cantacquire', 'srverrors')
+ */
+ protected function doLockingRequestAll( $bucket, array $paths, $type ) {
+ $yesVotes = 0; // locks made on trustable servers
+ $votesLeft = count( $this->srvsByBucket[$bucket] ); // remaining peers
+ $quorum = floor( $votesLeft/2 + 1 ); // simple majority
+ // Get votes for each peer, in order, until we have enough...
+ foreach ( $this->srvsByBucket[$bucket] as $lockSrv ) {
+ // Attempt to acquire the lock on this peer
+ if ( !$this->doLockingRequest( $lockSrv, $paths, $type ) ) {
+ return 'cantacquire'; // vetoed; resource locked
+ }
+ ++$yesVotes; // success for this peer
+ if ( $yesVotes >= $quorum ) {
+ return true; // lock obtained
+ }
+ --$votesLeft;
+ $votesNeeded = $quorum - $yesVotes;
+ if ( $votesNeeded > $votesLeft ) {
+ // In "trust cache" mode we don't have to meet the quorum
+ break; // short-circuit
+ }
+ }
+ // At this point, we must not have meet the quorum
+ return 'srverrors'; // not enough votes to ensure correctness
+ }
+
+ /**
+ * Get (or reuse) a connection to a lock server
+ *
+ * @param $lockSrv string
+ * @return resource
+ */
+ protected function getConnection( $lockSrv ) {
+ if ( !isset( $this->conns[$lockSrv] ) ) {
+ $cfg = $this->lockServers[$lockSrv];
+ wfSuppressWarnings();
+ $errno = $errstr = '';
+ $conn = fsockopen( $cfg['host'], $cfg['port'], $errno, $errstr, $this->connTimeout );
+ wfRestoreWarnings();
+ if ( $conn === false ) {
+ return null;
+ }
+ $sec = floor( $this->connTimeout );
+ $usec = floor( ( $this->connTimeout - floor( $this->connTimeout ) ) * 1e6 );
+ stream_set_timeout( $conn, $sec, $usec );
+ $this->conns[$lockSrv] = $conn;
+ }
+ return $this->conns[$lockSrv];
+ }
+
+ /**
+ * Release all locks that this session is holding
+ *
+ * @return Status
+ */
+ protected function releaseLocks() {
+ $status = Status::newGood();
+ foreach ( $this->conns as $lockSrv => $conn ) {
+ $response = $this->sendCommand( $lockSrv, 'RELEASE_ALL', '', array() );
+ if ( $response !== 'RELEASED_ALL' ) {
+ $status->fatal( 'lockmanager-fail-svr-release', $lockSrv );
+ }
+ }
+ return $status;
+ }
+
+ /**
+ * Get the bucket for resource path.
+ * This should avoid throwing any exceptions.
+ *
+ * @param $path string
+ * @return integer
+ */
+ protected function getBucketFromKey( $path ) {
+ $prefix = substr( sha1( $path ), 0, 2 ); // first 2 hex chars (8 bits)
+ return intval( base_convert( $prefix, 16, 10 ) ) % count( $this->srvsByBucket );
+ }
+
+ /**
+ * Make sure remaining locks get cleared for sanity
+ */
+ function __destruct() {
+ $this->releaseLocks();
+ foreach ( $this->conns as $conn ) {
+ fclose( $conn );
+ }
+ }
+}
diff --git a/includes/filerepo/backend/lockmanager/LockManager.php b/includes/filerepo/backend/lockmanager/LockManager.php
new file mode 100644
index 00000000..23603a4f
--- /dev/null
+++ b/includes/filerepo/backend/lockmanager/LockManager.php
@@ -0,0 +1,182 @@
+<?php
+/**
+ * @defgroup LockManager Lock management
+ * @ingroup FileBackend
+ */
+
+/**
+ * @file
+ * @ingroup LockManager
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class for handling resource locking.
+ *
+ * Locks on resource keys can either be shared or exclusive.
+ *
+ * Implementations must keep track of what is locked by this proccess
+ * in-memory and support nested locking calls (using reference counting).
+ * At least LOCK_UW and LOCK_EX must be implemented. LOCK_SH can be a no-op.
+ * Locks should either be non-blocking or have low wait timeouts.
+ *
+ * Subclasses should avoid throwing exceptions at all costs.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+abstract class LockManager {
+ /** @var Array Mapping of lock types to the type actually used */
+ protected $lockTypeMap = array(
+ self::LOCK_SH => self::LOCK_SH,
+ self::LOCK_UW => self::LOCK_EX, // subclasses may use self::LOCK_SH
+ self::LOCK_EX => self::LOCK_EX
+ );
+
+ /** @var Array Map of (resource path => lock type => count) */
+ protected $locksHeld = array();
+
+ /* Lock types; stronger locks have higher values */
+ const LOCK_SH = 1; // shared lock (for reads)
+ const LOCK_UW = 2; // shared lock (for reads used to write elsewhere)
+ const LOCK_EX = 3; // exclusive lock (for writes)
+
+ /**
+ * Construct a new instance from configuration
+ *
+ * @param $config Array
+ */
+ public function __construct( array $config ) {}
+
+ /**
+ * Lock the resources at the given abstract paths
+ *
+ * @param $paths Array List of resource names
+ * @param $type integer LockManager::LOCK_* constant
+ * @return Status
+ */
+ final public function lock( array $paths, $type = self::LOCK_EX ) {
+ return $this->doLock( array_unique( $paths ), $this->lockTypeMap[$type] );
+ }
+
+ /**
+ * Unlock the resources at the given abstract paths
+ *
+ * @param $paths Array List of storage paths
+ * @param $type integer LockManager::LOCK_* constant
+ * @return Status
+ */
+ final public function unlock( array $paths, $type = self::LOCK_EX ) {
+ return $this->doUnlock( array_unique( $paths ), $this->lockTypeMap[$type] );
+ }
+
+ /**
+ * Get the base 36 SHA-1 of a string, padded to 31 digits
+ *
+ * @param $path string
+ * @return string
+ */
+ final protected static function sha1Base36( $path ) {
+ return wfBaseConvert( sha1( $path ), 16, 36, 31 );
+ }
+
+ /**
+ * Lock resources with the given keys and lock type
+ *
+ * @param $paths Array List of storage paths
+ * @param $type integer LockManager::LOCK_* constant
+ * @return string
+ */
+ abstract protected function doLock( array $paths, $type );
+
+ /**
+ * Unlock resources with the given keys and lock type
+ *
+ * @param $paths Array List of storage paths
+ * @param $type integer LockManager::LOCK_* constant
+ * @return string
+ */
+ abstract protected function doUnlock( array $paths, $type );
+}
+
+/**
+ * Self releasing locks
+ *
+ * LockManager helper class to handle scoped locks, which
+ * release when an object is destroyed or goes out of scope.
+ *
+ * @ingroup LockManager
+ * @since 1.19
+ */
+class ScopedLock {
+ /** @var LockManager */
+ protected $manager;
+ /** @var Status */
+ protected $status;
+ /** @var Array List of resource paths*/
+ protected $paths;
+
+ protected $type; // integer lock type
+
+ /**
+ * @param $manager LockManager
+ * @param $paths Array List of storage paths
+ * @param $type integer LockManager::LOCK_* constant
+ * @param $status Status
+ */
+ protected function __construct(
+ LockManager $manager, array $paths, $type, Status $status
+ ) {
+ $this->manager = $manager;
+ $this->paths = $paths;
+ $this->status = $status;
+ $this->type = $type;
+ }
+
+ protected function __clone() {}
+
+ /**
+ * Get a ScopedLock object representing a lock on resource paths.
+ * Any locks are released once this object goes out of scope.
+ * The status object is updated with any errors or warnings.
+ *
+ * @param $manager LockManager
+ * @param $paths Array List of storage paths
+ * @param $type integer LockManager::LOCK_* constant
+ * @param $status Status
+ * @return ScopedLock|null Returns null on failure
+ */
+ public static function factory(
+ LockManager $manager, array $paths, $type, Status $status
+ ) {
+ $lockStatus = $manager->lock( $paths, $type );
+ $status->merge( $lockStatus );
+ if ( $lockStatus->isOK() ) {
+ return new self( $manager, $paths, $type, $status );
+ }
+ return null;
+ }
+
+ function __destruct() {
+ $wasOk = $this->status->isOK();
+ $this->status->merge( $this->manager->unlock( $this->paths, $this->type ) );
+ if ( $wasOk ) {
+ // Make sure status is OK, despite any unlockFiles() fatals
+ $this->status->setResult( true, $this->status->value );
+ }
+ }
+}
+
+/**
+ * Simple version of LockManager that does nothing
+ * @since 1.19
+ */
+class NullLockManager extends LockManager {
+ protected function doLock( array $paths, $type ) {
+ return Status::newGood();
+ }
+
+ protected function doUnlock( array $paths, $type ) {
+ return Status::newGood();
+ }
+}
diff --git a/includes/filerepo/backend/lockmanager/LockManagerGroup.php b/includes/filerepo/backend/lockmanager/LockManagerGroup.php
new file mode 100644
index 00000000..11e77972
--- /dev/null
+++ b/includes/filerepo/backend/lockmanager/LockManagerGroup.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Class to handle file lock manager registration
+ *
+ * @ingroup LockManager
+ * @author Aaron Schulz
+ * @since 1.19
+ */
+class LockManagerGroup {
+
+ /**
+ * @var LockManagerGroup
+ */
+ protected static $instance = null;
+
+ /** @var Array of (name => ('class' =>, 'config' =>, 'instance' =>)) */
+ protected $managers = array();
+
+ protected function __construct() {}
+ protected function __clone() {}
+
+ /**
+ * @return LockManagerGroup
+ */
+ public static function singleton() {
+ if ( self::$instance == null ) {
+ self::$instance = new self();
+ self::$instance->initFromGlobals();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Register lock managers from the global variables
+ *
+ * @return void
+ */
+ protected function initFromGlobals() {
+ global $wgLockManagers;
+
+ $this->register( $wgLockManagers );
+ }
+
+ /**
+ * Register an array of file lock manager configurations
+ *
+ * @param $configs Array
+ * @return void
+ * @throws MWException
+ */
+ protected function register( array $configs ) {
+ foreach ( $configs as $config ) {
+ if ( !isset( $config['name'] ) ) {
+ throw new MWException( "Cannot register a lock manager with no name." );
+ }
+ $name = $config['name'];
+ if ( !isset( $config['class'] ) ) {
+ throw new MWException( "Cannot register lock manager `{$name}` with no class." );
+ }
+ $class = $config['class'];
+ unset( $config['class'] ); // lock manager won't need this
+ $this->managers[$name] = array(
+ 'class' => $class,
+ 'config' => $config,
+ 'instance' => null
+ );
+ }
+ }
+
+ /**
+ * Get the lock manager object with a given name
+ *
+ * @param $name string
+ * @return LockManager
+ * @throws MWException
+ */
+ public function get( $name ) {
+ if ( !isset( $this->managers[$name] ) ) {
+ throw new MWException( "No lock manager defined with the name `$name`." );
+ }
+ // Lazy-load the actual lock manager instance
+ if ( !isset( $this->managers[$name]['instance'] ) ) {
+ $class = $this->managers[$name]['class'];
+ $config = $this->managers[$name]['config'];
+ $this->managers[$name]['instance'] = new $class( $config );
+ }
+ return $this->managers[$name]['instance'];
+ }
+}
diff --git a/includes/filerepo/ArchivedFile.php b/includes/filerepo/file/ArchivedFile.php
index 0d9e349b..3b9bd7f0 100644
--- a/includes/filerepo/ArchivedFile.php
+++ b/includes/filerepo/file/ArchivedFile.php
@@ -3,13 +3,13 @@
* Deleted file in the 'filearchive' table
*
* @file
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
/**
* Class representing a row of the 'filearchive' table
*
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
class ArchivedFile {
/**#@+
@@ -73,8 +73,8 @@ class ArchivedFile {
$this->dataLoaded = false;
$this->exists = false;
- if( is_object( $title ) ) {
- $this->title = $title;
+ if( $title instanceof Title ) {
+ $this->title = File::normalizeTitle( $title, 'exception' );
$this->name = $title->getDBkey();
}
@@ -86,7 +86,7 @@ class ArchivedFile {
$this->key = $key;
}
- if ( !$id && !$key && !is_object( $title ) ) {
+ if ( !$id && !$key && !( $title instanceof Title ) ) {
throw new MWException( "No specifications provided to ArchivedFile constructor." );
}
}
@@ -177,6 +177,9 @@ class ArchivedFile {
/**
* Loads a file object from the filearchive table
+ *
+ * @param $row
+ *
* @return ArchivedFile
*/
public static function newFromRow( $row ) {
@@ -205,6 +208,8 @@ class ArchivedFile {
/**
* Return the associated title object
+ *
+ * @return Title
*/
public function getTitle() {
return $this->title;
@@ -212,16 +217,24 @@ class ArchivedFile {
/**
* Return the file name
+ *
+ * @return string
*/
public function getName() {
return $this->name;
}
+ /**
+ * @return int
+ */
public function getID() {
$this->load();
return $this->id;
}
+ /**
+ * @return bool
+ */
public function exists() {
$this->load();
return $this->exists;
@@ -229,6 +242,7 @@ class ArchivedFile {
/**
* Return the FileStore key
+ * @return string
*/
public function getKey() {
$this->load();
@@ -237,6 +251,7 @@ class ArchivedFile {
/**
* Return the FileStore key (overriding base File class)
+ * @return string
*/
public function getStorageKey() {
return $this->getKey();
@@ -244,6 +259,7 @@ class ArchivedFile {
/**
* Return the FileStore storage group
+ * @return string
*/
public function getGroup() {
return $this->group;
@@ -251,6 +267,7 @@ class ArchivedFile {
/**
* Return the width of the image
+ * @return int
*/
public function getWidth() {
$this->load();
@@ -259,6 +276,7 @@ class ArchivedFile {
/**
* Return the height of the image
+ * @return int
*/
public function getHeight() {
$this->load();
@@ -267,6 +285,7 @@ class ArchivedFile {
/**
* Get handler-specific metadata
+ * @return string
*/
public function getMetadata() {
$this->load();
@@ -275,6 +294,7 @@ class ArchivedFile {
/**
* Return the size of the image file, in bytes
+ * @return int
*/
public function getSize() {
$this->load();
@@ -283,6 +303,7 @@ class ArchivedFile {
/**
* Return the bits of the image file, in bytes
+ * @return int
*/
public function getBits() {
$this->load();
@@ -291,6 +312,7 @@ class ArchivedFile {
/**
* Returns the mime type of the file.
+ * @return string
*/
public function getMimeType() {
$this->load();
@@ -326,6 +348,7 @@ class ArchivedFile {
/**
* Return the type of the media in the file.
* Use the value returned by this function with the MEDIATYPE_xxx constants.
+ * @return string
*/
public function getMediaType() {
$this->load();
@@ -334,6 +357,8 @@ class ArchivedFile {
/**
* Return upload timestamp.
+ *
+ * @return string
*/
public function getTimestamp() {
$this->load();
@@ -342,6 +367,8 @@ class ArchivedFile {
/**
* Return the user ID of the uploader.
+ *
+ * @return int
*/
public function getUser() {
$this->load();
@@ -354,6 +381,8 @@ class ArchivedFile {
/**
* Return the user name of the uploader.
+ *
+ * @return string
*/
public function getUserText() {
$this->load();
@@ -366,6 +395,8 @@ class ArchivedFile {
/**
* Return upload description.
+ *
+ * @return string
*/
public function getDescription() {
$this->load();
@@ -378,6 +409,8 @@ class ArchivedFile {
/**
* Return the user ID of the uploader.
+ *
+ * @return int
*/
public function getRawUser() {
$this->load();
@@ -386,6 +419,8 @@ class ArchivedFile {
/**
* Return the user name of the uploader.
+ *
+ * @return string
*/
public function getRawUserText() {
$this->load();
@@ -394,6 +429,8 @@ class ArchivedFile {
/**
* Return upload description.
+ *
+ * @return string
*/
public function getRawDescription() {
$this->load();
@@ -424,10 +461,11 @@ class ArchivedFile {
* Determine if the current user is allowed to view a particular
* field of this FileStore image file, if it's marked as deleted.
* @param $field Integer
+ * @param $user User object to check, or null to use $wgUser
* @return bool
*/
- public function userCan( $field ) {
+ public function userCan( $field, User $user = null ) {
$this->load();
- return Revision::userCanBitfield( $this->deleted, $field );
+ return Revision::userCanBitfield( $this->deleted, $field, $user );
}
}
diff --git a/includes/filerepo/File.php b/includes/filerepo/file/File.php
index 1fd6d452..f74fb678 100644
--- a/includes/filerepo/File.php
+++ b/includes/filerepo/file/File.php
@@ -1,9 +1,16 @@
<?php
/**
+ * @defgroup FileAbstraction File abstraction
+ * @ingroup FileRepo
+ *
+ * Represents files in a repository.
+ */
+
+/**
* Base code for files.
*
* @file
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
/**
@@ -23,14 +30,21 @@
* The convenience functions wfLocalFile() and wfFindFile() should be sufficient
* in most cases.
*
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
abstract class File {
const DELETED_FILE = 1;
const DELETED_COMMENT = 2;
const DELETED_USER = 4;
const DELETED_RESTRICTED = 8;
- const RENDER_NOW = 1;
+
+ /** Force rendering in the current process */
+ const RENDER_NOW = 1;
+ /**
+ * Force rendering even if thumbnail already exist and using RENDER_NOW
+ * I.e. you have to pass both flags: File::RENDER_NOW | File::RENDER_FORCE
+ */
+ const RENDER_FORCE = 2;
const DELETE_SOURCE = 1;
@@ -54,30 +68,88 @@ abstract class File {
*/
/**
- * @var LocalRepo
+ * @var FileRepo|false
*/
var $repo;
/**
- * @var Title
+ * @var Title|false
*/
var $title;
var $lastError, $redirected, $redirectedTitle;
/**
+ * @var FSFile|false
+ */
+ protected $fsFile;
+
+ /**
* @var MediaHandler
*/
protected $handler;
/**
- * Call this constructor from child classes
+ * @var string
+ */
+ protected $url, $extension, $name, $path, $hashPath, $pageCount, $transformScript;
+
+ /**
+ * @var bool
+ */
+ protected $canRender, $isSafeFile;
+
+ /**
+ * @var string Required Repository class type
+ */
+ protected $repoClass = 'FileRepo';
+
+ /**
+ * Call this constructor from child classes.
+ *
+ * Both $title and $repo are optional, though some functions
+ * may return false or throw exceptions if they are not set.
+ * Most subclasses will want to call assertRepoDefined() here.
+ *
+ * @param $title Title|string|false
+ * @param $repo FileRepo|false
*/
function __construct( $title, $repo ) {
+ if ( $title !== false ) { // subclasses may not use MW titles
+ $title = self::normalizeTitle( $title, 'exception' );
+ }
$this->title = $title;
$this->repo = $repo;
}
+ /**
+ * Given a string or Title object return either a
+ * valid Title object with namespace NS_FILE or null
+ *
+ * @param $title Title|string
+ * @param $exception string|false Use 'exception' to throw an error on bad titles
+ * @return Title|null
+ */
+ static function normalizeTitle( $title, $exception = false ) {
+ $ret = $title;
+ if ( $ret instanceof Title ) {
+ # Normalize NS_MEDIA -> NS_FILE
+ if ( $ret->getNamespace() == NS_MEDIA ) {
+ $ret = Title::makeTitleSafe( NS_FILE, $ret->getDBkey() );
+ # Sanity check the title namespace
+ } elseif ( $ret->getNamespace() !== NS_FILE ) {
+ $ret = null;
+ }
+ } else {
+ # Convert strings to Title objects
+ $ret = Title::makeTitleSafe( NS_FILE, (string)$ret );
+ }
+ if ( !$ret && $exception !== false ) {
+ throw new MWException( "`$title` is not a valid file title." );
+ }
+ return $ret;
+ }
+
function __get( $name ) {
$function = array( $this, 'get' . ucfirst( $name ) );
if ( !is_callable( $function ) ) {
@@ -123,8 +195,7 @@ abstract class File {
static function checkExtensionCompatibility( File $old, $new ) {
$oldMime = $old->getMimeType();
$n = strrpos( $new, '.' );
- $newExt = self::normalizeExtension(
- $n ? substr( $new, $n + 1 ) : '' );
+ $newExt = self::normalizeExtension( $n ? substr( $new, $n + 1 ) : '' );
$mimeMagic = MimeMagic::singleton();
return $mimeMagic->isMatchingExtension( $newExt, $oldMime );
}
@@ -158,6 +229,7 @@ abstract class File {
*/
public function getName() {
if ( !isset( $this->name ) ) {
+ $this->assertRepoDefined();
$this->name = $this->repo->getNameFromTitle( $this->title );
}
return $this->name;
@@ -179,9 +251,12 @@ abstract class File {
/**
* Return the associated title object
- * @return Title
+ *
+ * @return Title|false
*/
- public function getTitle() { return $this->title; }
+ public function getTitle() {
+ return $this->title;
+ }
/**
* Return the title used to find this file
@@ -202,6 +277,7 @@ abstract class File {
*/
public function getUrl() {
if ( !isset( $this->url ) ) {
+ $this->assertRepoDefined();
$this->url = $this->repo->getZoneUrl( 'public' ) . '/' . $this->getUrlRel();
}
return $this->url;
@@ -218,6 +294,9 @@ abstract class File {
return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
}
+ /**
+ * @return string
+ */
public function getCanonicalUrl() {
return wfExpandUrl( $this->getUrl(), PROTO_CANONICAL );
}
@@ -226,11 +305,12 @@ abstract class File {
* @return string
*/
function getViewURL() {
- if( $this->mustRender()) {
- if( $this->canRender() ) {
+ if ( $this->mustRender() ) {
+ if ( $this->canRender() ) {
return $this->createThumb( $this->getWidth() );
} else {
- wfDebug(__METHOD__.': supposed to render '.$this->getName().' ('.$this->getMimeType()."), but can't!\n");
+ wfDebug( __METHOD__.': supposed to render ' . $this->getName() .
+ ' (' . $this->getMimeType() . "), but can't!\n" );
return $this->getURL(); #hm... return NULL?
}
} else {
@@ -239,7 +319,7 @@ abstract class File {
}
/**
- * Return the full filesystem path to the file. Note that this does
+ * Return the storage path to the file. Note that this does
* not mean that a file actually exists under that location.
*
* This path depends on whether directory hashing is active or not,
@@ -253,21 +333,30 @@ abstract class File {
*/
public function getPath() {
if ( !isset( $this->path ) ) {
- $this->path = $this->repo->getZonePath('public') . '/' . $this->getRel();
+ $this->assertRepoDefined();
+ $this->path = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
}
return $this->path;
}
/**
- * Alias for getPath()
- *
- * @deprecated since 1.18 Use getPath().
+ * Get an FS copy or original of this file and return the path.
+ * Returns false on failure. Callers must not alter the file.
+ * Temporary files are cleared automatically.
*
- * @return string
+ * @return string|false
*/
- public function getFullPath() {
- wfDeprecated( __METHOD__ );
- return $this->getPath();
+ public function getLocalRefPath() {
+ $this->assertRepoDefined();
+ if ( !isset( $this->fsFile ) ) {
+ $this->fsFile = $this->repo->getLocalReference( $this->getPath() );
+ if ( !$this->fsFile ) {
+ $this->fsFile = false; // null => false; cache negative hits
+ }
+ }
+ return ( $this->fsFile )
+ ? $this->fsFile->getPath()
+ : false;
}
/**
@@ -292,6 +381,8 @@ abstract class File {
* STUB
* Overridden by LocalFile, UnregisteredLocalFile
*
+ * @param $page int
+ *
* @return false|number
*/
public function getHeight( $page = 1 ) {
@@ -357,7 +448,7 @@ abstract class File {
public function convertMetadataVersion($metadata, $version) {
$handler = $this->getHandler();
if ( !is_array( $metadata ) ) {
- //just to make the return type consistant
+ // Just to make the return type consistent
$metadata = unserialize( $metadata );
}
if ( $handler ) {
@@ -402,7 +493,9 @@ abstract class File {
* Overridden by LocalFile,
* STUB
*/
- function getMediaType() { return MEDIATYPE_UNKNOWN; }
+ function getMediaType() {
+ return MEDIATYPE_UNKNOWN;
+ }
/**
* Checks if the output of transform() for this file is likely
@@ -488,6 +581,8 @@ abstract class File {
* @return bool
*/
protected function _getIsSafeFile() {
+ global $wgTrustedMediaFormats;
+
if ( $this->allowInlineDisplay() ) {
return true;
}
@@ -495,8 +590,6 @@ abstract class File {
return true;
}
- global $wgTrustedMediaFormats;
-
$type = $this->getMediaType();
$mime = $this->getMimeType();
#wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
@@ -532,7 +625,7 @@ abstract class File {
* @return bool
*/
function isTrustedFile() {
- #this could be implemented to check a flag in the databas,
+ #this could be implemented to check a flag in the database,
#look for signatures, etc
return false;
}
@@ -545,7 +638,7 @@ abstract class File {
* @return boolean Whether file exists in the repository.
*/
public function exists() {
- return $this->getPath() && file_exists( $this->path );
+ return $this->getPath() && $this->repo->fileExists( $this->path );
}
/**
@@ -617,7 +710,8 @@ abstract class File {
return null;
}
$extension = $this->getExtension();
- list( $thumbExt, $thumbMime ) = $this->handler->getThumbType( $extension, $this->getMimeType(), $params );
+ list( $thumbExt, $thumbMime ) = $this->handler->getThumbType(
+ $extension, $this->getMimeType(), $params );
$thumbName = $this->handler->makeParamString( $params ) . '-' . $name;
if ( $thumbExt != $extension ) {
$thumbName .= ".$thumbExt";
@@ -648,85 +742,147 @@ abstract class File {
$params['height'] = $height;
}
$thumb = $this->transform( $params );
- if( is_null( $thumb ) || $thumb->isError() ) return '';
+ if ( is_null( $thumb ) || $thumb->isError() ) {
+ return '';
+ }
return $thumb->getUrl();
}
/**
+ * Return either a MediaTransformError or placeholder thumbnail (if $wgIgnoreImageErrors)
+ *
+ * @param $thumbPath string Thumbnail storage path
+ * @param $thumbUrl string Thumbnail URL
+ * @param $params Array
+ * @param $flags integer
+ * @return MediaTransformOutput
+ */
+ protected function transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags ) {
+ global $wgIgnoreImageErrors;
+
+ if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
+ return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ } else {
+ return new MediaTransformError( 'thumbnail_error',
+ $params['width'], 0, wfMsg( 'thumbnail-dest-create' ) );
+ }
+ }
+
+ /**
* Transform a media file
*
* @param $params Array: an associative array of handler-specific parameters.
* Typical keys are width, height and page.
* @param $flags Integer: a bitfield, may contain self::RENDER_NOW to force rendering
- * @return MediaTransformOutput | false
+ * @return MediaTransformOutput|false
*/
function transform( $params, $flags = 0 ) {
- global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch, $wgServer;
+ global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch;
wfProfileIn( __METHOD__ );
do {
if ( !$this->canRender() ) {
- // not a bitmap or renderable image, don't try.
$thumb = $this->iconThumb();
- break;
+ break; // not a bitmap or renderable image, don't try
}
// Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
- $descriptionUrl = $this->getDescriptionUrl();
+ $descriptionUrl = $this->getDescriptionUrl();
if ( $descriptionUrl ) {
$params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
}
$script = $this->getTransformScript();
- if ( $script && !($flags & self::RENDER_NOW) ) {
+ if ( $script && !( $flags & self::RENDER_NOW ) ) {
// Use a script to transform on client request, if possible
$thumb = $this->handler->getScriptedTransform( $this, $script, $params );
- if( $thumb ) {
+ if ( $thumb ) {
break;
}
}
$normalisedParams = $params;
$this->handler->normaliseParams( $this, $normalisedParams );
+
$thumbName = $this->thumbName( $normalisedParams );
- $thumbPath = $this->getThumbPath( $thumbName );
$thumbUrl = $this->getThumbUrl( $thumbName );
+ $thumbPath = $this->getThumbPath( $thumbName ); // final thumb path
- if ( $this->repo && $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
- $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
- break;
- }
-
- wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
- $this->migrateThumbFile( $thumbName );
- if ( file_exists( $thumbPath )) {
- $thumbTime = filemtime( $thumbPath );
- if ( $thumbTime !== FALSE &&
- gmdate( 'YmdHis', $thumbTime ) >= $wgThumbnailEpoch ) {
-
+ if ( $this->repo ) {
+ // Defer rendering if a 404 handler is set up...
+ if ( $this->repo->canTransformVia404() && !( $flags & self::RENDER_NOW ) ) {
+ wfDebug( __METHOD__ . " transformation deferred." );
+ // XXX: Pass in the storage path even though we are not rendering anything
+ // and the path is supposed to be an FS path. This is due to getScalerType()
+ // getting called on the path and clobbering $thumb->getUrl() if it's false.
$thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
break;
}
+ // Clean up broken thumbnails as needed
+ $this->migrateThumbFile( $thumbName );
+ // Check if an up-to-date thumbnail already exists...
+ wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
+ if ( $this->repo->fileExists( $thumbPath ) && !( $flags & self::RENDER_FORCE ) ) {
+ $timestamp = $this->repo->getFileTimestamp( $thumbPath );
+ if ( $timestamp !== false && $timestamp >= $wgThumbnailEpoch ) {
+ // XXX: Pass in the storage path even though we are not rendering anything
+ // and the path is supposed to be an FS path. This is due to getScalerType()
+ // getting called on the path and clobbering $thumb->getUrl() if it's false.
+ $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ $thumb->setStoragePath( $thumbPath );
+ break;
+ }
+ } elseif ( $flags & self::RENDER_FORCE ) {
+ wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" );
+ }
+ }
+
+ // Create a temp FS file with the same extension and the thumbnail
+ $thumbExt = FileBackend::extensionFromPath( $thumbPath );
+ $tmpFile = TempFSFile::factory( 'transform_', $thumbExt );
+ if ( !$tmpFile ) {
+ $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
+ break;
}
- $thumb = $this->handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
+ $tmpThumbPath = $tmpFile->getPath(); // path of 0-byte temp file
- // Ignore errors if requested
- if ( !$thumb ) {
+ // Actually render the thumbnail...
+ $thumb = $this->handler->doTransform( $this, $tmpThumbPath, $thumbUrl, $params );
+ $tmpFile->bind( $thumb ); // keep alive with $thumb
+
+ if ( !$thumb ) { // bad params?
$thumb = null;
- } elseif ( $thumb->isError() ) {
+ } elseif ( $thumb->isError() ) { // transform error
$this->lastError = $thumb->toText();
- if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) {
- $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ // Ignore errors if requested
+ if ( $wgIgnoreImageErrors && !( $flags & self::RENDER_NOW ) ) {
+ $thumb = $this->handler->getTransform( $this, $tmpThumbPath, $thumbUrl, $params );
+ }
+ } elseif ( $this->repo && $thumb->hasFile() && !$thumb->fileIsSource() ) {
+ $backend = $this->repo->getBackend();
+ // Copy the thumbnail from the file system into storage. This avoids using
+ // FileRepo::store(); getThumbPath() uses a different zone in some subclasses.
+ $backend->prepare( array( 'dir' => dirname( $thumbPath ) ) );
+ $status = $backend->store(
+ array( 'src' => $tmpThumbPath, 'dst' => $thumbPath, 'overwrite' => 1 ),
+ array( 'force' => 1, 'nonLocking' => 1, 'allowStale' => 1 )
+ );
+ if ( $status->isOK() ) {
+ $thumb->setStoragePath( $thumbPath );
+ } else {
+ $thumb = $this->transformErrorOutput( $thumbPath, $thumbUrl, $params, $flags );
}
}
// 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 ) );
+ if ( $wgUseSquid ) {
+ if ( !$thumb || $thumb->isError() || $thumb->getUrl() != $this->getURL() ) {
+ SquidUpdate::purge( array( $thumbUrl ) );
+ }
}
- } while (false);
+ } while ( false );
wfProfileOut( __METHOD__ );
return is_object( $thumb ) ? $thumb : false;
@@ -741,6 +897,7 @@ abstract class File {
/**
* Get a MediaHandler instance for this file
+ *
* @return MediaHandler
*/
function getHandler() {
@@ -752,16 +909,17 @@ abstract class File {
/**
* Get a ThumbnailImage representing a file type icon
+ *
* @return ThumbnailImage
*/
function iconThumb() {
global $wgStylePath, $wgStyleDirectory;
$try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
- foreach( $try as $icon ) {
+ foreach ( $try as $icon ) {
$path = '/common/images/icons/' . $icon;
$filepath = $wgStyleDirectory . $path;
- if( file_exists( $filepath ) ) {
+ if ( file_exists( $filepath ) ) { // always FS
return new ThumbnailImage( $this, $wgStylePath . $path, 120, 120 );
}
}
@@ -789,8 +947,10 @@ abstract class File {
* Purge shared caches such as thumbnails and DB data caching
* STUB
* Overridden by LocalFile
+ * @param $options Array Options, which include:
+ * 'forThumbRefresh' : The purging is only to refresh thumbnails
*/
- function purgeCache() {}
+ function purgeCache( $options = array() ) {}
/**
* Purge the file description page, but don't go after
@@ -866,13 +1026,15 @@ abstract class File {
*/
function getHashPath() {
if ( !isset( $this->hashPath ) ) {
+ $this->assertRepoDefined();
$this->hashPath = $this->repo->getHashPath( $this->getName() );
}
return $this->hashPath;
}
/**
- * Get the path of the file relative to the public zone root
+ * Get the path of the file relative to the public zone root.
+ * This function is overriden in OldLocalFile to be like getArchiveRel().
*
* @return string
*/
@@ -881,16 +1043,7 @@ 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 archived file
+ * Get the path of an archived file relative to the public zone root
*
* @param $suffix bool|string if not false, the name of an archived thumbnail file
*
@@ -907,11 +1060,39 @@ abstract class File {
}
/**
- * Get the relative path for an archived file's thumbs directory
+ * Get the path, relative to the thumbnail zone root, 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 getThumbRel( $suffix = false ) {
+ $path = $this->getRel();
+ if ( $suffix !== false ) {
+ $path .= '/' . $suffix;
+ }
+ return $path;
+ }
+
+ /**
+ * Get urlencoded path of the file relative to the public zone root.
+ * This function is overriden in OldLocalFile to be like getArchiveUrl().
+ *
+ * @return string
+ */
+ function getUrlRel() {
+ return $this->getHashPath() . rawurlencode( $this->getName() );
+ }
+
+ /**
+ * Get the path, relative to the thumbnail zone root, for an archived file's thumbs directory
* or a specific thumb if the $suffix is given.
*
* @param $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 getArchiveThumbRel( $archiveName, $suffix = false ) {
$path = 'archive/' . $this->getHashPath() . $archiveName . "/";
@@ -931,11 +1112,12 @@ abstract class File {
* @return string
*/
function getArchivePath( $suffix = false ) {
+ $this->assertRepoDefined();
return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
}
/**
- * Get the path of the archived file's thumbs, or a particular thumb if $suffix is specified
+ * Get the path of an 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
@@ -943,7 +1125,9 @@ abstract class File {
* @return string
*/
function getArchiveThumbPath( $archiveName, $suffix = false ) {
- return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getArchiveThumbRel( $archiveName, $suffix );
+ $this->assertRepoDefined();
+ return $this->repo->getZonePath( 'thumb' ) . '/' .
+ $this->getArchiveThumbRel( $archiveName, $suffix );
}
/**
@@ -954,11 +1138,8 @@ abstract class File {
* @return string
*/
function getThumbPath( $suffix = false ) {
- $path = $this->repo->getZonePath( 'thumb' ) . '/' . $this->getRel();
- if ( $suffix !== false ) {
- $path .= '/' . $suffix;
- }
- return $path;
+ $this->assertRepoDefined();
+ return $this->repo->getZonePath( 'thumb' ) . '/' . $this->getThumbRel( $suffix );
}
/**
@@ -969,7 +1150,8 @@ abstract class File {
* @return string
*/
function getArchiveUrl( $suffix = false ) {
- $path = $this->repo->getZoneUrl('public') . '/archive/' . $this->getHashPath();
+ $this->assertRepoDefined();
+ $path = $this->repo->getZoneUrl( 'public' ) . '/archive/' . $this->getHashPath();
if ( $suffix === false ) {
$path = substr( $path, 0, -1 );
} else {
@@ -987,7 +1169,9 @@ abstract class File {
* @return string
*/
function getArchiveThumbUrl( $archiveName, $suffix = false ) {
- $path = $this->repo->getZoneUrl('thumb') . '/archive/' . $this->getHashPath() . rawurlencode( $archiveName ) . "/";
+ $this->assertRepoDefined();
+ $path = $this->repo->getZoneUrl( 'thumb' ) . '/archive/' .
+ $this->getHashPath() . rawurlencode( $archiveName ) . "/";
if ( $suffix === false ) {
$path = substr( $path, 0, -1 );
} else {
@@ -1004,7 +1188,8 @@ abstract class File {
* @return path
*/
function getThumbUrl( $suffix = false ) {
- $path = $this->repo->getZoneUrl('thumb') . '/' . $this->getUrlRel();
+ $this->assertRepoDefined();
+ $path = $this->repo->getZoneUrl( 'thumb' ) . '/' . $this->getUrlRel();
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
@@ -1012,46 +1197,49 @@ abstract class File {
}
/**
- * Get the virtual URL for an archived file's thumbs, or a specific thumb.
+ * Get the public zone virtual URL for a current version source file
*
* @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 ) {
- $path = substr( $path, 0, -1 );
- } else {
- $path .= rawurlencode( $suffix );
+ function getVirtualUrl( $suffix = false ) {
+ $this->assertRepoDefined();
+ $path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
+ if ( $suffix !== false ) {
+ $path .= '/' . rawurlencode( $suffix );
}
return $path;
}
/**
- * Get the virtual URL for a thumbnail file or directory
+ * Get the public zone virtual URL for an archived version source file
*
* @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 ) {
- $path .= '/' . rawurlencode( $suffix );
+ function getArchiveVirtualUrl( $suffix = false ) {
+ $this->assertRepoDefined();
+ $path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
+ if ( $suffix === false ) {
+ $path = substr( $path, 0, -1 );
+ } else {
+ $path .= rawurlencode( $suffix );
}
return $path;
}
/**
- * Get the virtual URL for the file itself
+ * 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 getVirtualUrl( $suffix = false ) {
- $path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
+ function getThumbVirtualUrl( $suffix = false ) {
+ $this->assertRepoDefined();
+ $path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
@@ -1062,6 +1250,7 @@ abstract class File {
* @return bool
*/
function isHashed() {
+ $this->assertRepoDefined();
return $this->repo->isHashed();
}
@@ -1125,8 +1314,7 @@ abstract class File {
* @return bool
*/
function isLocal() {
- $repo = $this->getRepo();
- return $repo && $repo->isLocal();
+ return $this->repo && $this->repo->isLocal();
}
/**
@@ -1141,7 +1329,7 @@ abstract class File {
/**
* Returns the repository
*
- * @return FileRepo
+ * @return FileRepo|false
*/
function getRepo() {
return $this->repo;
@@ -1306,7 +1494,11 @@ abstract class File {
* @return string
*/
function getDescriptionUrl() {
- return $this->repo->getDescriptionUrl( $this->getName() );
+ if ( $this->repo ) {
+ return $this->repo->getDescriptionUrl( $this->getName() );
+ } else {
+ return false;
+ }
}
/**
@@ -1316,7 +1508,7 @@ abstract class File {
*/
function getDescriptionText() {
global $wgMemc, $wgLang;
- if ( !$this->repo->fetchDescription ) {
+ if ( !$this->repo || !$this->repo->fetchDescription ) {
return false;
}
$renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgLang->getCode() );
@@ -1354,17 +1546,13 @@ abstract class File {
}
/**
- * Get the 14-character timestamp of the file upload, or false if
- * it doesn't exist
+ * Get the 14-character timestamp of the file upload
*
- * @return string
+ * @return string|false TS_MW timestamp or false on failure
*/
function getTimestamp() {
- $path = $this->getPath();
- if ( !file_exists( $path ) ) {
- return false;
- }
- return wfTimestamp( TS_MW, filemtime( $path ) );
+ $this->assertRepoDefined();
+ return $this->repo->getFileTimestamp( $this->getPath() );
}
/**
@@ -1373,7 +1561,8 @@ abstract class File {
* @return string
*/
function getSha1() {
- return self::sha1Base36( $this->getPath() );
+ $this->assertRepoDefined();
+ return $this->repo->getFileSha1( $this->getPath() );
}
/**
@@ -1396,9 +1585,10 @@ abstract class File {
* field of this file, if it's marked as deleted.
* STUB
* @param $field Integer
+ * @param $user User object to check, or null to use $wgUser
* @return Boolean
*/
- function userCan( $field ) {
+ function userCan( $field, User $user = null ) {
return true;
}
@@ -1412,67 +1602,11 @@ abstract class File {
* @return array
*/
static function getPropsFromPath( $path, $ext = true ) {
- wfProfileIn( __METHOD__ );
wfDebug( __METHOD__.": Getting file info for $path\n" );
- $info = array(
- 'fileExists' => file_exists( $path ) && !is_dir( $path )
- );
- $gis = false;
- if ( $info['fileExists'] ) {
- $magic = MimeMagic::singleton();
-
- if ( $ext === true ) {
- $i = strrpos( $path, '.' );
- $ext = strtolower( $i ? substr( $path, $i + 1 ) : '' );
- }
-
- # mime type according to file contents
- $info['file-mime'] = $magic->guessMimeType( $path, false );
- # logical mime type
- $info['mime'] = $magic->improveTypeFromExtension( $info['file-mime'], $ext );
+ wfDeprecated( __METHOD__, '1.19' );
- list( $info['major_mime'], $info['minor_mime'] ) = self::splitMime( $info['mime'] );
- $info['media_type'] = $magic->getMediaType( $path, $info['mime'] );
-
- # Get size in bytes
- $info['size'] = filesize( $path );
-
- # Height, width and metadata
- $handler = MediaHandler::getHandler( $info['mime'] );
- if ( $handler ) {
- $tempImage = (object)array();
- $info['metadata'] = $handler->getMetadata( $tempImage, $path );
- $gis = $handler->getImageSize( $tempImage, $path, $info['metadata'] );
- } else {
- $gis = false;
- $info['metadata'] = '';
- }
- $info['sha1'] = self::sha1Base36( $path );
-
- wfDebug(__METHOD__.": $path loaded, {$info['size']} bytes, {$info['mime']}.\n");
- } else {
- $info['mime'] = null;
- $info['media_type'] = MEDIATYPE_UNKNOWN;
- $info['metadata'] = '';
- $info['sha1'] = '';
- wfDebug(__METHOD__.": $path NOT FOUND!\n");
- }
- if( $gis ) {
- # NOTE: $gis[2] contains a code for the image type. This is no longer used.
- $info['width'] = $gis[0];
- $info['height'] = $gis[1];
- if ( isset( $gis['bits'] ) ) {
- $info['bits'] = $gis['bits'];
- } else {
- $info['bits'] = 0;
- }
- } else {
- $info['width'] = 0;
- $info['height'] = 0;
- $info['bits'] = 0;
- }
- wfProfileOut( __METHOD__ );
- return $info;
+ $fsFile = new FSFile( $path );
+ return $fsFile->getProps();
}
/**
@@ -1487,14 +1621,10 @@ abstract class File {
* @return false|string False on failure
*/
static function sha1Base36( $path ) {
- wfSuppressWarnings();
- $hash = sha1_file( $path );
- wfRestoreWarnings();
- if ( $hash === false ) {
- return false;
- } else {
- return wfBaseConvert( $hash, 16, 36, 31 );
- }
+ wfDeprecated( __METHOD__, '1.19' );
+
+ $fsFile = new FSFile( $path );
+ return $fsFile->getSha1Base36();
}
/**
@@ -1566,11 +1696,24 @@ abstract class File {
function isMissing() {
return false;
}
+
+ /**
+ * Assert that $this->repo is set to a valid FileRepo instance
+ * @throws MWException
+ */
+ protected function assertRepoDefined() {
+ if ( !( $this->repo instanceof $this->repoClass ) ) {
+ throw new MWException( "A {$this->repoClass} object is not set for this File.\n" );
+ }
+ }
+
+ /**
+ * Assert that $this->title is set to a Title
+ * @throws MWException
+ */
+ protected function assertTitleDefined() {
+ if ( !( $this->title instanceof Title ) ) {
+ throw new MWException( "A Title object is not set for this File.\n" );
+ }
+ }
}
-/**
- * Aliases for backwards compatibility with 1.6
- */
-define( 'MW_IMG_DELETED_FILE', File::DELETED_FILE );
-define( 'MW_IMG_DELETED_COMMENT', File::DELETED_COMMENT );
-define( 'MW_IMG_DELETED_USER', File::DELETED_USER );
-define( 'MW_IMG_DELETED_RESTRICTED', File::DELETED_RESTRICTED );
diff --git a/includes/filerepo/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php
index 53c4a3bd..681544fd 100644
--- a/includes/filerepo/ForeignAPIFile.php
+++ b/includes/filerepo/file/ForeignAPIFile.php
@@ -3,43 +3,47 @@
* Foreign file accessible through api.php requests.
*
* @file
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
/**
* Foreign file accessible through api.php requests.
* Very hacky and inefficient, do not use :D
*
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
class ForeignAPIFile extends File {
-
private $mExists;
+ protected $repoClass = 'ForeignApiRepo';
+
/**
- * @param $title
- * @param $repo ForeignApiRepo
- * @param $info
+ * @param $title
+ * @param $repo ForeignApiRepo
+ * @param $info
* @param bool $exists
*/
function __construct( $title, $repo, $info, $exists = false ) {
parent::__construct( $title, $repo );
+
$this->mInfo = $info;
$this->mExists = $exists;
+
+ $this->assertRepoDefined();
}
/**
- * @param $title Title
- * @param $repo ForeignApiRepo
+ * @param $title Title
+ * @param $repo ForeignApiRepo
* @return ForeignAPIFile|null
*/
- static function newFromTitle( $title, $repo ) {
+ static function newFromTitle( Title $title, $repo ) {
$data = $repo->fetchImageQuery( array(
- 'titles' => 'File:' . $title->getDBKey(),
- 'iiprop' => self::getProps(),
- 'prop' => 'imageinfo',
+ 'titles' => 'File:' . $title->getDBKey(),
+ 'iiprop' => self::getProps(),
+ 'prop' => 'imageinfo',
'iimetadataversion' => MediaHandler::getMetadataVersion()
- ) );
+ ) );
$info = $repo->getImageInfo( $data );
@@ -49,31 +53,31 @@ class ForeignAPIFile extends File {
: -1;
if( $lastRedirect >= 0 ) {
$newtitle = Title::newFromText( $data['query']['redirects'][$lastRedirect]['to']);
- $img = new ForeignAPIFile( $newtitle, $repo, $info, true );
+ $img = new self( $newtitle, $repo, $info, true );
if( $img ) {
$img->redirectedFrom( $title->getDBkey() );
}
} else {
- $img = new ForeignAPIFile( $title, $repo, $info, true );
+ $img = new self( $title, $repo, $info, true );
}
return $img;
} else {
return null;
}
}
-
+
/**
* Get the property string for iiprop and aiprop
*/
static function getProps() {
return 'timestamp|user|comment|url|size|sha1|metadata|mime';
}
-
+
// Dummy functions...
public function exists() {
return $this->mExists;
}
-
+
public function getPath() {
return false;
}
@@ -100,18 +104,22 @@ class ForeignAPIFile extends File {
public function getWidth( $page = 1 ) {
return isset( $this->mInfo['width'] ) ? intval( $this->mInfo['width'] ) : 0;
}
-
+
+ /**
+ * @param $page int
+ * @return int
+ */
public function getHeight( $page = 1 ) {
return isset( $this->mInfo['height'] ) ? intval( $this->mInfo['height'] ) : 0;
}
-
+
public function getMetadata() {
if ( isset( $this->mInfo['metadata'] ) ) {
return serialize( self::parseMetadata( $this->mInfo['metadata'] ) );
}
return null;
}
-
+
public static function parseMetadata( $metadata ) {
if( !is_array( $metadata ) ) {
return $metadata;
@@ -122,11 +130,11 @@ class ForeignAPIFile extends File {
}
return $ret;
}
-
+
public function getSize() {
return isset( $this->mInfo['size'] ) ? intval( $this->mInfo['size'] ) : null;
}
-
+
public function getUrl() {
return isset( $this->mInfo['url'] ) ? strval( $this->mInfo['url'] ) : null;
}
@@ -134,25 +142,25 @@ class ForeignAPIFile extends File {
public function getUser( $method='text' ) {
return isset( $this->mInfo['user'] ) ? strval( $this->mInfo['user'] ) : null;
}
-
+
public function getDescription() {
return isset( $this->mInfo['comment'] ) ? strval( $this->mInfo['comment'] ) : null;
}
function getSha1() {
- return isset( $this->mInfo['sha1'] ) ?
- wfBaseConvert( strval( $this->mInfo['sha1'] ), 16, 36, 31 ) :
- null;
+ return isset( $this->mInfo['sha1'] )
+ ? wfBaseConvert( strval( $this->mInfo['sha1'] ), 16, 36, 31 )
+ : null;
}
-
+
function getTimestamp() {
- return wfTimestamp( TS_MW,
- isset( $this->mInfo['timestamp'] ) ?
- strval( $this->mInfo['timestamp'] ) :
- null
+ return wfTimestamp( TS_MW,
+ isset( $this->mInfo['timestamp'] )
+ ? strval( $this->mInfo['timestamp'] )
+ : null
);
}
-
+
function getMimeType() {
if( !isset( $this->mInfo['mime'] ) ) {
$magic = MimeMagic::singleton();
@@ -160,19 +168,19 @@ class ForeignAPIFile extends File {
}
return $this->mInfo['mime'];
}
-
+
/// @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() );
}
-
+
function getDescriptionUrl() {
return isset( $this->mInfo['descriptionurl'] )
? $this->mInfo['descriptionurl']
: false;
}
-
+
/**
* Only useful if we're locally caching thumbs anyway...
*/
@@ -187,47 +195,58 @@ class ForeignAPIFile extends File {
return null;
}
}
-
+
function getThumbnails() {
- $files = array();
$dir = $this->getThumbPath( $this->getName() );
- if ( is_dir( $dir ) ) {
- $handle = opendir( $dir );
- if ( $handle ) {
- while ( false !== ( $file = readdir($handle) ) ) {
- if ( $file[0] != '.' ) {
- $files[] = $file;
- }
- }
- closedir( $handle );
- }
+ $iter = $this->repo->getBackend()->getFileList( array( 'dir' => $dir ) );
+
+ $files = array();
+ foreach ( $iter as $file ) {
+ $files[] = $file;
}
+
return $files;
}
-
- function purgeCache() {
- $this->purgeThumbnails();
+
+ /**
+ * @see File::purgeCache()
+ */
+ function purgeCache( $options = array() ) {
+ $this->purgeThumbnails( $options );
$this->purgeDescriptionPage();
}
-
+
function purgeDescriptionPage() {
global $wgMemc, $wgContLang;
+
$url = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() );
$key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5($url) );
+
$wgMemc->delete( $key );
}
-
- function purgeThumbnails() {
+
+ function purgeThumbnails( $options = array() ) {
global $wgMemc;
+
$key = $this->repo->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $this->getName() );
$wgMemc->delete( $key );
+
$files = $this->getThumbnails();
+ // Give media handler a chance to filter the purge list
+ $handler = $this->getHandler();
+ if ( $handler ) {
+ $handler->filterThumbnailPurgeList( $files, $options );
+ }
+
$dir = $this->getThumbPath( $this->getName() );
+ $purgeList = array();
foreach ( $files as $file ) {
- unlink( $dir . $file );
- }
- if ( is_dir( $dir ) ) {
- rmdir( $dir ); // Might have already gone away, spews errors if we don't.
+ $purgeList[] = "{$dir}{$file}";
}
+
+ # Delete the thumbnails
+ $this->repo->cleanupBatch( $purgeList, FileRepo::SKIP_LOCKING );
+ # Clear out the thumbnail directory if empty
+ $this->repo->getBackend()->clean( array( 'dir' => $dir ) );
}
}
diff --git a/includes/filerepo/ForeignDBFile.php b/includes/filerepo/file/ForeignDBFile.php
index 09bee39c..191a712d 100644
--- a/includes/filerepo/ForeignDBFile.php
+++ b/includes/filerepo/file/ForeignDBFile.php
@@ -3,13 +3,13 @@
* Foreign file with an accessible MediaWiki database
*
* @file
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
/**
* Foreign file with an accessible MediaWiki database
*
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
class ForeignDBFile extends LocalFile {
diff --git a/includes/filerepo/LocalFile.php b/includes/filerepo/file/LocalFile.php
index 14da9122..0f8b4754 100644
--- a/includes/filerepo/LocalFile.php
+++ b/includes/filerepo/file/LocalFile.php
@@ -3,7 +3,7 @@
* Local file in the wiki's own database
*
* @file
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
/**
@@ -26,7 +26,7 @@ define( 'MW_FILE_VERSION', 8 );
* The convenience functions wfLocalFile() and wfFindFile() should be sufficient
* in most cases.
*
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
class LocalFile extends File {
/**#@+
@@ -58,6 +58,8 @@ class LocalFile extends File {
/**#@-*/
+ protected $repoClass = 'LocalRepo';
+
/**
* Create a LocalFile from a title
* Do not call this except from inside a repo class.
@@ -144,16 +146,15 @@ class LocalFile extends File {
* Do not call this except from inside a repo class.
*/
function __construct( $title, $repo ) {
- if ( !is_object( $title ) ) {
- throw new MWException( __CLASS__ . ' constructor given bogus title.' );
- }
-
parent::__construct( $title, $repo );
$this->metadata = '';
$this->historyLine = 0;
$this->historyRes = null;
$this->dataLoaded = false;
+
+ $this->assertRepoDefined();
+ $this->assertTitleDefined();
}
/**
@@ -233,7 +234,8 @@ class LocalFile extends File {
* Load metadata from the file itself
*/
function loadFromFile() {
- $this->setProps( self::getPropsFromPath( $this->getPath() ) );
+ $props = $this->repo->getFileProps( $this->getVirtualUrl() );
+ $this->setProps( $props );
}
function getCacheFields( $prefix = 'img_' ) {
@@ -383,6 +385,8 @@ class LocalFile extends File {
function upgradeRow() {
wfProfileIn( __METHOD__ );
+ $this->lock(); // begin
+
$this->loadFromFile();
# Don't destroy file info of missing files
@@ -403,19 +407,23 @@ class LocalFile extends File {
$dbw->update( 'image',
array(
- 'img_width' => $this->width,
- 'img_height' => $this->height,
- 'img_bits' => $this->bits,
+ 'img_width' => $this->width,
+ 'img_height' => $this->height,
+ 'img_bits' => $this->bits,
'img_media_type' => $this->media_type,
'img_major_mime' => $major,
'img_minor_mime' => $minor,
- 'img_metadata' => $this->metadata,
- 'img_sha1' => $this->sha1,
- ), array( 'img_name' => $this->getName() ),
+ 'img_metadata' => $this->metadata,
+ 'img_sha1' => $this->sha1,
+ ),
+ array( 'img_name' => $this->getName() ),
__METHOD__
);
$this->saveToCache();
+
+ $this->unlock(); // done
+
wfProfileOut( __METHOD__ );
}
@@ -456,7 +464,7 @@ class LocalFile extends File {
function isMissing() {
if ( $this->missing === null ) {
- list( $fileExists ) = $this->repo->fileExistsBatch( array( $this->getVirtualUrl() ), FileRepo::FILES_ONLY );
+ list( $fileExists ) = $this->repo->fileExists( $this->getVirtualUrl(), FileRepo::FILES_ONLY );
$this->missing = !$fileExists;
}
return $this->missing;
@@ -581,8 +589,9 @@ class LocalFile extends File {
*/
function migrateThumbFile( $thumbName ) {
$thumbDir = $this->getThumbPath();
- $thumbPath = "$thumbDir/$thumbName";
+ /* Old code for bug 2532
+ $thumbPath = "$thumbDir/$thumbName";
if ( is_dir( $thumbPath ) ) {
// Directory where file should be
// This happened occasionally due to broken migration code in 1.5
@@ -597,12 +606,11 @@ class LocalFile extends File {
// Doesn't exist anymore
clearstatcache();
}
+ */
- if ( is_file( $thumbDir ) ) {
- // File where directory should be
- unlink( $thumbDir );
- // Doesn't exist anymore
- clearstatcache();
+ if ( $this->repo->fileExists( $thumbDir, FileRepo::FILES_ONLY ) ) {
+ // Delete file where directory should be
+ $this->repo->cleanupBatch( array( $thumbDir ) );
}
}
@@ -623,21 +631,12 @@ class LocalFile extends File {
} else {
$dir = $this->getThumbPath();
}
- $files = array();
- $files[] = $dir;
-
- if ( is_dir( $dir ) ) {
- $handle = opendir( $dir );
- if ( $handle ) {
- while ( false !== ( $file = readdir( $handle ) ) ) {
- if ( $file { 0 } != '.' ) {
- $files[] = $file;
- }
- }
-
- closedir( $handle );
- }
+ $backend = $this->repo->getBackend();
+ $files = array( $dir );
+ $iterator = $backend->getFileList( array( 'dir' => $dir ) );
+ foreach ( $iterator as $file ) {
+ $files[] = $file;
}
return $files;
@@ -674,36 +673,30 @@ class LocalFile extends File {
/**
* Delete all previously generated thumbnails, refresh metadata in memcached and purge the squid
*/
- function purgeCache() {
+ function purgeCache( $options = array() ) {
// Refresh metadata cache
$this->purgeMetadataCache();
// Delete thumbnails
- $this->purgeThumbnails();
+ $this->purgeThumbnails( $options );
// Purge squid cache for this file
SquidUpdate::purge( array( $this->getURL() ) );
}
/**
- * Delete cached transformed files for archived files
+ * Delete cached transformed files for an archived version only.
* @param $archiveName string name of the archived file
*/
function purgeOldThumbnails( $archiveName ) {
global $wgUseSquid;
- // get a list of old thumbnails and URLs
+ // Get a list of old thumbnails and URLs
$files = $this->getThumbnails( $archiveName );
$dir = array_shift( $files );
$this->purgeThumbList( $dir, $files );
- // 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();
+ // Purge any custom thumbnail caches
+ wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, $archiveName ) );
// Purge the squid
if ( $wgUseSquid ) {
@@ -715,17 +708,29 @@ class LocalFile extends File {
}
}
-
/**
* Delete cached transformed files for the current version only.
*/
- function purgeThumbnails() {
+ function purgeThumbnails( $options = array() ) {
global $wgUseSquid;
- // get a list of thumbnails and URLs
+
+ // Delete thumbnails
$files = $this->getThumbnails();
+
+ // Give media handler a chance to filter the purge list
+ if ( !empty( $options['forThumbRefresh'] ) ) {
+ $handler = $this->getHandler();
+ if ( $handler ) {
+ $handler->filterThumbnailPurgeList( $files, $options );
+ }
+ }
+
$dir = array_shift( $files );
$this->purgeThumbList( $dir, $files );
+ // Purge any custom thumbnail caches
+ wfRunHooks( 'LocalFilePurgeThumbnails', array( $this, false ) );
+
// Purge the squid
if ( $wgUseSquid ) {
$urls = array();
@@ -741,19 +746,26 @@ class LocalFile extends File {
* @param $dir string base dir of the files.
* @param $files array of strings: relative filenames (to $dir)
*/
- function purgeThumbList($dir, $files) {
- global $wgExcludeFromThumbnailPurge;
+ protected function purgeThumbList( $dir, $files ) {
+ $fileListDebug = strtr(
+ var_export( $files, true ),
+ array("\n"=>'')
+ );
+ wfDebug( __METHOD__ . ": $fileListDebug\n" );
- wfDebug( __METHOD__ . ": " . var_export( $files, true ) . "\n" );
+ $purgeList = array();
foreach ( $files as $file ) {
# Check that the base file name is part of the thumb name
# This is a basic sanity check to avoid erasing unrelated directories
if ( strpos( $file, $this->getName() ) !== false ) {
- wfSuppressWarnings();
- unlink( "$dir/$file" );
- wfRestoreWarnings();
+ $purgeList[] = "{$dir}/{$file}";
}
}
+
+ # Delete the thumbnails
+ $this->repo->cleanupBatch( $purgeList, FileRepo::SKIP_LOCKING );
+ # Clear out the thumbnail directory if empty
+ $this->repo->getBackend()->clean( array( 'dir' => $dir ) );
}
/** purgeDescription inherited */
@@ -858,7 +870,6 @@ class LocalFile extends File {
}
}
- /** getFullPath inherited */
/** getHashPath inherited */
/** getRel inherited */
/** getUrlRel inherited */
@@ -873,7 +884,7 @@ class LocalFile extends File {
/**
* Upload a file and record it in the DB
- * @param $srcPath String: source path or virtual URL
+ * @param $srcPath String: source storage path or virtual URL
* @param $comment String: upload description
* @param $pageText String: text to use for the new description page,
* if a new description page is created
@@ -888,16 +899,24 @@ class LocalFile extends File {
* archive name, or an empty string if it was a new file.
*/
function upload( $srcPath, $comment, $pageText, $flags = 0, $props = false, $timestamp = false, $user = null ) {
- $this->lock();
+ global $wgContLang;
+ // truncate nicely or the DB will do it for us
+ // non-nicely (dangling multi-byte chars, non-truncated
+ // version in cache).
+ $comment = $wgContLang->truncate( $comment, 255 );
+ $this->lock(); // begin
$status = $this->publish( $srcPath, $flags );
- if ( $status->ok ) {
+ if ( $status->successCount > 0 ) {
+ # Essentially we are displacing any existing current file and saving
+ # a new current file at the old location. If just the first succeeded,
+ # we still need to displace the current DB entry and put in a new one.
if ( !$this->recordUpload2( $status->value, $comment, $pageText, $props, $timestamp, $user ) ) {
$status->fatal( 'filenotfound', $srcPath );
}
}
- $this->unlock();
+ $this->unlock(); // done
return $status;
}
@@ -968,82 +987,94 @@ class LocalFile extends File {
# doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition.
$dbw->insert( 'image',
array(
- 'img_name' => $this->getName(),
- 'img_size' => $this->size,
- 'img_width' => intval( $this->width ),
- 'img_height' => intval( $this->height ),
- 'img_bits' => $this->bits,
- 'img_media_type' => $this->media_type,
- 'img_major_mime' => $this->major_mime,
- 'img_minor_mime' => $this->minor_mime,
- 'img_timestamp' => $timestamp,
+ 'img_name' => $this->getName(),
+ 'img_size' => $this->size,
+ 'img_width' => intval( $this->width ),
+ 'img_height' => intval( $this->height ),
+ 'img_bits' => $this->bits,
+ 'img_media_type' => $this->media_type,
+ 'img_major_mime' => $this->major_mime,
+ 'img_minor_mime' => $this->minor_mime,
+ 'img_timestamp' => $timestamp,
'img_description' => $comment,
- 'img_user' => $user->getId(),
- 'img_user_text' => $user->getName(),
- 'img_metadata' => $this->metadata,
- 'img_sha1' => $this->sha1
+ 'img_user' => $user->getId(),
+ 'img_user_text' => $user->getName(),
+ 'img_metadata' => $this->metadata,
+ 'img_sha1' => $this->sha1
),
__METHOD__,
'IGNORE'
);
if ( $dbw->affectedRows() == 0 ) {
+ if ( $oldver == '' ) { // XXX
+ # (bug 34993) publish() can displace the current file and yet fail to save
+ # a new one. The next publish attempt will treat the file as a brand new file
+ # and pass an empty $oldver. Allow this bogus value so we can displace the
+ # `image` row to `oldimage`, leaving room for the new current file `image` row.
+ #throw new MWException( "Empty oi_archive_name. Database and storage out of sync?" );
+ }
$reupload = true;
-
# Collision, this is an update of a file
# Insert previous contents into oldimage
$dbw->insertSelect( 'oldimage', 'image',
array(
- 'oi_name' => 'img_name',
+ 'oi_name' => 'img_name',
'oi_archive_name' => $dbw->addQuotes( $oldver ),
- 'oi_size' => 'img_size',
- 'oi_width' => 'img_width',
- 'oi_height' => 'img_height',
- 'oi_bits' => 'img_bits',
- 'oi_timestamp' => 'img_timestamp',
- 'oi_description' => 'img_description',
- 'oi_user' => 'img_user',
- 'oi_user_text' => 'img_user_text',
- 'oi_metadata' => 'img_metadata',
- 'oi_media_type' => 'img_media_type',
- 'oi_major_mime' => 'img_major_mime',
- 'oi_minor_mime' => 'img_minor_mime',
- 'oi_sha1' => 'img_sha1'
- ), array( 'img_name' => $this->getName() ), __METHOD__
+ 'oi_size' => 'img_size',
+ 'oi_width' => 'img_width',
+ 'oi_height' => 'img_height',
+ 'oi_bits' => 'img_bits',
+ 'oi_timestamp' => 'img_timestamp',
+ 'oi_description' => 'img_description',
+ 'oi_user' => 'img_user',
+ 'oi_user_text' => 'img_user_text',
+ 'oi_metadata' => 'img_metadata',
+ 'oi_media_type' => 'img_media_type',
+ 'oi_major_mime' => 'img_major_mime',
+ 'oi_minor_mime' => 'img_minor_mime',
+ 'oi_sha1' => 'img_sha1'
+ ),
+ array( 'img_name' => $this->getName() ),
+ __METHOD__
);
# Update the current image row
$dbw->update( 'image',
array( /* SET */
- 'img_size' => $this->size,
- 'img_width' => intval( $this->width ),
- 'img_height' => intval( $this->height ),
- 'img_bits' => $this->bits,
- 'img_media_type' => $this->media_type,
- 'img_major_mime' => $this->major_mime,
- 'img_minor_mime' => $this->minor_mime,
- 'img_timestamp' => $timestamp,
+ 'img_size' => $this->size,
+ 'img_width' => intval( $this->width ),
+ 'img_height' => intval( $this->height ),
+ 'img_bits' => $this->bits,
+ 'img_media_type' => $this->media_type,
+ 'img_major_mime' => $this->major_mime,
+ 'img_minor_mime' => $this->minor_mime,
+ 'img_timestamp' => $timestamp,
'img_description' => $comment,
- 'img_user' => $user->getId(),
- 'img_user_text' => $user->getName(),
- 'img_metadata' => $this->metadata,
- 'img_sha1' => $this->sha1
- ), array( /* WHERE */
- 'img_name' => $this->getName()
- ), __METHOD__
+ 'img_user' => $user->getId(),
+ 'img_user_text' => $user->getName(),
+ 'img_metadata' => $this->metadata,
+ 'img_sha1' => $this->sha1
+ ),
+ array( 'img_name' => $this->getName() ),
+ __METHOD__
);
} 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();
+ $dbw->begin( __METHOD__ );
+ $dbw->update(
+ 'site_stats',
+ array( 'ss_images = ss_images+1' ),
+ '*',
+ __METHOD__
+ );
+ $dbw->commit( __METHOD__ );
}
$descTitle = $this->getTitle();
- $article = new ImagePage( $descTitle );
- $article->setFile( $this );
+ $wikiPage = new WikiFilePage( $descTitle );
+ $wikiPage->setFile( $this );
# Add the log entry
$log = new LogPage( 'upload' );
@@ -1059,11 +1090,12 @@ class LocalFile extends File {
$log->getRcComment(),
false
);
- $nullRevision->insertOn( $dbw );
-
- wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $user ) );
- $article->updateRevisionOn( $dbw, $nullRevision );
+ if (!is_null($nullRevision)) {
+ $nullRevision->insertOn( $dbw );
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $wikiPage, $nullRevision, $latest, $user ) );
+ $wikiPage->updateRevisionOn( $dbw, $nullRevision );
+ }
# Invalidate the cache for the description page
$descTitle->invalidateCache();
$descTitle->purgeSquid();
@@ -1071,7 +1103,7 @@ class LocalFile extends File {
# New file; create the description page.
# There's already a log entry, so don't make a second RC entry
# Squid and file cache for the description page are purged by doEdit.
- $article->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC );
+ $wikiPage->doEdit( $pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user );
}
# Commit the transaction now, in case something goes wrong later
@@ -1135,7 +1167,7 @@ class LocalFile extends File {
* archive name, or an empty string if it was a new file.
*/
function publishTo( $srcPath, $dstRel, $flags = 0 ) {
- $this->lock();
+ $this->lock(); // begin
$archiveName = wfTimestamp( TS_MW ) . '!'. $this->getName();
$archiveRel = 'archive/' . $this->getHashPath() . $archiveName;
@@ -1148,7 +1180,7 @@ class LocalFile extends File {
$status->value = $archiveName;
}
- $this->unlock();
+ $this->unlock(); // done
return $status;
}
@@ -1172,7 +1204,7 @@ class LocalFile extends File {
*/
function move( $target ) {
wfDebugLog( 'imagemove', "Got request to move {$this->name} to " . $target->getText() );
- $this->lock();
+ $this->lock(); // begin
$batch = new LocalFileMoveBatch( $this, $target );
$batch->addCurrent();
@@ -1182,7 +1214,7 @@ class LocalFile extends File {
wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
$this->purgeEverything();
- $this->unlock();
+ $this->unlock(); // done
if ( $status->isOk() ) {
// Now switch the object
@@ -1210,7 +1242,7 @@ class LocalFile extends File {
* @return FileRepoStatus object.
*/
function delete( $reason, $suppress = false ) {
- $this->lock();
+ $this->lock(); // begin
$batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$batch->addCurrent();
@@ -1233,7 +1265,7 @@ class LocalFile extends File {
$this->purgeEverything();
}
- $this->unlock();
+ $this->unlock(); // done
return $status;
}
@@ -1253,14 +1285,14 @@ class LocalFile extends File {
* @return FileRepoStatus object.
*/
function deleteOld( $archiveName, $reason, $suppress = false ) {
- $this->lock();
+ $this->lock(); // begin
$batch = new LocalFileDeleteBatch( $this, $reason, $suppress );
$batch->addOld( $archiveName );
$this->purgeOldThumbnails( $archiveName );
$status = $batch->execute();
- $this->unlock();
+ $this->unlock(); // done
if ( $status->ok ) {
$this->purgeDescription();
@@ -1345,7 +1377,9 @@ class LocalFile extends File {
$this->load();
// Initialise now if necessary
if ( $this->sha1 == '' && $this->fileExists ) {
- $this->sha1 = File::sha1Base36( $this->getPath() );
+ $this->lock(); // begin
+
+ $this->sha1 = $this->repo->getFileSha1( $this->getPath() );
if ( !wfReadOnly() && strval( $this->sha1 ) != '' ) {
$dbw = $this->repo->getMasterDB();
$dbw->update( 'image',
@@ -1354,6 +1388,8 @@ class LocalFile extends File {
__METHOD__ );
$this->saveToCache();
}
+
+ $this->unlock(); // done
}
return $this->sha1;
@@ -1403,7 +1439,7 @@ class LocalFile extends File {
/**
* Helper class for file deletion
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
class LocalFileDeleteBatch {
@@ -1704,7 +1740,7 @@ class LocalFileDeleteBatch {
$files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
}
- $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
+ $result = $this->file->repo->fileExistsBatch( $files, FileRepo::FILES_ONLY );
foreach ( $batch as $batchItem ) {
if ( $result[$batchItem[0]] ) {
@@ -1720,7 +1756,7 @@ class LocalFileDeleteBatch {
/**
* Helper class for file undeletion
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
class LocalFileRestoreBatch {
/**
@@ -1992,7 +2028,7 @@ class LocalFileRestoreBatch {
foreach ( $triplets as $file )
$files[$file[0]] = $file[0];
- $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
+ $result = $this->file->repo->fileExistsBatch( $files, FileRepo::FILES_ONLY );
foreach ( $triplets as $file ) {
if ( $result[$file[0]] ) {
@@ -2015,7 +2051,7 @@ class LocalFileRestoreBatch {
rawurlencode( $repo->getDeletedHashPath( $file ) . $file );
}
- $result = $repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
+ $result = $repo->fileExistsBatch( $files, FileRepo::FILES_ONLY );
foreach ( $batch as $file ) {
if ( $result[$file] ) {
@@ -2068,10 +2104,21 @@ class LocalFileRestoreBatch {
/**
* Helper class for file movement
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
class LocalFileMoveBatch {
- var $file, $cur, $olds, $oldCount, $archive, $target, $db;
+
+ /**
+ * @var File
+ */
+ var $file;
+
+ /**
+ * @var Title
+ */
+ var $target;
+
+ var $cur, $olds, $oldCount, $archive, $db;
function __construct( File $file, Title $target ) {
$this->file = $file;
@@ -2148,7 +2195,7 @@ class LocalFileMoveBatch {
// 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" );
+ wfDebugLog( 'imagemove', "Moved files for {$this->file->getName()}: {$statusMove->successCount} successes, {$statusMove->failCount} failures" );
if ( !$statusMove->isGood() ) {
wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
$this->cleanupTarget( $triplets );
@@ -2158,7 +2205,7 @@ class LocalFileMoveBatch {
$this->db->begin();
$statusDb = $this->doDBUpdates();
- wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
+ wfDebugLog( 'imagemove', "Renamed {$this->file->getName()} 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
@@ -2227,7 +2274,7 @@ class LocalFileMoveBatch {
}
/**
- * Generate triplets for FSRepo::storeBatch().
+ * Generate triplets for FileRepo::storeBatch().
*/
function getMoveTriplets() {
$moves = array_merge( array( $this->cur ), $this->olds );
@@ -2237,7 +2284,7 @@ class LocalFileMoveBatch {
// $move: (oldRelativePath, newRelativePath)
$srcUrl = $this->file->repo->getVirtualUrl() . '/public/' . rawurlencode( $move[0] );
$triplets[] = array( $srcUrl, 'public', $move[1] );
- wfDebugLog( 'imagemove', "Generated move triplet for {$this->file->name}: {$srcUrl} :: public :: {$move[1]}" );
+ wfDebugLog( 'imagemove', "Generated move triplet for {$this->file->getName()}: {$srcUrl} :: public :: {$move[1]}" );
}
return $triplets;
@@ -2253,7 +2300,7 @@ class LocalFileMoveBatch {
$files[$file[0]] = $file[0];
}
- $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
+ $result = $this->file->repo->fileExistsBatch( $files, FileRepo::FILES_ONLY );
$filteredTriplets = array();
foreach ( $triplets as $file ) {
diff --git a/includes/filerepo/OldLocalFile.php b/includes/filerepo/file/OldLocalFile.php
index bcb22c17..ebd83c4d 100644
--- a/includes/filerepo/OldLocalFile.php
+++ b/includes/filerepo/file/OldLocalFile.php
@@ -3,13 +3,13 @@
* Old file in the oldimage table
*
* @file
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
/**
* Class to represent a file in the oldimage table
*
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
class OldLocalFile extends LocalFile {
var $requestedTime, $archive_name;
@@ -212,11 +212,12 @@ class OldLocalFile extends LocalFile {
* field of this image file, if it's marked as deleted.
*
* @param $field Integer
+ * @param $user User object to check, or null to use $wgUser
* @return bool
*/
- function userCan( $field ) {
+ function userCan( $field, User $user = null ) {
$this->load();
- return Revision::userCanBitfield( $this->deleted, $field );
+ return Revision::userCanBitfield( $this->deleted, $field, $user );
}
/**
@@ -261,7 +262,7 @@ class OldLocalFile extends LocalFile {
$dbw->begin();
$dstPath = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
- $props = self::getPropsFromPath( $dstPath );
+ $props = $this->repo->getFileProps( $dstPath );
if ( !$props['fileExists'] ) {
return false;
}
diff --git a/includes/filerepo/UnregisteredLocalFile.php b/includes/filerepo/file/UnregisteredLocalFile.php
index 2df9a9b5..cd9d3d02 100644
--- a/includes/filerepo/UnregisteredLocalFile.php
+++ b/includes/filerepo/file/UnregisteredLocalFile.php
@@ -3,12 +3,12 @@
* File without associated database record
*
* @file
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
/**
* A file object referring to either a standalone local file, or a file in a
- * local repository with no database, for example an FSRepo repository.
+ * local repository with no database, for example an FileRepo repository.
*
* Read-only.
*
@@ -16,7 +16,7 @@
* lots of functions missing. It is used by the WebStore extension in the
* standalone role.
*
- * @ingroup FileRepo
+ * @ingroup FileAbstraction
*/
class UnregisteredLocalFile extends File {
var $title, $path, $mime, $dims;
@@ -27,12 +27,12 @@ class UnregisteredLocalFile extends File {
var $handler;
/**
- * @param $path
- * @param $mime
+ * @param $path string Storage path
+ * @param $mime string
* @return UnregisteredLocalFile
*/
static function newFromPath( $path, $mime ) {
- return new UnregisteredLocalFile( false, false, $path, $mime );
+ return new self( false, false, $path, $mime );
}
/**
@@ -41,13 +41,16 @@ class UnregisteredLocalFile extends File {
* @return UnregisteredLocalFile
*/
static function newFromTitle( $title, $repo ) {
- return new UnregisteredLocalFile( $title, $repo, false, false );
+ return new self( $title, $repo, false, false );
}
/**
+ * Create an UnregisteredLocalFile based on a path or a (title,repo) pair.
+ * A FileRepo object is not required here, unlike most other File classes.
+ *
* @throws MWException
- * @param $title string
- * @param $repo FSRepo
+ * @param $title Title|false
+ * @param $repo FileRepo
* @param $path string
* @param $mime string
*/
@@ -55,18 +58,20 @@ class UnregisteredLocalFile extends File {
if ( !( $title && $repo ) && !$path ) {
throw new MWException( __METHOD__.': not enough parameters, must specify title and repo, or a full path' );
}
- if ( $title ) {
- $this->title = $title;
+ if ( $title instanceof Title ) {
+ $this->title = File::normalizeTitle( $title, 'exception' );
$this->name = $repo->getNameFromTitle( $title );
} else {
$this->name = basename( $path );
- $this->title = Title::makeTitleSafe( NS_FILE, $this->name );
+ $this->title = File::normalizeTitle( $this->name, 'exception' );
}
$this->repo = $repo;
if ( $path ) {
$this->path = $path;
} else {
- $this->path = $repo->getRootDirectory() . '/' . $repo->getHashPath( $this->name ) . $this->name;
+ $this->assertRepoDefined();
+ $this->path = $repo->getRootDirectory() . '/' .
+ $repo->getHashPath( $this->name ) . $this->name;
}
if ( $mime ) {
$this->mime = $mime;
@@ -74,7 +79,7 @@ class UnregisteredLocalFile extends File {
$this->dims = array();
}
- function getPageDimensions( $page = 1 ) {
+ private function cachePageDimensions( $page = 1 ) {
if ( !isset( $this->dims[$page] ) ) {
if ( !$this->getHandler() ) {
return false;
@@ -85,19 +90,19 @@ class UnregisteredLocalFile extends File {
}
function getWidth( $page = 1 ) {
- $dim = $this->getPageDimensions( $page );
+ $dim = $this->cachePageDimensions( $page );
return $dim['width'];
}
function getHeight( $page = 1 ) {
- $dim = $this->getPageDimensions( $page );
+ $dim = $this->cachePageDimensions( $page );
return $dim['height'];
}
function getMimeType() {
if ( !isset( $this->mime ) ) {
$magic = MimeMagic::singleton();
- $this->mime = $magic->guessMimeType( $this->path );
+ $this->mime = $magic->guessMimeType( $this->getLocalRefPath() );
}
return $this->mime;
}
@@ -106,7 +111,7 @@ class UnregisteredLocalFile extends File {
if ( !$this->getHandler() ) {
return false;
}
- return $this->handler->getImageSize( $this, $this->getPath() );
+ return $this->handler->getImageSize( $this, $this->getLocalRefPath() );
}
function getMetadata() {
@@ -114,7 +119,7 @@ class UnregisteredLocalFile extends File {
if ( !$this->getHandler() ) {
$this->metadata = false;
} else {
- $this->metadata = $this->handler->getMetadata( $this, $this->getPath() );
+ $this->metadata = $this->handler->getMetadata( $this, $this->getLocalRefPath() );
}
}
return $this->metadata;
@@ -122,17 +127,19 @@ class UnregisteredLocalFile extends File {
function getURL() {
if ( $this->repo ) {
- return $this->repo->getZoneUrl( 'public' ) . '/' . $this->repo->getHashPath( $this->name ) . rawurlencode( $this->name );
+ return $this->repo->getZoneUrl( 'public' ) . '/' .
+ $this->repo->getHashPath( $this->name ) . rawurlencode( $this->name );
} else {
return false;
}
}
function getSize() {
- if ( file_exists( $this->path ) ) {
- return filesize( $this->path );
- } else {
- return false;
+ $this->assertRepoDefined();
+ $props = $this->repo->getFileProps( $this->path );
+ if ( isset( $props['size'] ) ) {
+ return $props['size'];
}
+ return false; // doesn't exist
}
}
diff --git a/includes/installer/CliInstaller.php b/includes/installer/CliInstaller.php
index 41392207..f9afbb20 100644
--- a/includes/installer/CliInstaller.php
+++ b/includes/installer/CliInstaller.php
@@ -24,11 +24,7 @@ class CliInstaller extends Installer {
'dbprefix' => 'wgDBprefix',
'dbtableoptions' => 'wgDBTableOptions',
'dbmysql5' => 'wgDBmysql5',
- 'dbserver' => 'wgDBserver',
'dbport' => 'wgDBport',
- 'dbname' => 'wgDBname',
- 'dbuser' => 'wgDBuser',
- 'dbpass' => 'wgDBpassword',
'dbschema' => 'wgDBmwschema',
'dbpath' => 'wgSQLiteDataDir',
'server' => 'wgServer',
@@ -88,6 +84,9 @@ class CliInstaller extends Installer {
$option['installdbuser'] );
$this->setVar( '_InstallPassword',
isset( $option['installdbpass'] ) ? $option['installdbpass'] : "" );
+
+ // Assume that if we're given the installer user, we'll create the account.
+ $this->setVar( '_CreateDBAccount', true );
}
if ( isset( $option['pass'] ) ) {
@@ -184,11 +183,8 @@ class CliInstaller extends Installer {
return parent::envCheckPath();
}
- protected function envCheckServer( $srv = null ) {
- if ( $this->getVar( 'wgServer' ) ) {
- $srv = $this->getVar( 'wgServer' );
- }
- return parent::envCheckServer( $srv );
+ protected function envGetDefaultServer() {
+ return $this->getVar( 'wgServer' );
}
public function dirIsExecutable( $dir, $url ) {
diff --git a/includes/installer/DatabaseInstaller.php b/includes/installer/DatabaseInstaller.php
index aadd6b49..ab77e2d3 100644
--- a/includes/installer/DatabaseInstaller.php
+++ b/includes/installer/DatabaseInstaller.php
@@ -55,6 +55,15 @@ abstract class DatabaseInstaller {
public abstract function isCompiled();
/**
+ * Checks for installation prerequisites other than those checked by isCompiled()
+ * @since 1.19
+ * @return Status
+ */
+ public function checkPrerequisites() {
+ return Status::newGood();
+ }
+
+ /**
* Get HTML for a web form that configures this database. Configuration
* at this time should be the minimum needed to connect and test
* whether install or upgrade is required.
@@ -148,7 +157,7 @@ abstract class DatabaseInstaller {
}
$this->db->selectDB( $this->getVar( 'wgDBname' ) );
- if( $this->db->tableExists( 'user' ) ) {
+ if( $this->db->tableExists( 'archive', __METHOD__ ) ) {
$status->warning( 'config-install-tables-exist' );
$this->enableLB();
return $status;
@@ -281,6 +290,7 @@ abstract class DatabaseInstaller {
/**
* Construct and initialise parent.
* This is typically only called from Installer::getDBInstaller()
+ * @param $parent
*/
public function __construct( $parent ) {
$this->parent = $parent;
@@ -291,6 +301,8 @@ abstract class DatabaseInstaller {
* Check if a named extension is present.
*
* @see wfDl
+ * @param $name
+ * @return bool
*/
protected static function checkExtension( $name ) {
wfSuppressWarnings();
@@ -323,6 +335,9 @@ abstract class DatabaseInstaller {
/**
* Get a variable, taking local defaults into account.
+ * @param $var string
+ * @param $default null
+ * @return mixed
*/
public function getVar( $var, $default = null ) {
$defaults = $this->getGlobalDefaults();
@@ -337,6 +352,8 @@ abstract class DatabaseInstaller {
/**
* Convenience alias for $this->parent->setVar()
+ * @param $name string
+ * @param $value mixed
*/
public function setVar( $name, $value ) {
$this->parent->setVar( $name, $value );
@@ -345,6 +362,10 @@ abstract class DatabaseInstaller {
/**
* Get a labelled text box to configure a local variable.
*
+ * @param $var string
+ * @param $label string
+ * @param $attribs array
+ * @param $helpData string
* @return string
*/
public function getTextBox( $var, $label, $attribs = array(), $helpData = "" ) {
@@ -367,6 +388,10 @@ abstract class DatabaseInstaller {
* Get a labelled password box to configure a local variable.
* Implements password hiding.
*
+ * @param $var string
+ * @param $label string
+ * @param $attribs array
+ * @param $helpData string
* @return string
*/
public function getPasswordBox( $var, $label, $attribs = array(), $helpData = "" ) {
@@ -450,7 +475,7 @@ abstract class DatabaseInstaller {
if ( !$this->db->selectDB( $this->getVar( 'wgDBname' ) ) ) {
return false;
}
- return $this->db->tableExists( 'cur' ) || $this->db->tableExists( 'revision' );
+ return $this->db->tableExists( 'cur', __METHOD__ ) || $this->db->tableExists( 'revision', __METHOD__ );
}
/**
@@ -462,8 +487,8 @@ abstract class DatabaseInstaller {
return
Html::openElement( 'fieldset' ) .
Html::element( 'legend', array(), wfMsg( 'config-db-install-account' ) ) .
- $this->getTextBox( '_InstallUser', 'config-db-username', array(), $this->parent->getHelpBox( 'config-db-install-username' ) ) .
- $this->getPasswordBox( '_InstallPassword', 'config-db-password', array(), $this->parent->getHelpBox( 'config-db-install-password' ) ) .
+ $this->getTextBox( '_InstallUser', 'config-db-username', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-install-username' ) ) .
+ $this->getPasswordBox( '_InstallPassword', 'config-db-password', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-install-password' ) ) .
Html::closeElement( 'fieldset' );
}
diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php
index 89fb16eb..f2e36aec 100644
--- a/includes/installer/DatabaseUpdater.php
+++ b/includes/installer/DatabaseUpdater.php
@@ -8,7 +8,7 @@
require_once( dirname(__FILE__) . '/../../maintenance/Maintenance.php' );
-/*
+/**
* Class for handling database updates. Roughly based off of updaters.inc, with
* a few improvements :)
*
@@ -41,6 +41,9 @@ abstract class DatabaseUpdater {
protected $postDatabaseUpdateMaintenance = array(
'DeleteDefaultMessages',
+ 'PopulateRevisionLength',
+ 'PopulateRevisionSha1',
+ 'PopulateImageSha1',
'FixExtLinksProtocolRelative',
);
@@ -150,6 +153,8 @@ abstract class DatabaseUpdater {
* Add a new update coming from an extension. This should be called by
* extensions while executing the LoadExtensionSchemaUpdates hook.
*
+ * @since 1.17
+ *
* @param $update Array: the update to run. Format is the following:
* first item is the callback function, it also can be a
* simple string with the name of a function in this class,
@@ -164,6 +169,9 @@ abstract class DatabaseUpdater {
/**
* Convenience wrapper for addExtensionUpdate() when adding a new table (which
* is the most common usage of updaters in an extension)
+ *
+ * @since 1.18
+ *
* @param $tableName String Name of table to create
* @param $sqlPath String Full path to the schema file
*/
@@ -172,6 +180,40 @@ abstract class DatabaseUpdater {
}
/**
+ * @since 1.19
+ *
+ * @param $tableName string
+ * @param $indexName string
+ * @param $sqlPath string
+ */
+ public function addExtensionIndex( $tableName, $indexName, $sqlPath ) {
+ $this->extensionUpdates[] = array( 'addIndex', $tableName, $indexName, $sqlPath, true );
+ }
+
+ /**
+ *
+ * @since 1.19
+ *
+ * @param $tableName string
+ * @param $columnName string
+ * @param $sqlPath string
+ */
+ public function addExtensionField( $tableName, $columnName, $sqlPath ) {
+ $this->extensionUpdates[] = array( 'addField', $tableName, $columnName, $sqlPath, true );
+ }
+
+ /**
+ * Add a maintenance script to be run after the database updates are complete.
+ *
+ * @since 1.19
+ *
+ * @param $class string Name of a Maintenance subclass
+ */
+ public function addPostDatabaseUpdateMaintenance( $class ) {
+ $this->postDatabaseUpdateMaintenance[] = $class;
+ }
+
+ /**
* Get the list of extension-defined updates
*
* @return Array
@@ -180,6 +222,11 @@ abstract class DatabaseUpdater {
return $this->extensionUpdates;
}
+ /**
+ * @since 1.17
+ *
+ * @return array
+ */
public function getPostDatabaseUpdateMaintenance() {
return $this->postDatabaseUpdateMaintenance;
}
@@ -203,6 +250,10 @@ abstract class DatabaseUpdater {
$this->setAppliedUpdates( $wgVersion, $this->updates );
+ if ( isset( $what['stats'] ) ) {
+ $this->checkStats();
+ }
+
if ( isset( $what['purge'] ) ) {
$this->purgeCache();
@@ -210,9 +261,6 @@ abstract class DatabaseUpdater {
$this->rebuildLocalisationCache();
}
}
- if ( isset( $what['stats'] ) ) {
- $this->checkStats();
- }
}
/**
@@ -236,6 +284,10 @@ abstract class DatabaseUpdater {
$this->updates = array_merge( $this->updates, $updates );
}
+ /**
+ * @param $version
+ * @param $updates array
+ */
protected function setAppliedUpdates( $version, $updates = array() ) {
$this->db->clearFlag( DBO_DDLMODE );
if( !$this->canUseNewUpdatelog() ) {
@@ -253,6 +305,8 @@ abstract class DatabaseUpdater {
* Obviously, only use this for updates that occur after the updatelog table was
* created!
* @param $key String Name of the key to check for
+ *
+ * @return bool
*/
public function updateRowExists( $key ) {
$row = $this->db->selectRow(
@@ -290,8 +344,8 @@ abstract class DatabaseUpdater {
* @return boolean
*/
protected function canUseNewUpdatelog() {
- return $this->db->tableExists( 'updatelog' ) &&
- $this->db->fieldExists( 'updatelog', 'ul_value' );
+ return $this->db->tableExists( 'updatelog', __METHOD__ ) &&
+ $this->db->fieldExists( 'updatelog', 'ul_value', __METHOD__ );
}
/**
@@ -299,6 +353,8 @@ abstract class DatabaseUpdater {
* $wgExtNewTables/Fields/Indexes. This is nasty :) We refactored a lot
* of this in 1.17 but we want to remain back-compatible for a while. So
* load up these old global-based things into our update list.
+ *
+ * @return array
*/
protected function getOldGlobalUpdates() {
global $wgExtNewFields, $wgExtNewTables, $wgExtModifiedFields,
@@ -372,12 +428,12 @@ abstract class DatabaseUpdater {
* @param $fullpath Boolean Whether to treat $patch path as a relative or not
*/
protected function addTable( $name, $patch, $fullpath = false ) {
- if ( $this->db->tableExists( $name ) ) {
+ if ( $this->db->tableExists( $name, __METHOD__ ) ) {
$this->output( "...$name table already exists.\n" );
} else {
$this->output( "Creating $name table..." );
$this->applyPatch( $patch, $fullpath );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
}
}
@@ -389,14 +445,14 @@ abstract class DatabaseUpdater {
* @param $fullpath Boolean Whether to treat $patch path as a relative or not
*/
protected function addField( $table, $field, $patch, $fullpath = false ) {
- if ( !$this->db->tableExists( $table ) ) {
- $this->output( "...$table table does not exist, skipping new field patch\n" );
- } elseif ( $this->db->fieldExists( $table, $field ) ) {
+ if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
+ $this->output( "...$table table does not exist, skipping new field patch.\n" );
+ } elseif ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
$this->output( "...have $field field in $table table.\n" );
} else {
$this->output( "Adding $field field to table $table..." );
$this->applyPatch( $patch, $fullpath );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
}
}
@@ -408,12 +464,12 @@ abstract class DatabaseUpdater {
* @param $fullpath Boolean Whether to treat $patch path as a relative or not
*/
protected function addIndex( $table, $index, $patch, $fullpath = false ) {
- if ( $this->db->indexExists( $table, $index ) ) {
- $this->output( "...$index key already set on $table table.\n" );
+ if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
+ $this->output( "...index $index already set on $table table.\n" );
} else {
- $this->output( "Adding $index key to table $table... " );
+ $this->output( "Adding index $index to table $table... " );
$this->applyPatch( $patch, $fullpath );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
}
}
@@ -426,10 +482,10 @@ abstract class DatabaseUpdater {
* @param $fullpath Boolean Whether to treat $patch path as a relative or not
*/
protected function dropField( $table, $field, $patch, $fullpath = false ) {
- if ( $this->db->fieldExists( $table, $field ) ) {
+ if ( $this->db->fieldExists( $table, $field, __METHOD__ ) ) {
$this->output( "Table $table contains $field field. Dropping... " );
$this->applyPatch( $patch, $fullpath );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
} else {
$this->output( "...$table table does not contain $field field.\n" );
}
@@ -444,16 +500,31 @@ abstract class DatabaseUpdater {
* @param $fullpath Boolean: Whether to treat $patch path as a relative or not
*/
protected function dropIndex( $table, $index, $patch, $fullpath = false ) {
- if ( $this->db->indexExists( $table, $index ) ) {
- $this->output( "Dropping $index from table $table... " );
+ if ( $this->db->indexExists( $table, $index, __METHOD__ ) ) {
+ $this->output( "Dropping $index index from table $table... " );
$this->applyPatch( $patch, $fullpath );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
} else {
$this->output( "...$index key doesn't exist.\n" );
}
}
/**
+ * @param $table string
+ * @param $patch string
+ * @param $fullpath bool
+ */
+ protected function dropTable( $table, $patch, $fullpath = false ) {
+ if ( $this->db->tableExists( $table, __METHOD__ ) ) {
+ $this->output( "Dropping table $table... " );
+ $this->applyPatch( $patch, $fullpath );
+ $this->output( "done.\n" );
+ } else {
+ $this->output( "...$table doesn't exist.\n" );
+ }
+ }
+
+ /**
* Modify an existing field
*
* @param $table String: name of the table to which the field belongs
@@ -463,17 +534,17 @@ abstract class DatabaseUpdater {
*/
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" );
+ if ( !$this->db->tableExists( $table, __METHOD__ ) ) {
+ $this->output( "...$table table does not exist, skipping modify field patch.\n" );
+ } elseif ( !$this->db->fieldExists( $table, $field, __METHOD__ ) ) {
+ $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" );
+ $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" );
+ $this->output( "done.\n" );
}
}
@@ -492,7 +563,7 @@ abstract class DatabaseUpdater {
* Check the site_stats table is not properly populated.
*/
protected function checkStats() {
- $this->output( "Checking site_stats row..." );
+ $this->output( "...site_stats is populated..." );
$row = $this->db->selectRow( 'site_stats', '*', array( 'ss_row_id' => 1 ), __METHOD__ );
if ( $row === false ) {
$this->output( "data is missing! rebuilding...\n" );
@@ -507,6 +578,9 @@ abstract class DatabaseUpdater {
# Common updater functions
+ /**
+ * Sets the number of active users in the site_stats table
+ */
protected function doActiveUsersInit() {
$activeUsers = $this->db->selectField( 'site_stats', 'ss_active_users', false, __METHOD__ );
if ( $activeUsers == -1 ) {
@@ -522,36 +596,41 @@ abstract class DatabaseUpdater {
$this->output( "...ss_active_users user count set...\n" );
}
+ /**
+ * Populates the log_user_text field in the logging table
+ */
protected function doLogUsertextPopulation() {
- if ( $this->updateRowExists( 'populate log_usertext' ) ) {
- $this->output( "...log_user_text field already populated.\n" );
- return;
- }
-
- $this->output(
+ if ( !$this->updateRowExists( 'populate log_usertext' ) ) {
+ $this->output(
"Populating log_user_text field, printing progress markers. For large\n" .
"databases, you may want to hit Ctrl-C and do this manually with\n" .
"maintenance/populateLogUsertext.php.\n" );
- $task = $this->maintenance->runChild( 'PopulateLogUsertext' );
- $task->execute();
- $this->output( "Done populating log_user_text field.\n" );
+
+ $task = $this->maintenance->runChild( 'PopulateLogUsertext' );
+ $task->execute();
+ $this->output( "done.\n" );
+ }
}
+ /**
+ * Migrate log params to new table and index for searching
+ */
protected function doLogSearchPopulation() {
- if ( $this->updateRowExists( 'populate log_search' ) ) {
- $this->output( "...log_search table already populated.\n" );
- return;
+ if ( !$this->updateRowExists( 'populate log_search' ) ) {
+ $this->output(
+ "Populating log_search table, printing progress markers. For large\n" .
+ "databases, you may want to hit Ctrl-C and do this manually with\n" .
+ "maintenance/populateLogSearch.php.\n" );
+
+ $task = $this->maintenance->runChild( 'PopulateLogSearch' );
+ $task->execute();
+ $this->output( "done.\n" );
}
-
- $this->output(
- "Populating log_search table, printing progress markers. For large\n" .
- "databases, you may want to hit Ctrl-C and do this manually with\n" .
- "maintenance/populateLogSearch.php.\n" );
- $task = $this->maintenance->runChild( 'PopulateLogSearch' );
- $task->execute();
- $this->output( "Done populating log_search table.\n" );
}
+ /**
+ * Updates the timestamps in the transcache table
+ */
protected function doUpdateTranscacheField() {
if ( $this->updateRowExists( 'convert transcache field' ) ) {
$this->output( "...transcache tc_time already converted.\n" );
@@ -560,9 +639,12 @@ abstract class DatabaseUpdater {
$this->output( "Converting tc_time from UNIX epoch to MediaWiki timestamp... " );
$this->applyPatch( 'patch-tc-timestamp.sql' );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
}
+ /**
+ * Update CategoryLinks collation
+ */
protected function doCollationUpdate() {
global $wgCategoryCollation;
if ( $this->db->selectField(
@@ -575,10 +657,24 @@ abstract class DatabaseUpdater {
return;
}
+ $this->output( "Updating category collations..." );
$task = $this->maintenance->runChild( 'UpdateCollation' );
$task->execute();
+ $this->output( "...done.\n" );
}
+ /**
+ * Migrates user options from the user table blob to user_properties
+ */
+ protected function doMigrateUserOptions() {
+ $cl = $this->maintenance->runChild( 'ConvertUserOptions', 'convertUserOptions.php' );
+ $cl->execute();
+ $this->output( "done.\n" );
+ }
+
+ /**
+ * Rebuilds the localisation cache
+ */
protected function rebuildLocalisationCache() {
/**
* @var $cl RebuildLocalisationCache
@@ -587,6 +683,6 @@ abstract class DatabaseUpdater {
$this->output( "Rebuilding localisation cache...\n" );
$cl->setForce();
$cl->execute();
- $this->output( "Rebuilding localisation cache done.\n" );
+ $this->output( "done.\n" );
}
}
diff --git a/includes/installer/Ibm_db2Installer.php b/includes/installer/Ibm_db2Installer.php
index 78e607fb..a6c4fd65 100644
--- a/includes/installer/Ibm_db2Installer.php
+++ b/includes/installer/Ibm_db2Installer.php
@@ -144,6 +144,9 @@ class Ibm_db2Installer extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
$dbName = $this->getVar( 'wgDBname' );
if( !$conn->selectDB( $dbName ) ) {
@@ -213,15 +216,16 @@ class Ibm_db2Installer extends DatabaseInstaller {
$this->db->selectDB( $this->getVar( 'wgDBname' ) );
try {
- $result = $this->db->query( 'SELECT PAGESIZE FROM SYSCAT.TABLESPACES' );
+ $result = $this->db->query( 'SELECT PAGESIZE FROM SYSCAT.TABLESPACES FOR READ ONLY' );
if( $result == false ) {
$status->fatal( 'config-connection-error', '' );
- }
- else {
- while ( $row = $this->db->fetchRow( $result ) ) {
+ } else {
+ $row = $this->db->fetchRow( $result );
+ while ( $row ) {
if( $row[0] >= 32768 ) {
return $status;
}
+ $row = $this->db->fetchRow( $result );
}
$status->fatal( 'config-ibm_db2-low-db-pagesize', '' );
}
@@ -245,7 +249,7 @@ class Ibm_db2Installer extends DatabaseInstaller {
\$wgDBport = \"{$port}\";";
}
- public function __construct($parent) {
- parent::__construct($parent);
+ public function __construct( $parent ) {
+ parent::__construct( $parent );
}
}
diff --git a/includes/installer/Ibm_db2Updater.php b/includes/installer/Ibm_db2Updater.php
index 39a9fb79..03540bb0 100644
--- a/includes/installer/Ibm_db2Updater.php
+++ b/includes/installer/Ibm_db2Updater.php
@@ -45,25 +45,30 @@ class Ibm_db2Updater extends DatabaseUpdater {
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( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
array( 'addIndex', 'module_deps', 'uq96_module_deps', 'patch-uq_96_module_deps.sql' ),
-
- // Fields
+ array( 'addField', 'interwiki', 'iw_api', 'patch-iw_api-field.sql' ),
+ array( 'addField', 'interwiki', 'iw_wikiid', 'patch-iw_wikiid-field.sql' ),
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' )
+
+ //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'),
+ array( 'doRebuildLocalisationCache' ),
+
+ // 1.19
+ array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
+ array( 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ),
+ array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
+ array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' )
);
}
-} \ No newline at end of file
+}
diff --git a/includes/installer/Installer.i18n.php b/includes/installer/Installer.i18n.php
index e9ede6f9..4f044097 100644
--- a/includes/installer/Installer.i18n.php
+++ b/includes/installer/Installer.i18n.php
@@ -103,6 +103,7 @@ 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-outdated-sqlite' => "'''Warning''': you have SQLite $1, which is lower than minimum required version $2. SQLite will be unavailable.",
'config-no-fts3' => "'''Warning''': SQLite is compiled without the [//sqlite.org/fts3.html FTS3 module], search features will be unavailable on this backend.",
'config-register-globals' => "'''Warning: PHP's <code>[http://php.net/register_globals register_globals]</code> option is enabled.'''
'''Disable it if you can.'''
@@ -132,13 +133,13 @@ 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-ctype' => "'''Fatal''': PHP must be compiled with support for the [http://www.php.net/manual/en/ctype.installation.php Ctype extension].",
'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://xcache.lighttpd.net/ XCache] or [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Warning:''' Could not find [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] or [http://www.iis.net/download/WinCacheForPhp WinCache].
Object caching is not enabled.",
- 'config-mod-security' => "'''Warning''': your web server has [http://modsecurity.org/ mod_security] enabled. If misconfigured, it can cause problems for MediaWiki or other software that allows users to post arbitrary content.
+ '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>.
@@ -217,7 +218,7 @@ In '''UTF-8 mode''', MySQL will know what character set your data is in, and can
but it will not let you store characters above the [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
'config-mysql-old' => 'MySQL $1 or later is required, you have $2.',
'config-db-port' => 'Database port:',
- 'config-db-schema' => 'Schema for MediaWiki',
+ 'config-db-schema' => 'Schema for MediaWiki:',
'config-db-schema-help' => 'This schema will usually be fine.
Only change it if you know you need to.',
'config-pg-test-error' => "Cannot connect to database '''$1''': $2",
@@ -462,7 +463,7 @@ If you do not want a logo, leave this box blank.",
'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].',
+For more information on this feature, including instructions on how to set it up for wikis other than the Wikimedia Commons, consult [//mediawiki.org/wiki/Manual:$wgForeignFileRepos the manual].',
'config-cc-error' => 'The Creative Commons license chooser gave no result.
Enter the license name manually.',
'config-cc-again' => 'Pick again...',
@@ -472,7 +473,7 @@ Enter the license name manually.',
'config-cache-help' => 'Object caching is used to improve the speed of MediaWiki by caching frequently used data.
Medium to large sites are highly encouraged to enable this, and small sites will see benefits as well.',
'config-cache-none' => 'No caching (no functionality is removed, but speed may be impacted on larger wiki sites)',
- 'config-cache-accel' => 'PHP object caching (APC, eAccelerator, XCache or WinCache)',
+ 'config-cache-accel' => 'PHP object caching (APC, XCache or WinCache)',
'config-cache-memcached' => 'Use Memcached (requires additional setup and configuration)',
'config-memcached-servers' => 'Memcached servers:',
'config-memcached-help' => 'List of IP addresses to use for Memcached.
@@ -564,6 +565,7 @@ When that has been done, you can '''[$2 enter your wiki]'''.",
* @author EugeneZelenko
* @author Kghbln
* @author McDutchie
+ * @author Mormegil
* @author Nike
* @author Platonides
* @author Purodha
@@ -604,20 +606,24 @@ Parameters:
* $2 is the value to which <code>memory_limit</code> was raised.',
'config-memory-bad' => 'Parameters:
* $1 is the configured <code>memory_limit</code>.',
+ 'config-ctype' => 'Message if support for [http://www.php.net/manual/en/ctype.installation.php Ctype] is missing from PHP',
'config-xcache' => 'Message indicates if this program is available',
'config-apc' => 'Message indicates if this program is available',
- 'config-eaccel' => 'Message indicates if this program is available',
'config-wincache' => 'Message indicates if this program is available',
'config-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-no-cli-uploads-check' => 'CLI = [[w:Command-line interface|command-line interface]] (i.e. the installer runs as a command-line script, not using HTML interface via an internet browser)',
+ 'config-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\"",
+ 'config-type-mysql' => '{{optional}}',
+ 'config-type-postgres' => '{{optional}}',
+ 'config-type-sqlite' => '{{optional}}',
+ 'config-type-oracle' => '{{optional}}',
'config-support-mysql' => 'Parameters:
* $1 - a link to the MySQL home page having the anchor text "MySQL".',
'config-support-postgres' => 'Parameters:
@@ -630,9 +636,7 @@ Add dir="ltr" to the <nowiki><code></nowiki> for right-to-left languages.',
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" ?
-
-Parameters:
+ 'config-can-upgrade' => 'Parameters:
* $1 - Version or Revision indicator.',
'config-show-table-status' => '{{doc-important|"SHOW TABLE STATUS" is a MySQL command. Do not translate this.}}',
'config-ns-generic' => '{{Identical|Project}}',
@@ -677,6 +681,13 @@ This message refers to a block of HTML being embedded into the installer page. I
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.',
);
+/** Goan Konkani (Latin script) (Konknni)
+ * @author The Discoverer
+ */
+$messages['gom-latn'] = array(
+ 'config-page-language' => 'Bhas',
+);
+
/** Magyar (magázó) (Magyar (magázó))
* @author Dani
* @author Glanthor Reviol
@@ -737,7 +748,7 @@ Adjon meg egy másik felhasználónevet.',
'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.',
+A funkcióról és hogy hogyan állítható be más wikik esetén [//mediawiki.org/wiki/Manual:$wgForeignFileRepos a kézikönyvben] találhat további információkat.',
'config-install-done' => "'''Gratulálunk!'''
Sikeresen telepítette a MediaWikit.
@@ -1309,6 +1320,7 @@ You should have received <doclink href=Copying>a copy of the GNU General Public
Калі вы выкарыÑтоўваеце агульны хоÑтынг, запытайцеÑÑ Ñž Ñвайго хоÑтынг-правайдÑра наконт уÑталÑÐ²Ð°Ð½ÑŒÐ½Ñ Ð¿Ð°Ñ‚Ñ€Ð°Ð±ÑƒÐµÐ¼Ð°Ð³Ð° драйвÑру базы зьвеÑтак.
Калі Ð’Ñ‹ кампілÑвалі PHP ÑамаÑтойна, пераканфігуруйце Ñ– ÑабÑрыце Ñго з дазволеным кліентам базаў зьвеÑтак, напрыклад, <code>./configure --with-mysql</code>.
Калі Ð’Ñ‹ ÑžÑталёўвалі PHP з пакетаў Debian ці Ubuntu, то Вам Ñ‚Ñ€Ñба ÑžÑталÑваць дадаткова модуль <code>php5-mysql</code>.',
+ 'config-outdated-sqlite' => "'''ПапÑÑ€Ñджаньне''': уÑталÑваны SQLite $1, у той чаÑ, калі Ð¼Ñ–Ð½Ñ–Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÑумÑÑˆÑ‡Ð°Ð»ÑŒÐ½Ð°Ñ Ð²ÑÑ€ÑÑ–Ñ â€” $2. SQLite Ð½Ñ Ð±ÑƒÐ´Ð·Ðµ даÑтупны.",
'config-no-fts3' => "'''ПапÑÑ€Ñджаньне''': SQLite Ñтвораны без Ð¼Ð¾Ð´ÑƒÐ»Ñ [//sqlite.org/fts3.html FTS3], Ð´Ð»Ñ Ð³Ñтага ўнутранага інтÑрфÑйÑу Ð½Ñ Ð±ÑƒÐ´Ð·Ðµ даÑÑ‚ÑƒÐ¿Ð½Ð°Ñ Ð¼Ð°Ð³Ñ‡Ñ‹Ð¼Ð°Ñьць пошуку.",
'config-register-globals' => "'''ПапÑÑ€Ñджаньне: ÑƒÐºÐ»ÑŽÑ‡Ð°Ð½Ð°Ñ Ð¾Ð¿Ñ†Ñ‹Ñ PHP <code>[http://php.net/register_globals register_globals]</code>.'''
'''Ðдключыце Ñе, калі можаце.'''
@@ -1332,17 +1344,17 @@ MediaWiki патрÑÐ±Ð½Ñ‹Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ‹Ñ– з гÑтага модулю, там
Калі Ð’Ñ‹ выкарыÑтоўваеце Mandrake, уÑталюйце пакет php-xml.',
'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-ctype' => "'''Ð¤Ð°Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ð°Ð¼Ñ‹Ð»ÐºÐ°''': PHP муÑіць быць ÑкампілÑваны з падтрымкай [http://www.php.net/manual/en/ctype.installation.php пашырÑÐ½ÑŒÐ½Ñ Ctype].",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] уÑталÑваны',
'config-apc' => '[http://www.php.net/apc APC] уÑталÑваны',
- 'config-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://xcache.lighttpd.net/ XCache] ці [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''ПапÑÑ€Ñджаньне:''' немагчыма знайÑьці [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ці [http://www.iis.net/download/WinCacheForPhp WinCache].
Ðб’ектнае кÑшаваньне Ð½Ñ ÑžÐºÐ»ÑŽÑ‡Ð°Ð½Ð°Ðµ.",
'config-mod-security' => "'''ПапÑÑ€Ñджаньне''': на Вашым ÑžÑб-ÑÑрверы ўключаны [http://modsecurity.org/ mod_security]. У выпадку нÑÑлушнай наладцы, ён можа Ñтаць прычынай праблемаў Ð´Ð»Ñ MediaWiki ці іншага праграмнага забеÑьпÑчÑньнÑ, Ñкое дазвалÑе ўдзельнікам даÑылаць на ÑÑрвÑÑ€ любы зьмеÑÑ‚.
ГлÑдзіце [http://modsecurity.org/documentation/ дакумÑнтацыю mod_security] ці зьвÑрніцеÑÑ Ñž падтрымку Вашага хоÑту, калі Ñž Ð’Ð°Ñ ÑƒÐ·ÑŒÐ½Ñ–ÐºÐ°ÑŽÑ†ÑŒ Ð²Ñ‹Ð¿Ð°Ð´ÐºÐ¾Ð²Ñ‹Ñ Ð¿Ñ€Ð°Ð±Ð»ÐµÐ¼Ñ‹.",
@@ -1373,9 +1385,11 @@ MediaWiki патрабуе падтрымкі UTF-8 Ð´Ð»Ñ Ñлушнай пра
'config-db-host' => 'ХоÑÑ‚ базы зьвеÑтак:',
'config-db-host-help' => 'Калі ÑÑрвÑÑ€ Вашай базы зьвеÑтак знаходзіцца на іншым ÑÑрвÑры, увÑдзіце тут Ñ–Ð¼Ñ Ñ…Ð¾Ñта ці IP-адраÑ.
-Калі Ð’Ñ‹ набываеце shared-хоÑтынг, Ваш хоÑтынг-правайдÑÑ€ муÑіць даць Вам Ñлушнае Ñ–Ð¼Ñ Ñ…Ð¾Ñта базы зьвеÑтак у Ñваёй дакумÑнтацыі.
+Калі Ð’Ñ‹ карыÑтаецеÑÑ shared-хоÑтынгам, Ваш хоÑтынг-правайдÑÑ€ муÑіць даць Вам Ñлушнае Ñ–Ð¼Ñ Ñ…Ð¾Ñта базы зьвеÑтак у Ñваёй дакумÑнтацыі.
-Калі Ð’Ñ‹ уÑталёўваеце ÑÑрвÑÑ€ 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 лёгкае злучÑньне].',
'config-db-wiki-settings' => 'ІдÑÐ½Ñ‚Ñ‹Ñ„Ñ–ÐºÐ°Ñ†Ñ‹Ñ Ð³Ñтай вікі',
@@ -1662,7 +1676,7 @@ chmod a+w $3</pre>',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] — магчымаÑьць, ÑÐºÐ°Ñ Ð´Ð°Ð·Ð²Ð°Ð»Ñе вікі выкарыÑтоўваць выÑвы, гукі Ñ– Ñ–Ð½ÑˆÑ‹Ñ Ð¼ÑдыÑ, ÑÐºÑ–Ñ Ð·Ð½Ð°Ñ…Ð¾Ð´Ð·Ñцца на Ñайце [//commons.wikimedia.org/ Wikimedia Commons].
Каб гÑта зрабіць, MediaWiki патрабуе доÑтупу да ІнтÑрнÑту.
-Каб даведацца болей пра гÑтую магчымаÑьць, уключаючы інÑтрукцыю пра тое, Ñк Ñе ÑžÑтанавіць Ñž любой вікі, Ð°ÐºÑ€Ð°Ð¼Ñ Wikimedia Commons, глÑдзіце [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos дакумÑнтацыю].',
+Каб даведацца болей пра гÑтую магчымаÑьць, уключаючы інÑтрукцыю пра тое, Ñк Ñе ÑžÑтанавіць Ñž любой вікі, Ð°ÐºÑ€Ð°Ð¼Ñ Wikimedia Commons, глÑдзіце [//mediawiki.org/wiki/Manual:$wgForeignFileRepos дакумÑнтацыю].',
'config-cc-error' => 'Выбар ліцÑнзіі Creative Commons Ð½Ñ Ð´Ð°Ñž вынікаў.
УвÑдзіце назву ліцÑнзіі ўручную.',
'config-cc-again' => 'Выберыце ÑÑˆÑ‡Ñ Ñ€Ð°Ð·â€¦',
@@ -1672,7 +1686,7 @@ chmod a+w $3</pre>',
'config-cache-help' => 'КÑшаваньне аб’ектаў павÑлічвае хуткаÑьць працы MediaWiki праз кÑшаваньне зьвеÑтак, ÑÐºÑ–Ñ Ñ‡Ð°Ñта выкарыÑтоўваюцца.
Вельмі Ñ€ÑкамÑндуем уключыць гÑта Ð´Ð»Ñ ÑÑÑ€Ñдніх Ñ– буйных Ñайтаў, такÑама будзе карыÑна Ð´Ð»Ñ Ð´Ñ€Ð¾Ð±Ð½Ñ‹Ñ… Ñайтаў.',
'config-cache-none' => 'Без кÑÑˆÐ°Ð²Ð°Ð½ÑŒÐ½Ñ (ніÑÐºÑ–Ñ Ð¼Ð°Ð³Ñ‡Ñ‹Ð¼Ð°Ñьці не Ñтрачваюцца, але хуткаÑьць працы буйных Ñайтаў можа зьнізіцца)',
- 'config-cache-accel' => 'КÑшаваньне аб’ектаў PHP (APC, eAccelerator, XCache ці WinCache)',
+ 'config-cache-accel' => 'КÑшаваньне аб’ектаў PHP (APC, XCache ці WinCache)',
'config-cache-memcached' => 'ВыкарыÑтоўваць Memcached (патрабуе дадатковай канфігурацыі)',
'config-memcached-servers' => 'СÑрвÑры memcached:',
'config-memcached-help' => 'Ð¡ÑŒÐ¿Ñ–Ñ IP-адраÑоў, ÑÐºÑ–Ñ Ð±ÑƒÐ´ÑƒÑ†ÑŒ выкарыÑтоўвацца Memcached.
@@ -1882,11 +1896,11 @@ $1
'config-memory-bad' => "'''Предупреждение:''' <code>memory_limit</code> на PHP е $1.
СтойноÑтта вероÑтно е твърде ниÑка.
Възможно е инÑталациÑта да Ñе провали!",
+ 'config-ctype' => "'''Фатално''': Ðеобходимо е PHP да бъде компилиран Ñ Ð¿Ð¾Ð´Ð´Ñ€ÑŠÐ¶ÐºÐ° на [http://www.php.net/manual/en/ctype.installation.php разширението Ctype].",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] е инÑталиран',
'config-apc' => '[http://www.php.net/apc APC] е инÑталиран',
- 'config-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://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Предупреждение:''' Ðе бÑха открити [http://www.php.net/apc APC] [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
Обектното кеширане не е включено.",
'config-diff3-bad' => 'GNU diff3 не беше намерен.',
'config-imagemagick' => 'Открит е ImageMagick: <code>$1</code>.
@@ -1970,7 +1984,7 @@ $1
$1
Ðко не виждате желаната за използване ÑиÑтема в ÑпиÑъка по-долу, Ñледвайте инÑтрукциите за активиране на поддръжка по-горе.',
- 'config-support-mysql' => '* $1 е най-фобре поддържата ÑиÑтема за база от данни, най-добре поддържана от МедиÑУики ([http://www.php.net/manual/en/mysql.installation.php Как Ñе компилира PHP Ñ Ð¿Ð¾Ð´Ð´Ñ€ÑŠÐ¶ÐºÐ° на MySQL])',
+ 'config-support-mysql' => '* $1 е най-добре поддържаната ÑиÑтема за база от данни, Ñ Ð½Ð°Ð¹-добра поддръжка от МедиÑУики ([http://www.php.net/manual/en/mysql.installation.php Как Ñе компилира PHP Ñ Ð¿Ð¾Ð´Ð´Ñ€ÑŠÐ¶ÐºÐ° на MySQL])',
'config-support-postgres' => '* $1 е популÑрна ÑиÑтема за бази от данни Ñ Ð¾Ñ‚Ð²Ð¾Ñ€ÐµÐ½ изходен код, коÑто е алтернатива на MySQL ([http://www.php.net/manual/en/pgsql.installation.php как Ñе компилира PHP Ñ Ð¿Ð¾Ð´Ð´Ñ€ÑŠÐ¶ÐºÐ° на PostgreSQL]). Възможно е вÑе още да има грешки, затова не Ñе препоръчва да Ñе използва в общодоÑтъпна Ñреда.',
'config-support-sqlite' => '* $1 е лека ÑиÑтема за база от данни, коÑто е много добре поддържана. ([http://www.php.net/manual/en/pdo.installation.php Как Ñе компилира PHP Ñ Ð¿Ð¾Ð´Ð´Ñ€ÑŠÐ¶ÐºÐ° на SQLite], използва PDO)',
'config-support-oracle' => '* $1 е комерÑиална корпоративна база от данни. ([http://www.php.net/manual/en/oci8.installation.php Как Ñе компилира PHP Ñ Ð¿Ð¾Ð´Ð´Ñ€ÑŠÐ¶ÐºÐ° на OCI8])',
@@ -2184,7 +2198,7 @@ chmod a+w $3</pre>',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] е функционалноÑÑ‚, коÑто позволÑва на уикитата да използват картинки, звуци и друга медиа от Ñайта на Ð£Ð¸ÐºÐ¸Ð¼ÐµÐ´Ð¸Ñ [//commons.wikimedia.org/ ОбщомедиÑ].
За да е възможно това, МедиÑУики изиÑква доÑтъп до Интернет.
-Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° тази функционалноÑÑ‚, както и инÑтрукции за наÑтройване за други уикита, различни от ОбщомедиÑ, е налична в [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos наръчника].',
+Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° тази функционалноÑÑ‚, както и инÑтрукции за наÑтройване за други уикита, различни от ОбщомедиÑ, е налична в [//mediawiki.org/wiki/Manual:$wgForeignFileRepos наръчника].',
'config-cc-error' => 'Избирането на лиценз на Криейтив ÐšÐ¾Ð¼ÑŠÐ½Ñ Ð½Ðµ даде резултат.
Ðеобходимо е името на лиценза да бъде въведено ръчно.',
'config-cc-again' => 'Повторно избиране...',
@@ -2194,7 +2208,7 @@ chmod a+w $3</pre>',
'config-cache-help' => 'Обектното кеширане Ñе използва за подобрÑване на ÑкороÑтта на МедиÑУики чрез кеширане на чеÑто използваните данни.
Силно препоръчително е на Ñредните и големите Ñайтове да включат тази наÑтройка, но малките Ñъщо могат да Ñе възползват от неÑ.',
'config-cache-none' => 'Без кеширане (не Ñе премахва от функционалноÑтта, но това влиÑе на ÑкороÑтта на по-големи уикита)',
- 'config-cache-accel' => 'PHP обектно кеширане (APC, eAccelerator, XCache или WinCache)',
+ 'config-cache-accel' => 'PHP обектно кеширане (APC, XCache или WinCache)',
'config-cache-memcached' => 'Използване на Memcached (изиÑква допълнителни наÑтройки и конфигуриране)',
'config-memcached-servers' => 'Memcached Ñървъри:',
'config-memcached-help' => 'СпиÑък Ñ IP адреÑи за използване за Memcached.
@@ -2548,9 +2562,8 @@ Re izel eo moarvat.
Marteze e c'hwito ar staliadenn !",
'config-xcache' => 'Staliet eo [http://xcache.lighttpd.net/ XCache]',
'config-apc' => 'Staliet eo [http://www.php.net/apc APC]',
- 'config-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].
+ 'config-no-cache' => "'''Diwallit:''' N'eus ket bet gallet kavout [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] pe [http://www.iis.net/download/WinCacheForPhp WinCache].
N'eo ket gweredekaet ar c'hrubuilhañ traezoù.",
'config-diff3-bad' => "N'eo ket bet kavet GNU diff3.",
'config-imagemagick' => "ImageMagick kavet : <code>$1</code>.
@@ -2721,6 +2734,7 @@ Gellout a rit tremen ar c'hefluniadur nevez ha staliañ ar wiki war-eeun.",
'config-license' => 'Copyright hag aotre-implijout:',
'config-license-none' => 'Aotre ebet en traoñ pajenn',
'config-license-cc-by-sa' => 'Creative Commons Deroadenn Kenrannañ heñvel',
+ 'config-license-cc-by' => 'Creative Commons Deroadenn',
'config-license-cc-by-nc-sa' => 'Creative Commons Deroadenn Angenwerzhel Kenrannañ heñvel',
'config-license-cc-0' => 'Creative Commons Zero (Domani foran)',
'config-license-pd' => 'Domani foran',
@@ -2753,7 +2767,7 @@ Merkit anv an aotre-implijout gant an dorn.",
'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-accel' => 'Krubuilhañ traezoù PHP (APC, XCache pe WinCache)',
'config-cache-memcached' => 'Implijout Memcached (en deus ezhomm bezañ staliet ha kefluniet)',
'config-memcached-servers' => 'Servijerioù Memcached :',
'config-memcached-help' => "Roll ar chomlec'hioù IP da implijout evit Memcached.
@@ -2984,22 +2998,542 @@ $messages['crh-cyrl'] = array(
/** Czech (ÄŒesky)
* @author Danny B.
+ * @author Mormegil
*/
$messages['cs'] = array(
+ 'config-desc' => 'InstalaÄní program pro MediaWiki',
+ 'config-title' => 'Instalace MediaWiki $1',
'config-information' => 'Informace',
+ 'config-localsettings-upgrade' => 'Byl nalezen soubor <code>LocalSettings.php</code>.
+Pokud chcete stávající instalaci aktualizovat, zadejte hodnotu <code>$wgUpgradeKey</code>, kterou naleznete v souboru LocalSettings.php, do následujícího rámeÄku.',
+ 'config-localsettings-cli-upgrade' => 'Byl detekován soubor <code>LocalSettings.php</code>
+Pro aktualizaci spusťte místo instalace skript <code>update.php</code>.',
+ 'config-localsettings-key' => 'KlÃ­Ä pro aktualizaci:',
+ 'config-localsettings-badkey' => 'Zadaný klÃ­Ä je nesprávný.',
+ 'config-upgrade-key-missing' => 'Byla detekována existující instalace MediaWiki.
+Pokud ji chcete aktualizovat, přidejte následující řádku na konec souboru LocalSettings.php:
+
+$1',
+ 'config-localsettings-incomplete' => 'Existující soubor LocalSettings.php vypadá neúplný.
+Není nastavena proměnná $1.
+Upravte soubor LocalSettings.php tak, aby tuto promÄ›nnou obsahoval, a kliknÄ›te na „PokraÄovat“.',
+ 'config-localsettings-connection-error' => 'Při připojování k databázi s využitím nastavení uvedených v LocalSettings.php nebo AdminSettings.php došlo k chybě. Opravte tato nastavení a zkuste to znovu.
+
+$1',
+ 'config-session-error' => 'Nepodařilo se inicializovat relaci: $1',
+ 'config-session-expired' => 'Platnost dat vašeho sezení patrně vypršela.
+Sezení má nastavenu životnost $1.
+Prodloužit ji můžete nastavením <code>session.gc_maxlifetime</code> v php.ini.
+SpusÅ¥te instalaÄní proces od zaÄátku.',
+ 'config-no-session' => 'Data vašeho sezení se ztratila!
+Zkontrolujte svůj soubor php.ini a ujistěte se, že <code>session.save_path</code> je nastaveno na odpovídající adresář.',
+ 'config-your-language' => 'Váš jazyk:',
+ 'config-your-language-help' => 'Zvolte jazyk, který se má použít v průběhu instalace.',
+ 'config-wiki-language' => 'Jazyk wiki:',
+ 'config-wiki-language-help' => 'Zvolte jazyk, ve kterém bude většina obsahu wiki.',
+ 'config-back' => '↠Zpět',
'config-continue' => 'PokraÄovat →',
'config-page-language' => 'Jazyk',
+ 'config-page-welcome' => 'Vítejte v MediaWiki!',
+ 'config-page-dbconnect' => 'Připojení k databázi',
+ 'config-page-upgrade' => 'Aktualizace existující instalace',
+ 'config-page-dbsettings' => 'Nastavení databáze',
'config-page-name' => 'Název',
'config-page-options' => 'Nastavení',
'config-page-install' => 'Instalovat',
+ 'config-page-complete' => 'Hotovo!',
+ 'config-page-restart' => 'Restartovat instalaci',
+ 'config-page-readme' => 'Soubor ÄŒti mÄ›',
+ 'config-page-releasenotes' => 'Poznámky k vydání',
+ 'config-page-copying' => 'Licence',
+ 'config-page-upgradedoc' => 'Upgrade',
+ 'config-page-existingwiki' => 'Existující wiki',
+ 'config-help-restart' => 'Chcete smazat vÅ¡echny údaje, které jste zadali, a spustit proces instalace znovu od zaÄátku?',
+ 'config-restart' => 'Ano, restartovat',
+ 'config-welcome' => '=== Kontrola prostředí ===
+Provedou se základní kontroly, aby se zjistilo, zda je toto prostředí použitelné k instalaci MediaWiki.
+Pokud budete potřebovat při instalaci pomoc, měli byste sdělit výsledky těchto testů.',
+ 'config-copyright' => "=== Licence a podmínky ===
+$1
+
+Tento program je svobodný software; můžete jej šířit nebo modifikovat podle podmínek GNU General Public License, vydávané Free Software Foundation; buÄ verze 2 této licence anebo (podle vaÅ¡eho uvážení) kterékoli pozdÄ›jší verze.
+
+Tento program je distribuován v nadÄ›ji, že bude užiteÄný, avÅ¡ak '''bez jakékoli záruky'''; neposkytují se ani odvozené záruky '''prodejnosti''' anebo '''vhodnosti pro urÄitý úÄel'''.
+Podrobnosti se doÄtete v textu GNU General Public License.
+
+<doclink href=Copying>Kopii GNU General Public License</doclink> jste mÄ›li obdržet spolu s tímto programem; pokud ne, napiÅ¡te na Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA nebo [http://www.gnu.org/copyleft/gpl.html si ji pÅ™eÄtÄ›te online].",
+ 'config-sidebar' => '* [//www.mediawiki.org Oficiální web MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents Uživatelská příruÄka]
+* [//www.mediawiki.org/wiki/Manual:Contents Administrátorská příruÄka]
+* [//www.mediawiki.org/wiki/Manual:FAQ FAQ]
+----
+* <doclink href=Readme>ÄŒti mÄ›</doclink>
+* <doclink href=ReleaseNotes>Poznámky k vydání</doclink>
+* <doclink href=Copying>Licence</doclink>
+* <doclink href=UpgradeDoc>Upgrade</doclink>',
+ 'config-env-good' => 'Prostředí bylo zkontrolováno.
+Můžete nainstalovat MediaWiki.',
+ 'config-env-bad' => 'Prostředí bylo zkontrolováno.
+MediaWiki nelze nainstalovat.',
+ 'config-env-php' => 'Je nainstalováno PHP $1.',
+ 'config-env-php-toolow' => 'Je nainstalováno PHP $1.
+MediaWiki ale vyžaduje PHP $2 nebo vyšší.',
+ 'config-unicode-using-utf8' => 'Pro normalizaci Unicode se používá utf8_normalize.so Briona Vibbera.',
+ 'config-unicode-using-intl' => 'Pro normalizaci Unicode se používá [http://pecl.php.net/intl PECL rozšíření intl].',
+ 'config-unicode-pure-php-warning' => "'''UpozornÄ›ní''': Není dostupné [http://pecl.php.net/intl PECL rozšíření intl] pro normalizaci Unicode, bude se využívat pomalá implementace v Äistém PHP.
+Pokud provozujete wiki s velkým provozem, mÄ›li byste si pÅ™eÄíst nÄ›co o [//www.mediawiki.org/wiki/Unicode_normalization_considerations normalizaci Unicode].",
+ 'config-unicode-update-warning' => "'''Upozornění''': Nainstalovaná verze vrstvy pro normalizaci Unicode používá starší verzi knihovny [http://site.icu-project.org/ projektu ICU].
+Pokud vám aspoň trochu záleží na používání Unicode, měli byste [//www.mediawiki.org/wiki/Unicode_normalization_considerations ji aktualizovat].",
+ 'config-no-db' => 'NepodaÅ™ilo se nalézt vhodný databázový ovladaÄ! Musíte do PHP nainstalovat databázový ovladaÄ.
+Jsou podporovány následující typy databází: $1.
+
+Pokud jste na sdíleném hostingu, požádejte svého poskytovale o instalaci vhodného databázového ovladaÄe.
+Pokud jste si PHP přeložili sami, překonfigurujte ho se zapnutým databázovým klientem, například pomocí <code>./configure --with-mysql</code>.
+Pokud jste PHP nainstalovali z balíÄku Debian Äi Ubuntu, potÅ™ebujete nainstalovat také modul php5-mysql.',
+ 'config-outdated-sqlite' => "'''Upozornění''': Máte SQLite $1, které je starší než minimálně vyžadovaná verze $2. SQLite nebude dostupné.",
+ 'config-no-fts3' => "'''Upozornění''': SQLite bylo přeloženo bez [//sqlite.org/fts3.html modulu FTS3], funkce pro vyhledávání zde nebudou dostupné.",
+ 'config-register-globals' => "'''Upozornění: Je zapnuta PHP volba <code>[http://php.net/register_globals register_globals]</code>.'''
+'''Pokud můžete, vypněte ji.'''
+MediaWiki bude fungovat, ale váš server je vystaven potenciálním bezpeÄnostním hrozbám.",
+ 'config-magic-quotes-runtime' => "'''Kritická chyba: Je zapnuto [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]!'''
+Toto nastavení nepředvídatelně poškozuje vstupní data.
+MediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
+ 'config-magic-quotes-sybase' => "'''Kritická chyba: Je zapnuto [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]!'''
+Toto nastavení nepředvídatelně poškozuje vstupní data.
+MediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
+ 'config-mbstring' => "'''Kritická chyba: Je zapnuto [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]!'''
+Toto nastavení způsobuje chyby a může nepředvídatelně poškozovat vstupní data.
+MediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
+ 'config-ze1' => "'''Kritická chyba: Je zapnut [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]!'''
+Toto nastavení způsobuje s MediaWiki příšerné chyby.
+MediaWiki nelze nainstalovat ani používat, dokud není toto nastavení vypnuto.",
+ 'config-safe-mode' => "'''UpozornÄ›ní:''' Je aktivní [http://www.php.net/features.safe-mode bezpeÄný režim] PHP.
+Může způsobovat potíže, zejména pÅ™i použití naÄítání souborů a podpory <code>math</code>.",
+ 'config-xml-bad' => 'Chybí XML modul pro PHP.
+MediaWiki potřebuje funkce v tomto modulu a v této konfiguraci nebude fungovat.
+Pokud běžíte na Mandrake, nainstalujte balíÄek php-xml.',
+ 'config-pcre' => 'Zdá se, že modul s podporou PCRE chybí.
+MediaWiki ke své Äinnosti potÅ™ebuje funkce pro Perl-kompatibilní regulární výrazy.',
+ 'config-pcre-no-utf8' => "'''Kritická chyba''': PHP modul PCRE byl zřejmě přeložen bez podpory PCRE_UTF8.
+MediaWiki vyžaduje ke správné funkci podporu UTF-8.",
+ 'config-memory-raised' => '<code>memory_limit</code> v PHP byl nastaven na $1, zvýšen na $2.',
+ 'config-memory-bad' => "'''Upozornění:''' <code>memory_limit</code> je v PHP nastaven na $1.
+To je pravděpodobně příliš málo.
+Instalace může selhat!",
+ 'config-ctype' => "'''Kritická chyba''': PHP musí být přeloženo s podporou pro [http://www.php.net/manual/en/ctype.installation.php rozšíření Ctype].",
+ 'config-xcache' => 'Je nainstalována [http://xcache.lighttpd.net/ XCache]',
+ 'config-apc' => 'Je nainstalováno [http://www.php.net/apc APC]',
+ 'config-wincache' => 'Je nainstalována [http://www.iis.net/download/WinCacheForPhp WinCache]',
+ 'config-no-cache' => "'''Upozornění:''' Nebylo nalezeno [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache], ani [http://www.iis.net/download/WinCacheForPhp WinCache].
+Kešování objektů bude vypnuto.",
+ 'config-mod-security' => "'''UpozornÄ›ní''': váš webový server má zapnuto [http://modsecurity.org/ mod_security]. PÅ™i chybné konfiguraci může způsobovat potíže MediaWiki Äi dalším programům, které umožňují ukládat libovolný obsah.
+Pokud narazíte na náhodné chyby, podívejte se do [http://modsecurity.org/documentation/ dokumentace mod_security] nebo kontaktujte technickou podporu vašeho poskytovatele.",
+ 'config-diff3-bad' => 'Nebyl nalezen GNU diff3.',
+ 'config-imagemagick' => 'Nalezen ImageMagick: <code>$1</code>.
+Pokud povolíte naÄítání souborů, bude zapnuto vytváření náhledů.',
+ 'config-gd' => 'Nalezena vestavěná grafická knihovna GD.
+Pokud povolíte naÄítání souborů, bude zapnuto vytváření náhledů.',
+ 'config-no-scaling' => 'Nebyla nalezena knihovna GD ani ImageMagick.
+Vytváření náhledů bude vypnuto.',
+ 'config-no-uri' => "'''Chyba:''' NepodaÅ™ilo se urÄit aktuální URI.
+Instalace přerušena.",
+ 'config-no-cli-uri' => "'''Upozornění''': Nebylo uvedeno --scriptpath, používá se implicitní hodnota: <code>$1</code>.",
+ 'config-using-server' => 'Použito jméno serveru „<nowiki>$1</nowiki>“.',
+ 'config-using-uri' => 'Použito URL serveru „<nowiki>$1$2</nowiki>“.',
+ 'config-uploads-not-safe' => "'''UpozornÄ›ní:''' Váš implicitní adresář pro naÄítání souborů <code>$1</code> umožňuje provádÄ›ní libovolných skriptů.
+PÅ™estože MediaWiki vÅ¡echny naÄítané soubory kontroluje proti bezpeÄnostním hrozbám, je důraznÄ› doporuÄeno [//www.mediawiki.org/wiki/Manual:Security#Upload_security tuto bezpeÄnostní díru zacelit] pÅ™ed povolením naÄítání souborů.",
+ 'config-no-cli-uploads-check' => "'''UpozornÄ›ní:''' Váš implicitní adresář pro naÄítané soubory (<code>$1</code>) se pÅ™i instalaci z příkazového řádku nekontroluje na bezpeÄnostní hrozbu provádÄ›ní libovolných skriptů.",
+ 'config-brokenlibxml' => 'Váš systém obsahuje kombinaci verzí PHP a libxml2, která je chybná a může v MediaWiki a dalších webových aplikacích způsobovat skryté poškozování dat.
+Aktualizujte na PHP 5.2.9 nebo novější a libxml2 2.7.3 nebo novější ([//bugs.php.net/bug.php?id=45996 chyba evidovaná u PHP]).
+Instalace přerušena.',
+ 'config-using531' => 'MediaWiki nelze používat na PHP $1 kvůli chybě při předávání parametrů odkazem do <code>__call()</code>.
+Pro vyřešení upgradujte na PHP 5.3.2 nebo vyšší nebo downgradujte na PHP 5.3.0.
+Instalace přerušena.',
+ 'config-suhosin-max-value-length' => 'Je nainstalován Suhosin, který omezuje délku parametrů GET na $1 bajtů. Komponenta ResourceLoader z MediaWiki dokáže s tímto omezením pracovat, ale sníží to výkon. Pokud to je alespoň trochu možné, měli byste v php.ini nastavit suhosin.get.max_value_length na 1024 nebo vyšší a na stejnou hodnotu nastavit v LocalSettings.php proměnnou $wgResourceLoaderMaxQueryLength.',
+ 'config-db-type' => 'Typ databáze:',
+ 'config-db-host' => 'Databázový server:',
+ 'config-db-host-help' => 'Pokud je váš databázový server na jiném poÄítaÄi, zadejte zde jméno stroje nebo IP adresu.
+
+Pokud používáte sdílený webový hosting, váš poskytovatel by vám měl v dokumentaci sdělit správné jméno stroje.
+
+Pokud instalujete na server běžící na Windows a používáte MySQL, jméno „localhost“ nemusí fungovat. V takovém případě zkuste jako místní IP adresu zadat „127.0.0.1“.
+
+Pokud používáte PostgreSQL, můžete se připojit Unixovými sockety tak, že toto pole necháte prázdné.',
+ 'config-db-host-oracle' => 'Databázové TNS:',
+ 'config-db-host-oracle-help' => 'Zadejte platné [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; tato instalace musí vidět soubor tnsnames.ora.<br />Pokud používáte klientské knihovny verze 10g nebo novější, můžete také používat názvy [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Identifikace této wiki',
+ 'config-db-name' => 'Jméno databáze:',
+ 'config-db-name-help' => 'Zvolte jméno, které oznaÄuje vaÅ¡i wiki.
+Nemělo by obsahovat mezery.
+
+Pokud používáte sdílený webový hosting, váš poskytovatel vám buÄ sdÄ›lí konkrétní jméno databáze, nebo vás nechá vytvářet databáze pomocí nÄ›jakého ovládacího panelu.',
+ 'config-db-name-oracle' => 'Databázové schéma:',
+ 'config-db-account-oracle-warn' => 'Existují tři podporované možnosti pro instalaci s použitím databáze Oracle.
+
+Pokud chcete v rámci instalace založit databázový úÄet, zadejte jako databázový úÄet pro instalaci úÄet s rolí SYSDBA a uveÄte požadované údaje pro úÄet pro webový přístup, jinak můžete vytvoÅ™it úÄet pro webový přístup ruÄnÄ› a zadat pouze tento úÄet (pokud má dostateÄná oprávnÄ›ní k zakládání objektů schématu) nebo poskytnout dva různé úÄty, jeden s oprávnÄ›ními k zakládání, druhý omezený pro webový přístup.
+
+Skript pro založení úÄtu s potÅ™ebnými privilegii můžete v této instalaci nalézt v adresáři „maintenance/oracle/“. Nezapomeňte, že použití omezeného úÄtu znepřístupní veÅ¡keré možnosti údržby pÅ™es implicitní úÄet.',
+ 'config-db-install-account' => 'Uživatelský úÄet pro instalaci',
+ 'config-db-username' => 'Databázové uživatelské jméno:',
+ 'config-db-password' => 'Databázové heslo:',
+ 'config-db-password-empty' => 'Zadejte heslo pro nového databázového uživatele: $1.
+PÅ™estože může jít zakládat nové uživatele i bez hesel, není to bezpeÄné.',
+ 'config-db-install-username' => 'Zadejte uživatelské jméno, které se použije pro připojení k databázi v průběhu instalace.
+Toto není jméno uživatelského úÄtu MediaWiki; toto je uživatelské jméno k vaší databázi.',
+ 'config-db-install-password' => 'Zadejte heslo, které se použije pro připojení k databázi v průběhu instalace.
+Toto není heslo uživatelského úÄtu MediaWiki; toto je heslo k vaší databázi.',
+ 'config-db-install-help' => 'Zadejte uživatelské jméno a heslo, které se použijí pro připojení k databázi v průběhu instalace.',
+ 'config-db-account-lock' => 'Použít stejné uživatelské jméno a heslo pro běžnou Äinnost',
+ 'config-db-wiki-account' => 'Uživatelský úÄet pro běžnou Äinnost',
+ 'config-db-wiki-help' => 'Zadejte uživatelské jméno a heslo, které se bude používat pro připojení k databázi za běžného provozu wiki.
+Pokud úÄet neexistuje a instalaÄní úÄet má dostateÄná oprávnÄ›ní, bude tento uživatelský úÄet založen s minimálními oprávnÄ›ními potÅ™ebnými k provozu wiki.',
+ 'config-db-prefix' => 'Prefix databázových tabulek:',
+ 'config-db-prefix-help' => 'Pokud potřebujete sdílet jednu databázi mezi vícero wiki, případně mezi MediaWiki a další webovou aplikací, můžete přidat k názvu každé tabulky prefix, abyste se vyhnuli konfliktům.
+Nepoužívejte mezery.
+
+Toto pole se zpravidla ponechává prázdné.',
+ 'config-db-charset' => 'Znaková sada databáze',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binární',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 zpětně kompatibilní UTF-8',
+ 'config-charset-help' => "'''UpozornÄ›ní:''' Pokud použijete '''zpÄ›tnÄ› kompatibilní UTF-8''' na MySQL 4.1+ a následnÄ› zazálohujete databázi pomocí <code>mysqldump</code>, může to zniÄit vÅ¡echny ne-ASCII znaky, což nevratnÄ› poÅ¡kodí vaÅ¡e zálohy!
+
+V '''binárním režimu''' ukládá MediaWiki text v UTF-8 do databáze v binárních sloupcích.
+To je výkonnější než UTF-8 režim MySQL a umožňuje využít plný rozsah znaků Unicode.
+V '''režimu UTF-8''' bude MySQL znát znakovou sadu vašich dat a může je příslušně zobrazovat a převádět,
+ale neumožní vám uložit znaky mimo [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ 'config-mysql-old' => 'Je vyžadováno MySQL $1 nebo novější, vy máte $2.',
+ 'config-db-port' => 'Databázový port:',
+ 'config-db-schema' => 'Schéma pro MediaWiki:',
+ 'config-db-schema-help' => 'Toto schéma zpravidla staÄí.
+Měňte ho, jen pokud víte, že je to potřeba.',
+ 'config-pg-test-error' => "Nelze se připojit k databázi '''$1''': $2",
+ 'config-sqlite-dir' => 'Adresář pro data SQLite:',
+ 'config-sqlite-dir-help' => "SQLite ukládá veškerá data v jediném souboru.
+
+Zadaný adresář musí být v průběhu instalace být přístupný pro zápis.
+
+'''Neměl by''' být dostupný z webu, proto ho nedáváme tam, kde jsou vaše PHP soubory.
+
+Instalátor do adresáře přidá soubor <code>.htaccess</code>, ale pokud to selže, mohl by někdo získat přístup k vaší holé databázi.
+To zahrnuje syrová uživatelská data (e-mailové adresy, hašovaná hesla), jako i smazané revize a další data s omezeným přístupem z vaší wiki.
+
+Zvažte umístění databáze někam zcela jinam, například do <code>/var/lib/mediawiki/mojewiki</code>.",
+ 'config-oracle-def-ts' => 'Implicitní tabulkový prostor:',
+ 'config-oracle-temp-ts' => 'DoÄasný tabulkový prostor:',
'config-type-mysql' => 'MySQL',
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Věštba',
+ 'config-type-ibm_db2' => 'IBM DB2',
+ 'config-support-info' => 'MediaWiki podporuje následující databázové systémy:
+
+$1
+
+Pokud v nabídce níže nevidíte databázový systém, který chcete použít, musíte pro zapnutí podpory následovat instrukce odkázané výše.',
+ 'config-support-mysql' => '* $1 je pro MediaWiki hlavní platformou a je podporováno nejlépe ([http://www.php.net/manual/en/mysql.installation.php jak zkompilovat PHP s podporou MySQL])',
+ 'config-support-postgres' => '* $1 je populární open-source databázový systém používaný jako alternativa k MySQL ([http://www.php.net/manual/en/pgsql.installation.php jak pÅ™eložit PHP s podporou PostgreSQL]). Mohou se vyskytnout jeÅ¡tÄ› nÄ›jaké menší chyby, použití v produkÄním prostÅ™edí se nedoporuÄuje.',
+ 'config-support-sqlite' => '* $1 je velmi dobře podporovaný lehký databázový systém. ([http://www.php.net/manual/en/pdo.installation.php Jak přeložit PHP s podporou SQLite], používá PDO)',
+ 'config-support-oracle' => '* $1 je komerÄní podniková databáze. ([http://www.php.net/manual/en/oci8.installation.php Jak pÅ™eložit PHP s podporou OCI8])',
+ 'config-support-ibm_db2' => '* $1 je komerÄní podniková databáze.',
+ 'config-header-mysql' => 'Nastavení MySQL',
+ 'config-header-postgres' => 'Nastavení PostgreSQL',
+ 'config-header-sqlite' => 'Nastavení SQLite',
+ 'config-header-oracle' => 'Nastavení Oracle',
+ 'config-header-ibm_db2' => 'Nastavení IBM DB2',
+ 'config-invalid-db-type' => 'Chybný typ databáze',
+ 'config-missing-db-name' => 'Musíte zadat hodnotu pro „Jméno databáze“',
+ 'config-missing-db-host' => 'Musíte zadat hodnotu pro „Databázový server“',
+ 'config-missing-db-server-oracle' => 'Musíte zadat hodnotu pro „Databázové TNS“',
+ 'config-invalid-db-server-oracle' => 'Chybné databázové TNS „$1“.
+Používejte pouze ASCII písmena (a-z, A-Z), Äísla (0-9), podtržítko (_) a teÄku (.).',
+ 'config-invalid-db-name' => 'Chybné jméno databáze „$1“.
+Používejte pouze ASCII písmena (a-z, A-Z), Äísla (0-9), podtržítko (_) a spojovník (-).',
+ 'config-invalid-db-prefix' => 'Chybný databázový prefix „$1“.
+Používejte pouze ASCII písmena (a-z, A-Z), Äísla (0-9), podtržítko (_) a spojovník (-).',
+ 'config-connection-error' => '$1.
+
+Zkontrolujte server, uživatelské jméno a heslo a zkuste to znovu.',
+ 'config-invalid-schema' => 'Neplatné schéma pro MediaWiki „$1“.
+Používejte pouze ASCII písmena (a-z, A-Z), Äísla (0-9) a podtržítko (_).',
+ 'config-db-sys-create-oracle' => 'Instalátor podporuje zakládání nového úÄtu pouze prostÅ™ednictvím úÄtu SYSDBA.',
+ 'config-db-sys-user-exists-oracle' => 'Uživatelský úÄet „$1“ již existuje. SYSDBA lze použít pouze pro založení nového úÄtu!',
+ 'config-postgres-old' => 'Je vyžadován PostgreSQL $1 nebo novější, vy máte $2.',
+ 'config-sqlite-name-help' => 'Zvolte jméno, které oznaÄuje vaÅ¡i wiki.
+Nepoužívejte mezery a spojovníky.
+Použije se jako název souboru s daty SQLite.',
+ 'config-sqlite-parent-unwritable-group' => 'Nelze vytvořit datový adresář <code><nowiki>$1</nowiki></code>, protože do nadřazeného adresáře <code><nowiki>$2</nowiki></code> nemá webový server právo zapisovat.
+
+Instalátor zjistil uživatele, pod kterým váš webový server běží.
+Abyste mohli pokraÄovat, umožnÄ›te mu zapisovat do adresáře <code><nowiki>$3</nowiki></code>.
+Na systémech Unix/Linux proveÄte:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Nelze vytvořit datový adresář <code><nowiki>$1</nowiki></code>, protože do nadřazeného adresáře <code><nowiki>$2</nowiki></code> nemá webový server právo zapisovat.
+
+Instalátoru se nepodařilo zjistit uživatele, pod kterým váš webový server běží.
+Abyste mohli pokraÄovat, umožnÄ›te zápis do <code><nowiki>$3</nowiki></code> vÅ¡em uživatelům.
+Na systémech Unix/Linux proveÄte:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Chyba při vytváření datového adresáře „$1“.
+Zkontrolujte umístění a zkuste to znovu.',
+ 'config-sqlite-dir-unwritable' => 'Nelze zapisovat do adresáře „$1“.
+Změňte na něm oprávnění, aby do něj mohl webový server zapisovat, a zkuste to znovu.',
+ 'config-sqlite-connection-error' => '$1.
+
+Zkontrolujte datový adresář a jméno databáze níže a zkuste to znovu.',
+ 'config-sqlite-readonly' => 'Do souboru <code>$1</code> nelze zapisovat.',
+ 'config-sqlite-cant-create-db' => 'Nepodařilo se vytvořit databázový soubor <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'PHP neobsahuje podporu FTS3, downgradují se tabulky',
+ 'config-can-upgrade' => "V této databázi jsou tabulky MediaWiki.
+Pokud je chcete aktualizovat na MediaWiki $1, kliknÄ›te na '''PokraÄovat'''.",
+ 'config-upgrade-done' => "Aktualizace byla dokonÄena.
+
+Svou wiki teÄ můžete [$1 zaÄít používat].
+
+Pokud chcete pÅ™egenerovat soubor <code>LocalSettings.php</code>, kliknÄ›te na tlaÄítko níže.
+To se ale '''nedoporuÄuje''', pokud s wiki nemáte problémy.",
+ 'config-upgrade-done-no-regenerate' => 'Aktualizace byla dokonÄena.
+
+Svou wiki teÄ můžete [$1 zaÄít používat].',
+ 'config-regenerate' => 'Přegenerovat LocalSettings.php →',
+ 'config-show-table-status' => 'Dotaz SHOW TABLE STATUS se nezdařil!',
+ 'config-unknown-collation' => "'''Upozornění:''' Databáze používá nerozpoznané řazení.",
+ 'config-db-web-account' => 'Databázový úÄet pro webový přístup',
+ 'config-db-web-help' => 'Zvolte uživatelské jméno a heslo, které bude webový server používat pro připojení k databázovému serveru při běžném provozu wiki.',
+ 'config-db-web-account-same' => 'Použít stejný úÄet jako pro instalaci',
+ 'config-db-web-create' => 'Založit úÄet, pokud zatím neexistuje',
+ 'config-db-web-no-create-privs' => 'ÚÄet uvedený pro instalaci nemá oprávnÄ›ní dostateÄná pro založení nového úÄtu.
+ÚÄet, který zde uvedete, již musí existovat.',
+ 'config-mysql-engine' => 'Typ úložiště:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''UpozornÄ›ní''': Jako typ úložiÅ¡tÄ› pro MySQL jste zvolili MyISAM, které není pro použití v MediaWiki doporuÄeno, neboÅ¥:
+* stěží podporuje souÄasný přístup kvůli zamykání tabulek,
+* je náchylnější na poškození dat než jiná úložiště,
+* kód MediaWiki nepodporuje MyISAM vždy tak dobře, jak by měl.
+
+Pokud vaÅ¡e instalace MySQL podporuje InnoDB, důraznÄ› doporuÄujeme použít spíše to.
+Pokud vaÅ¡e instalace MySQL InnoDB nepodporuje, možná je Äas na aktualizaci.",
+ 'config-mysql-engine-help' => "'''InnoDB''' je téměř vždy nejlepší volba, neboÅ¥ má dobrou podporu souÄasného přístupu.
+
+'''MyISAM''' může být rychlejší u instalací pro jednoho uživatele nebo jen pro Ätení.
+Databáze MyISAM bývají poÅ¡kozeny ÄastÄ›ji než databáze InnoDB.",
+ 'config-mysql-charset' => 'Znaková sada databáze:',
+ 'config-mysql-binary' => 'Binární',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "V '''binárním režimu''' ukládá MediaWiki text v UTF-8 do databáze v binárních sloupcích.
+To je výkonnější než UTF-8 režim MySQL a umožňuje využít plný rozsah znaků Unicode.
+
+V '''režimu UTF-8''' bude MySQL znát znakovou sadu vašich dat a může je příslušně zobrazovat a převádět, ale neumožní vám uložit znaky mimo [//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes Basic Multilingual Plane].",
+ 'config-ibm_db2-low-db-pagesize' => "VaÅ¡e DB2 databáze má implicitní tabulkový prostor s nedostateÄnou velikostí stránky. Velikost stránky musí být minimálnÄ› '''32K'''.",
+ 'config-site-name' => 'Název wiki:',
+ 'config-site-name-help' => 'Bude se zobrazovat v titulku prohlížeÄe a na dalších místech.',
+ 'config-site-name-blank' => 'Zadejte název serveru.',
+ 'config-project-namespace' => 'Jmenný prostor projektu:',
+ 'config-ns-generic' => 'Projekt',
+ 'config-ns-site-name' => 'Stejný jako název wiki: $1',
+ 'config-ns-other' => 'Jiný (uveÄte)',
+ 'config-ns-other-default' => 'MojeWiki',
+ 'config-project-namespace-help' => "Po vzoru Wikipedie udržuje mnoho wiki stránky se svými pravidly odděleně od stránek s vlastním obsahem, v „'''jmenném prostoru projektu'''“.
+Názvy vÅ¡ech stránek v tomto jmenném prostoru zaÄínají jistým prefixem, který zde můžete nastavit.
+Zvykem je odvozovat tento prefix z názvu wiki, ale nesmí obsahovat jisté interpunkÄní znaky jako „#“ nebo „:“.",
+ 'config-ns-invalid' => 'Uvedený jmenný prostor „<nowiki>$1</nowiki>“ je neplatný.
+Zadejte jiný jmenný prostor projektu.',
+ 'config-ns-conflict' => 'Uvedený jmenný prostor „<nowiki>$1</nowiki>“ koliduje se standardním jmenným prostorem MediaWiki.
+Zadejte jiný jmenný prostor projektu.',
+ 'config-admin-box' => 'Správcovský úÄet',
'config-admin-name' => 'Vaše jméno:',
+ 'config-admin-password' => 'Heslo:',
+ 'config-admin-password-confirm' => 'Heslo ještě jednou:',
+ 'config-admin-help' => 'Zde zadejte své požadované uživatelské jméno, například „Pepa Novák“.
+Tímto jménem se budete do wiki hlásit.',
+ 'config-admin-name-blank' => 'Zadejte uživatelské jméno správce.',
+ 'config-admin-name-invalid' => 'Uvedené uživatelské jméno „<nowiki>$1</nowiki>“ není platné.
+Zadejte jiné uživatelské jméno.',
+ 'config-admin-password-blank' => 'Zadejte heslo ke správcovskému úÄtu.',
+ 'config-admin-password-same' => 'Heslo nesmí být stejné jako uživatelské jméno.',
+ 'config-admin-password-mismatch' => 'Uvedená hesla se neshodují.',
'config-admin-email' => 'E-mailová adresa:',
+ 'config-admin-email-help' => 'Zde zadejte e-mailovou adresu, která vám umožní přijímat e-maily od ostatních uživatelů wiki, získat nové heslo a přijímat notifikace o změnách sledovaných stránek. Tohle pole můžete nechat prázdné.',
+ 'config-admin-error-user' => 'Vnitřní chyba při vytváření správce se jménem „<nowiki>$1</nowiki>“.',
+ 'config-admin-error-password' => 'Vnitřní chyba při nastavování hesla správci se jménem „<nowiki>$1</nowiki>“: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Zadali jste neplatnou e-mailovou adresu.',
+ 'config-subscribe' => 'Přihlásit se k odběru [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-mailové konference pro oznamování nových verzí].',
+ 'config-subscribe-help' => 'Tohle je e-mailová konference s nízkým provozem, na které se oznamují nové verze, vÄetnÄ› důležitých bezpeÄnostních oznámení.
+Měli byste se do ní přihlásit a při vydání nových verzí aktualizovat svou instalaci MediaWiki.',
+ 'config-subscribe-noemail' => 'Pokusili jste se přihlásit k odběru e-mailové konference pro oznamování nových verzí, aniž byste poskytli e-mailovou adresu.
+Pokud se chcete přihlásit k odběru, zadejte e-mailovou adresu.',
+ 'config-almost-done' => 'Už jsme skoro hotovi!
+Zbývající konfiguraci už můžete pÅ™eskoÄit a nainstalovat wiki hned teÄ.',
+ 'config-optional-continue' => 'Ptejte se mě dál.',
+ 'config-optional-skip' => 'Už mě to nudí, prostě nainstalujte wiki.',
+ 'config-profile' => 'Profil uživatelských práv:',
+ 'config-profile-wiki' => 'TradiÄní wiki',
+ 'config-profile-no-anon' => 'Vyžadována registrace uživatelů',
+ 'config-profile-fishbowl' => 'Editace jen pro vybrané',
+ 'config-profile-private' => 'Soukromá wiki',
+ 'config-profile-help' => "Wiki fungují nejlépe, když je necháte editovat co nejvÄ›tším možným poÄtem lidí.
+V MediaWiki můžete snadno kontrolovat poslední změny a vracet zpět libovolnou škodu způsobenou hloupými nebo zlými uživateli.
+
+Mnoho lidí vÅ¡ak zjistilo, že je MediaWiki užiteÄné v Å¡irokém spektru rolí a nÄ›kdy není snadné vÅ¡echny pÅ™esvÄ›dÄit o výhodách wikizvyklostí.
+Takže si můžete vybrat.
+
+'''{{int:config-profile-wiki}}''' dovoluje editovat všem, aniž by se museli přihlašovat.
+Na wiki, kde je '''{{int:config-profile-no-anon}}''', se lépe řídí zodpovědnost, ale může to odradit náhodné přispěvatele.
+
+Profil '''{{int:config-profile-fishbowl}}''' umožňuje schváleným uživatelům editovat, ale veÅ™ejnost si může stránky prohlížet vÄetnÄ› jejich historie.
+'''{{int:config-profile-private}}''' dovoluje stránky prohlížet jen schváleným uživatelům, kteří je i mohou editovat.
+
+Po instalaci je možná komplexní konfigurace uživatelských práv; vizte [//www.mediawiki.org/wiki/Manual:User_rights odpovídající stránku příruÄky].",
+ 'config-license' => 'Autorská práva a licence:',
+ 'config-license-none' => 'Bez patiÄky s licencí',
+ 'config-license-cc-by-sa' => 'Creative Commons UveÄte autora-Zachovejte licenci',
+ 'config-license-cc-by' => 'Creative Commons UveÄte autora',
+ 'config-license-cc-by-nc-sa' => 'Creative Commons UveÄte autora-Nevyužívejte dílo komerÄnÄ›-Zachovejte licenci',
+ 'config-license-cc-0' => 'Creative Commons Zero (volné dílo)',
+ 'config-license-gfdl' => 'GNU Free Documentation License 1.3 nebo novější',
+ 'config-license-pd' => 'Volné dílo',
+ 'config-license-cc-choose' => 'Zvolit vlastní licenci Creative Commons',
+ 'config-license-help' => "Mnoho veřejných wiki všechny příspěvky zveřejňuje pod některou [http://freedomdefined.org/Definition/Cs svobodnou licencí].
+To pomáhá vytvořit duch komunitního vlastnictví a povzbuzuje dlouhodobé přispívání.
+To obecně není potřeba u soukromé nebo firemní wiki.
+
+Pokud chcete být schopni používat text z Wikipedie a chcete, aby Wikipedie byla schopna pÅ™ijímat text okopírovaný z vaší wiki, mÄ›li byste zvolit '''Creative Commons UveÄte autora-Zachovejte licenci'''.
+
+Dříve Wikipedie používala GNU Free Documentation License.
+GFDL je platná licence, ale složité jí porozumět.
+Také je komplikované používat obsah licencovaný pod GFDL.",
'config-email-settings' => 'Nastavení e-mailu',
+ 'config-enable-email' => 'Zapnout odchozí e-mail',
+ 'config-enable-email-help' => 'Pokud chcete, aby e-mail fungoval, je potřeba správně nakonfigurovat [http://www.php.net/manual/en/mail.configuration.php e-mailová nastavení PHP].
+Pokud nechcete žádné e-mailové funkce, můžete je zde vypnout.',
+ 'config-email-user' => 'Umožnit vzájemné e-maily mezi uživateli',
+ 'config-email-user-help' => 'Umožní všem uživatelům posílat si navzájem e-maily, pokud si to zapnout v uživatelském nastavení.',
+ 'config-email-usertalk' => 'Umožnit notifikace k uživatelským diskusím',
+ 'config-email-usertalk-help' => 'Umožní uživatelům přijímat notifikace o změnách uživatelských diskusí, pokud si to zapnou v nastavení.',
+ 'config-email-watchlist' => 'Umožnit notifikace ke sledovaným stránkám',
+ 'config-email-watchlist-help' => 'Umožní uživatelům přijímat notifikace o změnách sledovaných stránek, pokud si to zapnou v nastavení.',
+ 'config-email-auth' => 'Zapnout ověřování e-mailů',
+ 'config-email-auth-help' => "Pokud je tato volba vybrána, uživatelé musí potvrdit svou e-mailovou adresu pomocí odkazu, který je jim poslán, kdykoli si ji nastaví nebo změní.
+Jen potvrzené e-mailové adresy mohou přijímat e-maily od ostatních uživatelů a e-maily s notifikacemi o změnách.
+Nastavení této volby je '''doporuÄeno''' pro veÅ™ejné wiki kvůli možnosti zneužití e-mailových funkcí.",
+ 'config-email-sender' => 'Návratová e-mailová adresa:',
+ 'config-email-sender-help' => 'Zadejte e-mailovou adresu, která se má použít jako návratová na odchozích e-mailech.
+Sem budou zasílány nedoruÄitelné zprávy.
+Mnoho mailových serverů vyžaduje, aby byla pÅ™inejmenším Äást s doménovým jménem platná.',
+ 'config-upload-settings' => 'Obrázky a naÄítání souborů',
+ 'config-upload-enable' => 'Povolit naÄítání souborů',
+ 'config-upload-help' => 'NaÄítání souborů potenciálnÄ› vystavuje váš server bezpeÄnostním rizikům.
+Více informací naleznete v [//www.mediawiki.org/wiki/Manual:Security Äásti o bezpeÄnosti] v příruÄce.
+
+Pro umožnÄ›ní naÄítání souborů změňte práva na podadresáři <code>images</code> pod koÅ™enovým adresářem MediaWiki, aby do nÄ›j mohl webový server zapisovat.
+Poté zapněte tuto volbu.',
+ 'config-upload-deleted' => 'Adresář pro smazané soubory:',
+ 'config-upload-deleted-help' => 'Zvolte adresář, do kterého se mají archivovat smazané soubory.
+Tento adresář by ideálně neměl být dostupný z webu.',
+ 'config-logo' => 'URL loga:',
+ 'config-logo-help' => 'Základní vzhled MediaWiki zahrnuje místo pro logo o velikosti 135×160 pixelů nad boÄním menu.
+NaÄtÄ›te obrázek odpovídající velikosti a zadejte sem jeho URL.
+
+Pokud logo nechcete, ponechte toto pole prázdné.',
+ 'config-instantcommons' => 'Zapnout Instant Commons',
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] je funkce, která umožňuje wiki používat obrázky, zvuky a další mediální soubory ze serveru [//commons.wikimedia.org/wiki/Hlavn%C3%AD_strana Wikimedia Commons].
+Aby to bylo možné, potřebuje mít MediaWiki přístup k internetu.
+
+Více informací o této funkci, vÄetnÄ› instrukcí, jak ji nastavit pro jiné wiki než Wikimedia Commons, najdete v [//mediawiki.org/wiki/Manual:$wgForeignFileRepos příruÄce].',
+ 'config-cc-error' => 'VoliÄ licence Creative Commons nevrátil žádný výsledek.
+Zadejte název licence ruÄnÄ›.',
+ 'config-cc-again' => 'Zvolit znovu…',
+ 'config-cc-not-chosen' => 'Zvolte si požadovanou licenci Creative Commons a kliknÄ›te na tlaÄítko.',
+ 'config-advanced-settings' => 'PokroÄilá konfigurace',
+ 'config-cache-options' => 'Nastavení cachování objektů:',
+ 'config-cache-help' => 'Cachování objektů se používá pro vylepÅ¡ení rychlosti MediaWiki tím, že se cachují Äasto používaná data.
+StÅ™edním až velkým serverům se jeho zapnutí důraznÄ› doporuÄuje, i menší servery pocítí jeho výhody.',
+ 'config-cache-none' => 'Bez cachování (o žádnou funkcionalitu nepřijdete, na větších wiki však může dojít ke zhoršení rychlosti)',
+ 'config-cache-accel' => 'Cachování PHP objektů (APC, XCache nebo WinCache)',
+ 'config-cache-memcached' => 'Použít Memcached (vyžaduje další nastavení a konfiguraci)',
+ 'config-memcached-servers' => 'Servery Memcached:',
+ 'config-memcached-help' => 'Seznam IP adres, které se mají používat pro Memcached.
+UveÄte jednu na řádek spolu s portem. Například:
+ 127.0.0.1:11211
+ 192.168.1.25:1234',
+ 'config-memcache-needservers' => 'Jako typ cache jste zvolili Memcached, ale neuvedli jste žádné servery.',
+ 'config-memcache-badip' => 'Zadali jste neplatnou IP adresu pro Memcached: $1.',
+ 'config-memcache-noport' => 'Nezadali jste port serveru Memcached: $1.
+Pokud port neznáte, implicitní je 11211.',
+ 'config-memcache-badport' => 'Čísla portů pro Memcached by měla být mezi $1 a $2.',
+ 'config-extensions' => 'Rozšíření',
+ 'config-extensions-help' => 'Výše uvedená rozšíření byla nalezena ve vašem adresáři <code>./extensions</code>.
+
+Mohou vyžadovat dodateÄnou konfiguraci, ale teÄ je můžete povolit.',
+ 'config-install-alreadydone' => "'''UpozornÄ›ní:''' Vypadá to, že jste MediaWiki již nainstalovali a teÄ se o to pokoušíte znovu.
+PokraÄujte na další stránku.",
+ 'config-install-begin' => 'Stisknutím „{{int:config-continue}}“ spustíte instalaci MediaWiki.
+Pokud jeÅ¡tÄ› chcete udÄ›lat nÄ›jaké zmÄ›ny, stisknÄ›te tlaÄítko zpÄ›t.',
+ 'config-install-step-done' => 'hotovo',
'config-install-step-failed' => 'selhaly',
+ 'config-install-extensions' => 'Vkládají se rozšíření',
+ 'config-install-database' => 'Připravuje se databáze',
+ 'config-install-schema' => 'Vytváří se schéma',
+ 'config-install-pg-schema-not-exist' => 'Schéma PostgreSQL neexistuje.',
+ 'config-install-pg-schema-failed' => 'Založení tabulek se nezdařilo.
+Ujistěte se, že uživatel „$1“ může zapisovat do schématu „$2“.',
+ 'config-install-pg-commit' => 'Potvrzují se změny',
+ 'config-install-pg-plpgsql' => 'Kontroluje se jazyk PL/pgSQL',
+ 'config-pg-no-plpgsql' => 'Musíte do databáze $1 nainstalovat jazyk PL/pgSQL',
+ 'config-pg-no-create-privs' => 'ÚÄet zadaný pro instalaci nemá oprávnÄ›ní k založení uživatelského úÄtu.',
+ 'config-pg-not-in-role' => 'ÚÄet zadaný pro webového uživatele již existuje
+ÚÄet zadaný pro instalaci není superuživatelský a není Älenem role webového uživatele, takže nemůže zakládat objekty vlastnÄ›né webovým uživatelem.
+
+MediaWiki v souÄasné dobÄ› vyžaduje, aby byl vlastníkem tabulek webový uživatel. UveÄte jiný název úÄtu webového uživatele nebo kliknÄ›te na „zpÄ›t“ a zadejte instalaÄního uživatele s odpovídajícími oprávnÄ›ními.',
+ 'config-install-user' => 'Vytváří se databázový uživatel',
+ 'config-install-user-alreadyexists' => 'Uživatel „$1“ už existuje',
+ 'config-install-user-create-failed' => 'Vytváření uživatele „$1“ selhalo: $2',
+ 'config-install-user-grant-failed' => 'Uživateli „$1“ se nepodařilo přidělit oprávnění: $2',
+ 'config-install-user-missing' => 'Zadaný uživatel „$1“ neexistuje.',
+ 'config-install-user-missing-create' => 'Zadaný uživatel „$1“ neexistuje.
+Pokud ho chcete založit, zaÅ¡krtnÄ›te možnost „založit úÄet“ níže.',
+ 'config-install-tables' => 'Vytvářejí se tabulky',
+ 'config-install-tables-exist' => "'''Upozornění''': Vypadá to, že tabulky MediaWiki již existují.
+Přeskakuje se jejich zakládání.",
+ 'config-install-tables-failed' => "'''Chyba''': Vytvoření tabulek selhalo s následující chybou: $1",
+ 'config-install-interwiki' => 'Tabulka interwiki se plní implicitními položkami',
+ 'config-install-interwiki-list' => 'Nelze pÅ™eÄíst soubor <code>interwiki.list</code>.',
+ 'config-install-interwiki-exists' => "'''Upozornění''': Vypadá to, že tabulka interwiki již obsahuje nějaké záznamy.
+Přeskakuje se implicitní seznam.",
+ 'config-install-stats' => 'Inicializují se statistiky',
+ 'config-install-keys' => 'Vytvářejí se tajné klíÄe',
+ 'config-insecure-keys' => "'''UpozornÄ›ní:''' {{PLURAL:$2|Tajný klíÄ|Tajné klíÄe}} ($1) vytvoÅ™ené v průbÄ›hu instalace {{PLURAL:$2|není|nejsou}} zcela {{PLURAL:$2|bezpeÄný|bezpeÄné}}. Zvažte {{PLURAL:$2|jeho|jejich}} ruÄní zmÄ›nu.",
+ 'config-install-sysop' => 'Zakládá se uživatelský úÄet správce',
+ 'config-install-subscribe-fail' => 'Nelze se přihlásit k odběru mediawiki-announce: $1',
+ 'config-install-subscribe-notpossible' => 'Není nainstalován cURL a není dostupné allow_url_fopen.',
+ 'config-install-mainpage' => 'Vytváří se poÄáteÄní obsah hlavní strany',
+ 'config-install-extension-tables' => 'Vytvářejí se tabulky pro zapnutá rozšíření',
+ 'config-install-mainpage-failed' => 'Nepodařilo se vložit hlavní stranu: $1',
+ 'config-install-done' => "'''Gratulujeme!'''
+Úspěšně jste nainstalovali MediaWiki.
+
+Instalátor vytvořil soubor <code>LocalSettings.php</code>.
+Ten obsahuje veškerou vaši konfiguraci.
+
+Budete si ho muset stáhnout a uložit do základního adresáře vaší instalace wiki (do stejného adresáře jako soubor index.php). Stažení souboru se mělo spustit automaticky.
+
+Pokud se vám stažení nenabídlo nebo jste ho zrušili, můžete ho spustit znovu kliknutím na následující odkaz:
+
+$3
+
+'''Poznámka''': Pokud to neudÄ›láte hned, tento vygenerovaný konfiguraÄní soubor nebude pozdÄ›ji dostupný, pokud instalaci opustíte, aniž byste si ho stáhli.
+
+Až to dokonÄíte, můžete '''[$2 vstoupit do své wiki]'''.",
+ 'config-download-localsettings' => 'Stáhnout LocalSettings.php',
+ 'config-help' => 'nápověda',
'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.
@@ -3038,10 +3572,17 @@ $messages['cy'] = array(
* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Rhestr postio datganiadau MediaWiki]',
);
-/** Danish (Dansk) */
+/** Danish (Dansk)
+ * @author Peter Alberti
+ */
$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.',
+ 'mainpagedocfooter' => 'Se [//meta.wikimedia.org/wiki/Help:Contents brugervejledningen] for oplysninger om brugen af wikiprogrammellet.
+
+== At komme i gang ==
+* [//www.mediawiki.org/wiki/Manual:Configuration_settings Listen over opsætningsmuligheder]
+* [//www.mediawiki.org/wiki/Manual:FAQ MediaWiki ofte stillede spørgsmål]
+* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Postliste angående udgivelser af MediaWiki]',
);
/** German (Deutsch)
@@ -3082,14 +3623,14 @@ Die Datei <code>php.ini</code> muss geprüft und es muss dabei sichergestellt we
'config-your-language' => 'Sprache:',
'config-your-language-help' => 'Bitte die Sprache auswählen, die während des Installationsvorgangs verwendet werden soll.',
'config-wiki-language' => 'Sprache des Wikis:',
- 'config-wiki-language-help' => 'Bitte die Hauptbearbeitungssprache des Wikis auswählen',
+ 'config-wiki-language-help' => 'Bitte die Hauptbearbeitungssprache des Wikis auswählen.',
'config-back' => '↠Zurück',
'config-continue' => 'Weiter →',
'config-page-language' => 'Sprache',
'config-page-welcome' => 'Willkommen bei MediaWiki!',
'config-page-dbconnect' => 'Mit der Datenbank verbinden',
'config-page-upgrade' => 'Eine vorhandene Installation aktualisieren',
- 'config-page-dbsettings' => 'Datenbankeinstellungen',
+ 'config-page-dbsettings' => 'Einstellungen zur Datenbank',
'config-page-name' => 'Name',
'config-page-options' => 'Optionen',
'config-page-install' => 'Installieren',
@@ -3114,10 +3655,10 @@ Dieses Programm ist freie Software, d. h. es kann, gemäß den Bedingungen der v
Dieses Programm wird in der Hoffnung verteilt, dass es nützlich sein wird, allerdings '''ohne jegliche Garantie''' und sogar ohne die implizierte Garantie einer '''Marktgängigkeit''' oder '''Eignung für einen bestimmten Zweck'''. Hierzu sind weitere Hinweise in der ''GNU General Public License'' enthalten.
Eine <doclink href=Copying>Kopie der ''GNU General Public License''</doclink> sollte zusammen mit diesem Programm verteilt worden sein. Sofern dies nicht der Fall war, kann eine Kopie bei der Free Software Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA, schriftlich angefordert oder auf deren Website [http://www.gnu.org/copyleft/gpl.html online gelesen] werden.",
- 'config-sidebar' => '* [//www.mediawiki.org 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]
+ 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/de Website von MediaWiki]
+* [//www.mediawiki.org/wiki/Help:Contents/de Benutzeranleitung]
+* [//www.mediawiki.org/wiki/Manual:Contents/de Administratorenanleitung]
+* [//www.mediawiki.org/wiki/Manual:FAQ/de Häufig gestellte Fragen]
----
* <doclink href=Readme>Lies mich</doclink>
* <doclink href=ReleaseNotes>Versionsinformationen</doclink>
@@ -3142,20 +3683,21 @@ 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 [//sqlite.org/fts3.html FTS3-Modul] kompiliert, so dass keine Suchfunktionen zur Verfügung stehen.",
+ 'config-outdated-sqlite' => "'''Warnung:''' SQLite $1 ist installiert. Allerdings benötigt MediaWiki SQLite $2 oder höher. SQLite wird daher nicht verfügbar sein.",
+ 'config-no-fts3' => "'''Warnung:''' SQLite wurde ohne das [//sqlite.org/fts3.html FTS3-Modul] kompiliert, so dass keine Suchfunktionen zur Verfügung stehen werden.",
'config-register-globals' => "'''Warnung: Der Parameter <code>[http://php.net/register_globals register_globals]</code> von PHP ist aktiviert.'''
'''Sie sollte deaktiviert werden, sofern dies möglich ist.'''
Die MediaWiki-Installation wird zwar laufen, wobei aber der Server für potentielle Sicherheitsprobleme anfällig ist.",
- 'config-magic-quotes-runtime' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]</code> von PHP ist aktiviert!'''
+ 'config-magic-quotes-runtime' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/de/function.set-magic-quotes-runtime.php set_magic_quotes_runtime]</code> von PHP ist aktiviert!'''
Diese Einstellung führt zu unvorhersehbaren Problemen bei der Dateneingabe.
MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
- 'config-magic-quotes-sybase' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> von PHP ist aktiviert!'''
+ 'config-magic-quotes-sybase' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/de/sybase.configuration.php#ini.magic-quotes-sybase magic_quotes_sybase]</code> von PHP ist aktiviert!'''
Diese Einstellung führt zu unvorhersehbaren Problemen bei der Dateneingabe.
MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
'config-mbstring' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]</code> von PHP ist aktiviert!'''
Diese Einstellung verursacht Fehler und führt zu unvorhersehbaren Problemen bei der Dateneingabe.
MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
- 'config-ze1' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]</code> von PHP ist aktiviert!'''
+ 'config-ze1' => "'''Fataler Fehler: Der Parameter <code>[http://www.php.net/manual/de/ini.core.php zend.ze1_compatibility_mode]</code> von PHP ist aktiviert!'''
Diese Einstellung führt zu großen Fehlern bei MediaWiki.
MediaWiki kann nicht installiert werden, solange dieser Parameter nicht deaktiviert wurde.",
'config-safe-mode' => "'''Warnung:''' Der Funktion <code>[http://www.php.net/features.safe-mode Safe Mode]</code> von PHP ist aktiviert.
@@ -3165,18 +3707,18 @@ MediaWiki benötigt Funktionen, die dieses Modul bereitstellt und wird in der be
Sofern Mandriva genutzt wird, muss noch das „php-xml“-Paket installiert werden.',
'config-pcre' => 'Das PHP-Modul für die PCRE-Unterstützung wurde nicht gefunden.
MediaWiki benötigt allerdings perl-kompatible reguläre Ausdrücke, um lauffähig zu sein.',
- 'config-pcre-no-utf8' => "'''Fataler Fehler: Das PHP-Modul PCRE scheint ohne PCRE_UTF8-Unterstützung kompiliert worden zu sein.'''
+ 'config-pcre-no-utf8' => "'''Fataler Fehler:''' Das PHP-Modul PCRE scheint ohne PCRE_UTF8-Unterstützung kompiliert worden zu sein.
MediaWiki benötigt die UTF-8-Unterstützung, um fehlerfrei lauffähig zu sein.",
'config-memory-raised' => 'Der PHP-Parameter <code>memory_limit</code> betrug $1 und wurde auf $2 erhöht.',
'config-memory-bad' => "'''Warnung:''' Der PHP-Parameter <code>memory_limit</code> beträgt $1.
Dieser Wert ist wahrscheinlich zu niedrig.
Der Installationsvorgang könnte daher scheitern!",
+ 'config-ctype' => "'''Fataler Fehler:''' PHP muss mit Unterstützung für das [http://www.php.net/manual/de/ctype.installation.php Modul ctype] kompiliert werden.",
'config-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://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] konnten nicht gefunden werden.
-Das Objektcaching ist daher nicht aktiviert.",
+ 'config-no-cache' => "'''Warnung:''' [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 kann daher nicht aktiviert werden.",
'config-mod-security' => "'''Warnung:''' Auf dem Webserver wurde [http://modsecurity.org/ ModSecurity] aktiviert. Sofern falsch konfiguriert, kann dies zu Problemen mit MediaWiki sowie anderer Software auf dem Server führen und es Benutzern ermöglichen beliebige Inhalte im Wiki einzustellen.
Für weitere Informationen empfehlen wir die [http://modsecurity.org/documentation/ Dokumentation zu ModSecurity] oder den Kontakt zum Hoster, sofern Fehler auftreten.",
'config-diff3-bad' => 'GNU diff3 wurde nicht gefunden.',
@@ -3193,13 +3735,13 @@ Der Installationsvorgang wurde daher abgebrochen.",
'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 [//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-no-cli-uploads-check' => "'''Warnung''': Das Standardverzeichnis für hochgeladene Dateien (<code>$1</code>) wird, während der Installation über die Kommandozeile, nicht auf Sicherheitsanfälligkeiten hinsichtlich willkürlicher Skriptausführungen geprüft.",
'config-brokenlibxml' => 'Das System nutzt eine Kombination aus PHP- und libxml2-Versionen, die fehleranfällig ist und versteckte Datenfehler bei MediaWiki und anderen Webanwendungen verursachen kann.
PHP muss auf Version 5.2.9 oder später sowie libxml2 auf die Version 2.7.3 oder später aktualisiert werden, um das Problem zu lösen. Installationsabbruch ([//bugs.php.net/bug.php?id=45996 siehe hierzu die Fehlermeldung bei PHP]).',
'config-using531' => 'MediaWiki kann nicht zusammen mit PHP $1 verwendet werden. Grund hierfür ist ein Fehler im Zusammenhang mit den Verweisparametern zu <code>__call()</code>.
PHP muss auf Version 5.3.2 oder höher oder 5.3.0 oder niedriger aktualisiert werden, um das Problem zu beheben.
Die Installation wurde abgebrochen.',
- 'config-suhosin-max-value-length' => 'Suhosin ist installiert, 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-suhosin-max-value-length' => 'Suhosin ist installiert und beschränkt die Länge des GET-Parameters auf $1 Bytes. Der ResouceLoader von MediaWiki wird zwar unter diesen Bedingungen funktionieren, allerdings nur mit verminderter Leistungsfähigkeit. Sofern möglich sollte der Parameter <code>suhosin.get.max_value_length</code> in der Datei php.ini auf 1024 oder höher festgelegt werden. Gleichzeitig muss der Parameter <code>$wgResourceLoaderMaxQueryLength</code> 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.
@@ -3208,7 +3750,7 @@ Sofern ein gemeinschaftlich genutzter Server verwendet wird, sollte der Hoster d
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.',
+Sofern PostgresQL genutzt wird, muss dieses Feld leer gelassen werden, um über ein Unix-Socket zu verbinden.',
'config-db-host-oracle' => 'Datenbank-TNS:',
'config-db-host-oracle-help' => 'Einen gültigen [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm „Local Connect“-Namen] angeben. Die „tnsnames.ora“-Datei muss von dieser Installation erkannt werden können.<br />Sofern die Client-Bibliotheken für Version 10g oder neuer verwendet werden, kann auch [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm „Easy Connect“] zur Namensgebung genutzt werden.',
'config-db-wiki-settings' => 'Bitte Daten zur eindeutigen Identifikation dieses Wikis angeben',
@@ -3248,8 +3790,8 @@ 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 [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
+Im '''UTF-8-Modus''' wird MySQL den Zeichensatz der Daten erkennen und sie richtig anzeigen und konvertieren.
+Es können allerdings keine Zeichen außerhalb des [//de.wikipedia.org/wiki/Basic_Multilingual_Plane#Gliederung_in_Ebenen_und_Bl.C3.B6cke ''Basic Multilingual Plane'' (BMP)] gespeichert werden.",
'config-mysql-old' => 'MySQL $1 oder höher wird benötigt. MySQL $2 ist momentan vorhanden.',
'config-db-port' => 'Datenbankport:',
'config-db-schema' => 'Datenschema für MediaWiki',
@@ -3356,10 +3898,10 @@ Das Wiki kann nun [$1 genutzt werden].',
'config-unknown-collation' => "'''Warnung:''' Die Datenbank nutzt eine unbekannte Kollation.",
'config-db-web-account' => 'Datenbankkonto für den Webzugriff',
'config-db-web-help' => 'Bitte Benutzernamen und Passwort auswählen, die der Webserver während des Normalbetriebes dazu verwenden soll, eine Verbindung zum Datenbankserver herzustellen.',
- 'config-db-web-account-same' => 'Dasselbe Konto wie während des Installationsvorgangs verwenden',
+ 'config-db-web-account-same' => 'Dasselbe Datenbankkonto wie während des Installationsvorgangs verwenden',
'config-db-web-create' => 'Sofern nicht bereits vorhanden, muss nun das Konto erstellt werden',
- 'config-db-web-no-create-privs' => 'Das angegebene und für den Installationsvorgang vorgesehene Konto verfügt nicht über ausreichend Berechtigungen, um ein Konto zu erstellen.
-Das hier angegebene Konto muss bereits vorhanden sein.',
+ 'config-db-web-no-create-privs' => 'Das angegebene und für den Installationsvorgang vorgesehene Datenbankkonto verfügt nicht über ausreichend Berechtigungen, um ein weiteres Datenbankkonto zu erstellen.
+Das hier angegebene Datenbankkonto muss daher bereits vorhanden sein.',
'config-mysql-engine' => 'Speicher-Engine:',
'config-mysql-innodb' => 'InnoDB',
'config-mysql-myisam' => 'MyISAM',
@@ -3421,7 +3963,7 @@ Diese Mailingliste sollte abonniert werden. Zudem sollte die MediaWiki-Installat
'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.',
+Die verbleibenden Konfigurationseinstellungen können übersprungen und das Wiki umgehend installiert werden.',
'config-optional-continue' => 'Ja, es sollen weitere Konfigurationseinstellungen vorgenommen werden.',
'config-optional-skip' => 'Nein, das Wiki soll nun installiert werden.',
'config-profile' => 'Profil der Benutzerberechtigungen:',
@@ -3435,7 +3977,7 @@ Mit MediaWiki ist es einfach die letzten Änderungen nachzuvollziehen und unbrau
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, 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.
+Ein Wiki bei dem die '''{{int:config-profile-no-anon}}''' ist, fordert von den Benutzern eine höhere Verantwortung für ihre Bearbeitungen ein, könnte allerdings Personen abschrecken, die nur gelegentlich Bearbeitungen vornehmen wollen. Ein Wiki für '''{{int:config-profile-fishbowl}}''' gestattet es nur bestimmten Benutzern, Bearbeitungen vorzunehmen. Allerdings kann dabei die Allgemeinheit die Seiten immer noch betrachten und Änderungen nachvollziehen. Ein '''{{int:config-profile-private}}''' gestattet es nur ausgewählten Benutzern, Seiten zu betrachten sowie zu bearbeiten.
Komplexere Konzepte zur Zugriffssteuerung können erst nach abgeschlossenem Installationsvorgang eingerichtet werden. Hierzu gibt es weitere Informationen auf der Website mit der [//www.mediawiki.org/wiki/Manual:User_rights entsprechenden Anleitung].",
'config-license' => 'Lizenz:',
@@ -3477,9 +4019,9 @@ 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 [//www.mediawiki.org/wiki/Manual:Security Abschnitt Sicherheit] der Anleitung gelesen werden.
+Weitere Informationen hierzu können im [//www.mediawiki.org/wiki/Manual:Security Abschnitt Sicherheit] der Anleitung nachgelesen werden.
-Um das Hochladen von Dateien zu ermöglichen, muss der Zugriff auf das Unterverzeichnis <code>./images</code> so geändert werden, das es für den Webserver beschreibbar ist.
+Um das Hochladen von Dateien zu ermöglichen, muss der Zugriff auf das Unterverzeichnis <code>./images</code> so geändert werden, das dieses für den Webserver beschreibbar ist.
Hernach kann diese Option aktiviert werden.',
'config-upload-deleted' => 'Verzeichnis für gelöschte Dateien:',
'config-upload-deleted-help' => 'Bitte ein Verzeichnis auswählen, in dem gelöschte Dateien archiviert werden sollen.
@@ -3491,25 +4033,25 @@ Bitte ein Logo in entsprechender Größe hochladen und die zugehörige URL an di
Sofern kein Logo benötigt wird, kann dieses Datenfeld leer bleiben.',
'config-instantcommons' => '„InstantCommons“ aktivieren',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons InstantCommons] ist eine Funktion, die es Wikis ermöglicht, Bild-, Klang- und andere Mediendateien zu nutzen, die auf der Website [//commons.wikimedia.org/ Wikimedia Commons] verfügbar sind.
-Um diese Funktion zu nutzen, muss MediaWiki eine Verbindung ins Internet herstellen können.
+Um diese Funktion nutzen zu können, muss das Wiki über eine Verbindung zum Internet verfügen.
-Weitere Informationen zu dieser Funktion, einschließlich der Anleitung, wie andere Wikis als Wikimedia Commons eingerichtet werden können, gibt es im [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos Handbuch].',
+Weitere Informationen zu dieser Funktion, einschließlich der Anleitung, wie hierfür andere Wikis als Wikimedia Commons eingerichtet werden können, gibt es im [//mediawiki.org/wiki/Manual:$wgForeignFileRepos Handbuch].',
'config-cc-error' => 'Der Creativ-Commons-Lizenzassistent konnte keine Lizenz ermitteln.
Die Lizenz ist daher jetzt manuell einzugeben.',
'config-cc-again' => 'Erneut auswählen …',
'config-cc-not-chosen' => 'Die gewünschte Creative-Commons-Lizenz auswählen und dann auf „weiter“ klicken.',
'config-advanced-settings' => 'Erweiterte Konfiguration',
'config-cache-options' => 'Einstellungen für die Zwischenspeicherung von Objekten:',
- 'config-cache-help' => 'Objektcaching wird dazu genutzt die Geschwindigkeit von MediaWiki zu verbessern, indem häufig genutzte Daten zwischengespeichert werden.
-Mittelgroße bis große Wikis werden sehr ermutigt dies zu nutzen, aber auch für kleine Wikis ergeben sich erkennbare Vorteile.',
- 'config-cache-none' => 'Kein Objektcaching (es wird keine Funktion entfernt, allerdings kann die Geschwindigkeit größerer Wikis beeinflusst werden)',
- 'config-cache-accel' => 'Objektcaching von PHP (APC, eAccelerator, XCache or WinCache)',
+ 'config-cache-help' => 'Das Objektcaching wird dazu genutzt die Geschwindigkeit von MediaWiki zu verbessern, indem häufig genutzte Daten zwischengespeichert werden.
+Es wird sehr empfohlen es für mittelgroße bis große Wikis zu nutzen, aber auch für kleine Wikis ergeben sich erkennbare Geschwindigkeitsverbesserungen.',
+ 'config-cache-none' => 'Kein Objektcaching (es wird keine Funktion entfernt, allerdings kann dies die Geschwindigkeit größerer Wikis negativ beeinflussen)',
+ 'config-cache-accel' => 'Objektcaching von PHP (APC, XCache oder WinCache)',
'config-cache-memcached' => 'Memchached Cacheserver nutzen (erfordert einen zusätzliche Installationsvorgang mitsamt Konfiguration)',
'config-memcached-servers' => 'Memcached Cacheserver',
'config-memcached-help' => 'Liste der für Memcached nutzbaren IP-Adressen.
Es sollte eine je Zeile mitsamt des vorgesehenen Ports angegeben werden. Beispiele:
-127.0.0.1:11211
-192.168.1.25:1234',
+127.0.0.1:11211 oder
+192.168.1.25:1234 usw.',
'config-memcache-needservers' => 'Memcached wurde als Cacheserver ausgewählt. Dabei wurde allerdings kein Server angegeben.',
'config-memcache-badip' => 'Es wurde für Memcached eine ungültige IP-Adresse angegeben: $1',
'config-memcache-noport' => 'Es wurde kein Port zur Nutzung durch den Memcached Cacheserver angegeben: $1
@@ -3518,14 +4060,14 @@ Sofern der Port unbekannt ist, ist 11211 die Standardangabe.',
'config-extensions' => 'Erweiterungen',
'config-extensions-help' => 'Die obig angegebenen Erweiterungen wurden im Verzeichnis <code>./extensions</code> gefunden.
-Sie könnten zusätzliche Konfigurierung erfordern, können aber bereits jetzt aktiviert werden.',
+Es könnten zusätzliche Konfigurierungen zu einzelnen Erweiterungen erforderlich sein, dennoch können sie aber bereits jetzt aktiviert werden.',
'config-install-alreadydone' => "'''Warnung:''' Es wurde eine vorhandene MediaWiki-Installation gefunden.
Es muss daher mit den nächsten Seite weitergemacht werden.",
'config-install-begin' => 'Durch Drücken von „{{int:config-continue}}“ wird die Installation von MediaWiki gestartet.
Sofern Änderungen vorgenommen werden sollen, kann man auf „↠Zurück“ klicken.',
'config-install-step-done' => 'erledigt',
'config-install-step-failed' => 'gescheitert',
- 'config-install-extensions' => 'Einschließlich Erweiterungen',
+ 'config-install-extensions' => 'Softwareerweiterungen',
'config-install-database' => 'Datenbank wird eingerichtet',
'config-install-schema' => 'Datenschema wird erstellt',
'config-install-pg-schema-not-exist' => 'Das PostgesSQL-Datenschema ist nicht vorhanden',
@@ -3535,10 +4077,10 @@ Es muss sichergestellt sein, dass der Benutzer „$1“ Schreibzugriff auf das D
'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.
+ 'config-pg-not-in-role' => 'Das für den Webbenutzer angegebene Benutzerkonto ist bereits vorhanden.
+Das für den Installationsvorgang angegebene Benutzerkonto ist kein Superbenutzer und nicht Mitglied der Benutzergruppe der Webbenutzer, so dass keine dem Webbenutzer zugeordneten Datenobjekte erstellt werden können.
-Für MediaWiki ist es momentan erforderlich, dass die Tabellen dem 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.',
+Für MediaWiki ist es momentan erforderlich, dass die Tabellen dem Webbenutzer rechtemäßig zugeordnet sind. Bitte einen anderen Namen für den Wikibenutzer angeben oder „↠Zurück“ anklicken, um einen ausreichend berechtigten Benutzer für den Installationsvorgang anzugeben.',
'config-install-user' => 'Datenbankbenutzer wird erstellt',
'config-install-user-alreadyexists' => 'Datenbankbenutzer „$1“ ist bereits vorhanden',
'config-install-user-create-failed' => 'Das Anlegen des Datenbankbenutzers „$1“ ist gescheitert: $2',
@@ -3551,12 +4093,12 @@ Bitte das Auswahlkästchen „Benutzerkonto erstellen“ anklicken, sofern diese
Die Erstellung wurde übersprungen.",
'config-install-tables-failed' => "'''Fehler:''' Die Erstellung der Datentabellen ist aufgrund des folgenden Fehlers gescheitert: $1",
'config-install-interwiki' => 'Interwikitabellen werden eingerichtet',
- 'config-install-interwiki-list' => 'Die Datei <code>interwiki.list</code> konnte nicht gefunden werden.',
+ 'config-install-interwiki-list' => 'Die Datei <code>interwiki.list</code> konnte nicht gelesen werden.',
'config-install-interwiki-exists' => "'''Warnung:''' Es wurden Interwikitabellen mit Daten gefunden.
Die Standardliste wird übersprungen.",
'config-install-stats' => 'Statistiken werden initialisiert',
'config-install-keys' => 'Geheimschlüssel werden erstellt',
- 'config-insecure-keys' => "'''Warnung:''' {{PLURAL:$2|Der Geheimschlüssel|Die Geheimschlüssel}} $1 {{PLURAL:$2|der|die}} während des Installationsvorgangs generiert wurde, ist nicht sehr sicher. {{PLURAL:$2|Er sollte|Sie sollten}} manuell geändert werden.",
+ 'config-insecure-keys' => "'''Warnung:''' {{PLURAL:$2|Der Geheimschlüssel|Die Geheimschlüssel}} $1, {{PLURAL:$2|der|die}} während des Installationsvorgangs generiert {{PLURAL:$2|wurde, ist|wurden, sind}} nicht sehr sicher. {{PLURAL:$2|Er sollte|Sie sollten}} manuell geändert werden.",
'config-install-sysop' => 'Administratorkonto wird erstellt',
'config-install-subscribe-fail' => 'Abonnieren von „mediawiki-announce“ ist gescheitert: $1',
'config-install-subscribe-notpossible' => 'cURL ist nicht installiert und allow_url_fopen ist nicht verfügbar.',
@@ -3567,7 +4109,7 @@ Die Standardliste wird übersprungen.",
MediaWiki wurde erfolgreich installiert.
Das Installationsprogramm hat die Datei <code>LocalSettings.php</code> erzeugt.
-Sie enthält alle Konfigurationseinstellungen.
+Sie enthält alle vorgenommenen Konfigurationseinstellungen.
Diese Datei muss nun heruntergeladen und anschließend in das Stammverzeichnis der MediaWiki-Installation hochgeladen werden. Dies ist dasselbe Verzeichnis, in dem sich auch die Datei <code>index.php</code> befindet. Das Herunterladen sollte inzwischen automatisch gestartet worden sein.
@@ -3575,9 +4117,9 @@ Sofern dies nicht der Fall war, oder das Herunterladen unterbrochen wurde, kann
$3
-'''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.
+'''Hinweis:''' Die Konfigurationsdatei sollte jetzt unbedingt heruntergeladen werden. Sie wird nach Beenden des Installationsprogramms, nicht mehr zur Verfügung stehen.
-Sobald alles erledigt wurde, kann auf das '''[$2 Wiki zugegriffen werden]'''. Wir wünschen viel Spaß mit dem Wiki.",
+Sobald alles erledigt wurde, kann auf das '''[$2 Wiki zugegriffen werden]'''. Wir wünschen viel Spaß und Erfolg mit dem Wiki.",
'config-download-localsettings' => 'LocalSettings.php herunterladen',
'config-help' => 'Hilfe',
'mainpagetext' => "'''MediaWiki wurde erfolgreich installiert.'''",
@@ -3782,9 +4324,8 @@ Probablemente este valor es demasiado bajo.
¡La instalación podrá fallar!",
'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://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Advertencia:''' No pudo encontrarse [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
El caché de objetos no está habilitado.",
'config-diff3-bad' => 'GNU diff3 no se encuentra.',
'config-imagemagick' => 'ImageMagick encontrado: <code>$1</code>.
@@ -3956,7 +4497,7 @@ Escribe el nombre de la licencia manualmente.',
'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-accel' => 'Almacenamiento en caché de objetos PHP (APC, XCache o WinCache)',
'config-cache-memcached' => 'Utilizar Memcached (necesita ser instalado y configurado aparte)',
'config-memcached-servers' => 'Servidores Memcached:',
'config-memcached-help' => 'Lista de direcciones IP que serán usadas por Memcached.
@@ -4157,11 +4698,16 @@ $messages['fi'] = array(
'config-desc' => 'MediaWiki-asennin',
'config-title' => 'MediaWikin version $1 asennus',
'config-information' => 'Tiedot',
- 'config-localsettings-upgrade' => '<code>LocalSettings.php</code>-tiedosto on havaittu.
-Syötä kohdan <code>$wgUpgradeKey</code> arvo alla olevaan kenttään päivittääksesi asennuksen.
+ 'config-localsettings-upgrade' => '<code>LocalSettings.php</code>-tiedosto havaittiin.
+Kirjoita muuttujan <code>$wgUpgradeKey</code> arvo alla olevaan kenttään päivittääksesi asennuksen.
Löydät sen LocalSettings.php-tiedostosta.',
+ 'config-localsettings-cli-upgrade' => 'LocalSettings.php-tiedosto havaittiin.
+Päivitä asennus suorittamalla update.php.',
'config-localsettings-key' => 'Päivitysavain',
'config-localsettings-badkey' => 'Antamasi avain on virheellinen.',
+ 'config-localsettings-incomplete' => 'Nykyinen LocalSettings.php-tiedosto näyttää olevan puutteellinen.
+Muuttujaa $1 ei ole asetettu.
+Muuta LocalSettings.php-tiedostoa siten, että muuttuja on asetettu ja napsauta »Jatka».',
'config-session-error' => 'Istunnon aloittaminen epäonnistui: $1',
'config-session-expired' => 'Istuntotietosi näyttävät olevan vanhentuneita.
Istuntojen elinajaksi on määritelty $1.
@@ -4207,6 +4753,8 @@ Voit asentaa MediaWikin.',
'config-env-bad' => 'Asennusympäristö on tarkastettu.
Et voi asentaa MediaWikiä.',
'config-env-php' => 'PHP $1 on asennettu.',
+ 'config-env-php-toolow' => 'PHP $1 on asennettu.
+MediaWiki vaatii PHP:n version $2 tai uudemman.',
'config-no-db' => 'Sopivaa tietokanta-ajuria ei löytynyt! Sinun täytyy asentaa tietokanta-ajurit PHP:lle.
Seuraavat tietokantatyypit ovat tuettuja: $1.',
'config-safe-mode' => "'''Varoitus:''' PHP:n [http://www.php.net/features.safe-mode safe mode] -tila on aktiivinen.
@@ -4218,7 +4766,6 @@ 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',
'config-diff3-bad' => 'GNU diff3:a ei löytynyt.',
'config-db-type' => 'Tietokannan tyyppi',
@@ -4232,6 +4779,7 @@ Asennus saattaa epäonnistua!",
'config-charset-mysql5-binary' => 'MySQL 4.1/5.0, binääri',
'config-charset-mysql5' => 'MySQL 4.1/5.0, UTF-8',
'config-charset-mysql4' => 'MySQL 4.0, taaksepäin yhteensopiva UTF-8',
+ 'config-mysql-old' => 'MediaWiki tarvitsee MySQL:n version $1 tai uudemman. Nykyinen versio on $2.',
'config-type-mysql' => 'MySQL',
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
@@ -4254,7 +4802,7 @@ Käytä ainoastaan kirjaimia (a-z, A-Z), numeroita (0-9), alaviivoja (_) ja tavu
Älä käytä välilyöntejä tai viivoja.
Nimeä käytetään SQLite-tietokannan tiedostonimessä.',
'config-sqlite-dir-unwritable' => 'Hakemistoon â€$1†kirjoittaminen epäonnistui.
-Muuta hakemiston käyttöoikeuksia siten, että palvelinohjelmisto voi kirjoittaa siihen ja koita uudelleen.',
+Muuta hakemiston käyttöoikeuksia siten, että palvelinohjelmisto voi kirjoittaa siihen ja yritä uudelleen.',
'config-sqlite-readonly' => 'Tiedostoon <code>$1</code> ei voi kirjoittaa.',
'config-sqlite-fts3-downgrade' => 'PHP:stä puuttuu FTS3-tuki. Poistetaan ominaisuus käytöstä tietokantatauluista.',
'config-upgrade-done' => "Päivitys valmis.
@@ -4274,6 +4822,7 @@ Voit [$1 aloittaa wikin käytön].',
'config-mysql-binary' => 'Binääri',
'config-mysql-utf8' => 'UTF-8',
'config-site-name' => 'Wikin nimi',
+ 'config-site-name-blank' => 'Kirjoita sivuston nimi.',
'config-project-namespace' => 'Projektinimiavaruus',
'config-ns-generic' => 'Projekti',
'config-ns-site-name' => 'Sama kuin wikin nimi: $1',
@@ -4283,17 +4832,21 @@ Voit [$1 aloittaa wikin käytön].',
'config-admin-password-confirm' => 'Salasana uudelleen',
'config-admin-name-blank' => 'Anna ylläpitäjän käyttäjänimi.',
'config-admin-email' => 'Sähköpostiosoite',
+ 'config-admin-error-bademail' => 'Annoit virheellisen sähköpostiosoitteen.',
'config-almost-done' => 'Olet jo lähes valmis!
Voit ohittaa jäljellä olevat määritykset ja asentaa wikin juuri nyt.',
'config-profile-wiki' => 'Perinteinen wiki',
'config-profile-no-anon' => 'Tunnuksen luonti vaaditaan',
'config-profile-private' => 'Yksityinen wiki',
+ 'config-license-pd' => 'Public domain',
'config-email-settings' => 'Sähköpostiasetukset',
'config-logo' => 'Logon URL-osoite',
'config-cc-again' => 'Valitse uudelleen...',
'config-extensions' => 'Laajennukset',
- 'config-install-step-done' => 'tehty',
+ 'config-install-step-done' => 'valmis',
'config-install-step-failed' => 'epäonnistui',
+ 'config-install-user-alreadyexists' => 'Käyttäjä $1 on jo olemassa',
+ 'config-install-interwiki-list' => 'Tiedostoa <code>interwiki.list</code> ei voitu lukea.',
'config-download-localsettings' => 'Lataa LocalSettings.php',
'config-help' => 'ohje',
'mainpagetext' => "'''MediaWiki on onnistuneesti asennettu.'''",
@@ -4307,13 +4860,13 @@ Voit ohittaa jäljellä olevat määritykset ja asentaa wikin juuri nyt.',
=== Asetukset ===
-Tarkista, että alla olevat taivutusmuodot ovat oikein. Jos eivät, tee tarvittavat muutokset LocalSettings.php:hen seuraavasti:
+Tarkista, että alla olevat taivutusmuodot ovat oikein. Jos eivät, tee tarvittavat muutokset tiedostoon LocalSettings.php seuraavasti:
\$wgGrammarForms['fi']['genitive']['{{SITENAME}}'] = '...';
\$wgGrammarForms['fi']['partitive']['{{SITENAME}}'] = '...';
\$wgGrammarForms['fi']['elative']['{{SITENAME}}'] = '...';
\$wgGrammarForms['fi']['inessive']['{{SITENAME}}'] = '...';
\$wgGrammarForms['fi']['illative']['{{SITENAME}}'] = '...';
-Taivutusmuodot: {{GRAMMAR:genitive|{{SITENAME}}}} (yön) — {{GRAMMAR:partitive|{{SITENAME}}}} (yötä) — {{GRAMMAR:elative|{{SITENAME}}}} (yöstä) — {{GRAMMAR:inessive|{{SITENAME}}}} (yössä) — {{GRAMMAR:illative|{{SITENAME}}}} (yöhön).",
+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) */
@@ -4428,6 +4981,7 @@ Vous devriez faire une [//www.mediawiki.org/wiki/Unicode_normalization_considera
Si vous êtes sur un site partagé, demandez à votre hébergeur d'installer un pilote de base de données approprié. Si vous avez compilé PHP, le configurer avec client de base de données activé, par exemple en insérant la directive <code>./configure --with-mysql</code>.
Si vous avez installé PHP d'une distribution Debian ou Ubuntu, vous devez aussi installer le module <code>php5-mysql</code>.",
+ 'config-outdated-sqlite' => "'''Attention''': vous avez SQLite $1, qui est inférieur à la version minimale requise $2. SQLite sera indisponible.",
'config-no-fts3' => "'''Attention :''' SQLite est compilé sans le module [//sqlite.org/fts3.html FTS3] ; les fonctions de recherche ne seront pas disponibles sur ce moteur.",
'config-register-globals' => "'''Attention : l'option <code>[http://php.net/register_globals register_globals]</code> de PHP est activée.'''
'''Désactivez-la si vous le pouvez.'''
@@ -4457,11 +5011,11 @@ MédiaWiki nécessite la gestion d’UTF-8 pour fonctionner correctement.",
'config-memory-bad' => "'''Attention :''' Le paramètre <code>memory_limit</code> de PHP est à $1.
Cette valeur est probablement trop faible.
Il est possible que l’installation échoue !",
+ 'config-ctype' => "'''Fatal ''': PHP doit être compilé avec le support pour l'[http://www.php.net/manual/en/ctype.installation.php extension Ctype].",
'config-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://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Attention :''' Impossible de trouver [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].
La mise en cache d'objets n'est pas activée.",
'config-mod-security' => "'''Attention''': Votre serveur web a [http://modsecurity.org/ mod_security] activé. S&il est mal configuré, cela peut poser des problèmes à MediaWiki ou à d'autres applications qui permettent aux utilisateurs de publier un contenu quelconque.
Reportez-vous à [http://modsecurity.org/documentation/ la documentation de mod_security] ou contactez le support de votre hébergeur si vous rencontrez des erreurs aléatoires.",
@@ -4725,7 +5279,7 @@ Des configurations de droits d’utilisateurs plus complexes sont disponibles ap
'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-by-nc-sa' => 'Creative Commons paternité – non commercial – partage à l’identique',
'config-license-cc-0' => 'Creative Commons Zero (domaine public)',
'config-license-gfdl' => 'GNU Free Documentation License 1.3 ou ultérieure',
'config-license-pd' => 'Domaine public',
@@ -4775,7 +5329,7 @@ Si vous ne voulez pas d'un logo, laissez cette case vide.",
'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).",
+Pour plus d'informations sur ce service, y compris les instructions sur la façon de le configurer pour d'autres wikis que Wikimedia Commons, consultez le [//mediawiki.org/wiki/Manual:\$wgForeignFileRepos manuel] (en anglais).",
'config-cc-error' => "Le sélection d'une licence ''Creative Commons'' n'a donné aucun résultat.
Entrez le nom de la licence manuellement.",
'config-cc-again' => 'Choisissez à nouveau...',
@@ -4785,7 +5339,7 @@ Entrez le nom de la licence manuellement.",
'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-accel' => 'Mise en cache des objets PHP (APC, XCache ou WinCache)',
'config-cache-memcached' => 'Utiliser Memcached (nécessite une installation et une configuration supplémentaires)',
'config-memcached-servers' => 'serveurs pour Memcached :',
'config-memcached-help' => 'Liste des adresses IP à utiliser pour Memcached.
@@ -4914,7 +5468,6 @@ 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 :',
@@ -4923,10 +5476,10 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
'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-install-account' => 'Compto usanciér por l’enstalacion',
+ 'config-db-username' => 'Nom d’usanciér de la bâsa de balyês :',
+ 'config-db-password' => 'Contresegno de la bâsa de balyês :',
+ 'config-db-wiki-account' => 'Compto usanciér por l’opèracion normala',
'config-db-prefix' => 'Prèfixo de les trâbles de la bâsa de balyês :',
'config-db-charset' => 'Juè de caractèros de la bâsa de balyês',
'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binèro',
@@ -4969,16 +5522,16 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
'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-password' => 'Contresegno :',
+ 'config-admin-password-confirm' => 'Tornar buchiér lo contresegno :',
'config-admin-name-blank' => 'Buchiéd un nom d’administrator.',
- 'config-admin-password-blank' => 'Buchiéd un mot de pâssa por lo compto administrator.',
+ 'config-admin-password-blank' => 'Buchiéd un contresegno por lo compto administrator.',
'config-admin-email' => 'Adrèce èlèctronica :',
'config-optional-continue' => 'Mè posar més de quèstions.',
- 'config-profile' => 'Profil des drêts d’utilisator :',
+ 'config-profile' => 'Profil des drêts d’usanciér :',
'config-profile-wiki' => 'Vouiqui tradicionâl',
'config-profile-no-anon' => 'Crèacion de compto nècèssèra',
- 'config-profile-fishbowl' => 'Ren que los èditors ôtorisâs',
+ 'config-profile-fishbowl' => 'Solament los èditors ôtorisâs',
'config-profile-private' => 'Vouiqui privâ',
'config-license' => 'Drêts d’ôtor et licence :',
'config-license-none' => 'Gins de licence d’avâl la pâge',
@@ -4986,13 +5539,13 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
'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-gfdl' => 'Licence de documentacion libra GNU 1.3 ou ben ples novèla',
'config-license-pd' => 'Domêno publico',
'config-license-cc-choose' => 'Chouèsir una licence Creative Commons pèrsonalisâ',
'config-email-settings' => 'Paramètres de mèssageria èlèctronica',
'config-enable-email' => 'Activar los mèssâjos que sôrtont',
- 'config-email-user' => 'Activar los mèssâjos d’utilisator a utilisator',
- 'config-email-usertalk' => 'Activar la notificacion de les pâges de discussion ux utilisators',
+ 'config-email-user' => 'Activar los mèssâjos d’usanciér a usanciér',
+ 'config-email-usertalk' => 'Activar la notificacion de les pâges de discussion ux usanciérs',
'config-email-watchlist' => 'Activar la notificacion de la lista de survelyence',
'config-email-auth' => 'Activar l’ôtenticacion per mèssageria èlèctronica',
'config-email-sender' => 'Adrèce èlèctronica de retôrn :',
@@ -5004,7 +5557,7 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
'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-cache-accel' => 'Misa en cache de les chouses PHP (APC, XCache ou ben WinCache)',
'config-memcached-servers' => 'Sèrvors por memcached :',
'config-extensions' => 'Èxtensions',
'config-install-step-done' => 'fêt',
@@ -5015,10 +5568,10 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
'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-user' => 'Crèacion d’un usanciér de la bâsa de balyês',
+ 'config-install-user-alreadyexists' => 'L’usanciér « $1 » ègziste ja',
+ 'config-install-user-create-failed' => 'Falyita pendent la crèacion de l’usanciér « $1 » : $2',
+ 'config-install-user-grant-failed' => 'Falyita pendent l’aponsa de pèrmissions a l’usanciér « $1 » : $2',
'config-install-tables' => 'Crèacion de les trâbles',
'config-install-interwiki' => 'Remplissâjo per dèfôt de la trâbla des entèrvouiquis',
'config-install-interwiki-list' => 'Empossiblo de trovar lo fichiér <code>interwiki.list</code>.',
@@ -5032,7 +5585,7 @@ Portant, MediaWiki at fôta de PHP $2 ou ben ples hôt.',
'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.
+ 'mainpagedocfooter' => 'Vêde lo [//meta.wikimedia.org/wiki/Aide:Contenu guido d’usanciér] por més d’enformacions sur l’usâjo de la programeria vouiqui.
== Emmodar avouéc MediaWiki ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Lista des paramètres de configuracion]
@@ -5204,7 +5757,7 @@ Debería recibir <doclink href=Copying>unha copia da licenza pública xeral GNU<
* <doclink href=Readme>Léame</doclink>
* <doclink href=ReleaseNotes>Notas de lanzamento</doclink>
* <doclink href=Copying>Copia</doclink>
-* <doclink href=UpgradeDoc>Actualización</doclink>',
+* <doclink href=UpgradeDoc>Actualizacións</doclink>',
'config-env-good' => 'Rematou a comprobación da contorna.
Pode instalar MediaWiki.',
'config-env-bad' => 'Rematou a comprobación da contorna.
@@ -5224,6 +5777,7 @@ Os tipos de base de datos admitidos son os seguintes: $1.
Se está nun aloxamento compartido, pregunte ao seu provedor de hospedaxe para instalar un controlador de base de datos axeitado.
Se compilou o PHP vostede mesmo, reconfigúreo activando un cliente de base de datos, por exemplo, usando <code>./configure --with-mysql</code>.
Se instalou o PHP desde un paquete Debian ou Ubuntu, entón tamén necesita instalar o módulo php5-mysql.',
+ 'config-outdated-sqlite' => "'''Atención:''' Ten o SQLite $1, que é inferior á versión mínima necesaria: $2. O SQLite non estará dispoñible.",
'config-no-fts3' => "'''Atención:''' O SQLite está compilado sen o [//sqlite.org/fts3.html módulo FTS3]; as características de procura non estarán dispoñibles nesta instalación.",
'config-register-globals' => "'''Atención: A opción PHP <code>[http://php.net/register_globals register_globals]</code> está activada.'''
'''Desactívea se pode.'''
@@ -5253,11 +5807,11 @@ 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-ctype' => "'''Fatal:''' O PHP debe compilarse co soporte para a [http://www.php.net/manual/en/ctype.installation.php extensión Ctype].",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] está instalado',
'config-apc' => '[http://www.php.net/apc APC] está instalado',
- 'config-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://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Atención:''' Non se puido atopar [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] ou [http://www.iis.net/download/WinCacheForPhp WinCache].
A caché de obxectos está desactivada.",
'config-mod-security' => "'''Atención:''' O seu servidor web ten o [http://modsecurity.org/ mod_security] activado. Se estivese mal configurado, pode causar problemas a MediaWiki ou calquera outro software que permita aos usuarios publicar contidos arbitrarios.
Olle a [http://modsecurity.org/documentation/ documentación do mod_security] ou póñase en contacto co soporte do seu servidor se atopa erros aleatorios.",
@@ -5282,7 +5836,7 @@ 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-suhosin-max-value-length' => 'Suhosin está instalado e limita a lonxitude do parámetro GET a $1 bytes. O compoñente ResourceLoader (xestor de recursos) de MediaWiki traballa neste límite, pero este prexudica o rendemento. Se é posible, debería establecer 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í.
@@ -5578,7 +6132,7 @@ Se non quere un logo, deixe esta caixa en branco.',
'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].',
+Para obter máis información sobre esta característica, incluíndo as instrucións sobre como configuralo para outros wikis que non sexan a Wikimedia Commons, consulte [//mediawiki.org/wiki/Manual:$wgForeignFileRepos o manual].',
'config-cc-error' => 'A escolla da licenza Creative Commons non deu resultados.
Escriba o nome da licenza manualmente.',
'config-cc-again' => 'Escolla outra vez...',
@@ -5588,7 +6142,7 @@ Escriba o nome da licenza manualmente.',
'config-cache-help' => 'A caché de obxectos emprégase para mellorar a velocidade de MediaWiki mediante a memorización de datos usados con frecuencia.
É amplamente recomendable a súa activación nos sitios de tamaño medio e grande; os sitios pequenos obterán tamén beneficios.',
'config-cache-none' => 'Sen caché (non se elimina ningunha funcionalidade, pero pode afectar á velocidade en wikis grandes)',
- 'config-cache-accel' => 'Caché de obxectos do PHP (APC, eAccelerator, XCache ou WinCache)',
+ 'config-cache-accel' => 'Caché de obxectos do PHP (APC, XCache ou WinCache)',
'config-cache-memcached' => 'Empregar o Memcached (necesita unha instalación e configuración adicional)',
'config-memcached-servers' => 'Servidores da memoria caché:',
'config-memcached-help' => 'Lista de enderezos IP para Memcached.
@@ -5787,9 +6341,8 @@ Dää Wärt isch wahrschyns z nider.
Dr Inschtallationsvorgang chennt wäge däm fählschlaa!",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] isch inschtalliert',
'config-apc' => '[http://www.php.net/apc APC] isch inschtalliert',
- 'config-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://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] hän nit chenne gfunde wäre.
+ 'config-no-cache' => "'''Warnig:''' [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] oder [http://www.iis.net/download/WinCacheForPhp WinCache] hän nit chenne gfunde wäre.
S Objäktcaching isch wäge däm nit aktiviert.",
'config-diff3-bad' => 'GNU diff3 isch nit gfunde wore.',
'config-imagemagick' => 'ImageMagick isch gfunde wore: <code>$1</code>.
@@ -5837,6 +6390,7 @@ $messages['haw'] = array(
/** Hebrew (עברית)
* @author Amire80
* @author YaronSh
+ * @author ערן
*/
$messages['he'] = array(
'config-desc' => 'תכנית ההתקנה של מדיה־ויקי',
@@ -5929,6 +6483,7 @@ $1
×× ××ª× ×ž×©×ª×ž×©×™× ×‘×ירוח משותף, בקשו מספק ×”×ירוח ×©×œ×›× ×œ×”×ª×§×™×Ÿ דרייבר מסד × ×ª×•× ×™× ×ž×ª××™×.
×× ×§Ö´×ž×¤×œ×ª× ×ת PHP בעצמכ×, הגדירו ×ותו מחדש והפעילו ×ת לקוח מסד נתוני×, למשל ב×מצעות <code dir="ltr">./configure --with-mysql</code>.
×× ×”×ª×§× ×ª× ×ת PHP כחבילה של דבי×ן ×ו של ×ובונטו, יש להתקין ×ת המודול php5-mysql.',
+ 'config-outdated-sqlite' => "'''×זהרה''': במערכת מתוקן SQLite $1. גרסה זו ×œ× × ×ª×ž×›×ª ולשימוש ב SQLite נדרשת גרסה $2 ומעלה. מסיבה זו ×”×פשרות SQLite ×œ× ×ž×ופשרת.",
'config-no-fts3' => "'''×זהרה''': SQLite מקומפל ×œ×œ× [//sqlite.org/fts3.html מודול FTS]. יכולות חיפוש ×œ× ×™×”×™×• זמינות בהתקנה ×”×–×ת.",
'config-register-globals' => "'''×זהרה: ×”×פשרות <code>[http://php.net/register_globals register_globals]</code> של PHP מופעלת.'''
'''כבו ×ותה ×× ××ª× ×™×›×•×œ×™×.'''
@@ -5958,11 +6513,11 @@ $1
'config-memory-bad' => "'''×זהרה:''' ערך ×”×פשרות <code>memory_limit</code> של PHP ×”×•× $1.
×–×” כנר××” נמוך מדי.
ההתקנה עשויה להיכשל!",
+ 'config-ctype' => "'''שגי××” סופנית''': נדרשת גרסת PHP שתומכת בהרחבה [http://www.php.net/manual/en/ctype.installation.php Ctype].",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] מותקן',
'config-apc' => '[http://www.php.net/apc APC] מותקן',
- 'config-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://xcache.lighttpd.net/ XCache] ×ו [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''×זהרה:''' ×חת מהתוכנות הב×ות ×œ× × ×ž×¦××”: [http://www.php.net/apc APC]&rlm;, [http://xcache.lighttpd.net/ XCache] ×ו [http://www.iis.net/download/WinCacheForPhp WinCache].
מטמון ×¢×¦×ž×™× ×œ× ×ž×•×¤×¢×œ.",
'config-mod-security' => "'''×זהרה''': בשרת הווב ×©×œ×›× ×ž×•×¤×¢×œ [http://modsecurity.org/ mod_security]. ×× ×”×•× ×œ× ×ž×•×’×“×¨ טוב, ×–×” יכול ×œ×’×¨×•× ×œ×‘×¢×™×•×ª במדיה־ויקי ובתכנה ×חרת שמ×פשרת ×œ×ž×©×ª×ž×©×™× ×œ×©×œ×•×— תוכן שרירותי.
קר×ו ×ת [http://modsecurity.org/documentation/ התיעוד של mod_security] ×ו צרו קשר ×¢× ×נשי התמיכה של שירותי ×”×ירוח ×©×œ×›× ×× ××ª× × ×ª×§×œ×™× ×‘×©×’×™×ות ×קר×יות.",
@@ -6279,7 +6834,7 @@ chmod a+w $3</pre></div>',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] ×”×™× ×ª×›×•× ×” שמ×פשרת ל×תרי ויקי להשתמש בתמונות, ×‘×¦×œ×™×œ×™× ×•×‘×ž×“×™×” ×חרת שנמצ×ת ב×תר [//commons.wikimedia.org/ ויקישיתוף] (Wikimedia Commons).
כדי לעשות ×ת ×–×”, מדיה־ויקי צריך לגשת ל×ינטרנט.
-למידע נוסף על התכונה ×”×–×ת, כולל הור×ות ×יך להפעיל ×ת ×–×” ל×תרי ויקי ש××™× × ×•×™×§×™×©×™×ª×•×£, ר׳ [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos ×ת ספר ההדרכה].',
+למידע נוסף על התכונה ×”×–×ת, כולל הור×ות ×יך להפעיל ×ת ×–×” ל×תרי ויקי ש××™× × ×•×™×§×™×©×™×ª×•×£, ר׳ [//mediawiki.org/wiki/Manual:$wgForeignFileRepos ×ת ספר ההדרכה].',
'config-cc-error' => 'בורר רישיונות קרי×ייטיב קומונז ×œ× ×”×—×–×™×¨ ×©×•× ×ª×•×¦××”.
הקלידו ×ת ×©× ×”×¨×™×©×™×•×Ÿ ידנית.',
'config-cc-again' => '× × ×œ×‘×—×•×¨ שוב...',
@@ -6289,7 +6844,7 @@ chmod a+w $3</pre></div>',
'config-cache-help' => 'מטמון ×¢×¦×ž×™× ×ž×©×ž×© לשיפור המהירות של מדיה־ויקי על־ידי שמירה של × ×ª×•× ×™× ×©×”×©×™×ž×•×© ×‘×”× × ×¤×•×¥ במטמון.
ל××ª×¨×™× ×‘×™× ×•× ×™×™× ×•×’×“×•×œ×™× ×›×“××™ מ×וד להפעיל ×ת ×–×”, ×•×’× ××ª×¨×™× ×§×˜× ×™× ×™×™×”× ×• מזה.',
'config-cache-none' => '×œ×œ× ×ž×˜×ž×•×Ÿ (×©×•× ×™×›×•×œ×ª ××™× ×” מוסרת, ×בל ×”×‘×™×¦×•×¢×™× ×‘××ª×¨×™× ×’×“×•×œ×™× ×™×™×¤×’×¢×•)',
- 'config-cache-accel' => 'מטמון ×¢×¦×ž×™× (object caching) של PHP&rlm; (APC&rlm;, eAccelerator&rlm;, XCache ×ו WinCache)',
+ 'config-cache-accel' => 'מטמון ×¢×¦×ž×™× (object caching) של PHP&rlm; (APC&rlm;, XCache ×ו WinCache)',
'config-cache-memcached' => 'להשתמש ב־Memcached (דורש התקנות והגדרות נוספות)',
'config-memcached-servers' => 'שרתי Memcached:',
'config-memcached-help' => 'רשימת כתובות IP ש־Memcached ישתמש בהן.
@@ -6515,7 +7070,6 @@ To je najskerje přeniske.
Instalacija móhła so njeporadźić!",
'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',
'config-diff3-bad' => 'GNU diff3 njenamakany.',
'config-no-uri' => "'''Zmylk:''' Aktualny URI njeda so postajić.
@@ -6548,6 +7102,7 @@ To njeje hesło konta MediaWiki; to je hesło za twoju datowu banku.',
'config-db-schema' => 'Å ema za MediaWiki',
'config-db-schema-help' => 'Tuta šema da so zwjetša derje wužiwać.
Změń ju jenož, jeli su pÅ™eswÄ›dÄiwe pÅ™iÄiny za to.',
+ 'config-pg-test-error' => "Zwisk z datowej banku '''$1''' móžno njeje: $2",
'config-sqlite-dir' => 'Zapis SQLite-datow:',
'config-oracle-def-ts' => 'Standardny tabelowy rum:',
'config-oracle-temp-ts' => 'Nachwilny tabelowy rum:',
@@ -6555,12 +7110,15 @@ Změń ju jenož, jeli su pÅ™eswÄ›dÄiwe pÅ™iÄiny za to.',
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
'config-type-oracle' => 'Oracle',
+ 'config-type-ibm_db2' => 'IBM DB2',
'config-support-mysql' => '* $1 je primarny cil za MediaWiki a podpěruje so najlěpje ([http://www.php.net/manual/en/mysql.installation.php Nawod ke kompilowanju PHP z MySQL-podpěru])',
'config-support-postgres' => '* $1 je popularny system datoweje banki zjawneho žórÅ‚a jako alternatiwa k MySQL ([http://www.php.net/manual/en/pgsql.installation.php nawod za kompilowanje PHP z podpÄ›ru PostgreSQL]). MóhÅ‚o hišće nÄ›kotre zmylki eksistować, a njeporuÄa so jón w produktiwnej wokolinje wužiwać.',
+ 'config-support-ibm_db2' => '* $1 je komercielna předewzaćelska datowa banka.',
'config-header-mysql' => 'Nastajenja MySQL',
'config-header-postgres' => 'Nastajenja PostgreSQL',
'config-header-sqlite' => 'Nastajenja SQLite',
'config-header-oracle' => 'Nastajenja Oracle',
+ 'config-header-ibm_db2' => 'Nastajenja IBM DB2',
'config-invalid-db-type' => 'Njepłaćiwy typ datoweje banki',
'config-missing-db-name' => 'Dyrbiš hódnotu za "Mjeno datoweje banki" zapodać',
'config-missing-db-host' => 'Dyrbiš hódnotu za "Database host" zapodać',
@@ -6654,8 +7212,10 @@ MóžeÅ¡ nÄ›tko zbytnu konfiguraciju pÅ™eskoÄić a wiki hnydom instalować.',
'config-license' => 'Awtorske prawo a licenca:',
'config-license-none' => 'Žane licencne podaća w nohowej lince',
'config-license-cc-by-sa' => 'Creative Commons Attribution Share Alike',
+ 'config-license-cc-by' => 'Creative Commons Attribution',
'config-license-cc-by-nc-sa' => 'Creative Commons Attribution Non-Commercial Share Alike',
'config-license-cc-0' => 'Creative Commons Zero (zjawnosći přistupny)',
+ 'config-license-gfdl' => 'GNU-licenca za swobodnu dokumentaciju 1.3 abo nowša',
'config-license-pd' => 'Powšitkownosći přistupny',
'config-license-cc-choose' => 'Swójsku licencu Creative Commons wubrać',
'config-email-settings' => 'E-mejlowe nastajenja',
@@ -6684,7 +7244,7 @@ Zapodaj licencne mjeno manuelnje.',
'config-advanced-settings' => 'Rozšěrjena konfiguraćija',
'config-cache-options' => 'Nastajenja za objektowe pufrowanje:',
'config-cache-none' => 'Žane pufrowanje (žana funkcionalnosć so njewotstronja, ale spěšnosć móže so na wjetšich wikijowych sydłach wobwliwować)',
- 'config-cache-accel' => 'Objektowe pufrowanje PHP (APC, eAccelerator, XCache abo WinCache)',
+ 'config-cache-accel' => 'Objektowe pufrowanje PHP (APC, XCache abo WinCache)',
'config-cache-memcached' => 'Memcached wužiwać (wužaduje sej přidatnu instalaciju a konfiguraciju)',
'config-memcached-servers' => 'Serwery memcached:',
'config-memcached-help' => 'Lisćina IP-adresow, kotrež maja so za Memcached wužiwać.
@@ -6708,6 +7268,7 @@ Jeli hišće chceš něšto změnić, klikń na "Wróćo".',
'config-install-step-failed' => 'njeporadźiło',
'config-install-extensions' => 'Inkluziwnje rozšěrjenja',
'config-install-database' => 'Datowa banka so připrawja',
+ 'config-install-schema' => 'Datowa Å¡ema so twori',
'config-install-pg-schema-not-exist' => 'Å ema PostgreSQL njeeksistuje',
'config-install-pg-schema-failed' => 'Wutworjenje tabelow je so njeporadźiło.
Zawěsć, zo wužiwar "$1" móže do šemy "$2" pisać.',
@@ -6719,6 +7280,9 @@ Zawěsć, zo wužiwar "$1" móže do šemy "$2" pisać.',
'config-install-user-alreadyexists' => 'Wužiwar "$1" hižo eksistuje',
'config-install-user-create-failed' => 'Wutworjenje wužiwarja "$1" je so njeporadźiło: $2',
'config-install-user-grant-failed' => 'Prawo njeda so wužiwarjej "$1" dać: $2',
+ 'config-install-user-missing' => 'Podaty wužiwar "$1" njeeksistuje.',
+ 'config-install-user-missing-create' => 'Podaty wužiwar "$1" njeeksistuje.
+Prošu klikń na slědowacy kontrolny kašćik "konto załožić", jeli chceš jo wutworić.',
'config-install-tables' => 'Tworjenje tabelow',
'config-install-tables-exist' => "'''Warnowanje''': Zda so, zo tabele MediaWiki hižo eksistuja.
Wutworjenje so přeskakuje.",
@@ -6731,6 +7295,7 @@ Standardna lisćina sp přeskakuje.",
'config-install-keys' => 'Tajne kluÄe so tworja',
'config-install-sysop' => 'Tworjenje administratoroweho wužiwarskeho konta',
'config-install-subscribe-fail' => 'Abonowanje "mediawiki-announce" njemóžno: $1',
+ 'config-install-subscribe-notpossible' => 'cURL njeje instalowany a allow_url_fopen k dispoziciji njesteji.',
'config-install-mainpage' => 'HÅ‚owna strona so ze standardnym wobsahom wutworja',
'config-install-extension-tables' => 'Tabele za zmóžnjene rozšěrjenja so tworja',
'config-install-mainpage-failed' => 'Powěsć njeda so zasunyć: $1',
@@ -6884,9 +7449,8 @@ A MediaWikinek UTF-8-támogatásra van szüksége a helyes működéshez.",
Ez az érték valószínűleg túl kevés, a telepítés sikertelen lehet.",
'config-xcache' => 'Az [http://xcache.lighttpd.net/ XCache] telepítve van',
'config-apc' => 'Az [http://www.php.net/apc APC] telepítve van',
- 'config-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://xcache.lighttpd.net/ XCache] és [http://www.iis.net/download/WinCacheForPhp WinCache] sem.
+ 'config-no-cache' => "'''Figyelmeztetés:''' Nem található [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] és [http://www.iis.net/download/WinCacheForPhp WinCache] sem.
Objektum-gyorsítótárazás nem lesz engedélyezve.",
'config-diff3-bad' => 'GNU diff3 nem található.',
'config-imagemagick' => 'Az ImageMagick megtalálható a rendszeren: <code>$1</code>.
@@ -7193,7 +7757,7 @@ Ha nem szeretnél logót használni, egyszerűen hagyd üresen a mezőt.',
'config-instantcommons-help' => 'Az [//www.mediawiki.org/wiki/InstantCommons Instant Commons] lehetővé teszi, hogy a wikin használhassák a [//commons.wikimedia.org/ Wikimedia Commons] oldalon található képeket, hangokat és más médiafájlokat.
A használatához a MediaWikinek internethozzáférésre van szüksége.
-A funkcióról és hogy hogyan állítható be más wikik esetén [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos a kézikönyvben] találhatsz további információkat.',
+A funkcióról és hogy hogyan állítható be más wikik esetén [//mediawiki.org/wiki/Manual:$wgForeignFileRepos a kézikönyvben] találhatsz további információkat.',
'config-cc-error' => 'A Creative Commons-licencválasztó nem tért vissza eredménnyel.
Add meg kézzel a licencet.',
'config-cc-again' => 'Válassz újra…',
@@ -7203,7 +7767,7 @@ Add meg kézzel a licencet.',
'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-accel' => 'PHP-objektumok gyorsítótárazása (APC, XCache or WinCache)',
'config-cache-memcached' => 'Memcached használata (további telepítés és konfigurálás szükséges)',
'config-memcached-servers' => 'Memcached-szerverek:',
'config-memcached-help' => 'Azon IP-címek listája, melyeket a Memcached használhat.
@@ -7391,6 +7955,7 @@ Le sequente typos de base de datos es supportate: $1.
Si tu sito usa un servitor dividite (shared hosting), demanda a tu providitor de installar un driver de base de datos appropriate.
Si tu compilava PHP tu mesme, reconfigura lo con un cliente de base de datos activate, per exemplo usante <code>./configure --with-mysql</code>.
Si tu installava PHP ex un pacchetto Debian o Ubuntu, tu debe installar equalmente le modulo php5-mysql.',
+ 'config-outdated-sqlite' => "'''Attention''': tu ha SQLite $1, que es inferior al version minimal requirite, $2. SQLite essera indisponibile.",
'config-no-fts3' => "'''Attention''': SQLite es compilate sin [//sqlite.org/fts3.html modulo FTS3]; functionalitate de recerca non essera disponibile in iste back-end.",
'config-register-globals' => "'''Attention: le option <code>[http://php.net/register_globals register_globals]</code> de PHP es activate.'''
'''Disactiva lo si tu pote.'''
@@ -7420,11 +7985,11 @@ 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-ctype' => "'''Fatal''': PHP debe esser compilate con supporto pro le [http://www.php.net/manual/en/ctype.installation.php extension Ctype].",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] es installate',
'config-apc' => '[http://www.php.net/apc APC] es installate',
- 'config-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://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Aviso:''' Non poteva trovar [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
Le cache de objectos non es activate.",
'config-mod-security' => "'''Attention''': [http://modsecurity.org/ mod_security] es active in tu servitor web. Si mal configurate, isto pote causar problemas pro MediaWiki o altere software que permitte al usatores de publicar contento arbitrari.
Consulta le [http://modsecurity.org/documentation/ documentation de mod_security] o contacta le servicio de adjuta de tu host si tu incontra estranie errores.",
@@ -7746,7 +8311,7 @@ Si tu non vole un logotypo, lassa iste quadro vacue.',
'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].',
+Pro plus information super iste function, includente instructiones super como configurar lo pro wikis altere que Wikimedia Commons, consulta [//mediawiki.org/wiki/Manual:$wgForeignFileRepos le manual].',
'config-cc-error' => 'Le selector de licentia Creative Commons non dava un resultato.
Entra le nomine del licentia manualmente.',
'config-cc-again' => 'Selige de novo…',
@@ -7756,7 +8321,7 @@ Entra le nomine del licentia manualmente.',
'config-cache-help' => 'Le cache de objectos es usate pro meliorar le rapiditate de MediaWiki per immagazinar le datos frequentemente usate.
Le sitos medie o grande es multo incoragiate de activar isto, ma anque le sitos parve percipera le beneficios.',
'config-cache-none' => 'Nulle cache (nulle functionalitate es removite, ma le rapiditate pote diminuer in grande sitos wiki)',
- 'config-cache-accel' => 'Cache de objectos de PHP (APC, eAccelerator, XCache o WinCache)',
+ 'config-cache-accel' => 'Cache de objectos de PHP (APC, XCache o WinCache)',
'config-cache-memcached' => 'Usar Memcached (require additional installation e configuration)',
'config-memcached-servers' => 'Servitores Memcached:',
'config-memcached-help' => 'Lista de adresses IP a usar pro Memcached.
@@ -7971,9 +8536,8 @@ Ini terlalu rendah.
Instalasi terancam gagal!",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] telah diinstal',
'config-apc' => '[http://www.php.net/apc APC] telah diinstal',
- 'config-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://xcache.lighttpd.net/ XCache], atau [http://www.iis.net/download/WinCacheForPhp WinCache]. Pinggahan obyek tidak dinonaktifkan.",
+ 'config-no-cache' => "'''Peringatan:''' Tidak dapat menemukan [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache], atau [http://www.iis.net/download/WinCacheForPhp WinCache]. Pinggahan obyek tidak dinonaktifkan.",
'config-diff3-bad' => 'GNU diff3 tidak ditemukan.',
'config-imagemagick' => 'ImageMagick ditemukan: <code>$1</code> .
Pembuatan gambar mini akan diaktifkan jika Anda mengaktifkan pengunggahan.',
@@ -8274,7 +8838,7 @@ Jika Anda tidak ingin menyertakan logo, biarkan kotak ini kosong.',
'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].',
+Untuk informasi lebih lanjut tentang fitur ini, termasuk petunjuk tentang cara untuk mengatur untuk wiki selain Wikimedia Commons, baca [//mediawiki.org/wiki/Manual:$wgForeignFileRepos manual].',
'config-cc-error' => 'Pemilih lisensi Creative Commons tidak memberikan hasil.
Masukkan nama lisensi secara manual.',
'config-cc-again' => 'Pilih lagi...',
@@ -8284,7 +8848,7 @@ Masukkan nama lisensi secara manual.',
'config-cache-help' => 'Penyinggahan objek digunakan untuk meningkatkan kecepatan MediaWiki dengan menyinggahkan data yang sering digunakan.
Situs berukuran sedang hingga besar sangat dianjurkan untuk mengaktifkan fitur ini, dan situs kecil juga akan merasakan manfaatnya.',
'config-cache-none' => 'Tidak ada penyinggahan (tidak ada fungsi yang dibuang, tetapi kecepatan dapat terpengaruh pada situs wiki yang besar)',
- 'config-cache-accel' => 'Penyinggahan objek PHP (APC, eAccelerator, XCache atau WinCache)',
+ 'config-cache-accel' => 'Penyinggahan objek PHP (APC, XCache atau WinCache)',
'config-cache-memcached' => 'Gunakan Memcached (memerlukan setup dan konfigurasi tambahan)',
'config-memcached-servers' => 'Server Memcached:',
'config-memcached-help' => 'Daftar alamat IP yang digunakan untuk Memcached.
@@ -8661,9 +9225,8 @@ MediaWikiã«ã¯UTF-8サãƒãƒ¼ãƒˆã®é–¢æ•°ãŒå¿…è¦ã§ã™ã€‚",
インストールãŒå¤±æ•—ã™ã‚‹ã‹ã‚‚ã—ã‚Œã¾ã›ã‚“ï¼",
'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://xcache.lighttpd.net/ XCache]ã‚ã‚‹ã„ã¯[http://www.iis.net/download/WinCacheForPhp WinCache]ã®ã„ãšã‚Œã‚‚見ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚
+ 'config-no-cache' => "'''警告:'''[http://www.php.net/apc APC]ã€[http://xcache.lighttpd.net/ XCache]ã‚ã‚‹ã„ã¯[http://www.iis.net/download/WinCacheForPhp WinCache]ã®ã„ãšã‚Œã‚‚見ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚
オブジェクトã®ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã¯æœ‰åŠ¹åŒ–ã•ã‚Œã¾ã›ã‚“。",
'config-diff3-bad' => 'GNU diff3ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。',
'config-imagemagick' => 'ImageMagickãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸï¼š<code>$1</code>。
@@ -8854,8 +9417,9 @@ chmod a+w $3</pre>',
'config-ns-site-name' => 'ウィキåã¨åŒã˜ï¼š$1',
'config-ns-other' => 'ãã®ä»–(指定ã—ã¦ãã ã•ã„)',
'config-ns-other-default' => 'マイウィキ',
- 'config-project-namespace-help' => "ウィキペディアã®ä¾‹ã«å¾“ãˆã°ã€å¤šãã®ã‚¦ã‚£ã‚­ã¯ã€Œ'''プロジェクトã®åå‰ç©ºé–“'''ã€ã«ãŠã„ã¦ã€ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã®ãƒšãƒ¼ã‚¸ã¨ã¯åˆ†é›¢ã—ãŸç‹¬è‡ªã®ãƒãƒªã‚·ãƒ¼ãƒšãƒ¼ã‚¸ã‚’æŒã¤ã€‚
-ä¼çµ±çš„ã«ã¯ã“ã®æŽ¥é ­è¾žã¯ã‚¦ã‚£ã‚­ã®ãƒšãƒ¼ã‚¸ã‹ã‚‰æ´¾ç”Ÿã•ã‚Œã‚‹ã€‚ã—ã‹ã—ã€\"#\" ã‚„ \":\"ã®ã‚ˆã†ãªå¥åˆ‡ã‚Šè¨˜å·ã¯å«ã‚“ã§ã„ãªã„。",
+ 'config-project-namespace-help' => "ウィキペディアã®ä¾‹ã«å¾“ã„ã€å¤šãã®ã‚¦ã‚£ã‚­ã¯ã€ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã®ãƒšãƒ¼ã‚¸ã¨ã¯åˆ†é›¢ã—ãŸãƒãƒªã‚·ãƒ¼ãƒšãƒ¼ã‚¸ã‚’「'''プロジェクトã®åå‰ç©ºé–“'''ã€ã«æŒã£ã¦ã„ã¾ã™ã€‚
+ã“ã®åå‰ç©ºé–“ã®ãƒšãƒ¼ã‚¸ã®ã‚¿ã‚¤ãƒˆãƒ«ã¯ã™ã¹ã¦ã€ã‚る接頭辞ã§å§‹ã¾ã‚Šã¾ã™ã€‚ãれをã“ã“ã§æŒ‡å®šã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚
+ã“ã®æŽ¥é ­è¾žã¯ã‚¦ã‚£ã‚­ã®åå‰ã«ç”±æ¥ã™ã‚‹ã®ãŒä¼çµ±çš„ã§ã™ãŒã€ã€Œ#ã€ã‚„「:ã€ã®ã‚ˆã†ãªåŒºåˆ‡ã‚Šè¨˜å·ã‚’å«ã‚ã‚‹ã“ã¨ã¯ã§ãã¾ã›ã‚“。",
'config-ns-invalid' => '"<nowiki>$1</nowiki>"ã®ã‚ˆã†ã«æŒ‡å®šã•ã‚ŒãŸåå‰ç©ºé–“ã¯ç„¡åŠ¹ã§ã™ã€‚
é•ã†ãƒ—ロジェクトåå‰ç©ºé–“を指定ã—ã¦ãã ã•ã„。',
'config-admin-box' => '管ç†ã‚¢ã‚«ã‚¦ãƒ³ãƒˆ',
@@ -8942,15 +9506,15 @@ GNUフリー文書利用許諾契約書ã¯ã‚¦ã‚£ã‚­ãƒšãƒ‡ã‚£ã‚¢ãŒæŽ¡ç”¨ã—ã¦ã
'config-upload-deleted-help' => '削除ã•ã‚Œã‚‹ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ä¿å­˜ã™ã‚‹ãŸã‚ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’é¸æŠžã—ã¦ãã ã•ã„。
ã“ã‚ŒãŒã‚¦ã‚§ãƒ–ã‹ã‚‰ã‚¢ã‚¯ã‚»ã‚¹ã§ããªã„ã“ã¨ãŒç†æƒ³ã§ã™ã€‚',
'config-logo' => 'ロゴã®URL:',
- 'config-logo-help' => 'メディアウィキã®åˆæœŸã®ã‚¹ã‚­ãƒ³ã¯æœ€ä¸Šéƒ¨å·¦è§’ã«ã‚ã‚‹135x160ピクセルã®ãƒ­ã‚´ã®ãŸã‚ã«ã‚¹ãƒšãƒ¼ã‚¹ã‚’å«ã‚“ã§ã„ã¾ã™ã€‚
-é©åˆ‡ãªã‚µã‚¤ã‚ºã®ã‚¤ãƒ¡ãƒ¼ã‚¸ã‚’アップロードã—ã€ã“ã“ã«URLを入力ã—ã¦ãã ã•ã„。
+ 'config-logo-help' => 'MediaWikiã®æœªè¨­å®šçŠ¶æ…‹ã®ã‚¹ã‚­ãƒ³ã§ã¯ã€ã‚µã‚¤ãƒ‰ãƒãƒ¼ä¸Šéƒ¨ã«135x160ピクセルã®ãƒ­ã‚´ç”¨ã®ä½™ç™½ãŒã‚ã‚Šã¾ã™ã€‚
+é©åˆ‡ãªã‚µã‚¤ã‚ºã®ç”»åƒã‚’アップロードã—ã€ãã®URLã‚’ã“ã“ã«å…¥åŠ›ã—ã¦ãã ã•ã„。
-ã‚‚ã—ã€ãƒ­ã‚´ã‚’望ã¾ãªã„ãªã‚‰ã°ã€ã“ã®ãƒœãƒƒã‚¯ã‚¹ã‚’空白状態ã®ã¾ã¾ã«ã—ã¦ãã ã•ã„。',
+ã‚‚ã—ロゴãŒè¦ã‚‰ãªã„ãªã‚‰ã°ã€ã“ã®ãƒœãƒƒã‚¯ã‚¹ã‚’空白ã®ã¾ã¾ã«ã—ã¦ãã ã•ã„。',
'config-instantcommons' => 'InstantCommons機能を有効ã«ã™ã‚‹',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons InstantCommons]ã¯ã€[//commons.wikimedia.org/ ウィキメディア・コモンズ]ã®ã‚µã‚¤ãƒˆã§è¦‹ã¤ã‹ã£ãŸç”»åƒã‚„音声ã€ãã®ä»–ã®ãƒ¡ãƒ‡ã‚£ã‚¢ã‚’ウィキ上ã§åˆ©ç”¨ã™ã‚‹ã“ã¨ãŒã§ãるよã†ã«ãªã‚‹æ©Ÿèƒ½ã§ã™ã€‚
ã“れを有効化ã™ã‚‹ã«ã¯ã€MediaWikiã¯ã‚¤ãƒ³ã‚¿ãƒ¼ãƒãƒƒãƒˆã«æŽ¥ç¶šã§ããªã‘ã‚Œã°ãªã‚Šã¾ã›ã‚“。
-ウィキメディアコモンズ以外ã®ã‚¦ã‚£ã‚­ã‚’åŒã˜ã‚ˆã†ã«è¨­å®šã™ã‚‹æ–¹æ³•ãªã©ã€ã“ã®æ©Ÿèƒ½ã«é–¢ã™ã‚‹è©³ç´°ãªæƒ…å ±ã¯ã€[http://mediawiki.org/wiki/Manual:$wgForeignFileRepos マニュアル]ã‚’ã”覧ãã ã•ã„。',
+ウィキメディアコモンズ以外ã®ã‚¦ã‚£ã‚­ã‚’åŒã˜ã‚ˆã†ã«è¨­å®šã™ã‚‹æ–¹æ³•ãªã©ã€ã“ã®æ©Ÿèƒ½ã«é–¢ã™ã‚‹è©³ç´°ãªæƒ…å ±ã¯ã€[//mediawiki.org/wiki/Manual:$wgForeignFileRepos マニュアル]ã‚’ã”覧ãã ã•ã„。',
'config-cc-error' => 'クリエイティブ・コモンズ・ライセンスã®é¸æŠžå™¨ã‹ã‚‰çµæžœãŒå¾—られã¾ã›ã‚“ã§ã—ãŸã€‚
ライセンスã®åå‰ã‚’手動ã§å…¥åŠ›ã—ã¦ãã ã•ã„。',
'config-cc-again' => 'ã‚‚ã†ä¸€åº¦é¸æŠžã—ã¦ãã ã•ã„...',
@@ -8960,7 +9524,7 @@ GNUフリー文書利用許諾契約書ã¯ã‚¦ã‚£ã‚­ãƒšãƒ‡ã‚£ã‚¢ãŒæŽ¡ç”¨ã—ã¦ã
'config-cache-help' => 'オブジェクトã®ã‚­ãƒ£ãƒƒã‚·ãƒ¥ã¯ã€ä½¿ç”¨ã—ãŸãƒ‡ãƒ¼ã‚¿ã‚’é »ç¹ã«ã‚­ãƒ£ãƒƒã‚·ãƒ³ã‚°ã™ã‚‹ã“ã¨ã«ã‚ˆã£ã¦ã€ãƒ¡ãƒ‡ã‚£ã‚¢ã‚¦ã‚£ã‚­ã®ã‚¹ãƒ”ード改善ã«ä½¿ç”¨ã•ã‚Œã¾ã™ã€‚
中〜大サイトã«ãŠã„ã¦ã¯ã€ã“れを有効ã«ã™ã‚‹ãŸã‚ã«å¤§å¤‰æœ›ã¾ã—ã„ã“ã¨ã§ã™ã€‚ã¾ãŸå°ã•ãªã‚µã‚¤ãƒˆã«ãŠã„ã¦ã‚‚åŒæ§˜ãªåˆ©ç‚¹ã‚’ã‚‚ãŸã‚‰ã™ã¨è€ƒãˆã‚‰ã‚Œã¾ã™ã€‚',
'config-cache-none' => 'キャッシングã—ãªã„(機能ã¯å–り払ã‚ã‚Œã¾ã™ã€ã—ã‹ã‚‚より大ããªã‚¦ã‚£ã‚­ã‚µã‚¤ãƒˆä¸Šã§ã‚¹ãƒ”ードã®å•é¡ŒãŒç™ºç”Ÿã—ã¾ã™)',
- 'config-cache-accel' => 'PHPオブジェクトキャッシング(APCã€eAcceleratorã€XCacheã‚ã‚‹ã„ã¯WinCache)',
+ 'config-cache-accel' => 'PHPオブジェクトキャッシング(APCã€XCacheã‚ã‚‹ã„ã¯WinCache)',
'config-cache-memcached' => 'Memcachedを使用(追加ã®è¨­å®šãŒå¿…è¦ã§ã™ï¼‰',
'config-memcached-servers' => 'メモリをキャッシュã•ã‚ŒãŸã‚µãƒ¼ãƒ:',
'config-memcached-help' => 'Memcachedを使用ã™ã‚‹IPアドレスã®ä¸€è¦§ã€‚
@@ -9211,6 +9775,7 @@ $messages['krc'] = array(
);
/** Colognian (Ripoarisch)
+ * @author Mormegil
* @author Purodha
*/
$messages['ksh'] = array(
@@ -9341,16 +9906,13 @@ 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-ctype' => "'''Fähler:''' <i lang=\"en\">PHP</i> moß met dä Ongerschtözong för der [http://www.php.net/manual/en/ctype.installation.php <code lang=\"en\">Ctype</code> Zohsaz] övversaz woode sin.",
'config-xcache' => 'Dä <code lang="en">[http://xcache.lighttpd.net/ XCache]</code> es ennjeresht.',
'config-apc' => 'Dä <code lang="en">[http://www.php.net/apc APC]</code> es ennjeresht.',
- 'config-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://xcache.lighttpd.net/ 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://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-mod-security' => "'''Opjepaß''': Dinge Webßööver hät <code lang=\"en\">[http://modsecurity.org/ mod_security]</code> enjeschalldt. Wann doh derbei en Enschtällong nit janz akeraat paßß, dann kann et goot sin, dat mer Probleme met MeedijaWiki un oc met ander Projramme kritt, die zohlööt, dat vun ußerhallef öhndsene Krohm op dä Webßööver jebraat wääde künnt.Beloor Der di Sigg <code lang=\"en\">[http://modsecurity.org/documentation/ mod_security documentation]</code> udder donn met dä Fachlück för Dinge Webßööver kalle, wann zohfälleje un koomijje Fähler bemerke deihß.",
'config-diff3-bad' => 'Mer han <i lang="en">GNU</i> <code lang="en">diff3</code> nit jefonge.',
'config-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ß.',
@@ -9374,7 +9936,7 @@ 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.
+ 'config-db-host-help' => 'Wann Dinge ẞööver för de Daatebangk ob enem andere Rääschner es, donn heh dämm singe Name udder dämm sing <i lang="en">IP</i>-Addräß enjävve.
Wann De ob enem Meetẞööver beß, weet Der Dinge Provaider odder däm sing Dokemäntazjuhn saare, wat De endraare moß.
@@ -9538,14 +10100,13 @@ 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
+ 'config-mysql-myisam-dep' => '\'\'\'Opjepaß:\'\'\' <i lang="en">MyISAM</i> es als Speicher för <i lang="en">MySQL</i> nit besönders joot för et Zosammeschpell met MediaWiki zo bruche:
+* Dorj_et kumplätte Sperre vun Tabälle, künne koum ens Saache parrallel en dä Daatebangk jedonn wääde.
* Dat Fomaat es anfällesch för Probleme met de Daate.
-* Et weed vun MediaWiki nit immer passend ongerschtöz.
+* Et weed vun MediaWiki nit ėmmer zopaß ongerschtöz.
-Wann Ding <i lang="en">MySQL</i> et schpeischere en <i lang="en">InnoDB</i>-Datteije 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.
-',
+Wann Ding <i lang="en">MySQL</i> et Schpeischere en <i lang="en">InnoDB</i>-Datteije ongerschtöze deiht, dom_mer dat nohdröcklesch ämfähle.
+Kann dä ẞööver dat nit, künnd et joode jelääjeheit sin, dä ens op der neuste Schtand ze bränge.',
'config-mysql-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.
@@ -9623,6 +10184,7 @@ Noch ander un un opwändijere Enshtellunge för de Rääschte sin müjjelesch, w
'config-license' => 'Urhävverrääsch un Lizänz:',
'config-license-none' => 'Kein Fooßreih övver de Lizänz',
'config-license-cc-by-sa' => '<i lang="en">Creative Commons</i> Der Name moß jenannt sin, et Wiggerjävve es zohjelohße onger dersellve Bedengunge',
+ 'config-license-cc-by' => 'De <i lang="en">Creative Commons</i> ier Lizänz met Namensnännong',
'config-license-cc-by-nc-sa' => '<i lang="en">Creative Commons</i> Nit för e Jeschäff ze maache, et Wiggerjävve es zohjelohße unger dersellve Bedengunge',
'config-license-cc-0' => '<i lang="en">Creative Commons</i> „Noll“ (jemeinfrei udder Pablic Domain)',
'config-license-gfdl' => 'De <i lang="en">GNU</i>-Lizänz för frei Dokemäntazjuhne, Version 1.3 udder en späädere',
@@ -9671,7 +10233,7 @@ Wells De kei Logo han, draach heh nix en.',
'config-instantcommons' => 'Donn <i lang="en">InstantCommons</i> zohlohße.',
'config-instantcommons-help' => '<i lang="en">[//www.mediawiki.org/wiki/InstantCommons InstantCommons]</i> es en Eijeschaff, di et för Wikis müjjelesch määt, Belder, Tondatteie un ander Meedijedatteie enzebenge, di op dä Webßait vun de <i lang="en">[//commons.wikimedia.org/ Wikimedia Commons]</i> ongerjebraat sin. Öm dat noze ze künne, moß dä ẞööver vum MediaWiki en Verbendung nohm Internet opnämme künne.
-Mieh Aanjaabe doh drövver un en Aanleidung, wi mer och ander Wikis ußer de <i lang="en">Wikimedia Commons</i> doför enreeschte kann, fengk mer em [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos Handbooch].',
+Mieh Aanjaabe doh drövver un en Aanleidung, wi mer och ander Wikis ußer de <i lang="en">Wikimedia Commons</i> doför enreeschte kann, fengk mer em [//mediawiki.org/wiki/Manual:$wgForeignFileRepos Handbooch].',
'config-cc-error' => 'Et Ußsöhke övver de <i lang="en">Creative Commons</i> iehr Projramm zum Lizänzbeshtemme hät nix jebraat.
Donn de Lizänz sellver beshtemme.',
'config-cc-again' => 'Noch ens neu ußsöhke&nbsp;…',
@@ -9681,7 +10243,7 @@ Donn de Lizänz sellver beshtemme.',
'config-cache-help' => 'Objäkte em Zwescheshpeisher faßhallde, dat heiß öff jebruchte Daate en der <i lang="en">cache</i> donn, bruche mer, öm MediaWiki flöcker ze maache,
Meddlere un jruuße Wiki-ẞaits sullte dat onbedengk ußnoze, un och bei klein Wikis weed mer et jood merke.',
'config-cache-none' => 'Keine Zweschshpeijsher (Et jeid_em Wiki nix verloore, ußer velleish Schnälleshkeid wann vill loss es)',
- 'config-cache-accel' => 'Ene Objäk<i lang="en">cache</i> vum PHP (<i lang="en">APC</i>, <i lang="en">eAccelerator</i>, <i lang="en">XCache</i>, udder <i lang="en">WinCache</i>)',
+ 'config-cache-accel' => 'Ene Objäk<i lang="en">cache</i> vum PHP (<i lang="en">APC</i>, <i lang="en">XCache</i>, udder <i lang="en">WinCache</i>)',
'config-cache-memcached' => 'Donn der <code lang="en">memcached</code> ẞööver nämme (Määt extra Enshtellunge un Opsäze nüüdesch)',
'config-memcached-servers' => 'De <code lang="en">memcached</code> ßöövere:',
'config-memcached-help' => 'Donn de Leß aanhjävve, met de <i lang="en">IP</i>-Addräße för der <code lang="en">memcached</code> ẞööver ze bruche.
@@ -9714,12 +10276,17 @@ Donn doför sorrje, dat dä Daatebangk-Aanwänder „$1“ en dämm Daatebangksc
'config-install-pg-plpgsql' => 'Ben noh dä Daatebangkshprooch <code lang="en">PL/pgSQL</code> aam söhke.',
'config-pg-no-plpgsql' => 'Do moß de Daatebangkshprooch <code lang="en">PL/pgSQL</code> en dä Daatebangk $1 enreeschte.',
'config-pg-no-create-privs' => 'Dä Daatebangk-Aanwänder för et Enreeschte hät nit jenooch Rääschde, öm ene andere Daatebangk-Aanwänder en dä Daatebangk aanzelääje.',
+ 'config-pg-not-in-role' => 'Dä aanjejovve Zohjang för et Web jiddet ald.
+Dä aanjejovve Zohjang för et Enschtalleere es keine <i lang="en">superuser<i> un es nit en de Web-Jropp, dröm kam_mer domet kein Dateije aanlääje, di däm Zohjang för et Web jehüüre.
+
+För MeedijaWiki mößße dämm ävver em Momang di Tabälle jehüüre.
+Dröm donn ene andere Name för dä Zohjang zom Wäb nämme, udder donn „retuur“ klicke, un jivv ene Zohjang för et Enschtalleere aan, dä jenooch Rääschte hät.',
'config-install-user' => 'Ben unse Daatebangk-Aanwänder en de Daatebangk am aanlääje.',
'config-install-user-alreadyexists' => 'Dä Aanwender „$1“ för dä Zohjref op de Daatebangk kann nit aanjelaat wääde, et jidd_en alld.',
'config-install-user-create-failed' => 'Dä Aanwender „$1“ för dä Zohjref op de Daatebangk kunnt nit aanjelaat wääde, wäje: <code lang="en">$2</code>',
'config-install-user-grant-failed' => 'Däm Daatebangk-Aanwänder sing Beräschtijunge ze säze däät nit fluppe wääje: $2',
'config-install-user-missing' => 'Dä aanjejovve Metmaacher „$1“ jidd_et nit.',
- 'config-install-user-missing-create' => '{{int:Config-install-user-missing}}
+ 'config-install-user-missing-create' => '{{int:Config-install-user-missing}}<!-- $1 -->
Donn e Höhksche en et Käßje „{{int:Createaccount}}“ onge, wann De dä aanlääje wells.',
'config-install-tables' => 'Ben de Daatebangk-Tabälle aam aanlääje.',
'config-install-tables-exist' => "'''Opjepaß''': Et schingk, dem MediaWiki sing Tabälle sin alt doh.
@@ -9807,6 +10374,8 @@ Dir fannt en am LocalSettings.php.",
'config-localsettings-key' => 'Aktualisatiounsschlëssel:',
'config-localsettings-badkey' => 'De Schlëssel deen Dir aginn hutt ass net korrekt',
'config-session-error' => 'Feeler beim Starte vun der Sessioun: $1',
+ 'config-no-session' => "D'Donnéeë vun ärer Sessioun si verluergaangen!
+Kuckt Är php.ini no a vergewëssert Iech datt <code>session.save_path</code> op adequate REpertoire agestallt ass.",
'config-your-language' => 'Är Sprooch',
'config-your-language-help' => 'Sicht déi Sprooch eraus déi Dir während der Installatioun benotze wëllt',
'config-wiki-language' => 'Sprooch vun der Wiki:',
@@ -9856,9 +10425,11 @@ 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-memory-bad' => "'''Opgepasst:''' De Parameter <code>memory_limit</code> vu PHP ass $1.
+Dat ass wahrscheinlech ze niddreg.
+D'Installatioun kéint net fonctionné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.
@@ -9885,7 +10456,10 @@ Wann et de Kont net gëtt, a wann den Installatiouns-Kont genuch Rechter huet, g
'config-db-schema' => 'Schema fir MediaWiki',
'config-db-schema-help' => "D'Schemaen hei driwwer si gewéinlech korrekt.
Ännert se nëmme wann Dir wësst datt et néideg ass.",
+ 'config-pg-test-error' => "Et ass net méiglech d'Datebank '''$1''' ze kontaktéieren: $2",
'config-sqlite-dir' => 'Repertoire vun den SQLite-Donnéeën',
+ 'config-oracle-def-ts' => "Standard 'tablespace':",
+ 'config-oracle-temp-ts' => "Temporären 'tablespace':",
'config-type-mysql' => 'MySQL',
'config-type-postgres' => 'PostgreSQL',
'config-type-sqlite' => 'SQLite',
@@ -10301,6 +10875,7 @@ $1
Ðко Ñте на заедничко (Ñподелено) вдомување, побарајте му на вдомителот да инÑталира Ñоодветен двигател за базата.
Ðко вие Ñамите го ÑоÑтавивте ова PHP, Ñменете ги поÑтавките така што ќе овозможите клиент на базата - на пр. Ñо кодот <code>./configure --with-mysql</code>.
Ðко инÑталиравте PHP од пакет на Debian или Ubuntu, тогаш ќе треба да го инÑталирате и модулот php5-mysql.',
+ 'config-outdated-sqlite' => "'''Предупредување''': имате SQLite $1. ÐајÑтарата допуштена верзија е $2. Затоа, SQLite ќе биде недоÑтапен.",
'config-no-fts3' => "'''Предупредување''': SQLite iе ÑоÑтавен без модулот [//sqlite.org/fts3.html FTS3] - за оваа база нема да има можноÑÑ‚ за пребарување.",
'config-register-globals' => "'''Предупредување: МожноÑта <code>[http://php.net/register_globals register_globals]</code> за PHP е овозможена.'''
'''Оневозможете ја ако е можно.'''
@@ -10330,11 +10905,11 @@ $1
'config-memory-bad' => "'''Предупредување:''' <code>memory_limit</code> за PHP изнеÑува $1.
Ова е веројатно премалку.
ИнÑталацијата може да не уÑпее!",
+ 'config-ctype' => "'''Фатална грешка''': PHP мора да Ñе ÑоÑтави Ñо поддршка за [http://www.php.net/manual/en/ctype.installation.php додатокот Ctype].",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] е инÑталиран',
'config-apc' => '[http://www.php.net/apc APC] е инÑталиран',
- 'config-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://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Предупредување:''' Ðе можев да го најдам [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
Кеширањето на објекти не е овозможено.",
'config-mod-security' => "'''Предупредување''': на вашиот опÑлужувач има овозможено [http://modsecurity.org/ mod_security]. Ðко не е поÑтавено како што треба, ова може да предизвика проблеми кај МедијаВики и други програми што им овозможуваат на кориÑниците да објавуваат произволни Ñодржини.
Погледнете ја [http://modsecurity.org/documentation/ mod_security документацијата] или обратете Ñе кај домаќинот ако наидете на Ñлучајни грешки.",
@@ -10654,7 +11229,7 @@ chmod a+w $3</pre>',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] е функција која им овозможува на викијата да кориÑтат Ñлики, звучни запиÑи и други мултимедијални Ñодржини од [//commons.wikimedia.org/ Заедничката Ризница].
За да може ова да работи, МедијаВики бара приÑтап до интернет.
-За повеќе информации за оваа функција и напатÑтвија за нејзино поÑтавување на вики (Ñите други оÑвен Ризницата), коноÑултирајте го [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos прирачникот].',
+За повеќе информации за оваа функција и напатÑтвија за нејзино поÑтавување на вики (Ñите други оÑвен Ризницата), коноÑултирајте го [//mediawiki.org/wiki/Manual:$wgForeignFileRepos прирачникот].',
'config-cc-error' => 'Изборникот на Creative Commons лиценца не даде резултати.
ВнеÑете го името на лиценцата рачно.',
'config-cc-again' => 'Одберете повторно...',
@@ -10664,7 +11239,7 @@ chmod a+w $3</pre>',
'config-cache-help' => 'Кеширањето на објекти Ñе кориÑти за зголемување на брзината на МедијаВики Ñо кеширање на чеÑто употребуваните податоци.
Ова многу Ñе препорачува на Ñредни до големи викија, но од тоа ќе имаат полза и малите викија.',
'config-cache-none' => 'Без кеширање (не Ñе оÑтранува ниедна функција, но може да влијае на брзината кај поголеми викија)',
- 'config-cache-accel' => 'Кеширање на PHP-објекти (APC, eAccelerator, XCache или WinCache)',
+ 'config-cache-accel' => 'Кеширање на PHP-објекти (APC, XCache или WinCache)',
'config-cache-memcached' => 'КориÑти Memcached (бара дополнително поÑтавување и нагодување)',
'config-memcached-servers' => 'Memcached-опÑлужувачи:',
'config-memcached-help' => 'СпиÑок на IP-адреÑи за употреба во Memcached.
@@ -10995,6 +11570,18 @@ Bagaimanapun, MediaWiki memerlukan PHP $2 ke atas.',
* @author Chrisportelli
*/
$messages['mt'] = array(
+ 'config-page-language' => 'Lingwa',
+ 'config-page-welcome' => 'Merħba fuq MediaWiki!',
+ 'config-page-dbconnect' => 'Aqbad mad-databażi',
+ 'config-page-upgrade' => 'Aġġorna l-installazzjoni eżistenti',
+ 'config-page-dbsettings' => 'Impostazzjonijiet tad-databażi',
+ 'config-page-name' => 'Isem',
+ 'config-page-options' => 'Għażliet',
+ 'config-page-install' => 'Installa',
+ 'config-page-complete' => 'Lesta!',
+ 'config-page-restart' => "Erġa' ibda l-installazzjoni",
+ 'config-page-readme' => 'Aqrani',
+ 'config-page-releasenotes' => 'Noti tal-verżjoni',
'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.
@@ -11051,6 +11638,454 @@ $messages['nan'] = array(
* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki的公布列單]',
);
+/** Norwegian (bokmål)‬ (‪Norsk (bokmål)‬)
+ * @author Event
+ * @author Jon Harald Søby
+ * @author Nghtwlkr
+ */
+$messages['nb'] = array(
+ 'config-desc' => 'Installasjonsprogrammet for MediaWiki',
+ 'config-title' => 'Installasjon av MediaWiki $1',
+ 'config-information' => 'Informasjon',
+ 'config-localsettings-upgrade' => 'En <code>LocalSettings.php</code>-fil har blitt oppdaget.
+For å oppgradere denne installasjonen, skriv inn verdien av <code>$wgUpgradeKey</code> i boksen nedenfor.
+Du finner denne i 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.
+Du kan øke dette ved å sette <code>session.gc_maxlifetime</code> i php.ini.
+Start installasjonsprosessen på nytt.',
+ 'config-no-session' => 'Dine øktdata ble tapt!
+Sjekk din php.ini og sørg for at <code>session.save_path</code> er satt til en passende mappe.',
+ 'config-your-language' => 'Ditt språk:',
+ 'config-your-language-help' => 'Velg et språk å bruke under installasjonsprosessen.',
+ 'config-wiki-language' => 'Wikispråk:',
+ 'config-wiki-language-help' => 'Velg språket som wikien hovedsakelig vil bli skrevet i.',
+ 'config-back' => '↠Tilbake',
+ 'config-continue' => 'Fortsett →',
+ 'config-page-language' => 'Språk',
+ 'config-page-welcome' => 'Velkommen til MediaWiki!',
+ 'config-page-dbconnect' => 'Koble til database',
+ 'config-page-upgrade' => 'Oppgrader eksisterende innstallasjon',
+ 'config-page-dbsettings' => 'Databaseinnstillinger',
+ 'config-page-name' => 'Navn',
+ 'config-page-options' => 'Valg',
+ 'config-page-install' => 'Innstaller',
+ 'config-page-complete' => 'Ferdig!',
+ 'config-page-restart' => 'Start installasjonen på nytt',
+ 'config-page-readme' => 'Les meg',
+ 'config-page-releasenotes' => 'Utgivelsesnotat',
+ 'config-page-copying' => 'Kopiering',
+ 'config-page-upgradedoc' => 'Oppgradering',
+ 'config-page-existingwiki' => 'Eksisterende wiki',
+ 'config-help-restart' => 'Ønsker du å fjerne alle lagrede data som du har skrevet inn og starte installasjonsprosessen på nytt?',
+ 'config-restart' => 'Ja, start på nytt',
+ 'config-welcome' => '=== Miljøsjekker ===
+Grunnleggende sjekker utføres for å se om dette miljøet er egnet for en MediaWiki-installasjon.
+Du bør oppgi resultatene fra disse sjekkene om du trenger hjelp under installasjonen.',
+ 'config-copyright' => "=== Opphavsrett og vilkår ===
+
+$1
+
+MediaWiki er fri programvare; du kan redistribuere det og/eller modifisere det under betingelsene i GNU General Public License som publisert av Free Software Foundation; enten versjon 2 av lisensen, eller (etter eget valg) enhver senere versjon.
+
+Dette programmet er distribuert i håp om at det vil være nyttig, men '''uten noen garanti'''; ikke engang implisitt garanti av '''salgbarhet''' eller '''egnethet for et bestemt formål'''.
+Se GNU General Public License for flere detaljer.
+
+Du skal ha mottatt <doclink href=Copying>en kopi av GNU General Public License</doclink> sammen med dette programmet; hvis ikke, skriv til Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA eller [http://www.gnu.org/copyleft/gpl.html les det på nettet].",
+ 'config-sidebar' => '* [//www.mediawiki.org MediaWiki hjem]
+* [//www.mediawiki.org/wiki/Help:Contents Brukerguide]
+* [//www.mediawiki.org/wiki/Manual:Contents Administratorguide]
+* [//www.mediawiki.org/wiki/Manual:FAQ OSS]
+----
+* <doclink href=Readme>Les meg</doclink>
+* <doclink href=ReleaseNotes>Utgivelsesnotater</doclink>
+* <doclink href=Copying>Kopiering</doclink>
+* <doclink href=UpgradeDoc>Oppgradering</doclink>',
+ 'config-env-good' => 'Miljøet har blitt sjekket.
+Du kan installere MediaWiki.',
+ 'config-env-bad' => 'Miljøet har blitt sjekket.
+Du kan installere MediaWiki.',
+ 'config-env-php' => 'PHP $1 er innstallert.',
+ 'config-env-php-toolow' => 'PHP $1 er installert.
+MediaWiki krever imidlertid PHP $2 eller høyere.',
+ 'config-unicode-using-utf8' => 'Bruker Brion Vibbers utf8_normalize.so for Unicode-normalisering.',
+ 'config-unicode-using-intl' => 'Bruker [http://pecl.php.net/intl intl PECL-utvidelsen] for Unicode-normalisering.',
+ 'config-unicode-pure-php-warning' => "'''Advarsel''': [http://pecl.php.net/intl intl PECL-utvidelsen] er ikke tilgjengelig for å håndtere Unicode-normaliseringen, faller tilbake til en langsommere ren-PHP-implementasjon.
+Om du kjører et nettsted med høy trafikk bør du lese litt om [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-normalisering].",
+ 'config-unicode-update-warning' => "'''Advarsel''': Den installerte versjonen av Unicode-normalisereren bruker en eldre versjon av [http://site.icu-project.org/ ICU-prosjektets] bibliotek.
+Du bør [//www.mediawiki.org/wiki/Unicode_normalization_considerations oppgradere] om du er bekymret for å bruke Unicode.",
+ 'config-no-db' => 'Fant ikke en passende databasedriver! Du må installere en databasedriver for PHP.
+Følgende databasetyper er støttet: $1
+
+Om du er på delt vertsskap, spør din vertsleverandør om å installere en passende databasedriver.
+Om du kompilerte PHP selv, rekonfigirer den med en aktivert databaseklient, for eksempel ved å bruke <code>./configure --with-mysql</code>.
+Om du installerte PHP fra en Debian- eller Ubuntu-pakke må du også installere modulen php5-mysql.',
+ 'config-no-fts3' => "'''Advarsel''': SQLite er kompilert uten [//sqlite.org/fts3.html FTS3-modulen], søkefunksjoner vil ikke være tilgjengelig på dette bakstykket.",
+ 'config-register-globals' => "'''Advarsel: PHPs <code>[http://php.net/register_globals register_globals]</code>-alternativ er aktivert.'''
+'''Deaktiver det om du kan.'''
+MediaWiki vil fungere, men tjeneren din er utsatt for potensielle sikkerhetssårbarheter.",
+ 'config-magic-quotes-runtime' => "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] er aktiv!'''
+Dette alternativet ødelegger inndata på en uforutsigbar måte.
+Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ 'config-magic-quotes-sybase' => "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] er aktiv!'''
+Dette alternativet ødelegger inndata på en uforutsigbar måte.
+Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ 'config-mbstring' => "'''Kritisk: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] er aktiv!'''
+Dette alternativet fører til feil og kan ødelegge data på en uforutsigbar måte.
+Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ 'config-ze1' => "'''Kritisk: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] er aktiv!'''
+Dette alternativet fører til horrible feil med MediaWiki.
+Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
+ 'config-safe-mode' => "'''Advarsel:''' PHPs [http://www.php.net/features.safe-mode safe mode] er aktiv.
+Det kan føre til problem, spesielt hvis du bruker støtte for filopplastinger og <code>math</code>.",
+ 'config-xml-bad' => 'PHPs XML-modul mangler.
+MediaWiki krever funksjonene i denne modulen og vil ikke virke i denne konfigurasjonen.
+Hvis du kjører Mandrak, installer pakken php-xml.',
+ 'config-pcre' => 'PCRE-støttemodulen ser ut til å mangle.
+MediaWiki krever funksjonene for de Perl-kompatible regulære uttrykkene for å virke.',
+ 'config-pcre-no-utf8' => "'''Fatal''': PHPs PCRE modul ser ut til å være kompilert uten PCRE_UTF8-støtte.
+MediaWiki krever UTF-8-støtte for å fungere riktig.",
+ 'config-memory-raised' => 'PHPs <code>memory_limit</code> er $1, økt til $2.',
+ 'config-memory-bad' => "'''Advarsel:''' PHPs <code>memory_limit</code> er $1.
+Dette er sannsynligvis for lavt.
+Installasjonen kan mislykkes!",
+ 'config-xcache' => '[http://xcache.lighttpd.net/ XCache] er innstallert',
+ 'config-apc' => '[http://www.php.net/apc APC] er innstallert',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache] er installert',
+ 'config-no-cache' => "'''Advarsel:''' Kunne ikke finne [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].
+Objekthurtiglagring er ikke aktivert.",
+ 'config-mod-security' => "'''Advarsel''': Din web-tjener har [http://modsecurity.org/ mod_security] påslått. Hvis denne er feilinnstilt, kan det gi problemer for MediaWiki eller annen programvare som tillater brukere å poste vilkårlig innhold.
+Sjekk [http://modsecurity.org/documentation/ mod_security-dokumentasjonen] eller ta kontakt med din nettleverandør hvis du opplever tilfeldige feil.",
+ 'config-diff3-bad' => 'GNU diff3 ikke funnet.',
+ 'config-imagemagick' => 'Fant ImageMagick: <code>$1</code>.
+Bildeminiatyrisering vil aktiveres om du aktiverer opplastinger.',
+ 'config-gd' => 'Fant innebygd GD-grafikkbibliotek.
+Bildeminiatyrisering vil aktiveres om du aktiverer opplastinger.',
+ 'config-no-scaling' => 'Kunne ikke finne GD-bibliotek eller ImageMagick.
+Bildeminiatyrisering vil være deaktivert.',
+ 'config-no-uri' => "'''Feil:''' Kunne ikke bestemme gjeldende URI.
+Installasjon avbrutt.",
+ 'config-no-cli-uri' => "'''Advarsel''': Ingen --scriptpath er angitt; bruker standard: <code>$1</code>.",
+ 'config-using-server' => 'Bruker servernavnet "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'Bruker server-URL "<nowiki>$1$2</nowiki>".',
+ 'config-uploads-not-safe' => "'''Advarsel:''' Din standardmappe for opplastinger <code>$1</code> er sårbar for kjøring av vilkårlige skript.
+Selv om MediaWiki sjekker alle opplastede filer for sikkerhetstrusler er det sterkt anbefalt å [//www.mediawiki.org/wiki/Manual:Security#Upload_security lukke denne sikkerhetssårbarheten] før du aktiverer opplastinger.",
+ 'config-no-cli-uploads-check' => "'''Advarsel:''' Din standard-katalog for opplastinger (<code>$1</code>) er ikke kontrollert for sårbarhet overfor vilkårlig skript-kjøring under CLI-installasjonen.",
+ 'config-brokenlibxml' => 'Ditt system har en kombinasjon av PHP- og libxml2-versjoner som er feilaktige og kan forårsake skjult dataødeleggelse i MediaWiki og andre web-applikasjoner.
+Oppgrader til PHP 5.2.9 eller nyere og libxml 2 2.7.3 eller nyere ([//bugs.php.net/bug.php?id=45996 Feil-liste for PHP]).
+Installasjon abortert.',
+ 'config-using531' => 'MediaWiki kan ikke brukes med PHP $1 på grunn av en feil med referanseparametere til <code>__call()</code>.
+Oppgrader til PHP 5.3.2 eller høyere, eller nedgrader til PHP 5.3.0 for å løse dette.
+Installasjonen avbrutt.',
+ 'config-suhosin-max-value-length' => 'Suhosin er installert og begrenser GET-parameterlengder til $1 bytes. MediaWiki\'s ResourceLoader-komponent klarer å komme rundt denne begrensningen, med med redusert ytelse. På mulig bør du sette 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.
+
+Hvis du bruker en delt nettvert bør verten din oppgi det korrekte vertsnavnet i deres dokumentasjon.
+
+Hvis du installerer på en Windowstjener og bruker MySQL kan det hende at «localhost» ikke virker som tjenernavnet. Hvis ikke, prøv «127.0.0.1» for den lokale IP-adressen.',
+ 'config-db-host-oracle' => 'Database TNS:',
+ 'config-db-host-oracle-help' => 'Skriv inn et gyldig [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; en tnsnames.ora-fil må være synlig for installasjonsprosessen.<br />Hvis du bruker klientbibliotek 10g eller nyere kan du også bruke navngivingsmetoden [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
+ 'config-db-wiki-settings' => 'Identifiser denne wikien',
+ 'config-db-name' => 'Databasenavn:',
+ 'config-db-name-help' => 'Velg et navn som identifiserer wikien din.
+Det bør ikke inneholde mellomrom.
+
+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',
+ 'config-db-wiki-help' => 'Skriv inn brukernavnet og passordet som vil bli brukt til å koble til databasen under normal wikidrift.
+Hvis kontoen ikke finnes, og installasjonskontoen har tilstrekkelige privilegier, vil denne brukerkontoen bli opprettet med et minimum av privilegier, tilstrekkelig for å operere wikien.',
+ 'config-db-prefix' => 'Databasetabellprefiks:',
+ 'config-db-prefix-help' => 'Hvis du trenger å dele en database mellom flere wikier, eller mellom MediaWiki og andre nettapplikasjoner, kan du velge å legge til et prefiks til alle tabellnavnene for å unngå konflikter.
+Ikke bruk mellomrom.
+
+Dette feltet er vanligvis tomt.',
+ 'config-db-charset' => 'Databasetegnsett',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binær',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 bakoverkompatibel UTF-8',
+ 'config-charset-help' => "'''Advarsel:''' Hvis du bruker '''bakoverkompatibel UTF-8''' på MySQL 4.1+, og deretter sikkerhetskopierer databasen med <code>mysqldump</code> kan det ødelegge alle ikke-ASCII tegn og irreversibelt ødelegge dine sikkerhetskopier!
+
+I '''binary mode''' lagrer MediaWiki UTF-8 tekst til databasen i binærfelt.
+Dette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spekteret av Unicode-tegn.
+I '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,
+men det vil ikke la deg lagre tegn over «[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
+ 'config-mysql-old' => 'MySQL $1 eller senere kreves, du har $2.',
+ 'config-db-port' => 'Databaseport:',
+ 'config-db-schema' => 'Skjema for MediaWiki',
+ 'config-db-schema-help' => 'Dette skjemaet er som regel riktig.
+Bare endre det hvis du vet at du trenger det.',
+ 'config-sqlite-dir' => 'SQLite datamappe:',
+ 'config-sqlite-dir-help' => "SQLite lagrer alle data i en enkelt fil.
+
+Mappen du oppgir må være skrivbar for nettjeneren under installasjonen.
+
+Den bør '''ikke''' være tilgjengelig fra nettet, dette er grunnen til at vi ikke legger det der PHP-filene dine er.
+
+Installasjonsprogrammet vil skrive en <code>.htaccess</code>-fil sammen med det, men om det mislykkes kan noen få tilgang til din råe database. Dette inkluderer rå brukerdata (e-postadresser, hashede passord) samt slettede revisjoner og andre begrensede data på wikien.
+
+Vurder å plassere databasen et helt annet sted, for eksempel i <code>/var/lib/mediawiki/yourwiki</code>.",
+ 'config-oracle-def-ts' => 'Standard tabellrom:',
+ 'config-oracle-temp-ts' => 'Midlertidig tabellrom:',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
+ 'config-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]). 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), undestreker (_) og bindestreker (-).',
+ 'config-invalid-db-prefix' => 'Ugyldig databaseprefiks «$1».
+Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9), undestreker (_) og bindestreker (-).',
+ 'config-connection-error' => '$1.
+
+Sjekk verten, brukernavnet og passordet nedenfor og prøv igjen.',
+ 'config-invalid-schema' => 'Ugyldig skjema for MediaWiki «$1».
+Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_).',
+ 'config-db-sys-create-oracle' => 'Installasjonsprogrammet støtter kun bruk av en SYSDBA-konto for opprettelse av en ny konto.',
+ 'config-db-sys-user-exists-oracle' => 'Brukerkontoen «$1» finnes allerede. SYSDBA kan kun brukes for oppretting av nye kontoer!',
+ 'config-postgres-old' => 'PostgreSQL $1 eller senere kreves, du har $2.',
+ 'config-sqlite-name-help' => 'Velg et navn som identifiserer wikien din.
+Ikke bruk mellomrom eller bindestreker.
+Dette vil bli brukt til SQLite-datafilnavnet.',
+ 'config-sqlite-parent-unwritable-group' => 'Kan ikke opprette datamappen <code><nowiki>$1</nowiki></code> fordi foreldremappen <code><nowiki>$2</nowiki></code> ikke er skrivbar for nettjeneren.
+
+Installasjonsprogrammet har bestemt brukeren nettjeneren din kjører som.
+Gjør <code><nowiki>$3</nowiki></code>-mappen skrivbar for denne for å fortsette.
+På et Unix/Linux-system, gjør:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => 'Kan ikke opprette datamappen <code><nowiki>$1</nowiki></code> fordi foreldremappen <code><nowiki>$2</nowiki></code> ikke er skrivbar for nettjeneren.
+
+Installasjonsprogrammet kunne ikke bestemme brukeren nettjeneren din kjører som.
+Gjør <code><nowiki>$3</nowiki></code>-mappen globalt skrivbar for denne (og andre!) for å fortsette.
+På et Unix/Linux-system, gjør:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => 'Feil under oppretting av datamappen «$1».
+Sjekk plasseringen og prøv igjen.',
+ 'config-sqlite-dir-unwritable' => 'Kan ikke skrive til mappen «$1».
+Endre dens tilganger slik at nettjeneren kan skrive til den og prøv igjen.',
+ 'config-sqlite-connection-error' => '$1.
+
+Sjekk datamappen og databasenavnet nedenfor og prøv igjen.',
+ 'config-sqlite-readonly' => 'Filen <code>$1</code> er ikke skrivbar.',
+ 'config-sqlite-cant-create-db' => 'Kunne ikke opprette databasefilen <code>$1</code>.',
+ 'config-sqlite-fts3-downgrade' => 'PHP mangler FTS3-støtte, nedgraderer tabeller',
+ 'config-can-upgrade' => "Det er MediaWiki-tabeller i denne databasen.
+For å oppgradere dem til MediaWiki $1, klikk '''Fortsett'''.",
+ 'config-upgrade-done' => "Oppgradering fullført.
+
+Du kan nå [$1 begynne å bruke wikien din].
+
+Hvis du ønsker å regenerere <code>LocalSettings.php</code>-filen din, klikk på knappen nedenfor.
+Dette er '''ikke anbefalt''' med mindre du har problemer med wikien din.",
+ 'config-upgrade-done-no-regenerate' => 'Oppgradering fullført.
+
+Du kan nå [$1 begynne å bruke wikien din].',
+ 'config-regenerate' => 'Regenerer LocalSettings.php →',
+ 'config-show-table-status' => 'SHOW TABLE STATUS etterspørselen mislyktes!',
+ 'config-unknown-collation' => "'''Advarsel:''' Databasen bruker en ukjent sortering.",
+ 'config-db-web-account' => 'Databasekonto for nettilgang',
+ 'config-db-web-help' => 'Velg brukernavnet og passordet som nettjeneren skal bruke for å koble til databasetjeneren under ordinær drift av wikien.',
+ 'config-db-web-account-same' => 'Bruk samme konto som for installasjonen',
+ 'config-db-web-create' => 'Opprett kontoen om den ikke finnes allerede',
+ 'config-db-web-no-create-privs' => 'Kontoen du oppga for installasjonen har ikke nok privilegier til å opprette en konto.
+Kontoen du oppgir her må finnes allerede.',
+ 'config-mysql-engine' => 'Lagringsmotor:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-engine-help' => "'''InnoDB''' er nesten alltid det beste alternativet siden den har god støtte for samtidighet («concurrency»).
+
+'''MyISAM''' kan være raskere i enbruker- eller les-bare-installasjoner.
+MyISAM-databaser har en tendens til å bli ødelagt oftere enn InnoDB-databaser.",
+ 'config-mysql-charset' => 'Databasetegnsett:',
+ 'config-mysql-binary' => 'Binær',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "I '''binary mode''' lagrer MediaWiki UTF-8 tekst til databasen i binærfelt.
+Dette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spekteret av Unicode-tegn.
+
+I '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,
+men det vil ikke la deg lagre tegn over «[//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.',
+ 'config-project-namespace' => 'Prosjektnavnerom:',
+ 'config-ns-generic' => 'Prosjekt',
+ 'config-ns-site-name' => 'Samme som wikinavnet: $1',
+ 'config-ns-other' => 'Annet (spesifiser)',
+ 'config-ns-other-default' => 'MyWiki',
+ 'config-project-namespace-help' => "Etter Wikipedias eksempel holder mange wikier deres sider med retningslinjer atskilt fra sine innholdssider, i et «'''prosjektnavnerom'''».
+Alle sidetitler i dette navnerommet starter med et gitt prefiks som du kan angi her.
+Tradisjonelt er dette prefikset avledet fra navnet på wikien, men det kan ikke innholde punkttegn som «#» eller «:».",
+ 'config-ns-invalid' => 'Det angitte navnerommet «<nowiki>$1</nowiki>» er ugyldig.
+Angi et annet prosjektnavnerom.',
+ 'config-ns-conflict' => 'Det angitte navnerommet «<nowiki>$1</nowiki>» er i konflikt med et standard MediaWiki-navnerom.
+Angi et annet prosjekt-navnerom.',
+ 'config-admin-box' => 'Administratorkonto',
+ 'config-admin-name' => 'Ditt navn:',
+ 'config-admin-password' => 'Passord:',
+ 'config-admin-password-confirm' => 'Passord igjen:',
+ 'config-admin-help' => 'Skriv inn ditt ønskede brukernavn her, for eksempel «Joe Bloggs».
+Dette er navnet du vil bruke for å logge inn på denne wikien.',
+ 'config-admin-name-blank' => 'Skriv inn et administratorbrukernavn.',
+ 'config-admin-name-invalid' => 'Det angitte brukernavnet «<nowiki>$1</nowiki>» er ugyldig.
+Angi et annet brukernavn.',
+ 'config-admin-password-blank' => 'Skriv inn et passord for administratorkontoen.',
+ 'config-admin-password-same' => 'Passordet skal ikke være det samme som brukernavnet.',
+ 'config-admin-password-mismatch' => 'De to passordene du skrev inn samsvarte ikke.',
+ 'config-admin-email' => 'E-postadresse:',
+ 'config-admin-email-help' => 'Skriv inn en e-postadresse her for at du skal kunne motta e-post fra andre brukere på wikien, tilbakestille passordet ditt, og bli varslet om endringer på sider på overvåkningslisten din. Du kan la dette feltet stå tomt.',
+ 'config-admin-error-user' => 'Intern feil ved opprettelse av en admin med navnet «<nowiki>$1</nowiki>».',
+ 'config-admin-error-password' => 'Intern feil ved opprettelse av passord for admin «<nowiki>$1</nowiki>»: <pre>$2</pre>',
+ 'config-admin-error-bademail' => 'Du har skrevet inn en ugyldig e-postadresse.',
+ 'config-subscribe' => 'Abonner på [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-postlisten for utgivelsesannonseringer].',
+ 'config-subscribe-help' => 'Dette er en lav-volums e-postliste brukt til utgivelsesannonseringer, herunder viktige sikkerhetsannonseringer.
+Du bør abonnere på den og oppdatere MediaWikiinstallasjonen din når nye versjoner kommer ut.',
+ 'config-almost-done' => 'Du er nesten ferdig!
+Du kan hoppe over de resterende konfigurasjonene og installere wikien nå.',
+ 'config-optional-continue' => 'Spør meg flere spørsmål.',
+ 'config-optional-skip' => 'Jeg er lei, bare installer wikien.',
+ 'config-profile' => 'Brukerrettighetsprofil:',
+ 'config-profile-wiki' => 'Tradisjonell wiki',
+ 'config-profile-no-anon' => 'Kontoopprettelse påkrevd',
+ 'config-profile-fishbowl' => 'Kun autoriserte bidragsytere',
+ 'config-profile-private' => 'Privat wiki',
+ 'config-profile-help' => "Wikier fungerer best når du lar så mange mennesker som mulig redigere den.
+I MediaWiki er det lett å revidere siste endringer og tilbakestille eventuell skade som er gjort av naive eller ondsinnede brukere.
+
+Imidlertid har mange funnet at MediaWiki er nyttig i mange roller, og av og til er det ikke lett å overbevise alle om fordelene med wikimåten.
+SÃ¥ du har valget.
+
+En '''{{int:config-profile-wiki}}''' tillater alle å redigere, selv uten å logge inn.
+En wiki med '''{{int:config-profile-no-anon}}''' tilbyr ekstra ansvarlighet, men kan avskrekke tilfeldige bidragsytere.
+
+'''{{int:config-profile-fishbowl}}'''-scenariet tillater godkjente brukere å redigere, mens publikum kan se sider, og også historikken.
+En '''{{int:config-profile-private}}''' tillater kun godkjente brukere å se sider, den samme gruppen som får lov til å redigere dem.
+
+Mer komplekse konfigurasjoner av brukerrettigheter er tilgjengelig etter installasjon, se det [//www.mediawiki.org/wiki/Manual:User_rights relevante manualavsnittet].",
+ '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-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',
+ 'config-enable-email' => 'Aktiver utgående e-post',
+ 'config-enable-email-help' => 'Hvis du vil at e-post skal virke må [http://www.php.net/manual/en/mail.configuration.php PHPs e-postinnstillinger] bli konfigurert riktig.
+Hvis du ikke ønsker noen e-postfunksjoner kan du deaktivere dem her.',
+ 'config-email-user' => 'Aktiver e-post mellom brukere',
+ 'config-email-user-help' => 'Tillat alle brukere å sende hverandre e-post hvis de har aktivert det i deres innstillinger.',
+ 'config-email-usertalk' => 'Aktiver brukerdiskusjonssidevarsler',
+ 'config-email-usertalk-help' => 'Tillat brukere å motta varsler ved endringer på deres brukerdiskusjonsside hvis de har aktivert dette i deres innstillinger.',
+ 'config-email-watchlist' => 'Aktiver overvåkningslistevarsler',
+ 'config-email-watchlist-help' => 'Tillat brukere å motta varsler ved endringer på deres overvåkede sider hvis de har aktivert dette i deres innstillinger.',
+ 'config-email-auth' => 'Aktiver e-postautentisering',
+ 'config-email-auth-help' => "Om dette alternativet er aktivert må brukere bekrefte sin e-postadresse ved å bruke en lenke som blir sendt til dem når de setter eller endrer adressen sin.
+Kun autentiserte e-postadresser kan motta e-post fra andre brukere eller endringsvarsel.
+Å sette dette valget er '''anbefalt''' for offentlige wikier på grunn av potensiell misbruk av e-postfunksjonene.",
+ 'config-email-sender' => 'Svar-e-postadresse:',
+ 'config-email-sender-help' => 'Skriv inn e-postadressen som skal brukes som svar-adresse ved utgående e-post.
+Det er hit returmeldinger vil bli sendt.
+Mange e-posttjenere krever at minst domenenavnet må være gyldig.',
+ 'config-upload-settings' => 'Bilde- og filopplastinger',
+ 'config-upload-enable' => 'Aktiver filopplastinger',
+ 'config-upload-help' => 'Filopplastinger kan potensielt utsette tjeneren din for sikkerhetsrisikoer.
+For mer informasjon, les [//www.mediawiki.org/wiki/Manual:Security sikkerhetsseksjonen] i manualen.
+
+For å aktivere filopplastinger, endre modusen i <code>images</code>-undermappen i MediaWikis rotmappe slik at nettjeneren kan skrive til den.
+Aktiver så dette alternativet.',
+ 'config-upload-deleted' => 'Mappe for slettede filer:',
+ 'config-upload-deleted-help' => 'Velg en mappe for å arkivere slettede filer.
+Ideelt burde ikke denne være tilgjengelig for nettet.',
+ 'config-logo' => 'Logo-URL:',
+ 'config-logo-help' => 'MediaWikis standarddrakt inkluderer plass til en 135x160 pikslers logo i øvre venstre hjørne.
+Last opp et bilde i passende størrelse og skriv inn nettadressen her.
+
+Hvis du ikke ønsker en logo, la denne boksen være tom.',
+ 'config-instantcommons' => 'Aktiver Instant Commons',
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] er en funksjon som gjør det mulig for wikier å bruke bilder, lyder og andre media funnet på nettstedet [//commons.wikimedia.org/ Wikimedia Commons].
+For å gjøre dette krever MediaWiki tilgang til internett.
+
+For mer informasjon om denne funksjonen, inklusive instruksjoner om hvordan man setter opp dette for andre wikier enn Wikimedia Commons, konsulter [//mediawiki.org/wiki/Manual:$wgForeignFileRepos manualen].',
+ 'config-cc-again' => 'Velg igjen...',
+ 'config-cc-not-chosen' => 'Velg hvilken Creative Commons-lisens du ønsker og klikk «fortsett».',
+ 'config-advanced-settings' => 'Avansert konfigurasjon',
+ 'config-extensions' => 'Utvidelser',
+ 'config-install-step-done' => 'ferdig',
+ 'config-install-step-failed' => 'mislyktes',
+ 'config-install-extensions' => 'Inkludert utvidelser',
+ 'config-install-database' => 'Setter opp database',
+ 'config-install-user' => 'Oppretter databasebruker',
+ 'config-install-user-alreadyexists' => 'Brukeren «$1» finnes allerede',
+ 'config-install-user-create-failed' => 'Opprettelse av brukeren «$1» mislyktes: $2',
+ 'config-install-user-grant-failed' => 'Å gi tillatelse til brukeren «$1» mislyktes: $2',
+ 'config-install-tables' => 'Oppretter tabeller',
+ 'config-install-mainpage-failed' => 'Kunne ikke sette inn hovedside: $1',
+ 'config-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]',
+);
+
/** Low German (Plattdüütsch) */
$messages['nds'] = array(
'mainpagetext' => "'''De MediaWiki-Software is mit Spood installeert worrn.'''",
@@ -11186,6 +12221,7 @@ De volgende databases worden ondersteund: $1.
Als u op een gedeelde omgeving zit, vraag dan aan uw hostingprovider een geschikte databasedriver te installeren.
Als u PHP zelf hebt gecompileerd, wijzig dan uw instellingen zodat een databasedriver wordt geactiveerd, bijvoorbeeld via <code>./configure --with-mysql</code>.
Als u PHP hebt geïnstalleerd via een Debian- of Ubuntu-package, installeer dan ook de module php5-mysql.',
+ 'config-outdated-sqlite' => "''' Waarschuwing:''' u gebruikt SQLite $1. SQLite is niet beschikbaar omdat de minimaal vereiste versie $2 is.",
'config-no-fts3' => "'''Waarschuwing''': SQLite is gecompileerd zonder de module [//sqlite.org/fts3.html FTS3]; er zijn geen zoekfuncties niet beschikbaar.",
'config-register-globals' => "'''Waarschuwing: De PHP-optie <code>[http://php.net/register_globals register_globals]</code> is ingeschakeld.'''
'''Schakel deze uit als dat mogelijk is.'''
@@ -11216,11 +12252,11 @@ 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-ctype' => "'''Fataal:''' PHP moet gecompileerd zijn met ondersteuning voor de [http://www.php.net/manual/en/ctype.installation.php extensie Ctype].",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache] is op dit moment geïnstalleerd',
'config-apc' => '[http://www.php.net/apc APC] is op dit moment geïnstalleerd',
- 'config-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.
+ 'config-no-cache' => "'''Waarschuwing:''' [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.",
@@ -11544,7 +12580,7 @@ Als u geen logo wilt gebruiken, kunt u dit veld leeg laten.',
'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].',
+Meer informatie over deze functie en hoe deze in te stellen voor andere wiki\'s dan Wikimedia Commons is te vinden in de [//mediawiki.org/wiki/Manual:$wgForeignFileRepos handleiding].',
'config-cc-error' => 'De licentiekiezer van Creative Commons heeft geen resultaat opgeleverd.
Voer de licentie handmatig in.',
'config-cc-again' => 'Opnieuw kiezen...',
@@ -11555,7 +12591,7 @@ Voer de licentie handmatig in.',
Middelgrote tot grote websites wordt geadviseerd dit in te schakelen en ook kleine sites merken de voordelen.',
'config-cache-none' => 'Niets cachen.
Er gaat geen functionaliteit verloren, maar dit kan invloed hebben op de snelheid.',
- 'config-cache-accel' => 'Cachen van objecten via PHP (APC, eAccelerator, XCache of WinCache)',
+ 'config-cache-accel' => 'Cachen van objecten via PHP (APC, XCache of WinCache)',
'config-cache-memcached' => 'Memcached gebruiken (dit vereist aanvullende instellingen)',
'config-memcached-servers' => 'Memcachedservers:',
'config-memcached-help' => 'Lijst met IP-adressen te gebruiken voor Memcached.
@@ -11695,455 +12731,6 @@ Berre bruk ASCII-bokstavar (a-z, A-Z), tal (0-9) og undestrekar (_).',
* [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
- */
-$messages['no'] = array(
- 'config-desc' => 'Installasjonsprogrammet for MediaWiki',
- 'config-title' => 'Installasjon av MediaWiki $1',
- 'config-information' => 'Informasjon',
- 'config-localsettings-upgrade' => 'En <code>LocalSettings.php</code>-fil har blitt oppdaget.
-For å oppgradere denne installasjonen, skriv inn verdien av <code>$wgUpgradeKey</code> i boksen nedenfor.
-Du finner denne i 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.
-Du kan øke dette ved å sette <code>session.gc_maxlifetime</code> i php.ini.
-Start installasjonsprosessen på nytt.',
- 'config-no-session' => 'Dine øktdata ble tapt!
-Sjekk din php.ini og sørg for at <code>session.save_path</code> er satt til en passende mappe.',
- 'config-your-language' => 'Ditt språk:',
- 'config-your-language-help' => 'Velg et språk å bruke under installasjonsprosessen.',
- 'config-wiki-language' => 'Wikispråk:',
- 'config-wiki-language-help' => 'Velg språket som wikien hovedsakelig vil bli skrevet i.',
- 'config-back' => '↠Tilbake',
- 'config-continue' => 'Fortsett →',
- 'config-page-language' => 'Språk',
- 'config-page-welcome' => 'Velkommen til MediaWiki!',
- 'config-page-dbconnect' => 'Koble til database',
- 'config-page-upgrade' => 'Oppgrader eksisterende innstallasjon',
- 'config-page-dbsettings' => 'Databaseinnstillinger',
- 'config-page-name' => 'Navn',
- 'config-page-options' => 'Valg',
- 'config-page-install' => 'Innstaller',
- 'config-page-complete' => 'Ferdig!',
- 'config-page-restart' => 'Start installasjonen på nytt',
- 'config-page-readme' => 'Les meg',
- 'config-page-releasenotes' => 'Utgivelsesnotat',
- 'config-page-copying' => 'Kopiering',
- 'config-page-upgradedoc' => 'Oppgradering',
- 'config-page-existingwiki' => 'Eksisterende wiki',
- 'config-help-restart' => 'Ønsker du å fjerne alle lagrede data som du har skrevet inn og starte installasjonsprosessen på nytt?',
- 'config-restart' => 'Ja, start på nytt',
- 'config-welcome' => '=== Miljøsjekker ===
-Grunnleggende sjekker utføres for å se om dette miljøet er egnet for en MediaWiki-installasjon.
-Du bør oppgi resultatene fra disse sjekkene om du trenger hjelp under installasjonen.',
- 'config-copyright' => "=== Opphavsrett og vilkår ===
-
-$1
-
-MediaWiki er fri programvare; du kan redistribuere det og/eller modifisere det under betingelsene i GNU General Public License som publisert av Free Software Foundation; enten versjon 2 av lisensen, eller (etter eget valg) enhver senere versjon.
-
-Dette programmet er distribuert i håp om at det vil være nyttig, men '''uten noen garanti'''; ikke engang implisitt garanti av '''salgbarhet''' eller '''egnethet for et bestemt formål'''.
-Se GNU General Public License for flere detaljer.
-
-Du skal ha mottatt <doclink href=Copying>en kopi av GNU General Public License</doclink> sammen med dette programmet; hvis ikke, skriv til Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA eller [http://www.gnu.org/copyleft/gpl.html les det på nettet].",
- 'config-sidebar' => '* [//www.mediawiki.org MediaWiki hjem]
-* [//www.mediawiki.org/wiki/Help:Contents Brukerguide]
-* [//www.mediawiki.org/wiki/Manual:Contents Administratorguide]
-* [//www.mediawiki.org/wiki/Manual:FAQ OSS]
-----
-* <doclink href=Readme>Les meg</doclink>
-* <doclink href=ReleaseNotes>Utgivelsesnotater</doclink>
-* <doclink href=Copying>Kopiering</doclink>
-* <doclink href=UpgradeDoc>Oppgradering</doclink>',
- 'config-env-good' => 'Miljøet har blitt sjekket.
-Du kan installere MediaWiki.',
- 'config-env-bad' => 'Miljøet har blitt sjekket.
-Du kan installere MediaWiki.',
- 'config-env-php' => 'PHP $1 er innstallert.',
- 'config-env-php-toolow' => 'PHP $1 er installert.
-MediaWiki krever imidlertid PHP $2 eller høyere.',
- 'config-unicode-using-utf8' => 'Bruker Brion Vibbers utf8_normalize.so for Unicode-normalisering.',
- 'config-unicode-using-intl' => 'Bruker [http://pecl.php.net/intl intl PECL-utvidelsen] for Unicode-normalisering.',
- 'config-unicode-pure-php-warning' => "'''Advarsel''': [http://pecl.php.net/intl intl PECL-utvidelsen] er ikke tilgjengelig for å håndtere Unicode-normaliseringen, faller tilbake til en langsommere ren-PHP-implementasjon.
-Om du kjører et nettsted med høy trafikk bør du lese litt om [//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode-normalisering].",
- 'config-unicode-update-warning' => "'''Advarsel''': Den installerte versjonen av Unicode-normalisereren bruker en eldre versjon av [http://site.icu-project.org/ ICU-prosjektets] bibliotek.
-Du bør [//www.mediawiki.org/wiki/Unicode_normalization_considerations oppgradere] om du er bekymret for å bruke Unicode.",
- 'config-no-db' => 'Fant ikke en passende databasedriver! Du må installere en databasedriver for PHP.
-Følgende databasetyper er støttet: $1
-
-Om du er på delt vertsskap, spør din vertsleverandør om å installere en passende databasedriver.
-Om du kompilerte PHP selv, rekonfigirer den med en aktivert databaseklient, for eksempel ved å bruke <code>./configure --with-mysql</code>.
-Om du installerte PHP fra en Debian- eller Ubuntu-pakke må du også installere modulen php5-mysql.',
- 'config-no-fts3' => "'''Advarsel''': SQLite er kompilert uten [//sqlite.org/fts3.html FTS3-modulen], søkefunksjoner vil ikke være tilgjengelig på dette bakstykket.",
- 'config-register-globals' => "'''Advarsel: PHPs <code>[http://php.net/register_globals register_globals]</code>-alternativ er aktivert.'''
-'''Deaktiver det om du kan.'''
-MediaWiki vil fungere, men tjeneren din er utsatt for potensielle sikkerhetssårbarheter.",
- 'config-magic-quotes-runtime' => "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] er aktiv!'''
-Dette alternativet ødelegger inndata på en uforutsigbar måte.
-Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
- 'config-magic-quotes-sybase' => "'''Kritisk: [http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] er aktiv!'''
-Dette alternativet ødelegger inndata på en uforutsigbar måte.
-Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
- 'config-mbstring' => "'''Kritisk: [http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] er aktiv!'''
-Dette alternativet fører til feil og kan ødelegge data på en uforutsigbar måte.
-Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
- 'config-ze1' => "'''Kritisk: [http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode] er aktiv!'''
-Dette alternativet fører til horrible feil med MediaWiki.
-Du kan ikke installere eller bruke MediaWiki med mindre dette alternativet deaktiveres.",
- 'config-safe-mode' => "'''Advarsel:''' PHPs [http://www.php.net/features.safe-mode safe mode] er aktiv.
-Det kan føre til problem, spesielt hvis du bruker støtte for filopplastinger og <code>math</code>.",
- 'config-xml-bad' => 'PHPs XML-modul mangler.
-MediaWiki krever funksjonene i denne modulen og vil ikke virke i denne konfigurasjonen.
-Hvis du kjører Mandrak, installer pakken php-xml.',
- 'config-pcre' => 'PCRE-støttemodulen ser ut til å mangle.
-MediaWiki krever funksjonene for de Perl-kompatible regulære uttrykkene for å virke.',
- 'config-pcre-no-utf8' => "'''Fatal''': PHPs PCRE modul ser ut til å være kompilert uten PCRE_UTF8-støtte.
-MediaWiki krever UTF-8-støtte for å fungere riktig.",
- 'config-memory-raised' => 'PHPs <code>memory_limit</code> er $1, økt til $2.',
- 'config-memory-bad' => "'''Advarsel:''' PHPs <code>memory_limit</code> er $1.
-Dette er sannsynligvis for lavt.
-Installasjonen kan mislykkes!",
- 'config-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://xcache.lighttpd.net/ XCache] eller [http://www.iis.net/download/WinCacheForPhp WinCache].
-Objekthurtiglagring er ikke aktivert.",
- 'config-mod-security' => "'''Advarsel''': Din web-tjener har [http://modsecurity.org/ mod_security] påslått. Hvis denne er feilinnstilt, kan det gi problemer for MediaWiki eller annen programvare som tillater brukere å poste vilkårlig innhold.
-Sjekk [http://modsecurity.org/documentation/ mod_security-dokumentasjonen] eller ta kontakt med din nettleverandør hvis du opplever tilfeldige feil.",
- 'config-diff3-bad' => 'GNU diff3 ikke funnet.',
- 'config-imagemagick' => 'Fant ImageMagick: <code>$1</code>.
-Bildeminiatyrisering vil aktiveres om du aktiverer opplastinger.',
- 'config-gd' => 'Fant innebygd GD-grafikkbibliotek.
-Bildeminiatyrisering vil aktiveres om du aktiverer opplastinger.',
- 'config-no-scaling' => 'Kunne ikke finne GD-bibliotek eller ImageMagick.
-Bildeminiatyrisering vil være deaktivert.',
- 'config-no-uri' => "'''Feil:''' Kunne ikke bestemme gjeldende URI.
-Installasjon avbrutt.",
- 'config-no-cli-uri' => "'''Advarsel''': Ingen --scriptpath er angitt; bruker standard: <code>$1</code>.",
- 'config-using-server' => 'Bruker servernavnet "<nowiki>$1</nowiki>".',
- 'config-using-uri' => 'Bruker server-URL "<nowiki>$1$2</nowiki>".',
- 'config-uploads-not-safe' => "'''Advarsel:''' Din standardmappe for opplastinger <code>$1</code> er sårbar for kjøring av vilkårlige skript.
-Selv om MediaWiki sjekker alle opplastede filer for sikkerhetstrusler er det sterkt anbefalt å [//www.mediawiki.org/wiki/Manual:Security#Upload_security lukke denne sikkerhetssårbarheten] før du aktiverer opplastinger.",
- 'config-no-cli-uploads-check' => "'''Advarsel:''' Din standard-katalog for opplastinger (<code>$1</code>) er ikke kontrollert for sårbarhet overfor vilkårlig skript-kjøring under CLI-installasjonen.",
- 'config-brokenlibxml' => 'Ditt system har en kombinasjon av PHP- og libxml2-versjoner som er feilaktige og kan forårsake skjult dataødeleggelse i MediaWiki og andre web-applikasjoner.
-Oppgrader til PHP 5.2.9 eller nyere og libxml 2 2.7.3 eller nyere ([//bugs.php.net/bug.php?id=45996 Feil-liste for PHP]).
-Installasjon abortert.',
- 'config-using531' => 'MediaWiki kan ikke brukes med PHP $1 på grunn av en feil med referanseparametere til <code>__call()</code>.
-Oppgrader til PHP 5.3.2 eller høyere, eller nedgrader til PHP 5.3.0 for å løse dette.
-Installasjonen avbrutt.',
- 'config-suhosin-max-value-length' => 'Suhosin er installert og begrenser GET-parameterlengder til $1 bytes. MediaWiki\'s ResourceLoader-komponent klarer å komme rundt denne begrensningen, med med redusert ytelse. På mulig bør du sette 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.
-
-Hvis du bruker en delt nettvert bør verten din oppgi det korrekte vertsnavnet i deres dokumentasjon.
-
-Hvis du installerer på en Windowstjener og bruker MySQL kan det hende at «localhost» ikke virker som tjenernavnet. Hvis ikke, prøv «127.0.0.1» for den lokale IP-adressen.',
- 'config-db-host-oracle' => 'Database TNS:',
- 'config-db-host-oracle-help' => 'Skriv inn et gyldig [http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm Local Connect Name]; en tnsnames.ora-fil må være synlig for installasjonsprosessen.<br />Hvis du bruker klientbibliotek 10g eller nyere kan du også bruke navngivingsmetoden [http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm Easy Connect].',
- 'config-db-wiki-settings' => 'Identifiser denne wikien',
- 'config-db-name' => 'Databasenavn:',
- 'config-db-name-help' => 'Velg et navn som identifiserer wikien din.
-Det bør ikke inneholde mellomrom.
-
-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',
- 'config-db-wiki-help' => 'Skriv inn brukernavnet og passordet som vil bli brukt til å koble til databasen under normal wikidrift.
-Hvis kontoen ikke finnes, og installasjonskontoen har tilstrekkelige privilegier, vil denne brukerkontoen bli opprettet med et minimum av privilegier, tilstrekkelig for å operere wikien.',
- 'config-db-prefix' => 'Databasetabellprefiks:',
- 'config-db-prefix-help' => 'Hvis du trenger å dele en database mellom flere wikier, eller mellom MediaWiki og andre nettapplikasjoner, kan du velge å legge til et prefiks til alle tabellnavnene for å unngå konflikter.
-Ikke bruk mellomrom.
-
-Dette feltet er vanligvis tomt.',
- 'config-db-charset' => 'Databasetegnsett',
- 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 binær',
- 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
- 'config-charset-mysql4' => 'MySQL 4.0 bakoverkompatibel UTF-8',
- 'config-charset-help' => "'''Advarsel:''' Hvis du bruker '''bakoverkompatibel UTF-8''' på MySQL 4.1+, og deretter sikkerhetskopierer databasen med <code>mysqldump</code> kan det ødelegge alle ikke-ASCII tegn og irreversibelt ødelegge dine sikkerhetskopier!
-
-I '''binary mode''' lagrer MediaWiki UTF-8 tekst til databasen i binærfelt.
-Dette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spekteret av Unicode-tegn.
-I '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,
-men det vil ikke la deg lagre tegn over «[//en.wikipedia.org/wiki/Mapping_of_Unicode_character_planes the Basic Multilingual Plane]».",
- 'config-mysql-old' => 'MySQL $1 eller senere kreves, du har $2.',
- 'config-db-port' => 'Databaseport:',
- 'config-db-schema' => 'Skjema for MediaWiki',
- 'config-db-schema-help' => 'Dette skjemaet er som regel riktig.
-Bare endre det hvis du vet at du trenger det.',
- 'config-sqlite-dir' => 'SQLite datamappe:',
- 'config-sqlite-dir-help' => "SQLite lagrer alle data i en enkelt fil.
-
-Mappen du oppgir må være skrivbar for nettjeneren under installasjonen.
-
-Den bør '''ikke''' være tilgjengelig fra nettet, dette er grunnen til at vi ikke legger det der PHP-filene dine er.
-
-Installasjonsprogrammet vil skrive en <code>.htaccess</code>-fil sammen med det, men om det mislykkes kan noen få tilgang til din råe database. Dette inkluderer rå brukerdata (e-postadresser, hashede passord) samt slettede revisjoner og andre begrensede data på wikien.
-
-Vurder å plassere databasen et helt annet sted, for eksempel i <code>/var/lib/mediawiki/yourwiki</code>.",
- 'config-oracle-def-ts' => 'Standard tabellrom:',
- 'config-oracle-temp-ts' => 'Midlertidig tabellrom:',
- 'config-type-mysql' => 'MySQL',
- 'config-type-postgres' => 'PostgreSQL',
- 'config-type-sqlite' => 'SQLite',
- 'config-type-oracle' => 'Oracle',
- 'config-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]). 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), undestreker (_) og bindestreker (-).',
- 'config-invalid-db-prefix' => 'Ugyldig databaseprefiks «$1».
-Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9), undestreker (_) og bindestreker (-).',
- 'config-connection-error' => '$1.
-
-Sjekk verten, brukernavnet og passordet nedenfor og prøv igjen.',
- 'config-invalid-schema' => 'Ugyldig skjema for MediaWiki «$1».
-Bruk bare ASCII-bokstaver (a-z, A-Z), tall (0-9) og undestreker (_).',
- 'config-db-sys-create-oracle' => 'Installasjonsprogrammet støtter kun bruk av en SYSDBA-konto for opprettelse av en ny konto.',
- 'config-db-sys-user-exists-oracle' => 'Brukerkontoen «$1» finnes allerede. SYSDBA kan kun brukes for oppretting av nye kontoer!',
- 'config-postgres-old' => 'PostgreSQL $1 eller senere kreves, du har $2.',
- 'config-sqlite-name-help' => 'Velg et navn som identifiserer wikien din.
-Ikke bruk mellomrom eller bindestreker.
-Dette vil bli brukt til SQLite-datafilnavnet.',
- 'config-sqlite-parent-unwritable-group' => 'Kan ikke opprette datamappen <code><nowiki>$1</nowiki></code> fordi foreldremappen <code><nowiki>$2</nowiki></code> ikke er skrivbar for nettjeneren.
-
-Installasjonsprogrammet har bestemt brukeren nettjeneren din kjører som.
-Gjør <code><nowiki>$3</nowiki></code>-mappen skrivbar for denne for å fortsette.
-På et Unix/Linux-system, gjør:
-
-<pre>cd $2
-mkdir $3
-chgrp $4 $3
-chmod g+w $3</pre>',
- 'config-sqlite-parent-unwritable-nogroup' => 'Kan ikke opprette datamappen <code><nowiki>$1</nowiki></code> fordi foreldremappen <code><nowiki>$2</nowiki></code> ikke er skrivbar for nettjeneren.
-
-Installasjonsprogrammet kunne ikke bestemme brukeren nettjeneren din kjører som.
-Gjør <code><nowiki>$3</nowiki></code>-mappen globalt skrivbar for denne (og andre!) for å fortsette.
-På et Unix/Linux-system, gjør:
-
-<pre>cd $2
-mkdir $3
-chmod a+w $3</pre>',
- 'config-sqlite-mkdir-error' => 'Feil under oppretting av datamappen «$1».
-Sjekk plasseringen og prøv igjen.',
- 'config-sqlite-dir-unwritable' => 'Kan ikke skrive til mappen «$1».
-Endre dens tilganger slik at nettjeneren kan skrive til den og prøv igjen.',
- 'config-sqlite-connection-error' => '$1.
-
-Sjekk datamappen og databasenavnet nedenfor og prøv igjen.',
- 'config-sqlite-readonly' => 'Filen <code>$1</code> er ikke skrivbar.',
- 'config-sqlite-cant-create-db' => 'Kunne ikke opprette databasefilen <code>$1</code>.',
- 'config-sqlite-fts3-downgrade' => 'PHP mangler FTS3-støtte, nedgraderer tabeller',
- 'config-can-upgrade' => "Det er MediaWiki-tabeller i denne databasen.
-For å oppgradere dem til MediaWiki $1, klikk '''Fortsett'''.",
- 'config-upgrade-done' => "Oppgradering fullført.
-
-Du kan nå [$1 begynne å bruke wikien din].
-
-Hvis du ønsker å regenerere <code>LocalSettings.php</code>-filen din, klikk på knappen nedenfor.
-Dette er '''ikke anbefalt''' med mindre du har problemer med wikien din.",
- 'config-upgrade-done-no-regenerate' => 'Oppgradering fullført.
-
-Du kan nå [$1 begynne å bruke wikien din].',
- 'config-regenerate' => 'Regenerer LocalSettings.php →',
- 'config-show-table-status' => 'SHOW TABLE STATUS etterspørselen mislyktes!',
- 'config-unknown-collation' => "'''Advarsel:''' Databasen bruker en ukjent sortering.",
- 'config-db-web-account' => 'Databasekonto for nettilgang',
- 'config-db-web-help' => 'Velg brukernavnet og passordet som nettjeneren skal bruke for å koble til databasetjeneren under ordinær drift av wikien.',
- 'config-db-web-account-same' => 'Bruk samme konto som for installasjonen',
- 'config-db-web-create' => 'Opprett kontoen om den ikke finnes allerede',
- 'config-db-web-no-create-privs' => 'Kontoen du oppga for installasjonen har ikke nok privilegier til å opprette en konto.
-Kontoen du oppgir her må finnes allerede.',
- 'config-mysql-engine' => 'Lagringsmotor:',
- 'config-mysql-innodb' => 'InnoDB',
- 'config-mysql-myisam' => 'MyISAM',
- 'config-mysql-engine-help' => "'''InnoDB''' er nesten alltid det beste alternativet siden den har god støtte for samtidighet («concurrency»).
-
-'''MyISAM''' kan være raskere i enbruker- eller les-bare-installasjoner.
-MyISAM-databaser har en tendens til å bli ødelagt oftere enn InnoDB-databaser.",
- 'config-mysql-charset' => 'Databasetegnsett:',
- 'config-mysql-binary' => 'Binær',
- 'config-mysql-utf8' => 'UTF-8',
- 'config-mysql-charset-help' => "I '''binary mode''' lagrer MediaWiki UTF-8 tekst til databasen i binærfelt.
-Dette er mer effektivt enn MySQLs UTF-8 modus og tillater deg å bruke hele spekteret av Unicode-tegn.
-
-I '''UTF-8 mode''' vil MySQL vite hvilket tegnsett dataene dine er i og kan presentere og konvertere det på en riktig måte,
-men det vil ikke la deg lagre tegn over «[//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.',
- 'config-project-namespace' => 'Prosjektnavnerom:',
- 'config-ns-generic' => 'Prosjekt',
- 'config-ns-site-name' => 'Samme som wikinavnet: $1',
- 'config-ns-other' => 'Annet (spesifiser)',
- 'config-ns-other-default' => 'MyWiki',
- 'config-project-namespace-help' => "Etter Wikipedias eksempel holder mange wikier deres sider med retningslinjer atskilt fra sine innholdssider, i et «'''prosjektnavnerom'''».
-Alle sidetitler i dette navnerommet starter med et gitt prefiks som du kan angi her.
-Tradisjonelt er dette prefikset avledet fra navnet på wikien, men det kan ikke innholde punkttegn som «#» eller «:».",
- 'config-ns-invalid' => 'Det angitte navnerommet «<nowiki>$1</nowiki>» er ugyldig.
-Angi et annet prosjektnavnerom.',
- 'config-ns-conflict' => 'Det angitte navnerommet «<nowiki>$1</nowiki>» er i konflikt med et standard MediaWiki-navnerom.
-Angi et annet prosjekt-navnerom.',
- 'config-admin-box' => 'Administratorkonto',
- 'config-admin-name' => 'Ditt navn:',
- 'config-admin-password' => 'Passord:',
- 'config-admin-password-confirm' => 'Passord igjen:',
- 'config-admin-help' => 'Skriv inn ditt ønskede brukernavn her, for eksempel «Joe Bloggs».
-Dette er navnet du vil bruke for å logge inn på denne wikien.',
- 'config-admin-name-blank' => 'Skriv inn et administratorbrukernavn.',
- 'config-admin-name-invalid' => 'Det angitte brukernavnet «<nowiki>$1</nowiki>» er ugyldig.
-Angi et annet brukernavn.',
- 'config-admin-password-blank' => 'Skriv inn et passord for administratorkontoen.',
- 'config-admin-password-same' => 'Passordet skal ikke være det samme som brukernavnet.',
- 'config-admin-password-mismatch' => 'De to passordene du skrev inn samsvarte ikke.',
- 'config-admin-email' => 'E-postadresse:',
- 'config-admin-email-help' => 'Skriv inn en e-postadresse her for at du skal kunne motta e-post fra andre brukere på wikien, tilbakestille passordet ditt, og bli varslet om endringer på sider på overvåkningslisten din. Du kan la dette feltet stå tomt.',
- 'config-admin-error-user' => 'Intern feil ved opprettelse av en admin med navnet «<nowiki>$1</nowiki>».',
- 'config-admin-error-password' => 'Intern feil ved opprettelse av passord for admin «<nowiki>$1</nowiki>»: <pre>$2</pre>',
- 'config-admin-error-bademail' => 'Du har skrevet inn en ugyldig e-postadresse.',
- 'config-subscribe' => 'Abonner på [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce e-postlisten for utgivelsesannonseringer].',
- 'config-subscribe-help' => 'Dette er en lav-volums e-postliste brukt til utgivelsesannonseringer, herunder viktige sikkerhetsannonseringer.
-Du bør abonnere på den og oppdatere MediaWikiinstallasjonen din når nye versjoner kommer ut.',
- 'config-almost-done' => 'Du er nesten ferdig!
-Du kan hoppe over de resterende konfigurasjonene og installere wikien nå.',
- 'config-optional-continue' => 'Spør meg flere spørsmål.',
- 'config-optional-skip' => 'Jeg er lei, bare installer wikien.',
- 'config-profile' => 'Brukerrettighetsprofil:',
- 'config-profile-wiki' => 'Tradisjonell wiki',
- 'config-profile-no-anon' => 'Kontoopprettelse påkrevd',
- 'config-profile-fishbowl' => 'Kun autoriserte bidragsytere',
- 'config-profile-private' => 'Privat wiki',
- 'config-profile-help' => "Wikier fungerer best når du lar så mange mennesker som mulig redigere den.
-I MediaWiki er det lett å revidere siste endringer og tilbakestille eventuell skade som er gjort av naive eller ondsinnede brukere.
-
-Imidlertid har mange funnet at MediaWiki er nyttig i mange roller, og av og til er det ikke lett å overbevise alle om fordelene med wikimåten.
-SÃ¥ du har valget.
-
-En '''{{int:config-profile-wiki}}''' tillater alle å redigere, selv uten å logge inn.
-En wiki med '''{{int:config-profile-no-anon}}''' tilbyr ekstra ansvarlighet, men kan avskrekke tilfeldige bidragsytere.
-
-'''{{int:config-profile-fishbowl}}'''-scenariet tillater godkjente brukere å redigere, mens publikum kan se sider, og også historikken.
-En '''{{int:config-profile-private}}''' tillater kun godkjente brukere å se sider, den samme gruppen som får lov til å redigere dem.
-
-Mer komplekse konfigurasjoner av brukerrettigheter er tilgjengelig etter installasjon, se det [//www.mediawiki.org/wiki/Manual:User_rights relevante manualavsnittet].",
- '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-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',
- 'config-enable-email' => 'Aktiver utgående e-post',
- 'config-enable-email-help' => 'Hvis du vil at e-post skal virke må [http://www.php.net/manual/en/mail.configuration.php PHPs e-postinnstillinger] bli konfigurert riktig.
-Hvis du ikke ønsker noen e-postfunksjoner kan du deaktivere dem her.',
- 'config-email-user' => 'Aktiver e-post mellom brukere',
- 'config-email-user-help' => 'Tillat alle brukere å sende hverandre e-post hvis de har aktivert det i deres innstillinger.',
- 'config-email-usertalk' => 'Aktiver brukerdiskusjonssidevarsler',
- 'config-email-usertalk-help' => 'Tillat brukere å motta varsler ved endringer på deres brukerdiskusjonsside hvis de har aktivert dette i deres innstillinger.',
- 'config-email-watchlist' => 'Aktiver overvåkningslistevarsler',
- 'config-email-watchlist-help' => 'Tillat brukere å motta varsler ved endringer på deres overvåkede sider hvis de har aktivert dette i deres innstillinger.',
- 'config-email-auth' => 'Aktiver e-postautentisering',
- 'config-email-auth-help' => "Om dette alternativet er aktivert må brukere bekrefte sin e-postadresse ved å bruke en lenke som blir sendt til dem når de setter eller endrer adressen sin.
-Kun autentiserte e-postadresser kan motta e-post fra andre brukere eller endringsvarsel.
-Å sette dette valget er '''anbefalt''' for offentlige wikier på grunn av potensiell misbruk av e-postfunksjonene.",
- 'config-email-sender' => 'Svar-e-postadresse:',
- 'config-email-sender-help' => 'Skriv inn e-postadressen som skal brukes som svar-adresse ved utgående e-post.
-Det er hit returmeldinger vil bli sendt.
-Mange e-posttjenere krever at minst domenenavnet må være gyldig.',
- 'config-upload-settings' => 'Bilde- og filopplastinger',
- 'config-upload-enable' => 'Aktiver filopplastinger',
- 'config-upload-help' => 'Filopplastinger kan potensielt utsette tjeneren din for sikkerhetsrisikoer.
-For mer informasjon, les [//www.mediawiki.org/wiki/Manual:Security sikkerhetsseksjonen] i manualen.
-
-For å aktivere filopplastinger, endre modusen i <code>images</code>-undermappen i MediaWikis rotmappe slik at nettjeneren kan skrive til den.
-Aktiver så dette alternativet.',
- 'config-upload-deleted' => 'Mappe for slettede filer:',
- 'config-upload-deleted-help' => 'Velg en mappe for å arkivere slettede filer.
-Ideelt burde ikke denne være tilgjengelig for nettet.',
- 'config-logo' => 'Logo-URL:',
- 'config-logo-help' => 'MediaWikis standarddrakt inkluderer plass til en 135x160 pikslers logo i øvre venstre hjørne.
-Last opp et bilde i passende størrelse og skriv inn nettadressen her.
-
-Hvis du ikke ønsker en logo, la denne boksen være tom.',
- 'config-instantcommons' => 'Aktiver Instant Commons',
- 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] er en funksjon som gjør det mulig for wikier å bruke bilder, lyder og andre media funnet på nettstedet [//commons.wikimedia.org/ Wikimedia Commons].
-For å gjøre dette krever MediaWiki tilgang til internett.
-
-For mer informasjon om denne funksjonen, inklusive instruksjoner om hvordan man setter opp dette for andre wikier enn Wikimedia Commons, konsulter [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos manualen].',
- 'config-cc-again' => 'Velg igjen...',
- 'config-cc-not-chosen' => 'Velg hvilken Creative Commons-lisens du ønsker og klikk «fortsett».',
- 'config-advanced-settings' => 'Avansert konfigurasjon',
- 'config-extensions' => 'Utvidelser',
- 'config-install-step-done' => 'ferdig',
- 'config-install-step-failed' => 'mislyktes',
- 'config-install-extensions' => 'Inkludert utvidelser',
- 'config-install-database' => 'Setter opp database',
- 'config-install-user' => 'Oppretter databasebruker',
- 'config-install-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.'''",
@@ -12156,6 +12743,18 @@ $messages['oc'] = array(
* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Lista de discussions de las parucions de MediaWiki]",
);
+/** Oriya (ଓଡ଼ିଆ)
+ * @author Jnanaranjan Sahu
+ */
+$messages['or'] = array(
+ 'config-back' => '↠ପଛକà­',
+ 'config-continue' => 'ଚାଲà­à¬°à¬–ିବେ →',
+ 'config-page-language' => 'ଭାଷା',
+ 'config-page-welcome' => 'ମେଡିଆଉଇକିକୠଆପଣଙà­à¬•à­ ସà­à¬µà¬¾à¬—ତ',
+ 'config-page-name' => 'ନାମ',
+ 'config-page-options' => 'ପସନà­à¬¦à¬¸à¬®à­‚ହ',
+);
+
/** Ossetic (Ирон)
* @author Amikeco
*/
@@ -12189,6 +12788,11 @@ $messages['pcd'] = array(
* @author Xqt
*/
$messages['pdc'] = array(
+ 'config-continue' => 'Weider →',
+ 'config-page-language' => 'Schprooch',
+ 'config-admin-password' => 'Paesswatt:',
+ 'config-install-step-done' => 'geduh',
+ 'config-help' => 'Hilf',
'mainpagedocfooter' => "Hilf fer's Yuuse unn Konfiguriere vun de Wiki-Software kansch finne im [//meta.wikimedia.org/wiki/Help:Contents Handbuch fer Yuuser].
== Hilf zum Schtaerte ==
@@ -12327,9 +12931,8 @@ 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://xcache.lighttpd.net/ XCache] lub [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Uwaga:''' Nie można odnaleźć [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>.
@@ -12532,7 +13135,7 @@ Wpisz nazwę licencji ręcznie.',
'config-advanced-settings' => 'Konfiguracja zaawansowana',
'config-cache-options' => 'Ustawienia buforowania obiektów',
'config-cache-none' => 'Brak buforowania (wszystkie funkcje będą działać, ale mogą wystąpić kłopoty z wydajnością na dużych witrynach wiki)',
- 'config-cache-accel' => 'Buforowania obiektów PHP (APC, eAccelerator, XCache lub WinCache)',
+ 'config-cache-accel' => 'Buforowania obiektów PHP (APC, XCache lub WinCache)',
'config-cache-memcached' => 'Użyj Memcached (wymaga dodatkowej instalacji i konfiguracji)',
'config-memcached-servers' => 'Serwery Memcached:',
'config-memcached-help' => 'Lista adresów IP do wykorzystania przez Memcached.
@@ -12686,9 +13289,8 @@ Sossì a l'é probabilment tròp bass.
L'instalassion a peul falì!",
'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://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-no-cache' => "'''Avis:''' As treuva pa [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache]. Ël buté d'oget an memòria local a l'é pa abilità.",
'config-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.",
@@ -12949,7 +13551,7 @@ S'a veul gnun-e marche, ch'a lassa ës camp bianch.",
'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].",
+Për pi d'anformassion su sta funsion, comprèise j'istrussion ëd com ampostela për wiki diferente da Wikimedia Commons, ch'a consulta [//mediawiki.org/wiki/Manual:\$wgForeignFileRepos ël manual].",
'config-cc-error' => "La selession ëd la licensa Creative Commons a l'ha dàit gnun arzultà.
Ch'a anserissa ël nòm dla licensa a man.",
'config-cc-again' => 'Torna cheuje...',
@@ -12959,7 +13561,7 @@ Ch'a anserissa ël nòm dla licensa a man.",
'config-cache-help' => "La memorisassion loca d'oget a l'é dovrà për amelioré l'andi ëd MediaWiki an butant an local dij dat dovrà 'd soens.
Ij sit da mesan a gròss a son motobin ancoragià a abilité sòn, e ij sit cit a l'avran ëdcò dij benefissi.",
'config-cache-none' => "Gnun-a memorisassion local (gnun-a funsionalità gavà, ma l'andi a peul esse anfluensà an sij sit ëd wiki gròsse)",
- 'config-cache-accel' => "Memorisassion local d'oget PHP (APC, eAccelerator, XCache o WinCache)",
+ 'config-cache-accel' => "Memorisassion local d'oget PHP (APC, XCache o WinCache)",
'config-cache-memcached' => "Dovré Memcached (a ciama n'ampostassion e na configurassion adissionaj)",
'config-memcached-servers' => 'Servent Memcached:',
'config-memcached-help' => "Lista d'adrësse IP da dovré për Memcached.
@@ -13020,7 +13622,7 @@ $messages['pnt'] = array(
*/
$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.
+ 'mainpagedocfooter' => 'WÄ«dais [//meta.wikimedia.org/wiki/Help:Contents przewodnik użytkownika] kÄi gaÅ«lai infÅrmaciÅnei ezze wiki prÅgramijas tÄ“rpausnan.
== En pagaūseņu ==
* [//www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]
@@ -13079,6 +13681,7 @@ $messages['ps'] = array(
/** Portuguese (Português)
* @author Crazymadlover
* @author Hamilton Abreu
+ * @author Mormegil
* @author Platonides
* @author SandroHc
* @author Waldir
@@ -13205,9 +13808,8 @@ Isto é provavelmente demasiado baixo.
A instalação poderá falhar!",
'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://xcache.lighttpd.net/ XCache] nem [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Aviso:''' Não foram encontrados [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>.
@@ -13525,7 +14127,7 @@ Se não pretende usar um logótipo, deixe este campo em branco.',
'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].',
+Para mais informações sobre esta funcionalidade, incluindo instruções sobre como configurá-la para usar outras wikis em vez da Wikimedia Commons, consulte o [//mediawiki.org/wiki/Manual:$wgForeignFileRepos Manual Técnico].',
'config-cc-error' => 'O auxiliar de escolha de licenças da Creative Commons não produziu resultados.
Introduza o nome da licença manualmente.',
'config-cc-again' => 'Escolha outra vez...',
@@ -13535,7 +14137,7 @@ Introduza o nome da licença manualmente.',
'config-cache-help' => 'A cache de objectos é usada para melhorar o desempenho do MediaWiki. Armazena dados usados com frequência.
Sites de tamanho médio ou grande são altamente encorajados a activar esta funcionalidade e os sites pequenos também terão alguns benefícios em fazê-lo.',
'config-cache-none' => 'Sem cache (não é removida nenhuma funcionalidade, mas a velocidade de operação pode ser afectada nas wikis grandes)',
- 'config-cache-accel' => 'Cache de objectos do PHP (APC, eAccelerator, XCache ou WinCache)',
+ 'config-cache-accel' => 'Cache de objectos do PHP (APC, XCache ou WinCache)',
'config-cache-memcached' => 'Usar Memcached (requer instalação e configurações adicionais)',
'config-memcached-servers' => 'Servidores Memcached:',
'config-memcached-help' => 'Lista de endereços IP que serão usados para o Memcached.
@@ -13933,9 +14535,8 @@ MediaWiki требует поддержки UTF-8 Ð´Ð»Ñ ÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ð¾Ð¹ Ñ€
УÑтановка может потерпеть неудачу!",
'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://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Внимание:''' Ðе найдены [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] или [http://www.iis.net/download/WinCacheForPhp WinCache].
КÑширование объектов будет отключено.",
'config-mod-security' => "'''Внимание''': на вашем веб-Ñервере включен [http://modsecurity.org/ mod_security]. При неправильной наÑтройке он может вызывать проблемы Ð´Ð»Ñ MediaWiki или другого ПО, позволÑющего пользователÑм отправлÑÑ‚ÑŒ на Ñервер произвольный текÑÑ‚.
ОбратитеÑÑŒ к [http://modsecurity.org/documentation/ документации mod_security] или в поддержку вашего хоÑтера, еÑли при работе возникают непонÑтные ошибки.",
@@ -14251,7 +14852,7 @@ GFDL может быть иÑпользована, но она Ñложна дл
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons Instant Commons] — Ñто функциÑ, позволÑÑŽÑ‰Ð°Ñ Ð¸Ñпользовать изображениÑ, звуки и другие медиафайлы Ñ Ð’Ð¸ÐºÐ¸Ñклада ([//commons.wikimedia.org/ Wikimedia Commons]).
Ð”Ð»Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ Ñтой функции MediaWiki необходим доÑтуп к Интернету.
-Дополнительную информацию об Instant Commons, в том чиÑле ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð¾ том, как её наÑтроить Ð´Ð»Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… вики, отличных от ВикиÑклада, можно найти в [http://mediawiki.org/wiki/Manual:$wgForeignFileRepos руководÑтве].',
+Дополнительную информацию об Instant Commons, в том чиÑле ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð¾ том, как её наÑтроить Ð´Ð»Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… вики, отличных от ВикиÑклада, можно найти в [//mediawiki.org/wiki/Manual:$wgForeignFileRepos руководÑтве].',
'config-cc-error' => 'Механизм выбора лицензии Creative Commons не вернул результата.
Введите название лицензии вручную.',
'config-cc-again' => 'Выберите ещё раз…',
@@ -14261,7 +14862,7 @@ GFDL может быть иÑпользована, но она Ñложна дл
'config-cache-help' => 'КÑширование объектов иÑпользуетÑÑ Ð´Ð»Ñ Ð¿Ð¾Ð²Ñ‹ÑˆÐµÐ½Ð¸Ñ ÑкороÑти MediaWiki путем кÑÑˆÐ¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ‡Ð°Ñто иÑпользуемых данных.
Ð”Ð»Ñ Ñредних и больших Ñайтов кеширование наÑтоÑтельно рекомендуетÑÑ Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ‚ÑŒ, а Ð´Ð»Ñ Ð½ÐµÐ±Ð¾Ð»ÑŒÑˆÐ¸Ñ… Ñайтов кеширование может показать преимущеÑтво.',
'config-cache-none' => 'Без кÑÑˆÐ¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ (никакой функционал не терÑетÑÑ, но крупные вики-Ñайты могут работать медленнее)',
- 'config-cache-accel' => 'PHP кÑширование объектов (APC, eAccelerator, XCache или WinCache)',
+ 'config-cache-accel' => 'PHP кÑширование объектов (APC, XCache или WinCache)',
'config-cache-memcached' => 'ИÑпользовать Memcached (требует дополнительной наÑтройки)',
'config-memcached-servers' => 'Сервера Memcached:',
'config-memcached-help' => 'СпиÑок IP-адреÑов, иÑпользуемых Memcached.
@@ -14546,7 +15147,6 @@ Vendar pa MediaWiki zahteva PHP $2 ali višji.',
'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:',
@@ -14675,7 +15275,7 @@ 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-accel' => 'Predpomnjenje predmetov PHP (APC, XCache ali WinCache)',
'config-cache-memcached' => 'Uporabi Memcached (zahteva dodatno namestitev in konfiguracijo)',
'config-memcached-servers' => 'Strežniki Memcached:',
'config-memcache-badip' => 'Vnesli ste neveljaven IP-naslov za Memcached: $1',
@@ -14751,6 +15351,12 @@ $messages['sr-ec'] = array(
/** Serbian (Latin script) (‪Srpski (latinica)‬) */
$messages['sr-el'] = array(
+ 'config-continue' => 'Nastavi →',
+ 'config-page-language' => 'Jezik',
+ 'config-type-mysql' => 'MySQL',
+ 'config-type-postgres' => 'PostgreSQL',
+ 'config-type-sqlite' => 'SQLite',
+ 'config-type-oracle' => 'Oracle',
'mainpagetext' => "'''MedijaViki je uspešno instaliran.'''",
'mainpagedocfooter' => 'Molimo vidite [//meta.wikimedia.org/wiki/Help:Contents korisniÄki vodiÄ] za informacije o upotrebi viki softvera.
@@ -14843,12 +15449,15 @@ Du kan inte installera MediaWiki.',
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-diff3-bad' => 'GNU diff3 hittades inte.',
+ 'config-using-server' => 'Använder servernamn "<nowiki>$1</nowiki>".',
+ 'config-using-uri' => 'Använder server-URL "<nowiki>$1$2</nowiki>".',
'config-db-wiki-settings' => 'Identifiera denna wiki',
'config-db-name' => 'Databasnamn:',
'config-db-username' => 'Databas-användarnamn:',
'config-db-password' => 'Databas-lösenord:',
+ 'config-db-schema' => 'Schema för MediaWiki',
'config-header-mysql' => 'MySQL-inställningar',
'config-header-postgres' => 'PostgreSQL-inställningar',
'config-header-sqlite' => 'SQLite-inställningar',
@@ -14865,6 +15474,19 @@ 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 (-).',
+ 'config-upgrade-done' => "Uppgraderingen slutfördes.
+
+Du kan nu [$1 börja använda din wiki].
+
+Om du vill förnya din <code>LocalSettings.php</code>-fil, klicka på knappen nedan.
+Detta '''rekommenderas inte''' om du har problem med din wiki.",
+ 'config-upgrade-done-no-regenerate' => 'Uppgraderingen slutfördes.
+
+Du kan nu [$1 börja använda din wiki].',
+ 'config-mysql-utf8' => 'UTF-8',
+ 'config-site-name' => 'Namnet på wikin:',
+ 'config-site-name-blank' => 'Ange ett sidnamn.',
+ 'config-ns-generic' => 'Projekt',
'mainpagetext' => "'''MediaWiki har installerats utan problem.'''",
'mainpagedocfooter' => 'Information om hur wiki-programvaran används finns i [//meta.wikimedia.org/wiki/Help:Contents användarguiden].
@@ -15145,9 +15767,8 @@ Ito ay maaaring napakababa.
Maaaring mabigo ang pagluluklok!",
'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://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
+ 'config-no-cache' => "'''Babala:''' Hindi mahanap ang [http://www.php.net/apc APC], [http://xcache.lighttpd.net/ XCache] o [http://www.iis.net/download/WinCacheForPhp WinCache].
Hindi pinapagana ang pagbabaon ng mga bagay.",
'config-diff3-bad' => 'Hindi natagpuan ang GNU diff3.',
'config-imagemagick' => 'Natagpuan ang ImageMagick: <code>$1</code>.
@@ -15465,7 +16086,6 @@ $messages['uk'] = array(
Якщо уÑтановлено 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] вÑтановлено',
'config-db-type' => 'Тип бази даних:',
'config-db-host' => 'ХоÑÑ‚ бази даних:',
@@ -15501,6 +16121,7 @@ $messages['uk'] = array(
'config-install-step-done' => 'виконано',
'config-install-step-failed' => 'не вдалоÑÑ',
'config-install-interwiki-list' => 'Ðе вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ файл <code>interwiki.list</code>.',
+ '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 поÑібнику кориÑтувача].
@@ -15650,6 +16271,8 @@ $messages['xal'] = array(
* @author פוילישער
*/
$messages['yi'] = array(
+ 'config-back' => '→ צוריק',
+ 'config-page-language' => 'שפר×ַך',
'config-admin-name' => '×ײַער × ×ָמען:',
'mainpagetext' => "'''מעדיעוויקי ×ינסט×לירט מיט דערפ×לג.'''",
'mainpagedocfooter' => "גיט זיך ×ן עצה מיט [//meta.wikimedia.org/wiki/Help:Contents ב×ניצער'ס וועגווײַזער] פֿ×ר ×ינפֿ×רמ×ציע ווי×זוי זיך ב×נוצן מיט וויקי ווייכוו×ַרג.
@@ -15698,6 +16321,7 @@ $messages['zea'] = array(
/** Simplified Chinese (‪中文(简体)‬)
* @author Hydra
+ * @author Hzy980512
* @author PhiLiP
* @author Xiaomingyan
* @author 阿pp
@@ -15774,28 +16398,30 @@ $1',
'config-no-db' => '找ä¸åˆ°åˆé€‚çš„æ•°æ®åº“驱动ï¼æ‚¨éœ€è¦ä¸ºPHP安装数æ®åº“驱动。目å‰æ”¯æŒä»¥ä¸‹æ•°æ®åº“:$1。
如果您正在使用共享主机,请å‘您的主机æ供商申请安装åˆé€‚çš„æ•°æ®åº“驱动。如果您通过自行编译安装的PHP,请对其进行é‡æ–°é…置以å¯ç”¨æ•°æ®åº“客户端,例如使用<code>./configure --with-mysql</code>。如果您通过Debian或Ubuntu包安装的PHP,您还需è¦å®‰è£…php5-mysql模å—。',
+ 'config-outdated-sqlite' => "'''警告''':您已安装SQLite $1,但是它的版本低于最低è¦æ±‚版本$2。因此您无法选择SQLite。",
'config-no-fts3' => "'''警告''':已编译的SQLiteä¸åŒ…å«[//sqlite.org/fts3.html FTS3模å—],åŽå°æœç´¢åŠŸèƒ½å°†ä¸å¯ç”¨ã€‚",
'config-register-globals' => "'''警告:PHPçš„<code>[http://php.net/register_globals register_globals]</code>选项被å¯ç”¨ã€‚请尽é‡ç¦ç”¨è¯¥åŠŸèƒ½ï¼Œ'''虽然ä¸ä¼šå½±å“MediaWikiçš„è¿è¡Œï¼Œä½†æ‚¨çš„æœåŠ¡å™¨ä¼šè¢«æš´éœ²ç»™æ½œåœ¨çš„安全æ¼æ´žã€‚",
- 'config-magic-quotes-runtime' => "'''致命错误:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]被å¯ç”¨ï¼'''
+ 'config-magic-quotes-runtime' => "'''æ¯ç­æ€§é”™è¯¯ï¼š[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]å·²å¯ç”¨ï¼'''
此选项会无法预测地破å输入的数æ®ï¼Œè¯·å°†å…¶ç¦ç”¨ï¼Œå¦åˆ™æ‚¨å°†ä¸èƒ½å®‰è£…或使用MediaWiki。",
- 'config-magic-quotes-sybase' => "'''致命错误:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase]被å¯ç”¨ï¼'''
+ 'config-magic-quotes-sybase' => "'''æ¯ç­æ€§é”™è¯¯ï¼š[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase]å·²å¯ç”¨ï¼'''
此选项会无法预测地破å输入的数æ®ï¼Œè¯·å°†å…¶ç¦ç”¨ï¼Œå¦åˆ™æ‚¨å°†ä¸èƒ½å®‰è£…或使用MediaWiki。",
- 'config-mbstring' => "'''致命错误:[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]被å¯ç”¨ï¼'''
+ 'config-mbstring' => "'''æ¯ç­æ€§é”™è¯¯ï¼š[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]å·²å¯ç”¨ï¼'''
此选项会导致错误并ä¸å¯é¢„测地破åæ•°æ®ï¼Œè¯·å°†å…¶ç¦ç”¨ï¼Œå¦åˆ™æ‚¨å°†ä¸èƒ½å®‰è£…或使用MediaWiki。",
- 'config-ze1' => "'''致命错误:[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]被å¯ç”¨ï¼'''
+ 'config-ze1' => "'''æ¯ç­æ€§é”™è¯¯ï¼š[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]å·²å¯ç”¨ï¼'''
此选项将导致MediaWiki出现æžå…¶ä¸¥é‡çš„故障,请将其ç¦ç”¨ï¼Œå¦åˆ™æ‚¨å°†ä¸èƒ½å®‰è£…或使用MediaWiki。",
'config-safe-mode' => "'''警告:'''PHPçš„[http://www.php.net/features.safe-mode 安全模å¼]å·²å¯ç”¨ã€‚它å¯èƒ½ä¼šå¯¼è‡´ä¸€äº›é—®é¢˜ï¼Œå°¤å…¶åœ¨å¯¹æ–‡ä»¶ä¸Šä¼ å’Œæ•°å­¦å…¬å¼<code>math</code>的支æŒæ–¹é¢ã€‚",
'config-xml-bad' => '缺少PHPçš„XML模å—。MediaWiki需è¦ä½¿ç”¨è¯¥æ¨¡å—æ供的函数,在当å‰é…置下将无法工作。如果您正在使用Mandrake Linux,请安装php-xml包。',
'config-pcre' => 'å¯èƒ½ç¼ºå°‘PCRE的支æŒæ¨¡å—。MediaWikiçš„è¿è¡Œéœ€è¦å…¼å®¹äºŽPerl的正则表达å¼å‡½æ•°ã€‚',
- 'config-pcre-no-utf8' => "'''致命错误''':PHPçš„PCRE模å—在编译时å¯èƒ½æ²¡æœ‰åŒ…å«PCRE_UTF8支æŒã€‚MediaWiki需è¦UTF-8支æŒæ‰èƒ½æ­£å¸¸å·¥ä½œã€‚",
+ 'config-pcre-no-utf8' => "'''æ¯ç­æ€§é”™è¯¯''':PHPçš„PCRE模å—在编译时å¯èƒ½æ²¡æœ‰åŒ…å«PCRE_UTF8支æŒã€‚MediaWiki需è¦UTF-8支æŒæ‰èƒ½æ­£å¸¸å·¥ä½œã€‚",
'config-memory-raised' => 'PHP的内存使用上é™<code>memory_limit</code>为$1,自动æå‡åˆ°$2。',
'config-memory-bad' => "'''警告:'''PHP的内存使用上é™<code>memory_limit</code>为$1。该设定å¯èƒ½è¿‡ä½Žï¼Œå¹¶å¯¼è‡´å®‰è£…失败ï¼",
+ 'config-ctype' => "'''æ¯ç­æ€§é”™è¯¯''':PHP必须有[http://www.php.net/manual/en/ctype.installation.php Ctype 扩展]æ¥æ”¯æŒç¼–译。",
'config-xcache' => '[http://xcache.lighttpd.net/ XCache]已安装',
'config-apc' => '[http://www.php.net/apc APC]已安装',
- 'config-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://xcache.lighttpd.net/ XCache]或[http://www.iis.net/download/WinCacheForPhp WinCache],无法å¯ç”¨å¯¹è±¡ç¼“存。
+ 'config-no-cache' => "'''警告:'''找ä¸åˆ°[http://www.php.net/apc APC]ã€[http://xcache.lighttpd.net/ XCache]或[http://www.iis.net/download/WinCacheForPhp WinCache],无法å¯ç”¨å¯¹è±¡ç¼“存。
Object caching is not enabled.",
+ 'config-mod-security' => "'''警告''':您的æœåŠ¡å™¨å·²å¯åŠ¨[http://modsecurity.org/ mod_security]。若其é…置错误, 会导致MediaWiki和其他软件的错误并å…许用户任æ„å‘布内容。如果您é‡åˆ°ä»»ä½•é”™è¯¯ï¼Œè¯·æŸ¥é˜…[http://modsecurity.org/documentation/ mod_security文档]或è”系您的客æœã€‚",
'config-diff3-bad' => '找ä¸åˆ°GNU diff3。',
'config-imagemagick' => '已找到ImageMagick:<code>$1</code>。如果你å¯ç”¨äº†ä¸Šä¼ åŠŸèƒ½ï¼Œç¼©ç•¥å›¾åŠŸèƒ½ä¹Ÿå°†è¢«å¯ç”¨ã€‚',
'config-gd' => '已找到内建的GD图形库。如果你å¯ç”¨äº†ä¸Šä¼ åŠŸèƒ½ï¼Œç¼©ç•¥å›¾åŠŸèƒ½ä¹Ÿå°†è¢«å¯ç”¨ã€‚',
@@ -15811,11 +16437,13 @@ Object caching is not enabled.",
'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地å€ã€‚
+ 'config-db-host-help' => '如果您的数æ®åº“在别的æœåŠ¡å™¨ä¸Šï¼Œè¯·åœ¨è¿™é‡Œè¾“入它的域å或IP地å€ã€‚
-如果您使用的是共享web主机,您的主机æ供商应会在他们的文档中给出正确的主机å称。
+如果您在使用共享网站套é¤ï¼Œæ‚¨çš„网站商应该已在他们的控制é¢æ¿ä¸­ç»™æ‚¨æ•°æ®åº“ä¿¡æ¯äº†ã€‚
-如果您使用了WindowsæœåŠ¡å™¨å’ŒMySQLæ•°æ®åº“,使用“localhostâ€å¯èƒ½æ— æ³•è¯†åˆ«åˆ°æœ¬åœ°æœåŠ¡å™¨ã€‚如果是这样的è¯ï¼Œè¯·å°è¯•æŒ‡å®šæœ¬åœ°æœåŠ¡å™¨çš„IP地å€ä¸ºâ€œ127.0.0.1â€ã€‚',
+如果您在Windows中安装并且使用MySQL,“localhostâ€å¯èƒ½æ— æ•ˆã€‚如果确实无效,请输入“127.0.0.1â€ä½œä¸ºIP地å€ã€‚
+
+如果您在使用PostgreSQL,并且è¦ç”¨Unix socketæ¥è¿žæŽ¥ï¼Œè¯·ç•™ç©ºã€‚',
'config-db-host-oracle' => 'æ•°æ®åº“é€æ˜Žç½‘络底层(TNS):',
'config-db-host-oracle-help' => '请输入åˆæ³•çš„[http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm 本地连接å],并确ä¿tnsnames.ora文件对本安装程åºå¯è§ã€‚<br />如果您使用的客户端库为10g或更新的版本,您还å¯ä»¥ä½¿ç”¨[http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 简å•è¿žæŽ¥å方法](easy connect naming method)。',
'config-db-wiki-settings' => '标识本wiki',
@@ -15933,7 +16561,7 @@ chmod a+w $3</pre>',
现在您å¯ä»¥[$1 开始使用您的wiki]了。',
'config-regenerate' => 'é‡æ–°ç”ŸæˆLocalSettings.php →',
- 'config-show-table-status' => '查询SHOW TABLE STATUS失败ï¼',
+ 'config-show-table-status' => 'SHOW TABLE STATUS语å¥æ‰§è¡Œå¤±è´¥ï¼',
'config-unknown-collation' => "'''警告:'''æ•°æ®åº“使用了无法识别的整ç†ã€‚",
'config-db-web-account' => '供网页访问使用的数æ®åº“å¸å·',
'config-db-web-help' => '请指定在wiki执行普通æ“作时,网页æœåŠ¡å™¨ç”¨äºŽè¿žæŽ¥æ•°æ®åº“æœåŠ¡å™¨çš„用户å和密ç ã€‚',
@@ -16047,7 +16675,7 @@ GNU自由文档许å¯è¯æ˜¯ç»´åŸºç™¾ç§‘曾ç»ä½¿ç”¨è¿‡çš„许å¯è¯ï¼Œå¹¶è¿„今æœ
'config-instantcommons' => 'å¯ç”¨å³æ—¶å…±äº«èµ„æº',
'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons å³æ—¶å…±äº«èµ„æº]å¯ä»¥è®©wiki使用æ¥è‡ª[//commons.wikimedia.org/ 维基共享资æº]网站的图åƒã€éŸ³é¢‘和其他媒体文件。è¦å¯ç”¨è¯¥åŠŸèƒ½ï¼ŒMediaWiki必须能够访问互è”网。
-有关此功能的详细信æ¯ï¼ŒåŒ…括如何将其他wiki网站设为具有类似共享功能的方法,请å‚考[http://mediawiki.org/wiki/Manual:$wgForeignFileRepos 手册]。',
+有关此功能的详细信æ¯ï¼ŒåŒ…括如何将其他wiki网站设为具有类似共享功能的方法,请å‚考[//mediawiki.org/wiki/Manual:$wgForeignFileRepos 手册]。',
'config-cc-error' => '知识共享许å¯è¯æŒ‘选器无法找到结果,请手动输入许å¯è¯çš„å称。',
'config-cc-again' => 'é‡æ–°æŒ‘选……',
'config-cc-not-chosen' => '选择您希望使用的知识共享许å¯è¯ï¼Œå¹¶ç‚¹å‡»â€œç»§ç»­â€ã€‚',
@@ -16055,7 +16683,7 @@ GNU自由文档许å¯è¯æ˜¯ç»´åŸºç™¾ç§‘曾ç»ä½¿ç”¨è¿‡çš„许å¯è¯ï¼Œå¹¶è¿„今æœ
'config-cache-options' => '对象缓存设置:',
'config-cache-help' => '对象缓存å¯é€šè¿‡ç¼“存频ç¹ä½¿ç”¨çš„æ•°æ®æ¥æ高MediaWiki的速度。高度推è中到大型的网站å¯ç”¨è¯¥åŠŸèƒ½ï¼Œå°åž‹ç½‘站亦能从其中å—益。',
'config-cache-none' => '无缓存(ä¸å½±å“功能,但对较大型的wiki网站会有速度影å“)',
- 'config-cache-accel' => 'PHP对象缓存(APCã€eAcceleratorã€XCache或WinCache)',
+ 'config-cache-accel' => 'PHP对象缓存(APCã€XCache或WinCache)',
'config-cache-memcached' => '使用Memcached(需è¦å¦å¤–安装并é…置)',
'config-memcached-servers' => 'MemcachedæœåŠ¡å™¨ï¼š',
'config-memcached-help' => '用于Memcachedçš„IP地å€åˆ—表。请ä¿æŒæ¯è¡Œä¸€æ¡ï¼Œå¹¶æŒ‡å®šè¦ä½¿ç”¨çš„端å£ã€‚例如:
@@ -16133,13 +16761,31 @@ $3
);
/** Traditional Chinese (‪中文(ç¹é«”)‬)
+ * @author Hzy980512
* @author Mark85296341
*/
$messages['zh-hant'] = array(
+ 'config-desc' => 'MediaWiki安è£ç¨‹åº',
+ 'config-title' => 'MediaWiki $1é…ç½®',
'config-information' => '資訊',
+ 'config-localsettings-upgrade' => '已檢測到<code>LocalSettings.php</code>文件。è¦å‡ç´šè©²é…置,請在下é¢çš„框中輸入<code>$wgUpgradeKey</code>的值。您å¯ä»¥åœ¨LocalSettings.php中找到它。',
+ 'config-localsettings-cli-upgrade' => '已檢測到LocalSettings.php文件。è¦å‡ç´šè©²é…置,請直接執行update.php。',
+ 'config-localsettings-key' => 'å‡ç´šå¯†é‘°ï¼š',
+ 'config-localsettings-badkey' => '您æ供的密鑰ä¸æ­£ç¢ºã€‚',
+ 'config-upgrade-key-missing' => '檢測到MediaWikiçš„é…置已經存在。若è¦å‡ç´šè©²é…置,請將下é¢ä¸€è¡Œæ–‡æœ¬æ·»åŠ åˆ°LocalSettings.php的底部:
+
+$1',
+ 'config-localsettings-incomplete' => '當å‰çš„LocalSettings.phpå¯èƒ½ä¸¦ä¸å®Œæ•´ï¼Œå› ç‚ºè®Šé‡$1沒有設置。請在LocalSettings.php設置該變é‡ï¼Œä¸¦å–®æ“Šâ€œç¹¼çºŒâ€ã€‚',
+ 'config-localsettings-connection-error' => '在使用LocalSettings.php或AdminSettings.php中指定的設置連接數據庫時發生錯誤。請修復相應設置並é‡è©¦ã€‚
+
+$1',
+ 'config-session-error' => '啟動會話出錯:$1',
+ 'config-session-expired' => '您的會話數據å¯èƒ½å·²ç¶“éŽæœŸï¼Œç•¶å‰æœƒè©±çš„使用期é™è¢«è¨­å®šç‚º$1。您å¯ä»¥åœ¨php.ini中設置<code>session.gc_maxlifetime</code>來延長此期é™ï¼Œä¸¦é‡æ–°å•Ÿå‹•æœ¬é…置程åºã€‚',
+ 'config-no-session' => '您的會話數據丟失了ï¼è«‹æª¢æŸ¥php.ini並確ä¿<code>session.save_path</code>被設置為é©ç•¶çš„目錄。',
'config-your-language' => '您的語言:',
'config-your-language-help' => 'é¸æ“‡ä¸€å€‹è¦ä½¿ç”¨çš„語言在安è£éŽç¨‹ä¸­ã€‚',
'config-wiki-language' => 'Wiki 語言:',
+ 'config-wiki-language-help' => 'é¸æ“‡å°‡è¦å®‰è£çš„wiki在多數情æ³ä¸‹ä½¿ç”¨çš„語言。',
'config-back' => 'â†è¿”回',
'config-continue' => '繼續→',
'config-page-language' => '語言',
@@ -16153,47 +16799,385 @@ $messages['zh-hant'] = array(
'config-page-complete' => '完æˆï¼',
'config-page-restart' => 'é‡æ–°å®‰è£',
'config-page-readme' => '讀我',
+ 'config-page-releasenotes' => '發布說明',
'config-page-copying' => '複製',
'config-page-upgradedoc' => 'å‡ç´š',
+ 'config-page-existingwiki' => '已有wiki',
+ 'config-help-restart' => '是å¦è¦æ¸…除所有已輸入且ä¿å­˜çš„數據,並é‡æ–°å•Ÿå‹•å®‰è£éŽç¨‹å—Žï¼Ÿ',
'config-restart' => '是的,é‡æ–°å•Ÿå‹•',
+ 'config-welcome' => '=== 環境檢查 ===
+å°ç•¶å‰ç’°å¢ƒæ˜¯å¦é©åˆå®‰è£MediaWiki作基本的檢查。如果您在安è£éŽç¨‹ä¸­éœ€è¦å¹«åŠ©ï¼Œè«‹æ供這些檢查的çµæžœã€‚',
+ 'config-copyright' => "=== 版權和æ¢æ¬¾ ===
+
+\$1
+
+本程åºç‚ºè‡ªç”±è»Ÿä»¶ï¼›æ‚¨å¯ä¾æ“šè‡ªç”±è»Ÿä»¶åŸºé‡‘會所發表的GNU通用公共授權æ¢æ¬¾è¦å®šï¼Œå°±æœ¬ç¨‹åºå†ç‚ºç™¼å¸ƒèˆ‡ï¼æˆ–修改;無論您ä¾æ“šçš„是本授權的第二版或(您自行é¸æ“‡çš„)任一日後發行的版本。
+
+本程åºæ˜¯åŸºæ–¼ä½¿ç”¨ç›®çš„而加以發布,然而'''ä¸è² ä»»ä½•æ“”ä¿è²¬ä»»''';亦無å°'''é©å”®æ€§'''或'''特定目的é©ç”¨æ€§'''所為的默示性擔ä¿ã€‚詳情請åƒç…§GNU通用公共授權。
+
+您應已收到附隨於本程åºçš„<doclink href=\"Copying\">GNU通用公共授權的副本</doclink>;如果沒有,請寫信至自由軟件基金會:59 Temple Place - Suite 330, Boston, Ma 02111-1307, USA,或[http://www.gnu.org/copyleft/gpl.html 在線閱讀]。",
+ 'config-sidebar' => '* [//www.mediawiki.org/wiki/MediaWiki/zh-hans MediaWiki首é ]
+* [//www.mediawiki.org/wiki/Help:Contents/zh-hans 用戶指å—]
+* [//www.mediawiki.org/wiki/Manual:Contents 管ç†å“¡æŒ‡å—]
+* [//www.mediawiki.org/wiki/Manual:FAQ/zh-hans 常見å•é¡Œè§£ç­”]
+----
+* <doclink href=Readme>自述文件</doclink>
+* <doclink href=ReleaseNotes>發行說明</doclink>
+* <doclink href=Copying>å”議副本</doclink>
+* <doclink href=UpgradeDoc>å‡ç´š</doclink>',
+ 'config-env-good' => '環境檢查已經完æˆã€‚您å¯ä»¥å®‰è£MediaWiki。',
+ 'config-env-bad' => '環境檢查已經完æˆã€‚您ä¸èƒ½å®‰è£MediaWiki。',
+ 'config-env-php' => 'PHP $1已安è£ã€‚',
+ 'config-env-php-toolow' => '已安è£PHP $1;但是,MediaWiki需è¦PHP $2或更高版本。',
+ 'config-unicode-using-utf8' => '使用Brion Vibberçš„utf8_normalize.so實ç¾Unicode正常化。',
+ 'config-unicode-using-intl' => '使用[http://pecl.php.net/intl intl PECL擴展]實ç¾Unicode正常化。',
+ 'config-unicode-pure-php-warning' => "'''警告''':[http://pecl.php.net/intl intl PECL擴展]無法處ç†Unicode正常化,故åªèƒ½é€€è€ŒæŽ¡ç”¨é‹è¡Œè¼ƒæ…¢çš„ç´”PHP實ç¾çš„方法。如果您é‹è¡Œç€ä¸€å€‹é«˜æµé‡çš„站點,請åƒé–±[//www.mediawiki.org/wiki/Unicode_normalization_considerations Unicode正常化]一文。",
+ 'config-unicode-update-warning' => "'''警告''':Unicode正常化å°è£å™¨çš„已安è£ç‰ˆæœ¬ä½¿ç”¨äº†èˆŠç‰ˆæœ¬çš„[http://site.icu-project.org/ ICUé …ç›®]庫。如果您需è¦ä½¿ç”¨Unicode,請將其[//www.mediawiki.org/wiki/Unicode_normalization_considerations å‡ç´š]。",
+ 'config-no-db' => '找ä¸åˆ°åˆé©çš„數據庫驅動ï¼æ‚¨éœ€è¦ç‚ºPHP安è£æ•¸æ“šåº«é©…動。目å‰æ”¯æŒä»¥ä¸‹æ•¸æ“šåº«ï¼š$1。
+
+如果您正在使用共享主機,請å‘您的主機æ供商申請安è£åˆé©çš„數據庫驅動。如果您通éŽè‡ªè¡Œç·¨è­¯å®‰è£çš„PHP,請å°å…¶é€²è¡Œé‡æ–°é…置以啟用數據庫客戶端,例如使用<code>./configure --with-mysql</code>。如果您通éŽDebian或Ubuntu包安è£çš„PHP,您還需è¦å®‰è£php5-mysql模塊。',
+ 'config-no-fts3' => "'''警告''':已編譯的SQLiteä¸åŒ…å«[//sqlite.org/fts3.html FTS3模塊],後å°æœç´¢åŠŸèƒ½å°‡ä¸å¯ç”¨ã€‚",
+ 'config-register-globals' => "'''警告:PHPçš„<code>[http://php.net/register_globals register_globals]</code>é¸é …被啟用。請盡é‡ç¦ç”¨è©²åŠŸèƒ½ï¼Œ'''雖然ä¸æœƒå½±éŸ¿MediaWikiçš„é‹è¡Œï¼Œä½†æ‚¨çš„æœå‹™å™¨æœƒè¢«æš´éœ²çµ¦æ½›åœ¨çš„安全æ¼æ´žã€‚",
+ 'config-magic-quotes-runtime' => "'''致命錯誤:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime]被啟用ï¼'''
+æ­¤é¸é …會無法é æ¸¬åœ°ç ´å£žè¼¸å…¥çš„數據,請將其ç¦ç”¨ï¼Œå¦å‰‡æ‚¨å°‡ä¸èƒ½å®‰è£æˆ–使用MediaWiki。",
+ 'config-magic-quotes-sybase' => "'''致命錯誤:[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_sybase]被啟用ï¼'''
+æ­¤é¸é …會無法é æ¸¬åœ°ç ´å£žè¼¸å…¥çš„數據,請將其ç¦ç”¨ï¼Œå¦å‰‡æ‚¨å°‡ä¸èƒ½å®‰è£æˆ–使用MediaWiki。",
+ 'config-mbstring' => "'''致命錯誤:[http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload]被啟用ï¼'''
+æ­¤é¸é …會導致錯誤並ä¸å¯é æ¸¬åœ°ç ´å£žæ•¸æ“šï¼Œè«‹å°‡å…¶ç¦ç”¨ï¼Œå¦å‰‡æ‚¨å°‡ä¸èƒ½å®‰è£æˆ–使用MediaWiki。",
+ 'config-ze1' => "'''致命錯誤:[http://www.php.net/manual/en/ini.core.php zend.ze1_compatibility_mode]被啟用ï¼'''
+æ­¤é¸é …將導致MediaWiki出ç¾æ¥µå…¶åš´é‡çš„故障,請將其ç¦ç”¨ï¼Œå¦å‰‡æ‚¨å°‡ä¸èƒ½å®‰è£æˆ–使用MediaWiki。",
+ 'config-safe-mode' => "'''警告:'''PHPçš„[http://www.php.net/features.safe-mode 安全模å¼]已啟用。它å¯èƒ½æœƒå°Žè‡´ä¸€äº›å•é¡Œï¼Œå°¤å…¶åœ¨å°æ–‡ä»¶ä¸Šå‚³å’Œæ•¸å­¸å…¬å¼<code>math</code>的支æŒæ–¹é¢ã€‚",
+ 'config-xml-bad' => '缺少PHPçš„XML模塊。MediaWiki需è¦ä½¿ç”¨è©²æ¨¡å¡Šæ供的函數,在當å‰é…置下將無法工作。如果您正在使用Mandrake Linux,請安è£php-xml包。',
+ 'config-pcre' => 'å¯èƒ½ç¼ºå°‘PCRE的支æŒæ¨¡å¡Šã€‚MediaWikiçš„é‹è¡Œéœ€è¦å…¼å®¹æ–¼Perl的正則表é”å¼å‡½æ•¸ã€‚',
+ 'config-pcre-no-utf8' => "'''致命錯誤''':PHPçš„PCRE模塊在編譯時å¯èƒ½æ²’有包å«PCRE_UTF8支æŒã€‚MediaWiki需è¦UTF-8支æŒæ‰èƒ½æ­£å¸¸å·¥ä½œã€‚",
+ 'config-memory-raised' => 'PHP的內存使用上é™<code>memory_limit</code>為$1,自動æå‡åˆ°$2。',
+ 'config-memory-bad' => "'''警告:'''PHP的內存使用上é™<code>memory_limit</code>為$1。該設定å¯èƒ½éŽä½Žï¼Œä¸¦å°Žè‡´å®‰è£å¤±æ•—ï¼",
+ 'config-xcache' => '[http://xcache.lighttpd.net/ XCache]已安è£',
+ 'config-apc' => '[http://www.php.net/apc APC]已安è£',
+ 'config-wincache' => '[http://www.iis.net/download/WinCacheForPhp WinCache]已安è£',
+ 'config-no-cache' => "'''警告:'''找ä¸åˆ°[http://www.php.net/apc APC]ã€[http://xcache.lighttpd.net/ XCache]或[http://www.iis.net/download/WinCacheForPhp WinCache],無法啟用å°è±¡ç·©å­˜ã€‚
+Object caching is not enabled.",
+ 'config-diff3-bad' => '找ä¸åˆ°GNU diff3。',
+ 'config-imagemagick' => '已找到ImageMagick:<code>$1</code>。如果你啟用了上傳功能,縮略圖功能也將被啟用。',
+ 'config-gd' => '已找到內建的GD圖形庫。如果你啟用了上傳功能,縮略圖功能也將被啟用。',
+ 'config-no-scaling' => '找ä¸åˆ°GD庫或ImageMagick。縮略圖功能將ä¸å¯ç”¨ã€‚',
+ 'config-no-uri' => "'''錯誤:'''無法確定當å‰çš„URI。安è£å·²ä¸­æ–·ã€‚",
+ 'config-no-cli-uri' => "'''警告''':未指定--scriptpathåƒæ•¸ï¼Œä½¿ç”¨é»˜èªå€¼ï¼š<code>$1</code>。",
+ 'config-using-server' => '使用æœå‹™å™¨å“<nowiki>$1</nowiki>â€ã€‚',
+ 'config-using-uri' => '使用æœå‹™å™¨URL“<nowiki>$1$2</nowiki>â€ã€‚',
+ 'config-uploads-not-safe' => "'''警告:'''您的默èªä¸Šå‚³ç›®éŒ„<code>$1</code>存在å…許執行任æ„腳本的æ¼æ´žã€‚儘管MediaWiki會å°æ‰€æœ‰å·²ä¸Šå‚³çš„文件進行安全檢查,但我們ä»ç„¶å¼·çƒˆå»ºè­°æ‚¨åœ¨å•Ÿç”¨ä¸Šå‚³åŠŸèƒ½å‰[//www.mediawiki.org/wiki/Manual:Security#Upload_security 關閉該安全æ¼æ´ž]。",
+ 'config-no-cli-uploads-check' => "'''警告''':在CLI安è£éŽç¨‹ä¸­ï¼Œæ²’有å°æ‚¨çš„默èªä¸Šå‚³ç›®éŒ„(<code>$1</code>)進行執行任æ„腳本的æ¼æ´žæª¢æŸ¥ã€‚",
+ 'config-brokenlibxml' => '您的系統安è£çš„PHPå’Œlibxml2版本組åˆå­˜åœ¨æ•…障,並å¯èƒ½åœ¨MediaWiki和其他web應用程åºä¸­é€ æˆéš±è—的數據æ壞。請將PHPå‡ç´šåˆ°5.2.9或以上,libxml2å‡ç´šåˆ°2.7.3或以上([//bugs.php.net/bug.php?id=45996 PHP的故障報告])。安è£å·²ä¸­æ–·ã€‚',
+ 'config-using531' => '由於函數<code>__call()</code>的引用åƒæ•¸å­˜åœ¨æ•…障,PHP $1å’ŒMediaWiki無法兼容。請å‡ç´šåˆ°PHP 5.3.2或更高版本,或é™ç´šåˆ°PHP 5.3.0以修復該å•é¡Œã€‚安è£å·²ä¸­æ–·ã€‚',
+ 'config-suhosin-max-value-length' => 'Suhosin已經安è£ä¸¦å°‡GET請求的åƒæ•¸é•·åº¦é™åˆ¶åœ¨$1字節。MediaWikiçš„ResourceLoader部件å¯ä»¥åœ¨æ­¤é™åˆ¶ä¸‹æ­£å¸¸å·¥ä½œï¼Œä½†å…¶æ€§èƒ½æœƒè¢«é™ä½Žã€‚如果å¯èƒ½ï¼Œè«‹åœ¨php.ini中將suhosin.get.max_value_length設為1024或更高值,並在LocalSettings.php中將$wgResourceLoaderMaxQueryLength設為åŒä¸€å€¼ã€‚',
'config-db-type' => '資料庫類型:',
'config-db-host' => '資料庫主機:',
'config-db-host-oracle' => '資料庫的 TNS:',
+ 'config-db-host-oracle-help' => '請輸入åˆæ³•çš„[http://download.oracle.com/docs/cd/B28359_01/network.111/b28317/tnsnames.htm 本地連接å],並確ä¿tnsnames.ora文件å°æœ¬å®‰è£ç¨‹åºå¯è¦‹ã€‚<br />如果您使用的客戶端庫為10g或更新的版本,您還å¯ä»¥ä½¿ç”¨[http://download.oracle.com/docs/cd/E11882_01/network.112/e10836/naming.htm 簡單連接å方法](easy connect naming method)。',
'config-db-wiki-settings' => '識別這個 Wiki',
'config-db-name' => '資料庫å稱:',
+ 'config-db-name-help' => '請輸入一個å¯ä»¥æ¨™è­˜æ‚¨çš„wikiçš„å稱。請勿使用空格。
+
+如果您正在使用共享web主機,您的主機æ供商或會給您指定一個數據庫å稱,或會讓您通éŽæŽ§åˆ¶é¢æ¿å‰µå»ºæ•¸æ“šåº«ã€‚',
'config-db-name-oracle' => '資料庫架構:',
+ 'config-db-account-oracle-warn' => 'ç¾æœ‰ä¸‰ç¨®å·²æ”¯æŒæ–¹æ¡ˆå¯ä»¥å°‡Oracle設置為後端數據庫:
+
+如果您希望在安è£éŽç¨‹ä¸­å‰µå»ºæ•¸æ“šåº«å¸³æˆ¶ï¼Œè«‹ç‚ºå®‰è£ç¨‹åºæ供具有SYSDBA角色的數據庫帳戶,並為web訪å•å¸³æˆ¶æŒ‡å®šæ‰€éœ€èº«ä»½è­‰æ˜Žï¼›å¦å‰‡æ‚¨å¯ä»¥æ‰‹å‹•å‰µå»ºweb訪å•çš„賬戶並僅須æ供該帳戶(確ä¿å¸³æˆ¶å·²æœ‰å‰µå»ºæ–¹æ¡ˆå°è±¡ï¼ˆschema object)的所需權é™ï¼‰ï¼›æˆ–æ供兩個ä¸åŒçš„帳戶,其一具有創建權é™ï¼Œå¦ä¸€å‰‡è¢«é™åˆ¶ç‚ºweb訪å•ã€‚
+
+具有所需權é™è³¬æˆ¶çš„創建腳本存放於本程åºçš„“maintenance/oracle/â€ç›®éŒ„下。請注æ„,使用å—é™åˆ¶çš„帳戶將ç¦ç”¨é»˜èªå¸³æˆ¶çš„所有維護性功能。',
+ 'config-db-install-account' => '用於安è£çš„用戶帳號',
'config-db-username' => '資料庫使用者å稱:',
'config-db-password' => '資料庫密碼:',
+ 'config-db-password-empty' => '請為新數據庫用戶$1輸入密碼。儘管您å¯ä»¥å‰µå»ºä¸ä½¿ç”¨å¯†ç¢¼çš„用戶,但這樣åšä¸¦ä¸å®‰å…¨ã€‚',
+ 'config-db-install-username' => '請輸入在安è£éŽç¨‹ä¸­ç”¨æ–¼é€£æŽ¥æ•¸æ“šåº«çš„用戶å。請勿輸入MediaWiki帳號的用戶å,請輸入您數據庫的用戶å。',
+ 'config-db-install-password' => '請輸入在安è£éŽç¨‹ä¸­ç”¨æ–¼é€£æŽ¥æ•¸æ“šåº«çš„密碼。請勿輸入MediaWiki帳號的密碼,請輸入您數據庫的密碼。',
+ 'config-db-install-help' => '請輸入在安è£éŽç¨‹ä¸­ç”¨æ–¼é€£æŽ¥æ•¸æ“šåº«çš„用戶å和密碼。',
+ 'config-db-account-lock' => '在普通æ“作中使用相åŒçš„用戶å和密碼',
+ 'config-db-wiki-account' => '用於普通æ“作的用戶帳號',
+ 'config-db-wiki-help' => '輸入在普通的wikiæ“作中(安è£å®Œæˆå¾Œï¼‰å°‡ç”¨æ–¼é€£æŽ¥æ•¸æ“šåº«çš„用戶å和密碼。如果該帳號並ä¸å­˜åœ¨ï¼Œè€Œå®‰è£å¸³è™Ÿå…·æœ‰è¶³å¤ çš„權é™ï¼Œè©²ç”¨æˆ¶å¸³è™Ÿæœƒè¢«è‡ªå‹•å‰µå»ºï¼Œä¸¦è¢«è³¦äºˆè¶³ä»¥é‹è¡Œæ­¤wiki的最低權é™ã€‚',
+ 'config-db-prefix' => '數據庫表å‰ç¶´ï¼š',
+ 'config-db-prefix-help' => '如果您需è¦åœ¨å¤šå€‹wiki之間(或在MediaWiki與其他web應用程åºä¹‹é–“)共享一個數據庫,您å¯ä»¥é€šéŽæ·»åŠ å‰ç¶´çš„æ–¹å¼ä¾†é¿å…出ç¾è¡¨å稱的è¡çªã€‚請勿使用空格。
+
+此字段通常å¯ç•™ç©ºã€‚',
+ 'config-db-charset' => '數據庫字符集',
+ 'config-charset-mysql5-binary' => 'MySQL 4.1/5.0 二進制',
+ 'config-charset-mysql5' => 'MySQL 4.1/5.0 UTF-8',
+ 'config-charset-mysql4' => 'MySQL 4.0 UTF-8(å‘後兼容)',
+ 'config-charset-help' => "'''警告:'''如果您在MySQL 4.1+中使用'''å‘後兼容的UTF-8'''字符集,並在之後使用<code>mysqldump</code>備份了數據庫,則å¯èƒ½æ壞所有的éžASCII字符,從而ä¸å¯é€†åœ°ç ´å£žæ‚¨çš„備份ï¼
+
+在'''二進制模å¼'''下,MediaWiki會將UTF-8編碼的文本存於數據庫的二進制字段中。相å°æ–¼MySQLçš„UTF-8模å¼ï¼Œé€™ç¨®æ–¹æ³•æ•ˆçŽ‡æ›´é«˜ï¼Œä¸¦å…許您使用全範åœçš„Unicode字符。
+
+在'''UTF-8模å¼'''下,MySQL將知é“您數據使用的字符集,並能é©ç•¶åœ°æ供和轉æ›å…§å®¹ã€‚但這樣åšæ‚¨å°‡ç„¡æ³•åœ¨æ•¸æ“šåº«ä¸­å­˜å„²[//zh.wikipedia.org/wiki/åŸºæœ¬å¤šæ–‡ç¨®å¹³é¢ åŸºæœ¬å¤šæ–‡ç¨®å¹³é¢]以外的字符。",
+ 'config-mysql-old' => '需è¦MySQL $1或更新的版本,您的版本為$2。',
+ 'config-db-port' => '數據庫端å£ï¼š',
+ 'config-db-schema' => 'MediaWiki的數據庫模å¼',
+ 'config-db-schema-help' => '此數據庫模å¼é€šå¸¸æ˜¯æ­£ç¢ºçš„,請在有明確需求時æ‰æ”¹å‹•ä¹‹ã€‚',
+ 'config-pg-test-error' => "無法連接到數據庫'''$1''':$2",
'config-sqlite-dir' => 'SQLite 的資料目錄:',
+ 'config-sqlite-dir-help' => "SQLite會將所有的數據存儲於單一文件中。
+
+您所æ供的目錄必須在安è£éŽç¨‹ä¸­å°ç¶²é æœå‹™å™¨å¯å¯«ã€‚
+
+該目錄'''ä¸æ‡‰'''å…許通éŽweb訪å•ï¼Œå› æ­¤æˆ‘們ä¸æœƒå°‡æ•¸æ“šæ–‡ä»¶å’ŒPHP文件放在一起。
+
+安è£ç¨‹åºåœ¨å‰µå»ºæ•¸æ“šæ–‡ä»¶æ™‚,亦會在相åŒç›®éŒ„下創建<code>.htaccess</code>以控制權é™ã€‚å‡è‹¥æ­¤ç­‰æŽ§åˆ¶å¤±æ•ˆï¼Œå‰‡å¯èƒ½æœƒå°‡æ‚¨çš„數據文件暴露於公共空間,讓他人å¯ä»¥ç²å–用戶數據(電å­éƒµä»¶åœ°å€ã€é›œæ¹Šå¾Œçš„密碼)ã€è¢«åˆªé™¤çš„版本以åŠå…¶ä»–在wiki上被é™åˆ¶è¨ªå•çš„數據。
+
+請考慮將數據庫統一放置在æŸè™•ï¼Œå¦‚<code>/var/lib/mediawiki/yourwiki</code>下。",
+ 'config-oracle-def-ts' => '默èªè¡¨ç©ºé–“:',
+ 'config-oracle-temp-ts' => '臨時表空間:',
+ 'config-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-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' => '甲骨文設定',
+ '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â€ã€‚è«‹åªä½¿ç”¨ASCIIå­—æ¯ï¼ˆa-zã€A-Z)ã€æ•¸å­—(0-9)ã€ä¸‹åŠƒç·šï¼ˆ_)和點號(.)。',
+ 'config-invalid-db-name' => '無效的數據庫å稱“$1â€ã€‚è«‹åªä½¿ç”¨ASCIIå­—æ¯ï¼ˆa-zã€A-Z)ã€æ•¸å­—(0-9)ã€ä¸‹åŠƒç·šï¼ˆ_)和連字號(-)。',
+ 'config-invalid-db-prefix' => '無效的數據庫å‰ç¶´â€œ$1â€ã€‚è«‹åªä½¿ç”¨ASCIIå­—æ¯ï¼ˆa-zã€A-Z)ã€æ•¸å­—(0-9)ã€ä¸‹åŠƒç·šï¼ˆ_)和連字號(-)。',
+ 'config-connection-error' => '$1。
+
+請檢查下列的主機ã€ç”¨æˆ¶å和密碼設置後é‡è©¦ã€‚',
+ 'config-invalid-schema' => '無效的MediaWiki數據庫模å¼â€œ$1â€ã€‚è«‹åªä½¿ç”¨ASCIIå­—æ¯ï¼ˆa-zã€A-Z)ã€æ•¸å­—(0-9)和下劃線(_)。',
+ 'config-db-sys-create-oracle' => '安è£ç¨‹åºåƒ…支æŒä½¿ç”¨SYSDBA帳戶創建新帳戶。',
+ 'config-db-sys-user-exists-oracle' => '用戶帳戶“$1â€å·²ç¶“存在。SYSDBA僅å¯ç”¨æ–¼å‰µå»ºæ–°å¸³æˆ¶ï¼',
+ 'config-postgres-old' => '需è¦PostgreSQL $1或更新的版本,您的版本為$2。',
+ 'config-sqlite-name-help' => '請為您的wiki指定一個用於標識的å稱。請勿使用空格或連字號,該å稱將被用作SQLite的數據文件å。',
+ 'config-sqlite-parent-unwritable-group' => '由於父目錄<code><nowiki>$2</nowiki></code>å°ç¶²é æœå‹™å™¨ä¸å¯å¯«ï¼Œç„¡æ³•å‰µå»ºæ•¸æ“šç›®éŒ„<code><nowiki>$1</nowiki></code>。
+
+安è£ç¨‹åºå·²ç¢ºå®šæ‚¨ç¶²é æœå‹™å™¨æ‰€ä½¿ç”¨çš„用戶。請將<code><nowiki>$3</nowiki></code>目錄設為å°è©²ç”¨æˆ¶å¯å¯«ä»¥ç¹¼çºŒå®‰è£éŽç¨‹ã€‚在Unix/Linux系統中,您å¯ä»¥é€è¡Œè¼¸å…¥ä¸‹åˆ—命令:
+
+<pre>cd $2
+mkdir $3
+chgrp $4 $3
+chmod g+w $3</pre>',
+ 'config-sqlite-parent-unwritable-nogroup' => '由於父目錄<code><nowiki>$2</nowiki></code>å°ç¶²é æœå‹™å™¨ä¸å¯å¯«ï¼Œç„¡æ³•å‰µå»ºæ•¸æ“šç›®éŒ„<code><nowiki>$1</nowiki></code>。
+
+安è£ç¨‹åºç„¡æ³•ç¢ºå®šæ‚¨ç¶²é æœå‹™å™¨æ‰€ä½¿ç”¨çš„用戶。請將<code><nowiki>$3</nowiki></code>目錄設為全局å¯å¯«ï¼ˆå°æ‰€æœ‰ç”¨æˆ¶ï¼‰ä»¥ç¹¼çºŒå®‰è£éŽç¨‹ã€‚在Unix/Linux系統中,您å¯ä»¥é€è¡Œè¼¸å…¥ä¸‹åˆ—命令:
+
+<pre>cd $2
+mkdir $3
+chmod a+w $3</pre>',
+ 'config-sqlite-mkdir-error' => '創建數據目錄“$1â€æ™‚發生錯誤。請檢查路徑後é‡è©¦ã€‚',
+ 'config-sqlite-dir-unwritable' => '無法寫入目錄“$1â€ã€‚請修改該目錄的權é™ï¼Œä½¿å…¶å°ç¶²é æœå‹™å™¨å¯å¯«å¾Œé‡è©¦ã€‚',
+ 'config-sqlite-connection-error' => '$1。
+
+請檢查下列的數據目錄和數據庫å稱後é‡è©¦ã€‚',
+ 'config-sqlite-readonly' => '文件<code>$1</code>ä¸å¯å¯«ã€‚',
+ 'config-sqlite-cant-create-db' => '無法創建數據文件<code>$1</code>。',
+ 'config-sqlite-fts3-downgrade' => 'PHP缺少FTS3支æŒï¼Œæ­£åœ¨é™ç´šæ•¸æ“šè¡¨',
+ 'config-can-upgrade' => "在數據庫中發ç¾äº†MediaWiki的數據表。è¦å°‡å®ƒå€‘å‡ç´šè‡³MediaWiki $1,請點擊'''繼續'''。",
+ 'config-upgrade-done' => "å‡ç´šå®Œæˆã€‚
+
+ç¾åœ¨æ‚¨å¯ä»¥[$1 開始使用您的wiki]了。
+
+如果您需è¦é‡æ–°ç”Ÿæˆ<code>LocalSettings.php</code>文件,請點擊下é¢çš„按鈕。除éžæ‚¨çš„wiki出ç¾äº†å•é¡Œï¼Œæˆ‘們'''ä¸æŽ¨è–¦'''您執行此æ“作。",
+ 'config-upgrade-done-no-regenerate' => 'å‡ç´šå®Œæˆã€‚
+
+ç¾åœ¨æ‚¨å¯ä»¥[$1 開始使用您的wiki]了。',
+ 'config-regenerate' => 'é‡æ–°ç”ŸæˆLocalSettings.php →',
+ 'config-show-table-status' => '查詢SHOW TABLE STATUS失敗ï¼',
+ 'config-unknown-collation' => "'''警告:'''數據庫使用了無法識別的整ç†ã€‚",
+ 'config-db-web-account' => '供網é è¨ªå•ä½¿ç”¨çš„數據庫帳號',
+ 'config-db-web-help' => '請指定在wiki執行普通æ“作時,網é æœå‹™å™¨ç”¨æ–¼é€£æŽ¥æ•¸æ“šåº«æœå‹™å™¨çš„用戶å和密碼。',
+ 'config-db-web-account-same' => '使用和安è£ç¨‹åºç›¸åŒçš„帳號',
'config-db-web-create' => '建立帳號,如果它ä¸å­˜åœ¨',
+ 'config-db-web-no-create-privs' => '您指定給安è£ç¨‹åºçš„帳號缺少創建帳號的權é™ï¼Œå› æ­¤æ‚¨æŒ‡å®šçš„帳號必須已經存在。',
+ 'config-mysql-engine' => '存儲引擎:',
+ 'config-mysql-innodb' => 'InnoDB',
+ 'config-mysql-myisam' => 'MyISAM',
+ 'config-mysql-myisam-dep' => "'''警告''':您é¸æ“‡äº†MyISAM作為MySQL的存儲引擎,MediaWiki並ä¸æŽ¨è–¦æ‚¨é€™éº¼åšï¼Œå› ç‚ºï¼š
+* 它僅能通éŽè¡¨éŽ–定來勉強支æŒä¸¦ç™¼
+* 與其他引擎相比,它更容易被æ壞
+* MediaWiki代碼庫並ä¸ç¸½æœƒåŽ»è™•ç†MyISAM
+
+如果您的MySQL程åºæ”¯æŒInnoDB,我們高度推薦您使用該引擎替代MyISAM。
+如果您的MySQL程åºä¸æ”¯æŒInnoDB,請考慮å‡ç´šã€‚",
+ 'config-mysql-engine-help' => "'''InnoDB'''通常是最佳é¸é …,因為它å°ä¸¦ç™¼æ“作有ç€è‰¯å¥½çš„支æŒã€‚
+
+'''MyISAM'''在單用戶或åªè®€ç’°å¢ƒä¸‹å¯èƒ½æœƒæœ‰æ›´å¿«çš„性能表ç¾ã€‚但MyISAM數據庫出錯的概率一般è¦å¤§æ–¼InnoDB數據庫。",
'config-mysql-charset' => '資料庫字符集:',
+ 'config-mysql-binary' => '二進制',
'config-mysql-utf8' => 'UTF-8',
+ 'config-mysql-charset-help' => "在'''二進制模å¼'''下,MediaWiki會將UTF-8編碼的文本存於數據庫的二進制字段中。相å°æ–¼MySQLçš„UTF-8模å¼ï¼Œé€™ç¨®æ–¹æ³•æ•ˆçŽ‡æ›´é«˜ï¼Œä¸¦å…許您使用全範åœçš„Unicode字符。
+
+在'''UTF-8模å¼'''下,MySQL將知é“您數據使用的字符集,並能é©ç•¶åœ°æ供和轉æ›å…§å®¹ã€‚但這樣åšæ‚¨å°‡ç„¡æ³•åœ¨æ•¸æ“šåº«ä¸­å­˜å„²[//zh.wikipedia.org/wiki/åŸºæœ¬å¤šæ–‡ç¨®å¹³é¢ åŸºæœ¬å¤šæ–‡ç¨®å¹³é¢]以外的字符。",
+ 'config-ibm_db2-low-db-pagesize' => "您的DB2數據庫默èªè¡¨ç©ºé–“çš„é é•·ï¼ˆpagesize)ä¸è¶³ã€‚至少需è¦'''32K'''或更大的é é•·ã€‚",
+ 'config-site-name' => 'Wikiçš„å稱:',
+ 'config-site-name-help' => '填入的內容會出ç¾åœ¨ç€è¦½å™¨çš„標題欄以åŠå…¶ä»–多處ä½ç½®ä¸­ã€‚',
'config-site-name-blank' => '輸入站點å稱。',
+ 'config-project-namespace' => 'é …ç›®å字空間:',
+ 'config-ns-generic' => 'é …ç›®',
+ 'config-ns-site-name' => '與wikiå稱相åŒï¼š$1',
'config-ns-other' => '其他(請註明)',
+ 'config-ns-other-default' => '我的Wiki',
+ 'config-project-namespace-help' => "ä¾å¾ªç¶­åŸºç™¾ç§‘å½¢æˆçš„慣例,許多wiki將他們的方é‡é é¢å­˜æ”¾åœ¨èˆ‡å…§å®¹é é¢ä¸åŒçš„“'''é …ç›®å字空間'''â€ä¸­ã€‚所有ä½æ–¼è©²å字空間下的é é¢æ¨™é¡Œéƒ½æœƒè¢«å† ä»¥å›ºå®šçš„å‰ç¶´ï¼Œæ‚¨å¯ä»¥åœ¨æ­¤è™•æŒ‡å®šé€™ä¸€å‰ç¶´ã€‚傳統上,這一å‰ç¶´æ‡‰èˆ‡wiki的命åä¿æŒä¸€è‡´ï¼Œä½†è«‹å‹¿åœ¨å…¶ä¸­ä½¿ç”¨æ¨™é»žç¬¦è™Ÿï¼Œå¦‚“#â€æˆ–“:â€ã€‚",
+ 'config-ns-invalid' => '指定的å字空間“<nowiki>$1</nowiki>â€ç„¡æ•ˆï¼Œè«‹ç‚ºé …ç›®å字空間指定其他å稱。',
+ 'config-ns-conflict' => '指定的å字空間“<nowiki>$1</nowiki>â€èˆ‡é»˜èªçš„MediaWikiå字空間è¡çªã€‚請指定一個ä¸åŒçš„é …ç›®å字空間。',
+ 'config-admin-box' => '管ç†å“¡å¸³è™Ÿ',
+ 'config-admin-name' => '您的å字:',
'config-admin-password' => '密碼:',
'config-admin-password-confirm' => 'å†æ¬¡è¼¸å…¥å¯†ç¢¼ï¼š',
+ 'config-admin-help' => '在此輸入您想使用的用戶å,例如“喬幫主â€ã€‚您將使用該å稱登錄本wiki。',
'config-admin-name-blank' => '輸入管ç†å“¡çš„使用者å稱。',
+ 'config-admin-name-invalid' => '指定的用戶å“<nowiki>$1</nowiki>â€ç„¡æ•ˆï¼Œè«‹æŒ‡å®šå…¶ä»–用戶å。',
'config-admin-password-blank' => '輸入管ç†å“¡å¸³è™Ÿå¯†ç¢¼ã€‚',
'config-admin-password-same' => '密碼ä¸èƒ½èˆ‡ä½¿ç”¨è€…å稱相åŒã€‚',
+ 'config-admin-password-mismatch' => '兩次輸入的密碼並ä¸ç›¸åŒã€‚',
'config-admin-email' => 'E-mail 地å€ï¼š',
+ 'config-admin-email-help' => '輸入電å­éƒµä»¶åœ°å€å¾Œï¼Œæ‚¨å¯ä»¥æ”¶åˆ°æ­¤wiki上其他用戶發來的電å­éƒµä»¶ï¼Œä¸¦èƒ½é‡ç½®æ‚¨çš„密碼,還å¯åœ¨ç›£è¦–列表中é é¢è¢«æ›´æ”¹æ™‚收到郵件通知。您å¯ä»¥å°‡æ­¤å­—段留空。',
+ 'config-admin-error-user' => '在創建用戶å為“<nowiki>$1</nowiki>â€çš„管ç†å“¡å¸³è™Ÿæ™‚發生內部錯誤。',
+ 'config-admin-error-password' => '在為管ç†å“¡â€œ<nowiki>$1</nowiki>â€è¨­ç½®å¯†ç¢¼æ™‚發生內部錯誤:<pre>$2</pre>',
'config-admin-error-bademail' => '你輸入了一個無效的電å­éƒµä»¶åœ°å€ã€‚',
+ 'config-subscribe' => '訂閱[https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce 發行公告郵件列表]。',
+ 'config-subscribe-help' => '此低æµé‡çš„郵件列表僅用於發行公告,其中包括é‡è¦å®‰å…¨å…¬å‘Šã€‚請訂閱該列表以便在新的版本推出時å‡ç´šæ‚¨çš„MediaWiki。',
+ 'config-subscribe-noemail' => '您é¸æ“‡äº†è¨‚閱發行公告郵件列表,但沒有æ供電å­éƒµä»¶åœ°å€ã€‚è«‹æ供一個電å­éƒµä»¶åœ°å€ä»¥è¨‚閱郵件列表。',
+ 'config-almost-done' => '您幾乎已經完æˆäº†ï¼ç¾åœ¨æ‚¨å¯ä»¥è·³éŽå‰©ä¸‹çš„é…ç½®æµç¨‹ä¸¦ç«‹å³å®‰è£wiki。',
+ 'config-optional-continue' => '多å•æˆ‘一些å•é¡Œå§ã€‚',
+ 'config-optional-skip' => '我已經ä¸è€ç…©äº†ï¼Œè¶•ç·Šå®‰è£æˆ‘çš„wiki。',
+ 'config-profile' => '用戶權é™é…置:',
+ 'config-profile-wiki' => '傳統wiki',
+ 'config-profile-no-anon' => '需è¦è¨»å†Šå¸³è™Ÿ',
+ 'config-profile-fishbowl' => '編輯å—é™',
+ 'config-profile-private' => 'éžå…¬é–‹wiki',
+ 'config-profile-help' => "如果您å…許盡é‡å¤šçš„人編寫wiki,網站上的內容會更加è±å¯Œã€‚在MediaWiki中,您å¯ä»¥è¼•é¬†åœ°å¯©æŸ¥æœ€è¿‘更改,並輕易回退掉新手或破壞者造æˆçš„æ害。
+
+然而,許多人覺得讓MediaWiki存在多種角色將更加好用;åŒæ™‚,è¦èªªæœæ‰€æœ‰äººéƒ½é¡˜ä»¥wikiçš„æ–¹å¼ä½œè²¢ç»ä¸¦éžä¸€ä»¶æ˜“事。因此,您å¯ä»¥æœ‰ä»¥ä¸‹é¸æ“‡ï¼š
+
+'''{{int:config-profile-wiki}}'''å…許包括未登錄用戶在內的所有人編輯。'''{{int:config-profile-no-anon}}'''çš„wiki需è¦é¡å¤–的註冊æµç¨‹ï¼Œé€™æœ‰å¯èƒ½æœƒé˜»ç¤™éš¨æ„è²¢ç»è€…。
+
+'''{{int:config-profile-fishbowl}}'''模å¼åªå…許ç²æ‰¹å‡†çš„用戶編輯,但å°å…¬çœ¾é–‹æ”¾é é¢ç€è¦½ï¼ˆåŒ…括歷å²è¨˜éŒ„)。'''{{int:config-profile-private}}'''則åªå…許ç²æ‰¹å‡†çš„用戶ç€è¦½ã€ç·¨è¼¯é é¢ã€‚
+
+安è£å®Œæˆå¾Œï¼Œæ‚¨é‚„å¯ä»¥å°ç”¨æˆ¶æ¬Šé™é€²è¡Œæ›´å¤šã€æ›´è¤‡é›œçš„é…置,åƒè¦‹[//www.mediawiki.org/wiki/Manual:User_rights 相關的使用手冊]。",
'config-license' => '版權和許å¯è­‰ï¼š',
+ 'config-license-none' => 'é è…³ç„¡è¨±å¯è­‰',
+ 'config-license-cc-by-sa' => '知識共享署å-相åŒæ–¹å¼åˆ†äº«',
+ 'config-license-cc-by' => '知識共享署å',
+ 'config-license-cc-by-nc-sa' => '知識共享署å-éžå•†æ¥­æ€§ä½¿ç”¨-相åŒæ–¹å¼å…±äº«',
+ 'config-license-cc-0' => '知識共享Zero(公有領域)',
+ 'config-license-gfdl' => 'GNU自由文檔許å¯è­‰1.3或更高版本',
'config-license-pd' => '公共領域',
+ 'config-license-cc-choose' => 'é¸æ“‡è‡ªå®šç¾©çš„知識共享許å¯è­‰',
+ 'config-license-help' => "許多公共wiki會以[http://freedomdefined.org/Definition 自由許å¯è­‰]çš„æ–¹å¼é‡‹æ”¾å‡ºç·¨è€…的所有貢ç»ã€‚這有助於構建社å€çš„主人ç¿æ„識,並能鼓勵長期貢ç»ã€‚å°æ–¼éžå…¬å…±wiki或公å¸wiki,這並éžå¿…è¦æ¢ä»¶ã€‚
+
+如果您希望使用來自維基百科的內容,並希望維基百科能接å—複製自您的wiki的內容,請é¸æ“‡'''知識共享署å-相åŒæ–¹å¼å…±äº«'''。
+
+GNU自由文檔許å¯è­‰æ˜¯ç¶­åŸºç™¾ç§‘曾經使用éŽçš„許å¯è­‰ï¼Œä¸¦è¿„今有效。然而,該許å¯è­‰é›£ä»¥ç†è§£ï¼Œä¸¦æœƒå¢žåŠ é‡ç”¨å…§å®¹çš„難度。",
'config-email-settings' => 'E-mail 設定',
+ 'config-enable-email' => '啟用出站電å­éƒµä»¶',
+ 'config-enable-email-help' => '如果您希望使用電å­éƒµä»¶åŠŸèƒ½ï¼Œè«‹æ­£ç¢ºé…ç½®[http://www.php.net/manual/en/mail.configuration.php PHP的郵件設定]。如果您ä¸éœ€è¦ä»»ä½•é›»å­éƒµä»¶åŠŸèƒ½ï¼Œè«‹åœ¨æ­¤è™•ç¦ç”¨å®ƒã€‚',
+ 'config-email-user' => '啟用用戶到用戶的電å­éƒµä»¶',
+ 'config-email-user-help' => 'å…許所有用戶互發郵件,å‡è‹¥ä»–們啟用了該功能。',
+ 'config-email-usertalk' => '啟用用戶討論é é€šçŸ¥',
+ 'config-email-usertalk-help' => 'å…許用戶收到用戶討論é è¢«ä¿®æ”¹çš„通知,å‡è‹¥ä»–們啟用了該功能。',
+ 'config-email-watchlist' => '啟用監視列表通知',
+ 'config-email-watchlist-help' => 'å…許用戶收到與其監視列表有關的通知,å‡è‹¥ä»–們啟用了該功能。',
'config-email-auth' => '啟用電å­éƒµä»¶èªè­‰',
+ 'config-email-auth-help' => "如果啟用此é¸é …,在用戶設置或修改電å­éƒµä»¶åœ°å€æ™‚,就會收到一å°éƒµä»¶ï¼Œå…§å«ç¢ºèªé›»å­åœ°å€çš„éˆæŽ¥ã€‚åªæœ‰ç¶“éŽèº«ä»½é©—證的電å­éƒµä»¶åœ°å€ï¼Œæ‰èƒ½æ”¶åˆ°ä¾†è‡ªå…¶ä»–用戶的電å­éƒµä»¶ï¼Œæˆ–任何修改通知的郵件。'''建議'''公開wiki啟用本é¸é …,以防å°é›»å­éƒµä»¶åŠŸèƒ½çš„濫用。",
'config-email-sender' => '返回電å­éƒµä»¶åœ°å€ï¼š',
+ 'config-email-sender-help' => '輸入è¦ç”¨ä¾†ç™¼é€å‡ºç«™é›»å­éƒµä»¶çš„地å€ï¼Œè©²åœ°å€å°‡æœƒæ”¶åˆ°è¢«æ‹’收的郵件。許多郵件æœå‹™å™¨è¦æ±‚域å部分必須有效。',
'config-upload-settings' => '圖片和檔案上傳',
'config-upload-enable' => '啟用檔案上傳',
+ 'config-upload-help' => '文件上傳å¯èƒ½æœƒå°‡æ‚¨çš„æœå‹™å™¨æš´éœ²åœ¨å®‰å…¨é¢¨éšªä¸‹ã€‚有關更多的信æ¯ï¼Œè«‹åƒé–±æ‰‹å†Šçš„[//www.mediawiki.org/wiki/Manual:Security 安全部分]。
+
+è¦å•Ÿç”¨æ–‡ä»¶ä¸Šå‚³ï¼Œè«‹å…ˆå°‡MediaWiki根目錄下的<code>images</code>å­ç›®éŒ„更改為å°webæœå‹™å™¨å¯å¯«ï¼Œç„¶å¾Œå†å•Ÿç”¨æ­¤é¸é …。',
+ 'config-upload-deleted' => '已刪除文件的目錄:',
+ 'config-upload-deleted-help' => '指定用於存放被刪除文件的目錄。ç†æƒ³æƒ…æ³ä¸‹ï¼Œè©²ç›®éŒ„ä¸æ‡‰èƒ½é€šéŽweb訪å•ã€‚',
+ 'config-logo' => '標誌URL:',
+ 'config-logo-help' => '在MediaWiki的默èªå¤–觀中,左å´æ¬„èœå–®ä¹‹ä¸Šæœ‰ä¸€å¡Š135x160åƒç´ çš„標誌å€ã€‚請上傳一幅相應大å°çš„圖åƒï¼Œä¸¦åœ¨æ­¤è¼¸å…¥URL。
+
+如果您ä¸å¸Œæœ›ä½¿ç”¨æ¨™èªŒï¼Œè«‹å°‡æœ¬è™•ç•™ç©ºã€‚',
+ 'config-instantcommons' => '啟用å³æ™‚共享資æº',
+ 'config-instantcommons-help' => '[//www.mediawiki.org/wiki/InstantCommons å³æ™‚共享資æº]å¯ä»¥è®“wiki使用來自[//commons.wikimedia.org/ 維基共享資æº]網站的圖åƒã€éŸ³é »å’Œå…¶ä»–媒體文件。è¦å•Ÿç”¨è©²åŠŸèƒ½ï¼ŒMediaWiki必須能夠訪å•äº’è¯ç¶²ã€‚
+
+有關此功能的詳細信æ¯ï¼ŒåŒ…括如何將其他wiki網站設為具有類似共享功能的方法,請åƒè€ƒ[//mediawiki.org/wiki/Manual:$wgForeignFileRepos 手冊]。',
+ 'config-cc-error' => '知識共享許å¯è­‰æŒ‘é¸å™¨ç„¡æ³•æ‰¾åˆ°çµæžœï¼Œè«‹æ‰‹å‹•è¼¸å…¥è¨±å¯è­‰çš„å稱。',
'config-cc-again' => 'é‡æ–°é¸å–......',
+ 'config-cc-not-chosen' => 'é¸æ“‡æ‚¨å¸Œæœ›ä½¿ç”¨çš„知識共享許å¯è­‰ï¼Œä¸¦é»žæ“Šâ€œç¹¼çºŒâ€ã€‚',
'config-advanced-settings' => '進階é…ç½®',
+ 'config-cache-options' => 'å°è±¡ç·©å­˜è¨­ç½®ï¼š',
+ 'config-cache-help' => 'å°è±¡ç·©å­˜å¯é€šéŽç·©å­˜é »ç¹ä½¿ç”¨çš„數據來æ高MediaWiki的速度。高度推薦中到大型的網站啟用該功能,å°åž‹ç¶²ç«™äº¦èƒ½å¾žå…¶ä¸­å—益。',
+ 'config-cache-none' => '無緩存(ä¸å½±éŸ¿åŠŸèƒ½ï¼Œä½†å°è¼ƒå¤§åž‹çš„wiki網站會有速度影響)',
+ 'config-cache-accel' => 'PHPå°è±¡ç·©å­˜ï¼ˆAPCã€XCache或WinCache)',
+ 'config-cache-memcached' => '使用Memcached(需è¦å¦å¤–安è£ä¸¦é…置)',
+ 'config-memcached-servers' => 'Memcachedæœå‹™å™¨ï¼š',
+ 'config-memcached-help' => '用於Memcachedçš„IP地å€åˆ—表。請ä¿æŒæ¯è¡Œä¸€æ¢ï¼Œä¸¦æŒ‡å®šè¦ä½¿ç”¨çš„端å£ã€‚例如:
+127.0.0.1:11211
+192.168.1.25:1234',
+ 'config-memcache-needservers' => '您é¸æ“‡äº†Memcached作為您的緩存,但並未指定任何æœå‹™å™¨ã€‚',
+ 'config-memcache-badip' => '您為Memcached輸入了無效的IP地å€ï¼š$1。',
+ 'config-memcache-noport' => '您沒有指定Memcachedæœå‹™å™¨çš„端å£ï¼š$1。如果您ä¸æ¸…楚端å£æ˜¯å¤šå°‘,默èªå€¼ç‚º11211。',
+ 'config-memcache-badport' => 'Memcached的端å£è™Ÿæ‡‰è©²åœ¨$1到$2之間。',
'config-extensions' => '擴充套件',
+ 'config-extensions-help' => '已在您的<code>./extensions</code>目錄中發ç¾ä¸‹åˆ—擴展。
+
+您å¯èƒ½è¦å°å®ƒå€‘進行é¡å¤–çš„é…置,但您ç¾åœ¨å¯ä»¥å•Ÿç”¨å®ƒå€‘。',
+ 'config-install-alreadydone' => "'''警告:'''您似乎已經安è£äº†MediaWiki,並試圖é‡æ–°å®‰è£å®ƒã€‚è«‹å‰å¾€ä¸‹ä¸€å€‹é é¢ã€‚",
+ 'config-install-begin' => '點擊“{{int:config-continue}}â€å¾Œï¼Œæ‚¨å°‡é–‹å§‹å®‰è£MediaWiki。如果您還想å°é…置作一些修改,請點擊後退。',
'config-install-step-done' => '完æˆ',
'config-install-step-failed' => '失敗',
+ 'config-install-extensions' => '正在啟用擴展',
+ 'config-install-database' => '正在é…置數據庫',
+ 'config-install-schema' => '創建架構',
+ 'config-install-pg-schema-not-exist' => 'PostgreSQL 架構ä¸å­˜åœ¨',
+ 'config-install-pg-schema-failed' => '創建數據表失敗。請確ä¿ç”¨æˆ¶â€œ$1â€æ“有寫入模å¼â€œ$2â€çš„權é™ã€‚',
'config-install-pg-commit' => 'æ交更改',
+ 'config-install-pg-plpgsql' => '正在檢查PL/pgSQL語言',
+ 'config-pg-no-plpgsql' => '您需è¦ç‚ºæ•¸æ“šåº«$1安è£PL/pgSQL語言',
+ 'config-pg-no-create-privs' => '為安è£ç¨‹åºæŒ‡å®šçš„帳號缺少創建帳號的權é™ã€‚',
+ 'config-pg-not-in-role' => '您指定為web用戶的帳戶已經存在。
+您給本程åºæŒ‡å®šçš„帳戶ä¸æ˜¯è¶…級用戶,也ä¸æ˜¯web用戶角色的æˆå“¡ï¼Œæ‰€ä»¥å®ƒä¸èƒ½å‰µå»ºweb用戶所æ“有的å°è±¡ã€‚
+
+MediaWiki當å‰éœ€è¦ä½¿ç”¨ç”±web用戶所有的表。請指定å¦ä¸€å€‹web帳戶å稱,或點擊“後退â€ä¸¦æŒ‡å®šå…·æœ‰é©ç•¶æ¬Šé™çš„安è£ç”¨æˆ¶ã€‚',
+ 'config-install-user' => '正在創建數據庫用戶',
+ 'config-install-user-alreadyexists' => '用戶“$1â€å·²å­˜åœ¨',
+ 'config-install-user-create-failed' => '創建用戶“$1â€å¤±æ•—:$2',
+ 'config-install-user-grant-failed' => '授予用戶“$1â€æ¬Šé™å¤±æ•—:$2',
+ 'config-install-user-missing' => '指定的用戶“$1â€ä¸å­˜åœ¨ã€‚',
+ 'config-install-user-missing-create' => '指定的用戶“$1â€ä¸å­˜åœ¨ã€‚如果您想è¦å‰µå»ºä¸€å,請點é¸â€œå‰µå»ºå¸³æˆ¶â€ä¸‹é¢çš„複é¸æ¡†ã€‚',
+ 'config-install-tables' => '正在創建數據表',
+ 'config-install-tables-exist' => "'''警告''':MediaWiki的數據表似乎已經存在,跳éŽå‰µå»ºã€‚",
+ 'config-install-tables-failed' => "'''錯誤''':創建數據表出錯,下為錯誤信æ¯ï¼š$1",
+ 'config-install-interwiki' => '正在填充默èªçš„è·¨wiki數據表',
+ 'config-install-interwiki-list' => '找ä¸åˆ°æ–‡ä»¶<code>interwiki.list</code>。',
+ 'config-install-interwiki-exists' => "'''警告''':跨wiki數據表似乎已有內容,跳éŽé»˜èªåˆ—表。",
+ 'config-install-stats' => 'åˆå§‹åŒ–統計',
+ 'config-install-keys' => '生æˆå¯†é‘°ä¸­',
+ 'config-insecure-keys' => "'''警告''':在安è£éŽç¨‹ä¸­ç”Ÿæˆçš„{{PLURAL:$2|安全密鑰|安全密鑰}}($1){{PLURAL:$2|並|並}}ä¸ä¸€å®šå®‰å…¨ã€‚請考慮手動更改{{PLURAL:$2|它|它們}}。",
+ 'config-install-sysop' => '正在創建管ç†å“¡ç”¨æˆ¶å¸³è™Ÿ',
+ 'config-install-subscribe-fail' => '無法訂閱mediawiki-announce:$1',
+ 'config-install-subscribe-notpossible' => '沒有安è£cURL,allow_url_fopen也ä¸å¯ç”¨ã€‚',
+ 'config-install-mainpage' => '正在創建顯示默èªå…§å®¹çš„首é ',
+ 'config-install-extension-tables' => '正在為已啟用擴展創建數據表',
+ 'config-install-mainpage-failed' => '無法æ’入首é :$1',
+ 'config-install-done' => "'''æ­å–œï¼'''
+您已經æˆåŠŸåœ°å®‰è£äº†MediaWiki。
+
+安è£ç¨‹åºå·²ç¶“生æˆäº†<code>LocalSettings.php</code>文件,其中包å«äº†æ‚¨æ‰€æœ‰çš„é…置。
+
+您需è¦ä¸‹è¼‰è©²æ–‡ä»¶ï¼Œä¸¦å°‡å…¶æ”¾åœ¨æ‚¨wiki的根目錄(index.phpçš„åŒç´šç›®éŒ„)中。ç¨å¾Œä¸‹è¼‰å°‡è‡ªå‹•é–‹å§‹ã€‚
+
+如果ç€è¦½å™¨æ²’有æ示您下載,或者您å–消了下載,您å¯ä»¥é»žæ“Šä¸‹é¢çš„éˆæŽ¥é‡æ–°é–‹å§‹ä¸‹è¼‰ï¼š
+
+$3
+
+'''注æ„''':如果您ç¾åœ¨ä¸å®Œæˆæœ¬æ­¥é©Ÿï¼Œè€Œæ˜¯æ²’有下載便退出了安è£éŽç¨‹ï¼Œæ­¤å¾Œæ‚¨å°‡ç„¡æ³•ç²å¾—自動生æˆçš„é…置文件。
+
+當本步驟完æˆå¾Œï¼Œæ‚¨å¯ä»¥ '''[$2 進入您的wiki]'''。",
+ 'config-download-localsettings' => '下載LocalSettings.php',
'config-help' => '說明',
'mainpagetext' => "'''å·²æˆåŠŸå®‰è£ MediaWiki。'''",
'mainpagedocfooter' => 'è«‹åƒé–±[//meta.wikimedia.org/wiki/Help:Contents 用戶手冊]以ç²å¾—使用此 wiki 軟體的訊æ¯ï¼
diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php
index dc31dfea..43683e82 100644
--- a/includes/installer/Installer.php
+++ b/includes/installer/Installer.php
@@ -97,6 +97,7 @@ abstract class Installer {
'envCheckPCRE',
'envCheckMemory',
'envCheckCache',
+ 'envCheckModSecurity',
'envCheckDiff3',
'envCheckGraphics',
'envCheckServer',
@@ -106,6 +107,7 @@ abstract class Installer {
'envCheckUploadsDirectory',
'envCheckLibicu',
'envCheckSuhosinMaxValueLength',
+ 'envCheckCtype',
);
/**
@@ -204,7 +206,6 @@ abstract class Installer {
protected $objectCaches = array(
'xcache' => 'xcache_get',
'apc' => 'apc_fetch',
- 'eaccel' => 'eaccelerator_get',
'wincache' => 'wincache_ucache_get'
);
@@ -295,11 +296,13 @@ abstract class Installer {
* The parameters are like parameters to wfMsg().
* The messages will be in wikitext format, which will be converted to an
* output format such as HTML or text before being sent to the user.
+ * @param $msg
*/
public abstract function showMessage( $msg /*, ... */ );
/**
* Same as showMessage(), but for displaying errors
+ * @param $msg
*/
public abstract function showError( $msg /*, ... */ );
@@ -601,7 +604,6 @@ abstract class Installer {
'ss_good_articles' => 0,
'ss_total_pages' => 0,
'ss_users' => 0,
- 'ss_admins' => 0,
'ss_images' => 0 ),
__METHOD__, 'IGNORE' );
return Status::newGood();
@@ -620,6 +622,7 @@ abstract class Installer {
/**
* Environment check for DB types.
+ * @return bool
*/
protected function envCheckDB() {
global $wgLang;
@@ -630,19 +633,32 @@ abstract class Installer {
$allNames[] = wfMsg( "config-type-$name" );
}
- if ( !$this->getVar( '_CompiledDBs' ) ) {
- $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
- // @todo FIXME: This only works for the web installer!
- return false;
+ // cache initially available databases to make sure that everything will be displayed correctly
+ // after a refresh on env checks page
+ $databases = $this->getVar( '_CompiledDBs-preFilter' );
+ if ( !$databases ) {
+ $databases = $this->getVar( '_CompiledDBs' );
+ $this->setVar( '_CompiledDBs-preFilter', $databases );
}
- // Check for FTS3 full-text search module
- $sqlite = $this->getDBInstaller( 'sqlite' );
- if ( $sqlite->isCompiled() ) {
- if( DatabaseSqlite::getFulltextSearchModule() != 'FTS3' ) {
- $this->showMessage( 'config-no-fts3' );
+ $databases = array_flip ( $databases );
+ foreach ( array_keys( $databases ) as $db ) {
+ $installer = $this->getDBInstaller( $db );
+ $status = $installer->checkPrerequisites();
+ if ( !$status->isGood() ) {
+ $this->showStatusMessage( $status );
+ }
+ if ( !$status->isOK() ) {
+ unset( $databases[$db] );
}
}
+ $databases = array_flip( $databases );
+ if ( !$databases ) {
+ $this->showError( 'config-no-db', $wgLang->commaList( $allNames ) );
+ // @todo FIXME: This only works for the web installer!
+ return false;
+ }
+ $this->setVar( '_CompiledDBs', $databases );
}
/**
@@ -803,6 +819,15 @@ abstract class Installer {
}
/**
+ * Scare user to death if they have mod_security
+ */
+ protected function envCheckModSecurity() {
+ if ( self::apacheModulePresent( 'mod_security' ) ) {
+ $this->showMessage( 'config-mod-security' );
+ }
+ }
+
+ /**
* Search for GNU diff3.
*/
protected function envCheckDiff3() {
@@ -842,25 +867,27 @@ abstract class Installer {
/**
* Environment check for the server hostname.
*/
- protected function envCheckServer( $srv = null ) {
- if ( $srv ) {
- // wgServer was pre-defined, perhaps by the cli installer
- $server = $srv;
- } else {
- $server = WebRequest::detectServer();
- }
+ protected function envCheckServer() {
+ $server = $this->envGetDefaultServer();
$this->showMessage( 'config-using-server', $server );
$this->setVar( 'wgServer', $server );
}
/**
+ * Helper function to be called from envCheckServer()
+ * @return String
+ */
+ protected abstract function envGetDefaultServer();
+
+ /**
* Environment check for setting $IP and $wgScriptPath.
+ * @return bool
*/
protected function envCheckPath() {
global $IP;
$IP = dirname( dirname( dirname( __FILE__ ) ) );
-
$this->setVar( 'IP', $IP );
+
$this->showMessage( 'config-using-uri', $this->getVar( 'wgServer' ), $this->getVar( 'wgScriptPath' ) );
return true;
}
@@ -880,6 +907,7 @@ abstract class Installer {
/**
* TODO: document
+ * @return bool
*/
protected function envCheckShellLocale() {
$os = php_uname( 's' );
@@ -1036,7 +1064,7 @@ abstract class Installer {
*/
if( $utf8 ) {
$useNormalizer = 'utf8';
- $utf8 = utf8_normalize( $not_normal_c, UNORM_NFC );
+ $utf8 = utf8_normalize( $not_normal_c, UtfNormal::UNORM_NFC );
if ( $utf8 !== $normal_c ) $needsUpdate = true;
}
if( $intl ) {
@@ -1056,6 +1084,13 @@ abstract class Installer {
}
}
+ protected function envCheckCtype() {
+ if ( !function_exists( 'ctype_digit' ) ) {
+ $this->showError( 'config-ctype' );
+ return false;
+ }
+ }
+
/**
* Get an array of likely places we can find executables. Check a bunch
* of known Unix-like defaults, as well as the PATH environment variable
@@ -1116,6 +1151,9 @@ abstract class Installer {
/**
* Same as locateExecutable(), but checks in getPossibleBinPaths() by default
* @see locateExecutable()
+ * @param $names
+ * @param $versionInfo bool
+ * @return bool|string
*/
public static function locateExecutableInDefaultPaths( $names, $versionInfo = false ) {
foreach( self::getPossibleBinPaths() as $path ) {
@@ -1174,17 +1212,36 @@ abstract class Installer {
}
/**
+ * Checks for presence of an Apache module. Works only if PHP is running as an Apache module, too.
+ *
+ * @param $moduleName String: Name of module to check.
+ * @return bool
+ */
+ public static function apacheModulePresent( $moduleName ) {
+ if ( function_exists( 'apache_get_modules' ) && in_array( $moduleName, apache_get_modules() ) ) {
+ return true;
+ }
+ // try it the hard way
+ ob_start();
+ phpinfo( INFO_MODULES );
+ $info = ob_get_clean();
+ return strpos( $info, $moduleName ) !== false;
+ }
+
+ /**
* ParserOptions are constructed before we determined the language, so fix it
*
* @param $lang Language
*/
public function setParserLanguage( $lang ) {
$this->parserOptions->setTargetLanguage( $lang );
- $this->parserOptions->setUserLang( $lang->getCode() );
+ $this->parserOptions->setUserLang( $lang );
}
/**
* Overridden by WebInstaller to provide lastPage parameters.
+ * @param $page string
+ * @return string
*/
protected function getDocUrl( $page ) {
return "{$_SERVER['PHP_SELF']}?page=" . urlencode( $page );
@@ -1213,6 +1270,7 @@ abstract class Installer {
$exts[] = $file;
}
}
+ natcasesort( $exts );
return $exts;
}
@@ -1241,7 +1299,7 @@ abstract class Installer {
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'] ) ?
@@ -1432,6 +1490,9 @@ abstract class Installer {
return $status;
}
+ /**
+ * @param $s Status
+ */
private function subscribeToMediaWikiAnnounce( Status $s ) {
$params = array(
'email' => $this->getVar( '_AdminEmail' ),
@@ -1461,18 +1522,19 @@ abstract class Installer {
/**
* Insert Main Page with default content.
*
+ * @param $installer DatabaseInstaller
* @return Status
*/
protected function createMainpage( DatabaseInstaller $installer ) {
$status = Status::newGood();
try {
- $article = new Article( Title::newMainPage() );
- $article->doEdit( wfMsgForContent( 'mainpagetext' ) . "\n\n" .
- wfMsgForContent( 'mainpagedocfooter' ),
- '',
- EDIT_NEW,
- false,
- User::newFromName( 'MediaWiki default' ) );
+ $page = WikiPage::factory( Title::newMainPage() );
+ $page->doEdit( wfMsgForContent( 'mainpagetext' ) . "\n\n" .
+ wfMsgForContent( 'mainpagedocfooter' ),
+ '',
+ EDIT_NEW,
+ false,
+ User::newFromName( 'MediaWiki default' ) );
} catch (MWException $e) {
//using raw, because $wgShowExceptionDetails can not be set yet
$status->fatal( 'config-install-mainpage-failed', $e->getMessage() );
diff --git a/includes/installer/LocalSettingsGenerator.php b/includes/installer/LocalSettingsGenerator.php
index a3ab21ea..89154e58 100644
--- a/includes/installer/LocalSettingsGenerator.php
+++ b/includes/installer/LocalSettingsGenerator.php
@@ -44,7 +44,7 @@ class LocalSettingsGenerator {
'wgLanguageCode', 'wgEnableEmail', 'wgEnableUserEmail', 'wgDiff3',
'wgEnotifUserTalk', 'wgEnotifWatchlist', 'wgEmailAuthentication',
'wgDBtype', 'wgSecretKey', 'wgRightsUrl', 'wgSitename', 'wgRightsIcon',
- 'wgRightsText', 'wgRightsCode', 'wgMainCacheType', 'wgEnableUploads',
+ 'wgRightsText', 'wgMainCacheType', 'wgEnableUploads',
'wgMainCacheType', '_MemCachedServers', 'wgDBserver', 'wgDBuser',
'wgDBpassword', 'wgUseInstantCommons', 'wgUpgradeKey', 'wgDefaultSkin',
'wgMetaNamespace', 'wgResourceLoaderMaxQueryLength'
@@ -187,7 +187,7 @@ class LocalSettingsGenerator {
$locale = '';
}
- $rightsUrl = $this->values['wgRightsUrl'] ? '' : '#';
+ //$rightsUrl = $this->values['wgRightsUrl'] ? '' : '#'; // TODO: Fixme, I'm unused!
$hashedUploads = $this->safeMode ? '' : '#';
$metaNamespace = '';
if( $this->values['wgMetaNamespace'] !== $this->values['wgSitename'] ) {
@@ -329,7 +329,6 @@ if ( !defined( 'MEDIAWIKI' ) ) {
\$wgRightsUrl = \"{$this->values['wgRightsUrl']}\";
\$wgRightsText = \"{$this->values['wgRightsText']}\";
\$wgRightsIcon = \"{$this->values['wgRightsIcon']}\";
-# \$wgRightsCode = \"{$this->values['wgRightsCode']}\"; # Not yet used
# Path to the GNU diff3 utility. Used for conflict resolution.
\$wgDiff3 = \"{$this->values['wgDiff3']}\";
diff --git a/includes/installer/MysqlInstaller.php b/includes/installer/MysqlInstaller.php
index 0c197e6b..7585fe7a 100644
--- a/includes/installer/MysqlInstaller.php
+++ b/includes/installer/MysqlInstaller.php
@@ -32,7 +32,7 @@ class MysqlInstaller extends DatabaseInstaller {
public $supportedEngines = array( 'InnoDB', 'MyISAM' );
- public $minimumVersion = '4.0.14';
+ public $minimumVersion = '5.0.2';
public $webUserPrivs = array(
'DELETE',
@@ -75,8 +75,8 @@ class MysqlInstaller extends DatabaseInstaller {
$this->getTextBox( 'wgDBserver', 'config-db-host', array(), $this->parent->getHelpBox( 'config-db-host-help' ) ) .
Html::openElement( 'fieldset' ) .
Html::element( 'legend', array(), wfMsg( 'config-db-wiki-settings' ) ) .
- $this->getTextBox( 'wgDBname', 'config-db-name', array(), $this->parent->getHelpBox( 'config-db-name-help' ) ) .
- $this->getTextBox( 'wgDBprefix', 'config-db-prefix', array(), $this->parent->getHelpBox( 'config-db-prefix-help' ) ) .
+ $this->getTextBox( 'wgDBname', 'config-db-name', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-name-help' ) ) .
+ $this->getTextBox( 'wgDBprefix', 'config-db-prefix', array( 'dir' => 'ltr' ), $this->parent->getHelpBox( 'config-db-prefix-help' ) ) .
Html::closeElement( 'fieldset' ) .
$this->getInstallUserBox();
}
@@ -113,6 +113,9 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
// Check version
@@ -154,11 +157,14 @@ class MysqlInstaller extends DatabaseInstaller {
$this->parent->showStatusError( $status );
return;
}
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
$conn->selectDB( $this->getVar( 'wgDBname' ) );
# Determine existing default character set
- if ( $conn->tableExists( "revision" ) ) {
+ if ( $conn->tableExists( "revision", __METHOD__ ) ) {
$revision = $conn->buildLike( $this->getVar( 'wgDBprefix' ) . 'revision' );
$res = $conn->query( "SHOW TABLE STATUS $revision", __METHOD__ );
$row = $conn->fetchObject( $res );
@@ -207,18 +213,12 @@ class MysqlInstaller extends DatabaseInstaller {
* @return array
*/
public function getEngines() {
- $engines = array( 'InnoDB', 'MyISAM' );
$status = $this->getConnection();
- if ( !$status->isOK() ) {
- return $engines;
- }
- $conn = $status->value;
- $version = $conn->getServerVersion();
- if ( version_compare( $version, "4.1.2", "<" ) ) {
- // No SHOW ENGINES in this version
- return $engines;
- }
+ /**
+ * @var $conn DatabaseBase
+ */
+ $conn = $status->value;
$engines = array();
$res = $conn->query( 'SHOW ENGINES', __METHOD__ );
@@ -237,16 +237,7 @@ class MysqlInstaller extends DatabaseInstaller {
* @return array
*/
public function getCharsets() {
- $status = $this->getConnection();
- $mysql5 = array( 'binary', 'utf8' );
- $mysql4 = array( 'mysql4' );
- if ( !$status->isOK() ) {
- return $mysql5;
- }
- if ( version_compare( $status->value->getServerVersion(), '4.1.0', '>=' ) ) {
- return $mysql5;
- }
- return $mysql4;
+ return array( 'binary', 'utf8' );
}
/**
@@ -259,13 +250,11 @@ class MysqlInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return false;
}
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
- // Check version, need INFORMATION_SCHEMA and CREATE USER
- if ( version_compare( $conn->getServerVersion(), '5.0.2', '<' ) ) {
- return false;
- }
-
// Get current account name
$currentName = $conn->selectField( '', 'CURRENT_USER()', '', __METHOD__ );
$parts = explode( '@', $currentName );
@@ -637,7 +626,7 @@ class MysqlInstaller extends DatabaseInstaller {
# MySQL table options to use during installation or update
\$wgDBTableOptions = \"{$tblOpts}\";
-# Experimental charset support for MySQL 4.1/5.0.
+# Experimental charset support for MySQL 5.0.
\$wgDBmysql5 = {$dbmysql5};";
}
}
diff --git a/includes/installer/MysqlUpdater.php b/includes/installer/MysqlUpdater.php
index 761d2338..9e7869ec 100644
--- a/includes/installer/MysqlUpdater.php
+++ b/includes/installer/MysqlUpdater.php
@@ -66,7 +66,6 @@ class MysqlUpdater extends DatabaseUpdater {
array( 'addTable', 'user_newtalk', 'patch-usernewtalk2.sql' ),
array( 'addTable', 'transcache', 'patch-transcache.sql' ),
array( 'addField', 'interwiki', 'iw_trans', 'patch-interwiki-trans.sql' ),
- array( 'addTable', 'trackbacks', 'patch-trackbacks.sql' ),
// 1.6
array( 'doWatchlistNull' ),
@@ -158,7 +157,6 @@ class MysqlUpdater extends DatabaseUpdater {
array( 'doUpdateTranscacheField' ),
array( 'renameEuWikiId' ),
array( 'doUpdateMimeMinorField' ),
- array( 'doPopulateRevLen' ),
// 1.17
array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
@@ -182,6 +180,18 @@ class MysqlUpdater extends DatabaseUpdater {
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'),
+
+ // 1.19
+ array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
+ array( 'doMigrateUserOptions' ),
+ array( 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ),
+ array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
+ array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ),
+ array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ),
+ array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
+ array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
+ array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ),
+ array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ufg_group-length-increase.sql' ),
);
}
@@ -220,12 +230,12 @@ class MysqlUpdater extends DatabaseUpdater {
if ( $info ) {
foreach ( $info as $row ) {
if ( $row->Column_name == $field ) {
- $this->output( "...index $index on table $table includes field $field\n" );
+ $this->output( "...index $index on table $table includes field $field.\n" );
return true;
}
}
}
- $this->output( "...index $index on table $table has no field $field; adding\n" );
+ $this->output( "...index $index on table $table has no field $field; added.\n" );
return false;
}
@@ -235,14 +245,14 @@ class MysqlUpdater extends DatabaseUpdater {
protected function doInterwikiUpdate() {
global $IP;
- if ( $this->db->tableExists( "interwiki" ) ) {
+ if ( $this->db->tableExists( "interwiki", __METHOD__ ) ) {
$this->output( "...already have interwiki table\n" );
return;
}
$this->output( 'Creating interwiki table...' );
$this->applyPatch( 'patch-interwiki.sql' );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
$this->output( 'Adding default interwiki definitions...' );
$this->applyPatch( "$IP/maintenance/interwiki.sql", true );
$this->output( "done.\n" );
@@ -257,7 +267,7 @@ class MysqlUpdater extends DatabaseUpdater {
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" );
+ $this->output( "...indexes seem up to 20031107 standards.\n" );
return;
}
@@ -291,7 +301,7 @@ class MysqlUpdater extends DatabaseUpdater {
$talk = $this->db->selectField( 'watchlist', 'count(*)', 'wl_namespace & 1', __METHOD__ );
$nontalk = $this->db->selectField( 'watchlist', 'count(*)', 'NOT (wl_namespace & 1)', __METHOD__ );
if ( $talk == $nontalk ) {
- $this->output( "...watchlist talk page rows already present\n" );
+ $this->output( "...watchlist talk page rows already present.\n" );
return;
}
@@ -307,7 +317,7 @@ class MysqlUpdater extends DatabaseUpdater {
}
function doSchemaRestructuring() {
- if ( $this->db->tableExists( 'page' ) ) {
+ if ( $this->db->tableExists( 'page', __METHOD__ ) ) {
$this->output( "...page table already exists.\n" );
return;
}
@@ -498,7 +508,7 @@ class MysqlUpdater extends DatabaseUpdater {
}
protected function doPagelinksUpdate() {
- if ( $this->db->tableExists( 'pagelinks' ) ) {
+ if ( $this->db->tableExists( 'pagelinks', __METHOD__ ) ) {
$this->output( "...already have pagelinks table.\n" );
return;
}
@@ -547,18 +557,18 @@ class MysqlUpdater extends DatabaseUpdater {
}
protected function doUserGroupsUpdate() {
- if ( $this->db->tableExists( 'user_groups' ) ) {
+ if ( $this->db->tableExists( 'user_groups', __METHOD__ ) ) {
$info = $this->db->fieldInfo( 'user_groups', 'ug_group' );
if ( $info->type() == 'int' ) {
$oldug = $this->db->tableName( 'user_groups' );
$newug = $this->db->tableName( 'user_groups_bogus' );
$this->output( "user_groups table exists but is in bogus intermediate format. Renaming to $newug... " );
$this->db->query( "ALTER TABLE $oldug RENAME TO $newug", __METHOD__ );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
$this->output( "Re-adding fresh user_groups table... " );
$this->applyPatch( 'patch-user_groups.sql' );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
$this->output( "***\n" );
$this->output( "*** WARNING: You will need to manually fix up user permissions in the user_groups\n" );
@@ -572,13 +582,13 @@ class MysqlUpdater extends DatabaseUpdater {
$this->output( "Adding user_groups table... " );
$this->applyPatch( 'patch-user_groups.sql' );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
- if ( !$this->db->tableExists( 'user_rights' ) ) {
- if ( $this->db->fieldExists( 'user', 'user_rights' ) ) {
+ if ( !$this->db->tableExists( 'user_rights', __METHOD__ ) ) {
+ if ( $this->db->fieldExists( 'user', 'user_rights', __METHOD__ ) ) {
$this->output( "Upgrading from a 1.3 or older database? Breaking out user_rights for conversion..." );
$this->db->applyPatch( 'patch-user_rights.sql' );
- $this->output( "ok\n" );
+ $this->output( "done.\n" );
} else {
$this->output( "*** WARNING: couldn't locate user_rights table or field for upgrade.\n" );
$this->output( "*** You may need to manually configure some sysops by manipulating\n" );
@@ -643,7 +653,7 @@ class MysqlUpdater extends DatabaseUpdater {
}
protected function doTemplatelinksUpdate() {
- if ( $this->db->tableExists( 'templatelinks' ) ) {
+ if ( $this->db->tableExists( 'templatelinks', __METHOD__ ) ) {
$this->output( "...templatelinks table already exists\n" );
return;
}
@@ -701,7 +711,7 @@ class MysqlUpdater extends DatabaseUpdater {
* -- Andrew Garrett, January 2007.
*/
protected function doRestrictionsUpdate() {
- if ( $this->db->tableExists( 'page_restrictions' ) ) {
+ if ( $this->db->tableExists( 'page_restrictions', __METHOD__ ) ) {
$this->output( "...page_restrictions table already exists.\n" );
return;
}
@@ -741,19 +751,21 @@ class MysqlUpdater extends DatabaseUpdater {
}
protected function doPopulateParentId() {
- if ( $this->updateRowExists( 'populate rev_parent_id' ) ) {
- $this->output( "...rev_parent_id column already populated.\n" );
- return;
- }
+ if ( !$this->updateRowExists( 'populate rev_parent_id' ) ) {
+ $this->output(
+ "Populating rev_parent_id fields, printing progress markers. For large\n" .
+ "databases, you may want to hit Ctrl-C and do this manually with\n" .
+ "maintenance/populateParentId.php.\n" );
- $task = $this->maintenance->runChild( 'PopulateParentId' );
- $task->execute();
+ $task = $this->maintenance->runChild( 'PopulateParentId' );
+ $task->execute();
+ }
}
protected function doMaybeProfilingMemoryUpdate() {
- if ( !$this->db->tableExists( 'profiling' ) ) {
+ if ( !$this->db->tableExists( 'profiling', __METHOD__ ) ) {
// Simply ignore
- } elseif ( $this->db->fieldExists( 'profiling', 'pf_memory' ) ) {
+ } elseif ( $this->db->fieldExists( 'profiling', 'pf_memory', __METHOD__ ) ) {
$this->output( "...profiling table has pf_memory field.\n" );
} else {
$this->output( "Adding pf_memory field to table profiling..." );
@@ -784,7 +796,7 @@ class MysqlUpdater extends DatabaseUpdater {
}
protected function renameEuWikiId() {
- if ( $this->db->fieldExists( 'external_user', 'eu_local_id' ) ) {
+ if ( $this->db->fieldExists( 'external_user', 'eu_local_id', __METHOD__ ) ) {
$this->output( "...eu_wiki_id already renamed to eu_local_id.\n" );
return;
}
@@ -805,16 +817,6 @@ class MysqlUpdater extends DatabaseUpdater {
$this->output( "done.\n" );
}
- protected function doPopulateRevLen() {
- if ( $this->updateRowExists( 'populate rev_len' ) ) {
- $this->output( "...rev_len column already populated.\n" );
- return;
- }
-
- $task = $this->maintenance->runChild( 'PopulateRevisionLength' );
- $task->execute();
- }
-
protected function doClFieldsUpdate() {
if ( $this->updateRowExists( 'cl_fields_update' ) ) {
$this->output( "...categorylinks up-to-date.\n" );
diff --git a/includes/installer/OracleInstaller.php b/includes/installer/OracleInstaller.php
index a8015832..51e6d4a2 100644
--- a/includes/installer/OracleInstaller.php
+++ b/includes/installer/OracleInstaller.php
@@ -117,6 +117,10 @@ class OracleInstaller extends DatabaseInstaller {
return $statusIS3;
}
}
+
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
// Check version
diff --git a/includes/installer/OracleUpdater.php b/includes/installer/OracleUpdater.php
index 964660c5..c4c52ee2 100644
--- a/includes/installer/OracleUpdater.php
+++ b/includes/installer/OracleUpdater.php
@@ -31,16 +31,30 @@ class OracleUpdater extends DatabaseUpdater {
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( 'doRecentchangesFK2Cascade' ),
+ //1.19
+ array( 'addIndex', 'logging', 'i05', 'patch-logging_type_action_index.sql'),
+ array( 'addTable', 'globaltemplatelinks', 'patch-globaltemplatelinks.sql' ),
+ array( 'addTable', 'globalnamespaces', 'patch-globalnamespaces.sql' ),
+ array( 'addTable', 'globalinterwiki', 'patch-globalinterwiki.sql' ),
+ array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1_field.sql' ),
+ array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1_field.sql' ),
+ array( 'doRemoveNotNullEmptyDefaults2' ),
+ array( 'addIndex', 'page', 'i03', 'patch-page_redirect_namespace_len.sql' ),
+ array( 'modifyField', 'user', 'ug_group', 'patch-ug_group-length-increase.sql' ),
+ array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-us_chunk_inx_field.sql' ),
+ array( 'addField', 'job', 'job_timestamp', 'patch-job_timestamp_field.sql' ),
+ array( 'addIndex', 'job', 'i02', 'patch-job_timestamp_index.sql' ),
+
// KEEP THIS AT THE BOTTOM!!
array( 'doRebuildDuplicateFunction' ),
-
+
);
}
@@ -136,6 +150,16 @@ class OracleUpdater extends DatabaseUpdater {
$this->applyPatch( 'patch_remove_not_null_empty_defs.sql', false );
$this->output( "ok\n" );
}
+ protected function doRemoveNotNullEmptyDefaults2() {
+ $this->output( "Removing not null empty constraints ... " );
+ $meta = $this->db->fieldInfo( 'ipblocks' , 'ipb_by_text' );
+ if ( $meta->isNullable() ) {
+ $this->output( "constraints seem to be removed\n" );
+ return;
+ }
+ $this->applyPatch( 'patch_remove_not_null_empty_defs2.sql', false );
+ $this->output( "ok\n" );
+ }
/**
* Removed forcing of invalid state on recentchanges_fk2.
diff --git a/includes/installer/PhpBugTests.php b/includes/installer/PhpBugTests.php
index bb1f7d11..773debe0 100644
--- a/includes/installer/PhpBugTests.php
+++ b/includes/installer/PhpBugTests.php
@@ -18,8 +18,7 @@
* http://www.gnu.org/copyleft/gpl.html
*
* @file
- * @defgroup PHPBugTests
- * @ingroup PHPBugTests
+ * @defgroup PHPBugTests PHP known bugs tests
*/
/**
diff --git a/includes/installer/PostgresInstaller.php b/includes/installer/PostgresInstaller.php
index 7c4a6a80..fea012e2 100644
--- a/includes/installer/PostgresInstaller.php
+++ b/includes/installer/PostgresInstaller.php
@@ -80,6 +80,9 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
// Check version
@@ -139,6 +142,9 @@ class PostgresInstaller extends DatabaseInstaller {
$status = $this->openPgConnection( $type );
if ( $status->isOK() ) {
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
$conn->clearFlag( DBO_TRX );
$conn->commit();
@@ -186,6 +192,9 @@ class PostgresInstaller extends DatabaseInstaller {
case 'create-tables':
$status = $this->openPgConnection( 'create-schema' );
if ( $status->isOK() ) {
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
$safeRole = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
$conn->query( "SET ROLE $safeRole" );
@@ -204,6 +213,7 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !in_array( $this->getVar( 'wgDBname' ), $dbs ) ) {
array_unshift( $dbs, $this->getVar( 'wgDBname' ) );
}
+ $conn = false;
$status = Status::newGood();
foreach ( $dbs as $db ) {
try {
@@ -233,6 +243,9 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return false;
}
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
$superuser = $this->getVar( '_InstallUser' );
@@ -383,6 +396,10 @@ class PostgresInstaller extends DatabaseInstaller {
}
public function preInstall() {
+ $createDbAccount = array(
+ 'name' => 'user',
+ 'callback' => array( $this, 'setupUser' ),
+ );
$commitCB = array(
'name' => 'pg-commit',
'callback' => array( $this, 'commitChanges' ),
@@ -395,15 +412,13 @@ class PostgresInstaller extends DatabaseInstaller {
'name' => 'schema',
'callback' => array( $this, 'setupSchema' )
);
+
+ if( $this->getVar( '_CreateDBAccount' ) ) {
+ $this->parent->addInstallStep( $createDbAccount, 'database' );
+ }
$this->parent->addInstallStep( $commitCB, 'interwiki' );
$this->parent->addInstallStep( $plpgCB, 'database' );
$this->parent->addInstallStep( $schemaCB, 'database' );
- if( $this->getVar( '_CreateDBAccount' ) ) {
- $this->parent->addInstallStep( array(
- 'name' => 'user',
- 'callback' => array( $this, 'setupUser' ),
- ) );
- }
}
function setupDatabase() {
@@ -414,10 +429,10 @@ class PostgresInstaller extends DatabaseInstaller {
$conn = $status->value;
$dbName = $this->getVar( 'wgDBname' );
- $schema = $this->getVar( 'wgDBmwschema' );
- $user = $this->getVar( 'wgDBuser' );
- $safeschema = $conn->addIdentifierQuotes( $schema );
- $safeuser = $conn->addIdentifierQuotes( $user );
+ //$schema = $this->getVar( 'wgDBmwschema' );
+ //$user = $this->getVar( 'wgDBuser' );
+ //$safeschema = $conn->addIdentifierQuotes( $schema );
+ //$safeuser = $conn->addIdentifierQuotes( $user );
$exists = $conn->selectField( '"pg_catalog"."pg_database"', '1',
array( 'datname' => $dbName ), __METHOD__ );
@@ -461,7 +476,7 @@ class PostgresInstaller extends DatabaseInstaller {
}
function commitChanges() {
- $this->db->query( 'COMMIT' );
+ $this->db->commit();
return Status::newGood();
}
@@ -476,10 +491,10 @@ class PostgresInstaller extends DatabaseInstaller {
}
$conn = $status->value;
- $schema = $this->getVar( 'wgDBmwschema' );
+ //$schema = $this->getVar( 'wgDBmwschema' );
$safeuser = $conn->addIdentifierQuotes( $this->getVar( 'wgDBuser' ) );
$safepass = $conn->addQuotes( $this->getVar( 'wgDBpassword' ) );
- $safeschema = $conn->addIdentifierQuotes( $schema );
+ //$safeschema = $conn->addIdentifierQuotes( $schema );
// Check if the user already exists
$userExists = $conn->roleExists( $this->getVar( 'wgDBuser' ) );
@@ -530,10 +545,15 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
+
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
if( $conn->tableExists( 'user' ) ) {
$status->warning( 'config-install-tables-exist' );
+ $this->enableLB();
return $status;
}
@@ -565,6 +585,9 @@ class PostgresInstaller extends DatabaseInstaller {
if ( !$status->isOK() ) {
return $status;
}
+ /**
+ * @var $conn DatabaseBase
+ */
$conn = $status->value;
$exists = $conn->selectField( '"pg_catalog"."pg_language"', 1,
diff --git a/includes/installer/PostgresUpdater.php b/includes/installer/PostgresUpdater.php
index ade46ac8..023cb300 100644
--- a/includes/installer/PostgresUpdater.php
+++ b/includes/installer/PostgresUpdater.php
@@ -16,11 +16,21 @@
class PostgresUpdater extends DatabaseUpdater {
/**
+ * @var DatabasePostgres
+ */
+ protected $db;
+
+ /**
* @todo FIXME: Postgres should use sequential updates like Mysql, Sqlite
* and everybody else. It never got refactored like it should've.
*/
protected function getCoreUpdateList() {
return array(
+ # rename tables 1.7.3
+ # r15791 Change reserved word table names "user" and "text"
+ array( 'renameTable', 'user', 'mwuser'),
+ array( 'renameTable', 'text', 'pagecontent'),
+
# new sequences
array( 'addSequence', 'logging_log_id_seq' ),
array( 'addSequence', 'page_restrictions_pr_id_seq' ),
@@ -55,7 +65,8 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
array( 'addTable', 'msg_resource_links','patch-msg_resource_links.sql' ),
array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
- array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
+ array( 'addTable', 'uploadstash', 'patch-uploadstash.sql' ),
+ array( 'addTable', 'user_former_groups','patch-user_former_groups.sql' ),
# Needed before new field
array( 'convertArchive2' ),
@@ -108,6 +119,10 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgField', 'logging', 'log_page', 'INTEGER' ),
array( 'addPgField', 'interwiki', 'iw_api', "TEXT NOT NULL DEFAULT ''"),
array( 'addPgField', 'interwiki', 'iw_wikiid', "TEXT NOT NULL DEFAULT ''"),
+ array( 'addPgField', 'revision', 'rev_sha1', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'archive', 'ar_sha1', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'uploadstash', 'us_chunk_inx', "INTEGER NULL" ),
+ array( 'addPgField', 'job', 'job_timestamp', "TIMESTAMPTZ" ),
# type changes
array( 'changeField', 'archive', 'ar_deleted', 'smallint', '' ),
@@ -175,6 +190,7 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'addPgIndex', 'logging', 'logging_user_type_time', '(log_user, log_type, log_timestamp)' ),
array( 'addPgIndex', 'logging', 'logging_page_id_time', '(log_page,log_timestamp)' ),
array( 'addPgIndex', 'iwlinks', 'iwl_prefix_title_from', '(iwl_prefix, iwl_title, iwl_from)' ),
+ array( 'addPgIndex', 'job', 'job_timestamp_idx', '(job_timestamp)' ),
array( 'checkOiNameConstraint' ),
array( 'checkPageDeletedTrigger' ),
@@ -208,7 +224,6 @@ class PostgresUpdater extends DatabaseUpdater {
array( 'changeFkeyDeferrable', 'revision', 'rev_page', 'page (page_id) ON DELETE CASCADE' ),
array( 'changeFkeyDeferrable', 'revision', 'rev_user', 'mwuser(user_id) ON DELETE RESTRICT' ),
array( 'changeFkeyDeferrable', 'templatelinks', 'tl_from', 'page(page_id) ON DELETE CASCADE' ),
- array( 'changeFkeyDeferrable', 'trackbacks', 'tb_page', 'page(page_id) ON DELETE CASCADE' ),
array( 'changeFkeyDeferrable', 'user_groups', 'ug_user', 'mwuser(user_id) ON DELETE CASCADE' ),
array( 'changeFkeyDeferrable', 'user_newtalk', 'user_id', 'mwuser(user_id) ON DELETE CASCADE' ),
array( 'changeFkeyDeferrable', 'user_properties', 'up_user', 'mwuser(user_id) ON DELETE CASCADE' ),
@@ -387,6 +402,10 @@ END;
}
protected function renameSequence( $old, $new ) {
+ if ( $this->db->sequenceExists( $new ) ) {
+ $this->output( "...sequence $new already exists.\n" );
+ return;
+ }
if ( $this->db->sequenceExists( $old ) ) {
$this->output( "Renaming sequence $old to $new\n" );
$this->db->query( "ALTER SEQUENCE $old RENAME TO $new" );
@@ -396,7 +415,8 @@ END;
protected function renameTable( $old, $new ) {
if ( $this->db->tableExists( $old ) ) {
$this->output( "Renaming table $old to $new\n" );
- $old = $this->db->addQuotes( $old );
+ $old = $this->db->realTableName( $old, "quoted" );
+ $new = $this->db->realTableName( $new, "quoted" );
$this->db->query( "ALTER TABLE $old RENAME TO $new" );
}
}
@@ -404,7 +424,7 @@ END;
protected function addPgField( $table, $field, $type ) {
$fi = $this->db->fieldInfo( $table, $field );
if ( !is_null( $fi ) ) {
- $this->output( "... column '$table.$field' already exists\n" );
+ $this->output( "...column '$table.$field' already exists\n" );
return;
} else {
$this->output( "Adding column '$table.$field'\n" );
@@ -415,12 +435,12 @@ END;
protected function changeField( $table, $field, $newtype, $default ) {
$fi = $this->db->fieldInfo( $table, $field );
if ( is_null( $fi ) ) {
- $this->output( "... error: expected column $table.$field to exist\n" );
+ $this->output( "...ERROR: expected column $table.$field to exist\n" );
exit( 1 );
}
if ( $fi->type() === $newtype )
- $this->output( "... column '$table.$field' is already of type '$newtype'\n" );
+ $this->output( "...column '$table.$field' is already of type '$newtype'\n" );
else {
$this->output( "Changing column type of '$table.$field' from '{$fi->type()}' to '$newtype'\n" );
$sql = "ALTER TABLE $table ALTER $field TYPE $newtype";
@@ -433,15 +453,16 @@ END;
}
$sql .= " USING $default";
}
- $sql .= ";\nCOMMIT;\n";
+ $this->db->begin( __METHOD__ );
$this->db->query( $sql );
+ $this->db->commit( __METHOD__ );
}
}
protected function changeNullableField( $table, $field, $null ) {
$fi = $this->db->fieldInfo( $table, $field );
if ( is_null( $fi ) ) {
- $this->output( "... error: expected column $table.$field to exist\n" );
+ $this->output( "...ERROR: expected column $table.$field to exist\n" );
exit( 1 );
}
if ( $fi->isNullable() ) {
@@ -450,7 +471,7 @@ END;
$this->output( "Changing '$table.$field' to not allow NULLs\n" );
$this->db->query( "ALTER TABLE $table ALTER $field SET NOT NULL" );
} else {
- $this->output( "... column '$table.$field' is already set as NULL\n" );
+ $this->output( "...column '$table.$field' is already set as NULL\n" );
}
} else {
# # It's NOT NULL - does it need to be NULL?
@@ -459,14 +480,14 @@ END;
$this->db->query( "ALTER TABLE $table ALTER $field DROP NOT NULL" );
}
else {
- $this->output( "... column '$table.$field' is already set as NOT NULL\n" );
+ $this->output( "...column '$table.$field' is already set as NOT NULL\n" );
}
}
}
public function addPgIndex( $table, $index, $type ) {
if ( $this->db->indexExists( $table, $index ) ) {
- $this->output( "... index '$index' on table '$table' already exists\n" );
+ $this->output( "...index '$index' on table '$table' already exists\n" );
} else {
$this->output( "Creating index '$index' on table '$table' $type\n" );
$this->db->query( "CREATE INDEX $index ON $table $type" );
@@ -475,7 +496,7 @@ END;
public function addPgExtIndex( $table, $index, $type ) {
if ( $this->db->indexExists( $table, $index ) ) {
- $this->output( "... index '$index' on table '$table' already exists\n" );
+ $this->output( "...index '$index' on table '$table' already exists\n" );
} else {
$this->output( "Creating index '$index' on table '$table'\n" );
if ( preg_match( '/^\(/', $type ) ) {
@@ -516,7 +537,7 @@ END;
}
$this->applyPatch( 'patch-remove-archive2.sql' );
} else {
- $this->output( "... obsolete table 'archive2' does not exist\n" );
+ $this->output( "...obsolete table 'archive2' does not exist\n" );
}
}
@@ -527,13 +548,13 @@ END;
$this->db->query( "ALTER TABLE oldimage ALTER oi_deleted TYPE SMALLINT USING (oi_deleted::smallint)" );
$this->db->query( "ALTER TABLE oldimage ALTER oi_deleted SET DEFAULT 0" );
} else {
- $this->output( "... column 'oldimage.oi_deleted' is already of type 'smallint'\n" );
+ $this->output( "...column 'oldimage.oi_deleted' is already of type 'smallint'\n" );
}
}
protected function checkOiNameConstraint() {
if ( $this->db->hasConstraint( "oldimage_oi_name_fkey_cascaded" ) ) {
- $this->output( "... table 'oldimage' has correct cascading delete/update foreign key to image\n" );
+ $this->output( "...table 'oldimage' has correct cascading delete/update foreign key to image\n" );
} else {
if ( $this->db->hasConstraint( "oldimage_oi_name_fkey" ) ) {
$this->db->query( "ALTER TABLE oldimage DROP CONSTRAINT oldimage_oi_name_fkey" );
@@ -552,7 +573,7 @@ END;
$this->output( "Adding function and trigger 'page_deleted' to table 'page'\n" );
$this->applyPatch( 'patch-page_deleted.sql' );
} else {
- $this->output( "... table 'page' has 'page_deleted' trigger\n" );
+ $this->output( "...table 'page' has 'page_deleted' trigger\n" );
}
}
@@ -562,7 +583,7 @@ END;
$this->output( "Removing NOT NULL constraint from 'recentchanges.rc_cur_id'\n" );
$this->applyPatch( 'patch-rc_cur_id-not-null.sql' );
} else {
- $this->output( "... column 'recentchanges.rc_cur_id' has a NOT NULL constraint\n" );
+ $this->output( "...column 'recentchanges.rc_cur_id' has a NOT NULL constraint\n" );
}
}
@@ -573,20 +594,20 @@ END;
$this->db->query( 'DROP INDEX pagelink_unique' );
$pu = null;
} else {
- $this->output( "... obsolete version of index 'pagelink_unique index' does not exist\n" );
+ $this->output( "...obsolete version of index 'pagelink_unique index' does not exist\n" );
}
if ( is_null( $pu ) ) {
$this->output( "Creating index 'pagelink_unique index'\n" );
$this->db->query( 'CREATE UNIQUE INDEX pagelink_unique ON pagelinks (pl_from,pl_namespace,pl_title)' );
} else {
- $this->output( "... index 'pagelink_unique_index' already exists\n" );
+ $this->output( "...index 'pagelink_unique_index' already exists\n" );
}
}
protected function checkRevUserFkey() {
if ( $this->fkeyDeltype( 'revision_rev_user_fkey' ) == 'r' ) {
- $this->output( "... constraint 'revision_rev_user_fkey' is ON DELETE RESTRICT\n" );
+ $this->output( "...constraint 'revision_rev_user_fkey' is ON DELETE RESTRICT\n" );
} else {
$this->output( "Changing constraint 'revision_rev_user_fkey' to ON DELETE RESTRICT\n" );
$this->applyPatch( 'patch-revision_rev_user_fkey.sql' );
@@ -599,7 +620,7 @@ END;
$this->db->query( 'DROP INDEX ipb_address' );
}
if ( $this->db->indexExists( 'ipblocks', 'ipb_address_unique' ) ) {
- $this->output( "... have ipb_address_unique\n" );
+ $this->output( "...have ipb_address_unique\n" );
} else {
$this->output( "Adding ipb_address_unique index\n" );
$this->applyPatch( 'patch-ipb_address_unique.sql' );
@@ -616,12 +637,14 @@ END;
protected function tsearchFixes() {
# Tweak the page_title tsearch2 trigger to filter out slashes
# This is create or replace, so harmless to call if not needed
+ $this->output( "Refreshing ts2_page_title()...\n" );
$this->applyPatch( 'patch-ts2pagetitle.sql' );
# If the server is 8.3 or higher, rewrite the tsearch2 triggers
# in case they have the old 'default' versions
# Gather version numbers in case we need them
if ( $this->db->getServerVersion() >= 8.3 ) {
+ $this->output( "Rewriting tsearch2 triggers...\n" );
$this->applyPatch( 'patch-tsearch2funcs.sql' );
}
}
diff --git a/includes/installer/SqliteInstaller.php b/includes/installer/SqliteInstaller.php
index 144e710d..658a3b16 100644
--- a/includes/installer/SqliteInstaller.php
+++ b/includes/installer/SqliteInstaller.php
@@ -13,6 +13,7 @@
* @since 1.17
*/
class SqliteInstaller extends DatabaseInstaller {
+ const MINIMUM_VERSION = '3.3.7';
/**
* @var DatabaseSqlite
@@ -32,6 +33,24 @@ class SqliteInstaller extends DatabaseInstaller {
return self::checkExtension( 'pdo_sqlite' );
}
+ /**
+ *
+ * @return Status:
+ */
+ public function checkPrerequisites() {
+ $result = Status::newGood();
+ // Bail out if SQLite is too old
+ $db = new DatabaseSqliteStandalone( ':memory:' );
+ if ( version_compare( $db->getServerVersion(), self::MINIMUM_VERSION, '<' ) ) {
+ $result->fatal( 'config-outdated-sqlite', $db->getServerVersion(), self::MINIMUM_VERSION );
+ }
+ // Check for FTS3 full-text search module
+ if( DatabaseSqlite::getFulltextSearchModule() != 'FTS3' ) {
+ $result->warning( 'config-no-fts3' );
+ }
+ return $result;
+ }
+
public function getGlobalDefaults() {
if ( isset( $_SERVER['DOCUMENT_ROOT'] ) ) {
$path = str_replace(
@@ -102,7 +121,7 @@ class SqliteInstaller extends DatabaseInstaller {
# if it's still writable
if ( $create ) {
wfSuppressWarnings();
- $ok = wfMkdirParents( $dir, 0700 );
+ $ok = wfMkdirParents( $dir, 0700, __METHOD__ );
wfRestoreWarnings();
if ( !$ok ) {
return Status::newFatal( 'config-sqlite-mkdir-error', $dir );
diff --git a/includes/installer/SqliteUpdater.php b/includes/installer/SqliteUpdater.php
index 6b819f8e..e1bc2926 100644
--- a/includes/installer/SqliteUpdater.php
+++ b/includes/installer/SqliteUpdater.php
@@ -42,13 +42,13 @@ class SqliteUpdater extends DatabaseUpdater {
array( 'sqliteSetupSearchindex' ),
// 1.17
- array( 'addTable', 'iwlinks', 'patch-iwlinks.sql' ),
+ 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', '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( 'addField', 'categorylinks', 'cl_collation', 'patch-categorylinks-better-collation.sql' ),
array( 'doCollationUpdate' ),
array( 'addTable', 'msg_resource', 'patch-msg_resource.sql' ),
array( 'addTable', 'module_deps', 'patch-module_deps.sql' ),
@@ -59,12 +59,24 @@ class SqliteUpdater extends DatabaseUpdater {
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'),
+
+ // 1.19
+ array( 'addIndex', 'logging', 'type_action', 'patch-logging-type-action-index.sql'),
+ array( 'doMigrateUserOptions' ),
+ array( 'dropField', 'user', 'user_options', 'patch-drop-user_options.sql' ),
+ array( 'addField', 'revision', 'rev_sha1', 'patch-rev_sha1.sql' ),
+ array( 'addField', 'archive', 'ar_sha1', 'patch-ar_sha1.sql' ),
+ array( 'addIndex', 'page', 'page_redirect_namespace_len', 'patch-page_redirect_namespace_len.sql' ),
+ array( 'modifyField', 'user_groups', 'ug_group', 'patch-ug_group-length-increase.sql' ),
+ array( 'addField', 'uploadstash', 'us_chunk_inx', 'patch-uploadstash_chunk.sql' ),
+ array( 'addfield', 'job', 'job_timestamp', 'patch-jobs-add-timestamp.sql' ),
+ array( 'modifyField', 'user_former_groups', 'ufg_group', 'patch-ug_group-length-increase.sql' ),
);
}
protected function sqliteInitialIndexes() {
// initial-indexes.sql fails if the indexes are already present, so we perform a quick check if our database is newer.
- if ( $this->updateRowExists( 'initial_indexes' ) || $this->db->indexExists( 'user', 'user_name' ) ) {
+ if ( $this->updateRowExists( 'initial_indexes' ) || $this->db->indexExists( 'user', 'user_name', __METHOD__ ) ) {
$this->output( "...have initial indexes\n" );
return;
}
diff --git a/includes/installer/WebInstaller.php b/includes/installer/WebInstaller.php
index 40726437..1ff77db7 100644
--- a/includes/installer/WebInstaller.php
+++ b/includes/installer/WebInstaller.php
@@ -355,6 +355,7 @@ class WebInstaller extends Installer {
/**
* Show an error message in a box. Parameters are like wfMsg().
+ * @param $msg
*/
public function showError( $msg /*...*/ ) {
$args = func_get_args();
@@ -366,6 +367,8 @@ class WebInstaller extends Installer {
/**
* Temporary error handler for session start debugging.
+ * @param $errno
+ * @param $errstr string
*/
public function errorHandler( $errno, $errstr ) {
$this->phpErrors[] = $errstr;
@@ -469,7 +472,6 @@ class WebInstaller extends Installer {
$this->setVar( '_UserLang', $wgLanguageCode );
} else {
$wgLanguageCode = $this->getVar( 'wgLanguageCode' );
- $wgLang = Language::factory( $this->getVar( '_UserLang' ) );
$wgContLang = Language::factory( $wgLanguageCode );
}
}
@@ -630,6 +632,7 @@ class WebInstaller extends Installer {
* Get small text indented help for a preceding form field.
* Parameters like wfMsg().
*
+ * @param $msg
* @return string
*/
public function getHelpBox( $msg /*, ... */ ) {
@@ -637,13 +640,12 @@ class WebInstaller extends Installer {
array_shift( $args );
$args = array_map( 'htmlspecialchars', $args );
$text = wfMsgReal( $msg, $args, false, false, false );
- $html = htmlspecialchars( $text );
$html = $this->parse( $text, true );
return "<div class=\"mw-help-field-container\">\n" .
- "<span class=\"mw-help-field-hint\">" . wfMsgHtml( 'config-help' ) . "</span>\n" .
- "<span class=\"mw-help-field-data\">" . $html . "</span>\n" .
- "</div>\n";
+ "<span class=\"mw-help-field-hint\">" . wfMsgHtml( 'config-help' ) . "</span>\n" .
+ "<span class=\"mw-help-field-data\">" . $html . "</span>\n" .
+ "</div>\n";
}
/**
@@ -675,18 +677,20 @@ class WebInstaller extends Installer {
* @param $status Status
*/
public function showStatusMessage( Status $status ) {
- $text = $status->getWikiText();
- $this->output->addWikiText(
- "<div class=\"config-message\">\n" .
- $text .
- "</div>"
- );
+ $errors = array_merge( $status->getErrorsArray(), $status->getWarningsArray() );
+ foreach ( $errors as $error ) {
+ call_user_func_array( array( $this, 'showMessage' ), $error );
+ }
}
/**
* Label a control by wrapping a config-input div around it and putting a
* label before it.
*
+ * @param $msg
+ * @param $forId
+ * @param $contents
+ * @param $helpData string
* @return string
*/
public function label( $msg, $forId, $contents, $helpData = "" ) {
@@ -1013,6 +1017,7 @@ class WebInstaller extends Installer {
/**
* Helper for Installer::docLink()
*
+ * @param $page
* @return string
*/
protected function getDocUrl( $page ) {
@@ -1028,6 +1033,9 @@ class WebInstaller extends Installer {
/**
* Extension tag hook for a documentation link.
*
+ * @param $linkText
+ * @param $attribs
+ * @param $parser
* @return string
*/
public function docLink( $linkText, $attribs, $parser ) {
@@ -1040,6 +1048,9 @@ class WebInstaller extends Installer {
/**
* Helper for "Download LocalSettings" link on WebInstall_Complete
*
+ * @param $text
+ * @param $attribs
+ * @param $parser
* @return String Html for download link
*/
public function downloadLinkHook( $text, $attribs, $parser ) {
@@ -1054,6 +1065,9 @@ class WebInstaller extends Installer {
return Html::rawElement( 'div', array( 'class' => 'config-download-link' ), $anchor );
}
+ /**
+ * @return bool
+ */
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
@@ -1071,9 +1085,10 @@ class WebInstaller extends Installer {
$this->showError( 'config-no-uri' );
return false;
}
-
-
return parent::envCheckPath();
}
+ protected function envGetDefaultServer() {
+ return WebRequest::detectServer();
+ }
}
diff --git a/includes/installer/WebInstallerOutput.php b/includes/installer/WebInstallerOutput.php
index 9eb2c2c6..ac97b37d 100644
--- a/includes/installer/WebInstallerOutput.php
+++ b/includes/installer/WebInstallerOutput.php
@@ -144,11 +144,7 @@ class WebInstallerOutput {
*/
public function getDir() {
global $wgLang;
- if( !is_object( $wgLang ) || !$wgLang->isRtl() ) {
- return 'ltr';
- } else {
- return 'rtl';
- }
+ return is_object( $wgLang ) ? $wgLang->getDir() : 'ltr';
}
/**
@@ -156,11 +152,7 @@ class WebInstallerOutput {
*/
public function getLanguageCode() {
global $wgLang;
- if( !is_object( $wgLang ) ) {
- return 'en';
- } else {
- return $wgLang->getCode();
- }
+ return is_object( $wgLang ) ? $wgLang->getCode() : 'en';
}
/**
@@ -240,7 +232,6 @@ class WebInstallerOutput {
href="http://www.mediawiki.org/"
title="Main Page"></a>
</div>
- <script type="text/javascript"> if (window.isMSIE55) fixalpha(); </script>
<div class="portal"><div class="body">
<?php
echo $this->parent->parse( wfMsgNoTrans( 'config-sidebar' ), true );
diff --git a/includes/installer/WebInstallerPage.php b/includes/installer/WebInstallerPage.php
index dc9c6d27..ff8185a1 100644
--- a/includes/installer/WebInstallerPage.php
+++ b/includes/installer/WebInstallerPage.php
@@ -69,7 +69,8 @@ abstract class WebInstallerPage {
if ( $continue ) {
// Fake submit button for enter keypress (bug 26267)
$s .= Xml::submitButton( wfMsg( "config-$continue" ),
- array( 'name' => "enter-$continue", 'style' => 'visibility:hidden;overflow:hidden;width:1px;margin:0' ) ) . "\n";
+ array( 'name' => "enter-$continue", 'style' =>
+ 'visibility:hidden;overflow:hidden;width:1px;margin:0' ) ) . "\n";
}
if ( $back ) {
@@ -122,7 +123,7 @@ abstract class WebInstallerPage {
/**
* Get the end tag of a fieldset.
*
- * @returns string
+ * @return string
*/
protected function getFieldsetEnd() {
return "</fieldset>\n";
@@ -133,9 +134,11 @@ abstract class WebInstallerPage {
*/
protected function startLiveBox() {
$this->addHTML(
- '<div id="config-spinner" style="display:none;"><img src="../skins/common/images/ajax-loader.gif" /></div>' .
+ '<div id="config-spinner" style="display:none;">' .
+ '<img src="../skins/common/images/ajax-loader.gif" /></div>' .
'<script>jQuery( "#config-spinner" ).show();</script>' .
- '<div id="config-live-log"><textarea name="LiveLog" rows="10" cols="30" readonly="readonly">'
+ '<div id="config-live-log">' .
+ '<textarea name="LiveLog" rows="10" cols="30" readonly="readonly">'
);
$this->parent->output->flush();
}
@@ -155,9 +158,10 @@ class WebInstaller_Language extends WebInstallerPage {
public function execute() {
global $wgLang;
$r = $this->parent->request;
- $userLang = $r->getVal( 'UserLang' );
+ $userLang = $r->getVal( 'uselang' );
$contLang = $r->getVal( 'ContLang' );
+ $languages = Language::getLanguageNames();
$lifetime = intval( ini_get( 'session.gc_maxlifetime' ) );
if ( !$lifetime ) {
$lifetime = 1440; // PHP default
@@ -178,7 +182,6 @@ class WebInstaller_Language extends WebInstallerPage {
}
$this->parent->showError( $msg, $wgLang->formatTimePeriod( $lifetime ) );
} else {
- $languages = Language::getLanguageNames();
if ( isset( $languages[$userLang] ) ) {
$this->setVar( '_UserLang', $userLang );
}
@@ -190,7 +193,8 @@ class WebInstaller_Language extends WebInstallerPage {
} elseif ( $this->parent->showSessionWarning ) {
# The user was knocked back from another page to the start
# This probably indicates a session expiry
- $this->parent->showError( 'config-session-expired', $wgLang->formatTimePeriod( $lifetime ) );
+ $this->parent->showError( 'config-session-expired',
+ $wgLang->formatTimePeriod( $lifetime ) );
}
$this->parent->setSession( 'test', true );
@@ -203,8 +207,10 @@ class WebInstaller_Language extends WebInstallerPage {
}
$this->startForm();
$s = Html::hidden( 'LanguageRequestTime', time() ) .
- $this->getLanguageSelector( 'UserLang', 'config-your-language', $userLang, $this->parent->getHelpBox( 'config-your-language-help' ) ) .
- $this->getLanguageSelector( 'ContLang', 'config-wiki-language', $contLang, $this->parent->getHelpBox( 'config-wiki-language-help' ) );
+ $this->getLanguageSelector( 'uselang', 'config-your-language', $userLang,
+ $this->parent->getHelpBox( 'config-your-language-help' ) ) .
+ $this->getLanguageSelector( 'ContLang', 'config-wiki-language', $contLang,
+ $this->parent->getHelpBox( 'config-wiki-language-help' ) );
$this->addHTML( $s );
$this->endForm( 'continue', false );
}
@@ -212,6 +218,10 @@ class WebInstaller_Language extends WebInstallerPage {
/**
* Get a <select> for selecting languages.
*
+ * @param $name
+ * @param $label
+ * @param $selectedCode
+ * @param $helpHtml string
* @return string
*/
public function getLanguageSelector( $name, $label, $selectedCode, $helpHtml = '' ) {
@@ -219,13 +229,13 @@ class WebInstaller_Language extends WebInstallerPage {
$s = $helpHtml;
- $s .= Html::openElement( 'select', array( 'id' => $name, 'name' => $name, 'tabindex' => $this->parent->nextTabIndex() ) ) . "\n";
+ $s .= Html::openElement( 'select', array( 'id' => $name, 'name' => $name,
+ 'tabindex' => $this->parent->nextTabIndex() ) ) . "\n";
$languages = Language::getLanguageNames();
ksort( $languages );
- $dummies = array_flip( $wgDummyLanguageCodes );
foreach ( $languages as $code => $lang ) {
- if ( isset( $dummies[$code] ) ) continue;
+ if ( isset( $wgDummyLanguageCodes[$code] ) ) continue;
$s .= "\n" . Xml::option( "$code - $lang", $code, $code == $selectedCode );
}
$s .= "\n</select>\n";
@@ -270,7 +280,7 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
$this->startForm();
$this->addHTML( $this->parent->getInfoBox(
wfMsgNoTrans( 'config-upgrade-key-missing',
- "<pre>\$wgUpgradeKey = '" . $this->getVar( 'wgUpgradeKey' ) . "';</pre>" )
+ "<pre dir=\"ltr\">\$wgUpgradeKey = '" . $this->getVar( 'wgUpgradeKey' ) . "';</pre>" )
) );
$this->endForm( 'continue' );
return 'output';
@@ -336,7 +346,8 @@ class WebInstaller_ExistingWiki extends WebInstallerPage {
*/
protected function handleExistingUpgrade( $vars ) {
// Check $wgDBtype
- if ( !isset( $vars['wgDBtype'] ) || !in_array( $vars['wgDBtype'], Installer::getDBTypes() ) ) {
+ if ( !isset( $vars['wgDBtype'] ) ||
+ !in_array( $vars['wgDBtype'], Installer::getDBTypes() ) ) {
return Status::newFatal( 'config-localsettings-connection-error', '' );
}
@@ -447,12 +458,13 @@ class WebInstaller_DBConnect extends WebInstallerPage {
"</li>\n";
$settings .=
- Html::openElement( 'div', array( 'id' => 'DB_wrapper_' . $type, 'class' => 'dbWrapper' ) ) .
+ Html::openElement( 'div', array( 'id' => 'DB_wrapper_' . $type,
+ 'class' => 'dbWrapper' ) ) .
Html::element( 'h3', array(), wfMsg( 'config-header-' . $type ) ) .
$installer->getConnectForm() .
"</div>\n";
}
- $types .= "</ul><br clear=\"left\"/>\n";
+ $types .= "</ul><br style=\"clear: left\"/>\n";
$this->addHTML(
$this->parent->label( 'config-db-type', false, $types ) .
@@ -620,7 +632,8 @@ class WebInstaller_Name extends WebInstallerPage {
'label' => 'config-project-namespace',
'itemLabelPrefix' => 'config-ns-',
'values' => array( 'site-name', 'generic', 'other' ),
- 'commonAttribs' => array( 'class' => 'enableForOther', 'rel' => 'config_wgMetaNamespace' ),
+ 'commonAttribs' => array( 'class' => 'enableForOther',
+ 'rel' => 'config_wgMetaNamespace' ),
'help' => $this->parent->getHelpBox( 'config-project-namespace-help' )
) ) .
$this->parent->getTextBox( array(
@@ -894,12 +907,14 @@ class WebInstaller_Options extends WebInstallerPage {
$this->parent->getTextBox( array(
'var' => 'wgDeletedDirectory',
'label' => 'config-upload-deleted',
+ 'attribs' => array( 'dir' => 'ltr' ),
'help' => $this->parent->getHelpBox( 'config-upload-deleted-help' )
) ) .
'</div>' .
$this->parent->getTextBox( array(
'var' => 'wgLogo',
'label' => 'config-logo',
+ 'attribs' => array( 'dir' => 'ltr' ),
'help' => $this->parent->getHelpBox( 'config-logo-help' )
) )
);
@@ -1174,10 +1189,12 @@ class WebInstaller_Complete extends WebInstallerPage {
// Pop up a dialog box, to make it difficult for the user to forget
// to download the file
$lsUrl = $this->getVar( 'wgServer' ) . $this->parent->getURL( array( 'localsettings' => 1 ) );
- if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false ) {
+ if ( isset( $_SERVER['HTTP_USER_AGENT'] ) &&
+ strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false ) {
// JS appears the only method that works consistently with IE7+
- $this->addHtml( "\n<script type=\"" . $GLOBALS['wgJsMimeType'] . '">jQuery( document ).ready( function() { document.location='
- . Xml::encodeJsVar( $lsUrl) . "; } );</script>\n" );
+ $this->addHtml( "\n<script type=\"" . $GLOBALS['wgJsMimeType'] .
+ '">jQuery( document ).ready( function() { document.location=' .
+ Xml::encodeJsVar( $lsUrl) . "; } );</script>\n" );
} else {
$this->parent->request->response()->header( "Refresh: 0;url=$lsUrl" );
}
diff --git a/includes/interwiki/Interwiki.php b/includes/interwiki/Interwiki.php
index 71bd9725..3aaa1c52 100644
--- a/includes/interwiki/Interwiki.php
+++ b/includes/interwiki/Interwiki.php
@@ -136,7 +136,7 @@ class Interwiki {
/**
* Load the interwiki, trying first memcached then the DB
*
- * @param $prefix The interwiki prefix
+ * @param $prefix string The interwiki prefix
* @return Boolean: the prefix is valid
*/
protected static function load( $prefix ) {
@@ -150,6 +150,9 @@ class Interwiki {
if ( !$iwData ) {
$key = wfMemcKey( 'interwiki', $prefix );
$iwData = $wgMemc->get( $key );
+ if ( $iwData === '!NONEXISTENT' ) {
+ return false; // negative cache hit
+ }
}
if( $iwData && is_array( $iwData ) ) { // is_array is hack for old keys
@@ -173,6 +176,8 @@ class Interwiki {
);
$wgMemc->add( $key, $mc, $wgInterwikiExpiry );
return $iw;
+ } else {
+ $wgMemc->add( $key, '!NONEXISTENT', $wgInterwikiExpiry ); // negative cache hit
}
return false;
@@ -181,8 +186,8 @@ class Interwiki {
/**
* Fill in member variables from an array (e.g. memcached result, Database::fetchRow, etc)
*
- * @param $mc Associative array: row from the interwiki table
- * @return Boolean: whether everything was there
+ * @param $mc array Associative array: row from the interwiki table
+ * @return Boolean|Interwiki whether everything was there
*/
protected static function loadFromArray( $mc ) {
if( isset( $mc['iw_url'] ) ) {
@@ -199,15 +204,130 @@ class Interwiki {
}
/**
+ * Fetch all interwiki prefixes from interwiki cache
+ *
+ * @param $local null|string If not null, limits output to local/non-local interwikis
+ * @return Array List of prefixes
+ * @since 1.19
+ */
+ protected static function getAllPrefixesCached( $local ) {
+ global $wgInterwikiCache, $wgInterwikiScopes, $wgInterwikiFallbackSite;
+ static $db, $site;
+
+ wfDebug( __METHOD__ . "()\n" );
+ if( !$db ) {
+ $db = CdbReader::open( $wgInterwikiCache );
+ }
+ /* Resolve site name */
+ if( $wgInterwikiScopes >= 3 && !$site ) {
+ $site = $db->get( '__sites:' . wfWikiID() );
+ if ( $site == '' ) {
+ $site = $wgInterwikiFallbackSite;
+ }
+ }
+
+ // List of interwiki sources
+ $sources = array();
+ // Global Level
+ if ( $wgInterwikiScopes >= 2 ) {
+ $sources[] = '__global';
+ }
+ // Site level
+ if ( $wgInterwikiScopes >= 3 ) {
+ $sources[] = '_' . $site;
+ }
+ $sources[] = wfWikiID();
+
+ $data = array();
+
+ foreach( $sources as $source ) {
+ $list = $db->get( "__list:{$source}" );
+ foreach ( explode( ' ', $list ) as $iw_prefix ) {
+ $row = $db->get( "{$source}:{$iw_prefix}" );
+ if( !$row ) {
+ continue;
+ }
+
+ list( $iw_local, $iw_url ) = explode( ' ', $row );
+
+ if ( $local !== null && $local != $iw_local ) {
+ continue;
+ }
+
+ $data[$iw_prefix] = array(
+ 'iw_prefix' => $iw_prefix,
+ 'iw_url' => $iw_url,
+ 'iw_local' => $iw_local,
+ );
+ }
+ }
+
+ ksort( $data );
+
+ return array_values( $data );
+ }
+
+ /**
+ * Fetch all interwiki prefixes from DB
+ *
+ * @param $local string|null If not null, limits output to local/non-local interwikis
+ * @return Array List of prefixes
+ * @since 1.19
+ */
+ protected static function getAllPrefixesDB( $local ) {
+ $db = wfGetDB( DB_SLAVE );
+
+ $where = array();
+
+ if ( $local !== null ) {
+ if ( $local == 1 ) {
+ $where['iw_local'] = 1;
+ } elseif ( $local == 0 ) {
+ $where['iw_local'] = 0;
+ }
+ }
+
+ $res = $db->select( 'interwiki',
+ array( 'iw_prefix', 'iw_url', 'iw_api', 'iw_wikiid', 'iw_local', 'iw_trans' ),
+ $where, __METHOD__, array( 'ORDER BY' => 'iw_prefix' )
+ );
+ $retval = array();
+ foreach ( $res as $row ) {
+ $retval[] = (array)$row;
+ }
+ return $retval;
+ }
+
+ /**
+ * Returns all interwiki prefixes
+ *
+ * @param $local string|null If set, limits output to local/non-local interwikis
+ * @return Array List of prefixes
+ * @since 1.19
+ */
+ public static function getAllPrefixes( $local = null ) {
+ global $wgInterwikiCache;
+
+ if ( $wgInterwikiCache ) {
+ return self::getAllPrefixesCached( $local );
+ } else {
+ return self::getAllPrefixesDB( $local );
+ }
+ }
+
+ /**
* Get the URL for a particular title (or with $1 if no title given)
*
* @param $title String: what text to put for the article name
* @return String: the URL
+ * @note Prior to 1.19 The getURL with an argument was broken.
+ * If you if you use this arg in an extension that supports MW earlier
+ * than 1.19 please wfUrlencode and substitute $1 on your own.
*/
public function getURL( $title = null ) {
$url = $this->mURL;
- if( $title != null ) {
- $url = str_replace( "$1", $title, $url );
+ if( $title !== null ) {
+ $url = str_replace( "$1", wfUrlencode( $title ), $url );
}
return $url;
}
diff --git a/includes/job/DoubleRedirectJob.php b/includes/job/DoubleRedirectJob.php
index d7991f5e..2b7cd7c8 100644
--- a/includes/job/DoubleRedirectJob.php
+++ b/includes/job/DoubleRedirectJob.php
@@ -19,7 +19,7 @@ class DoubleRedirectJob extends Job {
*/
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
@@ -28,10 +28,10 @@ class DoubleRedirectJob extends Job {
public static function fixRedirects( $reason, $redirTitle, $destTitle = false ) {
# Need to use the master to get the redirect table updated in the same transaction
$dbw = wfGetDB( DB_MASTER );
- $res = $dbw->select(
- array( 'redirect', 'page' ),
- array( 'page_namespace', 'page_title' ),
- array(
+ $res = $dbw->select(
+ array( 'redirect', 'page' ),
+ array( 'page_namespace', 'page_title' ),
+ array(
'page_id = rd_from',
'rd_namespace' => $redirTitle->getNamespace(),
'rd_title' => $redirTitle->getDBkey()
@@ -46,7 +46,7 @@ class DoubleRedirectJob extends Job {
continue;
}
- $jobs[] = new self( $title, array(
+ $jobs[] = new self( $title, array(
'reason' => $reason,
'redirTitle' => $redirTitle->getPrefixedDBkey() ) );
# Avoid excessive memory usage
@@ -65,6 +65,9 @@ class DoubleRedirectJob extends Job {
$this->destTitleText = !empty( $params['destTitle'] ) ? $params['destTitle'] : '';
}
+ /**
+ * @return bool
+ */
function run() {
if ( !$this->redirTitle ) {
$this->setLastError( 'Invalid title' );
@@ -103,13 +106,13 @@ class DoubleRedirectJob extends Job {
}
# Preserve fragment (bug 14904)
- $newTitle = Title::makeTitle( $newTitle->getNamespace(), $newTitle->getDBkey(),
+ $newTitle = Title::makeTitle( $newTitle->getNamespace(), $newTitle->getDBkey(),
$currentDest->getFragment() );
# Fix the text
# Remember that redirect pages can have categories, templates, etc.,
# so the regex has to be fairly general
- $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x',
+ $newText = preg_replace( '/ \[ \[ [^\]]* \] \] /x',
'[[' . $newTitle->getFullText() . ']]',
$text, 1 );
@@ -122,10 +125,10 @@ class DoubleRedirectJob extends Job {
global $wgUser;
$oldUser = $wgUser;
$wgUser = $this->getUser();
- $article = new Article( $this->title );
- $reason = wfMsgForContent( 'double-redirect-fixed-' . $this->reason,
+ $article = WikiPage::factory( $this->title );
+ $reason = wfMsgForContent( 'double-redirect-fixed-' . $this->reason,
$this->redirTitle->getPrefixedText(), $newTitle->getPrefixedText() );
- $article->doEdit( $newText, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC );
+ $article->doEdit( $newText, $reason, EDIT_UPDATE | EDIT_SUPPRESS_RC, false, $this->getUser() );
$wgUser = $oldUser;
return true;
@@ -152,10 +155,10 @@ class DoubleRedirectJob extends Job {
}
$seenTitles[$titleText] = true;
- $row = $dbw->selectRow(
+ $row = $dbw->selectRow(
array( 'redirect', 'page' ),
array( 'rd_namespace', 'rd_title' ),
- array(
+ array(
'rd_from=page_id',
'page_namespace' => $title->getNamespace(),
'page_title' => $title->getDBkey()
@@ -172,10 +175,12 @@ class DoubleRedirectJob extends Job {
/**
* Get a user object for doing edits, from a request-lifetime cache
+ * @return User
*/
function getUser() {
if ( !self::$user ) {
self::$user = User::newFromName( wfMsgForContent( 'double-redirect-fixer' ), false );
+ # FIXME: newFromName could return false on a badly configured wiki.
if ( !self::$user->isLoggedIn() ) {
self::$user->addToDatabase();
}
diff --git a/includes/job/EnotifNotifyJob.php b/includes/job/EnotifNotifyJob.php
index 5d2a08ea..eb154ece 100644
--- a/includes/job/EnotifNotifyJob.php
+++ b/includes/job/EnotifNotifyJob.php
@@ -20,10 +20,11 @@ class EnotifNotifyJob extends Job {
function run() {
$enotif = new EmailNotification();
// Get the user from ID (rename safe). Anons are 0, so defer to name.
- if( isset($this->params['editorID']) && $this->params['editorID'] ) {
+ if( isset( $this->params['editorID'] ) && $this->params['editorID'] ) {
$editor = User::newFromId( $this->params['editorID'] );
// B/C, only the name might be given.
} else {
+ # FIXME: newFromName could return false on a badly configured wiki.
$editor = User::newFromName( $this->params['editor'], false );
}
$enotif->actuallyNotifyOnPageChange(
diff --git a/includes/job/JobQueue.php b/includes/job/JobQueue.php
index 0d917ba3..e7c66719 100644
--- a/includes/job/JobQueue.php
+++ b/includes/job/JobQueue.php
@@ -213,6 +213,10 @@ abstract class Job {
throw new MWException( "Invalid job command `{$command}`" );
}
+ /**
+ * @param $params
+ * @return string
+ */
static function makeBlob( $params ) {
if ( $params !== false ) {
return serialize( $params );
@@ -221,6 +225,10 @@ abstract class Job {
}
}
+ /**
+ * @param $blob
+ * @return bool|mixed
+ */
static function extractBlob( $blob ) {
if ( (string)$blob !== '' ) {
return unserialize( $blob );
@@ -244,6 +252,10 @@ abstract class Job {
}
$dbw = wfGetDB( DB_MASTER );
$rows = array();
+
+ /**
+ * @var $job Job
+ */
foreach ( $jobs as $job ) {
$rows[] = $job->insertFields();
if ( count( $rows ) >= 50 ) {
@@ -323,13 +335,16 @@ abstract class Job {
if ( $this->removeDuplicates ) {
$res = $dbw->select( 'job', array( '1' ), $fields, __METHOD__ );
if ( $dbw->numRows( $res ) ) {
- return;
+ return true;
}
}
wfIncrStats( 'job-insert' );
return $dbw->insert( 'job', $fields, __METHOD__ );
}
+ /**
+ * @return array
+ */
protected function insertFields() {
$dbw = wfGetDB( DB_MASTER );
return array(
@@ -337,6 +352,7 @@ abstract class Job {
'job_cmd' => $this->command,
'job_namespace' => $this->title->getNamespace(),
'job_title' => $this->title->getDBkey(),
+ 'job_timestamp' => $dbw->timestamp(),
'job_params' => Job::makeBlob( $this->params )
);
}
@@ -362,6 +378,9 @@ abstract class Job {
}
}
+ /**
+ * @return string
+ */
function toString() {
$paramString = '';
if ( $this->params ) {
diff --git a/includes/job/RefreshLinksJob.php b/includes/job/RefreshLinksJob.php
index 910f0c58..1aa206f0 100644
--- a/includes/job/RefreshLinksJob.php
+++ b/includes/job/RefreshLinksJob.php
@@ -22,7 +22,7 @@ class RefreshLinksJob extends Job {
* @return boolean success
*/
function run() {
- global $wgParser;
+ global $wgParser, $wgContLang;
wfProfileIn( __METHOD__ );
$linkCache = LinkCache::singleton();
@@ -42,7 +42,7 @@ class RefreshLinksJob extends Job {
}
wfProfileIn( __METHOD__.'-parse' );
- $options = new ParserOptions;
+ $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
$parserOutput = $wgParser->parse( $revision->getText(), $this->title, $options, true, true, $revision->getId() );
wfProfileOut( __METHOD__.'-parse' );
wfProfileIn( __METHOD__.'-update' );
@@ -71,7 +71,7 @@ class RefreshLinksJob2 extends Job {
* @return boolean success
*/
function run() {
- global $wgParser;
+ global $wgParser, $wgContLang;
wfProfileIn( __METHOD__ );
@@ -88,8 +88,10 @@ class RefreshLinksJob2 extends Job {
wfProfileOut( __METHOD__ );
return false;
}
+ // Back compat for pre-r94435 jobs
+ $table = isset( $this->params['table'] ) ? $this->params['table'] : 'templatelinks';
$titles = $this->title->getBacklinkCache()->getLinks(
- 'templatelinks', $this->params['start'], $this->params['end']);
+ $table, $this->params['start'], $this->params['end']);
# Not suitable for page load triggered job running!
# Gracefully switch to refreshLinks jobs if this happens.
@@ -103,6 +105,7 @@ class RefreshLinksJob2 extends Job {
wfProfileOut( __METHOD__ );
return true;
}
+ $options = ParserOptions::newFromUserAndLang( new User, $wgContLang );
# Re-parse each page that transcludes this page and update their tracking links...
foreach ( $titles as $title ) {
$revision = Revision::newFromTitle( $title );
@@ -112,7 +115,6 @@ class RefreshLinksJob2 extends Job {
return false;
}
wfProfileIn( __METHOD__.'-parse' );
- $options = new ParserOptions;
$parserOutput = $wgParser->parse( $revision->getText(), $title, $options, true, true, $revision->getId() );
wfProfileOut( __METHOD__.'-parse' );
wfProfileIn( __METHOD__.'-update' );
diff --git a/includes/job/UploadFromUrlJob.php b/includes/job/UploadFromUrlJob.php
index 3f915245..26f6e4ba 100644
--- a/includes/job/UploadFromUrlJob.php
+++ b/includes/job/UploadFromUrlJob.php
@@ -61,24 +61,24 @@ class UploadFromUrlJob extends Job {
if ( !$this->params['ignoreWarnings'] ) {
$warnings = $this->upload->checkWarnings();
if ( $warnings ) {
- wfSetupSession( $this->params['sessionId'] );
+
+ # Stash the upload
+ $key = $this->upload->stashFile();
if ( $this->params['leaveMessage'] ) {
$this->user->leaveUserMessage(
wfMsg( 'upload-warning-subj' ),
wfMsg( 'upload-warning-msg',
- $this->params['sessionKey'],
+ $key,
$this->params['url'] )
);
} else {
+ wfSetupSession( $this->params['sessionId'] );
$this->storeResultInSession( 'Warning',
'warnings', $warnings );
+ session_write_close();
}
- # Stash the upload in the session
- $this->upload->stashSession( $this->params['sessionKey'] );
- session_write_close();
-
return true;
}
}
@@ -151,6 +151,10 @@ class UploadFromUrlJob extends Job {
$$session['result'] = 'Queued';
}
+ /**
+ * @param $key
+ * @return mixed
+ */
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 006f7720..f7373e45 100644
--- a/includes/json/FormatJson.php
+++ b/includes/json/FormatJson.php
@@ -11,11 +11,14 @@ if ( !defined( 'MEDIAWIKI' ) ) {
require_once dirname( __FILE__ ) . '/Services_JSON.php';
+/**
+ * JSON formatter wrapper class
+ */
class FormatJson {
-
+
/**
* Returns the JSON representation of a value.
- *
+ *
* @param $value Mixed: the value being encoded. Can be any type except a resource.
* @param $isHtml Boolean
*
@@ -23,7 +26,7 @@ class FormatJson {
* 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
*/
public static function encode( $value, $isHtml = false ) {
@@ -40,10 +43,10 @@ class FormatJson {
/**
* Decodes a JSON string.
- *
+ *
* @param $value String: the json string being decoded.
* @param $assoc Boolean: when true, returned objects will be converted into associative arrays.
- *
+ *
* @return Mixed: the value encoded in json in appropriate PHP type.
* Values true, false and null (case-insensitive) are returned as true, false
* and &null; respectively. &null; is returned if the json cannot be
@@ -51,15 +54,13 @@ class FormatJson {
*/
public static function decode( $value, $assoc = false ) {
if ( !function_exists( 'json_decode' ) ) {
- if( $assoc )
- $json = new Services_JSON( SERVICES_JSON_LOOSE_TYPE );
- else
- $json = new Services_JSON();
+ $json = $assoc ? new Services_JSON( SERVICES_JSON_LOOSE_TYPE ) :
+ new Services_JSON();
$jsonDec = $json->decode( $value );
return $jsonDec;
} else {
return json_decode( $value, $assoc );
}
}
-
+
}
diff --git a/includes/json/Services_JSON.php b/includes/json/Services_JSON.php
index 29cc3617..b2090dce 100644
--- a/includes/json/Services_JSON.php
+++ b/includes/json/Services_JSON.php
@@ -136,7 +136,7 @@ class Services_JSON
{
$this->use = $use;
}
-
+
private static $mHavePear = null;
/**
* Returns cached result of class_exists('pear'), to avoid calling AutoLoader numerous times
@@ -872,7 +872,7 @@ if (class_exists('PEAR_Error')) {
{
$this->message = $message;
}
-
+
function __toString()
{
return $this->message;
diff --git a/includes/libs/CSSJanus.php b/includes/libs/CSSJanus.php
index aa04bc49..c8fc296b 100644
--- a/includes/libs/CSSJanus.php
+++ b/includes/libs/CSSJanus.php
@@ -22,7 +22,9 @@
* written for LTR to RTL.
*
* The original Python version of CSSJanus is Copyright 2008 by Google Inc. and
- * is distributed under the Apache license.
+ * is distributed under the Apache license. This PHP port is Copyright 2010 by
+ * Roan Kattouw and is dual-licensed under the GPL (as in the comment above) and
+ * the Apache (as in the original code) licenses.
*
* Original code: http://code.google.com/p/cssjanus/source/browse/trunk/cssjanus.py
* License of original code: http://code.google.com/p/cssjanus/source/browse/trunk/LICENSE
@@ -111,8 +113,8 @@ class CSSJanus {
$patterns['four_notation_color'] = "/(-color\s*:\s*){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}/i";
// The two regexes below are parenthesized differently then in the original implementation to make the
// callback's job more straightforward
- $patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*[^%]*?)({$patterns['num']})(%\s*(?:{$patterns['quantity']}|{$patterns['ident']}))/";
- $patterns['bg_horizontal_percentage_x'] = "/(background-position-x\s*:\s*)({$patterns['num']})(%)/";
+ $patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*[^%]*?)(-?{$patterns['num']})(%\s*(?:{$patterns['quantity']}|{$patterns['ident']}))/";
+ $patterns['bg_horizontal_percentage_x'] = "/(background-position-x\s*:\s*)(-?{$patterns['num']})(%)/";
}
/**
@@ -173,6 +175,8 @@ class CSSJanus {
*
* See http://code.google.com/p/cssjanus/issues/detail?id=15 and
* TODO: URL
+ * @param $css string
+ * @return string
*/
private static function fixDirection( $css ) {
$css = preg_replace( self::$patterns['direction_ltr'],
@@ -185,6 +189,8 @@ class CSSJanus {
/**
* Replace 'ltr' with 'rtl' and vice versa in background URLs
+ * @param $css string
+ * @return string
*/
private static function fixLtrRtlInURL( $css ) {
$css = preg_replace( self::$patterns['ltr_in_url'], self::$patterns['tmpToken'], $css );
@@ -196,6 +202,8 @@ class CSSJanus {
/**
* Replace 'left' with 'right' and vice versa in background URLs
+ * @param $css string
+ * @return string
*/
private static function fixLeftRightInURL( $css ) {
$css = preg_replace( self::$patterns['left_in_url'], self::$patterns['tmpToken'], $css );
@@ -207,6 +215,8 @@ class CSSJanus {
/**
* Flip rules like left: , padding-right: , etc.
+ * @param $css string
+ * @return string
*/
private static function fixLeftAndRight( $css ) {
$css = preg_replace( self::$patterns['left'], self::$patterns['tmpToken'], $css );
@@ -218,6 +228,8 @@ class CSSJanus {
/**
* Flip East and West in rules like cursor: nw-resize;
+ * @param $css string
+ * @return string
*/
private static function fixCursorProperties( $css ) {
$css = preg_replace( self::$patterns['cursor_east'],
@@ -237,6 +249,8 @@ class CSSJanus {
* and four-part color rules with multiple whitespace characters between
* colors are not recognized.
* See http://code.google.com/p/cssjanus/issues/detail?id=16
+ * @param $css string
+ * @return string
*/
private static function fixFourPartNotation( $css ) {
$css = preg_replace( self::$patterns['four_notation_quantity'], '$1$2$7$4$5$6$3', $css );
@@ -247,6 +261,8 @@ class CSSJanus {
/**
* Flip horizontal background percentages.
+ * @param $css string
+ * @return string
*/
private static function fixBackgroundPosition( $css ) {
$css = preg_replace_callback( self::$patterns['bg_horizontal_percentage'],
@@ -259,6 +275,8 @@ class CSSJanus {
/**
* Callback for calculateNewBackgroundPosition()
+ * @param $matches array
+ * @return string
*/
private static function calculateNewBackgroundPosition( $matches ) {
return $matches[1] . ( 100 - $matches[2] ) . $matches[3];
@@ -295,6 +313,10 @@ class CSSJanus_Tokenizer {
return preg_replace_callback( $this->regex, array( $this, 'tokenizeCallback' ), $str );
}
+ /**
+ * @param $matches array
+ * @return string
+ */
private function tokenizeCallback( $matches ) {
$this->originals[] = $matches[0];
return $this->token;
@@ -314,6 +336,10 @@ class CSSJanus_Tokenizer {
array( $this, 'detokenizeCallback' ), $str );
}
+ /**
+ * @param $matches
+ * @return mixed
+ */
private function detokenizeCallback( $matches ) {
$retval = current( $this->originals );
next( $this->originals );
diff --git a/includes/libs/CSSMin.php b/includes/libs/CSSMin.php
index 4012b695..4f4b28bb 100644
--- a/includes/libs/CSSMin.php
+++ b/includes/libs/CSSMin.php
@@ -1,5 +1,5 @@
<?php
-/*
+/**
* Copyright 2010 Wikimedia Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -79,6 +79,10 @@ class CSSMin {
return $files;
}
+ /**
+ * @param $file string
+ * @return bool|string
+ */
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
@@ -112,10 +116,10 @@ class CSSMin {
* @param $source string CSS data to remap
* @param $local string File path where the source was read from
* @param $remote string URL path to the file
- * @param $embed ???
+ * @param $embedData bool If false, never do any data URI embedding, even if / * @embed * / is found
* @return string Remapped CSS data
*/
- public static function remap( $source, $local, $remote, $embed = true ) {
+ public static function remap( $source, $local, $remote, $embedData = true ) {
$pattern = '/((?P<embed>\s*\/\*\s*\@embed\s*\*\/)(?P<pre>[^\;\}]*))?' .
self::URL_REGEX . '(?P<post>[^;]*)[\;]?/';
$offset = 0;
@@ -162,7 +166,7 @@ class CSSMin {
// using Z for the timezone, meaning GMT
$url .= '?' . gmdate( 'Y-m-d\TH:i:s\Z', round( filemtime( $file ), -2 ) );
// Embedding requires a bit of extra processing, so let's skip that if we can
- if ( $embed ) {
+ if ( $embedData && $embed ) {
$type = self::getMimeType( $file );
// Detect when URLs were preceeded with embed tags, and also verify file size is
// below the limit
diff --git a/includes/libs/IEContentAnalyzer.php b/includes/libs/IEContentAnalyzer.php
index a2ef1a09..01e72e68 100644
--- a/includes/libs/IEContentAnalyzer.php
+++ b/includes/libs/IEContentAnalyzer.php
@@ -1,19 +1,19 @@
<?php
/**
- * This class simulates Microsoft Internet Explorer's terribly broken and
+ * This class simulates Microsoft Internet Explorer's terribly broken and
* insecure MIME type detection algorithm. It can be used to check web uploads
- * with an apparently safe type, to see if IE will reinterpret them to produce
+ * with an apparently safe type, to see if IE will reinterpret them to produce
* something dangerous.
*
- * It is full of bugs and strange design choices should not under any
- * circumstances be used to determine a MIME type to present to a user or
+ * It is full of bugs and strange design choices should not under any
+ * circumstances be used to determine a MIME type to present to a user or
* client. (Apple Safari developers, this means you too.)
*
- * This class is based on a disassembly of IE 5.0, 6.0 and 7.0. Although I have
- * attempted to ensure that this code works in exactly the same way as Internet
- * Explorer, it does not share any source code, or creative choices such as
- * variable names, thus I (Tim Starling) claim copyright on it.
+ * This class is based on a disassembly of IE 5.0, 6.0 and 7.0. Although I have
+ * attempted to ensure that this code works in exactly the same way as Internet
+ * Explorer, it does not share any source code, or creative choices such as
+ * variable names, thus I (Tim Starling) claim copyright on it.
*
* It may be redistributed without restriction. To aid reuse, this class does
* not depend on any MediaWiki module.
@@ -24,8 +24,8 @@ class IEContentAnalyzer {
*/
protected $baseTypeTable = array(
'ambiguous' /*1*/ => array(
- 'text/plain',
- 'application/octet-stream',
+ 'text/plain',
+ 'application/octet-stream',
'application/x-netcdf', // [sic]
),
'text' /*3*/ => array(
@@ -34,8 +34,8 @@ class IEContentAnalyzer {
),
'binary' /*4*/ => array(
'application/pdf', 'audio/x-aiff', 'audio/basic', 'audio/wav', 'image/gif',
- 'image/pjpeg', 'image/jpeg', 'image/tiff', 'image/x-png', 'image/png', 'image/bmp',
- 'image/x-jg', 'image/x-art', 'image/x-emf', 'image/x-wmf', 'video/avi',
+ 'image/pjpeg', 'image/jpeg', 'image/tiff', 'image/x-png', 'image/png', 'image/bmp',
+ 'image/x-jg', 'image/x-art', 'image/x-emf', 'image/x-wmf', 'video/avi',
'video/x-msvideo', 'video/mpeg', 'application/x-compressed',
'application/x-zip-compressed', 'application/x-gzip-compressed', 'application/java',
'application/x-msdownload'
@@ -293,21 +293,21 @@ class IEContentAnalyzer {
'.xsl' => 'text/xml',
);
- /**
- * IE versions which have been analysed to bring you this class, and for
- * which some substantive difference exists. These will appear as keys
+ /**
+ * IE versions which have been analysed to bring you this class, and for
+ * which some substantive difference exists. These will appear as keys
* in the return value of getRealMimesFromData(). The names are chosen to sort correctly.
*/
protected $versions = array( 'ie05', 'ie06', 'ie07', 'ie07.strict', 'ie07.nohtml' );
/**
- * Type table with versions expanded
+ * Type table with versions expanded
*/
protected $typeTable = array();
/** constructor */
function __construct() {
- // Construct versioned type arrays from the base type array plus additions
+ // Construct versioned type arrays from the base type array plus additions
$types = $this->baseTypeTable;
foreach ( $this->versions as $version ) {
if ( isset( $this->addedTypes[$version] ) ) {
@@ -320,7 +320,7 @@ class IEContentAnalyzer {
}
/**
- * Get the MIME types from getMimesFromData(), but convert the result from IE's
+ * Get the MIME types from getMimesFromData(), but convert the result from IE's
* idiosyncratic private types into something other apps will understand.
*
* @param $fileName String: the file name (unused at present)
@@ -338,6 +338,8 @@ class IEContentAnalyzer {
/**
* Translate a MIME type from IE's idiosyncratic private types into
* more commonly understood type strings
+ * @param $type
+ * @return string
*/
public function translateMimeType( $type ) {
static $table = array(
@@ -375,6 +377,11 @@ class IEContentAnalyzer {
/**
* Get the MIME type for a given named version
+ * @param $version
+ * @param $fileName
+ * @param $chunk
+ * @param $proposed
+ * @return bool|string
*/
protected function getMimeTypeForVersion( $version, $fileName, $chunk, $proposed ) {
// Strip text after a semicolon
@@ -397,8 +404,8 @@ class IEContentAnalyzer {
// Truncate chunk at 255 bytes
$chunk = substr( $chunk, 0, 255 );
- // IE does the Check*Headers() calls last, and instead does the following image
- // type checks by directly looking for the magic numbers. What I do here should
+ // IE does the Check*Headers() calls last, and instead does the following image
+ // type checks by directly looking for the magic numbers. What I do here should
// have the same effect since the magic number checks are identical in both cases.
$result = $this->sampleData( $version, $chunk );
$sampleFound = $result['found'];
@@ -413,7 +420,7 @@ class IEContentAnalyzer {
return 'image/gif';
}
if ( ( $proposed == 'image/pjpeg' || $proposed == 'image/jpeg' )
- && $binaryType == 'image/pjpeg' )
+ && $binaryType == 'image/pjpeg' )
{
return $proposed;
}
@@ -430,7 +437,7 @@ class IEContentAnalyzer {
return 'application/x-cdf';
}
- // RSS and Atom were added in IE 7 so they won't be in $sampleFound for
+ // RSS and Atom were added in IE 7 so they won't be in $sampleFound for
// previous versions
if ( isset( $sampleFound['rss'] ) ) {
return 'application/rss+xml';
@@ -483,8 +490,8 @@ class IEContentAnalyzer {
// Freaky heuristics to determine if the data is text or binary
// The heuristic is of course broken for non-ASCII text
- if ( $counters['ctrl'] != 0 && ( $counters['ff'] + $counters['low'] )
- < ( $counters['ctrl'] + $counters['high'] ) * 16 )
+ if ( $counters['ctrl'] != 0 && ( $counters['ff'] + $counters['low'] )
+ < ( $counters['ctrl'] + $counters['high'] ) * 16 )
{
$kindOfBinary = true;
$type = $binaryType ? $binaryType : $textType;
@@ -529,8 +536,8 @@ class IEContentAnalyzer {
return $this->registry[$ext];
}
- // TODO: If the extension has an application registered to it, IE will return
- // application/octet-stream. We'll skip that, so we could erroneously
+ // TODO: If the extension has an application registered to it, IE will return
+ // application/octet-stream. We'll skip that, so we could erroneously
// return text/plain or application/x-netcdf where application/octet-stream
// would be correct.
@@ -540,6 +547,9 @@ class IEContentAnalyzer {
/**
* Check for text headers at the start of the chunk
* Confirmed same in 5 and 7.
+ * @param $version
+ * @param $chunk
+ * @return bool|string
*/
private function checkTextHeaders( $version, $chunk ) {
$chunk2 = substr( $chunk, 0, 2 );
@@ -563,6 +573,9 @@ class IEContentAnalyzer {
/**
* Check for binary headers at the start of the chunk
* Confirmed same in 5 and 7.
+ * @param $version
+ * @param $chunk
+ * @return bool|string
*/
private function checkBinaryHeaders( $version, $chunk ) {
$chunk2 = substr( $chunk, 0, 2 );
@@ -578,13 +591,13 @@ class IEContentAnalyzer {
return 'image/pjpeg'; // actually plain JPEG but this is what IE returns
}
- if ( $chunk2 == 'BM'
+ if ( $chunk2 == 'BM'
&& substr( $chunk, 6, 2 ) == "\000\000"
&& substr( $chunk, 8, 2 ) == "\000\000" )
{
return 'image/bmp'; // another non-standard MIME
}
- if ( $chunk4 == 'RIFF'
+ if ( $chunk4 == 'RIFF'
&& substr( $chunk, 8, 4 ) == 'WAVE' )
{
return 'audio/wav';
@@ -661,6 +674,9 @@ class IEContentAnalyzer {
/**
* Do heuristic checks on the bulk of the data sample.
* Search for HTML tags.
+ * @param $version
+ * @param $chunk
+ * @return array
*/
protected function sampleData( $version, $chunk ) {
$found = array();
@@ -774,7 +790,7 @@ class IEContentAnalyzer {
}
if ( !strncasecmp( $remainder, $rdfPurl, strlen( $rdfPurl ) ) ) {
- if ( isset( $found['rdf-tag'] )
+ if ( isset( $found['rdf-tag'] )
&& isset( $found['rdf-url'] ) ) // [sic]
{
break;
@@ -808,6 +824,11 @@ class IEContentAnalyzer {
return array( 'found' => $found, 'counters' => $counters );
}
+ /**
+ * @param $version
+ * @param $type
+ * @return int|string
+ */
protected function getDataFormat( $version, $type ) {
$types = $this->typeTable[$version];
if ( $type == '(null)' || strval( $type ) === '' ) {
diff --git a/includes/libs/IEUrlExtension.php b/includes/libs/IEUrlExtension.php
index 100454d4..e00e6663 100644
--- a/includes/libs/IEUrlExtension.php
+++ b/includes/libs/IEUrlExtension.php
@@ -1,31 +1,31 @@
<?php
/**
- * Internet Explorer derives a cache filename from a URL, and then in certain
- * circumstances, uses the extension of the resulting file to determine the
- * content type of the data, ignoring the Content-Type header.
+ * Internet Explorer derives a cache filename from a URL, and then in certain
+ * circumstances, uses the extension of the resulting file to determine the
+ * content type of the data, ignoring the Content-Type header.
*
* This can be a problem, especially when non-HTML content is sent by MediaWiki,
* and Internet Explorer interprets it as HTML, exposing an XSS vulnerability.
*
- * Usually the script filename (e.g. api.php) is present in the URL, and this
+ * Usually the script filename (e.g. api.php) is present in the URL, and this
* makes Internet Explorer think the extension is a harmless script extension.
- * But Internet Explorer 6 and earlier allows the script extension to be
- * obscured by encoding the dot as "%2E".
+ * But Internet Explorer 6 and earlier allows the script extension to be
+ * obscured by encoding the dot as "%2E".
*
- * This class contains functions which help in detecting and dealing with this
+ * This class contains functions which help in detecting and dealing with this
* situation.
*
- * Checking the URL for a bad extension is somewhat complicated due to the fact
+ * Checking the URL for a bad extension is somewhat complicated due to the fact
* that CGI doesn't provide a standard method to determine the URL. Instead it
- * is necessary to pass a subset of $_SERVER variables, which we then attempt
+ * is necessary to pass a subset of $_SERVER variables, which we then attempt
* to use to guess parts of the URL.
*/
class IEUrlExtension {
/**
* Check a subset of $_SERVER (or the whole of $_SERVER if you like)
- * to see if it indicates that the request was sent with a bad file
- * extension. Returns true if the request should be denied or modified,
+ * to see if it indicates that the request was sent with a bad file
+ * extension. Returns true if the request should be denied or modified,
* false otherwise. The relevant $_SERVER elements are:
*
* - SERVER_SOFTWARE
@@ -37,6 +37,7 @@ class IEUrlExtension {
*
* @param $vars A subset of $_SERVER.
* @param $extWhitelist Extensions which are allowed, assumed harmless.
+ * @return bool
*/
public static function areServerVarsBad( $vars, $extWhitelist = array() ) {
// Check QUERY_STRING or REQUEST_URI
@@ -55,7 +56,7 @@ class IEUrlExtension {
return true;
}
- // Some servers have PATH_INFO but not REQUEST_URI, so we check both
+ // Some servers have PATH_INFO but not REQUEST_URI, so we check both
// to be on the safe side.
if ( isset( $vars['PATH_INFO'] )
&& self::isUrlExtensionBad( $vars['PATH_INFO'], $extWhitelist ) )
@@ -71,7 +72,7 @@ class IEUrlExtension {
* Given a right-hand portion of a URL, determine whether IE would detect
* a potentially harmful file extension.
*
- * @param $urlPart The right-hand portion of a URL
+ * @param $urlPart string The right-hand portion of a URL
* @param $extWhitelist An array of file extensions which may occur in this
* URL, and which should be allowed.
* @return bool
@@ -97,10 +98,10 @@ class IEUrlExtension {
}
if ( !preg_match( '/^[a-zA-Z0-9_-]+$/', $extension ) ) {
- // Non-alphanumeric extension, unlikely to be registered.
+ // Non-alphanumeric extension, unlikely to be registered.
//
// The regex above is known to match all registered file extensions
- // in a default Windows XP installation. It's important to allow
+ // in a default Windows XP installation. It's important to allow
// extensions with ampersands and percent signs, since that reduces
// the number of false positives substantially.
return false;
@@ -111,8 +112,11 @@ class IEUrlExtension {
}
/**
- * Returns a variant of $url which will pass isUrlExtensionBad() but has the
+ * Returns a variant of $url which will pass isUrlExtensionBad() but has the
* same GET parameters, or false if it can't figure one out.
+ * @param $url
+ * @param $extWhitelist array
+ * @return bool|string
*/
public static function fixUrlForIE6( $url, $extWhitelist = array() ) {
$questionPos = strpos( $url, '?' );
@@ -127,7 +131,7 @@ class IEUrlExtension {
$query = substr( $url, $questionPos + 1 );
}
- // Multiple question marks cause problems. Encode the second and
+ // Multiple question marks cause problems. Encode the second and
// subsequent question mark.
$query = str_replace( '?', '%3E', $query );
// Append an invalid path character so that IE6 won't see the end of the
@@ -153,16 +157,16 @@ class IEUrlExtension {
* insecure.
*
* The criteria for finding an extension are as follows:
- * - a possible extension is a dot followed by one or more characters not
+ * - a possible extension is a dot followed by one or more characters not
* in <>\"/:|?.#
- * - if we find a possible extension followed by the end of the string or
+ * - if we find a possible extension followed by the end of the string or
* a #, that's our extension
* - if we find a possible extension followed by a ?, that's our extension
- * - UNLESS it's exe, dll or cgi, in which case we ignore it and continue
+ * - UNLESS it's exe, dll or cgi, in which case we ignore it and continue
* searching for another possible extension
- * - if we find a possible extension followed by a dot or another illegal
+ * - if we find a possible extension followed by a dot or another illegal
* character, we ignore it and continue searching
- *
+ *
* @param $url string URL
* @return mixed Detected extension (string), or false if none found
*/
@@ -182,7 +186,7 @@ class IEUrlExtension {
// End of string, we're done
return false;
}
-
+
// We found a dot. Skip past it
$pos++;
$remainingLength = $urlLength - $pos;
@@ -220,12 +224,12 @@ class IEUrlExtension {
* with %2E not decoded to ".". On such a server, it is possible to detect
* whether the script filename has been obscured.
*
- * The function returns false if the server is not known to have this
+ * The function returns false if the server is not known to have this
* behaviour. Microsoft IIS in particular is known to decode escaped script
* filenames.
*
* SERVER_SOFTWARE typically contains either a plain string such as "Zeus",
- * or a specification in the style of a User-Agent header, such as
+ * or a specification in the style of a User-Agent header, such as
* "Apache/1.3.34 (Unix) mod_ssl/2.8.25 OpenSSL/0.9.8a PHP/4.4.2"
*
* @param $serverSoftware
@@ -234,8 +238,8 @@ class IEUrlExtension {
*/
public static function haveUndecodedRequestUri( $serverSoftware ) {
static $whitelist = array(
- 'Apache',
- 'Zeus',
+ 'Apache',
+ 'Zeus',
'LiteSpeed' );
if ( preg_match( '/^(.*?)($|\/| )/', $serverSoftware, $m ) ) {
return in_array( $m[1], $whitelist );
diff --git a/includes/libs/JavaScriptMinifier.php b/includes/libs/JavaScriptMinifier.php
index a991d915..baf93385 100644
--- a/includes/libs/JavaScriptMinifier.php
+++ b/includes/libs/JavaScriptMinifier.php
@@ -484,22 +484,42 @@ class JavaScriptMinifier {
$end++;
}
} elseif(
+ $ch === '0'
+ && ($pos + 1 < $length) && ($s[$pos + 1] === 'x' || $s[$pos + 1] === 'X' )
+ ) {
+ // Hex numeric literal
+ $end++; // x or X
+ $len = strspn( $s, '0123456789ABCDEFabcdef', $end );
+ if ( !$len ) {
+ return self::parseError($s, $pos, 'Expected a hexadecimal number but found ' . substr( $s, $pos, 5 ) . '...' );
+ }
+ $end += $len;
+ } elseif(
ctype_digit( $ch )
|| ( $ch === '.' && $pos + 1 < $length && ctype_digit( $s[$pos + 1] ) )
) {
- // Numeric literal. Search for the end of it, but don't care about [+-]exponent
- // at the end, as the results of "numeric [+-] numeric" and "numeric" are
- // identical to our state machine.
- $end += strspn( $s, '0123456789ABCDEFabcdefXx.', $end );
- while( $s[$end - 1] === '.' ) {
- // Special case: When a numeric ends with a dot, we have to check the
- // literal for proper syntax
- $decimal = strspn( $s, '0123456789', $pos, $end - $pos - 1 );
- if( $decimal === $end - $pos - 1 ) {
- break;
- } else {
- $end--;
+ $end += strspn( $s, '0123456789', $end );
+ $decimal = strspn( $s, '.', $end );
+ if ($decimal) {
+ if ( $decimal > 2 ) {
+ return self::parseError($s, $end, 'The number has too many decimal points' );
+ }
+ $end += strspn( $s, '0123456789', $end + 1 ) + $decimal;
+ }
+ $exponent = strspn( $s, 'eE', $end );
+ if( $exponent ) {
+ if ( $exponent > 1 ) {
+ return self::parseError($s, $end, 'Number with several E' );
+ }
+ $end++;
+
+ // + sign is optional; - sign is required.
+ $end += strspn( $s, '-+', $end );
+ $len = strspn( $s, '0123456789', $end );
+ if ( !$len ) {
+ return self::parseError($s, $pos, 'No decimal digits after e, how many zeroes should be added?' );
}
+ $end += $len;
}
} elseif( isset( $opChars[$ch] ) ) {
// Punctuation character. Search for the longest matching operator.
@@ -576,4 +596,9 @@ class JavaScriptMinifier {
}
return $out;
}
+
+ static function parseError($fullJavascript, $position, $errorMsg) {
+ // TODO: Handle the error: trigger_error, throw exception, return false...
+ return false;
+ }
}
diff --git a/includes/libs/jsminplus.php b/includes/libs/jsminplus.php
index bab4ff49..8ed08d74 100644
--- a/includes/libs/jsminplus.php
+++ b/includes/libs/jsminplus.php
@@ -1,7 +1,7 @@
<?php
/**
- * JSMinPlus version 1.3
+ * JSMinPlus version 1.4
*
* Minifies a javascript file using a javascript parser
*
@@ -15,8 +15,10 @@
* 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
+ * 23-07-2011 - remove dynamic creation of OP_* and KEYWORD_* defines and declare them on top
+ * reduce memory footprint by minifying by block-scope
+ * some small byte-saving and performance improvements
+ * 12-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
@@ -46,7 +48,7 @@
* the Initial Developer. All Rights Reserved.
*
* Contributor(s): Tino Zijdel <crisp@tweakers.net>
- * PHP port, modifications and minifier routine are (C) 2009
+ * PHP port, modifications and minifier routine are (C) 2009-2011
*
* 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
@@ -86,6 +88,8 @@ define('JS_SETTER', 111);
define('JS_GROUP', 112);
define('JS_LIST', 113);
+define('JS_MINIFIED', 999);
+
define('DECLARED_FORM', 0);
define('EXPRESSED_FORM', 1);
define('STATEMENT_FORM', 2);
@@ -188,7 +192,7 @@ class JSMinPlus
private function __construct()
{
- $this->parser = new JSParser();
+ $this->parser = new JSParser($this);
}
public static function minify($js, $filename='')
@@ -217,22 +221,18 @@ class JSMinPlus
return false;
}
- private function parseTree($n, $noBlockGrouping = false)
+ public 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) . '}';
+ case JS_MINIFIED:
+ $s = $n->value;
break;
case JS_SCRIPT:
- // we do nothing with funDecls or varDecls
+ // we do nothing yet with funDecls or varDecls
$noBlockGrouping = true;
// FALL THROUGH
@@ -279,6 +279,14 @@ class JSMinPlus
}
break;
+ 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 KEYWORD_IF:
$s = 'if(' . $this->parseTree($n->condition) . ')';
$thenPart = $this->parseTree($n->thenPart);
@@ -385,19 +393,14 @@ class JSMinPlus
break;
case KEYWORD_THROW:
- $s = 'throw ' . $this->parseTree($n->exception);
- break;
-
case KEYWORD_RETURN:
- $s = 'return';
+ $s = $n->type;
if ($n->value)
{
$t = $this->parseTree($n->value);
if (strlen($t))
{
- if ( $t[0] != '(' && $t[0] != '[' && $t[0] != '{' &&
- $t[0] != '"' && $t[0] != "'" && $t[0] != '/'
- )
+ if ($this->isWordChar($t[0]) || $t[0] == '\\')
$s .= ' ';
$s .= $t;
@@ -423,6 +426,40 @@ class JSMinPlus
}
break;
+ case KEYWORD_IN:
+ case KEYWORD_INSTANCEOF:
+ $left = $this->parseTree($n->treeNodes[0]);
+ $right = $this->parseTree($n->treeNodes[1]);
+
+ $s = $left;
+
+ if ($this->isWordChar(substr($left, -1)))
+ $s .= ' ';
+
+ $s .= $n->type;
+
+ if ($this->isWordChar($right[0]) || $right[0] == '\\')
+ $s .= ' ';
+
+ $s .= $right;
+ break;
+
+ case KEYWORD_DELETE:
+ case KEYWORD_TYPEOF:
+ $right = $this->parseTree($n->treeNodes[0]);
+
+ $s = $n->type;
+
+ if ($this->isWordChar($right[0]) || $right[0] == '\\')
+ $s .= ' ';
+
+ $s .= $right;
+ break;
+
+ case KEYWORD_VOID:
+ $s = 'void(' . $this->parseTree($n->treeNodes[0]) . ')';
+ break;
+
case KEYWORD_DEBUGGER:
throw new Exception('NOT IMPLEMENTED: DEBUGGER');
break;
@@ -497,26 +534,6 @@ class JSMinPlus
}
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:
@@ -606,13 +623,33 @@ class JSMinPlus
$s .= '}';
break;
+ case TOKEN_NUMBER:
+ $s = $n->value;
+ if (preg_match('/^([1-9]+)(0{3,})$/', $s, $m))
+ $s = $m[1] . 'e' . strlen($m[2]);
+ 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:
+ case TOKEN_IDENTIFIER: case TOKEN_STRING: case TOKEN_REGEXP:
$s = $n->value;
break;
case JS_GROUP:
- $s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
+ if (in_array(
+ $n->treeNodes[0]->type,
+ array(
+ JS_ARRAY_INIT, JS_OBJECT_INIT, JS_GROUP,
+ TOKEN_NUMBER, TOKEN_STRING, TOKEN_REGEXP, TOKEN_IDENTIFIER,
+ KEYWORD_NULL, KEYWORD_THIS, KEYWORD_TRUE, KEYWORD_FALSE
+ )
+ ))
+ {
+ $s = $this->parseTree($n->treeNodes[0]);
+ }
+ else
+ {
+ $s = '(' . $this->parseTree($n->treeNodes[0]) . ')';
+ }
break;
default:
@@ -626,11 +663,17 @@ class JSMinPlus
{
return preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $string) && !in_array($string, $this->reserved);
}
+
+ private function isWordChar($char)
+ {
+ return $char == '_' || $char == '$' || ctype_alnum($char);
+ }
}
class JSParser
{
private $t;
+ private $minifier;
private $opPrecedence = array(
';' => 0,
@@ -680,8 +723,9 @@ class JSParser
TOKEN_CONDCOMMENT_START => 1, TOKEN_CONDCOMMENT_END => 1
);
- public function __construct()
+ public function __construct($minifier=null)
{
+ $this->minifier = $minifier;
$this->t = new JSTokenizer();
}
@@ -705,6 +749,19 @@ class JSParser
$n->funDecls = $x->funDecls;
$n->varDecls = $x->varDecls;
+ // minify by scope
+ if ($this->minifier)
+ {
+ $n->value = $this->minifier->parseTree($n);
+
+ // clear tree from node to save memory
+ $n->treeNodes = null;
+ $n->funDecls = null;
+ $n->varDecls = null;
+
+ $n->type = JS_MINIFIED;
+ }
+
return $n;
}
@@ -963,7 +1020,7 @@ class JSParser
case KEYWORD_THROW:
$n = new JSNode($this->t);
- $n->exception = $this->Expression($x);
+ $n->value = $this->Expression($x);
break;
case KEYWORD_RETURN:
@@ -1678,44 +1735,11 @@ class JSTokenizer
);
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('|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%');
@@ -1723,7 +1747,7 @@ class JSTokenizer
public function __construct()
{
- $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', array_keys($this->opTypeNames))) . ')#';
+ $this->opRegExp = '#^(' . implode('|', array_map('preg_quote', $this->opTypeNames)) . ')#';
}
public function init($source, $filename = '', $lineno = 1)
@@ -1874,22 +1898,38 @@ class JSTokenizer
{
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))
+ case '0':
+ // hexadecimal
+ if (($input[1] == 'x' || $input[1] == 'X') && preg_match('/^0x[0-9a-f]+/i', $input, $match))
{
$tt = TOKEN_NUMBER;
+ break;
}
- else if (preg_match('/^0[xX][\da-fA-F]+|^0[0-7]*|^\d+/', $input, $match))
+ // FALL THROUGH
+
+ case '1': case '2': case '3': case '4': case '5':
+ case '6': case '7': case '8': case '9':
+ // should always match
+ preg_match('/^\d+(?:\.\d*)?(?:[eE][-+]?\d+)?/', $input, $match);
+ $tt = TOKEN_NUMBER;
+ break;
+
+ case "'":
+ if (preg_match('/^\'(?:[^\\\\\'\r\n]++|\\\\(?:.|\r?\n))*\'/', $input, $match))
{
- // this should always match because of \d+
- $tt = TOKEN_NUMBER;
+ $tt = TOKEN_STRING;
+ }
+ else
+ {
+ if ($chunksize)
+ return $this->get(null); // retry with a full chunk fetch
+
+ throw $this->newSyntaxError('Unterminated string literal');
}
break;
case '"':
- case "'":
- if (preg_match('/^"(?:\\\\(?:.|\r?\n)|[^\\\\"\r\n]+)*"|^\'(?:\\\\(?:.|\r?\n)|[^\\\\\'\r\n]+)*\'/', $input, $match))
+ if (preg_match('/^"(?:[^\\\\"\r\n]++|\\\\(?:.|\r?\n))*"/', $input, $match))
{
$tt = TOKEN_STRING;
}
@@ -2091,4 +2131,3 @@ class JSToken
public $lineno;
public $assignOp;
}
-
diff --git a/includes/logging/LogEntry.php b/includes/logging/LogEntry.php
new file mode 100644
index 00000000..4aa6a826
--- /dev/null
+++ b/includes/logging/LogEntry.php
@@ -0,0 +1,518 @@
+<?php
+/**
+ * Contain classes for dealing with individual log entries
+ *
+ * This is how I see the log system history:
+ * - appending to plain wiki pages
+ * - formatting log entries based on database fields
+ * - user is now part of the action message
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.19
+ */
+
+/**
+ * Interface for log entries. Every log entry has these methods.
+ * @since 1.19
+ */
+interface LogEntry {
+
+ /**
+ * The main log type.
+ * @return string
+ */
+ public function getType();
+
+ /**
+ * The log subtype.
+ * @return string
+ */
+ public function getSubtype();
+
+ /**
+ * The full logtype in format maintype/subtype.
+ * @return string
+ */
+ public function getFullType();
+
+ /**
+ * Get the extra parameters stored for this message.
+ * @return array
+ */
+ public function getParameters();
+
+ /**
+ * Get the user for performed this action.
+ * @return User
+ */
+ public function getPerformer();
+
+ /**
+ * Get the target page of this action.
+ * @return Title
+ */
+ public function getTarget();
+
+ /**
+ * Get the timestamp when the action was executed.
+ * @return string
+ */
+ public function getTimestamp();
+
+ /**
+ * Get the user provided comment.
+ * @return string
+ */
+ public function getComment();
+
+ /**
+ * Get the access restriction.
+ * @return string
+ */
+ public function getDeleted();
+
+ /**
+ * @param $field Integer: one of LogPage::DELETED_* bitfield constants
+ * @return Boolean
+ */
+ public function isDeleted( $field );
+}
+
+/**
+ * Extends the LogEntryInterface with some basic functionality
+ * @since 1.19
+ */
+abstract class LogEntryBase implements LogEntry {
+
+ public function getFullType() {
+ return $this->getType() . '/' . $this->getSubtype();
+ }
+
+ public function isDeleted( $field ) {
+ return ( $this->getDeleted() & $field ) === $field;
+ }
+
+ /**
+ * Whether the parameters for this log are stored in new or
+ * old format.
+ */
+ public function isLegacy() {
+ return false;
+ }
+
+}
+
+/**
+ * This class wraps around database result row.
+ * @since 1.19
+ */
+class DatabaseLogEntry extends LogEntryBase {
+ // Static->
+
+ /**
+ * Returns array of information that is needed for querying
+ * log entries. Array contains the following keys:
+ * tables, fields, conds, options and join_conds
+ * @return array
+ */
+ public static function getSelectQueryData() {
+ $tables = array( 'logging', 'user' );
+ $fields = array(
+ 'log_id', 'log_type', 'log_action', 'log_timestamp',
+ 'log_user', 'log_user_text',
+ 'log_namespace', 'log_title', // unused log_page
+ 'log_comment', 'log_params', 'log_deleted',
+ 'user_id', 'user_name', 'user_editcount',
+ );
+
+ $joins = array(
+ // IP's don't have an entry in user table
+ 'user' => array( 'LEFT JOIN', 'log_user=user_id' ),
+ );
+
+ return array(
+ 'tables' => $tables,
+ 'fields' => $fields,
+ 'conds' => array(),
+ 'options' => array(),
+ 'join_conds' => $joins,
+ );
+ }
+
+ /**
+ * Constructs new LogEntry from database result row.
+ * Supports rows from both logging and recentchanges table.
+ * @param $row
+ * @return DatabaseLogEntry
+ */
+ public static function newFromRow( $row ) {
+ if ( is_array( $row ) && isset( $row['rc_logid'] ) ) {
+ return new RCDatabaseLogEntry( (object) $row );
+ } else {
+ return new self( $row );
+ }
+ }
+
+ // Non-static->
+
+ /// Database result row.
+ protected $row;
+
+ protected function __construct( $row ) {
+ $this->row = $row;
+ }
+
+ /**
+ * Returns the unique database id.
+ * @return int
+ */
+ public function getId() {
+ return (int)$this->row->log_id;
+ }
+
+ /**
+ * Returns whatever is stored in the database field.
+ * @return string
+ */
+ protected function getRawParameters() {
+ return $this->row->log_params;
+ }
+
+ // LogEntryBase->
+
+ public function isLegacy() {
+ // This does the check
+ $this->getParameters();
+ return $this->legacy;
+ }
+
+ // LogEntry->
+
+ public function getType() {
+ return $this->row->log_type;
+ }
+
+ public function getSubtype() {
+ return $this->row->log_action;
+ }
+
+ public function getParameters() {
+ if ( !isset( $this->params ) ) {
+ $blob = $this->getRawParameters();
+ wfSuppressWarnings();
+ $params = unserialize( $blob );
+ wfRestoreWarnings();
+ if ( $params !== false ) {
+ $this->params = $params;
+ $this->legacy = false;
+ } else {
+ $this->params = $blob === '' ? array() : explode( "\n", $blob );
+ $this->legacy = true;
+ }
+ }
+ return $this->params;
+ }
+
+ public function getPerformer() {
+ $userId = (int) $this->row->log_user;
+ if ( $userId !== 0 ) { // logged-in users
+ if ( isset( $this->row->user_name ) ) {
+ return User::newFromRow( $this->row );
+ } else {
+ return User::newFromId( $userId );
+ }
+ } else { // IP users
+ $userText = $this->row->log_user_text;
+ return User::newFromName( $userText, false );
+ }
+ }
+
+ public function getTarget() {
+ $namespace = $this->row->log_namespace;
+ $page = $this->row->log_title;
+ $title = Title::makeTitle( $namespace, $page );
+ return $title;
+ }
+
+ public function getTimestamp() {
+ return wfTimestamp( TS_MW, $this->row->log_timestamp );
+ }
+
+ public function getComment() {
+ return $this->row->log_comment;
+ }
+
+ public function getDeleted() {
+ return $this->row->log_deleted;
+ }
+
+}
+
+class RCDatabaseLogEntry extends DatabaseLogEntry {
+
+ public function getId() {
+ return $this->row->rc_logid;
+ }
+
+ protected function getRawParameters() {
+ return $this->row->rc_params;
+ }
+
+ // LogEntry->
+
+ public function getType() {
+ return $this->row->rc_log_type;
+ }
+
+ public function getSubtype() {
+ return $this->row->rc_log_action;
+ }
+
+ public function getPerformer() {
+ $userId = (int) $this->row->rc_user;
+ if ( $userId !== 0 ) {
+ return User::newFromId( $userId );
+ } else {
+ $userText = $this->row->rc_user_text;
+ // Might be an IP, don't validate the username
+ return User::newFromName( $userText, false );
+ }
+ }
+
+ public function getTarget() {
+ $namespace = $this->row->rc_namespace;
+ $page = $this->row->rc_title;
+ $title = Title::makeTitle( $namespace, $page );
+ return $title;
+ }
+
+ public function getTimestamp() {
+ return wfTimestamp( TS_MW, $this->row->rc_timestamp );
+ }
+
+ public function getComment() {
+ return $this->row->rc_comment;
+ }
+
+ public function getDeleted() {
+ return $this->row->rc_deleted;
+ }
+
+}
+
+/**
+ * Class for creating log entries manually, for
+ * example to inject them into the database.
+ * @since 1.19
+ */
+class ManualLogEntry extends LogEntryBase {
+ protected $type; ///!< @var string
+ protected $subtype; ///!< @var string
+ protected $parameters = array(); ///!< @var array
+ protected $performer; ///!< @var User
+ protected $target; ///!< @var Title
+ protected $timestamp; ///!< @var string
+ protected $comment = ''; ///!< @var string
+ protected $deleted; ///!< @var int
+
+ /**
+ * Constructor.
+ *
+ * @since 1.19
+ *
+ * @param string $type
+ * @param string $subtype
+ */
+ public function __construct( $type, $subtype ) {
+ $this->type = $type;
+ $this->subtype = $subtype;
+ }
+
+ /**
+ * Set extra log parameters.
+ * You can pass params to the log action message
+ * by prefixing the keys with a number and colon.
+ * The numbering should start with number 4, the
+ * first three parameters are hardcoded for every
+ * message. Example:
+ * $entry->setParameters(
+ * '4:color' => 'blue',
+ * 'animal' => 'dog'
+ * );
+ *
+ * @since 1.19
+ *
+ * @param $parameters Associative array
+ */
+ public function setParameters( $parameters ) {
+ $this->parameters = $parameters;
+ }
+
+ /**
+ * Set the user that performed the action being logged.
+ *
+ * @since 1.19
+ *
+ * @param User $performer
+ */
+ public function setPerformer( User $performer ) {
+ $this->performer = $performer;
+ }
+
+ /**
+ * Set the title of the object changed.
+ *
+ * @since 1.19
+ *
+ * @param Title $target
+ */
+ public function setTarget( Title $target ) {
+ $this->target = $target;
+ }
+
+ /**
+ * Set the timestamp of when the logged action took place.
+ *
+ * @since 1.19
+ *
+ * @param string $timestamp
+ */
+ public function setTimestamp( $timestamp ) {
+ $this->timestamp = $timestamp;
+ }
+
+ /**
+ * Set a comment associated with the action being logged.
+ *
+ * @since 1.19
+ *
+ * @param string $comment
+ */
+ public function setComment( $comment ) {
+ $this->comment = $comment;
+ }
+
+ /**
+ * TODO: document
+ *
+ * @since 1.19
+ *
+ * @param integer $deleted
+ */
+ public function setDeleted( $deleted ) {
+ $this->deleted = $deleted;
+ }
+
+ /**
+ * Inserts the entry into the logging table.
+ * @return int If of the log entry
+ */
+ public function insert() {
+ global $wgContLang;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
+
+ if ( $this->timestamp === null ) {
+ $this->timestamp = wfTimestampNow();
+ }
+
+ # Truncate for whole multibyte characters.
+ $comment = $wgContLang->truncate( $this->getComment(), 255 );
+
+ $data = array(
+ 'log_id' => $id,
+ 'log_type' => $this->getType(),
+ 'log_action' => $this->getSubtype(),
+ 'log_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
+ 'log_user' => $this->getPerformer()->getId(),
+ 'log_user_text' => $this->getPerformer()->getName(),
+ 'log_namespace' => $this->getTarget()->getNamespace(),
+ 'log_title' => $this->getTarget()->getDBkey(),
+ 'log_page' => $this->getTarget()->getArticleId(),
+ 'log_comment' => $comment,
+ 'log_params' => serialize( (array) $this->getParameters() ),
+ );
+ $dbw->insert( 'logging', $data, __METHOD__ );
+ $this->id = !is_null( $id ) ? $id : $dbw->insertId();
+ return $this->id;
+ }
+
+ /**
+ * Publishes the log entry.
+ * @param $newId int id of the log entry.
+ * @param $to string: rcandudp (default), rc, udp
+ */
+ public function publish( $newId, $to = 'rcandudp' ) {
+ $log = new LogPage( $this->getType() );
+ if ( $log->isRestricted() ) {
+ return;
+ }
+
+ $formatter = LogFormatter::newFromEntry( $this );
+ $context = RequestContext::newExtraneousContext( $this->getTarget() );
+ $formatter->setContext( $context );
+
+ $logpage = SpecialPage::getTitleFor( 'Log', $this->getType() );
+ $user = $this->getPerformer();
+ $rc = RecentChange::newLogEntry(
+ $this->getTimestamp(),
+ $logpage,
+ $user,
+ $formatter->getIRCActionText(), // Used for IRC feeds
+ $user->isAnon() ? $user->getName() : '',
+ $this->getType(),
+ $this->getSubtype(),
+ $this->getTarget(),
+ $this->getComment(),
+ serialize( (array) $this->getParameters() ),
+ $newId
+ );
+
+ if ( $to === 'rc' || $to === 'rcandudp' ) {
+ $rc->save( 'pleasedontudp' );
+ }
+
+ if ( $to === 'udp' || $to === 'rcandudp' ) {
+ $rc->notifyRC2UDP();
+ }
+ }
+
+ // LogEntry->
+
+ public function getType() {
+ return $this->type;
+ }
+
+ public function getSubtype() {
+ return $this->subtype;
+ }
+
+ public function getParameters() {
+ return $this->parameters;
+ }
+
+ public function getPerformer() {
+ return $this->performer;
+ }
+
+ public function getTarget() {
+ return $this->target;
+ }
+
+ public function getTimestamp() {
+ $ts = $this->timestamp !== null ? $this->timestamp : wfTimestampNow();
+ return wfTimestamp( TS_MW, $ts );
+ }
+
+ public function getComment() {
+ return $this->comment;
+ }
+
+ public function getDeleted() {
+ return (int) $this->deleted;
+ }
+
+}
diff --git a/includes/LogEventsList.php b/includes/logging/LogEventsList.php
index 744a60ce..437670d0 100644
--- a/includes/LogEventsList.php
+++ b/includes/logging/LogEventsList.php
@@ -74,13 +74,16 @@ class LogEventsList {
/**
* Set page title and show header for this log type
* @param $type Array
+ * @deprecated in 1.19
*/
public function showHeader( $type ) {
+ wfDeprecated( __METHOD__, '1.19' );
// If only one log type is used, then show a special message...
$headerType = (count($type) == 1) ? $type[0] : '';
if( LogPage::isLogType( $headerType ) ) {
- $this->out->setPageTitle( LogPage::logName( $headerType ) );
- $this->out->addHTML( LogPage::logHeader( $headerType ) );
+ $page = new LogPage( $headerType );
+ $this->out->setPageTitle( $page->getName()->text() );
+ $this->out->addHTML( $page->getDescription()->parseAsBlock() );
} else {
$this->out->addHTML( wfMsgExt('alllogstext',array('parseinline')) );
}
@@ -210,52 +213,53 @@ class LogEventsList {
return $this->out->getTitle();
}
+ public function getContext() {
+ return $this->out->getContext();
+ }
+
/**
* @param $queryTypes Array
* @return String: Formatted HTML
*/
private function getTypeMenu( $queryTypes ) {
- global $wgLogRestrictions, $wgUser;
+ $queryType = count($queryTypes) == 1 ? $queryTypes[0] : '';
+ $selector = $this->getTypeSelector();
+ $selector->setDefault( $queryType );
+ return $selector->getHtml();
+ }
- $html = "<select name='type'>\n";
+ /**
+ * Returns log page selector.
+ * @return XmlSelect
+ * @since 1.19
+ */
+ public function getTypeSelector() {
+ global $wgUser;
- $validTypes = LogPage::validTypes();
$typesByName = array(); // Temporary array
-
// First pass to load the log names
- foreach( $validTypes as $type ) {
- $text = LogPage::logName( $type );
- $typesByName[$type] = $text;
+ foreach( LogPage::validTypes() as $type ) {
+ $page = new LogPage( $type );
+ $restriction = $page->getRestriction();
+ if ( $wgUser->isAllowed( $restriction ) ) {
+ $typesByName[$type] = $page->getName()->text();
+ }
}
// Second pass to sort by name
asort($typesByName);
- // Note the query type
- $queryType = count($queryTypes) == 1 ? $queryTypes[0] : '';
-
// Always put "All public logs" on top
- if ( isset( $typesByName[''] ) ) {
- $all = $typesByName[''];
- unset( $typesByName[''] );
- $typesByName = array( '' => $all ) + $typesByName;
- }
+ $public = $typesByName[''];
+ unset( $typesByName[''] );
+ $typesByName = array( '' => $public ) + $typesByName;
- // Third pass generates sorted XHTML content
- foreach( $typesByName as $type => $text ) {
- $selected = ($type == $queryType);
- // Restricted types
- if ( isset($wgLogRestrictions[$type]) ) {
- if ( $wgUser->isAllowed( $wgLogRestrictions[$type] ) ) {
- $html .= Xml::option( $text, $type, $selected ) . "\n";
- }
- } else {
- $html .= Xml::option( $text, $type, $selected ) . "\n";
- }
+ $select = new XmlSelect( 'type' );
+ foreach( $typesByName as $type => $name ) {
+ $select->addOption( $name, $type );
}
- $html .= '</select>';
- return $html;
+ return $select;
}
/**
@@ -279,7 +283,8 @@ class LogEventsList {
}
/**
- * @return boolean Checkbox
+ * @param $pattern
+ * @return string Checkbox
*/
private function getTitlePattern( $pattern ) {
return '<span style="white-space: nowrap">' .
@@ -287,6 +292,10 @@ class LogEventsList {
'</span>';
}
+ /**
+ * @param $types
+ * @return string
+ */
private function getExtraInputs( $types ) {
global $wgRequest;
$offender = $wgRequest->getVal('offender');
@@ -301,10 +310,16 @@ class LogEventsList {
return '';
}
+ /**
+ * @return string
+ */
public function beginLogEventsList() {
return "<ul>\n";
}
+ /**
+ * @return string
+ */
public function endLogEventsList() {
return "</ul>\n";
}
@@ -314,18 +329,19 @@ class LogEventsList {
* @return String: Formatted HTML list item
*/
public function logLine( $row ) {
- $classes = array( 'mw-logline-' . $row->log_type );
- $title = Title::makeTitle( $row->log_namespace, $row->log_title );
- // Log time
- $time = $this->logTimestamp( $row );
- // User links
- $userLink = $this->logUserLinks( $row );
+ $entry = DatabaseLogEntry::newFromRow( $row );
+ $formatter = LogFormatter::newFromEntry( $entry );
+ $formatter->setShowUserToolLinks( !( $this->flags & self::NO_EXTRA_USER_LINKS ) );
+
+ $action = $formatter->getActionText();
+ $comment = $formatter->getComment();
+
+ $classes = array( 'mw-logline-' . $entry->getType() );
+ $title = $entry->getTarget();
+ $time = $this->logTimestamp( $entry );
+
// Extract extra parameters
$paramArray = LogPage::extractParams( $row->log_params );
- // Event description
- $action = $this->logAction( $row, $title, $paramArray );
- // Log comment
- $comment = $this->logComment( $row );
// Add review/revert links and such...
$revert = $this->logActionLinks( $row, $title, $paramArray, $comment );
@@ -338,53 +354,15 @@ class LogEventsList {
$classes = array_merge( $classes, $newClasses );
return Xml::tags( 'li', array( "class" => implode( ' ', $classes ) ),
- $del . "$time $userLink $action $comment $revert $tagDisplay" ) . "\n";
+ $del . "$time $action $comment $revert $tagDisplay" ) . "\n";
}
- private function logTimestamp( $row ) {
+ private function logTimestamp( LogEntry $entry ) {
global $wgLang;
- $time = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->log_timestamp ), true );
+ $time = $wgLang->timeanddate( wfTimestamp( TS_MW, $entry->getTimestamp() ), true );
return htmlspecialchars( $time );
}
- private function logUserLinks( $row ) {
- if( self::isDeleted( $row, LogPage::DELETED_USER ) ) {
- $userLinks = '<span class="history-deleted">' .
- wfMsgHtml( 'rev-deleted-user' ) . '</span>';
- } else {
- $userLinks = Linker::userLink( $row->log_user, $row->user_name );
- // Talk|Contribs links...
- if( !( $this->flags & self::NO_EXTRA_USER_LINKS ) ) {
- $userLinks .= Linker::userToolLinks(
- $row->log_user, $row->user_name, true, 0, $row->user_editcount );
- }
- }
- return $userLinks;
- }
-
- private function logAction( $row, $title, $paramArray ) {
- if( self::isDeleted( $row, LogPage::DELETED_ACTION ) ) {
- $action = '<span class="history-deleted">' .
- wfMsgHtml( 'rev-deleted-event' ) . '</span>';
- } else {
- $action = LogPage::actionText(
- $row->log_type, $row->log_action, $title, $this->skin, $paramArray, true );
- }
- return $action;
- }
-
- private function logComment( $row ) {
- if( self::isDeleted( $row, LogPage::DELETED_COMMENT ) ) {
- $comment = '<span class="history-deleted">' .
- wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
- } else {
- global $wgLang;
- $comment = $wgLang->getDirMark() .
- Linker::commentBlock( $row->log_comment );
- }
- return $comment;
- }
-
/**
* @TODO: split up!
*
@@ -487,7 +465,7 @@ class LogEventsList {
// If an edit was hidden from a page give a review link to the history
} elseif( self::typeAction( $row, array( 'delete', 'suppress' ), 'revision', 'deletedhistory' ) ) {
$revert = RevisionDeleter::getLogLinks( $title, $paramArray,
- $this->skin, $this->message );
+ $this->message );
// Hidden log items, give review link
} elseif( self::typeAction( $row, array( 'delete', 'suppress' ), 'event', 'deletedhistory' ) ) {
if( count($paramArray) >= 1 ) {
@@ -507,19 +485,6 @@ class LogEventsList {
array( 'known', 'noclasses' )
) . ')';
}
- // Self-created users
- } elseif( self::typeAction( $row, 'newusers', 'create2' ) ) {
- if( isset( $paramArray[0] ) ) {
- $revert = Linker::userToolLinks( $paramArray[0], $title->getDBkey(), true );
- } else {
- # Fall back to a blue contributions link
- $revert = Linker::userToolLinks( 1, $title->getDBkey() );
- }
- if( wfTimestamp( TS_MW, $row->log_timestamp ) < '20080129000000' ) {
- # Suppress $comment from old entries (before 2008-01-29),
- # not needed and can contain incorrect links
- $comment = '';
- }
// Do nothing. The implementation is handled by the hook modifiying the passed-by-ref parameters.
} else {
wfRunHooks( 'LogLine', array( $row->log_type, $row->log_action, $title, $paramArray,
@@ -591,10 +556,11 @@ class LogEventsList {
*
* @param $row Row
* @param $field Integer
+ * @param $user User object to check, or null to use $wgUser
* @return Boolean
*/
- public static function userCan( $row, $field ) {
- return self::userCanBitfield( $row->log_deleted, $field );
+ public static function userCan( $row, $field, User $user = null ) {
+ return self::userCanBitfield( $row->log_deleted, $field, $user );
}
/**
@@ -603,19 +569,22 @@ class LogEventsList {
*
* @param $bitfield Integer (current field)
* @param $field Integer
+ * @param $user User object to check, or null to use $wgUser
* @return Boolean
*/
- public static function userCanBitfield( $bitfield, $field ) {
+ public static function userCanBitfield( $bitfield, $field, User $user = null ) {
if( $bitfield & $field ) {
- global $wgUser;
-
if ( $bitfield & LogPage::DELETED_RESTRICTED ) {
$permission = 'suppressrevision';
} else {
$permission = 'deletedhistory';
}
wfDebug( "Checking for $permission due to $field match on $bitfield\n" );
- return $wgUser->isAllowed( $permission );
+ if ( $user === null ) {
+ global $wgUser;
+ $user = $wgUser;
+ }
+ return $user->isAllowed( $permission );
} else {
return true;
}
@@ -634,8 +603,8 @@ class LogEventsList {
* Show log extract. Either with text and a box (set $msgKey) or without (don't set $msgKey)
*
* @param $out OutputPage|String-by-reference
- * @param $types String or Array
- * @param $page String The page title to show log entries for
+ * @param $types String|Array Log types to show
+ * @param $page String|Title The page title to show log entries for
* @param $user String The user who made the log entries
* @param $param Associative Array with the following additional options:
* - lim Integer Limit of items to show, default is 50
@@ -644,7 +613,7 @@ class LogEventsList {
* if set to true (default), "No matching items in log" is displayed if loglist is empty
* - msgKey Array If you want a nice box with a message, set this to the key of the message.
* First element is the message key, additional optional elements are parameters for the key
- * that are processed with wgMsgExt and option 'parse'
+ * that are processed with wfMsgExt and option 'parse'
* - offset Set to overwrite offset parameter in $wgRequest
* set to '' to unset offset
* - wrap String Wrap the message in html (usually something like "<div ...>$1</div>").
@@ -654,7 +623,6 @@ class LogEventsList {
public static function showLogExtract(
&$out, $types=array(), $page='', $user='', $param = array()
) {
- global $wgUser, $wgOut;
$defaultParameters = array(
'lim' => 25,
'conds' => array(),
@@ -676,8 +644,15 @@ class LogEventsList {
if ( !is_array( $msgKey ) ) {
$msgKey = array( $msgKey );
}
+
+ if ( $out instanceof OutputPage ) {
+ $context = $out->getContext();
+ } else {
+ $context = RequestContext::getMain();
+ }
+
# Insert list of top 50 (or top $lim) items
- $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, $flags );
+ $loglist = new LogEventsList( $context->getSkin(), $context->getOutput(), $flags );
$pager = new LogPager( $loglist, $types, $user, $page, '', $conds );
if ( isset( $param['offset'] ) ) { # Tell pager to ignore $wgRequest offset
$pager->setOffset( $param['offset'] );
@@ -701,14 +676,18 @@ class LogEventsList {
$logBody .
$loglist->endLogEventsList();
} else {
- if ( $showIfEmpty )
+ if ( $showIfEmpty ) {
$s = Html::rawElement( 'div', array( 'class' => 'mw-warning-logempty' ),
wfMsgExt( 'logempty', array( 'parseinline' ) ) );
+ }
}
if( $pager->getNumRows() > $pager->mLimit ) { # Show "Full log" link
$urlParam = array();
- if ( $page != '')
+ if ( $page instanceof Title ) {
+ $urlParam['page'] = $page->getPrefixedDBkey();
+ } elseif ( $page != '' ) {
$urlParam['page'] = $page;
+ }
if ( $user != '')
$urlParam['user'] = $user;
if ( !is_array( $types ) ) # Make it an array, if it isn't
@@ -727,23 +706,27 @@ class LogEventsList {
$s .= '</div>';
}
- if ( $wrap!='' ) { // Wrap message in html
+ if ( $wrap != '' ) { // Wrap message in html
$s = str_replace( '$1', $s, $wrap );
}
- // $out can be either an OutputPage object or a String-by-reference
- if( $out instanceof OutputPage ){
- $out->addHTML( $s );
- } else {
- $out = $s;
+ /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */
+ if ( wfRunHooks( 'LogEventsListShowLogExtract', array( &$s, $types, $page, $user, $param ) ) ) {
+ // $out can be either an OutputPage object or a String-by-reference
+ if ( $out instanceof OutputPage ){
+ $out->addHTML( $s );
+ } else {
+ $out = $s;
+ }
}
+
return $pager->getNumRows();
}
/**
* SQL clause to skip forbidden log types for this user
*
- * @param $db Database
+ * @param $db DatabaseBase
* @param $audience string, public/user
* @return Mixed: string or false
*/
@@ -765,308 +748,4 @@ class LogEventsList {
}
return false;
}
-}
-
-/**
- * @ingroup Pager
- */
-class LogPager extends ReverseChronologicalPager {
- private $types = array(), $user = '', $title = '', $pattern = '';
- private $typeCGI = '';
- public $mLogEventsList;
-
- /**
- * Constructor
- *
- * @param $list LogEventsList
- * @param $types String or Array: log types to show
- * @param $user String: the user who made the log entries
- * @param $title String: the page title the log entries are for
- * @param $pattern String: do a prefix search rather than an exact title match
- * @param $conds Array: extra conditions for the query
- * @param $year Integer: the year to start from
- * @param $month Integer: the month to start from
- * @param $tagFilter String: tag
- */
- public function __construct( $list, $types = array(), $user = '', $title = '', $pattern = '',
- $conds = array(), $year = false, $month = false, $tagFilter = '' ) {
- parent::__construct();
- $this->mConds = $conds;
-
- $this->mLogEventsList = $list;
-
- $this->limitType( $types ); // also excludes hidden types
- $this->limitUser( $user );
- $this->limitTitle( $title, $pattern );
- $this->getDateCond( $year, $month );
- $this->mTagFilter = $tagFilter;
- }
-
- public function getDefaultQuery() {
- $query = parent::getDefaultQuery();
- $query['type'] = $this->typeCGI; // arrays won't work here
- $query['user'] = $this->user;
- $query['month'] = $this->mMonth;
- $query['year'] = $this->mYear;
- return $query;
- }
-
- function getTitle() {
- return $this->mLogEventsList->getDisplayTitle();
- }
-
- // Call ONLY after calling $this->limitType() already!
- public function getFilterParams() {
- global $wgFilterLogTypes, $wgUser, $wgRequest;
- $filters = array();
- if( count($this->types) ) {
- return $filters;
- }
- foreach( $wgFilterLogTypes as $type => $default ) {
- // Avoid silly filtering
- if( $type !== 'patrol' || $wgUser->useNPPatrol() ) {
- $hide = $wgRequest->getInt( "hide_{$type}_log", $default );
- $filters[$type] = $hide;
- if( $hide )
- $this->mConds[] = 'log_type != ' . $this->mDb->addQuotes( $type );
- }
- }
- return $filters;
- }
-
- /**
- * Set the log reader to return only entries of the given type.
- * Type restrictions enforced here
- *
- * @param $types String or array: Log types ('upload', 'delete', etc);
- * empty string means no restriction
- */
- private function limitType( $types ) {
- global $wgLogRestrictions, $wgUser;
- // If $types is not an array, make it an array
- $types = ($types === '') ? array() : (array)$types;
- // Don't even show header for private logs; don't recognize it...
- foreach ( $types as $type ) {
- if( isset( $wgLogRestrictions[$type] )
- && !$wgUser->isAllowed($wgLogRestrictions[$type])
- ) {
- $types = array_diff( $types, array( $type ) );
- }
- }
- $this->types = $types;
- // Don't show private logs to unprivileged users.
- // Also, only show them upon specific request to avoid suprises.
- $audience = $types ? 'user' : 'public';
- $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience );
- if( $hideLogs !== false ) {
- $this->mConds[] = $hideLogs;
- }
- if( count($types) ) {
- $this->mConds['log_type'] = $types;
- // Set typeCGI; used in url param for paging
- if( count($types) == 1 ) $this->typeCGI = $types[0];
- }
- }
-
- /**
- * Set the log reader to return only entries by the given user.
- *
- * @param $name String: (In)valid user name
- */
- private function limitUser( $name ) {
- if( $name == '' ) {
- return false;
- }
- $usertitle = Title::makeTitleSafe( NS_USER, $name );
- if( is_null($usertitle) ) {
- return false;
- }
- /* Fetch userid at first, if known, provides awesome query plan afterwards */
- $userid = User::idFromName( $name );
- if( !$userid ) {
- /* It should be nicer to abort query at all,
- but for now it won't pass anywhere behind the optimizer */
- $this->mConds[] = "NULL";
- } else {
- global $wgUser;
- $this->mConds['log_user'] = $userid;
- // Paranoia: avoid brute force searches (bug 17342)
- if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
- $this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::DELETED_USER) . ' = 0';
- } elseif( !$wgUser->isAllowed( 'suppressrevision' ) ) {
- $this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::SUPPRESSED_USER) .
- ' != ' . LogPage::SUPPRESSED_USER;
- }
- $this->user = $usertitle->getText();
- }
- }
-
- /**
- * Set the log reader to return only entries affecting the given page.
- * (For the block and rights logs, this is a user page.)
- *
- * @param $page String: Title name as text
- * @param $pattern String
- */
- private function limitTitle( $page, $pattern ) {
- global $wgMiserMode, $wgUser;
-
- $title = Title::newFromText( $page );
- if( strlen( $page ) == 0 || !$title instanceof Title ) {
- return false;
- }
-
- $this->title = $title->getPrefixedText();
- $ns = $title->getNamespace();
- $db = $this->mDb;
-
- # Using the (log_namespace, log_title, log_timestamp) index with a
- # range scan (LIKE) on the first two parts, instead of simple equality,
- # makes it unusable for sorting. Sorted retrieval using another index
- # would be possible, but then we might have to scan arbitrarily many
- # nodes of that index. Therefore, we need to avoid this if $wgMiserMode
- # is on.
- #
- # This is not a problem with simple title matches, because then we can
- # use the page_time index. That should have no more than a few hundred
- # log entries for even the busiest pages, so it can be safely scanned
- # in full to satisfy an impossible condition on user or similar.
- if( $pattern && !$wgMiserMode ) {
- $this->mConds['log_namespace'] = $ns;
- $this->mConds[] = 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() );
- $this->pattern = $pattern;
- } else {
- $this->mConds['log_namespace'] = $ns;
- $this->mConds['log_title'] = $title->getDBkey();
- }
- // Paranoia: avoid brute force searches (bug 17342)
- if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
- $this->mConds[] = $db->bitAnd('log_deleted', LogPage::DELETED_ACTION) . ' = 0';
- } elseif( !$wgUser->isAllowed( 'suppressrevision' ) ) {
- $this->mConds[] = $db->bitAnd('log_deleted', LogPage::SUPPRESSED_ACTION) .
- ' != ' . LogPage::SUPPRESSED_ACTION;
- }
- }
-
- public function getQueryInfo() {
- $tables = array( 'logging', 'user' );
- $this->mConds[] = 'user_id = log_user';
- $index = array();
- $options = array();
- # 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';
- 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.
- } 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';
- }
- } elseif( count($this->types) == 1 ) {
- $index['logging'] = 'type_time';
- } else {
- $index['logging'] = 'times';
- }
- $options['USE INDEX'] = $index;
- # Don't show duplicate rows when using log_search
- $info = array(
- 'tables' => $tables,
- 'fields' => array( 'log_type', 'log_action', 'log_user', 'log_namespace',
- 'log_title', 'log_params', 'log_comment', 'log_id', 'log_deleted',
- 'log_timestamp', 'user_name', 'user_editcount' ),
- 'conds' => $this->mConds,
- 'options' => $options,
- 'join_conds' => array(
- 'user' => array( 'INNER JOIN', 'user_id=log_user' ),
- 'log_search' => array( 'INNER JOIN', 'ls_log_id=log_id' )
- )
- );
- # Add ChangeTags filter query
- ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'],
- $info['join_conds'], $info['options'], $this->mTagFilter );
- return $info;
- }
-
- // Checks if $this->mConds has $field matched to a *single* value
- 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';
- }
-
- public function getStartBody() {
- wfProfileIn( __METHOD__ );
- # Do a link batch query
- if( $this->getNumRows() > 0 ) {
- $lb = new LinkBatch;
- foreach ( $this->mResult as $row ) {
- $lb->add( $row->log_namespace, $row->log_title );
- $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
- $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
- }
- $lb->execute();
- $this->mResult->seek( 0 );
- }
- wfProfileOut( __METHOD__ );
- return '';
- }
-
- public function formatRow( $row ) {
- return $this->mLogEventsList->logLine( $row );
- }
-
- public function getType() {
- return $this->types;
- }
-
- public function getUser() {
- return $this->user;
- }
-
- public function getPage() {
- return $this->title;
- }
-
- public function getPattern() {
- return $this->pattern;
- }
-
- public function getYear() {
- return $this->mYear;
- }
-
- public function getMonth() {
- return $this->mMonth;
- }
-
- public function getTagFilter() {
- return $this->mTagFilter;
- }
-
- public function doQuery() {
- // Workaround MySQL optimizer bug
- $this->mDb->setBigSelects();
- parent::doQuery();
- $this->mDb->setBigSelects( 'default' );
- }
-}
-
+ }
diff --git a/includes/logging/LogFormatter.php b/includes/logging/LogFormatter.php
new file mode 100644
index 00000000..24490eed
--- /dev/null
+++ b/includes/logging/LogFormatter.php
@@ -0,0 +1,673 @@
+<?php
+/**
+ * Contains classes for formatting log entries
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @since 1.19
+ */
+
+/**
+ * Implements the default log formatting.
+ * Can be overridden by subclassing and setting
+ * $wgLogActionsHandlers['type/subtype'] = 'class'; or
+ * $wgLogActionsHandlers['type/*'] = 'class';
+ * @since 1.19
+ */
+class LogFormatter {
+ // Audience options for viewing usernames, comments, and actions
+ const FOR_PUBLIC = 1;
+ const FOR_THIS_USER = 2;
+
+ // Static->
+
+ /**
+ * Constructs a new formatter suitable for given entry.
+ * @param $entry LogEntry
+ * @return LogFormatter
+ */
+ public static function newFromEntry( LogEntry $entry ) {
+ global $wgLogActionsHandlers;
+ $fulltype = $entry->getFullType();
+ $wildcard = $entry->getType() . '/*';
+ $handler = '';
+
+ if ( isset( $wgLogActionsHandlers[$fulltype] ) ) {
+ $handler = $wgLogActionsHandlers[$fulltype];
+ } elseif ( isset( $wgLogActionsHandlers[$wildcard] ) ) {
+ $handler = $wgLogActionsHandlers[$wildcard];
+ }
+
+ if ( $handler !== '' && is_string( $handler ) && class_exists( $handler ) ) {
+ return new $handler( $entry );
+ }
+
+ return new LegacyLogFormatter( $entry );
+ }
+
+ /**
+ * Handy shortcut for constructing a formatter directly from
+ * database row.
+ * @param $row
+ * @see DatabaseLogEntry::getSelectQueryData
+ * @return LogFormatter
+ */
+ public static function newFromRow( $row ) {
+ return self::newFromEntry( DatabaseLogEntry::newFromRow( $row ) );
+ }
+
+ // Nonstatic->
+
+ /// @var LogEntry
+ protected $entry;
+
+ /// Integer constant for handling log_deleted
+ protected $audience = self::FOR_PUBLIC;
+
+ /// Whether to output user tool links
+ protected $linkFlood = false;
+
+ /**
+ * Set to true if we are constructing a message text that is going to
+ * be included in page history or send to IRC feed. Links are replaced
+ * with plaintext or with [[pagename]] kind of syntax, that is parsed
+ * by page histories and IRC feeds.
+ * @var boolean
+ */
+ protected $plaintext = false;
+
+ protected $irctext = false;
+
+ protected function __construct( LogEntry $entry ) {
+ $this->entry = $entry;
+ $this->context = RequestContext::getMain();
+ }
+
+ /**
+ * Replace the default context
+ * @param $context IContextSource
+ */
+ public function setContext( IContextSource $context ) {
+ $this->context = $context;
+ }
+
+ /**
+ * Set the visibility restrictions for displaying content.
+ * If set to public, and an item is deleted, then it will be replaced
+ * with a placeholder even if the context user is allowed to view it.
+ * @param $audience integer self::FOR_THIS_USER or self::FOR_PUBLIC
+ */
+ public function setAudience( $audience ) {
+ $this->audience = ( $audience == self::FOR_THIS_USER )
+ ? self::FOR_THIS_USER
+ : self::FOR_PUBLIC;
+ }
+
+ /**
+ * Check if a log item can be displayed
+ * @param $field integer LogPage::DELETED_* constant
+ * @return bool
+ */
+ protected function canView( $field ) {
+ if ( $this->audience == self::FOR_THIS_USER ) {
+ return LogEventsList::userCanBitfield(
+ $this->entry->getDeleted(), $field, $this->context->getUser() );
+ } else {
+ return !$this->entry->isDeleted( $field );
+ }
+ }
+
+ /**
+ * If set to true, will produce user tool links after
+ * the user name. This should be replaced with generic
+ * CSS/JS solution.
+ * @param $value boolean
+ */
+ public function setShowUserToolLinks( $value ) {
+ $this->linkFlood = $value;
+ }
+
+ /**
+ * Ugly hack to produce plaintext version of the message.
+ * Usually you also want to set extraneous request context
+ * to avoid formatting for any particular user.
+ * @see getActionText()
+ * @return string text
+ */
+ public function getPlainActionText() {
+ $this->plaintext = true;
+ $text = $this->getActionText();
+ $this->plaintext = false;
+ return $text;
+ }
+
+ /**
+ * Even uglier hack to maintain backwards compatibilty with IRC bots
+ * (bug 34508).
+ * @see getActionText()
+ * @return string text
+ */
+ public function getIRCActionText() {
+ $this->plaintext = true;
+ $text = $this->getActionText();
+
+ $entry = $this->entry;
+ $parameters = $entry->getParameters();
+ // @see LogPage::actionText()
+ $msgOpts = array( 'parsemag', 'escape', 'replaceafter', 'content' );
+ // Text of title the action is aimed at.
+ $target = $entry->getTarget()->getPrefixedText() ;
+ $text = null;
+ switch( $entry->getType() ) {
+ case 'move':
+ switch( $entry->getSubtype() ) {
+ case 'move':
+ $movesource = $parameters['4::target'];
+ $text = wfMsgExt( '1movedto2', $msgOpts, $target, $movesource );
+ break;
+ case 'move_redir':
+ $movesource = $parameters['4::target'];
+ $text = wfMsgExt( '1movedto2_redir', $msgOpts, $target, $movesource );
+ break;
+ case 'move-noredirect':
+ break;
+ case 'move_redir-noredirect':
+ break;
+ }
+ break;
+
+ case 'delete':
+ switch( $entry->getSubtype() ) {
+ case 'delete':
+ $text = wfMsgExt( 'deletedarticle', $msgOpts, $target );
+ break;
+ case 'restore':
+ $text = wfMsgExt( 'undeletedarticle', $msgOpts, $target );
+ break;
+ //case 'revision': // Revision deletion
+ //case 'event': // Log deletion
+ // see https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/LogPage.php?&pathrev=97044&r1=97043&r2=97044
+ //default:
+ }
+ break;
+
+ case 'patrol':
+ // https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/PatrolLog.php?&pathrev=97495&r1=97494&r2=97495
+ // Create a diff link to the patrolled revision
+ if ( $entry->getSubtype() === 'patrol' ) {
+ $diffLink = htmlspecialchars(
+ wfMsgForContent( 'patrol-log-diff', $parameters['4::curid'] ) );
+ $text = wfMsgForContent( 'patrol-log-line', $diffLink, "[[$target]]", "" );
+ } else {
+ // broken??
+ }
+ break;
+
+ case 'newusers':
+ switch( $entry->getSubtype() ) {
+ case 'newusers':
+ case 'create':
+ $text = wfMsgExt( 'newuserlog-create-entry', $msgOpts /* no params */ );
+ break;
+ case 'create2':
+ $text = wfMsgExt( 'newuserlog-create2-entry', $msgOpts, $target );
+ break;
+ case 'autocreate':
+ $text = wfMsgExt( 'newuserlog-autocreate-entry', $msgOpts /* no params */ );
+ break;
+ }
+ break;
+
+ case 'upload':
+ switch( $entry->getSubtype() ) {
+ case 'upload':
+ $text = wfMsgExt( 'uploadedimage', $msgOpts, $target );
+ break;
+ case 'overwrite':
+ $text = wfMsgExt( 'overwroteimage', $msgOpts, $target );
+ break;
+ }
+ break;
+
+ // case 'suppress' --private log -- aaron (sign your messages so we know who to blame in a few years :-D)
+ // default:
+ }
+ if( is_null( $text ) ) {
+ $text = $this->getPlainActionText();
+ }
+
+ $this->plaintext = false;
+ return $text;
+ }
+
+ /**
+ * Gets the log action, including username.
+ * @return string HTML
+ */
+ public function getActionText() {
+ if ( $this->canView( LogPage::DELETED_ACTION ) ) {
+ $element = $this->getActionMessage();
+ if ( $element instanceof Message ) {
+ $element = $this->plaintext ? $element->text() : $element->escaped();
+ }
+ if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) ) {
+ $element = $this->styleRestricedElement( $element );
+ }
+ } else {
+ $performer = $this->getPerformerElement() . $this->msg( 'word-separator' )->text();
+ $element = $performer . $this->getRestrictedElement( 'rev-deleted-event' );
+ }
+
+ return $element;
+ }
+
+ /**
+ * Returns a sentence describing the log action. Usually
+ * a Message object is returned, but old style log types
+ * and entries might return pre-escaped html string.
+ * @return Message|pre-escaped html
+ */
+ protected function getActionMessage() {
+ $message = $this->msg( $this->getMessageKey() );
+ $message->params( $this->getMessageParameters() );
+ return $message;
+ }
+
+ /**
+ * Returns a key to be used for formatting the action sentence.
+ * Default is logentry-TYPE-SUBTYPE for modern logs. Legacy log
+ * types will use custom keys, and subclasses can also alter the
+ * key depending on the entry itself.
+ * @return string message key
+ */
+ protected function getMessageKey() {
+ $type = $this->entry->getType();
+ $subtype = $this->entry->getSubtype();
+
+ return "logentry-$type-$subtype";
+ }
+
+ /**
+ * Extracts the optional extra parameters for use in action messages.
+ * The array indexes start from number 3.
+ * @return array
+ */
+ protected function extractParameters() {
+ $entry = $this->entry;
+ $params = array();
+
+ if ( $entry->isLegacy() ) {
+ foreach ( $entry->getParameters() as $index => $value ) {
+ $params[$index + 3] = $value;
+ }
+ }
+
+ // Filter out parameters which are not in format #:foo
+ foreach ( $entry->getParameters() as $key => $value ) {
+ if ( strpos( $key, ':' ) === false ) continue;
+ list( $index, $type, $name ) = explode( ':', $key, 3 );
+ $params[$index - 1] = $value;
+ }
+
+ /* Message class doesn't like non consecutive numbering.
+ * Fill in missing indexes with empty strings to avoid
+ * incorrect renumbering.
+ */
+ if ( count( $params ) ) {
+ $max = max( array_keys( $params ) );
+ for ( $i = 4; $i < $max; $i++ ) {
+ if ( !isset( $params[$i] ) ) {
+ $params[$i] = '';
+ }
+ }
+ }
+ return $params;
+ }
+
+ /**
+ * Formats parameters intented for action message from
+ * array of all parameters. There are three hardcoded
+ * parameters (array is zero-indexed, this list not):
+ * - 1: user name with premade link
+ * - 2: usable for gender magic function
+ * - 3: target page with premade link
+ * @return array
+ */
+ protected function getMessageParameters() {
+ if ( isset( $this->parsedParameters ) ) {
+ return $this->parsedParameters;
+ }
+
+ $entry = $this->entry;
+ $params = $this->extractParameters();
+ $params[0] = Message::rawParam( $this->getPerformerElement() );
+ $params[1] = $entry->getPerformer()->getName();
+ $params[2] = Message::rawParam( $this->makePageLink( $entry->getTarget() ) );
+
+ // Bad things happens if the numbers are not in correct order
+ ksort( $params );
+ return $this->parsedParameters = $params;
+ }
+
+ /**
+ * Helper to make a link to the page, taking the plaintext
+ * value in consideration.
+ * @param $title Title the page
+ * @param $parameters array query parameters
+ * @return String
+ */
+ protected function makePageLink( Title $title = null, $parameters = array() ) {
+ if ( !$this->plaintext ) {
+ $link = Linker::link( $title, null, array(), $parameters );
+ } else {
+ if ( !$title instanceof Title ) {
+ throw new MWException( "Expected title, got null" );
+ }
+ $link = '[[' . $title->getPrefixedText() . ']]';
+ }
+ return $link;
+ }
+
+ /**
+ * Provides the name of the user who performed the log action.
+ * Used as part of log action message or standalone, depending
+ * which parts of the log entry has been hidden.
+ */
+ public function getPerformerElement() {
+ if ( $this->canView( LogPage::DELETED_USER ) ) {
+ $performer = $this->entry->getPerformer();
+ $element = $this->makeUserLink( $performer );
+ if ( $this->entry->isDeleted( LogPage::DELETED_USER ) ) {
+ $element = $this->styleRestricedElement( $element );
+ }
+ } else {
+ $element = $this->getRestrictedElement( 'rev-deleted-user' );
+ }
+
+ return $element;
+ }
+
+ /**
+ * Gets the luser provided comment
+ * @return string HTML
+ */
+ public function getComment() {
+ if ( $this->canView( LogPage::DELETED_COMMENT ) ) {
+ $comment = Linker::commentBlock( $this->entry->getComment() );
+ // No hard coded spaces thanx
+ $element = ltrim( $comment );
+ if ( $this->entry->isDeleted( LogPage::DELETED_COMMENT ) ) {
+ $element = $this->styleRestricedElement( $element );
+ }
+ } else {
+ $element = $this->getRestrictedElement( 'rev-deleted-comment' );
+ }
+
+ return $element;
+ }
+
+ /**
+ * Helper method for displaying restricted element.
+ * @param $message string
+ * @return string HTML or wikitext
+ */
+ protected function getRestrictedElement( $message ) {
+ if ( $this->plaintext ) {
+ return $this->msg( $message )->text();
+ }
+
+ $content = $this->msg( $message )->escaped();
+ $attribs = array( 'class' => 'history-deleted' );
+ return Html::rawElement( 'span', $attribs, $content );
+ }
+
+ /**
+ * Helper method for styling restricted element.
+ * @param $content string
+ * @return string HTML or wikitext
+ */
+ protected function styleRestricedElement( $content ) {
+ if ( $this->plaintext ) {
+ return $content;
+ }
+ $attribs = array( 'class' => 'history-deleted' );
+ return Html::rawElement( 'span', $attribs, $content );
+ }
+
+ /**
+ * Shortcut for wfMessage which honors local context.
+ * @todo Would it be better to require replacing the global context instead?
+ * @param $key string
+ * @return Message
+ */
+ protected function msg( $key ) {
+ return wfMessage( $key )
+ ->inLanguage( $this->context->getLanguage() )
+ ->title( $this->context->getTitle() );
+ }
+
+ protected function makeUserLink( User $user ) {
+ if ( $this->plaintext ) {
+ $element = $user->getName();
+ } else {
+ $element = Linker::userLink(
+ $user->getId(),
+ $user->getName()
+ );
+
+ if ( $this->linkFlood ) {
+ $element .= Linker::userToolLinks(
+ $user->getId(),
+ $user->getName(),
+ true, // Red if no edits
+ 0, // Flags
+ $user->getEditCount()
+ );
+ }
+ }
+ return $element;
+ }
+
+ /**
+ * @return Array of titles that should be preloaded with LinkBatch.
+ */
+ public function getPreloadTitles() {
+ return array();
+ }
+
+}
+
+/**
+ * This class formats all log entries for log types
+ * which have not been converted to the new system.
+ * This is not about old log entries which store
+ * parameters in a different format - the new
+ * LogFormatter classes have code to support formatting
+ * those too.
+ * @since 1.19
+ */
+class LegacyLogFormatter extends LogFormatter {
+ protected function getActionMessage() {
+ $entry = $this->entry;
+ $action = LogPage::actionText(
+ $entry->getType(),
+ $entry->getSubtype(),
+ $entry->getTarget(),
+ $this->plaintext ? null : $this->context->getSkin(),
+ (array)$entry->getParameters(),
+ !$this->plaintext // whether to filter [[]] links
+ );
+
+ $performer = $this->getPerformerElement();
+ return $performer . $this->msg( 'word-separator' )->text() . $action;
+ }
+
+}
+
+/**
+ * This class formats move log entries.
+ * @since 1.19
+ */
+class MoveLogFormatter extends LogFormatter {
+ public function getPreloadTitles() {
+ $params = $this->extractParameters();
+ return array( Title::newFromText( $params[3] ) );
+ }
+
+ protected function getMessageKey() {
+ $key = parent::getMessageKey();
+ $params = $this->getMessageParameters();
+ if ( isset( $params[4] ) && $params[4] === '1' ) {
+ $key .= '-noredirect';
+ }
+ return $key;
+ }
+
+ protected function getMessageParameters() {
+ $params = parent::getMessageParameters();
+ $oldname = $this->makePageLink( $this->entry->getTarget(), array( 'redirect' => 'no' ) );
+ $newname = $this->makePageLink( Title::newFromText( $params[3] ) );
+ $params[2] = Message::rawParam( $oldname );
+ $params[3] = Message::rawParam( $newname );
+ return $params;
+ }
+}
+
+/**
+ * This class formats delete log entries.
+ * @since 1.19
+ */
+class DeleteLogFormatter extends LogFormatter {
+ protected function getMessageKey() {
+ $key = parent::getMessageKey();
+ if ( in_array( $this->entry->getSubtype(), array( 'event', 'revision' ) ) ) {
+ if ( count( $this->getMessageParameters() ) < 5 ) {
+ return "$key-legacy";
+ }
+ }
+ return $key;
+ }
+
+ protected function getMessageParameters() {
+ if ( isset( $this->parsedParametersDeleteLog ) ) {
+ return $this->parsedParametersDeleteLog;
+ }
+
+ $params = parent::getMessageParameters();
+ $subtype = $this->entry->getSubtype();
+ if ( in_array( $subtype, array( 'event', 'revision' ) ) ) {
+ if (
+ ($subtype === 'event' && count( $params ) === 6 ) ||
+ ($subtype === 'revision' && isset( $params[3] ) && $params[3] === 'revision' )
+ ) {
+ $paramStart = $subtype === 'revision' ? 4 : 3;
+
+ $old = $this->parseBitField( $params[$paramStart+1] );
+ $new = $this->parseBitField( $params[$paramStart+2] );
+ list( $hid, $unhid, $extra ) = RevisionDeleter::getChanges( $new, $old );
+ $changes = array();
+ foreach ( $hid as $v ) {
+ $changes[] = $this->msg( "$v-hid" )->plain();
+ }
+ foreach ( $unhid as $v ) {
+ $changes[] = $this->msg( "$v-unhid" )->plain();
+ }
+ foreach ( $extra as $v ) {
+ $changes[] = $this->msg( $v )->plain();
+ }
+ $changeText = $this->context->getLanguage()->listToText( $changes );
+
+
+ $newParams = array_slice( $params, 0, 3 );
+ $newParams[3] = $changeText;
+ $count = count( explode( ',', $params[$paramStart] ) );
+ $newParams[4] = $this->context->getLanguage()->formatNum( $count );
+ return $this->parsedParametersDeleteLog = $newParams;
+ } else {
+ return $this->parsedParametersDeleteLog = array_slice( $params, 0, 3 );
+ }
+ }
+
+ return $this->parsedParametersDeleteLog = $params;
+ }
+
+ protected function parseBitField( $string ) {
+ // Input is like ofield=2134 or just the number
+ if ( strpos( $string, 'field=' ) === 1 ) {
+ list( , $field ) = explode( '=', $string );
+ return (int) $field;
+ } else {
+ return (int) $string;
+ }
+ }
+}
+
+/**
+ * This class formats patrol log entries.
+ * @since 1.19
+ */
+class PatrolLogFormatter extends LogFormatter {
+ protected function getMessageKey() {
+ $key = parent::getMessageKey();
+ $params = $this->getMessageParameters();
+ if ( isset( $params[5] ) && $params[5] ) {
+ $key .= '-auto';
+ }
+ return $key;
+ }
+
+ protected function getMessageParameters() {
+ $params = parent::getMessageParameters();
+ $newParams = array_slice( $params, 0, 3 );
+
+ $target = $this->entry->getTarget();
+ $oldid = $params[3];
+ $revision = $this->context->getLanguage()->formatNum( $oldid, true );
+
+ if ( $this->plaintext ) {
+ $revlink = $revision;
+ } elseif ( $target->exists() ) {
+ $query = array(
+ 'oldid' => $oldid,
+ 'diff' => 'prev'
+ );
+ $revlink = Linker::link( $target, htmlspecialchars( $revision ), array(), $query );
+ } else {
+ $revlink = htmlspecialchars( $revision );
+ }
+
+ $newParams[3] = Message::rawParam( $revlink );
+ return $newParams;
+ }
+}
+
+/**
+ * This class formats new user log entries.
+ * @since 1.19
+ */
+class NewUsersLogFormatter extends LogFormatter {
+ protected function getMessageParameters() {
+ $params = parent::getMessageParameters();
+ if ( $this->entry->getSubtype() === 'create2' ) {
+ if ( isset( $params[3] ) ) {
+ $target = User::newFromId( $params[3] );
+ } else {
+ $target = User::newFromName( $this->entry->getTarget()->getText(), false );
+ }
+ $params[2] = Message::rawParam( $this->makeUserLink( $target ) );
+ $params[3] = $target->getName();
+ }
+ return $params;
+ }
+
+ public function getComment() {
+ $timestamp = wfTimestamp( TS_MW, $this->entry->getTimestamp() );
+ if ( $timestamp < '20080129000000' ) {
+ # Suppress $comment from old entries (before 2008-01-29),
+ # not needed and can contain incorrect links
+ return '';
+ }
+ return parent::getComment();
+ }
+}
diff --git a/includes/LogPage.php b/includes/logging/LogPage.php
index 5155d9a5..bbb4de8f 100644
--- a/includes/LogPage.php
+++ b/includes/logging/LogPage.php
@@ -96,6 +96,7 @@ class LogPage {
# And update recentchanges
if( $this->updateRecentChanges ) {
$titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
+
RecentChange::notifyLog(
$now, $titleObj, $this->doer, $this->getRcComment(), '',
$this->type, $this->action, $this->target, $this->comment,
@@ -106,6 +107,7 @@ class LogPage {
if( isset( $wgLogRestrictions[$this->type] ) && $wgLogRestrictions[$this->type] != '*' ) {
return true;
}
+
# Notify external application via UDP.
# We send this to IRC but do not want to add it the RC table.
$titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
@@ -121,9 +123,12 @@ class LogPage {
/**
* Get the RC comment from the last addEntry() call
+ *
+ * @return string
*/
public function getRcComment() {
$rcComment = $this->actionText;
+
if( $this->comment != '' ) {
if ( $rcComment == '' ) {
$rcComment = $this->comment;
@@ -131,6 +136,7 @@ class LogPage {
$rcComment .= wfMsgForContent( 'colon-separator' ) . $this->comment;
}
}
+
return $rcComment;
}
@@ -166,8 +172,10 @@ class LogPage {
*
* @param $type String: logtype
* @return String: log name
+ * @deprecated in 1.19, warnings in 1.21. Use getName()
*/
public static function logName( $type ) {
+ wfDeprecated( __METHOD__, '1.19' );
global $wgLogNames;
if( isset( $wgLogNames[$type] ) ) {
@@ -184,14 +192,17 @@ class LogPage {
* @todo handle missing log types
* @param $type String: logtype
* @return String: headertext of this logtype
+ * @deprecated in 1.19, warnings in 1.21. Use getDescription()
*/
public static function logHeader( $type ) {
+ wfDeprecated( __METHOD__, '1.19' );
global $wgLogHeaders;
return wfMsgExt( $wgLogHeaders[$type], array( 'parseinline' ) );
}
/**
- * Generate text for a log entry
+ * Generate text for a log entry.
+ * Only LogFormatter should call this function.
*
* @param $type String: log type
* @param $action String: log action
@@ -216,17 +227,16 @@ class LogPage {
}
$key = "$type/$action";
- # Defer patrol log to PatrolLog class
- if( $key == 'patrol/patrol' ) {
- return PatrolLog::makeActionText( $title, $params, $langObjOrNull );
- }
+
if( isset( $wgLogActions[$key] ) ) {
if( is_null( $title ) ) {
$rv = wfMsgExt( $wgLogActions[$key], array( 'parsemag', 'escape', 'language' => $langObj ) );
} else {
$titleLink = self::getTitleLink( $type, $langObjOrNull, $title, $params );
+
if( preg_match( '/^rights\/(rights|autopromote)/', $key ) ) {
$rightsnone = wfMsgExt( 'rightsnone', array( 'parsemag', 'language' => $langObj ) );
+
if( $skin ) {
foreach ( $params as &$param ) {
$groupArray = array_map( 'trim', explode( ',', $param ) );
@@ -234,18 +244,22 @@ class LogPage {
$param = $wgLang->listToText( $groupArray );
}
}
+
if( !isset( $params[0] ) || trim( $params[0] ) == '' ) {
$params[0] = $rightsnone;
}
+
if( !isset( $params[1] ) || trim( $params[1] ) == '' ) {
$params[1] = $rightsnone;
}
}
+
if( count( $params ) == 0 ) {
$rv = wfMsgExt( $wgLogActions[$key], array( 'parsemag', 'escape', 'replaceafter', 'language' => $langObj ), $titleLink );
} else {
$details = '';
array_unshift( $params, $titleLink );
+
// User suppression
if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) {
if ( $skin ) {
@@ -254,9 +268,9 @@ class LogPage {
} else {
$params[1] = $wgContLang->translateBlockExpiry( $params[1] );
}
+
$params[2] = isset( $params[2] ) ?
self::formatBlockFlags( $params[2], $langObj ) : '';
-
// Page protections
} elseif ( $type == 'protect' && count($params) == 3 ) {
// Restrictions and expiries
@@ -265,30 +279,11 @@ class LogPage {
} else {
$details .= " {$params[1]}";
}
+
// Cascading flag...
if( $params[2] ) {
$details .= ' [' . wfMsgExt( 'protect-summary-cascade', array( 'parsemag', 'language' => $langObj ) ) . ']';
}
-
- // Page moves
- } elseif ( $type == 'move' && count( $params ) == 3 ) {
- if( $params[2] ) {
- $details .= ' [' . wfMsgExt( 'move-redirect-suppressed', array( 'parsemag', 'language' => $langObj ) ) . ']';
- }
-
- // Revision deletion
- } elseif ( preg_match( '/^(delete|suppress)\/revision$/', $key ) && count( $params ) == 5 ) {
- $count = substr_count( $params[2], ',' ) + 1; // revisions
- $ofield = intval( substr( $params[3], 7 ) ); // <ofield=x>
- $nfield = intval( substr( $params[4], 7 ) ); // <nfield=x>
- $details .= ': ' . RevisionDeleter::getLogMessage( $count, $nfield, $ofield, $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, $langObj, true );
}
$rv = wfMsgExt( $wgLogActions[$key], array( 'parsemag', 'escape', 'replaceafter', 'language' => $langObj ), $params ) . $details;
@@ -296,6 +291,7 @@ class LogPage {
}
} else {
global $wgLogActionsHandlers;
+
if( isset( $wgLogActionsHandlers[$key] ) ) {
$args = func_get_args();
$rv = call_user_func_array( $wgLogActionsHandlers[$key], $args );
@@ -319,6 +315,7 @@ class LogPage {
$rv = str_replace( '[[', '', $rv );
$rv = str_replace( ']]', '', $rv );
}
+
return $rv;
}
@@ -332,9 +329,11 @@ class LogPage {
*/
protected static function getTitleLink( $type, $lang, $title, &$params ) {
global $wgContLang, $wgUserrightsInterwikiDelimiter;
+
if( !$lang ) {
return $title->getPrefixedText();
}
+
switch( $type ) {
case 'move':
$titleLink = Linker::link(
@@ -343,7 +342,9 @@ class LogPage {
array(),
array( 'redirect' => 'no' )
);
+
$targetTitle = Title::newFromText( $params[0] );
+
if ( !$targetTitle ) {
# Workaround for broken database
$params[0] = htmlspecialchars( $params[0] );
@@ -358,7 +359,7 @@ class LogPage {
if( substr( $title->getText(), 0, 1 ) == '#' ) {
$titleLink = $title->getText();
} else {
- // TODO: Store the user identifier in the parameters
+ // @todo Store the user identifier in the parameters
// to make this faster for future log entries
$id = User::idFromName( $title->getText() );
$titleLink = Linker::userLink( $id, $title->getText() )
@@ -368,9 +369,11 @@ class LogPage {
case 'rights':
$text = $wgContLang->ucfirst( $title->getText() );
$parts = explode( $wgUserrightsInterwikiDelimiter, $text, 2 );
+
if ( count( $parts ) == 2 ) {
$titleLink = WikiMap::foreignUserLink( $parts[1], $parts[0],
htmlspecialchars( $title->getPrefixedText() ) );
+
if ( $titleLink !== false ) {
break;
}
@@ -391,8 +394,9 @@ class LogPage {
$params[1] = $lang->timeanddate( $params[1] );
break;
default:
- if( $title->getNamespace() == NS_SPECIAL ) {
+ if( $title->isSpecialPage() ) {
list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
+
# Use the language name for log titles, rather than Log/X
if( $name == 'Log' ) {
$titleLink = '(' . Linker::link( $title, LogPage::logName( $par ) ) . ')';
@@ -403,6 +407,7 @@ class LogPage {
$titleLink = Linker::link( $title );
}
}
+
return $titleLink;
}
@@ -414,8 +419,13 @@ class LogPage {
* @param $comment String: description associated
* @param $params Array: parameters passed later to wfMsg.* functions
* @param $doer User object: the user doing the action
+ *
+ * @return bool|int|null
+ * @TODO: make this use LogEntry::saveContent()
*/
public function addEntry( $action, $target, $comment, $params = array(), $doer = null ) {
+ global $wgContLang;
+
if ( !is_array( $params ) ) {
$params = array( $params );
}
@@ -424,6 +434,9 @@ class LogPage {
$comment = '';
}
+ # Truncate for whole multibyte characters.
+ $comment = $wgContLang->truncate( $comment, 255 );
+
$this->action = $action;
$this->target = $target;
$this->comment = $comment;
@@ -438,7 +451,16 @@ class LogPage {
$this->doer = $doer;
- $this->actionText = LogPage::actionText( $this->type, $action, $target, null, $params );
+ $logEntry = new ManualLogEntry( $this->type, $action );
+ $logEntry->setTarget( $target );
+ $logEntry->setPerformer( $doer );
+ $logEntry->setParameters( $params );
+
+ $formatter = LogFormatter::newFromEntry( $logEntry );
+ $context = RequestContext::newExtraneousContext( $target );
+ $formatter->setContext( $context );
+
+ $this->actionText = $formatter->getPlainActionText();
return $this->saveContent();
}
@@ -455,7 +477,9 @@ class LogPage {
if( !strlen( $field ) || empty( $values ) ) {
return false; // nothing
}
+
$data = array();
+
foreach( $values as $value ) {
$data[] = array(
'ls_field' => $field,
@@ -463,8 +487,10 @@ class LogPage {
'ls_log_id' => $logid
);
}
+
$dbw = wfGetDB( DB_MASTER );
$dbw->insert( 'log_search', $data, __METHOD__, 'IGNORE' );
+
return true;
}
@@ -502,6 +528,7 @@ class LogPage {
*/
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], $lang );
@@ -521,22 +548,87 @@ class LogPage {
*/
public static function formatBlockFlag( $flag, $lang ) {
static $messages = array();
+
if( !isset( $messages[$flag] ) ) {
$messages[$flag] = htmlspecialchars( $flag ); // Fallback
+ // For grepping. The following core messages can be used here:
+ // * block-log-flags-angry-autoblock
+ // * block-log-flags-anononly
+ // * block-log-flags-hiddenname
+ // * block-log-flags-noautoblock
+ // * block-log-flags-nocreate
+ // * block-log-flags-noemail
+ // * block-log-flags-nousertalk
$msg = wfMessage( 'block-log-flags-' . $flag )->inLanguage( $lang );
+
if ( $msg->exists() ) {
$messages[$flag] = $msg->escaped();
}
}
+
return $messages[$flag];
}
-}
-/**
- * Aliases for backwards compatibility with 1.6
- */
-define( 'MW_LOG_DELETED_ACTION', LogPage::DELETED_ACTION );
-define( 'MW_LOG_DELETED_USER', LogPage::DELETED_USER );
-define( 'MW_LOG_DELETED_COMMENT', LogPage::DELETED_COMMENT );
-define( 'MW_LOG_DELETED_RESTRICTED', LogPage::DELETED_RESTRICTED );
+
+ /**
+ * Name of the log.
+ * @return Message
+ * @since 1.19
+ */
+ public function getName() {
+ global $wgLogNames;
+
+ // BC
+ if ( isset( $wgLogNames[$this->type] ) ) {
+ $key = $wgLogNames[$this->type];
+ } else {
+ $key = 'log-name-' . $this->type;
+ }
+
+ return wfMessage( $key );
+ }
+
+ /**
+ * Description of this log type.
+ * @return Message
+ * @since 1.19
+ */
+ public function getDescription() {
+ global $wgLogHeaders;
+ // BC
+ if ( isset( $wgLogHeaders[$this->type] ) ) {
+ $key = $wgLogHeaders[$this->type];
+ } else {
+ $key = 'log-description-' . $this->type;
+ }
+ return wfMessage( $key );
+ }
+
+ /**
+ * Returns the right needed to read this log type.
+ * @return string
+ * @since 1.19
+ */
+ public function getRestriction() {
+ global $wgLogRestrictions;
+ if ( isset( $wgLogRestrictions[$this->type] ) ) {
+ $restriction = $wgLogRestrictions[$this->type];
+ } else {
+ // '' always returns true with $user->isAllowed()
+ $restriction = '';
+ }
+ return $restriction;
+ }
+
+ /**
+ * Tells if this log is not viewable by all.
+ * @return bool
+ * @since 1.19
+ */
+ public function isRestricted() {
+ $restriction = $this->getRestriction();
+ return $restriction !== '' && $restriction !== '*';
+ }
+
+}
diff --git a/includes/logging/LogPager.php b/includes/logging/LogPager.php
new file mode 100644
index 00000000..16781a6e
--- /dev/null
+++ b/includes/logging/LogPager.php
@@ -0,0 +1,356 @@
+<?php
+/**
+ * Contain classes to list log entries
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>, 2008 Aaron Schulz
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * @ingroup Pager
+ */
+class LogPager extends ReverseChronologicalPager {
+ private $types = array(), $performer = '', $title = '', $pattern = '';
+ private $typeCGI = '';
+ public $mLogEventsList;
+
+ /**
+ * Constructor
+ *
+ * @param $list LogEventsList
+ * @param $types String or Array: log types to show
+ * @param $performer String: the user who made the log entries
+ * @param $title String|Title: the page title the log entries are for
+ * @param $pattern String: do a prefix search rather than an exact title match
+ * @param $conds Array: extra conditions for the query
+ * @param $year Integer: the year to start from
+ * @param $month Integer: the month to start from
+ * @param $tagFilter String: tag
+ */
+ public function __construct( $list, $types = array(), $performer = '', $title = '', $pattern = '',
+ $conds = array(), $year = false, $month = false, $tagFilter = '' ) {
+ parent::__construct( $list->getContext() );
+ $this->mConds = $conds;
+
+ $this->mLogEventsList = $list;
+
+ $this->limitType( $types ); // also excludes hidden types
+ $this->limitPerformer( $performer );
+ $this->limitTitle( $title, $pattern );
+ $this->getDateCond( $year, $month );
+ $this->mTagFilter = $tagFilter;
+ }
+
+ public function getDefaultQuery() {
+ $query = parent::getDefaultQuery();
+ $query['type'] = $this->typeCGI; // arrays won't work here
+ $query['user'] = $this->performer;
+ $query['month'] = $this->mMonth;
+ $query['year'] = $this->mYear;
+ return $query;
+ }
+
+ // Call ONLY after calling $this->limitType() already!
+ public function getFilterParams() {
+ global $wgFilterLogTypes;
+ $filters = array();
+ if( count($this->types) ) {
+ return $filters;
+ }
+ foreach( $wgFilterLogTypes as $type => $default ) {
+ // Avoid silly filtering
+ if( $type !== 'patrol' || $this->getUser()->useNPPatrol() ) {
+ $hide = $this->getRequest()->getInt( "hide_{$type}_log", $default );
+ $filters[$type] = $hide;
+ if( $hide )
+ $this->mConds[] = 'log_type != ' . $this->mDb->addQuotes( $type );
+ }
+ }
+ return $filters;
+ }
+
+ /**
+ * Set the log reader to return only entries of the given type.
+ * Type restrictions enforced here
+ *
+ * @param $types String or array: Log types ('upload', 'delete', etc);
+ * empty string means no restriction
+ */
+ private function limitType( $types ) {
+ global $wgLogRestrictions;
+ // If $types is not an array, make it an array
+ $types = ($types === '') ? array() : (array)$types;
+ // Don't even show header for private logs; don't recognize it...
+ $needReindex = false;
+ foreach ( $types as $type ) {
+ if( isset( $wgLogRestrictions[$type] )
+ && !$this->getUser()->isAllowed($wgLogRestrictions[$type])
+ ) {
+ $needReindex = true;
+ $types = array_diff( $types, array( $type ) );
+ }
+ }
+ if ( $needReindex ) {
+ // Lots of this code makes assumptions that
+ // the first entry in the array is $types[0].
+ $types = array_values( $types );
+ }
+ $this->types = $types;
+ // Don't show private logs to unprivileged users.
+ // Also, only show them upon specific request to avoid suprises.
+ $audience = $types ? 'user' : 'public';
+ $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience );
+ if( $hideLogs !== false ) {
+ $this->mConds[] = $hideLogs;
+ }
+ if( count($types) ) {
+ $this->mConds['log_type'] = $types;
+ // Set typeCGI; used in url param for paging
+ if( count($types) == 1 ) $this->typeCGI = $types[0];
+ }
+ }
+
+ /**
+ * Set the log reader to return only entries by the given user.
+ *
+ * @param $name String: (In)valid user name
+ */
+ private function limitPerformer( $name ) {
+ if( $name == '' ) {
+ return false;
+ }
+ $usertitle = Title::makeTitleSafe( NS_USER, $name );
+ if( is_null($usertitle) ) {
+ return false;
+ }
+ /* Fetch userid at first, if known, provides awesome query plan afterwards */
+ $userid = User::idFromName( $name );
+ if( !$userid ) {
+ /* It should be nicer to abort query at all,
+ but for now it won't pass anywhere behind the optimizer */
+ $this->mConds[] = "NULL";
+ } else {
+ $this->mConds['log_user'] = $userid;
+ // Paranoia: avoid brute force searches (bug 17342)
+ $user = $this->getUser();
+ if( !$user->isAllowed( 'deletedhistory' ) ) {
+ $this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::DELETED_USER) . ' = 0';
+ } elseif( !$user->isAllowed( 'suppressrevision' ) ) {
+ $this->mConds[] = $this->mDb->bitAnd('log_deleted', LogPage::SUPPRESSED_USER) .
+ ' != ' . LogPage::SUPPRESSED_USER;
+ }
+ $this->performer = $usertitle->getText();
+ }
+ }
+
+ /**
+ * Set the log reader to return only entries affecting the given page.
+ * (For the block and rights logs, this is a user page.)
+ *
+ * @param $page String or Title object: Title name
+ * @param $pattern String
+ */
+ private function limitTitle( $page, $pattern ) {
+ global $wgMiserMode;
+
+ if ( $page instanceof Title ) {
+ $title = $page;
+ } else {
+ $title = Title::newFromText( $page );
+ if( strlen( $page ) == 0 || !$title instanceof Title ) {
+ return false;
+ }
+ }
+
+ $this->title = $title->getPrefixedText();
+ $ns = $title->getNamespace();
+ $db = $this->mDb;
+
+ # Using the (log_namespace, log_title, log_timestamp) index with a
+ # range scan (LIKE) on the first two parts, instead of simple equality,
+ # makes it unusable for sorting. Sorted retrieval using another index
+ # would be possible, but then we might have to scan arbitrarily many
+ # nodes of that index. Therefore, we need to avoid this if $wgMiserMode
+ # is on.
+ #
+ # This is not a problem with simple title matches, because then we can
+ # use the page_time index. That should have no more than a few hundred
+ # log entries for even the busiest pages, so it can be safely scanned
+ # in full to satisfy an impossible condition on user or similar.
+ if( $pattern && !$wgMiserMode ) {
+ $this->mConds['log_namespace'] = $ns;
+ $this->mConds[] = 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() );
+ $this->pattern = $pattern;
+ } else {
+ $this->mConds['log_namespace'] = $ns;
+ $this->mConds['log_title'] = $title->getDBkey();
+ }
+ // Paranoia: avoid brute force searches (bug 17342)
+ $user = $this->getUser();
+ if( !$user->isAllowed( 'deletedhistory' ) ) {
+ $this->mConds[] = $db->bitAnd('log_deleted', LogPage::DELETED_ACTION) . ' = 0';
+ } elseif( !$user->isAllowed( 'suppressrevision' ) ) {
+ $this->mConds[] = $db->bitAnd('log_deleted', LogPage::SUPPRESSED_ACTION) .
+ ' != ' . LogPage::SUPPRESSED_ACTION;
+ }
+ }
+
+ /**
+ * Constructs the most part of the query. Extra conditions are sprinkled in
+ * all over this class.
+ * @return array
+ */
+ public function getQueryInfo() {
+ $basic = DatabaseLogEntry::getSelectQueryData();
+
+ $tables = $basic['tables'];
+ $fields = $basic['fields'];
+ $conds = $basic['conds'];
+ $options = $basic['options'];
+ $joins = $basic['join_conds'];
+
+ $index = array();
+ # 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';
+ 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.
+ } elseif( $this->title || $this->pattern || $this->performer ) {
+ $index['logging'] = array( 'page_time', 'user_time' );
+ if( count($this->types) == 1 ) {
+ $index['logging'][] = 'log_user_type_time';
+ }
+ } elseif( count($this->types) == 1 ) {
+ $index['logging'] = 'type_time';
+ } else {
+ $index['logging'] = 'times';
+ }
+ $options['USE INDEX'] = $index;
+ # Don't show duplicate rows when using log_search
+ $joins['log_search'] = array( 'INNER JOIN', 'ls_log_id=log_id' );
+
+ $info = array(
+ 'tables' => $tables,
+ 'fields' => $fields,
+ 'conds' => array_merge( $conds, $this->mConds ),
+ 'options' => $options,
+ 'join_conds' => $joins,
+ );
+ # Add ChangeTags filter query
+ ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'],
+ $info['join_conds'], $info['options'], $this->mTagFilter );
+ return $info;
+ }
+
+ /**
+ * Checks if $this->mConds has $field matched to a *single* value
+ * @param $field
+ * @return bool
+ */
+ 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';
+ }
+
+ public function getStartBody() {
+ wfProfileIn( __METHOD__ );
+ # Do a link batch query
+ if( $this->getNumRows() > 0 ) {
+ $lb = new LinkBatch;
+ foreach ( $this->mResult as $row ) {
+ $lb->add( $row->log_namespace, $row->log_title );
+ $lb->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
+ $lb->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
+ $formatter = LogFormatter::newFromRow( $row );
+ foreach ( $formatter->getPreloadTitles() as $title ) {
+ $lb->addObj( $title );
+ }
+ }
+ $lb->execute();
+ $this->mResult->seek( 0 );
+ }
+ wfProfileOut( __METHOD__ );
+ return '';
+ }
+
+ public function formatRow( $row ) {
+ return $this->mLogEventsList->logLine( $row );
+ }
+
+ public function getType() {
+ return $this->types;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPerformer() {
+ return $this->performer;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPage() {
+ return $this->title;
+ }
+
+ public function getPattern() {
+ return $this->pattern;
+ }
+
+ public function getYear() {
+ return $this->mYear;
+ }
+
+ public function getMonth() {
+ return $this->mMonth;
+ }
+
+ public function getTagFilter() {
+ return $this->mTagFilter;
+ }
+
+ public function doQuery() {
+ // Workaround MySQL optimizer bug
+ $this->mDb->setBigSelects();
+ parent::doQuery();
+ $this->mDb->setBigSelects( 'default' );
+ }
+}
diff --git a/includes/logging/PatrolLog.php b/includes/logging/PatrolLog.php
new file mode 100644
index 00000000..04fdc4f2
--- /dev/null
+++ b/includes/logging/PatrolLog.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Class containing static functions for working with
+ * logs of patrol events
+ *
+ * @author Rob Church <robchur@gmail.com>
+ * @author Niklas Laxström
+ */
+class PatrolLog {
+
+ /**
+ * Record a log event for a change being patrolled
+ *
+ * @param $rc Mixed: change identifier or RecentChange object
+ * @param $auto Boolean: was this patrol event automatic?
+ *
+ * @return bool
+ */
+ public static function record( $rc, $auto = false ) {
+ if ( !$rc instanceof RecentChange ) {
+ $rc = RecentChange::newFromId( $rc );
+ if ( !is_object( $rc ) ) {
+ return false;
+ }
+ }
+
+ $title = Title::makeTitleSafe( $rc->getAttribute( 'rc_namespace' ), $rc->getAttribute( 'rc_title' ) );
+ if( $title ) {
+ $entry = new ManualLogEntry( 'patrol', 'patrol' );
+ $entry->setTarget( $title );
+ $entry->setParameters( self::buildParams( $rc, $auto ) );
+ $entry->setPerformer( User::newFromName( $rc->getAttribute( 'rc_user_text' ), false ) );
+ $logid = $entry->insert();
+ if ( !$auto ) {
+ $entry->publish( $logid, 'udp' );
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Prepare log parameters for a patrolled change
+ *
+ * @param $change RecentChange to represent
+ * @param $auto Boolean: whether the patrol event was automatic
+ * @return Array
+ */
+ private static function buildParams( $change, $auto ) {
+ return array(
+ '4::curid' => $change->getAttribute( 'rc_this_oldid' ),
+ '5::previd' => $change->getAttribute( 'rc_last_oldid' ),
+ '6::auto' => (int)$auto
+ );
+ }
+
+}
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
index 3a66d8c9..619485cc 100644
--- a/includes/media/Bitmap.php
+++ b/includes/media/Bitmap.php
@@ -12,7 +12,6 @@
* @ingroup Media
*/
class BitmapHandler extends ImageHandler {
-
/**
* @param $image File
* @param $params array Transform parameters. Entries with the keys 'width'
@@ -21,12 +20,10 @@ class BitmapHandler extends ImageHandler {
* @return bool
*/
function normaliseParams( $image, &$params ) {
- global $wgMaxImageArea;
if ( !parent::normaliseParams( $image, $params ) ) {
return false;
}
- $mimeType = $image->getMimeType();
# Obtain the source, pre-rotation dimensions
$srcWidth = $image->getWidth( $params['page'] );
$srcHeight = $image->getHeight( $params['page'] );
@@ -43,19 +40,27 @@ class BitmapHandler extends ImageHandler {
}
}
- # 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.
- # @todo FIXME: This actually only applies to ImageMagick
- if ( $mimeType !== 'image/jpeg' &&
- $srcWidth * $srcHeight > $wgMaxImageArea )
- {
- return false;
+ # Check if the file is smaller than the maximum image area for thumbnailing
+ $checkImageAreaHookResult = null;
+ wfRunHooks( 'BitmapHandlerCheckImageArea', array( $image, &$params, &$checkImageAreaHookResult ) );
+ if ( is_null( $checkImageAreaHookResult ) ) {
+ global $wgMaxImageArea;
+
+ if ( $srcWidth * $srcHeight > $wgMaxImageArea &&
+ !( $image->getMimeType() == 'image/jpeg' &&
+ self::getScalerType( false, false ) == 'im' ) ) {
+ # Only ImageMagick can efficiently downsize jpg images without loading
+ # the entire file in memory
+ return false;
+ }
+ } else {
+ return $checkImageAreaHookResult;
}
return true;
}
+
/**
* Extracts the width/height if the image will be scaled before rotating
*
@@ -81,10 +86,15 @@ class BitmapHandler extends ImageHandler {
}
- // Function that returns the number of pixels to be thumbnailed.
- // Intended for animated GIFs to multiply by the number of frames.
- function getImageArea( $image, $width, $height ) {
- return $width * $height;
+ /**
+ * Function that returns the number of pixels to be thumbnailed.
+ * Intended for animated GIFs to multiply by the number of frames.
+ *
+ * @param File $image
+ * @return int
+ */
+ function getImageArea( $image ) {
+ return $image->getWidth() * $image->getHeight();
}
/**
@@ -115,12 +125,14 @@ class BitmapHandler extends ImageHandler {
'srcWidth' => $image->getWidth(),
'srcHeight' => $image->getHeight(),
'mimeType' => $image->getMimeType(),
- 'srcPath' => $image->getPath(),
'dstPath' => $dstPath,
'dstUrl' => $dstUrl,
);
- wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath\n" );
+ # Determine scaler type
+ $scaler = self::getScalerType( $dstPath );
+
+ wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath using scaler $scaler\n" );
if ( !$image->mustRender() &&
$scalerParams['physicalWidth'] == $scalerParams['srcWidth']
@@ -131,9 +143,6 @@ class BitmapHandler extends ImageHandler {
return $this->getClientScalingThumbnailImage( $image, $scalerParams );
}
- # Determine scaler type
- $scaler = self::getScalerType( $dstPath );
- wfDebug( __METHOD__ . ": scaler $scaler\n" );
if ( $scaler == 'client' ) {
# Client-side image scaling, use the source URL
@@ -144,15 +153,18 @@ class BitmapHandler extends ImageHandler {
if ( $flags & self::TRANSFORM_LATER ) {
wfDebug( __METHOD__ . ": Transforming later per flags.\n" );
return new ThumbnailImage( $image, $dstUrl, $scalerParams['clientWidth'],
- $scalerParams['clientHeight'], $dstPath );
+ $scalerParams['clientHeight'], false );
}
# Try to make a target path for the thumbnail
- if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+ if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
wfDebug( __METHOD__ . ": Unable to create thumbnail destination directory, falling back to client scaling\n" );
return $this->getClientScalingThumbnailImage( $image, $scalerParams );
}
+ # Transform functions and binaries need a FS source file
+ $scalerParams['srcPath'] = $image->getLocalRefPath();
+
# Try a hook
$mto = null;
wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) );
@@ -223,13 +235,6 @@ class BitmapHandler extends ImageHandler {
} else {
$scaler = 'client';
}
-
- if ( $scaler != 'client' && $dstPath ) {
- if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
- # Unable to create a path for the thumbnail
- return 'client';
- }
- }
return $scaler;
}
@@ -245,7 +250,7 @@ class BitmapHandler extends ImageHandler {
*/
protected function getClientScalingThumbnailImage( $image, $params ) {
return new ThumbnailImage( $image, $image->getURL(),
- $params['clientWidth'], $params['clientHeight'], $params['srcPath'] );
+ $params['clientWidth'], $params['clientHeight'], null );
}
/**
@@ -276,15 +281,16 @@ class BitmapHandler extends ImageHandler {
< $wgSharpenReductionThreshold ) {
$sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter );
}
- // JPEG decoder hint to reduce memory, available since IM 6.5.6-2
- $decoderHint = "-define jpeg:size={$params['physicalDimensions']}";
+ if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) {
+ // JPEG decoder hint to reduce memory, available since IM 6.5.6-2
+ $decoderHint = "-define jpeg:size={$params['physicalDimensions']}";
+ }
} elseif ( $params['mimeType'] == 'image/png' ) {
$quality = "-quality 95"; // zlib 9, adaptive filtering
} elseif ( $params['mimeType'] == 'image/gif' ) {
- if ( $this->getImageArea( $image, $params['srcWidth'],
- $params['srcHeight'] ) > $wgMaxAnimatedGifArea ) {
+ if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
// Extract initial frame only; we're so big it'll
// be a total drag. :P
$scene = 0;
@@ -298,6 +304,8 @@ class BitmapHandler extends ImageHandler {
$animation_post = '-fuzz 5% -layers optimizeTransparency';
}
}
+ } elseif ( $params['mimeType'] == 'image/x-xcf' ) {
+ $animation_post = '-layers merge';
}
// Use one thread only, to avoid deadlock bugs on OOM
@@ -372,8 +380,7 @@ class BitmapHandler extends ImageHandler {
} elseif( $params['mimeType'] == 'image/png' ) {
$im->setCompressionQuality( 95 );
} elseif ( $params['mimeType'] == 'image/gif' ) {
- if ( $this->getImageArea( $image, $params['srcWidth'],
- $params['srcHeight'] ) > $wgMaxAnimatedGifArea ) {
+ if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
// Extract initial frame only; we're so big it'll
// be a total drag. :P
$im->setImageScene( 0 );
@@ -502,7 +509,7 @@ class BitmapHandler extends ImageHandler {
if ( !isset( $typemap[$params['mimeType']] ) ) {
$err = 'Image type not supported';
wfDebug( "$err\n" );
- $errMsg = wfMsg ( 'thumbnail_image-type' );
+ $errMsg = wfMsg( 'thumbnail_image-type' );
return $this->getMediaTransformError( $params, $errMsg );
}
list( $loader, $colorStyle, $saveType ) = $typemap[$params['mimeType']];
@@ -510,14 +517,14 @@ class BitmapHandler extends ImageHandler {
if ( !function_exists( $loader ) ) {
$err = "Incomplete GD library configuration: missing function $loader";
wfDebug( "$err\n" );
- $errMsg = wfMsg ( 'thumbnail_gd-library', $loader );
+ $errMsg = wfMsg( 'thumbnail_gd-library', $loader );
return $this->getMediaTransformError( $params, $errMsg );
}
if ( !file_exists( $params['srcPath'] ) ) {
$err = "File seems to be missing: {$params['srcPath']}";
wfDebug( "$err\n" );
- $errMsg = wfMsg ( 'thumbnail_image-missing', $params['srcPath'] );
+ $errMsg = wfMsg( 'thumbnail_image-missing', $params['srcPath'] );
return $this->getMediaTransformError( $params, $errMsg );
}
diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php
index d1caa67a..746dddda 100644
--- a/includes/media/BitmapMetadataHandler.php
+++ b/includes/media/BitmapMetadataHandler.php
@@ -32,7 +32,15 @@ class BitmapMetadataHandler {
* @param String $app13 String containing app13 block from jpeg file
*/
private function doApp13 ( $app13 ) {
- $this->iptcType = JpegMetadataExtractor::doPSIR( $app13 );
+ try {
+ $this->iptcType = JpegMetadataExtractor::doPSIR( $app13 );
+ } catch ( MWException $e ) {
+ // Error reading the iptc hash information.
+ // This probably means the App13 segment is something other than what we expect.
+ // However, still try to read it, and treat it as if the hash didn't exist.
+ wfDebug( "Error parsing iptc data of file: " . $e->getMessage() . "\n" );
+ $this->iptcType = 'iptc-no-hash';
+ }
$iptc = IPTC::parse( $app13 );
$this->addMetadata( $iptc, $this->iptcType );
@@ -44,7 +52,10 @@ class BitmapMetadataHandler {
* Basically what used to be in BitmapHandler::getMetadata().
* Just calls stuff in the Exif class.
*
+ * Parameters are passed to the Exif class.
+ *
* @param $filename string
+ * @param $byteOrder string
*/
function getExif ( $filename, $byteOrder ) {
global $wgShowEXIF;
@@ -122,8 +133,10 @@ class BitmapMetadataHandler {
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['PSIR'] ) && count( $seg['PSIR'] ) > 0 ) {
+ foreach( $seg['PSIR'] as $curPSIRValue ) {
+ $meta->doApp13( $curPSIRValue );
+ }
}
if ( isset( $seg['XMP'] ) && $showXMP ) {
$xmp = new XMPReader();
diff --git a/includes/media/Bitmap_ClientOnly.php b/includes/media/Bitmap_ClientOnly.php
index 50679229..3c5d9738 100644
--- a/includes/media/Bitmap_ClientOnly.php
+++ b/includes/media/Bitmap_ClientOnly.php
@@ -38,6 +38,6 @@ class BitmapHandler_ClientOnly extends BitmapHandler {
return new TransformParameterError( $params );
}
return new ThumbnailImage( $image, $image->getURL(), $params['width'],
- $params['height'], $image->getPath() );
+ $params['height'], $image->getLocalRefPath() );
}
}
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
index 2833f683..dedbee0d 100644
--- a/includes/media/DjVu.php
+++ b/includes/media/DjVu.php
@@ -131,7 +131,7 @@ class DjVuHandler extends ImageHandler {
}
$width = $params['width'];
$height = $params['height'];
- $srcPath = $image->getPath();
+ $srcPath = $image->getLocalRefPath();
$page = $params['page'];
if ( $page > $this->pageCount( $image ) ) {
return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'djvu_page_error' ) );
@@ -141,13 +141,13 @@ class DjVuHandler extends ImageHandler {
return new ThumbnailImage( $image, $dstUrl, $width, $height, $dstPath, $page );
}
- if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+ if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'thumbnail_dest_directory' ) );
}
# 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}" .
+ $cmd = '(' . wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page}" .
" -size={$params['physicalWidth']}x{$params['physicalHeight']} " .
wfEscapeShellArg( $srcPath );
if ( $wgDjvuPostProcessor ) {
@@ -190,6 +190,7 @@ class DjVuHandler extends ImageHandler {
/**
* Cache a document tree for the DjVu XML metadata
* @param $image File
+ * @param $gettext Boolean: DOCUMENT (Default: false)
*/
function getMetaTree( $image , $gettext = false ) {
if ( isset( $image->dejaMetaTree ) ) {
@@ -222,7 +223,7 @@ class DjVuHandler extends ImageHandler {
$image->dejaMetaTree = $tree;
}
} catch( Exception $e ) {
- wfDebug( "Bogus multipage XML metadata on '$image->name'\n" );
+ wfDebug( "Bogus multipage XML metadata on '{$image->getName()}'\n" );
}
wfRestoreWarnings();
wfProfileOut( __METHOD__ );
diff --git a/includes/DjVuImage.php b/includes/media/DjVuImage.php
index 80b7408c..80b7408c 100644
--- a/includes/DjVuImage.php
+++ b/includes/media/DjVuImage.php
diff --git a/includes/media/Exif.php b/includes/media/Exif.php
index 345a6f19..a4acdfe0 100644
--- a/includes/media/Exif.php
+++ b/includes/media/Exif.php
@@ -101,6 +101,7 @@ class Exif {
* Constructor
*
* @param $file String: filename.
+ * @param $byteOrder String Type of byte ordering either 'BE' (Big Endian) or 'LE' (Little Endian). Default ''.
* @todo FIXME: The following are broke:
* SubjectArea. Need to test the more obscure tags.
*
@@ -537,7 +538,7 @@ class Exif {
* @deprecated since 1.18
*/
function makeFormattedData( ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
$this->mFormattedExifData = FormatMetadata::getFormattedData(
$this->mFilteredExifData );
}
@@ -569,7 +570,7 @@ class Exif {
* @deprecated since 1.18
*/
function getFormattedData() {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
if (!$this->mFormattedExifData) {
$this->makeFormattedData();
}
diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php
index 05ce161b..7b9867f7 100644
--- a/includes/media/ExifBitmap.php
+++ b/includes/media/ExifBitmap.php
@@ -34,8 +34,8 @@ class ExifBitmapHandler extends BitmapHandler {
// Treat Software as a special case because in can contain
// an array of (SoftwareName, Version).
- if (isset( $metadata['Software'] )
- && is_array( $metadata['Software'] )
+ if (isset( $metadata['Software'] )
+ && is_array( $metadata['Software'] )
&& is_array( $metadata['Software'][0])
&& isset( $metadata['Software'][0][0] )
&& isset( $metadata['Software'][0][1])
@@ -136,8 +136,8 @@ class ExifBitmapHandler extends BitmapHandler {
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.
+
+ // Don't just call $image->getMetadata(); FSFile::getPropsFromPath() calls us with a bogus object.
// This may mean we read EXIF data twice on initial upload.
if ( $wgEnableAutoRotation ) {
$meta = $this->getMetadata( $image, $path );
@@ -171,7 +171,7 @@ class ExifBitmapHandler extends BitmapHandler {
if ( !$wgEnableAutoRotation ) {
return 0;
}
-
+
$data = $file->getMetadata();
return $this->getRotationForExif( $data );
}
diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php
index 47fc1adc..91cb6914 100644
--- a/includes/media/FormatMetadata.php
+++ b/includes/media/FormatMetadata.php
@@ -233,10 +233,19 @@ class FormatMetadata {
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 ) ) {
+ // Full date.
$time = wfTimestamp( TS_MW, $val );
if ( $time && intval( $time ) > 0 ) {
$val = $wgLang->timeanddate( $time );
}
+ } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
+ // No second field. Still format the same
+ // since timeanddate doesn't include seconds anyways,
+ // but second still available in api
+ $time = wfTimestamp( TS_MW, $val . ':00' );
+ if ( $time && intval( $time ) > 0 ) {
+ $val = $wgLang->timeanddate( $time );
+ }
} elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
// If only the date but not the time is filled in.
$time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
@@ -1174,7 +1183,7 @@ class FormatMetadata {
* Format a coordinate value, convert numbers from floating point
* into degree minute second representation.
*
- * @param $coords Array: degrees, minutes and seconds
+ * @param $coord 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
*/
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
index 3bfa45a1..32618e94 100644
--- a/includes/media/GIF.php
+++ b/includes/media/GIF.php
@@ -14,7 +14,7 @@
class GIFHandler extends BitmapHandler {
const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
-
+
function getMetadata( $image, $filename ) {
try {
$parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
@@ -50,17 +50,16 @@ class GIFHandler extends BitmapHandler {
/**
* @param $image File
- * @param $width
- * @param $height
- * @return
+ * @todo unittests
+ * @return bool
*/
- function getImageArea( $image, $width, $height ) {
+ function getImageArea( $image ) {
$ser = $image->getMetadata();
if ( $ser ) {
$metadata = unserialize( $ser );
- return $width * $height * $metadata['frameCount'];
+ return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
} else {
- return $width * $height;
+ return $image->getWidth() * $image->getHeight();
}
}
@@ -118,7 +117,7 @@ class GIFHandler extends BitmapHandler {
wfSuppressWarnings();
$metadata = unserialize($image->getMetadata());
wfRestoreWarnings();
-
+
if (!$metadata || $metadata['frameCount'] <= 1) {
return $original;
}
@@ -126,19 +125,19 @@ class GIFHandler extends BitmapHandler {
/* Preserve original image info string, but strip the last char ')' so we can add even more */
$info = array();
$info[] = $original;
-
+
if ( $metadata['looped'] ) {
$info[] = wfMsgExt( 'file-info-gif-looped', 'parseinline' );
}
-
+
if ( $metadata['frameCount'] > 1 ) {
$info[] = wfMsgExt( 'file-info-gif-frames', 'parseinline', $metadata['frameCount'] );
}
-
+
if ( $metadata['duration'] ) {
$info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
}
-
+
return $wgLang->commaList( $info );
}
}
diff --git a/includes/media/Generic.php b/includes/media/Generic.php
index 6ef21e1e..271d3a8d 100644
--- a/includes/media/Generic.php
+++ b/includes/media/Generic.php
@@ -89,7 +89,7 @@ abstract class MediaHandler {
*
* @param $image File: the image object, or false if there isn't one
* @param $path String: the filename
- * @return Array
+ * @return Array Follow the format of PHP getimagesize() internal function. See http://www.php.net/getimagesize
*/
abstract function getImageSize( $image, $path );
@@ -97,7 +97,7 @@ abstract class MediaHandler {
* Get handler-specific metadata which will be saved in the img_metadata field.
*
* @param $image File: the image object, or false if there isn't one.
- * Warning, File::getPropsFromPath might pass an (object)array() instead (!)
+ * Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
* @param $path String: the filename
* @return String
*/
@@ -187,7 +187,7 @@ abstract class MediaHandler {
* @param $dstUrl String: Destination URL to use in output HTML
* @param $params Array: Arbitrary set of parameters validated by $this->validateParam()
*/
- function getTransform( $image, $dstPath, $dstUrl, $params ) {
+ final function getTransform( $image, $dstPath, $dstUrl, $params ) {
return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
}
@@ -200,6 +200,8 @@ abstract class MediaHandler {
* @param $dstUrl String: destination URL to use in output HTML
* @param $params Array: arbitrary set of parameters validated by $this->validateParam()
* @param $flags Integer: a bitfield, may contain self::TRANSFORM_LATER
+ *
+ * @return MediaTransformOutput
*/
abstract function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
@@ -258,7 +260,7 @@ abstract class MediaHandler {
* @param $image File
*/
function getPageDimensions( $image, $page ) {
- $gis = $this->getImageSize( $image, $image->getPath() );
+ $gis = $this->getImageSize( $image, $image->getLocalRefPath() );
return array(
'width' => $gis[0],
'height' => $gis[1]
@@ -362,7 +364,7 @@ abstract class MediaHandler {
* @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
+ * @param $visibility 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).
@@ -378,8 +380,10 @@ abstract class MediaHandler {
* 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 ) ) {
+ $msg = wfMessage( "$type-$id", $param );
+ if ( $msg->exists() ) {
+ $name = $msg->text();
+ } else {
// 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
@@ -387,8 +391,6 @@ abstract class MediaHandler {
// by default).
wfDebug( __METHOD__ . ' Unknown metadata name: ' . $id . "\n" );
$name = wfEscapeWikiText( $id );
- } else {
- $name = wfMsg( $msgName, $param );
}
$array[$visibility][] = array(
'id' => "$type-$id",
@@ -403,9 +405,7 @@ abstract class MediaHandler {
*/
function getShortDesc( $file ) {
global $wgLang;
- $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $file->getSize() ) );
- return "$nbytes";
+ return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
}
/**
@@ -414,9 +414,8 @@ abstract class MediaHandler {
*/
function getLongDesc( $file ) {
global $wgLang;
- return wfMsgExt( 'file-info', 'parseinline',
- $wgLang->formatSize( $file->getSize() ),
- $file->getMimeType() );
+ return wfMessage( 'file-info', htmlspecialchars( $wgLang->formatSize( $file->getSize() ) ),
+ $file->getMimeType() )->parse();
}
/**
@@ -425,9 +424,7 @@ abstract class MediaHandler {
*/
static function getGeneralShortDesc( $file ) {
global $wgLang;
- $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $file->getSize() ) );
- return "$nbytes";
+ return $wgLang->formatSize( $file->getSize() );
}
/**
@@ -436,9 +433,26 @@ abstract class MediaHandler {
*/
static function getGeneralLongDesc( $file ) {
global $wgLang;
- return wfMsgExt( 'file-info', 'parseinline',
- $wgLang->formatSize( $file->getSize() ),
- $file->getMimeType() );
+ return wfMessage( 'file-info', $wgLang->formatSize( $file->getSize() ),
+ $file->getMimeType() )->parse();
+ }
+
+ /**
+ * Calculate the largest thumbnail width for a given original file size
+ * such that the thumbnail's height is at most $maxHeight.
+ * @param $boxWidth Integer Width of the thumbnail box.
+ * @param $boxHeight Integer Height of the thumbnail box.
+ * @param $maxHeight Integer Maximum height expected for the thumbnail.
+ * @return Integer.
+ */
+ public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
+ $idealWidth = $boxWidth * $maxHeight / $boxHeight;
+ $roundedUp = ceil( $idealWidth );
+ if( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight ) {
+ return floor( $idealWidth );
+ } else {
+ return $roundedUp;
+ }
}
function getDimensionsString( $file ) {
@@ -476,15 +490,32 @@ abstract class MediaHandler {
if( file_exists( $dstPath ) ) {
$thumbstat = stat( $dstPath );
if( $thumbstat['size'] == 0 || $retval != 0 ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'Removing bad %d-byte thumbnail "%s"',
- $thumbstat['size'], $dstPath ) );
- unlink( $dstPath );
+ $result = unlink( $dstPath );
+
+ if ( $result ) {
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() succeeded',
+ $thumbstat['size'], $dstPath ) );
+ } else {
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed',
+ $thumbstat['size'], $dstPath ) );
+ }
return true;
}
}
return false;
}
+
+ /**
+ * Remove files from the purge list
+ *
+ * @param array $files
+ * @param array $options
+ */
+ public function filterThumbnailPurgeList( &$files, $options ) {
+ // Do nothing
+ }
}
/**
@@ -575,7 +606,7 @@ abstract class ImageHandler extends MediaHandler {
# 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'] );
+ $params['width'] = self::fitBoxWidth( $srcWidth, $srcHeight, $params['height'] );
if ( $params['width'] == 0 ) {
# Very small image, so we need to rely on client side scaling :(
@@ -614,13 +645,6 @@ abstract class ImageHandler extends MediaHandler {
}
/**
- * Get a transform output object without actually doing the transform
- */
- function getTransform( $image, $dstPath, $dstUrl, $params ) {
- return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
- }
-
- /**
* Validate thumbnail parameters and fill in the correct height
*
* @param $width Integer: specified width (input/output)
@@ -686,9 +710,8 @@ abstract class ImageHandler extends MediaHandler {
*/
function getShortDesc( $file ) {
global $wgLang;
- $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $file->getSize() ) );
- $widthheight = wfMsgHtml( 'widthheight', $wgLang->formatNum( $file->getWidth() ) ,$wgLang->formatNum( $file->getHeight() ) );
+ $nbytes = htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
+ $widthheight = wfMessage( 'widthheight' )->numParams( $file->getWidth(), $file->getHeight() )->escaped();
return "$widthheight ($nbytes)";
}
@@ -700,19 +723,15 @@ abstract class ImageHandler extends MediaHandler {
function getLongDesc( $file ) {
global $wgLang;
$pages = $file->pageCount();
+ $size = htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
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() );
+ $msg = wfMessage( 'file-info-size' )->numParams( $file->getWidth(),
+ $file->getHeight() )->params( $size,
+ $file->getMimeType() )->parse();
} 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 ) );
+ $msg = wfMessage( 'file-info-size-pages' )->numParams( $file->getWidth(),
+ $file->getHeight() )->params( $size,
+ $file->getMimeType() )->numParams( $pages )->parse();
}
return $msg;
}
@@ -722,16 +741,11 @@ abstract class ImageHandler extends MediaHandler {
* @return string
*/
function getDimensionsString( $file ) {
- global $wgLang;
$pages = $file->pageCount();
- $width = $wgLang->formatNum( $file->getWidth() );
- $height = $wgLang->formatNum( $file->getHeight() );
- $pagesFmt = $wgLang->formatNum( $pages );
-
if ( $pages > 1 ) {
- return wfMsgExt( 'widthheightpage', 'parsemag', $width, $height, $pagesFmt );
+ return wfMessage( 'widthheightpage' )->numParams( $file->getWidth(), $file->getHeight(), $pages )->text();
} else {
- return wfMsg( 'widthheight', $width, $height );
+ return wfMessage( 'widthheight' )->numParams( $file->getWidth(), $file->getHeight() )->text();
}
}
}
diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php
index 4769bf8e..224b4a2b 100644
--- a/includes/media/JpegMetadataExtractor.php
+++ b/includes/media/JpegMetadataExtractor.php
@@ -31,6 +31,7 @@ class JpegMetadataExtractor {
$segments = array(
'XMP_ext' => array(),
'COM' => array(),
+ 'PSIR' => array(),
);
if ( !$filename ) {
@@ -122,7 +123,7 @@ class JpegMetadataExtractor {
// APP13 - PSIR. IPTC and some photoshop stuff
$temp = self::jpegExtractMarker( $fh );
if ( substr( $temp, 0, 14 ) === "Photoshop 3.0\x00" ) {
- $segments["PSIR"] = $temp;
+ $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
@@ -162,11 +163,12 @@ class JpegMetadataExtractor {
* This should generally be called by BitmapMetadataHandler::doApp13()
*
* @param String $app13 photoshop psir app13 block from jpg.
+ * @throws MWException (It gets caught next level up though)
* @return String if the iptc hash is good or not.
*/
public static function doPSIR ( $app13 ) {
if ( !$app13 ) {
- return;
+ throw new MWException( "No App13 segment given" );
}
// First compare hash with real thing
// 0x404 contains IPTC, 0x425 has hash
@@ -218,8 +220,8 @@ class JpegMetadataExtractor {
// this should not happen, but check.
if ( $lenData['len'] + $offset > $appLen ) {
- wfDebug( __METHOD__ . " PSIR data too long.\n" );
- return 'iptc-no-hash';
+ throw new MWException( "PSIR data too long. (item length=" . $lenData['len']
+ . "; offset=$offset; total length=$appLen)" );
}
if ( $valid ) {
diff --git a/includes/media/MediaTransformOutput.php b/includes/media/MediaTransformOutput.php
index f170bb9d..fcfb2f45 100644
--- a/includes/media/MediaTransformOutput.php
+++ b/includes/media/MediaTransformOutput.php
@@ -18,33 +18,42 @@ abstract class MediaTransformOutput {
var $file;
var $width, $height, $url, $page, $path;
+ protected $storagePath = false;
/**
* Get the width of the output box
*/
- function getWidth() {
+ public function getWidth() {
return $this->width;
}
/**
* Get the height of the output box
*/
- function getHeight() {
+ public function getHeight() {
return $this->height;
}
/**
* @return string The thumbnail URL
*/
- function getUrl() {
+ public function getUrl() {
return $this->url;
}
/**
- * @return String: destination file path (local filesystem)
+ * @return string|false The permanent thumbnail storage path
*/
- function getPath() {
- return $this->path;
+ public function getStoragePath() {
+ return $this->storagePath;
+ }
+
+ /**
+ * @param $storagePath string The permanent storage path
+ * @return void
+ */
+ public function setStoragePath( $storagePath ) {
+ $this->storagePath = $storagePath;
}
/**
@@ -67,16 +76,66 @@ abstract class MediaTransformOutput {
*
* @return string
*/
- abstract function toHtml( $options = array() );
+ abstract public function toHtml( $options = array() );
/**
* This will be overridden to return true in error classes
*/
- function isError() {
+ public function isError() {
return false;
}
/**
+ * Check if an output thumbnail file actually exists.
+ * This will return false if there was an error, the
+ * thumbnail is to be handled client-side only, or if
+ * transformation was deferred via TRANSFORM_LATER.
+ *
+ * @return Bool
+ */
+ public function hasFile() {
+ // If TRANSFORM_LATER, $this->path will be false.
+ // Note: a null path means "use the source file".
+ return ( !$this->isError() && ( $this->path || $this->path === null ) );
+ }
+
+ /**
+ * Check if the output thumbnail is the same as the source.
+ * This can occur if the requested width was bigger than the source.
+ *
+ * @return Bool
+ */
+ public function fileIsSource() {
+ return ( !$this->isError() && $this->path === null );
+ }
+
+ /**
+ * Get the path of a file system copy of the thumbnail.
+ * Callers should never write to this path.
+ *
+ * @return string|false Returns false if there isn't one
+ */
+ public function getLocalCopyPath() {
+ if ( $this->isError() ) {
+ return false;
+ } elseif ( $this->path === null ) {
+ return $this->file->getLocalRefPath();
+ } else {
+ return $this->path; // may return false
+ }
+ }
+
+ /**
+ * Stream the file if there were no errors
+ *
+ * @param $headers Array Additional HTTP headers to send on success
+ * @return Bool success
+ */
+ public function streamFile( $headers = array() ) {
+ return $this->path && StreamFile::stream( $this->getLocalCopyPath(), $headers );
+ }
+
+ /**
* Wrap some XHTML text in an anchor tag with the given attributes
*
* @param $linkAttribs array
@@ -97,7 +156,7 @@ abstract class MediaTransformOutput {
* @param $params array
* @return array
*/
- function getDescLinkAttribs( $title = null, $params = '' ) {
+ public function getDescLinkAttribs( $title = null, $params = '' ) {
$query = $this->page ? ( 'page=' . urlencode( $this->page ) ) : '';
if( $params ) {
$query .= $query ? '&'.$params : $params;
@@ -119,13 +178,16 @@ abstract class MediaTransformOutput {
* @ingroup Media
*/
class ThumbnailImage extends MediaTransformOutput {
-
/**
+ * Get a thumbnail object from a file and parameters.
+ * If $path is set to null, the output file is treated as a source copy.
+ * If $path is set to false, no output file will be created.
+ *
* @param $file File object
* @param $url String: URL path to the thumb
* @param $width Integer: file's width
* @param $height Integer: file's height
- * @param $path String: filesystem path to the thumb
+ * @param $path String|false|null: filesystem path to the thumb
* @param $page Integer: page number, for multipage files
* @private
*/
@@ -185,7 +247,7 @@ class ThumbnailImage extends MediaTransformOutput {
} elseif ( !empty( $options['custom-title-link'] ) ) {
$title = $options['custom-title-link'];
$linkAttribs = array(
- 'href' => $title->getLinkUrl(),
+ 'href' => $title->getLinkURL(),
'title' => empty( $options['title'] ) ? $title->getFullText() : $options['title']
);
} elseif ( !empty( $options['desc-link'] ) ) {
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
index ceffd7c3..aac838e1 100644
--- a/includes/media/SVG.php
+++ b/includes/media/SVG.php
@@ -93,13 +93,13 @@ class SvgHandler extends ImageHandler {
$clientHeight = $params['height'];
$physicalWidth = $params['physicalWidth'];
$physicalHeight = $params['physicalHeight'];
- $srcPath = $image->getPath();
+ $srcPath = $image->getLocalRefPath();
if ( $flags & self::TRANSFORM_LATER ) {
return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
}
- if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+ if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
wfMsg( 'thumbnail_dest_directory' ) );
}
@@ -119,7 +119,7 @@ class SvgHandler extends ImageHandler {
* @param string $dstPath
* @param string $width
* @param string $height
- * @returns TRUE/MediaTransformError
+ * @return true|MediaTransformError
*/
public function rasterize( $srcPath, $dstPath, $width, $height ) {
global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
@@ -129,7 +129,7 @@ class SvgHandler extends ImageHandler {
if ( is_array( $wgSVGConverters[$wgSVGConverter] ) ) {
// This is a PHP callable
$func = $wgSVGConverters[$wgSVGConverter][0];
- $args = array_merge( array( $srcPath, $dstPath, $width, $height ),
+ $args = array_merge( array( $srcPath, $dstPath, $width, $height ),
array_slice( $wgSVGConverters[$wgSVGConverter], 1 ) );
if ( !is_callable( $func ) ) {
throw new MWException( "$func is not callable" );
@@ -161,13 +161,13 @@ 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';
}
diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php
index 22ef8e61..db9f05fd 100644
--- a/includes/media/SVGMetadataExtractor.php
+++ b/includes/media/SVGMetadataExtractor.php
@@ -68,6 +68,12 @@ class SVGReader {
$this->reader->open( $source, null, LIBXML_NOERROR | LIBXML_NOWARNING );
}
+ // Expand entities, since Adobe Illustrator uses them for xmlns
+ // attributes (bug 31719). Note that libxml2 has some protection
+ // against large recursive entity expansions so this is not as
+ // insecure as it might appear to be.
+ $this->reader->setParserProperty( XMLReader::SUBST_ENTITIES, true );
+
$this->metadata['width'] = self::DEFAULT_WIDTH;
$this->metadata['height'] = self::DEFAULT_HEIGHT;
@@ -166,7 +172,7 @@ class SVGReader {
}
}
- /*
+ /**
* Read an XML snippet from an element
*
* @param String $metafield that we will fill with the result
diff --git a/includes/media/XCF.php b/includes/media/XCF.php
new file mode 100644
index 00000000..806db73c
--- /dev/null
+++ b/includes/media/XCF.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Handler for the Gimp's native file format (XCF)
+ *
+ * Overview:
+ * http://en.wikipedia.org/wiki/XCF_(file_format)
+ * Specification in Gnome repository:
+ * http://svn.gnome.org/viewvc/gimp/trunk/devel-docs/xcf.txt?view=markup
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for the Gimp's native file format; getimagesize() doesn't
+ * support these files
+ *
+ * @ingroup Media
+ */
+class XCFHandler extends BitmapHandler {
+
+ /**
+ * @param $file
+ * @return bool
+ */
+ function mustRender( $file ) {
+ return true;
+ }
+
+ /**
+ * Render files as PNG
+ *
+ * @param $ext
+ * @param $mime
+ * @param $params
+ * @return array
+ */
+ function getThumbType( $ext, $mime, $params = null ) {
+ return array( 'png', 'image/png' );
+ }
+
+ /**
+ * Get width and height from the XCF header.
+ *
+ * @param $image
+ * @param $filename
+ * @return array
+ */
+ function getImageSize( $image, $filename ) {
+ return self::getXCFMetaData( $filename );
+ }
+
+ /**
+ * Metadata for a given XCF file
+ *
+ * Will return false if file magic signature is not recognized
+ * @author Hexmode
+ * @author Hashar
+ *
+ * @param $filename String Full path to a XCF file
+ * @return false|metadata array just like PHP getimagesize()
+ */
+ static function getXCFMetaData( $filename ) {
+ # Decode master structure
+ $f = fopen( $filename, 'rb' );
+ if( !$f ) {
+ return false;
+ }
+ # The image structure always starts at offset 0 in the XCF file.
+ # So we just read it :-)
+ $binaryHeader = fread( $f, 26 );
+ fclose($f);
+
+ # Master image structure:
+ #
+ # byte[9] "gimp xcf " File type magic
+ # byte[4] version XCF version
+ # "file" - version 0
+ # "v001" - version 1
+ # "v002" - version 2
+ # byte 0 Zero-terminator for version tag
+ # uint32 width With of canvas
+ # uint32 height Height of canvas
+ # uint32 base_type Color mode of the image; one of
+ # 0: RGB color
+ # 1: Grayscale
+ # 2: Indexed color
+ # (enum GimpImageBaseType in libgimpbase/gimpbaseenums.h)
+ try {
+ $header = wfUnpack(
+ "A9magic" # A: space padded
+ . "/a5version" # a: zero padded
+ . "/Nwidth" # \
+ . "/Nheight" # N: unsigned long 32bit big endian
+ . "/Nbase_type" # /
+ , $binaryHeader
+ );
+ } catch( MWException $mwe ) {
+ return false;
+ }
+
+ # Check values
+ if( $header['magic'] !== 'gimp xcf' ) {
+ wfDebug( __METHOD__ . " '$filename' has invalid magic signature.\n" );
+ return false;
+ }
+ # TODO: we might want to check for sane values of width and height
+
+ wfDebug( __METHOD__ . ": canvas size of '$filename' is {$header['width']} x {$header['height']} px\n" );
+
+ # Forge a return array containing metadata information just like getimagesize()
+ # See PHP documentation at: http://www.php.net/getimagesize
+ $metadata = array();
+ $metadata[0] = $header['width'];
+ $metadata[1] = $header['height'];
+ $metadata[2] = null; # IMAGETYPE constant, none exist for XCF.
+ $metadata[3] = sprintf(
+ 'height="%s" width="%s"', $header['height'], $header['width']
+ );
+ $metadata['mime'] = 'image/x-xcf';
+ $metadata['channels'] = null;
+ $metadata['bits'] = 8; # Always 8-bits per color
+
+ assert( '7 == count($metadata); # return array must contains 7 elements just like getimagesize() return' );
+
+ return $metadata;
+ }
+
+ /**
+ * Must use "im" for XCF
+ *
+ * @return string
+ */
+ protected static function getScalerType( $dstPath, $checkDstPath = true ) {
+ return "im";
+ }
+}
diff --git a/includes/media/XMP.php b/includes/media/XMP.php
index 1e578582..0dbf5632 100644
--- a/includes/media/XMP.php
+++ b/includes/media/XMP.php
@@ -210,9 +210,9 @@ class XMPReader {
* 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
+ * @param $content String: XMP data
+ * @param $allOfIt Boolean: If this is all the data (true) or if its split up (false). Default true
+ * @param $reset Boolean: does xml parser need to be reset. Default false
* @return Boolean success.
*/
public function parse( $content, $allOfIt = true, $reset = false ) {
diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php
index 1d580ff7..156d9b50 100644
--- a/includes/media/XMPInfo.php
+++ b/includes/media/XMPInfo.php
@@ -631,12 +631,23 @@ class XMPInfo {
'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 ),
- ),
+ /********
+ * Disable extracting this property (bug 31944)
+ * Several files have a string instead of a Seq
+ * for this property. XMPReader doesn't handle
+ * mismatched types very gracefully (it marks
+ * the entire file as invalid, instead of just
+ * the relavent prop). Since this prop
+ * doesn't communicate all that useful information
+ * just disable this prop for now, until such
+ * XMPReader is more graceful (bug 32172)
+ * 'YCbCrSubSampling' => array(
+ * 'map_group' => 'exif',
+ * 'mode' => XMPReader::MODE_SEQ,
+ * 'validate' => 'validateClosed',
+ * 'choices' => array( '1' => true, '2' => true ),
+ * ),
+ */
),
'http://ns.adobe.com/exif/1.0/aux/' => array(
'Lens' => array(
diff --git a/includes/media/XMPValidate.php b/includes/media/XMPValidate.php
index 0f1d375c..600d99de 100644
--- a/includes/media/XMPValidate.php
+++ b/includes/media/XMPValidate.php
@@ -201,10 +201,20 @@ class XMPValidate {
}
/**
- * function to validate date properties, and convert to Exif format.
+ * function to validate date properties, and convert to (partial) Exif format.
+ *
+ * Dates can be one of the following formats:
+ * YYYY
+ * YYYY-MM
+ * YYYY-MM-DD
+ * YYYY-MM-DDThh:mmTZD
+ * YYYY-MM-DDThh:mm:ssTZD
+ * YYYY-MM-DDThh:mm:ss.sTZD
*
* @param $info Array information about current property
* @param &$val Mixed current value to validate. Converts to TS_EXIF as a side-effect.
+ * in cases where there's only a partial date, it will give things like
+ * 2011:04.
* @param $standalone Boolean if this is a simple property or array
*/
public static function validateDate( $info, &$val, $standalone ) {
@@ -240,25 +250,41 @@ class XMPValidate {
$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];
+ //just have the year month day (if that)
+ $val = $res[1];
+ if ( isset( $res[2] ) ) {
+ $val .= ':' . $res[2];
+ }
+ if ( isset( $res[3] ) ) {
+ $val .= ':' . $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' ) {
+ //if hour is set, then minute must also be or regex above will fail.
$val = $res[1] . ':' . $res[2] . ':' . $res[3]
- . ' ' . $res[4] . ':' . $res[5] . ':' . $res[6];
+ . ' ' . $res[4] . ':' . $res[5];
+ if ( isset( $res[6] ) && $res[6] !== '' ) {
+ $val .= ':' . $res[6];
+ }
return;
}
- //do timezone processing. We've already done the case that tz = Z.
+
+ // Extra check for empty string necessary due to TZ but no second case.
+ $stripSeconds = false;
+ if ( !isset( $res[6] ) || $res[6] === '' ) {
+ $res[6] = '00';
+ $stripSeconds = true;
+ }
+
+ // Do timezone processing. We've already done the case that tz = Z.
+
+ // We know that if we got to this step, year, month day hour and min must be set
+ // by virtue of regex not failing.
$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;
@@ -267,6 +293,11 @@ class XMPValidate {
$offset = -$offset;
}
$val = wfTimestamp( TS_EXIF, $unix + $offset );
+
+ if ( $stripSeconds ) {
+ // If seconds weren't specified, remove the trailing ':00'.
+ $val = substr( $val, 0, -3 );
+ }
}
}
diff --git a/includes/mime.info b/includes/mime.info
index d705715d..f7576e41 100644
--- a/includes/mime.info
+++ b/includes/mime.info
@@ -13,12 +13,12 @@ image/jpeg [BITMAP]
image/jp2 [BITMAP]
image/xbm [BITMAP]
image/tiff [BITMAP]
-image/x-icon [BITMAP]
+image/x-icon image/x-ico [BITMAP]
image/x-rgb [BITMAP]
image/x-portable-pixmap [BITMAP]
image/x-portable-graymap image/x-portable-greymap [BITMAP]
image/x-bmp image/x-ms-bmp image/bmp application/x-bmp application/bmp [BITMAP]
-image/x-photoshop image/psd image/x-psd image/photoshop [BITMAP]
+image/x-photoshop image/psd image/x-psd image/photoshop image/vnd.adobe.photoshop [BITMAP]
image/vnd.djvu image/x.djvu image/x-djvu [BITMAP]
image/webp [BITMAP]
diff --git a/includes/mime.types b/includes/mime.types
index 9e640322..5b64201f 100644
--- a/includes/mime.types
+++ b/includes/mime.types
@@ -57,9 +57,9 @@ application/x-wais-source src
application/x-xpinstall xpi
application/xhtml+xml xhtml xht
application/xslt+xml xslt
-application/xml xml xsl xsd
+application/xml xml xsl xsd kml
application/xml-dtd dtd
-application/zip zip jar xpi sxc stc sxd std sxi sti sxm stm sxw stw
+application/zip zip jar xpi sxc stc sxd std sxi sti sxm stm sxw stw
application/x-rar rar
audio/basic au snd
audio/midi mid midi kar
@@ -115,6 +115,7 @@ text/vnd.wap.wml wml
text/vnd.wap.wmlscript wmls
text/xml xml xsl xslt rss rdf
text/x-setext etx
+text/x-sawfish jl
video/mpeg mpeg mpg mpe
video/ogg ogv ogm ogg
video/quicktime qt mov
@@ -126,21 +127,22 @@ video/x-msvideo avi
video/x-ogg ogv ogm ogg
video/x-sgi-movie movie
x-conference/x-cooltalk ice
-application/vnd.oasis.opendocument.text odt
-application/vnd.oasis.opendocument.text-template ott
+application/vnd.oasis.opendocument.chart odc
+application/vnd.oasis.opendocument.chart-template otc
+application/vnd.oasis.opendocument.database odb
+application/vnd.oasis.opendocument.formula odf
+application/vnd.oasis.opendocument.formula-template otf
application/vnd.oasis.opendocument.graphics odg
application/vnd.oasis.opendocument.graphics-template otg
+application/vnd.oasis.opendocument.image odi
+application/vnd.oasis.opendocument.image-template oti
application/vnd.oasis.opendocument.presentation odp
application/vnd.oasis.opendocument.presentation-template otp
application/vnd.oasis.opendocument.spreadsheet ods
application/vnd.oasis.opendocument.spreadsheet-template ots
-application/vnd.oasis.opendocument.chart odc
-application/vnd.oasis.opendocument.chart-template otc
-application/vnd.oasis.opendocument.image odi
-application/vnd.oasis.opendocument.image-template oti
-application/vnd.oasis.opendocument.formula odf
-application/vnd.oasis.opendocument.formula-template otf
+application/vnd.oasis.opendocument.text odt
application/vnd.oasis.opendocument.text-master odm
+application/vnd.oasis.opendocument.text-template ott
application/vnd.oasis.opendocument.text-web oth
application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx
diff --git a/includes/normal/RandomTest.php b/includes/normal/RandomTest.php
index f2ec460e..d96cb09a 100644
--- a/includes/normal/RandomTest.php
+++ b/includes/normal/RandomTest.php
@@ -54,7 +54,7 @@ function donorm( $str ) {
# UnicodeString constructor fails if the string ends with a head byte.
# Add a junk char at the end, we'll strip it off
- return rtrim( utf8_normalize( $str . "\x01", UNORM_NFC ), "\x01" );
+ return rtrim( utf8_normalize( $str . "\x01", UtfNormal::UNORM_NFC ), "\x01" );
}
function wfMsg($x) {
diff --git a/includes/normal/UtfNormal.php b/includes/normal/UtfNormal.php
index 919278c9..b5aad301 100644
--- a/includes/normal/UtfNormal.php
+++ b/includes/normal/UtfNormal.php
@@ -28,17 +28,6 @@
* @defgroup UtfNormal UtfNormal
*/
-/**
- * For using the ICU wrapper
- */
-define( 'UNORM_NONE', 1 );
-define( 'UNORM_NFD', 2 );
-define( 'UNORM_NFKD', 3 );
-define( 'UNORM_NFC', 4 );
-define( 'UNORM_DEFAULT', UNORM_NFC );
-define( 'UNORM_NFKC', 5 );
-define( 'UNORM_FCD', 6 );
-
define( 'NORMALIZE_ICU', function_exists( 'utf8_normalize' ) );
define( 'NORMALIZE_INTL', function_exists( 'normalizer_normalize' ) );
@@ -57,6 +46,17 @@ define( 'NORMALIZE_INTL', function_exists( 'normalizer_normalize' ) );
* @ingroup UtfNormal
*/
class UtfNormal {
+ /**
+ * For using the ICU wrapper
+ */
+ const UNORM_NONE = 1;
+ const UNORM_NFD = 2;
+ const UNORM_NFKD = 3;
+ const UNORM_NFC = 4;
+ const UNORM_NFKC = 5;
+ const UNORM_FCD = 6;
+ const UNORM_DEFAULT = self::UNORM_NFC;
+
static $utfCombiningClass = null;
static $utfCanonicalComp = null;
static $utfCanonicalDecomp = null;
@@ -82,7 +82,7 @@ class UtfNormal {
# UnicodeString constructor fails if the string ends with a
# head byte. Add a junk char at the end, we'll strip it off.
- return rtrim( utf8_normalize( $string . "\x01", UNORM_NFC ), "\x01" );
+ return rtrim( utf8_normalize( $string . "\x01", self::UNORM_NFC ), "\x01" );
} elseif( NORMALIZE_INTL ) {
$string = self::replaceForNativeNormalize( $string );
$norm = normalizer_normalize( $string, Normalizer::FORM_C );
@@ -121,7 +121,7 @@ class UtfNormal {
if( NORMALIZE_INTL )
return normalizer_normalize( $string, Normalizer::FORM_C );
elseif( NORMALIZE_ICU )
- return utf8_normalize( $string, UNORM_NFC );
+ return utf8_normalize( $string, self::UNORM_NFC );
elseif( UtfNormal::quickIsNFC( $string ) )
return $string;
else
@@ -139,7 +139,7 @@ class UtfNormal {
if( NORMALIZE_INTL )
return normalizer_normalize( $string, Normalizer::FORM_D );
elseif( NORMALIZE_ICU )
- return utf8_normalize( $string, UNORM_NFD );
+ return utf8_normalize( $string, self::UNORM_NFD );
elseif( preg_match( '/[\x80-\xff]/', $string ) )
return UtfNormal::NFD( $string );
else
@@ -158,7 +158,7 @@ class UtfNormal {
if( NORMALIZE_INTL )
return normalizer_normalize( $string, Normalizer::FORM_KC );
elseif( NORMALIZE_ICU )
- return utf8_normalize( $string, UNORM_NFKC );
+ return utf8_normalize( $string, self::UNORM_NFKC );
elseif( preg_match( '/[\x80-\xff]/', $string ) )
return UtfNormal::NFKC( $string );
else
@@ -177,7 +177,7 @@ class UtfNormal {
if( NORMALIZE_INTL )
return normalizer_normalize( $string, Normalizer::FORM_KD );
elseif( NORMALIZE_ICU )
- return utf8_normalize( $string, UNORM_NFKD );
+ return utf8_normalize( $string, self::UNORM_NFKD );
elseif( preg_match( '/[\x80-\xff]/', $string ) )
return UtfNormal::NFKD( $string );
else
diff --git a/includes/objectcache/BagOStuff.php b/includes/objectcache/BagOStuff.php
index 97b6cb2c..81ad6621 100644
--- a/includes/objectcache/BagOStuff.php
+++ b/includes/objectcache/BagOStuff.php
@@ -92,11 +92,15 @@ abstract class BagOStuff {
}
/**
- * Delete all objects expiring before a certain date.
+ * Delete all objects expiring before a certain date.
+ * @param $date The reference date in MW format
+ * @param $progressCallback Optional, a function which will be called
+ * regularly during long-running operations with the percentage progress
+ * as the first parameter.
*
* @return true on success, false if unimplemented
*/
- public function deleteObjectsExpiringBefore( $date ) {
+ public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) {
// stub
return false;
}
diff --git a/includes/objectcache/DBABagOStuff.php b/includes/objectcache/DBABagOStuff.php
index a273a4f7..ade8c0a9 100644
--- a/includes/objectcache/DBABagOStuff.php
+++ b/includes/objectcache/DBABagOStuff.php
@@ -187,8 +187,10 @@ class DBABagOStuff extends BagOStuff {
$result[] = $k1;
- while ( $key = dba_nextkey( $reader ) ) {
+ $key = dba_nextkey( $reader );
+ while ( $key ) {
$result[] = $key;
+ $key = dba_nextkey( $reader );
}
return $result;
diff --git a/includes/objectcache/EmptyBagOStuff.php b/includes/objectcache/EmptyBagOStuff.php
index e956e2ee..2aee6b12 100644
--- a/includes/objectcache/EmptyBagOStuff.php
+++ b/includes/objectcache/EmptyBagOStuff.php
@@ -19,7 +19,7 @@ class EmptyBagOStuff extends BagOStuff {
}
}
-/**
+/**
* Backwards compatibility alias for EmptyBagOStuff
* @deprecated since 1.18
*/
diff --git a/includes/objectcache/MemcachedClient.php b/includes/objectcache/MemcachedClient.php
index dd4401a8..868ad69f 100644
--- a/includes/objectcache/MemcachedClient.php
+++ b/includes/objectcache/MemcachedClient.php
@@ -396,7 +396,7 @@ class MWMemcached {
/**
* Retrieves the value associated with the key from the memcache server
*
- * @param $key Mixed: key to retrieve
+ * @param $key array|string key to retrieve
*
* @return Mixed
*/
@@ -419,6 +419,7 @@ class MWMemcached {
return false;
}
+ $key = is_array( $key ) ? $key[1] : $key;
if ( isset( $this->stats['get'] ) ) {
$this->stats['get']++;
} else {
@@ -826,9 +827,9 @@ class MWMemcached {
/**
* Perform increment/decriment on $key
*
- * @param $cmd String: command to perform
- * @param $key String: key to perform it on
- * @param $amt Integer: amount to adjust
+ * @param $cmd String command to perform
+ * @param $key String|array key to perform it on
+ * @param $amt Integer amount to adjust
*
* @return Integer: new value of $key
* @access private
@@ -958,10 +959,13 @@ 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 ) {
+
+ // TTLs higher than 30 days will be detected as absolute TTLs
+ // (UNIX timestamps), and will result in the cache entry being
+ // discarded immediately because the expiry is in the past.
+ // Clamp expiries >30d at 30d, unless they're >=1e9 in which
+ // case they are likely to really be absolute (1e9 = 2011-09-09)
+ if ( $exp > 2592000 && $exp < 1000000000 ) {
$exp = 2592000;
}
diff --git a/includes/objectcache/MultiWriteBagOStuff.php b/includes/objectcache/MultiWriteBagOStuff.php
index 2b88b427..0d95a846 100644
--- a/includes/objectcache/MultiWriteBagOStuff.php
+++ b/includes/objectcache/MultiWriteBagOStuff.php
@@ -101,10 +101,10 @@ class MultiWriteBagOStuff extends BagOStuff {
*
* Succeed if any of the child caches succeed.
*/
- public function deleteObjectsExpiringBefore( $date ) {
+ public function deleteObjectsExpiringBefore( $date, $progressCallback = false ) {
$ret = false;
foreach ( $this->caches as $cache ) {
- if ( $cache->deleteObjectsExpiringBefore( $date ) ) {
+ if ( $cache->deleteObjectsExpiringBefore( $date, $progressCallback ) ) {
$ret = true;
}
}
diff --git a/includes/objectcache/ObjectCache.php b/includes/objectcache/ObjectCache.php
index 99e38953..77ca8371 100644
--- a/includes/objectcache/ObjectCache.php
+++ b/includes/objectcache/ObjectCache.php
@@ -43,7 +43,7 @@ class ObjectCache {
global $wgObjectCaches;
if ( !isset( $wgObjectCaches[$id] ) ) {
- throw new MWException( "Invalid object cache type \"$id\" requested. " .
+ throw new MWException( "Invalid object cache type \"$id\" requested. " .
"It is not present in \$wgObjectCaches." );
}
@@ -89,9 +89,7 @@ class ObjectCache {
* @return ObjectCache
*/
static function newAccelerator( $params ) {
- if ( function_exists( 'eaccelerator_get' ) ) {
- $id = 'eaccelerator';
- } elseif ( function_exists( 'apc_fetch') ) {
+ if ( function_exists( 'apc_fetch') ) {
$id = 'apc';
} elseif( function_exists( 'xcache_get' ) && wfIniGetBool( 'xcache.var_size' ) ) {
$id = 'xcache';
@@ -106,11 +104,11 @@ class ObjectCache {
/**
* Factory function that creates a memcached client object.
- * The idea of this is that it might eventually detect and automatically
+ * 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 ) {
diff --git a/includes/objectcache/SqlBagOStuff.php b/includes/objectcache/SqlBagOStuff.php
index 78817d0b..93d22f11 100644
--- a/includes/objectcache/SqlBagOStuff.php
+++ b/includes/objectcache/SqlBagOStuff.php
@@ -24,24 +24,24 @@ class SqlBagOStuff extends BagOStuff {
/**
* Constructor. Parameters are:
- * - server: A server info structure in the format required by each
+ * - 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
+ * - 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
+ * - 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
@@ -108,7 +108,7 @@ class SqlBagOStuff extends BagOStuff {
protected function getTableByShard( $index ) {
if ( $this->shards > 1 ) {
$decimals = strlen( $this->shards - 1 );
- return $this->tableName .
+ return $this->tableName .
sprintf( "%0{$decimals}d", $index );
} else {
return $this->tableName;
@@ -133,7 +133,7 @@ class SqlBagOStuff extends BagOStuff {
if ( $this->isExpired( $row->exptime ) ) {
$this->debug( "get: key has expired, deleting" );
try {
- $db->begin();
+ $db->begin( __METHOD__ );
# Put the expiry time in the WHERE condition to avoid deleting a
# newly-inserted value
$db->delete( $tableName,
@@ -141,7 +141,7 @@ class SqlBagOStuff extends BagOStuff {
'keyname' => $key,
'exptime' => $row->exptime
), __METHOD__ );
- $db->commit();
+ $db->commit( __METHOD__ );
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e );
}
@@ -170,18 +170,18 @@ class SqlBagOStuff extends BagOStuff {
$encExpiry = $db->timestamp( $exptime );
}
try {
- $db->begin();
+ $db->begin( __METHOD__ );
// (bug 24425) use a replace if the db supports it instead of
// delete/insert to avoid clashes with conflicting keynames
- $db->replace(
- $this->getTableByKey( $key ),
+ $db->replace(
+ $this->getTableByKey( $key ),
array( 'keyname' ),
array(
'keyname' => $key,
'value' => $db->encodeBlob( $this->serialize( $value ) ),
'exptime' => $encExpiry
), __METHOD__ );
- $db->commit();
+ $db->commit( __METHOD__ );
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e );
@@ -195,12 +195,12 @@ class SqlBagOStuff extends BagOStuff {
$db = $this->getDB();
try {
- $db->begin();
- $db->delete(
- $this->getTableByKey( $key ),
- array( 'keyname' => $key ),
+ $db->begin( __METHOD__ );
+ $db->delete(
+ $this->getTableByKey( $key ),
+ array( 'keyname' => $key ),
__METHOD__ );
- $db->commit();
+ $db->commit( __METHOD__ );
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e );
@@ -216,23 +216,23 @@ class SqlBagOStuff extends BagOStuff {
$step = intval( $step );
try {
- $db->begin();
- $row = $db->selectRow(
- $tableName,
+ $db->begin( __METHOD__ );
+ $row = $db->selectRow(
+ $tableName,
array( 'value', 'exptime' ),
- array( 'keyname' => $key ),
- __METHOD__,
+ array( 'keyname' => $key ),
+ __METHOD__,
array( 'FOR UPDATE' ) );
if ( $row === false ) {
// Missing
- $db->commit();
+ $db->commit( __METHOD__ );
return null;
}
$db->delete( $tableName, array( 'keyname' => $key ), __METHOD__ );
if ( $this->isExpired( $row->exptime ) ) {
// Expired, do not reinsert
- $db->commit();
+ $db->commit( __METHOD__ );
return null;
}
@@ -245,12 +245,12 @@ class SqlBagOStuff extends BagOStuff {
'value' => $db->encodeBlob( $this->serialize( $newValue ) ),
'exptime' => $row->exptime
), __METHOD__, 'IGNORE' );
-
+
if ( $db->affectedRows() == 0 ) {
// Race condition. See bug 28611
$newValue = null;
}
- $db->commit();
+ $db->commit( __METHOD__ );
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e );
@@ -265,7 +265,7 @@ class SqlBagOStuff extends BagOStuff {
$result = array();
for ( $i = 0; $i < $this->shards; $i++ ) {
- $res = $db->select( $this->getTableByShard( $i ),
+ $res = $db->select( $this->getTableByShard( $i ),
array( 'keyname' ), false, __METHOD__ );
foreach ( $res as $row ) {
$result[] = $row->keyname;
@@ -311,18 +311,67 @@ class SqlBagOStuff extends BagOStuff {
/**
* Delete objects from the database which expire before a certain date.
*/
- public function deleteObjectsExpiringBefore( $timestamp ) {
+ public function deleteObjectsExpiringBefore( $timestamp, $progressCallback = false ) {
$db = $this->getDB();
$dbTimestamp = $db->timestamp( $timestamp );
+ $totalSeconds = false;
+ $baseConds = array( 'exptime < ' . $db->addQuotes( $dbTimestamp ) );
try {
for ( $i = 0; $i < $this->shards; $i++ ) {
- $db->begin();
- $db->delete(
- $this->getTableByShard( $i ),
- array( 'exptime < ' . $db->addQuotes( $dbTimestamp ) ),
- __METHOD__ );
- $db->commit();
+ $maxExpTime = false;
+ while ( true ) {
+ $conds = $baseConds;
+ if ( $maxExpTime !== false ) {
+ $conds[] = 'exptime > ' . $db->addQuotes( $maxExpTime );
+ }
+ $rows = $db->select(
+ $this->getTableByShard( $i ),
+ array( 'keyname', 'exptime' ),
+ $conds,
+ __METHOD__,
+ array( 'LIMIT' => 100, 'ORDER BY' => 'exptime' ) );
+ if ( !$rows->numRows() ) {
+ break;
+ }
+ $keys = array();
+ $row = $rows->current();
+ $minExpTime = $row->exptime;
+ if ( $totalSeconds === false ) {
+ $totalSeconds = wfTimestamp( TS_UNIX, $timestamp )
+ - wfTimestamp( TS_UNIX, $minExpTime );
+ }
+ foreach ( $rows as $row ) {
+ $keys[] = $row->keyname;
+ $maxExpTime = $row->exptime;
+ }
+
+ $db->begin( __METHOD__ );
+ $db->delete(
+ $this->getTableByShard( $i ),
+ array(
+ 'exptime >= ' . $db->addQuotes( $minExpTime ),
+ 'exptime < ' . $db->addQuotes( $dbTimestamp ),
+ 'keyname' => $keys
+ ),
+ __METHOD__ );
+ $db->commit( __METHOD__ );
+
+ if ( $progressCallback ) {
+ if ( intval( $totalSeconds ) === 0 ) {
+ $percent = 0;
+ } else {
+ $remainingSeconds = wfTimestamp( TS_UNIX, $timestamp )
+ - wfTimestamp( TS_UNIX, $maxExpTime );
+ if ( $remainingSeconds > $totalSeconds ) {
+ $totalSeconds = $remainingSeconds;
+ }
+ $percent = ( $i + $remainingSeconds / $totalSeconds )
+ / $this->shards * 100;
+ }
+ call_user_func( $progressCallback, $percent );
+ }
+ }
}
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e );
@@ -335,9 +384,9 @@ class SqlBagOStuff extends BagOStuff {
try {
for ( $i = 0; $i < $this->shards; $i++ ) {
- $db->begin();
+ $db->begin( __METHOD__ );
$db->delete( $this->getTableByShard( $i ), '*', __METHOD__ );
- $db->commit();
+ $db->commit( __METHOD__ );
}
} catch ( DBQueryError $e ) {
$this->handleWriteError( $e );
@@ -415,12 +464,12 @@ class SqlBagOStuff extends BagOStuff {
}
for ( $i = 0; $i < $this->shards; $i++ ) {
- $db->begin();
+ $db->begin( __METHOD__ );
$db->query(
'CREATE TABLE ' . $db->tableName( $this->getTableByShard( $i ) ) .
' LIKE ' . $db->tableName( 'objectcache' ),
__METHOD__ );
- $db->commit();
+ $db->commit( __METHOD__ );
}
}
}
diff --git a/includes/objectcache/eAccelBagOStuff.php b/includes/objectcache/eAccelBagOStuff.php
deleted file mode 100644
index 30d24e80..00000000
--- a/includes/objectcache/eAccelBagOStuff.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?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/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php
index 5dffd978..0e5702b7 100644
--- a/includes/parser/CoreParserFunctions.php
+++ b/includes/parser/CoreParserFunctions.php
@@ -97,7 +97,7 @@ class CoreParserFunctions {
static function intFunction( $parser, $part1 = '' /*, ... */ ) {
if ( strval( $part1 ) !== '' ) {
$args = array_slice( func_get_args(), 2 );
- $message = wfMessage( $part1, $args )->inLanguage( $parser->getOptions()->getUserLang() )->plain();
+ $message = wfMessage( $part1, $args )->inLanguage( $parser->getOptions()->getUserLangObj() )->plain();
return array( $message, 'noparse' => false );
} else {
return array( 'found' => false );
@@ -279,7 +279,14 @@ class CoreParserFunctions {
*/
static function gender( $parser, $username ) {
wfProfileIn( __METHOD__ );
- $forms = array_slice( func_get_args(), 2);
+ $forms = array_slice( func_get_args(), 2 );
+
+ // Some shortcuts to avoid loading user data unnecessarily
+ if ( count( $forms ) === 0 ) {
+ return '';
+ } elseif ( count( $forms ) === 1 ) {
+ return $forms[0];
+ }
$username = trim( $username );
@@ -564,7 +571,11 @@ class CoreParserFunctions {
* to the link cache, so the local cache here should be unnecessary, but
* in fact calling getLength() repeatedly for the same $page does seem to
* run one query for each call?
+ * @todo Document parameters
+ *
* @param $parser Parser
+ * @param $page String TODO DOCUMENT (Default: empty string)
+ * @param $raw TODO DOCUMENT (Default: null)
*/
static function pagesize( $parser, $page = '', $raw = null ) {
static $cache = array();
@@ -625,7 +636,7 @@ class CoreParserFunctions {
/**
* Unicode-safe str_pad with the restriction that $length is forced to be <= 500
- */
+ */
static function pad( $parser, $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) {
$padding = $parser->killMarkers( $padding );
$lengthOfPadding = mb_strlen( $padding );
@@ -680,23 +691,36 @@ class CoreParserFunctions {
/**
* @param $parser Parser
- * @param $text
+ * @param $text String The sortkey to use
+ * @param $uarg String Either "noreplace" or "noerror" (in en)
+ * both suppress errors, and noreplace does nothing if
+ * a default sortkey already exists.
* @return string
*/
- public static function defaultsort( $parser, $text ) {
+ public static function defaultsort( $parser, $text, $uarg = '' ) {
+ static $magicWords = null;
+ if ( is_null( $magicWords ) ) {
+ $magicWords = new MagicWordArray( array( 'defaultsort_noerror', 'defaultsort_noreplace' ) );
+ }
+ $arg = $magicWords->matchStartToEnd( $uarg );
+
$text = trim( $text );
if( strlen( $text ) == 0 )
return '';
$old = $parser->getCustomDefaultSort();
- $parser->setDefaultSort( $text );
- if( $old === false || $old == $text )
+ if ( $old === false || $arg !== 'defaultsort_noreplace' ) {
+ $parser->setDefaultSort( $text );
+ }
+
+ if( $old === false || $old == $text || $arg ) {
return '';
- else
+ } else {
return( '<span class="error">' .
wfMsgForContent( 'duplicate-defaultsort',
htmlspecialchars( $old ),
htmlspecialchars( $text ) ) .
'</span>' );
+ }
}
// Usage {{filepath|300}}, {{filepath|nowiki}}, {{filepath|nowiki|300}} or {{filepath|300|nowiki}}
@@ -724,7 +748,7 @@ class CoreParserFunctions {
if ( $file ) {
$url = $file->getFullUrl();
- // If a size is requested...
+ // If a size is requested...
if ( is_integer( $size ) ) {
$mto = $file->transform( array( 'width' => $size ) );
// ... and we can
diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php
index 5418b6e5..fb013047 100644
--- a/includes/parser/LinkHolderArray.php
+++ b/includes/parser/LinkHolderArray.php
@@ -320,7 +320,7 @@ class LinkHolderArray {
foreach ( $res as $s ) {
$title = Title::makeTitle( $s->page_namespace, $s->page_title );
$pdbk = $title->getPrefixedDBkey();
- $linkCache->addGoodLinkObj( $s->page_id, $title, $s->page_len, $s->page_is_redirect, $s->page_latest );
+ $linkCache->addGoodLinkObjFromRow( $title, $s );
$output->addLink( $title, $s->page_id );
# @todo FIXME: Convoluted data flow
# The redirect status and length is passed to getLinkColour via the LinkCache
@@ -490,7 +490,7 @@ class LinkHolderArray {
// construct query
$dbr = wfGetDB( DB_SLAVE );
$varRes = $dbr->select( 'page',
- array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len' ),
+ array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect', 'page_len', 'page_latest' ),
$linkBatch->constructSet( 'page', $dbr ),
__METHOD__
);
@@ -507,7 +507,7 @@ class LinkHolderArray {
$holderKeys = array();
if( isset( $variantMap[$varPdbk] ) ) {
$holderKeys = $variantMap[$varPdbk];
- $linkCache->addGoodLinkObj( $s->page_id, $variantTitle, $s->page_len, $s->page_is_redirect );
+ $linkCache->addGoodLinkObjFromRow( $variantTitle, $s );
$output->addLink( $variantTitle, $s->page_id );
}
diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php
index 939d9e3f..2abf1b93 100644
--- a/includes/parser/Parser.php
+++ b/includes/parser/Parser.php
@@ -32,9 +32,9 @@
* Removes <noinclude> sections, and <includeonly> tags.
*
* Globals used:
- * objects: $wgLang, $wgContLang
+ * object: $wgContLang
*
- * NOT $wgUser or $wgTitle. Keep them away!
+ * NOT $wgUser or $wgTitle or $wgRequest or $wgLang. Keep them away!
*
* settings:
* $wgUseDynamicDates*, $wgInterwikiMagic*,
@@ -68,9 +68,11 @@ class Parser {
# Constants needed for external link processing
# Everything except bracket, space, or control characters
- const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F]';
- const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F]+)
- \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sx';
+ # \p{Zs} is unicode 'separator, space' category. It covers the space 0x20
+ # as well as U+3000 is IDEOGRAPHIC SPACE for bug 19052
+ const EXT_LINK_URL_CLASS = '[^][<>"\\x00-\\x20\\x7F\p{Zs}]';
+ const EXT_IMAGE_REGEX = '/^(http:\/\/|https:\/\/)([^][<>"\\x00-\\x20\\x7F\p{Zs}]+)
+ \\/([A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]+)\\.((?i)gif|png|jpg|jpeg)$/Sxu';
# State constants for the definition list colon extraction
const COLON_STATE_TEXT = 0;
@@ -146,6 +148,7 @@ class Parser {
var $mTplExpandCache; # empty-frame expansion cache
var $mTplRedirCache, $mTplDomCache, $mHeadings, $mDoubleUnderscores;
var $mExpensiveFunctionCount; # number of expensive parser function calls
+ var $mShowToc, $mForceTocPosition;
/**
* @var User
@@ -179,12 +182,14 @@ class Parser {
/**
* Constructor
+ *
+ * @param $conf array
*/
public function __construct( $conf = array() ) {
$this->mConf = $conf;
$this->mUrlProtocols = wfUrlProtocols();
$this->mExtLinkBracketedRegex = '/\[((' . wfUrlProtocols() . ')'.
- '[^][<>"\\x00-\\x20\\x7F]+) *([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/S';
+ self::EXT_LINK_URL_CLASS.'+)\p{Zs}*([^\]\\x00-\\x08\\x0a-\\x1F]*?)\]/Su';
if ( isset( $conf['preprocessorClass'] ) ) {
$this->mPreprocessorClass = $conf['preprocessorClass'];
} elseif ( defined( 'MW_COMPILED' ) ) {
@@ -316,7 +321,7 @@ class Parser {
* to internalParse() which does all the real work.
*/
- global $wgUseTidy, $wgAlwaysUseTidy, $wgContLang, $wgDisableLangConversion, $wgDisableTitleConversion;
+ global $wgUseTidy, $wgAlwaysUseTidy, $wgDisableLangConversion, $wgDisableTitleConversion;
$fname = __METHOD__.'-' . wfGetCaller();
wfProfileIn( __METHOD__ );
wfProfileIn( $fname );
@@ -345,7 +350,7 @@ class Parser {
$fixtags = array(
# french spaces, last one Guillemet-left
# only if there is something before the space
- '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;\\2',
+ '/(.) (?=\\?|:|;|!|%|\\302\\273)/' => '\\1&#160;',
# french spaces, Guillemet-right
'/(\\302\\253) /' => '\\1&#160;',
'/&#160;(!\s*important)/' => ' \\1', # Beware of CSS magic word !important, bug #11874.
@@ -357,20 +362,24 @@ class Parser {
$this->replaceLinkHolders( $text );
/**
- * The page doesn't get language converted if
+ * The input doesn't get language converted if
* a) It's disabled
* b) Content isn't converted
* c) It's a conversion table
+ * d) it is an interface message (which is in the user language)
*/
if ( !( $wgDisableLangConversion
|| isset( $this->mDoubleUnderscores['nocontentconvert'] )
- || $this->mTitle->isConversionTable() ) ) {
-
- # The position of the convert() call should not be changed. it
- # assumes that the links are all replaced and the only thing left
- # is the <nowiki> mark.
-
- $text = $wgContLang->convert( $text );
+ || $this->mTitle->isConversionTable() ) )
+ {
+ # Run convert unconditionally in 1.18-compatible mode
+ global $wgBug34832TransitionalRollback;
+ if ( $wgBug34832TransitionalRollback || !$this->mOptions->getInterfaceMessage() ) {
+ # The position of the convert() call should not be changed. it
+ # assumes that the links are all replaced and the only thing left
+ # is the <nowiki> mark.
+ $text = $this->getConverterLanguage()->convert( $text );
+ }
}
/**
@@ -386,11 +395,11 @@ class Parser {
|| isset( $this->mDoubleUnderscores['notitleconvert'] )
|| $this->mOutput->getDisplayTitle() !== false ) )
{
- $convruletitle = $wgContLang->getConvRuleTitle();
+ $convruletitle = $this->getConverterLanguage()->getConvRuleTitle();
if ( $convruletitle ) {
$this->mOutput->setTitleText( $convruletitle );
} else {
- $titleText = $wgContLang->convertTitle( $title );
+ $titleText = $this->getConverterLanguage()->convertTitle( $title );
$this->mOutput->setTitleText( $titleText );
}
}
@@ -504,10 +513,32 @@ class Parser {
}
/**
+ * Recursive parser entry point that can be called from an extension tag
+ * hook.
+ *
+ * @param $text String: text to be expanded
+ * @param $frame PPFrame: The frame to use for expanding any template variables
+ * @return String
+ * @since 1.19
+ */
+ public function recursivePreprocess( $text, $frame = false ) {
+ wfProfileIn( __METHOD__ );
+ $text = $this->replaceVariables( $text, $frame );
+ $text = $this->mStripState->unstripBoth( $text );
+ wfProfileOut( __METHOD__ );
+ return $text;
+ }
+
+ /**
* Process the wikitext for the ?preload= feature. (bug 5210)
*
* <noinclude>, <includeonly> etc. are parsed as for template transclusion,
* comments, templates, arguments, tags hooks and parser functions are untouched.
+ *
+ * @param $text String
+ * @param $title Title
+ * @param $options ParserOptions
+ * @return String
*/
public function getPreloadText( $text, Title $title, ParserOptions $options ) {
# Parser (re)initialisation
@@ -664,15 +695,23 @@ class Parser {
}
/**
+ * Get a language object for use in parser functions such as {{FORMATNUM:}}
* @return Language
*/
function getFunctionLang() {
+ return $this->getTargetLanguage();
+ }
+
+ /**
+ * Get the target language for the content being parsed. This is usually the
+ * language that the content is in.
+ */
+ function getTargetLanguage() {
$target = $this->mOptions->getTargetLanguage();
if ( $target !== null ) {
return $target;
} elseif( $this->mOptions->getInterfaceMessage() ) {
- global $wgLang;
- return $wgLang;
+ return $this->mOptions->getUserLangObj();
} elseif( is_null( $this->mTitle ) ) {
throw new MWException( __METHOD__.': $this->mTitle is null' );
}
@@ -680,6 +719,18 @@ class Parser {
}
/**
+ * Get the language object for language conversion
+ */
+ function getConverterLanguage() {
+ global $wgBug34832TransitionalRollback, $wgContLang;
+ if ( $wgBug34832TransitionalRollback ) {
+ return $wgContLang;
+ } else {
+ return $this->getTargetLanguage();
+ }
+ }
+
+ /**
* Get a User object either from $this->mUser, if set, or from the
* ParserOptions object otherwise
*
@@ -797,6 +848,10 @@ class Parser {
* 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()
+ *
+ * @param $text string
+ *
+ * @return string
*/
function insertStripItem( $text ) {
$rnd = "{$this->mUniqPrefix}-item-{$this->mMarkerIndex}-" . self::MARKER_SUFFIX;
@@ -1005,8 +1060,14 @@ class Parser {
* HTML. Only called for $mOutputType == self::OT_HTML.
*
* @private
+ *
+ * @param $text string
+ * @param $isMain bool
+ * @param $frame bool
+ *
+ * @return string
*/
- function internalParse( $text, $isMain = true, $frame=false ) {
+ function internalParse( $text, $isMain = true, $frame = false ) {
wfProfileIn( __METHOD__ );
$origText = $text;
@@ -1072,6 +1133,10 @@ class Parser {
*
* DML
* @private
+ *
+ * @param $text string
+ *
+ * @return string
*/
function doMagicLinks( $text ) {
wfProfileIn( __METHOD__ );
@@ -1088,7 +1153,7 @@ class Parser {
(?: [0-9] [\ \-]? ){9} # 9 digits with opt. delimiters
[0-9Xx] # check digit
\b)
- )!x', array( &$this, 'magicLinkCallback' ), $text );
+ )!xu', array( &$this, 'magicLinkCallback' ), $text );
wfProfileOut( __METHOD__ );
return $text;
}
@@ -1136,7 +1201,7 @@ class Parser {
));
$titleObj = SpecialPage::getTitleFor( 'Booksources', $num );
return'<a href="' .
- $titleObj->escapeLocalUrl() .
+ htmlspecialchars( $titleObj->getLocalUrl() ) .
"\" class=\"internal mw-magiclink-isbn\">ISBN $isbn</a>";
} else {
return $m[0];
@@ -1145,11 +1210,13 @@ class Parser {
/**
* Make a free external link, given a user-supplied URL
- * @return HTML
+ *
+ * @param $url string
+ *
+ * @return string HTML
* @private
*/
function makeFreeExternalLink( $url ) {
- global $wgContLang;
wfProfileIn( __METHOD__ );
$trail = '';
@@ -1182,7 +1249,8 @@ class Parser {
$text = $this->maybeMakeExternalImage( $url );
if ( $text === false ) {
# Not an image, make a link
- $text = Linker::makeExternalLink( $url, $wgContLang->markNoConversion($url), true, 'free',
+ $text = Linker::makeExternalLink( $url,
+ $this->getConverterLanguage()->markNoConversion($url), true, 'free',
$this->getExternalLinkAttribs( $url ) );
# Register it in the output object...
# Replace unnecessary URL escape codes with their equivalent characters
@@ -1198,6 +1266,10 @@ class Parser {
* Parse headers and return html
*
* @private
+ *
+ * @param $text string
+ *
+ * @return string
*/
function doHeadings( $text ) {
wfProfileIn( __METHOD__ );
@@ -1213,6 +1285,9 @@ class Parser {
/**
* Replace single quotes with HTML markup
* @private
+ *
+ * @param $text string
+ *
* @return string the altered text
*/
function doAllQuotes( $text ) {
@@ -1229,6 +1304,10 @@ class Parser {
/**
* Helper function for doAllQuotes()
+ *
+ * @param $text string
+ *
+ * @return string
*/
public function doQuotes( $text ) {
$arr = preg_split( "/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE );
@@ -1393,9 +1472,12 @@ class Parser {
* Make sure to run maintenance/parserTests.php if you change this code.
*
* @private
+ *
+ * @param $text string
+ *
+ * @return string
*/
function replaceExternalLinks( $text ) {
- global $wgContLang;
wfProfileIn( __METHOD__ );
$bits = preg_split( $this->mExtLinkBracketedRegex, $text, -1, PREG_SPLIT_DELIM_CAPTURE );
@@ -1432,7 +1514,7 @@ class Parser {
# No link text, e.g. [http://domain.tld/some.link]
if ( $text == '' ) {
# Autonumber
- $langObj = $this->getFunctionLang();
+ $langObj = $this->getTargetLanguage();
$text = '[' . $langObj->formatNum( ++$this->mAutonumber ) . ']';
$linktype = 'autonumber';
} else {
@@ -1441,7 +1523,7 @@ class Parser {
list( $dtrail, $trail ) = Linker::splitTrail( $trail );
}
- $text = $wgContLang->markNoConversion( $text );
+ $text = $this->getConverterLanguage()->markNoConversion( $text );
$url = Sanitizer::cleanUrl( $url );
@@ -1469,9 +1551,9 @@ class Parser {
* (depending on configuration, namespace, and the URL's domain) and/or a
* target attribute (depending on configuration).
*
- * @param $url String: optional URL, to extract the domain from for rel =>
+ * @param $url String|bool optional URL, to extract the domain from for rel =>
* nofollow if appropriate
- * @return Array: associative array of HTML attributes
+ * @return Array associative array of HTML attributes
*/
function getExternalLinkAttribs( $url = false ) {
$attribs = array();
@@ -1507,6 +1589,10 @@ class Parser {
/**
* Callback function used in replaceUnusualEscapes().
* Replaces unusual URL escape codes with their equivalent character
+ *
+ * @param $matches array
+ *
+ * @return string
*/
private static function replaceUnusualEscapesCallback( $matches ) {
$char = urldecode( $matches[0] );
@@ -1525,6 +1611,10 @@ class Parser {
* make an image if it's allowed, either through the global
* option, through the exception, or through the on-wiki whitelist
* @private
+ *
+ * $param $url string
+ *
+ * @return string
*/
function maybeMakeExternalImage( $url ) {
$imagesfrom = $this->mOptions->getAllowExternalImagesFrom();
@@ -1571,6 +1661,9 @@ class Parser {
/**
* Process [[ ]] wikilinks
+ *
+ * @param $s string
+ *
* @return String: processed text
*
* @private
@@ -1587,8 +1680,6 @@ class Parser {
* @private
*/
function replaceInternalLinks2( &$s ) {
- global $wgContLang;
-
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__.'-setup' );
@@ -1612,7 +1703,7 @@ class Parser {
$line = $a->current(); # Workaround for broken ArrayIterator::next() that returns "void"
$s = substr( $s, 1 );
- $useLinkPrefixExtension = $wgContLang->linkPrefixExtension();
+ $useLinkPrefixExtension = $this->getTargetLanguage()->linkPrefixExtension();
$e2 = null;
if ( $useLinkPrefixExtension ) {
# Match the end of a line for a word that's not followed by whitespace,
@@ -1638,8 +1729,9 @@ class Parser {
$prefix = '';
}
- if ( $wgContLang->hasVariants() ) {
- $selflink = $wgContLang->autoConvertToAllVariants( $this->mTitle->getPrefixedText() );
+ if ( $this->getConverterLanguage()->hasVariants() ) {
+ $selflink = $this->getConverterLanguage()->autoConvertToAllVariants(
+ $this->mTitle->getPrefixedText() );
} else {
$selflink = array( $this->mTitle->getPrefixedText() );
}
@@ -1807,6 +1899,7 @@ class Parser {
# Link not escaped by : , create the various objects
if ( $noforce ) {
+ global $wgContLang;
# Interwikis
wfProfileIn( __METHOD__."-interwiki" );
@@ -1856,7 +1949,7 @@ class Parser {
}
$sortkey = Sanitizer::decodeCharReferences( $sortkey );
$sortkey = str_replace( "\n", '', $sortkey );
- $sortkey = $wgContLang->convertCategoryKey( $sortkey );
+ $sortkey = $this->getConverterLanguage()->convertCategoryKey( $sortkey );
$this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
/**
@@ -1883,11 +1976,12 @@ class Parser {
if ( $ns == NS_MEDIA ) {
wfProfileIn( __METHOD__."-media" );
# Give extensions a chance to select the file revision for us
- $time = $sha1 = $descQuery = false;
+ $options = array();
+ $descQuery = false;
wfRunHooks( 'BeforeParserFetchFileAndTitle',
- array( $this, $nt, &$time, &$sha1, &$descQuery ) );
+ array( $this, $nt, &$options, &$descQuery ) );
# Fetch and register the file (file title may be different via hooks)
- list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $time, $sha1 );
+ list( $file, $nt ) = $this->fetchFileAndTitle( $nt, $options );
# Cloak with NOPARSE to avoid replacement in replaceExternalLinks
$s .= $prefix . $this->armorLinks(
Linker::makeMediaLinkFile( $nt, $file, $text ) ) . $trail;
@@ -1999,6 +2093,11 @@ class Parser {
* getCommon() returns the length of the longest common substring
* of both arguments, starting at the beginning of both.
* @private
+ *
+ * @param $st1 string
+ * @param $st2 string
+ *
+ * @return int
*/
function getCommon( $st1, $st2 ) {
$fl = strlen( $st1 );
@@ -2020,6 +2119,8 @@ class Parser {
* element appropriate to the prefix character passed into them.
* @private
*
+ * @param $char char
+ *
* @return string
*/
function openList( $char ) {
@@ -2283,10 +2384,10 @@ class Parser {
* Split up a string on ':', ignoring any occurences inside tags
* to prevent illegal overlapping.
*
- * @param $str String: the string to split
- * @param &$before String: set to everything before the ':'
- * @param &$after String: set to everything after the ':'
- * return String: the position of the ':', or false if none found
+ * @param $str String the string to split
+ * @param &$before String set to everything before the ':'
+ * @param &$after String set to everything after the ':'
+ * @return String the position of the ':', or false if none found
*/
function findColonNoLinks( $str, &$before, &$after ) {
wfProfileIn( __METHOD__ );
@@ -2451,11 +2552,22 @@ class Parser {
*
* @param $index integer
* @param $frame PPFrame
+ *
+ * @return string
*/
- function getVariableValue( $index, $frame=false ) {
+ function getVariableValue( $index, $frame = false ) {
global $wgContLang, $wgSitename, $wgServer;
global $wgArticlePath, $wgScriptPath, $wgStylePath;
+ if ( is_null( $this->mTitle ) ) {
+ // If no title set, bad things are going to happen
+ // later. Title should always be set since this
+ // should only be called in the middle of a parse
+ // operation (but the unit-tests do funky stuff)
+ throw new MWException( __METHOD__ . ' Should only be '
+ . ' called while parsing (no title set)' );
+ }
+
/**
* Some of these require message or data lookups and can be
* expensive to check many times.
@@ -2490,48 +2602,50 @@ class Parser {
date_default_timezone_set( $oldtz );
}
+ $pageLang = $this->getFunctionLang();
+
switch ( $index ) {
case 'currentmonth':
- $value = $wgContLang->formatNum( gmdate( 'm', $ts ) );
+ $value = $pageLang->formatNum( gmdate( 'm', $ts ) );
break;
case 'currentmonth1':
- $value = $wgContLang->formatNum( gmdate( 'n', $ts ) );
+ $value = $pageLang->formatNum( gmdate( 'n', $ts ) );
break;
case 'currentmonthname':
- $value = $wgContLang->getMonthName( gmdate( 'n', $ts ) );
+ $value = $pageLang->getMonthName( gmdate( 'n', $ts ) );
break;
case 'currentmonthnamegen':
- $value = $wgContLang->getMonthNameGen( gmdate( 'n', $ts ) );
+ $value = $pageLang->getMonthNameGen( gmdate( 'n', $ts ) );
break;
case 'currentmonthabbrev':
- $value = $wgContLang->getMonthAbbreviation( gmdate( 'n', $ts ) );
+ $value = $pageLang->getMonthAbbreviation( gmdate( 'n', $ts ) );
break;
case 'currentday':
- $value = $wgContLang->formatNum( gmdate( 'j', $ts ) );
+ $value = $pageLang->formatNum( gmdate( 'j', $ts ) );
break;
case 'currentday2':
- $value = $wgContLang->formatNum( gmdate( 'd', $ts ) );
+ $value = $pageLang->formatNum( gmdate( 'd', $ts ) );
break;
case 'localmonth':
- $value = $wgContLang->formatNum( $localMonth );
+ $value = $pageLang->formatNum( $localMonth );
break;
case 'localmonth1':
- $value = $wgContLang->formatNum( $localMonth1 );
+ $value = $pageLang->formatNum( $localMonth1 );
break;
case 'localmonthname':
- $value = $wgContLang->getMonthName( $localMonthName );
+ $value = $pageLang->getMonthName( $localMonthName );
break;
case 'localmonthnamegen':
- $value = $wgContLang->getMonthNameGen( $localMonthName );
+ $value = $pageLang->getMonthNameGen( $localMonthName );
break;
case 'localmonthabbrev':
- $value = $wgContLang->getMonthAbbreviation( $localMonthName );
+ $value = $pageLang->getMonthAbbreviation( $localMonthName );
break;
case 'localday':
- $value = $wgContLang->formatNum( $localDay );
+ $value = $pageLang->formatNum( $localDay );
break;
case 'localday2':
- $value = $wgContLang->formatNum( $localDay2 );
+ $value = $pageLang->formatNum( $localDay2 );
break;
case 'pagename':
$value = wfEscapeWikiText( $this->mTitle->getText() );
@@ -2656,68 +2770,68 @@ class Parser {
$value = ( wfUrlencode( $this->mTitle->getSubjectNsText() ) );
break;
case 'currentdayname':
- $value = $wgContLang->getWeekdayName( gmdate( 'w', $ts ) + 1 );
+ $value = $pageLang->getWeekdayName( gmdate( 'w', $ts ) + 1 );
break;
case 'currentyear':
- $value = $wgContLang->formatNum( gmdate( 'Y', $ts ), true );
+ $value = $pageLang->formatNum( gmdate( 'Y', $ts ), true );
break;
case 'currenttime':
- $value = $wgContLang->time( wfTimestamp( TS_MW, $ts ), false, false );
+ $value = $pageLang->time( wfTimestamp( TS_MW, $ts ), false, false );
break;
case 'currenthour':
- $value = $wgContLang->formatNum( gmdate( 'H', $ts ), true );
+ $value = $pageLang->formatNum( gmdate( 'H', $ts ), true );
break;
case 'currentweek':
# @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
# int to remove the padding
- $value = $wgContLang->formatNum( (int)gmdate( 'W', $ts ) );
+ $value = $pageLang->formatNum( (int)gmdate( 'W', $ts ) );
break;
case 'currentdow':
- $value = $wgContLang->formatNum( gmdate( 'w', $ts ) );
+ $value = $pageLang->formatNum( gmdate( 'w', $ts ) );
break;
case 'localdayname':
- $value = $wgContLang->getWeekdayName( $localDayOfWeek + 1 );
+ $value = $pageLang->getWeekdayName( $localDayOfWeek + 1 );
break;
case 'localyear':
- $value = $wgContLang->formatNum( $localYear, true );
+ $value = $pageLang->formatNum( $localYear, true );
break;
case 'localtime':
- $value = $wgContLang->time( $localTimestamp, false, false );
+ $value = $pageLang->time( $localTimestamp, false, false );
break;
case 'localhour':
- $value = $wgContLang->formatNum( $localHour, true );
+ $value = $pageLang->formatNum( $localHour, true );
break;
case 'localweek':
# @bug 4594 PHP5 has it zero padded, PHP4 does not, cast to
# int to remove the padding
- $value = $wgContLang->formatNum( (int)$localWeek );
+ $value = $pageLang->formatNum( (int)$localWeek );
break;
case 'localdow':
- $value = $wgContLang->formatNum( $localDayOfWeek );
+ $value = $pageLang->formatNum( $localDayOfWeek );
break;
case 'numberofarticles':
- $value = $wgContLang->formatNum( SiteStats::articles() );
+ $value = $pageLang->formatNum( SiteStats::articles() );
break;
case 'numberoffiles':
- $value = $wgContLang->formatNum( SiteStats::images() );
+ $value = $pageLang->formatNum( SiteStats::images() );
break;
case 'numberofusers':
- $value = $wgContLang->formatNum( SiteStats::users() );
+ $value = $pageLang->formatNum( SiteStats::users() );
break;
case 'numberofactiveusers':
- $value = $wgContLang->formatNum( SiteStats::activeUsers() );
+ $value = $pageLang->formatNum( SiteStats::activeUsers() );
break;
case 'numberofpages':
- $value = $wgContLang->formatNum( SiteStats::pages() );
+ $value = $pageLang->formatNum( SiteStats::pages() );
break;
case 'numberofadmins':
- $value = $wgContLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
+ $value = $pageLang->formatNum( SiteStats::numberingroup( 'sysop' ) );
break;
case 'numberofedits':
- $value = $wgContLang->formatNum( SiteStats::edits() );
+ $value = $pageLang->formatNum( SiteStats::edits() );
break;
case 'numberofviews':
- $value = $wgContLang->formatNum( SiteStats::views() );
+ $value = $pageLang->formatNum( SiteStats::views() );
break;
case 'currenttimestamp':
$value = wfTimestamp( TS_MW, $ts );
@@ -2742,7 +2856,7 @@ class Parser {
case 'stylepath':
return $wgStylePath;
case 'directionmark':
- return $wgContLang->getDirMark();
+ return $pageLang->getDirMark();
case 'contentlanguage':
global $wgLanguageCode;
return $wgLanguageCode;
@@ -2755,8 +2869,9 @@ class Parser {
}
}
- if ( $index )
+ if ( $index ) {
$this->mVarCache[$index] = $value;
+ }
return $value;
}
@@ -2808,6 +2923,8 @@ class Parser {
/**
* Return a three-element array: leading whitespace, string contents, trailing whitespace
*
+ * @param $s string
+ *
* @return array
*/
public static function splitWhitespace( $s ) {
@@ -2833,11 +2950,11 @@ class Parser {
* self::OT_PREPROCESS: templates but not extension tags
* self::OT_HTML: all templates and extension tags
*
- * @param $text String: the text to transform
+ * @param $text String the text to transform
* @param $frame PPFrame Object describing the arguments passed to the template.
* Arguments may also be provided as an associative array, as was the usual case before MW1.12.
* Providing arguments this way may be useful for extensions wishing to perform variable replacement explicitly.
- * @param $argsOnly Boolean: only do argument (triple-brace) expansion, not double-brace expansion
+ * @param $argsOnly Boolean only do argument (triple-brace) expansion, not double-brace expansion
* @private
*
* @return string
@@ -2867,6 +2984,8 @@ class Parser {
/**
* Clean up argument array - refactored in 1.9 so parserfunctions can use it, too.
*
+ * @param $args array
+ *
* @return array
*/
static function createAssocArgs( $args ) {
@@ -2929,7 +3048,7 @@ class Parser {
* @private
*/
function braceSubstitution( $piece, $frame ) {
- global $wgContLang, $wgNonincludableNamespaces;
+ global $wgNonincludableNamespaces, $wgContLang;
wfProfileIn( __METHOD__ );
wfProfileIn( __METHOD__.'-setup' );
@@ -2957,7 +3076,8 @@ class Parser {
# @todo FIXME: If piece['parts'] is null then the call to getLength() below won't work b/c this $args isn't an object
$args = ( null == $piece['parts'] ) ? array() : $piece['parts'];
wfProfileOut( __METHOD__.'-setup' );
- wfProfileIn( __METHOD__."-title-$originalTitle" );
+
+ $titleProfileIn = null; // profile templates
# SUBST
wfProfileIn( __METHOD__.'-modifiers' );
@@ -3039,6 +3159,7 @@ class Parser {
}
}
if ( $function ) {
+ wfProfileIn( __METHOD__ . '-pfunc-' . $function );
list( $callback, $flags ) = $this->mFunctionHooks[$function];
$initialArgs = array( &$this );
$funcArgs = array( trim( substr( $part1, $colonPos + 1 ) ) );
@@ -3060,6 +3181,7 @@ class Parser {
# Workaround for PHP bug 35229 and similar
if ( !is_callable( $callback ) ) {
+ wfProfileOut( __METHOD__ . '-pfunc-' . $function );
wfProfileOut( __METHOD__ . '-pfunc' );
wfProfileOut( __METHOD__ );
throw new MWException( "Tag hook for $function is not callable\n" );
@@ -3085,6 +3207,7 @@ class Parser {
$text = $this->preprocessToDom( $text, $preprocessFlags );
$isChildObj = true;
}
+ wfProfileOut( __METHOD__ . '-pfunc-' . $function );
}
}
wfProfileOut( __METHOD__ . '-pfunc' );
@@ -3104,8 +3227,8 @@ class Parser {
if ( $title ) {
$titleText = $title->getPrefixedText();
# Check for language variants if the template is not found
- if ( $wgContLang->hasVariants() && $title->getArticleID() == 0 ) {
- $wgContLang->findVariantLink( $part1, $title, true );
+ if ( $this->getConverterLanguage()->hasVariants() && $title->getArticleID() == 0 ) {
+ $this->getConverterLanguage()->findVariantLink( $part1, $title, true );
}
# Do recursion depth check
$limit = $this->mOptions->getMaxTemplateDepth();
@@ -3120,14 +3243,37 @@ class Parser {
# Load from database
if ( !$found && $title ) {
+ $titleProfileIn = __METHOD__ . "-title-" . $title->getDBKey();
+ wfProfileIn( $titleProfileIn ); // template in
wfProfileIn( __METHOD__ . '-loadtpl' );
if ( !$title->isExternal() ) {
- if ( $title->getNamespace() == NS_SPECIAL
+ if ( $title->isSpecialPage()
&& $this->mOptions->getAllowSpecialInclusion()
&& $this->ot['html'] )
{
- $text = SpecialPageFactory::capturePath( $title );
- if ( is_string( $text ) ) {
+ // Pass the template arguments as URL parameters.
+ // "uselang" will have no effect since the Language object
+ // is forced to the one defined in ParserOptions.
+ $pageArgs = array();
+ for ( $i = 0; $i < $args->getLength(); $i++ ) {
+ $bits = $args->item( $i )->splitArg();
+ if ( strval( $bits['index'] ) === '' ) {
+ $name = trim( $frame->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
+ $value = trim( $frame->expand( $bits['value'] ) );
+ $pageArgs[$name] = $value;
+ }
+ }
+
+ // Create a new context to execute the special page
+ $context = new RequestContext;
+ $context->setTitle( $title );
+ $context->setRequest( new FauxRequest( $pageArgs ) );
+ $context->setUser( $this->getUser() );
+ $context->setLanguage( $this->mOptions->getUserLangObj() );
+ $ret = SpecialPageFactory::capturePath( $title, $context );
+ if ( $ret ) {
+ $text = $context->getOutput()->getHTML();
+ $this->mOutput->addOutputPageMetadata( $context->getOutput() );
$found = true;
$isHTML = true;
$this->disableCache();
@@ -3176,7 +3322,9 @@ class Parser {
# Recover the source wikitext and return it
if ( !$found ) {
$text = $frame->virtualBracketedImplode( '{{', '|', '}}', $titleWithSpaces, $args );
- wfProfileOut( __METHOD__."-title-$originalTitle" );
+ if ( $titleProfileIn ) {
+ wfProfileOut( $titleProfileIn ); // template out
+ }
wfProfileOut( __METHOD__ );
return array( 'object' => $text );
}
@@ -3206,6 +3354,10 @@ class Parser {
$isLocalObj = false;
}
+ if ( $titleProfileIn ) {
+ wfProfileOut( $titleProfileIn ); // template out
+ }
+
# Replace raw HTML by a placeholder
# Add a blank line preceding, to prevent it from mucking up
# immediately preceding headings
@@ -3245,7 +3397,6 @@ class Parser {
$ret = array( 'text' => $text );
}
- wfProfileOut( __METHOD__."-title-$originalTitle" );
wfProfileOut( __METHOD__ );
return $ret;
}
@@ -3254,6 +3405,8 @@ class Parser {
* Get the semi-parsed DOM representation of a template with a given title,
* and its redirect destination title. Cached.
*
+ * @param $title Title
+ *
* @return array
*/
function getTemplateDom( $title ) {
@@ -3320,6 +3473,9 @@ class Parser {
* Static function to get a template
* Can be overridden via ParserOptions::setTemplateCallback().
*
+ * @parma $title Title
+ * @param $parser Parser
+ *
* @return array
*/
static function statelessFetchTemplate( $title, $parser = false ) {
@@ -3394,30 +3550,30 @@ class Parser {
/**
* Fetch a file and its title and register a reference to it.
+ * If 'broken' is a key in $options then the file will appear as a broken thumbnail.
* @param Title $title
- * @param string $time MW timestamp
- * @param string $sha1 base 36 SHA-1
- * @return mixed File or false
+ * @param Array $options Array of options to RepoGroup::findFile
+ * @return File|false
*/
- function fetchFile( $title, $time = false, $sha1 = false ) {
- $res = $this->fetchFileAndTitle( $title, $time, $sha1 );
+ function fetchFile( $title, $options = array() ) {
+ $res = $this->fetchFileAndTitle( $title, $options );
return $res[0];
}
/**
* Fetch a file and its title and register a reference to it.
+ * If 'broken' is a key in $options then the file will appear as a broken thumbnail.
* @param Title $title
- * @param string $time MW timestamp
- * @param string $sha1 base 36 SHA-1
+ * @param Array $options Array of options to RepoGroup::findFile
* @return Array ( File or false, Title of file )
*/
- function fetchFileAndTitle( $title, $time = false, $sha1 = false ) {
- if ( $time === '0' ) {
+ function fetchFileAndTitle( $title, $options = array() ) {
+ if ( isset( $options['broken'] ) ) {
$file = false; // broken thumbnail forced by hook
- } elseif ( $sha1 ) { // get by (sha1,timestamp)
- $file = RepoGroup::singleton()->findFileFromKey( $sha1, array( 'time' => $time ) );
+ } elseif ( isset( $options['sha1'] ) ) { // get by (sha1,timestamp)
+ $file = RepoGroup::singleton()->findFileFromKey( $options['sha1'], $options );
} else { // get by (name,timestamp)
- $file = wfFindFile( $title, array( 'time' => $time ) );
+ $file = wfFindFile( $title, $options );
}
$time = $file ? $file->getTimestamp() : false;
$sha1 = $file ? $file->getSha1() : false;
@@ -3660,6 +3816,10 @@ class Parser {
/**
* Strip double-underscore items like __NOGALLERY__ and __NOTOC__
* Fills $this->mDoubleUnderscores, returns the modified text
+ *
+ * @param $text string
+ *
+ * @return string
*/
function doDoubleUnderscore( $text ) {
wfProfileIn( __METHOD__ );
@@ -3719,12 +3879,16 @@ class Parser {
* @param $msg String: message key
* @return Boolean: whether the addition was successful
*/
- protected function addTrackingCategory( $msg ) {
+ public 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 );
+ // Important to parse with correct title (bug 31469)
+ $cat = wfMessage( $msg )
+ ->title( $this->getTitle() )
+ ->inContentLanguage()
+ ->text();
# Allow tracking categories to be disabled by setting them to "-"
if ( $cat === '-' ) {
@@ -3761,8 +3925,9 @@ class Parser {
# Inhibit editsection links if requested in the page
if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) {
- $showEditLink = 0;
+ $maybeShowEditLink = $showEditLink = false;
} else {
+ $maybeShowEditLink = true; /* Actual presence will depend on ParserOptions option */
$showEditLink = $this->mOptions->getEditSection();
}
if ( $showEditLink ) {
@@ -3894,24 +4059,28 @@ class Parser {
if ( $dot ) {
$numbering .= '.';
}
- $numbering .= $this->getFunctionLang()->formatNum( $sublevelCount[$i] );
+ $numbering .= $this->getTargetLanguage()->formatNum( $sublevelCount[$i] );
$dot = 1;
}
}
# The safe header is a version of the header text safe to use for links
- # Avoid insertion of weird stuff like <math> by expanding the relevant sections
- $safeHeadline = $this->mStripState->unstripBoth( $headline );
# Remove link placeholders by the link text.
# <!--LINK number-->
# turns into
# link text with suffix
- $safeHeadline = $this->replaceLinkHoldersText( $safeHeadline );
+ # Do this before unstrip since link text can contain strip markers
+ $safeHeadline = $this->replaceLinkHoldersText( $headline );
- # Strip out HTML (other than plain <sup> and <sub>: bug 8393)
+ # Avoid insertion of weird stuff like <math> by expanding the relevant sections
+ $safeHeadline = $this->mStripState->unstripBoth( $safeHeadline );
+
+ # Strip out HTML (first regex removes any tag not allowed)
+ # Allowed tags are <sup> and <sub> (bug 8393), <i> (bug 26375) and <b> (r105284)
+ # We strip any parameter from accepted tags (second regex)
$tocline = preg_replace(
- array( '#<(?!/?(sup|sub)).*?'.'>#', '#<(/?(sup|sub)).*?'.'>#' ),
+ array( '#<(?!/?(sup|sub|i|b)(?: [^>]*)?>).*?'.'>#', '#<(/?(sup|sub|i|b))(?: .*?)?'.'>#' ),
array( '', '<$1>' ),
$safeHeadline
);
@@ -4017,7 +4186,7 @@ class Parser {
);
# give headline the correct <h#> tag
- if ( $sectionIndex !== false ) {
+ if ( $maybeShowEditLink && $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()
@@ -4061,7 +4230,7 @@ class Parser {
if ( $prevtoclevel > 0 && $prevtoclevel < $wgMaxTocLevel ) {
$toc .= Linker::tocUnindent( $prevtoclevel - 1 );
}
- $toc = Linker::tocList( $toc, $this->mOptions->getUserLang() );
+ $toc = Linker::tocList( $toc, $this->mOptions->getUserLangObj() );
$this->mOutput->setTOCHTML( $toc );
}
@@ -4070,30 +4239,42 @@ class Parser {
}
# split up and insert constructed headlines
-
$blocks = preg_split( '/<H[1-6].*?' . '>.*?<\/H[1-6]>/i', $text );
$i = 0;
+ // build an array of document sections
+ $sections = array();
foreach ( $blocks as $block ) {
- if ( $showEditLink && $headlineCount > 0 && $i == 0 && $block !== "\n" ) {
- # This is the [edit] link that appears for the top block of text when
- # section editing is enabled
-
- # Disabled because it broke block formatting
- # For example, a bullet point in the top line
- # $full .= $sk->editSectionLink(0);
- }
- $full .= $block;
- if ( $enoughToc && !$i && $isMain && !$this->mForceTocPosition ) {
- # Top anchor now in skin
- $full = $full.$toc;
+ // $head is zero-based, sections aren't.
+ if ( empty( $head[$i - 1] ) ) {
+ $sections[$i] = $block;
+ } else {
+ $sections[$i] = $head[$i - 1] . $block;
}
- if ( !empty( $head[$i] ) ) {
- $full .= $head[$i];
- }
+ /**
+ * Send a hook, one per section.
+ * The idea here is to be able to make section-level DIVs, but to do so in a
+ * lower-impact, more correct way than r50769
+ *
+ * $this : caller
+ * $section : the section number
+ * &$sectionContent : ref to the content of the section
+ * $showEditLinks : boolean describing whether this section has an edit link
+ */
+ wfRunHooks( 'ParserSectionCreate', array( $this, $i, &$sections[$i], $showEditLink ) );
+
$i++;
}
+
+ if ( $enoughToc && $isMain && !$this->mForceTocPosition ) {
+ // append the TOC at the beginning
+ // Top anchor now in skin
+ $sections[0] = $sections[0] . $toc . "\n";
+ }
+
+ $full .= join( '', $sections );
+
if ( $this->mForceTocPosition ) {
return str_replace( '<!--MWTOC-->', $toc, $full );
} else {
@@ -4133,6 +4314,11 @@ class Parser {
/**
* Pre-save transform helper function
* @private
+ *
+ * @param $text string
+ * @param $user User
+ *
+ * @return string
*/
function pstPass2( $text, $user ) {
global $wgContLang, $wgLocaltimezone;
@@ -4188,9 +4374,9 @@ class Parser {
$tc = "[$wgLegalTitleChars]";
$nc = '[ _0-9A-Za-z\x80-\xff-]'; # Namespaces can use non-ascii!
- $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\))\\|]]/"; # [[ns:page (context)|]]
- $p4 = "/\[\[(:?$nc+:|:|)($tc+?)(($tc+))\\|]]/"; # [[ns:page(context)|]]
- $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( \\($tc+\\)|)(, $tc+|)\\|]]/"; # [[ns:page (context), context|]]
+ $p1 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\))\\|]]/"; # [[ns:page (context)|]]
+ $p4 = "/\[\[(:?$nc+:|:|)($tc+?)( ?($tc+))\\|]]/"; # [[ns:page(context)|]]
+ $p3 = "/\[\[(:?$nc+:|:|)($tc+?)( ?\\($tc+\\)|)(, $tc+|)\\|]]/"; # [[ns:page (context), context|]]
$p2 = "/\[\[\\|($tc+)]]/"; # [[|page]]
# try $p1 first, to turn "[[A, B (C)|]]" into "[[A, B (C)|A, B]]"
@@ -4224,8 +4410,8 @@ class Parser {
* 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
- * @param $fancySig Boolean: whether the nicknname is the complete signature
+ * @param $nickname String|bool nickname to use or false to use user's default nickname
+ * @param $fancySig Boolean|null whether the nicknname is the complete signature
* or null to use default value
* @return string
*/
@@ -4260,7 +4446,7 @@ class Parser {
}
# Make sure nickname doesnt get a sig in a sig
- $nickname = $this->cleanSigInSig( $nickname );
+ $nickname = self::cleanSigInSig( $nickname );
# If we're still here, make it a link to the user page
$userText = wfEscapeWikiText( $username );
@@ -4287,16 +4473,13 @@ class Parser {
* 2) Substitute all transclusions
*
* @param $text String
- * @param $parsing Whether we're cleaning (preferences save) or parsing
+ * @param $parsing bool Whether we're cleaning (preferences save) or parsing
* @return String: signature text
*/
- function cleanSig( $text, $parsing = false ) {
+ public function cleanSig( $text, $parsing = false ) {
if ( !$parsing ) {
global $wgTitle;
- $this->mOptions = new ParserOptions;
- $this->clearState();
- $this->setTitle( $wgTitle );
- $this->setOutputType = self::OT_PREPROCESS;
+ $this->startParse( $wgTitle, new ParserOptions, self::OT_PREPROCESS, true );
}
# Option to disable this feature
@@ -4311,7 +4494,7 @@ class Parser {
$substText = '{{' . $substWord->getSynonym( 0 );
$text = preg_replace( $substRegex, $substText, $text );
- $text = $this->cleanSigInSig( $text );
+ $text = self::cleanSigInSig( $text );
$dom = $this->preprocessToDom( $text );
$frame = $this->getPreprocessor()->newFrame();
$text = $frame->expand( $dom );
@@ -4329,7 +4512,7 @@ class Parser {
* @param $text String
* @return String: signature text with /~{3,5}/ removed
*/
- function cleanSigInSig( $text ) {
+ public static function cleanSigInSig( $text ) {
$text = preg_replace( '/~{3,5}/', '', $text );
return $text;
}
@@ -4337,11 +4520,22 @@ class Parser {
/**
* Set up some variables which are usually set up in parse()
* so that an external function can call some class members with confidence
+ *
+ * @param $title Title|null
+ * @param $options ParserOptions
+ * @param $outputType
+ * @param $clearState bool
*/
public function startExternalParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
$this->startParse( $title, $options, $outputType, $clearState );
}
+ /**
+ * @param $title Title|null
+ * @param $options ParserOptions
+ * @param $outputType
+ * @param $clearState bool
+ */
private function startParse( Title $title = null, ParserOptions $options, $outputType, $clearState = true ) {
$this->setTitle( $title );
$this->mOptions = $options;
@@ -4450,6 +4644,7 @@ class Parser {
*/
function clearTagHooks() {
$this->mTagHooks = array();
+ $this->mFunctionTagHooks = array();
$this->mStripList = $this->mDefaultStripList;
}
@@ -4559,7 +4754,11 @@ class Parser {
* @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.
+ *
+ * @param $text string
+ * @param $options int
+ *
+ * @return array of link CSS classes, indexed by PDBK.
*/
function replaceLinkHolders( &$text, $options = 0 ) {
return $this->mLinkHolders->replace( $text );
@@ -4586,7 +4785,7 @@ class Parser {
* 'A tree'.
*
* @param string $text
- * @param array $param
+ * @param array $params
* @return string HTML
*/
function renderImageGallery( $text, $params ) {
@@ -4671,6 +4870,10 @@ class Parser {
return $ig->toHTML();
}
+ /**
+ * @param $handler
+ * @return array
+ */
function getImageParams( $handler ) {
if ( $handler ) {
$handlerClass = get_class( $handler );
@@ -4716,7 +4919,7 @@ class Parser {
*
* @param $title Title
* @param $options String
- * @param $holders LinkHolderArray
+ * @param $holders LinkHolderArray|false
* @return string HTML
*/
function makeImage( $title, $options, $holders = false ) {
@@ -4748,11 +4951,12 @@ class Parser {
$parts = StringUtils::explode( "|", $options );
# Give extensions a chance to select the file revision for us
- $time = $sha1 = $descQuery = false;
+ $options = array();
+ $descQuery = false;
wfRunHooks( 'BeforeParserFetchFileAndTitle',
- array( $this, $title, &$time, &$sha1, &$descQuery ) );
+ array( $this, $title, &$options, &$descQuery ) );
# Fetch and register the file (file title may be different via hooks)
- list( $file, $title ) = $this->fetchFileAndTitle( $title, $time, $sha1 );
+ list( $file, $title ) = $this->fetchFileAndTitle( $title, $options );
# Get parameter map
$handler = $file ? $file->getHandler() : false;
@@ -4820,7 +5024,7 @@ class Parser {
$value = true;
$validated = true;
} elseif ( preg_match( "/^$prots/", $value ) ) {
- if ( preg_match( "/^($prots)$chars+$/", $value, $m ) ) {
+ if ( preg_match( "/^($prots)$chars+$/u", $value, $m ) ) {
$paramName = 'link-url';
$this->mOutput->addExternalLink( $value );
if ( $this->mOptions->getExternalLinkTarget() ) {
@@ -4912,6 +5116,7 @@ class Parser {
wfRunHooks( 'ParserMakeImageParams', array( $title, $file, &$params ) );
# Linker does the rest
+ $time = isset( $options['time'] ) ? $options['time'] : false;
$ret = Linker::makeImageLink2( $title, $file, $params['frame'], $params['handler'],
$time, $descQuery, $this->mOptions->getThumbSize() );
@@ -4953,6 +5158,10 @@ class Parser {
*/
function disableCache() {
wfDebug( "Parser output marked as uncacheable.\n" );
+ if ( !$this->mOutput ) {
+ throw new MWException( __METHOD__ .
+ " can only be called when actually parsing something" );
+ }
$this->mOutput->setCacheTime( -1 ); // old style, for compatibility
$this->mOutput->updateCacheExpiry( 0 ); // new style, for consistency
}
@@ -4977,7 +5186,7 @@ class Parser {
* @return array
*/
function getTags() {
- return array_merge( array_keys( $this->mTransparentTagHooks ), array_keys( $this->mTagHooks ) );
+ return array_merge( array_keys( $this->mTransparentTagHooks ), array_keys( $this->mTagHooks ), array_keys( $this->mFunctionTagHooks ) );
}
/**
@@ -4985,6 +5194,10 @@ class Parser {
*
* Transparent tag hooks are like regular XML-style tag hooks, except they
* operate late in the transformation sequence, on HTML instead of wikitext.
+ *
+ * @param $text string
+ *
+ * @return string
*/
function replaceTransparentTags( $text ) {
$matches = array();
@@ -5026,7 +5239,7 @@ class Parser {
* 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
+ * string. If $text is the empty string and section 0 is replaced, $newText is
* returned.
*
* @param $mode String: one of "get" or "replace"
@@ -5161,7 +5374,7 @@ class Parser {
/**
* This function returns $oldtext after the content of the section
- * specified by $section has been replaced with $text. If the target
+ * specified by $section has been replaced with $text. If the target
* section does not exist, $oldtext is returned unchanged.
*
* @param $oldtext String: former text of the article
@@ -5287,6 +5500,10 @@ class Parser {
* Try to guess the section anchor name based on a wikitext fragment
* presumably extracted from a heading, for example "Header" from
* "== Header ==".
+ *
+ * @param $text string
+ *
+ * @return string
*/
public function guessSectionNameFromWikiText( $text ) {
# Strip out wikitext links(they break the anchor)
@@ -5346,6 +5563,11 @@ class Parser {
/**
* strip/replaceVariables/unstrip for preprocessor regression testing
*
+ * @param $text string
+ * @param $title Title
+ * @param $options ParserOptions
+ * @param $outputType int
+ *
* @return string
*/
function testSrvus( $text, Title $title, ParserOptions $options, $outputType = self::OT_HTML ) {
@@ -5357,10 +5579,22 @@ class Parser {
return $text;
}
+ /**
+ * @param $text string
+ * @param $title Title
+ * @param $options ParserOptions
+ * @return string
+ */
function testPst( $text, Title $title, ParserOptions $options ) {
return $this->preSaveTransform( $text, $title, $options->getUser(), $options );
}
+ /**
+ * @param $text
+ * @param $title Title
+ * @param $options ParserOptions
+ * @return string
+ */
function testPreprocess( $text, Title $title, ParserOptions $options ) {
return $this->testSrvus( $text, $title, $options, self::OT_PREPROCESS );
}
@@ -5376,6 +5610,9 @@ class Parser {
* two strings will be replaced with the value returned by the callback in
* each case.
*
+ * @param $s string
+ * @param $callback
+ *
* @return string
*/
function markerSkipCallback( $s, $callback ) {
@@ -5424,6 +5661,8 @@ class Parser {
* unserializeHalfParsedText(). The text can then be safely incorporated into
* the return value of a parser hook.
*
+ * @param $text string
+ *
* @return array
*/
function serializeHalfParsedText( $text ) {
diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php
index dcbf7a4d..8b043290 100644
--- a/includes/parser/ParserCache.php
+++ b/includes/parser/ParserCache.php
@@ -15,6 +15,8 @@ class ParserCache {
/**
* Get an instance of this object
+ *
+ * @return ParserCache
*/
public static function singleton() {
static $instance;
@@ -78,7 +80,7 @@ class ParserCache {
*/
function getETag( $article, $popts ) {
return 'W/"' . $this->getParserOutputKey( $article,
- $popts->optionsHash( ParserOptions::legacyOptions() ) ) .
+ $popts->optionsHash( ParserOptions::legacyOptions(), $article->getTitle() ) ) .
"--" . $article->getTouched() . '"';
}
@@ -98,6 +100,8 @@ class ParserCache {
* It would be preferable to have this code in get()
* instead of having Article looking in our internals.
*
+ * @todo Document parameter $useOutdated
+ *
* @param $article Article
* @param $popts ParserOptions
*/
@@ -128,7 +132,7 @@ class ParserCache {
$usedOptions = ParserOptions::legacyOptions();
}
- return $this->getParserOutputKey( $article, $popts->optionsHash( $usedOptions ) );
+ return $this->getParserOutputKey( $article, $popts->optionsHash( $usedOptions, $article->getTitle() ) );
}
/**
@@ -156,6 +160,7 @@ class ParserCache {
$parserOutputKey = $this->getKey( $article, $popts, $useOutdated );
if ( $parserOutputKey === false ) {
+ wfIncrStats( 'pcache_miss_absent' );
wfProfileOut( __METHOD__ );
return false;
}
@@ -163,18 +168,19 @@ class ParserCache {
$value = $this->mMemc->get( $parserOutputKey );
if ( self::try116cache && !$value && strpos( $value, '*' ) !== -1 ) {
wfDebug( "New format parser cache miss.\n" );
- $parserOutputKey = $this->getParserOutputKey( $article, $popts->optionsHash( ParserOptions::legacyOptions() ) );
+ $parserOutputKey = $this->getParserOutputKey( $article,
+ $popts->optionsHash( ParserOptions::legacyOptions(), $article->getTitle() ) );
$value = $this->mMemc->get( $parserOutputKey );
}
if ( !$value ) {
- wfDebug( "Parser cache miss.\n" );
+ wfDebug( "ParserOutput cache miss.\n" );
wfIncrStats( "pcache_miss_absent" );
wfProfileOut( __METHOD__ );
return false;
}
- wfDebug( "Found.\n" );
-
+ wfDebug( "ParserOutput cache 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.
@@ -197,7 +203,6 @@ class ParserCache {
* @param $parserOutput ParserOutput
* @param $article Article
* @param $popts ParserOptions
- * @return void
*/
public function save( $parserOutput, $article, $popts ) {
$expire = $parserOutput->getCacheExpiry();
@@ -215,10 +220,10 @@ class ParserCache {
$optionsKey->setContainsOldMagic( $parserOutput->containsOldMagic() );
$parserOutputKey = $this->getParserOutputKey( $article,
- $popts->optionsHash( $optionsKey->mUsedOptions ) );
+ $popts->optionsHash( $optionsKey->mUsedOptions, $article->getTitle() ) );
// Save the timestamp so that we don't have to load the revision row on view
- $parserOutput->mTimestamp = $article->getTimestamp();
+ $parserOutput->setTimestamp( $article->getTimestamp() );
$parserOutput->mText .= "\n<!-- Saved in parser cache with key $parserOutputKey and timestamp $now -->\n";
wfDebug( "Saved in parser cache with key $parserOutputKey and timestamp $now\n" );
diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php
index 07752768..57d3a7eb 100644
--- a/includes/parser/ParserOptions.php
+++ b/includes/parser/ParserOptions.php
@@ -1,58 +1,188 @@
<?php
/**
- * Options for the PHP parser
+ * \brief Options for the PHP parser
*
* @file
* @ingroup Parser
*/
/**
- * Set options of the Parser
- * @todo document
+ * \brief Set options of the Parser
+ *
+ * All member variables are supposed to be private in theory, although in practise this is not the case.
+ *
* @ingroup Parser
*/
class ParserOptions {
- # All variables are supposed to be private in theory, although in practise this is not the case.
- var $mUseDynamicDates; # Use DateFormatter to format dates
- var $mInterwikiMagic; # Interlanguage links are removed and returned in an array
- 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 $mDateFormat = null; # Date format index
- var $mEditSection = true; # Create "edit section" links
- var $mAllowSpecialInclusion; # Allow inclusion of special pages
- 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 = true; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
- var $mTemplateCallback = # Callback for template fetching
+
+ /**
+ * Use DateFormatter to format dates
+ */
+ var $mUseDynamicDates;
+
+ /**
+ * Interlanguage links are removed and returned in an array
+ */
+ var $mInterwikiMagic;
+
+ /**
+ * Allow external images inline?
+ */
+ var $mAllowExternalImages;
+
+ /**
+ * If not, any exception?
+ */
+ var $mAllowExternalImagesFrom;
+
+ /**
+ * If not or it doesn't match, should we check an on-wiki whitelist?
+ */
+ var $mEnableImageWhitelist;
+
+ /**
+ * Date format index
+ */
+ var $mDateFormat = null;
+
+ /**
+ * Create "edit section" links?
+ */
+ var $mEditSection = true;
+
+ /**
+ * Allow inclusion of special pages?
+ */
+ var $mAllowSpecialInclusion;
+
+ /**
+ * Use tidy to cleanup output HTML?
+ */
+ var $mTidy = false;
+
+ /**
+ * Which lang to call for PLURAL and GRAMMAR
+ */
+ var $mInterfaceMessage = false;
+
+ /**
+ * Overrides $mInterfaceMessage with arbitrary language
+ */
+ var $mTargetLanguage = null;
+
+ /**
+ * Maximum size of template expansions, in bytes
+ */
+ var $mMaxIncludeSize;
+
+ /**
+ * Maximum number of nodes touched by PPFrame::expand()
+ */
+ var $mMaxPPNodeCount;
+
+ /**
+ * Maximum recursion depth in PPFrame::expand()
+ */
+ var $mMaxPPExpandDepth;
+
+ /**
+ * Maximum recursion depth for templates within templates
+ */
+ var $mMaxTemplateDepth;
+
+ /**
+ * Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
+ */
+ var $mRemoveComments = true;
+
+ /**
+ * Callback for template fetching. Used as first argument to call_user_func().
+ */
+ var $mTemplateCallback =
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.
+
+ /**
+ * Enable limit report in an HTML comment on output
+ */
+ var $mEnableLimitReport = false;
+
+ /**
+ * Timestamp used for {{CURRENTDAY}} etc.
+ */
+ var $mTimestamp;
+
+ /**
+ * Target attribute for external links
+ */
+ var $mExternalLinkTarget;
+
+ /**
+ * Clean up signature texts?
+ *
+ * 1) Strip ~~~, ~~~~ and ~~~~~ out of signatures
+ * 2) Substitute all transclusions
+ */
+ var $mCleanSignatures;
+
+ /**
+ * Transform wiki markup when saving the page?
+ */
+ var $mPreSaveTransform = true;
- var $mNumberHeadings; # Automatically number headings
- var $mMath; # User math preference (as integer)
- var $mThumbSize; # Thumb size preferred by the user.
- private $mStubThreshold; # Maximum article size of an article to be marked as "stub"
- var $mUserLang; # Language code of the User language.
+ /**
+ * Automatically number headings?
+ */
+ var $mNumberHeadings;
+
+ /**
+ * User math preference (as integer). Not used (1.19)
+ */
+ var $mMath;
+
+ /**
+ * Thumb size preferred by the user.
+ */
+ var $mThumbSize;
+
+ /**
+ * Maximum article size of an article to be marked as "stub"
+ */
+ private $mStubThreshold;
+
+ /**
+ * Language object of the User language.
+ */
+ var $mUserLang;
/**
- * @var User
+ * @var User
+ * Stored user object
+ */
+ var $mUser;
+
+ /**
+ * Parsing the page for a "preview" operation?
+ */
+ var $mIsPreview = false;
+
+ /**
+ * Parsing the page for a "preview" operation on a single section?
+ */
+ var $mIsSectionPreview = false;
+
+ /**
+ * Parsing the printable version of the page?
*/
- 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 $mIsPrintable = false;
- var $mExtraKey = ''; # Extra key that should be present in the caching key.
+ /**
+ * Extra key that should be present in the caching key.
+ */
+ var $mExtraKey = '';
+ /**
+ * Function to be called when an option is accessed.
+ */
protected $onAccessCallback = null;
function getUseDynamicDates() { return $this->mUseDynamicDates; }
@@ -96,7 +226,7 @@ class ParserOptions {
* @deprecated since 1.18 Use Linker::* instead
*/
function getSkin( $title = null ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
return new DummyLinker;
}
@@ -119,12 +249,23 @@ 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 Language object
+ * @since 1.19
+ */
+ function getUserLangObj() {
+ $this->optionUsed( 'userlang' );
+ return $this->mUserLang;
+ }
+
+ /**
+ * Same as getUserLangObj() but returns a string instead.
+ *
* @return String Language code
* @since 1.17
*/
function getUserLang() {
- $this->optionUsed( 'userlang' );
- return $this->mUserLang;
+ return $this->getUserLangObj()->getCode();
}
function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
@@ -137,7 +278,9 @@ class ParserOptions {
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 setSkin( $x ) { $this->mSkin = $x; }
+
+ /** @deprecated in 1.19; will be removed in 1.20 */
+ function setSkin( $x ) { wfDeprecated( __METHOD__, '1.19' ); }
function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x ); }
function setTargetLanguage( $x ) { return wfSetVar( $this->mTargetLanguage, $x, true ); }
function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); }
@@ -151,8 +294,8 @@ class ParserOptions {
function setExternalLinkTarget( $x ) { return wfSetVar( $this->mExternalLinkTarget, $x ); }
function setMath( $x ) { return wfSetVar( $this->mMath, $x ); }
function setUserLang( $x ) {
- if ( $x instanceof Language ) {
- $x = $x->getCode();
+ if ( is_string( $x ) ) {
+ $x = Language::factory( $x );
}
return wfSetVar( $this->mUserLang, $x );
}
@@ -171,41 +314,75 @@ class ParserOptions {
$this->mExtraKey .= '!' . $key;
}
- function __construct( $user = null ) {
- $this->initialiseFromUser( $user );
+ /**
+ * Constructor
+ * @param $user User object
+ * @param $lang Language object
+ */
+ function __construct( $user = null, $lang = null ) {
+ if ( $user === null ) {
+ global $wgUser;
+ if ( $wgUser === null ) {
+ $user = new User;
+ } else {
+ $user = $wgUser;
+ }
+ }
+ if ( $lang === null ) {
+ global $wgLang;
+ if ( !StubObject::isRealObject( $wgLang ) ) {
+ $wgLang->_unstub();
+ }
+ $lang = $wgLang;
+ }
+ $this->initialiseFromUser( $user, $lang );
}
/**
- * Get parser options
+ * Get a ParserOptions object from a given user.
+ * Language will be taken from $wgLang.
*
* @param $user User object
* @return ParserOptions object
*/
- static function newFromUser( $user ) {
+ public static function newFromUser( $user ) {
return new ParserOptions( $user );
}
- /** Get user options */
- function initialiseFromUser( $userInput ) {
- global $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
- global $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion, $wgMaxArticleSize;
- global $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth, $wgCleanSignatures;
- global $wgExternalLinkTarget, $wgLang;
+ /**
+ * Get a ParserOptions object from a given user and language
+ *
+ * @param $user User object
+ * @param $lang Language object
+ * @return ParserOptions object
+ */
+ public static function newFromUserAndLang( User $user, Language $lang ) {
+ return new ParserOptions( $user, $lang );
+ }
- wfProfileIn( __METHOD__ );
+ /**
+ * Get a ParserOptions object from a IContextSource object
+ *
+ * @param $context IContextSource object
+ * @return ParserOptions object
+ */
+ public static function newFromContext( IContextSource $context ) {
+ return new ParserOptions( $context->getUser(), $context->getLanguage() );
+ }
- if ( !$userInput ) {
- global $wgUser;
- if ( isset( $wgUser ) ) {
- $user = $wgUser;
- } else {
- $user = new User;
- }
- } else {
- $user =& $userInput;
- }
+ /**
+ * Get user options
+ *
+ * @param $user User object
+ * @param $lang Language object
+ */
+ private function initialiseFromUser( $user, $lang ) {
+ global $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages,
+ $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
+ $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
+ $wgCleanSignatures, $wgExternalLinkTarget;
- $this->mUser = $user;
+ wfProfileIn( __METHOD__ );
$this->mUseDynamicDates = $wgUseDynamicDates;
$this->mInterwikiMagic = $wgInterwikiMagic;
@@ -220,11 +397,12 @@ class ParserOptions {
$this->mCleanSignatures = $wgCleanSignatures;
$this->mExternalLinkTarget = $wgExternalLinkTarget;
+ $this->mUser = $user;
$this->mNumberHeadings = $user->getOption( 'numberheadings' );
$this->mMath = $user->getOption( 'math' );
$this->mThumbSize = $user->getOption( 'thumbsize' );
$this->mStubThreshold = $user->getStubThreshold();
- $this->mUserLang = $wgLang->getCode();
+ $this->mUserLang = $lang;
wfProfileOut( __METHOD__ );
}
@@ -274,10 +452,12 @@ class ParserOptions {
* settings.
*
* @since 1.17
- * @return \string Page rendering hash
+ * @param $forOptions Array
+ * @param $title Title: used to get the content language of the page (since r97636)
+ * @return string Page rendering hash
*/
- public function optionsHash( $forOptions ) {
- global $wgContLang, $wgRenderHashAppend;
+ public function optionsHash( $forOptions, $title = null ) {
+ global $wgRenderHashAppend;
$confstr = '';
@@ -308,7 +488,7 @@ class ParserOptions {
}
if ( in_array( 'userlang', $forOptions ) ) {
- $confstr .= '!' . $this->mUserLang;
+ $confstr .= '!' . $this->mUserLang->getCode();
} else {
$confstr .= '!*';
}
@@ -321,7 +501,12 @@ class ParserOptions {
// add in language specific options, if any
// @todo FIXME: This is just a way of retrieving the url/user preferred variant
- $confstr .= $wgContLang->getExtraHashOptions();
+ if( !is_null( $title ) ) {
+ $confstr .= $title->getPageLanguage()->getExtraHashOptions();
+ } else {
+ global $wgContLang;
+ $confstr .= $wgContLang->getExtraHashOptions();
+ }
$confstr .= $wgRenderHashAppend;
diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php
index 403b6625..2d99a3b5 100644
--- a/includes/parser/ParserOutput.php
+++ b/includes/parser/ParserOutput.php
@@ -5,7 +5,7 @@
* @file
* @ingroup Parser
*/
-
+
/**
* @todo document
* @ingroup Parser
@@ -16,7 +16,7 @@ class CacheTime {
$mCacheTime = '', # Time when this object was generated, or -1 for uncacheable. Used in ParserCache.
$mCacheExpiry = null, # Seconds after which the object should expire, use 0 for uncachable. Used in ParserCache.
$mContainsOldMagic; # Boolean variable indicating if the input contained variables like {{CURRENTDAY}}
-
+
function getCacheTime() { return $this->mCacheTime; }
function containsOldMagic() { return $this->mContainsOldMagic; }
@@ -30,17 +30,17 @@ class CacheTime {
*/
function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime, $t ); }
- /**
+ /**
* Sets the number of seconds after which this object should expire.
* This value is used with the ParserCache.
- * If called with a value greater than the value provided at any previous call,
+ * If called with a value greater than the value provided at any previous call,
* the new call has no effect. The value returned by getCacheExpiry is smaller
- * or equal to the smallest number that was provided as an argument to
+ * or equal to the smallest number that was provided as an argument to
* updateCacheExpiry().
*
* @param $seconds number
*/
- function updateCacheExpiry( $seconds ) {
+ function updateCacheExpiry( $seconds ) {
$seconds = (int)$seconds;
if ( $this->mCacheExpiry === null || $this->mCacheExpiry > $seconds ) {
@@ -52,23 +52,23 @@ class CacheTime {
$this->mCacheTime = -1;
}
}
-
+
/**
* Returns the number of seconds after which this object should expire.
* This method is used by ParserCache to determine how long the ParserOutput can be cached.
* The timestamp of expiry can be calculated by adding getCacheExpiry() to getCacheTime().
- * The value returned by getCacheExpiry is smaller or equal to the smallest number
+ * The value returned by getCacheExpiry is smaller or equal to the smallest number
* that was provided to a call of updateCacheExpiry(), and smaller or equal to the
* value of $wgParserCacheExpireTime.
*/
- function getCacheExpiry() {
+ function getCacheExpiry() {
global $wgParserCacheExpireTime;
if ( $this->mCacheTime < 0 ) {
return 0;
} // old-style marker for "not cachable"
- $expire = $this->mCacheExpiry;
+ $expire = $this->mCacheExpiry;
if ( $expire === null ) {
$expire = $wgParserCacheExpireTime;
@@ -78,7 +78,7 @@ class CacheTime {
if( $this->containsOldMagic() ) { //compatibility hack
$expire = min( $expire, 3600 ); # 1 hour
- }
+ }
if ( $expire <= 0 ) {
return 0; // not cachable
@@ -90,7 +90,7 @@ class CacheTime {
/**
* @return bool
*/
- function isCacheable() {
+ function isCacheable() {
return $this->getCacheExpiry() > 0;
}
@@ -105,14 +105,14 @@ class CacheTime {
public function expired( $touched ) {
global $wgCacheEpoch;
return !$this->isCacheable() || // parser says it's uncacheable
- $this->getCacheTime() < $touched ||
- $this->getCacheTime() <= $wgCacheEpoch ||
- $this->getCacheTime() < wfTimestamp( TS_MW, time() - $this->getCacheExpiry() ) || // expiry period has passed
- !isset( $this->mVersion ) ||
- version_compare( $this->mVersion, Parser::VERSION, "lt" );
- }
+ $this->getCacheTime() < $touched ||
+ $this->getCacheTime() <= $wgCacheEpoch ||
+ $this->getCacheTime() < wfTimestamp( TS_MW, time() - $this->getCacheExpiry() ) || // expiry period has passed
+ !isset( $this->mVersion ) ||
+ version_compare( $this->mVersion, Parser::VERSION, "lt" );
+ }
}
-
+
class ParserOutput extends CacheTime {
var $mText, # The output text
$mLanguageLinks, # List of the full text of language links, in the order they appear
@@ -122,7 +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
+ $mFileSearchOptions = 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?
@@ -138,8 +138,9 @@ class ParserOutput extends CacheTime {
$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.
+ $mTOCHTML = '', # HTML of the TOC
+ $mTimestamp; # Timestamp of the revision
+ private $mIndexPolicy = ''; # 'index' or 'noindex'? Any other value will result in no change.
private $mAccessedOptions = array(); # List of ParserOptions (stored in the keys)
const EDITSECTION_REGEX = '#<(?:mw:)?editsection page="(.*?)" section="(.*?)"(?:/>|>(.*?)(</(?:mw:)?editsection>))#';
@@ -158,12 +159,10 @@ class ParserOutput extends CacheTime {
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;
+ return preg_replace( ParserOutput::EDITSECTION_REGEX, '', $this->mText );
}
-
+
/**
* callback used by getText to replace editsection tokens
* @private
@@ -195,7 +194,7 @@ class ParserOutput extends CacheTime {
function &getTemplates() { return $this->mTemplates; }
function &getTemplateIds() { return $this->mTemplateIds; }
function &getImages() { return $this->mImages; }
- function &getImageTimeKeys() { return $this->mImageTimeKeys; }
+ function &getFileSearchOptions() { return $this->mFileSearchOptions; }
function &getExternalLinks() { return $this->mExternalLinks; }
function getNoGallery() { return $this->mNoGallery; }
function getHeadItems() { return $this->mHeadItems; }
@@ -207,6 +206,7 @@ class ParserOutput extends CacheTime {
function getWarnings() { return array_keys( $this->mWarnings ); }
function getIndexPolicy() { return $this->mIndexPolicy; }
function getTOCHTML() { return $this->mTOCHTML; }
+ function getTimestamp() { return $this->mTimestamp; }
function setText( $text ) { return wfSetVar( $this->mText, $text ); }
function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); }
@@ -217,6 +217,7 @@ class ParserOutput extends CacheTime {
function setEditSectionTokens( $t ) { return wfSetVar( $this->mEditSectionTokens, $t ); }
function setIndexPolicy( $policy ) { return wfSetVar( $this->mIndexPolicy, $policy ); }
function setTOCHTML( $tochtml ) { return wfSetVar( $this->mTOCHTML, $tochtml ); }
+ function setTimestamp( $timestamp ) { return wfSetVar( $this->mTimestamp, $timestamp ); }
function addCategory( $c, $sort ) { $this->mCategories[$c] = $sort; }
function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; }
@@ -243,7 +244,7 @@ class ParserOutput extends CacheTime {
# We don't register links pointing to our own server, unless... :-)
global $wgServer, $wgRegisterInternalExternals;
if( $wgRegisterInternalExternals or stripos($url,$wgServer.'/')!==0)
- $this->mExternalLinks[$url] = 1;
+ $this->mExternalLinks[$url] = 1;
}
/**
@@ -284,13 +285,13 @@ class ParserOutput extends CacheTime {
* 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)
+ * @param $sha1 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 );
+ $this->mFileSearchOptions[$name] = array( 'time' => $timestamp, 'sha1' => $sha1 );
}
}
@@ -313,7 +314,7 @@ class ParserOutput extends CacheTime {
}
$this->mTemplateIds[$ns][$dbk] = $rev_id; // For versioning
}
-
+
/**
* @param $title Title object, must be an interwiki link
* @throws MWException if given invalid input
@@ -341,7 +342,7 @@ class ParserOutput extends CacheTime {
$this->mHeadItems[] = $section;
}
}
-
+
public function addModules( $modules ) {
$this->mModules = array_merge( $this->mModules, (array) $modules );
}
@@ -359,6 +360,20 @@ class ParserOutput extends CacheTime {
}
/**
+ * Copy items from the OutputPage object into this one
+ *
+ * @param $out OutputPage object
+ */
+ public function addOutputPageMetadata( OutputPage $out ) {
+ $this->addModules( $out->getModules() );
+ $this->addModuleScripts( $out->getModuleScripts() );
+ $this->addModuleStyles( $out->getModuleStyles() );
+ $this->addModuleMessages( $out->getModuleMessages() );
+
+ $this->mHeadItems = array_merge( $this->mHeadItems, $out->getHeadItemsArray() );
+ }
+
+ /**
* Override the title to be used for display
* -- this is assumed to have been validated
* (check equal normalisation, etc.)
@@ -411,10 +426,10 @@ class ParserOutput extends CacheTime {
}
return $this->mProperties;
}
-
-
+
+
/**
- * Returns the options from its ParserOptions which have been taken
+ * Returns the options from its ParserOptions which have been taken
* into account to produce this output or false if not available.
* @return mixed Array
*/
@@ -424,7 +439,7 @@ class ParserOutput extends CacheTime {
}
return array_keys( $this->mAccessedOptions );
}
-
+
/**
* Callback passed by the Parser to the ParserOptions to keep track of which options are used.
* @access private
diff --git a/includes/parser/Preprocessor.php b/includes/parser/Preprocessor.php
index d6328aa7..ae088fdb 100644
--- a/includes/parser/Preprocessor.php
+++ b/includes/parser/Preprocessor.php
@@ -27,7 +27,7 @@ interface Preprocessor {
* Create a new custom frame for programmatic use of parameter replacement as used in some extensions
*
* @param $args array
- *
+ *
* @return PPFrame
*/
function newCustomFrame( $args );
@@ -44,7 +44,7 @@ interface Preprocessor {
*
* @param $text
* @param $flags
- *
+ *
* @return PPNode
*/
function preprocessToObj( $text, $flags = 0 );
@@ -138,6 +138,13 @@ interface PPFrame {
* Return true if the frame is a template frame
*/
function isTemplate();
+
+ /**
+ * Get a title of frame
+ *
+ * @return Title
+ */
+ function getTitle();
}
/**
diff --git a/includes/parser/Preprocessor_DOM.php b/includes/parser/Preprocessor_DOM.php
index 755563a0..066589f6 100644
--- a/includes/parser/Preprocessor_DOM.php
+++ b/includes/parser/Preprocessor_DOM.php
@@ -81,7 +81,7 @@ class Preprocessor_DOM implements Preprocessor {
*/
function memCheck() {
if ( $this->memoryLimit === false ) {
- return;
+ return true;
}
$usage = memory_get_usage();
if ( $usage > $this->memoryLimit * 0.9 ) {
@@ -543,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 );
@@ -957,8 +957,7 @@ class PPFrame_DOM implements PPFrame {
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>';
}
@@ -1340,6 +1339,15 @@ class PPFrame_DOM implements PPFrame {
function isTemplate() {
return false;
}
+
+ /**
+ * Get a title of frame
+ *
+ * @return Title
+ */
+ function getTitle() {
+ return $this->title;
+ }
}
/**
diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php
index c2d7d3d8..2934181a 100644
--- a/includes/parser/Preprocessor_Hash.php
+++ b/includes/parser/Preprocessor_Hash.php
@@ -498,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 );
@@ -1268,6 +1268,15 @@ class PPFrame_Hash implements PPFrame {
function isTemplate() {
return false;
}
+
+ /**
+ * Get a title of frame
+ *
+ * @return Title
+ */
+ function getTitle() {
+ return $this->title;
+ }
}
/**
diff --git a/includes/parser/Preprocessor_HipHop.hphp b/includes/parser/Preprocessor_HipHop.hphp
index dc404f7c..f5af0154 100644
--- a/includes/parser/Preprocessor_HipHop.hphp
+++ b/includes/parser/Preprocessor_HipHop.hphp
@@ -260,8 +260,8 @@ class Preprocessor_HipHop implements Preprocessor {
if ( $found === 'angle' ) {
$matches = false;
// Handle </onlyinclude>
- if ( $enableOnlyinclude
- && substr( $text, $i, strlen( '</onlyinclude>' ) ) === '</onlyinclude>' )
+ if ( $enableOnlyinclude
+ && substr( $text, $i, strlen( '</onlyinclude>' ) ) === '</onlyinclude>' )
{
$findOnlyinclude = true;
continue;
@@ -362,7 +362,7 @@ class Preprocessor_HipHop implements Preprocessor {
}
$tagStartPos = $i;
- $inner = $close = '';
+ $close = '';
if ( $text[$tagEndPos-1] === '/' ) {
// Short end tag
$attrEnd = $tagEndPos - 1;
@@ -1008,7 +1008,6 @@ class PPFrame_HipHop implements PPFrame {
*/
var $depth;
-
/**
* Construct a new preprocessor frame.
* @param $preprocessor Preprocessor: the parent preprocessor
@@ -1426,6 +1425,15 @@ class PPFrame_HipHop implements PPFrame {
function isTemplate() {
return false;
}
+
+ /**
+ * Get a title of frame
+ *
+ * @return Title
+ */
+ function getTitle() {
+ return $this->title;
+ }
}
/**
diff --git a/includes/parser/StripState.php b/includes/parser/StripState.php
index a6eff70e..7ad80fa1 100644
--- a/includes/parser/StripState.php
+++ b/includes/parser/StripState.php
@@ -11,6 +11,9 @@ class StripState {
protected $tempType, $tempMergePrefix;
+ /**
+ * @param $prefix string
+ */
function __construct( $prefix ) {
$this->prefix = $prefix;
$this->data = array(
@@ -170,6 +173,10 @@ class StripState {
return $texts;
}
+ /**
+ * @param $m
+ * @return string
+ */
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 3a6d3e9c..2b98f01d 100644
--- a/includes/parser/Tidy.php
+++ b/includes/parser/Tidy.php
@@ -11,7 +11,7 @@
* 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.
*/
@@ -40,7 +40,7 @@ class MWTidyWrapper {
$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 );
@@ -126,7 +126,7 @@ class MWTidy {
*/
public static function checkErrors( $text, &$errorStr = null ) {
global $wgTidyInternal;
-
+
$retval = 0;
if( $wgTidyInternal ) {
$errorStr = self::execInternalTidy( $text, true, $retval );
@@ -166,7 +166,7 @@ class MWTidy {
2 => array( 'file', wfGetNull(), 'a' )
);
}
-
+
$readpipe = $stderr ? 2 : 1;
$pipes = array();
@@ -217,7 +217,7 @@ class MWTidy {
if ( !MWInit::classExists( 'tidy' ) ) {
wfWarn( "Unable to load internal tidy class." );
$retval = -1;
-
+
wfProfileOut( __METHOD__ );
return null;
}
@@ -245,7 +245,7 @@ class MWTidy {
"\n-->";
}
}
-
+
wfProfileOut( __METHOD__ );
return $cleansource;
}
diff --git a/includes/profiler/Profiler.php b/includes/profiler/Profiler.php
index 93b03f59..0fe18c25 100644
--- a/includes/profiler/Profiler.php
+++ b/includes/profiler/Profiler.php
@@ -34,26 +34,28 @@ function wfProfileOut( $functionname = 'missing' ) {
* @todo document
*/
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;
+ protected $mStack = array(), $mWorkStack = array (), $mCollated = array (),
+ $mCalls = array (), $mTotals = array ();
+ protected $mTimeMetric = 'wall';
+ protected $mProfileID = false, $mCollateDone = false, $mTemplated = false;
private static $__instance = null;
function __construct( $params ) {
+ if ( isset( $params['timeMetric'] ) ) {
+ $this->mTimeMetric = $params['timeMetric'];
+ }
+ if ( isset( $params['profileID'] ) ) {
+ $this->mProfileID = $params['profileID'];
+ }
+
// Push an entry for the pre-profile setup time onto the stack
- global $wgRequestTime;
- if ( !empty( $wgRequestTime ) ) {
- $this->mWorkStack[] = array( '-total', 0, $wgRequestTime, 0 );
- $this->mStack[] = array( '-setup', 1, $wgRequestTime, 0, microtime(true), 0 );
+ $initial = $this->getInitialTime();
+ if ( $initial !== null ) {
+ $this->mWorkStack[] = array( '-total', 0, $initial, 0 );
+ $this->mStack[] = array( '-setup', 1, $initial, 0, $this->getTime(), 0 );
} else {
$this->profileIn( '-total' );
}
- if ( isset( $params['timeMetric'] ) ) {
- $this->mTimeMetric = $params['timeMetric'];
- }
}
/**
@@ -64,11 +66,19 @@ class Profiler {
if( is_null( self::$__instance ) ) {
global $wgProfiler;
if( is_array( $wgProfiler ) ) {
- $class = isset( $wgProfiler['class'] ) ? $wgProfiler['class'] : 'ProfilerStub';
+ if( !isset( $wgProfiler['class'] ) ) {
+ wfDebug( __METHOD__ . " called without \$wgProfiler['class']"
+ . " set, falling back to ProfilerStub for safety\n" );
+ $class = 'ProfilerStub';
+ } else {
+ $class = $wgProfiler['class'];
+ }
self::$__instance = new $class( $wgProfiler );
} elseif( $wgProfiler instanceof Profiler ) {
self::$__instance = $wgProfiler; // back-compat
} else {
+ wfDebug( __METHOD__ . ' called with bogus $wgProfiler setting,'
+ . " falling back to ProfilerStub for safety\n" );
self::$__instance = new ProfilerStub( $wgProfiler );
}
}
@@ -141,12 +151,12 @@ class Profiler {
if( $functionname == 'close' ){
$message = "Profile section ended by close(): {$bit[0]}";
$this->debug( "$message\n" );
- $this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 );
+ $this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
}
elseif( $bit[0] != $functionname ){
$message = "Profiling error: in({$bit[0]}), out($functionname)";
$this->debug( "$message\n" );
- $this->mStack[] = array( $message, 0, '0 0', 0, '0 0', 0 );
+ $this->mStack[] = array( $message, 0, 0.0, 0, 0.0, 0 );
}
//}
$bit[] = $time;
@@ -256,13 +266,31 @@ class Profiler {
if ( $this->mTimeMetric === 'user' ) {
return $this->getUserTime();
} else {
- return microtime(true);
+ return microtime( true );
}
}
function getUserTime() {
$ru = getrusage();
- return $ru['ru_utime.tv_sec'].' '.$ru['ru_utime.tv_usec'] / 1e6;
+ return $ru['ru_utime.tv_sec'] + $ru['ru_utime.tv_usec'] / 1e6;
+ }
+
+ private function getInitialTime() {
+ global $wgRequestTime, $wgRUstart;
+
+ if ( $this->mTimeMetric === 'user' ) {
+ if ( count( $wgRUstart ) ) {
+ return $wgRUstart['ru_utime.tv_sec'] + $wgRUstart['ru_utime.tv_usec'] / 1e6;
+ } else {
+ return null;
+ }
+ } else {
+ if ( empty( $wgRequestTime ) ) {
+ return null;
+ } else {
+ return $wgRequestTime;
+ }
+ }
}
protected function collateData() {
@@ -344,7 +372,8 @@ class Profiler {
/**
* Returns a list of profiled functions.
- * Also log it into the database if $wgProfileToDatabase is set to true.
+ *
+ * @return string
*/
function getFunctionReport() {
$this->collateData();
diff --git a/includes/profiler/ProfilerSimple.php b/includes/profiler/ProfilerSimple.php
index bbdbec8e..055a0ea0 100644
--- a/includes/profiler/ProfilerSimple.php
+++ b/includes/profiler/ProfilerSimple.php
@@ -115,13 +115,4 @@ class ProfilerSimple extends Profiler {
return 0;
}
}
-
- /* If argument is passed, it assumes that it is dual-format time string, returns proper float time value */
- function getTime($time=null) {
- if ($time==null) {
- return microtime(true);
- }
- list($a,$b)=explode(" ",$time);
- return (float)($a+$b);
- }
}
diff --git a/includes/profiler/ProfilerSimpleText.php b/includes/profiler/ProfilerSimpleText.php
index ef9049fa..3621a41f 100644
--- a/includes/profiler/ProfilerSimpleText.php
+++ b/includes/profiler/ProfilerSimpleText.php
@@ -41,7 +41,6 @@ class ProfilerSimpleText extends ProfilerSimple {
}
}
- /* dense is good */
static function sort( $a, $b ) {
return $a['real'] < $b['real']; /* sort descending by time elapsed */
}
diff --git a/includes/profiler/ProfilerSimpleTrace.php b/includes/profiler/ProfilerSimpleTrace.php
index ba41babc..784609f5 100644
--- a/includes/profiler/ProfilerSimpleTrace.php
+++ b/includes/profiler/ProfilerSimpleTrace.php
@@ -10,7 +10,6 @@
* @ingroup Profiler
*/
class ProfilerSimpleTrace extends ProfilerSimple {
- var $mMinimumTime = 0;
var $trace = "";
var $memory = 0;
@@ -36,7 +35,7 @@ class ProfilerSimpleTrace extends ProfilerSimple {
$this->debug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n");
}
- list( $ofname, /* $ocount */ , $ortime, $octime ) = array_pop( $this->mWorkStack );
+ list( $ofname, /* $ocount */ , $ortime ) = array_pop( $this->mWorkStack );
if ( !$ofname ) {
$this->trace .= "Profiling error: $functionname\n";
diff --git a/includes/profiler/ProfilerSimpleUDP.php b/includes/profiler/ProfilerSimpleUDP.php
index ed49d5a2..ae607aa6 100644
--- a/includes/profiler/ProfilerSimpleUDP.php
+++ b/includes/profiler/ProfilerSimpleUDP.php
@@ -20,22 +20,34 @@ class ProfilerSimpleUDP extends ProfilerSimple {
return;
}
+ if ( !MWInit::functionExists( 'socket_create' ) ) {
+ # Sockets are not enabled
+ return;
+ }
+
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
- $plength=0;
- $packet="";
- foreach ($this->mCollated as $entry=>$pfdata) {
- $pfline=sprintf ("%s %s %d %f %f %f %f %s\n", $this->getProfileID(),"-",$pfdata['count'],
- $pfdata['cpu'],$pfdata['cpu_sq'],$pfdata['real'],$pfdata['real_sq'],$entry);
- $length=strlen($pfline);
+ $plength = 0;
+ $packet = "";
+ foreach ( $this->mCollated as $entry => $pfdata ) {
+ if( !isset($pfdata['count'])
+ || !isset( $pfdata['cpu'] )
+ || !isset( $pfdata['cpu_sq'] )
+ || !isset( $pfdata['real'] )
+ || !isset( $pfdata['real_sq'] ) ) {
+ continue;
+ }
+ $pfline = sprintf( "%s %s %d %f %f %f %f %s\n", $this->getProfileID(), "-", $pfdata['count'],
+ $pfdata['cpu'], $pfdata['cpu_sq'], $pfdata['real'], $pfdata['real_sq'], $entry);
+ $length = strlen( $pfline );
/* printf("<!-- $pfline -->"); */
- if ($length+$plength>1400) {
- socket_sendto($sock,$packet,$plength,0,$wgUDPProfilerHost,$wgUDPProfilerPort);
- $packet="";
- $plength=0;
+ if ( $length + $plength > 1400 ) {
+ socket_sendto( $sock, $packet, $plength, 0, $wgUDPProfilerHost, $wgUDPProfilerPort );
+ $packet = "";
+ $plength = 0;
}
- $packet.=$pfline;
- $plength+=$length;
+ $packet .= $pfline;
+ $plength += $length;
}
- socket_sendto($sock,$packet,$plength,0x100,$wgUDPProfilerHost,$wgUDPProfilerPort);
+ socket_sendto( $sock, $packet, $plength, 0x100, $wgUDPProfilerHost, $wgUDPProfilerPort );
}
}
diff --git a/includes/profiler/ProfilerStub.php b/includes/profiler/ProfilerStub.php
index 58c1975c..1a0933c4 100644
--- a/includes/profiler/ProfilerStub.php
+++ b/includes/profiler/ProfilerStub.php
@@ -12,4 +12,6 @@ class ProfilerStub extends Profiler {
public function profileOut( $fn ) {}
public function getOutput() {}
public function close() {}
+ public function logData() {}
+ public function getCurrentSection() { return ''; }
}
diff --git a/includes/resourceloader/ResourceLoader.php b/includes/resourceloader/ResourceLoader.php
index 01b70e8e..9175b10d 100644
--- a/includes/resourceloader/ResourceLoader.php
+++ b/includes/resourceloader/ResourceLoader.php
@@ -29,12 +29,21 @@
class ResourceLoader {
/* Protected Static Members */
- protected static $filterCacheVersion = 4;
+ protected static $filterCacheVersion = 7;
+ protected static $requiredSourceProperties = array( 'loadScript' );
/** Array: List of module name/ResourceLoaderModule object pairs */
protected $modules = array();
+
/** Associative array mapping module name to info associative array */
protected $moduleInfos = array();
+
+ /** Associative array mapping framework ids to a list of names of test suite modules */
+ /** like array( 'qunit' => array( 'mediawiki.tests.qunit.suites', 'ext.foo.tests', .. ), .. ) */
+ protected $testModuleNames = array();
+
+ /** array( 'source-id' => array( 'loadScript' => 'http://.../load.php' ) ) **/
+ protected $sources = array();
/* Protected Methods */
@@ -178,16 +187,27 @@ class ResourceLoader {
* Registers core modules and runs registration hooks.
*/
public function __construct() {
- global $IP, $wgResourceModules;
+ global $IP, $wgResourceModules, $wgResourceLoaderSources, $wgLoadScript, $wgEnableJavaScriptTest;
wfProfileIn( __METHOD__ );
+ // Add 'local' source first
+ $this->addSource( 'local', array( 'loadScript' => $wgLoadScript, 'apiScript' => wfScript( 'api' ) ) );
+
+ // Add other sources
+ $this->addSource( $wgResourceLoaderSources );
+
// Register core modules
$this->register( include( "$IP/resources/Resources.php" ) );
// Register extension modules
wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
$this->register( $wgResourceModules );
+ if ( $wgEnableJavaScriptTest === true ) {
+ $this->registerTestModules();
+ }
+
+
wfProfileOut( __METHOD__ );
}
@@ -208,49 +228,114 @@ class ResourceLoader {
wfProfileIn( __METHOD__ );
// Allow multiple modules to be registered in one call
- if ( is_array( $name ) ) {
- foreach ( $name as $key => $value ) {
- $this->register( $key, $value );
+ $registrations = is_array( $name ) ? $name : array( $name => $info );
+ foreach ( $registrations as $name => $info ) {
+ // Disallow duplicate registrations
+ if ( isset( $this->moduleInfos[$name] ) ) {
+ // A module has already been registered by this name
+ throw new MWException(
+ 'ResourceLoader duplicate registration error. ' .
+ 'Another module has already been registered as ' . $name
+ );
+ }
+
+ // Check $name for illegal characters
+ if ( preg_match( '/[|,!]/', $name ) ) {
+ throw new MWException( "ResourceLoader module name '$name' is invalid. Names may not contain pipes (|), commas (,) or exclamation marks (!)" );
+ }
+
+ // Attach module
+ if ( is_object( $info ) ) {
+ // Old calling convention
+ // Validate the input
+ if ( !( $info instanceof ResourceLoaderModule ) ) {
+ throw new MWException( 'ResourceLoader invalid module error. ' .
+ 'Instances of ResourceLoaderModule expected.' );
+ }
+
+ $this->moduleInfos[$name] = array( 'object' => $info );
+ $info->setName( $name );
+ $this->modules[$name] = $info;
+ } else {
+ // New calling convention
+ $this->moduleInfos[$name] = $info;
}
- wfProfileOut( __METHOD__ );
- return;
}
- // Disallow duplicate registrations
- if ( isset( $this->moduleInfos[$name] ) ) {
- // A module has already been registered by this name
- throw new MWException(
- 'ResourceLoader duplicate registration error. ' .
- 'Another module has already been registered as ' . $name
- );
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ */
+ public function registerTestModules() {
+ global $IP, $wgEnableJavaScriptTest;
+
+ if ( $wgEnableJavaScriptTest !== true ) {
+ throw new MWException( 'Attempt to register JavaScript test modules but <tt>$wgEnableJavaScriptTest</tt> is false. Edit your <tt>LocalSettings.php</tt> to enable it.' );
}
- // Check $name for illegal characters
- if ( preg_match( '/[|,!]/', $name ) ) {
- throw new MWException( "ResourceLoader module name '$name' is invalid. Names may not contain pipes (|), commas (,) or exclamation marks (!)" );
+ wfProfileIn( __METHOD__ );
+
+ // Get core test suites
+ $testModules = array();
+ $testModules['qunit'] = include( "$IP/tests/qunit/QUnitTestResources.php" );
+ // Get other test suites (e.g. from extensions)
+ wfRunHooks( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
+
+ // Add the testrunner (which configures QUnit) to the dependencies.
+ // Since it must be ready before any of the test suites are executed.
+ foreach( $testModules['qunit'] as $moduleName => $moduleProps ) {
+ $testModules['qunit'][$moduleName]['dependencies'][] = 'mediawiki.tests.qunit.testrunner';
}
- // Attach module
- if ( is_object( $info ) ) {
- // Old calling convention
- // Validate the input
- if ( !( $info instanceof ResourceLoaderModule ) ) {
- throw new MWException( 'ResourceLoader invalid module error. ' .
- 'Instances of ResourceLoaderModule expected.' );
- }
+ foreach( $testModules as $id => $names ) {
+ // Register test modules
+ $this->register( $testModules[$id] );
- $this->moduleInfos[$name] = array( 'object' => $info );
- $info->setName( $name );
- $this->modules[$name] = $info;
- } else {
- // New calling convention
- $this->moduleInfos[$name] = $info;
+ // Keep track of their names so that they can be loaded together
+ $this->testModuleNames[$id] = array_keys( $testModules[$id] );
}
wfProfileOut( __METHOD__ );
}
- /**
+ /**
+ * Add a foreign source of modules.
+ *
+ * Source properties:
+ * 'loadScript': URL (either fully-qualified or protocol-relative) of load.php for this source
+ *
+ * @param $id Mixed: source ID (string), or array( id1 => props1, id2 => props2, ... )
+ * @param $properties Array: source properties
+ */
+ public function addSource( $id, $properties = null) {
+ // Allow multiple sources to be registered in one call
+ if ( is_array( $id ) ) {
+ foreach ( $id as $key => $value ) {
+ $this->addSource( $key, $value );
+ }
+ return;
+ }
+
+ // Disallow duplicates
+ if ( isset( $this->sources[$id] ) ) {
+ throw new MWException(
+ 'ResourceLoader duplicate source addition error. ' .
+ 'Another source has already been registered as ' . $id
+ );
+ }
+
+ // Validate properties
+ foreach ( self::$requiredSourceProperties as $prop ) {
+ if ( !isset( $properties[$prop] ) ) {
+ throw new MWException( "Required property $prop missing from source ID $id" );
+ }
+ }
+
+ $this->sources[$id] = $properties;
+ }
+
+ /**
* Get a list of module names
*
* @return Array: List of module names
@@ -258,6 +343,25 @@ class ResourceLoader {
public function getModuleNames() {
return array_keys( $this->moduleInfos );
}
+
+ /**
+ * Get a list of test module names for one (or all) frameworks.
+ * If the given framework id is unknkown, or if the in-object variable is not an array,
+ * then it will return an empty array.
+ *
+ * @param $framework String: Optional. Get only the test module names for one
+ * particular framework.
+ * @return Array
+ */
+ public function getTestModuleNames( $framework = 'all' ) {
+ if ( $framework == 'all' ) {
+ return $this->testModuleNames;
+ } elseif ( isset( $this->testModuleNames[$framework] ) && is_array( $this->testModuleNames[$framework] ) ) {
+ return $this->testModuleNames[$framework];
+ } else {
+ return array();
+ }
+ }
/**
* Get the ResourceLoaderModule object for a given module name.
@@ -292,12 +396,29 @@ class ResourceLoader {
}
/**
+ * Get the list of sources
+ *
+ * @return Array: array( id => array of properties, .. )
+ */
+ public function getSources() {
+ return $this->sources;
+ }
+
+ /**
* Outputs a response to a resource load-request, including a content-type header.
*
* @param $context ResourceLoaderContext: Context in which a response should be formed
*/
public function respond( ResourceLoaderContext $context ) {
- global $wgResourceLoaderMaxage, $wgCacheEpoch;
+ global $wgCacheEpoch, $wgUseFileCache;
+
+ // Use file cache if enabled and available...
+ if ( $wgUseFileCache ) {
+ $fileCache = ResourceFileCache::newFromContext( $context );
+ if ( $this->tryRespondFromFileCache( $fileCache, $context ) ) {
+ return; // output handled
+ }
+ }
// 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
@@ -329,19 +450,6 @@ class ResourceLoader {
}
}
- // If a version wasn't specified we need a shorter expiry time for updates
- // to propagate to clients quickly
- if ( is_null( $context->getVersion() ) ) {
- $maxage = $wgResourceLoaderMaxage['unversioned']['client'];
- $smaxage = $wgResourceLoaderMaxage['unversioned']['server'];
- }
- // If a version was specified we can use a longer expiry time since changing
- // version numbers causes cache misses
- else {
- $maxage = $wgResourceLoaderMaxage['versioned']['client'];
- $smaxage = $wgResourceLoaderMaxage['versioned']['server'];
- }
-
// Preload information needed to the mtime calculation below
try {
$this->preloadModuleInfo( array_keys( $modules ), $context );
@@ -356,6 +464,9 @@ class ResourceLoader {
// the last modified time
$mtime = wfTimestamp( TS_UNIX, $wgCacheEpoch );
foreach ( $modules as $module ) {
+ /**
+ * @var $module ResourceLoaderModule
+ */
try {
// Calculate maximum modified time
$mtime = max( $mtime, $module->getModifiedTime( $context ) );
@@ -367,6 +478,65 @@ class ResourceLoader {
wfProfileOut( __METHOD__.'-getModifiedTime' );
+ // Send content type and cache related headers
+ $this->sendResponseHeaders( $context, $mtime );
+
+ // If there's an If-Modified-Since header, respond with a 304 appropriately
+ if ( $this->tryRespondLastModified( $context, $mtime ) ) {
+ wfProfileOut( __METHOD__ );
+ return; // output handled (buffers cleared)
+ }
+
+ // Generate a response
+ $response = $this->makeModuleResponse( $context, $modules, $missing );
+
+ // Prepend comments indicating exceptions
+ $response = $errors . $response;
+
+ // Capture any PHP warnings from the output buffer and append them to the
+ // response in a comment if we're in debug mode.
+ if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) {
+ $response = $this->makeComment( $warnings ) . $response;
+ }
+
+ // Remove the output buffer and output the response
+ ob_end_clean();
+ echo $response;
+
+ // Save response to file cache unless there are errors
+ if ( isset( $fileCache ) && !$errors && !$missing ) {
+ // Cache single modules...and other requests if there are enough hits
+ if ( ResourceFileCache::useFileCache( $context ) ) {
+ if ( $fileCache->isCacheWorthy() ) {
+ $fileCache->saveText( $response );
+ } else {
+ $fileCache->incrMissesRecent( $context->getRequest() );
+ }
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Send content type and last modified headers to the client.
+ * @param $context ResourceLoaderContext
+ * @param $mtime string TS_MW timestamp to use for last-modified
+ * @return void
+ */
+ protected function sendResponseHeaders( ResourceLoaderContext $context, $mtime ) {
+ global $wgResourceLoaderMaxage;
+ // If a version wasn't specified we need a shorter expiry time for updates
+ // to propagate to clients quickly
+ if ( is_null( $context->getVersion() ) ) {
+ $maxage = $wgResourceLoaderMaxage['unversioned']['client'];
+ $smaxage = $wgResourceLoaderMaxage['unversioned']['server'];
+ // If a version was specified we can use a longer expiry time since changing
+ // version numbers causes cache misses
+ } else {
+ $maxage = $wgResourceLoaderMaxage['versioned']['client'];
+ $smaxage = $wgResourceLoaderMaxage['versioned']['server'];
+ }
if ( $context->getOnly() === 'styles' ) {
header( 'Content-Type: text/css; charset=utf-8' );
} else {
@@ -382,7 +552,16 @@ class ResourceLoader {
$exp = min( $maxage, $smaxage );
header( 'Expires: ' . wfTimestamp( TS_RFC2822, $exp + time() ) );
}
+ }
+ /**
+ * If there's an If-Modified-Since header, respond with a 304 appropriately
+ * and clear out the output buffer. If the client cache is too old then do nothing.
+ * @param $context ResourceLoaderContext
+ * @param $mtime string The TS_MW timestamp to check the header against
+ * @return bool True iff 304 header sent and output handled
+ */
+ protected function tryRespondLastModified( ResourceLoaderContext $context, $mtime ) {
// If there's an If-Modified-Since header, respond with a 304 appropriately
// Some clients send "timestamp;length=123". Strip the part after the first ';'
// so we get a valid timestamp.
@@ -410,28 +589,61 @@ class ResourceLoader {
header( 'HTTP/1.0 304 Not Modified' );
header( 'Status: 304 Not Modified' );
- wfProfileOut( __METHOD__ );
- return;
+ return true;
}
}
+ return false;
+ }
- // Generate a response
- $response = $this->makeModuleResponse( $context, $modules, $missing );
-
- // Prepend comments indicating exceptions
- $response = $errors . $response;
-
- // Capture any PHP warnings from the output buffer and append them to the
- // response in a comment if we're in debug mode.
- if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) {
- $response = $this->makeComment( $warnings ) . $response;
+ /**
+ * Send out code for a response from file cache if possible
+ *
+ * @param $fileCache ObjectFileCache: Cache object for this request URL
+ * @param $context ResourceLoaderContext: Context in which to generate a response
+ * @return bool If this found a cache file and handled the response
+ */
+ protected function tryRespondFromFileCache(
+ ResourceFileCache $fileCache, ResourceLoaderContext $context
+ ) {
+ global $wgResourceLoaderMaxage;
+ // Buffer output to catch warnings.
+ ob_start();
+ // Get the maximum age the cache can be
+ $maxage = is_null( $context->getVersion() )
+ ? $wgResourceLoaderMaxage['unversioned']['server']
+ : $wgResourceLoaderMaxage['versioned']['server'];
+ // Minimum timestamp the cache file must have
+ $good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) );
+ if ( !$good ) {
+ try { // RL always hits the DB on file cache miss...
+ wfGetDB( DB_SLAVE );
+ } catch( DBConnectionError $e ) { // ...check if we need to fallback to cache
+ $good = $fileCache->isCacheGood(); // cache existence check
+ }
}
-
- // Remove the output buffer and output the response
+ if ( $good ) {
+ $ts = $fileCache->cacheTimestamp();
+ // Send content type and cache headers
+ $this->sendResponseHeaders( $context, $ts, false );
+ // If there's an If-Modified-Since header, respond with a 304 appropriately
+ if ( $this->tryRespondLastModified( $context, $ts ) ) {
+ return false; // output handled (buffers cleared)
+ }
+ $response = $fileCache->fetchText();
+ // Capture any PHP warnings from the output buffer and append them to the
+ // response in a comment if we're in debug mode.
+ if ( $context->getDebug() && strlen( $warnings = ob_get_contents() ) ) {
+ $response = "/*\n$warnings\n*/\n" . $response;
+ }
+ // Remove the output buffer and output the response
+ ob_end_clean();
+ echo $response . "\n/* Cached {$ts} */";
+ return true; // cache hit
+ }
+ // Clear buffer
ob_end_clean();
- echo $response;
- wfProfileOut( __METHOD__ );
+ return false; // cache miss
}
protected function makeComment( $text ) {
@@ -471,6 +683,10 @@ class ResourceLoader {
// Generate output
foreach ( $modules as $name => $module ) {
+ /**
+ * @var $module ResourceLoaderModule
+ */
+
wfProfileIn( __METHOD__ . '-' . $name );
try {
$scripts = '';
@@ -509,8 +725,10 @@ class ResourceLoader {
switch ( $context->getOnly() ) {
case 'scripts':
if ( is_string( $scripts ) ) {
+ // Load scripts raw...
$out .= $scripts;
} elseif ( is_array( $scripts ) ) {
+ // ...except when $scripts is an array of URLs
$out .= self::makeLoaderImplementScript( $name, $scripts, array(), array() );
}
break;
@@ -676,30 +894,31 @@ 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.
+ * @param $source String: Source of the module, or 'local' if not foreign.
* @param $script String: JavaScript code
*
* @return string
*/
- public static function makeCustomLoaderScript( $name, $version, $dependencies, $group, $script ) {
+ public static function makeCustomLoaderScript( $name, $version, $dependencies, $group, $source, $script ) {
$script = str_replace( "\n", "\n\t", trim( $script ) );
return Xml::encodeJsCall(
- "( function( name, version, dependencies, group ) {\n\t$script\n} )",
- array( $name, $version, $dependencies, $group ) );
+ "( function( name, version, dependencies, group, source ) {\n\t$script\n} )",
+ array( $name, $version, $dependencies, $group, $source ) );
}
/**
* Returns JS code which calls mw.loader.register with the given
* parameters. Has three calling conventions:
*
- * - ResourceLoader::makeLoaderRegisterScript( $name, $version, $dependencies, $group ):
+ * - ResourceLoader::makeLoaderRegisterScript( $name, $version, $dependencies, $group, $source ):
* Register a single module.
*
* - ResourceLoader::makeLoaderRegisterScript( array( $name1, $name2 ) ):
* Register modules with the given names.
*
* - ResourceLoader::makeLoaderRegisterScript( array(
- * array( $name1, $version1, $dependencies1, $group1 ),
- * array( $name2, $version2, $dependencies1, $group2 ),
+ * array( $name1, $version1, $dependencies1, $group1, $source1 ),
+ * array( $name2, $version2, $dependencies1, $group2, $source2 ),
* ...
* ) ):
* Registers modules with the given names and parameters.
@@ -708,18 +927,42 @@ 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.
+ * @param $source String: source of the module, or 'local' if not foreign
*
* @return string
*/
public static function makeLoaderRegisterScript( $name, $version = null,
- $dependencies = null, $group = null )
+ $dependencies = null, $group = null, $source = null )
{
if ( is_array( $name ) ) {
return Xml::encodeJsCall( 'mw.loader.register', array( $name ) );
} else {
$version = (int) $version > 1 ? (int) $version : 1;
return Xml::encodeJsCall( 'mw.loader.register',
- array( $name, $version, $dependencies, $group ) );
+ array( $name, $version, $dependencies, $group, $source ) );
+ }
+ }
+
+ /**
+ * Returns JS code which calls mw.loader.addSource() with the given
+ * parameters. Has two calling conventions:
+ *
+ * - ResourceLoader::makeLoaderSourcesScript( $id, $properties ):
+ * Register a single source
+ *
+ * - ResourceLoader::makeLoaderSourcesScript( array( $id1 => $props1, $id2 => $props2, ... ) );
+ * Register sources with the given IDs and properties.
+ *
+ * @param $id String: source ID
+ * @param $properties Array: source properties (see addSource())
+ *
+ * @return string
+ */
+ public static function makeLoaderSourcesScript( $id, $properties = null ) {
+ if ( is_array( $id ) ) {
+ return Xml::encodeJsCall( 'mw.loader.addSource', array( $id ) );
+ } else {
+ return Xml::encodeJsCall( 'mw.loader.addSource', array( $id, $properties ) );
}
}
@@ -732,8 +975,7 @@ class ResourceLoader {
* @return string
*/
public static function makeLoaderConditionalScript( $script ) {
- $script = str_replace( "\n", "\n\t", trim( $script ) );
- return "if(window.mw){\n\t$script\n}\n";
+ return "if(window.mw){\n".trim( $script )."\n}";
}
/**
@@ -809,7 +1051,7 @@ class ResourceLoader {
$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 );
@@ -844,7 +1086,7 @@ class ResourceLoader {
$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 326b7c4a..dd69bb01 100644
--- a/includes/resourceloader/ResourceLoaderContext.php
+++ b/includes/resourceloader/ResourceLoaderContext.php
@@ -21,7 +21,7 @@
*/
/**
- * Object passed around to modules which contains information about the state
+ * Object passed around to modules which contains information about the state
* of a specific loader request
*/
class ResourceLoaderContext {
@@ -42,7 +42,11 @@ class ResourceLoaderContext {
/* Methods */
- public function __construct( ResourceLoader $resourceLoader, WebRequest $request ) {
+ /**
+ * @param $resourceLoader ResourceLoader
+ * @param $request WebRequest
+ */
+ public function __construct( $resourceLoader, WebRequest $request ) {
global $wgDefaultSkin, $wgResourceLoaderDebug;
$this->resourceLoader = $resourceLoader;
@@ -59,11 +63,13 @@ class ResourceLoaderContext {
$this->only = $request->getVal( 'only' );
$this->version = $request->getVal( 'version' );
- if ( !$this->skin ) {
+ $skinnames = Skin::getSkinNames();
+ // If no skin is specified, or we don't recognize the skin, use the default skin
+ if ( !$this->skin || !isset( $skinnames[$this->skin] ) ) {
$this->skin = $wgDefaultSkin;
}
}
-
+
/**
* Expand a string of the form jquery.foo,bar|jquery.ui.baz,quux to
* an array of module names like array( 'jquery.foo', 'jquery.bar',
@@ -101,6 +107,14 @@ class ResourceLoaderContext {
}
/**
+ * Return a dummy ResourceLoaderContext object suitable for passing into things that don't "really" need a context
+ * @return ResourceLoaderContext
+ */
+ public static function newDummyContext() {
+ return new self( null, new FauxRequest( array() ) );
+ }
+
+ /**
* @return ResourceLoader
*/
public function getResourceLoader() {
@@ -150,14 +164,14 @@ class ResourceLoaderContext {
}
/**
- * @return string
+ * @return string|null
*/
public function getSkin() {
return $this->skin;
}
/**
- * @return string
+ * @return string|null
*/
public function getUser() {
return $this->user;
@@ -171,14 +185,14 @@ class ResourceLoaderContext {
}
/**
- * @return String
+ * @return String|null
*/
public function getOnly() {
return $this->only;
}
/**
- * @return String
+ * @return String|null
*/
public function getVersion() {
return $this->version;
@@ -211,7 +225,7 @@ class ResourceLoaderContext {
public function getHash() {
if ( !isset( $this->hash ) ) {
$this->hash = implode( '|', array(
- $this->getLanguage(), $this->getDirection(), $this->skin, $this->user,
+ $this->getLanguage(), $this->getDirection(), $this->skin, $this->user,
$this->debug, $this->only, $this->version
) );
}
diff --git a/includes/resourceloader/ResourceLoaderFileModule.php b/includes/resourceloader/ResourceLoaderFileModule.php
index f38c60ae..3d657e1c 100644
--- a/includes/resourceloader/ResourceLoaderFileModule.php
+++ b/includes/resourceloader/ResourceLoaderFileModule.php
@@ -33,47 +33,74 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected $remoteBasePath = '';
/**
* Array: List of paths to JavaScript files to always include
- * @example array( [file-path], [file-path], ... )
+ * @par Usage:
+ * @code
+ * array( [file-path], [file-path], ... )
+ * @endcode
*/
protected $scripts = array();
/**
* Array: List of JavaScript files to include when using a specific language
- * @example array( [language-code] => array( [file-path], [file-path], ... ), ... )
+ * @par Usage:
+ * @code
+ * array( [language-code] => array( [file-path], [file-path], ... ), ... )
+ * @endcode
*/
protected $languageScripts = array();
/**
* Array: List of JavaScript files to include when using a specific skin
- * @example array( [skin-name] => array( [file-path], [file-path], ... ), ... )
+ * @par Usage:
+ * @code
+ * array( [skin-name] => array( [file-path], [file-path], ... ), ... )
+ * @endcode
*/
protected $skinScripts = array();
/**
* Array: List of paths to JavaScript files to include in debug mode
- * @example array( [skin-name] => array( [file-path], [file-path], ... ), ... )
+ * @par Usage:
+ * @code
+ * array( [skin-name] => array( [file-path], [file-path], ... ), ... )
+ * @endcode
*/
protected $debugScripts = array();
/**
* Array: List of paths to JavaScript files to include in the startup module
- * @example array( [file-path], [file-path], ... )
+ * @par Usage:
+ * @code
+ * array( [file-path], [file-path], ... )
+ * @endcode
*/
protected $loaderScripts = array();
/**
* Array: List of paths to CSS files to always include
- * @example array( [file-path], [file-path], ... )
+ * @par Usage:
+ * @code
+ * array( [file-path], [file-path], ... )
+ * @endcode
*/
protected $styles = array();
/**
* Array: List of paths to CSS files to include when using specific skins
- * @example array( [file-path], [file-path], ... )
+ * @par Usage:
+ * @code
+ * array( [file-path], [file-path], ... )
+ * @endcode
*/
protected $skinStyles = array();
/**
* Array: List of modules this module depends on
- * @example array( [file-path], [file-path], ... )
+ * @par Usage:
+ * @code
+ * array( [file-path], [file-path], ... )
+ * @endcode
*/
protected $dependencies = array();
/**
* Array: List of message keys used by this module
- * @example array( [message-key], [message-key], ... )
+ * @par Usage:
+ * @code
+ * array( [message-key], [message-key], ... )
+ * @endcode
*/
protected $messages = array();
/** String: Name of group to load this module in */
@@ -84,12 +111,18 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
protected $debugRaw = true;
/**
* Array: Cache for mtime
- * @example array( [hash] => [mtime], [hash] => [mtime], ... )
+ * @par Usage:
+ * @code
+ * array( [hash] => [mtime], [hash] => [mtime], ... )
+ * @endcode
*/
protected $modifiedTime = array();
/**
* Array: Place where readStyleFile() tracks file dependencies
- * @example array( [file-path], [file-path], ... )
+ * @par Usage:
+ * @code
+ * array( [file-path], [file-path], ... )
+ * @endcode
*/
protected $localFileRefs = array();
@@ -106,6 +139,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
* to $wgScriptPath
*
* Below is a description for the $options array:
+ * @par Construction options:
* @code
* array(
* // Base path to prepend to all local paths in $options. Defaults to $IP
@@ -223,7 +257,11 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$files = $this->getScriptFiles( $context );
return $this->readScriptFiles( $files );
}
-
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
public function getScriptURLsForDebug( ResourceLoaderContext $context ) {
$urls = array();
foreach ( $this->getScriptFiles( $context ) as $file ) {
@@ -232,6 +270,9 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
return $urls;
}
+ /**
+ * @return bool
+ */
public function supportsURLLoading() {
return $this->debugRaw;
}
@@ -275,6 +316,10 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
return $styles;
}
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
public function getStyleURLsForDebug( ResourceLoaderContext $context ) {
$urls = array();
foreach ( $this->getStyleFiles( $context ) as $mediaType => $list ) {
@@ -377,7 +422,7 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
wfProfileIn( __METHOD__.'-filemtime' );
- $filesMtime = max( array_map( 'filemtime', $files ) );
+ $filesMtime = max( array_map( array( __CLASS__, 'safeFilemtime' ), $files ) );
wfProfileOut( __METHOD__.'-filemtime' );
$this->modifiedTime[$context->getHash()] = max(
$filesMtime,
@@ -503,10 +548,10 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
$js = '';
foreach ( array_unique( $scripts ) as $fileName ) {
$localPath = $this->getLocalPath( $fileName );
- $contents = file_get_contents( $localPath );
- if ( $contents === false ) {
+ if ( !file_exists( $localPath ) ) {
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
@@ -552,17 +597,18 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
*
* This method can be used as a callback for array_map()
*
- * @param $path String: File path of script file to read
+ * @param $path String: File path of style 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 ) {
$localPath = $this->getLocalPath( $path );
- $style = file_get_contents( $localPath );
- if ( $style === false ) {
+ if ( !file_exists( $localPath ) ) {
throw new MWException( __METHOD__.": style file not found: \"$localPath\"" );
}
+ $style = file_get_contents( $localPath );
if ( $flip ) {
$style = CSSJanus::transform( $style, true, false );
}
@@ -583,6 +629,23 @@ class ResourceLoaderFileModule extends ResourceLoaderModule {
}
/**
+ * 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
* @param $context ResourceLoaderContext
* @return bool
diff --git a/includes/resourceloader/ResourceLoaderFilePageModule.php b/includes/resourceloader/ResourceLoaderFilePageModule.php
index fc9aef1b..e3b719bb 100644
--- a/includes/resourceloader/ResourceLoaderFilePageModule.php
+++ b/includes/resourceloader/ResourceLoaderFilePageModule.php
@@ -1,8 +1,13 @@
<?php
-/*
+/**
* ResourceLoader definition for MediaWiki:Filepage.css
*/
class ResourceLoaderFilePageModule extends ResourceLoaderWikiModule {
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
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 ae1be5af..1a232ec2 100644
--- a/includes/resourceloader/ResourceLoaderModule.php
+++ b/includes/resourceloader/ResourceLoaderModule.php
@@ -52,11 +52,11 @@ abstract class ResourceLoaderModule {
# 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 */
protected $name = null;
-
+
// In-object cache for file dependencies
protected $fileDeps = array();
// In-object cache for message blob mtime
@@ -126,35 +126,36 @@ 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()
+ $url = ResourceLoader::makeLoaderURL(
+ array( $this->getName() ),
+ $context->getLanguage(),
+ $context->getSkin(),
+ $context->getUser(),
+ $context->getVersion(),
+ true, // debug
+ 'scripts', // only
+ $context->getRequest()->getBool( 'printable' ),
+ $context->getRequest()->getBool( 'handheld' )
);
- ksort( $query );
- return array( wfAppendQuery( $wgLoadScript, $query ) . '&*' );
+ return array( $url );
}
-
+
/**
* Whether this module supports URL loading. If this function returns false,
* getScript() will be used even in cases (debug mode, no only param) where
@@ -175,28 +176,29 @@ abstract class ResourceLoaderModule {
// Stub, override expected
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()
+ $url = ResourceLoader::makeLoaderURL(
+ array( $this->getName() ),
+ $context->getLanguage(),
+ $context->getSkin(),
+ $context->getUser(),
+ $context->getVersion(),
+ true, // debug
+ 'styles', // only
+ $context->getRequest()->getBool( 'printable' ),
+ $context->getRequest()->getBool( 'handheld' )
);
- ksort( $query );
- return array( 'all' => array( wfAppendQuery( $wgLoadScript, $query ) . '&*' ) );
+ return array( 'all' => array( $url ) );
}
/**
@@ -210,17 +212,27 @@ abstract class ResourceLoaderModule {
// Stub, override expected
return array();
}
-
+
/**
* Get the group this module is in.
- *
+ *
* @return String: Group name
*/
public function getGroup() {
// Stub, override expected
return null;
}
-
+
+ /**
+ * Get the origin of this module. Should only be overridden for foreign modules.
+ *
+ * @return String: Origin name, 'local' for local modules
+ */
+ public function getSource() {
+ // Stub, override expected
+ return 'local';
+ }
+
/**
* Where on the HTML page should this module's JS be loaded?
* 'top': in the <head>
@@ -261,7 +273,7 @@ abstract class ResourceLoaderModule {
// Stub, override expected
return array();
}
-
+
/**
* Get the files this module depends on indirectly for a given skin.
* Currently these are only image files referenced by the module's CSS.
@@ -288,7 +300,7 @@ abstract class ResourceLoaderModule {
}
return $this->fileDeps[$skin];
}
-
+
/**
* Set preloaded file dependency information. Used so we can load this
* information for all modules at once.
@@ -298,7 +310,7 @@ abstract class ResourceLoaderModule {
public function setFileDependencies( $skin, $deps ) {
$this->fileDeps[$skin] = $deps;
}
-
+
/**
* Get the last modification timestamp of the message blob for this
* module in a given language.
@@ -309,7 +321,7 @@ abstract class ResourceLoaderModule {
if ( !isset( $this->msgBlobMtime[$lang] ) ) {
if ( !count( $this->getMessages() ) )
return 0;
-
+
$dbr = wfGetDB( DB_SLAVE );
$msgBlobMtime = $dbr->selectField( 'msg_resource', 'mr_timestamp', array(
'mr_resource' => $this->getName(),
@@ -325,7 +337,7 @@ abstract class ResourceLoaderModule {
}
return $this->msgBlobMtime[$lang];
}
-
+
/**
* Set a preloaded message blob last modification timestamp. Used so we
* can load this information for all modules at once.
@@ -335,9 +347,9 @@ abstract class ResourceLoaderModule {
public function setMsgBlobMtime( $lang, $mtime ) {
$this->msgBlobMtime[$lang] = $mtime;
}
-
+
/* Abstract Methods */
-
+
/**
* Get this module's last modification timestamp for a given
* combination of language, skin and debug mode flag. This is typically
@@ -345,6 +357,10 @@ abstract class ResourceLoaderModule {
* timestamps. Whenever anything happens that changes the module's
* contents for these parameters, the mtime should increase.
*
+ * NOTE: The mtime of the module's messages is NOT automatically included.
+ * If you want this to happen, you'll need to call getMsgBlobMtime()
+ * yourself and take its result into consideration.
+ *
* @param $context ResourceLoaderContext: Context object
* @return Integer: UNIX timestamp
*/
@@ -352,7 +368,7 @@ abstract class ResourceLoaderModule {
// 0 would mean now
return 1;
}
-
+
/**
* Check whether this module is known to be empty. If a child class
* has an easy and cheap way to determine that this module is
@@ -400,7 +416,7 @@ abstract class ResourceLoaderModule {
$err = $e->getMessage();
$result = "throw new Error(" . Xml::encodeJsVar("JavaScript parse error: $err") . ");";
}
-
+
$cache->set( $key, $result );
return $result;
} else {
@@ -408,6 +424,9 @@ abstract class ResourceLoaderModule {
}
}
+ /**
+ * @return JSParser
+ */
protected static function javaScriptParser() {
if ( !self::$jsParser ) {
self::$jsParser = new JSParser();
diff --git a/includes/resourceloader/ResourceLoaderStartUpModule.php b/includes/resourceloader/ResourceLoaderStartUpModule.php
index 43f1dbd2..5dbce439 100644
--- a/includes/resourceloader/ResourceLoaderStartUpModule.php
+++ b/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -38,21 +38,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
$wgVariantArticlePath, $wgActionPaths, $wgUseAjax, $wgVersion,
$wgEnableAPI, $wgEnableWriteAPI, $wgDBname, $wgEnableMWSuggest,
$wgSitename, $wgFileExtensions, $wgExtensionAssetsPath,
- $wgCookiePrefix, $wgResourceLoaderMaxQueryLength, $wgLegacyJavaScriptGlobals;
+ $wgCookiePrefix, $wgResourceLoaderMaxQueryLength;
- // Pre-process information
- $separatorTransTable = $wgContLang->separatorTransformTable();
- $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
- $compactSeparatorTransTable = array(
- implode( "\t", array_keys( $separatorTransTable ) ),
- implode( "\t", $separatorTransTable ),
- );
- $digitTransTable = $wgContLang->digitTransformTable();
- $digitTransTable = $digitTransTable ? $digitTransTable : array();
- $compactDigitTransTable = array(
- implode( "\t", array_keys( $digitTransTable ) ),
- implode( "\t", $digitTransTable ),
- );
$mainPage = Title::newMainPage();
/**
@@ -81,7 +68,9 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgScriptExtension' => $wgScriptExtension,
'wgScript' => $wgScript,
'wgVariantArticlePath' => $wgVariantArticlePath,
- 'wgActionPaths' => $wgActionPaths,
+ // Force object to avoid "empty" associative array from
+ // becoming [] instead of {} in JS (bug 34604)
+ 'wgActionPaths' => (object)$wgActionPaths,
'wgServer' => $wgServer,
'wgUserLanguage' => $context->getLanguage(),
'wgContentLanguage' => $wgContLang->getCode(),
@@ -91,8 +80,6 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
'wgDefaultDateFormat' => $wgContLang->getDefaultDateFormat(),
'wgMonthNames' => $wgContLang->getMonthNamesArray(),
'wgMonthNamesShort' => $wgContLang->getMonthAbbreviationsArray(),
- 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
- 'wgDigitTransformTable' => $compactDigitTransTable,
'wgMainPageTitle' => $mainPage ? $mainPage->getPrefixedText() : null,
'wgFormattedNamespaces' => $wgContLang->getFormattedNamespaces(),
'wgNamespaceIds' => $namespaceIds,
@@ -107,7 +94,6 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
// MediaWiki sets cookies to have this prefix by default
'wgCookiePrefix' => $wgCookiePrefix,
'wgResourceLoaderMaxQueryLength' => $wgResourceLoaderMaxQueryLength,
- 'wgLegacyJavaScriptGlobals' => $wgLegacyJavaScriptGlobals,
'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
);
if ( $wgUseAjax && $wgEnableMWSuggest ) {
@@ -132,6 +118,11 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
$out = '';
$registrations = array();
$resourceLoader = $context->getResourceLoader();
+
+ // Register sources
+ $out .= ResourceLoader::makeLoaderSourcesScript( $resourceLoader->getSources() );
+
+ // Register modules
foreach ( $resourceLoader->getModuleNames() as $name ) {
$module = $resourceLoader->getModule( $name );
// Support module loader scripts
@@ -139,9 +130,10 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
if ( $loader !== false ) {
$deps = $module->getDependencies();
$group = $module->getGroup();
+ $source = $module->getSource();
$version = wfTimestamp( TS_ISO_8601_BASIC,
$module->getModifiedTime( $context ) );
- $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $loader );
+ $out .= ResourceLoader::makeCustomLoaderScript( $name, $version, $deps, $group, $source, $loader );
}
// Automatically register module
else {
@@ -149,23 +141,29 @@ 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
+ // Modules without dependencies, a group or a foreign source pass two arguments (name, timestamp) to
// mw.loader.register()
- if ( !count( $module->getDependencies() && $module->getGroup() === null ) ) {
+ if ( !count( $module->getDependencies() && $module->getGroup() === null && $module->getSource() === 'local' ) ) {
$registrations[] = array( $name, $mtime );
}
- // Modules with dependencies but no group pass three arguments
+ // Modules with dependencies but no group or foreign source pass three arguments
// (name, timestamp, dependencies) to mw.loader.register()
- elseif ( $module->getGroup() === null ) {
+ elseif ( $module->getGroup() === null && $module->getSource() === 'local' ) {
$registrations[] = array(
$name, $mtime, $module->getDependencies() );
}
- // Modules with dependencies pass four arguments (name, timestamp, dependencies, group)
+ // Modules with a group but no foreign source pass four arguments (name, timestamp, dependencies, group)
// to mw.loader.register()
- else {
+ elseif ( $module->getSource() === 'local' ) {
$registrations[] = array(
$name, $mtime, $module->getDependencies(), $module->getGroup() );
}
+ // Modules with a foreign source pass five arguments (name, timestamp, dependencies, group, source)
+ // to mw.loader.register()
+ else {
+ $registrations[] = array(
+ $name, $mtime, $module->getDependencies(), $module->getGroup(), $module->getSource() );
+ }
}
}
$out .= ResourceLoader::makeLoaderRegisterScript( $registrations );
@@ -229,6 +227,9 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
return $out;
}
+ /**
+ * @return bool
+ */
public function supportsURLLoading() {
return false;
}
diff --git a/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
new file mode 100644
index 00000000..02693d3e
--- /dev/null
+++ b/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Trevor Parscal
+ * @author Roan Kattouw
+ */
+
+/**
+ * Module for user preference customizations
+ */
+class ResourceLoaderUserCSSPrefsModule extends ResourceLoaderModule {
+
+ /* Protected Members */
+
+ 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] ) ) {
+ return $this->modifiedTime[$hash];
+ }
+
+ global $wgUser;
+ return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
+ }
+
+ /**
+ * @param $context ResourceLoaderContext
+ * @return array
+ */
+ public function getStyles( ResourceLoaderContext $context ) {
+ global $wgAllowUserCssPrefs, $wgUser;
+
+ if ( $wgAllowUserCssPrefs ) {
+ $options = $wgUser->getOptions();
+
+ // Build CSS rules
+ $rules = array();
+
+ // Underline: 2 = browser default, 1 = always, 0 = never
+ 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";
+ } else {
+ $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }";
+ $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #ba0000; }";
+ $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }";
+ }
+ if ( $options['justify'] ) {
+ $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
+ }
+ if ( !$options['showtoc'] ) {
+ $rules[] = "#toc { display: none; }\n";
+ }
+ if ( !$options['editsection'] ) {
+ $rules[] = ".editsection { display: none; }\n";
+ }
+ if ( $options['editfont'] !== 'default' ) {
+ $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
+ }
+ $style = implode( "\n", $rules );
+ if ( $this->getFlip( $context ) ) {
+ $style = CSSJanus::transform( $style, true, false );
+ }
+ return array( 'all' => $style );
+ }
+ return array();
+ }
+
+ /**
+ * @return string
+ */
+ public function getGroup() {
+ return 'private';
+ }
+
+ /**
+ * @return array
+ */
+ public function getDependencies() {
+ return array( 'mediawiki.user' );
+ }
+}
diff --git a/includes/resourceloader/ResourceLoaderUserModule.php b/includes/resourceloader/ResourceLoaderUserModule.php
index 892e8462..338b6322 100644
--- a/includes/resourceloader/ResourceLoaderUserModule.php
+++ b/includes/resourceloader/ResourceLoaderUserModule.php
@@ -34,15 +34,30 @@ class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
*/
protected function getPages( ResourceLoaderContext $context ) {
if ( $context->getUser() ) {
+ // Get the normalized title of the user's user page
$username = $context->getUser();
- return array(
- "User:$username/common.js" => array( 'type' => 'script' ),
- "User:$username/" . $context->getSkin() . '.js' =>
+ $userpageTitle = Title::makeTitleSafe( NS_USER, $username );
+ $userpage = $userpageTitle->getPrefixedDBkey(); // Needed so $excludepages works
+
+ $pages = array(
+ "$userpage/common.js" => array( 'type' => 'script' ),
+ "$userpage/" . $context->getSkin() . '.js' =>
array( 'type' => 'script' ),
- "User:$username/common.css" => array( 'type' => 'style' ),
- "User:$username/" . $context->getSkin() . '.css' =>
+ "$userpage/common.css" => array( 'type' => 'style' ),
+ "$userpage/" . $context->getSkin() . '.css' =>
array( 'type' => 'style' ),
);
+
+ // Hack for bug 26283: if we're on a preview page for a CSS/JS page,
+ // we need to exclude that page from this module. In that case, the excludepage
+ // parameter will be set to the name of the page we need to exclude.
+ $excludepage = $context->getRequest()->getVal( 'excludepage' );
+ if ( isset( $pages[$excludepage] ) ) {
+ // This works because $excludepage is generated with getPrefixedDBkey(),
+ // just like the keys in $pages[] above
+ unset( $pages[$excludepage] );
+ }
+ return $pages;
}
return array();
}
diff --git a/includes/resourceloader/ResourceLoaderUserOptionsModule.php b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
index 78548416..7b162205 100644
--- a/includes/resourceloader/ResourceLoaderUserOptionsModule.php
+++ b/includes/resourceloader/ResourceLoaderUserOptionsModule.php
@@ -42,9 +42,8 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
if ( isset( $this->modifiedTime[$hash] ) ) {
return $this->modifiedTime[$hash];
}
-
+
global $wgUser;
-
return $this->modifiedTime[$hash] = wfTimestamp( TS_UNIX, $wgUser->getTouched() );
}
@@ -54,59 +53,11 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
*/
public function getScript( ResourceLoaderContext $context ) {
global $wgUser;
- return Xml::encodeJsCall( 'mw.user.options.set',
+ return Xml::encodeJsCall( 'mw.user.options.set',
array( $wgUser->getOptions() ) );
}
/**
- * @param $context ResourceLoaderContext
- * @return array
- */
- public function getStyles( ResourceLoaderContext $context ) {
- global $wgAllowUserCssPrefs, $wgUser;
-
- if ( $wgAllowUserCssPrefs ) {
- $options = $wgUser->getOptions();
-
- // Build CSS rules
- $rules = array();
- 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";
- } else {
- $rules[] = "a.new, #quickbar a.new, a.stub, #quickbar a.stub { color: inherit; }";
- $rules[] = "a.new:after, #quickbar a.new:after { content: '?'; color: #ba0000; }";
- $rules[] = "a.stub:after, #quickbar a.stub:after { content: '!'; color: #772233; }";
- }
- if ( $options['justify'] ) {
- $rules[] = "#article, #bodyContent, #mw_content { text-align: justify; }\n";
- }
- if ( !$options['showtoc'] ) {
- $rules[] = "#toc { display: none; }\n";
- }
- if ( !$options['editsection'] ) {
- $rules[] = ".editsection { display: none; }\n";
- }
- if ( $options['editfont'] !== 'default' ) {
- $rules[] = "textarea { font-family: {$options['editfont']}; }\n";
- }
- $style = implode( "\n", $rules );
- if ( $this->getFlip( $context ) ) {
- $style = CSSJanus::transform( $style, true, false );
- }
- return array( 'all' => $style );
- }
- return array();
- }
-
- /**
* @return string
*/
public function getGroup() {
diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php
index 9403534c..e1a52388 100644
--- a/includes/resourceloader/ResourceLoaderUserTokensModule.php
+++ b/includes/resourceloader/ResourceLoaderUserTokensModule.php
@@ -32,7 +32,7 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
/**
* Fetch the tokens for the current user.
- *
+ *
* @param $context ResourceLoaderContext: Context object
* @return Array: List of tokens keyed by token type
*/
@@ -40,7 +40,7 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
global $wgUser;
return array(
- 'editToken' => $wgUser->edittoken(),
+ 'editToken' => $wgUser->getEditToken(),
'watchToken' => ApiQueryInfo::getWatchToken(null, null),
);
}
@@ -50,7 +50,7 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
* @return string
*/
public function getScript( ResourceLoaderContext $context ) {
- return Xml::encodeJsCall( 'mw.user.tokens.set',
+ return Xml::encodeJsCall( 'mw.user.tokens.set',
array( $this->contextUserTokens( $context ) ) );
}
diff --git a/includes/resourceloader/ResourceLoaderWikiModule.php b/includes/resourceloader/ResourceLoaderWikiModule.php
index bad61cb9..91a51f89 100644
--- a/includes/resourceloader/ResourceLoaderWikiModule.php
+++ b/includes/resourceloader/ResourceLoaderWikiModule.php
@@ -24,28 +24,47 @@ defined( 'MEDIAWIKI' ) || die( 1 );
/**
* Abstraction for resource loader modules which pull from wiki pages
- *
- * This can only be used for wiki pages in the MediaWiki and User namespaces,
- * because of its dependence on the functionality of
- * Title::isValidCssJsSubpage.
+ *
+ * This can only be used for wiki pages in the MediaWiki and User namespaces,
+ * because of its dependence on the functionality of
+ * Title::isCssJsSubpage.
*/
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();
-
+
/* Abstract Protected Methods */
-
+
+ /**
+ * @abstract
+ * @param $context ResourceLoaderContext
+ */
abstract protected function getPages( ResourceLoaderContext $context );
-
+
/* Protected Methods */
/**
+ * Get the Database object used in getTitleMTimes(). Defaults to the local slave DB
+ * but subclasses may want to override this to return a remote DB object, or to return
+ * null if getTitleMTimes() shouldn't access the DB at all.
+ *
+ * NOTE: This ONLY works for getTitleMTimes() and getModifiedTime(), NOT FOR ANYTHING ELSE.
+ * In particular, it doesn't work for getting the content of JS and CSS pages. That functionality
+ * will use the local DB irrespective of the return value of this method.
+ *
+ * @return DatabaseBase|null
+ */
+ protected function getDB() {
+ return wfGetDB( DB_SLAVE );
+ }
+
+ /**
* @param $title Title
* @return null|string
*/
@@ -54,7 +73,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
$message = wfMessage( $title->getDBkey() )->inContentLanguage();
return $message->exists() ? $message->plain() : '';
}
- if ( !$title->isCssJsSubpage() ) {
+ if ( !$title->isCssJsSubpage() && !$title->isCssOrJsPage() ) {
return null;
}
$revision = Revision::newFromTitle( $title );
@@ -63,7 +82,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
}
return $revision->getRawText();
}
-
+
/* Methods */
/**
@@ -98,7 +117,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
*/
public function getStyles( ResourceLoaderContext $context ) {
global $wgScriptPath;
-
+
$styles = array();
foreach ( $this->getPages( $context ) as $titleText => $options ) {
if ( $options['type'] !== 'style' ) {
@@ -107,7 +126,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
$title = Title::newFromText( $titleText );
if ( !$title || $title->isRedirect() ) {
continue;
- }
+ }
$media = isset( $options['media'] ) ? $options['media'] : 'all';
$style = $this->getContent( $title );
if ( strval( $style ) === '' ) {
@@ -138,6 +157,7 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
if ( count( $mtimes ) ) {
$modifiedTime = max( $modifiedTime, max( $mtimes ) );
}
+ $modifiedTime = max( $modifiedTime, $this->getMsgBlobMtime( $context->getLanguage() ) );
return $modifiedTime;
}
@@ -156,19 +176,24 @@ abstract class ResourceLoaderWikiModule extends ResourceLoaderModule {
* @return array( prefixed DB key => UNIX timestamp ), nonexistent titles are dropped
*/
protected function getTitleMtimes( ResourceLoaderContext $context ) {
+ $dbr = $this->getDB();
+ if ( !$dbr ) {
+ // We're dealing with a subclass that doesn't have a DB
+ return array();
+ }
+
$hash = $context->getHash();
if ( isset( $this->titleMtimes[$hash] ) ) {
return $this->titleMtimes[$hash];
}
-
+
$this->titleMtimes[$hash] = array();
$batch = new LinkBatch;
foreach ( $this->getPages( $context ) as $titleText => $options ) {
$batch->addObj( Title::newFromText( $titleText ) );
}
-
+
if ( !$batch->isEmpty() ) {
- $dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'page',
array( 'page_namespace', 'page_title', 'page_touched' ),
$batch->constructSet( 'page', $dbr ),
diff --git a/includes/revisiondelete/RevisionDelete.php b/includes/revisiondelete/RevisionDelete.php
index b329fc4b..6cee6246 100644
--- a/includes/revisiondelete/RevisionDelete.php
+++ b/includes/revisiondelete/RevisionDelete.php
@@ -25,14 +25,18 @@ class RevDel_RevisionList extends RevDel_List {
*/
public function doQuery( $db ) {
$ids = array_map( 'intval', $this->ids );
- $live = $db->select( array('revision','page'), '*',
+ $live = $db->select(
+ array( 'revision', 'page', 'user' ),
+ array_merge( Revision::selectFields(), Revision::selectUserFields() ),
array(
'rev_page' => $this->title->getArticleID(),
'rev_id' => $ids,
- 'rev_page = page_id'
),
__METHOD__,
- array( 'ORDER BY' => 'rev_id DESC' )
+ array( 'ORDER BY' => 'rev_id DESC' ),
+ array(
+ 'page' => Revision::pageJoinCond(),
+ 'user' => Revision::userJoinCond() )
);
if ( $live->numRows() >= count( $ids ) ) {
@@ -128,19 +132,19 @@ class RevDel_RevisionItem extends RevDel_Item {
}
public function getAuthorNameField() {
- return 'rev_user_text';
+ return 'user_name'; // see Revision::selectUserFields()
}
public function canView() {
- return $this->revision->userCan( Revision::DELETED_RESTRICTED );
+ return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->list->getUser() );
}
public function canViewContent() {
- return $this->revision->userCan( Revision::DELETED_TEXT );
+ return $this->revision->userCan( Revision::DELETED_TEXT, $this->list->getUser() );
}
public function getBits() {
- return $this->revision->mDeleted;
+ return $this->revision->getVisibility();
}
public function setBits( $bits ) {
@@ -189,7 +193,7 @@ class RevDel_RevisionItem extends RevDel_Item {
* Overridden by RevDel_ArchiveItem.
*/
protected function getRevisionLink() {
- $date = $this->list->getLang()->timeanddate( $this->revision->getTimestamp(), true );
+ $date = $this->list->getLanguage()->timeanddate( $this->revision->getTimestamp(), true );
if ( $this->isDeleted() && !$this->canViewContent() ) {
return $date;
}
@@ -336,7 +340,7 @@ class RevDel_ArchiveItem extends RevDel_RevisionItem {
protected function getRevisionLink() {
$undelete = SpecialPage::getTitleFor( 'Undelete' );
- $date = $this->list->getLang()->timeanddate( $this->revision->getTimestamp(), true );
+ $date = $this->list->getLanguage()->timeanddate( $this->revision->getTimestamp(), true );
if ( $this->isDeleted() && !$this->canViewContent() ) {
return $date;
}
@@ -509,11 +513,11 @@ class RevDel_FileItem extends RevDel_Item {
}
public function canView() {
- return $this->file->userCan( File::DELETED_RESTRICTED );
+ return $this->file->userCan( File::DELETED_RESTRICTED, $this->list->getUser() );
}
public function canViewContent() {
- return $this->file->userCan( File::DELETED_FILE );
+ return $this->file->userCan( File::DELETED_FILE, $this->list->getUser() );
}
public function getBits() {
@@ -567,7 +571,7 @@ class RevDel_FileItem extends RevDel_Item {
* Overridden by RevDel_ArchivedFileItem.
*/
protected function getLink() {
- $date = $this->list->getLang()->timeanddate( $this->file->getTimestamp(), true );
+ $date = $this->list->getLanguage()->timeanddate( $this->file->getTimestamp(), true );
if ( $this->isDeleted() ) {
# Hidden files...
if ( !$this->canViewContent() ) {
@@ -580,7 +584,7 @@ class RevDel_FileItem extends RevDel_Item {
array(
'target' => $this->list->title->getPrefixedText(),
'file' => $this->file->getArchiveName(),
- 'token' => $this->list->getUser()->editToken(
+ 'token' => $this->list->getUser()->getEditToken(
$this->file->getArchiveName() )
)
);
@@ -596,7 +600,7 @@ class RevDel_FileItem extends RevDel_Item {
* @return string HTML
*/
protected function getUserTools() {
- if( $this->file->userCan( Revision::DELETED_USER ) ) {
+ if( $this->file->userCan( Revision::DELETED_USER, $this->list->getUser() ) ) {
$link = Linker::userLink( $this->file->user, $this->file->user_text ) .
Linker::userToolLinks( $this->file->user, $this->file->user_text );
} else {
@@ -615,7 +619,7 @@ class RevDel_FileItem extends RevDel_Item {
* @return string HTML
*/
protected function getComment() {
- if( $this->file->userCan( File::DELETED_COMMENT ) ) {
+ if( $this->file->userCan( File::DELETED_COMMENT, $this->list->getUser() ) ) {
$block = Linker::commentBlock( $this->file->description );
} else {
$block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
@@ -630,11 +634,11 @@ class RevDel_FileItem extends RevDel_Item {
$data =
wfMsg(
'widthheight',
- $this->list->getLang()->formatNum( $this->file->getWidth() ),
- $this->list->getLang()->formatNum( $this->file->getHeight() )
+ $this->list->getLanguage()->formatNum( $this->file->getWidth() ),
+ $this->list->getLanguage()->formatNum( $this->file->getHeight() )
) .
' (' .
- wfMsgExt( 'nbytes', 'parsemag', $this->list->getLang()->formatNum( $this->file->getSize() ) ) .
+ wfMsgExt( 'nbytes', 'parsemag', $this->list->getLanguage()->formatNum( $this->file->getSize() ) ) .
')';
return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
@@ -718,7 +722,7 @@ class RevDel_ArchivedFileItem extends RevDel_FileItem {
}
protected function getLink() {
- $date = $this->list->getLang()->timeanddate( $this->file->getTimestamp(), true );
+ $date = $this->list->getLanguage()->timeanddate( $this->file->getTimestamp(), true );
$undelete = SpecialPage::getTitleFor( 'Undelete' );
$key = $this->file->getKey();
# Hidden files...
@@ -729,7 +733,7 @@ class RevDel_ArchivedFileItem extends RevDel_FileItem {
array(
'target' => $this->list->title->getPrefixedText(),
'file' => $key,
- 'token' => $this->list->getUser()->editToken( $key )
+ 'token' => $this->list->getUser()->getEditToken( $key )
)
);
}
@@ -807,7 +811,7 @@ class RevDel_LogItem extends RevDel_Item {
}
public function canView() {
- return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
+ return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED, $this->list->getUser() );
}
public function canViewContent() {
@@ -843,9 +847,10 @@ class RevDel_LogItem extends RevDel_Item {
}
public function getHTML() {
- $date = htmlspecialchars( $this->list->getLang()->timeanddate( $this->row->log_timestamp ) );
- $paramArray = LogPage::extractParams( $this->row->log_params );
+ $date = htmlspecialchars( $this->list->getLanguage()->timeanddate( $this->row->log_timestamp ) );
$title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
+ $formatter = LogFormatter::newFromRow( $this->row );
+ $formatter->setAudience( LogFormatter::FOR_THIS_USER );
// Log link for this page
$loglink = Linker::link(
@@ -854,27 +859,14 @@ class RevDel_LogItem extends RevDel_Item {
array(),
array( 'page' => $title->getPrefixedText() )
);
- // Action text
- if( !$this->canView() ) {
- $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
- } else {
- $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 = 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>';
- }
+ // User links and action text
+ $action = $formatter->getActionText();
// Comment
- $comment = $this->list->getLang()->getDirMark() . Linker::commentBlock( $this->row->log_comment );
+ $comment = $this->list->getLanguage()->getDirMark() . Linker::commentBlock( $this->row->log_comment );
if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
$comment = '<span class="history-deleted">' . $comment . '</span>';
}
- return "<li>($loglink) $date $userLink $action $comment</li>";
+
+ return "<li>($loglink) $date $action $comment</li>";
}
}
diff --git a/includes/revisiondelete/RevisionDeleteAbstracts.php b/includes/revisiondelete/RevisionDeleteAbstracts.php
index 73af1e5f..dc7af194 100644
--- a/includes/revisiondelete/RevisionDeleteAbstracts.php
+++ b/includes/revisiondelete/RevisionDeleteAbstracts.php
@@ -6,7 +6,7 @@
* relevant rows, to return RevDel_Item subclasses wrapping them, and
* to wrap bulk update operations.
*/
-abstract class RevDel_List extends Rev_List {
+abstract class RevDel_List extends RevisionListBase {
function __construct( IContextSource $context, Title $title, array $ids ) {
parent::__construct( $context, $title );
$this->ids = $ids;
@@ -242,7 +242,7 @@ abstract class RevDel_List extends Rev_List {
/**
* Abstract base class for deletable items
*/
-abstract class RevDel_Item extends Rev_Item {
+abstract class RevDel_Item extends RevisionItemBase {
/**
* Returns true if the item is "current", and the operation to set the given
* bits can't be executed for that reason
diff --git a/includes/revisiondelete/RevisionDeleter.php b/includes/revisiondelete/RevisionDeleter.php
index bde586c5..59a9fa82 100644
--- a/includes/revisiondelete/RevisionDeleter.php
+++ b/includes/revisiondelete/RevisionDeleter.php
@@ -28,18 +28,24 @@ 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
- * restrictions to sysops", "removed restrictions from sysops", or null.
+ * 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
+ * keys describing the items that were hidden, $arr[1] will contain
+ * an array of keys describing the items that were unhidden, and $arr[2]
+ * will contain an array with a single message key, which can be one of
+ * "revdelete-restricted", "revdelete-unrestricted" indicating (un)suppression
+ * or null to indicate nothing in particular.
+ * You can turn the keys in $arr[0] and $arr[1] into message keys by
+ * appending -hid and and -unhid to the keys respectively.
*
* @param $n Integer: the new bitfield.
* @param $o Integer: the old bitfield.
* @return An array as described above.
+ * @since 1.19 public
*/
- protected static function getChanges( $n, $o ) {
+ public static function getChanges( $n, $o ) {
$diff = $n ^ $o;
$ret = array( 0 => array(), 1 => array(), 2 => array() );
// Build bitfield changes in language
@@ -59,51 +65,11 @@ class RevisionDeleter {
return $ret;
}
- /**
- * Gets a log message to describe the given revision visibility change. This
- * message will be of the form "[hid {content, edit summary, username}];
- * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
- *
- * @param $count Integer: The number of effected revisions.
- * @param $nbitfield Integer: The new bitfield for the revision.
- * @param $obitfield Integer: The old bitfield for the revision.
- * @param $language Language object to use
- * @param $isForLog Boolean
+ /** Get DB field name for URL param...
+ * Future code for other things may also track
+ * other types of revision-specific changes.
+ * @return string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
*/
- public static function getLogMessage( $count, $nbitfield, $obitfield, $language, $isForLog = false ) {
- $changes = self::getChanges( $nbitfield, $obitfield );
- array_walk( $changes, array( __CLASS__, 'expandMessageArray' ), $language );
-
- $changesText = array();
-
- if( count( $changes[0] ) ) {
- $changesText[] = wfMsgExt( 'revdelete-hid', array( 'parsemag', 'language' => $language ), $language->commaList( $changes[0] ) );
- }
- if( count( $changes[1] ) ) {
- $changesText[] = wfMsgExt( 'revdelete-unhid', array( 'parsemag', 'language' => $language ), $language->commaList( $changes[1] ) );
- }
-
- $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, array( 'parsemag', 'language' => $language ), $s, $language->formatNum($count) );
- }
-
- private static function expandMessageArray( &$msg, $key, $language ) {
- if ( is_array ( $msg ) ) {
- array_walk( $msg, array( __CLASS__, 'expandMessageArray' ), $language );
- } else {
- $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.
- // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
public static function getRelationType( $typeName ) {
if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
$typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
@@ -147,16 +113,15 @@ class RevisionDeleter {
*
* @param $title Title
* @param $paramArray Array
- * @param $skin Skin
* @param $messages
* @return String
*/
- public static function getLogLinks( $title, $paramArray, $skin, $messages ) {
+ public static function getLogLinks( $title, $paramArray, $messages ) {
global $wgLang;
if ( count( $paramArray ) >= 2 ) {
// Different revision types use different URL params...
- $originalKey = $key = $paramArray[0];
+ $key = $paramArray[0];
// $paramArray[1] is a CSV of the IDs
$Ids = explode( ',', $paramArray[1] );
@@ -166,19 +131,18 @@ class RevisionDeleter {
if ( count( $Ids ) == 1 ) {
// Live revision diffs...
if ( in_array( $key, array( 'oldid', 'revision' ) ) ) {
- $revert[] = $skin->link(
+ $revert[] = Linker::linkKnown(
$title,
$messages['diff'],
array(),
array(
'diff' => intval( $Ids[0] ),
'unhide' => 1
- ),
- array( 'known', 'noclasses' )
+ )
);
// Deleted revision diffs...
} elseif ( in_array( $key, array( 'artimestamp','archive' ) ) ) {
- $revert[] = $skin->link(
+ $revert[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Undelete' ),
$messages['diff'],
array(),
@@ -186,14 +150,13 @@ class RevisionDeleter {
'target' => $title->getPrefixedDBKey(),
'diff' => 'prev',
'timestamp' => $Ids[0]
- ),
- array( 'known', 'noclasses' )
+ )
);
}
}
// View/modify link...
- $revert[] = $skin->link(
+ $revert[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Revisiondelete' ),
$messages['revdel-restore'],
array(),
@@ -201,8 +164,7 @@ class RevisionDeleter {
'target' => $title->getPrefixedText(),
'type' => $key,
'ids' => implode(',', $Ids),
- ),
- array( 'known', 'noclasses' )
+ )
);
// Pipe links
diff --git a/includes/search/SearchEngine.php b/includes/search/SearchEngine.php
index 40b992de..2f7dfd7e 100644
--- a/includes/search/SearchEngine.php
+++ b/includes/search/SearchEngine.php
@@ -67,6 +67,7 @@ class SearchEngine {
* @deprecated since 1.18 Call supports( 'list-redirects' );
*/
function acceptListRedirects() {
+ wfDeprecated( __METHOD__, '1.18' );
return $this->supports( 'list-redirects' );
}
@@ -148,7 +149,7 @@ class SearchEngine {
* Really find the title match.
*/
private static function getNearMatchInternal( $searchterm ) {
- global $wgContLang;
+ global $wgContLang, $wgEnableSearchContributorsByIP;
$allSearchTerms = array( $searchterm );
@@ -161,8 +162,6 @@ class SearchEngine {
return $titleResult;
}
- $context = new RequestContext;
-
foreach ( $allSearchTerms as $term ) {
# Exact match? No need to look further.
@@ -171,14 +170,13 @@ class SearchEngine {
return null;
}
- if ( $title->getNamespace() == NS_SPECIAL || $title->isExternal() || $title->exists() ) {
+ if ( $title->isSpecialPage() || $title->isExternal() || $title->exists() ) {
return $title;
}
# See if it still otherwise has content is some sane sense
- $context->setTitle( $title );
- $article = Article::newFromTitle( $title, $context );
- if ( $article->hasViewableContent() ) {
+ $page = WikiPage::factory( $title );
+ if ( $page->hasViewableContent() ) {
return $title;
}
@@ -218,10 +216,13 @@ class SearchEngine {
$title = Title::newFromText( $searchterm );
+
# Entering an IP address goes to the contributions page
- if ( ( $title->getNamespace() == NS_USER && User::isIP( $title->getText() ) )
- || User::isIP( trim( $searchterm ) ) ) {
- return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() );
+ if ( $wgEnableSearchContributorsByIP ) {
+ if ( ( $title->getNamespace() == NS_USER && User::isIP( $title->getText() ) )
+ || User::isIP( trim( $searchterm ) ) ) {
+ return SpecialPage::getTitleFor( 'Contributions', $title->getDBkey() );
+ }
}
@@ -343,20 +344,22 @@ class SearchEngine {
public static function userNamespaces( $user ) {
global $wgSearchEverythingOnlyLoggedIn;
- // get search everything preference, that can be set to be read for logged-in users
- $searcheverything = false;
- if ( ( $wgSearchEverythingOnlyLoggedIn && $user->isLoggedIn() )
- || !$wgSearchEverythingOnlyLoggedIn )
- $searcheverything = $user->getOption( 'searcheverything' );
-
- // searcheverything overrides other options
- if ( $searcheverything )
- return array_keys( SearchEngine::searchableNamespaces() );
-
- $arr = Preferences::loadOldSearchNs( $user );
$searchableNamespaces = SearchEngine::searchableNamespaces();
- $arr = array_intersect( $arr, array_keys( $searchableNamespaces ) ); // Filter
+ // get search everything preference, that can be set to be read for logged-in users
+ // it overrides other options
+ if ( !$wgSearchEverythingOnlyLoggedIn || $user->isLoggedIn() ) {
+ if ( $user->getOption( 'searcheverything' ) ) {
+ return array_keys( $searchableNamespaces );
+ }
+ }
+
+ $arr = array();
+ foreach ( $searchableNamespaces as $ns => $name ) {
+ if ( $user->getOption( 'searchNs' . $ns ) ) {
+ $arr[] = $ns;
+ }
+ }
return $arr;
}
@@ -1098,7 +1101,7 @@ class SearchHighlighter {
} else {
// if begin of the article contains the whole phrase, show only that !!
if ( array_key_exists( $first, $snippets ) && preg_match( $pat1, $snippets[$first] )
- && $offsets[$first] < $contextchars * 2 ) {
+ && $offsets[$first] < $contextchars * 2 ) {
$snippets = array ( $first => $snippets[$first] );
}
@@ -1119,10 +1122,10 @@ class SearchHighlighter {
// add more lines
$add = $index + 1;
while ( $len < $targetchars - 20
- && array_key_exists( $add, $all )
- && !array_key_exists( $add, $snippets ) ) {
- $offsets[$add] = 0;
- $tt = "\n" . $this->extract( $all[$add], 0, $targetchars - $len, $offsets[$add] );
+ && array_key_exists( $add, $all )
+ && !array_key_exists( $add, $snippets ) ) {
+ $offsets[$add] = 0;
+ $tt = "\n" . $this->extract( $all[$add], 0, $targetchars - $len, $offsets[$add] );
$extended[$add] = $tt;
$len += strlen( $tt );
$add++;
@@ -1152,7 +1155,7 @@ class SearchHighlighter {
if ( ! isset( $processed[$term] ) ) {
$pat3 = "/$patPre(" . $term . ")$patPost/ui"; // highlight word
$extract = preg_replace( $pat3,
- "\\1<span class='searchmatch'>\\2</span>\\3", $extract );
+ "\\1<span class='searchmatch'>\\2</span>\\3", $extract );
$processed[$term] = true;
}
}
@@ -1187,8 +1190,9 @@ class SearchHighlighter {
global $wgContLang;
if ( strlen( $matches[0] ) > 1 ) {
return '[' . $wgContLang->lc( $matches[0] ) . $wgContLang->uc( $matches[0] ) . ']';
- } else
+ } else {
return $matches[0];
+ }
}
/**
@@ -1202,22 +1206,27 @@ class SearchHighlighter {
* @return String
*/
function extract( $text, $start, $end, &$posStart = null, &$posEnd = null ) {
- if ( $start != 0 )
+ if ( $start != 0 ) {
$start = $this->position( $text, $start, 1 );
- if ( $end >= strlen( $text ) )
+ }
+ if ( $end >= strlen( $text ) ) {
$end = strlen( $text );
- else
+ } else {
$end = $this->position( $text, $end );
+ }
- if ( !is_null( $posStart ) )
+ if ( !is_null( $posStart ) ) {
$posStart = $start;
- if ( !is_null( $posEnd ) )
+ }
+ if ( !is_null( $posEnd ) ) {
$posEnd = $end;
+ }
- if ( $end > $start )
+ if ( $end > $start ) {
return substr( $text, $start, $end - $start );
- else
+ } else {
return '';
+ }
}
/**
@@ -1342,61 +1351,61 @@ class SearchHighlighter {
}
/**
- * Simple & fast snippet extraction, but gives completely unrelevant
- * snippets
- *
- * @param $text String
- * @param $terms Array
- * @param $contextlines Integer
- * @param $contextchars Integer
- * @return String
- */
- public function highlightSimple( $text, $terms, $contextlines, $contextchars ) {
- global $wgContLang;
- $fname = __METHOD__;
-
- $lines = explode( "\n", $text );
-
- $terms = implode( '|', $terms );
- $max = intval( $contextchars ) + 1;
- $pat1 = "/(.*)($terms)(.{0,$max})/i";
-
- $lineno = 0;
-
- $extract = "";
- wfProfileIn( "$fname-extract" );
- foreach ( $lines as $line ) {
- if ( 0 == $contextlines ) {
- break;
- }
- ++$lineno;
- $m = array();
- if ( ! preg_match( $pat1, $line, $m ) ) {
- continue;
- }
- --$contextlines;
- // truncate function changes ... to relevant i18n message.
- $pre = $wgContLang->truncate( $m[1], - $contextchars, '...', false );
-
- if ( count( $m ) < 3 ) {
- $post = '';
- } else {
- $post = $wgContLang->truncate( $m[3], $contextchars, '...', false );
- }
-
- $found = $m[2];
-
- $line = htmlspecialchars( $pre . $found . $post );
- $pat2 = '/(' . $terms . ")/i";
- $line = preg_replace( $pat2,
- "<span class='searchmatch'>\\1</span>", $line );
-
- $extract .= "${line}\n";
- }
- wfProfileOut( "$fname-extract" );
-
- return $extract;
- }
+ * Simple & fast snippet extraction, but gives completely unrelevant
+ * snippets
+ *
+ * @param $text String
+ * @param $terms Array
+ * @param $contextlines Integer
+ * @param $contextchars Integer
+ * @return String
+ */
+ public function highlightSimple( $text, $terms, $contextlines, $contextchars ) {
+ global $wgContLang;
+ $fname = __METHOD__;
+
+ $lines = explode( "\n", $text );
+
+ $terms = implode( '|', $terms );
+ $max = intval( $contextchars ) + 1;
+ $pat1 = "/(.*)($terms)(.{0,$max})/i";
+
+ $lineno = 0;
+
+ $extract = "";
+ wfProfileIn( "$fname-extract" );
+ foreach ( $lines as $line ) {
+ if ( 0 == $contextlines ) {
+ break;
+ }
+ ++$lineno;
+ $m = array();
+ if ( ! preg_match( $pat1, $line, $m ) ) {
+ continue;
+ }
+ --$contextlines;
+ // truncate function changes ... to relevant i18n message.
+ $pre = $wgContLang->truncate( $m[1], - $contextchars, '...', false );
+
+ if ( count( $m ) < 3 ) {
+ $post = '';
+ } else {
+ $post = $wgContLang->truncate( $m[3], $contextchars, '...', false );
+ }
+
+ $found = $m[2];
+
+ $line = htmlspecialchars( $pre . $found . $post );
+ $pat2 = '/(' . $terms . ")/i";
+ $line = preg_replace( $pat2,
+ "<span class='searchmatch'>\\1</span>", $line );
+
+ $extract .= "${line}\n";
+ }
+ wfProfileOut( "$fname-extract" );
+
+ return $extract;
+ }
}
diff --git a/includes/search/SearchMySQL.php b/includes/search/SearchMySQL.php
index c52c9e5b..af8f3875 100644
--- a/includes/search/SearchMySQL.php
+++ b/includes/search/SearchMySQL.php
@@ -45,7 +45,7 @@ class SearchMySQL extends SearchEngine {
* become part of a WHERE clause
*
* @param $filteredText string
- * @param $fullText string
+ * @param $fulltext string
*
* @return string
*/
@@ -290,6 +290,7 @@ class SearchMySQL extends SearchEngine {
/**
* Get the base part of the search query.
*
+ * @param &$query Search query array
* @param $filteredTerm String
* @param $fulltext Boolean
* @since 1.18 (changed)
diff --git a/includes/search/SearchOracle.php b/includes/search/SearchOracle.php
index 85337ca1..2d6fc3e2 100644
--- a/includes/search/SearchOracle.php
+++ b/includes/search/SearchOracle.php
@@ -257,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() . '.' . $dbw->tableName( 'si_text_idx', false ) ) . ")" );
+ $dbw->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_text_idx', 'raw' ) ) . ")" );
$dbw->query( "CALL ctx_ddl.sync_index(" .
- $dbw->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_title_idx', false ) ) . ")" );
+ $dbw->addQuotes( $dbw->getDBname() . '.' . $dbw->tableName( 'si_title_idx', 'raw' ) ) . ")" );
}
/**
diff --git a/includes/search/SearchUpdate.php b/includes/search/SearchUpdate.php
index f79092cb..a162d2b3 100644
--- a/includes/search/SearchUpdate.php
+++ b/includes/search/SearchUpdate.php
@@ -13,7 +13,7 @@
*
* @ingroup Search
*/
-class SearchUpdate {
+class SearchUpdate implements DeferrableUpdate {
private $mId = 0, $mNamespace, $mTitle, $mText;
private $mTitleWords;
@@ -37,7 +37,7 @@ class SearchUpdate {
global $wgContLang, $wgDisableSearchUpdate;
if( $wgDisableSearchUpdate || !$this->mId ) {
- return false;
+ return;
}
wfProfileIn( __METHOD__ );
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
index e4bf42d3..617a8026 100644
--- a/includes/specials/SpecialActiveusers.php
+++ b/includes/specials/SpecialActiveusers.php
@@ -42,10 +42,18 @@ class ActiveUsersPager extends UsersPager {
*/
protected $groups;
- function __construct( $group = null ) {
- global $wgRequest, $wgActiveUserDays;
+ /**
+ * @param $context IContextSource
+ * @param $group null Unused
+ * @param $par string Parameter passed to the page
+ */
+ function __construct( IContextSource $context = null, $group = null, $par = null ) {
+ global $wgActiveUserDays;
+
+ parent::__construct( $context );
+
$this->RCMaxAge = $wgActiveUserDays;
- $un = $wgRequest->getText( 'username' );
+ $un = $this->getRequest()->getText( 'username', $par );
$this->requestedUser = '';
if ( $un != '' ) {
$username = Title::makeTitleSafe( NS_USER, $un );
@@ -55,23 +63,15 @@ class ActiveUsersPager extends UsersPager {
}
$this->setupOptions();
-
- parent::__construct();
- }
-
- function getTitle() {
- return SpecialPage::getTitleFor( 'Activeusers' );
}
public function setupOptions() {
- global $wgRequest;
-
$this->opts = new FormOptions();
$this->opts->add( 'hidebots', false, FormOptions::BOOL );
$this->opts->add( 'hidesysops', false, FormOptions::BOOL );
- $this->opts->fetchValuesFromRequest( $wgRequest );
+ $this->opts->fetchValuesFromRequest( $this->getRequest() );
$this->groups = array();
if ( $this->opts->getValue( 'hidebots' ) == 1 ) {
@@ -119,29 +119,26 @@ class ActiveUsersPager extends UsersPager {
}
function formatRow( $row ) {
- global $wgLang;
$userName = $row->user_name;
- $ulinks = $this->getSkin()->userLink( $row->user_id, $userName );
- $ulinks .= $this->getSkin()->userToolLinks( $row->user_id, $userName );
+ $ulinks = Linker::userLink( $row->user_id, $userName );
+ $ulinks .= Linker::userToolLinks( $row->user_id, $userName );
+
+ $lang = $this->getLanguage();
$list = array();
foreach( self::getGroups( $row->user_id ) as $group ) {
if ( isset( $this->groups[$group] ) ) {
return;
}
- $list[] = self::buildGroupLink( $group );
+ $list[] = self::buildGroupLink( $group, $userName );
}
- $groups = $wgLang->commaList( $list );
-
- $item = wfSpecialList( $ulinks, $groups );
- $count = wfMsgExt( 'activeusers-count',
- array( 'parsemag' ),
- $wgLang->formatNum( $row->recentedits ),
- $userName,
- $wgLang->formatNum ( $this->RCMaxAge )
- );
- $blocked = $row->blocked ? ' ' . wfMsgExt( 'listusers-blocked', array( 'parsemag' ), $userName ) : '';
+ $groups = $lang->commaList( $list );
+
+ $item = $lang->specialList( $ulinks, $groups );
+ $count = $this->msg( 'activeusers-count' )->numParams( $row->recentedits )
+ ->params( $userName )->numParams( $this->RCMaxAge )->escaped();
+ $blocked = $row->blocked ? ' ' . $this->msg( 'listusers-blocked', $userName )->escaped() : '';
return Html::rawElement( 'li', array(), "{$item} [{$count}]{$blocked}" );
}
@@ -153,16 +150,19 @@ class ActiveUsersPager extends UsersPager {
$limit = $this->mLimit ? Html::hidden( 'limit', $this->mLimit ) : '';
$out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); # Form tag
- $out .= Xml::fieldset( wfMsg( 'activeusers' ) ) . "\n";
+ $out .= Xml::fieldset( $this->msg( 'activeusers' )->text() ) . "\n";
$out .= Html::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n";
- $out .= Xml::inputLabel( wfMsg( 'activeusers-from' ), 'username', 'offset', 20, $this->requestedUser ) . '<br />';# Username field
+ $out .= Xml::inputLabel( $this->msg( 'activeusers-from' )->text(),
+ 'username', 'offset', 20, $this->requestedUser ) . '<br />';# Username field
- $out .= Xml::checkLabel( wfMsg('activeusers-hidebots'), 'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ) );
+ $out .= Xml::checkLabel( $this->msg( 'activeusers-hidebots' )->text(),
+ 'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ) );
- $out .= Xml::checkLabel( wfMsg('activeusers-hidesysops'), 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ) ) . '<br />';
+ $out .= Xml::checkLabel( $this->msg( 'activeusers-hidesysops' )->text(),
+ 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ) ) . '<br />';
- $out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n";# Submit button and form bottom
+ $out .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . "\n";# Submit button and form bottom
$out .= Xml::closeElement( 'fieldset' );
$out .= Xml::closeElement( 'form' );
@@ -188,30 +188,30 @@ class SpecialActiveUsers extends SpecialPage {
* @param $par Mixed: parameter passed to the page or null
*/
public function execute( $par ) {
- global $wgOut, $wgLang, $wgActiveUserDays;
+ global $wgActiveUserDays;
$this->setHeaders();
$this->outputHeader();
- $up = new ActiveUsersPager();
+ $out = $this->getOutput();
+ $out->wrapWikiMsg( "<div class='mw-activeusers-intro'>\n$1\n</div>",
+ array( 'activeusers-intro', $this->getLanguage()->formatNum( $wgActiveUserDays ) ) );
+
+ $up = new ActiveUsersPager( $this->getContext(), null, $par );
# getBody() first to check, if empty
$usersbody = $up->getBody();
- $s = Html::rawElement( 'div', array( 'class' => 'mw-activeusers-intro' ),
- wfMsgExt( 'activeusers-intro', array( 'parsemag', 'escape' ), $wgLang->formatNum( $wgActiveUserDays ) )
- );
-
- $s .= $up->getPageHeader();
- if( $usersbody ) {
- $s .= $up->getNavigationBar();
- $s .= Html::rawElement( 'ul', array(), $usersbody );
- $s .= $up->getNavigationBar();
+ $out->addHTML( $up->getPageHeader() );
+ if ( $usersbody ) {
+ $out->addHTML(
+ $up->getNavigationBar() .
+ Html::rawElement( 'ul', array(), $usersbody ) .
+ $up->getNavigationBar()
+ );
} else {
- $s .= Html::element( 'p', array(), wfMsg( 'activeusers-noresult' ) );
+ $out->addWikiMsg( 'activeusers-noresult' );
}
-
- $wgOut->addHTML( $s );
}
}
diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php
index 24815825..2bfea4c3 100644
--- a/includes/specials/SpecialAllmessages.php
+++ b/includes/specials/SpecialAllmessages.php
@@ -100,22 +100,22 @@ class AllmessagesTablePager extends TablePager {
public $custom;
function __construct( $page, $conds, $langObj = null ) {
- parent::__construct();
+ parent::__construct( $page->getContext() );
$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;
+ global $wgContLang;
- $this->talk = htmlspecialchars( wfMsg( 'talkpagelinktext' ) );
+ $this->talk = $this->msg( 'talkpagelinktext' )->escaped();
$this->lang = ( $langObj ? $langObj : $wgContLang );
$this->langcode = $this->lang->getCode();
$this->foreign = $this->langcode != $wgContLang->getCode();
- $request = $wgRequest;
+ $request = $this->getRequest();
$this->filter = $request->getVal( 'filter', 'all' );
if( $this->filter === 'all' ){
@@ -124,8 +124,8 @@ class AllmessagesTablePager extends TablePager {
$this->custom = ($this->filter == 'unmodified');
}
- $prefix = $wgLang->ucfirst( $wgRequest->getVal( 'prefix', '' ) );
- $prefix = $prefix != '' ? Title::makeTitleSafe( NS_MEDIAWIKI, $wgRequest->getVal( 'prefix', null ) ) : null;
+ $prefix = $this->getLanguage()->ucfirst( $request->getVal( 'prefix', '' ) );
+ $prefix = $prefix != '' ? Title::makeTitleSafe( NS_MEDIAWIKI, $request->getVal( 'prefix', null ) ) : null;
if( $prefix !== null ){
$this->displayPrefix = $prefix->getDBkey();
$this->prefix = '/^' . preg_quote( $this->displayPrefix ) . '/i';
@@ -150,12 +150,12 @@ class AllmessagesTablePager extends TablePager {
ksort( $languages );
$out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-allmessages-form' ) ) .
- Xml::fieldset( wfMsg( 'allmessages-filter-legend' ) ) .
+ Xml::fieldset( $this->msg( 'allmessages-filter-legend' )->text() ) .
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::openElement( 'table', array( 'class' => 'mw-allmessages-table' ) ) . "\n" .
'<tr>
<td class="mw-label">' .
- Xml::label( wfMsg( 'allmessages-prefix' ), 'mw-allmessages-form-prefix' ) .
+ Xml::label( $this->msg( 'allmessages-prefix' )->text(), 'mw-allmessages-form-prefix' ) .
"</td>\n
<td class=\"mw-input\">" .
Xml::input( 'prefix', 20, str_replace( '_', ' ', $this->displayPrefix ), array( 'id' => 'mw-allmessages-form-prefix' ) ) .
@@ -163,22 +163,22 @@ class AllmessagesTablePager extends TablePager {
</tr>
<tr>\n
<td class='mw-label'>" .
- wfMsg( 'allmessages-filter' ) .
+ $this->msg( 'allmessages-filter' )->escaped() .
"</td>\n
<td class='mw-input'>" .
- Xml::radioLabel( wfMsg( 'allmessages-filter-unmodified' ),
+ Xml::radioLabel( $this->msg( 'allmessages-filter-unmodified' )->text(),
'filter',
'unmodified',
'mw-allmessages-form-filter-unmodified',
( $this->filter == 'unmodified' )
) .
- Xml::radioLabel( wfMsg( 'allmessages-filter-all' ),
+ Xml::radioLabel( $this->msg( 'allmessages-filter-all' )->text(),
'filter',
'all',
'mw-allmessages-form-filter-all',
( $this->filter == 'all' )
) .
- Xml::radioLabel( wfMsg( 'allmessages-filter-modified' ),
+ Xml::radioLabel( $this->msg( 'allmessages-filter-modified' )->text(),
'filter',
'modified',
'mw-allmessages-form-filter-modified',
@@ -188,7 +188,7 @@ class AllmessagesTablePager extends TablePager {
</tr>
<tr>\n
<td class=\"mw-label\">" .
- Xml::label( wfMsg( 'allmessages-language' ), 'mw-allmessages-form-lang' ) .
+ Xml::label( $this->msg( 'allmessages-language' )->text(), 'mw-allmessages-form-lang' ) .
"</td>\n
<td class=\"mw-input\">" .
Xml::openElement( 'select', array( 'id' => 'mw-allmessages-form-lang', 'name' => 'lang' ) );
@@ -203,7 +203,7 @@ class AllmessagesTablePager extends TablePager {
'<tr>
<td class="mw-label">' .
- Xml::label( wfMsg( 'table_pager_limit_label'), 'mw-table_pager_limit_label' ) .
+ Xml::label( $this->msg( 'table_pager_limit_label' )->text(), 'mw-table_pager_limit_label' ) .
'</td>
<td class="mw-input">' .
$this->getLimitSelect() .
@@ -211,7 +211,7 @@ class AllmessagesTablePager extends TablePager {
<tr>
<td></td>
<td>' .
- Xml::submitButton( wfMsg( 'allmessages-filter-submit' ) ) .
+ Xml::submitButton( $this->msg( 'allmessages-filter-submit' )->text() ) .
"</td>\n
</tr>" .
@@ -247,8 +247,10 @@ class AllmessagesTablePager extends TablePager {
* @param array $messageNames
* @param string $langcode What language code
* @param bool $foreign Whether the $langcode is not the content language
+ * @return array: a 'pages' and 'talks' array with the keys of existing pages
*/
public static function getCustomisedStatuses( $messageNames, $langcode = 'en', $foreign = false ) {
+ // FIXME: This function should be moved to Language:: or something.
wfProfileIn( __METHOD__ . '-db' );
$dbr = wfGetDB( DB_SLAVE );
@@ -263,18 +265,20 @@ class AllmessagesTablePager extends TablePager {
$pageFlags = $talkFlags = array();
foreach ( $res as $s ) {
- if( $s->page_namespace == NS_MEDIAWIKI ) {
- if( $foreign ) {
- $title = explode( '/', $s->page_title );
- 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;
+ $exists = false;
+ if( $foreign ) {
+ $title = explode( '/', $s->page_title );
+ if( count( $title ) === 2 && $langcode == $title[1]
+ && isset( $xNames[$title[0]] ) ) {
+ $exists = $title[0];
}
- } elseif( $s->page_namespace == NS_MEDIAWIKI_TALK ){
- $talkFlags[$s->page_title] = true;
+ } elseif( isset( $xNames[$s->page_title] ) ) {
+ $exists = $s->page_title;
+ }
+ if( $exists && $s->page_namespace == NS_MEDIAWIKI ) {
+ $pageFlags[$exists] = true;
+ } elseif( $exists && $s->page_namespace == NS_MEDIAWIKI_TALK ) {
+ $talkFlags[$exists] = true;
}
}
@@ -319,24 +323,23 @@ class AllmessagesTablePager extends TablePager {
}
function getStartBody() {
- return Xml::openElement( 'table', array( 'class' => 'TablePager', 'id' => 'mw-allmessagestable' ) ) . "\n" .
+ return Xml::openElement( 'table', array( 'class' => 'mw-datatable TablePager', 'id' => 'mw-allmessagestable' ) ) . "\n" .
"<thead><tr>
<th rowspan=\"2\">" .
- wfMsg( 'allmessagesname' ) . "
+ $this->msg( 'allmessagesname' )->escaped() . "
</th>
<th>" .
- wfMsg( 'allmessagesdefault' ) .
+ $this->msg( 'allmessagesdefault' )->escaped() .
"</th>
</tr>\n
<tr>
<th>" .
- wfMsg( 'allmessagescurrent' ) .
+ $this->msg( 'allmessagescurrent' )->escaped() .
"</th>
</tr></thead><tbody>\n";
}
function formatValue( $field, $value ){
- global $wgLang;
switch( $field ){
case 'am_title' :
@@ -345,11 +348,11 @@ class AllmessagesTablePager extends TablePager {
$talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
if( $this->mCurrentRow->am_customised ){
- $title = Linker::linkKnown( $title, $wgLang->lcfirst( $value ) );
+ $title = Linker::linkKnown( $title, $this->getLanguage()->lcfirst( $value ) );
} else {
$title = Linker::link(
$title,
- $wgLang->lcfirst( $value ),
+ $this->getLanguage()->lcfirst( $value ),
array(),
array(),
array( 'broken' )
@@ -394,12 +397,11 @@ class AllmessagesTablePager extends TablePager {
function getRowAttrs( $row, $isSecond = false ){
$arr = array();
- global $wgLang;
if( $row->am_customised ){
$arr['class'] = 'allmessages-customised';
}
if( !$isSecond ){
- $arr['id'] = Sanitizer::escapeId( 'msg_' . $wgLang->lcfirst( $row->am_title ) );
+ $arr['id'] = Sanitizer::escapeId( 'msg_' . $this->getLanguage()->lcfirst( $row->am_title ) );
}
return $arr;
}
@@ -407,7 +409,7 @@ class AllmessagesTablePager extends TablePager {
function getCellAttrs( $field, $value ){
if( $this->mCurrentRow->am_customised && $field == 'am_title' ){
return array( 'rowspan' => '2', 'class' => $field );
- } else if( $field == 'am_title' ) {
+ } elseif( $field == 'am_title' ) {
return array( 'class' => $field );
} else {
return array( 'lang' => $this->langcode, 'dir' => $this->lang->getDir(), 'class' => $field );
@@ -417,8 +419,8 @@ class AllmessagesTablePager extends TablePager {
// This is not actually used, as getStartBody is overridden above
function getFieldNames() {
return array(
- 'am_title' => wfMsg( 'allmessagesname' ),
- 'am_default' => wfMsg( 'allmessagesdefault' )
+ 'am_title' => $this->msg( 'allmessagesname' )->text(),
+ 'am_default' => $this->msg( 'allmessagesdefault' )->text()
);
}
diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php
index a9cbf3ab..960a327a 100644
--- a/includes/specials/SpecialAllpages.php
+++ b/includes/specials/SpecialAllpages.php
@@ -73,16 +73,16 @@ class SpecialAllpages extends IncludableSpecialPage {
$namespaces = $wgContLang->getNamespaces();
- $out->setPagetitle(
+ $out->setPageTitle(
( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ?
- wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
- wfMsg( 'allarticles' )
+ $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
+ $this->msg( 'allarticles' )
);
$out->addModuleStyles( 'mediawiki.special' );
- if( isset($par) ) {
+ if( $par !== null ) {
$this->showChunk( $namespace, $par, $to );
- } elseif( isset($from) && !isset($to) ) {
+ } elseif( $from !== null && $to === null ) {
$this->showChunk( $namespace, $from, $to );
} else {
$this->showToplevel( $namespace, $from, $to );
@@ -104,11 +104,11 @@ class SpecialAllpages extends IncludableSpecialPage {
$out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
$out .= Html::hidden( 'title', $t->getPrefixedText() );
$out .= Xml::openElement( 'fieldset' );
- $out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) );
+ $out .= Xml::element( 'legend', null, $this->msg( 'allpages' )->text() );
$out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
$out .= "<tr>
<td class='mw-label'>" .
- Xml::label( wfMsg( 'allpagesfrom' ), 'nsfrom' ) .
+ Xml::label( $this->msg( 'allpagesfrom' )->text(), 'nsfrom' ) .
" </td>
<td class='mw-input'>" .
Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) .
@@ -116,7 +116,7 @@ class SpecialAllpages extends IncludableSpecialPage {
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( wfMsg( 'allpagesto' ), 'nsto' ) .
+ Xml::label( $this->msg( 'allpagesto' )->text(), 'nsto' ) .
" </td>
<td class='mw-input'>" .
Xml::input( 'to', 30, str_replace('_',' ',$to), array( 'id' => 'nsto' ) ) .
@@ -124,11 +124,14 @@ class SpecialAllpages extends IncludableSpecialPage {
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( wfMsg( 'namespace' ), 'namespace' ) .
+ Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) .
" </td>
<td class='mw-input'>" .
- Xml::namespaceSelector( $namespace, null ) . ' ' .
- Xml::submitButton( wfMsg( 'allpagessubmit' ) ) .
+ Html::namespaceSelector(
+ array( 'selected' => $namespace ),
+ array( 'name' => 'namespace', 'id' => 'namespace' )
+ ) . ' ' .
+ Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) .
" </td>
</tr>";
$out .= Xml::closeElement( 'table' );
@@ -249,7 +252,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$nsForm .
'</td>
<td class="mw-allpages-nav">' .
- $this->getSkin()->link( $this->getTitle(), wfMsgHtml ( 'allpages' ),
+ Linker::link( $this->getTitle(), $this->msg( 'allpages' )->escaped(),
array(), array(), 'known' ) .
"</td>
</tr>" .
@@ -278,12 +281,12 @@ class SpecialAllpages extends IncludableSpecialPage {
$queryparams = $namespace ? "namespace=$namespace&" : '';
$special = $this->getTitle();
- $link = $special->escapeLocalUrl( $queryparams . 'from=' . urlencode($inpoint) . '&to=' . urlencode($outpoint) );
+ $link = htmlspecialchars( $special->getLocalUrl( $queryparams . 'from=' . urlencode($inpoint) . '&to=' . urlencode($outpoint) ) );
- $out = wfMsgHtml( 'alphaindexline',
+ $out = $this->msg( 'alphaindexline' )->rawParams(
"<a href=\"$link\">$inpointf</a></td><td>",
"</td><td><a href=\"$link\">$outpointf</a>"
- );
+ )->escaped();
return '<tr><td class="mw-allpages-alphaindexline">' . $out . '</td></tr>';
}
@@ -293,9 +296,8 @@ 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 $wgContLang, $wgLang;
+ global $wgContLang;
$output = $this->getOutput();
- $sk = $this->getSkin();
$fromList = $this->getNamespaceKeyAndText($namespace, $from);
$toList = $this->getNamespaceKeyAndText( $namespace, $to );
@@ -303,10 +305,10 @@ class SpecialAllpages extends IncludableSpecialPage {
$n = 0;
if ( !$fromList || !$toList ) {
- $out = wfMsgExt( 'allpagesbadtitle', 'parse' );
+ $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
} elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
// Show errormessage and reset to NS_MAIN
- $out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace );
+ $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
$namespace = NS_MAIN;
} else {
list( $namespace, $fromKey, $from ) = $fromList;
@@ -338,7 +340,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$t = Title::newFromRow( $s );
if( $t ) {
$link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
- $sk->link( $t ) .
+ Linker::link( $t ) .
($s->page_is_redirect ? '</div>' : '' );
} else {
$link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
@@ -411,7 +413,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$nsForm .
'</td>
<td class="mw-allpages-nav">' .
- $sk->link( $self, wfMsgHtml ( 'allpages' ) );
+ Linker::link( $self, $this->msg( 'allpages' )->escaped() );
# Do we put a previous link ?
if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) {
@@ -420,13 +422,13 @@ class SpecialAllpages extends IncludableSpecialPage {
if( $namespace )
$query['namespace'] = $namespace;
- $prevLink = $sk->linkKnown(
+ $prevLink = Linker::linkKnown(
$self,
- wfMessage( 'prevpage', $pt )->escaped(),
+ $this->msg( 'prevpage', $pt )->escaped(),
array(),
$query
);
- $out2 = $wgLang->pipeList( array( $out2, $prevLink ) );
+ $out2 = $this->getLanguage()->pipeList( array( $out2, $prevLink ) );
}
if( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
@@ -437,13 +439,13 @@ class SpecialAllpages extends IncludableSpecialPage {
if( $namespace )
$query['namespace'] = $namespace;
- $nextLink = $sk->linkKnown(
+ $nextLink = Linker::linkKnown(
$self,
- wfMessage( 'nextpage', $t->getText() )->escaped(),
+ $this->msg( 'nextpage', $t->getText() )->escaped(),
array(),
$query
);
- $out2 = $wgLang->pipeList( array( $out2, $nextLink ) );
+ $out2 = $this->getLanguage()->pipeList( array( $out2, $nextLink ) );
}
$out2 .= "</td></tr></table>";
}
@@ -458,7 +460,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$output->addHTML(
Html::element( 'hr' ) .
Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ),
- $wgLang->pipeList( $links )
+ $this->getLanguage()->pipeList( $links )
) );
}
diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php
index cbb5df80..1203e1fd 100644
--- a/includes/specials/SpecialAncientpages.php
+++ b/includes/specials/SpecialAncientpages.php
@@ -59,14 +59,14 @@ class AncientPagesPage extends QueryPage {
}
function formatResult( $skin, $result ) {
- global $wgLang, $wgContLang;
+ global $wgContLang;
- $d = $wgLang->timeanddate( wfTimestamp( TS_MW, $result->value ), true );
+ $d = $this->getLanguage()->userTimeAndDate( $result->value, $this->getUser() );
$title = Title::makeTitle( $result->namespace, $result->title );
- $link = $skin->linkKnown(
+ $link = Linker::linkKnown(
$title,
htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) )
);
- return wfSpecialList( $link, htmlspecialchars($d) );
+ return $this->getLanguage()->specialList( $link, htmlspecialchars( $d ) );
}
}
diff --git a/includes/specials/SpecialBlock.php b/includes/specials/SpecialBlock.php
index f1fe8386..7d93cc75 100644
--- a/includes/specials/SpecialBlock.php
+++ b/includes/specials/SpecialBlock.php
@@ -27,8 +27,7 @@
*
* @ingroup SpecialPage
*/
-class SpecialBlock extends SpecialPage {
-
+class SpecialBlock extends FormSpecialPage {
/** The maximum number of edits a user can have and still be hidden
* TODO: config setting? */
const HIDEUSER_CONTRIBLIMIT = 1000;
@@ -56,65 +55,66 @@ class SpecialBlock extends SpecialPage {
parent::__construct( 'Block', 'block' );
}
- public function execute( $par ) {
- global $wgUser, $wgOut, $wgRequest;
-
- # Permission check
- if( !$this->userCanExecute( $wgUser ) ) {
- $this->displayRestrictionError();
- return;
- }
+ /**
+ * Checks that the user can unblock themselves if they are trying to do so
+ *
+ * @param User $user
+ * @throws ErrorPageError
+ */
+ protected function checkExecutePermissions( User $user ) {
+ parent::checkExecutePermissions( $user );
- # Can't block when the database is locked
- if( wfReadOnly() ) {
- throw new ReadOnlyError;
+ # bug 15810: blocked admins should have limited access here
+ $status = self::checkUnblockSelf( $this->target, $user );
+ if ( $status !== true ) {
+ throw new ErrorPageError( 'badaccess', $status );
}
+ }
+ /**
+ * Handle some magic here
+ *
+ * @param $par String
+ */
+ protected function setParameter( $par ) {
# 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 );
+ $request = $this->getRequest();
+ list( $this->target, $this->type ) = self::getTargetAndType( $par, $request );
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' ) );
+ list( $this->previousTarget, /*...*/ ) = Block::parseTarget( $request->getVal( 'wpPreviousTarget' ) );
+ $this->requestedHideUser = $request->getBool( 'wpHideUser' );
+ }
- $t = $this->alreadyBlocked
- ? wfMsg( 'ipb-change-block' )
- : wfMsg( 'ipbsubmit' );
- $form->setSubmitText( $t );
+ /**
+ * Customizes the HTMLForm a bit
+ *
+ * @param $form HTMLForm
+ */
+ protected function alterForm( HTMLForm $form ) {
+ $form->setWrapperLegendMsg( 'blockip-legend' );
+ $form->setHeaderText( '' );
+ $form->setSubmitCallback( array( __CLASS__, 'processUIForm' ) );
- $this->doPreText( $form );
- $this->doHeadertext( $form );
- $this->doPostText( $form );
+ $msg = $this->alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit';
+ $form->setSubmitTextMsg( $msg );
- if( $form->show() ){
- $wgOut->setPageTitle( wfMsg( 'blockipsuccesssub' ) );
- $wgOut->addWikiMsg( 'blockipsuccesstext', $this->target );
+ # Don't need to do anything if the form has been posted
+ if( !$this->getRequest()->wasPosted() && $this->preErrors ){
+ $s = HTMLForm::formatErrors( $this->preErrors );
+ if( $s ){
+ $form->addHeaderText( Html::rawElement(
+ 'div',
+ array( 'class' => 'error' ),
+ $s
+ ) );
+ }
}
}
@@ -122,8 +122,10 @@ class SpecialBlock extends SpecialPage {
* Get the HTMLForm descriptor array for the block form
* @return Array
*/
- protected static function getFormFields(){
- global $wgUser, $wgBlockAllowsUTEdit;
+ protected function getFormFields(){
+ global $wgBlockAllowsUTEdit;
+
+ $user = $this->getUser();
$a = array(
'Target' => array(
@@ -141,7 +143,7 @@ class SpecialBlock extends SpecialPage {
'required' => true,
'tabindex' => '2',
'options' => self::getSuggestedDurations(),
- 'other' => wfMsg( 'ipbother' ),
+ 'other' => $this->msg( 'ipbother' )->text(),
),
'Reason' => array(
'type' => 'selectandother',
@@ -155,7 +157,7 @@ class SpecialBlock extends SpecialPage {
),
);
- if( self::canBlockEmail( $wgUser ) ) {
+ if( self::canBlockEmail( $user ) ) {
$a['DisableEmail'] = array(
'type' => 'check',
'label-message' => 'ipbemailban',
@@ -177,7 +179,7 @@ class SpecialBlock extends SpecialPage {
);
# Allow some users to hide name from block log, blocklist and listusers
- if( $wgUser->isAllowed( 'hideuser' ) ) {
+ if( $user->isAllowed( 'hideuser' ) ) {
$a['HideUser'] = array(
'type' => 'check',
'label-message' => 'ipbhidename',
@@ -186,7 +188,7 @@ class SpecialBlock extends SpecialPage {
}
# Watchlist their user page? (Only if user is logged in)
- if( $wgUser->isLoggedIn() ) {
+ if( $user->isLoggedIn() ) {
$a['Watch'] = array(
'type' => 'check',
'label-message' => 'ipbwatchuser',
@@ -213,19 +215,19 @@ class SpecialBlock extends SpecialPage {
'label-message' => 'ipb-confirm',
);
+ $this->maybeAlterFormDefaults( $a );
+
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
+ * @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;
@@ -242,18 +244,22 @@ class SpecialBlock extends SpecialPage {
$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() ){
+ if( $this->getRequest()->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';
@@ -282,7 +288,7 @@ class SpecialBlock extends SpecialPage {
}
# Or if the user is trying to block themselves
- if( (string)$this->target === $wgUser->getName() ){
+ if( (string)$this->target === $this->getUser()->getName() ){
$fields['Confirm']['type'] = 'check';
unset( $fields['Confirm']['default'] );
$this->preErrors[] = 'ipb-blockingself';
@@ -291,11 +297,9 @@ class SpecialBlock extends SpecialPage {
/**
* Add header elements like block log entries, etc.
- * @param $form HTMLForm
- * @return void
*/
- protected function doPreText( HTMLForm &$form ){
- $form->addPreText( wfMsgExt( 'blockiptext', 'parse' ) );
+ protected function preText(){
+ $text = $this->msg( 'blockiptext' )->parse();
$otherBlockMessages = array();
if( $this->target !== null ) {
@@ -306,65 +310,48 @@ class SpecialBlock extends SpecialPage {
$s = Html::rawElement(
'h2',
array(),
- wfMsgExt( 'ipb-otherblocks-header', 'parseinline', count( $otherBlockMessages ) )
+ $this->msg( 'ipb-otherblocks-header', count( $otherBlockMessages ) )->parse()
) . "\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
- ) );
+ $text .= $s;
}
}
+
+ return $text;
}
/**
* Add footer elements to the form
- * @param $form HTMLForm
- * @return void
+ * @return string
*/
- protected function doPostText( HTMLForm &$form ){
- global $wgUser, $wgLang;
-
+ protected function postText(){
# 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() )
+ $this->msg( 'ipb-blocklist-contribs', $this->target->getName() )->escaped()
);
}
# 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() );
+ $message = $this->msg( 'ipb-unblock-addr', $this->target->getName() )->parse();
$list = SpecialPage::getTitleFor( 'Unblock', $this->target->getName() );
} else {
- $message = wfMsgExt( 'ipb-unblock', array( 'parseinline' ) );
+ $message = $this->msg( 'ipb-unblock' )->parse();
$list = SpecialPage::getTitleFor( 'Unblock' );
}
$links[] = Linker::linkKnown( $list, $message, array() );
@@ -372,24 +359,26 @@ class SpecialBlock extends SpecialPage {
# Link to the block list
$links[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'BlockList' ),
- wfMsg( 'ipb-blocklist' )
+ $this->msg( 'ipb-blocklist' )->escaped()
);
+ $user = $this->getUser();
+
# Link to edit the block dropdown reasons, if applicable
- if ( $wgUser->isAllowed( 'editinterface' ) ) {
+ if ( $user->isAllowed( 'editinterface' ) ) {
$links[] = Linker::link(
Title::makeTitle( NS_MEDIAWIKI, 'Ipbreason-dropdown' ),
- wfMsgHtml( 'ipb-edit-dropdown' ),
+ $this->msg( 'ipb-edit-dropdown' )->escaped(),
array(),
array( 'action' => 'edit' )
);
}
- $form->addPostText( Html::rawElement(
+ $text = Html::rawElement(
'p',
array( 'class' => 'mw-ipb-conveniencelinks' ),
- $wgLang->pipeList( $links )
- ) );
+ $this->getLanguage()->pipeList( $links )
+ );
if( $this->target instanceof User ){
# Get relevant extracts from the block and suppression logs, if possible
@@ -399,7 +388,7 @@ class SpecialBlock extends SpecialPage {
LogEventsList::showLogExtract(
$out,
'block',
- $userpage->getPrefixedText(),
+ $userpage,
'',
array(
'lim' => 10,
@@ -407,14 +396,14 @@ class SpecialBlock extends SpecialPage {
'showIfEmpty' => false
)
);
- $form->addPostText( $out );
+ $text .= $out;
# Add suppression block entries if allowed
- if( $wgUser->isAllowed( 'suppressionlog' ) ) {
+ if( $user->isAllowed( 'suppressionlog' ) ) {
LogEventsList::showLogExtract(
$out,
'suppress',
- $userpage->getPrefixedText(),
+ $userpage,
'',
array(
'lim' => 10,
@@ -423,9 +412,12 @@ class SpecialBlock extends SpecialPage {
'showIfEmpty' => false
)
);
- $form->addPostText( $out );
+
+ $text .= $out;
}
}
+
+ return $text;
}
/**
@@ -434,11 +426,12 @@ class SpecialBlock extends SpecialPage {
* @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
+ * @return array( User|string|null, Block::TYPE_ constant|null )
*/
public static function getTargetAndType( $par, WebRequest $request = null ){
$i = 0;
$target = null;
+
while( true ){
switch( $i++ ){
case 0:
@@ -467,11 +460,14 @@ class SpecialBlock extends SpecialPage {
case 4:
break 2;
}
+
list( $target, $type ) = Block::parseTarget( $target );
+
if( $type !== null ){
return array( $target, $type );
}
}
+
return array( null, null );
}
@@ -480,9 +476,10 @@ class SpecialBlock extends SpecialPage {
* @since 1.18
* @param $value String
* @param $alldata Array
+ * @param $form HTMLForm
* @return Message
*/
- public static function validateTargetField( $value, $alldata = null ) {
+ public static function validateTargetField( $value, $alldata, $form ) {
global $wgBlockCIDRLimit;
list( $target, $type ) = self::getTargetAndType( $value );
@@ -490,13 +487,13 @@ class SpecialBlock extends SpecialPage {
if( $type == Block::TYPE_USER ){
# TODO: why do we not have a User->exists() method?
if( !$target->getId() ){
- return wfMessage( 'nosuchusershort',
+ return $form->msg( 'nosuchusershort',
wfEscapeWikiText( $target->getName() ) );
}
- $status = self::checkUnblockSelf( $target );
+ $status = self::checkUnblockSelf( $target, $form->getUser() );
if ( $status !== true ) {
- return wfMessage( 'badaccess', $status );
+ return $form->msg( 'badaccess', $status );
}
} elseif( $type == Block::TYPE_RANGE ){
@@ -506,41 +503,52 @@ class SpecialBlock extends SpecialPage {
|| ( IP::isIPv6( $ip ) && $wgBlockCIDRLimit['IPv6'] == 128 ) )
{
# Range block effectively disabled
- return wfMessage( 'range_block_disabled' );
+ return $form->msg( 'range_block_disabled' );
}
if( ( IP::isIPv4( $ip ) && $range > 32 )
|| ( IP::isIPv6( $ip ) && $range > 128 ) )
{
# Dodgy range
- return wfMessage( 'ip_range_invalid' );
+ return $form->msg( 'ip_range_invalid' );
}
if( IP::isIPv4( $ip ) && $range < $wgBlockCIDRLimit['IPv4'] ) {
- return wfMessage( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] );
+ return $form->msg( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv4'] );
}
if( IP::isIPv6( $ip ) && $range < $wgBlockCIDRLimit['IPv6'] ) {
- return wfMessage( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] );
+ return $form->msg( 'ip_range_toolarge', $wgBlockCIDRLimit['IPv6'] );
}
-
} elseif( $type == Block::TYPE_IP ){
# All is well
-
} else {
- return wfMessage( 'badipaddress' );
+ return $form->msg( 'badipaddress' );
}
return true;
}
/**
+ * Submit callback for an HTMLForm object, will simply pass
+ * @param $data array
+ * @param $form HTMLForm
+ * @return Bool|String
+ */
+ public static function processUIForm( array $data, HTMLForm $form ) {
+ return self::processForm( $data, $form->getContext() );
+ }
+
+ /**
* Given the form data, actually implement a block
* @param $data Array
+ * @param $context IContextSource
* @return Bool|String
*/
- public static function processForm( array $data ){
- global $wgUser, $wgBlockAllowsUTEdit;
+ public static function processForm( array $data, IContextSource $context ){
+ global $wgBlockAllowsUTEdit;
+
+ $performer = $context->getUser();
// Handled by field validator callback
// self::validateTargetField( $data['Target'] );
@@ -557,20 +565,21 @@ class SpecialBlock extends SpecialPage {
# 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'] ) )
+ # permission anyway, although the code does allow for it.
+ # Note: Important to use $target instead of $data['Target']
+ # since both $data['PreviousTarget'] and $target are normalized
+ # but $data['target'] gets overriden by (non-normalized) request variable
+ # from previous request.
+ if( $target === $performer->getName() &&
+ ( $data['PreviousTarget'] !== $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' );
@@ -591,8 +600,9 @@ class SpecialBlock extends SpecialPage {
if( !isset( $data['HideUser'] ) ){
$data['HideUser'] = false;
}
+
if( $data['HideUser'] ) {
- if( !$wgUser->isAllowed('hideuser') ){
+ if( !$performer->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
@@ -603,16 +613,13 @@ class SpecialBlock extends SpecialPage {
# 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' );
}
@@ -621,7 +628,7 @@ class SpecialBlock extends SpecialPage {
# Create block object.
$block = new Block();
$block->setTarget( $target );
- $block->setBlocker( $wgUser );
+ $block->setBlocker( $performer );
$block->mReason = $data['Reason'][0];
$block->mExpiry = self::parseExpiryInput( $data['Expiry'] );
$block->prevents( 'createaccount', $data['CreateAccount'] );
@@ -631,7 +638,7 @@ class SpecialBlock extends SpecialPage {
$block->isAutoblocking( $data['AutoBlock'] );
$block->mHideName = $data['HideUser'];
- if( !wfRunHooks( 'BlockIp', array( &$block, &$wgUser ) ) ) {
+ if( !wfRunHooks( 'BlockIp', array( &$block, &$performer ) ) ) {
return array( 'hookaborted' );
}
@@ -655,7 +662,7 @@ class SpecialBlock extends SpecialPage {
# 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' ) ) {
+ if( $currentBlock->mHideName && !$performer->isAllowed( 'hideuser' ) ) {
return array( 'cant-see-hidden-user' );
}
@@ -677,7 +684,7 @@ class SpecialBlock extends SpecialPage {
$logaction = 'block';
}
- wfRunHooks( 'BlockIpComplete', array( $block, $wgUser ) );
+ wfRunHooks( 'BlockIpComplete', array( $block, $performer ) );
# Set *_deleted fields if requested
if( $data['HideUser'] ) {
@@ -686,7 +693,7 @@ class SpecialBlock extends SpecialPage {
# Can't watch a rangeblock
if( $type != Block::TYPE_RANGE && $data['Watch'] ) {
- $wgUser->addWatch( Title::makeTitle( NS_USER, $target ) );
+ $performer->addWatch( Title::makeTitle( NS_USER, $target ) );
}
# Block constructor sanitizes certain block options on insert
@@ -737,9 +744,11 @@ class SpecialBlock extends SpecialPage {
if( strpos( $option, ':' ) === false ){
$option = "$option:$option";
}
+
list( $show, $value ) = explode( ':', $option );
$a[htmlspecialchars( $show )] = htmlspecialchars( $value );
}
+
return $a;
}
@@ -754,15 +763,19 @@ class SpecialBlock extends SpecialPage {
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;
}
@@ -773,6 +786,7 @@ class SpecialBlock extends SpecialPage {
*/
public static function canBlockEmail( $user ) {
global $wgEnableUserEmail, $wgSysopEmailBans;
+
return ( $wgEnableUserEmail && $wgSysopEmailBans && $user->isAllowed( 'blockemail' ) );
}
@@ -781,22 +795,23 @@ class SpecialBlock extends SpecialPage {
* others, and probably shouldn't be able to unblock themselves
* either.
* @param $user User|Int|String
+ * @param $performer User user doing the request
* @return Bool|String true or error message key
*/
- public static function checkUnblockSelf( $user ) {
- global $wgUser;
+ public static function checkUnblockSelf( $user, User $performer ) {
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() ) {
+
+ if( $performer->isBlocked() ){
+ if( $user instanceof User && $user->getId() == $performer->getId() ) {
# User is trying to unblock themselves
- if ( $wgUser->isAllowed( 'unblockself' ) ) {
+ if ( $performer->isAllowed( 'unblockself' ) ) {
return true;
# User blocked themselves and is now trying to reverse it
- } elseif ( $wgUser->blockedBy() === $wgUser->getName() ) {
+ } elseif ( $performer->blockedBy() === $performer->getName() ) {
return true;
} else {
return 'ipbnounblockself';
@@ -814,7 +829,7 @@ class SpecialBlock extends SpecialPage {
* 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
+ * @param $type Block::TYPE_ constant (USER, RANGE, or IP)
* @return array
*/
protected static function blockLogFlags( array $data, $type ) {
@@ -823,32 +838,58 @@ class SpecialBlock extends SpecialPage {
# 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 ){
+ // For grepping: message block-log-flags-anononly
$flags[] = 'anononly';
}
if( $data['CreateAccount'] ){
+ // For grepping: message block-log-flags-nocreate
$flags[] = 'nocreate';
}
# Same as anononly, this is not displayed when blocking an IP address
- if( !$data['AutoBlock'] && $type != Block::TYPE_IP ){
+ if( !$data['AutoBlock'] && $type == Block::TYPE_USER ){
+ // For grepping: message block-log-flags-noautoblock
$flags[] = 'noautoblock';
}
if( $data['DisableEmail'] ){
+ // For grepping: message block-log-flags-noemail
$flags[] = 'noemail';
}
if( $wgBlockAllowsUTEdit && $data['DisableUTEdit'] ){
+ // For grepping: message block-log-flags-nousertalk
$flags[] = 'nousertalk';
}
if( $data['HideUser'] ){
+ // For grepping: message block-log-flags-hiddenname
$flags[] = 'hiddenname';
}
return implode( ',', $flags );
}
+
+ /**
+ * 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 function onSubmit( array $data ) {
+ // This isn't used since we need that HTMLForm that's passed in the
+ // second parameter. See alterForm for the real function
+ }
+
+ /**
+ * Do something exciting on successful processing of the form, most likely to show a
+ * confirmation message
+ */
+ public function onSuccess() {
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'blockipsuccesssub' ) );
+ $out->addWikiMsg( 'blockipsuccesstext', $this->target );
+ }
}
# BC @since 1.18
diff --git a/includes/specials/SpecialBlockList.php b/includes/specials/SpecialBlockList.php
index 04787a65..0a3a28fe 100644
--- a/includes/specials/SpecialBlockList.php
+++ b/includes/specials/SpecialBlockList.php
@@ -40,24 +40,25 @@ class SpecialBlockList extends SpecialPage {
* @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' );
+ $out = $this->getOutput();
+ $lang = $this->getLanguage();
+ $out->setPageTitle( $this->msg( 'ipblocklist' ) );
+ $out->addModuleStyles( 'mediawiki.special' );
- $par = $wgRequest->getVal( 'ip', $par );
- $this->target = trim( $wgRequest->getVal( 'wpTarget', $par ) );
+ $request = $this->getRequest();
+ $par = $request->getVal( 'ip', $par );
+ $this->target = trim( $request->getVal( 'wpTarget', $par ) );
- $this->options = $wgRequest->getArray( 'wpOptions', array() );
+ $this->options = $request->getArray( 'wpOptions', array() );
- $action = $wgRequest->getText( 'action' );
+ $action = $request->getText( 'action' );
- if( $action == 'unblock' || $action == 'submit' && $wgRequest->wasPosted() ) {
+ if( $action == 'unblock' || $action == 'submit' && $request->wasPosted() ) {
# B/C @since 1.18: Unblock interface is now at Special:Unblock
$title = SpecialPage::getTitleFor( 'Unblock', $this->target );
- $wgOut->redirect( $title->getFullUrl() );
+ $out->redirect( $title->getFullUrl() );
return;
}
@@ -68,13 +69,15 @@ class SpecialBlockList extends SpecialPage {
'label-message' => 'ipadressorusername',
'tabindex' => '1',
'size' => '45',
+ 'default' => $this->target,
),
'Options' => array(
'type' => 'multiselect',
'options' => array(
- wfMsg( 'blocklist-userblocks' ) => 'userblocks',
- wfMsg( 'blocklist-tempblocks' ) => 'tempblocks',
- wfMsg( 'blocklist-addressblocks' ) => 'addressblocks',
+ $this->msg( 'blocklist-userblocks' )->text() => 'userblocks',
+ $this->msg( 'blocklist-tempblocks' )->text() => 'tempblocks',
+ $this->msg( 'blocklist-addressblocks' )->text() => 'addressblocks',
+ $this->msg( 'blocklist-rangeblocks' )->text() => 'rangeblocks',
),
'flatlist' => true,
),
@@ -82,11 +85,11 @@ class SpecialBlockList extends SpecialPage {
'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,
+ $lang->formatNum( 20 ) => 20,
+ $lang->formatNum( 50 ) => 50,
+ $lang->formatNum( 100 ) => 100,
+ $lang->formatNum( 250 ) => 250,
+ $lang->formatNum( 500 ) => 500,
),
'name' => 'limit',
'default' => 50,
@@ -94,8 +97,8 @@ class SpecialBlockList extends SpecialPage {
);
$form = new HTMLForm( $fields, $this->getContext() );
$form->setMethod( 'get' );
- $form->setWrapperLegend( wfMsg( 'ipblocklist-legend' ) );
- $form->setSubmitText( wfMsg( 'ipblocklist-submit' ) );
+ $form->setWrapperLegendMsg( 'ipblocklist-legend' );
+ $form->setSubmitTextMsg( 'ipblocklist-submit' );
$form->prepareForm();
$form->displayForm( '' );
@@ -103,8 +106,6 @@ class SpecialBlockList extends SpecialPage {
}
function showList() {
- global $wgOut, $wgUser;
-
# Purge expired entries on one in every 10 queries
if ( !mt_rand( 0, 10 ) ) {
Block::purgeExpired();
@@ -112,7 +113,7 @@ class SpecialBlockList extends SpecialPage {
$conds = array();
# Is the user allowed to see hidden blocks?
- if ( !$wgUser->isAllowed( 'hideuser' ) ){
+ if ( !$this->getUser()->isAllowed( 'hideuser' ) ){
$conds['ipb_deleted'] = 0;
}
@@ -121,6 +122,7 @@ class SpecialBlockList extends SpecialPage {
switch( $type ){
case Block::TYPE_ID:
+ case Block::TYPE_AUTO:
$conds['ipb_id'] = $target;
break;
@@ -155,51 +157,52 @@ class SpecialBlockList extends SpecialPage {
if( in_array( 'addressblocks', $this->options ) ) {
$conds[] = "ipb_user != 0 OR ipb_range_end > ipb_range_start";
}
+ if( in_array( 'rangeblocks', $this->options ) ) {
+ $conds[] = "ipb_range_end = ipb_range_start";
+ }
# Check for other blocks, i.e. global/tor blocks
$otherBlockLink = array();
wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockLink, $this->target ) );
+ $out = $this->getOutput();
+
# 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"
+ $out->addHTML(
+ Html::element( 'h2', array(), $this->msg( 'ipblocklist-localblock' )->text() ) . "\n"
);
}
$pager = new BlockListPager( $this, $conds );
if ( $pager->getNumRows() ) {
- $wgOut->addHTML(
+ $out->addHTML(
$pager->getNavigationBar() .
$pager->getBody().
$pager->getNavigationBar()
);
} elseif ( $this->target ) {
- $wgOut->addWikiMsg( 'ipblocklist-no-results' );
+ $out->addWikiMsg( 'ipblocklist-no-results' );
} else {
- $wgOut->addWikiMsg( 'ipblocklist-empty' );
+ $out->addWikiMsg( 'ipblocklist-empty' );
}
if( count( $otherBlockLink ) ) {
- $wgOut->addHTML(
+ $out->addHTML(
Html::rawElement(
'h2',
array(),
- wfMsgExt(
- 'ipblocklist-otherblocks',
- 'parseinline',
- count( $otherBlockLink )
- )
+ $this->msg( 'ipblocklist-otherblocks', count( $otherBlockLink ) )->parse()
) . "\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" );
+ $out->addHTML( Html::rawElement( 'ul', array( 'class' => 'mw-ipblocklist-otherblocks' ), $list ) . "\n" );
}
}
}
@@ -208,11 +211,15 @@ class BlockListPager extends TablePager {
protected $conds;
protected $page;
+ /**
+ * @param $page SpecialPage
+ * @param $conds Array
+ */
function __construct( $page, $conds ) {
$this->page = $page;
$this->conds = $conds;
$this->mDefaultDirection = true;
- parent::__construct();
+ parent::__construct( $page->getContext() );
}
function getFieldNames() {
@@ -227,18 +234,17 @@ class BlockListPager extends TablePager {
'ipb_params' => 'blocklist-params',
'ipb_reason' => 'blocklist-reason',
);
- $headers = array_map( 'wfMsg', $headers );
+ foreach( $headers as $key => $val ) {
+ $headers[$key] = $this->msg( $val )->text();
+ }
}
return $headers;
}
function formatValue( $name, $value ) {
- global $wgLang, $wgUser;
-
- static $sk, $msg;
- if ( empty( $sk ) ) {
- $sk = $this->getSkin();
+ static $msg = null;
+ if ( $msg === null ) {
$msg = array(
'anononlyblock',
'createaccountblock',
@@ -249,27 +255,29 @@ class BlockListPager extends TablePager {
'change-blocklink',
'infiniteblock',
);
- $msg = array_combine( $msg, array_map( 'wfMessage', $msg ) );
+ $msg = array_combine( $msg, array_map( array( $this, 'msg' ), $msg ) );
}
+ /** @var $row object */
$row = $this->mCurrentRow;
+
$formatted = '';
switch( $name ) {
case 'ipb_timestamp':
- $formatted = $wgLang->timeanddate( $value, /* User preference timezone */ true );
+ $formatted = $this->getLanguage()->userTimeAndDate( $value, $this->getUser() );
break;
case 'ipb_target':
if( $row->ipb_auto ){
- $formatted = wfMessage( 'autoblockid', $row->ipb_id )->parse();
+ $formatted = $this->msg( '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(
+ $formatted = Linker::userLink( $target->getId(), $target );
+ $formatted .= Linker::userToolLinks(
$target->getId(),
$target,
false,
@@ -283,21 +291,21 @@ class BlockListPager extends TablePager {
break;
case 'ipb_expiry':
- $formatted = $wgLang->formatExpiry( $value, /* User preference timezone */ true );
- if( $wgUser->isAllowed( 'block' ) ){
+ $formatted = $this->getLanguage()->formatExpiry( $value, /* User preference timezone */ true );
+ if( $this->getUser()->isAllowed( 'block' ) ){
if( $row->ipb_auto ){
- $links[] = $sk->linkKnown(
+ $links[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Unblock' ),
$msg['unblocklink'],
array(),
array( 'wpTarget' => "#{$row->ipb_id}" )
);
} else {
- $links[] = $sk->linkKnown(
+ $links[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Unblock', $row->ipb_address ),
$msg['unblocklink']
);
- $links[] = $sk->linkKnown(
+ $links[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Block', $row->ipb_address ),
$msg['change-blocklink']
);
@@ -305,21 +313,23 @@ class BlockListPager extends TablePager {
$formatted .= ' ' . Html::rawElement(
'span',
array( 'class' => 'mw-blocklist-actions' ),
- wfMsg( 'parentheses', $wgLang->pipeList( $links ) )
+ $this->msg( 'parentheses' )->rawParams(
+ $this->getLanguage()->pipeList( $links ) )->escaped()
);
}
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() );
+ if ( isset( $row->by_user_name ) ) {
+ $formatted = Linker::userLink( $value, $row->by_user_name );
+ $formatted .= Linker::userToolLinks( $value, $row->by_user_name );
+ } else {
+ $formatted = htmlspecialchars( $row->ipb_by_text ); // foreign user?
}
break;
case 'ipb_reason':
- $formatted = $sk->commentBlock( $value );
+ $formatted = Linker::commentBlock( $value );
break;
case 'ipb_params':
@@ -342,7 +352,7 @@ class BlockListPager extends TablePager {
$properties[] = $msg['blocklist-nousertalk'];
}
- $formatted = $wgLang->commaList( $properties );
+ $formatted = $this->getLanguage()->commaList( $properties );
break;
default:
@@ -355,12 +365,14 @@ class BlockListPager extends TablePager {
function getQueryInfo() {
$info = array(
- 'tables' => array( 'ipblocks' ),
+ 'tables' => array( 'ipblocks', 'user' ),
'fields' => array(
'ipb_id',
'ipb_address',
'ipb_user',
'ipb_by',
+ 'ipb_by_text',
+ 'user_name AS by_user_name',
'ipb_reason',
'ipb_timestamp',
'ipb_auto',
@@ -375,12 +387,12 @@ class BlockListPager extends TablePager {
'ipb_allow_usertalk',
),
'conds' => $this->conds,
+ 'join_conds' => array( 'user' => array( 'LEFT JOIN', 'user_id = ipb_by' ) )
);
- global $wgUser;
# Is the user allowed to see hidden blocks?
- if ( !$wgUser->isAllowed( 'hideuser' ) ){
- $conds['ipb_deleted'] = 0;
+ if ( !$this->getUser()->isAllowed( 'hideuser' ) ){
+ $info['conds']['ipb_deleted'] = 0;
}
return $info;
@@ -402,8 +414,37 @@ class BlockListPager extends TablePager {
return false;
}
- function getTitle() {
- return $this->page->getTitle();
+ /**
+ * Do a LinkBatch query to minimise database load when generating all these links
+ * @param $result
+ */
+ function preprocessResults( $result ){
+ wfProfileIn( __METHOD__ );
+ # Do a link batch query
+ $lb = new LinkBatch;
+ $lb->setCaller( __METHOD__ );
+
+ $userids = array();
+
+ foreach ( $result as $row ) {
+ $userids[] = $row->ipb_by;
+
+ # Usernames and titles are in fact related by a simple substitution of space -> underscore
+ # The last few lines of Title::secureAndSplit() tell the story.
+ $name = str_replace( ' ', '_', $row->ipb_address );
+ $lb->add( NS_USER, $name );
+ $lb->add( NS_USER_TALK, $name );
+ }
+
+ $ua = UserArray::newFromIDs( $userids );
+ foreach( $ua as $user ){
+ $name = str_replace( ' ', '_', $user->getName() );
+ $lb->add( NS_USER, $name );
+ $lb->add( NS_USER_TALK, $name );
+ }
+
+ $lb->execute();
+ wfProfileOut( __METHOD__ );
}
}
diff --git a/includes/specials/SpecialBlockme.php b/includes/specials/SpecialBlockme.php
index 40747667..3840b2ff 100644
--- a/includes/specials/SpecialBlockme.php
+++ b/includes/specials/SpecialBlockme.php
@@ -33,18 +33,19 @@ class SpecialBlockme extends UnlistedSpecialPage {
}
function execute( $par ) {
- global $wgRequest, $wgOut, $wgBlockOpenProxies, $wgProxyKey;
+ global $wgBlockOpenProxies, $wgProxyKey;
$this->setHeaders();
$this->outputHeader();
- $ip = wfGetIP();
- if( !$wgBlockOpenProxies || $wgRequest->getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) {
- $wgOut->addWikiMsg( 'proxyblocker-disabled' );
+ $ip = $this->getRequest()->getIP();
+ if( !$wgBlockOpenProxies || $this->getRequest()->getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) {
+ $this->getOutput()->addWikiMsg( 'proxyblocker-disabled' );
return;
}
- $user = User::newFromName( wfMsgForContent( 'proxyblocker' ) );
+ $user = User::newFromName( $this->msg( 'proxyblocker' )->inContentLanguage()->text() );
+ # FIXME: newFromName could return false on a badly configured wiki.
if ( !$user->isLoggedIn() ) {
$user->addToDatabase();
}
@@ -52,10 +53,10 @@ class SpecialBlockme extends UnlistedSpecialPage {
$block = new Block();
$block->setTarget( $ip );
$block->setBlocker( $user );
- $block->mReason = wfMsg( 'proxyblockreason' );
+ $block->mReason = $this->msg( 'proxyblockreason' )->inContentLanguage()->text();
$block->insert();
- $wgOut->addWikiMsg( 'proxyblocksuccess' );
+ $this->getOutput()->addWikiMsg( 'proxyblocksuccess' );
}
}
diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php
index 20819329..48ca4f05 100644
--- a/includes/specials/SpecialBooksources.php
+++ b/includes/specials/SpecialBooksources.php
@@ -115,11 +115,11 @@ class SpecialBookSources extends SpecialPage {
private function makeForm() {
global $wgScript;
- $form = '<fieldset><legend>' . wfMsgHtml( 'booksources-search-legend' ) . '</legend>';
+ $form = '<fieldset><legend>' . $this->msg( 'booksources-search-legend' )->escaped() . '</legend>';
$form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
$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 .= '<p>' . Xml::inputLabel( $this->msg( 'booksources-isbn' )->text(), 'isbn', 'isbn', 20, $this->isbn );
+ $form .= '&#160;' . Xml::submitButton( $this->msg( 'booksources-go' )->text() ) . '</p>';
$form .= Xml::closeElement( 'form' );
$form .= '</fieldset>';
return $form;
@@ -139,7 +139,8 @@ class SpecialBookSources extends SpecialPage {
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
+ $page = $this->msg( 'booksources' )->inContentLanguage()->text();
+ $title = Title::makeTitleSafe( NS_PROJECT, $page ); # Show list in content language
if( is_object( $title ) && $title->exists() ) {
$rev = Revision::newFromTitle( $title );
$this->getOutput()->addWikiText( str_replace( 'MAGICNUMBER', $this->isbn, $rev->getText() ) );
diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php
index 2330c896..b8dbe9e8 100644
--- a/includes/specials/SpecialBrokenRedirects.php
+++ b/includes/specials/SpecialBrokenRedirects.php
@@ -22,7 +22,7 @@
*/
/**
- * A special page listing redirects tonon existent page. Those should be
+ * A special page listing redirects to non existent page. Those should be
* fixed to point to an existing page.
*
* @ingroup SpecialPage
@@ -38,7 +38,7 @@ class BrokenRedirectsPage extends PageQueryPage {
function sortDescending() { return false; }
function getPageHeader() {
- return wfMsgExt( 'brokenredirectstext', array( 'parse' ) );
+ return $this->msg( 'brokenredirectstext' )->parseAsBlock();
}
function getQueryInfo() {
@@ -47,6 +47,7 @@ class BrokenRedirectsPage extends PageQueryPage {
'p2' => 'page' ),
'fields' => array( 'p1.page_namespace AS namespace',
'p1.page_title AS title',
+ 'p1.page_title AS value',
'rd_namespace',
'rd_title'
),
@@ -77,8 +78,6 @@ class BrokenRedirectsPage extends PageQueryPage {
* @return String
*/
function formatResult( $skin, $result ) {
- global $wgUser, $wgLang;
-
$fromObj = Title::makeTitle( $result->namespace, $result->title );
if ( isset( $result->rd_title ) ) {
$toObj = Title::makeTitle( $result->rd_namespace, $result->rd_title );
@@ -93,43 +92,43 @@ class BrokenRedirectsPage extends PageQueryPage {
// $toObj may very easily be false if the $result list is cached
if ( !is_object( $toObj ) ) {
- return '<del>' . $skin->link( $fromObj ) . '</del>';
+ return '<del>' . Linker::link( $fromObj ) . '</del>';
}
- $from = $skin->linkKnown(
+ $from = Linker::linkKnown(
$fromObj,
null,
array(),
array( 'redirect' => 'no' )
);
$links = array();
- $links[] = $skin->linkKnown(
+ $links[] = Linker::linkKnown(
$fromObj,
- wfMsgHtml( 'brokenredirects-edit' ),
+ $this->msg( 'brokenredirects-edit' )->escaped(),
array(),
array( 'action' => 'edit' )
);
- $to = $skin->link(
+ $to = Linker::link(
$toObj,
null,
array(),
array(),
array( 'broken' )
);
- $arr = $wgLang->getArrow();
+ $arr = $this->getLanguage()->getArrow();
- $out = $from . wfMsg( 'word-separator' );
+ $out = $from . $this->msg( 'word-separator' )->escaped();
- if( $wgUser->isAllowed( 'delete' ) ) {
- $links[] = $skin->linkKnown(
+ if( $this->getUser()->isAllowed( 'delete' ) ) {
+ $links[] = Linker::linkKnown(
$fromObj,
- wfMsgHtml( 'brokenredirects-delete' ),
+ $this->msg( 'brokenredirects-delete' )->escaped(),
array(),
array( 'action' => 'delete' )
);
}
- $out .= wfMsg( 'parentheses', $wgLang->pipeList( $links ) );
+ $out .= $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( $links ) )->escaped();
$out .= " {$arr} {$to}";
return $out;
}
diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php
index 91d98b86..338cd706 100644
--- a/includes/specials/SpecialCategories.php
+++ b/includes/specials/SpecialCategories.php
@@ -31,20 +31,18 @@ class SpecialCategories extends SpecialPage {
}
function execute( $par ) {
- global $wgOut, $wgRequest;
-
$this->setHeaders();
$this->outputHeader();
- $wgOut->allowClickjacking();
+ $this->getOutput()->allowClickjacking();
- $from = $wgRequest->getText( 'from', $par );
+ $from = $this->getRequest()->getText( 'from', $par );
- $cap = new CategoryPager( $from );
+ $cap = new CategoryPager( $this->getContext(), $from );
$cap->doQuery();
- $wgOut->addHTML(
+ $this->getOutput()->addHTML(
Html::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) .
- wfMsgExt( 'categoriespagetext', array( 'parse' ), $cap->getNumRows() ) .
+ $this->msg( 'categoriespagetext', $cap->getNumRows() )->parseAsBlock() .
$cap->getStartForm( $from ) .
$cap->getNavigationBar() .
'<ul>' . $cap->getBody() . '</ul>' .
@@ -61,12 +59,16 @@ class SpecialCategories extends SpecialPage {
* @ingroup SpecialPage Pager
*/
class CategoryPager extends AlphabeticPager {
- function __construct( $from ) {
- parent::__construct();
+ private $conds = array( 'cat_pages > 0' );
+
+ function __construct( IContextSource $context, $from ) {
+ parent::__construct( $context );
$from = str_replace( ' ', '_', $from );
if( $from !== '' ) {
$from = Title::capitalize( $from, NS_CATEGORY );
- $this->mOffset = $from;
+ $dbr = wfGetDB( DB_SLAVE );
+ $this->conds[] = 'cat_title >= ' . $dbr->addQuotes( $from );
+ $this->setOffset( '' );
}
}
@@ -74,15 +76,11 @@ class CategoryPager extends AlphabeticPager {
return array(
'tables' => array( 'category' ),
'fields' => array( 'cat_title','cat_pages' ),
- 'conds' => array( 'cat_pages > 0' ),
+ 'conds' => $this->conds,
'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';
@@ -118,12 +116,10 @@ class CategoryPager extends AlphabeticPager {
}
function formatRow($result) {
- global $wgLang;
$title = Title::makeTitle( NS_CATEGORY, $result->cat_title );
$titleText = Linker::link( $title, htmlspecialchars( $title->getText() ) );
- $count = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->cat_pages ) );
- return Xml::tags('li', null, wfSpecialList( $titleText, $count ) ) . "\n";
+ $count = $this->msg( 'nmembers' )->numParams( $result->cat_pages )->escaped();
+ return Xml::tags( 'li', null, $this->getLanguage()->specialList( $titleText, $count ) ) . "\n";
}
public function getStartForm( $from ) {
@@ -132,10 +128,10 @@ class CategoryPager extends AlphabeticPager {
return
Xml::tags( 'form', array( 'method' => 'get', 'action' => $wgScript ),
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
- Xml::fieldset( wfMsg( 'categories' ),
- Xml::inputLabel( wfMsg( 'categoriesfrom' ),
+ Xml::fieldset( $this->msg( 'categories' )->text(),
+ Xml::inputLabel( $this->msg( 'categoriesfrom' )->text(),
'from', 'from', 20, $from ) .
' ' .
- Xml::submitButton( wfMsg( 'allpagessubmit' ) ) ) );
+ Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) ) );
}
}
diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php
new file mode 100644
index 00000000..0f85f516
--- /dev/null
+++ b/includes/specials/SpecialChangeEmail.php
@@ -0,0 +1,213 @@
+<?php
+/**
+ * Implements Special:ChangeEmail
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Let users change their email address.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialChangeEmail extends UnlistedSpecialPage {
+ public function __construct() {
+ parent::__construct( 'ChangeEmail' );
+ }
+
+ function isListed() {
+ global $wgAuth;
+ return $wgAuth->allowPropChange( 'emailaddress' );
+ }
+
+ /**
+ * Main execution point
+ */
+ function execute( $par ) {
+ global $wgAuth;
+
+ $this->checkReadOnly();
+
+ $this->setHeaders();
+ $this->outputHeader();
+
+ if ( !$wgAuth->allowPropChange( 'emailaddress' ) ) {
+ $this->error( 'cannotchangeemail' );
+ return;
+ }
+
+ $user = $this->getUser();
+ $request = $this->getRequest();
+
+ if ( !$request->wasPosted() && !$user->isLoggedIn() ) {
+ $this->error( 'changeemail-no-info' );
+ return;
+ }
+
+ if ( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) {
+ $this->doReturnTo();
+ return;
+ }
+
+ $out = $this->getOutput();
+ $out->disallowUserJs();
+ $out->addModules( 'mediawiki.special.changeemail' );
+
+ $this->mPassword = $request->getVal( 'wpPassword' );
+ $this->mNewEmail = $request->getVal( 'wpNewEmail' );
+
+ if ( $request->wasPosted()
+ && $user->matchEditToken( $request->getVal( 'token' ) ) )
+ {
+ $info = $this->attemptChange( $user, $this->mPassword, $this->mNewEmail );
+ if ( $info === true ) {
+ $this->doReturnTo();
+ } elseif ( $info === 'eauth' ) {
+ # Notify user that a confirmation email has been sent...
+ $out->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>",
+ 'eauthentsent', $user->getName() );
+ $this->doReturnTo( 'soft' ); // just show the link to go back
+ return; // skip form
+ }
+ }
+
+ $this->showForm();
+ }
+
+ protected function doReturnTo( $type = 'hard' ) {
+ $titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) );
+ if ( !$titleObj instanceof Title ) {
+ $titleObj = Title::newMainPage();
+ }
+ if ( $type == 'hard' ) {
+ $this->getOutput()->redirect( $titleObj->getFullURL() );
+ } else {
+ $this->getOutput()->addReturnTo( $titleObj );
+ }
+ }
+
+ protected function error( $msg ) {
+ $this->getOutput()->wrapWikiMsg( "<p class='error'>\n$1\n</p>", $msg );
+ }
+
+ protected function showForm() {
+ $user = $this->getUser();
+
+ $oldEmailText = $user->getEmail()
+ ? $user->getEmail()
+ : $this->msg( 'changeemail-none' )->text();
+
+ $this->getOutput()->addHTML(
+ Xml::fieldset( $this->msg( 'changeemail-header' )->text() ) .
+ Xml::openElement( 'form',
+ array(
+ 'method' => 'post',
+ 'action' => $this->getTitle()->getLocalUrl(),
+ 'id' => 'mw-changeemail-form' ) ) . "\n" .
+ Html::hidden( 'token', $user->getEditToken() ) . "\n" .
+ Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" .
+ $this->msg( 'changeemail-text' )->parseAsBlock() . "\n" .
+ Xml::openElement( 'table', array( 'id' => 'mw-changeemail-table' ) ) . "\n" .
+ $this->pretty( array(
+ array( 'wpName', 'username', 'text', $user->getName() ),
+ array( 'wpOldEmail', 'changeemail-oldemail', 'text', $oldEmailText ),
+ array( 'wpNewEmail', 'changeemail-newemail', 'input', $this->mNewEmail ),
+ array( 'wpPassword', 'yourpassword', 'password', $this->mPassword ),
+ ) ) . "\n" .
+ "<tr>\n" .
+ "<td></td>\n" .
+ '<td class="mw-input">' .
+ Xml::submitButton( $this->msg( 'changeemail-submit' )->text() ) .
+ Xml::submitButton( $this->msg( 'changeemail-cancel' )->text(), array( 'name' => 'wpCancel' ) ) .
+ "</td>\n" .
+ "</tr>\n" .
+ Xml::closeElement( 'table' ) .
+ Xml::closeElement( 'form' ) .
+ Xml::closeElement( 'fieldset' ) . "\n"
+ );
+ }
+
+ protected function pretty( $fields ) {
+ $out = '';
+ foreach ( $fields as $list ) {
+ list( $name, $label, $type, $value ) = $list;
+ if( $type == 'text' ) {
+ $field = htmlspecialchars( $value );
+ } else {
+ $attribs = array( 'id' => $name );
+ if ( $name == 'wpPassword' ) {
+ $attribs[] = 'autofocus';
+ }
+ $field = Html::input( $name, $value, $type, $attribs );
+ }
+ $out .= "<tr>\n";
+ $out .= "\t<td class='mw-label'>";
+ if ( $type != 'text' ) {
+ $out .= Xml::label( $this->msg( $label )->text(), $name );
+ } else {
+ $out .= $this->msg( $label )->escaped();
+ }
+ $out .= "</td>\n";
+ $out .= "\t<td class='mw-input'>";
+ $out .= $field;
+ $out .= "</td>\n";
+ $out .= "</tr>";
+ }
+ return $out;
+ }
+
+ /**
+ * @return bool|string true or string on success, false on failure
+ */
+ protected function attemptChange( User $user, $pass, $newaddr ) {
+ if ( $newaddr != '' && !Sanitizer::validateEmail( $newaddr ) ) {
+ $this->error( 'invalidemailaddress' );
+ return false;
+ }
+
+ $throttleCount = LoginForm::incLoginThrottle( $user->getName() );
+ if ( $throttleCount === true ) {
+ $this->error( 'login-throttled' );
+ return false;
+ }
+
+ if ( !$user->checkTemporaryPassword( $pass ) && !$user->checkPassword( $pass ) ) {
+ $this->error( 'wrongpassword' );
+ return false;
+ }
+
+ if ( $throttleCount ) {
+ LoginForm::clearLoginThrottle( $user->getName() );
+ }
+
+ list( $status, $info ) = Preferences::trySetUserEmail( $user, $newaddr );
+ if ( $status !== true ) {
+ if ( $status instanceof Status ) {
+ $this->getOutput()->addHTML(
+ '<p class="error">' .
+ $this->getOutput()->parseInline( $status->getWikiText( $info ) ) .
+ '</p>' );
+ }
+ return false;
+ }
+
+ $user->saveSettings();
+ return $info ? $info : true;
+ }
+}
diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php
index 46562b36..f6482ef5 100644
--- a/includes/specials/SpecialChangePassword.php
+++ b/includes/specials/SpecialChangePassword.php
@@ -26,7 +26,7 @@
*
* @ingroup SpecialPage
*/
-class SpecialChangePassword extends SpecialPage {
+class SpecialChangePassword extends UnlistedSpecialPage {
public function __construct() {
parent::__construct( 'ChangePassword' );
}
@@ -35,47 +35,46 @@ class SpecialChangePassword extends SpecialPage {
* Main execution point
*/
function execute( $par ) {
- global $wgUser, $wgAuth, $wgOut, $wgRequest;
+ global $wgAuth;
- if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- }
+ $this->checkReadOnly();
- $this->mUserName = $wgRequest->getVal( 'wpName' );
- $this->mOldpass = $wgRequest->getVal( 'wpPassword' );
- $this->mNewpass = $wgRequest->getVal( 'wpNewPassword' );
- $this->mRetype = $wgRequest->getVal( 'wpRetype' );
- $this->mDomain = $wgRequest->getVal( 'wpDomain' );
+ $request = $this->getRequest();
+ $this->mUserName = trim( $request->getVal( 'wpName' ) );
+ $this->mOldpass = $request->getVal( 'wpPassword' );
+ $this->mNewpass = $request->getVal( 'wpNewPassword' );
+ $this->mRetype = $request->getVal( 'wpRetype' );
+ $this->mDomain = $request->getVal( 'wpDomain' );
$this->setHeaders();
$this->outputHeader();
- $wgOut->disallowUserJs();
+ $this->getOutput()->disallowUserJs();
- if( !$wgRequest->wasPosted() && !$wgUser->isLoggedIn() ) {
- $this->error( wfMsg( 'resetpass-no-info' ) );
+ $user = $this->getUser();
+ if( !$request->wasPosted() && !$user->isLoggedIn() ) {
+ $this->error( $this->msg( 'resetpass-no-info' )->text() );
return;
}
- if( $wgRequest->wasPosted() && $wgRequest->getBool( 'wpCancel' ) ) {
+ if( $request->wasPosted() && $request->getBool( 'wpCancel' ) ) {
$this->doReturnTo();
return;
}
- if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'token' ) ) ) {
+ if( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'token' ) ) ) {
try {
if ( isset( $_SESSION['wsDomain'] ) ) {
$this->mDomain = $_SESSION['wsDomain'];
}
$wgAuth->setDomain( $this->mDomain );
if( !$wgAuth->allowPasswordChange() ) {
- $this->error( wfMsg( 'resetpass_forbidden' ) );
+ $this->error( $this->msg( 'resetpass_forbidden' )->text() );
return;
}
$this->attemptReset( $this->mNewpass, $this->mRetype );
- $wgOut->addWikiMsg( 'resetpass_success' );
- if( !$wgUser->isLoggedIn() ) {
+ $this->getOutput()->addWikiMsg( 'resetpass_success' );
+ if( !$user->isLoggedIn() ) {
LoginForm::setLoginToken();
$token = LoginForm::getLoginToken();
$data = array(
@@ -84,12 +83,13 @@ class SpecialChangePassword extends SpecialPage {
'wpDomain' => $this->mDomain,
'wpLoginToken' => $token,
'wpPassword' => $this->mNewpass,
- 'returnto' => $wgRequest->getVal( 'returnto' ),
+ 'returnto' => $request->getVal( 'returnto' ),
);
- if( $wgRequest->getCheck( 'wpRemember' ) ) {
+ if( $request->getCheck( 'wpRemember' ) ) {
$data['wpRemember'] = 1;
}
$login = new LoginForm( new FauxRequest( $data, true ) );
+ $login->setContext( $this->getContext() );
$login->execute( null );
}
$this->doReturnTo();
@@ -101,36 +101,33 @@ class SpecialChangePassword extends SpecialPage {
}
function doReturnTo() {
- global $wgRequest, $wgOut;
- $titleObj = Title::newFromText( $wgRequest->getVal( 'returnto' ) );
+ $titleObj = Title::newFromText( $this->getRequest()->getVal( 'returnto' ) );
if ( !$titleObj instanceof Title ) {
$titleObj = Title::newMainPage();
}
- $wgOut->redirect( $titleObj->getFullURL() );
+ $this->getOutput()->redirect( $titleObj->getFullURL() );
}
function error( $msg ) {
- global $wgOut;
- $wgOut->addHTML( Xml::element('p', array( 'class' => 'error' ), $msg ) );
+ $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $msg ) );
}
function showForm() {
- global $wgOut, $wgUser, $wgRequest;
+ global $wgCookieExpiration;
- $self = $this->getTitle();
+ $user = $this->getUser();
if ( !$this->mUserName ) {
- $this->mUserName = $wgUser->getName();
+ $this->mUserName = $user->getName();
}
$rememberMe = '';
- if ( !$wgUser->isLoggedIn() ) {
- global $wgCookieExpiration, $wgLang;
+ if ( !$user->isLoggedIn() ) {
$rememberMe = '<tr>' .
'<td></td>' .
'<td class="mw-input">' .
Xml::checkLabel(
- wfMsgExt( 'remembermypassword', 'parsemag', $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) ) ),
+ $this->msg( 'remembermypassword' )->numParams( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) )->text(),
'wpRemember', 'wpRemember',
- $wgRequest->getCheck( 'wpRemember' ) ) .
+ $this->getRequest()->getCheck( 'wpRemember' ) ) .
'</td>' .
'</tr>';
$submitMsg = 'resetpass_submit';
@@ -139,18 +136,18 @@ class SpecialChangePassword extends SpecialPage {
$oldpassMsg = 'oldpassword';
$submitMsg = 'resetpass-submit-loggedin';
}
- $wgOut->addHTML(
- Xml::fieldset( wfMsg( 'resetpass_header' ) ) .
+ $this->getOutput()->addHTML(
+ Xml::fieldset( $this->msg( 'resetpass_header' )->text() ) .
Xml::openElement( 'form',
array(
'method' => 'post',
- 'action' => $self->getLocalUrl(),
+ 'action' => $this->getTitle()->getLocalUrl(),
'id' => 'mw-resetpass-form' ) ) . "\n" .
- Html::hidden( 'token', $wgUser->editToken() ) . "\n" .
+ Html::hidden( 'token', $user->getEditToken() ) . "\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" .
+ Html::hidden( 'returnto', $this->getRequest()->getVal( 'returnto' ) ) . "\n" .
+ $this->msg( 'resetpass_text' )->parseAsBlock() . "\n" .
Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" .
$this->pretty( array(
array( 'wpName', 'username', 'text', $this->mUserName ),
@@ -162,8 +159,8 @@ class SpecialChangePassword extends SpecialPage {
"<tr>\n" .
"<td></td>\n" .
'<td class="mw-input">' .
- Xml::submitButton( wfMsg( $submitMsg ) ) .
- Xml::submitButton( wfMsg( 'resetpass-submit-cancel' ), array( 'name' => 'wpCancel' ) ) .
+ Xml::submitButton( $this->msg( $submitMsg )->text() ) .
+ Xml::submitButton( $this->msg( 'resetpass-submit-cancel' )->text(), array( 'name' => 'wpCancel' ) ) .
"</td>\n" .
"</tr>\n" .
Xml::closeElement( 'table' ) .
@@ -192,9 +189,9 @@ class SpecialChangePassword extends SpecialPage {
$out .= "<tr>\n";
$out .= "\t<td class='mw-label'>";
if ( $type != 'text' )
- $out .= Xml::label( wfMsg( $label ), $name );
+ $out .= Xml::label( $this->msg( $label )->text(), $name );
else
- $out .= wfMsgHtml( $label );
+ $out .= $this->msg( $label )->escaped();
$out .= "</td>\n";
$out .= "\t<td class='mw-input'>";
$out .= $field;
@@ -210,22 +207,22 @@ class SpecialChangePassword extends SpecialPage {
protected function attemptReset( $newpass, $retype ) {
$user = User::newFromName( $this->mUserName );
if( !$user || $user->isAnon() ) {
- throw new PasswordError( wfMsg( 'nosuchusershort', $this->mUserName ) );
+ throw new PasswordError( $this->msg( 'nosuchusershort', $this->mUserName )->text() );
}
if( $newpass !== $retype ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'badretype' ) );
- throw new PasswordError( wfMsg( 'badretype' ) );
+ throw new PasswordError( $this->msg( 'badretype' )->text() );
}
$throttleCount = LoginForm::incLoginThrottle( $this->mUserName );
if ( $throttleCount === true ) {
- throw new PasswordError( wfMsg( 'login-throttled' ) );
+ throw new PasswordError( $this->msg( 'login-throttled' )->text() );
}
if( !$user->checkTemporaryPassword($this->mOldpass) && !$user->checkPassword($this->mOldpass) ) {
wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'wrongpassword' ) );
- throw new PasswordError( wfMsg( 'resetpass-wrong-oldpass' ) );
+ throw new PasswordError( $this->msg( 'resetpass-wrong-oldpass' )->text() );
}
// Please reset throttle for successful logins, thanks!
diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php
index 6b9ef0a9..9e3c52b9 100644
--- a/includes/specials/SpecialComparePages.php
+++ b/includes/specials/SpecialComparePages.php
@@ -57,6 +57,7 @@ class SpecialComparePages extends SpecialPage {
'label-message' => 'compare-page1',
'size' => '40',
'section' => 'page1',
+ 'validation-callback' => array( $this, 'checkExistingTitle' ),
),
'Revision1' => array(
'type' => 'int',
@@ -64,6 +65,7 @@ class SpecialComparePages extends SpecialPage {
'label-message' => 'compare-rev1',
'size' => '8',
'section' => 'page1',
+ 'validation-callback' => array( $this, 'checkExistingRevision' ),
),
'Page2' => array(
'type' => 'text',
@@ -71,6 +73,7 @@ class SpecialComparePages extends SpecialPage {
'label-message' => 'compare-page2',
'size' => '40',
'section' => 'page2',
+ 'validation-callback' => array( $this, 'checkExistingTitle' ),
),
'Revision2' => array(
'type' => 'int',
@@ -78,6 +81,7 @@ class SpecialComparePages extends SpecialPage {
'label-message' => 'compare-rev2',
'size' => '8',
'section' => 'page2',
+ 'validation-callback' => array( $this, 'checkExistingRevision' ),
),
'Action' => array(
'type' => 'hidden',
@@ -87,29 +91,33 @@ class SpecialComparePages extends SpecialPage {
'type' => 'hidden',
'name' => 'diffonly',
),
- ), 'compare' );
- $form->setSubmitText( wfMsg( 'compare-submit' ) );
+ 'Unhide' => array(
+ 'type' => 'hidden',
+ 'name' => 'unhide',
+ ),
+ ), $this->getContext(), 'compare' );
+ $form->setSubmitTextMsg( 'compare-submit' );
$form->suppressReset();
$form->setMethod( 'get' );
- $form->setTitle( $this->getTitle() );
+ $form->setSubmitCallback( array( __CLASS__, 'showDiff' ) );
$form->loadData();
$form->displayForm( '' );
-
- self::showDiff( $form->mFieldData );
+ $form->trySubmit();
}
- public static function showDiff( $data ){
+ public static function showDiff( $data, HTMLForm $form ){
$rev1 = self::revOrTitle( $data['Revision1'], $data['Page1'] );
$rev2 = self::revOrTitle( $data['Revision2'], $data['Page2'] );
if( $rev1 && $rev2 ) {
- $de = new DifferenceEngine( null,
+ $de = new DifferenceEngine( $form->getContext(),
$rev1,
$rev2,
null, // rcid
- ( $data["Action"] == 'purge' ),
- false );
+ ( $data['Action'] == 'purge' ),
+ ( $data['Unhide'] == '1' )
+ );
$de->showDiffPage( true );
}
}
@@ -125,4 +133,29 @@ class SpecialComparePages extends SpecialPage {
}
return null;
}
+
+ public function checkExistingTitle( $value, $alldata ) {
+ if ( $value === '' || $value === null ) {
+ return true;
+ }
+ $title = Title::newFromText( $value );
+ if ( !$title instanceof Title ) {
+ return $this->msg( 'compare-invalid-title' )->parseAsBlock();
+ }
+ if ( !$title->exists() ) {
+ return $this->msg( 'compare-title-not-exists' )->parseAsBlock();
+ }
+ return true;
+ }
+
+ public function checkExistingRevision( $value, $alldata ) {
+ if ( $value === '' || $value === null ) {
+ return true;
+ }
+ $revision = Revision::newFromId( $value );
+ if ( $revision === null ) {
+ return $this->msg( 'compare-revision-not-exists' )->parseAsBlock();
+ }
+ return true;
+ }
}
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
index 70bbfe39..912f7733 100644
--- a/includes/specials/SpecialConfirmemail.php
+++ b/includes/specials/SpecialConfirmemail.php
@@ -46,11 +46,9 @@ class EmailConfirmation extends UnlistedSpecialPage {
function execute( $code ) {
$this->setHeaders();
- if ( wfReadOnly() ) {
- throw new ReadOnlyError;
- }
+ $this->checkReadOnly();
- if( empty( $code ) ) {
+ if( $code === null || $code === '' ) {
if( $this->getUser()->isLoggedIn() ) {
if( Sanitizer::validateEmail( $this->getUser()->getEmail() ) ) {
$this->showRequestForm();
@@ -60,11 +58,11 @@ class EmailConfirmation extends UnlistedSpecialPage {
} else {
$llink = Linker::linkKnown(
SpecialPage::getTitleFor( 'Userlogin' ),
- wfMsgHtml( 'loginreqlink' ),
+ $this->msg( 'loginreqlink' )->escaped(),
array(),
array( 'returnto' => $this->getTitle()->getPrefixedText() )
);
- $this->getOutput()->addHTML( wfMessage( 'confirmemail_needlogin' )->rawParams( $llink )->parse() );
+ $this->getOutput()->addHTML( $this->msg( 'confirmemail_needlogin' )->rawParams( $llink )->parse() );
}
} else {
$this->attemptConfirm( $code );
@@ -89,9 +87,11 @@ class EmailConfirmation extends UnlistedSpecialPage {
// date and time are separate parameters to facilitate localisation.
// $time is kept for backward compat reasons.
// 'emailauthenticated' is also used in SpecialPreferences.php
- $time = $this->getLang()->timeAndDate( $user->mEmailAuthenticated, true );
- $d = $this->getLang()->date( $user->mEmailAuthenticated, true );
- $t = $this->getLang()->time( $user->mEmailAuthenticated, true );
+ $lang = $this->getLanguage();
+ $emailAuthenticated = $user->getEmailAuthenticationTimestamp();
+ $time = $lang->userTimeAndDate( $emailAuthenticated, $user );
+ $d = $lang->userDate( $emailAuthenticated, $user );
+ $t = $lang->userTime( $emailAuthenticated, $user );
$out->addWikiMsg( 'emailauthenticated', $time, $d, $t );
}
if( $user->isEmailConfirmationPending() ) {
@@ -99,8 +99,8 @@ class EmailConfirmation extends UnlistedSpecialPage {
}
$out->addWikiMsg( 'confirmemail_text' );
$form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl() ) );
- $form .= Html::hidden( 'token', $user->editToken() );
- $form .= Xml::submitButton( wfMsg( 'confirmemail_send' ) );
+ $form .= Html::hidden( 'token', $user->getEditToken() );
+ $form .= Xml::submitButton( $this->msg( 'confirmemail_send' )->text() );
$form .= Xml::closeElement( 'form' );
$out->addHTML( $form );
}
diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php
index fea27bfd..31df4a9b 100644
--- a/includes/specials/SpecialContributions.php
+++ b/includes/specials/SpecialContributions.php
@@ -36,86 +36,96 @@ class SpecialContributions extends SpecialPage {
}
public function execute( $par ) {
- global $wgUser, $wgOut, $wgRequest;
-
$this->setHeaders();
$this->outputHeader();
- $wgOut->addModuleStyles( 'mediawiki.special' );
+ $out = $this->getOutput();
+ $out->addModuleStyles( 'mediawiki.special' );
$this->opts = array();
+ $request = $this->getRequest();
- if( $par == 'newbies' ) {
+ if ( $par == 'newbies' ) {
$target = 'newbies';
$this->opts['contribs'] = 'newbie';
- } elseif( isset( $par ) ) {
+ } elseif ( $par !== null ) {
$target = $par;
} else {
- $target = $wgRequest->getVal( 'target' );
+ $target = $request->getVal( 'target' );
}
// check for radiobox
- if( $wgRequest->getVal( 'contribs' ) == 'newbie' ) {
+ if ( $request->getVal( 'contribs' ) == 'newbie' ) {
$target = 'newbies';
$this->opts['contribs'] = 'newbie';
+ } else {
+ $this->opts['contribs'] = 'user';
}
- $this->opts['deletedOnly'] = $wgRequest->getBool( 'deletedOnly' );
+ $this->opts['deletedOnly'] = $request->getBool( 'deletedOnly' );
- if( !strlen( $target ) ) {
- $wgOut->addHTML( $this->getForm() );
+ if ( !strlen( $target ) ) {
+ $out->addHTML( $this->getForm() );
return;
}
- $this->opts['limit'] = $wgRequest->getInt( 'limit', $wgUser->getOption('rclimit') );
+ $user = $this->getUser();
+
+ $this->opts['limit'] = $request->getInt( 'limit', $user->getOption( 'rclimit' ) );
$this->opts['target'] = $target;
- $this->opts['topOnly'] = $wgRequest->getBool( 'topOnly' );
+ $this->opts['topOnly'] = $request->getBool( 'topOnly' );
$nt = Title::makeTitleSafe( NS_USER, $target );
- if( !$nt ) {
- $wgOut->addHTML( $this->getForm() );
+ if ( !$nt ) {
+ $out->addHTML( $this->getForm() );
return;
}
- $id = User::idFromName( $nt->getText() );
+ $userObj = User::newFromName( $nt->getText(), false );
+ if ( !$userObj ) {
+ $out->addHTML( $this->getForm() );
+ return;
+ }
+ $id = $userObj->getID();
- if( $target != 'newbies' ) {
+ if ( $this->opts['contribs'] != 'newbie' ) {
$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 );
- }
+ $out->addSubtitle( $this->contributionsSub( $userObj ) );
+ $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'contributions-title', $target )->plain() ) );
+ $this->getSkin()->setRelevantUser( $userObj );
} else {
- $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') );
- $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'sp-contributions-newbies-title' ) ) );
+ $out->addSubtitle( $this->msg( 'sp-contributions-newbies-sub' ) );
+ $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'sp-contributions-newbies-title' )->plain() ) );
}
- if( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) {
+ if ( ( $ns = $request->getVal( 'namespace', null ) ) !== null && $ns !== '' ) {
$this->opts['namespace'] = intval( $ns );
} else {
$this->opts['namespace'] = '';
}
- $this->opts['tagFilter'] = (string) $wgRequest->getVal( 'tagFilter' );
+ $this->opts['associated'] = $request->getBool( 'associated' );
+
+ $this->opts['nsInvert'] = (bool) $request->getVal( 'nsInvert' );
+
+ $this->opts['tagfilter'] = (string) $request->getVal( 'tagfilter' );
// Allows reverts to have the bot flag in recent changes. It is just here to
// be passed in the form at the top of the page
- if( $wgUser->isAllowed( 'markbotedits' ) && $wgRequest->getBool( 'bot' ) ) {
+ if ( $user->isAllowed( 'markbotedits' ) && $request->getBool( 'bot' ) ) {
$this->opts['bot'] = '1';
}
- $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev';
+ $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev';
# Offset overrides year/month selection
- if( $skip ) {
+ if ( $skip ) {
$this->opts['year'] = '';
$this->opts['month'] = '';
} else {
- $this->opts['year'] = $wgRequest->getIntOrNull( 'year' );
- $this->opts['month'] = $wgRequest->getIntOrNull( 'month' );
+ $this->opts['year'] = $request->getIntOrNull( 'year' );
+ $this->opts['month'] = $request->getIntOrNull( 'month' );
}
- $feedType = $wgRequest->getVal( 'feed' );
- if( $feedType ) {
+ $feedType = $request->getVal( 'feed' );
+ if ( $feedType ) {
// Maintain some level of backwards compatability
// If people request feeds using the old parameters, redirect to API
$apiParams = array(
@@ -129,8 +139,8 @@ class SpecialContributions extends SpecialPage {
if ( $this->opts['deletedOnly'] ) {
$apiParams['deletedonly'] = true;
}
- if ( $this->opts['tagFilter'] !== '' ) {
- $apiParams['tagfilter'] = $this->opts['tagFilter'];
+ if ( $this->opts['tagfilter'] !== '' ) {
+ $apiParams['tagfilter'] = $this->opts['tagfilter'];
}
if ( $this->opts['namespace'] !== '' ) {
$apiParams['namespace'] = $this->opts['namespace'];
@@ -144,7 +154,7 @@ class SpecialContributions extends SpecialPage {
$url = wfScript( 'api' ) . '?' . wfArrayToCGI( $apiParams );
- $wgOut->redirect( $url, '301' );
+ $out->redirect( $url, '301' );
return;
}
@@ -153,47 +163,49 @@ class SpecialContributions extends SpecialPage {
if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id ) ) ) {
- $wgOut->addHTML( $this->getForm() );
+ $out->addHTML( $this->getForm() );
- $pager = new ContribsPager( array(
+ $pager = new ContribsPager( $this->getContext(), array(
'target' => $target,
+ 'contribs' => $this->opts['contribs'],
'namespace' => $this->opts['namespace'],
'year' => $this->opts['year'],
'month' => $this->opts['month'],
'deletedOnly' => $this->opts['deletedOnly'],
'topOnly' => $this->opts['topOnly'],
+ 'nsInvert' => $this->opts['nsInvert'],
+ 'associated' => $this->opts['associated'],
) );
- if( !$pager->getNumRows() ) {
- $wgOut->addWikiMsg( 'nocontribs', $target );
+ if ( !$pager->getNumRows() ) {
+ $out->addWikiMsg( 'nocontribs', $target );
} else {
# Show a message about slave lag, if applicable
$lag = wfGetLB()->safeGetLag( $pager->getDatabase() );
- if( $lag > 0 )
- $wgOut->showLagWarning( $lag );
+ if ( $lag > 0 )
+ $out->showLagWarning( $lag );
- $wgOut->addHTML(
+ $out->addHTML(
'<p>' . $pager->getNavigationBar() . '</p>' .
$pager->getBody() .
'<p>' . $pager->getNavigationBar() . '</p>'
);
}
- $wgOut->preventClickjacking( $pager->getPreventClickjacking() );
+ $out->preventClickjacking( $pager->getPreventClickjacking() );
# Show the appropriate "footer" message - WHOIS tools, etc.
- if( $target != 'newbies' ) {
+ if ( $this->opts['contribs'] != 'newbie' ) {
$message = 'sp-contributions-footer';
if ( IP::isIPAddress( $target ) ) {
$message = 'sp-contributions-footer-anon';
} else {
- $user = User::newFromName( $target );
- if ( !$user || $user->isAnon() ) {
+ if ( $userObj->isAnon() ) {
// No message for non-existing users
return;
}
}
- if( !wfMessage( $message, $target )->isDisabled() ) {
- $wgOut->wrapWikiMsg(
+ if ( !$this->msg( $message, $target )->isDisabled() ) {
+ $out->wrapWikiMsg(
"<div class='mw-contributions-footer'>\n$1\n</div>",
array( $message, $target ) );
}
@@ -203,33 +215,29 @@ class SpecialContributions extends SpecialPage {
/**
* Generates the subheading with links
- * @param $nt Title object for the target
- * @param $id Integer: User ID for the target
+ * @param $userObj User object for the target
* @return String: appropriately-escaped HTML to be output literally
* @todo FIXME: Almost the same as getSubTitle in SpecialDeletedContributions.php. Could be combined.
*/
- protected function contributionsSub( $nt, $id ) {
- global $wgLang, $wgUser, $wgOut;
-
- $sk = $this->getSkin();
-
- if ( $id === null ) {
- $user = htmlspecialchars( $nt->getText() );
+ protected function contributionsSub( $userObj ) {
+ if ( $userObj->isAnon() ) {
+ $user = htmlspecialchars( $userObj->getName() );
} else {
- $user = $sk->link( $nt, htmlspecialchars( $nt->getText() ) );
+ $user = Linker::link( $userObj->getUserPage(), htmlspecialchars( $userObj->getName() ) );
}
- $userObj = User::newFromName( $nt->getText(), /* check for username validity not needed */ false );
- $talk = $nt->getTalkPage();
- if( $talk ) {
- $tools = self::getUserLinks( $nt, $talk, $userObj, $wgUser );
- $links = $wgLang->pipeList( $tools );
+ $nt = $userObj->getUserPage();
+ $talk = $userObj->getTalkPage();
+ if ( $talk ) {
+ $tools = $this->getUserLinks( $nt, $talk, $userObj );
+ $links = $this->getLanguage()->pipeList( $tools );
// Show a note if the user is blocked and display the last block log entry.
if ( $userObj->isBlocked() ) {
+ $out = $this->getOutput(); // showLogExtract() wants first parameter by reference
LogEventsList::showLogExtract(
- $wgOut,
+ $out,
'block',
- $nt->getPrefixedText(),
+ $nt,
'',
array(
'lim' => 1,
@@ -238,9 +246,9 @@ class SpecialContributions extends SpecialPage {
$userObj->isAnon() ?
'sp-contributions-blocked-notice-anon' :
'sp-contributions-blocked-notice',
- $nt->getText() # Support GENDER in 'sp-contributions-blocked-notice'
+ $userObj->getName() # Support GENDER in 'sp-contributions-blocked-notice'
),
- 'offset' => '' # don't use $wgRequest parameter offset
+ 'offset' => '' # don't use WebRequest parameter offset
)
);
}
@@ -250,10 +258,11 @@ 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' ) ) {
- return wfMsgHtml( 'contribsub2', $user, $links );
+ $oldMsg = $this->msg( 'contribsub' );
+ if ( $oldMsg->exists() ) {
+ return $oldMsg->rawParams( "$user ($links)" );
} else {
- return wfMsgHtml( 'contribsub', "$user ($links)" );
+ return $this->msg( 'contribsub2' )->rawParams( $user, $links );
}
}
@@ -262,38 +271,37 @@ class SpecialContributions extends SpecialPage {
* @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!!)
+ * @return array
*/
- public static function getUserLinks( Title $userpage, Title $talkpage, User $target, User $subject ) {
+ public function getUserLinks( Title $userpage, Title $talkpage, User $target ) {
- $sk = $subject->getSkin();
$id = $target->getId();
$username = $target->getName();
- $tools[] = $sk->link( $talkpage, wfMsgHtml( 'sp-contributions-talk' ) );
+ $tools[] = Linker::link( $talkpage, $this->msg( 'sp-contributions-talk' )->escaped() );
- if( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) {
- if( $subject->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
+ if ( ( $id !== null ) || ( $id === null && IP::isIPAddress( $username ) ) ) {
+ if ( $this->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
if ( $target->isBlocked() ) {
- $tools[] = $sk->linkKnown( # Change block link
+ $tools[] = Linker::linkKnown( # Change block link
SpecialPage::getTitleFor( 'Block', $username ),
- wfMsgHtml( 'change-blocklink' )
+ $this->msg( 'change-blocklink' )->escaped()
);
- $tools[] = $sk->linkKnown( # Unblock link
+ $tools[] = Linker::linkKnown( # Unblock link
SpecialPage::getTitleFor( 'Unblock', $username ),
- wfMsgHtml( 'unblocklink' )
+ $this->msg( 'unblocklink' )->escaped()
);
} else { # User is not blocked
- $tools[] = $sk->linkKnown( # Block link
+ $tools[] = Linker::linkKnown( # Block link
SpecialPage::getTitleFor( 'Block', $username ),
- wfMsgHtml( 'blocklink' )
+ $this->msg( 'blocklink' )->escaped()
);
}
}
# Block log link
- $tools[] = $sk->linkKnown(
+ $tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Log', 'block' ),
- wfMsgHtml( 'sp-contributions-blocklog' ),
+ $this->msg( 'sp-contributions-blocklog' )->escaped(),
array(),
array(
'page' => $userpage->getPrefixedText()
@@ -301,31 +309,32 @@ class SpecialContributions extends SpecialPage {
);
}
# Uploads
- $tools[] = $sk->linkKnown(
+ $tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Listfiles', $username ),
- wfMsgHtml( 'sp-contributions-uploads' )
+ $this->msg( 'sp-contributions-uploads' )->escaped()
);
# Other logs link
- $tools[] = $sk->linkKnown(
+ $tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Log', $username ),
- wfMsgHtml( 'sp-contributions-logs' )
+ $this->msg( 'sp-contributions-logs' )->escaped()
);
# Add link to deleted user contributions for priviledged users
- if( $subject->isAllowed( 'deletedhistory' ) ) {
- $tools[] = $sk->linkKnown(
+ if ( $this->getUser()->isAllowed( 'deletedhistory' ) ) {
+ $tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'DeletedContributions', $username ),
- wfMsgHtml( 'sp-contributions-deleted' )
+ $this->msg( 'sp-contributions-deleted' )->escaped()
);
}
# Add a link to change user rights for privileged users
$userrightsPage = new UserrightsPage();
- if( $id !== null && $userrightsPage->userCanChangeRights( $target ) ) {
- $tools[] = $sk->linkKnown(
+ $userrightsPage->setContext( $this->getContext() );
+ if ( $id !== null && $userrightsPage->userCanChangeRights( $target ) ) {
+ $tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Userrights', $username ),
- wfMsgHtml( 'sp-contributions-userrights' )
+ $this->msg( 'sp-contributions-userrights' )->escaped()
);
}
@@ -341,82 +350,185 @@ class SpecialContributions extends SpecialPage {
global $wgScript;
$this->opts['title'] = $this->getTitle()->getPrefixedText();
- if( !isset( $this->opts['target'] ) ) {
+ if ( !isset( $this->opts['target'] ) ) {
$this->opts['target'] = '';
} else {
$this->opts['target'] = str_replace( '_' , ' ' , $this->opts['target'] );
}
- if( !isset( $this->opts['namespace'] ) ) {
+ if ( !isset( $this->opts['namespace'] ) ) {
$this->opts['namespace'] = '';
}
- if( !isset( $this->opts['contribs'] ) ) {
+ if ( !isset( $this->opts['nsInvert'] ) ) {
+ $this->opts['nsInvert'] = '';
+ }
+
+ if ( !isset( $this->opts['associated'] ) ) {
+ $this->opts['associated'] = false;
+ }
+
+ if ( !isset( $this->opts['contribs'] ) ) {
$this->opts['contribs'] = 'user';
}
- if( !isset( $this->opts['year'] ) ) {
+ if ( !isset( $this->opts['year'] ) ) {
$this->opts['year'] = '';
}
- if( !isset( $this->opts['month'] ) ) {
+ if ( !isset( $this->opts['month'] ) ) {
$this->opts['month'] = '';
}
- if( $this->opts['contribs'] == 'newbie' ) {
+ if ( $this->opts['contribs'] == 'newbie' ) {
$this->opts['target'] = '';
}
- if( !isset( $this->opts['tagFilter'] ) ) {
- $this->opts['tagFilter'] = '';
+ if ( !isset( $this->opts['tagfilter'] ) ) {
+ $this->opts['tagfilter'] = '';
}
- if( !isset( $this->opts['topOnly'] ) ) {
+ if ( !isset( $this->opts['topOnly'] ) ) {
$this->opts['topOnly'] = false;
}
- $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'class' => 'mw-contributions-form' ) );
+ $form = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'class' => 'mw-contributions-form' ) );
# Add hidden params for tracking except for parameters in $skipParameters
- $skipParameters = array( 'namespace', 'deletedOnly', 'target', 'contribs', 'year', 'month', 'topOnly' );
+ $skipParameters = array( 'namespace', 'nsInvert', 'deletedOnly', 'target', 'contribs', 'year', 'month', 'topOnly', 'associated' );
foreach ( $this->opts as $name => $value ) {
- if( in_array( $name, $skipParameters ) ) {
+ if ( in_array( $name, $skipParameters ) ) {
continue;
}
- $f .= "\t" . Html::hidden( $name, $value ) . "\n";
+ $form .= "\t" . Html::hidden( $name, $value ) . "\n";
}
- $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagFilter'] );
+ $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] );
- $f .= Xml::fieldset( wfMsg( 'sp-contributions-search' ) ) .
- Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parsemag' ) ),
- 'contribs', 'newbie' , 'newbie', $this->opts['contribs'] == 'newbie' ) . '<br />' .
- Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parsemag' ) ),
- 'contribs' , 'user', 'user', $this->opts['contribs'] == 'user' ) . ' ' .
- Html::input( 'target', $this->opts['target'], 'text', array(
- 'size' => '20',
- 'required' => ''
- ) + ( $this->opts['target'] ? array() : array( 'autofocus' ) ) ) . ' '.
+ if ( $tagFilter ) {
+ $filterSelection =
+ Xml::tags( 'td', array( 'class' => 'mw-label' ), array_shift( $tagFilter ) ) .
+ Xml::tags( 'td', array( 'class' => 'mw-input' ), implode( '&#160', $tagFilter ) );
+ } else {
+ $filterSelection = Xml::tags( 'td', array( 'colspan' => 2 ), '' );
+ }
+
+ $targetSelection = Xml::tags( 'td', array( 'colspan' => 2 ),
+ Xml::radioLabel(
+ $this->msg( 'sp-contributions-newbies' )->text(),
+ 'contribs',
+ 'newbie' ,
+ 'newbie',
+ $this->opts['contribs'] == 'newbie',
+ array( 'class' => 'mw-input' )
+ ) . '<br />' .
+ Xml::radioLabel(
+ $this->msg( 'sp-contributions-username' )->text(),
+ 'contribs',
+ 'user',
+ 'user',
+ $this->opts['contribs'] == 'user',
+ array( 'class' => 'mw-input' )
+ ) . ' ' .
+ Html::input(
+ 'target',
+ $this->opts['target'],
+ 'text',
+ array( 'size' => '20', 'required' => '', 'class' => 'mw-input' ) +
+ ( $this->opts['target'] ? array() : array( 'autofocus' )
+ )
+ ) . ' '
+ ) ;
+
+ $namespaceSelection =
+ Xml::tags( 'td', array( 'class' => 'mw-label' ),
+ Xml::label(
+ $this->msg( 'namespace' )->text(),
+ 'namespace',
+ ''
+ )
+ ) .
+ Xml::tags( 'td', null,
+ Xml::namespaceSelector( $this->opts['namespace'], '' ) . '&#160;' .
+ Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
+ Xml::checkLabel(
+ $this->msg( 'invert' )->text(),
+ 'nsInvert',
+ 'nsInvert',
+ $this->opts['nsInvert'],
+ array( 'title' => $this->msg( 'tooltip-invert' )->text(), 'class' => 'mw-input' )
+ ) . '&#160;'
+ ) .
+ Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
+ Xml::checkLabel(
+ $this->msg( 'namespace_association' )->text(),
+ 'associated',
+ 'associated',
+ $this->opts['associated'],
+ array( 'title' => $this->msg( 'tooltip-namespace_association' )->text(), 'class' => 'mw-input' )
+ ) . '&#160;'
+ )
+ ) ;
+
+ $extraOptions = Xml::tags( 'td', array( 'colspan' => 2 ),
Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
- Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' .
- Xml::namespaceSelector( $this->opts['namespace'], '' )
+ Xml::checkLabel(
+ $this->msg( 'history-show-deleted' )->text(),
+ 'deletedOnly',
+ 'mw-show-deleted-only',
+ $this->opts['deletedOnly'],
+ array( 'class' => 'mw-input' )
+ )
) .
- Xml::checkLabel( wfMsg( 'history-show-deleted' ),
- 'deletedOnly', 'mw-show-deleted-only', $this->opts['deletedOnly'] ) . '<br />' .
- Xml::tags( 'p', null, Xml::checkLabel( wfMsg( 'sp-contributions-toponly' ),
- 'topOnly', 'mw-show-top-only', $this->opts['topOnly'] ) ) .
- ( $tagFilter ? Xml::tags( 'p', null, implode( '&#160;', $tagFilter ) ) : '' ) .
- Html::rawElement( 'p', array( 'style' => 'white-space: nowrap' ),
- Xml::dateMenu( $this->opts['year'], $this->opts['month'] ) . ' ' .
- Xml::submitButton( wfMsg( 'sp-contributions-submit' ) )
- ) . ' ';
- $explain = wfMessage( 'sp-contributions-explain' );
+ Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ),
+ Xml::checkLabel(
+ $this->msg( 'sp-contributions-toponly' )->text(),
+ 'topOnly',
+ 'mw-show-top-only',
+ $this->opts['topOnly'],
+ array( 'class' => 'mw-input' )
+ )
+ )
+ ) ;
+
+ $dateSelectionAndSubmit = Xml::tags( 'td', array( 'colspan' => 2 ),
+ Xml::dateMenu(
+ $this->opts['year'],
+ $this->opts['month']
+ ) . ' ' .
+ Xml::submitButton(
+ $this->msg( 'sp-contributions-submit' )->text(),
+ array( 'class' => 'mw-submit' )
+ )
+ ) ;
+
+ $form .=
+ Xml::fieldset( $this->msg( 'sp-contributions-search' )->text() ) .
+ Xml::openElement( 'table', array( 'class' => 'mw-contributions-table' ) ) .
+ Xml::openElement( 'tr' ) .
+ $targetSelection .
+ Xml::closeElement( 'tr' ) .
+ Xml::openElement( 'tr' ) .
+ $namespaceSelection .
+ Xml::closeElement( 'tr' ) .
+ Xml::openElement( 'tr' ) .
+ $filterSelection .
+ Xml::closeElement( 'tr' ) .
+ Xml::openElement( 'tr' ) .
+ $extraOptions .
+ Xml::closeElement( 'tr' ) .
+ Xml::openElement( 'tr' ) .
+ $dateSelectionAndSubmit .
+ Xml::closeElement( 'tr' ) .
+ Xml::closeElement( 'table' );
+
+ $explain = $this->msg( 'sp-contributions-explain' );
if ( $explain->exists() ) {
- $f .= "<p id='mw-sp-contributions-explain'>{$explain}</p>";
+ $form .= "<p id='mw-sp-contributions-explain'>{$explain}</p>";
}
- $f .= Xml::closeElement('fieldset' ) .
+ $form .= Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' );
- return $f;
+ return $form;
}
}
@@ -430,18 +542,21 @@ class ContribsPager extends ReverseChronologicalPager {
var $namespace = '', $mDb;
var $preventClickjacking = false;
- function __construct( $options ) {
- parent::__construct();
+ function __construct( IContextSource $context, array $options ) {
+ parent::__construct( $context );
$msgs = array( 'uctop', 'diff', 'newarticle', 'rollbacklink', 'diff', 'hist', 'rev-delundel', 'pipe-separator' );
- foreach( $msgs as $msg ) {
- $this->messages[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
+ foreach ( $msgs as $msg ) {
+ $this->messages[$msg] = $this->msg( $msg )->escaped();
}
$this->target = isset( $options['target'] ) ? $options['target'] : '';
+ $this->contribs = isset( $options['contribs'] ) ? $options['contribs'] : 'users';
$this->namespace = isset( $options['namespace'] ) ? $options['namespace'] : '';
- $this->tagFilter = isset( $options['tagFilter'] ) ? $options['tagFilter'] : false;
+ $this->tagFilter = isset( $options['tagfilter'] ) ? $options['tagfilter'] : false;
+ $this->nsInvert = isset( $options['nsInvert'] ) ? $options['nsInvert'] : false;
+ $this->associated = isset( $options['associated'] ) ? $options['associated'] : false;
$this->deletedOnly = !empty( $options['deletedOnly'] );
$this->topOnly = !empty( $options['topOnly'] );
@@ -459,33 +574,35 @@ class ContribsPager extends ReverseChronologicalPager {
return $query;
}
- function getTitle() {
- return SpecialPage::getTitleFor( 'Contributions' );
- }
-
function getQueryInfo() {
- global $wgUser;
list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond();
+ $user = $this->getUser();
$conds = array_merge( $userCond, $this->getNamespaceCond() );
+
// Paranoia: avoid brute force searches (bug 17342)
- if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
- $conds[] = $this->mDb->bitAnd('rev_deleted',Revision::DELETED_USER) . ' = 0';
- } elseif( !$wgUser->isAllowed( 'suppressrevision' ) ) {
- $conds[] = $this->mDb->bitAnd('rev_deleted',Revision::SUPPRESSED_USER) .
+ if ( !$user->isAllowed( 'deletedhistory' ) ) {
+ $conds[] = $this->mDb->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0';
+ } elseif ( !$user->isAllowed( 'suppressrevision' ) ) {
+ $conds[] = $this->mDb->bitAnd( 'rev_deleted', Revision::SUPPRESSED_USER ) .
' != ' . Revision::SUPPRESSED_USER;
}
- $join_cond['page'] = array( 'INNER JOIN', 'page_id=rev_page' );
+
+ # Don't include orphaned revisions
+ $join_cond['page'] = Revision::pageJoinCond();
+ # Get the current user name for accounts
+ $join_cond['user'] = Revision::userJoinCond();
$queryInfo = array(
- 'tables' => $tables,
- 'fields' => array(
- 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'page_is_redirect',
- 'page_len','rev_id', 'rev_page', 'rev_text_id', 'rev_timestamp', 'rev_comment',
- 'rev_minor_edit', 'rev_user', 'rev_user_text', 'rev_parent_id', 'rev_deleted'
+ 'tables' => $tables,
+ 'fields' => array_merge(
+ Revision::selectFields(),
+ Revision::selectUserFields(),
+ array( 'page_namespace', 'page_title', 'page_is_new',
+ 'page_latest', 'page_is_redirect', 'page_len' )
),
- 'conds' => $conds,
- 'options' => array( 'USE INDEX' => array('revision' => $index) ),
+ 'conds' => $conds,
+ 'options' => array( 'USE INDEX' => array( 'revision' => $index ) ),
'join_conds' => $join_cond
);
@@ -505,31 +622,52 @@ class ContribsPager extends ReverseChronologicalPager {
function getUserCond() {
$condition = array();
$join_conds = array();
- if( $this->target == 'newbies' ) {
- $tables = array( 'user_groups', 'page', 'revision' );
+ $tables = array( 'revision', 'page', 'user' );
+ if ( $this->contribs == 'newbie' ) {
+ $tables[] = 'user_groups';
$max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ );
- $condition[] = 'rev_user >' . (int)($max - $max / 100);
+ $condition[] = 'rev_user >' . (int)( $max - $max / 100 );
$condition[] = 'ug_group IS NULL';
$index = 'user_timestamp';
# @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' );
- $condition['rev_user_text'] = $this->target;
- $index = 'usertext_timestamp';
+ if ( IP::isIPAddress( $this->target ) ) {
+ $condition['rev_user_text'] = $this->target;
+ $index = 'usertext_timestamp';
+ } else {
+ $condition['rev_user'] = User::idFromName( $this->target );
+ $index = 'user_timestamp';
+ }
}
- if( $this->deletedOnly ) {
+ if ( $this->deletedOnly ) {
$condition[] = "rev_deleted != '0'";
}
- if( $this->topOnly ) {
+ if ( $this->topOnly ) {
$condition[] = "rev_id = page_latest";
}
return array( $tables, $index, $condition, $join_conds );
}
function getNamespaceCond() {
- if( $this->namespace !== '' ) {
- return array( 'page_namespace' => (int)$this->namespace );
+ if ( $this->namespace !== '' ) {
+ $selectedNS = $this->mDb->addQuotes( $this->namespace );
+ $eq_op = $this->nsInvert ? '!=' : '=';
+ $bool_op = $this->nsInvert ? 'AND' : 'OR';
+
+ if ( !$this->associated ) {
+ return array( "page_namespace $eq_op $selectedNS" );
+ } else {
+ $associatedNS = $this->mDb->addQuotes (
+ MWNamespace::getAssociated( $this->namespace )
+ );
+ return array(
+ "page_namespace $eq_op $selectedNS " .
+ $bool_op .
+ " page_namespace $eq_op $associatedNS"
+ );
+ }
+
} else {
return array();
}
@@ -539,10 +677,62 @@ class ContribsPager extends ReverseChronologicalPager {
return 'rev_timestamp';
}
+ function doBatchLookups() {
+ $this->mResult->rewind();
+ $revIds = array();
+ foreach ( $this->mResult as $row ) {
+ if( $row->rev_parent_id ) {
+ $revIds[] = $row->rev_parent_id;
+ }
+ }
+ $this->mParentLens = $this->getParentLengths( $revIds );
+ $this->mResult->rewind(); // reset
+
+ # Do a link batch query
+ $this->mResult->seek( 0 );
+ $batch = new LinkBatch();
+ # Give some pointers to make (last) links
+ foreach ( $this->mResult as $row ) {
+ if ( $this->contribs === 'newbie' ) { // multiple users
+ $batch->add( NS_USER, $row->user_name );
+ $batch->add( NS_USER_TALK, $row->user_name );
+ }
+ $batch->add( $row->page_namespace, $row->page_title );
+ }
+ $batch->execute();
+ $this->mResult->seek( 0 );
+ }
+
+ /**
+ * Do a batched query to get the parent revision lengths
+ */
+ private function getParentLengths( array $revIds ) {
+ $revLens = array();
+ if ( !$revIds ) {
+ return $revLens; // empty
+ }
+ wfProfileIn( __METHOD__ );
+ $res = $this->getDatabase()->select( 'revision',
+ array( 'rev_id', 'rev_len' ),
+ array( 'rev_id' => $revIds ),
+ __METHOD__ );
+ foreach ( $res as $row ) {
+ $revLens[$row->rev_id] = $row->rev_len;
+ }
+ wfProfileOut( __METHOD__ );
+ return $revLens;
+ }
+
+ /**
+ * @return string
+ */
function getStartBody() {
return "<ul>\n";
}
+ /**
+ * @return string
+ */
function getEndBody() {
return "</ul>\n";
}
@@ -558,15 +748,13 @@ class ContribsPager extends ReverseChronologicalPager {
* @todo This would probably look a lot nicer in a table.
*/
function formatRow( $row ) {
- global $wgUser, $wgLang;
wfProfileIn( __METHOD__ );
- $sk = $this->getSkin();
$rev = new Revision( $row );
$classes = array();
$page = Title::newFromRow( $row );
- $link = $sk->link(
+ $link = Linker::link(
$page,
htmlspecialchars( $page->getPrefixedText() ),
array(),
@@ -574,19 +762,20 @@ class ContribsPager extends ReverseChronologicalPager {
);
# Mark current revisions
$topmarktext = '';
- if( $row->rev_id == $row->page_latest ) {
+ if ( $row->rev_id == $row->page_latest ) {
$topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
# Add rollback link
- if( !$row->page_is_new && $page->quickUserCan( 'rollback' )
+ if ( !$row->page_is_new && $page->quickUserCan( 'rollback' )
&& $page->quickUserCan( 'edit' ) )
{
$this->preventClickjacking();
- $topmarktext .= ' '.$sk->generateRollback( $rev );
+ $topmarktext .= ' ' . Linker::generateRollback( $rev );
}
}
+ $user = $this->getUser();
# Is there a visible previous revision?
- if( $rev->userCan( Revision::DELETED_TEXT ) && $rev->getParentId() !== 0 ) {
- $difftext = $sk->linkKnown(
+ if ( $rev->userCan( Revision::DELETED_TEXT, $user ) && $rev->getParentId() !== 0 ) {
+ $difftext = Linker::linkKnown(
$page,
$this->messages['diff'],
array(),
@@ -598,77 +787,78 @@ class ContribsPager extends ReverseChronologicalPager {
} else {
$difftext = $this->messages['diff'];
}
- $histlink = $sk->linkKnown(
+ $histlink = Linker::linkKnown(
$page,
$this->messages['hist'],
array(),
array( 'action' => 'history' )
);
- $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(
+ if ( $row->rev_parent_id === null ) {
+ // For some reason rev_parent_id isn't populated for this row.
+ // Its rumoured this is true on wikipedia for some revisions (bug 34922).
+ // Next best thing is to have the total number of bytes.
+ $chardiff = ' . . ' . Linker::formatRevisionSize( $row->rev_len ) . ' . . ';
+ } else {
+ $parentLen = isset( $this->mParentLens[$row->rev_parent_id] ) ? $this->mParentLens[$row->rev_parent_id] : 0;
+ $chardiff = ' . . ' . ChangesList::showCharacterDifference(
+ $parentLen, $row->rev_len ) . ' . . ';
+ }
+
+ $lang = $this->getLanguage();
+ $comment = $lang->getDirMark() . Linker::revComment( $rev, false, true );
+ $date = $lang->userTimeAndDate( $row->rev_timestamp, $user );
+ if ( $rev->userCan( Revision::DELETED_TEXT, $user ) ) {
+ $d = Linker::linkKnown(
$page,
- htmlspecialchars($date),
+ htmlspecialchars( $date ),
array(),
array( 'oldid' => intval( $row->rev_id ) )
);
} else {
$d = htmlspecialchars( $date );
}
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
$d = '<span class="history-deleted">' . $d . '</span>';
}
- if( $this->target == 'newbies' ) {
- $userlink = ' . . ' . $sk->userLink( $row->rev_user, $row->rev_user_text );
- $userlink .= ' ' . wfMsg( 'parentheses', $sk->userTalkLink( $row->rev_user, $row->rev_user_text ) ) . ' ';
+ # Show user names for /newbies as there may be different users.
+ # Note that we already excluded rows with hidden user names.
+ if ( $this->contribs == 'newbie' ) {
+ $userlink = ' . . ' . Linker::userLink( $rev->getUser(), $rev->getUserText() );
+ $userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
+ Linker::userTalkLink( $rev->getUser(), $rev->getUserText() ) )->escaped() . ' ';
} else {
$userlink = '';
}
- if( $rev->getParentId() === 0 ) {
+ if ( $rev->getParentId() === 0 ) {
$nflag = ChangesList::flag( 'newpage' );
} else {
$nflag = '';
}
- if( $rev->isMinor() ) {
+ if ( $rev->isMinor() ) {
$mflag = ChangesList::flag( 'minor' );
} else {
$mflag = '';
}
- // Don't show useless link to people who cannot hide revisions
- $canHide = $wgUser->isAllowed( 'deleterevision' );
- if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
- if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
- $del = $this->mSkin->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
- } else {
- $query = array(
- 'type' => 'revision',
- 'target' => $page->getPrefixedDbkey(),
- 'ids' => $rev->getId()
- );
- $del = $this->mSkin->revDeleteLink( $query,
- $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
- }
+ $del = Linker::getRevDeleteLink( $user, $rev, $page );
+ if ( $del !== '' ) {
$del .= ' ';
- } else {
- $del = '';
}
$diffHistLinks = '(' . $difftext . $this->messages['pipe-separator'] . $histlink . ')';
- $ret = "{$del}{$d} {$diffHistLinks} {$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}";
+ $ret = "{$del}{$d} {$diffHistLinks}{$chardiff}{$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}";
# Denote if username is redacted for this edit
- if( $rev->isDeleted( Revision::DELETED_USER ) ) {
- $ret .= " <strong>" . wfMsgHtml('rev-deleted-user-contribs') . "</strong>";
+ if ( $rev->isDeleted( Revision::DELETED_USER ) ) {
+ $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
}
# Tags, if any.
- list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'contributions' );
+ list( $tagSummary, $newClasses ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'contributions' );
$classes = array_merge( $classes, $newClasses );
$ret .= " $tagSummary";
diff --git a/includes/specials/SpecialDeadendpages.php b/includes/specials/SpecialDeadendpages.php
index f8ef4d44..1266a0ce 100644
--- a/includes/specials/SpecialDeadendpages.php
+++ b/includes/specials/SpecialDeadendpages.php
@@ -33,7 +33,7 @@ class DeadendPagesPage extends PageQueryPage {
}
function getPageHeader() {
- return wfMsgExt( 'deadendpagestext', array( 'parse' ) );
+ return $this->msg( 'deadendpagestext' )->parseAsBlock();
}
/**
diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php
index 65858482..3498a16d 100644
--- a/includes/specials/SpecialDeletedContributions.php
+++ b/includes/specials/SpecialDeletedContributions.php
@@ -31,11 +31,11 @@ class DeletedContribsPager extends IndexPager {
var $messages, $target;
var $namespace = '', $mDb;
- function __construct( $target, $namespace = false ) {
- parent::__construct();
+ function __construct( IContextSource $context, $target, $namespace = false ) {
+ parent::__construct( $context );
$msgs = array( 'deletionlog', 'undeleteviewlink', 'diff' );
foreach( $msgs as $msg ) {
- $this->messages[$msg] = wfMsgExt( $msg, array( 'escapenoentities') );
+ $this->messages[$msg] = $this->msg( $msg )->escaped();
}
$this->target = $target;
$this->namespace = $namespace;
@@ -49,13 +49,13 @@ class DeletedContribsPager extends IndexPager {
}
function getQueryInfo() {
- global $wgUser;
list( $index, $userCond ) = $this->getUserCond();
$conds = array_merge( $userCond, $this->getNamespaceCond() );
+ $user = $this->getUser();
// Paranoia: avoid brute force searches (bug 17792)
- if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
+ if( !$user->isAllowed( 'deletedhistory' ) ) {
$conds[] = $this->mDb->bitAnd('ar_deleted',Revision::DELETED_USER) . ' = 0';
- } elseif( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ } elseif( !$user->isAllowed( 'suppressrevision' ) ) {
$conds[] = $this->mDb->bitAnd('ar_deleted',Revision::SUPPRESSED_USER) .
' != ' . Revision::SUPPRESSED_USER;
}
@@ -92,25 +92,24 @@ class DeletedContribsPager extends IndexPager {
}
function getNavigationBar() {
- global $wgLang;
-
if ( isset( $this->mNavigationBar ) ) {
return $this->mNavigationBar;
}
- $fmtLimit = $wgLang->formatNum( $this->mLimit );
+ $lang = $this->getLanguage();
+ $fmtLimit = $lang->formatNum( $this->mLimit );
$linkTexts = array(
- 'prev' => wfMsgExt( 'pager-newer-n', array( 'escape', 'parsemag' ), $fmtLimit ),
- 'next' => wfMsgExt( 'pager-older-n', array( 'escape', 'parsemag' ), $fmtLimit ),
- 'first' => wfMsgHtml( 'histlast' ),
- 'last' => wfMsgHtml( 'histfirst' )
+ 'prev' => $this->msg( 'pager-newer-n', $fmtLimit )->escaped(),
+ 'next' => $this->msg( 'pager-older-n', $fmtLimit )->escaped(),
+ 'first' => $this->msg( 'histlast' )->escaped(),
+ 'last' => $this->msg( 'histfirst' )->escaped()
);
$pagingLinks = $this->getPagingLinks( $linkTexts );
$limitLinks = $this->getLimitLinks();
- $limits = $wgLang->pipeList( $limitLinks );
+ $limits = $lang->pipeList( $limitLinks );
- $this->mNavigationBar = "(" . $wgLang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) ) . ") " .
- wfMsgExt( 'viewprevnext', array( 'parsemag', 'escape', 'replaceafter' ), $pagingLinks['prev'], $pagingLinks['next'], $limits );
+ $this->mNavigationBar = "(" . $lang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) ) . ") " .
+ $this->msg( 'viewprevnext' )->rawParams( $pagingLinks['prev'], $pagingLinks['next'], $limits )->escaped();
return $this->mNavigationBar;
}
@@ -133,11 +132,8 @@ class DeletedContribsPager extends IndexPager {
* @todo This would probably look a lot nicer in a table.
*/
function formatRow( $row ) {
- global $wgUser, $wgLang;
wfProfileIn( __METHOD__ );
- $sk = $this->getSkin();
-
$rev = new Revision( array(
'id' => $row->ar_rev_id,
'comment' => $row->ar_comment,
@@ -153,7 +149,7 @@ class DeletedContribsPager extends IndexPager {
$undelete = SpecialPage::getTitleFor( 'Undelete' );
$logs = SpecialPage::getTitleFor( 'Log' );
- $dellog = $sk->linkKnown(
+ $dellog = Linker::linkKnown(
$logs,
$this->messages['deletionlog'],
array(),
@@ -163,13 +159,15 @@ class DeletedContribsPager extends IndexPager {
)
);
- $reviewlink = $sk->linkKnown(
+ $reviewlink = Linker::linkKnown(
SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
$this->messages['undeleteviewlink']
);
- if( $wgUser->isAllowed('deletedtext') ) {
- $last = $sk->linkKnown(
+ $user = $this->getUser();
+
+ if( $user->isAllowed('deletedtext') ) {
+ $last = Linker::linkKnown(
$undelete,
$this->messages['diff'],
array(),
@@ -183,13 +181,13 @@ class DeletedContribsPager extends IndexPager {
$last = $this->messages['diff'];
}
- $comment = $sk->revComment( $rev );
- $date = htmlspecialchars( $wgLang->timeanddate( $rev->getTimestamp(), true ) );
+ $comment = Linker::revComment( $rev );
+ $date = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $rev->getTimestamp(), $user ) );
- if( !$wgUser->isAllowed('undelete') || !$rev->userCan(Revision::DELETED_TEXT) ) {
+ if( !$user->isAllowed( 'undelete' ) || !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
$link = $date; // unusable link
} else {
- $link = $sk->linkKnown(
+ $link = Linker::linkKnown(
$undelete,
$date,
array(),
@@ -204,7 +202,7 @@ class DeletedContribsPager extends IndexPager {
$link = '<span class="history-deleted">' . $link . '</span>';
}
- $pagelink = $sk->link( $page );
+ $pagelink = Linker::link( $page );
if( $rev->isMinor() ) {
$mflag = ChangesList::flag( 'minor' );
@@ -213,33 +211,21 @@ class DeletedContribsPager extends IndexPager {
}
// Revision delete link
- $canHide = $wgUser->isAllowed( 'deleterevision' );
- if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
- if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
- $del = $this->mSkin->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
- } else {
- $query = array(
- 'type' => 'archive',
- 'target' => $page->getPrefixedDbkey(),
- 'ids' => $rev->getTimestamp() );
- $del = $this->mSkin->revDeleteLink( $query,
- $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide ) . ' ';
- }
- } else {
- $del = '';
- }
+ $del = Linker::getRevDeleteLink( $user, $rev, $page );
+ if ( $del ) $del .= ' ';
$tools = Html::rawElement(
'span',
array( 'class' => 'mw-deletedcontribs-tools' ),
- wfMsg( 'parentheses', $wgLang->pipeList( array( $last, $dellog, $reviewlink ) ) )
+ $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList(
+ array( $last, $dellog, $reviewlink ) ) )->escaped()
);
$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>";
+ $ret .= " <strong>" . $this->msg( 'rev-deleted-user-contribs' )->escaped() . "</strong>";
}
$ret = Html::rawElement( 'li', array(), $ret ) . "\n";
@@ -272,64 +258,65 @@ class DeletedContributionsPage extends SpecialPage {
* @param $par String: (optional) user name of the user for which to show the contributions
*/
function execute( $par ) {
- global $wgUser;
+ global $wgQueryPageDefaultLimit;
$this->setHeaders();
- if ( !$this->userCanExecute( $wgUser ) ) {
+ $user = $this->getUser();
+
+ if ( !$this->userCanExecute( $user ) ) {
$this->displayRestrictionError();
return;
}
- global $wgOut, $wgRequest;
-
- $wgOut->setPageTitle( wfMsgExt( 'deletedcontributions-title', array( 'parsemag' ) ) );
+ $request = $this->getRequest();
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'deletedcontributions-title' ) );
$options = array();
- if ( isset( $par ) ) {
+ if ( $par !== null ) {
$target = $par;
} else {
- $target = $wgRequest->getVal( 'target' );
+ $target = $request->getVal( 'target' );
}
if ( !strlen( $target ) ) {
- $wgOut->addHTML( $this->getForm( '' ) );
+ $out->addHTML( $this->getForm( '' ) );
return;
}
- $options['limit'] = $wgRequest->getInt( 'limit', 50 );
+ $options['limit'] = $request->getInt( 'limit', $wgQueryPageDefaultLimit );
$options['target'] = $target;
- $nt = Title::makeTitleSafe( NS_USER, $target );
- if ( !$nt ) {
- $wgOut->addHTML( $this->getForm( '' ) );
+ $userObj = User::newFromName( $target, false );
+ if ( !$userObj ) {
+ $out->addHTML( $this->getForm( '' ) );
return;
}
- $id = User::idFromName( $nt->getText() );
- $target = $nt->getText();
- $wgOut->setSubtitle( $this->getSubTitle( $nt, $id ) );
+ $target = $userObj->getName();
+ $out->addSubtitle( $this->getSubTitle( $userObj ) );
- if ( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) {
+ if ( ( $ns = $request->getVal( 'namespace', null ) ) !== null && $ns !== '' ) {
$options['namespace'] = intval( $ns );
} else {
$options['namespace'] = '';
}
- $wgOut->addHTML( $this->getForm( $options ) );
+ $out->addHTML( $this->getForm( $options ) );
- $pager = new DeletedContribsPager( $target, $options['namespace'] );
+ $pager = new DeletedContribsPager( $this->getContext(), $target, $options['namespace'] );
if ( !$pager->getNumRows() ) {
- $wgOut->addWikiMsg( 'nocontribs' );
+ $out->addWikiMsg( 'nocontribs' );
return;
}
# Show a message about slave lag, if applicable
$lag = wfGetLB()->safeGetLag( $pager->getDatabase() );
if( $lag > 0 )
- $wgOut->showLagWarning( $lag );
+ $out->showLagWarning( $lag );
- $wgOut->addHTML(
+ $out->addHTML(
'<p>' . $pager->getNavigationBar() . '</p>' .
$pager->getBody() .
'<p>' . $pager->getNavigationBar() . '</p>' );
@@ -341,44 +328,40 @@ class DeletedContributionsPage extends SpecialPage {
? 'sp-contributions-footer-anon'
: 'sp-contributions-footer';
- if( !wfMessage( $message )->isDisabled() ) {
- $wgOut->wrapWikiMsg( "<div class='mw-contributions-footer'>\n$1\n</div>", array( $message, $target ) );
+ if( !$this->msg( $message )->isDisabled() ) {
+ $out->wrapWikiMsg( "<div class='mw-contributions-footer'>\n$1\n</div>", array( $message, $target ) );
}
}
}
/**
* Generates the subheading with links
- * @param $nt Title object for the target
- * @param $id Integer: User ID for the target
+ * @param $userObj User object for the target
* @return String: appropriately-escaped HTML to be output literally
* @todo FIXME: Almost the same as contributionsSub in SpecialContributions.php. Could be combined.
*/
- function getSubTitle( $nt, $id ) {
- global $wgLang, $wgUser, $wgOut;
-
- $sk = $this->getSkin();
-
- if ( $id === null ) {
- $user = htmlspecialchars( $nt->getText() );
+ function getSubTitle( $userObj ) {
+ if ( $userObj->isAnon() ) {
+ $user = htmlspecialchars( $userObj->getName() );
} else {
- $user = $sk->link( $nt, htmlspecialchars( $nt->getText() ) );
+ $user = Linker::link( $userObj->getUserPage(), htmlspecialchars( $userObj->getName() ) );
}
- $userObj = User::newFromName( $nt->getText(), /* check for username validity not needed */ false );
+ $nt = $userObj->getUserPage();
+ $id = $userObj->getID();
$talk = $nt->getTalkPage();
if( $talk ) {
# Talk page link
- $tools[] = $sk->link( $talk, wfMsgHtml( 'sp-contributions-talk' ) );
+ $tools[] = Linker::link( $talk, $this->msg( 'sp-contributions-talk' )->escaped() );
if( ( $id !== null ) || ( $id === null && IP::isIPAddress( $nt->getText() ) ) ) {
- if( $wgUser->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
+ if( $this->getUser()->isAllowed( 'block' ) ) { # Block / Change block / Unblock links
if ( $userObj->isBlocked() ) {
- $tools[] = $sk->linkKnown( # Change block link
+ $tools[] = Linker::linkKnown( # Change block link
SpecialPage::getTitleFor( 'Block', $nt->getDBkey() ),
- wfMsgHtml( 'change-blocklink' )
+ $this->msg( 'change-blocklink' )->escaped()
);
- $tools[] = $sk->linkKnown( # Unblock link
+ $tools[] = Linker::linkKnown( # Unblock link
SpecialPage::getTitleFor( 'BlockList' ),
- wfMsgHtml( 'unblocklink' ),
+ $this->msg( 'unblocklink' )->escaped(),
array(),
array(
'action' => 'unblock',
@@ -387,16 +370,16 @@ class DeletedContributionsPage extends SpecialPage {
);
}
else { # User is not blocked
- $tools[] = $sk->linkKnown( # Block link
+ $tools[] = Linker::linkKnown( # Block link
SpecialPage::getTitleFor( 'Block', $nt->getDBkey() ),
- wfMsgHtml( 'blocklink' )
+ $this->msg( 'blocklink' )->escaped()
);
}
}
# Block log link
- $tools[] = $sk->linkKnown(
+ $tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Log' ),
- wfMsgHtml( 'sp-contributions-blocklog' ),
+ $this->msg( 'sp-contributions-blocklog' )->escaped(),
array(),
array(
'type' => 'block',
@@ -405,37 +388,39 @@ class DeletedContributionsPage extends SpecialPage {
);
}
# Other logs link
- $tools[] = $sk->linkKnown(
+ $tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Log' ),
- wfMsgHtml( 'sp-contributions-logs' ),
+ $this->msg( 'sp-contributions-logs' )->escaped(),
array(),
array( 'user' => $nt->getText() )
);
# Link to contributions
- $tools[] = $sk->linkKnown(
+ $tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Contributions', $nt->getDBkey() ),
- wfMsgHtml( 'sp-deletedcontributions-contribs' )
+ $this->msg( 'sp-deletedcontributions-contribs' )->escaped()
);
# Add a link to change user rights for privileged users
$userrightsPage = new UserrightsPage();
+ $userrightsPage->setContext( $this->getContext() );
if( $id !== null && $userrightsPage->userCanChangeRights( User::newFromId( $id ) ) ) {
- $tools[] = $sk->linkKnown(
+ $tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Userrights', $nt->getDBkey() ),
- wfMsgHtml( 'sp-contributions-userrights' )
+ $this->msg( 'sp-contributions-userrights' )->escaped()
);
}
wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) );
- $links = $wgLang->pipeList( $tools );
+ $links = $this->getLanguage()->pipeList( $tools );
// Show a note if the user is blocked and display the last block log entry.
if ( $userObj->isBlocked() ) {
+ $out = $this->getOutput(); // LogEventsList::showLogExtract() wants the first parameter by ref
LogEventsList::showLogExtract(
- $wgOut,
+ $out,
'block',
- $nt->getPrefixedText(),
+ $nt,
'',
array(
'lim' => 1,
@@ -444,7 +429,7 @@ class DeletedContributionsPage extends SpecialPage {
'sp-contributions-blocked-notice',
$nt->getText() # Support GENDER in 'sp-contributions-blocked-notice'
),
- 'offset' => '' # don't use $wgRequest parameter offset
+ 'offset' => '' # don't use $this->getRequest() parameter offset
)
);
}
@@ -454,10 +439,11 @@ 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' ) ) {
- return wfMsgHtml( 'contribsub2', $user, $links );
+ $oldMsg = $this->msg( 'contribsub' );
+ if ( $oldMsg->exists() ) {
+ return $oldMsg->rawParams( "$user ($links)" );
} else {
- return wfMsgHtml( 'contribsub', "$user ($links)" );
+ return $this->msg( 'contribsub2' )->rawParams( $user, $links );
}
}
@@ -468,7 +454,7 @@ class DeletedContributionsPage extends SpecialPage {
function getForm( $options ) {
global $wgScript;
- $options['title'] = SpecialPage::getTitleFor( 'DeletedContributions' )->getPrefixedText();
+ $options['title'] = $this->getTitle()->getPrefixedText();
if ( !isset( $options['target'] ) ) {
$options['target'] = '';
} else {
@@ -497,15 +483,15 @@ class DeletedContributionsPage extends SpecialPage {
}
$f .= Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) .
- Xml::tags( 'label', array( 'for' => 'target' ), wfMsgExt( 'sp-contributions-username', 'parseinline' ) ) . ' ' .
+ Xml::element( 'legend', array(), $this->msg( 'sp-contributions-search' )->text() ) .
+ Xml::tags( 'label', array( 'for' => 'target' ), $this->msg( 'sp-contributions-username' )->parse() ) . ' ' .
Html::input( 'target', $options['target'], 'text', array(
'size' => '20',
'required' => ''
) + ( $options['target'] ? array() : array( 'autofocus' ) ) ) . ' '.
- Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' .
+ Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) . ' ' .
Xml::namespaceSelector( $options['namespace'], '' ) . ' ' .
- Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) .
+ Xml::submitButton( $this->msg( 'sp-contributions-submit' )->text() ) .
Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' );
return $f;
diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php
index 431dfe76..2b05fb6b 100644
--- a/includes/specials/SpecialDisambiguations.php
+++ b/includes/specials/SpecialDisambiguations.php
@@ -36,12 +36,12 @@ class DisambiguationsPage extends PageQueryPage {
function isSyndicated() { return false; }
function getPageHeader() {
- return wfMsgExt( 'disambiguations-text', array( 'parse' ) );
+ return $this->msg( 'disambiguations-text' )->parseAsBlock();
}
function getQueryInfo() {
$dbr = wfGetDB( DB_SLAVE );
- $dMsgText = wfMsgForContent( 'disambiguationspage' );
+ $dMsgText = $this->msg( 'disambiguationspage' )->inContentLanguage()->text();
$linkBatch = new LinkBatch;
# If the text can be treated as a title, use it verbatim.
@@ -122,16 +122,14 @@ class DisambiguationsPage extends PageQueryPage {
}
function formatResult( $skin, $result ) {
- 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' ) ) ,
+ $from = Linker::link( $title );
+ $edit = Linker::link( $title, $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->escaped(),
array(), array( 'redirect' => 'no', 'action' => 'edit' ) );
- $arr = $wgLang->getArrow();
- $to = $skin->link( $dp );
+ $arr = $this->getLanguage()->getArrow();
+ $to = Linker::link( $dp );
return "$from $edit $arr $to";
}
diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php
index ec899d8a..a6df66f6 100644
--- a/includes/specials/SpecialDoubleRedirects.php
+++ b/includes/specials/SpecialDoubleRedirects.php
@@ -38,7 +38,7 @@ class DoubleRedirectsPage extends PageQueryPage {
function sortDescending() { return false; }
function getPageHeader() {
- return wfMsgExt( 'doubleredirectstext', array( 'parse' ) );
+ return $this->msg( 'doubleredirectstext' )->parseAsBlock();
}
function reallyGetQueryInfo( $namespace = null, $title = null ) {
@@ -49,6 +49,7 @@ class DoubleRedirectsPage extends PageQueryPage {
'pb' => 'page', 'pc' => 'page' ),
'fields' => array ( 'pa.page_namespace AS namespace',
'pa.page_title AS title',
+ 'pa.page_title AS value',
'pb.page_namespace AS nsb',
'pb.page_title AS tb',
'pc.page_namespace AS nsc',
@@ -76,8 +77,6 @@ class DoubleRedirectsPage extends PageQueryPage {
}
function formatResult( $skin, $result ) {
- global $wgLang;
-
$titleA = Title::makeTitle( $result->namespace, $result->title );
if ( $result && !isset( $result->nsb ) ) {
@@ -91,35 +90,40 @@ class DoubleRedirectsPage extends PageQueryPage {
}
}
if ( !$result ) {
- return '<del>' . $skin->link( $titleA, null, array(), array( 'redirect' => 'no' ) ) . '</del>';
+ return '<del>' . Linker::link( $titleA, null, array(), array( 'redirect' => 'no' ) ) . '</del>';
}
$titleB = Title::makeTitle( $result->nsb, $result->tb );
$titleC = Title::makeTitle( $result->nsc, $result->tc );
- $linkA = $skin->linkKnown(
+ $linkA = Linker::linkKnown(
$titleA,
null,
array(),
array( 'redirect' => 'no' )
);
- $edit = $skin->linkKnown(
+
+ $edit = Linker::linkKnown(
$titleA,
- wfMsgExt( 'parentheses', array( 'escape' ), wfMsg( 'editlink' ) ),
+ $this->msg( 'parentheses', $this->msg( 'editlink' )->text() )->escaped(),
array(),
array(
'redirect' => 'no',
'action' => 'edit'
)
);
- $linkB = $skin->linkKnown(
+
+ $linkB = Linker::linkKnown(
$titleB,
null,
array(),
array( 'redirect' => 'no' )
);
- $linkC = $skin->linkKnown( $titleC );
- $arr = $wgLang->getArrow() . $wgLang->getDirMark();
+
+ $linkC = Linker::linkKnown( $titleC );
+
+ $lang = $this->getLanguage();
+ $arr = $lang->getArrow() . $lang->getDirMark();
return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
}
diff --git a/includes/specials/SpecialEditWatchlist.php b/includes/specials/SpecialEditWatchlist.php
index bb2ecd80..9c9689ae 100644
--- a/includes/specials/SpecialEditWatchlist.php
+++ b/includes/specials/SpecialEditWatchlist.php
@@ -20,6 +20,8 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
protected $toc;
+ private $badItems = array();
+
public function __construct(){
parent::__construct( 'EditWatchlist' );
}
@@ -30,32 +32,29 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* @param $mode int
*/
public function execute( $mode ) {
- if( wfReadOnly() ) {
- throw new ReadOnlyError;
- }
+ $this->setHeaders();
$out = $this->getOutput();
# Anons don't get a watchlist
if( $this->getUser()->isAnon() ) {
- $out->setPageTitle( wfMsg( 'watchnologin' ) );
+ $out->setPageTitle( $this->msg( 'watchnologin' ) );
$llink = Linker::linkKnown(
SpecialPage::getTitleFor( 'Userlogin' ),
- wfMsgHtml( 'loginreqlink' ),
+ $this->msg( 'loginreqlink' )->escaped(),
array(),
array( 'returnto' => $this->getTitle()->getPrefixedText() )
);
- $out->addHTML( wfMessage( 'watchlistanontext' )->rawParams( $llink )->parse() );
+ $out->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() );
return;
}
- $sub = wfMsgExt(
- 'watchlistfor2',
- array( 'parseinline', 'replaceafter' ),
- $this->getUser()->getName(),
- SpecialEditWatchlist::buildTools( null )
- );
- $out->setSubtitle( $sub );
+ $this->checkPermissions();
+
+ $this->outputHeader();
+
+ $out->addSubtitle( $this->msg( 'watchlistfor2', $this->getUser()->getName()
+ )->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
# B/C: $mode used to be waaay down the parameter list, and the first parameter
# was $wgUser
@@ -73,7 +72,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
// Pass on to the raw editor, from which it's very easy to clear.
case self::EDIT_RAW:
- $out->setPageTitle( wfMsg( 'watchlistedit-raw-title' ) );
+ $out->setPageTitle( $this->msg( 'watchlistedit-raw-title' ) );
$form = $this->getRawForm();
if( $form->show() ){
$out->addHTML( $this->successMessage );
@@ -83,7 +82,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
case self::EDIT_NORMAL:
default:
- $out->setPageTitle( wfMsg( 'watchlistedit-normal-title' ) );
+ $out->setPageTitle( $this->msg( 'watchlistedit-normal-title' ) );
$form = $this->getNormalForm();
if( $form->show() ){
$out->addHTML( $this->successMessage );
@@ -132,33 +131,34 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$this->getUser()->invalidateCache();
if( count( $toWatch ) > 0 || count( $toUnwatch ) > 0 ){
- $this->successMessage = wfMessage( 'watchlistedit-raw-done' )->parse();
+ $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse();
} else {
return false;
}
if( count( $toWatch ) > 0 ) {
- $this->successMessage .= wfMessage(
- 'watchlistedit-raw-added',
- $this->getLang()->formatNum( count( $toWatch ) )
- );
+ $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-added'
+ )->numParams( count( $toWatch ) )->parse();
$this->showTitles( $toWatch, $this->successMessage );
}
if( count( $toUnwatch ) > 0 ) {
- $this->successMessage .= wfMessage(
- 'watchlistedit-raw-removed',
- $this->getLang()->formatNum( count( $toUnwatch ) )
- );
+ $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed'
+ )->numParams( count( $toUnwatch ) )->parse();
$this->showTitles( $toUnwatch, $this->successMessage );
}
} else {
$this->clearWatchlist();
$this->getUser()->invalidateCache();
- $this->successMessage .= wfMessage(
- 'watchlistedit-raw-removed',
- $this->getLang()->formatNum( count( $current ) )
- );
+
+ if( count( $current ) > 0 ){
+ $this->successMessage = $this->msg( 'watchlistedit-raw-done' )->parse();
+ } else {
+ return false;
+ }
+
+ $this->successMessage .= ' ' . $this->msg( 'watchlistedit-raw-removed'
+ )->numParams( count( $current ) )->parse();
$this->showTitles( $current, $this->successMessage );
}
return true;
@@ -174,7 +174,7 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* @param $output String
*/
private function showTitles( $titles, &$output ) {
- $talk = wfMsgHtml( 'talkpagelinktext' );
+ $talk = $this->msg( 'talkpagelinktext' )->escaped();
// Do a batch existence check
$batch = new LinkBatch();
foreach( $titles as $title ) {
@@ -223,18 +223,21 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
if( $res->numRows() > 0 ) {
foreach ( $res as $row ) {
$title = Title::makeTitleSafe( $row->wl_namespace, $row->wl_title );
- if( $title instanceof Title && !$title->isTalkPage() )
+ if ( $this->checkTitle( $title, $row->wl_namespace, $row->wl_title )
+ && !$title->isTalkPage()
+ ) {
$list[] = $title->getPrefixedText();
+ }
}
$res->free();
}
+ $this->cleanupWatchlist();
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
+ * and return as a two-dimensional array with namespace and title.
*
* @return array
*/
@@ -243,46 +246,80 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$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( 'watchlist' ),
+ array( 'wl_namespace', 'wl_title' ),
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'
- ) )
+ array( 'ORDER BY' => 'wl_namespace, wl_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;
- }
- }
+ $lb = new LinkBatch();
+ foreach ( $res as $row ) {
+ $lb->add( $row->wl_namespace, $row->wl_title );
+ if ( !MWNamespace::isTalk( $row->wl_namespace ) ) {
+ $titles[$row->wl_namespace][$row->wl_title] = 1;
}
}
+
+ $lb->execute();
return $titles;
}
/**
+ * Validates watchlist entry
+ *
+ * @param Title $title
+ * @param int $namespace
+ * @param String $dbKey
+ * @return bool: Whether this item is valid
+ */
+ private function checkTitle( $title, $namespace, $dbKey ) {
+ if ( $title
+ && ( $title->isExternal()
+ || $title->getNamespace() < 0
+ )
+ ) {
+ $title = false; // unrecoverable
+ }
+ if ( !$title
+ || $title->getNamespace() != $namespace
+ || $title->getDBkey() != $dbKey
+ ) {
+ $this->badItems[] = array( $title, $namespace, $dbKey );
+ }
+ return (bool)$title;
+ }
+
+ /**
+ * Attempts to clean up broken items
+ */
+ private function cleanupWatchlist() {
+ if ( count( $this->badItems ) ) {
+ $dbw = wfGetDB( DB_MASTER );
+ }
+ foreach ( $this->badItems as $row ) {
+ list( $title, $namespace, $dbKey ) = $row;
+ wfDebug( "User {$this->getUser()} has broken watchlist item ns($namespace):$dbKey, "
+ . ( $title ? 'cleaning up' : 'deleting' ) . ".\n"
+ );
+
+ $dbw->delete( 'watchlist',
+ array(
+ 'wl_user' => $this->getUser()->getId(),
+ 'wl_namespace' => $namespace,
+ 'wl_title' => $dbKey,
+ ),
+ __METHOD__
+ );
+
+ // Can't just do an UPDATE instead of DELETE/INSERT due to unique index
+ if ( $title ) {
+ $this->getUser()->addWatch( $title );
+ }
+ }
+ }
+
+ /**
* Remove all titles from a user's watchlist
*/
private function clearWatchlist() {
@@ -360,8 +397,8 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
),
__METHOD__
);
- $article = new Article( $title, 0 );
- wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$article ) );
+ $page = WikiPage::factory( $title );
+ wfRunHooks( 'UnwatchArticleComplete', array( $this->getUser(), &$page ) );
}
}
}
@@ -375,10 +412,8 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
}
if( count( $removed ) > 0 ) {
- $this->successMessage = wfMessage(
- 'watchlistedit-normal-done',
- $this->getLang()->formatNum( count( $removed ) )
- );
+ $this->successMessage = $this->msg( 'watchlistedit-normal-done'
+ )->numParams( count( $removed ) )->parse();
$this->showTitles( $removed, $this->successMessage );
return true;
} else {
@@ -397,40 +432,39 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$fields = array();
$count = 0;
- $haveInvalidNamespaces = false;
foreach( $this->getWatchlistInfo() as $namespace => $pages ){
- if ( $namespace < 0 ) {
- $haveInvalidNamespaces = true;
- continue;
+ if ( $namespace >= 0 ) {
+ $fields['TitlesNs'.$namespace] = array(
+ 'class' => 'EditWatchlistCheckboxSeriesField',
+ 'options' => array(),
+ 'section' => "ns$namespace",
+ );
}
- $fields['TitlesNs'.$namespace] = array(
- 'class' => 'EditWatchlistCheckboxSeriesField',
- 'options' => array(),
- 'section' => "ns$namespace",
- );
-
- foreach( $pages as $dbkey => $redirect ){
+ foreach( array_keys( $pages ) as $dbkey ){
$title = Title::makeTitleSafe( $namespace, $dbkey );
- $text = $this->buildRemoveLine( $title, $redirect );
- $fields['TitlesNs'.$namespace]['options'][$text] = $title->getEscapedText();
- $count++;
+ if ( $this->checkTitle( $title, $namespace, $dbkey ) ) {
+ $text = $this->buildRemoveLine( $title );
+ $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();
- }
+ $this->cleanupWatchlist();
if ( count( $fields ) > 1 && $count > 30 ) {
$this->toc = Linker::tocIndent();
$tocLength = 0;
foreach( $fields as $key => $data ) {
+
+ # strip out the 'ns' prefix from the section name:
$ns = substr( $data['section'], 2 );
- $nsText = $ns == NS_MAIN
- ? wfMsgHtml( 'blanknamespace' )
+
+ $nsText = ($ns == NS_MAIN)
+ ? $this->msg( 'blanknamespace' )->escaped()
: htmlspecialchars( $wgContLang->getFormattedNsText( $ns ) );
- $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText, ++$tocLength, 1 ) . Linker::tocLineEnd();
+ $this->toc .= Linker::tocLine( "editwatchlist-{$data['section']}", $nsText,
+ $this->getLanguage()->formatNum( ++$tocLength ), 1 ) . Linker::tocLineEnd();
}
$this->toc = Linker::tocList( $this->toc );
} else {
@@ -439,9 +473,11 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
$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->setSubmitTextMsg( 'watchlistedit-normal-submit' );
+ # Used message keys: 'accesskey-watchlistedit-normal-submit', 'tooltip-watchlistedit-normal-submit'
+ $form->setSubmitTooltip('watchlistedit-normal-submit');
+ $form->setWrapperLegendMsg( 'watchlistedit-normal-legend' );
+ $form->addHeaderText( $this->msg( 'watchlistedit-normal-explain' )->parse() );
$form->setSubmitCallback( array( $this, 'submitNormal' ) );
return $form;
}
@@ -450,19 +486,19 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
* 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 ) {
+ private function buildRemoveLine( $title ) {
$link = Linker::link( $title );
- if( $redirect ) {
+ if( $title->isRedirect() ) {
+ // Linker already makes class mw-redirect, so this is redundant
$link = '<span class="watchlistredir">' . $link . '</span>';
}
- $tools[] = Linker::link( $title->getTalkPage(), wfMsgHtml( 'talkpagelinktext' ) );
+ $tools[] = Linker::link( $title->getTalkPage(), $this->msg( 'talkpagelinktext' )->escaped() );
if( $title->exists() ) {
$tools[] = Linker::linkKnown(
$title,
- wfMsgHtml( 'history_short' ),
+ $this->msg( 'history_short' )->escaped(),
array(),
array( 'action' => 'history' )
);
@@ -470,13 +506,13 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
if( $title->getNamespace() == NS_USER && !$title->isSubpage() ) {
$tools[] = Linker::linkKnown(
SpecialPage::getTitleFor( 'Contributions', $title->getText() ),
- wfMsgHtml( 'contributions' )
+ $this->msg( 'contributions' )->escaped()
);
}
- wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $redirect, $this->getSkin() ) );
+ wfRunHooks( 'WatchlistEditorBuildRemoveLine', array( &$tools, $title, $title->isRedirect(), $this->getSkin() ) );
- return $link . " (" . $this->getLang()->pipeList( $tools ) . ")";
+ return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")";
}
/**
@@ -493,11 +529,13 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
'default' => $titles,
),
);
- $form = new HTMLForm( $fields );
+ $form = new HTMLForm( $fields, $this->getContext() );
$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->setSubmitTextMsg( 'watchlistedit-raw-submit' );
+ # Used message keys: 'accesskey-watchlistedit-raw-submit', 'tooltip-watchlistedit-raw-submit'
+ $form->setSubmitTooltip('watchlistedit-raw-submit');
+ $form->setWrapperLegendMsg( 'watchlistedit-raw-legend' );
+ $form->addHeaderText( $this->msg( 'watchlistedit-raw-explain' )->parse() );
$form->setSubmitCallback( array( $this, 'submitRaw' ) );
return $form;
}
@@ -569,8 +607,8 @@ class EditWatchlistNormalHTMLForm extends HTMLForm {
public function getLegend( $namespace ){
$namespace = substr( $namespace, 2 );
return $namespace == NS_MAIN
- ? wfMsgHtml( 'blanknamespace' )
- : htmlspecialchars( $this->getContext()->getLang()->getFormattedNsText( $namespace ) );
+ ? $this->msg( 'blanknamespace' )->escaped()
+ : htmlspecialchars( $this->getContext()->getLanguage()->getFormattedNsText( $namespace ) );
}
public function getBody() {
return $this->displaySection( $this->mFieldTree, '', 'editwatchlist-' );
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
index 7c2ba570..314da727 100644
--- a/includes/specials/SpecialEmailuser.php
+++ b/includes/specials/SpecialEmailuser.php
@@ -34,14 +34,13 @@ class SpecialEmailUser extends UnlistedSpecialPage {
}
protected function getFormFields() {
- global $wgUser;
return array(
'From' => array(
'type' => 'info',
'raw' => 1,
- 'default' => $this->getSkin()->link(
- $wgUser->getUserPage(),
- htmlspecialchars( $wgUser->getName() )
+ 'default' => Linker::link(
+ $this->getUser()->getUserPage(),
+ htmlspecialchars( $this->getUser()->getName() )
),
'label-message' => 'emailfrom',
'id' => 'mw-emailuser-sender',
@@ -49,7 +48,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
'To' => array(
'type' => 'info',
'raw' => 1,
- 'default' => $this->getSkin()->link(
+ 'default' => Linker::link(
$this->mTargetObj->getUserPage(),
htmlspecialchars( $this->mTargetObj->getName() )
),
@@ -62,7 +61,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
),
'Subject' => array(
'type' => 'text',
- 'default' => wfMsgExt( 'defemailsubject', array( 'content', 'parsemag' ) ),
+ 'default' => wfMsgExt( 'defemailsubject', array( 'content', 'parsemag' ), $this->getUser()->getName() ),
'label-message' => 'emailsubject',
'maxlength' => 200,
'size' => 60,
@@ -78,59 +77,53 @@ class SpecialEmailUser extends UnlistedSpecialPage {
'CCMe' => array(
'type' => 'check',
'label-message' => 'emailccme',
- 'default' => $wgUser->getBoolOption( 'ccmeonemails' ),
+ 'default' => $this->getUser()->getBoolOption( 'ccmeonemails' ),
),
);
}
public function execute( $par ) {
- global $wgRequest, $wgOut, $wgUser;
-
$this->setHeaders();
$this->outputHeader();
- $wgOut->addModuleStyles( 'mediawiki.special' );
+ $out = $this->getOutput();
+ $out->addModuleStyles( 'mediawiki.special' );
$this->mTarget = is_null( $par )
- ? $wgRequest->getVal( 'wpTarget', $wgRequest->getVal( 'target', '' ) )
+ ? $this->getRequest()->getVal( 'wpTarget', $this->getRequest()->getVal( 'target', '' ) )
: $par;
// error out if sending user cannot do this
- $error = self::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) );
+ $error = self::getPermissionsError( $this->getUser(), $this->getRequest()->getVal( 'wpEditToken' ) );
switch ( $error ) {
case null:
# Wahey!
break;
case 'badaccess':
- $wgOut->permissionRequired( 'sendemail' );
- return;
+ throw new PermissionsError( 'sendemail' );
case 'blockedemailuser':
- $wgOut->blockedPage();
- return;
+ throw new UserBlockedError( $this->getUser()->mBlock );
case 'actionthrottledtext':
- $wgOut->rateLimited();
- return;
+ throw new ThrottledError;
case 'mailnologin':
case 'usermaildisabled':
- $wgOut->showErrorPage( $error, "{$error}text" );
- return;
+ throw new ErrorPageError( $error, "{$error}text" );
default:
# It's a hook error
list( $title, $msg, $params ) = $error;
- $wgOut->showErrorPage( $title, $msg, $params );
- return;
+ throw new ErrorPageError( $title, $msg, $params );
}
// 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 );
+ $out->wrapWikiMsg( "<p class='error'>$1</p>", $ret );
}
- $wgOut->addHTML( self::userForm( $this->mTarget ) );
+ $out->addHTML( $this->userForm( $this->mTarget ) );
return false;
}
$this->mTargetObj = $ret;
- $form = new HTMLForm( $this->getFormFields() );
+ $form = new HTMLForm( $this->getFormFields(), $this->getContext() );
$form->addPreText( wfMsgExt( 'emailpagetext', 'parseinline' ) );
$form->setSubmitText( wfMsg( 'emailsend' ) );
$form->setTitle( $this->getTitle() );
@@ -142,13 +135,13 @@ class SpecialEmailUser extends UnlistedSpecialPage {
return false;
}
- $wgOut->setPageTitle( wfMsg( 'emailpage' ) );
+ $out->setPageTitle( $this->msg( 'emailpage' ) );
$result = $form->show();
if( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
- $wgOut->setPageTitle( wfMsg( 'emailsent' ) );
- $wgOut->addWikiMsg( 'emailsenttext' );
- $wgOut->returnToMain( false, $this->mTargetObj->getUserPage() );
+ $out->setPageTitle( $this->msg( 'emailsent' ) );
+ $out->addWikiMsg( 'emailsenttext' );
+ $out->returnToMain( false, $this->mTargetObj->getUserPage() );
}
}
@@ -226,17 +219,16 @@ class SpecialEmailUser extends UnlistedSpecialPage {
* @param $name String: user name submitted.
* @return String: form asking for user name.
*/
-
- function userForm( $name ) {
- global $wgScript ;
+ protected 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";
+ 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;
}
diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php
index 50754b6a..d061389e 100644
--- a/includes/specials/SpecialExport.php
+++ b/includes/specials/SpecialExport.php
@@ -38,9 +38,9 @@ class SpecialExport extends SpecialPage {
}
public function execute( $par ) {
- global $wgOut, $wgRequest, $wgSitename, $wgExportAllowListContributors;
+ global $wgSitename, $wgExportAllowListContributors, $wgExportFromNamespaces;
global $wgExportAllowHistory, $wgExportMaxHistory, $wgExportMaxLinkDepth;
- global $wgExportFromNamespaces;
+ global $wgExportAllowAll;
$this->setHeaders();
$this->outputHeader();
@@ -48,16 +48,18 @@ class SpecialExport extends SpecialPage {
// Set some variables
$this->curonly = true;
$this->doExport = false;
- $this->templates = $wgRequest->getCheck( 'templates' );
- $this->images = $wgRequest->getCheck( 'images' ); // Doesn't do anything yet
+ $request = $this->getRequest();
+ $this->templates = $request->getCheck( 'templates' );
+ $this->images = $request->getCheck( 'images' ); // Doesn't do anything yet
$this->pageLinkDepth = $this->validateLinkDepth(
- $wgRequest->getIntOrNull( 'pagelink-depth' )
+ $request->getIntOrNull( 'pagelink-depth' )
);
$nsindex = '';
+ $exportall = false;
- if ( $wgRequest->getCheck( 'addcat' ) ) {
- $page = $wgRequest->getText( 'pages' );
- $catname = $wgRequest->getText( 'catname' );
+ if ( $request->getCheck( 'addcat' ) ) {
+ $page = $request->getText( 'pages' );
+ $catname = $request->getText( 'catname' );
if ( $catname !== '' && $catname !== null && $catname !== false ) {
$t = Title::makeTitleSafe( NS_MAIN, $catname );
@@ -74,9 +76,9 @@ class SpecialExport extends SpecialPage {
}
}
}
- elseif( $wgRequest->getCheck( 'addns' ) && $wgExportFromNamespaces ) {
- $page = $wgRequest->getText( 'pages' );
- $nsindex = $wgRequest->getText( 'nsindex', '' );
+ elseif( $request->getCheck( 'addns' ) && $wgExportFromNamespaces ) {
+ $page = $request->getText( 'pages' );
+ $nsindex = $request->getText( 'nsindex', '' );
if ( strval( $nsindex ) !== '' ) {
/**
@@ -88,10 +90,14 @@ class SpecialExport extends SpecialPage {
}
}
}
- elseif( $wgRequest->wasPosted() && $par == '' ) {
- $page = $wgRequest->getText( 'pages' );
- $this->curonly = $wgRequest->getCheck( 'curonly' );
- $rawOffset = $wgRequest->getVal( 'offset' );
+ elseif( $request->getCheck( 'exportall' ) && $wgExportAllowAll ) {
+ $this->doExport = true;
+ $exportall = true;
+ }
+ elseif( $request->wasPosted() && $par == '' ) {
+ $page = $request->getText( 'pages' );
+ $this->curonly = $request->getCheck( 'curonly' );
+ $rawOffset = $request->getVal( 'offset' );
if( $rawOffset ) {
$offset = wfTimestamp( TS_MW, $rawOffset );
@@ -99,14 +105,14 @@ class SpecialExport extends SpecialPage {
$offset = null;
}
- $limit = $wgRequest->getInt( 'limit' );
- $dir = $wgRequest->getVal( 'dir' );
+ $limit = $request->getInt( 'limit' );
+ $dir = $request->getVal( 'dir' );
$history = array(
'dir' => 'asc',
'offset' => false,
'limit' => $wgExportMaxHistory,
);
- $historyCheck = $wgRequest->getCheck( 'history' );
+ $historyCheck = $request->getCheck( 'history' );
if ( $this->curonly ) {
$history = WikiExporter::CURRENT;
@@ -127,8 +133,8 @@ class SpecialExport extends SpecialPage {
}
} else {
// Default to current-only for GET requests.
- $page = $wgRequest->getText( 'pages', $par );
- $historyCheck = $wgRequest->getCheck( 'history' );
+ $page = $request->getText( 'pages', $par );
+ $historyCheck = $request->getCheck( 'history' );
if( $historyCheck ) {
$history = WikiExporter::FULL;
@@ -146,31 +152,32 @@ class SpecialExport extends SpecialPage {
$history = WikiExporter::CURRENT;
}
- $list_authors = $wgRequest->getCheck( 'listauthors' );
+ $list_authors = $request->getCheck( 'listauthors' );
if ( !$this->curonly || !$wgExportAllowListContributors ) {
$list_authors = false ;
}
if ( $this->doExport ) {
- $wgOut->disable();
+ $this->getOutput()->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" );
+ $request->response()->header( "Content-type: application/xml; charset=utf-8" );
- if( $wgRequest->getCheck( 'wpDownload' ) ) {
+ if( $request->getCheck( 'wpDownload' ) ) {
// Provide a sane filename suggestion
$filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' );
- $wgRequest->response()->header( "Content-disposition: attachment;filename={$filename}" );
+ $request->response()->header( "Content-disposition: attachment;filename={$filename}" );
}
- $this->doExport( $page, $history, $list_authors );
+ $this->doExport( $page, $history, $list_authors, $exportall );
return;
}
- $wgOut->addWikiMsg( 'exporttext' );
+ $out = $this->getOutput();
+ $out->addWikiMsg( 'exporttext' );
$form = Xml::openElement( 'form', array( 'method' => 'post',
'action' => $this->getTitle()->getLocalUrl( 'action=submit' ) ) );
@@ -182,6 +189,15 @@ class SpecialExport extends SpecialPage {
$form .= Xml::submitButton( wfMsg( 'export-addns' ), array( 'name' => 'addns' ) ) . '<br />';
}
+ if ( $wgExportAllowAll ) {
+ $form .= Xml::checkLabel(
+ wfMsg( 'exportall' ),
+ 'exportall',
+ 'exportall',
+ $request->wasPosted() ? $request->getCheck( 'exportall' ) : false
+ ) . '<br />';
+ }
+
$form .= Xml::element( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ), $page, false );
$form .= '<br />';
@@ -190,17 +206,17 @@ class SpecialExport extends SpecialPage {
wfMsg( 'exportcuronly' ),
'curonly',
'curonly',
- $wgRequest->wasPosted() ? $wgRequest->getCheck( 'curonly' ) : true
+ $request->wasPosted() ? $request->getCheck( 'curonly' ) : true
) . '<br />';
} else {
- $wgOut->addHTML( wfMsgExt( 'exportnohistory', 'parse' ) );
+ $out->addHTML( wfMsgExt( 'exportnohistory', 'parse' ) );
}
$form .= Xml::checkLabel(
wfMsg( 'export-templates' ),
'templates',
'wpExportTemplates',
- $wgRequest->wasPosted() ? $wgRequest->getCheck( 'templates' ) : false
+ $request->wasPosted() ? $request->getCheck( 'templates' ) : false
) . '<br />';
if( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) {
@@ -212,18 +228,29 @@ class SpecialExport extends SpecialPage {
wfMsg( 'export-download' ),
'wpDownload',
'wpDownload',
- $wgRequest->wasPosted() ? $wgRequest->getCheck( 'wpDownload' ) : true
+ $request->wasPosted() ? $request->getCheck( 'wpDownload' ) : true
) . '<br />';
+ if ( $wgExportAllowListContributors ) {
+ $form .= Xml::checkLabel(
+ wfMsg( 'exportlistauthors' ),
+ 'listauthors',
+ 'listauthors',
+ $request->wasPosted() ? $request->getCheck( 'listauthors' ) : false
+ ) . '<br />';
+ }
+
$form .= Xml::submitButton( wfMsg( 'export-submit' ), Linker::tooltipAndAccesskeyAttribs( 'export' ) );
$form .= Xml::closeElement( 'form' );
- $wgOut->addHTML( $form );
+ $out->addHTML( $form );
}
+ /**
+ * @return bool
+ */
private function userCanOverrideExportDepth() {
- global $wgUser;
- return $wgUser->isAllowed( 'override-export-depth' );
+ return $this->getUser()->isAllowed( 'override-export-depth' );
}
/**
@@ -233,47 +260,55 @@ class SpecialExport extends SpecialPage {
* @param $history Mixed: one of the WikiExporter history export constants
* @param $list_authors Boolean: Whether to add distinct author list (when
* not returning full history)
+ * @param $exportall Boolean: Whether to export everything
*/
- private function doExport( $page, $history, $list_authors ) {
- $pageSet = array(); // Inverted index of all pages to look up
-
- // Split up and normalize input
- foreach( explode( "\n", $page ) as $pageName ) {
- $pageName = trim( $pageName );
- $title = Title::newFromText( $pageName );
- if( $title && $title->getInterwiki() == '' && $title->getText() !== '' ) {
- // Only record each page once!
- $pageSet[$title->getPrefixedText()] = true;
+ private function doExport( $page, $history, $list_authors, $exportall ) {
+
+ // If we are grabbing everything, enable full history and ignore the rest
+ if ( $exportall ) {
+ $history = WikiExporter::FULL;
+ } else {
+
+ $pageSet = array(); // Inverted index of all pages to look up
+
+ // Split up and normalize input
+ foreach( explode( "\n", $page ) as $pageName ) {
+ $pageName = trim( $pageName );
+ $title = Title::newFromText( $pageName );
+ if( $title && $title->getInterwiki() == '' && $title->getText() !== '' ) {
+ // Only record each page once!
+ $pageSet[$title->getPrefixedText()] = true;
+ }
}
- }
- // Set of original pages to pass on to further manipulation...
- $inputPages = array_keys( $pageSet );
+ // Set of original pages to pass on to further manipulation...
+ $inputPages = array_keys( $pageSet );
- // Look up any linked pages if asked...
- if( $this->templates ) {
- $pageSet = $this->getTemplates( $inputPages, $pageSet );
- }
- $linkDepth = $this->pageLinkDepth;
- if( $linkDepth ) {
- $pageSet = $this->getPageLinks( $inputPages, $pageSet, $linkDepth );
- }
+ // Look up any linked pages if asked...
+ if( $this->templates ) {
+ $pageSet = $this->getTemplates( $inputPages, $pageSet );
+ }
+ $linkDepth = $this->pageLinkDepth;
+ if( $linkDepth ) {
+ $pageSet = $this->getPageLinks( $inputPages, $pageSet, $linkDepth );
+ }
- /*
- // Enable this when we can do something useful exporting/importing image information. :)
- if( $this->images ) ) {
- $pageSet = $this->getImages( $inputPages, $pageSet );
- }
- */
+ /*
+ // Enable this when we can do something useful exporting/importing image information. :)
+ if( $this->images ) ) {
+ $pageSet = $this->getImages( $inputPages, $pageSet );
+ }
+ */
- $pages = array_keys( $pageSet );
+ $pages = array_keys( $pageSet );
- // Normalize titles to the same format and remove dupes, see bug 17374
- foreach( $pages as $k => $v ) {
- $pages[$k] = str_replace( " ", "_", $v );
- }
+ // Normalize titles to the same format and remove dupes, see bug 17374
+ foreach( $pages as $k => $v ) {
+ $pages[$k] = str_replace( " ", "_", $v );
+ }
- $pages = array_unique( $pages );
+ $pages = array_unique( $pages );
+ }
/* Ok, let's get to it... */
if( $history == WikiExporter::CURRENT ) {
@@ -296,7 +331,10 @@ class SpecialExport extends SpecialPage {
$exporter->list_authors = $list_authors;
$exporter->openStream();
- foreach( $pages as $page ) {
+ if ( $exportall ) {
+ $exporter->allPages();
+ } else {
+ foreach( $pages as $page ) {
/*
if( $wgExportMaxHistory && !$this->curonly ) {
$title = Title::newFromText( $page );
@@ -310,15 +348,16 @@ 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.
- }
+ $title = Title::newFromText( $page );
+ if( is_null( $title ) ) {
+ continue; #TODO: perhaps output an <error> tag or something.
+ }
+ if( !$title->userCan( 'read', $this->getUser() ) ) {
+ continue; #TODO: perhaps output an <error> tag or something.
+ }
- $exporter->pageByTitle( $title );
+ $exporter->pageByTitle( $title );
+ }
}
$exporter->closeStream();
@@ -328,6 +367,10 @@ class SpecialExport extends SpecialPage {
}
}
+ /**
+ * @param $title Title
+ * @return array
+ */
private function getPagesFromCategory( $title ) {
global $wgContLang;
@@ -356,6 +399,10 @@ class SpecialExport extends SpecialPage {
return $pages;
}
+ /**
+ * @param $nsindex int
+ * @return array
+ */
private function getPagesFromNamespace( $nsindex ) {
global $wgContLang;
@@ -399,6 +446,8 @@ class SpecialExport extends SpecialPage {
/**
* Validate link depth setting, if available.
+ * @param $depth int
+ * @return int
*/
private function validateLinkDepth( $depth ) {
global $wgExportMaxLinkDepth;
@@ -421,7 +470,13 @@ class SpecialExport extends SpecialPage {
return intval( min( $depth, 5 ) );
}
- /** Expand a list of pages to include pages linked to from that page. */
+ /**
+ * Expand a list of pages to include pages linked to from that page.
+ * @param $inputPages array
+ * @param $pageSet array
+ * @param $depth int
+ * @return array
+ */
private function getPageLinks( $inputPages, $pageSet, $depth ) {
for( ; $depth > 0; --$depth ) {
$pageSet = $this->getLinks(
diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php
index 6d621a2e..27d17f63 100644
--- a/includes/specials/SpecialFewestrevisions.php
+++ b/includes/specials/SpecialFewestrevisions.php
@@ -70,30 +70,26 @@ class FewestrevisionsPage extends QueryPage {
* @param $result Object: database row
*/
function formatResult( $skin, $result ) {
- global $wgLang, $wgContLang;
+ global $wgContLang;
$nt = Title::makeTitleSafe( $result->namespace, $result->title );
if( !$nt ) {
return '<!-- bad title -->';
}
- $text = $wgContLang->convert( $nt->getPrefixedText() );
+ $text = htmlspecialchars( $wgContLang->convert( $nt->getPrefixedText() ) );
+ $plink = Linker::linkKnown( $nt, $text );
- $plink = $skin->linkKnown(
- $nt,
- $text
- );
-
- $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->value ) );
- $redirect = $result->redirect ? ' - ' . wfMsgHtml( 'isredirect' ) : '';
- $nlink = $skin->linkKnown(
+ $nl = $this->msg( 'nrevisions' )->numParams( $result->value )->escaped();
+ $redirect = isset( $result->redirect ) && $result->redirect ?
+ ' - ' . wfMsgHtml( 'isredirect' ) : '';
+ $nlink = Linker::linkKnown(
$nt,
$nl,
array(),
array( 'action' => 'history' )
) . $redirect;
- return wfSpecialList( $plink, $nlink );
+ return $this->getLanguage()->specialList( $plink, $nlink );
}
}
diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php
index a296fd95..18d19db8 100644
--- a/includes/specials/SpecialFileDuplicateSearch.php
+++ b/includes/specials/SpecialFileDuplicateSearch.php
@@ -62,19 +62,16 @@ class FileDuplicateSearchPage extends QueryPage {
* @param $dupes Array of File objects
*/
function showList( $dupes ) {
- global $wgOut;
- $skin = $this->getSkin();
-
$html = array();
$html[] = $this->openList( 0 );
foreach ( $dupes as $dupe ) {
- $line = $this->formatResult( $skin, $dupe );
+ $line = $this->formatResult( null, $dupe );
$html[] = "<li>" . $line . "</li>";
}
$html[] = $this->closeList();
- $wgOut->addHtml( implode( "\n", $html ) );
+ $this->getOutput()->addHtml( implode( "\n", $html ) );
}
function getQueryInfo() {
@@ -91,12 +88,12 @@ class FileDuplicateSearchPage extends QueryPage {
}
function execute( $par ) {
- global $wgRequest, $wgOut, $wgLang, $wgScript;
+ global $wgScript;
$this->setHeaders();
$this->outputHeader();
- $this->filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' );
+ $this->filename = isset( $par ) ? $par : $this->getRequest()->getText( 'filename' );
$this->file = null;
$this->hash = '';
$title = Title::newFromText( $this->filename, NS_FILE );
@@ -104,14 +101,16 @@ class FileDuplicateSearchPage extends QueryPage {
$this->file = wfFindFile( $title );
}
+ $out = $this->getOutput();
+
# Create the input form
- $wgOut->addHTML(
+ $out->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::element( 'legend', null, $this->msg( 'fileduplicatesearch-legend' )->text() ) .
+ Xml::inputLabel( $this->msg( 'fileduplicatesearch-filename' )->text(), 'filename', 'filename', 50, $this->filename ) . ' ' .
+ Xml::submitButton( $this->msg( 'fileduplicatesearch-submit' )->text() ) .
Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' )
);
@@ -119,7 +118,7 @@ class FileDuplicateSearchPage extends QueryPage {
if( $this->file ) {
$this->hash = $this->file->getSha1();
} elseif( $this->filename !== '' ) {
- $wgOut->wrapWikiMsg(
+ $out->wrapWikiMsg(
"<p class='mw-fileduplicatesearch-noresults'>\n$1\n</p>",
array( 'fileduplicatesearch-noresults', wfEscapeWikiText( $this->filename ) )
);
@@ -131,14 +130,12 @@ class FileDuplicateSearchPage extends QueryPage {
if ( $img ) {
$thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
if( $thumb ) {
- $wgOut->addHTML( '<div id="mw-fileduplicatesearch-icon">' .
+ $out->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()
- ) .
+ $this->msg( 'fileduplicatesearch-info' )->numParams(
+ $img->getWidth(), $img->getHeight() )->params(
+ $this->getLanguage()->formatSize( $img->getSize() ),
+ $img->getMimeType() )->parseAsBlock() .
'</div>' );
}
}
@@ -148,15 +145,15 @@ class FileDuplicateSearchPage extends QueryPage {
# Show a short summary
if( $numRows == 1 ) {
- $wgOut->wrapWikiMsg(
+ $out->wrapWikiMsg(
"<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>",
array( 'fileduplicatesearch-result-1', wfEscapeWikiText( $this->filename ) )
);
} elseif ( $numRows ) {
- $wgOut->wrapWikiMsg(
+ $out->wrapWikiMsg(
"<p class='mw-fileduplicatesearch-result-n'>\n$1\n</p>",
array( 'fileduplicatesearch-result-n', wfEscapeWikiText( $this->filename ),
- $wgLang->formatNum( $numRows - 1 ) )
+ $this->getLanguage()->formatNum( $numRows - 1 ) )
);
}
@@ -171,18 +168,18 @@ class FileDuplicateSearchPage extends QueryPage {
* @return string
*/
function formatResult( $skin, $result ) {
- global $wgContLang, $wgLang;
+ global $wgContLang;
$nt = $result->getTitle();
$text = $wgContLang->convert( $nt->getText() );
- $plink = $skin->link(
+ $plink = Linker::link(
Title::newFromText( $nt->getPrefixedText() ),
$text
);
$userText = $result->getUser( 'text' );
- $user = $skin->link( Title::makeTitle( NS_USER, $userText ), $userText );
- $time = $wgLang->timeanddate( $result->getTimestamp() );
+ $user = Linker::link( Title::makeTitle( NS_USER, $userText ), $userText );
+ $time = $this->getLanguage()->userTimeAndDate( $result->getTimestamp(), $this->getUser() );
return "$plink . . $user . . $time";
}
diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php
index 08f90fd2..101a33f4 100644
--- a/includes/specials/SpecialFilepath.php
+++ b/includes/specials/SpecialFilepath.php
@@ -33,12 +33,11 @@ class SpecialFilepath extends SpecialPage {
}
function execute( $par ) {
- global $wgRequest, $wgOut;
-
$this->setHeaders();
$this->outputHeader();
- $file = !is_null( $par ) ? $par : $wgRequest->getText( 'file' );
+ $request = $this->getRequest();
+ $file = !is_null( $par ) ? $par : $request->getText( 'file' );
$title = Title::newFromText( $file, NS_FILE );
@@ -50,8 +49,8 @@ class SpecialFilepath extends SpecialPage {
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 );
+ $width = $request->getInt( 'width', -1 );
+ $height = $request->getInt( 'height', -1 );
// If a width is requested...
if ( $width != -1 ) {
@@ -62,9 +61,9 @@ class SpecialFilepath extends SpecialPage {
$url = $mto->getURL();
}
}
- $wgOut->redirect( $url );
+ $this->getOutput()->redirect( $url );
} else {
- $wgOut->setStatusCode( 404 );
+ $this->getOutput()->setStatusCode( 404 );
$this->showForm( $title );
}
}
@@ -74,9 +73,9 @@ class SpecialFilepath extends SpecialPage {
* @param $title Title
*/
function showForm( $title ) {
- global $wgOut, $wgScript;
+ global $wgScript;
- $wgOut->addHTML(
+ $this->getOutput()->addHTML(
Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'specialfilepath' ) ) .
Html::openElement( 'fieldset' ) .
Html::element( 'legend', null, wfMsg( 'filepath' ) ) .
diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php
index fc904a23..a2380fbe 100644
--- a/includes/specials/SpecialImport.php
+++ b/includes/specials/SpecialImport.php
@@ -52,18 +52,12 @@ class SpecialImport extends SpecialPage {
* Execute
*/
function execute( $par ) {
- global $wgRequest, $wgUser, $wgOut;
-
$this->setHeaders();
$this->outputHeader();
- if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- }
-
- if( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) {
- return $wgOut->permissionRequired( 'import' );
+ $user = $this->getUser();
+ if ( !$user->isAllowedAny( 'import', 'importupload' ) ) {
+ throw new PermissionsError( 'import' );
}
# @todo Allow Title::getUserPermissionsErrors() to take an array
@@ -71,21 +65,23 @@ class SpecialImport extends SpecialPage {
# getUserPermissionsErrors() might actually be used for, hence the 'ns-specialprotected'
$errors = wfMergeErrorArrays(
$this->getTitle()->getUserPermissionsErrors(
- 'import', $wgUser, true,
+ 'import', $user, true,
array( 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' )
),
$this->getTitle()->getUserPermissionsErrors(
- 'importupload', $wgUser, true,
+ 'importupload', $user, true,
array( 'ns-specialprotected', 'badaccess-group0', 'badaccess-groups' )
)
);
- if( $errors ){
- $wgOut->showPermissionsErrorPage( $errors );
- return;
+ if ( $errors ) {
+ throw new PermissionsError( 'import', $errors );
}
- if ( $wgRequest->wasPosted() && $wgRequest->getVal( 'action' ) == 'submit' ) {
+ $this->checkReadOnly();
+
+ $request = $this->getRequest();
+ if ( $request->wasPosted() && $request->getVal( 'action' ) == 'submit' ) {
$this->doImport();
}
$this->showForm();
@@ -95,34 +91,37 @@ class SpecialImport extends SpecialPage {
* Do the actual import
*/
private function doImport() {
- global $wgOut, $wgRequest, $wgUser, $wgImportSources, $wgExportMaxLinkDepth;
+ global $wgImportSources, $wgExportMaxLinkDepth;
+
$isUpload = false;
- $this->namespace = $wgRequest->getIntOrNull( 'namespace' );
- $sourceName = $wgRequest->getVal( "source" );
+ $request = $this->getRequest();
+ $this->namespace = $request->getIntOrNull( 'namespace' );
+ $sourceName = $request->getVal( "source" );
- $this->logcomment = $wgRequest->getText( 'log-comment' );
- $this->pageLinkDepth = $wgExportMaxLinkDepth == 0 ? 0 : $wgRequest->getIntOrNull( 'pagelink-depth' );
+ $this->logcomment = $request->getText( 'log-comment' );
+ $this->pageLinkDepth = $wgExportMaxLinkDepth == 0 ? 0 : $request->getIntOrNull( 'pagelink-depth' );
- if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'editToken' ) ) ) {
+ $user = $this->getUser();
+ if ( !$user->matchEditToken( $request->getVal( 'editToken' ) ) ) {
$source = Status::newFatal( 'import-token-mismatch' );
} elseif ( $sourceName == 'upload' ) {
$isUpload = true;
- if( $wgUser->isAllowed( 'importupload' ) ) {
+ if( $user->isAllowed( 'importupload' ) ) {
$source = ImportStreamSource::newFromUpload( "xmlimport" );
} else {
- return $wgOut->permissionRequired( 'importupload' );
+ throw new PermissionsError( 'importupload' );
}
} elseif ( $sourceName == "interwiki" ) {
- if( !$wgUser->isAllowed( 'import' ) ){
- return $wgOut->permissionRequired( 'import' );
+ if( !$user->isAllowed( 'import' ) ){
+ throw new PermissionsError( 'import' );
}
- $this->interwiki = $wgRequest->getVal( 'interwiki' );
+ $this->interwiki = $request->getVal( 'interwiki' );
if ( !in_array( $this->interwiki, $wgImportSources ) ) {
$source = Status::newFatal( "import-invalid-interwiki" );
} else {
- $this->history = $wgRequest->getCheck( 'interwikiHistory' );
- $this->frompage = $wgRequest->getText( "frompage" );
- $this->includeTemplates = $wgRequest->getCheck( 'interwikiTemplates' );
+ $this->history = $request->getCheck( 'interwikiHistory' );
+ $this->frompage = $request->getText( "frompage" );
+ $this->includeTemplates = $request->getCheck( 'interwikiTemplates' );
$source = ImportStreamSource::newFromInterwiki(
$this->interwiki,
$this->frompage,
@@ -134,16 +133,18 @@ class SpecialImport extends SpecialPage {
$source = Status::newFatal( "importunknownsource" );
}
+ $out = $this->getOutput();
if( !$source->isGood() ) {
- $wgOut->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $source->getWikiText() ) );
+ $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $source->getWikiText() ) );
} else {
- $wgOut->addWikiMsg( "importstart" );
+ $out->addWikiMsg( "importstart" );
$importer = new WikiImporter( $source->value );
if( !is_null( $this->namespace ) ) {
$importer->setTargetNamespace( $this->namespace );
}
$reporter = new ImportReporter( $importer, $isUpload, $this->interwiki , $this->logcomment);
+ $reporter->setContext( $this->getContext() );
$exception = false;
$reporter->open();
@@ -156,26 +157,28 @@ class SpecialImport extends SpecialPage {
if ( $exception ) {
# No source or XML parse error
- $wgOut->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $exception->getMessage() ) );
+ $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $exception->getMessage() ) );
} elseif( !$result->isGood() ) {
# Zero revisions
- $wgOut->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $result->getWikiText() ) );
+ $out->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $result->getWikiText() ) );
} else {
# Success!
- $wgOut->addWikiMsg( 'importsuccess' );
+ $out->addWikiMsg( 'importsuccess' );
}
- $wgOut->addHTML( '<hr />' );
+ $out->addHTML( '<hr />' );
}
}
private function showForm() {
- global $wgUser, $wgOut, $wgImportSources, $wgExportMaxLinkDepth;
+ global $wgImportSources, $wgExportMaxLinkDepth;
$action = $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) );
+ $user = $this->getUser();
+ $out = $this->getOutput();
- if( $wgUser->isAllowed( 'importupload' ) ) {
- $wgOut->addWikiMsg( "importtext" );
- $wgOut->addHTML(
+ if( $user->isAllowed( 'importupload' ) ) {
+ $out->addWikiMsg( "importtext" );
+ $out->addHTML(
Xml::fieldset( wfMsg( 'import-upload' ) ).
Xml::openElement( 'form', array( 'enctype' => 'multipart/form-data', 'method' => 'post',
'action' => $action, 'id' => 'mw-import-upload-form' ) ) .
@@ -207,17 +210,17 @@ class SpecialImport extends SpecialPage {
"</td>
</tr>" .
Xml::closeElement( 'table' ).
- Html::hidden( 'editToken', $wgUser->editToken() ) .
+ Html::hidden( 'editToken', $user->getEditToken() ) .
Xml::closeElement( 'form' ) .
Xml::closeElement( 'fieldset' )
);
} else {
if( empty( $wgImportSources ) ) {
- $wgOut->addWikiMsg( 'importnosources' );
+ $out->addWikiMsg( 'importnosources' );
}
}
- if( $wgUser->isAllowed( 'import' ) && !empty( $wgImportSources ) ) {
+ if( $user->isAllowed( 'import' ) && !empty( $wgImportSources ) ) {
# Show input field for import depth only if $wgExportMaxLinkDepth > 0
$importDepth = '';
if( $wgExportMaxLinkDepth > 0 ) {
@@ -231,13 +234,13 @@ class SpecialImport extends SpecialPage {
</tr>";
}
- $wgOut->addHTML(
+ $out->addHTML(
Xml::fieldset( wfMsg( 'importinterwiki' ) ) .
Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'mw-import-interwiki-form' ) ) .
wfMsgExt( 'import-interwiki-text', array( 'parse' ) ) .
Html::hidden( 'action', 'submit' ) .
Html::hidden( 'source', 'interwiki' ) .
- Html::hidden( 'editToken', $wgUser->editToken() ) .
+ Html::hidden( 'editToken', $user->getEditToken() ) .
Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) .
"<tr>
<td class='mw-label'>" .
@@ -248,10 +251,10 @@ class SpecialImport extends SpecialPage {
);
foreach( $wgImportSources as $prefix ) {
$selected = ( $this->interwiki === $prefix ) ? ' selected="selected"' : '';
- $wgOut->addHTML( Xml::option( $prefix, $prefix, $selected ) );
+ $out->addHTML( Xml::option( $prefix, $prefix, $selected ) );
}
- $wgOut->addHTML(
+ $out->addHTML(
Xml::closeElement( 'select' ) .
Xml::input( 'frompage', 50, $this->frompage ) .
"</td>
@@ -307,7 +310,7 @@ class SpecialImport extends SpecialPage {
* Reporting callback
* @ingroup SpecialPage
*/
-class ImportReporter {
+class ImportReporter extends ContextSource {
private $reason=false;
private $mOriginalLogCallback = null;
private $mOriginalPageOutCallback = null;
@@ -318,6 +321,7 @@ class ImportReporter {
$importer->setPageOutCallback( array( $this, 'reportPage' ) );
$this->mOriginalLogCallback =
$importer->setLogItemCallback( array( $this, 'reportLogItem' ) );
+ $importer->setNoticeCallback( array( $this, 'reportNotice' ) );
$this->mPageCount = 0;
$this->mIsUpload = $upload;
$this->mInterwiki = $interwiki;
@@ -325,8 +329,11 @@ class ImportReporter {
}
function open() {
- global $wgOut;
- $wgOut->addHTML( "<ul>\n" );
+ $this->getOutput()->addHTML( "<ul>\n" );
+ }
+
+ function reportNotice( $msg, array $params ) {
+ $this->getOutput()->addHTML( Html::element( 'li', array(), $this->msg( $msg, $params )->text() ) );
}
function reportLogItem( /* ... */ ) {
@@ -345,18 +352,23 @@ class ImportReporter {
* @return void
*/
function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
- global $wgOut, $wgUser, $wgLang, $wgContLang;
+ global $wgContLang;
$args = func_get_args();
call_user_func_array( $this->mOriginalPageOutCallback, $args );
+ if ( $title === null ) {
+ # Invalid or non-importable title; a notice is already displayed
+ return;
+ }
+
$this->mPageCount++;
- $localCount = $wgLang->formatNum( $successCount );
+ $localCount = $this->getLanguage()->formatNum( $successCount );
$contentCount = $wgContLang->formatNum( $successCount );
if( $successCount > 0 ) {
- $wgOut->addHTML( "<li>" . Linker::linkKnown( $title ) . " " .
+ $this->getOutput()->addHTML( "<li>" . Linker::linkKnown( $title ) . " " .
wfMsgExt( 'import-revision-count', array( 'parsemag', 'escape' ), $localCount ) .
"</li>\n"
);
@@ -384,29 +396,30 @@ class ImportReporter {
$dbw = wfGetDB( DB_MASTER );
$latest = $title->getLatestRevID();
$nullRevision = Revision::newNullRevision( $dbw, $title->getArticleId(), $comment, true );
- $nullRevision->insertOn( $dbw );
- $article = new Article( $title );
- # Update page record
- $article->updateRevisionOn( $dbw, $nullRevision );
- wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
+ if (!is_null($nullRevision)) {
+ $nullRevision->insertOn( $dbw );
+ $page = WikiPage::factory( $title );
+ # Update page record
+ $page->updateRevisionOn( $dbw, $nullRevision );
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $page, $nullRevision, $latest, $this->getUser() ) );
+ }
} else {
- $wgOut->addHTML( "<li>" . Linker::linkKnown( $title ) . " " .
+ $this->getOutput()->addHTML( "<li>" . Linker::linkKnown( $title ) . " " .
wfMsgHtml( 'import-nonewrevisions' ) . "</li>\n" );
}
}
function close() {
- global $wgOut, $wgLang;
-
+ $out = $this->getOutput();
if ( $this->mLogItemCount > 0 ) {
$msg = wfMsgExt( 'imported-log-entries', 'parseinline',
- $wgLang->formatNum( $this->mLogItemCount ) );
- $wgOut->addHTML( Xml::tags( 'li', null, $msg ) );
+ $this->getLanguage()->formatNum( $this->mLogItemCount ) );
+ $out->addHTML( Xml::tags( 'li', null, $msg ) );
} elseif( $this->mPageCount == 0 && $this->mLogItemCount == 0 ) {
- $wgOut->addHTML( "</ul>\n" );
+ $out->addHTML( "</ul>\n" );
return Status::newFatal( 'importnopages' );
}
- $wgOut->addHTML( "</ul>\n" );
+ $out->addHTML( "</ul>\n" );
return Status::newGood( $this->mPageCount );
}
diff --git a/includes/specials/SpecialJavaScriptTest.php b/includes/specials/SpecialJavaScriptTest.php
new file mode 100644
index 00000000..d7e1655f
--- /dev/null
+++ b/includes/specials/SpecialJavaScriptTest.php
@@ -0,0 +1,142 @@
+<?php
+
+class SpecialJavaScriptTest extends SpecialPage {
+
+ /**
+ * @var $frameworks Array: Mapping of framework ids and their initilizer methods
+ * in this class. If a framework is requested but not in this array,
+ * the 'unknownframework' error is served.
+ */
+ static $frameworks = array(
+ 'qunit' => 'initQUnitTesting',
+ );
+
+ public function __construct() {
+ parent::__construct( 'JavaScriptTest' );
+ }
+
+ public function execute( $par ) {
+ global $wgEnableJavaScriptTest;
+
+ $out = $this->getOutput();
+
+ $this->setHeaders();
+ $out->disallowUserJs();
+
+ // Abort early if we're disabled
+ if ( $wgEnableJavaScriptTest !== true ) {
+ $out->addWikiMsg( 'javascripttest-disabled' );
+ return;
+ }
+
+ $out->addModules( 'mediawiki.special.javaScriptTest' );
+
+ // Determine framework
+ $pars = explode( '/', $par );
+ $framework = strtolower( $pars[0] );
+
+ // No framework specified
+ if ( $par == '' ) {
+ $out->setPagetitle( wfMsgHtml( 'javascripttest' ) );
+ $summary = $this->wrapSummaryHtml(
+ wfMsgHtml( 'javascripttest-pagetext-noframework' ) . $this->getFrameworkListHtml(),
+ 'noframework'
+ );
+ $out->addHtml( $summary );
+
+ // Matched! Display proper title and initialize the framework
+ } elseif ( isset( self::$frameworks[$framework] ) ) {
+ $out->setPagetitle( wfMsgHtml( 'javascripttest-title', wfMsgHtml( "javascripttest-$framework-name" ) ) );
+ $out->setSubtitle(
+ wfMessage( 'javascripttest-backlink' )->rawParams( Linker::linkKnown( $this->getTitle() ) )->escaped()
+ );
+ $this->{self::$frameworks[$framework]}();
+
+ // Framework not found, display error
+ } else {
+ $out->setPagetitle( wfMsgHtml( 'javascripttest' ) );
+ $summary = $this->wrapSummaryHtml( '<p class="error">'
+ . wfMsgHtml( 'javascripttest-pagetext-unknownframework', $par )
+ . '</p>'
+ . $this->getFrameworkListHtml(),
+ 'unknownframework'
+ );
+ $out->addHtml( $summary );
+ }
+ }
+
+ /**
+ * Get a list of frameworks (including introduction paragraph and links to the framework run pages)
+ * @return String: HTML
+ */
+ private function getFrameworkListHtml() {
+ $list = '<ul>';
+ foreach( self::$frameworks as $framework => $initFn ) {
+ $list .= Html::rawElement(
+ 'li',
+ array(),
+ Linker::link( $this->getTitle( $framework ), wfMsgHtml( "javascripttest-$framework-name" ) )
+ );
+ }
+ $list .= '</ul>';
+ $msg = wfMessage( 'javascripttest-pagetext-frameworks' )->rawParams( $list )->parseAsBlock();
+
+ return $msg;
+ }
+
+ /**
+ * Function to wrap the summary.
+ * It must be given a valid state as a second parameter or an exception will
+ * be thrown.
+ * @param $html String: The raw HTML.
+ * @param $state String: State, one of 'noframework', 'unknownframework' or 'frameworkfound'
+ */
+ private function wrapSummaryHtml( $html, $state ) {
+ $validStates = array( 'noframework', 'unknownframework', 'frameworkfound' );
+ if( !in_array( $state, $validStates ) ) {
+ throw new MWException( __METHOD__
+ . ' given an invalid state. Must be one of "'
+ . join( '", "', $validStates) . '".'
+ );
+ }
+ return "<div id=\"mw-javascripttest-summary\" class=\"mw-javascripttest-$state\">$html</div>";
+ }
+
+ /**
+ * Initialize the page for QUnit.
+ */
+ private function initQUnitTesting() {
+ global $wgJavaScriptTestConfig, $wgLang;
+
+ $out = $this->getOutput();
+
+ $out->addModules( 'mediawiki.tests.qunit.testrunner' );
+ $qunitTestModules = $out->getResourceLoader()->getTestModuleNames( 'qunit' );
+ $out->addModules( $qunitTestModules );
+
+ $summary = wfMessage( 'javascripttest-qunit-intro' )
+ ->params( $wgJavaScriptTestConfig['qunit']['documentation'] )
+ ->parseAsBlock();
+ $header = wfMessage( 'javascripttest-qunit-heading' )->escaped();
+ $userDir = $wgLang->getDir();
+
+ $baseHtml = <<<HTML
+<div class="mw-content-ltr">
+<div id="qunit-header"><span dir="$userDir">$header</span></div>
+<div id="qunit-banner"></div>
+<div id="qunit-testrunner-toolbar"></div>
+<div id="qunit-userAgent"></div>
+<ol id="qunit-tests"></ol>
+<div id="qunit-fixture">test markup, will be hidden</div>
+</div>
+HTML;
+ $out->addHtml( $this->wrapSummaryHtml( $summary, 'frameworkfound' ) . $baseHtml );
+
+ }
+
+ public function isListed(){
+ global $wgEnableJavaScriptTest;
+ return $wgEnableJavaScriptTest === true;
+ }
+
+}
diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php
index 53db6f87..d3ab2f04 100644
--- a/includes/specials/SpecialLinkSearch.php
+++ b/includes/specials/SpecialLinkSearch.php
@@ -56,7 +56,6 @@ class LinkSearchPage extends QueryPage {
$namespace = $request->getIntorNull( 'namespace', null );
$protocols_list = array();
-
foreach( $wgUrlProtocols as $prot ) {
if ( $prot !== '//' ) {
$protocols_list[] = $prot;
@@ -85,8 +84,7 @@ class LinkSearchPage extends QueryPage {
$protocol = '';
}
- $out->addWikiMsg( 'linksearch-text', '<nowiki>' . $this->getLang()->commaList( $wgUrlProtocols ) . '</nowiki>' );
-
+ $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', $this->getTitle()->getPrefixedDbKey() ) .
'<fieldset>' .
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
index 427de167..b5754991 100644
--- a/includes/specials/SpecialListfiles.php
+++ b/includes/specials/SpecialListfiles.php
@@ -28,7 +28,6 @@ class SpecialListFiles extends IncludableSpecialPage {
}
public function execute( $par ){
- global $wgOut, $wgRequest;
$this->setHeaders();
$this->outputHeader();
@@ -36,11 +35,11 @@ class SpecialListFiles extends IncludableSpecialPage {
$userName = $par;
$search = '';
} else {
- $userName = $wgRequest->getText( 'user', $par );
- $search = $wgRequest->getText( 'ilsearch', '' );
+ $userName = $this->getRequest()->getText( 'user', $par );
+ $search = $this->getRequest()->getText( 'ilsearch', '' );
}
- $pager = new ImageListPager( $userName, $search, $this->including() );
+ $pager = new ImageListPager( $this->getContext(), $userName, $search, $this->including() );
if ( $this->including() ) {
$html = $pager->getBody();
@@ -50,7 +49,7 @@ class SpecialListFiles extends IncludableSpecialPage {
$nav = $pager->getNavigationBar();
$html = "$form<br />\n$body<br />\n$nav";
}
- $wgOut->addHTML( $html );
+ $this->getOutput()->addHTML( $html );
}
}
@@ -64,8 +63,8 @@ class ImageListPager extends TablePager {
var $mSearch = '';
var $mIncluding = false;
- function __construct( $userName = null, $search = '', $including = false ) {
- global $wgRequest, $wgMiserMode;
+ function __construct( IContextSource $context, $userName = null, $search = '', $including = false ) {
+ global $wgMiserMode;
$this->mIncluding = $including;
@@ -89,7 +88,7 @@ class ImageListPager extends TablePager {
}
if ( !$including ) {
- if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) {
+ if ( $context->getRequest()->getText( 'sort', 'img_date' ) == 'img_date' ) {
$this->mDefaultDirection = true;
} else {
$this->mDefaultDirection = false;
@@ -98,11 +97,7 @@ class ImageListPager extends TablePager {
$this->mDefaultDirection = true;
}
- parent::__construct();
- }
-
- function getTitle() {
- return SpecialPage::getTitleFor( 'Listfiles' );
+ parent::__construct( $context );
}
/**
@@ -197,14 +192,13 @@ class ImageListPager extends TablePager {
}
function formatValue( $field, $value ) {
- global $wgLang;
switch ( $field ) {
case 'thumb':
$file = wfLocalFile( $value );
$thumb = $file->transform( array( 'width' => 180, 'height' => 360 ) );
return $thumb->toHtml( array( 'desc-link' => true ) );
case 'img_timestamp':
- return htmlspecialchars( $wgLang->timeanddate( $value, true ) );
+ return htmlspecialchars( $this->getLanguage()->timeanddate( $value, true ) );
case 'img_name':
static $imgfile = null;
if ( $imgfile === null ) $imgfile = wfMsg( 'imgfile' );
@@ -212,7 +206,7 @@ class ImageListPager extends TablePager {
// Weird files can maybe exist? Bug 22227
$filePage = Title::makeTitleSafe( NS_FILE, $value );
if( $filePage ) {
- $link = $this->getSkin()->linkKnown( $filePage, htmlspecialchars( $filePage->getText() ) );
+ $link = Linker::linkKnown( $filePage, htmlspecialchars( $filePage->getText() ) );
$download = Xml::element( 'a',
array( 'href' => wfLocalFile( $filePage )->getURL() ),
$imgfile
@@ -223,7 +217,7 @@ class ImageListPager extends TablePager {
}
case 'img_user_text':
if ( $this->mCurrentRow->img_user ) {
- $link = $this->getSkin()->link(
+ $link = Linker::link(
Title::makeTitle( NS_USER, $value ),
htmlspecialchars( $value )
);
@@ -232,9 +226,9 @@ class ImageListPager extends TablePager {
}
return $link;
case 'img_size':
- return $this->getSkin()->formatSize( $value );
+ return htmlspecialchars( $this->getLanguage()->formatSize( $value ) );
case 'img_description':
- return $this->getSkin()->commentBlock( $value );
+ return Linker::commentBlock( $value );
case 'count':
return intval( $value ) + 1;
}
diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php
index 07e08e77..91d8ed87 100644
--- a/includes/specials/SpecialListgrouprights.php
+++ b/includes/specials/SpecialListgrouprights.php
@@ -44,10 +44,11 @@ class SpecialListGroupRights extends SpecialPage {
global $wgImplicitGroups;
global $wgGroupPermissions, $wgRevokePermissions, $wgAddGroups, $wgRemoveGroups;
global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
- $out = $this->getOutput();
$this->setHeaders();
$this->outputHeader();
+
+ $out = $this->getOutput();
$out->addModuleStyles( 'mediawiki.special' );
$out->addHTML(
@@ -96,20 +97,16 @@ class SpecialListGroupRights extends SpecialPage {
if ( $group === 'user' ) {
// Link to Special:listusers for implicit group 'user'
- $grouplink = '<br />' . Linker::link(
+ $grouplink = '<br />' . Linker::linkKnown(
SpecialPage::getTitleFor( 'Listusers' ),
- wfMsgHtml( 'listgrouprights-members' ),
- array(),
- array(),
- array( 'known', 'noclasses' )
+ wfMsgHtml( 'listgrouprights-members' )
);
} elseif ( !in_array( $group, $wgImplicitGroups ) ) {
- $grouplink = '<br />' . Linker::link(
+ $grouplink = '<br />' . Linker::linkKnown(
SpecialPage::getTitleFor( 'Listusers' ),
wfMsgHtml( 'listgrouprights-members' ),
array(),
- array( 'group' => $group ),
- array( 'known', 'noclasses' )
+ array( 'group' => $group )
);
} else {
// No link to Special:listusers for other implicit groups as they are unlistable
@@ -127,7 +124,8 @@ class SpecialListGroupRights extends SpecialPage {
"
<td>$grouppage$grouplink</td>
<td>" .
- self::formatPermissions( $permissions, $revoke, $addgroups, $removegroups, $addgroupsSelf, $removegroupsSelf ) .
+ $this->formatPermissions( $permissions, $revoke, $addgroups, $removegroups,
+ $addgroupsSelf, $removegroupsSelf ) .
'</td>
'
) );
@@ -149,9 +147,7 @@ class SpecialListGroupRights extends SpecialPage {
* @param $removeSelf Array of group this group is allowed to remove from self or true
* @return string List of all granted permissions, separated by comma separator
*/
- private static function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
- global $wgLang;
-
+ private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
$r = array();
foreach( $permissions as $permission => $granted ) {
//show as granted only if it isn't revoked to prevent duplicate display of permissions
@@ -173,29 +169,42 @@ class SpecialListGroupRights extends SpecialPage {
}
}
sort( $r );
+ $lang = $this->getLanguage();
if( $add === true ){
$r[] = wfMsgExt( 'listgrouprights-addgroup-all', array( 'escape' ) );
} 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 ) );
+ $r[] = wfMsgExt( 'listgrouprights-addgroup', array( 'parseinline' ),
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $add ) ),
+ count( $add )
+ );
}
if( $remove === true ){
$r[] = wfMsgExt( 'listgrouprights-removegroup-all', array( 'escape' ) );
} 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 ) );
+ $r[] = wfMsgExt( 'listgrouprights-removegroup', array( 'parseinline' ),
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $remove ) ),
+ count( $remove )
+ );
}
if( $addSelf === true ){
$r[] = wfMsgExt( 'listgrouprights-addgroup-self-all', array( 'escape' ) );
} 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 ) );
+ $r[] = wfMsgExt( 'listgrouprights-addgroup-self', array( 'parseinline' ),
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $addSelf ) ),
+ count( $addSelf )
+ );
}
if( $removeSelf === true ){
$r[] = wfMsgExt( 'listgrouprights-removegroup-self-all', array( 'escape' ) );
} 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 ) );
+ $r[] = wfMsgExt( 'listgrouprights-removegroup-self', array( 'parseinline' ),
+ $lang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $removeSelf ) ),
+ count( $removeSelf )
+ );
}
if( empty( $r ) ) {
return '';
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
index acf5fbd9..f9cf3e6e 100644
--- a/includes/specials/SpecialListredirects.php
+++ b/includes/specials/SpecialListredirects.php
@@ -43,6 +43,7 @@ class ListredirectsPage extends QueryPage {
'tables' => array( 'p1' => 'page', 'redirect', 'p2' => 'page' ),
'fields' => array( 'p1.page_namespace AS namespace',
'p1.page_title AS title',
+ 'p1.page_title AS value',
'rd_namespace',
'rd_title',
'rd_fragment',
@@ -90,7 +91,7 @@ class ListredirectsPage extends QueryPage {
);
} else {
$title = Title::makeTitle( $row->namespace, $row->title );
- $article = new Article( $title );
+ $article = WikiPage::factory( $title );
return $article->getRedirectTarget();
}
}
@@ -98,7 +99,7 @@ class ListredirectsPage extends QueryPage {
function formatResult( $skin, $result ) {
# Make a link to the redirect itself
$rd_title = Title::makeTitle( $result->namespace, $result->title );
- $rd_link = $skin->link(
+ $rd_link = Linker::link(
$rd_title,
null,
array(),
@@ -108,10 +109,10 @@ class ListredirectsPage extends QueryPage {
# Find out where the redirect leads
$target = $this->getRedirectTarget( $result );
if( $target ) {
- global $wgLang;
# Make a link to the destination page
- $arr = $wgLang->getArrow() . $wgLang->getDirMark();
- $targetLink = $skin->link( $target );
+ $lang = $this->getLanguage();
+ $arr = $lang->getArrow() . $lang->getDirMark();
+ $targetLink = Linker::link( $target );
return "$rd_link $arr $targetLink";
} else {
return "<del>$rd_link</del>";
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
index 0531444a..d743712d 100644
--- a/includes/specials/SpecialListusers.php
+++ b/includes/specials/SpecialListusers.php
@@ -3,7 +3,7 @@
* Implements Special:Listusers
*
* Copyright © 2004 Brion Vibber, lcrocker, Tim Starling,
- * Domas Mituzas, Ashar Voultoiz, Jens Frank, Zhengzhu,
+ * Domas Mituzas, Antoine Musso, Jens Frank, Zhengzhu,
* 2006 Rob Church <robchur@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
@@ -34,25 +34,30 @@
*/
class UsersPager extends AlphabeticPager {
- function __construct( $par=null ) {
- global $wgRequest;
- $parms = explode( '/', ($par = ( $par !== null ) ? $par : '' ) );
+ function __construct( IContextSource $context = null, $par = null ) {
+ if ( $context ) {
+ $this->setContext( $context );
+ }
+
+ $request = $this->getRequest();
+ $par = ( $par !== null ) ? $par : '';
+ $parms = explode( '/', $par );
$symsForAll = array( '*', 'user' );
if ( $parms[0] != '' && ( in_array( $par, User::getAllGroups() ) || in_array( $par, $symsForAll ) ) ) {
$this->requestedGroup = $par;
- $un = $wgRequest->getText( 'username' );
+ $un = $request->getText( 'username' );
} elseif ( count( $parms ) == 2 ) {
$this->requestedGroup = $parms[0];
$un = $parms[1];
} else {
- $this->requestedGroup = $wgRequest->getVal( 'group' );
- $un = ( $par != '' ) ? $par : $wgRequest->getText( 'username' );
+ $this->requestedGroup = $request->getVal( 'group' );
+ $un = ( $par != '' ) ? $par : $request->getText( 'username' );
}
if ( in_array( $this->requestedGroup, $symsForAll ) ) {
$this->requestedGroup = '';
}
- $this->editsOnly = $wgRequest->getBool( 'editsOnly' );
- $this->creationSort = $wgRequest->getBool( 'creationSort' );
+ $this->editsOnly = $request->getBool( 'editsOnly' );
+ $this->creationSort = $request->getBool( 'creationSort' );
$this->requestedUser = '';
if ( $un != '' ) {
@@ -64,20 +69,15 @@ class UsersPager extends AlphabeticPager {
parent::__construct();
}
- function getTitle() {
- return SpecialPage::getTitleFor( 'Listusers' );
- }
-
function getIndexField() {
return $this->creationSort ? 'user_id' : 'user_name';
}
function getQueryInfo() {
- global $wgUser;
$dbr = wfGetDB( DB_SLAVE );
$conds = array();
// Don't show hidden names
- if( !$wgUser->isAllowed('hideuser') ) {
+ if( !$this->getUser()->isAllowed('hideuser') ) {
$conds[] = 'ipb_deleted IS NULL';
}
@@ -126,32 +126,32 @@ class UsersPager extends AlphabeticPager {
}
function formatRow( $row ) {
- global $wgLang;
-
if ($row->user_id == 0) #Bug 16487
return '';
$userPage = Title::makeTitle( NS_USER, $row->user_name );
$name = Linker::link( $userPage, htmlspecialchars( $userPage->getText() ) );
+ $lang = $this->getLanguage();
+
$groups_list = self::getGroups( $row->user_id );
if( count( $groups_list ) > 0 ) {
$list = array();
foreach( $groups_list as $group )
- $list[] = self::buildGroupLink( $group );
- $groups = $wgLang->commaList( $list );
+ $list[] = self::buildGroupLink( $group, $userPage->getText() );
+ $groups = $lang->commaList( $list );
} else {
$groups = '';
}
- $item = wfSpecialList( $name, $groups );
+ $item = $lang->specialList( $name, $groups );
if( $row->ipb_deleted ) {
$item = "<span class=\"deleted\">$item</span>";
}
global $wgEdititis;
if ( $wgEdititis ) {
- $editCount = $wgLang->formatNum( $row->edits );
+ $editCount = $lang->formatNum( $row->edits );
$edits = ' [' . wfMsgExt( 'usereditcount', array( 'parsemag', 'escape' ), $editCount ) . ']';
} else {
$edits = '';
@@ -160,10 +160,9 @@ class UsersPager extends AlphabeticPager {
$created = '';
# Some rows may be NULL
if( $row->creation ) {
- $d = $wgLang->date( wfTimestamp( TS_MW, $row->creation ), true );
- $t = $wgLang->time( wfTimestamp( TS_MW, $row->creation ), true );
- $created = ' (' . wfMsg( 'usercreated', $d, $t ) . ')';
- $created = htmlspecialchars( $created );
+ $d = $lang->date( wfTimestamp( TS_MW, $row->creation ), true );
+ $t = $lang->time( wfTimestamp( TS_MW, $row->creation ), true );
+ $created = ' (' . wfMsgExt( 'usercreated', array( 'parsemag', 'escape' ), $d, $t, $row->user_name ) . ')';
}
wfRunHooks( 'SpecialListusersFormatRow', array( &$item, $row ) );
@@ -186,12 +185,13 @@ class UsersPager extends AlphabeticPager {
function getPageHeader( ) {
global $wgScript;
- $self = $this->getTitle();
+ // @todo Add a PrefixedBaseDBKey
+ list( $self ) = explode( '/', $this->getTitle()->getPrefixedDBkey() );
# Form tag
$out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listusers-form' ) ) .
Xml::fieldset( wfMsg( 'listusers' ) ) .
- Html::hidden( 'title', $self->getPrefixedDbKey() );
+ Html::hidden( 'title', $self );
# Username field
$out .= Xml::label( wfMsg( 'listusersfrom' ), 'offset' ) . ' ' .
@@ -264,13 +264,11 @@ class UsersPager extends AlphabeticPager {
* Format a link to a group description page
*
* @param $group String: group name
+ * @param $username String Username
* @return string
*/
- protected static function buildGroupLink( $group ) {
- static $cache = array();
- if( !isset( $cache[$group] ) )
- $cache[$group] = User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupMember( $group ) ) );
- return $cache[$group];
+ protected static function buildGroupLink( $group, $username ) {
+ return User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupMember( $group, $username ) ) );
}
}
@@ -292,12 +290,10 @@ class SpecialListUsers extends SpecialPage {
* @param $par string (optional) A group to list users from
*/
public function execute( $par ) {
- global $wgOut;
-
$this->setHeaders();
$this->outputHeader();
- $up = new UsersPager( $par );
+ $up = new UsersPager( $this->getContext(), $par );
# getBody() first to check, if empty
$usersbody = $up->getBody();
@@ -311,6 +307,6 @@ class SpecialListUsers extends SpecialPage {
$s .= wfMessage( 'listusers-noresult' )->parseAsBlock();
}
- $wgOut->addHTML( $s );
+ $this->getOutput()->addHTML( $s );
}
}
diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php
index 5c861b31..c1453518 100644
--- a/includes/specials/SpecialLockdb.php
+++ b/includes/specials/SpecialLockdb.php
@@ -26,89 +26,53 @@
*
* @ingroup SpecialPage
*/
-class SpecialLockdb extends SpecialPage {
+class SpecialLockdb extends FormSpecialPage {
var $reason = '';
public function __construct() {
parent::__construct( 'Lockdb', 'siteadmin' );
}
- public function execute( $par ) {
- global $wgUser, $wgRequest;
-
- $this->setHeaders();
-
- # Permission check
- if( !$this->userCanExecute( $wgUser ) ) {
- $this->displayRestrictionError();
- return;
- }
-
- $this->outputHeader();
+ public function requiresWrite() {
+ return false;
+ }
- # If the lock file isn't writable, we can do sweet bugger all
+ public function checkExecutePermissions( User $user ) {
global $wgReadOnlyFile;
- if( !is_writable( dirname( $wgReadOnlyFile ) ) ) {
- self::notWritable();
- return;
- }
- $action = $wgRequest->getVal( 'action' );
- $this->reason = $wgRequest->getVal( 'wpLockReason', '' );
-
- if ( $action == 'success' ) {
- $this->showSuccess();
- } elseif ( $action == 'submit' && $wgRequest->wasPosted() &&
- $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
- $this->doSubmit();
- } else {
- $this->showForm();
+ parent::checkExecutePermissions( $user );
+ # If the lock file isn't writable, we can do sweet bugger all
+ if ( !is_writable( dirname( $wgReadOnlyFile ) ) ) {
+ throw new ErrorPageError( 'lockdb', 'lockfilenotwritable' );
}
}
- private function showForm( $err = '' ) {
- global $wgOut, $wgUser;
-
- $wgOut->addWikiMsg( 'lockdbtext' );
-
- if ( $err != '' ) {
- $wgOut->setSubtitle( wfMsg( 'formerror' ) );
- $wgOut->addHTML( '<p class="error">' . htmlspecialchars( $err ) . "</p>\n" );
- }
-
- $wgOut->addHTML(
- Html::openElement( 'form', array( 'id' => 'lockdb', 'method' => 'POST',
- 'action' => $this->getTitle()->getLocalURL( 'action=submit' ) ) ). "\n" .
- wfMsgHtml( 'enterlockreason' ) . ":\n" .
- Html::textarea( 'wpLockReason', $this->reason, array( 'rows' => 4 ) ). "
-<table>
- <tr>
- " . Html::openElement( 'td', array( 'style' => 'text-align:right' ) ) . "
- " . Html::input( 'wpLockConfirm', null, 'checkbox' ) . "
- </td>
- " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) .
- wfMsgHtml( 'lockconfirm' ) . "</td>
- </tr>
- <tr>
- <td>&#160;</td>
- " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) . "
- " . Html::input( 'wpLock', wfMsg( 'lockbtn' ), 'submit' ) . "
- </td>
- </tr>
-</table>\n" .
- Html::hidden( 'wpEditToken', $wgUser->editToken() ) . "\n" .
- Html::closeElement( 'form' )
+ protected function getFormFields() {
+ return array(
+ 'Reason' => array(
+ 'type' => 'textarea',
+ 'rows' => 4,
+ 'vertical-label' => true,
+ 'label-message' => 'enterlockreason',
+ ),
+ 'Confirm' => array(
+ 'type' => 'toggle',
+ 'label-message' => 'lockconfirm',
+ ),
);
+ }
+ protected function alterForm( HTMLForm $form ) {
+ $form->setWrapperLegend( false );
+ $form->setHeaderText( $this->msg( 'lockdbtext' )->parseAsBlock() );
+ $form->setSubmitTextMsg( 'lockbtn' );
}
- private function doSubmit() {
- global $wgOut, $wgUser, $wgContLang, $wgRequest;
- global $wgReadOnlyFile;
+ public function onSubmit( array $data ) {
+ global $wgContLang, $wgReadOnlyFile;
- if ( ! $wgRequest->getCheck( 'wpLockConfirm' ) ) {
- $this->showForm( wfMsg( 'locknoconfirm' ) );
- return;
+ if ( !$data['Confirm'] ) {
+ return Status::newFatal( 'locknoconfirm' );
}
wfSuppressWarnings();
@@ -119,33 +83,25 @@ class SpecialLockdb extends SpecialPage {
# This used to show a file not found error, but the likeliest reason for fopen()
# to fail at this point is insufficient permission to write to the file...good old
# is_writable() is plain wrong in some cases, it seems...
- self::notWritable();
- return;
+ return Status::newFatal( 'lockfilenotwritable' );
}
- fwrite( $fp, $this->reason );
+ fwrite( $fp, $data['Reason'] );
$timestamp = wfTimestampNow();
fwrite( $fp, "\n<p>" . wfMsgExt(
'lockedbyandtime',
array( 'content', 'parsemag' ),
- $wgUser->getName(),
+ $this->getUser()->getName(),
$wgContLang->date( $timestamp ),
$wgContLang->time( $timestamp )
) . "</p>\n" );
fclose( $fp );
- $wgOut->redirect( $this->getTitle()->getFullURL( 'action=success' ) );
- }
-
- private function showSuccess() {
- global $wgOut;
-
- $wgOut->setPagetitle( wfMsg( 'lockdb' ) );
- $wgOut->setSubtitle( wfMsg( 'lockdbsuccesssub' ) );
- $wgOut->addWikiMsg( 'lockdbsuccesstext' );
+ return Status::newGood();
}
- public static function notWritable() {
- global $wgOut;
- $wgOut->showErrorPage( 'lockdb', 'lockfilenotwritable' );
+ public function onSuccess() {
+ $out = $this->getOutput();
+ $out->addSubtitle( $this->msg( 'lockdbsuccesssub' ) );
+ $out->addWikiMsg( 'lockdbsuccesstext' );
}
}
diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php
index d8f6d8cf..64190df1 100644
--- a/includes/specials/SpecialLog.php
+++ b/includes/specials/SpecialLog.php
@@ -30,13 +30,24 @@
*/
class SpecialLog extends SpecialPage {
+ /**
+ * List log type for which the target is a user
+ * Thus if the given target is in NS_MAIN we can alter it to be an NS_USER
+ * Title user instead.
+ */
+ private $typeOnUser = array(
+ 'block',
+ 'newusers',
+ 'rights',
+ );
+
public function __construct() {
parent::__construct( 'Log' );
}
public function execute( $par ) {
- global $wgRequest;
-
+ global $wgLogRestrictions;
+
$this->setHeaders();
$this->outputHeader();
@@ -53,7 +64,7 @@ class SpecialLog extends SpecialPage {
$opts->add( 'offender', '' );
// Set values
- $opts->fetchValuesFromRequest( $wgRequest );
+ $opts->fetchValuesFromRequest( $this->getRequest() );
if ( $par ) {
$this->parseParams( $opts, (string)$par );
}
@@ -64,6 +75,16 @@ class SpecialLog extends SpecialPage {
$opts->setValue( 'month', '' );
}
+ // Reset the log type to default (nothing) if it's invalid or if the
+ // user does not possess the right to view it
+ $type = $opts->getValue( 'type' );
+ if ( !LogPage::isLogType( $type )
+ || ( isset( $wgLogRestrictions[$type] )
+ && !$this->getUser()->isAllowed( $wgLogRestrictions[$type] ) )
+ ) {
+ $opts->setValue( 'type', '' );
+ }
+
# Handle type-specific inputs
$qc = array();
if ( $opts->getValue( 'type' ) == 'suppress' ) {
@@ -75,6 +96,20 @@ class SpecialLog extends SpecialPage {
}
}
+ # Some log types are only for a 'User:' title but we might have been given
+ # only the username instead of the full title 'User:username'. This part try
+ # to lookup for a user by that name and eventually fix user input. See bug 1697.
+ if( in_array( $opts->getValue( 'type' ), $this->typeOnUser ) ) {
+ # ok we have a type of log which expect a user title.
+ $target = Title::newFromText( $opts->getValue( 'page' ) );
+ if( $target && $target->getNamespace() === NS_MAIN ) {
+ # User forgot to add 'User:', we are adding it for him
+ $opts->setValue( 'page',
+ Title::makeTitleSafe( NS_USER, $opts->getValue( 'page' ) )
+ );
+ }
+ }
+
$this->show( $opts, $qc );
}
@@ -95,30 +130,27 @@ class SpecialLog extends SpecialPage {
}
private function show( FormOptions $opts, array $extraConds ) {
- global $wgOut;
-
# Create a LogPager item to get the results and a LogEventsList item to format them...
- $loglist = new LogEventsList( $this->getSkin(), $wgOut, 0 );
+ $loglist = new LogEventsList( $this->getSkin(), $this->getOutput(), 0 );
$pager = new LogPager( $loglist, $opts->getValue( 'type' ), $opts->getValue( 'user' ),
$opts->getValue( 'page' ), $opts->getValue( 'pattern' ), $extraConds, $opts->getValue( 'year' ),
$opts->getValue( 'month' ), $opts->getValue( 'tagfilter' ) );
- # Set title and add header
- $loglist->showHeader( $pager->getType() );
+ $this->addHeader( $opts->getValue( 'type' ) );
# Set relevant user
- if ( $pager->getUser() ) {
- $this->getSkin()->setRelevantUser( User::newFromName( $pager->getUser() ) );
+ if ( $pager->getPerformer() ) {
+ $this->getSkin()->setRelevantUser( User::newFromName( $pager->getPerformer() ) );
}
# Show form options
- $loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(),
+ $loglist->showOptions( $pager->getType(), $opts->getValue( 'user' ), $pager->getPage(), $pager->getPattern(),
$pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $opts->getValue( 'tagfilter' ) );
# Insert list
$logBody = $pager->getBody();
if ( $logBody ) {
- $wgOut->addHTML(
+ $this->getOutput()->addHTML(
$pager->getNavigationBar() .
$loglist->beginLogEventsList() .
$logBody .
@@ -126,7 +158,19 @@ class SpecialLog extends SpecialPage {
$pager->getNavigationBar()
);
} else {
- $wgOut->addWikiMsg( 'logempty' );
+ $this->getOutput()->addWikiMsg( 'logempty' );
}
}
+
+ /**
+ * Set page title and show header for this log type
+ * @param $type string
+ * @since 1.19
+ */
+ protected function addHeader( $type ) {
+ $page = new LogPage( $type );
+ $this->getOutput()->setPageTitle( $page->getName()->text() );
+ $this->getOutput()->addHTML( $page->getDescription()->parseAsBlock() );
+ }
+
}
diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php
index aefe7bf5..2213ffa4 100644
--- a/includes/specials/SpecialMIMEsearch.php
+++ b/includes/specials/SpecialMIMEsearch.php
@@ -59,12 +59,11 @@ class MIMEsearchPage extends QueryPage {
}
function execute( $par ) {
- global $wgRequest, $wgOut;
- $mime = $par ? $par : $wgRequest->getText( 'mime' );
+ $mime = $par ? $par : $this->getRequest()->getText( 'mime' );
$this->setHeaders();
$this->outputHeader();
- $wgOut->addHTML(
+ $this->getOutput()->addHTML(
Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => SpecialPage::getTitleFor( 'MIMEsearch' )->getLocalUrl() ) ) .
Xml::openElement( 'fieldset' ) .
Html::hidden( 'title', SpecialPage::getTitleFor( 'MIMEsearch' )->getPrefixedText() ) .
@@ -85,24 +84,24 @@ class MIMEsearchPage extends QueryPage {
function formatResult( $skin, $result ) {
- global $wgContLang, $wgLang;
+ global $wgContLang;
$nt = Title::makeTitle( $result->namespace, $result->title );
$text = $wgContLang->convert( $nt->getText() );
- $plink = $skin->link(
+ $plink = Linker::link(
Title::newFromText( $nt->getPrefixedText() ),
htmlspecialchars( $text )
);
- $download = $skin->makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) );
- $bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->img_size ) );
+ $download = Linker::makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) );
+ $lang = $this->getLanguage();
+ $bytes = htmlspecialchars( $lang->formatSize( $result->img_size ) );
$dimensions = htmlspecialchars( wfMsg( 'widthheight',
- $wgLang->formatNum( $result->img_width ),
- $wgLang->formatNum( $result->img_height )
+ $lang->formatNum( $result->img_width ),
+ $lang->formatNum( $result->img_height )
) );
- $user = $skin->link( Title::makeTitle( NS_USER, $result->img_user_text ), htmlspecialchars( $result->img_user_text ) );
- $time = htmlspecialchars( $wgLang->timeanddate( $result->img_timestamp ) );
+ $user = Linker::link( Title::makeTitle( NS_USER, $result->img_user_text ), htmlspecialchars( $result->img_user_text ) );
+ $time = htmlspecialchars( $lang->timeanddate( $result->img_timestamp ) );
return "($download) $plink . . $dimensions . . $bytes . . $user . . $time";
}
diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php
index 88e90ee5..19650da9 100644
--- a/includes/specials/SpecialMergeHistory.php
+++ b/includes/specials/SpecialMergeHistory.php
@@ -40,12 +40,10 @@ class SpecialMergeHistory extends SpecialPage {
}
/**
- * @param $request WebRequest
* @return void
*/
- private function loadRequestParams( $request ) {
- global $wgUser;
-
+ private function loadRequestParams() {
+ $request = $this->getRequest();
$this->mAction = $request->getVal( 'action' );
$this->mTarget = $request->getVal( 'target' );
$this->mDest = $request->getVal( 'dest' );
@@ -59,7 +57,7 @@ class SpecialMergeHistory extends SpecialPage {
}
$this->mComment = $request->getText( 'wpComment' );
- $this->mMerge = $request->wasPosted() && $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) );
+ $this->mMerge = $request->wasPosted() && $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) );
// target page
if( $this->mSubmitted ) {
$this->mTargetObj = Title::newFromURL( $this->mTarget );
@@ -83,19 +81,10 @@ class SpecialMergeHistory extends SpecialPage {
}
public function execute( $par ) {
- global $wgOut, $wgRequest, $wgUser;
-
- if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- }
-
- if( !$this->userCanExecute( $wgUser ) ) {
- $this->displayRestrictionError();
- return;
- }
+ $this->checkPermissions();
+ $this->checkReadOnly();
- $this->loadRequestParams( $wgRequest );
+ $this->loadRequestParams();
$this->setHeaders();
$this->outputHeader();
@@ -132,7 +121,7 @@ class SpecialMergeHistory extends SpecialPage {
if ( count( $errors ) ) {
$this->showMergeForm();
- $wgOut->addHTML( implode( "\n", $errors ) );
+ $this->getOutput()->addHTML( implode( "\n", $errors ) );
} else {
$this->showHistory();
}
@@ -140,11 +129,11 @@ class SpecialMergeHistory extends SpecialPage {
}
function showMergeForm() {
- global $wgOut, $wgScript;
+ global $wgScript;
- $wgOut->addWikiMsg( 'mergehistory-header' );
+ $this->getOutput()->addWikiMsg( 'mergehistory-header' );
- $wgOut->addHTML(
+ $this->getOutput()->addHTML(
Xml::openElement( 'form', array(
'method' => 'get',
'action' => $wgScript ) ) .
@@ -171,12 +160,6 @@ class SpecialMergeHistory extends SpecialPage {
}
private function showHistory() {
- global $wgUser, $wgOut;
-
- $this->sk = $this->getSkin();
-
- $wgOut->setPageTitle( wfMsg( 'mergehistory' ) );
-
$this->showMergeForm();
# List all stored revisions
@@ -185,6 +168,7 @@ class SpecialMergeHistory extends SpecialPage {
);
$haveRevisions = $revisions && $revisions->getNumRows() > 0;
+ $out = $this->getOutput();
$titleObj = $this->getTitle();
$action = $titleObj->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
@@ -196,7 +180,7 @@ class SpecialMergeHistory extends SpecialPage {
'id' => 'merge'
)
);
- $wgOut->addHTML( $top );
+ $out->addHTML( $top );
if( $haveRevisions ) {
# Format the user-visible controls (comment field, submission button)
@@ -223,27 +207,27 @@ class SpecialMergeHistory extends SpecialPage {
Xml::closeElement( 'table' ) .
Xml::closeElement( 'fieldset' );
- $wgOut->addHTML( $table );
+ $out->addHTML( $table );
}
- $wgOut->addHTML(
+ $out->addHTML(
'<h2 id="mw-mergehistory">' .
wfMsgHtml( 'mergehistory-list' ) . "</h2>\n"
);
if( $haveRevisions ) {
- $wgOut->addHTML( $revisions->getNavigationBar() );
- $wgOut->addHTML( '<ul>' );
- $wgOut->addHTML( $revisions->getBody() );
- $wgOut->addHTML( '</ul>' );
- $wgOut->addHTML( $revisions->getNavigationBar() );
+ $out->addHTML( $revisions->getNavigationBar() );
+ $out->addHTML( '<ul>' );
+ $out->addHTML( $revisions->getBody() );
+ $out->addHTML( '</ul>' );
+ $out->addHTML( $revisions->getNavigationBar() );
} else {
- $wgOut->addWikiMsg( 'mergehistory-empty' );
+ $out->addWikiMsg( 'mergehistory-empty' );
}
# Show relevant lines from the deletion log:
- $wgOut->addHTML( '<h2>' . htmlspecialchars( LogPage::logName( 'merge' ) ) . "</h2>\n" );
- LogEventsList::showLogExtract( $wgOut, 'merge', $this->mTargetObj->getPrefixedText() );
+ $out->addHTML( '<h2>' . htmlspecialchars( LogPage::logName( 'merge' ) ) . "</h2>\n" );
+ LogEventsList::showLogExtract( $out, 'merge', $this->mTargetObj );
# When we submit, go by page ID to avoid some nasty but unlikely collisions.
# Such would happen if a page was renamed after the form loaded, but before submit
@@ -251,16 +235,14 @@ class SpecialMergeHistory extends SpecialPage {
$misc .= Html::hidden( 'destID', $this->mDestObj->getArticleID() );
$misc .= Html::hidden( 'target', $this->mTarget );
$misc .= Html::hidden( 'dest', $this->mDest );
- $misc .= Html::hidden( 'wpEditToken', $wgUser->editToken() );
+ $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
$misc .= Xml::closeElement( 'form' );
- $wgOut->addHTML( $misc );
+ $out->addHTML( $misc );
return true;
}
function formatRevisionRow( $row ) {
- global $wgLang;
-
$rev = new Revision( $row );
$stxt = '';
@@ -269,9 +251,9 @@ class SpecialMergeHistory extends SpecialPage {
$ts = wfTimestamp( TS_MW, $row->rev_timestamp );
$checkBox = Xml::radio( 'mergepoint', $ts, false );
- $pageLink = $this->sk->linkKnown(
+ $pageLink = Linker::linkKnown(
$rev->getTitle(),
- htmlspecialchars( $wgLang->timeanddate( $ts ) ),
+ htmlspecialchars( $this->getLanguage()->timeanddate( $ts ) ),
array(),
array( 'oldid' => $rev->getId() )
);
@@ -280,10 +262,10 @@ class SpecialMergeHistory extends SpecialPage {
}
# Last link
- if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ if( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
$last = $this->message['last'];
} elseif( isset( $this->prevId[$row->rev_id] ) ) {
- $last = $this->sk->linkKnown(
+ $last = Linker::linkKnown(
$rev->getTitle(),
$this->message['last'],
array(),
@@ -294,46 +276,18 @@ class SpecialMergeHistory extends SpecialPage {
);
}
- $userLink = $this->sk->revUserTools( $rev );
+ $userLink = Linker::revUserTools( $rev );
$size = $row->rev_len;
if( !is_null( $size ) ) {
- $stxt = $this->sk->formatRevisionSize( $size );
+ $stxt = Linker::formatRevisionSize( $size );
}
- $comment = $this->sk->revComment( $rev );
+ $comment = Linker::revComment( $rev );
return "<li>$checkBox ($last) $pageLink . . $userLink $stxt $comment</li>";
}
- /**
- * Fetch revision text link if it's available to all users
- * @return string
- */
- 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>';
- } else {
- $link = $this->sk->linkKnown(
- $titleObj,
- $wgLang->timeanddate( $ts, true ),
- array(),
- array(
- 'target' => $target,
- 'timestamp' => $ts
- )
- );
- if( $this->isDeleted( $row, Revision::DELETED_TEXT ) ) {
- $link = '<span class="history-deleted">' . $link . '</span>';
- }
- return $link;
- }
- }
-
function merge() {
- global $wgOut;
# Get the titles directly from the IDs, in case the target page params
# were spoofed. The queries are done based on the IDs, so it's best to
# keep it consistent...
@@ -359,7 +313,7 @@ class SpecialMergeHistory extends SpecialPage {
);
# Destination page must exist with revisions
if( !$maxtimestamp ) {
- $wgOut->addWikiMsg( 'mergehistory-fail' );
+ $this->getOutput()->addWikiMsg( 'mergehistory-fail' );
return false;
}
# Get the latest timestamp of the source
@@ -371,7 +325,7 @@ class SpecialMergeHistory extends SpecialPage {
);
# $this->mTimestamp must be older than $maxtimestamp
if( $this->mTimestamp >= $maxtimestamp ) {
- $wgOut->addWikiMsg( 'mergehistory-fail' );
+ $this->getOutput()->addWikiMsg( 'mergehistory-fail' );
return false;
}
# Update the revisions
@@ -416,13 +370,13 @@ class SpecialMergeHistory extends SpecialPage {
}
$mwRedir = MagicWord::get( 'redirect' );
$redirectText = $mwRedir->getSynonym( 0 ) . ' [[' . $destTitle->getPrefixedText() . "]]\n";
- $redirectArticle = new Article( $targetTitle );
+ $redirectPage = WikiPage::factory( $targetTitle );
$redirectRevision = new Revision( array(
'page' => $this->mTargetID,
'comment' => $comment,
'text' => $redirectText ) );
$redirectRevision->insertOn( $dbw );
- $redirectArticle->updateRevisionOn( $dbw, $redirectRevision );
+ $redirectPage->updateRevisionOn( $dbw, $redirectRevision );
# Now, we record the link from the redirect to the new title.
# It should have no other outgoing links...
@@ -440,7 +394,7 @@ class SpecialMergeHistory extends SpecialPage {
$destTitle->invalidateCache(); // update histories
# Check if this did anything
if( !$count ) {
- $wgOut->addWikiMsg( 'mergehistory-fail' );
+ $this->getOutput()->addWikiMsg( 'mergehistory-fail' );
return false;
}
# Update our logs
@@ -450,7 +404,7 @@ class SpecialMergeHistory extends SpecialPage {
array( $destTitle->getPrefixedText(), $timestampLimit )
);
- $wgOut->addHTML(
+ $this->getOutput()->addHTML(
wfMsgExt( 'mergehistory-success', array('parseinline'),
$targetTitle->getPrefixedText(), $destTitle->getPrefixedText(), $count ) );
@@ -478,11 +432,7 @@ class MergeHistoryPager extends ReverseChronologicalPager {
);
$this->maxTimestamp = $maxtimestamp;
- parent::__construct();
- }
-
- function getTitle() {
- return SpecialPage::getTitleFor( 'Contributions' );
+ parent::__construct( $form->getContext() );
}
function getStartBody() {
@@ -493,8 +443,8 @@ class MergeHistoryPager extends ReverseChronologicalPager {
# Give some pointers to make (last) links
$this->mForm->prevId = array();
foreach ( $this->mResult as $row ) {
- $batch->addObj( Title::makeTitleSafe( NS_USER, $row->rev_user_text ) );
- $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->rev_user_text ) );
+ $batch->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) );
+ $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->user_name ) );
$rev_id = isset( $rev_id ) ? $rev_id : $row->rev_id;
if( $rev_id > $row->rev_id ) {
@@ -520,16 +470,14 @@ class MergeHistoryPager extends ReverseChronologicalPager {
function getQueryInfo() {
$conds = $this->mConds;
$conds['rev_page'] = $this->articleID;
- $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'
- ),
- 'conds' => $conds
+ 'tables' => array( 'revision', 'page', 'user' ),
+ 'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ),
+ 'conds' => $conds,
+ 'join_conds' => array(
+ 'page' => Revision::pageJoinCond(),
+ 'user' => Revision::userJoinCond() )
);
}
diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php
index 2e437196..98b73675 100644
--- a/includes/specials/SpecialMostcategories.php
+++ b/includes/specials/SpecialMostcategories.php
@@ -58,11 +58,10 @@ class MostcategoriesPage extends QueryPage {
* @return string
*/
function formatResult( $skin, $result ) {
- global $wgLang;
$title = Title::makeTitleSafe( $result->namespace, $result->title );
- $count = wfMsgExt( 'ncategories', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->value ) );
- $link = $skin->link( $title );
- return wfSpecialList( $link, $count );
+ $count = $this->msg( 'ncategories' )->numParams( $result->value )->escaped();
+ $link = Linker::link( $title );
+ return $this->getLanguage()->specialList( $link, $count );
}
}
diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php
index ac2b5206..7805e53e 100644
--- a/includes/specials/SpecialMostimages.php
+++ b/includes/specials/SpecialMostimages.php
@@ -50,9 +50,7 @@ class MostimagesPage extends ImageQueryPage {
}
function getCellHtml( $row ) {
- global $wgLang;
- return wfMsgExt( 'nimagelinks', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $row->value ) ) . '<br />';
+ return $this->msg( 'nimagelinks' )->numParams( $row->value )->escaped() . '<br />';
}
}
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
index 58f686e8..a16f0872 100644
--- a/includes/specials/SpecialMostlinked.php
+++ b/includes/specials/SpecialMostlinked.php
@@ -77,12 +77,11 @@ class MostlinkedPage extends QueryPage {
*
* @param $title Title being queried
* @param $caption String: text to display on the link
- * @param $skin Skin to use
* @return String
*/
- function makeWlhLink( &$title, $caption, &$skin ) {
+ function makeWlhLink( $title, $caption ) {
$wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
- return $skin->linkKnown( $wlh, $caption );
+ return Linker::linkKnown( $wlh, $caption );
}
/**
@@ -93,15 +92,13 @@ class MostlinkedPage extends QueryPage {
* @return string
*/
function formatResult( $skin, $result ) {
- global $wgLang;
$title = Title::makeTitleSafe( $result->namespace, $result->title );
if ( !$title ) {
return '<!-- ' . htmlspecialchars( "Invalid title: [[$title]]" ) . ' -->';
}
- $link = $skin->link( $title );
+ $link = Linker::link( $title );
$wlh = $this->makeWlhLink( $title,
- wfMsgExt( 'nlinks', array( 'parsemag', 'escape'),
- $wgLang->formatNum( $result->value ) ), $skin );
- return wfSpecialList( $link, $wlh );
+ $this->msg( 'nlinks' )->numParams( $result->value )->escaped() );
+ return $this->getLanguage()->specialList( $link, $wlh );
}
}
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
index 195282f7..7fb9dea9 100644
--- a/includes/specials/SpecialMostlinkedcategories.php
+++ b/includes/specials/SpecialMostlinkedcategories.php
@@ -25,7 +25,7 @@
*/
/**
- * A querypage to show categories ordered in descending order by the pages in them
+ * A querypage to show categories ordered in descending order by the pages in them
*
* @ingroup SpecialPage
*/
@@ -35,16 +35,14 @@ class MostlinkedCategoriesPage extends QueryPage {
parent::__construct( $name );
}
- function isExpensive() { return true; }
function isSyndicated() { return false; }
function getQueryInfo() {
return array (
- 'tables' => array ( 'categorylinks' ),
- 'fields' => array ( 'cl_to AS title',
+ 'tables' => array ( 'category' ),
+ 'fields' => array ( 'cat_title AS title',
NS_CATEGORY . ' AS namespace',
- 'COUNT(*) AS value' ),
- 'options' => array ( 'GROUP BY' => 'cl_to' )
+ 'cat_pages AS value' ),
);
}
@@ -76,15 +74,14 @@ class MostlinkedCategoriesPage extends QueryPage {
* @return string
*/
function formatResult( $skin, $result ) {
- global $wgLang, $wgContLang;
+ global $wgContLang;
$nt = Title::makeTitle( NS_CATEGORY, $result->title );
$text = $wgContLang->convert( $nt->getText() );
- $plink = $skin->link( $nt, htmlspecialchars( $text ) );
+ $plink = Linker::link( $nt, htmlspecialchars( $text ) );
- $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->value ) );
- return wfSpecialList( $plink, $nlinks );
+ $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+ return $this->getLanguage()->specialList( $plink, $nlinks );
}
}
diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php
index 69771925..6fb09426 100644
--- a/includes/specials/SpecialMostlinkedtemplates.php
+++ b/includes/specials/SpecialMostlinkedtemplates.php
@@ -98,9 +98,9 @@ class MostlinkedTemplatesPage extends QueryPage {
public function formatResult( $skin, $result ) {
$title = Title::makeTitle( $result->namespace, $result->title );
- return wfSpecialList(
- $skin->link( $title ),
- $this->makeWlhLink( $title, $skin, $result )
+ return $this->getLanguage()->specialList(
+ Linker::link( $title ),
+ $this->makeWlhLink( $title, $result )
);
}
@@ -108,16 +108,13 @@ class MostlinkedTemplatesPage extends QueryPage {
* Make a "what links here" link for a given title
*
* @param $title Title to make the link for
- * @param $skin Skin to use
* @param $result Result row
* @return String
*/
- private function makeWlhLink( $title, $skin, $result ) {
- global $wgLang;
- $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' );
- $label = wfMsgExt( 'ntransclusions', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->value ) );
- return $skin->link( $wlh, $label, array(), array( 'target' => $title->getPrefixedText() ) );
+ private function makeWlhLink( $title, $result ) {
+ $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
+ $label = $this->msg( 'ntransclusions' )->numParams( $result->value )->escaped();
+ return Linker::link( $wlh, $label );
}
}
diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php
index ec7f4024..d7ebd310 100644
--- a/includes/specials/SpecialMovepage.php
+++ b/includes/specials/SpecialMovepage.php
@@ -42,103 +42,104 @@ class MovePageForm extends UnlistedSpecialPage {
}
public function execute( $par ) {
- global $wgUser, $wgOut, $wgRequest;
-
- # Check for database lock
- if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- }
+ $this->checkReadOnly();
$this->setHeaders();
$this->outputHeader();
- $target = !is_null( $par ) ? $par : $wgRequest->getVal( 'target' );
+ $request = $this->getRequest();
+ $target = !is_null( $par ) ? $par : $request->getVal( 'target' );
// Yes, the use of getVal() and getText() is wanted, see bug 20365
- $oldTitleText = $wgRequest->getVal( 'wpOldTitle', $target );
- $newTitleText = $wgRequest->getText( 'wpNewTitle' );
+ $oldTitleText = $request->getVal( 'wpOldTitle', $target );
$this->oldTitle = Title::newFromText( $oldTitleText );
- $this->newTitle = Title::newFromText( $newTitleText );
+
+ $newTitleTextMain = $request->getText( 'wpNewTitleMain' );
+ $newTitleTextNs = $request->getInt( 'wpNewTitleNs', $this->oldTitle->getNamespace() );
+ // Backwards compatibility for forms submitting here from other sources
+ // which is more common than it should be..
+ $newTitleText_bc = $request->getText( 'wpNewTitle' );
+ $this->newTitle = strlen( $newTitleText_bc ) > 0
+ ? Title::newFromText( $newTitleText_bc )
+ : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
if( is_null( $this->oldTitle ) ) {
- $wgOut->showErrorPage( 'notargettitle', 'notargettext' );
- return;
+ throw new ErrorPageError( 'notargettitle', 'notargettext' );
}
if( !$this->oldTitle->exists() ) {
- $wgOut->showErrorPage( 'nopagetitle', 'nopagetext' );
- return;
+ throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
}
+ $user = $this->getUser();
+
# Check rights
- $permErrors = $this->oldTitle->getUserPermissionsErrors( 'move', $wgUser );
- if( !empty( $permErrors ) ) {
+ $permErrors = $this->oldTitle->getUserPermissionsErrors( 'move', $user );
+ if ( count( $permErrors ) ) {
// Auto-block user's IP if the account was "hard" blocked
- $wgUser->spreadAnyEditBlock();
- $this->getOutput()->showPermissionsErrorPage( $permErrors );
- return;
+ $user->spreadAnyEditBlock();
+ throw new PermissionsError( 'move', $permErrors );
}
- $def = !$wgRequest->wasPosted();
+ $def = !$request->wasPosted();
- $this->reason = $wgRequest->getText( 'wpReason' );
- $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', $def );
- $this->fixRedirects = $wgRequest->getBool( 'wpFixRedirects', $def );
- $this->leaveRedirect = $wgRequest->getBool( 'wpLeaveRedirect', $def );
- $this->moveSubpages = $wgRequest->getBool( 'wpMovesubpages', false );
- $this->deleteAndMove = $wgRequest->getBool( 'wpDeleteAndMove' ) && $wgRequest->getBool( 'wpConfirm' );
- $this->moveOverShared = $wgRequest->getBool( 'wpMoveOverSharedFile', false );
- $this->watch = $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn();
+ $this->reason = $request->getText( 'wpReason' );
+ $this->moveTalk = $request->getBool( 'wpMovetalk', $def );
+ $this->fixRedirects = $request->getBool( 'wpFixRedirects', $def );
+ $this->leaveRedirect = $request->getBool( 'wpLeaveRedirect', $def );
+ $this->moveSubpages = $request->getBool( 'wpMovesubpages', false );
+ $this->deleteAndMove = $request->getBool( 'wpDeleteAndMove' ) && $request->getBool( 'wpConfirm' );
+ $this->moveOverShared = $request->getBool( 'wpMoveOverSharedFile', false );
+ $this->watch = $request->getCheck( 'wpWatch' ) && $user->isLoggedIn();
- if ( 'submit' == $wgRequest->getVal( 'action' ) && $wgRequest->wasPosted()
- && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
+ if ( 'submit' == $request->getVal( 'action' ) && $request->wasPosted()
+ && $user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
$this->doSubmit();
} else {
- $this->showForm( '' );
+ $this->showForm( array() );
}
}
/**
* Show the form
*
- * @param $err Mixed: error message. May either be a string message name or
- * array message name and parameters, like the second argument to
- * OutputPage::wrapWikiMsg().
+ * @param $err Array: error messages. Each item is an error message.
+ * It may either be a string message name or array message name and
+ * parameters, like the second argument to OutputPage::wrapWikiMsg().
*/
function showForm( $err ) {
- global $wgOut, $wgUser, $wgContLang, $wgFixDoubleRedirects;
-
- $skin = $this->getSkin();
+ global $wgContLang, $wgFixDoubleRedirects, $wgMaximumMovedPages;
- $oldTitleLink = $skin->link( $this->oldTitle );
+ $this->getSkin()->setRelevantTitle( $this->oldTitle );
- $wgOut->setPagetitle( wfMsg( 'move-page', $this->oldTitle->getPrefixedText() ) );
- $skin->setRelevantTitle( $this->oldTitle );
+ $oldTitleLink = Linker::link( $this->oldTitle );
- $wgOut->addModules( 'mediawiki.special.movePage' );
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'move-page', $this->oldTitle->getPrefixedText() ) );
+ $out->addModules( 'mediawiki.special.movePage' );
$newTitle = $this->newTitle;
- if( !$newTitle ) {
+ if ( !$newTitle ) {
# Show the current title as a default
# when the form is first opened.
$newTitle = $this->oldTitle;
- }
- else {
- if( empty($err) ) {
- # If a title was supplied, probably from the move log revert
- # link, check for validity. We can then show some diagnostic
- # information and save a click.
- $newerr = $this->oldTitle->isValidMoveOperation( $newTitle );
- if( $newerr ) {
- $err = $newerr[0];
- }
+ } elseif ( !count( $err ) ) {
+ # If a title was supplied, probably from the move log revert
+ # link, check for validity. We can then show some diagnostic
+ # information and save a click.
+ $newerr = $this->oldTitle->isValidMoveOperation( $newTitle );
+ if( is_array( $newerr ) ) {
+ $err = $newerr;
}
}
- if ( !empty($err) && $err[0] == 'articleexists' && $wgUser->isAllowed( 'delete' ) ) {
- $wgOut->addWikiMsg( 'delete_and_move_text', $newTitle->getPrefixedText() );
+ $user = $this->getUser();
+
+ if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'articleexists'
+ && $newTitle->quickUserCan( 'delete', $user )
+ ) {
+ $out->addWikiMsg( 'delete_and_move_text', $newTitle->getPrefixedText() );
$movepagebtn = wfMsg( 'delete_and_move' );
$submitVar = 'wpDeleteAndMove';
$confirm = "
@@ -148,26 +149,38 @@ class MovePageForm extends UnlistedSpecialPage {
Xml::checkLabel( wfMsg( 'delete_and_move_confirm' ), 'wpConfirm', 'wpConfirm' ) .
"</td>
</tr>";
- $err = '';
+ $err = array();
} else {
if ($this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
- $wgOut->wrapWikiMsg( "<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>", 'moveuserpage-warning' );
+ $out->wrapWikiMsg( "<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>", 'moveuserpage-warning' );
}
- $wgOut->addWikiMsg( $wgFixDoubleRedirects ? 'movepagetext' :
+ $out->addWikiMsg( $wgFixDoubleRedirects ? 'movepagetext' :
'movepagetext-noredirectfixer' );
$movepagebtn = wfMsg( 'movepagebtn' );
$submitVar = 'wpMove';
$confirm = false;
}
- if ( !empty($err) && $err[0] == 'file-exists-sharedrepo' && $wgUser->isAllowed( 'reupload-shared' ) ) {
- $wgOut->addWikiMsg( 'move-over-sharedrepo', $newTitle->getPrefixedText() );
+ if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'file-exists-sharedrepo'
+ && $user->isAllowed( 'reupload-shared' )
+ ) {
+ $out->addWikiMsg( 'move-over-sharedrepo', $newTitle->getPrefixedText() );
$submitVar = 'wpMoveOverSharedFile';
- $err = '';
+ $err = array();
}
$oldTalk = $this->oldTitle->getTalkPage();
- $considerTalk = ( !$this->oldTitle->isTalkPage() && $oldTalk->exists() );
+ $oldTitleSubpages = $this->oldTitle->hasSubpages();
+ $oldTitleTalkSubpages = $this->oldTitle->getTalkPage()->hasSubpages();
+
+ $canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) &&
+ !count( $this->oldTitle->getUserPermissionsErrors( 'move-subpages', $user ) );
+
+ # We also want to be able to move assoc. subpage talk-pages even if base page
+ # has no associated talk page, so || with $oldTitleTalkSubpages.
+ $considerTalk = !$this->oldTitle->isTalkPage() &&
+ ( $oldTalk->exists()
+ || ( $oldTitleTalkSubpages && $canMoveSubpage ) );
$dbr = wfGetDB( DB_SLAVE );
if ( $wgFixDoubleRedirects ) {
@@ -181,20 +194,36 @@ class MovePageForm extends UnlistedSpecialPage {
}
if ( $considerTalk ) {
- $wgOut->addWikiMsg( 'movepagetalktext' );
+ $out->addWikiMsg( 'movepagetalktext' );
}
- $token = htmlspecialchars( $wgUser->editToken() );
+ if ( count( $err ) ) {
+ $out->addHTML( "<div class='error'>\n" );
+ $action_desc = $this->msg( 'action-move' )->plain();
+ $out->addWikiMsg( 'permissionserrorstext-withaction', count( $err ), $action_desc );
- if ( !empty($err) ) {
- $wgOut->setSubtitle( wfMsg( 'formerror' ) );
- if( $err[0] == 'hookaborted' ) {
- $hookErr = $err[1];
- $errMsg = "<p><strong class=\"error\">$hookErr</strong></p>\n";
- $wgOut->addHTML( $errMsg );
+ if ( count( $err ) == 1 ) {
+ $errMsg = $err[0];
+ $errMsgName = array_shift( $errMsg );
+ if ( $errMsgName == 'hookaborted' ) {
+ $out->addHTML( "<p>{$errMsg[0]}</p>\n" );
+ } else {
+ $out->addWikiMsgArray( $errMsgName, $errMsg );
+ }
} else {
- $wgOut->wrapWikiMsg( "<p><strong class=\"error\">\n$1\n</strong></p>", $err );
+ $errStr = array();
+ foreach( $err as $errMsg ) {
+ if( $errMsg[0] == 'hookaborted' ) {
+ $errStr[] = $errMsg[1];
+ } else {
+ $errMsgName = array_shift( $errMsg );
+ $errStr[] = $this->msg( $errMsgName, $errMsg )->parse();
+ }
+ }
+
+ $out->addHTML( '<ul><li>' . implode( "</li>\n<li>", $errStr ) . "</li></ul>\n" );
}
+ $out->addHTML( "</div>\n" );
}
if ( $this->oldTitle->isProtected( 'move' ) ) {
@@ -207,13 +236,16 @@ class MovePageForm extends UnlistedSpecialPage {
$noticeMsg = 'protectedpagemovewarning';
$classes[] = 'mw-textarea-protected';
}
- $wgOut->addHTML( "<div class='mw-warning-with-logexcerpt'>\n" );
- $wgOut->addWikiMsg( $noticeMsg );
- LogEventsList::showLogExtract( $wgOut, 'protect', $this->oldTitle->getPrefixedText(), '', array( 'lim' => 1 ) );
- $wgOut->addHTML( "</div>\n" );
+ $out->addHTML( "<div class='mw-warning-with-logexcerpt'>\n" );
+ $out->addWikiMsg( $noticeMsg );
+ LogEventsList::showLogExtract( $out, 'protect', $this->oldTitle, '', array( 'lim' => 1 ) );
+ $out->addHTML( "</div>\n" );
}
- $wgOut->addHTML(
+ // Byte limit (not string length limit) for wpReason and wpNewTitleMain
+ // is enforced in the mediawiki.special.movePage module
+
+ $out->addHTML(
Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL( 'action=submit' ), 'id' => 'movepage' ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'move-page-legend' ) ) .
@@ -228,10 +260,18 @@ class MovePageForm extends UnlistedSpecialPage {
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( wfMsg( 'newtitle' ), 'wpNewTitle' ) .
+ Xml::label( wfMsg( 'newtitle' ), 'wpNewTitleMain' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'wpNewTitle', 40, $wgContLang->recodeForEdit( $newTitle->getPrefixedText() ), array( 'type' => 'text', 'id' => 'wpNewTitle' ) ) .
+ Html::namespaceSelector(
+ array( 'selected' => $newTitle->getNamespace() ),
+ array( 'name' => 'wpNewTitleNs', 'id' => 'wpNewTitleNs' )
+ ) .
+ Xml::input( 'wpNewTitleMain', 60, $wgContLang->recodeForEdit( $newTitle->getText() ), array(
+ 'type' => 'text',
+ 'id' => 'wpNewTitleMain',
+ 'maxlength' => 255,
+ ) ) .
Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
"</td>
</tr>
@@ -241,13 +281,13 @@ 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 byte limit is enforce in mediawiki.special.movePage.js
+ 'maxlength' => 200 ), $this->reason ) .
"</td>
</tr>"
);
if( $considerTalk ) {
- $wgOut->addHTML( "
+ $out->addHTML( "
<tr>
<td></td>
<td class='mw-input'>" .
@@ -257,8 +297,8 @@ class MovePageForm extends UnlistedSpecialPage {
);
}
- if ( $wgUser->isAllowed( 'suppressredirect' ) ) {
- $wgOut->addHTML( "
+ if ( $user->isAllowed( 'suppressredirect' ) ) {
+ $out->addHTML( "
<tr>
<td></td>
<td class='mw-input' >" .
@@ -270,7 +310,7 @@ class MovePageForm extends UnlistedSpecialPage {
}
if ( $hasRedirects ) {
- $wgOut->addHTML( "
+ $out->addHTML( "
<tr>
<td></td>
<td class='mw-input' >" .
@@ -281,12 +321,8 @@ class MovePageForm extends UnlistedSpecialPage {
);
}
- if( ($this->oldTitle->hasSubpages() || $this->oldTitle->getTalkPage()->hasSubpages())
- && $this->oldTitle->userCan( 'move-subpages' ) )
- {
- global $wgMaximumMovedPages, $wgLang;
-
- $wgOut->addHTML( "
+ if( $canMoveSubpage ) {
+ $out->addHTML( "
<tr>
<td></td>
<td class=\"mw-input\">" .
@@ -303,7 +339,7 @@ class MovePageForm extends UnlistedSpecialPage {
? 'move-subpages'
: 'move-talk-subpages' ),
array( 'parseinline' ),
- $wgLang->formatNum( $wgMaximumMovedPages ),
+ $this->getLanguage()->formatNum( $wgMaximumMovedPages ),
# $2 to allow use of PLURAL in message.
$wgMaximumMovedPages
)
@@ -313,11 +349,11 @@ class MovePageForm extends UnlistedSpecialPage {
);
}
- $watchChecked = $wgUser->isLoggedIn() && ($this->watch || $wgUser->getBoolOption( 'watchmoves' )
+ $watchChecked = $user->isLoggedIn() && ($this->watch || $user->getBoolOption( 'watchmoves' )
|| $this->oldTitle->userIsWatching());
# Don't allow watching if user is not logged in
- if( $wgUser->isLoggedIn() ) {
- $wgOut->addHTML( "
+ if( $user->isLoggedIn() ) {
+ $out->addHTML( "
<tr>
<td></td>
<td class='mw-input'>" .
@@ -326,7 +362,7 @@ class MovePageForm extends UnlistedSpecialPage {
</tr>");
}
- $wgOut->addHTML( "
+ $out->addHTML( "
{$confirm}
<tr>
<td>&#160;</td>
@@ -335,69 +371,74 @@ class MovePageForm extends UnlistedSpecialPage {
"</td>
</tr>" .
Xml::closeElement( 'table' ) .
- Html::hidden( 'wpEditToken', $token ) .
+ Html::hidden( 'wpEditToken', $user->getEditToken() ) .
Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' ) .
"\n"
);
- $this->showLogFragment( $this->oldTitle, $wgOut );
- $this->showSubpages( $this->oldTitle, $wgOut );
+ $this->showLogFragment( $this->oldTitle );
+ $this->showSubpages( $this->oldTitle );
}
function doSubmit() {
- global $wgOut, $wgUser, $wgMaximumMovedPages, $wgLang;
- global $wgFixDoubleRedirects;
+ global $wgMaximumMovedPages, $wgFixDoubleRedirects, $wgDeleteRevisionsLimit;
- if ( $wgUser->pingLimiter( 'move' ) ) {
- $wgOut->rateLimited();
- return;
+ $user = $this->getUser();
+
+ if ( $user->pingLimiter( 'move' ) ) {
+ throw new ThrottledError;
}
$ot = $this->oldTitle;
$nt = $this->newTitle;
- # Delete to make way if requested
- if ( $wgUser->isAllowed( 'delete' ) && $this->deleteAndMove ) {
- $article = new Article( $nt );
-
- # Disallow deletions of big articles
- $bigHistory = $article->isBigDeletion();
- if( $bigHistory && !$nt->userCan( 'bigdelete' ) ) {
- global $wgDeleteRevisionsLimit;
- $this->showForm( array('delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
- return;
- }
-
- // Delete an associated image if there is
- $file = wfLocalFile( $nt );
- if( $file->exists() ) {
- $file->delete( wfMsgForContent( 'delete_and_move_reason' ), false );
- }
-
- // This may output an error message and exit
- $article->doDelete( wfMsgForContent( 'delete_and_move_reason' ) );
- }
-
# don't allow moving to pages with # in
if ( !$nt || $nt->getFragment() != '' ) {
- $this->showForm( 'badtitletext' );
+ $this->showForm( array( array( 'badtitletext' ) ) );
return;
}
# Show a warning if the target file exists on a shared repo
if ( $nt->getNamespace() == NS_FILE
- && !( $this->moveOverShared && $wgUser->isAllowed( 'reupload-shared' ) )
+ && !( $this->moveOverShared && $user->isAllowed( 'reupload-shared' ) )
&& !RepoGroup::singleton()->getLocalRepo()->findFile( $nt )
&& wfFindFile( $nt ) )
{
- $this->showForm( array('file-exists-sharedrepo') );
+ $this->showForm( array( array( 'file-exists-sharedrepo' ) ) );
return;
}
- if ( $wgUser->isAllowed( 'suppressredirect' ) ) {
+ # Delete to make way if requested
+ if ( $this->deleteAndMove ) {
+ $permErrors = $nt->getUserPermissionsErrors( 'delete', $user );
+ if ( count( $permErrors ) ) {
+ # Only show the first error
+ $this->showForm( $permErrors );
+ return;
+ }
+
+ $reason = wfMessage( 'delete_and_move_reason', $ot )->inContentLanguage()->text();
+
+ // Delete an associated image if there is
+ if ( $nt->getNamespace() == NS_FILE ) {
+ $file = wfLocalFile( $nt );
+ if ( $file->exists() ) {
+ $file->delete( $reason, false );
+ }
+ }
+
+ $error = ''; // passed by ref
+ $page = WikiPage::factory( $nt );
+ if ( !$page->doDeleteArticle( $reason, false, 0, true, $error, $user ) ) {
+ $this->showForm( array( array( 'cannotdelete', wfEscapeWikiText( $nt->getPrefixedText() ) ) ) );
+ return;
+ }
+ }
+
+ if ( $user->isAllowed( 'suppressredirect' ) ) {
$createRedirect = $this->leaveRedirect;
} else {
$createRedirect = true;
@@ -406,8 +447,7 @@ class MovePageForm extends UnlistedSpecialPage {
# Do the actual move.
$error = $ot->moveTo( $nt, true, $this->reason, $createRedirect );
if ( $error !== true ) {
- # @todo FIXME: Show all the errors in a list, not just the first one
- $this->showForm( reset( $error ) );
+ $this->showForm( $error );
return;
}
@@ -417,18 +457,23 @@ class MovePageForm extends UnlistedSpecialPage {
wfRunHooks( 'SpecialMovepageAfterMove', array( &$this, &$ot, &$nt ) );
- $wgOut->setPagetitle( wfMsg( 'pagemovedsub' ) );
+ $out = $this->getOutput();
+ $out->setPagetitle( wfMsg( 'pagemovedsub' ) );
- $oldUrl = $ot->getFullUrl( 'redirect=no' );
- $newUrl = $nt->getFullUrl();
+ $oldLink = Linker::link(
+ $ot,
+ null,
+ array(),
+ array( 'redirect' => 'no' )
+ );
+ $newLink = Linker::linkKnown( $nt );
$oldText = $ot->getPrefixedText();
$newText = $nt->getPrefixedText();
- $oldLink = "<span class='plainlinks'>[$oldUrl $oldText]</span>";
- $newLink = "<span class='plainlinks'>[$newUrl $newText]</span>";
$msgName = $createRedirect ? 'movepage-moved-redirect' : 'movepage-moved-noredirect';
- $wgOut->addWikiMsg( 'movepage-moved', $oldLink, $newLink, $oldText, $newText );
- $wgOut->addWikiMsg( $msgName );
+ $out->addHTML( wfMessage( 'movepage-moved' )->rawParams( $oldLink,
+ $newLink )->params( $oldText, $newText )->parseAsBlock() );
+ $out->addWikiMsg( $msgName );
# Now we move extra pages we've been asked to move: subpages and talk
# pages. First, if the old page or the new page is a talk page, we
@@ -437,7 +482,7 @@ class MovePageForm extends UnlistedSpecialPage {
$this->moveTalk = false;
}
- if( !$ot->userCan( 'move-subpages' ) ) {
+ if ( count( $ot->getUserPermissionsErrors( 'move-subpages', $user ) ) ) {
$this->moveSubpages = false;
}
@@ -494,7 +539,6 @@ class MovePageForm extends UnlistedSpecialPage {
}
$extraOutput = array();
- $skin = $this->getSkin();
$count = 1;
foreach( $extraPages as $oldSubpage ) {
if( $ot->equals( $oldSubpage ) ) {
@@ -516,7 +560,7 @@ class MovePageForm extends UnlistedSpecialPage {
# be longer than 255 characters.
$newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
if( !$newSubpage ) {
- $oldLink = $skin->linkKnown( $oldSubpage );
+ $oldLink = Linker::linkKnown( $oldSubpage );
$extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink,
htmlspecialchars(Title::makeName( $newNs, $newPageName )));
continue;
@@ -524,7 +568,7 @@ class MovePageForm extends UnlistedSpecialPage {
# This was copy-pasted from Renameuser, bleh.
if ( $newSubpage->exists() && !$oldSubpage->isValidMoveTarget( $newSubpage ) ) {
- $link = $skin->linkKnown( $newSubpage );
+ $link = Linker::linkKnown( $newSubpage );
$extraOutput []= wfMsgHtml( 'movepage-page-exists', $link );
} else {
$success = $oldSubpage->moveTo( $newSubpage, true, $this->reason, $createRedirect );
@@ -532,22 +576,22 @@ class MovePageForm extends UnlistedSpecialPage {
if ( $this->fixRedirects ) {
DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage );
}
- $oldLink = $skin->linkKnown(
+ $oldLink = Linker::link(
$oldSubpage,
null,
array(),
array( 'redirect' => 'no' )
);
- $newLink = $skin->linkKnown( $newSubpage );
+ $newLink = Linker::linkKnown( $newSubpage );
$extraOutput []= wfMsgHtml( 'movepage-page-moved', $oldLink, $newLink );
++$count;
if( $count >= $wgMaximumMovedPages ) {
- $extraOutput []= wfMsgExt( 'movepage-max-pages', array( 'parsemag', 'escape' ), $wgLang->formatNum( $wgMaximumMovedPages ) );
+ $extraOutput []= wfMsgExt( 'movepage-max-pages', array( 'parsemag', 'escape' ), $this->getLanguage()->formatNum( $wgMaximumMovedPages ) );
break;
}
} else {
- $oldLink = $skin->linkKnown( $oldSubpage );
- $newLink = $skin->link( $newSubpage );
+ $oldLink = Linker::linkKnown( $oldSubpage );
+ $newLink = Linker::link( $newSubpage );
$extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink, $newLink );
}
}
@@ -555,16 +599,16 @@ class MovePageForm extends UnlistedSpecialPage {
}
if( $extraOutput !== array() ) {
- $wgOut->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $extraOutput ) . "</li>\n</ul>" );
+ $out->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $extraOutput ) . "</li>\n</ul>" );
}
# Deal with watches (we don't watch subpages)
- if( $this->watch && $wgUser->isLoggedIn() ) {
- $wgUser->addWatch( $ot );
- $wgUser->addWatch( $nt );
+ if( $this->watch && $user->isLoggedIn() ) {
+ $user->addWatch( $ot );
+ $user->addWatch( $nt );
} else {
- $wgUser->removeWatch( $ot );
- $wgUser->removeWatch( $nt );
+ $user->removeWatch( $ot );
+ $user->removeWatch( $nt );
}
# Re-clear the file redirect cache, which may have been polluted by
@@ -575,20 +619,20 @@ class MovePageForm extends UnlistedSpecialPage {
}
}
- function showLogFragment( $title, &$out ) {
+ function showLogFragment( $title ) {
+ $out = $this->getOutput();
$out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'move' ) ) );
- LogEventsList::showLogExtract( $out, 'move', $title->getPrefixedText() );
+ LogEventsList::showLogExtract( $out, 'move', $title );
}
- function showSubpages( $title, $out ) {
- global $wgLang;
-
+ function showSubpages( $title ) {
if( !MWNamespace::hasSubpages( $title->getNamespace() ) )
return;
$subpages = $title->getSubpages();
$count = $subpages instanceof TitleArray ? $subpages->count() : 0;
+ $out = $this->getOutput();
$out->wrapWikiMsg( '== $1 ==', array( 'movesubpage', $count ) );
# No subpages.
@@ -597,15 +641,13 @@ class MovePageForm extends UnlistedSpecialPage {
return;
}
- $out->addWikiMsg( 'movesubpagetext', $wgLang->formatNum( $count ) );
- $skin = $this->getSkin();
+ $out->addWikiMsg( 'movesubpagetext', $this->getLanguage()->formatNum( $count ) );
$out->addHTML( "<ul>\n" );
foreach( $subpages as $subpage ) {
- $link = $skin->link( $subpage );
+ $link = Linker::link( $subpage );
$out->addHTML( "<li>$link</li>\n" );
}
$out->addHTML( "</ul>\n" );
}
}
-
diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php
index ea20c2f7..b88123dc 100644
--- a/includes/specials/SpecialNewimages.php
+++ b/includes/specials/SpecialNewimages.php
@@ -30,7 +30,7 @@ class SpecialNewFiles extends IncludableSpecialPage {
$this->setHeaders();
$this->outputHeader();
- $pager = new NewFilesPager( $par );
+ $pager = new NewFilesPager( $this->getContext(), $par );
if ( !$this->including() ) {
$form = $pager->getForm();
@@ -50,18 +50,16 @@ class SpecialNewFiles extends IncludableSpecialPage {
*/
class NewFilesPager extends ReverseChronologicalPager {
- function __construct( $par = null ) {
- global $wgRequest;
+ /**
+ * @var ImageGallery
+ */
+ var $gallery;
- $this->like = $wgRequest->getText( 'like' );
- $this->showbots = $wgRequest->getBool( 'showbots' , 0 );
- $this->skin = $this->getSkin();
+ function __construct( IContextSource $context, $par = null ) {
+ $this->like = $context->getRequest()->getText( 'like' );
+ $this->showbots = $context->getRequest()->getBool( 'showbots' , 0 );
- parent::__construct();
- }
-
- function getTitle() {
- return SpecialPage::getTitleFor( 'Newimages' );
+ parent::__construct( $context );
}
function getQueryInfo() {
@@ -105,7 +103,10 @@ class NewFilesPager extends ReverseChronologicalPager {
}
function getStartBody(){
- $this->gallery = new ImageGallery();
+ if ( !$this->gallery ) {
+ $this->gallery = new ImageGallery();
+ }
+ return '';
}
function getEndBody(){
@@ -113,24 +114,22 @@ class NewFilesPager extends ReverseChronologicalPager {
}
function formatRow( $row ) {
- global $wgLang;
-
$name = $row->img_name;
$user = User::newFromId( $row->img_user );
$title = Title::makeTitle( NS_FILE, $name );
- $ul = $this->skin->link( $user->getUserpage(), $user->getName() );
+ $ul = Linker::link( $user->getUserpage(), $user->getName() );
$this->gallery->add(
$title,
"$ul<br />\n<i>"
- . htmlspecialchars( $wgLang->timeanddate( $row->img_timestamp, true ) )
+ . htmlspecialchars( $this->getLanguage()->timeanddate( $row->img_timestamp, true ) )
. "</i><br />\n"
);
}
function getForm() {
- global $wgRequest, $wgMiserMode;
+ global $wgMiserMode;
$fields = array(
'like' => array(
@@ -142,16 +141,16 @@ class NewFilesPager extends ReverseChronologicalPager {
'type' => 'check',
'label' => wfMessage( 'showhidebots', wfMsg( 'show' ) ),
'name' => 'showbots',
- # 'default' => $wgRequest->getBool( 'showbots', 0 ),
+ # 'default' => $this->getRequest()->getBool( 'showbots', 0 ),
),
'limit' => array(
'type' => 'hidden',
- 'default' => $wgRequest->getText( 'limit' ),
+ 'default' => $this->getRequest()->getText( 'limit' ),
'name' => 'limit',
),
'offset' => array(
'type' => 'hidden',
- 'default' => $wgRequest->getText( 'offset' ),
+ 'default' => $this->getRequest()->getText( 'offset' ),
'name' => 'offset',
),
);
@@ -160,7 +159,7 @@ class NewFilesPager extends ReverseChronologicalPager {
unset( $fields['like'] );
}
- $form = new HTMLForm( $fields );
+ $form = new HTMLForm( $fields, $this->getContext() );
$form->setTitle( $this->getTitle() );
$form->setSubmitText( wfMsg( 'ilsubmit' ) );
$form->setMethod( 'get' );
diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php
index bf9fb9f7..54bcb97f 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -78,7 +78,6 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function parseParams( $par ) {
- global $wgLang;
$bits = preg_split( '/\s*,\s*/', trim( $par ) );
foreach ( $bits as $bit ) {
if ( 'shownav' == $bit ) {
@@ -112,7 +111,7 @@ class SpecialNewpages extends IncludableSpecialPage {
$this->opts->setValue( 'username', $m[1] );
}
if ( preg_match( '/^namespace=(.*)$/', $bit, $m ) ) {
- $ns = $wgLang->getNsIndex( $m[1] );
+ $ns = $this->getLanguage()->getNsIndex( $m[1] );
if( $ns !== false ) {
$this->opts->setValue( 'namespace', $ns );
}
@@ -139,11 +138,12 @@ class SpecialNewpages extends IncludableSpecialPage {
// Settings
$this->form();
- $this->setSyndicated();
$feedType = $this->opts->getValue( 'feed' );
if( $feedType ) {
return $this->feed( $feedType );
}
+
+ $out->setFeedAppendQuery( wfArrayToCGI( $this->opts->getAllValues() ) );
}
$pager = new NewPagesPager( $this, $this->opts );
@@ -162,7 +162,7 @@ class SpecialNewpages extends IncludableSpecialPage {
}
protected function filterLinks() {
- global $wgGroupPermissions, $wgLang;
+ global $wgGroupPermissions;
// show/hide links
$showhide = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) );
@@ -194,13 +194,13 @@ class SpecialNewpages extends IncludableSpecialPage {
$self = $this->getTitle();
foreach ( $filters as $key => $msg ) {
$onoff = 1 - $this->opts->getValue( $key );
- $link = $this->getSkin()->link( $self, $showhide[$onoff], array(),
+ $link = Linker::link( $self, $showhide[$onoff], array(),
array( $key => $onoff ) + $changed
);
$links[$key] = wfMsgHtml( $msg, $link );
}
- return $wgLang->pipeList( $links );
+ return $this->getLanguage()->pipeList( $links );
}
protected function form() {
@@ -276,12 +276,6 @@ class SpecialNewpages extends IncludableSpecialPage {
$this->getOutput()->addHTML( $form );
}
- protected function setSyndicated() {
- $out = $this->getOutput();
- $out->setSyndicated( true );
- $out->setFeedAppendQuery( wfArrayToCGI( $this->opts->getAllValues() ) );
- }
-
/**
* Format a row, providing the timestamp, links to the page/history, size, user links, and a comment
*
@@ -289,8 +283,6 @@ class SpecialNewpages extends IncludableSpecialPage {
* @return String
*/
public function formatRow( $result ) {
- global $wgLang;
-
# Revision deletion works on revisions, so we should cast one
$row = array(
'comment' => $result->rc_comment,
@@ -302,11 +294,19 @@ class SpecialNewpages extends IncludableSpecialPage {
$classes = array();
- $dm = $wgLang->getDirMark();
+ $lang = $this->getLanguage();
+ $dm = $lang->getDirMark();
- $title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title );
- $time = Html::element( 'span', array( 'class' => 'mw-newpages-time' ),
- $wgLang->timeAndDate( $result->rc_timestamp, true )
+ $title = Title::newFromRow( $result );
+ $spanTime = Html::element( 'span', array( 'class' => 'mw-newpages-time' ),
+ $lang->timeanddate( $result->rc_timestamp, true )
+ );
+ $time = Linker::linkKnown(
+ $title,
+ $spanTime,
+ array(),
+ array( 'oldid' => $result->rc_this_oldid ),
+ array()
);
$query = array( 'redirect' => 'no' );
@@ -315,14 +315,14 @@ class SpecialNewpages extends IncludableSpecialPage {
$query['rcid'] = $result->rc_id;
}
- $plink = $this->getSkin()->linkKnown(
+ $plink = Linker::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->getSkin()->linkKnown(
+ $histLink = Linker::linkKnown(
$title,
wfMsgHtml( 'hist' ),
array(),
@@ -330,13 +330,12 @@ class SpecialNewpages extends IncludableSpecialPage {
);
$hist = Html::rawElement( 'span', array( 'class' => 'mw-newpages-history' ), wfMsg( 'parentheses', $histLink ) );
- $length = Html::rawElement( 'span', array( 'class' => 'mw-newpages-length' ),
- '[' . wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->length ) ) .
- ']'
+ $length = Html::element( 'span', array( 'class' => 'mw-newpages-length' ),
+ '[' . $this->msg( 'nbytes' )->numParams( $result->length )->text() . ']'
);
- $ulink = $this->getSkin()->revUserTools( $rev );
- $comment = $this->getSkin()->revComment( $rev );
+ $ulink = Linker::revUserTools( $rev );
+ $comment = Linker::revComment( $rev );
if ( $this->patrollable( $result ) ) {
$classes[] = 'not-patrolled';
@@ -357,7 +356,14 @@ class SpecialNewpages extends IncludableSpecialPage {
$css = count( $classes ) ? ' class="' . implode( ' ', $classes ) . '"' : '';
- return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n";
+ # Display the old title if the namespace has been changed
+ $oldTitleText = '';
+ if ( $result->page_namespace !== $result->rc_namespace ) {
+ $oldTitleText = wfMessage( 'rc-old-title' )->params( Title::makeTitle( $result->rc_namespace, $result->rc_title )
+ ->getPrefixedText() )->escaped();
+ }
+
+ return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay} {$oldTitleText}</li>\n";
}
/**
@@ -461,33 +467,11 @@ class NewPagesPager extends ReverseChronologicalPager {
protected $mForm;
function __construct( $form, FormOptions $opts ) {
- parent::__construct();
+ parent::__construct( $form->getContext() );
$this->mForm = $form;
$this->opts = $opts;
}
- /**
- * @return Title
- */
- function getTitle() {
- static $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;
$conds = array();
@@ -531,7 +515,8 @@ class NewPagesPager extends ReverseChronologicalPager {
$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'
+ 'page_len AS length', 'page_latest AS rev_id', 'ts_tags', 'rc_this_oldid',
+ 'page_namespace', 'page_title'
);
$join_conds = array( 'page' => array( 'INNER JOIN', 'page_id=rc_cur_id' ) );
diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php
index db5268d6..62731e98 100644
--- a/includes/specials/SpecialPasswordReset.php
+++ b/includes/specials/SpecialPasswordReset.php
@@ -1,6 +1,6 @@
<?php
/**
- * Implements Special:Blankpage
+ * Implements Special:PasswordReset
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,19 +28,33 @@
*/
class SpecialPasswordReset extends FormSpecialPage {
+ /**
+ * @var Message
+ */
+ private $email;
+
+ /**
+ * @var Status
+ */
+ private $result;
+
public function __construct() {
parent::__construct( 'PasswordReset' );
}
public function userCanExecute( User $user ) {
+ return $this->canChangePassword( $user ) === true && parent::userCanExecute( $user );
+ }
+
+ public function checkExecutePermissions( User $user ) {
$error = $this->canChangePassword( $user );
if ( is_string( $error ) ) {
throw new ErrorPageError( 'internalerror', $error );
- } else if ( !$error ) {
+ } elseif ( !$error ) {
throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' );
}
- return parent::userCanExecute( $user );
+ return parent::checkExecutePermissions( $user );
}
protected function getFormFields() {
@@ -69,6 +83,14 @@ class SpecialPasswordReset extends FormSpecialPage {
);
}
+ if( $this->getUser()->isAllowed( 'passwordreset' ) ){
+ $a['Capture'] = array(
+ 'type' => 'check',
+ 'label-message' => 'passwordreset-capture',
+ 'help-message' => 'passwordreset-capture-help',
+ );
+ }
+
return $a;
}
@@ -109,6 +131,16 @@ class SpecialPasswordReset extends FormSpecialPage {
}
}
+ if( isset( $data['Capture'] ) && !$this->getUser()->isAllowed( 'passwordreset' ) ){
+ // The user knows they don't have the passwordreset permission, but they tried to spoof the form. That's naughty
+ throw new PermissionsError( 'passwordreset' );
+ }
+
+ /**
+ * @var $firstUser User
+ * @var $users User[]
+ */
+
if ( isset( $data['Username'] ) && $data['Username'] !== '' ) {
$method = 'username';
$users = array( User::newFromName( $data['Username'] ) );
@@ -183,7 +215,7 @@ class SpecialPasswordReset extends FormSpecialPage {
// 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();
+ $ip = $this->getRequest()->getIP();
if ( !$ip ) {
return array( 'badipaddress' );
}
@@ -207,8 +239,8 @@ class SpecialPasswordReset extends FormSpecialPage {
}
$passwordBlock = implode( "\n\n", $passwords );
- $body = wfMessage( $msg )->inLanguage( $userLanguage );
- $body->params(
+ $this->email = wfMessage( $msg )->inLanguage( $userLanguage );
+ $this->email->params(
$username,
$passwordBlock,
count( $passwords ),
@@ -218,28 +250,49 @@ class SpecialPasswordReset extends FormSpecialPage {
$title = wfMessage( 'passwordreset-emailtitle' );
- $result = $firstUser->sendMail( $title->text(), $body->text() );
+ $this->result = $firstUser->sendMail( $title->escaped(), $this->email->escaped() );
+
+ // Blank the email if the user is not supposed to see it
+ if( !isset( $data['Capture'] ) || !$data['Capture'] ) {
+ $this->email = null;
+ }
- if ( $result->isGood() ) {
+ if ( $this->result->isGood() ) {
+ return true;
+ } elseif( isset( $data['Capture'] ) && $data['Capture'] ){
+ // The email didn't send, but maybe they knew that and that's why they captured it
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() ) );
+ return array( array( 'mailerror', $this->result->getMessage() ) );
}
}
public function onSuccess() {
+ if( $this->getUser()->isAllowed( 'passwordreset' ) && $this->email != null ){
+ // @todo: Logging
+
+ if( $this->result->isGood() ){
+ $this->getOutput()->addWikiMsg( 'passwordreset-emailsent-capture' );
+ } else {
+ $this->getOutput()->addWikiMsg( 'passwordreset-emailerror-capture', $this->result->getMessage() );
+ }
+
+ $this->getOutput()->addHTML( Html::rawElement( 'pre', array(), $this->email->escaped() ) );
+ }
+
$this->getOutput()->addWikiMsg( 'passwordreset-emailsent' );
$this->getOutput()->returnToMain();
}
- function canChangePassword(User $user) {
+ protected 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 ) ) ) {
+ !in_array( true, array_values( $wgPasswordResetRoutes ) ) )
+ {
return 'passwordreset-disabled';
}
@@ -257,15 +310,12 @@ class SpecialPasswordReset extends FormSpecialPage {
return true;
}
-
/**
* Hide the password reset page if resets are disabled.
* @return Bool
*/
function isListed() {
- global $wgUser;
-
- if ( $this->canChangePassword( $wgUser ) === true ) {
+ if ( $this->canChangePassword( $this->getUser() ) === true ) {
return parent::isListed();
}
diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php
index 7c7190ad..803f03e7 100644
--- a/includes/specials/SpecialPopularpages.php
+++ b/includes/specials/SpecialPopularpages.php
@@ -36,6 +36,7 @@ class PopularPagesPage extends QueryPage {
# page_counter is not indexed
return true;
}
+
function isSyndicated() { return false; }
function getQueryInfo() {
@@ -54,17 +55,13 @@ class PopularPagesPage extends QueryPage {
* @return string
*/
function formatResult( $skin, $result ) {
- global $wgLang, $wgContLang;
+ global $wgContLang;
$title = Title::makeTitle( $result->namespace, $result->title );
- $link = $skin->linkKnown(
+ $link = Linker::linkKnown(
$title,
htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) )
);
- $nv = wfMsgExt(
- 'nviews',
- array( 'parsemag', 'escape'),
- $wgLang->formatNum( $result->value )
- );
- return wfSpecialList($link, $nv);
+ $nv = $this->msg( 'nviews' )->numParams( $result->value )->escaped();
+ return $this->getLanguage()->specialList( $link, $nv );
}
}
diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php
index edc26bc1..946112bf 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -31,70 +31,60 @@ class SpecialPreferences extends SpecialPage {
parent::__construct( 'Preferences' );
}
- function execute( $par ) {
- global $wgOut, $wgUser, $wgRequest;
-
+ public function execute( $par ) {
$this->setHeaders();
$this->outputHeader();
- $wgOut->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc.
+ $out = $this->getOutput();
+ $out->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc.
- if ( $wgUser->isAnon() ) {
- $wgOut->showErrorPage( 'prefsnologin', 'prefsnologintext', array( $this->getTitle()->getPrefixedDBkey() ) );
- return;
- }
- if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
+ $user = $this->getUser();
+ if ( $user->isAnon() ) {
+ $out->showErrorPage( 'prefsnologin', 'prefsnologintext', array( $this->getTitle()->getPrefixedDBkey() ) );
return;
}
+ $this->checkReadOnly();
if ( $par == 'reset' ) {
$this->showResetForm();
return;
}
- $wgOut->addModules( 'mediawiki.special.preferences' );
+ $out->addModules( 'mediawiki.special.preferences' );
- if ( $wgRequest->getCheck( 'success' ) ) {
- $wgOut->wrapWikiMsg(
+ if ( $this->getRequest()->getCheck( 'success' ) ) {
+ $out->wrapWikiMsg(
"<div class=\"successbox\"><strong>\n$1\n</strong></div><div id=\"mw-pref-clear\"></div>",
'savedprefs'
);
}
- if ( $wgRequest->getCheck( 'eauth' ) ) {
- $wgOut->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>",
- 'eauthentsent', $wgUser->getName() );
- }
-
- $htmlForm = Preferences::getFormObject( $wgUser );
+ $htmlForm = Preferences::getFormObject( $user, $this->getContext() );
$htmlForm->setSubmitCallback( array( 'Preferences', 'tryUISubmit' ) );
$htmlForm->show();
}
- function showResetForm() {
- global $wgOut;
-
- $wgOut->addWikiMsg( 'prefs-reset-intro' );
+ private function showResetForm() {
+ $this->getOutput()->addWikiMsg( 'prefs-reset-intro' );
$htmlForm = new HTMLForm( array(), $this->getContext(), 'prefs-restore' );
$htmlForm->setSubmitText( wfMsg( 'restoreprefs' ) );
$htmlForm->setTitle( $this->getTitle( 'reset' ) );
- $htmlForm->setSubmitCallback( array( __CLASS__, 'submitReset' ) );
+ $htmlForm->setSubmitCallback( array( $this, 'submitReset' ) );
$htmlForm->suppressReset();
$htmlForm->show();
}
- static function submitReset( $formData ) {
- global $wgUser, $wgOut;
- $wgUser->resetOptions();
- $wgUser->saveSettings();
+ public function submitReset( $formData ) {
+ $user = $this->getUser();
+ $user->resetOptions();
+ $user->saveSettings();
$url = SpecialPage::getTitleFor( 'Preferences' )->getFullURL( 'success' );
- $wgOut->redirect( $url );
+ $this->getOutput()->redirect( $url );
return true;
}
diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php
index 28be4daf..495f15f7 100644
--- a/includes/specials/SpecialPrefixindex.php
+++ b/includes/specials/SpecialPrefixindex.php
@@ -38,23 +38,26 @@ class SpecialPrefixindex extends SpecialAllpages {
* @param $par String: becomes "FOO" when called like Special:Prefixindex/FOO (default null)
*/
function execute( $par ) {
- global $wgRequest, $wgOut, $wgContLang;
+ global $wgContLang;
$this->setHeaders();
$this->outputHeader();
- $wgOut->addModuleStyles( 'mediawiki.special' );
+
+ $out = $this->getOutput();
+ $out->addModuleStyles( 'mediawiki.special' );
# GET values
- $from = $wgRequest->getVal( 'from', '' );
- $prefix = $wgRequest->getVal( 'prefix', '' );
- $ns = $wgRequest->getIntOrNull( 'namespace' );
+ $request = $this->getRequest();
+ $from = $request->getVal( 'from', '' );
+ $prefix = $request->getVal( 'prefix', '' );
+ $ns = $request->getIntOrNull( 'namespace' );
$namespace = (int)$ns; // if no namespace given, use 0 (NS_MAIN).
$namespaces = $wgContLang->getNamespaces();
- $wgOut->setPagetitle(
+ $out->setPageTitle(
( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) )
- ? wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) )
- : wfMsg( 'prefixindex' )
+ ? $this->msg( 'prefixindex-namespace', str_replace( '_', ' ', $namespaces[$namespace] ) )
+ : $this->msg( 'prefixindex' )
);
$showme = '';
@@ -62,8 +65,9 @@ class SpecialPrefixindex extends SpecialAllpages {
$showme = $par;
} elseif( $prefix != '' ) {
$showme = $prefix;
- } elseif( $from != '' ) {
+ } elseif( $from != '' && $ns === null ) {
// For back-compat with Special:Allpages
+ // Don't do this if namespace is passed, so paging works when doing NS views.
$showme = $from;
}
@@ -71,7 +75,7 @@ class SpecialPrefixindex extends SpecialAllpages {
if ( $this->including() || $showme != '' || $ns !== null ) {
$this->showPrefixChunk( $namespace, $showme, $from );
} else {
- $wgOut->addHTML( $this->namespacePrefixForm( $namespace, null ) );
+ $out->addHTML( $this->namespacePrefixForm( $namespace, null ) );
}
}
@@ -82,11 +86,10 @@ class SpecialPrefixindex extends SpecialAllpages {
*/
function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ) {
global $wgScript;
- $t = $this->getTitle();
$out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
$out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
- $out .= Html::hidden( 'title', $t->getPrefixedText() );
+ $out .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
$out .= Xml::openElement( 'fieldset' );
$out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) );
$out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
@@ -120,11 +123,11 @@ 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, $wgContLang, $wgLang;
-
- $sk = $this->getSkin();
+ global $wgContLang;
- if (!isset($from)) $from = $prefix;
+ if ( $from === null ) {
+ $from = $prefix;
+ }
$fromList = $this->getNamespaceKeyAndText($namespace, $from);
$prefixList = $this->getNamespaceKeyAndText($namespace, $prefix);
@@ -169,7 +172,7 @@ class SpecialPrefixindex extends SpecialAllpages {
$t = Title::makeTitle( $s->page_namespace, $s->page_title );
if( $t ) {
$link = ($s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
- $sk->linkKnown(
+ Linker::linkKnown(
$t,
htmlspecialchars( $t->getText() )
) .
@@ -214,10 +217,11 @@ class SpecialPrefixindex extends SpecialAllpages {
'prefix' => $prefix
);
- if( $namespace ) {
+ if( $namespace || ($prefix == '')) {
+ // Keep the namespace even if it's 0 for empty prefixes.
+ // This tells us we're not just a holdover from old links.
$query['namespace'] = $namespace;
}
-
$nextLink = Linker::linkKnown(
$self,
wfMsgHtml( 'nextpage', str_replace( '_',' ', htmlspecialchars( $s->page_title ) ) ),
diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php
index b1f61f09..eec974fe 100644
--- a/includes/specials/SpecialProtectedpages.php
+++ b/includes/specials/SpecialProtectedpages.php
@@ -36,8 +36,6 @@ class SpecialProtectedpages extends SpecialPage {
}
public function execute( $par ) {
- global $wgOut, $wgRequest;
-
$this->setHeaders();
$this->outputHeader();
@@ -46,17 +44,18 @@ class SpecialProtectedpages extends SpecialPage {
Title::purgeExpiredRestrictions();
}
- $type = $wgRequest->getVal( $this->IdType );
- $level = $wgRequest->getVal( $this->IdLevel );
- $sizetype = $wgRequest->getVal( 'sizetype' );
- $size = $wgRequest->getIntOrNull( 'size' );
- $NS = $wgRequest->getIntOrNull( 'namespace' );
- $indefOnly = $wgRequest->getBool( 'indefonly' ) ? 1 : 0;
- $cascadeOnly = $wgRequest->getBool('cascadeonly') ? 1 : 0;
+ $request = $this->getRequest();
+ $type = $request->getVal( $this->IdType );
+ $level = $request->getVal( $this->IdLevel );
+ $sizetype = $request->getVal( 'sizetype' );
+ $size = $request->getIntOrNull( 'size' );
+ $NS = $request->getIntOrNull( 'namespace' );
+ $indefOnly = $request->getBool( 'indefonly' ) ? 1 : 0;
+ $cascadeOnly = $request->getBool('cascadeonly') ? 1 : 0;
$pager = new ProtectedPagesPager( $this, array(), $type, $level, $NS, $sizetype, $size, $indefOnly, $cascadeOnly );
- $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size, $indefOnly, $cascadeOnly ) );
+ $this->getOutput()->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size, $indefOnly, $cascadeOnly ) );
if( $pager->getNumRows() ) {
$s = $pager->getNavigationBar();
@@ -67,7 +66,7 @@ class SpecialProtectedpages extends SpecialPage {
} else {
$s = '<p>' . wfMsgHtml( 'protectedpagesempty' ) . '</p>';
}
- $wgOut->addHTML( $s );
+ $this->getOutput()->addHTML( $s );
}
/**
@@ -76,19 +75,16 @@ class SpecialProtectedpages extends SpecialPage {
* @return string Formatted <li> element
*/
public function formatRow( $row ) {
- global $wgUser, $wgLang;
-
wfProfileIn( __METHOD__ );
- static $skin = null, $infinity = null;
+ static $infinity = null;
- if( is_null( $skin ) ){
- $skin = $wgUser->getSkin();
+ if( is_null( $infinity ) ){
$infinity = wfGetDB( DB_SLAVE )->getInfinity();
}
$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
- $link = $skin->link( $title );
+ $link = Linker::link( $title );
$description_items = array ();
@@ -101,27 +97,28 @@ class SpecialProtectedpages extends SpecialPage {
}
$stxt = '';
+ $lang = $this->getLanguage();
- $expiry = $wgLang->formatExpiry( $row->pr_expiry, TS_MW );
+ $expiry = $lang->formatExpiry( $row->pr_expiry, TS_MW );
if( $expiry != $infinity ) {
$expiry_description = wfMsg(
- 'protect-expiring',
- $wgLang->timeanddate( $expiry ),
- $wgLang->date( $expiry ),
- $wgLang->time( $expiry )
+ 'protect-expiring-local',
+ $lang->timeanddate( $expiry, true ),
+ $lang->date( $expiry, true ),
+ $lang->time( $expiry, true )
);
$description_items[] = htmlspecialchars($expiry_description);
}
if(!is_null($size = $row->page_len)) {
- $stxt = $wgLang->getDirMark() . ' ' . $skin->formatRevisionSize( $size );
+ $stxt = $lang->getDirMark() . ' ' . Linker::formatRevisionSize( $size );
}
# Show a link to the change protection form for allowed users otherwise a link to the protection log
- if( $wgUser->isAllowed( 'protect' ) ) {
- $changeProtection = ' (' . $skin->linkKnown(
+ if( $this->getUser()->isAllowed( 'protect' ) ) {
+ $changeProtection = ' (' . Linker::linkKnown(
$title,
wfMsgHtml( 'protect_change' ),
array(),
@@ -129,7 +126,7 @@ class SpecialProtectedpages extends SpecialPage {
) . ')';
} else {
$ltitle = SpecialPage::getTitleFor( 'Log' );
- $changeProtection = ' (' . $skin->linkKnown(
+ $changeProtection = ' (' . Linker::linkKnown(
$ltitle,
wfMsgHtml( 'protectlogpage' ),
array(),
@@ -145,7 +142,7 @@ class SpecialProtectedpages extends SpecialPage {
return Html::rawElement(
'li',
array(),
- wfSpecialList( $link . $stxt, $wgLang->commaList( $description_items ), false ) . $changeProtection ) . "\n";
+ $lang->specialList( $link . $stxt, $lang->commaList( $description_items ), false ) . $changeProtection ) . "\n";
}
/**
@@ -160,7 +157,7 @@ class SpecialProtectedpages extends SpecialPage {
*/
protected function showOptions( $namespace, $type='edit', $level, $sizetype, $size, $indefOnly, $cascadeOnly ) {
global $wgScript;
- $title = SpecialPage::getTitleFor( 'Protectedpages' );
+ $title = $this->getTitle();
return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', array(), wfMsg( 'protectedpages' ) ) .
@@ -306,7 +303,7 @@ class ProtectedPagesPager extends AlphabeticPager {
$this->size = intval($size);
$this->indefonly = (bool)$indefonly;
$this->cascadeonly = (bool)$cascadeonly;
- parent::__construct();
+ parent::__construct( $form->getContext() );
}
function getStartBody() {
@@ -319,10 +316,6 @@ class ProtectedPagesPager extends AlphabeticPager {
return '';
}
- function getTitle() {
- return SpecialPage::getTitleFor( 'Protectedpages' );
- }
-
function formatRow( $row ) {
return $this->mForm->formatRow( $row );
}
diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php
index 5fb91af7..982feb66 100644
--- a/includes/specials/SpecialProtectedtitles.php
+++ b/includes/specials/SpecialProtectedtitles.php
@@ -36,8 +36,6 @@ class SpecialProtectedtitles extends SpecialPage {
}
function execute( $par ) {
- global $wgOut, $wgRequest;
-
$this->setHeaders();
$this->outputHeader();
@@ -46,15 +44,16 @@ class SpecialProtectedtitles extends SpecialPage {
Title::purgeExpiredRestrictions();
}
- $type = $wgRequest->getVal( $this->IdType );
- $level = $wgRequest->getVal( $this->IdLevel );
- $sizetype = $wgRequest->getVal( 'sizetype' );
- $size = $wgRequest->getIntOrNull( 'size' );
- $NS = $wgRequest->getIntOrNull( 'namespace' );
+ $request = $this->getRequest();
+ $type = $request->getVal( $this->IdType );
+ $level = $request->getVal( $this->IdLevel );
+ $sizetype = $request->getVal( 'sizetype' );
+ $size = $request->getIntOrNull( 'size' );
+ $NS = $request->getIntOrNull( 'namespace' );
$pager = new ProtectedTitlesPager( $this, array(), $type, $level, $NS, $sizetype, $size );
- $wgOut->addHTML( $this->showOptions( $NS, $type, $level ) );
+ $this->getOutput()->addHTML( $this->showOptions( $NS, $type, $level ) );
if ( $pager->getNumRows() ) {
$s = $pager->getNavigationBar();
@@ -65,7 +64,7 @@ class SpecialProtectedtitles extends SpecialPage {
} else {
$s = '<p>' . wfMsgHtml( 'protectedtitlesempty' ) . '</p>';
}
- $wgOut->addHTML( $s );
+ $this->getOutput()->addHTML( $s );
}
/**
@@ -74,19 +73,16 @@ class SpecialProtectedtitles extends SpecialPage {
* @return string
*/
function formatRow( $row ) {
- global $wgLang;
-
wfProfileIn( __METHOD__ );
- static $skin = null, $infinity = null;
+ static $infinity = null;
- if( is_null( $skin ) ){
- $skin = $this->getSkin();
+ if( is_null( $infinity ) ){
$infinity = wfGetDB( DB_SLAVE )->getInfinity();
}
$title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title );
- $link = $skin->link( $title );
+ $link = Linker::link( $title );
$description_items = array ();
@@ -94,17 +90,22 @@ class SpecialProtectedtitles extends SpecialPage {
$description_items[] = $protType;
- $expiry = strlen( $row->pt_expiry ) ? $wgLang->formatExpiry( $row->pt_expiry, TS_MW ) : $infinity;
+ $lang = $this->getLanguage();
+ $expiry = strlen( $row->pt_expiry ) ? $lang->formatExpiry( $row->pt_expiry, TS_MW ) : $infinity;
if( $expiry != $infinity ) {
-
- $expiry_description = wfMsg( 'protect-expiring', $wgLang->timeanddate( $expiry ) , $wgLang->date( $expiry ) , $wgLang->time( $expiry ) );
+ $expiry_description = wfMsg(
+ 'protect-expiring-local',
+ $lang->timeanddate( $expiry, true ),
+ $lang->date( $expiry, true ),
+ $lang->time( $expiry, true )
+ );
$description_items[] = htmlspecialchars($expiry_description);
}
wfProfileOut( __METHOD__ );
- return '<li>' . wfSpecialList( $link, implode( $description_items, ', ' ) ) . "</li>\n";
+ return '<li>' . $lang->specialList( $link, implode( $description_items, ', ' ) ) . "</li>\n";
}
/**
@@ -116,7 +117,7 @@ class SpecialProtectedtitles extends SpecialPage {
function showOptions( $namespace, $type='edit', $level ) {
global $wgScript;
$action = htmlspecialchars( $wgScript );
- $title = SpecialPage::getTitleFor( 'Protectedtitles' );
+ $title = $this->getTitle();
$special = htmlspecialchars( $title->getPrefixedDBkey() );
return "<form action=\"$action\" method=\"get\">\n" .
'<fieldset>' .
@@ -189,7 +190,7 @@ class ProtectedTitlesPager extends AlphabeticPager {
$this->level = $level;
$this->namespace = $namespace;
$this->size = intval($size);
- parent::__construct();
+ parent::__construct( $form->getContext() );
}
function getStartBody() {
@@ -207,6 +208,9 @@ class ProtectedTitlesPager extends AlphabeticPager {
return '';
}
+ /**
+ * @return Title
+ */
function getTitle() {
return SpecialPage::getTitleFor( 'Protectedtitles' );
}
@@ -215,6 +219,9 @@ class ProtectedTitlesPager extends AlphabeticPager {
return $this->mForm->formatRow( $row );
}
+ /**
+ * @return array
+ */
function getQueryInfo() {
$conds = $this->mConds;
$conds[] = 'pt_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() );
diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php
index e299dc77..0b6239bb 100644
--- a/includes/specials/SpecialRandompage.php
+++ b/includes/specials/SpecialRandompage.php
@@ -33,8 +33,7 @@ class RandomPage extends SpecialPage {
protected $extra = array(); // Extra SQL statements
public function __construct( $name = 'Randompage' ){
- global $wgContentNamespaces;
- $this->namespaces = $wgContentNamespaces;
+ $this->namespaces = MWNamespace::getContentNamespaces();
parent::__construct( $name );
}
@@ -55,9 +54,9 @@ class RandomPage extends SpecialPage {
}
public function execute( $par ) {
- global $wgOut, $wgContLang, $wgRequest;
+ global $wgContLang;
- if ($par) {
+ if ( $par ) {
$this->setNamespace( $wgContLang->getNsIndex( $par ) );
}
@@ -65,15 +64,15 @@ class RandomPage extends SpecialPage {
if( is_null( $title ) ) {
$this->setHeaders();
- $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages',
+ $this->getOutput()->addWikiMsg( strtolower( $this->getName() ) . '-nopages',
$this->getNsList(), count( $this->namespaces ) );
return;
}
$redirectParam = $this->isRedirect() ? array( 'redirect' => 'no' ) : array();
- $query = array_merge( $wgRequest->getValues(), $redirectParam );
+ $query = array_merge( $this->getRequest()->getValues(), $redirectParam );
unset( $query['title'] );
- $wgOut->redirect( $title->getFullUrl( $query ) );
+ $this->getOutput()->redirect( $title->getFullUrl( $query ) );
}
/**
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
index 6c78ced0..daf47f62 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -73,9 +73,7 @@ class SpecialRecentChanges extends IncludableSpecialPage {
public function setup( $parameters ) {
$opts = $this->getDefaultOptions();
- $this->customFilters = array();
- wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ) );
- foreach( $this->customFilters as $key => $params ) {
+ foreach( $this->getCustomFilters() as $key => $params ) {
$opts->add( $key, $params['default'] );
}
@@ -91,6 +89,19 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
/**
+ * Get custom show/hide filters
+ *
+ * @return Array Map of filter URL param names to properties (msg/default)
+ */
+ protected function getCustomFilters() {
+ if ( $this->customFilters === null ) {
+ $this->customFilters = array();
+ wfRunHooks( 'SpecialRecentChangesFilters', array( $this, &$this->customFilters ) );
+ }
+ return $this->customFilters;
+ }
+
+ /**
* Create a FormOptions object specific for feed requests and return it
*
* @return FormOptions
@@ -232,6 +243,9 @@ class SpecialRecentChanges extends IncludableSpecialPage {
if( preg_match( '/^days=(\d+)$/', $bit, $m ) ) {
$opts['days'] = $m[1];
}
+ if( preg_match( '/^namespace=(\d+)$/', $bit, $m ) ) {
+ $opts['namespace'] = $m[1];
+ }
}
}
@@ -617,7 +631,15 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @param $opts FormOptions
*/
function setTopText( FormOptions $opts ) {
- $this->getOutput()->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) );
+ global $wgContLang;
+ $this->getOutput()->addWikiText(
+ Html::rawElement( 'p',
+ array( 'lang' => $wgContLang->getCode(), 'dir' => $wgContLang->getDir() ),
+ "\n" . wfMsgForContentNoTrans( 'recentchangestext' ) . "\n"
+ ),
+ /* $lineStart */ false,
+ /* $interface */ false
+ );
}
/**
@@ -636,7 +658,10 @@ class SpecialRecentChanges extends IncludableSpecialPage {
* @return String
*/
protected function namespaceFilterForm( FormOptions $opts ) {
- $nsSelect = Xml::namespaceSelector( $opts['namespace'], '' );
+ $nsSelect = Html::namespaceSelector(
+ array( 'selected' => $opts['namespace'], 'all' => '' ),
+ array( 'name' => 'namespace', 'id' => 'namespace' )
+ );
$nsLabel = Xml::label( wfMsg( 'namespace' ), 'namespace' );
$invert = Xml::checkLabel(
wfMsg( 'invert' ), 'invert', 'nsinvert',
@@ -766,10 +791,10 @@ class SpecialRecentChanges extends IncludableSpecialPage {
}
if( $options['from'] ) {
$note .= wfMsgExt( 'rcnotefrom', array( 'parseinline' ),
- $this->getLang()->formatNum( $options['limit'] ),
- $this->getLang()->timeanddate( $options['from'], true ),
- $this->getLang()->date( $options['from'], true ),
- $this->getLang()->time( $options['from'], true ) ) . '<br />';
+ $this->getLanguage()->formatNum( $options['limit'] ),
+ $this->getLanguage()->timeanddate( $options['from'], true ),
+ $this->getLanguage()->date( $options['from'], true ),
+ $this->getLanguage()->time( $options['from'], true ) ) . '<br />';
}
# Sort data for display and make sure it's unique after we've added user data.
@@ -782,30 +807,30 @@ class SpecialRecentChanges extends IncludableSpecialPage {
// limit links
foreach( $wgRCLinkLimits as $value ) {
- $cl[] = $this->makeOptionsLink( $this->getLang()->formatNum( $value ),
+ $cl[] = $this->makeOptionsLink( $this->getLanguage()->formatNum( $value ),
array( 'limit' => $value ), $nondefaults, $value == $options['limit'] );
}
- $cl = $this->getLang()->pipeList( $cl );
+ $cl = $this->getLanguage()->pipeList( $cl );
// day links, reset 'from' to none
foreach( $wgRCLinkDays as $value ) {
- $dl[] = $this->makeOptionsLink( $this->getLang()->formatNum( $value ),
+ $dl[] = $this->makeOptionsLink( $this->getLanguage()->formatNum( $value ),
array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] );
}
- $dl = $this->getLang()->pipeList( $dl );
+ $dl = $this->getLanguage()->pipeList( $dl );
// show/hide links
$showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) );
$filters = array(
- 'hideminor' => 'rcshowhideminor',
- 'hidebots' => 'rcshowhidebots',
- 'hideanons' => 'rcshowhideanons',
- 'hideliu' => 'rcshowhideliu',
+ 'hideminor' => 'rcshowhideminor',
+ 'hidebots' => 'rcshowhidebots',
+ 'hideanons' => 'rcshowhideanons',
+ 'hideliu' => 'rcshowhideliu',
'hidepatrolled' => 'rcshowhidepatr',
- 'hidemyself' => 'rcshowhidemine'
+ 'hidemyself' => 'rcshowhidemine'
);
- foreach ( $this->customFilters as $key => $params ) {
+ foreach ( $this->getCustomFilters() as $key => $params ) {
$filters[$key] = $params['msg'];
}
// Disable some if needed
@@ -822,13 +847,13 @@ class SpecialRecentChanges extends IncludableSpecialPage {
// show from this onward link
$timestamp = wfTimestampNow();
- $now = $this->getLang()->timeanddate( $timestamp, true );
+ $now = $this->getLanguage()->timeanddate( $timestamp, true );
$tl = $this->makeOptionsLink(
$now, array( 'from' => $timestamp ), $nondefaults
);
$rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter' ),
- $cl, $dl, $this->getLang()->pipeList( $links ) );
+ $cl, $dl, $this->getLanguage()->pipeList( $links ) );
$rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter' ), $tl );
return "{$note}$rclinks<br />$rclistfrom";
}
diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php
index 8b8369b5..1f556f89 100644
--- a/includes/specials/SpecialRecentchangeslinked.php
+++ b/includes/specials/SpecialRecentchangeslinked.php
@@ -69,13 +69,14 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
if ( $target === '' ) {
return false;
}
+ $outputPage = $this->getOutput();
$title = Title::newFromURL( $target );
if( !$title || $title->getInterwiki() != '' ){
- $this->getOutput()->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' );
+ $outputPage->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' );
return false;
}
- $this->getOutput()->setPageTitle( wfMsg( 'recentchangeslinked-title', $title->getPrefixedText() ) );
+ $outputPage->setPageTitle( $this->msg( 'recentchangeslinked-title', $title->getPrefixedText() ) );
/*
* Ordinary links are in the pagelinks table, while transclusions are
@@ -113,8 +114,9 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$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, &$select ) ) ) {
return false;
+ }
if( $ns == NS_CATEGORY && !$showlinkedto ) {
// special handling for categories
@@ -125,11 +127,14 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
// for now, always join on these tables; really should be configurable as in whatlinkshere
$link_tables = array( 'pagelinks', 'templatelinks' );
// imagelinks only contains links to pages in NS_FILE
- if( $ns == NS_FILE || !$showlinkedto ) $link_tables[] = 'imagelinks';
+ if( $ns == NS_FILE || !$showlinkedto ) {
+ $link_tables[] = 'imagelinks';
+ }
}
- if( $id == 0 && !$showlinkedto )
+ if( $id == 0 && !$showlinkedto ) {
return false; // nonexistent pages can't link to any pages
+ }
// field name prefixes for all the various tables we might want to join with
$prefix = array( 'pagelinks' => 'pl', 'templatelinks' => 'tl', 'categorylinks' => 'cl', 'imagelinks' => 'il' );
@@ -140,14 +145,20 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$pfx = $prefix[$link_table];
// imagelinks and categorylinks tables have no xx_namespace field, and have xx_to instead of xx_title
- if( $link_table == 'imagelinks' ) $link_ns = NS_FILE;
- elseif( $link_table == 'categorylinks' ) $link_ns = NS_CATEGORY;
- else $link_ns = 0;
+ if( $link_table == 'imagelinks' ) {
+ $link_ns = NS_FILE;
+ } elseif( $link_table == 'categorylinks' ) {
+ $link_ns = NS_CATEGORY;
+ } else {
+ $link_ns = 0;
+ }
if( $showlinkedto ) {
// find changes to pages linking to this page
if( $link_ns ) {
- if( $ns != $link_ns ) continue; // should never happen, but check anyway
+ if( $ns != $link_ns ) {
+ continue;
+ } // should never happen, but check anyway
$subconds = array( "{$pfx}_to" => $dbkey );
} else {
$subconds = array( "{$pfx}_namespace" => $ns, "{$pfx}_title" => $dbkey );
@@ -164,11 +175,11 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
}
}
- if( $dbr->unionSupportsOrderAndLimit())
+ if( $dbr->unionSupportsOrderAndLimit()) {
$order = array( 'ORDER BY' => 'rc_timestamp DESC' );
- else
+ } else {
$order = array();
-
+ }
$query = $dbr->selectSQLText(
array_merge( $tables, array( $link_table ) ),
@@ -185,11 +196,12 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$subsql[] = $query;
}
- if( count($subsql) == 0 )
+ if( count($subsql) == 0 ) {
return false; // should never happen
- if( count($subsql) == 1 && $dbr->unionSupportsOrderAndLimit() )
+ }
+ if( count($subsql) == 1 && $dbr->unionSupportsOrderAndLimit() ) {
$sql = $subsql[0];
- else {
+ } else {
// need to resort and relimit after union
$sql = $dbr->unionQueries($subsql, false).' ORDER BY rc_timestamp DESC';
$sql = $dbr->limitResult($sql, $limit, false);
@@ -197,12 +209,17 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
$res = $dbr->query( $sql, __METHOD__ );
- if( $res->numRows() == 0 )
+ if( $res->numRows() == 0 ) {
$this->mResultEmpty = true;
+ }
return $res;
}
+ /**
+ * @param $opts FormOptions
+ * @return array
+ */
function getExtraOptions( $opts ){
$opts->consumeValues( array( 'showlinkedto', 'target', 'tagfilter' ) );
$extraOpts = array();
@@ -212,8 +229,9 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
Xml::check( 'showlinkedto', $opts['showlinkedto'], array('id' => 'showlinkedto') ) . ' ' .
Xml::label( wfMsg("recentchangeslinked-to"), 'showlinkedto' ) );
$tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] );
- if ($tagFilter)
+ if ($tagFilter) {
$extraOpts['tagfilter'] = $tagFilter;
+ }
return $extraOpts;
}
@@ -235,8 +253,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
function setTopText( FormOptions $opts ) {
$target = $this->getTargetTitle();
if( $target ) {
- $this->getOutput()->setSubtitle( wfMsg( 'recentchangeslinked-backlink', Linker::link( $target,
- $target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) );
+ $this->getOutput()->addBacklinkSubtitle( $target );
}
}
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
index 3c643253..df60a26a 100644
--- a/includes/specials/SpecialRevisiondelete.php
+++ b/includes/specials/SpecialRevisiondelete.php
@@ -111,15 +111,12 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
public function execute( $par ) {
+ $this->checkPermissions();
+ $this->checkReadOnly();
+
$output = $this->getOutput();
$user = $this->getUser();
- if( !$user->isAllowed( 'deletedhistory' ) ) {
- $output->permissionRequired( 'deletedhistory' );
- return;
- } elseif( wfReadOnly() ) {
- $output->readOnlyPage();
- return;
- }
+
$this->mIsAllowed = $user->isAllowed('deleterevision'); // for changes
$this->setHeaders();
$this->outputHeader();
@@ -137,7 +134,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
// $this->ids = array_map( 'intval', $this->ids );
$this->ids = array_unique( array_filter( $this->ids ) );
- if ( $request->getVal( 'action' ) == 'historysubmit' ) {
+ if ( $request->getVal( 'action' ) == 'historysubmit' || $request->getVal( 'action' ) == 'revisiondelete' ) {
// For show/hide form submission from history page
// Since we are access through index.php?title=XXX&action=historysubmit
// getFullTitle() will contain the target title and not our title
@@ -206,12 +203,12 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# Show relevant lines from the deletion log
$output->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
LogEventsList::showLogExtract( $output, 'delete',
- $this->targetObj->getPrefixedText(), '', array( 'lim' => 25, 'conds' => $qc ) );
+ $this->targetObj, '', array( 'lim' => 25, 'conds' => $qc ) );
# Show relevant lines from the suppression log
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 ) );
+ $this->targetObj, '', array( 'lim' => 25, 'conds' => $qc ) );
}
}
@@ -228,7 +225,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
array(),
array( 'page' => $this->targetObj->getPrefixedText() )
);
- if ( $this->targetObj->getNamespace() != NS_SPECIAL ) {
+ if ( !$this->targetObj->isSpecialPage() ) {
# Give a link to the page history
$links[] = Linker::linkKnown(
$this->targetObj,
@@ -248,7 +245,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
}
}
# Logs themselves don't have histories or archived revisions
- $this->getOutput()->setSubtitle( '<p>' . $this->getLang()->pipeList( $links ) . '</p>' );
+ $this->getOutput()->addSubtitle( $this->getLanguage()->pipeList( $links ) );
}
}
@@ -278,7 +275,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$this->getOutput()->addWikiMsg( 'revdelete-no-file' );
return;
}
- if( !$oimage->userCan(File::DELETED_FILE) ) {
+ if( !$oimage->userCan( File::DELETED_FILE, $this->getUser() ) ) {
if( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
$this->getOutput()->permissionRequired( 'suppressrevision' );
} else {
@@ -289,15 +286,15 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
if ( !$this->getUser()->matchEditToken( $this->token, $archiveName ) ) {
$this->getOutput()->addWikiMsg( 'revdelete-show-file-confirm',
$this->targetObj->getText(),
- $this->getLang()->date( $oimage->getTimestamp() ),
- $this->getLang()->time( $oimage->getTimestamp() ) );
+ $this->getLanguage()->date( $oimage->getTimestamp() ),
+ $this->getLanguage()->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( $this->getUser()->editToken( $archiveName ) ) )
+ '&token=' . urlencode( $this->getUser()->getEditToken( $archiveName ) ) )
)
) .
Xml::submitButton( wfMsg( 'revdelete-show-file-submit' ) ) .
@@ -314,12 +311,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$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;
- require_once( "$IP/includes/StreamFile.php" );
$key = $oimage->getStorageKey();
$path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
- wfStreamFile( $path );
+ $repo->streamFile( $path );
}
/**
@@ -341,7 +335,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$UserAllowed = true;
if ( $this->typeName == 'logging' ) {
- $this->getOutput()->addWikiMsg( 'logdelete-selected', $this->getLang()->formatNum( count($this->ids) ) );
+ $this->getOutput()->addWikiMsg( 'logdelete-selected', $this->getLanguage()->formatNum( count($this->ids) ) );
} else {
$this->getOutput()->addWikiMsg( 'revdelete-selected',
$this->targetObj->getPrefixedText(), count( $this->ids ) );
@@ -410,7 +404,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
'</td>' .
"</tr>\n" .
Xml::closeElement( 'table' ) .
- Html::hidden( 'wpEditToken', $this->getUser()->editToken() ) .
+ Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) .
Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
Html::hidden( 'type', $this->typeName ) .
Html::hidden( 'ids', implode( ',', $this->ids ) ) .
@@ -541,7 +535,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* Report that the submit operation succeeded
*/
protected function success() {
- $this->getOutput()->setPagetitle( wfMsg( 'actioncomplete' ) );
+ $this->getOutput()->setPageTitle( $this->msg( 'actioncomplete' ) );
$this->getOutput()->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeInfo['success'] );
$this->list->reloadFromMaster();
$this->showForm();
@@ -551,7 +545,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
* Report that the submit operation failed
*/
protected function failure( $status ) {
- $this->getOutput()->setPagetitle( wfMsg( 'actionfailed' ) );
+ $this->getOutput()->setPageTitle( $this->msg( 'actionfailed' ) );
$this->getOutput()->addWikiText( $status->getWikiText( $this->typeInfo['failure'] ) );
$this->showForm();
}
diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php
index ba9d378a..3fa86875 100644
--- a/includes/specials/SpecialSearch.php
+++ b/includes/specials/SpecialSearch.php
@@ -35,8 +35,9 @@ class SpecialSearch extends SpecialPage {
* string when applicable. Extensions can add new profiles with hooks
* with custom search options just for that profile.
* null|string
- */
+ */
protected $profile;
+ function getProfile() { return $this->profile; }
/// Search engine
protected $searchEngine;
@@ -47,6 +48,27 @@ class SpecialSearch extends SpecialPage {
/// No idea, apparently used by some other classes
protected $mPrefix;
+ /**
+ * @var int
+ */
+ protected $limit, $offset;
+
+ /**
+ * @var array
+ */
+ protected $namespaces;
+ function getNamespaces() { return $this->namespaces; }
+
+ /**
+ * @var bool
+ */
+ protected $searchRedirects;
+
+ /**
+ * @var string
+ */
+ protected $didYouMeanHtml, $fulltext;
+
const NAMESPACES_CURRENT = 'sense';
public function __construct() {
@@ -59,25 +81,26 @@ class SpecialSearch extends SpecialPage {
* @param $par String or null
*/
public function execute( $par ) {
- global $wgRequest, $wgUser, $wgOut;
-
$this->setHeaders();
$this->outputHeader();
- $wgOut->allowClickjacking();
- $wgOut->addModuleStyles( 'mediawiki.special' );
+ $out = $this->getOutput();
+ $out->allowClickjacking();
+ $out->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 );
+ $request = $this->getRequest();
+
// Fetch the search term
- $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $titleParam ) );
+ $search = str_replace( "\n", " ", $request->getText( 'search', $titleParam ) );
- $this->load( $wgRequest, $wgUser );
+ $this->load();
- if ( $wgRequest->getVal( 'fulltext' )
- || !is_null( $wgRequest->getVal( 'offset' ) )
- || !is_null( $wgRequest->getVal( 'searchx' ) ) )
+ if ( $request->getVal( 'fulltext' )
+ || !is_null( $request->getVal( 'offset' ) )
+ || !is_null( $request->getVal( 'searchx' ) ) )
{
$this->showResults( $search );
} else {
@@ -87,33 +110,39 @@ class SpecialSearch extends SpecialPage {
/**
* Set up basic search parameters from the request and user settings.
- * Typically you'll pass $wgRequest and $wgUser.
*
- * @param $request WebRequest
- * @param $user User
+ * @see tests/phpunit/includes/specials/SpecialSearchTest.php
*/
- public function load( &$request, &$user ) {
+ public function load() {
+ $request = $this->getRequest();
list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
$this->mPrefix = $request->getVal( 'prefix', '' );
+ $user = $this->getUser();
# Extract manually requested namespaces
$nslist = $this->powerSearch( $request );
- $this->profile = $profile = $request->getVal( 'profile', null );
+ if ( !count( $nslist ) ) {
+ # Fallback to user preference
+ $nslist = SearchEngine::userNamespaces( $user );
+ }
+
+ $profile = null;
+ if ( !count( $nslist ) ) {
+ $profile = 'default';
+ }
+
+ $profile = $request->getVal( 'profile', $profile );
$profiles = $this->getSearchProfiles();
- if ( $profile === null) {
+ 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;
- }
+ $profile = 'advanced';
+ foreach( $profiles as $key => $data ) {
+ if ( $nslist === $data['namespaces'] && $key !== 'advanced') {
+ $profile = $key;
}
- $this->namespaces = $nslist;
- } else {
- $this->namespaces = SearchEngine::userNamespaces( $user );
}
+ $this->namespaces = $nslist;
} elseif ( $profile === 'advanced' ) {
$this->namespaces = $nslist;
} else {
@@ -121,7 +150,7 @@ class SpecialSearch extends SpecialPage {
$this->namespaces = $profiles[$profile]['namespaces'];
} else {
// Unknown profile requested
- $this->profile = 'default';
+ $profile = 'default';
$this->namespaces = $profiles['default']['namespaces'];
}
}
@@ -129,9 +158,9 @@ class SpecialSearch extends SpecialPage {
// Redirects defaults to true, but we don't know whether it was ticked of or just missing
$default = $request->getBool( 'profile' ) ? 0 : 1;
$this->searchRedirects = $request->getBool( 'redirs', $default ) ? 1 : 0;
- $this->sk = $this->getSkin();
$this->didYouMeanHtml = ''; # html of did you mean... link
$this->fulltext = $request->getVal('fulltext');
+ $this->profile = $profile;
}
/**
@@ -140,7 +169,6 @@ class SpecialSearch extends SpecialPage {
* @param $term String
*/
public function goResult( $term ) {
- global $wgOut;
$this->setupPage( $term );
# Try to go to page as entered.
$t = Title::newFromText( $term );
@@ -157,7 +185,7 @@ class SpecialSearch extends SpecialPage {
}
if( !is_null( $t ) ) {
- $wgOut->redirect( $t->getFullURL() );
+ $this->getOutput()->redirect( $t->getFullURL() );
return;
}
# No match, generate an edit URL
@@ -169,7 +197,7 @@ class SpecialSearch extends SpecialPage {
# If the feature is enabled, go straight to the edit page
if( $wgGoToEdit ) {
- $wgOut->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) );
+ $this->getOutput()->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) );
return;
}
}
@@ -180,11 +208,9 @@ class SpecialSearch extends SpecialPage {
* @param $term String
*/
public function showResults( $term ) {
- global $wgOut, $wgDisableTextSearch, $wgContLang, $wgScript;
+ global $wgDisableTextSearch, $wgSearchForwardUrl, $wgContLang, $wgScript;
wfProfileIn( __METHOD__ );
- $sk = $this->getSkin();
-
$search = $this->getSearchEngine();
$search->setLimitOffset( $this->limit, $this->offset );
$search->setNamespaces( $this->namespaces );
@@ -197,25 +223,25 @@ class SpecialSearch extends SpecialPage {
$this->setupPage( $term );
- if( $wgDisableTextSearch ) {
- global $wgSearchForwardUrl;
- if( $wgSearchForwardUrl ) {
+ $out = $this->getOutput();
+
+ if ( $wgDisableTextSearch ) {
+ if ( $wgSearchForwardUrl ) {
$url = str_replace( '$1', urlencode( $term ), $wgSearchForwardUrl );
- $wgOut->redirect( $url );
- wfProfileOut( __METHOD__ );
- return;
+ $out->redirect( $url );
+ } else {
+ $out->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( 'UTF-8' ),
+ htmlspecialchars( wfMsg( 'searchbutton' ) )
+ ) .
+ Xml::closeElement( 'fieldset' )
+ );
}
- $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( 'UTF-8' ),
- htmlspecialchars( wfMsg( 'searchbutton' ) )
- ) .
- Xml::closeElement( 'fieldset' )
- );
wfProfileOut( __METHOD__ );
return;
}
@@ -226,8 +252,9 @@ class SpecialSearch extends SpecialPage {
$rewritten = $search->replacePrefixes($term);
$titleMatches = $search->searchTitle( $rewritten );
- if( !($titleMatches instanceof SearchResultTooMany))
+ if( !( $titleMatches instanceof SearchResultTooMany ) ) {
$textMatches = $search->searchText( $rewritten );
+ }
// did you mean... suggestions
if( $textMatches && $textMatches->hasSuggestion() ) {
@@ -236,8 +263,9 @@ class SpecialSearch extends SpecialPage {
# mirror Go/Search behaviour of original request ..
$didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() );
- if($this->fulltext != null)
+ if( $this->fulltext != null ) {
$didYouMeanParams['fulltext'] = $this->fulltext;
+ }
$stParams = array_merge(
$didYouMeanParams,
@@ -246,10 +274,11 @@ class SpecialSearch extends SpecialPage {
$suggestionSnippet = $textMatches->getSuggestionSnippet();
- if( $suggestionSnippet == '' )
+ if( $suggestionSnippet == '' ) {
$suggestionSnippet = null;
+ }
- $suggestLink = $sk->linkKnown(
+ $suggestLink = Linker::linkKnown(
$st,
$suggestionSnippet,
array(),
@@ -259,7 +288,7 @@ class SpecialSearch extends SpecialPage {
$this->didYouMeanHtml = '<div class="searchdidyoumean">'.wfMsg('search-suggest',$suggestLink).'</div>';
}
// start rendering the page
- $wgOut->addHtml(
+ $out->addHtml(
Xml::openElement(
'form',
array(
@@ -269,7 +298,7 @@ class SpecialSearch extends SpecialPage {
)
)
);
- $wgOut->addHtml(
+ $out->addHtml(
Xml::openElement( 'table', array( 'id'=>'mw-search-top-table', 'border'=>0, 'cellpadding'=>0, 'cellspacing'=>0 ) ) .
Xml::openElement( 'tr' ) .
Xml::openElement( 'td' ) . "\n" .
@@ -281,16 +310,16 @@ class SpecialSearch extends SpecialPage {
// Sometimes the search engine knows there are too many hits
if( $titleMatches instanceof SearchResultTooMany ) {
- $wgOut->wrapWikiMsg( "==$1==\n", 'toomanymatches' );
+ $out->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 ) );
- $wgOut->addHtml( $this->getProfileForm( $this->profile, $term ) );
- $wgOut->addHTML( '</form>' );
+ $out->addHTML( $this->formHeader( $term, 0, 0 ) );
+ $out->addHtml( $this->getProfileForm( $this->profile, $term ) );
+ $out->addHTML( '</form>' );
// Empty query -- straight view of search form
wfProfileOut( __METHOD__ );
return;
@@ -316,33 +345,32 @@ class SpecialSearch extends SpecialPage {
$totalRes += $textMatches->getTotalHits();
// show number of results and current offset
- $wgOut->addHTML( $this->formHeader( $term, $num, $totalRes ) );
- $wgOut->addHtml( $this->getProfileForm( $this->profile, $term ) );
+ $out->addHTML( $this->formHeader( $term, $num, $totalRes ) );
+ $out->addHtml( $this->getProfileForm( $this->profile, $term ) );
- $wgOut->addHtml( Xml::closeElement( 'form' ) );
- $wgOut->addHtml( "<div class='searchresults'>" );
+ $out->addHtml( Xml::closeElement( 'form' ) );
+ $out->addHtml( "<div class='searchresults'>" );
// prev/next links
if( $num || $this->offset ) {
// Show the create link ahead
$this->showCreateLink( $t );
- $prevnext = wfViewPrevNext( $this->offset, $this->limit,
- SpecialPage::getTitleFor( 'Search' ),
- wfArrayToCGI( $this->powerSearchOptions(), array( 'search' => $term ) ),
+ $prevnext = $this->getLanguage()->viewPrevNext( $this->getTitle(), $this->offset, $this->limit,
+ $this->powerSearchOptions() + array( 'search' => $term ),
max( $titleMatchesNum, $textMatchesNum ) < $this->limit
);
- //$wgOut->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" );
+ //$out->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" );
wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
} else {
wfRunHooks( 'SpecialSearchNoResults', array( $term ) );
}
- $wgOut->parserOptions()->setEditSection( false );
+ $out->parserOptions()->setEditSection( false );
if( $titleMatches ) {
if( $numTitleMatches > 0 ) {
- $wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' );
- $wgOut->addHTML( $this->showMatches( $titleMatches ) );
+ $out->wrapWikiMsg( "==$1==\n", 'titlematches' );
+ $out->addHTML( $this->showMatches( $titleMatches ) );
}
$titleMatches->free();
}
@@ -350,37 +378,38 @@ class SpecialSearch extends SpecialPage {
// output appropriate heading
if( $numTextMatches > 0 && $numTitleMatches > 0 ) {
// if no title matches the heading is redundant
- $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' );
+ $out->wrapWikiMsg( "==$1==\n", 'textmatches' );
} elseif( $totalRes == 0 ) {
# Don't show the 'no text matches' if we received title matches
- # $wgOut->wrapWikiMsg( "==$1==\n", 'notextmatches' );
+ # $out->wrapWikiMsg( "==$1==\n", 'notextmatches' );
}
// show interwiki results if any
if( $textMatches->hasInterwikiResults() ) {
- $wgOut->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) );
+ $out->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) );
}
// show results
if( $numTextMatches > 0 ) {
- $wgOut->addHTML( $this->showMatches( $textMatches ) );
+ $out->addHTML( $this->showMatches( $textMatches ) );
}
$textMatches->free();
}
if( $num === 0 ) {
- $wgOut->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", array( 'search-nonefound', wfEscapeWikiText( $term ) ) );
+ $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", array( 'search-nonefound', wfEscapeWikiText( $term ) ) );
$this->showCreateLink( $t );
}
- $wgOut->addHtml( "</div>" );
+ $out->addHtml( "</div>" );
if( $num || $this->offset ) {
- $wgOut->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
+ $out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
}
wfProfileOut( __METHOD__ );
}
+ /**
+ * @param $t Title
+ */
protected function showCreateLink( $t ) {
- global $wgOut;
-
// show direct page/create link if applicable
// Check DBkey !== '' in case of fragment link only.
@@ -390,7 +419,7 @@ class SpecialSearch extends SpecialPage {
$this->getOutput()->addHtml( '<p></p>' );
return;
}
- $messageName = '';
+
if( $t->isKnown() ) {
$messageName = 'searchmenu-exists';
} elseif( $t->userCan( 'create' ) ) {
@@ -406,24 +435,23 @@ class SpecialSearch extends SpecialPage {
$this->getOutput()->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", $params );
} else {
// preserve the paragraph for margins etc...
- $wgOut->addHtml( '<p></p>' );
+ $this->getOutput()->addHtml( '<p></p>' );
}
}
/**
- *
+ * @param $term string
*/
protected function setupPage( $term ) {
- global $wgOut;
-
# Should advanced UI be used?
$this->searchAdvanced = ($this->profile === 'advanced');
+ $out = $this->getOutput();
if( strval( $term ) !== '' ) {
- $wgOut->setPageTitle( wfMsg( 'searchresults') );
- $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'searchresults-title', $term ) ) );
+ $out->setPageTitle( $this->msg( 'searchresults' ) );
+ $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'searchresults-title', $term )->plain() ) );
}
// add javascript specific to special:search
- $wgOut->addModules( 'mediawiki.special.search' );
+ $out->addModules( 'mediawiki.special.search' );
}
/**
@@ -466,6 +494,8 @@ class SpecialSearch extends SpecialPage {
* Show whole set of results
*
* @param $matches SearchResultSet
+ *
+ * @return string
*/
protected function showMatches( &$matches ) {
global $wgContLang;
@@ -479,8 +509,10 @@ class SpecialSearch extends SpecialPage {
$out .= "\n<!-- {$infoLine} -->\n";
}
$out .= "<ul class='mw-search-results'>\n";
- while( $result = $matches->next() ) {
+ $result = $matches->next();
+ while( $result ) {
$out .= $this->showHit( $result, $terms );
+ $result = $matches->next();
}
$out .= "</ul>\n";
@@ -495,9 +527,10 @@ class SpecialSearch extends SpecialPage {
*
* @param $result SearchResult
* @param $terms Array: terms to highlight
+ *
+ * @return string
*/
protected function showHit( $result, $terms ) {
- global $wgLang;
wfProfileIn( __METHOD__ );
if( $result->isBrokenTitle() ) {
@@ -505,7 +538,6 @@ class SpecialSearch extends SpecialPage {
return "<!-- Broken link in search result -->\n";
}
- $sk = $this->getSkin();
$t = $result->getTitle();
$titleSnippet = $result->getTitleSnippet($terms);
@@ -518,7 +550,7 @@ class SpecialSearch extends SpecialPage {
wfRunHooks( 'ShowSearchHitTitle',
array( &$link_t, &$titleSnippet, $result, $terms, $this ) );
- $link = $this->sk->linkKnown(
+ $link = Linker::linkKnown(
$link_t,
$titleSnippet
);
@@ -526,7 +558,7 @@ class SpecialSearch extends SpecialPage {
//If page content is not readable, just return the title.
//This is not quite safe, but better than showing excerpts from non-readable pages
//Note that hiding the entry entirely would screw up paging.
- if( !$t->userCanRead() ) {
+ if( !$t->userCan( 'read' ) ) {
wfProfileOut( __METHOD__ );
return "<li>{$link}</li>\n";
}
@@ -553,7 +585,7 @@ class SpecialSearch extends SpecialPage {
$redirect = "<span class='searchalttitle'>" .
wfMsg(
'search-redirect',
- $this->sk->linkKnown(
+ Linker::linkKnown(
$redirectTitle,
$redirectText
)
@@ -569,7 +601,7 @@ class SpecialSearch extends SpecialPage {
$section = "<span class='searchalttitle'>" .
wfMsg(
- 'search-section', $this->sk->linkKnown(
+ 'search-section', Linker::linkKnown(
$sectionTitle,
$sectionText
)
@@ -580,13 +612,15 @@ class SpecialSearch extends SpecialPage {
// format text extract
$extract = "<div class='searchresult'>".$result->getTextSnippet($terms)."</div>";
+ $lang = $this->getLanguage();
+
// format score
if( is_null( $result->getScore() ) ) {
// Search engine doesn't report scoring info
$score = '';
} else {
$percent = sprintf( '%2.1f', $result->getScore() * 100 );
- $score = wfMsg( 'search-result-score', $wgLang->formatNum( $percent ) )
+ $score = wfMsg( 'search-result-score', $lang->formatNum( $percent ) )
. ' - ';
}
@@ -597,8 +631,8 @@ class SpecialSearch extends SpecialPage {
$size = wfMsgExt(
'search-result-size',
array( 'parsemag', 'escape' ),
- $wgLang->formatSize( $byteSize ),
- $wgLang->formatNum( $wordCount )
+ $lang->formatSize( $byteSize ),
+ $lang->formatNum( $wordCount )
);
if( $t->getNamespace() == NS_CATEGORY ) {
@@ -606,13 +640,13 @@ class SpecialSearch extends SpecialPage {
$size = wfMsgExt(
'search-result-category-size',
array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $cat->getPageCount() ),
- $wgLang->formatNum( $cat->getSubcatCount() ),
- $wgLang->formatNum( $cat->getFileCount() )
+ $lang->formatNum( $cat->getPageCount() ),
+ $lang->formatNum( $cat->getSubcatCount() ),
+ $lang->formatNum( $cat->getFileCount() )
);
}
- $date = $wgLang->timeanddate( $timestamp );
+ $date = $lang->timeanddate( $timestamp );
// link to related articles if supported
$related = '';
@@ -626,7 +660,7 @@ class SpecialSearch extends SpecialPage {
)
);
- $related = ' -- ' . $sk->linkKnown(
+ $related = ' -- ' . Linker::linkKnown(
$st,
wfMsg('search-relatedarticle'),
array(),
@@ -675,6 +709,8 @@ class SpecialSearch extends SpecialPage {
*
* @param $matches SearchResultSet
* @param $query String
+ *
+ * @return string
*/
protected function showInterwiki( &$matches, $query ) {
global $wgContLang;
@@ -695,9 +731,11 @@ class SpecialSearch extends SpecialPage {
}
$prev = null;
- while( $result = $matches->next() ) {
+ $result = $matches->next();
+ while( $result ) {
$out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions );
$prev = $result->getInterwikiPrefix();
+ $result = $matches->next();
}
// TODO: should support paging in a non-confusing way (not sure how though, maybe via ajax)..
$out .= "</ul></div>\n";
@@ -716,6 +754,8 @@ class SpecialSearch extends SpecialPage {
* @param $terms Array
* @param $query String
* @param $customCaptions Array: iw prefix -> caption
+ *
+ * @return string
*/
protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) {
wfProfileIn( __METHOD__ );
@@ -732,7 +772,7 @@ class SpecialSearch extends SpecialPage {
if( $titleSnippet == '' )
$titleSnippet = null;
- $link = $this->sk->linkKnown(
+ $link = Linker::linkKnown(
$t,
$titleSnippet
);
@@ -748,7 +788,7 @@ class SpecialSearch extends SpecialPage {
$redirect = "<span class='searchalttitle'>" .
wfMsg(
'search-redirect',
- $this->sk->linkKnown(
+ Linker::linkKnown(
$redirectTitle,
$redirectText
)
@@ -770,7 +810,7 @@ class SpecialSearch extends SpecialPage {
}
// "more results" link (special page stuff could be localized, but we might not know target lang)
$searchTitle = Title::newFromText($t->getInterwiki().":Special:Search");
- $searchLink = $this->sk->linkKnown(
+ $searchLink = Linker::linkKnown(
$searchTitle,
wfMsg('search-interwiki-more'),
array(),
@@ -788,6 +828,11 @@ class SpecialSearch extends SpecialPage {
return $out;
}
+ /**
+ * @param $profile
+ * @param $term
+ * @return String
+ */
protected function getProfileForm( $profile, $term ) {
// Hidden stuff
$opts = array();
@@ -807,6 +852,7 @@ class SpecialSearch extends SpecialPage {
* Generates the power search box at [[Special:Search]]
*
* @param $term String: search term
+ * @param $opts array
* @return String: HTML form
*/
protected function powerSearchBox( $term, $opts ) {
@@ -900,6 +946,9 @@ class SpecialSearch extends SpecialPage {
Xml::closeElement( 'fieldset' );
}
+ /**
+ * @return array
+ */
protected function getSearchProfiles() {
// Builds list of Search Types (profiles)
$nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
@@ -948,9 +997,13 @@ class SpecialSearch extends SpecialPage {
return $profiles;
}
+ /**
+ * @param $term
+ * @param $resultsShown
+ * @param $totalNum
+ * @return string
+ */
protected function formHeader( $term, $resultsShown, $totalNum ) {
- global $wgLang;
-
$out = Xml::openElement('div', array( 'class' => 'mw-search-formheader' ) );
$bareterm = $term;
@@ -960,6 +1013,7 @@ class SpecialSearch extends SpecialPage {
}
$profiles = $this->getSearchProfiles();
+ $lang = $this->getLanguage();
// Outputs XML for Search Types
$out .= Xml::openElement( 'div', array( 'class' => 'search-types' ) );
@@ -971,7 +1025,7 @@ class SpecialSearch extends SpecialPage {
$profile['parameters']['profile'] = $id;
$tooltipParam = isset( $profile['namespace-messages'] ) ?
- $wgLang->commaList( $profile['namespace-messages'] ) : null;
+ $lang->commaList( $profile['namespace-messages'] ) : null;
$out .= Xml::tags(
'li',
array(
@@ -993,19 +1047,22 @@ class SpecialSearch extends SpecialPage {
if ( $resultsShown > 0 ) {
if ( $totalNum > 0 ){
$top = wfMsgExt( 'showingresultsheader', array( 'parseinline' ),
- $wgLang->formatNum( $this->offset + 1 ),
- $wgLang->formatNum( $this->offset + $resultsShown ),
- $wgLang->formatNum( $totalNum ),
+ $lang->formatNum( $this->offset + 1 ),
+ $lang->formatNum( $this->offset + $resultsShown ),
+ $lang->formatNum( $totalNum ),
wfEscapeWikiText( $term ),
- $wgLang->formatNum( $resultsShown )
+ $lang->formatNum( $resultsShown )
);
} elseif ( $resultsShown >= $this->limit ) {
- $top = wfShowingResults( $this->offset, $this->limit );
+ $top = wfMsgExt( 'showingresults', array( 'parseinline' ),
+ $lang->formatNum( $this->limit ),
+ $lang->formatNum( $this->offset + 1 )
+ );
} else {
$top = wfMsgExt( 'showingresultsnum', array( 'parseinline' ),
- $wgLang->formatNum( $this->limit ),
- $wgLang->formatNum( $this->offset + 1 ),
- $wgLang->formatNum( $resultsShown )
+ $lang->formatNum( $this->limit ),
+ $lang->formatNum( $this->offset + 1 ),
+ $lang->formatNum( $resultsShown )
);
}
$out .= Xml::tags( 'div', array( 'class' => 'results-info' ),
@@ -1019,6 +1076,10 @@ class SpecialSearch extends SpecialPage {
return $out;
}
+ /**
+ * @param $term string
+ * @return string
+ */
protected function shortDialog( $term ) {
$out = Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
$out .= Html::hidden( 'profile', $this->profile ) . "\n";
@@ -1102,6 +1163,8 @@ class SpecialSearch extends SpecialPage {
/**
* @since 1.18
+ *
+ * @return SearchEngine
*/
public function getSearchEngine() {
if ( $this->searchEngine === null ) {
@@ -1115,6 +1178,9 @@ class SpecialSearch extends SpecialPage {
* add more params to links to not lose selection when
* user navigates search results.
* @since 1.18
+ *
+ * @param $key
+ * @param $value
*/
public function setExtraParam( $key, $value ) {
$this->extraParams[$key] = $value;
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
index 3b785018..c176f913 100644
--- a/includes/specials/SpecialShortpages.php
+++ b/includes/specials/SpecialShortpages.php
@@ -33,14 +33,6 @@ class ShortPagesPage extends QueryPage {
parent::__construct( $name );
}
- // inexpensive?
- /**
- * This query is indexed as of 1.5
- */
- function isExpensive() {
- return true;
- }
-
function isSyndicated() {
return false;
}
@@ -51,9 +43,9 @@ class ShortPagesPage extends QueryPage {
'fields' => array ( 'page_namespace AS namespace',
'page_title AS title',
'page_len AS value' ),
- 'conds' => array ( 'page_namespace' => MWNamespace::getContentNamespaces(),
+ 'conds' => array ( 'page_namespace' => NS_MAIN,
'page_is_redirect' => 0 ),
- 'options' => array ( 'USE INDEX' => 'page_len' )
+ 'options' => array ( 'USE INDEX' => 'page_redirect_namespace_len' )
);
}
@@ -86,23 +78,22 @@ class ShortPagesPage extends QueryPage {
}
function formatResult( $skin, $result ) {
- global $wgLang;
- $dm = $wgLang->getDirMark();
+ $dm = $this->getLanguage()->getDirMark();
$title = Title::makeTitle( $result->namespace, $result->title );
if ( !$title ) {
return '<!-- Invalid title ' . htmlspecialchars( "{$result->namespace}:{$result->title}" ). '-->';
}
- $hlink = $skin->linkKnown(
+ $hlink = Linker::linkKnown(
$title,
wfMsgHtml( 'hist' ),
array(),
array( 'action' => 'history' )
);
$plink = $this->isCached()
- ? $skin->link( $title )
- : $skin->linkKnown( $title );
- $size = wfMessage( 'nbytes', $wgLang->formatNum( $result->value ) )->escaped();
+ ? Linker::link( $title )
+ : Linker::linkKnown( $title );
+ $size = $this->msg( 'nbytes' )->numParams( $result->value )->escaped();
return $title->exists()
? "({$hlink}) {$dm}{$plink} {$dm}[{$size}]"
diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php
index 13bc4c2b..e973ddc8 100644
--- a/includes/specials/SpecialSpecialpages.php
+++ b/includes/specials/SpecialSpecialpages.php
@@ -51,7 +51,7 @@ class SpecialSpecialpages extends UnlistedSpecialPage {
private function getPageGroups() {
global $wgSortSpecialPages;
- $pages = SpecialPageFactory::getUsablePages();
+ $pages = SpecialPageFactory::getUsablePages( $this->getUser() );
if( !count( $pages ) ) {
# Yeah, that was pointless. Thanks for coming.
diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php
index 5def4da5..b9c092b6 100644
--- a/includes/specials/SpecialStatistics.php
+++ b/includes/specials/SpecialStatistics.php
@@ -138,15 +138,17 @@ class SpecialStatistics extends SpecialPage {
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' ) ),
- $this->getLang()->formatNum( $this->good ),
+ $this->formatRow( Linker::linkKnown( SpecialPage::getTitleFor( 'Allpages' ),
+ wfMsgExt( 'statistics-articles', array( 'parseinline' ) ) ),
+ $this->getLanguage()->formatNum( $this->good ),
array( 'class' => 'mw-statistics-articles' ) ) .
$this->formatRow( wfMsgExt( 'statistics-pages', array( 'parseinline' ) ),
- $this->getLang()->formatNum( $this->total ),
+ $this->getLanguage()->formatNum( $this->total ),
array( 'class' => 'mw-statistics-pages' ),
'statistics-pages-desc' ) .
- $this->formatRow( wfMsgExt( 'statistics-files', array( 'parseinline' ) ),
- $this->getLang()->formatNum( $this->images ),
+ $this->formatRow( Linker::linkKnown( SpecialPage::getTitleFor( 'Listfiles' ),
+ wfMsgExt( 'statistics-files', array( 'parseinline' ) ) ),
+ $this->getLanguage()->formatNum( $this->images ),
array( 'class' => 'mw-statistics-files' ) );
}
private function getEditStats() {
@@ -154,10 +156,10 @@ class SpecialStatistics extends SpecialPage {
Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-edits', array( 'parseinline' ) ) ) .
Xml::closeElement( 'tr' ) .
$this->formatRow( wfMsgExt( 'statistics-edits', array( 'parseinline' ) ),
- $this->getLang()->formatNum( $this->edits ),
+ $this->getLanguage()->formatNum( $this->edits ),
array( 'class' => 'mw-statistics-edits' ) ) .
$this->formatRow( wfMsgExt( 'statistics-edits-average', array( 'parseinline' ) ),
- $this->getLang()->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ),
+ $this->getLanguage()->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ),
array( 'class' => 'mw-statistics-edits-average' ) );
}
@@ -167,17 +169,17 @@ class SpecialStatistics extends SpecialPage {
Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-users', array( 'parseinline' ) ) ) .
Xml::closeElement( 'tr' ) .
$this->formatRow( wfMsgExt( 'statistics-users', array( 'parseinline' ) ),
- $this->getLang()->formatNum( $this->users ),
+ $this->getLanguage()->formatNum( $this->users ),
array( 'class' => 'mw-statistics-users' ) ) .
$this->formatRow( wfMsgExt( 'statistics-users-active', array( 'parseinline' ) ) . ' ' .
Linker::linkKnown(
SpecialPage::getTitleFor( 'Activeusers' ),
wfMsgHtml( 'listgrouprights-members' )
),
- $this->getLang()->formatNum( $this->activeUsers ),
+ $this->getLanguage()->formatNum( $this->activeUsers ),
array( 'class' => 'mw-statistics-users-active' ),
'statistics-users-active-desc',
- $this->getLang()->formatNum( $wgActiveUserDays ) );
+ $this->getLanguage()->formatNum( $wgActiveUserDays ) );
}
private function getGroupStats() {
@@ -219,7 +221,7 @@ class SpecialStatistics extends SpecialPage {
$classZero = ' statistics-group-zero';
}
$text .= $this->formatRow( $grouppage . ' ' . $grouplink,
- $this->getLang()->formatNum( $countUsers ),
+ $this->getLanguage()->formatNum( $countUsers ),
array( 'class' => 'statistics-group-' . Sanitizer::escapeClass( $group ) . $classZero ) );
}
return $text;
@@ -230,10 +232,10 @@ class SpecialStatistics extends SpecialPage {
Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-views', array( 'parseinline' ) ) ) .
Xml::closeElement( 'tr' ) .
$this->formatRow( wfMsgExt( 'statistics-views-total', array( 'parseinline' ) ),
- $this->getLang()->formatNum( $this->views ),
+ $this->getLanguage()->formatNum( $this->views ),
array ( 'class' => 'mw-statistics-views-total' ), 'statistics-views-total-desc' ) .
$this->formatRow( wfMsgExt( 'statistics-views-peredit', array( 'parseinline' ) ),
- $this->getLang()->formatNum( sprintf( '%.2f', $this->edits ?
+ $this->getLanguage()->formatNum( sprintf( '%.2f', $this->edits ?
$this->views / $this->edits : 0 ) ),
array ( 'class' => 'mw-statistics-views-peredit' ) );
}
@@ -266,7 +268,7 @@ class SpecialStatistics extends SpecialPage {
$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
if( $title instanceof Title ) {
$text .= $this->formatRow( Linker::link( $title ),
- $this->getLang()->formatNum( $row->page_counter ) );
+ $this->getLanguage()->formatNum( $row->page_counter ) );
}
}
@@ -287,7 +289,7 @@ class SpecialStatistics extends SpecialPage {
$name = htmlspecialchars( $name );
$number = htmlspecialchars( $number );
- $return .= $this->formatRow( $name, $this->getLang()->formatNum( $number ), array( 'class' => 'mw-statistics-hook' ) );
+ $return .= $this->formatRow( $name, $this->getLanguage()->formatNum( $number ), array( 'class' => 'mw-statistics-hook' ) );
}
return $return;
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index 66a89e94..adfc7441 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -36,10 +36,12 @@ class SpecialTags extends SpecialPage {
}
function execute( $par ) {
- global $wgOut;
+ $this->setHeaders();
+ $this->outputHeader();
- $wgOut->setPageTitle( wfMsg( 'tags-title' ) );
- $wgOut->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1\n</div>", 'tags-intro' );
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'tags-title' ) );
+ $out->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1\n</div>", 'tags-intro' );
// Write the headers
$html = Xml::tags( 'tr', null, Xml::tags( 'th', null, wfMsgExt( 'tags-tag', 'parseinline' ) ) .
@@ -59,35 +61,30 @@ class SpecialTags extends SpecialPage {
$html .= $this->doTagRow( $tag, 0 );
}
- $wgOut->addHTML( Xml::tags( 'table', array( 'class' => 'wikitable mw-tags-table' ), $html ) );
+ $out->addHTML( Xml::tags( 'table', array( 'class' => 'wikitable mw-tags-table' ), $html ) );
}
function doTagRow( $tag, $hitcount ) {
- static $sk = null, $doneTags = array();
- if ( !$sk ) {
- $sk = $this->getSkin();
- }
+ static $doneTags = array();
if ( in_array( $tag, $doneTags ) ) {
return '';
}
- global $wgLang;
-
$newRow = '';
$newRow .= Xml::tags( 'td', null, Xml::element( 'tt', null, $tag ) );
$disp = ChangeTags::tagDescription( $tag );
- $disp .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), wfMsgHtml( 'tags-edit' ) ) . ')';
+ $disp .= ' (' . Linker::link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), wfMsgHtml( 'tags-edit' ) ) . ')';
$newRow .= Xml::tags( 'td', null, $disp );
$msg = wfMessage( "tag-$tag-description" );
$desc = !$msg->exists() ? '' : $msg->parse();
- $desc .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), wfMsgHtml( 'tags-edit' ) ) . ')';
+ $desc .= ' (' . Linker::link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), wfMsgHtml( 'tags-edit' ) ) . ')';
$newRow .= Xml::tags( 'td', null, $desc );
- $hitcount = wfMsgExt( 'tags-hitcount', array( 'parsemag' ), $wgLang->formatNum( $hitcount ) );
- $hitcount = $sk->link( SpecialPage::getTitleFor( 'Recentchanges' ), $hitcount, array(), array( 'tagfilter' => $tag ) );
+ $hitcount = wfMsgExt( 'tags-hitcount', array( 'parsemag' ), $this->getLanguage()->formatNum( $hitcount ) );
+ $hitcount = Linker::link( SpecialPage::getTitleFor( 'Recentchanges' ), $hitcount, array(), array( 'tagfilter' => $tag ) );
$newRow .= Xml::tags( 'td', null, $hitcount );
$doneTags[] = $tag;
diff --git a/includes/specials/SpecialUnblock.php b/includes/specials/SpecialUnblock.php
index 521c1775..47944309 100644
--- a/includes/specials/SpecialUnblock.php
+++ b/includes/specials/SpecialUnblock.php
@@ -35,35 +35,22 @@ class SpecialUnblock extends SpecialPage {
}
public function execute( $par ){
- global $wgUser, $wgOut, $wgRequest;
+ $this->checkPermissions();
+ $this->checkReadOnly();
- # 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 );
+ list( $this->target, $this->type ) = SpecialBlock::getTargetAndType( $par, $this->getRequest() );
$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 );
- }
+ $this->setHeaders();
+ $this->outputHeader();
- $wgOut->setPageTitle( wfMsg( 'unblockip' ) );
- $wgOut->addModules( 'mediawiki.special' );
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'unblockip' ) );
+ $out->addModules( 'mediawiki.special' );
$form = new HTMLForm( $this->getFields(), $this->getContext() );
$form->setWrapperLegend( wfMsg( 'unblockip' ) );
- $form->setSubmitCallback( array( __CLASS__, 'processUnblock' ) );
+ $form->setSubmitCallback( array( __CLASS__, 'processUIUnblock' ) );
$form->setSubmitText( wfMsg( 'ipusubmit' ) );
$form->addPreText( wfMsgExt( 'unblockiptext', 'parse' ) );
@@ -71,14 +58,14 @@ class SpecialUnblock extends SpecialPage {
switch( $this->type ){
case Block::TYPE_USER:
case Block::TYPE_IP:
- $wgOut->addWikiMsg( 'unblocked', $this->target );
+ $out->addWikiMsg( 'unblocked', $this->target );
break;
case Block::TYPE_RANGE:
- $wgOut->addWikiMsg( 'unblocked-range', $this->target );
+ $out->addWikiMsg( 'unblocked-range', $this->target );
break;
case Block::TYPE_ID:
case Block::TYPE_AUTO:
- $wgOut->addWikiMsg( 'unblocked-id', $this->target );
+ $out->addWikiMsg( 'unblocked-id', $this->target );
break;
}
}
@@ -120,8 +107,7 @@ class SpecialUnblock extends SpecialPage {
switch( $type ){
case Block::TYPE_USER:
case Block::TYPE_IP:
- $skin = $this->getSkin();
- $fields['Name']['default'] = $skin->link(
+ $fields['Name']['default'] = Linker::link(
$target->getUserPage(),
$target->getName()
);
@@ -149,12 +135,21 @@ class SpecialUnblock extends SpecialPage {
}
/**
+ * Submit callback for an HTMLForm object
+ */
+ public static function processUIUnblock( array $data, HTMLForm $form ) {
+ return self::processUnblock( $data, $form->getContext() );
+ }
+
+ /**
* Process the form
+ *
+ * @param $data Array
+ * @param $context IContextSource
* @return Array( Array(message key, parameters) ) on failure, True on success
*/
- public static function processUnblock( array $data ){
- global $wgUser;
-
+ public static function processUnblock( array $data, IContextSource $context ){
+ $performer = $context->getUser();
$target = $data['Target'];
$block = Block::newFromTarget( $data['Target'] );
@@ -162,6 +157,14 @@ class SpecialUnblock extends SpecialPage {
return array( array( 'ipb_cant_unblock', $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( $target, $performer );
+ if ( $status !== true ) {
+ throw new ErrorPageError( 'badaccess', $status );
+ }
+
# 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 );
@@ -172,7 +175,7 @@ class SpecialUnblock extends SpecialPage {
# 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 ) {
+ if( !$performer->isAllowed( 'hideuser' ) && $block->mHideName ) {
return array( 'unblock-hideuser' );
}
diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php
index d4636e74..5d8b17b7 100644
--- a/includes/specials/SpecialUndelete.php
+++ b/includes/specials/SpecialUndelete.php
@@ -116,7 +116,7 @@ class PageArchive {
$res = $dbr->select( 'archive',
array(
'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
- 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id'
+ 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1'
),
array( 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey() ),
@@ -187,10 +187,12 @@ class PageArchive {
'ar_flags',
'ar_text_id',
'ar_deleted',
- 'ar_len' ),
+ 'ar_len',
+ 'ar_sha1',
+ ),
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() ) );
@@ -398,18 +400,17 @@ class PageArchive {
$dbw = wfGetDB( DB_MASTER );
# Does this page already exist? We'll have to update it...
- $article = new Article( $this->title );
+ $article = WikiPage::factory( $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() ),
__METHOD__,
- $options
+ array( 'FOR UPDATE' ) // lock page
);
if( $page ) {
$makepage = false;
@@ -460,7 +461,8 @@ class PageArchive {
'ar_text_id',
'ar_deleted',
'ar_page_id',
- 'ar_len' ),
+ 'ar_len',
+ 'ar_sha1' ),
/* WHERE */ array(
'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey(),
@@ -580,12 +582,20 @@ class SpecialUndelete extends SpecialPage {
parent::__construct( 'Undelete', 'deletedhistory' );
}
- function loadRequest() {
+ function loadRequest( $par ) {
$request = $this->getRequest();
$user = $this->getUser();
$this->mAction = $request->getVal( 'action' );
- $this->mTarget = $request->getVal( 'target' );
+ if ( $par !== null && $par !== '' ) {
+ $this->mTarget = $par;
+ } else {
+ $this->mTarget = $request->getVal( 'target' );
+ }
+ $this->mTargetObj = null;
+ if ( $this->mTarget !== null && $this->mTarget !== '' ) {
+ $this->mTargetObj = Title::newFromURL( $this->mTarget );
+ }
$this->mSearchPrefix = $request->getText( 'prefix' );
$time = $request->getVal( 'timestamp' );
$this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
@@ -597,6 +607,7 @@ class SpecialUndelete extends SpecialPage {
$this->mInvert = $request->getCheck( 'invert' ) && $posted;
$this->mPreview = $request->getCheck( 'preview' ) && $posted;
$this->mDiff = $request->getCheck( 'diff' );
+ $this->mDiffOnly = $request->getBool( 'diffonly', $this->getUser()->getOption( 'diffonly' ) );
$this->mComment = $request->getText( 'wpComment' );
$this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' );
$this->mToken = $request->getVal( 'token' );
@@ -607,6 +618,7 @@ class SpecialUndelete extends SpecialPage {
} elseif ( $user->isAllowed( 'deletedtext' ) ) {
$this->mAllowed = false; // user cannot restore
$this->mCanView = true; // user can view content
+ $this->mRestore = false;
} else { // user can only view the list of revisions
$this->mAllowed = false;
$this->mCanView = false;
@@ -633,104 +645,84 @@ class SpecialUndelete extends SpecialPage {
}
function execute( $par ) {
+ $this->checkPermissions();
+ $user = $this->getUser();
+
$this->setHeaders();
- if ( !$this->userCanExecute( $this->getUser() ) ) {
- $this->displayRestrictionError();
- return;
- }
$this->outputHeader();
- $this->loadRequest();
+ $this->loadRequest( $par );
$out = $this->getOutput();
- if ( $this->mAllowed ) {
- $out->setPageTitle( wfMsg( 'undeletepage' ) );
- } else {
- $out->setPageTitle( wfMsg( 'viewdeletedpage' ) );
- }
+ if ( is_null( $this->mTargetObj ) ) {
+ $out->addWikiMsg( 'undelete-header' );
- if( $par != '' ) {
- $this->mTarget = $par;
- }
- 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( $this->getUser()->isAllowed( 'browsearchive' ) ) {
+ if ( $user->isAllowed( 'browsearchive' ) ) {
$this->showSearchForm();
-
- # List undeletable articles
- if( $this->mSearchPrefix ) {
- $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
- $this->showList( $result );
- }
- } else {
- $out->addWikiMsg( 'undelete-header' );
}
return;
}
- if( $this->mTimestamp !== '' ) {
- return $this->showRevision( $this->mTimestamp );
+
+ if ( $this->mAllowed ) {
+ $out->setPageTitle( $this->msg( 'undeletepage' ) );
+ } else {
+ $out->setPageTitle( $this->msg( 'viewdeletedpage' ) );
}
- if( $this->mFilename !== null ) {
+
+ $this->getSkin()->setRelevantTitle( $this->mTargetObj );
+
+ if ( $this->mTimestamp !== '' ) {
+ $this->showRevision( $this->mTimestamp );
+ } elseif ( $this->mFilename !== null ) {
$file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
// Check if user is allowed to see this file
if ( !$file->exists() ) {
$out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
- return;
- } elseif( !$file->userCan( File::DELETED_FILE ) ) {
+ } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
if( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
- $out->permissionRequired( 'suppressrevision' );
+ throw new PermissionsError( 'suppressrevision' );
} else {
- $out->permissionRequired( 'deletedtext' );
+ throw new PermissionsError( 'deletedtext' );
}
- return false;
- } elseif ( !$this->getUser()->matchEditToken( $this->mToken, $this->mFilename ) ) {
+ } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
$this->showFileConfirmationForm( $this->mFilename );
- return false;
} else {
- return $this->showFile( $this->mFilename );
- }
- }
- if( $this->mRestore && $this->mAction == 'submit' ) {
- global $wgUploadMaintenance;
- if( $wgUploadMaintenance && $this->mTargetObj && $this->mTargetObj->getNamespace() == NS_FILE ) {
- $out->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", array( 'filedelete-maintenance' ) );
- return;
+ $this->showFile( $this->mFilename );
}
- return $this->undelete();
- }
- if( $this->mInvert && $this->mAction == 'submit' ) {
- return $this->showHistory();
+ } elseif ( $this->mRestore && $this->mAction == 'submit' ) {
+ $this->undelete();
+ } else {
+ $this->showHistory();
}
- return $this->showHistory();
}
function showSearchForm() {
global $wgScript;
- $this->getOutput()->addWikiMsg( 'undelete-header' );
-
- $this->getOutput()->addHTML(
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'undelete-search-title' ) );
+ $out->addHTML(
Xml::openElement( 'form', array(
'method' => 'get',
'action' => $wgScript ) ) .
- Xml::fieldset( wfMsg( 'undelete-search-box' ) ) .
+ Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
Html::hidden( 'title',
$this->getTitle()->getPrefixedDbKey() ) .
- Xml::inputLabel( wfMsg( 'undelete-search-prefix' ),
+ Xml::inputLabel( $this->msg( 'undelete-search-prefix' )->text(),
'prefix', 'prefix', 20,
$this->mSearchPrefix ) . ' ' .
- Xml::submitButton( wfMsg( 'undelete-search-submit' ) ) .
+ Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) .
Xml::closeElement( 'fieldset' ) .
Xml::closeElement( 'form' )
);
+
+ # List undeletable articles
+ if( $this->mSearchPrefix ) {
+ $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
+ $this->showList( $result );
+ }
}
/**
@@ -747,7 +739,7 @@ class SpecialUndelete extends SpecialPage {
return;
}
- $out->addWikiMsg( 'undeletepagetext', $this->getLang()->formatNum( $result->numRows() ) );
+ $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
$undelete = $this->getTitle();
$out->addHTML( "<ul>\n" );
@@ -759,9 +751,7 @@ class SpecialUndelete extends SpecialPage {
array(),
array( 'target' => $title->getPrefixedText() )
);
- $revs = wfMsgExt( 'undeleterevisions',
- array( 'parseinline' ),
- $this->getLang()->formatNum( $row->count ) );
+ $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse();
$out->addHTML( "<li>{$link} ({$revs})</li>\n" );
}
$result->free();
@@ -771,8 +761,6 @@ class SpecialUndelete extends SpecialPage {
}
private function showRevision( $timestamp ) {
- $out = $this->getOutput();
-
if( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
return 0;
}
@@ -781,13 +769,16 @@ class SpecialUndelete extends SpecialPage {
wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) );
$rev = $archive->getRevision( $timestamp );
+ $out = $this->getOutput();
+ $user = $this->getUser();
+
if( !$rev ) {
$out->addWikiMsg( 'undeleterevision-missing' );
return;
}
if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ if( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
$out->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
return;
} else {
@@ -797,13 +788,11 @@ class SpecialUndelete extends SpecialPage {
}
}
- $out->setPageTitle( wfMsg( 'undeletepage' ) );
-
if( $this->mDiff ) {
$previousRev = $archive->getPreviousRevision( $timestamp );
if( $previousRev ) {
$this->showDiff( $previousRev, $rev );
- if( $this->getUser()->getOption( 'diffonly' ) ) {
+ if( $this->mDiffOnly ) {
return;
} else {
$out->addHTML( '<hr />' );
@@ -818,12 +807,14 @@ class SpecialUndelete extends SpecialPage {
htmlspecialchars( $this->mTargetObj->getPrefixedText() )
);
+ $lang = $this->getLanguage();
+
// date and time are separate parameters to facilitate localisation.
// $time is kept for backward compat reasons.
- $time = $this->getLang()->timeAndDate( $timestamp, true );
- $d = $this->getLang()->date( $timestamp, true );
- $t = $this->getLang()->time( $timestamp, true );
- $user = Linker::revUserTools( $rev );
+ $time = $lang->userTimeAndDate( $timestamp, $user );
+ $d = $lang->userDate( $timestamp, $user );
+ $t = $lang->userTime( $timestamp, $user );
+ $userLink = Linker::revUserTools( $rev );
if( $this->mPreview ) {
$openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
@@ -834,14 +825,14 @@ class SpecialUndelete extends SpecialPage {
// Revision delete links
if ( !$this->mDiff ) {
- $revdel = $this->revDeleteLink( $rev );
+ $revdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
if ( $revdel ) {
- $out->addHTML( $revdel );
+ $out->addHTML( "$revdel " );
}
}
- $out->addHTML( wfMessage( 'undelete-revision' )->rawParams( $link )->params(
- $time )->rawParams( $user )->params( $d, $t )->parse() . '</div>' );
+ $out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params(
+ $time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' );
wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) );
if( $this->mPreview ) {
@@ -849,15 +840,15 @@ class SpecialUndelete extends SpecialPage {
$popts = $out->parserOptions();
$popts->setEditSection( false );
$out->parserOptions( $popts );
- $out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER ), $this->mTargetObj, true );
+ $out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER, $user ), $this->mTargetObj, true );
}
$out->addHTML(
Xml::element( 'textarea', array(
'readonly' => 'readonly',
- 'cols' => intval( $this->getUser()->getOption( 'cols' ) ),
- 'rows' => intval( $this->getUser()->getOption( 'rows' ) ) ),
- $rev->getText( Revision::FOR_THIS_USER ) . "\n" ) .
+ 'cols' => intval( $user->getOption( 'cols' ) ),
+ 'rows' => intval( $user->getOption( 'rows' ) ) ),
+ $rev->getText( Revision::FOR_THIS_USER, $user ) . "\n" ) .
Xml::openElement( 'div' ) .
Xml::openElement( 'form', array(
'method' => 'post',
@@ -873,62 +864,20 @@ class SpecialUndelete extends SpecialPage {
Xml::element( 'input', array(
'type' => 'hidden',
'name' => 'wpEditToken',
- 'value' => $this->getUser()->editToken() ) ) .
+ 'value' => $user->getEditToken() ) ) .
Xml::element( 'input', array(
'type' => 'submit',
'name' => 'preview',
- 'value' => wfMsg( 'showpreview' ) ) ) .
+ 'value' => $this->msg( 'showpreview' )->text() ) ) .
Xml::element( 'input', array(
'name' => 'diff',
'type' => 'submit',
- 'value' => wfMsg( 'showdiff' ) ) ) .
+ 'value' => $this->msg( 'showdiff' )->text() ) ) .
Xml::closeElement( 'form' ) .
Xml::closeElement( 'div' ) );
}
/**
- * 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.
*
@@ -937,7 +886,7 @@ class SpecialUndelete extends SpecialPage {
* @return String: HTML
*/
function showDiff( $previousRev, $currentRev ) {
- $diffEngine = new DifferenceEngine( $previousRev->getTitle() );
+ $diffEngine = new DifferenceEngine( $this->getContext() );
$diffEngine->showDiffStyle();
$this->getOutput()->addHTML(
"<div>" .
@@ -981,18 +930,20 @@ class SpecialUndelete extends SpecialPage {
$targetQuery = array( 'oldid' => $rev->getId() );
}
// Add show/hide deletion links if available
- $del = $this->revDeleteLink( $rev );
+ $user = $this->getUser();
+ $lang = $this->getLanguage();
+ $rdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
+ if ( $rdel ) $rdel = " $rdel";
return
'<div id="mw-diff-' . $prefix . 'title1"><strong>' .
Linker::link(
$targetPage,
- wfMsgExt(
+ $this->msg(
'revisionasof',
- array( 'escape' ),
- $this->getLang()->timeanddate( $rev->getTimestamp(), true ),
- $this->getLang()->date( $rev->getTimestamp(), true ),
- $this->getLang()->time( $rev->getTimestamp(), true )
- ),
+ $lang->userTimeAndDate( $rev->getTimestamp(), $user ),
+ $lang->userDate( $rev->getTimestamp(), $user ),
+ $lang->userTime( $rev->getTimestamp(), $user )
+ )->escaped(),
array(),
$targetQuery
) .
@@ -1001,7 +952,7 @@ class SpecialUndelete extends SpecialPage {
Linker::revUserTools( $rev ) . '<br />' .
'</div>' .
'<div id="mw-diff-'.$prefix.'title3">' .
- Linker::revComment( $rev ) . $del . '<br />' .
+ Linker::revComment( $rev ) . $rdel . '<br />' .
'</div>';
}
@@ -1009,21 +960,24 @@ class SpecialUndelete extends SpecialPage {
* Show a form confirming whether a tokenless user really wants to see a file
*/
private function showFileConfirmationForm( $key ) {
+ $out = $this->getOutput();
+ $lang = $this->getLanguage();
+ $user = $this->getUser();
$file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
- $this->getOutput()->addWikiMsg( 'undelete-show-file-confirm',
+ $out->addWikiMsg( 'undelete-show-file-confirm',
$this->mTargetObj->getText(),
- $this->getLang()->date( $file->getTimestamp() ),
- $this->getLang()->time( $file->getTimestamp() ) );
- $this->getOutput()->addHTML(
+ $lang->userDate( $file->getTimestamp(), $user ),
+ $lang->userTime( $file->getTimestamp(), $user ) );
+ $out->addHTML(
Xml::openElement( 'form', array(
'method' => 'POST',
'action' => $this->getTitle()->getLocalURL(
'target=' . urlencode( $this->mTarget ) .
'&file=' . urlencode( $key ) .
- '&token=' . urlencode( $this->getUser()->editToken( $key ) ) )
+ '&token=' . urlencode( $user->getEditToken( $key ) ) )
)
) .
- Xml::submitButton( wfMsg( 'undelete-show-file-submit' ) ) .
+ Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
'</form>'
);
}
@@ -1043,20 +997,15 @@ class SpecialUndelete extends SpecialPage {
$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" );
$repo = RepoGroup::singleton()->getLocalRepo();
$path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
- wfStreamFile( $path );
+ $repo->streamFile( $path );
}
private function showHistory() {
$out = $this->getOutput();
if( $this->mAllowed ) {
$out->addModules( 'mediawiki.special.undelete' );
- $out->setPageTitle( wfMsg( 'undeletepage' ) );
- } else {
- $out->setPageTitle( wfMsg( 'viewdeletedpage' ) );
}
$out->wrapWikiMsg(
"<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
@@ -1117,11 +1066,11 @@ class SpecialUndelete extends SpecialPage {
# Show relevant lines from the deletion log:
$out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) . "\n" );
- LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj->getPrefixedText() );
+ LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj );
# Show relevant lines from the suppression log:
if( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
$out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'suppress' ) ) . "\n" );
- LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj->getPrefixedText() );
+ LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj );
}
if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
@@ -1132,24 +1081,24 @@ class SpecialUndelete extends SpecialPage {
"<tr>
<td>&#160;</td>
<td class='mw-input'>" .
- Xml::checkLabel( wfMsg( 'revdelete-unsuppress' ), 'wpUnsuppress',
- 'mw-undelete-unsuppress', $this->mUnsuppress ).
+ Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(),
+ 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ).
"</td>
</tr>";
} else {
$unsuppressBox = '';
}
$table =
- Xml::fieldset( wfMsg( 'undelete-fieldset-title' ) ) .
+ Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) .
Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
"<tr>
<td colspan='2' class='mw-undelete-extrahelp'>" .
- wfMsgExt( 'undeleteextrahelp', 'parse' ) .
+ $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
"</td>
</tr>
<tr>
<td class='mw-label'>" .
- Xml::label( wfMsg( 'undeletecomment' ), 'wpComment' ) .
+ Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
"</td>
<td class='mw-input'>" .
Xml::input( 'wpComment', 50, $this->mComment, array( 'id' => 'wpComment' ) ) .
@@ -1158,8 +1107,8 @@ class SpecialUndelete extends SpecialPage {
<tr>
<td>&#160;</td>
<td class='mw-submit'>" .
- Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' .
- Xml::submitButton( wfMsg( 'undeleteinvert' ), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) .
+ Xml::submitButton( $this->msg( 'undeletebtn' )->text(), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' .
+ Xml::submitButton( $this->msg( 'undeleteinvert' )->text(), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) .
"</td>
</tr>" .
$unsuppressBox .
@@ -1169,7 +1118,7 @@ class SpecialUndelete extends SpecialPage {
$out->addHTML( $table );
}
- $out->addHTML( Xml::element( 'h2', null, wfMsg( 'history' ) ) . "\n" );
+ $out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" );
if( $haveRevisions ) {
# The page's stored (deleted) history:
@@ -1188,7 +1137,7 @@ class SpecialUndelete extends SpecialPage {
}
if( $haveFiles ) {
- $out->addHTML( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" );
+ $out->addHTML( Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n" );
$out->addHTML( '<ul>' );
foreach ( $files as $row ) {
$out->addHTML( $this->formatFileRow( $row ) );
@@ -1200,7 +1149,7 @@ class SpecialUndelete extends SpecialPage {
if ( $this->mAllowed ) {
# Slip in the hidden controls here
$misc = Html::hidden( 'target', $this->mTarget );
- $misc .= Html::hidden( 'wpEditToken', $this->getUser()->editToken() );
+ $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
$misc .= Xml::closeElement( 'form' );
$out->addHTML( $misc );
}
@@ -1227,18 +1176,19 @@ class SpecialUndelete extends SpecialPage {
} else {
$checkBox = '';
}
+ $user = $this->getUser();
// Build page & diff links...
if( $this->mCanView ) {
$titleObj = $this->getTitle();
# Last link
- if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
- $pageLink = htmlspecialchars( $this->getLang()->timeanddate( $ts, true ) );
- $last = wfMsgHtml( 'diff' );
+ if( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
+ $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
+ $last = $this->msg( 'diff' )->escaped();
} elseif( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
$pageLink = $this->getPageLink( $rev, $titleObj, $ts );
$last = Linker::linkKnown(
$titleObj,
- wfMsgHtml( 'diff' ),
+ $this->msg( 'diff' )->escaped(),
array(),
array(
'target' => $this->mTargetObj->getPrefixedText(),
@@ -1248,11 +1198,11 @@ class SpecialUndelete extends SpecialPage {
);
} else {
$pageLink = $this->getPageLink( $rev, $titleObj, $ts );
- $last = wfMsgHtml( 'diff' );
+ $last = $this->msg( 'diff' )->escaped();
}
} else {
- $pageLink = htmlspecialchars( $this->getLang()->timeanddate( $ts, true ) );
- $last = wfMsgHtml( 'diff' );
+ $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
+ $last = $this->msg( 'diff' )->escaped();
}
// User links
$userLink = Linker::revUserTools( $rev );
@@ -1264,7 +1214,7 @@ class SpecialUndelete extends SpecialPage {
// Edit summary
$comment = Linker::revComment( $rev );
// Revision delete links
- $revdlink = $this->revDeleteLink( $rev );
+ $revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
return "<li>$checkBox $revdlink ($last) $pageLink . . $userLink $stxt $comment</li>";
}
@@ -1272,28 +1222,25 @@ class SpecialUndelete extends SpecialPage {
$file = ArchivedFile::newFromRow( $row );
$ts = wfTimestamp( TS_MW, $row->fa_timestamp );
+ $user = $this->getUser();
if( $this->mAllowed && $row->fa_storage_key ) {
$checkBox = Xml::check( 'fileid' . $row->fa_id );
$key = urlencode( $row->fa_storage_key );
$pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key );
} else {
$checkBox = '';
- $pageLink = $this->getLang()->timeanddate( $ts, true );
+ $pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user );
}
$userLink = $this->getFileUser( $file );
- $data =
- wfMsg( 'widthheight',
- $this->getLang()->formatNum( $row->fa_width ),
- $this->getLang()->formatNum( $row->fa_height ) ) .
- ' (' .
- wfMsg( 'nbytes', $this->getLang()->formatNum( $row->fa_size ) ) .
- ')';
+ $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text() .
+ ' (' . $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() . ')';
$data = htmlspecialchars( $data );
$comment = $this->getFileComment( $file );
+
// Add show/hide deletion links if available
- $canHide = $this->getUser()->isAllowed( 'deleterevision' );
- if( $canHide || ( $file->getVisibility() && $this->getUser()->isAllowed( 'deletedhistory' ) ) ) {
- if( !$file->userCan( File::DELETED_RESTRICTED ) ) {
+ $canHide = $user->isAllowed( 'deleterevision' );
+ if( $canHide || ( $file->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
+ if( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
$revdlink = Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
} else {
$query = array(
@@ -1307,6 +1254,7 @@ class SpecialUndelete extends SpecialPage {
} else {
$revdlink = '';
}
+
return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
}
@@ -1314,17 +1262,20 @@ class SpecialUndelete extends SpecialPage {
* Fetch revision text link if it's available to all users
*
* @param $rev Revision
+ * @param $titleObj Title
+ * @param $ts Timestamp
* @return string
*/
function getPageLink( $rev, $titleObj, $ts ) {
- $time = htmlspecialchars( $this->getLang()->timeanddate( $ts, true ) );
+ $user = $this->getUser();
+ $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
- if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ if( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
return '<span class="history-deleted">' . $time . '</span>';
} else {
$link = Linker::linkKnown(
$titleObj,
- $time,
+ htmlspecialchars( $time ),
array(),
array(
'target' => $this->mTargetObj->getPrefixedText(),
@@ -1342,20 +1293,27 @@ class SpecialUndelete extends SpecialPage {
* Fetch image view link if it's available to all users
*
* @param $file File
+ * @param $titleObj Title
+ * @param $ts A timestamp
+ * @param $key String: a storage key
+ *
* @return String: HTML fragment
*/
function getFileLink( $file, $titleObj, $ts, $key ) {
- if( !$file->userCan( File::DELETED_FILE ) ) {
- return '<span class="history-deleted">' . $this->getLang()->timeanddate( $ts, true ) . '</span>';
+ $user = $this->getUser();
+ $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
+
+ if( !$file->userCan( File::DELETED_FILE, $user ) ) {
+ return '<span class="history-deleted">' . $time . '</span>';
} else {
$link = Linker::linkKnown(
$titleObj,
- $this->getLang()->timeanddate( $ts, true ),
+ htmlspecialchars( $time ),
array(),
array(
'target' => $this->mTargetObj->getPrefixedText(),
'file' => $key,
- 'token' => $this->getUser()->editToken( $key )
+ 'token' => $user->getEditToken( $key )
)
);
if( $file->isDeleted( File::DELETED_FILE ) ) {
@@ -1372,8 +1330,8 @@ class SpecialUndelete extends SpecialPage {
* @return String: HTML fragment
*/
function getFileUser( $file ) {
- if( !$file->userCan( File::DELETED_USER ) ) {
- return '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
+ if( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) {
+ return '<span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
} else {
$link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) .
Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() );
@@ -1391,9 +1349,9 @@ class SpecialUndelete extends SpecialPage {
* @return String: HTML fragment
*/
function getFileComment( $file ) {
- if( !$file->userCan( File::DELETED_COMMENT ) ) {
+ if( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) {
return '<span class="history-deleted"><span class="comment">' .
- wfMsgHtml( 'rev-deleted-comment' ) . '</span></span>';
+ $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>';
} else {
$link = Linker::commentBlock( $file->getRawDescription() );
if( $file->isDeleted( File::DELETED_COMMENT ) ) {
@@ -1404,41 +1362,44 @@ class SpecialUndelete extends SpecialPage {
}
function undelete() {
+ global $wgUploadMaintenance;
+
+ if ( $wgUploadMaintenance && $this->mTargetObj->getNamespace() == NS_FILE ) {
+ throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
+ }
+
if ( wfReadOnly() ) {
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
- wfRunHooks( 'FileUndeleteComplete', array(
- $this->mTargetObj, $this->mFileVersions,
- $this->getUser(), $this->mComment ) );
- }
-
- $link = Linker::linkKnown( $this->mTargetObj );
- $this->getOutput()->addHTML( wfMessage( 'undeletedpage' )->rawParams( $link )->parse() );
- } else {
- $this->getOutput()->showFatalError( wfMsg( 'cannotundelete' ) );
- $this->getOutput()->addWikiMsg( 'undeleterevdel' );
+ $out = $this->getOutput();
+ $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
+ wfRunHooks( 'FileUndeleteComplete', array(
+ $this->mTargetObj, $this->mFileVersions,
+ $this->getUser(), $this->mComment ) );
}
- // Show file deletion warnings and errors
- $status = $archive->getFileStatus();
- if( $status && !$status->isGood() ) {
- $this->getOutput()->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) );
- }
+ $link = Linker::linkKnown( $this->mTargetObj );
+ $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
} else {
- $this->getOutput()->showFatalError( wfMsg( 'cannotundelete' ) );
+ $out->setPageTitle( $this->msg( 'undelete-error' ) );
+ $out->addWikiMsg( 'cannotundelete' );
+ $out->addWikiMsg( 'undeleterevdel' );
+ }
+
+ // Show file deletion warnings and errors
+ $status = $archive->getFileStatus();
+ if( $status && !$status->isGood() ) {
+ $out->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) );
}
- return false;
}
}
diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php
index 95ad0bf5..2e772540 100644
--- a/includes/specials/SpecialUnlockdb.php
+++ b/includes/specials/SpecialUnlockdb.php
@@ -26,102 +26,62 @@
*
* @ingroup SpecialPage
*/
-class SpecialUnlockdb extends SpecialPage {
+class SpecialUnlockdb extends FormSpecialPage {
public function __construct() {
parent::__construct( 'Unlockdb', 'siteadmin' );
}
- public function execute( $par ) {
- global $wgUser, $wgRequest;
-
- $this->setHeaders();
-
- # Permission check
- if( !$this->userCanExecute( $wgUser ) ) {
- $this->displayRestrictionError();
- return;
- }
-
- $this->outputHeader();
-
- $action = $wgRequest->getVal( 'action' );
-
- if ( $action == 'success' ) {
- $this->showSuccess();
- } elseif ( $action == 'submit' && $wgRequest->wasPosted() &&
- $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
- $this->doSubmit();
- } else {
- $this->showForm();
- }
+ public function requiresWrite() {
+ return false;
}
- private function showForm( $err = '' ) {
- global $wgOut, $wgUser;
-
+ public function checkExecutePermissions( User $user ) {
global $wgReadOnlyFile;
- if( !file_exists( $wgReadOnlyFile ) ) {
- $wgOut->addWikiMsg( 'databasenotlocked' );
- return;
- }
-
- $wgOut->addWikiMsg( 'unlockdbtext' );
- if ( $err != '' ) {
- $wgOut->setSubtitle( wfMsg( 'formerror' ) );
- $wgOut->addHTML( '<p class="error">' . htmlspecialchars( $err ) . "</p>\n" );
+ parent::checkExecutePermissions( $user );
+ # If the lock file isn't writable, we can do sweet bugger all
+ if ( !file_exists( $wgReadOnlyFile ) ) {
+ throw new ErrorPageError( 'lockdb', 'databasenotlocked' );
}
+ }
- $wgOut->addHTML(
- Html::openElement( 'form', array( 'id' => 'unlockdb', 'method' => 'POST',
- 'action' => $this->getTitle()->getLocalURL( 'action=submit' ) ) ) . "
-<table>
- <tr>
- " . Html::openElement( 'td', array( 'style' => 'text-align:right' ) ) . "
- " . Html::input( 'wpLockConfirm', null, 'checkbox' ) . "
- </td>
- " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) .
- wfMsgHtml( 'unlockconfirm' ) . "</td>
- </tr>
- <tr>
- <td>&#160;</td>
- " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) . "
- " . Html::input( 'wpLock', wfMsg( 'unlockbtn' ), 'submit' ) . "
- </td>
- </tr>
-</table>\n" .
- Html::hidden( 'wpEditToken', $wgUser->editToken() ) . "\n" .
- Html::closeElement( 'form' )
+ protected function getFormFields() {
+ return array(
+ 'Confirm' => array(
+ 'type' => 'toggle',
+ 'label-message' => 'unlockconfirm',
+ ),
);
+ }
+ protected function alterForm( HTMLForm $form ) {
+ $form->setWrapperLegend( false );
+ $form->setHeaderText( $this->msg( 'unlockdbtext' )->parseAsBlock() );
+ $form->setSubmitTextMsg( 'unlockbtn' );
}
- private function doSubmit() {
- global $wgOut, $wgRequest, $wgReadOnlyFile;
+ public function onSubmit( array $data ) {
+ global $wgReadOnlyFile;
- $wpLockConfirm = $wgRequest->getCheck( 'wpLockConfirm' );
- if ( !$wpLockConfirm ) {
- $this->showForm( wfMsg( 'locknoconfirm' ) );
- return;
+ if ( !$data['Confirm'] ) {
+ return Status::newFatal( 'locknoconfirm' );
}
wfSuppressWarnings();
$res = unlink( $wgReadOnlyFile );
wfRestoreWarnings();
- if ( !$res ) {
- $wgOut->showFileDeleteError( $wgReadOnlyFile );
- return;
+ if ( $res ) {
+ return Status::newGood();
+ } else {
+ return Status::newFatal( 'filedeleteerror', $wgReadOnlyFile );
}
-
- $wgOut->redirect( $this->getTitle()->getFullURL( 'action=success' ) );
}
- private function showSuccess() {
- global $wgOut;
-
- $wgOut->setSubtitle( wfMsg( 'unlockdbsuccesssub' ) );
- $wgOut->addWikiMsg( 'unlockdbsuccesstext' );
+ public function onSuccess() {
+ $out = $this->getOutput();
+ $out->addSubtitle( $this->msg( 'unlockdbsuccesssub' ) );
+ $out->addWikiMsg( 'unlockdbsuccesstext' );
}
}
diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php
index e4b8e544..48a93e8d 100644
--- a/includes/specials/SpecialUnusedcategories.php
+++ b/includes/specials/SpecialUnusedcategories.php
@@ -33,7 +33,7 @@ class UnusedCategoriesPage extends QueryPage {
}
function getPageHeader() {
- return wfMsgExt( 'unusedcategoriestext', array( 'parse' ) );
+ return $this->msg( 'unusedcategoriestext' )->parseAsBlock();
}
function getQueryInfo() {
@@ -59,6 +59,6 @@ class UnusedCategoriesPage extends QueryPage {
function formatResult( $skin, $result ) {
$title = Title::makeTitle( NS_CATEGORY, $result->title );
- return $skin->link( $title, $title->getText() );
+ return Linker::link( $title, htmlspecialchars( $title->getText() ) );
}
}
diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php
index da501605..e5c55b83 100644
--- a/includes/specials/SpecialUnusedtemplates.php
+++ b/includes/specials/SpecialUnusedtemplates.php
@@ -61,19 +61,19 @@ class UnusedtemplatesPage extends QueryPage {
*/
function formatResult( $skin, $result ) {
$title = Title::makeTitle( NS_TEMPLATE, $result->title );
- $pageLink = $skin->linkKnown(
+ $pageLink = Linker::linkKnown(
$title,
null,
array(),
array( 'redirect' => 'no' )
);
- $wlhLink = $skin->linkKnown(
+ $wlhLink = Linker::linkKnown(
SpecialPage::getTitleFor( 'Whatlinkshere' ),
wfMsgHtml( 'unusedtemplateswlh' ),
array(),
array( 'target' => $title->getPrefixedText() )
);
- return wfSpecialList( $pageLink, $wlhLink );
+ return $this->getLanguage()->specialList( $pageLink, $wlhLink );
}
function getPageHeader() {
diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php
index 0f11140b..22c64858 100644
--- a/includes/specials/SpecialUnwatchedpages.php
+++ b/includes/specials/SpecialUnwatchedpages.php
@@ -83,6 +83,6 @@ class UnwatchedpagesPage extends QueryPage {
array( 'action' => 'watch', 'token' => $token )
);
- return wfSpecialList( $plink, $wlink );
+ return $this->getLanguage()->specialList( $plink, $wlink );
}
}
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 33013e08..d6a76d02 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -35,11 +35,7 @@ class SpecialUpload extends SpecialPage {
* @param $request WebRequest : data posted.
*/
public function __construct( $request = null ) {
- global $wgRequest;
-
parent::__construct( 'Upload', 'upload' );
-
- $this->loadRequest( is_null( $request ) ? $wgRequest : $request );
}
/** Misc variables **/
@@ -83,13 +79,9 @@ class SpecialUpload extends SpecialPage {
/**
* Initialize instance variables from request and create an Upload handler
- *
- * @param $request WebRequest: the request to extract variables from
*/
- protected function loadRequest( $request ) {
- global $wgUser;
-
- $this->mRequest = $request;
+ protected function loadRequest() {
+ $this->mRequest = $request = $this->getRequest();
$this->mSourceType = $request->getVal( 'wpSourceType', 'file' );
$this->mUpload = UploadBase::createFromRequest( $request );
$this->mUploadClicked = $request->wasPosted()
@@ -108,7 +100,7 @@ class SpecialUpload extends SpecialPage {
$this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' );
$this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' )
|| $request->getCheck( 'wpUploadIgnoreWarning' );
- $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $wgUser->isLoggedIn();
+ $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $this->getUser()->isLoggedIn();
$this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' );
$this->mCopyrightSource = $request->getText( 'wpUploadSource' );
@@ -119,7 +111,7 @@ class SpecialUpload extends SpecialPage {
// If it was posted check for the token (no remote POST'ing with user credentials)
$token = $request->getVal( 'wpEditToken' );
- $this->mTokenOk = $wgUser->matchEditToken( $token );
+ $this->mTokenOk = $this->getUser()->matchEditToken( $token );
$this->uploadFormTextTop = '';
$this->uploadFormTextAfterSummary = '';
@@ -141,42 +133,30 @@ class SpecialUpload extends SpecialPage {
* Special page entry point
*/
public function execute( $par ) {
- global $wgUser, $wgOut;
-
$this->setHeaders();
$this->outputHeader();
# Check uploading enabled
if( !UploadBase::isEnabled() ) {
- $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext' );
- return;
+ throw new ErrorPageError( 'uploaddisabled', 'uploaddisabledtext' );
}
# Check permissions
- global $wgGroupPermissions;
- $permissionRequired = UploadBase::isAllowed( $wgUser );
+ $user = $this->getUser();
+ $permissionRequired = UploadBase::isAllowed( $user );
if( $permissionRequired !== true ) {
- if( !$wgUser->isLoggedIn() && ( $wgGroupPermissions['user']['upload']
- || $wgGroupPermissions['autoconfirmed']['upload'] ) ) {
- // Custom message if logged-in users without any special rights can upload
- $wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
- } else {
- $wgOut->permissionRequired( $permissionRequired );
- }
- return;
+ throw new PermissionsError( $permissionRequired );
}
# Check blocks
- if( $wgUser->isBlocked() ) {
- $wgOut->blockedPage();
- return;
+ if( $user->isBlocked() ) {
+ throw new UserBlockedError( $user->mBlock );
}
# Check whether we actually want to allow changing stuff
- if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- }
+ $this->checkReadOnly();
+
+ $this->loadRequest();
# Unsave the temporary file in case this was a cancelled upload
if ( $this->mCancelUpload ) {
@@ -190,8 +170,7 @@ class SpecialUpload extends SpecialPage {
if (
$this->mTokenOk && !$this->mCancelUpload &&
( $this->mUpload && $this->mUploadClicked )
- )
- {
+ ) {
$this->processUpload();
} else {
# Backwards compatibility hook
@@ -199,8 +178,6 @@ class SpecialUpload extends SpecialPage {
wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
return;
}
-
-
$this->showUploadForm( $this->getUploadForm() );
}
@@ -224,8 +201,7 @@ class SpecialUpload extends SpecialPage {
if ( $form instanceof HTMLForm ) {
$form->show();
} else {
- global $wgOut;
- $wgOut->addHTML( $form );
+ $this->getOutput()->addHTML( $form );
}
}
@@ -239,8 +215,6 @@ class SpecialUpload extends SpecialPage {
* @return UploadForm
*/
protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) {
- global $wgOut;
-
# Initialize form
$form = new UploadForm( array(
'watch' => $this->getWatchCheck(),
@@ -253,15 +227,14 @@ class SpecialUpload extends SpecialPage {
'texttop' => $this->uploadFormTextTop,
'textaftersummary' => $this->uploadFormTextAfterSummary,
'destfile' => $this->mDesiredDestName,
- ) );
+ ), $this->getContext() );
$form->setTitle( $this->getTitle() );
# Check the token, but only if necessary
if(
!$this->mTokenOk && !$this->mCancelUpload &&
( $this->mUpload && $this->mUploadClicked )
- )
- {
+ ) {
$form->addPreText( wfMsgExt( 'session_fail_preview', 'parseinline' ) );
}
@@ -271,7 +244,7 @@ class SpecialUpload extends SpecialPage {
$delNotice = ''; // empty by default
if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) {
LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ),
- $desiredTitleObj->getPrefixedText(),
+ $desiredTitleObj,
'', array( 'lim' => 10,
'conds' => array( "log_action != 'revision'" ),
'showIfEmpty' => false,
@@ -291,33 +264,31 @@ class SpecialUpload extends SpecialPage {
$uploadFooter = wfMessage( 'uploadfooter' );
if ( !$uploadFooter->isDisabled() ) {
$form->addPostText( '<div id="mw-upload-footer-message">'
- . $wgOut->parse( $uploadFooter->plain() ) . "</div>\n" );
+ . $this->getOutput()->parse( $uploadFooter->plain() ) . "</div>\n" );
}
return $form;
-
}
/**
* Shows the "view X deleted revivions link""
*/
protected function showViewDeletedLinks() {
- global $wgOut, $wgUser;
-
$title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
+ $user = $this->getUser();
// Show a subtitle link to deleted revisions (to sysops et al only)
if( $title instanceof Title ) {
$count = $title->isDeleted();
- if ( $count > 0 && $wgUser->isAllowed( 'deletedhistory' ) ) {
+ if ( $count > 0 && $user->isAllowed( 'deletedhistory' ) ) {
$link = wfMsgExt(
- $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted',
+ $user->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted',
array( 'parse', 'replaceafter' ),
- $this->getSkin()->linkKnown(
+ Linker::linkKnown(
SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count )
)
);
- $wgOut->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
+ $this->getOutput()->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
}
}
}
@@ -402,7 +373,7 @@ class SpecialUpload extends SpecialPage {
/**
* Show the upload form with error message, but do not stash the file.
*
- * @param $message HTML string
+ * @param $message string HTML string
*/
protected function showUploadError( $message ) {
$message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" .
@@ -415,12 +386,10 @@ class SpecialUpload extends SpecialPage {
* Checks are made in SpecialUpload::execute()
*/
protected function processUpload() {
- global $wgUser, $wgOut;
-
// Fetch the file if required
$status = $this->mUpload->fetchFile();
if( !$status->isOK() ) {
- $this->showUploadError( $wgOut->parse( $status->getWikiText() ) );
+ $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
return;
}
@@ -442,7 +411,7 @@ class SpecialUpload extends SpecialPage {
}
// Verify permissions for this title
- $permErrors = $this->mUpload->verifyTitlePermissions( $wgUser );
+ $permErrors = $this->mUpload->verifyTitlePermissions( $this->getUser() );
if( $permErrors !== true ) {
$code = array_shift( $permErrors[0] );
$this->showRecoverableUploadError( wfMsgExt( $code,
@@ -467,25 +436,31 @@ class SpecialUpload extends SpecialPage {
} else {
$pageText = false;
}
- $status = $this->mUpload->performUpload( $this->mComment, $pageText, $this->mWatchthis, $wgUser );
+ $status = $this->mUpload->performUpload( $this->mComment, $pageText, $this->mWatchthis, $this->getUser() );
if ( !$status->isGood() ) {
- $this->showUploadError( $wgOut->parse( $status->getWikiText() ) );
+ $this->showUploadError( $this->getOutput()->parse( $status->getWikiText() ) );
return;
}
// Success, redirect to description page
$this->mUploadSuccessful = true;
wfRunHooks( 'SpecialUploadComplete', array( &$this ) );
- $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() );
+ $this->getOutput()->redirect( $this->mLocalFile->getTitle()->getFullURL() );
}
/**
* Get the initial image page text based on a comment and optional file status information
+ * @param $comment string
+ * @param $license string
+ * @param $copyStatus string
+ * @param $source string
+ * @return string
*/
public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) {
global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg;
$wgForceUIMsgAsContentMsg = (array) $wgForceUIMsgAsContentMsg;
+ $msg = array();
/* These messages are transcluded into the actual text of the description page.
* Thus, forcing them as content messages makes the upload to produce an int: template
* instead of hardcoding it there in the uploader language.
@@ -529,10 +504,10 @@ class SpecialUpload extends SpecialPage {
*
* Note that the page target can be changed *on the form*, so our check
* state can get out of sync.
+ * @return Bool|String
*/
protected function getWatchCheck() {
- global $wgUser;
- if( $wgUser->getOption( 'watchdefault' ) ) {
+ if( $this->getUser()->getOption( 'watchdefault' ) ) {
// Watch all edits!
return true;
}
@@ -544,7 +519,7 @@ class SpecialUpload extends SpecialPage {
return $local->getTitle()->userIsWatching();
} else {
// New page should get watched if that's our option.
- return $wgUser->getOption( 'watchcreations' );
+ return $this->getUser()->getOption( 'watchcreations' );
}
}
@@ -555,7 +530,7 @@ class SpecialUpload extends SpecialPage {
* @param $details Array: result of UploadBase::verifyUpload
*/
protected function processVerificationError( $details ) {
- global $wgFileExtensions, $wgLang;
+ global $wgFileExtensions;
switch( $details['status'] ) {
@@ -567,6 +542,9 @@ class SpecialUpload extends SpecialPage {
$this->showRecoverableUploadError( wfMsgExt( 'illegalfilename',
'parseinline', $details['filtered'] ) );
break;
+ case UploadBase::FILENAME_TOO_LONG:
+ $this->showRecoverableUploadError( wfMsgHtml( 'filename-toolong' ) );
+ break;
case UploadBase::FILETYPE_MISSING:
$this->showRecoverableUploadError( wfMsgExt( 'filetype-missing',
'parseinline' ) );
@@ -586,11 +564,11 @@ class SpecialUpload extends SpecialPage {
case UploadBase::FILETYPE_BADTYPE:
$msg = wfMessage( 'filetype-banned-type' );
if ( isset( $details['blacklistedExt'] ) ) {
- $msg->params( $wgLang->commaList( $details['blacklistedExt'] ) );
+ $msg->params( $this->getLanguage()->commaList( $details['blacklistedExt'] ) );
} else {
$msg->params( $details['finalExt'] );
}
- $msg->params( $wgLang->commaList( $wgFileExtensions ),
+ $msg->params( $this->getLanguage()->commaList( $wgFileExtensions ),
count( $wgFileExtensions ) );
// Add PLURAL support for the first parameter. This results
@@ -631,13 +609,12 @@ class SpecialUpload extends SpecialPage {
* @return Boolean: success
*/
protected function unsaveUploadedFile() {
- global $wgOut;
if ( !( $this->mUpload instanceof UploadFromStash ) ) {
return true;
}
$success = $this->mUpload->unsaveUploadedFile();
if ( !$success ) {
- $wgOut->showFileDeleteError( $this->mUpload->getTempPath() );
+ $this->getOutput()->showFileDeleteError( $this->mUpload->getTempPath() );
return false;
} else {
return true;
@@ -654,8 +631,6 @@ class SpecialUpload extends SpecialPage {
* @return String: empty string if there is no warning or an HTML fragment
*/
public static function getExistsWarning( $exists ) {
- global $wgUser;
-
if ( !$exists ) {
return '';
}
@@ -664,8 +639,6 @@ class SpecialUpload extends SpecialPage {
$filename = $file->getTitle()->getPrefixedText();
$warning = '';
- $sk = $wgUser->getSkin();
-
if( $exists['warning'] == 'exists' ) {
// Exact match
$warning = wfMsgExt( 'fileexists', 'parseinline', $filename );
@@ -689,7 +662,7 @@ class SpecialUpload extends SpecialPage {
} elseif ( $exists['warning'] == 'was-deleted' ) {
# If the file existed before and was deleted, warn the user of this
$ltitle = SpecialPage::getTitleFor( 'Log' );
- $llink = $sk->linkKnown(
+ $llink = Linker::linkKnown(
$ltitle,
wfMsgHtml( 'deletionlog' ),
array(),
@@ -730,10 +703,12 @@ class SpecialUpload extends SpecialPage {
/**
* Construct a warning and a gallery from an array of duplicate files.
+ * @param $dupes array
+ * @return string
*/
public static function getDupeWarning( $dupes ) {
+ global $wgOut;
if( $dupes ) {
- global $wgOut;
$msg = '<gallery>';
foreach( $dupes as $file ) {
$title = $file->getTitle();
@@ -771,7 +746,9 @@ class UploadForm extends HTMLForm {
protected $mMaxFileSize = array();
- public function __construct( $options = array() ) {
+ protected $mMaxUploadSize = array();
+
+ public function __construct( array $options = array(), IContextSource $context = null ) {
$this->mWatch = !empty( $options['watch'] );
$this->mForReUpload = !empty( $options['forreupload'] );
$this->mSessionKey = isset( $options['sessionkey'] )
@@ -795,7 +772,7 @@ class UploadForm extends HTMLForm {
+ $this->getOptionsSection();
wfRunHooks( 'UploadFormInitDescriptor', array( &$descriptor ) );
- parent::__construct( $descriptor, 'upload' );
+ parent::__construct( $descriptor, $context, 'upload' );
# Set some form properties
$this->setSubmitText( wfMsg( 'uploadbtn' ) );
@@ -821,8 +798,6 @@ class UploadForm extends HTMLForm {
* @return Array: descriptor array
*/
protected function getSourceSection() {
- global $wgLang, $wgUser, $wgRequest;
-
if ( $this->mSessionKey ) {
return array(
'SessionKey' => array(
@@ -836,9 +811,9 @@ class UploadForm extends HTMLForm {
);
}
- $canUploadByUrl = UploadFromUrl::isEnabled() && $wgUser->isAllowed( 'upload_by_url' );
+ $canUploadByUrl = UploadFromUrl::isEnabled() && UploadFromUrl::isAllowed( $this->getUser() );
$radio = $canUploadByUrl;
- $selectedSourceType = strtolower( $wgRequest->getText( 'wpSourceType', 'File' ) );
+ $selectedSourceType = strtolower( $this->getRequest()->getText( 'wpSourceType', 'File' ) );
$descriptor = array();
if ( $this->mTextTop ) {
@@ -868,7 +843,7 @@ class UploadForm extends HTMLForm {
'radio' => &$radio,
'help' => wfMsgExt( 'upload-maxfilesize',
array( 'parseinline', 'escapenoentities' ),
- $wgLang->formatSize( $this->mMaxUploadSize['file'] )
+ $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['file'] )
) . ' ' . wfMsgHtml( 'upload_source_file' ),
'checked' => $selectedSourceType == 'file',
);
@@ -883,7 +858,7 @@ class UploadForm extends HTMLForm {
'radio' => &$radio,
'help' => wfMsgExt( 'upload-maxfilesize',
array( 'parseinline', 'escapenoentities' ),
- $wgLang->formatSize( $this->mMaxUploadSize['url'] )
+ $this->getContext()->getLanguage()->formatSize( $this->mMaxUploadSize['url'] )
) . ' ' . wfMsgHtml( 'upload_source_url' ),
'checked' => $selectedSourceType == 'url',
);
@@ -907,7 +882,7 @@ class UploadForm extends HTMLForm {
protected function getExtensionsMessage() {
# Print a list of allowed file extensions, if so configured. We ignore
# MIME type here, it's incomprehensible to most people and too long.
- global $wgLang, $wgCheckFileExtensions, $wgStrictFileExtensions,
+ global $wgCheckFileExtensions, $wgStrictFileExtensions,
$wgFileExtensions, $wgFileBlacklist;
if( $wgCheckFileExtensions ) {
@@ -915,16 +890,16 @@ class UploadForm extends HTMLForm {
# Everything not permitted is banned
$extensionsList =
'<div id="mw-upload-permitted">' .
- wfMsgExt( 'upload-permitted', 'parse', $wgLang->commaList( $wgFileExtensions ) ) .
+ wfMsgExt( 'upload-permitted', 'parse', $this->getContext()->getLanguage()->commaList( $wgFileExtensions ) ) .
"</div>\n";
} else {
# We have to list both preferred and prohibited
$extensionsList =
'<div id="mw-upload-preferred">' .
- wfMsgExt( 'upload-preferred', 'parse', $wgLang->commaList( $wgFileExtensions ) ) .
+ wfMsgExt( 'upload-preferred', 'parse', $this->getContext()->getLanguage()->commaList( $wgFileExtensions ) ) .
"</div>\n" .
'<div id="mw-upload-prohibited">' .
- wfMsgExt( 'upload-prohibited', 'parse', $wgLang->commaList( $wgFileBlacklist ) ) .
+ wfMsgExt( 'upload-prohibited', 'parse', $this->getContext()->getLanguage()->commaList( $wgFileBlacklist ) ) .
"</div>\n";
}
} else {
@@ -941,8 +916,6 @@ class UploadForm extends HTMLForm {
* @return Array: descriptor array
*/
protected function getDescriptionSection() {
- global $wgUser;
-
if ( $this->mSessionKey ) {
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
try {
@@ -982,7 +955,7 @@ class UploadForm extends HTMLForm {
? 'filereuploadsummary'
: 'fileuploadsummary',
'default' => $this->mComment,
- 'cols' => intval( $wgUser->getOption( 'cols' ) ),
+ 'cols' => intval( $this->getUser()->getOption( 'cols' ) ),
'rows' => 8,
)
);
@@ -1041,16 +1014,15 @@ class UploadForm extends HTMLForm {
* @return Array: descriptor array
*/
protected function getOptionsSection() {
- global $wgUser;
-
- if ( $wgUser->isLoggedIn() ) {
+ $user = $this->getUser();
+ if ( $user->isLoggedIn() ) {
$descriptor = array(
'Watchthis' => array(
'type' => 'check',
'id' => 'wpWatchthis',
'label-message' => 'watchthisupload',
'section' => 'options',
- 'default' => $wgUser->getOption( 'watchcreations' ),
+ 'default' => $user->getOption( 'watchcreations' ),
)
);
}
@@ -1089,11 +1061,10 @@ class UploadForm extends HTMLForm {
}
/**
- * Add upload JS to $wgOut
+ * Add upload JS to the OutputPage
*/
protected function addUploadJS() {
global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview, $wgEnableAPI, $wgStrictFileExtensions;
- global $wgOut;
$useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
$useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview && $wgEnableAPI;
@@ -1112,10 +1083,11 @@ class UploadForm extends HTMLForm {
'wgMaxUploadSize' => $this->mMaxUploadSize,
);
- $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
+ $out = $this->getOutput();
+ $out->addJsConfigVars( $scriptVars );
- $wgOut->addModules( array(
+ $out->addModules( array(
'mediawiki.action.edit', // For <charinsert> support
'mediawiki.legacy.upload', // Old form stuff...
'mediawiki.special.upload', // Newer extras for thumbnail preview.
@@ -1137,6 +1109,11 @@ class UploadForm extends HTMLForm {
* A form field that contains a radio box in the label
*/
class UploadSourceField extends HTMLTextField {
+
+ /**
+ * @param $cellAttributes array
+ * @return string
+ */
function getLabelHtml( $cellAttributes = array() ) {
$id = "wpSourceType{$this->mParams['upload-type']}";
$label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel );
@@ -1157,6 +1134,9 @@ class UploadSourceField extends HTMLTextField {
return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, $label );
}
+ /**
+ * @return int
+ */
function getSize() {
return isset( $this->mParams['size'] )
? $this->mParams['size']
diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php
index 20a37f0b..121b6a44 100644
--- a/includes/specials/SpecialUploadStash.php
+++ b/includes/specials/SpecialUploadStash.php
@@ -35,7 +35,6 @@ class SpecialUploadStash extends UnlistedSpecialPage {
try {
$this->stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
} catch ( UploadStashNotAvailableException $e ) {
- return null;
}
}
@@ -46,21 +45,14 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* @return Boolean: success
*/
public function execute( $subPage ) {
- global $wgUser;
+ $this->checkPermissions();
- if ( !$this->userCanExecute( $wgUser ) ) {
- $this->displayRestrictionError();
- return;
- }
-
- if ( !isset( $subPage ) || $subPage === '' ) {
+ if ( $subPage === null || $subPage === '' ) {
return $this->showUploads();
}
-
return $this->showUpload( $subPage );
}
-
/**
* If file available in stash, cats it out to the client as a simple HTTP response.
* n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
@@ -68,10 +60,8 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* @param $key String: the key of a particular requested file
*/
public function showUpload( $key ) {
- global $wgOut;
-
// prevent callers from doing standard HTML output -- we'll take it from here
- $wgOut->disable();
+ $this->getOutput()->disable();
try {
$params = $this->parseKey( $key );
@@ -97,8 +87,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
$message = $e->getMessage();
}
- wfHttpError( $code, HttpStatus::getMessage( $code ), $message );
- return false;
+ throw new HttpError( $code, $message );
}
/**
@@ -177,14 +166,15 @@ class SpecialUploadStash extends UnlistedSpecialPage {
}
// we should have just generated it locally
- if ( ! $thumbnailImage->getPath() ) {
+ if ( !$thumbnailImage->getStoragePath() ) {
throw new UploadStashFileNotFoundException( "no local path for scaled item" );
}
// now we should construct a File, so we can get mime and other such info in a standard way
// n.b. mimetype may be different from original (ogx original -> jpeg thumb)
- $thumbFile = new UnregisteredLocalFile( false, $this->stash->repo, $thumbnailImage->getPath(), false );
- if ( ! $thumbFile ) {
+ $thumbFile = new UnregisteredLocalFile( false,
+ $this->stash->repo, $thumbnailImage->getStoragePath(), false );
+ if ( !$thumbFile ) {
throw new UploadStashFileNotFoundException( "couldn't create local file object for thumbnail" );
}
@@ -210,12 +200,20 @@ class SpecialUploadStash extends UnlistedSpecialPage {
// this global probably looks something like 'http://upload.wikimedia.org/wikipedia/test/thumb/temp'
// do not use trailing slash
global $wgUploadStashScalerBaseUrl;
+ $scalerBaseUrl = $wgUploadStashScalerBaseUrl;
+
+ if( preg_match( '/^\/\//', $scalerBaseUrl ) ) {
+ // this is apparently a protocol-relative URL, which makes no sense in this context,
+ // since this is used for communication that's internal to the application.
+ // default to http.
+ $scalerBaseUrl = wfExpandUrl( $scalerBaseUrl, PROTO_CANONICAL );
+ }
// 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() .
+ $scalerThumbUrl = $scalerBaseUrl . '/' . $file->getUrlRel() .
'/' . rawurlencode( $scalerThumbName );
// make a curl call to the scaler to create a thumbnail
@@ -241,18 +239,17 @@ class SpecialUploadStash extends UnlistedSpecialPage {
/**
* Output HTTP response for file
* Side effect: writes HTTP response to STDOUT.
- * XXX could use wfStreamfile (in includes/Streamfile.php), but for consistency with outputContents() doing it this way.
- * XXX is mimeType really enough, or do we need encoding for full Content-Type header?
*
* @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!)
*/
- private function outputLocalFile( $file ) {
+ private function outputLocalFile( File $file ) {
if ( $file->getSize() > self::MAX_SERVE_BYTES ) {
throw new SpecialUploadStashTooLargeException();
}
- self::outputFileHeaders( $file->getMimeType(), $file->getSize() );
- readfile( $file->getPath() );
- return true;
+ return $file->getRepo()->streamFile( $file->getPath(),
+ array( 'Content-Transfer-Encoding: binary',
+ 'Expires: Sun, 17-Jan-2038 19:14:07 GMT' )
+ );
}
/**
@@ -309,7 +306,6 @@ class SpecialUploadStash extends UnlistedSpecialPage {
* @param Status : $status - the result of processRequest
*/
private function showUploads( $status = null ) {
- global $wgOut;
if ( $status === null ) {
$status = Status::newGood();
}
@@ -327,7 +323,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
'default' => true,
'name' => 'clear',
)
- ), 'clearStashedUploads' );
+ ), $this->getContext(), 'clearStashedUploads' );
$form->setSubmitCallback( array( __CLASS__ , 'tryClearStashedUploads' ) );
$form->setTitle( $this->getTitle() );
$form->setSubmitText( wfMsg( 'uploadstash-clear' ) );
@@ -340,7 +336,7 @@ class SpecialUploadStash extends UnlistedSpecialPage {
array( 'href' => $this->getTitle()->getLocalURL() ),
wfMsg( 'uploadstash-refresh' ) );
$files = $this->stash->listFiles();
- if ( count( $files ) ) {
+ if ( $files && count( $files ) ) {
sort( $files );
$fileListItemsHtml = '';
foreach ( $files as $file ) {
@@ -350,11 +346,11 @@ class SpecialUploadStash extends UnlistedSpecialPage {
$this->getTitle( "file/$file" )->getLocalURL() ), $file )
);
}
- $wgOut->addHtml( Html::rawElement( 'ul', array(), $fileListItemsHtml ) );
+ $this->getOutput()->addHtml( Html::rawElement( 'ul', array(), $fileListItemsHtml ) );
$form->displayForm( $formResult );
- $wgOut->addHtml( Html::rawElement( 'p', array(), $refreshHtml ) );
+ $this->getOutput()->addHtml( Html::rawElement( 'p', array(), $refreshHtml ) );
} else {
- $wgOut->addHtml( Html::rawElement( 'p', array(),
+ $this->getOutput()->addHtml( Html::rawElement( 'p', array(),
Html::element( 'span', array(), wfMsg( 'uploadstash-nofiles' ) )
. ' '
. $refreshHtml
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index 0e5baa2d..13ea5def 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -49,6 +49,7 @@ class LoginForm extends SpecialPage {
var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS;
var $mType, $mReason, $mRealName;
var $mAbortLoginErrorMsg = 'login-abort-generic';
+ private $mLoaded = false;
/**
* @var ExternalUser
@@ -56,27 +57,36 @@ class LoginForm extends SpecialPage {
private $mExtUser = null;
/**
+ * @ var WebRequest
+ */
+ private $mOverrideRequest = null;
+
+ /**
* @param WebRequest $request
*/
public function __construct( $request = null ) {
parent::__construct( 'Userlogin' );
- if ( $request === null ) {
- global $wgRequest;
- $this->load( $wgRequest );
- } else {
- $this->load( $request );
- }
+ $this->mOverrideRequest = $request;
}
/**
* Loader
- *
- * @param $request WebRequest object
*/
- function load( $request ) {
+ function load() {
global $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
+ if ( $this->mLoaded ) {
+ return;
+ }
+ $this->mLoaded = true;
+
+ if ( $this->mOverrideRequest === null ) {
+ $request = $this->getRequest();
+ } else {
+ $request = $this->mOverrideRequest;
+ }
+
$this->mType = $request->getText( 'type' );
$this->mUsername = $request->getText( 'wpName' );
$this->mPassword = $request->getText( 'wpPassword' );
@@ -115,7 +125,11 @@ class LoginForm extends SpecialPage {
}
if( !$wgAuth->validDomain( $this->mDomain ) ) {
- $this->mDomain = 'invaliddomain';
+ if ( isset( $_SESSION['wsDomain'] ) ) {
+ $this->mDomain = $_SESSION['wsDomain'];
+ } else {
+ $this->mDomain = 'invaliddomain';
+ }
}
$wgAuth->setDomain( $this->mDomain );
@@ -127,11 +141,19 @@ class LoginForm extends SpecialPage {
}
}
+ function getDescription() {
+ return $this->msg( $this->getUser()->isAllowed( 'createaccount' ) ?
+ 'userlogin' : 'userloginnocreate' )->text();
+ }
+
public function execute( $par ) {
if ( session_id() == '' ) {
wfSetupSession();
}
+ $this->load();
+ $this->setHeaders();
+
if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]]
$this->mType = 'signup';
}
@@ -155,10 +177,8 @@ class LoginForm extends SpecialPage {
* @private
*/
function addNewAccountMailPassword() {
- global $wgOut;
-
if ( $this->mEmail == '' ) {
- $this->mainLoginForm( wfMsgExt( 'noemailcreate', array( 'parsemag', 'escape' ) ) );
+ $this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() );
return;
}
@@ -176,13 +196,14 @@ class LoginForm extends SpecialPage {
wfRunHooks( 'AddNewAccount', array( $u, true ) );
$u->addNewUserLogEntry( true, $this->mReason );
- $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) );
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'accmailtitle' ) );
if( !$result->isGood() ) {
- $this->mainLoginForm( wfMsg( 'mailerror', $result->getWikiText() ) );
+ $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() );
} else {
- $wgOut->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() );
- $wgOut->returnToMain( false );
+ $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() );
+ $out->returnToMain( false );
}
}
@@ -190,7 +211,7 @@ class LoginForm extends SpecialPage {
* @private
*/
function addNewAccount() {
- global $wgUser, $wgEmailAuthentication, $wgOut;
+ global $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector;
# Create the account and abort if there's a problem doing so
$u = $this->addNewAccountInternal();
@@ -200,18 +221,19 @@ class LoginForm extends SpecialPage {
# If we showed up language selection links, and one was in use, be
# smart (and sensible) and save that language as the user's preference
- global $wgLoginLanguageSelector;
if( $wgLoginLanguageSelector && $this->mLanguage ) {
$u->setOption( 'language', $this->mLanguage );
}
+ $out = $this->getOutput();
+
# Send out an email authentication message if needed
if( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) {
$status = $u->sendConfirmationMail();
if( $status->isGood() ) {
- $wgOut->addWikiMsg( 'confirmemail_oncreate' );
+ $out->addWikiMsg( 'confirmemail_oncreate' );
} else {
- $wgOut->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
+ $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
}
}
@@ -221,15 +243,15 @@ class LoginForm extends SpecialPage {
# If not logged in, assume the new account as the current one and set
# session cookies then show a "welcome" message or a "need cookies"
# message as needed
- if( $wgUser->isAnon() ) {
+ if( $this->getUser()->isAnon() ) {
+ $u->setCookies();
$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();
+ $this->getContext()->setUser( $u );
+ wfRunHooks( 'AddNewAccount', array( $u, false ) );
+ $u->addNewUserLogEntry();
if( $this->hasSessionCookie() ) {
return $this->successfulCreation();
} else {
@@ -237,10 +259,9 @@ class LoginForm extends SpecialPage {
}
} else {
# Confirm that the account was created
- $self = SpecialPage::getTitleFor( 'Userlogin' );
- $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) );
- $wgOut->addWikiMsg( 'accountcreatedtext', $u->getName() );
- $wgOut->returnToMain( false, $self );
+ $out->setPageTitle( $this->msg( 'accountcreated' ) );
+ $out->addWikiMsg( 'accountcreatedtext', $u->getName() );
+ $out->returnToMain( false, $this->getTitle() );
wfRunHooks( 'AddNewAccount', array( $u, false ) );
$u->addNewUserLogEntry( false, $this->mReason );
return true;
@@ -251,14 +272,12 @@ class LoginForm extends SpecialPage {
* @private
*/
function addNewAccountInternal() {
- global $wgUser, $wgOut;
- global $wgMemc, $wgAccountCreationThrottle;
- global $wgAuth, $wgMinimalPasswordLength;
- global $wgEmailConfirmToEdit;
+ global $wgAuth, $wgMemc, $wgAccountCreationThrottle,
+ $wgMinimalPasswordLength, $wgEmailConfirmToEdit;
// If the user passes an invalid domain, something is fishy
if( !$wgAuth->validDomain( $this->mDomain ) ) {
- $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
+ $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
return false;
}
@@ -270,47 +289,46 @@ class LoginForm extends SpecialPage {
if( 'local' != $this->mDomain && $this->mDomain != '' ) {
if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mUsername )
|| !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) ) ) {
- $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
+ $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
return false;
}
}
if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return false;
+ throw new ReadOnlyError;
}
# Request forgery checks.
if ( !self::getCreateaccountToken() ) {
self::setCreateaccountToken();
- $this->mainLoginForm( wfMsgExt( 'nocookiesfornew', array( 'parseinline' ) ) );
+ $this->mainLoginForm( $this->msg( 'nocookiesfornew' )->parse() );
return false;
}
# The user didn't pass a createaccount token
if ( !$this->mToken ) {
- $this->mainLoginForm( wfMsg( 'sessionfailure' ) );
+ $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() );
return false;
}
# Validate the createaccount token
if ( $this->mToken !== self::getCreateaccountToken() ) {
- $this->mainLoginForm( wfMsg( 'sessionfailure' ) );
+ $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() );
return false;
}
# Check permissions
- if ( !$wgUser->isAllowed( 'createaccount' ) ) {
- $wgOut->permissionRequired( 'createaccount' );
- return false;
- } elseif ( $wgUser->isBlockedFromCreateAccount() ) {
- $this->userBlockedMessage( $wgUser->isBlockedFromCreateAccount() );
+ $currentUser = $this->getUser();
+ if ( !$currentUser->isAllowed( 'createaccount' ) ) {
+ throw new PermissionsError( 'createaccount' );
+ } elseif ( $currentUser->isBlockedFromCreateAccount() ) {
+ $this->userBlockedMessage( $currentUser->isBlockedFromCreateAccount() );
return false;
}
- $ip = wfGetIP();
- if ( $wgUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
- $this->mainLoginForm( wfMsg( 'sorbs_create_account_reason' ) . ' (' . htmlspecialchars( $ip ) . ')' );
+ $ip = $this->getRequest()->getIP();
+ if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
+ $this->mainLoginForm( $this->msg( 'sorbs_create_account_reason' )->text() . ' (' . htmlspecialchars( $ip ) . ')' );
return false;
}
@@ -318,17 +336,17 @@ class LoginForm extends SpecialPage {
$name = trim( $this->mUsername );
$u = User::newFromName( $name, 'creatable' );
if ( !is_object( $u ) ) {
- $this->mainLoginForm( wfMsg( 'noname' ) );
+ $this->mainLoginForm( $this->msg( 'noname' )->text() );
return false;
}
if ( 0 != $u->idForName() ) {
- $this->mainLoginForm( wfMsg( 'userexists' ) );
+ $this->mainLoginForm( $this->msg( 'userexists' )->text() );
return false;
}
if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) {
- $this->mainLoginForm( wfMsg( 'badretype' ) );
+ $this->mainLoginForm( $this->msg( 'badretype' )->text() );
return false;
}
@@ -343,7 +361,7 @@ class LoginForm extends SpecialPage {
$message = $valid;
$params = array( $wgMinimalPasswordLength );
}
- $this->mainLoginForm( wfMsgExt( $message, array( 'parsemag' ), $params ) );
+ $this->mainLoginForm( $this->msg( $message, $params )->text() );
return false;
} else {
# do not force a password for account creation by email
@@ -355,12 +373,12 @@ class LoginForm extends SpecialPage {
# if you need a confirmed email address to edit, then obviously you
# need an email address.
if ( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) {
- $this->mainLoginForm( wfMsg( 'noemailtitle' ) );
+ $this->mainLoginForm( $this->msg( 'noemailtitle' )->text() );
return false;
}
if( !empty( $this->mEmail ) && !Sanitizer::validateEmail( $this->mEmail ) ) {
- $this->mainLoginForm( wfMsg( 'invalidemailaddress' ) );
+ $this->mainLoginForm( $this->msg( 'invalidemailaddress' )->text() );
return false;
}
@@ -377,21 +395,26 @@ class LoginForm extends SpecialPage {
return false;
}
- if ( $wgAccountCreationThrottle && $wgUser->isPingLimitable() ) {
- $key = wfMemcKey( 'acctcreate', 'ip', $ip );
- $value = $wgMemc->get( $key );
- if ( !$value ) {
- $wgMemc->set( $key, 0, 86400 );
- }
- if ( $value >= $wgAccountCreationThrottle ) {
- $this->throttleHit( $wgAccountCreationThrottle );
- return false;
+ // Hook point to check for exempt from account creation throttle
+ if ( !wfRunHooks( 'ExemptFromAccountCreationThrottle', array( $ip ) ) ) {
+ wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook allowed account creation w/o throttle\n" );
+ } else {
+ if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) {
+ $key = wfMemcKey( 'acctcreate', 'ip', $ip );
+ $value = $wgMemc->get( $key );
+ if ( !$value ) {
+ $wgMemc->set( $key, 0, 86400 );
+ }
+ if ( $value >= $wgAccountCreationThrottle ) {
+ $this->throttleHit( $wgAccountCreationThrottle );
+ return false;
+ }
+ $wgMemc->incr( $key );
}
- $wgMemc->incr( $key );
}
if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) {
- $this->mainLoginForm( wfMsg( 'externaldberror' ) );
+ $this->mainLoginForm( $this->msg( 'externaldberror' )->text() );
return false;
}
@@ -451,6 +474,8 @@ class LoginForm extends SpecialPage {
public function authenticateUserData() {
global $wgUser, $wgAuth;
+ $this->load();
+
if ( $this->mUsername == '' ) {
return self::NO_NAME;
}
@@ -480,13 +505,13 @@ class LoginForm extends SpecialPage {
return self::WRONG_TOKEN;
}
- // Load $wgUser now, and check to see if we're logging in as the same
- // name. This is necessary because loading $wgUser (say by calling
- // getName()) calls the UserLoadFromSession hook, which potentially
- // creates the user in the database. Until we load $wgUser, checking
- // for user existence using User::newFromName($name)->getId() below
+ // Load the current user now, and check to see if we're logging in as
+ // the same name. This is necessary because loading the current user
+ // (say by calling getName()) calls the UserLoadFromSession hook, which
+ // potentially 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->mUsername ) {
+ if ( $this->getUser()->getName() === $this->mUsername ) {
wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" );
return self::SUCCESS;
}
@@ -567,7 +592,7 @@ class LoginForm extends SpecialPage {
// This should set it for OutputPage and the Skin
// which is needed or the personal links will be
// wrong.
- RequestContext::getMain()->setUser( $u );
+ $this->getContext()->setUser( $u );
// Please reset throttle for successful logins, thanks!
if ( $throttleCount ) {
@@ -576,7 +601,7 @@ class LoginForm extends SpecialPage {
if ( $isAutoCreated ) {
// Must be run after $wgUser is set, for correct new user log
- wfRunHooks( 'AuthPluginAutoCreate', array( $wgUser ) );
+ wfRunHooks( 'AuthPluginAutoCreate', array( $u ) );
}
$retval = self::SUCCESS;
@@ -585,18 +610,19 @@ class LoginForm extends SpecialPage {
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;
+ global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest;
+ $username = trim( $username ); // sanity
$throttleCount = 0;
if ( is_array( $wgPasswordAttemptThrottle ) ) {
- $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $username ) );
+ $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
$count = $wgPasswordAttemptThrottle['count'];
$period = $wgPasswordAttemptThrottle['seconds'];
@@ -613,15 +639,16 @@ class LoginForm extends SpecialPage {
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;
+ global $wgMemc, $wgRequest;
+ $username = trim( $username ); // sanity
- $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $username ) );
+ $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
$wgMemc->delete( $throttleKey );
}
@@ -634,9 +661,9 @@ class LoginForm extends SpecialPage {
* @return integer Status code
*/
function attemptAutoCreate( $user ) {
- global $wgAuth, $wgUser, $wgAutocreatePolicy;
+ global $wgAuth, $wgAutocreatePolicy;
- if ( $wgUser->isBlockedFromCreateAccount() ) {
+ if ( $this->getUser()->isBlockedFromCreateAccount() ) {
wfDebug( __METHOD__ . ": user is blocked from account creation\n" );
return self::CREATE_BLOCKED;
}
@@ -684,32 +711,34 @@ class LoginForm extends SpecialPage {
}
function processLogin() {
- global $wgUser;
+ global $wgMemc, $wgLang;
switch ( $this->authenticateUserData() ) {
case self::SUCCESS:
# We've verified now, update the real record
- if( (bool)$this->mRemember != (bool)$wgUser->getOption( 'rememberpassword' ) ) {
- $wgUser->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
- $wgUser->saveSettings();
+ $user = $this->getUser();
+ if( (bool)$this->mRemember != (bool)$user->getOption( 'rememberpassword' ) ) {
+ $user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
+ $user->saveSettings();
} else {
- $wgUser->invalidateCache();
+ $user->invalidateCache();
}
- $wgUser->setCookies();
+ $user->setCookies();
self::clearLoginToken();
// Reset the throttle
- $key = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mUsername ) );
- global $wgMemc;
+ $request = $this->getRequest();
+ $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) );
$wgMemc->delete( $key );
if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
/* Replace the language object to provide user interface in
* correct language immediately on this first page load.
*/
- global $wgLang, $wgRequest;
- $code = $wgRequest->getVal( 'uselang', $wgUser->getOption( 'language' ) );
- $wgLang = Language::factory( $code );
+ $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
+ $userLang = Language::factory( $code );
+ $wgLang = $userLang;
+ $this->getContext()->setLanguage( $userLang );
return $this->successfulLogin();
} else {
return $this->cookieRedirectCheck( 'login' );
@@ -717,48 +746,48 @@ class LoginForm extends SpecialPage {
break;
case self::NEED_TOKEN:
- $this->mainLoginForm( wfMsgExt( 'nocookiesforlogin', array( 'parseinline' ) ) );
+ $this->mainLoginForm( $this->msg( 'nocookiesforlogin' )->parse() );
break;
case self::WRONG_TOKEN:
- $this->mainLoginForm( wfMsg( 'sessionfailure' ) );
+ $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() );
break;
case self::NO_NAME:
case self::ILLEGAL:
- $this->mainLoginForm( wfMsg( 'noname' ) );
+ $this->mainLoginForm( $this->msg( 'noname' )->text() );
break;
case self::WRONG_PLUGIN_PASS:
- $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
+ $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
break;
case self::NOT_EXISTS:
- if( $wgUser->isAllowed( 'createaccount' ) ) {
- $this->mainLoginForm( wfMsgExt( 'nosuchuser', 'parseinline',
- wfEscapeWikiText( $this->mUsername ) ) );
+ if( $this->getUser()->isAllowed( 'createaccount' ) ) {
+ $this->mainLoginForm( $this->msg( 'nosuchuser',
+ wfEscapeWikiText( $this->mUsername ) )->parse() );
} else {
- $this->mainLoginForm( wfMsg( 'nosuchusershort',
- wfEscapeWikiText( $this->mUsername ) ) );
+ $this->mainLoginForm( $this->msg( 'nosuchusershort',
+ wfEscapeWikiText( $this->mUsername ) )->text() );
}
break;
case self::WRONG_PASS:
- $this->mainLoginForm( wfMsg( 'wrongpassword' ) );
+ $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
break;
case self::EMPTY_PASS:
- $this->mainLoginForm( wfMsg( 'wrongpasswordempty' ) );
+ $this->mainLoginForm( $this->msg( 'wrongpasswordempty' )->text() );
break;
case self::RESET_PASS:
- $this->resetLoginForm( wfMsg( 'resetpass_announce' ) );
+ $this->resetLoginForm( $this->msg( 'resetpass_announce' )->text() );
break;
case self::CREATE_BLOCKED:
- $this->userBlockedMessage( $wgUser->mBlock );
+ $this->userBlockedMessage( $this->getUser()->mBlock );
break;
case self::THROTTLED:
- $this->mainLoginForm( wfMsg( 'login-throttled' ) );
+ $this->mainLoginForm( $this->msg( 'login-throttled' )->text() );
break;
case self::USER_BLOCKED:
- $this->mainLoginForm( wfMsgExt( 'login-userblocked',
- array( 'parsemag', 'escape' ), $this->mUsername ) );
+ $this->mainLoginForm( $this->msg( 'login-userblocked',
+ $this->mUsername )->escaped() );
break;
case self::ABORTED:
- $this->mainLoginForm( wfMsg( $this->mAbortLoginErrorMsg ) );
+ $this->mainLoginForm( $this->msg( $this->mAbortLoginErrorMsg )->text() );
break;
default:
throw new MWException( 'Unhandled case value' );
@@ -766,9 +795,9 @@ class LoginForm extends SpecialPage {
}
function resetLoginForm( $error ) {
- global $wgOut;
- $wgOut->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) );
+ $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) );
$reset = new SpecialChangePassword();
+ $reset->setContext( $this->getContext() );
$reset->execute( null );
}
@@ -778,28 +807,28 @@ class LoginForm extends SpecialPage {
* @param $emailTitle String: message name of email title
* @param $emailText String: message name of email text
* @return Status object
- * @private
*/
function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) {
- global $wgServer, $wgScript, $wgUser, $wgNewPasswordExpiry;
+ global $wgServer, $wgScript, $wgNewPasswordExpiry;
if ( $u->getEmail() == '' ) {
return Status::newFatal( 'noemail', $u->getName() );
}
- $ip = wfGetIP();
+ $ip = $this->getRequest()->getIP();
if( !$ip ) {
return Status::newFatal( 'badipaddress' );
}
- wfRunHooks( 'User::mailPasswordInternal', array( &$wgUser, &$ip, &$u ) );
+ $currentUser = $this->getUser();
+ wfRunHooks( 'User::mailPasswordInternal', array( &$currentUser, &$ip, &$u ) );
$np = $u->randomPassword();
$u->setNewpassword( $np, $throttle );
$u->saveSettings();
$userLanguage = $u->getOption( 'language' );
- $m = wfMsgExt( $emailText, array( 'parsemag', 'language' => $userLanguage ), $ip, $u->getName(), $np,
- $wgServer . $wgScript, round( $wgNewPasswordExpiry / 86400 ) );
- $result = $u->sendMail( wfMsgExt( $emailTitle, array( 'parsemag', 'language' => $userLanguage ) ), $m );
+ $m = $this->msg( $emailText, $ip, $u->getName(), $np, $wgServer . $wgScript,
+ round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text();
+ $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m );
return $result;
}
@@ -816,11 +845,10 @@ class LoginForm extends SpecialPage {
* @private
*/
function successfulLogin() {
- global $wgUser, $wgOut;
-
# Run any hooks; display injected HTML if any, else redirect
+ $currentUser = $this->getUser();
$injected_html = '';
- wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$injected_html ) );
+ wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
if( $injected_html !== '' ) {
$this->displaySuccessfulLogin( 'loginsuccess', $injected_html );
@@ -834,7 +862,7 @@ class LoginForm extends SpecialPage {
if( $wgSecureLogin && !$this->mStickHTTPS ) {
$redirectUrl = preg_replace( '/^https:/', 'http:', $redirectUrl );
}
- $wgOut->redirect( $redirectUrl );
+ $this->getOutput()->redirect( $redirectUrl );
}
}
@@ -845,14 +873,18 @@ class LoginForm extends SpecialPage {
* @private
*/
function successfulCreation() {
- global $wgUser;
# Run any hooks; display injected HTML
+ $currentUser = $this->getUser();
$injected_html = '';
$welcome_creation_msg = 'welcomecreation';
- wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$injected_html ) );
+ wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
- //let any extensions change what message is shown
+ /**
+ * Let any extensions change what message is shown.
+ * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforeWelcomeCreation
+ * @since 1.18
+ */
wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) );
$this->displaySuccessfulLogin( $welcome_creation_msg, $injected_html );
@@ -862,19 +894,18 @@ class LoginForm extends SpecialPage {
* Display a "login successful" page.
*/
private function displaySuccessfulLogin( $msgname, $injected_html ) {
- global $wgOut, $wgUser;
-
- $wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) );
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'loginsuccesstitle' ) );
if( $msgname ){
- $wgOut->addWikiMsg( $msgname, wfEscapeWikiText( $wgUser->getName() ) );
+ $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
}
- $wgOut->addHTML( $injected_html );
+ $out->addHTML( $injected_html );
if ( !empty( $this->mReturnTo ) ) {
- $wgOut->returnToMain( null, $this->mReturnTo, $this->mReturnToQuery );
+ $out->returnToMain( null, $this->mReturnTo, $this->mReturnToQuery );
} else {
- $wgOut->returnToMain( null );
+ $out->returnToMain( null );
}
}
@@ -886,8 +917,6 @@ class LoginForm extends SpecialPage {
* @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
# schools. Don't blame it on the user. There's a small chance that it
@@ -896,56 +925,56 @@ class LoginForm extends SpecialPage {
# evade it, but we'll leave that to their guilty conscience to figure
# out.
- $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) );
+ $out = $this->getOutput();
+ $out->setPageTitle( $this->msg( 'cantcreateaccounttitle' ) );
$block_reason = $block->mReason;
if ( strval( $block_reason ) === '' ) {
- $block_reason = wfMsg( 'blockednoreason' );
+ $block_reason = $this->msg( 'blockednoreason' )->text();
}
- $wgOut->addWikiMsg(
+ $out->addWikiMsg(
'cantcreateaccount-text',
$block->getTarget(),
$block_reason,
- $block->getBlocker()->getName()
+ $block->getByName()
);
- $wgOut->returnToMain( false );
+ $out->returnToMain( false );
}
/**
* @private
*/
function mainLoginForm( $msg, $msgtype = 'error' ) {
- global $wgUser, $wgOut, $wgHiddenPrefs;
global $wgEnableEmail, $wgEnableUserEmail;
- global $wgRequest, $wgLoginLanguageSelector;
+ global $wgHiddenPrefs, $wgLoginLanguageSelector;
global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
global $wgSecureLogin, $wgPasswordResetRoutes;
- $titleObj = SpecialPage::getTitleFor( 'Userlogin' );
+ $titleObj = $this->getTitle();
+ $user = $this->getUser();
if ( $this->mType == 'signup' ) {
// Block signup here if in readonly. Keeps user from
// going through the process (filling out data, etc)
// and being informed later.
- if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- } elseif ( $wgUser->isBlockedFromCreateAccount() ) {
- $this->userBlockedMessage( $wgUser->isBlockedFromCreateAccount() );
- return;
- } elseif ( count( $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) {
- $wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' );
+ $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $user, true );
+ if ( count( $permErrors ) ) {
+ throw new PermissionsError( 'createaccount', $permErrors );
+ } elseif ( $user->isBlockedFromCreateAccount() ) {
+ $this->userBlockedMessage( $user->isBlockedFromCreateAccount() );
return;
+ } elseif ( wfReadOnly() ) {
+ throw new ReadOnlyError;
}
}
if ( $this->mUsername == '' ) {
- if ( $wgUser->isLoggedIn() ) {
- $this->mUsername = $wgUser->getName();
+ if ( $user->isLoggedIn() ) {
+ $this->mUsername = $user->getName();
} else {
- $this->mUsername = $wgRequest->getCookie( 'UserName' );
+ $this->mUsername = $this->getRequest()->getCookie( 'UserName' );
}
}
@@ -971,18 +1000,16 @@ class LoginForm extends SpecialPage {
$linkq .= $returnto;
}
- # Pass any language selection on to the mode switch link
- if( $wgLoginLanguageSelector && $this->mLanguage ) {
- $linkq .= '&uselang=' . $this->mLanguage;
- }
-
- $link = '<a href="' . htmlspecialchars ( $titleObj->getLocalURL( $linkq ) ) . '">';
- $link .= wfMsgHtml( $linkmsg . 'link' ); # Calling either 'gotaccountlink' or 'nologinlink'
- $link .= '</a>';
-
# Don't show a "create account" link if the user can't
- if( $this->showCreateOrLoginLink( $wgUser ) ) {
- $template->set( 'link', wfMsgExt( $linkmsg, array( 'parseinline', 'replaceafter' ), $link ) );
+ if( $this->showCreateOrLoginLink( $user ) ) {
+ # Pass any language selection on to the mode switch link
+ if( $wgLoginLanguageSelector && $this->mLanguage ) {
+ $linkq .= '&uselang=' . $this->mLanguage;
+ }
+ $link = Html::element( 'a', array( 'href' => $titleObj->getLocalURL( $linkq ) ),
+ $this->msg( $linkmsg . 'link' )->text() ); # Calling either 'gotaccountlink' or 'nologinlink'
+
+ $template->set( 'link', $this->msg( $linkmsg )->rawParams( $link )->parse() );
} else {
$template->set( 'link', '' );
}
@@ -1003,7 +1030,7 @@ class LoginForm extends SpecialPage {
$template->set( 'action', $titleObj->getLocalURL( $q ) );
$template->set( 'message', $msg );
$template->set( 'messagetype', $msgtype );
- $template->set( 'createemail', $wgEnableEmail && $wgUser->isLoggedIn() );
+ $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
$template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) );
$template->set( 'useemail', $wgEnableEmail );
$template->set( 'emailrequired', $wgEmailConfirmToEdit );
@@ -1011,8 +1038,8 @@ class LoginForm extends SpecialPage {
$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 );
+ $template->set( 'usereason', $user->isLoggedIn() );
+ $template->set( 'remember', $user->getOption( 'rememberpassword' ) || $this->mRemember );
$template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
$template->set( 'stickHTTPS', $this->mStickHTTPS );
@@ -1031,24 +1058,25 @@ class LoginForm extends SpecialPage {
# Prepare language selection links as needed
if( $wgLoginLanguageSelector ) {
$template->set( 'languages', $this->makeLanguageSelector() );
- if( $this->mLanguage )
+ 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' );
+ $loginendHTTPS = $this->msg( 'loginend-https' );
+ $signupendHTTPS = $this->msg( 'signupend-https' );
if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) {
$template->set( 'loginend', $loginendHTTPS->parse() );
} else {
- $template->set( 'loginend', wfMessage( 'loginend' )->parse() );
+ $template->set( 'loginend', $this->msg( 'loginend' )->parse() );
}
if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) {
$template->set( 'signupend', $signupendHTTPS->parse() );
} else {
- $template->set( 'signupend', wfMessage( 'signupend' )->parse() );
+ $template->set( 'signupend', $this->msg( 'signupend' )->parse() );
}
// Give authentication and captcha plugins a chance to modify the form
@@ -1059,15 +1087,9 @@ class LoginForm extends SpecialPage {
wfRunHooks( 'UserLoginForm', array( &$template ) );
}
- // Changes the title depending on permissions for creating account
- if ( $wgUser->isAllowed( 'createaccount' ) ) {
- $wgOut->setPageTitle( wfMsg( 'userlogin' ) );
- } else {
- $wgOut->setPageTitle( wfMsg( 'userloginnocreate' ) );
- }
-
- $wgOut->disallowUserJs(); // just in case...
- $wgOut->addTemplate( $template );
+ $out = $this->getOutput();
+ $out->disallowUserJs(); // just in case...
+ $out->addTemplate( $template );
}
/**
@@ -1097,8 +1119,8 @@ class LoginForm extends SpecialPage {
* @private
*/
function hasSessionCookie() {
- global $wgDisableCookieCheck, $wgRequest;
- return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie();
+ global $wgDisableCookieCheck;
+ return $wgDisableCookieCheck ? true : $this->getRequest()->checkSessionCookie();
}
/**
@@ -1155,8 +1177,6 @@ class LoginForm extends SpecialPage {
* @private
*/
function cookieRedirectCheck( $type ) {
- global $wgOut;
-
$titleObj = SpecialPage::getTitleFor( 'Userlogin' );
$query = array( 'wpCookieCheck' => $type );
if ( $this->mReturnTo ) {
@@ -1164,7 +1184,7 @@ class LoginForm extends SpecialPage {
}
$check = $titleObj->getFullURL( $query );
- return $wgOut->redirect( $check );
+ return $this->getOutput()->redirect( $check );
}
/**
@@ -1173,12 +1193,12 @@ class LoginForm extends SpecialPage {
function onCookieRedirectCheck( $type ) {
if ( !$this->hasSessionCookie() ) {
if ( $type == 'new' ) {
- return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) );
+ return $this->mainLoginForm( $this->msg( 'nocookiesnew' )->parse() );
} elseif ( $type == 'login' ) {
- return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
+ return $this->mainLoginForm( $this->msg( 'nocookieslogin' )->parse() );
} else {
# shouldn't happen
- return $this->mainLoginForm( wfMsg( 'error' ) );
+ return $this->mainLoginForm( $this->msg( 'error' )->text() );
}
} else {
return $this->successfulLogin();
@@ -1189,7 +1209,7 @@ class LoginForm extends SpecialPage {
* @private
*/
function throttleHit( $limit ) {
- $this->mainLoginForm( wfMsgExt( 'acct_creation_throttle_hit', array( 'parseinline' ), $limit ) );
+ $this->mainLoginForm( $this->msg( 'acct_creation_throttle_hit' )->numParams( $limit )->parse() );
}
/**
@@ -1199,9 +1219,7 @@ class LoginForm extends SpecialPage {
* @return string
*/
function makeLanguageSelector() {
- global $wgLang;
-
- $msg = wfMessage( 'loginlanguagelinks' )->inContentLanguage();
+ $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
if( !$msg->isBlank() ) {
$langs = explode( "\n", $msg->text() );
$links = array();
@@ -1212,7 +1230,8 @@ class LoginForm extends SpecialPage {
$links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
}
}
- return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : '';
+ return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
+ $this->getLanguage()->pipeList( $links ) )->escaped() : '';
} else {
return '';
}
@@ -1226,7 +1245,6 @@ class LoginForm extends SpecialPage {
* @param $lang Language code
*/
function makeLanguageSelectorLink( $text, $lang ) {
- $self = SpecialPage::getTitleFor( 'Userlogin' );
$attr = array( 'uselang' => $lang );
if( $this->mType == 'signup' ) {
$attr['type'] = 'signup';
@@ -1235,7 +1253,7 @@ class LoginForm extends SpecialPage {
$attr['returnto'] = $this->mReturnTo;
}
return Linker::linkKnown(
- $self,
+ $this->getTitle(),
htmlspecialchars( $text ),
array(),
$attr
diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php
index 39b5b284..d747448f 100644
--- a/includes/specials/SpecialUserlogout.php
+++ b/includes/specials/SpecialUserlogout.php
@@ -1,6 +1,6 @@
<?php
/**
- * Implements Special:Upload
+ * Implements Special:Userlogout
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,31 +33,30 @@ class SpecialUserlogout extends UnlistedSpecialPage {
}
function execute( $par ) {
- global $wgUser, $wgOut;
-
/**
* Some satellite ISPs use broken precaching schemes that log people out straight after
* they're logged in (bug 17790). Luckily, there's a way to detect such requests.
*/
if ( isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], '&amp;' ) !== false ) {
wfDebug( "Special:Userlogout request {$_SERVER['REQUEST_URI']} looks suspicious, denying.\n" );
- wfHttpError( 400, wfMsg( 'loginerror' ), wfMsg( 'suspicious-userlogout' ) );
- return;
+ throw new HttpError( 400, wfMessage( 'suspicious-userlogout' ), wfMessage( 'loginerror' ) );
}
$this->setHeaders();
$this->outputHeader();
- $oldName = $wgUser->getName();
- $wgUser->logout();
+ $user = $this->getUser();
+ $oldName = $user->getName();
+ $user->logout();
- $wgOut->addWikiMsg( 'logouttext' );
+ $out = $this->getOutput();
+ $out->addWikiMsg( 'logouttext' );
// Hook.
$injected_html = '';
- wfRunHooks( 'UserLogoutComplete', array( &$wgUser, &$injected_html, $oldName ) );
- $wgOut->addHTML( $injected_html );
+ wfRunHooks( 'UserLogoutComplete', array( &$user, &$injected_html, $oldName ) );
+ $out->addHTML( $injected_html );
- $wgOut->returnToMain();
+ $out->returnToMain();
}
}
diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php
index 4de048c0..e2e0f38b 100644
--- a/includes/specials/SpecialUserrights.php
+++ b/includes/specials/SpecialUserrights.php
@@ -63,22 +63,24 @@ class UserrightsPage extends SpecialPage {
public function execute( $par ) {
// If the visitor doesn't have permissions to assign or remove
// any groups, it's a bit silly to give them the user search prompt.
- global $wgUser, $wgRequest, $wgOut;
- if( $par !== null ) {
- $this->mTarget = $par;
- } else {
- $this->mTarget = $wgRequest->getVal( 'user' );
- }
+ $user = $this->getUser();
/*
* If the user is blocked and they only have "partial" access
* (e.g. they don't have the userrights permission), then don't
* allow them to use Special:UserRights.
*/
- if( $wgUser->isBlocked() && !$wgUser->isAllowed( 'userrights' ) ) {
- $wgOut->blockedPage();
- return;
+ if( $user->isBlocked() && !$user->isAllowed( 'userrights' ) ) {
+ throw new UserBlockedError( $user->mBlock );
+ }
+
+ $request = $this->getRequest();
+
+ if( $par !== null ) {
+ $this->mTarget = $par;
+ } else {
+ $this->mTarget = $request->getVal( 'user' );
}
$available = $this->changeableGroups();
@@ -90,49 +92,44 @@ class UserrightsPage extends SpecialPage {
* target.
*/
if ( !count( $available['add'] ) && !count( $available['remove'] ) )
- $this->mTarget = $wgUser->getName();
+ $this->mTarget = $user->getName();
}
- if ( User::getCanonicalName( $this->mTarget ) == $wgUser->getName() ) {
+ if ( User::getCanonicalName( $this->mTarget ) == $user->getName() ) {
$this->isself = true;
}
- if( !$this->userCanChangeRights( $wgUser, true ) ) {
+ if( !$this->userCanChangeRights( $user, true ) ) {
// @todo FIXME: There may be intermediate groups we can mention.
- $wgOut->showPermissionsErrorPage( array( array(
- $wgUser->isAnon()
- ? 'userrights-nologin'
- : 'userrights-notallowed' ) ) );
- return;
+ $msg = $user->isAnon() ? 'userrights-nologin' : 'userrights-notallowed';
+ throw new PermissionsError( null, array( array( $msg ) ) );
}
- if ( wfReadOnly() ) {
- $wgOut->readOnlyPage();
- return;
- }
+ $this->checkReadOnly();
- $this->outputHeader();
- $wgOut->addModuleStyles( 'mediawiki.special' );
$this->setHeaders();
+ $this->outputHeader();
+
+ $out = $this->getOutput();
+ $out->addModuleStyles( 'mediawiki.special' );
// show the general form
if ( count( $available['add'] ) || count( $available['remove'] ) ) {
$this->switchForm();
}
- if( $wgRequest->wasPosted() ) {
+ if( $request->wasPosted() ) {
// save settings
- if( $wgRequest->getCheck( 'saveusergroups' ) ) {
- $reason = $wgRequest->getVal( 'user-reason' );
- $tok = $wgRequest->getVal( 'wpEditToken' );
- if( $wgUser->matchEditToken( $tok, $this->mTarget ) ) {
+ if( $request->getCheck( 'saveusergroups' ) ) {
+ $reason = $request->getVal( 'user-reason' );
+ $tok = $request->getVal( 'wpEditToken' );
+ if( $user->matchEditToken( $tok, $this->mTarget ) ) {
$this->saveUserGroups(
$this->mTarget,
$reason
);
- $url = $this->getSuccessURL();
- $wgOut->redirect( $url );
+ $out->redirect( $this->getSuccessURL() );
return;
}
}
@@ -157,11 +154,9 @@ class UserrightsPage extends SpecialPage {
* @return null
*/
function saveUserGroups( $username, $reason = '' ) {
- global $wgRequest, $wgOut;
-
$status = $this->fetchUser( $username );
if( !$status->isOK() ) {
- $wgOut->addWikiText( $status->getWikiText() );
+ $this->getOutput()->addWikiText( $status->getWikiText() );
return;
} else {
$user = $status->value;
@@ -176,7 +171,7 @@ class UserrightsPage extends SpecialPage {
foreach ( $allgroups as $group ) {
// We'll tell it to remove all unchecked groups, and add all checked groups.
// Later on, this gets filtered for what can actually be removed
- if ( $wgRequest->getCheck( "wpGroup-$group" ) ) {
+ if ( $this->getRequest()->getCheck( "wpGroup-$group" ) ) {
$addgroup[] = $group;
} else {
$removegroup[] = $group;
@@ -196,10 +191,8 @@ class UserrightsPage extends SpecialPage {
* @return Array: Tuple of added, then removed groups
*/
function doSaveUserGroups( $user, $add, $remove, $reason = '' ) {
- global $wgUser;
-
// Validate input set...
- $isself = ( $user->getName() == $wgUser->getName() );
+ $isself = ( $user->getName() == $this->getUser()->getName() );
$groups = $user->getGroups();
$changeable = $this->changeableGroups();
$addable = array_merge( $changeable['add'], $isself ? $changeable['add-self'] : array() );
@@ -265,11 +258,9 @@ class UserrightsPage extends SpecialPage {
* @param $username String: name of the user.
*/
function editUserGroupsForm( $username ) {
- global $wgOut;
-
$status = $this->fetchUser( $username );
if( !$status->isOK() ) {
- $wgOut->addWikiText( $status->getWikiText() );
+ $this->getOutput()->addWikiText( $status->getWikiText() );
return;
} else {
$user = $status->value;
@@ -281,7 +272,7 @@ class UserrightsPage extends SpecialPage {
// This isn't really ideal logging behavior, but let's not hide the
// interwiki logs if we're using them as is.
- $this->showLogFragment( $user, $wgOut );
+ $this->showLogFragment( $user, $this->getOutput() );
}
/**
@@ -292,7 +283,7 @@ class UserrightsPage extends SpecialPage {
* @return Status object
*/
public function fetchUser( $username ) {
- global $wgUser, $wgUserrightsInterwikiDelimiter;
+ global $wgUserrightsInterwikiDelimiter;
$parts = explode( $wgUserrightsInterwikiDelimiter, $username );
if( count( $parts ) < 2 ) {
@@ -304,7 +295,7 @@ class UserrightsPage extends SpecialPage {
if( $database == wfWikiID() ) {
$database = '';
} else {
- if( !$wgUser->isAllowed( 'userrights-interwiki' ) ) {
+ if( !$this->getUser()->isAllowed( 'userrights-interwiki' ) ) {
return Status::newFatal( 'userrights-no-interwiki' );
}
if( !UserRightsProxy::validDatabase( $database ) ) {
@@ -372,8 +363,8 @@ class UserrightsPage extends SpecialPage {
* Output a form to allow searching for a user
*/
function switchForm() {
- global $wgOut, $wgScript;
- $wgOut->addHTML(
+ global $wgScript;
+ $this->getOutput()->addHTML(
Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) .
Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) .
Xml::fieldset( wfMsg( 'userrights-lookup-user' ) ) .
@@ -414,8 +405,6 @@ class UserrightsPage extends SpecialPage {
* @param $groups Array: Array of groups the user is in
*/
protected function showEditUserGroupsForm( $user, $groups ) {
- global $wgOut, $wgUser, $wgLang, $wgRequest;
-
$list = array();
foreach( $groups as $group ) {
$list[] = self::buildGroupLink( $group );
@@ -432,30 +421,38 @@ class UserrightsPage extends SpecialPage {
$count = count( $list );
if( $count > 0 ) {
$grouplist = wfMessage( 'userrights-groupsmember', $count)->parse();
- $grouplist = '<p>' . $grouplist . ' ' . $wgLang->listToText( $list ) . "</p>\n";
+ $grouplist = '<p>' . $grouplist . ' ' . $this->getLanguage()->listToText( $list ) . "</p>\n";
}
$count = count( $autolist );
if( $count > 0 ) {
$autogrouplistintro = wfMessage( 'userrights-groupsmember-auto', $count)->parse();
- $grouplist .= '<p>' . $autogrouplistintro . ' ' . $wgLang->listToText( $autolist ) . "</p>\n";
+ $grouplist .= '<p>' . $autogrouplistintro . ' ' . $this->getLanguage()->listToText( $autolist ) . "</p>\n";
}
- $wgOut->addHTML(
+
+ $userToolLinks = Linker::userToolLinks(
+ $user->getId(),
+ $user->getName(),
+ false, /* default for redContribsWhenNoEdits */
+ Linker::TOOL_LINKS_EMAIL /* Add "send e-mail" link */
+ );
+
+ $this->getOutput()->addHTML(
Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) .
Html::hidden( 'user', $this->mTarget ) .
- Html::hidden( 'wpEditToken', $wgUser->editToken( $this->mTarget ) ) .
+ Html::hidden( 'wpEditToken', $this->getUser()->getEditToken( $this->mTarget ) ) .
Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) .
- wfMsgExt( 'editinguser', array( 'parse' ), wfEscapeWikiText( $user->getName() ) ) .
- wfMsgExt( 'userrights-groups-help', array( 'parse' ) ) .
+ Xml::element( 'legend', array(), wfMessage( 'userrights-editusergroup', $user->getName() )->text() ) .
+ wfMessage( 'editinguser' )->params( wfEscapeWikiText( $user->getName() ) )->rawParams( $userToolLinks )->parse() .
+ wfMessage( 'userrights-groups-help', $user->getName() )->parse() .
$grouplist .
- Xml::tags( 'p', null, $this->groupCheckboxes( $groups ) ) .
+ Xml::tags( 'p', null, $this->groupCheckboxes( $groups, $user ) ) .
Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-userrights-table-outer' ) ) .
"<tr>
<td class='mw-label'>" .
Xml::label( wfMsg( 'userrights-reason' ), 'wpReason' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'user-reason', 60, $wgRequest->getVal( 'user-reason', false ),
+ Xml::input( 'user-reason', 60, $this->getRequest()->getVal( 'user-reason', false ),
array( 'id' => 'wpReason', 'maxlength' => 255 ) ) .
"</td>
</tr>
@@ -496,10 +493,12 @@ class UserrightsPage extends SpecialPage {
/**
* Adds a table with checkboxes where you can select what groups to add/remove
*
+ * @todo Just pass the username string?
* @param $usergroups Array: groups the user belongs to
+ * @param $user User a user object
* @return string XHTML table element with checkboxes
*/
- private function groupCheckboxes( $usergroups ) {
+ private function groupCheckboxes( $usergroups, $user ) {
$allgroups = $this->getAllGroups();
$ret = '';
@@ -547,11 +546,11 @@ class UserrightsPage extends SpecialPage {
foreach( $column as $group => $checkbox ) {
$attr = $checkbox['disabled'] ? array( 'disabled' => 'disabled' ) : array();
+ $member = User::getGroupMember( $group, $user->getName() );
if ( $checkbox['irreversible'] ) {
- $text = htmlspecialchars( wfMsg( 'userrights-irreversible-marker',
- User::getGroupMember( $group ) ) );
+ $text = wfMessage( 'userrights-irreversible-marker', $member )->escaped();
} else {
- $text = htmlspecialchars( User::getGroupMember( $group ) );
+ $text = htmlspecialchars( $member );
}
$checkboxHtml = Xml::checkLabel( $text, "wpGroup-" . $group,
"wpGroup-" . $group, $checkbox['set'], $attr );
@@ -588,13 +587,12 @@ class UserrightsPage extends SpecialPage {
}
/**
- * Returns $wgUser->changeableGroups()
+ * Returns $this->getUser()->changeableGroups()
*
* @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) )
*/
function changeableGroups() {
- global $wgUser;
- return $wgUser->changeableGroups();
+ return $this->getUser()->changeableGroups();
}
/**
@@ -605,6 +603,6 @@ class UserrightsPage extends SpecialPage {
*/
protected function showLogFragment( $user, $output ) {
$output->addHTML( Xml::element( 'h2', null, LogPage::logName( 'rights' ) . "\n" ) );
- LogEventsList::showLogExtract( $output, 'rights', $user->getUserPage()->getPrefixedText() );
+ LogEventsList::showLogExtract( $output, 'rights', $user->getUserPage() );
}
}
diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php
index 0331f056..8185fe88 100644
--- a/includes/specials/SpecialVersion.php
+++ b/includes/specials/SpecialVersion.php
@@ -37,8 +37,7 @@ class SpecialVersion extends SpecialPage {
protected static $viewvcUrls = array(
'svn+ssh://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
'http://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
- # Doesn't work at the time of writing but maybe some day:
- 'https://svn.wikimedia.org/viewvc/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
+ 'https://svn.wikimedia.org/viewvc/mediawiki' => 'https://svn.wikimedia.org/viewvc/mediawiki',
);
public function __construct(){
@@ -49,11 +48,12 @@ class SpecialVersion extends SpecialPage {
* main()
*/
public function execute( $par ) {
- global $wgOut, $wgSpecialVersionShowHooks, $wgRequest;
+ global $wgSpecialVersionShowHooks;
$this->setHeaders();
$this->outputHeader();
- $wgOut->allowClickjacking();
+ $out = $this->getOutput();
+ $out->allowClickjacking();
$text =
$this->getMediaWikiCredits() .
@@ -63,10 +63,10 @@ class SpecialVersion extends SpecialPage {
$text .= $this->getWgHooks();
}
- $wgOut->addWikiText( $text );
- $wgOut->addHTML( $this->IPInfo() );
+ $out->addWikiText( $text );
+ $out->addHTML( $this->IPInfo() );
- if ( $wgRequest->getVal( 'easteregg' ) ) {
+ if ( $this->getRequest()->getVal( 'easteregg' ) ) {
if ( $this->showEasterEgg() ) {
// TODO: put something interesting here
}
@@ -106,7 +106,7 @@ class SpecialVersion extends SpecialPage {
'Aryeh Gregor', 'Aaron Schulz', 'Andrew Garrett', 'Raimond Spekking',
'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe',
'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
- 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Ashar Voultoiz',
+ 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
wfMsg( 'version-poweredby-others' )
);
@@ -126,7 +126,7 @@ class SpecialVersion extends SpecialPage {
// be loaded here, so feel free to use wfMsg*() in the 'name'. Raw HTML or wikimarkup
// can be used.
$software = array();
- $software['[http://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
+ $software['[https://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
$software['[http://www.php.net/ PHP]'] = phpversion() . " (" . php_sapi_name() . ")";
$software[$dbr->getSoftwareLink()] = $dbr->getServerInfo();
@@ -143,7 +143,7 @@ class SpecialVersion extends SpecialPage {
foreach( $software as $name => $version ) {
$out .= "<tr>
<td>" . $name . "</td>
- <td class=\"ltr\">" . $version . "</td>
+ <td dir=\"ltr\">" . $version . "</td>
</tr>\n";
}
@@ -153,6 +153,7 @@ class SpecialVersion extends SpecialPage {
/**
* Return a string of the MediaWiki version with SVN revision if available.
*
+ * @param $flags String
* @return mixed
*/
public static function getVersion( $flags = '' ) {
@@ -357,18 +358,17 @@ class SpecialVersion extends SpecialPage {
* Callback to sort extensions by type.
*/
function compare( $a, $b ) {
- global $wgLang;
if( $a['name'] === $b['name'] ) {
return 0;
} else {
- return $wgLang->lc( $a['name'] ) > $wgLang->lc( $b['name'] )
+ return $this->getLanguage()->lc( $a['name'] ) > $this->getLanguage()->lc( $b['name'] )
? 1
: -1;
}
}
/**
- * Creates and formats the creidts for a single extension and returns this.
+ * Creates and formats the credits for a single extension and returns this.
*
* @param $extension Array
*
@@ -502,7 +502,7 @@ class SpecialVersion extends SpecialPage {
* @return String: HTML fragment
*/
private function IPInfo() {
- $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) );
+ $ip = str_replace( '--', ' - ', htmlspecialchars( $this->getRequest()->getIP() ) );
return "<!-- visited from $ip -->\n" .
"<span style='display:none'>visited from $ip</span>";
}
@@ -542,11 +542,10 @@ class SpecialVersion extends SpecialPage {
} elseif ( $cnt == 0 ) {
return '';
} else {
- global $wgLang;
if ( $sort ) {
sort( $list );
}
- return $wgLang->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
+ return $this->getLanguage()->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
}
}
diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php
index 800e940a..f497e4e2 100644
--- a/includes/specials/SpecialWantedcategories.php
+++ b/includes/specials/SpecialWantedcategories.php
@@ -54,14 +54,14 @@ class WantedCategoriesPage extends WantedQueryPage {
* @return string
*/
function formatResult( $skin, $result ) {
- global $wgLang, $wgContLang;
+ global $wgContLang;
$nt = Title::makeTitle( $result->namespace, $result->title );
$text = htmlspecialchars( $wgContLang->convert( $nt->getText() ) );
$plink = $this->isCached() ?
- $skin->link( $nt, $text ) :
- $skin->link(
+ Linker::link( $nt, $text ) :
+ Linker::link(
$nt,
$text,
array(),
@@ -69,8 +69,7 @@ class WantedCategoriesPage extends WantedQueryPage {
array( 'broken' )
);
- $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'),
- $wgLang->formatNum( $result->value ) );
- return wfSpecialList($plink, $nlinks);
+ $nlinks = $this->msg( 'nmembers' )->numParams( $result->value )->escaped();
+ return $this->getLanguage()->specialList( $plink, $nlinks );
}
}
diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php
index 8a4fe56f..ec0912df 100644
--- a/includes/specials/SpecialWantedfiles.php
+++ b/includes/specials/SpecialWantedfiles.php
@@ -35,6 +35,32 @@ class WantedFilesPage extends WantedQueryPage {
parent::__construct( $name );
}
+ function getPageHeader() {
+ # Specifically setting to use "Wanted Files" (NS_MAIN) as title, so as to get what
+ # category would be used on main namespace pages, for those tricky wikipedia
+ # admins who like to do {{#ifeq:{{NAMESPACE}}|foo|bar|....}}.
+ $catMessage = wfMessage( 'broken-file-category' )
+ ->title( Title::newFromText( "Wanted Files", NS_MAIN ) )
+ ->inContentLanguage();
+
+ if ( !$catMessage->isDisabled() ) {
+ $category = Title::makeTitleSafe( NS_CATEGORY, $catMessage->text() );
+ } else {
+ $category = false;
+ }
+
+ if ( $category ) {
+ return $this
+ ->msg( 'wantedfiletext-cat' )
+ ->params( $category->getFullText() )
+ ->parseAsBlock();
+ } else {
+ return $this
+ ->msg( 'wantedfiletext-nocat' )
+ ->parseAsBlock();
+ }
+ }
+
/**
* KLUGE: The results may contain false positives for files
* that exist e.g. in a shared repo. Setting this at least
diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php
index a4233155..4624b355 100644
--- a/includes/specials/SpecialWantedpages.php
+++ b/includes/specials/SpecialWantedpages.php
@@ -27,9 +27,10 @@
* @ingroup SpecialPage
*/
class WantedPagesPage extends WantedQueryPage {
+
function __construct( $name = 'Wantedpages' ) {
parent::__construct( $name );
- $this->includable( true );
+ $this->mIncludable = true;
}
function execute( $par ) {
@@ -39,12 +40,12 @@ class WantedPagesPage extends WantedQueryPage {
$parts = explode( '/', $par, 2 );
$this->limit = (int)$parts[0];
// @todo FIXME: nlinks is ignored
- $nlinks = isset( $parts[1] ) && $parts[1] === 'nlinks';
+ //$nlinks = isset( $parts[1] ) && $parts[1] === 'nlinks';
$this->offset = 0;
} else {
- $nlinks = true;
+ //$nlinks = true;
}
- $this->setListOutput( $inc );
+ $this->setListoutput( $inc );
$this->shownavigation = !$inc;
parent::execute( $par );
}
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index fd562be4..fef54911 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -40,6 +40,20 @@ class SpecialWatchlist extends SpecialPage {
$user = $this->getUser();
$output = $this->getOutput();
+ # Anons don't get a watchlist
+ if( $user->isAnon() ) {
+ $output->setPageTitle( $this->msg( 'watchnologin' ) );
+ $output->setRobotPolicy( 'noindex,nofollow' );
+ $llink = Linker::linkKnown(
+ SpecialPage::getTitleFor( 'Userlogin' ),
+ $this->msg( 'loginreqlink' )->escaped(),
+ array(),
+ array( 'returnto' => $this->getTitle()->getPrefixedText() )
+ );
+ $output->addHTML( $this->msg( 'watchlistanontext' )->rawParams( $llink )->parse() );
+ return;
+ }
+
// Add feed links
$wlToken = $user->getOption( 'watchlisttoken' );
if ( !$wlToken ) {
@@ -51,31 +65,11 @@ class SpecialWatchlist extends SpecialPage {
$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;
- }
-
$this->setHeaders();
$this->outputHeader();
- $sub = wfMsgExt(
- 'watchlistfor2',
- array( 'parseinline', 'replaceafter' ),
- $user->getName(),
- SpecialEditWatchlist::buildTools( $this->getSkin() )
- );
- $output->setSubtitle( $sub );
+ $output->addSubtitle( $this->msg( 'watchlistfor2', $user->getName()
+ )->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
$request = $this->getRequest();
@@ -245,23 +239,21 @@ class SpecialWatchlist extends SpecialPage {
$output->showLagWarning( $lag );
}
- $lang = $this->getLang();
-
# Create output form
- $form = Xml::fieldset( wfMsg( 'watchlist-options' ), false, array( 'id' => 'mw-watchlist-options' ) );
+ $form = Xml::fieldset( $this->msg( 'watchlist-options' )->text(), false, array( 'id' => 'mw-watchlist-options' ) );
# Show watchlist header
- $form .= wfMsgExt( 'watchlist-details', array( 'parseinline' ), $lang->formatNum( $nitems ) );
+ $form .= $this->msg( 'watchlist-details' )->numParams( $nitems )->parse();
if( $user->getOption( 'enotifwatchlistpages' ) && $wgEnotifWatchlist) {
- $form .= wfMsgExt( 'wlheader-enotif', 'parse' ) . "\n";
+ $form .= $this->msg( 'wlheader-enotif' )->parseAsBlock() . "\n";
}
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' ) ) .
+ $this->msg( 'wlheader-showupdated' )->parse() . ' ' .
+ Xml::submitButton( $this->msg( 'enotif_reset' )->text(), array( 'name' => 'dummy' ) ) .
Html::hidden( 'reset', 'all' ) .
Xml::closeElement( 'form' );
}
@@ -297,21 +289,12 @@ class SpecialWatchlist extends SpecialPage {
/* Start bottom header */
+ $lang = $this->getLanguage();
$wlInfo = '';
- if( $values['days'] >= 1 ) {
+ if( $values['days'] > 0 ) {
$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 />';
+ $wlInfo = $this->msg( 'wlnote' )->numParams( $numRows, round( $values['days'] * 24 ) )->params(
+ $lang->userDate( $timestamp, $user ), $lang->userTime( $timestamp, $user ) )->parse() . '<br />';
}
$cutofflinks = "\n" . $this->cutoffLinks( $values['days'], $nondefaults ) . "<br />\n";
@@ -344,10 +327,10 @@ class SpecialWatchlist extends SpecialPage {
$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::label( $this->msg( 'namespace' )->text(), 'namespace' ) . '&#160;';
$form .= Xml::namespaceSelector( $nameSpace, '' ) . '&#160;';
- $form .= Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $invert ) . '&#160;';
- $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '</p>';
+ $form .= Xml::checkLabel( $this->msg( 'invert' )->text(), 'invert', 'nsinvert', $invert ) . '&#160;';
+ $form .= Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . '</p>';
$form .= Html::hidden( 'days', $values['days'] );
foreach ( $filters as $key => $msg ) {
if ( $values[$key] ) {
@@ -416,13 +399,10 @@ class SpecialWatchlist extends SpecialPage {
}
protected function showHideLink( $options, $message, $name, $value ) {
- $showLinktext = wfMsgHtml( 'show' );
- $hideLinktext = wfMsgHtml( 'hide' );
-
- $label = $value ? $showLinktext : $hideLinktext;
+ $label = $this->msg( $value ? 'show' : 'hide' )->escaped();
$options[$name] = 1 - (int) $value;
- return wfMsgHtml( $message, Linker::linkKnown( $this->getTitle(), $label, array(), $options ) );
+ return $this->msg( $message )->rawParams( Linker::linkKnown( $this->getTitle(), $label, array(), $options ) )->escaped();
}
protected function hoursLink( $h, $options = array() ) {
@@ -430,7 +410,7 @@ class SpecialWatchlist extends SpecialPage {
return Linker::linkKnown(
$this->getTitle(),
- $this->getLang()->formatNum( $h ),
+ $this->getLanguage()->formatNum( $h ),
array(),
$options
);
@@ -438,7 +418,7 @@ class SpecialWatchlist extends SpecialPage {
protected function daysLink( $d, $options = array() ) {
$options['days'] = $d;
- $message = ( $d ? $this->getLang()->formatNum( $d ) : wfMsgHtml( 'watchlistall2' ) );
+ $message = ( $d ? $this->getLanguage()->formatNum( $d ) : $this->msg( 'watchlistall2' )->escaped() );
return Linker::linkKnown(
$this->getTitle(),
@@ -464,11 +444,10 @@ class SpecialWatchlist extends SpecialPage {
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 $this->msg( 'wlshowlast' )->rawParams(
+ $this->getLanguage()->pipeList( $hours ),
+ $this->getLanguage()->pipeList( $days ),
+ $this->daysLink( 0, $options ) )->parse();
}
/**
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index f7d7bfef..d5129bf6 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -47,8 +47,9 @@ class SpecialWhatLinksHere extends SpecialPage {
}
function execute( $par ) {
+ global $wgQueryPageDefaultLimit;
$out = $this->getOutput();
-
+
$this->setHeaders();
$this->outputHeader();
@@ -56,7 +57,7 @@ class SpecialWhatLinksHere extends SpecialPage {
$opts->add( 'target', '' );
$opts->add( 'namespace', '', FormOptions::INTNULL );
- $opts->add( 'limit', 50 );
+ $opts->add( 'limit', $wgQueryPageDefaultLimit );
$opts->add( 'from', 0 );
$opts->add( 'back', 0 );
$opts->add( 'hideredirs', false );
@@ -83,11 +84,10 @@ class SpecialWhatLinksHere extends SpecialPage {
$this->getSkin()->setRelevantTitle( $this->target );
-
$this->selfTitle = $this->getTitle( $this->target->getPrefixedDBkey() );
- $out->setPageTitle( wfMsg( 'whatlinkshere-title', $this->target->getPrefixedText() ) );
- $out->setSubtitle( wfMsg( 'whatlinkshere-backlink', Linker::link( $this->target, $this->target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) );
+ $out->setPageTitle( $this->msg( 'whatlinkshere-title', $this->target->getPrefixedText() ) );
+ $out->addBacklinkSubtitle( $this->target );
$this->showIndirectLinks( 0, $this->target, $opts->getValue( 'limit' ),
$opts->getValue( 'from' ), $opts->getValue( 'back' ) );
@@ -120,9 +120,9 @@ class SpecialWhatLinksHere extends SpecialPage {
'pl_title' => $target->getDBkey(),
);
if( $hideredirs ) {
- $plConds['page_is_redirect'] = 0;
+ $plConds['rd_from'] = null;
} elseif( $hidelinks ) {
- $plConds['page_is_redirect'] = 1;
+ $plConds[] = 'rd_from is NOT NULL';
}
$tlConds = array(
@@ -157,24 +157,34 @@ class SpecialWhatLinksHere extends SpecialPage {
$options[] = 'STRAIGHT_JOIN';
$options['LIMIT'] = $queryLimit;
- $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' );
+ $fields = array( 'page_id', 'page_namespace', 'page_title', 'rd_from' );
+
+ $joinConds = array( 'redirect' => array( 'LEFT JOIN', array(
+ 'rd_from = page_id',
+ 'rd_namespace' => $target->getNamespace(),
+ 'rd_title' => $target->getDBkey(),
+ '(rd_interwiki is NULL) or (rd_interwiki = \'\')'
+ )));
if( $fetchlinks ) {
$options['ORDER BY'] = 'pl_from';
- $plRes = $dbr->select( array( 'pagelinks', 'page' ), $fields,
- $plConds, __METHOD__, $options );
+ $plRes = $dbr->select( array( 'pagelinks', 'page', 'redirect' ), $fields,
+ $plConds, __METHOD__, $options,
+ $joinConds);
}
if( !$hidetrans ) {
$options['ORDER BY'] = 'tl_from';
- $tlRes = $dbr->select( array( 'templatelinks', 'page' ), $fields,
- $tlConds, __METHOD__, $options );
+ $tlRes = $dbr->select( array( 'templatelinks', 'page', 'redirect' ), $fields,
+ $tlConds, __METHOD__, $options,
+ $joinConds);
}
if( !$hideimages ) {
$options['ORDER BY'] = 'il_from';
- $ilRes = $dbr->select( array( 'imagelinks', 'page' ), $fields,
- $ilConds, __METHOD__, $options );
+ $ilRes = $dbr->select( array( 'imagelinks', 'page', 'redirect' ), $fields,
+ $ilConds, __METHOD__, $options,
+ $joinConds);
}
if( ( !$fetchlinks || !$dbr->numRows($plRes) ) && ( $hidetrans || !$dbr->numRows($tlRes) ) && ( $hideimages || !$dbr->numRows($ilRes) ) ) {
@@ -248,7 +258,7 @@ class SpecialWhatLinksHere extends SpecialPage {
foreach ( $rows as $row ) {
$nt = Title::makeTitle( $row->page_namespace, $row->page_title );
- if ( $row->page_is_redirect && $level < 2 ) {
+ if ( $row->rd_from && $level < 2 ) {
$out->addHTML( $this->listItem( $row, $nt, true ) );
$this->showIndirectLinks( $level + 1, $nt, $wgMaxRedirectLinksRetrieved );
$out->addHTML( Xml::closeElement( 'li' ) );
@@ -269,8 +279,7 @@ class SpecialWhatLinksHere extends SpecialPage {
}
protected function listItem( $row, $nt, $notClose = false ) {
- global $wgLang;
- $dirmark = $wgLang->getDirMark();
+ $dirmark = $this->getLanguage()->getDirMark();
# local message cache
static $msgcache = null;
@@ -283,7 +292,7 @@ class SpecialWhatLinksHere extends SpecialPage {
}
}
- if( $row->page_is_redirect ) {
+ if( $row->rd_from ) {
$query = array( 'redirect' => 'no' );
} else {
$query = array();
@@ -299,7 +308,7 @@ class SpecialWhatLinksHere extends SpecialPage {
// Display properties (redirect or template)
$propsText = '';
$props = array();
- if ( $row->page_is_redirect )
+ if ( $row->rd_from )
$props[] = $msgcache['isredirect'];
if ( $row->is_template )
$props[] = $msgcache['istemplate'];
@@ -346,11 +355,9 @@ class SpecialWhatLinksHere extends SpecialPage {
}
function getPrevNext( $prevId, $nextId ) {
- global $wgLang;
$currentLimit = $this->opts->getValue( 'limit' );
- $fmtLimit = $wgLang->formatNum( $currentLimit );
- $prev = wfMsgExt( 'whatlinkshere-prev', array( 'parsemag', 'escape' ), $fmtLimit );
- $next = wfMsgExt( 'whatlinkshere-next', array( 'parsemag', 'escape' ), $fmtLimit );
+ $prev = wfMessage( 'whatlinkshere-prev' )->numParams( $currentLimit )->escaped();
+ $next = wfMessage( 'whatlinkshere-next' )->numParams( $currentLimit )->escaped();
$changed = $this->opts->getChangedValues();
unset($changed['target']); // Already in the request title
@@ -365,13 +372,14 @@ class SpecialWhatLinksHere extends SpecialPage {
}
$limitLinks = array();
+ $lang = $this->getLanguage();
foreach ( $this->limits as $limit ) {
- $prettyLimit = $wgLang->formatNum( $limit );
+ $prettyLimit = htmlspecialchars( $lang->formatNum( $limit ) );
$overrides = array( 'limit' => $limit );
$limitLinks[] = $this->makeSelfLink( $prettyLimit, array_merge( $changed, $overrides ) );
}
- $nums = $wgLang->pipeList( $limitLinks );
+ $nums = $lang->pipeList( $limitLinks );
return wfMsgHtml( 'viewprevnext', $prev, $next, $nums );
}
@@ -425,7 +433,6 @@ class SpecialWhatLinksHere extends SpecialPage {
* @return string HTML fieldset and filter panel with the show/hide links
*/
function getFilterPanel() {
- global $wgLang;
$show = wfMsgHtml( 'show' );
$hide = wfMsgHtml( 'hide' );
@@ -445,6 +452,6 @@ class SpecialWhatLinksHere extends SpecialPage {
$overrides = array( $type => !$chosen );
$links[] = wfMsgHtml( "whatlinkshere-{$type}", $this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) );
}
- return Xml::fieldset( wfMsg( 'whatlinkshere-filters' ), $wgLang->pipeList( $links ) );
+ return Xml::fieldset( wfMsg( 'whatlinkshere-filters' ), $this->getLanguage()->pipeList( $links ) );
}
}
diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php
index 9d91b833..89dae203 100644
--- a/includes/specials/SpecialWithoutinterwiki.php
+++ b/includes/specials/SpecialWithoutinterwiki.php
@@ -35,8 +35,8 @@ class WithoutInterwikiPage extends PageQueryPage {
}
function execute( $par ) {
- global $wgRequest;
- $this->prefix = Title::capitalize( $wgRequest->getVal( 'prefix', $par ), NS_MAIN );
+ $this->prefix = Title::capitalize(
+ $this->getRequest()->getVal( 'prefix', $par ), NS_MAIN );
parent::execute( $par );
}
@@ -84,7 +84,7 @@ class WithoutInterwikiPage extends PageQueryPage {
'page_title AS title',
'page_title AS value' ),
'conds' => array ( 'll_title IS NULL',
- 'page_namespace' => NS_MAIN,
+ 'page_namespace' => MWNamespace::getContentNamespaces(),
'page_is_redirect' => 0 ),
'join_conds' => array ( 'langlinks' => array (
'LEFT JOIN', 'll_from = page_id' ) )
diff --git a/includes/templates/NoLocalSettings.php b/includes/templates/NoLocalSettings.php
index facb6167..59284af0 100644
--- a/includes/templates/NoLocalSettings.php
+++ b/includes/templates/NoLocalSettings.php
@@ -10,6 +10,7 @@ if ( !isset( $wgVersion ) ) {
$wgVersion = 'VERSION';
}
+# bug 30219 : can not use pathinfo() on URLs since slashes do not match
$matches = array();
$ext = 'php';
$path = '/';
diff --git a/includes/templates/Usercreate.php b/includes/templates/Usercreate.php
new file mode 100644
index 00000000..c93b02cc
--- /dev/null
+++ b/includes/templates/Usercreate.php
@@ -0,0 +1,238 @@
+<?php
+/**
+ * Html form for account creation
+ *
+ * @file
+ * @ingroup Templates
+ */
+
+/**
+ * @defgroup Templates Templates
+ */
+
+if( !defined( 'MEDIAWIKI' ) ) die( -1 );
+
+/**
+ * @ingroup Templates
+ */
+class UsercreateTemplate extends QuickTemplate {
+ function addInputItem( $name, $value, $type, $msg, $helptext = false ) {
+ $this->data['extraInput'][] = array(
+ 'name' => $name,
+ 'value' => $value,
+ 'type' => $type,
+ 'msg' => $msg,
+ 'helptext' => $helptext,
+ );
+ }
+
+ function execute() {
+ if( $this->data['message'] ) {
+?>
+ <div class="<?php $this->text('messagetype') ?>box">
+ <?php if ( $this->data['messagetype'] == 'error' ) { ?>
+ <strong><?php $this->msg( 'loginerror' )?></strong><br />
+ <?php } ?>
+ <?php $this->html('message') ?>
+ </div>
+ <div class="visualClear"></div>
+<?php } ?>
+
+<div id="signupstart"><?php $this->msgWiki( 'signupstart' ); ?></div>
+<div id="userlogin">
+
+<form name="userlogin2" id="userlogin2" method="post" action="<?php $this->text('action') ?>">
+ <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 } ?>
+ <table>
+ <tr>
+ <td class="mw-label"><label for='wpName2'><?php $this->msg('yourname') ?></label></td>
+ <td class="mw-input">
+ <?php
+ echo Html::input( 'wpName', $this->data['name'], 'text', array(
+ 'class' => 'loginText',
+ 'id' => 'wpName2',
+ 'tabindex' => '1',
+ 'size' => '20',
+ 'required',
+ 'autofocus'
+ ) ); ?>
+ </td>
+ </tr>
+ <tr>
+ <td class="mw-label"><label for='wpPassword2'><?php $this->msg('yourpassword') ?></label></td>
+ <td class="mw-input">
+<?php
+ echo Html::input( 'wpPassword', null, 'password', array(
+ 'class' => 'loginPassword',
+ 'id' => 'wpPassword2',
+ 'tabindex' => '2',
+ 'size' => '20'
+ ) + User::passwordChangeInputAttribs() ); ?>
+ </td>
+ </tr>
+ <?php if( $this->data['usedomain'] ) {
+ $doms = "";
+ foreach( $this->data['domainnames'] as $dom ) {
+ $doms .= "<option>" . htmlspecialchars( $dom ) . "</option>";
+ }
+ ?>
+ <tr>
+ <td class="mw-label"><?php $this->msg( 'yourdomainname' ) ?></td>
+ <td class="mw-input">
+ <select name="wpDomain" value="<?php $this->text( 'domain' ) ?>"
+ tabindex="3">
+ <?php echo $doms ?>
+ </select>
+ </td>
+ </tr>
+ <?php } ?>
+ <tr>
+ <td class="mw-label"><label for='wpRetype'><?php $this->msg('yourpasswordagain') ?></label></td>
+ <td class="mw-input">
+ <?php
+ echo Html::input( 'wpRetype', null, 'password', array(
+ 'class' => 'loginPassword',
+ 'id' => 'wpRetype',
+ 'tabindex' => '4',
+ 'size' => '20'
+ ) + User::passwordChangeInputAttribs() ); ?>
+ </td>
+ </tr>
+ <tr>
+ <?php if( $this->data['useemail'] ) { ?>
+ <td class="mw-label"><label for='wpEmail'><?php $this->msg('youremail') ?></label></td>
+ <td class="mw-input">
+ <?php
+ echo Html::input( 'wpEmail', $this->data['email'], 'email', array(
+ 'class' => 'loginText',
+ 'id' => 'wpEmail',
+ 'tabindex' => '5',
+ 'size' => '20'
+ ) ); ?>
+ <div class="prefsectiontip">
+ <?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 } ?>
+ <?php if( $this->data['userealname'] ) { ?>
+ </tr>
+ <tr>
+ <td class="mw-label"><label for='wpRealName'><?php $this->msg('yourrealname') ?></label></td>
+ <td class="mw-input">
+ <input type='text' class='loginText' name="wpRealName" id="wpRealName"
+ tabindex="6"
+ value="<?php $this->text('realname') ?>" size='20' />
+ <div class="prefsectiontip">
+ <?php $this->msgWiki('prefs-help-realname'); ?>
+ </div>
+ </td>
+ <?php } ?>
+ <?php if( $this->data['usereason'] ) { ?>
+ </tr>
+ <tr>
+ <td class="mw-label"><label for='wpReason'><?php $this->msg('createaccountreason') ?></label></td>
+ <td class="mw-input">
+ <input type='text' class='loginText' name="wpReason" id="wpReason"
+ tabindex="7"
+ value="<?php $this->text('reason') ?>" size='20' />
+ </td>
+ <?php } ?>
+ </tr>
+ <?php if( $this->data['canremember'] ) { ?>
+ <tr>
+ <td></td>
+ <td class="mw-input">
+ <?php
+ global $wgCookieExpiration, $wgLang;
+ echo Xml::checkLabel(
+ wfMsgExt( 'remembermypassword', 'parsemag', $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) ) ),
+ 'wpRemember',
+ 'wpRemember',
+ $this->data['remember'],
+ array( 'tabindex' => '8' )
+ )
+ ?>
+ </td>
+ </tr>
+<?php }
+
+ $tabIndex = 9;
+ if ( isset( $this->data['extraInput'] ) && is_array( $this->data['extraInput'] ) ) {
+ foreach ( $this->data['extraInput'] as $inputItem ) { ?>
+ <tr>
+ <?php
+ if ( !empty( $inputItem['msg'] ) && $inputItem['type'] != 'checkbox' ) {
+ ?><td class="mw-label"><label for="<?php
+ echo htmlspecialchars( $inputItem['name'] ); ?>"><?php
+ $this->msgWiki( $inputItem['msg'] ) ?></label><?php
+ } else {
+ ?><td><?php
+ }
+ ?></td>
+ <td class="mw-input">
+ <input type="<?php echo htmlspecialchars( $inputItem['type'] ) ?>" name="<?php
+ echo htmlspecialchars( $inputItem['name'] ); ?>"
+ tabindex="<?php echo $tabIndex++; ?>"
+ value="<?php
+ if ( $inputItem['type'] != 'checkbox' ) {
+ echo htmlspecialchars( $inputItem['value'] );
+ } else {
+ echo '1';
+ }
+ ?>" id="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
+ <?php
+ if ( $inputItem['type'] == 'checkbox' && !empty( $inputItem['value'] ) )
+ echo 'checked="checked"';
+ ?> /> <?php
+ if ( $inputItem['type'] == 'checkbox' && !empty( $inputItem['msg'] ) ) {
+ ?>
+ <label for="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"><?php
+ $this->msgHtml( $inputItem['msg'] ) ?></label><?php
+ }
+ if( $inputItem['helptext'] !== false ) {
+ ?>
+ <div class="prefsectiontip">
+ <?php $this->msgWiki( $inputItem['helptext'] ); ?>
+ </div>
+ <?php } ?>
+ </td>
+ </tr>
+<?php
+
+ }
+ }
+?>
+ <tr>
+ <td></td>
+ <td class="mw-submit">
+ <input type='submit' name="wpCreateaccount" id="wpCreateaccount"
+ tabindex="<?php echo $tabIndex++; ?>"
+ value="<?php $this->msg('createaccount') ?>" />
+ <?php if( $this->data['createemail'] ) { ?>
+ <input type='submit' name="wpCreateaccountMail" id="wpCreateaccountMail"
+ tabindex="<?php echo $tabIndex++; ?>"
+ value="<?php $this->msg('createaccountmail') ?>" />
+ <?php } ?>
+ </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 } ?>
+</form>
+</div>
+<div id="signupend"><?php $this->html( 'signupend' ); ?></div>
+<?php
+
+ }
+}
diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php
index 0bfd9737..efe826f4 100644
--- a/includes/templates/Userlogin.php
+++ b/includes/templates/Userlogin.php
@@ -1,6 +1,6 @@
<?php
/**
- * Html forms for user login and account creation
+ * Html form for user login
*
* @file
* @ingroup Templates
@@ -157,228 +157,3 @@ class UserloginTemplate extends QuickTemplate {
}
}
-
-/**
- * @ingroup Templates
- */
-class UsercreateTemplate extends QuickTemplate {
- function addInputItem( $name, $value, $type, $msg, $helptext = false ) {
- $this->data['extraInput'][] = array(
- 'name' => $name,
- 'value' => $value,
- 'type' => $type,
- 'msg' => $msg,
- 'helptext' => $helptext,
- );
- }
-
- function execute() {
- if( $this->data['message'] ) {
-?>
- <div class="<?php $this->text('messagetype') ?>box">
- <?php if ( $this->data['messagetype'] == 'error' ) { ?>
- <strong><?php $this->msg( 'loginerror' )?></strong><br />
- <?php } ?>
- <?php $this->html('message') ?>
- </div>
- <div class="visualClear"></div>
-<?php } ?>
-
-<div id="signupstart"><?php $this->msgWiki( 'signupstart' ); ?></div>
-<div id="userlogin">
-
-<form name="userlogin2" id="userlogin2" method="post" action="<?php $this->text('action') ?>">
- <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 } ?>
- <table>
- <tr>
- <td class="mw-label"><label for='wpName2'><?php $this->msg('yourname') ?></label></td>
- <td class="mw-input">
- <?php
- echo Html::input( 'wpName', $this->data['name'], 'text', array(
- 'class' => 'loginText',
- 'id' => 'wpName2',
- 'tabindex' => '1',
- 'size' => '20',
- 'required',
- 'autofocus'
- ) ); ?>
- </td>
- </tr>
- <tr>
- <td class="mw-label"><label for='wpPassword2'><?php $this->msg('yourpassword') ?></label></td>
- <td class="mw-input">
-<?php
- echo Html::input( 'wpPassword', null, 'password', array(
- 'class' => 'loginPassword',
- 'id' => 'wpPassword2',
- 'tabindex' => '2',
- 'size' => '20'
- ) + User::passwordChangeInputAttribs() ); ?>
- </td>
- </tr>
- <?php if( $this->data['usedomain'] ) {
- $doms = "";
- foreach( $this->data['domainnames'] as $dom ) {
- $doms .= "<option>" . htmlspecialchars( $dom ) . "</option>";
- }
- ?>
- <tr>
- <td class="mw-label"><?php $this->msg( 'yourdomainname' ) ?></td>
- <td class="mw-input">
- <select name="wpDomain" value="<?php $this->text( 'domain' ) ?>"
- tabindex="3">
- <?php echo $doms ?>
- </select>
- </td>
- </tr>
- <?php } ?>
- <tr>
- <td class="mw-label"><label for='wpRetype'><?php $this->msg('yourpasswordagain') ?></label></td>
- <td class="mw-input">
- <?php
- echo Html::input( 'wpRetype', null, 'password', array(
- 'class' => 'loginPassword',
- 'id' => 'wpRetype',
- 'tabindex' => '4',
- 'size' => '20'
- ) + User::passwordChangeInputAttribs() ); ?>
- </td>
- </tr>
- <tr>
- <?php if( $this->data['useemail'] ) { ?>
- <td class="mw-label"><label for='wpEmail'><?php $this->msg('youremail') ?></label></td>
- <td class="mw-input">
- <?php
- echo Html::input( 'wpEmail', $this->data['email'], 'email', array(
- 'class' => 'loginText',
- 'id' => 'wpEmail',
- 'tabindex' => '5',
- 'size' => '20'
- ) ); ?>
- <div class="prefsectiontip">
- <?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 } ?>
- <?php if( $this->data['userealname'] ) { ?>
- </tr>
- <tr>
- <td class="mw-label"><label for='wpRealName'><?php $this->msg('yourrealname') ?></label></td>
- <td class="mw-input">
- <input type='text' class='loginText' name="wpRealName" id="wpRealName"
- tabindex="6"
- value="<?php $this->text('realname') ?>" size='20' />
- <div class="prefsectiontip">
- <?php $this->msgWiki('prefs-help-realname'); ?>
- </div>
- </td>
- <?php } ?>
- <?php if( $this->data['usereason'] ) { ?>
- </tr>
- <tr>
- <td class="mw-label"><label for='wpReason'><?php $this->msg('createaccountreason') ?></label></td>
- <td class="mw-input">
- <input type='text' class='loginText' name="wpReason" id="wpReason"
- tabindex="7"
- value="<?php $this->text('reason') ?>" size='20' />
- </td>
- <?php } ?>
- </tr>
- <?php if( $this->data['canremember'] ) { ?>
- <tr>
- <td></td>
- <td class="mw-input">
- <?php
- global $wgCookieExpiration, $wgLang;
- echo Xml::checkLabel(
- wfMsgExt( 'remembermypassword', 'parsemag', $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) ) ),
- 'wpRemember',
- 'wpRemember',
- $this->data['remember'],
- array( 'tabindex' => '8' )
- )
- ?>
- </td>
- </tr>
-<?php }
-
- $tabIndex = 9;
- if ( isset( $this->data['extraInput'] ) && is_array( $this->data['extraInput'] ) ) {
- foreach ( $this->data['extraInput'] as $inputItem ) { ?>
- <tr>
- <?php
- if ( !empty( $inputItem['msg'] ) && $inputItem['type'] != 'checkbox' ) {
- ?><td class="mw-label"><label for="<?php
- echo htmlspecialchars( $inputItem['name'] ); ?>"><?php
- $this->msgWiki( $inputItem['msg'] ) ?></label><?php
- } else {
- ?><td><?php
- }
- ?></td>
- <td class="mw-input">
- <input type="<?php echo htmlspecialchars( $inputItem['type'] ) ?>" name="<?php
- echo htmlspecialchars( $inputItem['name'] ); ?>"
- tabindex="<?php echo $tabIndex++; ?>"
- value="<?php
- if ( $inputItem['type'] != 'checkbox' ) {
- echo htmlspecialchars( $inputItem['value'] );
- } else {
- echo '1';
- }
- ?>" id="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"
- <?php
- if ( $inputItem['type'] == 'checkbox' && !empty( $inputItem['value'] ) )
- echo 'checked="checked"';
- ?> /> <?php
- if ( $inputItem['type'] == 'checkbox' && !empty( $inputItem['msg'] ) ) {
- ?>
- <label for="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"><?php
- $this->msgHtml( $inputItem['msg'] ) ?></label><?php
- }
- if( $inputItem['helptext'] !== false ) {
- ?>
- <div class="prefsectiontip">
- <?php $this->msgWiki( $inputItem['helptext'] ); ?>
- </div>
- <?php } ?>
- </td>
- </tr>
-<?php
-
- }
- }
-?>
- <tr>
- <td></td>
- <td class="mw-submit">
- <input type='submit' name="wpCreateaccount" id="wpCreateaccount"
- tabindex="<?php echo $tabIndex++; ?>"
- value="<?php $this->msg('createaccount') ?>" />
- <?php if( $this->data['createemail'] ) { ?>
- <input type='submit' name="wpCreateaccountMail" id="wpCreateaccountMail"
- tabindex="<?php echo $tabIndex++; ?>"
- value="<?php $this->msg('createaccountmail') ?>" />
- <?php } ?>
- </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 } ?>
-</form>
-</div>
-<div id="signupend"><?php $this->html( 'signupend' ); ?></div>
-<?php
-
- }
-}
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php
index a97edbc7..32eeeb38 100644
--- a/includes/upload/UploadBase.php
+++ b/includes/upload/UploadBase.php
@@ -1,7 +1,10 @@
<?php
/**
- * @file
- * @ingroup upload
+ * @defgroup Upload
+ */
+
+/**
+ * @ingroup Upload
*
* UploadBase and subclasses are the backend of MediaWiki's file uploads.
* The frontends are formed by ApiUpload and SpecialUpload.
@@ -12,7 +15,6 @@
* @author Bryan Tong Minh
* @author Michael Dale
*/
-
abstract class UploadBase {
protected $mTempPath;
protected $mDesiredDestName, $mDestName, $mRemoveTempFile, $mSourceType;
@@ -37,6 +39,7 @@ abstract class UploadBase {
const HOOK_ABORTED = 11;
const FILE_TOO_LARGE = 12;
const WINDOWS_NONASCII_FILENAME = 13;
+ const FILENAME_TOO_LONG = 14;
public function getVerificationErrorCode( $error ) {
$code_to_status = array(self::EMPTY_FILE => 'empty-file',
@@ -49,6 +52,7 @@ abstract class UploadBase {
self::VERIFICATION_ERROR => 'verification-error',
self::HOOK_ABORTED => 'hookaborted',
self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename',
+ self::FILENAME_TOO_LONG => 'filename-toolong',
);
if( isset( $code_to_status[$error] ) ) {
return $code_to_status[$error];
@@ -161,6 +165,9 @@ abstract class UploadBase {
*/
public function initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile = false ) {
$this->mDesiredDestName = $name;
+ if ( FileBackend::isStoragePath( $tempPath ) ) {
+ throw new MWException( __METHOD__ . " given storage path `$tempPath`." );
+ }
$this->mTempPath = $tempPath;
$this->mFileSize = $fileSize;
$this->mRemoveTempFile = $removeTempFile;
@@ -195,39 +202,17 @@ abstract class UploadBase {
}
/**
- * Append a file to the Repo file
- *
- * @param $srcPath String: path to source file
- * @param $toAppendPath String: path to the Repo file that will be appended to.
- * @return Status Status
- */
- protected function appendToUploadFile( $srcPath, $toAppendPath ) {
- $repo = RepoGroup::singleton()->getLocalRepo();
- $status = $repo->append( $srcPath, $toAppendPath );
- return $status;
- }
-
- /**
- * 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
*/
function getRealPath( $srcPath ) {
$repo = RepoGroup::singleton()->getLocalRepo();
if ( $repo->isVirtualUrl( $srcPath ) ) {
- return $repo->resolveVirtualUrl( $srcPath );
+ // @TODO: just make uploads work with storage paths
+ // UploadFromStash loads files via virtuals URLs
+ $tmpFile = $repo->getLocalCopy( $srcPath );
+ $tmpFile->bind( $this ); // keep alive with $thumb
+ return $tmpFile->getPath();
}
return $srcPath;
}
@@ -355,12 +340,12 @@ abstract class UploadBase {
* @return mixed true of the file is verified, array otherwise.
*/
protected function verifyFile() {
- global $wgAllowJavaUploads;
+ global $wgAllowJavaUploads, $wgDisableUploadScriptChecks;
# get the title, even though we are doing nothing with it, because
# we need to populate mFinalExtension
$this->getTitle();
- $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
+ $this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension );
# check mime type, if desired
$mime = $this->mFileProps[ 'file-mime' ];
@@ -370,13 +355,15 @@ abstract class UploadBase {
}
# check for htmlish code and javascript
- if( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
- return array( 'uploadscripted' );
- }
- if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
- if( $this->detectScriptInSvg( $this->mTempPath ) ) {
+ if ( !$wgDisableUploadScriptChecks ) {
+ if( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) {
return array( 'uploadscripted' );
}
+ if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) {
+ if( $this->detectScriptInSvg( $this->mTempPath ) ) {
+ return array( 'uploadscripted' );
+ }
+ }
}
# Check for Java applets, which if uploaded can bypass cross-site
@@ -445,7 +432,7 @@ abstract class UploadBase {
}
/**
- * Alias for verifyTitlePermissions. The function was originally 'verifyPermissions'
+ * 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
@@ -478,7 +465,7 @@ abstract class UploadBase {
$permErrors = $nt->getUserPermissionsErrors( 'edit', $user );
$permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $user );
if ( !$nt->exists() ) {
- $permErrorsCreate = $nt->getUserPermissionsErrors( 'createpage', $user );
+ $permErrorsCreate = $nt->getUserPermissionsErrors( 'create', $user );
} else {
$permErrorsCreate = array();
}
@@ -544,7 +531,7 @@ abstract class UploadBase {
}
// Check dupes against existing files
- $hash = File::sha1Base36( $this->mTempPath );
+ $hash = FSFile::getSha1Base36FromPath( $this->mTempPath );
$dupes = RepoGroup::singleton()->findBySha1( $hash );
$title = $this->getTitle();
// Remove all matches against self
@@ -589,7 +576,7 @@ abstract class UploadBase {
if ( $watch ) {
$user->addWatch( $this->getLocalFile()->getTitle() );
}
-
+
wfRunHooks( 'UploadComplete', array( &$this ) );
}
@@ -606,7 +593,7 @@ 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.
*/
@@ -617,6 +604,13 @@ abstract class UploadBase {
$this->mFilteredName = $this->mDesiredDestName;
}
+ # oi_archive_name is max 255 bytes, which include a timestamp and an
+ # exclamation mark, so restrict file name to 240 bytes.
+ if ( strlen( $this->mFilteredName ) > 240 ) {
+ $this->mTitleError = self::FILENAME_TOO_LONG;
+ return $this->mTitle = null;
+ }
+
/**
* Chop off any directories in the given filename. Then
* filter out illegal characters, and try to make a legible name
@@ -631,6 +625,8 @@ abstract class UploadBase {
}
$this->mFilteredName = $nt->getDBkey();
+
+
/**
* We'll want to blacklist against *any* 'extension', and use
* only the final one for the whitelist.
@@ -677,7 +673,7 @@ abstract class UploadBase {
$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;
@@ -742,14 +738,12 @@ abstract class UploadBase {
* 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 file key used to find the file info again. If not supplied, a key will be autogenerated.
* @return UploadStashFile stashed file
*/
- public function stashFile( $key = null ) {
+ public function stashFile() {
// was stashSessionFile
$stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
-
- $file = $stash->stashFile( $this->mTempPath, $this->getSourceType(), $key );
+ $file = $stash->stashFile( $this->mTempPath, $this->getSourceType() );
$this->mLocalFile = $file;
return $file;
}
@@ -757,21 +751,19 @@ abstract class UploadBase {
/**
* 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 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();
+ public function stashFileGetKey() {
+ return $this->stashFile()->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->stashFileGetKey( $key );
+ public function stashSession() {
+ return $this->stashFileGetKey();
}
/**
diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php
new file mode 100644
index 00000000..ec83f7d3
--- /dev/null
+++ b/includes/upload/UploadFromChunks.php
@@ -0,0 +1,276 @@
+<?php
+/**
+ * Implements uploading from chunks
+ *
+ * @ingroup Upload
+ * @author Michael Dale
+ */
+class UploadFromChunks extends UploadFromFile {
+ protected $mOffset, $mChunkIndex, $mFileKey, $mVirtualTempPath;
+
+ /**
+ * Setup local pointers to stash, repo and user ( similar to UploadFromStash )
+ *
+ * @param $user User
+ * @param $stash UploadStash
+ * @param $repo FileRepo
+ */
+ 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 {
+ if( $user ) {
+ wfDebug( __METHOD__ . " creating new UploadFromChunks instance for " . $user->getId() . "\n" );
+ } else {
+ wfDebug( __METHOD__ . " creating new UploadFromChunks instance with no user\n" );
+ }
+ $this->stash = new UploadStash( $this->repo, $this->user );
+ }
+
+ return true;
+ }
+ /**
+ * Calls the parent stashFile and updates the uploadsession table to handle "chunks"
+ *
+ * @return UploadStashFile stashed file
+ */
+ public function stashFile() {
+ // Stash file is the called on creating a new chunk session:
+ $this->mChunkIndex = 0;
+ $this->mOffset = 0;
+ // Create a local stash target
+ $this->mLocalFile = parent::stashFile();
+ // Update the initial file offset ( based on file size )
+ $this->mOffset = $this->mLocalFile->getSize();
+ $this->mFileKey = $this->mLocalFile->getFileKey();
+
+ // Output a copy of this first to chunk 0 location:
+ $status = $this->outputChunk( $this->mLocalFile->getPath() );
+
+ // Update db table to reflect initial "chunk" state
+ $this->updateChunkStatus();
+ return $this->mLocalFile;
+ }
+
+ /**
+ * Continue chunk uploading
+ */
+ public function continueChunks( $name, $key, $webRequestUpload ) {
+ $this->mFileKey = $key;
+ $this->mUpload = $webRequestUpload;
+ // Get the chunk status form the db:
+ $this->getChunkStatus();
+
+ $metadata = $this->stash->getMetadata( $key );
+ $this->initializePathInfo( $name,
+ $this->getRealPath( $metadata['us_path'] ),
+ $metadata['us_size'],
+ false
+ );
+ }
+
+ /**
+ * Append the final chunk and ready file for parent::performUpload()
+ * @return FileRepoStatus
+ */
+ public function concatenateChunks() {
+ wfDebug( __METHOD__ . " concatenate {$this->mChunkIndex} chunks:" .
+ $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" );
+
+ // Concatenate all the chunks to mVirtualTempPath
+ $fileList = Array();
+ // The first chunk is stored at the mVirtualTempPath path so we start on "chunk 1"
+ for( $i = 0; $i <= $this->getChunkIndex(); $i++ ){
+ $fileList[] = $this->getVirtualChunkLocation( $i );
+ }
+
+ // Get the file extension from the last chunk
+ $ext = FileBackend::extensionFromPath( $this->mVirtualTempPath );
+ // Get a 0-byte temp file to perform the concatenation at
+ $tmpFile = TempFSFile::factory( 'chunkedupload_', $ext );
+ $tmpPath = $tmpFile
+ ? $tmpFile->getPath()
+ : false; // fail in concatenate()
+ // Concatenate the chunks at the temp file
+ $status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE );
+ if( !$status->isOk() ){
+ return $status;
+ }
+ // Update the mTempPath and mLocalFile
+ // ( for FileUpload or normal Stash to take over )
+ $this->mTempPath = $tmpPath; // file system path
+ $this->mLocalFile = parent::stashFile();
+
+ return $status;
+ }
+
+ /**
+ * Perform the upload, then remove the temp copy afterward
+ * @param $comment string
+ * @param $pageText string
+ * @param $watch bool
+ * @param $user User
+ * @return Status
+ */
+ public function performUpload( $comment, $pageText, $watch, $user ) {
+ $rv = parent::performUpload( $comment, $pageText, $watch, $user );
+ return $rv;
+ }
+
+ /**
+ * Returns the virtual chunk location:
+ * @param unknown_type $index
+ */
+ function getVirtualChunkLocation( $index ){
+ return $this->repo->getVirtualUrl( 'temp' ) .
+ '/' .
+ $this->repo->getHashPath(
+ $this->getChunkFileKey( $index )
+ ) .
+ $this->getChunkFileKey( $index );
+ }
+ /**
+ * Add a chunk to the temporary directory
+ *
+ * @param $chunkPath path to temporary chunk file
+ * @param $chunkSize size of the current chunk
+ * @param $offset offset of current chunk ( mutch match database chunk offset )
+ * @return Status
+ */
+ public function addChunk( $chunkPath, $chunkSize, $offset ) {
+ // Get the offset before we add the chunk to the file system
+ $preAppendOffset = $this->getOffset();
+
+ if ( $preAppendOffset + $chunkSize > $this->getMaxUploadSize()) {
+ $status = Status::newFatal( 'file-too-large' );
+ } else {
+ // Make sure the client is uploading the correct chunk with a matching offset.
+ if ( $preAppendOffset == $offset ) {
+ // Update local chunk index for the current chunk
+ $this->mChunkIndex++;
+ $status = $this->outputChunk( $chunkPath );
+ if( $status->isGood() ){
+ // Update local offset:
+ $this->mOffset = $preAppendOffset + $chunkSize;
+ // Update chunk table status db
+ $this->updateChunkStatus();
+ }
+ } else {
+ $status = Status::newFatal( 'invalid-chunk-offset' );
+ }
+ }
+ return $status;
+ }
+
+ /**
+ * Update the chunk db table with the current status:
+ */
+ private function updateChunkStatus(){
+ wfDebug( __METHOD__ . " update chunk status for {$this->mFileKey} offset:" .
+ $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" );
+
+ $dbw = $this->repo->getMasterDb();
+ $dbw->update(
+ 'uploadstash',
+ array(
+ 'us_status' => 'chunks',
+ 'us_chunk_inx' => $this->getChunkIndex(),
+ 'us_size' => $this->getOffset()
+ ),
+ array( 'us_key' => $this->mFileKey ),
+ __METHOD__
+ );
+ }
+ /**
+ * Get the chunk db state and populate update relevant local values
+ */
+ private function getChunkStatus(){
+ // get Master db to avoid race conditions.
+ // Otherwise, if chunk upload time < replag there will be spurious errors
+ $dbw = $this->repo->getMasterDb();
+ $row = $dbw->selectRow(
+ 'uploadstash',
+ array(
+ 'us_chunk_inx',
+ 'us_size',
+ 'us_path',
+ ),
+ array( 'us_key' => $this->mFileKey ),
+ __METHOD__
+ );
+ // Handle result:
+ if ( $row ) {
+ $this->mChunkIndex = $row->us_chunk_inx;
+ $this->mOffset = $row->us_size;
+ $this->mVirtualTempPath = $row->us_path;
+ }
+ }
+ /**
+ * Get the current Chunk index
+ * @return Integer index of the current chunk
+ */
+ private function getChunkIndex(){
+ if( $this->mChunkIndex !== null ){
+ return $this->mChunkIndex;
+ }
+ return 0;
+ }
+
+ /**
+ * Gets the current offset in fromt the stashedupload table
+ * @return Integer current byte offset of the chunk file set
+ */
+ private function getOffset(){
+ if ( $this->mOffset !== null ){
+ return $this->mOffset;
+ }
+ return 0;
+ }
+
+ /**
+ * Output the chunk to disk
+ *
+ * @param $chunkPath string
+ */
+ private function outputChunk( $chunkPath ){
+ // Key is fileKey + chunk index
+ $fileKey = $this->getChunkFileKey();
+
+ // Store the chunk per its indexed fileKey:
+ $hashPath = $this->repo->getHashPath( $fileKey );
+ $storeStatus = $this->repo->store( $chunkPath, 'temp', "$hashPath$fileKey" );
+
+ // Check for error in stashing the chunk:
+ if ( ! $storeStatus->isOK() ) {
+ $error = $storeStatus->getErrorsArray();
+ $error = reset( $error );
+ if ( ! count( $error ) ) {
+ $error = $storeStatus->getWarningsArray();
+ $error = reset( $error );
+ if ( ! count( $error ) ) {
+ $error = array( 'unknown', 'no error recorded' );
+ }
+ }
+ throw new UploadChunkFileException( "error storing file in '$chunkPath': " . implode( '; ', $error ) );
+ }
+ return $storeStatus;
+ }
+ private function getChunkFileKey( $index = null ){
+ if( $index === null ){
+ $index = $this->getChunkIndex();
+ }
+ return $this->mFileKey . '.' . $index ;
+ }
+}
+
+class UploadChunkZeroLengthFileException extends MWException {};
+class UploadChunkFileException extends MWException {};
diff --git a/includes/upload/UploadFromFile.php b/includes/upload/UploadFromFile.php
index c2ab6467..23ec2ef4 100644
--- a/includes/upload/UploadFromFile.php
+++ b/includes/upload/UploadFromFile.php
@@ -2,11 +2,9 @@
/**
* Implements regular file uploads
*
- * @file
- * @ingroup upload
+ * @ingroup Upload
* @author Bryan Tong Minh
*/
-
class UploadFromFile extends UploadBase {
/**
@@ -75,12 +73,4 @@ class UploadFromFile extends UploadBase {
return parent::verifyUpload();
}
-
- /**
- * Get the path to the file underlying the upload
- * @return String path to file
- */
- public function getFileTempname() {
- return $this->mUpload->getTempname();
- }
}
diff --git a/includes/upload/UploadFromStash.php b/includes/upload/UploadFromStash.php
index f34f156d..f7589bd2 100644
--- a/includes/upload/UploadFromStash.php
+++ b/includes/upload/UploadFromStash.php
@@ -2,11 +2,9 @@
/**
* Implements uploading from previously stored file.
*
- * @file
- * @ingroup upload
+ * @ingroup Upload
* @author Bryan Tong Minh
*/
-
class UploadFromStash extends UploadBase {
protected $mFileKey, $mVirtualTempPath, $mFileProps, $mSourceType;
diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php
index 8178988f..da772fe2 100644
--- a/includes/upload/UploadFromUrl.php
+++ b/includes/upload/UploadFromUrl.php
@@ -2,27 +2,28 @@
/**
* Implements uploading from a HTTP resource.
*
- * @file
- * @ingroup upload
+ * @ingroup Upload
* @author Bryan Tong Minh
* @author Michael Dale
*/
-
class UploadFromUrl extends UploadBase {
protected $mAsync, $mUrl;
protected $mIgnoreWarnings = true;
- protected $mTempPath;
+ protected $mTempPath, $mTmpHandle;
/**
* 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
+ *
+ * @return bool
*/
public static function isAllowed( $user ) {
- if ( !$user->isAllowed( 'upload_by_url' ) )
+ if ( !$user->isAllowed( 'upload_by_url' ) ) {
return 'upload_by_url';
+ }
return parent::isAllowed( $user );
}
@@ -64,8 +65,9 @@ class UploadFromUrl extends UploadBase {
*/
public function initializeFromRequest( &$request ) {
$desiredDestName = $request->getText( 'wpDestFile' );
- if ( !$desiredDestName )
+ if ( !$desiredDestName ) {
$desiredDestName = $request->getText( 'wpUploadFileURL' );
+ }
return $this->initialize(
$desiredDestName,
trim( $request->getVal( 'wpUploadFileURL' ) ),
@@ -75,6 +77,7 @@ class UploadFromUrl extends UploadBase {
/**
* @param $request WebRequest object
+ * @return bool
*/
public static function isValidRequest( $request ) {
global $wgUser;
@@ -85,8 +88,14 @@ class UploadFromUrl extends UploadBase {
&& $wgUser->isAllowed( 'upload_by_url' );
}
+ /**
+ * @return string
+ */
public function getSourceType() { return 'url'; }
+ /**
+ * @return Status
+ */
public function fetchFile() {
if ( !Http::isValidURI( $this->mUrl ) ) {
return Status::newFatal( 'http-invalid-url' );
@@ -130,6 +139,7 @@ class UploadFromUrl extends UploadBase {
/**
* Download the file, save it to the temporary file and update the file
* size and set $mRemoveTempFile to true.
+ * @return Status
*/
protected function reallyFetchFile() {
if ( $this->mTempPath === false ) {
@@ -209,9 +219,7 @@ class UploadFromUrl extends UploadBase {
if ( $this->mAsync ) {
$sessionKey = $this->insertJob( $comment, $pageText, $watch, $user );
- $status = new Status;
- $status->error( 'async', $sessionKey );
- return $status;
+ return Status::newFatal( 'async', $sessionKey );
}
return parent::performUpload( $comment, $pageText, $watch, $user );
diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php
index 217b84dc..ad153d2f 100644
--- a/includes/upload/UploadStash.php
+++ b/includes/upload/UploadStash.php
@@ -16,6 +16,8 @@
* 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
+ *
+ * @ingroup Upload
*/
class UploadStash {
@@ -48,7 +50,7 @@ class UploadStash {
*
* @param $repo FileRepo
*/
- public function __construct( $repo, $user = null ) {
+ public function __construct( FileRepo $repo, $user = null ) {
// this might change based on wiki's configuration.
$this->repo = $repo;
@@ -106,10 +108,7 @@ class UploadStash {
// fetch fileprops
$path = $this->fileMetadata[$key]['us_path'];
- if ( $this->repo->isVirtualUrl( $path ) ) {
- $path = $this->repo->resolveVirtualUrl( $path );
- }
- $this->fileProps[$key] = File::getPropsFromPath( $path );
+ $this->fileProps[$key] = $this->repo->getFileProps( $path );
}
if ( ! $this->files[$key]->exists() ) {
@@ -163,7 +162,7 @@ class UploadStash {
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 = FSFile::getPropsFromPath( $path );
wfDebug( __METHOD__ . " stashing file at '$path'\n" );
// we will be initializing from some tmpnam files that don't have extensions.
@@ -215,7 +214,8 @@ class UploadStash {
$error = array( 'unknown', 'no error recorded' );
}
}
- throw new UploadStashFileException( "error storing file in '$path': " . implode( '; ', $error ) );
+ // at this point, $error should contain the single "most important" error, plus any parameters.
+ throw new UploadStashFileException( "Error storing file in '$path': " . wfMessage( $error )->text() );
}
$stashPath = $storeStatus->value;
@@ -233,7 +233,7 @@ class UploadStash {
'us_user' => $this->userId,
'us_key' => $key,
'us_orig_path' => $path,
- 'us_path' => $stashPath,
+ 'us_path' => $stashPath, // virtual URL
'us_size' => $fileProps['size'],
'us_sha1' => $fileProps['sha1'],
'us_mime' => $fileProps['mime'],
@@ -334,13 +334,13 @@ class UploadStash {
$dbw = $this->repo->getMasterDb();
// this gets its own transaction since it's called serially by the cleanupUploadStash maintenance script
- $dbw->begin();
+ $dbw->begin( __METHOD__ );
$dbw->delete(
'uploadstash',
array( 'us_key' => $key ),
__METHOD__
);
- $dbw->commit();
+ $dbw->commit( __METHOD__ );
// TODO: look into UnregisteredLocalFile and find out why the rv here is sometimes wrong (false when file was removed)
// for now, ignore.
@@ -475,7 +475,7 @@ class UploadStashFile extends UnregisteredLocalFile {
* A LocalFile wrapper around a file that has been temporarily stashed, so we can do things like create thumbnails for it
* Arguably UnregisteredLocalFile should be handling its own file repo but that class is a bit retarded currently
*
- * @param $repo FSRepo: repository where we should find the path
+ * @param $repo FileRepo: repository where we should find the path
* @param $path String: path to file
* @param $key String: key to store the path and any stashed data under
* @throws UploadStashBadPathException