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 = "INSERT 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
157 if( $wgUser->getOption( "nocache" ) ) return;
159 if( $_SERVER["HTTP_IF_MODIFIED_SINCE"] != "" ) {
160 $ismodsince = wfUnix2Timestamp( strtotime( $_SERVER["HTTP_IF_MODIFIED_SINCE"] ) );
161 $lastmod = gmdate( "D, j M Y H:i:s", wfTimestamp2Unix(
162 max( $timestamp, $wgUser->mTouched
) ) ) . " GMT";
164 if( ($ismodsince >= $timestamp ) and $wgUser->validateCache( $ismodsince ) ) {
165 # Make sure you're in a place you can leave when you call us!
166 header( "HTTP/1.0 304 Not Modified" );
167 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
168 header( "Cache-Control: private, must-revalidate, max-age=0" );
169 header( "Last-Modified: {$lastmod}" );
170 #wfDebug( "CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
173 #wfDebug( "READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
174 $this->mLastModified
= $lastmod;
179 function setRobotpolicy( $str ) { $this->mRobotpolicy
= $str; }
180 function setHTMLtitle( $name ) { $this->mHTMLtitle
= $name; }
181 function setPageTitle( $name ) { $this->mPagetitle
= $name; }
182 function getPageTitle() { return $this->mPagetitle
; }
183 function setSubtitle( $str ) { $this->mSubtitle
= $str; }
184 function getSubtitle() { return $this->mSubtitle
; }
185 function setArticleFlag( $v ) { $this->mIsarticle
= $v; }
186 function isArticle() { return $this->mIsarticle
; }
187 function setPrintable() { $this->mPrintable
= true; }
188 function isPrintable() { return $this->mPrintable
; }
190 function getLanguageLinks() {
191 global $wgUseNewInterlanguage, $wgTitle, $wgLanguageCode;
192 global $wgDBconnection, $wgDBname, $wgDBintlname;
194 if ( ! $wgUseNewInterlanguage )
195 return $this->mLanguageLinks
;
197 mysql_select_db( $wgDBintlname, $wgDBconnection ) or die(
198 htmlspecialchars(mysql_error()) );
201 $sql = "SELECT * FROM ilinks WHERE lang_from=\"" .
202 "{$wgLanguageCode}\" AND title_from=\"" . $wgTitle->getDBkey() . "\"";
203 $res = mysql_query( $sql, $wgDBconnection );
205 while ( $q = mysql_fetch_object ( $res ) ) {
206 $list[] = $q->lang_to
. ":" . $q->title_to
;
208 mysql_free_result( $res );
209 mysql_select_db( $wgDBname, $wgDBconnection ) or die(
210 htmlspecialchars(mysql_error()) );
215 function supressQuickbar() { $this->mSupressQuickbar
= true; }
216 function isQuickbarSupressed() { return $this->mSupressQuickbar
; }
218 function addHTML( $text ) { $this->mBodytext
.= $text; }
219 function addHeadtext( $text ) { $this->mHeadtext
.= $text; }
220 function debug( $text ) { $this->mDebugtext
.= $text; }
222 # First pass--just handle <nowiki> sections, pass the rest off
223 # to doWikiPass2() which does all the real work.
226 function addWikiText( $text, $linestart = true )
229 wfProfileIn( "OutputPage::addWikiText" );
230 $unique = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4";
231 $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO";
232 $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp";
243 while ( "" != $text ) {
244 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
246 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $text = ""; }
248 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
250 $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]);
251 $stripped .= $unique;
257 while ( "" != $stripped ) {
258 $p = preg_split( "/<\\s*math\\s*>/i", $stripped, 2 );
260 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $stripped = ""; }
262 $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 );
264 $mathlist[$mathsecs] = renderMath($q[0]);
265 $stripped2 .= $unique2;
270 $stripped2 = $stripped;
273 while ( "" != $stripped2 ) {
274 $p = preg_split( "/<\\s*pre\\s*>/i", $stripped2, 2 );
276 if ( ( count( $p ) < 2 ) ||
( "" == $p[1] ) ) { $stripped2 = ""; }
278 $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 );
280 $prelist[$presecs] = "<pre>". wfEscapeHTMLTagsOnly($q[0]). "</pre>";
281 $stripped3 .= $unique3;
286 $text = $this->doWikiPass2( $stripped3, $linestart );
288 for ( $i = 1; $i <= $presecs; ++
$i ) {
289 $text = preg_replace( "/{$unique3}/", str_replace( '$', '\$', $prelist[$i] ), $text, 1 );
292 for ( $i = 1; $i <= $mathsecs; ++
$i ) {
293 $text = preg_replace( "/{$unique2}/", str_replace( '$', '\$', $mathlist[$i] ), $text, 1 );
296 for ( $i = 1; $i <= $nwsecs; ++
$i ) {
297 $text = preg_replace( "/{$unique}/", str_replace( '$', '\$', $nwlist[$i] ), $text, 1 );
299 $this->addHTML( $text );
303 # Finally, all the text has been munged and accumulated into
304 # the object, let's actually output it:
308 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
309 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
310 wfProfileIn( "OutputPage::output" );
311 $sk = $wgUser->getSkin();
313 wfProfileIn( "OutputPage::output-headers" );
314 if( $this->mLastModified
!= "" ) {
315 header( "Cache-Control: private, must-revalidate, max-age=0" );
316 header( "Last-modified: {$this->mLastModified}" );
318 header( "Cache-Control: no-cache" ); # Experimental - see below
319 header( "Pragma: no-cache" );
320 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
322 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
324 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
325 header( "Content-language: {$wgLanguageCode}" );
327 if ( "" != $this->mRedirect
) {
328 header( "Location: {$this->mRedirect}" );
333 $exp = time() +
$wgCookieExpiration;
334 foreach( $this->mCookies
as $name => $val ) {
335 setcookie( $name, $val, $exp, "/" );
339 wfProfileIn( "OutputPage::output-middle" );
341 $this->out( $this->headElement() );
343 $this->out( "\n<body" );
344 $ops = $sk->getBodyOptions();
345 foreach ( $ops as $name => $val ) {
346 $this->out( " $name='$val'" );
349 if ( $wgDebugComments ) {
350 $this->out( "<!-- Wiki debugging output:\n" .
351 $this->mDebugtext
. "-->\n" );
353 $this->out( $sk->beforeContent() );
356 wfProfileIn( "OutputPage::output-bodytext" );
357 $this->out( $this->mBodytext
);
359 wfProfileIn( "OutputPage::output-after" );
360 $this->out( $sk->afterContent() );
363 wfProfileOut(); # A hack - we can't report after here
364 $this->out( $this->reportTime() );
366 $this->out( "\n</body></html>" );
372 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
373 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
376 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
377 if ( false === $outs ) { $outs = $ins; }
382 function setEncodings()
384 global $HTTP_SERVER_VARS, $wgInputEncoding, $wgOutputEncoding;
385 global $wgUser, $wgLang;
387 $wgInputEncoding = strtolower( $wgInputEncoding );
388 $s = $HTTP_SERVER_VARS['HTTP_ACCEPT_CHARSET'];
390 if( $wgUser->getOption( 'altencoding' ) ) {
391 $wgLang->setAltEncoding();
396 $wgOutputEncoding = strtolower( $wgOutputEncoding );
399 $a = explode( ",", $s );
403 foreach ( $a as $s ) {
404 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
416 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
417 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
418 $wgOutputEncoding = strtolower( $bestset );
422 $wgOutputEncoding = $wgInputEncoding;
425 function reportTime()
427 global $wgRequestTime, $wgDebugLogFile, $HTTP_SERVER_VARS;
428 global $wgProfiling, $wgProfileStack, $wgUser;
430 list( $usec, $sec ) = explode( " ", microtime() );
431 $now = (float)$sec +
(float)$usec;
433 list( $usec, $sec ) = explode( " ", $wgRequestTime );
434 $start = (float)$sec +
(float)$usec;
435 $elapsed = $now - $start;
437 if ( "" != $wgDebugLogFile ) {
439 if( $wgProfiling and count( $wgProfileStack ) ) {
441 foreach( $wgProfileStack as $ile ) {
442 # "foo::bar 99 0.12345 1 0.23456 2"
443 if( preg_match( '/^(\S+)\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/', $ile, $m ) ) {
444 $thisstart = (float)$m[3] +
(float)$m[4] - $start;
445 $thisend = (float)$m[5] +
(float)$m[6] - $start;
446 $thiselapsed = $thisend - $thisstart;
447 $thispercent = $thiselapsed / $elapsed * 100.0;
449 $prof .= sprintf( "\tat %04.3f in %04.3f (%2.1f%%) - %s %s\n",
450 $thisstart, $thiselapsed, $thispercent,
451 str_repeat( "*", $m[2] ), $m[1] );
452 $lasttime = $thistime;
453 #$prof .= "\t(^ $ile)\n";
455 $prof .= "\t?broken? $ile\n";
460 if( $forward = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'] )
461 $forward = " forwarded for $forward";
462 if( $client = $HTTP_SERVER_VARS['HTTP_CLIENT_IP'] )
463 $forward .= " client IP $client";
464 if( $from = $HTTP_SERVER_VARS['HTTP_FROM'] )
465 $forward .= " from $from";
467 $forward = "\t(proxied via {$HTTP_SERVER_VARS['REMOTE_ADDR']}{$forward})";
468 if($wgUser->getId() == 0)
470 $log = sprintf( "%s\t%04.3f\t%s\n",
471 date( "YmdHis" ), $elapsed,
472 urldecode( $HTTP_SERVER_VARS['REQUEST_URI'] . $forward ) );
473 error_log( $log . $prof, 3, $wgDebugLogFile );
475 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
480 # Note: these arguments are keys into wfMsg(), not text!
482 function errorpage( $title, $msg )
486 $this->mDebugtext
.= "Original title: " .
487 $wgTitle->getPrefixedText() . "\n";
488 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
489 $this->setPageTitle( wfMsg( $title ) );
490 $this->setRobotpolicy( "noindex,nofollow" );
491 $this->setArticleFlag( false );
493 $this->mBodytext
= "";
494 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
495 $this->returnToMain( false );
501 function sysopRequired()
505 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
506 $this->setPageTitle( wfMsg( "sysoptitle" ) );
507 $this->setRobotpolicy( "noindex,nofollow" );
508 $this->setArticleFlag( false );
509 $this->mBodytext
= "";
511 $sk = $wgUser->getSkin();
512 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
513 $text = str_replace( "$1", $ap, wfMsg( "sysoptext" ) );
514 $this->addHTML( $text );
515 $this->returnToMain();
518 function developerRequired()
522 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
523 $this->setPageTitle( wfMsg( "developertitle" ) );
524 $this->setRobotpolicy( "noindex,nofollow" );
525 $this->setArticleFlag( false );
526 $this->mBodytext
= "";
528 $sk = $wgUser->getSkin();
529 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
530 $text = str_replace( "$1", $ap, wfMsg( "developertext" ) );
531 $this->addHTML( $text );
532 $this->returnToMain();
535 function databaseError( $fname )
537 global $wgUser, $wgCommandLineMode;
539 $this->setPageTitle( wfMsg( "databaseerror" ) );
540 $this->setRobotpolicy( "noindex,nofollow" );
541 $this->setArticleFlag( false );
543 if ( $wgCommandLineMode ) {
544 $msg = wfMsg( "dberrortextcl" );
546 $msg = wfMsg( "dberrortextcl" );
548 $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
549 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
550 $msg = str_replace( "$3", wfLastErrno(), $msg );
551 $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
553 if ( $wgCommandLineMode ) {
557 $sk = $wgUser->getSkin();
558 $shlink = $sk->makeKnownLink( wfMsg( "searchhelppage" ),
559 wfMsg( "searchingwikipedia" ) );
560 $msg = str_replace( "$5", $shlink, $msg );
562 $this->mBodytext
= $msg;
567 function readOnlyPage()
569 global $wgUser, $wgReadOnlyFile;
571 $this->setPageTitle( wfMsg( "readonly" ) );
572 $this->setRobotpolicy( "noindex,nofollow" );
573 $this->setArticleFlag( false );
575 $reason = implode( "", file( $wgReadOnlyFile ) );
576 $text = str_replace( "$1", $reason, wfMsg( "readonlytext" ) );
577 $this->addHTML( $text );
578 $this->returnToMain( false );
581 function fatalError( $message )
583 $this->setPageTitle( wfMsg( "internalerror" ) );
584 $this->setRobotpolicy( "noindex,nofollow" );
585 $this->setArticleFlag( false );
587 $this->mBodytext
= $message;
592 function unexpectedValueError( $name, $val )
594 $msg = str_replace( "$1", $name, wfMsg( "unexpected" ) );
595 $msg = str_replace( "$2", $val, $msg );
596 $this->fatalError( $msg );
599 function fileCopyError( $old, $new )
601 $msg = str_replace( "$1", $old, wfMsg( "filecopyerror" ) );
602 $msg = str_replace( "$2", $new, $msg );
603 $this->fatalError( $msg );
606 function fileRenameError( $old, $new )
608 $msg = str_replace( "$1", $old, wfMsg( "filerenameerror" ) );
609 $msg = str_replace( "$2", $new, $msg );
610 $this->fatalError( $msg );
613 function fileDeleteError( $name )
615 $msg = str_replace( "$1", $name, wfMsg( "filedeleteerror" ) );
616 $this->fatalError( $msg );
619 function fileNotFoundError( $name )
621 $msg = str_replace( "$1", $name, wfMsg( "filenotfound" ) );
622 $this->fatalError( $msg );
625 function returnToMain( $auto = true )
627 global $wgUser, $wgOut, $returnto;
629 $sk = $wgUser->getSkin();
630 if ( "" == $returnto ) {
631 $returnto = wfMsg( "mainpage" );
633 $link = $sk->makeKnownLink( $returnto, "" );
635 $r = str_replace( "$1", $link, wfMsg( "returnto" ) );
637 $wgOut->addMeta( "http:Refresh", "10;url=" .
638 wfLocalUrlE( wfUrlencode( $returnto ) ) );
640 $wgOut->addHTML( "\n<p>$r\n" );
643 # Well, OK, it's actually about 14 passes. But since all the
644 # hard lifting is done inside PHP's regex code, it probably
645 # wouldn't speed things up much to add a real parser.
647 function doWikiPass2( $text, $linestart )
650 wfProfileIn( "OutputPage::doWikiPass2" );
652 $text = $this->removeHTMLtags( $text );
653 $text = $this->replaceVariables( $text );
655 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
656 $text = str_replace ( "<HR>", "<hr>", $text );
658 $text = $this->doQuotes( $text );
659 $text = $this->doHeadings( $text );
660 $text = $this->doBlockLevels( $text, $linestart );
662 $text = $this->replaceExternalLinks( $text );
663 $text = $this->replaceInternalLinks ( $text );
665 $text = $this->magicISBN( $text );
666 $text = $this->magicRFC( $text );
667 $text = $this->autoNumberHeadings( $text );
669 $sk = $wgUser->getSkin();
670 $text = $sk->transformContent( $text );
676 /* private */ function doQuotes( $text )
678 $text = preg_replace( "/'''(.+)'''/mU", "<strong>\$1</strong>", $text );
679 $text = preg_replace( "/''(.+)''/mU", "<em>\$1</em>", $text );
683 /* private */ function doHeadings( $text )
685 for ( $i = 6; $i >= 1; --$i ) {
686 $h = substr( "======", 0, $i );
687 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
688 "<h{$i}>\\1</h{$i}>\\2", $text );
693 # Note: we have to do external links before the internal ones,
694 # and otherwise take great care in the order of things here, so
695 # that we don't end up interpreting some URLs twice.
697 /* private */ function replaceExternalLinks( $text )
699 wfProfileIn( "OutputPage::replaceExternalLinks" );
700 $text = $this->subReplaceExternalLinks( $text, "http", true );
701 $text = $this->subReplaceExternalLinks( $text, "https", true );
702 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
703 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
704 $text = $this->subReplaceExternalLinks( $text, "news", false );
705 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
710 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
712 global $wgUser, $printable;
713 global $wgAllowExternalImages;
716 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
717 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
719 # this is the list of separators that should be ignored if they
720 # are the last character of an URL but that should be included
721 # if they occur within the URL, e.g. "go to www.foo.com, where .."
722 # in this case, the last comma should not become part of the URL,
723 # but in "www.foo.com/123,2342,32.htm" it should.
725 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
726 $images = "gif|png|jpg|jpeg";
728 # PLEASE NOTE: The curly braces { } are not part of the regex,
729 # they are interpreted as part of the string (used to tell PHP
730 # that the content of the string should be inserted there).
731 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
732 "((?i){$images})([^{$uc}]|$)/";
734 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
735 $sk = $wgUser->getSkin();
737 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
738 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
739 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
741 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
742 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
743 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
745 $s = str_replace( $unique, $protocol, $s );
747 $a = explode( "[{$protocol}:", " " . $s );
748 $s = array_shift( $a );
749 $s = substr( $s, 1 );
751 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
752 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
754 foreach ( $a as $line ) {
755 if ( preg_match( $e1, $line, $m ) ) {
756 $link = "{$protocol}:{$m[1]}";
758 if ( $autonumber ) { $text = "[" . ++
$this->mAutonumber
. "]"; }
759 else { $text = wfEscapeHTML( $link ); }
760 } else if ( preg_match( $e2, $line, $m ) ) {
761 $link = "{$protocol}:{$m[1]}";
765 $s .= "[{$protocol}:" . $line;
768 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
770 $la = $sk->getExternalLinkAttributes( $link, $text );
771 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
777 /* private */ function replaceInternalLinks( $s )
779 global $wgTitle, $wgUser, $wgLang;
780 global $wgLinkCache, $wgInterwikiMagic;
781 global $wgNamespacesWithSubpages;
782 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
784 wfProfileIn( "$fname-setup" );
785 $tc = Title
::legalChars() . "#";
786 $sk = $wgUser->getSkin();
788 $a = explode( "[[", " " . $s );
789 $s = array_shift( $a );
790 $s = substr( $s, 1 );
792 $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD";
793 $e2 = "/^([{$tc}]+)]](.*)\$/sD";
796 wfProfileIn( "$fname-loop" );
797 foreach ( $a as $line ) {
798 if ( preg_match( $e1, $line, $m ) ) { # page with alternate text
803 } else if ( preg_match( $e2, $line, $m ) ) { # page with normal text
809 else { # Invalid form; output directly
813 if(substr($m[1],0,1)=="/") { # subpage
814 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
815 $m[1]=substr($m[1],1,strlen($m[1])-2);
819 $noslash=substr($m[1],1);
821 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
822 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
825 } # this might be changed for ugliness reasons
827 $link = $noslash; # no subpage allowed, use standard link
829 } else { # no subpage
833 if ( preg_match( "/^([A-Za-z\\x80-\\xff]+):(.*)\$/", $link, $m ) ) {
834 $pre = strtolower( $m[1] );
836 if ( $wgLang->getNsIndex( $pre ) ==
837 Namespace::getImage() ) {
838 $nt = Title
::newFromText( $suf );
839 $name = $nt->getDBkey();
840 if ( "" == $text ) { $text = $nt->GetText(); }
842 $wgLinkCache->addImageLink( $name );
843 $s .= $sk->makeImageLink( $name,
844 wfImageUrl( $name ), $text );
846 } else if ( "media" == $pre ) {
847 $nt = Title
::newFromText( $suf );
848 $name = $nt->getDBkey();
849 if ( "" == $text ) { $text = $nt->GetText(); }
851 $wgLinkCache->addImageLink( $name );
852 $s .= $sk->makeMediaLink( $name,
853 wfImageUrl( $name ), $text );
856 $l = $wgLang->getLanguageName( $pre );
857 if ( "" == $l or !$wgInterwikiMagic or
858 Namespace::isTalk( $wgTitle->getNamespace() ) ) {
859 if ( "" == $text ) { $text = $link; }
860 $s .= $sk->makeLink( $link, $text, "", $trail );
862 array_push( $this->mLanguageLinks
, "$pre:$suf" );
866 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
867 # $link = substr( $link, 2 );
868 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
870 if ( "" == $text ) { $text = $link; }
871 $s .= $sk->makeLink( $link, $text, "", $trail );
879 # Some functions here used by doBlockLevels()
881 /* private */ function closeParagraph()
884 if ( 0 != strcmp( "p", $this->mLastSection
) &&
885 0 != strcmp( "", $this->mLastSection
) ) {
886 $result = "</" . $this->mLastSection
. ">";
888 $this->mLastSection
= "";
891 # getCommon() returns the length of the longest common substring
892 # of both arguments, starting at the beginning of both.
894 /* private */ function getCommon( $st1, $st2 )
896 $fl = strlen( $st1 );
897 $shorter = strlen( $st2 );
898 if ( $fl < $shorter ) { $shorter = $fl; }
900 for ( $i = 0; $i < $shorter; ++
$i ) {
901 if ( $st1{$i} != $st2{$i} ) { break; }
905 # These next three functions open, continue, and close the list
906 # element appropriate to the prefix character passed into them.
908 /* private */ function openList( $char )
910 $result = $this->closeParagraph();
912 if ( "*" == $char ) { $result .= "<ul><li>"; }
913 else if ( "#" == $char ) { $result .= "<ol><li>"; }
914 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
915 else if ( ";" == $char ) {
916 $result .= "<dl><dt>";
917 $this->mDTopen
= true;
919 else { $result = "<!-- ERR 1 -->"; }
924 /* private */ function nextItem( $char )
926 if ( "*" == $char ||
"#" == $char ) { return "</li><li>"; }
927 else if ( ":" == $char ||
";" == $char ) {
929 if ( $this->mDTopen
) { $close = "</dt>"; }
930 if ( ";" == $char ) {
931 $this->mDTopen
= true;
932 return $close . "<dt>";
934 $this->mDTopen
= false;
935 return $close . "<dd>";
938 return "<!-- ERR 2 -->";
941 /* private */function closeList( $char )
943 if ( "*" == $char ) { return "</li></ul>"; }
944 else if ( "#" == $char ) { return "</li></ol>"; }
945 else if ( ":" == $char ) {
946 if ( $this->mDTopen
) {
947 $this->mDTopen
= false;
953 return "<!-- ERR 3 -->";
956 /* private */ function doBlockLevels( $text, $linestart )
958 wfProfileIn( "OutputPage::doBlockLevels" );
959 # Parsing through the text line by line. The main thing
960 # happening here is handling of block-level elements p, pre,
961 # and making lists from lines starting with * # : etc.
963 $a = explode( "\n", $text );
964 $text = $lastPref = "";
965 $this->mDTopen
= $inBlockElem = false;
967 if ( ! $linestart ) { $text .= array_shift( $a ); }
968 foreach ( $a as $t ) {
969 if ( "" != $text ) { $text .= "\n"; }
972 $opl = strlen( $lastPref );
973 $npl = strspn( $t, "*#:;" );
974 $pref = substr( $t, 0, $npl );
975 $pref2 = str_replace( ";", ":", $pref );
976 $t = substr( $t, $npl );
978 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
979 $text .= $this->nextItem( substr( $pref, -1 ) );
981 if ( ";" == substr( $pref, -1 ) ) {
982 $cpos = strpos( $t, ":" );
983 if ( ! ( false === $cpos ) ) {
984 $term = substr( $t, 0, $cpos );
985 $text .= $term . $this->nextItem( ":" );
986 $t = substr( $t, $cpos +
1 );
989 } else if (0 != $npl ||
0 != $opl) {
990 $cpl = $this->getCommon( $pref, $lastPref );
992 while ( $cpl < $opl ) {
993 $text .= $this->closeList( $lastPref{$opl-1} );
996 if ( $npl <= $cpl && $cpl > 0 ) {
997 $text .= $this->nextItem( $pref{$cpl-1} );
999 while ( $npl > $cpl ) {
1000 $char = substr( $pref, $cpl, 1 );
1001 $text .= $this->openList( $char );
1003 if ( ";" == $char ) {
1004 $cpos = strpos( $t, ":" );
1005 if ( ! ( false === $cpos ) ) {
1006 $term = substr( $t, 0, $cpos );
1007 $text .= $term . $this->nextItem( ":" );
1008 $t = substr( $t, $cpos +
1 );
1015 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1017 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1018 $text .= $this->closeParagraph();
1019 $inBlockElem = true;
1021 if ( ! $inBlockElem ) {
1022 if ( " " == $t{0} ) {
1023 $newSection = "pre";
1024 # $t = wfEscapeHTML( $t );
1026 else { $newSection = "p"; }
1028 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1029 $text .= $this->closeParagraph();
1030 $text .= "<" . $newSection . ">";
1031 } else if ( 0 != strcmp( $this->mLastSection
,
1033 $text .= $this->closeParagraph();
1034 if ( 0 != strcmp( "p", $newSection ) ) {
1035 $text .= "<" . $newSection . ">";
1038 $this->mLastSection
= $newSection;
1040 if ( $inBlockElem &&
1041 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1042 $inBlockElem = false;
1048 $text .= $this->closeList( $pref2{$npl-1} );
1051 if ( "" != $this->mLastSection
) {
1052 if ( "p" != $this->mLastSection
) {
1053 $text .= "</" . $this->mLastSection
. ">";
1055 $this->mLastSection
= "";
1061 /* private */ function replaceVariables( $text )
1064 wfProfileIn( "OutputPage:replaceVariables" );
1067 $text = str_replace( "{{CURRENTMONTH}}", $v, $text );
1068 $v = $wgLang->getMonthName( date( "n" ) );
1069 $text = str_replace( "{{CURRENTMONTHNAME}}", $v, $text );
1070 $v = $wgLang->getMonthNameGen( date( "n" ) );
1071 $text = str_replace( "{{CURRENTMONTHNAMEGEN}}", $v, $text );
1073 $text = str_replace( "{{CURRENTDAY}}", $v, $text );
1074 $v = $wgLang->getWeekdayName( date( "w" )+
1 );
1075 $text = str_replace( "{{CURRENTDAYNAME}}", $v, $text );
1077 $text = str_replace( "{{CURRENTYEAR}}", $v, $text );
1078 $v = $wgLang->time( date( "YmdHis" ), false );
1079 $text = str_replace( "{{CURRENTTIME}}", $v, $text );
1081 if ( false !== strstr( $text, "{{NUMBEROFARTICLES}}" ) ) {
1082 $v = wfNumberOfArticles();
1083 $text = str_replace( "{{NUMBEROFARTICLES}}", $v, $text );
1089 /* private */ function removeHTMLtags( $text )
1091 wfProfileIn( "OutputPage::removeHTMLtags" );
1092 $htmlpairs = array( # Tags that must be closed
1093 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1094 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1095 "strike", "strong", "tt", "var", "div", "center",
1096 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1097 "ruby", "rt" , "rb" , "rp"
1099 $htmlsingle = array(
1100 "br", "p", "hr", "li", "dt", "dd"
1102 $htmlnest = array( # Tags that can be nested--??
1103 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1104 "dl", "font", "big", "small", "sub", "sup"
1106 $tabletags = array( # Can only appear inside table
1110 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1111 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1113 $htmlattrs = array( # Allowed attributes--no scripting, etc.
1114 "title", "align", "lang", "dir", "width", "height",
1115 "bgcolor", "clear", /* BR */ "noshade", /* HR */
1116 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
1117 /* FONT */ "type", "start", "value", "compact",
1118 /* For various lists, mostly deprecated but safe */
1119 "summary", "width", "border", "frame", "rules",
1120 "cellspacing", "cellpadding", "valign", "char",
1121 "charoff", "colgroup", "col", "span", "abbr", "axis",
1122 "headers", "scope", "rowspan", "colspan", /* Tables */
1123 "id", "class", "name", "style" /* For CSS */
1126 # Remove HTML comments
1127 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1129 $bits = explode( "<", $text );
1130 $text = array_shift( $bits );
1131 $tagstack = array(); $tablestack = array();
1133 foreach ( $bits as $x ) {
1134 $prev = error_reporting( E_ALL
& ~
( E_NOTICE | E_WARNING
) );
1135 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1137 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1138 error_reporting( $prev );
1141 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1145 if ( ! in_array( $t, $htmlsingle ) &&
1146 ( $ot = array_pop( $tagstack ) ) != $t ) {
1147 array_push( $tagstack, $ot );
1150 if ( $t == "table" ) {
1151 $tagstack = array_pop( $tablestack );
1156 # Keep track for later
1157 if ( in_array( $t, $tabletags ) &&
1158 ! in_array( "table", $tagstack ) ) {
1160 } else if ( in_array( $t, $tagstack ) &&
1161 ! in_array ( $t , $htmlnest ) ) {
1163 } else if ( ! in_array( $t, $htmlsingle ) ) {
1164 if ( $t == "table" ) {
1165 array_push( $tablestack, $tagstack );
1166 $tagstack = array();
1168 array_push( $tagstack, $t );
1170 # Strip non-approved attributes from the tag
1171 $newparams = preg_replace(
1172 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
1173 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
1177 $rest = str_replace( ">", ">", $rest );
1178 $text .= "<$slash$t$newparams$brace$rest";
1182 $text .= "<" . str_replace( ">", ">", $x);
1184 # Close off any remaining tags
1185 while ( $t = array_pop( $tagstack ) ) {
1187 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1193 /* private */ function autoNumberHeadings( $text )
1196 if ( 1 != $wgUser->getOption( "numberheadings" ) ) {
1201 for ( $i = 0; $i < 9; ++
$i ) {
1202 if ( stristr( $text, "<h$i>" ) != false ) {
1204 if ( $n == -1 ) $n = $i;
1207 if ( $j < 2 ) return $text;
1209 $v = array( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
1211 while ( count( spliti( "<h", $text, 2 ) ) == 2 ) {
1212 $a = spliti( "<h", $text, 2 );
1213 $j = substr( $a[1], 0, 1 );
1214 if ( strtolower( $j ) != "r" ) {
1215 $t .= $a[0] . "<h" . $j . ">";
1218 for ( $k = $i; $k <= $j; $k++
) array_push( $b, $v[$k] );
1219 for ( $k = $j+
1; $k < 9; $k++
) $v[$k] = 0;
1220 $t .= implode( ".", $b ) . " ";
1221 $text = substr( $a[1] , 2 ) ;
1222 } else { # <HR> tag, not a heading!
1223 $t .= $a[0] . "<hr>";
1224 $text = substr( $a[1], 2 );
1230 /* private */ function magicISBN( $text )
1234 $a = split( "ISBN ", " $text" );
1235 if ( count ( $a ) < 2 ) return $text;
1236 $text = substr( array_shift( $a ), 1);
1237 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1239 foreach ( $a as $x ) {
1240 $isbn = $blank = "" ;
1241 while ( " " == $x{0} ) {
1243 $x = substr( $x, 1 );
1245 while ( strstr( $valid, $x{0} ) != false ) {
1247 $x = substr( $x, 1 );
1249 $num = str_replace( "-", "", $isbn );
1250 $num = str_replace( " ", "", $num );
1253 $text .= "ISBN $blank$x";
1255 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1256 "Booksources"), "isbn={$num}" ) . "\">ISBN $isbn</a>";
1263 /* private */ function magicRFC( $text )
1268 /* private */ function headElement()
1270 global $wgDocType, $wgUser, $wgLanguageCode, $wgOutputEncoding;
1272 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\">\n";
1274 if ( "" == $this->mHTMLtitle
) {
1275 $this->mHTMLtitle
= $this->mPagetitle
;
1277 $ret .= "<html lang=\"$wgLanguageCode\"><head><title>{$this->mHTMLtitle}</title>\n";
1278 array_push( $this->mMetatags
, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1279 foreach ( $this->mMetatags
as $tag ) {
1280 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1282 $tag[0] = substr( $tag[0], 5 );
1286 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1288 $p = $this->mRobotpolicy
;
1289 if ( "" == $p ) { $p = "index,follow"; }
1290 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1292 if ( count( $this->mKeywords
) > 0 ) {
1293 $ret .= "<meta name=\"keywords\" content=\"" .
1294 implode( ",", $this->mKeywords
) . "\">\n";
1296 foreach ( $this->mLinktags
as $tag ) {
1298 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1299 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1300 $ret .= "href=\"{$tag[2]}\">\n";
1302 $sk = $wgUser->getSkin();
1303 $ret .= $sk->getHeadScripts();
1304 $ret .= $sk->getUserStyles();
1306 $ret .= "</head>\n";