Force redirect headers to use absolute URLs as per spec (though
[lhc/web/wiklou.git] / includes / OutputPage.php
index 7f1948a..5dd58a6 100644 (file)
@@ -1,118 +1,7 @@
 <?
 # See design.doc
 
-function linkToMathImage ( $tex, $outputhash )
-{
-    global $wgMathPath;
-    return "<img src=\"".$wgMathPath."/".$outputhash.".png\" alt=\"".wfEscapeHTML($tex)."\">";
-}
-
-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 "<b>".$mf." (".$munk."): ".wfEscapeHTML($tex)."</b>";
-       $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 "<h3>".$mf." (".$errmsg.substr($contents, 1)."): ".wfEscapeHTML($tex)."</h3>";
-       }
-
-       $outmd5 = substr ($contents, 1, 32);
-       if (!preg_match("/^[a-f0-9]{32}$/", $outmd5))
-           return "<b>".$mf." (".$munk."): ".wfEscapeHTML($tex)."</b>";
-
-       $outmd5_sql = mysql_escape_string(pack("H32", $outmd5));
-
-       $sql = "REPLACE 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;
-    }
-}
+if($wgUseTeX) include_once( "Math.php" );
 
 class OutputPage {
        var $mHeaders, $mCookies, $mMetatags, $mKeywords;
@@ -123,6 +12,8 @@ class OutputPage {
 
        var $mDTopen, $mLastSection; # Used for processing DL, PRE
        var $mLanguageLinks, $mSupressQuickbar;
+       var $mOnloadHandler;
+       var $mDoNothing;
 
        function OutputPage()
        {
@@ -130,12 +21,14 @@ class OutputPage {
                $this->mKeywords = $this->mLinktags = array();
                $this->mHTMLtitle = $this->mPagetitle = $this->mBodytext =
                $this->mLastSection = $this->mRedirect = $this->mLastModified =
-               $this->mSubtitle = $this->mDebugtext = $this->mRobotpolicy = "";
+               $this->mSubtitle = $this->mDebugtext = $this->mRobotpolicy = 
+               $this->mOnloadHandler = "";
                $this->mIsarticle = $this->mPrintable = true;
                $this->mSupressQuickbar = $this->mDTopen = $this->mPrintable = false;
                $this->mLanguageLinks = array();
                 $this->mCategoryLinks = array() ;
                $this->mAutonumber = 0;
+               $this->mDoNothing = false;
        }
 
        function addHeader( $name, $val ) { array_push( $this->mHeaders, "$name: $val" ) ; }
@@ -147,6 +40,10 @@ class OutputPage {
        function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
        function addLink( $rel, $rev, $target ) { array_push( $this->mLinktags, array( $rel, $rev, $target ) ); }
 
+       # checkLastModified tells the client to use the client-cached page if
+       # possible. If sucessful, the OutputPage is disabled so that
+       # any future call to OutputPage->output() have no effect. The method
+       # returns true iff cache-ok headers was sent. 
        function checkLastModified ( $timestamp )
        {
                global $wgLang, $wgCachePages, $wgUser;
@@ -167,9 +64,13 @@ class OutputPage {
                $lastmod = gmdate( "D, j M Y H:i:s", wfTimestamp2Unix(
                        max( $timestamp, $wgUser->mTouched ) ) ) . " GMT";
                
-               if( $_SERVER["HTTP_IF_MODIFIED_SINCE"] != "" ) {
-                       $ismodsince = wfUnix2Timestamp( strtotime( $_SERVER["HTTP_IF_MODIFIED_SINCE"] ) );
-                       wfDebug( "-- client send If-Modified-Since: " . $_SERVER["HTTP_IF_MODIFIED_SINCE"] . "\n", false );
+               if( !empty( $_SERVER["HTTP_IF_MODIFIED_SINCE"] ) ) {
+                       # IE sends sizes after the date like this:
+                       # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
+                       # this breaks strtotime().
+                       $modsince = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
+                       $ismodsince = wfUnix2Timestamp( strtotime( $modsince ) );
+                       wfDebug( "-- client send If-Modified-Since: " . $modsince . "\n", false );
                        wfDebug( "--  we might send Last-Modified : $lastmod\n", false ); 
                
                        if( ($ismodsince >= $timestamp ) and $wgUser->validateCache( $ismodsince ) ) {
@@ -179,7 +80,8 @@ class OutputPage {
                                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;
+                               $this->disable();
+                               return true;
                        } else {
                                wfDebug( "READY  client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
                                $this->mLastModified = $lastmod;
@@ -200,32 +102,15 @@ class OutputPage {
        function isArticle() { return $this->mIsarticle; }
        function setPrintable() { $this->mPrintable = true; }
        function isPrintable() { return $this->mPrintable; }
+       function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; }
+       function getOnloadHandler() { return $this->mOnloadHandler; }
+       function disable() { $this->mDoNothing = true; }
 
        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;
+               global $wgTitle, $wgLanguageCode;
+               global $wgDBconnection, $wgDBname;
+               return $this->mLanguageLinks;
        }
-
        function supressQuickbar() { $this->mSupressQuickbar = true; }
        function isQuickbarSupressed() { return $this->mSupressQuickbar; }
 
@@ -240,7 +125,8 @@ class OutputPage {
        function addWikiText( $text, $linestart = true )
        {
                global $wgUseTeX;
-               wfProfileIn( "OutputPage::addWikiText" );
+               $fname = "OutputPage::addWikiText";
+               wfProfileIn( $fname );
                $unique  = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4";
                $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO";
                $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp";
@@ -262,7 +148,7 @@ class OutputPage {
                                $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
                                ++$nwsecs;
                                $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]);
-                               $stripped .= $unique;
+                               $stripped .= $unique . $nwsecs . "s";
                                $text = $q[1];
                        }
                }
@@ -276,7 +162,7 @@ class OutputPage {
                                        $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 );
                                        ++$mathsecs;
                                        $mathlist[$mathsecs] = renderMath($q[0]);
-                                       $stripped2 .= $unique2;
+                                       $stripped2 .= $unique2 . $mathsecs . "s";
                                        $stripped = $q[1];
                                }
                        }
@@ -292,7 +178,7 @@ class OutputPage {
                                $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 );
                                ++$presecs;
                                $prelist[$presecs] = "<pre>". wfEscapeHTMLTagsOnly($q[0]). "</pre>";
-                               $stripped3 .= $unique3;
+                               $stripped3 .= $unique3 . $presecs . "s";
                                $stripped2 = $q[1];
                        }
                }
@@ -302,34 +188,38 @@ class OutputPage {
                $specialChars = array("\\", "$");
                $escapedChars = array("\\\\", "\\$");
                for ( $i = 1; $i <= $presecs; ++$i ) {
-                       $text = preg_replace( "/{$unique3}/", str_replace( $specialChars, 
-                               $escapedChars, $prelist[$i] ), $text, 1 );
+                       $text = preg_replace( "/{$unique3}{$i}s/", str_replace( $specialChars, 
+                               $escapedChars, $prelist[$i] ), $text );
                }
 
                for ( $i = 1; $i <= $mathsecs; ++$i ) {
-                       $text = preg_replace( "/{$unique2}/", str_replace( $specialChars, 
-                               $escapedChars, $mathlist[$i] ), $text, 1 );
+                       $text = preg_replace( "/{$unique2}{$i}s/", str_replace( $specialChars, 
+                               $escapedChars, $mathlist[$i] ), $text );
                }
 
                for ( $i = 1; $i <= $nwsecs; ++$i ) {
-                       $text = preg_replace( "/{$unique}/", str_replace( $specialChars, 
-                               $escapedChars, $nwlist[$i] ), $text, 1 );
+                       $text = preg_replace( "/{$unique}{$i}s/", str_replace( $specialChars, 
+                               $escapedChars, $nwlist[$i] ), $text );
                }
                $this->addHTML( $text );
-               wfProfileOut();
+               wfProfileOut( $fname );
        }
 
        function sendCacheControl() {
+               global $wgUseGzip;
                if( $this->mLastModified != "" ) {
                        wfDebug( "** private caching; {$this->mLastModified} **\n", false );
                        header( "Cache-Control: private, must-revalidate, max-age=0" );
-                       header( "Vary: Accept-Encoding" );
                        header( "Last-modified: {$this->mLastModified}" );
+                       if( $wgUseGzip ) {
+                               # We should put in Accept-Encoding, but IE chokes on anything but
+                               # User-Agent in a Vary: header (at least through 6.0)
+                               header( "Vary: User-Agent" );
+                       }
                } else {
                        wfDebug( "** no caching **\n", false );
                        header( "Cache-Control: no-cache" ); # Experimental - see below
                        header( "Pragma: no-cache" );
-                       header( "Vary: Accept-Encoding" );
                        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!
@@ -342,18 +232,26 @@ class OutputPage {
        {
                global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
                global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
-               wfProfileIn( "OutputPage::output" );
+               if( $this->mDoNothing ){
+                       return;
+               }
+               $fname = "OutputPage::output";
+               wfProfileIn( $fname );
+               
                $sk = $wgUser->getSkin();
 
-               wfProfileIn( "OutputPage::output-headers" );
                $this->sendCacheControl();
 
                header( "Content-type: text/html; charset={$wgOutputEncoding}" );
                header( "Content-language: {$wgLanguageCode}" );
                
                if ( "" != $this->mRedirect ) {
+                       if( substr( $this->mRedirect, 0, 4 ) != "http" ) {
+                               # Standards require redirect URLs to be absolute
+                               global $wgServer;
+                               $this->mRedirect = $wgServer . $this->mRedirect;
+                       }
                        header( "Location: {$this->mRedirect}" );
-                       wfProfileOut();
                        return;
                }
 
@@ -361,36 +259,8 @@ class OutputPage {
                foreach( $this->mCookies as $name => $val ) {
                        setcookie( $name, $val, $exp, "/" );
                }
-               wfProfileOut();
-
-               wfProfileIn( "OutputPage::output-middle" );
-               $sk->initPage();
-               $this->out( $this->headElement() );
 
-               $this->out( "\n<body" );
-               $ops = $sk->getBodyOptions();
-               foreach ( $ops as $name => $val ) {
-                       $this->out( " $name='$val'" );
-               }
-               $this->out( ">\n" );
-               if ( $wgDebugComments ) {
-                       $this->out( "<!-- Wiki debugging output:\n" .
-                         $this->mDebugtext . "-->\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</body></html>" );
+               $sk->outputPage( $this );
                flush();
        }
 
@@ -408,22 +278,26 @@ class OutputPage {
 
        function setEncodings()
        {
-               global $HTTP_SERVER_VARS, $wgInputEncoding, $wgOutputEncoding;
+               global $wgInputEncoding, $wgOutputEncoding;
                global $wgUser, $wgLang;
 
                $wgInputEncoding = strtolower( $wgInputEncoding );
-               $s = $HTTP_SERVER_VARS['HTTP_ACCEPT_CHARSET'];
                
                if( $wgUser->getOption( 'altencoding' ) ) {
                        $wgLang->setAltEncoding();
                        return;
                }
 
-               if ( "" == $s ) {
+               if ( empty( $_SERVER['HTTP_ACCEPT_CHARSET'] ) ) {
                        $wgOutputEncoding = strtolower( $wgOutputEncoding );
                        return;
                }
-               $a = explode( ",", $s );
+               
+               /*
+               # This code is unused anyway!
+               # Commenting out. --bv 2003-11-15
+               
+               $a = explode( ",", $_SERVER['HTTP_ACCEPT_CHARSET'] );
                $best = 0.0;
                $bestset = "*";
 
@@ -446,13 +320,15 @@ class OutputPage {
 
 # Disable for now
 #
+               */
                $wgOutputEncoding = $wgInputEncoding;
        }
 
+       # Returns a HTML comment with the elapsed time since request. 
+       # This method has no side effects.
        function reportTime()
        {
-               global $wgRequestTime, $wgDebugLogFile, $HTTP_SERVER_VARS;
-               global $wgProfiling, $wgProfileStack, $wgUser;
+               global $wgRequestTime;
 
                list( $usec, $sec ) = explode( " ", microtime() );
                $now = (float)$sec + (float)$usec;
@@ -460,45 +336,6 @@ class OutputPage {
                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})";
-                       if($wgUser->getId() == 0)
-                               $forward .= " anon";
-                       $log = sprintf( "%s\t%04.3f\t%s\n",
-                         gmdate( "YmdHis" ), $elapsed,
-                         urldecode( $HTTP_SERVER_VARS['REQUEST_URI'] . $forward ) );
-                       error_log( $log . $prof, 3, $wgDebugLogFile );
-               }
                $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
                  $elapsed );
                return $com;
@@ -522,7 +359,7 @@ class OutputPage {
                $this->returnToMain( false );
 
                $this->output();
-               exit;
+               wfAbruptExit();
        }
 
        function sysopRequired()
@@ -537,8 +374,7 @@ class OutputPage {
 
                $sk = $wgUser->getSkin();
                $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );      
-               $text = str_replace( "$1", $ap, wfMsg( "sysoptext" ) );
-               $this->addHTML( $text );
+               $this->addHTML( wfMsg( "sysoptext", $ap ) );
                $this->returnToMain();
        }
 
@@ -554,54 +390,67 @@ class OutputPage {
 
                $sk = $wgUser->getSkin();
                $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );      
-               $text = str_replace( "$1", $ap, wfMsg( "developertext" ) );
-               $this->addHTML( $text );
+               $this->addHTML( wfMsg( "developertext", $ap ) );
                $this->returnToMain();
        }
 
        function databaseError( $fname )
        {
                global $wgUser, $wgCommandLineMode;
-
-               $this->setPageTitle( wfMsg( "databaseerror" ) );
+               
+               $this->setPageTitle( wfMsgNoDB( "databaseerror" ) );
                $this->setRobotpolicy( "noindex,nofollow" );
                $this->setArticleFlag( false );
 
                if ( $wgCommandLineMode ) {
-                       $msg = wfMsg( "dberrortextcl" );
+                       $msg = wfMsgNoDB( "dberrortextcl" );
                } else {
-                       $msg = wfMsg( "dberrortextcl" );
+                       $msg = wfMsgNoDB( "dberrortext" );
                }
+
                $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
                $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
                $msg = str_replace( "$3", wfLastErrno(), $msg );
                $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
-
+               
                if ( $wgCommandLineMode ) {
-                       print $msg;
-                       exit();
+                       print "$msg\n";
+                       wfAbruptExit();
                }
                $sk = $wgUser->getSkin();
-               $shlink = $sk->makeKnownLink( wfMsg( "searchhelppage" ),
-                 wfMsg( "searchingwikipedia" ) );
+               $shlink = $sk->makeKnownLink( wfMsgNoDB( "searchhelppage" ),
+                 wfMsgNoDB( "searchingwikipedia" ) );
                $msg = str_replace( "$5", $shlink, $msg );
 
                $this->mBodytext = $msg;
                $this->output();
-               exit();
+               wfAbruptExit();
        }
 
-       function readOnlyPage()
+       function readOnlyPage( $source = "", $protected = false )
        {
                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 );
+               if( $protected ) {
+                       $this->setPageTitle( wfMsg( "viewsource" ) );
+                       $this->addWikiText( wfMsg( "protectedtext" ) );
+               } else {
+                       $this->setPageTitle( wfMsg( "readonly" ) );
+                       $reason = file_get_contents( $wgReadOnlyFile );
+                       $this->addHTML( wfMsg( "readonlytext", $reason ) );
+               }
+               
+               if($source) {
+                       $rows = $wgUser->getOption( "rows" );
+                       $cols = $wgUser->getOption( "cols" );
+                       $text .= "</p>\n<textarea cols='$cols' rows='$rows' readonly>" .
+                               htmlspecialchars( $source ) . "\n</textarea>";
+                       $this->addHTML( $text );
+               }
+               
                $this->returnToMain( false );
        }
 
@@ -613,40 +462,32 @@ class OutputPage {
 
                $this->mBodytext = $message;
                $this->output();
-               exit;
+               wfAbruptExit();
        }
 
        function unexpectedValueError( $name, $val )
        {
-               $msg = str_replace( "$1", $name, wfMsg( "unexpected" ) );
-               $msg = str_replace( "$2", $val, $msg );
-               $this->fatalError( $msg );
+               $this->fatalError( wfMsg( "unexpected", $name, $val ) );
        }
 
        function fileCopyError( $old, $new )
        {
-               $msg = str_replace( "$1", $old, wfMsg( "filecopyerror" ) );
-               $msg = str_replace( "$2", $new, $msg );
-               $this->fatalError( $msg );
+               $this->fatalError( wfMsg( "filecopyerror", $old, $new ) );
        }
 
        function fileRenameError( $old, $new )
        {
-               $msg = str_replace( "$1", $old, wfMsg( "filerenameerror" ) );
-               $msg = str_replace( "$2", $new, $msg );
-               $this->fatalError( $msg );
+               $this->fatalError( wfMsg( "filerenameerror", $old, $new ) );
        }
 
        function fileDeleteError( $name )
        {
-               $msg = str_replace( "$1", $name, wfMsg( "filedeleteerror" ) );
-               $this->fatalError( $msg );
+               $this->fatalError( wfMsg( "filedeleteerror", $name ) );
        }
 
        function fileNotFoundError( $name )
        {
-               $msg = str_replace( "$1", $name, wfMsg( "filenotfound" ) );
-               $this->fatalError( $msg );
+               $this->fatalError( wfMsg( "filenotfound", $name ) );
        }
 
        function returnToMain( $auto = true )
@@ -659,7 +500,7 @@ class OutputPage {
                }
                $link = $sk->makeKnownLink( $returnto, "" );
 
-               $r = str_replace( "$1", $link, wfMsg( "returnto" ) );
+               $r = wfMsg( "returnto", $link );
                if ( $auto ) {
                        $wgOut->addMeta( "http:Refresh", "10;url=" .
                          wfLocalUrlE( wfUrlencode( $returnto ) ) );
@@ -668,62 +509,189 @@ class OutputPage {
        }
 
 
-function categoryMagic ()
+       function categoryMagic ()
+       {
+               global $wgTitle , $wgUseCategoryMagic ;
+               if ( !isset ( $wgUseCategoryMagic ) || !$wgUseCategoryMagic ) return ;
+               $id = $wgTitle->getArticleID() ;
+               $cat = ucfirst ( wfMsg ( "category" ) ) ;
+               $ti = $wgTitle->getText() ;
+               $ti = explode ( ":" , $ti , 2 ) ;
+               if ( $cat != $ti[0] ) return "" ;
+               $r = "<br break=all>\n" ;
+
+               $articles = array() ;
+               $parents = array () ;
+               $children = array() ;
+
+
+               global $wgUser ;
+               $sk = $wgUser->getSkin() ;
+               $sql = "SELECT l_from FROM links WHERE l_to={$id}" ;
+               $res = wfQuery ( $sql, DB_READ ) ;
+               while ( $x = wfFetchObject ( $res ) )
+               {
+               #  $t = new Title ; 
+               #  $t->newFromDBkey ( $x->l_from ) ;
+               #  $t = $t->getText() ;
+                       $t = $x->l_from ;
+                       $y = explode ( ":" , $t , 2 ) ;
+                       if ( count ( $y ) == 2 && $y[0] == $cat ) {
+                               array_push ( $children , $sk->makeLink ( $t , $y[1] ) ) ;
+                       } else {
+                               array_push ( $articles , $sk->makeLink ( $t ) ) ;
+                       }
+               }
+               wfFreeResult ( $res ) ;
+
+               # Children
+               if ( count ( $children ) > 0 )
+               {
+                       asort ( $children ) ;
+                       $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
+                       $r .= implode ( ", " , $children ) ;
+               }
+
+               # Articles
+               if ( count ( $articles ) > 0 )
+               {
+                       asort ( $articles ) ;
+                       $h =  wfMsg( "category_header", $ti[1] );
+                       $r .= "<h2>{$h}</h2>\n" ;
+                       $r .= implode ( ", " , $articles ) ;
+               }
+
+
+               return $r ;
+       }
+
+function getHTMLattrs ()
 {
-global $wgTitle , $wgUseCategoryMagic ;
-if ( !isset ( $wgUseCategoryMagic ) || !$wgUseCategoryMagic ) return ;
-$id = $wgTitle->getArticleID() ;
-$cat = ucfirst ( wfMsg ( "category" ) ) ;
-$ti = $wgTitle->getText() ;
-$ti = explode ( ":" , $ti , 2 ) ;
-if ( $cat != $ti[0] ) return "" ;
-$r = "<br break=all>\n" ;
-
-$articles = array() ;
-$parents = array () ;
-$children = array() ;
-
-
-global $wgUser ;
-$sk = $wgUser->getSkin() ;
-$sql = "SELECT l_from FROM links WHERE l_to={$id}" ;
-$res = wfQuery ( $sql ) ;
-while ( $x = wfFetchObject ( $res ) )
+               $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 */
+               );
+return $htmlattrs ;
+}
+
+function fixTableTags ( $t )
 {
-#  $t = new Title ; 
-#  $t->newFromDBkey ( $x->l_from ) ;
-#  $t = $t->getText() ;
-  $t = $x->l_from ;
-  $y = explode ( ":" , $t , 2 ) ;
-  if ( count ( $y ) == 2 && $y[0] == $cat ) 
+  if ( trim ( $t ) == "" ) return "" ; # Saves runtime ;-)
+  $htmlattrs = $this->getHTMLattrs() ;
+  
+# Strip non-approved attributes from the tag
+  $t = preg_replace(
+                   "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
+                   "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
+                   $t);
+
+  return trim ( $t ) ;
+}
+
+function doTableStuff ( $t )
+{
+  $t = explode ( "\n" , $t ) ;
+  $td = array () ; # Is currently a td tag open?
+  $ltd = array () ; # Was it TD or TH?
+  $tr = array () ; # Is currently a tr tag open?
+  $ltr = array () ; # tr attributes
+  foreach ( $t AS $k => $x )
     {
-      array_push ( $children , $sk->makeLink ( $t , $y[1] ) ) ;
+      $x = rtrim ( $x ) ;
+      $fc = substr ( $x , 0 , 1 ) ;
+      if ( "{|" == substr ( $x , 0 , 2 ) )
+       {
+         $t[$k] = "<table " . $this->fixTableTags ( substr ( $x , 3 ) ) . ">" ;
+         array_push ( $td , false ) ;
+         array_push ( $ltd , "" ) ;
+         array_push ( $tr , false ) ;
+         array_push ( $ltr , "" ) ;
+       }
+      else if ( count ( $td ) == 0 ) { } # Don't do any of the following
+      else if ( "|}" == substr ( $x , 0 , 2 ) )
+       {
+         $z = "</table>\n" ;
+          $l = array_pop ( $ltd ) ;
+          if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
+         if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
+          array_pop ( $ltr ) ;
+         $t[$k] = $z ;
+       }
+/*      else if ( "|_" == substr ( $x , 0 , 2 ) ) # Caption
+        { 
+        $z = trim ( substr ( $x , 2 ) ) ;
+        $t[$k] = "<caption>{$z}</caption>\n" ;
+        }*/
+      else if ( "|-" == substr ( $x , 0 , 2 ) ) # Allows for |---------------
+       {
+          $x = substr ( $x , 1 ) ;
+          while ( $x != "" && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ;
+          $z = "" ;
+          $l = array_pop ( $ltd ) ;
+          if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
+         if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
+          array_pop ( $ltr ) ;
+         $t[$k] = $z ;
+          array_push ( $tr , false ) ;
+         array_push ( $td , false ) ;
+          array_push ( $ltd , "" ) ;
+          array_push ( $ltr , $this->fixTableTags ( $x ) ) ;
+       }
+      else if ( "|" == $fc || "!" == $fc || "|+" == substr ( $x , 0 , 2 ) ) # Caption
+       {
+          if ( "|+" == substr ( $x , 0 , 2 ) )
+              {
+              $fc = "+" ;
+              $x = substr ( $x , 1 ) ;
+              }
+          $after = substr ( $x , 1 ) ;
+          if ( $fc == "!" ) $after = str_replace ( "!!" , "||" , $after ) ;
+          $after = explode ( "||" , $after ) ;
+          $t[$k] = "" ;
+          foreach ( $after AS $theline )
+             {
+         $z = "" ;
+          $tra = array_pop ( $ltr ) ;
+          if ( !array_pop ( $tr ) ) $z = "<tr {$tra}>\n" ;
+          array_push ( $tr , true ) ;
+          array_push ( $ltr , "" ) ;
+
+          $l = array_pop ( $ltd ) ;
+         if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
+          if ( $fc == "|" ) $l = "TD" ;
+          else if ( $fc == "!" ) $l = "TH" ;
+          else if ( $fc == "+" ) $l = "CAPTION" ;
+          else $l = "" ;
+          array_push ( $ltd , $l ) ;
+         $y = explode ( "|" , $theline , 2 ) ;
+          if ( count ( $y ) == 1 ) $y = "{$z}<{$l}>{$y[0]}" ;
+          else $y = $y = "{$z}<{$l} ".$this->fixTableTags($y[0]).">{$y[1]}" ;
+          $t[$k] .= $y ;
+         array_push ( $td , true ) ;
+             }
+       }
     }
-  else array_push ( $articles , $sk->makeLink ( $t ) ) ;
-}
-wfFreeResult ( $res ) ;
-
-# Children
- if ( count ( $children ) > 0 )
-   {
-     asort ( $children ) ;
-     $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
-     $r .= implode ( ", " , $children ) ;
-   }
-
-# Articles
- if ( count ( $articles ) > 0 )
-   {
-     asort ( $articles ) ;
-     $h = str_replace ( "$1" , $ti[1] , wfMsg("category_header") ) ;
-     $r .= "<h2>{$h}</h2>\n" ;
-     $r .= implode ( ", " , $articles ) ;
-   }
-
-
-return $r ;
+
+# Closing open td, tr && table
+while ( count ( $td ) > 0 )
+{
+if ( array_pop ( $td ) ) $t[] = "</td>" ;
+if ( array_pop ( $tr ) ) $t[] = "</tr>" ;
+$t[] = "</table>" ;
 }
 
+  $t = implode ( "\n" , $t ) ;
+#              $t = $this->removeHTMLtags( $t );
+  return $t ;
+}
 
        # Well, OK, it's actually about 14 passes.  But since all the
        # hard lifting is done inside PHP's regex code, it probably
@@ -732,7 +700,8 @@ return $r ;
        function doWikiPass2( $text, $linestart )
        {
                global $wgUser, $wgLang, $wgUseDynamicDates;
-               wfProfileIn( "OutputPage::doWikiPass2" );
+               $fname = "OutputPage::doWikiPass2";
+               wfProfileIn( $fname );
                
                $text = $this->removeHTMLtags( $text );
                $text = $this->replaceVariables( $text );
@@ -745,11 +714,13 @@ return $r ;
                $text = $this->doBlockLevels( $text, $linestart );
                
                if($wgUseDynamicDates) {
-                       $text = $wgLang->replaceDates( $text );
+                       global $wgDateFormatter;
+                       $text = $wgDateFormatter->reformat( $wgUser->getOption("date"), $text );
                }
 
                $text = $this->replaceExternalLinks( $text );
                $text = $this->replaceInternalLinks ( $text );
+               $text = $this->doTableStuff ( $text ) ;
 
                $text = $this->magicISBN( $text );
                $text = $this->magicRFC( $text );
@@ -757,9 +728,9 @@ return $r ;
 
                $sk = $wgUser->getSkin();
                $text = $sk->transformContent( $text );
-                $text .= $this->categoryMagic () ;
+               $text .= $this->categoryMagic () ;
 
-               wfProfileOut();
+               wfProfileOut( $fname );
                return $text;
        }
 
@@ -837,14 +808,15 @@ return $r ;
 
        /* private */ function replaceExternalLinks( $text )
        {
-               wfProfileIn( "OutputPage::replaceExternalLinks" );
+               $fname = "OutputPage::replaceExternalLinks";
+               wfProfileIn( $fname );
                $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();
+               wfProfileOut( $fname );
                return $text;
        }
        
@@ -918,7 +890,7 @@ return $r ;
        /* private */ function replaceInternalLinks( $s )
        {
                global $wgTitle, $wgUser, $wgLang;
-               global $wgLinkCache, $wgInterwikiMagic;
+               global $wgLinkCache, $wgInterwikiMagic, $wgUseCategoryMagic;
                global $wgNamespacesWithSubpages, $wgLanguageCode;
                wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
 
@@ -930,60 +902,67 @@ return $r ;
                $s = array_shift( $a );
                $s = substr( $s, 1 );
 
-               $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD";
-               $e2 = "/^([{$tc}]+)]](.*)\$/sD";
-               wfProfileOut();
+               $e1 = "/^([{$tc}]+)(?:\\|([^]]+))?]](.*)\$/sD";
+
+               # Special and Media are pseudo-namespaces; no pages actually exist in them
+               $image = Namespace::getImage();
+               $special = Namespace::getSpecial();
+               $media = Namespace::getMedia();
+               $nottalk = !Namespace::isTalk( $wgTitle->getNamespace() );
+               wfProfileOut( "$fname-setup" );
 
-               wfProfileIn( "$fname-loop" );
                foreach ( $a as $line ) {
-                       if ( preg_match( $e1, $line, $m ) ) { # page with alternate text
-                               
+                       if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
                                $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
+                       } else { # Invalid form; output directly
                                $s .= "[[" . $line ;
                                continue;
                        }
-                       if(substr($m[1],0,1)=="/") { # subpage
+                       
+                       /* Valid link forms:
+                       Foobar -- normal
+                       :Foobar -- override special treatment of prefix (images, language links)
+                       /Foobar -- convert to CurrentPage/Foobar
+                       /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
+                       */
+                       $c = substr($m[1],0,1);
+                       $noforce = ($c != ":");
+                       if( $c == "/" ) { # 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) {                                            
+                                       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]; 
+                       } elseif( $noforce ) { # no subpage
+                               $link = $m[1];
+                       } else {
+                               $link = substr( $m[1], 1 );
                        }
+                       if( "" == $text )
+                               $text = $link;
 
-                       if ( preg_match( "/^((?:i|x|[a-z]{2,3})(?:-[a-z0-9]+)?|[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 );
+                       $nt = Title::newFromText( $link );
+                       if( !$nt ) {
+                               $s .= "[[" . $line;
+                               continue;
+                       }
+                       $ns = $nt->getNamespace();
+                       $iw = $nt->getInterWiki();
+                       if( $noforce ) {
+                               if( $iw && $wgInterwikiMagic && $nottalk && $wgLang->getLanguageName( $iw ) ) {
+                                       array_push( $this->mLanguageLinks, $nt->getPrefixedText() );
                                        $s .= $trail;
+/* CHECK MERGE @@@
                                } else if ( "media" == $pre ) {
                                        $nt = Title::newFromText( $suf );
                                        $name = $nt->getDBkey();
@@ -993,27 +972,50 @@ return $r ;
                                        $s .= $sk->makeMediaLink( $name,
                                          wfImageUrl( $name ), $text );
                                        $s .= $trail;
+                               } else if ( isset($wgUseCategoryMagic) && $wgUseCategoryMagic && $pre == wfMsg ( "category" ) ) {
+                                       $l = $sk->makeLink ( $pre.":".ucfirst( $m[2] ), ucfirst ( $m[2] ) ) ;
+                                       array_push ( $this->mCategoryLinks , $l ) ;
+                                       $s .= $trail ;
                                } else {
                                        $l = $wgLang->getLanguageName( $pre );
-                                       if ( "" == $l or !$wgInterwikiMagic or
-                                         Namespace::isTalk( $wgTitle->getNamespace() ) ) {
-                                               if ( "" == $text ) { $text = $link; }
+                                       if ( "" == $l or !$wgInterwikiMagic or Namespace::isTalk( $wgTitle->getNamespace() ) ) {
+                                               if ( "" == $text ) { 
+                                                       $text = $link; 
+                                               }
                                                $s .= $sk->makeLink( $link, $text, "", $trail );
                                        } else if ( $pre != $wgLanguageCode ) {
                                                array_push( $this->mLanguageLinks, "$pre:$suf" );
                                                $s .= $trail;
                                        }
+*/
+                                       continue;
                                }
+                               if( $ns == $image ) {
+                                       $s .= $sk->makeImageLinkObj( $nt, $text ) . $trail;
+                                       $wgLinkCache->addImageLinkObj( $nt );
+                                       continue;
+                               }
+/* CHECK MERGE @@@
 #                      } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
 #                              $link = substr( $link, 2 );
 #                              $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
                        } else {
                                if ( "" == $text ) { $text = $link; }
+                               # Hotspot: 
                                $s .= $sk->makeLink( $link, $text, "", $trail );
+*/
                        }
+                       if( $ns == $media ) {
+                               $s .= $sk->makeMediaLinkObj( $nt, $text ) . $trail;
+                               $wgLinkCache->addImageLinkObj( $nt );
+                               continue;
+                       } elseif( $ns == $special ) {
+                               $s .= $sk->makeKnownLinkObj( $nt, $text, "", $trail );
+                               continue;
+                       }
+                       $s .= $sk->makeLinkObj( $nt, $text, "", $trail );
                }
-               wfProfileOut();
-               wfProfileOut();
+               wfProfileOut( $fname );
                return $s;
        }
 
@@ -1096,7 +1098,8 @@ return $r ;
 
        /* private */ function doBlockLevels( $text, $linestart )
        {
-               wfProfileIn( "OutputPage::doBlockLevels" );
+               $fname = "OutputPage::doBlockLevels";
+               wfProfileIn( $fname );
                # 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.
@@ -1195,43 +1198,73 @@ return $r ;
                        }
                        $this->mLastSection = "";
                }
-               wfProfileOut();
+               wfProfileOut( $fname );
                return $text;
        }
 
        /* private */ function replaceVariables( $text )
        {
                global $wgLang;
-               wfProfileIn( "OutputPage:replaceVariables" );
+               $fname = "OutputPage::replaceVariables";
+               wfProfileIn( $fname );
+
+               
+               # Basic variables
+               # See Language.php for the definition of each magic word
 
-               /* As with sigs, use server's local time --
-                  ensure this is appropriate for your audience! */
+               # As with sigs, this uses the server's local time -- ensure 
+               # this is appropriate for your audience!
                $v = date( "m" );
-               $text = str_replace( "{{CURRENTMONTH}}", $v, $text );
+               $mw =& MagicWord::get( MAG_CURRENTMONTH );
+               $text = $mw->replace( $v, $text );
+               
                $v = $wgLang->getMonthName( date( "n" ) );
-               $text = str_replace( "{{CURRENTMONTHNAME}}", $v, $text );
+               $mw =& MagicWord::get( MAG_CURRENTMONTHNAME );
+               $text = $mw->replace( $v, $text );
+               
                $v = $wgLang->getMonthNameGen( date( "n" ) );
-               $text = str_replace( "{{CURRENTMONTHNAMEGEN}}", $v, $text );
+               $mw =& MagicWord::get( MAG_CURRENTMONTHNAMEGEN );
+               $text = $mw->replace( $v, $text );
+               
                $v = date( "j" );
-               $text = str_replace( "{{CURRENTDAY}}", $v, $text );
+               $mw = MagicWord::get( MAG_CURRENTDAY );
+               $text = $mw->replace( $v, $text );
+               
                $v = $wgLang->getWeekdayName( date( "w" )+1 );
-               $text = str_replace( "{{CURRENTDAYNAME}}", $v, $text );
+               $mw =& MagicWord::get( MAG_CURRENTDAYNAME );
+               $text = $mw->replace( $v, $text );
+               
                $v = date( "Y" );
-               $text = str_replace( "{{CURRENTYEAR}}", $v, $text );
+               $mw =& MagicWord::get( MAG_CURRENTYEAR );
+               $text = $mw->replace( $v, $text );
+       
                $v = $wgLang->time( wfTimestampNow(), false );
-               $text = str_replace( "{{CURRENTTIME}}", $v, $text );
+               $mw =& MagicWord::get( MAG_CURRENTTIME );
+               $text = $mw->replace( $v, $text );
 
-               if ( false !== strstr( $text, "{{NUMBEROFARTICLES}}" ) ) {
+               $mw =& MagicWord::get( MAG_NUMBEROFARTICLES );
+               if ( $mw->match( $text ) ) {
                        $v = wfNumberOfArticles();
-                       $text = str_replace( "{{NUMBEROFARTICLES}}", $v, $text );
+                       $text = $mw->replace( $v, $text );
                }
-               wfProfileOut();
+
+               # "Variables" with an additional parameter e.g. {{MSG:wikipedia}}
+               # The callbacks are at the bottom of this file
+               $mw =& MagicWord::get( MAG_MSG );
+               $text = $mw->substituteCallback( $text, "wfReplaceMsgVar" );
+
+               $mw =& MagicWord::get( MAG_MSGNW );
+               $text = $mw->substituteCallback( $text, "wfReplaceMsgnwVar" );
+
+               wfProfileOut( $fname );
                return $text;
        }
 
+       # Cleans up HTML, removes dangerous tags and attributes
        /* private */ function removeHTMLtags( $text )
        {
-               wfProfileIn( "OutputPage::removeHTMLtags" );
+               $fname = "OutputPage::removeHTMLtags";
+               wfProfileIn( $fname );
                $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",
@@ -1253,18 +1286,7 @@ return $r ;
                $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 */
-               );
+                $htmlattrs = $this->getHTMLattrs () ;
 
                # Remove HTML comments
                $text = preg_replace( "/<!--.*-->/sU", "", $text );
@@ -1329,7 +1351,7 @@ return $r ;
                        $text .= "</$t>\n";
                        if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
                }
-               wfProfileOut();
+               wfProfileOut( $fname );
                return $text;
        }
 
@@ -1358,11 +1380,20 @@ return $r ;
                        $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
                        $esr=$wgUser->getID() && $wgUser->getOption( "editsectiononrightclick" );
                }
-               # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, do not 
-               # add TOC
-               if(preg_match("/__NOTOC__/i",$text)) { 
-                       $text=preg_replace("/__NOTOC__/i","",$text);
-                       $st=0; 
+
+               # Inhibit editsection links if requested in the page
+               if ($es) {
+                       $esw=& MagicWord::get(MAG_NOEDITSECTION);
+                       if ($esw->matchAndRemove( $text )) {
+                               $es=0;
+                       }
+               }
+               # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, 
+               # do not add TOC
+               $mw =& MagicWord::get( MAG_NOTOC );
+               if ($mw->matchAndRemove( $text ))
+               {
+                       $st = 0;
                }
 
                # never add the TOC to the Main Page. This is an entry page that should not
@@ -1409,20 +1440,29 @@ return $r ;
                                }
                        }
 
+                       // The canonized header is a version of the header text safe to use for links
                        
                        $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
-                       $tocline=$canonized_headline;
+                       $tocline = trim( $canonized_headline );
                        $canonized_headline=str_replace('"',"",$canonized_headline);
                        $canonized_headline=str_replace(" ","_",trim($canonized_headline));                     
                        $refer[$c]=$canonized_headline;
                        $refers[$canonized_headline]++;  // count how many in assoc. array so we can track dupes in anchors
                        $refcount[$c]=$refers[$canonized_headline];
+
+            // Prepend the number to the heading text
+                       
                        if($nh||$st) {
                                $tocline=$numbering ." ". $tocline;
-                               if($nh) {
+                               
+                               // Don't number the heading if it is the only one (looks silly)
+                               if($nh && count($matches[3]) > 1) {
                                        $headline=$numbering . " " . $headline; // the two are different if the line contains a link
-                               }                               
+                               }
                        }
+                       
+                       // Create the anchor for linking from the TOC to the section
+                       
                        $anchor=$canonized_headline;
                        if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
                        if($st) {
@@ -1431,14 +1471,21 @@ return $r ;
                        if($es && !isset($wpPreview)) {
                                $head[$c].=$sk->editSectionLink($c+1);
                        }
-                       $head[$c].="<H".$level.$matches[2][$c]
+                       
+                       // Put it all together
+                       
+                       $head[$c].="<h".$level.$matches[2][$c]
                         ."<a name=\"".$anchor."\">"
                         .$headline
                         ."</a>"
-                        ."</H".$level.">";
+                        ."</h".$level.">";
+                       
+                       // Add the edit section link
+                       
                        if($esr && !isset($wpPreview)) {
                                $head[$c]=$sk->editSectionScript($c+1,$head[$c]);       
                        }
+                       
                        $numbering="";
                        $c++;
                        $dot=0;
@@ -1455,12 +1502,11 @@ return $r ;
                $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
                $i=0;
 
-
                foreach($blocks as $block) {
                        if(($es) && !isset($wpPreview) && $c>0 && $i==0) {
                            # This is the [edit] link that appears for the top block of text when 
                                # section editing is enabled
-                               $full.=$sk->editSectionLink(0);                         
+                               $full.=$sk->editSectionLink(0);
                        }
                        $full.=$block;
                        if($st && $toclines>3 && !$i) {
@@ -1471,6 +1517,7 @@ return $r ;
                        $full.=$head[$i];
                        $i++;
                }
+               
                return $full;
        }
 
@@ -1481,30 +1528,30 @@ return $r ;
                $a = split( "ISBN ", " $text" );
                if ( count ( $a ) < 2 ) return $text;
                $text = substr( array_shift( $a ), 1);
-        $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+               $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
                foreach ( $a as $x ) {
                        $isbn = $blank = "" ;
                        while ( " " == $x{0} ) {
-                $blank .= " ";
-                $x = substr( $x, 1 );
+                               $blank .= " ";
+                               $x = substr( $x, 1 );
                        }
-            while ( strstr( $valid, $x{0} ) != false ) {
+                       while ( strstr( $valid, $x{0} ) != false ) {
                                $isbn .= $x{0};
                                $x = substr( $x, 1 );
                        }
-            $num = str_replace( "-", "", $isbn );
-            $num = str_replace( " ", "", $num );
+                       $num = str_replace( "-", "", $isbn );
+                       $num = str_replace( " ", "", $num );
 
-            if ( "" == $num ) {
+                       if ( "" == $num ) {
                                $text .= "ISBN $blank$x";
-            } else {
+                       } else {
                                $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
-                                 "Booksources"), "isbn={$num}" ) . "\" CLASS=\"internal\">ISBN $isbn</a>";
+                                 "Booksources"), "isbn={$num}" ) . "\" class=\"internal\">ISBN $isbn</a>";
                                $text .= $x;
                        }
                }
-        return $text;
+               return $text;
        }
 
        /* private */ function magicRFC( $text )
@@ -1556,4 +1603,22 @@ return $r ;
        }
 }
 
+# Regex callbacks, used in OutputPage::replaceVariables
+
+# Just get rid of the dangerous stuff
+# Necessary because replaceVariables is called after removeHTMLtags, 
+# and message text can come from any user
+function wfReplaceMsgVar( $matches ) {
+       global $wgOut;
+       $text = $wgOut->removeHTMLtags( wfMsg( $matches[1] ) );
+       return $text;
+}
+
+# Effective <nowiki></nowiki>
+# Not real <nowiki> because this is called after nowiki sections are processed
+function wfReplaceMsgnwVar( $matches ) {
+       $text = wfEscapeWikiText( wfMsg( $matches[1] ) );
+       return $text;
+}
+
 ?>