From 370e83bb0dfd0c70de268c93bf07ad5ee0897192 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Fri, 15 Aug 2008 01:29:47 +0200 Subject: Update auf 1.13.0 --- includes/AjaxDispatcher.php | 23 +- includes/AjaxFunctions.php | 33 +- includes/AjaxResponse.php | 10 +- includes/Article.php | 708 ++- includes/AuthPlugin.php | 4 +- includes/AutoLoader.php | 669 +-- includes/Autopromote.php | 24 +- includes/BagOStuff.php | 135 +- includes/Block.php | 138 +- includes/CacheDependency.php | 46 +- includes/Category.php | 261 + includes/CategoryPage.php | 125 +- includes/Categoryfinder.php | 2 - includes/ChangesFeed.php | 129 + includes/ChangesList.php | 471 +- includes/Credits.php | 4 +- includes/DatabaseFunctions.php | 4 +- includes/DefaultSettings.php | 1107 +++-- includes/Defines.php | 83 +- includes/DifferenceEngine.php | 367 +- includes/DjVuImage.php | 55 +- includes/DoubleRedirectJob.php | 166 + includes/EditPage.php | 590 ++- includes/EmaillingJob.php | 5 +- includes/EnotifNotifyJob.php | 24 +- includes/Exception.php | 91 +- includes/Exif.php | 15 +- includes/Export.php | 149 +- includes/ExternalEdit.php | 3 - includes/ExternalStore.php | 34 +- includes/ExternalStoreDB.php | 20 +- includes/ExternalStoreHttp.php | 5 +- includes/FakeTitle.php | 2 - includes/Feed.php | 2 - includes/FeedUtils.php | 152 + includes/FileDeleteForm.php | 142 +- includes/FileRevertForm.php | 91 +- includes/FileStore.php | 80 +- includes/FormOptions.php | 202 + includes/GlobalFunctions.php | 632 ++- includes/HTMLCacheUpdate.php | 33 +- includes/HTMLFileCache.php | 17 +- includes/HistoryBlob.php | 8 +- includes/Hooks.php | 7 +- includes/HttpFunctions.php | 3 +- includes/IP.php | 34 +- includes/ImageFunctions.php | 13 +- includes/ImageGallery.php | 29 +- includes/ImagePage.php | 647 ++- includes/ImageQueryPage.php | 5 +- includes/JobQueue.php | 57 +- includes/Licenses.php | 5 +- includes/LinkBatch.php | 64 +- includes/LinkCache.php | 121 +- includes/LinkFilter.php | 1 - includes/Linker.php | 464 +- includes/LinksUpdate.php | 227 +- includes/LogEventsList.php | 742 +++ includes/LogPage.php | 112 +- includes/MacBinary.php | 4 +- includes/MagicWord.php | 68 +- includes/Math.php | 9 +- includes/MediaTransformOutput.php | 51 +- includes/MemcachedSessions.php | 4 +- includes/MessageCache.php | 726 +-- includes/Metadata.php | 4 +- includes/MimeMagic.php | 51 +- includes/Namespace.php | 55 +- includes/NamespaceCompat.php | 9 + includes/ObjectCache.php | 11 +- includes/OutputHandler.php | 16 +- includes/OutputPage.php | 451 +- includes/PageHistory.php | 258 +- includes/PageQueryPage.php | 9 +- includes/Pager.php | 260 +- includes/PatrolLog.php | 6 +- includes/PrefixSearch.php | 85 +- includes/Profiler.php | 243 +- includes/ProfilerSimple.php | 17 +- includes/ProfilerSimpleText.php | 35 + includes/ProfilerSimpleUDP.php | 11 +- includes/ProfilerStub.php | 46 +- includes/ProtectionForm.php | 221 +- includes/ProxyTools.php | 48 +- includes/QueryPage.php | 56 +- includes/RawPage.php | 20 +- includes/RecentChange.php | 125 +- includes/RefreshLinksJob.php | 5 +- includes/Revision.php | 283 +- includes/Sanitizer.php | 35 +- includes/SearchEngine.php | 831 +++- includes/SearchMySQL.php | 71 +- includes/SearchMySQL4.php | 60 +- includes/SearchOracle.php | 13 +- includes/SearchPostgres.php | 36 +- includes/SearchUpdate.php | 10 +- includes/Setup.php | 78 +- includes/SiteConfiguration.php | 10 +- includes/SiteStats.php | 19 +- includes/Skin.php | 229 +- includes/SkinTemplate.php | 167 +- includes/SpecialPage.php | 170 +- includes/SquidUpdate.php | 19 +- includes/Status.php | 194 + includes/StreamFile.php | 8 +- includes/StringUtils.php | 49 +- includes/StubObject.php | 98 +- includes/Title.php | 619 ++- includes/User.php | 905 ++-- includes/UserArray.php | 62 + includes/UserMailer.php | 85 +- includes/UserRightsProxy.php | 45 +- includes/WatchedItem.php | 13 +- includes/WatchlistEditor.php | 105 +- includes/WebRequest.php | 150 +- includes/WebResponse.php | 2 - includes/WebStart.php | 39 +- includes/Wiki.php | 281 +- includes/WikiError.php | 19 +- includes/Xml.php | 279 +- includes/XmlFunctions.php | 6 +- includes/XmlTypeCheck.php | 22 +- includes/ZhClient.php | 17 +- includes/ZhConversion.php | 4946 +++++++++++++++---- includes/api/ApiBase.php | 134 +- includes/api/ApiBlock.php | 10 +- includes/api/ApiDelete.php | 117 +- includes/api/ApiEditPage.php | 299 ++ includes/api/ApiEmailUser.php | 114 + includes/api/ApiExpandTemplates.php | 38 +- includes/api/ApiFeedWatchlist.php | 33 +- includes/api/ApiFormatBase.php | 51 +- includes/api/ApiFormatDbg.php | 5 +- includes/api/ApiFormatJson.php | 7 +- includes/api/ApiFormatJson_json.php | 19 +- includes/api/ApiFormatPhp.php | 5 +- includes/api/ApiFormatTxt.php | 5 +- includes/api/ApiFormatWddx.php | 5 +- includes/api/ApiFormatXml.php | 34 +- includes/api/ApiFormatYaml.php | 5 +- includes/api/ApiFormatYaml_spyc.php | 235 +- includes/api/ApiHelp.php | 7 +- includes/api/ApiLogin.php | 73 +- includes/api/ApiLogout.php | 10 +- includes/api/ApiMain.php | 150 +- includes/api/ApiMove.php | 50 +- includes/api/ApiOpenSearch.php | 22 +- includes/api/ApiPageSet.php | 200 +- includes/api/ApiParamInfo.php | 11 +- includes/api/ApiParse.php | 83 +- includes/api/ApiProtect.php | 15 +- includes/api/ApiQuery.php | 67 +- includes/api/ApiQueryAllCategories.php | 63 +- includes/api/ApiQueryAllLinks.php | 49 +- includes/api/ApiQueryAllUsers.php | 81 +- includes/api/ApiQueryAllimages.php | 205 + includes/api/ApiQueryAllmessages.php | 25 +- includes/api/ApiQueryAllpages.php | 48 +- includes/api/ApiQueryBacklinks.php | 371 +- includes/api/ApiQueryBase.php | 208 +- includes/api/ApiQueryBlocks.php | 59 +- includes/api/ApiQueryCategories.php | 73 +- includes/api/ApiQueryCategoryInfo.php | 91 + includes/api/ApiQueryCategoryMembers.php | 75 +- includes/api/ApiQueryDeletedrevs.php | 30 +- includes/api/ApiQueryExtLinksUsage.php | 65 +- includes/api/ApiQueryExternalLinks.php | 51 +- includes/api/ApiQueryImageInfo.php | 162 +- includes/api/ApiQueryImages.php | 70 +- includes/api/ApiQueryInfo.php | 344 +- includes/api/ApiQueryLangLinks.php | 61 +- includes/api/ApiQueryLinks.php | 78 +- includes/api/ApiQueryLogEvents.php | 57 +- includes/api/ApiQueryRandom.php | 16 +- includes/api/ApiQueryRecentChanges.php | 120 +- includes/api/ApiQueryRevisions.php | 183 +- includes/api/ApiQuerySearch.php | 19 +- includes/api/ApiQuerySiteinfo.php | 304 +- includes/api/ApiQueryUserContributions.php | 69 +- includes/api/ApiQueryUserInfo.php | 18 +- includes/api/ApiQueryUsers.php | 47 +- includes/api/ApiQueryWatchlist.php | 29 +- includes/api/ApiResult.php | 36 +- includes/api/ApiRollback.php | 13 +- includes/api/ApiUnblock.php | 11 +- includes/api/ApiUndelete.php | 17 +- includes/cbt/CBTCompiler.php | 35 +- includes/cbt/CBTProcessor.php | 81 +- includes/cbt/README | 44 +- includes/db/Database.php | 2699 +++++++++++ includes/db/DatabaseMssql.php | 1029 ++++ includes/db/DatabaseOracle.php | 720 +++ includes/db/DatabasePostgres.php | 1394 ++++++ includes/db/DatabaseSqlite.php | 405 ++ includes/db/LBFactory.php | 261 + includes/db/LBFactory_Multi.php | 233 + includes/db/LoadBalancer.php | 918 ++++ includes/db/LoadMonitor.php | 121 + includes/filerepo/ArchivedFile.php | 72 +- includes/filerepo/FSRepo.php | 44 +- includes/filerepo/File.php | 221 +- includes/filerepo/FileRepo.php | 174 +- includes/filerepo/FileRepoStatus.php | 151 +- includes/filerepo/ForeignAPIFile.php | 101 + includes/filerepo/ForeignAPIRepo.php | 110 + includes/filerepo/ForeignDBFile.php | 33 +- includes/filerepo/ForeignDBRepo.php | 13 +- includes/filerepo/ForeignDBViaLBRepo.php | 39 + includes/filerepo/Image.php | 74 + includes/filerepo/LocalFile.php | 533 ++- includes/filerepo/LocalRepo.php | 103 +- includes/filerepo/NullRepo.php | 8 +- includes/filerepo/OldLocalFile.php | 192 +- includes/filerepo/README | 14 +- includes/filerepo/RepoGroup.php | 74 +- includes/filerepo/UnregisteredLocalFile.php | 9 +- includes/media/BMP.php | 8 +- includes/media/Bitmap.php | 22 +- includes/media/DjVu.php | 20 +- includes/media/Generic.php | 56 +- includes/media/SVG.php | 18 +- includes/memcached-client.php | 79 +- includes/mime.info | 15 +- includes/mime.types | 9 +- includes/normal/CleanUpTest.php | 3 +- includes/normal/Makefile | 9 +- includes/normal/RandomTest.php | 4 +- includes/normal/Utf8Case.php | 2078 ++++++++ includes/normal/Utf8CaseGenerate.php | 112 + includes/normal/Utf8Test.php | 4 +- includes/normal/UtfNormal.php | 56 +- includes/normal/UtfNormalBench.php | 4 +- includes/normal/UtfNormalData.inc | 2 +- includes/normal/UtfNormalDataK.inc | 2 +- includes/normal/UtfNormalDefines.php | 53 + includes/normal/UtfNormalGenerate.php | 4 +- includes/normal/UtfNormalTest.php | 4 +- includes/normal/UtfNormalUtil.php | 5 +- includes/parser/CoreParserFunctions.php | 385 ++ includes/parser/DateFormatter.php | 283 ++ includes/parser/Parser.php | 5002 ++++++++++++++++++++ includes/parser/ParserCache.php | 116 + includes/parser/ParserOptions.php | 149 + includes/parser/ParserOutput.php | 206 + includes/parser/Parser_DiffTest.php | 87 + includes/parser/Parser_OldPP.php | 4944 +++++++++++++++++++ includes/parser/Preprocessor.php | 163 + includes/parser/Preprocessor_DOM.php | 1421 ++++++ includes/parser/Preprocessor_Hash.php | 1539 ++++++ includes/proxy_check.php | 2 - includes/specials/SpecialAllmessages.php | 217 + includes/specials/SpecialAllpages.php | 404 ++ includes/specials/SpecialAncientpages.php | 60 + includes/specials/SpecialBlankpage.php | 6 + includes/specials/SpecialBlockip.php | 494 ++ includes/specials/SpecialBlockme.php | 37 + includes/specials/SpecialBooksources.php | 110 + includes/specials/SpecialBrokenRedirects.php | 93 + includes/specials/SpecialCategories.php | 112 + includes/specials/SpecialConfirmemail.php | 139 + includes/specials/SpecialContributions.php | 470 ++ includes/specials/SpecialDeadendpages.php | 62 + includes/specials/SpecialDisambiguations.php | 108 + includes/specials/SpecialDoubleRedirects.php | 103 + includes/specials/SpecialEmailuser.php | 286 ++ includes/specials/SpecialExport.php | 284 ++ includes/specials/SpecialFewestrevisions.php | 74 + includes/specials/SpecialFileDuplicateSearch.php | 135 + includes/specials/SpecialFilepath.php | 53 + includes/specials/SpecialImagelist.php | 161 + includes/specials/SpecialImport.php | 1154 +++++ includes/specials/SpecialIpblocklist.php | 427 ++ includes/specials/SpecialListgrouprights.php | 112 + includes/specials/SpecialListredirects.php | 58 + includes/specials/SpecialListusers.php | 235 + includes/specials/SpecialLockdb.php | 131 + includes/specials/SpecialLog.php | 65 + includes/specials/SpecialLonelypages.php | 58 + includes/specials/SpecialLongpages.php | 31 + includes/specials/SpecialMIMEsearch.php | 138 + includes/specials/SpecialMergeHistory.php | 448 ++ includes/specials/SpecialMostcategories.php | 58 + includes/specials/SpecialMostimages.php | 54 + includes/specials/SpecialMostlinked.php | 95 + includes/specials/SpecialMostlinkedcategories.php | 78 + includes/specials/SpecialMostlinkedtemplates.php | 132 + includes/specials/SpecialMostrevisions.php | 64 + includes/specials/SpecialMovepage.php | 452 ++ includes/specials/SpecialNewimages.php | 211 + includes/specials/SpecialNewpages.php | 437 ++ includes/specials/SpecialPopularpages.php | 67 + includes/specials/SpecialPreferences.php | 1126 +++++ includes/specials/SpecialPrefixindex.php | 152 + includes/specials/SpecialProtectedpages.php | 309 ++ includes/specials/SpecialProtectedtitles.php | 216 + includes/specials/SpecialRandompage.php | 100 + includes/specials/SpecialRandomredirect.php | 19 + includes/specials/SpecialRecentchanges.php | 662 +++ includes/specials/SpecialRecentchangeslinked.php | 178 + includes/specials/SpecialResetpass.php | 167 + includes/specials/SpecialRevisiondelete.php | 1474 ++++++ includes/specials/SpecialSearch.php | 651 +++ includes/specials/SpecialShortpages.php | 98 + includes/specials/SpecialSpecialpages.php | 82 + includes/specials/SpecialStatistics.php | 93 + .../specials/SpecialUncategorizedcategories.php | 30 + includes/specials/SpecialUncategorizedimages.php | 48 + includes/specials/SpecialUncategorizedpages.php | 55 + .../specials/SpecialUncategorizedtemplates.php | 33 + includes/specials/SpecialUndelete.php | 1276 +++++ includes/specials/SpecialUnlockdb.php | 107 + includes/specials/SpecialUnusedcategories.php | 46 + includes/specials/SpecialUnusedimages.php | 60 + includes/specials/SpecialUnusedtemplates.php | 54 + includes/specials/SpecialUnwatchedpages.php | 68 + includes/specials/SpecialUpload.php | 1755 +++++++ includes/specials/SpecialUploadMogile.php | 135 + includes/specials/SpecialUserlogin.php | 929 ++++ includes/specials/SpecialUserlogout.php | 23 + includes/specials/SpecialUserrights.php | 589 +++ includes/specials/SpecialVersion.php | 391 ++ includes/specials/SpecialWantedcategories.php | 90 + includes/specials/SpecialWantedpages.php | 131 + includes/specials/SpecialWatchlist.php | 383 ++ includes/specials/SpecialWhatlinkshere.php | 408 ++ includes/specials/SpecialWithoutinterwiki.php | 88 + includes/templates/NoLocalSettings.php | 5 + includes/templates/Userlogin.php | 68 +- includes/tidy.conf | 6 +- includes/zhtable/Makefile | 118 +- includes/zhtable/README | 21 +- includes/zhtable/simp2trad.manual | 96 +- includes/zhtable/simp2trad_noconvert.manual | 4 + includes/zhtable/simp2trad_supp_set.manual | 2 + includes/zhtable/simpphrases.manual | 389 ++ includes/zhtable/simpphrases_exclude.manual | 0 includes/zhtable/toCN.manual | 11 +- includes/zhtable/toHK.manual | 10 + includes/zhtable/toSG.manual | 6 +- includes/zhtable/toSimp.manual | 21 + includes/zhtable/toTW.manual | 34 +- includes/zhtable/toTrad.manual | 42 + includes/zhtable/trad2simp.manual | 11 + includes/zhtable/trad2simp_noconvert.manual | 4 + includes/zhtable/trad2simp_supp_set.manual | 1 + includes/zhtable/trad2simp_supp_unset.manual | 0 includes/zhtable/tradphrases.manual | 1522 +++++- includes/zhtable/tradphrases_exclude.manual | 161 + 348 files changed, 67745 insertions(+), 8718 deletions(-) create mode 100644 includes/Category.php create mode 100644 includes/ChangesFeed.php create mode 100644 includes/DoubleRedirectJob.php create mode 100644 includes/FeedUtils.php create mode 100644 includes/FormOptions.php create mode 100644 includes/LogEventsList.php create mode 100644 includes/NamespaceCompat.php create mode 100644 includes/ProfilerSimpleText.php create mode 100644 includes/Status.php create mode 100644 includes/UserArray.php create mode 100644 includes/api/ApiEditPage.php create mode 100644 includes/api/ApiEmailUser.php create mode 100644 includes/api/ApiQueryAllimages.php create mode 100644 includes/api/ApiQueryCategoryInfo.php create mode 100644 includes/db/Database.php create mode 100644 includes/db/DatabaseMssql.php create mode 100644 includes/db/DatabaseOracle.php create mode 100644 includes/db/DatabasePostgres.php create mode 100644 includes/db/DatabaseSqlite.php create mode 100644 includes/db/LBFactory.php create mode 100644 includes/db/LBFactory_Multi.php create mode 100644 includes/db/LoadBalancer.php create mode 100644 includes/db/LoadMonitor.php create mode 100644 includes/filerepo/ForeignAPIFile.php create mode 100644 includes/filerepo/ForeignAPIRepo.php create mode 100644 includes/filerepo/ForeignDBViaLBRepo.php create mode 100644 includes/filerepo/Image.php create mode 100644 includes/normal/Utf8Case.php create mode 100644 includes/normal/Utf8CaseGenerate.php create mode 100644 includes/normal/UtfNormalDefines.php create mode 100644 includes/parser/CoreParserFunctions.php create mode 100644 includes/parser/DateFormatter.php create mode 100644 includes/parser/Parser.php create mode 100644 includes/parser/ParserCache.php create mode 100644 includes/parser/ParserOptions.php create mode 100644 includes/parser/ParserOutput.php create mode 100644 includes/parser/Parser_DiffTest.php create mode 100644 includes/parser/Parser_OldPP.php create mode 100644 includes/parser/Preprocessor.php create mode 100644 includes/parser/Preprocessor_DOM.php create mode 100644 includes/parser/Preprocessor_Hash.php create mode 100644 includes/specials/SpecialAllmessages.php create mode 100644 includes/specials/SpecialAllpages.php create mode 100644 includes/specials/SpecialAncientpages.php create mode 100644 includes/specials/SpecialBlankpage.php create mode 100644 includes/specials/SpecialBlockip.php create mode 100644 includes/specials/SpecialBlockme.php create mode 100644 includes/specials/SpecialBooksources.php create mode 100644 includes/specials/SpecialBrokenRedirects.php create mode 100644 includes/specials/SpecialCategories.php create mode 100644 includes/specials/SpecialConfirmemail.php create mode 100644 includes/specials/SpecialContributions.php create mode 100644 includes/specials/SpecialDeadendpages.php create mode 100644 includes/specials/SpecialDisambiguations.php create mode 100644 includes/specials/SpecialDoubleRedirects.php create mode 100644 includes/specials/SpecialEmailuser.php create mode 100644 includes/specials/SpecialExport.php create mode 100644 includes/specials/SpecialFewestrevisions.php create mode 100644 includes/specials/SpecialFileDuplicateSearch.php create mode 100644 includes/specials/SpecialFilepath.php create mode 100644 includes/specials/SpecialImagelist.php create mode 100644 includes/specials/SpecialImport.php create mode 100644 includes/specials/SpecialIpblocklist.php create mode 100644 includes/specials/SpecialListgrouprights.php create mode 100644 includes/specials/SpecialListredirects.php create mode 100644 includes/specials/SpecialListusers.php create mode 100644 includes/specials/SpecialLockdb.php create mode 100644 includes/specials/SpecialLog.php create mode 100644 includes/specials/SpecialLonelypages.php create mode 100644 includes/specials/SpecialLongpages.php create mode 100644 includes/specials/SpecialMIMEsearch.php create mode 100644 includes/specials/SpecialMergeHistory.php create mode 100644 includes/specials/SpecialMostcategories.php create mode 100644 includes/specials/SpecialMostimages.php create mode 100644 includes/specials/SpecialMostlinked.php create mode 100644 includes/specials/SpecialMostlinkedcategories.php create mode 100644 includes/specials/SpecialMostlinkedtemplates.php create mode 100644 includes/specials/SpecialMostrevisions.php create mode 100644 includes/specials/SpecialMovepage.php create mode 100644 includes/specials/SpecialNewimages.php create mode 100644 includes/specials/SpecialNewpages.php create mode 100644 includes/specials/SpecialPopularpages.php create mode 100644 includes/specials/SpecialPreferences.php create mode 100644 includes/specials/SpecialPrefixindex.php create mode 100644 includes/specials/SpecialProtectedpages.php create mode 100644 includes/specials/SpecialProtectedtitles.php create mode 100644 includes/specials/SpecialRandompage.php create mode 100644 includes/specials/SpecialRandomredirect.php create mode 100644 includes/specials/SpecialRecentchanges.php create mode 100644 includes/specials/SpecialRecentchangeslinked.php create mode 100644 includes/specials/SpecialResetpass.php create mode 100644 includes/specials/SpecialRevisiondelete.php create mode 100644 includes/specials/SpecialSearch.php create mode 100644 includes/specials/SpecialShortpages.php create mode 100644 includes/specials/SpecialSpecialpages.php create mode 100644 includes/specials/SpecialStatistics.php create mode 100644 includes/specials/SpecialUncategorizedcategories.php create mode 100644 includes/specials/SpecialUncategorizedimages.php create mode 100644 includes/specials/SpecialUncategorizedpages.php create mode 100644 includes/specials/SpecialUncategorizedtemplates.php create mode 100644 includes/specials/SpecialUndelete.php create mode 100644 includes/specials/SpecialUnlockdb.php create mode 100644 includes/specials/SpecialUnusedcategories.php create mode 100644 includes/specials/SpecialUnusedimages.php create mode 100644 includes/specials/SpecialUnusedtemplates.php create mode 100644 includes/specials/SpecialUnwatchedpages.php create mode 100644 includes/specials/SpecialUpload.php create mode 100644 includes/specials/SpecialUploadMogile.php create mode 100644 includes/specials/SpecialUserlogin.php create mode 100644 includes/specials/SpecialUserlogout.php create mode 100644 includes/specials/SpecialUserrights.php create mode 100644 includes/specials/SpecialVersion.php create mode 100644 includes/specials/SpecialWantedcategories.php create mode 100644 includes/specials/SpecialWantedpages.php create mode 100644 includes/specials/SpecialWatchlist.php create mode 100644 includes/specials/SpecialWhatlinkshere.php create mode 100644 includes/specials/SpecialWithoutinterwiki.php create mode 100644 includes/zhtable/simp2trad_noconvert.manual create mode 100644 includes/zhtable/simp2trad_supp_set.manual create mode 100644 includes/zhtable/simpphrases.manual create mode 100644 includes/zhtable/simpphrases_exclude.manual create mode 100644 includes/zhtable/toSimp.manual create mode 100644 includes/zhtable/toTrad.manual create mode 100644 includes/zhtable/trad2simp_noconvert.manual create mode 100644 includes/zhtable/trad2simp_supp_set.manual create mode 100644 includes/zhtable/trad2simp_supp_unset.manual create mode 100644 includes/zhtable/tradphrases_exclude.manual (limited to 'includes') diff --git a/includes/AjaxDispatcher.php b/includes/AjaxDispatcher.php index 7b85ed20..c489cf1c 100644 --- a/includes/AjaxDispatcher.php +++ b/includes/AjaxDispatcher.php @@ -1,5 +1,9 @@ func_name, $wgAjaxExportList ) ) { + wfDebug( __METHOD__ . ' Bad Request for unknown function ' . $this->func_name . "\n" ); + wfHttpError( 400, 'Bad Request', "unknown function " . (string) $this->func_name ); } else { + wfDebug( __METHOD__ . ' dispatching ' . $this->func_name . "\n" ); + if ( strpos( $this->func_name, '::' ) !== false ) { $func = explode( '::', $this->func_name, 2 ); } else { @@ -93,6 +102,10 @@ class AjaxDispatcher { $result = call_user_func_array($func, $this->args); if ( $result === false || $result === NULL ) { + wfDebug( __METHOD__ . ' ERROR while dispatching ' + . $this->func_name . "(" . var_export( $this->args, true ) . "): " + . "no data returned\n" ); + wfHttpError( 500, 'Internal Error', "{$this->func_name} returned no data" ); } @@ -103,9 +116,15 @@ class AjaxDispatcher { $result->sendHeaders(); $result->printText(); + + wfDebug( __METHOD__ . ' dispatch complete for ' . $this->func_name . "\n" ); } } catch (Exception $e) { + wfDebug( __METHOD__ . ' ERROR while dispatching ' + . $this->func_name . "(" . var_export( $this->args, true ) . "): " + . get_class($e) . ": " . $e->getMessage() . "\n" ); + if (!headers_sent()) { wfHttpError( 500, 'Internal Error', $e->getMessage() ); @@ -119,5 +138,3 @@ class AjaxDispatcher { $wgOut = null; } } - - diff --git a/includes/AjaxFunctions.php b/includes/AjaxFunctions.php index ffd3168a..9daca9e5 100644 --- a/includes/AjaxFunctions.php +++ b/includes/AjaxFunctions.php @@ -1,8 +1,7 @@ getSkin(); $output = ''; @@ -84,7 +83,7 @@ function wfSajaxSearch( $term ) { $term = trim( $term ); $term = $wgContLang->checkTitleEncoding( $wgContLang->recodeInput( js_unescape( $term ) ) ); if ( $wgCapitalLinks ) - $term = $wgContLang->ucfirst( $term ); + $term = $wgContLang->ucfirst( $term ); $term_title = Title::newFromText( $term ); $memckey = $term_title ? wfMemcKey( 'ajaxsearch', md5( $term_title->getFullText() ) ) : wfMemcKey( 'ajaxsearch', md5( $term ) ); @@ -97,12 +96,12 @@ function wfSajaxSearch( $term ) { $r = $more = ''; $canSearch = true; - + $results = PrefixSearch::titleSearch( $term, $limit + 1 ); foreach( array_slice( $results, 0, $limit ) as $titleText ) { $r .= '
  • ' . $sk->makeKnownLink( $titleText ) . "
  • \n"; } - + // Hack to check for specials if( $results ) { $t = Title::newFromText( $results[0] ); @@ -128,9 +127,10 @@ function wfSajaxSearch( $term ) { $valid = (bool) $term_title; $term_url = urlencode( $term ); - $term_diplay = htmlspecialchars( $valid ? $term_title->getFullText() : $term ); + $term_normalized = $valid ? $term_title->getFullText() : $term; + $term_display = htmlspecialchars( $term ); $subtitlemsg = ( $valid ? 'searchsubtitle' : 'searchsubtitleinvalid' ); - $subtitle = wfMsgWikiHtml( $subtitlemsg, $term_diplay ); + $subtitle = wfMsgExt( $subtitlemsg, array( 'parse' ), wfEscapeWikiText( $term_normalized ) ); $html = '
    ' . wfMsgHtml( 'hideresults' ) . '
    ' . '

    '.wfMsgHtml('search') @@ -138,15 +138,15 @@ function wfSajaxSearch( $term ) { if( $canSearch ) { $html .= '"; } if( $r ) { - $html .= "

    " . wfMsgHtml( 'articletitles', $term_diplay ) . "

    " + $html .= "

    " . wfMsgHtml( 'articletitles', $term_display ) . "

    " . '' . $more; } @@ -161,7 +161,7 @@ function wfSajaxSearch( $term ) { * Called for AJAX watch/unwatch requests. * @param $pagename Prefixed title string for page to watch/unwatch * @param $watch String 'w' to watch, 'u' to unwatch - * @return String '' or '' on successful watch or unwatch, + * @return String '' or '' on successful watch or unwatch, * respectively, followed by an HTML message to display in the alert box; or * '' on error */ @@ -169,7 +169,7 @@ function wfAjaxWatch($pagename = "", $watch = "") { if(wfReadOnly()) { // redirect to action=(un)watch, which will display the database lock // message - return ''; + return ''; } if('w' !== $watch && 'u' !== $watch) { @@ -206,4 +206,3 @@ function wfAjaxWatch($pagename = "", $watch = "") { return ''.wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() ); } } - diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php index 8fa08539..c79e928b 100644 --- a/includes/AjaxResponse.php +++ b/includes/AjaxResponse.php @@ -1,11 +1,16 @@ mCacheDuration ) { - # If squid caches are configured, tell them to cache the response, + # If squid caches are configured, tell them to cache the response, # and tell the client to always check with the squid. Otherwise, # tell the client to use a cached copy, without a way to purge it. @@ -220,4 +225,3 @@ class AjaxResponse { return true; } } - diff --git a/includes/Article.php b/includes/Article.php index 0544db7d..4d8277bb 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -1,6 +1,7 @@ mTitle || !$this->mTitle->isRedirect()) + return null; + if(!is_null($this->mRedirectTarget)) + return $this->mRedirectTarget; + + # Query the redirect table + $dbr = wfGetDB(DB_SLAVE); + $res = $dbr->select('redirect', + array('rd_namespace', 'rd_title'), + array('rd_from' => $this->getID()), + __METHOD__ + ); + $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 + return $this->mRedirectTarget = $this->insertRedirect(); + } + + /** + * Insert an entry for this page into the redirect table. + * + * Don't call this function directly unless you know what you're doing. + * @return Title object + */ + public function insertRedirect() { + $retval = Title::newFromRedirect($this->getContent()); + if(!$retval) + return null; + $dbw = wfGetDB(DB_MASTER); + $dbw->replace('redirect', array('rd_from'), array( + 'rd_from' => $this->getID(), + 'rd_namespace' => $retval->getNamespace(), + 'rd_title' => $retval->getDBKey() + ), __METHOD__); + return $retval; + } + + /** + * Get the Title object this page redirects to + * * @return mixed false, Title of in-wiki target, or string with URL */ - function followRedirect() { + public function followRedirect() { $text = $this->getContent(); + return self::followRedirectText( $text ); + } + + /** + * Get the Title object this text redirects to + * + * @return mixed false, Title of in-wiki target, or string with URL + */ + public function followRedirectText( $text ) { $rt = Title::newFromRedirect( $text ); - # process if title object is valid and not special:userlogout if( $rt ) { if( $rt->getInterwiki() != '' ) { @@ -92,7 +152,6 @@ class Article { return $rt; } } - // No or invalid redirect return false; } @@ -114,6 +173,7 @@ class Article { $this->mCurID = $this->mUser = $this->mCounter = -1; # Not loaded $this->mRedirectedFrom = null; # Title object if set + $this->mRedirectTarget = null; # Title object if set $this->mUserText = $this->mTimestamp = $this->mComment = ''; $this->mGoodAdjustment = $this->mTotalAdjustment = 0; @@ -138,7 +198,7 @@ class Article { * @return Return the text of this revision */ function getContent() { - global $wgUser, $wgOut; + global $wgUser, $wgOut, $wgMessageCache; wfProfileIn( __METHOD__ ); @@ -147,12 +207,13 @@ class Article { $wgOut->setRobotpolicy( 'noindex,nofollow' ); if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { + $wgMessageCache->loadAllMessages(); $ret = wfMsgWeirdKey ( $this->mTitle->getText() ) ; } else { $ret = wfMsg( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon' ); } - return "
    $ret
    "; + return "
    \n$ret\n
    "; } else { $this->loadContent(); wfProfileOut( __METHOD__ ); @@ -298,13 +359,13 @@ class Article { */ function loadPageData( $data = 'fromdb' ) { if ( $data === 'fromdb' ) { - $dbr = $this->getDB(); + $dbr = wfGetDB( DB_MASTER ); $data = $this->pageDataFromId( $dbr, $this->getId() ); } - $lc =& LinkCache::singleton(); + $lc = LinkCache::singleton(); if ( $data ) { - $lc->addGoodLinkObj( $data->page_id, $this->mTitle ); + $lc->addGoodLinkObj( $data->page_id, $this->mTitle, $data->page_len, $data->page_is_redirect ); $this->mTitle->mArticleID = $data->page_id; @@ -336,15 +397,13 @@ class Article { return $this->mContent; } - $dbr = $this->getDB(); + $dbr = wfGetDB( DB_MASTER ); # Pre-fill content with error message so that if something # fails we'll have something telling us what we intended. $t = $this->mTitle->getPrefixedText(); - if( $oldid ) { - $t .= ',oldid='.$oldid; - } - $this->mContent = wfMsg( 'missingarticle', $t ) ; + $d = $oldid ? wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ) : ''; + $this->mContent = wfMsg( 'missing-article', $t, $d ) ; if( $oldid ) { $revision = Revision::newFromId( $oldid ); @@ -377,15 +436,14 @@ class Article { // FIXME: Horrible, horrible! This content-loading interface just plain sucks. // We should instead work with the Revision object when we need it... - $this->mContent = $revision->userCan( Revision::DELETED_TEXT ) ? $revision->getRawText() : ""; - //$this->mContent = $revision->getText(); + $this->mContent = $revision->revText(); // Loads if user is allowed $this->mUser = $revision->getUser(); $this->mUserText = $revision->getUserText(); $this->mComment = $revision->getComment(); $this->mTimestamp = wfTimestamp( TS_MW, $revision->getTimestamp() ); - $this->mRevIdFetched = $revision->getID(); + $this->mRevIdFetched = $revision->getId(); $this->mContentLoaded = true; $this->mRevision =& $revision; @@ -407,8 +465,10 @@ class Article { * Get the database which should be used for reads * * @return Database + * @deprecated - just call wfGetDB( DB_MASTER ) instead */ function getDB() { + wfDeprecated( __METHOD__ ); return wfGetDB( DB_MASTER ); } @@ -490,6 +550,10 @@ class Article { */ function isRedirect( $text = false ) { if ( $text === false ) { + if ( $this->mDataLoaded ) + return $this->mIsRedirect; + + // Apparently loadPageData was never called $this->loadContent(); $titleObj = Title::newFromRedirect( $this->fetchContent() ); } else { @@ -526,14 +590,14 @@ class Article { $id = $this->getID(); if ( 0 == $id ) return; - $this->mLastRevision = Revision::loadFromPageId( $this->getDB(), $id ); + $this->mLastRevision = Revision::loadFromPageId( wfGetDB( DB_MASTER ), $id ); if( !is_null( $this->mLastRevision ) ) { $this->mUser = $this->mLastRevision->getUser(); $this->mUserText = $this->mLastRevision->getUserText(); $this->mTimestamp = $this->mLastRevision->getTimestamp(); $this->mComment = $this->mLastRevision->getComment(); $this->mMinorEdit = $this->mLastRevision->isMinor(); - $this->mRevIdFetched = $this->mLastRevision->getID(); + $this->mRevIdFetched = $this->mLastRevision->getId(); } } @@ -571,7 +635,6 @@ class Article { } /** - * @todo Document, fixme $offset never used. * @param $limit Integer: default 0. * @param $offset Integer: default 0. */ @@ -593,6 +656,8 @@ class Article { ORDER BY timestamp DESC"; if ($limit > 0) { $sql .= ' LIMIT '.$limit; } + if ($offset > 0) { $sql .= ' OFFSET '.$offset; } + $sql .= ' '. $this->getSelectOptions(); $res = $dbr->query($sql, __METHOD__); @@ -609,7 +674,7 @@ class Article { * This is the default action of the script: just view the page of * the given title. */ - function view() { + function view() { global $wgUser, $wgOut, $wgRequest, $wgContLang; global $wgEnableParserCache, $wgStylePath, $wgParser; global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies; @@ -618,7 +683,7 @@ class Article { wfProfileIn( __METHOD__ ); - $parserCache =& ParserCache::singleton(); + $parserCache = ParserCache::singleton(); $ns = $this->mTitle->getNamespace(); # shortcut # Get variables from query string @@ -689,12 +754,7 @@ class Article { } # Should the parser cache be used? - $pcache = $wgEnableParserCache - && intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 - && $this->exists() - && empty( $oldid ) - && !$this->mTitle->isCssOrJsPage() - && !$this->mTitle->isCssJsSubpage(); + $pcache = $this->useParserCache( $oldid ); wfDebug( 'Article::view using parser cache: ' . ($pcache ? 'yes' : 'no' ) . "\n" ); if ( $wgUser->getOption( 'stubthreshold' ) ) { wfIncrStats( 'pcache_miss_stub' ); @@ -705,7 +765,6 @@ class Article { // This is an internally redirected page view. // We'll need a backlink to the source page for navigation. if ( wfRunHooks( 'ArticleViewRedirect', array( &$this ) ) ) { - $sk = $wgUser->getSkin(); $redir = $sk->makeKnownLinkObj( $this->mRedirectedFrom, '', 'redirect=no' ); $s = wfMsg( 'redirectedfrom', $redir ); $wgOut->setSubtitle( $s ); @@ -722,7 +781,6 @@ class Article { // If it was reported from a trusted site, supply a backlink. global $wgRedirectSources; if( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) { - $sk = $wgUser->getSkin(); $redir = $sk->makeExternalLink( $rdfrom, $rdfrom ); $s = wfMsg( 'redirectedfrom', $redir ); $wgOut->setSubtitle( $s ); @@ -740,16 +798,17 @@ class Article { $outputDone = true; } } + # Fetch content and check for errors if ( !$outputDone ) { $text = $this->getContent(); if ( $text === false ) { # Failed to load, replace text with error message $t = $this->mTitle->getPrefixedText(); if( $oldid ) { - $t .= ',oldid='.$oldid; - $text = wfMsg( 'missingarticle', $t ); + $d = wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ); + $text = wfMsg( 'missing-article', $t, $d ); } else { - $text = wfMsg( 'noarticletext', $t ); + $text = wfMsg( 'noarticletext' ); } } @@ -757,11 +816,11 @@ class Article { if ( !$this->mTitle->userCanRead() ) { $wgOut->loginToUse(); $wgOut->output(); + wfProfileOut( __METHOD__ ); exit; } # We're looking at an old revision - if ( !empty( $oldid ) ) { $wgOut->setRobotpolicy( 'noindex,nofollow' ); if( is_null( $this->mRevision ) ) { @@ -772,6 +831,7 @@ class Article { if( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) { $wgOut->addWikiMsg( 'rev-deleted-text-permission' ); $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); + wfProfileOut( __METHOD__ ); return; } else { $wgOut->addWikiMsg( 'rev-deleted-text-view' ); @@ -779,16 +839,13 @@ class Article { } } } - } - } - if( !$outputDone ) { - $wgOut->setRevisionId( $this->getRevIdFetched() ); + $wgOut->setRevisionId( $this->getRevIdFetched() ); + // Pages containing custom CSS or JavaScript get special treatment 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 ) ) ) { // Wrap the whole lot in a
     and don't parse
    @@ -798,22 +855,9 @@ class Article {
     					$wgOut->addHtml( htmlspecialchars( $this->mContent ) );
     					$wgOut->addHtml( "\n
    \n" ); } - - } - - elseif ( $rt = Title::newFromRedirect( $text ) ) { - # Display redirect - $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr'; - $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png'; - # Don't overwrite the subtitle if this was an old revision - if( !$wasRedirected && $this->isCurrent() ) { - $wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) ); - } - $link = $sk->makeLinkObj( $rt, $rt->getFullText() ); - - $wgOut->addHTML( '#REDIRECT ' . - ''.$link.'' ); - + } else if ( $rt = Title::newFromRedirect( $text ) ) { + # Don't append the subtitle if this was an old revision + $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ); $parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser)); $wgOut->addParserOutputNoText( $parseout ); } else if ( $pcache ) { @@ -840,7 +884,6 @@ class Article { if( !$this->isCurrent() ) { $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting ); } - } } /* title may have been set from the cache */ @@ -850,8 +893,7 @@ class Article { } # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page - if( $ns == NS_USER_TALK && - User::isIP( $this->mTitle->getText() ) ) { + if( $ns == NS_USER_TALK && IP::isValid( $this->mTitle->getText() ) ) { $wgOut->addWikiMsg('anontalkpagetext'); } @@ -875,6 +917,41 @@ class Article { $this->viewUpdates(); wfProfileOut( __METHOD__ ); } + + /* + * Should the parser cache be used? + */ + protected function useParserCache( $oldid ) { + global $wgUser, $wgEnableParserCache; + + return $wgEnableParserCache + && intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 + && $this->exists() + && empty( $oldid ) + && !$this->mTitle->isCssOrJsPage() + && !$this->mTitle->isCssJsSubpage(); + } + + protected function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) { + global $wgParser, $wgOut, $wgContLang, $wgStylePath, $wgUser; + + # Display redirect + $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr'; + $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png'; + + if( $appendSubtitle ) { + $wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) ); + } + $sk = $wgUser->getSkin(); + if ( $forceKnown ) + $link = $sk->makeKnownLinkObj( $target, htmlspecialchars( $target->getFullText() ) ); + else + $link = $sk->makeLinkObj( $target, htmlspecialchars( $target->getFullText() ) ); + + $wgOut->addHTML( '#REDIRECT ' . + ''.$link.'' ); + + } function addTrackbacks() { global $wgOut, $wgUser; @@ -897,6 +974,7 @@ class Article { . $o->tb_id . "&token=" . urlencode( $wgUser->editToken() ) ); $rmvtxt = wfMsg( 'trackbackremove', htmlspecialchars( $delurl ) ); } + $tbtext .= "\n"; $tbtext .= wfMsg(strlen($o->tb_ex) ? 'trackbackexcerpt' : 'trackback', $o->tb_title, $o->tb_url, @@ -1067,7 +1145,6 @@ class Article { $result = $dbw->affectedRows() != 0; if ($result) { - // FIXME: Should the result from updateRedirectOn() be returned instead? $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect ); } @@ -1112,6 +1189,8 @@ class Article { $dbw->delete( 'redirect', $where, __METHOD__); } + if( $this->getTitle()->getNamespace() == NS_IMAGE ) + RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() ); wfProfileOut( __METHOD__ ); return ( $dbw->affectedRows() != 0 ); } @@ -1252,10 +1331,10 @@ class Article { } } - $extraq = ''; // Give extensions a chance to modify URL query on update - wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this, &$sectionanchor, &$extraq ) ); + $extraQuery = ''; // Give extensions a chance to modify URL query on update + wfRunHooks( 'ArticleUpdateBeforeRedirect', array( $this, &$sectionanchor, &$extraQuery ) ); - $this->doRedirect( $this->isRedirect( $text ), $sectionanchor, $extraq ); + $this->doRedirect( $this->isRedirect( $text ), $sectionanchor, $extraQuery ); } return $good; } @@ -1291,11 +1370,12 @@ class Article { * EDIT_NEW is specified and the article does exist, a duplicate key error will cause an exception * to be thrown from the Database. These two conditions are also possible with auto-detection due * to MediaWiki's performance-optimised locking strategy. + * @param $baseRevId, the revision ID this edit was based off, if any * * @return bool success */ - function doEdit( $text, $summary, $flags = 0 ) { - global $wgUser, $wgDBtransactions; + function doEdit( $text, $summary, $flags = 0, $baseRevId = false ) { + global $wgUser, $wgDBtransactions, $wgUseAutomaticEditSummaries; wfProfileIn( __METHOD__ ); $good = true; @@ -1325,9 +1405,10 @@ class Article { $oldtext = $this->getContent(); $oldsize = strlen( $oldtext ); - # Provide autosummaries if one is not provided. - if ($flags & EDIT_AUTOSUMMARY && $summary == '') + # Provide autosummaries if one is not provided and autosummaries are enabled. + if( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) { $summary = $this->getAutosummary( $oldtext, $text, $flags ); + } $editInfo = $this->prepareTextForEdit( $text ); $text = $editInfo->pst; @@ -1346,7 +1427,7 @@ class Article { $lastRevision = 0; $revisionId = 0; - + $changed = ( strcmp( $text, $oldtext ) != 0 ); if ( $changed ) { @@ -1368,7 +1449,8 @@ class Article { 'page' => $this->getId(), 'comment' => $summary, 'minor_edit' => $isminor, - 'text' => $text + 'text' => $text, + 'parent_id' => $lastRevision ) ); $dbw->begin(); @@ -1382,6 +1464,8 @@ class Article { $good = false; $dbw->rollback(); } else { + wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId ) ); + # Update recentchanges if( !( $flags & EDIT_SUPPRESS_RC ) ) { $rcid = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $wgUser, $summary, @@ -1414,7 +1498,7 @@ class Article { # Invalidate cache of this article and all pages using this article # as a template. Partly deferred. Article::onArticleEdit( $this->mTitle ); - + # Update links tables, site stats, etc. $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed ); } @@ -1446,6 +1530,8 @@ class Article { # Update the page record with revision data $this->updateRevisionOn( $dbw, $revision, 0 ); + + wfRunHooks( 'NewRevisionFromEditComplete', array($this, $revision, false) ); if( !( $flags & EDIT_SUPPRESS_RC ) ) { $rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot, @@ -1495,15 +1581,16 @@ class Article { * * @param boolean $noRedir Add redirect=no * @param string $sectionAnchor section to redirect to, including "#" + * @param string $extraQuery, extra query params */ - function doRedirect( $noRedir = false, $sectionAnchor = '', $extraq = '' ) { + function doRedirect( $noRedir = false, $sectionAnchor = '', $extraQuery = '' ) { global $wgOut; if ( $noRedir ) { $query = 'redirect=no'; - if( $extraq ) + if( $extraQuery ) $query .= "&$query"; } else { - $query = $extraq; + $query = $extraQuery; } $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor ); } @@ -1518,8 +1605,8 @@ class Article { # Check patrol config options if ( !($wgUseNPPatrol || $wgUseRCPatrol)) { - $wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); - return; + $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); + return; } # If we haven't been given an rc_id value, we can't do anything @@ -1527,18 +1614,18 @@ class Article { $rc = $rcid ? RecentChange::newFromId($rcid) : null; if ( is_null ( $rc ) ) { - $wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' ); + $wgOut->showErrorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' ); return; } - if ( !$wgUseRCPatrol && $rc->mAttribs['rc_type'] != RC_NEW) { + if ( !$wgUseRCPatrol && $rc->getAttribute( 'rc_type' ) != RC_NEW) { // Only new pages can be patrolled if the general patrolling is off....??? // @fixme -- is this necessary? Shouldn't we only bother controlling the // front end here? - $wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); + $wgOut->showErrorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' ); return; } - + # Check permissions $permission_errors = $this->mTitle->getUserPermissionsErrors( 'patrol', $wgUser ); @@ -1554,7 +1641,7 @@ class Article { } #It would be nice to see where the user had actually come from, but for now just guess - $returnto = $rc->mAttribs['rc_type'] == RC_NEW ? 'Newpages' : 'Recentchanges'; + $returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges'; $return = Title::makeTitle( NS_SPECIAL, $returnto ); # If it's left up to us, check that the user is allowed to patrol this edit @@ -1575,10 +1662,14 @@ class Article { } } - # Mark the edit as patrolled - RecentChange::markPatrolled( $rcid ); - PatrolLog::record( $rcid ); - wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) ); + # Check that the revision isn't patrolled already + # Prevents duplicate log entries + if( !$rc->getAttribute( 'rc_patrolled' ) ) { + # Mark the edit as patrolled + RecentChange::markPatrolled( $rcid ); + PatrolLog::record( $rcid ); + wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) ); + } # Inform the user $wgOut->setPageTitle( wfMsg( 'markedaspatrolled' ) ); @@ -1724,8 +1815,8 @@ class Article { $updated = Article::flattenRestrictions( $limit ); $changed = ( $current != $updated ); - $changed = $changed || ($this->mTitle->areRestrictionsCascading() != $cascade); - $changed = $changed || ($this->mTitle->mRestrictionsExpiry != $expiry); + $changed = $changed || ($updated && $this->mTitle->areRestrictionsCascading() != $cascade); + $changed = $changed || ($updated && $this->mTitle->mRestrictionsExpiry != $expiry); $protect = ( $updated != '' ); # If nothing's changed, do nothing @@ -1751,12 +1842,16 @@ class Article { } $comment = $wgContLang->ucfirst( wfMsgForContent( $comment_type, $this->mTitle->getPrefixedText() ) ); - foreach( $limit as $action => $restrictions ) { - # Check if the group level required to edit also can protect pages - # Otherwise, people who cannot normally protect can "protect" pages via transclusion - $cascade = ( $cascade && isset($wgGroupPermissions[$restrictions]['protect']) && $wgGroupPermissions[$restrictions]['protect'] ); + # Only restrictions with the 'protect' right can cascade... + # Otherwise, people who cannot normally protect can "protect" pages via transclusion + foreach( $limit as $action => $restriction ) { + # FIXME: can $restriction be an array or what? (same as fixme above) + if( $restriction != 'protect' && $restriction != 'sysop' ) { + $cascade = false; + break; + } } - + $cascade_description = ''; if ($cascade) { $cascade_description = ' ['.wfMsg('protect-summary-cascade').']'; @@ -1770,8 +1865,7 @@ class Article { $comment .= "$expiry_description"; if ( $cascade ) $comment .= "$cascade_description"; - - $rowsAffected = false; + # Update restrictions table foreach( $limit as $action => $restrictions ) { if ($restrictions != '' ) { @@ -1779,18 +1873,11 @@ class Article { array( 'pr_page' => $id, 'pr_type' => $action , 'pr_level' => $restrictions, 'pr_cascade' => $cascade ? 1 : 0 , 'pr_expiry' => $encodedExpiry ), __METHOD__ ); - if($dbw->affectedRows() != 0) - $rowsAffected = true; } else { $dbw->delete( 'page_restrictions', array( 'pr_page' => $id, 'pr_type' => $action ), __METHOD__ ); - if($dbw->affectedRows() != 0) - $rowsAffected = true; } } - if(!$rowsAffected) - // No change - return true; # Insert a null revision $nullRevision = Revision::newNullRevision( $dbw, $id, $comment, true ); @@ -1806,15 +1893,15 @@ class Article { 'page_id' => $id ), 'Article::protect' ); + + wfRunHooks( 'NewRevisionFromEditComplete', array($this, $nullRevision, false) ); wfRunHooks( 'ArticleProtectComplete', array( &$this, &$wgUser, $limit, $reason ) ); # Update the protection log $log = new LogPage( 'protect' ); - - - if( $protect ) { - $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, trim( $reason . " [$updated]$cascade_description$expiry_description" ) ); + $log->addEntry( $modified ? 'modify' : 'protect', $this->mTitle, + trim( $reason . " [$updated]$cascade_description$expiry_description" ) ); } else { $log->addEntry( 'unprotect', $this->mTitle, $reason ); } @@ -1845,7 +1932,7 @@ class Article { } return implode( ':', $bits ); } - + /** * Auto-generates a deletion reason * @param bool &$hasHistory Whether the page has a history @@ -1907,7 +1994,7 @@ class Article { else $reason = wfMsgForContent('excontent', '$1'); } - + // Replace newlines with spaces to prevent uglyness $contents = preg_replace("/[\n\r]/", ' ', $contents); // Calculate the maximum amount of chars to get @@ -1930,18 +2017,20 @@ class Article { $confirm = $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ); - + $this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' ); $this->DeleteReason = $wgRequest->getText( 'wpReason' ); - + $reason = $this->DeleteReasonList; - + if ( $reason != 'other' && $this->DeleteReason != '') { // Entry from drop down menu + additional comment $reason .= ': ' . $this->DeleteReason; } elseif ( $reason == 'other' ) { $reason = $this->DeleteReason; } + # Flag to hide all contents of the archived revisions + $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgUser->isAllowed('suppressrevision'); # This code desperately needs to be totally rewritten @@ -1950,7 +2039,7 @@ class Article { $wgOut->readOnlyPage(); return; } - + # Check permissions $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser ); @@ -1980,7 +2069,7 @@ class Article { } if( $confirm ) { - $this->doDelete( $reason ); + $this->doDelete( $reason, $suppress ); if( $wgRequest->getCheck( 'wpWatch' ) ) { $this->doWatch(); } elseif( $this->mTitle->userIsWatching() ) { @@ -2003,10 +2092,10 @@ class Article { array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) ); } } - - return $this->confirmDelete( '', $reason ); + + return $this->confirmDelete( $reason ); } - + /** * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions */ @@ -2018,7 +2107,7 @@ class Article { } return false; } - + /** * @return int approximate revision count */ @@ -2079,10 +2168,9 @@ class Article { /** * Output deletion confirmation dialog - * @param $par string FIXME: do we need this parameter? One Call from Article::delete with '' only. * @param $reason string Prefilled reason */ - function confirmDelete( $par, $reason ) { + function confirmDelete( $reason ) { global $wgOut, $wgUser, $wgContLang; $align = $wgContLang->isRtl() ? 'left' : 'right'; @@ -2092,9 +2180,17 @@ class Article { $wgOut->setRobotpolicy( 'noindex,nofollow' ); $wgOut->addWikiMsg( 'confirmdeletetext' ); - $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mTitle->getLocalURL( 'action=delete' . $par ), 'id' => 'deleteconfirm' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', array(), wfMsg( 'delete-legend' ) ) . + if( $wgUser->isAllowed( 'suppressrevision' ) ) { + $suppress = ""; + $suppress .= Xml::checkLabel( wfMsg( 'revdelete-suppress' ), 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '2' ) ); + $suppress .= ""; + } else { + $suppress = ''; + } + + $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mTitle->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) . + Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) . + Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) . Xml::openElement( 'table' ) . " " . @@ -2102,7 +2198,7 @@ class Article { " " . Xml::listDropDown( 'wpDeleteReasonList', - wfMsgForContent( 'deletereason-dropdown' ), + wfMsgForContent( 'deletereason-dropdown' ), wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) . " @@ -2120,6 +2216,7 @@ class Article { Xml::checkLabel( wfMsg( 'watchthis' ), 'wpWatch', 'wpWatch', $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(), array( 'tabindex' => '3' ) ) . " + $suppress " . @@ -2131,6 +2228,12 @@ class Article { Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . Xml::closeElement( 'form' ); + if ( $wgUser->isAllowed( 'editinterface' ) ) { + $skin = $wgUser->getSkin(); + $link = $skin->makeLink ( 'MediaWiki:Deletereason-dropdown', wfMsgHtml( 'delete-edit-reasonlist' ) ); + $form .= '

    ' . $link . '

    '; + } + $wgOut->addHTML( $form ); $this->showLogExtract( $wgOut ); } @@ -2140,25 +2243,24 @@ class Article { * Show relevant lines from the deletion log */ function showLogExtract( $out ) { - $out->addHtml( '

    ' . htmlspecialchars( LogPage::logName( 'delete' ) ) . '

    ' ); - $logViewer = new LogViewer( - new LogReader( - new FauxRequest( - array( 'page' => $this->mTitle->getPrefixedText(), - 'type' => 'delete' ) ) ) ); - $logViewer->showList( $out ); + $out->addHtml( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) ); + LogEventsList::showLogExtract( $out, 'delete', $this->mTitle->getPrefixedText() ); } /** * Perform a deletion and output success or failure messages */ - function doDelete( $reason ) { + function doDelete( $reason, $suppress = false ) { global $wgOut, $wgUser; wfDebug( __METHOD__."\n" ); + + $id = $this->getId(); + + $error = ''; - if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) { - if ( $this->doDeleteArticle( $reason ) ) { + if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason, &$error))) { + if ( $this->doDeleteArticle( $reason, $suppress ) ) { $deleted = $this->mTitle->getPrefixedText(); $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) ); @@ -2168,9 +2270,12 @@ class Article { $wgOut->addWikiMsg( 'deletedtext', $deleted, $loglink ); $wgOut->returnToMain( false ); - wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason)); + wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason, $id)); } else { - $wgOut->showFatalError( wfMsg( 'cannotdelete' ) ); + if ($error = '') + $wgOut->showFatalError( wfMsg( 'cannotdelete' ) ); + else + $wgOut->showFatalError( $error ); } } } @@ -2180,7 +2285,7 @@ class Article { * Deletes the article with database consistency, writes logs, purges caches * Returns success */ - function doDeleteArticle( $reason ) { + function doDeleteArticle( $reason, $suppress = false ) { global $wgUseSquid, $wgDeferredUpdateList; global $wgUseTrackbacks; @@ -2198,6 +2303,19 @@ class Article { $u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getContent() ), -1 ); array_push( $wgDeferredUpdateList, $u ); + // Bitfields to further suppress the content + if ( $suppress ) { + $bitfield = 0; + // This should be 15... + $bitfield |= Revision::DELETED_TEXT; + $bitfield |= Revision::DELETED_COMMENT; + $bitfield |= Revision::DELETED_USER; + $bitfield |= Revision::DELETED_RESTRICTED; + } else { + $bitfield = 'rev_deleted'; + } + + $dbw->begin(); // For now, shunt the revision data into the archive table. // Text is *not* removed from the text table; bulk storage // is left intact to avoid breaking block-compression or @@ -2221,8 +2339,9 @@ class Article { 'ar_text_id' => 'rev_text_id', 'ar_text' => '\'\'', // Be explicit to appease 'ar_flags' => '\'\'', // MySQL's "strict mode"... - 'ar_len' => 'rev_len', + 'ar_len' => 'rev_len', 'ar_page_id' => 'page_id', + 'ar_deleted' => $bitfield ), array( 'page_id' => $id, 'page_id = rev_page' @@ -2232,12 +2351,25 @@ class Article { # Delete restrictions for it $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ ); + # Fix category table counts + $cats = array(); + $res = $dbw->select( 'categorylinks', 'cl_to', + array( 'cl_from' => $id ), __METHOD__ ); + foreach( $res as $row ) { + $cats []= $row->cl_to; + } + $this->updateCategoryCounts( array(), $cats ); + # Now that it's safely backed up, delete it $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__); + $ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy + if( !$ok ) { + $dbw->rollback(); + return false; + } # If using cascading deletes, we can skip some explicit deletes if ( !$dbw->cascadingDeletes() ) { - $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ ); if ($wgUseTrackbacks) @@ -2257,19 +2389,26 @@ class Article { if ( !$dbw->cleanupTriggers() ) { # Clean up recentchanges entries... - $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), __METHOD__ ); + $dbw->delete( 'recentchanges', + array( 'rc_namespace' => $ns, 'rc_title' => $t, 'rc_type != '.RC_LOG ), + __METHOD__ ); } + $dbw->commit(); # Clear caches Article::onArticleDelete( $this->mTitle ); - # Log the deletion - $log = new LogPage( 'delete' ); - $log->addEntry( 'delete', $this->mTitle, $reason ); - # 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'; + $log = new LogPage( $logtype ); + + # Make sure logging got through + $log->addEntry( 'delete', $this->mTitle, $reason, array() ); + return true; } @@ -2280,15 +2419,15 @@ class Article { * performs permissions checks on $wgUser, then calls commitRollback() * to do the dirty work * - * @param string $fromP - Name of the user whose edits to rollback. + * @param string $fromP - Name of the user whose edits to rollback. * @param string $summary - Custom summary. Set to default summary if empty. * @param string $token - Rollback token. * @param bool $bot - If true, mark all reverted edits as bot. - * + * * @param array $resultDetails contains result-specific array of additional values * 'alreadyrolled' : 'current' (rev) * success : 'summary' (str), 'current' (rev), 'target' (rev) - * + * * @return array of errors, each error formatted as * array(messagekey, param1, param2, ...). * On success, the array is empty. This array can also be passed to @@ -2310,10 +2449,10 @@ class Article { # If there were errors, bail out now if(!empty($errors)) return $errors; - + return $this->commitRollback($fromP, $summary, $bot, $resultDetails); } - + /** * Backend implementation of doRollback(), please refer there for parameter * and return value documentation @@ -2322,9 +2461,9 @@ class Article { * rollback to the DB Therefore, you should only call this function direct- * ly if you want to use custom permissions checks. If you don't, use * doRollback() instead. - */ + */ public function commitRollback($fromP, $summary, $bot, &$resultDetails) { - global $wgUseRCPatrol, $wgUser; + global $wgUseRCPatrol, $wgUser, $wgLang; $dbw = wfGetDB( DB_MASTER ); if( wfReadOnly() ) { @@ -2352,7 +2491,7 @@ class Article { $user = intval( $current->getUser() ); $user_text = $dbw->addQuotes( $current->getUserText() ); $s = $dbw->selectRow( 'revision', - array( 'rev_id', 'rev_timestamp' ), + array( 'rev_id', 'rev_timestamp', 'rev_deleted' ), array( 'rev_page' => $current->getPage(), "rev_user <> {$user} OR rev_user_text <> {$user_text}" ), __METHOD__, @@ -2362,8 +2501,11 @@ class Article { if( $s === false ) { # No one else ever edited this page return array(array('cantrollback')); + } else if( $s->rev_deleted & REVISION::DELETED_TEXT || $s->rev_deleted & REVISION::DELETED_USER ) { + # Only admins can see this text + return array(array('notvisiblerev')); } - + $set = array(); if ( $bot && $wgUser->isAllowed('markbotedits') ) { # Mark all reverted edits as bot @@ -2386,15 +2528,17 @@ class Article { # Generate the edit summary if necessary $target = Revision::newFromId( $s->rev_id ); - if( empty( $summary ) ) - { - global $wgLang; - $summary = wfMsgForContent( 'revertpage', - $target->getUserText(), $from, - $s->rev_id, $wgLang->timeanddate(wfTimestamp(TS_MW, $s->rev_timestamp), true), - $current->getId(), $wgLang->timeanddate($current->getTimestamp()) - ); + if( empty( $summary ) ){ + $summary = wfMsgForContent( 'revertpage' ); } + + # Allow the custom summary to use the same args as the default message + $args = array( + $target->getUserText(), $from, $s->rev_id, + $wgLang->timeanddate(wfTimestamp(TS_MW, $s->rev_timestamp), true), + $current->getId(), $wgLang->timeanddate($current->getTimestamp()) + ); + $summary = wfMsgReplaceArgs( $summary, $args ); # Save $flags = EDIT_UPDATE; @@ -2404,7 +2548,7 @@ class Article { if( $bot && ($wgUser->isAllowed('markbotedits') || $wgUser->isAllowed('bot')) ) $flags |= EDIT_FORCE_BOT; - $this->doEdit( $target->getText(), $summary, $flags ); + $this->doEdit( $target->getText(), $summary, $flags, $target->getId() ); wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target ) ); @@ -2439,6 +2583,19 @@ class Article { $wgOut->rateLimited(); return; } + if( isset( $result[0][0] ) && ( $result[0][0] == 'alreadyrolled' || $result[0][0] == 'cantrollback' ) ){ + $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) ); + $errArray = $result[0]; + $errMsg = array_shift( $errArray ); + $wgOut->addWikiMsgArray( $errMsg, $errArray ); + if( isset( $details['current'] ) ){ + $current = $details['current']; + if( $current->getComment() != '' ) { + $wgOut->addWikiMsgArray( 'editcomment', array( $wgUser->getSkin()->formatComment( $current->getComment() ) ), array( 'replaceafter' ) ); + } + } + return; + } # Display permissions errors before read-only message -- there's no # point in misleading the user into thinking the inability to rollback # is only temporary. @@ -2469,6 +2626,11 @@ class Article { . $wgUser->getSkin()->userToolLinks( $target->getUser(), $target->getUserText() ); $wgOut->addHtml( wfMsgExt( 'rollback-success', array( 'parse', 'replaceafter' ), $old, $new ) ); $wgOut->returnToMain( false, $this->mTitle ); + + if( !$wgRequest->getBool( 'hidediff', false ) ) { + $de = new DifferenceEngine( $this->mTitle, $current->getId(), 'next', false, true ); + $de->showDiff( '', '' ); + } } @@ -2477,11 +2639,12 @@ class Article { * @private */ function viewUpdates() { - global $wgDeferredUpdateList; + global $wgDeferredUpdateList, $wgUser; if ( 0 != $this->getID() ) { + # Don't update page view counters on views from bot users (bug 14044) global $wgDisableCounters; - if( !$wgDisableCounters ) { + if( !$wgDisableCounters && !$wgUser->isAllowed( 'bot' ) ) { Article::incViewCount( $this->getID() ); $u = new SiteStatsUpdate( 1, 0, 0 ); array_push( $wgDeferredUpdateList, $u ); @@ -2489,7 +2652,6 @@ class Article { } # Update newtalk / watchlist notification status - global $wgUser; $wgUser->clearNotification( $this->mTitle ); } @@ -2546,7 +2708,7 @@ class Article { # Save it to the parser cache if ( $wgEnableParserCache ) { - $parserCache =& ParserCache::singleton(); + $parserCache = ParserCache::singleton(); $parserCache->save( $editInfo->output, $this, $wgUser ); } @@ -2646,7 +2808,7 @@ class Article { $sk = $wgUser->getSkin(); $lnk = $current ? wfMsg( 'currentrevisionlink' ) - : $lnk = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'currentrevisionlink' ) ); + : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'currentrevisionlink' ) ); $curdiff = $current ? wfMsg( 'diff' ) : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=cur&oldid='.$oldid ); @@ -2664,16 +2826,38 @@ class Article { ? wfMsg( 'diff' ) : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=next&oldid='.$oldid ); - $userlinks = $sk->userLink( $revision->getUser(), $revision->getUserText() ) - . $sk->userToolLinks( $revision->getUser(), $revision->getUserText() ); + $cdel=''; + if( $wgUser->isAllowed( 'deleterevision' ) ) { + $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); + if( $revision->isCurrent() ) { + // We don't handle top deleted edits too well + $cdel = wfMsgHtml('rev-delundel'); + } else if( !$revision->userCan( Revision::DELETED_RESTRICTED ) ) { + // If revision was hidden from sysops + $cdel = wfMsgHtml('rev-delundel'); + } else { + $cdel = $sk->makeKnownLinkObj( $revdel, + wfMsgHtml('rev-delundel'), + 'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) . + '&oldid=' . urlencode( $oldid ) ); + // Bolden oversighted content + if( $revision->isDeleted( Revision::DELETED_RESTRICTED ) ) + $cdel = "$cdel"; + } + $cdel = "($cdel) "; + } + # 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 ); $m = wfMsg( 'revision-info-current' ); $infomsg = $current && !wfEmptyMsg( 'revision-info-current', $m ) && $m != '-' ? 'revision-info-current' : 'revision-info'; - + $r = "\n\t\t\t\t
    " . wfMsg( $infomsg, $td, $userlinks ) . "
    \n" . - "\n\t\t\t\t
    " . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "
    \n\t\t\t"; + + "\n\t\t\t\t
    " . $cdel . wfMsg( 'revision-nav', $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "
    \n\t\t\t"; $wgOut->setSubtitle( $r ); } @@ -2731,9 +2915,9 @@ class Article { $printable = $wgRequest->getVal( 'printable' ); $page = $wgRequest->getVal( 'page' ); - //check for non-standard user language; this covers uselang, + //check for non-standard user language; this covers uselang, //and extensions for auto-detecting user language. - $ulang = $wgLang->getCode(); + $ulang = $wgLang->getCode(); $clang = $wgContLang->getCode(); $cacheable = $wgUseFileCache @@ -2814,6 +2998,8 @@ class Article { $revision->insertOn( $dbw ); $this->updateRevisionOn( $dbw, $revision ); $dbw->commit(); + + wfRunHooks( 'NewRevisionFromEditComplete', array($this, $revision, false) ); wfProfileOut( __METHOD__ ); } @@ -2911,6 +3097,15 @@ class Article { static function onArticleDelete( $title ) { global $wgUseFileCache, $wgMessageCache; + // Update existence markers on article/talk tabs... + if( $title->isTalkPage() ) { + $other = $title->getSubjectPage(); + } else { + $other = $title->getTalkPage(); + } + $other->invalidateCache(); + $other->purgeSquid(); + $title->touchLinks(); $title->purgeSquid(); @@ -2920,13 +3115,20 @@ class Article { @unlink( $cm->fileCacheName() ); } - if( $title->getNamespace() == NS_MEDIAWIKI) { + # Messages + if( $title->getNamespace() == NS_MEDIAWIKI ) { $wgMessageCache->replace( $title->getDBkey(), false ); } + # Images if( $title->getNamespace() == NS_IMAGE ) { $update = new HTMLCacheUpdate( $title, 'imagelinks' ); $update->doUpdate(); } + # User talk pages + if( $title->getNamespace() == NS_USER_TALK ) { + $user = User::newFromName( $title->getText(), false ); + $user->setNewtalk( false ); + } } /** @@ -2940,7 +3142,7 @@ class Article { // Invalidate the caches of all pages which redirect here $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' ); - + # Purge squid for this page only $title->purgeSquid(); @@ -2953,6 +3155,15 @@ class Article { /**#@-*/ + /** + * Overriden by ImagePage class, only present here to avoid a fatal error + * Called for ?action=revert + */ + public function revert(){ + global $wgOut; + $wgOut->showErrorPage( 'nosuchaction', 'nosuchactiontext' ); + } + /** * Info about this page * Called for ?action=info when $wgAllowPageInfo is on. @@ -3080,38 +3291,33 @@ class Article { } /** - * Return an auto-generated summary if the text provided is a redirect. + * Returns a list of hidden categories this page is a member of. + * Uses the page_props and categorylinks tables. * - * @param string $text The wikitext to check - * @return string '' or an appropriate summary + * @return array Array of Title objects */ - public static function getRedirectAutosummary( $text ) { - $rt = Title::newFromRedirect( $text ); - if( is_object( $rt ) ) - return wfMsgForContent( 'autoredircomment', $rt->getFullText() ); - else - return ''; - } + function getHiddenCategories() { + $result = array(); + $id = $this->mTitle->getArticleID(); + if( $id == 0 ) { + return array(); + } - /** - * Return an auto-generated summary if the new text is much shorter than - * the old text. - * - * @param string $oldtext The previous text of the page - * @param string $text The submitted text of the page - * @return string An appropriate autosummary, or an empty string. - */ - public static function getBlankingAutosummary( $oldtext, $text ) { - if ($oldtext!='' && $text=='') { - return wfMsgForContent('autosumm-blank'); - } elseif (strlen($oldtext) > 10 * strlen($text) && strlen($text) < 500) { - #Removing more than 90% of the article - global $wgContLang; - $truncatedtext = $wgContLang->truncate($text, max(0, 200 - strlen(wfMsgForContent('autosumm-replace'))), '...'); - return wfMsgForContent('autosumm-replace', $truncatedtext); - } else { - return ''; + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ), + array( 'cl_to' ), + array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat', + 'page_namespace' => NS_CATEGORY, 'page_title=cl_to'), + 'Article:getHiddenCategories' ); + if ( false !== $res ) { + if ( $dbr->numRows( $res ) ) { + while ( $row = $dbr->fetchObject( $res ) ) { + $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to ); + } + } } + $dbr->freeResult( $res ); + return $result; } /** @@ -3122,38 +3328,42 @@ class Article { * @return string An appropriate autosummary, or an empty string. */ public static function getAutosummary( $oldtext, $newtext, $flags ) { + # Decide what kind of autosummary is needed. - # This code is UGLY UGLY UGLY. - # Somebody PLEASE come up with a more elegant way to do it. - - #Redirect autosummaries - $summary = self::getRedirectAutosummary( $newtext ); - - if ($summary) - return $summary; - - #Blanking autosummaries - if (!($flags & EDIT_NEW)) - $summary = self::getBlankingAutosummary( $oldtext, $newtext ); - - if ($summary) - return $summary; + # Redirect autosummaries + $rt = Title::newFromRedirect( $newtext ); + if( is_object( $rt ) ) { + return wfMsgForContent( 'autoredircomment', $rt->getFullText() ); + } - #New page autosummaries - if ($flags & EDIT_NEW && strlen($newtext)) { - #If they're making a new article, give its text, truncated, in the summary. + # New page autosummaries + if( $flags & EDIT_NEW && strlen( $newtext ) ) { + # If they're making a new article, give its text, truncated, in the summary. global $wgContLang; $truncatedtext = $wgContLang->truncate( str_replace("\n", ' ', $newtext), max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new') ) ), '...' ); - $summary = wfMsgForContent( 'autosumm-new', $truncatedtext ); + return wfMsgForContent( 'autosumm-new', $truncatedtext ); } - if ($summary) - return $summary; + # Blanking autosummaries + if( $oldtext != '' && $newtext == '' ) { + return wfMsgForContent('autosumm-blank'); + } elseif( strlen( $oldtext ) > 10 * strlen( $newtext ) && strlen( $newtext ) < 500) { + # Removing more than 90% of the article + global $wgContLang; + $truncatedtext = $wgContLang->truncate( + $newtext, + max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ), + '...' + ); + return wfMsgForContent( 'autosumm-replace', $truncatedtext ); + } - return $summary; + # If we reach this point, there's no applicable autosummary for our case, so our + # autosummary is empty. + return ''; } /** @@ -3175,7 +3385,7 @@ class Article { $popts->setTidy(false); $popts->enableLimitReport( false ); if ( $wgEnableParserCache && $cache && $this && $parserOutput->getCacheTime() != -1 ) { - $parserCache =& ParserCache::singleton(); + $parserCache = ParserCache::singleton(); $parserCache->save( $parserOutput, $this, $wgUser ); } @@ -3235,4 +3445,60 @@ class Article { $wgOut->addParserOutput( $parserOutput ); } + /** + * Update all the appropriate counts in the category table, given that + * we've added the categories $added and deleted the categories $deleted. + * + * @param $added array The names of categories that were added + * @param $deleted array The names of categories that were deleted + * @return null + */ + public function updateCategoryCounts( $added, $deleted ) { + $ns = $this->mTitle->getNamespace(); + $dbw = wfGetDB( DB_MASTER ); + + # First make sure the rows exist. If one of the "deleted" ones didn't + # exist, we might legitimately not create it, but it's simpler to just + # create it and then give it a negative value, since the value is bogus + # anyway. + # + # Sometimes I wish we had INSERT ... ON DUPLICATE KEY UPDATE. + $insertCats = array_merge( $added, $deleted ); + if( !$insertCats ) { + # Okay, nothing to do + return; + } + $insertRows = array(); + foreach( $insertCats as $cat ) { + $insertRows[] = array( 'cat_title' => $cat ); + } + $dbw->insert( 'category', $insertRows, __METHOD__, 'IGNORE' ); + + $addFields = array( 'cat_pages = cat_pages + 1' ); + $removeFields = array( 'cat_pages = cat_pages - 1' ); + if( $ns == NS_CATEGORY ) { + $addFields[] = 'cat_subcats = cat_subcats + 1'; + $removeFields[] = 'cat_subcats = cat_subcats - 1'; + } elseif( $ns == NS_IMAGE ) { + $addFields[] = 'cat_files = cat_files + 1'; + $removeFields[] = 'cat_files = cat_files - 1'; + } + + if ( $added ) { + $dbw->update( + 'category', + $addFields, + array( 'cat_title' => $added ), + __METHOD__ + ); + } + if ( $deleted ) { + $dbw->update( + 'category', + $removeFields, + array( 'cat_title' => $deleted ), + __METHOD__ + ); + } + } } diff --git a/includes/AuthPlugin.php b/includes/AuthPlugin.php index 2ad137e2..7717e001 100644 --- a/includes/AuthPlugin.php +++ b/includes/AuthPlugin.php @@ -230,7 +230,7 @@ class AuthPlugin { * @param $autocreate bool True if user is being autocreated on login * @public */ - function initUser( $user, $autocreate=false ) { + function initUser( &$user, $autocreate=false ) { # Override this to do something. } @@ -242,5 +242,3 @@ class AuthPlugin { return $username; } } - - diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 2e2083b2..4f36784a 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -4,352 +4,265 @@ ini_set('unserialize_callback_func', '__autoload' ); -function __autoload($className) { - global $wgAutoloadClasses; - +class AutoLoader { # Locations of core classes # Extension classes are specified with $wgAutoloadClasses static $localClasses = array( # Includes 'AjaxDispatcher' => 'includes/AjaxDispatcher.php', - 'AjaxCachePolicy' => 'includes/AjaxFunctions.php', 'AjaxResponse' => 'includes/AjaxResponse.php', 'AlphabeticPager' => 'includes/Pager.php', + 'APCBagOStuff' => 'includes/BagOStuff.php', + 'ArrayDiffFormatter' => 'includes/DifferenceEngine.php', 'Article' => 'includes/Article.php', + 'AtomFeed' => 'includes/Feed.php', 'AuthPlugin' => 'includes/AuthPlugin.php', 'Autopromote' => 'includes/Autopromote.php', 'BagOStuff' => 'includes/BagOStuff.php', - 'HashBagOStuff' => 'includes/BagOStuff.php', - 'SqlBagOStuff' => 'includes/BagOStuff.php', - 'MediaWikiBagOStuff' => 'includes/BagOStuff.php', - 'TurckBagOStuff' => 'includes/BagOStuff.php', - 'APCBagOStuff' => 'includes/BagOStuff.php', - 'eAccelBagOStuff' => 'includes/BagOStuff.php', - 'XCacheBagOStuff' => 'includes/BagOStuff.php', - 'DBABagOStuff' => 'includes/BagOStuff.php', 'Block' => 'includes/Block.php', - 'HTMLFileCache' => 'includes/HTMLFileCache.php', - 'DependencyWrapper' => 'includes/CacheDependency.php', - 'FileDependency' => 'includes/CacheDependency.php', - 'TitleDependency' => 'includes/CacheDependency.php', - 'TitleListDependency' => 'includes/CacheDependency.php', + 'CacheDependency' => 'includes/CacheDependency.php', + 'Category' => 'includes/Category.php', + 'Categoryfinder' => 'includes/Categoryfinder.php', 'CategoryPage' => 'includes/CategoryPage.php', 'CategoryViewer' => 'includes/CategoryPage.php', - 'Categoryfinder' => 'includes/Categoryfinder.php', - 'RCCacheEntry' => 'includes/ChangesList.php', 'ChangesList' => 'includes/ChangesList.php', - 'OldChangesList' => 'includes/ChangesList.php', - 'EnhancedChangesList' => 'includes/ChangesList.php', - 'CoreParserFunctions' => 'includes/CoreParserFunctions.php', - 'DBObject' => 'includes/Database.php', - 'Database' => 'includes/Database.php', - 'DatabaseMysql' => 'includes/Database.php', - 'ResultWrapper' => 'includes/Database.php', - 'DatabasePostgres' => 'includes/DatabasePostgres.php', - 'DatabaseOracle' => 'includes/DatabaseOracle.php', - 'DateFormatter' => 'includes/DateFormatter.php', + 'ChangesFeed' => 'includes/ChangesFeed.php', + 'ChannelFeed' => 'includes/Feed.php', + 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php', + 'ConstantDependency' => 'includes/CacheDependency.php', + 'DBABagOStuff' => 'includes/BagOStuff.php', + 'DependencyWrapper' => 'includes/CacheDependency.php', + '_DiffEngine' => 'includes/DifferenceEngine.php', 'DifferenceEngine' => 'includes/DifferenceEngine.php', - '_DiffOp' => 'includes/DifferenceEngine.php', - '_DiffOp_Copy' => 'includes/DifferenceEngine.php', - '_DiffOp_Delete' => 'includes/DifferenceEngine.php', + 'DiffFormatter' => 'includes/DifferenceEngine.php', + 'Diff' => 'includes/DifferenceEngine.php', '_DiffOp_Add' => 'includes/DifferenceEngine.php', '_DiffOp_Change' => 'includes/DifferenceEngine.php', - '_DiffEngine' => 'includes/DifferenceEngine.php', - 'Diff' => 'includes/DifferenceEngine.php', - 'MappedDiff' => 'includes/DifferenceEngine.php', - 'DiffFormatter' => 'includes/DifferenceEngine.php', - 'UnifiedDiffFormatter' => 'includes/DifferenceEngine.php', - 'ArrayDiffFormatter' => 'includes/DifferenceEngine.php', + '_DiffOp_Copy' => 'includes/DifferenceEngine.php', + '_DiffOp_Delete' => 'includes/DifferenceEngine.php', + '_DiffOp' => 'includes/DifferenceEngine.php', 'DjVuImage' => 'includes/DjVuImage.php', - '_HWLDF_WordAccumulator' => 'includes/DifferenceEngine.php', - 'WordLevelDiff' => 'includes/DifferenceEngine.php', - 'TableDiffFormatter' => 'includes/DifferenceEngine.php', - 'EditPage' => 'includes/EditPage.php', - 'MWException' => 'includes/Exception.php', - 'Exif' => 'includes/Exif.php', - 'FormatExif' => 'includes/Exif.php', - 'WikiExporter' => 'includes/Export.php', - 'XmlDumpWriter' => 'includes/Export.php', - 'DumpOutput' => 'includes/Export.php', - 'DumpFileOutput' => 'includes/Export.php', - 'DumpPipeOutput' => 'includes/Export.php', - 'DumpGZipOutput' => 'includes/Export.php', - 'DumpBZip2Output' => 'includes/Export.php', + 'DoubleReplacer' => 'includes/StringUtils.php', + 'DoubleRedirectJob' => 'includes/DoubleRedirectJob.php', 'Dump7ZipOutput' => 'includes/Export.php', + 'DumpBZip2Output' => 'includes/Export.php', + 'DumpFileOutput' => 'includes/Export.php', 'DumpFilter' => 'includes/Export.php', - 'DumpNotalkFilter' => 'includes/Export.php', - 'DumpNamespaceFilter' => 'includes/Export.php', + 'DumpGZipOutput' => 'includes/Export.php', 'DumpLatestFilter' => 'includes/Export.php', 'DumpMultiWriter' => 'includes/Export.php', + 'DumpNamespaceFilter' => 'includes/Export.php', + 'DumpNotalkFilter' => 'includes/Export.php', + 'DumpOutput' => 'includes/Export.php', + 'DumpPipeOutput' => 'includes/Export.php', + 'eAccelBagOStuff' => 'includes/BagOStuff.php', + 'EditPage' => 'includes/EditPage.php', + 'EmaillingJob' => 'includes/EmaillingJob.php', + 'EmailNotification' => 'includes/UserMailer.php', + 'EnhancedChangesList' => 'includes/ChangesList.php', + 'EnotifNotifyJob' => 'includes/EnotifNotifyJob.php', + 'ErrorPageError' => 'includes/Exception.php', + 'Exif' => 'includes/Exif.php', 'ExternalEdit' => 'includes/ExternalEdit.php', - 'ExternalStore' => 'includes/ExternalStore.php', 'ExternalStoreDB' => 'includes/ExternalStoreDB.php', 'ExternalStoreHttp' => 'includes/ExternalStoreHttp.php', + 'ExternalStore' => 'includes/ExternalStore.php', + 'FatalError' => 'includes/Exception.php', 'FakeTitle' => 'includes/FakeTitle.php', + 'FauxRequest' => 'includes/WebRequest.php', 'FeedItem' => 'includes/Feed.php', - 'ChannelFeed' => 'includes/Feed.php', - 'RSSFeed' => 'includes/Feed.php', - 'AtomFeed' => 'includes/Feed.php', + 'FeedUtils' => 'includes/FeedUtils.php', + 'FileDeleteForm' => 'includes/FileDeleteForm.php', + 'FileDependency' => 'includes/CacheDependency.php', + 'FileRevertForm' => 'includes/FileRevertForm.php', 'FileStore' => 'includes/FileStore.php', + 'FormatExif' => 'includes/Exif.php', + 'FormOptions' => 'includes/FormOptions.php', 'FSException' => 'includes/FileStore.php', 'FSTransaction' => 'includes/FileStore.php', + 'GlobalDependency' => 'includes/CacheDependency.php', + 'HashBagOStuff' => 'includes/BagOStuff.php', + 'HashtableReplacer' => 'includes/StringUtils.php', + 'HistoryBlobCurStub' => 'includes/HistoryBlob.php', 'HistoryBlob' => 'includes/HistoryBlob.php', - 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php', 'HistoryBlobStub' => 'includes/HistoryBlob.php', - 'HistoryBlobCurStub' => 'includes/HistoryBlob.php', 'HTMLCacheUpdate' => 'includes/HTMLCacheUpdate.php', + 'HTMLCacheUpdateJob' => 'includes/HTMLCacheUpdate.php', + 'HTMLFileCache' => 'includes/HTMLFileCache.php', 'Http' => 'includes/HttpFunctions.php', - 'IP' => 'includes/IP.php', + '_HWLDF_WordAccumulator' => 'includes/DifferenceEngine.php', 'ImageGallery' => 'includes/ImageGallery.php', - 'ImagePage' => 'includes/ImagePage.php', 'ImageHistoryList' => 'includes/ImagePage.php', - 'FileDeleteForm' => 'includes/FileDeleteForm.php', - 'FileRevertForm' => 'includes/FileRevertForm.php', + 'ImagePage' => 'includes/ImagePage.php', + 'ImageQueryPage' => 'includes/ImageQueryPage.php', + 'IncludableSpecialPage' => 'includes/SpecialPage.php', + 'IndexPager' => 'includes/Pager.php', + 'IP' => 'includes/IP.php', 'Job' => 'includes/JobQueue.php', - 'EmaillingJob' => 'includes/EmaillingJob.php', - 'EnotifNotifyJob' => 'includes/EnotifNotifyJob.php', - 'HTMLCacheUpdateJob' => 'includes/HTMLCacheUpdate.php', - 'RefreshLinksJob' => 'includes/RefreshLinksJob.php', - 'Licenses' => 'includes/Licenses.php', 'License' => 'includes/Licenses.php', + 'Licenses' => 'includes/Licenses.php', 'LinkBatch' => 'includes/LinkBatch.php', 'LinkCache' => 'includes/LinkCache.php', - 'LinkFilter' => 'includes/LinkFilter.php', 'Linker' => 'includes/Linker.php', + 'LinkFilter' => 'includes/LinkFilter.php', 'LinksUpdate' => 'includes/LinksUpdate.php', - 'LoadBalancer' => 'includes/LoadBalancer.php', 'LogPage' => 'includes/LogPage.php', + 'LogPager' => 'includes/LogEventsList.php', + 'LogEventsList' => 'includes/LogEventsList.php', + 'LogReader' => 'includes/LogEventsList.php', + 'LogViewer' => 'includes/LogEventsList.php', 'MacBinary' => 'includes/MacBinary.php', - 'MagicWord' => 'includes/MagicWord.php', 'MagicWordArray' => 'includes/MagicWord.php', + 'MagicWord' => 'includes/MagicWord.php', + 'MailAddress' => 'includes/UserMailer.php', + 'MappedDiff' => 'includes/DifferenceEngine.php', 'MathRenderer' => 'includes/Math.php', - 'MediaTransformOutput' => 'includes/MediaTransformOutput.php', - 'ThumbnailImage' => 'includes/MediaTransformOutput.php', 'MediaTransformError' => 'includes/MediaTransformOutput.php', - 'TransformParameterError' => 'includes/MediaTransformOutput.php', + 'MediaTransformOutput' => 'includes/MediaTransformOutput.php', + 'MediaWikiBagOStuff' => 'includes/BagOStuff.php', + 'MediaWiki_I18N' => 'includes/SkinTemplate.php', + 'MediaWiki' => 'includes/Wiki.php', + 'memcached' => 'includes/memcached-client.php', 'MessageCache' => 'includes/MessageCache.php', 'MimeMagic' => 'includes/MimeMagic.php', - 'Namespace' => 'includes/Namespace.php', - 'FakeMemCachedClient' => 'includes/ObjectCache.php', + 'MWException' => 'includes/Exception.php', + 'MWNamespace' => 'includes/Namespace.php', + 'MySQLSearchResultSet' => 'includes/SearchMySQL.php', + 'Namespace' => 'includes/NamespaceCompat.php', // Compat + 'OldChangesList' => 'includes/ChangesList.php', + 'OracleSearchResultSet' => 'includes/SearchOracle.php', 'OutputPage' => 'includes/OutputPage.php', 'PageHistory' => 'includes/PageHistory.php', - 'IndexPager' => 'includes/Pager.php', - 'ReverseChronologicalPager' => 'includes/Pager.php', - 'TablePager' => 'includes/Pager.php', - 'Parser' => 'includes/Parser.php', - 'Parser_OldPP' => 'includes/Parser_OldPP.php', - 'Parser_DiffTest' => 'includes/Parser_DiffTest.php', - 'ParserCache' => 'includes/ParserCache.php', - 'ParserOutput' => 'includes/ParserOutput.php', - 'ParserOptions' => 'includes/ParserOptions.php', + 'PageHistoryPager' => 'includes/PageHistory.php', + 'PageQueryPage' => 'includes/PageQueryPage.php', + 'Pager' => 'includes/Pager.php', + 'PasswordError' => 'includes/User.php', 'PatrolLog' => 'includes/PatrolLog.php', - 'Preprocessor' => 'includes/Preprocessor.php', + 'PostgresSearchResult' => 'includes/SearchPostgres.php', + 'PostgresSearchResultSet' => 'includes/SearchPostgres.php', 'PrefixSearch' => 'includes/PrefixSearch.php', - 'PPFrame' => 'includes/Preprocessor.php', - 'PPNode' => 'includes/Preprocessor.php', - 'Preprocessor_DOM' => 'includes/Preprocessor_DOM.php', - 'PPFrame_DOM' => 'includes/Preprocessor_DOM.php', - 'PPTemplateFrame_DOM' => 'includes/Preprocessor_DOM.php', - 'PPDStack' => 'includes/Preprocessor_DOM.php', - 'PPDStackElement' => 'includes/Preprocessor_DOM.php', - 'PPNode_DOM' => 'includes/Preprocessor_DOM.php', - 'Preprocessor_Hash' => 'includes/Preprocessor_Hash.php', + 'Profiler' => 'includes/Profiler.php', 'ProfilerSimple' => 'includes/ProfilerSimple.php', + 'ProfilerSimpleText' => 'includes/ProfilerSimpleText.php', 'ProfilerSimpleUDP' => 'includes/ProfilerSimpleUDP.php', - 'Profiler' => 'includes/Profiler.php', - 'ProxyTools' => 'includes/ProxyTools.php', 'ProtectionForm' => 'includes/ProtectionForm.php', 'QueryPage' => 'includes/QueryPage.php', - 'PageQueryPage' => 'includes/PageQueryPage.php', - 'ImageQueryPage' => 'includes/ImageQueryPage.php', + 'QuickTemplate' => 'includes/SkinTemplate.php', 'RawPage' => 'includes/RawPage.php', + 'RCCacheEntry' => 'includes/ChangesList.php', 'RecentChange' => 'includes/RecentChange.php', + 'RefreshLinksJob' => 'includes/RefreshLinksJob.php', + 'RegexlikeReplacer' => 'includes/StringUtils.php', + 'ReplacementArray' => 'includes/StringUtils.php', + 'Replacer' => 'includes/StringUtils.php', + 'ReverseChronologicalPager' => 'includes/Pager.php', 'Revision' => 'includes/Revision.php', + 'RSSFeed' => 'includes/Feed.php', 'Sanitizer' => 'includes/Sanitizer.php', - 'SearchEngine' => 'includes/SearchEngine.php', - 'SearchResultSet' => 'includes/SearchEngine.php', - 'SearchResult' => 'includes/SearchEngine.php', 'SearchEngineDummy' => 'includes/SearchEngine.php', - 'SearchMySQL' => 'includes/SearchMySQL.php', - 'MySQLSearchResultSet' => 'includes/SearchMySQL.php', + 'SearchEngine' => 'includes/SearchEngine.php', + 'SearchHighlighter' => 'includes/SearchEngine.php', 'SearchMySQL4' => 'includes/SearchMySQL4.php', + 'SearchMySQL' => 'includes/SearchMySQL.php', + 'SearchOracle' => 'includes/SearchOracle.php', 'SearchPostgres' => 'includes/SearchPostgres.php', + 'SearchResult' => 'includes/SearchEngine.php', + 'SearchResultSet' => 'includes/SearchEngine.php', + 'SearchResultTooMany' => 'includes/SearchEngine.php', 'SearchUpdate' => 'includes/SearchUpdate.php', 'SearchUpdateMyISAM' => 'includes/SearchUpdate.php', - 'SearchOracle' => 'includes/SearchOracle.php', 'SiteConfiguration' => 'includes/SiteConfiguration.php', 'SiteStats' => 'includes/SiteStats.php', 'SiteStatsUpdate' => 'includes/SiteStats.php', 'Skin' => 'includes/Skin.php', - 'MediaWiki_I18N' => 'includes/SkinTemplate.php', 'SkinTemplate' => 'includes/SkinTemplate.php', - 'QuickTemplate' => 'includes/SkinTemplate.php', - 'SpecialAllpages' => 'includes/SpecialAllpages.php', - 'AncientPagesPage' => 'includes/SpecialAncientpages.php', - 'IPBlockForm' => 'includes/SpecialBlockip.php', - 'SpecialBookSources' => 'includes/SpecialBooksources.php', - 'BrokenRedirectsPage' => 'includes/SpecialBrokenRedirects.php', - 'EmailConfirmation' => 'includes/SpecialConfirmemail.php', - 'ContributionsPage' => 'includes/SpecialContributions.php', - 'DeadendPagesPage' => 'includes/SpecialDeadendpages.php', - 'DisambiguationsPage' => 'includes/SpecialDisambiguations.php', - 'DoubleRedirectsPage' => 'includes/SpecialDoubleRedirects.php', - 'EmailUserForm' => 'includes/SpecialEmailuser.php', - 'WikiRevision' => 'includes/SpecialImport.php', - 'WikiImporter' => 'includes/SpecialImport.php', - 'ImportStringSource' => 'includes/SpecialImport.php', - 'ImportStreamSource' => 'includes/SpecialImport.php', - 'IPUnblockForm' => 'includes/SpecialIpblocklist.php', - 'ListredirectsPage' => 'includes/SpecialListredirects.php', - 'DBLockForm' => 'includes/SpecialLockdb.php', - 'LogReader' => 'includes/SpecialLog.php', - 'LogViewer' => 'includes/SpecialLog.php', - 'LonelyPagesPage' => 'includes/SpecialLonelypages.php', - 'LongPagesPage' => 'includes/SpecialLongpages.php', - 'MIMEsearchPage' => 'includes/SpecialMIMEsearch.php', - 'MostcategoriesPage' => 'includes/SpecialMostcategories.php', - 'MostimagesPage' => 'includes/SpecialMostimages.php', - 'MostlinkedPage' => 'includes/SpecialMostlinked.php', - 'MostlinkedCategoriesPage' => 'includes/SpecialMostlinkedcategories.php', - 'SpecialMostlinkedtemplates' => 'includes/SpecialMostlinkedtemplates.php', - 'MostrevisionsPage' => 'includes/SpecialMostrevisions.php', - 'FewestrevisionsPage' => 'includes/SpecialFewestrevisions.php', - 'MovePageForm' => 'includes/SpecialMovepage.php', - 'NewbieContributionsPage' => 'includes/SpecialNewbieContributions.php', - 'NewPagesPage' => 'includes/SpecialNewpages.php', + 'SpecialMycontributions' => 'includes/SpecialPage.php', + 'SpecialMypage' => 'includes/SpecialPage.php', + 'SpecialMytalk' => 'includes/SpecialPage.php', 'SpecialPage' => 'includes/SpecialPage.php', - 'UnlistedSpecialPage' => 'includes/SpecialPage.php', - 'IncludableSpecialPage' => 'includes/SpecialPage.php', - 'PopularPagesPage' => 'includes/SpecialPopularpages.php', - 'PreferencesForm' => 'includes/SpecialPreferences.php', - 'SpecialPrefixindex' => 'includes/SpecialPrefixindex.php', - 'RandomPage' => 'includes/SpecialRandompage.php', - 'SpecialRandomredirect' => 'includes/SpecialRandomredirect.php', - 'PasswordResetForm' => 'includes/SpecialResetpass.php', - 'RevisionDeleteForm' => 'includes/SpecialRevisiondelete.php', - 'RevisionDeleter' => 'includes/SpecialRevisiondelete.php', - 'SpecialSearch' => 'includes/SpecialSearch.php', - 'ShortPagesPage' => 'includes/SpecialShortpages.php', - 'UncategorizedCategoriesPage' => 'includes/SpecialUncategorizedcategories.php', - 'UncategorizedPagesPage' => 'includes/SpecialUncategorizedpages.php', - 'UncategorizedTemplatesPage' => 'includes/SpecialUncategorizedtemplates.php', - 'PageArchive' => 'includes/SpecialUndelete.php', - 'UndeleteForm' => 'includes/SpecialUndelete.php', - 'DBUnlockForm' => 'includes/SpecialUnlockdb.php', - 'UnusedCategoriesPage' => 'includes/SpecialUnusedcategories.php', - 'UnusedimagesPage' => 'includes/SpecialUnusedimages.php', - 'UnusedtemplatesPage' => 'includes/SpecialUnusedtemplates.php', - 'UnwatchedpagesPage' => 'includes/SpecialUnwatchedpages.php', - 'UploadForm' => 'includes/SpecialUpload.php', - 'UploadFormMogile' => 'includes/SpecialUploadMogile.php', - 'LoginForm' => 'includes/SpecialUserlogin.php', - 'UserrightsPage' => 'includes/SpecialUserrights.php', - 'SpecialVersion' => 'includes/SpecialVersion.php', - 'WantedCategoriesPage' => 'includes/SpecialWantedcategories.php', - 'WantedPagesPage' => 'includes/SpecialWantedpages.php', - 'WhatLinksHerePage' => 'includes/SpecialWhatlinkshere.php', - 'WithoutInterwikiPage' => 'includes/SpecialWithoutinterwiki.php', + 'SpecialRedirectToSpecial' => 'includes/SpecialPage.php', + 'SqlBagOStuff' => 'includes/BagOStuff.php', 'SquidUpdate' => 'includes/SquidUpdate.php', - 'ReplacementArray' => 'includes/StringUtils.php', - 'Replacer' => 'includes/StringUtils.php', - 'RegexlikeReplacer' => 'includes/StringUtils.php', - 'DoubleReplacer' => 'includes/StringUtils.php', - 'HashtableReplacer' => 'includes/StringUtils.php', + 'Status' => 'includes/Status.php', 'StringUtils' => 'includes/StringUtils.php', + 'TableDiffFormatter' => 'includes/DifferenceEngine.php', + 'TablePager' => 'includes/Pager.php', + 'ThumbnailImage' => 'includes/MediaTransformOutput.php', + 'TitleDependency' => 'includes/CacheDependency.php', 'Title' => 'includes/Title.php', + 'TitleListDependency' => 'includes/CacheDependency.php', + 'TransformParameterError' => 'includes/MediaTransformOutput.php', + 'TurckBagOStuff' => 'includes/BagOStuff.php', + 'UnifiedDiffFormatter' => 'includes/DifferenceEngine.php', + 'UnlistedSpecialPage' => 'includes/SpecialPage.php', 'User' => 'includes/User.php', - 'UserRightsProxy' => 'includes/UserRightsProxy.php', - 'MailAddress' => 'includes/UserMailer.php', - 'EmailNotification' => 'includes/UserMailer.php', + 'UserArray' => 'includes/UserArray.php', + 'UserArrayFromResult' => 'includes/UserArray.php', 'UserMailer' => 'includes/UserMailer.php', + 'UserRightsProxy' => 'includes/UserRightsProxy.php', 'WatchedItem' => 'includes/WatchedItem.php', + 'WatchlistEditor' => 'includes/WatchlistEditor.php', 'WebRequest' => 'includes/WebRequest.php', 'WebResponse' => 'includes/WebResponse.php', - 'FauxRequest' => 'includes/WebRequest.php', - 'MediaWiki' => 'includes/Wiki.php', 'WikiError' => 'includes/WikiError.php', 'WikiErrorMsg' => 'includes/WikiError.php', + 'WikiExporter' => 'includes/Export.php', 'WikiXmlError' => 'includes/WikiError.php', + 'WordLevelDiff' => 'includes/DifferenceEngine.php', + 'XCacheBagOStuff' => 'includes/BagOStuff.php', + 'XmlDumpWriter' => 'includes/Export.php', 'Xml' => 'includes/Xml.php', + 'XmlSelect' => 'includes/Xml.php', 'XmlTypeCheck' => 'includes/XmlTypeCheck.php', 'ZhClient' => 'includes/ZhClient.php', - 'memcached' => 'includes/memcached-client.php', - 'EmaillingJob' => 'includes/JobQueue.php', - 'WatchlistEditor' => 'includes/WatchlistEditor.php', - # filerepo - 'ArchivedFile' => 'includes/filerepo/ArchivedFile.php', - 'File' => 'includes/filerepo/File.php', - 'FileRepo' => 'includes/filerepo/FileRepo.php', - 'FileRepoStatus' => 'includes/filerepo/FileRepoStatus.php', - 'ForeignDBFile' => 'includes/filerepo/ForeignDBFile.php', - 'ForeignDBRepo' => 'includes/filerepo/ForeignDBRepo.php', - 'FSRepo' => 'includes/filerepo/FSRepo.php', - 'Image' => 'includes/filerepo/LocalFile.php', - 'LocalFile' => 'includes/filerepo/LocalFile.php', - 'LocalFileDeleteBatch' => 'includes/filerepo/LocalFile.php', - 'LocalFileRestoreBatch' => 'includes/filerepo/LocalFile.php', - 'LocalRepo' => 'includes/filerepo/LocalRepo.php', - 'OldLocalFile' => 'includes/filerepo/OldLocalFile.php', - 'RepoGroup' => 'includes/filerepo/RepoGroup.php', - 'UnregisteredLocalFile' => 'includes/filerepo/UnregisteredLocalFile.php', - - # Media - 'BitmapHandler' => 'includes/media/Bitmap.php', - 'BmpHandler' => 'includes/media/BMP.php', - 'DjVuHandler' => 'includes/media/DjVu.php', - 'MediaHandler' => 'includes/media/Generic.php', - 'ImageHandler' => 'includes/media/Generic.php', - 'SvgHandler' => 'includes/media/SVG.php', - - # Normal - 'UtfNormal' => 'includes/normal/UtfNormal.php', - - # Templates - 'UsercreateTemplate' => 'includes/templates/Userlogin.php', - 'UserloginTemplate' => 'includes/templates/Userlogin.php', - - # Languages - 'Language' => 'languages/Language.php', - - # API + # includes/api 'ApiBase' => 'includes/api/ApiBase.php', + 'ApiBlock' => 'includes/api/ApiBlock.php', + 'ApiDelete' => 'includes/api/ApiDelete.php', + 'ApiEditPage' => 'includes/api/ApiEditPage.php', + 'ApiEmailUser' => 'includes/api/ApiEmailUser.php', 'ApiExpandTemplates' => 'includes/api/ApiExpandTemplates.php', - 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php', 'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php', 'ApiFormatBase' => 'includes/api/ApiFormatBase.php', - 'Services_JSON' => 'includes/api/ApiFormatJson_json.php', + 'ApiFormatDbg' => 'includes/api/ApiFormatDbg.php', + 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php', 'ApiFormatJson' => 'includes/api/ApiFormatJson.php', 'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php', + 'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php', 'ApiFormatWddx' => 'includes/api/ApiFormatWddx.php', 'ApiFormatXml' => 'includes/api/ApiFormatXml.php', - 'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php', - 'ApiFormatDbg' => 'includes/api/ApiFormatDbg.php', - 'Spyc' => 'includes/api/ApiFormatYaml_spyc.php', 'ApiFormatYaml' => 'includes/api/ApiFormatYaml.php', 'ApiHelp' => 'includes/api/ApiHelp.php', 'ApiLogin' => 'includes/api/ApiLogin.php', 'ApiLogout' => 'includes/api/ApiLogout.php', 'ApiMain' => 'includes/api/ApiMain.php', + 'ApiMove' => 'includes/api/ApiMove.php', 'ApiOpenSearch' => 'includes/api/ApiOpenSearch.php', 'ApiPageSet' => 'includes/api/ApiPageSet.php', 'ApiParamInfo' => 'includes/api/ApiParamInfo.php', 'ApiParse' => 'includes/api/ApiParse.php', + 'ApiProtect' => 'includes/api/ApiProtect.php', 'ApiQuery' => 'includes/api/ApiQuery.php', - 'ApiQueryAllpages' => 'includes/api/ApiQueryAllpages.php', - 'ApiQueryAllLinks' => 'includes/api/ApiQueryAllLinks.php', 'ApiQueryAllCategories' => 'includes/api/ApiQueryAllCategories.php', + 'ApiQueryAllimages' => 'includes/api/ApiQueryAllimages.php', + 'ApiQueryAllLinks' => 'includes/api/ApiQueryAllLinks.php', 'ApiQueryAllUsers' => 'includes/api/ApiQueryAllUsers.php', - 'ApiQueryBase' => 'includes/api/ApiQueryBase.php', - 'ApiQueryGeneratorBase' => 'includes/api/ApiQueryBase.php', + 'ApiQueryAllmessages' => 'includes/api/ApiQueryAllmessages.php', + 'ApiQueryAllpages' => 'includes/api/ApiQueryAllpages.php', 'ApiQueryBacklinks' => 'includes/api/ApiQueryBacklinks.php', + 'ApiQueryBase' => 'includes/api/ApiQueryBase.php', + 'ApiQueryBlocks' => 'includes/api/ApiQueryBlocks.php', 'ApiQueryCategories' => 'includes/api/ApiQueryCategories.php', + 'ApiQueryCategoryInfo' => 'includes/api/ApiQueryCategoryInfo.php', 'ApiQueryCategoryMembers' => 'includes/api/ApiQueryCategoryMembers.php', 'ApiQueryContributions' => 'includes/api/ApiQueryUserContributions.php', - 'ApiQueryExternalLinks' => 'includes/api/ApiQueryExternalLinks.php', + 'ApiQueryDeletedrevs' => 'includes/api/ApiQueryDeletedrevs.php', 'ApiQueryExtLinksUsage' => 'includes/api/ApiQueryExtLinksUsage.php', - 'ApiQueryImages' => 'includes/api/ApiQueryImages.php', + 'ApiQueryExternalLinks' => 'includes/api/ApiQueryExternalLinks.php', + 'ApiQueryGeneratorBase' => 'includes/api/ApiQueryBase.php', 'ApiQueryImageInfo' => 'includes/api/ApiQueryImageInfo.php', + 'ApiQueryImages' => 'includes/api/ApiQueryImages.php', 'ApiQueryInfo' => 'includes/api/ApiQueryInfo.php', 'ApiQueryLangLinks' => 'includes/api/ApiQueryLangLinks.php', 'ApiQueryLinks' => 'includes/api/ApiQueryLinks.php', @@ -358,72 +271,264 @@ function __autoload($className) { 'ApiQueryRecentChanges'=> 'includes/api/ApiQueryRecentChanges.php', 'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php', 'ApiQuerySearch' => 'includes/api/ApiQuerySearch.php', - 'ApiQueryAllmessages' => 'includes/api/ApiQueryAllmessages.php', 'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php', - 'ApiQueryUsers' => 'includes/api/ApiQueryUsers.php', 'ApiQueryUserInfo' => 'includes/api/ApiQueryUserInfo.php', + 'ApiQueryUsers' => 'includes/api/ApiQueryUsers.php', 'ApiQueryWatchlist' => 'includes/api/ApiQueryWatchlist.php', 'ApiResult' => 'includes/api/ApiResult.php', - - # apiedit branch - 'ApiBlock' => 'includes/api/ApiBlock.php', - #'ApiChangeRights' => 'includes/api/ApiChangeRights.php', - # Disabled for now - 'ApiDelete' => 'includes/api/ApiDelete.php', - 'ApiMove' => 'includes/api/ApiMove.php', - 'ApiProtect' => 'includes/api/ApiProtect.php', - 'ApiQueryBlocks' => 'includes/api/ApiQueryBlocks.php', - 'ApiQueryDeletedrevs' => 'includes/api/ApiQueryDeletedrevs.php', 'ApiRollback' => 'includes/api/ApiRollback.php', 'ApiUnblock' => 'includes/api/ApiUnblock.php', - 'ApiUndelete' => 'includes/api/ApiUndelete.php' + 'ApiUndelete' => 'includes/api/ApiUndelete.php', + 'Services_JSON' => 'includes/api/ApiFormatJson_json.php', + 'Services_JSON_Error' => 'includes/api/ApiFormatJson_json.php', + 'Spyc' => 'includes/api/ApiFormatYaml_spyc.php', + 'UsageException' => 'includes/api/ApiMain.php', + 'YAMLNode' => 'includes/api/ApiFormatYaml_spyc.php', + + # includes/db + 'Blob' => 'includes/db/Database.php', + 'ChronologyProtector' => 'includes/db/LBFactory.php', + 'Database' => 'includes/db/Database.php', + 'DatabaseMssql' => 'includes/db/DatabaseMssql.php', + 'DatabaseMysql' => 'includes/db/Database.php', + 'DatabaseOracle' => 'includes/db/DatabaseOracle.php', + 'DatabasePostgres' => 'includes/db/DatabasePostgres.php', + 'DatabaseSqlite' => 'includes/db/DatabaseSqlite.php', + 'DBConnectionError' => 'includes/db/Database.php', + 'DBError' => 'includes/db/Database.php', + 'DBObject' => 'includes/db/Database.php', + 'DBQueryError' => 'includes/db/Database.php', + 'DBUnexpectedError' => 'includes/db/Database.php', + 'LBFactory' => 'includes/db/LBFactory.php', + 'LBFactory_Multi' => 'includes/db/LBFactory_Multi.php', + 'LBFactory_Simple' => 'includes/db/LBFactory.php', + 'LoadBalancer' => 'includes/db/LoadBalancer.php', + 'LoadMonitor' => 'includes/db/LoadMonitor.php', + 'LoadMonitor_MySQL' => 'includes/db/LoadMonitor.php', + 'MSSQLField' => 'includes/db/DatabaseMssql.php', + 'MySQLField' => 'includes/db/Database.php', + 'MySQLMasterPos' => 'includes/db/Database.php', + 'ORABlob' => 'includes/db/DatabaseOracle.php', + 'ORAResult' => 'includes/db/DatabaseOracle.php', + 'PostgresField' => 'includes/db/DatabasePostgres.php', + 'ResultWrapper' => 'includes/db/Database.php', + 'SQLiteField' => 'includes/db/DatabaseSqlite.php', + + # includes/filerepo + 'ArchivedFile' => 'includes/filerepo/ArchivedFile.php', + 'File' => 'includes/filerepo/File.php', + 'FileRepo' => 'includes/filerepo/FileRepo.php', + 'FileRepoStatus' => 'includes/filerepo/FileRepoStatus.php', + 'ForeignAPIFile' => 'includes/filerepo/ForeignAPIFile.php', + 'ForeignAPIRepo' => 'includes/filerepo/ForeignAPIRepo.php', + 'ForeignDBFile' => 'includes/filerepo/ForeignDBFile.php', + 'ForeignDBRepo' => 'includes/filerepo/ForeignDBRepo.php', + 'ForeignDBViaLBRepo' => 'includes/filerepo/ForeignDBViaLBRepo.php', + 'FSRepo' => 'includes/filerepo/FSRepo.php', + 'Image' => 'includes/filerepo/Image.php', + 'LocalFile' => 'includes/filerepo/LocalFile.php', + 'LocalFileDeleteBatch' => 'includes/filerepo/LocalFile.php', + 'LocalFileMoveBatch' => 'includes/filerepo/LocalFile.php', + 'LocalFileRestoreBatch' => 'includes/filerepo/LocalFile.php', + 'LocalRepo' => 'includes/filerepo/LocalRepo.php', + 'OldLocalFile' => 'includes/filerepo/OldLocalFile.php', + 'RepoGroup' => 'includes/filerepo/RepoGroup.php', + 'UnregisteredLocalFile' => 'includes/filerepo/UnregisteredLocalFile.php', + + # includes/media + 'BitmapHandler' => 'includes/media/Bitmap.php', + 'BmpHandler' => 'includes/media/BMP.php', + 'DjVuHandler' => 'includes/media/DjVu.php', + 'ImageHandler' => 'includes/media/Generic.php', + 'MediaHandler' => 'includes/media/Generic.php', + 'SvgHandler' => 'includes/media/SVG.php', + + # includes/normal + 'UtfNormal' => 'includes/normal/UtfNormal.php', + + # includes/parser + 'CoreParserFunctions' => 'includes/parser/CoreParserFunctions.php', + 'DateFormatter' => 'includes/parser/DateFormatter.php', + 'OnlyIncludeReplacer' => 'includes/parser/Parser.php', + 'PPDAccum_Hash' => 'includes/parser/Preprocessor_Hash.php', + 'PPDPart' => 'includes/parser/Preprocessor_DOM.php', + 'PPDPart_Hash' => 'includes/parser/Preprocessor_Hash.php', + 'PPDStack' => 'includes/parser/Preprocessor_DOM.php', + 'PPDStackElement' => 'includes/parser/Preprocessor_DOM.php', + 'PPDStackElement_Hash' => 'includes/parser/Preprocessor_Hash.php', + 'PPDStack_Hash' => 'includes/parser/Preprocessor_Hash.php', + 'PPFrame' => 'includes/parser/Preprocessor.php', + 'PPFrame_DOM' => 'includes/parser/Preprocessor_DOM.php', + 'PPFrame_Hash' => 'includes/parser/Preprocessor_Hash.php', + 'PPNode' => 'includes/parser/Preprocessor.php', + 'PPNode_DOM' => 'includes/parser/Preprocessor_DOM.php', + 'PPNode_Hash_Array' => 'includes/parser/Preprocessor_Hash.php', + 'PPNode_Hash_Attr' => 'includes/parser/Preprocessor_Hash.php', + 'PPNode_Hash_Text' => 'includes/parser/Preprocessor_Hash.php', + 'PPNode_Hash_Tree' => 'includes/parser/Preprocessor_Hash.php', + 'PPTemplateFrame_DOM' => 'includes/parser/Preprocessor_DOM.php', + 'PPTemplateFrame_Hash' => 'includes/parser/Preprocessor_Hash.php', + 'Parser' => 'includes/parser/Parser.php', + 'ParserCache' => 'includes/parser/ParserCache.php', + 'ParserOptions' => 'includes/parser/ParserOptions.php', + 'ParserOutput' => 'includes/parser/ParserOutput.php', + 'Parser_DiffTest' => 'includes/parser/Parser_DiffTest.php', + 'Parser_OldPP' => 'includes/parser/Parser_OldPP.php', + 'Preprocessor' => 'includes/parser/Preprocessor.php', + 'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php', + 'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php', + 'StripState' => 'includes/parser/Parser.php', + + # includes/specials + 'AncientPagesPage' => 'includes/specials/SpecialAncientpages.php', + 'BrokenRedirectsPage' => 'includes/specials/SpecialBrokenRedirects.php', + 'ContribsPager' => 'includes/specials/SpecialContributions.php', + 'DBLockForm' => 'includes/specials/SpecialLockdb.php', + 'DBUnlockForm' => 'includes/specials/SpecialUnlockdb.php', + 'DeadendPagesPage' => 'includes/specials/SpecialDeadendpages.php', + 'DisambiguationsPage' => 'includes/specials/SpecialDisambiguations.php', + 'DoubleRedirectsPage' => 'includes/specials/SpecialDoubleRedirects.php', + 'EmailConfirmation' => 'includes/specials/SpecialConfirmemail.php', + 'EmailInvalidation' => 'includes/specials/SpecialConfirmemail.php', + 'EmailUserForm' => 'includes/specials/SpecialEmailuser.php', + 'FewestrevisionsPage' => 'includes/specials/SpecialFewestrevisions.php', + 'FileDuplicateSearchPage' => 'includes/specials/SpecialFileDuplicateSearch.php', + 'IPBlockForm' => 'includes/specials/SpecialBlockip.php', + 'IPBlocklistPager' => 'includes/specials/SpecialIpblocklist.php', + 'IPUnblockForm' => 'includes/specials/SpecialIpblocklist.php', + 'ImportReporter' => 'includes/specials/SpecialImport.php', + 'ImportStreamSource' => 'includes/specials/SpecialImport.php', + 'ImportStringSource' => 'includes/specials/SpecialImport.php', + 'ListredirectsPage' => 'includes/specials/SpecialListredirects.php', + 'LoginForm' => 'includes/specials/SpecialUserlogin.php', + 'LonelyPagesPage' => 'includes/specials/SpecialLonelypages.php', + 'LongPagesPage' => 'includes/specials/SpecialLongpages.php', + 'MIMEsearchPage' => 'includes/specials/SpecialMIMEsearch.php', + 'MostcategoriesPage' => 'includes/specials/SpecialMostcategories.php', + 'MostimagesPage' => 'includes/specials/SpecialMostimages.php', + 'MostlinkedCategoriesPage' => 'includes/specials/SpecialMostlinkedcategories.php', + 'MostlinkedPage' => 'includes/specials/SpecialMostlinked.php', + 'MostrevisionsPage' => 'includes/specials/SpecialMostrevisions.php', + 'MovePageForm' => 'includes/specials/SpecialMovepage.php', + 'SpecialNewpages' => 'includes/specials/SpecialNewpages.php', + 'NewPagesPager' => 'includes/specials/SpecialNewpages.php', + 'PageArchive' => 'includes/specials/SpecialUndelete.php', + 'PasswordResetForm' => 'includes/specials/SpecialResetpass.php', + 'PopularPagesPage' => 'includes/specials/SpecialPopularpages.php', + 'PreferencesForm' => 'includes/specials/SpecialPreferences.php', + 'RandomPage' => 'includes/specials/SpecialRandompage.php', + 'RevisionDeleteForm' => 'includes/specials/SpecialRevisiondelete.php', + 'RevisionDeleter' => 'includes/specials/SpecialRevisiondelete.php', + 'ShortPagesPage' => 'includes/specials/SpecialShortpages.php', + 'SpecialAllpages' => 'includes/specials/SpecialAllpages.php', + 'SpecialBookSources' => 'includes/specials/SpecialBooksources.php', + 'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php', + 'SpecialMostlinkedtemplates' => 'includes/specials/SpecialMostlinkedtemplates.php', + 'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php', + 'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php', + 'SpecialRecentchanges' => 'includes/specials/SpecialRecentchanges.php', + 'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php', + 'SpecialSearch' => 'includes/specials/SpecialSearch.php', + 'SpecialVersion' => 'includes/specials/SpecialVersion.php', + 'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php', + 'UncategorizedPagesPage' => 'includes/specials/SpecialUncategorizedpages.php', + 'UncategorizedTemplatesPage' => 'includes/specials/SpecialUncategorizedtemplates.php', + 'UndeleteForm' => 'includes/specials/SpecialUndelete.php', + 'UnusedCategoriesPage' => 'includes/specials/SpecialUnusedcategories.php', + 'UnusedimagesPage' => 'includes/specials/SpecialUnusedimages.php', + 'UnusedtemplatesPage' => 'includes/specials/SpecialUnusedtemplates.php', + 'UnwatchedpagesPage' => 'includes/specials/SpecialUnwatchedpages.php', + 'UploadForm' => 'includes/specials/SpecialUpload.php', + 'UploadFormMogile' => 'includes/specials/SpecialUploadMogile.php', + 'UserrightsPage' => 'includes/specials/SpecialUserrights.php', + 'UsersPager' => 'includes/specials/SpecialListusers.php', + 'WantedCategoriesPage' => 'includes/specials/SpecialWantedcategories.php', + 'WantedPagesPage' => 'includes/specials/SpecialWantedpages.php', + 'WhatLinksHerePage' => 'includes/specials/SpecialWhatlinkshere.php', + 'WikiImporter' => 'includes/specials/SpecialImport.php', + 'WikiRevision' => 'includes/specials/SpecialImport.php', + 'WithoutInterwikiPage' => 'includes/specials/SpecialWithoutinterwiki.php', + + # includes/templates + 'UsercreateTemplate' => 'includes/templates/Userlogin.php', + 'UserloginTemplate' => 'includes/templates/Userlogin.php', + + # languages + 'Language' => 'languages/Language.php', + 'FakeConverter' => 'languages/Language.php', + + # maintenance/language + 'statsOutput' => 'maintenance/language/StatOutputs.php', + 'wikiStatsOutput' => 'maintenance/language/StatOutputs.php', + 'metawikiStatsOutput' => 'maintenance/language/StatOutputs.php', + 'textStatsOutput' => 'maintenance/language/StatOutputs.php', + 'csvStatsOutput' => 'maintenance/language/StatOutputs.php', + ); - - wfProfileIn( __METHOD__ ); - if ( isset( $localClasses[$className] ) ) { - $filename = $localClasses[$className]; - } elseif ( isset( $wgAutoloadClasses[$className] ) ) { - $filename = $wgAutoloadClasses[$className]; - } else { - # Try a different capitalisation - # The case can sometimes be wrong when unserializing PHP 4 objects - $filename = false; - $lowerClass = strtolower( $className ); - foreach ( $localClasses as $class2 => $file2 ) { - if ( strtolower( $class2 ) == $lowerClass ) { - $filename = $file2; + + /** + * autoload - take a class name and attempt to load it + * + * @param string $className Name of class we're looking for. + * @return bool Returning false is important on failure as + * it allows Zend to try and look in other registered autoloaders + * as well. + */ + static function autoload( $className ) { + global $wgAutoloadClasses; + + wfProfileIn( __METHOD__ ); + if ( isset( self::$localClasses[$className] ) ) { + $filename = self::$localClasses[$className]; + } elseif ( isset( $wgAutoloadClasses[$className] ) ) { + $filename = $wgAutoloadClasses[$className]; + } else { + # Try a different capitalisation + # The case can sometimes be wrong when unserializing PHP 4 objects + $filename = false; + $lowerClass = strtolower( $className ); + foreach ( self::$localClasses as $class2 => $file2 ) { + if ( strtolower( $class2 ) == $lowerClass ) { + $filename = $file2; + } + } + if ( !$filename ) { + # Give up + wfProfileOut( __METHOD__ ); + return false; } } - if ( !$filename ) { - # Give up - wfProfileOut( __METHOD__ ); - return; + + # Make an absolute path, this improves performance by avoiding some stat calls + if ( substr( $filename, 0, 1 ) != '/' && substr( $filename, 1, 1 ) != ':' ) { + global $IP; + $filename = "$IP/$filename"; } + require( $filename ); + wfProfileOut( __METHOD__ ); + return true; } - # Make an absolute path, this improves performance by avoiding some stat calls - if ( substr( $filename, 0, 1 ) != '/' && substr( $filename, 1, 1 ) != ':' ) { - global $IP; - $filename = "$IP/$filename"; + static function loadAllExtensions() { + global $wgAutoloadClasses; + + foreach( $wgAutoloadClasses as $class => $file ) { + if( !( class_exists( $class ) || interface_exists( $class ) ) ) { + require( $file ); + } + } } - require( $filename ); - wfProfileOut( __METHOD__ ); } function wfLoadAllExtensions() { - global $wgAutoloadClasses; + AutoLoader::loadAllExtensions(); +} - # It is crucial that SpecialPage.php is included before any special page - # extensions are loaded. Otherwise the parent class will not be available - # when APC loads the early-bound extension class. Normally this is - # guaranteed by entering special pages via SpecialPage members such as - # executePath(), but here we have to take a more explicit measure. - - require_once( dirname(__FILE__) . '/SpecialPage.php' ); - - foreach( $wgAutoloadClasses as $class => $file ) { - if( !( class_exists( $class ) || interface_exists( $class ) ) ) { - require( $file ); - } +if ( function_exists( 'spl_autoload_register' ) ) { + spl_autoload_register( array( 'AutoLoader', 'autoload' ) ); +} else { + function __autoload( $class ) { + AutoLoader::autoload( $class ); } } + diff --git a/includes/Autopromote.php b/includes/Autopromote.php index b5097423..68fe6636 100644 --- a/includes/Autopromote.php +++ b/includes/Autopromote.php @@ -8,7 +8,7 @@ class Autopromote { /** * Get the groups for the given user based on $wgAutopromote. * - * @param User $user The user to get the groups for + * @param $user The user to get the groups for * @return array Array of groups to promote to. */ public static function getAutopromoteGroups( User $user ) { @@ -18,6 +18,9 @@ class Autopromote { if( self::recCheckCondition( $cond, $user ) ) $promote[] = $group; } + + wfRunHooks( 'GetAutoPromoteGroups', array($user, &$promote) ); + return $promote; } @@ -33,12 +36,12 @@ class Autopromote { * This function evaluates the former type recursively, and passes off to * self::checkCondition for evaluation of the latter type. * - * @param mixed $cond A condition, possibly containing other conditions - * @param User $user The user to check the conditions against + * @param $cond Mixed: a condition, possibly containing other conditions + * @param $user The user to check the conditions against * @return bool Whether the condition is true */ private static function recCheckCondition( $cond, User $user ) { - $validOps = array( '&', '|', '^' ); + $validOps = array( '&', '|', '^', '!' ); if( is_array( $cond ) && count( $cond ) >= 2 && in_array( $cond[0], $validOps ) ) { # Recursive condition if( $cond[0] == '&' ) { @@ -47,7 +50,7 @@ class Autopromote { return false; return true; } elseif( $cond[0] == '|' ) { - foreach( array_slice( $cond, 1 ) as $subcond ) + foreach( array_slice( $cond, 1 ) as $subcond ) if( self::recCheckCondition( $subcond, $user ) ) return true; return false; @@ -60,6 +63,11 @@ class Autopromote { $res = ($res xor self::recCheckCondition( $subcond, $user )); } return $res; + } elseif ( $cond[0] = '!' ) { + foreach( array_slice( $cond, 1 ) as $subcond ) + if( self::recCheckCondition( $subcond, $user ) ) + return false; + return true; } } # If we got here, the array presumably does not contain other condi- @@ -75,8 +83,8 @@ class Autopromote { * APCOND_AGE. Other types will throw an exception if no extension evalu- * ates them. * - * @param array $cond A condition, which must not contain other conditions - * @param User $user The user to check the condition against + * @param $cond Array: A condition, which must not contain other conditions + * @param $user The user to check the condition against * @return bool Whether the condition is true for the user */ private static function checkCondition( $cond, User $user ) { @@ -87,7 +95,7 @@ class Autopromote { if( User::isValidEmailAddr( $user->getEmail() ) ) { global $wgEmailAuthentication; if( $wgEmailAuthentication ) { - return $user->getEmailAuthenticationTimestamp() ? true : false; + return (bool)$user->getEmailAuthenticationTimestamp(); } else { return true; } diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php index 226abb35..b4fefc97 100644 --- a/includes/BagOStuff.php +++ b/includes/BagOStuff.php @@ -17,22 +17,25 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # http://www.gnu.org/copyleft/gpl.html + /** + * @defgroup Cache Cache * + * @file + * @ingroup Cache */ /** - * Simple generic object store - * * interface is intended to be more or less compatible with * the PHP memcached client. * * backends for local hash array and SQL table included: * * $bag = new HashBagOStuff(); - * $bag = new MysqlBagOStuff($tablename); # connect to db first + * $bag = new MediaWikiBagOStuff($tablename); # connect to db first * * + * @ingroup Cache */ class BagOStuff { var $debugmode; @@ -167,14 +170,12 @@ class BagOStuff { /** * Functional versions! - * @todo document + * This is a test of the interface, mainly. It stores things in an associative + * array, which is not going to persist between program runs. + * + * @ingroup Cache */ class HashBagOStuff extends BagOStuff { - /* - This is a test of the interface, mainly. It stores - things in an associative array, which is not going to - persist between program runs. - */ var $bag; function __construct() { @@ -213,24 +214,20 @@ class HashBagOStuff extends BagOStuff { } } -/* -CREATE TABLE objectcache ( - keyname char(255) binary not null default '', - value mediumblob, - exptime datetime, - unique key (keyname), - key (exptime) -); -*/ - /** - * @todo document - * @abstract + * Generic class to store objects in a database + * + * @ingroup Cache */ abstract class SqlBagOStuff extends BagOStuff { var $table; var $lastexpireall = 0; + /** + * Constructor + * + * @param $tablename String: name of the table to use + */ function __construct($tablename = 'objectcache') { $this->table = $tablename; } @@ -247,8 +244,8 @@ abstract class SqlBagOStuff extends BagOStuff { } if($row=$this->_fetchobject($res)) { $this->_debug("get: retrieved data; exp time is " . $row->exptime); - if ( $row->exptime != $this->_maxdatetime() && - wfTimestamp( TS_UNIX, $row->exptime ) < time() ) + if ( $row->exptime != $this->_maxdatetime() && + wfTimestamp( TS_UNIX, $row->exptime ) < time() ) { $this->_debug("get: key has expired, deleting"); $this->delete($key); @@ -262,7 +259,7 @@ abstract class SqlBagOStuff extends BagOStuff { } function set($key,$value,$exptime=0) { - if ( wfReadOnly() ) { + if ( $this->_readonly() ) { return false; } $exptime = intval($exptime); @@ -284,7 +281,7 @@ abstract class SqlBagOStuff extends BagOStuff { } function delete($key,$time=0) { - if ( wfReadOnly() ) { + if ( $this->_readonly() ) { return false; } $this->_query( @@ -340,6 +337,8 @@ abstract class SqlBagOStuff extends BagOStuff { abstract function _doinsert($table, $vals); abstract function _doquery($sql); + abstract function _readonly(); + function _freeresult($result) { /* stub */ return false; @@ -367,7 +366,7 @@ abstract class SqlBagOStuff extends BagOStuff { function expireall() { /* Remove any items that have expired */ - if ( wfReadOnly() ) { + if ( $this->_readonly() ) { return false; } $now = $this->_fromunixtime( time() ); @@ -376,7 +375,7 @@ abstract class SqlBagOStuff extends BagOStuff { function deleteall() { /* Clear *all* items from cache table */ - if ( wfReadOnly() ) { + if ( $this->_readonly() ) { return false; } $this->_query( "DELETE FROM $0" ); @@ -387,7 +386,7 @@ abstract class SqlBagOStuff extends BagOStuff { * On typical message and page data, this can provide a 3X decrease * in storage requirements. * - * @param mixed $data + * @param $data mixed * @return string */ function _serialize( &$data ) { @@ -401,7 +400,7 @@ abstract class SqlBagOStuff extends BagOStuff { /** * Unserialize and, if necessary, decompress an object. - * @param string $serial + * @param $serial string * @return mixed */ function _unserialize( $serial ) { @@ -417,31 +416,33 @@ abstract class SqlBagOStuff extends BagOStuff { } /** - * @todo document + * Stores objects in the main database of the wiki + * + * @ingroup Cache */ class MediaWikiBagOStuff extends SqlBagOStuff { var $tableInitialised = false; + function _getDB(){ + static $db; + if( !isset( $db ) ) + $db = wfGetDB( DB_MASTER ); + return $db; + } function _doquery($sql) { - $dbw = wfGetDB( DB_MASTER ); - return $dbw->query($sql, 'MediaWikiBagOStuff::_doquery'); + return $this->_getDB()->query( $sql, __METHOD__ ); } function _doinsert($t, $v) { - $dbw = wfGetDB( DB_MASTER ); - return $dbw->insert($t, $v, 'MediaWikiBagOStuff::_doinsert', - array( 'IGNORE' ) ); + return $this->_getDB()->insert($t, $v, __METHOD__, array( 'IGNORE' ) ); } function _fetchobject($result) { - $dbw = wfGetDB( DB_MASTER ); - return $dbw->fetchObject($result); + return $this->_getDB()->fetchObject($result); } function _freeresult($result) { - $dbw = wfGetDB( DB_MASTER ); - return $dbw->freeResult($result); + return $this->_getDB()->freeResult($result); } function _dberror($result) { - $dbw = wfGetDB( DB_MASTER ); - return $dbw->lastError(); + return $this->_getDB()->lastError(); } function _maxdatetime() { if ( time() > 0x7fffffff ) { @@ -451,24 +452,23 @@ class MediaWikiBagOStuff extends SqlBagOStuff { } } function _fromunixtime($ts) { - $dbw = wfGetDB(DB_MASTER); - return $dbw->timestamp($ts); + return $this->_getDB()->timestamp($ts); + } + function _readonly(){ + return wfReadOnly(); } function _strencode($s) { - $dbw = wfGetDB( DB_MASTER ); - return $dbw->strencode($s); + return $this->_getDB()->strencode($s); } function _blobencode($s) { - $dbw = wfGetDB( DB_MASTER ); - return $dbw->encodeBlob($s); + return $this->_getDB()->encodeBlob($s); } function _blobdecode($s) { - $dbw = wfGetDB( DB_MASTER ); - return $dbw->decodeBlob($s); + return $this->_getDB()->decodeBlob($s); } function getTableName() { if ( !$this->tableInitialised ) { - $dbw = wfGetDB( DB_MASTER ); + $dbw = $this->_getDB(); /* This is actually a hack, we should be able to use Language classes here... or not */ if (!$dbw) @@ -493,6 +493,7 @@ class MediaWikiBagOStuff extends SqlBagOStuff { * that Turck's serializer is faster, so a possible future extension would be * to use it for arrays but not for objects. * + * @ingroup Cache */ class TurckBagOStuff extends BagOStuff { function get($key) { @@ -527,6 +528,7 @@ class TurckBagOStuff extends BagOStuff { /** * This is a wrapper for APC's shared memory functions * + * @ingroup Cache */ class APCBagOStuff extends BagOStuff { function get($key) { @@ -536,12 +538,12 @@ class APCBagOStuff extends BagOStuff { } return $val; } - + function set($key, $value, $exptime=0) { apc_store($key, serialize($value), $exptime); return true; } - + function delete($key, $time=0) { apc_delete($key); return true; @@ -555,6 +557,7 @@ class APCBagOStuff extends BagOStuff { * This is basically identical to the Turck MMCache version, * mostly because eAccelerator is based on Turck MMCache. * + * @ingroup Cache */ class eAccelBagOStuff extends BagOStuff { function get($key) { @@ -589,13 +592,15 @@ class eAccelBagOStuff extends BagOStuff { /** * Wrapper for XCache object caching functions; identical interface * to the APC wrapper + * + * @ingroup Cache */ class XCacheBagOStuff extends BagOStuff { /** * Get a value from the XCache object cache * - * @param string $key Cache key + * @param $key String: cache key * @return mixed */ public function get( $key ) { @@ -604,40 +609,41 @@ class XCacheBagOStuff extends BagOStuff { $val = unserialize( $val ); return $val; } - + /** * Store a value in the XCache object cache * - * @param string $key Cache key - * @param mixed $value Object to store - * @param int $expire Expiration time + * @param $key String: cache key + * @param $value Mixed: object to store + * @param $expire Int: expiration time * @return bool */ public function set( $key, $value, $expire = 0 ) { xcache_set( $key, serialize( $value ), $expire ); return true; } - + /** * Remove a value from the XCache object cache * - * @param string $key Cache key - * @param int $time Not used in this implementation + * @param $key String: cache key + * @param $time Int: not used in this implementation * @return bool */ public function delete( $key, $time = 0 ) { xcache_unset( $key ); return true; } - + } /** * @todo document + * @ingroup Cache */ class DBABagOStuff extends BagOStuff { var $mHandler, $mFile, $mReader, $mWriter, $mDisabled; - + function __construct( $handler = 'db3', $dir = false ) { if ( $dir === false ) { global $wgTmpDirectory; @@ -645,6 +651,7 @@ class DBABagOStuff extends BagOStuff { } $this->mFile = "$dir/mw-cache-" . wfWikiID(); $this->mFile .= '.db'; + wfDebug( __CLASS__.": using cache file {$this->mFile}\n" ); $this->mHandler = $handler; } @@ -664,7 +671,7 @@ class DBABagOStuff extends BagOStuff { if ( !is_string( $blob ) ) { return array( null, 0 ); } else { - return array( + return array( unserialize( substr( $blob, 11 ) ), intval( substr( $blob, 0, 10 ) ) ); @@ -779,5 +786,3 @@ class DBABagOStuff extends BagOStuff { return $result; } } - - diff --git a/includes/Block.php b/includes/Block.php index 3688d7cf..b208fa8a 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -1,5 +1,6 @@ mId = 0; @@ -45,6 +46,7 @@ class Block $this->mForUpdate = false; $this->mFromMaster = false; $this->mByName = false; + $this->mAngryAutoblock = false; $this->initialiseRange(); } @@ -59,10 +61,10 @@ class Block } } - static function newFromID( $id ) + static function newFromID( $id ) { $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->resultObject( $dbr->select( 'ipblocks', '*', + $res = $dbr->resultObject( $dbr->select( 'ipblocks', '*', array( 'ipb_id' => $id ), __METHOD__ ) ); $block = new Block; if ( $block->loadFromResult( $res ) ) { @@ -75,8 +77,8 @@ class Block function clear() { $this->mAddress = $this->mReason = $this->mTimestamp = ''; - $this->mId = $this->mAnonOnly = $this->mCreateAccount = - $this->mEnableAutoblock = $this->mAuto = $this->mUser = + $this->mId = $this->mAnonOnly = $this->mCreateAccount = + $this->mEnableAutoblock = $this->mAuto = $this->mUser = $this->mBy = $this->mHideName = $this->mBlockEmail = 0; $this->mByName = false; } @@ -124,7 +126,7 @@ class Block # Try user block if ( $user ) { - $res = $db->resultObject( $db->select( 'ipblocks', '*', array( 'ipb_user' => $user ), + $res = $db->resultObject( $db->select( 'ipblocks', '*', array( 'ipb_user' => $user ), __METHOD__, $options ) ); if ( $this->loadFromResult( $res, $killExpired ) ) { return true; @@ -170,7 +172,7 @@ class Block return true; } } - + # Give up $this->clear(); return false; @@ -179,7 +181,7 @@ class Block /** * Fill in member variables from a result wrapper */ - function loadFromResult( ResultWrapper $res, $killExpired = true ) + function loadFromResult( ResultWrapper $res, $killExpired = true ) { $ret = false; if ( 0 != $res->numRows() ) { @@ -234,7 +236,7 @@ class Block "ipb_range_start <= '$iaddr'", "ipb_range_end >= '$iaddr'" ); - + if ( $user ) { $conds['ipb_anon_only'] = 0; } @@ -270,7 +272,7 @@ class Block if ( isset( $row->user_name ) ) { $this->mByName = $row->user_name; } else { - $this->mByName = false; + $this->mByName = $row->ipb_by_text; } $this->mRangeStart = $row->ipb_range_start; $this->mRangeEnd = $row->ipb_range_end; @@ -358,7 +360,7 @@ class Block /** * Insert a block into the block table. - *@return Whether or not the insertion was successful. + * @return Whether or not the insertion was successful. */ function insert() { @@ -376,6 +378,15 @@ class Block $this->mBlockEmail = 0; //Same goes for email... } + if( !$this->mByName ) { + if( $this->mBy ) { + $this->mByName = User::whoIs( $this->mBy ); + } else { + global $wgUser; + $this->mByName = $wgUser->getName(); + } + } + # Don't collide with expired blocks Block::purgeExpired(); @@ -386,6 +397,7 @@ class Block 'ipb_address' => $this->mAddress, 'ipb_user' => $this->mUser, 'ipb_by' => $this->mBy, + 'ipb_by_text' => $this->mByName, 'ipb_reason' => $this->mReason, 'ipb_timestamp' => $dbw->timestamp($this->mTimestamp), 'ipb_auto' => $this->mAuto, @@ -400,7 +412,6 @@ class Block ), 'Block::insert', array( 'IGNORE' ) ); $affected = $dbw->affectedRows(); - $dbw->commit(); if ($affected) $this->doRetroactiveAutoblock(); @@ -420,17 +431,30 @@ class Block if ($this->mEnableAutoblock && $this->mUser) { wfDebug("Doing retroactive autoblocks for " . $this->mAddress . "\n"); + + $options = array( 'ORDER BY' => 'rc_timestamp DESC' ); + $conds = array( 'rc_user_text' => $this->mAddress ); + + if ($this->mAngryAutoblock) { + // Block any IP used in the last 7 days. Up to five IPs. + $conds[] = 'rc_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( time() - (7*86400) ) ); + $options['LIMIT'] = 5; + } else { + // Just the last IP used. + $options['LIMIT'] = 1; + } - $row = $dbr->selectRow( 'recentchanges', array( 'rc_ip' ), array( 'rc_user_text' => $this->mAddress ), - __METHOD__ , array( 'ORDER BY' => 'rc_timestamp DESC' ) ); + $res = $dbr->select( 'recentchanges', array( 'rc_ip' ), $conds, + __METHOD__ , $options); - if ( !$row || !$row->rc_ip ) { + if ( !$dbr->numRows( $res ) ) { #No results, don't autoblock anything wfDebug("No IP found to retroactively autoblock\n"); } else { - #Limit is 1, so no loop needed. - $retroblockip = $row->rc_ip; - return $this->doAutoblock( $retroblockip, true ); + while ( $row = $dbr->fetchObject( $res ) ) { + if ( $row->rc_ip ) + $this->doAutoblock( $row->rc_ip ); + } } } } @@ -476,6 +500,12 @@ class Block wfDebug( " No match\n" ); } } + + ## Allow hooks to cancel the autoblock. + if (!wfRunHooks( 'AbortAutoblock', array( $autoblockip, &$this ) )) { + wfDebug( "Autoblock aborted by hook." ); + return false; + } # It's okay to autoblock. Go ahead and create/insert the block. @@ -502,6 +532,7 @@ class Block $ipblock->mAddress = $autoblockip; $ipblock->mUser = 0; $ipblock->mBy = $this->mBy; + $ipblock->mByName = $this->mByName; $ipblock->mReason = wfMsgForContent( 'autoblocker', $this->mAddress, $this->mReason ); $ipblock->mTimestamp = wfTimestampNow(); $ipblock->mAuto = 1; @@ -592,9 +623,6 @@ class Block */ function getByName() { - if ( $this->mByName === false ) { - $this->mByName = User::whoIs( $this->mBy ); - } return $this->mByName; } @@ -613,7 +641,7 @@ class Block return $this->mAddress; } } - + /** * Encode expiry for DB */ @@ -625,7 +653,7 @@ class Block } } - /** + /** * Decode expiry which has come from the DB */ static function decodeExpiry( $expiry, $timestampType = TS_MW ) { @@ -635,14 +663,14 @@ class Block return wfTimestamp( $timestampType, $expiry ); } } - + static function getAutoblockExpiry( $timestamp ) { global $wgAutoblockExpiry; return wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $timestamp ) + $wgAutoblockExpiry ); } - - /** + + /** * Gets rid of uneeded numbers in quad-dotted/octet IP strings * For example, 127.111.113.151/24 -> 127.111.113.0/24 */ @@ -675,7 +703,7 @@ class Block return $range; } - /** + /** * Purge expired blocks from the ipblocks table */ static function purgeExpired() { @@ -684,8 +712,8 @@ class Block } static function infinity() { - # This is a special keyword for timestamps in PostgreSQL, and - # works with CHAR(14) as well because "i" sorts after all numbers. + # This is a special keyword for timestamps in PostgreSQL, and + # works with CHAR(14) as well because "i" sorts after all numbers. return 'infinity'; /* @@ -697,6 +725,48 @@ class Block return $infinity; */ } + + /** + * Convert a DB-encoded expiry into a real string that humans can read. + */ + static function formatExpiry( $encoded_expiry ) { + + static $msg = null; + + if( is_null( $msg ) ) { + $msg = array(); + $keys = array( 'infiniteblock', 'expiringblock' ); + foreach( $keys as $key ) { + $msg[$key] = wfMsgHtml( $key ); + } + } + + $expiry = Block::decodeExpiry( $encoded_expiry ); + if ($expiry == 'infinity') { + $expirystr = $msg['infiniteblock']; + } else { + global $wgLang; + $expiretimestr = $wgLang->timeanddate( $expiry, true ); + $expirystr = wfMsgReplaceArgs( $msg['expiringblock'], array($expiretimestr) ); + } + + return $expirystr; + } + + /** + * Convert a typed-in expiry time into something we can put into the database. + */ + static function parseExpiryInput( $expiry_input ) { + if ( $expiry_input == 'infinite' || $expiry_input == 'indefinite' ) { + $expiry = 'infinity'; + } else { + $expiry = strtotime( $expiry_input ); + if ($expiry < 0 || $expiry === false) { + return false; + } + } + + return $expiry; + } } - diff --git a/includes/CacheDependency.php b/includes/CacheDependency.php index 1d48c383..b050c46d 100644 --- a/includes/CacheDependency.php +++ b/includes/CacheDependency.php @@ -1,10 +1,10 @@ deps = $deps; } - /** + /** * Returns true if any of the dependencies have expired */ function isExpired() { @@ -62,24 +62,24 @@ class DependencyWrapper { } /** - * Attempt to get a value from the cache. If the value is expired or missing, + * Attempt to get a value from the cache. If the value is expired or missing, * it will be generated with the callback function (if present), and the newly - * calculated value will be stored to the cache in a wrapper. + * calculated value will be stored to the cache in a wrapper. * * @param object $cache A cache object such as $wgMemc * @param string $key The cache key * @param integer $expiry The expiry timestamp or interval in seconds * @param mixed $callback The callback for generating the value, or false * @param array $callbackParams The function parameters for the callback - * @param array $deps The dependencies to store on a cache miss. Note: these + * @param array $deps The dependencies to store on a cache miss. Note: these * are not the dependencies used on a cache hit! Cache hits use the stored * dependency array. * * @return mixed The value, or null if it was not present in the cache and no * callback was defined. */ - static function getValueFromCache( $cache, $key, $expiry = 0, $callback = false, - $callbackParams = array(), $deps = array() ) + static function getValueFromCache( $cache, $key, $expiry = 0, $callback = false, + $callbackParams = array(), $deps = array() ) { $obj = $cache->get( $key ); if ( is_object( $obj ) && $obj instanceof DependencyWrapper && !$obj->isExpired() ) { @@ -97,7 +97,7 @@ class DependencyWrapper { } /** - * @addtogroup Cache + * @ingroup Cache */ abstract class CacheDependency { /** @@ -112,7 +112,7 @@ abstract class CacheDependency { } /** - * @addtogroup Cache + * @ingroup Cache */ class FileDependency extends CacheDependency { var $filename, $timestamp; @@ -122,11 +122,11 @@ class FileDependency extends CacheDependency { * * @param string $filename The name of the file, preferably fully qualified * @param mixed $timestamp The unix last modified timestamp, or false if the - * file does not exist. If omitted, the timestamp will be loaded from + * file does not exist. If omitted, the timestamp will be loaded from * the file. * - * A dependency on a nonexistent file will be triggered when the file is - * created. A dependency on an existing file will be triggered when the + * A dependency on a nonexistent file will be triggered when the file is + * created. A dependency on an existing file will be triggered when the * file is changed. */ function __construct( $filename, $timestamp = null ) { @@ -171,7 +171,7 @@ class FileDependency extends CacheDependency { } /** - * @addtogroup Cache + * @ingroup Cache */ class TitleDependency extends CacheDependency { var $titleObj; @@ -191,7 +191,7 @@ class TitleDependency extends CacheDependency { function loadDependencyValues() { $this->touched = $this->getTitle()->getTouched(); } - + /** * Get rid of bulky Title object for sleep */ @@ -202,7 +202,7 @@ class TitleDependency extends CacheDependency { function getTitle() { if ( !isset( $this->titleObj ) ) { $this->titleObj = Title::makeTitle( $this->ns, $this->dbk ); - } + } return $this->titleObj; } @@ -230,12 +230,12 @@ class TitleDependency extends CacheDependency { } /** - * @addtogroup Cache + * @ingroup Cache */ class TitleListDependency extends CacheDependency { var $linkBatch; var $timestamps; - + /** * Construct a dependency on a list of titles */ @@ -259,7 +259,7 @@ class TitleListDependency extends CacheDependency { if ( count( $timestamps ) ) { $dbr = wfGetDB( DB_SLAVE ); $where = $this->getLinkBatch()->constructSet( 'page', $dbr ); - $res = $dbr->select( 'page', + $res = $dbr->select( 'page', array( 'page_namespace', 'page_title', 'page_touched' ), $where, __METHOD__ ); while ( $row = $dbr->fetchObject( $res ) ) { @@ -313,11 +313,11 @@ class TitleListDependency extends CacheDependency { } /** - * @addtogroup Cache + * @ingroup Cache */ class GlobalDependency extends CacheDependency { var $name, $value; - + function __construct( $name ) { $this->name = $name; $this->value = $GLOBALS[$name]; @@ -329,7 +329,7 @@ class GlobalDependency extends CacheDependency { } /** - * @addtogroup Cache + * @ingroup Cache */ class ConstantDependency extends CacheDependency { var $name, $value; @@ -343,5 +343,3 @@ class ConstantDependency extends CacheDependency { return constant( $this->name ) != $this->value; } } - - diff --git a/includes/Category.php b/includes/Category.php new file mode 100644 index 00000000..acafc47a --- /dev/null +++ b/includes/Category.php @@ -0,0 +1,261 @@ +mName === null && $this->mTitle ) + $this->mName = $title->getDBKey(); + + if( $this->mName === null && $this->mID === null ) { + throw new MWException( __METHOD__.' has both names and IDs null' ); + } elseif( $this->mID === null ) { + $where = array( 'cat_title' => $this->mName ); + } elseif( $this->mName === null ) { + $where = array( 'cat_id' => $this->mID ); + } else { + # Already initialized + return true; + } + $dbr = wfGetDB( DB_SLAVE ); + $row = $dbr->selectRow( + 'category', + array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', + 'cat_files' ), + $where, + __METHOD__ + ); + if( !$row ) { + # Okay, there were no contents. Nothing to initialize. + if ( $this->mTitle ) { + # If there is a title object but no record in the category table, treat this as an empty category + $this->mID = false; + $this->mName = $this->mTitle->getDBKey(); + $this->mPages = 0; + $this->mSubcats = 0; + $this->mFiles = 0; + + return true; + } else { + return false; # Fail + } + } + $this->mID = $row->cat_id; + $this->mName = $row->cat_title; + $this->mPages = $row->cat_pages; + $this->mSubcats = $row->cat_subcats; + $this->mFiles = $row->cat_files; + + # (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 ) { + $this->refreshCounts(); + } + + return true; + } + + /** + * Factory function. + * + * @param array $name A category name (no "Category:" prefix). It need + * not be normalized, with spaces replaced by underscores. + * @return mixed Category, or false on a totally invalid name + */ + public static function newFromName( $name ) { + $cat = new self(); + $title = Title::makeTitleSafe( NS_CATEGORY, $name ); + if( !is_object( $title ) ) { + return false; + } + + $cat->mTitle = $title; + $cat->mName = $title->getDBKey(); + + return $cat; + } + + /** + * Factory function. + * + * @param array $title Title for the category page + * @return mixed Category, or false on a totally invalid name + */ + public static function newFromTitle( $title ) { + $cat = new self(); + + $cat->mTitle = $title; + $cat->mName = $title->getDBKey(); + + return $cat; + } + + /** + * Factory function. + * + * @param array $id A category id + * @return Category + */ + public static function newFromID( $id ) { + $cat = new self(); + $cat->mID = intval( $id ); + return $cat; + } + + /** + * Factory function, for constructing a Category object from a result set + * + * @param $row result set row, must contain the cat_xxx fields. If the fields are null, + * the resulting Category object will represent an empty category if a title object + * was given. If the fields are null and no title was given, this method fails and returns false. + * @param $title optional title object for the category represented by the given row. + * May be provided if it is already known, to avoid having to re-create a title object later. + * @return Category + */ + public static function newFromRow( $row, $title = null ) { + $cat = new self(); + $cat->mTitle = $title; + + + # NOTE: the row often results from a LEFT JOIN on categorylinks. This may result in + # all the cat_xxx fields being null, if the category page exists, but nothing + # was ever added to the category. This case should be treated linke an empty + # category, if possible. + + if ( $row->cat_title === null ) { + if ( $title === null ) { + # the name is probably somewhere in the row, for example as page_title, + # but we can't know that here... + return false; + } else { + $cat->mName = $title->getDBKey(); # if we have a title object, fetch the category name from there + } + + $cat->mID = false; + $cat->mSubcats = 0; + $cat->mPages = 0; + $cat->mFiles = 0; + } else { + $cat->mName = $row->cat_title; + $cat->mID = $row->cat_id; + $cat->mSubcats = $row->cat_subcats; + $cat->mPages = $row->cat_pages; + $cat->mFiles = $row->cat_files; + } + + return $cat; + } + + /** @return mixed DB key name, or false on failure */ + public function getName() { return $this->getX( 'mName' ); } + /** @return mixed Category ID, or false on failure */ + public function getID() { return $this->getX( 'mID' ); } + /** @return mixed Total number of member pages, or false on failure */ + public function getPageCount() { return $this->getX( 'mPages' ); } + /** @return mixed Number of subcategories, or false on failure */ + public function getSubcatCount() { return $this->getX( 'mSubcats' ); } + /** @return mixed Number of member files, or false on failure */ + public function getFileCount() { return $this->getX( 'mFiles' ); } + + /** + * @return mixed The Title for this category, or false on failure. + */ + public function getTitle() { + if( $this->mTitle ) return $this->mTitle; + + if( !$this->initialize() ) { + return false; + } + + $this->mTitle = Title::makeTitleSafe( NS_CATEGORY, $this->mName ); + return $this->mTitle; + } + + /** Generic accessor */ + private function getX( $key ) { + if( !$this->initialize() ) { + return false; + } + return $this->{$key}; + } + + /** + * Refresh the counts for this category. + * + * @return bool True on success, false on failure + */ + public function refreshCounts() { + if( wfReadOnly() ) { + return false; + } + $dbw = wfGetDB( DB_MASTER ); + $dbw->begin(); + # Note, we must use names for this, since categorylinks does. + if( $this->mName === null ) { + if( !$this->initialize() ) { + return false; + } + } else { + # Let's be sure that the row exists in the table. We don't need to + # do this if we got the row from the table in initialization! + $dbw->insert( + 'category', + array( 'cat_title' => $this->mName ), + __METHOD__, + 'IGNORE' + ); + } + + $cond1 = $dbw->conditional( 'page_namespace='.NS_CATEGORY, 1, 'NULL' ); + $cond2 = $dbw->conditional( 'page_namespace='.NS_IMAGE, 1, 'NULL' ); + $result = $dbw->selectRow( + array( 'categorylinks', 'page' ), + array( 'COUNT(*) AS pages', + "COUNT($cond1) AS subcats", + "COUNT($cond2) AS files" + ), + array( 'cl_to' => $this->mName, 'page_id = cl_from' ), + __METHOD__, + 'LOCK IN SHARE MODE' + ); + $ret = $dbw->update( + 'category', + array( + 'cat_pages' => $result->pages, + 'cat_subcats' => $result->subcats, + 'cat_files' => $result->files + ), + array( 'cat_title' => $this->mName ), + __METHOD__ + ); + $dbw->commit(); + + # Now we should update our local counts. + $this->mPages = $result->pages; + $this->mSubcats = $result->subcats; + $this->mFiles = $result->files; + + return $ret; + } +} diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php index 6fbcd3c1..92e4e279 100644 --- a/includes/CategoryPage.php +++ b/includes/CategoryPage.php @@ -66,10 +66,12 @@ class CategoryPage extends Article { class CategoryViewer { var $title, $limit, $from, $until, - $articles, $articles_start_char, + $articles, $articles_start_char, $children, $children_start_char, $showGallery, $gallery, $skin; + /** Category object for this page */ + private $cat; function __construct( $title, $from = '', $until = '' ) { global $wgCategoryPagingLimit; @@ -77,8 +79,9 @@ class CategoryViewer { $this->from = $from; $this->until = $until; $this->limit = $wgCategoryPagingLimit; + $this->cat = Category::newFromName( $title->getDBKey() ); } - + /** * Format the category data list. * @@ -132,12 +135,21 @@ class CategoryViewer { } /** - * Add a subcategory to the internal lists + * Add a subcategory to the internal lists, using a Category object + */ + function addSubcategoryObject( $cat, $sortkey, $pageLength ) { + $title = $cat->getTitle(); + $this->addSubcategory( $title, $sortkey, $pageLength ); + } + + /** + * Add a subcategory to the internal lists, using a title object + * @deprectated kept for compatibility, please use addSubcategoryObject instead */ function addSubcategory( $title, $sortkey, $pageLength ) { global $wgContLang; // Subcategory; strip the 'Category' namespace from the link text. - $this->children[] = $this->getSkin()->makeKnownLinkObj( + $this->children[] = $this->getSkin()->makeKnownLinkObj( $title, $wgContLang->convertHtml( $title->getText() ) ); $this->children_start_char[] = $this->getSubcategorySortChar( $title, $sortkey ); @@ -152,13 +164,13 @@ class CategoryViewer { */ function getSubcategorySortChar( $title, $sortkey ) { global $wgContLang; - + if( $title->getPrefixedText() == $sortkey ) { $firstChar = $wgContLang->firstChar( $title->getDBkey() ); } else { $firstChar = $wgContLang->firstChar( $sortkey ); } - + return $wgContLang->convert( $firstChar ); } @@ -167,11 +179,10 @@ class CategoryViewer { */ function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) { if ( $this->showGallery ) { - $image = new Image( $title ); if( $this->flip ) { - $this->gallery->insert( $image ); + $this->gallery->insert( $title ); } else { - $this->gallery->add( $image ); + $this->gallery->add( $title ); } } else { $this->addPage( $title, $sortkey, $pageLength, $isRedirect ); @@ -211,17 +222,17 @@ class CategoryViewer { $this->flip = false; } $res = $dbr->select( - array( 'page', 'categorylinks' ), - array( 'page_title', 'page_namespace', 'page_len', 'page_is_redirect', 'cl_sortkey' ), + array( 'page', 'categorylinks', 'category' ), + array( 'page_title', 'page_namespace', 'page_len', 'page_is_redirect', 'cl_sortkey', + 'cat_id', 'cat_title', 'cat_subcats', 'cat_pages', 'cat_files' ), array( $pageCondition, - 'cl_from = page_id', - 'cl_to' => $this->title->getDBkey()), - #'page_is_redirect' => 0), - #+ $pageCondition, + 'cl_to' => $this->title->getDBkey() ), __METHOD__, array( 'ORDER BY' => $this->flip ? 'cl_sortkey DESC' : 'cl_sortkey', - 'USE INDEX' => 'cl_sortkey', - 'LIMIT' => $this->limit + 1 ) ); + 'USE INDEX' => array( 'categorylinks' => 'cl_sortkey' ), + 'LIMIT' => $this->limit + 1 ), + array( 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ), + 'category' => array( 'LEFT JOIN', 'cat_title = page_title AND page_namespace = ' . NS_CATEGORY ) ) ); $count = 0; $this->nextPage = null; @@ -236,7 +247,8 @@ class CategoryViewer { $title = Title::makeTitle( $x->page_namespace, $x->page_title ); if( $title->getNamespace() == NS_CATEGORY ) { - $this->addSubcategory( $title, $x->cl_sortkey, $x->page_len ); + $cat = Category::newFromRow( $x, $title ); + $this->addSubcategoryObject( $cat, $x->cl_sortkey, $x->page_len ); } elseif( $this->showGallery && $title->getNamespace() == NS_IMAGE ) { $this->addImage( $title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect ); } else { @@ -261,12 +273,14 @@ class CategoryViewer { function getSubcategorySection() { # Don't show subcategories section if there are none. $r = ''; - $c = count( $this->children ); - if( $c > 0 ) { + $rescnt = count( $this->children ); + $dbcnt = $this->cat->getSubcatCount(); + $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'subcat' ); + if( $rescnt > 0 ) { # Showing subcategories $r .= "
    \n"; $r .= '

    ' . wfMsg( 'subcategories' ) . "

    \n"; - $r .= wfMsgExt( 'subcategorycount', array( 'parse' ), $c ); + $r .= $countmsg; $r .= $this->formatList( $this->children, $this->children_start_char ); $r .= "\n
    "; } @@ -277,11 +291,20 @@ class CategoryViewer { $ti = htmlspecialchars( $this->title->getText() ); # Don't show articles section if there are none. $r = ''; - $c = count( $this->articles ); - if( $c > 0 ) { + + # FIXME, here and in the other two sections: we don't need to bother + # with this rigamarole if the entire category contents fit on one page + # and have already been retrieved. We can just use $rescnt in that + # case and save a query and some logic. + $dbcnt = $this->cat->getPageCount() - $this->cat->getSubcatCount() + - $this->cat->getFileCount(); + $rescnt = count( $this->articles ); + $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'article' ); + + if( $rescnt > 0 ) { $r = "
    \n"; $r .= '

    ' . wfMsg( 'category_header', $ti ) . "

    \n"; - $r .= wfMsgExt( 'categoryarticlecount', array( 'parse' ), $c ); + $r .= $countmsg; $r .= $this->formatList( $this->articles, $this->articles_start_char ); $r .= "\n
    "; } @@ -290,10 +313,13 @@ class CategoryViewer { function getImageSection() { if( $this->showGallery && ! $this->gallery->isEmpty() ) { + $dbcnt = $this->cat->getFileCount(); + $rescnt = $this->gallery->count(); + $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' ); + return "
    \n" . '

    ' . wfMsg( 'category-media-header', htmlspecialchars($this->title->getText()) ) . "

    \n" . - wfMsgExt( 'category-media-count', array( 'parse' ), $this->gallery->count() ) . - $this->gallery->toHTML() . "\n
    "; + $countmsg . $this->gallery->toHTML() . "\n"; } else { return ''; } @@ -440,7 +466,48 @@ class CategoryViewer { return "($prevLink) ($nextLink)"; } -} - - + /** + * What to do if the category table conflicts with the number of results + * returned? This function says what. It works the same whether the + * things being counted are articles, subcategories, or files. + * + * Note for grepping: uses the messages category-article-count, + * category-article-count-limited, category-subcat-count, + * category-subcat-count-limited, category-file-count, + * category-file-count-limited. + * + * @param int $rescnt The number of items returned by our database query. + * @param int $dbcnt The number of items according to the category table. + * @param string $type 'subcat', 'article', or 'file' + * @return string A message giving the number of items, to output to HTML. + */ + private function getCountMessage( $rescnt, $dbcnt, $type ) { + global $wgLang; + # There are three cases: + # 1) The category table figure seems sane. It might be wrong, but + # we can't do anything about it if we don't recalculate it on ev- + # ery category view. + # 2) The category table figure isn't sane, like it's smaller than the + # number of actual results, *but* the number of results is less + # than $this->limit and there's no offset. In this case we still + # know the right figure. + # 3) We have no idea. + $totalrescnt = count( $this->articles ) + count( $this->children ) + + ($this->showGallery ? $this->gallery->count() : 0); + if($dbcnt == $rescnt || (($totalrescnt == $this->limit || $this->from + || $this->until) && $dbcnt > $rescnt)){ + # Case 1: seems sane. + $totalcnt = $dbcnt; + } elseif($totalrescnt < $this->limit && !$this->from && !$this->until){ + # Case 2: not sane, but salvageable. + $totalcnt = $rescnt; + } else { + # Case 3: hopeless. Don't give a total count at all. + return wfMsgExt("category-$type-count-limited", 'parse', + $wgLang->formatNum( $rescnt ) ); + } + return wfMsgExt( "category-$type-count", 'parse', $wgLang->formatNum( $rescnt ), + $wgLang->formatNum( $totalcnt ) ); + } +} diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php index b94dcf5e..d28f2eeb 100644 --- a/includes/Categoryfinder.php +++ b/includes/Categoryfinder.php @@ -188,5 +188,3 @@ class Categoryfinder { } } # END OF CLASS "Categoryfinder" - - diff --git a/includes/ChangesFeed.php b/includes/ChangesFeed.php new file mode 100644 index 00000000..9bee1790 --- /dev/null +++ b/includes/ChangesFeed.php @@ -0,0 +1,129 @@ +format = $format; + $this->type = $type; + } + + public function getFeedObject( $title, $description ) { + global $wgSitename, $wgContLanguageCode, $wgFeedClasses, $wgTitle; + $feedTitle = "$wgSitename - {$title} [$wgContLanguageCode]"; + + return new $wgFeedClasses[$this->format]( + $feedTitle, htmlspecialchars( $description ), $wgTitle->getFullUrl() ); + } + + public function execute( $feed, $rows, $limit = 0 , $hideminor = false, $lastmod = false ) { + global $messageMemc, $wgFeedCacheTimeout; + global $wgFeedClasses, $wgTitle, $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 ); + + FeedUtils::checkPurge($timekey, $key); + + /* + * Bumping around loading up diffs can be pretty slow, so where + * possible we want to cache the feed output so the next visitor + * gets it quick too. + */ + $cachedFeed = $this->loadFromCache( $lastmod, $timekey, $key ); + if( is_string( $cachedFeed ) ) { + wfDebug( "RC: Outputting cached feed\n" ); + $feed->httpHeaders(); + echo $cachedFeed; + } else { + wfDebug( "RC: rendering new feed and caching it\n" ); + ob_start(); + self::generateFeed( $rows, $feed ); + $cachedFeed = ob_get_contents(); + ob_end_flush(); + $this->saveToCache( $cachedFeed, $timekey, $key ); + } + return true; + } + + public function saveToCache( $feed, $timekey, $key ) { + global $messageMemc; + $expire = 3600 * 24; # One day + $messageMemc->set( $key, $feed ); + $messageMemc->set( $timekey, wfTimestamp( TS_MW ), $expire ); + } + + public function loadFromCache( $lastmod, $timekey, $key ) { + global $wgFeedCacheTimeout, $messageMemc; + $feedLastmod = $messageMemc->get( $timekey ); + + if( ( $wgFeedCacheTimeout > 0 ) && $feedLastmod ) { + /* + * If the cached feed was rendered very recently, we may + * go ahead and use it even if there have been edits made + * since it was rendered. This keeps a swarm of requests + * from being too bad on a super-frequently edited wiki. + */ + + $feedAge = time() - wfTimestamp( TS_UNIX, $feedLastmod ); + $feedLastmodUnix = wfTimestamp( TS_UNIX, $feedLastmod ); + $lastmodUnix = wfTimestamp( TS_UNIX, $lastmod ); + + if( $feedAge < $wgFeedCacheTimeout || $feedLastmodUnix > $lastmodUnix) { + wfDebug( "RC: loading feed from cache ($key; $feedLastmod; $lastmod)...\n" ); + return $messageMemc->get( $key ); + } else { + wfDebug( "RC: cached feed timestamp check failed ($feedLastmod; $lastmod)\n" ); + } + } + return false; + } + + /** + * @todo document + * @param $rows Database resource with recentchanges rows + * @param $feed Feed object + */ + public static function generateFeed( $rows, &$feed ) { + wfProfileIn( __METHOD__ ); + + $feed->outHeader(); + + # Merge adjacent edits by one user + $sorted = array(); + $n = 0; + foreach( $rows as $obj ) { + if( $n > 0 && + $obj->rc_namespace >= 0 && + $obj->rc_cur_id == $sorted[$n-1]->rc_cur_id && + $obj->rc_user_text == $sorted[$n-1]->rc_user_text ) { + $sorted[$n-1]->rc_last_oldid = $obj->rc_last_oldid; + } else { + $sorted[$n] = $obj; + $n++; + } + } + + foreach( $sorted as $obj ) { + $title = Title::makeTitle( $obj->rc_namespace, $obj->rc_title ); + $talkpage = $title->getTalkPage(); + $item = new FeedItem( + $title->getPrefixedText(), + FeedUtils::formatDiff( $obj ), + $title->getFullURL( 'diff=' . $obj->rc_this_oldid . '&oldid=prev' ), + $obj->rc_timestamp, + ($obj->rc_deleted & Revision::DELETED_USER) ? wfMsgHtml('rev-deleted-user') : $obj->rc_user_text, + $talkpage->getFullURL() + ); + $feed->outItem( $item ); + } + $feed->outFooter(); + wfProfileOut( __METHOD__ ); + } + +} \ No newline at end of file diff --git a/includes/ChangesList.php b/includes/ChangesList.php index 507e88fa..436f006e 100644 --- a/includes/ChangesList.php +++ b/includes/ChangesList.php @@ -27,7 +27,10 @@ class ChangesList { # Called by history lists and recent changes # - /** @todo document */ + /** + * Changeslist contructor + * @param Skin $skin + */ function __construct( &$skin ) { $this->skin =& $skin; $this->preCacheMessages(); @@ -54,12 +57,12 @@ class ChangesList { * As we use the same small set of messages in various methods and that * they are called often, we call them once and save them in $this->message */ - function preCacheMessages() { + private function preCacheMessages() { // Precache various messages if( !isset( $this->message ) ) { foreach( explode(' ', 'cur diff hist minoreditletter newpageletter last '. 'blocklink history boteditletter semicolon-separator' ) as $msg ) { - $this->message[$msg] = wfMsgExt( $msg, array( 'escape') ); + $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) ); } } } @@ -67,8 +70,14 @@ class ChangesList { /** * Returns the appropriate flags for new page, minor change and patrolling + * @param bool $new + * @param bool $minor + * @param bool $patrolled + * @param string $nothing, string to use for empty space + * @param bool $bot + * @return string */ - function recentChangesFlags( $new, $minor, $patrolled, $nothing = ' ', $bot = false ) { + protected function recentChangesFlags( $new, $minor, $patrolled, $nothing = ' ', $bot = false ) { $f = $new ? '' . $this->message['newpageletter'] . '' : $nothing; $f .= $minor ? '' . $this->message['minoreditletter'] . '' @@ -80,8 +89,9 @@ class ChangesList { /** * Returns text for the start of the tabular part of RC + * @return string */ - function beginRecentChangesList() { + public function beginRecentChangesList() { $this->rc_cache = array(); $this->rcMoveIndex = 0; $this->rcCacheIndex = 0; @@ -92,8 +102,9 @@ class ChangesList { /** * Returns text for the end of RC + * @return string */ - function endRecentChangesList() { + public function endRecentChangesList() { if( $this->rclistOpen ) { return "\n"; } else { @@ -101,8 +112,7 @@ class ChangesList { } } - - function insertMove( &$s, $rc ) { + protected function insertMove( &$s, $rc ) { # Diff $s .= '(' . $this->message['diff'] . ') ('; # Hist @@ -115,7 +125,7 @@ class ChangesList { $this->skin->makeKnownLinkObj( $rc->getMovedToTitle(), '' ) ); } - function insertDateHeader(&$s, $rc_timestamp) { + protected function insertDateHeader(&$s, $rc_timestamp) { global $wgLang; # Make date header if necessary @@ -131,15 +141,16 @@ class ChangesList { } } - function insertLog(&$s, $title, $logtype) { + protected function insertLog(&$s, $title, $logtype) { $logname = LogPage::logName( $logtype ); $s .= '(' . $this->skin->makeKnownLinkObj($title, $logname ) . ')'; } - - function insertDiffHist(&$s, &$rc, $unpatrolled) { + protected function insertDiffHist(&$s, &$rc, $unpatrolled) { # Diff link - if( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) { + if( !$this->userCan($rc,Revision::DELETED_TEXT) ) { + $diffLink = $this->message['diff']; + } else if( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) { $diffLink = $this->message['diff']; } else { $rcidparam = $unpatrolled @@ -163,14 +174,19 @@ class ChangesList { $s .= ') . . '; } - function insertArticleLink(&$s, &$rc, $unpatrolled, $watched) { + protected function insertArticleLink(&$s, &$rc, $unpatrolled, $watched) { # Article link # If it's a new article, there is no diff link, but if it hasn't been # patrolled yet, we need to give users a way to do so $params = ( $unpatrolled && $rc->mAttribs['rc_type'] == RC_NEW ) ? 'rcid='.$rc->mAttribs['rc_id'] : ''; - $articlelink = ' '. $this->skin->makeKnownLinkObj( $rc->getTitle(), '', $params ); + if( $this->isDeleted($rc,Revision::DELETED_TEXT) ) { + $articlelink = $this->skin->makeKnownLinkObj( $rc->getTitle(), '', $params ); + $articlelink = ''.$articlelink.''; + } else { + $articlelink = ' '. $this->skin->makeKnownLinkObj( $rc->getTitle(), '', $params ); + } if( $watched ) $articlelink = "{$articlelink}"; global $wgContLang; @@ -178,27 +194,50 @@ class ChangesList { wfRunHooks('ChangesListInsertArticleLink', array(&$this, &$articlelink, &$s, &$rc, $unpatrolled, $watched)); - + $s .= ' '.$articlelink; } - function insertTimestamp(&$s, $rc) { + protected function insertTimestamp(&$s, $rc) { global $wgLang; # Timestamp $s .= $this->message['semicolon-separator'] . ' ' . $wgLang->time( $rc->mAttribs['rc_timestamp'], true, true ) . ' . . '; } /** Insert links to user page, user talk page and eventually a blocking link */ - function insertUserRelatedLinks(&$s, &$rc) { - $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'] ); + protected function insertUserRelatedLinks(&$s, &$rc) { + if ( $this->isDeleted($rc,Revision::DELETED_USER) ) { + $s .= ' ' . wfMsgHtml('rev-deleted-user') . ''; + } 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'] ); + } + } + + /** insert a formatted action */ + protected function insertAction(&$s, &$rc) { + # Add action + if( $rc->mAttribs['rc_type'] == RC_LOG ) { + // log action + if ( $this->isDeleted($rc,LogPage::DELETED_ACTION) ) { + $s .= ' ' . wfMsgHtml('rev-deleted-event') . ''; + } 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 ); + } + } } /** insert a formatted comment */ - function insertComment(&$s, &$rc) { + protected function insertComment(&$s, &$rc) { # Add comment if( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) { - $s .= $this->skin->commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() ); + // log comment + if ( $this->isDeleted($rc,Revision::DELETED_COMMENT) ) { + $s .= ' ' . wfMsgHtml('rev-deleted-comment') . ''; + } else { + $s .= $this->skin->commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() ); + } } } @@ -206,15 +245,15 @@ class ChangesList { * Check whether to enable recent changes patrol features * @return bool */ - function usePatrol() { - global $wgUseRCPatrol, $wgUser; - return( $wgUseRCPatrol && ($wgUser->isAllowed('patrol') || $wgUser->isAllowed('patrolmarks')) ); + public static function usePatrol() { + global $wgUser; + return $wgUser->useRCPatrol(); } /** * Returns the string which indicates the number of watching users */ - function numberofWatchingusers( $count ) { + protected function numberofWatchingusers( $count ) { global $wgLang; static $cache = array(); if ( $count > 0 ) { @@ -227,6 +266,36 @@ class ChangesList { return ''; } } + + /** + * Determine if said field of a revision is hidden + * @param RCCacheEntry $rc + * @param int $field one of DELETED_* bitfield constants + * @return bool + */ + public static function isDeleted( $rc, $field ) { + return ($rc->mAttribs['rc_deleted'] & $field) == $field; + } + + /** + * Determine if the current user is allowed to view a particular + * field of this revision, if it's marked as deleted. + * @param RCCacheEntry $rc + * @param int $field + * @return bool + */ + public static function userCan( $rc, $field ) { + if( ( $rc->mAttribs['rc_deleted'] & $field ) == $field ) { + global $wgUser; + $permission = ( $rc->mAttribs['rc_deleted'] & Revision::DELETED_RESTRICTED ) == Revision::DELETED_RESTRICTED + ? 'suppressrevision' + : 'deleterevision'; + wfDebug( "Checking for $permission due to $field match on $rc->mAttribs['rc_deleted']\n" ); + return $wgUser->isAllowed( $permission ); + } else { + return true; + } + } } @@ -237,8 +306,8 @@ class OldChangesList extends ChangesList { /** * Format a line using the old system (aka without any javascript). */ - function recentChangesLine( &$rc, $watched = false ) { - global $wgContLang, $wgRCShowChangedSize; + public function recentChangesLine( &$rc, $watched = false ) { + global $wgContLang, $wgRCShowChangedSize, $wgUser; $fname = 'ChangesList::recentChangesLineOld'; wfProfileIn( $fname ); @@ -248,31 +317,35 @@ class OldChangesList extends ChangesList { extract( $rc->mAttribs ); # Should patrol-related stuff be shown? - $unpatrolled = $this->usePatrol() && $rc_patrolled == 0; + $unpatrolled = $wgUser->useRCPatrol() && $rc_patrolled == 0; $this->insertDateHeader($s,$rc_timestamp); $s .= '
  • '; - // moved pages + // Moved pages if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) { $this->insertMove( $s, $rc ); - // log entries - } elseif ( $rc_namespace == NS_SPECIAL ) { + // Log entries + } elseif( $rc_log_type ) { + $logtitle = Title::newFromText( "Log/$rc_log_type", NS_SPECIAL ); + $this->insertLog( $s, $logtitle, $rc_log_type ); + // Log entries (old format) or log targets, and special pages + } elseif( $rc_namespace == NS_SPECIAL ) { list( $specialName, $specialSubpage ) = SpecialPage::resolveAliasWithSubpage( $rc_title ); if ( $specialName == 'Log' ) { $this->insertLog( $s, $rc->getTitle(), $specialSubpage ); } else { wfDebug( "Unexpected special page in recentchanges\n" ); } - // all other stuff + // Regular entries } else { wfProfileIn($fname.'-page'); $this->insertDiffHist($s, $rc, $unpatrolled); # M, N, b and ! (minor, new, bot and unpatrolled) - $s .= ' ' . $this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $unpatrolled, '', $rc_bot ); + $s .= $this->recentChangesFlags( $rc_type == RC_NEW, $rc_minor, $unpatrolled, '', $rc_bot ); $this->insertArticleLink($s, $rc, $unpatrolled, $watched); wfProfileOut($fname.'-page'); @@ -285,11 +358,19 @@ class OldChangesList extends ChangesList { if( $wgRCShowChangedSize ) { $s .= ( $rc->getCharacterDifference() == '' ? '' : $rc->getCharacterDifference() . ' . . ' ); } - + # User tool links $this->insertUserRelatedLinks($s,$rc); + # Log action text (if any) + $this->insertAction($s, $rc); + # Edit or log comment $this->insertComment($s, $rc); - $s .= rtrim(' ' . $this->numberofWatchingusers($rc->numberofWatchingusers)); + # Mark revision as deleted if so + if ( !$rc_log_type && $this->isDeleted($rc,Revision::DELETED_TEXT) ) + $s .= ' ' . wfMsgHtml( 'deletedrev' ) . ''; + if($rc->numberofWatchingusers > 0) { + $s .= ' ' . wfMsg('number_of_watching_users_RCview', $wgContLang->formatNum($rc->numberofWatchingusers)); + } $s .= "
  • \n"; @@ -308,8 +389,8 @@ class EnhancedChangesList extends ChangesList { /** * Format a line for enhanced recentchange (aka with javascript and block of lines). */ - function recentChangesLine( &$baseRC, $watched = false ) { - global $wgLang, $wgContLang; + public function recentChangesLine( &$baseRC, $watched = false ) { + global $wgLang, $wgContLang, $wgUser; # Create a specialised object $rc = RCCacheEntry::newFromParent( $baseRC ); @@ -331,17 +412,20 @@ class EnhancedChangesList extends ChangesList { } # Should patrol-related stuff be shown? - if( $this->usePatrol() ) { + if( $wgUser->useRCPatrol() ) { $rc->unpatrolled = !$rc_patrolled; } else { $rc->unpatrolled = false; } + $showdifflinks = true; # Make article link + // Page moves if( $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) { $msg = ( $rc_type == RC_MOVE ) ? "1movedto2" : "1movedto2_redir"; $clink = wfMsg( $msg, $this->skin->makeKnownLinkObj( $rc->getTitle(), '', 'redirect=no' ), $this->skin->makeKnownLinkObj( $rc->getMovedToTitle(), '' ) ); + // Log entries (old format) and special pages } elseif( $rc_namespace == NS_SPECIAL ) { list( $specialName, $logtype ) = SpecialPage::resolveAliasWithSubpage( $rc_title ); if ( $specialName == 'Log' ) { @@ -352,13 +436,28 @@ class EnhancedChangesList extends ChangesList { wfDebug( "Unexpected special page in recentchanges\n" ); $clink = ''; } - } elseif( $rc->unpatrolled && $rc_type == RC_NEW ) { - # Unpatrolled new page, give rc_id in query + // New unpatrolled pages + } else if( $rc->unpatrolled && $rc_type == RC_NEW ) { $clink = $this->skin->makeKnownLinkObj( $rc->getTitle(), '', "rcid={$rc_id}" ); + // Log entries + } else if( $rc_type == RC_LOG ) { + if( $rc_log_type ) { + $logtitle = SpecialPage::getTitleFor( 'Log', $rc_log_type ); + $clink = '(' . $this->skin->makeKnownLinkObj( $logtitle, LogPage::logName($rc_log_type) ) . ')'; + } else { + $clink = $this->skin->makeLinkObj( $rc->getTitle(), '' ); + } + $watched = false; + // Edits } else { $clink = $this->skin->makeKnownLinkObj( $rc->getTitle(), '' ); } + # Don't show unusable diff links + if ( !ChangesList::userCan($rc,Revision::DELETED_TEXT) ) { + $showdifflinks = false; + } + $time = $wgContLang->time( $rc_timestamp, true, true ); $rc->watched = $watched; $rc->link = $clink; @@ -375,7 +474,12 @@ class EnhancedChangesList extends ChangesList { $querydiff = $curIdEq."&diff=$rc_this_oldid&oldid=$rc_last_oldid$rcIdQuery"; $aprops = ' tabindex="'.$baseRC->counter.'"'; $curLink = $this->skin->makeKnownLinkObj( $rc->getTitle(), $this->message['cur'], $querycur, '' ,'', $aprops ); - if( $rc_type == RC_NEW || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) { + + # Make "diff" an "cur" links + 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']; } @@ -385,21 +489,27 @@ class EnhancedChangesList extends ChangesList { } # Make "last" link - if( $rc_last_oldid == 0 || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) { + if( !$showdifflinks ) { + $lastLink = $this->message['last']; + } else if( $rc_last_oldid == 0 || $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'], - $curIdEq.'&diff='.$rc_this_oldid.'&oldid='.$rc_last_oldid . $rcIdQuery ); + $curIdEq.'&diff='.$rc_this_oldid.'&oldid='.$rc_last_oldid . $rcIdQuery ); } - $rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text ); + # Make user links + if( $this->isDeleted($rc,Revision::DELETED_USER) ) { + $rc->userlink = ' ' . wfMsgHtml('rev-deleted-user') . ''; + } else { + $rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text ); + $rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text ); + } $rc->lastlink = $lastLink; $rc->curlink = $curLink; $rc->difflink = $diffLink; - $rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text ); - # Put accumulated information into the cache, for later display # Page moves go on their own line $title = $rc->getTitle(); @@ -408,7 +518,11 @@ class EnhancedChangesList extends ChangesList { # Use an @ character to prevent collision with page names $this->rc_cache['@@' . ($this->rcMoveIndex++)] = array($rc); } else { - if( !isset ( $this->rc_cache[$secureName] ) ) { + # Logs are grouped by type + if( $rc_type == RC_LOG ){ + $secureName = SpecialPage::getTitleFor( 'Log', $rc_log_type )->getPrefixedDBkey(); + } + if( !isset( $this->rc_cache[$secureName] ) ) { $this->rc_cache[$secureName] = array(); } array_push( $this->rc_cache[$secureName], $rc ); @@ -419,19 +533,29 @@ class EnhancedChangesList extends ChangesList { /** * Enhanced RC group */ - function recentChangesBlockGroup( $block ) { + protected function recentChangesBlockGroup( $block ) { global $wgLang, $wgContLang, $wgRCShowChangedSize; - $r = ''; + $r = ''; # Collate list of users - $isnew = false; - $unpatrolled = false; $userlinks = array(); + # Other properties + $unpatrolled = false; + $isnew = false; + $curId = $currentRevision = 0; + # Some catalyst variables... + $namehidden = true; + $alllogs = true; foreach( $block as $rcObj ) { $oldid = $rcObj->mAttribs['rc_last_oldid']; if( $rcObj->mAttribs['rc_new'] ) { $isnew = true; } + // If all log actions to this page were hidden, then don't + // give the name of the affected page for this block! + if( !$this->isDeleted( $rcObj, LogPage::DELETED_ACTION ) ) { + $namehidden = false; + } $u = $rcObj->userlink; if( !isset( $userlinks[$u] ) ) { $userlinks[$u] = 0; @@ -439,6 +563,18 @@ class EnhancedChangesList extends ChangesList { if( $rcObj->unpatrolled ) { $unpatrolled = true; } + if( $rcObj->mAttribs['rc_type'] != RC_LOG ) { + $alllogs = false; + } + # Get the latest entry with a page_id and oldid + # since logs may not have these. + if( !$curId && $rcObj->mAttribs['rc_cur_id'] ) { + $curId = $rcObj->mAttribs['rc_cur_id']; + } + if( !$currentRevision && $rcObj->mAttribs['rc_this_oldid'] ) { + $currentRevision = $rcObj->mAttribs['rc_this_oldid']; + } + $bot = $rcObj->mAttribs['rc_bot']; $userlinks[$u]++; } @@ -465,111 +601,148 @@ class EnhancedChangesList extends ChangesList { $toggleLink = "javascript:toggleVisibility('$rci','$rcm','$rcl')"; $tl = '' . $this->sideArrow() . ''; $tl .= ''; - $r .= $tl; + $r .= '
    '.$tl.' '; # Main line - $r .= ''; $r .= $this->recentChangesFlags( $isnew, false, $unpatrolled, ' ', $bot ); # Timestamp - $r .= ' '.$block[0]->timestamp.' '; + $r .= ' '.$block[0]->timestamp.' '; # Article link - $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched ); - $r .= $wgContLang->getDirMark(); - - $curIdEq = 'curid=' . $block[0]->mAttribs['rc_cur_id']; - $currentRevision = $block[0]->mAttribs['rc_this_oldid']; - if( $block[0]->mAttribs['rc_type'] != RC_LOG ) { - # Changes - - $n = count($block); - static $nchanges = array(); - if ( !isset( $nchanges[$n] ) ) { - $nchanges[$n] = wfMsgExt( 'nchanges', array( 'parsemag', 'escape'), - $wgLang->formatNum( $n ) ); - } + if( $namehidden ) { + $r .= ' ' . wfMsgHtml('rev-deleted-event') . ''; + } else { + $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched ); + } - $r .= ' ('; + $r .= $wgContLang->getDirMark(); - if( $isnew ) { + $curIdEq = 'curid=' . $curId; + # Changes message + $n = count($block); + static $nchanges = array(); + if ( !isset( $nchanges[$n] ) ) { + $nchanges[$n] = wfMsgExt( 'nchanges', array( 'parsemag', 'escape' ), $wgLang->formatNum( $n ) ); + } + # Total change link + $r .= ' '; + if( !$alllogs ) { + $r .= '('; + if( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) { + $r .= $nchanges[$n]; + } else if( $isnew ) { $r .= $nchanges[$n]; } else { $r .= $this->skin->makeKnownLinkObj( $block[0]->getTitle(), $nchanges[$n], $curIdEq."&diff=$currentRevision&oldid=$oldid" ); } - $r .= ') . . '; + } - if( $wgRCShowChangedSize ) { - # Character difference - $chardiff = $rcObj->getCharacterDifference( $block[ count( $block ) - 1 ]->mAttribs['rc_old_len'], - $block[0]->mAttribs['rc_new_len'] ); - if( $chardiff == '' ) { - $r .= ' ('; - } else { - $r .= ' ' . $chardiff. ' . . '; - } - } - - # History + # Character difference (does not apply if only log items) + if( $wgRCShowChangedSize && !$alllogs ) { + $last = 0; + $first = count($block) - 1; + # Some events (like logs) have an "empty" size, so we need to skip those... + while( $last < $first && $block[$last]->mAttribs['rc_new_len'] === NULL ) { + $last++; + } + while( $first > $last && $block[$first]->mAttribs['rc_old_len'] === NULL ) { + $first--; + } + # Get net change + $chardiff = $rcObj->getCharacterDifference( $block[$first]->mAttribs['rc_old_len'], + $block[$last]->mAttribs['rc_new_len'] ); + + if( $chardiff == '' ) { + $r .= ' '; + } else { + $r .= ' ' . $chardiff. ' . . '; + } + } + + # History + if( $alllogs ) { + // don't show history link for logs + } else if( $namehidden || !$block[0]->getTitle()->exists() ) { + $r .= '(' . $this->message['history'] . ')'; + } else { $r .= '(' . $this->skin->makeKnownLinkObj( $block[0]->getTitle(), - $this->message['history'], $curIdEq.'&action=history' ); - $r .= ')'; + $this->message['history'], $curIdEq.'&action=history' ) . ')'; } $r .= $users; - $r .= $this->numberofWatchingusers($block[0]->numberofWatchingusers); - $r .= "
    \n"; + + $r .= "
    \n"; # Sub-entries - $r .= '