Alternate db connect, use proper charset in abort page
[mediawiki.git] / includes / OutputPage.php
blob34ea53b7241bd3f55abea3071ea368008abdc34c
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 $ismodsince = wfUnix2Timestamp( strtotime( $_SERVER["HTTP_IF_MODIFIED_SINCE"] ) );
172 wfDebug( "-- client send If-Modified-Since: " . $_SERVER["HTTP_IF_MODIFIED_SINCE"] . "\n", false );
173 wfDebug( "-- we might send Last-Modified : $lastmod\n", false );
175 if( ($ismodsince >= $timestamp ) and $wgUser->validateCache( $ismodsince ) ) {
176 # Make sure you're in a place you can leave when you call us!
177 header( "HTTP/1.0 304 Not Modified" );
178 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
179 header( "Cache-Control: private, must-revalidate, max-age=0" );
180 header( "Last-Modified: {$lastmod}" );
181 wfDebug( "CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
182 exit;
183 } else {
184 wfDebug( "READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
185 $this->mLastModified = $lastmod;
187 } else {
188 wfDebug( "We're confused.\n", false );
189 $this->mLastModified = $lastmod;
193 function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; }
194 function setHTMLtitle( $name ) { $this->mHTMLtitle = $name; }
195 function setPageTitle( $name ) { $this->mPagetitle = $name; }
196 function getPageTitle() { return $this->mPagetitle; }
197 function setSubtitle( $str ) { $this->mSubtitle = $str; }
198 function getSubtitle() { return $this->mSubtitle; }
199 function setArticleFlag( $v ) { $this->mIsarticle = $v; }
200 function isArticle() { return $this->mIsarticle; }
201 function setPrintable() { $this->mPrintable = true; }
202 function isPrintable() { return $this->mPrintable; }
204 function getLanguageLinks() {
205 global $wgUseNewInterlanguage, $wgTitle, $wgLanguageCode;
206 global $wgDBconnection, $wgDBname, $wgDBintlname;
208 if ( ! $wgUseNewInterlanguage )
209 return $this->mLanguageLinks;
211 mysql_select_db( $wgDBintlname, $wgDBconnection ) or die(
212 htmlspecialchars(mysql_error()) );
214 $list = array();
215 $sql = "SELECT * FROM ilinks WHERE lang_from=\"" .
216 "{$wgLanguageCode}\" AND title_from=\"" . $wgTitle->getDBkey() . "\"";
217 $res = mysql_query( $sql, $wgDBconnection );
219 while ( $q = mysql_fetch_object ( $res ) ) {
220 $list[] = $q->lang_to . ":" . $q->title_to;
222 mysql_free_result( $res );
223 mysql_select_db( $wgDBname, $wgDBconnection ) or die(
224 htmlspecialchars(mysql_error()) );
226 return $list;
229 function supressQuickbar() { $this->mSupressQuickbar = true; }
230 function isQuickbarSupressed() { return $this->mSupressQuickbar; }
232 function addHTML( $text ) { $this->mBodytext .= $text; }
233 function addHeadtext( $text ) { $this->mHeadtext .= $text; }
234 function debug( $text ) { $this->mDebugtext .= $text; }
236 # First pass--just handle <nowiki> sections, pass the rest off
237 # to doWikiPass2() which does all the real work.
240 function addWikiText( $text, $linestart = true )
242 global $wgUseTeX;
243 wfProfileIn( "OutputPage::addWikiText" );
244 $unique = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4";
245 $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO";
246 $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp";
247 $nwlist = array();
248 $nwsecs = 0;
249 $mathlist = array();
250 $mathsecs = 0;
251 $prelist = array ();
252 $presecs = 0;
253 $stripped = "";
254 $stripped2 = "";
255 $stripped3 = "";
257 while ( "" != $text ) {
258 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
259 $stripped .= $p[0];
260 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $text = ""; }
261 else {
262 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
263 ++$nwsecs;
264 $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]);
265 $stripped .= $unique;
266 $text = $q[1];
270 if( $wgUseTeX ) {
271 while ( "" != $stripped ) {
272 $p = preg_split( "/<\\s*math\\s*>/i", $stripped, 2 );
273 $stripped2 .= $p[0];
274 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped = ""; }
275 else {
276 $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 );
277 ++$mathsecs;
278 $mathlist[$mathsecs] = renderMath($q[0]);
279 $stripped2 .= $unique2;
280 $stripped = $q[1];
283 } else {
284 $stripped2 = $stripped;
287 while ( "" != $stripped2 ) {
288 $p = preg_split( "/<\\s*pre\\s*>/i", $stripped2, 2 );
289 $stripped3 .= $p[0];
290 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped2 = ""; }
291 else {
292 $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 );
293 ++$presecs;
294 $prelist[$presecs] = "<pre>". wfEscapeHTMLTagsOnly($q[0]). "</pre>";
295 $stripped3 .= $unique3;
296 $stripped2 = $q[1];
300 $text = $this->doWikiPass2( $stripped3, $linestart );
302 $specialChars = array("\\", "$");
303 $escapedChars = array("\\\\", "\\$");
304 for ( $i = 1; $i <= $presecs; ++$i ) {
305 $text = preg_replace( "/{$unique3}/", str_replace( $specialChars,
306 $escapedChars, $prelist[$i] ), $text, 1 );
309 for ( $i = 1; $i <= $mathsecs; ++$i ) {
310 $text = preg_replace( "/{$unique2}/", str_replace( $specialChars,
311 $escapedChars, $mathlist[$i] ), $text, 1 );
314 for ( $i = 1; $i <= $nwsecs; ++$i ) {
315 $text = preg_replace( "/{$unique}/", str_replace( $specialChars,
316 $escapedChars, $nwlist[$i] ), $text, 1 );
318 $this->addHTML( $text );
319 wfProfileOut();
322 function sendCacheControl() {
323 if( $this->mLastModified != "" ) {
324 wfDebug( "** private caching; {$this->mLastModified} **\n", false );
325 header( "Cache-Control: private, must-revalidate, max-age=0" );
326 header( "Vary: Accept-Encoding" );
327 header( "Last-modified: {$this->mLastModified}" );
328 } else {
329 wfDebug( "** no caching **\n", false );
330 header( "Cache-Control: no-cache" ); # Experimental - see below
331 header( "Pragma: no-cache" );
332 header( "Vary: Accept-Encoding" );
333 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
335 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
338 # Finally, all the text has been munged and accumulated into
339 # the object, let's actually output it:
341 function output()
343 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
344 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
345 wfProfileIn( "OutputPage::output" );
346 $sk = $wgUser->getSkin();
348 wfProfileIn( "OutputPage::output-headers" );
349 $this->sendCacheControl();
351 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
352 header( "Content-language: {$wgLanguageCode}" );
354 if ( "" != $this->mRedirect ) {
355 header( "Location: {$this->mRedirect}" );
356 wfProfileOut();
357 return;
360 $exp = time() + $wgCookieExpiration;
361 foreach( $this->mCookies as $name => $val ) {
362 setcookie( $name, $val, $exp, "/" );
364 wfProfileOut();
366 wfProfileIn( "OutputPage::output-middle" );
367 $sk->initPage();
368 $this->out( $this->headElement() );
370 $this->out( "\n<body" );
371 $ops = $sk->getBodyOptions();
372 foreach ( $ops as $name => $val ) {
373 $this->out( " $name='$val'" );
375 $this->out( ">\n" );
376 if ( $wgDebugComments ) {
377 $this->out( "<!-- Wiki debugging output:\n" .
378 $this->mDebugtext . "-->\n" );
380 $this->out( $sk->beforeContent() );
381 wfProfileOut();
383 wfProfileIn( "OutputPage::output-bodytext" );
384 $this->out( $this->mBodytext );
385 wfProfileOut();
386 wfProfileIn( "OutputPage::output-after" );
387 $this->out( $sk->afterContent() );
388 wfProfileOut();
390 wfProfileOut(); # A hack - we can't report after here
391 $this->out( $this->reportTime() );
393 $this->out( "\n</body></html>" );
394 flush();
397 function out( $ins )
399 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
400 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
401 $outs = $ins;
402 } else {
403 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
404 if ( false === $outs ) { $outs = $ins; }
406 print $outs;
409 function setEncodings()
411 global $HTTP_SERVER_VARS, $wgInputEncoding, $wgOutputEncoding;
412 global $wgUser, $wgLang;
414 $wgInputEncoding = strtolower( $wgInputEncoding );
415 $s = $HTTP_SERVER_VARS['HTTP_ACCEPT_CHARSET'];
417 if( $wgUser->getOption( 'altencoding' ) ) {
418 $wgLang->setAltEncoding();
419 return;
422 if ( "" == $s ) {
423 $wgOutputEncoding = strtolower( $wgOutputEncoding );
424 return;
426 $a = explode( ",", $s );
427 $best = 0.0;
428 $bestset = "*";
430 foreach ( $a as $s ) {
431 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
432 $set = $m[1];
433 $q = (float)($m[2]);
434 } else {
435 $set = $s;
436 $q = 1.0;
438 if ( $q > $best ) {
439 $bestset = $set;
440 $best = $q;
443 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
444 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
445 $wgOutputEncoding = strtolower( $bestset );
447 # Disable for now
449 $wgOutputEncoding = $wgInputEncoding;
452 function reportTime()
454 global $wgRequestTime, $wgDebugLogFile, $HTTP_SERVER_VARS;
455 global $wgProfiling, $wgProfileStack, $wgUser;
457 list( $usec, $sec ) = explode( " ", microtime() );
458 $now = (float)$sec + (float)$usec;
460 list( $usec, $sec ) = explode( " ", $wgRequestTime );
461 $start = (float)$sec + (float)$usec;
462 $elapsed = $now - $start;
464 if ( "" != $wgDebugLogFile ) {
465 $prof = "";
466 if( $wgProfiling and count( $wgProfileStack ) ) {
467 $lasttime = $start;
468 foreach( $wgProfileStack as $ile ) {
469 # "foo::bar 99 0.12345 1 0.23456 2"
470 if( preg_match( '/^(\S+)\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/', $ile, $m ) ) {
471 $thisstart = (float)$m[3] + (float)$m[4] - $start;
472 $thisend = (float)$m[5] + (float)$m[6] - $start;
473 $thiselapsed = $thisend - $thisstart;
474 $thispercent = $thiselapsed / $elapsed * 100.0;
476 $prof .= sprintf( "\tat %04.3f in %04.3f (%2.1f%%) - %s %s\n",
477 $thisstart, $thiselapsed, $thispercent,
478 str_repeat( "*", $m[2] ), $m[1] );
479 $lasttime = $thistime;
480 #$prof .= "\t(^ $ile)\n";
481 } else {
482 $prof .= "\t?broken? $ile\n";
487 if( $forward = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'] )
488 $forward = " forwarded for $forward";
489 if( $client = $HTTP_SERVER_VARS['HTTP_CLIENT_IP'] )
490 $forward .= " client IP $client";
491 if( $from = $HTTP_SERVER_VARS['HTTP_FROM'] )
492 $forward .= " from $from";
493 if( $forward )
494 $forward = "\t(proxied via {$HTTP_SERVER_VARS['REMOTE_ADDR']}{$forward})";
495 if($wgUser->getId() == 0)
496 $forward .= " anon";
497 $log = sprintf( "%s\t%04.3f\t%s\n",
498 gmdate( "YmdHis" ), $elapsed,
499 urldecode( $HTTP_SERVER_VARS['REQUEST_URI'] . $forward ) );
500 error_log( $log . $prof, 3, $wgDebugLogFile );
502 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
503 $elapsed );
504 return $com;
507 # Note: these arguments are keys into wfMsg(), not text!
509 function errorpage( $title, $msg )
511 global $wgTitle;
513 $this->mDebugtext .= "Original title: " .
514 $wgTitle->getPrefixedText() . "\n";
515 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
516 $this->setPageTitle( wfMsg( $title ) );
517 $this->setRobotpolicy( "noindex,nofollow" );
518 $this->setArticleFlag( false );
520 $this->mBodytext = "";
521 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
522 $this->returnToMain( false );
524 $this->output();
525 exit;
528 function sysopRequired()
530 global $wgUser;
532 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
533 $this->setPageTitle( wfMsg( "sysoptitle" ) );
534 $this->setRobotpolicy( "noindex,nofollow" );
535 $this->setArticleFlag( false );
536 $this->mBodytext = "";
538 $sk = $wgUser->getSkin();
539 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
540 $text = str_replace( "$1", $ap, wfMsg( "sysoptext" ) );
541 $this->addHTML( $text );
542 $this->returnToMain();
545 function developerRequired()
547 global $wgUser;
549 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
550 $this->setPageTitle( wfMsg( "developertitle" ) );
551 $this->setRobotpolicy( "noindex,nofollow" );
552 $this->setArticleFlag( false );
553 $this->mBodytext = "";
555 $sk = $wgUser->getSkin();
556 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
557 $text = str_replace( "$1", $ap, wfMsg( "developertext" ) );
558 $this->addHTML( $text );
559 $this->returnToMain();
562 function databaseError( $fname )
564 global $wgUser, $wgCommandLineMode;
566 $this->setPageTitle( wfMsg( "databaseerror" ) );
567 $this->setRobotpolicy( "noindex,nofollow" );
568 $this->setArticleFlag( false );
570 if ( $wgCommandLineMode ) {
571 $msg = wfMsg( "dberrortextcl" );
572 } else {
573 $msg = wfMsg( "dberrortextcl" );
575 $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
576 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
577 $msg = str_replace( "$3", wfLastErrno(), $msg );
578 $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
580 if ( $wgCommandLineMode ) {
581 print $msg;
582 exit();
584 $sk = $wgUser->getSkin();
585 $shlink = $sk->makeKnownLink( wfMsg( "searchhelppage" ),
586 wfMsg( "searchingwikipedia" ) );
587 $msg = str_replace( "$5", $shlink, $msg );
589 $this->mBodytext = $msg;
590 $this->output();
591 exit();
594 function readOnlyPage()
596 global $wgUser, $wgReadOnlyFile;
598 $this->setPageTitle( wfMsg( "readonly" ) );
599 $this->setRobotpolicy( "noindex,nofollow" );
600 $this->setArticleFlag( false );
602 $reason = implode( "", file( $wgReadOnlyFile ) );
603 $text = str_replace( "$1", $reason, wfMsg( "readonlytext" ) );
604 $this->addHTML( $text );
605 $this->returnToMain( false );
608 function fatalError( $message )
610 $this->setPageTitle( wfMsg( "internalerror" ) );
611 $this->setRobotpolicy( "noindex,nofollow" );
612 $this->setArticleFlag( false );
614 $this->mBodytext = $message;
615 $this->output();
616 exit;
619 function unexpectedValueError( $name, $val )
621 $msg = str_replace( "$1", $name, wfMsg( "unexpected" ) );
622 $msg = str_replace( "$2", $val, $msg );
623 $this->fatalError( $msg );
626 function fileCopyError( $old, $new )
628 $msg = str_replace( "$1", $old, wfMsg( "filecopyerror" ) );
629 $msg = str_replace( "$2", $new, $msg );
630 $this->fatalError( $msg );
633 function fileRenameError( $old, $new )
635 $msg = str_replace( "$1", $old, wfMsg( "filerenameerror" ) );
636 $msg = str_replace( "$2", $new, $msg );
637 $this->fatalError( $msg );
640 function fileDeleteError( $name )
642 $msg = str_replace( "$1", $name, wfMsg( "filedeleteerror" ) );
643 $this->fatalError( $msg );
646 function fileNotFoundError( $name )
648 $msg = str_replace( "$1", $name, wfMsg( "filenotfound" ) );
649 $this->fatalError( $msg );
652 function returnToMain( $auto = true )
654 global $wgUser, $wgOut, $returnto;
656 $sk = $wgUser->getSkin();
657 if ( "" == $returnto ) {
658 $returnto = wfMsg( "mainpage" );
660 $link = $sk->makeKnownLink( $returnto, "" );
662 $r = str_replace( "$1", $link, wfMsg( "returnto" ) );
663 if ( $auto ) {
664 $wgOut->addMeta( "http:Refresh", "10;url=" .
665 wfLocalUrlE( wfUrlencode( $returnto ) ) );
667 $wgOut->addHTML( "\n<p>$r\n" );
671 function categoryMagic ()
673 global $wgTitle , $wgUseCategoryMagic ;
674 if ( !isset ( $wgUseCategoryMagic ) || !$wgUseCategoryMagic ) return ;
675 $id = $wgTitle->getArticleID() ;
676 $cat = ucfirst ( wfMsg ( "category" ) ) ;
677 $ti = $wgTitle->getText() ;
678 $ti = explode ( ":" , $ti , 2 ) ;
679 if ( $cat != $ti[0] ) return "" ;
680 $r = "<br break=all>\n" ;
682 $articles = array() ;
683 $parents = array () ;
684 $children = array() ;
687 global $wgUser ;
688 $sk = $wgUser->getSkin() ;
689 $sql = "SELECT l_from FROM links WHERE l_to={$id}" ;
690 $res = wfQuery ( $sql ) ;
691 while ( $x = wfFetchObject ( $res ) )
693 # $t = new Title ;
694 # $t->newFromDBkey ( $x->l_from ) ;
695 # $t = $t->getText() ;
696 $t = $x->l_from ;
697 $y = explode ( ":" , $t , 2 ) ;
698 if ( count ( $y ) == 2 && $y[0] == $cat )
700 array_push ( $children , $sk->makeLink ( $t , $y[1] ) ) ;
702 else array_push ( $articles , $sk->makeLink ( $t ) ) ;
704 wfFreeResult ( $res ) ;
706 # Children
707 if ( count ( $children ) > 0 )
709 asort ( $children ) ;
710 $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
711 $r .= implode ( ", " , $children ) ;
714 # Articles
715 if ( count ( $articles ) > 0 )
717 asort ( $articles ) ;
718 $h = str_replace ( "$1" , $ti[1] , wfMsg("category_header") ) ;
719 $r .= "<h2>{$h}</h2>\n" ;
720 $r .= implode ( ", " , $articles ) ;
724 return $r ;
728 # Well, OK, it's actually about 14 passes. But since all the
729 # hard lifting is done inside PHP's regex code, it probably
730 # wouldn't speed things up much to add a real parser.
732 function doWikiPass2( $text, $linestart )
734 global $wgUser, $wgLang, $wgUseDynamicDates;
735 wfProfileIn( "OutputPage::doWikiPass2" );
737 $text = $this->removeHTMLtags( $text );
738 $text = $this->replaceVariables( $text );
740 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
741 $text = str_replace ( "<HR>", "<hr>", $text );
743 $text = $this->doAllQuotes( $text );
744 $text = $this->doHeadings( $text );
745 $text = $this->doBlockLevels( $text, $linestart );
747 if($wgUseDynamicDates) {
748 $text = $wgLang->replaceDates( $text );
751 $text = $this->replaceExternalLinks( $text );
752 $text = $this->replaceInternalLinks ( $text );
754 $text = $this->magicISBN( $text );
755 $text = $this->magicRFC( $text );
756 $text = $this->formatHeadings( $text );
758 $sk = $wgUser->getSkin();
759 $text = $sk->transformContent( $text );
760 $text .= $this->categoryMagic () ;
762 wfProfileOut();
763 return $text;
766 /* private */ function doAllQuotes( $text )
768 $outtext = "";
769 $lines = explode( "\r\n", $text );
770 foreach ( $lines as $line ) {
771 $outtext .= $this->doQuotes ( "", $line, "" ) . "\r\n";
773 return $outtext;
776 /* private */ function doQuotes( $pre, $text, $mode )
778 if ( preg_match( "/^(.*)''(.*)$/sU", $text, $m ) ) {
779 $m1_strong = ($m[1] == "") ? "" : "<strong>{$m[1]}</strong>";
780 $m1_em = ($m[1] == "") ? "" : "<em>{$m[1]}</em>";
781 if ( substr ($m[2], 0, 1) == "'" ) {
782 $m[2] = substr ($m[2], 1);
783 if ($mode == "em") {
784 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ? "both" : "emstrong" );
785 } else if ($mode == "strong") {
786 return $m1_strong . $this->doQuotes ( "", $m[2], "" );
787 } else if (($mode == "emstrong") || ($mode == "both")) {
788 return $this->doQuotes ( "", $pre.$m1_strong.$m[2], "em" );
789 } else if ($mode == "strongem") {
790 return "<strong>{$pre}{$m1_em}</strong>" . $this->doQuotes ( "", $m[2], "em" );
791 } else {
792 return $m[1] . $this->doQuotes ( "", $m[2], "strong" );
794 } else {
795 if ($mode == "strong") {
796 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ? "both" : "strongem" );
797 } else if ($mode == "em") {
798 return $m1_em . $this->doQuotes ( "", $m[2], "" );
799 } else if ($mode == "emstrong") {
800 return "<em>{$pre}{$m1_strong}</em>" . $this->doQuotes ( "", $m[2], "strong" );
801 } else if (($mode == "strongem") || ($mode == "both")) {
802 return $this->doQuotes ( "", $pre.$m1_em.$m[2], "strong" );
803 } else {
804 return $m[1] . $this->doQuotes ( "", $m[2], "em" );
807 } else {
808 $text_strong = ($text == "") ? "" : "<strong>{$text}</strong>";
809 $text_em = ($text == "") ? "" : "<em>{$text}</em>";
810 if ($mode == "") {
811 return $pre . $text;
812 } else if ($mode == "em") {
813 return $pre . $text_em;
814 } else if ($mode == "strong") {
815 return $pre . $text_strong;
816 } else if ($mode == "strongem") {
817 return (($pre == "") && ($text == "")) ? "" : "<strong>{$pre}{$text_em}</strong>";
818 } else {
819 return (($pre == "") && ($text == "")) ? "" : "<em>{$pre}{$text_strong}</em>";
824 /* private */ function doHeadings( $text )
826 for ( $i = 6; $i >= 1; --$i ) {
827 $h = substr( "======", 0, $i );
828 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
829 "<h{$i}>\\1</h{$i}>\\2", $text );
831 return $text;
834 # Note: we have to do external links before the internal ones,
835 # and otherwise take great care in the order of things here, so
836 # that we don't end up interpreting some URLs twice.
838 /* private */ function replaceExternalLinks( $text )
840 wfProfileIn( "OutputPage::replaceExternalLinks" );
841 $text = $this->subReplaceExternalLinks( $text, "http", true );
842 $text = $this->subReplaceExternalLinks( $text, "https", true );
843 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
844 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
845 $text = $this->subReplaceExternalLinks( $text, "news", false );
846 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
847 wfProfileOut();
848 return $text;
851 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
853 global $wgUser, $printable;
854 global $wgAllowExternalImages;
857 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
858 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
860 # this is the list of separators that should be ignored if they
861 # are the last character of an URL but that should be included
862 # if they occur within the URL, e.g. "go to www.foo.com, where .."
863 # in this case, the last comma should not become part of the URL,
864 # but in "www.foo.com/123,2342,32.htm" it should.
865 $sep = ",;\.:";
866 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
867 $images = "gif|png|jpg|jpeg";
869 # PLEASE NOTE: The curly braces { } are not part of the regex,
870 # they are interpreted as part of the string (used to tell PHP
871 # that the content of the string should be inserted there).
872 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
873 "((?i){$images})([^{$uc}]|$)/";
875 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
876 $sk = $wgUser->getSkin();
878 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
879 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
880 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
882 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
883 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
884 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
885 "</a>\\5", $s );
886 $s = str_replace( $unique, $protocol, $s );
888 $a = explode( "[{$protocol}:", " " . $s );
889 $s = array_shift( $a );
890 $s = substr( $s, 1 );
892 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
893 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
895 foreach ( $a as $line ) {
896 if ( preg_match( $e1, $line, $m ) ) {
897 $link = "{$protocol}:{$m[1]}";
898 $trail = $m[2];
899 if ( $autonumber ) { $text = "[" . ++$this->mAutonumber . "]"; }
900 else { $text = wfEscapeHTML( $link ); }
901 } else if ( preg_match( $e2, $line, $m ) ) {
902 $link = "{$protocol}:{$m[1]}";
903 $text = $m[2];
904 $trail = $m[3];
905 } else {
906 $s .= "[{$protocol}:" . $line;
907 continue;
909 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
910 else $paren = "";
911 $la = $sk->getExternalLinkAttributes( $link, $text );
912 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
915 return $s;
918 /* private */ function replaceInternalLinks( $s )
920 global $wgTitle, $wgUser, $wgLang;
921 global $wgLinkCache, $wgInterwikiMagic, $wgUseCategoryMagic;
922 global $wgNamespacesWithSubpages, $wgLanguageCode;
923 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
925 wfProfileIn( "$fname-setup" );
926 $tc = Title::legalChars() . "#";
927 $sk = $wgUser->getSkin();
929 $a = explode( "[[", " " . $s );
930 $s = array_shift( $a );
931 $s = substr( $s, 1 );
933 $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD";
934 $e2 = "/^([{$tc}]+)]](.*)\$/sD";
935 wfProfileOut();
937 wfProfileIn( "$fname-loop" );
938 foreach ( $a as $line ) {
939 if ( preg_match( $e1, $line, $m ) ) { # page with alternate text
941 $text = $m[2];
942 $trail = $m[3];
944 } else if ( preg_match( $e2, $line, $m ) ) { # page with normal text
946 $text = "";
947 $trail = $m[2];
950 else { # Invalid form; output directly
951 $s .= "[[" . $line ;
952 continue;
954 if(substr($m[1],0,1)=="/") { # subpage
955 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
956 $m[1]=substr($m[1],1,strlen($m[1])-2);
957 $noslash=$m[1];
959 } else {
960 $noslash=substr($m[1],1);
962 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
963 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
964 if(!$text) {
965 $text= $m[1];
966 } # this might be changed for ugliness reasons
967 } else {
968 $link = $noslash; # no subpage allowed, use standard link
970 } else { # no subpage
971 $link = $m[1];
974 if ( preg_match( "/^((?:i|x|[a-z]{2,3})(?:-[a-z0-9]+)?|[A-Za-z\\x80-\\xff]+):(.*)\$/", $link, $m ) ) {
975 $pre = strtolower( $m[1] );
976 $suf = $m[2];
977 if ( $wgLang->getNsIndex( $pre ) ==
978 Namespace::getImage() ) {
979 $nt = Title::newFromText( $suf );
980 $name = $nt->getDBkey();
981 if ( "" == $text ) { $text = $nt->GetText(); }
983 $wgLinkCache->addImageLink( $name );
984 $s .= $sk->makeImageLink( $name,
985 wfImageUrl( $name ), $text );
986 $s .= $trail;
987 } else if ( "media" == $pre ) {
988 $nt = Title::newFromText( $suf );
989 $name = $nt->getDBkey();
990 if ( "" == $text ) { $text = $nt->GetText(); }
992 $wgLinkCache->addImageLink( $name );
993 $s .= $sk->makeMediaLink( $name,
994 wfImageUrl( $name ), $text );
995 $s .= $trail;
996 } else if ( isset($wgUseCategoryMagic) && $wgUseCategoryMagic && $pre == wfMsg ( "category" ) ) {
997 $l = $sk->makeLink ( $pre.":".ucfirst($m[2]) , ucfirst ( $m[2] ) ) ;
998 array_push ( $this->mCategoryLinks , $l ) ;
999 $s .= $trail ;
1000 } else {
1001 $l = $wgLang->getLanguageName( $pre );
1002 if ( "" == $l or !$wgInterwikiMagic or
1003 Namespace::isTalk( $wgTitle->getNamespace() ) ) {
1004 if ( "" == $text ) { $text = $link; }
1005 $s .= $sk->makeLink( $link, $text, "", $trail );
1006 } else if ( $pre != $wgLanguageCode ) {
1007 array_push( $this->mLanguageLinks, "$pre:$suf" );
1008 $s .= $trail;
1011 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
1012 # $link = substr( $link, 2 );
1013 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
1014 } else {
1015 if ( "" == $text ) { $text = $link; }
1016 $s .= $sk->makeLink( $link, $text, "", $trail );
1019 wfProfileOut();
1020 wfProfileOut();
1021 return $s;
1024 # Some functions here used by doBlockLevels()
1026 /* private */ function closeParagraph()
1028 $result = "";
1029 if ( 0 != strcmp( "p", $this->mLastSection ) &&
1030 0 != strcmp( "", $this->mLastSection ) ) {
1031 $result = "</" . $this->mLastSection . ">";
1033 $this->mLastSection = "";
1034 return $result;
1036 # getCommon() returns the length of the longest common substring
1037 # of both arguments, starting at the beginning of both.
1039 /* private */ function getCommon( $st1, $st2 )
1041 $fl = strlen( $st1 );
1042 $shorter = strlen( $st2 );
1043 if ( $fl < $shorter ) { $shorter = $fl; }
1045 for ( $i = 0; $i < $shorter; ++$i ) {
1046 if ( $st1{$i} != $st2{$i} ) { break; }
1048 return $i;
1050 # These next three functions open, continue, and close the list
1051 # element appropriate to the prefix character passed into them.
1053 /* private */ function openList( $char )
1055 $result = $this->closeParagraph();
1057 if ( "*" == $char ) { $result .= "<ul><li>"; }
1058 else if ( "#" == $char ) { $result .= "<ol><li>"; }
1059 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
1060 else if ( ";" == $char ) {
1061 $result .= "<dl><dt>";
1062 $this->mDTopen = true;
1064 else { $result = "<!-- ERR 1 -->"; }
1066 return $result;
1069 /* private */ function nextItem( $char )
1071 if ( "*" == $char || "#" == $char ) { return "</li><li>"; }
1072 else if ( ":" == $char || ";" == $char ) {
1073 $close = "</dd>";
1074 if ( $this->mDTopen ) { $close = "</dt>"; }
1075 if ( ";" == $char ) {
1076 $this->mDTopen = true;
1077 return $close . "<dt>";
1078 } else {
1079 $this->mDTopen = false;
1080 return $close . "<dd>";
1083 return "<!-- ERR 2 -->";
1086 /* private */function closeList( $char )
1088 if ( "*" == $char ) { return "</li></ul>"; }
1089 else if ( "#" == $char ) { return "</li></ol>"; }
1090 else if ( ":" == $char ) {
1091 if ( $this->mDTopen ) {
1092 $this->mDTopen = false;
1093 return "</dt></dl>";
1094 } else {
1095 return "</dd></dl>";
1098 return "<!-- ERR 3 -->";
1101 /* private */ function doBlockLevels( $text, $linestart )
1103 wfProfileIn( "OutputPage::doBlockLevels" );
1104 # Parsing through the text line by line. The main thing
1105 # happening here is handling of block-level elements p, pre,
1106 # and making lists from lines starting with * # : etc.
1108 $a = explode( "\n", $text );
1109 $text = $lastPref = "";
1110 $this->mDTopen = $inBlockElem = false;
1112 if ( ! $linestart ) { $text .= array_shift( $a ); }
1113 foreach ( $a as $t ) {
1114 if ( "" != $text ) { $text .= "\n"; }
1116 $oLine = $t;
1117 $opl = strlen( $lastPref );
1118 $npl = strspn( $t, "*#:;" );
1119 $pref = substr( $t, 0, $npl );
1120 $pref2 = str_replace( ";", ":", $pref );
1121 $t = substr( $t, $npl );
1123 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1124 $text .= $this->nextItem( substr( $pref, -1 ) );
1126 if ( ";" == substr( $pref, -1 ) ) {
1127 $cpos = strpos( $t, ":" );
1128 if ( ! ( false === $cpos ) ) {
1129 $term = substr( $t, 0, $cpos );
1130 $text .= $term . $this->nextItem( ":" );
1131 $t = substr( $t, $cpos + 1 );
1134 } else if (0 != $npl || 0 != $opl) {
1135 $cpl = $this->getCommon( $pref, $lastPref );
1137 while ( $cpl < $opl ) {
1138 $text .= $this->closeList( $lastPref{$opl-1} );
1139 --$opl;
1141 if ( $npl <= $cpl && $cpl > 0 ) {
1142 $text .= $this->nextItem( $pref{$cpl-1} );
1144 while ( $npl > $cpl ) {
1145 $char = substr( $pref, $cpl, 1 );
1146 $text .= $this->openList( $char );
1148 if ( ";" == $char ) {
1149 $cpos = strpos( $t, ":" );
1150 if ( ! ( false === $cpos ) ) {
1151 $term = substr( $t, 0, $cpos );
1152 $text .= $term . $this->nextItem( ":" );
1153 $t = substr( $t, $cpos + 1 );
1156 ++$cpl;
1158 $lastPref = $pref2;
1160 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1161 if ( preg_match(
1162 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1163 $text .= $this->closeParagraph();
1164 $inBlockElem = true;
1166 if ( ! $inBlockElem ) {
1167 if ( " " == $t{0} ) {
1168 $newSection = "pre";
1169 # $t = wfEscapeHTML( $t );
1171 else { $newSection = "p"; }
1173 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1174 $text .= $this->closeParagraph();
1175 $text .= "<" . $newSection . ">";
1176 } else if ( 0 != strcmp( $this->mLastSection,
1177 $newSection ) ) {
1178 $text .= $this->closeParagraph();
1179 if ( 0 != strcmp( "p", $newSection ) ) {
1180 $text .= "<" . $newSection . ">";
1183 $this->mLastSection = $newSection;
1185 if ( $inBlockElem &&
1186 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1187 $inBlockElem = false;
1190 $text .= $t;
1192 while ( $npl ) {
1193 $text .= $this->closeList( $pref2{$npl-1} );
1194 --$npl;
1196 if ( "" != $this->mLastSection ) {
1197 if ( "p" != $this->mLastSection ) {
1198 $text .= "</" . $this->mLastSection . ">";
1200 $this->mLastSection = "";
1202 wfProfileOut();
1203 return $text;
1206 /* private */ function replaceVariables( $text )
1208 global $wgLang;
1209 wfProfileIn( "OutputPage:replaceVariables" );
1211 /* As with sigs, use server's local time --
1212 ensure this is appropriate for your audience! */
1213 $v = date( "m" );
1214 $text = str_replace( "{{CURRENTMONTH}}", $v, $text );
1215 $v = $wgLang->getMonthName( date( "n" ) );
1216 $text = str_replace( "{{CURRENTMONTHNAME}}", $v, $text );
1217 $v = $wgLang->getMonthNameGen( date( "n" ) );
1218 $text = str_replace( "{{CURRENTMONTHNAMEGEN}}", $v, $text );
1219 $v = date( "j" );
1220 $text = str_replace( "{{CURRENTDAY}}", $v, $text );
1221 $v = $wgLang->getWeekdayName( date( "w" )+1 );
1222 $text = str_replace( "{{CURRENTDAYNAME}}", $v, $text );
1223 $v = date( "Y" );
1224 $text = str_replace( "{{CURRENTYEAR}}", $v, $text );
1225 $v = $wgLang->time( wfTimestampNow(), false );
1226 $text = str_replace( "{{CURRENTTIME}}", $v, $text );
1228 if ( false !== strstr( $text, "{{NUMBEROFARTICLES}}" ) ) {
1229 $v = wfNumberOfArticles();
1230 $text = str_replace( "{{NUMBEROFARTICLES}}", $v, $text );
1232 wfProfileOut();
1233 return $text;
1236 /* private */ function removeHTMLtags( $text )
1238 wfProfileIn( "OutputPage::removeHTMLtags" );
1239 $htmlpairs = array( # Tags that must be closed
1240 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1241 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1242 "strike", "strong", "tt", "var", "div", "center",
1243 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1244 "ruby", "rt" , "rb" , "rp"
1246 $htmlsingle = array(
1247 "br", "p", "hr", "li", "dt", "dd"
1249 $htmlnest = array( # Tags that can be nested--??
1250 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1251 "dl", "font", "big", "small", "sub", "sup"
1253 $tabletags = array( # Can only appear inside table
1254 "td", "th", "tr"
1257 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1258 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1260 $htmlattrs = array( # Allowed attributes--no scripting, etc.
1261 "title", "align", "lang", "dir", "width", "height",
1262 "bgcolor", "clear", /* BR */ "noshade", /* HR */
1263 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
1264 /* FONT */ "type", "start", "value", "compact",
1265 /* For various lists, mostly deprecated but safe */
1266 "summary", "width", "border", "frame", "rules",
1267 "cellspacing", "cellpadding", "valign", "char",
1268 "charoff", "colgroup", "col", "span", "abbr", "axis",
1269 "headers", "scope", "rowspan", "colspan", /* Tables */
1270 "id", "class", "name", "style" /* For CSS */
1273 # Remove HTML comments
1274 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1276 $bits = explode( "<", $text );
1277 $text = array_shift( $bits );
1278 $tagstack = array(); $tablestack = array();
1280 foreach ( $bits as $x ) {
1281 $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
1282 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1283 $x, $regs );
1284 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1285 error_reporting( $prev );
1287 $badtag = 0 ;
1288 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1289 # Check our stack
1290 if ( $slash ) {
1291 # Closing a tag...
1292 if ( ! in_array( $t, $htmlsingle ) &&
1293 ( $ot = array_pop( $tagstack ) ) != $t ) {
1294 array_push( $tagstack, $ot );
1295 $badtag = 1;
1296 } else {
1297 if ( $t == "table" ) {
1298 $tagstack = array_pop( $tablestack );
1300 $newparams = "";
1302 } else {
1303 # Keep track for later
1304 if ( in_array( $t, $tabletags ) &&
1305 ! in_array( "table", $tagstack ) ) {
1306 $badtag = 1;
1307 } else if ( in_array( $t, $tagstack ) &&
1308 ! in_array ( $t , $htmlnest ) ) {
1309 $badtag = 1 ;
1310 } else if ( ! in_array( $t, $htmlsingle ) ) {
1311 if ( $t == "table" ) {
1312 array_push( $tablestack, $tagstack );
1313 $tagstack = array();
1315 array_push( $tagstack, $t );
1317 # Strip non-approved attributes from the tag
1318 $newparams = preg_replace(
1319 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
1320 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
1321 $params);
1323 if ( ! $badtag ) {
1324 $rest = str_replace( ">", "&gt;", $rest );
1325 $text .= "<$slash$t$newparams$brace$rest";
1326 continue;
1329 $text .= "&lt;" . str_replace( ">", "&gt;", $x);
1331 # Close off any remaining tags
1332 while ( $t = array_pop( $tagstack ) ) {
1333 $text .= "</$t>\n";
1334 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1336 wfProfileOut();
1337 return $text;
1343 * This function accomplishes several tasks:
1344 * 1) Auto-number headings if that option is enabled
1345 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1346 * 3) Add a Table of contents on the top for users who have enabled the option
1347 * 4) Auto-anchor headings
1349 * It loops through all headlines, collects the necessary data, then splits up the
1350 * string and re-inserts the newly formatted headlines.
1352 * */
1353 /* private */ function formatHeadings( $text )
1355 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1356 $nh=$wgUser->getOption( "numberheadings" );
1357 $st=$wgUser->getOption( "showtoc" );
1358 if(!$wgTitle->userCanEdit()) {
1359 $es=0;
1360 $esr=0;
1361 } else {
1362 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1363 $esr=$wgUser->getID() && $wgUser->getOption( "editsectiononrightclick" );
1365 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, do not
1366 # add TOC
1367 if(preg_match("/__NOTOC__/i",$text)) {
1368 $text=preg_replace("/__NOTOC__/i","",$text);
1369 $st=0;
1372 # never add the TOC to the Main Page. This is an entry page that should not
1373 # be more than 1-2 screens large anyway
1374 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1376 # We need this to perform operations on the HTML
1377 $sk=$wgUser->getSkin();
1379 # Get all headlines for numbering them and adding funky stuff like [edit]
1380 # links
1381 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1383 # headline counter
1384 $c=0;
1386 # Ugh .. the TOC should have neat indentation levels which can be
1387 # passed to the skin functions. These are determined here
1388 foreach($matches[3] as $headline) {
1389 if($level) { $prevlevel=$level;}
1390 $level=$matches[1][$c];
1391 if(($nh||$st) && $prevlevel && $level>$prevlevel) {
1393 $h[$level]=0; // reset when we enter a new level
1394 $toc.=$sk->tocIndent($level-$prevlevel);
1395 $toclevel+=$level-$prevlevel;
1398 if(($nh||$st) && $level<$prevlevel) {
1399 $h[$level+1]=0; // reset when we step back a level
1400 $toc.=$sk->tocUnindent($prevlevel-$level);
1401 $toclevel-=$prevlevel-$level;
1404 $h[$level]++; // count number of headlines for each level
1406 if($nh||$st) {
1407 for($i=1;$i<=$level;$i++) {
1408 if($h[$i]) {
1409 if($dot) {$numbering.=".";}
1410 $numbering.=$h[$i];
1411 $dot=1;
1417 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1418 $tocline=$canonized_headline;
1419 $canonized_headline=str_replace('"',"",$canonized_headline);
1420 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1421 $refer[$c]=$canonized_headline;
1422 $refers[$canonized_headline]++; // count how many in assoc. array so we can track dupes in anchors
1423 $refcount[$c]=$refers[$canonized_headline];
1424 if($nh||$st) {
1425 $tocline=$numbering ." ". $tocline;
1426 if($nh) {
1427 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1430 $anchor=$canonized_headline;
1431 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1432 if($st) {
1433 $toc.=$sk->tocLine($anchor,$tocline,$toclevel);
1435 if($es && !isset($wpPreview)) {
1436 $head[$c].=$sk->editSectionLink($c+1);
1438 $head[$c].="<H".$level.$matches[2][$c]
1439 ."<a name=\"".$anchor."\">"
1440 .$headline
1441 ."</a>"
1442 ."</H".$level.">";
1443 if($esr && !isset($wpPreview)) {
1444 $head[$c]=$sk->editSectionScript($c+1,$head[$c]);
1446 $numbering="";
1447 $c++;
1448 $dot=0;
1451 if($st) {
1452 $toclines=$c;
1453 $toc.=$sk->tocUnindent($toclevel);
1454 $toc=$sk->tocTable($toc);
1457 // split up and insert constructed headlines
1459 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1460 $i=0;
1463 foreach($blocks as $block) {
1464 if(($es) && !isset($wpPreview) && $c>0 && $i==0) {
1465 # This is the [edit] link that appears for the top block of text when
1466 # section editing is enabled
1467 $full.=$sk->editSectionLink(0);
1469 $full.=$block;
1470 if($st && $toclines>3 && !$i) {
1471 # Let's add a top anchor just in case we want to link to the top of the page
1472 $full="<a name=\"top\"></a>".$full.$toc;
1475 $full.=$head[$i];
1476 $i++;
1478 return $full;
1481 /* private */ function magicISBN( $text )
1483 global $wgLang;
1485 $a = split( "ISBN ", " $text" );
1486 if ( count ( $a ) < 2 ) return $text;
1487 $text = substr( array_shift( $a ), 1);
1488 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1490 foreach ( $a as $x ) {
1491 $isbn = $blank = "" ;
1492 while ( " " == $x{0} ) {
1493 $blank .= " ";
1494 $x = substr( $x, 1 );
1496 while ( strstr( $valid, $x{0} ) != false ) {
1497 $isbn .= $x{0};
1498 $x = substr( $x, 1 );
1500 $num = str_replace( "-", "", $isbn );
1501 $num = str_replace( " ", "", $num );
1503 if ( "" == $num ) {
1504 $text .= "ISBN $blank$x";
1505 } else {
1506 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1507 "Booksources"), "isbn={$num}" ) . "\" CLASS=\"internal\">ISBN $isbn</a>";
1508 $text .= $x;
1511 return $text;
1514 /* private */ function magicRFC( $text )
1516 return $text;
1519 /* private */ function headElement()
1521 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding, $wgLang;
1523 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1525 if ( "" == $this->mHTMLtitle ) {
1526 $this->mHTMLtitle = $this->mPagetitle;
1528 $rtl = $wgLang->isRTL() ? " dir='RTL'" : "";
1529 $ret .= "<html lang=\"$wgLanguageCode\"$rtl><head><title>{$this->mHTMLtitle}</title>\n";
1530 array_push( $this->mMetatags, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1531 foreach ( $this->mMetatags as $tag ) {
1532 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1533 $a = "http-equiv";
1534 $tag[0] = substr( $tag[0], 5 );
1535 } else {
1536 $a = "name";
1538 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1540 $p = $this->mRobotpolicy;
1541 if ( "" == $p ) { $p = "index,follow"; }
1542 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1544 if ( count( $this->mKeywords ) > 0 ) {
1545 $ret .= "<meta name=\"keywords\" content=\"" .
1546 implode( ",", $this->mKeywords ) . "\">\n";
1548 foreach ( $this->mLinktags as $tag ) {
1549 $ret .= "<link ";
1550 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1551 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1552 $ret .= "href=\"{$tag[2]}\">\n";
1554 $sk = $wgUser->getSkin();
1555 $ret .= $sk->getHeadScripts();
1556 $ret .= $sk->getUserStyles();
1558 $ret .= "</head>\n";
1559 return $ret;