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