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/