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