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