Force redirect headers to use absolute URLs as per spec (though
[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>";
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;
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 ) { return "</li></ul>"; }
1087 else if ( "#" == $char ) { return "</li></ol>"; }
1088 else if ( ":" == $char ) {
1089 if ( $this->mDTopen ) {
1090 $this->mDTopen = false;
1091 return "</dt></dl>";
1092 } else {
1093 return "</dd></dl>";
1094 }
1095 }
1096 return "<!-- ERR 3 -->";
1097 }
1098
1099 /* private */ function doBlockLevels( $text, $linestart )
1100 {
1101 $fname = "OutputPage::doBlockLevels";
1102 wfProfileIn( $fname );
1103 # Parsing through the text line by line. The main thing
1104 # happening here is handling of block-level elements p, pre,
1105 # and making lists from lines starting with * # : etc.
1106 #
1107 $a = explode( "\n", $text );
1108 $text = $lastPref = "";
1109 $this->mDTopen = $inBlockElem = false;
1110
1111 if ( ! $linestart ) { $text .= array_shift( $a ); }
1112 foreach ( $a as $t ) {
1113 if ( "" != $text ) { $text .= "\n"; }
1114
1115 $oLine = $t;
1116 $opl = strlen( $lastPref );
1117 $npl = strspn( $t, "*#:;" );
1118 $pref = substr( $t, 0, $npl );
1119 $pref2 = str_replace( ";", ":", $pref );
1120 $t = substr( $t, $npl );
1121
1122 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1123 $text .= $this->nextItem( substr( $pref, -1 ) );
1124
1125 if ( ";" == substr( $pref, -1 ) ) {
1126 $cpos = strpos( $t, ":" );
1127 if ( ! ( false === $cpos ) ) {
1128 $term = substr( $t, 0, $cpos );
1129 $text .= $term . $this->nextItem( ":" );
1130 $t = substr( $t, $cpos + 1 );
1131 }
1132 }
1133 } else if (0 != $npl || 0 != $opl) {
1134 $cpl = $this->getCommon( $pref, $lastPref );
1135
1136 while ( $cpl < $opl ) {
1137 $text .= $this->closeList( $lastPref{$opl-1} );
1138 --$opl;
1139 }
1140 if ( $npl <= $cpl && $cpl > 0 ) {
1141 $text .= $this->nextItem( $pref{$cpl-1} );
1142 }
1143 while ( $npl > $cpl ) {
1144 $char = substr( $pref, $cpl, 1 );
1145 $text .= $this->openList( $char );
1146
1147 if ( ";" == $char ) {
1148 $cpos = strpos( $t, ":" );
1149 if ( ! ( false === $cpos ) ) {
1150 $term = substr( $t, 0, $cpos );
1151 $text .= $term . $this->nextItem( ":" );
1152 $t = substr( $t, $cpos + 1 );
1153 }
1154 }
1155 ++$cpl;
1156 }
1157 $lastPref = $pref2;
1158 }
1159 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1160 if ( preg_match(
1161 "/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6)/i", $t ) ) {
1162 $text .= $this->closeParagraph();
1163 $inBlockElem = true;
1164 }
1165 if ( ! $inBlockElem ) {
1166 if ( " " == $t{0} ) {
1167 $newSection = "pre";
1168 # $t = wfEscapeHTML( $t );
1169 }
1170 else { $newSection = "p"; }
1171
1172 if ( 0 == strcmp( "", trim( $oLine ) ) ) {
1173 $text .= $this->closeParagraph();
1174 $text .= "<" . $newSection . ">";
1175 } else if ( 0 != strcmp( $this->mLastSection,
1176 $newSection ) ) {
1177 $text .= $this->closeParagraph();
1178 if ( 0 != strcmp( "p", $newSection ) ) {
1179 $text .= "<" . $newSection . ">";
1180 }
1181 }
1182 $this->mLastSection = $newSection;
1183 }
1184 if ( $inBlockElem &&
1185 preg_match( "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6)/i", $t ) ) {
1186 $inBlockElem = false;
1187 }
1188 }
1189 $text .= $t;
1190 }
1191 while ( $npl ) {
1192 $text .= $this->closeList( $pref2{$npl-1} );
1193 --$npl;
1194 }
1195 if ( "" != $this->mLastSection ) {
1196 if ( "p" != $this->mLastSection ) {
1197 $text .= "</" . $this->mLastSection . ">";
1198 }
1199 $this->mLastSection = "";
1200 }
1201 wfProfileOut( $fname );
1202 return $text;
1203 }
1204
1205 /* private */ function replaceVariables( $text )
1206 {
1207 global $wgLang;
1208 $fname = "OutputPage::replaceVariables";
1209 wfProfileIn( $fname );
1210
1211
1212 # Basic variables
1213 # See Language.php for the definition of each magic word
1214
1215 # As with sigs, this uses the server's local time -- ensure
1216 # this is appropriate for your audience!
1217 $v = date( "m" );
1218 $mw =& MagicWord::get( MAG_CURRENTMONTH );
1219 $text = $mw->replace( $v, $text );
1220
1221 $v = $wgLang->getMonthName( date( "n" ) );
1222 $mw =& MagicWord::get( MAG_CURRENTMONTHNAME );
1223 $text = $mw->replace( $v, $text );
1224
1225 $v = $wgLang->getMonthNameGen( date( "n" ) );
1226 $mw =& MagicWord::get( MAG_CURRENTMONTHNAMEGEN );
1227 $text = $mw->replace( $v, $text );
1228
1229 $v = date( "j" );
1230 $mw = MagicWord::get( MAG_CURRENTDAY );
1231 $text = $mw->replace( $v, $text );
1232
1233 $v = $wgLang->getWeekdayName( date( "w" )+1 );
1234 $mw =& MagicWord::get( MAG_CURRENTDAYNAME );
1235 $text = $mw->replace( $v, $text );
1236
1237 $v = date( "Y" );
1238 $mw =& MagicWord::get( MAG_CURRENTYEAR );
1239 $text = $mw->replace( $v, $text );
1240
1241 $v = $wgLang->time( wfTimestampNow(), false );
1242 $mw =& MagicWord::get( MAG_CURRENTTIME );
1243 $text = $mw->replace( $v, $text );
1244
1245 $mw =& MagicWord::get( MAG_NUMBEROFARTICLES );
1246 if ( $mw->match( $text ) ) {
1247 $v = wfNumberOfArticles();
1248 $text = $mw->replace( $v, $text );
1249 }
1250
1251 # "Variables" with an additional parameter e.g. {{MSG:wikipedia}}
1252 # The callbacks are at the bottom of this file
1253 $mw =& MagicWord::get( MAG_MSG );
1254 $text = $mw->substituteCallback( $text, "wfReplaceMsgVar" );
1255
1256 $mw =& MagicWord::get( MAG_MSGNW );
1257 $text = $mw->substituteCallback( $text, "wfReplaceMsgnwVar" );
1258
1259 wfProfileOut( $fname );
1260 return $text;
1261 }
1262
1263 # Cleans up HTML, removes dangerous tags and attributes
1264 /* private */ function removeHTMLtags( $text )
1265 {
1266 $fname = "OutputPage::removeHTMLtags";
1267 wfProfileIn( $fname );
1268 $htmlpairs = array( # Tags that must be closed
1269 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1270 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1271 "strike", "strong", "tt", "var", "div", "center",
1272 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1273 "ruby", "rt" , "rb" , "rp"
1274 );
1275 $htmlsingle = array(
1276 "br", "p", "hr", "li", "dt", "dd"
1277 );
1278 $htmlnest = array( # Tags that can be nested--??
1279 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1280 "dl", "font", "big", "small", "sub", "sup"
1281 );
1282 $tabletags = array( # Can only appear inside table
1283 "td", "th", "tr"
1284 );
1285
1286 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1287 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1288
1289 $htmlattrs = $this->getHTMLattrs () ;
1290
1291 # Remove HTML comments
1292 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1293
1294 $bits = explode( "<", $text );
1295 $text = array_shift( $bits );
1296 $tagstack = array(); $tablestack = array();
1297
1298 foreach ( $bits as $x ) {
1299 $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
1300 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1301 $x, $regs );
1302 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1303 error_reporting( $prev );
1304
1305 $badtag = 0 ;
1306 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1307 # Check our stack
1308 if ( $slash ) {
1309 # Closing a tag...
1310 if ( ! in_array( $t, $htmlsingle ) &&
1311 ( $ot = array_pop( $tagstack ) ) != $t ) {
1312 array_push( $tagstack, $ot );
1313 $badtag = 1;
1314 } else {
1315 if ( $t == "table" ) {
1316 $tagstack = array_pop( $tablestack );
1317 }
1318 $newparams = "";
1319 }
1320 } else {
1321 # Keep track for later
1322 if ( in_array( $t, $tabletags ) &&
1323 ! in_array( "table", $tagstack ) ) {
1324 $badtag = 1;
1325 } else if ( in_array( $t, $tagstack ) &&
1326 ! in_array ( $t , $htmlnest ) ) {
1327 $badtag = 1 ;
1328 } else if ( ! in_array( $t, $htmlsingle ) ) {
1329 if ( $t == "table" ) {
1330 array_push( $tablestack, $tagstack );
1331 $tagstack = array();
1332 }
1333 array_push( $tagstack, $t );
1334 }
1335 # Strip non-approved attributes from the tag
1336 $newparams = preg_replace(
1337 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
1338 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
1339 $params);
1340 }
1341 if ( ! $badtag ) {
1342 $rest = str_replace( ">", "&gt;", $rest );
1343 $text .= "<$slash$t$newparams$brace$rest";
1344 continue;
1345 }
1346 }
1347 $text .= "&lt;" . str_replace( ">", "&gt;", $x);
1348 }
1349 # Close off any remaining tags
1350 while ( $t = array_pop( $tagstack ) ) {
1351 $text .= "</$t>\n";
1352 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1353 }
1354 wfProfileOut( $fname );
1355 return $text;
1356 }
1357
1358
1359 /*
1360 *
1361 * This function accomplishes several tasks:
1362 * 1) Auto-number headings if that option is enabled
1363 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1364 * 3) Add a Table of contents on the top for users who have enabled the option
1365 * 4) Auto-anchor headings
1366 *
1367 * It loops through all headlines, collects the necessary data, then splits up the
1368 * string and re-inserts the newly formatted headlines.
1369 *
1370 * */
1371 /* private */ function formatHeadings( $text )
1372 {
1373 global $wgUser,$wgArticle,$wgTitle,$wpPreview;
1374 $nh=$wgUser->getOption( "numberheadings" );
1375 $st=$wgUser->getOption( "showtoc" );
1376 if(!$wgTitle->userCanEdit()) {
1377 $es=0;
1378 $esr=0;
1379 } else {
1380 $es=$wgUser->getID() && $wgUser->getOption( "editsection" );
1381 $esr=$wgUser->getID() && $wgUser->getOption( "editsectiononrightclick" );
1382 }
1383
1384 # Inhibit editsection links if requested in the page
1385 if ($es) {
1386 $esw=& MagicWord::get(MAG_NOEDITSECTION);
1387 if ($esw->matchAndRemove( $text )) {
1388 $es=0;
1389 }
1390 }
1391 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
1392 # do not add TOC
1393 $mw =& MagicWord::get( MAG_NOTOC );
1394 if ($mw->matchAndRemove( $text ))
1395 {
1396 $st = 0;
1397 }
1398
1399 # never add the TOC to the Main Page. This is an entry page that should not
1400 # be more than 1-2 screens large anyway
1401 if($wgTitle->getPrefixedText()==wfMsg("mainpage")) {$st=0;}
1402
1403 # We need this to perform operations on the HTML
1404 $sk=$wgUser->getSkin();
1405
1406 # Get all headlines for numbering them and adding funky stuff like [edit]
1407 # links
1408 preg_match_all("/<H([1-6])(.*?>)(.*?)<\/H[1-6]>/i",$text,$matches);
1409
1410 # headline counter
1411 $c=0;
1412
1413 # Ugh .. the TOC should have neat indentation levels which can be
1414 # passed to the skin functions. These are determined here
1415 foreach($matches[3] as $headline) {
1416 if($level) { $prevlevel=$level;}
1417 $level=$matches[1][$c];
1418 if(($nh||$st) && $prevlevel && $level>$prevlevel) {
1419
1420 $h[$level]=0; // reset when we enter a new level
1421 $toc.=$sk->tocIndent($level-$prevlevel);
1422 $toclevel+=$level-$prevlevel;
1423
1424 }
1425 if(($nh||$st) && $level<$prevlevel) {
1426 $h[$level+1]=0; // reset when we step back a level
1427 $toc.=$sk->tocUnindent($prevlevel-$level);
1428 $toclevel-=$prevlevel-$level;
1429
1430 }
1431 $h[$level]++; // count number of headlines for each level
1432
1433 if($nh||$st) {
1434 for($i=1;$i<=$level;$i++) {
1435 if($h[$i]) {
1436 if($dot) {$numbering.=".";}
1437 $numbering.=$h[$i];
1438 $dot=1;
1439 }
1440 }
1441 }
1442
1443 // The canonized header is a version of the header text safe to use for links
1444
1445 $canonized_headline=preg_replace("/<.*?>/","",$headline); // strip out HTML
1446 $tocline = trim( $canonized_headline );
1447 $canonized_headline=str_replace('"',"",$canonized_headline);
1448 $canonized_headline=str_replace(" ","_",trim($canonized_headline));
1449 $refer[$c]=$canonized_headline;
1450 $refers[$canonized_headline]++; // count how many in assoc. array so we can track dupes in anchors
1451 $refcount[$c]=$refers[$canonized_headline];
1452
1453 // Prepend the number to the heading text
1454
1455 if($nh||$st) {
1456 $tocline=$numbering ." ". $tocline;
1457
1458 // Don't number the heading if it is the only one (looks silly)
1459 if($nh && count($matches[3]) > 1) {
1460 $headline=$numbering . " " . $headline; // the two are different if the line contains a link
1461 }
1462 }
1463
1464 // Create the anchor for linking from the TOC to the section
1465
1466 $anchor=$canonized_headline;
1467 if($refcount[$c]>1) {$anchor.="_".$refcount[$c];}
1468 if($st) {
1469 $toc.=$sk->tocLine($anchor,$tocline,$toclevel);
1470 }
1471 if($es && !isset($wpPreview)) {
1472 $head[$c].=$sk->editSectionLink($c+1);
1473 }
1474
1475 // Put it all together
1476
1477 $head[$c].="<h".$level.$matches[2][$c]
1478 ."<a name=\"".$anchor."\">"
1479 .$headline
1480 ."</a>"
1481 ."</h".$level.">";
1482
1483 // Add the edit section link
1484
1485 if($esr && !isset($wpPreview)) {
1486 $head[$c]=$sk->editSectionScript($c+1,$head[$c]);
1487 }
1488
1489 $numbering="";
1490 $c++;
1491 $dot=0;
1492 }
1493
1494 if($st) {
1495 $toclines=$c;
1496 $toc.=$sk->tocUnindent($toclevel);
1497 $toc=$sk->tocTable($toc);
1498 }
1499
1500 // split up and insert constructed headlines
1501
1502 $blocks=preg_split("/<H[1-6].*?>.*?<\/H[1-6]>/i",$text);
1503 $i=0;
1504
1505 foreach($blocks as $block) {
1506 if(($es) && !isset($wpPreview) && $c>0 && $i==0) {
1507 # This is the [edit] link that appears for the top block of text when
1508 # section editing is enabled
1509 $full.=$sk->editSectionLink(0);
1510 }
1511 $full.=$block;
1512 if($st && $toclines>3 && !$i) {
1513 # Let's add a top anchor just in case we want to link to the top of the page
1514 $full="<a name=\"top\"></a>".$full.$toc;
1515 }
1516
1517 $full.=$head[$i];
1518 $i++;
1519 }
1520
1521 return $full;
1522 }
1523
1524 /* private */ function magicISBN( $text )
1525 {
1526 global $wgLang;
1527
1528 $a = split( "ISBN ", " $text" );
1529 if ( count ( $a ) < 2 ) return $text;
1530 $text = substr( array_shift( $a ), 1);
1531 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1532
1533 foreach ( $a as $x ) {
1534 $isbn = $blank = "" ;
1535 while ( " " == $x{0} ) {
1536 $blank .= " ";
1537 $x = substr( $x, 1 );
1538 }
1539 while ( strstr( $valid, $x{0} ) != false ) {
1540 $isbn .= $x{0};
1541 $x = substr( $x, 1 );
1542 }
1543 $num = str_replace( "-", "", $isbn );
1544 $num = str_replace( " ", "", $num );
1545
1546 if ( "" == $num ) {
1547 $text .= "ISBN $blank$x";
1548 } else {
1549 $text .= "<a href=\"" . wfLocalUrlE( $wgLang->specialPage(
1550 "Booksources"), "isbn={$num}" ) . "\" class=\"internal\">ISBN $isbn</a>";
1551 $text .= $x;
1552 }
1553 }
1554 return $text;
1555 }
1556
1557 /* private */ function magicRFC( $text )
1558 {
1559 return $text;
1560 }
1561
1562 /* private */ function headElement()
1563 {
1564 global $wgDocType, $wgDTD, $wgUser, $wgLanguageCode, $wgOutputEncoding, $wgLang;
1565
1566 $ret = "<!DOCTYPE HTML PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
1567
1568 if ( "" == $this->mHTMLtitle ) {
1569 $this->mHTMLtitle = $this->mPagetitle;
1570 }
1571 $rtl = $wgLang->isRTL() ? " dir='RTL'" : "";
1572 $ret .= "<html lang=\"$wgLanguageCode\"$rtl><head><title>{$this->mHTMLtitle}</title>\n";
1573 array_push( $this->mMetatags, array( "http:Content-type", "text/html; charset={$wgOutputEncoding}" ) );
1574 foreach ( $this->mMetatags as $tag ) {
1575 if ( 0 == strcasecmp( "http:", substr( $tag[0], 0, 5 ) ) ) {
1576 $a = "http-equiv";
1577 $tag[0] = substr( $tag[0], 5 );
1578 } else {
1579 $a = "name";
1580 }
1581 $ret .= "<meta $a=\"{$tag[0]}\" content=\"{$tag[1]}\">\n";
1582 }
1583 $p = $this->mRobotpolicy;
1584 if ( "" == $p ) { $p = "index,follow"; }
1585 $ret .= "<meta name=\"robots\" content=\"$p\">\n";
1586
1587 if ( count( $this->mKeywords ) > 0 ) {
1588 $ret .= "<meta name=\"keywords\" content=\"" .
1589 implode( ",", $this->mKeywords ) . "\">\n";
1590 }
1591 foreach ( $this->mLinktags as $tag ) {
1592 $ret .= "<link ";
1593 if ( "" != $tag[0] ) { $ret .= "rel=\"{$tag[0]}\" "; }
1594 if ( "" != $tag[1] ) { $ret .= "rev=\"{$tag[1]}\" "; }
1595 $ret .= "href=\"{$tag[2]}\">\n";
1596 }
1597 $sk = $wgUser->getSkin();
1598 $ret .= $sk->getHeadScripts();
1599 $ret .= $sk->getUserStyles();
1600
1601 $ret .= "</head>\n";
1602 return $ret;
1603 }
1604 }
1605
1606 # Regex callbacks, used in OutputPage::replaceVariables
1607
1608 # Just get rid of the dangerous stuff
1609 # Necessary because replaceVariables is called after removeHTMLtags,
1610 # and message text can come from any user
1611 function wfReplaceMsgVar( $matches ) {
1612 global $wgOut;
1613 $text = $wgOut->removeHTMLtags( wfMsg( $matches[1] ) );
1614 return $text;
1615 }
1616
1617 # Effective <nowiki></nowiki>
1618 # Not real <nowiki> because this is called after nowiki sections are processed
1619 function wfReplaceMsgnwVar( $matches ) {
1620 $text = wfEscapeWikiText( wfMsg( $matches[1] ) );
1621 return $text;
1622 }
1623
1624 ?>