URL-escape the main page link on the logo
[mediawiki.git] / includes / OutputPage.php
blob3021fa6c5925e157d8e1961de2d6d3a0e9d49060
1 <?
2 # See design.doc
4 if($wgUseTeX) include_once( "Math.php" );
6 class OutputPage {
7 var $mHeaders, $mCookies, $mMetatags, $mKeywords;
8 var $mLinktags, $mPagetitle, $mBodytext, $mDebugtext;
9 var $mHTMLtitle, $mRobotpolicy, $mIsarticle, $mPrintable;
10 var $mSubtitle, $mRedirect, $mAutonumber, $mHeadtext;
11 var $mLastModified, $mCategoryLinks;
13 var $mDTopen, $mLastSection; # Used for processing DL, PRE
14 var $mLanguageLinks, $mSupressQuickbar;
16 function OutputPage()
18 $this->mHeaders = $this->mCookies = $this->mMetatags =
19 $this->mKeywords = $this->mLinktags = array();
20 $this->mHTMLtitle = $this->mPagetitle = $this->mBodytext =
21 $this->mLastSection = $this->mRedirect = $this->mLastModified =
22 $this->mSubtitle = $this->mDebugtext = $this->mRobotpolicy = "";
23 $this->mIsarticle = $this->mPrintable = true;
24 $this->mSupressQuickbar = $this->mDTopen = $this->mPrintable = false;
25 $this->mLanguageLinks = array();
26 $this->mCategoryLinks = array() ;
27 $this->mAutonumber = 0;
30 function addHeader( $name, $val ) { array_push( $this->mHeaders, "$name: $val" ) ; }
31 function addCookie( $name, $val ) { array_push( $this->mCookies, array( $name, $val ) ); }
32 function redirect( $url ) { $this->mRedirect = $url; }
34 # To add an http-equiv meta tag, precede the name with "http:"
35 function addMeta( $name, $val ) { array_push( $this->mMetatags, array( $name, $val ) ); }
36 function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
37 function addLink( $rel, $rev, $target ) { array_push( $this->mLinktags, array( $rel, $rev, $target ) ); }
39 function checkLastModified ( $timestamp )
41 global $wgLang, $wgCachePages, $wgUser;
42 if( !$wgCachePages ) {
43 wfDebug( "CACHE DISABLED\n", false );
44 return;
46 if( preg_match( '/MSIE ([1-4]|5\.0)/', $_SERVER["HTTP_USER_AGENT"] ) ) {
47 # IE 5.0 has probs with our caching
48 wfDebug( "-- bad client, not caching\n", false );
49 return;
51 if( $wgUser->getOption( "nocache" ) ) {
52 wfDebug( "USER DISABLED CACHE\n", false );
53 return;
56 $lastmod = gmdate( "D, j M Y H:i:s", wfTimestamp2Unix(
57 max( $timestamp, $wgUser->mTouched ) ) ) . " GMT";
59 if( $_SERVER["HTTP_IF_MODIFIED_SINCE"] != "" ) {
60 # IE sends sizes after the date for compressed pages:
61 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
62 # this breaks strtotime().
63 $modsince = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
64 $ismodsince = wfUnix2Timestamp( strtotime( $modsince ) );
65 wfDebug( "-- client send If-Modified-Since: " . $modsince . "\n", false );
66 wfDebug( "-- we might send Last-Modified : $lastmod\n", false );
68 if( ($ismodsince >= $timestamp ) and $wgUser->validateCache( $ismodsince ) ) {
69 # Make sure you're in a place you can leave when you call us!
70 header( "HTTP/1.0 304 Not Modified" );
71 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
72 header( "Cache-Control: private, must-revalidate, max-age=0" );
73 header( "Last-Modified: {$lastmod}" );
74 wfDebug( "CACHED client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
75 exit;
76 } else {
77 wfDebug( "READY client: $ismodsince ; user: $wgUser->mTouched ; page: $timestamp\n", false );
78 $this->mLastModified = $lastmod;
80 } else {
81 wfDebug( "We're confused.\n", false );
82 $this->mLastModified = $lastmod;
86 function setRobotpolicy( $str ) { $this->mRobotpolicy = $str; }
87 function setHTMLtitle( $name ) { $this->mHTMLtitle = $name; }
88 function setPageTitle( $name ) { $this->mPagetitle = $name; }
89 function getPageTitle() { return $this->mPagetitle; }
90 function setSubtitle( $str ) { $this->mSubtitle = $str; }
91 function getSubtitle() { return $this->mSubtitle; }
92 function setArticleFlag( $v ) { $this->mIsarticle = $v; }
93 function isArticle() { return $this->mIsarticle; }
94 function setPrintable() { $this->mPrintable = true; }
95 function isPrintable() { return $this->mPrintable; }
97 function getLanguageLinks() {
98 global $wgUseNewInterlanguage, $wgTitle, $wgLanguageCode;
99 global $wgDBconnection, $wgDBname, $wgDBintlname;
101 if ( ! $wgUseNewInterlanguage )
102 return $this->mLanguageLinks;
104 mysql_select_db( $wgDBintlname, $wgDBconnection ) or die(
105 htmlspecialchars(mysql_error()) );
107 $list = array();
108 $sql = "SELECT * FROM ilinks WHERE lang_from=\"" .
109 "{$wgLanguageCode}\" AND title_from=\"" . $wgTitle->getDBkey() . "\"";
110 $res = mysql_query( $sql, $wgDBconnection );
112 while ( $q = mysql_fetch_object ( $res ) ) {
113 $list[] = $q->lang_to . ":" . $q->title_to;
115 mysql_free_result( $res );
116 mysql_select_db( $wgDBname, $wgDBconnection ) or die(
117 htmlspecialchars(mysql_error()) );
119 return $list;
122 function supressQuickbar() { $this->mSupressQuickbar = true; }
123 function isQuickbarSupressed() { return $this->mSupressQuickbar; }
125 function addHTML( $text ) { $this->mBodytext .= $text; }
126 function addHeadtext( $text ) { $this->mHeadtext .= $text; }
127 function debug( $text ) { $this->mDebugtext .= $text; }
129 # First pass--just handle <nowiki> sections, pass the rest off
130 # to doWikiPass2() which does all the real work.
133 function addWikiText( $text, $linestart = true )
135 global $wgUseTeX;
136 wfProfileIn( "OutputPage::addWikiText" );
137 $unique = "3iyZiyA7iMwg5rhxP0Dcc9oTnj8qD1jm1Sfv4";
138 $unique2 = "4LIQ9nXtiYFPCSfitVwDw7EYwQlL4GeeQ7qSO";
139 $unique3 = "fPaA8gDfdLBqzj68Yjg9Hil3qEF8JGO0uszIp";
140 $nwlist = array();
141 $nwsecs = 0;
142 $mathlist = array();
143 $mathsecs = 0;
144 $prelist = array ();
145 $presecs = 0;
146 $stripped = "";
147 $stripped2 = "";
148 $stripped3 = "";
150 while ( "" != $text ) {
151 $p = preg_split( "/<\\s*nowiki\\s*>/i", $text, 2 );
152 $stripped .= $p[0];
153 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $text = ""; }
154 else {
155 $q = preg_split( "/<\\/\\s*nowiki\\s*>/i", $p[1], 2 );
156 ++$nwsecs;
157 $nwlist[$nwsecs] = wfEscapeHTMLTagsOnly($q[0]);
158 $stripped .= $unique;
159 $text = $q[1];
163 if( $wgUseTeX ) {
164 while ( "" != $stripped ) {
165 $p = preg_split( "/<\\s*math\\s*>/i", $stripped, 2 );
166 $stripped2 .= $p[0];
167 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped = ""; }
168 else {
169 $q = preg_split( "/<\\/\\s*math\\s*>/i", $p[1], 2 );
170 ++$mathsecs;
171 $mathlist[$mathsecs] = renderMath($q[0]);
172 $stripped2 .= $unique2;
173 $stripped = $q[1];
176 } else {
177 $stripped2 = $stripped;
180 while ( "" != $stripped2 ) {
181 $p = preg_split( "/<\\s*pre\\s*>/i", $stripped2, 2 );
182 $stripped3 .= $p[0];
183 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) { $stripped2 = ""; }
184 else {
185 $q = preg_split( "/<\\/\\s*pre\\s*>/i", $p[1], 2 );
186 ++$presecs;
187 $prelist[$presecs] = "<pre>". wfEscapeHTMLTagsOnly($q[0]). "</pre>";
188 $stripped3 .= $unique3;
189 $stripped2 = $q[1];
193 $text = $this->doWikiPass2( $stripped3, $linestart );
195 $specialChars = array("\\", "$");
196 $escapedChars = array("\\\\", "\\$");
197 for ( $i = 1; $i <= $presecs; ++$i ) {
198 $text = preg_replace( "/{$unique3}/", str_replace( $specialChars,
199 $escapedChars, $prelist[$i] ), $text, 1 );
202 for ( $i = 1; $i <= $mathsecs; ++$i ) {
203 $text = preg_replace( "/{$unique2}/", str_replace( $specialChars,
204 $escapedChars, $mathlist[$i] ), $text, 1 );
207 for ( $i = 1; $i <= $nwsecs; ++$i ) {
208 $text = preg_replace( "/{$unique}/", str_replace( $specialChars,
209 $escapedChars, $nwlist[$i] ), $text, 1 );
211 $this->addHTML( $text );
212 wfProfileOut();
215 function sendCacheControl() {
216 global $wgUseGzip;
217 if( $this->mLastModified != "" ) {
218 wfDebug( "** private caching; {$this->mLastModified} **\n", false );
219 header( "Cache-Control: private, must-revalidate, max-age=0" );
220 header( "Last-modified: {$this->mLastModified}" );
221 if( $wgUseGzip ) {
222 # We should put in Accept-Encoding, but IE chokes on anything but
223 # User-Agent in a Vary: header (at least through 6.0)
224 header( "Vary: User-Agent" );
226 } else {
227 wfDebug( "** no caching **\n", false );
228 header( "Cache-Control: no-cache" ); # Experimental - see below
229 header( "Pragma: no-cache" );
230 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
232 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
235 # Finally, all the text has been munged and accumulated into
236 # the object, let's actually output it:
238 function output()
240 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
241 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
242 wfProfileIn( "OutputPage::output" );
243 $sk = $wgUser->getSkin();
245 wfProfileIn( "OutputPage::output-headers" );
246 $this->sendCacheControl();
248 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
249 header( "Content-language: {$wgLanguageCode}" );
251 if ( "" != $this->mRedirect ) {
252 header( "Location: {$this->mRedirect}" );
253 wfProfileOut();
254 return;
257 $exp = time() + $wgCookieExpiration;
258 foreach( $this->mCookies as $name => $val ) {
259 setcookie( $name, $val, $exp, "/" );
261 wfProfileOut();
263 wfProfileIn( "OutputPage::output-middle" );
264 $sk->initPage();
265 $this->out( $this->headElement() );
267 $this->out( "\n<body" );
268 $ops = $sk->getBodyOptions();
269 foreach ( $ops as $name => $val ) {
270 $this->out( " $name='$val'" );
272 $this->out( ">\n" );
273 if ( $wgDebugComments ) {
274 $this->out( "<!-- Wiki debugging output:\n" .
275 $this->mDebugtext . "-->\n" );
277 $this->out( $sk->beforeContent() );
278 wfProfileOut();
280 wfProfileIn( "OutputPage::output-bodytext" );
281 $this->out( $this->mBodytext );
282 wfProfileOut();
283 wfProfileIn( "OutputPage::output-after" );
284 $this->out( $sk->afterContent() );
285 wfProfileOut();
287 wfProfileOut(); # A hack - we can't report after here
288 $this->out( $this->reportTime() );
290 $this->out( "\n</body></html>" );
291 flush();
294 function out( $ins )
296 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
297 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
298 $outs = $ins;
299 } else {
300 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
301 if ( false === $outs ) { $outs = $ins; }
303 print $outs;
306 function setEncodings()
308 global $HTTP_SERVER_VARS, $wgInputEncoding, $wgOutputEncoding;
309 global $wgUser, $wgLang;
311 $wgInputEncoding = strtolower( $wgInputEncoding );
312 $s = $HTTP_SERVER_VARS['HTTP_ACCEPT_CHARSET'];
314 if( $wgUser->getOption( 'altencoding' ) ) {
315 $wgLang->setAltEncoding();
316 return;
319 if ( "" == $s ) {
320 $wgOutputEncoding = strtolower( $wgOutputEncoding );
321 return;
323 $a = explode( ",", $s );
324 $best = 0.0;
325 $bestset = "*";
327 foreach ( $a as $s ) {
328 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
329 $set = $m[1];
330 $q = (float)($m[2]);
331 } else {
332 $set = $s;
333 $q = 1.0;
335 if ( $q > $best ) {
336 $bestset = $set;
337 $best = $q;
340 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
341 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
342 $wgOutputEncoding = strtolower( $bestset );
344 # Disable for now
346 $wgOutputEncoding = $wgInputEncoding;
349 function reportTime()
351 global $wgRequestTime, $wgDebugLogFile, $HTTP_SERVER_VARS;
352 global $wgProfiling, $wgProfileStack, $wgUser;
354 list( $usec, $sec ) = explode( " ", microtime() );
355 $now = (float)$sec + (float)$usec;
357 list( $usec, $sec ) = explode( " ", $wgRequestTime );
358 $start = (float)$sec + (float)$usec;
359 $elapsed = $now - $start;
361 if ( "" != $wgDebugLogFile ) {
362 $prof = "";
363 if( $wgProfiling and count( $wgProfileStack ) ) {
364 $lasttime = $start;
365 foreach( $wgProfileStack as $ile ) {
366 # "foo::bar 99 0.12345 1 0.23456 2"
367 if( preg_match( '/^(\S+)\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/', $ile, $m ) ) {
368 $thisstart = (float)$m[3] + (float)$m[4] - $start;
369 $thisend = (float)$m[5] + (float)$m[6] - $start;
370 $thiselapsed = $thisend - $thisstart;
371 $thispercent = $thiselapsed / $elapsed * 100.0;
373 $prof .= sprintf( "\tat %04.3f in %04.3f (%2.1f%%) - %s %s\n",
374 $thisstart, $thiselapsed, $thispercent,
375 str_repeat( "*", $m[2] ), $m[1] );
376 $lasttime = $thistime;
377 #$prof .= "\t(^ $ile)\n";
378 } else {
379 $prof .= "\t?broken? $ile\n";
384 if( $forward = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'] )
385 $forward = " forwarded for $forward";
386 if( $client = $HTTP_SERVER_VARS['HTTP_CLIENT_IP'] )
387 $forward .= " client IP $client";
388 if( $from = $HTTP_SERVER_VARS['HTTP_FROM'] )
389 $forward .= " from $from";
390 if( $forward )
391 $forward = "\t(proxied via {$HTTP_SERVER_VARS['REMOTE_ADDR']}{$forward})";
392 if($wgUser->getId() == 0)
393 $forward .= " anon";
394 $log = sprintf( "%s\t%04.3f\t%s\n",
395 gmdate( "YmdHis" ), $elapsed,
396 urldecode( $HTTP_SERVER_VARS['REQUEST_URI'] . $forward ) );
397 error_log( $log . $prof, 3, $wgDebugLogFile );
399 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
400 $elapsed );
401 return $com;
404 # Note: these arguments are keys into wfMsg(), not text!
406 function errorpage( $title, $msg )
408 global $wgTitle;
410 $this->mDebugtext .= "Original title: " .
411 $wgTitle->getPrefixedText() . "\n";
412 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
413 $this->setPageTitle( wfMsg( $title ) );
414 $this->setRobotpolicy( "noindex,nofollow" );
415 $this->setArticleFlag( false );
417 $this->mBodytext = "";
418 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
419 $this->returnToMain( false );
421 $this->output();
422 exit;
425 function sysopRequired()
427 global $wgUser;
429 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
430 $this->setPageTitle( wfMsg( "sysoptitle" ) );
431 $this->setRobotpolicy( "noindex,nofollow" );
432 $this->setArticleFlag( false );
433 $this->mBodytext = "";
435 $sk = $wgUser->getSkin();
436 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
437 $text = str_replace( "$1", $ap, wfMsg( "sysoptext" ) );
438 $this->addHTML( $text );
439 $this->returnToMain();
442 function developerRequired()
444 global $wgUser;
446 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
447 $this->setPageTitle( wfMsg( "developertitle" ) );
448 $this->setRobotpolicy( "noindex,nofollow" );
449 $this->setArticleFlag( false );
450 $this->mBodytext = "";
452 $sk = $wgUser->getSkin();
453 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
454 $text = str_replace( "$1", $ap, wfMsg( "developertext" ) );
455 $this->addHTML( $text );
456 $this->returnToMain();
459 function databaseError( $fname )
461 global $wgUser, $wgCommandLineMode;
463 $this->setPageTitle( wfMsg( "databaseerror" ) );
464 $this->setRobotpolicy( "noindex,nofollow" );
465 $this->setArticleFlag( false );
467 if ( $wgCommandLineMode ) {
468 $msg = wfMsg( "dberrortextcl" );
469 } else {
470 $msg = wfMsg( "dberrortextcl" );
472 $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
473 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
474 $msg = str_replace( "$3", wfLastErrno(), $msg );
475 $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
477 if ( $wgCommandLineMode ) {
478 print $msg;
479 exit();
481 $sk = $wgUser->getSkin();
482 $shlink = $sk->makeKnownLink( wfMsg( "searchhelppage" ),
483 wfMsg( "searchingwikipedia" ) );
484 $msg = str_replace( "$5", $shlink, $msg );
486 $this->mBodytext = $msg;
487 $this->output();
488 exit();
491 function readOnlyPage()
493 global $wgUser, $wgReadOnlyFile;
495 $this->setPageTitle( wfMsg( "readonly" ) );
496 $this->setRobotpolicy( "noindex,nofollow" );
497 $this->setArticleFlag( false );
499 $reason = implode( "", file( $wgReadOnlyFile ) );
500 $text = str_replace( "$1", $reason, wfMsg( "readonlytext" ) );
501 $this->addHTML( $text );
502 $this->returnToMain( false );
505 function fatalError( $message )
507 $this->setPageTitle( wfMsg( "internalerror" ) );
508 $this->setRobotpolicy( "noindex,nofollow" );
509 $this->setArticleFlag( false );
511 $this->mBodytext = $message;
512 $this->output();
513 exit;
516 function unexpectedValueError( $name, $val )
518 $msg = str_replace( "$1", $name, wfMsg( "unexpected" ) );
519 $msg = str_replace( "$2", $val, $msg );
520 $this->fatalError( $msg );
523 function fileCopyError( $old, $new )
525 $msg = str_replace( "$1", $old, wfMsg( "filecopyerror" ) );
526 $msg = str_replace( "$2", $new, $msg );
527 $this->fatalError( $msg );
530 function fileRenameError( $old, $new )
532 $msg = str_replace( "$1", $old, wfMsg( "filerenameerror" ) );
533 $msg = str_replace( "$2", $new, $msg );
534 $this->fatalError( $msg );
537 function fileDeleteError( $name )
539 $msg = str_replace( "$1", $name, wfMsg( "filedeleteerror" ) );
540 $this->fatalError( $msg );
543 function fileNotFoundError( $name )
545 $msg = str_replace( "$1", $name, wfMsg( "filenotfound" ) );
546 $this->fatalError( $msg );
549 function returnToMain( $auto = true )
551 global $wgUser, $wgOut, $returnto;
553 $sk = $wgUser->getSkin();
554 if ( "" == $returnto ) {
555 $returnto = wfMsg( "mainpage" );
557 $link = $sk->makeKnownLink( $returnto, "" );
559 $r = str_replace( "$1", $link, wfMsg( "returnto" ) );
560 if ( $auto ) {
561 $wgOut->addMeta( "http:Refresh", "10;url=" .
562 wfLocalUrlE( wfUrlencode( $returnto ) ) );
564 $wgOut->addHTML( "\n<p>$r\n" );
568 function categoryMagic ()
570 global $wgTitle , $wgUseCategoryMagic ;
571 if ( !isset ( $wgUseCategoryMagic ) || !$wgUseCategoryMagic ) return ;
572 $id = $wgTitle->getArticleID() ;
573 $cat = ucfirst ( wfMsg ( "category" ) ) ;
574 $ti = $wgTitle->getText() ;
575 $ti = explode ( ":" , $ti , 2 ) ;
576 if ( $cat != $ti[0] ) return "" ;
577 $r = "<br break=all>\n" ;
579 $articles = array() ;
580 $parents = array () ;
581 $children = array() ;
584 global $wgUser ;
585 $sk = $wgUser->getSkin() ;
586 $sql = "SELECT l_from FROM links WHERE l_to={$id}" ;
587 $res = wfQuery ( $sql ) ;
588 while ( $x = wfFetchObject ( $res ) )
590 # $t = new Title ;
591 # $t->newFromDBkey ( $x->l_from ) ;
592 # $t = $t->getText() ;
593 $t = $x->l_from ;
594 $y = explode ( ":" , $t , 2 ) ;
595 if ( count ( $y ) == 2 && $y[0] == $cat )
597 array_push ( $children , $sk->makeLink ( $t , $y[1] ) ) ;
599 else array_push ( $articles , $sk->makeLink ( $t ) ) ;
601 wfFreeResult ( $res ) ;
603 # Children
604 if ( count ( $children ) > 0 )
606 asort ( $children ) ;
607 $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
608 $r .= implode ( ", " , $children ) ;
611 # Articles
612 if ( count ( $articles ) > 0 )
614 asort ( $articles ) ;
615 $h = str_replace ( "$1" , $ti[1] , wfMsg("category_header") ) ;
616 $r .= "<h2>{$h}</h2>\n" ;
617 $r .= implode ( ", " , $articles ) ;
621 return $r ;
625 # Well, OK, it's actually about 14 passes. But since all the
626 # hard lifting is done inside PHP's regex code, it probably
627 # wouldn't speed things up much to add a real parser.
629 function doWikiPass2( $text, $linestart )
631 global $wgUser, $wgLang, $wgUseDynamicDates;
632 wfProfileIn( "OutputPage::doWikiPass2" );
634 $text = $this->removeHTMLtags( $text );
635 $text = $this->replaceVariables( $text );
637 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
638 $text = str_replace ( "<HR>", "<hr>", $text );
640 $text = $this->doAllQuotes( $text );
641 $text = $this->doHeadings( $text );
642 $text = $this->doBlockLevels( $text, $linestart );
644 if($wgUseDynamicDates) {
645 $text = $wgLang->replaceDates( $text );
648 $text = $this->replaceExternalLinks( $text );
649 $text = $this->replaceInternalLinks ( $text );
651 $text = $this->magicISBN( $text );
652 $text = $this->magicRFC( $text );
653 $text = $this->formatHeadings( $text );
655 $sk = $wgUser->getSkin();
656 $text = $sk->transformContent( $text );
657 $text .= $this->categoryMagic () ;
659 wfProfileOut();
660 return $text;
663 /* private */ function doAllQuotes( $text )
665 $outtext = "";
666 $lines = explode( "\r\n", $text );
667 foreach ( $lines as $line ) {
668 $outtext .= $this->doQuotes ( "", $line, "" ) . "\r\n";
670 return $outtext;
673 /* private */ function doQuotes( $pre, $text, $mode )
675 if ( preg_match( "/^(.*)''(.*)$/sU", $text, $m ) ) {
676 $m1_strong = ($m[1] == "") ? "" : "<strong>{$m[1]}</strong>";
677 $m1_em = ($m[1] == "") ? "" : "<em>{$m[1]}</em>";
678 if ( substr ($m[2], 0, 1) == "'" ) {
679 $m[2] = substr ($m[2], 1);
680 if ($mode == "em") {
681 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ? "both" : "emstrong" );
682 } else if ($mode == "strong") {
683 return $m1_strong . $this->doQuotes ( "", $m[2], "" );
684 } else if (($mode == "emstrong") || ($mode == "both")) {
685 return $this->doQuotes ( "", $pre.$m1_strong.$m[2], "em" );
686 } else if ($mode == "strongem") {
687 return "<strong>{$pre}{$m1_em}</strong>" . $this->doQuotes ( "", $m[2], "em" );
688 } else {
689 return $m[1] . $this->doQuotes ( "", $m[2], "strong" );
691 } else {
692 if ($mode == "strong") {
693 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ? "both" : "strongem" );
694 } else if ($mode == "em") {
695 return $m1_em . $this->doQuotes ( "", $m[2], "" );
696 } else if ($mode == "emstrong") {
697 return "<em>{$pre}{$m1_strong}</em>" . $this->doQuotes ( "", $m[2], "strong" );
698 } else if (($mode == "strongem") || ($mode == "both")) {
699 return $this->doQuotes ( "", $pre.$m1_em.$m[2], "strong" );
700 } else {
701 return $m[1] . $this->doQuotes ( "", $m[2], "em" );
704 } else {
705 $text_strong = ($text == "") ? "" : "<strong>{$text}</strong>";
706 $text_em = ($text == "") ? "" : "<em>{$text}</em>";
707 if ($mode == "") {
708 return $pre . $text;
709 } else if ($mode == "em") {
710 return $pre . $text_em;
711 } else if ($mode == "strong") {
712 return $pre . $text_strong;
713 } else if ($mode == "strongem") {
714 return (($pre == "") && ($text == "")) ? "" : "<strong>{$pre}{$text_em}</strong>";
715 } else {
716 return (($pre == "") && ($text == "")) ? "" : "<em>{$pre}{$text_strong}</em>";
721 /* private */ function doHeadings( $text )
723 for ( $i = 6; $i >= 1; --$i ) {
724 $h = substr( "======", 0, $i );
725 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
726 "<h{$i}>\\1</h{$i}>\\2", $text );
728 return $text;
731 # Note: we have to do external links before the internal ones,
732 # and otherwise take great care in the order of things here, so
733 # that we don't end up interpreting some URLs twice.
735 /* private */ function replaceExternalLinks( $text )
737 wfProfileIn( "OutputPage::replaceExternalLinks" );
738 $text = $this->subReplaceExternalLinks( $text, "http", true );
739 $text = $this->subReplaceExternalLinks( $text, "https", true );
740 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
741 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
742 $text = $this->subReplaceExternalLinks( $text, "news", false );
743 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
744 wfProfileOut();
745 return $text;
748 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
750 global $wgUser, $printable;
751 global $wgAllowExternalImages;
754 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
755 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
757 # this is the list of separators that should be ignored if they
758 # are the last character of an URL but that should be included
759 # if they occur within the URL, e.g. "go to www.foo.com, where .."
760 # in this case, the last comma should not become part of the URL,
761 # but in "www.foo.com/123,2342,32.htm" it should.
762 $sep = ",;\.:";
763 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
764 $images = "gif|png|jpg|jpeg";
766 # PLEASE NOTE: The curly braces { } are not part of the regex,
767 # they are interpreted as part of the string (used to tell PHP
768 # that the content of the string should be inserted there).
769 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
770 "((?i){$images})([^{$uc}]|$)/";
772 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
773 $sk = $wgUser->getSkin();
775 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
776 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
777 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
779 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
780 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
781 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
782 "</a>\\5", $s );
783 $s = str_replace( $unique, $protocol, $s );
785 $a = explode( "[{$protocol}:", " " . $s );
786 $s = array_shift( $a );
787 $s = substr( $s, 1 );
789 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
790 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
792 foreach ( $a as $line ) {
793 if ( preg_match( $e1, $line, $m ) ) {
794 $link = "{$protocol}:{$m[1]}";
795 $trail = $m[2];
796 if ( $autonumber ) { $text = "[" . ++$this->mAutonumber . "]"; }
797 else { $text = wfEscapeHTML( $link ); }
798 } else if ( preg_match( $e2, $line, $m ) ) {
799 $link = "{$protocol}:{$m[1]}";
800 $text = $m[2];
801 $trail = $m[3];
802 } else {
803 $s .= "[{$protocol}:" . $line;
804 continue;
806 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
807 else $paren = "";
808 $la = $sk->getExternalLinkAttributes( $link, $text );
809 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
812 return $s;
815 /* private */ function replaceInternalLinks( $s )
817 global $wgTitle, $wgUser, $wgLang;
818 global $wgLinkCache, $wgInterwikiMagic, $wgUseCategoryMagic;
819 global $wgNamespacesWithSubpages, $wgLanguageCode;
820 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
822 wfProfileIn( "$fname-setup" );
823 $tc = Title::legalChars() . "#";
824 $sk = $wgUser->getSkin();
826 $a = explode( "[[", " " . $s );
827 $s = array_shift( $a );
828 $s = substr( $s, 1 );
830 $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD";
831 $e2 = "/^([{$tc}]+)]](.*)\$/sD";
832 wfProfileOut();
834 foreach ( $a as $line ) {
835 wfProfileIn( "$fname-loop" );
836 if ( preg_match( $e1, $line, $m ) ) { # page with alternate text
838 $text = $m[2];
839 $trail = $m[3];
841 } else if ( preg_match( $e2, $line, $m ) ) { # page with normal text
843 $text = "";
844 $trail = $m[2];
847 else { # Invalid form; output directly
848 $s .= "[[" . $line ;
849 wfProfileOut();
850 continue;
852 if(substr($m[1],0,1)=="/") { # subpage
853 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
854 $m[1]=substr($m[1],1,strlen($m[1])-2);
855 $noslash=$m[1];
857 } else {
858 $noslash=substr($m[1],1);
860 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
861 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
862 if(!$text) {
863 $text= $m[1];
864 } # this might be changed for ugliness reasons
865 } else {
866 $link = $noslash; # no subpage allowed, use standard link
868 } else { # no subpage
869 $link = $m[1];
872 if ( preg_match( "/^((?:i|x|[a-z]{2,3})(?:-[a-z0-9]+)?|[A-Za-z\\x80-\\xff]+):(.*)\$/", $link, $m ) ) {
873 $pre = strtolower( $m[1] );
874 $suf = $m[2];
875 if ( $wgLang->getNsIndex( $pre ) ==
876 Namespace::getImage() ) {
877 $nt = Title::newFromText( $suf );
878 $name = $nt->getDBkey();
879 if ( "" == $text ) { $text = $nt->GetText(); }
881 $wgLinkCache->addImageLink( $name );
882 $s .= $sk->makeImageLink( $name,
883 wfImageUrl( $name ), $text );
884 $s .= $trail;
885 } else if ( "media" == $pre ) {
886 $nt = Title::newFromText( $suf );
887 $name = $nt->getDBkey();
888 if ( "" == $text ) { $text = $nt->GetText(); }
890 $wgLinkCache->addImageLink( $name );
891 $s .= $sk->makeMediaLink( $name,
892 wfImageUrl( $name ), $text );
893 $s .= $trail;
894 } else if ( isset($wgUseCategoryMagic) && $wgUseCategoryMagic && $pre == wfMsg ( "category" ) ) {
895 $l = $sk->makeLink ( $pre.":".ucfirst($m[2]) , ucfirst ( $m[2] ) ) ;
896 array_push ( $this->mCategoryLinks , $l ) ;
897 $s .= $trail ;
898 } else {
899 $l = $wgLang->getLanguageName( $pre );
900 if ( "" == $l or !$wgInterwikiMagic or
901 Namespace::isTalk( $wgTitle->getNamespace() ) ) {
902 if ( "" == $text ) { $text = $link; }
903 $s .= $sk->makeLink( $link, $text, "", $trail );
904 } else if ( $pre != $wgLanguageCode ) {
905 array_push( $this->mLanguageLinks, "$pre:$suf" );
906 $s .= $trail;
909 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
910 # $link = substr( $link, 2 );
911 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
912 } else {
913 if ( "" == $text ) { $text = $link; }
914 $s .= $sk->makeLink( $link, $text, "", $trail );
916 wfProfileOut();
918 wfProfileOut();
919 return $s;
922 # Some functions here used by doBlockLevels()
924 /* private */ function closeParagraph()
926 $result = "";
927 if ( 0 != strcmp( "p", $this->mLastSection ) &&
928 0 != strcmp( "", $this->mLastSection ) ) {
929 $result = "</" . $this->mLastSection . ">";
931 $this->mLastSection = "";
932 return $result;
934 # getCommon() returns the length of the longest common substring
935 # of both arguments, starting at the beginning of both.
937 /* private */ function getCommon( $st1, $st2 )
939 $fl = strlen( $st1 );
940 $shorter = strlen( $st2 );
941 if ( $fl < $shorter ) { $shorter = $fl; }
943 for ( $i = 0; $i < $shorter; ++$i ) {
944 if ( $st1{$i} != $st2{$i} ) { break; }
946 return $i;
948 # These next three functions open, continue, and close the list
949 # element appropriate to the prefix character passed into them.
951 /* private */ function openList( $char )
953 $result = $this->closeParagraph();
955 if ( "*" == $char ) { $result .= "<ul><li>"; }
956 else if ( "#" == $char ) { $result .= "<ol><li>"; }
957 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
958 else if ( ";" == $char ) {
959 $result .= "<dl><dt>";
960 $this->mDTopen = true;
962 else { $result = "<!-- ERR 1 -->"; }
964 return $result;
967 /* private */ function nextItem( $char )
969 if ( "*" == $char || "#" == $char ) { return "</li><li>"; }
970 else if ( ":" == $char || ";" == $char ) {
971 $close = "</dd>";
972 if ( $this->mDTopen ) { $close = "</dt>"; }
973 if ( ";" == $char ) {
974 $this->mDTopen = true;
975 return $close . "<dt>";
976 } else {
977 $this->mDTopen = false;
978 return $close . "<dd>";
981 return "<!-- ERR 2 -->";
984 /* private */function closeList( $char )
986 if ( "*" == $char ) { return "</li></ul>"; }
987 else if ( "#" == $char ) { return "</li></ol>"; }
988 else if ( ":" == $char ) {
989 if ( $this->mDTopen ) {
990 $this->mDTopen = false;
991 return "</dt></dl>";
992 } else {
993 return "</dd></dl>";
996 return "<!-- ERR 3 -->";
999 /* private */ function doBlockLevels( $text, $linestart )
1001 wfProfileIn( "OutputPage::doBlockLevels" );
1002 # Parsing through the text line by line. The main thing
1003 # happening here is handling of block-level elements p, pre,
1004 # and making lists from lines starting with * # : etc.
1006 $a = explode( "\n", $text );
1007 $text = $lastPref = "";
1008 $this->mDTopen = $inBlockElem = false;
1010 if ( ! $linestart ) { $text .= array_shift( $a ); }
1011 foreach ( $a as $t ) {
1012 if ( "" != $text ) { $text .= "\n"; }
1014 $oLine = $t;
1015 $opl = strlen( $lastPref );
1016 $npl = strspn( $t, "*#:;" );
1017 $pref = substr( $t, 0, $npl );
1018 $pref2 = str_replace( ";", ":", $pref );
1019 $t = substr( $t, $npl );
1021 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1022 $text .= $this->nextItem( substr( $pref, -1 ) );
1024 if ( ";" == substr( $pref, -1 ) ) {
1025 $cpos = strpos( $t, ":" );
1026 if ( ! ( false === $cpos ) ) {
1027 $term = substr( $t, 0, $cpos );
1028 $text .= $term . $this->nextItem( ":" );
1029 $t = substr( $t, $cpos + 1 );
1032 } else if (0 != $npl || 0 != $opl) {
1033 $cpl = $this->getCommon( $pref, $lastPref );
1035 while ( $cpl < $opl ) {
1036 $text .= $this->closeList( $lastPref{$opl-1} );
1037 --$opl;
1039 if ( $npl <= $cpl && $cpl > 0 ) {
1040 $text .= $this->nextItem( $pref{$cpl-1} );
1042 while ( $npl > $cpl ) {
1043 $char = substr( $pref, $cpl, 1 );
1044 $text .= $this->openList( $char );
1046 if ( ";" == $char ) {
1047 $cpos = strpos( $t, ":" );
1048 if ( ! ( false === $cpos ) ) {
1049 $term = substr( $t, 0, $cpos );
1050 $text .= $term . $this->nextItem( ":" );
1051 $t = substr( $t, $cpos + 1 );
1054 ++$cpl;
1056 $lastPref = $pref2;
1058 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1059 if ( preg_match(
1060 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1061 $text .= $this->closeParagraph();
1062 $inBlockElem = true;
1064 if ( ! $inBlockElem ) {
1065 if ( " " == $t{0} ) {
1066 $newSection = "pre";
1067 # $t = wfEscapeHTML( $t );
1069 else { $newSection = "p"; }
1071 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1072 $text .= $this->closeParagraph();
1073 $text .= "<" . $newSection . ">";
1074 } else if ( 0 != strcmp( $this->mLastSection,
1075 $newSection ) ) {
1076 $text .= $this->closeParagraph();
1077 if ( 0 != strcmp( "p", $newSection ) ) {
1078 $text .= "<" . $newSection . ">";
1081 $this->mLastSection = $newSection;
1083 if ( $inBlockElem &&
1084 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1085 $inBlockElem = false;
1088 $text .= $t;
1090 while ( $npl ) {
1091 $text .= $this->closeList( $pref2{$npl-1} );
1092 --$npl;
1094 if ( "" != $this->mLastSection ) {
1095 if ( "p" != $this->mLastSection ) {
1096 $text .= "</" . $this->mLastSection . ">";
1098 $this->mLastSection = "";
1100 wfProfileOut();
1101 return $text;
1104 /* private */ function replaceVariables( $text )
1106 global $wgLang;
1107 wfProfileIn( "OutputPage:replaceVariables" );
1109 /* As with sigs, use server's local time --
1110 ensure this is appropriate for your audience! */
1111 $v = date( "m" );
1112 $mw =& MagicWord::get( MAG_CURRENTMONTH );
1113 $text = $mw->replace( $v, $text );
1115 $v = $wgLang->getMonthName( date( "n" ) );
1116 $mw =& MagicWord::get( MAG_CURRENTMONTHNAME );
1117 $text = $mw->replace( $v, $text );
1119 $v = $wgLang->getMonthNameGen( date( "n" ) );
1120 $mw =& MagicWord::get( MAG_CURRENTMONTHNAMEGEN );
1121 $text = $mw->replace( $v, $text );
1123 $v = date( "j" );
1124 $mw = MagicWord::get( MAG_CURRENTDAY );
1125 $text = $mw->replace( $v, $text );
1127 $v = $wgLang->getWeekdayName( date( "w" )+1 );
1128 $mw =& MagicWord::get( MAG_CURRENTDAYNAME );
1129 $text = $mw->replace( $v, $text );
1131 $v = date( "Y" );
1132 $mw =& MagicWord::get( MAG_CURRENTYEAR );
1133 $text = $mw->replace( $v, $text );
1135 $v = $wgLang->time( wfTimestampNow(), false );
1136 $mw =& MagicWord::get( MAG_CURRENTTIME );
1137 $text = $mw->replace( $v, $text );
1139 $mw =& MagicWord::get( MAG_NUMBEROFARTICLES );
1140 if ( $mw->match( $text ) ) {
1141 $v = wfNumberOfArticles();
1142 $text = $mw->replace( $v, $text );
1144 wfProfileOut();
1145 return $text;
1148 /* private */ function removeHTMLtags( $text )
1150 wfProfileIn( "OutputPage::removeHTMLtags" );
1151 $htmlpairs = array( # Tags that must be closed
1152 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1153 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1154 "strike", "strong", "tt", "var", "div", "center",
1155 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1156 "ruby", "rt" , "rb" , "rp"
1158 $htmlsingle = array(
1159 "br", "p", "hr", "li", "dt", "dd"
1161 $htmlnest = array( # Tags that can be nested--??
1162 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1163 "dl", "font", "big", "small", "sub", "sup"
1165 $tabletags = array( # Can only appear inside table
1166 "td", "th", "tr"
1169 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1170 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1172 $htmlattrs = array( # Allowed attributes--no scripting, etc.
1173 "title", "align", "lang", "dir", "width", "height",
1174 "bgcolor", "clear", /* BR */ "noshade", /* HR */
1175 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
1176 /* FONT */ "type", "start", "value", "compact",
1177 /* For various lists, mostly deprecated but safe */
1178 "summary", "width", "border", "frame", "rules",
1179 "cellspacing", "cellpadding", "valign", "char",
1180 "charoff", "colgroup", "col", "span", "abbr", "axis",
1181 "headers", "scope", "rowspan", "colspan", /* Tables */
1182 "id", "class", "name", "style" /* For CSS */
1185 # Remove HTML comments
1186 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1188 $bits = explode( "<", $text );
1189 $text = array_shift( $bits );
1190 $tagstack = array(); $tablestack = array();
1192 foreach ( $bits as $x ) {
1193 $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
1194 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1195 $x, $regs );
1196 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1197 error_reporting( $prev );
1199 $badtag = 0 ;
1200 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1201 # Check our stack
1202 if ( $slash ) {
1203 # Closing a tag...
1204 if ( ! in_array( $t, $htmlsingle ) &&
1205 ( $ot = array_pop( $tagstack ) ) != $t ) {
1206 array_push( $tagstack, $ot );
1207 $badtag = 1;
1208 } else {
1209 if ( $t == "table" ) {
1210 $tagstack = array_pop( $tablestack );
1212 $newparams = "";
1214 } else {
1215 # Keep track for later
1216 if ( in_array( $t, $tabletags ) &&
1217 ! in_array( "table", $tagstack ) ) {
1218 $badtag = 1;
1219 } else if ( in_array( $t, $tagstack ) &&
1220 ! in_array ( $t , $htmlnest ) ) {
1221 $badtag = 1 ;
1222 } else if ( ! in_array( $t, $htmlsingle ) ) {
1223 if ( $t == "table" ) {
1224 array_push( $tablestack, $tagstack );
1225 $tagstack = array();
1227 array_push( $tagstack, $t );
1229 # Strip non-approved attributes from the tag
1230 $newparams = preg_replace(
1231 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
1232 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
1233 $params);
1235 if ( ! $badtag ) {
1236 $rest = str_replace( ">", "&gt;", $rest );
1237 $text .= "<$slash$t$newparams$brace$rest";
1238 continue;
1241 $text .= "&lt;" . str_replace( ">", "&gt;", $x);
1243 # Close off any remaining tags
1244 while ( $t = array_pop( $tagstack ) ) {
1245 $text .= "</$t>\n";
1246 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1248 wfProfileOut();
1249 return $text;
1255 * This function accomplishes several tasks:
1256 * 1) Auto-number headings if that option is enabled
1257 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1258 * 3) Add a Table of contents on the top for users who have enabled the option
1259 * 4) Auto-anchor headings
1261 * It loops through all headlines, collects the necessary data, then splits up the
1262 * string and re-inserts the newly formatted headlines.
1264 * */
1265 /* private */ function formatHeadings( $text )
1267 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1268 $nh=$wgUser->getOption( "numberheadings" );
1269 $st=$wgUser->getOption( "showtoc" );
1270 if(!$wgTitle->userCanEdit()) {
1271 $es=0;
1272 $esr=0;
1273 } else {
1274 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1275 $esr=$wgUser->getID() && $wgUser->getOption( "editsectiononrightclick" );
1277 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, do not
1278 # add TOC
1279 $mw =& MagicWord::get( MAG_NOTOC );
1280 $st = ! $mw->matchAndRemove( $text );
1282 # never add the TOC to the Main Page. This is an entry page that should not
1283 # be more than 1-2 screens large anyway
1284 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1286 # We need this to perform operations on the HTML
1287 $sk=$wgUser->getSkin();
1289 # Get all headlines for numbering them and adding funky stuff like [edit]
1290 # links
1291 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1293 # headline counter
1294 $c=0;
1296 # Ugh .. the TOC should have neat indentation levels which can be
1297 # passed to the skin functions. These are determined here
1298 foreach($matches[3] as $headline) {
1299 if($level) { $prevlevel=$level;}
1300 $level=$matches[1][$c];
1301 if(($nh||$st) && $prevlevel && $level>$prevlevel) {
1303 $h[$level]=0; // reset when we enter a new level
1304 $toc.=$sk->tocIndent($level-$prevlevel);
1305 $toclevel+=$level-$prevlevel;
1308 if(($nh||$st) && $level<$prevlevel) {
1309 $h[$level+1]=0; // reset when we step back a level
1310 $toc.=$sk->tocUnindent($prevlevel-$level);
1311 $toclevel-=$prevlevel-$level;
1314 $h[$level]++; // count number of headlines for each level
1316 if($nh||$st) {
1317 for($i=1;$i<=$level;$i++) {
1318 if($h[$i]) {
1319 if($dot) {$numbering.=".";}
1320 $numbering.=$h[$i];
1321 $dot=1;
1327 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1328 $tocline=$canonized_headline;
1329 $canonized_headline=str_replace('"',"",$canonized_headline);
1330 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1331 $refer[$c]=$canonized_headline;
1332 $refers[$canonized_headline]++; // count how many in assoc. array so we can track dupes in anchors
1333 $refcount[$c]=$refers[$canonized_headline];
1334 if($nh||$st) {
1335 $tocline=$numbering ." ". $tocline;
1336 if($nh) {
1337 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1340 $anchor=$canonized_headline;
1341 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1342 if($st) {
1343 $toc.=$sk->tocLine($anchor,$tocline,$toclevel);
1345 if($es && !isset($wpPreview)) {
1346 $head[$c].=$sk->editSectionLink($c+1);
1348 $head[$c].="<H".$level.$matches[2][$c]
1349 ."<a name=\"".$anchor."\">"
1350 .$headline
1351 ."</a>"
1352 ."</H".$level.">";
1353 if($esr && !isset($wpPreview)) {
1354 $head[$c]=$sk->editSectionScript($c+1,$head[$c]);
1356 $numbering="";
1357 $c++;
1358 $dot=0;
1361 if($st) {
1362 $toclines=$c;
1363 $toc.=$sk->tocUnindent($toclevel);
1364 $toc=$sk->tocTable($toc);
1367 // split up and insert constructed headlines
1369 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1370 $i=0;
1373 foreach($blocks as $block) {
1374 if(($es) && !isset($wpPreview) && $c>0 && $i==0) {
1375 # This is the [edit] link that appears for the top block of text when
1376 # section editing is enabled
1377 $full.=$sk->editSectionLink(0);
1379 $full.=$block;
1380 if($st && $toclines>3 && !$i) {
1381 # Let's add a top anchor just in case we want to link to the top of the page
1382 $full="<a name=\"top\"></a>".$full.$toc;
1385 $full.=$head[$i];
1386 $i++;
1388 return $full;
1391 /* private */ function magicISBN( $text )
1393 global $wgLang;
1395 $a = split( "ISBN ", " $text" );
1396 if ( count ( $a ) < 2 ) return $text;
1397 $text = substr( array_shift( $a ), 1);
1398 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1400 foreach ( $a as $x ) {
1401 $isbn = $blank = "" ;
1402 while ( " " == $x{0} ) {
1403 $blank .= " ";
1404 $x = substr( $x, 1 );
1406 while ( strstr( $valid, $x{0} ) != false ) {
1407 $isbn .= $x{0};
1408 $x = substr( $x, 1 );
1410 $num = str_replace( "-", "", $isbn );
1411 $num = str_replace( " ", "", $num );
1413 if ( "" == $num ) {
1414 $text .= "ISBN $blank$x";
1415 } else {
1416 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1417 "Booksources"), "isbn={$num}" ) . "\" CLASS=\"internal\">ISBN $isbn</a>";
1418 $text .= $x;
1421 return $text;
1424 /* private */ function magicRFC( $text )
1426 return $text;
1429 /* private */ function headElement()
1431 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding, $wgLang;
1433 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1435 if ( "" == $this->mHTMLtitle ) {
1436 $this->mHTMLtitle = $this->mPagetitle;
1438 $rtl = $wgLang->isRTL() ? " dir='RTL'" : "";
1439 $ret .= "<html lang=\"$wgLanguageCode\"$rtl><head><title>{$this->mHTMLtitle}</title>\n";
1440 array_push( $this->mMetatags, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1441 foreach ( $this->mMetatags as $tag ) {
1442 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1443 $a = "http-equiv";
1444 $tag[0] = substr( $tag[0], 5 );
1445 } else {
1446 $a = "name";
1448 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1450 $p = $this->mRobotpolicy;
1451 if ( "" == $p ) { $p = "index,follow"; }
1452 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1454 if ( count( $this->mKeywords ) > 0 ) {
1455 $ret .= "<meta name=\"keywords\" content=\"" .
1456 implode( ",", $this->mKeywords ) . "\">\n";
1458 foreach ( $this->mLinktags as $tag ) {
1459 $ret .= "<link ";
1460 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1461 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1462 $ret .= "href=\"{$tag[2]}\">\n";
1464 $sk = $wgUser->getSkin();
1465 $ret .= $sk->getHeadScripts();
1466 $ret .= $sk->getUserStyles();
1468 $ret .= "</head>\n";
1469 return $ret;