From d82c14fb4fbac288b42ca5918b0a72f33ecb1e69 Mon Sep 17 00:00:00 2001 From: Lee Daniel Crocker Date: Mon, 14 Apr 2003 23:10:40 +0000 Subject: [PATCH] Initial revision --- AdminSettings.sample | 19 + INSTALL | 159 ++ LocalSettings.sample | 53 + README | 45 + docs/deferred.doc | 19 + docs/design.doc | 119 ++ docs/globals.doc | 29 + docs/language.doc | 24 + docs/linkcache.doc | 31 + docs/schema.doc | 224 +++ docs/skin.doc | 8 + docs/title.doc | 72 + docs/user.doc | 77 + images/favicon.ico | Bin 0 -> 318 bytes images/startrek.png | Bin 0 -> 301 bytes images/valid-html401.png | Bin 0 -> 2948 bytes images/wiki.png | Bin 0 -> 4029 bytes includes/Article.php | 1478 +++++++++++++++ includes/DatabaseFunctions.php | 134 ++ includes/DefaultSettings.php | 66 + includes/DifferenceEngine.php | 1138 +++++++++++ includes/FulltextStoplist.php | 592 ++++++ includes/GlobalFunctions.php | 494 +++++ includes/Interwiki.php | 256 +++ includes/LinkCache.php | 109 ++ includes/LinksUpdate.php | 110 ++ includes/LogPage.php | 118 ++ includes/Namespace.php | 49 + includes/OutputPage.php | 1295 +++++++++++++ includes/SearchEngine.php | 383 ++++ includes/SearchUpdate.php | 78 + includes/Setup.php | 37 + includes/SiteStatsUpdate.php | 40 + includes/Skin.php | 1674 +++++++++++++++++ includes/SkinCologneBlue.php | 216 +++ includes/SkinFramed.php | 87 + includes/SkinNostalgia.php | 78 + includes/SkinStandard.php | 60 + includes/SpecialAllpages.php | 116 ++ includes/SpecialAsksql.php | 113 ++ includes/SpecialBlockip.php | 97 + includes/SpecialBooksources.php | 57 + includes/SpecialContributions.php | 151 ++ includes/SpecialDebug.php | 13 + includes/SpecialEmailuser.php | 135 ++ includes/SpecialImagelist.php | 115 ++ includes/SpecialIntl.php | 555 ++++++ includes/SpecialIpblocklist.php | 128 ++ includes/SpecialListusers.php | 42 + includes/SpecialLockdb.php | 96 + includes/SpecialLonelypages.php | 46 + includes/SpecialLongpages.php | 47 + includes/SpecialMaintenance.php | 368 ++++ includes/SpecialMovepage.php | 424 +++++ includes/SpecialNeglectedpages.php | 13 + includes/SpecialNewpages.php | 54 + includes/SpecialPopularpages.php | 47 + includes/SpecialPreferences.php | 260 +++ includes/SpecialRandompage.php | 29 + includes/SpecialRecentchanges.php | 172 ++ includes/SpecialRecentchangeslinked.php | 89 + includes/SpecialShortpages.php | 46 + includes/SpecialSpecialpages.php | 46 + includes/SpecialStatistics.php | 53 + includes/SpecialUndelete.php | 161 ++ includes/SpecialUnlockdb.php | 82 + includes/SpecialUnusedimages.php | 56 + includes/SpecialUpload.php | 245 +++ includes/SpecialUserlogin.php | 248 +++ includes/SpecialUserlogout.php | 13 + includes/SpecialVote.php | 10 + includes/SpecialWantedpages.php | 74 + includes/SpecialWatchlist.php | 90 + includes/SpecialWhatlinkshere.php | 101 + includes/Title.php | 370 ++++ includes/UpdateClasses.php | 11 + includes/User.php | 530 ++++++ includes/UserTalkUpdate.php | 63 + includes/UserUpdate.php | 15 + includes/Utf8Case.php | 1520 +++++++++++++++ includes/ViewCountUpdate.php | 21 + install.php | 259 +++ languages/Language.php | 1308 +++++++++++++ languages/LanguageCs.php | 1002 ++++++++++ languages/LanguageDa.php | 1260 +++++++++++++ languages/LanguageDe.php | 932 +++++++++ languages/LanguageEn.php | 8 + languages/LanguageEo.php | 1261 +++++++++++++ languages/LanguageEs.php | 1137 +++++++++++ languages/LanguageFr.php | 1034 ++++++++++ languages/LanguageIt.php | 891 +++++++++ languages/LanguageJa.php | 1175 ++++++++++++ languages/LanguageKo.php | 1094 +++++++++++ languages/LanguageNl.php | 1008 ++++++++++ languages/LanguagePl.php | 1224 ++++++++++++ languages/LanguageRu.php | 25 + languages/LanguageSv.php | 1110 +++++++++++ languages/LanguageZh.php | 1156 ++++++++++++ maintenance/README | 10 + maintenance/apache-ampersand.diff | 53 + maintenance/archives/convertdb.php | 592 ++++++ maintenance/archives/importTests.php | 261 +++ maintenance/archives/importUseModWiki.php | 468 +++++ maintenance/archives/patch-bot.sql | 11 + maintenance/archives/patch-cache.sql | 41 + maintenance/archives/patch-list.txt | 107 ++ maintenance/archives/patch-math.sql | 16 + .../archives/patch-random-dateindex.sql | 54 + maintenance/archives/patch-searchindex.sql | 33 + maintenance/archives/patch-userindex.sql | 1 + maintenance/archives/patch-usernewtalk.sql | 20 + maintenance/archives/patch-watchlist.sql | 30 + maintenance/archives/rebuildRecentchanges.inc | 116 ++ maintenance/archives/upgradeWatchlist.php | 54 + maintenance/build-intl-wiki.sql | 31 + maintenance/cleandb.php | 24 + maintenance/database.sql | 7 + maintenance/fetchInterwiki.pl | 186 ++ maintenance/indexes.sql | 56 + maintenance/initialdata.sql | 15 + maintenance/rebuildIndex.php | 46 + maintenance/rebuildLinks.inc | 166 ++ maintenance/rebuildLinks.php | 25 + maintenance/tables.sql | 185 ++ maintenance/users.sql | 115 ++ math/Makefile | 64 + math/README | 18 + math/TODO | 3 + math/html.ml | 119 ++ math/html.mli | 5 + math/lexer.mll | 90 + math/mathml.ml | 20 + math/mathml.mli | 1 + math/parser.mly | 103 + math/render.ml | 29 + math/render_info.mli | 20 + math/tex.mli | 19 + math/texutil.ml | 453 +++++ math/texutil.mli | 11 + math/texvc.ml | 34 + math/texvc_cgi.ml | 62 + math/texvc_test.ml | 25 + math/texvc_tex.ml | 3 + math/util.ml | 17 + rdf/recent.phtml | 76 + redirect.phtml | 6 + stylesheets/cologneblue.css | 93 + stylesheets/nostalgia.css | 12 + stylesheets/quickbar.css | 1 + stylesheets/sticky.js | 124 ++ stylesheets/wikibits.js | 32 + stylesheets/wikiprintable.css | 7 + stylesheets/wikistandard.css | 27 + testsuite/README | 14 + testsuite/build.xml | 27 + testsuite/data/Agriculture.txt | 49 + testsuite/data/Anthropology.txt | 36 + testsuite/data/Archaeology.txt | 104 + testsuite/data/Architecture.txt | 21 + testsuite/data/Astronomy_and_astrophysics.txt | 151 ++ testsuite/data/Biology.txt | 42 + testsuite/data/Blocklevels.txt | 107 ++ testsuite/data/Bracketvars.txt | 17 + testsuite/data/Business_and_industry.txt | 56 + testsuite/data/Card_game.txt | 70 + testsuite/data/Chemistry.txt | 28 + testsuite/data/Classics.txt | 20 + testsuite/data/Communication.txt | 37 + testsuite/data/Computer_Science.txt | 63 + testsuite/data/Cooking.txt | 47 + testsuite/data/Critical_theory.txt | 10 + testsuite/data/Dance.txt | 17 + testsuite/data/Earth_science.txt | 50 + testsuite/data/Economics.txt | 59 + testsuite/data/Education.txt | 34 + testsuite/data/Engineering.txt | 60 + testsuite/data/Entertainment.txt | 30 + testsuite/data/Equations.txt | 14 + testsuite/data/ExternalLinks.txt | 32 + .../data/Family_and_consumer_science.txt | 6 + testsuite/data/Film.txt | 40 + testsuite/data/Game.txt | 33 + testsuite/data/Geography.txt | 45 + testsuite/data/Handicraft.txt | 28 + testsuite/data/Headings.txt | 46 + testsuite/data/Health_science.txt | 7 + testsuite/data/History.txt | 87 + .../History_of_science_and_technology.txt | 115 ++ testsuite/data/Hobby.txt | 87 + testsuite/data/InternalLinks.txt | 24 + testsuite/data/Language.txt | 31 + testsuite/data/Law.txt | 177 ++ .../data/Library_and_information_science.txt | 38 + testsuite/data/Linguistics.txt | 52 + testsuite/data/Literature.txt | 68 + testsuite/data/Magics.txt | 6 + testsuite/data/Main_Page.txt | 20 + testsuite/data/Mathematics.txt | 59 + testsuite/data/Music.txt | 19 + testsuite/data/Opera.txt | 44 + testsuite/data/Painting.txt | 60 + testsuite/data/Philosophy.txt | 206 ++ testsuite/data/Physics.txt | 87 + testsuite/data/Poker.txt | 51 + testsuite/data/Political_science.txt | 98 + testsuite/data/Psychology.txt | 143 ++ testsuite/data/Public_affairs.txt | 9 + testsuite/data/Quotes.txt | 40 + testsuite/data/Recreation.txt | 8 + testsuite/data/Religion.txt | 65 + testsuite/data/Sculpture.txt | 18 + testsuite/data/Sociology.txt | 17 + testsuite/data/Sport.txt | 272 +++ testsuite/data/Statistics.txt | 16 + testsuite/data/Technology.txt | 174 ++ testsuite/data/Theater.txt | 61 + testsuite/data/Tourism.txt | 67 + testsuite/data/Transport.txt | 161 ++ testsuite/data/Visual_arts_and_design.txt | 57 + testsuite/data/World_Series_of_Poker.txt | 53 + testsuite/data/startrek.png | Bin 0 -> 301 bytes testsuite/jars/httpunit.jar | Bin 0 -> 288272 bytes testsuite/jars/js.jar | Bin 0 -> 596528 bytes testsuite/jars/nekohtml.jar | Bin 0 -> 51667 bytes testsuite/jars/xercesImpl.jar | Bin 0 -> 831473 bytes testsuite/jars/xml-apis.jar | Bin 0 -> 108484 bytes testsuite/run | 12 + .../src/com/piclab/wikitest/DBLoader.java | 90 + .../src/com/piclab/wikitest/EditTest.java | 219 +++ .../src/com/piclab/wikitest/HTMLTest.java | 202 ++ .../src/com/piclab/wikitest/LinkTest.java | 205 ++ .../src/com/piclab/wikitest/MathTest.java | 128 ++ .../src/com/piclab/wikitest/ParserTest.java | 278 +++ .../src/com/piclab/wikitest/SearchTest.java | 204 ++ .../src/com/piclab/wikitest/SpecialTest.java | 358 ++++ .../src/com/piclab/wikitest/Test.template | 38 + .../src/com/piclab/wikitest/UploadTest.java | 38 + .../com/piclab/wikitest/WikiFetchThread.java | 74 + .../com/piclab/wikitest/WikiLogFormatter.java | 38 + .../com/piclab/wikitest/WikiSearchThread.java | 175 ++ .../src/com/piclab/wikitest/WikiSuite.java | 343 ++++ .../wikitest/WikiSuiteFailureException.java | 28 + .../src/com/piclab/wikitest/WikiTest.java | 461 +++++ testsuite/wikitest.prefs.sample | 31 + texvc.phtml | 155 ++ update.php | 85 + wiki.phtml | 74 + 247 files changed, 43500 insertions(+) create mode 100644 AdminSettings.sample create mode 100644 INSTALL create mode 100644 LocalSettings.sample create mode 100644 README create mode 100644 docs/deferred.doc create mode 100644 docs/design.doc create mode 100644 docs/globals.doc create mode 100644 docs/language.doc create mode 100644 docs/linkcache.doc create mode 100644 docs/schema.doc create mode 100644 docs/skin.doc create mode 100644 docs/title.doc create mode 100644 docs/user.doc create mode 100644 images/favicon.ico create mode 100644 images/startrek.png create mode 100644 images/valid-html401.png create mode 100644 images/wiki.png create mode 100644 includes/Article.php create mode 100644 includes/DatabaseFunctions.php create mode 100644 includes/DefaultSettings.php create mode 100644 includes/DifferenceEngine.php create mode 100644 includes/FulltextStoplist.php create mode 100644 includes/GlobalFunctions.php create mode 100644 includes/Interwiki.php create mode 100644 includes/LinkCache.php create mode 100644 includes/LinksUpdate.php create mode 100644 includes/LogPage.php create mode 100644 includes/Namespace.php create mode 100644 includes/OutputPage.php create mode 100644 includes/SearchEngine.php create mode 100644 includes/SearchUpdate.php create mode 100644 includes/Setup.php create mode 100644 includes/SiteStatsUpdate.php create mode 100644 includes/Skin.php create mode 100644 includes/SkinCologneBlue.php create mode 100644 includes/SkinFramed.php create mode 100644 includes/SkinNostalgia.php create mode 100644 includes/SkinStandard.php create mode 100644 includes/SpecialAllpages.php create mode 100644 includes/SpecialAsksql.php create mode 100644 includes/SpecialBlockip.php create mode 100644 includes/SpecialBooksources.php create mode 100644 includes/SpecialContributions.php create mode 100644 includes/SpecialDebug.php create mode 100644 includes/SpecialEmailuser.php create mode 100644 includes/SpecialImagelist.php create mode 100644 includes/SpecialIntl.php create mode 100644 includes/SpecialIpblocklist.php create mode 100644 includes/SpecialListusers.php create mode 100644 includes/SpecialLockdb.php create mode 100644 includes/SpecialLonelypages.php create mode 100644 includes/SpecialLongpages.php create mode 100644 includes/SpecialMaintenance.php create mode 100644 includes/SpecialMovepage.php create mode 100644 includes/SpecialNeglectedpages.php create mode 100644 includes/SpecialNewpages.php create mode 100644 includes/SpecialPopularpages.php create mode 100644 includes/SpecialPreferences.php create mode 100644 includes/SpecialRandompage.php create mode 100644 includes/SpecialRecentchanges.php create mode 100644 includes/SpecialRecentchangeslinked.php create mode 100644 includes/SpecialShortpages.php create mode 100644 includes/SpecialSpecialpages.php create mode 100644 includes/SpecialStatistics.php create mode 100644 includes/SpecialUndelete.php create mode 100644 includes/SpecialUnlockdb.php create mode 100644 includes/SpecialUnusedimages.php create mode 100644 includes/SpecialUpload.php create mode 100644 includes/SpecialUserlogin.php create mode 100644 includes/SpecialUserlogout.php create mode 100644 includes/SpecialVote.php create mode 100644 includes/SpecialWantedpages.php create mode 100644 includes/SpecialWatchlist.php create mode 100644 includes/SpecialWhatlinkshere.php create mode 100644 includes/Title.php create mode 100644 includes/UpdateClasses.php create mode 100644 includes/User.php create mode 100644 includes/UserTalkUpdate.php create mode 100644 includes/UserUpdate.php create mode 100644 includes/Utf8Case.php create mode 100644 includes/ViewCountUpdate.php create mode 100644 install.php create mode 100644 languages/Language.php create mode 100644 languages/LanguageCs.php create mode 100644 languages/LanguageDa.php create mode 100644 languages/LanguageDe.php create mode 100644 languages/LanguageEn.php create mode 100644 languages/LanguageEo.php create mode 100644 languages/LanguageEs.php create mode 100644 languages/LanguageFr.php create mode 100644 languages/LanguageIt.php create mode 100644 languages/LanguageJa.php create mode 100644 languages/LanguageKo.php create mode 100644 languages/LanguageNl.php create mode 100644 languages/LanguagePl.php create mode 100644 languages/LanguageRu.php create mode 100644 languages/LanguageSv.php create mode 100644 languages/LanguageZh.php create mode 100644 maintenance/README create mode 100644 maintenance/apache-ampersand.diff create mode 100644 maintenance/archives/convertdb.php create mode 100644 maintenance/archives/importTests.php create mode 100644 maintenance/archives/importUseModWiki.php create mode 100644 maintenance/archives/patch-bot.sql create mode 100644 maintenance/archives/patch-cache.sql create mode 100644 maintenance/archives/patch-list.txt create mode 100644 maintenance/archives/patch-math.sql create mode 100644 maintenance/archives/patch-random-dateindex.sql create mode 100644 maintenance/archives/patch-searchindex.sql create mode 100644 maintenance/archives/patch-userindex.sql create mode 100644 maintenance/archives/patch-usernewtalk.sql create mode 100644 maintenance/archives/patch-watchlist.sql create mode 100644 maintenance/archives/rebuildRecentchanges.inc create mode 100644 maintenance/archives/upgradeWatchlist.php create mode 100644 maintenance/build-intl-wiki.sql create mode 100644 maintenance/cleandb.php create mode 100644 maintenance/database.sql create mode 100644 maintenance/fetchInterwiki.pl create mode 100644 maintenance/indexes.sql create mode 100644 maintenance/initialdata.sql create mode 100644 maintenance/rebuildIndex.php create mode 100644 maintenance/rebuildLinks.inc create mode 100644 maintenance/rebuildLinks.php create mode 100644 maintenance/tables.sql create mode 100644 maintenance/users.sql create mode 100644 math/Makefile create mode 100644 math/README create mode 100644 math/TODO create mode 100644 math/html.ml create mode 100644 math/html.mli create mode 100644 math/lexer.mll create mode 100644 math/mathml.ml create mode 100644 math/mathml.mli create mode 100644 math/parser.mly create mode 100644 math/render.ml create mode 100644 math/render_info.mli create mode 100644 math/tex.mli create mode 100644 math/texutil.ml create mode 100644 math/texutil.mli create mode 100644 math/texvc.ml create mode 100644 math/texvc_cgi.ml create mode 100644 math/texvc_test.ml create mode 100644 math/texvc_tex.ml create mode 100644 math/util.ml create mode 100644 rdf/recent.phtml create mode 100644 redirect.phtml create mode 100644 stylesheets/cologneblue.css create mode 100644 stylesheets/nostalgia.css create mode 100644 stylesheets/quickbar.css create mode 100644 stylesheets/sticky.js create mode 100644 stylesheets/wikibits.js create mode 100644 stylesheets/wikiprintable.css create mode 100644 stylesheets/wikistandard.css create mode 100644 testsuite/README create mode 100644 testsuite/build.xml create mode 100644 testsuite/data/Agriculture.txt create mode 100644 testsuite/data/Anthropology.txt create mode 100644 testsuite/data/Archaeology.txt create mode 100644 testsuite/data/Architecture.txt create mode 100644 testsuite/data/Astronomy_and_astrophysics.txt create mode 100644 testsuite/data/Biology.txt create mode 100644 testsuite/data/Blocklevels.txt create mode 100644 testsuite/data/Bracketvars.txt create mode 100644 testsuite/data/Business_and_industry.txt create mode 100644 testsuite/data/Card_game.txt create mode 100644 testsuite/data/Chemistry.txt create mode 100644 testsuite/data/Classics.txt create mode 100644 testsuite/data/Communication.txt create mode 100644 testsuite/data/Computer_Science.txt create mode 100644 testsuite/data/Cooking.txt create mode 100644 testsuite/data/Critical_theory.txt create mode 100644 testsuite/data/Dance.txt create mode 100644 testsuite/data/Earth_science.txt create mode 100644 testsuite/data/Economics.txt create mode 100644 testsuite/data/Education.txt create mode 100644 testsuite/data/Engineering.txt create mode 100644 testsuite/data/Entertainment.txt create mode 100644 testsuite/data/Equations.txt create mode 100644 testsuite/data/ExternalLinks.txt create mode 100644 testsuite/data/Family_and_consumer_science.txt create mode 100644 testsuite/data/Film.txt create mode 100644 testsuite/data/Game.txt create mode 100644 testsuite/data/Geography.txt create mode 100644 testsuite/data/Handicraft.txt create mode 100644 testsuite/data/Headings.txt create mode 100644 testsuite/data/Health_science.txt create mode 100644 testsuite/data/History.txt create mode 100644 testsuite/data/History_of_science_and_technology.txt create mode 100644 testsuite/data/Hobby.txt create mode 100644 testsuite/data/InternalLinks.txt create mode 100644 testsuite/data/Language.txt create mode 100644 testsuite/data/Law.txt create mode 100644 testsuite/data/Library_and_information_science.txt create mode 100644 testsuite/data/Linguistics.txt create mode 100644 testsuite/data/Literature.txt create mode 100644 testsuite/data/Magics.txt create mode 100644 testsuite/data/Main_Page.txt create mode 100644 testsuite/data/Mathematics.txt create mode 100644 testsuite/data/Music.txt create mode 100644 testsuite/data/Opera.txt create mode 100644 testsuite/data/Painting.txt create mode 100644 testsuite/data/Philosophy.txt create mode 100644 testsuite/data/Physics.txt create mode 100644 testsuite/data/Poker.txt create mode 100644 testsuite/data/Political_science.txt create mode 100644 testsuite/data/Psychology.txt create mode 100644 testsuite/data/Public_affairs.txt create mode 100644 testsuite/data/Quotes.txt create mode 100644 testsuite/data/Recreation.txt create mode 100644 testsuite/data/Religion.txt create mode 100644 testsuite/data/Sculpture.txt create mode 100644 testsuite/data/Sociology.txt create mode 100644 testsuite/data/Sport.txt create mode 100644 testsuite/data/Statistics.txt create mode 100644 testsuite/data/Technology.txt create mode 100644 testsuite/data/Theater.txt create mode 100644 testsuite/data/Tourism.txt create mode 100644 testsuite/data/Transport.txt create mode 100644 testsuite/data/Visual_arts_and_design.txt create mode 100644 testsuite/data/World_Series_of_Poker.txt create mode 100644 testsuite/data/startrek.png create mode 100755 testsuite/jars/httpunit.jar create mode 100755 testsuite/jars/js.jar create mode 100755 testsuite/jars/nekohtml.jar create mode 100755 testsuite/jars/xercesImpl.jar create mode 100755 testsuite/jars/xml-apis.jar create mode 100755 testsuite/run create mode 100644 testsuite/src/com/piclab/wikitest/DBLoader.java create mode 100644 testsuite/src/com/piclab/wikitest/EditTest.java create mode 100644 testsuite/src/com/piclab/wikitest/HTMLTest.java create mode 100644 testsuite/src/com/piclab/wikitest/LinkTest.java create mode 100644 testsuite/src/com/piclab/wikitest/MathTest.java create mode 100644 testsuite/src/com/piclab/wikitest/ParserTest.java create mode 100644 testsuite/src/com/piclab/wikitest/SearchTest.java create mode 100644 testsuite/src/com/piclab/wikitest/SpecialTest.java create mode 100644 testsuite/src/com/piclab/wikitest/Test.template create mode 100644 testsuite/src/com/piclab/wikitest/UploadTest.java create mode 100644 testsuite/src/com/piclab/wikitest/WikiFetchThread.java create mode 100644 testsuite/src/com/piclab/wikitest/WikiLogFormatter.java create mode 100644 testsuite/src/com/piclab/wikitest/WikiSearchThread.java create mode 100644 testsuite/src/com/piclab/wikitest/WikiSuite.java create mode 100644 testsuite/src/com/piclab/wikitest/WikiSuiteFailureException.java create mode 100644 testsuite/src/com/piclab/wikitest/WikiTest.java create mode 100644 testsuite/wikitest.prefs.sample create mode 100644 texvc.phtml create mode 100644 update.php create mode 100644 wiki.phtml diff --git a/AdminSettings.sample b/AdminSettings.sample new file mode 100644 index 0000000000..b4d74a130a --- /dev/null +++ b/AdminSettings.sample @@ -0,0 +1,19 @@ + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000000..f4d5e2d998 --- /dev/null +++ b/INSTALL @@ -0,0 +1,159 @@ +The Wikipedia software was developed collabortively by +many people, so it's something of a hodgepodge. The +main wiki software itself is written in PHP, and requires +the Apache web server and MySQL database. The optional +math rendering functions are written in Objective CAML, +which is required to compile them. The test suite is +written in Java, using several external libraries. + +Recommended versions are: Apache 1.3.26 or later; MySQL +2.23.51 or later; PHP 4.2.2 or later. The installation at +wikipedia.org also uses the APC caching software, but +that's entirely optional and doesn't affect anything else. + +The math rendering functions are more complex, and will +probably only work on Linux. Objective CAML (probably +3.06 or later) is required to compile texvc, but produces +static binaries. TeTeX and ImageMagick are required at +runtime, and ImageMagick requires GhostScript. These are +present in most Linux distributions. + +Before installing the software, you must copy the file +"LocalSettings.sample" to "LocalSettings.php", and +"AdminSettings.sample" to "AdminSettings.php", and +customize both of the php files to your local setup +(things like installation parh, passwords, etc.) The +script install.php in the maintenance directory can then +be run to install the software. + +Here are some more notes on building a system from scratch +the way it was done for the Wikipedia server: + +Downloads: + + gcc-2.95.3.tar.gz + mysql-3.23.51.tar.gz + libiconv-1.8.tar.gz + apache_1.3.26.tar.gz + php-4.2.1.tar.gz + apc-cvs.tar.gz + +And for math support: + ocaml-3.06.tar.gz + (TeTeX, ImageMagick, and GhostScript come with most Linux distros) + +1. MySQL strongly recommends using gcc 2.95 to compile MySQL. + RedHat Linux comes with 2.96 by default, so you'll have to install + 2.95 first. Use "../gcc*/configure --enable-shared" If your Linux + installation doesn't use gcc 2.96 you can skip this step. + +2. Install MySQL source; add "mysql" user and group. Make sure the + directory into which you installed gcc 2.95 appears before the + directory of gcc 2.96 in your path. Configure with: + + FLAGS="-O3 -mpentiumpro" CXX=gcc CXXFLAGS="-O3 -mpentiumpro -felide-constructors -fno-exceptions -fno-rtti" ./configure --prefix=/usr/local/mysql --enable-assembler --with-mysqld-ldflags=-all-static --disable-shared --with-extra-charset=complex + + Edit the file myisam/ftdefs.h, changing the define for minimum word + length for fulltext indexing: #define MIN_WORD_LEN 2. Update + $wgDBminWordLen in LocalSettings.php to reflect this. Build and + install according to instructions. Make root user as recommended; + the root password will be required for the wiki installation script. + +3. Unpack the phase3.zip source distribution, or check out the "phase3" + module from CVS. Copy LocalSettings.sample to LocalSettings.php, + AdminSettings.sample to AdminSettings.php, and customize them for + things like local paths and passwords. If desired, update + FulltextStoplist.php from the MySQL sources if you have customized + MySQL's stop list. + +4. Optionally, install libiconv (http://www.gnu.org/software/libiconv/). + This will be used by some language packages for converting + native-charset URLs to and from UTF-8. If you're running an + English-only wiki, this won't be necessary. + +5. Unpack Apache distribution and begin configuring, but don't finish + build yet. Configure with something like: + + OPTIM='-O2 -mpentiumpro' ./configure --with-layout=Apache + +6. If you'll want to use Apache's mod_rewrite to make page-viewing URLs + look like static links (as wikipedia.org does), install the included + patch "apache-ampersand.diff" which is needed to support page titles + with ampersands in them: + + patch -p0 < /path/to/maintenance/apache-ampersand.diff + +7. Unpack and configure PHP. Configure with something like: + + ./configure --enable-apc --with-mysql=/usr/local/mysql --with-iconv=/usr/local/lib --with-apache=/home/lee/src/apache_1.3.26 + + (using your own local paths, of course). Build and install as + instructed. Set "register_globals" on in the config file. + +8. Finish building Apache. Configure with something like: + + OPTIM='-O2 -mpentiumpro' ./configure --with-layout=Apache --enable-module=rewrite --activate-module=src/modules/php4/libphp4.a + + Update httpd.conf as needed for your site. For example: + + + AddType application/x-httpd-php .php .php4 .phtml + AddType application/x-httpd-php-source .phps + + + php_admin_flag engine off + + + + php_admin_flag engine on + + + RewriteEngine On + RewriteMap ampescape int:ampescape + RewriteRule ^/wiki/(.*)$ /wiki.phtml?title=${ampescape:$1} [L] + + It is *seriously* recommended that you configure the webserver + to disable running of PHP scripts except in the script directories + (the "php_admin_flag engine off/on" directives above) to prevent + the uploading and running of malicious scripts. + +9. Optionally, install APC, following standard instructions for + installing as a Zend extension. + +10. If using embedded TeX support, be sure TeX and ImageMagick are + installed (they are common on most Linux distros and freely + downloadable). Also get and install OCaml according to its + instructions. + + You'll need to compile the texvc helper script; enter the math + subdirectory of the source tree and run "make". + + If you don't want embedded TeX support, disable it by setting + + $wgUseTex = false; + + in LocalSettings.php + +11. You should now be able to run the install.php script. Use PHP in + command-line mode, i.e., type "php install.php". Should be run as + root, or as a user or group able to create files and directories + in the installation tree. + +12. If you have Java installed and running, install the "ant" package + from Apache (http://ant.apache.org/) and run ant in the testsuite + directory to build the tests. Copy wikitest.prefs.sample to + wikitest.prefs, and edit to reflect your local settings. Then + "./run WikiSuite -o -b" will run the whole test suite and report. + +---- + +Don't forget that this is pre-release software under development! +Chances are good there's a crucial step that hasn't made it +into the documentation. You should probably sign up for the +Wikipedia developers' mailing list; you can ask for help (please +provide enough information to work with, and preferably be aware +of what you're doing!) and keep track of major changes to the +software, including performance improvements and security patches. + +http://www.wikipedia.org/mailman/listinfo/wikitech-l + diff --git a/LocalSettings.sample b/LocalSettings.sample new file mode 100644 index 0000000000..f0ac8e48a8 --- /dev/null +++ b/LocalSettings.sample @@ -0,0 +1,53 @@ + diff --git a/README b/README new file mode 100644 index 0000000000..9b0fbde0dd --- /dev/null +++ b/README @@ -0,0 +1,45 @@ +2003-04-14 + +This is the "Phase III" codebase for the Wikipedia project +(http://www.wikipedia.org). It is possibly usable as a +general-purpose wiki, but it was not intended as such and +there is little or no support available for using it as such +from the developers, who are busy full time supporting the +Wikipedia project. There are many good general-purpose Wikis +in the world. I personally recommend UseMod +(http://www.usemod.com/). + +The code as a whole is Copyright 2002-3 by Lee Daniel Crocker, +Magnus Manske, Jan Hidders, Brion Vibber, Axel Boldt, +Geoffrey T. Dairiki, Tomasz Wegrzanowski, and others, +licensed under the terms of the GNU General Public License, +version 2 (see http://www.fsf.org/licenses/gpl.html). +Derivative works and later versions of the code will also be +considered free software licensed under the same terms. + +Many thanks to the Wikipedia regulars for testing and +suggestions. + +Sections of code written exclusively by Lee Crocker are also +released into the public domain, wich does not impair the +obligations of users under the GPL for use of the whole code +or other sections thereof. + +The code is currently maintained at Sourceforge under the +project "wikipedia", module name "phase3". You can view the +code in CVS, report bugs and make feature requests there: + + http://wikipedia.sourceforge.net + +The code is written in PHP. It was tested mainly on version +4.06, but it is known to run in later versions. I requires +MySQL 3.23 or later, and PHP-MySQL. It is now running with +PHP 4.2.2 and MySQL 3.23.49 at http://www.wikipedia.org . +The code was written on and for Linux, and no attempt has +been made to make it portable to other OSs, though it may +happen to run on some. The math support in particular has +run-time dependencies on things like TeTeX and GhostScript +that are generally found in Linux but not other OSs. + +-- Lee Daniel Crocker + diff --git a/docs/deferred.doc b/docs/deferred.doc new file mode 100644 index 0000000000..425395fa63 --- /dev/null +++ b/docs/deferred.doc @@ -0,0 +1,19 @@ + +DEFERRED.DOC + +A few of the database updates required by various functions here +can be deferred until after the result page is displayed to the +user. For example, updating the view counts, updating the +linked-to tables after a save, etc. PHP does not yet have any +way to tell the server to actually return and disconnect while +still running these updates (as a Java servelet could), but it +might have such a feature in the future. + +We handle these by creating a deferred-update object (in a real +O-O language these would be classes that implement an interface) +and putting those objects on a global list, then executing the +whole list after the page is displayed. We don't do anything +smart like collating updates to the same table or such because +the list is almost always going to have just one item on it, if +that, so it's not worth the trouble. + diff --git a/docs/design.doc b/docs/design.doc new file mode 100644 index 0000000000..7c03071362 --- /dev/null +++ b/docs/design.doc @@ -0,0 +1,119 @@ +This is a brief overview of the new design. + +Primary source files/objects: + + wiki.phtml + Main script. It creates the necessary global objects and parses + the URL to determine what to do, which it then generally passes + off to somebody else (depending on the action to be taken). + + All of the functions to which it might delegate generally do + their job by sending content to the $wgOut object. After returning, + the script flushes that out by calling $wgOut->output(). If there + are any changes that need to be made to the database that can be + deferred until after page display, those happen at the end. + + Note that the order in the includes is touchy; Language uses + some global functions, etc. Likewise with the creation of the + global variables. Don't move them around without some forethought. + + User + Encapsulates the state of the user viewing/using the site. + Can be queried for things like the user's settings, name, etc. + Handles the details of getting and saving to the "user" table + of the database, and dealing with sessions and cookies. + More details in USER.DOC. + + OutputPage + Encapsulates the entire HTML page that will be sent in + response to any server request. It is used by calling its + functions to add text, headers, etc., in any order, and then + calling output() to send it all. It could be easily changed + to send incrementally if that becomes useful, but I prefer + the flexibility. This should also do the output encoding. + The system allocates a global one in $wgOut. This class + also handles converting wikitext format to HTML. + + Title + Represents the title of an article, and does all the work + of translating among various forms such as plain text, URL, + database key, etc. For convenience, and for historical + reasons, it also represents a few features of articles that + don't involve their text, such as access rights. + + Article + Encapsulates access to the "cur" table of the database. The + object represents a Wikipedia article, and maintains state + such as text (in Wikitext format), flags, etc. + + Skin + Encapsulates a "look and feel" for the wiki. All of the + functions that render HTML, and make choices about how to + render it, are here, and are called from various other + places when needed (most notably, OutputPage::addWikiText()). + The StandardSkin object is a complete implementation, and is + meant to be subclassed with other skins that may override + some of its functions. The User object contains a reference + to a skin (according to that user's preference), and so + rather than having a global skin object we just rely on the + global User and get the skin with $wgUser->getSkin(). + + Language + Represents the language used for incidental text, and also + has some character encoding functions and other locale stuff. + A global one is allocated in $wgLang. + + LinkCache + Keeps information on existence of articles. See LINKCACHE.DOC. + +Naming/coding conventions: + + These are meant to be descriptive, not dictatorial; I won't + presume to tell you how to program, I'm just describing the + methods I chose to use for myself. If you do choose to + follow these guidelines, it will probably be easier for you + to collaborate with others on the project, but if you want + to contribute without bothering, by all means do so (and don't + be surprized if I reformat your code). + + - I have the code indented with tabs to save file size and + so that users can set their tab stops to any depth they like. + I use 4-space tab stops, which work well. I also use K&R brace + matching style. I know that's a religious issue for some, + so if you want to use a style that puts opening braces on the + next line, that's OK too, but please don't use a style where + closing braces don't align with either the opening brace on + its own line or the statement that opened the block--that's + confusing as hell. + + - PHP doesn't have "private" member variables of functions, + so I've used the comment "/* private */" in some places to + indicate my intent. Don't access things marked that way + from outside the class def--use the accessor functions (or + make your own if you need them). Yes, even some globals + are marked private, because PHP is broken and doesn't + allow static class variables. + + - Member variables are generally "mXxx" to distinguish them. + This should make it easier to spot errors of forgetting the + required "$this->", which PHP will happily accept by creating + a new local variable rather than complaining. + + - Globals are particularly evil in PHP; it sets a lot of them + automatically from cookies, query strings, and such, leading to + namespace conflicts; when a variable name is used in a function, + it is silently declared as a new local masking the global, so + you'll get weird error because you forgot the global declaration; + lack of static class member variables means you have to use + globals for them, etc. Evil, evil. + + I think I've managed to pare down the number of globals we use + to a scant few dozen or so, and I've prefixed them all with "wg" + so you can spot errors better (odds are, if you see a "wg" + variable being used in a function that doesn't declare it global, + that's probably an error). + + Other conventions: Top-level functions are wfFuncname(), names + if session variables are wsName, cookies wcName, and form field + values wpName ("p" for "POST"). + diff --git a/docs/globals.doc b/docs/globals.doc new file mode 100644 index 0000000000..ac1a609163 --- /dev/null +++ b/docs/globals.doc @@ -0,0 +1,29 @@ +GLOBALS.DOC + +PHP loves globals. I hate them. This is not a great +combination, but I manage. I could get rid of most of +them by having a single "HTTP request" object, and using +it to hold everything that's now global (which is exactly +what I'd do in a Java servlet). But that's really +awkward in PHP, and wouldn't really provide much benefit +in readability or maintainability, so I go with the flow +of PHP and use globals. Here's documentation on the +important globals used by the system. + +$wgOut + OutputPage object for HTTP response. + +$wgTitle + Title object created from the request URL. + +$wgLang + Language object for this request. + +$wgArticle + Article object corresponsing to $wgTitle. + +$wgLinkCache + LinkCache object. + +... + diff --git a/docs/language.doc b/docs/language.doc new file mode 100644 index 0000000000..06639f73ba --- /dev/null +++ b/docs/language.doc @@ -0,0 +1,24 @@ +LANGUAGE.DOC + +The Language object handles all readable text produced by the +software. The most used function is getMessage(), usually +called with the wrapper function wfMsg() which calls that method +on the global language object. It just returns a piece of text +given a text key. It is recommended that you use each key only +once--bits of text in different contexts that happen to be +identical in English may not be in other languages, so it's +better to add new keys than to reuse them a lot. Likewise, +if there is text that gets combined with things like names and +titles, it is better to put markers like "$1" inside a piece +of text and use str_replace() than to compose such messages in +code, because their order may change in other languages too. + +While the system is running, there will be one global language +object, which will be a subtype of Language. The methods in +these objects will return the native text requested if available, +otherwise they fall back to sending English text (which is why +the LanguageEn object has no code at all--it just inherits the +English defaults of the Language base class). + +The names of the namespaces are also contained in the language +object, though the numbers are fixed. diff --git a/docs/linkcache.doc b/docs/linkcache.doc new file mode 100644 index 0000000000..b0afbeec6e --- /dev/null +++ b/docs/linkcache.doc @@ -0,0 +1,31 @@ +LINKCACHE.DOC + +The LinkCache class maintains a list of article titles and +the information about whether or not the article exists in +the database. This is used to mark up links when displaying +a page. If the same link appears more than once on any page, +then it only has to be looked up once. + +In practice, what happens is that the global cache object +$wgLinkCache is consulted and updated every time the function +getArticleID() from Title is called. + +This has a side benefit that we take advantage of. We have +tables "links" and "brokenlinks" which we use to do things +like the Orphans page and Whatlinkshere page. It just so +happens that after we update a page, we display it--and as +we're displaying it, we look up all the links on that page, +causing them to be put into the cache. That information is +exactly what we need to update those two tables. So, we do +something tricky when we update pages: just after the update +and before we display, we clear the cache. Then we display +the updated page. Finally, we put a LinksUpdate object onto +the deferred updates list, which fetches its information from +the cache. + +There's a minor complication: displaying a page also looks up +a few things like the talk page link in the quick bar and the +date links. Since we don't want those in the link tables, we +must take care to suspend the cache while we look those up. +Skin.php does exactly that--see dateLink(), for example. + diff --git a/docs/schema.doc b/docs/schema.doc new file mode 100644 index 0000000000..d28af2d653 --- /dev/null +++ b/docs/schema.doc @@ -0,0 +1,224 @@ +SCHEMA.DOC + +The most up-to-date schema for the tables in the database +should always be "tables.sql" in the maintenance directory, +which is called from the installation script. Here are a +few highlight that may be out of date: + +user (Wikipedia users) + + user_id + integer, primary key, autoincrement + user_name + Usernames must be unique, must not be in the form of + an IP address. _Shouldn't_ allow slashes or case + conflicts. Spaces are allowed, and are _not_ converted + to underscores like titles. (Conflicts?) + user_rights + Comma-separated list of textual flags. + user_password + Hash of current password. + user_newpassword + Generated for mail-a-new-password feature + user_email + Note -- email should be restricted, not public info. + Same with passwords. ;) + user_options + Newline-separated list of name=value pairs. + + + +cur (Wikipedia "current" articles) + + cur_id + integer, primary key, autoincrement + cur_namespace + integer index into list of namespaces. See the + Namespace class for more details. + cur_title + Title of article (in dbkey form--see Title), without + namespace. The combination of namespace,title should + be unique in this table. + cur_text + Wikitext of the article. + cur_comment + The summary of the last change. + cur_user + User id who made the last change, or 0 if unknown. + cur_user_text + Name of the user above, or IP address. + cur_timestamp + Time of the last change. + cur_minor_edit + Flag: 0 or 1 is last change was a "minor" edit. + cur_restrictions + Who may or may not edit the article. + cur_counter + Number of times this page has been viewed. + cur_ind_title + Text version of title for fulltext searches. + cur_ind_text + Plaintext version of text for fulltext searches. + cur_is_redirect + 1 indicates the article is a redirect. + cur_minor_edit + 1 indicates this was a minor edit. + cur_is_new + 1 indicates this is the first revision of a new entry. + + + +old (Historical versions articles. Most fields + correspond to the same fields in "cur") + + old_id + old_namespace + old_title + old_text + old_comment + old_user + old_user_text + old_timestamp + old_minor_edit + old_flags + This last is currently unused. + + + +archive (Temporary storage of deleted articles which may be restored. + Fields correspond to those of "cur" and "old") + ar_namespace + ar_title + ar_text + ar_comment + ar_user + ar_user_text + ar_timestamp + ar_minor_edit + ar_flags + This last is currently unused. + + + +links (Internal links to existing articles) + + l_from + ID of source article. (currently title, may be changed) + l_to + ID of target article. + + + +brokenlinks (Internal links to non-existent articles) + + bl_from + ID of source link. + bl_to + Title of target link. + + + +imagelinks (Internal links to images via [[Image:filename]] syntax) + + il_from + Title of target article. + il_to + Filename of target image. + + + +image (Uploaded images and other files) + + img_name + Filename. + img_size + File size in bytes. + img_description + Description field given during upload. + img_user + User ID who uploaded the file. + img_user_text + User name who uploaded the file. + img_timestamp + Timestamp when upload took place. + + + +oldimage (Old versions of images stored for potential revert) + + oi_name + Original filename. + oi_archive_name + Filename of stored old revision; timestamp and + exclaimation point prepended to oi_name + oi_size + File size in bytes. + oi_description + Description field given during upload. + oi_user + User ID who uploaded the file. + oi_user_text + User name who uploaded the file. + oi_timestamp + Timestamp when upload took place. + + + +ipblocks (IP addresses and users blocked from editing) + + ipb_address + Blocked IP address in dotted-quad form or "" + ipb_user + Blocked user ID or 0. + ipb_by + User ID who made the block. + ipb_reason + Text comment made by blocker. + + + +random (Random page queue) + + ra_current + 1 = hasn't come up on a random page view yet. + >1 = has been viewed, will be ignored for a few + ra_title + Title of an article. + + + +site_stats (Site-wide statistics) + + ss_row_id + Token for where clauses. There's only one row in + this table. At some point we might want to use a + date here so we can get stats-by-date. + ss_total_views + Number of total views of all pages. + ss_total_edits + Number of total page edits. + ss_good_articles + Number of "countable" articles. + + + +recentchanges + + (Will document further when working) + + + +watchlist + + wl_user + Foreign key -> user_id + wl_namespace + Namespace -> cur_namespace + Note that these should only include even-numbered + namespaces for regular pages; associated talk pages + (odd numbered namespaces) are folded in. + wl_title + Page title -> cur_title + Note also that the linked page may not exist in page + or talk namespace, or at all. + diff --git a/docs/skin.doc b/docs/skin.doc new file mode 100644 index 0000000000..cdc9cfda94 --- /dev/null +++ b/docs/skin.doc @@ -0,0 +1,8 @@ + +SKIN.DOC + +This document describes the overall architecture of Wikipedia's +HTML rendering code. It is placed here rather than in comments +in the code itself to help reduce the code size. + + diff --git a/docs/title.doc b/docs/title.doc new file mode 100644 index 0000000000..1ae445601d --- /dev/null +++ b/docs/title.doc @@ -0,0 +1,72 @@ +TITLE.DOC + +The Wikipedia software's "Title" class represents article +titles, which are used for many purposes: as the human-readable +text title of the article, in the URL used to access the article, +the wikitext link to the article, the key into the article +database, and so on. The class in instantiated from one of +these forms and can be queried for the others, and for other +attributes of the title. This is intended to be an +immutable "value" class, so there are no mutator functions. + +To get a new instance, call one of the static factory +methods WikiTitle::newFromURL(), WikiTitle::newFromDBKey(), +or WikiTitle::newFromText(). Once instantiated, the +other non-static accessor methods can be used, such as +getText(), getDBKey(), getNamespace(), etc. + +The prefix rules: a title consists of an optional Interwiki +prefix (such as "m:" for meta or "de:" for German), followed +by an optional namespace, followed by the remainder of the +title. Both Interwiki prefixes and namespace prefixes have +the same rules: they contain only letters, digits, space, and +underscore, must start with a letter, are case insensitive, +and spaces and underscores are interchangeable. Prefixes end +with a ":". A prefix is only recognized if it is one of those +specifically allowed by the software. For example, "de:name" +is a link to the article "name" in the German Wikipedia, because +"de" is recognized as one of the allowable interwikis. The +title "talk:name" is a link to the article "name" in the "talk" +namespace of the current wiki, because "talk" is a recognized +namespace. Both may be present, and if so, the interwiki must +come first, for example, "m:talk:name". If a title begins with +a colon as its first character, no prefixes are scanned for, +and the colon is just removed. Note that because of these +rules, it is possible to have articles with colons in their +names. "E. Coli 0157:H7" is a valid title, as is "2001: A Space +Odyssey", because "E. Coli 0157" and "2001" are not valid +interwikis or namespaces. Likewise, ":de:name" is a link to +the article "de:name"--even though "de" is a valid interwiki, +the initial colon stops all prefix matching. + +Character mapping rules: Once prefixes have been stripped, the +rest of the title processed this way: spaces and underscores are +treated as equivalent and each is converted to the other in the +appropriate context (underscore in URL and database keys, spaces +in plain text). "Extended" characters in the 0x80..0xFF range +are allowed in all places, and are valid characters. They are +encoded in URLs. Other characters may be ASCII letters, digits, +hyphen, comma, period, apostrophe, parentheses, and colon. No +other ASCII characters are allowed, and will be deleted if found +(they will probably cause a browser to misinterpret the URL). +Extended characters are _not_ urlencoded when used as text or +database keys. + +Character encoding rules: TODO + +Canonical forms: the canonical form of a title will always be +returned by the object. In this form, the first (and only the +first) character of the namespace and title will be uppercased; +the rest of the namespace will be lowercased, while the title +will be left as is. The text form will use spaces, the URL and +DBkey forms will use underscores. Interwiki prefixes are all +lowercase. The namespace will use underscores when returned +alone; it will use spaces only when attached to the text title. + +getArticleID() needs some explanation: for "internal" articles, +it should return the "cur_id" field if the article exists, else +it returns 0. For all external articles it returns 0. All of +the IDs for all instances of Title created during a request are +cached, so they can be looked up wuickly while rendering wiki +text with lots of internal links. + diff --git a/docs/user.doc b/docs/user.doc new file mode 100644 index 0000000000..2eb0dee3fb --- /dev/null +++ b/docs/user.doc @@ -0,0 +1,77 @@ + +USER.DOC + +Documenting the Wikipedia User object. + +(DISCLAIMER: The documentation is not guaranteed to be in sync with +the code at all times. If in doubt, check the table definitions +and User.php.) + +Database fields: + + user_id + Unique integer identifier; primary key. Sent to user in + cookie "$WikiUserOpts". + + user_name + Text of full user name; title of "user:" page. Displayed + on change lists, etc. Sent to user as cookie "$WikiUserName". + Note that user names can contain spaces, while these are + converted to underscores in page titles. + + user_rights + Comma-separated list of rights. Right now, only "sysop", + "developer", and "bot". + + user_password + md5 has of user login password. If user option to + remember password is set, this is stored in cookie + "$WikiUserPassword". + + user_email + User's e-mail address. (Optional, used for user-to-user + e-mail and password recovery.) + + user_options + A urlencoded string of name=value pairs to set various + user options. Some of these (but not all) are encoded into + the cookie "$WikiUserOpts". + +The user object encapsulates all of the settings, and clients +classes use the getXXX() functions to access them. These functions +do all the work of determining whether the user is logged in, +whether the requested option can be satisfied from cookies or +whether a database query is needed. Most of the settings needed +for rendering normal pages are set in the cookie to minimize use +of the database. + +Options + The user_options field is a list of name-value pairs. The + following option names are used at various points in the system: + + +Cookies + + $WikiUserName + Plain text user name directly from database + + $WikiUserPassword + From the database; only set if the user's options are set + to remember passwords. + + $WikiUserOpts + User ID and various options encoded into a compact string; + for example, "12e0q0s2h0u1j0n1f0y0". The first string of digits + is the ID; the rest of the string a flag-letter plus number + pairs representing a few important options: + + e = "encoding" (index into array) + q = "quickBar" (0=none, 1=left, 2=right) + s = "skin" (0=standard, 1=startrek, 2=nostalgia, 3=cologneblue) + h = "showHover" (1=true) + u = "underlineLinks" (1=true) + j = "justify" (1=true) + n = "numberHeadings" (1=true) + f = "viewFrames" (1=true) + y = 1 if user_rights include "is_sysop" + diff --git a/images/favicon.ico b/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..31b0e380926531c268b0642cac2300fc829e46d2 GIT binary patch literal 318 zcmZQzU<5(|0RbS%!l1#(z#zuJz@P!d0zj+)#2|5W0Fna%7A6*kmgW|Q6UR?5c({8o zynOMJAu}tJVdaXI3>q3541fOqVUU!NWSB8y2E&b;HyFYr!WpWns~C3f+R0#JWy8<` z2YnhH`#_v#>yB^`onF>gt^uUwukEdp0Ma#X@ev{&9gS?=-(Rc(DeL~+vA3h+r9>A` whvSQmlNy@~Gx{>Rr+TbRifD;wtm`n}BGnT0#;(nF$DQ3VKm)J>h%d+|0VGXX9smFU literal 0 HcmV?d00001 diff --git a/images/startrek.png b/images/startrek.png new file mode 100644 index 0000000000000000000000000000000000000000..d3a1053ae4f3a658b515095e5b05de509d447bf7 GIT binary patch literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^g+ScO$P6UIHn+|JQY`6?zK#qG8~eHcB(eheYymzY zu0Z<#e}<0R1pz=7W0JSK3quF1tOt<8S>O>_%)p>04#JEvDV&@lt$l-F)#J1ptP)wJ%v9TEd3_xzr4*o}t`#qSxp*K9Z#XA!pl z{$88J#;rgp_Bqx36^DW%xTW6|9Ju+pL0xBqS#Imz0+YF`-7jz6AaZTl+70HizUk)| pzxEYRXFi^jucNwfHq*Cl%|M4Tc)I$ztaD0e0ssx^ZgKzs literal 0 HcmV?d00001 diff --git a/images/valid-html401.png b/images/valid-html401.png new file mode 100644 index 0000000000000000000000000000000000000000..3855210c6c3c85c56f90221b3247fa664374b6bf GIT binary patch literal 2948 zcmV-~3w!j5P))Ny!GVlSJK8mpb&TtF z4@tYm4elYq(XQhr5*)aW`cBE?t^WN;1=H{4~EE4iYG zwso*K^ldh6pMOh)VE2)t!WHx7zCb|;;(ei8y?<~>GE}%ig)8oc#Z^7Lp~4kOnloSR zQQ?Z-c!S9Rif3q@(i?41;fm$v16;{`^MMLiMEmr#np{A;b){na6A^sSM;@Lx3`39C zad6hh2hG=6#AGz!X;{xVj>ZyB->^2PO_bB+eKssyu$JX=Nt&iSJUrn0K1q@wx?Pmx zT=|~a;`vz)bT17@W6?*;!+n?8^8l0OJkNJ1NAUaC0RXPsL8q3ZDL%{SgQsy1fcYjN z(FTCjSP+nvcE(gA@@Jx$iv6|J09IXzwwAY8^E$5RWfz?qHrwP1;Jr>c>~(R|Y4BJE zGU%K&&(F^+77GAgUS8O4x4gf1pvnGo8i&XJ7!Nl8lEQ)>xkBJ3J*B zKYEq<7hfDHSgZ~_jqW(6Im!vsR{IdfP@ z;1TaM`e;y&JokzB8f?z+VZdx$M9<+PW%LpuTv(Jl!5G7Gxy184hQlGd-HzdKh_#l{ zXjGr!iQY=pAu$}1L(TCH6d?}SJip*+%E^|LA_ROLc}y1v-eQeTbF8&|9s5idhXS4o zHWg;3PICVt4!-XzusQKT*Yv(}Ap1b z0jXSOqrurWiXxt$pONws{eS%N2g~K{s;#=|q>>=H-kFt~;_rn!^O8?Hz|*)#q7C|J znU4Kt*Rw2Ed#2Nv>1@mLExF-Q3kM6(>-CRhKCc};T%~D_6!blp-61788azjG&`08( zhAza1^h^`+L32zD!v`Nn3M??1N03{VvmuTw(6H8$Wf|d#XDd>R-V;8h?`~HR_zu1& z3p%jiN=e{5+}(A{`B?-V!oGul=TeQ}p6_7t93eW40!0)klq2zdm*kMvo^z+Vgnft1 zLcC3}SOP4BUIoZD9?lU$a9q6+?_RKg(fUXK+qW;R00Rs>hh6kRwAFY+pG~X*i|;88 zdrh?0OhUhm=A2PbfHYt4IBY+_?Go=bf$t)Pz*>t^3Z+!raCNblFTU61`|_bsmR#5G zb@{$}DDF#J*@gGT1GUJq@HFc2G`c&3vw00mY%HGV@_lt*bkT;#(Btt0rV!`Fs{`8L z-%>h>LGCWmzF#cJv+PH(Sp2QER1SIy(v(;p8 z$yIKVtk>u`{{L2h2cC=Q3O>@5U2-JR2Bo?LrGBq3`oLGn61YDm0qJJKW;R5)E^)kP z86{_cZyku#t(tCQM|N8nx2yy!~?S7yjv%JL#bc(132+sNgbOgSV zt^I?Z10B$*#ae!z9%?~V-=>EoBZ@N;T|O4nWPvEoD6-L0 zTQPXD++%cFSXX@92Ai{^cA8kTcs4-DfgeE zm73!xHq@!3x!1}ljJ7$?E5mGMi1t~-@N(TH8IxtoGBRuv%eTldS!V2#9I%)S5^Y($ zXUtZHO`KK6fx}`?Jl_#N$C#u6vJf0&ONJiqkkF&G1rp=2{vOotLXX$iKYg3GKm~YuGi;6MThRrDA z80)esEFyRFHEJ!KyW;IeJg>Fz^a}Sq*)Q4lEZj&&vX2eKaNa z<^;ZCFc`2}tw@r3;@(z#G?RjOoADYM`kr7ERO;tu2VptroTCBd2*N=J1O&Yf$`KfA zNm2+0U7j8#k3oG*{g#};J#Tw!d4G;M#u^BBiXJyOj$>RJRYbog91d}b#<W3BZO$L?8>D7#OGZB(ZTYzv*p-u(3V77 zdUqWJ7Mp?ST9n&C2veK`rxwQ(9FroG5dxIkX?cm3gW94Ycn#^unEg7rN&-(}fx*Pb zpA67CyJ1zx=K$aC*P3=oDF|dc7ghURoj+8?p6p=q&;^@=C}M4|bC!`I@C2iviRo-ZBDT5Fd(NaPJF>gy$0RdEOWl3s%sd21r-3o$p#R?!i+Z zZ_;Z7?Y|Mhe~DDq%~xg8Z!JiHAZO62^gK z6rNIs0>n-y=S~US2I!}DZ_-1!9pdM>P75b+Pv_2Uj#uQ%e!u_m>;3R#extES#p;`< zT!4no*n&D__q5xZ5hk!$M2#{URhxukqgIq&AR z0)%aX3pJ2z6WBIJtx^LQN-AHr7;pv(Cs5QXsq@Yejc3;7H&LF~CP_k;Wvxbkik$MO z>;rV#9>8#YzH=l0{FkLIkelOF+D@FWkCsFiRc4#~W{hDvonnk3zr0rIbUNgD{-<5f u<6_B8LPrR4Yk%46NvwrLr$}XJlYam}=l+xQyMnK>U`>l5GX^YMlV%Y%42#`5@zx=YZOKYEERJ4O(`}Vx zQlh=iu|$gEQrnUEkhnRqBwmu@P+JzohwcX362;fuP}_R&anr)4WWFxY#NzAT^X*(d zBvK+(IS7XR(F2h8-t+PN?)QD?JKuSbfX|^2{|6Ahp8-J3XNSab^GKJeZ#HJqQX%%U zK^!EL^!K$6jKoADnOZySW1tGD8YDIhU1M~{;GGV{g45%+F;kz!=oo$o2%?RB-TZi{ zq{?WC+omjfdi#)Nk9e99Zb--$EI{~*ILn-4yO3UshXqQw8}xB}2(k{o=!}@&UqP7) z5Z;ym;JV^I3VKJw3lex>6c4%kjjUiEiW#>BiNO-EnO<=-1Csf0U;tL>;|Y8H;H=GV z$CM>zTjl_1qEcR6GeF?vdJlFOg$!Fja4>mQa_v!A>QspIvzHwXpC=ZK(U7fU)hfSaa!QpP z)bcvu+-y|{i%^%AG?Czm<1D0!FhxP8O|T;2ao%6BEP})bp6;p;pPMsjNN=HSz+SA6 z@@r1;t}L)dISo}INT$nbed~&@#mH?2#)ZU?D;#Te^o6RL!`TZF;Ll%FMM0Pm`h65z zl+%cJdwQ!vB8kFH91<~hiBd&y4oY`7E!-J2n|Q}e0#LzH&T6g(iO2F6%HzCvZP?nN zzKY|PJ8^O;>}hCn`R|M{h!R2 zkO;uD!CbqaP7Q#;NDftoAdUNjfD(LVnzD$24Fv^&B*~%pdMB4aIFht6IZ3Xk)%n^? z!$yekgC+aW()!#{0*ShdFYOh+3gODNRe)f1qFG%fNG{VLDz603s1guExIqGc>%hg>i0Mxl?)V!w+)8krgX}wXoINrgFa9!K2@(|Mw}AVc z&VeCGs`r6+MJK}M++ESKkhsS3qLE3LjbN3mTy}=7g}Z;CrPf($`3qu;!l(oOH?<8>3_Gr+@V7p_i;A}3yqGL=}u}oPf z6{``QCqvrV%@EEe0lC5EO|ToLJy^*{1Ye);74RXDyC$_J^JYlKU|(C4G3x}}uZ022 zmqt2@+Ef)Fh0!8(++t3%JSnLw)2d|c$FGHKX`FCuh61%44}Re(oBvHpS;pY=%rsM4 zSq6cO8AzH!w{@pM7z#9gXG*fu<9#Aver4XTyE_4)HjEnw!^08nfbOB%M8tn-kW!7 z4tLB>&Ubla`lQp-JI4;os%S#a^{cyislUEE=~nCF&9nRoaSu*uV$Fmn);2Z}Oqt_i zGBqFQT?B14P4rWv;?97mx$acbEoLQ+*EnU%dw4qRq;G_{w$X`T(k$y8UG|esC+m(@ zhG5t7Of)`jGfgeq-f5PpX30>~9WlV$?f09aqJb4jN;hSXMp-YPC~LNH1}v7ePlkjv z7Q#Kn58b5M^69UM_7A#i>@cZq;DzGx{n)5UEzJ zID0~Ju|cO+Pb^rl^HyeE-&IHE+tgv1tF>fCFN>3a*N~cF#G;H; zZ<)F!>;x}QF&Ah(+||rUJZ(w;o3P9&#Vu^qgoj?ShR_1Y~U z0cWDWM?EKN7FmZZqm0{(x3l18u2QhnKY4LhYv#aVTZC6LlBj*-y~25*j&kV6&i7Rj1nJ~OCGxBB4c?74Zow>)e~ z(#L^o@&`Bhtnv|=&!nCip1ChRyJJT0C9uDNb-ncjF@u@xi-Vq-CCj<(Q%4D=KO zj!+0?nTLc_Dv>JnTSNFQrQ3wus~-5xjup2o{12!CWTJ-n z^5}lz>B}{{R0jy@!$87BTLA(;#kZFTDSxC5t~~ZI47g3~D?-A=AK=d`*{`v1>6M3q zTqhncLee#lL#F15z3JV40xdHkRG^eSjM~0segty#EL`mxb^v za0g$m5_moK{vOEEFI+j+oaVd*F)T*xrGs3lRRu6FCTfL}PoGpo@?z zgfhFIXiGj;gb>7^u0xy$+Hmc&`ybr^$sTy}$(r0a$w9DZmmjIwck7F5n&8k`Gv~cN zp_b+Taswo_Cm5W^eNupA_CIm`rP{|XH54H`Cy0i1b8@vTQWYRvZQduh@jJ8of2#<= zo;__oz4I;e8icFez3$jNBtPO9;gzrFAeUYjTJA1FaJ>E@cFga<8bol@CJ174kT5~z zAU_nM^|TQ9L}57Y!Lr@!5H=&kO_@0WH_bW7`;X=#77sKm_!(g?LY9eV1jpoW^JC`{ z9u~Q=Q_zG0^e)zfhLsv=ZtSF<6fC?!yi$Z@_A9Y@_z}=~q7Dz%795wmz3&oz(b^q& zea8Qkcuw_`UHJ0!*{{_BL~D2A5IM*I9_YKw1qlBSnEIh$W467$BZ@zG)@fI@w|6?* z+naN>?d^YmOt^I9@(I4SxBr-e2(FR-znU_B1E~aw1>=E`xKI%cmY(^B%ARm2Ql(F$ zg)O)M`AGHU(G$-V1V3w@(5?UMefb5ZwAugYN}qlN2iG88_zEy@76iYN@+EKkm@Z#M ze_95)UN~6I`{XMCM^S!N^NBQ96VE?=EC5mQD2rz>M5s>Wr2L0@;X34v^KG08XZufD zt^BJmWL46v9^*b9(!$%9UjxJ2ADQ0+!yBLKQtH;stAD1;j4894Kj*)LMqe(0e4l&% zuJ0Qd@?sc z^8E+x%R$V7Ca=Zdd+@6hu}{ta0w1B5xrTqE>3^lqV+mC>XXPoH|D(t^Q(x-G>G0dY zy+9|wq+ESU2k6YP5#{%SFYwPEhcV!ut-CHt%DT`~76wZneEq!Vg?q>ThRs6|?xzI) z5_(TqH>2FYBf{TWY$B0ABxFHIaKcst@AzM0a?+5{*TUX++hZrae)Nu>-yqvIclear(); } + + /* private */ function clear() + { + $this->mContentLoaded = false; + $this->mUser = $this->mCounter = -1; # Not loaded + $this->mRedirectedFrom = $this->mUserText = + $this->mTimestamp = $this->mComment = ""; + $this->mCountAdjustment = 0; + $this->mTouched = "19700101000000"; + } + + /* static */ function newFromID( $newid ) + { + global $wgOut, $wgTitle, $wgArticle; + $a = new Article(); + $n = Article::nameOf( $newid ); + + $wgTitle = Title::newFromDBkey( $n ); + $wgTitle->resetArticleID( $newid ); + + return $a; + } + + /* static */ function nameOf( $id ) + { + $sql = "SELECT cur_namespace,cur_title FROM cur WHERE " . + "cur_id={$id}"; + $res = wfQuery( $sql, "Article::nameOf" ); + if ( 0 == wfNumRows( $res ) ) { return NULL; } + + $s = wfFetchObject( $res ); + $n = Title::makeName( $s->cur_namespace, $s->cur_title ); + return $n; + } + + # Note that getContent/loadContent may follow redirects if + # not told otherwise, and so may cause a change to wgTitle. + + function getContent( $noredir = false ) + { + global $action,$wgTitle; # From query string + wfProfileIn( "Article::getContent" ); + + if ( 0 == $this->getID() ) { + if ( "edit" == $action ) { + + global $wgTitle; + return ""; # was "newarticletext", now moved above the box) + + + } + wfProfileOut(); + return wfMsg( "noarticletext" ); + } else { + $this->loadContent( $noredir ); + wfProfileOut(); + + if( + # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page + ( $wgTitle->getNamespace() == Namespace::getTalk( Namespace::getUser()) ) && + preg_match("/^\d{1,3}\.\d{1,3}.\d{1,3}\.\d{1,3}$/",$wgTitle->getText()) && + $action=="view" + ) + { + return $this->mContent . "\n" .wfMsg("anontalkpagetext"); } + else { + return $this->mContent; + } + } + } + + function loadContent( $noredir = false ) + { + global $wgOut, $wgTitle; + global $oldid, $redirect; # From query + + if ( $this->mContentLoaded ) return; + $fname = "Article::loadContent"; + + # Pre-fill content with error message so that if something + # fails we'll have something telling us what we intended. + + $t = $wgTitle->getPrefixedText(); + if ( $oldid ) { $t .= ",oldid={$oldid}"; } + if ( $redirect ) { $t .= ",redirect={$redirect}"; } + $this->mContent = str_replace( "$1", $t, wfMsg( "missingarticle" ) ); + + if ( ! $oldid ) { # Retrieve current version + $id = $this->getID(); + if ( 0 == $id ) return; + + $sql = "SELECT " . + "cur_text,cur_timestamp,cur_user,cur_counter,cur_restrictions,cur_touched " . + "FROM cur WHERE cur_id={$id}"; + $res = wfQuery( $sql, $fname ); + if ( 0 == wfNumRows( $res ) ) { return; } + + $s = wfFetchObject( $res ); + + # If we got a redirect, follow it (unless we've been told + # not to by either the function parameter or the query + + if ( ( "no" != $redirect ) && ( false == $noredir ) && + ( preg_match( "/^#redirect/i", $s->cur_text ) ) ) { + if ( preg_match( "/\\[\\[([^\\]\\|]+)[\\]\\|]/", + $s->cur_text, $m ) ) { + $rt = Title::newFromText( $m[1] ); + + # Gotta hand redirects to special pages differently: + # Fill the HTTP response "Location" header and ignore + # the rest of the page we're on. + + if ( $rt->getInterwiki() != "" ) { + $wgOut->redirect( $rt->getFullURL() ) ; + return; + } + if ( $rt->getNamespace() == Namespace::getSpecial() ) { + $wgOut->redirect( wfLocalUrl( + $rt->getPrefixedURL() ) ); + return; + } + $rid = $rt->getArticleID(); + if ( 0 != $rid ) { + $sql = "SELECT cur_text,cur_timestamp,cur_user," . + "cur_counter,cur_touched FROM cur WHERE cur_id={$rid}"; + $res = wfQuery( $sql, $fname ); + + if ( 0 != wfNumRows( $res ) ) { + $this->mRedirectedFrom = $wgTitle->getPrefixedText(); + $wgTitle = $rt; + $s = wfFetchObject( $res ); + } + } + } + } + $this->mContent = $s->cur_text; + $this->mUser = $s->cur_user; + $this->mCounter = $s->cur_counter; + $this->mTimestamp = $s->cur_timestamp; + $this->mTouched = $s->cur_touched; + $wgTitle->mRestrictions = explode( ",", trim( $s->cur_restrictions ) ); + $wgTitle->mRestrictionsLoaded = true; + wfFreeResult( $res ); + } else { # oldid set, retrieve historical version + $sql = "SELECT old_text,old_timestamp,old_user FROM old " . + "WHERE old_id={$oldid}"; + $res = wfQuery( $sql, $fname ); + if ( 0 == wfNumRows( $res ) ) { return; } + + $s = wfFetchObject( $res ); + $this->mContent = $s->old_text; + $this->mUser = $s->old_user; + $this->mCounter = 0; + $this->mTimestamp = $s->old_timestamp; + wfFreeResult( $res ); + } + $this->mContentLoaded = true; + } + + function getID() { global $wgTitle; return $wgTitle->getArticleID(); } + + function getCount() + { + if ( -1 == $this->mCounter ) { + $id = $this->getID(); + $this->mCounter = wfGetSQL( "cur", "cur_counter", "cur_id={$id}" ); + } + return $this->mCounter; + } + + # Would the given text make this article a "good" article (i.e., + # suitable for including in the article count)? + + function isCountable( $text ) + { + global $wgTitle, $wgUseCommaCount; + + if ( 0 != $wgTitle->getNamespace() ) { return 0; } + if ( preg_match( "/^#redirect/i", $text ) ) { return 0; } + $token = ($wgUseCommaCount ? "," : "[[" ); + if ( false === strstr( $text, $token ) ) { return 0; } + return 1; + } + + # Load the field related to the last edit time of the article. + # This isn't necessary for all uses, so it's only done if needed. + + /* private */ function loadLastEdit() + { + global $wgOut; + if ( -1 != $this->mUser ) return; + + $sql = "SELECT cur_user,cur_user_text,cur_timestamp," . + "cur_comment,cur_minor_edit FROM cur WHERE " . + "cur_id=" . $this->getID(); + $res = wfQuery( $sql, "Article::loadLastEdit" ); + + if ( wfNumRows( $res ) > 0 ) { + $s = wfFetchObject( $res ); + $this->mUser = $s->cur_user; + $this->mUserText = $s->cur_user_text; + $this->mTimestamp = $s->cur_timestamp; + $this->mComment = $s->cur_comment; + $this->mMinorEdit = $s->cur_minor_edit; + } + } + + function getTimestamp() + { + $this->loadLastEdit(); + return $this->mTimestamp; + } + + function getUser() + { + $this->loadLastEdit(); + return $this->mUser; + } + + function getUserText() + { + $this->loadLastEdit(); + return $this->mUserText; + } + + function getComment() + { + $this->loadLastEdit(); + return $this->mComment; + } + + function getMinorEdit() + { + $this->loadLastEdit(); + return $this->mMinorEdit; + } + + # This is the default action of the script: just view the page of + # the given title. + + function view() + { + global $wgUser, $wgOut, $wgTitle, $wgLang; + global $oldid, $diff; # From query + global $wgLinkCache; + wfProfileIn( "Article::view" ); + + $wgOut->setArticleFlag( true ); + $wgOut->setRobotpolicy( "index,follow" ); + + # If we got diff and oldid in the query, we want to see a + # diff page instead of the article. + + if ( isset( $diff ) ) { + $wgOut->setPageTitle( $wgTitle->getPrefixedText() ); + $de = new DifferenceEngine( $oldid, $diff ); + $de->showDiffPage(); + wfProfileOut(); + return; + } + $text = $this->getContent(); # May change wgTitle! + $wgOut->setPageTitle( $wgTitle->getPrefixedText() ); + $wgOut->setHTMLTitle( $wgTitle->getPrefixedText() . + " - " . wfMsg( "wikititlesuffix" ) ); + + # We're looking at an old revision + + if ( $oldid ) { + $this->setOldSubtitle(); + $wgOut->setRobotpolicy( "noindex,follow" ); + } + if ( "" != $this->mRedirectedFrom ) { + $sk = $wgUser->getSkin(); + $redir = $sk->makeKnownLink( $this->mRedirectedFrom, "", + "redirect=no" ); + $s = str_replace( "$1", $redir, wfMsg( "redirectedfrom" ) ); + $wgOut->setSubtitle( $s ); + } + $wgOut->checkLastModified( $this->mTouched ); + $wgLinkCache->preFill( $wgTitle ); + $wgOut->addWikiText( $text ); + + # If the article we've just shown is in the "Image" namespace, + # follow it with the history list and link list for the image + # it describes. + + if ( Namespace::getImage() == $wgTitle->getNamespace() ) { + $this->imageHistory(); + $this->imageLinks(); + } + $this->viewUpdates(); + wfProfileOut(); + } + + # This is the function that gets called for "action=edit". + + function edit() + { + global $wgOut, $wgUser, $wgTitle; + global $wpTextbox1, $wpSummary, $wpSave, $wpPreview; + global $wpMinoredit, $wpEdittime, $wpTextbox2; + + $fields = array( "wpTextbox1", "wpSummary", "wpTextbox2" ); + wfCleanFormFields( $fields ); + + if ( ! $wgTitle->userCanEdit() ) { + $this->view(); + return; + } + if ( $wgUser->isBlocked() ) { + $this->blockedIPpage(); + return; + } + if ( wfReadOnly() ) { + if( isset( $wpSave ) or isset( $wpPreview ) ) { + $this->editForm( "preview" ); + } else { + $wgOut->readOnlyPage(); + } + return; + } + if ( $_SERVER['REQUEST_METHOD'] != "POST" ) unset( $wpSave ); + if ( isset( $wpSave ) ) { + $this->editForm( "save" ); + } else if ( isset( $wpPreview ) ) { + $this->editForm( "preview" ); + } else { # First time through + $this->editForm( "initial" ); + } + } + + # Since there is only one text field on the edit form, + # pressing will cause the form to be submitted, but + # the submit button value won't appear in the query, so we + # Fake it here before going back to edit(). This is kind of + # ugly, but it helps some old URLs to still work. + + function submit() + { + global $wpSave, $wpPreview; + if ( ! isset( $wpPreview ) ) { $wpSave = 1; } + + $this->edit(); + } + + # The edit form is self-submitting, so that when things like + # preview and edit conflicts occur, we get the same form back + # with the extra stuff added. Only when the final submission + # is made and all is well do we actually save and redirect to + # the newly-edited page. + + function editForm( $formtype ) + { + global $wgOut, $wgUser, $wgTitle; + global $wpTextbox1, $wpSummary, $wpWatchthis; + global $wpSave, $wpPreview; + global $wpMinoredit, $wpEdittime, $wpTextbox2; + global $oldid, $redirect; + global $wgLang; + + $sk = $wgUser->getSkin(); + $isConflict = false; + $wpTextbox1 = rtrim ( $wpTextbox1 ) ; # To avoid text getting longer on each preview + + if(!$wgTitle->getArticleID()) { # new article + + $wgOut->addWikiText(wfmsg("newarticletext")); + + } + + # Attempt submission here. This will check for edit conflicts, + # and redundantly check for locked database, blocked IPs, etc. + # that edit() already checked just in case someone tries to sneak + # in the back door with a hand-edited submission URL. + + if ( "save" == $formtype ) { + if ( $wgUser->isBlocked() ) { + $this->blockedIPpage(); + return; + } + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + # If article is new, insert it. + + $aid = $wgTitle->getArticleID(); + if ( 0 == $aid ) { + # we need to strip Windoze linebreaks because some browsers + # append them and the string comparison fails + if ( ( "" == $wpTextbox1 ) || + ( wfMsg( "newarticletext" ) == rtrim( preg_replace("/\r/","",$wpTextbox1) ) ) ) { + $wgOut->redirect( wfLocalUrl( + $wgTitle->getPrefixedURL() ) ); + return; + } + $this->mCountAdjustment = $this->isCountable( $wpTextbox1 ); + $this->insertNewArticle( $wpTextbox1, $wpSummary, $wpMinoredit, $wpWatchthis ); + return; + } + # Article exists. Check for edit conflict. + + $this->clear(); # Force reload of dates, etc. + if ( $this->getTimestamp() != $wpEdittime ) { $isConflict = true; } + $u = $wgUser->getID(); + + # Supress edit conflict with self + + if ( ( 0 != $u ) && ( $this->getUser() == $u ) ) { + $isConflict = false; + } + if ( ! $isConflict ) { + # All's well: update the article here + $this->updateArticle( $wpTextbox1, $wpSummary, $wpMinoredit, $wpWatchthis ); + return; + } + } + # First time through: get contents, set time for conflict + # checking, etc. + + if ( "initial" == $formtype ) { + $wpEdittime = $this->getTimestamp(); + $wpTextbox1 = $this->getContent(); + $wpSummary = ""; + } + $wgOut->setRobotpolicy( "noindex,nofollow" ); + $wgOut->setArticleFlag( false ); + + if ( $isConflict ) { + $s = str_replace( "$1", $wgTitle->getPrefixedText(), + wfMsg( "editconflict" ) ); + $wgOut->setPageTitle( $s ); + $wgOut->addHTML( wfMsg( "explainconflict" ) ); + + $wpTextbox2 = $wpTextbox1; + $wpTextbox1 = $this->getContent(); + $wpEdittime = $this->getTimestamp(); + } else { + $s = str_replace( "$1", $wgTitle->getPrefixedText(), + wfMsg( "editing" ) ); + $wgOut->setPageTitle( $s ); + if ( $oldid ) { + $this->setOldSubtitle(); + $wgOut->addHTML( wfMsg( "editingold" ) ); + } + } + + if( wfReadOnly() ) { + $wgOut->addHTML( "" . + wfMsg( "readonlywarning" ) . + "" ); + } + if( $wgTitle->isProtected() ) { + $wgOut->addHTML( "" . wfMsg( "protectedpagewarning" ) . + "
\n" ); + } + + $kblength = (int)(strlen( $wpTextbox1 ) / 1024); + if( $kblength > 29 ) { + $wgOut->addHTML( "" . + str_replace( '$1', $kblength , wfMsg( "longpagewarning" ) ) + . "" ); + } + + $rows = $wgUser->getOption( "rows" ); + $cols = $wgUser->getOption( "cols" ); + + $ew = $wgUser->getOption( "editwidth" ); + if ( $ew ) $ew = " style=\"width:100%\""; + else $ew = "" ; + + $q = "action=submit"; + if ( "no" == $redirect ) { $q .= "&redirect=no"; } + $action = wfEscapeHTML( wfLocalUrl( $wgTitle->getPrefixedURL(), $q ) ); + + $summary = wfMsg( "summary" ); + $minor = wfMsg( "minoredit" ); + $watchthis = wfMsg ("watchthis"); + $save = wfMsg( "savearticle" ); + $prev = wfMsg( "showpreview" ); + + $cancel = $sk->makeKnownLink( $wgTitle->getPrefixedURL(), + wfMsg( "cancel" ) ); + $edithelp = $sk->makeKnownLink( wfMsg( "edithelppage" ), + wfMsg( "edithelp" ) ); + $copywarn = str_replace( "$1", $sk->makeKnownLink( + wfMsg( "copyrightpage" ) ), wfMsg( "copyrightwarning" ) ); + + $wpTextbox1 = wfEscapeHTML( $wpTextbox1 ); + $wpTextbox2 = wfEscapeHTML( $wpTextbox2 ); + $wpSummary = wfEscapeHTML( $wpSummary ); + + // activate checkboxes if user wants them to be always active + if (!$wpPreview && $wgUser->getOption("watchdefault")) $wpWatchthis=1; + if (!$wpPreview && $wgUser->getOption("minordefault")) $wpMinoredit=1; + + // activate checkbox also if user is already watching the page, + // require wpWatchthis to be unset so that second condition is not + // checked unnecessarily + if (!$wpWatchthis && !$wpPreview && $wgTitle->userIsWatching()) $wpWatchthis=1; + + if ( 0 != $wgUser->getID() ) { + $checkboxhtml= + "{$minor}". + "{$watchthis}
"; + + } else { + $checkboxhtml=""; + } + + + if ( "preview" == $formtype) { + + $previewhead="

" . wfMsg( "preview" ) . "

\n

" . + wfMsg( "note" ) . wfMsg( "previewnote" ) . "

\n"; + if ( $isConflict ) { + $previewhead.="

" . wfMsg( "previewconflict" ) . + "

\n"; + } + $previewtext = wfUnescapeHTML( $wpTextbox1 ); + + if($wgUser->getOption("previewontop")) { + $wgOut->addHTML($previewhead); + $wgOut->addWikiText( $this->preSaveTransform( $previewtext ) ."\n\n"); + } + } + $wgOut->addHTML( " +
+
+{$summary}:
+{$checkboxhtml} + + +{$cancel} | {$edithelp} +

{$copywarn} +\n" ); + + if ( $isConflict ) { + $wgOut->addHTML( "

" . wfMsg( "yourdiff" ) . "

\n" ); + DifferenceEngine::showDiff( $wpTextbox2, $wpTextbox1, + wfMsg( "yourtext" ), wfMsg( "storedversion" ) ); + + $wgOut->addHTML( "

" . wfMsg( "yourtext" ) . "

+" ); + } + $wgOut->addHTML( "
\n" ); + if($formtype =="preview" && !$wgUser->getOption("previewontop")) { + $wgOut->addHTML($previewhead); + $wgOut->addWikiText( $this->preSaveTransform( $previewtext ) ); + } + + } + + # Theoretically we could defer these whole insert and update + # functions for after display, but that's taking a big leap + # of faith, and we want to be able to report database + # errors at some point. + + /* private */ function insertNewArticle( $text, $summary, $isminor, $watchthis ) + { + global $wgOut, $wgUser, $wgTitle, $wgLinkCache; + $fname = "Article::insertNewArticle"; + + $ns = $wgTitle->getNamespace(); + $ttl = $wgTitle->getDBkey(); + $text = $this->preSaveTransform( $text ); + if ( preg_match( "/^#redirect/i", $text ) ) { $redir = 1; } + else { $redir = 0; } + + $now = wfTimestampNow(); + $won = wfInvertTimestamp( $now ); + $sql = "INSERT INTO cur (cur_namespace,cur_title,cur_text," . + "cur_comment,cur_user,cur_timestamp,cur_minor_edit,cur_counter," . + "cur_restrictions,cur_user_text,cur_is_redirect," . + "cur_is_new,cur_random,cur_touched,inverse_timestamp) VALUES ({$ns},'" . wfStrencode( $ttl ) . "', '" . + wfStrencode( $text ) . "', '" . + wfStrencode( $summary ) . "', '" . + $wgUser->getID() . "', '{$now}', " . + ( $isminor ? 1 : 0 ) . ", 0, '', '" . + wfStrencode( $wgUser->getName() ) . "', $redir, 1, RAND(), '{$now}', '{$won}')"; + $res = wfQuery( $sql, $fname ); + + $newid = wfInsertId(); + $wgTitle->resetArticleID( $newid ); + + $sql = "INSERT INTO recentchanges (rc_timestamp,rc_cur_time," . + "rc_namespace,rc_title,rc_new,rc_minor,rc_cur_id,rc_user," . + "rc_user_text,rc_comment,rc_this_oldid,rc_last_oldid,rc_bot) VALUES (" . + "'{$now}','{$now}',{$ns},'" . wfStrencode( $ttl ) . "',1," . + ( $isminor ? 1 : 0 ) . ",{$newid}," . $wgUser->getID() . ",'" . + wfStrencode( $wgUser->getName() ) . "','" . + wfStrencode( $summary ) . "',0,0," . + ( $wgUser->isBot() ? 1 : 0 ) . ")"; + wfQuery( $sql, $fname ); + if ($watchthis) { + if(!$wgTitle->userIsWatching()) $this->watch(); + } else { + if ( $wgTitle->userIsWatching() ) { + $this->unwatch(); + } + } + + $this->showArticle( $text, wfMsg( "newarticle" ) ); + } + + function updateArticle( $text, $summary, $minor, $watchthis ) + { + global $wgOut, $wgUser, $wgTitle, $wgLinkCache; + global $wgDBtransactions; + $fname = "Article::updateArticle"; + + if ( $this->mMinorEdit ) { $me1 = 1; } else { $me1 = 0; } + if ( $minor ) { $me2 = 1; } else { $me2 = 0; } + if ( preg_match( "/^(#redirect[^\\n]+)/i", $text, $m ) ) { + $redir = 1; + $text = $m[1] . "\n"; # Remove all content but redirect + } + else { $redir = 0; } + $this->loadLastEdit(); + + $text = $this->preSaveTransform( $text ); + + # Update article, but only if changed. + + if( $wgDBtransactions ) { + $sql = "BEGIN"; + wfQuery( $sql ); + } + $oldtext = $this->getContent( true ); + + if ( 0 != strcmp( $text, $oldtext ) ) { + $this->mCountAdjustment = $this->isCountable( $text ) + - $this->isCountable( $oldtext ); + + $sql = "INSERT INTO old (old_namespace,old_title,old_text," . + "old_comment,old_user,old_user_text,old_timestamp," . + "old_minor_edit,inverse_timestamp) VALUES (" . + $wgTitle->getNamespace() . ", '" . + wfStrencode( $wgTitle->getDBkey() ) . "', '" . + wfStrencode( $oldtext ) . "', '" . + wfStrencode( $this->getComment() ) . "', " . + $this->getUser() . ", '" . + wfStrencode( $this->getUserText() ) . "', '" . + $this->getTimestamp() . "', " . $me1 . ", '" . + wfInvertTimestamp( $this->getTimestamp() ) . "')"; + $res = wfQuery( $sql, $fname ); + $oldid = wfInsertID( $res ); + + $now = wfTimestampNow(); + $won = wfInvertTimestamp( $now ); + $sql = "UPDATE cur SET cur_text='" . wfStrencode( $text ) . + "',cur_comment='" . wfStrencode( $summary ) . + "',cur_minor_edit={$me2}, cur_user=" . $wgUser->getID() . + ",cur_timestamp='{$now}',cur_user_text='" . + wfStrencode( $wgUser->getName() ) . + "',cur_is_redirect={$redir}, cur_is_new=0, cur_touched='{$now}', inverse_timestamp='{$won}' " . + "WHERE cur_id=" . $this->getID(); + wfQuery( $sql, $fname ); + + $sql = "INSERT INTO recentchanges (rc_timestamp,rc_cur_time," . + "rc_namespace,rc_title,rc_new,rc_minor,rc_bot,rc_cur_id,rc_user," . + "rc_user_text,rc_comment,rc_this_oldid,rc_last_oldid) VALUES (" . + "'{$now}','{$now}'," . $wgTitle->getNamespace() . ",'" . + wfStrencode( $wgTitle->getDBkey() ) . "',0,{$me2}," . + ( $wgUser->isBot() ? 1 : 0 ) . "," . + $this->getID() . "," . $wgUser->getID() . ",'" . + wfStrencode( $wgUser->getName() ) . "','" . + wfStrencode( $summary ) . "',0,{$oldid})"; + wfQuery( $sql, $fname ); + + $sql = "UPDATE recentchanges SET rc_this_oldid={$oldid} " . + "WHERE rc_namespace=" . $wgTitle->getNamespace() . " AND " . + "rc_title='" . wfStrencode( $wgTitle->getDBkey() ) . "' AND " . + "rc_timestamp='" . $this->getTimestamp() . "'"; + wfQuery( $sql, $fname ); + + $sql = "UPDATE recentchanges SET rc_cur_time='{$now}' " . + "WHERE rc_cur_id=" . $this->getID(); + wfQuery( $sql, $fname ); + } + if( $wgDBtransactions ) { + $sql = "COMMIT"; + wfQuery( $sql ); + } + + if ($watchthis) { + if (!$wgTitle->userIsWatching()) $this->watch(); + } else { + if ( $wgTitle->userIsWatching() ) { + $this->unwatch(); + } + } + + $this->showArticle( $text, wfMsg( "updated" ) ); + } + + # After we've either updated or inserted the article, update + # the link tables and redirect to the new page. + + function showArticle( $text, $subtitle ) + { + global $wgOut, $wgTitle, $wgUser, $wgLinkCache; + + $wgLinkCache = new LinkCache(); + $wgOut->addWikiText( $text ); # Just to update links + + $this->editUpdates( $text ); + if( preg_match( "/^#redirect/i", $text ) ) + $r = "redirect=no"; + else + $r = ""; + $wgOut->redirect( wfLocalUrl( $wgTitle->getPrefixedURL(), $r ) ); + } + + # If the page we've just displayed is in the "Image" namespace, + # we follow it with an upload history of the image and its usage. + + function imageHistory() + { + global $wgUser, $wgOut, $wgLang, $wgTitle; + $fname = "Article::imageHistory"; + + $sql = "SELECT img_size,img_description,img_user," . + "img_user_text,img_timestamp FROM image WHERE " . + "img_name='" . wfStrencode( $wgTitle->getDBkey() ) . "'"; + $res = wfQuery( $sql, $fname ); + + if ( 0 == wfNumRows( $res ) ) { return; } + + $sk = $wgUser->getSkin(); + $s = $sk->beginImageHistoryList(); + + $line = wfFetchObject( $res ); + $s .= $sk->imageHistoryLine( true, $line->img_timestamp, + $wgTitle->getText(), $line->img_user, + $line->img_user_text, $line->img_size, $line->img_description ); + + $sql = "SELECT oi_size,oi_description,oi_user," . + "oi_user_text,oi_timestamp,oi_archive_name FROM oldimage WHERE " . + "oi_name='" . wfStrencode( $wgTitle->getDBkey() ) . "' " . + "ORDER BY oi_timestamp DESC"; + $res = wfQuery( $sql, $fname ); + + while ( $line = wfFetchObject( $res ) ) { + $s .= $sk->imageHistoryLine( false, $line->oi_timestamp, + $line->oi_archive_name, $line->oi_user, + $line->oi_user_text, $line->oi_size, $line->oi_description ); + } + $s .= $sk->endImageHistoryList(); + $wgOut->addHTML( $s ); + } + + function imageLinks() + { + global $wgUser, $wgOut, $wgTitle; + + $wgOut->addHTML( "

" . wfMsg( "imagelinks" ) . "

\n" ); + + $sql = "SELECT il_from FROM imagelinks WHERE il_to='" . + wfStrencode( $wgTitle->getDBkey() ) . "'"; + $res = wfQuery( $sql, "Article::imageLinks" ); + + if ( 0 == wfNumRows( $res ) ) { + $wgOut->addHtml( "

" . wfMsg( "nolinkstoimage" ) . "\n" ); + return; + } + $wgOut->addHTML( "

" . wfMsg( "linkstoimage" ) . "\n

    " ); + + $sk = $wgUser->getSkin(); + while ( $s = wfFetchObject( $res ) ) { + $name = $s->il_from; + $link = $sk->makeKnownLink( $name, "" ); + $wgOut->addHTML( "
  • {$link}
  • \n" ); + } + $wgOut->addHTML( "
\n" ); + } + + # Add this page to my watchlist + + function watch() + { + global $wgUser, $wgTitle, $wgOut, $wgLang; + global $wgDeferredUpdateList; + + if ( 0 == $wgUser->getID() ) { + $wgOut->errorpage( "watchnologin", "watchnologintext" ); + return; + } + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + $wgUser->addWatch( $wgTitle ); + + $wgOut->setPagetitle( wfMsg( "addedwatch" ) ); + $wgOut->setRobotpolicy( "noindex,follow" ); + + $sk = $wgUser->getSkin() ; + $link = $sk->makeKnownLink ( $wgTitle->getPrefixedText() ) ; + + $text = str_replace( "$1", $link , + wfMsg( "addedwatchtext" ) ); + $wgOut->addHTML( $text ); + + $up = new UserUpdate(); + array_push( $wgDeferredUpdateList, $up ); + + $wgOut->returnToMain( false ); + } + + function unwatch() + { + global $wgUser, $wgTitle, $wgOut, $wgLang; + global $wgDeferredUpdateList; + + if ( 0 == $wgUser->getID() ) { + $wgOut->errorpage( "watchnologin", "watchnologintext" ); + return; + } + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + $wgUser->removeWatch( $wgTitle ); + + $wgOut->setPagetitle( wfMsg( "removedwatch" ) ); + $wgOut->setRobotpolicy( "noindex,follow" ); + + $sk = $wgUser->getSkin() ; + $link = $sk->makeKnownLink ( $wgTitle->getPrefixedText() ) ; + + $text = str_replace( "$1", $link , + wfMsg( "removedwatchtext" ) ); + $wgOut->addHTML( $text ); + + $up = new UserUpdate(); + array_push( $wgDeferredUpdateList, $up ); + + $wgOut->returnToMain( false ); + } + + # This shares a lot of issues (and code) with Recent Changes + + function history() + { + global $wgUser, $wgOut, $wgLang, $wgTitle, $offset, $limit; + + # If page hasn't changed, client can cache this + + $wgOut->checkLastModified( $this->getTimestamp() ); + wfProfileIn( "Article::history" ); + + $wgOut->setPageTitle( $wgTitle->getPRefixedText() ); + $wgOut->setSubtitle( wfMsg( "revhistory" ) ); + $wgOut->setArticleFlag( false ); + $wgOut->setRobotpolicy( "noindex,nofollow" ); + + if( $wgTitle->getArticleID() == 0 ) { + $wgOut->addHTML( wfMsg( "nohistory" ) ); + wfProfileOut(); + return; + } + + $offset = (int)$offset; + $limit = (int)$limit; + if( $limit == 0 ) $limit = 50; + $namespace = $wgTitle->getNamespace(); + $title = $wgTitle->getText(); + $sql = "SELECT old_id,old_user," . + "old_comment,old_user_text,old_timestamp,old_minor_edit ". + "FROM old USE INDEX (name_title_timestamp) " . + "WHERE old_namespace={$namespace} AND " . + "old_title='" . wfStrencode( $wgTitle->getDBkey() ) . "' " . + "ORDER BY inverse_timestamp LIMIT $offset, $limit"; + $res = wfQuery( $sql, "Article::history" ); + + $revs = wfNumRows( $res ); + if( $wgTitle->getArticleID() == 0 ) { + $wgOut->addHTML( wfMsg( "nohistory" ) ); + wfProfileOut(); + return; + } + + $sk = $wgUser->getSkin(); + $numbar = wfViewPrevNext( + $offset, $limit, + $wgTitle->getPrefixedText(), + "action=history" ); + $s = $numbar; + $s .= $sk->beginHistoryList(); + + if($offset == 0 ) + $s .= $sk->historyLine( $this->getTimestamp(), $this->getUser(), + $this->getUserText(), $namespace, + $title, 0, $this->getComment(), + ( $this->getMinorEdit() > 0 ) ); + + $revs = wfNumRows( $res ); + while ( $line = wfFetchObject( $res ) ) { + $s .= $sk->historyLine( $line->old_timestamp, $line->old_user, + $line->old_user_text, $namespace, + $title, $line->old_id, + $line->old_comment, ( $line->old_minor_edit > 0 ) ); + } + $s .= $sk->endHistoryList(); + $s .= $numbar; + $wgOut->addHTML( $s ); + wfProfileOut(); + } + + function protect() + { + global $wgUser, $wgOut, $wgTitle; + + if ( ! $wgUser->isSysop() ) { + $wgOut->sysopRequired(); + return; + } + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + $id = $wgTitle->getArticleID(); + if ( 0 == $id ) { + $wgOut->fatalEror( wfMsg( "badarticleerror" ) ); + return; + } + $sql = "UPDATE cur SET cur_touched='" . wfTimestampNow() . "'," . + "cur_restrictions='sysop' WHERE cur_id={$id}"; + wfQuery( $sql, "Article::protect" ); + + $wgOut->redirect( wfLocalUrl( $wgTitle->getPrefixedURL() ) ); + } + + function unprotect() + { + global $wgUser, $wgOut, $wgTitle; + + if ( ! $wgUser->isSysop() ) { + $wgOut->sysopRequired(); + return; + } + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + $id = $wgTitle->getArticleID(); + if ( 0 == $id ) { + $wgOut->fatalEror( wfMsg( "badarticleerror" ) ); + return; + } + $sql = "UPDATE cur SET cur_touched='" . wfTimestampNow() . "'," . + "cur_restrictions='' WHERE cur_id={$id}"; + wfQuery( $sql, "Article::unprotect" ); + + $wgOut->redirect( wfLocalUrl( $wgTitle->getPrefixedURL() ) ); + } + + function delete() + { + global $wgUser, $wgOut, $wgTitle; + global $wpConfirm, $wpReason, $image, $oldimage; + + # Anybody can delete old revisions of images; only sysops + # can delete articles and current images + + if ( ( ! $oldimage ) && ( ! $wgUser->isSysop() ) ) { + $wgOut->sysopRequired(); + return; + } + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + + # Better double-check that it hasn't been deleted yet! + $wgOut->setPagetitle( wfMsg( "confirmdelete" ) ); + if ( $image ) { + if ( "" == trim( $image ) ) { + $wgOut->fatalError( wfMsg( "cannotdelete" ) ); + return; + } + $sub = str_replace( "$1", $image, wfMsg( "deletesub" ) ); + } else { + if ( ( "" == trim( $wgTitle->getText() ) ) + or ( $wgTitle->getArticleId() == 0 ) ) { + $wgOut->fatalError( wfMsg( "cannotdelete" ) ); + return; + } + $sub = str_replace( "$1", $wgTitle->getPrefixedText(), + wfMsg( "deletesub" ) ); + } + + # Likewise, deleting old images doesn't require confirmation + if ( $oldimage || 1 == $wpConfirm ) { + $this->doDelete(); + return; + } + + $wgOut->setSubtitle( $sub ); + $wgOut->setRobotpolicy( "noindex,nofollow" ); + $wgOut->addWikiText( wfMsg( "confirmdeletetext" ) ); + + $t = $wgTitle->getPrefixedURL(); + $q = "action=delete"; + + if ( $image ) { + $q .= "&image={$image}"; + } else if ( $oldimage ) { + $q .= "&oldimage={$oldimage}"; + } else { + $q .= "&title={$t}"; + } + $formaction = wfEscapeHTML( wfLocalUrl( "", $q ) ); + $confirm = wfMsg( "confirm" ); + $check = wfMsg( "confirmcheck" ); + $delcom = wfMsg( "deletecomment" ); + + $wgOut->addHTML( " +
+ + +
+{$delcom}: + +
 
+ +{$check}
  + +
\n" ); + + $wgOut->returnToMain( false ); + } + + function doDelete() + { + global $wgOut, $wgTitle, $wgUser, $wgLang; + global $image, $oldimage, $wpReason; + $fname = "Article::doDelete"; + + if ( $image ) { + $dest = wfImageDir( $image ); + $archive = wfImageDir( $image ); + if ( ! unlink( "{$dest}/{$image}" ) ) { + $wgOut->fileDeleteError( "{$dest}/{$image}" ); + return; + } + $sql = "DELETE FROM image WHERE img_name='" . + wfStrencode( $image ) . "'"; + wfQuery( $sql, $fname ); + + $sql = "SELECT oi_archive_name FROM oldimage WHERE oi_name='" . + wfStrencode( $image ) . "'"; + $res = wfQuery( $sql, $fname ); + + while ( $s = wfFetchObject( $res ) ) { + $this->doDeleteOldImage( $s->oi_archive_name ); + } + $sql = "DELETE FROM oldimage WHERE oi_name='" . + wfStrencode( $image ) . "'"; + wfQuery( $sql, $fname ); + + # Image itself is now gone, and database is cleaned. + # Now we remove the image description page. + + $nt = Title::newFromText( $wgLang->getNsText( Namespace::getImage() ) . ":" . $image ); + $this->doDeleteArticle( $nt ); + + $deleted = $image; + } else if ( $oldimage ) { + $this->doDeleteOldImage( $oldimage ); + $sql = "DELETE FROM oldimage WHERE oi_archive_name='" . + wfStrencode( $oldimage ) . "'"; + wfQuery( $sql, $fname ); + + $deleted = $oldimage; + } else { + $this->doDeleteArticle( $wgTitle ); + $deleted = $wgTitle->getPrefixedText(); + } + $wgOut->setPagetitle( wfMsg( "actioncomplete" ) ); + $wgOut->setRobotpolicy( "noindex,nofollow" ); + + $sk = $wgUser->getSkin(); + $loglink = $sk->makeKnownLink( $wgLang->getNsText( + Namespace::getWikipedia() ) . + ":" . wfMsg( "dellogpage" ), wfMsg( "deletionlog" ) ); + + $text = str_replace( "$1" , $deleted, wfMsg( "deletedtext" ) ); + $text = str_replace( "$2", $loglink, $text ); + + $wgOut->addHTML( "

" . $text ); + $wgOut->returnToMain( false ); + } + + function doDeleteOldImage( $oldimage ) + { + global $wgOut; + + $name = substr( $oldimage, 15 ); + $archive = wfImageArchiveDir( $name ); + if ( ! unlink( "{$archive}/{$oldimage}" ) ) { + $wgOut->fileDeleteError( "{$archive}/{$oldimage}" ); + } + } + + function doDeleteArticle( $title ) + { + global $wgUser, $wgOut, $wgLang, $wpReason, $wgTitle, $wgDeferredUpdateList; + + $fname = "Article::doDeleteArticle"; + $ns = $title->getNamespace(); + $t = wfStrencode( $title->getDBkey() ); + $id = $title->getArticleID(); + + if ( "" == $t ) { + $wgOut->fatalError( wfMsg( "cannotdelete" ) ); + return; + } + + $u = new SiteStatsUpdate( 0, 1, -$this->isCountable( $this->getContent( true ) ) ); + array_push( $wgDeferredUpdateList, $u ); + + # Move article and history to the "archive" table + $sql = "INSERT INTO archive (ar_namespace,ar_title,ar_text," . + "ar_comment,ar_user,ar_user_text,ar_timestamp,ar_minor_edit," . + "ar_flags) SELECT cur_namespace,cur_title,cur_text,cur_comment," . + "cur_user,cur_user_text,cur_timestamp,cur_minor_edit,0 FROM cur " . + "WHERE cur_namespace={$ns} AND cur_title='{$t}'"; + wfQuery( $sql, $fname ); + + $sql = "INSERT INTO archive (ar_namespace,ar_title,ar_text," . + "ar_comment,ar_user,ar_user_text,ar_timestamp,ar_minor_edit," . + "ar_flags) SELECT old_namespace,old_title,old_text,old_comment," . + "old_user,old_user_text,old_timestamp,old_minor_edit,old_flags " . + "FROM old WHERE old_namespace={$ns} AND old_title='{$t}'"; + wfQuery( $sql, $fname ); + + # Now that it's safely backed up, delete it + + $sql = "DELETE FROM cur WHERE cur_namespace={$ns} AND " . + "cur_title='{$t}'"; + wfQuery( $sql, $fname ); + + $sql = "DELETE FROM old WHERE old_namespace={$ns} AND " . + "old_title='{$t}'"; + wfQuery( $sql, $fname ); + + $sql = "DELETE FROM recentchanges WHERE rc_namespace={$ns} AND " . + "rc_title='{$t}'"; + wfQuery( $sql, $fname ); + + # Finally, clean up the link tables + + if ( 0 != $id ) { + $t = wfStrencode( $title->getPrefixedDBkey() ); + $sql = "SELECT l_from FROM links WHERE l_to={$id}"; + $res = wfQuery( $sql, $fname ); + + $sql = "INSERT INTO brokenlinks (bl_from,bl_to) VALUES "; + $now = wfTimestampNow(); + $sql2 = "UPDATE cur SET cur_touched='{$now}' WHERE cur_id IN ("; + $first = true; + + while ( $s = wfFetchObject( $res ) ) { + $nt = Title::newFromDBkey( $s->l_from ); + $lid = $nt->getArticleID(); + + if ( ! $first ) { $sql .= ","; $sql2 .= ","; } + $first = false; + $sql .= "({$lid},'{$t}')"; + $sql2 .= "{$lid}"; + } + $sql2 .= ")"; + if ( ! $first ) { + wfQuery( $sql, $fname ); + wfQuery( $sql2, $fname ); + } + wfFreeResult( $res ); + + $sql = "DELETE FROM links WHERE l_to={$id}"; + wfQuery( $sql, $fname ); + + $sql = "DELETE FROM links WHERE l_from='{$t}'"; + wfQuery( $sql, $fname ); + + $sql = "DELETE FROM imagelinks WHERE il_from='{$t}'"; + wfQuery( $sql, $fname ); + + $sql = "DELETE FROM brokenlinks WHERE bl_from={$id}"; + wfQuery( $sql, $fname ); + } + + $log = new LogPage( wfMsg( "dellogpage" ), wfMsg( "dellogpagetext" ) ); + $art = $title->getPrefixedText(); + $wpReason = wfCleanQueryVar( $wpReason ); + $log->addEntry( str_replace( "$1", $art, wfMsg( "deletedarticle" ) ), $wpReason ); + + # Clear the cached article id so the interface doesn't act like we exist + $wgTitle->resetArticleID( 0 ); + $wgTitle->mArticleID = 0; + } + + function revert() + { + global $wgOut; + global $oldimage; + + if ( strlen( $oldimage ) < 16 ) { + $wgOut->unexpectedValueError( "oldimage", $oldimage ); + return; + } + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + $name = substr( $oldimage, 15 ); + + $dest = wfImageDir( $name ); + $archive = wfImageArchiveDir( $name ); + $curfile = "{$dest}/{$name}"; + + if ( ! is_file( $curfile ) ) { + $wgOut->fileNotFoundError( $curfile ); + return; + } + $oldver = wfTimestampNow() . "!{$name}"; + $size = wfGetSQL( "oldimage", "oi_size", "oi_archive_name='" . + wfStrencode( $oldimage ) . "'" ); + + if ( ! rename( $curfile, "${archive}/{$oldver}" ) ) { + $wgOut->fileRenameError( $curfile, "${archive}/{$oldver}" ); + return; + } + if ( ! copy( "{$archive}/{$oldimage}", $curfile ) ) { + $wgOut->fileCopyError( "${archive}/{$oldimage}", $curfile ); + } + wfRecordUpload( $name, $oldver, $size, wfMsg( "reverted" ) ); + + $wgOut->setPagetitle( wfMsg( "actioncomplete" ) ); + $wgOut->setRobotpolicy( "noindex,nofollow" ); + $wgOut->addHTML( wfMsg( "imagereverted" ) ); + $wgOut->returnToMain( false ); + } + + function rollback() + { + global $wgUser, $wgTitle, $wgLang, $wgOut; + + if ( ! $wgUser->isSysop() ) { + $wgOut->sysopRequired(); + return; + } + + # Replace all this user's current edits with the next one down + $tt = wfStrencode( $wgTitle->getDBKey() ); + $n = $wgTitle->getNamespace(); + + # Get the last editor + $sql = "SELECT cur_id,cur_user,cur_user_text FROM cur WHERE cur_title='{$tt}' AND cur_namespace={$n}"; + $res = wfQuery( $sql ); + if( ($x = wfNumRows( $res )) != 1 ) { + # Something wrong + $wgOut->addHTML( wfMsg( "notanarticle" ) ); + return; + } + $s = wfFetchObject( $res ); + $ut = wfStrencode( $s->cur_user_text ); + $uid = $s->cur_user; + $pid = $s->cur_id; + + # Get the last edit not by this guy + $sql = "SELECT old_text,old_user,old_user_text + FROM old USE INDEX (name_title_timestamp) + WHERE old_namespace={$n} AND old_title='{$tt}' + AND (old_user <> {$uid} OR old_user_text <> '{$ut}') + ORDER BY inverse_timestamp LIMIT 1"; + $res = wfQuery( $sql ); + if( wfNumRows( $res ) != 1 ) { + # Something wrong + $wgOut->addHTML( wfMsg( "cantrollback" ) ); + return; + } + $s = wfFetchObject( $res ); + + # Save it! + $newcomment = str_replace( "$1", $s->old_user_text, wfMsg( "revertpage" ) ); + $wgOut->setPagetitle( wfMsg( "actioncomplete" ) ); + $wgOut->setRobotpolicy( "noindex,nofollow" ); + $wgOut->addHTML( "

" . $newcomment . "

\n
\n" ); + $this->updateArticle( $s->old_text, $newcomment, 1, $wgTitle->userIsWatching() ); + + $wgOut->returnToMain( false ); + } + + + # Do standard deferred updates after page view + + /* private */ function viewUpdates() + { + global $wgDeferredUpdateList, $wgTitle; + + if ( 0 != $this->getID() ) { + $u = new ViewCountUpdate( $this->getID() ); + array_push( $wgDeferredUpdateList, $u ); + $u = new SiteStatsUpdate( 1, 0, 0 ); + array_push( $wgDeferredUpdateList, $u ); + + $u = new UserTalkUpdate( 0, $wgTitle->getNamespace(), + $wgTitle->getDBkey() ); + array_push( $wgDeferredUpdateList, $u ); + } + } + + # Do standard deferred updates after page edit. + # Every 1000th edit, prune the recent changes table. + + /* private */ function editUpdates( $text ) + { + global $wgDeferredUpdateList, $wgTitle; + + wfSeedRandom(); + if ( 0 == mt_rand( 0, 999 ) ) { + $cutoff = wfUnix2Timestamp( time() - ( 7 * 86400 ) ); + $sql = "DELETE FROM recentchanges WHERE rc_timestamp < '{$cutoff}'"; + wfQuery( $sql ); + } + $id = $this->getID(); + $title = $wgTitle->getPrefixedDBkey(); + $adj = $this->mCountAdjustment; + + if ( 0 != $id ) { + $u = new LinksUpdate( $id, $title ); + array_push( $wgDeferredUpdateList, $u ); + $u = new SiteStatsUpdate( 0, 1, $adj ); + array_push( $wgDeferredUpdateList, $u ); + $u = new SearchUpdate( $id, $title, $text ); + array_push( $wgDeferredUpdateList, $u ); + + $u = new UserTalkUpdate( 1, $wgTitle->getNamespace(), + $wgTitle->getDBkey() ); + array_push( $wgDeferredUpdateList, $u ); + } + } + + /* private */ function setOldSubtitle() + { + global $wgLang, $wgOut; + + $td = $wgLang->timeanddate( $this->mTimestamp, true ); + $r = str_replace( "$1", "{$td}", wfMsg( "revisionasof" ) ); + $wgOut->setSubtitle( "({$r})" ); + } + + function blockedIPpage() + { + global $wgOut, $wgUser, $wgLang; + + $wgOut->setPageTitle( wfMsg( "blockedtitle" ) ); + $wgOut->setRobotpolicy( "noindex,nofollow" ); + $wgOut->setArticleFlag( false ); + + $id = $wgUser->blockedBy(); + $reason = $wgUser->blockedFor(); + + $name = User::whoIs( $id ); + $link = "[[" . $wgLang->getNsText( Namespace::getUser() ) . + ":{$name}|{$name}]]"; + + $text = str_replace( "$1", $link, wfMsg( "blockedtext" ) ); + $text = str_replace( "$2", $reason, $text ); + $wgOut->addWikiText( $text ); + $wgOut->returnToMain( false ); + } + + # This function is called right before saving the wikitext, + # so we can do things like signatures and links-in-context. + + function preSaveTransform( $text ) + { + $s = ""; + while ( "" != $text ) { + $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 ); + $s .= $this->pstPass2( $p[0] ); + + if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $text = ""; } + else { + $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 ); + $s .= "{$q[0]}"; + $text = $q[1]; + } + } + return rtrim( $s ); + } + + /* private */ function pstPass2( $text ) + { + global $wgUser, $wgLang, $wgTitle, $wgLocaltimezone; + + # Signatures + # + $n = $wgUser->getName(); + $k = $wgUser->getOption( "nickname" ); + if ( "" == $k ) { $k = $n; } + if(isset($wgLocaltimezone)) { + $oldtz = getenv("TZ"); putenv("TZ=$wgLocaltimezone"); + } + $d = $wgLang->timeanddate( wfTimestampNow(), false ) . + " (" . date( "T" ) . ")"; + if(isset($wgLocaltimezone)) putenv("TZ=$oldtz"); + + $text = preg_replace( "/~~~~/", "[[" . $wgLang->getNsText( + Namespace::getUser() ) . ":$n|$k]] $d", $text ); + $text = preg_replace( "/~~~/", "[[" . $wgLang->getNsText( + Namespace::getUser() ) . ":$n|$k]]", $text ); + + # Context links: [[|name]] and [[name (context)|]] + # + $tc = "[&;%\\-,.\\(\\)' _0-9A-Za-z\\/:\\x80-\\xff]"; + $np = "[&;%\\-,.' _0-9A-Za-z\\/:\\x80-\\xff]"; # No parens + $conpat = "/^({$np}+) \\(({$tc}+)\\)$/"; + + $p1 = "/\[\[({$np}+) \\(({$np}+)\\)\\|]]/"; # [[page (context)|]] + $p2 = "/\[\[\\|({$tc}+)]]/"; # [[|page]] + $p3 = "/\[\[([A-Za-z _]+):({$np}+)\\|]]/"; # [[namespace:page|]] + $p4 = "/\[\[([A-Aa-z _]+):({$np}+) \\(({$np}+)\\)\\|]]/"; + # [[ns:page (cont)|]] + $context = ""; + $t = $wgTitle->getText(); + if ( preg_match( $conpat, $t, $m ) ) { + $context = $m[2]; + } + $text = preg_replace( $p4, "[[\\1:\\2 (\\3)|\\2]]", $text ); + $text = preg_replace( $p1, "[[\\1 (\\2)|\\1]]", $text ); + $text = preg_replace( $p3, "[[\\1:\\2|\\2]]", $text ); + + if ( "" == $context ) { + $text = preg_replace( $p2, "[[\\1]]", $text ); + } else { + $text = preg_replace( $p2, "[[\\1 ({$context})|\\1]]", $text ); + } + # Replace local image links with new [[image:]] style + + $text = preg_replace( + "/(^|[^[])http:\/\/(www.|)wikipedia.com\/upload\/" . + "([a-zA-Z0-9_:.~\%\-]+)\.(png|PNG|jpg|JPG|jpeg|JPEG|gif|GIF)/", + "\\1[[image:\\3.\\4]]", $text ); + $text = preg_replace( + "/(^|[^[])http:\/\/(www.|)wikipedia.com\/images\/uploads\/" . + "([a-zA-Z0-9_:.~\%\-]+)\.(png|PNG|jpg|JPG|jpeg|JPEG|gif|GIF)/", + "\\1[[image:\\3.\\4]]", $text ); + + return $text; + } +} + +?> diff --git a/includes/DatabaseFunctions.php b/includes/DatabaseFunctions.php new file mode 100644 index 0000000000..3d930472e0 --- /dev/null +++ b/includes/DatabaseFunctions.php @@ -0,0 +1,134 @@ +If this error persists after reloading and clearing " . + "your browser cache, please notify the Wikipedia developers.

"; + + if ( $altuser != "" ) { + $wgDBconnection = mysql_connect( $wgDBserver, $altuser, $altpassword ) + or die( "bad sql user" ); + mysql_select_db( $wgDBname, $wgDBconnection ) or die( + htmlspecialchars(mysql_error()) ); + } + + if ( ! $wgDBconnection ) { + $wgDBconnection = mysql_pconnect( $wgDBserver, $wgDBuser, + $wgDBpassword ) or die( $noconn . + "\n

" . htmlspecialchars(mysql_error()) . "

\n" . $helpme ); + if( !mysql_select_db( $wgDBname, $wgDBconnection ) ) { + wfDebug( "Persistent connection is broken?\n", true ); + + $wgDBconnection = mysql_connect( $wgDBserver, $wgDBuser, + $wgDBpassword ) or die( $noconn . + "\n

" . htmlspecialchars(mysql_error()) . " (tried non-p connect)

\n" . $helpme ); + mysql_select_db( $wgDBname, $wgDBconnection ) or die( $nodb . + "\n

" . htmlspecialchars(mysql_error()) . " (tried non-p connect)

\n" . $helpme ); + } + } + # mysql_ping( $wgDBconnection ); + return $wgDBconnection; +} + +function wfQuery( $sql, $fname = "" ) +{ + global $wgLastDatabaseQuery, $wgOut; +## wfProfileIn( "wfQuery" ); + $wgLastDatabaseQuery = $sql; + + $conn = wfGetDB(); + $ret = mysql_query( $sql, $conn ); + + if ( "" != $fname ) { +# wfDebug( "{$fname}:SQL: {$sql}\n", true ); + } else { +# wfDebug( "SQL: {$sql}\n", true ); + } + if ( false === $ret ) { + $wgOut->databaseError( $fname ); + exit; + } +## wfProfileOut(); + return $ret; +} + +function wfFreeResult( $res ) { mysql_free_result( $res ); } +function wfFetchObject( $res ) { return mysql_fetch_object( $res ); } +function wfNumRows( $res ) { return mysql_num_rows( $res ); } +function wfNumFields( $res ) { return mysql_num_fields( $res ); } +function wfFieldName( $res, $n ) { return mysql_field_name( $res, $n ); } +function wfInsertId() { return mysql_insert_id( wfGetDB() ); } +function wfDataSeek( $res, $row ) { return mysql_data_seek( $res, $row ); } +function wfLastErrno() { return mysql_errno(); } +function wfLastError() { return mysql_error(); } + +function wfLastDBquery() +{ + global $wgLastDatabaseQuery; + return $wgLastDatabaseQuery; +} + +function wfSetSQL( $table, $var, $value, $cond ) +{ + $sql = "UPDATE $table SET $var = '" . + wfStrencode( $value ) . "' WHERE ($cond)"; + wfQuery( $sql, "wfSetSQL" ); +} + +function wfGetSQL( $table, $var, $cond ) +{ + $sql = "SELECT $var FROM $table WHERE ($cond)"; + $result = wfQuery( $sql, "wfGetSQL" ); + + $ret = ""; + if ( mysql_num_rows( $result ) > 0 ) { + $s = mysql_fetch_object( $result ); + $ret = $s->$var; + mysql_free_result( $result ); + } + return $ret; +} + +function wfStrencode( $s ) +{ + return addslashes( $s ); +} + +# Ideally we'd be using actual time fields in the db +function wfTimestamp2Unix( $ts ) { + return mktime( ( (int)substr( $ts, 8, 2) ), + (int)substr( $ts, 10, 2 ), (int)substr( $ts, 12, 2 ), + (int)substr( $ts, 4, 2 ), (int)substr( $ts, 6, 2 ), + (int)substr( $ts, 0, 4 ) ); +} + +function wfUnix2Timestamp( $unixtime ) { + return date( "YmdHis", $unixtime ); +} + +function wfTimestampNow() { + # return NOW + return date( "YmdHis" ); +} + +# Sorting hack for MySQL 3, which doesn't use index sorts for DESC +function wfInvertTimestamp( $ts ) { + return strtr( + $ts, + "0123456789", + "9876543210" + ); +} + +?> diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php new file mode 100644 index 0000000000..77be904f3a --- /dev/null +++ b/includes/DefaultSettings.php @@ -0,0 +1,66 @@ + 0, 0 => 0, 1 => 1, + 2 => 1, 3 => 1, 4 => 0, 5 => 1, 6 => 0, 7 => 1 ); + +?> diff --git a/includes/DifferenceEngine.php b/includes/DifferenceEngine.php new file mode 100644 index 0000000000..835ac74f03 --- /dev/null +++ b/includes/DifferenceEngine.php @@ -0,0 +1,1138 @@ +mOldid = $old; + $this->mNewid = $new; + } + + function showDiffPage() + { + global $wgUser, $wgTitle, $wgOut, $wgLang; + + $t = $wgTitle->getPrefixedText() . " (Diff: {$this->mOldid}, " . + "{$this->mNewid})"; + $mtext = str_replace( "$1", $t, wfMsg( "missingarticle" ) ); + + $wgOut->setArticleFlag( false ); + if ( ! $this->loadText() ) { + $wgOut->setPagetitle( wfMsg( "errorpagetitle" ) ); + $wgOut->addHTML( $mtext ); + return; + } + $wgOut->supressQuickbar(); + $wgOut->setSubtitle( wfMsg( "difference" ) ); + $wgOut->setRobotpolicy( "noindex,follow" ); + + DifferenceEngine::showDiff( $this->mOldtext, $this->mNewtext, + $this->mOldtitle, $this->mNewtitle ); + $wgOut->addHTML( "

{$this->mNewtitle}

\n" ); + $wgOut->addWikiText( $this->mNewtext ); + } + + function showDiff( $otext, $ntext, $otitle, $ntitle ) + { + global $wgOut; + + $ota = explode( "\n", str_replace( "\r\n", "\n", + htmlspecialchars( $otext ) ) ); + $nta = explode( "\n", str_replace( "\r\n", "\n", + htmlspecialchars( $ntext ) ) ); + + $wgOut->addHTML( " + + +\n" ); + + $diffs = new Diff( $ota, $nta ); + $formatter = new TableDiffFormatter(); + $formatter->format( $diffs ); + $wgOut->addHTML( "
+{$otitle} +{$ntitle}
\n" ); + } + + # Load the text of the articles to compare. If newid is 0, then compare + # the old article in oldid to the current article; if oldid is 0, then + # compare the current article to the immediately previous one (ignoring + # the value of newid). + # + function loadText() + { + global $wgTitle, $wgOut, $wgLang; + $fname = "DifferenceEngine::loadText"; + + if ( 0 == $this->mNewid || 0 == $this->mOldid ) { + $wgOut->setArticleFlag( true ); + $this->mNewtitle = wfMsg( "currentrev" ); + $id = $wgTitle->getArticleID(); + + $sql = "SELECT cur_text FROM cur WHERE cur_id={$id}"; + $res = wfQuery( $sql, $fname ); + if ( 0 == wfNumRows( $res ) ) { return false; } + + $s = wfFetchObject( $res ); + $this->mNewtext = $s->cur_text; + } else { + $sql = "SELECT old_timestamp,old_text FROM old WHERE " . + "old_id={$this->mNewid}"; + + $res = wfQuery( $sql, $fname ); + if ( 0 == wfNumRows( $res ) ) { return false; } + + $s = wfFetchObject( $res ); + $this->mNewtext = $s->old_text; + + $t = $wgLang->timeanddate( $s->old_timestamp, true ); + $this->mNewtitle = str_replace( "$1", "{$t}", + wfMsg( "revisionasof" ) ); + } + if ( 0 == $this->mOldid ) { + $sql = "SELECT old_timestamp,old_text FROM old USE INDEX (name_title_timestamp) WHERE " . + "old_namespace=" . $wgTitle->getNamespace() . " AND " . + "old_title='" . wfStrencode( $wgTitle->getDBkey() ) . + "' ORDER BY inverse_timestamp LIMIT 1"; + $res = wfQuery( $sql, $fname ); + } else { + $sql = "SELECT old_timestamp,old_text FROM old WHERE " . + "old_id={$this->mOldid}"; + $res = wfQuery( $sql, $fname ); + } + if ( 0 == wfNumRows( $res ) ) { return false; } + + $s = wfFetchObject( $res ); + $this->mOldtext = $s->old_text; + + $t = $wgLang->timeanddate( $s->old_timestamp, true ); + $this->mOldtitle = str_replace( "$1", "{$t}", + wfMsg( "revisionasof" ) ); + + return true; + } +} + +// A PHP diff engine for phpwiki. (Taken from phpwiki-1.3.3) +// +// Copyright (C) 2000, 2001 Geoffrey T. Dairiki +// You may copy this code freely under the conditions of the GPL. +// + +define('USE_ASSERTS', function_exists('assert')); + +class _DiffOp { + var $type; + var $orig; + var $final; + + function reverse() { + trigger_error("pure virtual", E_USER_ERROR); + } + + function norig() { + return $this->orig ? sizeof($this->orig) : 0; + } + + function nfinal() { + return $this->final ? sizeof($this->final) : 0; + } +} + +class _DiffOp_Copy extends _DiffOp { + var $type = 'copy'; + + function _DiffOp_Copy ($orig, $final = false) { + if (!is_array($final)) + $final = $orig; + $this->orig = $orig; + $this->final = $final; + } + + function reverse() { + return new _DiffOp_Copy($this->final, $this->orig); + } +} + +class _DiffOp_Delete extends _DiffOp { + var $type = 'delete'; + + function _DiffOp_Delete ($lines) { + $this->orig = $lines; + $this->final = false; + } + + function reverse() { + return new _DiffOp_Add($this->orig); + } +} + +class _DiffOp_Add extends _DiffOp { + var $type = 'add'; + + function _DiffOp_Add ($lines) { + $this->final = $lines; + $this->orig = false; + } + + function reverse() { + return new _DiffOp_Delete($this->final); + } +} + +class _DiffOp_Change extends _DiffOp { + var $type = 'change'; + + function _DiffOp_Change ($orig, $final) { + $this->orig = $orig; + $this->final = $final; + } + + function reverse() { + return new _DiffOp_Change($this->final, $this->orig); + } +} + + +/** + * Class used internally by Diff to actually compute the diffs. + * + * The algorithm used here is mostly lifted from the perl module + * Algorithm::Diff (version 1.06) by Ned Konz, which is available at: + * http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip + * + * More ideas are taken from: + * http://www.ics.uci.edu/~eppstein/161/960229.html + * + * Some ideas are (and a bit of code) are from from analyze.c, from GNU + * diffutils-2.7, which can be found at: + * ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz + * + * Finally, some ideas (subdivision by NCHUNKS > 2, and some optimizations) + * are my own. + * + * @author Geoffrey T. Dairiki + * @access private + */ +class _DiffEngine +{ + function diff ($from_lines, $to_lines) { + $n_from = sizeof($from_lines); + $n_to = sizeof($to_lines); + + $this->xchanged = $this->ychanged = array(); + $this->xv = $this->yv = array(); + $this->xind = $this->yind = array(); + unset($this->seq); + unset($this->in_seq); + unset($this->lcs); + + // Skip leading common lines. + for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { + if ($from_lines[$skip] != $to_lines[$skip]) + break; + $this->xchanged[$skip] = $this->ychanged[$skip] = false; + } + // Skip trailing common lines. + $xi = $n_from; $yi = $n_to; + for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { + if ($from_lines[$xi] != $to_lines[$yi]) + break; + $this->xchanged[$xi] = $this->ychanged[$yi] = false; + } + + // Ignore lines which do not exist in both files. + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) + $xhash[$from_lines[$xi]] = 1; + for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { + $line = $to_lines[$yi]; + if ( ($this->ychanged[$yi] = empty($xhash[$line])) ) + continue; + $yhash[$line] = 1; + $this->yv[] = $line; + $this->yind[] = $yi; + } + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { + $line = $from_lines[$xi]; + if ( ($this->xchanged[$xi] = empty($yhash[$line])) ) + continue; + $this->xv[] = $line; + $this->xind[] = $xi; + } + + // Find the LCS. + $this->_compareseq(0, sizeof($this->xv), 0, sizeof($this->yv)); + + // Merge edits when possible + $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); + $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); + + // Compute the edit operations. + $edits = array(); + $xi = $yi = 0; + while ($xi < $n_from || $yi < $n_to) { + USE_ASSERTS && assert($yi < $n_to || $this->xchanged[$xi]); + USE_ASSERTS && assert($xi < $n_from || $this->ychanged[$yi]); + + // Skip matching "snake". + $copy = array(); + while ( $xi < $n_from && $yi < $n_to + && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { + $copy[] = $from_lines[$xi++]; + ++$yi; + } + if ($copy) + $edits[] = new _DiffOp_Copy($copy); + + // Find deletes & adds. + $delete = array(); + while ($xi < $n_from && $this->xchanged[$xi]) + $delete[] = $from_lines[$xi++]; + + $add = array(); + while ($yi < $n_to && $this->ychanged[$yi]) + $add[] = $to_lines[$yi++]; + + if ($delete && $add) + $edits[] = new _DiffOp_Change($delete, $add); + elseif ($delete) + $edits[] = new _DiffOp_Delete($delete); + elseif ($add) + $edits[] = new _DiffOp_Add($add); + } + return $edits; + } + + + /* Divide the Largest Common Subsequence (LCS) of the sequences + * [XOFF, XLIM) and [YOFF, YLIM) into NCHUNKS approximately equally + * sized segments. + * + * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an + * array of NCHUNKS+1 (X, Y) indexes giving the diving points between + * sub sequences. The first sub-sequence is contained in [X0, X1), + * [Y0, Y1), the second in [X1, X2), [Y1, Y2) and so on. Note + * that (X0, Y0) == (XOFF, YOFF) and + * (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). + * + * This function assumes that the first lines of the specified portions + * of the two files do not match, and likewise that the last lines do not + * match. The caller must trim matching lines from the beginning and end + * of the portions it is going to specify. + */ + function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) { + $flip = false; + + if ($xlim - $xoff > $ylim - $yoff) { + // Things seems faster (I'm not sure I understand why) + // when the shortest sequence in X. + $flip = true; + list ($xoff, $xlim, $yoff, $ylim) + = array( $yoff, $ylim, $xoff, $xlim); + } + + if ($flip) + for ($i = $ylim - 1; $i >= $yoff; $i--) + $ymatches[$this->xv[$i]][] = $i; + else + for ($i = $ylim - 1; $i >= $yoff; $i--) + $ymatches[$this->yv[$i]][] = $i; + + $this->lcs = 0; + $this->seq[0]= $yoff - 1; + $this->in_seq = array(); + $ymids[0] = array(); + + $numer = $xlim - $xoff + $nchunks - 1; + $x = $xoff; + for ($chunk = 0; $chunk < $nchunks; $chunk++) { + if ($chunk > 0) + for ($i = 0; $i <= $this->lcs; $i++) + $ymids[$i][$chunk-1] = $this->seq[$i]; + + $x1 = $xoff + (int)(($numer + ($xlim-$xoff)*$chunk) / $nchunks); + for ( ; $x < $x1; $x++) { + $line = $flip ? $this->yv[$x] : $this->xv[$x]; + if (empty($ymatches[$line])) + continue; + $matches = $ymatches[$line]; + reset($matches); + while (list ($junk, $y) = each($matches)) + if (empty($this->in_seq[$y])) { + $k = $this->_lcs_pos($y); + USE_ASSERTS && assert($k > 0); + $ymids[$k] = $ymids[$k-1]; + break; + } + while (list ($junk, $y) = each($matches)) { + if ($y > $this->seq[$k-1]) { + USE_ASSERTS && assert($y < $this->seq[$k]); + // Optimization: this is a common case: + // next match is just replacing previous match. + $this->in_seq[$this->seq[$k]] = false; + $this->seq[$k] = $y; + $this->in_seq[$y] = 1; + } + else if (empty($this->in_seq[$y])) { + $k = $this->_lcs_pos($y); + USE_ASSERTS && assert($k > 0); + $ymids[$k] = $ymids[$k-1]; + } + } + } + } + + $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); + $ymid = $ymids[$this->lcs]; + for ($n = 0; $n < $nchunks - 1; $n++) { + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); + $y1 = $ymid[$n] + 1; + $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); + } + $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); + + return array($this->lcs, $seps); + } + + function _lcs_pos ($ypos) { + $end = $this->lcs; + if ($end == 0 || $ypos > $this->seq[$end]) { + $this->seq[++$this->lcs] = $ypos; + $this->in_seq[$ypos] = 1; + return $this->lcs; + } + + $beg = 1; + while ($beg < $end) { + $mid = (int)(($beg + $end) / 2); + if ( $ypos > $this->seq[$mid] ) + $beg = $mid + 1; + else + $end = $mid; + } + + USE_ASSERTS && assert($ypos != $this->seq[$end]); + + $this->in_seq[$this->seq[$end]] = false; + $this->seq[$end] = $ypos; + $this->in_seq[$ypos] = 1; + return $end; + } + + /* Find LCS of two sequences. + * + * The results are recorded in the vectors $this->{x,y}changed[], by + * storing a 1 in the element for each line that is an insertion + * or deletion (ie. is not in the LCS). + * + * The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1. + * + * Note that XLIM, YLIM are exclusive bounds. + * All line numbers are origin-0 and discarded lines are not counted. + */ + function _compareseq ($xoff, $xlim, $yoff, $ylim) { + // Slide down the bottom initial diagonal. + while ($xoff < $xlim && $yoff < $ylim + && $this->xv[$xoff] == $this->yv[$yoff]) { + ++$xoff; + ++$yoff; + } + + // Slide up the top initial diagonal. + while ($xlim > $xoff && $ylim > $yoff + && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { + --$xlim; + --$ylim; + } + + if ($xoff == $xlim || $yoff == $ylim) + $lcs = 0; + else { + // This is ad hoc but seems to work well. + //$nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); + //$nchunks = max(2,min(8,(int)$nchunks)); + $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; + list ($lcs, $seps) + = $this->_diag($xoff,$xlim,$yoff, $ylim,$nchunks); + } + + if ($lcs == 0) { + // X and Y sequences have no common subsequence: + // mark all changed. + while ($yoff < $ylim) + $this->ychanged[$this->yind[$yoff++]] = 1; + while ($xoff < $xlim) + $this->xchanged[$this->xind[$xoff++]] = 1; + } + else { + // Use the partitions to split this problem into subproblems. + reset($seps); + $pt1 = $seps[0]; + while ($pt2 = next($seps)) { + $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); + $pt1 = $pt2; + } + } + } + + /* Adjust inserts/deletes of identical lines to join changes + * as much as possible. + * + * We do something when a run of changed lines include a + * line at one end and has an excluded, identical line at the other. + * We are free to choose which identical line is included. + * `compareseq' usually chooses the one at the beginning, + * but usually it is cleaner to consider the following identical line + * to be the "change". + * + * This is extracted verbatim from analyze.c (GNU diffutils-2.7). + */ + function _shift_boundaries ($lines, &$changed, $other_changed) { + $i = 0; + $j = 0; + + USE_ASSERTS && assert('sizeof($lines) == sizeof($changed)'); + $len = sizeof($lines); + $other_len = sizeof($other_changed); + + while (1) { + /* + * Scan forwards to find beginning of another run of changes. + * Also keep track of the corresponding point in the other file. + * + * Throughout this code, $i and $j are adjusted together so that + * the first $i elements of $changed and the first $j elements + * of $other_changed both contain the same number of zeros + * (unchanged lines). + * Furthermore, $j is always kept so that $j == $other_len or + * $other_changed[$j] == false. + */ + while ($j < $other_len && $other_changed[$j]) + $j++; + + while ($i < $len && ! $changed[$i]) { + USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); + $i++; $j++; + while ($j < $other_len && $other_changed[$j]) + $j++; + } + + if ($i == $len) + break; + + $start = $i; + + // Find the end of this run of changes. + while (++$i < $len && $changed[$i]) + continue; + + do { + /* + * Record the length of this run of changes, so that + * we can later determine whether the run has grown. + */ + $runlength = $i - $start; + + /* + * Move the changed region back, so long as the + * previous unchanged line matches the last changed one. + * This merges with previous changed regions. + */ + while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { + $changed[--$start] = 1; + $changed[--$i] = false; + while ($start > 0 && $changed[$start - 1]) + $start--; + USE_ASSERTS && assert('$j > 0'); + while ($other_changed[--$j]) + continue; + USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); + } + + /* + * Set CORRESPONDING to the end of the changed run, at the last + * point where it corresponds to a changed run in the other file. + * CORRESPONDING == LEN means no such point has been found. + */ + $corresponding = $j < $other_len ? $i : $len; + + /* + * Move the changed region forward, so long as the + * first changed line matches the following unchanged one. + * This merges with following changed regions. + * Do this second, so that if there are no merges, + * the changed region is moved forward as far as possible. + */ + while ($i < $len && $lines[$start] == $lines[$i]) { + $changed[$start++] = false; + $changed[$i++] = 1; + while ($i < $len && $changed[$i]) + $i++; + + USE_ASSERTS && assert('$j < $other_len && ! $other_changed[$j]'); + $j++; + if ($j < $other_len && $other_changed[$j]) { + $corresponding = $i; + while ($j < $other_len && $other_changed[$j]) + $j++; + } + } + } while ($runlength != $i - $start); + + /* + * If possible, move the fully-merged run of changes + * back to a corresponding run in the other file. + */ + while ($corresponding < $i) { + $changed[--$start] = 1; + $changed[--$i] = 0; + USE_ASSERTS && assert('$j > 0'); + while ($other_changed[--$j]) + continue; + USE_ASSERTS && assert('$j >= 0 && !$other_changed[$j]'); + } + } + } +} + +/** + * Class representing a 'diff' between two sequences of strings. + */ +class Diff +{ + var $edits; + + /** + * Constructor. + * Computes diff between sequences of strings. + * + * @param $from_lines array An array of strings. + * (Typically these are lines from a file.) + * @param $to_lines array An array of strings. + */ + function Diff($from_lines, $to_lines) { + $eng = new _DiffEngine; + $this->edits = $eng->diff($from_lines, $to_lines); + //$this->_check($from_lines, $to_lines); + } + + /** + * Compute reversed Diff. + * + * SYNOPSIS: + * + * $diff = new Diff($lines1, $lines2); + * $rev = $diff->reverse(); + * @return object A Diff object representing the inverse of the + * original diff. + */ + function reverse () { + $rev = $this; + $rev->edits = array(); + foreach ($this->edits as $edit) { + $rev->edits[] = $edit->reverse(); + } + return $rev; + } + + /** + * Check for empty diff. + * + * @return bool True iff two sequences were identical. + */ + function isEmpty () { + foreach ($this->edits as $edit) { + if ($edit->type != 'copy') + return false; + } + return true; + } + + /** + * Compute the length of the Longest Common Subsequence (LCS). + * + * This is mostly for diagnostic purposed. + * + * @return int The length of the LCS. + */ + function lcs () { + $lcs = 0; + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $lcs += sizeof($edit->orig); + } + return $lcs; + } + + /** + * Get the original set of lines. + * + * This reconstructs the $from_lines parameter passed to the + * constructor. + * + * @return array The original sequence of strings. + */ + function orig() { + $lines = array(); + + foreach ($this->edits as $edit) { + if ($edit->orig) + array_splice($lines, sizeof($lines), 0, $edit->orig); + } + return $lines; + } + + /** + * Get the final set of lines. + * + * This reconstructs the $to_lines parameter passed to the + * constructor. + * + * @return array The sequence of strings. + */ + function final() { + $lines = array(); + + foreach ($this->edits as $edit) { + if ($edit->final) + array_splice($lines, sizeof($lines), 0, $edit->final); + } + return $lines; + } + + /** + * Check a Diff for validity. + * + * This is here only for debugging purposes. + */ + function _check ($from_lines, $to_lines) { + if (serialize($from_lines) != serialize($this->orig())) + trigger_error("Reconstructed original doesn't match", E_USER_ERROR); + if (serialize($to_lines) != serialize($this->final())) + trigger_error("Reconstructed final doesn't match", E_USER_ERROR); + + $rev = $this->reverse(); + if (serialize($to_lines) != serialize($rev->orig())) + trigger_error("Reversed original doesn't match", E_USER_ERROR); + if (serialize($from_lines) != serialize($rev->final())) + trigger_error("Reversed final doesn't match", E_USER_ERROR); + + + $prevtype = 'none'; + foreach ($this->edits as $edit) { + if ( $prevtype == $edit->type ) + trigger_error("Edit sequence is non-optimal", E_USER_ERROR); + $prevtype = $edit->type; + } + + $lcs = $this->lcs(); + trigger_error("Diff okay: LCS = $lcs", E_USER_NOTICE); + } +} + +/** + * FIXME: bad name. + */ +class MappedDiff +extends Diff +{ + /** + * Constructor. + * + * Computes diff between sequences of strings. + * + * This can be used to compute things like + * case-insensitve diffs, or diffs which ignore + * changes in white-space. + * + * @param $from_lines array An array of strings. + * (Typically these are lines from a file.) + * + * @param $to_lines array An array of strings. + * + * @param $mapped_from_lines array This array should + * have the same size number of elements as $from_lines. + * The elements in $mapped_from_lines and + * $mapped_to_lines are what is actually compared + * when computing the diff. + * + * @param $mapped_to_lines array This array should + * have the same number of elements as $to_lines. + */ + function MappedDiff($from_lines, $to_lines, + $mapped_from_lines, $mapped_to_lines) { + + assert(sizeof($from_lines) == sizeof($mapped_from_lines)); + assert(sizeof($to_lines) == sizeof($mapped_to_lines)); + + $this->Diff($mapped_from_lines, $mapped_to_lines); + + $xi = $yi = 0; + for ($i = 0; $i < sizeof($this->edits); $i++) { + $orig = &$this->edits[$i]->orig; + if (is_array($orig)) { + $orig = array_slice($from_lines, $xi, sizeof($orig)); + $xi += sizeof($orig); + } + + $final = &$this->edits[$i]->final; + if (is_array($final)) { + $final = array_slice($to_lines, $yi, sizeof($final)); + $yi += sizeof($final); + } + } + } +} + +/** + * A class to format Diffs + * + * This class formats the diff in classic diff format. + * It is intended that this class be customized via inheritance, + * to obtain fancier outputs. + */ +class DiffFormatter +{ + /** + * Number of leading context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses + * may want to set this to other values. + */ + var $leading_context_lines = 0; + + /** + * Number of trailing context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses + * may want to set this to other values. + */ + var $trailing_context_lines = 0; + + /** + * Format a diff. + * + * @param $diff object A Diff object. + * @return string The formatted output. + */ + function format($diff) { + + $xi = $yi = 1; + $block = false; + $context = array(); + + $nlead = $this->leading_context_lines; + $ntrail = $this->trailing_context_lines; + + $this->_start_diff(); + + foreach ($diff->edits as $edit) { + if ($edit->type == 'copy') { + if (is_array($block)) { + if (sizeof($edit->orig) <= $nlead + $ntrail) { + $block[] = $edit; + } + else{ + if ($ntrail) { + $context = array_slice($edit->orig, 0, $ntrail); + $block[] = new _DiffOp_Copy($context); + } + $this->_block($x0, $ntrail + $xi - $x0, + $y0, $ntrail + $yi - $y0, + $block); + $block = false; + } + } + $context = $edit->orig; + } + else { + if (! is_array($block)) { + $context = array_slice($context, sizeof($context) - $nlead); + $x0 = $xi - sizeof($context); + $y0 = $yi - sizeof($context); + $block = array(); + if ($context) + $block[] = new _DiffOp_Copy($context); + } + $block[] = $edit; + } + + if ($edit->orig) + $xi += sizeof($edit->orig); + if ($edit->final) + $yi += sizeof($edit->final); + } + + if (is_array($block)) + $this->_block($x0, $xi - $x0, + $y0, $yi - $y0, + $block); + + return $this->_end_diff(); + } + + function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) { + $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen)); + foreach ($edits as $edit) { + if ($edit->type == 'copy') + $this->_context($edit->orig); + elseif ($edit->type == 'add') + $this->_added($edit->final); + elseif ($edit->type == 'delete') + $this->_deleted($edit->orig); + elseif ($edit->type == 'change') + $this->_changed($edit->orig, $edit->final); + else + trigger_error("Unknown edit type", E_USER_ERROR); + } + $this->_end_block(); + } + + function _start_diff() { + ob_start(); + } + + function _end_diff() { + $val = ob_get_contents(); + ob_end_clean(); + return $val; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) { + if ($xlen > 1) + $xbeg .= "," . ($xbeg + $xlen - 1); + if ($ylen > 1) + $ybeg .= "," . ($ybeg + $ylen - 1); + + return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; + } + + function _start_block($header) { + echo $header; + } + + function _end_block() { + } + + function _lines($lines, $prefix = ' ') { + foreach ($lines as $line) + echo "$prefix $line\n"; + } + + function _context($lines) { + $this->_lines($lines); + } + + function _added($lines) { + $this->_lines($lines, ">"); + } + function _deleted($lines) { + $this->_lines($lines, "<"); + } + + function _changed($orig, $final) { + $this->_deleted($orig); + echo "---\n"; + $this->_added($final); + } +} + + +/** + * Additions by Axel Boldt follow, partly taken from diff.php, phpwiki-1.3.3 + * + */ + +define('NBSP', "\xA0"); // iso-8859-x non-breaking space. + +class _HWLDF_WordAccumulator { + function _HWLDF_WordAccumulator () { + $this->_lines = array(); + $this->_line = ''; + $this->_group = ''; + $this->_tag = ''; + } + + function _flushGroup ($new_tag) { + if ($this->_group !== '') { + if ($this->_tag == 'mark') + $this->_line .= "$this->_group"; + else + $this->_line .= $this->_group; + } + $this->_group = ''; + $this->_tag = $new_tag; + } + + function _flushLine ($new_tag) { + $this->_flushGroup($new_tag); + if ($this->_line != '') + $this->_lines[] = $this->_line; + $this->_line = ''; + } + + function addWords ($words, $tag = '') { + if ($tag != $this->_tag) + $this->_flushGroup($tag); + + foreach ($words as $word) { + // new-line should only come as first char of word. + if ($word == '') + continue; + if ($word[0] == "\n") { + $this->_group .= NBSP; + $this->_flushLine($tag); + $word = substr($word, 1); + } + assert(!strstr($word, "\n")); + $this->_group .= $word; + } + } + + function getLines() { + $this->_flushLine('~done'); + return $this->_lines; + } +} + +class WordLevelDiff extends MappedDiff +{ + function WordLevelDiff ($orig_lines, $final_lines) { + list ($orig_words, $orig_stripped) = $this->_split($orig_lines); + list ($final_words, $final_stripped) = $this->_split($final_lines); + + + $this->MappedDiff($orig_words, $final_words, + $orig_stripped, $final_stripped); + } + + function _split($lines) { + // FIXME: fix POSIX char class. +# if (!preg_match_all('/ ( [^\S\n]+ | [[:alnum:]]+ | . ) (?: (?!< \n) [^\S\n])? /xs', + if (!preg_match_all('/ ( [^\S\n]+ | [0-9_A-Za-z\x80-\xff]+ | . ) (?: (?!< \n) [^\S\n])? /xs', + implode("\n", $lines), + $m)) { + return array(array(''), array('')); + } + return array($m[0], $m[1]); + } + + function orig () { + $orig = new _HWLDF_WordAccumulator; + + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $orig->addWords($edit->orig); + elseif ($edit->orig) + $orig->addWords($edit->orig, 'mark'); + } + return $orig->getLines(); + } + + function final () { + $final = new _HWLDF_WordAccumulator; + + foreach ($this->edits as $edit) { + if ($edit->type == 'copy') + $final->addWords($edit->final); + elseif ($edit->final) + $final->addWords($edit->final, 'mark'); + } + return $final->getLines(); + } +} + +/** + * Wikipedia Table style diff formatter. + * + */ +class TableDiffFormatter extends DiffFormatter +{ + function TableDiffFormatter() { + $this->leading_context_lines = 2; + $this->trailing_context_lines = 2; + } + + function _block_header( $xbeg, $xlen, $ybeg, $ylen ) { + $l1 = str_replace( "$1", $xbeg, wfMsg( "lineno" ) ); + $l2 = str_replace( "$1", $ybeg, wfMsg( "lineno" ) ); + + $r = "{$l1}\n" . + "{$l2}\n"; + return $r; + } + + function _start_block( $header ) { + global $wgOut; + $wgOut->addHTML( $header ); + } + + function _end_block() { + } + + function _lines( $lines, $prefix=' ', $color="white" ) { + } + + function addedLine( $line ) { + return "+" . + "{$line}"; + } + + function deletedLine( $line ) { + return "-" . + "{$line}"; + } + + function emptyLine() { + return " "; + } + + function contextLine( $line ) { + return " {$line}"; + } + + function _added($lines) { + global $wgOut; + foreach ($lines as $line) { + $wgOut->addHTML( "" . $this->emptyLine() . + $this->addedLine( $line ) . "\n" ); + } + } + + function _deleted($lines) { + global $wgOut; + foreach ($lines as $line) { + $wgOut->addHTML( "" . $this->deletedLine( $line ) . + $this->emptyLine() . "\n" ); + } + } + + function _context( $lines ) { + global $wgOut; + foreach ($lines as $line) { + $wgOut->addHTML( "" . $this->contextLine( $line ) . + $this->contextLine( $line ) . "\n" ); + } + } + + function _changed( $orig, $final ) { + global $wgOut; + $diff = new WordLevelDiff( $orig, $final ); + $del = $diff->orig(); + $add = $diff->final(); + + while ( $line = array_shift( $del ) ) { + $aline = array_shift( $add ); + $wgOut->addHTML( "" . $this->deletedLine( $line ) . + $this->addedLine( $aline ) . "\n" ); + } + $this->_added( $add ); # If any leftovers + } +} + +?> diff --git a/includes/FulltextStoplist.php b/includes/FulltextStoplist.php new file mode 100644 index 0000000000..ce0756f057 --- /dev/null +++ b/includes/FulltextStoplist.php @@ -0,0 +1,592 @@ + diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php new file mode 100644 index 0000000000..a60007bea4 --- /dev/null +++ b/includes/GlobalFunctions.php @@ -0,0 +1,494 @@ +getDBkey(); + $hash = md5( $name ); + + $url = "{$wgUploadPath}/" . $hash{0} . "/" . + substr( $hash, 0, 2 ) . "/{$name}"; + return wfUrlencode( $url ); +} + +function wfImageArchiveUrl( $name ) +{ + global $wgUploadPath; + + $hash = md5( substr( $name, 15) ); + $url = "{$wgUploadPath}/archive/" . $hash{0} . "/" . + substr( $hash, 0, 2 ) . "/{$name}"; + return $url; +} + +function wfUrlencode ( $s ) +{ + $ulink = urlencode( $s ); + $ulink = preg_replace( "/%3[Aa]/", ":", $ulink ); + $ulink = preg_replace( "/%2[Ff]/", "/", $ulink ); + return $ulink; +} + +function wfUtf8Sequence($codepoint) { + if($codepoint < 0x80) return chr($codepoint); + if($codepoint < 0x800) return chr($codepoint >> 6 & 0x3f | 0xc0) . + chr($codepoint & 0x3f | 0x80); + if($codepoint < 0x10000) return chr($codepoint >> 12 & 0x0f | 0xe0) . + chr($codepoint >> 6 & 0x3f | 0x80) . + chr($codepoint & 0x3f | 0x80); + if($codepoint < 0x100000) return chr($codepoint >> 18 & 0x07 | 0xf0) . # Double-check this + chr($codepoint >> 12 & 0x3f | 0x80) . + chr($codepoint >> 6 & 0x3f | 0x80) . + chr($codepoint & 0x3f | 0x80); + # Doesn't yet handle outside the BMP + return "&#$codepoint;"; +} + +function wfMungeToUtf8($string) { + global $wgInputEncoding; # This is debatable + #$string = iconv($wgInputEncoding, "UTF-8", $string); + $string = preg_replace ( '/&#([0-9]+);/e', 'wfUtf8Sequence($1)', $string ); + $string = preg_replace ( '/&#x([0-9a-f]+);/ie', 'wfUtf8Sequence(0x$1)', $string ); + # Should also do named entities here + return $string; +} + +function wfDebug( $text, $logonly = false ) +{ + global $wgOut, $wgDebugLogFile; + + if ( ! $logonly ) { + $wgOut->debug( $text ); + } + if ( "" != $wgDebugLogFile ) { + error_log( $text, 3, $wgDebugLogFile ); + } +} + +if( !isset( $wgProfiling ) ) + $wgProfiling = false; +$wgProfileStack = array(); +$wgProfileWorkStack = array(); + +if( $wgProfiling ) { + function wfProfileIn( $functionname ) + { + global $wgProfileStack, $wgProfileWorkStack; + array_push( $wgProfileWorkStack, "$functionname " . + count( $wgProfileWorkStack ) . " " . microtime() ); + } + + function wfProfileOut() { + global $wgProfileStack, $wgProfileWorkStack; + $bit = array_pop( $wgProfileWorkStack ); + $bit .= " " . microtime(); + array_push( $wgProfileStack, $bit ); + } +} else { + function wfProfileIn( $functionname ) { } + function wfProfileOut( ) { } +} + +function wfReadOnly() +{ + global $wgReadOnlyFile; + + if ( "" == $wgReadOnlyFile ) { return false; } + return is_file( $wgReadOnlyFile ); +} + +$wgReplacementKeys = array( "$1", "$2", "$3", "$4", "$5", "$6", "$7", "$8", "$9" ); +function wfMsg( $key ) +{ + global $wgLang, $wgReplacementKeys; + $ret = $wgLang->getMessage( $key ); + + if( func_num_args() > 1 ) { + $reps = func_get_args(); + array_shift( $reps ); + $ret = str_replace( $wgReplacementKeys, $reps, $ret ); + } + + if ( "" == $ret ) { + user_error( "Couldn't find text for message \"{$key}\"." ); + } + return $ret; +} + +function wfCleanFormFields( $fields ) +{ + global $HTTP_POST_VARS; + global $wgInputEncoding, $wgOutputEncoding, $wgEditEncoding, $wgLang; + + if ( get_magic_quotes_gpc() ) { + foreach ( $fields as $fname ) { + if ( isset( $HTTP_POST_VARS[$fname] ) ) { + $HTTP_POST_VARS[$fname] = stripslashes( + $HTTP_POST_VARS[$fname] ); + } + global ${$fname}; + if ( isset( ${$fname} ) ) { + ${$fname} = stripslashes( ${$fname} ); + } + } + } + $enc = $wgOutputEncoding; + if( $wgEditEncoding != "") $enc = $wgEditEncoding; + if ( $enc != $wgInputEncoding ) { + foreach ( $fields as $fname ) { + if ( isset( $HTTP_POST_VARS[$fname] ) ) { + $HTTP_POST_VARS[$fname] = $wgLang->iconv( + $wgOutputEncoding, $wgInputEncoding, + $HTTP_POST_VARS[$fname] ); + } + global ${$fname}; + if ( isset( ${$fname} ) ) { + ${$fname} = $wgLang->iconv( + $enc, $wgInputEncoding, ${$fname} ); + } + } + } +} + +function wfMungeQuotes( $in ) +{ + $out = str_replace( "%", "%25", $in ); + $out = str_replace( "'", "%27", $out ); + $out = str_replace( "\"", "%22", $out ); + return $out; +} + +function wfDemungeQuotes( $in ) +{ + $out = str_replace( "%22", "\"", $in ); + $out = str_replace( "%27", "'", $out ); + $out = str_replace( "%25", "%", $out ); + return $out; +} + +function wfCleanQueryVar( $var ) +{ + global $wgLang; + if ( get_magic_quotes_gpc() ) { + $var = stripslashes( $var ); + } + return $wgLang->recodeInput( $var ); +} + +function wfSpecialPage() +{ + global $wgUser, $wgOut, $wgTitle, $wgLang; + + $validSP = $wgLang->getValidSpecialPages(); + $sysopSP = $wgLang->getSysopSpecialPages(); + $devSP = $wgLang->getDeveloperSpecialPages(); + + $wgOut->setArticleFlag( false ); + $wgOut->setRobotpolicy( "noindex,follow" ); + + $t = $wgTitle->getDBkey(); + if ( array_key_exists( $t, $validSP ) || + ( $wgUser->isSysop() && array_key_exists( $t, $sysopSP ) ) || + ( $wgUser->isDeveloper() && array_key_exists( $t, $devSP ) ) ) { + $wgOut->setPageTitle( wfMsg( strtolower( $wgTitle->getText() ) ) ); + + $inc = "Special" . $t . ".php"; + include_once( $inc ); + $call = "wfSpecial" . $t; + $call(); + } else if ( array_key_exists( $t, $sysopSP ) ) { + $wgOut->sysopRequired(); + } else if ( array_key_exists( $t, $devSP ) ) { + $wgOut->developerRequired(); + } else { + $wgOut->errorpage( "nosuchspecialpage", "nospecialpagetext" ); + } +} + +function wfSearch( $s ) +{ + $se = new SearchEngine( wfCleanQueryVar( $s ) ); + $se->showResults(); +} + +function wfGo( $s ) +{ # pick the nearest match + $se = new SearchEngine( wfCleanQueryVar( $s ) ); + $se->goResult(); +} + +function wfNumberOfArticles() +{ + global $wgNumberOfArticles; + + wfLoadSiteStats(); + return $wgNumberOfArticles; +} + +/* private */ function wfLoadSiteStats() +{ + global $wgNumberOfArticles, $wgTotalViews, $wgTotalEdits; + if ( -1 != $wgNumberOfArticles ) return; + + $sql = "SELECT ss_total_views, ss_total_edits, ss_good_articles " . + "FROM site_stats WHERE ss_row_id=1"; + $res = wfQuery( $sql, "wfLoadSiteStats" ); + + if ( 0 == wfNumRows( $res ) ) { return; } + else { + $s = wfFetchObject( $res ); + $wgTotalViews = $s->ss_total_views; + $wgTotalEdits = $s->ss_total_edits; + $wgNumberOfArticles = $s->ss_good_articles; + } +} + +function wfEscapeHTML( $in ) +{ + return str_replace( + array( "&", "\"", ">", "<" ), + array( "&", """, ">", "<" ), + $in ); +} + +function wfEscapeHTMLTagsOnly( $in ) { + return str_replace( + array( "\"", ">", "<" ), + array( """, ">", "<" ), + $in ); +} + +function wfUnescapeHTML( $in ) +{ + $in = str_replace( "<", "<", $in ); + $in = str_replace( ">", ">", $in ); + $in = str_replace( """, "\"", $in ); + $in = str_replace( "&", "&", $in ); + return $in; +} + +function wfImageDir( $fname ) +{ + global $wgUploadDirectory; + + $hash = md5( $fname ); + $oldumask = umask(0); + $dest = $wgUploadDirectory . "/" . $hash{0}; + if ( ! is_dir( $dest ) ) { mkdir( $dest, 0777 ); } + $dest .= "/" . substr( $hash, 0, 2 ); + if ( ! is_dir( $dest ) ) { mkdir( $dest, 0777 ); } + + umask( $oldumask ); + return $dest; +} + +function wfImageArchiveDir( $fname ) +{ + global $wgUploadDirectory; + + $hash = md5( $fname ); + $oldumask = umask(0); + $archive = "{$wgUploadDirectory}/archive"; + if ( ! is_dir( $archive ) ) { mkdir( $archive, 0777 ); } + $archive .= "/" . $hash{0}; + if ( ! is_dir( $archive ) ) { mkdir( $archive, 0777 ); } + $archive .= "/" . substr( $hash, 0, 2 ); + if ( ! is_dir( $archive ) ) { mkdir( $archive, 0777 ); } + + umask( $oldumask ); + return $archive; +} + +function wfRecordUpload( $name, $oldver, $size, $desc ) +{ + global $wgUser, $wgLang, $wgTitle, $wgOut, $wgDeferredUpdateList; + $fname = "wfRecordUpload"; + + $sql = "SELECT img_name,img_size,img_timestamp,img_description,img_user," . + "img_user_text FROM image WHERE img_name='" . wfStrencode( $name ) . "'"; + $res = wfQuery( $sql, $fname ); + + if ( 0 == wfNumRows( $res ) ) { + $sql = "INSERT INTO image (img_name,img_size,img_timestamp," . + "img_description,img_user,img_user_text) VALUES ('" . + wfStrencode( $name ) . "',{$size},'" . date( "YmdHis" ) . "','" . + wfStrencode( $desc ) . "', '" . $wgUser->getID() . + "', '" . wfStrencode( $wgUser->getName() ) . "')"; + wfQuery( $sql, $fname ); + + $sql = "SELECT cur_id,cur_text FROM cur WHERE cur_namespace=" . + Namespace::getImage() . " AND cur_title='" . + wfStrencode( $name ) . "'"; + $res = wfQuery( $sql, $fname ); + if ( 0 == wfNumRows( $res ) ) { + $now = wfTimestampNow(); + $won = wfInvertTimestamp( $now ); + $common = + Namespace::getImage() . ",'" . + wfStrencode( $name ) . "','" . + wfStrencode( $desc ) . "','" . $wgUser->getID() . "','" . + wfStrencode( $wgUser->getName() ) . "','" . $now . + "',1"; + $sql = "INSERT INTO cur (cur_namespace,cur_title," . + "cur_comment,cur_user,cur_user_text,cur_timestamp,cur_is_new," . + "cur_text,inverse_timestamp) VALUES (" . + $common . + ",'" . wfStrencode( $desc ) . "','{$won}')"; + wfQuery( $sql, $fname ); + $id = wfInsertId() or 0; # We should throw an error instead + $sql = "INSERT INTO recentchanges (rc_namespace,rc_title, + rc_comment,rc_user,rc_user_text,rc_timestamp,rc_new, + rc_cur_id,rc_cur_time) VALUES ({$common},{$id},'{$now}')"; + wfQuery( $sql, $fname ); + $u = new SearchUpdate( $id, $name, $desc ); + $u->doUpdate(); + } + } else { + $s = wfFetchObject( $res ); + + $sql = "INSERT INTO oldimage (oi_name,oi_archive_name,oi_size," . + "oi_timestamp,oi_description,oi_user,oi_user_text) VALUES ('" . + wfStrencode( $s->img_name ) . "','" . + wfStrencode( $oldver ) . + "',{$s->img_size},'{$s->img_timestamp}','" . + wfStrencode( $s->img_description ) . "','" . + wfStrencode( $s->img_user ) . "','" . + wfStrencode( $s->img_user_text) . "')"; + wfQuery( $sql, $fname ); + + $sql = "UPDATE image SET img_size={$size}," . + "img_timestamp='" . date( "YmdHis" ) . "',img_user='" . + $wgUser->getID() . "',img_user_text='" . + wfStrencode( $wgUser->getName() ) . "', img_description='" . + wfStrencode( $desc ) . "' WHERE img_name='" . + wfStrencode( $name ) . "'"; + wfQuery( $sql, $fname ); + } + + $log = new LogPage( wfMsg( "uploadlogpage" ), wfMsg( "uploadlogpagetext" ) ); + $da = str_replace( "$1", "[[:" . $wgLang->getNsText( + Namespace::getImage() ) . ":{$name}|{$name}]]", + wfMsg( "uploadedimage" ) ); + $ta = str_replace( "$1", $name, wfMsg( "uploadedimage" ) ); + $log->addEntry( $da, $desc, $ta ); +} + + +/* Some generic result counters, pulled out of SearchEngine */ + +function wfShowingResults( $offset, $limit ) +{ + $top = str_replace( "$1", $limit, wfMsg( "showingresults" ) ); + $top = str_replace( "$2", $offset+1, $top ); + return $top; +} + +function wfViewPrevNext( $offset, $limit, $link, $query = "" ) +{ + global $wgUser; + $prev = str_replace( "$1", $limit, wfMsg( "prevn" ) ); + $next = str_replace( "$1", $limit, wfMsg( "nextn" ) ); + + $sk = $wgUser->getSkin(); + if ( 0 != $offset ) { + $po = $offset - $limit; + if ( $po < 0 ) { $po = 0; } + $q = "limit={$limit}&offset={$po}"; + if ( "" != $query ) { $q .= "&{$query}"; } + $plink = "{$prev}"; + } else { $plink = $prev; } + + $no = $offset + $limit; + $q = "limit={$limit}&offset={$no}"; + if ( "" != $query ) { $q .= "&{$query}"; } + + $nlink = "{$next}"; + $nums = wfNumLink( $offset, 20, $link , $query ) . " | " . + wfNumLink( $offset, 50, $link, $query ) . " | " . + wfNumLink( $offset, 100, $link, $query ) . " | " . + wfNumLink( $offset, 250, $link, $query ) . " | " . + wfNumLink( $offset, 500, $link, $query ); + + $sl = str_replace( "$1", $plink, wfMsg( "viewprevnext" ) ); + $sl = str_replace( "$2", $nlink, $sl ); + $sl = str_replace( "$3", $nums, $sl ); + return $sl; +} + +function wfNumLink( $offset, $limit, $link, $query = "" ) +{ + global $wgUser; + if ( "" == $query ) { $q = ""; } + else { $q = "{$query}&"; } + $q .= "limit={$limit}&offset={$offset}"; + + $s = "{$limit}"; + return $s; +} + +?> diff --git a/includes/Interwiki.php b/includes/Interwiki.php new file mode 100644 index 0000000000..417159e38f --- /dev/null +++ b/includes/Interwiki.php @@ -0,0 +1,256 @@ + "http://www.ourpla.net/cgi-bin/pikie.cgi?$1", + "AcadWiki" => "http://xarch.tu-graz.ac.at/autocad/wiki/$1", + "Acronym" => "http://www.acronymfinder.com/af-query.asp?String=exact&Acronym=$1", + "Advogato" => "http://www.advogato.org/$1", + "AIWiki" => "http://www.ifi.unizh.ch/ailab/aiwiki/aiw.cgi?$1", + "ALife" => "http://news.alife.org/wiki/index.php?$1", + "AndStuff" => "http://andstuff.org/wiki.php?$1", + "Annotation" => "http://bayle.stanford.edu/crit/nph-med.cgi/$1", + "AnnotationWiki" => "http://www.seedwiki.com/page.cfm?wikiid=368&doc=$1", + "AwarenessWiki" => "http://taoriver.net/aware/$1", + "BenefitsWiki" => "http://www.benefitslink.com/cgi-bin/wiki.cgi?$1", + "BridgesWiki" => "http://c2.com/w2/bridges/$1", + "C2find" => "http://c2.com/cgi/wiki?FindPage&value=$1", + "Cache" => "http://www.google.com/search?q=cache:$1", + "CLiki" => "http://ww.telent.net/cliki/$1", + "CmWiki" => "http://www.ourpla.net/cgi-bin/wiki.pl?$1", + "CreationMatters" => "http://www.ourpla.net/cgi-bin/wiki.pl?$1", + "DejaNews" => "http://www.deja.com/=dnc/getdoc.xp?AN=$1", + "DeWikiPedia" => "http://www.wikipedia.de/wiki.cgi?$1", + "Dictionary" => "http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query=$1", + "DiveIntoOsx" => "http://diveintoosx.org/$1", + "DocBook" => "http://docbook.org/wiki/moin.cgi/$1", + "DolphinWiki" => "http://www.object-arts.com/wiki/html/Dolphin/$1", + "EfnetCeeWiki" => "http://purl.net/wiki/c/$1", + "EfnetCppWiki" => "http://purl.net/wiki/cpp/$1", + "EfnetPythonWiki" => "http://purl.net/wiki/python/$1", + "EfnetXmlWiki" => "http://purl.net/wiki/xml/$1", + "EljWiki" => "http://elj.sourceforge.net/phpwiki/index.php/$1", + "EmacsWiki" => "http://www.emacswiki.org/cgi-bin/wiki.pl?$1", + "FinalEmpire" => "http://final-empire.sourceforge.net/cgi-bin/wiki.pl?$1", + "Foldoc" => "http://www.foldoc.org/foldoc/foldoc.cgi?$1", + "FoxWiki" => "http://fox.wikis.com/wc.dll?Wiki~$1", + "FreeBSDman" => "http://www.FreeBSD.org/cgi/man.cgi?apropos=1&query=$1", + "Google" => "http://www.google.com/search?q=$1", + "GoogleGroups" => "http://groups.google.com/groups?q=$1", + "GreenCheese" => "http://www.greencheese.org/$1", + "HammondWiki" => "http://www.dairiki.org/HammondWiki/index.php3?$1", + "Haribeau" => "http://wiki.haribeau.de/cgi-bin/wiki.pl?$1", + "IAWiki" => "http://www.IAwiki.net/$1", + "IMDB" => "http://us.imdb.com/Title?$1", + "JargonFile" => "http://sunir.org/apps/meta.pl?wiki=JargonFile&redirect=$1", + "JiniWiki" => "http://www.cdegroot.com/cgi-bin/jini?$1", + "JspWiki" => "http://www.ecyrd.com/JSPWiki/Wiki.jsp?page=$1", + "KmWiki" => "http://www.voght.com/cgi-bin/pywiki?$1", + "KnowHow" => "http://www2.iro.umontreal.ca/~paquetse/cgi-bin/wiki.cgi?$1", + "LanifexWiki" => "http://opt.lanifex.com/cgi-bin/wiki.pl?$1", + "LegoWiki" => "http://www.object-arts.com/wiki/html/Lego-Robotics/$1", + "LinuxWiki" => "http://www.linuxwiki.de/$1", + "LugKR" => "http://lug-kr.sourceforge.net/cgi-bin/lugwiki.pl?$1", + "MathSongsWiki" => "http://SeedWiki.com/page.cfm?wikiid=237&doc=$1", + "MbTest" => "http://www.usemod.com/cgi-bin/mbtest.pl?$1", + "MeatBall" => "http://www.usemod.com/cgi-bin/mb.pl?$1", + "MetaWiki" => "http://sunir.org/apps/meta.pl?$1", + "MetaWikiPedia" => "http://meta.wikipedia.com/wiki/$1", + "MoinMoin" => "http://purl.net/wiki/moin/$1", + "MuWeb" => "http://www.dunstable.com/scripts/MuWebWeb?$1", + "NetVillage" => "http://www.netbros.com/?$1", + "OpenWiki" => "http://openwiki.com/?$1", + "OrgPatterns" => "http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns?$1", + "PangalacticOrg" => "http://www.pangalactic.org/Wiki/$1", + "PersonalTelco" => "http://www.personaltelco.net/index.cgi/$1", + "PhpWiki" => "http://phpwiki.sourceforge.net/phpwiki/index.php?$1", + "Pikie" => "http://pikie.darktech.org/cgi/pikie?$1", + "PPR" => "http://c2.com/cgi/wiki?$1", + "PurlNet" => "http://purl.oclc.org/NET/$1", + "PythonInfo" => "http://www.python.org/cgi-bin/moinmoin/$1", + "PythonWiki" => "http://www.pythonwiki.de/$1", + "PyWiki" => "http://www.voght.com/cgi-bin/pywiki?$1", + "SeaPig" => "http://www.seapig.org/ $1", + "SeattleWireless" => "http://seattlewireless.net/?$1", + "SenseisLibrary" => "http://senseis.xmp.net/?$1", + "Shakti" => "http://cgi.algonet.se/htbin/cgiwrap/pgd/ShaktiWiki/$1", + "SourceForge" => "http://sourceforge.net/$1", + "Squeak" => "http://minnow.cc.gatech.edu/squeak/$1", + "StrikiWiki" => "http://ch.twi.tudelft.nl/~mostert/striki/teststriki.pl?$1", + "SVGWiki" => "http://www.protocol7.com/svg-wiki/default.asp?$1", + "Tavi" => "http://tavi.sourceforge.net/index.php?$1", + "TmNet" => "http://www.technomanifestos.net/?$1", + "TMwiki" => "http://www.EasyTopicMaps.com/?page=$1", + "TWiki" => "http://twiki.org/cgi-bin/view/$1", + "TwistedWiki" => "http://purl.net/wiki/twisted/$1", + "Unreal" => "http://wiki.beyondunreal.com/wiki/$1", + "UseMod" => "http://www.usemod.com/cgi-bin/wiki.pl?$1", + "VisualWorks" => "http://wiki.cs.uiuc.edu/VisualWorks/$1", + "WebDevWikiNL" => "http://www.promo-it.nl/WebDevWiki/index.php?page=$1", + "WebSeitzWiki" => "http://webseitz.fluxent.com/wiki/$1", + "Why" => "http://clublet.com/c/c/why?$1", + "Wiki" => "http://c2.com/cgi/wiki?$1", + "WikiPedia" => "http://www.wikipedia.com/wiki/$1", + "WikiWorld" => "http://WikiWorld.com/wiki/index.php/$1", + "YpsiEyeball" => "http://sknkwrks.dyndns.org:1957/writewiki/wiki.pl?$1", + "ZWiki" => "http://www.zwiki.org/$1", + + # Some custom additions: + "ReVo" => "http://purl.org/NET/voko/revo/art/$1.html", + # eg [[ReVo:cerami]], [[ReVo:astero]] - note X-sensitive! + "EcheI" => "http://www.ikso.net/cgi-bin/wiki.pl?$1", + "E\xc4\x89eI" => "http://www.ikso.net/cgi-bin/wiki.pl?$1", + "UnuMondo" => "http://unumondo.com/cgi-bin/wiki.pl?$1", # X-sensitive! + "JEFO" => "http://esperanto.jeunes.free.fr/vikio/index.php?$1", + "PMEG" => "http://www.bertilow.com/pmeg/$1.php", + # ekz [[PMEG:gramatiko/kunligaj vortetoj/au]] + "EnciclopediaLibre" => "http://enciclopedia.us.es/wiki.phtml?title=$1", + + # Wikipedia-specific stuff: + # Special cases + "w" => "http://www.wikipedia.org/wiki/$1", + "m" => "http://meta.wikipedia.org/wiki/$1", + "meta" => "http://meta.wikipedia.org/wiki/$1", + "sep11" => "http://sep11.wikipedia.org/wiki/$1", + "simple"=> "http://simple.wikipedia.com/wiki.cgi?$1", + "wiktionary" => "http://wiktionary.wikipedia.org/wiki/$1", + + # ISO 639 2-letter language codes + "aa" => "http://aa.wikipedia.com/wiki.cgi?$1", + "ab" => "http://ab.wikipedia.com/wiki.cgi?$1", + "af" => "http://af.wikipedia.com/wiki.cgi?$1", + "am" => "http://am.wikipedia.com/wiki.cgi?$1", + "ar" => "http://ar.wikipedia.com/wiki.cgi?$1", + "as" => "http://as.wikipedia.com/wiki.cgi?$1", + "ay" => "http://ay.wikipedia.com/wiki.cgi?$1", + "az" => "http://az.wikipedia.com/wiki.cgi?$1", + "ba" => "http://ba.wikipedia.com/wiki.cgi?$1", + "be" => "http://be.wikipedia.com/wiki.cgi?$1", + "bh" => "http://bh.wikipedia.com/wiki.cgi?$1", + "bi" => "http://bi.wikipedia.com/wiki.cgi?$1", + "bn" => "http://bn.wikipedia.com/wiki.cgi?$1", + "bs" => "http://bs.wikipedia.org/wiki/$1", + "bo" => "http://bo.wikipedia.com/wiki.cgi?$1", + "ca" => "http://ca.wikipedia.com/wiki.cgi?$1", + "co" => "http://co.wikipedia.com/wiki.cgi?$1", + "cs" => "http://cs.wikipedia.org/wiki/$1", + "cy" => "http://cy.wikipedia.com/wiki.cgi?$1", + "da" => "http://da.wikipedia.org/wiki/$1", + "de" => "http://de.wikipedia.org/wiki/$1", + "dk" => "http://da.wikipedia.org/wiki/$1", + "dz" => "http://dz.wikipedia.com/wiki.cgi?$1", + "el" => "http://el.wikipedia.org/wiki/$1", + "en" => "http://www.wikipedia.org/wiki/$1", # May in future be renamed to en.wikipedia.org; should work as alternate + "eo" => "http://eo.wikipedia.org/wiki/$1", + "es" => "http://es.wikipedia.org/wiki/$1", + "et" => "http://et.wikipedia.com/wiki.cgi?$1", + "eu" => "http://eu.wikipedia.com/wiki.cgi?$1", + "fa" => "http://fa.wikipedia.com/wiki.cgi?$1", + "fi" => "http://fi.wikipedia.com/wiki.cgi?$1", + "fj" => "http://fj.wikipedia.com/wiki.cgi?$1", + "fo" => "http://fo.wikipedia.com/wiki.cgi?$1", + "fr" => "http://fr.wikipedia.org/wiki/$1", + "fy" => "http://fy.wikipedia.com/wiki.cgi?$1", + "ga" => "http://ga.wikipedia.com/wiki.cgi?$1", + "gl" => "http://gl.wikipedia.com/wiki.cgi?$1", + "gn" => "http://gn.wikipedia.com/wiki.cgi?$1", + "gu" => "http://gu.wikipedia.com/wiki.cgi?$1", + "ha" => "http://ha.wikipedia.com/wiki.cgi?$1", + "he" => "http://he.wikipedia.com/wiki.cgi?$1", + "hi" => "http://hi.wikipedia.com/wiki.cgi?$1", + "hr" => "http://hr.wikipedia.org/wiki/$1", + "hu" => "http://hu.wikipedia.com/wiki.cgi?$1", + "hy" => "http://hy.wikipedia.com/wiki.cgi?$1", + "ia" => "http://ia.wikipedia.com/wiki.cgi?$1", + "id" => "http://id.wikipedia.com/wiki.cgi?$1", + "ik" => "http://ik.wikipedia.com/wiki.cgi?$1", + "is" => "http://is.wikipedia.com/wiki.cgi?$1", + "it" => "http://it.wikipedia.com/wiki.cgi?$1", + "iu" => "http://iu.wikipedia.com/wiki.cgi?$1", + "ja" => "http://ja.wikipedia.org/wiki/$1", + "jv" => "http://jv.wikipedia.com/wiki.cgi?$1", + "ka" => "http://ka.wikipedia.com/wiki.cgi?$1", + "kk" => "http://kk.wikipedia.com/wiki.cgi?$1", + "kl" => "http://kl.wikipedia.com/wiki.cgi?$1", + "km" => "http://km.wikipedia.com/wiki.cgi?$1", + "kn" => "http://kn.wikipedia.com/wiki.cgi?$1", + "ko" => "http://ko.wikipedia.org/wiki/$1", + "ks" => "http://ks.wikipedia.com/wiki.cgi?$1", + "ku" => "http://ku.wikipedia.com/wiki.cgi?$1", + "ky" => "http://ky.wikipedia.com/wiki.cgi?$1", + "la" => "http://la.wikipedia.com/wiki.cgi?$1", + "lo" => "http://lo.wikipedia.com/wiki.cgi?$1", + "lv" => "http://lv.wikipedia.com/wiki.cgi?$1", + "mg" => "http://mg.wikipedia.com/wiki.cgi?$1", + "mi" => "http://mi.wikipedia.com/wiki.cgi?$1", + "mk" => "http://mk.wikipedia.com/wiki.cgi?$1", + "ml" => "http://ml.wikipedia.org/wiki/$1", + "mn" => "http://mn.wikipedia.com/wiki.cgi?$1", + "mo" => "http://mo.wikipedia.com/wiki.cgi?$1", + "mr" => "http://mr.wikipedia.com/wiki.cgi?$1", + "ms" => "http://ms.wikipedia.org/wiki/$1", + "my" => "http://my.wikipedia.com/wiki.cgi?$1", + "na" => "http://na.wikipedia.com/wiki.cgi?$1", + "ne" => "http://ne.wikipedia.com/wiki.cgi?$1", + "nl" => "http://nl.wikipedia.org/wiki/$1", + "no" => "http://no.wikipedia.com/wiki.cgi?$1", + "oc" => "http://oc.wikipedia.com/wiki.cgi?$1", + "om" => "http://om.wikipedia.com/wiki.cgi?$1", + "or" => "http://or.wikipedia.com/wiki.cgi?$1", + "pa" => "http://pa.wikipedia.com/wiki.cgi?$1", + "pl" => "http://pl.wikipedia.org/wiki/$1", + "ps" => "http://ps.wikipedia.com/wiki.cgi?$1", + "pt" => "http://pt.wikipedia.com/wiki.cgi?$1", + "qu" => "http://qu.wikipedia.com/wiki.cgi?$1", + "rm" => "http://rm.wikipedia.com/wiki.cgi?$1", + "rn" => "http://rn.wikipedia.com/wiki.cgi?$1", + "ro" => "http://ro.wikipedia.com/wiki.cgi?$1", + "ru" => "http://ru.wikipedia.org/wiki/$1", + "rw" => "http://rw.wikipedia.com/wiki.cgi?$1", + "sa" => "http://sa.wikipedia.com/wiki.cgi?$1", + "sd" => "http://sd.wikipedia.com/wiki.cgi?$1", + "sg" => "http://sg.wikipedia.com/wiki.cgi?$1", + "sh" => "http://sh.wikipedia.org/wiki/$1", + "si" => "http://si.wikipedia.com/wiki.cgi?$1", + "sk" => "http://sk.wikipedia.com/wiki.cgi?$1", + "sl" => "http://sl.wikipedia.com/wiki.cgi?$1", + "sm" => "http://sm.wikipedia.com/wiki.cgi?$1", + "sn" => "http://sn.wikipedia.com/wiki.cgi?$1", + "so" => "http://so.wikipedia.com/wiki.cgi?$1", + "sq" => "http://sq.wikipedia.com/wiki.cgi?$1", + "sr" => "http://sr.wikipedia.org/wiki/$1", + "ss" => "http://ss.wikipedia.com/wiki.cgi?$1", + "st" => "http://st.wikipedia.com/wiki.cgi?$1", + "su" => "http://su.wikipedia.com/wiki.cgi?$1", + "sv" => "http://sv.wikipedia.org/wiki/$1", + "sw" => "http://sw.wikipedia.com/wiki.cgi?$1", + "ta" => "http://ta.wikipedia.com/wiki.cgi?$1", + "te" => "http://te.wikipedia.com/wiki.cgi?$1", + "tg" => "http://tg.wikipedia.com/wiki.cgi?$1", + "th" => "http://th.wikipedia.com/wiki.cgi?$1", + "ti" => "http://ti.wikipedia.com/wiki.cgi?$1", + "tk" => "http://tk.wikipedia.com/wiki.cgi?$1", + "tl" => "http://tl.wikipedia.com/wiki.cgi?$1", + "tn" => "http://tn.wikipedia.com/wiki.cgi?$1", + "to" => "http://to.wikipedia.com/wiki.cgi?$1", + "tr" => "http://tr.wikipedia.org/wiki/$1", + "ts" => "http://ts.wikipedia.com/wiki.cgi?$1", + "tt" => "http://tt.wikipedia.com/wiki.cgi?$1", + "tw" => "http://tw.wikipedia.com/wiki.cgi?$1", + "ug" => "http://ug.wikipedia.com/wiki.cgi?$1", + "uk" => "http://uk.wikipedia.com/wiki.cgi?$1", + "ur" => "http://ur.wikipedia.com/wiki.cgi?$1", + "uz" => "http://uz.wikipedia.com/wiki.cgi?$1", + "vi" => "http://vi.wikipedia.com/wiki.cgi?$1", + "vo" => "http://vo.wikipedia.com/wiki.cgi?$1", + "wo" => "http://wo.wikipedia.com/wiki.cgi?$1", + "xh" => "http://xh.wikipedia.com/wiki.cgi?$1", + "yi" => "http://yi.wikipedia.com/wiki.cgi?$1", + "yo" => "http://yo.wikipedia.com/wiki.cgi?$1", + "za" => "http://za.wikipedia.com/wiki.cgi?$1", + "zh" => "http://zh.wikipedia.org/wiki/$1", + "zu" => "http://zu.wikipedia.com/wiki.cgi?$1" +); +?> diff --git a/includes/LinkCache.php b/includes/LinkCache.php new file mode 100644 index 0000000000..af8505236a --- /dev/null +++ b/includes/LinkCache.php @@ -0,0 +1,109 @@ +mActive = true; + $this->mGoodLinks = array(); + $this->mBadLinks = array(); + $this->mImageLinks = array(); + } + + function getGoodLinkID( $title ) + { + if ( array_key_exists( $title, $this->mGoodLinks ) ) { + return $this->mGoodLinks[$title]; + } else { + return 0; + } + } + + function isBadLink( $title ) + { + return in_array( $title, $this->mBadLinks ); + } + + function addGoodLink( $id, $title ) + { + if ( $this->mActive ) { + $this->mGoodLinks[$title] = $id; + } + } + + function addBadLink( $title ) + { + if ( $this->mActive && ( ! $this->isBadLink( $title ) ) ) { + array_push( $this->mBadLinks, $title ); + } + } + + function addImageLink( $title ) + { + if ( $this->mActive ) { $this->mImageLinks[$title] = 1; } + } + + function clearBadLink( $title ) + { + $index = array_search( $title, $this->mBadLinks ); + if ( isset( $index ) ) { + unset( $this->mBadLinks[$index] ); + } + } + + function suspend() { $this->mActive = false; } + function resume() { $this->mActive = true; } + function getGoodLinks() { return $this->mGoodLinks; } + function getBadLinks() { return $this->mBadLinks; } + function getImageLinks() { return $this->mImageLinks; } + + function addLink( $title ) + { + if ( $this->isBadLink( $title ) ) { return 0; } + $id = $this->getGoodLinkID( $title ); + if ( 0 != $id ) { return $id; } + + $nt = Title::newFromDBkey( $title ); + $ns = $nt->getNamespace(); + $t = $nt->getDBkey(); + + if ( "" == $t ) { return 0; } + $sql = "SELECT HIGH_PRIORITY cur_id FROM cur WHERE cur_namespace=" . + "{$ns} AND cur_title='" . wfStrencode( $t ) . "'"; + $res = wfQuery( $sql, "LinkCache::addLink" ); + + if ( 0 == wfNumRows( $res ) ) { + $id = 0; + } else { + $s = wfFetchObject( $res ); + $id = $s->cur_id; + } + if ( 0 == $id ) { $this->addBadLink( $title ); } + else { $this->addGoodLink( $id, $title ); } + return $id; + } + + function preFill( $fromtitle ) + { + wfProfileIn( "LinkCache::preFill" ); + # Note -- $fromtitle is a Title *object* + $dbkeyfrom = wfStrencode( $fromtitle->getPrefixedDBKey() ); + $sql = "SELECT HIGH_PRIORITY cur_id,cur_namespace,cur_title + FROM cur,links + WHERE cur_id=l_to AND l_from='{$dbkeyfrom}'"; + $res = wfQuery( $sql, "LinkCache::preFill" ); + while( $s = wfFetchObject( $res ) ) { + $this->addGoodLink( $s->cur_id, + Title::makeName( $s->cur_namespace, $s->cur_title ) + ); + } + wfProfileOut(); + } + +} + +?> diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php new file mode 100644 index 0000000000..bf181ad6a5 --- /dev/null +++ b/includes/LinksUpdate.php @@ -0,0 +1,110 @@ +mId = $id; + $this->mTitle = $title; + } + + function doUpdate() + { + global $wgLinkCache, $wgDBtransactions; + $fname = "LinksUpdate::doUpdate"; + wfProfileIn( $fname ); + $t = wfStrencode( $this->mTitle ); + + if( $wgDBtransactions ) { + $sql = "BEGIN"; + wfQuery( $sql, $fname ); + } + + $sql = "DELETE FROM links WHERE l_from='{$t}'"; + wfQuery( $sql, $fname ); + + $a = $wgLinkCache->getGoodLinks(); + $sql = ""; + if ( 0 != count( $a ) ) { + $sql = "INSERT INTO links (l_from,l_to) VALUES "; + $first = true; + foreach( $a as $lt => $lid ) { + if ( ! $first ) { $sql .= ","; } + $first = false; + + $sql .= "('{$t}',{$lid})"; + } + } + if ( "" != $sql ) { wfQuery( $sql, $fname ); } + + $sql = "DELETE FROM brokenlinks WHERE bl_from={$this->mId}"; + wfQuery( $sql, $fname ); + + $a = $wgLinkCache->getBadLinks(); + $sql = ""; + if ( 0 != count ( $a ) ) { + $sql = "INSERT INTO brokenlinks (bl_from,bl_to) VALUES "; + $first = true; + foreach( $a as $blt ) { + $blt = wfStrencode( $blt ); + if ( ! $first ) { $sql .= ","; } + $first = false; + + $sql .= "({$this->mId},'{$blt}')"; + } + } + if ( "" != $sql ) { wfQuery( $sql, $fname ); } + + $sql = "DELETE FROM imagelinks WHERE il_from='{$t}'"; + wfQuery( $sql, $fname ); + + $a = $wgLinkCache->getImageLinks(); + $sql = ""; + if ( 0 != count ( $a ) ) { + $sql = "INSERT INTO imagelinks (il_from,il_to) VALUES "; + $first = true; + foreach( $a as $iname => $val ) { + $iname = wfStrencode( $iname ); + if ( ! $first ) { $sql .= ","; } + $first = false; + + $sql .= "('{$t}','{$iname}')"; + } + } + if ( "" != $sql ) { wfQuery( $sql, $fname ); } + + $sql = "SELECT bl_from FROM brokenlinks WHERE bl_to='{$t}'"; + $res = wfQuery( $sql, $fname ); + if ( 0 == wfNumRows( $res ) ) { return; } + + $sql = "INSERT INTO links (l_from,l_to) VALUES "; + $now = wfTimestampNow(); + $sql2 = "UPDATE cur SET cur_touched='{$now}' WHERE cur_id IN ("; + $first = true; + while ( $row = wfFetchObject( $res ) ) { + if ( ! $first ) { $sql .= ","; $sql2 .= ","; } + $first = false; + $nl = wfStrencode( Article::nameOf( $row->bl_from ) ); + + $sql .= "('{$nl}',{$this->mId})"; + $sql2 .= $row->bl_from; + } + $sql2 .= ")"; + wfQuery( $sql, $fname ); + wfQuery( $sql2, $fname ); + + $sql = "DELETE FROM brokenlinks WHERE bl_to='{$t}'"; + wfQuery( $sql, $fname ); + + if( $wgDBtransactions ) { + $sql = "COMMIT"; + wfQuery( $sql, $fname ); + } + wfProfileOut(); + } +} + +?> diff --git a/includes/LogPage.php b/includes/LogPage.php new file mode 100644 index 0000000000..eca14cb48f --- /dev/null +++ b/includes/LogPage.php @@ -0,0 +1,118 @@ +\n" ) + { + # For now, assume title is correct dbkey + # and log pages always go in Wikipedia namespace + $this->mTitle = $title; + $this->mId = 0; + $this->mUpdateRecentChanges = true ; + $this->mContentLoaded = false; + $this->getContent( $defaulttext ); + } + + function getContent( $defaulttext = "
    \n
" ) + { + $sql = "SELECT cur_id,cur_text FROM cur " . + "WHERE cur_namespace=" . Namespace::getWikipedia() . " AND " . + "cur_title='" . wfStrencode($this->mTitle ) . "'"; + $res = wfQuery( $sql, "LogPage::getContent" ); + + if( wfNumRows( $res ) > 0 ) { + $s = wfFetchObject( $res ); + $this->mId = $s->cur_id; + $this->mContent = $s->cur_text; + } else { + $this->mId = 0; + $this->mContent = $defaulttext; + } + $this->mContentLoaded = true; # Well, sort of + + return $this->mContent; + } + + function saveContent() + { + global $wgUser; + $fname = "LogPage::saveContent"; + $uid = $wgUser->getID(); + $ut = wfStrencode( $wgUser->getName() ); + + if( !$this->mContentLoaded ) return false; + $now = date( "YmdHis" ); + $won = wfInvertTimestamp( $now ); + if($this->mId == 0) { + $sql = "INSERT INTO cur (cur_timestamp,cur_user,cur_user_text, + cur_namespace,cur_title,cur_text,cur_comment,cur_restrictions,inverse_timestamp) + VALUES ('{$now}', {$uid}, '{$ut}', " . + Namespace::getWikipedia() . ", '" . + wfStrencode( $this->mTitle ) . "', '" . + wfStrencode( $this->mContent ) . "', '" . + wfStrencode( $this->mComment ) . "', 'sysop', '{$won}')"; + wfQuery( $sql, $fname ); + $this->mId = wfInsertId(); + } else { + $sql = "UPDATE cur SET cur_timestamp='{$now}', " . + "cur_user={$uid}, cur_user_text='{$ut}', " . + "cur_text='" . wfStrencode( $this->mContent ) . "', " . + "cur_comment='" . wfStrencode( $this->mComment ) . "', " . + "cur_restrictions='sysop', inverse_timestamp='{$won}' " . + "WHERE cur_id={$this->mId}"; + wfQuery( $sql, $fname ); + } + + # And update recentchanges + if ( $this->mUpdateRecentChanges ) { + $sql = "INSERT INTO recentchanges (rc_timestamp,rc_cur_time, + rc_user,rc_user_text,rc_namespace,rc_title,rc_comment, + rc_cur_id) VALUES ('{$now}','{$now}',{$uid},'{$ut}',4,'" . + wfStrencode( $this->mTitle ) . "','" . + wfStrencode( $this->mComment ) . "',{$this->mId})"; + wfQuery( $sql, $fname ); + } + return true; + } + + function addEntry( $action, $comment, $textaction = "" ) + { + global $wgLang, $wgUser; + $this->getContent(); + + $ut = $wgUser->getName(); + $uid = $wgUser->getID(); + if( $uid ) { + $ul = "[[" . + $wgLang->getNsText( Namespace::getUser() ) . + ":{$ut}|{$ut}]]"; + } else { + $ul = $ut; + } + $d = $wgLang->timeanddate( date( "YmdHis" ), false ); + + preg_match( "/^(.*?)
    (.*)$/sD", $this->mContent, $m ); + + if($textaction) + $this->mComment = $textaction; + else + $this->mComment = $action; + + if ( "" == $comment ) { + $inline = ""; + } else { + $inline = " ({$comment})"; + $this->mComment .= ": {$comment}"; + } + $this->mContent = "{$m[1]}
    • {$d} {$ul} {$action}{$inline}
    • \n{$m[2]}"; + + # TODO: automatic log rotation... + + return $this->saveContent(); + } +} + +?> diff --git a/includes/Namespace.php b/includes/Namespace.php new file mode 100644 index 0000000000..03b351f9b1 --- /dev/null +++ b/includes/Namespace.php @@ -0,0 +1,49 @@ + 5 ) { return false; } + return true; + } + + function isTalk( $index ) + { + if ( 1 == $index || 3 == $index || 5 == $index || 7 == $index ) { + return true; + } + return false; + } + + # Get the talk namespace corresponding to the given index + # + function getTalk( $index ) + { + if ( Namespace::isTalk( $index ) ) { + return $index; + } else { + return $index + 1; + } + } + + function getSubject( $index ) + { + if ( Namespace::isTalk( $index ) ) { + return $index - 1; + } else { + return $index; + } + } +} + +?> diff --git a/includes/OutputPage.php b/includes/OutputPage.php new file mode 100644 index 0000000000..b1c97a1f81 --- /dev/null +++ b/includes/OutputPage.php @@ -0,0 +1,1295 @@ +"; +} + +function renderMath( $tex ) +{ + global $wgUser, $wgMathDirectory, $wgTmpDirectory, $wgInputEncoding; + $mf = wfMsg( "math_failure" ); + $munk = wfMsg( "math_unknown_error" ); + + $fname = "renderMath"; + + $math = $wgUser->getOption("math"); + if ($math == 3) + return ('$ '.wfEscapeHTML($tex).' $'); + + $md5 = md5($tex); + $md5_sql = mysql_escape_string(pack("H32", $md5)); + if ($math == 0) + $sql = "SELECT math_outputhash FROM math WHERE math_inputhash = '".$md5_sql."'"; + else + $sql = "SELECT math_outputhash,math_html_conservativeness,math_html FROM math WHERE math_inputhash = '".$md5_sql."'"; + + $res = wfQuery( $sql, $fname ); + if ( wfNumRows( $res ) == 0 ) + { + $cmd = "./math/texvc ".escapeshellarg($wgTmpDirectory)." ". + escapeshellarg($wgMathDirectory)." ".escapeshellarg($tex)." ".escapeshellarg($wgInputEncoding); + $contents = `$cmd`; + + if (strlen($contents) == 0) + return "".$mf." (".$munk."): ".wfEscapeHTML($tex).""; + $retval = substr ($contents, 0, 1); + if (($retval == "C") || ($retval == "M") || ($retval == "L")) { + if ($retval == "C") + $conservativeness = 2; + else if ($retval == "M") + $conservativeness = 1; + else + $conservativeness = 0; + $outdata = substr ($contents, 33); + + $i = strpos($outdata, "\000"); + + $outhtml = substr($outdata, 0, $i); + $mathml = substr($outdata, $i+1); + + $sql_html = "'".mysql_escape_string($outhtml)."'"; + $sql_mathml = "'".mysql_escape_string($mathml)."'"; + } else if (($retval == "c") || ($retval == "m") || ($retval == "l")) { + $outhtml = substr ($contents, 33); + if ($retval == "c") + $conservativeness = 2; + else if ($retval == "m") + $conservativeness = 1; + else + $conservativeness = 0; + $sql_html = "'".mysql_escape_string($outhtml)."'"; + $mathml = ''; + $sql_mathml = 'NULL'; + } else if ($retval == "X") { + $outhtml = ''; + $mathml = substr ($contents, 33); + $sql_html = 'NULL'; + $sql_mathml = "'".mysql_escape_string($mathml)."'"; + $conservativeness = 0; + } else if ($retval == "+") { + $outhtml = ''; + $mathml = ''; + $sql_html = 'NULL'; + $sql_mathml = 'NULL'; + $conservativeness = 0; + } else { + if ($retval == "E") + $errmsg = wfMsg( "math_lexing_error" ); + else if ($retval == "S") + $errmsg = wfMsg( "math_syntax_error" ); + else if ($retval == "F") + $errmsg = wfMsg( "math_unknown_function" ); + else + $errmsg = $munk; + return "

      ".$mf." (".$errmsg.substr($contents, 1)."): ".wfEscapeHTML($tex)."

      "; + } + + $outmd5 = substr ($contents, 1, 32); + if (!preg_match("/^[a-f0-9]{32}$/", $outmd5)) + return "".$mf." (".$munk."): ".wfEscapeHTML($tex).""; + + $outmd5_sql = mysql_escape_string(pack("H32", $outmd5)); + + $sql = "INSERT INTO math VALUES ('".$md5_sql."', '".$outmd5_sql."', ".$conservativeness.", ".$sql_html.", ".$sql_mathml.")"; + + $res = wfQuery( $sql, $fname ); + # we don't really care if it fails + + if (($math == 0) || ($rpage->math_html == '') || (($math == 1) && ($conservativeness != 2)) || (($math == 4) && ($conservativeness == 0))) + return linkToMathImage($tex, $outmd5); + else + return $outhtml; + } else { + $rpage = wfFetchObject ( $res ); + $outputhash = unpack( "H32md5", $rpage->math_outputhash . " " ); + $outputhash = $outputhash ['md5']; + + if (($math == 0) || ($rpage->math_html == '') || (($math == 1) && ($rpage->math_html_conservativeness != 2)) || (($math == 4) && ($rpage->math_html_conservativeness == 0))) + return linkToMathImage ( $tex, $outputhash ); + else + return $rpage->math_html; + } +} + +class OutputPage { + var $mHeaders, $mCookies, $mMetatags, $mKeywords; + var $mLinktags, $mPagetitle, $mBodytext, $mDebugtext; + var $mHTMLtitle, $mRobotpolicy, $mIsarticle, $mPrintable; + var $mSubtitle, $mRedirect, $mAutonumber, $mHeadtext; + var $mLastModified; + + var $mDTopen, $mLastSection; # Used for processing DL, PRE + var $mLanguageLinks, $mSupressQuickbar; + + function OutputPage() + { + $this->mHeaders = $this->mCookies = $this->mMetatags = + $this->mKeywords = $this->mLinktags = array(); + $this->mHTMLtitle = $this->mPagetitle = $this->mBodytext = + $this->mLastSection = $this->mRedirect = $this->mLastModified = + $this->mSubtitle = $this->mDebugtext = $this->mRobotpolicy = ""; + $this->mIsarticle = $this->mPrintable = true; + $this->mSupressQuickbar = $this->mDTopen = $this->mPrintable = false; + $this->mLanguageLinks = array(); + $this->mAutonumber = 0; + } + + function addHeader( $name, $val ) { array_push( $this->mHeaders, "$name: $val" ) ; } + function addCookie( $name, $val ) { array_push( $this->mCookies, array( $name, $val ) ); } + function redirect( $url ) { $this->mRedirect = $url; } + + # To add an http-equiv meta tag, precede the name with "http:" + function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); } + function addKeyword( $text ) { array_push( $this->mKeywords, $text ); } + function addLink( $rel, $rev, $target ) { array_push( $this->mLinktags, array( $rel, $rev, $target ) ); } + + function checkLastModified ( $timestamp ) + { + global $wgLang, $wgCachePages, $wgUser; + if( !$wgCachePages ) return; + if( preg_match( '/MSIE ([1-4]|5\.0)/', $_SERVER["HTTP_USER_AGENT"] ) ) { + # IE 5.0 has probs with our caching + return; + } + + if( $_SERVER["HTTP_IF_MODIFIED_SINCE"] != "" ) { + $ismodsince = wfUnix2Timestamp( strtotime( $_SERVER["HTTP_IF_MODIFIED_SINCE"] ) ); + $lastmod = gmdate( "D, j M Y H:i:s", wfTimestamp2Unix( + max( $timestamp, $wgUser->mTouched ) ) ) . " GMT"; + + if( ($ismodsince >= $timestamp ) and $wgUser->validateCache( $ismodsince ) ) { + # Make sure you're in a place you can leave when you call us! + header( "HTTP/1.0 304 Not Modified" ); + header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page! + header( "Cache-Control: private, must-revalidate, max-age=0" ); + header( "Last-Modified: {$lastmod}" ); + #wfDebug( "CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false ); + exit; + } else { + #wfDebug( "READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false ); + $this->mLastModified = $lastmod; + } + } + } + + function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; } + function setHTMLtitle( $name ) { $this->mHTMLtitle = $name; } + function setPageTitle( $name ) { $this->mPagetitle = $name; } + function getPageTitle() { return $this->mPagetitle; } + function setSubtitle( $str ) { $this->mSubtitle = $str; } + function getSubtitle() { return $this->mSubtitle; } + function setArticleFlag( $v ) { $this->mIsarticle = $v; } + function isArticle() { return $this->mIsarticle; } + function setPrintable() { $this->mPrintable = true; } + function isPrintable() { return $this->mPrintable; } + + function getLanguageLinks() { + global $wgUseNewInterlanguage, $wgTitle, $wgLanguageCode; + global $wgDBconnection, $wgDBname, $wgDBintlname; + + if ( ! $wgUseNewInterlanguage ) + return $this->mLanguageLinks; + + mysql_select_db( $wgDBintlname, $wgDBconnection ) or die( + htmlspecialchars(mysql_error()) ); + + $list = array(); + $sql = "SELECT * FROM ilinks WHERE lang_from=\"" . + "{$wgLanguageCode}\" AND title_from=\"" . $wgTitle->getDBkey() . "\""; + $res = mysql_query( $sql, $wgDBconnection ); + + while ( $q = mysql_fetch_object ( $res ) ) { + $list[] = $q->lang_to . ":" . $q->title_to; + } + mysql_free_result( $res ); + mysql_select_db( $wgDBname, $wgDBconnection ) or die( + htmlspecialchars(mysql_error()) ); + + return $list; + } + + function supressQuickbar() { $this->mSupressQuickbar = true; } + function isQuickbarSupressed() { return $this->mSupressQuickbar; } + + function addHTML( $text ) { $this->mBodytext .= $text; } + function addHeadtext( $text ) { $this->mHeadtext .= $text; } + function debug( $text ) { $this->mDebugtext .= $text; } + + # First pass--just handle sections, pass the rest off + # to doWikiPass2() which does all the real work. + # + + function addWikiText( $text, $linestart = true ) + { + global $wgUseTeX; + wfProfileIn( "OutputPage::addWikiText" ); + $unique = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4"; + $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO"; + $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp"; + $nwlist = array(); + $nwsecs = 0; + $mathlist = array(); + $mathsecs = 0; + $prelist = array (); + $presecs = 0; + $stripped = ""; + $stripped2 = ""; + $stripped3 = ""; + + while ( "" != $text ) { + $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 ); + $stripped .= $p[0]; + if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $text = ""; } + else { + $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 ); + ++$nwsecs; + $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]); + $stripped .= $unique; + $text = $q[1]; + } + } + + if( $wgUseTeX ) { + while ( "" != $stripped ) { + $p = preg_split( "/<\\s*math\\s*>/i", $stripped, 2 ); + $stripped2 .= $p[0]; + if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped = ""; } + else { + $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 ); + ++$mathsecs; + $mathlist[$mathsecs] = renderMath($q[0]); + $stripped2 .= $unique2; + $stripped = $q[1]; + } + } + } else { + $stripped2 = $stripped; + } + + while ( "" != $stripped2 ) { + $p = preg_split( "/<\\s*pre\\s*>/i", $stripped2, 2 ); + $stripped3 .= $p[0]; + if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped2 = ""; } + else { + $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 ); + ++$presecs; + $prelist[$presecs] = "
      ". wfEscapeHTMLTagsOnly($q[0]). "
      "; + $stripped3 .= $unique3; + $stripped2 = $q[1]; + } + } + + $text = $this->doWikiPass2( $stripped3, $linestart ); + + for ( $i = 1; $i <= $presecs; ++$i ) { + $text = preg_replace( "/{$unique3}/", str_replace( '$', '\$', $prelist[$i] ), $text, 1 ); + } + + for ( $i = 1; $i <= $mathsecs; ++$i ) { + $text = preg_replace( "/{$unique2}/", str_replace( '$', '\$', $mathlist[$i] ), $text, 1 ); + } + + for ( $i = 1; $i <= $nwsecs; ++$i ) { + $text = preg_replace( "/{$unique}/", str_replace( '$', '\$', $nwlist[$i] ), $text, 1 ); + } + $this->addHTML( $text ); + wfProfileOut(); + } + + # Finally, all the text has been munged and accumulated into + # the object, let's actually output it: + # + function output() + { + global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration; + global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode; + wfProfileIn( "OutputPage::output" ); + $sk = $wgUser->getSkin(); + + wfProfileIn( "OutputPage::output-headers" ); + if( $this->mLastModified != "" ) { + header( "Cache-Control: private, must-revalidate, max-age=0" ); + header( "Last-modified: {$this->mLastModified}" ); + } else { + header( "Cache-Control: no-cache" ); # Experimental - see below + header( "Pragma: no-cache" ); + header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" ); + } + header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page! + + header( "Content-type: text/html; charset={$wgOutputEncoding}" ); + header( "Content-language: {$wgLanguageCode}" ); + + if ( "" != $this->mRedirect ) { + header( "Location: {$this->mRedirect}" ); + wfProfileOut(); + return; + } + + $exp = time() + $wgCookieExpiration; + foreach( $this->mCookies as $name => $val ) { + setcookie( $name, $val, $exp, "/" ); + } + wfProfileOut(); + + wfProfileIn( "OutputPage::output-middle" ); + $sk->initPage(); + $this->out( $this->headElement() ); + + $this->out( "\ngetBodyOptions(); + foreach ( $ops as $name => $val ) { + $this->out( " $name='$val'" ); + } + $this->out( ">\n" ); + if ( $wgDebugComments ) { + $this->out( "\n" ); + } + $this->out( $sk->beforeContent() ); + wfProfileOut(); + + wfProfileIn( "OutputPage::output-bodytext" ); + $this->out( $this->mBodytext ); + wfProfileOut(); + wfProfileIn( "OutputPage::output-after" ); + $this->out( $sk->afterContent() ); + wfProfileOut(); + + wfProfileOut(); # A hack - we can't report after here + $this->out( $this->reportTime() ); + + $this->out( "\n" ); + flush(); + } + + function out( $ins ) + { + global $wgInputEncoding, $wgOutputEncoding, $wgLang; + if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) { + $outs = $ins; + } else { + $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins ); + if ( false === $outs ) { $outs = $ins; } + } + print $outs; + } + + function setEncodings() + { + global $HTTP_SERVER_VARS, $wgInputEncoding, $wgOutputEncoding; + global $wgUser, $wgLang; + + $wgInputEncoding = strtolower( $wgInputEncoding ); + $s = $HTTP_SERVER_VARS['HTTP_ACCEPT_CHARSET']; + + if( $wgUser->getOption( 'altencoding' ) ) { + $wgLang->setAltEncoding(); + return; + } + + if ( "" == $s ) { + $wgOutputEncoding = strtolower( $wgOutputEncoding ); + return; + } + $a = explode( ",", $s ); + $best = 0.0; + $bestset = "*"; + + foreach ( $a as $s ) { + if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) { + $set = $m[1]; + $q = (float)($m[2]); + } else { + $set = $s; + $q = 1.0; + } + if ( $q > $best ) { + $bestset = $set; + $best = $q; + } + } + #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; } + if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; } + $wgOutputEncoding = strtolower( $bestset ); + +# Disable for now +# + $wgOutputEncoding = $wgInputEncoding; + } + + function reportTime() + { + global $wgRequestTime, $wgDebugLogFile, $HTTP_SERVER_VARS; + global $wgProfiling, $wgProfileStack; + + list( $usec, $sec ) = explode( " ", microtime() ); + $now = (float)$sec + (float)$usec; + + list( $usec, $sec ) = explode( " ", $wgRequestTime ); + $start = (float)$sec + (float)$usec; + $elapsed = $now - $start; + + if ( "" != $wgDebugLogFile ) { + $prof = ""; + if( $wgProfiling and count( $wgProfileStack ) ) { + $lasttime = $start; + foreach( $wgProfileStack as $ile ) { + # "foo::bar 99 0.12345 1 0.23456 2" + if( preg_match( '/^(\S+)\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/', $ile, $m ) ) { + $thisstart = (float)$m[3] + (float)$m[4] - $start; + $thisend = (float)$m[5] + (float)$m[6] - $start; + $thiselapsed = $thisend - $thisstart; + $thispercent = $thiselapsed / $elapsed * 100.0; + + $prof .= sprintf( "\tat %04.3f in %04.3f (%2.1f%%) - %s %s\n", + $thisstart, $thiselapsed, $thispercent, + str_repeat( "*", $m[2] ), $m[1] ); + $lasttime = $thistime; + #$prof .= "\t(^ $ile)\n"; + } else { + $prof .= "\t?broken? $ile\n"; + } + } + } + + if( $forward = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'] ) + $forward = " forwarded for $forward"; + if( $client = $HTTP_SERVER_VARS['HTTP_CLIENT_IP'] ) + $forward .= " client IP $client"; + if( $from = $HTTP_SERVER_VARS['HTTP_FROM'] ) + $forward .= " from $from"; + if( $forward ) + $forward = "\t(proxied via {$HTTP_SERVER_VARS['REMOTE_ADDR']}{$forward})"; + $log = sprintf( "%s\t%04.3f\t%s\n", + date( "YmdHis" ), $elapsed, + urldecode( $HTTP_SERVER_VARS['REQUEST_URI'] . $forward ) ); + error_log( $log . $prof, 3, $wgDebugLogFile ); + } + $com = sprintf( "", + $elapsed ); + return $com; + } + + # Note: these arguments are keys into wfMsg(), not text! + # + function errorpage( $title, $msg ) + { + global $wgTitle; + + $this->mDebugtext .= "Original title: " . + $wgTitle->getPrefixedText() . "\n"; + $this->setHTMLTitle( wfMsg( "errorpagetitle" ) ); + $this->setPageTitle( wfMsg( $title ) ); + $this->setRobotpolicy( "noindex,nofollow" ); + $this->setArticleFlag( false ); + + $this->mBodytext = ""; + $this->addHTML( "

      " . wfMsg( $msg ) . "\n" ); + $this->returnToMain( false ); + + $this->output(); + exit; + } + + function sysopRequired() + { + global $wgUser; + + $this->setHTMLTitle( wfMsg( "errorpagetitle" ) ); + $this->setPageTitle( wfMsg( "sysoptitle" ) ); + $this->setRobotpolicy( "noindex,nofollow" ); + $this->setArticleFlag( false ); + $this->mBodytext = ""; + + $sk = $wgUser->getSkin(); + $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" ); + $text = str_replace( "$1", $ap, wfMsg( "sysoptext" ) ); + $this->addHTML( $text ); + $this->returnToMain(); + } + + function developerRequired() + { + global $wgUser; + + $this->setHTMLTitle( wfMsg( "errorpagetitle" ) ); + $this->setPageTitle( wfMsg( "developertitle" ) ); + $this->setRobotpolicy( "noindex,nofollow" ); + $this->setArticleFlag( false ); + $this->mBodytext = ""; + + $sk = $wgUser->getSkin(); + $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" ); + $text = str_replace( "$1", $ap, wfMsg( "developertext" ) ); + $this->addHTML( $text ); + $this->returnToMain(); + } + + function databaseError( $fname ) + { + global $wgUser; + + $this->setPageTitle( wfMsg( "databaseerror" ) ); + $this->setRobotpolicy( "noindex,nofollow" ); + $this->setArticleFlag( false ); + + $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), wfMsg( "dberrortext" ) ); + $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg ); + $msg = str_replace( "$3", wfLastErrno(), $msg ); + $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg ); + + $sk = $wgUser->getSkin(); + $shlink = $sk->makeKnownLink( wfMsg( "searchhelppage" ), + wfMsg( "searchingwikipedia" ) ); + $msg = str_replace( "$5", $shlink, $msg ); + + $this->mBodytext = $msg; + $this->output(); + exit(); + } + + function readOnlyPage() + { + global $wgUser, $wgReadOnlyFile; + + $this->setPageTitle( wfMsg( "readonly" ) ); + $this->setRobotpolicy( "noindex,nofollow" ); + $this->setArticleFlag( false ); + + $reason = implode( "", file( $wgReadOnlyFile ) ); + $text = str_replace( "$1", $reason, wfMsg( "readonlytext" ) ); + $this->addHTML( $text ); + $this->returnToMain( false ); + } + + function fatalError( $message ) + { + $this->setPageTitle( wfMsg( "internalerror" ) ); + $this->setRobotpolicy( "noindex,nofollow" ); + $this->setArticleFlag( false ); + + $this->mBodytext = $message; + $this->output(); + exit; + } + + function unexpectedValueError( $name, $val ) + { + $msg = str_replace( "$1", $name, wfMsg( "unexpected" ) ); + $msg = str_replace( "$2", $val, $msg ); + $this->fatalError( $msg ); + } + + function fileCopyError( $old, $new ) + { + $msg = str_replace( "$1", $old, wfMsg( "filecopyerror" ) ); + $msg = str_replace( "$2", $new, $msg ); + $this->fatalError( $msg ); + } + + function fileRenameError( $old, $new ) + { + $msg = str_replace( "$1", $old, wfMsg( "filerenameerror" ) ); + $msg = str_replace( "$2", $new, $msg ); + $this->fatalError( $msg ); + } + + function fileDeleteError( $name ) + { + $msg = str_replace( "$1", $name, wfMsg( "filedeleteerror" ) ); + $this->fatalError( $msg ); + } + + function fileNotFoundError( $name ) + { + $msg = str_replace( "$1", $name, wfMsg( "filenotfound" ) ); + $this->fatalError( $msg ); + } + + function returnToMain( $auto = true ) + { + global $wgUser, $wgOut, $returnto; + + $sk = $wgUser->getSkin(); + if ( "" == $returnto ) { + $returnto = wfMsg( "mainpage" ); + } + $link = $sk->makeKnownLink( $returnto, "" ); + + $r = str_replace( "$1", $link, wfMsg( "returnto" ) ); + if ( $auto ) { + $wgOut->addMeta( "http:Refresh", "10;url=" . + wfLocalUrlE( wfUrlencode( $returnto ) ) ); + } + $wgOut->addHTML( "\n

      $r\n" ); + } + + # Well, OK, it's actually about 14 passes. But since all the + # hard lifting is done inside PHP's regex code, it probably + # wouldn't speed things up much to add a real parser. + # + function doWikiPass2( $text, $linestart ) + { + global $wgUser; + wfProfileIn( "OutputPage::doWikiPass2" ); + + $text = $this->removeHTMLtags( $text ); + $text = $this->replaceVariables( $text ); + + $text = preg_replace( "/(^|\n)-----*/", "\\1


      ", $text ); + $text = str_replace ( "
      ", "
      ", $text ); + + $text = $this->doQuotes( $text ); + $text = $this->doHeadings( $text ); + $text = $this->doBlockLevels( $text, $linestart ); + + $text = $this->replaceExternalLinks( $text ); + $text = $this->replaceInternalLinks ( $text ); + + $text = $this->magicISBN( $text ); + $text = $this->magicRFC( $text ); + $text = $this->autoNumberHeadings( $text ); + + $sk = $wgUser->getSkin(); + $text = $sk->transformContent( $text ); + + wfProfileOut(); + return $text; + } + + /* private */ function doQuotes( $text ) + { + $text = preg_replace( "/'''(.+)'''/mU", "\$1", $text ); + $text = preg_replace( "/''(.+)''/mU", "\$1", $text ); + return $text; + } + + /* private */ function doHeadings( $text ) + { + for ( $i = 6; $i >= 1; --$i ) { + $h = substr( "======", 0, $i ); + $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m", + "\\1\\2", $text ); + } + return $text; + } + + # Note: we have to do external links before the internal ones, + # and otherwise take great care in the order of things here, so + # that we don't end up interpreting some URLs twice. + + /* private */ function replaceExternalLinks( $text ) + { + wfProfileIn( "OutputPage::replaceExternalLinks" ); + $text = $this->subReplaceExternalLinks( $text, "http", true ); + $text = $this->subReplaceExternalLinks( $text, "https", true ); + $text = $this->subReplaceExternalLinks( $text, "ftp", false ); + $text = $this->subReplaceExternalLinks( $text, "gopher", false ); + $text = $this->subReplaceExternalLinks( $text, "news", false ); + $text = $this->subReplaceExternalLinks( $text, "mailto", false ); + wfProfileOut(); + return $text; + } + + /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber ) + { + global $wgUser, $printable; + global $wgAllowExternalImages; + + + $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3"; + $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF"; + + # this is the list of separators that should be ignored if they + # are the last character of an URL but that should be included + # if they occur within the URL, e.g. "go to www.foo.com, where .." + # in this case, the last comma should not become part of the URL, + # but in "www.foo.com/123,2342,32.htm" it should. + $sep = ",;\.:"; + $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF"; + $images = "gif|png|jpg|jpeg"; + + # PLEASE NOTE: The curly braces { } are not part of the regex, + # they are interpreted as part of the string (used to tell PHP + # that the content of the string should be inserted there). + $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." . + "((?i){$images})([^{$uc}]|$)/"; + + $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/"; + $sk = $wgUser->getSkin(); + + if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls + $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" . + "/\\4.\\5", "\\4.\\5" ) . "\\6", $s ); + } + $s = preg_replace( $e2, "\\1" . "getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML( + "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) . + "\\5", $s ); + $s = str_replace( $unique, $protocol, $s ); + + $a = explode( "[{$protocol}:", " " . $s ); + $s = array_shift( $a ); + $s = substr( $s, 1 ); + + $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD"; + $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD"; + + foreach ( $a as $line ) { + if ( preg_match( $e1, $line, $m ) ) { + $link = "{$protocol}:{$m[1]}"; + $trail = $m[2]; + if ( $autonumber ) { $text = "[" . ++$this->mAutonumber . "]"; } + else { $text = wfEscapeHTML( $link ); } + } else if ( preg_match( $e2, $line, $m ) ) { + $link = "{$protocol}:{$m[1]}"; + $text = $m[2]; + $trail = $m[3]; + } else { + $s .= "[{$protocol}:" . $line; + continue; + } + if ( $printable == "yes") $paren = " (" . htmlspecialchars ( $link ) . ")"; + else $paren = ""; + $la = $sk->getExternalLinkAttributes( $link, $text ); + $s .= "{$text}{$paren}{$trail}"; + + } + return $s; + } + + /* private */ function replaceInternalLinks( $s ) + { + global $wgTitle, $wgUser, $wgLang; + global $wgLinkCache, $wgInterwikiMagic; + global $wgNamespacesWithSubpages; + wfProfileIn( "OutputPage::replaceInternalLinks" ); + + $tc = Title::legalChars() . "#"; + $sk = $wgUser->getSkin(); + + $a = explode( "[[", " " . $s ); + $s = array_shift( $a ); + $s = substr( $s, 1 ); + + $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD"; + $e2 = "/^([{$tc}]+)]](.*)\$/sD"; + + foreach ( $a as $line ) { + if ( preg_match( $e1, $line, $m ) ) { # page with alternate text + + $text = $m[2]; + $trail = $m[3]; + + } else if ( preg_match( $e2, $line, $m ) ) { # page with normal text + + $text = ""; + $trail = $m[2]; + } + + else { # Invalid form; output directly + $s .= "[[" . $line ; + continue; + } + if(substr($m[1],0,1)=="/") { # subpage + if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown + $m[1]=substr($m[1],1,strlen($m[1])-2); + $noslash=$m[1]; + + } else { + $noslash=substr($m[1],1); + } + if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here + $link = $wgTitle->getPrefixedText(). "/" . trim($noslash); + if(!$text) { + $text= $m[1]; + } # this might be changed for ugliness reasons + } else { + $link = $noslash; # no subpage allowed, use standard link + } + } else { # no subpage + $link = $m[1]; + } + + if ( preg_match( "/^([A-Za-z\\x80-\\xff]+):(.*)\$/", $link, $m ) ) { + $pre = strtolower( $m[1] ); + $suf = $m[2]; + if ( $wgLang->getNsIndex( $pre ) == + Namespace::getImage() ) { + $nt = Title::newFromText( $suf ); + $name = $nt->getDBkey(); + if ( "" == $text ) { $text = $nt->GetText(); } + + $wgLinkCache->addImageLink( $name ); + $s .= $sk->makeImageLink( $name, + wfImageUrl( $name ), $text ); + $s .= $trail; + } else if ( "media" == $pre ) { + $nt = Title::newFromText( $suf ); + $name = $nt->getDBkey(); + if ( "" == $text ) { $text = $nt->GetText(); } + + $wgLinkCache->addImageLink( $name ); + $s .= $sk->makeMediaLink( $name, + wfImageUrl( $name ), $text ); + $s .= $trail; + } else { + $l = $wgLang->getLanguageName( $pre ); + if ( "" == $l or !$wgInterwikiMagic or + Namespace::isTalk( $wgTitle->getNamespace() ) ) { + if ( "" == $text ) { $text = $link; } + $s .= $sk->makeLink( $link, $text, "", $trail ); + } else { + array_push( $this->mLanguageLinks, "$pre:$suf" ); + $s .= $trail; + } + } +# } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) { +# $link = substr( $link, 2 ); +# $s .= "{$text}{$trail}"; + } else { + if ( "" == $text ) { $text = $link; } + $s .= $sk->makeLink( $link, $text, "", $trail ); + } + } + wfProfileOut(); + return $s; + } + + # Some functions here used by doBlockLevels() + # + /* private */ function closeParagraph() + { + $result = ""; + if ( 0 != strcmp( "p", $this->mLastSection ) && + 0 != strcmp( "", $this->mLastSection ) ) { + $result = "mLastSection . ">"; + } + $this->mLastSection = ""; + return $result; + } + # getCommon() returns the length of the longest common substring + # of both arguments, starting at the beginning of both. + # + /* private */ function getCommon( $st1, $st2 ) + { + $fl = strlen( $st1 ); + $shorter = strlen( $st2 ); + if ( $fl < $shorter ) { $shorter = $fl; } + + for ( $i = 0; $i < $shorter; ++$i ) { + if ( $st1{$i} != $st2{$i} ) { break; } + } + return $i; + } + # These next three functions open, continue, and close the list + # element appropriate to the prefix character passed into them. + # + /* private */ function openList( $char ) + { + $result = $this->closeParagraph(); + + if ( "*" == $char ) { $result .= "
      • "; } + else if ( "#" == $char ) { $result .= "
        1. "; } + else if ( ":" == $char ) { $result .= "
          "; } + else if ( ";" == $char ) { + $result .= "
          "; + $this->mDTopen = true; + } + else { $result = ""; } + + return $result; + } + + /* private */ function nextItem( $char ) + { + if ( "*" == $char || "#" == $char ) { return "
        2. "; } + else if ( ":" == $char || ";" == $char ) { + $close = ""; + if ( $this->mDTopen ) { $close = ""; } + if ( ";" == $char ) { + $this->mDTopen = true; + return $close . "
          "; + } else { + $this->mDTopen = false; + return $close . "
          "; + } + } + return ""; + } + + /* private */function closeList( $char ) + { + if ( "*" == $char ) { return "
      "; } + else if ( "#" == $char ) { return ""; } + else if ( ":" == $char ) { + if ( $this->mDTopen ) { + $this->mDTopen = false; + return ""; + } else { + return ""; + } + } + return ""; + } + + /* private */ function doBlockLevels( $text, $linestart ) + { + wfProfileIn( "OutputPage::doBlockLevels" ); + # Parsing through the text line by line. The main thing + # happening here is handling of block-level elements p, pre, + # and making lists from lines starting with * # : etc. + # + $a = explode( "\n", $text ); + $text = $lastPref = ""; + $this->mDTopen = $inBlockElem = false; + + if ( ! $linestart ) { $text .= array_shift( $a ); } + foreach ( $a as $t ) { + if ( "" != $text ) { $text .= "\n"; } + + $oLine = $t; + $opl = strlen( $lastPref ); + $npl = strspn( $t, "*#:;" ); + $pref = substr( $t, 0, $npl ); + $pref2 = str_replace( ";", ":", $pref ); + $t = substr( $t, $npl ); + + if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) { + $text .= $this->nextItem( substr( $pref, -1 ) ); + + if ( ";" == substr( $pref, -1 ) ) { + $cpos = strpos( $t, ":" ); + if ( ! ( false === $cpos ) ) { + $term = substr( $t, 0, $cpos ); + $text .= $term . $this->nextItem( ":" ); + $t = substr( $t, $cpos + 1 ); + } + } + } else if (0 != $npl || 0 != $opl) { + $cpl = $this->getCommon( $pref, $lastPref ); + + while ( $cpl < $opl ) { + $text .= $this->closeList( $lastPref{$opl-1} ); + --$opl; + } + if ( $npl <= $cpl && $cpl > 0 ) { + $text .= $this->nextItem( $pref{$cpl-1} ); + } + while ( $npl > $cpl ) { + $char = substr( $pref, $cpl, 1 ); + $text .= $this->openList( $char ); + + if ( ";" == $char ) { + $cpos = strpos( $t, ":" ); + if ( ! ( false === $cpos ) ) { + $term = substr( $t, 0, $cpos ); + $text .= $term . $this->nextItem( ":" ); + $t = substr( $t, $cpos + 1 ); + } + } + ++$cpl; + } + $lastPref = $pref2; + } + if ( 0 == $npl ) { # No prefix--go to paragraph mode + if ( preg_match( + "/(closeParagraph(); + $inBlockElem = true; + } + if ( ! $inBlockElem ) { + if ( " " == $t{0} ) { + $newSection = "pre"; + # $t = wfEscapeHTML( $t ); + } + else { $newSection = "p"; } + + if ( 0 == strcmp( "", trim( $oLine ) ) ) { + $text .= $this->closeParagraph(); + $text .= "<" . $newSection . ">"; + } else if ( 0 != strcmp( $this->mLastSection, + $newSection ) ) { + $text .= $this->closeParagraph(); + if ( 0 != strcmp( "p", $newSection ) ) { + $text .= "<" . $newSection . ">"; + } + } + $this->mLastSection = $newSection; + } + if ( $inBlockElem && + preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) { + $inBlockElem = false; + } + } + $text .= $t; + } + while ( $npl ) { + $text .= $this->closeList( $pref2{$npl-1} ); + --$npl; + } + if ( "" != $this->mLastSection ) { + if ( "p" != $this->mLastSection ) { + $text .= "mLastSection . ">"; + } + $this->mLastSection = ""; + } + wfProfileOut(); + return $text; + } + + /* private */ function replaceVariables( $text ) + { + global $wgLang; + wfProfileIn( "OutputPage:replaceVariables" ); + + $v = date( "m" ); + $text = str_replace( "{{CURRENTMONTH}}", $v, $text ); + $v = $wgLang->getMonthName( date( "n" ) ); + $text = str_replace( "{{CURRENTMONTHNAME}}", $v, $text ); + $v = $wgLang->getMonthNameGen( date( "n" ) ); + $text = str_replace( "{{CURRENTMONTHNAMEGEN}}", $v, $text ); + $v = date( "j" ); + $text = str_replace( "{{CURRENTDAY}}", $v, $text ); + $v = $wgLang->getWeekdayName( date( "w" )+1 ); + $text = str_replace( "{{CURRENTDAYNAME}}", $v, $text ); + $v = date( "Y" ); + $text = str_replace( "{{CURRENTYEAR}}", $v, $text ); + $v = $wgLang->time( date( "YmdHis" ), false ); + $text = str_replace( "{{CURRENTTIME}}", $v, $text ); + + if ( false !== strstr( $text, "{{NUMBEROFARTICLES}}" ) ) { + $v = wfNumberOfArticles(); + $text = str_replace( "{{NUMBEROFARTICLES}}", $v, $text ); + } + wfProfileOut(); + return $text; + } + + /* private */ function removeHTMLtags( $text ) + { + wfProfileIn( "OutputPage::removeHTMLtags" ); + $htmlpairs = array( # Tags that must be closed + "b", "i", "u", "font", "big", "small", "sub", "sup", "h1", + "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s", + "strike", "strong", "tt", "var", "div", "center", + "blockquote", "ol", "ul", "dl", "table", "caption", "pre", + "ruby", "rt" , "rb" , "rp" + ); + $htmlsingle = array( + "br", "p", "hr", "li", "dt", "dd" + ); + $htmlnest = array( # Tags that can be nested--?? + "table", "tr", "td", "th", "div", "blockquote", "ol", "ul", + "dl", "font", "big", "small", "sub", "sup" + ); + $tabletags = array( # Can only appear inside table + "td", "th", "tr" + ); + + $htmlsingle = array_merge( $tabletags, $htmlsingle ); + $htmlelements = array_merge( $htmlsingle, $htmlpairs ); + + $htmlattrs = array( # Allowed attributes--no scripting, etc. + "title", "align", "lang", "dir", "width", "height", + "bgcolor", "clear", /* BR */ "noshade", /* HR */ + "cite", /* BLOCKQUOTE, Q */ "size", "face", "color", + /* FONT */ "type", "start", "value", "compact", + /* For various lists, mostly deprecated but safe */ + "summary", "width", "border", "frame", "rules", + "cellspacing", "cellpadding", "valign", "char", + "charoff", "colgroup", "col", "span", "abbr", "axis", + "headers", "scope", "rowspan", "colspan", /* Tables */ + "id", "class", "name", "style" /* For CSS */ + ); + + # Remove HTML comments + $text = preg_replace( "//sU", "", $text ); + + $bits = explode( "<", $text ); + $text = array_shift( $bits ); + $tagstack = array(); $tablestack = array(); + + foreach ( $bits as $x ) { + $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) ); + preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/", + $x, $regs ); + list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs; + error_reporting( $prev ); + + $badtag = 0 ; + if ( in_array( $t = strtolower( $t ), $htmlelements ) ) { + # Check our stack + if ( $slash ) { + # Closing a tag... + if ( ! in_array( $t, $htmlsingle ) && + ( $ot = array_pop( $tagstack ) ) != $t ) { + array_push( $tagstack, $ot ); + $badtag = 1; + } else { + if ( $t == "table" ) { + $tagstack = array_pop( $tablestack ); + } + $newparams = ""; + } + } else { + # Keep track for later + if ( in_array( $t, $tabletags ) && + ! in_array( "table", $tagstack ) ) { + $badtag = 1; + } else if ( in_array( $t, $tagstack ) && + ! in_array ( $t , $htmlnest ) ) { + $badtag = 1 ; + } else if ( ! in_array( $t, $htmlsingle ) ) { + if ( $t == "table" ) { + array_push( $tablestack, $tagstack ); + $tagstack = array(); + } + array_push( $tagstack, $t ); + } + # Strip non-approved attributes from the tag + $newparams = preg_replace( + "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e", + "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')", + $params); + } + if ( ! $badtag ) { + $rest = str_replace( ">", ">", $rest ); + $text .= "<$slash$t$newparams$brace$rest"; + continue; + } + } + $text .= "<" . str_replace( ">", ">", $x); + } + # Close off any remaining tags + while ( $t = array_pop( $tagstack ) ) { + $text .= "\n"; + if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); } + } + wfProfileOut(); + return $text; + } + + /* private */ function autoNumberHeadings( $text ) + { + global $wgUser; + if ( 1 != $wgUser->getOption( "numberheadings" ) ) { + return $text; + } + $j = 0; + $n = -1; + for ( $i = 0; $i < 9; ++$i ) { + if ( stristr( $text, "" ) != false ) { + ++$j; + if ( $n == -1 ) $n = $i; + } + } + if ( $j < 2 ) return $text; + $i = $n; + $v = array( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + $t = ""; + while ( count( spliti( ""; + ++$v[$j]; + $b = array(); + for ( $k = $i; $k <= $j; $k++ ) array_push( $b, $v[$k] ); + for ( $k = $j+1; $k < 9; $k++ ) $v[$k] = 0; + $t .= implode( ".", $b ) . " "; + $text = substr( $a[1] , 2 ) ; + } else { #
      tag, not a heading! + $t .= $a[0] . "
      "; + $text = substr( $a[1], 2 ); + } + } + return $t . $text; + } + + /* private */ function magicISBN( $text ) + { + global $wgLang; + + $a = split( "ISBN ", " $text" ); + if ( count ( $a ) < 2 ) return $text; + $text = substr( array_shift( $a ), 1); + $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + foreach ( $a as $x ) { + $isbn = $blank = "" ; + while ( " " == $x{0} ) { + $blank .= " "; + $x = substr( $x, 1 ); + } + while ( strstr( $valid, $x{0} ) != false ) { + $isbn .= $x{0}; + $x = substr( $x, 1 ); + } + $num = str_replace( "-", "", $isbn ); + $num = str_replace( " ", "", $num ); + + if ( "" == $num ) { + $text .= "ISBN $blank$x"; + } else { + $text .= "specialPage( + "Booksources"), "isbn={$num}" ) . "\">ISBN $isbn"; + $text .= $x; + } + } + return $text; + } + + /* private */ function magicRFC( $text ) + { + return $text; + } + + /* private */ function headElement() + { + global $wgDocType, $wgUser, $wgLanguageCode, $wgOutputEncoding; + + $ret = "\n"; + + if ( "" == $this->mHTMLtitle ) { + $this->mHTMLtitle = $this->mPagetitle; + } + $ret .= "{$this->mHTMLtitle}\n"; + array_push( $this->mMetatags, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) ); + foreach ( $this->mMetatags as $tag ) { + if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) { + $a = "http-equiv"; + $tag[0] = substr( $tag[0], 5 ); + } else { + $a = "name"; + } + $ret .= "\n"; + } + $p = $this->mRobotpolicy; + if ( "" == $p ) { $p = "index,follow"; } + $ret .= "\n"; + + if ( count( $this->mKeywords ) > 0 ) { + $ret .= "mKeywords ) . "\">\n"; + } + foreach ( $this->mLinktags as $tag ) { + $ret .= "\n"; + } + $sk = $wgUser->getSkin(); + $ret .= $sk->getHeadScripts(); + $ret .= $sk->getUserStyles(); + + $ret .= "\n"; + return $ret; + } +} + +?> diff --git a/includes/SearchEngine.php b/includes/SearchEngine.php new file mode 100644 index 0000000000..53be8f7eaf --- /dev/null +++ b/includes/SearchEngine.php @@ -0,0 +1,383 @@ +mUsertext = trim( preg_replace( "/[^{$lc}]/", " ", $text ) ); + $this->mSearchterms = array(); + } + + function queryNamespaces() + { + return "cur_namespace IN (" . implode( ",", $this->namespacesToSearch ) . ")"; + #return "1"; + } + + function searchRedirects() + { + if ( $this->doSearchRedirects ) return ""; + return "AND cur_is_redirect=0 "; + } + + function powersearch() + { + global $wgUser, $wgOut, $wgLang, $wgTitle; + $nscb = array(); + + $search = $_REQUEST['search']; + $searchx = $_REQUEST['searchx']; + $listredirs = $_REQUEST['redirs']; + $nscb[0] = $_REQUEST['ns0']; + $nscb[1] = $_REQUEST['ns1']; + $nscb[2] = $_REQUEST['ns2']; + $nscb[3] = $_REQUEST['ns3']; + $nscb[4] = $_REQUEST['ns4']; + $nscb[5] = $_REQUEST['ns5']; + $nscb[6] = $_REQUEST['ns6']; + $nscb[7] = $_REQUEST['ns7']; + + if ( ! isset ( $searchx ) ) { /* First time here */ + $nscb[0] = $listredirs = 1; /* All others should be unset */ + } + $this->checkboxes["searchx"] = 1; + $ret = wfMsg("powersearchtext"); + + # Determine namespace checkboxes + + $ns = $wgLang->getNamespaces(); + array_shift( $ns ); /* Skip "Special" */ + + $r1 = ""; + for ( $i = 0; $i < count( $ns ); ++$i ) { + $checked = ""; + if ( $nscb[$i] == 1 ) { + $checked = " checked"; + $this->addtoquery["ns{$i}"] = 1; + array_push( $this->namespacesToSearch, $i ); + } + $name = str_replace( "_", " ", $ns[$i] ); + if ( "" == $name ) { $name = "(Main)"; } + + if ( 0 != $i ) { $r1 .= " "; } + $r1 .= "{$name}\n"; + } + $ret = str_replace ( "$1", $r1, $ret ); + + # List redirects checkbox + + $checked = ""; + if ( $listredirs == 1 ) { + $this->addtoquery["redirs"] = 1; + $checked = " checked"; + } + $r2 = "\n"; + $ret = str_replace( "$2", $r2, $ret ); + + # Search field + + $r3 = "\n"; + $ret = str_replace( "$3", $r3, $ret ); + + # Searchx button + + $r9 = "\n"; + $ret = str_replace( "$9", $r9, $ret ); + + $ret = "

      \n
      \n{$ret}\n
      \n"; + + if ( isset ( $searchx ) ) { + if ( ! $listredirs ) { $this->doSearchRedirects = false; } + } + return $ret; + } + + function showResults() + { + global $wgUser, $wgTitle, $wgOut, $wgLang, $wgDisableTextSearch; + $fname = "SearchEngine::showResults"; + + $offset = $_REQUEST['offset']; + $limit = $_REQUEST['limit']; + $search = $_REQUEST['search']; + + $powersearch = $this->powersearch(); /* Need side-effects here? */ + + $wgOut->setPageTitle( wfMsg( "searchresults" ) ); + $q = str_replace( "$1", $this->mUsertext, + wfMsg( "searchquery" ) ); + $wgOut->setSubtitle( $q ); + $wgOut->setArticleFlag( false ); + $wgOut->setRobotpolicy( "noindex,nofollow" ); + + $sk = $wgUser->getSkin(); + $text = str_replace( "$1", $sk->makeKnownLink( + wfMsg( "searchhelppage" ), wfMsg( "searchingwikipedia" ) ), + wfMsg( "searchresulttext" ) ); + $wgOut->addHTML( $text ); + + $this->parseQuery(); + if ( "" == $this->mTitlecond || "" == $this->mTextcond ) { + $wgOut->addHTML( "

      " . wfMsg( "badquery" ) . "

      \n" . + "

      " . wfMsg( "badquerytext" ) ); + return; + } + if ( ! isset( $limit ) ) { + $limit = $wgUser->getOption( "searchlimit" ); + if ( ! $limit ) { $limit = 20; } + } + if ( ! $offset ) { $offset = 0; } + + $searchnamespaces = $this->queryNamespaces(); + $redircond = $this->searchRedirects(); + + $sql = "SELECT cur_id,cur_namespace,cur_title," . + "cur_text FROM cur,searchindex " . + "WHERE cur_id=si_page AND {$this->mTitlecond} " . + "AND {$searchnamespaces} {$redircond}" . + "LIMIT {$offset}, {$limit}"; + $res1 = wfQuery( $sql, $fname ); + + if ( $wgDisableTextSearch ) { + $res2 = 0; + } else { + $sql = "SELECT cur_id,cur_namespace,cur_title," . + "cur_text FROM cur,searchindex " . + "WHERE cur_id=si_page AND {$this->mTextcond} " . + "AND {$searchnamespaces} {$redircond} " . + "LIMIT {$offset}, {$limit}"; + $res2 = wfQuery( $sql, $fname ); + } + + $top = wfShowingResults( $offset, $limit ); + $wgOut->addHTML( "

      {$top}\n" ); + + # For powersearch + + $a2l = "" ; + $akk = array_keys( $this->addtoquery ) ; + foreach ( $akk AS $ak ) { + $a2l .= "&{$ak}={$this->addtoquery[$ak]}" ; + } + + $sl = wfViewPrevNext( $offset, $limit, "", + "search=" . wfUrlencode( $this->mUsertext ) . $a2l ); + $wgOut->addHTML( "
      {$sl}\n" ); + + $foundsome = false; + + if ( 0 == wfNumRows( $res1 ) ) { + $wgOut->addHTML( "

      " . wfMsg( "notitlematches" ) . + "

      \n" ); + } else { + $foundsome = true; + $off = $offset + 1; + $wgOut->addHTML( "

      " . wfMsg( "titlematches" ) . + "

      \n
        " ); + + while ( $row = wfFetchObject( $res1 ) ) { + $this->showHit( $row ); + } + wfFreeResult( $res1 ); + $wgOut->addHTML( "
      \n" ); + } + + if ( $wgDisableTextSearch ) { + $wgOut->addHTML( str_replace( "$1", + htmlspecialchars( $search ), wfMsg( "searchdisabled" ) ) ); + } else { + if ( 0 == wfNumRows( $res2 ) ) { + $wgOut->addHTML( "

      " . wfMsg( "notextmatches" ) . + "

      \n" ); + } else { + $foundsome = true; + $off = $offset + 1; + $wgOut->addHTML( "

      " . wfMsg( "textmatches" ) . "

      \n" . + "
        " ); + while ( $row = wfFetchObject( $res2 ) ) { + $this->showHit( $row ); + } + wfFreeResult( $res2 ); + $wgOut->addHTML( "
      \n" ); + } + } + if ( ! $foundsome ) { + $wgOut->addHTML( "

      " . wfMsg( "nonefound" ) . "\n" ); + } + $wgOut->addHTML( "

      {$sl}\n" ); + $wgOut->addHTML( $powersearch ); + } + + function legalSearchChars() + { + $lc = "A-Za-z_'0-9\\x80-\\xFF\\-"; + return $lc; + } + + function parseQuery() + { + global $wgDBminWordLen, $wgLang; + + $lc = SearchEngine::legalSearchChars() . "()"; + $q = preg_replace( "/([()])/", " \\1 ", $this->mUsertext ); + $q = preg_replace( "/\\s+/", " ", $q ); + $w = explode( " ", strtolower( trim( $q ) ) ); + + $last = $cond = ""; + foreach ( $w as $word ) { + $word = $wgLang->stripForSearch( $word ); + if ( "and" == $word || "or" == $word || "not" == $word + || "(" == $word || ")" == $word ) { + $cond .= " " . strtoupper( $word ); + $last = ""; + } else if ( strlen( $word ) < $wgDBminWordLen ) { + continue; + } else if ( FulltextStoplist::inList( $word ) ) { + continue; + } else { + if ( "" != $last ) { $cond .= " AND"; } + $cond .= " (MATCH (##field##) AGAINST ('" . + wfStrencode( $word ). "'))"; + $last = $word; + array_push( $this->mSearchterms, "\\b" . $word . "\\b" ); + } + } + if ( 0 == count( $this->mSearchterms ) ) { return; } + + # To disable boolean: + # $cond = "MATCH (##field##) AGAINST('" . wfStrencode( $q ) . "')"; + + $this->mTitlecond = "(" . str_replace( "##field##", + "si_title", $cond ) . " )"; + + $this->mTextcond = "(" . str_replace( "##field##", + "si_text", $cond ) . " AND (cur_is_redirect=0) )"; + } + + function showHit( $row ) + { + global $wgUser, $wgOut; + + $t = Title::makeName( $row->cur_namespace, $row->cur_title ); + $sk = $wgUser->getSkin(); + + $contextlines = $wgUser->getOption( "contextlines" ); + if ( "" == $contextlines ) { $contextlines = 5; } + $contextchars = $wgUser->getOption( "contextchars" ); + if ( "" == $contextchars ) { $contextchars = 50; } + + $link = $sk->makeKnownLink( $t, "" ); + $size = str_replace( "$1", strlen( $row->cur_text ), WfMsg( "nbytes" ) ); + $wgOut->addHTML( "

    • {$link} ({$size})" ); + + $lines = explode( "\n", $row->cur_text ); + $pat1 = "/(.*)(" . implode( "|", $this->mSearchterms ) . ")(.*)/i"; + $lineno = 0; + + foreach ( $lines as $line ) { + if ( 0 == $contextlines ) { break; } + --$contextlines; + ++$lineno; + if ( ! preg_match( $pat1, $line, $m ) ) { continue; } + + $pre = $m[1]; + if ( 0 == $contextchars ) { $pre = "..."; } + else { + if ( strlen( $pre ) > $contextchars ) { + $pre = "..." . substr( $pre, -$contextchars ); + } + } + $pre = wfEscapeHTML( $pre ); + + if ( count( $m ) < 3 ) { $post = ""; } + else { $post = $m[3]; } + + if ( 0 == $contextchars ) { $post = "..."; } + else { + if ( strlen( $post ) > $contextchars ) { + $post = substr( $post, 0, $contextchars ) . "..."; + } + } + $post = wfEscapeHTML( $post ); + $found = wfEscapeHTML( $m[2] ); + + $line = "{$pre}{$found}{$post}"; + $pat2 = "/(" . implode( "|", $this->mSearchterms ) . ")/i"; + $line = preg_replace( $pat2, + "\\1", $line ); + + $wgOut->addHTML( "
      {$lineno}: {$line}\n" ); + } + $wgOut->addHTML( "
    • \n" ); + } + + function goResult() + { + global $wgOut, $wgArticle, $wgTitle; + $fname = "SearchEngine::goResult"; + + $search = $_REQUEST['search']; + + # First try to go to page as entered + # + $wgArticle = new Article(); + $wgTitle = Title::newFromText( $search ); + + if ( 0 != $wgArticle->getID() ) { + $wgArticle->view(); + return; + } + + # Now try all lower case (i.e. first letter capitalized) + # + $wgTitle = Title::newFromText( strtolower( $search ) ); + if ( 0 != $wgArticle->getID() ) { + $wgArticle->view(); + return; + } + + # Now try capitalized string + # + $wgTitle=Title::newFromText( ucwords( strtolower( $search ) ) ); + if ( 0 != $wgArticle->getID() ) { + $wgArticle->view(); + return; + } + + # Try a near match + # + $this->parseQuery(); + $sql = "SELECT cur_id,cur_title,cur_namespace,si_page FROM cur,searchindex " . + "WHERE cur_id=si_page AND {$this->mTitlecond} LIMIT 1"; + + if ( "" != $this->mTitlecond ) { + $res = wfQuery( $sql, $fname ); + } + if ( isset( $res ) && 0 != wfNumRows( $res ) ) { + $s = wfFetchObject( $res ); + + $wgTitle = Title::newFromDBkey( $s->cur_title ); + $wgTitle->setNamespace( $s->cur_namespace ); + $wgArticle->view(); + return; + } + $wgOut->addHTML( wfMsg("nogomatch") . "\n

      " ); + $this->showResults(); + } +} + diff --git a/includes/SearchUpdate.php b/includes/SearchUpdate.php new file mode 100644 index 0000000000..9e9ff2af1f --- /dev/null +++ b/includes/SearchUpdate.php @@ -0,0 +1,78 @@ +mId = $id; + $this->mText = $text; + + $nt = Title::newFromText( $title ); + $this->mNamespace = $nt->getNamespace(); + $this->mTitle = $nt->getText(); # Discard namespace + + $this->mTitleWords = $this->mTextWords = array(); + } + + function doUpdate() + { + global $wgDBminWordLen, $wgLang; + $lc = SearchEngine::legalSearchChars() . "&#;"; + + if( $this->mText == false ) { + # Just update the title + $sql = "UPDATE LOW_PRIORITY searchindex SET si_title='" . + wfStrencode( Title::indexTitle( $this->mNamespace, $this->mTitle ) ) . + "' WHERE si_page={$this->mId}"; + wfQuery( $sql, "SearchUpdate::doUpdate" ); + return; + } + + # Language-specific strip/conversion + $text = $wgLang->stripForSearch( $this->mText ); + + $text = preg_replace( "/<\\/?\\s*[A-Za-z][A-Za-z0-9]*\\s*([^>]*?)>/", + " ", strtolower( " " . $text /*$this->mText*/ . " " ) ); # Strip HTML markup + $text = preg_replace( "/(^|\\n)\\s*==\\s+([^\\n]+)\\s+==\\s/sD", + "\\2 \\2 \\2 ", $text ); # Emphasize headings + + # Strip external URLs + $uc = "A-Za-z0-9_\\/:.,~%\\-+&;#?!=()@\\xA0-\\xFF"; + $protos = "http|https|ftp|mailto|news|gopher"; + $pat = "/(^|[^\\[])({$protos}):[{$uc}]+([^{$uc}]|$)/"; + $text = preg_replace( $pat, "\\1 \\3", $text ); + + $p1 = "/([^\\[])\\[({$protos}):[{$uc}]+]/"; + $p2 = "/([^\\[])\\[({$protos}):[{$uc}]+\\s+([^\\]]+)]/"; + $text = preg_replace( $p1, "\\1 ", $text ); + $text = preg_replace( $p2, "\\1 \\3 ", $text ); + + # Internal image links + $pat2 = "/\\[\\[image:([{$uc}]+)\\.(gif|png|jpg|jpeg)([^{$uc}])/i"; + $text = preg_replace( $pat2, " \\1 \\3", $text ); + + $text = preg_replace( "/([^{$lc}])([{$lc}]+)]]([a-z]+)/", + "\\1\\2 \\2\\3", $text ); # Handle [[game]]s + + # Strip all remaining non-search characters + $text = preg_replace( "/[^{$lc}]+/", " ", $text ); + + # Handle 's, s' + $text = preg_replace( "/([{$lc}]+)'s /", "\\1 \\1's ", $text ); + $text = preg_replace( "/([{$lc}]+)s' /", "\\1s ", $text ); + + # Strip wiki '' and ''' + $text = preg_replace( "/''[']*/", " ", $text ); + + $sql = "REPLACE DELAYED INTO searchindex (si_page,si_title,si_text) VALUES ({$this->mId},'" . + wfStrencode( Title::indexTitle( $this->mNamespace, $this->mTitle ) ) . "','" . + wfStrencode( $text ) . "')"; + wfQuery( $sql, "SearchUpdate::doUpdate" ); + } +} + +?> diff --git a/includes/Setup.php b/includes/Setup.php new file mode 100644 index 0000000000..7b0c64670c --- /dev/null +++ b/includes/Setup.php @@ -0,0 +1,37 @@ +loadFromSession(); +$wgDeferredUpdateList = array(); +$wgLinkCache = new LinkCache(); + +?> diff --git a/includes/SiteStatsUpdate.php b/includes/SiteStatsUpdate.php new file mode 100644 index 0000000000..61ca8464c3 --- /dev/null +++ b/includes/SiteStatsUpdate.php @@ -0,0 +1,40 @@ +mViews = $views; + $this->mEdits = $edits; + $this->mGood = $good; + } + + function doUpdate() + { + $a = array(); + + if ( $this->mViews < 0 ) { $m = "-1"; } + else if ( $this->mViews > 0 ) { $m = "+1"; } + else $m = ""; + array_push( $a, "ss_total_views=(ss_total_views$m)" ); + + if ( $this->mEdits < 0 ) { $m = "-1"; } + else if ( $this->mEdits > 0 ) { $m = "+1"; } + else $m = ""; + array_push( $a, "ss_total_edits=(ss_total_edits$m)" ); + + if ( $this->mGood < 0 ) { $m = "-1"; } + else if ( $this->mGood > 0 ) { $m = "+1"; } + else $m = ""; + array_push( $a, "ss_good_articles=(ss_good_articles$m)" ); + + $sql = "UPDATE LOW_PRIORITY site_stats SET " . implode ( ",", $a ) . + " WHERE ss_row_id=1"; + wfQuery( $sql, "SiteStatsUpdate::doUpdate" ); + } +} + +?> diff --git a/includes/Skin.php b/includes/Skin.php new file mode 100644 index 0000000000..303ad176fc --- /dev/null +++ b/includes/Skin.php @@ -0,0 +1,1674 @@ +isQuickbarSupressed() ) { return 0; } + $q = $wgUser->getOption( "quickbar" ); + if ( "" == $q ) { $q = 0; } + return $q; + } + + function initPage() + { + global $wgOut, $wgStyleSheetPath; + wfProfileIn( "Skin::initPage" ); + + $wgOut->addLink( "shortcut icon", "", "/favicon.ico" ); + if ( $wgOut->isPrintable() ) { $ss = "wikiprintable.css"; } + else { $ss = $this->getStylesheet(); } + $wgOut->addLink( "stylesheet", "", "{$wgStyleSheetPath}/{$ss}" ); + wfProfileOut(); + } + + function getHeadScripts() { + $r = " + + " ; + return $r; + } + + function getUserStyles() + { + $s = "\n"; + return $s; + } + + function doGetUserStyles() + { + global $wgUser; + + $s = ""; + if ( 1 == $wgUser->getOption( "underline" ) ) { + $s .= "a.stub, a.new, a.internal, a.external { " . + "text-decoration: underline; }\n"; + } else { + $s .= "a.stub, a.new, a.internal, a.external { " . + "text-decoration: none; }\n"; + } + if ( 1 == $wgUser->getOption( "highlightbroken" ) ) { + $s .= "a.new { color: #CC2200; }\n" . + "#quickbar a.new { color: CC2200; }\n"; + } + if ( 1 == $wgUser->getOption( "justify" ) ) { + $s .= "#article { text-align: justify; }\n"; + } + return $s; + } + + function getBodyOptions() + { + global $wgUser, $wgTitle, $wgNamespaceBackgrounds, $wgOut, $oldid, $redirect, $diff,$action; + + if ( 0 != $wgTitle->getNamespace() ) { + $a = array( "bgcolor" => "#FFFFDD" ); + } + else $a = array( "bgcolor" => "#FFFFFF" ); + if($wgOut->isArticle() && $wgUser->getOption("editondblclick") + && + (!$wgTitle->isProtected() || $wgUser->isSysop()) + + ) { + $n = $wgTitle->getPrefixedURL(); + $t = wfMsg( "editthispage" ); + $oid = $red = ""; + if ( $redirect ) { $red = "&redirect={$redirect}"; } + if ( $oldid && ! isset( $diff ) ) { + $oid = "&oldid={$oldid}"; + } + $s = wfLocalUrlE($n,"action=edit{$oid}{$red}"); + $s = "document.location = \"" .$s ."\";"; + $a += array ("ondblclick" => $s); + + } + if($action=="edit") { # set focus in edit box + $a += array("onLoad"=>"document.editform.wpTextbox1.focus()"); + } + return $a; + } + + function getExternalLinkAttributes( $link, $text ) + { + global $wgUser, $wgOut, $wgLang; + + $link = urldecode( $link ); + $link = $wgLang->checkTitleEncoding( $link ); + $link = str_replace( "_", " ", $link ); + $link = wfEscapeHTML( $link ); + + if ( $wgOut->isPrintable() ) { $r = " class='printable'"; } + else { $r = " class='external'"; } + + if ( 1 == $wgUser->getOption( "hover" ) ) { + $r .= " title=\"{$link}\""; + } + return $r; + } + + function getInternalLinkAttributes( $link, $text, $broken = false ) + { + global $wgUser, $wgOut; + + $link = urldecode( $link ); + $link = str_replace( "_", " ", $link ); + $link = wfEscapeHTML( $link ); + + if ( $wgOut->isPrintable() ) { $r = " class='printable'"; } + else if ( $broken == "stub" ) { $r = " class='stub'"; } + else if ( $broken == "yes" ) { $r = " class='new'"; } + else { $r = " class='internal'"; } + + if ( 1 == $wgUser->getOption( "hover" ) ) { + $r .= " title=\"{$link}\""; + } + return $r; + } + + function getLogo() + { + global $wgLogo; + return $wgLogo; + } + + # This will be called immediately after the tag. Split into + # two functions to make it easier to subclass. + # + function beforeContent() + { + global $wgUser, $wgOut; + + if ( $wgOut->isPrintable() ) { + $s = $this->pageTitle() . $this->pageSubtitle() . "\n"; + $s .= "\n

      "; + return $s; + } + return $this->doBeforeContent(); + } + + function doBeforeContent() + { + global $wgUser, $wgOut, $wgTitle; + wfProfileIn( "Skin::doBeforeContent" ); + + $s = ""; + $qb = $this->qbSetting(); + + if( $langlinks = $this->otherLanguages() ) { + $rows = 2; + $borderhack = ""; + } else { + $rows = 1; + $langlinks = false; + $borderhack = "class='top'"; + } + + $s .= "\n
      \n
      " . + ""; + + if ( 0 == $qb ) { + $s .= ""; + } else if ( 1 == $qb || 3 == $qb ) { # Left + $s .= $this->getQuickbarCompensator( $rows ); + } + $s .= "\n"; + + if ( $langlinks ) { + $s .= "\n"; + } + + if ( 2 == $qb ) { # Right + $s .= $this->getQuickbarCompensator( $rows ); + } + $s .= "
      " . + $this->logoText() . ""; + + $s .= $this->topLinks() ; + $s .= "

      " . $this->pageTitleLinks(); + + $s .= "

      "; + $s .= $this->nameAndLogin(); + $s .= "\n
      " . $this->searchForm() . "
      $langlinks
      \n
      \n"; + $s .= "\n
      "; + + $s .= $this->pageTitle(); + $s .= $this->pageSubtitle() . "\n

      "; + wfProfileOut(); + return $s; + } + + function getQuickbarCompensator( $rows = 1 ) + { + return " "; + } + + # This gets called immediately before the tag. + # + function afterContent() + { + global $wgUser, $wgOut, $wgServer, $HTTP_SERVER_VARS; + + if ( $wgOut->isPrintable() ) { + $s = "\n

      \n"; + + $u = $wgServer . $HTTP_SERVER_VARS['REQUEST_URI']; + $u = preg_replace( "/[?&]printable=yes/", "", $u ); + $rf = str_replace( "$1", $u, wfMsg( "retrievedfrom" ) ); + + if ( $wgOut->isArticle() ) { + $lm = "
      " . $this->lastModified(); + } else { $lm = ""; } + + $s .= "

      {$rf}{$lm}\n"; + return $s; + } + return $this->doAfterContent(); + } + + function doAfterContent() + { + global $wgUser, $wgOut; + wfProfileIn( "Skin::doAfterContent" ); + + $s = "\n


      \n"; + + $s .= "\n\n
      \n"; + + if ( 0 != $qb ) { $s .= $this->quickBar(); } + wfProfileOut(); + return $s; + } + + function pageTitleLinks() + { + global $wgOut, $wgTitle, $oldid, $action, $diff, $wgUser, $wgLang; + + $s = $this->printableLink(); + + if ( $wgOut->isArticle() ) { + if ( $wgTitle->getNamespace() == Namespace::getImage() ) { + $name = $wgTitle->getDBkey(); + $link = wfEscapeHTML( wfImageUrl( $name ) ); + $style = $this->getInternalLinkAttributes( $link, $name ); + $s .= " | {$name}"; + } + } + if ( "history" == $action || isset( $diff ) || isset( $oldid ) ) { + $s .= " | " . $this->makeKnownLink( $wgTitle->getPrefixedText(), + wfMsg( "currentrev" ) ); + } + + if ( $wgUser->getNewtalk() ) { + # do not show "You have new messages" text when we are viewing our + # own talk page + + if(!(strcmp($wgTitle->getText(),$wgUser->getName()) == 0 && + $wgTitle->getNamespace()==Namespace::getTalk(Namespace::getUser()))) { + $n =$wgUser->getName(); + $tl = $this->makeKnownLink( $wgLang->getNsText( + Namespace::getTalk( Namespace::getUser() ) ) . ":{$n}", + wfMsg("newmessageslink") ); + $s.=" | ". str_replace( "$1", $tl, wfMsg("newmessages") ) . ""; + } + } + return $s; + } + + function printableLink() + { + global $wgOut, $wgTitle, $oldid, $action; + + if ( "history" == $action ) { $q = "action=history&"; } + else { $q = ""; } + + $s = $this->makeKnownLink( $wgTitle->getPrefixedText(), + WfMsg( "printableversion" ), "{$q}printable=yes" ); + return $s; + } + + function pageTitle() + { + global $wgOut, $wgTitle; + + $s = "

      " . $wgOut->getPageTitle() . "

      "; + return $s; + } + + function pageSubtitle() + { + global $wgOut,$wgTitle,$wgNamespacesWithSubpages; + + $sub = $wgOut->getSubtitle(); + if ( "" == $sub ) { $sub = wfMsg( "fromwikipedia" ); } + if($wgOut->isArticle() && $wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { + $ptext=$wgTitle->getPrefixedText(); + if(preg_match("/\//",$ptext)) { + $sub.="

      "; + $links=explode("/",$ptext); + $c=0; + $growinglink=""; + foreach($links as $link) { + $c++; + if ($cmakeLink($growinglink,$link); + if(preg_match("/class='new'/i",$getlink)) { break; } # this is a hack, but it saves time + if ($c>1) { + $sub .= " | "; + } else { + $sub .="< "; + } + $sub .= $getlink; + $growinglink.="/"; + } + + } + } + } + $s = "

      {$sub}\n"; + return $s; + } + + function nameAndLogin() + { + global $wgUser, $wgTitle, $wgLang; + + $li = $wgLang->specialPage( "Userlogin" ); + $lo = $wgLang->specialPage( "Userlogout" ); + + $s = ""; + if ( 0 == $wgUser->getID() ) { + $n = getenv( "REMOTE_ADDR" ); + $rt = $wgTitle->getPrefixedURL(); + if ( 0 == strcasecmp( urlencode( $lo ), $rt ) ) { + $q = ""; + } else { $q = "returnto={$rt}"; } + + + $tl = $this->makeKnownLink( $wgLang->getNsText( + Namespace::getTalk( Namespace::getUser() ) ) . ":{$n}", + $wgLang->getNsText( Namespace::getTalk( 0 ) ) ); + + $s .= $n . " (".$tl.")" . "\n
      " . $this->makeKnownLink( $li, + wfMsg( "login" ), $q ); + + $tl = " ({$tl})"; + + } else { + $n = $wgUser->getName(); + $rt = $wgTitle->getPrefixedURL(); + $tl = $this->makeKnownLink( $wgLang->getNsText( + Namespace::getTalk( Namespace::getUser() ) ) . ":{$n}", + $wgLang->getNsText( Namespace::getTalk( 0 ) ) ); + + $tl = " ({$tl})"; + + $s .= $this->makeKnownLink( $wgLang->getNsText( + Namespace::getUser() ) . ":{$n}", $n ) . "{$tl}
      " . + $this->makeKnownLink( $lo, wfMsg( "logout" ), + "returnto={$rt}" ) . " | " . + $this->specialLink( "preferences" ); + } + $s .= " | " . $this->makeKnownLink( wfMsg( "helppage" ), + wfMsg( "help" ) ); + + return $s; + } + + function searchForm() + { + global $search; + $s = "

      " + . "\n" + . " 
      "; + + return $s; + } + + function topLinks() + { + global $wgOut; + $sep = " |\n"; + + $s = $this->mainPageLink() . $sep + . $this->specialLink( "recentchanges" ); + + if ( $wgOut->isArticle() ) { + $s .= $sep . $this->editThisPage() + . $sep . $this->historyLink(); + } + $s .= $sep . $this->specialPagesList(); + + return $s; + } + + function bottomLinks() + { + global $wgOut, $wgUser, $wgTitle; + $sep = " |\n"; + + $s = ""; + if ( $wgOut->isArticle() ) { + $s .= "" . $this->editThisPage() . ""; + if ( 0 != $wgUser->getID() ) { + $s .= $sep . $this->watchThisPage(); + } + $s .= $sep . $this->talkLink() + . $sep . $this->historyLink() + . $sep . $this->whatLinksHere() + . $sep . $this->watchPageLinksLink(); + + if ( $wgTitle->getNamespace() == Namespace::getUser() + || $wgTitle->getNamespace() == Namespace::getTalk(Namespace::getUser()) ) + + { + $id=User::idFromName($wgTitle->getText()); + $ip=User::isIP($wgTitle->getText()); + + if($id || $ip) { # both anons and non-anons have contri list + $s .= $sep . $this->userContribsLink(); + } + if ( 0 != $wgUser->getID() ) { # show only to signed in users + if($id) { # can only email non-anons + $s .= $sep . $this->emailUserLink(); + } + } + } + if ( $wgUser->isSysop() && $wgTitle->getArticleId() ) { + $s .= "\n
      " . $this->deleteThisPage() . + $sep . $this->protectThisPage() . + $sep . $this->moveThisPage(); + } + $s .= "
      \n" . $this->otherLanguages(); + } + return $s; + } + + function pageStats() + { + global $wgOut, $wgLang, $wgArticle; + global $oldid, $diff; + + if ( ! $wgOut->isArticle() ) { return ""; } + if ( isset( $oldid ) || isset( $diff ) ) { return ""; } + if ( 0 == $wgArticle->getID() ) { return ""; } + + $count = $wgArticle->getCount(); + $s = str_replace( "$1", $count, wfMsg( "viewcount" ) ); + + $s .= $this->lastModified(); + $s .= " ".wfMsg( "gnunote" ) ; + return "{$s}"; + } + + function lastModified() + { + global $wgLang, $wgArticle; + + $d = $wgLang->timeanddate( $wgArticle->getTimestamp(), true ); + $s = " " . str_replace( "$1", $d, wfMsg( "lastmodified" ) ); + return $s; + } + + function logoText( $align = "" ) + { + if ( "" != $align ) { $a = " align='{$align}'"; } + else { $a = ""; } + + $mp = wfMsg( "mainpage" ); + $s = "getLogo() . "\" alt=\"" . "[{$mp}]\">"; + return $s; + } + + function quickBar() + { + global $wgOut, $wgTitle, $wgUser, $action, $wgLang; + global $wpPreview; + wfProfileIn( "Skin::quickBar" ); + + $s = "\n
      "; + $s .= "\n" . $this->logoText() . "\n
      "; + + $sep = "\n
      "; + $s .= $this->mainPageLink() + . $sep . $this->specialLink( "recentchanges" ) + . $sep . $this->specialLink( "randompage" ); + if ($wgUser->getID()) { + $s.= $sep . $this->specialLink( "watchlist" ) ; + $s .= $sep .$this->makeKnownLink( $wgLang->specialPage( "Contributions" ), + wfMsg( "mycontris" ), "target=" . wfUrlencode($wgUser->getName() ) ); + + } + // only show watchlist link if logged in + if ( wfMsg ( "currentevents" ) != "-" ) $s .= $sep . $this->makeKnownLink( wfMsg( "currentevents" ), "" ) ; + $s .= "\n
      "; + $articleExists = $wgTitle->getArticleId(); + if ( $wgOut->isArticle() || $action =="edit" || $action =="history" || $wpPreview) { + + if($wgOut->isArticle()) { + $s .= "" . $this->editThisPage() . ""; + } else { # backlink to the article in edit or history mode + + if($articleExists){ # no backlink if no article + $tns=$wgTitle->getNamespace(); + switch($tns) { + case 0: + $text = wfMsg("articlepage"); + break; + case 1: + $text = wfMsg("viewtalkpage"); + break; + case 2: + $text = wfMsg("userpage"); + break; + case 3: + $text = wfMsg("viewtalkpage"); + break; + case 4: + $text = wfMsg("wikipediapage"); + break; + case 5: + $text = wfMsg("viewtalkpage"); + break; + case 6: + $text = wfMsg("imagepage"); + break; + case 7: + $text = wfMsg("viewtalkpage"); + break; + default: + $text= wfMsg("articlepage"); + } + + $link = $wgTitle->getText(); + if ($nstext = $wgLang->getNsText($tns) ) { # add namespace if necessary + $link = $nstext . ":" . $link ; + } + $s .= $this->makeLink($link, $text ); + } elseif( $wgTitle->getNamespace() != Namespace::getSpecial() ) { + # we just throw in a "New page" text to tell the user that he's in edit mode, + # and to avoid messing with the separator that is prepended to the next item + $s .= "" . wfMsg("newpage") . ""; + } + + } + + /* + watching could cause problems in edit mode: + if user edits article, then loads "watch this article" in background and then saves + article with "Watch this article" checkbox disabled, the article is transparently + unwatched. Therefore we do not show the "Watch this page" link in edit mode + */ + if ( 0 != $wgUser->getID() && $articleExists) { + if($action!="edit" && $action!="history" && + $action != "submit" ) + {$s .= $sep . $this->watchThisPage(); } + if ( $wgTitle->userCanEdit() ) $s .= $sep . $this->moveThisPage(); + } + if ( $wgUser->isSysop() and $articleExists ) { + $s .= $sep . $this->deleteThisPage() . + $sep . $this->protectThisPage(); + } + $s .= $sep . $this->talkLink(); + if ($articleExists && $action !="history") { $s .= $sep . $this->historyLink();} + $s.=$sep . $this->whatLinksHere(); + + if($wgOut->isArticle()) { + $s .= $sep . $this->watchPageLinksLink(); + } + + if ( Namespace::getUser() == $wgTitle->getNamespace() + || $wgTitle->getNamespace() == Namespace::getTalk(Namespace::getUser()) + ) { + + $id=User::idFromName($wgTitle->getText()); + $ip=User::isIP($wgTitle->getText()); + + if($id||$ip) { + $s .= $sep . $this->userContribsLink(); + } + if ( 0 != $wgUser->getID() ) { + if($id) { # can only email real users + $s .= $sep . $this->emailUserLink(); + } + } + } + $s .= "\n
      "; + } + + if ( 0 != $wgUser->getID() ) { + $s .= $this->specialLink( "upload" ) . $sep; + } + $s .= $this->specialLink( "specialpages" ) + . $sep . $this->bugReportsLink(); + + $s .= "\n
      \n"; + wfProfileOut(); + return $s; + } + + function specialPagesList() + { + global $wgUser, $wgOut, $wgLang, $wgServer, $wgRedirectScript; + $a = array(); + + $validSP = $wgLang->getValidSpecialPages(); + + foreach ( $validSP as $name => $desc ) { + if ( "" == $desc ) { continue; } + $a[$name] = $desc; + } + if ( $wgUser->isSysop() ) + { + $sysopSP = $wgLang->getSysopSpecialPages(); + + foreach ( $sysopSP as $name => $desc ) { + if ( "" == $desc ) { continue; } + $a[$name] = $desc ; + } + } + if ( $wgUser->isDeveloper() ) + { + $devSP = $wgLang->getDeveloperSpecialPages(); + + foreach ( $devSP as $name => $desc ) { + if ( "" == $desc ) { continue; } + $a[$name] = $desc ; + } + } + $go = wfMsg( "go" ); + $sp = wfMsg( "specialpages" ); + $spp = $wgLang->specialPage( "Specialpages" ); + + $s = "
      \n"; + $s .= "\n"; + $s .= "\n"; + $s .= "
      \n"; + return $s; + } + + function mainPageLink() + { + $mp = wfMsg( "mainpage" ); + $s = $this->makeKnownLink( $mp, $mp ); + return $s; + } + + function copyrightLink() + { + $s = $this->makeKnownLink( wfMsg( "copyrightpage" ), + wfMsg( "copyrightpagename" ) ); + return $s; + } + + function aboutLink() + { + $s = $this->makeKnownLink( wfMsg( "aboutpage" ), + wfMsg( "aboutwikipedia" ) ); + return $s; + } + + function editThisPage() + { + global $wgOut, $wgTitle, $oldid, $redirect, $diff; + + if ( ! $wgOut->isArticle() || $diff ) { + $s = wfMsg( "protectedpage" ); + } else if ( $wgTitle->userCanEdit() ) { + $n = $wgTitle->getPrefixedText(); + $t = wfMsg( "editthispage" ); + $oid = $red = ""; + + if ( $redirect ) { $red = "&redirect={$redirect}"; } + if ( $oldid && ! isset( $diff ) ) { + $oid = "&oldid={$oldid}"; + } + $s = $this->makeKnownLink( $n, $t, "action=edit{$oid}{$red}" ); + } else { + $s = wfMsg( "protectedpage" ); + } + return $s; + } + + function deleteThisPage() + { + global $wgUser, $wgOut, $wgTitle, $diff; + + if ( $wgTitle->getArticleId() && ( ! $diff ) && $wgUser->isSysop() ) { + $n = $wgTitle->getPrefixedText(); + $t = wfMsg( "deletethispage" ); + + $s = $this->makeKnownLink( $n, $t, "action=delete" ); + } else { + $s = wfMsg( "error" ); + } + return $s; + } + + function protectThisPage() + { + global $wgUser, $wgOut, $wgTitle, $diff; + + if ( $wgTitle->getArticleId() && ( ! $diff ) && $wgUser->isSysop() ) { + $n = $wgTitle->getPrefixedText(); + + if ( $wgTitle->isProtected() ) { + $t = wfMsg( "unprotectthispage" ); + $q = "action=unprotect"; + } else { + $t = wfMsg( "protectthispage" ); + $q = "action=protect"; + } + $s = $this->makeKnownLink( $n, $t, $q ); + } else { + $s = wfMsg( "error" ); + } + return $s; + } + + function watchThisPage() + { + global $wgUser, $wgOut, $wgTitle, $diff; + + if ( $wgOut->isArticle() && ( ! $diff ) ) { + $n = $wgTitle->getPrefixedText(); + + if ( $wgTitle->userIsWatching() ) { + $t = wfMsg( "unwatchthispage" ); + $q = "action=unwatch"; + } else { + $t = wfMsg( "watchthispage" ); + $q = "action=watch"; + } + $s = $this->makeKnownLink( $n, $t, $q ); + } else { + $s = wfMsg( "notanarticle" ); + } + return $s; + } + + function moveThisPage() + { + global $wgTitle, $wgLang; + + if ( $wgTitle->userCanEdit() ) { + $s = $this->makeKnownLink( $wgLang->specialPage( "Movepage" ), + wfMsg( "movethispage" ), "target=" . $wgTitle->getPrefixedURL() ); + } // no message if page is protected - would be redundant + return $s; + } + + function historyLink() + { + global $wgTitle; + + $s = $this->makeKnownLink( $wgTitle->getPrefixedText(), + wfMsg( "history" ), "action=history" ); + return $s; + } + + function whatLinksHere() + { + global $wgTitle, $wgLang; + + $s = $this->makeKnownLink( $wgLang->specialPage( "Whatlinkshere" ), + wfMsg( "whatlinkshere" ), "target=" . $wgTitle->getPrefixedURL() ); + return $s; + } + + function userContribsLink() + { + global $wgTitle, $wgLang; + + $s = $this->makeKnownLink( $wgLang->specialPage( "Contributions" ), + wfMsg( "contributions" ), "target=" . $wgTitle->getURL() ); + return $s; + } + + function emailUserLink() + { + global $wgTitle, $wgLang; + + $s = $this->makeKnownLink( $wgLang->specialPage( "Emailuser" ), + wfMsg( "emailuser" ), "target=" . $wgTitle->getURL() ); + return $s; + } + + function watchPageLinksLink() + { + global $wgOut, $wgTitle, $wgLang; + + if ( ! $wgOut->isArticle() ) { + $s = "(" . wfMsg( "notanarticle" ) . ")"; + } else { + $s = $this->makeKnownLink( $wgLang->specialPage( + "Recentchangeslinked" ), wfMsg( "recentchangeslinked" ), + "target=" . $wgTitle->getPrefixedURL() ); + } + return $s; + } + + function otherLanguages() + { + global $wgOut, $wgLang, $wgTitle , $wgUseNewInterlanguage ; + + $a = $wgOut->getLanguageLinks(); + if ( 0 == count( $a ) ) { + if ( !$wgUseNewInterlanguage ) return ""; + $ns = $wgLang->getNsIndex ( $wgTitle->getNamespace () ) ; + if ( $ns != 0 AND $ns != 1 ) return "" ; + $pn = "Intl" ; + $x = "mode=addlink&xt=".$wgTitle->getDBkey() ; + return $this->makeKnownLink( $wgLang->specialPage( $pn ), + wfMsg( "intl" ) , $x ); + } + + if ( !$wgUseNewInterlanguage ) { + $s = wfMsg( "otherlanguages" ) . ": "; + } else { + global $wgLanguageCode ; + $x = "mode=zoom&xt=".$wgTitle->getDBkey() ; + $x .= "&xl=".$wgLanguageCode ; + $s = $this->makeKnownLink( $wgLang->specialPage( "Intl" ), + wfMsg( "otherlanguages" ) , $x ) . ": " ; + } + + $first = true; + foreach( $a as $l ) { + if ( ! $first ) { $s .= " | "; } + $first = false; + + $nt = Title::newFromText( $l ); + $url = $nt->getFullURL(); + $text = $wgLang->getLanguageName( $nt->getInterwiki() ); + + if ( "" == $text ) { $text = $l; } + $style = $this->getExternalLinkAttributes( $l, $text ); + $s .= "{$text}"; + } + return $s; + } + + function bugReportsLink() + { + $s = $this->makeKnownLink( wfMsg( "bugreportspage" ), + wfMsg( "bugreports" ) ); + return $s; + } + + function dateLink() + { + global $wgLinkCache; + $t1 = Title::newFromText( date( "F j" ) ); + $t2 = Title::newFromText( date( "Y" ) ); + + $wgLinkCache->suspend(); + $id = $t1->getArticleID(); + $wgLinkCache->resume(); + + if ( 0 == $id ) { + $s = $this->makeBrokenLink( $t1->getText() ); + } else { + $s = $this->makeKnownLink( $t1->getText() ); + } + $s .= ", "; + + $wgLinkCache->suspend(); + $id = $t2->getArticleID(); + $wgLinkCache->resume(); + + if ( 0 == $id ) { + $s .= $this->makeBrokenLink( $t2->getText() ); + } else { + $s .= $this->makeKnownLink( $t2->getText() ); + } + return $s; + } + + function talkLink() + { + global $wgLang, $wgTitle, $wgLinkCache; + + $tns = $wgTitle->getNamespace(); + if ( -1 == $tns ) { return ""; } + + $pn = $wgTitle->getText(); + $tp = wfMsg( "talkpage" ); + if ( Namespace::isTalk( $tns ) ) { + $lns = Namespace::getSubject( $tns ); + switch($tns) { + case 1: + $text = wfMsg("articlepage"); + break; + case 3: + $text = wfMsg("userpage"); + break; + case 5: + $text = wfMsg("wikipediapage"); + break; + case 7: + $text = wfMsg("imagepage"); + break; + default: + $text= wfMsg("articlepage"); + } + } else { + + $lns = Namespace::getTalk( $tns ); + $text=$tp; + } + $n = $wgLang->getNsText( $lns ); + if ( "" == $n ) { $link = $pn; } + else { $link = "{$n}:{$pn}"; } + + $wgLinkCache->suspend(); + $s = $this->makeLink( $link, $text ); + $wgLinkCache->resume(); + + return $s; + } + + # After all the page content is transformed into HTML, it makes + # a final pass through here for things like table backgrounds. + # + function transformContent( $text ) + { + return $text; + } + + # Note: This function MUST call getArticleID() on the link, + # otherwise the cache won't get updated properly. See LINKCACHE.DOC. + # + function makeLink( $title, $text= "", $query = "", $trail = "" ) + { + global $wgOut, $wgUser; + + $nt = Title::newFromText( $title ); + + if ( $nt->isExternal() ) { + $u = $nt->getFullURL(); + if ( "" == $text ) { $text = $nt->getPrefixedText(); } + $style = $this->getExternalLinkAttributes( $link, $text ); + + $inside = ""; + if ( "" != $trail ) { + if ( preg_match( "/^([a-z]+)(.*)$$/sD", $trail, $m ) ) { + $inside = $m[1]; + $trail = $m[2]; + } + } + return "{$text}{$inside}{$trail}"; + } + if ( 0 == $nt->getNamespace() && "" == $nt->getText() ) { + return $this->makeKnownLink( $title, $text, $query, $trail ); + } + if ( ( -1 == $nt->getNamespace() ) || + ( Namespace::getImage() == $nt->getNamespace() ) ) { + return $this->makeKnownLink( $title, $text, $query, $trail ); + } + $aid = $nt->getArticleID() ; + if ( 0 == $aid ) { + return $this->makeBrokenLink( $title, $text, $query, $trail ); + } else { + $threshold = $wgUser->getOption("stubthreshold") ; + if ( $threshold > 0 ) { + $res = wfQuery ( "SELECT HIGH_PRIORITY length(cur_text) AS x, cur_namespace, cur_is_redirect FROM cur WHERE cur_id='{$aid}'" ) ; + + if ( wfNumRows( $res ) > 0 ) { + $s = wfFetchObject( $res ); + $size = $s->x; + if ( $s->cur_is_redirect OR $s->cur_namespace != 0 ) + $size = $threshold*2 ; # Really big + wfFreeResult( $res ); + } else $size = $threshold*2 ; # Really big + } else $size = 1 ; + + if ( $size < $threshold ) + return $this->makeStubLink( $title, $text, $query, $trail ); + return $this->makeKnownLink( $title, $text, $query, $trail ); + } + } + + function makeKnownLink( $title, $text = "", $query = "", $trail = "" ) + { + global $wgOut, $wgTitle; + + $nt = Title::newFromText( $title ); + $link = $nt->getPrefixedURL(); + + if ( "" == $link ) { + $u = ""; + if ( "" == $text ) { $text = $nt->getFragment(); } + } else { + $u = wfLocalUrlE( $link, $query ); + } + if ( "" != $nt->getFragment() ) { + $u .= "#" . wfEscapeHTML( $nt->getFragment() ); + } + if ( "" == $text ) { $text = $nt->getPrefixedText(); } + $style = $this->getInternalLinkAttributes( $link, $text ); + + $inside = ""; + if ( "" != $trail ) { + if ( preg_match( wfMsg("linktrail"), $trail, $m ) ) { + $inside = $m[1]; + $trail = $m[2]; + } + } + $r = "{$text}{$inside}{$trail}"; + return $r; + } + + function makeBrokenLink( $title, $text = "", $query = "", $trail = "" ) + { + global $wgOut, $wgUser; + + $nt = Title::newFromText( $title ); + $link = $nt->getPrefixedURL(); + + if ( "" == $query ) { $q = "action=edit"; } + else { $q = "action=edit&{$query}"; } + $u = wfLocalUrlE( $link, $q ); + + if ( "" == $text ) { $text = $nt->getPrefixedText(); } + $style = $this->getInternalLinkAttributes( $link, $text, "yes" ); + + $inside = ""; + if ( "" != $trail ) { + if ( preg_match( wfMsg("linktrail"), $trail, $m ) ) { + $inside = $m[1]; + $trail = $m[2]; + } + } + if ( $wgOut->isPrintable() || + ( 1 == $wgUser->getOption( "highlightbroken" ) ) ) { + $s = "{$text}{$inside}{$trail}"; + } else { + $s = "{$text}{$inside}?{$trail}"; + } + return $s; + } + + function makeStubLink( $title, $text = "", $query = "", $trail = "" ) + { + global $wgOut, $wgUser; + + $nt = Title::newFromText( $title ); + $link = $nt->getPrefixedURL(); + + $u = wfLocalUrlE( $link, $query ); + + if ( "" == $text ) { $text = $nt->getPrefixedText(); } + $style = $this->getInternalLinkAttributes( $link, $text, "stub" ); + + $inside = ""; + if ( "" != $trail ) { + if ( preg_match( wfMsg("linktrail"), $trail, $m ) ) { + $inside = $m[1]; + $trail = $m[2]; + } + } + if ( $wgOut->isPrintable() || + ( 1 == $wgUser->getOption( "highlightbroken" ) ) ) { + $s = "{$text}{$inside}{$trail}"; + } else { + $s = "{$text}{$inside}!{$trail}"; + } + return $s; + } + + function fnamePart( $url ) + { + $basename = strrchr( $url, "/" ); + if ( false === $basename ) { $basename = $url; } + else { $basename = substr( $basename, 1 ); } + return wfEscapeHTML( $basename ); + } + + function makeImage( $url, $alt = "" ) + { + global $wgOut; + + if ( "" == $alt ) { $alt = $this->fnamePart( $url ); } + $s = "\"{$alt}\""; + return $s; + } + + function makeImageLink( $name, $url, $alt = "" ) + { + global $wgOut, $wgTitle, $wgLang; + + $nt = Title::newFromText( $wgLang->getNsText( + Namespace::getImage() ) . ":{$name}" ); + $link = $nt->getPrefixedURL(); + if ( "" == $alt ) { $alt = $name; } + + $u = wfLocalUrlE( $link ); + $s = "" . + "\"{$alt}\""; + return $s; + } + + function makeMediaLink( $name, $url, $alt = "" ) + { + global $wgOut, $wgTitle; + + if ( "" == $alt ) { $alt = $name; } + $u = wfEscapeHTML( $url ); + $s = "{$alt}"; + return $s; + } + + function specialLink( $name, $key = "" ) + { + global $wgLang; + + if ( "" == $key ) { $key = strtolower( $name ); } + $pn = $wgLang->ucfirst( $name ); + return $this->makeKnownLink( $wgLang->specialPage( $pn ), + wfMsg( $key ) ); + } + + # Called by history lists and recent changes + # + + function beginRecentChangesList() + { + $rc_cache = array() ; + $rccc = 0 ; + $this->lastdate = ""; + return ""; + } + + function beginHistoryList() + { + $this->lastdate = $this->lastline = ""; + $s = "\n

      " . wfMsg( "histlegend" ) . "\n

        "; + return $s; + } + + function beginImageHistoryList() + { + $s = "\n

        " . wfMsg( "imghistory" ) . "

        \n" . + "

        " . wfMsg( "imghistlegend" ) . "\n

          "; + return $s; + } + + function endRecentChangesList() + { + $s = $this->recentChangesBlock() ; + $s .= "
        \n"; + return $s; + } + + function endHistoryList() + { + $last = wfMsg( "last" ); + + $s = preg_replace( "/!OLDID![0-9]+!/", $last, $this->lastline ); + $s .= "
      \n"; + return $s; + } + + function endImageHistoryList() + { + $s = "
    \n"; + return $s; + } + + function historyLine( $ts, $u, $ut, $ns, $ttl, $oid, $c, $isminor ) + { + global $wgLang; + + $artname = Title::makeName( $ns, $ttl ); + $last = wfMsg( "last" ); + $cur = wfMsg( "cur" ); + $cr = wfMsg( "currentrev" ); + + if ( $oid && $this->lastline ) { + $ret = preg_replace( "/!OLDID!([0-9]+)!/", $this->makeKnownLink( + $artname, $last, "diff=\\1&oldid={$oid}" ), $this->lastline ); + } else { + $ret = ""; + } + $dt = $wgLang->timeanddate( $ts, true ); + + if ( $oid ) { $q = "oldid={$oid}"; } + else { $q = ""; } + $link = $this->makeKnownLink( $artname, $dt, $q ); + + if ( 0 == $u ) { + $ul = $this->makeKnownLink( $wgLang->specialPage( "Contributions" ), + $ut, "target=" . $ut ); + } else { $ul = $this->makeLink( $wgLang->getNsText( + Namespace::getUser() ) . ":{$ut}", $ut ); } + + $s = "
  • "; + if ( $oid ) { + $curlink = $this->makeKnownLink( $artname, $cur, + "diff=0&oldid={$oid}" ); + } else { + $curlink = $cur; + } + $s .= "({$curlink}) (!OLDID!{$oid}!) . ."; + + $M = wfMsg( "minoreditletter" ); + if ( $isminor ) { $s .= " {$M}"; } + $s .= " {$link} . . {$ul}"; + + if ( "" != $c && "*" != $c ) { $s .= " (" . wfEscapeHTML($c) . ")"; } + $s .= "
  • \n"; + + $this->lastline = $s; + return $ret; + } + + function recentChangesBlockLine ( $y ) { + global $wgUploadPath ; + + $M = wfMsg( "minoreditletter" ); + $N = wfMsg( "newpageletter" ); + $r = "" ; + $r .= "" ; + $r .= "" ; + if ( $y->isnew ) $r .= $N ; + else $r .= " " ; + if ( $y->isminor ) $r .= $M ; + else $r .= " " ; + $r .= " ".$y->timestamp." " ; + $r .= "" ; + $link = $y->link ; + if ( $y->watched ) $link = "{$link}" ; + $r .= $link ; + + $r .= " (" ; + $r .= $y->curlink ; + $r .= "; " ; + $r .= $this->makeKnownLink( $y->secureName, wfMsg( "hist" ), "action=history" ); + + $r .= ") . . ".$y->userlink ; + $r .= $y->usertalklink ; + if ( $y->usercomment != "" ) + $r .= " (".wfEscapeHTML($y->usercomment).")" ; + $r .= "
    \n" ; + return $r ; + } + + function recentChangesBlockGroup ( $y ) { + global $wgUploadPath ; + + $r = "" ; + $M = wfMsg( "minoreditletter" ); + $N = wfMsg( "newpageletter" ); + $isnew = false ; + $userlinks = array () ; + foreach ( $y AS $x ) { + $oldid = $x->diffid ; + if ( $x->isnew ) $isnew = true ; + $u = $x->userlink ; + if ( !isset ( $userlinks[$u] ) ) $userlinks[$u] = 0 ; + $userlinks[$u]++ ; + } + + krsort ( $userlinks ) ; + asort ( $userlinks ) ; + $users = array () ; + $u = array_keys ( $userlinks ) ; + foreach ( $u as $x ) { + $z = $x ; + if ( $userlinks[$x] > 1 ) $z .= " ({$userlinks[$x]}×)" ; + array_push ( $users , $z ) ; + } + $users = " [".implode("; ",$users)."]" ; + + $e = $y ; + $e = array_shift ( $e ) ; + + # Arrow + $rci = "RCI{$this->rccc}" ; + $rcl = "RCL{$this->rccc}" ; + $rcm = "RCM{$this->rccc}" ; + $tl = "" ; + $tl .= "" ; + $tl .= "" ; + $tl .= "" ; + $r .= $tl ; + + # Main line + $r .= "" ; + if ( $isnew ) $r .= $N ; + else $r .= " " ; + $r .= " " ; # Minor + $r .= " ".$e->timestamp." " ; + $r .= "" ; + + $link = $e->link ; + if ( $e->watched ) $link = "{$link}" ; + $r .= $link ; + + if ( !$e->islog ) { + $r .= " (".count($y)." " ; + if ( $isnew ) $r .= wfMsg("changes"); + else $r .= $this->makeKnownLink( $e->secureName , wfMsg("changes") , "diff=0&oldid=".$oldid ) ; + $r .= "; " ; + $r .= $this->makeKnownLink( $e->secureName, wfMsg( "history" ), "action=history" ); + $r .= ")" ; + } + + $r .= $users ; + $r .= "
    \n" ; + + # Sub-entries + $r .= "\n" ; + + $this->rccc++ ; + return $r ; + } + + function recentChangesBlock () + { + global $wgUploadPath ; + if ( count ( $this->rc_cache ) == 0 ) return "" ; + $k = array_keys ( $this->rc_cache ) ; + foreach ( $k AS $x ) + { + $y = $this->rc_cache[$x] ; + if ( count ( $y ) < 2 ) { + $r .= $this->recentChangesBlockLine ( array_shift ( $y ) ) ; + } else { + $r .= $this->recentChangesBlockGroup ( $y ) ; + } + } + + return "
    {$r}
    " ; + } + + function recentChangesLine( $ts, $u, $ut, $ns, $ttl, $c, $isminor, $isnew, $watched = false, $oldid = 0 , $diffid = 0 ) + { + global $wgUser ; + $usenew = $wgUser->getOption( "usenewrc" ); + if ( $usenew ) + $r = $this->recentChangesLineNew ( $ts, $u, $ut, $ns, $ttl, $c, $isminor, $isnew, $watched , $oldid , $diffid ) ; + else + $r = $this->recentChangesLineOld ( $ts, $u, $ut, $ns, $ttl, $c, $isminor, $isnew, $watched , $oldid , $diffid ) ; + return $r ; + } + + function recentChangesLineOld( $ts, $u, $ut, $ns, $ttl, $c, $isminor, $isnew, $watched = false, $oldid = 0, $diffid = 0 ) + { + global $wgTitle, $wgLang, $wgUser; + + $d = $wgLang->date( $ts, true); + $s = ""; + if ( $d != $this->lastdate ) { + if ( "" != $this->lastdate ) { $s .= "
\n"; } + $s .= "

{$d}

\n