4 function linkToMathImage ( $tex, $outputhash )
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");
20 return ('$ '.wfEscapeHTML($tex).' $');
23 $md5_sql = mysql_escape_string(pack("H32", $md5));
25 $sql = "SELECT math_outputhash FROM math WHERE math_inputhash = '".$md5_sql."'";
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);
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")) {
41 $conservativeness = 2;
42 else if ($retval == "M")
43 $conservativeness = 1;
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);
58 $conservativeness = 2;
59 else if ($retval == "m")
60 $conservativeness = 1;
62 $conservativeness = 0;
63 $sql_html = "'".mysql_escape_string($outhtml)."'";
66 } else if ($retval == "X") {
68 $mathml = substr ($contents, 33);
70 $sql_mathml = "'".mysql_escape_string($mathml)."'";
71 $conservativeness = 0;
72 } else if ($retval == "+") {
77 $conservativeness = 0;
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" );
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);
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 );
113 return $rpage->math_html
;
118 var $mHeaders, $mCookies, $mMetatags, $mKeywords;
119 var $mLinktags, $mPagetitle, $mBodytext, $mDebugtext;
120 var $mHTMLtitle, $mRobotpolicy, $mIsarticle, $mPrintable;
121 var $mSubtitle, $mRedirect, $mAutonumber, $mHeadtext;
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->mAutonumber
= 0;
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; }
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 ) ); }
149 function checkLastModified ( $timestamp )
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 );
158 if( $wgUser->getOption( "nocache" ) ) return;
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 );
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 );
176 #wfDebug( "READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
177 $this->mLastModified
= $lastmod;
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
; }
193 function getLanguageLinks() {
194 global $wgUseNewInterlanguage, $wgTitle, $wgLanguageCode;
195 global $wgDBconnection, $wgDBname, $wgDBintlname;
197 if ( ! $wgUseNewInterlanguage )
198 return $this->mLanguageLinks
;
200 mysql_select_db( $wgDBintlname, $wgDBconnection ) or die(
201 htmlspecialchars(mysql_error()) );
204 $sql = "SELECT * FROM ilinks WHERE lang_from=\"" .
205 "{$wgLanguageCode}\" AND title_from=\"" . $wgTitle->getDBkey() . "\"";
206 $res = mysql_query( $sql, $wgDBconnection );
208 while ( $q = mysql_fetch_object ( $res ) ) {
209 $list[] = $q->lang_to
. ":" . $q->title_to
;
211 mysql_free_result( $res );
212 mysql_select_db( $wgDBname, $wgDBconnection ) or die(
213 htmlspecialchars(mysql_error()) );
218 function supressQuickbar() { $this->mSupressQuickbar
= true; }
219 function isQuickbarSupressed() { return $this->mSupressQuickbar
; }
221 function addHTML( $text ) { $this->mBodytext
.= $text; }
222 function addHeadtext( $text ) { $this->mHeadtext
.= $text; }
223 function debug( $text ) { $this->mDebugtext
.= $text; }
225 # First pass--just handle <nowiki> sections, pass the rest off
226 # to doWikiPass2() which does all the real work.
229 function addWikiText( $text, $linestart = true )
232 wfProfileIn( "OutputPage::addWikiText" );
233 $unique = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4";
234 $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO";
235 $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp";
246 while ( "" != $text ) {
247 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
249 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $text = ""; }
251 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
253 $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]);
254 $stripped .= $unique;
260 while ( "" != $stripped ) {
261 $p = preg_split( "/<\\s*math\\s*>/i", $stripped, 2 );
263 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $stripped = ""; }
265 $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 );
267 $mathlist[$mathsecs] = renderMath($q[0]);
268 $stripped2 .= $unique2;
273 $stripped2 = $stripped;
276 while ( "" != $stripped2 ) {
277 $p = preg_split( "/<\\s*pre\\s*>/i", $stripped2, 2 );
279 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $stripped2 = ""; }
281 $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 );
283 $prelist[$presecs] = "<pre>". wfEscapeHTMLTagsOnly($q[0]). "</pre>";
284 $stripped3 .= $unique3;
289 $text = $this->doWikiPass2( $stripped3, $linestart );
291 for ( $i = 1; $i <= $presecs; ++
$i ) {
292 $text = preg_replace( "/{$unique3}/", str_replace( '$', '\$', $prelist[$i] ), $text, 1 );
295 for ( $i = 1; $i <= $mathsecs; ++
$i ) {
296 $text = preg_replace( "/{$unique2}/", str_replace( '$', '\$', $mathlist[$i] ), $text, 1 );
299 for ( $i = 1; $i <= $nwsecs; ++
$i ) {
300 $text = preg_replace( "/{$unique}/", str_replace( '$', '\$', $nwlist[$i] ), $text, 1 );
302 $this->addHTML( $text );
306 # Finally, all the text has been munged and accumulated into
307 # the object, let's actually output it:
311 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
312 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
313 wfProfileIn( "OutputPage::output" );
314 $sk = $wgUser->getSkin();
316 wfProfileIn( "OutputPage::output-headers" );
317 if( $this->mLastModified
!= "" ) {
318 header( "Cache-Control: private, must-revalidate, max-age=0" );
319 header( "Last-modified: {$this->mLastModified}" );
321 header( "Cache-Control: no-cache" ); # Experimental - see below
322 header( "Pragma: no-cache" );
323 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
325 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
327 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
328 header( "Content-language: {$wgLanguageCode}" );
330 if ( "" != $this->mRedirect
) {
331 header( "Location: {$this->mRedirect}" );
336 $exp = time() +
$wgCookieExpiration;
337 foreach( $this->mCookies
as $name => $val ) {
338 setcookie( $name, $val, $exp, "/" );
342 wfProfileIn( "OutputPage::output-middle" );
344 $this->out( $this->headElement() );
346 $this->out( "\n<body" );
347 $ops = $sk->getBodyOptions();
348 foreach ( $ops as $name => $val ) {
349 $this->out( " $name='$val'" );
352 if ( $wgDebugComments ) {
353 $this->out( "<!-- Wiki debugging output:\n" .
354 $this->mDebugtext
. "-->\n" );
356 $this->out( $sk->beforeContent() );
359 wfProfileIn( "OutputPage::output-bodytext" );
360 $this->out( $this->mBodytext
);
362 wfProfileIn( "OutputPage::output-after" );
363 $this->out( $sk->afterContent() );
366 wfProfileOut(); # A hack - we can't report after here
367 $this->out( $this->reportTime() );
369 $this->out( "\n</body></html>" );
375 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
376 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
379 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
380 if ( false === $outs ) { $outs = $ins; }
385 function setEncodings()
387 global $HTTP_SERVER_VARS, $wgInputEncoding, $wgOutputEncoding;
388 global $wgUser, $wgLang;
390 $wgInputEncoding = strtolower( $wgInputEncoding );
391 $s = $HTTP_SERVER_VARS['HTTP_ACCEPT_CHARSET'];
393 if( $wgUser->getOption( 'altencoding' ) ) {
394 $wgLang->setAltEncoding();
399 $wgOutputEncoding = strtolower( $wgOutputEncoding );
402 $a = explode( ",", $s );
406 foreach ( $a as $s ) {
407 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
419 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
420 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
421 $wgOutputEncoding = strtolower( $bestset );
425 $wgOutputEncoding = $wgInputEncoding;
428 function reportTime()
430 global $wgRequestTime, $wgDebugLogFile, $HTTP_SERVER_VARS;
431 global $wgProfiling, $wgProfileStack, $wgUser;
433 list( $usec, $sec ) = explode( " ", microtime() );
434 $now = (float)$sec +
(float)$usec;
436 list( $usec, $sec ) = explode( " ", $wgRequestTime );
437 $start = (float)$sec +
(float)$usec;
438 $elapsed = $now - $start;
440 if ( "" != $wgDebugLogFile ) {
442 if( $wgProfiling and count( $wgProfileStack ) ) {
444 foreach( $wgProfileStack as $ile ) {
445 # "foo::bar 99 0.12345 1 0.23456 2"
446 if( preg_match( '/^(\S+)\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/', $ile, $m ) ) {
447 $thisstart = (float)$m[3] +
(float)$m[4] - $start;
448 $thisend = (float)$m[5] +
(float)$m[6] - $start;
449 $thiselapsed = $thisend - $thisstart;
450 $thispercent = $thiselapsed / $elapsed * 100.0;
452 $prof .= sprintf( "\tat %04.3f in %04.3f (%2.1f%%) - %s %s\n",
453 $thisstart, $thiselapsed, $thispercent,
454 str_repeat( "*", $m[2] ), $m[1] );
455 $lasttime = $thistime;
456 #$prof .= "\t(^ $ile)\n";
458 $prof .= "\t?broken? $ile\n";
463 if( $forward = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'] )
464 $forward = " forwarded for $forward";
465 if( $client = $HTTP_SERVER_VARS['HTTP_CLIENT_IP'] )
466 $forward .= " client IP $client";
467 if( $from = $HTTP_SERVER_VARS['HTTP_FROM'] )
468 $forward .= " from $from";
470 $forward = "\t(proxied via {$HTTP_SERVER_VARS['REMOTE_ADDR']}{$forward})";
471 if($wgUser->getId() == 0)
473 $log = sprintf( "%s\t%04.3f\t%s\n",
474 gmdate( "YmdHis" ), $elapsed,
475 urldecode( $HTTP_SERVER_VARS['REQUEST_URI'] . $forward ) );
476 error_log( $log . $prof, 3, $wgDebugLogFile );
478 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
483 # Note: these arguments are keys into wfMsg(), not text!
485 function errorpage( $title, $msg )
489 $this->mDebugtext
.= "Original title: " .
490 $wgTitle->getPrefixedText() . "\n";
491 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
492 $this->setPageTitle( wfMsg( $title ) );
493 $this->setRobotpolicy( "noindex,nofollow" );
494 $this->setArticleFlag( false );
496 $this->mBodytext
= "";
497 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
498 $this->returnToMain( false );
504 function sysopRequired()
508 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
509 $this->setPageTitle( wfMsg( "sysoptitle" ) );
510 $this->setRobotpolicy( "noindex,nofollow" );
511 $this->setArticleFlag( false );
512 $this->mBodytext
= "";
514 $sk = $wgUser->getSkin();
515 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
516 $text = str_replace( "$1", $ap, wfMsg( "sysoptext" ) );
517 $this->addHTML( $text );
518 $this->returnToMain();
521 function developerRequired()
525 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
526 $this->setPageTitle( wfMsg( "developertitle" ) );
527 $this->setRobotpolicy( "noindex,nofollow" );
528 $this->setArticleFlag( false );
529 $this->mBodytext
= "";
531 $sk = $wgUser->getSkin();
532 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
533 $text = str_replace( "$1", $ap, wfMsg( "developertext" ) );
534 $this->addHTML( $text );
535 $this->returnToMain();
538 function databaseError( $fname )
540 global $wgUser, $wgCommandLineMode;
542 $this->setPageTitle( wfMsg( "databaseerror" ) );
543 $this->setRobotpolicy( "noindex,nofollow" );
544 $this->setArticleFlag( false );
546 if ( $wgCommandLineMode ) {
547 $msg = wfMsg( "dberrortextcl" );
549 $msg = wfMsg( "dberrortextcl" );
551 $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
552 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
553 $msg = str_replace( "$3", wfLastErrno(), $msg );
554 $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
556 if ( $wgCommandLineMode ) {
560 $sk = $wgUser->getSkin();
561 $shlink = $sk->makeKnownLink( wfMsg( "searchhelppage" ),
562 wfMsg( "searchingwikipedia" ) );
563 $msg = str_replace( "$5", $shlink, $msg );
565 $this->mBodytext
= $msg;
570 function readOnlyPage()
572 global $wgUser, $wgReadOnlyFile;
574 $this->setPageTitle( wfMsg( "readonly" ) );
575 $this->setRobotpolicy( "noindex,nofollow" );
576 $this->setArticleFlag( false );
578 $reason = implode( "", file( $wgReadOnlyFile ) );
579 $text = str_replace( "$1", $reason, wfMsg( "readonlytext" ) );
580 $this->addHTML( $text );
581 $this->returnToMain( false );
584 function fatalError( $message )
586 $this->setPageTitle( wfMsg( "internalerror" ) );
587 $this->setRobotpolicy( "noindex,nofollow" );
588 $this->setArticleFlag( false );
590 $this->mBodytext
= $message;
595 function unexpectedValueError( $name, $val )
597 $msg = str_replace( "$1", $name, wfMsg( "unexpected" ) );
598 $msg = str_replace( "$2", $val, $msg );
599 $this->fatalError( $msg );
602 function fileCopyError( $old, $new )
604 $msg = str_replace( "$1", $old, wfMsg( "filecopyerror" ) );
605 $msg = str_replace( "$2", $new, $msg );
606 $this->fatalError( $msg );
609 function fileRenameError( $old, $new )
611 $msg = str_replace( "$1", $old, wfMsg( "filerenameerror" ) );
612 $msg = str_replace( "$2", $new, $msg );
613 $this->fatalError( $msg );
616 function fileDeleteError( $name )
618 $msg = str_replace( "$1", $name, wfMsg( "filedeleteerror" ) );
619 $this->fatalError( $msg );
622 function fileNotFoundError( $name )
624 $msg = str_replace( "$1", $name, wfMsg( "filenotfound" ) );
625 $this->fatalError( $msg );
628 function returnToMain( $auto = true )
630 global $wgUser, $wgOut, $returnto;
632 $sk = $wgUser->getSkin();
633 if ( "" == $returnto ) {
634 $returnto = wfMsg( "mainpage" );
636 $link = $sk->makeKnownLink( $returnto, "" );
638 $r = str_replace( "$1", $link, wfMsg( "returnto" ) );
640 $wgOut->addMeta( "http:Refresh", "10;url=" .
641 wfLocalUrlE( wfUrlencode( $returnto ) ) );
643 $wgOut->addHTML( "\n<p>$r\n" );
646 # Well, OK, it's actually about 14 passes. But since all the
647 # hard lifting is done inside PHP's regex code, it probably
648 # wouldn't speed things up much to add a real parser.
650 function doWikiPass2( $text, $linestart )
652 global $wgUser, $wgLang;
653 wfProfileIn( "OutputPage::doWikiPass2" );
655 $text = $this->removeHTMLtags( $text );
656 $text = $this->replaceVariables( $text );
658 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
659 $text = str_replace ( "<HR>", "<hr>", $text );
661 $text = $this->doQuotes( $text );
662 $text = $this->doHeadings( $text );
663 $text = $this->doBlockLevels( $text, $linestart );
665 $text = $wgLang->replaceDates( $text );
666 $text = $this->replaceExternalLinks( $text );
667 $text = $this->replaceInternalLinks ( $text );
669 $text = $this->magicISBN( $text );
670 $text = $this->magicRFC( $text );
671 $text = $this->formatHeadings( $text );
673 $sk = $wgUser->getSkin();
674 $text = $sk->transformContent( $text );
680 /* private */ function doQuotes( $text )
682 $text = preg_replace( "/'''(.+)'''/mU", "<strong>\$1</strong>", $text );
683 $text = preg_replace( "/''(.+)''/mU", "<em>\$1</em>", $text );
687 /* private */ function doHeadings( $text )
689 for ( $i = 6; $i >= 1; --$i ) {
690 $h = substr( "======", 0, $i );
691 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
692 "<h{$i}>\\1</h{$i}>\\2", $text );
697 # Note: we have to do external links before the internal ones,
698 # and otherwise take great care in the order of things here, so
699 # that we don't end up interpreting some URLs twice.
701 /* private */ function replaceExternalLinks( $text )
703 wfProfileIn( "OutputPage::replaceExternalLinks" );
704 $text = $this->subReplaceExternalLinks( $text, "http", true );
705 $text = $this->subReplaceExternalLinks( $text, "https", true );
706 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
707 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
708 $text = $this->subReplaceExternalLinks( $text, "news", false );
709 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
714 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
716 global $wgUser, $printable;
717 global $wgAllowExternalImages;
720 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
721 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
723 # this is the list of separators that should be ignored if they
724 # are the last character of an URL but that should be included
725 # if they occur within the URL, e.g. "go to www.foo.com, where .."
726 # in this case, the last comma should not become part of the URL,
727 # but in "www.foo.com/123,2342,32.htm" it should.
729 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
730 $images = "gif|png|jpg|jpeg";
732 # PLEASE NOTE: The curly braces { } are not part of the regex,
733 # they are interpreted as part of the string (used to tell PHP
734 # that the content of the string should be inserted there).
735 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
736 "((?i){$images})([^{$uc}]|$)/";
738 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
739 $sk = $wgUser->getSkin();
741 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
742 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
743 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
745 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
746 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
747 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
749 $s = str_replace( $unique, $protocol, $s );
751 $a = explode( "[{$protocol}:", " " . $s );
752 $s = array_shift( $a );
753 $s = substr( $s, 1 );
755 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
756 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
758 foreach ( $a as $line ) {
759 if ( preg_match( $e1, $line, $m ) ) {
760 $link = "{$protocol}:{$m[1]}";
762 if ( $autonumber ) { $text = "[" . ++
$this->mAutonumber
. "]"; }
763 else { $text = wfEscapeHTML( $link ); }
764 } else if ( preg_match( $e2, $line, $m ) ) {
765 $link = "{$protocol}:{$m[1]}";
769 $s .= "[{$protocol}:" . $line;
772 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
774 $la = $sk->getExternalLinkAttributes( $link, $text );
775 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
781 /* private */ function replaceInternalLinks( $s )
783 global $wgTitle, $wgUser, $wgLang;
784 global $wgLinkCache, $wgInterwikiMagic;
785 global $wgNamespacesWithSubpages;
786 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
788 wfProfileIn( "$fname-setup" );
789 $tc = Title
::legalChars() . "#";
790 $sk = $wgUser->getSkin();
792 $a = explode( "[[", " " . $s );
793 $s = array_shift( $a );
794 $s = substr( $s, 1 );
796 $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD";
797 $e2 = "/^([{$tc}]+)]](.*)\$/sD";
800 wfProfileIn( "$fname-loop" );
801 foreach ( $a as $line ) {
802 if ( preg_match( $e1, $line, $m ) ) { # page with alternate text
807 } else if ( preg_match( $e2, $line, $m ) ) { # page with normal text
813 else { # Invalid form; output directly
817 if(substr($m[1],0,1)=="/") { # subpage
818 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
819 $m[1]=substr($m[1],1,strlen($m[1])-2);
823 $noslash=substr($m[1],1);
825 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
826 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
829 } # this might be changed for ugliness reasons
831 $link = $noslash; # no subpage allowed, use standard link
833 } else { # no subpage
837 if ( preg_match( "/^((?:i|x|[a-z]{2,3})(?:-[a-z0-9]+)?|[A-Za-z\\x80-\\xff]+):(.*)\$/", $link, $m ) ) {
838 $pre = strtolower( $m[1] );
840 if ( $wgLang->getNsIndex( $pre ) ==
841 Namespace::getImage() ) {
842 $nt = Title
::newFromText( $suf );
843 $name = $nt->getDBkey();
844 if ( "" == $text ) { $text = $nt->GetText(); }
846 $wgLinkCache->addImageLink( $name );
847 $s .= $sk->makeImageLink( $name,
848 wfImageUrl( $name ), $text );
850 } else if ( "media" == $pre ) {
851 $nt = Title
::newFromText( $suf );
852 $name = $nt->getDBkey();
853 if ( "" == $text ) { $text = $nt->GetText(); }
855 $wgLinkCache->addImageLink( $name );
856 $s .= $sk->makeMediaLink( $name,
857 wfImageUrl( $name ), $text );
860 $l = $wgLang->getLanguageName( $pre );
861 if ( "" == $l or !$wgInterwikiMagic or
862 Namespace::isTalk( $wgTitle->getNamespace() ) ) {
863 if ( "" == $text ) { $text = $link; }
864 $s .= $sk->makeLink( $link, $text, "", $trail );
866 array_push( $this->mLanguageLinks
, "$pre:$suf" );
870 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
871 # $link = substr( $link, 2 );
872 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
874 if ( "" == $text ) { $text = $link; }
875 $s .= $sk->makeLink( $link, $text, "", $trail );
883 # Some functions here used by doBlockLevels()
885 /* private */ function closeParagraph()
888 if ( 0 != strcmp( "p", $this->mLastSection
) &&
889 0 != strcmp( "", $this->mLastSection
) ) {
890 $result = "</" . $this->mLastSection
. ">";
892 $this->mLastSection
= "";
895 # getCommon() returns the length of the longest common substring
896 # of both arguments, starting at the beginning of both.
898 /* private */ function getCommon( $st1, $st2 )
900 $fl = strlen( $st1 );
901 $shorter = strlen( $st2 );
902 if ( $fl < $shorter ) { $shorter = $fl; }
904 for ( $i = 0; $i < $shorter; ++
$i ) {
905 if ( $st1{$i} != $st2{$i} ) { break; }
909 # These next three functions open, continue, and close the list
910 # element appropriate to the prefix character passed into them.
912 /* private */ function openList( $char )
914 $result = $this->closeParagraph();
916 if ( "*" == $char ) { $result .= "<ul><li>"; }
917 else if ( "#" == $char ) { $result .= "<ol><li>"; }
918 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
919 else if ( ";" == $char ) {
920 $result .= "<dl><dt>";
921 $this->mDTopen
= true;
923 else { $result = "<!-- ERR 1 -->"; }
928 /* private */ function nextItem( $char )
930 if ( "*" == $char ||
"#" == $char ) { return "</li><li>"; }
931 else if ( ":" == $char ||
";" == $char ) {
933 if ( $this->mDTopen
) { $close = "</dt>"; }
934 if ( ";" == $char ) {
935 $this->mDTopen
= true;
936 return $close . "<dt>";
938 $this->mDTopen
= false;
939 return $close . "<dd>";
942 return "<!-- ERR 2 -->";
945 /* private */function closeList( $char )
947 if ( "*" == $char ) { return "</li></ul>"; }
948 else if ( "#" == $char ) { return "</li></ol>"; }
949 else if ( ":" == $char ) {
950 if ( $this->mDTopen
) {
951 $this->mDTopen
= false;
957 return "<!-- ERR 3 -->";
960 /* private */ function doBlockLevels( $text, $linestart )
962 wfProfileIn( "OutputPage::doBlockLevels" );
963 # Parsing through the text line by line. The main thing
964 # happening here is handling of block-level elements p, pre,
965 # and making lists from lines starting with * # : etc.
967 $a = explode( "\n", $text );
968 $text = $lastPref = "";
969 $this->mDTopen
= $inBlockElem = false;
971 if ( ! $linestart ) { $text .= array_shift( $a ); }
972 foreach ( $a as $t ) {
973 if ( "" != $text ) { $text .= "\n"; }
976 $opl = strlen( $lastPref );
977 $npl = strspn( $t, "*#:;" );
978 $pref = substr( $t, 0, $npl );
979 $pref2 = str_replace( ";", ":", $pref );
980 $t = substr( $t, $npl );
982 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
983 $text .= $this->nextItem( substr( $pref, -1 ) );
985 if ( ";" == substr( $pref, -1 ) ) {
986 $cpos = strpos( $t, ":" );
987 if ( ! ( false === $cpos ) ) {
988 $term = substr( $t, 0, $cpos );
989 $text .= $term . $this->nextItem( ":" );
990 $t = substr( $t, $cpos +
1 );
993 } else if (0 != $npl ||
0 != $opl) {
994 $cpl = $this->getCommon( $pref, $lastPref );
996 while ( $cpl < $opl ) {
997 $text .= $this->closeList( $lastPref{$opl-1} );
1000 if ( $npl <= $cpl && $cpl > 0 ) {
1001 $text .= $this->nextItem( $pref{$cpl-1} );
1003 while ( $npl > $cpl ) {
1004 $char = substr( $pref, $cpl, 1 );
1005 $text .= $this->openList( $char );
1007 if ( ";" == $char ) {
1008 $cpos = strpos( $t, ":" );
1009 if ( ! ( false === $cpos ) ) {
1010 $term = substr( $t, 0, $cpos );
1011 $text .= $term . $this->nextItem( ":" );
1012 $t = substr( $t, $cpos +
1 );
1019 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1021 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1022 $text .= $this->closeParagraph();
1023 $inBlockElem = true;
1025 if ( ! $inBlockElem ) {
1026 if ( " " == $t{0} ) {
1027 $newSection = "pre";
1028 # $t = wfEscapeHTML( $t );
1030 else { $newSection = "p"; }
1032 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1033 $text .= $this->closeParagraph();
1034 $text .= "<" . $newSection . ">";
1035 } else if ( 0 != strcmp( $this->mLastSection
,
1037 $text .= $this->closeParagraph();
1038 if ( 0 != strcmp( "p", $newSection ) ) {
1039 $text .= "<" . $newSection . ">";
1042 $this->mLastSection
= $newSection;
1044 if ( $inBlockElem &&
1045 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1046 $inBlockElem = false;
1052 $text .= $this->closeList( $pref2{$npl-1} );
1055 if ( "" != $this->mLastSection
) {
1056 if ( "p" != $this->mLastSection
) {
1057 $text .= "</" . $this->mLastSection
. ">";
1059 $this->mLastSection
= "";
1065 /* private */ function replaceVariables( $text )
1068 wfProfileIn( "OutputPage:replaceVariables" );
1070 /* As with sigs, use server's local time --
1071 ensure this is appropriate for your audience! */
1073 $text = str_replace( "{{CURRENTMONTH}}", $v, $text );
1074 $v = $wgLang->getMonthName( date( "n" ) );
1075 $text = str_replace( "{{CURRENTMONTHNAME}}", $v, $text );
1076 $v = $wgLang->getMonthNameGen( date( "n" ) );
1077 $text = str_replace( "{{CURRENTMONTHNAMEGEN}}", $v, $text );
1079 $text = str_replace( "{{CURRENTDAY}}", $v, $text );
1080 $v = $wgLang->getWeekdayName( date( "w" )+
1 );
1081 $text = str_replace( "{{CURRENTDAYNAME}}", $v, $text );
1083 $text = str_replace( "{{CURRENTYEAR}}", $v, $text );
1084 $v = $wgLang->time( wfTimestampNow(), false );
1085 $text = str_replace( "{{CURRENTTIME}}", $v, $text );
1087 if ( false !== strstr( $text, "{{NUMBEROFARTICLES}}" ) ) {
1088 $v = wfNumberOfArticles();
1089 $text = str_replace( "{{NUMBEROFARTICLES}}", $v, $text );
1095 /* private */ function removeHTMLtags( $text )
1097 wfProfileIn( "OutputPage::removeHTMLtags" );
1098 $htmlpairs = array( # Tags that must be closed
1099 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1100 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1101 "strike", "strong", "tt", "var", "div", "center",
1102 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1103 "ruby", "rt" , "rb" , "rp"
1105 $htmlsingle = array(
1106 "br", "p", "hr", "li", "dt", "dd"
1108 $htmlnest = array( # Tags that can be nested--??
1109 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1110 "dl", "font", "big", "small", "sub", "sup"
1112 $tabletags = array( # Can only appear inside table
1116 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1117 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1119 $htmlattrs = array( # Allowed attributes--no scripting, etc.
1120 "title", "align", "lang", "dir", "width", "height",
1121 "bgcolor", "clear", /* BR */ "noshade", /* HR */
1122 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
1123 /* FONT */ "type", "start", "value", "compact",
1124 /* For various lists, mostly deprecated but safe */
1125 "summary", "width", "border", "frame", "rules",
1126 "cellspacing", "cellpadding", "valign", "char",
1127 "charoff", "colgroup", "col", "span", "abbr", "axis",
1128 "headers", "scope", "rowspan", "colspan", /* Tables */
1129 "id", "class", "name", "style" /* For CSS */
1132 # Remove HTML comments
1133 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1135 $bits = explode( "<", $text );
1136 $text = array_shift( $bits );
1137 $tagstack = array(); $tablestack = array();
1139 foreach ( $bits as $x ) {
1140 $prev = error_reporting( E_ALL
& ~
( E_NOTICE | E_WARNING
) );
1141 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1143 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1144 error_reporting( $prev );
1147 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1151 if ( ! in_array( $t, $htmlsingle ) &&
1152 ( $ot = array_pop( $tagstack ) ) != $t ) {
1153 array_push( $tagstack, $ot );
1156 if ( $t == "table" ) {
1157 $tagstack = array_pop( $tablestack );
1162 # Keep track for later
1163 if ( in_array( $t, $tabletags ) &&
1164 ! in_array( "table", $tagstack ) ) {
1166 } else if ( in_array( $t, $tagstack ) &&
1167 ! in_array ( $t , $htmlnest ) ) {
1169 } else if ( ! in_array( $t, $htmlsingle ) ) {
1170 if ( $t == "table" ) {
1171 array_push( $tablestack, $tagstack );
1172 $tagstack = array();
1174 array_push( $tagstack, $t );
1176 # Strip non-approved attributes from the tag
1177 $newparams = preg_replace(
1178 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
1179 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
1183 $rest = str_replace( ">", ">", $rest );
1184 $text .= "<$slash$t$newparams$brace$rest";
1188 $text .= "<" . str_replace( ">", ">", $x);
1190 # Close off any remaining tags
1191 while ( $t = array_pop( $tagstack ) ) {
1193 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1202 * This function accomplishes several tasks:
1203 * 1) Auto-number headings if that option is enabled
1204 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1205 * 3) Add a Table of contents on the top for users who have enabled the option
1206 * 4) Auto-anchor headings
1208 * It loops through all headlines, collects the necessary data, then splits up the
1209 * string and re-inserts the newly formatted headlines.
1212 /* private */ function formatHeadings( $text )
1214 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1215 $nh=$wgUser->getOption( "numberheadings" );
1216 $st=$wgUser->getOption( "showtoc" );
1217 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1218 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1220 $sk=$wgUser->getSkin();
1221 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1225 foreach($matches[3] as $headline) {
1226 if($level) { $prevlevel=$level;}
1227 $level=$matches[1][$c];
1228 if(($nh||
$st) && $level>$prevlevel) {
1230 $h[$level]=0; // reset when we enter a new level
1232 $toc.=$sk->tocIndent($level-$prevlevel);
1237 if(($nh||
$st) && $level<$prevlevel) {
1238 $h[$level+
1]=0; // reset when we step back a level
1240 $toc.=$sk->tocUnindent($prevlevel-$level);
1245 $h[$level]++
; // count number of headlines for each level
1248 for($i=1;$i<=$level;$i++
) {
1250 if($dot) {$numbering.=".";}
1258 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1259 $tocline=$canonized_headline;
1260 $canonized_headline=str_replace('"',"",$canonized_headline);
1261 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1262 $refer[$c]=$canonized_headline;
1263 $refers[$canonized_headline]++
; // count how many in assoc. array so we can track dupes in anchors
1264 $refcount[$c]=$refers[$canonized_headline];
1266 $tocline=$numbering ." ". $tocline;
1268 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1271 $anchor=$canonized_headline;
1272 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1274 $toc.=$sk->tocLine($anchor,$tocline);
1276 if($es && !isset($wpPreview)) {
1277 $head[$c].=$sk->editSectionLink($c+
1);
1279 $head[$c].="<H".$level.$matches[2][$c]
1280 ."<a name=\"".$anchor."\">"
1291 while($toclevel>0) {
1296 $toc=$sk->tocTable($toc);
1300 // split up and insert constructed headlines
1302 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1305 foreach($blocks as $block) {
1306 if($es && !isset($wpPreview) && $c>0 && $i==0) {
1307 $full.=$sk->editSectionLink(0);
1314 if($st && $toclines>3) {
1315 $full=$toc."<a name=\"top\"></a>".$full;
1320 /* private */ function magicISBN( $text )
1324 $a = split( "ISBN ", " $text" );
1325 if ( count ( $a ) < 2 ) return $text;
1326 $text = substr( array_shift( $a ), 1);
1327 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1329 foreach ( $a as $x ) {
1330 $isbn = $blank = "" ;
1331 while ( " " == $x{0} ) {
1333 $x = substr( $x, 1 );
1335 while ( strstr( $valid, $x{0} ) != false ) {
1337 $x = substr( $x, 1 );
1339 $num = str_replace( "-", "", $isbn );
1340 $num = str_replace( " ", "", $num );
1343 $text .= "ISBN $blank$x";
1345 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1346 "Booksources"), "isbn={$num}" ) . "\" CLASS=\"internal\">ISBN $isbn</a>";
1353 /* private */ function magicRFC( $text )
1358 /* private */ function headElement()
1360 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding;
1362 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1364 if ( "" == $this->mHTMLtitle
) {
1365 $this->mHTMLtitle
= $this->mPagetitle
;
1367 $ret .= "<html lang=\"$wgLanguageCode\"><head><title>{$this->mHTMLtitle}</title>\n";
1368 array_push( $this->mMetatags
, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1369 foreach ( $this->mMetatags
as $tag ) {
1370 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1372 $tag[0] = substr( $tag[0], 5 );
1376 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1378 $p = $this->mRobotpolicy
;
1379 if ( "" == $p ) { $p = "index,follow"; }
1380 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1382 if ( count( $this->mKeywords
) > 0 ) {
1383 $ret .= "<meta name=\"keywords\" content=\"" .
1384 implode( ",", $this->mKeywords
) . "\">\n";
1386 foreach ( $this->mLinktags
as $tag ) {
1388 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1389 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1390 $ret .= "href=\"{$tag[2]}\">\n";
1392 $sk = $wgUser->getSkin();
1393 $ret .= $sk->getHeadScripts();
1394 $ret .= $sk->getUserStyles();
1396 $ret .= "</head>\n";