Fixes missing cache-control and last-modified headers with file cached pages.
[lhc/web/wiklou.git] / includes / OutputPage.php
1 <?
2 # See design.doc
3
4 function linkToMathImage ( $tex, $outputhash )
5 {
6 global $wgMathPath;
7 return "<img src=\"".$wgMathPath."/".$outputhash.".png\" alt=\"".wfEscapeHTML($tex)."\">";
8 }
9
10 function renderMath( $tex )
11 {
12 global $wgUser, $wgMathDirectory, $wgTmpDirectory, $wgInputEncoding;
13 $mf = wfMsg( "math_failure" );
14 $munk = wfMsg( "math_unknown_error" );
15
16 $fname = "renderMath";
17
18 $math = $wgUser->getOption("math");
19 if ($math == 3)
20 return ('$ '.wfEscapeHTML($tex).' $');
21
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."'";
28
29 $res = wfQuery( $sql, $fname );
30 if ( wfNumRows( $res ) == 0 )
31 {
32 $cmd = "./math/texvc ".escapeshellarg($wgTmpDirectory)." ".
33 escapeshellarg($wgMathDirectory)." ".escapeshellarg($tex)." ".escapeshellarg($wgInputEncoding);
34 $contents = `$cmd`;
35
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);
47
48 $i = strpos($outdata, "\000");
49
50 $outhtml = substr($outdata, 0, $i);
51 $mathml = substr($outdata, $i+1);
52
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>";
88 }
89
90 $outmd5 = substr ($contents, 1, 32);
91 if (!preg_match("/^[a-f0-9]{32}$/", $outmd5))
92 return "<b>".$mf." (".$munk."): ".wfEscapeHTML($tex)."</b>";
93
94 $outmd5_sql = mysql_escape_string(pack("H32", $outmd5));
95
96 $sql = "REPLACE INTO math VALUES ('".$md5_sql."', '".$outmd5_sql."', ".$conservativeness.", ".$sql_html.", ".$sql_mathml.")";
97
98 $res = wfQuery( $sql, $fname );
99 # we don't really care if it fails
100
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'];
109
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;
114 }
115 }
116
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;
123
124 var $mDTopen, $mLastSection; # Used for processing DL, PRE
125 var $mLanguageLinks, $mSupressQuickbar;
126
127 function OutputPage()
128 {
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->mAutonumber = 0;
138 }
139
140 function addHeader( $name, $val ) { array_push( $this->mHeaders, "$name: $val" ) ; }
141 function addCookie( $name, $val ) { array_push( $this->mCookies, array( $name, $val ) ); }
142 function redirect( $url ) { $this->mRedirect = $url; }
143
144 # To add an http-equiv meta tag, precede the name with "http:"
145 function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); }
146 function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
147 function addLink( $rel, $rev, $target ) { array_push( $this->mLinktags, array( $rel, $rev, $target ) ); }
148
149 function checkLastModified ( $timestamp )
150 {
151 global $wgLang, $wgCachePages, $wgUser;
152 if( !$wgCachePages ) return;
153 if( preg_match( '/MSIE ([1-4]|5\.0)/', $_SERVER["HTTP_USER_AGENT"] ) ) {
154 # IE 5.0 has probs with our caching
155 #wfDebug( "-- bad client, not caching\n", false );
156 return;
157 }
158 if( $wgUser->getOption( "nocache" ) ) return;
159
160 if( $_SERVER["HTTP_IF_MODIFIED_SINCE"] != "" ) {
161 $ismodsince = wfUnix2Timestamp( strtotime( $_SERVER["HTTP_IF_MODIFIED_SINCE"] ) );
162 #wfDebug( "-- client send If-Modified-Since: " . $_SERVER["HTTP_IF_MODIFIED_SINCE"] . "\n", false );
163 $lastmod = gmdate( "D, j M Y H:i:s", wfTimestamp2Unix(
164 max( $timestamp, $wgUser->mTouched ) ) ) . " GMT";
165 #wfDebug( "-- we might send Last-Modified : $lastmod\n", false );
166
167 if( ($ismodsince >= $timestamp ) and $wgUser->validateCache( $ismodsince ) ) {
168 # Make sure you're in a place you can leave when you call us!
169 header( "HTTP/1.0 304 Not Modified" );
170 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
171 header( "Cache-Control: private, must-revalidate, max-age=0" );
172 header( "Last-Modified: {$lastmod}" );
173 #wfDebug( "CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
174 exit;
175 } else {
176 #wfDebug( "READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
177 $this->mLastModified = $lastmod;
178 }
179 }
180 }
181
182 function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; }
183 function setHTMLtitle( $name ) { $this->mHTMLtitle = $name; }
184 function setPageTitle( $name ) { $this->mPagetitle = $name; }
185 function getPageTitle() { return $this->mPagetitle; }
186 function setSubtitle( $str ) { $this->mSubtitle = $str; }
187 function getSubtitle() { return $this->mSubtitle; }
188 function setArticleFlag( $v ) { $this->mIsarticle = $v; }
189 function isArticle() { return $this->mIsarticle; }
190 function setPrintable() { $this->mPrintable = true; }
191 function isPrintable() { return $this->mPrintable; }
192
193 function getLanguageLinks() {
194 global $wgUseNewInterlanguage, $wgTitle, $wgLanguageCode;
195 global $wgDBconnection, $wgDBname, $wgDBintlname;
196
197 if ( ! $wgUseNewInterlanguage )
198 return $this->mLanguageLinks;
199
200 mysql_select_db( $wgDBintlname, $wgDBconnection ) or die(
201 htmlspecialchars(mysql_error()) );
202
203 $list = array();
204 $sql = "SELECT * FROM ilinks WHERE lang_from=\"" .
205 "{$wgLanguageCode}\" AND title_from=\"" . $wgTitle->getDBkey() . "\"";
206 $res = mysql_query( $sql, $wgDBconnection );
207
208 while ( $q = mysql_fetch_object ( $res ) ) {
209 $list[] = $q->lang_to . ":" . $q->title_to;
210 }
211 mysql_free_result( $res );
212 mysql_select_db( $wgDBname, $wgDBconnection ) or die(
213 htmlspecialchars(mysql_error()) );
214
215 return $list;
216 }
217
218 function supressQuickbar() { $this->mSupressQuickbar = true; }
219 function isQuickbarSupressed() { return $this->mSupressQuickbar; }
220
221 function addHTML( $text ) { $this->mBodytext .= $text; }
222 function addHeadtext( $text ) { $this->mHeadtext .= $text; }
223 function debug( $text ) { $this->mDebugtext .= $text; }
224
225 # First pass--just handle <nowiki> sections, pass the rest off
226 # to doWikiPass2() which does all the real work.
227 #
228
229 function addWikiText( $text, $linestart = true )
230 {
231 global $wgUseTeX;
232 wfProfileIn( "OutputPage::addWikiText" );
233 $unique = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4";
234 $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO";
235 $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp";
236 $nwlist = array();
237 $nwsecs = 0;
238 $mathlist = array();
239 $mathsecs = 0;
240 $prelist = array ();
241 $presecs = 0;
242 $stripped = "";
243 $stripped2 = "";
244 $stripped3 = "";
245
246 while ( "" != $text ) {
247 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
248 $stripped .= $p[0];
249 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $text = ""; }
250 else {
251 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
252 ++$nwsecs;
253 $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]);
254 $stripped .= $unique;
255 $text = $q[1];
256 }
257 }
258
259 if( $wgUseTeX ) {
260 while ( "" != $stripped ) {
261 $p = preg_split( "/<\\s*math\\s*>/i", $stripped, 2 );
262 $stripped2 .= $p[0];
263 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped = ""; }
264 else {
265 $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 );
266 ++$mathsecs;
267 $mathlist[$mathsecs] = renderMath($q[0]);
268 $stripped2 .= $unique2;
269 $stripped = $q[1];
270 }
271 }
272 } else {
273 $stripped2 = $stripped;
274 }
275
276 while ( "" != $stripped2 ) {
277 $p = preg_split( "/<\\s*pre\\s*>/i", $stripped2, 2 );
278 $stripped3 .= $p[0];
279 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped2 = ""; }
280 else {
281 $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 );
282 ++$presecs;
283 $prelist[$presecs] = "<pre>". wfEscapeHTMLTagsOnly($q[0]). "</pre>";
284 $stripped3 .= $unique3;
285 $stripped2 = $q[1];
286 }
287 }
288
289 $text = $this->doWikiPass2( $stripped3, $linestart );
290
291 for ( $i = 1; $i <= $presecs; ++$i ) {
292 $text = preg_replace( "/{$unique3}/", str_replace( '$', '\$', $prelist[$i] ), $text, 1 );
293 }
294
295 for ( $i = 1; $i <= $mathsecs; ++$i ) {
296 $text = preg_replace( "/{$unique2}/", str_replace( '$', '\$', $mathlist[$i] ), $text, 1 );
297 }
298
299 for ( $i = 1; $i <= $nwsecs; ++$i ) {
300 $text = preg_replace( "/{$unique}/", str_replace( '$', '\$', $nwlist[$i] ), $text, 1 );
301 }
302 $this->addHTML( $text );
303 wfProfileOut();
304 }
305
306 function sendCacheControl() {
307 if( $this->mLastModified != "" ) {
308 header( "Cache-Control: private, must-revalidate, max-age=0" );
309 header( "Last-modified: {$this->mLastModified}" );
310 } else {
311 header( "Cache-Control: no-cache" ); # Experimental - see below
312 header( "Pragma: no-cache" );
313 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
314 }
315 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
316 }
317
318 # Finally, all the text has been munged and accumulated into
319 # the object, let's actually output it:
320 #
321 function output()
322 {
323 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
324 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
325 wfProfileIn( "OutputPage::output" );
326 $sk = $wgUser->getSkin();
327
328 wfProfileIn( "OutputPage::output-headers" );
329 $this->sendCacheControl();
330
331 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
332 header( "Content-language: {$wgLanguageCode}" );
333
334 if ( "" != $this->mRedirect ) {
335 header( "Location: {$this->mRedirect}" );
336 wfProfileOut();
337 return;
338 }
339
340 $exp = time() + $wgCookieExpiration;
341 foreach( $this->mCookies as $name => $val ) {
342 setcookie( $name, $val, $exp, "/" );
343 }
344 wfProfileOut();
345
346 wfProfileIn( "OutputPage::output-middle" );
347 $sk->initPage();
348 $this->out( $this->headElement() );
349
350 $this->out( "\n<body" );
351 $ops = $sk->getBodyOptions();
352 foreach ( $ops as $name => $val ) {
353 $this->out( " $name='$val'" );
354 }
355 $this->out( ">\n" );
356 if ( $wgDebugComments ) {
357 $this->out( "<!-- Wiki debugging output:\n" .
358 $this->mDebugtext . "-->\n" );
359 }
360 $this->out( $sk->beforeContent() );
361 wfProfileOut();
362
363 wfProfileIn( "OutputPage::output-bodytext" );
364 $this->out( $this->mBodytext );
365 wfProfileOut();
366 wfProfileIn( "OutputPage::output-after" );
367 $this->out( $sk->afterContent() );
368 wfProfileOut();
369
370 wfProfileOut(); # A hack - we can't report after here
371 $this->out( $this->reportTime() );
372
373 $this->out( "\n</body></html>" );
374 flush();
375 }
376
377 function out( $ins )
378 {
379 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
380 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
381 $outs = $ins;
382 } else {
383 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
384 if ( false === $outs ) { $outs = $ins; }
385 }
386 print $outs;
387 }
388
389 function setEncodings()
390 {
391 global $HTTP_SERVER_VARS, $wgInputEncoding, $wgOutputEncoding;
392 global $wgUser, $wgLang;
393
394 $wgInputEncoding = strtolower( $wgInputEncoding );
395 $s = $HTTP_SERVER_VARS['HTTP_ACCEPT_CHARSET'];
396
397 if( $wgUser->getOption( 'altencoding' ) ) {
398 $wgLang->setAltEncoding();
399 return;
400 }
401
402 if ( "" == $s ) {
403 $wgOutputEncoding = strtolower( $wgOutputEncoding );
404 return;
405 }
406 $a = explode( ",", $s );
407 $best = 0.0;
408 $bestset = "*";
409
410 foreach ( $a as $s ) {
411 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
412 $set = $m[1];
413 $q = (float)($m[2]);
414 } else {
415 $set = $s;
416 $q = 1.0;
417 }
418 if ( $q > $best ) {
419 $bestset = $set;
420 $best = $q;
421 }
422 }
423 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
424 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
425 $wgOutputEncoding = strtolower( $bestset );
426
427 # Disable for now
428 #
429 $wgOutputEncoding = $wgInputEncoding;
430 }
431
432 function reportTime()
433 {
434 global $wgRequestTime, $wgDebugLogFile, $HTTP_SERVER_VARS;
435 global $wgProfiling, $wgProfileStack, $wgUser;
436
437 list( $usec, $sec ) = explode( " ", microtime() );
438 $now = (float)$sec + (float)$usec;
439
440 list( $usec, $sec ) = explode( " ", $wgRequestTime );
441 $start = (float)$sec + (float)$usec;
442 $elapsed = $now - $start;
443
444 if ( "" != $wgDebugLogFile ) {
445 $prof = "";
446 if( $wgProfiling and count( $wgProfileStack ) ) {
447 $lasttime = $start;
448 foreach( $wgProfileStack as $ile ) {
449 # "foo::bar 99 0.12345 1 0.23456 2"
450 if( preg_match( '/^(\S+)\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/', $ile, $m ) ) {
451 $thisstart = (float)$m[3] + (float)$m[4] - $start;
452 $thisend = (float)$m[5] + (float)$m[6] - $start;
453 $thiselapsed = $thisend - $thisstart;
454 $thispercent = $thiselapsed / $elapsed * 100.0;
455
456 $prof .= sprintf( "\tat %04.3f in %04.3f (%2.1f%%) - %s %s\n",
457 $thisstart, $thiselapsed, $thispercent,
458 str_repeat( "*", $m[2] ), $m[1] );
459 $lasttime = $thistime;
460 #$prof .= "\t(^ $ile)\n";
461 } else {
462 $prof .= "\t?broken? $ile\n";
463 }
464 }
465 }
466
467 if( $forward = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'] )
468 $forward = " forwarded for $forward";
469 if( $client = $HTTP_SERVER_VARS['HTTP_CLIENT_IP'] )
470 $forward .= " client IP $client";
471 if( $from = $HTTP_SERVER_VARS['HTTP_FROM'] )
472 $forward .= " from $from";
473 if( $forward )
474 $forward = "\t(proxied via {$HTTP_SERVER_VARS['REMOTE_ADDR']}{$forward})";
475 if($wgUser->getId() == 0)
476 $forward .= " anon";
477 $log = sprintf( "%s\t%04.3f\t%s\n",
478 gmdate( "YmdHis" ), $elapsed,
479 urldecode( $HTTP_SERVER_VARS['REQUEST_URI'] . $forward ) );
480 error_log( $log . $prof, 3, $wgDebugLogFile );
481 }
482 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
483 $elapsed );
484 return $com;
485 }
486
487 # Note: these arguments are keys into wfMsg(), not text!
488 #
489 function errorpage( $title, $msg )
490 {
491 global $wgTitle;
492
493 $this->mDebugtext .= "Original title: " .
494 $wgTitle->getPrefixedText() . "\n";
495 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
496 $this->setPageTitle( wfMsg( $title ) );
497 $this->setRobotpolicy( "noindex,nofollow" );
498 $this->setArticleFlag( false );
499
500 $this->mBodytext = "";
501 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
502 $this->returnToMain( false );
503
504 $this->output();
505 exit;
506 }
507
508 function sysopRequired()
509 {
510 global $wgUser;
511
512 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
513 $this->setPageTitle( wfMsg( "sysoptitle" ) );
514 $this->setRobotpolicy( "noindex,nofollow" );
515 $this->setArticleFlag( false );
516 $this->mBodytext = "";
517
518 $sk = $wgUser->getSkin();
519 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
520 $text = str_replace( "$1", $ap, wfMsg( "sysoptext" ) );
521 $this->addHTML( $text );
522 $this->returnToMain();
523 }
524
525 function developerRequired()
526 {
527 global $wgUser;
528
529 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
530 $this->setPageTitle( wfMsg( "developertitle" ) );
531 $this->setRobotpolicy( "noindex,nofollow" );
532 $this->setArticleFlag( false );
533 $this->mBodytext = "";
534
535 $sk = $wgUser->getSkin();
536 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
537 $text = str_replace( "$1", $ap, wfMsg( "developertext" ) );
538 $this->addHTML( $text );
539 $this->returnToMain();
540 }
541
542 function databaseError( $fname )
543 {
544 global $wgUser, $wgCommandLineMode;
545
546 $this->setPageTitle( wfMsg( "databaseerror" ) );
547 $this->setRobotpolicy( "noindex,nofollow" );
548 $this->setArticleFlag( false );
549
550 if ( $wgCommandLineMode ) {
551 $msg = wfMsg( "dberrortextcl" );
552 } else {
553 $msg = wfMsg( "dberrortextcl" );
554 }
555 $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
556 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
557 $msg = str_replace( "$3", wfLastErrno(), $msg );
558 $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
559
560 if ( $wgCommandLineMode ) {
561 print $msg;
562 exit();
563 }
564 $sk = $wgUser->getSkin();
565 $shlink = $sk->makeKnownLink( wfMsg( "searchhelppage" ),
566 wfMsg( "searchingwikipedia" ) );
567 $msg = str_replace( "$5", $shlink, $msg );
568
569 $this->mBodytext = $msg;
570 $this->output();
571 exit();
572 }
573
574 function readOnlyPage()
575 {
576 global $wgUser, $wgReadOnlyFile;
577
578 $this->setPageTitle( wfMsg( "readonly" ) );
579 $this->setRobotpolicy( "noindex,nofollow" );
580 $this->setArticleFlag( false );
581
582 $reason = implode( "", file( $wgReadOnlyFile ) );
583 $text = str_replace( "$1", $reason, wfMsg( "readonlytext" ) );
584 $this->addHTML( $text );
585 $this->returnToMain( false );
586 }
587
588 function fatalError( $message )
589 {
590 $this->setPageTitle( wfMsg( "internalerror" ) );
591 $this->setRobotpolicy( "noindex,nofollow" );
592 $this->setArticleFlag( false );
593
594 $this->mBodytext = $message;
595 $this->output();
596 exit;
597 }
598
599 function unexpectedValueError( $name, $val )
600 {
601 $msg = str_replace( "$1", $name, wfMsg( "unexpected" ) );
602 $msg = str_replace( "$2", $val, $msg );
603 $this->fatalError( $msg );
604 }
605
606 function fileCopyError( $old, $new )
607 {
608 $msg = str_replace( "$1", $old, wfMsg( "filecopyerror" ) );
609 $msg = str_replace( "$2", $new, $msg );
610 $this->fatalError( $msg );
611 }
612
613 function fileRenameError( $old, $new )
614 {
615 $msg = str_replace( "$1", $old, wfMsg( "filerenameerror" ) );
616 $msg = str_replace( "$2", $new, $msg );
617 $this->fatalError( $msg );
618 }
619
620 function fileDeleteError( $name )
621 {
622 $msg = str_replace( "$1", $name, wfMsg( "filedeleteerror" ) );
623 $this->fatalError( $msg );
624 }
625
626 function fileNotFoundError( $name )
627 {
628 $msg = str_replace( "$1", $name, wfMsg( "filenotfound" ) );
629 $this->fatalError( $msg );
630 }
631
632 function returnToMain( $auto = true )
633 {
634 global $wgUser, $wgOut, $returnto;
635
636 $sk = $wgUser->getSkin();
637 if ( "" == $returnto ) {
638 $returnto = wfMsg( "mainpage" );
639 }
640 $link = $sk->makeKnownLink( $returnto, "" );
641
642 $r = str_replace( "$1", $link, wfMsg( "returnto" ) );
643 if ( $auto ) {
644 $wgOut->addMeta( "http:Refresh", "10;url=" .
645 wfLocalUrlE( wfUrlencode( $returnto ) ) );
646 }
647 $wgOut->addHTML( "\n<p>$r\n" );
648 }
649
650 # Well, OK, it's actually about 14 passes. But since all the
651 # hard lifting is done inside PHP's regex code, it probably
652 # wouldn't speed things up much to add a real parser.
653 #
654 function doWikiPass2( $text, $linestart )
655 {
656 global $wgUser, $wgLang, $wgMungeDates;
657 wfProfileIn( "OutputPage::doWikiPass2" );
658
659 $text = $this->removeHTMLtags( $text );
660 $text = $this->replaceVariables( $text );
661
662 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
663 $text = str_replace ( "<HR>", "<hr>", $text );
664
665 $text = $this->doQuotes( $text );
666 $text = $this->doHeadings( $text );
667 $text = $this->doBlockLevels( $text, $linestart );
668
669 if($wgMungeDates)
670 $text = $wgLang->replaceDates( $text );
671 $text = $this->replaceExternalLinks( $text );
672 $text = $this->replaceInternalLinks ( $text );
673
674 $text = $this->magicISBN( $text );
675 $text = $this->magicRFC( $text );
676 $text = $this->formatHeadings( $text );
677
678 $sk = $wgUser->getSkin();
679 $text = $sk->transformContent( $text );
680
681 wfProfileOut();
682 return $text;
683 }
684
685 /* private */ function doQuotes( $text )
686 {
687 $text = preg_replace( "/'''(.+)'''/mU", "<strong>\$1</strong>", $text );
688 $text = preg_replace( "/''(.+)''/mU", "<em>\$1</em>", $text );
689 return $text;
690 }
691
692 /* private */ function doHeadings( $text )
693 {
694 for ( $i = 6; $i >= 1; --$i ) {
695 $h = substr( "======", 0, $i );
696 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
697 "<h{$i}>\\1</h{$i}>\\2", $text );
698 }
699 return $text;
700 }
701
702 # Note: we have to do external links before the internal ones,
703 # and otherwise take great care in the order of things here, so
704 # that we don't end up interpreting some URLs twice.
705
706 /* private */ function replaceExternalLinks( $text )
707 {
708 wfProfileIn( "OutputPage::replaceExternalLinks" );
709 $text = $this->subReplaceExternalLinks( $text, "http", true );
710 $text = $this->subReplaceExternalLinks( $text, "https", true );
711 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
712 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
713 $text = $this->subReplaceExternalLinks( $text, "news", false );
714 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
715 wfProfileOut();
716 return $text;
717 }
718
719 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
720 {
721 global $wgUser, $printable;
722 global $wgAllowExternalImages;
723
724
725 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
726 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
727
728 # this is the list of separators that should be ignored if they
729 # are the last character of an URL but that should be included
730 # if they occur within the URL, e.g. "go to www.foo.com, where .."
731 # in this case, the last comma should not become part of the URL,
732 # but in "www.foo.com/123,2342,32.htm" it should.
733 $sep = ",;\.:";
734 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
735 $images = "gif|png|jpg|jpeg";
736
737 # PLEASE NOTE: The curly braces { } are not part of the regex,
738 # they are interpreted as part of the string (used to tell PHP
739 # that the content of the string should be inserted there).
740 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
741 "((?i){$images})([^{$uc}]|$)/";
742
743 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
744 $sk = $wgUser->getSkin();
745
746 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
747 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
748 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
749 }
750 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
751 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
752 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
753 "</a>\\5", $s );
754 $s = str_replace( $unique, $protocol, $s );
755
756 $a = explode( "[{$protocol}:", " " . $s );
757 $s = array_shift( $a );
758 $s = substr( $s, 1 );
759
760 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
761 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
762
763 foreach ( $a as $line ) {
764 if ( preg_match( $e1, $line, $m ) ) {
765 $link = "{$protocol}:{$m[1]}";
766 $trail = $m[2];
767 if ( $autonumber ) { $text = "[" . ++$this->mAutonumber . "]"; }
768 else { $text = wfEscapeHTML( $link ); }
769 } else if ( preg_match( $e2, $line, $m ) ) {
770 $link = "{$protocol}:{$m[1]}";
771 $text = $m[2];
772 $trail = $m[3];
773 } else {
774 $s .= "[{$protocol}:" . $line;
775 continue;
776 }
777 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
778 else $paren = "";
779 $la = $sk->getExternalLinkAttributes( $link, $text );
780 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
781
782 }
783 return $s;
784 }
785
786 /* private */ function replaceInternalLinks( $s )
787 {
788 global $wgTitle, $wgUser, $wgLang;
789 global $wgLinkCache, $wgInterwikiMagic;
790 global $wgNamespacesWithSubpages;
791 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
792
793 wfProfileIn( "$fname-setup" );
794 $tc = Title::legalChars() . "#";
795 $sk = $wgUser->getSkin();
796
797 $a = explode( "[[", " " . $s );
798 $s = array_shift( $a );
799 $s = substr( $s, 1 );
800
801 $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD";
802 $e2 = "/^([{$tc}]+)]](.*)\$/sD";
803 wfProfileOut();
804
805 wfProfileIn( "$fname-loop" );
806 foreach ( $a as $line ) {
807 if ( preg_match( $e1, $line, $m ) ) { # page with alternate text
808
809 $text = $m[2];
810 $trail = $m[3];
811
812 } else if ( preg_match( $e2, $line, $m ) ) { # page with normal text
813
814 $text = "";
815 $trail = $m[2];
816 }
817
818 else { # Invalid form; output directly
819 $s .= "[[" . $line ;
820 continue;
821 }
822 if(substr($m[1],0,1)=="/") { # subpage
823 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
824 $m[1]=substr($m[1],1,strlen($m[1])-2);
825 $noslash=$m[1];
826
827 } else {
828 $noslash=substr($m[1],1);
829 }
830 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
831 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
832 if(!$text) {
833 $text= $m[1];
834 } # this might be changed for ugliness reasons
835 } else {
836 $link = $noslash; # no subpage allowed, use standard link
837 }
838 } else { # no subpage
839 $link = $m[1];
840 }
841
842 if ( preg_match( "/^((?:i|x|[a-z]{2,3})(?:-[a-z0-9]+)?|[A-Za-z\\x80-\\xff]+):(.*)\$/", $link, $m ) ) {
843 $pre = strtolower( $m[1] );
844 $suf = $m[2];
845 if ( $wgLang->getNsIndex( $pre ) ==
846 Namespace::getImage() ) {
847 $nt = Title::newFromText( $suf );
848 $name = $nt->getDBkey();
849 if ( "" == $text ) { $text = $nt->GetText(); }
850
851 $wgLinkCache->addImageLink( $name );
852 $s .= $sk->makeImageLink( $name,
853 wfImageUrl( $name ), $text );
854 $s .= $trail;
855 } else if ( "media" == $pre ) {
856 $nt = Title::newFromText( $suf );
857 $name = $nt->getDBkey();
858 if ( "" == $text ) { $text = $nt->GetText(); }
859
860 $wgLinkCache->addImageLink( $name );
861 $s .= $sk->makeMediaLink( $name,
862 wfImageUrl( $name ), $text );
863 $s .= $trail;
864 } else {
865 $l = $wgLang->getLanguageName( $pre );
866 if ( "" == $l or !$wgInterwikiMagic or
867 Namespace::isTalk( $wgTitle->getNamespace() ) ) {
868 if ( "" == $text ) { $text = $link; }
869 $s .= $sk->makeLink( $link, $text, "", $trail );
870 } else {
871 array_push( $this->mLanguageLinks, "$pre:$suf" );
872 $s .= $trail;
873 }
874 }
875 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
876 # $link = substr( $link, 2 );
877 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
878 } else {
879 if ( "" == $text ) { $text = $link; }
880 $s .= $sk->makeLink( $link, $text, "", $trail );
881 }
882 }
883 wfProfileOut();
884 wfProfileOut();
885 return $s;
886 }
887
888 # Some functions here used by doBlockLevels()
889 #
890 /* private */ function closeParagraph()
891 {
892 $result = "";
893 if ( 0 != strcmp( "p", $this->mLastSection ) &&
894 0 != strcmp( "", $this->mLastSection ) ) {
895 $result = "</" . $this->mLastSection . ">";
896 }
897 $this->mLastSection = "";
898 return $result;
899 }
900 # getCommon() returns the length of the longest common substring
901 # of both arguments, starting at the beginning of both.
902 #
903 /* private */ function getCommon( $st1, $st2 )
904 {
905 $fl = strlen( $st1 );
906 $shorter = strlen( $st2 );
907 if ( $fl < $shorter ) { $shorter = $fl; }
908
909 for ( $i = 0; $i < $shorter; ++$i ) {
910 if ( $st1{$i} != $st2{$i} ) { break; }
911 }
912 return $i;
913 }
914 # These next three functions open, continue, and close the list
915 # element appropriate to the prefix character passed into them.
916 #
917 /* private */ function openList( $char )
918 {
919 $result = $this->closeParagraph();
920
921 if ( "*" == $char ) { $result .= "<ul><li>"; }
922 else if ( "#" == $char ) { $result .= "<ol><li>"; }
923 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
924 else if ( ";" == $char ) {
925 $result .= "<dl><dt>";
926 $this->mDTopen = true;
927 }
928 else { $result = "<!-- ERR 1 -->"; }
929
930 return $result;
931 }
932
933 /* private */ function nextItem( $char )
934 {
935 if ( "*" == $char || "#" == $char ) { return "</li><li>"; }
936 else if ( ":" == $char || ";" == $char ) {
937 $close = "</dd>";
938 if ( $this->mDTopen ) { $close = "</dt>"; }
939 if ( ";" == $char ) {
940 $this->mDTopen = true;
941 return $close . "<dt>";
942 } else {
943 $this->mDTopen = false;
944 return $close . "<dd>";
945 }
946 }
947 return "<!-- ERR 2 -->";
948 }
949
950 /* private */function closeList( $char )
951 {
952 if ( "*" == $char ) { return "</li></ul>"; }
953 else if ( "#" == $char ) { return "</li></ol>"; }
954 else if ( ":" == $char ) {
955 if ( $this->mDTopen ) {
956 $this->mDTopen = false;
957 return "</dt></dl>";
958 } else {
959 return "</dd></dl>";
960 }
961 }
962 return "<!-- ERR 3 -->";
963 }
964
965 /* private */ function doBlockLevels( $text, $linestart )
966 {
967 wfProfileIn( "OutputPage::doBlockLevels" );
968 # Parsing through the text line by line. The main thing
969 # happening here is handling of block-level elements p, pre,
970 # and making lists from lines starting with * # : etc.
971 #
972 $a = explode( "\n", $text );
973 $text = $lastPref = "";
974 $this->mDTopen = $inBlockElem = false;
975
976 if ( ! $linestart ) { $text .= array_shift( $a ); }
977 foreach ( $a as $t ) {
978 if ( "" != $text ) { $text .= "\n"; }
979
980 $oLine = $t;
981 $opl = strlen( $lastPref );
982 $npl = strspn( $t, "*#:;" );
983 $pref = substr( $t, 0, $npl );
984 $pref2 = str_replace( ";", ":", $pref );
985 $t = substr( $t, $npl );
986
987 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
988 $text .= $this->nextItem( substr( $pref, -1 ) );
989
990 if ( ";" == substr( $pref, -1 ) ) {
991 $cpos = strpos( $t, ":" );
992 if ( ! ( false === $cpos ) ) {
993 $term = substr( $t, 0, $cpos );
994 $text .= $term . $this->nextItem( ":" );
995 $t = substr( $t, $cpos + 1 );
996 }
997 }
998 } else if (0 != $npl || 0 != $opl) {
999 $cpl = $this->getCommon( $pref, $lastPref );
1000
1001 while ( $cpl < $opl ) {
1002 $text .= $this->closeList( $lastPref{$opl-1} );
1003 --$opl;
1004 }
1005 if ( $npl <= $cpl && $cpl > 0 ) {
1006 $text .= $this->nextItem( $pref{$cpl-1} );
1007 }
1008 while ( $npl > $cpl ) {
1009 $char = substr( $pref, $cpl, 1 );
1010 $text .= $this->openList( $char );
1011
1012 if ( ";" == $char ) {
1013 $cpos = strpos( $t, ":" );
1014 if ( ! ( false === $cpos ) ) {
1015 $term = substr( $t, 0, $cpos );
1016 $text .= $term . $this->nextItem( ":" );
1017 $t = substr( $t, $cpos + 1 );
1018 }
1019 }
1020 ++$cpl;
1021 }
1022 $lastPref = $pref2;
1023 }
1024 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1025 if ( preg_match(
1026 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1027 $text .= $this->closeParagraph();
1028 $inBlockElem = true;
1029 }
1030 if ( ! $inBlockElem ) {
1031 if ( " " == $t{0} ) {
1032 $newSection = "pre";
1033 # $t = wfEscapeHTML( $t );
1034 }
1035 else { $newSection = "p"; }
1036
1037 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1038 $text .= $this->closeParagraph();
1039 $text .= "<" . $newSection . ">";
1040 } else if ( 0 != strcmp( $this->mLastSection,
1041 $newSection ) ) {
1042 $text .= $this->closeParagraph();
1043 if ( 0 != strcmp( "p", $newSection ) ) {
1044 $text .= "<" . $newSection . ">";
1045 }
1046 }
1047 $this->mLastSection = $newSection;
1048 }
1049 if ( $inBlockElem &&
1050 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1051 $inBlockElem = false;
1052 }
1053 }
1054 $text .= $t;
1055 }
1056 while ( $npl ) {
1057 $text .= $this->closeList( $pref2{$npl-1} );
1058 --$npl;
1059 }
1060 if ( "" != $this->mLastSection ) {
1061 if ( "p" != $this->mLastSection ) {
1062 $text .= "</" . $this->mLastSection . ">";
1063 }
1064 $this->mLastSection = "";
1065 }
1066 wfProfileOut();
1067 return $text;
1068 }
1069
1070 /* private */ function replaceVariables( $text )
1071 {
1072 global $wgLang;
1073 wfProfileIn( "OutputPage:replaceVariables" );
1074
1075 /* As with sigs, use server's local time --
1076 ensure this is appropriate for your audience! */
1077 $v = date( "m" );
1078 $text = str_replace( "{{CURRENTMONTH}}", $v, $text );
1079 $v = $wgLang->getMonthName( date( "n" ) );
1080 $text = str_replace( "{{CURRENTMONTHNAME}}", $v, $text );
1081 $v = $wgLang->getMonthNameGen( date( "n" ) );
1082 $text = str_replace( "{{CURRENTMONTHNAMEGEN}}", $v, $text );
1083 $v = date( "j" );
1084 $text = str_replace( "{{CURRENTDAY}}", $v, $text );
1085 $v = $wgLang->getWeekdayName( date( "w" )+1 );
1086 $text = str_replace( "{{CURRENTDAYNAME}}", $v, $text );
1087 $v = date( "Y" );
1088 $text = str_replace( "{{CURRENTYEAR}}", $v, $text );
1089 $v = $wgLang->time( wfTimestampNow(), false );
1090 $text = str_replace( "{{CURRENTTIME}}", $v, $text );
1091
1092 if ( false !== strstr( $text, "{{NUMBEROFARTICLES}}" ) ) {
1093 $v = wfNumberOfArticles();
1094 $text = str_replace( "{{NUMBEROFARTICLES}}", $v, $text );
1095 }
1096 wfProfileOut();
1097 return $text;
1098 }
1099
1100 /* private */ function removeHTMLtags( $text )
1101 {
1102 wfProfileIn( "OutputPage::removeHTMLtags" );
1103 $htmlpairs = array( # Tags that must be closed
1104 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1105 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1106 "strike", "strong", "tt", "var", "div", "center",
1107 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1108 "ruby", "rt" , "rb" , "rp"
1109 );
1110 $htmlsingle = array(
1111 "br", "p", "hr", "li", "dt", "dd"
1112 );
1113 $htmlnest = array( # Tags that can be nested--??
1114 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1115 "dl", "font", "big", "small", "sub", "sup"
1116 );
1117 $tabletags = array( # Can only appear inside table
1118 "td", "th", "tr"
1119 );
1120
1121 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1122 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1123
1124 $htmlattrs = array( # Allowed attributes--no scripting, etc.
1125 "title", "align", "lang", "dir", "width", "height",
1126 "bgcolor", "clear", /* BR */ "noshade", /* HR */
1127 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
1128 /* FONT */ "type", "start", "value", "compact",
1129 /* For various lists, mostly deprecated but safe */
1130 "summary", "width", "border", "frame", "rules",
1131 "cellspacing", "cellpadding", "valign", "char",
1132 "charoff", "colgroup", "col", "span", "abbr", "axis",
1133 "headers", "scope", "rowspan", "colspan", /* Tables */
1134 "id", "class", "name", "style" /* For CSS */
1135 );
1136
1137 # Remove HTML comments
1138 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1139
1140 $bits = explode( "<", $text );
1141 $text = array_shift( $bits );
1142 $tagstack = array(); $tablestack = array();
1143
1144 foreach ( $bits as $x ) {
1145 $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
1146 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1147 $x, $regs );
1148 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1149 error_reporting( $prev );
1150
1151 $badtag = 0 ;
1152 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1153 # Check our stack
1154 if ( $slash ) {
1155 # Closing a tag...
1156 if ( ! in_array( $t, $htmlsingle ) &&
1157 ( $ot = array_pop( $tagstack ) ) != $t ) {
1158 array_push( $tagstack, $ot );
1159 $badtag = 1;
1160 } else {
1161 if ( $t == "table" ) {
1162 $tagstack = array_pop( $tablestack );
1163 }
1164 $newparams = "";
1165 }
1166 } else {
1167 # Keep track for later
1168 if ( in_array( $t, $tabletags ) &&
1169 ! in_array( "table", $tagstack ) ) {
1170 $badtag = 1;
1171 } else if ( in_array( $t, $tagstack ) &&
1172 ! in_array ( $t , $htmlnest ) ) {
1173 $badtag = 1 ;
1174 } else if ( ! in_array( $t, $htmlsingle ) ) {
1175 if ( $t == "table" ) {
1176 array_push( $tablestack, $tagstack );
1177 $tagstack = array();
1178 }
1179 array_push( $tagstack, $t );
1180 }
1181 # Strip non-approved attributes from the tag
1182 $newparams = preg_replace(
1183 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
1184 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
1185 $params);
1186 }
1187 if ( ! $badtag ) {
1188 $rest = str_replace( ">", "&gt;", $rest );
1189 $text .= "<$slash$t$newparams$brace$rest";
1190 continue;
1191 }
1192 }
1193 $text .= "&lt;" . str_replace( ">", "&gt;", $x);
1194 }
1195 # Close off any remaining tags
1196 while ( $t = array_pop( $tagstack ) ) {
1197 $text .= "</$t>\n";
1198 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1199 }
1200 wfProfileOut();
1201 return $text;
1202 }
1203
1204
1205 /*
1206 *
1207 * This function accomplishes several tasks:
1208 * 1) Auto-number headings if that option is enabled
1209 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1210 * 3) Add a Table of contents on the top for users who have enabled the option
1211 * 4) Auto-anchor headings
1212 *
1213 * It loops through all headlines, collects the necessary data, then splits up the
1214 * string and re-inserts the newly formatted headlines.
1215 *
1216 * */
1217 /* private */ function formatHeadings( $text )
1218 {
1219 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1220 $nh=$wgUser->getOption( "numberheadings" );
1221 $st=$wgUser->getOption( "showtoc" );
1222 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1223 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1224
1225 $sk=$wgUser->getSkin();
1226 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1227
1228 $c=0;
1229
1230 foreach($matches[3] as $headline) {
1231 if($level) { $prevlevel=$level;}
1232 $level=$matches[1][$c];
1233 if(($nh||$st) && $prevlevel && $level>$prevlevel) {
1234
1235 $h[$level]=0; // reset when we enter a new level
1236 $toc.=$sk->tocIndent($level-$prevlevel);
1237 $toclevel+=$level-$prevlevel;
1238
1239 }
1240 if(($nh||$st) && $level<$prevlevel) {
1241 $h[$level+1]=0; // reset when we step back a level
1242 $toc.=$sk->tocUnindent($prevlevel-$level);
1243 $toclevel-=$prevlevel-$level;
1244
1245 }
1246 $h[$level]++; // count number of headlines for each level
1247
1248 if($nh||$st) {
1249 for($i=1;$i<=$level;$i++) {
1250 if($h[$i]) {
1251 if($dot) {$numbering.=".";}
1252 $numbering.=$h[$i];
1253 $dot=1;
1254 }
1255 }
1256 }
1257
1258
1259 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1260 $tocline=$canonized_headline;
1261 $canonized_headline=str_replace('"',"",$canonized_headline);
1262 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1263 $refer[$c]=$canonized_headline;
1264 $refers[$canonized_headline]++; // count how many in assoc. array so we can track dupes in anchors
1265 $refcount[$c]=$refers[$canonized_headline];
1266 if($nh||$st) {
1267 $tocline=$numbering ." ". $tocline;
1268 if($nh) {
1269 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1270 }
1271 }
1272 $anchor=$canonized_headline;
1273 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1274 if($st) {
1275 $toc.=$sk->tocLine($anchor,$tocline,$toclevel);
1276 }
1277 if($es && !isset($wpPreview)) {
1278 $head[$c].=$sk->editSectionLink($c+1);
1279 }
1280 $head[$c].="<H".$level.$matches[2][$c]
1281 ."<a name=\"".$anchor."\">"
1282 .$headline
1283 ."</a>"
1284 ."</H".$level.">";
1285 $numbering="";
1286 $c++;
1287 $dot=0;
1288 }
1289
1290 if($st) {
1291 $toclines=$c;
1292 $toc.=$sk->tocUnindent($toclevel);
1293 $toc=$sk->tocTable($toc);
1294 }
1295
1296 // split up and insert constructed headlines
1297
1298 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1299 $i=0;
1300
1301
1302 foreach($blocks as $block) {
1303 if($es && !isset($wpPreview) && $c>0 && $i==0) {
1304 $full.=$sk->editSectionLink(0);
1305 }
1306 $full.=$block;
1307 if($st && $toclines>3 && !$i) {
1308 $full="<a name=\"top\"></a>".$full.$toc;
1309 }
1310
1311 $full.=$head[$i];
1312 $i++;
1313 }
1314 return $full;
1315 }
1316
1317 /* private */ function magicISBN( $text )
1318 {
1319 global $wgLang;
1320
1321 $a = split( "ISBN ", " $text" );
1322 if ( count ( $a ) < 2 ) return $text;
1323 $text = substr( array_shift( $a ), 1);
1324 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1325
1326 foreach ( $a as $x ) {
1327 $isbn = $blank = "" ;
1328 while ( " " == $x{0} ) {
1329 $blank .= " ";
1330 $x = substr( $x, 1 );
1331 }
1332 while ( strstr( $valid, $x{0} ) != false ) {
1333 $isbn .= $x{0};
1334 $x = substr( $x, 1 );
1335 }
1336 $num = str_replace( "-", "", $isbn );
1337 $num = str_replace( " ", "", $num );
1338
1339 if ( "" == $num ) {
1340 $text .= "ISBN $blank$x";
1341 } else {
1342 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1343 "Booksources"), "isbn={$num}" ) . "\" CLASS=\"internal\">ISBN $isbn</a>";
1344 $text .= $x;
1345 }
1346 }
1347 return $text;
1348 }
1349
1350 /* private */ function magicRFC( $text )
1351 {
1352 return $text;
1353 }
1354
1355 /* private */ function headElement()
1356 {
1357 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding;
1358
1359 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1360
1361 if ( "" == $this->mHTMLtitle ) {
1362 $this->mHTMLtitle = $this->mPagetitle;
1363 }
1364 $ret .= "<html lang=\"$wgLanguageCode\"><head><title>{$this->mHTMLtitle}</title>\n";
1365 array_push( $this->mMetatags, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1366 foreach ( $this->mMetatags as $tag ) {
1367 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1368 $a = "http-equiv";
1369 $tag[0] = substr( $tag[0], 5 );
1370 } else {
1371 $a = "name";
1372 }
1373 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1374 }
1375 $p = $this->mRobotpolicy;
1376 if ( "" == $p ) { $p = "index,follow"; }
1377 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1378
1379 if ( count( $this->mKeywords ) > 0 ) {
1380 $ret .= "<meta name=\"keywords\" content=\"" .
1381 implode( ",", $this->mKeywords ) . "\">\n";
1382 }
1383 foreach ( $this->mLinktags as $tag ) {
1384 $ret .= "<link ";
1385 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1386 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1387 $ret .= "href=\"{$tag[2]}\">\n";
1388 }
1389 $sk = $wgUser->getSkin();
1390 $ret .= $sk->getHeadScripts();
1391 $ret .= $sk->getUserStyles();
1392
1393 $ret .= "</head>\n";
1394 return $ret;
1395 }
1396 }
1397
1398 ?>