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