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