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 $specialChars = array("\\", "$");
292 $escapedChars = array("\\\\", "\\$");
293 for ( $i = 1; $i <= $presecs; ++
$i ) {
294 $text = preg_replace( "/{$unique3}/", str_replace( $specialChars,
295 $escapedChars, $prelist[$i] ), $text, 1 );
298 for ( $i = 1; $i <= $mathsecs; ++
$i ) {
299 $text = preg_replace( "/{$unique2}/", str_replace( $specialChars,
300 $escapedChars, $mathlist[$i] ), $text, 1 );
303 for ( $i = 1; $i <= $nwsecs; ++
$i ) {
304 $text = preg_replace( "/{$unique}/", str_replace( $specialChars,
305 $escapedChars, $nwlist[$i] ), $text, 1 );
307 $this->addHTML( $text );
311 function sendCacheControl() {
312 if( $this->mLastModified
!= "" ) {
313 header( "Cache-Control: private, must-revalidate, max-age=0" );
314 header( "Last-modified: {$this->mLastModified}" );
316 header( "Cache-Control: no-cache" ); # Experimental - see below
317 header( "Pragma: no-cache" );
318 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
320 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
323 # Finally, all the text has been munged and accumulated into
324 # the object, let's actually output it:
328 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
329 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
330 wfProfileIn( "OutputPage::output" );
331 $sk = $wgUser->getSkin();
333 wfProfileIn( "OutputPage::output-headers" );
334 $this->sendCacheControl();
336 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
337 header( "Content-language: {$wgLanguageCode}" );
339 if ( "" != $this->mRedirect
) {
340 header( "Location: {$this->mRedirect}" );
345 $exp = time() +
$wgCookieExpiration;
346 foreach( $this->mCookies
as $name => $val ) {
347 setcookie( $name, $val, $exp, "/" );
351 wfProfileIn( "OutputPage::output-middle" );
353 $this->out( $this->headElement() );
355 $this->out( "\n<body" );
356 $ops = $sk->getBodyOptions();
357 foreach ( $ops as $name => $val ) {
358 $this->out( " $name='$val'" );
361 if ( $wgDebugComments ) {
362 $this->out( "<!-- Wiki debugging output:\n" .
363 $this->mDebugtext
. "-->\n" );
365 $this->out( $sk->beforeContent() );
368 wfProfileIn( "OutputPage::output-bodytext" );
369 $this->out( $this->mBodytext
);
371 wfProfileIn( "OutputPage::output-after" );
372 $this->out( $sk->afterContent() );
375 wfProfileOut(); # A hack - we can't report after here
376 $this->out( $this->reportTime() );
378 $this->out( "\n</body></html>" );
384 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
385 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
388 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
389 if ( false === $outs ) { $outs = $ins; }
394 function setEncodings()
396 global $HTTP_SERVER_VARS, $wgInputEncoding, $wgOutputEncoding;
397 global $wgUser, $wgLang;
399 $wgInputEncoding = strtolower( $wgInputEncoding );
400 $s = $HTTP_SERVER_VARS['HTTP_ACCEPT_CHARSET'];
402 if( $wgUser->getOption( 'altencoding' ) ) {
403 $wgLang->setAltEncoding();
408 $wgOutputEncoding = strtolower( $wgOutputEncoding );
411 $a = explode( ",", $s );
415 foreach ( $a as $s ) {
416 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
428 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
429 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
430 $wgOutputEncoding = strtolower( $bestset );
434 $wgOutputEncoding = $wgInputEncoding;
437 function reportTime()
439 global $wgRequestTime, $wgDebugLogFile, $HTTP_SERVER_VARS;
440 global $wgProfiling, $wgProfileStack, $wgUser;
442 list( $usec, $sec ) = explode( " ", microtime() );
443 $now = (float)$sec +
(float)$usec;
445 list( $usec, $sec ) = explode( " ", $wgRequestTime );
446 $start = (float)$sec +
(float)$usec;
447 $elapsed = $now - $start;
449 if ( "" != $wgDebugLogFile ) {
451 if( $wgProfiling and count( $wgProfileStack ) ) {
453 foreach( $wgProfileStack as $ile ) {
454 # "foo::bar 99 0.12345 1 0.23456 2"
455 if( preg_match( '/^(\S+)\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/', $ile, $m ) ) {
456 $thisstart = (float)$m[3] +
(float)$m[4] - $start;
457 $thisend = (float)$m[5] +
(float)$m[6] - $start;
458 $thiselapsed = $thisend - $thisstart;
459 $thispercent = $thiselapsed / $elapsed * 100.0;
461 $prof .= sprintf( "\tat %04.3f in %04.3f (%2.1f%%) - %s %s\n",
462 $thisstart, $thiselapsed, $thispercent,
463 str_repeat( "*", $m[2] ), $m[1] );
464 $lasttime = $thistime;
465 #$prof .= "\t(^ $ile)\n";
467 $prof .= "\t?broken? $ile\n";
472 if( $forward = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'] )
473 $forward = " forwarded for $forward";
474 if( $client = $HTTP_SERVER_VARS['HTTP_CLIENT_IP'] )
475 $forward .= " client IP $client";
476 if( $from = $HTTP_SERVER_VARS['HTTP_FROM'] )
477 $forward .= " from $from";
479 $forward = "\t(proxied via {$HTTP_SERVER_VARS['REMOTE_ADDR']}{$forward})";
480 if($wgUser->getId() == 0)
482 $log = sprintf( "%s\t%04.3f\t%s\n",
483 gmdate( "YmdHis" ), $elapsed,
484 urldecode( $HTTP_SERVER_VARS['REQUEST_URI'] . $forward ) );
485 error_log( $log . $prof, 3, $wgDebugLogFile );
487 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
492 # Note: these arguments are keys into wfMsg(), not text!
494 function errorpage( $title, $msg )
498 $this->mDebugtext
.= "Original title: " .
499 $wgTitle->getPrefixedText() . "\n";
500 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
501 $this->setPageTitle( wfMsg( $title ) );
502 $this->setRobotpolicy( "noindex,nofollow" );
503 $this->setArticleFlag( false );
505 $this->mBodytext
= "";
506 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
507 $this->returnToMain( false );
513 function sysopRequired()
517 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
518 $this->setPageTitle( wfMsg( "sysoptitle" ) );
519 $this->setRobotpolicy( "noindex,nofollow" );
520 $this->setArticleFlag( false );
521 $this->mBodytext
= "";
523 $sk = $wgUser->getSkin();
524 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
525 $text = str_replace( "$1", $ap, wfMsg( "sysoptext" ) );
526 $this->addHTML( $text );
527 $this->returnToMain();
530 function developerRequired()
534 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
535 $this->setPageTitle( wfMsg( "developertitle" ) );
536 $this->setRobotpolicy( "noindex,nofollow" );
537 $this->setArticleFlag( false );
538 $this->mBodytext
= "";
540 $sk = $wgUser->getSkin();
541 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
542 $text = str_replace( "$1", $ap, wfMsg( "developertext" ) );
543 $this->addHTML( $text );
544 $this->returnToMain();
547 function databaseError( $fname )
549 global $wgUser, $wgCommandLineMode;
551 $this->setPageTitle( wfMsg( "databaseerror" ) );
552 $this->setRobotpolicy( "noindex,nofollow" );
553 $this->setArticleFlag( false );
555 if ( $wgCommandLineMode ) {
556 $msg = wfMsg( "dberrortextcl" );
558 $msg = wfMsg( "dberrortextcl" );
560 $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
561 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
562 $msg = str_replace( "$3", wfLastErrno(), $msg );
563 $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
565 if ( $wgCommandLineMode ) {
569 $sk = $wgUser->getSkin();
570 $shlink = $sk->makeKnownLink( wfMsg( "searchhelppage" ),
571 wfMsg( "searchingwikipedia" ) );
572 $msg = str_replace( "$5", $shlink, $msg );
574 $this->mBodytext
= $msg;
579 function readOnlyPage()
581 global $wgUser, $wgReadOnlyFile;
583 $this->setPageTitle( wfMsg( "readonly" ) );
584 $this->setRobotpolicy( "noindex,nofollow" );
585 $this->setArticleFlag( false );
587 $reason = implode( "", file( $wgReadOnlyFile ) );
588 $text = str_replace( "$1", $reason, wfMsg( "readonlytext" ) );
589 $this->addHTML( $text );
590 $this->returnToMain( false );
593 function fatalError( $message )
595 $this->setPageTitle( wfMsg( "internalerror" ) );
596 $this->setRobotpolicy( "noindex,nofollow" );
597 $this->setArticleFlag( false );
599 $this->mBodytext
= $message;
604 function unexpectedValueError( $name, $val )
606 $msg = str_replace( "$1", $name, wfMsg( "unexpected" ) );
607 $msg = str_replace( "$2", $val, $msg );
608 $this->fatalError( $msg );
611 function fileCopyError( $old, $new )
613 $msg = str_replace( "$1", $old, wfMsg( "filecopyerror" ) );
614 $msg = str_replace( "$2", $new, $msg );
615 $this->fatalError( $msg );
618 function fileRenameError( $old, $new )
620 $msg = str_replace( "$1", $old, wfMsg( "filerenameerror" ) );
621 $msg = str_replace( "$2", $new, $msg );
622 $this->fatalError( $msg );
625 function fileDeleteError( $name )
627 $msg = str_replace( "$1", $name, wfMsg( "filedeleteerror" ) );
628 $this->fatalError( $msg );
631 function fileNotFoundError( $name )
633 $msg = str_replace( "$1", $name, wfMsg( "filenotfound" ) );
634 $this->fatalError( $msg );
637 function returnToMain( $auto = true )
639 global $wgUser, $wgOut, $returnto;
641 $sk = $wgUser->getSkin();
642 if ( "" == $returnto ) {
643 $returnto = wfMsg( "mainpage" );
645 $link = $sk->makeKnownLink( $returnto, "" );
647 $r = str_replace( "$1", $link, wfMsg( "returnto" ) );
649 $wgOut->addMeta( "http:Refresh", "10;url=" .
650 wfLocalUrlE( wfUrlencode( $returnto ) ) );
652 $wgOut->addHTML( "\n<p>$r\n" );
655 # Well, OK, it's actually about 14 passes. But since all the
656 # hard lifting is done inside PHP's regex code, it probably
657 # wouldn't speed things up much to add a real parser.
659 function doWikiPass2( $text, $linestart )
661 global $wgUser, $wgLang, $wgMungeDates;
662 wfProfileIn( "OutputPage::doWikiPass2" );
664 $text = $this->removeHTMLtags( $text );
665 $text = $this->replaceVariables( $text );
667 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
668 $text = str_replace ( "<HR>", "<hr>", $text );
670 $text = $this->doQuotes( $text );
671 $text = $this->doHeadings( $text );
672 $text = $this->doBlockLevels( $text, $linestart );
675 $text = $wgLang->replaceDates( $text );
676 $text = $this->replaceExternalLinks( $text );
677 $text = $this->replaceInternalLinks ( $text );
679 $text = $this->magicISBN( $text );
680 $text = $this->magicRFC( $text );
681 $text = $this->formatHeadings( $text );
683 $sk = $wgUser->getSkin();
684 $text = $sk->transformContent( $text );
690 /* private */ function doQuotes( $text )
692 $text = preg_replace( "/'''(.+)'''/mU", "<strong>\$1</strong>", $text );
693 $text = preg_replace( "/''(.+)''/mU", "<em>\$1</em>", $text );
697 /* private */ function doHeadings( $text )
699 for ( $i = 6; $i >= 1; --$i ) {
700 $h = substr( "======", 0, $i );
701 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
702 "<h{$i}>\\1</h{$i}>\\2", $text );
707 # Note: we have to do external links before the internal ones,
708 # and otherwise take great care in the order of things here, so
709 # that we don't end up interpreting some URLs twice.
711 /* private */ function replaceExternalLinks( $text )
713 wfProfileIn( "OutputPage::replaceExternalLinks" );
714 $text = $this->subReplaceExternalLinks( $text, "http", true );
715 $text = $this->subReplaceExternalLinks( $text, "https", true );
716 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
717 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
718 $text = $this->subReplaceExternalLinks( $text, "news", false );
719 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
724 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
726 global $wgUser, $printable;
727 global $wgAllowExternalImages;
730 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
731 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
733 # this is the list of separators that should be ignored if they
734 # are the last character of an URL but that should be included
735 # if they occur within the URL, e.g. "go to www.foo.com, where .."
736 # in this case, the last comma should not become part of the URL,
737 # but in "www.foo.com/123,2342,32.htm" it should.
739 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
740 $images = "gif|png|jpg|jpeg";
742 # PLEASE NOTE: The curly braces { } are not part of the regex,
743 # they are interpreted as part of the string (used to tell PHP
744 # that the content of the string should be inserted there).
745 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
746 "((?i){$images})([^{$uc}]|$)/";
748 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
749 $sk = $wgUser->getSkin();
751 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
752 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
753 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
755 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
756 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
757 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
759 $s = str_replace( $unique, $protocol, $s );
761 $a = explode( "[{$protocol}:", " " . $s );
762 $s = array_shift( $a );
763 $s = substr( $s, 1 );
765 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
766 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
768 foreach ( $a as $line ) {
769 if ( preg_match( $e1, $line, $m ) ) {
770 $link = "{$protocol}:{$m[1]}";
772 if ( $autonumber ) { $text = "[" . ++
$this->mAutonumber
. "]"; }
773 else { $text = wfEscapeHTML( $link ); }
774 } else if ( preg_match( $e2, $line, $m ) ) {
775 $link = "{$protocol}:{$m[1]}";
779 $s .= "[{$protocol}:" . $line;
782 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
784 $la = $sk->getExternalLinkAttributes( $link, $text );
785 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
791 /* private */ function replaceInternalLinks( $s )
793 global $wgTitle, $wgUser, $wgLang;
794 global $wgLinkCache, $wgInterwikiMagic;
795 global $wgNamespacesWithSubpages, $wgLanguageCode;
796 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
798 wfProfileIn( "$fname-setup" );
799 $tc = Title
::legalChars() . "#";
800 $sk = $wgUser->getSkin();
802 $a = explode( "[[", " " . $s );
803 $s = array_shift( $a );
804 $s = substr( $s, 1 );
806 $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD";
807 $e2 = "/^([{$tc}]+)]](.*)\$/sD";
810 wfProfileIn( "$fname-loop" );
811 foreach ( $a as $line ) {
812 if ( preg_match( $e1, $line, $m ) ) { # page with alternate text
817 } else if ( preg_match( $e2, $line, $m ) ) { # page with normal text
823 else { # Invalid form; output directly
827 if(substr($m[1],0,1)=="/") { # subpage
828 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
829 $m[1]=substr($m[1],1,strlen($m[1])-2);
833 $noslash=substr($m[1],1);
835 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
836 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
839 } # this might be changed for ugliness reasons
841 $link = $noslash; # no subpage allowed, use standard link
843 } else { # no subpage
847 if ( preg_match( "/^((?:i|x|[a-z]{2,3})(?:-[a-z0-9]+)?|[A-Za-z\\x80-\\xff]+):(.*)\$/", $link, $m ) ) {
848 $pre = strtolower( $m[1] );
850 if ( $wgLang->getNsIndex( $pre ) ==
851 Namespace::getImage() ) {
852 $nt = Title
::newFromText( $suf );
853 $name = $nt->getDBkey();
854 if ( "" == $text ) { $text = $nt->GetText(); }
856 $wgLinkCache->addImageLink( $name );
857 $s .= $sk->makeImageLink( $name,
858 wfImageUrl( $name ), $text );
860 } else if ( "media" == $pre ) {
861 $nt = Title
::newFromText( $suf );
862 $name = $nt->getDBkey();
863 if ( "" == $text ) { $text = $nt->GetText(); }
865 $wgLinkCache->addImageLink( $name );
866 $s .= $sk->makeMediaLink( $name,
867 wfImageUrl( $name ), $text );
870 $l = $wgLang->getLanguageName( $pre );
871 if ( "" == $l or !$wgInterwikiMagic or
872 Namespace::isTalk( $wgTitle->getNamespace() ) ) {
873 if ( "" == $text ) { $text = $link; }
874 $s .= $sk->makeLink( $link, $text, "", $trail );
875 } else if ( $pre != $wgLanguageCode ) {
876 array_push( $this->mLanguageLinks
, "$pre:$suf" );
880 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
881 # $link = substr( $link, 2 );
882 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
884 if ( "" == $text ) { $text = $link; }
885 $s .= $sk->makeLink( $link, $text, "", $trail );
893 # Some functions here used by doBlockLevels()
895 /* private */ function closeParagraph()
898 if ( 0 != strcmp( "p", $this->mLastSection
) &&
899 0 != strcmp( "", $this->mLastSection
) ) {
900 $result = "</" . $this->mLastSection
. ">";
902 $this->mLastSection
= "";
905 # getCommon() returns the length of the longest common substring
906 # of both arguments, starting at the beginning of both.
908 /* private */ function getCommon( $st1, $st2 )
910 $fl = strlen( $st1 );
911 $shorter = strlen( $st2 );
912 if ( $fl < $shorter ) { $shorter = $fl; }
914 for ( $i = 0; $i < $shorter; ++
$i ) {
915 if ( $st1{$i} != $st2{$i} ) { break; }
919 # These next three functions open, continue, and close the list
920 # element appropriate to the prefix character passed into them.
922 /* private */ function openList( $char )
924 $result = $this->closeParagraph();
926 if ( "*" == $char ) { $result .= "<ul><li>"; }
927 else if ( "#" == $char ) { $result .= "<ol><li>"; }
928 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
929 else if ( ";" == $char ) {
930 $result .= "<dl><dt>";
931 $this->mDTopen
= true;
933 else { $result = "<!-- ERR 1 -->"; }
938 /* private */ function nextItem( $char )
940 if ( "*" == $char ||
"#" == $char ) { return "</li><li>"; }
941 else if ( ":" == $char ||
";" == $char ) {
943 if ( $this->mDTopen
) { $close = "</dt>"; }
944 if ( ";" == $char ) {
945 $this->mDTopen
= true;
946 return $close . "<dt>";
948 $this->mDTopen
= false;
949 return $close . "<dd>";
952 return "<!-- ERR 2 -->";
955 /* private */function closeList( $char )
957 if ( "*" == $char ) { return "</li></ul>"; }
958 else if ( "#" == $char ) { return "</li></ol>"; }
959 else if ( ":" == $char ) {
960 if ( $this->mDTopen
) {
961 $this->mDTopen
= false;
967 return "<!-- ERR 3 -->";
970 /* private */ function doBlockLevels( $text, $linestart )
972 wfProfileIn( "OutputPage::doBlockLevels" );
973 # Parsing through the text line by line. The main thing
974 # happening here is handling of block-level elements p, pre,
975 # and making lists from lines starting with * # : etc.
977 $a = explode( "\n", $text );
978 $text = $lastPref = "";
979 $this->mDTopen
= $inBlockElem = false;
981 if ( ! $linestart ) { $text .= array_shift( $a ); }
982 foreach ( $a as $t ) {
983 if ( "" != $text ) { $text .= "\n"; }
986 $opl = strlen( $lastPref );
987 $npl = strspn( $t, "*#:;" );
988 $pref = substr( $t, 0, $npl );
989 $pref2 = str_replace( ";", ":", $pref );
990 $t = substr( $t, $npl );
992 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
993 $text .= $this->nextItem( substr( $pref, -1 ) );
995 if ( ";" == substr( $pref, -1 ) ) {
996 $cpos = strpos( $t, ":" );
997 if ( ! ( false === $cpos ) ) {
998 $term = substr( $t, 0, $cpos );
999 $text .= $term . $this->nextItem( ":" );
1000 $t = substr( $t, $cpos +
1 );
1003 } else if (0 != $npl ||
0 != $opl) {
1004 $cpl = $this->getCommon( $pref, $lastPref );
1006 while ( $cpl < $opl ) {
1007 $text .= $this->closeList( $lastPref{$opl-1} );
1010 if ( $npl <= $cpl && $cpl > 0 ) {
1011 $text .= $this->nextItem( $pref{$cpl-1} );
1013 while ( $npl > $cpl ) {
1014 $char = substr( $pref, $cpl, 1 );
1015 $text .= $this->openList( $char );
1017 if ( ";" == $char ) {
1018 $cpos = strpos( $t, ":" );
1019 if ( ! ( false === $cpos ) ) {
1020 $term = substr( $t, 0, $cpos );
1021 $text .= $term . $this->nextItem( ":" );
1022 $t = substr( $t, $cpos +
1 );
1029 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1031 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1032 $text .= $this->closeParagraph();
1033 $inBlockElem = true;
1035 if ( ! $inBlockElem ) {
1036 if ( " " == $t{0} ) {
1037 $newSection = "pre";
1038 # $t = wfEscapeHTML( $t );
1040 else { $newSection = "p"; }
1042 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1043 $text .= $this->closeParagraph();
1044 $text .= "<" . $newSection . ">";
1045 } else if ( 0 != strcmp( $this->mLastSection
,
1047 $text .= $this->closeParagraph();
1048 if ( 0 != strcmp( "p", $newSection ) ) {
1049 $text .= "<" . $newSection . ">";
1052 $this->mLastSection
= $newSection;
1054 if ( $inBlockElem &&
1055 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1056 $inBlockElem = false;
1062 $text .= $this->closeList( $pref2{$npl-1} );
1065 if ( "" != $this->mLastSection
) {
1066 if ( "p" != $this->mLastSection
) {
1067 $text .= "</" . $this->mLastSection
. ">";
1069 $this->mLastSection
= "";
1075 /* private */ function replaceVariables( $text )
1078 wfProfileIn( "OutputPage:replaceVariables" );
1080 /* As with sigs, use server's local time --
1081 ensure this is appropriate for your audience! */
1083 $text = str_replace( "{{CURRENTMONTH}}", $v, $text );
1084 $v = $wgLang->getMonthName( date( "n" ) );
1085 $text = str_replace( "{{CURRENTMONTHNAME}}", $v, $text );
1086 $v = $wgLang->getMonthNameGen( date( "n" ) );
1087 $text = str_replace( "{{CURRENTMONTHNAMEGEN}}", $v, $text );
1089 $text = str_replace( "{{CURRENTDAY}}", $v, $text );
1090 $v = $wgLang->getWeekdayName( date( "w" )+
1 );
1091 $text = str_replace( "{{CURRENTDAYNAME}}", $v, $text );
1093 $text = str_replace( "{{CURRENTYEAR}}", $v, $text );
1094 $v = $wgLang->time( wfTimestampNow(), false );
1095 $text = str_replace( "{{CURRENTTIME}}", $v, $text );
1097 if ( false !== strstr( $text, "{{NUMBEROFARTICLES}}" ) ) {
1098 $v = wfNumberOfArticles();
1099 $text = str_replace( "{{NUMBEROFARTICLES}}", $v, $text );
1105 /* private */ function removeHTMLtags( $text )
1107 wfProfileIn( "OutputPage::removeHTMLtags" );
1108 $htmlpairs = array( # Tags that must be closed
1109 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1110 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1111 "strike", "strong", "tt", "var", "div", "center",
1112 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1113 "ruby", "rt" , "rb" , "rp"
1115 $htmlsingle = array(
1116 "br", "p", "hr", "li", "dt", "dd"
1118 $htmlnest = array( # Tags that can be nested--??
1119 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1120 "dl", "font", "big", "small", "sub", "sup"
1122 $tabletags = array( # Can only appear inside table
1126 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1127 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1129 $htmlattrs = array( # Allowed attributes--no scripting, etc.
1130 "title", "align", "lang", "dir", "width", "height",
1131 "bgcolor", "clear", /* BR */ "noshade", /* HR */
1132 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
1133 /* FONT */ "type", "start", "value", "compact",
1134 /* For various lists, mostly deprecated but safe */
1135 "summary", "width", "border", "frame", "rules",
1136 "cellspacing", "cellpadding", "valign", "char",
1137 "charoff", "colgroup", "col", "span", "abbr", "axis",
1138 "headers", "scope", "rowspan", "colspan", /* Tables */
1139 "id", "class", "name", "style" /* For CSS */
1142 # Remove HTML comments
1143 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1145 $bits = explode( "<", $text );
1146 $text = array_shift( $bits );
1147 $tagstack = array(); $tablestack = array();
1149 foreach ( $bits as $x ) {
1150 $prev = error_reporting( E_ALL
& ~
( E_NOTICE | E_WARNING
) );
1151 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1153 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1154 error_reporting( $prev );
1157 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1161 if ( ! in_array( $t, $htmlsingle ) &&
1162 ( $ot = array_pop( $tagstack ) ) != $t ) {
1163 array_push( $tagstack, $ot );
1166 if ( $t == "table" ) {
1167 $tagstack = array_pop( $tablestack );
1172 # Keep track for later
1173 if ( in_array( $t, $tabletags ) &&
1174 ! in_array( "table", $tagstack ) ) {
1176 } else if ( in_array( $t, $tagstack ) &&
1177 ! in_array ( $t , $htmlnest ) ) {
1179 } else if ( ! in_array( $t, $htmlsingle ) ) {
1180 if ( $t == "table" ) {
1181 array_push( $tablestack, $tagstack );
1182 $tagstack = array();
1184 array_push( $tagstack, $t );
1186 # Strip non-approved attributes from the tag
1187 $newparams = preg_replace(
1188 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
1189 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
1193 $rest = str_replace( ">", ">", $rest );
1194 $text .= "<$slash$t$newparams$brace$rest";
1198 $text .= "<" . str_replace( ">", ">", $x);
1200 # Close off any remaining tags
1201 while ( $t = array_pop( $tagstack ) ) {
1203 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1212 * This function accomplishes several tasks:
1213 * 1) Auto-number headings if that option is enabled
1214 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1215 * 3) Add a Table of contents on the top for users who have enabled the option
1216 * 4) Auto-anchor headings
1218 * It loops through all headlines, collects the necessary data, then splits up the
1219 * string and re-inserts the newly formatted headlines.
1222 /* private */ function formatHeadings( $text )
1224 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1225 $nh=$wgUser->getOption( "numberheadings" );
1226 $st=$wgUser->getOption( "showtoc" );
1227 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1228 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1230 $sk=$wgUser->getSkin();
1231 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1235 foreach($matches[3] as $headline) {
1236 if($level) { $prevlevel=$level;}
1237 $level=$matches[1][$c];
1238 if(($nh||
$st) && $prevlevel && $level>$prevlevel) {
1240 $h[$level]=0; // reset when we enter a new level
1241 $toc.=$sk->tocIndent($level-$prevlevel);
1242 $toclevel+
=$level-$prevlevel;
1245 if(($nh||
$st) && $level<$prevlevel) {
1246 $h[$level+
1]=0; // reset when we step back a level
1247 $toc.=$sk->tocUnindent($prevlevel-$level);
1248 $toclevel-=$prevlevel-$level;
1251 $h[$level]++
; // count number of headlines for each level
1254 for($i=1;$i<=$level;$i++
) {
1256 if($dot) {$numbering.=".";}
1264 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1265 $tocline=$canonized_headline;
1266 $canonized_headline=str_replace('"',"",$canonized_headline);
1267 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1268 $refer[$c]=$canonized_headline;
1269 $refers[$canonized_headline]++
; // count how many in assoc. array so we can track dupes in anchors
1270 $refcount[$c]=$refers[$canonized_headline];
1272 $tocline=$numbering ." ". $tocline;
1274 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1277 $anchor=$canonized_headline;
1278 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1280 $toc.=$sk->tocLine($anchor,$tocline,$toclevel);
1282 if($es && !isset($wpPreview)) {
1283 $head[$c].=$sk->editSectionLink($c+
1);
1285 $head[$c].="<H".$level.$matches[2][$c]
1286 ."<a name=\"".$anchor."\">"
1297 $toc.=$sk->tocUnindent($toclevel);
1298 $toc=$sk->tocTable($toc);
1301 // split up and insert constructed headlines
1303 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1307 foreach($blocks as $block) {
1308 if($es && !isset($wpPreview) && $c>0 && $i==0) {
1309 $full.=$sk->editSectionLink(0);
1312 if($st && $toclines>3 && !$i) {
1313 $full="<a name=\"top\"></a>".$full.$toc;
1322 /* private */ function magicISBN( $text )
1326 $a = split( "ISBN ", " $text" );
1327 if ( count ( $a ) < 2 ) return $text;
1328 $text = substr( array_shift( $a ), 1);
1329 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1331 foreach ( $a as $x ) {
1332 $isbn = $blank = "" ;
1333 while ( " " == $x{0} ) {
1335 $x = substr( $x, 1 );
1337 while ( strstr( $valid, $x{0} ) != false ) {
1339 $x = substr( $x, 1 );
1341 $num = str_replace( "-", "", $isbn );
1342 $num = str_replace( " ", "", $num );
1345 $text .= "ISBN $blank$x";
1347 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1348 "Booksources"), "isbn={$num}" ) . "\" CLASS=\"internal\">ISBN $isbn</a>";
1355 /* private */ function magicRFC( $text )
1360 /* private */ function headElement()
1362 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding;
1364 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1366 if ( "" == $this->mHTMLtitle
) {
1367 $this->mHTMLtitle
= $this->mPagetitle
;
1369 $ret .= "<html lang=\"$wgLanguageCode\"><head><title>{$this->mHTMLtitle}</title>\n";
1370 array_push( $this->mMetatags
, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1371 foreach ( $this->mMetatags
as $tag ) {
1372 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1374 $tag[0] = substr( $tag[0], 5 );
1378 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1380 $p = $this->mRobotpolicy
;
1381 if ( "" == $p ) { $p = "index,follow"; }
1382 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1384 if ( count( $this->mKeywords
) > 0 ) {
1385 $ret .= "<meta name=\"keywords\" content=\"" .
1386 implode( ",", $this->mKeywords
) . "\">\n";
1388 foreach ( $this->mLinktags
as $tag ) {
1390 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1391 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1392 $ret .= "href=\"{$tag[2]}\">\n";
1394 $sk = $wgUser->getSkin();
1395 $ret .= $sk->getHeadScripts();
1396 $ret .= $sk->getUserStyles();
1398 $ret .= "</head>\n";