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