summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2009-06-10 13:00:47 +0200
committerPierre Schmitz <pierre@archlinux.de>2009-06-10 13:00:47 +0200
commit72e90545454c0e014318fa3c81658e035aac58c1 (patch)
tree9212e3f46868989c4d57ae9a5c8a1a80e4dc0702
parent565a0ccc371ec1a2a0e9b39487cbac18e6f60e25 (diff)
applying patch to version 1.15.0
-rw-r--r--AdminSettings.sample5
-rw-r--r--CREDITS14
-rw-r--r--HISTORY592
-rw-r--r--RELEASE-NOTES981
-rw-r--r--api.php49
-rw-r--r--config/index.php159
-rw-r--r--docs/hooks.txt28
-rw-r--r--img_auth.php6
-rw-r--r--includes/AjaxResponse.php3
-rw-r--r--includes/Article.php253
-rw-r--r--includes/AutoLoader.php26
-rw-r--r--includes/BacklinkCache.php232
-rw-r--r--includes/Block.php3
-rw-r--r--includes/Category.php6
-rw-r--r--includes/CategoryPage.php22
-rw-r--r--includes/Categoryfinder.php8
-rw-r--r--includes/ChangeTags.php183
-rw-r--r--includes/ChangesFeed.php6
-rw-r--r--includes/ChangesList.php173
-rw-r--r--includes/DefaultSettings.php199
-rw-r--r--includes/EditPage.php283
-rw-r--r--includes/EnotifNotifyJob.php3
-rw-r--r--includes/Exception.php93
-rw-r--r--includes/Exif.php32
-rw-r--r--includes/Export.php141
-rw-r--r--includes/ExternalStore.php2
-rw-r--r--includes/FileDeleteForm.php5
-rw-r--r--includes/ForkController.php160
-rw-r--r--includes/GlobalFunctions.php208
-rw-r--r--includes/HTMLCacheUpdate.php163
-rw-r--r--includes/HTMLFileCache.php11
-rw-r--r--includes/ImageGallery.php2
-rw-r--r--includes/ImagePage.php84
-rw-r--r--includes/Import.php9
-rw-r--r--includes/Linker.php63
-rw-r--r--includes/LinksUpdate.php60
-rw-r--r--includes/LogEventsList.php150
-rw-r--r--includes/LogPage.php74
-rw-r--r--includes/MagicWord.php5
-rw-r--r--includes/MessageCache.php9
-rw-r--r--includes/MimeMagic.php16
-rw-r--r--includes/OutputHandler.php20
-rw-r--r--includes/OutputPage.php74
-rw-r--r--includes/PageHistory.php143
-rw-r--r--includes/Pager.php35
-rw-r--r--includes/PatrolLog.php27
-rw-r--r--includes/PrefixSearch.php2
-rw-r--r--includes/Profiler.php26
-rw-r--r--includes/ProfilerSimple.php4
-rw-r--r--includes/ProfilerSimpleTrace.php73
-rw-r--r--includes/ProtectionForm.php5
-rw-r--r--includes/QueryPage.php10
-rw-r--r--includes/RawPage.php11
-rw-r--r--includes/RecentChange.php64
-rw-r--r--includes/RefreshLinksJob.php22
-rw-r--r--includes/Revision.php33
-rw-r--r--includes/SearchEngine.php15
-rw-r--r--includes/SearchIBM_DB2.php247
-rw-r--r--includes/SearchPostgres.php6
-rw-r--r--includes/Setup.php4
-rw-r--r--includes/SiteStats.php4
-rw-r--r--includes/Skin.php164
-rw-r--r--includes/SkinTemplate.php40
-rw-r--r--includes/SpecialPage.php176
-rw-r--r--includes/SquidUpdate.php12
-rw-r--r--includes/StreamFile.php1
-rw-r--r--includes/StubObject.php2
-rw-r--r--includes/Title.php551
-rw-r--r--includes/UploadBase.php867
-rw-r--r--includes/UploadFromStash.php58
-rw-r--r--includes/UploadFromUpload.php20
-rw-r--r--includes/UploadFromUrl.php92
-rw-r--r--includes/User.php67
-rw-r--r--includes/UserArray.php11
-rw-r--r--includes/UserMailer.php114
-rw-r--r--includes/WatchedItem.php35
-rw-r--r--includes/WatchlistEditor.php8
-rw-r--r--includes/WebRequest.php3
-rw-r--r--includes/Wiki.php17
-rw-r--r--includes/WikiError.php10
-rw-r--r--includes/Xml.php33
-rw-r--r--includes/ZhConversion.php28274
-rw-r--r--includes/api/ApiBase.php245
-rw-r--r--includes/api/ApiBlock.php9
-rw-r--r--includes/api/ApiDelete.php22
-rw-r--r--includes/api/ApiDisabled.php6
-rw-r--r--includes/api/ApiEditPage.php84
-rw-r--r--includes/api/ApiEmailUser.php11
-rw-r--r--includes/api/ApiFeedWatchlist.php4
-rw-r--r--includes/api/ApiFormatBase.php82
-rw-r--r--includes/api/ApiFormatJson.php4
-rw-r--r--includes/api/ApiFormatJson_json.php1480
-rw-r--r--includes/api/ApiFormatRaw.php71
-rw-r--r--includes/api/ApiFormatWddx.php14
-rw-r--r--includes/api/ApiFormatXml.php15
-rw-r--r--includes/api/ApiHelp.php6
-rw-r--r--includes/api/ApiImport.php179
-rw-r--r--includes/api/ApiLogin.php10
-rw-r--r--includes/api/ApiLogout.php6
-rw-r--r--includes/api/ApiMain.php71
-rw-r--r--includes/api/ApiMove.php51
-rw-r--r--includes/api/ApiOpenSearch.php10
-rw-r--r--includes/api/ApiPageSet.php146
-rw-r--r--includes/api/ApiParamInfo.php27
-rw-r--r--includes/api/ApiParse.php15
-rw-r--r--includes/api/ApiPatrol.php11
-rw-r--r--includes/api/ApiProtect.php21
-rw-r--r--includes/api/ApiPurge.php11
-rw-r--r--includes/api/ApiQuery.php118
-rw-r--r--includes/api/ApiQueryAllCategories.php20
-rw-r--r--includes/api/ApiQueryAllLinks.php24
-rw-r--r--includes/api/ApiQueryAllUsers.php33
-rw-r--r--includes/api/ApiQueryAllimages.php19
-rw-r--r--includes/api/ApiQueryAllmessages.php23
-rw-r--r--includes/api/ApiQueryAllpages.php18
-rw-r--r--includes/api/ApiQueryBacklinks.php108
-rw-r--r--includes/api/ApiQueryBase.php145
-rw-r--r--includes/api/ApiQueryBlocks.php16
-rw-r--r--includes/api/ApiQueryCategories.php43
-rw-r--r--includes/api/ApiQueryCategoryInfo.php43
-rw-r--r--includes/api/ApiQueryCategoryMembers.php25
-rw-r--r--includes/api/ApiQueryDeletedrevs.php183
-rw-r--r--includes/api/ApiQueryDuplicateFiles.php28
-rw-r--r--includes/api/ApiQueryExtLinksUsage.php21
-rw-r--r--includes/api/ApiQueryExternalLinks.php26
-rw-r--r--includes/api/ApiQueryImageInfo.php148
-rw-r--r--includes/api/ApiQueryImages.php28
-rw-r--r--includes/api/ApiQueryInfo.php555
-rw-r--r--includes/api/ApiQueryLangLinks.php26
-rw-r--r--includes/api/ApiQueryLinks.php29
-rw-r--r--includes/api/ApiQueryLogEvents.php85
-rw-r--r--includes/api/ApiQueryProtectedTitles.php191
-rw-r--r--includes/api/ApiQueryRandom.php26
-rw-r--r--includes/api/ApiQueryRecentChanges.php45
-rw-r--r--includes/api/ApiQueryRevisions.php155
-rw-r--r--includes/api/ApiQuerySearch.php25
-rw-r--r--includes/api/ApiQuerySiteinfo.php133
-rw-r--r--includes/api/ApiQueryUserContributions.php101
-rw-r--r--includes/api/ApiQueryUserInfo.php19
-rw-r--r--includes/api/ApiQueryUsers.php121
-rw-r--r--includes/api/ApiQueryWatchlist.php31
-rw-r--r--includes/api/ApiQueryWatchlistRaw.php18
-rw-r--r--includes/api/ApiResult.php144
-rw-r--r--includes/api/ApiRollback.php17
-rw-r--r--includes/api/ApiUnblock.php9
-rw-r--r--includes/api/ApiUndelete.php13
-rw-r--r--includes/api/ApiWatch.php10
-rw-r--r--includes/db/Database.php248
-rw-r--r--includes/db/DatabaseIbm_db2.php1796
-rw-r--r--includes/db/DatabasePostgres.php65
-rw-r--r--includes/db/DatabaseSqlite.php238
-rw-r--r--includes/db/LBFactory.php26
-rw-r--r--includes/db/LoadBalancer.php2
-rw-r--r--includes/diff/DifferenceEngine.php211
-rw-r--r--includes/diff/HTMLDiff.php4
-rw-r--r--includes/filerepo/ArchivedFile.php12
-rw-r--r--includes/filerepo/File.php28
-rw-r--r--includes/filerepo/FileRepo.php80
-rw-r--r--includes/filerepo/ForeignAPIFile.php26
-rw-r--r--includes/filerepo/ForeignAPIRepo.php13
-rw-r--r--includes/filerepo/ForeignDBFile.php2
-rw-r--r--includes/filerepo/LocalFile.php4
-rw-r--r--includes/filerepo/LocalRepo.php51
-rw-r--r--includes/media/Bitmap.php4
-rw-r--r--includes/media/Tiff.php33
-rw-r--r--includes/mime.types16
-rw-r--r--includes/parser/CoreParserFunctions.php231
-rw-r--r--includes/parser/DateFormatter.php122
-rw-r--r--includes/parser/Parser.php360
-rw-r--r--includes/parser/ParserCache.php26
-rw-r--r--includes/parser/ParserOptions.php17
-rw-r--r--includes/parser/ParserOutput.php10
-rw-r--r--includes/parser/Preprocessor_DOM.php82
-rw-r--r--includes/parser/Preprocessor_Hash.php41
-rw-r--r--includes/parser/Tidy.php170
-rw-r--r--includes/specials/SpecialAllmessages.php16
-rw-r--r--includes/specials/SpecialAllpages.php20
-rw-r--r--includes/specials/SpecialBlankpage.php2
-rw-r--r--includes/specials/SpecialBlockip.php137
-rw-r--r--includes/specials/SpecialBooksources.php2
-rw-r--r--includes/specials/SpecialCategories.php2
-rw-r--r--includes/specials/SpecialConfirmemail.php4
-rw-r--r--includes/specials/SpecialContributions.php144
-rw-r--r--includes/specials/SpecialDeletedContributions.php20
-rw-r--r--includes/specials/SpecialEmailuser.php2
-rw-r--r--includes/specials/SpecialExport.php551
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php5
-rw-r--r--includes/specials/SpecialImport.php52
-rw-r--r--includes/specials/SpecialIpblocklist.php27
-rw-r--r--includes/specials/SpecialLinkSearch.php4
-rw-r--r--includes/specials/SpecialListUserRestrictions.php3
-rw-r--r--includes/specials/SpecialListfiles.php24
-rw-r--r--includes/specials/SpecialListgrouprights.php7
-rw-r--r--includes/specials/SpecialListusers.php58
-rw-r--r--includes/specials/SpecialLog.php5
-rw-r--r--includes/specials/SpecialMergeHistory.php27
-rw-r--r--includes/specials/SpecialMovepage.php48
-rw-r--r--includes/specials/SpecialNewpages.php64
-rw-r--r--includes/specials/SpecialPreferences.php183
-rw-r--r--includes/specials/SpecialPrefixindex.php43
-rw-r--r--includes/specials/SpecialProtectedpages.php40
-rw-r--r--includes/specials/SpecialRandompage.php2
-rw-r--r--includes/specials/SpecialRecentchanges.php61
-rw-r--r--includes/specials/SpecialRecentchangeslinked.php22
-rw-r--r--includes/specials/SpecialRestrictUser.php3
-rw-r--r--includes/specials/SpecialRevisiondelete.php428
-rw-r--r--includes/specials/SpecialSearch.php85
-rw-r--r--includes/specials/SpecialSpecialpages.php13
-rw-r--r--includes/specials/SpecialTags.php73
-rw-r--r--includes/specials/SpecialUndelete.php38
-rw-r--r--includes/specials/SpecialUnlockdb.php3
-rw-r--r--includes/specials/SpecialUnusedimages.php12
-rw-r--r--includes/specials/SpecialUpload.php106
-rw-r--r--includes/specials/SpecialUserlogin.php22
-rw-r--r--includes/specials/SpecialUserrights.php17
-rw-r--r--includes/specials/SpecialVersion.php2
-rw-r--r--includes/specials/SpecialWantedfiles.php23
-rw-r--r--includes/specials/SpecialWantedpages.php8
-rw-r--r--includes/specials/SpecialWantedtemplates.php4
-rw-r--r--includes/specials/SpecialWatchlist.php32
-rw-r--r--includes/specials/SpecialWhatlinkshere.php5
-rw-r--r--includes/templates/NoLocalSettings.php13
-rw-r--r--includes/templates/Userlogin.php2
-rw-r--r--includes/zhtable/Makefile2
-rw-r--r--includes/zhtable/Makefile.py438
-rw-r--r--includes/zhtable/simp2trad.manual157
-rw-r--r--includes/zhtable/simp2trad_supp_set.manual2
-rw-r--r--includes/zhtable/simpphrases.manual2451
-rw-r--r--includes/zhtable/simpphrases_exclude.manual18
-rw-r--r--includes/zhtable/toCN.manual42
-rw-r--r--includes/zhtable/toHK.manual2002
-rw-r--r--includes/zhtable/toSG.manual2
-rw-r--r--includes/zhtable/toSimp.manual100
-rw-r--r--includes/zhtable/toTW.manual40
-rw-r--r--includes/zhtable/toTrad.manual25
-rw-r--r--includes/zhtable/trad2simp.manual253
-rw-r--r--includes/zhtable/trad2simp_supp_set.manual2
-rw-r--r--includes/zhtable/tradphrases.manual1411
-rw-r--r--includes/zhtable/tradphrases_exclude.manual106
-rw-r--r--index.php8
-rw-r--r--install-utils.inc7
-rw-r--r--languages/Language.php96
-rw-r--r--languages/LanguageConverter.php280
-rw-r--r--languages/Names.php23
-rw-r--r--languages/classes/LanguageGan.php168
-rw-r--r--languages/classes/LanguageGv.php27
-rw-r--r--languages/classes/LanguageKk.php4
-rw-r--r--languages/classes/LanguageKu.php4
-rw-r--r--languages/classes/LanguageSr.php4
-rw-r--r--languages/classes/LanguageZh.php47
-rw-r--r--languages/messages/MessagesAb.php3
-rw-r--r--languages/messages/MessagesAce.php279
-rw-r--r--languages/messages/MessagesAf.php267
-rw-r--r--languages/messages/MessagesAk.php3
-rw-r--r--languages/messages/MessagesAln.php9
-rw-r--r--languages/messages/MessagesAm.php139
-rw-r--r--languages/messages/MessagesAn.php302
-rw-r--r--languages/messages/MessagesAng.php3
-rw-r--r--languages/messages/MessagesAr.php498
-rw-r--r--languages/messages/MessagesArc.php679
-rw-r--r--languages/messages/MessagesArn.php114
-rw-r--r--languages/messages/MessagesArz.php309
-rw-r--r--languages/messages/MessagesAs.php8
-rw-r--r--languages/messages/MessagesAst.php219
-rw-r--r--languages/messages/MessagesAv.php3
-rw-r--r--languages/messages/MessagesAvk.php508
-rw-r--r--languages/messages/MessagesAy.php36
-rw-r--r--languages/messages/MessagesAz.php19
-rw-r--r--languages/messages/MessagesBa.php6
-rw-r--r--languages/messages/MessagesBar.php713
-rw-r--r--languages/messages/MessagesBat_smg.php602
-rw-r--r--languages/messages/MessagesBcc.php409
-rw-r--r--languages/messages/MessagesBcl.php224
-rw-r--r--languages/messages/MessagesBe.php245
-rw-r--r--languages/messages/MessagesBe_tarask.php699
-rw-r--r--languages/messages/MessagesBg.php473
-rw-r--r--languages/messages/MessagesBh.php3
-rw-r--r--languages/messages/MessagesBi.php3
-rw-r--r--languages/messages/MessagesBm.php3
-rw-r--r--languages/messages/MessagesBn.php467
-rw-r--r--languages/messages/MessagesBo.php3
-rw-r--r--languages/messages/MessagesBpy.php10
-rw-r--r--languages/messages/MessagesBqi.php7
-rw-r--r--languages/messages/MessagesBr.php1099
-rw-r--r--languages/messages/MessagesBs.php649
-rw-r--r--languages/messages/MessagesBug.php5
-rw-r--r--languages/messages/MessagesCa.php440
-rw-r--r--languages/messages/MessagesCbk_zam.php3
-rw-r--r--languages/messages/MessagesCdo.php6
-rw-r--r--languages/messages/MessagesCe.php42
-rw-r--r--languages/messages/MessagesCeb.php5
-rw-r--r--languages/messages/MessagesCh.php15
-rw-r--r--languages/messages/MessagesChr.php3
-rw-r--r--languages/messages/MessagesCo.php3
-rw-r--r--languages/messages/MessagesCrh_cyrl.php76
-rw-r--r--languages/messages/MessagesCrh_latn.php76
-rw-r--r--languages/messages/MessagesCs.php827
-rw-r--r--languages/messages/MessagesCsb.php16
-rw-r--r--languages/messages/MessagesCu.php29
-rw-r--r--languages/messages/MessagesCv.php9
-rw-r--r--languages/messages/MessagesCy.php593
-rw-r--r--languages/messages/MessagesDa.php1546
-rw-r--r--languages/messages/MessagesDe.php453
-rw-r--r--languages/messages/MessagesDe_at.php19
-rw-r--r--languages/messages/MessagesDe_ch.php101
-rw-r--r--languages/messages/MessagesDe_formal.php38
-rw-r--r--languages/messages/MessagesDiq.php555
-rw-r--r--languages/messages/MessagesDsb.php432
-rw-r--r--languages/messages/MessagesDv.php100
-rw-r--r--languages/messages/MessagesDz.php6
-rw-r--r--languages/messages/MessagesEe.php168
-rw-r--r--languages/messages/MessagesEl.php792
-rw-r--r--languages/messages/MessagesEml.php3
-rw-r--r--languages/messages/MessagesEn.php550
-rw-r--r--languages/messages/MessagesEn_gb.php8
-rw-r--r--languages/messages/MessagesEo.php464
-rw-r--r--languages/messages/MessagesEs.php519
-rw-r--r--languages/messages/MessagesEt.php663
-rw-r--r--languages/messages/MessagesEu.php352
-rw-r--r--languages/messages/MessagesExt.php40
-rw-r--r--languages/messages/MessagesFa.php453
-rw-r--r--languages/messages/MessagesFf.php3
-rw-r--r--languages/messages/MessagesFi.php510
-rw-r--r--languages/messages/MessagesFiu_vro.php2154
-rw-r--r--languages/messages/MessagesFj.php3
-rw-r--r--languages/messages/MessagesFo.php5
-rw-r--r--languages/messages/MessagesFr.php1102
-rw-r--r--languages/messages/MessagesFrc.php12
-rw-r--r--languages/messages/MessagesFrp.php3522
-rw-r--r--languages/messages/MessagesFur.php19
-rw-r--r--languages/messages/MessagesFy.php194
-rw-r--r--languages/messages/MessagesGa.php359
-rw-r--r--languages/messages/MessagesGag.php6
-rw-r--r--languages/messages/MessagesGan.php528
-rw-r--r--languages/messages/MessagesGan_hans.php2142
-rw-r--r--languages/messages/MessagesGan_hant.php2204
-rw-r--r--languages/messages/MessagesGd.php324
-rw-r--r--languages/messages/MessagesGl.php664
-rw-r--r--languages/messages/MessagesGlk.php3
-rw-r--r--languages/messages/MessagesGn.php3
-rw-r--r--languages/messages/MessagesGot.php3
-rw-r--r--languages/messages/MessagesGrc.php400
-rw-r--r--languages/messages/MessagesGsw.php423
-rw-r--r--languages/messages/MessagesGu.php925
-rw-r--r--languages/messages/MessagesGv.php246
-rw-r--r--languages/messages/MessagesHak.php23
-rw-r--r--languages/messages/MessagesHaw.php357
-rw-r--r--languages/messages/MessagesHe.php465
-rw-r--r--languages/messages/MessagesHi.php90
-rw-r--r--languages/messages/MessagesHif_deva.php3
-rw-r--r--languages/messages/MessagesHif_latn.php927
-rw-r--r--languages/messages/MessagesHil.php3
-rw-r--r--languages/messages/MessagesHr.php297
-rw-r--r--languages/messages/MessagesHsb.php418
-rw-r--r--languages/messages/MessagesHt.php182
-rw-r--r--languages/messages/MessagesHu.php440
-rw-r--r--languages/messages/MessagesHy.php471
-rw-r--r--languages/messages/MessagesIa.php693
-rw-r--r--languages/messages/MessagesId.php646
-rw-r--r--languages/messages/MessagesIe.php12
-rw-r--r--languages/messages/MessagesIg.php3
-rw-r--r--languages/messages/MessagesIk.php3
-rw-r--r--languages/messages/MessagesIke_cans.php3
-rw-r--r--languages/messages/MessagesIke_latn.php3
-rw-r--r--languages/messages/MessagesIlo.php60
-rw-r--r--languages/messages/MessagesInh.php3
-rw-r--r--languages/messages/MessagesIo.php577
-rw-r--r--languages/messages/MessagesIs.php108
-rw-r--r--languages/messages/MessagesIt.php470
-rw-r--r--languages/messages/MessagesJa.php625
-rw-r--r--languages/messages/MessagesJbo.php33
-rw-r--r--languages/messages/MessagesJut.php11
-rw-r--r--languages/messages/MessagesJv.php104
-rw-r--r--languages/messages/MessagesKa.php459
-rw-r--r--languages/messages/MessagesKaa.php58
-rw-r--r--languages/messages/MessagesKab.php33
-rw-r--r--languages/messages/MessagesKg.php3
-rw-r--r--languages/messages/MessagesKk.php3
-rw-r--r--languages/messages/MessagesKk_arab.php116
-rw-r--r--languages/messages/MessagesKk_cyrl.php106
-rw-r--r--languages/messages/MessagesKk_latn.php92
-rw-r--r--languages/messages/MessagesKl.php112
-rw-r--r--languages/messages/MessagesKm.php132
-rw-r--r--languages/messages/MessagesKn.php85
-rw-r--r--languages/messages/MessagesKo.php506
-rw-r--r--languages/messages/MessagesKri.php38
-rw-r--r--languages/messages/MessagesKrj.php4
-rw-r--r--languages/messages/MessagesKsh.php552
-rw-r--r--languages/messages/MessagesKu_arab.php217
-rw-r--r--languages/messages/MessagesKu_latn.php12
-rw-r--r--languages/messages/MessagesKv.php3
-rw-r--r--languages/messages/MessagesKw.php517
-rw-r--r--languages/messages/MessagesKy.php3
-rw-r--r--languages/messages/MessagesLa.php800
-rw-r--r--languages/messages/MessagesLad.php9
-rw-r--r--languages/messages/MessagesLb.php540
-rw-r--r--languages/messages/MessagesLbe.php3
-rw-r--r--languages/messages/MessagesLez.php6
-rw-r--r--languages/messages/MessagesLfn.php46
-rw-r--r--languages/messages/MessagesLg.php6
-rw-r--r--languages/messages/MessagesLi.php1021
-rw-r--r--languages/messages/MessagesLij.php8
-rw-r--r--languages/messages/MessagesLmo.php6
-rw-r--r--languages/messages/MessagesLn.php5
-rw-r--r--languages/messages/MessagesLo.php111
-rw-r--r--languages/messages/MessagesLoz.php11
-rw-r--r--languages/messages/MessagesLt.php450
-rw-r--r--languages/messages/MessagesLv.php250
-rw-r--r--languages/messages/MessagesLzh.php2473
-rw-r--r--languages/messages/MessagesLzz.php78
-rw-r--r--languages/messages/MessagesMai.php3
-rw-r--r--languages/messages/MessagesMap_bms.php3
-rw-r--r--languages/messages/MessagesMdf.php224
-rw-r--r--languages/messages/MessagesMg.php746
-rw-r--r--languages/messages/MessagesMhr.php537
-rw-r--r--languages/messages/MessagesMi.php3
-rw-r--r--languages/messages/MessagesMk.php491
-rw-r--r--languages/messages/MessagesMl.php527
-rw-r--r--languages/messages/MessagesMn.php20
-rw-r--r--languages/messages/MessagesMo.php3
-rw-r--r--languages/messages/MessagesMr.php312
-rw-r--r--languages/messages/MessagesMs.php425
-rw-r--r--languages/messages/MessagesMt.php585
-rw-r--r--languages/messages/MessagesMwl.php711
-rw-r--r--languages/messages/MessagesMy.php5
-rw-r--r--languages/messages/MessagesMyv.php692
-rw-r--r--languages/messages/MessagesMzn.php304
-rw-r--r--languages/messages/MessagesNa.php3
-rw-r--r--languages/messages/MessagesNah.php51
-rw-r--r--languages/messages/MessagesNan.php8
-rw-r--r--languages/messages/MessagesNap.php14
-rw-r--r--languages/messages/MessagesNds.php440
-rw-r--r--languages/messages/MessagesNds_nl.php1156
-rw-r--r--languages/messages/MessagesNe.php4
-rw-r--r--languages/messages/MessagesNew.php4
-rw-r--r--languages/messages/MessagesNiu.php3
-rw-r--r--languages/messages/MessagesNl.php481
-rw-r--r--languages/messages/MessagesNn.php584
-rw-r--r--languages/messages/MessagesNo.php416
-rw-r--r--languages/messages/MessagesNov.php118
-rw-r--r--languages/messages/MessagesNso.php20
-rw-r--r--languages/messages/MessagesNv.php3
-rw-r--r--languages/messages/MessagesNy.php3
-rw-r--r--languages/messages/MessagesOc.php750
-rw-r--r--languages/messages/MessagesOm.php3
-rw-r--r--languages/messages/MessagesOr.php3
-rw-r--r--languages/messages/MessagesOs.php424
-rw-r--r--languages/messages/MessagesPa.php31
-rw-r--r--languages/messages/MessagesPag.php8
-rw-r--r--languages/messages/MessagesPam.php72
-rw-r--r--languages/messages/MessagesPap.php38
-rw-r--r--languages/messages/MessagesPdc.php112
-rw-r--r--languages/messages/MessagesPdt.php3
-rw-r--r--languages/messages/MessagesPfl.php645
-rw-r--r--languages/messages/MessagesPi.php3
-rw-r--r--languages/messages/MessagesPih.php3
-rw-r--r--languages/messages/MessagesPl.php681
-rw-r--r--languages/messages/MessagesPms.php37
-rw-r--r--languages/messages/MessagesPnb.php335
-rw-r--r--languages/messages/MessagesPnt.php347
-rw-r--r--languages/messages/MessagesPs.php200
-rw-r--r--languages/messages/MessagesPt.php439
-rw-r--r--languages/messages/MessagesPt_br.php437
-rw-r--r--languages/messages/MessagesQqq.php2933
-rw-r--r--languages/messages/MessagesQu.php438
-rw-r--r--languages/messages/MessagesRif.php10
-rw-r--r--languages/messages/MessagesRm.php37
-rw-r--r--languages/messages/MessagesRmy.php3
-rw-r--r--languages/messages/MessagesRo.php744
-rw-r--r--languages/messages/MessagesRoa_rup.php3
-rw-r--r--languages/messages/MessagesRoa_tara.php1048
-rw-r--r--languages/messages/MessagesRu.php450
-rw-r--r--languages/messages/MessagesRuq_cyrl.php3
-rw-r--r--languages/messages/MessagesRuq_latn.php3
-rw-r--r--languages/messages/MessagesSa.php221
-rw-r--r--languages/messages/MessagesSah.php426
-rw-r--r--languages/messages/MessagesSc.php1528
-rw-r--r--languages/messages/MessagesScn.php136
-rw-r--r--languages/messages/MessagesSco.php35
-rw-r--r--languages/messages/MessagesSd.php87
-rw-r--r--languages/messages/MessagesSdc.php36
-rw-r--r--languages/messages/MessagesSe.php8
-rw-r--r--languages/messages/MessagesSei.php3
-rw-r--r--languages/messages/MessagesSg.php11
-rw-r--r--languages/messages/MessagesSh.php706
-rw-r--r--languages/messages/MessagesShi.php3
-rw-r--r--languages/messages/MessagesSi.php196
-rw-r--r--languages/messages/MessagesSk.php515
-rw-r--r--languages/messages/MessagesSl.php840
-rw-r--r--languages/messages/MessagesSm.php3
-rw-r--r--languages/messages/MessagesSma.php6
-rw-r--r--languages/messages/MessagesSn.php5
-rw-r--r--languages/messages/MessagesSo.php45
-rw-r--r--languages/messages/MessagesSq.php292
-rw-r--r--languages/messages/MessagesSr.php3
-rw-r--r--languages/messages/MessagesSr_ec.php755
-rw-r--r--languages/messages/MessagesSr_el.php33
-rw-r--r--languages/messages/MessagesSrn.php52
-rw-r--r--languages/messages/MessagesSs.php3
-rw-r--r--languages/messages/MessagesSt.php3
-rw-r--r--languages/messages/MessagesStq.php280
-rw-r--r--languages/messages/MessagesSu.php107
-rw-r--r--languages/messages/MessagesSv.php620
-rw-r--r--languages/messages/MessagesSw.php259
-rw-r--r--languages/messages/MessagesSzl.php115
-rw-r--r--languages/messages/MessagesTa.php537
-rw-r--r--languages/messages/MessagesTcy.php6
-rw-r--r--languages/messages/MessagesTe.php331
-rw-r--r--languages/messages/MessagesTet.php11
-rw-r--r--languages/messages/MessagesTg_cyrl.php93
-rw-r--r--languages/messages/MessagesTh.php618
-rw-r--r--languages/messages/MessagesTi.php3
-rw-r--r--languages/messages/MessagesTk.php1191
-rw-r--r--languages/messages/MessagesTl.php531
-rw-r--r--languages/messages/MessagesTn.php3
-rw-r--r--languages/messages/MessagesTo.php11
-rw-r--r--languages/messages/MessagesTokipona.php3
-rw-r--r--languages/messages/MessagesTpi.php3
-rw-r--r--languages/messages/MessagesTr.php528
-rw-r--r--languages/messages/MessagesTs.php5
-rw-r--r--languages/messages/MessagesTt_cyrl.php186
-rw-r--r--languages/messages/MessagesTt_latn.php3
-rw-r--r--languages/messages/MessagesTy.php3
-rw-r--r--languages/messages/MessagesTyv.php3
-rw-r--r--languages/messages/MessagesUdm.php3
-rw-r--r--languages/messages/MessagesUg.php157
-rw-r--r--languages/messages/MessagesUg_latn.php206
-rw-r--r--languages/messages/MessagesUk.php717
-rw-r--r--languages/messages/MessagesUr.php195
-rw-r--r--languages/messages/MessagesUz.php5
-rw-r--r--languages/messages/MessagesVe.php3
-rw-r--r--languages/messages/MessagesVec.php669
-rw-r--r--languages/messages/MessagesVep.php2656
-rw-r--r--languages/messages/MessagesVi.php631
-rw-r--r--languages/messages/MessagesVls.php3
-rw-r--r--languages/messages/MessagesVo.php281
-rw-r--r--languages/messages/MessagesVro.php2189
-rw-r--r--languages/messages/MessagesWa.php137
-rw-r--r--languages/messages/MessagesWar.php266
-rw-r--r--languages/messages/MessagesWo.php1184
-rw-r--r--languages/messages/MessagesWuu.php38
-rw-r--r--languages/messages/MessagesXal.php255
-rw-r--r--languages/messages/MessagesXh.php3
-rw-r--r--languages/messages/MessagesXmf.php5
-rw-r--r--languages/messages/MessagesYi.php641
-rw-r--r--languages/messages/MessagesYo.php76
-rw-r--r--languages/messages/MessagesYue.php558
-rw-r--r--languages/messages/MessagesZa.php60
-rw-r--r--languages/messages/MessagesZea.php13
-rw-r--r--languages/messages/MessagesZh.php144
-rw-r--r--languages/messages/MessagesZh_classical.php2407
-rw-r--r--languages/messages/MessagesZh_cn.php15
-rw-r--r--languages/messages/MessagesZh_hans.php1342
-rw-r--r--languages/messages/MessagesZh_hant.php667
-rw-r--r--languages/messages/MessagesZh_hk.php8
-rw-r--r--languages/messages/MessagesZh_sg.php15
-rw-r--r--languages/messages/MessagesZh_tw.php221
-rw-r--r--languages/messages/MessagesZu.php3
-rw-r--r--maintenance/FiveUpgrade.inc2
-rw-r--r--maintenance/README2
-rw-r--r--maintenance/archives/patch-change_tag.sql32
-rw-r--r--maintenance/archives/patch-fix-il_from.sql11
-rw-r--r--maintenance/archives/patch-log_user_text.sql6
-rw-r--r--maintenance/archives/patch-pl-tl-il-unique.sql11
-rw-r--r--maintenance/changePassword.php4
-rw-r--r--maintenance/checkImages.php8
-rw-r--r--maintenance/cleanupCaps.php68
-rw-r--r--maintenance/cleanupTable.inc10
-rw-r--r--maintenance/cleanupWatchlist.php78
-rw-r--r--maintenance/commandLine.inc23
-rw-r--r--maintenance/dumpBackup.php2
-rw-r--r--maintenance/dumpInterwiki.php4
-rw-r--r--maintenance/findhooks.php5
-rw-r--r--maintenance/fuzz-tester.php2
-rw-r--r--maintenance/gearman/gearman.inc104
-rw-r--r--maintenance/gearman/gearmanRefreshLinks.php45
-rw-r--r--maintenance/gearman/gearmanWorker.php41
-rw-r--r--maintenance/generateSitemap.php1
-rw-r--r--maintenance/ibm_db2/README41
-rw-r--r--maintenance/ibm_db2/tables.sql604
-rw-r--r--maintenance/importImages.php43
-rw-r--r--maintenance/importTextFile.php57
-rw-r--r--maintenance/language/StatOutputs.php4
-rw-r--r--maintenance/language/dumpMessages.php4
-rw-r--r--maintenance/language/makeMessageDB.php45
-rw-r--r--maintenance/language/messageTypes.inc11
-rw-r--r--maintenance/language/messages.inc98
-rw-r--r--maintenance/mcc.php6
-rw-r--r--maintenance/namespaceDupes.php19
-rw-r--r--maintenance/orphans.php4
-rw-r--r--maintenance/parserTests.inc42
-rw-r--r--maintenance/parserTests.php2
-rw-r--r--maintenance/parserTests.txt116
-rw-r--r--maintenance/populateCategory.php1
-rw-r--r--maintenance/postgres/archives/patch-change_tag.sql28
-rw-r--r--maintenance/postgres/compare_schemas.pl54
-rw-r--r--maintenance/postgres/tables.sql36
-rw-r--r--maintenance/rebuildFileCache.php5
-rw-r--r--maintenance/rebuildInterwiki.php4
-rw-r--r--maintenance/refreshLinks.inc74
-rw-r--r--maintenance/refreshLinks.php33
-rw-r--r--maintenance/runJobs.php34
-rw-r--r--maintenance/showJobs.php5
-rw-r--r--maintenance/sqlite/README12
-rw-r--r--maintenance/sqlite/archives/initial-indexes.sql417
-rw-r--r--maintenance/sqlite/tables.sql340
-rw-r--r--maintenance/storage/blobs.sql2
-rw-r--r--maintenance/storage/recompressTracked.php8
-rw-r--r--maintenance/storage/trackBlobs.php12
-rw-r--r--maintenance/tables.sql662
-rw-r--r--maintenance/updateRestrictions.php23
-rw-r--r--maintenance/updaters.inc457
-rw-r--r--math/README3
-rw-r--r--opensearch_desc.php2
-rw-r--r--redirect.php8
-rw-r--r--skins/CologneBlue.php59
-rw-r--r--skins/Modern.php17
-rw-r--r--skins/MonoBook.php19
-rw-r--r--skins/Nostalgia.php6
-rw-r--r--skins/Standard.php11
-rw-r--r--skins/common/block.js6
-rw-r--r--skins/common/changepassword.js16
-rw-r--r--skins/common/cologneblue.css5
-rw-r--r--skins/common/common_rtl.css6
-rw-r--r--skins/common/history.js18
-rw-r--r--skins/common/images/feed-icon.pngbin0 -> 557 bytes
-rw-r--r--skins/common/images/nextredirectltr.pngbin0 -> 187 bytes
-rw-r--r--skins/common/images/nextredirectrtl.pngbin0 -> 187 bytes
-rw-r--r--skins/common/shared.css35
-rw-r--r--skins/common/wikibits.js7
-rw-r--r--skins/modern/main.css4
-rw-r--r--skins/modern/rtl.css8
-rw-r--r--skins/monobook/FF2Fixes.css4
-rw-r--r--skins/monobook/IE60Fixes.css11
-rw-r--r--skins/monobook/IE70Fixes.css10
-rw-r--r--skins/monobook/Opera6Fixes.css6
-rw-r--r--skins/monobook/Opera7Fixes.css10
-rw-r--r--skins/monobook/Opera9Fixes.css11
-rw-r--r--skins/monobook/external-rtl.pngbin0 -> 198 bytes
-rw-r--r--skins/monobook/main.css43
-rw-r--r--skins/monobook/rtl.css18
-rw-r--r--skins/simple/main.css6
-rw-r--r--skins/simple/rtl.css7
-rw-r--r--trackback.php51
644 files changed, 102895 insertions, 53056 deletions
diff --git a/AdminSettings.sample b/AdminSettings.sample
index 140cb0a9..8b6fe993 100644
--- a/AdminSettings.sample
+++ b/AdminSettings.sample
@@ -17,6 +17,11 @@
*
* This is not to be confused with sysop accounts for the
* wiki.
+ *
+ * NOTE: for PostgreSQL this should be set to the same user and
+ * password as the web user, that is, the same as $wgDBuser and
+ * $wgDBpassword in LocalSettings.php. This is necessary to
+ * ensure that the owner for new tables is set correctly.
*/
$wgDBadminuser = 'wikiadmin';
$wgDBadminpassword = 'adminpass';
diff --git a/CREDITS b/CREDITS
index 0b456c48..4aef01af 100644
--- a/CREDITS
+++ b/CREDITS
@@ -1,4 +1,4 @@
-MediaWiki 1.14 is a collaborative project released under the
+MediaWiki 1.15 is a collaborative project released under the
GNU General Public License v2. We would like to recognize the
following names for their contribution to the product.
@@ -58,17 +58,25 @@ following names for their contribution to the product.
* Agbad
* Brad Jorsch
* Brent G
+* Brianna Laugher
+* Carlin
+* church of emacs
* Daniel Arnold
* Danny B.
* FunPika
* Happy-melon
* Jeremy Baron
+* Jidanni
* Juliano F. Ravasi
* Lucas Garczewski
* Louperivois
+* Luigi Corsaro
+* Manuel Menal
+* Marcin Cieślak
* Marooned
* Max Semenik
* Michael De La Rue
+* Michael Walsh
* Mike Horvath
* Mormegil
* Nakon
@@ -79,7 +87,9 @@ following names for their contribution to the product.
* RememberTheDot
* René Kijewski
* ST47
-* Stefano
+* Simon Walker
+* Stefano Codari
+* Str4nd
== Translators ==
* Anders Wegge Jakobsen
diff --git a/HISTORY b/HISTORY
index f9e3692a..6851fcf5 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,5 +1,596 @@
Change notes from older releases. For current info see RELEASE-NOTES.
+== MediaWiki 1.14 ==
+
+=== Changes since 1.14.0rc1 ===
+
+* Fixed the performance of the backlinks API module
+* (bug 17420) Send the correct content type from action=raw when the HTML file
+ cache is enabled.
+* (bug 17437) Fixed incorrect link to web-based installer
+* (bug 17527) Fixed missing MySQL-specific options in installer
+
+=== Configuration changes in 1.14 ===
+
+* $wgExemptFromUserRobotsControl is an array of namespaces to be exempt from
+ the effect of the new __INDEX__/__NOINDEX__ magic words. (Default: null, ex-
+ empt all content namespaces.)
+* $wgForwardSearchUrl has been removed entirely. Documented setting since 1.4
+ has been $wgSearchForwardUrl.
+* (bug 15080) $wgOverrideSiteFeed has been added. Setting either
+ $wgSiteFeed['rss'] or 'atom' to a URL will override the default Recent
+ Changes feed that appears on all pages.
+* $wgSQLiteDataDirMode has been introduced as the default directory mode for
+ SQLite data directories on creation. Note that this setting is separate from
+ $wgDirectoryMode, which applies to all normal dirs created by MediaWiki.
+* $wgGroupsAddToSelf and $wgGroupsRemoveFromSelf now work more like
+ $wgAddGroups and $wgRemoveGroups, where the user must belong to a specified
+ group in order to add or remove those groups from themselves.
+ Backwards compatibility is maintained.
+* $wgRestrictDisplayTitle controls if the use of the {{DISPLAYTITLE}} magic
+ word is restricted to titles equivalent to the actual page title. This
+ is true per default, but can be set to false to allow any title.
+* $wgSpamRegex may now be an array of multiple regular expressions.
+* $wgAjaxSearch has been removed; use $wgEnableMWSuggest instead.
+* Editing the MediaWiki namespace is now unconditionally restricted to people
+ with the editinterface right, configuring this in $wgNamespaceProtection
+ is not required.
+* $wgAllowExternalImagesFrom may now be an array of multiple strings.
+* Introduced $wgEnableImageWhitelist to toggle the on-wiki external image
+ whitelist on or off.
+* Added $wgRenderHashAppend to append some string to the parser cache and the
+ sitenotice cache keys.
+* $wgRCChangedSizeThreshold is now a positive integer by default,
+* (bug 16006) $wgEnableWriteAPI is now true by default. Authorized can perform
+ write actions using the API.
+* Added $wgRC2UDPInterwikiPrefix which adds an interwiki prefix
+ ($wgLocalInterwiki) onto the page names in the UDP feed.
+* Added $wgAllowUserSkin to let the wiki's owner disable user selectable skins
+ on the wiki. If it's set to false, then the skin used will *always* be
+ $wgDefaultSkin.
+* Added $wgEnotifUseRealName, which allows UserMailer to send out e-mails based
+ on the user's real name if one is set. Defaults to false (use the username)
+* Removed the 'apiThumbCacheDir' option from $wgForeignFileRepos (only used in
+ ForeignAPIRepo)
+* (bug 44) Image namespace and accompanying talk namespace renamed to File.
+ For backward compatibility purposes, Image still works. External tools may
+ need to be updated.
+* The constants NS_FILE and NS_FILE_TALK can now be used instead of NS_IMAGE and
+ NS_IMAGE_TALK. The old constants are retained as aliases for compatibility,
+ and should still be used in code meant to be compatible with v1.13 or older.
+* MediaWiki can be forced to use private IPs forwarded by a proxy server by
+ using $wgUsePrivateIPs.
+* The 'BeforeWatchlist' hook has been removed due to internal changes in
+ Special:Watchlist. 'SpecialWatchlistQuery' should now be used by extensions
+ to customize the watchlist database query.
+
+=== Migrated extensions ===
+The following extensions are migrated into MediaWiki 1.14:
+
+* Special:DeletedContributions to show deleted user contributions (was
+ extension DeletedContributions)
+* Special:Log/newusers recording new users (was extension Newuserlog)
+* Special:LinkSearch to search for external links (was extension LinkSearch)
+* RenderHash
+* NoMoveUserPages
+* UniversalEditButton
+
+=== New features in 1.14 ===
+
+* New URL syntaxes for Special:ListUsers - 'Special:ListUsers/USER' and
+ 'Special:ListUsers/GROUP/USER', in addition to the older syntax
+ 'Special:ListUsers/GROUP' where GROUP is a valid group name.
+* Configurable per-namespace and per-page notices for the edit form,
+ respectively MediaWiki:Editnotice-# where # is the namespace number, and
+ MediaWiki:Editnotice-#-PAGENAME where # is the page's namespace number and
+ PAGENAME is the page name minus the namespace prefix.
+* (bug 8068) New __INDEX__ and __NOINDEX__ magic words allow user control of
+ search engine indexing on a per-article basis.
+* Handheld stylesheet options
+* Added 'DoEditSectionLink' hook as a cleaner unified version of the old
+ 'EditSectionLink' and 'EditSectionLinkForOther' hooks. Note that the
+ 'EditSectionLinkForOther' hook has been removed, but 'EditSectionLink' is
+ run in all cases instead, so extensions using the old hooks should still work
+ if they ran roughly the same code for both hooks (as is almost certain).
+* Signature (~~~~) "cleaning", i.e. template removal, can be disabled with
+ $wgCleanSignatures=false
+* Extensions can use the SkinBuildSidebar hook to modify the content of the
+ sidebar and add custom portlets to it
+* Added 'MakeGlobalVariablesScript' hook for extensions to be able to add vari-
+ ables into into the output of Skin::makeVariablesScript
+* (bug 13846) Added $wgAddGroups and $wgRemoveGroups display on
+ Special:ListGroupRights
+* (bug 14377) Add a date selector to history pages
+* (bug 15007) New 'pagetitle-view-mainpage' message allows the HTML <title> of
+ the main page to be customized
+* Added $wgDisableTitleConversion to disabling the conversion for all pages on
+ the wiki
+* Added 'noconvertlink' toggle that can be set per user preferences, also
+ added 'convertlink=no|yes' on GET requests whether have the link titles
+ being converted or not
+* (bug 14921) Special:Contributions/: add user name to <title>
+ Patch by Emufarmers
+* Unescape more "safe" characters when producing URLs, for added prettiness
+* Introduced a new hook 'SkinAfterContent' that allows extensions to add text
+ after the page content and article metadata. Updated all skins and skin
+ templates to work with that hook.
+* (bug 14929) removeUnusedAccounts.php now supports 'ignore-touched' and
+ 'ignore-groups'. Patch by Louperivois
+* (bug 15127) Work around minor display glitch in Opera.
+* By default, reject file uploads that look like ZIP files, to avoid the
+ so-called GIFAR vulnerability.
+* (bug 15141) Give ability to only list protected pages with the cascading
+ option enabled on Special:ProtectedPages
+* (bug 15157) Special:Watchlist has the same options as Special:Watchlist:
+ Show/Hide logged in users, Show/Hide anonymous, Invert namespace selection
+* Added hook 'UserrightsChangeableGroups' to allow modification of what
+ groups may be added or removed via the Special:UserRights interface.
+* HTML entities like &nbsp; now work (are not escaped) in edit summaries.
+* (bug 13815) In the comment for page moves, use the colon-separator message
+ instead of a hardcoded colon.
+* Allow <gallery> to accept image names without an Image: prefix
+* Add tooltips to rollback and undo links
+* BMP images are now displayed as PNG
+* (bug 13471) Added NUMBERINGROUP magic word
+* (bug 11884) Now support Flash EXIF attribute
+* Show thumbnails in the file history list, patch by User:Agbad
+* Added support of piped wikilinks using double-width brackets
+* Added an on-wiki external image whitelist. Items in this whitelist are
+ treated as regular expression fragments to match for when possibly
+ displaying an external image inline.
+* (bugs 15405, 15436) Sort more currency types correctly in sortable tables
+* (bug 15422) Sort more different types of numbers in sortable tables
+* (bug 2889) MediaWiki:Print.css applies to the printable version
+* Category counts (e.g. from {{PAGESINCATEGORY:}}) should be more accurate for
+ small categories
+* After logging in, automatically redirect to wherever you logged in from
+* (bug 5619) Break messages used in Special:Statistics down further
+* (bug 11029) Add link to Special:Listusers?group=sysop etc at
+ Special:Statistics
+* (bug 15514) Setting $wgRightsText without $wgRightsUrl now produces a
+ plaintext copyright notice. Patch by Juliano F. Ravasi.
+* (bug 15551) Deletion log excerpt is now shown whenever a user vists a
+ deleted page, even if they are unable to edit it.
+* Added Wantedfiles special pages, allowing users to find image links with no
+ image.
+* (bug 12650) It is now possible to set different expiration times for
+ different restriction types on the protection form.
+* (bug 8440) Allow preventing blocked users from editing their talk pages
+* Improved upload file type detection for OpenDocument formats
+* Added the ability to set the target attribute on external links with
+ $wgExternalLinkTarget
+* api.php now sends "Retry-After" and "X-Database-Lag" HTTP headers if the
+ maxlag check fails, just like index.php does
+* Added "link" parameter to image links, to allow images to link to an
+ arbitrary title or URL. This should replace inaccessible and incomplete
+ solutions such as CSS-based overlays and ImageMap.
+* (bug 368) Don't use caption for alt attribute; allow manual specification
+ using new "alt=" parameter for images
+* (bug 44) The {{ns:}} core parser function now also accepts localized
+ namespace names and aliases; also, its output now uses spaces instead of
+ underscores to match the behavior of the {{NAMESPACE}} magic word
+* Added the ability to display user edit counts in Special:ListUsers. Off by
+ default, enabled with $wgEdititis = true (named after the medical condition
+ marked by unhealthy obsession with edit counts).
+* Added a file cache to the parser to improve page rendering time on pages with
+ several uses of the same image.
+* (bug 1250) Users can still use "show preview" and "show changes" even if the
+ wiki is set to read-only mode.
+* Added a call to the 'UnwatchArticleComplete' hook to the watchlist editor.
+ This should make it so that ALL user-accessible methods of removing a page
+ from a watchlist lead to this hook being called (it was previously only
+ called from within Article.php
+* Maximum execution time for shell processes on linux is now configured with
+ $wgMaxShellTime (180 seconds by default)
+* (bug 1306) 'Email user' link no longer shown on user page when emailing
+ is not available due to lack of confirmed address or disabled preference
+* Special:Wanted templates special page added to display missing templates
+ linked from articles
+* Make search matches bold only, not red as well
+* (bug 10080) Blocks can be modified without unblocking first
+* (bug 15820) Special:BlockIP shows a notice if the user being blocked is
+ already directly blocked
+* (bug 13710) Allow to force "watch this" checkbox via URL using parameter
+ "watchthis"
+* (bug 15125) Add Public Domain to default options when installing. Patch by
+ Nathan Larson.
+* Set a special temporary directory for ImageMagick with $wgImageMagickTempDir
+* (bug 16113) Show/hide for redirects in Special:NewPages
+* (bug 15903) Upload link was added to Nostalgia skin
+* (bug 15761) Add user toggle to omit diff after rollback
+* Added the BitmapHandler_ClientOnly media handler, which allows server-side
+ image scaling to be completely disabled for specific media types, via the
+ $wgMediaHandlers configuration variable.
+* New 'AbortDiffCache' hook can be used to cancel the caching of a diff
+* (bug 15835) Added Content-Style-Type meta tag
+* (bug 11027) Add parameter to MW:Randompage-nopages so that user can see the
+ namespace.
+* Add id="mw-user-domain-section" to <tr> tag in Userlogin.php template so that
+ admins with a single domain can hide the domain section using CSS
+* Dropped old Paser_OldPP class. Only new parser with preprocessor is used.
+* Moved password reset form from Special:Preferences to Special:ResetPass
+* Added Special:ChangePassword as a special page alias for Special:ResetPass
+* Added complimentary function for addHandler() called removeHandler() for removing events
+* Improved security of file uploads for IE clients, using a reverse-engineered
+ algorithm very similar to IE's content detection algorithm.
+* Cascading protection no longer requires that both edit and move are restricted
+ to sysop, just edit=sysop is enough
+* (bug 2391) A warning is now shown for invalid ISBN numbers on Special:Booksources.
+* Installer has been updated to reflect the release of the GFDL 1.3. The URL for 1.2
+ has been updated, and the 1.3 URL has been given. 1.2 is still Wikipedia-compatible.
+ RightsCode was changed from 'gfdl' to 'gfdl1_2', so we can now support 1.2 as well
+ as 1.3 (gfdl1_3).
+* (bug 16293) PD URL was changed to the CreativeCommons site on PD (which auto-detects
+ your language) instead of Wikipedia.
+* (bug 16635) The "view and edit watchlist" page (Special:Watchlist/edit) now
+ includes a table of contents
+* File objects returned by wfFindFile() are now cached by default
+* (bug 7492) Rights can now be assigned to specific IP addresses and ranges by
+ using $wgAutopromote (new defines: APCOND_ISIP and APCOND_IPINRANGE)
+* Add a 'change block' link to Special:IPBlockList and Special:Log
+* (bug 16459) Use native getElementsByClassName where possible, for better
+ performance in modern browsers
+* Enable \cancel and \cancelto in texvc (recompile required)
+* Added 'UserCryptPassword' and 'UserComparePasswords' hooks to allow extensions to implement
+ their own password hashing methods.
+* (bug 16760) Add CSS-class to action links of Special:Log
+* (bug 505) Time zones can now be specified by location in user preferences,
+ avoiding the need to manually update for DST. Patch by Brad Jorsch.
+* (bug 2585) HTTP 404 return code is now given for a page view if the page
+ does not exist, allowing spiders and link checkers to detect broken links.
+* Special:Log: Add 'change protection' link for unprotected pages too
+* Special:Log: Add log type specific CSS classes 'mw-logline-$logtype' to
+ 'li' elements
+* (bug 16754) Making arbitrary rows of sortable tables sticky:
+ |- class="unsortable"
+* Show subversion too even if a "normal" version number is available
+* (bug 16121) Add a note that a page move was without creating a redirect in the
+ move log
+* Image moving is now enabled for sysops by default
+* Make "Did you mean" search feature more noticeable
+* (bug 16720) Transcluded Special:NewPages processes "/username="
+
+=== Bug fixes in 1.14 ===
+
+* (bug 14907) DatabasePostgres::fieldType now defined.
+* (bug 14659) Passing the default limit param to Special:Recentchanges no more
+ falls back to the user option
+* (bug 14954) Fix regression in Modern and Simple skins
+* Recursion loop check added to Categoryfinder class
+* Fixed few performance troubles of large job queue processing
+* Not setting various parameters in Foreign Repos now fails more gracefully
+* (bug 2333) Redirects are properly rendered when previewing an edit.
+* (bug 14972) Use localized alias of Special:Search on all search forms
+* (bug 11035) Special:Search should have descriptive <title>
+* Special pages are now not subject to special handling for "self-links"
+* (bug 15053) Syntactically incorrect redirects with another link in them
+ no longer redirect to the second link
+* (bug 15049) Fix for CheckUser extension's log search: usernames containing
+ a "-" were incorrectly turned into bogus IP range searches.
+ Patch by Max Semenik.
+* (bug 15055) Talk page notifications no longer attempt to send mail when
+ user's e-mail address is invalid or unconfirmed
+* (bug 12370) Add throttle on password attempts. Defaults to max 5 attempts in
+ 5 minutes.
+* (bug 15016) 'Templates used on this page' list in view source should be
+ wrapped in a div with class "templatesUsed"
+* (bug 14868) Setting $wgFeedDiffCutoff to 0 now disables generation of the
+ diff entirely, not just the display of it.
+* (bug 6387) Introduced new setting $wgCategoryPrefixedDefaultSortkey which
+ allows having the unprefixed page title as the default category sortkey
+* (bug 15079) Add class="ns-talk" / "ns-subject" to <body>. Also added
+ ns-special to special pages.
+* (bug 15052) Skins should add their name as a class in <body>
+* (bug 14165, bug 14294) Wikimedia specific configuration in convertGrammar()
+ for several languages was removed. The settings have been put in extension
+ WikimediaMessages. Patch for Czech by Danny B.
+* (bug 15101) Displaying only bots edits in Special:Recentchanges now works
+ again
+* (bug 13770) Fixed incorrect detection of PHP's DOM module
+* (bug 14790) Export of category pages when using Category: prefix now actually
+ gives results
+* Avoid recursive crazy expansions in section edit comments for pages which
+ contain '/*' in the title
+* Fix excessive memory usage when parsing pages with lots of links
+* $wgSpamRegex now matches the edit summary and page move descriptions in
+ addition to body text.
+* Navigation links to images available from a shared repository (like Commons)
+ from their local talk pages no longer appear as redlinks
+* Action=purge on ForeignApiFiles now works (purges their thumbnails and
+ description pages).
+* (bug 15303) Title conversion for templates wasn't working in some cases.
+* (bug 15264) Underscores in Special:Search/Foo_bar parameters were taken
+ literally; now converting them to spaces per expectation.
+* (bug 15342) "Invert" checkbox now works correctly when selecting main
+ namespace in Special:Watchlist
+* (bug 15172) 'Go' button of Special:Recentchanges now on the same line as the
+ last input element (like Special:Watchlist too)
+* (bug 15351) Fix fatal error for invalid section fragments in autocomments
+* Fixed intermittent deadlock errors involving objectcache table queries.
+ Use a separate database connection for the objectcache table to avoid
+ long-lasting locks on that table.
+* Respect file restrictions in the file history list
+* (bug 15399) Odd/even classes on sortable tables' rows could be slow for large
+ tables, and have been disabled by default.
+* (bug 15482) Special:Recentchangeslinked has no longer two submit buttons
+* (bug 15292) New message notification for unregistred users now works again
+* (bug 14398) mwsuggest.js: Let width of container be configurable
+* (bug 15543) Only include user touched timestamp to generated CSS
+* (bug 15497) Removed encoding attribute from <?xml ?> tag
+* (bug 12284) Special:Preferences now sets a returnto parameter on the link to
+ Special:UserLogin. Patch by Marooned.
+* Fixed the HTTP accept language string detection length in
+ LanguageConverter.php, instead of the fixed length language codes.
+* Special:RecentChangesLinked no longer shows outgoing links for nonexistent
+ pages even if there are broken link records with source article id 0 in the
+ database
+* (bug 15598) Special:Newpages default limit uses user preference for
+ recentchanges limit instead of hardcoded 50.
+* (bug 15617) $wgFeedClassesOutputPage::getHeadLinks() respects $wgFeedClasses,
+ instead of hardcoding rss and atom. Patch by Juliano F. Ravasi.
+* (bug 14638) Special:Blockip now provides a link to the block log if the user
+ has been blocked more than 10 times. Patch by Matt Johnston.
+* (bug 12678) Skins don't show Upload link if the user isn't allowed to upload.
+* Fixed incorrect usage of DB_LAST in Special:Export. Deprecated DB_LAST.
+* (bug 15642) Blocked sysops can no longer block other users
+* Http::request() now respects $wgHTTPtimeout when not using cURL
+* (bug 15158) Userinvalidcssjstitle not shown on preview
+* (bug 15196) Free external links should be numbered in a localised manner
+* (bug 15388) Title of Special:PrefixIndex
+* Links with no title but a curid parameter now use the curid to pick a page
+* (bug 10323) Special:Undelete should have "inverse selection" button
+* (bug 15831) Modern skin RTL support is bugous
+* (bug 15869) Nostalgia skin does not show page title in printable mode
+* (bug 15795) Special:Userrights is now listed on Special:SpecialPages when the
+ user can only change his rights
+* (bug 15846) Categories "leak" from older revisions in certain circumstances
+* (bug 15928) Special pages dropdown should be inline in non-MonoBook skins
+* (bug 14178) Some uses of UserLoadFromSession hook cause segfault
+* (bug 15925) Postitive bytes added on recentchanges and watchlists are now
+ bolded if above the threshold, previously it only worked for negatives
+* Specify apple-touch-icon before favicon in HTML head section to make the
+ Konqueror browser correctly use the latter
+* (bug 15717) Set $separatorTransformTable for language 'eu'
+* (bug 15605) Enabled $datePreferences for language 'hr'. Added standard date
+ preferences.
+* (bug 13701) {{NUMBEROFVIEWS}} magic word to show number of total views.
+* (bug 5101) Image from Commons doesn't show up when searched in Wikipedia
+ search box
+* (bug 14609) User's namespaces to be searched default not updated after adding
+ new namespace
+* Purge form uses valid XHTML
+* (bug 12764) Special:LonelyPages shows transcluded pages
+* (bug 16073) Enhanced RecentChanges uses onclick handler with better fallback
+ if JavaScript is disabled
+* (bug 4253) Recentchanges IRC messages no longer include title in diff URLs
+* Allow '0' to be an accesskey.
+* (bug 8063) Use language-dependent sorting in client-side sortable tables
+* (bug 16160) Suggestions box should be resized from left for RTL wikis
+* (bug 11533) Fixed insane slowdown when in read-only mode for long periods
+ of time with CACHE_NONE (default objectcache table configuration).
+* Trying to set two different default category sort keys for one page now
+ produces a warning
+* (bug 16143) Fix redirect loop on special pages starting with lower case
+ letters
+* (bug 15737) Fix notices while expanding using PPCustomFrame
+* (bug 15544) Non-index entry points cause the "Wiki not set up" message to
+ have corrupt URLs
+* (bug 5101) Image from Commons doesn't show up when searched in Wikipedia
+ search box
+* (bug 4362) [[MediaWiki:History copyright]] no more used with most recent
+ revision when passing oldid parameter in the url
+* (bug 16265) When caching thumbs with the ForeignApiRepo, we now use the same
+ filename as the remote site.
+* (bug 8345) Don't autosummarize where a redirect was left unchanged
+* Made thumb caching in ForeignApiFile objects integrated with normal thumb
+ path naming (/thumbs/hash/file), retired 'apiThumbCacheDir' as a result.
+* (bug 5530) Consistency between character encoding in {{PAGENAMEE}},
+ {{SUBPAGENAMEE}} and {{FULLPAGENAMEE}}
+* Safer handling of non-MediaWiki exceptions -- now obeys our settings for
+ formatting and path exposure.
+* Less verbose errors from profileinfo.php when not configured
+* Blacklist redirects via Special:Filepath, hard to use.
+* Improved input validation on Special:Import form
+* Add a .htaccess to deleted images directory for additional protection
+ against exposure of deleted files with known SHA-1 hashes on default
+ installations.
+* Improved scripting safety heuristics for IE 5/6 content-type detection.
+* Improved scripting safety heuristics on SVG uploads.
+* (bug 11728) Unify layout of enhanced watchlist/recent changes
+* (bug 8702) Properly update stats when running nukePage maintenance script
+* (bug 7726) Searches for words less than 4 characters now work without
+ requiring customization of MySQL server settings
+* Honour unchecked "Leave a redirect behind" for moved subpages
+* (bug 16440) Broken 0-byte math renderings are now deleted and re-rendered
+ when page is re-parsed.
+* (bug 6100) Unicode BiDi embedding/override characters (U+202A - U+202E) are
+ now automatically removed from titles; these characters can accidentally end
+ up in copy-and-pasted titles, and, by overriding normal bidirectional text
+ handling, can lead to annoying behavior such as text rendering backwards
+* Fixed minor bug where the memcached value for how many accounts an IP had
+ created that day would be increased even if $wgAccountCreationThrottle was
+ hit. This meant if an IP hit the throttle and then the throttle was raised
+ later that day, the IP still couldn't create another account, because it
+ had marked them as having created another account, when their last account
+ creation had actually failed.
+* (bug 12647) Allow autogenerated edit summary messages to be blanked with '-'
+* (bug 16026) 'Revision-info' and 'revision-info-current' both accept wiki
+ markup now.
+* (bug 16529) Fix for search suggestions with some third-party JS libraries
+* (bug 13342) importScript() generates more consistent URI encoding
+* (bug 16577) When a blocked user tries to rollback a page, the block message
+ is now only displayed once
+* (bug 14268) SVG image sizes now extracted with proper XML parser
+* (bug 14365) RepoGroup::findFiles() no longer crashes if passed an invalid
+ title via the API
+* (bug 4253, bug 16586) Revision ID is now given instead of title in URLs for
+ new pages in the recent changes IRC feed
+* Ugly tooltips in Special:Statistics were phased out in favor of more direct
+ information. Went ahead and rewrote SpecialStatistics to subclass SpecialPage
+* (bug 5506) Links to files on foreign repositories are now shown consistently
+ as bluelinks e.g. in logs and edit summaries
+* (bug 16623) Add missing </p> tag in Special:LockDB
+* (bug 15849) Special:Movepage now throws a more specific error when trying to
+ move a title to an interwiki target
+* (bug 16638) 8-bit URL fallback encoding now set on additional languages using
+ Arabic script (Persian, Urdu, Sindhi, Punjabi)
+* (bug 16656) cleanupTitles and friends should now work in load-balanced
+ DB environments when $wgDBserver isn't set.
+* (bug 3691) Aspect ratio from viewBox attribute is now preserved for SVG
+ images which do not specify width and height attributes.
+* (bug 15027) Internet domain names and IP addresses can now be indexed and
+ searched sensibly with the default MySQL search backend.
+* (bug 11733) Fixed parameter validation in importTextFile.php
+* (bug 16712) Special:NewFiles updated to use "newer"/"older" paging messages
+ for clarity over "previous/next"
+* (bug 16612) Fixed "noprint" class for Modern skin print style
+* Section anchors now have an "id" attribute as well as a "name" attribute,
+ even when Tidy is not used
+* (bug 16026) revision-info, revision-info-current, cannotdelete,
+ redirectedfrom, historywarning and difference messages now use Wiki text
+ rather than raw HTML markup
+* (bug 13835) Fix rendering of {{filepath:Wiki.png|nowiki}}
+* (bug 16772) Special:Upload now correctly rejects files with spaces in the
+ file extension (e.g. Foo. jpg).
+* Image moving over an existing file no longer throws a database error
+* (bug 16786) Restored "redundant" links recently removed from Classic sidebar
+* (bug 16850) $wgActionPaths can have query strings now, previously, this broke
+ local URLs
+* (bug 16376) Mention in deleteBatch.php and moveBatch.php maintenance scripts
+ that STDIN can be used for page list
+* (bug 16560) Special:Random returns a page from ContentNamespaces, and no
+ longer from NS_MAIN
+
+=== API changes in 1.14 ===
+
+* Registration time of users registered before the DB field was created is now
+ shown as empty instead of the current time.
+* API search now falls back to fulltext search by default when using Lucene
+ or other engine which doesn't support a separate title search function.
+ This means you can use API search on Wikipedia without explicitly adding
+ &srwhat=text to the query.
+* Added iiprop=bitdepth to imageinfo and aiprop=bitdepth to allimages
+* (bug 14713) API-specific permissions (such as 'writeapi' and 'apihighlimits'
+ are now listed on action=help
+* (bug 15044) Added requestid parameter to api.php to facilitate distinguishing
+ between requests
+* (bug 15048) Added limit field for multivalue parameters to action=paraminfo
+ output.
+* When the limit on multivalue parameters is exceeded, a warning is issued
+* list=search doesn't list missing pages any more
+* (bug 15178) Added clshow to prop=categories to allow filtering for hidden/
+ non-hidden categories
+* (bug 15228) Combining revids= and redirects now throws a warning instead of
+ an error, and still resolves redirects generated by the generator.
+* list={backlinks,embeddedin,imageusage} now return arrays with keys 0, 1, 2,
+ etc. (AKA lists) instead of arrays with pageIDs as keys (AKA hash tables)
+ for consistency with other list modules.
+* Added action=watch
+* (bug 15275) apprefix and related parameters ignore spaces at the end
+* action=edit no longer throws unknown error 228 when trying to create an
+ empty section with section=new
+* Database replication lag doesn't cause all action=edit requests to return the
+ nochange flag any more
+* (bug 15392) ApiFormatBase::formatHTML now uses $wgUrlProtocols.
+* (bug 15444) action=edit returns "Unknown error: ``AS_END''" where it should
+ return just "Unknown error"
+* (bug 15448) YAML output returns empty values instead of 0
+* (bug 15445) Added action=patrol
+* (bug 15466) Added action=purge
+* (bug 15486) action=block ignores autoblock parameter
+* (bug 15492) added rcprop=loginfo to list=recentchanges
+* (bug 15527) action=rollback can now revert anonymous editors
+* (bug 15535) prop=info&inprop=protection doesn't list pre-1.10 protections
+ if the page is also protected otherwise (1.10+ style or cascading)
+* list=random now has rnredirect parameter, to get random redirects.
+* Added APIAfterExecute, APIQueryAfterExecute and APIQueryGeneratorAfterExecute
+ hooks which allow for extending core modules in a cleaner way
+* action=protect checks for invalid protection types and levels
+* (bug 15673) Added indentation to format=wddxfm output and improved built-in
+ WDDX formatter to resemble PHP's more
+* (bug 15706) Empty values for apprtype and apprlevel are now silently ignored
+ rather than causing an exception
+* Added uiprop=preferencestoken to meta=userinfo
+* (bug 15609) Add inprop=url and inprop=readable to prop=info
+* Add ApiDisabled and ApiQueryDisabled classes so individual modules can
+ be disabled in LocalSettings.php
+* (bug 15653) Add prop=duplicatefiles
+* (bug 15768) Add list=watchlistraw
+* (bug 15647) action=edit with basetimestamp fails if the page has been deleted
+ and undeleted since the last edit
+* (bug 15785) Allow for different expiry times for different protections in
+ action=protect
+* Added allowsduplicates attribute to action=paraminfo output
+* (bug 15767) apfilterlanglinks returns duplicate results
+* (bug 15845) Added pageid/fromid parameter to action=delete/move, making
+ manipulation of legacy pages with invalid titles possible
+* (bug 15881) Empty or invalid parameters cause database errors
+* The maxage and smaxage parameters are now properly validated
+* (bug 15945) list=recentchanges doesn't check $wgUseRCPatrol, $wgUseNPPatrol
+ and patrolmarks right
+* (bug 15985) acfrom and aifrom parameters didn't work when sorting in
+ descending order.
+* (bug 15995) Add cmstartsortkey and cmendsortkey parameters to
+ list=categorymembers
+* (bug 16017) list=categorymembers sets invalid continue parameters for
+ sortkeys containing pipes
+* (bug 16018) Added uccontinue parameter to list=usercontribs so paging
+ works properly when multiple users are queried or a userprefix is used
+* (bug 16047) Added activeusers attribute to meta=siteinfo&siprop=statistics
+ output
+* Added redirect resolution to action=parse
+* (bug 16074) rvprop=content combined with a generator with a high limit causes
+ an error
+* (bug 16105) Image metadata attributes containing spaces result in invalid XML
+* (bug 16126) Added siprop=magicwords to meta=siteinfo
+* (bug 16159) Added wlshow=patrolled|!patrolled to list=watchlist
+* (bug 16225) Titles like Talk:Talk:Foo broke apfrom and friends
+* meta=siteinfo&siprop=interwikimap no longer throws an exception for empty
+ sifilter parameter.
+* (bug 12760) meta=userinfo&uiprop=ratelimits doesn't list group-specific rate
+ limits
+* (bug 16398) meta=userinfo&uiprop=rights lists some rights twice in some cases
+* (bug 16408) Added rvgeneratexml to prop=revisions
+* (bug 16421) Made list=logevents's leuser accept user names with underscores
+ instead of spaces
+* (bug 16516) Made rvsection=T-2 work
+* (bug 16526) Added usprop=emailable to list=users
+* (bug 16548) list=search threw errors with an invalid error code
+* (bug 16515) Added pst and onlypst parameters to action=parse
+* (bug 16541) Added block expiry timestamp to list=logevents output
+* (bug 16613) action=protect doesn't tell when &cascade was set but cascading
+ protection wasn't allowed
+* (bug 16626) action=delete now correctly handles empty "reason" param
+* (bug 15579) clshow considers all categories !hidden
+* (bug 16647) list=allcategories, prop=categories don't return "hidden"
+ property for hidden categories
+* New siprop parameter of 'extensions' to list all installed extensions
+* (bug 16672) Include canonical namespace name in
+ meta=siteinfo&siprop=namespaces.
+* (bug 16726) siprop=namespacealiases should also list localized aliases
+* (bug 16730) Added apprfiltercascade parameter to list=allpages to filter
+ cascade-protected pages
+
+=== Languages updated in 1.14 ===
+
+MediaWiki supports over 300 languages. Many localisations are updated
+regularly. Below only new and removed languages are listed.
+
+* Bakhtiari (bqi) (new)
+* Fiji Hindi (Devanagari script) (hif-deva) (new)
+* Krio (kri) (new)
+* Lezghian (lez) (new)
+* Laz (lzz) (new)
+* Eastern Mari (mhr) (new)
+* Niuean (niu) (new)
+* Oromo (om) (new)
+* Plautdietsch (pdt) (new)
+* Western Punjabi (pnb) (new)
+* Tarantino (roa-tara) (new)
+* Serbo-Croatian (sh) (new)
+* Tulu (tcy) (new)
+
+
== MediaWiki 1.13 ==
== Changes since 1.13.2 ==
@@ -113,6 +704,7 @@ Other changes in this release:
* (bug 14944) Shell invocation of external programs such as ImageMagick convert
was broken in PHP 5.2.6, if the server had a non-UTF-8 locale.
+
=== Configuration changes in 1.13 ===
* New option $wgFeed can be set false to turn off syndication feeds
diff --git a/RELEASE-NOTES b/RELEASE-NOTES
index 7ce61f90..f6d2523e 100644
--- a/RELEASE-NOTES
+++ b/RELEASE-NOTES
@@ -3,11 +3,11 @@
Security reminder: MediaWiki does not require PHP's register_globals
setting since version 1.2.0. If you have it on, turn it *off* if you can.
-== MediaWiki 1.14.0 ==
+== MediaWiki 1.15.0 ==
-February 22, 2009
+2009-06-10
-This is the first stable release of the 2009 Q1 branch of MediaWiki.
+This is a stable release of the the 2009 Q2 branch of MediaWiki.
MediaWiki is now using a "continuous integration" development model with
quarterly snapshot releases. The latest development code is always kept
@@ -20,616 +20,385 @@ will be made on the development trunk and appear in the next quarterly release.
Those wishing to use the latest code instead of a branch release can obtain
it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
-NOTE: Installation of MediaWiki on SQLite has been temporarily disabled in this
-release due to the discovery of serious problems with the schema. We expect to
-fix this problem for the release of 1.15.0.
-
-== Changes since 1.14.0rc1 ==
-
-* Fixed the performance of the backlinks API module
+=== Changes since 1.15.0rc1 ===
+
+* Removed category redirect feature, implementation was incomplete.
+* (bug 18846) Remove update_password_format(), unnecessary, destroys all
+ passwords if a wiki with $wgPasswordSalt=false is upgraded with the web
+ installer.
+* (bug 19127) Documentation warning for PostgreSQL users who run update.php:
+ use the same user in AdminSettings.php as in LocalSettings.php.
+* Fixed possible web invocation of some maintenance scripts, due to the use of
+ include() instead of require(). A full exploit would require a very strange
+ web server configuration.
+* Localisation updates.
+
+=== Configuration changes in 1.15 ===
+
+* Added $wgNewPasswordExpiry, to specify an expiry time (in seconds) to
+ temporary passwords
+* Added $wgUseTwoButtonsSearchForm to choose the Search form behavior/look
+* Added $wgNoFollowDomainExceptions to allow exempting particular domain names
+ from rel="nofollow" on external links
+* (bug 12970) Brought back $wgUseImageResize.
+* Added $wgRedirectOnLogin to allow specifying a specifc page to redirect users
+ to upon logging in (ex: "Main Page")
+* Add $wgExportFromNamespaces for enabling/disabling the "export all from
+ namespace" option (disabled by default)
+
+=== New features in 1.15 ===
+
+* (bug 2242) Add an expiry time to temporary passwords
+* (bug 9947) Add PROTECTIONLEVEL parser function to return the protection level
+ for the current page for a given action
+* (bug 17002) Add &minor= and &summary= as parameters in the url when editing,
+ to automatically add a summary or a minor edit.
+* (bug 16852) padleft and padright now accept multiletter pad characters
+* When using 'UserCreateForm' hook to add new checkboxes into
+ Special:UserLogin/signup, the messages can now contain HTML to allow
+ hyperlinking to the site's Terms of Service page, for example
+* Add new hook 'UserLoadFromDatabase' that is called while loading a user
+ from the database.
+* (bug 17045) Options on the block form are prefilled with the options of the
+ existing block when modifying an existing block.
+* (bug 17055) "(show/hide)" links to Special:RevisionDelete now use a CSS class
+ rather than hardcoded HTML tags
+* Added new hook 'WantedPages::getSQL' into SpecialWantedpages.php to allow
+ extensions to alter the SQL query which is used to get the list of wanted
+ pages
+* (bugs 16957/16969) Add show/hide to preferences for RC patrol options on
+ specialpages
+* (bug 11443) Auto-noindex user/user talk pages for blocked user
+* (bug 11644) Add $wgMaxRedirects variable to control how many redirects are
+ recursed through until the "destination" page is reached.
+* Add $wgInvalidRedirectTargets variable to prevent redirects to certain
+ special pages.
+* Use HTML5 rel attributes for some links, where appropriate
+* Added optional alternative Search form look - Go button & Advanced search
+ link instead of Go button & Search button
+* (bug 2314) Add links to user custom CSS and JS to Special:Preferences
+* More helpful error message on raw page access if PHP_SELF isn't set
+* (bug 13040) Gender switch in user preferences
+* (bug 13040) {{GENDER:}} magic word for interface messages
+* (bug 3301) Optionally sort user list according to account creation time
+* Remote description pages for foreign file repos are now fetched in the
+ content language.
+* (bug 17180) If $wgUseFileCache is enabled, $wgShowIPinHeader is automatically
+ set to false.
+* (bug 16604) Mark non-patrolled edits in feeds with "!"
+* (bug 16604) Show title/rev in IRC for patrol log
+* (bug 16854) Whether a page is being parsed as a preview or section preview
+ can now be determined and set with ParserOptions.
+* Wrap message 'confirmemail_pending' into a div with CSS classes "error" and
+ "mw-confirmemail-pending"
+* (bug 8249) The magic words for namespaces and pagenames can now be used as
+ parser functions to return the desired namespace or normalized title/title
+ part for a given title.
+* (bug 17110) Styled #mw-data-after-content in cologneblue.css to match the
+ rest of the font
+* (bug 7556) Time zone names in signatures lack i18n
+* (bug 3311) Automatic category redirects
+* (bug 17236) Suppress 'watch user page link' for IP range blocks
+* Wrap message 'searchresulttext' (Special:Search) into a div with
+ class "mw-searchresult"
+* (bug 15283) Interwiki imports can now fetch included templates
+* Treat svn:// URLs as external links by default
+* New function to convert namespace text for display (only applies on wiki with
+ LanguageConverter class)
+* (bug 17379) Contributions-title is now parsed for magic words.
+* Preprocessor output now cached in memcached.
+* (bug 14468) Lines in classic RecentChanges and Watchlist have classes
+ "mw-line-odd" and "mw-line-even" to make styling using css possible.
+* (bug 17311) Add a note beside the gender selection menu to tell users that
+ this information will be public
+* Localize time zone regions in Special:Preferences
+* Add NUMBEROFACTIVEUSERS magic word, which is like NUMBEROFUSERS, but uses
+ the active users data from site_stats.
+* Add a <link rel="canonical"> tag on redirected page views
+* Replace hardcoded '...' as indication of a truncation with the
+ 'ellipsis' message
+* Wrap warning message 'editinginterface' into a div with class
+ 'mw-editinginterface'
+* (bug 17497) Oasis opendocument added to mime.types
+* Remove the link to Special:FileDuplicateSearch from the "file history" section
+ of image description pages as the list of duplicated files is shown in the
+ next section anyway.
+* Added $wgRateLimitsExcludedIPs, to allow specific IPs to be whitelisted from
+ rate limits.
+* (bug 14981) Shared repositories can now have display names, located at
+ Mediawiki:Shared-repo-name-REPONAME, where REPONAME is the name in
+ $wgForeignFileRepos
+* Special:ListUsers: Sort list of usergroups by alphabet
+* (bug 16762) Special:Movepage now shows a list of subpages when possible
+* (bug 17585) Hide legend on Special:Specialpages from non-privileged users
+* Added $wgUseTagFilter to control enabling of filter-by-change-tag
+* (bug 17291) MediaWiki:Nocontribs now has an optional $1 parameter for the
+ username
+* Wrap special page summary message '$specialPageName-summary' into a div
+ with class 'mw-specialpage-summary'
+* $wgSummarySpamRegex added to handle edit summary spam. This is used *instead*
+ of $wgSpamRegex for edit summary checks. Text checks still use $wgSpamRegex.
+* New function to convert content text to specified language (only applies on wiki with
+ LanguageConverter class)
+* (bug 17844) Redirect users to a specific page when they log in, see
+ $wgRedirectOnLogin
+* Added a link to Special:UserRights on Special:Contributions for privileged users
+* (bug 10336) Added new magic word {{REVISIONUSER}}, which displays the editor
+ of the displayed revision's author user name
+* LinkerMakeExternalLink now has an $attribs parameter for link attributes and
+ a $linkType parameter for the type of external link being made
+* (bug 17785) Dynamic dates surrounded with a <span> tag, fixing sortable tables
+ with dynamic dates.
+* (bug 4582) Provide preference-based autoformatting of unlinked dates with the
+ dateformat parser function.
+* (bug 17886) Special:Export now allows you to export a whole namespace (limited
+ to 5000 pages)
+* (bug 17714) Limited TIFF upload support now built in if 'tif' extension is
+ enabled. Image width and height are now recognized, and when using ImageMagick,
+ optional flattening to PNG or JPEG for inline display can be enabled by setting
+ $wgTiffThumbnailType
+* Renamed two input IDs on Special:Log from 'page' and 'user' to 'mw-log-page' and
+ 'mw-log-user', respectively
+* Added $wgInvalidUsernameCharacters to disallow certain characters in
+ usernames during registration (such as "@")
+* Added $wgUserrightsInterwikiDelimiter to allow changing the delimiter
+ used in Special:UserRights to denote the user should be searched for
+ on a different database
+* Add a class if 'missingsummary' is triggered to allow styling of the summary
+ line
+
+=== Bug fixes in 1.15 ===
+
+* (bug 16968) Special:Upload no longer throws useless warnings.
+* (bug 17000) Special:RevisionDelete now checks if the database is locked
+ before trying to delete the edit.
+* (bug 16852) padleft and padright now handle multibyte characters correctly
+* (bug 17010) maintenance/namespaceDupes.php now add the suffix recursively if
+ the destination page exists
+* (bug 17035) Special:Upload now fails gracefully if PHP's file_uploads has
+ been disabled
+* Fixing the caching issue by using -{T|xxx}- syntax (only applies on wiki with
+ LanguageConverter class)
+* Improving the efficiency by using -{A|xxx}- syntax (only applies on wiki with
+ LanguageConverter class)
+* (bug 17054) Added more descriptive errors in Special:RevisionDelete
+* (bug 11527) Diff on page with one revision shows "Next" link to same diff
+* (bug 8065) Fix summary forcing for new pages
+* (bug 10569) redirects to Special:Mypage and Special:Mytalk are no longer
+ allowed by default. Change $wgInvalidRedirectTargets to re-enable.
+* (bug 3043) Feed links of given page are now preceded by standard feed icon
+* (bug 17150) escapeLike now escapes literal \ properly
+* Inconsistent use of sysop, admin, administrator in system messages changed
+ to 'administrator'
+* (bug 14423) Check block flag validity for block logging
+* DB transaction and slave-lag avoidance tweaks for Email Notifications
+* (bug 17104) Removed [Mark as patrolled] link for already patrolled revisions
+* (bug 17106) Added 'redirect=no' and 'mw-redirect' class to redirects at
+ "user contributions"
+* Rollback links on new pages removed from "user contributions"
+* (bug 15811) Re-upload form tweaks: license fields removed, destination locked,
+ comment label uses better message
+* Whole HTML validation ($wgValidateAllHtml) now works with external tidy
+* Parser tests no longer fail when $wgExternalLinkTarget is set in
+ LocalSettings
+* (bug 15391) catch DBQueryErrors on external storage insertion. This avoids
+ error messages on save were the edit in fact is saved.
+* (bug 17184) Remove duplicate "z" accesskey in MonoBook
+* Parser tests no longer fail when $wgAlwaysUseTidy is set in LocalSettings.php
+* Removed redundant dupe warnings on reupload for the same title. Dupe warnings
+ for identical files at different titles are still given.
+* Add 'change tagging' facility, where changes can be tagged internally with
+ certain designations, which are displayed on various summaries of changes,
+ and the entries can be styled with CSS.
+* (bug 17207) Fix regression breaking category page display on PHP 5.1
+* Categoryfinder utility class no longer fails on invalid input or gives wrong
+ results for category names that include pseudo-namespaces
+* (bug 17252) Galician numbering format
+* (bug 17146) Fix for UTF-8 and short word search for some possible MySQL
+ configs
+* (bug 7480) Internationalize database error message
+* (bug 16555) Number of links to mediawiki.org scaled back on post-installation
+* (bug 14938) Removing a section no longer leaves excess whitespace
+* (bug 17304) Fixed fatal error when thumbnails couldn't be generated for file
+ history
+* (bug 17283) Remove double URL escaping in show/hide links for log entries
+ and RevisionDeleteForm::__construct
+* (bug 17105) Numeric table sorting broken
+* (bug 17231) Transcluding special pages on wikis using language conversion no
+ longer affects the page title
+* (bug 6702) Default system messages updated/improved
+* (bug 17190) User ID on preference page no longer has delimeters
+* (bug 17341) "Powered by MediaWiki" should be on the left on RTL wikis
+* (bug 17404) "userrights-interwiki" right was missing in User::$mCoreRights
+* (bug 7509) Separation strings should be configurable
* (bug 17420) Send the correct content type from action=raw when the HTML file
cache is enabled.
+* (bug 12746) Do not allow new password e-mails when wiki is in read-only mode
+* (bug 17478) Fixed a PHP Strict standards error in
+ maintenance/cleanupWatchlist.php
+* (bug 17488) RSS/Atom links in left toolbar are now localized in classic skin
+* (bug 17472) use print <<<EOF in maintenance/importTextFile.php
+* Special:PrefixIndex: Move table styling to shared.css, add CSS IDs to tables
+ use correct message 'allpagesprefix' for input form label, replace _ with ' '
+ in next page link
+* (bug 17506) Exceptions within exceptions now respect $wgShowExceptionDetails
+* Fixed excessive job queue utilisation
+* File dupe messages for remote repos are now shown only once.
+* (bug 14980) Messages 'shareduploadwiki' and 'shareduploadwiki-desc' are now
+ used as a parameter in 'sharedupload' for easier styling and customization.
+* (bug 17482) Formatting error in Special:Preferences#Misc (Opera)
+* (bug 17556) <link> parameters in Special:Contributions feeds (RSS and Atom)
+ now point to the actual contributors' feed.
+* ForeignApiRepos now fetch MIME types, rather than trying to figure it locally
+* Special:Import: Do not show input field for import depth if
+ $wgExportMaxLinkDepth == 0
+* (bug 17570) $wgMaxRedirects is now correctly respected when following
+ redirects (was previously one more than $wgMaxRedirects)
+* (bug 16335) __NONEWSECTIONLINK__ magic word to suppress new section link.
+* (bug 17581) Wrong index name in PostgreSQL's updater: was rc_timestamp_nobot,
+ changed to rc_timestamp_bot
* (bug 17437) Fixed incorrect link to web-based installer
-* (bug 17527) Fixed missing MySQL-specific options in installer
-
-=== Configuration changes in 1.14 ===
-
-* $wgExemptFromUserRobotsControl is an array of namespaces to be exempt from
- the effect of the new __INDEX__/__NOINDEX__ magic words. (Default: null, ex-
- empt all content namespaces.)
-* $wgForwardSearchUrl has been removed entirely. Documented setting since 1.4
- has been $wgSearchForwardUrl.
-* (bug 15080) $wgOverrideSiteFeed has been added. Setting either
- $wgSiteFeed['rss'] or 'atom' to a URL will override the default Recent
- Changes feed that appears on all pages.
-* $wgSQLiteDataDirMode has been introduced as the default directory mode for
- SQLite data directories on creation. Note that this setting is separate from
- $wgDirectoryMode, which applies to all normal dirs created by MediaWiki.
-* $wgGroupsAddToSelf and $wgGroupsRemoveFromSelf now work more like
- $wgAddGroups and $wgRemoveGroups, where the user must belong to a specified
- group in order to add or remove those groups from themselves.
- Backwards compatibility is maintained.
-* $wgRestrictDisplayTitle controls if the use of the {{DISPLAYTITLE}} magic
- word is restricted to titles equivalent to the actual page title. This
- is true per default, but can be set to false to allow any title.
-* $wgSpamRegex may now be an array of multiple regular expressions.
-* $wgAjaxSearch has been removed; use $wgEnableMWSuggest instead.
-* Editing the MediaWiki namespace is now unconditionally restricted to people
- with the editinterface right, configuring this in $wgNamespaceProtection
- is not required.
-* $wgAllowExternalImagesFrom may now be an array of multiple strings.
-* Introduced $wgEnableImageWhitelist to toggle the on-wiki external image
- whitelist on or off.
-* Added $wgRenderHashAppend to append some string to the parser cache and the
- sitenotice cache keys.
-* $wgRCChangedSizeThreshold is now a positive integer by default,
-* (bug 16006) $wgEnableWriteAPI is now true by default. Authorized can perform
- write actions using the API.
-* Added $wgRC2UDPInterwikiPrefix which adds an interwiki prefix
- ($wgLocalInterwiki) onto the page names in the UDP feed.
-* Added $wgAllowUserSkin to let the wiki's owner disable user selectable skins
- on the wiki. If it's set to true, then the skin used will *always* be
- $wgDefaultSkin.
-* Added $wgEnotifUseRealName, which allows UserMailer to send out e-mails based
- on the user's real name if one is set. Defaults to false (use the username)
-* Removed the 'apiThumbCacheDir' option from $wgForeignFileRepos (only used in
- ForeignAPIRepo)
-* (bug 44) Image namespace and accompanying talk namespace renamed to File.
- For backward compatibility purposes, Image still works. External tools may
- need to be updated.
-* The constants NS_FILE and NS_FILE_TALK can now be used instead of NS_IMAGE and
- NS_IMAGE_TALK. The old constants are retained as aliases for compatibility,
- and should still be used in code meant to be compatible with v1.13 or older.
-* MediaWiki can be forced to use private IPs forwarded by a proxy server by
- using $wgUsePrivateIPs.
-* The 'BeforeWatchlist' hook has been removed due to internal changes in
- Special:Watchlist. 'SpecialWatchlistQuery' should now be used by extensions
- to customize the watchlist database query.
-
-
-=== Migrated extensions ===
-The following extensions are migrated into MediaWiki 1.14:
-
-* Special:DeletedContributions to show deleted user contributions (was
- extension DeletedContributions)
-* Special:Log/newusers recording new users (was extension Newuserlog)
-* Special:LinkSearch to search for external links (was extension LinkSearch)
-* RenderHash
-* NoMoveUserPages
-* UniversalEditButton
-
-=== New features in 1.14 ===
-
-* New URL syntaxes for Special:ListUsers - 'Special:ListUsers/USER' and
- 'Special:ListUsers/GROUP/USER', in addition to the older syntax
- 'Special:ListUsers/GROUP' where GROUP is a valid group name.
-* Configurable per-namespace and per-page notices for the edit form,
- respectively MediaWiki:Editnotice-# where # is the namespace number, and
- MediaWiki:Editnotice-#-PAGENAME where # is the page's namespace number and
- PAGENAME is the page name minus the namespace prefix.
-* (bug 8068) New __INDEX__ and __NOINDEX__ magic words allow user control of
- search engine indexing on a per-article basis.
-* Handheld stylesheet options
-* Added 'DoEditSectionLink' hook as a cleaner unified version of the old
- 'EditSectionLink' and 'EditSectionLinkForOther' hooks. Note that the
- 'EditSectionLinkForOther' hook has been removed, but 'EditSectionLink' is
- run in all cases instead, so extensions using the old hooks should still work
- if they ran roughly the same code for both hooks (as is almost certain).
-* Signature (~~~~) "cleaning", i.e. template removal, can be disabled with
- $wgCleanSignatures=false
-* Extensions can use the SkinBuildSidebar hook to modify the content of the
- sidebar and add custom portlets to it
-* Added 'MakeGlobalVariablesScript' hook for extensions to be able to add vari-
- ables into into the output of Skin::makeVariablesScript
-* (bug 13846) Added $wgAddGroups and $wgRemoveGroups display on
- Special:ListGroupRights
-* (bug 14377) Add a date selector to history pages
-* (bug 15007) New 'pagetitle-view-mainpage' message allows the HTML <title> of
- the main page to be customized
-* Added $wgDisableTitleConversion to disabling the conversion for all pages on
- the wiki
-* Added 'noconvertlink' toggle that can be set per user preferences, also
- added 'convertlink=no|yes' on GET requests whether have the link titles
- being converted or not
-* (bug 14921) Special:Contributions/: add user name to <title>
- Patch by Emufarmers
-* Unescape more "safe" characters when producing URLs, for added prettiness
-* Introduced a new hook 'SkinAfterContent' that allows extensions to add text
- after the page content and article metadata. Updated all skins and skin
- templates to work with that hook.
-* (bug 14929) removeUnusedAccounts.php now supports 'ignore-touched' and
- 'ignore-groups'. Patch by Louperivois
-* (bug 15127) Work around minor display glitch in Opera.
-* By default, reject file uploads that look like ZIP files, to avoid the
- so-called GIFAR vulnerability.
-* (bug 15141) Give ability to only list protected pages with the cascading
- option enabled on Special:ProtectedPages
-* (bug 15157) Special:Watchlist has the same options as Special:Watchlist:
- Show/Hide logged in users, Show/Hide anonymous, Invert namespace selection
-* Added hook 'UserrightsChangeableGroups' to allow modification of what
- groups may be added or removed via the Special:UserRights interface.
-* HTML entities like &nbsp; now work (are not escaped) in edit summaries.
-* (bug 13815) In the comment for page moves, use the colon-separator message
- instead of a hardcoded colon.
-* Allow <gallery> to accept image names without an Image: prefix
-* Add tooltips to rollback and undo links
-* BMP images are now displayed as PNG
-* (bug 13471) Added NUMBERINGROUP magic word
-* (bug 11884) Now support Flash EXIF attribute
-* Show thumbnails in the file history list, patch by User:Agbad
-* Added support of piped wikilinks using double-width brackets
-* Added an on-wiki external image whitelist. Items in this whitelist are
- treated as regular expression fragments to match for when possibly
- displaying an external image inline.
-* (bugs 15405, 15436) Sort more currency types correctly in sortable tables
-* (bug 15422) Sort more different types of numbers in sortable tables
-* (bug 2889) MediaWiki:Print.css applies to the printable version
-* Category counts (e.g. from {{PAGESINCATEGORY:}}) should be more accurate for
- small categories
-* After logging in, automatically redirect to wherever you logged in from
-* (bug 5619) Break messages used in Special:Statistics down further
-* (bug 11029) Add link to Special:Listusers?group=sysop etc at
- Special:Statistics
-* (bug 15514) Setting $wgRightsText without $wgRightsUrl now produces a
- plaintext copyright notice. Patch by Juliano F. Ravasi.
-* (bug 15551) Deletion log excerpt is now shown whenever a user vists a
- deleted page, even if they are unable to edit it.
-* Added Wantedfiles special pages, allowing users to find image links with no
- image.
-* (bug 12650) It is now possible to set different expiration times for
- different restriction types on the protection form.
-* (bug 8440) Allow preventing blocked users from editing their talk pages
-* Improved upload file type detection for OpenDocument formats
-* Added the ability to set the target attribute on external links with
- $wgExternalLinkTarget
-* api.php now sends "Retry-After" and "X-Database-Lag" HTTP headers if the
- maxlag check fails, just like index.php does
-* Added "link" parameter to image links, to allow images to link to an
- arbitrary title or URL. This should replace inaccessible and incomplete
- solutions such as CSS-based overlays and ImageMap.
-* (bug 368) Don't use caption for alt attribute; allow manual specification
- using new "alt=" parameter for images
-* (bug 44) The {{ns:}} core parser function now also accepts localized
- namespace names and aliases; also, its output now uses spaces instead of
- underscores to match the behavior of the {{NAMESPACE}} magic word
-* Added the ability to display user edit counts in Special:ListUsers. Off by
- default, enabled with $wgEdititis = true (named after the medical condition
- marked by unhealthy obsession with edit counts).
-* Added a file cache to the parser to improve page rendering time on pages with
- several uses of the same image.
-* (bug 1250) Users can still use "show preview" and "show changes" even if the
- wiki is set to read-only mode.
-* Added a call to the 'UnwatchArticleComplete' hook to the watchlist editor.
- This should make it so that ALL user-accessible methods of removing a page
- from a watchlist lead to this hook being called (it was previously only
- called from within Article.php
-* Maximum execution time for shell processes on linux is now configured with
- $wgMaxShellTime (180 seconds by default)
-* (bug 1306) 'Email user' link no longer shown on user page when emailing
- is not available due to lack of confirmed address or disabled preference
-* Special:Wanted templates special page added to display missing templates
- linked from articles
-* Make search matches bold only, not red as well
-* (bug 10080) Blocks can be modified without unblocking first
-* (bug 15820) Special:BlockIP shows a notice if the user being blocked is
- already directly blocked
-* (bug 13710) Allow to force "watch this" checkbox via URL using parameter
- "watchthis"
-* (bug 15125) Add Public Domain to default options when installing. Patch by
- Nathan Larson.
-* Set a special temporary directory for ImageMagick with $wgImageMagickTempDir
-* (bug 16113) Show/hide for redirects in Special:NewPages
-* (bug 15903) Upload link was added to Nostalgia skin
-* (bug 15761) Add user toggle to omit diff after rollback
-* Added the BitmapHandler_ClientOnly media handler, which allows server-side
- image scaling to be completely disabled for specific media types, via the
- $wgMediaHandlers configuration variable.
-* New 'AbortDiffCache' hook can be used to cancel the caching of a diff
-* (bug 15835) Added Content-Style-Type meta tag
-* (bug 11027) Add parameter to MW:Randompage-nopages so that user can see the
- namespace.
-* Add id="mw-user-domain-section" to <tr> tag in Userlogin.php template so that
- admins with a single domain can hide the domain section using CSS
-* Dropped old Paser_OldPP class. Only new parser with preprocessor is used.
-* Moved password reset form from Special:Preferences to Special:ResetPass
-* Added Special:ChangePassword as a special page alias for Special:ResetPass
-* Added complimentary function for addHandler() called removeHandler() for removing events
-* Improved security of file uploads for IE clients, using a reverse-engineered
- algorithm very similar to IE's content detection algorithm.
-* Cascading protection no longer requires that both edit and move are restricted
- to sysop, just edit=sysop is enough
-* (bug 2391) A warning is now shown for invalid ISBN numbers on Special:Booksources.
-* Installer has been updated to reflect the release of the GFDL 1.3. The URL for 1.2
- has been updated, and the 1.3 URL has been given. 1.2 is still Wikipedia-compatible.
- RightsCode was changed from 'gfdl' to 'gfdl1_2', so we can now support 1.2 as well
- as 1.3 (gfdl1_3).
-* (bug 16293) PD URL was changed to the CreativeCommons site on PD (which auto-detects
- your language) instead of Wikipedia.
-* (bug 16635) The "view and edit watchlist" page (Special:Watchlist/edit) now
- includes a table of contents
-* File objects returned by wfFindFile() are now cached by default
-* (bug 7492) Rights can now be assigned to specific IP addresses and ranges by
- using $wgAutopromote (new defines: APCOND_ISIP and APCOND_IPINRANGE)
-* Add a 'change block' link to Special:IPBlockList and Special:Log
-* (bug 16459) Use native getElementsByClassName where possible, for better
- performance in modern browsers
-* Enable \cancel and \cancelto in texvc (recompile required)
-* Added 'UserCryptPassword' and 'UserComparePasswords' hooks to allow extensions to implement
- their own password hashing methods.
-* (bug 16760) Add CSS-class to action links of Special:Log
-* (bug 505) Time zones can now be specified by location in user preferences,
- avoiding the need to manually update for DST. Patch by Brad Jorsch.
-* (bug 2585) HTTP 404 return code is now given for a page view if the page
- does not exist, allowing spiders and link checkers to detect broken links.
-* Special:Log: Add 'change protection' link for unprotected pages too
-* Special:Log: Add log type specific CSS classes 'mw-logline-$logtype' to
- 'li' elements
-* (bug 16754) Making arbitrary rows of sortable tables sticky:
- |- class="unsortable"
-* Show subversion too even if a "normal" version number is available
-* (bug 16121) Add a note that a page move was without creating a redirect in the
- move log
-* Image moving is now enabled for sysops by default
-* Make "Did you mean" search feature more noticeable
-* (bug 16720) Transcluded Special:NewPages processes "/username="
-
-=== Bug fixes in 1.14 ===
-
-* (bug 14907) DatabasePostgres::fieldType now defined.
-* (bug 14659) Passing the default limit param to Special:Recentchanges no more
- falls back to the user option
-* (bug 14954) Fix regression in Modern and Simple skins
-* Recursion loop check added to Categoryfinder class
-* Fixed few performance troubles of large job queue processing
-* Not setting various parameters in Foreign Repos now fails more gracefully
-* (bug 2333) Redirects are properly rendered when previewing an edit.
-* (bug 14972) Use localized alias of Special:Search on all search forms
-* (bug 11035) Special:Search should have descriptive <title>
-* Special pages are now not subject to special handling for "self-links"
-* (bug 15053) Syntactically incorrect redirects with another link in them
- no longer redirect to the second link
-* (bug 15049) Fix for CheckUser extension's log search: usernames containing
- a "-" were incorrectly turned into bogus IP range searches.
- Patch by Max Semenik.
-* (bug 15055) Talk page notifications no longer attempt to send mail when
- user's e-mail address is invalid or unconfirmed
-* (bug 12370) Add throttle on password attempts. Defaults to max 5 attempts in
- 5 minutes.
-* (bug 15016) 'Templates used on this page' list in view source should be
- wrapped in a div with class "templatesUsed"
-* (bug 14868) Setting $wgFeedDiffCutoff to 0 now disables generation of the
- diff entirely, not just the display of it.
-* (bug 6387) Introduced new setting $wgCategoryPrefixedDefaultSortkey which
- allows having the unprefixed page title as the default category sortkey
-* (bug 15079) Add class="ns-talk" / "ns-subject" to <body>. Also added
- ns-special to special pages.
-* (bug 15052) Skins should add their name as a class in <body>
-* (bug 14165, bug 14294) Wikimedia specific configuration in convertGrammar()
- for several languages was removed. The settings have been put in extension
- WikimediaMessages. Patch for Czech by Danny B.
-* (bug 15101) Displaying only bots edits in Special:Recentchanges now works
- again
-* (bug 13770) Fixed incorrect detection of PHP's DOM module
-* (bug 14790) Export of category pages when using Category: prefix now actually
- gives results
-* Avoid recursive crazy expansions in section edit comments for pages which
- contain '/*' in the title
-* Fix excessive memory usage when parsing pages with lots of links
-* $wgSpamRegex now matches the edit summary and page move descriptions in
- addition to body text.
-* Navigation links to images available from a shared repository (like Commons)
- from their local talk pages no longer appear as redlinks
-* Action=purge on ForeignApiFiles now works (purges their thumbnails and
- description pages).
-* (bug 15303) Title conversion for templates wasn't working in some cases.
-* (bug 15264) Underscores in Special:Search/Foo_bar parameters were taken
- literally; now converting them to spaces per expectation.
-* (bug 15342) "Invert" checkbox now works correctly when selecting main
- namespace in Special:Watchlist
-* (bug 15172) 'Go' button of Special:Recentchanges now on the same line as the
- last input element (like Special:Watchlist too)
-* (bug 15351) Fix fatal error for invalid section fragments in autocomments
-* Fixed intermittent deadlock errors involving objectcache table queries.
- Use a separate database connection for the objectcache table to avoid
- long-lasting locks on that table.
-* Respect file restrictions in the file history list
-* (bug 15399) Odd/even classes on sortable tables' rows could be slow for large
- tables, and have been disabled by default.
-* (bug 15482) Special:Recentchangeslinked has no longer two submit buttons
-* (bug 15292) New message notification for unregistred users now works again
-* (bug 14398) mwsuggest.js: Let width of container be configurable
-* (bug 15543) Only include user touched timestamp to generated CSS
-* (bug 15497) Removed encoding attribute from <?xml ?> tag
-* (bug 12284) Special:Preferences now sets a returnto parameter on the link to
- Special:UserLogin. Patch by Marooned.
-* Fixed the HTTP accept language string detection length in
- LanguageConverter.php, instead of the fixed length language codes.
-* Special:RecentChangesLinked no longer shows outgoing links for nonexistent
- pages even if there are broken link records with source article id 0 in the
- database
-* (bug 15598) Special:Newpages default limit uses user preference for
- recentchanges limit instead of hardcoded 50.
-* (bug 15617) $wgFeedClassesOutputPage::getHeadLinks() respects $wgFeedClasses,
- instead of hardcoding rss and atom. Patch by Juliano F. Ravasi.
-* (bug 14638) Special:Blockip now provides a link to the block log if the user
- has been blocked more than 10 times. Patch by Matt Johnston.
-* (bug 12678) Skins don't show Upload link if the user isn't allowed to upload.
-* Fixed incorrect usage of DB_LAST in Special:Export. Deprecated DB_LAST.
-* (bug 15642) Blocked sysops can no longer block other users
-* Http::request() now respects $wgHTTPtimeout when not using cURL
-* (bug 15158) Userinvalidcssjstitle not shown on preview
-* (bug 15196) Free external links should be numbered in a localised manner
-* (bug 15388) Title of Special:PrefixIndex
-* Links with no title but a curid parameter now use the curid to pick a page
-* (bug 10323) Special:Undelete should have "inverse selection" button
-* (bug 15831) Modern skin RTL support is bugous
-* (bug 15869) Nostalgia skin does not show page title in printable mode
-* (bug 15795) Special:Userrights is now listed on Special:SpecialPages when the
- user can only change his rights
-* (bug 15846) Categories "leak" from older revisions in certain circumstances
-* (bug 15928) Special pages dropdown should be inline in non-MonoBook skins
-* (bug 14178) Some uses of UserLoadFromSession hook cause segfault
-* (bug 15925) Postitive bytes added on recentchanges and watchlists are now
- bolded if above the threshold, previously it only worked for negatives
-* Specify apple-touch-icon before favicon in HTML head section to make the
- Konqueror browser correctly use the latter
-* (bug 15717) Set $separatorTransformTable for language 'eu'
-* (bug 15605) Enabled $datePreferences for language 'hr'. Added standard date
- preferences.
-* (bug 13701) {{NUMBEROFVIEWS}} magic word to show number of total views.
-* (bug 5101) Image from Commons doesn't show up when searched in Wikipedia
- search box
-* (bug 14609) User's namespaces to be searched default not updated after adding
- new namespace
-* Purge form uses valid XHTML
-* (bug 12764) Special:LonelyPages shows transcluded pages
-* (bug 16073) Enhanced RecentChanges uses onclick handler with better fallback
- if JavaScript is disabled
-* (bug 4253) Recentchanges IRC messages no longer include title in diff URLs
-* Allow '0' to be an accesskey.
-* (bug 8063) Use language-dependent sorting in client-side sortable tables
-* (bug 16160) Suggestions box should be resized from left for RTL wikis
-* (bug 11533) Fixed insane slowdown when in read-only mode for long periods
- of time with CACHE_NONE (default objectcache table configuration).
-* Trying to set two different default category sort keys for one page now
- produces a warning
-* (bug 16143) Fix redirect loop on special pages starting with lower case
- letters
-* (bug 15737) Fix notices while expanding using PPCustomFrame
-* (bug 15544) Non-index entry points cause the "Wiki not set up" message to
- have corrupt URLs
-* (bug 5101) Image from Commons doesn't show up when searched in Wikipedia
- search box
-* (bug 4362) [[MediaWiki:History copyright]] no more used with most recent
- revision when passing oldid parameter in the url
-* (bug 16265) When caching thumbs with the ForeignApiRepo, we now use the same
- filename as the remote site.
-* (bug 8345) Don't autosummarize where a redirect was left unchanged
-* Made thumb caching in ForeignApiFile objects integrated with normal thumb
- path naming (/thumbs/hash/file), retired 'apiThumbCacheDir' as a result.
-* (bug 5530) Consistency between character encoding in {{PAGENAMEE}},
- {{SUBPAGENAMEE}} and {{FULLPAGENAMEE}}
-* Safer handling of non-MediaWiki exceptions -- now obeys our settings for
- formatting and path exposure.
-* Less verbose errors from profileinfo.php when not configured
-* Blacklist redirects via Special:Filepath, hard to use.
-* Improved input validation on Special:Import form
-* Add a .htaccess to deleted images directory for additional protection
- against exposure of deleted files with known SHA-1 hashes on default
- installations.
-* Improved scripting safety heuristics for IE 5/6 content-type detection.
-* Improved scripting safety heuristics on SVG uploads.
-* (bug 11728) Unify layout of enhanced watchlist/recent changes
-* (bug 8702) Properly update stats when running nukePage maintenance script
-* (bug 7726) Searches for words less than 4 characters now work without
- requiring customization of MySQL server settings
-* Honour unchecked "Leave a redirect behind" for moved subpages
-* (bug 16440) Broken 0-byte math renderings are now deleted and re-rendered
- when page is re-parsed.
-* (bug 6100) Unicode BiDi embedding/override characters (U+202A - U+202E) are
- now automatically removed from titles; these characters can accidentally end
- up in copy-and-pasted titles, and, by overriding normal bidirectional text
- handling, can lead to annoying behavior such as text rendering backwards
-* Fixed minor bug where the memcached value for how many accounts an IP had
- created that day would be increased even if $wgAccountCreationThrottle was
- hit. This meant if an IP hit the throttle and then the throttle was raised
- later that day, the IP still couldn't create another account, because it
- had marked them as having created another account, when their last account
- creation had actually failed.
-* (bug 12647) Allow autogenerated edit summary messages to be blanked with '-'
-* (bug 16026) 'Revision-info' and 'revision-info-current' both accept wiki
- markup now.
-* (bug 16529) Fix for search suggestions with some third-party JS libraries
-* (bug 13342) importScript() generates more consistent URI encoding
-* (bug 16577) When a blocked user tries to rollback a page, the block message
- is now only displayed once
-* (bug 14268) SVG image sizes now extracted with proper XML parser
-* (bug 14365) RepoGroup::findFiles() no longer crashes if passed an invalid
- title via the API
-* (bug 4253, bug 16586) Revision ID is now given instead of title in URLs for
- new pages in the recent changes IRC feed
-* Ugly tooltips in Special:Statistics were phased out in favor of more direct
- information. Went ahead and rewrote SpecialStatistics to subclass SpecialPage
-* (bug 5506) Links to files on foreign repositories are now shown consistently
- as bluelinks e.g. in logs and edit summaries
-* (bug 16623) Add missing </p> tag in Special:LockDB
-* (bug 15849) Special:Movepage now throws a more specific error when trying to
- move a title to an interwiki target
-* (bug 16638) 8-bit URL fallback encoding now set on additional languages using
- Arabic script (Persian, Urdu, Sindhi, Punjabi)
-* (bug 16656) cleanupTitles and friends should now work in load-balanced
- DB environments when $wgDBserver isn't set.
-* (bug 3691) Aspect ratio from viewBox attribute is now preserved for SVG
- images which do not specify width and height attributes.
-* (bug 15027) Internet domain names and IP addresses can now be indexed and
- searched sensibly with the default MySQL search backend.
-* (bug 11733) Fixed parameter validation in importTextFile.php
-* (bug 16712) Special:NewFiles updated to use "newer"/"older" paging messages
- for clarity over "previous/next"
-* (bug 16612) Fixed "noprint" class for Modern skin print style
-* Section anchors now have an "id" attribute as well as a "name" attribute,
- even when Tidy is not used
-* (bug 16026) revision-info, revision-info-current, cannotdelete,
- redirectedfrom, historywarning and difference messages now use Wiki text
- rather than raw HTML markup
-* (bug 13835) Fix rendering of {{filepath:Wiki.png|nowiki}}
-* (bug 16772) Special:Upload now correctly rejects files with spaces in the
- file extension (e.g. Foo. jpg).
-* Image moving over an existing file no longer throws a database error
-* (bug 16786) Restored "redundant" links recently removed from Classic sidebar
-* (bug 16850) $wgActionPaths can have query strings now, previously, this broke
- local URLs
-* (bug 16376) Mention in deleteBatch.php and moveBatch.php maintenance scripts
- that STDIN can be used for page list
-* (bug 16560) Special:Random returns a page from ContentNamespaces, and no
- longer from NS_MAIN
-* (bug 16123) Fixed Special:Import on SQLite.
-* (bug 16937) Show appropriate error message for attempted installs on
- PostgreSQL 7.3 or earlier.
-* Disabled SQLite support in the installer.
-* Fixed XSS vulnerabilities in the web-based installer.
-* Added a meta robots tag to the installer to prevent indexing of potentially
- sensitive configuration data.
-* (bug 16483) Prevented a filesort in ApiQueryBacklinks caused by missing parentheses.
+* (bug 17538) Use shorter URLs in <link> elements
+* (bug 13778) Hidden input added to the search form so that using the Enter key
+ on IE will do a fulltext search like clicking the button does
+* (bug 1061) CSS-added icons next to links display through the text and makes
+ it unreadable in RTL
+* Special:Wantedtemplates now works on PostgreSQL
+* (bug 14414) maintenance/updateSpecialPages.php no longer throws error with
+ PostgreSQL
+* (bug 17546) Correct Tongan language native name is "lea faka-Tonga"
+* (bug 17621) Special:WantedFiles has no link to Special:Whatlinkshere
+* (bug 17460) Client ecoding is now correctly set for PostgreSQL
+* (bug 17648) Prevent floats from intruding into edit area in previews if no
+ toolbar present
+* (bug 17692) Added (list of members) link to 'user' in Special:Listgrouprights
+* (bug 17707) Show file destination as plain text if &wpForReUpload=1
+* (bug 10172) Moved setting of "changed since last visit" flags out of the job
+ queue
+* (bug 17761) "show/hide" link in page history in now works for the first
+ displayed revision if it's not the current one
+* (bug 17722) Fix regression where users are unable to change temporary passwords
+* (bug 17799) Special:Random no longer throws a database error when a non-
+ namespace is given, silently falls back to NS_MAIN
+* (bug 17751) The message for bad titles in WantedPages is now localized
+* (bug 17860) Moving a page in the "MediaWiki" namespace using SuppressRedirect
+ no longer corrupts the message cache
+* (bug 17900) Fixed User Groups interface log display after saving groups.
+* (bug 17897) Fixed string offset error in <pre> tags
+* (bug 17778) MediaWiki:Catseparator can now have HTML entities
+* (bug 17676) Error on Special:ListFiles when using Postgres
+* Special:Export doesn't use raw SQL queries anymore
+* (bug 14771) Thumbnail links to individual DjVu pages have two no longer have
+ two "page" parameters
+* (bug 17972) Special:FileDuplicateSearch form now works correctly on wikis that
+ don't use PathInfo or short urls
+* (bug 17990) trackback.php now has a trackback.php5 alias and works with
+ $wgScriptExtension
+* (bug 14990) Parser tests works again with PostgreSQL
+* (bug 11487) Special:Protectedpages doesn't list protections with pr_expiry
+ IS NULL
+* (bug 18018) Deleting a file redirect leaves behind a malfunctioning redirect
+* (bug 17537) Disable bad zlib.output_compression output on HTTP 304 responses
+* (bug 11213) [edit] section links in printable version no longer appear when
+ you cut-and-paste article text
+* (bug 17405) "Did you mean" to mirror Go/Search behavior of original request
+* (bug 18116) 'edittools' is now output identically on edit and upload pages
+* (bug 17241) The diffonly URI parameter should cascade to "Next edit" and
+ "Previous edit" diff links
+* (bug 16823) 'Sidebar search form should not use Special:Search view URL as
+ target'
+* (bug 16343) Non-existing, but in use, category pages can be "go" match hits
+* Fixed the circular template inclusion check, was broken when the loop
+ involved redirects. Without this, infinite recursion within the parser is
+ possible.
+* (bug 17611) Provide a sensible error message on install when the SQLite data
+ directory is wrong.
+* (bug 16937) Fixed PostgreSQL installation on Windows, workaround for upstream
+ pg_version() bug.
+* (bug 11451) Fix upgrade from MediaWiki 1.2 or earlier (imagelinks schema).
+* Fixed SQLite indexes, installation and upgrade. Reintroduced it as an option
+ to the installer.
+* (bug 18170) Fixed a PHP warning in Parser::preSaveTransform() in PHP 5.3
+* (bug 8873) Enable variant conversion in text on 'alt' and 'title' attributes
+
+== API changes in 1.15 ==
+
+* (bug 16858) Revamped list=deletedrevs to make listing deleted contributions
+ and listing all deleted pages possible
+* (bug 16844) Added clcategories parameter to prop=categories
+* (bug 17025) Add "fileextension" parameter to meta=siteinfo&siprop=
+* (bug 17048) Show the 'new' flag in list=usercontribs for the revision that
+ created the page, even if it's not the top revision
+* (bug 17069) Added ucshow=patrolled|!patrolled to list=usercontribs
+* action=delete respects $wgDeleteRevisionsLimit and the bigdelete user right
+* (bug 15949) Add undo functionality to action=edit
+* (bug 16483) Kill filesort in ApiQueryBacklinks caused by missing parentheses.
Building query properly now using makeList()
-
-=== API changes in 1.14 ===
-
-* Registration time of users registered before the DB field was created is now
- shown as empty instead of the current time.
-* API search now falls back to fulltext search by default when using Lucene
- or other engine which doesn't support a separate title search function.
- This means you can use API search on Wikipedia without explicitly adding
- &srwhat=text to the query.
-* Added iiprop=bitdepth to imageinfo and aiprop=bitdepth to allimages
-* (bug 14713) API-specific permissions (such as 'writeapi' and 'apihighlimits'
- are now listed on action=help
-* (bug 15044) Added requestid parameter to api.php to facilitate distinguishing
- between requests
-* (bug 15048) Added limit field for multivalue parameters to action=paraminfo
- output.
-* When the limit on multivalue parameters is exceeded, a warning is issued
-* list=search doesn't list missing pages any more
-* (bug 15178) Added clshow to prop=categories to allow filtering for hidden/
- non-hidden categories
-* (bug 15228) Combining revids= and redirects now throws a warning instead of
- an error, and still resolves redirects generated by the generator.
-* list={backlinks,embeddedin,imageusage} now return arrays with keys 0, 1, 2,
- etc. (AKA lists) instead of arrays with pageIDs as keys (AKA hash tables)
- for consistency with other list modules.
-* Added action=watch
-* (bug 15275) apprefix and related parameters ignore spaces at the end
-* action=edit no longer throws unknown error 228 when trying to create an
- empty section with section=new
-* Database replication lag doesn't cause all action=edit requests to return the
- nochange flag any more
-* (bug 15392) ApiFormatBase::formatHTML now uses $wgUrlProtocols.
-* (bug 15444) action=edit returns "Unknown error: ``AS_END''" where it should
- return just "Unknown error"
-* (bug 15448) YAML output returns empty values instead of 0
-* (bug 15445) Added action=patrol
-* (bug 15466) Added action=purge
-* (bug 15486) action=block ignores autoblock parameter
-* (bug 15492) added rcprop=loginfo to list=recentchanges
-* (bug 15527) action=rollback can now revert anonymous editors
-* (bug 15535) prop=info&inprop=protection doesn't list pre-1.10 protections
- if the page is also protected otherwise (1.10+ style or cascading)
-* list=random now has rnredirect parameter, to get random redirects.
-* Added APIAfterExecute, APIQueryAfterExecute and APIQueryGeneratorAfterExecute
- hooks which allow for extending core modules in a cleaner way
-* action=protect checks for invalid protection types and levels
-* (bug 15673) Added indentation to format=wddxfm output and improved built-in
- WDDX formatter to resemble PHP's more
-* (bug 15706) Empty values for apprtype and apprlevel are now silently ignored
- rather than causing an exception
-* Added uiprop=preferencestoken to meta=userinfo
-* (bug 15609) Add inprop=url and inprop=readable to prop=info
-* Add ApiDisabled and ApiQueryDisabled classes so individual modules can
- be disabled in LocalSettings.php
-* (bug 15653) Add prop=duplicatefiles
-* (bug 15768) Add list=watchlistraw
-* (bug 15647) action=edit with basetimestamp fails if the page has been deleted
- and undeleted since the last edit
-* (bug 15785) Allow for different expiry times for different protections in
- action=protect
-* Added allowsduplicates attribute to action=paraminfo output
-* (bug 15767) apfilterlanglinks returns duplicate results
-* (bug 15845) Added pageid/fromid parameter to action=delete/move, making
- manipulation of legacy pages with invalid titles possible
-* (bug 15881) Empty or invalid parameters cause database errors
-* The maxage and smaxage parameters are now properly validated
-* (bug 15945) list=recentchanges doesn't check $wgUseRCPatrol, $wgUseNPPatrol
- and patrolmarks right
-* (bug 15985) acfrom and aifrom parameters didn't work when sorting in
- descending order.
-* (bug 15995) Add cmstartsortkey and cmendsortkey parameters to
- list=categorymembers
-* (bug 16017) list=categorymembers sets invalid continue parameters for
- sortkeys containing pipes
-* (bug 16018) Added uccontinue parameter to list=usercontribs so paging
- works properly when multiple users are queried or a userprefix is used
-* (bug 16047) Added activeusers attribute to meta=siteinfo&siprop=statistics
- output
-* Added redirect resolution to action=parse
-* (bug 16074) rvprop=content combined with a generator with a high limit causes
- an error
-* (bug 16105) Image metadata attributes containing spaces result in invalid XML
-* (bug 16126) Added siprop=magicwords to meta=siteinfo
-* (bug 16159) Added wlshow=patrolled|!patrolled to list=watchlist
-* (bug 16225) Titles like Talk:Talk:Foo broke apfrom and friends
-* meta=siteinfo&siprop=interwikimap no longer throws an exception for empty
- sifilter parameter.
-* (bug 12760) meta=userinfo&uiprop=ratelimits doesn't list group-specific rate
- limits
-* (bug 16398) meta=userinfo&uiprop=rights lists some rights twice in some cases
-* (bug 16408) Added rvgeneratexml to prop=revisions
-* (bug 16421) Made list=logevents's leuser accept user names with underscores
- instead of spaces
-* (bug 16516) Made rvsection=T-2 work
-* (bug 16526) Added usprop=emailable to list=users
-* (bug 16548) list=search threw errors with an invalid error code
-* (bug 16515) Added pst and onlypst parameters to action=parse
-* (bug 16541) Added block expiry timestamp to list=logevents output
-* (bug 16613) action=protect doesn't tell when &cascade was set but cascading
- protection wasn't allowed
-* (bug 16626) action=delete now correctly handles empty "reason" param
-* (bug 15579) clshow considers all categories !hidden
-* (bug 16647) list=allcategories, prop=categories don't return "hidden"
- property for hidden categories
-* New siprop parameter of 'extensions' to list all installed extensions
-* (bug 16672) Include canonical namespace name in
- meta=siteinfo&siprop=namespaces.
-* (bug 16726) siprop=namespacealiases should also list localized aliases
-* (bug 16730) Added apprfiltercascade parameter to list=allpages to filter
- cascade-protected pages
-* (bug 16798) JSON encoding errors for some characters outside the BMP
-* (bug 16629) prop=info&inprop=protection lists empty legacy protections
- incorrectly
-* (bug 15261, 16262) API no longer outputs invalid UTF-8
-* Fix broken list=alllinks paging and make alunique actually work
-
-=== Languages updated in 1.14 ===
+* (bug 17182) Fix pretty printer so URLs with parentheses in them are
+ autolinked correctly
+* (bug 17224) Added siprop=rightsinfo to meta=siteinfo
+* (bug 17239) Added prop=displaytitle to action=parse
+* (bug 17317) Added watch parameter to action=protect
+* (bug 17007) Added export and exportnowrap parameters to action=query
+* (bug 17326) BREAKING CHANGE: Changed output format for iiprop=metadata
+* (bug 17355) Added auwitheditsonly parameter to list=allusers
+* (bug 17007) Added action=import
+* BREAKING CHANGE: Removed rctitles parameter from list=recentchanges because
+ of performance concerns
+* Listing (semi-)deleted revisions and log entries as well in prop=revisions
+ and list=logevents
+* (bug 11430) BREAKING CHANGE: Modules may return fewer results than the
+ limit and still set a query-continue in some cases
+* (bug 17357) Added movesubpages parameter to action=move
+* (bug 17433) Added bot flag to list=watchlist&wlprop=flags output
+* (bug 16740) Added list=protectedtitles
+* Added mainmodule and pagesetmodule parameters to action=paraminfo
+* (bug 17502) meta=siteinfo&siprop=namespacealiases no longer lists namespace
+ aliases already listed in siprop=namespaces
+* (bug 17529) rvend ignored when rvstartid is specified
+* (bug 17626) Added uiprop=email to list=userinfo
+* (bug 13209) Added rvdiffto parameter to prop=revisions
+* Manual language conversion improve: Now we can include both ";" and ":" in
+ conversion rules
+* (bug 17795) Don't report views count on meta=siteinfo if $wgDisableCounters
+ is set
+* (bug 17774) Don't hide read-restricted modules like action=query from users
+ without read rights, but throw an error when they try to use them.
+* Don't hide write modules when $wgEnableWriteAPI is false, but throw an error
+ when someone tries to use them
+* BREAKING CHANGE: action=purge requires write rights and, for anonymous users,
+ a POST request
+* (bug 18099) Using appendtext to edit a non-existent page causes an interface
+ message to be included in the page text
+* (bug 18601) generator=backlinks returns invalid continue parameter
+* (bug 18597) Internal error with empty generator= parameter
+* (bug 18617) Add xml:space="preserve" attribute to relevant tags in XML output
+
+=== Languages updated in 1.15 ===
MediaWiki supports over 300 languages. Many localisations are updated
-regularly. Below only new and removed languages are listed.
-
-* Bakhtiari (bqi) (new)
-* Fiji Hindi (Devanagari script) (hif-deva) (new)
-* Krio (kri) (new)
-* Lezghian (lez) (new)
-* Laz (lzz) (new)
-* Eastern Mari (mhr) (new)
-* Niuean (niu) (new)
-* Oromo (om) (new)
-* Plautdietsch (pdt) (new)
-* Western Punjabi (pnb) (new)
-* Tarantino (roa-tara) (new)
-* Serbo-Croatian (sh) (new)
-* Tulu (tcy) (new)
+regularly. Below only new and removed languages are listed, as well as
+changes to languages because of MediaZilla reports.
+
+* Austrian German (de-at) (new)
+* Swiss Standard German (de-ch) (new)
+* Simplified Gan Chinese (gan-hans) (new)
+* Traditional Gan Chinese (gan-hant) (new)
+* Literary Chinese (lzh) (new)
+* Uyghur (Latin script) (ug-latn) (renamed from 'ug')
+* Veps (vep) (new)
+* Võro (vro) (renamed from fiu-vro)
+* (bug 17151) Add magic word alias for #redirect for Vietnamese
+* (bug 17288) Messages improved for default language (English)
+* (bug 12937) Update native name for Afar
+* (bug 16909) 'histlegend' now reuses messages instead of copying them
+* (bug 17832) action=delete returns 'unknownerror' instead of 'permissiondenied'
+ when the user is blocked
+* Traditional/Simplified Gan Chinese conversion support
== Compatibility ==
-MediaWiki 1.14 requires PHP 5 (5.2 recommended). PHP 4 is no longer supported.
+MediaWiki 1.15 requires PHP 5 (5.2 recommended). PHP 4 is no longer supported.
PHP 5.0.x fails on 64-bit systems due to serious bugs with array processing:
http://bugs.php.net/bug.php?id=34879
@@ -641,7 +410,7 @@ At this time we still recommend 4.0, but 4.1/5.0 will work fine in most cases.
== Upgrading ==
-1.14 has several database changes since 1.13, and will not work without schema
+1.15 has several database changes since 1.14, and will not work without schema
updates.
If upgrading from before 1.11, and you are using a wiki as a commons reposito-
@@ -666,7 +435,7 @@ set $wgMimeType = "application/xhtml+xml"; to test for remaining problem
cases, but this is not recommended on live sites. (This must be set for
MathML to display properly in Mozilla.)
-For notes on 1.13.x and older releases, see HISTORY.
+For notes on 1.14.x and older releases, see HISTORY.
=== Online documentation ===
diff --git a/api.php b/api.php
index 817f4bca..58e06d88 100644
--- a/api.php
+++ b/api.php
@@ -1,25 +1,27 @@
<?php
-/*
-* API for MediaWiki 1.8+
-*
-* Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
-*
-* This program is free software; you can redistribute it and/or modify
-* it under the terms of the GNU General Public License as published by
-* the Free Software Foundation; either version 2 of the License, or
-* (at your option) any later version.
-*
-* This program is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License along
-* with this program; if not, write to the Free Software Foundation, Inc.,
-* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-* http://www.gnu.org/copyleft/gpl.html
-*/
+/**
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
/**
* This file is the entry point for all API queries. It begins by checking
@@ -67,6 +69,13 @@ if (!$wgEnableAPI) {
die(1);
}
+// So extensions can check whether they're running in API mode
+define('MW_API', true);
+
+// Set a dummy $wgTitle, because $wgTitle == null breaks various things
+// In a perfect world this wouldn't be necessary
+$wgTitle = Title::newFromText('API');
+
/* Construct an ApiMain with the arguments passed via the URL. What we get back
* is some form of an ApiMain, possibly even one that produces an error message,
* but we don't care here, as that is handled by the ctor.
diff --git a/config/index.php b/config/index.php
index ced8bc92..85fdb86f 100644
--- a/config/index.php
+++ b/config/index.php
@@ -47,11 +47,13 @@ require_once( "$IP/includes/Namespace.php" );
require_once( "$IP/includes/ProfilerStub.php" );
require_once( "$IP/includes/GlobalFunctions.php" );
require_once( "$IP/includes/Hooks.php" );
+require_once( "$IP/includes/Exception.php" );
# If we get an exception, the user needs to know
# all the details
$wgShowExceptionDetails = true;
-
+$wgShowSQLErrors = true;
+wfInstallExceptionHandler();
## Databases we support:
$ourdb = array();
@@ -67,13 +69,11 @@ $ourdb['postgres']['compile'] = 'pgsql';
$ourdb['postgres']['bgcolor'] = '#aaccff';
$ourdb['postgres']['rootuser'] = 'postgres';
-/*** SQLITE DISABLED -- USE MEDIAWIKI 1.15 */
-#$ourdb['sqlite']['fullname'] = 'SQLite';
-#$ourdb['sqlite']['havedriver'] = 0;
-#$ourdb['sqlite']['compile'] = 'pdo_sqlite';
-#$ourdb['sqlite']['bgcolor'] = '#b1ebb1';
-#$ourdb['sqlite']['rootuser'] = '';
-/************************************/
+$ourdb['sqlite']['fullname'] = 'SQLite';
+$ourdb['sqlite']['havedriver'] = 0;
+$ourdb['sqlite']['compile'] = 'pdo_sqlite';
+$ourdb['sqlite']['bgcolor'] = '#b1ebb1';
+$ourdb['sqlite']['rootuser'] = '';
$ourdb['mssql']['fullname'] = 'MSSQL';
$ourdb['mssql']['havedriver'] = 0;
@@ -81,6 +81,12 @@ $ourdb['mssql']['compile'] = 'mssql not ready'; # Change to 'mssql' after
$ourdb['mssql']['bgcolor'] = '#ffc0cb';
$ourdb['mssql']['rootuser'] = 'administrator';
+$ourdb['ibm_db2']['fullname'] = 'DB2';
+$ourdb['ibm_db2']['havedriver'] = 0;
+$ourdb['ibm_db2']['compile'] = 'ibm_db2';
+$ourdb['ibm_db2']['bgcolor'] = '#ffeba1';
+$ourdb['ibm_db2']['rootuser'] = 'db2admin';
+
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
@@ -621,6 +627,12 @@ print "<li style='font-weight:bold;color:green;font-size:110%'>Environment check
## MSSQL specific
// We need a second field so it doesn't overwrite the MySQL one
$conf->DBprefix2 = importPost( "DBprefix2" );
+
+ ## DB2 specific:
+ // New variable in order to have a different default port number
+ $conf->DBport_db2 = importPost( "DBport_db2", "50000" );
+ $conf->DBmwschema = importPost( "DBmwschema", "mediawiki" );
+ $conf->DBcataloged = importPost( "DBcataloged", "cataloged" );
$conf->ShellLocale = getShellLocale( $conf->LanguageCode );
@@ -644,6 +656,8 @@ if( $conf->DBpassword != $conf->DBpassword2 ) {
}
if( !preg_match( '/^[A-Za-z_0-9]*$/', $conf->DBprefix ) ) {
$errs["DBprefix"] = "Invalid table prefix";
+} else {
+ untaint( $conf->DBprefix, TC_MYSQL );
}
error_reporting( E_ALL );
@@ -720,7 +734,7 @@ $conf->MCServers = importRequest( "MCServers" );
/* Test memcached servers */
if ( $conf->Shm == 'memcached' && $conf->MCServers ) {
- $conf->MCServerArray = array_map( 'trim', explode( ',', $conf->MCServers ) );
+ $conf->MCServerArray = wfArrayMap( 'trim', explode( ',', $conf->MCServers ) );
foreach ( $conf->MCServerArray as $server ) {
$error = testMemcachedServer( $server );
if ( $error ) {
@@ -792,6 +806,9 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) {
$wgDBprefix = $conf->DBprefix2;
}
+ ## DB2 specific:
+ $wgDBcataloged = $conf->DBcataloged;
+
$wgCommandLineMode = true;
if (! defined ( 'STDERR' ) )
define( 'STDERR', fopen("php://stderr", "wb"));
@@ -802,7 +819,9 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) {
$wgTitle = Title::newFromText( "Installation script" );
error_reporting( E_ALL );
print "<li>Loading class: " . htmlspecialchars( $dbclass ) . "</li>\n";
- $dbc = new $dbclass;
+ if ( $conf->DBtype != 'sqlite' ) {
+ $dbc = new $dbclass;
+ }
if( $conf->DBtype == 'mysql' ) {
$mysqlOldClient = version_compare( mysql_get_client_info(), "4.1.0", "lt" );
@@ -867,12 +886,71 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) {
} #conn. att.
if( !$ok ) { continue; }
+ }
+ else if( $conf->DBtype == 'ibm_db2' ) {
+ if( $useRoot ) {
+ $db_user = $conf->RootUser;
+ $db_pass = $conf->RootPW;
+ } else {
+ $db_user = $wgDBuser;
+ $db_pass = $wgDBpassword;
+ }
+
+ echo( "<li>Attempting to connect to database \"" . htmlspecialchars( $wgDBname ) .
+ "\" as \"" . htmlspecialchars( $db_user ) . "\"..." );
+ $wgDatabase = $dbc->newFromParams($wgDBserver, $db_user, $db_pass, $wgDBname, 1);
+ if (!$wgDatabase->isOpen()) {
+ print " error: " . htmlspecialchars( $wgDatabase->lastError() ) . "</li>\n";
+ } else {
+ $myver = $wgDatabase->getServerVersion();
+ }
+ if (is_callable(array($wgDatabase, 'initial_setup'))) $wgDatabase->initial_setup('', $wgDBname);
+ } elseif ( $conf->DBtype == 'sqlite' ) {
+ if ("$wgSQLiteDataDir" == '') {
+ $wgSQLiteDataDir = dirname($_SERVER['DOCUMENT_ROOT']).'/data';
+ }
+ echo "<li>Attempting to connect to SQLite database at \"" .
+ htmlspecialchars( $wgSQLiteDataDir ) . "\"";
+ if ( !is_dir( $wgSQLiteDataDir ) ) {
+ if ( is_writable( dirname( $wgSQLiteDataDir ) ) ) {
+ $ok = wfMkdirParents( $wgSQLiteDataDir, $wgSQLiteDataDirMode );
+ } else {
+ $ok = false;
+ }
+ if ( !$ok ) {
+ echo ": cannot create data directory</li>";
+ $errs['SQLiteDataDir'] = 'Enter a valid data directory';
+ continue;
+ }
+ }
+ if ( !is_writable( $wgSQLiteDataDir ) ) {
+ echo ": data directory not writable</li>";
+ $errs['SQLiteDataDir'] = 'Enter a writable data directory';
+ continue;
+ }
+ $dataFile = "$wgSQLiteDataDir/$wgDBname.sqlite";
+ if ( file_exists( $dataFile ) && !is_writable( $dataFile ) ) {
+ echo ": data file not writable</li>";
+ $errs['SQLiteDataDir'] = "$wgDBname.sqlite is not writable";
+ continue;
+ }
+ $wgDatabase = new DatabaseSqlite( false, false, false, $wgDBname, 1 );
+ if (!$wgDatabase->isOpen()) {
+ print ": error: " . htmlspecialchars( $wgDatabase->lastError() ) . "</li>\n";
+ $errs['SQLiteDataDir'] = 'Could not connect to database';
+ continue;
+ } else {
+ $myver = $wgDatabase->getServerVersion();
+ }
+ if (is_callable(array($wgDatabase, 'initial_setup'))) $wgDatabase->initial_setup('', $wgDBname);
+ echo "ok</li>\n";
} else { # not mysql
error_reporting( E_ALL );
$wgSuperUser = '';
## Possible connect as a superuser
- if( $useRoot && $conf->DBtype != 'sqlite' ) {
+ // Changed !mysql to postgres check since it seems to only apply to postgres
+ if( $useRoot && $conf->DBtype == 'postgres' ) {
$wgDBsuperuser = $conf->RootUser;
echo( "<li>Attempting to connect to database \"postgres\" as superuser \"" .
htmlspecialchars( $wgDBsuperuser ) . "\"..." );
@@ -1126,6 +1204,8 @@ if( $conf->posted && ( 0 == count( $errs ) ) ) {
$revid = $revision->insertOn( $wgDatabase );
$article->updateRevisionOn( $wgDatabase, $revision );
}
+ // Now that all database work is done, make sure everything is committed
+ $wgDatabase->commit();
/* Write out the config file now that all is well */
print "<li style=\"list-style: none\">\n";
@@ -1450,10 +1530,6 @@ if( count( $errs ) ) {
</div>
</fieldset>
- <?php
- # SQLITE DISABLED -- USE MEDIAWIKI 1.15
- if (false):
- ?>
<?php database_switcher('sqlite'); ?>
<div class="config-desc">
<b>NOTE:</b> SQLite only uses the <i>Database name</i> setting above, the user, password and root settings are ignored.
@@ -1470,11 +1546,6 @@ if( count( $errs ) ) {
</div>
</fieldset>
- <?php
- # SQLITE DISABLED -- USE MEDIAWIKI 1.15
- endif
- ?>
-
<?php database_switcher('mssql'); ?>
<div class="config-input"><?php
aField( $conf, "DBprefix2", "Database table prefix:" );
@@ -1487,6 +1558,25 @@ if( count( $errs ) ) {
<p>Avoid exotic characters; something like <tt>mw_</tt> is good.</p>
</div>
</fieldset>
+
+ <?php database_switcher('ibm_db2'); ?>
+ <div class="config-input"><?php
+ aField( $conf, "DBport_db2", "Database port:" );
+ ?></div>
+ <div class="config-input"><?php
+ aField( $conf, "DBmwschema", "Schema for mediawiki:" );
+ ?></div>
+ <div>Select one:</div>
+ <ul class="plain">
+ <li><?php aField( $conf, "DBcataloged", "Cataloged (DB2 installed locally)", "radio", "cataloged" ); ?></li>
+ <li><?php aField( $conf, "DBcataloged", "Uncataloged (remote DB2 through ODBC)", "radio", "uncataloged" ); ?></li>
+ </ul>
+ <div class="config-desc">
+ <p>If you need to share one database between multiple wikis, or
+ between MediaWiki and another web application, you may specify
+ a different schema to avoid conflicts.</p>
+ </div>
+ </fieldset>
<div class="config-input" style="padding:2em 0 3em">
<label class='column'>&nbsp;</label>
@@ -1495,7 +1585,7 @@ if( count( $errs ) ) {
</div>
</form>
<script type="text/javascript">
-window.onload = toggleDBarea(<?php echo Xml::encodeJsVar( $conf->DBtype ); ?>,
+window.onload = toggleDBarea( <?php echo Xml::encodeJsVar( $conf->DBtype ); ?>,
<?php
## If they passed in a root user name, don't populate it on page load
echo strlen(importPost('RootUser', '')) ? 0 : 1;
@@ -1627,7 +1717,7 @@ function writeLocalSettings( $conf ) {
}
# Add slashes to strings for double quoting
- $slconf = array_map( "escapePhpString", get_object_vars( $conf ) );
+ $slconf = wfArrayMap( "escapePhpString", get_object_vars( $conf ) );
if( $conf->License == 'gfdl1_2' || $conf->License == 'pd' || $conf->License == 'gfdl1_3' ) {
# Needs literal string interpolation for the current style path
$slconf['RightsIcon'] = $conf->RightsIcon;
@@ -1657,6 +1747,12 @@ function writeLocalSettings( $conf ) {
$dbsettings =
"# MSSQL specific settings
\$wgDBprefix = \"{$slconf['DBprefix2']}\";";
+ } elseif( $conf->DBtype == 'ibm_db2' ) {
+ $dbsettings =
+"# DB2 specific settings
+\$wgDBport_db2 = \"{$slconf['DBport_db2']}\";
+\$wgDBmwschema = \"{$slconf['DBmwschema']}\";
+\$wgDBcataloged = \"{$slconf['DBcataloged']}\";";
} else {
// ummm... :D
$dbsettings = '';
@@ -1806,6 +1902,7 @@ function importVar( &$var, $name, $default = "" ) {
} else {
$retval = $default;
}
+ taint( $retval );
return $retval;
}
@@ -2007,7 +2104,7 @@ function getShellLocale( $wikiLang ) {
return false;
}
- $lines = array_map( 'trim', $lines );
+ $lines = wfArrayMap( 'trim', $lines );
$candidatesByLocale = array();
$candidatesByLang = array();
foreach ( $lines as $line ) {
@@ -2051,6 +2148,17 @@ function getShellLocale( $wikiLang ) {
return false;
}
+function wfArrayMap( $function, $input ) {
+ $ret = array_map( $function, $input );
+ foreach ( $ret as $key => $value ) {
+ $taint = istainted( $input[$key] );
+ if ( $taint ) {
+ taint( $ret[$key], $taint );
+ }
+ }
+ return $ret;
+}
+
?>
<div class="license">
@@ -2077,13 +2185,12 @@ function getShellLocale( $wikiLang ) {
<div id="column-one">
<div class="portlet" id="p-logo">
<a style="background-image: url(../skins/common/images/mediawiki.png);"
- href="http://www.mediawiki.org/"
+ href="../"
title="Main Page"></a>
</div>
<script type="text/javascript"> if (window.isMSIE55) fixalpha(); </script>
<div class='portlet'><div class='pBody'>
<ul>
- <li><strong><a href="http://www.mediawiki.org/">MediaWiki home</a></strong></li>
<li><a href="../README">Readme</a></li>
<li><a href="../RELEASE-NOTES">Release notes</a></li>
<li><a href="../docs/">Documentation</a></li>
@@ -2091,7 +2198,7 @@ function getShellLocale( $wikiLang ) {
<li><a href="http://www.mediawiki.org/wiki/Manual:Contents">Administrator's Guide</a></li>
<li><a href="http://www.mediawiki.org/wiki/Manual:FAQ">FAQ</a></li>
</ul>
- <p style="font-size:90%;margin-top:1em">MediaWiki is Copyright © 2001-2008 by Magnus Manske, Brion Vibber,
+ <p style="font-size:90%;margin-top:1em">MediaWiki is Copyright © 2001-2009 by Magnus Manske, Brion Vibber,
Lee Daniel Crocker, Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason, Niklas Laxström,
Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor, Aaron Schulz and others.</p>
</div></div>
diff --git a/docs/hooks.txt b/docs/hooks.txt
index 04b1e2ac..f973d6b8 100644
--- a/docs/hooks.txt
+++ b/docs/hooks.txt
@@ -441,6 +441,7 @@ $isWatch: (No longer used)
$section: (No longer used)
$flags: Flags passed to Article::doEdit()
$revision: New Revision of the article
+$baseRevId: the rev ID (or false) this edit was based on
'ArticleUndelete': When one or more revisions of an article are restored
$title: Title corresponding to the article restored
@@ -551,6 +552,13 @@ Return true to allow the normal editor to be used, or false
if implementing a custom editor, e.g. for a special namespace,
etc.
+'NewDifferenceEngine': Called when a new DifferenceEngine object is made
+$title: the diff page title (nullable)
+&$oldId: the actual old Id to use in the diff
+&$newId: the actual new Id to use in the diff (0 means current)
+$old: the ?old= param value from the url
+$new: the ?new= param value from the url
+
'DiffViewHeader': called before diff display
$diff: DifferenceEngine object that's calling
$oldRev: Revision object of the "old" revision (may be null/invalid)
@@ -829,6 +837,8 @@ $options: the options. Will always include either 'known' or 'broken', and may
&$url: the link url
&$text: the link text
&$link: the new link HTML (if returning false)
+&$attribs: the attributes to be applied.
+$linkType: The external link type
'LinksUpdate': At the beginning of LinksUpdate::doUpdate() just before the actual update
&$linksUpdate: the LinkUpdate object
@@ -839,6 +849,9 @@ $options: the options. Will always include either 'known' or 'broken', and may
'LinksUpdateConstructed': At the end of LinksUpdate() is contruction.
&$linksUpdate: the LinkUpdate object
+'ListDefinedTags': When trying to find all defined tags.
+&$tags: The list of tags.
+
'LoadAllMessages': called by MessageCache::loadAllMessages() to load extensions messages
'LoadExtensionSchemaUpdates': called by maintenance/updaters.inc when upgrading database schema
@@ -884,7 +897,7 @@ $time: timestamp of the log entry (added in 1.12)
'MagicWordMagicWords': When defining new magic word. DEPRECATED: Use LanguageGetMagic hook instead
$magicWords: array of strings
-'MagicWordwgVariableIDs': When definig new magic words IDs. DEPRECATED: Use LanguageGetMagic hook instead
+'MagicWordwgVariableIDs': When definig new magic words IDs.
$variableIDs: array of strings
'MakeGlobalVariablesScript': called right before Skin::makeVariablesScript is executed
@@ -1375,10 +1388,19 @@ $user: User object
$user: User to get rights for
&$rights: Current rights
+'UserIsBlockedGlobally': Check if user is blocked on all wikis.
+&$user: User object
+$ip: User's IP address
+&$blocked: Whether the user is blocked, to be modified by the hook
+
'UserLoadDefaults': called when loading a default user
$user: user object
$name: user name
+'UserLoadFromDatabase': called when loading a user from the database
+$user: user object
+&$s: database query object
+
'UserLoadFromSession': called to authenticate users on external/environmental means; occurs before session is loaded
$user: user object being loaded
&$result: set this to a boolean value to abort the normal authentification process
@@ -1440,6 +1462,10 @@ $user: User object
'UserToggles': called when initialising User::$mToggles, use this to add new toggles
$toggles: array of toggles to add
+'WantedPages::getSQL': called in WantedPagesPage::getSQL(), can be used to alter the SQL query which gets the list of wanted pages
+&$wantedPages: WantedPagesPage object
+&$sql: raw SQL query used to get the list of wanted pages
+
'WatchArticle': before a watch is added to an article
$user: user that will watch
$article: article object to be watched
diff --git a/img_auth.php b/img_auth.php
index 91410181..e5d8d888 100644
--- a/img_auth.php
+++ b/img_auth.php
@@ -5,11 +5,13 @@
*
* To use this:
*
- * Set $wgUploadDirectory to a non-public directory (not web accessible)
- * Set $wgUploadPath to point to this file
+ * - Set $wgUploadDirectory to a non-public directory (not web accessible)
+ * - Set $wgUploadPath to point to this file
*
* Your server needs to support PATH_INFO; CGI-based configurations
* usually don't.
+ *
+ * @file
*/
define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php
index 63468a14..26b6f443 100644
--- a/includes/AjaxResponse.php
+++ b/includes/AjaxResponse.php
@@ -45,7 +45,7 @@ class AjaxResponse {
$this->mText = '';
$this->mResponseCode = '200 OK';
$this->mLastModified = false;
- $this->mContentType= 'text/html; charset=utf-8';
+ $this->mContentType= 'application/x-wiki';
if ( $text ) {
$this->addText( $text );
@@ -178,6 +178,7 @@ class AjaxResponse {
wfDebug( "$fname: -- client send If-Modified-Since: " . $modsince . "\n", false );
wfDebug( "$fname: -- we might send Last-Modified : $lastmod\n", false );
if( ($ismodsince >= $timestamp ) && $wgUser->validateCache( $ismodsince ) && $ismodsince >= $wgCacheEpoch ) {
+ ini_set('zlib.output_compression', 0);
$this->setResponseCode( "304 Not Modified" );
$this->disable();
$this->mLastModified = $lastmod;
diff --git a/includes/Article.php b/includes/Article.php
index 3d9c2147..ef219ea3 100644
--- a/includes/Article.php
+++ b/includes/Article.php
@@ -84,12 +84,12 @@ class Article {
return $this->mRedirectTarget;
# Query the redirect table
$dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'redirect',
+ $row = $dbr->selectRow( 'redirect',
array('rd_namespace', 'rd_title'),
- array('rd_from' => $this->getID()),
+ array('rd_from' => $this->getID() ),
__METHOD__
);
- if( $row = $dbr->fetchObject($res) ) {
+ if( $row ) {
return $this->mRedirectTarget = Title::makeTitle($row->rd_namespace, $row->rd_title);
}
# This page doesn't have an entry in the redirect table
@@ -135,7 +135,7 @@ class Article {
* @return mixed false, Title of in-wiki target, or string with URL
*/
public function followRedirectText( $text ) {
- $rt = Title::newFromRedirect( $text );
+ $rt = Title::newFromRedirectRecurse( $text ); // recurse through to only get the final target
# process if title object is valid and not special:userlogout
if( $rt ) {
if( $rt->getInterwiki() != '' ) {
@@ -218,7 +218,7 @@ class Article {
if( wfEmptyMsg( $message, $text ) )
$text = '';
} else {
- $text = wfMsg( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon' );
+ $text = wfMsgExt( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', 'parsemag' );
}
wfProfileOut( __METHOD__ );
return $text;
@@ -228,6 +228,21 @@ class Article {
return $this->mContent;
}
}
+
+ /**
+ * Get the text of the current revision. No side-effects...
+ *
+ * @return Return the text of the current revision
+ */
+ public function getRawText() {
+ // Check process cache for current revision
+ if( $this->mContentLoaded && $this->mOldId == 0 ) {
+ return $this->mContent;
+ }
+ $rev = Revision::newFromTitle( $this->mTitle );
+ $text = $rev ? $rev->getRawText() : false;
+ return $text;
+ }
/**
* This function returns the text of a section, specified by a number ($section).
@@ -245,6 +260,28 @@ class Article {
global $wgParser;
return $wgParser->getSection( $text, $section );
}
+
+ /**
+ * Get the text that needs to be saved in order to undo all revisions
+ * between $undo and $undoafter. Revisions must belong to the same page,
+ * must exist and must not be deleted
+ * @param $undo Revision
+ * @param $undoafter Revision Must be an earlier revision than $undo
+ * @return mixed string on success, false on failure
+ */
+ public function getUndoText( Revision $undo, Revision $undoafter = null ) {
+ $undo_text = $undo->getText();
+ $undoafter_text = $undoafter->getText();
+ $cur_text = $this->getContent();
+ if ( $cur_text == $undo_text ) {
+ # No use doing a merge if it's just a straight revert.
+ return $undoafter_text;
+ }
+ $undone_text = '';
+ if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) )
+ return false;
+ return $undone_text;
+ }
/**
* @return int The oldid of the article that is to be shown, 0 for the
@@ -569,7 +606,7 @@ class Article {
}
// Apparently loadPageData was never called
$this->loadContent();
- $titleObj = Title::newFromRedirect( $this->fetchContent() );
+ $titleObj = Title::newFromRedirectRecurse( $this->fetchContent() );
} else {
$titleObj = Title::newFromRedirect( $text );
}
@@ -660,10 +697,13 @@ class Article {
$user = $this->getUser();
$pageId = $this->getId();
+ $hideBit = Revision::DELETED_USER; // username hidden?
+
$sql = "SELECT {$userTable}.*, MAX(rev_timestamp) as timestamp
FROM $revTable LEFT JOIN $userTable ON rev_user = user_id
WHERE rev_page = $pageId
AND rev_user != $user
+ AND rev_deleted & $hideBit = 0
GROUP BY rev_user, rev_user_text, user_real_name
ORDER BY timestamp DESC";
@@ -687,21 +727,28 @@ class Article {
global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies;
global $wgDefaultRobotPolicy;
+ # Let the parser know if this is the printable version
+ if( $wgOut->isPrintable() ) {
+ $wgOut->parserOptions()->setIsPrintable( true );
+ }
+
wfProfileIn( __METHOD__ );
# Get variables from query string
$oldid = $this->getOldID();
- # Try file cache
+ # Try client and file cache
if( $oldid === 0 && $this->checkTouched() ) {
global $wgUseETag;
if( $wgUseETag ) {
$parserCache = ParserCache::singleton();
- $wgOut->setETag( $parserCache->getETag($this,$wgUser) );
+ $wgOut->setETag( $parserCache->getETag($this, $wgOut->parserOptions()) );
}
+ # Is is client cached?
if( $wgOut->checkLastModified( $this->getTouched() ) ) {
wfProfileOut( __METHOD__ );
return;
+ # Try file cache
} else if( $this->tryFileCache() ) {
# tell wgOut that output is taken care of
$wgOut->disable();
@@ -743,15 +790,17 @@ class Article {
}
$wgOut->setRobotPolicy( $policy );
+ # Allow admins to see deleted content if explicitly requested
+ $delId = $diff ? $diff : $oldid;
+ $unhide = $wgRequest->getInt('unhide') == 1
+ && $wgUser->matchEditToken( $wgRequest->getVal('token'), $delId );
# If we got diff and oldid in the query, we want to see a
# diff page instead of the article.
-
if( !is_null( $diff ) ) {
$wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
- $diff = $wgRequest->getVal( 'diff' );
$htmldiff = $wgRequest->getVal( 'htmldiff' , false);
- $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $htmldiff);
+ $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $htmldiff, $unhide );
// DifferenceEngine directly fetched the revision:
$this->mRevIdFetched = $de->mNewid;
$de->showDiffPage( $diffOnly );
@@ -765,6 +814,16 @@ class Article {
wfProfileOut( __METHOD__ );
return;
}
+
+ if( $ns == NS_USER || $ns == NS_USER_TALK ) {
+ # User/User_talk subpages are not modified. (bug 11443)
+ if( !$this->mTitle->isSubpage() ) {
+ $block = new Block();
+ if( $block->load( $this->mTitle->getBaseText() ) ) {
+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
+ }
+ }
+ }
# Should the parser cache be used?
$pcache = $this->useParserCache( $oldid );
@@ -787,6 +846,11 @@ class Article {
$fragment = Xml::escapeJsString( $this->mTitle->getFragmentForURL() );
$wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" );
}
+
+ // Add a <link rel="canonical"> tag
+ $wgOut->addLink( array( 'rel' => 'canonical',
+ 'href' => $this->mTitle->getLocalURL() )
+ );
$wasRedirected = true;
}
} elseif( !empty( $rdfrom ) ) {
@@ -803,7 +867,7 @@ class Article {
$outputDone = false;
wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$pcache ) );
- if( $pcache && $wgOut->tryParserCache( $this, $wgUser ) ) {
+ if( $pcache && $wgOut->tryParserCache( $this ) ) {
// Ensure that UI elements requiring revision ID have
// the correct version information.
$wgOut->setRevisionId( $this->mLatest );
@@ -816,14 +880,18 @@ class Article {
$this->showDeletionLog();
}
$text = $this->getContent();
- if( $text === false ) {
+ // For now, check also for ID until getContent actually returns
+ // false for pages that do not exists
+ if( $text === false || $this->getID() === 0 ) {
# Failed to load, replace text with error message
$t = $this->mTitle->getPrefixedText();
if( $oldid ) {
- $d = wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid );
- $text = wfMsg( 'missing-article', $t, $d );
- } else {
- $text = wfMsg( 'noarticletext' );
+ $d = wfMsgExt( 'missingarticle-rev', 'escape', $oldid );
+ $text = wfMsgExt( 'missing-article', 'parsemag', $t, $d );
+ // Always use page content for pages in the MediaWiki namespace
+ // since it contains the default message
+ } elseif ( $this->mTitle->getNamespace() != NS_MEDIAWIKI ) {
+ $text = wfMsgExt( 'noarticletext', 'parsemag' );
}
}
@@ -836,7 +904,7 @@ class Article {
// for better machine handling of broken links.
$return404 = true;
}
- }
+ }
if( $return404 ) {
$wgRequest->response()->header( "HTTP/1.x 404 Not Found" );
@@ -862,24 +930,35 @@ class Article {
// FIXME: This would be a nice place to load the 'no such page' text.
} else {
$this->setOldSubtitle( isset($this->mOldId) ? $this->mOldId : $oldid );
+ # Allow admins to see deleted content if explicitly requested
if( $this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
- if( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) {
- $wgOut->addWikiMsg( 'rev-deleted-text-permission' );
+ if( !$unhide || !$this->mRevision->userCan(Revision::DELETED_TEXT) ) {
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' );
$wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
wfProfileOut( __METHOD__ );
return;
} else {
- $wgOut->addWikiMsg( 'rev-deleted-text-view' );
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' );
// and we are allowed to see...
}
}
+ // Is this the current revision and otherwise cacheable? Try the parser cache...
+ if( $oldid === $this->getLatest() && $this->useParserCache( false )
+ && $wgOut->tryParserCache( $this ) )
+ {
+ $outputDone = true;
+ }
}
}
+ // Ensure that UI elements requiring revision ID have
+ // the correct version information.
$wgOut->setRevisionId( $this->getRevIdFetched() );
- // Pages containing custom CSS or JavaScript get special treatment
- if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) {
+ if( $outputDone ) {
+ // do nothing...
+ // Pages containing custom CSS or JavaScript get special treatment
+ } else if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) {
$wgOut->addHTML( wfMsgExt( 'clearyourcache', 'parse' ) );
// Give hooks a chance to customise the output
if( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) {
@@ -890,7 +969,7 @@ class Article {
$wgOut->addHTML( htmlspecialchars( $this->mContent ) );
$wgOut->addHTML( "\n</pre>\n" );
}
- } else if( $rt = Title::newFromRedirect( $text ) ) {
+ } else if( $rt = Title::newFromRedirectArray( $text ) ) { # get an array of redirect targets
# Don't append the subtitle if this was an old revision
$wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) );
$parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser));
@@ -942,7 +1021,7 @@ class Article {
# If we have been passed an &rcid= parameter, we want to give the user a
# chance to mark this new article as patrolled.
- if( !empty($rcid) && $this->mTitle->exists() && $this->mTitle->userCan('patrol') ) {
+ if( !empty($rcid) && $this->mTitle->exists() && $this->mTitle->quickUserCan('patrol') ) {
$wgOut->addHTML(
"<div class='patrollink'>" .
wfMsgHtml( 'markaspatrolledlink',
@@ -1003,24 +1082,41 @@ class Article {
/**
* View redirect
- * @param $target Title object of destination to redirect
+ * @param $target Title object or Array of destination(s) to redirect
* @param $appendSubtitle Boolean [optional]
* @param $forceKnown Boolean: should the image be shown as a bluelink regardless of existence?
*/
public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
global $wgParser, $wgOut, $wgContLang, $wgStylePath, $wgUser;
# Display redirect
+ if( !is_array( $target ) ) {
+ $target = array( $target );
+ }
$imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
- $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png';
-
+ $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png';
+ $imageUrl2 = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png';
+ $alt2 = $wgContLang->isRTL() ? '&larr;' : '&rarr;'; // should -> and <- be used instead of entities?
+
if( $appendSubtitle ) {
$wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) );
}
$sk = $wgUser->getSkin();
+ // the loop prepends the arrow image before the link, so the first case needs to be outside
+ $title = array_shift( $target );
if( $forceKnown ) {
- $link = $sk->makeKnownLinkObj( $target, htmlspecialchars( $target->getFullText() ) );
+ $link = $sk->makeKnownLinkObj( $title, htmlspecialchars( $title->getFullText() ) );
} else {
- $link = $sk->makeLinkObj( $target, htmlspecialchars( $target->getFullText() ) );
+ $link = $sk->makeLinkObj( $title, htmlspecialchars( $title->getFullText() ) );
+ }
+ // automatically append redirect=no to each link, since most of them are redirect pages themselves
+ foreach( $target as $rt ) {
+ if( $forceKnown ) {
+ $link .= '<img src="'.$imageUrl2.'" alt="'.$alt2.' " />'
+ . $sk->makeKnownLinkObj( $rt, htmlspecialchars( $rt->getFullText() ) );
+ } else {
+ $link .= '<img src="'.$imageUrl2.'" alt="'.$alt2.' " />'
+ . $sk->makeLinkObj( $rt, htmlspecialchars( $rt->getFullText() ) );
+ }
}
return '<img src="'.$imageUrl.'" alt="#REDIRECT " />' .
'<span class="redirectText">'.$link.'</span>';
@@ -1052,7 +1148,7 @@ class Article {
$o->tb_name,
$rmvtxt);
}
- $wgOut->addWikiMsg( 'trackbackbox', $tbtext );
+ $wgOut->wrapWikiMsg( "<div id='mw_trackbacks'>$1</div>\n", array( 'trackbackbox', $tbtext ) );
$this->mTitle->invalidateCache();
}
@@ -1128,7 +1224,7 @@ class Article {
if( $this->getID() == 0 ) {
$text = false;
} else {
- $text = $this->getContent();
+ $text = $this->getRawText();
}
$wgMessageCache->replace( $this->mTitle->getDBkey(), $text );
}
@@ -1490,7 +1586,7 @@ class Article {
$isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed('minoredit');
$bot = $flags & EDIT_FORCE_BOT;
- $oldtext = $this->getContent();
+ $oldtext = $this->getRawText(); // current revision
$oldsize = strlen( $oldtext );
# Provide autosummaries if one is not provided and autosummaries are enabled.
@@ -1601,8 +1697,8 @@ class Article {
}
# Invalidate cache of this article and all pages using this article
- # as a template. Partly deferred. Leave templatelinks for editUpdates().
- Article::onArticleEdit( $this->mTitle, 'skiptransclusions' );
+ # as a template. Partly deferred.
+ Article::onArticleEdit( $this->mTitle );
# Update links tables, site stats, etc.
$this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed );
} else {
@@ -1680,7 +1776,7 @@ class Article {
$status->value['revision'] = $revision;
wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status ) );
+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
wfProfileOut( __METHOD__ );
return $status;
@@ -1731,7 +1827,7 @@ class Article {
#It would be nice to see where the user had actually come from, but for now just guess
$returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges';
- $return = Title::makeTitle( NS_SPECIAL, $returnto );
+ $return = SpecialPage::getTitleFor( $returnto );
$dbw = wfGetDB( DB_MASTER );
$errors = $rc->doMarkPatrolled();
@@ -1867,7 +1963,18 @@ class Article {
global $wgUser, $wgRestrictionTypes, $wgContLang;
$id = $this->mTitle->getArticleID();
- if( $id <= 0 || wfReadOnly() || !$this->mTitle->userCan('protect') ) {
+ if ( $id <= 0 ) {
+ wfDebug( "updateRestrictions failed: $id <= 0\n" );
+ return false;
+ }
+
+ if ( wfReadOnly() ) {
+ wfDebug( "updateRestrictions failed: read-only\n" );
+ return false;
+ }
+
+ if ( !$this->mTitle->userCan( 'protect' ) ) {
+ wfDebug( "updateRestrictions failed: insufficient permissions\n" );
return false;
}
@@ -1938,6 +2045,9 @@ class Article {
$encodedExpiry = array();
$protect_description = '';
foreach( $limit as $action => $restrictions ) {
+ if ( !isset($expiry[$action]) )
+ $expiry[$action] = 'infinite';
+
$encodedExpiry[$action] = Block::encodeExpiry($expiry[$action], $dbw );
if( $restrictions != '' ) {
$protect_description .= "[$action=$restrictions] (";
@@ -2099,7 +2209,7 @@ class Article {
// Calculate the maximum amount of chars to get
// Max content length = max comment length - length of the comment (excl. $1) - '...'
$maxLength = 255 - (strlen( $reason ) - 2) - 3;
- $contents = $wgContLang->truncate( $contents, $maxLength, '...' );
+ $contents = $wgContLang->truncate( $contents, $maxLength );
// Remove possible unfinished links
$contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
// Now replace the '$1' placeholder
@@ -2124,7 +2234,7 @@ class Article {
if( $reason != 'other' && $this->DeleteReason != '' ) {
// Entry from drop down menu + additional comment
- $reason .= ': ' . $this->DeleteReason;
+ $reason .= wfMsgForContent( 'colon-separator' ) . $this->DeleteReason;
} elseif( $reason == 'other' ) {
$reason = $this->DeleteReason;
}
@@ -2398,7 +2508,7 @@ class Article {
return false;
}
- $u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getContent() ), -1 );
+ $u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getRawText() ), -1 );
array_push( $wgDeferredUpdateList, $u );
// Bitfields to further suppress the content
@@ -2500,7 +2610,6 @@ class Article {
# Clear the cached article id so the interface doesn't act like we exist
$this->mTitle->resetArticleID( 0 );
- $this->mTitle->mArticleID = 0;
# Log the deletion, if the page was suppressed, log it at Oversight instead
$logtype = $suppress ? 'suppress' : 'delete';
@@ -2812,13 +2921,15 @@ class Article {
# Save it to the parser cache
if( $wgEnableParserCache ) {
+ $popts = new ParserOptions;
+ $popts->setTidy( true );
+ $popts->enableLimitReport();
$parserCache = ParserCache::singleton();
- $parserCache->save( $editInfo->output, $this, $wgUser );
+ $parserCache->save( $editInfo->output, $this, $popts );
}
# Update the links tables
- $u = new LinksUpdate( $this->mTitle, $editInfo->output, false );
- $u->setRecursiveTouch( $changed ); // refresh/invalidate including pages too
+ $u = new LinksUpdate( $this->mTitle, $editInfo->output );
$u->doUpdate();
wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $changed ) );
@@ -2903,7 +3014,7 @@ class Article {
* @param $oldid String: revision ID of this article revision
*/
public function setOldSubtitle( $oldid = 0 ) {
- global $wgLang, $wgOut, $wgUser;
+ global $wgLang, $wgOut, $wgUser, $wgRequest;
if( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) {
return;
@@ -2954,16 +3065,17 @@ class Article {
}
$cdel = "(<small>$cdel</small>) ";
}
- # Show user links if allowed to see them. Normally they
- # are hidden regardless, but since we can already see the text here...
- $userlinks = $sk->revUserTools( $revision, false );
+ $unhide = $wgRequest->getInt('unhide') == 1 && $wgUser->matchEditToken( $wgRequest->getVal('token'), $oldid );
+ # Show user links if allowed to see them. If hidden, then show them only if requested...
+ $userlinks = $sk->revUserTools( $revision, !$unhide );
$m = wfMsg( 'revision-info-current' );
$infomsg = $current && !wfEmptyMsg( 'revision-info-current', $m ) && $m != '-'
? 'revision-info-current'
: 'revision-info';
- $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . wfMsgExt( $infomsg, array( 'parseinline', 'replaceafter' ), $td, $userlinks, $revision->getID() ) . "</div>\n" .
+ $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . wfMsgExt( $infomsg, array( 'parseinline', 'replaceafter' ),
+ $td, $userlinks, $revision->getID() ) . "</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";
@@ -3056,7 +3168,7 @@ class Article {
if( !$this->mDataLoaded ) {
$this->loadPageData();
}
- return $this->mLatest;
+ return (int)$this->mLatest;
}
/**
@@ -3206,17 +3318,18 @@ class Article {
$user = User::newFromName( $title->getText(), false );
$user->setNewtalk( false );
}
+ # Image redirects
+ RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
}
/**
* Purge caches on page update etc
*/
- public static function onArticleEdit( $title, $transclusions = 'transclusions' ) {
+ public static function onArticleEdit( $title, $flags = '' ) {
global $wgDeferredUpdateList;
// Invalidate caches of articles which include this page
- if( $transclusions !== 'skiptransclusions' )
- $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
+ $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' );
// Invalidate the caches of all pages which redirect here
$wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' );
@@ -3405,8 +3518,7 @@ class Article {
global $wgContLang;
$truncatedtext = $wgContLang->truncate(
str_replace("\n", ' ', $newtext),
- max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ),
- '...' );
+ max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) );
return wfMsgForContent( 'autosumm-new', $truncatedtext );
}
@@ -3418,9 +3530,7 @@ class Article {
global $wgContLang;
$truncatedtext = $wgContLang->truncate(
$newtext,
- max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ),
- '...'
- );
+ max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) );
return wfMsgForContent( 'autosumm-replace', $truncatedtext );
}
@@ -3449,7 +3559,7 @@ class Article {
$popts->enableLimitReport( false );
if( $wgEnableParserCache && $cache && $this && $parserOutput->getCacheTime() != -1 ) {
$parserCache = ParserCache::singleton();
- $parserCache->save( $parserOutput, $this, $wgUser );
+ $parserCache->save( $parserOutput, $this, $popts );
}
// Make sure file cache is not used on uncacheable content.
// Output that has magic words in it can still use the parser cache
@@ -3478,27 +3588,26 @@ class Article {
__METHOD__ );
global $wgContLang;
-
- if( $res !== false ) {
- foreach( $res as $row ) {
- $tlTemplates[] = $wgContLang->getNsText( $row->tl_namespace ) . ':' . $row->tl_title ;
- }
+ foreach( $res as $row ) {
+ $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
}
# Get templates from parser output.
- $poTemplates_allns = $parserOutput->getTemplates();
-
- $poTemplates = array ();
- foreach ( $poTemplates_allns as $ns_templates ) {
- $poTemplates = array_merge( $poTemplates, $ns_templates );
+ $poTemplates = array();
+ foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
+ foreach ( $templates as $dbk => $id ) {
+ $key = $row->tl_namespace . ':'. $row->tl_title;
+ $poTemplates["$ns:$dbk"] = true;
+ }
}
# Get the diff
- $templates_diff = array_diff( $poTemplates, $tlTemplates );
+ # Note that we simulate array_diff_key in PHP <5.0.x
+ $templates_diff = array_diff_key( $poTemplates, $tlTemplates );
if( count( $templates_diff ) > 0 ) {
# Whee, link updates time.
- $u = new LinksUpdate( $this->mTitle, $parserOutput );
+ $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
$u->doUpdate();
}
}
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index ce1912ea..85e7e668 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -19,6 +19,7 @@ $wgAutoloadLocalClasses = array(
'AuthPlugin' => 'includes/AuthPlugin.php',
'AuthPluginUser' => 'includes/AuthPlugin.php',
'Autopromote' => 'includes/Autopromote.php',
+ 'BacklinkCache' => 'includes/BacklinkCache.php',
'BagOStuff' => 'includes/BagOStuff.php',
'Block' => 'includes/Block.php',
'CacheDependency' => 'includes/CacheDependency.php',
@@ -28,6 +29,7 @@ $wgAutoloadLocalClasses = array(
'CategoryViewer' => 'includes/CategoryPage.php',
'ChangesList' => 'includes/ChangesList.php',
'ChangesFeed' => 'includes/ChangesFeed.php',
+ 'ChangeTags' => 'includes/ChangeTags.php',
'ChannelFeed' => 'includes/Feed.php',
'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php',
'ConstantDependency' => 'includes/CacheDependency.php',
@@ -73,6 +75,7 @@ $wgAutoloadLocalClasses = array(
'FileDependency' => 'includes/CacheDependency.php',
'FileRevertForm' => 'includes/FileRevertForm.php',
'FileStore' => 'includes/FileStore.php',
+ 'ForkController' => 'includes/ForkController.php',
'FormatExif' => 'includes/Exif.php',
'FormOptions' => 'includes/FormOptions.php',
'FSException' => 'includes/FileStore.php',
@@ -186,6 +189,7 @@ $wgAutoloadLocalClasses = array(
'StringUtils' => 'includes/StringUtils.php',
'TablePager' => 'includes/Pager.php',
'ThumbnailImage' => 'includes/MediaTransformOutput.php',
+ 'TiffHandler' => 'includes/media/Tiff.php',
'TitleDependency' => 'includes/CacheDependency.php',
'Title' => 'includes/Title.php',
'TitleArray' => 'includes/TitleArray.php',
@@ -193,10 +197,6 @@ $wgAutoloadLocalClasses = array(
'TransformParameterError' => 'includes/MediaTransformOutput.php',
'TurckBagOStuff' => 'includes/BagOStuff.php',
'UnlistedSpecialPage' => 'includes/SpecialPage.php',
- 'UploadBase' => 'includes/UploadBase.php',
- 'UploadFromStash' => 'includes/UploadFromStash.php',
- 'UploadFromUpload' => 'includes/UploadFromUpload.php',
- 'UploadFromUrl' => 'includes/UploadFromUrl.php',
'User' => 'includes/User.php',
'UserArray' => 'includes/UserArray.php',
'UserArrayFromResult' => 'includes/UserArray.php',
@@ -231,11 +231,14 @@ $wgAutoloadLocalClasses = array(
'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
'ApiFormatJson' => 'includes/api/ApiFormatJson.php',
'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php',
+ 'ApiFormatRaw' => 'includes/api/ApiFormatRaw.php',
'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php',
'ApiFormatWddx' => 'includes/api/ApiFormatWddx.php',
'ApiFormatXml' => 'includes/api/ApiFormatXml.php',
'ApiFormatYaml' => 'includes/api/ApiFormatYaml.php',
'ApiHelp' => 'includes/api/ApiHelp.php',
+ 'ApiImport' => 'includes/api/ApiImport.php',
+ 'ApiImportReporter' => 'includes/api/ApiImport.php',
'ApiLogin' => 'includes/api/ApiLogin.php',
'ApiLogout' => 'includes/api/ApiLogout.php',
'ApiMain' => 'includes/api/ApiMain.php',
@@ -273,6 +276,7 @@ $wgAutoloadLocalClasses = array(
'ApiQueryLangLinks' => 'includes/api/ApiQueryLangLinks.php',
'ApiQueryLinks' => 'includes/api/ApiQueryLinks.php',
'ApiQueryLogEvents' => 'includes/api/ApiQueryLogEvents.php',
+ 'ApiQueryProtectedTitles' => 'includes/api/ApiQueryProtectedTitles.php',
'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php',
'ApiQueryRecentChanges'=> 'includes/api/ApiQueryRecentChanges.php',
'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php',
@@ -320,6 +324,11 @@ $wgAutoloadLocalClasses = array(
'PostgresField' => 'includes/db/DatabasePostgres.php',
'ResultWrapper' => 'includes/db/Database.php',
'SQLiteField' => 'includes/db/DatabaseSqlite.php',
+
+ 'DatabaseIbm_db2' => 'includes/db/DatabaseIbm_db2.php',
+ 'IBM_DB2Field' => 'includes/db/DatabaseIbm_db2.php',
+ 'IBM_DB2SearchResultSet' => 'includes/SearchIBM_DB2.php',
+ 'SearchIBM_DB2' => 'includes/SearchIBM_DB2.php',
# includes/diff
'AncestorComparator' => 'includes/diff/HTMLDiff.php',
@@ -431,6 +440,7 @@ $wgAutoloadLocalClasses = array(
'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php',
'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php',
'StripState' => 'includes/parser/Parser.php',
+ 'MWTidy' => 'includes/parser/Tidy.php',
# includes/specials
'AncientPagesPage' => 'includes/specials/SpecialAncientpages.php',
@@ -474,11 +484,12 @@ $wgAutoloadLocalClasses = array(
'PopularPagesPage' => 'includes/specials/SpecialPopularpages.php',
'PreferencesForm' => 'includes/specials/SpecialPreferences.php',
'RandomPage' => 'includes/specials/SpecialRandompage.php',
- 'RevisionDeleteForm' => 'includes/specials/SpecialRevisiondelete.php',
+ 'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.php',
'RevisionDeleter' => 'includes/specials/SpecialRevisiondelete.php',
'ShortPagesPage' => 'includes/specials/SpecialShortpages.php',
'SpecialAllpages' => 'includes/specials/SpecialAllpages.php',
'SpecialBookSources' => 'includes/specials/SpecialBooksources.php',
+ 'SpecialExport' => 'includes/specials/SpecialExport.php',
'SpecialImport' => 'includes/specials/SpecialImport.php',
'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php',
'SpecialMostlinkedtemplates' => 'includes/specials/SpecialMostlinkedtemplates.php',
@@ -489,6 +500,7 @@ $wgAutoloadLocalClasses = array(
'SpecialSearch' => 'includes/specials/SpecialSearch.php',
'SpecialSearchOld' => 'includes/specials/SpecialSearch.php',
'SpecialStatistics' => 'includes/specials/SpecialStatistics.php',
+ 'SpecialTags' => 'includes/specials/SpecialTags.php',
'SpecialVersion' => 'includes/specials/SpecialVersion.php',
'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php',
'UncategorizedPagesPage' => 'includes/specials/SpecialUncategorizedpages.php',
@@ -556,7 +568,7 @@ class AutoLoader {
}
if ( !$filename ) {
if( function_exists( 'wfDebug' ) )
- wfDebug( "Class {$className} not found; skipped loading" );
+ wfDebug( "Class {$className} not found; skipped loading\n" );
# Give up
return false;
}
@@ -575,7 +587,7 @@ class AutoLoader {
global $wgAutoloadClasses;
foreach( $wgAutoloadClasses as $class => $file ) {
- if( !( class_exists( $class ) || interface_exists( $class ) ) ) {
+ if( !( class_exists( $class, false ) || interface_exists( $class, false ) ) ) {
require( $file );
}
}
diff --git a/includes/BacklinkCache.php b/includes/BacklinkCache.php
new file mode 100644
index 00000000..a7bcd858
--- /dev/null
+++ b/includes/BacklinkCache.php
@@ -0,0 +1,232 @@
+<?php
+
+/**
+ * Class for fetching backlink lists, approximate backlink counts and partitions.
+ * Instances of this class should typically be fetched with $title->getBacklinkCache().
+ *
+ * Ideally you should only get your backlinks from here when you think there is some
+ * advantage in caching them. Otherwise it's just a waste of memory.
+ */
+class BacklinkCache {
+ var $partitionCache = array();
+ var $fullResultCache = array();
+ var $title;
+ var $db;
+
+ const CACHE_EXPIRY = 3600;
+
+ /**
+ * Create a new BacklinkCache
+ */
+ function __construct( $title ) {
+ $this->title = $title;
+ }
+
+ /**
+ * Clear locally stored data
+ */
+ function clear() {
+ $this->partitionCache = array();
+ $this->fullResultCache = array();
+ unset( $this->db );
+ }
+
+ /**
+ * Set the Database object to use
+ */
+ public function setDB( $db ) {
+ $this->db = $db;
+ }
+
+ protected function getDB() {
+ if ( !isset( $this->db ) ) {
+ $this->db = wfGetDB( DB_SLAVE );
+ }
+ return $this->db;
+ }
+
+ /**
+ * Get the backlinks for a given table. Cached in process memory only.
+ * @param string $table
+ * @return TitleArray
+ */
+ public function getLinks( $table, $startId = false, $endId = false ) {
+ wfProfileIn( __METHOD__ );
+
+ if ( $startId || $endId ) {
+ // Partial range, not cached
+ wfDebug( __METHOD__.": from DB (uncacheable range)\n" );
+ $conds = $this->getConditions( $table );
+ // Use the from field in the condition rather than the joined page_id,
+ // because databases are stupid and don't necessarily propagate indexes.
+ $fromField = $this->getPrefix( $table ) . '_from';
+ if ( $startId ) {
+ $conds[] = "$fromField >= " . intval( $startId );
+ }
+ if ( $endId ) {
+ $conds[] = "$fromField <= " . intval( $endId );
+ }
+ $res = $this->getDB()->select(
+ array( $table, 'page' ),
+ array( 'page_namespace', 'page_title', 'page_id'),
+ $conds,
+ __METHOD__,
+ array('STRAIGHT_JOIN') );
+ $ta = TitleArray::newFromResult( $res );
+ wfProfileOut( __METHOD__ );
+ return $ta;
+ }
+
+ if ( !isset( $this->fullResultCache[$table] ) ) {
+ wfDebug( __METHOD__.": from DB\n" );
+ $res = $this->getDB()->select(
+ array( $table, 'page' ),
+ array( 'page_namespace', 'page_title', 'page_id' ),
+ $this->getConditions( $table ),
+ __METHOD__,
+ array('STRAIGHT_JOIN') );
+ $this->fullResultCache[$table] = $res;
+ }
+ $ta = TitleArray::newFromResult( $this->fullResultCache[$table] );
+ wfProfileOut( __METHOD__ );
+ return $ta;
+ }
+
+ /**
+ * Get the field name prefix for a given table
+ */
+ protected function getPrefix( $table ) {
+ static $prefixes = array(
+ 'pagelinks' => 'pl',
+ 'imagelinks' => 'il',
+ 'categorylinks' => 'cl',
+ 'templatelinks' => 'tl',
+ 'redirect' => 'rd',
+ );
+ if ( isset( $prefixes[$table] ) ) {
+ return $prefixes[$table];
+ } else {
+ throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
+ }
+ }
+
+ /**
+ * Get the SQL condition array for selecting backlinks, with a join on the page table
+ */
+ protected function getConditions( $table ) {
+ $prefix = $this->getPrefix( $table );
+ switch ( $table ) {
+ case 'pagelinks':
+ case 'templatelinks':
+ case 'redirect':
+ $conds = array(
+ "{$prefix}_namespace" => $this->title->getNamespace(),
+ "{$prefix}_title" => $this->title->getDBkey(),
+ "page_id={$prefix}_from"
+ );
+ break;
+ case 'imagelinks':
+ $conds = array(
+ 'il_to' => $this->title->getDBkey(),
+ 'page_id=il_from'
+ );
+ break;
+ case 'categorylinks':
+ $conds = array(
+ 'cl_to' => $this->title->getDBkey(),
+ 'page_id=cl_from',
+ );
+ break;
+ default:
+ throw new MWException( "Invalid table \"$table\" in " . __CLASS__ );
+ }
+ return $conds;
+ }
+
+ /**
+ * Get the approximate number of backlinks
+ */
+ public function getNumLinks( $table ) {
+ if ( isset( $this->fullResultCache[$table] ) ) {
+ return $this->fullResultCache[$table]->numRows();
+ }
+ if ( isset( $this->partitionCache[$table] ) ) {
+ $entry = reset( $this->partitionCache[$table] );
+ return $entry['numRows'];
+ }
+ $titleArray = $this->getLinks( $table );
+ return $titleArray->count();
+ }
+
+ /**
+ * Partition the backlinks into batches.
+ * Returns an array giving the start and end of each range. The first batch has
+ * a start of false, and the last batch has an end of false.
+ *
+ * @param string $table The links table name
+ * @param integer $batchSize
+ * @return array
+ */
+ public function partition( $table, $batchSize ) {
+ // Try cache
+ if ( isset( $this->partitionCache[$table][$batchSize] ) ) {
+ wfDebug( __METHOD__.": got from partition cache\n" );
+ return $this->partitionCache[$table][$batchSize]['batches'];
+ }
+ $this->partitionCache[$table][$batchSize] = false;
+ $cacheEntry =& $this->partitionCache[$table][$batchSize];
+
+ // Try full result cache
+ if ( isset( $this->fullResultCache[$table] ) ) {
+ $cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
+ wfDebug( __METHOD__.": got from full result cache\n" );
+ return $cacheEntry['batches'];
+ }
+ // Try memcached
+ global $wgMemc;
+ $memcKey = wfMemcKey( 'backlinks', md5( $this->title->getPrefixedDBkey() ),
+ $table, $batchSize );
+ $memcValue = $wgMemc->get( $memcKey );
+ if ( is_array( $memcValue ) ) {
+ $cacheEntry = $memcValue;
+ wfDebug( __METHOD__.": got from memcached $memcKey\n" );
+ return $cacheEntry['batches'];
+ }
+ // Fetch from database
+ $this->getLinks( $table );
+ $cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize );
+ // Save to memcached
+ $wgMemc->set( $memcKey, $cacheEntry, self::CACHE_EXPIRY );
+ wfDebug( __METHOD__.": got from database\n" );
+ return $cacheEntry['batches'];
+ }
+
+ /**
+ * Partition a DB result with backlinks in it into batches
+ */
+ protected function partitionResult( $res, $batchSize ) {
+ $batches = array();
+ $numRows = $res->numRows();
+ $numBatches = ceil( $numRows / $batchSize );
+ for ( $i = 0; $i < $numBatches; $i++ ) {
+ if ( $i == 0 ) {
+ $start = false;
+ } else {
+ $rowNum = intval( $numRows * $i / $numBatches );
+ $res->seek( $rowNum );
+ $row = $res->fetchObject();
+ $start = $row->page_id;
+ }
+ if ( $i == $numBatches - 1 ) {
+ $end = false;
+ } else {
+ $rowNum = intval( $numRows * ( $i + 1 ) / $numBatches );
+ $res->seek( $rowNum );
+ $row = $res->fetchObject();
+ $end = $row->page_id - 1;
+ }
+ $batches[] = array( $start, $end );
+ }
+ return array( 'numRows' => $numRows, 'batches' => $batches );
+ }
+}
diff --git a/includes/Block.php b/includes/Block.php
index 2c2227e2..a44941f1 100644
--- a/includes/Block.php
+++ b/includes/Block.php
@@ -105,6 +105,7 @@ class Block {
&& $this->mHideName == $block->mHideName
&& $this->mBlockEmail == $block->mBlockEmail
&& $this->mAllowUsertalk == $block->mAllowUsertalk
+ && $this->mReason == $block->mReason
);
}
@@ -570,7 +571,7 @@ class Block {
## Allow hooks to cancel the autoblock.
if (!wfRunHooks( 'AbortAutoblock', array( $autoblockIP, &$this ) )) {
- wfDebug( "Autoblock aborted by hook." );
+ wfDebug( "Autoblock aborted by hook.\n" );
return false;
}
diff --git a/includes/Category.php b/includes/Category.php
index 78567add..50efdbc1 100644
--- a/includes/Category.php
+++ b/includes/Category.php
@@ -41,8 +41,7 @@ class Category {
$dbr = wfGetDB( DB_SLAVE );
$row = $dbr->selectRow(
'category',
- array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats',
- 'cat_files' ),
+ array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ),
$where,
__METHOD__
);
@@ -70,8 +69,7 @@ class Category {
# (bug 13683) If the count is negative, then 1) it's obviously wrong
# and should not be kept, and 2) we *probably* don't have to scan many
# rows to obtain the correct figure, so let's risk a one-time recount.
- if( $this->mPages < 0 || $this->mSubcats < 0 ||
- $this->mFiles < 0 ) {
+ if( $this->mPages < 0 || $this->mSubcats < 0 || $this->mFiles < 0 ) {
$this->refreshCounts();
}
diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php
index 4ac24b5f..03ecb5dc 100644
--- a/includes/CategoryPage.php
+++ b/includes/CategoryPage.php
@@ -20,7 +20,8 @@ class CategoryPage extends Article {
if ( isset( $diff ) && $diffOnly )
return Article::view();
- if(!wfRunHooks('CategoryPageView', array(&$this))) return;
+ if( !wfRunHooks( 'CategoryPageView', array( &$this ) ) )
+ return;
if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
$this->openShowCategory();
@@ -28,10 +29,6 @@ class CategoryPage extends Article {
Article::view();
- # If the article we've just shown is in the "Image" namespace,
- # follow it with the history list and link list for the image
- # it describes.
-
if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
$this->closeShowCategory();
}
@@ -79,7 +76,7 @@ class CategoryViewer {
$this->from = $from;
$this->until = $until;
$this->limit = $wgCategoryPagingLimit;
- $this->cat = Category::newFromName( $title->getDBKey() );
+ $this->cat = Category::newFromTitle( $title );
}
/**
@@ -192,9 +189,10 @@ class CategoryViewer {
*/
function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) {
global $wgContLang;
+ $titletext = $wgContLang->convert( $title->getPrefixedText() );
$this->articles[] = $isRedirect
- ? '<span class="redirect-in-category">' . $this->getSkin()->makeKnownLinkObj( $title ) . '</span>'
- : $this->getSkin()->makeSizeLinkObj( $pageLength, $title );
+ ? '<span class="redirect-in-category">' . $this->getSkin()->makeKnownLinkObj( $title, $titletext ) . '</span>'
+ : $this->getSkin()->makeSizeLinkObj( $pageLength, $title, $titletext );
$this->articles_start_char[] = $wgContLang->convert( $wgContLang->firstChar( $sortkey ) );
}
@@ -208,7 +206,7 @@ class CategoryViewer {
}
function doCategoryQuery() {
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = wfGetDB( DB_SLAVE, 'category' );
if( $this->from != '' ) {
$pageCondition = 'cl_sortkey >= ' . $dbr->addQuotes( $this->from );
$this->flip = false;
@@ -316,7 +314,7 @@ class CategoryViewer {
$countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' );
return "<div id=\"mw-category-media\">\n" .
- '<h2>' . wfMsg( 'category-media-header', htmlspecialchars($this->title->getText()) ) . "</h2>\n" .
+ '<h2>' . wfMsg( 'category-media-header', htmlspecialchars( $this->title->getText() ) ) . "</h2>\n" .
$countmsg . $this->gallery->toHTML() . "\n</div>";
} else {
return '';
@@ -451,12 +449,12 @@ class CategoryViewer {
$sk = $this->getSkin();
$limitText = $wgLang->formatNum( $limit );
- $prevLink = htmlspecialchars( wfMsg( 'prevn', $limitText ) );
+ $prevLink = wfMsgExt( 'prevn', array( 'escape', 'parsemag' ), $limitText );
if( $first != '' ) {
$prevLink = $sk->makeLinkObj( $title, $prevLink,
wfArrayToCGI( $query + array( 'until' => $first ) ) );
}
- $nextLink = htmlspecialchars( wfMsg( 'nextn', $limitText ) );
+ $nextLink = wfMsgExt( 'nextn', array( 'escape', 'parsemag' ), $limitText );
if( $last != '' ) {
$nextLink = $sk->makeLinkObj( $title, $nextLink,
wfArrayToCGI( $query + array( 'from' => $last ) ) );
diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php
index 4413bd1a..7c1c2856 100644
--- a/includes/Categoryfinder.php
+++ b/includes/Categoryfinder.php
@@ -53,9 +53,11 @@ class Categoryfinder {
# Set the list of target categories; convert them to DBKEY form first
$this->targets = array () ;
foreach ( $categories AS $c ) {
- $ct = Title::newFromText ( $c , NS_CATEGORY ) ;
- $c = $ct->getDBkey () ;
- $this->targets[$c] = $c ;
+ $ct = Title::makeTitleSafe( NS_CATEGORY, $c );
+ if( $ct ) {
+ $c = $ct->getDBkey();
+ $this->targets[$c] = $c;
+ }
}
}
diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php
new file mode 100644
index 00000000..de804c5c
--- /dev/null
+++ b/includes/ChangeTags.php
@@ -0,0 +1,183 @@
+<?php
+
+if (!defined( 'MEDIAWIKI' ))
+ die;
+
+class ChangeTags {
+ static function formatSummaryRow( $tags, $page ) {
+ if (!$tags)
+ return array('',array());
+
+ $classes = array();
+
+ $tags = explode( ',', $tags );
+ $displayTags = array();
+ foreach( $tags as $tag ) {
+ $displayTags[] = self::tagDescription( $tag );
+ $classes[] = "mw-tag-$tag";
+ }
+
+ return array( '(' . implode( ', ', $displayTags ) . ')', $classes );
+ }
+
+ static function tagDescription( $tag ) {
+ $msg = wfMsgExt( "tag-$tag", 'parseinline' );
+ if ( wfEmptyMsg( "tag-$tag", $msg ) ) {
+ return htmlspecialchars($tag);
+ }
+ return $msg;
+ }
+
+ ## Basic utility method to add tags to a particular change, given its rc_id, rev_id and/or log_id.
+ static function addTags( $tags, $rc_id=null, $rev_id=null, $log_id=null, $params = null ) {
+ if ( !is_array($tags) ) {
+ $tags = array( $tags );
+ }
+
+ $tags = array_filter( $tags ); // Make sure we're submitting all tags...
+
+ if (!$rc_id && !$rev_id && !$log_id) {
+ throw new MWException( "At least one of: RCID, revision ID, and log ID MUST be specified when adding a tag to a change!" );
+ }
+
+ $dbr = wfGetDB( DB_SLAVE );
+
+ // Might as well look for rcids and so on.
+ if (!$rc_id) {
+ $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave.
+ if ($log_id) {
+ $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_logid' => $log_id ), __METHOD__ );
+ } elseif ($rev_id) {
+ $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_this_oldid' => $rev_id ), __METHOD__ );
+ }
+ } elseif (!$log_id && !$rev_id) {
+ $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave.
+ $log_id = $dbr->selectField( 'recentchanges', 'rc_logid', array( 'rc_id' => $rc_id ), __METHOD__ );
+ $rev_id = $dbr->selectField( 'recentchanges', 'rc_this_oldid', array( 'rc_id' => $rc_id ), __METHOD__ );
+ }
+
+ $tsConds = array_filter( array( 'ts_rc_id' => $rc_id, 'ts_rev_id' => $rev_id, 'ts_log_id' => $log_id ) );
+
+ ## Update the summary row.
+ $prevTags = $dbr->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ );
+ $prevTags = $prevTags ? $prevTags : '';
+ $prevTags = array_filter( explode( ',', $prevTags ) );
+ $newTags = array_unique( array_merge( $prevTags, $tags ) );
+ sort($prevTags);
+ sort($newTags);
+
+ if ( $prevTags == $newTags ) {
+ // No change.
+ return false;
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->replace( 'tag_summary', array( 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ), array_filter( array_merge( $tsConds, array( 'ts_tags' => implode( ',', $newTags ) ) ) ), __METHOD__ );
+
+ // Insert the tags rows.
+ $tagsRows = array();
+ foreach( $tags as $tag ) { // Filter so we don't insert NULLs as zero accidentally.
+ $tagsRows[] = array_filter( array( 'ct_tag' => $tag, 'ct_rc_id' => $rc_id, 'ct_log_id' => $log_id, 'ct_rev_id' => $rev_id, 'ct_params' => $params ) );
+ }
+
+ $dbw->insert( 'change_tag', $tagsRows, __METHOD__, array('IGNORE') );
+
+ return true;
+ }
+
+ /**
+ * 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.
+ */
+ static function modifyDisplayQuery( &$tables, &$fields, &$conds,
+ &$join_conds, &$options, $filter_tag = false ) {
+ global $wgRequest, $wgUseTagFilter;
+
+ if ($filter_tag === false) {
+ $filter_tag = $wgRequest->getVal( 'tagfilter' );
+ }
+
+ // Figure out which conditions can be done.
+ $join_field = '';
+ if ( in_array('recentchanges', $tables) ) {
+ $join_cond = 'rc_id';
+ } elseif( in_array('logging', $tables) ) {
+ $join_cond = 'log_id';
+ } elseif ( in_array('revision', $tables) ) {
+ $join_cond = 'rev_id';
+ } else {
+ throw new MWException( "Unable to determine appropriate JOIN condition for tagging." );
+ }
+
+ // JOIN on tag_summary
+ $tables[] = 'tag_summary';
+ $join_conds['tag_summary'] = array( 'LEFT JOIN', "ts_$join_cond=$join_cond" );
+ $fields[] = 'ts_tags';
+
+ if ($wgUseTagFilter && $filter_tag) {
+ // Somebody wants to filter on a tag.
+ // Add an INNER JOIN on change_tag
+
+ // FORCE INDEX -- change_tags will almost ALWAYS be the correct query plan.
+ $options['USE INDEX'] = array( 'change_tag' => 'change_tag_tag_id' );
+ unset( $options['FORCE INDEX'] );
+ $tables[] = 'change_tag';
+ $join_conds['change_tag'] = array( 'INNER JOIN', "ct_$join_cond=$join_cond" );
+ $conds['ct_tag'] = $filter_tag;
+ }
+ }
+
+ /**
+ * If $fullForm is set to false, then it returns an array of (label, form).
+ * If $fullForm is true, it returns an entire form.
+ */
+ static function buildTagFilterSelector( $selected='', $fullForm = false /* used to put a full form around the selector */ ) {
+ global $wgUseTagFilter;
+
+ if ( !$wgUseTagFilter || !count( self::listDefinedTags() ) )
+ return $fullForm ? '' : array();
+
+ global $wgTitle;
+
+ $data = array( wfMsgExt( 'tag-filter', 'parseinline' ), Xml::input( 'tagfilter', 20, $selected ) );
+
+ if (!$fullForm) {
+ return $data;
+ }
+
+ $html = implode( '&nbsp;', $data );
+ $html .= "\n" . Xml::element( 'input', array( 'type' => 'submit', 'value' => wfMsg( 'tag-filter-submit' ) ) );
+ $html .= "\n" . Xml::hidden( 'title', $wgTitle-> getPrefixedText() );
+ $html = Xml::tags( 'form', array( 'action' => $wgTitle->getLocalURL(), 'method' => 'get' ), $html );
+
+ return $html;
+ }
+
+ /** Basically lists defined tags which count even if they aren't applied to anything */
+ static function listDefinedTags() {
+ // Caching...
+ global $wgMemc;
+ $key = wfMemcKey( 'valid-tags' );
+
+ if ($tags = $wgMemc->get( $key ))
+ return $tags;
+
+ $emptyTags = array();
+
+ // Some DB stuff
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'valid_tag', 'vt_tag', array(), __METHOD__ );
+ while( $row = $res->fetchObject() ) {
+ $emptyTags[] = $row->vt_tag;
+ }
+
+ wfRunHooks( 'ListDefinedTags', array(&$emptyTags) );
+
+ $emptyTags = array_filter( array_unique( $emptyTags ) );
+
+ // Short-term caching.
+ $wgMemc->set( $key, $emptyTags, 300 );
+ return $emptyTags;
+ }
+}
diff --git a/includes/ChangesFeed.php b/includes/ChangesFeed.php
index f3c3e429..a0c2767a 100644
--- a/includes/ChangesFeed.php
+++ b/includes/ChangesFeed.php
@@ -18,16 +18,16 @@ class ChangesFeed {
$feedTitle, htmlspecialchars( $description ), $wgTitle->getFullUrl() );
}
- public function execute( $feed, $rows, $limit = 0 , $hideminor = false, $lastmod = false ) {
+ public function execute( $feed, $rows, $limit=0, $hideminor=false, $lastmod=false, $target='' ) {
global $messageMemc, $wgFeedCacheTimeout;
- global $wgFeedClasses, $wgSitename, $wgContLanguageCode;
+ global $wgSitename, $wgContLanguageCode;
if ( !FeedUtils::checkFeedOutput( $this->format ) ) {
return;
}
$timekey = wfMemcKey( $this->type, $this->format, 'timestamp' );
- $key = wfMemcKey( $this->type, $this->format, 'limit', $limit, 'minor', $hideminor );
+ $key = wfMemcKey( $this->type, $this->format, $limit, $hideminor, $target );
FeedUtils::checkPurge($timekey, $key);
diff --git a/includes/ChangesList.php b/includes/ChangesList.php
index a8f5fff0..4eda1dbd 100644
--- a/includes/ChangesList.php
+++ b/includes/ChangesList.php
@@ -108,7 +108,7 @@ class ChangesList {
public static function showCharacterDifference( $old, $new ) {
global $wgRCChangedSizeThreshold, $wgLang;
$szdiff = $new - $old;
- $formatedSize = wfMsgExt( 'rc-change-size', array( 'parsemag', 'escape'), $wgLang->formatNum($szdiff) );
+ $formatedSize = wfMsgExt( 'rc-change-size', array( 'parsemag', 'escape' ), $wgLang->formatNum( $szdiff ) );
if( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) {
$tag = 'strong';
} else {
@@ -223,9 +223,9 @@ class ChangesList {
}
/** Insert links to user page, user talk page and eventually a blocking link */
- public function insertUserRelatedLinks(&$s, &$rc) {
- if( $this->isDeleted($rc,Revision::DELETED_USER) ) {
- $s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-user') . '</span>';
+ public function insertUserRelatedLinks( &$s, &$rc ) {
+ if( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
+ $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
} else {
$s .= $this->skin->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
$s .= $this->skin->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
@@ -233,22 +233,22 @@ class ChangesList {
}
/** insert a formatted action */
- protected function insertAction(&$s, &$rc) {
+ protected 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>';
+ if( $this->isDeleted( $rc, LogPage::DELETED_ACTION ) ) {
+ $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-event' ) . '</span>';
} else {
$s .= ' '.LogPage::actionText( $rc->mAttribs['rc_log_type'], $rc->mAttribs['rc_log_action'],
- $rc->getTitle(), $this->skin, LogPage::extractParams($rc->mAttribs['rc_params']), true, true );
+ $rc->getTitle(), $this->skin, LogPage::extractParams( $rc->mAttribs['rc_params'] ), true, true );
}
}
}
/** insert a formatted comment */
- protected function insertComment(&$s, &$rc) {
+ protected function insertComment( &$s, &$rc ) {
if( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) {
- if( $this->isDeleted($rc,Revision::DELETED_COMMENT) ) {
- $s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-comment') . '</span>';
+ if( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) {
+ $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span>';
} else {
$s .= $this->skin->commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() );
}
@@ -272,8 +272,8 @@ class ChangesList {
static $cache = array();
if( $count > 0 ) {
if( !isset( $cache[$count] ) ) {
- $cache[$count] = wfMsgExt('number_of_watching_users_RCview',
- array('parsemag', 'escape'), $wgLang->formatNum($count));
+ $cache[$count] = wfMsgExt( 'number_of_watching_users_RCview',
+ array('parsemag', 'escape' ), $wgLang->formatNum( $count ) );
}
return $cache[$count];
} else {
@@ -288,7 +288,7 @@ class ChangesList {
* @return bool
*/
public static function isDeleted( $rc, $field ) {
- return ($rc->mAttribs['rc_deleted'] & $field) == $field;
+ return ( $rc->mAttribs['rc_deleted'] & $field ) == $field;
}
/**
@@ -318,6 +318,40 @@ class ChangesList {
return '<span class="mw-rc-unwatched">' . $link . '</span>';
}
}
+
+ /** Inserts a rollback link */
+ protected function insertRollback( &$s, &$rc ) {
+ global $wgUser;
+ if( !$rc->mAttribs['rc_new'] && $rc->mAttribs['rc_this_oldid'] && $rc->mAttribs['rc_cur_id'] ) {
+ $page = $rc->getTitle();
+ /** Check for rollback and edit permissions, disallow special pages, and only
+ * show a link on the top-most revision */
+ if ($wgUser->isAllowed('rollback') && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] )
+ {
+ $rev = new Revision( array(
+ 'id' => $rc->mAttribs['rc_this_oldid'],
+ 'user' => $rc->mAttribs['rc_user'],
+ 'user_text' => $rc->mAttribs['rc_user_text'],
+ 'deleted' => $rc->mAttribs['rc_deleted']
+ ) );
+ $rev->setTitle( $page );
+ $s .= ' '.$this->skin->generateRollback( $rev );
+ }
+ }
+ }
+
+ protected function insertTags( &$s, &$rc, &$classes ) {
+ if ( empty($rc->mAttribs['ts_tags']) )
+ return;
+
+ list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' );
+ $classes = array_merge( $classes, $newClasses );
+ $s .= ' ' . $tagSummary;
+ }
+
+ protected function insertExtra( &$s, &$rc, &$classes ) {
+ ## Empty, used for subclassers to add anything special.
+ }
}
@@ -328,8 +362,8 @@ class OldChangesList extends ChangesList {
/**
* Format a line using the old system (aka without any javascript).
*/
- public function recentChangesLine( &$rc, $watched = false ) {
- global $wgContLang, $wgRCShowChangedSize, $wgUser;
+ public function recentChangesLine( &$rc, $watched = false, $linenumber = NULL ) {
+ global $wgContLang, $wgLang, $wgRCShowChangedSize, $wgUser;
wfProfileIn( __METHOD__ );
# Should patrol-related stuff be shown?
$unpatrolled = $wgUser->useRCPatrol() && !$rc->mAttribs['rc_patrolled'];
@@ -338,6 +372,17 @@ class OldChangesList extends ChangesList {
$this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] );
$s = '';
+ $classes = array();
+ // use mw-line-even/mw-line-odd class only if linenumber is given (feature from bug 14468)
+ if( $linenumber ) {
+ if( $linenumber & 1 ) {
+ $classes[] = 'mw-line-odd';
+ }
+ else {
+ $classes[] = 'mw-line-even';
+ }
+ }
+
// Moved pages
if( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) {
$this->insertMove( $s, $rc );
@@ -369,25 +414,32 @@ class OldChangesList extends ChangesList {
}
}
# User tool links
- $this->insertUserRelatedLinks($s,$rc);
+ $this->insertUserRelatedLinks( $s, $rc );
# Log action text (if any)
- $this->insertAction($s, $rc);
+ $this->insertAction( $s, $rc );
# Edit or log comment
- $this->insertComment($s, $rc);
+ $this->insertComment( $s, $rc );
+ # Tags
+ $this->insertTags( $s, $rc, $classes );
+ # Rollback
+ $this->insertRollback( $s, $rc );
+ # For subclasses
+ $this->insertExtra( $s, $rc, $classes );
+
# Mark revision as deleted if so
if( !$rc->mAttribs['rc_log_type'] && $this->isDeleted($rc,Revision::DELETED_TEXT) ) {
$s .= ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
}
# How many users watch this page
if( $rc->numberofWatchingusers > 0 ) {
- $s .= ' ' . wfMsg( 'number_of_watching_users_RCview',
- $wgContLang->formatNum($rc->numberofWatchingusers) );
+ $s .= ' ' . wfMsgExt( 'number_of_watching_users_RCview',
+ array( 'parsemag', 'escape' ), $wgLang->formatNum( $rc->numberofWatchingusers ) );
}
wfRunHooks( 'OldChangesListRecentChangesLine', array(&$this, &$s, $rc) );
wfProfileOut( __METHOD__ );
- return "$dateheader<li>$s</li>\n";
+ return "$dateheader<li class=\"".implode( ' ', $classes )."\">$s</li>\n";
}
}
@@ -417,6 +469,8 @@ class EnhancedChangesList extends ChangesList {
*/
public function recentChangesLine( &$baseRC, $watched = false ) {
global $wgLang, $wgContLang, $wgUser;
+
+ wfProfileIn( __METHOD__ );
# Create a specialised object
$rc = RCCacheEntry::newFromParent( $baseRC );
@@ -508,10 +562,8 @@ class EnhancedChangesList extends ChangesList {
if( !$showdifflinks ) {
$curLink = $this->message['cur'];
$diffLink = $this->message['diff'];
- } else if( $rc_type == RC_NEW || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
- if( $rc_type != RC_NEW ) {
- $curLink = $this->message['cur'];
- }
+ } else if( in_array( $rc_type, array(RC_NEW,RC_LOG,RC_MOVE,RC_MOVE_OVER_REDIRECT) ) ) {
+ $curLink = ($rc_type != RC_NEW) ? $this->message['cur'] : $curLink;
$diffLink = $this->message['diff'];
} else {
$diffLink = $this->skin->makeKnownLinkObj( $rc->getTitle(), $this->message['diff'],
@@ -519,9 +571,9 @@ class EnhancedChangesList extends ChangesList {
}
# Make "last" link
- if( !$showdifflinks ) {
+ if( !$showdifflinks || !$rc_last_oldid ) {
$lastLink = $this->message['last'];
- } else if( $rc_last_oldid == 0 || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
+ } else if( $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) {
$lastLink = $this->message['last'];
} else {
$lastLink = $this->skin->makeKnownLinkObj( $rc->getTitle(), $this->message['last'],
@@ -530,7 +582,7 @@ class EnhancedChangesList extends ChangesList {
# Make user links
if( $this->isDeleted($rc,Revision::DELETED_USER) ) {
- $rc->userlink = ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-user') . '</span>';
+ $rc->userlink = ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
} else {
$rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text );
$rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text );
@@ -555,8 +607,12 @@ class EnhancedChangesList extends ChangesList {
if( !isset( $this->rc_cache[$secureName] ) ) {
$this->rc_cache[$secureName] = array();
}
+
array_push( $this->rc_cache[$secureName], $rc );
}
+
+ wfProfileOut( __METHOD__ );
+
return $ret;
}
@@ -565,6 +621,9 @@ class EnhancedChangesList extends ChangesList {
*/
protected function recentChangesBlockGroup( $block ) {
global $wgLang, $wgContLang, $wgRCShowChangedSize;
+
+ wfProfileIn( __METHOD__ );
+
$r = '<table cellpadding="0" cellspacing="0" border="0" style="background: none"><tr>';
# Collate list of users
@@ -630,10 +689,10 @@ class EnhancedChangesList extends ChangesList {
# onclick handler to toggle hidden/expanded
$toggleLink = "onclick='toggleVisibility($jsid); return false'";
# Title for <a> tags
- $expandTitle = htmlspecialchars( wfMsg('rc-enhanced-expand') );
- $closeTitle = htmlspecialchars( wfMsg('rc-enhanced-hide') );
+ $expandTitle = htmlspecialchars( wfMsg( 'rc-enhanced-expand' ) );
+ $closeTitle = htmlspecialchars( wfMsg( 'rc-enhanced-hide' ) );
- $tl = "<span id='mw-rc-openarrow-$jsid' class='mw-changeslist-expanded' style='visibility:hidden'><a href='#' $toggleLink title='$expandTitle'>" . $this->sideArrow() . "</a></span>";
+ $tl = "<span id='mw-rc-openarrow-$jsid' class='mw-changeslist-expanded' style='visibility:hidden'><a href='#' $toggleLink title='$expandTitle'>" . $this->sideArrow() . "</a></span>";
$tl .= "<span id='mw-rc-closearrow-$jsid' class='mw-changeslist-hidden' style='display:none'><a href='#' $toggleLink title='$closeTitle'>" . $this->downArrow() . "</a></span>";
$r .= '<td valign="top" style="white-space: nowrap"><tt>'.$tl.'&nbsp;';
@@ -645,7 +704,7 @@ class EnhancedChangesList extends ChangesList {
# Article link
if( $namehidden ) {
- $r .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+ $r .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-event' ) . '</span>';
} else if( $allLogs ) {
$r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched );
} else {
@@ -665,7 +724,7 @@ class EnhancedChangesList extends ChangesList {
$r .= ' ';
if( !$allLogs ) {
$r .= '(';
- if( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) {
+ if( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT ) ) {
$r .= $nchanges[$n];
} else if( $isnew ) {
$r .= $nchanges[$n];
@@ -720,6 +779,8 @@ class EnhancedChangesList extends ChangesList {
# Extract fields from DB into the function scope (rc_xxxx variables)
// FIXME: Would be good to replace this extract() call with something
// that explicitly initializes variables.
+ # Classes to apply -- TODO implement
+ $classes = array();
extract( $rcObj->mAttribs );
#$r .= '<tr><td valign="top">'.$this->spacerArrow();
@@ -765,9 +826,14 @@ class EnhancedChangesList extends ChangesList {
$r .= $rcObj->userlink;
$r .= $rcObj->usertalklink;
// log action
- parent::insertAction( $r, $rcObj );
+ $this->insertAction( $r, $rcObj );
// log comment
- parent::insertComment( $r, $rcObj );
+ $this->insertComment( $r, $rcObj );
+ # Rollback
+ $this->insertRollback( $r, $rcObj );
+ # Tags
+ $this->insertTags( $r, $rcObj, $classes );
+
# Mark revision as deleted
if( !$rc_log_type && $this->isDeleted($rcObj,Revision::DELETED_TEXT) ) {
$r .= ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
@@ -778,6 +844,9 @@ class EnhancedChangesList extends ChangesList {
$r .= "</table></div>\n";
$this->rcCacheIndex++;
+
+ wfProfileOut( __METHOD__ );
+
return $r;
}
@@ -804,7 +873,7 @@ class EnhancedChangesList extends ChangesList {
protected function sideArrow() {
global $wgContLang;
$dir = $wgContLang->isRTL() ? 'l' : 'r';
- return $this->arrow( $dir, '+', wfMsg('rc-enhanced-expand') );
+ return $this->arrow( $dir, '+', wfMsg( 'rc-enhanced-expand' ) );
}
/**
@@ -813,7 +882,7 @@ class EnhancedChangesList extends ChangesList {
* @return string HTML <img> tag
*/
protected function downArrow() {
- return $this->arrow( 'd', '-', wfMsg('rc-enhanced-hide') );
+ return $this->arrow( 'd', '-', wfMsg( 'rc-enhanced-hide' ) );
}
/**
@@ -838,9 +907,13 @@ class EnhancedChangesList extends ChangesList {
*/
protected function recentChangesBlockLine( $rcObj ) {
global $wgContLang, $wgRCShowChangedSize;
+
+ wfProfileIn( __METHOD__ );
+
# Extract fields from DB into the function scope (rc_xxxx variables)
// FIXME: Would be good to replace this extract() call with something
// that explicitly initializes variables.
+ $classes = array(); // TODO implement
extract( $rcObj->mAttribs );
$curIdEq = "curid={$rc_cur_id}";
@@ -864,7 +937,7 @@ class EnhancedChangesList extends ChangesList {
# Diff and hist links
if ( $rc_type != RC_LOG ) {
$r .= ' ('. $rcObj->difflink . $this->message['semicolon-separator'];
- $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), wfMsg( 'hist' ),
+ $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), $this->message['hist'],
$curIdEq.'&action=history' ) . ')';
}
$r .= ' . . ';
@@ -883,19 +956,17 @@ class EnhancedChangesList extends ChangesList {
$this->skin, LogPage::extractParams($rc_params), true, true );
}
}
- # Edit or log comment
- if( $rc_type != RC_MOVE && $rc_type != RC_MOVE_OVER_REDIRECT ) {
- // log comment
- if ( $this->isDeleted($rcObj,LogPage::DELETED_COMMENT) ) {
- $r .= ' <span class="history-deleted">' . wfMsg('rev-deleted-comment') . '</span>';
- } else {
- $r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() );
- }
- }
+ $this->insertComment( $r, $rcObj );
+ $this->insertRollback( $r, $rcObj );
+ # Tags
+ $this->insertTags( $r, $rcObj, $classes );
# Show how many people are watching this if enabled
$r .= $this->numberofWatchingusers($rcObj->numberofWatchingusers);
$r .= "</td></tr></table>\n";
+
+ wfProfileOut( __METHOD__ );
+
return $r;
}
@@ -907,6 +978,9 @@ class EnhancedChangesList extends ChangesList {
if( count ( $this->rc_cache ) == 0 ) {
return '';
}
+
+ wfProfileIn( __METHOD__ );
+
$blockOut = '';
foreach( $this->rc_cache as $block ) {
if( count( $block ) < 2 ) {
@@ -915,6 +989,9 @@ class EnhancedChangesList extends ChangesList {
$blockOut .= $this->recentChangesBlockGroup( $block );
}
}
+
+ wfProfileOut( __METHOD__ );
+
return '<div>'.$blockOut.'</div>';
}
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index ed68fe7a..19878f76 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -33,7 +33,7 @@ if ( !defined( 'MW_PHP4' ) ) {
}
/** MediaWiki version number */
-$wgVersion = '1.14.0';
+$wgVersion = '1.15.0';
/** Name of the site. It must be changed in LocalSettings.php */
$wgSitename = 'MediaWiki';
@@ -224,6 +224,10 @@ $wgFileStore['deleted']['hash'] = 3; ///< 3-level subdirectory split
* equivalent to the corresponding member of $wgDBservers
* tablePrefix Table prefix, the foreign wiki's $wgDBprefix
* hasSharedCache True if the wiki's shared cache is accessible via the local $wgMemc
+ *
+ * ForeignAPIRepo:
+ * apibase Use for the foreign API's URL
+ * apiThumbCacheExpiry How long to locally cache thumbs for
*
* The default is to initialise these arrays from the MW<1.11 backwards compatible settings:
* $wgUploadPath, $wgThumbnailScriptPath, $wgSharedUploadDirectory, etc.
@@ -274,7 +278,8 @@ $wgUrlProtocols = array(
'nntp://', // @bug 3808 RFC 1738
'worldwind://',
'mailto:',
- 'news:'
+ 'news:',
+ 'svn://',
);
/** internal name of virus scanner. This servers as a key to the $wgAntivirusSetup array.
@@ -521,6 +526,11 @@ $wgUserEmailUseReplyTo = false;
$wgPasswordReminderResendTime = 24;
/**
+ * The time, in seconds, when an emailed temporary password expires.
+ */
+$wgNewPasswordExpiry = 3600 * 24 * 7;
+
+/**
* SMTP Mode
* For using a direct (authenticated) SMTP server connection.
* Default to false or fill an array :
@@ -977,7 +987,7 @@ $wgReadOnly = null;
$wgReadOnlyFile = false; ///< defaults to "{$wgUploadDirectory}/lock_yBgMBwiR";
/**
- * Filename for debug logging.
+ * Filename for debug logging. See http://www.mediawiki.org/wiki/How_to_debug
* The debug log file should be not be publicly accessible if it is used, as it
* may contain private data.
*/
@@ -1029,6 +1039,13 @@ $wgDebugDumpSql = false;
$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;
+
+/**
* Show the contents of $wgHooks in Special:Version
*/
$wgSpecialVersionShowHooks = false;
@@ -1240,6 +1257,8 @@ $wgGroupPermissions['bureaucrat']['userrights'] = true;
$wgGroupPermissions['bureaucrat']['noratelimit'] = true;
// Permission to change users' groups assignments across wikis
#$wgGroupPermissions['bureaucrat']['userrights-interwiki'] = true;
+// Permission to export pages including linked pages regardless of $wgExportMaxLinkDepth
+#$wgGroupPermissions['bureaucrat']['override-export-depth'] = true;
#$wgGroupPermissions['sysop']['deleterevision'] = true;
// To hide usernames from users and Sysops
@@ -1287,7 +1306,7 @@ $wgGroupsRemoveFromSelf = array();
/**
* Set of available actions that can be restricted via action=protect
* You probably shouldn't change this.
- * Translated trough restriction-* messages.
+ * Translated through restriction-* messages.
*/
$wgRestrictionTypes = array( 'edit', 'move' );
@@ -1349,6 +1368,10 @@ $wgAutoConfirmCount = 0;
* array( APCOND_EMAILCONFIRMED ), *OR*
* array( APCOND_EDITCOUNT, number of edits ), *OR*
* array( APCOND_AGE, seconds since registration ), *OR*
+ * array( APCOND_INGROUPS, group1, group2, ... ), *OR*
+ * array( APCOND_ISIP, ip ), *OR*
+ * array( APCOND_IPINRANGE, range ), *OR*
+ * array( APCOND_AGE_FROM_EDIT, seconds since first edit ), *OR*
* similar constructs defined by extensions.
*
* If $wgEmailAuthentication is off, APCOND_EMAILCONFIRMED will be true for any
@@ -1446,7 +1469,7 @@ $wgCacheEpoch = '20030516000000';
* to ensure that client-side caches don't keep obsolete copies of global
* styles.
*/
-$wgStyleVersion = '195';
+$wgStyleVersion = '207';
# Server-side caching:
@@ -1974,6 +1997,7 @@ $wgMediaHandlers = array(
'image/jpeg' => 'BitmapHandler',
'image/png' => 'BitmapHandler',
'image/gif' => 'BitmapHandler',
+ 'image/tiff' => 'TiffHandler',
'image/x-ms-bmp' => 'BmpHandler',
'image/x-bmp' => 'BmpHandler',
'image/svg+xml' => 'SvgHandler', // official
@@ -2052,6 +2076,16 @@ $wgMaxImageArea = 1.25e7;
*/
$wgMaxAnimatedGifArea = 1.0e6;
/**
+ * Browsers don't support TIFF inline generally...
+ * For inline display, we need to convert to PNG or JPEG.
+ * Note scaling should work with ImageMagick, but may not with GD scaling.
+ * // PNG is lossless, but inefficient for photos
+ * $wgTiffThumbnailType = array( 'png', 'image/png' );
+ * // JPEG is good for photos, but has no transparency support. Bad for diagrams.
+ * $wgTiffThumbnailType = array( 'jpg', 'image/jpeg' );
+ */
+$wgTiffThumbnailType = false;
+/**
* If rendered thumbnail files are older than this timestamp, they
* will be rerendered on demand as if the file didn't already exist.
* Update if there is some need to force thumbs and SVG rasterizations
@@ -2081,10 +2115,9 @@ $wgIgnoreImageErrors = false;
*/
$wgGenerateThumbnailOnParse = true;
-/** Obsolete, always true, kept for compatibility with extensions */
+/** Whether or not to use image resizing */
$wgUseImageResize = true;
-
/** Set $wgCommandLineMode if it's not set already, to avoid notices */
if( !isset( $wgCommandLineMode ) ) {
$wgCommandLineMode = false;
@@ -2252,9 +2285,26 @@ $wgExportMaxHistory = 0;
$wgExportAllowListContributors = false ;
+/**
+ * If non-zero, Special:Export accepts a "pagelink-depth" parameter
+ * up to this specified level, which will cause it to include all
+ * pages linked to from the pages you specify. Since this number
+ * can become *insanely large* and could easily break your wiki,
+ * it's disabled by default for now.
+ *
+ * There's a HARD CODED limit of 5 levels of recursion to prevent a
+ * crazy-big export from being done by someone setting the depth
+ * number too high. In other words, last resort safety net.
+ */
+$wgExportMaxLinkDepth = 0;
/**
- * Edits matching these regular expressions in body text or edit summary
+ * Whether to allow the "export all pages in namespace" option
+ */
+$wgExportFromNamespaces = false;
+
+/**
+ * Edits matching these regular expressions in body text
* will be recognised as spam and rejected automatically.
*
* There's no administrator override on-wiki, so be careful what you set. :)
@@ -2264,6 +2314,9 @@ $wgExportAllowListContributors = false ;
*/
$wgSpamRegex = array();
+/** Same as the above except for edit summaries */
+$wgSummarySpamRegex = array();
+
/** Similarly you can get a function to do the job. The function will be given
* the following args:
* - a Title object for the article the edit is made on
@@ -2374,6 +2427,8 @@ $wgDefaultUserOptions = array(
'rclimit' => 50,
'wllimit' => 250,
'hideminor' => 0,
+ 'hidepatrolled' => 0,
+ 'newpageshidepatrolled' => 0,
'highlightbroken' => 1,
'stubthreshold' => 0,
'previewontop' => 1,
@@ -2413,11 +2468,13 @@ $wgDefaultUserOptions = array(
'watchlisthideown' => 0,
'watchlisthideanons' => 0,
'watchlisthideliu' => 0,
+ 'watchlisthidepatrolled' => 0,
'watchcreations' => 0,
'watchdefault' => 0,
'watchmoves' => 0,
'watchdeletion' => 0,
'noconvertlink' => 0,
+ 'gender' => 'unknown',
);
/** Whether or not to allow and use real name fields. Defaults to true. */
@@ -2504,7 +2561,7 @@ $wgAutoloadClasses = array();
* $wgExtensionCredits[$type][] = array(
* 'name' => 'Example extension',
* 'version' => 1.9,
- * 'svn-revision' => '$LastChangedRevision: 47653 $',
+ * 'svn-revision' => '$LastChangedRevision: 51678 $',
* 'author' => 'Foo Barstein',
* 'url' => 'http://wwww.example.com/Example%20Extension/',
* 'description' => 'An example extension',
@@ -2724,6 +2781,9 @@ $wgBrowserBlackList = array(
*
* This variable is currently used ONLY for signature formatting, not for
* anything else.
+ *
+ * Timezones can be translated by editing MediaWiki messages of type
+ * timezone-nameinlowercase like timezone-utc.
*/
# $wgLocaltimezone = 'GMT';
# $wgLocaltimezone = 'PST8PDT';
@@ -2754,17 +2814,17 @@ $wgLocalTZoffset = null;
/**
- * When translating messages with wfMsg(), it is not always clear what should be
- * considered UI messages and what shoud be content messages.
+ * When translating messages with wfMsg(), it is not always clear what should
+ * be considered UI messages and what should be content messages.
*
- * For example, for regular wikipedia site like en, there should be only one
- * 'mainpage', therefore when getting the link of 'mainpage', we should treate
- * it as content of the site and call wfMsgForContent(), while for rendering the
- * text of the link, we call wfMsg(). The code in default behaves this way.
- * However, sites like common do offer different versions of 'mainpage' and the
- * like for different languages. This array provides a way to override the
- * default behavior. For example, to allow language specific mainpage and
- * community portal, set
+ * For example, for the English Wikipedia, there should be only one 'mainpage',
+ * so when getting the link for 'mainpage', we should treat it as site content
+ * and call wfMsgForContent(), but for rendering the text of the link, we call
+ * wfMsg(). The code behaves this way by default. However, sites like the
+ * Wikimedia Commons do offer different versions of 'mainpage' and the like for
+ * different languages. This array provides a way to override the default
+ * behavior. For example, to allow language-specific main page and community
+ * portal, set
*
* $wgForceUIMsgAsContentMsg = array( 'mainpage', 'portal-url' );
*/
@@ -2965,6 +3025,7 @@ $wgSpecialPageGroups = array(
'Newimages' => 'changes',
'Newpages' => 'changes',
'Log' => 'changes',
+ 'Tags' => 'changes',
'Upload' => 'media',
'Listfiles' => 'media',
@@ -3073,6 +3134,19 @@ $wgNoFollowLinks = true;
$wgNoFollowNsExceptions = array();
/**
+ * If this is set to an array of domains, external links to these domain names
+ * (or any subdomains) will not be set to rel="nofollow" regardless of the
+ * value of $wgNoFollowLinks. For instance:
+ *
+ * $wgNoFollowDomainExceptions = array( 'en.wikipedia.org', 'wiktionary.org' );
+ *
+ * This would add rel="nofollow" to links to de.wikipedia.org, but not
+ * en.wikipedia.org, wiktionary.org, en.wiktionary.org, us.en.wikipedia.org,
+ * etc.
+ */
+$wgNoFollowDomainExceptions = array();
+
+/**
* Default robot policy. The default policy is to encourage indexing and fol-
* lowing of links. It may be overridden on a per-namespace and/or per-page
* basis.
@@ -3216,6 +3290,12 @@ $wgRateLimitLog = null;
$wgRateLimitsExcludedGroups = array();
/**
+ * Array of IPs which should be excluded from rate limits.
+ * This may be useful for whitelisting NAT gateways for conferences, etc.
+ */
+$wgRateLimitsExcludedIPs = array();
+
+/**
* On Special:Unusedimages, consider images "used", if they are put
* into a category. Default (false) is not to count those as used.
*/
@@ -3490,6 +3570,18 @@ $wgAPIListModules = array();
$wgAPIMaxDBRows = 5000;
/**
+ * The maximum size (in bytes) of an API result.
+ * Don't set this lower than $wgMaxArticleSize*1024
+ */
+$wgAPIMaxResultSize = 8388608;
+
+/**
+ * The maximum number of uncached diffs that can be retrieved in one API
+ * request. Set this to 0 to disable API diffs altogether
+ */
+$wgAPIMaxUncachedDiffs = 1;
+
+/**
* Parser test suite files to be run by parserTests.php when no specific
* filename is passed to it.
*
@@ -3600,6 +3692,25 @@ $wgMaximumMovedPages = 100;
$wgFixDoubleRedirects = false;
/**
+ * Max number of redirects to follow when resolving redirects.
+ * 1 means only the first redirect is followed (default behavior).
+ * 0 or less means no redirects are followed.
+ */
+$wgMaxRedirects = 1;
+
+/**
+ * Array of invalid page redirect targets.
+ * Attempting to create a redirect to any of the pages in this array
+ * will make the redirect fail.
+ * Userlogout is hard-coded, so it does not need to be listed here.
+ * (bug 10569) Disallow Mypage and Mytalk as well.
+ *
+ * As of now, this only checks special pages. Redirects to pages in
+ * other namespaces cannot be invalidated by this variable.
+ */
+$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk' );
+
+/**
* Array of namespaces to generate a sitemap for when the
* maintenance/generateSitemap.php script is run, or false if one is to be ge-
* nerated for all namespaces.
@@ -3626,10 +3737,10 @@ $wgPasswordAttemptThrottle = array( 'count' => 5, 'seconds' => 300 );
$wgEdititis = false;
/**
-* Enable the UniversalEditButton for browsers that support it
-* (currently only Firefox with an extension)
-* See http://universaleditbutton.org for more background information
-*/
+ * Enable the UniversalEditButton for browsers that support it
+ * (currently only Firefox with an extension)
+ * See http://universaleditbutton.org for more background information
+ */
$wgUniversalEditButton = true;
/**
@@ -3638,3 +3749,45 @@ $wgUniversalEditButton = true;
* and the functionality will be enabled universally.
*/
$wgEnforceHtmlIds = true;
+
+/**
+ * Search form behavior
+ * true = use Go & Search buttons
+ * false = use Go button & Advanced search link
+ */
+$wgUseTwoButtonsSearchForm = true;
+
+/**
+ * Preprocessor caching threshold
+ */
+$wgPreprocessorCacheThreshold = 1000;
+
+/**
+ * Allow filtering by change tag in recentchanges, history, etc
+ * Has no effect if no tags are defined in valid_tag.
+ */
+$wgUseTagFilter = true;
+
+/**
+ * Allow redirection to another page when a user logs in.
+ * To enable, set to a string like 'Main Page'
+ */
+$wgRedirectOnLogin = null;
+
+/**
+ * Characters to prevent during new account creations.
+ * This is used in a regular expression character class during
+ * registration (regex metacharacters like / are escaped).
+ */
+$wgInvalidUsernameCharacters = '@';
+
+/**
+ * Character used as a delimiter when testing for interwiki userrights
+ * (In Special:UserRights, it is possible to modify users on different
+ * databases if the delimiter is used, e.g. Someuser@enwiki).
+ *
+ * It is recommended that you have this delimiter in
+ * $wgInvalidUsernameCharacters above, or you will not be able to
+ * modify the user rights of those users via Special:UserRights
+ */
+$wgUserrightsInterwikiDelimiter = '@';
diff --git a/includes/EditPage.php b/includes/EditPage.php
index 0193dc38..3589b52d 100644
--- a/includes/EditPage.php
+++ b/includes/EditPage.php
@@ -84,6 +84,7 @@ class EditPage {
/* $didSave should be set to true whenever an article was succesfully altered. */
public $didSave = false;
+ public $undidRev = 0;
public $suppressIntro = false;
@@ -164,35 +165,28 @@ class EditPage {
$undorev->getPage() == $this->mArticle->getID() &&
!$undorev->isDeleted( Revision::DELETED_TEXT ) &&
!$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
- $undorev_text = $undorev->getText();
- $oldrev_text = $oldrev->getText();
- $currev_text = $text;
-
- if ( $currev_text != $undorev_text ) {
- $result = wfMerge( $undorev_text, $oldrev_text, $currev_text, $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 {
- # No use doing a merge if it's just a straight revert.
- $text = $oldrev_text;
- $result = true;
- }
- if ( $result ) {
+ $text = $undotext;
# Inform the user of our success and set an automatic edit summary
- $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-success' ) );
+ $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->summary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() );
+ $this->undidRev = $undo;
}
$this->formtype = 'diff';
- } else {
- # Warn the user that something went wrong
- $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-failure' ) );
}
} 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( wfMsgNoTrans( 'undo-norev' ) );
+ $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-norev">' . wfMsgNoTrans( 'undo-norev' ) . '</div>' );
}
} else if ( $section != '' ) {
if ( $section == 'new' ) {
@@ -330,7 +324,7 @@ class EditPage {
protected function wasDeletedSinceLastEdit() {
if ( $this->deletedSinceEdit )
return true;
- if ( $this->mTitle->isDeleted() ) {
+ if ( $this->mTitle->isDeletedQuick() ) {
$this->lastDelete = $this->getLastDelete();
if ( $this->lastDelete ) {
$deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp );
@@ -409,6 +403,11 @@ class EditPage {
}
}
}
+
+ // 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" );
@@ -427,7 +426,6 @@ class EditPage {
# Optional notices on a per-namespace and per-page basis
$editnotice_ns = 'editnotice-'.$this->mTitle->getNamespace();
- $editnotice_page = $editnotice_ns.'-'.$this->mTitle->getDBkey();
if ( !wfEmptyMsg( $editnotice_ns, wfMsgForContent( $editnotice_ns ) ) ) {
$wgOut->addWikiText( wfMsgForContent( $editnotice_ns ) );
}
@@ -440,8 +438,6 @@ class EditPage {
$wgOut->addWikiText( wfMsgForContent( $editnotice_base ) );
}
}
- } else if ( !wfEmptyMsg( $editnotice_page, wfMsgForContent( $editnotice_page ) ) ) {
- $wgOut->addWikiText( wfMsgForContent( $editnotice_page ) );
}
# Attempt submission here. This will check for edit conflicts,
@@ -529,7 +525,7 @@ class EditPage {
} elseif ( $this->section == 'new' ) {
// Nothing *to* preview for new sections
return false;
- } elseif ( ( $wgRequest->getVal( 'preload' ) !== '' || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
+ } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) {
// Standard preference behaviour
return true;
} elseif ( !$this->mTitle->exists() && $this->mTitle->getNamespace() == NS_CATEGORY ) {
@@ -560,7 +556,7 @@ class EditPage {
$this->textbox2 = $this->safeUnicodeInput( $request, 'wpTextbox2' );
$this->mMetaData = rtrim( $request->getText( 'metadata' ) );
# Truncate for whole multibyte characters. +5 bytes for ellipsis
- $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250 );
+ $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250, '' );
# Remove extra headings from summaries and new sections.
$this->summary = preg_replace('/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary);
@@ -574,7 +570,7 @@ class EditPage {
# If the form is incomplete, force to preview.
wfDebug( "$fname: Form data appears to be incomplete\n" );
wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" );
- $this->preview = true;
+ $this->preview = true;
} else {
/* Fallback for live preview */
$this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' );
@@ -644,6 +640,13 @@ class EditPage {
if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) {
$this->summary = $request->getVal( 'preloadtitle' );
}
+ elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) {
+ $this->summary = $request->getText( 'summary' );
+ }
+
+ if ( $request->getVal( 'minor' ) ) {
+ $this->minoredit = true;
+ }
}
$this->oldid = $request->getInt( 'oldid' );
@@ -677,8 +680,16 @@ class EditPage {
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</div>", 'editinginterface' );
+ }
+
# Show a warning message when someone creates/edits a user (talk) page but the user does not exists
- if ( $this->mTitle->getNamespace() == NS_USER || $this->mTitle->getNamespace() == NS_USER_TALK ) {
+ if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) {
$parts = explode( '/', $this->mTitle->getText(), 2 );
$username = $parts[0];
$id = User::idFromName( $username );
@@ -737,7 +748,7 @@ class EditPage {
if ( !wfRunHooks( 'EditPage::attemptSave', array( &$this ) ) )
{
- wfDebug( "Hook 'EditPage::attemptSave' aborted article saving" );
+ wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" );
return self::AS_HOOK_ERROR;
}
@@ -757,7 +768,7 @@ class EditPage {
$this->mMetaData = '' ;
# Check for spam
- $match = self::matchSpamRegex( $this->summary );
+ $match = self::matchSummarySpamRegex( $this->summary );
if ( $match === false ) {
$match = self::matchSpamRegex( $this->textbox1 );
}
@@ -859,11 +870,20 @@ class EditPage {
wfProfileOut( $fname );
return self::AS_HOOK_ERROR;
}
+
+ # Handle the user preference to force summaries here. Check if it's not a redirect.
+ if ( !$this->allowBlankSummary && !Title::newFromRedirect( $this->textbox1 ) ) {
+ if ( md5( $this->summary ) == $this->autoSumm ) {
+ $this->missingSummary = true;
+ wfProfileOut( $fname );
+ return self::AS_SUMMARY_NEEDED;
+ }
+ }
$isComment = ( $this->section == 'new' );
$this->mArticle->insertNewArticle( $this->textbox1, $this->summary,
- $this->minoredit, $this->watchthis, false, $isComment, $bot);
+ $this->minoredit, $this->watchthis, false, $isComment, $bot );
wfProfileOut( $fname );
return self::AS_SUCCESS_NEW_ARTICLE;
@@ -893,39 +913,35 @@ class EditPage {
}
}
$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( "EditPage::editForm Suppressing edit conflict, same user.\n" );
+ $this->isConflict = false;
+ }
if ( $this->isConflict ) {
wfDebug( "EditPage::editForm conflict! getting section '$this->section' for time '$this->edittime' (article time '" .
$this->mArticle->getTimestamp() . "')\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary, $this->edittime);
- }
- else {
+ $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary, $this->edittime );
+ } else {
wfDebug( "EditPage::editForm getting section '$this->section'\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary);
+ $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary );
}
if ( is_null( $text ) ) {
wfDebug( "EditPage::editForm activating conflict; section replace failed.\n" );
$this->isConflict = true;
- $text = $this->textbox1;
- }
-
- # Suppress edit conflict with self, except for section edits where merging is required.
- if ( $this->section == '' && $userid && $this->userWasLastToEdit($userid,$this->edittime) ) {
- wfDebug( "EditPage::editForm Suppressing edit conflict, same user.\n" );
- $this->isConflict = false;
- } else {
- # switch from section editing to normal editing in edit conflict
- if ( $this->isConflict ) {
- # Attempt merge
- if ( $this->mergeChangesInto( $text ) ) {
- // Successful merge! Maybe we should tell the user the good news?
- $this->isConflict = false;
- wfDebug( "EditPage::editForm Suppressing edit conflict, successful merge.\n" );
- } else {
- $this->section = '';
- $this->textbox1 = $text;
- wfDebug( "EditPage::editForm Keeping edit conflict, failed merge.\n" );
- }
+ $text = $this->textbox1; // do not try to merge here!
+ } else if ( $this->isConflict ) {
+ # Attempt merge
+ if ( $this->mergeChangesInto( $text ) ) {
+ // Successful merge! Maybe we should tell the user the good news?
+ $this->isConflict = false;
+ wfDebug( "EditPage::editForm Suppressing edit conflict, successful merge.\n" );
+ } else {
+ $this->section = '';
+ $this->textbox1 = $text;
+ wfDebug( "EditPage::editForm Keeping edit conflict, failed merge.\n" );
}
}
@@ -944,9 +960,9 @@ 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) &&
- !is_object( Title::newFromRedirect( $text ) ) # check if it's not a redirect
- ) {
+ if ( $this->section != 'new' && !$this->allowBlankSummary && 0 != strcmp($oldtext,$text)
+ && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
+ {
if ( md5( $this->summary ) == $this->autoSumm ) {
$this->missingSummary = true;
wfProfileOut( $fname );
@@ -1008,7 +1024,8 @@ class EditPage {
# update the article here
if ( $this->mArticle->updateArticle( $text, $this->summary, $this->minoredit,
- $this->watchthis, $bot, $sectionanchor ) ) {
+ $this->watchthis, $bot, $sectionanchor ) )
+ {
wfProfileOut( $fname );
return self::AS_SUCCESS_UPDATE;
} else {
@@ -1024,6 +1041,7 @@ class EditPage {
* 50 revisions for the sake of performance.
*/
protected function userWasLastToEdit( $id, $edittime ) {
+ if( !$id ) return false;
$dbw = wfGetDB( DB_MASTER );
$res = $dbw->select( 'revision',
'rev_user',
@@ -1047,14 +1065,26 @@ class EditPage {
*/
public static function matchSpamRegex( $text ) {
global $wgSpamRegex;
- if ( $wgSpamRegex ) {
- // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
- $regexes = (array)$wgSpamRegex;
- foreach( $regexes as $regex ) {
- $matches = array();
- if ( preg_match( $regex, $text, $matches ) ) {
- return $matches[0];
- }
+ // For back compatibility, $wgSpamRegex may be a single string or an array of regexes.
+ $regexes = (array)$wgSpamRegex;
+ return self::matchSpamRegexInternal( $text, $regexes );
+ }
+
+ /**
+ * Check given input text against $wgSpamRegex, and return the text of the first match.
+ * @return mixed -- matching string or false
+ */
+ public static function matchSummarySpamRegex( $text ) {
+ global $wgSummarySpamRegex;
+ $regexes = (array)$wgSummarySpamRegex;
+ return self::matchSpamRegexInternal( $text, $regexes );
+ }
+
+ protected static function matchSpamRegexInternal( $text, $regexes ) {
+ foreach( $regexes as $regex ) {
+ $matches = array();
+ if( preg_match( $regex, $text, $matches ) ) {
+ return $matches[0];
}
}
return false;
@@ -1133,7 +1163,7 @@ class EditPage {
$wgOut->setArticleRelated( true );
if ( $this->isConflict ) {
- $wgOut->addWikiMsg( 'explainconflict' );
+ $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1</div>", 'explainconflict' );
$this->textbox2 = $this->textbox1;
$this->textbox1 = $this->getContent();
@@ -1142,9 +1172,7 @@ class EditPage {
if ( $this->section != '' && $this->section != 'new' ) {
$matches = array();
if ( !$this->summary && !$this->preview && !$this->diff ) {
- preg_match( "/^(=+)(.+)\\1/mi",
- $this->textbox1,
- $matches );
+ preg_match( "/^(=+)(.+)\\1/mi", $this->textbox1, $matches );
if ( !empty( $matches[2] ) ) {
global $wgParser;
$this->summary = "/* " .
@@ -1155,7 +1183,7 @@ class EditPage {
}
if ( $this->missingComment ) {
- $wgOut->wrapWikiMsg( '<div id="mw-missingcommenttext">$1</div>', 'missingcommenttext' );
+ $wgOut->wrapWikiMsg( '<div id="mw-missingcommenttext">$1</div>', 'missingcommenttext' );
}
if ( $this->missingSummary && $this->section != 'new' ) {
@@ -1177,9 +1205,9 @@ class EditPage {
// Let sysop know that this will make private content public if saved
if ( !$this->mArticle->mRevision->userCan( Revision::DELETED_TEXT ) ) {
- $wgOut->addWikiMsg( 'rev-deleted-text-permission' );
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' );
} else if ( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
- $wgOut->addWikiMsg( 'rev-deleted-text-view' );
+ $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' );
}
if ( !$this->mArticle->mRevision->isCurrent() ) {
@@ -1208,8 +1236,6 @@ class EditPage {
$classes = array(); // Textarea CSS
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
- # Show a warning if editing an interface message
- $wgOut->addWikiMsg( 'editinginterface' );
} elseif ( $this->mTitle->isProtected( 'edit' ) ) {
# Is the title semi-protected?
if ( $this->mTitle->isSemiProtected() ) {
@@ -1228,17 +1254,19 @@ class EditPage {
if ( $this->mTitle->isCascadeProtected() ) {
# Is this page under cascading protection from some source pages?
list($cascadeSources, /* $restrictions */) = $this->mTitle->getCascadeProtectionSources();
- $notice = "$1\n";
- if ( count($cascadeSources) > 0 ) {
+ $notice = "<div class='mw-cascadeprotectedwarning'>$1\n";
+ $cascadeSourcesCount = count( $cascadeSources );
+ if ( $cascadeSourcesCount > 0 ) {
# Explain, and list the titles responsible
foreach( $cascadeSources as $page ) {
$notice .= '* [[:' . $page->getPrefixedText() . "]]\n";
}
}
- $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', count($cascadeSources) ) );
+ $notice .= '</div>';
+ $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) );
}
if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) {
- $wgOut->addWikiMsg( 'titleprotectedwarning' );
+ $wgOut->wrapWikiMsg( '<div class="mw-titleprotectedwarning">$1</div>', 'titleprotectedwarning' );
}
if ( $this->kblength === false ) {
@@ -1263,6 +1291,7 @@ class EditPage {
$cancel = $sk->makeKnownLink( $wgTitle->getPrefixedText(),
wfMsgExt('cancel', array('parseinline')) );
+ $separator = wfMsgExt( 'pipe-separator' , 'escapenoentities' );
$edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ));
$edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'.
htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '.
@@ -1318,7 +1347,7 @@ class EditPage {
# if this is a comment, show a subject line at the top, which is also the edit summary.
# Otherwise, show a summary field at the bottom
- $summarytext = htmlspecialchars( $wgContLang->recodeForEdit( $this->summary ) ); # FIXME
+ $summarytext = $wgContLang->recodeForEdit( $this->summary );
# If a blank edit summary was previously provided, and the appropriate
# user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the
@@ -1332,7 +1361,26 @@ class EditPage {
$autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary );
$summaryhiddens .= Xml::hidden( 'wpAutoSummary', $autosumm );
if ( $this->section == 'new' ) {
- $commentsubject="<span id='wpSummaryLabel'><label for='wpSummary'>{$subject}</label></span>\n<input tabindex='1' type='text' value=\"$summarytext\" name='wpSummary' id='wpSummary' maxlength='200' size='60' />{$summaryhiddens}<br />";
+ $commentsubject = '';
+ if ( !$wgRequest->getBool( 'nosummary' ) ) {
+ # Add a class if 'missingsummary' is triggered to allow styling of the summary line
+ $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
+
+ $commentsubject =
+ Xml::tags( 'label', array( 'for' => 'wpSummary' ), $subject );
+ $commentsubject =
+ Xml::tags( 'span', array( 'class' => $summaryClass, 'id' => "wpSummaryLabel" ),
+ $commentsubject );
+ $commentsubject .= '&nbsp;';
+ $commentsubject .= Xml::input( 'wpSummary',
+ 60,
+ $summarytext,
+ array(
+ 'id' => 'wpSummary',
+ 'maxlength' => '200',
+ 'tabindex' => '1'
+ ) );
+ }
$editsummary = "<div class='editOptions'>\n";
global $wgParser;
$formattedSummary = wfMsgForContent( 'newsectionsummary', $wgParser->stripSectionName( $this->summary ) );
@@ -1340,10 +1388,39 @@ class EditPage {
$summarypreview = '';
} else {
$commentsubject = '';
- $editsummary="<div class='editOptions'>\n<span id='wpSummaryLabel'><label for='wpSummary'>{$summary}</label></span>\n<input tabindex='2' type='text' value=\"$summarytext\" name='wpSummary' id='wpSummary' maxlength='200' size='60' />{$summaryhiddens}<br />";
- $summarypreview = $summarytext && $this->preview ? "<div class=\"mw-summary-preview\">". wfMsg('summary-preview') .$sk->commentBlock( $this->summary, $this->mTitle )."</div>\n" : '';
+
+ # Add a class if 'missingsummary' is triggered to allow styling of the summary line
+ $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary';
+
+ $editsummary = Xml::tags( 'label', array( 'for' => 'wpSummary' ), $summary );
+ $editsummary = Xml::tags( 'span', array( 'class' => $summaryClass, 'id' => "wpSummaryLabel" ),
+ $editsummary ) . ' ';
+
+ $editsummary .= Xml::input( 'wpSummary',
+ 60,
+ $summarytext,
+ array(
+ 'id' => 'wpSummary',
+ 'maxlength' => '200',
+ 'tabindex' => '1'
+ ) );
+
+ // No idea where this is closed.
+ $editsummary = Xml::openElement( 'div', array( 'class' => 'editOptions' ) )
+ . $editsummary . '<br/>';
+
+ $summarypreview = '';
+ if ( $summarytext && $this->preview ) {
+ $summarypreview =
+ Xml::tags( 'div',
+ array( 'class' => 'mw-summary-preview' ),
+ wfMsg( 'summary-preview' ) .
+ $sk->commentBlock( $this->summary, $this->mTitle )
+ );
+ }
$subjectpreview = '';
}
+ $commentsubject .= $summaryhiddens;
# Set focus to the edit box on load, except on preview or diff, where it would interfere with the display
if ( !$this->preview && !$this->diff ) {
@@ -1373,15 +1450,18 @@ class EditPage {
$recreate = '';
if ( $this->wasDeletedSinceLastEdit() ) {
if ( 'save' != $this->formtype ) {
- $wgOut->addWikiMsg('deletedwhileediting');
+ $wgOut->wrapWikiMsg(
+ "<div class='error mw-deleted-while-editing'>\n$1</div>",
+ 'deletedwhileediting' );
} else {
- // Hide the toolbar and edit area, use can click preview to get it back
+ // Hide the toolbar and edit area, user can click preview to get it back
// Add an confirmation checkbox and explanation.
$toolbar = '';
- $recreate = $wgOut->parse( wfMsg( 'confirmrecreate', $this->lastDelete->user_name , $this->lastDelete->log_comment ));
- $recreate .=
- "<br /><input tabindex='1' type='checkbox' value='1' name='wpRecreate' id='wpRecreate' />".
- "<label for='wpRecreate' title='".wfMsg('tooltip-recreate')."'>". wfMsg('recreate')."</label>";
+ $recreate = '<div class="mw-confirm-recreate">' .
+ $wgOut->parse( wfMsg( 'confirmrecreate', $this->lastDelete->user_name , $this->lastDelete->log_comment ) ) .
+ Xml::checkLabel( wfMsg( 'recreate' ), 'wpRecreate', 'wpRecreate', false,
+ array( 'title' => $sk->titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' )
+ ) . '</div>';
}
}
@@ -1436,7 +1516,7 @@ END
$wgOut->addHTML(
"<div class='editButtons'>
{$buttonshtml}
- <span class='editHelp'>{$cancel} | {$edithelp}</span>
+ <span class='editHelp'>{$cancel}{$separator}{$edithelp}</span>
</div><!-- editButtons -->
</div><!-- editOptions -->");
@@ -1606,7 +1686,7 @@ END
$wgOut->addHTML( '</div>' );
}
- function getLastDelete() {
+ protected function getLastDelete() {
$dbr = wfGetDB( DB_SLAVE );
$data = $dbr->selectRow(
array( 'logging', 'user' ),
@@ -1618,15 +1698,23 @@ END
'log_title',
'log_comment',
'log_params',
- 'user_name', ),
+ 'log_deleted',
+ 'user_name' ),
array( 'log_namespace' => $this->mTitle->getNamespace(),
'log_title' => $this->mTitle->getDBkey(),
'log_type' => 'delete',
'log_action' => 'delete',
'user_id=log_user' ),
__METHOD__,
- array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ) );
-
+ array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' )
+ );
+ // Quick paranoid permission checks...
+ if( is_object($data) ) {
+ if( $data->log_deleted & LogPage::DELETED_USER )
+ $data->user_name = wfMsgHtml('rev-deleted-user');
+ if( $data->log_deleted & LogPage::DELETED_COMMENT )
+ $data->log_comment = wfMsgHtml('rev-deleted-comment');
+ }
return $data;
}
@@ -1651,6 +1739,8 @@ END
$parserOptions = ParserOptions::newFromUser( $wgUser );
$parserOptions->setEditSection( false );
+ $parserOptions->setIsPreview( true );
+ $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
global $wgRawHtml;
if ( $wgRawHtml && !$this->mTokenOk ) {
@@ -1672,7 +1762,7 @@ END
$parserOptions->setTidy(true);
$parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
$previewHTML = $parserOutput->mText;
- } elseif ( $rt = Title::newFromRedirect( $this->textbox1 ) ) {
+ } elseif ( $rt = Title::newFromRedirectArray( $this->textbox1 ) ) {
$previewHTML = $this->mArticle->viewRedirect( $rt, false );
} else {
$toparse = $this->textbox1;
@@ -1834,8 +1924,7 @@ END
$baseText = $baseRevision->getText();
// The current state, we want to merge updates into it
- $currentRevision = Revision::loadFromTitle(
- $db, $this->mTitle );
+ $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
if ( is_null( $currentRevision ) ) {
wfProfileOut( $fname );
return false;
@@ -2389,7 +2478,9 @@ END
global $wgUser, $wgOut, $wgTitle, $wgRequest;
$resultDetails = false;
- $value = $this->internalAttemptSave( $resultDetails, $wgUser->isAllowed('bot') && $wgRequest->getBool('bot', true) );
+ # Allow bots to exempt some edits from bot flagging
+ $bot = $wgUser->isAllowed('bot') && $wgRequest->getBool('bot',true);
+ $value = $this->internalAttemptSave( $resultDetails, $bot );
if ( $value == self::AS_SUCCESS_UPDATE || $value == self::AS_SUCCESS_NEW_ARTICLE ) {
$this->didSave = true;
diff --git a/includes/EnotifNotifyJob.php b/includes/EnotifNotifyJob.php
index 31fcb0d5..f7178d0f 100644
--- a/includes/EnotifNotifyJob.php
+++ b/includes/EnotifNotifyJob.php
@@ -26,7 +26,8 @@ class EnotifNotifyJob extends Job {
$this->params['timestamp'],
$this->params['summary'],
$this->params['minorEdit'],
- $this->params['oldid']
+ $this->params['oldid'],
+ $this->params['watchers']
);
return true;
}
diff --git a/includes/Exception.php b/includes/Exception.php
index eb715986..5f808b20 100644
--- a/includes/Exception.php
+++ b/includes/Exception.php
@@ -161,23 +161,26 @@ class MWException extends Exception {
if( $hookResult = $this->runHooks( get_class( $this ) . "Raw" ) ) {
die( $hookResult );
}
- echo $this->htmlHeader();
- echo $this->getHTML();
- echo $this->htmlFooter();
+ if ( defined( 'MEDIAWIKI_INSTALL' ) || $this->htmlBodyOnly() ) {
+ echo $this->getHTML();
+ } else {
+ echo $this->htmlHeader();
+ echo $this->getHTML();
+ echo $this->htmlFooter();
+ }
}
}
/**
* Output a report about the exception and takes care of formatting.
- * It will be either HTML or plain text based on $wgCommandLineMode.
+ * It will be either HTML or plain text based on isCommandLine().
*/
function report() {
- global $wgCommandLineMode;
$log = $this->getLogMessage();
if ( $log ) {
wfDebugLog( 'exception', $log );
}
- if ( $wgCommandLineMode ) {
+ if ( self::isCommandLine() ) {
wfPrintError( $this->getText() );
} else {
$this->reportHTML();
@@ -204,7 +207,7 @@ class MWException extends Exception {
<title>$title</title>
</head>
<body>
- <h1><img src='$wgLogo' style='float:left;margin-right:1em' alt=''>$title</h1>
+ <h1><img src='$wgLogo' style='float:left;margin-right:1em' alt=''/>$title</h1>
";
}
@@ -214,6 +217,17 @@ class MWException extends Exception {
function htmlFooter() {
echo "</body></html>";
}
+
+ /**
+ * headers handled by subclass?
+ */
+ function htmlBodyOnly() {
+ return false;
+ }
+
+ static function isCommandLine() {
+ return !empty( $GLOBALS['wgCommandLineMode'] ) && !defined( 'MEDIAWIKI_INSTALL' );
+ }
}
/**
@@ -264,41 +278,44 @@ function wfInstallExceptionHandler() {
* Report an exception to the user
*/
function wfReportException( Exception $e ) {
- if ( $e instanceof MWException ) {
- try {
- $e->report();
- } catch ( Exception $e2 ) {
- // Exception occurred from within exception handler
- // Show a simpler error message for the original exception,
- // don't try to invoke report()
- $message = "MediaWiki internal error.\n\n" .
- "Original exception: " . $e->__toString() .
- "\n\nException caught inside exception handler: " .
- $e2->__toString() . "\n";
-
- if ( !empty( $GLOBALS['wgCommandLineMode'] ) ) {
- wfPrintError( $message );
- } else {
- echo nl2br( htmlspecialchars( $message ) ). "\n";
- }
- }
- } else {
- $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
- $e->__toString() . "\n";
- if ( $GLOBALS['wgShowExceptionDetails'] ) {
- $message .= "\n" . $e->getTraceAsString() ."\n";
- }
- if ( !empty( $GLOBALS['wgCommandLineMode'] ) ) {
- wfPrintError( $message );
- } else {
- echo nl2br( htmlspecialchars( $message ) ). "\n";
- }
- }
+ $cmdLine = MWException::isCommandLine();
+ if ( $e instanceof MWException ) {
+ try {
+ $e->report();
+ } catch ( Exception $e2 ) {
+ // Exception occurred from within exception handler
+ // Show a simpler error message for the original exception,
+ // don't try to invoke report()
+ $message = "MediaWiki internal error.\n\n";
+ if ( $GLOBALS['wgShowExceptionDetails'] )
+ $message .= "Original exception: " . $e->__toString();
+ $message .= "\n\nException caught inside exception handler";
+ if ( $GLOBALS['wgShowExceptionDetails'] )
+ $message .= ": " . $e2->__toString();
+ $message .= "\n";
+ if ( $cmdLine ) {
+ wfPrintError( $message );
+ } else {
+ echo nl2br( htmlspecialchars( $message ) ). "\n";
+ }
+ }
+ } else {
+ $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" .
+ $e->__toString() . "\n";
+ if ( $GLOBALS['wgShowExceptionDetails'] ) {
+ $message .= "\n" . $e->getTraceAsString() ."\n";
+ }
+ if ( $cmdLine ) {
+ wfPrintError( $message );
+ } else {
+ echo nl2br( htmlspecialchars( $message ) ). "\n";
+ }
+ }
}
/**
* Print a message, if possible to STDERR.
- * Use this in command line mode only (see wgCommandLineMode)
+ * Use this in command line mode only (see isCommandLine)
*/
function wfPrintError( $message ) {
#NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
diff --git a/includes/Exif.php b/includes/Exif.php
index d5cf09cf..9e54bd55 100644
--- a/includes/Exif.php
+++ b/includes/Exif.php
@@ -1,10 +1,5 @@
<?php
/**
- * @ingroup Media
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
- * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
- *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
@@ -20,7 +15,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
+ * @ingroup Media
+ * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+ * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
* @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification
+ * @file
*/
/**
@@ -28,23 +28,21 @@
* @ingroup Media
*/
class Exif {
+
+ const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer.
+ const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
+ const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer.
+ const LONG = 4; //!< A 32-bit (4-byte) unsigned integer.
+ const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
+ const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition
+ const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
+ const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
+
//@{
/* @var array
* @private
*/
- /**#@+
- * Exif tag type definition
- */
- const BYTE = 1; # An 8-bit (1-byte) unsigned integer.
- const ASCII = 2; # An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
- const SHORT = 3; # A 16-bit (2-byte) unsigned integer.
- const LONG = 4; # A 32-bit (4-byte) unsigned integer.
- const RATIONAL = 5; # Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
- const UNDEFINED = 7; # An 8-bit byte that can take any value depending on the field definition
- const SLONG = 9; # A 32-bit (4-byte) signed integer (2's complement notation),
- const SRATIONAL = 10; # Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
-
/**
* Exif tags grouped by category, the tagname itself is the key and the type
* is the value, in the case of more than one possible value type they are
diff --git a/includes/Export.php b/includes/Export.php
index 5f040b13..909804cf 100644
--- a/includes/Export.php
+++ b/includes/Export.php
@@ -30,9 +30,10 @@ class WikiExporter {
var $dumpUploads = false;
- const FULL = 0;
- const CURRENT = 1;
- const LOGS = 2;
+ const FULL = 1;
+ const CURRENT = 2;
+ const STABLE = 4; // extension defined
+ const LOGS = 8;
const BUFFER = 0;
const STREAM = 1;
@@ -175,93 +176,103 @@ class WikiExporter {
}
protected function dumpFrom( $cond = '' ) {
- $fname = 'WikiExporter::dumpFrom';
- wfProfileIn( $fname );
-
- # For logs dumps...
+ wfProfileIn( __METHOD__ );
+ # For logging dumps...
if( $this->history & self::LOGS ) {
+ if( $this->buffer == WikiExporter::STREAM ) {
+ $prev = $this->db->bufferResults( false );
+ }
$where = array( 'user_id = log_user' );
# Hide private logs
- $where[] = LogEventsList::getExcludeClause( $this->db );
+ $hideLogs = LogEventsList::getExcludeClause( $this->db );
+ if( $hideLogs ) $where[] = $hideLogs;
+ # Add on any caller specified conditions
if( $cond ) $where[] = $cond;
+ # Get logging table name for logging.* clause
+ $logging = $this->db->tableName('logging');
$result = $this->db->select( array('logging','user'),
- '*',
+ array( "{$logging}.*", 'user_name' ), // grab the user name
$where,
- $fname,
+ __METHOD__,
array( 'ORDER BY' => 'log_id', 'USE INDEX' => array('logging' => 'PRIMARY') )
);
$wrapper = $this->db->resultObject( $result );
+ if( $this->buffer == WikiExporter::STREAM ) {
+ $this->db->bufferResults( $prev );
+ }
$this->outputLogStream( $wrapper );
# For page dumps...
} else {
- list($page,$revision,$text) = $this->db->tableNamesN('page','revision','text');
-
- $order = 'ORDER BY page_id';
- $limit = '';
-
- if( $this->history == WikiExporter::FULL ) {
- $join = 'page_id=rev_page';
- } elseif( $this->history == WikiExporter::CURRENT ) {
- if ( $this->list_authors && $cond != '' ) { // List authors, if so desired
- $this->do_list_authors ( $page , $revision , $cond );
+ $tables = array( 'page', 'revision' );
+ $opts = array( 'ORDER BY' => 'page_id ASC' );
+ $opts['USE INDEX'] = array();
+ $join = array();
+ # Full history dumps...
+ if( $this->history & WikiExporter::FULL ) {
+ $join['revision'] = array('INNER JOIN','page_id=rev_page');
+ # Latest revision dumps...
+ } elseif( $this->history & WikiExporter::CURRENT ) {
+ if( $this->list_authors && $cond != '' ) { // List authors, if so desired
+ list($page,$revision) = $this->db->tableNamesN('page','revision');
+ $this->do_list_authors( $page, $revision, $cond );
+ }
+ $join['revision'] = array('INNER JOIN','page_id=rev_page AND page_latest=rev_id');
+ # "Stable" revision dumps...
+ } elseif( $this->history & WikiExporter::STABLE ) {
+ # Default JOIN, to be overridden...
+ $join['revision'] = array('INNER JOIN','page_id=rev_page AND page_latest=rev_id');
+ # One, and only one hook should set this, and return false
+ if( wfRunHooks( 'WikiExporter::dumpStableQuery', array(&$tables,&$opts,&$join) ) ) {
+ wfProfileOut( __METHOD__ );
+ return new WikiError( __METHOD__." given invalid history dump type." );
}
- $join = 'page_id=rev_page AND page_latest=rev_id';
- } elseif ( is_array( $this->history ) ) {
- $join = 'page_id=rev_page';
- if ( $this->history['dir'] == 'asc' ) {
+ # Time offset/limit for all pages/history...
+ } elseif( is_array( $this->history ) ) {
+ $revJoin = 'page_id=rev_page';
+ # Set time order
+ if( $this->history['dir'] == 'asc' ) {
$op = '>';
- $order .= ', rev_timestamp';
+ $opts['ORDER BY'] = 'rev_timestamp ASC';
} else {
$op = '<';
- $order .= ', rev_timestamp DESC';
+ $opts['ORDER BY'] = 'rev_timestamp DESC';
}
- if ( !empty( $this->history['offset'] ) ) {
- $join .= " AND rev_timestamp $op " . $this->db->addQuotes(
- $this->db->timestamp( $this->history['offset'] ) );
+ # Set offset
+ if( !empty( $this->history['offset'] ) ) {
+ $revJoin .= " AND rev_timestamp $op " .
+ $this->db->addQuotes( $this->db->timestamp( $this->history['offset'] ) );
}
- if ( !empty( $this->history['limit'] ) ) {
- $limitNum = intval( $this->history['limit'] );
- if ( $limitNum > 0 ) {
- $limit = "LIMIT $limitNum";
- }
+ $join['revision'] = array('INNER JOIN',$revJoin);
+ # Set query limit
+ if( !empty( $this->history['limit'] ) ) {
+ $opts['LIMIT'] = intval( $this->history['limit'] );
}
+ # Uknown history specification parameter?
} else {
- wfProfileOut( $fname );
- return new WikiError( "$fname given invalid history dump type." );
+ wfProfileOut( __METHOD__ );
+ return new WikiError( __METHOD__." given invalid history dump type." );
+ }
+ # Query optimization hacks
+ if( $cond == '' ) {
+ $opts[] = 'STRAIGHT_JOIN';
+ $opts['USE INDEX']['page'] = 'PRIMARY';
+ }
+ # Build text join options
+ if( $this->text != WikiExporter::STUB ) { // 1-pass
+ $tables[] = 'text';
+ $join['text'] = array('INNER JOIN','rev_text_id=old_id');
}
- $where = ( $cond == '' ) ? '' : "$cond AND";
if( $this->buffer == WikiExporter::STREAM ) {
$prev = $this->db->bufferResults( false );
}
- if( $cond == '' ) {
- // Optimization hack for full-database dump
- $revindex = $pageindex = $this->db->useIndexClause("PRIMARY");
- $straight = ' /*! STRAIGHT_JOIN */ ';
- } else {
- $pageindex = '';
- $revindex = '';
- $straight = '';
- }
- if( $this->text == WikiExporter::STUB ) {
- $sql = "SELECT $straight * FROM
- $page $pageindex,
- $revision $revindex
- WHERE $where $join
- $order $limit";
- } else {
- $sql = "SELECT $straight * FROM
- $page $pageindex,
- $revision $revindex,
- $text
- WHERE $where $join AND rev_text_id=old_id
- $order $limit";
- }
- $result = $this->db->query( $sql, $fname );
+
+ # Do the query!
+ $result = $this->db->select( $tables, '*', $cond, __METHOD__, $opts, $join );
$wrapper = $this->db->resultObject( $result );
+ # Output dump results
$this->outputPageStream( $wrapper );
-
- if ( $this->list_authors ) {
+ if( $this->list_authors ) {
$this->outputPageStream( $wrapper );
}
@@ -269,7 +280,7 @@ class WikiExporter {
$this->db->bufferResults( $prev );
}
}
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
}
/**
@@ -399,7 +410,7 @@ class XmlDumpWriter {
function namespaces() {
global $wgContLang;
- $spaces = " <namespaces>\n";
+ $spaces = "<namespaces>\n";
foreach( $wgContLang->getFormattedNamespaces() as $ns => $title ) {
$spaces .= ' ' . Xml::element( 'namespace', array( 'key' => $ns ), $title ) . "\n";
}
diff --git a/includes/ExternalStore.php b/includes/ExternalStore.php
index d095aba0..1e750bb5 100644
--- a/includes/ExternalStore.php
+++ b/includes/ExternalStore.php
@@ -92,6 +92,8 @@ class ExternalStore {
$url = $store->store( $params, $data ); // Try to save the object
} catch ( DBConnectionError $error ) {
$url = false;
+ } catch( DBQueryError $error ) {
+ $url = false;
}
if ( $url ) {
return $url; // Done!
diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php
index 66086b0f..5177d35f 100644
--- a/includes/FileDeleteForm.php
+++ b/includes/FileDeleteForm.php
@@ -67,7 +67,7 @@ class FileDeleteForm {
$reason = $this->DeleteReasonList;
if ( $reason != 'other' && $this->DeleteReason != '') {
// Entry from drop down menu + additional comment
- $reason .= ': ' . $this->DeleteReason;
+ $reason .= wfMsgForContent( 'colon-separator' ) . $this->DeleteReason;
} elseif ( $reason == 'other' ) {
$reason = $this->DeleteReason;
}
@@ -108,7 +108,8 @@ class FileDeleteForm {
$id = $title->getArticleID( GAID_FOR_UPDATE );
// Need to delete the associated article
$article = new Article( $title );
- if( wfRunHooks('ArticleDelete', array(&$article, &$wgUser, &$reason)) ) {
+ $error = '';
+ if( wfRunHooks('ArticleDelete', array(&$article, &$wgUser, &$reason, &$error)) ) {
if( $article->doDeleteArticle( $reason, $suppress, $id ) ) {
global $wgRequest;
if( $wgRequest->getCheck( 'wpWatch' ) ) {
diff --git a/includes/ForkController.php b/includes/ForkController.php
new file mode 100644
index 00000000..09e1788b
--- /dev/null
+++ b/includes/ForkController.php
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * Class for managing forking command line scripts.
+ * Currently just does forking and process control, but it could easily be extended
+ * to provide IPC and job dispatch.
+ *
+ * This class requires the posix and pcntl extensions.
+ */
+class ForkController {
+ var $children = array();
+ var $termReceived = false;
+ var $flags = 0, $procsToStart = 0;
+
+ static $restartableSignals = array(
+ SIGFPE,
+ SIGILL,
+ SIGSEGV,
+ SIGBUS,
+ SIGABRT,
+ SIGSYS,
+ SIGPIPE,
+ SIGXCPU,
+ SIGXFSZ,
+ );
+
+ /**
+ * Pass this flag to __construct() to cause the class to automatically restart
+ * workers that exit with non-zero exit status or a signal such as SIGSEGV.
+ */
+ const RESTART_ON_ERROR = 1;
+
+ public function __construct( $numProcs, $flags = 0 ) {
+ if ( php_sapi_name() != 'cli' ) {
+ throw new MWException( "MultiProcess cannot be used from the web." );
+ }
+ $this->procsToStart = $numProcs;
+ $this->flags = $flags;
+ }
+
+ /**
+ * Start the child processes.
+ *
+ * This should only be called from the command line. It should be called
+ * as early as possible during execution.
+ *
+ * This will return 'child' in the child processes. In the parent process,
+ * it will run until all the child processes exit or a TERM signal is
+ * received. It will then return 'done'.
+ */
+ public function start() {
+ // Trap SIGTERM
+ pcntl_signal( SIGTERM, array( $this, 'handleTermSignal' ), false );
+
+ do {
+ // Start child processes
+ if ( $this->procsToStart ) {
+ if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) {
+ return 'child';
+ }
+ $this->procsToStart = 0;
+ }
+
+ // Check child status
+ $status = false;
+ $deadPid = pcntl_wait( $status );
+
+ if ( $deadPid > 0 ) {
+ // Respond to child process termination
+ unset( $this->children[$deadPid] );
+ if ( $this->flags & self::RESTART_ON_ERROR ) {
+ if ( pcntl_wifsignaled( $status ) ) {
+ // Restart if the signal was abnormal termination
+ // Don't restart if it was deliberately killed
+ $signal = pcntl_wtermsig( $status );
+ if ( in_array( $signal, self::$restartableSignals ) ) {
+ echo "Worker exited with signal $signal, restarting\n";
+ $this->procsToStart++;
+ }
+ } elseif ( pcntl_wifexited( $status ) ) {
+ // Restart on non-zero exit status
+ $exitStatus = pcntl_wexitstatus( $status );
+ if ( $exitStatus > 0 ) {
+ echo "Worker exited with status $exitStatus, restarting\n";
+ $this->procsToStart++;
+ }
+ }
+ }
+ // Throttle restarts
+ if ( $this->procsToStart ) {
+ usleep( 500000 );
+ }
+ }
+
+ // Run signal handlers
+ if ( function_exists( 'pcntl_signal_dispatch' ) ) {
+ pcntl_signal_dispatch();
+ } else {
+ declare (ticks=1) { $status = $status; }
+ }
+ // Respond to TERM signal
+ if ( $this->termReceived ) {
+ foreach ( $this->children as $childPid => $unused ) {
+ posix_kill( $childPid, SIGTERM );
+ }
+ $this->termReceived = false;
+ }
+ } while ( count( $this->children ) );
+ pcntl_signal( SIGTERM, SIG_DFL );
+ return 'done';
+ }
+
+ protected function prepareEnvironment() {
+ global $wgCaches, $wgMemc;
+ // Don't share DB or memcached connections
+ wfGetLBFactory()->destroyInstance();
+ $wgCaches = array();
+ unset( $wgMemc );
+ }
+
+ /**
+ * Fork a number of worker processes.
+ */
+ protected function forkWorkers( $numProcs ) {
+ global $wgMemc, $wgCaches, $wgMainCacheType;
+
+ $this->prepareEnvironment();
+
+ // Create the child processes
+ for ( $i = 0; $i < $numProcs; $i++ ) {
+ // Do the fork
+ $pid = pcntl_fork();
+ if ( $pid === -1 || $pid === false ) {
+ echo "Error creating child processes\n";
+ exit( 1 );
+ }
+
+ if ( !$pid ) {
+ $this->initChild();
+ return 'child';
+ } else {
+ // This is the parent process
+ $this->children[$pid] = true;
+ }
+ }
+
+ return 'parent';
+ }
+
+ protected function initChild() {
+ global $wgMemc, $wgMainCacheType;
+ $wgMemc = wfGetCache( $wgMainCacheType );
+ $this->children = null;
+ pcntl_signal( SIGTERM, SIG_DFL );
+ }
+
+ protected function handleTermSignal( $signal ) {
+ $this->termReceived = true;
+ }
+}
diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php
index 33f5831d..0807f0be 100644
--- a/includes/GlobalFunctions.php
+++ b/includes/GlobalFunctions.php
@@ -89,6 +89,19 @@ if ( !function_exists( 'array_diff_key' ) ) {
}
}
+// Support for Wietse Venema's taint feature
+if ( !function_exists( 'istainted' ) ) {
+ function istainted( $var ) {
+ return 0;
+ }
+ function taint( $var, $level = 0 ) {}
+ function untaint( $var, $level = 0 ) {}
+ define( 'TC_HTML', 1 );
+ define( 'TC_SHELL', 1 );
+ define( 'TC_MYSQL', 1 );
+ define( 'TC_PCRE', 1 );
+ define( 'TC_SELF', 1 );
+}
/// @endcond
@@ -337,12 +350,14 @@ function wfErrorLog( $text, $file ) {
*/
function wfLogProfilingData() {
global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
- global $wgProfiler, $wgUser;
- if ( !isset( $wgProfiler ) )
- return;
-
+ global $wgProfiler, $wgProfileLimit, $wgUser;
+ # Profiling must actually be enabled...
+ if( !isset( $wgProfiler ) ) return;
+ # Get total page request time
$now = wfTime();
$elapsed = $now - $wgRequestTime;
+ # Only show pages that longer than $wgProfileLimit time (default is 0)
+ if( $elapsed <= $wgProfileLimit ) return;
$prof = wfGetProfilingOutput( $wgRequestTime, $elapsed );
$forward = '';
if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
@@ -431,7 +446,7 @@ function wfGetLangObj( $langcode = false ){
return Language::factory( $langcode );
# $langcode is a string, but not a valid language code; use content language.
- wfDebug( 'Invalid language code passed to wfGetLangObj, falling back to content language.' );
+ wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" );
return $wgContLang;
}
@@ -771,7 +786,7 @@ function wfAbruptExit( $error = false ){
wfDebug("WARNING: Abrupt exit in $file at line $line\n");
}
} else {
- wfDebug('WARNING: Abrupt exit\n');
+ wfDebug("WARNING: Abrupt exit\n");
}
wfLogProfilingData();
@@ -860,18 +875,35 @@ function wfHostname() {
* murky circumstances, which may be triggered in part by stub objects
* or other fancy talkin'.
*
- * Will return an empty array if Zend Optimizer is detected, otherwise
- * the output from debug_backtrace() (trimmed).
+ * Will return an empty array if Zend Optimizer is detected or if
+ * debug_backtrace is disabled, otherwise the output from
+ * debug_backtrace() (trimmed).
*
* @return array of backtrace information
*/
function wfDebugBacktrace() {
+ static $disabled = null;
+
if( extension_loaded( 'Zend Optimizer' ) ) {
wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" );
return array();
- } else {
- return array_slice( debug_backtrace(), 1 );
}
+
+ if ( is_null( $disabled ) ) {
+ $disabled = false;
+ $functions = explode( ',', ini_get( 'disable_functions' ) );
+ $functions = array_map( 'trim', $functions );
+ $functions = array_map( 'strtolower', $functions );
+ if ( in_array( 'debug_backtrace', $functions ) ) {
+ wfDebug( "debug_backtrace is in disabled_functions\n" );
+ $disabled = true;
+ }
+ }
+ if ( $disabled ) {
+ return array();
+ }
+
+ return array_slice( debug_backtrace(), 1 );
}
function wfBacktrace() {
@@ -927,7 +959,8 @@ function wfBacktrace() {
*/
function wfShowingResults( $offset, $limit ) {
global $wgLang;
- return wfMsgExt( 'showingresults', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ) );
+ return wfMsgExt( 'showingresults', array( 'parseinline' ), $wgLang->formatNum( $limit ),
+ $wgLang->formatNum( $offset+1 ) );
}
/**
@@ -935,18 +968,28 @@ function wfShowingResults( $offset, $limit ) {
*/
function wfShowingResultsNum( $offset, $limit, $num ) {
global $wgLang;
- return wfMsgExt( 'showingresultsnum', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) );
+ return wfMsgExt( 'showingresultsnum', array( 'parseinline' ), $wgLang->formatNum( $limit ),
+ $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) );
}
/**
- * @todo document
+ * Generate (prev x| next x) (20|50|100...) type links for paging
+ * @param $offset string
+ * @param $limit int
+ * @param $link string
+ * @param $query string, optional URL query parameter string
+ * @param $atend bool, optional param for specified if this is the last page
*/
function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
global $wgLang;
$fmtLimit = $wgLang->formatNum( $limit );
- $prev = wfMsg( 'prevn', $fmtLimit );
- $next = wfMsg( 'nextn', $fmtLimit );
-
+ # Get prev/next link display text
+ $prev = wfMsgHtml( 'prevn', $fmtLimit );
+ $next = wfMsgHtml( 'nextn', $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
if( is_object( $link ) ) {
$title =& $link;
} else {
@@ -955,44 +998,58 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) {
return false;
}
}
-
- if ( 0 != $offset ) {
+ # Make 'previous' link
+ if( 0 != $offset ) {
$po = $offset - $limit;
- if ( $po < 0 ) { $po = 0; }
+ $po = max($po,0);
$q = "limit={$limit}&offset={$po}";
- if ( '' != $query ) { $q .= '&'.$query; }
- $plink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-prevlink\">{$prev}</a>";
- } else { $plink = $prev; }
-
+ 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 ) {
+ $q = "limit={$limit}&offset={$no}";
+ if( $query != '' ) {
+ $q .= '&'.$query;
+ }
+ if( $atend ) {
$nlink = $next;
} else {
- $nlink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-nextlink\">{$next}</a>";
- }
- $nums = 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 );
-
+ $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 wfMsg( 'viewprevnext', $plink, $nlink, $nums );
}
/**
- * @todo document
+ * Generate links for (20|50|100...) items-per-page links
+ * @param $offset string
+ * @param $limit int
+ * @param $title Title
+ * @param $query string, optional URL query parameter string
*/
-function wfNumLink( $offset, $limit, &$title, $query = '' ) {
+function wfNumLink( $offset, $limit, $title, $query = '' ) {
global $wgLang;
- if ( '' == $query ) { $q = ''; }
- else { $q = $query.'&'; }
- $q .= 'limit='.$limit.'&offset='.$offset;
-
+ if( $query == '' ) {
+ $q = '';
+ } else {
+ $q = $query.'&';
+ }
+ $q .= "limit={$limit}&offset={$offset}";
$fmtLimit = $wgLang->formatNum( $limit );
- $s = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-numlink\">{$fmtLimit}</a>";
+ $lTitle = wfMsgExt('shown-title',array('parsemag','escape'),$limit);
+ $s = '<a href="' . $title->escapeLocalUrl( $q ) . "\" title=\"{$lTitle}\" class=\"mw-numlink\">{$fmtLimit}</a>";
return $s;
}
@@ -1693,6 +1750,11 @@ define('TS_ORACLE', 6);
define('TS_POSTGRES', 7);
/**
+ * DB2 format time
+ */
+define('TS_DB2', 8);
+
+/**
* @param mixed $outputtype A timestamp in one of the supported formats, the
* function will autodetect which format is supplied
* and act accordingly.
@@ -1753,6 +1815,8 @@ function wfTimestamp($outputtype=TS_UNIX,$ts=0) {
return gmdate( 'd-M-y h.i.s A', $uts) . ' +00:00';
case TS_POSTGRES:
return gmdate( 'Y-m-d H:i:s', $uts) . ' GMT';
+ case TS_DB2:
+ return gmdate( 'Y-m-d H:i:s', $uts);
default:
throw new MWException( 'wfTimestamp() called with illegal output type.');
}
@@ -1837,7 +1901,7 @@ function wfGetCachedNotice( $name ) {
$parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
$notice = $parsed;
} else {
- wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' );
+ wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available'."\n" );
$notice = '';
}
}
@@ -1929,11 +1993,16 @@ function wfTempDir() {
*
* @param string $dir Full path to directory to create
* @param int $mode Chmod value to use, default is $wgDirectoryMode
+ * @param string $caller Optional caller param for debugging.
* @return bool
*/
-function wfMkdirParents( $dir, $mode = null ) {
+function wfMkdirParents( $dir, $mode = null, $caller = null ) {
global $wgDirectoryMode;
+ if ( !is_null( $caller ) ) {
+ wfDebug( "$caller: called wfMkdirParents($dir)" );
+ }
+
if( strval( $dir ) === '' || file_exists( $dir ) )
return true;
@@ -2101,11 +2170,26 @@ function wfIniGetBool( $setting ) {
function wfShellExec( $cmd, &$retval=null ) {
global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime;
- if( wfIniGetBool( 'safe_mode' ) ) {
- wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
+ static $disabled;
+ if ( is_null( $disabled ) ) {
+ $disabled = false;
+ if( wfIniGetBool( 'safe_mode' ) ) {
+ wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" );
+ $disabled = true;
+ }
+ $functions = explode( ',', ini_get( 'disable_functions' ) );
+ $functions = array_map( 'trim', $functions );
+ $functions = array_map( 'strtolower', $functions );
+ if ( in_array( 'passthru', $functions ) ) {
+ wfDebug( "passthru is in disabled_functions\n" );
+ $disabled = true;
+ }
+ }
+ if ( $disabled ) {
$retval = 1;
return "Unable to run external programs in safe mode.";
}
+
wfInitShellLocale();
if ( php_uname( 's' ) == 'Linux' ) {
@@ -2317,9 +2401,16 @@ function wfMergeErrorArrays(/*...*/) {
}
/**
- * Make a URL index, appropriate for the el_index field of externallinks.
+ * parse_url() work-alike, but non-broken. Differences:
+ *
+ * 1) Does not raise warnings on bad URLs (just returns false)
+ * 2) Handles protocols that don't use :// (e.g., mailto: and news:) correctly
+ * 3) Adds a "delimiter" element to the array, either '://' or ':' (see (2))
+ *
+ * @param string $url A URL to parse
+ * @return array Bits of the URL in an associative array, per PHP docs
*/
-function wfMakeUrlIndex( $url ) {
+function wfParseUrl( $url ) {
global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
wfSuppressWarnings();
$bits = parse_url( $url );
@@ -2327,12 +2418,12 @@ function wfMakeUrlIndex( $url ) {
if ( !$bits ) {
return false;
}
+
// most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it
- $delimiter = '';
- if ( in_array( $bits['scheme'] . '://' , $wgUrlProtocols ) ) {
- $delimiter = '://';
- } elseif ( in_array( $bits['scheme'] .':' , $wgUrlProtocols ) ) {
- $delimiter = ':';
+ if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) {
+ $bits['delimiter'] = '://';
+ } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) {
+ $bits['delimiter'] = ':';
// parse_url detects for news: and mailto: the host part of an url as path
// We have to correct this wrong detection
if ( isset ( $bits['path'] ) ) {
@@ -2343,6 +2434,15 @@ function wfMakeUrlIndex( $url ) {
return false;
}
+ return $bits;
+}
+
+/**
+ * Make a URL index, appropriate for the el_index field of externallinks.
+ */
+function wfMakeUrlIndex( $url ) {
+ $bits = wfParseUrl( $url );
+
// Reverse the labels in the hostname, convert to lower case
// For emails reverse domainpart only
if ( $bits['scheme'] == 'mailto' ) {
@@ -2364,7 +2464,7 @@ function wfMakeUrlIndex( $url ) {
}
// Reconstruct the pseudo-URL
$prot = $bits['scheme'];
- $index = "$prot$delimiter$reversedHost";
+ $index = $prot . $bits['delimiter'] . $reversedHost;
// Leave out user and password. Add the port, path, query and fragment
if ( isset( $bits['port'] ) ) $index .= ':' . $bits['port'];
if ( isset( $bits['path'] ) ) {
diff --git a/includes/HTMLCacheUpdate.php b/includes/HTMLCacheUpdate.php
index 402102ea..bd63c072 100644
--- a/includes/HTMLCacheUpdate.php
+++ b/includes/HTMLCacheUpdate.php
@@ -35,146 +35,76 @@ class HTMLCacheUpdate
$this->mTable = $table;
$this->mRowsPerJob = $wgUpdateRowsPerJob;
$this->mRowsPerQuery = $wgUpdateRowsPerQuery;
+ $this->mCache = $this->mTitle->getBacklinkCache();
}
public function doUpdate() {
# Fetch the IDs
- $cond = $this->getToCondition();
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( $this->mTable, $this->getFromField(), $cond, __METHOD__ );
+ $numRows = $this->mCache->getNumLinks( $this->mTable );
- if ( $dbr->numRows( $res ) != 0 ) {
- if ( $dbr->numRows( $res ) > $this->mRowsPerJob ) {
- $this->insertJobs( $res );
+ if ( $numRows != 0 ) {
+ if ( $numRows > $this->mRowsPerJob ) {
+ $this->insertJobs();
} else {
- $this->invalidateIDs( $res );
+ $this->invalidate();
}
}
wfRunHooks( 'HTMLCacheUpdate::doUpdate', array($this->mTitle) );
}
- protected function insertJobs( ResultWrapper $res ) {
- $numRows = $res->numRows();
- $numBatches = ceil( $numRows / $this->mRowsPerJob );
- $realBatchSize = $numRows / $numBatches;
- $start = false;
- $jobs = array();
- do {
- for ( $i = 0; $i <= $realBatchSize - 1; $i++ ) {
- $row = $res->fetchRow();
- if ( $row ) {
- $id = $row[0];
- } else {
- $id = false;
- break;
- }
- }
-
+ protected function insertJobs() {
+ $batches = $this->mCache->partition( $this->mTable, $this->mRowsPerJob );
+ if ( !$batches ) {
+ return;
+ }
+ foreach ( $batches as $batch ) {
$params = array(
'table' => $this->mTable,
- 'start' => $start,
- 'end' => ( $id !== false ? $id - 1 : false ),
+ 'start' => $batch[0],
+ 'end' => $batch[1],
);
$jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
-
- $start = $id;
- } while ( $start );
-
- Job::batchInsert( $jobs );
- }
-
- protected function getPrefix() {
- static $prefixes = array(
- 'pagelinks' => 'pl',
- 'imagelinks' => 'il',
- 'categorylinks' => 'cl',
- 'templatelinks' => 'tl',
- 'redirect' => 'rd',
- );
-
- if ( is_null( $this->mPrefix ) ) {
- $this->mPrefix = $prefixes[$this->mTable];
- if ( is_null( $this->mPrefix ) ) {
- throw new MWException( "Invalid table type \"{$this->mTable}\" in " . __CLASS__ );
- }
}
- return $this->mPrefix;
- }
-
- public function getFromField() {
- return $this->getPrefix() . '_from';
+ Job::batchInsert( $jobs );
}
- public function getToCondition() {
- $prefix = $this->getPrefix();
- switch ( $this->mTable ) {
- case 'pagelinks':
- case 'templatelinks':
- case 'redirect':
- return array(
- "{$prefix}_namespace" => $this->mTitle->getNamespace(),
- "{$prefix}_title" => $this->mTitle->getDBkey()
- );
- case 'imagelinks':
- return array( 'il_to' => $this->mTitle->getDBkey() );
- case 'categorylinks':
- return array( 'cl_to' => $this->mTitle->getDBkey() );
- }
- throw new MWException( 'Invalid table type in ' . __CLASS__ );
- }
/**
- * Invalidate a set of IDs, right now
+ * Invalidate a set of pages, right now
*/
- public function invalidateIDs( ResultWrapper $res ) {
+ public function invalidate( $startId = false, $endId = false ) {
global $wgUseFileCache, $wgUseSquid;
- if ( $res->numRows() == 0 ) {
+ $titleArray = $this->mCache->getLinks( $this->mTable, $startId, $endId );
+ if ( $titleArray->count() == 0 ) {
return;
}
$dbw = wfGetDB( DB_MASTER );
$timestamp = $dbw->timestamp();
- $done = false;
-
- while ( !$done ) {
- # Get all IDs in this query into an array
- $ids = array();
- for ( $i = 0; $i < $this->mRowsPerQuery; $i++ ) {
- $row = $res->fetchRow();
- if ( $row ) {
- $ids[] = $row[0];
- } else {
- $done = true;
- break;
- }
- }
- if ( !count( $ids ) ) {
- break;
- }
+ # Get all IDs in this query into an array
+ $ids = array();
+ foreach ( $titleArray as $title ) {
+ $ids[] = $title->getArticleID();
+ }
+ # Update page_touched
+ $dbw->update( 'page',
+ array( 'page_touched' => $timestamp ),
+ array( 'page_id IN (' . $dbw->makeList( $ids ) . ')' ),
+ __METHOD__
+ );
- # Update page_touched
- $dbw->update( 'page',
- array( 'page_touched' => $timestamp ),
- array( 'page_id IN (' . $dbw->makeList( $ids ) . ')' ),
- __METHOD__
- );
+ # Update squid
+ if ( $wgUseSquid ) {
+ $u = SquidUpdate::newFromTitles( $titleArray );
+ $u->doUpdate();
+ }
- # Update squid
- if ( $wgUseSquid || $wgUseFileCache ) {
- $titles = Title::newFromIDs( $ids );
- if ( $wgUseSquid ) {
- $u = SquidUpdate::newFromTitles( $titles );
- $u->doUpdate();
- }
-
- # Update file cache
- if ( $wgUseFileCache ) {
- foreach ( $titles as $title ) {
- HTMLFileCache::clearFileCache( $title );
- }
- }
+ # Update file cache
+ if ( $wgUseFileCache ) {
+ foreach ( $titleArray as $title ) {
+ HTMLFileCache::clearFileCache( $title );
}
}
}
@@ -204,20 +134,7 @@ class HTMLCacheUpdateJob extends Job {
public function run() {
$update = new HTMLCacheUpdate( $this->title, $this->table );
-
- $fromField = $update->getFromField();
- $conds = $update->getToCondition();
- if ( $this->start ) {
- $conds[] = "$fromField >= {$this->start}";
- }
- if ( $this->end ) {
- $conds[] = "$fromField <= {$this->end}";
- }
-
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( $this->table, $fromField, $conds, __METHOD__ );
- $update->invalidateIDs( $res );
-
+ $update->invalidate( $this->start, $this->end );
return true;
}
}
diff --git a/includes/HTMLFileCache.php b/includes/HTMLFileCache.php
index e267962c..68cafa24 100644
--- a/includes/HTMLFileCache.php
+++ b/includes/HTMLFileCache.php
@@ -128,7 +128,6 @@ class HTMLFileCache {
public function loadFromFileCache() {
global $wgOut, $wgMimeType, $wgOutputEncoding, $wgContLanguageCode;
wfDebug(" loadFromFileCache()\n");
-
$filename = $this->fileCacheName();
// Raw pages should handle cache control on their own,
// even when using file cache. This reduces hits from clients.
@@ -148,6 +147,7 @@ class HTMLFileCache {
}
}
readfile( $filename );
+ $wgOut->disable(); // tell $wgOut that output is taken care of
}
protected function checkCacheDirs() {
@@ -159,13 +159,12 @@ class HTMLFileCache {
wfMkdirParents( $mydir2 );
}
- public function saveToFileCache( $origtext ) {
+ public function saveToFileCache( $text ) {
global $wgUseFileCache;
- if( !$wgUseFileCache ) {
- return $origtext; // return to output
+ if( !$wgUseFileCache || strlen( $text ) < 512 ) {
+ // Disabled or empty/broken output (OOM and PHP errors)
+ return $text;
}
- $text = $origtext;
- if( strcmp($text,'') == 0 ) return '';
wfDebug(" saveToFileCache()\n", false);
diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php
index f3f525c1..8a38bed7 100644
--- a/includes/ImageGallery.php
+++ b/includes/ImageGallery.php
@@ -289,7 +289,7 @@ class ImageGallery
}
$textlink = $this->mShowFilename ?
- $sk->makeKnownLinkObj( $nt, htmlspecialchars( $wgLang->truncate( $nt->getText(), 20, '...' ) ) ) . "<br />\n" :
+ $sk->makeKnownLinkObj( $nt, htmlspecialchars( $wgLang->truncate( $nt->getText(), 20 ) ) ) . "<br />\n" :
'' ;
# ATTENTION: The newline after <div class="gallerytext"> is needed to accommodate htmltidy which
diff --git a/includes/ImagePage.php b/includes/ImagePage.php
index 314d478e..4f3b859a 100644
--- a/includes/ImagePage.php
+++ b/includes/ImagePage.php
@@ -105,7 +105,6 @@ class ImagePage extends Article {
} else {
# Just need to set the right headers
$wgOut->setArticleFlag( true );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
$this->viewUpdates();
}
@@ -117,8 +116,6 @@ class ImagePage extends Article {
$wgOut->addWikiText( $fol );
}
$wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . '</div>' );
- } else {
- $this->checkSharedConflict();
}
$this->closeShowImage();
@@ -129,11 +126,9 @@ class ImagePage extends Article {
array( 'id' => 'filelinks' ),
wfMsg( 'imagelinks' ) ) . "\n" );
$this->imageDupes();
- // TODO: We may want to find local images redirecting to a foreign
- // file: "The following local files redirect to this file"
- if( $this->img->isLocal() ) {
- $this->imageRedirects();
- }
+ # TODO! FIXME! For some freaky reason, we can't redirect to foreign images.
+ # Yet we return metadata about the target. Definitely an issue in the FileRepo
+ $this->imageRedirects();
$this->imageLinks();
if( $showmeta ) {
@@ -473,6 +468,7 @@ EOT
$title = SpecialPage::getTitleFor( 'Upload' );
$link = $sk->makeKnownLinkObj($title, wfMsgHtml('noimage-linktext'),
'wpDestFile=' . urlencode( $this->displayImg->getName() ) );
+ $wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->addHTML( wfMsgWikiHtml( 'noimage', $link ) );
}
}
@@ -487,17 +483,18 @@ EOT
$descUrl = $this->img->getDescriptionUrl();
$descText = $this->img->getDescriptionText();
- $s = "<div class='sharedUploadNotice'>" . wfMsgWikiHtml( 'sharedupload' );
+ $msg = '';
if( $descUrl ) {
$sk = $wgUser->getSkin();
$link = $sk->makeExternalLink( $descUrl, wfMsg( 'shareduploadwiki-linktext' ) );
$msg = ( $descText ) ? 'shareduploadwiki-desc' : 'shareduploadwiki';
$msg = wfMsgExt( $msg, array( 'parseinline', 'replaceafter' ), $link );
- if( $msg != '-' ) {
- # Show message only if not voided by local sysops
- $s .= $msg;
+ if( $msg == '-' ) {
+ $msg = '';
}
}
+ $s = "<div class='sharedUploadNotice'>";
+ $s .= wfMsgWikiHtml( 'sharedupload', $this->img->getRepo()->getDisplayName(), $msg );
$s .= "</div>";
$wgOut->addHTML( $s );
@@ -506,58 +503,10 @@ EOT
}
}
- /*
- * Check for files with the same name on the foreign repos.
- */
- protected function checkSharedConflict() {
- global $wgOut, $wgUser;
-
- $repoGroup = RepoGroup::singleton();
- if( !$repoGroup->hasForeignRepos() ) {
- return;
- }
-
- $this->loadFile();
- if( !$this->img->isLocal() ) {
- return;
- }
-
- $this->dupFile = null;
- $repoGroup->forEachForeignRepo( array( $this, 'checkSharedConflictCallback' ) );
-
- if( !$this->dupFile )
- return;
- $dupfile = $this->dupFile;
- $same = (
- ($this->img->getSha1() == $dupfile->getSha1()) &&
- ($this->img->getSize() == $dupfile->getSize())
- );
-
- $sk = $wgUser->getSkin();
- $descUrl = $dupfile->getDescriptionUrl();
- if( $same ) {
- $link = $sk->makeExternalLink( $descUrl, wfMsg( 'shareduploadduplicate-linktext' ) );
- $wgOut->addHTML( '<div id="shared-image-dup">' . wfMsgWikiHtml( 'shareduploadduplicate', $link ) . '</div>' );
- } else {
- $link = $sk->makeExternalLink( $descUrl, wfMsg( 'shareduploadconflict-linktext' ) );
- $wgOut->addHTML( '<div id="shared-image-conflict">' . wfMsgWikiHtml( 'shareduploadconflict', $link ) . '</div>' );
- }
- }
-
- public function checkSharedConflictCallback( $repo ) {
- $this->loadFile();
- $dupfile = $repo->newFile( $this->img->getTitle() );
- if( $dupfile && $dupfile->exists() ) {
- $this->dupFile = $dupfile;
- return $dupfile->exists();
- }
- return false;
- }
-
public function getUploadUrl() {
$this->loadFile();
$uploadTitle = SpecialPage::getTitleFor( 'Upload' );
- return $uploadTitle->getFullUrl( 'wpDestFile=' . urlencode( $this->img->getName() ) );
+ return $uploadTitle->getFullUrl( 'wpDestFile=' . urlencode( $this->img->getName() ) . '&wpForReUpload=1' );
}
/**
@@ -581,10 +530,6 @@ EOT
$wgOut->addHTML( "<li><div class='plainlinks'>{$ulink}</div></li>" );
}
- # Link to Special:FileDuplicateSearch
- $dupeLink = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'FileDuplicateSearch', $this->mTitle->getDBkey() ), wfMsgHtml( 'imagepage-searchdupe' ) );
- $wgOut->addHTML( "<li>{$dupeLink}</li>" );
-
# External editing link
$elink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'edit-externally' ), 'action=edit&externaledit=true&mode=file' );
$wgOut->addHTML( '<li>' . $elink . ' <small>' . wfMsgExt( 'edit-externally-help', array( 'parseinline' ) ) . '</small></li>' );
@@ -698,19 +643,21 @@ EOT
$wgOut->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
$wgOut->addWikiMsg( 'duplicatesoffile',
- $wgLang->formatNum( count( $dupes ) )
+ $wgLang->formatNum( count( $dupes ) ), $this->mTitle->getDBkey()
);
$wgOut->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
$sk = $wgUser->getSkin();
foreach ( $dupes as $file ) {
+ $fromSrc = '';
if( $file->isLocal() )
$link = $sk->makeKnownLinkObj( $file->getTitle(), "" );
else {
$link = $sk->makeExternalLink( $file->getDescriptionUrl(),
$file->getTitle()->getPrefixedText() );
+ $fromSrc = wfMsg( 'shared-repo-from', $file->getRepo()->getDisplayName() );
}
- $wgOut->addHTML( "<li>{$link}</li>\n" );
+ $wgOut->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
}
$wgOut->addHTML( "</ul></div>\n" );
}
@@ -922,7 +869,8 @@ class ImageHistoryList {
'alt' => wfMsg( 'filehist-thumbtext', $wgLang->timeAndDate( $timestamp, true ) ),
'file-link' => true,
);
- $row .= '</td><td>' . $thumbnail->toHtml( $options );
+ $row .= '</td><td>' . ( $thumbnail ? $thumbnail->toHtml( $options ) :
+ wfMsgHtml( 'filehist-nothumb' ) );
} else {
$row .= '</td><td>' . wfMsgHtml( 'filehist-nothumb' );
}
diff --git a/includes/Import.php b/includes/Import.php
index 56e7a7fb..973866df 100644
--- a/includes/Import.php
+++ b/includes/Import.php
@@ -223,7 +223,7 @@ class WikiRevision {
} elseif( $changed ) {
wfDebug( __METHOD__ . ": running onArticleEdit\n" );
- Article::onArticleEdit( $this->title, 'skiptransclusions' ); // leave templatelinks for editUpdates()
+ Article::onArticleEdit( $this->title );
wfDebug( __METHOD__ . ": running edit updates\n" );
$article->editUpdates(
@@ -1116,7 +1116,7 @@ class ImportStreamSource {
}
}
- public static function newFromInterwiki( $interwiki, $page, $history=false ) {
+ public static function newFromInterwiki( $interwiki, $page, $history = false, $templates = false, $pageLinkDepth = 0 ) {
if( $page == '' ) {
return new WikiErrorMsg( 'import-noarticle' );
}
@@ -1124,7 +1124,10 @@ class ImportStreamSource {
if( is_null( $link ) || $link->getInterwiki() == '' ) {
return new WikiErrorMsg( 'importbadinterwiki' );
} else {
- $params = $history ? 'history=1' : '';
+ $params = array();
+ if ( $history ) $params['history'] = 1;
+ if ( $templates ) $params['templates'] = 1;
+ if ( $pageLinkDepth ) $params['pagelink-depth'] = $pageLinkDepth;
$url = $link->getFullUrl( $params );
# For interwikis, use POST to avoid redirects.
return ImportStreamSource::newFromURL( $url, "POST" );
diff --git a/includes/Linker.php b/includes/Linker.php
index f116fb4a..b739244b 100644
--- a/includes/Linker.php
+++ b/includes/Linker.php
@@ -618,7 +618,7 @@ class Linker {
$img = '';
$success = wfRunHooks('LinkerMakeExternalImage', array( &$url, &$alt, &$img ) );
if(!$success) {
- wfDebug("Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}", true);
+ wfDebug("Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true);
return $img;
}
return Xml::element( 'img',
@@ -882,10 +882,13 @@ class Linker {
}
}
- if( $page ) {
- $query = $query ? '&page=' . urlencode( $page ) : 'page=' . urlencode( $page );
- }
+ # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs
+ # So we don't need to pass it here in $query. However, the URL for the
+ # zoom icon still needs it, so we make a unique query for it. See bug 14771
$url = $title->getLocalURL( $query );
+ if( $page ) {
+ $url = wfAppendQuery( $url, 'page=' . urlencode( $page ) );
+ }
$more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
@@ -1007,22 +1010,37 @@ class Linker {
wfMsg( $key ) );
}
- /** @todo document */
+ /**
+ * Make an external link
+ * @param String $url URL to link to
+ * @param String $text text of link
+ * @param boolean $escape Do we escape the link text?
+ * @param String $linktype Type of external link. Gets added to the classes
+ * @param array $attribs Array of extra attributes to <a>
+ *
+ * @TODO! @FIXME! This is a really crappy implementation. $linktype and
+ * 'external' are mashed into the class attrib for the link (which is made
+ * into a string). Then, if we've got additional params in $attribs, we
+ * add to it. People using this might want to change the classes (or other
+ * default link attributes), but passing $attribsText is just messy. Would
+ * make a lot more sense to make put the classes into $attribs, let the
+ * hook play with them, *then* expand it all at once.
+ */
function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) {
$attribsText = $this->getExternalLinkAttributes( $url, $text, 'external ' . $linktype );
- if ( $attribs ) {
- $attribsText .= Xml::expandAttributes( $attribs );
- }
$url = htmlspecialchars( $url );
if( $escape ) {
$text = htmlspecialchars( $text );
}
$link = '';
- $success = wfRunHooks('LinkerMakeExternalLink', array( &$url, &$text, &$link ) );
+ $success = wfRunHooks('LinkerMakeExternalLink', array( &$url, &$text, &$link, &$attribs, $linktype ) );
if(!$success) {
- wfDebug("Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}", true);
+ wfDebug("Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true);
return $link;
}
+ if ( $attribs ) {
+ $attribsText .= Xml::expandAttributes( $attribs );
+ }
return '<a href="'.$url.'"'.$attribsText.'>'.$text.'</a>';
}
@@ -1053,7 +1071,7 @@ class Linker {
* @return string
*/
public function userToolLinks( $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits=null ) {
- global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans;
+ global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans, $wgLang;
$talkable = !( $wgDisableAnonTalk && 0 == $userId );
$blockable = ( $wgSysopUserBans || 0 == $userId ) && !$flags & self::TOOL_LINKS_NOBLOCK;
@@ -1079,7 +1097,7 @@ class Linker {
}
if( $items ) {
- return ' <span class="mw-usertoollinks">(' . implode( ' | ', $items ) . ')</span>';
+ return ' <span class="mw-usertoollinks">(' . $wgLang->pipeList( $items ) . ')</span>';
} else {
return '';
}
@@ -1783,9 +1801,7 @@ class Linker {
# FIXME: Per standard MW behavior, a value of '-' means to suppress the
# attribute, but this is broken for accesskey: that might be a useful
# value.
- if( $accesskey != ''
- && $accesskey != '-'
- && !wfEmptyMsg( "accesskey-$name", $accesskey ) ) {
+ if( $accesskey != '' && $accesskey != '-' && !wfEmptyMsg( "accesskey-$name", $accesskey ) ) {
wfProfileOut( __METHOD__ );
return $accesskey;
}
@@ -1793,4 +1809,21 @@ class Linker {
wfProfileOut( __METHOD__ );
return false;
}
+
+ /**
+ * Creates a (show/hide) link for deleting revisions/log entries
+ *
+ * @param array $query Query parameters to be passed to link()
+ * @param bool $restricted Set to true to use a <strong> instead of a <span>
+ *
+ * @return string HTML <a> link to Special:Revisiondelete, wrapped in a
+ * span to allow for customization of appearance with CSS
+ */
+ public function revDeleteLink( $query = array(), $restricted = false ) {
+ $sp = SpecialPage::getTitleFor( 'Revisiondelete' );
+ $text = wfMsgHtml( 'rev-delundel' );
+ $tag = $restricted ? 'strong' : 'span';
+ $link = $this->link( $sp, $text, array(), $query, array( 'known', 'noclasses' ) );
+ return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), "($link)" );
+ }
}
diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php
index 13f35b5a..caacb49c 100644
--- a/includes/LinksUpdate.php
+++ b/includes/LinksUpdate.php
@@ -20,8 +20,7 @@ class LinksUpdate {
$mProperties, //!< Map of arbitrary name to value
$mDb, //!< Database connection reference
$mOptions, //!< SELECT options to be used (array)
- $mRecursive, //!< Whether to queue jobs for recursive updates
- $mTouchTmplLinks; //!< Whether to queue HTMLCacheUpdate jobs IF recursive
+ $mRecursive; //!< Whether to queue jobs for recursive updates
/**@}}*/
/**
@@ -72,15 +71,6 @@ class LinksUpdate {
wfRunHooks( 'LinksUpdateConstructed', array( &$this ) );
}
-
- /**
- * Invalidate HTML cache of pages that include this page?
- */
- public function setRecursiveTouch( $val ) {
- $this->mTouchTmplLinks = (bool)$val;
- if( $val ) // Cannot invalidate without queueRecursiveJobs()
- $this->mRecursive = true;
- }
/**
* Update link tables with outgoing links from an updated article
@@ -95,7 +85,6 @@ class LinksUpdate {
$this->doIncrementalUpdate();
}
wfRunHooks( 'LinksUpdateComplete', array( &$this ) );
-
}
protected function doIncrementalUpdate() {
@@ -207,49 +196,21 @@ class LinksUpdate {
global $wgUpdateRowsPerJob;
wfProfileIn( __METHOD__ );
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'templatelinks',
- array( 'tl_from' ),
- array(
- 'tl_namespace' => $this->mTitle->getNamespace(),
- 'tl_title' => $this->mTitle->getDBkey()
- ), __METHOD__
- );
-
- $numRows = $res->numRows();
- if( !$numRows ) {
+ $cache = $this->mTitle->getBacklinkCache();
+ $batches = $cache->partition( 'templatelinks', $wgUpdateRowsPerJob );
+ if ( !$batches ) {
wfProfileOut( __METHOD__ );
- return; // nothing to do
+ return;
}
- $numBatches = ceil( $numRows / $wgUpdateRowsPerJob );
- $realBatchSize = $numRows / $numBatches;
- $start = false;
$jobs = array();
- do {
- for( $i = 0; $i <= $realBatchSize - 1; $i++ ) {
- $row = $res->fetchRow();
- if( $row ) {
- $id = $row[0];
- } else {
- $id = false;
- break;
- }
- }
+ foreach ( $batches as $batch ) {
+ list( $start, $end ) = $batch;
$params = array(
'start' => $start,
- 'end' => ( $id !== false ? $id - 1 : false ),
+ 'end' => $end,
);
$jobs[] = new RefreshLinksJob2( $this->mTitle, $params );
- # Hit page caches while we're at it if set to do so...
- if( $this->mTouchTmplLinks ) {
- $params['table'] = 'templatelinks';
- $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params );
- }
- $start = $id;
- } while ( $start );
-
- $dbr->freeResult( $res );
-
+ }
Job::batchInsert( $jobs );
wfProfileOut( __METHOD__ );
@@ -465,9 +426,12 @@ class LinksUpdate {
* @private
*/
function getCategoryInsertions( $existing = array() ) {
+ global $wgContLang;
$diffs = array_diff_assoc( $this->mCategories, $existing );
$arr = array();
foreach ( $diffs as $name => $sortkey ) {
+ $nt = Title::makeTitleSafe( NS_CATEGORY, $name );
+ $wgContLang->findVariantLink( $name, $nt, true );
$arr[] = array(
'cl_from' => $this->mId,
'cl_to' => $name,
diff --git a/includes/LogEventsList.php b/includes/LogEventsList.php
index 528bd3aa..95109eb5 100644
--- a/includes/LogEventsList.php
+++ b/includes/LogEventsList.php
@@ -39,9 +39,10 @@ class LogEventsList {
// Precache various messages
if( !isset( $this->message ) ) {
$messages = array( 'revertmerge', 'protect_change', 'unblocklink', 'change-blocklink',
- 'revertmove', 'undeletelink', 'revdel-restore', 'rev-delundel', 'hist', 'pipe-separator' );
+ 'revertmove', 'undeletelink', 'revdel-restore', 'rev-delundel', 'hist', 'diff',
+ 'pipe-separator' );
foreach( $messages as $msg ) {
- $this->message[$msg] = wfMsgExt( $msg, array( 'escape' ) );
+ $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
}
}
}
@@ -65,16 +66,19 @@ class LogEventsList {
* @param $pattern String
* @param $year Integer: year
* @param $month Integer: month
- * @param $filter Boolean
+ * @param $filter: array
+ * @param $tagFilter: array?
*/
public function showOptions( $type = '', $user = '', $page = '', $pattern = '', $year = '',
- $month = '', $filter = null )
+ $month = '', $filter = null, $tagFilter='' )
{
global $wgScript, $wgMiserMode;
$action = htmlspecialchars( $wgScript );
$title = SpecialPage::getTitleFor( 'Log' );
$special = htmlspecialchars( $title->getPrefixedDBkey() );
+ $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
+
$this->out->addHTML( "<form action=\"$action\" method=\"get\"><fieldset>" .
Xml::element( 'legend', array(), wfMsg( 'log' ) ) .
Xml::hidden( 'title', $special ) . "\n" .
@@ -82,28 +86,31 @@ class LogEventsList {
$this->getUserInput( $user ) . "\n" .
$this->getTitleInput( $page ) . "\n" .
( !$wgMiserMode ? ($this->getTitlePattern( $pattern )."\n") : "" ) .
- "<p>" . $this->getDateMenu( $year, $month ) . "\n" .
- ( $filter ? "</p><p>".$this->getFilterLinks( $type, $filter )."\n" : "" ) .
+ "<p>" . Xml::dateMenu( $year, $month ) . "\n" .
+ ( $tagSelector ? Xml::tags( 'p', null, implode( '&nbsp;', $tagSelector ) ) :'' ). "\n" .
+ ( $filter ? "</p><p>".$this->getFilterLinks( $type, $filter )."\n" : "" ) . "\n" .
Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "</p>\n" .
"</fieldset></form>"
);
}
private function getFilterLinks( $logType, $filter ) {
- global $wgTitle;
+ global $wgTitle, $wgLang;
// show/hide links
$messages = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) );
// Option value -> message mapping
$links = array();
+ $hiddens = ''; // keep track for "go" button
foreach( $filter as $type => $val ) {
$hideVal = 1 - intval($val);
$link = $this->skin->makeKnownLinkObj( $wgTitle, $messages[$hideVal],
wfArrayToCGI( array( "hide_{$type}_log" => $hideVal ), $this->getDefaultQuery() )
);
$links[$type] = wfMsgHtml( "log-show-hide-{$type}", $link );
+ $hiddens .= Xml::hidden( "hide_{$type}_log", $val ) . "\n";
}
// Build links
- return implode( ' | ', $links );
+ return '<small>'.$wgLang->pipeList( $links ) . '</small>' . $hiddens;
}
private function getDefaultQuery() {
@@ -163,7 +170,7 @@ class LogEventsList {
* @return String: Formatted HTML
*/
private function getUserInput( $user ) {
- return Xml::inputLabel( wfMsg( 'specialloguserlabel' ), 'user', 'user', 15, $user );
+ return Xml::inputLabel( wfMsg( 'specialloguserlabel' ), 'user', 'mw-log-user', 15, $user );
}
/**
@@ -171,38 +178,7 @@ class LogEventsList {
* @return String: Formatted HTML
*/
private function getTitleInput( $title ) {
- return Xml::inputLabel( wfMsg( 'speciallogtitlelabel' ), 'page', 'page', 20, $title );
- }
-
- /**
- * @param $year Integer
- * @param $month Integer
- * @return string Formatted HTML
- */
- private function getDateMenu( $year, $month ) {
- # Offset overrides year/month selection
- if( $month && $month !== -1 ) {
- $encMonth = intval( $month );
- } else {
- $encMonth = '';
- }
- if ( $year ) {
- $encYear = intval( $year );
- } else if( $encMonth ) {
- $thisMonth = intval( gmdate( 'n' ) );
- $thisYear = intval( gmdate( 'Y' ) );
- if( intval($encMonth) > $thisMonth ) {
- $thisYear--;
- }
- $encYear = $thisYear;
- } else {
- $encYear = '';
- }
- return Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
- Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) .
- ' '.
- Xml::label( wfMsg( 'month' ), 'month' ) . ' '.
- Xml::monthSelector( $encMonth, -1 );
+ return Xml::inputLabel( wfMsg( 'speciallogtitlelabel' ), 'page', 'mw-log-page', 20, $title );
}
/**
@@ -230,6 +206,7 @@ class LogEventsList {
global $wgLang, $wgUser, $wgContLang;
$title = Title::makeTitle( $row->log_namespace, $row->log_title );
+ $classes = array( "mw-logline-{$row->log_type}" );
$time = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->log_timestamp), true );
// User links
if( self::isDeleted($row,LogPage::DELETED_USER) ) {
@@ -276,7 +253,7 @@ class LogEventsList {
array(),
array( 'action' => 'unblock', 'ip' => $row->log_title ),
'known' )
- . ' ' . $this->message['pipe-separator'] . ' ' .
+ . $this->message['pipe-separator'] .
$this->skin->link( SpecialPage::getTitleFor( 'Blockip', $row->log_title ),
$this->message['change-blocklink'],
array(), array(), 'known' ) .
@@ -289,7 +266,7 @@ class LogEventsList {
array(),
array( 'action' => 'history', 'offset' => $row->log_timestamp ) );
if( $wgUser->isAllowed( 'protect' ) ) {
- $revert .= ' ' . $this->message['pipe-separator'] . ' ' .
+ $revert .= $this->message['pipe-separator'] .
$this->skin->link( $title,
$this->message['protect_change'],
array(),
@@ -315,8 +292,17 @@ class LogEventsList {
foreach( $Ids as $n => $id ) {
$revParams .= '&' . urlencode($key) . '[]=' . urlencode($id);
}
- $revert = '(' . $this->skin->makeKnownLinkObj( $revdel, $this->message['revdel-restore'],
- 'target=' . $title->getPrefixedUrl() . $revParams ) . ')';
+ $revert = array();
+ // Diff link for single rev deletions
+ if( $key === 'oldid' && count($Ids) == 1 ) {
+ $token = urlencode( $wgUser->editToken( intval($Ids[0]) ) );
+ $revert[] = $this->skin->makeKnownLinkObj( $title, $this->message['diff'],
+ 'diff='.intval($Ids[0])."&unhide=1&token=$token" );
+ }
+ // View/modify link...
+ $revert[] = $this->skin->makeKnownLinkObj( $revdel, $this->message['revdel-restore'],
+ 'target=' . $title->getPrefixedUrl() . $revParams );
+ $revert = '(' . implode(' | ',$revert) . ')';
}
// Hidden log items, give review link
} else if( self::typeAction($row,array('delete','suppress'),'event','deleterevision') ) {
@@ -357,12 +343,16 @@ class LogEventsList {
$this->skin, $paramArray, true );
}
+ // Any tags...
+ list($tagDisplay, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'logevent' );
+ $classes = array_merge( $classes, $newClasses );
+
if( $revert != '' ) {
$revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>';
}
- return Xml::tags( 'li', array( "class" => "mw-logline-$row->log_type" ),
- $del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert );
+ return Xml::tags( 'li', array( "class" => implode( ' ', $classes ) ),
+ $del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert . " $tagDisplay" ) . "\n";
}
/**
@@ -373,19 +363,18 @@ class LogEventsList {
$revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
// If event was hidden from sysops
if( !self::userCan( $row, LogPage::DELETED_RESTRICTED ) ) {
- $del = $this->message['rev-delundel'];
+ $del = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.$this->message['rev-delundel'].')' );
} else if( $row->log_type == 'suppress' ) {
// No one should be hiding from the oversight log
- $del = $this->message['rev-delundel'];
+ $del = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.$this->message['rev-delundel'].')' );
} else {
$target = SpecialPage::getTitleFor( 'Log', $row->log_type );
- $del = $this->skin->makeKnownLinkObj( $revdel, $this->message['rev-delundel'],
- 'target=' . $target->getPrefixedUrl() . '&logid='.$row->log_id );
- // Bolden oversighted content
- if( self::isDeleted( $row, LogPage::DELETED_RESTRICTED ) )
- $del = "<strong>$del</strong>";
+ $query = array( 'target' => $target->getPrefixedDBkey(),
+ 'logid[]' => $row->log_id
+ );
+ $del = $this->skin->revDeleteLink( $query, self::isDeleted( $row, LogPage::DELETED_RESTRICTED ) );
}
- return "<tt>(<small>$del</small>)</tt>";
+ return $del;
}
/**
@@ -468,15 +457,16 @@ class LogEventsList {
/**
* SQL clause to skip forbidden log types for this user
* @param $db Database
+ * @param $audience string, public/user
* @return mixed (string or false)
*/
- public static function getExcludeClause( $db ) {
+ public static function getExcludeClause( $db, $audience = 'public' ) {
global $wgLogRestrictions, $wgUser;
// Reset the array, clears extra "where" clauses when $par is used
$hiddenLogs = array();
// Don't show private logs to unprivileged users
foreach( $wgLogRestrictions as $logType => $right ) {
- if( !$wgUser->isAllowed($right) ) {
+ if( $audience == 'public' || !$wgUser->isAllowed($right) ) {
$safeType = $db->strencode( $logType );
$hiddenLogs[] = $safeType;
}
@@ -509,17 +499,18 @@ class LogPager extends ReverseChronologicalPager {
* @param $month Integer
*/
public function __construct( $list, $type = '', $user = '', $title = '', $pattern = '',
- $conds = array(), $year = false, $month = false )
+ $conds = array(), $year = false, $month = false, $tagFilter = '' )
{
parent::__construct();
$this->mConds = $conds;
$this->mLogEventsList = $list;
- $this->limitType( $type );
+ $this->limitType( $type ); // also excludes hidden types
$this->limitUser( $user );
$this->limitTitle( $title, $pattern );
$this->getDateCond( $year, $month );
+ $this->mTagFilter = $tagFilter;
}
public function getDefaultQuery() {
@@ -560,16 +551,17 @@ class LogPager extends ReverseChronologicalPager {
if( isset($wgLogRestrictions[$type]) && !$wgUser->isAllowed($wgLogRestrictions[$type]) ) {
$type = '';
}
- // Don't show private logs to unpriviledged users
- $hideLogs = LogEventsList::getExcludeClause( $this->mDb );
+ // Don't show private logs to unpriviledged users.
+ // Also, only show them upon specific request to avoid suprises.
+ $audience = $type ? 'user' : 'public';
+ $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience );
if( $hideLogs !== false ) {
$this->mConds[] = $hideLogs;
}
- if( !$type ) {
- return false;
+ if( $type ) {
+ $this->type = $type;
+ $this->mConds['log_type'] = $type;
}
- $this->type = $type;
- $this->mConds['log_type'] = $type;
}
/**
@@ -591,7 +583,12 @@ class LogPager extends ReverseChronologicalPager {
but for now it won't pass anywhere behind the optimizer */
$this->mConds[] = "NULL";
} else {
+ global $wgUser;
$this->mConds['log_user'] = $userid;
+ // Paranoia: avoid brute force searches (bug 17342)
+ if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ $this->mConds[] = 'log_deleted & ' . LogPage::DELETED_USER . ' = 0';
+ }
$this->user = $usertitle->getText();
}
}
@@ -603,7 +600,7 @@ class LogPager extends ReverseChronologicalPager {
* @param $pattern String
*/
private function limitTitle( $page, $pattern ) {
- global $wgMiserMode;
+ global $wgMiserMode, $wgUser;
$title = Title::newFromText( $page );
if( strlen($page) == 0 || !$title instanceof Title )
@@ -632,6 +629,10 @@ class LogPager extends ReverseChronologicalPager {
$this->mConds['log_namespace'] = $ns;
$this->mConds['log_title'] = $title->getDBkey();
}
+ // Paranoia: avoid brute force searches (bug 17342)
+ if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ $this->mConds[] = 'log_deleted & ' . LogPage::DELETED_ACTION . ' = 0';
+ }
}
public function getQueryInfo() {
@@ -644,13 +645,19 @@ class LogPager extends ReverseChronologicalPager {
} else {
$index = array( 'USE INDEX' => array( 'logging' => 'times' ) );
}
- return array(
+ $info = array(
'tables' => array( 'logging', 'user' ),
'fields' => array( 'log_type', 'log_action', 'log_user', 'log_namespace', 'log_title', 'log_params',
'log_comment', 'log_id', 'log_deleted', 'log_timestamp', 'user_name', 'user_editcount' ),
'conds' => $this->mConds,
- 'options' => $index
+ 'options' => $index,
+ 'join_conds' => array( 'user' => array( 'INNER JOIN', 'user_id=log_user' ) ),
);
+
+ ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'],
+ $info['join_conds'], $info['options'], $this->mTagFilter );
+
+ return $info;
}
function getIndexField() {
@@ -701,6 +708,10 @@ class LogPager extends ReverseChronologicalPager {
public function getMonth() {
return $this->mMonth;
}
+
+ public function getTagFilter() {
+ return $this->mTagFilter;
+ }
}
/**
@@ -722,6 +733,7 @@ class LogReader {
$pattern = $request->getBool( 'pattern' );
$year = $request->getIntOrNull( 'year' );
$month = $request->getIntOrNull( 'month' );
+ $tagFilter = $request->getVal( 'tagfilter' );
# Don't let the user get stuck with a certain date
$skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev';
if( $skip ) {
@@ -730,7 +742,7 @@ class LogReader {
}
# Use new list class to output results
$loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
- $this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month );
+ $this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month, $tagFilter );
}
/**
diff --git a/includes/LogPage.php b/includes/LogPage.php
index 50a9a232..0d572385 100644
--- a/includes/LogPage.php
+++ b/includes/LogPage.php
@@ -37,7 +37,7 @@ class LogPage {
/* @access private */
var $type, $action, $comment, $params, $target, $doer;
/* @acess public */
- var $updateRecentChanges;
+ var $updateRecentChanges, $sendToUDP;
/**
* Constructor
@@ -45,15 +45,16 @@ class LogPage {
* @param string $type One of '', 'block', 'protect', 'rights', 'delete',
* 'upload', 'move'
* @param bool $rc Whether to update recent changes as well as the logging table
+ * @param bool $udp Whether to send to the UDP feed if NOT sent to RC
*/
- function __construct( $type, $rc = true ) {
+ public function __construct( $type, $rc = true, $udp = 'skipUDP' ) {
$this->type = $type;
$this->updateRecentChanges = $rc;
+ $this->sendToUDP = ($udp == 'UDP');
}
protected function saveContent() {
- global $wgUser, $wgLogRestrictions;
- $fname = 'LogPage::saveContent';
+ global $wgLogRestrictions;
$dbw = wfGetDB( DB_MASTER );
$log_id = $dbw->nextSequenceValue( 'log_log_id_seq' );
@@ -70,21 +71,25 @@ class LogPage {
'log_comment' => $this->comment,
'log_params' => $this->params
);
- $dbw->insert( 'logging', $data, $fname );
+ $dbw->insert( 'logging', $data, __METHOD__ );
$newId = !is_null($log_id) ? $log_id : $dbw->insertId();
- if( !($dbw->affectedRows() > 0) ) {
- wfDebugLog( "logging", "LogPage::saveContent failed to insert row - Error {$dbw->lastErrno()}: {$dbw->lastError()}" );
- }
# And update recentchanges
if( $this->updateRecentChanges ) {
- # Don't add private logs to RC!
- if( !isset($wgLogRestrictions[$this->type]) || $wgLogRestrictions[$this->type]=='*' ) {
- $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
- $rcComment = $this->getRcComment();
- RecentChange::notifyLog( $now, $titleObj, $this->doer, $rcComment, '',
- $this->type, $this->action, $this->target, $this->comment, $this->params, $newId );
+ $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
+ RecentChange::notifyLog( $now, $titleObj, $this->doer, $this->getRcComment(), '', $this->type,
+ $this->action, $this->target, $this->comment, $this->params, $newId );
+ } else if( $this->sendToUDP ) {
+ # Don't send private logs to UDP
+ if( isset($wgLogRestrictions[$this->type]) && $wgLogRestrictions[$this->type] !='*' ) {
+ return true;
}
+ # Notify external application via UDP.
+ # We send this to IRC but do not want to add it the RC table.
+ $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
+ $rc = RecentChange::newLogEntry( $now, $titleObj, $this->doer, $this->getRcComment(), '',
+ $this->type, $this->action, $this->target, $this->comment, $this->params, $newId );
+ $rc->notifyRC2UDP();
}
return true;
}
@@ -98,7 +103,7 @@ class LogPage {
if ($rcComment == '')
$rcComment = $this->comment;
else
- $rcComment .= ': ' . $this->comment;
+ $rcComment .= wfMsgForContent( 'colon-separator' ) . $this->comment;
}
return $rcComment;
}
@@ -145,7 +150,7 @@ class LogPage {
* @param string $type logtype
* @return string Headertext of this logtype
*/
- static function logHeader( $type ) {
+ public static function logHeader( $type ) {
global $wgLogHeaders, $wgMessageCache;
$wgMessageCache->loadAllMessages();
return wfMsgExt($wgLogHeaders[$type],array('parseinline'));
@@ -155,7 +160,7 @@ class LogPage {
* @static
* @return HTML string
*/
- static function actionText( $type, $action, $title = NULL, $skin = NULL,
+ public static function actionText( $type, $action, $title = NULL, $skin = NULL,
$params = array(), $filterWikilinks = false )
{
global $wgLang, $wgContLang, $wgLogActions, $wgMessageCache;
@@ -196,7 +201,7 @@ class LogPage {
} else {
$details = '';
array_unshift( $params, $titleLink );
- if ( $key == 'block/block' || $key == 'suppress/block' || $key == 'block/reblock' ) {
+ if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) {
if ( $skin ) {
$params[1] = '<span title="' . htmlspecialchars( $params[1] ). '">' .
$wgLang->translateBlockExpiry( $params[1] ) . '</span>';
@@ -208,11 +213,19 @@ class LogPage {
} else if ( $type == 'protect' && count($params) == 3 ) {
$details .= " {$params[1]}"; // restrictions and expiries
if( $params[2] ) {
- $details .= ' ['.wfMsg('protect-summary-cascade').']';
+ if ( $skin ) {
+ $details .= ' ['.wfMsg('protect-summary-cascade').']';
+ } else {
+ $details .= ' ['.wfMsgForContent('protect-summary-cascade').']';
+ }
}
} else if ( $type == 'move' && count( $params ) == 3 ) {
if( $params[2] ) {
- $details .= ' [' . wfMsg( 'move-redirect-suppressed' ) . ']';
+ if ( $skin ) {
+ $details .= ' [' . wfMsg( 'move-redirect-suppressed' ) . ']';
+ } else {
+ $details .= ' [' . wfMsgForContent( 'move-redirect-suppressed' ) . ']';
+ }
}
}
$rv = wfMsgReal( $wgLogActions[$key], $params, true, !$skin ) . $details;
@@ -228,6 +241,17 @@ class LogPage {
$rv = "$action";
}
}
+
+ // For the perplexed, this feature was added in r7855 by Erik.
+ // The feature was added because we liked adding [[$1]] in our log entries
+ // but the log entries are parsed as Wikitext on RecentChanges but as HTML
+ // on Special:Log. The hack is essentially that [[$1]] represented a link
+ // to the title in question. The first parameter to the HTML version (Special:Log)
+ // is that link in HTML form, and so this just gets rid of the ugly [[]].
+ // However, this is a horrible hack and it doesn't work like you expect if, say,
+ // you want to link to something OTHER than the title of the log entry.
+ // The real problem, which Erik was trying to fix (and it sort-of works now) is
+ // that the same messages are being treated as both wikitext *and* HTML.
if( $filterWikilinks ) {
$rv = str_replace( "[[", "", $rv );
$rv = str_replace( "]]", "", $rv );
@@ -296,7 +320,7 @@ class LogPage {
* @param array $params Parameters passed later to wfMsg.* functions
* @param User $doer The user doing the action
*/
- function addEntry( $action, $target, $comment, $params = array(), $doer = null ) {
+ public function addEntry( $action, $target, $comment, $params = array(), $doer = null ) {
if ( !is_array( $params ) ) {
$params = array( $params );
}
@@ -324,7 +348,7 @@ class LogPage {
* Create a blob from a parameter array
* @static
*/
- static function makeParamBlob( $params ) {
+ public static function makeParamBlob( $params ) {
return implode( "\n", $params );
}
@@ -332,7 +356,7 @@ class LogPage {
* Extract a parameter array from a blob
* @static
*/
- static function extractParams( $blob ) {
+ public static function extractParams( $blob ) {
if ( $blob === '' ) {
return array();
} else {
@@ -350,11 +374,13 @@ class LogPage {
* @return string
*/
public static function formatBlockFlags( $flags, $forContent = false ) {
+ global $wgLang;
+
$flags = explode( ',', trim( $flags ) );
if( count( $flags ) > 0 ) {
for( $i = 0; $i < count( $flags ); $i++ )
$flags[$i] = self::formatBlockFlag( $flags[$i], $forContent );
- return '(' . implode( ', ', $flags ) . ')';
+ return '(' . $wgLang->commaList( $flags ) . ')';
} else {
return '';
}
diff --git a/includes/MagicWord.php b/includes/MagicWord.php
index 5b5b77f0..4e97016d 100644
--- a/includes/MagicWord.php
+++ b/includes/MagicWord.php
@@ -78,6 +78,7 @@ class MagicWord {
'revisionmonth',
'revisionyear',
'revisiontimestamp',
+ 'revisionuser',
'subpagename',
'subpagenamee',
'displaytitle',
@@ -90,7 +91,9 @@ class MagicWord {
'subjectpagename',
'subjectpagenamee',
'numberofusers',
+ 'numberofactiveusers',
'newsectionlink',
+ 'nonewsectionlink',
'numberofpages',
'currentversion',
'basepagename',
@@ -141,6 +144,7 @@ class MagicWord {
'localweek' => 3600,
'localdow' => 3600,
'numberofusers' => 3600,
+ 'numberofactiveusers' => 3600,
'numberofpages' => 3600,
'currentversion' => 86400,
'currenttimestamp' => 3600,
@@ -158,6 +162,7 @@ class MagicWord {
'toc',
'noeditsection',
'newsectionlink',
+ 'nonewsectionlink',
'hiddencat',
'index',
'noindex',
diff --git a/includes/MessageCache.php b/includes/MessageCache.php
index a06b0cb9..2236bdd7 100644
--- a/includes/MessageCache.php
+++ b/includes/MessageCache.php
@@ -702,7 +702,10 @@ class MessageCache {
* @param string $lang The messages language, English by default
*/
function addMessage( $key, $value, $lang = 'en' ) {
- $this->mExtensionMessages[$lang][$key] = $value;
+ global $wgContLang;
+ # Normalise title-case input
+ $lckey = str_replace( ' ', '_', $wgContLang->lcfirst( $key ) );
+ $this->mExtensionMessages[$lang][$lckey] = $value;
}
/**
@@ -800,6 +803,7 @@ class MessageCache {
*/
function loadMessagesFile( $filename, $langcode = false ) {
global $wgLang, $wgContLang;
+ wfProfileIn( __METHOD__ );
$messages = $magicWords = false;
require( $filename );
@@ -822,6 +826,7 @@ class MessageCache {
global $wgContLang;
$wgContLang->addMagicWordsByLang( $magicWords );
}
+ wfProfileOut( __METHOD__ );
}
/**
@@ -831,6 +836,7 @@ class MessageCache {
* @param string $langcode Language code to process.
*/
function processMessagesArray( $messages, $langcode ) {
+ wfProfileIn( __METHOD__ );
$fallbackCode = $langcode;
$mergedMessages = array();
do {
@@ -842,6 +848,7 @@ class MessageCache {
if ( !empty($mergedMessages) )
$this->addMessages( $mergedMessages, $langcode );
+ wfProfileOut( __METHOD__ );
}
public function figureMessage( $key ) {
diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php
index 4797752d..d52de994 100644
--- a/includes/MimeMagic.php
+++ b/includes/MimeMagic.php
@@ -579,22 +579,22 @@ class MimeMagic {
*/
function detectZipType( $header ) {
$opendocTypes = array(
- 'chart',
'chart-template',
- 'formula',
+ 'chart',
'formula-template',
- 'graphics',
+ 'formula',
'graphics-template',
- 'image',
+ 'graphics',
'image-template',
- 'presentation',
+ 'image',
'presentation-template',
- 'spreadsheet',
+ 'presentation',
'spreadsheet-template',
- 'text',
+ 'spreadsheet',
'text-template',
'text-master',
- 'text-web' );
+ 'text-web',
+ 'text' );
// http://lists.oasis-open.org/archives/office/200505/msg00006.html
$types = '(?:' . implode( '|', $opendocTypes ) . ')';
diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php
index 2b3e9fae..1f4798b7 100644
--- a/includes/OutputHandler.php
+++ b/includes/OutputHandler.php
@@ -10,7 +10,7 @@ function wfOutputHandler( $s ) {
$headers = apache_response_headers();
$isHTML = true;
foreach ( $headers as $name => $value ) {
- if ( strtolower( $name ) == 'content-type' && strpos( $value, 'text/html' ) === false ) {
+ if ( strtolower( $name ) == 'content-type' && strpos( $value, 'text/html' ) === false && strpos( $value, 'application/xhtml+xml' ) === false ) {
$isHTML = false;
break;
}
@@ -123,10 +123,9 @@ function wfDoContentLength( $length ) {
* Replace the output with an error if the HTML is not valid
*/
function wfHtmlValidationHandler( $s ) {
- global $IP;
- $tidy = new tidy;
- $tidy->parseString( $s, "$IP/includes/tidy.conf", 'utf8' );
- if ( $tidy->getStatus() == 0 ) {
+
+ $errors = '';
+ if ( MWTidy::checkErrors( $s, $errors ) ) {
return $s;
}
@@ -134,7 +133,7 @@ function wfHtmlValidationHandler( $s ) {
$out = <<<EOT
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<title>HTML validation error</title>
<style>
@@ -147,7 +146,7 @@ li { white-space: pre }
<ul>
EOT;
- $error = strtok( $tidy->errorBuffer, "\n" );
+ $error = strtok( $errors, "\n" );
$badLines = array();
while ( $error !== false ) {
if ( preg_match( '/^line (\d+)/', $error, $m ) ) {
@@ -158,8 +157,9 @@ EOT;
$error = strtok( "\n" );
}
- $out .= '<pre>' . htmlspecialchars( $tidy->errorBuffer ) . '</pre>';
- $out .= '<ol>';
+ $out .= '</ul>';
+ $out .= '<pre>' . htmlspecialchars( $errors ) . '</pre>';
+ $out .= "<ol>\n";
$line = strtok( $s, "\n" );
$i = 1;
while ( $line !== false ) {
@@ -168,7 +168,7 @@ EOT;
} else {
$out .= '<li>';
}
- $out .= htmlspecialchars( $line ) . '</li>';
+ $out .= htmlspecialchars( $line ) . "</li>\n";
$line = strtok( "\n" );
$i++;
}
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index f8dba714..ed9a43d3 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -29,6 +29,7 @@ class OutputPage {
var $mArticleBodyOnly = false;
var $mNewSectionLink = false;
+ var $mHideNewSectionLink = false;
var $mNoGallery = false;
var $mPageTitleActionText = '';
var $mParseWarnings = array();
@@ -165,7 +166,7 @@ class OutputPage {
*
* @return bool True iff cache-ok headers was sent.
*/
- function checkLastModified ( $timestamp ) {
+ function checkLastModified( $timestamp ) {
global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest;
if ( !$timestamp || $timestamp == '19700101000000' ) {
@@ -232,6 +233,7 @@ class OutputPage {
# Not modified
# Give a 304 response code and disable body output
wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
+ ini_set('zlib.output_compression', 0);
$wgRequest->response()->header( "HTTP/1.1 304 Not Modified" );
$this->sendCacheControl();
$this->disable();
@@ -309,20 +311,20 @@ class OutputPage {
}
}
- public function setHTMLTitle( $name ) {$this->mHTMLtitle = $name; }
+ public function setHTMLTitle( $name ) { $this->mHTMLtitle = $name; }
public function setPageTitle( $name ) {
- global $action, $wgContLang;
- $name = $wgContLang->convert($name, true);
+ global $wgContLang;
+ $name = $wgContLang->convert( $name, true );
$this->mPagetitle = $name;
- if(!empty($action)) {
- $taction = $this->getPageTitleActionText();
- if( !empty( $taction ) ) {
- $name .= ' - '.$taction;
- }
+
+ $taction = $this->getPageTitleActionText();
+ if( !empty( $taction ) ) {
+ $name .= ' - '.$taction;
}
$this->setHTMLTitle( wfMsg( 'pagetitle', $name ) );
}
+
public function getHTMLTitle() { return $this->mHTMLtitle; }
public function getPageTitle() { return $this->mPagetitle; }
public function setSubtitle( $str ) { $this->mSubtitle = /*$this->parse(*/$str/*)*/; } // @bug 2514
@@ -338,6 +340,7 @@ class OutputPage {
public function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; }
public function getOnloadHandler() { return $this->mOnloadHandler; }
public function disable() { $this->mDoNothing = true; }
+ public function isDisabled() { return $this->mDoNothing; }
public function setArticleRelated( $v ) {
$this->mIsArticleRelated = $v;
@@ -408,7 +411,12 @@ class OutputPage {
if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) {
$sk = $wgUser->getSkin();
foreach ( $categories as $category => $type ) {
+ $origcategory = $category;
$title = Title::makeTitleSafe( NS_CATEGORY, $category );
+ $wgContLang->findVariantLink( $category, $title, true );
+ if ( $category != $origcategory )
+ if ( array_key_exists( $category, $categories ) )
+ continue;
$text = $wgContLang->convertHtml( $title->getText() );
$this->mCategoryLinks[$type][] = $sk->makeLinkObj( $title, $text );
}
@@ -511,6 +519,7 @@ class OutputPage {
$this->mLanguageLinks += $parserOutput->getLanguageLinks();
$this->addCategoryLinks( $parserOutput->getCategories() );
$this->mNewSectionLink = $parserOutput->getNewSection();
+ $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
if( is_null( $wgExemptFromUserRobotsControl ) ) {
$bannedNamespaces = $wgContentNamespaces;
@@ -538,9 +547,11 @@ class OutputPage {
$this->mTemplateIds[$ns] = $dbks;
}
}
- // Display title
+ // Page title
if( ( $dt = $parserOutput->getDisplayTitle() ) !== false )
$this->setPageTitle( $dt );
+ else if ( ( $title = $parserOutput->getTitleText() ) != '' )
+ $this->setPageTitle( $title );
// Hooks registered in the object
global $wgParserOutputHooks;
@@ -586,7 +597,7 @@ class OutputPage {
$popts->setTidy(false);
if ( $cache && $article && $parserOutput->getCacheTime() != -1 ) {
$parserCache = ParserCache::singleton();
- $parserCache->save( $parserOutput, $article, $wgUser );
+ $parserCache->save( $parserOutput, $article, $popts);
}
$this->addParserOutput( $parserOutput );
@@ -642,15 +653,27 @@ class OutputPage {
return $parserOutput->getText();
}
+ /** Parse wikitext, strip paragraphs, and return the HTML. */
+ public function parseInline( $text, $linestart = true, $interface = false ) {
+ $parsed = $this->parse( $text, $linestart, $interface );
+
+ $m = array();
+ if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) {
+ $parsed = $m[1];
+ }
+
+ return $parsed;
+ }
+
/**
* @param Article $article
* @param User $user
*
* @return bool True if successful, else false.
*/
- public function tryParserCache( &$article, $user ) {
+ public function tryParserCache( &$article ) {
$parserCache = ParserCache::singleton();
- $parserOutput = $parserCache->get( $article, $user );
+ $parserOutput = $parserCache->get( $article, $this->parserOptions() );
if ( $parserOutput !== false ) {
$this->addParserOutput( $parserOutput );
return true;
@@ -917,13 +940,13 @@ class OutputPage {
'rel' => 'alternate',
'type' => 'application/x-wiki',
'title' => wfMsg( 'edit' ),
- 'href' => $wgTitle->getFullURL( 'action=edit' )
+ 'href' => $wgTitle->getLocalURL( 'action=edit' )
) );
// Alternate edit link
$this->addLink( array(
'rel' => 'edit',
'title' => wfMsg( 'edit' ),
- 'href' => $wgTitle->getFullURL( 'action=edit' )
+ 'href' => $wgTitle->getLocalURL( 'action=edit' )
) );
}
}
@@ -1132,7 +1155,7 @@ class OutputPage {
* @param string $permission key required
*/
public function permissionRequired( $permission ) {
- global $wgUser;
+ global $wgUser, $wgLang;
$this->setPageTitle( wfMsg( 'badaccess' ) );
$this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
@@ -1144,7 +1167,7 @@ class OutputPage {
User::getGroupsWithPermission( $permission ) );
if( $groups ) {
$this->addWikiMsg( 'badaccess-groups',
- implode( ', ', $groups ),
+ $wgLang->commaList( $groups ),
count( $groups) );
} else {
$this->addWikiMsg( 'badaccess-group0' );
@@ -1457,7 +1480,7 @@ class OutputPage {
$ret = '';
if( $wgMimeType == 'text/xml' || $wgMimeType == 'application/xhtml+xml' || $wgMimeType == 'application/xml' ) {
- $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?>\n";
+ $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?" . ">\n";
}
$ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
@@ -1505,7 +1528,7 @@ class OutputPage {
if ( count( $this->mKeywords ) > 0 ) {
$strip = array(
- "/<.*?>/" => '',
+ "/<.*?" . ">/" => '',
"/_/" => ' '
);
$this->addMeta( 'keywords', preg_replace(array_keys($strip), array_values($strip),implode( ",", $this->mKeywords ) ) );
@@ -1576,7 +1599,7 @@ class OutputPage {
foreach( $wgFeedClasses as $format => $class ) {
$tags[] = $this->feedLink(
$format,
- $rctitle->getFullURL( "feed={$format}" ),
+ $rctitle->getLocalURL( "feed={$format}" ),
wfMsg( "site-{$format}-feed", $wgSitename ) ); # For grep: 'site-rss-feed', 'site-atom-feed'.
}
}
@@ -1759,6 +1782,15 @@ class OutputPage {
}
/**
+ * Forcibly hide the new section link?
+ *
+ * @return bool
+ */
+ public function forceHideNewSectionLink() {
+ return $this->mHideNewSectionLink;
+ }
+
+ /**
* Show a warning about slave lag
*
* If the lag is higher than $wgSlaveLagCritical seconds,
@@ -1843,7 +1875,7 @@ class OutputPage {
$args = array();
$name = $spec;
}
- $s = str_replace( '$' . ($n+1), wfMsgExt( $name, $options, $args ), $s );
+ $s = str_replace( '$' . ( $n + 1 ), wfMsgExt( $name, $options, $args ), $s );
}
$this->addHTML( $this->parse( $s, /*linestart*/true, /*uilang*/true ) );
}
diff --git a/includes/PageHistory.php b/includes/PageHistory.php
index b01b485e..9477981f 100644
--- a/includes/PageHistory.php
+++ b/includes/PageHistory.php
@@ -23,6 +23,8 @@ class PageHistory {
var $lastdate;
var $linesonpage;
var $mLatestId = null;
+
+ private $mOldIdChecked = 0;
/**
* Construct a new PageHistory.
@@ -112,6 +114,8 @@ class PageHistory {
*/
$year = $wgRequest->getInt( 'year' );
$month = $wgRequest->getInt( 'month' );
+ $tagFilter = $wgRequest->getVal( 'tagfilter' );
+ $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter );
$action = htmlspecialchars( $wgScript );
$wgOut->addHTML(
@@ -119,7 +123,8 @@ class PageHistory {
Xml::fieldset( wfMsg( 'history-fieldset-title' ), false, array( 'id' => 'mw-history-search' ) ) .
Xml::hidden( 'title', $this->mTitle->getPrefixedDBKey() ) . "\n" .
Xml::hidden( 'action', 'history' ) . "\n" .
- $this->getDateMenu( $year, $month ) . '&nbsp;' .
+ xml::dateMenu( $year, $month ) . '&nbsp;' .
+ ( $tagSelector ? ( implode( '&nbsp;', $tagSelector ) . '&nbsp;' ) : '' ) .
Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
'</fieldset></form>'
);
@@ -129,7 +134,7 @@ class PageHistory {
/**
* Do the list
*/
- $pager = new PageHistoryPager( $this, $year, $month );
+ $pager = new PageHistoryPager( $this, $year, $month, $tagFilter );
$this->linesonpage = $pager->getNumRows();
$wgOut->addHTML(
$pager->getNavigationBar() .
@@ -143,37 +148,6 @@ class PageHistory {
}
/**
- * @return string Formatted HTML
- * @param int $year
- * @param int $month
- */
- private function getDateMenu( $year, $month ) {
- # Offset overrides year/month selection
- if( $month && $month !== -1 ) {
- $encMonth = intval( $month );
- } else {
- $encMonth = '';
- }
- if( $year ) {
- $encYear = intval( $year );
- } else if( $encMonth ) {
- $thisMonth = intval( gmdate( 'n' ) );
- $thisYear = intval( gmdate( 'Y' ) );
- if( intval($encMonth) > $thisMonth ) {
- $thisYear--;
- }
- $encYear = $thisYear;
- } else {
- $encYear = '';
- }
- return Xml::label( wfMsg( 'year' ), 'year' ) . ' '.
- Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) .
- ' '.
- Xml::label( wfMsg( 'month' ), 'month' ) . ' '.
- Xml::monthSelector( $encMonth, -1 );
- }
-
- /**
* Creates begin of history list with a submit button
*
* @return string HTML output
@@ -287,37 +261,34 @@ class PageHistory {
$lastlink = $this->lastLink( $rev, $next, $counter );
$arbitrary = $this->diffButtons( $rev, $firstInList, $counter );
$link = $this->revLink( $rev );
+ $classes = array();
$s = "($curlink) ($lastlink) $arbitrary";
if( $wgUser->isAllowed( 'deleterevision' ) ) {
- $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
- if( $firstInList ) {
+ if( $latest ) {
// We don't currently handle well changing the top revision's settings
- $del = $this->message['rev-delundel'];
+ $del = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.$this->message['rev-delundel'].')' );
} else if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
// If revision was hidden from sysops
- $del = $this->message['rev-delundel'];
+ $del = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.$this->message['rev-delundel'].')' );
} else {
- $del = $this->mSkin->makeKnownLinkObj( $revdel,
- $this->message['rev-delundel'],
- 'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) .
- '&oldid=' . urlencode( $rev->getId() ) );
- // Bolden oversighted content
- if( $rev->isDeleted( Revision::DELETED_RESTRICTED ) )
- $del = "<strong>$del</strong>";
+ $query = array( 'target' => $this->mTitle->getPrefixedDbkey(),
+ 'oldid' => $rev->getId()
+ );
+ $del = $this->mSkin->revDeleteLink( $query, $rev->isDeleted( Revision::DELETED_RESTRICTED ) );
}
- $s .= " <tt>(<small>$del</small>)</tt> ";
+ $s .= " $del ";
}
$s .= " $link";
$s .= " <span class='history-user'>" . $this->mSkin->revUserTools( $rev, true ) . "</span>";
- if( $row->rev_minor_edit ) {
+ if( $rev->isMinor() ) {
$s .= ' ' . Xml::element( 'span', array( 'class' => 'minor' ), wfMsg( 'minoreditletter') );
}
- if( !is_null( $size = $rev->getSize() ) && $rev->userCan( Revision::DELETED_TEXT ) ) {
+ if( !is_null( $size = $rev->getSize() ) && !$rev->isDeleted( Revision::DELETED_TEXT ) ) {
$s .= ' ' . $this->mSkin->formatRevisionSize( $size );
}
@@ -356,12 +327,19 @@ class PageHistory {
}
if( $tools ) {
- $s .= ' (' . implode( ' | ', $tools ) . ')';
+ $s .= ' (' . $wgLang->pipeList( $tools ) . ')';
}
+ # Tags
+ list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'history' );
+ $classes = array_merge( $classes, $newClasses );
+ $s .= " $tagSummary";
+
wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row , &$s ) );
- return "<li>$s</li>\n";
+ $classes = implode( ' ', $classes );
+
+ return "<li class=\"$classes\">$s</li>\n";
}
/**
@@ -372,14 +350,10 @@ class PageHistory {
function revLink( $rev ) {
global $wgLang;
$date = $wgLang->timeanddate( wfTimestamp(TS_MW, $rev->getTimestamp()), true );
- if( $rev->userCan( Revision::DELETED_TEXT ) ) {
- $link = $this->mSkin->makeKnownLinkObj(
- $this->mTitle, $date, "oldid=" . $rev->getId() );
+ if( !$rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $link = $this->mSkin->makeKnownLinkObj( $this->mTitle, $date, "oldid=" . $rev->getId() );
} else {
- $link = $date;
- }
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- return '<span class="history-deleted">' . $link . '</span>';
+ $link = '<span class="history-deleted">' . $date . '</span>';
}
return $link;
}
@@ -392,7 +366,7 @@ class PageHistory {
*/
function curLink( $rev, $latest ) {
$cur = $this->message['cur'];
- if( $latest || !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ if( $latest || $rev->isDeleted( Revision::DELETED_TEXT ) ) {
return $cur;
} else {
return $this->mSkin->makeKnownLinkObj( $this->mTitle, $cur,
@@ -418,7 +392,7 @@ class PageHistory {
# Next row probably exists but is unknown, use an oldid=prev link
return $this->mSkin->makeKnownLinkObj( $this->mTitle, $last,
"diff=" . $prevRev->getId() . "&oldid=prev" );
- } elseif( !$prevRev->userCan(Revision::DELETED_TEXT) || !$nextRev->userCan(Revision::DELETED_TEXT) ) {
+ } elseif( $prevRev->isDeleted(Revision::DELETED_TEXT) || $nextRev->isDeleted(Revision::DELETED_TEXT) ) {
return $last;
} else {
return $this->mSkin->makeKnownLinkObj( $this->mTitle, $last,
@@ -435,40 +409,29 @@ class PageHistory {
* @return string HTML output for the radio buttons
*/
function diffButtons( $rev, $firstInList, $counter ) {
- if( $this->linesonpage > 1) {
- $radio = array(
- 'type' => 'radio',
- 'value' => $rev->getId(),
- );
-
- if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
- $radio['disabled'] = 'disabled';
- }
-
+ if( $this->linesonpage > 1 ) {
+ $radio = array( 'type' => 'radio', 'value' => $rev->getId() );
/** @todo: move title texts to javascript */
if( $firstInList ) {
- $first = Xml::element( 'input', array_merge(
- $radio,
- array(
- 'style' => 'visibility:hidden',
- 'name' => 'oldid' ) ) );
+ $first = Xml::element( 'input',
+ array_merge( $radio, array( 'style' => 'visibility:hidden', 'name' => 'oldid' ) )
+ );
$checkmark = array( 'checked' => 'checked' );
} else {
- if( $counter == 2 ) {
+ # Check visibility of old revisions
+ if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $radio['disabled'] = 'disabled';
+ $checkmark = array(); // We will check the next possible one
+ } else if( $counter == 2 || !$this->mOldIdChecked ) {
$checkmark = array( 'checked' => 'checked' );
+ $this->mOldIdChecked = $rev->getId();
} else {
$checkmark = array();
}
- $first = Xml::element( 'input', array_merge(
- $radio,
- $checkmark,
- array( 'name' => 'oldid' ) ) );
+ $first = Xml::element( 'input', array_merge( $radio, $checkmark, array( 'name' => 'oldid' ) ) );
$checkmark = array();
}
- $second = Xml::element( 'input', array_merge(
- $radio,
- $checkmark,
- array( 'name' => 'diff' ) ) );
+ $second = Xml::element( 'input', array_merge( $radio, $checkmark, array( 'name' => 'diff' ) ) );
return $first . $second;
} else {
return '';
@@ -573,7 +536,7 @@ class PageHistory {
$rev->getUserText(),
$wgContLang->timeanddate( $rev->getTimestamp() ) );
} else {
- $title = $rev->getUserText() . ": " . FeedItem::stripComment( $rev->getComment() );
+ $title = $rev->getUserText() . wfMsgForContent( 'colon-separator' ) . FeedItem::stripComment( $rev->getComment() );
}
return new FeedItem(
@@ -593,20 +556,28 @@ class PageHistory {
class PageHistoryPager extends ReverseChronologicalPager {
public $mLastRow = false, $mPageHistory, $mTitle;
- function __construct( $pageHistory, $year='', $month='' ) {
+ function __construct( $pageHistory, $year='', $month='', $tagFilter = '' ) {
parent::__construct();
$this->mPageHistory = $pageHistory;
$this->mTitle =& $this->mPageHistory->mTitle;
+ $this->tagFilter = $tagFilter;
$this->getDateCond( $year, $month );
}
function getQueryInfo() {
$queryInfo = array(
'tables' => array('revision'),
- 'fields' => Revision::selectFields(),
+ 'fields' => array_merge( Revision::selectFields(), array('ts_tags') ),
'conds' => array('rev_page' => $this->mPageHistory->mTitle->getArticleID() ),
- 'options' => array( 'USE INDEX' => array('revision' => 'page_timestamp') )
+ 'options' => array( 'USE INDEX' => array('revision' => 'page_timestamp') ),
+ 'join_conds' => array( 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ),
);
+ ChangeTags::modifyDisplayQuery( $queryInfo['tables'],
+ $queryInfo['fields'],
+ $queryInfo['conds'],
+ $queryInfo['join_conds'],
+ $queryInfo['options'],
+ $this->tagFilter );
wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) );
return $queryInfo;
}
diff --git a/includes/Pager.php b/includes/Pager.php
index 8ec32ff4..8faec533 100644
--- a/includes/Pager.php
+++ b/includes/Pager.php
@@ -304,20 +304,18 @@ abstract class IndexPager implements Pager {
if ( $query === null ) {
return $text;
}
- if( $type == 'prev' || $type == 'next' ) {
- $attrs = "rel=\"$type\"";
- } elseif( $type == 'first' ) {
- $attrs = "rel=\"start\"";
- } else {
- # HTML 4 has no rel="end" . . .
- $attrs = '';
+
+ $attrs = array();
+ if( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) {
+ # HTML5 rel attributes
+ $attrs['rel'] = $type;
}
if( $type ) {
- $attrs .= " class=\"mw-{$type}link\"" ;
+ $attrs['class'] = "mw-{$type}link";
}
- return $this->getSkin()->makeKnownLinkObj( $this->getTitle(), $text,
- wfArrayToCGI( $query, $this->getDefaultQuery() ), '', '', $attrs );
+ return $this->getSkin()->link( $this->getTitle(), $text,
+ $attrs, $query + $this->getDefaultQuery(), array('noclasses','known') );
}
/**
@@ -532,10 +530,10 @@ abstract class AlphabeticPager extends IndexPager {
$pagingLinks = $this->getPagingLinks( $linkTexts );
$limitLinks = $this->getLimitLinks();
- $limits = implode( ' | ', $limitLinks );
+ $limits = $wgLang->pipeList( $limitLinks );
$this->mNavigationBar =
- "({$pagingLinks['first']} | {$pagingLinks['last']}) " .
+ "(" . $wgLang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) ) . ") " .
wfMsgHtml( 'viewprevnext', $pagingLinks['prev'],
$pagingLinks['next'], $limits );
@@ -551,7 +549,7 @@ abstract class AlphabeticPager extends IndexPager {
if( $first ) {
$first = false;
} else {
- $extra .= ' | ';
+ $extra .= wfMsgExt( 'pipe-separator' , 'escapenoentities' );
}
if( $order == $this->mOrderType ) {
@@ -612,9 +610,9 @@ abstract class ReverseChronologicalPager extends IndexPager {
$pagingLinks = $this->getPagingLinks( $linkTexts );
$limitLinks = $this->getLimitLinks();
- $limits = implode( ' | ', $limitLinks );
+ $limits = $wgLang->pipeList( $limitLinks );
- $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " .
+ $this->mNavigationBar = "({$pagingLinks['first']}" . wfMsgExt( 'pipe-separator' , 'escapenoentities' ) . "{$pagingLinks['last']}) " .
wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits);
return $this->mNavigationBar;
}
@@ -747,7 +745,8 @@ abstract class TablePager extends IndexPager {
}
function formatRow( $row ) {
- $s = "<tr>\n";
+ $rowClass = $this->getRowClass( $row );
+ $s = "<tr class=\"$rowClass\">\n";
$fieldNames = $this->getFieldNames();
$this->mCurrentRow = $row; # In case formatValue needs to know
foreach ( $fieldNames as $field => $name ) {
@@ -763,6 +762,10 @@ abstract class TablePager extends IndexPager {
return $s;
}
+ function getRowClass($row) {
+ return '';
+ }
+
function getIndexField() {
return $this->mSort;
}
diff --git a/includes/PatrolLog.php b/includes/PatrolLog.php
index 5f305c10..978821c1 100644
--- a/includes/PatrolLog.php
+++ b/includes/PatrolLog.php
@@ -14,22 +14,20 @@ class PatrolLog {
* @param mixed $change Change identifier or RecentChange object
* @param bool $auto Was this patrol event automatic?
*/
- public static function record( $change, $auto = false ) {
- if( !( is_object( $change ) && $change instanceof RecentChange ) ) {
- $change = RecentChange::newFromId( $change );
- if( !is_object( $change ) )
+ public static function record( $rc, $auto = false ) {
+ if( !( $rc instanceof RecentChange ) ) {
+ $rc = RecentChange::newFromId( $rc );
+ if( !is_object( $rc ) )
return false;
}
- $title = Title::makeTitleSafe( $change->getAttribute( 'rc_namespace' ),
- $change->getAttribute( 'rc_title' ) );
+ $title = Title::makeTitleSafe( $rc->getAttribute( 'rc_namespace' ), $rc->getAttribute( 'rc_title' ) );
if( is_object( $title ) ) {
- $params = self::buildParams( $change, $auto );
- $log = new LogPage( 'patrol', false ); # False suppresses RC entries
+ $params = self::buildParams( $rc, $auto );
+ $log = new LogPage( 'patrol', false, $auto ? "skipUDP" : "UDP" ); # False suppresses RC entries
$log->addEntry( 'patrol', $title, '', $params );
return true;
- } else {
- return false;
}
+ return false;
}
/**
@@ -41,12 +39,8 @@ class PatrolLog {
* @return string
*/
public static function makeActionText( $title, $params, $skin ) {
- # This is a bit of a hack, but...if $skin is not a Skin, then *do nothing*
- # -- this is fine, because the action text we would be queried for under
- # these conditions would have gone into recentchanges, which we aren't
- # supposed to be updating
+ list( $cur, /* $prev */, $auto ) = $params;
if( is_object( $skin ) ) {
- list( $cur, /* $prev */, $auto ) = $params;
# Standard link to the page in question
$link = $skin->makeLinkObj( $title );
if( $title->exists() ) {
@@ -64,7 +58,8 @@ class PatrolLog {
# Put it all together
return wfMsgHtml( 'patrol-log-line', $diff, $link, $auto );
} else {
- return '';
+ $text = $title->getPrefixedText();
+ return wfMsgForContent( 'patrol-log-line', wfMsgHtml('patrol-log-diff',$cur), "[[$text]]", '' );
}
}
diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php
index af569112..10c85930 100644
--- a/includes/PrefixSearch.php
+++ b/includes/PrefixSearch.php
@@ -135,7 +135,7 @@ class PrefixSearch {
// Reformat useful data for future printing by JSON engine
$srchres = array ();
- foreach ($data['query']['allpages'] as & $pageinfo) {
+ foreach ((array)$data['query']['allpages'] as $pageinfo) {
// Note: this data will no be printable by the xml engine
// because it does not support lists of unnamed items
$srchres[] = $pageinfo['title'];
diff --git a/includes/Profiler.php b/includes/Profiler.php
index ffb48978..80a6a68a 100644
--- a/includes/Profiler.php
+++ b/includes/Profiler.php
@@ -78,8 +78,8 @@ class Profiler {
* @param $functionname string
*/
function profileIn( $functionname ) {
- global $wgDebugFunctionEntry;
-
+ global $wgDebugFunctionEntry, $wgProfiling;
+ if( !$wgProfiling ) return;
if( $wgDebugFunctionEntry ){
$this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" );
}
@@ -92,8 +92,8 @@ class Profiler {
* @param $functionname string
*/
function profileOut($functionname) {
- global $wgDebugFunctionEntry;
-
+ global $wgDebugFunctionEntry, $wgProfiling;
+ if( !$wgProfiling ) return;
$memory = memory_get_usage();
$time = $this->getTime();
@@ -145,7 +145,12 @@ class Profiler {
}
$this->close();
- if( $wgProfileCallTree ){
+ if( $wgProfileCallTree ) {
+ global $wgProfileToDatabase;
+ # XXX: We must call $this->getFunctionReport() to log to the DB
+ if( $wgProfileToDatabase ) {
+ $this->getFunctionReport();
+ }
return $this->getCallTree();
} else {
return $this->getFunctionReport();
@@ -202,16 +207,13 @@ class Profiler {
/**
* Callback to get a formatted line for the call tree
*/
- function getCallTreeLine($entry) {
+ function getCallTreeLine( $entry ) {
list( $fname, $level, $start, /* $x */, $end) = $entry;
$delta = $end - $start;
$space = str_repeat(' ', $level);
-
# The ugly double sprintf is to work around a PHP bug,
# which has been fixed in recent releases.
- return sprintf( "%10s %s %s\n",
- trim( sprintf( "%7.3f", $delta * 1000.0 ) ),
- $space, $fname );
+ return sprintf( "%10s %s %s\n", trim( sprintf( "%7.3f", $delta * 1000.0 ) ), $space, $fname );
}
function getTime() {
@@ -316,8 +318,8 @@ class Profiler {
$percent = $total ? 100. * $elapsed / $total : 0;
$memory = $this->mMemory[$fname];
$prof .= sprintf($format, substr($fname, 0, $nameWidth), $calls, (float) ($elapsed * 1000), (float) ($elapsed * 1000) / $calls, $percent, $memory, ($this->mMin[$fname] * 1000.0), ($this->mMax[$fname] * 1000.0), $this->mOverhead[$fname]);
-
- if( $wgProfileToDatabase ){
+ # Log to the DB
+ if( $wgProfileToDatabase ) {
self::logToDB($fname, (float) ($elapsed * 1000), $calls, (float) ($memory) );
}
}
diff --git a/includes/ProfilerSimple.php b/includes/ProfilerSimple.php
index 349a7cac..5989061d 100644
--- a/includes/ProfilerSimple.php
+++ b/includes/ProfilerSimple.php
@@ -4,7 +4,9 @@
* @ingroup Profiler
*/
-require_once(dirname(__FILE__).'/Profiler.php');
+if ( !class_exists( 'Profiler' ) ) {
+ require_once(dirname(__FILE__).'/Profiler.php');
+}
/**
* Simple profiler base class.
diff --git a/includes/ProfilerSimpleTrace.php b/includes/ProfilerSimpleTrace.php
new file mode 100644
index 00000000..63119228
--- /dev/null
+++ b/includes/ProfilerSimpleTrace.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * @file
+ * @ingroup Profiler
+ */
+
+if ( !class_exists( 'ProfilerSimple' ) ) {
+ require_once(dirname(__FILE__).'/ProfilerSimple.php');
+}
+
+/**
+ * Execution trace
+ * @todo document methods (?)
+ * @ingroup Profiler
+ */
+class ProfilerSimpleTrace extends ProfilerSimple {
+ var $mMinimumTime = 0;
+ var $mProfileID = false;
+ var $trace = "";
+ var $memory = 0;
+
+ function __construct() {
+ global $wgRequestTime, $wgRUstart;
+ if (!empty($wgRequestTime) && !empty($wgRUstart)) {
+ $this->mWorkStack[] = array( '-total', 0, $wgRequestTime,$this->getCpuTime($wgRUstart));
+ $elapsedcpu = $this->getCpuTime() - $this->getCpuTime($wgRUstart);
+ $elapsedreal = microtime(true) - $wgRequestTime;
+ }
+ $this->trace .= "Beginning trace: \n";
+ }
+
+ function profileIn($functionname) {
+ global $wgDebugFunctionEntry;
+ $this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), microtime(true), $this->getCpuTime());
+ $this->trace .= " " . sprintf("%6.1f",$this->memoryDiff()) . str_repeat( " ", count($this->mWorkStack)) . " > " . $functionname . "\n";
+ }
+
+ function profileOut($functionname) {
+ global $wgDebugFunctionEntry;
+
+ if ($wgDebugFunctionEntry) {
+ $this->debug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n");
+ }
+
+ list($ofname, /* $ocount */ ,$ortime,$octime) = array_pop($this->mWorkStack);
+
+ if (!$ofname) {
+ $this->trace .= "Profiling error: $functionname\n";
+ } else {
+ if ($functionname == 'close') {
+ $message = "Profile section ended by close(): {$ofname}";
+ $functionname = $ofname;
+ $this->trace .= $message . "\n";
+ }
+ elseif ($ofname != $functionname) {
+ $self->trace .= "Profiling error: in({$ofname}), out($functionname)";
+ }
+ $elapsedcpu = $this->getCpuTime() - $octime;
+ $elapsedreal = microtime(true) - $ortime;
+ $this->trace .= sprintf("%03.6f %6.1f",$elapsedreal,$this->memoryDiff()) . str_repeat(" ",count($this->mWorkStack)+1) . " < " . $functionname . "\n";
+ }
+ }
+
+ function memoryDiff() {
+ $diff = memory_get_usage() - $this->memory;
+ $this->memory = memory_get_usage();
+ return $diff/1024;
+ }
+
+ function getOutput() {
+ print "<!-- \n {$this->trace} \n -->";
+ }
+}
diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php
index 372edfcd..5fe3cbc7 100644
--- a/includes/ProtectionForm.php
+++ b/includes/ProtectionForm.php
@@ -235,7 +235,7 @@ class ProtectionForm {
$reasonstr = $this->mReasonSelection;
if ( $reasonstr != 'other' && $this->mReason != '' ) {
// Entry from drop down menu + additional comment
- $reasonstr .= ': ' . $this->mReason;
+ $reasonstr .= wfMsgForContent( 'colon-separator' ) . $this->mReason;
} elseif ( $reasonstr == 'other' ) {
$reasonstr = $this->mReason;
}
@@ -374,7 +374,8 @@ class ProtectionForm {
</tr></table>";
}
# Add custom expiry field
- $attribs = array( 'id' => "mwProtect-$action-expires", 'onkeyup' => 'ProtectionForm.updateExpiry(this)' ) + $this->disabledAttrib;
+ $attribs = array( 'id' => "mwProtect-$action-expires",
+ 'onkeyup' => 'ProtectionForm.updateExpiry(this)' ) + $this->disabledAttrib;
$out .= "<table><tr>
<td class='mw-label'>" .
$mProtectother .
diff --git a/includes/QueryPage.php b/includes/QueryPage.php
index 0b587508..1cef31ea 100644
--- a/includes/QueryPage.php
+++ b/includes/QueryPage.php
@@ -204,7 +204,7 @@ class QueryPage {
* Clear the cache and save new results
*/
function recache( $limit, $ignoreErrors = true ) {
- $fname = get_class($this) . '::recache';
+ $fname = get_class( $this ) . '::recache';
$dbw = wfGetDB( DB_MASTER );
$dbr = wfGetDB( DB_SLAVE, array( $this->getName(), 'QueryPage::recache', 'vslow' ) );
if ( !$dbw || !$dbr ) {
@@ -222,9 +222,9 @@ class QueryPage {
$dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname );
# Do query
$sql = $this->getSQL() . $this->getOrder();
- if ($limit !== false)
- $sql = $dbr->limitResult($sql, $limit, 0);
- $res = $dbr->query($sql, $fname);
+ if ( $limit !== false )
+ $sql = $dbr->limitResult( $sql, $limit, 0 );
+ $res = $dbr->query( $sql, $fname );
$num = false;
if ( $res ) {
$num = $dbr->numRows( $res );
@@ -238,7 +238,7 @@ class QueryPage {
$insertSql .= ',';
}
if ( isset( $row->value ) ) {
- $value = $row->value;
+ $value = intval( $row->value ); // @bug 14414
} else {
$value = 0;
}
diff --git a/includes/RawPage.php b/includes/RawPage.php
index 7093367f..b422d49e 100644
--- a/includes/RawPage.php
+++ b/includes/RawPage.php
@@ -127,6 +127,15 @@ class RawPage {
$url = $_SERVER['PHP_SELF'];
}
+ if( $url == '' ) {
+ # This will make the next check fail with a confusing error
+ # message, so we should mention it separately.
+ wfHttpError( 500, 'Internal Server Error',
+ "\$_SERVER['PHP_SELF'] is not set. Perhaps you're using CGI" .
+ " and haven't set cgi.fix_pathinfo = 1 in php.ini?" );
+ return;
+ }
+
if( strcmp( $wgScript, $url ) ) {
# Internet Explorer will ignore the Content-Type header if it
# thinks it sees a file extension it recognizes. Make sure that
@@ -164,7 +173,7 @@ class RawPage {
$text = $this->getRawText();
if( !wfRunHooks( 'RawPageViewBeforeOutput', array( &$this, &$text ) ) ) {
- wfDebug( __METHOD__ . ': RawPageViewBeforeOutput hook broke raw page output.' );
+ wfDebug( __METHOD__ . ": RawPageViewBeforeOutput hook broke raw page output.\n" );
}
echo $text;
diff --git a/includes/RecentChange.php b/includes/RecentChange.php
index f03fbcbb..8e3f1107 100644
--- a/includes/RecentChange.php
+++ b/includes/RecentChange.php
@@ -166,6 +166,9 @@ class RecentChange
# Set the ID
$this->mAttribs['rc_id'] = $dbw->insertId();
+
+ # Notify extensions
+ wfRunHooks( 'RecentChange_save', array( &$this ) );
# Notify external application via UDP
if( $wgRC2UDPAddress && ( !$this->mAttribs['rc_bot'] || !$wgRC2UDPOmitBots ) ) {
@@ -193,9 +196,14 @@ class RecentChange
$this->mAttribs['rc_minor'],
$this->mAttribs['rc_last_oldid'] );
}
-
- # Notify extensions
- wfRunHooks( 'RecentChange_save', array( &$this ) );
+ }
+
+ public function notifyRC2UDP() {
+ global $wgRC2UDPAddress, $wgRC2UDPOmitBots;
+ # Notify external application via UDP
+ if( $wgRC2UDPAddress && ( !$this->mAttribs['rc_bot'] || !$wgRC2UDPOmitBots ) ) {
+ self::sendToUDP( $this->getIRCLine() );
+ }
}
/**
@@ -227,12 +235,12 @@ class RecentChange
}
/**
- * Remove newlines and carriage returns
+ * Remove newlines, carriage returns and decode html entites
* @param string $line
* @return string
*/
public static function cleanupForIRC( $text ) {
- return str_replace(array("\n", "\r"), array("", ""), $text);
+ return Sanitizer::decodeCharReferences( str_replace( array( "\n", "\r" ), array( "", "" ), $text ) );
}
/**
@@ -318,9 +326,7 @@ class RecentChange
{
if( !$ip ) {
$ip = wfGetIP();
- if( !$ip ) {
- $ip = '';
- }
+ if( !$ip ) $ip = '';
}
$rc = new RecentChange;
@@ -372,9 +378,7 @@ class RecentChange
{
if( !$ip ) {
$ip = wfGetIP();
- if( !$ip ) {
- $ip = '';
- }
+ if( !$ip ) $ip = '';
}
$rc = new RecentChange;
@@ -420,12 +424,9 @@ class RecentChange
public static function notifyMove( $timestamp, &$oldTitle, &$newTitle, &$user, $comment, $ip='', $overRedir = false )
{
global $wgRequest;
-
if( !$ip ) {
$ip = wfGetIP();
- if( !$ip ) {
- $ip = '';
- }
+ if( !$ip ) $ip = '';
}
$rc = new RecentChange;
@@ -473,16 +474,27 @@ class RecentChange
RecentChange::notifyMove( $timestamp, $oldTitle, $newTitle, $user, $comment, $ip, true );
}
- public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip='',
- $type, $action, $target, $logComment, $params, $newId=0 )
+ public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip='', $type,
+ $action, $target, $logComment, $params, $newId=0 )
{
- global $wgRequest;
+ global $wgLogRestrictions;
+ # Don't add private logs to RC!
+ if( isset($wgLogRestrictions[$type]) && $wgLogRestrictions[$type] != '*' ) {
+ return false;
+ }
+ $rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action,
+ $target, $logComment, $params, $newId );
+ $rc->save();
+ return true;
+ }
+ public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip='',
+ $type, $action, $target, $logComment, $params, $newId=0 )
+ {
+ global $wgRequest;
if( !$ip ) {
$ip = wfGetIP();
- if( !$ip ) {
- $ip = '';
- }
+ if( !$ip ) $ip = '';
}
$rc = new RecentChange;
@@ -518,7 +530,7 @@ class RecentChange
'lastTimestamp' => 0,
'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage
);
- $rc->save();
+ return $rc;
}
# Initialises the members of this object from a mysql row object
@@ -589,7 +601,7 @@ class RecentChange
return $trail;
}
- protected function getIRCLine() {
+ public function getIRCLine() {
global $wgUseRCPatrol, $wgUseNPPatrol, $wgRC2UDPInterwikiPrefix, $wgLocalInterwiki;
// FIXME: Would be good to replace these 2 extract() calls with something more explicit
@@ -643,7 +655,11 @@ class RecentChange
$flag = $rc_log_action;
} else {
$comment = self::cleanupForIRC( $rc_comment );
- $flag = ($rc_new ? "N" : "") . ($rc_minor ? "M" : "") . ($rc_bot ? "B" : "");
+ $flag = '';
+ if( !$rc_patrolled && ($wgUseRCPatrol || $rc_new && $wgUseNPPatrol) ) {
+ $flag .= '!';
+ }
+ $flag .= ($rc_new ? "N" : "") . ($rc_minor ? "M" : "") . ($rc_bot ? "B" : "");
}
if ( $wgRC2UDPInterwikiPrefix === true ) {
diff --git a/includes/RefreshLinksJob.php b/includes/RefreshLinksJob.php
index 1c119a8d..91cff40b 100644
--- a/includes/RefreshLinksJob.php
+++ b/includes/RefreshLinksJob.php
@@ -82,35 +82,21 @@ class RefreshLinksJob2 extends Job {
wfProfileOut( __METHOD__ );
return false;
}
- $start = intval($this->params['start']);
- $end = intval($this->params['end']);
-
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( array( 'templatelinks', 'page' ),
- array( 'page_namespace', 'page_title' ),
- array(
- 'page_id=tl_from',
- "tl_from >= '$start'",
- "tl_from <= '$end'",
- 'tl_namespace' => $this->title->getNamespace(),
- 'tl_title' => $this->title->getDBkey()
- ), __METHOD__
- );
+ $titles = $this->title->getBacklinkCache()->getLinks(
+ 'templatelinks', $this->params['start'], $this->params['end']);
# Not suitable for page load triggered job running!
# Gracefully switch to refreshLinks jobs if this happens.
if( php_sapi_name() != 'cli' ) {
$jobs = array();
- while( $row = $dbr->fetchObject( $res ) ) {
- $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ foreach ( $titles as $title ) {
$jobs[] = new RefreshLinksJob( $title, '' );
}
Job::batchInsert( $jobs );
return true;
}
# Re-parse each page that transcludes this page and update their tracking links...
- while( $row = $dbr->fetchObject( $res ) ) {
- $title = Title::makeTitle( $row->page_namespace, $row->page_title );
+ foreach ( $titles as $title ) {
$revision = Revision::newFromTitle( $title );
if ( !$revision ) {
$this->error = 'refreshLinks: Article not found "' . $title->getPrefixedDBkey() . '"';
diff --git a/includes/Revision.php b/includes/Revision.php
index 7938d88a..8a2149c0 100644
--- a/includes/Revision.php
+++ b/includes/Revision.php
@@ -53,6 +53,10 @@ class Revision {
// Get the latest revision ID from the master
$dbw = wfGetDB( DB_MASTER );
$latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
+ if ( $latest === false ) {
+ // Page does not exist
+ return null;
+ }
$conds['rev_id'] = $latest;
} else {
// Use a join to get the latest revision
@@ -363,6 +367,7 @@ class Revision {
} else {
throw new MWException( 'Revision constructor passed invalid row format.' );
}
+ $this->mUnpatrolled = NULL;
}
/**#@+
@@ -536,6 +541,27 @@ class Revision {
public function isMinor() {
return (bool)$this->mMinorEdit;
}
+
+ /**
+ * @return int rcid of the unpatrolled row, zero if there isn't one
+ */
+ public function isUnpatrolled() {
+ if( $this->mUnpatrolled !== NULL ) {
+ return $this->mUnpatrolled;
+ }
+ $dbr = wfGetDB( DB_SLAVE );
+ $this->mUnpatrolled = $dbr->selectField( 'recentchanges',
+ 'rc_id',
+ array( // Add redundant user,timestamp condition so we can use the existing index
+ 'rc_user_text' => $this->getRawUserText(),
+ 'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ),
+ 'rc_this_oldid' => $this->getId(),
+ 'rc_patrolled' => 0
+ ),
+ __METHOD__
+ );
+ return (int)$this->mUnpatrolled;
+ }
/**
* int $field one of DELETED_* bitfield constants
@@ -819,7 +845,8 @@ class Revision {
'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
'rev_deleted' => $this->mDeleted,
'rev_len' => $this->mSize,
- 'rev_parent_id' => $this->mParentId ? $this->mParentId : $this->getPreviousRevisionId( $dbw )
+ 'rev_parent_id' => is_null($this->mParentId) ?
+ $this->getPreviousRevisionId( $dbw ) : $this->mParentId
), __METHOD__
);
@@ -961,6 +988,10 @@ class Revision {
*/
static function getTimestampFromId( $title, $id ) {
$dbr = wfGetDB( DB_SLAVE );
+ // Casting fix for DB2
+ if ($id == '') {
+ $id = 0;
+ }
$conds = array( 'rev_id' => $id );
$conds['rev_page'] = $title->getArticleId();
$timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ );
diff --git a/includes/SearchEngine.php b/includes/SearchEngine.php
index 3ea0341d..e5392f7c 100644
--- a/includes/SearchEngine.php
+++ b/includes/SearchEngine.php
@@ -80,8 +80,13 @@ class SearchEngine {
if (is_null($title))
return NULL;
- if ( $title->getNamespace() == NS_SPECIAL || $title->isExternal()
- || $title->exists() ) {
+ if ( $title->getNamespace() == NS_SPECIAL || $title->isExternal() || $title->exists() ) {
+ return $title;
+ }
+
+ # See if it still otherwise has content is some sane sense
+ $article = MediaWiki::articleFromTitle( $title );
+ if( $article->hasViewableContent() ) {
return $title;
}
@@ -403,7 +408,7 @@ class SearchEngine {
if($wgMWSuggestTemplate)
return $wgMWSuggestTemplate;
else
- return $wgServer . $wgScriptPath . '/api.php?action=opensearch&search={searchTerms}&namespace={namespaces}';
+ return $wgServer . $wgScriptPath . '/api.php?action=opensearch&search={searchTerms}&namespace={namespaces}&suggest';
}
}
@@ -1165,12 +1170,12 @@ class SearchHighlighter {
continue;
}
--$contextlines;
- $pre = $wgContLang->truncate( $m[1], -$contextchars, ' ... ' );
+ $pre = $wgContLang->truncate( $m[1], -$contextchars );
if ( count( $m ) < 3 ) {
$post = '';
} else {
- $post = $wgContLang->truncate( $m[3], $contextchars, ' ... ' );
+ $post = $wgContLang->truncate( $m[3], $contextchars );
}
$found = $m[2];
diff --git a/includes/SearchIBM_DB2.php b/includes/SearchIBM_DB2.php
new file mode 100644
index 00000000..57813a73
--- /dev/null
+++ b/includes/SearchIBM_DB2.php
@@ -0,0 +1,247 @@
+<?php
+# Copyright (C) 2004 Brion Vibber <brion@pobox.com>
+# http://www.mediawiki.org/
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# http://www.gnu.org/copyleft/gpl.html
+
+/**
+ * @file
+ * @ingroup Search
+ */
+
+/**
+ * Search engine hook base class for IBM DB2
+ * @ingroup Search
+ */
+class SearchIBM_DB2 extends SearchEngine {
+ function __construct($db) {
+ $this->db = $db;
+ }
+
+ /**
+ * Perform a full text search query and return a result set.
+ *
+ * @param string $term - Raw search term
+ * @return IBM_DB2SearchResultSet
+ * @access public
+ */
+ function searchText( $term ) {
+ $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), true)));
+ return new IBM_DB2SearchResultSet($resultSet, $this->searchTerms);
+ }
+
+ /**
+ * Perform a title-only search query and return a result set.
+ *
+ * @param string $term - Raw search term
+ * @return IBM_DB2SearchResultSet
+ * @access public
+ */
+ function searchTitle($term) {
+ $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), false)));
+ return new MySQLSearchResultSet($resultSet, $this->searchTerms);
+ }
+
+
+ /**
+ * Return a partial WHERE clause to exclude redirects, if so set
+ * @return string
+ * @private
+ */
+ function queryRedirect() {
+ if ($this->showRedirects) {
+ return '';
+ } else {
+ return 'AND page_is_redirect=0';
+ }
+ }
+
+ /**
+ * Return a partial WHERE clause to limit the search to the given namespaces
+ * @return string
+ * @private
+ */
+ function queryNamespaces() {
+ if( is_null($this->namespaces) )
+ return '';
+ $namespaces = implode(',', $this->namespaces);
+ if ($namespaces == '') {
+ $namespaces = '0';
+ }
+ return 'AND page_namespace IN (' . $namespaces . ')';
+ }
+
+ /**
+ * Return a LIMIT clause to limit results on the query.
+ * @return string
+ * @private
+ */
+ function queryLimit($sql) {
+ return $this->db->limitResult($sql, $this->limit, $this->offset);
+ }
+
+ /**
+ * Does not do anything for generic search engine
+ * subclasses may define this though
+ * @return string
+ * @private
+ */
+ function queryRanking($filteredTerm, $fulltext) {
+ // requires Net Search Extender or equivalent
+ // return ' ORDER BY score(1)';
+ return '';
+ }
+
+ /**
+ * Construct the full SQL query to do the search.
+ * The guts shoulds be constructed in queryMain()
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @private
+ */
+ function getQuery( $filteredTerm, $fulltext ) {
+ return $this->queryLimit($this->queryMain($filteredTerm, $fulltext) . ' ' .
+ $this->queryRedirect() . ' ' .
+ $this->queryNamespaces() . ' ' .
+ $this->queryRanking( $filteredTerm, $fulltext ) . ' ');
+ }
+
+
+ /**
+ * Picks which field to index on, depending on what type of query.
+ * @param bool $fulltext
+ * @return string
+ */
+ function getIndexField($fulltext) {
+ return $fulltext ? 'si_text' : 'si_title';
+ }
+
+ /**
+ * Get the base part of the search query.
+ *
+ * @param string $filteredTerm
+ * @param bool $fulltext
+ * @return string
+ * @private
+ */
+ function queryMain( $filteredTerm, $fulltext ) {
+ $match = $this->parseQuery($filteredTerm, $fulltext);
+ $page = $this->db->tableName('page');
+ $searchindex = $this->db->tableName('searchindex');
+ return 'SELECT page_id, page_namespace, page_title ' .
+ "FROM $page,$searchindex " .
+ 'WHERE page_id=si_page AND ' . $match;
+ }
+
+ /** @todo document */
+ function parseQuery($filteredText, $fulltext) {
+ global $wgContLang;
+ $lc = SearchEngine::legalSearchChars();
+ $this->searchTerms = array();
+
+ # FIXME: This doesn't handle parenthetical expressions.
+ $m = array();
+ $q = array();
+
+ if (preg_match_all('/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/',
+ $filteredText, $m, PREG_SET_ORDER)) {
+ foreach($m as $terms) {
+ $q[] = $terms[1] . $wgContLang->stripForSearch($terms[2]);
+
+ if (!empty($terms[3])) {
+ $regexp = preg_quote( $terms[3], '/' );
+ if ($terms[4])
+ $regexp .= "[0-9A-Za-z_]+";
+ } else {
+ $regexp = preg_quote(str_replace('"', '', $terms[2]), '/');
+ }
+ $this->searchTerms[] = $regexp;
+ }
+ }
+
+ $searchon = $this->db->strencode(join(',', $q));
+ $field = $this->getIndexField($fulltext);
+
+ // requires Net Search Extender or equivalent
+ //return " CONTAINS($field, '$searchon') > 0 ";
+
+ return " lcase($field) LIKE lcase('%$searchon%')";
+ }
+
+ /**
+ * Create or update the search index record for the given page.
+ * Title and text should be pre-processed.
+ *
+ * @param int $id
+ * @param string $title
+ * @param string $text
+ */
+ function update($id, $title, $text) {
+ $dbw = wfGetDB(DB_MASTER);
+ $dbw->replace('searchindex',
+ array('si_page'),
+ array(
+ 'si_page' => $id,
+ 'si_title' => $title,
+ 'si_text' => $text
+ ), 'SearchIBM_DB2::update' );
+ // ?
+ //$dbw->query("CALL ctx_ddl.sync_index('si_text_idx')");
+ //$dbw->query("CALL ctx_ddl.sync_index('si_title_idx')");
+ }
+
+ /**
+ * Update a search index record's title only.
+ * Title should be pre-processed.
+ *
+ * @param int $id
+ * @param string $title
+ */
+ function updateTitle($id, $title) {
+ $dbw = wfGetDB(DB_MASTER);
+
+ $dbw->update('searchindex',
+ array('si_title' => $title),
+ array('si_page' => $id),
+ 'SearchIBM_DB2::updateTitle',
+ array());
+ }
+}
+
+/**
+ * @ingroup Search
+ */
+class IBM_DB2SearchResultSet extends SearchResultSet {
+ function __construct($resultSet, $terms) {
+ $this->mResultSet = $resultSet;
+ $this->mTerms = $terms;
+ }
+
+ function termMatches() {
+ return $this->mTerms;
+ }
+
+ function numRows() {
+ return $this->mResultSet->numRows();
+ }
+
+ function next() {
+ $row = $this->mResultSet->fetchObject();
+ if ($row === false)
+ return false;
+ return new SearchResult($row);
+ }
+}
diff --git a/includes/SearchPostgres.php b/includes/SearchPostgres.php
index 4862a44e..fa9d8420 100644
--- a/includes/SearchPostgres.php
+++ b/includes/SearchPostgres.php
@@ -70,7 +70,7 @@ class SearchPostgres extends SearchEngine {
*/
function parseQuery( $term ) {
- wfDebug( "parseQuery received: $term" );
+ wfDebug( "parseQuery received: $term \n" );
## No backslashes allowed
$term = preg_replace('/\\\/', '', $term);
@@ -122,7 +122,7 @@ class SearchPostgres extends SearchEngine {
## Quote the whole thing
$searchstring = $this->db->addQuotes($searchstring);
- wfDebug( "parseQuery returned: $searchstring" );
+ wfDebug( "parseQuery returned: $searchstring \n" );
return $searchstring;
@@ -194,7 +194,7 @@ class SearchPostgres extends SearchEngine {
$query .= $this->db->limitResult( '', $this->limit, $this->offset );
- wfDebug( "searchQuery returned: $query" );
+ wfDebug( "searchQuery returned: $query \n" );
return $query;
}
diff --git a/includes/Setup.php b/includes/Setup.php
index 859ad008..d450dfdb 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -197,6 +197,10 @@ if($wgMetaNamespace === FALSE) {
# To determine the user language, use $wgLang->getCode()
$wgContLanguageCode = $wgLanguageCode;
+# Easy to forget to falsify $wgShowIPinHeader for static caches.
+# If file cache or squid cache is on, just disable this (DWIMD).
+if( $wgUseFileCache || $wgUseSquid ) $wgShowIPinHeader = false;
+
wfProfileOut( $fname.'-misc1' );
wfProfileIn( $fname.'-memcached' );
diff --git a/includes/SiteStats.php b/includes/SiteStats.php
index ab0caa7e..9427536f 100644
--- a/includes/SiteStats.php
+++ b/includes/SiteStats.php
@@ -222,7 +222,7 @@ class SiteStatsUpdate {
if ( $updates ) {
$site_stats = $dbw->tableName( 'site_stats' );
- $sql = $dbw->limitResultForUpdate("UPDATE $site_stats SET $updates", 1);
+ $sql = "UPDATE $site_stats SET $updates";
# Need a separate transaction because this a global lock
$dbw->begin();
@@ -240,7 +240,7 @@ class SiteStatsUpdate {
__METHOD__ );
$dbw->update( 'site_stats',
array( 'ss_active_users' => intval($activeUsers) ),
- array( 'ss_row_id' => 1 ), __METHOD__, array( 'LIMIT' => 1 )
+ array( 'ss_row_id' => 1 ), __METHOD__
);
}
}
diff --git a/includes/Skin.php b/includes/Skin.php
index 636b96bf..47285acc 100644
--- a/includes/Skin.php
+++ b/includes/Skin.php
@@ -401,7 +401,7 @@ class Skin extends Linker {
$vars['wgLivepreviewMessageError'] = wfMsg( 'livepreview-error' );
}
- if($wgUseAjax && $wgAjaxWatch && $wgUser->isLoggedIn() ) {
+ if ( $wgOut->isArticleRelated() && $wgUseAjax && $wgAjaxWatch && $wgUser->isLoggedIn() ) {
$msgs = (object)array();
foreach ( array( 'watch', 'unwatch', 'watching', 'unwatching' ) as $msgName ) {
$msgs->{$msgName . 'Msg'} = wfMsg( $msgName );
@@ -758,7 +758,7 @@ END;
if( count( $wgOut->mCategoryLinks ) == 0 ) return '';
# Separator
- $sep = wfMsgHtml( 'catseparator' );
+ $sep = wfMsgExt( 'catseparator', array( 'parsemag', 'escapenoentities' ) );
// Use Unicode bidi embedding override characters,
// to make sure links don't smash each other up in ugly ways.
@@ -890,12 +890,27 @@ END;
}
/**
+ * Generate debug data HTML for displaying at the bottom of the main content
+ * area.
+ * @return String HTML containing debug data, if enabled (otherwise empty).
+ */
+ protected function generateDebugHTML() {
+ global $wgShowDebug, $wgOut;
+ if ( $wgShowDebug ) {
+ $listInternals = str_replace( "\n", "</li>\n<li>", htmlspecialchars( $wgOut->mDebugtext ) );
+ return "\n<hr>\n<strong>Debug data:</strong><ul style=\"font-family:monospace;\"><li>" .
+ $listInternals . "</li></ul>\n";
+ }
+ return '';
+ }
+
+ /**
* This gets called shortly before the </body> tag.
* @return String HTML to be put before </body>
*/
function afterContent() {
$printfooter = "<div class=\"printfooter\">\n" . $this->printFooter() . "</div>\n";
- return $printfooter . $this->doAfterContent();
+ return $printfooter . $this->generateDebugHTML() . $this->doAfterContent();
}
/**
@@ -925,20 +940,20 @@ END;
function doAfterContent() { return "</div></div>"; }
function pageTitleLinks() {
- global $wgOut, $wgTitle, $wgUser, $wgRequest;
+ global $wgOut, $wgTitle, $wgUser, $wgRequest, $wgLang;
$oldid = $wgRequest->getVal( 'oldid' );
$diff = $wgRequest->getVal( 'diff' );
$action = $wgRequest->getText( 'action' );
- $s = $this->printableLink();
+ $s[] = $this->printableLink();
$disclaimer = $this->disclaimerLink(); # may be empty
if( $disclaimer ) {
- $s .= ' | ' . $disclaimer;
+ $s[] = $disclaimer;
}
$privacy = $this->privacyLink(); # may be empty too
if( $privacy ) {
- $s .= ' | ' . $privacy;
+ $s[] = $privacy;
}
if ( $wgOut->isArticleRelated() ) {
@@ -948,12 +963,12 @@ END;
if( $image ) {
$link = htmlspecialchars( $image->getURL() );
$style = $this->getInternalLinkAttributes( $link, $name );
- $s .= " | <a href=\"{$link}\"{$style}>{$name}</a>";
+ $s[] = "<a href=\"{$link}\"{$style}>{$name}</a>";
}
}
}
if ( 'history' == $action || isset( $diff ) || isset( $oldid ) ) {
- $s .= ' | ' . $this->makeKnownLinkObj( $wgTitle,
+ $s[] .= $this->makeKnownLinkObj( $wgTitle,
wfMsg( 'currentrev' ) );
}
@@ -963,7 +978,7 @@ END;
if( !$wgTitle->equals( $wgUser->getTalkPage() ) ) {
$tl = $this->makeKnownLinkObj( $wgUser->getTalkPage(), wfMsgHtml( 'newmessageslink' ), 'redirect=no' );
$dl = $this->makeKnownLinkObj( $wgUser->getTalkPage(), wfMsgHtml( 'newmessagesdifflink' ), 'diff=cur' );
- $s.= ' | <strong>'. wfMsg( 'youhavenewmessages', $tl, $dl ) . '</strong>';
+ $s[] = '<strong>'. wfMsg( 'youhavenewmessages', $tl, $dl ) . '</strong>';
# disable caching
$wgOut->setSquidMaxage(0);
$wgOut->enableClientCache(false);
@@ -972,9 +987,9 @@ END;
$undelete = $this->getUndeleteLink();
if( !empty( $undelete ) ) {
- $s .= ' | '.$undelete;
+ $s[] = $undelete;
}
- return $s;
+ return $wgLang->pipeList( $s );
}
function getUndeleteLink() {
@@ -997,18 +1012,19 @@ END;
}
function printableLink() {
- global $wgOut, $wgFeedClasses, $wgRequest;
+ global $wgOut, $wgFeedClasses, $wgRequest, $wgLang;
$printurl = $wgRequest->escapeAppendQuery( 'printable=yes' );
- $s = "<a href=\"$printurl\">" . wfMsg( 'printableversion' ) . '</a>';
+ $s[] = "<a href=\"$printurl\" rel=\"alternate\">" . wfMsg( 'printableversion' ) . '</a>';
if( $wgOut->isSyndicated() ) {
foreach( $wgFeedClasses as $format => $class ) {
$feedurl = $wgRequest->escapeAppendQuery( "feed=$format" );
- $s .= " | <a href=\"$feedurl\">{$format}</a>";
+ $s[] = "<a href=\"$feedurl\" rel=\"alternate\" type=\"application/{$format}+xml\""
+ . " class=\"feedlink\">" . wfMsgHtml( "feed-$format" ) . "</a>";
}
}
- return $s;
+ return $wgLang->pipeList( $s );
}
function pageTitle() {
@@ -1053,7 +1069,7 @@ END;
$getlink = $this->makeKnownLinkObj( $linkObj, htmlspecialchars( $display ) );
$c++;
if ($c>1) {
- $subpages .= ' | ';
+ $subpages .= wfMsgExt( 'pipe-separator' , 'escapenoentities' );
} else {
$subpages .= '&lt; ';
}
@@ -1116,16 +1132,21 @@ END;
$ret .= $this->link( $wgUser->getUserPage(),
htmlspecialchars( $wgUser->getName() ) );
$ret .= " ($talkLink)<br />";
- $ret .= $this->link(
- SpecialPage::getTitleFor( 'Userlogout' ), wfMsg( 'logout' ),
- array(), array( 'returnto' => $returnTo )
- );
- $ret .= ' | ' . $this->specialLink( 'preferences' );
- }
- $ret .= ' | ' . $this->link(
- Title::newFromText( wfMsgForContent( 'helppage' ) ),
- wfMsg( 'help' )
- );
+ $ret .= $wgLang->pipeList( array(
+ $this->link(
+ SpecialPage::getTitleFor( 'Userlogout' ), wfMsg( 'logout' ),
+ array(), array( 'returnto' => $returnTo )
+ ),
+ $this->specialLink( 'preferences' ),
+ ) );
+ }
+ $ret = $wgLang->pipeList( array(
+ $ret,
+ $this->link(
+ Title::newFromText( wfMsgForContent( 'helppage' ) ),
+ wfMsg( 'help' )
+ ),
+ ) );
return $ret;
}
@@ -1140,16 +1161,22 @@ END;
}
function searchForm() {
- global $wgRequest;
+ global $wgRequest, $wgUseTwoButtonsSearchForm;
$search = $wgRequest->getText( 'search' );
$s = '<form id="searchform'.$this->searchboxes.'" name="search" class="inline" method="post" action="'
. $this->escapeSearchLink() . "\">\n"
. '<input type="text" id="searchInput'.$this->searchboxes.'" name="search" size="19" value="'
. htmlspecialchars(substr($search,0,256)) . "\" />\n"
- . '<input type="submit" name="go" value="' . wfMsg ('searcharticle') . '" />&nbsp;'
- . '<input type="submit" name="fulltext" value="' . wfMsg ('searchbutton') . "\" />\n</form>";
-
+ . '<input type="submit" name="go" value="' . wfMsg ('searcharticle') . '" />';
+
+ if ($wgUseTwoButtonsSearchForm)
+ $s .= '&nbsp;<input type="submit" name="fulltext" value="' . wfMsg ('searchbutton') . "\" />\n";
+ else
+ $s .= ' <a href="' . $this->escapeSearchLink() . '" rel="search">' . wfMsg ('powersearch-legend') . "</a>\n";
+
+ $s .= '</form>';
+
// Ensure unique id's for search boxes made after the first
$this->searchboxes = $this->searchboxes == '' ? 2 : $this->searchboxes + 1;
@@ -1158,23 +1185,29 @@ END;
function topLinks() {
global $wgOut;
- $sep = " |\n";
- $s = $this->mainPageLink() . $sep
- . $this->specialLink( 'recentchanges' );
+ $s = array(
+ $this->mainPageLink(),
+ $this->specialLink( 'recentchanges' )
+ );
if ( $wgOut->isArticleRelated() ) {
- $s .= $sep . $this->editThisPage()
- . $sep . $this->historyLink();
+ $s[] = $this->editThisPage();
+ $s[] = $this->historyLink();
}
# Many people don't like this dropdown box
- #$s .= $sep . $this->specialPagesList();
+ #$s[] = $this->specialPagesList();
- $s .= $this->variantLinks();
+ if( $this->variantLinks() ) {
+ $s[] = $this->variantLinks();
+ }
- $s .= $this->extensionTabLinks();
+ if( $this->extensionTabLinks() ) {
+ $s[] = $this->extensionTabLinks();
+ }
- return $s;
+ // FIXME: Is using Language::pipeList impossible here? Do not quite understand the use of the newline
+ return implode( $s, wfMsgExt( 'pipe-separator' , 'escapenoentities' ) . "\n" );
}
/**
@@ -1185,14 +1218,23 @@ END;
*/
function extensionTabLinks() {
$tabs = array();
- $s = '';
+ $out = '';
+ $s = array();
wfRunHooks( 'SkinTemplateTabs', array( $this, &$tabs ) );
foreach( $tabs as $tab ) {
- $s .= ' | ' . Xml::element( 'a',
+ $s[] = Xml::element( 'a',
array( 'href' => $tab['href'] ),
$tab['text'] );
}
- return $s;
+
+ if( count( $s ) ) {
+ global $wgLang;
+
+ $out = wfMsgExt( 'pipe-separator' , 'escapenoentities' );
+ $out .= $wgLang->pipeList( $s );
+ }
+
+ return $out;
}
/**
@@ -1202,14 +1244,14 @@ END;
function variantLinks() {
$s = '';
/* show links to different language variants */
- global $wgDisableLangConversion, $wgContLang, $wgTitle;
+ global $wgDisableLangConversion, $wgLang, $wgContLang, $wgTitle;
$variants = $wgContLang->getVariants();
if( !$wgDisableLangConversion && sizeof( $variants ) > 1 ) {
foreach( $variants as $code ) {
$varname = $wgContLang->getVariantname( $code );
if( $varname == 'disable' )
continue;
- $s .= ' | <a href="' . $wgTitle->escapeLocalUrl( 'variant=' . $code ) . '">' . htmlspecialchars( $varname ) . '</a>';
+ $s = $wgLang->pipeList( array( $s, '<a href="' . $wgTitle->escapeLocalUrl( 'variant=' . $code ) . '">' . htmlspecialchars( $varname ) . '</a>' ) );
}
}
return $s;
@@ -1217,21 +1259,21 @@ END;
function bottomLinks() {
global $wgOut, $wgUser, $wgTitle, $wgUseTrackbacks;
- $sep = " |\n";
+ $sep = wfMsgExt( 'pipe-separator' , 'escapenoentities' ) . "\n";
$s = '';
if ( $wgOut->isArticleRelated() ) {
- $s .= '<strong>' . $this->editThisPage() . '</strong>';
+ $element[] = '<strong>' . $this->editThisPage() . '</strong>';
if ( $wgUser->isLoggedIn() ) {
- $s .= $sep . $this->watchThisPage();
+ $element[] = $this->watchThisPage();
}
- $s .= $sep . $this->talkLink()
- . $sep . $this->historyLink()
- . $sep . $this->whatLinksHere()
- . $sep . $this->watchPageLinksLink();
+ $element[] = $this->talkLink();
+ $element[] = $this->historyLink();
+ $element[] = $this->whatLinksHere();
+ $element[] = $this->watchPageLinksLink();
if ($wgUseTrackbacks)
- $s .= $sep . $this->trackbackLink();
+ $element[] = $this->trackbackLink();
if ( $wgTitle->getNamespace() == NS_USER
|| $wgTitle->getNamespace() == NS_USER_TALK )
@@ -1241,12 +1283,15 @@ END;
$ip=User::isIP($wgTitle->getText());
if($id || $ip) { # both anons and non-anons have contri list
- $s .= $sep . $this->userContribsLink();
+ $element[] = $this->userContribsLink();
}
if( $this->showEmailUser( $id ) ) {
- $s .= $sep . $this->emailUserLink();
+ $element[] = $this->emailUserLink();
}
}
+
+ $s = implode( $element, $sep );
+
if ( $wgTitle->getArticleId() ) {
$s .= "\n<br />";
if($wgUser->isAllowed('delete')) { $s .= $this->deleteThisPage(); }
@@ -1255,6 +1300,7 @@ END;
}
$s .= "<br />\n" . $this->otherLanguages();
}
+
return $s;
}
@@ -1566,8 +1612,8 @@ END;
function historyLink() {
global $wgTitle;
- return $this->makeKnownLinkObj( $wgTitle,
- wfMsg( 'history' ), 'action=history' );
+ return $this->link( $wgTitle, wfMsg( 'history' ),
+ array( 'rel' => 'archives' ), array( 'action' => 'history' ) );
}
function whatLinksHere() {
@@ -1632,11 +1678,11 @@ END;
return '';
}
- $s = wfMsg( 'otherlanguages' ) . ': ';
+ $s = wfMsg( 'otherlanguages' ) . wfMsg( 'colon-separator' );
$first = true;
if($wgContLang->isRTL()) $s .= '<span dir="LTR">';
foreach( $a as $l ) {
- if ( ! $first ) { $s .= ' | '; }
+ if ( ! $first ) { $s .= wfMsgExt( 'pipe-separator' , 'escapenoentities' ); }
$first = false;
$nt = Title::newFromText( $l );
diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php
index 4f13571a..4317a93e 100644
--- a/includes/SkinTemplate.php
+++ b/includes/SkinTemplate.php
@@ -138,7 +138,7 @@ class SkinTemplate extends Skin {
global $wgScript, $wgStylePath, $wgContLanguageCode;
global $wgMimeType, $wgJsMimeType, $wgOutputEncoding, $wgRequest;
global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces;
- global $wgDisableCounters, $wgLogo, $action, $wgFeedClasses, $wgHideInterlanguageLinks;
+ global $wgDisableCounters, $wgLogo, $wgHideInterlanguageLinks;
global $wgMaxCredits, $wgShowCreditsIfMax;
global $wgPageShowWatchingUsers;
global $wgUseTrackbacks, $wgUseSiteJs;
@@ -148,6 +148,7 @@ class SkinTemplate extends Skin {
$oldid = $wgRequest->getVal( 'oldid' );
$diff = $wgRequest->getVal( 'diff' );
+ $action = $wgRequest->getVal( 'action', 'view' );
wfProfileIn( __METHOD__."-init" );
$this->initPage( $out );
@@ -258,6 +259,7 @@ class SkinTemplate extends Skin {
$tpl->set( "helppage", wfMsg('helppage'));
*/
$tpl->set( 'searchaction', $this->escapeSearchLink() );
+ $tpl->set( 'searchtitle', SpecialPage::getTitleFor('search')->getPrefixedDBKey() );
$tpl->set( 'search', trim( $wgRequest->getVal( 'search' ) ) );
$tpl->setRef( 'stylepath', $wgStylePath );
$tpl->setRef( 'articlepath', $wgArticlePath );
@@ -401,7 +403,7 @@ class SkinTemplate extends Skin {
$tpl->set( 'bottomscripts', $this->bottomScripts() );
$printfooter = "<div class=\"printfooter\">\n" . $this->printSource() . "</div>\n";
- $out->mBodytext .= $printfooter ;
+ $out->mBodytext .= $printfooter . $this->generateDebugHTML();
$tpl->setRef( 'bodytext', $out->mBodytext );
# Language links
@@ -449,7 +451,7 @@ class SkinTemplate extends Skin {
// original version by hansm
if( !wfRunHooks( 'SkinTemplateOutputPageBeforeExec', array( &$this, &$tpl ) ) ) {
- wfDebug( __METHOD__ . ': Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!' );
+ wfDebug( __METHOD__ . ": Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!\n" );
}
// allow extensions adding stuff after the page content.
@@ -647,12 +649,12 @@ class SkinTemplate extends Skin {
* @private
*/
function buildContentActionUrls() {
- global $wgContLang, $wgLang, $wgOut;
+ global $wgContLang, $wgLang, $wgOut, $wgUser, $wgRequest;
+
wfProfileIn( __METHOD__ );
- global $wgUser, $wgRequest;
- $action = $wgRequest->getText( 'action' );
- $section = $wgRequest->getText( 'section' );
+ $action = $wgRequest->getVal( 'action', 'view' );
+ $section = $wgRequest->getVal( 'section' );
$content_actions = array();
$prevent_active_tabs = false ;
@@ -689,11 +691,13 @@ class SkinTemplate extends Skin {
);
if ( $istalk || $wgOut->showNewSectionLink() ) {
- $content_actions['addsection'] = array(
- 'class' => $section == 'new'?'selected':false,
- 'text' => wfMsg('addsection'),
- 'href' => $this->mTitle->getLocalUrl( 'action=edit&section=new' )
- );
+ if ( !$wgOut->forceHideNewSectionLink() ) {
+ $content_actions['addsection'] = array(
+ 'class' => $section == 'new' ? 'selected' : false,
+ 'text' => wfMsg('addsection'),
+ 'href' => $this->mTitle->getLocalUrl( 'action=edit&section=new' )
+ );
+ }
}
} elseif ( $this->mTitle->isKnown() ) {
$content_actions['viewsource'] = array(
@@ -710,7 +714,8 @@ class SkinTemplate extends Skin {
$content_actions['history'] = array(
'class' => ($action == 'history') ? 'selected' : false,
'text' => wfMsg('history_short'),
- 'href' => $this->mTitle->getLocalUrl( 'action=history')
+ 'href' => $this->mTitle->getLocalUrl( 'action=history' ),
+ 'rel' => 'archives',
);
if( $wgUser->isAllowed('delete') ) {
@@ -848,7 +853,7 @@ class SkinTemplate extends Skin {
wfProfileIn( __METHOD__ );
- $action = $wgRequest->getText( 'action' );
+ $action = $wgRequest->getVal( 'action', 'view' );
$nav_urls = array();
$nav_urls['mainpage'] = array( 'href' => self::makeMainPageUrl() );
@@ -871,7 +876,7 @@ class SkinTemplate extends Skin {
// A print stylesheet is attached to all pages, but nobody ever
// figures that out. :) Add a link...
- if( $this->iscontent && ($action == '' || $action == 'view' || $action == 'purge' ) ) {
+ if( $this->iscontent && ( $action == 'view' || $action == 'purge' ) ) {
$nav_urls['print'] = array(
'text' => wfMsg( 'printableversion' ),
'href' => $wgRequest->appendQuery( 'printable=yes' )
@@ -965,10 +970,11 @@ class SkinTemplate extends Skin {
* @private
*/
function setupUserJs( $allowUserJs ) {
+ global $wgRequest, $wgJsMimeType;
+
wfProfileIn( __METHOD__ );
- global $wgRequest, $wgJsMimeType;
- $action = $wgRequest->getText('action');
+ $action = $wgRequest->getVal( 'action', 'view' );
if( $allowUserJs && $this->loggedin ) {
if( $this->mTitle->isJsSubpage() and $this->userCanPreview( $action ) ) {
diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php
index 00eacd1e..31b43839 100644
--- a/includes/SpecialPage.php
+++ b/includes/SpecialPage.php
@@ -80,92 +80,112 @@ class SpecialPage
** array( 'SpecialRedirectToSpecial', name, page to redirect to, special page param, ... )
*/
static public $mList = array(
- 'DoubleRedirects' => array( 'SpecialPage', 'DoubleRedirects' ),
+ # Maintenance Reports
'BrokenRedirects' => array( 'SpecialPage', 'BrokenRedirects' ),
+ 'Deadendpages' => array( 'SpecialPage', 'Deadendpages' ),
+ 'DoubleRedirects' => array( 'SpecialPage', 'DoubleRedirects' ),
+ 'Longpages' => array( 'SpecialPage', 'Longpages' ),
+ 'Ancientpages' => array( 'SpecialPage', 'Ancientpages' ),
+ 'Lonelypages' => array( 'SpecialPage', 'Lonelypages' ),
+ 'Fewestrevisions' => array( 'SpecialPage', 'Fewestrevisions' ),
+ 'Withoutinterwiki' => array( 'SpecialPage', 'Withoutinterwiki' ),
+ 'Protectedpages' => array( 'SpecialPage', 'Protectedpages' ),
+ 'Protectedtitles' => array( 'SpecialPage', 'Protectedtitles' ),
+ 'Shortpages' => array( 'SpecialPage', 'Shortpages' ),
+ 'Uncategorizedcategories' => array( 'SpecialPage', 'Uncategorizedcategories' ),
+ 'Uncategorizedimages' => array( 'SpecialPage', 'Uncategorizedimages' ),
+ 'Uncategorizedpages' => array( 'SpecialPage', 'Uncategorizedpages' ),
+ 'Uncategorizedtemplates' => array( 'SpecialPage', 'Uncategorizedtemplates' ),
+ 'Unusedcategories' => array( 'SpecialPage', 'Unusedcategories' ),
+ 'Unusedimages' => array( 'SpecialPage', 'Unusedimages' ),
+ 'Unusedtemplates' => array( 'SpecialPage', 'Unusedtemplates' ),
+ 'Unwatchedpages' => array( 'SpecialPage', 'Unwatchedpages', 'unwatchedpages' ),
+ 'Wantedcategories' => array( 'SpecialPage', 'Wantedcategories' ),
+ 'Wantedfiles' => array( 'SpecialPage', 'Wantedfiles' ),
+ 'Wantedpages' => array( 'IncludableSpecialPage', 'Wantedpages' ),
+ 'Wantedtemplates' => array( 'SpecialPage', 'Wantedtemplates' ),
+
+ # List of pages
+ 'Allpages' => 'SpecialAllpages',
+ 'Prefixindex' => 'SpecialPrefixindex',
+ 'Categories' => array( 'SpecialPage', 'Categories' ),
'Disambiguations' => array( 'SpecialPage', 'Disambiguations' ),
+ 'Listredirects' => array( 'SpecialPage', 'Listredirects' ),
- 'Userlogin' => array( 'SpecialPage', 'Userlogin' ),
- 'Userlogout' => array( 'UnlistedSpecialPage', 'Userlogout' ),
+ # Login/create account
+ 'Userlogin' => array( 'SpecialPage', 'Userlogin' ),
'CreateAccount' => array( 'SpecialRedirectToSpecial', 'CreateAccount', 'Userlogin', 'signup', array( 'uselang' ) ),
- 'Preferences' => array( 'SpecialPage', 'Preferences' ),
- 'Watchlist' => array( 'SpecialPage', 'Watchlist' ),
- 'Resetpass' => 'SpecialResetpass',
+ # Users and rights
+ 'Blockip' => array( 'SpecialPage', 'Blockip', 'block' ),
+ 'Ipblocklist' => array( 'SpecialPage', 'Ipblocklist' ),
+ 'Resetpass' => 'SpecialResetpass',
+ 'DeletedContributions' => 'DeletedContributionsPage',
+ 'Preferences' => array( 'SpecialPage', 'Preferences' ),
+ 'Contributions' => 'SpecialContributions',
+ 'Listgrouprights' => 'SpecialListGroupRights',
+ 'Listusers' => array( 'SpecialPage', 'Listusers' ),
+ 'Userrights' => 'UserrightsPage',
+ # Recent changes and logs
+ 'Newimages' => array( 'IncludableSpecialPage', 'Newimages' ),
+ 'Log' => array( 'SpecialPage', 'Log' ),
+ 'Watchlist' => array( 'SpecialPage', 'Watchlist' ),
+ 'Newpages' => 'SpecialNewpages',
'Recentchanges' => 'SpecialRecentchanges',
- 'Upload' => array( 'SpecialPage', 'Upload' ),
+ 'Recentchangeslinked' => 'SpecialRecentchangeslinked',
+ 'Tags' => 'SpecialTags',
+
+ # Media reports and uploads
'Listfiles' => array( 'SpecialPage', 'Listfiles' ),
- 'Newimages' => array( 'IncludableSpecialPage', 'Newimages' ),
- 'Listusers' => array( 'SpecialPage', 'Listusers' ),
- 'Listgrouprights' => 'SpecialListGroupRights',
- 'DeletedContributions' => 'DeletedContributionsPage',
+ 'Filepath' => array( 'SpecialPage', 'Filepath' ),
+ 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ),
+ 'FileDuplicateSearch' => array( 'SpecialPage', 'FileDuplicateSearch' ),
+ 'Upload' => array( 'SpecialPage', 'Upload' ),
+
+ # Wiki data and tools
'Statistics' => 'SpecialStatistics',
+ 'Allmessages' => array( 'SpecialPage', 'Allmessages' ),
+ 'Version' => 'SpecialVersion',
+ 'Lockdb' => array( 'SpecialPage', 'Lockdb', 'siteadmin' ),
+ 'Unlockdb' => array( 'SpecialPage', 'Unlockdb', 'siteadmin' ),
+
+ # Redirecting special pages
+ 'LinkSearch' => array( 'SpecialPage', 'LinkSearch' ),
'Randompage' => 'Randompage',
- 'Lonelypages' => array( 'SpecialPage', 'Lonelypages' ),
- 'Uncategorizedpages' => array( 'SpecialPage', 'Uncategorizedpages' ),
- 'Uncategorizedcategories' => array( 'SpecialPage', 'Uncategorizedcategories' ),
- 'Uncategorizedimages' => array( 'SpecialPage', 'Uncategorizedimages' ),
- 'Uncategorizedtemplates' => array( 'SpecialPage', 'Uncategorizedtemplates' ),
- 'Unusedcategories' => array( 'SpecialPage', 'Unusedcategories' ),
- 'Unusedimages' => array( 'SpecialPage', 'Unusedimages' ),
- 'Wantedpages' => array( 'IncludableSpecialPage', 'Wantedpages' ),
- 'Wantedcategories' => array( 'SpecialPage', 'Wantedcategories' ),
- 'Wantedfiles' => array( 'SpecialPage', 'Wantedfiles' ),
- 'Wantedtemplates' => array( 'SpecialPage', 'Wantedtemplates' ),
- 'Mostlinked' => array( 'SpecialPage', 'Mostlinked' ),
+ 'Randomredirect' => 'SpecialRandomredirect',
+
+ # High use pages
'Mostlinkedcategories' => array( 'SpecialPage', 'Mostlinkedcategories' ),
+ 'Mostimages' => array( 'SpecialPage', 'Mostimages' ),
+ 'Mostlinked' => array( 'SpecialPage', 'Mostlinked' ),
'Mostlinkedtemplates' => array( 'SpecialPage', 'Mostlinkedtemplates' ),
'Mostcategories' => array( 'SpecialPage', 'Mostcategories' ),
- 'Mostimages' => array( 'SpecialPage', 'Mostimages' ),
'Mostrevisions' => array( 'SpecialPage', 'Mostrevisions' ),
- 'Fewestrevisions' => array( 'SpecialPage', 'Fewestrevisions' ),
- 'Shortpages' => array( 'SpecialPage', 'Shortpages' ),
- 'Longpages' => array( 'SpecialPage', 'Longpages' ),
- 'Newpages' => 'SpecialNewpages',
- 'Ancientpages' => array( 'SpecialPage', 'Ancientpages' ),
- 'Deadendpages' => array( 'SpecialPage', 'Deadendpages' ),
- 'Protectedpages' => array( 'SpecialPage', 'Protectedpages' ),
- 'Protectedtitles' => array( 'SpecialPage', 'Protectedtitles' ),
- 'Allpages' => 'SpecialAllpages',
- 'Prefixindex' => 'SpecialPrefixindex',
- 'Ipblocklist' => array( 'SpecialPage', 'Ipblocklist' ),
- 'Specialpages' => array( 'UnlistedSpecialPage', 'Specialpages' ),
- 'Contributions' => 'SpecialContributions',
- 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ),
+
+ # Page tools
+ 'Export' => 'SpecialExport',
+ 'Import' => 'SpecialImport',
+ 'Undelete' => array( 'SpecialPage', 'Undelete', 'deletedhistory' ),
'Whatlinkshere' => array( 'SpecialPage', 'Whatlinkshere' ),
- 'LinkSearch' => array( 'SpecialPage', 'LinkSearch' ),
- 'Recentchangeslinked' => 'SpecialRecentchangeslinked',
- 'Movepage' => array( 'UnlistedSpecialPage', 'Movepage' ),
- 'Blockme' => array( 'UnlistedSpecialPage', 'Blockme' ),
+ 'MergeHistory' => array( 'SpecialPage', 'MergeHistory', 'mergehistory' ),
+
+ # Other
'Booksources' => 'SpecialBookSources',
- 'Categories' => array( 'SpecialPage', 'Categories' ),
- 'Export' => array( 'SpecialPage', 'Export' ),
- 'Version' => 'SpecialVersion',
+
+ # Unlisted / redirects
'Blankpage' => array( 'UnlistedSpecialPage', 'Blankpage' ),
- 'Allmessages' => array( 'SpecialPage', 'Allmessages' ),
- 'Log' => array( 'SpecialPage', 'Log' ),
- 'Blockip' => array( 'SpecialPage', 'Blockip', 'block' ),
- 'Undelete' => array( 'SpecialPage', 'Undelete', 'deletedhistory' ),
- 'Import' => 'SpecialImport',
- 'Lockdb' => array( 'SpecialPage', 'Lockdb', 'siteadmin' ),
- 'Unlockdb' => array( 'SpecialPage', 'Unlockdb', 'siteadmin' ),
- 'Userrights' => 'UserrightsPage',
- 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ),
- 'FileDuplicateSearch' => array( 'SpecialPage', 'FileDuplicateSearch' ),
- 'Unwatchedpages' => array( 'SpecialPage', 'Unwatchedpages', 'unwatchedpages' ),
- 'Listredirects' => array( 'SpecialPage', 'Listredirects' ),
- 'Revisiondelete' => array( 'UnlistedSpecialPage', 'Revisiondelete', 'deleterevision' ),
- 'Unusedtemplates' => array( 'SpecialPage', 'Unusedtemplates' ),
- 'Randomredirect' => 'SpecialRandomredirect',
- 'Withoutinterwiki' => array( 'SpecialPage', 'Withoutinterwiki' ),
- 'Filepath' => array( 'SpecialPage', 'Filepath' ),
-
- 'Mypage' => array( 'SpecialMypage' ),
- 'Mytalk' => array( 'SpecialMytalk' ),
- 'Mycontributions' => array( 'SpecialMycontributions' ),
+ 'Blockme' => array( 'UnlistedSpecialPage', 'Blockme' ),
+ 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ),
'Listadmins' => array( 'SpecialRedirectToSpecial', 'Listadmins', 'Listusers', 'sysop' ),
- 'MergeHistory' => array( 'SpecialPage', 'MergeHistory', 'mergehistory' ),
'Listbots' => array( 'SpecialRedirectToSpecial', 'Listbots', 'Listusers', 'bot' ),
+ 'Movepage' => array( 'UnlistedSpecialPage', 'Movepage' ),
+ 'Mycontributions' => array( 'SpecialMycontributions' ),
+ 'Mypage' => array( 'SpecialMypage' ),
+ 'Mytalk' => array( 'SpecialMytalk' ),
+ 'Revisiondelete' => 'SpecialRevisionDelete',
+ 'Specialpages' => array( 'UnlistedSpecialPage', 'Specialpages' ),
+ 'Userlogout' => array( 'UnlistedSpecialPage', 'Userlogout' ),
);
static public $mAliases;
@@ -695,7 +715,9 @@ class SpecialPage
* pages?
*/
public function isRestricted() {
- return $this->mRestriction != '';
+ global $wgGroupPermissions;
+ // DWIM: If all anons can do something, then it is not restricted
+ return $this->mRestriction != '' && empty($wgGroupPermissions['*'][$this->mRestriction]);
}
/**
@@ -752,13 +774,25 @@ class SpecialPage
}
}
- function outputHeader() {
+ /**
+ * Outputs a summary message on top of special pages
+ * Per default the message key is the canonical name of the special page
+ * May be overriden, i.e. by extensions to stick with the naming conventions
+ * for message keys: 'extensionname-xxx'
+ *
+ * @param string message key of the summary
+ */
+ function outputHeader( $summaryMessageKey = '' ) {
global $wgOut, $wgContLang;
- $msg = $wgContLang->lc( $this->name() ) . '-summary';
+ if( $summaryMessageKey == '' ) {
+ $msg = $wgContLang->lc( $this->name() ) . '-summary';
+ } else {
+ $msg = $summaryMessageKey;
+ }
$out = wfMsgNoTrans( $msg );
if ( ! wfEmptyMsg( $msg, $out ) and $out !== '' and ! $this->including() ) {
- $wgOut->addWikiMsg( $msg );
+ $wgOut->wrapWikiMsg( "<div class='mw-specialpage-summary'>\n$1</div>", $msg );
}
}
diff --git a/includes/SquidUpdate.php b/includes/SquidUpdate.php
index c8497a83..b1f01924 100644
--- a/includes/SquidUpdate.php
+++ b/includes/SquidUpdate.php
@@ -52,13 +52,17 @@ class SquidUpdate {
return new SquidUpdate( $blurlArr );
}
- static function newFromTitles( &$titles, $urlArr = array() ) {
+ /**
+ * Create a SquidUpdate from an array of Title objects, or a TitleArray object
+ */
+ static function newFromTitles( $titles, $urlArr = array() ) {
global $wgMaxSquidPurgeTitles;
- if ( count( $titles ) > $wgMaxSquidPurgeTitles ) {
- $titles = array_slice( $titles, 0, $wgMaxSquidPurgeTitles );
- }
+ $i = 0;
foreach ( $titles as $title ) {
$urlArr[] = $title->getInternalURL();
+ if ( $i++ > $wgMaxSquidPurgeTitles ) {
+ break;
+ }
}
return new SquidUpdate( $urlArr );
}
diff --git a/includes/StreamFile.php b/includes/StreamFile.php
index 4abd7364..bdd2a2e5 100644
--- a/includes/StreamFile.php
+++ b/includes/StreamFile.php
@@ -48,6 +48,7 @@ function wfStreamFile( $fname, $headers = array() ) {
$modsince = preg_replace( '/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
$sinceTime = strtotime( $modsince );
if ( $stat['mtime'] <= $sinceTime ) {
+ ini_set('zlib.output_compression', 0);
header( "HTTP/1.0 304 Not Modified" );
return;
}
diff --git a/includes/StubObject.php b/includes/StubObject.php
index e27f0b25..f1847a39 100644
--- a/includes/StubObject.php
+++ b/includes/StubObject.php
@@ -154,7 +154,7 @@ class StubUserLang extends StubObject {
}
# Validate $code
- if( empty( $code ) || !preg_match( '/^[a-z-]+$/', $code ) ) {
+ if( empty( $code ) || !preg_match( '/^[a-z-]+$/', $code ) || ( $code === 'qqq' ) ) {
wfDebug( "Invalid user language code\n" );
$code = $wgContLanguageCode;
}
diff --git a/includes/Title.php b/includes/Title.php
index 515a3b65..f6c0d5de 100644
--- a/includes/Title.php
+++ b/includes/Title.php
@@ -69,6 +69,7 @@ class Title {
var $mLength = -1; ///< The page length, 0 for special pages
var $mRedirect = null; ///< Is the article at this title a redirect?
var $mNotificationTimestamp = array(); ///< Associative array of user ID -> timestamp/false
+ var $mBacklinkCache = null; ///< Cache of links to this title
//@}
@@ -294,11 +295,77 @@ class Title {
/**
* Extract a redirect destination from a string and return the
* Title, or null if the text doesn't contain a valid redirect
+ * This will only return the very next target, useful for
+ * the redirect table and other checks that don't need full recursion
*
- * @param $text \type{String} Text with possible redirect
+ * @param $text \type{\string} Text with possible redirect
* @return \type{Title} The corresponding Title
*/
public static function newFromRedirect( $text ) {
+ return self::newFromRedirectInternal( $text );
+ }
+
+ /**
+ * Extract a redirect destination from a string and return the
+ * Title, or null if the text doesn't contain a valid redirect
+ * This will recurse down $wgMaxRedirects times or until a non-redirect target is hit
+ * in order to provide (hopefully) the Title of the final destination instead of another redirect
+ *
+ * @param $text \type{\string} Text with possible redirect
+ * @return \type{Title} The corresponding Title
+ */
+ public static function newFromRedirectRecurse( $text ) {
+ $titles = self::newFromRedirectArray( $text );
+ return $titles ? array_pop( $titles ) : null;
+ }
+
+ /**
+ * Extract a redirect destination from a string and return an
+ * array of Titles, or null if the text doesn't contain a valid redirect
+ * The last element in the array is the final destination after all redirects
+ * have been resolved (up to $wgMaxRedirects times)
+ *
+ * @param $text \type{\string} Text with possible redirect
+ * @return \type{\array} Array of Titles, with the destination last
+ */
+ public static function newFromRedirectArray( $text ) {
+ global $wgMaxRedirects;
+ // are redirects disabled?
+ if( $wgMaxRedirects < 1 )
+ return null;
+ $title = self::newFromRedirectInternal( $text );
+ if( is_null( $title ) )
+ return null;
+ // recursive check to follow double redirects
+ $recurse = $wgMaxRedirects;
+ $titles = array( $title );
+ while( --$recurse > 0 ) {
+ if( $title->isRedirect() ) {
+ $article = new Article( $title, 0 );
+ $newtitle = $article->getRedirectTarget();
+ } else {
+ break;
+ }
+ // Redirects to some special pages are not permitted
+ if( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) {
+ // the new title passes the checks, so make that our current title so that further recursion can be checked
+ $title = $newtitle;
+ $titles[] = $newtitle;
+ } else {
+ break;
+ }
+ }
+ return $titles;
+ }
+
+ /**
+ * Really extract the redirect destination
+ * Do not call this function directly, use one of the newFromRedirect* functions above
+ *
+ * @param $text \type{\string} Text with possible redirect
+ * @return \type{Title} The corresponding Title
+ */
+ protected static function newFromRedirectInternal( $text ) {
$redir = MagicWord::get( 'redirect' );
$text = trim($text);
if( $redir->matchStartAndRemove( $text ) ) {
@@ -316,13 +383,11 @@ class Title {
$m[1] = urldecode( ltrim( $m[1], ':' ) );
}
$title = Title::newFromText( $m[1] );
- // Redirects to some special pages are not permitted
- if( $title instanceof Title
- && !$title->isSpecial( 'Userlogout' )
- && !$title->isSpecial( 'Filepath' ) )
- {
- return $title;
+ // If the title is a redirect to bad special pages or is invalid, return null
+ if( !$title instanceof Title || !$title->isValidRedirectTarget() ) {
+ return null;
}
+ return $title;
}
}
return null;
@@ -802,19 +867,21 @@ class Title {
* @return \type{\string} the URL
*/
public function getLinkUrl( $query = array(), $variant = false ) {
+ wfProfileIn( __METHOD__ );
if( !is_array( $query ) ) {
+ wfProfileOut( __METHOD__ );
throw new MWException( 'Title::getLinkUrl passed a non-array for '.
'$query' );
}
if( $this->isExternal() ) {
- return $this->getFullURL( $query );
- } elseif( $this->getPrefixedText() === ''
- and $this->getFragment() !== '' ) {
- return $this->getFragmentForURL();
+ $ret = $this->getFullURL( $query );
+ } elseif( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) {
+ $ret = $this->getFragmentForURL();
} else {
- return $this->getLocalURL( $query, $variant )
- . $this->getFragmentForURL();
+ $ret = $this->getLocalURL( $query, $variant ) . $this->getFragmentForURL();
}
+ wfProfileOut( __METHOD__ );
+ return $ret;
}
/**
@@ -992,7 +1059,7 @@ class Title {
*/
public function userCan( $action, $doExpensiveQueries = true ) {
global $wgUser;
- return ( $this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries ) === array());
+ return ($this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries, true ) === array());
}
/**
@@ -1024,7 +1091,7 @@ class Title {
}
// Edit blocks should not affect reading. Account creation blocks handled at userlogin.
- if ( $user->isBlockedFrom( $this ) && $action != 'read' && $action != 'createaccount' ) {
+ if ( $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) ) {
$block = $user->mBlock;
// This is from OutputPage::blockedPage
@@ -1094,19 +1161,73 @@ class Title {
* @param $action \type{\string} action that permission needs to be checked for
* @param $user \type{User} user to check
* @param $doExpensiveQueries \type{\bool} Set this to false to avoid doing unnecessary queries.
+ * @param $short \type{\bool} Set this to true to stop after the first permission error.
* @return \type{\array} Array of arrays of the arguments to wfMsg to explain permissions problems.
*/
- private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true ) {
+ private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries=true, $short=false ) {
wfProfileIn( __METHOD__ );
$errors = array();
+ // First stop is permissions checks, which fail most often, and which are easiest to test.
+ if ( $action == 'move' ) {
+ if( !$user->isAllowed( 'move-rootuserpages' )
+ && $this->getNamespace() == NS_USER && !$this->isSubpage() )
+ {
+ // Show user page-specific message only if the user can move other pages
+ $errors[] = array( 'cant-move-user-page' );
+ }
+
+ // Check if user is allowed to move files if it's a file
+ if( $this->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
+ $errors[] = array( 'movenotallowedfile' );
+ }
+
+ if( !$user->isAllowed( 'move' ) ) {
+ // User can't move anything
+ $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
+ }
+ } elseif ( $action == 'create' ) {
+ if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
+ ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) )
+ {
+ $errors[] = $user->isAnon() ? array ('nocreatetext') : array ('nocreate-loggedin');
+ }
+ } elseif( $action == 'move-target' ) {
+ if( !$user->isAllowed( 'move' ) ) {
+ // User can't move anything
+ $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
+ } elseif( !$user->isAllowed( 'move-rootuserpages' )
+ && $this->getNamespace() == NS_USER && !$this->isSubpage() )
+ {
+ // Show user page-specific message only if the user can move other pages
+ $errors[] = array( 'cant-move-to-user-page' );
+ }
+ } elseif( !$user->isAllowed( $action ) ) {
+ $return = null;
+ $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
+ User::getGroupsWithPermission( $action ) );
+ if( $groups ) {
+ $return = array( 'badaccess-groups',
+ array( implode( ', ', $groups ), count( $groups ) ) );
+ } else {
+ $return = array( "badaccess-group0" );
+ }
+ $errors[] = $return;
+ }
+
+ # Short-circuit point
+ if( $short && count($errors) > 0 ) {
+ wfProfileOut( __METHOD__ );
+ return $errors;
+ }
+
// Use getUserPermissionsErrors instead
if( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
wfProfileOut( __METHOD__ );
return $result ? array() : array( array( 'badaccess-group0' ) );
}
-
+ // Check getUserPermissionsErrors hook
if( !wfRunHooks( 'getUserPermissionsErrors', array(&$this,&$user,$action,&$result) ) ) {
if( is_array($result) && count($result) && !is_array($result[0]) )
$errors[] = $result; # A single array representing an error
@@ -1117,6 +1238,12 @@ class Title {
else if( $result === false )
$errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
}
+ # Short-circuit point
+ if( $short && count($errors) > 0 ) {
+ wfProfileOut( __METHOD__ );
+ return $errors;
+ }
+ // Check getUserPermissionsErrorsExpensive hook
if( $doExpensiveQueries && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array(&$this,&$user,$action,&$result) ) ) {
if( is_array($result) && count($result) && !is_array($result[0]) )
$errors[] = $result; # A single array representing an error
@@ -1127,13 +1254,20 @@ class Title {
else if( $result === false )
$errors[] = array('badaccess-group0'); # a generic "We don't want them to do that"
}
+ # Short-circuit point
+ if( $short && count($errors) > 0 ) {
+ wfProfileOut( __METHOD__ );
+ return $errors;
+ }
- // TODO: document
+ # Only 'createaccount' and 'execute' can be performed on
+ # special pages, which don't actually exist in the DB.
$specialOKActions = array( 'createaccount', 'execute' );
if( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions) ) {
$errors[] = array('ns-specialprotected');
}
+ # Check $wgNamespaceProtection for restricted namespaces
if( $this->isNamespaceProtected() ) {
$ns = $this->getNamespace() == NS_MAIN ?
wfMsg( 'nstab-main' ) : $this->getNsText();
@@ -1141,7 +1275,7 @@ class Title {
array('protectedinterface') : array( 'namespaceprotected', $ns );
}
- # protect css/js subpages of user pages
+ # Protect css/js subpages of user pages
# XXX: this might be better using restrictions
# XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working
if( $this->isCssJsSubpage() && !$user->isAllowed('editusercssjs')
@@ -1150,6 +1284,32 @@ class Title {
$errors[] = array('customcssjsprotected');
}
+ # Check against page_restrictions table requirements on this
+ # page. The user must possess all required rights for this action.
+ foreach( $this->getRestrictions($action) as $right ) {
+ // Backwards compatibility, rewrite sysop -> protect
+ if( $right == 'sysop' ) {
+ $right = 'protect';
+ }
+ if( '' != $right && !$user->isAllowed( $right ) ) {
+ // Users with 'editprotected' permission can edit protected pages
+ if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) {
+ // Users with 'editprotected' permission cannot edit protected pages
+ // with cascading option turned on.
+ if( $this->mCascadeRestriction ) {
+ $errors[] = array( 'protectedpagetext', $right );
+ }
+ } else {
+ $errors[] = array( 'protectedpagetext', $right );
+ }
+ }
+ }
+ # Short-circuit point
+ if( $short && count($errors) > 0 ) {
+ wfProfileOut( __METHOD__ );
+ return $errors;
+ }
+
if( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
# We /could/ use the protection level on the source page, but it's fairly ugly
# as we have to establish a precedence hierarchy for pages included by multiple
@@ -1172,26 +1332,10 @@ class Title {
}
}
}
-
- foreach( $this->getRestrictions($action) as $right ) {
- // Backwards compatibility, rewrite sysop -> protect
- if( $right == 'sysop' ) {
- $right = 'protect';
- }
- if( '' != $right && !$user->isAllowed( $right ) ) {
- // Users with 'editprotected' permission can edit protected pages
- if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) {
- // Users with 'editprotected' permission cannot edit protected pages
- // with cascading option turned on.
- if( $this->mCascadeRestriction ) {
- $errors[] = array( 'protectedpagetext', $right );
- } else {
- // Nothing, user can edit!
- }
- } else {
- $errors[] = array( 'protectedpagetext', $right );
- }
- }
+ # Short-circuit point
+ if( $short && count($errors) > 0 ) {
+ wfProfileOut( __METHOD__ );
+ return $errors;
}
if( $action == 'protect' ) {
@@ -1212,26 +1356,7 @@ class Title {
$errors[] = array( 'titleprotected', User::whoIs($pt_user), $pt_reason );
}
}
-
- if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) ||
- ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) )
- {
- $errors[] = $user->isAnon() ? array ('nocreatetext') : array ('nocreate-loggedin');
- }
} elseif( $action == 'move' ) {
- if( !$user->isAllowed( 'move' ) ) {
- // User can't move anything
- $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
- } elseif( !$user->isAllowed( 'move-rootuserpages' )
- && $this->getNamespace() == NS_USER && !$this->isSubpage() )
- {
- // Show user page-specific message only if the user can move other pages
- $errors[] = array( 'cant-move-user-page' );
- }
- // Check if user is allowed to move files if it's a file
- if( $this->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) {
- $errors[] = array( 'movenotallowedfile' );
- }
// Check for immobile pages
if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
// Specific message for this case
@@ -1241,31 +1366,11 @@ class Title {
$errors[] = array( 'immobile-page' );
}
} elseif( $action == 'move-target' ) {
- if( !$user->isAllowed( 'move' ) ) {
- // User can't move anything
- $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed');
- } elseif( !$user->isAllowed( 'move-rootuserpages' )
- && $this->getNamespace() == NS_USER && !$this->isSubpage() )
- {
- // Show user page-specific message only if the user can move other pages
- $errors[] = array( 'cant-move-to-user-page' );
- }
if( !MWNamespace::isMovable( $this->getNamespace() ) ) {
$errors[] = array( 'immobile-target-namespace', $this->getNsText() );
} elseif( !$this->isMovable() ) {
$errors[] = array( 'immobile-target-page' );
}
- } elseif( !$user->isAllowed( $action ) ) {
- $return = null;
- $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
- User::getGroupsWithPermission( $action ) );
- if( $groups ) {
- $return = array( 'badaccess-groups',
- array( implode( ', ', $groups ), count( $groups ) ) );
- } else {
- $return = array( "badaccess-group0" );
- }
- $errors[] = $return;
}
wfProfileOut( __METHOD__ );
@@ -1412,7 +1517,7 @@ class Title {
}
# Shortcut for public wikis, allows skipping quite a bit of code
- if ($wgGroupPermissions['*']['read'])
+ if ( !empty( $wgGroupPermissions['*']['read'] ) )
return true;
if( $wgUser->isAllowed( 'read' ) ) {
@@ -1510,11 +1615,36 @@ class Title {
return $this->mHasSubpages;
}
- $db = wfGetDB( DB_SLAVE );
- return $this->mHasSubpages = (bool)$db->selectField( 'page', '1',
- "page_namespace = {$this->mNamespace} AND page_title LIKE '"
- . $db->escapeLike( $this->mDbkeyform ) . "/%'",
- __METHOD__
+ $subpages = $this->getSubpages( 1 );
+ if( $subpages instanceof TitleArray )
+ return $this->mHasSubpages = (bool)$subpages->count();
+ return $this->mHasSubpages = false;
+ }
+
+ /**
+ * Get all subpages of this page.
+ * @param $limit Maximum number of subpages to fetch; -1 for no limit
+ * @return mixed TitleArray, or empty array if this page's namespace
+ * doesn't allow subpages
+ */
+ public function getSubpages( $limit = -1 ) {
+ if( !MWNamespace::hasSubpages( $this->getNamespace() ) )
+ return array();
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $conds['page_namespace'] = $this->getNamespace();
+ $conds[] = 'page_title LIKE ' . $dbr->addQuotes(
+ $dbr->escapeLike( $this->getDBkey() ) . '/%' );
+ $options = array();
+ if( $limit > -1 )
+ $options['LIMIT'] = $limit;
+ return $this->mSubpages = TitleArray::newFromResult(
+ $dbr->select( 'page',
+ array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ),
+ $conds,
+ __METHOD__,
+ $options
+ )
);
}
@@ -1849,20 +1979,45 @@ class Title {
* @return \type{\int} the number of archived revisions
*/
public function isDeleted() {
- $fname = 'Title::isDeleted';
- if ( $this->getNamespace() < 0 ) {
+ if( $this->getNamespace() < 0 ) {
$n = 0;
} else {
$dbr = wfGetDB( DB_SLAVE );
- $n = $dbr->selectField( 'archive', 'COUNT(*)', array( 'ar_namespace' => $this->getNamespace(),
- 'ar_title' => $this->getDBkey() ), $fname );
+ $n = $dbr->selectField( 'archive', 'COUNT(*)',
+ array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
+ __METHOD__
+ );
if( $this->getNamespace() == NS_FILE ) {
$n += $dbr->selectField( 'filearchive', 'COUNT(*)',
- array( 'fa_name' => $this->getDBkey() ), $fname );
+ array( 'fa_name' => $this->getDBkey() ),
+ __METHOD__
+ );
}
}
return (int)$n;
}
+
+ /**
+ * Is there a version of this page in the deletion archive?
+ * @return bool
+ */
+ public function isDeletedQuick() {
+ if( $this->getNamespace() < 0 ) {
+ return false;
+ }
+ $dbr = wfGetDB( DB_SLAVE );
+ $deleted = (bool)$dbr->selectField( 'archive', '1',
+ array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ),
+ __METHOD__
+ );
+ if( !$deleted && $this->getNamespace() == NS_FILE ) {
+ $deleted = (bool)$dbr->selectField( 'filearchive', '1',
+ array( 'fa_name' => $this->getDBkey() ),
+ __METHOD__
+ );
+ }
+ return $deleted;
+ }
/**
* Get the article ID for this Title from the link cache,
@@ -1955,7 +2110,7 @@ class Title {
$linkCache = LinkCache::singleton();
$linkCache->clearBadLink( $this->getPrefixedDBkey() );
- if ( 0 == $newid ) { $this->mArticleID = -1; }
+ if ( $newid === false ) { $this->mArticleID = -1; }
else { $this->mArticleID = $newid; }
$this->mRestrictionsLoaded = false;
$this->mRestrictions = array();
@@ -2064,14 +2219,22 @@ class Title {
# Namespace or interwiki prefix
$firstPass = true;
+ $prefixRegexp = "/^(.+?)_*:_*(.*)$/S";
do {
$m = array();
- if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $dbkey, $m ) ) {
+ if ( preg_match( $prefixRegexp, $dbkey, $m ) ) {
$p = $m[1];
- if ( $ns = $wgContLang->getNsIndex( $p )) {
+ if ( $ns = $wgContLang->getNsIndex( $p ) ) {
# Ordinary namespace
$dbkey = $m[2];
$this->mNamespace = $ns;
+ # For Talk:X pages, check if X has a "namespace" prefix
+ if( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) {
+ if( $wgContLang->getNsIndex( $x[1] ) )
+ return false; # Disallow Talk:File:x type titles...
+ else if( Interwiki::isValidInterwiki( $x[1] ) )
+ return false; # Disallow Talk:Interwiki:x type titles...
+ }
} elseif( Interwiki::isValidInterwiki( $p ) ) {
if( !$firstPass ) {
# Can't make a local interwiki link to an interwiki link.
@@ -2254,13 +2417,13 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param $options \type{\string} may be FOR UPDATE
+ * @param array $options may be FOR UPDATE
* @return \type{\arrayof{Title}} the Title objects linking here
*/
- public function getLinksTo( $options = '', $table = 'pagelinks', $prefix = 'pl' ) {
+ public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) {
$linkCache = LinkCache::singleton();
- if ( $options ) {
+ if ( count( $options ) > 0 ) {
$db = wfGetDB( DB_MASTER );
} else {
$db = wfGetDB( DB_SLAVE );
@@ -2295,10 +2458,10 @@ class Title {
* WARNING: do not use this function on arbitrary user-supplied titles!
* On heavily-used templates it will max out the memory.
*
- * @param $options \type{\string} may be FOR UPDATE
+ * @param array $options may be FOR UPDATE
* @return \type{\arrayof{Title}} the Title objects linking here
*/
- public function getTemplateLinksTo( $options = '' ) {
+ public function getTemplateLinksTo( $options = array() ) {
return $this->getLinksTo( $options, 'templatelinks', 'tl' );
}
@@ -2306,42 +2469,35 @@ class Title {
* Get an array of Title objects referring to non-existent articles linked from this page
*
* @todo check if needed (used only in SpecialBrokenRedirects.php, and should use redirect table in this case)
- * @param $options \type{\string} may be FOR UPDATE
* @return \type{\arrayof{Title}} the Title objects
*/
- public function getBrokenLinksFrom( $options = '' ) {
+ public function getBrokenLinksFrom() {
if ( $this->getArticleId() == 0 ) {
# All links from article ID 0 are false positives
return array();
}
- if ( $options ) {
- $db = wfGetDB( DB_MASTER );
- } else {
- $db = wfGetDB( DB_SLAVE );
- }
-
- $res = $db->safeQuery(
- "SELECT pl_namespace, pl_title
- FROM !
- LEFT JOIN !
- ON pl_namespace=page_namespace
- AND pl_title=page_title
- WHERE pl_from=?
- AND page_namespace IS NULL
- !",
- $db->tableName( 'pagelinks' ),
- $db->tableName( 'page' ),
- $this->getArticleId(),
- $options );
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select(
+ array( 'page', 'pagelinks' ),
+ array( 'pl_namespace', 'pl_title' ),
+ array(
+ 'pl_from' => $this->getArticleId(),
+ 'page_namespace IS NULL'
+ ),
+ __METHOD__, array(),
+ array(
+ 'page' => array(
+ 'LEFT JOIN',
+ array( 'pl_namespace=page_namespace', 'pl_title=page_title' )
+ )
+ )
+ );
$retVal = array();
- if ( $db->numRows( $res ) ) {
- foreach( $res as $row ) {
- $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
- }
+ foreach( $res as $row ) {
+ $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title );
}
- $db->freeResult( $res );
return $retVal;
}
@@ -2459,7 +2615,7 @@ class Title {
$nt->getUserPermissionsErrors('edit', $wgUser) );
}
- $match = EditPage::matchSpamRegex( $reason );
+ $match = EditPage::matchSummarySpamRegex( $reason );
if( $match !== false ) {
// This is kind of lame, won't display nice
$errors[] = array('spamprotectiontext');
@@ -2559,8 +2715,8 @@ class Title {
);
# Update the protection log
$log = new LogPage( 'protect' );
- $comment = wfMsgForContent('prot_1movedto2',$this->getPrefixedText(), $nt->getPrefixedText() );
- if( $reason ) $comment .= ': ' . $reason;
+ $comment = wfMsgForContent( 'prot_1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() );
+ if( $reason ) $comment .= wfMsgForContent( 'colon-separator' ) . $reason;
$log->addEntry( 'move_prot', $nt, $comment, array($this->getPrefixedText()) ); // FIXME: $params?
}
@@ -2601,8 +2757,16 @@ class Title {
# Update message cache for interface messages
if( $nt->getNamespace() == NS_MEDIAWIKI ) {
global $wgMessageCache;
- $oldarticle = new Article( $this );
- $wgMessageCache->replace( $this->getDBkey(), $oldarticle->getContent() );
+
+ # @bug 17860: old article can be deleted, if this the case,
+ # delete it from message cache
+ if ( $this->getArticleID === 0 ) {
+ $wgMessageCache->replace( $this->getDBkey(), false );
+ } else {
+ $oldarticle = new Article( $this );
+ $wgMessageCache->replace( $this->getDBkey(), $oldarticle->getContent() );
+ }
+
$newarticle = new Article( $nt );
$wgMessageCache->replace( $nt->getDBkey(), $newarticle->getContent() );
}
@@ -2830,6 +2994,67 @@ class Title {
}
/**
+ * Move this page's subpages to be subpages of $nt
+ * @param $nt Title Move target
+ * @param $auth bool Whether $wgUser's permissions should be checked
+ * @param $reason string The reason for the move
+ * @param $createRedirect bool Whether to create redirects from the old subpages to the new ones
+ * Ignored if the user doesn't have the 'suppressredirect' right
+ * @return mixed array with old page titles as keys, and strings (new page titles) or
+ * arrays (errors) as values, or an error array with numeric indices if no pages were moved
+ */
+ public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) {
+ global $wgUser, $wgMaximumMovedPages;
+ // Check permissions
+ if( !$this->userCan( 'move-subpages' ) )
+ return array( 'cant-move-subpages' );
+ // Do the source and target namespaces support subpages?
+ if( !MWNamespace::hasSubpages( $this->getNamespace() ) )
+ return array( 'namespace-nosubpages',
+ MWNamespace::getCanonicalName( $this->getNamespace() ) );
+ if( !MWNamespace::hasSubpages( $nt->getNamespace() ) )
+ return array( 'namespace-nosubpages',
+ MWNamespace::getCanonicalName( $nt->getNamespace() ) );
+
+ $subpages = $this->getSubpages($wgMaximumMovedPages + 1);
+ $retval = array();
+ $count = 0;
+ foreach( $subpages as $oldSubpage ) {
+ $count++;
+ if( $count > $wgMaximumMovedPages ) {
+ $retval[$oldSubpage->getPrefixedTitle()] =
+ array( 'movepage-max-pages',
+ $wgMaximumMovedPages );
+ break;
+ }
+
+ if( $oldSubpage->getArticleId() == $this->getArticleId() )
+ // When moving a page to a subpage of itself,
+ // don't move it twice
+ continue;
+ $newPageName = preg_replace(
+ '#^'.preg_quote( $this->getDBKey(), '#' ).'#',
+ $nt->getDBKey(), $oldSubpage->getDBKey() );
+ if( $oldSubpage->isTalkPage() ) {
+ $newNs = $nt->getTalkPage()->getNamespace();
+ } else {
+ $newNs = $nt->getSubjectPage()->getNamespace();
+ }
+ # Bug 14385: we need makeTitleSafe because the new page names may
+ # be longer than 255 characters.
+ $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
+
+ $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect );
+ if( $success === true ) {
+ $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText();
+ } else {
+ $retval[$oldSubpage->getPrefixedText()] = $success;
+ }
+ }
+ return $retval;
+ }
+
+ /**
* Checks if this page is just a one-rev redirect.
* Adds lock, so don't use just for light purposes.
*
@@ -2842,7 +3067,7 @@ class Title {
array( 'page_is_redirect', 'page_latest', 'page_id' ),
$this->pageCond(),
__METHOD__,
- 'FOR UPDATE'
+ array( 'FOR UPDATE' )
);
# Cache some fields we may want
$this->mArticleID = $row ? intval($row->page_id) : 0;
@@ -2860,7 +3085,7 @@ class Title {
'page_latest != rev_id'
),
__METHOD__,
- 'FOR UPDATE'
+ array( 'FOR UPDATE' )
);
# Return true if there was no history
return ($row === false);
@@ -3034,6 +3259,28 @@ class Title {
}
/**
+ * Get the first revision of the page
+ *
+ * @param $flags \type{\int} GAID_FOR_UPDATE
+ * @return Revision (or NULL if page doesn't exist)
+ */
+ public function getFirstRevision( $flags=0 ) {
+ $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE );
+ $pageId = $this->getArticleId($flags);
+ if( !$pageId ) return NULL;
+ $row = $db->selectRow( 'revision', '*',
+ array( 'rev_page' => $pageId ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 )
+ );
+ if( !$row ) {
+ return NULL;
+ } else {
+ return new Revision( $row );
+ }
+ }
+
+ /**
* Check if this is a new page
*
* @return bool
@@ -3074,8 +3321,8 @@ class Title {
'rev_page = ' . intval( $this->getArticleId() ) .
' AND rev_id > ' . intval( $old ) .
' AND rev_id < ' . intval( $new ),
- __METHOD__,
- array( 'USE INDEX' => 'PRIMARY' ) );
+ __METHOD__
+ );
}
/**
@@ -3094,7 +3341,7 @@ class Title {
/**
* Callback for usort() to do title sorts by (namespace, title)
*/
- static function compare( $a, $b ) {
+ public static function compare( $a, $b ) {
if( $a->getNamespace() == $b->getNamespace() ) {
return strcmp( $a->getText(), $b->getText() );
} else {
@@ -3144,7 +3391,7 @@ class Title {
if( $this->mInterwiki != '' ) {
return true; // any interwiki link might be viewable, for all we know
}
- switch( $this->mNamespace ) {
+ switch( $this->mNamespace ) {
case NS_MEDIA:
case NS_FILE:
return wfFindFile( $this ); // file exists, possibly in a foreign repo
@@ -3250,9 +3497,9 @@ class Title {
* @return \type{\string} Trackback URL
*/
public function trackbackURL() {
- global $wgScriptPath, $wgServer;
+ global $wgScriptPath, $wgServer, $wgScriptExtension;
- return "$wgServer$wgScriptPath/trackback.php?article="
+ return "$wgServer$wgScriptPath/trackback$wgScriptExtension?article="
. htmlspecialchars(urlencode($this->getPrefixedDBkey()));
}
@@ -3396,4 +3643,36 @@ class Title {
}
return $redirs;
}
+
+ /**
+ * Check if this Title is a valid redirect target
+ *
+ * @return \type{\bool} TRUE or FALSE
+ */
+ public function isValidRedirectTarget() {
+ global $wgInvalidRedirectTargets;
+
+ // invalid redirect targets are stored in a global array, but explicity disallow Userlogout here
+ if( $this->isSpecial( 'Userlogout' ) ) {
+ return false;
+ }
+
+ foreach( $wgInvalidRedirectTargets as $target ) {
+ if( $this->isSpecial( $target ) ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get a backlink cache object
+ */
+ function getBacklinkCache() {
+ if ( is_null( $this->mBacklinkCache ) ) {
+ $this->mBacklinkCache = new BacklinkCache( $this );
+ }
+ return $this->mBacklinkCache;
+ }
}
diff --git a/includes/UploadBase.php b/includes/UploadBase.php
deleted file mode 100644
index 91155a1b..00000000
--- a/includes/UploadBase.php
+++ /dev/null
@@ -1,867 +0,0 @@
-<?php
-
-class UploadBase {
- var $mTempPath;
- var $mDesiredDestName, $mDestName, $mRemoveTempFile, $mSourceType;
- var $mTitle = false, $mTitleError = 0;
- var $mFilteredName, $mFinalExtension;
-
- const SUCCESS = 0;
- const OK = 0;
- const BEFORE_PROCESSING = 1;
- const LARGE_FILE_SERVER = 2;
- const EMPTY_FILE = 3;
- const MIN_LENGTH_PARTNAME = 4;
- const ILLEGAL_FILENAME = 5;
- const PROTECTED_PAGE = 6;
- const OVERWRITE_EXISTING_FILE = 7;
- const FILETYPE_MISSING = 8;
- const FILETYPE_BADTYPE = 9;
- const VERIFICATION_ERROR = 10;
- const UPLOAD_VERIFICATION_ERROR = 11;
- const UPLOAD_WARNING = 12;
- const INTERNAL_ERROR = 13;
-
- const SESSION_VERSION = 2;
-
- /**
- * Returns true if uploads are enabled.
- * Can be overriden by subclasses.
- */
- static function isEnabled() {
- global $wgEnableUploads;
- return $wgEnableUploads;
- }
- /**
- * Returns true if the user can use this upload module or else a string
- * identifying the missing permission.
- * Can be overriden by subclasses.
- */
- static function isAllowed( $user ) {
- if( !$user->isAllowed( 'upload' ) )
- return 'upload';
- return true;
- }
-
- // Upload handlers. Should probably just be a global
- static $uploadHandlers = array( 'Stash', 'Upload', 'Url' );
- /**
- * Create a form of UploadBase depending on wpSourceType and initializes it
- */
- static function createFromRequest( &$request, $type = null ) {
- $type = $type ? $type : $request->getVal( 'wpSourceType' );
- if( !$type )
- return null;
- $type = ucfirst($type);
- $className = 'UploadFrom'.$type;
- if( !in_array( $type, self::$uploadHandlers ) )
- return null;
- if( !call_user_func( array( $className, 'isEnabled' ) ) )
- return null;
- if( !call_user_func( array( $className, 'isValidRequest' ), $request ) )
- return null;
-
- $handler = new $className;
- $handler->initializeFromRequest( $request );
- return $handler;
- }
-
- /**
- * Check whether a request if valid for this handler
- */
- static function isValidRequest( $request ) {
- return false;
- }
-
- function __construct() {}
-
- /**
- * Do the real variable initialization
- */
- function initialize( $name, $tempPath, $fileSize, $removeTempFile = false ) {
- $this->mDesiredDestName = $name;
- $this->mTempPath = $tempPath;
- $this->mFileSize = $fileSize;
- $this->mRemoveTempFile = $removeTempFile;
- }
-
- /**
- * Fetch the file. Usually a no-op
- */
- function fetchFile() {
- return self::OK;
- }
-
- /**
- * Verify whether the upload is sane.
- * Returns self::OK or else an array with error information
- */
- function verifyUpload() {
- global $wgUser;
-
- /**
- * If there was no filename or a zero size given, give up quick.
- */
- if( empty( $this->mFileSize ) )
- return array( 'status' => self::EMPTY_FILE );
-
- $nt = $this->getTitle();
- if( is_null( $nt ) ) {
- $result = array( 'status' => $this->mTitleError );
- if( $this->mTitleError == self::ILLEGAL_FILENAME )
- $resul['filtered'] = $this->mFilteredName;
- if ( $this->mTitleError == self::FILETYPE_BADTYPE )
- $result['finalExt'] = $this->mFinalExtension;
- return $result;
- }
- $this->mLocalFile = wfLocalFile( $nt );
- $this->mDestName = $this->mLocalFile->getName();
-
- /**
- * In some cases we may forbid overwriting of existing files.
- */
- $overwrite = $this->checkOverwrite( $this->mDestName );
- if( $overwrite !== true )
- return array( 'status' => self::OVERWRITE_EXISTING_FILE, 'overwrite' => $overwrite );
-
- /**
- * Look at the contents of the file; if we can recognize the
- * type but it's corrupt or data of the wrong type, we should
- * probably not accept it.
- */
- $verification = $this->verifyFile( $this->mTempPath );
-
- if( $verification !== true ) {
- if( !is_array( $verification ) )
- $verification = array( $verification );
- $verification['status'] = self::VERIFICATION_ERROR;
- return $verification;
- }
-
- $error = '';
- if( !wfRunHooks( 'UploadVerification',
- array( $this->mDestName, $this->mTempPath, &$error ) ) ) {
- return array( 'status' => self::UPLOAD_VERIFICATION_ERROR, 'error' => $error );
- }
-
- return self::OK;
- }
-
- /**
- * Verifies that it's ok to include the uploaded file
- *
- * @param string $tmpfile the full path of the temporary file to verify
- * @return mixed true of the file is verified, a string or array otherwise.
- */
- protected function verifyFile( $tmpfile ) {
- $this->mFileProps = File::getPropsFromPath( $this->mTempPath,
- $this->mFinalExtension );
- $this->checkMacBinary();
-
- #magically determine mime type
- $magic = MimeMagic::singleton();
- $mime = $magic->guessMimeType( $tmpfile, false );
-
- #check mime type, if desired
- global $wgVerifyMimeType;
- if ( $wgVerifyMimeType ) {
-
- wfDebug ( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n");
- #check mime type against file extension
- if( !self::verifyExtension( $mime, $this->mFinalExtension ) ) {
- return 'uploadcorrupt';
- }
-
- #check mime type blacklist
- global $wgMimeTypeBlacklist;
- if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist)
- && $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
- return array( 'filetype-badmime', $mime );
- }
- }
-
- #check for htmlish code and javascript
- if( $this->detectScript ( $tmpfile, $mime, $this->mFinalExtension ) ) {
- return 'uploadscripted';
- }
-
- /**
- * Scan the uploaded file for viruses
- */
- $virus = $this->detectVirus($tmpfile);
- if ( $virus ) {
- return array( 'uploadvirus', $virus );
- }
-
- wfDebug( __METHOD__.": all clear; passing.\n" );
- return true;
- }
-
- /**
- * Check whether the user can edit, upload and create the image
- */
- function verifyPermissions( $user ) {
- /**
- * If the image is protected, non-sysop users won't be able
- * to modify it by uploading a new revision.
- */
- $nt = $this->getTitle();
- if( is_null( $nt ) )
- return true;
- $permErrors = $nt->getUserPermissionsErrors( 'edit', $user );
- $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $user );
- $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $user ) );
- if( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
- $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
- $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
- return $permErrors;
- }
- return true;
- }
-
- /**
- * Check for non fatal problems with the file
- */
- function checkWarnings() {
- $warning = array();
-
- $filename = $this->mLocalFile->getName();
- $n = strrpos( $filename, '.' );
- $partname = $n ? substr( $filename, 0, $n ) : $filename;
-
- // Check whether the resulting filename is different from the desired one
- if( $this->mDesiredDestName != $filename )
- $warning['badfilename'] = $filename;
-
- // Check whether the file extension is on the unwanted list
- global $wgCheckFileExtensions, $wgFileExtensions;
- if ( $wgCheckFileExtensions ) {
- if ( !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) )
- $warning['filetype-unwanted-type'] = $this->mFinalExtension;
- }
-
- global $wgUploadSizeWarning;
- if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) )
- $warning['large-file'] = $wgUploadSizeWarning;
-
- if ( $this->mFileSize == 0 )
- $warning['emptyfile'] = true;
-
- $exists = self::getExistsWarning( $this->mLocalFile );
- if( $exists !== false )
- $warning['exists'] = $exists;
-
- // Check whether this may be a thumbnail
- if( $exists !== false && $exists[0] != 'thumb'
- && self::isThumbName( $this->mLocalFile->getName() ) )
- $warning['file-thumbnail-no'] = substr( $filename , 0,
- strpos( $nt->getText() , '-' ) +1 );
-
- $hash = File::sha1Base36( $this->mTempPath );
- $dupes = RepoGroup::singleton()->findBySha1( $hash );
- if( $dupes )
- $warning['duplicate'] = $dupes;
-
- $filenamePrefixBlacklist = self::getFilenamePrefixBlacklist();
- foreach( $filenamePrefixBlacklist as $prefix ) {
- if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
- $warning['filename-bad-prefix'] = $prefix;
- break;
- }
- }
-
- # If the file existed before and was deleted, warn the user of this
- # Don't bother doing so if the file exists now, however
- if( $this->mLocalFile->wasDeleted() && !$this->mLocalFile->exists() )
- $warning['filewasdeleted'] = $this->mLocalFile->getTitle();
-
- return $warning;
- }
-
- /**
- * Really perform the upload.
- */
- function performUpload( $comment, $pageText, $watch, $user ) {
- $status = $this->mLocalFile->upload( $this->mTempPath, $comment, $pageText,
- File::DELETE_SOURCE, $this->mFileProps, false, $user );
-
- if( $status->isGood() && $watch ) {
- $user->addWatch( $this->mLocalFile->getTitle() );
- }
-
- if( $status->isGood() )
- wfRunHooks( 'UploadComplete', array( &$this ) );
-
- return $status;
- }
-
- /**
- * Returns a title or null
- */
- function getTitle() {
- if ( $this->mTitle !== false )
- return $this->mTitle;
-
- /**
- * Chop off any directories in the given filename. Then
- * filter out illegal characters, and try to make a legible name
- * out of it. We'll strip some silently that Title would die on.
- */
-
- $basename = $this->mDesiredDestName;
-
- $this->mFilteredName = wfStripIllegalFilenameChars( $basename );
-
- /**
- * We'll want to blacklist against *any* 'extension', and use
- * only the final one for the whitelist.
- */
- list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName );
-
- if( count( $ext ) ) {
- $this->mFinalExtension = $ext[count( $ext ) - 1];
- } else {
- $this->mFinalExtension = '';
- }
-
- /* Don't allow users to override the blacklist (check file extension) */
- global $wgCheckFileExtensions, $wgStrictFileExtensions;
- global $wgFileExtensions, $wgFileBlacklist;
- if ( $this->mFinalExtension == '' ) {
- $this->mTitleError = self::FILETYPE_MISSING;
- return $this->mTitle = null;
- } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) ||
- ( $wgCheckFileExtensions && $wgStrictFileExtensions &&
- !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) ) {
- $this->mTitleError = self::FILETYPE_BADTYPE;
- return $this->mTitle = null;
- }
-
- # If there was more than one "extension", reassemble the base
- # filename to prevent bogus complaints about length
- if( count( $ext ) > 1 ) {
- for( $i = 0; $i < count( $ext ) - 1; $i++ )
- $partname .= '.' . $ext[$i];
- }
-
- if( strlen( $partname ) < 1 ) {
- $this->mTitleError = self::MIN_LENGTH_PARTNAME;
- return $this->mTitle = null;
- }
-
- $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName );
- if( is_null( $nt ) ) {
- $this->mTitleError = self::ILLEGAL_FILENAME;
- return $this->mTitle = null;
- }
- return $this->mTitle = $nt;
- }
-
- function getLocalFile() {
- if( is_null( $this->mLocalFile ) ) {
- $nt = $this->getTitle();
- $this->mLocalFile = is_null( $nt ) ? null : wfLocalFile( $nt );
- }
- return $this->mLocalFile;
- }
-
- /**
- * Stash a file in a temporary directory for later processing
- * after the user has confirmed it.
- *
- * If the user doesn't explicitly cancel or accept, these files
- * can accumulate in the temp directory.
- *
- * @param string $saveName - the destination filename
- * @param string $tempName - the source temporary file to save
- * @return string - full path the stashed file, or false on failure
- * @access private
- */
- function saveTempUploadedFile( $saveName, $tempName ) {
- global $wgOut;
- $repo = RepoGroup::singleton()->getLocalRepo();
- $status = $repo->storeTemp( $saveName, $tempName );
- return $status;
- }
-
- /**
- * Stash a file in a temporary directory for later processing,
- * and save the necessary descriptive info into the session.
- * Returns a key value which will be passed through a form
- * to pick up the path info on a later invocation.
- *
- * @return int
- * @access private
- */
- function stashSession() {
- $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
-
- if( !$status->isGood() ) {
- # Couldn't save the file.
- return false;
- }
-
- return array(
- 'mTempPath' => $status->value,
- 'mFileSize' => $this->mFileSize,
- 'mFileProps' => $this->mFileProps,
- 'version' => self::SESSION_VERSION,
- );
- }
-
- /**
- * Remove a temporarily kept file stashed by saveTempUploadedFile().
- * @return success
- */
- function unsaveUploadedFile() {
- $repo = RepoGroup::singleton()->getLocalRepo();
- $success = $repo->freeTemp( $this->mTempPath );
- return $success;
- }
-
- /**
- * If we've modified the upload file we need to manually remove it
- * on exit to clean up.
- * @access private
- */
- function cleanupTempFile() {
- if ( $this->mRemoveTempFile && file_exists( $this->mTempPath ) ) {
- wfDebug( __METHOD__.": Removing temporary file {$this->mTempPath}\n" );
- unlink( $this->mTempPath );
- }
- }
-
- function getTempPath() {
- return $this->mTempPath;
- }
-
-
- /**
- * Split a file into a base name and all dot-delimited 'extensions'
- * on the end. Some web server configurations will fall back to
- * earlier pseudo-'extensions' to determine type and execute
- * scripts, so the blacklist needs to check them all.
- *
- * @return array
- */
- function splitExtensions( $filename ) {
- $bits = explode( '.', $filename );
- $basename = array_shift( $bits );
- return array( $basename, $bits );
- }
-
- /**
- * Perform case-insensitive match against a list of file extensions.
- * Returns true if the extension is in the list.
- *
- * @param string $ext
- * @param array $list
- * @return bool
- */
- function checkFileExtension( $ext, $list ) {
- return in_array( strtolower( $ext ), $list );
- }
-
- /**
- * Perform case-insensitive match against a list of file extensions.
- * Returns true if any of the extensions are in the list.
- *
- * @param array $ext
- * @param array $list
- * @return bool
- */
- function checkFileExtensionList( $ext, $list ) {
- foreach( $ext as $e ) {
- if( in_array( strtolower( $e ), $list ) ) {
- return true;
- }
- }
- return false;
- }
-
-
- /**
- * Checks if the mime type of the uploaded file matches the file extension.
- *
- * @param string $mime the mime type of the uploaded file
- * @param string $extension The filename extension that the file is to be served with
- * @return bool
- */
- public static function verifyExtension( $mime, $extension ) {
- $magic = MimeMagic::singleton();
-
- if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' )
- if ( ! $magic->isRecognizableExtension( $extension ) ) {
- wfDebug( __METHOD__.": passing file with unknown detected mime type; " .
- "unrecognized extension '$extension', can't verify\n" );
- return true;
- } else {
- wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ".
- "recognized extension '$extension', so probably invalid file\n" );
- return false;
- }
-
- $match= $magic->isMatchingExtension($extension,$mime);
-
- if ($match===NULL) {
- wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" );
- return true;
- } elseif ($match===true) {
- wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" );
-
- #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it!
- return true;
-
- } else {
- wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" );
- return false;
- }
- }
-
- /**
- * Heuristic for detecting files that *could* contain JavaScript instructions or
- * things that may look like HTML to a browser and are thus
- * potentially harmful. The present implementation will produce false positives in some situations.
- *
- * @param string $file Pathname to the temporary upload file
- * @param string $mime The mime type of the file
- * @param string $extension The extension of the file
- * @return bool true if the file contains something looking like embedded scripts
- */
- function detectScript($file, $mime, $extension) {
- global $wgAllowTitlesInSVG;
-
- #ugly hack: for text files, always look at the entire file.
- #For binary field, just check the first K.
-
- if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file );
- else {
- $fp = fopen( $file, 'rb' );
- $chunk = fread( $fp, 1024 );
- fclose( $fp );
- }
-
- $chunk= strtolower( $chunk );
-
- if (!$chunk) return false;
-
- #decode from UTF-16 if needed (could be used for obfuscation).
- if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE";
- elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE";
- else $enc= NULL;
-
- if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk);
-
- $chunk= trim($chunk);
-
- #FIXME: convert from UTF-16 if necessarry!
-
- wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n");
-
- #check for HTML doctype
- if (eregi("<!DOCTYPE *X?HTML",$chunk)) return true;
-
- /**
- * Internet Explorer for Windows performs some really stupid file type
- * autodetection which can cause it to interpret valid image files as HTML
- * and potentially execute JavaScript, creating a cross-site scripting
- * attack vectors.
- *
- * Apple's Safari browser also performs some unsafe file type autodetection
- * which can cause legitimate files to be interpreted as HTML if the
- * web server is not correctly configured to send the right content-type
- * (or if you're really uploading plain text and octet streams!)
- *
- * Returns true if IE is likely to mistake the given file for HTML.
- * Also returns true if Safari would mistake the given file for HTML
- * when served with a generic content-type.
- */
-
- $tags = array(
- '<body',
- '<head',
- '<html', #also in safari
- '<img',
- '<pre',
- '<script', #also in safari
- '<table'
- );
- if( ! $wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) {
- $tags[] = '<title';
- }
-
- foreach( $tags as $tag ) {
- if( false !== strpos( $chunk, $tag ) ) {
- return true;
- }
- }
-
- /*
- * look for javascript
- */
-
- #resolve entity-refs to look at attributes. may be harsh on big files... cache result?
- $chunk = Sanitizer::decodeCharReferences( $chunk );
-
- #look for script-types
- if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim',$chunk)) return true;
-
- #look for html-style script-urls
- if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true;
-
- #look for css-style script-urls
- if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true;
-
- wfDebug("SpecialUpload::detectScript: no scripts found\n");
- return false;
- }
-
- /**
- * Generic wrapper function for a virus scanner program.
- * This relies on the $wgAntivirus and $wgAntivirusSetup variables.
- * $wgAntivirusRequired may be used to deny upload if the scan fails.
- *
- * @param string $file Pathname to the temporary upload file
- * @return mixed false if not virus is found, NULL if the scan fails or is disabled,
- * or a string containing feedback from the virus scanner if a virus was found.
- * If textual feedback is missing but a virus was found, this function returns true.
- */
- function detectVirus($file) {
- global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut;
-
- if ( !$wgAntivirus ) {
- wfDebug( __METHOD__.": virus scanner disabled\n");
- return NULL;
- }
-
- if ( !$wgAntivirusSetup[$wgAntivirus] ) {
- wfDebug( __METHOD__.": unknown virus scanner: $wgAntivirus\n" );
- $wgOut->wrapWikiMsg( '<div class="error">$1</div>', array( 'virus-badscanner', $wgAntivirus ) );
- return wfMsg('virus-unknownscanner') . " $wgAntivirus";
- }
-
- # look up scanner configuration
- $command = $wgAntivirusSetup[$wgAntivirus]["command"];
- $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]["codemap"];
- $msgPattern = isset( $wgAntivirusSetup[$wgAntivirus]["messagepattern"] ) ?
- $wgAntivirusSetup[$wgAntivirus]["messagepattern"] : null;
-
- if ( strpos( $command,"%f" ) === false ) {
- # simple pattern: append file to scan
- $command .= " " . wfEscapeShellArg( $file );
- } else {
- # complex pattern: replace "%f" with file to scan
- $command = str_replace( "%f", wfEscapeShellArg( $file ), $command );
- }
-
- wfDebug( __METHOD__.": running virus scan: $command \n" );
-
- # execute virus scanner
- $exitCode = false;
-
- #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
- # that does not seem to be worth the pain.
- # Ask me (Duesentrieb) about it if it's ever needed.
- $output = array();
- if ( wfIsWindows() ) {
- exec( "$command", $output, $exitCode );
- } else {
- exec( "$command 2>&1", $output, $exitCode );
- }
-
- # map exit code to AV_xxx constants.
- $mappedCode = $exitCode;
- if ( $exitCodeMap ) {
- if ( isset( $exitCodeMap[$exitCode] ) ) {
- $mappedCode = $exitCodeMap[$exitCode];
- } elseif ( isset( $exitCodeMap["*"] ) ) {
- $mappedCode = $exitCodeMap["*"];
- }
- }
-
- if ( $mappedCode === AV_SCAN_FAILED ) {
- # scan failed (code was mapped to false by $exitCodeMap)
- wfDebug( __METHOD__.": failed to scan $file (code $exitCode).\n" );
-
- if ( $wgAntivirusRequired ) {
- return wfMsg('virus-scanfailed', array( $exitCode ) );
- } else {
- return NULL;
- }
- } else if ( $mappedCode === AV_SCAN_ABORTED ) {
- # scan failed because filetype is unknown (probably imune)
- wfDebug( __METHOD__.": unsupported file type $file (code $exitCode).\n" );
- return NULL;
- } else if ( $mappedCode === AV_NO_VIRUS ) {
- # no virus found
- wfDebug( __METHOD__.": file passed virus scan.\n" );
- return false;
- } else {
- $output = join( "\n", $output );
- $output = trim( $output );
-
- if ( !$output ) {
- $output = true; #if there's no output, return true
- } elseif ( $msgPattern ) {
- $groups = array();
- if ( preg_match( $msgPattern, $output, $groups ) ) {
- if ( $groups[1] ) {
- $output = $groups[1];
- }
- }
- }
-
- wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output" );
- return $output;
- }
- }
-
- /**
- * Check if the temporary file is MacBinary-encoded, as some uploads
- * from Internet Explorer on Mac OS Classic and Mac OS X will be.
- * If so, the data fork will be extracted to a second temporary file,
- * which will then be checked for validity and either kept or discarded.
- *
- * @access private
- */
- function checkMacBinary() {
- $macbin = new MacBinary( $this->mTempPath );
- if( $macbin->isValid() ) {
- $dataFile = tempnam( wfTempDir(), "WikiMacBinary" );
- $dataHandle = fopen( $dataFile, 'wb' );
-
- wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" );
- $macbin->extractData( $dataHandle );
-
- $this->mTempPath = $dataFile;
- $this->mFileSize = $macbin->dataForkLength();
-
- // We'll have to manually remove the new file if it's not kept.
- $this->mRemoveTempFile = true;
- }
- $macbin->close();
- }
-
- /**
- * Check if there's an overwrite conflict and, if so, if restrictions
- * forbid this user from performing the upload.
- *
- * @return mixed true on success, WikiError on failure
- * @access private
- */
- function checkOverwrite() {
- global $wgUser;
- // First check whether the local file can be overwritten
- if( $this->mLocalFile->exists() )
- if( !self::userCanReUpload( $wgUser, $this->mLocalFile ) )
- return 'fileexists-forbidden';
-
- // Check shared conflicts
- $file = wfFindFile( $this->mLocalFile->getName() );
- if ( $file && ( !$wgUser->isAllowed( 'reupload' ) ||
- !$wgUser->isAllowed( 'reupload-shared' ) ) )
- return 'fileexists-shared-forbidden';
-
- return true;
-
- }
-
- /**
- * Check if a user is the last uploader
- *
- * @param User $user
- * @param string $img, image name
- * @return bool
- */
- public static function userCanReUpload( User $user, $img ) {
- if( $user->isAllowed( 'reupload' ) )
- return true; // non-conditional
- if( !$user->isAllowed( 'reupload-own' ) )
- return false;
- if( is_string( $img ) )
- $img = wfLocalFile( $img );
- if ( !( $img instanceof LocalFile ) )
- return false;
-
- return $user->getId() == $img->getUser( 'id' );
- }
-
- public static function getExistsWarning( $file ) {
- if( $file->exists() )
- return array( 'exists', $file );
-
- if( $file->getTitle()->getArticleID() )
- return array( 'page-exists', $file );
-
- if( strpos( $file->getName(), '.' ) == false ) {
- $partname = $file->getName();
- $rawExtension = '';
- } else {
- $n = strrpos( $file->getName(), '.' );
- $rawExtension = substr( $file->getName(), $n + 1 );
- $partname = substr( $file->getName(), 0, $n );
- }
-
- if ( $rawExtension != $file->getExtension() ) {
- // We're not using the normalized form of the extension.
- // Normal form is lowercase, using most common of alternate
- // extensions (eg 'jpg' rather than 'JPEG').
- //
- // Check for another file using the normalized form...
- $nt_lc = Title::makeTitle( NS_FILE, $partname . '.' . $file->getExtension() );
- $file_lc = wfLocalFile( $nt_lc );
-
- if( $file_lc->exists() )
- return array( 'exists-normalized', $file_lc );
- }
-
- if ( self::isThumbName( $file->getName() ) ) {
- # Check for filenames like 50px- or 180px-, these are mostly thumbnails
- $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension );
- $file_thb = wfLocalFile( $nt_thb );
- if( $file_thb->exists() )
- return array( 'thumb', $file_thb );
- }
-
- return false;
- }
-
- public static function isThumbName( $filename ) {
- $n = strrpos( $filename, '.' );
- $partname = $n ? substr( $filename, 0, $n ) : $filename;
- return (
- substr( $partname , 3, 3 ) == 'px-' ||
- substr( $partname , 2, 3 ) == 'px-'
- ) &&
- ereg( "[0-9]{2}" , substr( $partname , 0, 2) );
- }
-
- /**
- * Get a list of blacklisted filename prefixes from [[MediaWiki:filename-prefix-blacklist]]
- *
- * @return array list of prefixes
- */
- public static function getFilenamePrefixBlacklist() {
- $blacklist = array();
- $message = wfMsgForContent( 'filename-prefix-blacklist' );
- if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) {
- $lines = explode( "\n", $message );
- foreach( $lines as $line ) {
- // Remove comment lines
- $comment = substr( trim( $line ), 0, 1 );
- if ( $comment == '#' || $comment == '' ) {
- continue;
- }
- // Remove additional comments after a prefix
- $comment = strpos( $line, '#' );
- if ( $comment > 0 ) {
- $line = substr( $line, 0, $comment-1 );
- }
- $blacklist[] = trim( $line );
- }
- }
- return $blacklist;
- }
-
-
-}
diff --git a/includes/UploadFromStash.php b/includes/UploadFromStash.php
deleted file mode 100644
index 8bff3b49..00000000
--- a/includes/UploadFromStash.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-class UploadFromStash extends UploadBase {
- static function isValidSessionKey( $key, $sessionData ) {
- return !empty( $key ) &&
- is_array( $sessionData ) &&
- isset( $sessionData[$key] ) &&
- isset( $sessionData[$key]['version'] ) &&
- $sessionData[$key]['version'] == self::SESSION_VERSION
- ;
- }
- static function isValidRequest( $request ) {
- $sessionData = $request->getSessionData('wsUploadData');
- return self::isValidSessionKey(
- $request->getInt( 'wpSessionKey' ),
- $sessionData
- );
- }
-
- function initialize( $name, $sessionData ) {
- /**
- * Confirming a temporarily stashed upload.
- * We don't want path names to be forged, so we keep
- * them in the session on the server and just give
- * an opaque key to the user agent.
- */
- $this->initialize( $name,
- $sessionData['mTempPath'],
- $sessionData['mFileSize'],
- false
- );
-
- $this->mFileProps = $sessionData['mFileProps'];
- }
- function initializeFromRequest( &$request ) {
- $sessionKey = $request->getInt( 'wpSessionKey' );
- $sessionData = $request->getSessionData('wsUploadData');
-
- $desiredDestName = $request->getText( 'wpDestFile' );
- if( !$desiredDestName )
- $desiredDestName = $request->getText( 'wpUploadFile' );
-
- return $this->initialize( $desiredDestName, $sessionData[$sessionKey] );
- }
-
- /**
- * File has been previously verified so no need to do so again.
- */
- protected function verifyFile( $tmpfile ) {
- return true;
- }
- /**
- * We're here from "ignore warnings anyway" so return just OK
- */
- function checkWarnings() {
- return array();
- }
-}
diff --git a/includes/UploadFromUpload.php b/includes/UploadFromUpload.php
deleted file mode 100644
index 1b6762c6..00000000
--- a/includes/UploadFromUpload.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-class UploadFromUpload extends UploadBase {
-
- function initializeFromRequest( &$request ) {
- $desiredDestName = $request->getText( 'wpDestFile' );
- if( !$desiredDestName )
- $desiredDestName = $request->getText( 'wpUploadFile' );
-
- return $this->initialize(
- $desiredDestName,
- $request->getFileTempName( 'wpUploadFile' ),
- $request->getFileSize( 'wpUploadFile' )
- );
- }
-
- static function isValidRequest( $request ) {
- return (bool)$request->getFileTempName( 'wpUploadFile' );
- }
-}
diff --git a/includes/UploadFromUrl.php b/includes/UploadFromUrl.php
deleted file mode 100644
index 7e23b8cd..00000000
--- a/includes/UploadFromUrl.php
+++ /dev/null
@@ -1,92 +0,0 @@
-<?php
-
-
-class UploadFromUrl extends UploadBase {
- static function isAllowed( $user ) {
- if( !$user->isAllowed( 'upload_by_url' ) )
- return 'upload_by_url';
- return parent::isAllowed( $user );
- }
- static function isEnabled() {
- global $wgAllowCopyUploads;
- return $wgAllowCopyUploads && parent::isEnabled();
- }
-
- function initialize( $name, $url ) {
- global $wgTmpDirectory;
- $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' );
- $this-initialize( $name, $local_file, 0, true );
-
- $this->mUrl = trim( $url );
- }
-
- /**
- * Do the real fetching stuff
- */
- function fetchFile() {
- if( stripos($this->mUrl, 'http://') !== 0 && stripos($this->mUrl, 'ftp://') !== 0 ) {
- return array(
- 'status' => self::BEFORE_PROCESSING,
- 'error' => 'upload-proto-error',
- );
- }
- $res = $this->curlCopy();
- if( $res !== true ) {
- return array(
- 'status' => self::BEFORE_PROCESSING,
- 'error' => $res,
- );
- }
- return self::OK;
- }
-
- /**
- * Safe copy from URL
- * Returns true if there was an error, false otherwise
- */
- private function curlCopy() {
- global $wgUser, $wgOut;
-
- # Open temporary file
- $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" );
- if( $this->mCurlDestHandle === false ) {
- # Could not open temporary file to write in
- return 'upload-file-error';
- }
-
- $ch = curl_init();
- curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug
- curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout
- curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed
- curl_setopt( $ch, CURLOPT_URL, $this->mUrl);
- curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) );
- curl_exec( $ch );
- $error = curl_errno( $ch );
- curl_close( $ch );
-
- fclose( $this->mCurlDestHandle );
- unset( $this->mCurlDestHandle );
-
- if( $error )
- return "upload-curl-error$errornum";
-
- return true;
- }
-
- /**
- * Callback function for CURL-based web transfer
- * Write data to file unless we've passed the length limit;
- * if so, abort immediately.
- * @access private
- */
- function uploadCurlCallback( $ch, $data ) {
- global $wgMaxUploadSize;
- $length = strlen( $data );
- $this->mFileSize += $length;
- if( $this->mFileSize > $wgMaxUploadSize ) {
- return 0;
- }
- fwrite( $this->mCurlDestHandle, $data );
- return $length;
- }
-}
diff --git a/includes/User.php b/includes/User.php
index 9fee089c..cc861ad4 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -141,9 +141,11 @@ class User {
'createtalk',
'delete',
'deletedhistory',
+ 'deleterevision',
'edit',
'editinterface',
'editusercssjs',
+ 'hideuser',
'import',
'importupload',
'ipblock-exempt',
@@ -155,6 +157,7 @@ class User {
'move-subpages',
'nominornewtalk',
'noratelimit',
+ 'override-export-depth',
'patrol',
'protect',
'proxyunbannable',
@@ -164,13 +167,17 @@ class User {
'reupload-shared',
'rollback',
'siteadmin',
+ 'suppressionlog',
'suppressredirect',
+ 'suppressrevision',
'trackback',
'undelete',
'unwatchedpages',
'upload',
'upload_by_url',
'userrights',
+ 'userrights-interwiki',
+ 'writeapi',
);
/**
* \string Cached results of getAllRights()
@@ -581,11 +588,12 @@ class User {
* @return \bool True or false
*/
static function isCreatableName( $name ) {
+ global $wgInvalidUsernameCharacters;
return
self::isUsableName( $name ) &&
// Registration-time character blacklisting...
- strpos( $name, '@' ) === false;
+ !preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name );
}
/**
@@ -886,6 +894,8 @@ class User {
$dbr = wfGetDB( DB_MASTER );
$s = $dbr->selectRow( 'user', '*', array( 'user_id' => $this->mId ), __METHOD__ );
+ wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) );
+
if ( $s !== false ) {
# Initialise user table data
$this->loadFromRow( $s );
@@ -909,7 +919,7 @@ class User {
$this->mDataLoaded = true;
if ( isset( $row->user_id ) ) {
- $this->mId = $row->user_id;
+ $this->mId = intval( $row->user_id );
}
$this->mName = $row->user_name;
$this->mRealName = $row->user_real_name;
@@ -1013,9 +1023,14 @@ class User {
* @return \type{\arrayof{\string}} Array of user toggle names
*/
static function getToggles() {
- global $wgContLang;
+ global $wgContLang, $wgUseRCPatrol;
$extraToggles = array();
wfRunHooks( 'UserToggles', array( &$extraToggles ) );
+ if( $wgUseRCPatrol ) {
+ $extraToggles[] = 'hidepatrolled';
+ $extraToggles[] = 'newpageshidepatrolled';
+ $extraToggles[] = 'watchlisthidepatrolled';
+ }
return array_merge( self::$mToggles, $extraToggles, $wgContLang->getExtraUserToggles() );
}
@@ -1149,10 +1164,17 @@ class User {
*/
public function isPingLimitable() {
global $wgRateLimitsExcludedGroups;
+ global $wgRateLimitsExcludedIPs;
if( array_intersect( $this->getEffectiveGroups(), $wgRateLimitsExcludedGroups ) ) {
// Deprecated, but kept for backwards-compatibility config
return false;
}
+ if( in_array( wfGetIP(), $wgRateLimitsExcludedIPs ) ) {
+ // No other good way currently to disable rate limits
+ // for specific IPs. :P
+ // But this is a crappy hack and should die.
+ return false;
+ }
return !$this->isAllowed('noratelimit');
}
@@ -1309,6 +1331,15 @@ class User {
}
/**
+ * If user is blocked, return the ID for the block
+ * @return \int Block ID
+ */
+ function getBlockId() {
+ $this->getBlockedStatus();
+ return ($this->mBlock ? $this->mBlock->mId : false);
+ }
+
+ /**
* Check if user is blocked on all wikis.
* Do not use for actual edit permission checks!
* This is intented for quick UI checks.
@@ -1909,6 +1940,13 @@ class User {
}
$this->mOptions[$oname] = $val;
}
+
+ /**
+ * Reset all options to the site defaults
+ */
+ function restoreOptions() {
+ $this->mOptions = User::getDefaultOptions();
+ }
/**
* Get the user's preferred date format.
@@ -1983,7 +2021,7 @@ class User {
* @return \int User'e edit count
*/
function getEditCount() {
- if ($this->mId) {
+ if ($this->getId()) {
if ( !isset( $this->mEditCount ) ) {
/* Populate the count, if it has not been populated yet */
$this->mEditCount = User::edits($this->mId);
@@ -2073,11 +2111,15 @@ class User {
* @param $action \string action to be checked
* @return \bool True if action is allowed, else false
*/
- function isAllowed($action='') {
+ function isAllowed( $action = '' ) {
if ( $action === '' )
- // In the spirit of DWIM
- return true;
-
+ return true; // In the spirit of DWIM
+ # Patrolling may not be enabled
+ if( $action === 'patrol' || $action === 'autopatrol' ) {
+ global $wgUseRCPatrol, $wgUseNPPatrol;
+ if( !$wgUseRCPatrol && !$wgUseNPPatrol )
+ return false;
+ }
# Use strict parameter to avoid matching numeric 0 accidentally inserted
# by misconfiguration: 0 == 'foo'
return in_array( $action, $this->getRights(), true );
@@ -2705,7 +2747,14 @@ class User {
* @return \bool True if matches, false otherwise
*/
function checkTemporaryPassword( $plaintext ) {
- return self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() );
+ global $wgNewPasswordExpiry;
+ if( self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ) ) {
+ $this->load();
+ $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgNewPasswordExpiry;
+ return ( time() < $expiry );
+ } else {
+ return false;
+ }
}
/**
diff --git a/includes/UserArray.php b/includes/UserArray.php
index a2f54b7f..d48a4440 100644
--- a/includes/UserArray.php
+++ b/includes/UserArray.php
@@ -12,6 +12,17 @@ abstract class UserArray implements Iterator {
return $userArray;
}
+ static function newFromIDs( $ids ) {
+ $ids = array_map( 'intval', (array)$ids ); // paranoia
+ if ( !$ids )
+ // Database::select() doesn't like empty arrays
+ return new ArrayIterator(array());
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'user', '*', array( 'user_id' => $ids ),
+ __METHOD__ );
+ return self::newFromResult( $res );
+ }
+
protected static function newFromResult_internal( $res ) {
$userArray = new UserArrayFromResult( $res );
return $userArray;
diff --git a/includes/UserMailer.php b/includes/UserMailer.php
index ab1a740b..b6484935 100644
--- a/includes/UserMailer.php
+++ b/includes/UserMailer.php
@@ -40,7 +40,7 @@ class MailAddress {
} else {
$this->address = strval( $address );
$this->name = strval( $name );
- $this->reaName = strval( $realName );
+ $this->realName = strval( $realName );
}
}
@@ -101,7 +101,7 @@ class UserMailer {
* @param $from MailAddress: sender's email
* @param $subject String: email's subject.
* @param $body String: email's text.
- * @param $replyto String: optional reply-to email (default: null).
+ * @param $replyto MailAddress: optional reply-to email (default: null).
* @param $contentType String: optional custom Content-Type
* @return mixed True on success, a WikiError object on failure.
*/
@@ -281,11 +281,44 @@ class EmailNotification {
* @param $oldid (default: false)
*/
function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false) {
- global $wgEnotifUseJobQ;
+ global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker;
- if( $title->getNamespace() < 0 )
+ if ($title->getNamespace() < 0)
return;
+ // Build a list of users to notfiy
+ $watchers = array();
+ if ($wgEnotifWatchlist || $wgShowUpdatedMarker) {
+ $dbw = wfGetDB( DB_MASTER );
+ $res = $dbw->select( array( 'watchlist' ),
+ array( 'wl_user' ),
+ array(
+ 'wl_title' => $title->getDBkey(),
+ 'wl_namespace' => $title->getNamespace(),
+ 'wl_user != ' . intval( $editor->getID() ),
+ 'wl_notificationtimestamp IS NULL',
+ ), __METHOD__
+ );
+ while ($row = $dbw->fetchObject( $res ) ) {
+ $watchers[] = intval( $row->wl_user );
+ }
+ if ($watchers) {
+ // Update wl_notificationtimestamp for all watching users except
+ // the editor
+ $dbw->begin();
+ $dbw->update( 'watchlist',
+ array( /* SET */
+ 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp )
+ ), array( /* WHERE */
+ 'wl_title' => $title->getDBkey(),
+ 'wl_namespace' => $title->getNamespace(),
+ 'wl_user' => $watchers
+ ), __METHOD__
+ );
+ $dbw->commit();
+ }
+ }
+
if ($wgEnotifUseJobQ) {
$params = array(
"editor" => $editor->getName(),
@@ -293,11 +326,12 @@ class EmailNotification {
"timestamp" => $timestamp,
"summary" => $summary,
"minorEdit" => $minorEdit,
- "oldid" => $oldid);
+ "oldid" => $oldid,
+ "watchers" => $watchers);
$job = new EnotifNotifyJob( $title, $params );
$job->insert();
} else {
- $this->actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid);
+ $this->actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers );
}
}
@@ -310,16 +344,16 @@ class EmailNotification {
*
* @param $editor User object
* @param $title Title object
- * @param $timestamp
- * @param $summary
- * @param $minorEdit
- * @param $oldid (default: false)
+ * @param $timestamp string Edit timestamp
+ * @param $summary string Edit summary
+ * @param $minorEdit bool
+ * @param $oldid int Revision ID
+ * @param $watchers array of user IDs
*/
- function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid=false) {
-
+ function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers) {
# we use $wgPasswordSender as sender's address
global $wgEnotifWatchlist;
- global $wgEnotifMinorEdits, $wgEnotifUserTalk, $wgShowUpdatedMarker;
+ global $wgEnotifMinorEdits, $wgEnotifUserTalk;
global $wgEnotifImpersonal;
wfProfileIn( __METHOD__ );
@@ -364,30 +398,12 @@ class EmailNotification {
if ( $wgEnotifWatchlist ) {
// Send updates to watchers other than the current editor
- $userCondition = 'wl_user != ' . $editor->getID();
- if ( $userTalkId !== false ) {
- // Already sent an email to this person
- $userCondition .= ' AND wl_user != ' . intval( $userTalkId );
- }
- $dbr = wfGetDB( DB_SLAVE );
-
- list( $user ) = $dbr->tableNamesN( 'user' );
-
- $res = $dbr->select( array( 'watchlist', 'user' ),
- array( "$user.*" ),
- array(
- 'wl_user=user_id',
- 'wl_title' => $title->getDBkey(),
- 'wl_namespace' => $title->getNamespace(),
- $userCondition,
- 'wl_notificationtimestamp IS NULL',
- ), __METHOD__ );
- $userArray = UserArray::newFromResult( $res );
-
+ $userArray = UserArray::newFromIDs( $watchers );
foreach ( $userArray as $watchingUser ) {
if ( $watchingUser->getOption( 'enotifwatchlistpages' ) &&
( !$minorEdit || $watchingUser->getOption('enotifminoredits') ) &&
- $watchingUser->isEmailConfirmed() )
+ $watchingUser->isEmailConfirmed() &&
+ $watchingUser->getID() != $userTalkId )
{
$this->compose( $watchingUser );
}
@@ -402,28 +418,8 @@ class EmailNotification {
}
$this->sendMails();
-
- $latestTimestamp = Revision::getTimestampFromId( $title, $title->getLatestRevID() );
- // Do not update watchlists if something else already did.
- if ( $timestamp >= $latestTimestamp && ($wgShowUpdatedMarker || $wgEnotifWatchlist) ) {
- # Mark the changed watch-listed page with a timestamp, so that the page is
- # listed with an "updated since your last visit" icon in the watch list. Do
- # not do this to users for their own edits.
- $dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'watchlist',
- array( /* SET */
- 'wl_notificationtimestamp' => $dbw->timestamp($timestamp)
- ), array( /* WHERE */
- 'wl_title' => $title->getDBkey(),
- 'wl_namespace' => $title->getNamespace(),
- 'wl_notificationtimestamp IS NULL',
- 'wl_user != ' . $editor->getID()
- ), __METHOD__
- );
- }
-
wfProfileOut( __METHOD__ );
- } # function NotifyOnChange
+ }
/**
* @private
@@ -563,7 +559,7 @@ class EmailNotification {
* @private
*/
function sendPersonalised( $watchingUser ) {
- global $wgLang, $wgEnotifUseRealName;
+ global $wgContLang, $wgEnotifUseRealName;
// From the PHP manual:
// Note: The to parameter cannot be an address in the form of "Something <someone@example.com>".
// The mail command will not parse this properly while talking with the MTA.
@@ -577,7 +573,7 @@ class EmailNotification {
# expressed in terms of individual local time of the notification
# recipient, i.e. watching user
$body = str_replace('$PAGEEDITDATE',
- $wgLang->timeanddate( $this->timestamp, true, false, $timecorrection ),
+ $wgContLang->timeanddate( $this->timestamp, true, false, $timecorrection ),
$body);
return UserMailer::send($to, $this->from, $this->subject, $body, $this->replyto);
@@ -588,7 +584,7 @@ class EmailNotification {
* mailing. Takes an array of MailAddress objects.
*/
function sendImpersonal( $addresses ) {
- global $wgLang;
+ global $wgContLang;
if (empty($addresses))
return;
@@ -597,7 +593,7 @@ class EmailNotification {
array( '$WATCHINGUSERNAME',
'$PAGEEDITDATE'),
array( wfMsgForContent('enotif_impersonal_salutation'),
- $wgLang->timeanddate($this->timestamp, true, false, false)),
+ $wgContLang->timeanddate($this->timestamp, true, false, false)),
$this->body);
return UserMailer::send($addresses, $this->from, $this->subject, $body, $this->replyto);
diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php
index 2d2d34f1..a2c1f036 100644
--- a/includes/WatchedItem.php
+++ b/includes/WatchedItem.php
@@ -38,11 +38,10 @@ class WatchedItem {
public function isWatched() {
# Pages and their talk pages are considered equivalent for watching;
# remember that talk namespaces are numbered as page namespace+1.
- $fname = 'WatchedItem::isWatched';
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'watchlist', 1, array( 'wl_user' => $this->id, 'wl_namespace' => $this->ns,
- 'wl_title' => $this->ti ), $fname );
+ 'wl_title' => $this->ti ), __METHOD__ );
$iswatched = ($dbr->numRows( $res ) > 0) ? 1 : 0;
return $iswatched;
}
@@ -53,31 +52,30 @@ class WatchedItem {
* @return bool (always true)
*/
public function addWatch() {
- $fname = 'WatchedItem::addWatch';
- wfProfileIn( $fname );
+ wfProfileIn( __METHOD__ );
// Use INSERT IGNORE to avoid overwriting the notification timestamp
// if there's already an entry for this page
$dbw = wfGetDB( DB_MASTER );
$dbw->insert( 'watchlist',
array(
- 'wl_user' => $this->id,
- 'wl_namespace' => ($this->ns & ~1),
+ 'wl_user' => $this->id,
+ 'wl_namespace' => MWNamespace::getSubject($this->ns),
'wl_title' => $this->ti,
'wl_notificationtimestamp' => NULL
- ), $fname, 'IGNORE' );
+ ), __METHOD__, 'IGNORE' );
// Every single watched page needs now to be listed in watchlist;
// namespace:page and namespace_talk:page need separate entries:
$dbw->insert( 'watchlist',
array(
'wl_user' => $this->id,
- 'wl_namespace' => ($this->ns | 1 ),
+ 'wl_namespace' => MWNamespace::getTalk($this->ns),
'wl_title' => $this->ti,
'wl_notificationtimestamp' => NULL
- ), $fname, 'IGNORE' );
+ ), __METHOD__, 'IGNORE' );
- wfProfileOut( $fname );
+ wfProfileOut( __METHOD__ );
return true;
}
@@ -86,16 +84,14 @@ class WatchedItem {
* @return bool
*/
public function removeWatch() {
- $fname = 'WatchedItem::removeWatch';
-
$success = false;
$dbw = wfGetDB( DB_MASTER );
$dbw->delete( 'watchlist',
array(
'wl_user' => $this->id,
- 'wl_namespace' => ($this->ns & ~1),
+ 'wl_namespace' => MWNamespace::getSubject($this->ns),
'wl_title' => $this->ti
- ), $fname
+ ), __METHOD__
);
if ( $dbw->affectedRow