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 )
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 = $this->replaceExternalLinks( $text );
666 $text = $this->replaceInternalLinks ( $text );
668 $text = $this->magicISBN( $text );
669 $text = $this->magicRFC( $text );
670 $text = $this->formatHeadings( $text );
672 $sk = $wgUser->getSkin();
673 $text = $sk->transformContent( $text );
679 /* private */ function doQuotes( $text )
681 $text = preg_replace( "/'''(.+)'''/mU", "<strong>\$1</strong>", $text );
682 $text = preg_replace( "/''(.+)''/mU", "<em>\$1</em>", $text );
686 /* private */ function doHeadings( $text )
688 for ( $i = 6; $i >= 1; --$i ) {
689 $h = substr( "======", 0, $i );
690 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
691 "<h{$i}>\\1</h{$i}>\\2", $text );
696 # Note: we have to do external links before the internal ones,
697 # and otherwise take great care in the order of things here, so
698 # that we don't end up interpreting some URLs twice.
700 /* private */ function replaceExternalLinks( $text )
702 wfProfileIn( "OutputPage::replaceExternalLinks" );
703 $text = $this->subReplaceExternalLinks( $text, "http", true );
704 $text = $this->subReplaceExternalLinks( $text, "https", true );
705 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
706 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
707 $text = $this->subReplaceExternalLinks( $text, "news", false );
708 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
713 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
715 global $wgUser, $printable;
716 global $wgAllowExternalImages;
719 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
720 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
722 # this is the list of separators that should be ignored if they
723 # are the last character of an URL but that should be included
724 # if they occur within the URL, e.g. "go to www.foo.com, where .."
725 # in this case, the last comma should not become part of the URL,
726 # but in "www.foo.com/123,2342,32.htm" it should.
728 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
729 $images = "gif|png|jpg|jpeg";
731 # PLEASE NOTE: The curly braces { } are not part of the regex,
732 # they are interpreted as part of the string (used to tell PHP
733 # that the content of the string should be inserted there).
734 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
735 "((?i){$images})([^{$uc}]|$)/";
737 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
738 $sk = $wgUser->getSkin();
740 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
741 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
742 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
744 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
745 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
746 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
748 $s = str_replace( $unique, $protocol, $s );
750 $a = explode( "[{$protocol}:", " " . $s );
751 $s = array_shift( $a );
752 $s = substr( $s, 1 );
754 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
755 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
757 foreach ( $a as $line ) {
758 if ( preg_match( $e1, $line, $m ) ) {
759 $link = "{$protocol}:{$m[1]}";
761 if ( $autonumber ) { $text = "[" . ++
$this->mAutonumber
. "]"; }
762 else { $text = wfEscapeHTML( $link ); }
763 } else if ( preg_match( $e2, $line, $m ) ) {
764 $link = "{$protocol}:{$m[1]}";
768 $s .= "[{$protocol}:" . $line;
771 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
773 $la = $sk->getExternalLinkAttributes( $link, $text );
774 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
780 /* private */ function replaceInternalLinks( $s )
782 global $wgTitle, $wgUser, $wgLang;
783 global $wgLinkCache, $wgInterwikiMagic;
784 global $wgNamespacesWithSubpages;
785 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
787 wfProfileIn( "$fname-setup" );
788 $tc = Title
::legalChars() . "#";
789 $sk = $wgUser->getSkin();
791 $a = explode( "[[", " " . $s );
792 $s = array_shift( $a );
793 $s = substr( $s, 1 );
795 $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD";
796 $e2 = "/^([{$tc}]+)]](.*)\$/sD";
799 wfProfileIn( "$fname-loop" );
800 foreach ( $a as $line ) {
801 if ( preg_match( $e1, $line, $m ) ) { # page with alternate text
806 } else if ( preg_match( $e2, $line, $m ) ) { # page with normal text
812 else { # Invalid form; output directly
816 if(substr($m[1],0,1)=="/") { # subpage
817 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
818 $m[1]=substr($m[1],1,strlen($m[1])-2);
822 $noslash=substr($m[1],1);
824 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
825 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
828 } # this might be changed for ugliness reasons
830 $link = $noslash; # no subpage allowed, use standard link
832 } else { # no subpage
836 if ( preg_match( "/^([A-Za-z\\x80-\\xff]+):(.*)\$/", $link, $m ) ) {
837 $pre = strtolower( $m[1] );
839 if ( $wgLang->getNsIndex( $pre ) ==
840 Namespace::getImage() ) {
841 $nt = Title
::newFromText( $suf );
842 $name = $nt->getDBkey();
843 if ( "" == $text ) { $text = $nt->GetText(); }
845 $wgLinkCache->addImageLink( $name );
846 $s .= $sk->makeImageLink( $name,
847 wfImageUrl( $name ), $text );
849 } else if ( "media" == $pre ) {
850 $nt = Title
::newFromText( $suf );
851 $name = $nt->getDBkey();
852 if ( "" == $text ) { $text = $nt->GetText(); }
854 $wgLinkCache->addImageLink( $name );
855 $s .= $sk->makeMediaLink( $name,
856 wfImageUrl( $name ), $text );
859 $l = $wgLang->getLanguageName( $pre );
860 if ( "" == $l or !$wgInterwikiMagic or
861 Namespace::isTalk( $wgTitle->getNamespace() ) ) {
862 if ( "" == $text ) { $text = $link; }
863 $s .= $sk->makeLink( $link, $text, "", $trail );
865 array_push( $this->mLanguageLinks
, "$pre:$suf" );
869 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
870 # $link = substr( $link, 2 );
871 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
873 if ( "" == $text ) { $text = $link; }
874 $s .= $sk->makeLink( $link, $text, "", $trail );
882 # Some functions here used by doBlockLevels()
884 /* private */ function closeParagraph()
887 if ( 0 != strcmp( "p", $this->mLastSection
) &&
888 0 != strcmp( "", $this->mLastSection
) ) {
889 $result = "</" . $this->mLastSection
. ">";
891 $this->mLastSection
= "";
894 # getCommon() returns the length of the longest common substring
895 # of both arguments, starting at the beginning of both.
897 /* private */ function getCommon( $st1, $st2 )
899 $fl = strlen( $st1 );
900 $shorter = strlen( $st2 );
901 if ( $fl < $shorter ) { $shorter = $fl; }
903 for ( $i = 0; $i < $shorter; ++
$i ) {
904 if ( $st1{$i} != $st2{$i} ) { break; }
908 # These next three functions open, continue, and close the list
909 # element appropriate to the prefix character passed into them.
911 /* private */ function openList( $char )
913 $result = $this->closeParagraph();
915 if ( "*" == $char ) { $result .= "<ul><li>"; }
916 else if ( "#" == $char ) { $result .= "<ol><li>"; }
917 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
918 else if ( ";" == $char ) {
919 $result .= "<dl><dt>";
920 $this->mDTopen
= true;
922 else { $result = "<!-- ERR 1 -->"; }
927 /* private */ function nextItem( $char )
929 if ( "*" == $char ||
"#" == $char ) { return "</li><li>"; }
930 else if ( ":" == $char ||
";" == $char ) {
932 if ( $this->mDTopen
) { $close = "</dt>"; }
933 if ( ";" == $char ) {
934 $this->mDTopen
= true;
935 return $close . "<dt>";
937 $this->mDTopen
= false;
938 return $close . "<dd>";
941 return "<!-- ERR 2 -->";
944 /* private */function closeList( $char )
946 if ( "*" == $char ) { return "</li></ul>"; }
947 else if ( "#" == $char ) { return "</li></ol>"; }
948 else if ( ":" == $char ) {
949 if ( $this->mDTopen
) {
950 $this->mDTopen
= false;
956 return "<!-- ERR 3 -->";
959 /* private */ function doBlockLevels( $text, $linestart )
961 wfProfileIn( "OutputPage::doBlockLevels" );
962 # Parsing through the text line by line. The main thing
963 # happening here is handling of block-level elements p, pre,
964 # and making lists from lines starting with * # : etc.
966 $a = explode( "\n", $text );
967 $text = $lastPref = "";
968 $this->mDTopen
= $inBlockElem = false;
970 if ( ! $linestart ) { $text .= array_shift( $a ); }
971 foreach ( $a as $t ) {
972 if ( "" != $text ) { $text .= "\n"; }
975 $opl = strlen( $lastPref );
976 $npl = strspn( $t, "*#:;" );
977 $pref = substr( $t, 0, $npl );
978 $pref2 = str_replace( ";", ":", $pref );
979 $t = substr( $t, $npl );
981 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
982 $text .= $this->nextItem( substr( $pref, -1 ) );
984 if ( ";" == substr( $pref, -1 ) ) {
985 $cpos = strpos( $t, ":" );
986 if ( ! ( false === $cpos ) ) {
987 $term = substr( $t, 0, $cpos );
988 $text .= $term . $this->nextItem( ":" );
989 $t = substr( $t, $cpos +
1 );
992 } else if (0 != $npl ||
0 != $opl) {
993 $cpl = $this->getCommon( $pref, $lastPref );
995 while ( $cpl < $opl ) {
996 $text .= $this->closeList( $lastPref{$opl-1} );
999 if ( $npl <= $cpl && $cpl > 0 ) {
1000 $text .= $this->nextItem( $pref{$cpl-1} );
1002 while ( $npl > $cpl ) {
1003 $char = substr( $pref, $cpl, 1 );
1004 $text .= $this->openList( $char );
1006 if ( ";" == $char ) {
1007 $cpos = strpos( $t, ":" );
1008 if ( ! ( false === $cpos ) ) {
1009 $term = substr( $t, 0, $cpos );
1010 $text .= $term . $this->nextItem( ":" );
1011 $t = substr( $t, $cpos +
1 );
1018 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1020 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1021 $text .= $this->closeParagraph();
1022 $inBlockElem = true;
1024 if ( ! $inBlockElem ) {
1025 if ( " " == $t{0} ) {
1026 $newSection = "pre";
1027 # $t = wfEscapeHTML( $t );
1029 else { $newSection = "p"; }
1031 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1032 $text .= $this->closeParagraph();
1033 $text .= "<" . $newSection . ">";
1034 } else if ( 0 != strcmp( $this->mLastSection
,
1036 $text .= $this->closeParagraph();
1037 if ( 0 != strcmp( "p", $newSection ) ) {
1038 $text .= "<" . $newSection . ">";
1041 $this->mLastSection
= $newSection;
1043 if ( $inBlockElem &&
1044 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1045 $inBlockElem = false;
1051 $text .= $this->closeList( $pref2{$npl-1} );
1054 if ( "" != $this->mLastSection
) {
1055 if ( "p" != $this->mLastSection
) {
1056 $text .= "</" . $this->mLastSection
. ">";
1058 $this->mLastSection
= "";
1064 /* private */ function replaceVariables( $text )
1067 wfProfileIn( "OutputPage:replaceVariables" );
1069 /* As with sigs, use server's local time --
1070 ensure this is appropriate for your audience! */
1072 $text = str_replace( "{{CURRENTMONTH}}", $v, $text );
1073 $v = $wgLang->getMonthName( date( "n" ) );
1074 $text = str_replace( "{{CURRENTMONTHNAME}}", $v, $text );
1075 $v = $wgLang->getMonthNameGen( date( "n" ) );
1076 $text = str_replace( "{{CURRENTMONTHNAMEGEN}}", $v, $text );
1078 $text = str_replace( "{{CURRENTDAY}}", $v, $text );
1079 $v = $wgLang->getWeekdayName( date( "w" )+
1 );
1080 $text = str_replace( "{{CURRENTDAYNAME}}", $v, $text );
1082 $text = str_replace( "{{CURRENTYEAR}}", $v, $text );
1083 $v = $wgLang->time( wfTimestampNow(), false );
1084 $text = str_replace( "{{CURRENTTIME}}", $v, $text );
1086 if ( false !== strstr( $text, "{{NUMBEROFARTICLES}}" ) ) {
1087 $v = wfNumberOfArticles();
1088 $text = str_replace( "{{NUMBEROFARTICLES}}", $v, $text );
1094 /* private */ function removeHTMLtags( $text )
1096 wfProfileIn( "OutputPage::removeHTMLtags" );
1097 $htmlpairs = array( # Tags that must be closed
1098 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1099 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1100 "strike", "strong", "tt", "var", "div", "center",
1101 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1102 "ruby", "rt" , "rb" , "rp"
1104 $htmlsingle = array(
1105 "br", "p", "hr", "li", "dt", "dd"
1107 $htmlnest = array( # Tags that can be nested--??
1108 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1109 "dl", "font", "big", "small", "sub", "sup"
1111 $tabletags = array( # Can only appear inside table
1115 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1116 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1118 $htmlattrs = array( # Allowed attributes--no scripting, etc.
1119 "title", "align", "lang", "dir", "width", "height",
1120 "bgcolor", "clear", /* BR */ "noshade", /* HR */
1121 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
1122 /* FONT */ "type", "start", "value", "compact",
1123 /* For various lists, mostly deprecated but safe */
1124 "summary", "width", "border", "frame", "rules",
1125 "cellspacing", "cellpadding", "valign", "char",
1126 "charoff", "colgroup", "col", "span", "abbr", "axis",
1127 "headers", "scope", "rowspan", "colspan", /* Tables */
1128 "id", "class", "name", "style" /* For CSS */
1131 # Remove HTML comments
1132 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1134 $bits = explode( "<", $text );
1135 $text = array_shift( $bits );
1136 $tagstack = array(); $tablestack = array();
1138 foreach ( $bits as $x ) {
1139 $prev = error_reporting( E_ALL
& ~
( E_NOTICE | E_WARNING
) );
1140 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1142 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1143 error_reporting( $prev );
1146 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1150 if ( ! in_array( $t, $htmlsingle ) &&
1151 ( $ot = array_pop( $tagstack ) ) != $t ) {
1152 array_push( $tagstack, $ot );
1155 if ( $t == "table" ) {
1156 $tagstack = array_pop( $tablestack );
1161 # Keep track for later
1162 if ( in_array( $t, $tabletags ) &&
1163 ! in_array( "table", $tagstack ) ) {
1165 } else if ( in_array( $t, $tagstack ) &&
1166 ! in_array ( $t , $htmlnest ) ) {
1168 } else if ( ! in_array( $t, $htmlsingle ) ) {
1169 if ( $t == "table" ) {
1170 array_push( $tablestack, $tagstack );
1171 $tagstack = array();
1173 array_push( $tagstack, $t );
1175 # Strip non-approved attributes from the tag
1176 $newparams = preg_replace(
1177 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
1178 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
1182 $rest = str_replace( ">", ">", $rest );
1183 $text .= "<$slash$t$newparams$brace$rest";
1187 $text .= "<" . str_replace( ">", ">", $x);
1189 # Close off any remaining tags
1190 while ( $t = array_pop( $tagstack ) ) {
1192 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1201 * This function accomplishes several tasks:
1202 * 1) Auto-number headings if that option is enabled
1203 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1204 * 3) Add a Table of contents on the top for users who have enabled the option
1205 * 4) Auto-anchor headings
1207 * It loops through all headlines, collects the necessary data, then splits up the
1208 * string and re-inserts the newly formatted headlines.
1211 /* private */ function formatHeadings( $text )
1213 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1214 $nh=$wgUser->getOption( "numberheadings" );
1215 $st=$wgUser->getOption( "showtoc" );
1216 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1217 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1219 $sk=$wgUser->getSkin();
1220 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1224 foreach($matches[3] as $headline) {
1225 if($level) { $prevlevel=$level;}
1226 $level=$matches[1][$c];
1227 if(($nh||
$st) && $level>$prevlevel) {
1229 $h[$level]=0; // reset when we enter a new level
1231 $toc.=$sk->tocIndent($level-$prevlevel);
1236 if(($nh||
$st) && $level<$prevlevel) {
1237 $h[$level+
1]=0; // reset when we step back a level
1239 $toc.=$sk->tocUnindent($prevlevel-$level);
1244 $h[$level]++
; // count number of headlines for each level
1247 for($i=1;$i<=$level;$i++
) {
1249 if($dot) {$numbering.=".";}
1257 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1258 $tocline=$canonized_headline;
1259 $canonized_headline=str_replace('"',"",$canonized_headline);
1260 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1261 $refer[$c]=$canonized_headline;
1262 $refers[$canonized_headline]++
; // count how many in assoc. array so we can track dupes in anchors
1263 $refcount[$c]=$refers[$canonized_headline];
1265 $tocline=$numbering ." ". $tocline;
1267 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1270 $anchor=$canonized_headline;
1271 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1273 $toc.=$sk->tocLine($anchor,$tocline);
1275 if($es && !isset($wpPreview)) {
1276 $head[$c].=$sk->editSectionLink($c+
1);
1278 $head[$c].="<H".$level.$matches[2][$c]
1279 ."<a name=\"".$anchor."\">"
1290 while($toclevel>0) {
1295 $toc=$sk->tocTable($toc);
1299 // split up and insert constructed headlines
1301 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1304 foreach($blocks as $block) {
1305 if($es && !isset($wpPreview) && $c>0 && $i==0) {
1306 $full.=$sk->editSectionLink(0);
1313 if($st && $toclines>3) {
1314 $full=$toc."<a name=\"top\"></a>".$full;
1319 /* private */ function magicISBN( $text )
1323 $a = split( "ISBN ", " $text" );
1324 if ( count ( $a ) < 2 ) return $text;
1325 $text = substr( array_shift( $a ), 1);
1326 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1328 foreach ( $a as $x ) {
1329 $isbn = $blank = "" ;
1330 while ( " " == $x{0} ) {
1332 $x = substr( $x, 1 );
1334 while ( strstr( $valid, $x{0} ) != false ) {
1336 $x = substr( $x, 1 );
1338 $num = str_replace( "-", "", $isbn );
1339 $num = str_replace( " ", "", $num );
1342 $text .= "ISBN $blank$x";
1344 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1345 "Booksources"), "isbn={$num}" ) . "\" CLASS=\"internal\">ISBN $isbn</a>";
1352 /* private */ function magicRFC( $text )
1357 /* private */ function headElement()
1359 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding;
1361 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\" \"$wgDTD\">\n";
1363 if ( "" == $this->mHTMLtitle
) {
1364 $this->mHTMLtitle
= $this->mPagetitle
;
1366 $ret .= "<html lang=\"$wgLanguageCode\"><head><title>{$this->mHTMLtitle}</title>\n";
1367 array_push( $this->mMetatags
, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1368 foreach ( $this->mMetatags
as $tag ) {
1369 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1371 $tag[0] = substr( $tag[0], 5 );
1375 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1377 $p = $this->mRobotpolicy
;
1378 if ( "" == $p ) { $p = "index,follow"; }
1379 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1381 if ( count( $this->mKeywords
) > 0 ) {
1382 $ret .= "<meta name=\"keywords\" content=\"" .
1383 implode( ",", $this->mKeywords
) . "\">\n";
1385 foreach ( $this->mLinktags
as $tag ) {
1387 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1388 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1389 $ret .= "href=\"{$tag[2]}\">\n";
1391 $sk = $wgUser->getSkin();
1392 $ret .= $sk->getHeadScripts();
1393 $ret .= $sk->getUserStyles();
1395 $ret .= "</head>\n";