f5b464cf73474b4054b32f5de602da921b882442
[lhc/web/wiklou.git] / includes / OutputPage.php
1 <?
2 # See design.doc
3
4 if($wgUseTeX) include_once( "Math.php" );
5
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;
12
13 var $mDTopen, $mLastSection; # Used for processing DL, PRE
14 var $mLanguageLinks, $mSupressQuickbar;
15
16 function OutputPage()
17 {
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;
28 }
29
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; }
33
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 ) ); }
38
39 function checkLastModified ( $timestamp )
40 {
41 global $wgLang, $wgCachePages, $wgUser;
42 if( !$wgCachePages ) {
43 wfDebug( "CACHE DISABLED\n", false );
44 return;
45 }
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;
50 }
51 if( $wgUser->getOption( "nocache" ) ) {
52 wfDebug( "USER DISABLED CACHE\n", false );
53 return;
54 }
55
56 $lastmod = gmdate( "D, j M Y H:i:s", wfTimestamp2Unix(
57 max( $timestamp, $wgUser->mTouched ) ) ) . " GMT";
58
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 );
67
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;
79 }
80 } else {
81 wfDebug( "We're confused.\n", false );
82 $this->mLastModified = $lastmod;
83 }
84 }
85
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; }
96
97 function getLanguageLinks() {
98 global $wgUseNewInterlanguage, $wgTitle, $wgLanguageCode;
99 global $wgDBconnection, $wgDBname, $wgDBintlname;
100
101 if ( ! $wgUseNewInterlanguage )
102 return $this->mLanguageLinks;
103
104 mysql_select_db( $wgDBintlname, $wgDBconnection ) or die(
105 htmlspecialchars(mysql_error()) );
106
107 $list = array();
108 $sql = "SELECT * FROM ilinks WHERE lang_from=\"" .
109 "{$wgLanguageCode}\" AND title_from=\"" . $wgTitle->getDBkey() . "\"";
110 $res = mysql_query( $sql, $wgDBconnection );
111
112 while ( $q = mysql_fetch_object ( $res ) ) {
113 $list[] = $q->lang_to . ":" . $q->title_to;
114 }
115 mysql_free_result( $res );
116 mysql_select_db( $wgDBname, $wgDBconnection ) or die(
117 htmlspecialchars(mysql_error()) );
118
119 return $list;
120 }
121
122 function supressQuickbar() { $this->mSupressQuickbar = true; }
123 function isQuickbarSupressed() { return $this->mSupressQuickbar; }
124
125 function addHTML( $text ) { $this->mBodytext .= $text; }
126 function addHeadtext( $text ) { $this->mHeadtext .= $text; }
127 function debug( $text ) { $this->mDebugtext .= $text; }
128
129 # First pass--just handle <nowiki> sections, pass the rest off
130 # to doWikiPass2() which does all the real work.
131 #
132
133 function addWikiText( $text, $linestart = true )
134 {
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 = "";
149
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];
160 }
161 }
162
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];
174 }
175 }
176 } else {
177 $stripped2 = $stripped;
178 }
179
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];
190 }
191 }
192
193 $text = $this->doWikiPass2( $stripped3, $linestart );
194
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 );
200 }
201
202 for ( $i = 1; $i <= $mathsecs; ++$i ) {
203 $text = preg_replace( "/{$unique2}/", str_replace( $specialChars,
204 $escapedChars, $mathlist[$i] ), $text, 1 );
205 }
206
207 for ( $i = 1; $i <= $nwsecs; ++$i ) {
208 $text = preg_replace( "/{$unique}/", str_replace( $specialChars,
209 $escapedChars, $nwlist[$i] ), $text, 1 );
210 }
211 $this->addHTML( $text );
212 wfProfileOut();
213 }
214
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" );
225 }
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" );
231 }
232 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
233 }
234
235 # Finally, all the text has been munged and accumulated into
236 # the object, let's actually output it:
237 #
238 function output()
239 {
240 global $wgUser, $wgLang, $wgDebugComments, $wgCookieExpiration;
241 global $wgInputEncoding, $wgOutputEncoding, $wgLanguageCode;
242 $sk = $wgUser->getSkin();
243
244 $this->sendCacheControl();
245
246 header( "Content-type: text/html; charset={$wgOutputEncoding}" );
247 header( "Content-language: {$wgLanguageCode}" );
248
249 if ( "" != $this->mRedirect ) {
250 header( "Location: {$this->mRedirect}" );
251 return;
252 }
253
254 $exp = time() + $wgCookieExpiration;
255 foreach( $this->mCookies as $name => $val ) {
256 setcookie( $name, $val, $exp, "/" );
257 }
258
259 $sk->outputPage( $this );
260 flush();
261 }
262
263 function out( $ins )
264 {
265 global $wgInputEncoding, $wgOutputEncoding, $wgLang;
266 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
267 $outs = $ins;
268 } else {
269 $outs = $wgLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
270 if ( false === $outs ) { $outs = $ins; }
271 }
272 print $outs;
273 }
274
275 function setEncodings()
276 {
277 global $HTTP_SERVER_VARS, $wgInputEncoding, $wgOutputEncoding;
278 global $wgUser, $wgLang;
279
280 $wgInputEncoding = strtolower( $wgInputEncoding );
281 $s = $HTTP_SERVER_VARS['HTTP_ACCEPT_CHARSET'];
282
283 if( $wgUser->getOption( 'altencoding' ) ) {
284 $wgLang->setAltEncoding();
285 return;
286 }
287
288 if ( "" == $s ) {
289 $wgOutputEncoding = strtolower( $wgOutputEncoding );
290 return;
291 }
292 $a = explode( ",", $s );
293 $best = 0.0;
294 $bestset = "*";
295
296 foreach ( $a as $s ) {
297 if ( preg_match( "/(.*);q=(.*)/", $s, $m ) ) {
298 $set = $m[1];
299 $q = (float)($m[2]);
300 } else {
301 $set = $s;
302 $q = 1.0;
303 }
304 if ( $q > $best ) {
305 $bestset = $set;
306 $best = $q;
307 }
308 }
309 #if ( "*" == $bestset ) { $bestset = "iso-8859-1"; }
310 if ( "*" == $bestset ) { $bestset = $wgOutputEncoding; }
311 $wgOutputEncoding = strtolower( $bestset );
312
313 # Disable for now
314 #
315 $wgOutputEncoding = $wgInputEncoding;
316 }
317
318 function reportTime()
319 {
320 global $wgRequestTime, $wgDebugLogFile, $HTTP_SERVER_VARS;
321 global $wgProfiling, $wgProfileStack, $wgUser;
322
323 list( $usec, $sec ) = explode( " ", microtime() );
324 $now = (float)$sec + (float)$usec;
325
326 list( $usec, $sec ) = explode( " ", $wgRequestTime );
327 $start = (float)$sec + (float)$usec;
328 $elapsed = $now - $start;
329
330 if ( "" != $wgDebugLogFile ) {
331 $prof = "";
332 if( $wgProfiling and count( $wgProfileStack ) ) {
333 $lasttime = $start;
334 foreach( $wgProfileStack as $ile ) {
335 # "foo::bar 99 0.12345 1 0.23456 2"
336 if( preg_match( '/^(\S+)\s+([0-9]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/', $ile, $m ) ) {
337 $thisstart = (float)$m[3] + (float)$m[4] - $start;
338 $thisend = (float)$m[5] + (float)$m[6] - $start;
339 $thiselapsed = $thisend - $thisstart;
340 $thispercent = $thiselapsed / $elapsed * 100.0;
341
342 $prof .= sprintf( "\tat %04.3f in %04.3f (%2.1f%%) - %s %s\n",
343 $thisstart, $thiselapsed, $thispercent,
344 str_repeat( "*", $m[2] ), $m[1] );
345 $lasttime = $thistime;
346 #$prof .= "\t(^ $ile)\n";
347 } else {
348 $prof .= "\t?broken? $ile\n";
349 }
350 }
351 }
352
353 if( $forward = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'] )
354 $forward = " forwarded for $forward";
355 if( $client = $HTTP_SERVER_VARS['HTTP_CLIENT_IP'] )
356 $forward .= " client IP $client";
357 if( $from = $HTTP_SERVER_VARS['HTTP_FROM'] )
358 $forward .= " from $from";
359 if( $forward )
360 $forward = "\t(proxied via {$HTTP_SERVER_VARS['REMOTE_ADDR']}{$forward})";
361 if($wgUser->getId() == 0)
362 $forward .= " anon";
363 $log = sprintf( "%s\t%04.3f\t%s\n",
364 gmdate( "YmdHis" ), $elapsed,
365 urldecode( $HTTP_SERVER_VARS['REQUEST_URI'] . $forward ) );
366 error_log( $log . $prof, 3, $wgDebugLogFile );
367 }
368 $com = sprintf( "<!-- Time since request: %01.2f secs. -->",
369 $elapsed );
370 return $com;
371 }
372
373 # Note: these arguments are keys into wfMsg(), not text!
374 #
375 function errorpage( $title, $msg )
376 {
377 global $wgTitle;
378
379 $this->mDebugtext .= "Original title: " .
380 $wgTitle->getPrefixedText() . "\n";
381 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
382 $this->setPageTitle( wfMsg( $title ) );
383 $this->setRobotpolicy( "noindex,nofollow" );
384 $this->setArticleFlag( false );
385
386 $this->mBodytext = "";
387 $this->addHTML( "<p>" . wfMsg( $msg ) . "\n" );
388 $this->returnToMain( false );
389
390 $this->output();
391 exit;
392 }
393
394 function sysopRequired()
395 {
396 global $wgUser;
397
398 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
399 $this->setPageTitle( wfMsg( "sysoptitle" ) );
400 $this->setRobotpolicy( "noindex,nofollow" );
401 $this->setArticleFlag( false );
402 $this->mBodytext = "";
403
404 $sk = $wgUser->getSkin();
405 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
406 $text = str_replace( "$1", $ap, wfMsg( "sysoptext" ) );
407 $this->addHTML( $text );
408 $this->returnToMain();
409 }
410
411 function developerRequired()
412 {
413 global $wgUser;
414
415 $this->setHTMLTitle( wfMsg( "errorpagetitle" ) );
416 $this->setPageTitle( wfMsg( "developertitle" ) );
417 $this->setRobotpolicy( "noindex,nofollow" );
418 $this->setArticleFlag( false );
419 $this->mBodytext = "";
420
421 $sk = $wgUser->getSkin();
422 $ap = $sk->makeKnownLink( wfMsg( "administrators" ), "" );
423 $text = str_replace( "$1", $ap, wfMsg( "developertext" ) );
424 $this->addHTML( $text );
425 $this->returnToMain();
426 }
427
428 function databaseError( $fname )
429 {
430 global $wgUser, $wgCommandLineMode;
431
432 $this->setPageTitle( wfMsg( "databaseerror" ) );
433 $this->setRobotpolicy( "noindex,nofollow" );
434 $this->setArticleFlag( false );
435
436 if ( $wgCommandLineMode ) {
437 $msg = wfMsg( "dberrortextcl" );
438 } else {
439 $msg = wfMsg( "dberrortextcl" );
440 }
441 $msg = str_replace( "$1", htmlspecialchars( wfLastDBquery() ), $msg );
442 $msg = str_replace( "$2", htmlspecialchars( $fname ), $msg );
443 $msg = str_replace( "$3", wfLastErrno(), $msg );
444 $msg = str_replace( "$4", htmlspecialchars( wfLastError() ), $msg );
445
446 if ( $wgCommandLineMode ) {
447 print $msg;
448 exit();
449 }
450 $sk = $wgUser->getSkin();
451 $shlink = $sk->makeKnownLink( wfMsg( "searchhelppage" ),
452 wfMsg( "searchingwikipedia" ) );
453 $msg = str_replace( "$5", $shlink, $msg );
454
455 $this->mBodytext = $msg;
456 $this->output();
457 exit();
458 }
459
460 function readOnlyPage( $source = "" )
461 {
462 global $wgUser, $wgReadOnlyFile;
463
464 $this->setPageTitle( wfMsg( "readonly" ) );
465 $this->setRobotpolicy( "noindex,nofollow" );
466 $this->setArticleFlag( false );
467
468 $reason = implode( "", file( $wgReadOnlyFile ) );
469 $text = str_replace( "$1", $reason, wfMsg( "readonlytext" ) );
470
471 if($source) {
472 $rows = $wgUser->getOption( "rows" );
473 $cols = $wgUser->getOption( "cols" );
474 $text .= "</p>\n<textarea cols='$cols' rows='$rows' readonly>" .
475 htmlspecialchars( $source ) . "\n</textarea>";
476 }
477
478 $this->addHTML( $text );
479 $this->returnToMain( false );
480 }
481
482 function fatalError( $message )
483 {
484 $this->setPageTitle( wfMsg( "internalerror" ) );
485 $this->setRobotpolicy( "noindex,nofollow" );
486 $this->setArticleFlag( false );
487
488 $this->mBodytext = $message;
489 $this->output();
490 exit;
491 }
492
493 function unexpectedValueError( $name, $val )
494 {
495 $msg = str_replace( "$1", $name, wfMsg( "unexpected" ) );
496 $msg = str_replace( "$2", $val, $msg );
497 $this->fatalError( $msg );
498 }
499
500 function fileCopyError( $old, $new )
501 {
502 $msg = str_replace( "$1", $old, wfMsg( "filecopyerror" ) );
503 $msg = str_replace( "$2", $new, $msg );
504 $this->fatalError( $msg );
505 }
506
507 function fileRenameError( $old, $new )
508 {
509 $msg = str_replace( "$1", $old, wfMsg( "filerenameerror" ) );
510 $msg = str_replace( "$2", $new, $msg );
511 $this->fatalError( $msg );
512 }
513
514 function fileDeleteError( $name )
515 {
516 $msg = str_replace( "$1", $name, wfMsg( "filedeleteerror" ) );
517 $this->fatalError( $msg );
518 }
519
520 function fileNotFoundError( $name )
521 {
522 $msg = str_replace( "$1", $name, wfMsg( "filenotfound" ) );
523 $this->fatalError( $msg );
524 }
525
526 function returnToMain( $auto = true )
527 {
528 global $wgUser, $wgOut, $returnto;
529
530 $sk = $wgUser->getSkin();
531 if ( "" == $returnto ) {
532 $returnto = wfMsg( "mainpage" );
533 }
534 $link = $sk->makeKnownLink( $returnto, "" );
535
536 $r = str_replace( "$1", $link, wfMsg( "returnto" ) );
537 if ( $auto ) {
538 $wgOut->addMeta( "http:Refresh", "10;url=" .
539 wfLocalUrlE( wfUrlencode( $returnto ) ) );
540 }
541 $wgOut->addHTML( "\n<p>$r\n" );
542 }
543
544
545 function categoryMagic ()
546 {
547 global $wgTitle , $wgUseCategoryMagic ;
548 if ( !isset ( $wgUseCategoryMagic ) || !$wgUseCategoryMagic ) return ;
549 $id = $wgTitle->getArticleID() ;
550 $cat = ucfirst ( wfMsg ( "category" ) ) ;
551 $ti = $wgTitle->getText() ;
552 $ti = explode ( ":" , $ti , 2 ) ;
553 if ( $cat != $ti[0] ) return "" ;
554 $r = "<br break=all>\n" ;
555
556 $articles = array() ;
557 $parents = array () ;
558 $children = array() ;
559
560
561 global $wgUser ;
562 $sk = $wgUser->getSkin() ;
563 $sql = "SELECT l_from FROM links WHERE l_to={$id}" ;
564 $res = wfQuery ( $sql ) ;
565 while ( $x = wfFetchObject ( $res ) )
566 {
567 # $t = new Title ;
568 # $t->newFromDBkey ( $x->l_from ) ;
569 # $t = $t->getText() ;
570 $t = $x->l_from ;
571 $y = explode ( ":" , $t , 2 ) ;
572 if ( count ( $y ) == 2 && $y[0] == $cat )
573 {
574 array_push ( $children , $sk->makeLink ( $t , $y[1] ) ) ;
575 }
576 else array_push ( $articles , $sk->makeLink ( $t ) ) ;
577 }
578 wfFreeResult ( $res ) ;
579
580 # Children
581 if ( count ( $children ) > 0 )
582 {
583 asort ( $children ) ;
584 $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
585 $r .= implode ( ", " , $children ) ;
586 }
587
588 # Articles
589 if ( count ( $articles ) > 0 )
590 {
591 asort ( $articles ) ;
592 $h = str_replace ( "$1" , $ti[1] , wfMsg("category_header") ) ;
593 $r .= "<h2>{$h}</h2>\n" ;
594 $r .= implode ( ", " , $articles ) ;
595 }
596
597
598 return $r ;
599 }
600
601
602 function doTableStuff ( $t )
603 {
604 $t = explode ( "\n" , $t ) ;
605 $td = array () ; # Is currently a td tag open?
606 $tr = array () ; # Is currently a tr tag open?
607 foreach ( $t AS $k => $x )
608 {
609 $x = rtrim ( $x ) ;
610 if ( "{|" == substr ( $x , 0 , 2 ) )
611 {
612 $t[$k] = "<table " . substr ( $x , 3 ) . ">" ;
613 array_push ( $td , false ) ;
614 array_push ( $tr , false ) ;
615 }
616 else if ( count ( $td ) == 0 ) { } # Don't do any of the following
617 else if ( "|}" == substr ( $x , 0 , 2 ) )
618 {
619 $z = "</table>\n" ;
620 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
621 if ( array_pop ( $td ) ) $z = "</td>" . $z ;
622 $t[$k] = $z ;
623 }
624 else if ( "|_" == substr ( $x , 0 , 2 ) ) # Caption
625 {
626 $z = trim ( substr ( $x , 2 ) ) ;
627 $t[$k] = "<caption>{$z}</caption\n" ;
628 }
629 else if ( "|-" == substr ( $x , 0 , 2 ) ) # Allows for |---------------
630 {
631 $z = "" ;
632 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
633 if ( array_pop ( $td ) ) $z = "</td>" . $z ;
634 $t[$k] = $z ;
635 array_push ( $tr , false ) ;
636 array_push ( $td , false ) ;
637 }
638 else if ( "|" == substr ( $x , 0 , 1 ) )
639 {
640 $after = substr ( $x , 1 ) ;
641 $after = explode ( "||" , $after ) ;
642 $t[$k] = "" ;
643 foreach ( $after AS $theline )
644 {
645 $z = "" ;
646 if ( !array_pop ( $tr ) ) $z = "<tr>\n" ;
647 array_push ( $tr , true ) ;
648 if ( array_pop ( $td ) ) $z = "</td>" . $z ;
649 $y = explode ( "|" , $theline , 2 ) ;
650 if ( count ( $y ) == 1 ) $y = "{$z}<td>{$y[0]}" ;
651 else $y = $y = "{$z}<td {$y[0]}>{$y[1]}" ;
652 $t[$k] .= $y ;
653 array_push ( $td , true ) ;
654 }
655 }
656 }
657 $t = implode ( "\n" , $t ) ;
658 return $t ;
659 }
660
661 # Well, OK, it's actually about 14 passes. But since all the
662 # hard lifting is done inside PHP's regex code, it probably
663 # wouldn't speed things up much to add a real parser.
664 #
665 function doWikiPass2( $text, $linestart )
666 {
667 global $wgUser, $wgLang, $wgUseDynamicDates;
668 wfProfileIn( "OutputPage::doWikiPass2" );
669
670 $text = $this->removeHTMLtags( $text );
671 $text = $this->replaceVariables( $text );
672
673 $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
674 $text = str_replace ( "<HR>", "<hr>", $text );
675
676 $text = $this->doAllQuotes( $text );
677 $text = $this->doHeadings( $text );
678 $text = $this->doBlockLevels( $text, $linestart );
679
680 if($wgUseDynamicDates) {
681 $text = $wgLang->replaceDates( $text );
682 }
683
684 $text = $this->replaceExternalLinks( $text );
685 $text = $this->replaceInternalLinks ( $text );
686 $text = $this->doTableStuff ( $text ) ;
687
688 $text = $this->magicISBN( $text );
689 $text = $this->magicRFC( $text );
690 $text = $this->formatHeadings( $text );
691
692 $sk = $wgUser->getSkin();
693 $text = $sk->transformContent( $text );
694 $text .= $this->categoryMagic () ;
695
696 wfProfileOut();
697 return $text;
698 }
699
700 /* private */ function doAllQuotes( $text )
701 {
702 $outtext = "";
703 $lines = explode( "\r\n", $text );
704 foreach ( $lines as $line ) {
705 $outtext .= $this->doQuotes ( "", $line, "" ) . "\r\n";
706 }
707 return $outtext;
708 }
709
710 /* private */ function doQuotes( $pre, $text, $mode )
711 {
712 if ( preg_match( "/^(.*)''(.*)$/sU", $text, $m ) ) {
713 $m1_strong = ($m[1] == "") ? "" : "<strong>{$m[1]}</strong>";
714 $m1_em = ($m[1] == "") ? "" : "<em>{$m[1]}</em>";
715 if ( substr ($m[2], 0, 1) == "'" ) {
716 $m[2] = substr ($m[2], 1);
717 if ($mode == "em") {
718 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ? "both" : "emstrong" );
719 } else if ($mode == "strong") {
720 return $m1_strong . $this->doQuotes ( "", $m[2], "" );
721 } else if (($mode == "emstrong") || ($mode == "both")) {
722 return $this->doQuotes ( "", $pre.$m1_strong.$m[2], "em" );
723 } else if ($mode == "strongem") {
724 return "<strong>{$pre}{$m1_em}</strong>" . $this->doQuotes ( "", $m[2], "em" );
725 } else {
726 return $m[1] . $this->doQuotes ( "", $m[2], "strong" );
727 }
728 } else {
729 if ($mode == "strong") {
730 return $this->doQuotes ( $m[1], $m[2], ($m[1] == "") ? "both" : "strongem" );
731 } else if ($mode == "em") {
732 return $m1_em . $this->doQuotes ( "", $m[2], "" );
733 } else if ($mode == "emstrong") {
734 return "<em>{$pre}{$m1_strong}</em>" . $this->doQuotes ( "", $m[2], "strong" );
735 } else if (($mode == "strongem") || ($mode == "both")) {
736 return $this->doQuotes ( "", $pre.$m1_em.$m[2], "strong" );
737 } else {
738 return $m[1] . $this->doQuotes ( "", $m[2], "em" );
739 }
740 }
741 } else {
742 $text_strong = ($text == "") ? "" : "<strong>{$text}</strong>";
743 $text_em = ($text == "") ? "" : "<em>{$text}</em>";
744 if ($mode == "") {
745 return $pre . $text;
746 } else if ($mode == "em") {
747 return $pre . $text_em;
748 } else if ($mode == "strong") {
749 return $pre . $text_strong;
750 } else if ($mode == "strongem") {
751 return (($pre == "") && ($text == "")) ? "" : "<strong>{$pre}{$text_em}</strong>";
752 } else {
753 return (($pre == "") && ($text == "")) ? "" : "<em>{$pre}{$text_strong}</em>";
754 }
755 }
756 }
757
758 /* private */ function doHeadings( $text )
759 {
760 for ( $i = 6; $i >= 1; --$i ) {
761 $h = substr( "======", 0, $i );
762 $text = preg_replace( "/^{$h}([^=]+){$h}(\\s|$)/m",
763 "<h{$i}>\\1</h{$i}>\\2", $text );
764 }
765 return $text;
766 }
767
768 # Note: we have to do external links before the internal ones,
769 # and otherwise take great care in the order of things here, so
770 # that we don't end up interpreting some URLs twice.
771
772 /* private */ function replaceExternalLinks( $text )
773 {
774 wfProfileIn( "OutputPage::replaceExternalLinks" );
775 $text = $this->subReplaceExternalLinks( $text, "http", true );
776 $text = $this->subReplaceExternalLinks( $text, "https", true );
777 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
778 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
779 $text = $this->subReplaceExternalLinks( $text, "news", false );
780 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
781 wfProfileOut();
782 return $text;
783 }
784
785 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
786 {
787 global $wgUser, $printable;
788 global $wgAllowExternalImages;
789
790
791 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
792 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
793
794 # this is the list of separators that should be ignored if they
795 # are the last character of an URL but that should be included
796 # if they occur within the URL, e.g. "go to www.foo.com, where .."
797 # in this case, the last comma should not become part of the URL,
798 # but in "www.foo.com/123,2342,32.htm" it should.
799 $sep = ",;\.:";
800 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
801 $images = "gif|png|jpg|jpeg";
802
803 # PLEASE NOTE: The curly braces { } are not part of the regex,
804 # they are interpreted as part of the string (used to tell PHP
805 # that the content of the string should be inserted there).
806 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
807 "((?i){$images})([^{$uc}]|$)/";
808
809 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
810 $sk = $wgUser->getSkin();
811
812 if ( $autonumber and $wgAllowExternalImages) { # Use img tags only for HTTP urls
813 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
814 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
815 }
816 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
817 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
818 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
819 "</a>\\5", $s );
820 $s = str_replace( $unique, $protocol, $s );
821
822 $a = explode( "[{$protocol}:", " " . $s );
823 $s = array_shift( $a );
824 $s = substr( $s, 1 );
825
826 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
827 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
828
829 foreach ( $a as $line ) {
830 if ( preg_match( $e1, $line, $m ) ) {
831 $link = "{$protocol}:{$m[1]}";
832 $trail = $m[2];
833 if ( $autonumber ) { $text = "[" . ++$this->mAutonumber . "]"; }
834 else { $text = wfEscapeHTML( $link ); }
835 } else if ( preg_match( $e2, $line, $m ) ) {
836 $link = "{$protocol}:{$m[1]}";
837 $text = $m[2];
838 $trail = $m[3];
839 } else {
840 $s .= "[{$protocol}:" . $line;
841 continue;
842 }
843 if ( $printable == "yes") $paren = " (<i>" . htmlspecialchars ( $link ) . "</i>)";
844 else $paren = "";
845 $la = $sk->getExternalLinkAttributes( $link, $text );
846 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
847
848 }
849 return $s;
850 }
851
852 /* private */ function replaceInternalLinks( $s )
853 {
854 global $wgTitle, $wgUser, $wgLang;
855 global $wgLinkCache, $wgInterwikiMagic, $wgUseCategoryMagic;
856 global $wgNamespacesWithSubpages, $wgLanguageCode;
857 wfProfileIn( $fname = "OutputPage::replaceInternalLinks" );
858
859 wfProfileIn( "$fname-setup" );
860 $tc = Title::legalChars() . "#";
861 $sk = $wgUser->getSkin();
862
863 $a = explode( "[[", " " . $s );
864 $s = array_shift( $a );
865 $s = substr( $s, 1 );
866
867 $e1 = "/^([{$tc}]+)\\|([^]]+)]](.*)\$/sD";
868 $e2 = "/^([{$tc}]+)]](.*)\$/sD";
869 wfProfileOut();
870
871 foreach ( $a as $line ) {
872 wfProfileIn( "$fname-loop" );
873 if ( preg_match( $e1, $line, $m ) ) { # page with alternate text
874
875 $text = $m[2];
876 $trail = $m[3];
877
878 } else if ( preg_match( $e2, $line, $m ) ) { # page with normal text
879
880 $text = "";
881 $trail = $m[2];
882 }
883
884 else { # Invalid form; output directly
885 $s .= "[[" . $line ;
886 wfProfileOut();
887 continue;
888 }
889 if(substr($m[1],0,1)=="/") { # subpage
890 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
891 $m[1]=substr($m[1],1,strlen($m[1])-2);
892 $noslash=$m[1];
893
894 } else {
895 $noslash=substr($m[1],1);
896 }
897 if($wgNamespacesWithSubpages[$wgTitle->getNamespace()]) { # subpages allowed here
898 $link = $wgTitle->getPrefixedText(). "/" . trim($noslash);
899 if(!$text) {
900 $text= $m[1];
901 } # this might be changed for ugliness reasons
902 } else {
903 $link = $noslash; # no subpage allowed, use standard link
904 }
905 } else { # no subpage
906 $link = $m[1];
907 }
908
909 if ( preg_match( "/^((?:i|x|[a-z]{2,3})(?:-[a-z0-9]+)?|[A-Za-z\\x80-\\xff]+):(.*)\$/", $link, $m ) ) {
910 $pre = strtolower( $m[1] );
911 $suf = $m[2];
912 if ( $wgLang->getNsIndex( $pre ) ==
913 Namespace::getImage() ) {
914 $nt = Title::newFromText( $suf );
915 $name = $nt->getDBkey();
916 if ( "" == $text ) { $text = $nt->GetText(); }
917
918 $wgLinkCache->addImageLink( $name );
919 $s .= $sk->makeImageLink( $name,
920 wfImageUrl( $name ), $text );
921 $s .= $trail;
922 } else if ( "media" == $pre ) {
923 $nt = Title::newFromText( $suf );
924 $name = $nt->getDBkey();
925 if ( "" == $text ) { $text = $nt->GetText(); }
926
927 $wgLinkCache->addImageLink( $name );
928 $s .= $sk->makeMediaLink( $name,
929 wfImageUrl( $name ), $text );
930 $s .= $trail;
931 } else if ( isset($wgUseCategoryMagic) && $wgUseCategoryMagic && $pre == wfMsg ( "category" ) ) {
932 $l = $sk->makeLink ( $pre.":".ucfirst($m[2]) , ucfirst ( $m[2] ) ) ;
933 array_push ( $this->mCategoryLinks , $l ) ;
934 $s .= $trail ;
935 } else {
936 $l = $wgLang->getLanguageName( $pre );
937 if ( "" == $l or !$wgInterwikiMagic or
938 Namespace::isTalk( $wgTitle->getNamespace() ) ) {
939 if ( "" == $text ) { $text = $link; }
940 $s .= $sk->makeLink( $link, $text, "", $trail );
941 } else if ( $pre != $wgLanguageCode ) {
942 array_push( $this->mLanguageLinks, "$pre:$suf" );
943 $s .= $trail;
944 }
945 }
946 # } else if ( 0 == strcmp( "##", substr( $link, 0, 2 ) ) ) {
947 # $link = substr( $link, 2 );
948 # $s .= "<a name=\"{$link}\">{$text}</a>{$trail}";
949 } else {
950 if ( "" == $text ) { $text = $link; }
951 $s .= $sk->makeLink( $link, $text, "", $trail );
952 }
953 wfProfileOut();
954 }
955 wfProfileOut();
956 return $s;
957 }
958
959 # Some functions here used by doBlockLevels()
960 #
961 /* private */ function closeParagraph()
962 {
963 $result = "";
964 if ( 0 != strcmp( "p", $this->mLastSection ) &&
965 0 != strcmp( "", $this->mLastSection ) ) {
966 $result = "</" . $this->mLastSection . ">";
967 }
968 $this->mLastSection = "";
969 return $result;
970 }
971 # getCommon() returns the length of the longest common substring
972 # of both arguments, starting at the beginning of both.
973 #
974 /* private */ function getCommon( $st1, $st2 )
975 {
976 $fl = strlen( $st1 );
977 $shorter = strlen( $st2 );
978 if ( $fl < $shorter ) { $shorter = $fl; }
979
980 for ( $i = 0; $i < $shorter; ++$i ) {
981 if ( $st1{$i} != $st2{$i} ) { break; }
982 }
983 return $i;
984 }
985 # These next three functions open, continue, and close the list
986 # element appropriate to the prefix character passed into them.
987 #
988 /* private */ function openList( $char )
989 {
990 $result = $this->closeParagraph();
991
992 if ( "*" == $char ) { $result .= "<ul><li>"; }
993 else if ( "#" == $char ) { $result .= "<ol><li>"; }
994 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
995 else if ( ";" == $char ) {
996 $result .= "<dl><dt>";
997 $this->mDTopen = true;
998 }
999 else { $result = "<!-- ERR 1 -->"; }
1000
1001 return $result;
1002 }
1003
1004 /* private */ function nextItem( $char )
1005 {
1006 if ( "*" == $char || "#" == $char ) { return "</li><li>"; }
1007 else if ( ":" == $char || ";" == $char ) {
1008 $close = "</dd>";
1009 if ( $this->mDTopen ) { $close = "</dt>"; }
1010 if ( ";" == $char ) {
1011 $this->mDTopen = true;
1012 return $close . "<dt>";
1013 } else {
1014 $this->mDTopen = false;
1015 return $close . "<dd>";
1016 }
1017 }
1018 return "<!-- ERR 2 -->";
1019 }
1020
1021 /* private */function closeList( $char )
1022 {
1023 if ( "*" == $char ) { return "</li></ul>"; }
1024 else if ( "#" == $char ) { return "</li></ol>"; }
1025 else if ( ":" == $char ) {
1026 if ( $this->mDTopen ) {
1027 $this->mDTopen = false;
1028 return "</dt></dl>";
1029 } else {
1030 return "</dd></dl>";
1031 }
1032 }
1033 return "<!-- ERR 3 -->";
1034 }
1035
1036 /* private */ function doBlockLevels( $text, $linestart )
1037 {
1038 wfProfileIn( "OutputPage::doBlockLevels" );
1039 # Parsing through the text line by line. The main thing
1040 # happening here is handling of block-level elements p, pre,
1041 # and making lists from lines starting with * # : etc.
1042 #
1043 $a = explode( "\n", $text );
1044 $text = $lastPref = "";
1045 $this->mDTopen = $inBlockElem = false;
1046
1047 if ( ! $linestart ) { $text .= array_shift( $a ); }
1048 foreach ( $a as $t ) {
1049 if ( "" != $text ) { $text .= "\n"; }
1050
1051 $oLine = $t;
1052 $opl = strlen( $lastPref );
1053 $npl = strspn( $t, "*#:;" );
1054 $pref = substr( $t, 0, $npl );
1055 $pref2 = str_replace( ";", ":", $pref );
1056 $t = substr( $t, $npl );
1057
1058 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1059 $text .= $this->nextItem( substr( $pref, -1 ) );
1060
1061 if ( ";" == substr( $pref, -1 ) ) {
1062 $cpos = strpos( $t, ":" );
1063 if ( ! ( false === $cpos ) ) {
1064 $term = substr( $t, 0, $cpos );
1065 $text .= $term . $this->nextItem( ":" );
1066 $t = substr( $t, $cpos + 1 );
1067 }
1068 }
1069 } else if (0 != $npl || 0 != $opl) {
1070 $cpl = $this->getCommon( $pref, $lastPref );
1071
1072 while ( $cpl < $opl ) {
1073 $text .= $this->closeList( $lastPref{$opl-1} );
1074 --$opl;
1075 }
1076 if ( $npl <= $cpl && $cpl > 0 ) {
1077 $text .= $this->nextItem( $pref{$cpl-1} );
1078 }
1079 while ( $npl > $cpl ) {
1080 $char = substr( $pref, $cpl, 1 );
1081 $text .= $this->openList( $char );
1082
1083 if ( ";" == $char ) {
1084 $cpos = strpos( $t, ":" );
1085 if ( ! ( false === $cpos ) ) {
1086 $term = substr( $t, 0, $cpos );
1087 $text .= $term . $this->nextItem( ":" );
1088 $t = substr( $t, $cpos + 1 );
1089 }
1090 }
1091 ++$cpl;
1092 }
1093 $lastPref = $pref2;
1094 }
1095 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1096 if ( preg_match(
1097 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1098 $text .= $this->closeParagraph();
1099 $inBlockElem = true;
1100 }
1101 if ( ! $inBlockElem ) {
1102 if ( " " == $t{0} ) {
1103 $newSection = "pre";
1104 # $t = wfEscapeHTML( $t );
1105 }
1106 else { $newSection = "p"; }
1107
1108 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1109 $text .= $this->closeParagraph();
1110 $text .= "<" . $newSection . ">";
1111 } else if ( 0 != strcmp( $this->mLastSection,
1112 $newSection ) ) {
1113 $text .= $this->closeParagraph();
1114 if ( 0 != strcmp( "p", $newSection ) ) {
1115 $text .= "<" . $newSection . ">";
1116 }
1117 }
1118 $this->mLastSection = $newSection;
1119 }
1120 if ( $inBlockElem &&
1121 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1122 $inBlockElem = false;
1123 }
1124 }
1125 $text .= $t;
1126 }
1127 while ( $npl ) {
1128 $text .= $this->closeList( $pref2{$npl-1} );
1129 --$npl;
1130 }
1131 if ( "" != $this->mLastSection ) {
1132 if ( "p" != $this->mLastSection ) {
1133 $text .= "</" . $this->mLastSection . ">";
1134 }
1135 $this->mLastSection = "";
1136 }
1137 wfProfileOut();
1138 return $text;
1139 }
1140
1141 /* private */ function replaceVariables( $text )
1142 {
1143 global $wgLang;
1144 wfProfileIn( "OutputPage:replaceVariables" );
1145
1146 /* As with sigs, use server's local time --
1147 ensure this is appropriate for your audience! */
1148 $v = date( "m" );
1149 $mw =& MagicWord::get( MAG_CURRENTMONTH );
1150 $text = $mw->replace( $v, $text );
1151
1152 $v = $wgLang->getMonthName( date( "n" ) );
1153 $mw =& MagicWord::get( MAG_CURRENTMONTHNAME );
1154 $text = $mw->replace( $v, $text );
1155
1156 $v = $wgLang->getMonthNameGen( date( "n" ) );
1157 $mw =& MagicWord::get( MAG_CURRENTMONTHNAMEGEN );
1158 $text = $mw->replace( $v, $text );
1159
1160 $v = date( "j" );
1161 $mw = MagicWord::get( MAG_CURRENTDAY );
1162 $text = $mw->replace( $v, $text );
1163
1164 $v = $wgLang->getWeekdayName( date( "w" )+1 );
1165 $mw =& MagicWord::get( MAG_CURRENTDAYNAME );
1166 $text = $mw->replace( $v, $text );
1167
1168 $v = date( "Y" );
1169 $mw =& MagicWord::get( MAG_CURRENTYEAR );
1170 $text = $mw->replace( $v, $text );
1171
1172 $v = $wgLang->time( wfTimestampNow(), false );
1173 $mw =& MagicWord::get( MAG_CURRENTTIME );
1174 $text = $mw->replace( $v, $text );
1175
1176 $mw =& MagicWord::get( MAG_NUMBEROFARTICLES );
1177 if ( $mw->match( $text ) ) {
1178 $v = wfNumberOfArticles();
1179 $text = $mw->replace( $v, $text );
1180 }
1181 wfProfileOut();
1182 return $text;
1183 }
1184
1185 /* private */ function removeHTMLtags( $text )
1186 {
1187 wfProfileIn( "OutputPage::removeHTMLtags" );
1188 $htmlpairs = array( # Tags that must be closed
1189 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1190 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1191 "strike", "strong", "tt", "var", "div", "center",
1192 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1193 "ruby", "rt" , "rb" , "rp"
1194 );
1195 $htmlsingle = array(
1196 "br", "p", "hr", "li", "dt", "dd"
1197 );
1198 $htmlnest = array( # Tags that can be nested--??
1199 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1200 "dl", "font", "big", "small", "sub", "sup"
1201 );
1202 $tabletags = array( # Can only appear inside table
1203 "td", "th", "tr"
1204 );
1205
1206 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1207 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1208
1209 $htmlattrs = array( # Allowed attributes--no scripting, etc.
1210 "title", "align", "lang", "dir", "width", "height",
1211 "bgcolor", "clear", /* BR */ "noshade", /* HR */
1212 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
1213 /* FONT */ "type", "start", "value", "compact",
1214 /* For various lists, mostly deprecated but safe */
1215 "summary", "width", "border", "frame", "rules",
1216 "cellspacing", "cellpadding", "valign", "char",
1217 "charoff", "colgroup", "col", "span", "abbr", "axis",
1218 "headers", "scope", "rowspan", "colspan", /* Tables */
1219 "id", "class", "name", "style" /* For CSS */
1220 );
1221
1222 # Remove HTML comments
1223 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1224
1225 $bits = explode( "<", $text );
1226 $text = array_shift( $bits );
1227 $tagstack = array(); $tablestack = array();
1228
1229 foreach ( $bits as $x ) {
1230 $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
1231 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1232 $x, $regs );
1233 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1234 error_reporting( $prev );
1235
1236 $badtag = 0 ;
1237 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1238 # Check our stack
1239 if ( $slash ) {
1240 # Closing a tag...
1241 if ( ! in_array( $t, $htmlsingle ) &&
1242 ( $ot = array_pop( $tagstack ) ) != $t ) {
1243 array_push( $tagstack, $ot );
1244 $badtag = 1;
1245 } else {
1246 if ( $t == "table" ) {
1247 $tagstack = array_pop( $tablestack );
1248 }
1249 $newparams = "";
1250 }
1251 } else {
1252 # Keep track for later
1253 if ( in_array( $t, $tabletags ) &&
1254 ! in_array( "table", $tagstack ) ) {
1255 $badtag = 1;
1256 } else if ( in_array( $t, $tagstack ) &&
1257 ! in_array ( $t , $htmlnest ) ) {
1258 $badtag = 1 ;
1259 } else if ( ! in_array( $t, $htmlsingle ) ) {
1260 if ( $t == "table" ) {
1261 array_push( $tablestack, $tagstack );
1262 $tagstack = array();
1263 }
1264 array_push( $tagstack, $t );
1265 }
1266 # Strip non-approved attributes from the tag
1267 $newparams = preg_replace(
1268 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
1269 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
1270 $params);
1271 }
1272 if ( ! $badtag ) {
1273 $rest = str_replace( ">", "&gt;", $rest );
1274 $text .= "<$slash$t$newparams$brace$rest";
1275 continue;
1276 }
1277 }
1278 $text .= "&lt;" . str_replace( ">", "&gt;", $x);
1279 }
1280 # Close off any remaining tags
1281 while ( $t = array_pop( $tagstack ) ) {
1282 $text .= "</$t>\n";
1283 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1284 }
1285 wfProfileOut();
1286 return $text;
1287 }
1288
1289
1290 /*
1291 *
1292 * This function accomplishes several tasks:
1293 * 1) Auto-number headings if that option is enabled
1294 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1295 * 3) Add a Table of contents on the top for users who have enabled the option
1296 * 4) Auto-anchor headings
1297 *
1298 * It loops through all headlines, collects the necessary data, then splits up the
1299 * string and re-inserts the newly formatted headlines.
1300 *
1301 * */
1302 /* private */ function formatHeadings( $text )
1303 {
1304 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1305 $nh=$wgUser->getOption( "numberheadings" );
1306 $st=$wgUser->getOption( "showtoc" );
1307 if(!$wgTitle->userCanEdit()) {
1308 $es=0;
1309 $esr=0;
1310 } else {
1311 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1312 $esr=$wgUser->getID() && $wgUser->getOption( "editsectiononrightclick" );
1313 }
1314 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML, do not
1315 # add TOC
1316 $mw =& MagicWord::get( MAG_NOTOC );
1317 $st = ! $mw->matchAndRemove( $text );
1318
1319 # never add the TOC to the Main Page. This is an entry page that should not
1320 # be more than 1-2 screens large anyway
1321 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1322
1323 # We need this to perform operations on the HTML
1324 $sk=$wgUser->getSkin();
1325
1326 # Get all headlines for numbering them and adding funky stuff like [edit]
1327 # links
1328 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1329
1330 # headline counter
1331 $c=0;
1332
1333 # Ugh .. the TOC should have neat indentation levels which can be
1334 # passed to the skin functions. These are determined here
1335 foreach($matches[3] as $headline) {
1336 if($level) { $prevlevel=$level;}
1337 $level=$matches[1][$c];
1338 if(($nh||$st) && $prevlevel && $level>$prevlevel) {
1339
1340 $h[$level]=0; // reset when we enter a new level
1341 $toc.=$sk->tocIndent($level-$prevlevel);
1342 $toclevel+=$level-$prevlevel;
1343
1344 }
1345 if(($nh||$st) && $level<$prevlevel) {
1346 $h[$level+1]=0; // reset when we step back a level
1347 $toc.=$sk->tocUnindent($prevlevel-$level);
1348 $toclevel-=$prevlevel-$level;
1349
1350 }
1351 $h[$level]++; // count number of headlines for each level
1352
1353 if($nh||$st) {
1354 for($i=1;$i<=$level;$i++) {
1355 if($h[$i]) {
1356 if($dot) {$numbering.=".";}
1357 $numbering.=$h[$i];
1358 $dot=1;
1359 }
1360 }
1361 }
1362
1363
1364 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1365 $tocline=$canonized_headline;
1366 $canonized_headline=str_replace('"',"",$canonized_headline);
1367 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1368 $refer[$c]=$canonized_headline;
1369 $refers[$canonized_headline]++; // count how many in assoc. array so we can track dupes in anchors
1370 $refcount[$c]=$refers[$canonized_headline];
1371 if($nh||$st) {
1372 $tocline=$numbering ." ". $tocline;
1373 if($nh) {
1374 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1375 }
1376 }
1377 $anchor=$canonized_headline;
1378 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1379 if($st) {
1380 $toc.=$sk->tocLine($anchor,$tocline,$toclevel);
1381 }
1382 if($es && !isset($wpPreview)) {
1383 $head[$c].=$sk->editSectionLink($c+1);
1384 }
1385 $head[$c].="<H".$level.$matches[2][$c]
1386 ."<a name=\"".$anchor."\">"
1387 .$headline
1388 ."</a>"
1389 ."</H".$level.">";
1390 if($esr && !isset($wpPreview)) {
1391 $head[$c]=$sk->editSectionScript($c+1,$head[$c]);
1392 }
1393 $numbering="";
1394 $c++;
1395 $dot=0;
1396 }
1397
1398 if($st) {
1399 $toclines=$c;
1400 $toc.=$sk->tocUnindent($toclevel);
1401 $toc=$sk->tocTable($toc);
1402 }
1403
1404 // split up and insert constructed headlines
1405
1406 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1407 $i=0;
1408
1409
1410 foreach($blocks as $block) {
1411 if(($es) && !isset($wpPreview) && $c>0 && $i==0) {
1412 # This is the [edit] link that appears for the top block of text when
1413 # section editing is enabled
1414 $full.=$sk->editSectionLink(0);
1415 }
1416 $full.=$block;
1417 if($st && $toclines>3 && !$i) {
1418 # Let's add a top anchor just in case we want to link to the top of the page
1419 $full="<a name=\"top\"></a>".$full.$toc;
1420 }
1421
1422 $full.=$head[$i];
1423 $i++;
1424 }
1425 return $full;
1426 }
1427
1428 /* private */ function magicISBN( $text )
1429 {
1430 global $wgLang;
1431
1432 $a = split( "ISBN ", " $text" );
1433 if ( count ( $a ) < 2 ) return $text;
1434 $text = substr( array_shift( $a ), 1);
1435 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1436
1437 foreach ( $a as $x ) {
1438 $isbn = $blank = "" ;
1439 while ( " " == $x{0} ) {
1440 $blank .= " ";
1441 $x = substr( $x, 1 );
1442 }
1443 while ( strstr( $valid, $x{0} ) != false ) {
1444 $isbn .= $x{0};
1445 $x = substr( $x, 1 );
1446 }
1447 $num = str_replace( "-", "", $isbn );
1448 $num = str_replace( " ", "", $num );
1449
1450 if ( "" == $num ) {
1451 $text .= "ISBN $blank$x";
1452 } else {
1453 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1454 "Booksources"), "isbn={$num}" ) . "\" CLASS=\"internal\">ISBN $isbn</a>";
1455 $text .= $x;
1456 }
1457 }
1458 return $text;
1459 }
1460
1461 /* private */ function magicRFC( $text )
1462 {
1463 return $text;
1464 }
1465
1466 /* private */ function headElement()
1467 {
1468 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding, $wgLang;
1469
1470 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1471
1472 if ( "" == $this->mHTMLtitle ) {
1473 $this->mHTMLtitle = $this->mPagetitle;
1474 }
1475 $rtl = $wgLang->isRTL() ? " dir='RTL'" : "";
1476 $ret .= "<html lang=\"$wgLanguageCode\"$rtl><head><title>{$this->mHTMLtitle}</title>\n";
1477 array_push( $this->mMetatags, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1478 foreach ( $this->mMetatags as $tag ) {
1479 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1480 $a = "http-equiv";
1481 $tag[0] = substr( $tag[0], 5 );
1482 } else {
1483 $a = "name";
1484 }
1485 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1486 }
1487 $p = $this->mRobotpolicy;
1488 if ( "" == $p ) { $p = "index,follow"; }
1489 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1490
1491 if ( count( $this->mKeywords ) > 0 ) {
1492 $ret .= "<meta name=\"keywords\" content=\"" .
1493 implode( ",", $this->mKeywords ) . "\">\n";
1494 }
1495 foreach ( $this->mLinktags as $tag ) {
1496 $ret .= "<link ";
1497 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1498 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1499 $ret .= "href=\"{$tag[2]}\">\n";
1500 }
1501 $sk = $wgUser->getSkin();
1502 $ret .= $sk->getHeadScripts();
1503 $ret .= $sk->getUserStyles();
1504
1505 $ret .= "</head>\n";
1506 return $ret;
1507 }
1508 }
1509
1510 ?>