Cache fixes for internet explorer: vary header, deal with odd last-modified for compr...
[mediawiki.git] / includes / OutputPage.php
blob80bb8a09634c555cfa5d5d6993023c50755e8ae6
1 <?
2 # See design.doc
4 function linkToMathImage ( $tex, $outputhash )
6 global $wgMathPath;
7 return "<img src=\"".$wgMathPath."/".$outputhash.".png\" alt=\"".wfEscapeHTML($tex)."\">";
10 function renderMath( $tex )
12 global $wgUser, $wgMathDirectory, $wgTmpDirectory, $wgInputEncoding;
13 $mf = wfMsg( "math_failure" );
14 $munk = wfMsg( "math_unknown_error" );
16 $fname = "renderMath";
18 $math = $wgUser->getOption("math");
19 if ($math == 3)
20 return ('$ '.wfEscapeHTML($tex).' $');
22 $md5 = md5($tex);
23 $md5_sql = mysql_escape_string(pack("H32", $md5));
24 if ($math == 0)
25 $sql = "SELECT math_outputhash FROM math WHERE math_inputhash = '".$md5_sql."'";
26 else
27 $sql = "SELECT math_outputhash,math_html_conservativeness,math_html FROM math WHERE math_inputhash = '".$md5_sql."'";
29 $res = wfQuery( $sql, $fname );
30 if ( wfNumRows( $res ) == 0 )
32 $cmd = "./math/texvc ".escapeshellarg($wgTmpDirectory)." ".
33 escapeshellarg($wgMathDirectory)." ".escapeshellarg($tex)." ".escapeshellarg($wgInputEncoding);
34 $contents = `$cmd`;
36 if (strlen($contents) == 0)
37 return "<b>".$mf." (".$munk."): ".wfEscapeHTML($tex)."</b>";
38 $retval = substr ($contents, 0, 1);
39 if (($retval == "C") || ($retval == "M") || ($retval == "L")) {
40 if ($retval == "C")
41 $conservativeness = 2;
42 else if ($retval == "M")
43 $conservativeness = 1;
44 else
45 $conservativeness = 0;
46 $outdata = substr ($contents, 33);
48 $i = strpos($outdata, "\000");
50 $outhtml = substr($outdata, 0, $i);
51 $mathml = substr($outdata, $i+1);
53 $sql_html = "'".mysql_escape_string($outhtml)."'";
54 $sql_mathml = "'".mysql_escape_string($mathml)."'";
55 } else if (($retval == "c") || ($retval == "m") || ($retval == "l")) {
56 $outhtml = substr ($contents, 33);
57 if ($retval == "c")
58 $conservativeness = 2;
59 else if ($retval == "m")
60 $conservativeness = 1;
61 else
62 $conservativeness = 0;
63 $sql_html = "'".mysql_escape_string($outhtml)."'";
64 $mathml = '';
65 $sql_mathml = 'NULL';
66 } else if ($retval == "X") {
67 $outhtml = '';
68 $mathml = substr ($contents, 33);
69 $sql_html = 'NULL';
70 $sql_mathml = "'".mysql_escape_string($mathml)."'";
71 $conservativeness = 0;
72 } else if ($retval == "+") {
73 $outhtml = '';
74 $mathml = '';
75 $sql_html = 'NULL';
76 $sql_mathml = 'NULL';
77 $conservativeness = 0;
78 } else {
79 if ($retval == "E")
80 $errmsg = wfMsg( "math_lexing_error" );
81 else if ($retval == "S")
82 $errmsg = wfMsg( "math_syntax_error" );
83 else if ($retval == "F")
84 $errmsg = wfMsg( "math_unknown_function" );
85 else
86 $errmsg = $munk;
87 return "<h3>".$mf." (".$errmsg.substr($contents, 1)."): ".wfEscapeHTML($tex)."</h3>";
90 $outmd5 = substr ($contents, 1, 32);
91 if (!preg_match("/^[a-f0-9]{32}$/", $outmd5))
92 return "<b>".$mf." (".$munk."): ".wfEscapeHTML($tex)."</b>";
94 $outmd5_sql = mysql_escape_string(pack("H32", $outmd5));
96 $sql = "REPLACE INTO math VALUES ('".$md5_sql."', '".$outmd5_sql."', ".$conservativeness.", ".$sql_html.", ".$sql_mathml.")";
98 $res = wfQuery( $sql, $fname );
99 # we don't really care if it fails
101 if (($math == 0) || ($rpage->math_html == '') || (($math == 1) && ($conservativeness != 2)) || (($math == 4) && ($conservativeness == 0)))
102 return linkToMathImage($tex, $outmd5);
103 else
104 return $outhtml;
105 } else {
106 $rpage = wfFetchObject ( $res );
107 $outputhash = unpack( "H32md5", $rpage->math_outputhash . " " );
108 $outputhash = $outputhash ['md5'];
110 if (($math == 0) || ($rpage->math_html == '') || (($math == 1) && ($rpage->math_html_conservativeness != 2)) || (($math == 4) && ($rpage->math_html_conservativeness == 0)))
111 return linkToMathImage ( $tex, $outputhash );
112 else
113 return $rpage->math_html;
117 class OutputPage {
118 var $mHeaders, $mCookies, $mMetatags, $mKeywords;
119 var $mLinktags, $mPagetitle, $mBodytext, $mDebugtext;
120 var $mHTMLtitle, $mRobotpolicy, $mIsarticle, $mPrintable;
121 var $mSubtitle, $mRedirect, $mAutonumber, $mHeadtext;
122 var $mLastModified, $mCategoryLinks;
124 var $mDTopen, $mLastSection; # Used for processing DL, PRE
125 var $mLanguageLinks, $mSupressQuickbar;
127 function OutputPage()
129 $this->mHeaders = $this->mCookies = $this->mMetatags =
130 $this->mKeywords = $this->mLinktags = array();
131 $this->mHTMLtitle = $this->mPagetitle = $this->mBodytext =
132 $this->mLastSection = $this->mRedirect = $this->mLastModified =
133 $this->mSubtitle = $this->mDebugtext = $this->mRobotpolicy = "";
134 $this->mIsarticle = $this->mPrintable = true;
135 $this->mSupressQuickbar = $this->mDTopen = $this->mPrintable = false;
136 $this->mLanguageLinks = array();
137 $this->mCategoryLinks = array() ;
138 $this->mAutonumber = 0;
141 function addHeader( $name, $val ) { array_push( $this->mHeaders, "$name: $val" ) ; }
142 function addCookie( $name, $val ) { array_push( $this->mCookies, array( $name, $val ) ); }
143 function redirect( $url ) { $this->mRedirect = $url; }
145 # To add an http-equiv meta tag, precede the name with "http:"
146 function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); }
147 function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
148 function addLink( $rel, $rev, $target ) { array_push( $this->mLinktags, array( $rel, $rev, $target ) ); }
150 function checkLastModified ( $timestamp )
152 global $wgLang, $wgCachePages, $wgUser;
153 if( !$wgCachePages ) {
154 wfDebug( "CACHE DISABLED\n", false );
155 return;
157 if( preg_match( '/MSIE ([1-4]|5\.0)/', $_SERVER["HTTP_USER_AGENT"] ) ) {
158 # IE 5.0 has probs with our caching
159 wfDebug( "-- bad client, not caching\n", false );
160 return;
162 if( $wgUser->getOption( "nocache" ) ) {
163 wfDebug( "USER DISABLED CACHE\n", false );
164 return;
167 $lastmod = gmdate( "D, j M Y H:i:s", wfTimestamp2Unix(
168 max( $timestamp, $wgUser->mTouched ) ) ) . " GMT";
170 if( $_SERVER["HTTP_IF_MODIFIED_SINCE"] != "" ) {
171 # IE sends sizes after the date for compressed pages:
172 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
173 # this breaks strtotime().
174 $modsince = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
175 $ismodsince = wfUnix2Timestamp( strtotime( $modsince ) );
176 wfDebug( "-- client send If-Modified-Since: " . $modsince . "\n", false );
177 wfDebug( "-- we might send Last-Modified : $lastmod\n", false );
179 if( ($ismodsince >= $timestamp ) and $wgUser->validateCache( $ismodsince ) ) {
180 # Make sure you're in a place you can leave when you call us!
181 header( "HTTP/1.0 304 Not Modified" );
182 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
183 header( "Cache-Control: private, must-revalidate, max-age=0" );
184 header( "Last-Modified: {$lastmod}" );
185 wfDebug( "CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
186 exit;
187 } else {
188 wfDebug( "READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
189 $this->mLastModified = $lastmod;
191 } else {
192 wfDebug( "We're confused.\n", false );
193 $this->mLastModified = $lastmod;
197 function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; }
198 function setHTMLtitle( $name ) { $this->mHTMLtitle = $name; }
199 function setPageTitle( $name ) { $this->mPagetitle = $name; }
200 function getPageTitle() { return $this->mPagetitle; }
201 function setSubtitle( $str ) { $this->mSubtitle = $str; }
202 function getSubtitle() { return $this->mSubtitle; }
203 function setArticleFlag( $v ) { $this->mIsarticle = $v; }
204 function isArticle() { return $this->mIsarticle; }
205 function setPrintable() { $this->mPrintable = true; }
206 function isPrintable() { return $this->mPrintable; }
208 function getLanguageLinks() {
209 global $wgUseNewInterlanguage, $wgTitle, $wgLanguageCode;
210 global $wgDBconnection, $wgDBname, $wgDBintlname;
212 if ( ! $wgUseNewInterlanguage )
213 return $this->mLanguageLinks;
215 mysql_select_db( $wgDBintlname, $wgDBconnection ) or die(
216 htmlspecialchars(mysql_error()) );
218 $list = array();
219 $sql = "SELECT * FROM ilinks WHERE lang_from=\"" .
220 "{$wgLanguageCode}\" AND title_from=\"" . $wgTitle->getDBkey() . "\"";
221 $res = mysql_query( $sql, $wgDBconnection );
223 while ( $q = mysql_fetch_object ( $res ) ) {
224 $list[] = $q->lang_to . ":" . $q->title_to;
226 mysql_free_result( $res );
227 mysql_select_db( $wgDBname, $wgDBconnection ) or die(
228 htmlspecialchars(mysql_error()) );
230 return $list;
233 function supressQuickbar() { $this->mSupressQuickbar = true; }
234 function isQuickbarSupressed() { return $this->mSupressQuickbar; }
236 function addHTML( $text ) { $this->mBodytext .= $text; }
237 function addHeadtext( $text ) { $this->mHeadtext .= $text; }
238 function debug( $text ) { $this->mDebugtext .= $text; }
240 # First pass--just handle <nowiki> sections, pass the rest off
241 # to doWikiPass2() which does all the real work.
244 function addWikiText( $text, $linestart = true )
246 global $wgUseTeX;
247 wfProfileIn( "OutputPage::addWikiText" );
248 $unique = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4";
249 $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO";
250 $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp";
251 $nwlist = array();
252 $nwsecs = 0;
253 $mathlist = array();
254 $mathsecs = 0;
255 $prelist = array ();
256 $presecs = 0;
257 $stripped = "";
258 $stripped2 = "";
259 $stripped3 = "";
261 while ( "" != $text ) {
262 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
263 $stripped .= $p[0];
264 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $text = ""; }
265 else {
266 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
267 ++$nwsecs;
268 $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]);
269 $stripped .= $unique;
270 $text = $q[1];
274 if( $wgUseTeX ) {
275 while ( "" != $stripped ) {
276 $p = preg_split( "/<\\s*math\\s*>/i", $stripped, 2 );
277 $stripped2 .= $p[0];
278 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped = ""; }
279 else {
280 $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 );
281 ++$mathsecs;
282 $mathlist[$mathsecs] = renderMath($q[0]);
283 $stripped2 .= $unique2;
284 $stripped = $q[1];
287 } else {
288 $stripped2 = $stripped;
291 while ( "" != $stripped2 ) {
292 $p = preg_split( "/<\\s*pre\\s*>/i", $stripped2, 2 );
293 $stripped3 .= $p[0];
294 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped2 = ""; }
295 else {
296 $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 );
297 ++$presecs;
298 $prelist[$presecs] = "<pre>". wfEscapeHTMLTagsOnly($q[0]). "</pre>";
299 $stripped3 .= $unique3;
300 $stripped2 = $q[1];
304 $text = $this->doWikiPass2( $stripped3, $linestart );
306 $specialChars = array("\\", "$");
307 $escapedChars = array("\\\\", "\\$");
308 for ( $i = 1; $i <= $presecs; ++$i ) {
309 $text = preg_replace( "/{$unique3}/", str_replace( $specialChars,
310 $escapedChars, $prelist[$i] ), $text, 1 );
313 for ( $i = 1; $i <= $mathsecs; ++$i ) {
314 $text = preg_replace( "/{$unique2}/", str_replace( $specialChars,
315 $escapedChars, $mathlist[$i] ), $text, 1 );
318 for ( $i = 1; $i <= $nwsecs; ++$i ) {
319 $text = preg_replace( "/{$unique}/", str_replace( $specialChars,
320 $escapedChars, $nwlist[$i] ), $text, 1 );
322 $this->addHTML( $text );
323 wfProfileOut();
326 function sendCacheControl() {
327 global $wgUseGzip;
328 if( $this->mLastModified != "" ) {
329 wfDebug( "** private caching; {$this->mLastModified} **\n", false );
330 header( "Cache-Control: private, must-revalidate, max-age=0" );
331 header( "Last-modified: {$this->mLastModified}" );
332 if( $wgUseGzip ) {
333 # We should put in Accept-Encoding, but IE chokes on anything but
334 # User-Agent in a Vary: header (at least through 6.0)
335 header( "Vary: User-Agent" );
337 } else {
338 wfDebug( "** no caching **\n", false );
339 header( "Cache-Control: no-cache" ); # Experimental - see below
340 header( "Pragma: no-cache" );
341 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
343 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
346 # Finally, all the text has been munged and accumulated into
347 # the object, let's actually output it:
349 function output()
351 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
352 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
353 wfProfileIn( "OutputPage::output" );
354 $sk = $wgUser->getSkin();
356 wfProfileIn( "OutputPage::output-headers" );
357 $this->sendCacheControl();
359 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
360 header( "Content-language: {$wgLanguageCode}" );
362 if ( "" != $this->mRedirect ) {
363 header( "Location: {$this->mRedirect}" );
364 wfProfileOut();
365 return;
368 $exp = time() + $wgCookieExpiration;
369 foreach( $this->mCookies as $name => $val ) {
370 setcookie( $name, $val, $exp, "/" );
372 wfProfileOut();
374 wfProfileIn( "OutputPage::output-middle" );
375 $sk->initPage();
376 $this->out( $this->headElement() );
378 $this->out( "\n<body" );
379 $ops = $sk->getBodyOptions();
380 foreach ( $ops as $name => $val ) {
381 $this->out( " $name='$val'" );
383 $this->out( ">\n" );
384 if ( $wgDebugComments ) {
385 $this->out( "<!-- Wiki debugging output:\n" .
386 $this->mDebugtext . "-->\n" );
388 $this->out( $sk->beforeContent() );
389 wfProfileOut();
391 wfProfileIn( "OutputPage::output-bodytext" );
392 $this->out( $this->mBodytext );
393 wfProfileOut();
394 wfProfileIn( "OutputPage::output-after" );
395 $this->out( $sk->afterContent() );
396 wfProfileOut();
398 wfProfileOut(); # A hack - we can't report after here
399 $this->out( $this->reportTime() );
401 $this->out( "\n</body></html>" );
402 flush();
405 function out( $ins )
407 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
408 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
409 $outs = $ins;
410 } else {
411 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
412 if ( false === $outs ) { $outs = $ins; }
414 print $outs;
417 function setEncodings()
419 global $HTTP_SERVER_VARS, $wgInputEncoding, $wgOutputEncoding;
420 global $wgUser, $wgLang;
422 $wgInputEncoding = strtolower( $wgInputEncoding );
423 $s = $HTTP_SERVER_VARS['HTTP_ACCEPT_CHARSET'];
425 if( $wgUser->getOption( 'altencoding' ) ) {
426 $wgLang->setAltEncoding();
427 return;
430 if ( "" == $s ) {
431 $wgOutputEncoding = strtolower( $wgOutputEncoding );
432 return;
434 $a = explode( ",", $s );
435 $best = 0.0;
436 $bestset = "*";
438 foreach ( $a as $s ) {
439 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
440 $set = $m[1];
441 $q = (float)($m[2]);
442 } else {
443 $set = $s;
444 $q = 1.0;
446 if ( $q > $best ) {
447 $bestset = $set;
448 $best = $q;
451 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
452 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
453 $wgOutputEncoding = strtolower( $bestset );
455 # Disable for now
457 $wgOutputEncoding = $wgInputEncoding;
460 function reportTime()
462 global $wgRequestTime, $wgDebugLogFile, $HTTP_SERVER_VARS;
463 global $wgProfiling, $wgProfileStack, $wgUser;
465 list( $usec, $sec ) = explode( " ", microtime() );
466 $now = (float)$sec + (float)$usec;
468 list( $usec, $sec ) = explode( " ", $wgRequestTime );
469 $start = (float)$sec + (float)$usec;
470 $elapsed = $now - $start;
472 if ( "" != $wgDebugLogFile ) {
473 $prof = "";
474 if( $wgProfiling and count( $wgProfileStack ) ) {
475 $lasttime = $start;
476 foreach( $wgProfileStack as $ile ) {
477 # "foo::bar 99 0.12345 1 0.23456 2"
478 if( preg_match( '/^(\S+)\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/', $ile, $m ) ) {
479 $thisstart = (float)$m[3] + (float)$m[4] - $start;
480 $thisend = (float)$m[5] + (float)$m[6] - $start;
481 $thiselapsed = $thisend - $thisstart;
482 $thispercent = $thiselapsed / $elapsed * 100.0;
484 $prof .= sprintf( "\tat %04.3f in %04.3f (%2.1f%%) - %s %s\n",
485 $thisstart, $thiselapsed, $thispercent,
486 str_repeat( "*", $m[2] ), $m[1] );
487 $lasttime = $thistime;
488 #$prof .= "\t(^ $ile)\n";
489 } else {
490 $prof .= "\t?broken? $ile\n";
495 if( $forward = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'] )
496 $forward = " forwarded for $forward";
497 if( $client = $HTTP_SERVER_VARS['HTTP_CLIENT_IP'] )
498 $forward .= " client IP $client";
499 if( $from = $HTTP_SERVER_VARS['HTTP_FROM'] )
500 $forward .= " from $from";
501 if( $forward )
502 $forward = "\t(proxied via {$HTTP_SERVER_VARS['REMOTE_ADDR']}{$forward})";
503 if($wgUser->getId() == 0)
504 $forward .= " anon";
505 $log = sprintf( "%s\t%04.3f\t%s\n",
506 gmdate( "YmdHis" ), $elapsed,
507 urldecode( $HTTP_SERVER_VARS['REQUEST_URI'] . $forward ) );
508 error_log( $log . $prof, 3, $wgDebugLogFile );
510 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
511 $elapsed );
512 return $com;
515 # Note: these arguments are keys into wfMsg(), not text!
517 function errorpage( $title, $msg )
519 global $wgTitle;
521 $this->mDebugtext .= "Original title: " .
522 $wgTitle->getPrefixedText() . "\n";
523 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
524 $this->setPageTitle( wfMsg( $title ) );
525 $this->setRobotpolicy( "noindex,nofollow" );
526 $this->setArticleFlag( false );
528 $this->mBodytext = "";
529 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
530 $this->returnToMain( false );
532 $this->output();
533 exit;
536 function sysopRequired()
538 global $wgUser;
540 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
541 $this->setPageTitle( wfMsg( "sysoptitle" ) );
542 $this->setRobotpolicy( "noindex,nofollow" );
543 $this->setArticleFlag( false );
544 $this->mBodytext = "";
546 $sk = $wgUser->getSkin();
547 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
548 $text = str_replace( "$1", $ap, wfMsg( "sysoptext" ) );
549 $this->addHTML( $text );
550 $this->returnToMain();
553 function developerRequired()
555 global $wgUser;
557 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
558 $this->setPageTitle( wfMsg( "developertitle" ) );
559 $this->setRobotpolicy( "noindex,nofollow" );
560 $this->setArticleFlag( false );
561 $this->mBodytext = "";
563 $sk = $wgUser->getSkin();
564 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
565 $text = str_replace( "$1", $ap, wfMsg( "developertext" ) );
566 $this->addHTML( $text );
567 $this->returnToMain();
570 function databaseError( $fname )
572 global $wgUser, $wgCommandLineMode;
574 $this->setPageTitle( wfMsg( "databaseerror" ) );
575 $this->setRobotpolicy( "noindex,nofollow" );
576 $this->setArticleFlag( false );
578 if ( $wgCommandLineMode ) {
579 $msg = wfMsg( "dberrortextcl" );
580 } else {
581 $msg = wfMsg( "dberrortextcl" );
583 $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
584 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
585 $msg = str_replace( "$3", wfLastErrno(), $msg );
586 $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
588 if ( $wgCommandLineMode ) {
589 print $msg;
590 exit();
592 $sk = $wgUser->getSkin();
593 $shlink = $sk->makeKnownLink( wfMsg( "searchhelppage" ),
594 wfMsg( "searchingwikipedia" ) );
595 $msg = str_replace( "$5", $shlink, $msg );
597 $this->mBodytext = $msg;
598 $this->output();
599 exit();
602 function readOnlyPage()
604 global $wgUser, $wgReadOnlyFile;
606 $this->setPageTitle( wfMsg( "readonly" ) );
607 $this->setRobotpolicy( "noindex,nofollow" );
608 $this->setArticleFlag( false );
610 $reason = implode( "", file( $wgReadOnlyFile ) );
611 $text = str_replace( "$1", $reason, wfMsg( "readonlytext" ) );
612 $this->addHTML( $text );
613 $this->returnToMain( false );
616 function fatalError( $message )
618 $this->setPageTitle( wfMsg( "internalerror" ) );
619 $this->setRobotpolicy( "noindex,nofollow" );
620 $this->setArticleFlag( false );
622 $this->mBodytext = $message;
623 $this->output();
624 exit;
627 function unexpectedValueError( $name, $val )
629 $msg = str_replace( "$1", $name, wfMsg( "unexpected" ) );
630 $msg = str_replace( "$2", $val, $msg );
631 $this->fatalError( $msg );
634 function fileCopyError( $old, $new )
636 $msg = str_replace( "$1", $old, wfMsg( "filecopyerror" ) );
637 $msg = str_replace( "$2", $new, $msg );
638 $this->fatalError( $msg );
641 function fileRenameError( $old, $new )
643 $msg = str_replace( "$1", $old, wfMsg( "filerenameerror" ) );
644 $msg = str_replace( "$2", $new, $msg );
645 $this->fatalError( $msg );
648 function fileDeleteError( $name )
650 $msg = str_replace( "$1", $name, wfMsg( "filedeleteerror" ) );
651 $this->fatalError( $msg );
654 function fileNotFoundError( $name )
656 $msg = str_replace( "$1", $name, wfMsg( "filenotfound" ) );
657 $this->fatalError( $msg );
660 function returnToMain( $auto = true )
662 global $wgUser, $wgOut, $returnto;
664 $sk = $wgUser->getSkin();
665 if ( "" == $returnto ) {
666 $returnto = wfMsg( "mainpage" );
668 $link = $sk->makeKnownLink( $returnto, "" );
670 $r = str_replace( "$1", $link, wfMsg( "returnto" ) );
671 if ( $auto ) {
672 $wgOut->addMeta( "http:Refresh", "10;url=" .
673 wfLocalUrlE( wfUrlencode( $returnto ) ) );
675 $wgOut->addHTML( "\n<p>$r\n" );
679 function categoryMagic ()
681 global $wgTitle , $wgUseCategoryMagic ;
682 if ( !isset ( $wgUseCategoryMagic ) || !$wgUseCategoryMagic ) return ;
683 $id = $wgTitle->getArticleID() ;
684 $cat = ucfirst ( wfMsg ( "category" ) ) ;
685 $ti = $wgTitle->getText() ;
686 $ti = explode ( ":" , $ti , 2 ) ;
687 if ( $cat != $ti[0] ) return "" ;
688 $r = "<br break=all>\n" ;
690 $articles = array() ;
691 $parents = array () ;
692 $children = array() ;
695 global $wgUser ;
696 $sk = $wgUser->getSkin() ;
697 $sql = "SELECT l_from FROM links WHERE l_to={$id}" ;
698 $res = wfQuery ( $sql ) ;
699 while ( $x = wfFetchObject ( $res ) )
701 # $t = new Title ;
702 # $t->newFromDBkey ( $x->l_from ) ;
703 # $t = $t->getText() ;
704 $t = $x->l_from ;
705 $y = explode ( ":" , $t , 2 ) ;
706 if ( count ( $y ) == 2 && $y[0] == $cat )
708 array_push ( $children , $sk->makeLink ( $t , $y[1] ) ) ;
710 else array_push ( $articles , $sk->makeLink ( $t ) ) ;
712 wfFreeResult ( $res ) ;
714 # Children
715 if ( count ( $children ) > 0 )
717 asort ( $children ) ;
718 $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
719 $r .= implode ( ", " , $children ) ;
722 # Articles
723 if ( count ( $articles ) > 0 )
725 asort ( $articles ) ;
726 $h = str_replace ( "$1" , $ti[1] , wfMsg("category_header") ) ;
727 $r .= "<h2>{$h}</h2>\n" ;
728 $r .= implode ( ", " , $articles ) ;
732 return $r ;
736 # Well, OK, it's actually about 14 passes. But since all the
737 # hard lifting is done inside PHP's regex code, it probably
738 # wouldn't speed things up much to add a real parser.
740 function doWikiPass2( $text, $linestart )
742 global $wgUser, $wgLang, $wgUseDynamicDates;
743 wfProfileIn( "OutputPage::doWikiPass2" );
745 $text = $this->removeHTMLtags( $text );
746 $text = $this->replaceVariables( $text );
748 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
749 $text = str_replace ( "<HR>", "<hr>", $text );
751 $text = $this->doAllQuotes( $text );
752 $text = $this->doHeadings( $text );
753 $text = $this->doBlockLevels( $text, $linestart );
755 if($wgUseDynamicDates) {
756 $text = $wgLang->replaceDates( $text );
759 $text = $this->replaceExternalLinks( $text );
760 $text = $this->replaceInternalLinks ( $text );
762 $text = $this->magicISBN( $text );
763 $text = $this->magicRFC( $text );
764 $text = $this->formatHeadings( $text );
766 $sk = $wgUser->getSkin();
767 $text = $sk->transformContent( $text );
768 $text .= $this->categoryMagic () ;
770 wfProfileOut();
771 return $text;
774 /* private */ function doAllQuotes( $text )
776 $outtext = "";
777 $lines = explode( "\r\n", $text );
778 foreach ( $lines as $line ) {
779 $outtext .= $this->doQuotes ( "", $line, "" ) . "\r\n";
781 return $outtext;
784 /* private */ function doQuotes( $pre, $text, $mode )
786 if ( preg_match( "/^(.*)''(.*)$/sU", $text, $m ) ) {
787 $m1_strong = ($m[1] == "") ? "" : "<strong>{$m[1]}</strong>";
788 $m1_em = ($m[1] == "") ? "" : "<em>{$m[1]}</em>";
789 if ( substr ($m[2], 0, 1) == "'" ) {
790 $m[2] = substr ($m[2], 1);
791 if ($mode == "em") {
792 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ? "both" : "emstrong" );
793 } else if ($mode == "strong") {
794 return $m1_strong . $this->doQuotes ( "", $m[2], "" );
795 } else if (($mode == "emstrong") || ($mode == "both")) {
796 return $this->doQuotes ( "", $pre.$m1_strong.$m[2], "em" );
797 } else if ($mode == "strongem") {
798 return "<strong>{$pre}{$m1_em}</strong>" . $this->doQuotes ( "", $m[2], "em" );
799 } else {
800 return $m[1] . $this->doQuotes ( "", $m[2], "strong" );
802 } else {
803 if ($mode == "strong") {
804 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ? "both" : "strongem" );
805 } else if ($mode == "em") {
806 return $m1_em . $this->doQuotes ( "", $m[2], "" );
807 } else if ($mode == "emstrong") {
808 return "<em>{$pre}{$m1_strong}</em>" . $this->doQuotes ( "", $m[2], "strong" );
809 } else if (($mode == "strongem") || ($mode == "both")) {
810 return $this->doQuotes ( "", $pre.$m1_em.$m[2], "strong" );
811 } else {
812 return $m[1] . $this->doQuotes ( "", $m[2], "em" );
815 } else {
816 $text_strong = ($text == "") ? "" : "<strong>{$text}</strong>";
817 $text_em = ($text == "") ? "" : "<em>{$text}</em>";
818 if ($mode == "") {
819 return $pre . $text;
820 } else if ($mode == "em") {
821 return $pre . $text_em;
822 } else if ($mode == "strong") {
823 return $pre . $text_strong;
824 } else if ($mode == "strongem") {
825 return (($pre == "") && ($text == "")) ? "" : "<strong>{$pre}{$text_em}</strong>";
826 } else {
827 return (($pre == "") && ($text == "")) ? "" : "<em>{$pre}{$text_strong}</em>";
832 /* private */ function doHeadings( $text )
834 for ( $i = 6; $i >= 1; --$i ) {
835 $h = substr( "======", 0, $i );
836 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
837 "<h{$i}>\\1</h{$i}>\\2", $text );
839 return $text;
842 # Note: we have to do external links before the internal ones,
843 # and otherwise take great care in the order of things here, so
844 # that we don't end up interpreting some URLs twice.
846 /* private */ function replaceExternalLinks( $text )
848 wfProfileIn( "OutputPage::replaceExternalLinks" );
849 $text = $this->subReplaceExternalLinks( $text, "http", true );
850 $text = $this->subReplaceExternalLinks( $text, "https", true );
851 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
852 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
853 $text = $this->subReplaceExternalLinks( $text, "news", false );
854 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
855 wfProfileOut();
856 return $text;
859 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
861 global $wgUser, $printable;
862 global $wgAllowExternalImages;
865 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
866 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
868 # this is the list of separators that should be ignored if they
869 # are the last character of an URL but that should be included
870 # if they occur within the URL, e.g. "go to www.foo.com, where .."
871 # in this case, the last comma should not become part of the URL,
872 # but in "www.foo.com/123,2342,32.htm" it should.
873 $sep = ",;\.:";
874 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
875 $images = "gif|png|jpg|jpeg";
877 # PLEASE NOTE: The curly braces { } are not part of the regex,
878 # they are interpreted as part of the string (used to tell PHP
879 # that the content of the string should be inserted there).
880 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
881 "((?i){$images})([^{$uc}]|$)/";
883 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
884 $sk = $wgUser->getSkin();
886 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
887 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
888 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
890 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
891 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
892 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
893 "</a>\\5", $s );
894 $s = str_replace( $unique, $protocol, $s );
896 $a = explode( "[{$protocol}:", " " . $s );
897 $s = array_shift( $a );
898 $s = substr( $s, 1 );
900 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
901 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
903 foreach ( $a as $line ) {
904 if ( preg_match( $e1, $line, $m ) ) {
905 $link = "{$protocol}:{$m[1]}";
906 $trail = $m[2];
907 if ( $autonumber ) { $text = "[" . ++$this->mAutonumber . "]"; }
908 else { $text = wfEscapeHTML( $link ); }
909 } else if ( preg_match( $e2, $line, $m ) ) {
910 $link = "{$protocol}:{$m[1]}";
911 $text = $m[2];
912 $trail = $m[3];
913 } else {
914 $s .= "[{$protocol}:" . $line;
915 continue;
917 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
918 else $paren = "";
919 $la = $sk->getExternalLinkAttributes( $link, $text );
920 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
923 return $s;
926 /* private */ function replaceInternalLinks( $s )
928 global $wgTitle, $wgUser, $wgLang;
929 global $wgLinkCache, $wgInterwikiMagic, $wgUseCategoryMagic;
930 global $wgNamespacesWithSubpages, $wgLanguageCode;
931 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
933 wfProfileIn( "$fname-setup" );
934 $tc = Title::legalChars() . "#";
935 $sk = $wgUser->getSkin();
937 $a = explode( "[[", " " . $s );
938 $s = array_shift( $a );
939 $s = substr( $s, 1 );
941 $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD";
942 $e2 = "/^([{$tc}]+)]](.*)\$/sD";
943 wfProfileOut();
945 wfProfileIn( "$fname-loop" );
946 foreach ( $a as $line ) {
947 if ( preg_match( $e1, $line, $m ) ) { # page with alternate text
949 $text = $m[2];
950 $trail = $m[3];
952 } else if ( preg_match( $e2, $line, $m ) ) { # page with normal text
954 $text = "";
955 $trail = $m[2];
958 else { # Invalid form; output directly
959 $s .= "[[" . $line ;
960 continue;
962 if(substr($m[1],0,1)=="/") { # subpage
963 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
964 $m[1]=substr($m[1],1,strlen($m[1])-2);
965 $noslash=$m[1];
967 } else {
968 $noslash=substr($m[1],1);
970 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
971 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
972 if(!$text) {
973 $text= $m[1];
974 } # this might be changed for ugliness reasons
975 } else {
976 $link = $noslash; # no subpage allowed, use standard link
978 } else { # no subpage
979 $link = $m[1];
982 if ( preg_match( "/^((?:i|x|[a-z]{2,3})(?:-[a-z0-9]+)?|[A-Za-z\\x80-\\xff]+):(.*)\$/", $link, $m ) ) {
983 $pre = strtolower( $m[1] );
984 $suf = $m[2];
985 if ( $wgLang->getNsIndex( $pre ) ==
986 Namespace::getImage() ) {
987 $nt = Title::newFromText( $suf );
988 $name = $nt->getDBkey();
989 if ( "" == $text ) { $text = $nt->GetText(); }
991 $wgLinkCache->addImageLink( $name );
992 $s .= $sk->makeImageLink( $name,
993 wfImageUrl( $name ), $text );
994 $s .= $trail;
995 } else if ( "media" == $pre ) {
996 $nt = Title::newFromText( $suf );
997 $name = $nt->getDBkey();
998 if ( "" == $text ) { $text = $nt->GetText(); }
1000 $wgLinkCache->addImageLink( $name );
1001 $s .= $sk->makeMediaLink( $name,
1002 wfImageUrl( $name ), $text );
1003 $s .= $trail;
1004 } else if ( isset($wgUseCategoryMagic) && $wgUseCategoryMagic && $pre == wfMsg ( "category" ) ) {
1005 $l = $sk->makeLink ( $pre.":".ucfirst($m[2]) , ucfirst ( $m[2] ) ) ;
1006 array_push ( $this->mCategoryLinks , $l ) ;
1007 $s .= $trail ;
1008 } else {
1009 $l = $wgLang->getLanguageName( $pre );
1010 if ( "" == $l or !$wgInterwikiMagic or
1011 Namespace::isTalk( $wgTitle->getNamespace() ) ) {
1012 if ( "" == $text ) { $text = $link; }
1013 $s .= $sk->makeLink( $link, $text, "", $trail );
1014 } else if ( $pre != $wgLanguageCode ) {
1015 array_push( $this->mLanguageLinks, "$pre:$suf" );
1016 $s .= $trail;
1019 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
1020 # $link = substr( $link, 2 );
1021 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
1022 } else {
1023 if ( "" == $text ) { $text = $link; }
1024 $s .= $sk->makeLink( $link, $text, "", $trail );
1027 wfProfileOut();
1028 wfProfileOut();
1029 return $s;
1032 # Some functions here used by doBlockLevels()
1034 /* private */ function closeParagraph()
1036 $result = "";
1037 if ( 0 != strcmp( "p", $this->mLastSection ) &&
1038 0 != strcmp( "", $this->mLastSection ) ) {
1039 $result = "</" . $this->mLastSection . ">";
1041 $this->mLastSection = "";
1042 return $result;
1044 # getCommon() returns the length of the longest common substring
1045 # of both arguments, starting at the beginning of both.
1047 /* private */ function getCommon( $st1, $st2 )
1049 $fl = strlen( $st1 );
1050 $shorter = strlen( $st2 );
1051 if ( $fl < $shorter ) { $shorter = $fl; }
1053 for ( $i = 0; $i < $shorter; ++$i ) {
1054 if ( $st1{$i} != $st2{$i} ) { break; }
1056 return $i;
1058 # These next three functions open, continue, and close the list
1059 # element appropriate to the prefix character passed into them.
1061 /* private */ function openList( $char )
1063 $result = $this->closeParagraph();
1065 if ( "*" == $char ) { $result .= "<ul><li>"; }
1066 else if ( "#" == $char ) { $result .= "<ol><li>"; }
1067 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
1068 else if ( ";" == $char ) {
1069 $result .= "<dl><dt>";
1070 $this->mDTopen = true;
1072 else { $result = "<!-- ERR 1 -->"; }
1074 return $result;
1077 /* private */ function nextItem( $char )
1079 if ( "*" == $char || "#" == $char ) { return "</li><li>"; }
1080 else if ( ":" == $char || ";" == $char ) {
1081 $close = "</dd>";
1082 if ( $this->mDTopen ) { $close = "</dt>"; }
1083 if ( ";" == $char ) {
1084 $this->mDTopen = true;
1085 return $close . "<dt>";
1086 } else {
1087 $this->mDTopen = false;
1088 return $close . "<dd>";
1091 return "<!-- ERR 2 -->";
1094 /* private */function closeList( $char )
1096 if ( "*" == $char ) { return "</li></ul>"; }
1097 else if ( "#" == $char ) { return "</li></ol>"; }
1098 else if ( ":" == $char ) {
1099 if ( $this->mDTopen ) {
1100 $this->mDTopen = false;
1101 return "</dt></dl>";
1102 } else {
1103 return "</dd></dl>";
1106 return "<!-- ERR 3 -->";
1109 /* private */ function doBlockLevels( $text, $linestart )
1111 wfProfileIn( "OutputPage::doBlockLevels" );
1112 # Parsing through the text line by line. The main thing
1113 # happening here is handling of block-level elements p, pre,
1114 # and making lists from lines starting with * # : etc.
1116 $a = explode( "\n", $text );
1117 $text = $lastPref = "";
1118 $this->mDTopen = $inBlockElem = false;
1120 if ( ! $linestart ) { $text .= array_shift( $a ); }
1121 foreach ( $a as $t ) {
1122 if ( "" != $text ) { $text .= "\n"; }
1124 $oLine = $t;
1125 $opl = strlen( $lastPref );
1126 $npl = strspn( $t, "*#:;" );
1127 $pref = substr( $t, 0, $npl );
1128 $pref2 = str_replace( ";", ":", $pref );
1129 $t = substr( $t, $npl );
1131 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1132 $text .= $this->nextItem( substr( $pref, -1 ) );
1134 if ( ";" == substr( $pref, -1 ) ) {
1135 $cpos = strpos( $t, ":" );
1136 if ( ! ( false === $cpos ) ) {
1137 $term = substr( $t, 0, $cpos );
1138 $text .= $term . $this->nextItem( ":" );
1139 $t = substr( $t, $cpos + 1 );
1142 } else if (0 != $npl || 0 != $opl) {
1143 $cpl = $this->getCommon( $pref, $lastPref );
1145 while ( $cpl < $opl ) {
1146 $text .= $this->closeList( $lastPref{$opl-1} );
1147 --$opl;
1149 if ( $npl <= $cpl && $cpl > 0 ) {
1150 $text .= $this->nextItem( $pref{$cpl-1} );
1152 while ( $npl > $cpl ) {
1153 $char = substr( $pref, $cpl, 1 );
1154 $text .= $this->openList( $char );
1156 if ( ";" == $char ) {
1157 $cpos = strpos( $t, ":" );
1158 if ( ! ( false === $cpos ) ) {
1159 $term = substr( $t, 0, $cpos );
1160 $text .= $term . $this->nextItem( ":" );
1161 $t = substr( $t, $cpos + 1 );
1164 ++$cpl;
1166 $lastPref = $pref2;
1168 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1169 if ( preg_match(
1170 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1171 $text .= $this->closeParagraph();
1172 $inBlockElem = true;
1174 if ( ! $inBlockElem ) {
1175 if ( " " == $t{0} ) {
1176 $newSection = "pre";
1177 # $t = wfEscapeHTML( $t );
1179 else { $newSection = "p"; }
1181 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1182 $text .= $this->closeParagraph();
1183 $text .= "<" . $newSection . ">";
1184 } else if ( 0 != strcmp( $this->mLastSection,
1185 $newSection ) ) {
1186 $text .= $this->closeParagraph();
1187 if ( 0 != strcmp( "p", $newSection ) ) {
1188 $text .= "<" . $newSection . ">";
1191 $this->mLastSection = $newSection;
1193 if ( $inBlockElem &&
1194 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1195 $inBlockElem = false;
1198 $text .= $t;
1200 while ( $npl ) {
1201 $text .= $this->closeList( $pref2{$npl-1} );
1202 --$npl;
1204 if ( "" != $this->mLastSection ) {
1205 if ( "p" != $this->mLastSection ) {
1206 $text .= "</" . $this->mLastSection . ">";
1208 $this->mLastSection = "";
1210 wfProfileOut();
1211 return $text;
1214 /* private */ function replaceVariables( $text )
1216 global $wgLang;
1217 wfProfileIn( "OutputPage:replaceVariables" );
1219 /* As with sigs, use server's local time --
1220 ensure this is appropriate for your audience! */
1221 $v = date( "m" );
1222 $text = str_replace( "{{CURRENTMONTH}}", $v, $text );
1223 $v = $wgLang->getMonthName( date( "n" ) );
1224 $text = str_replace( "{{CURRENTMONTHNAME}}", $v, $text );
1225 $v = $wgLang->getMonthNameGen( date( "n" ) );
1226 $text = str_replace( "{{CURRENTMONTHNAMEGEN}}", $v, $text );
1227 $v = date( "j" );
1228 $text = str_replace( "{{CURRENTDAY}}", $v, $text );
1229 $v = $wgLang->getWeekdayName( date( "w" )+1 );
1230 $text = str_replace( "{{CURRENTDAYNAME}}", $v, $text );
1231 $v = date( "Y" );
1232 $text = str_replace( "{{CURRENTYEAR}}", $v, $text );
1233 $v = $wgLang->time( wfTimestampNow(), false );
1234 $text = str_replace( "{{CURRENTTIME}}", $v, $text );
1236 if ( false !== strstr( $text, "{{NUMBEROFARTICLES}}" ) ) {
1237 $v = wfNumberOfArticles();
1238 $text = str_replace( "{{NUMBEROFARTICLES}}", $v, $text );
1240 wfProfileOut();
1241 return $text;
1244 /* private */ function removeHTMLtags( $text )
1246 wfProfileIn( "OutputPage::removeHTMLtags" );
1247 $htmlpairs = array( # Tags that must be closed
1248 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1249 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1250 "strike", "strong", "tt", "var", "div", "center",
1251 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1252 "ruby", "rt" , "rb" , "rp"
1254 $htmlsingle = array(
1255 "br", "p", "hr", "li", "dt", "dd"
1257 $htmlnest = array( # Tags that can be nested--??
1258 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1259 "dl", "font", "big", "small", "sub", "sup"
1261 $tabletags = array( # Can only appear inside table
1262 "td", "th", "tr"
1265 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1266 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1268 $htmlattrs = array( # Allowed attributes--no scripting, etc.
1269 "title", "align", "lang", "dir", "width", "height",
1270 "bgcolor", "clear", /* BR */ "noshade", /* HR */
1271 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
1272 /* FONT */ "type", "start", "value", "compact",
1273 /* For various lists, mostly deprecated but safe */
1274 "summary", "width", "border", "frame", "rules",
1275 "cellspacing", "cellpadding", "valign", "char",
1276 "charoff", "colgroup", "col", "span", "abbr", "axis",
1277 "headers", "scope", "rowspan", "colspan", /* Tables */
1278 "id", "class", "name", "style" /* For CSS */
1281 # Remove HTML comments
1282 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1284 $bits = explode( "<", $text );
1285 $text = array_shift( $bits );
1286 $tagstack = array(); $tablestack = array();
1288 foreach ( $bits as $x ) {
1289 $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
1290 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1291 $x, $regs );
1292 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1293 error_reporting( $prev );
1295 $badtag = 0 ;
1296 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1297 # Check our stack
1298 if ( $slash ) {
1299 # Closing a tag...
1300 if ( ! in_array( $t, $htmlsingle ) &&
1301 ( $ot = array_pop( $tagstack ) ) != $t ) {
1302 array_push( $tagstack, $ot );
1303 $badtag = 1;
1304 } else {
1305 if ( $t == "table" ) {
1306 $tagstack = array_pop( $tablestack );
1308 $newparams = "";
1310 } else {
1311 # Keep track for later
1312 if ( in_array( $t, $tabletags ) &&
1313 ! in_array( "table", $tagstack ) ) {
1314 $badtag = 1;
1315 } else if ( in_array( $t, $tagstack ) &&
1316 ! in_array ( $t , $htmlnest ) ) {
1317 $badtag = 1 ;
1318 } else if ( ! in_array( $t, $htmlsingle ) ) {
1319 if ( $t == "table" ) {
1320 array_push( $tablestack, $tagstack );
1321 $tagstack = array();
1323 array_push( $tagstack, $t );
1325 # Strip non-approved attributes from the tag
1326 $newparams = preg_replace(
1327 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
1328 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
1329 $params);
1331 if ( ! $badtag ) {
1332 $rest = str_replace( ">", "&gt;", $rest );
1333 $text .= "<$slash$t$newparams$brace$rest";
1334 continue;
1337 $text .= "&lt;" . str_replace( ">", "&gt;", $x);
1339 # Close off any remaining tags
1340 while ( $t = array_pop( $tagstack ) ) {
1341 $text .= "</$t>\n";
1342 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1344 wfProfileOut();
1345 return $text;
1351 * This function accomplishes several tasks:
1352 * 1) Auto-number headings if that option is enabled
1353 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1354 * 3) Add a Table of contents on the top for users who have enabled the option
1355 * 4) Auto-anchor headings
1357 * It loops through all headlines, collects the necessary data, then splits up the
1358 * string and re-inserts the newly formatted headlines.
1360 * */
1361 /* private */ function formatHeadings( $text )
1363 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1364 $nh=$wgUser->getOption( "numberheadings" );
1365 $st=$wgUser->getOption( "showtoc" );
1366 if(!$wgTitle->userCanEdit()) {
1367 $es=0;
1368 $esr=0;
1369 } else {
1370 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1371 $esr=$wgUser->getID() && $wgUser->getOption( "editsectiononrightclick" );
1373 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, do not
1374 # add TOC
1375 if(preg_match("/__NOTOC__/i",$text)) {
1376 $text=preg_replace("/__NOTOC__/i","",$text);
1377 $st=0;
1380 # never add the TOC to the Main Page. This is an entry page that should not
1381 # be more than 1-2 screens large anyway
1382 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1384 # We need this to perform operations on the HTML
1385 $sk=$wgUser->getSkin();
1387 # Get all headlines for numbering them and adding funky stuff like [edit]
1388 # links
1389 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1391 # headline counter
1392 $c=0;
1394 # Ugh .. the TOC should have neat indentation levels which can be
1395 # passed to the skin functions. These are determined here
1396 foreach($matches[3] as $headline) {
1397 if($level) { $prevlevel=$level;}
1398 $level=$matches[1][$c];
1399 if(($nh||$st) && $prevlevel && $level>$prevlevel) {
1401 $h[$level]=0; // reset when we enter a new level
1402 $toc.=$sk->tocIndent($level-$prevlevel);
1403 $toclevel+=$level-$prevlevel;
1406 if(($nh||$st) && $level<$prevlevel) {
1407 $h[$level+1]=0; // reset when we step back a level
1408 $toc.=$sk->tocUnindent($prevlevel-$level);
1409 $toclevel-=$prevlevel-$level;
1412 $h[$level]++; // count number of headlines for each level
1414 if($nh||$st) {
1415 for($i=1;$i<=$level;$i++) {
1416 if($h[$i]) {
1417 if($dot) {$numbering.=".";}
1418 $numbering.=$h[$i];
1419 $dot=1;
1425 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1426 $tocline=$canonized_headline;
1427 $canonized_headline=str_replace('"',"",$canonized_headline);
1428 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1429 $refer[$c]=$canonized_headline;
1430 $refers[$canonized_headline]++; // count how many in assoc. array so we can track dupes in anchors
1431 $refcount[$c]=$refers[$canonized_headline];
1432 if($nh||$st) {
1433 $tocline=$numbering ." ". $tocline;
1434 if($nh) {
1435 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1438 $anchor=$canonized_headline;
1439 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1440 if($st) {
1441 $toc.=$sk->tocLine($anchor,$tocline,$toclevel);
1443 if($es && !isset($wpPreview)) {
1444 $head[$c].=$sk->editSectionLink($c+1);
1446 $head[$c].="<H".$level.$matches[2][$c]
1447 ."<a name=\"".$anchor."\">"
1448 .$headline
1449 ."</a>"
1450 ."</H".$level.">";
1451 if($esr && !isset($wpPreview)) {
1452 $head[$c]=$sk->editSectionScript($c+1,$head[$c]);
1454 $numbering="";
1455 $c++;
1456 $dot=0;
1459 if($st) {
1460 $toclines=$c;
1461 $toc.=$sk->tocUnindent($toclevel);
1462 $toc=$sk->tocTable($toc);
1465 // split up and insert constructed headlines
1467 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1468 $i=0;
1471 foreach($blocks as $block) {
1472 if(($es) && !isset($wpPreview) && $c>0 && $i==0) {
1473 # This is the [edit] link that appears for the top block of text when
1474 # section editing is enabled
1475 $full.=$sk->editSectionLink(0);
1477 $full.=$block;
1478 if($st && $toclines>3 && !$i) {
1479 # Let's add a top anchor just in case we want to link to the top of the page
1480 $full="<a name=\"top\"></a>".$full.$toc;
1483 $full.=$head[$i];
1484 $i++;
1486 return $full;
1489 /* private */ function magicISBN( $text )
1491 global $wgLang;
1493 $a = split( "ISBN ", " $text" );
1494 if ( count ( $a ) < 2 ) return $text;
1495 $text = substr( array_shift( $a ), 1);
1496 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1498 foreach ( $a as $x ) {
1499 $isbn = $blank = "" ;
1500 while ( " " == $x{0} ) {
1501 $blank .= " ";
1502 $x = substr( $x, 1 );
1504 while ( strstr( $valid, $x{0} ) != false ) {
1505 $isbn .= $x{0};
1506 $x = substr( $x, 1 );
1508 $num = str_replace( "-", "", $isbn );
1509 $num = str_replace( " ", "", $num );
1511 if ( "" == $num ) {
1512 $text .= "ISBN $blank$x";
1513 } else {
1514 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1515 "Booksources"), "isbn={$num}" ) . "\" CLASS=\"internal\">ISBN $isbn</a>";
1516 $text .= $x;
1519 return $text;
1522 /* private */ function magicRFC( $text )
1524 return $text;
1527 /* private */ function headElement()
1529 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding, $wgLang;
1531 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1533 if ( "" == $this->mHTMLtitle ) {
1534 $this->mHTMLtitle = $this->mPagetitle;
1536 $rtl = $wgLang->isRTL() ? " dir='RTL'" : "";
1537 $ret .= "<html lang=\"$wgLanguageCode\"$rtl><head><title>{$this->mHTMLtitle}</title>\n";
1538 array_push( $this->mMetatags, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1539 foreach ( $this->mMetatags as $tag ) {
1540 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1541 $a = "http-equiv";
1542 $tag[0] = substr( $tag[0], 5 );
1543 } else {
1544 $a = "name";
1546 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1548 $p = $this->mRobotpolicy;
1549 if ( "" == $p ) { $p = "index,follow"; }
1550 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1552 if ( count( $this->mKeywords ) > 0 ) {
1553 $ret .= "<meta name=\"keywords\" content=\"" .
1554 implode( ",", $this->mKeywords ) . "\">\n";
1556 foreach ( $this->mLinktags as $tag ) {
1557 $ret .= "<link ";
1558 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1559 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1560 $ret .= "href=\"{$tag[2]}\">\n";
1562 $sk = $wgUser->getSkin();
1563 $ret .= $sk->getHeadScripts();
1564 $ret .= $sk->getUserStyles();
1566 $ret .= "</head>\n";
1567 return $ret;