pre area fixes, ampersand hack
[lhc/web/wiklou.git] / includes / Parser.php
1 <?php
2
3 include_once('Tokenizer.php');
4
5 if( $GLOBALS['wgUseWikiHiero'] ){
6 include_once('wikihiero.php');
7 }
8
9 # PHP Parser
10 #
11 # Processes wiki markup
12 #
13 # There are two main entry points into the Parser class: parse() and preSaveTransform().
14 # The parse() function produces HTML output, preSaveTransform() produces altered wiki markup.
15 #
16 # Globals used:
17 # objects: $wgLang, $wgDateFormatter, $wgLinkCache, $wgCurParser
18 #
19 # NOT $wgArticle, $wgUser or $wgTitle. Keep them away!
20 #
21 # settings: $wgUseTex*, $wgUseCategoryMagic*, $wgUseDynamicDates*, $wgInterwikiMagic*,
22 # $wgNamespacesWithSubpages, $wgLanguageCode, $wgAllowExternalImages*,
23 # $wgLocaltimezone
24 #
25 # * only within ParserOptions
26 #
27 #
28 #----------------------------------------
29 # Variable substitution O(N^2) attack
30 #-----------------------------------------
31 # Without countermeasures, it would be possible to attack the parser by saving a page
32 # filled with a large number of inclusions of large pages. The size of the generated
33 # page would be proportional to the square of the input size. Hence, we limit the number
34 # of inclusions of any given page, thus bringing any attack back to O(N).
35 #
36 define( "MAX_INCLUDE_REPEAT", 5 );
37
38 # Recursion depth of variable/inclusion evaluation
39 define( "MAX_INCLUDE_PASSES", 3 );
40
41 # Allowed values for $mOutputType
42 define( "OT_HTML", 1 );
43 define( "OT_WIKI", 2 );
44 define( "OT_MSG", 3 );
45
46 # prefix for escaping, used in two functions at least
47 define( "UNIQ_PREFIX", "NaodW29");
48
49 class Parser
50 {
51 # Cleared with clearState():
52 var $mOutput, $mAutonumber, $mLastSection, $mDTopen, $mStripState = array();
53 var $mVariables, $mIncludeCount;
54
55 # Temporary:
56 var $mOptions, $mTitle, $mOutputType;
57
58 function Parser()
59 {
60 $this->clearState();
61 }
62
63 function clearState()
64 {
65 $this->mOutput = new ParserOutput;
66 $this->mAutonumber = 0;
67 $this->mLastSection = "";
68 $this->mDTopen = false;
69 $this->mVariables = false;
70 $this->mIncludeCount = array();
71 $this->mStripState = array();
72 }
73
74 # First pass--just handle <nowiki> sections, pass the rest off
75 # to doWikiPass2() which does all the real work.
76 #
77 # Returns a ParserOutput
78 #
79 function parse( $text, &$title, $options, $linestart = true, $clearState = true )
80 {
81 $fname = "Parser::parse";
82 wfProfileIn( $fname );
83
84 if ( $clearState ) {
85 $this->clearState();
86 }
87
88 $this->mOptions = $options;
89 $this->mTitle =& $title;
90 $this->mOutputType = OT_HTML;
91
92 $stripState = NULL;
93 $text = $this->strip( $text, $this->mStripState );
94 $text = $this->doWikiPass2( $text, $linestart );
95 $text = $this->unstrip( $text, $this->mStripState );
96
97 $this->mOutput->setText( $text );
98 wfProfileOut( $fname );
99 return $this->mOutput;
100 }
101
102 /* static */ function getRandomString()
103 {
104 return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff));
105 }
106
107 # Replaces all occurences of <$tag>content</$tag> in the text
108 # with a random marker and returns the new text. the output parameter
109 # $content will be an associative array filled with data on the form
110 # $unique_marker => content.
111
112 /* static */ function extractTags($tag, $text, &$content, $uniq_prefix = ""){
113 $result = array();
114 $rnd = $uniq_prefix . '-' . $tag . Parser::getRandomString();
115 $content = array( );
116 $n = 1;
117 $stripped = "";
118
119 while ( "" != $text ) {
120 $p = preg_split( "/<\\s*$tag\\s*>/i", $text, 2 );
121 $stripped .= $p[0];
122 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) {
123 $text = "";
124 } else {
125 $q = preg_split( "/<\\/\\s*$tag\\s*>/i", $p[1], 2 );
126 $marker = $rnd . sprintf("%08X", $n++);
127 $content[$marker] = $q[0];
128 $stripped .= $marker;
129 $text = $q[1];
130 }
131 }
132 return $stripped;
133 }
134
135 # Strips <nowiki>, <pre> and <math>
136 # Returns the text, and fills an array with data needed in unstrip()
137 #
138 function strip( $text, &$state )
139 {
140 $render = ($this->mOutputType == OT_HTML);
141 $nowiki_content = array();
142 $hiero_content = array();
143 $math_content = array();
144 $pre_content = array();
145
146 # Replace any instances of the placeholders
147 $uniq_prefix = UNIQ_PREFIX;
148 $text = str_replace( $uniq_prefix, wfHtmlEscapeFirst( $uniq_prefix ), $text );
149
150 $text = Parser::extractTags("nowiki", $text, $nowiki_content, $uniq_prefix);
151 foreach( $nowiki_content as $marker => $content ){
152 if( $render ){
153 $nowiki_content[$marker] = wfEscapeHTMLTagsOnly( $content );
154 } else {
155 $nowiki_content[$marker] = "<nowiki>$content</nowiki>";
156 }
157 }
158
159 if( $GLOBALS['wgUseWikiHiero'] ){
160 $text = Parser::extractTags("hiero", $text, $hiero_content, $uniq_prefix);
161 foreach( $hiero_content as $marker => $content ){
162 if( $render ){
163 $hiero_content[$marker] = WikiHiero( $content, WH_MODE_HTML);
164 } else {
165 $hiero_content[$marker] = "<hiero>$content</hiero>";
166 }
167 }
168 }
169
170 if( $this->mOptions->getUseTeX() ){
171 $text = Parser::extractTags("math", $text, $math_content, $uniq_prefix);
172 foreach( $math_content as $marker => $content ){
173 if( $render ){
174 $math_content[$marker] = renderMath( $content );
175 } else {
176 $math_content[$marker] = "<math>$content</math>";
177 }
178 }
179 }
180
181 $text = Parser::extractTags("pre", $text, $pre_content, $uniq_prefix);
182 foreach( $pre_content as $marker => $content ){
183 if( $render ){
184 $pre_content[$marker] = "<pre>" . wfEscapeHTMLTagsOnly( $content ) . "</pre>";
185 } else {
186 $pre_content[$marker] = "<pre>$content</pre>";
187 }
188 }
189
190 # Must expand in reverse order, otherwise nested tags will be corrupted
191 $state = array( $pre_content, $math_content, $hiero_content, $nowiki_content );
192 return $text;
193 }
194
195 function unstrip( $text, &$state )
196 {
197 foreach( $state as $content_dict ){
198 foreach( $content_dict as $marker => $content ){
199 $text = str_replace( $marker, $content, $text );
200 }
201 }
202 return $text;
203 }
204
205 function categoryMagic ()
206 {
207 global $wgLang , $wgUser ;
208 if ( !$this->mOptions->getUseCategoryMagic() ) return ;
209 $id = $this->mTitle->getArticleID() ;
210 $cat = $wgLang->ucfirst ( wfMsg ( "category" ) ) ;
211 $ti = $this->mTitle->getText() ;
212 $ti = explode ( ":" , $ti , 2 ) ;
213 if ( $cat != $ti[0] ) return "" ;
214 $r = "<br break='all' />\n" ;
215
216 $articles = array() ;
217 $parents = array () ;
218 $children = array() ;
219
220
221 # $sk =& $this->mGetSkin();
222 $sk =& $wgUser->getSkin() ;
223
224 $data = array () ;
225 $sql1 = "SELECT DISTINCT cur_title,cur_namespace FROM cur,links WHERE l_to={$id} AND l_from=cur_id";
226 $sql2 = "SELECT DISTINCT cur_title,cur_namespace FROM cur,brokenlinks WHERE bl_to={$id} AND bl_from=cur_id" ;
227
228 $res = wfQuery ( $sql1, DB_READ ) ;
229 while ( $x = wfFetchObject ( $res ) ) $data[] = $x ;
230
231 $res = wfQuery ( $sql2, DB_READ ) ;
232 while ( $x = wfFetchObject ( $res ) ) $data[] = $x ;
233
234
235 foreach ( $data AS $x )
236 {
237 $t = $wgLang->getNsText ( $x->cur_namespace ) ;
238 if ( $t != "" ) $t .= ":" ;
239 $t .= $x->cur_title ;
240
241 $y = explode ( ":" , $t , 2 ) ;
242 if ( count ( $y ) == 2 && $y[0] == $cat ) {
243 array_push ( $children , $sk->makeLink ( $t , $y[1] ) ) ;
244 } else {
245 array_push ( $articles , $sk->makeLink ( $t ) ) ;
246 }
247 }
248 wfFreeResult ( $res ) ;
249
250 # Children
251 if ( count ( $children ) > 0 )
252 {
253 asort ( $children ) ;
254 $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
255 $r .= implode ( ", " , $children ) ;
256 }
257
258 # Articles
259 if ( count ( $articles ) > 0 )
260 {
261 asort ( $articles ) ;
262 $h = wfMsg( "category_header", $ti[1] );
263 $r .= "<h2>{$h}</h2>\n" ;
264 $r .= implode ( ", " , $articles ) ;
265 }
266
267
268 return $r ;
269 }
270
271 function getHTMLattrs ()
272 {
273 $htmlattrs = array( # Allowed attributes--no scripting, etc.
274 "title", "align", "lang", "dir", "width", "height",
275 "bgcolor", "clear", /* BR */ "noshade", /* HR */
276 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
277 /* FONT */ "type", "start", "value", "compact",
278 /* For various lists, mostly deprecated but safe */
279 "summary", "width", "border", "frame", "rules",
280 "cellspacing", "cellpadding", "valign", "char",
281 "charoff", "colgroup", "col", "span", "abbr", "axis",
282 "headers", "scope", "rowspan", "colspan", /* Tables */
283 "id", "class", "name", "style" /* For CSS */
284 );
285 return $htmlattrs ;
286 }
287
288 function fixTagAttributes ( $t )
289 {
290 if ( trim ( $t ) == "" ) return "" ; # Saves runtime ;-)
291 $htmlattrs = $this->getHTMLattrs() ;
292
293 # Strip non-approved attributes from the tag
294 $t = preg_replace(
295 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
296 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
297 $t);
298 # Strip javascript "expression" from stylesheets. Brute force approach:
299 # If anythin offensive is found, all attributes of the HTML tag are dropped
300
301 if( preg_match(
302 "/style\\s*=.*(expression|tps*:\/\/|url\\s*\().*/is",
303 wfMungeToUtf8( $t ) ) )
304 {
305 $t="";
306 }
307
308 return trim ( $t ) ;
309 }
310
311 function doTableStuff ( $t )
312 {
313 $t = explode ( "\n" , $t ) ;
314 $td = array () ; # Is currently a td tag open?
315 $ltd = array () ; # Was it TD or TH?
316 $tr = array () ; # Is currently a tr tag open?
317 $ltr = array () ; # tr attributes
318 foreach ( $t AS $k => $x )
319 {
320 $x = rtrim ( $x ) ;
321 $fc = substr ( $x , 0 , 1 ) ;
322 if ( "{|" == substr ( $x , 0 , 2 ) )
323 {
324 $t[$k] = "\n<table " . $this->fixTagAttributes ( substr ( $x , 3 ) ) . ">" ;
325 array_push ( $td , false ) ;
326 array_push ( $ltd , "" ) ;
327 array_push ( $tr , false ) ;
328 array_push ( $ltr , "" ) ;
329 }
330 else if ( count ( $td ) == 0 ) { } # Don't do any of the following
331 else if ( "|}" == substr ( $x , 0 , 2 ) )
332 {
333 $z = "</table>\n" ;
334 $l = array_pop ( $ltd ) ;
335 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
336 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
337 array_pop ( $ltr ) ;
338 $t[$k] = $z ;
339 }
340 /* else if ( "|_" == substr ( $x , 0 , 2 ) ) # Caption
341 {
342 $z = trim ( substr ( $x , 2 ) ) ;
343 $t[$k] = "<caption>{$z}</caption>\n" ;
344 }*/
345 else if ( "|-" == substr ( $x , 0 , 2 ) ) # Allows for |---------------
346 {
347 $x = substr ( $x , 1 ) ;
348 while ( $x != "" && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ;
349 $z = "" ;
350 $l = array_pop ( $ltd ) ;
351 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
352 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
353 array_pop ( $ltr ) ;
354 $t[$k] = $z ;
355 array_push ( $tr , false ) ;
356 array_push ( $td , false ) ;
357 array_push ( $ltd , "" ) ;
358 array_push ( $ltr , $this->fixTagAttributes ( $x ) ) ;
359 }
360 else if ( "|" == $fc || "!" == $fc || "|+" == substr ( $x , 0 , 2 ) ) # Caption
361 {
362 if ( "|+" == substr ( $x , 0 , 2 ) )
363 {
364 $fc = "+" ;
365 $x = substr ( $x , 1 ) ;
366 }
367 $after = substr ( $x , 1 ) ;
368 if ( $fc == "!" ) $after = str_replace ( "!!" , "||" , $after ) ;
369 $after = explode ( "||" , $after ) ;
370 $t[$k] = "" ;
371 foreach ( $after AS $theline )
372 {
373 $z = "" ;
374 if ( $fc != "+" )
375 {
376 $tra = array_pop ( $ltr ) ;
377 if ( !array_pop ( $tr ) ) $z = "<tr {$tra}>\n" ;
378 array_push ( $tr , true ) ;
379 array_push ( $ltr , "" ) ;
380 }
381
382 $l = array_pop ( $ltd ) ;
383 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
384 if ( $fc == "|" ) $l = "td" ;
385 else if ( $fc == "!" ) $l = "th" ;
386 else if ( $fc == "+" ) $l = "caption" ;
387 else $l = "" ;
388 array_push ( $ltd , $l ) ;
389 $y = explode ( "|" , $theline , 2 ) ;
390 if ( count ( $y ) == 1 ) $y = "{$z}<{$l}>{$y[0]}" ;
391 else $y = $y = "{$z}<{$l} ".$this->fixTagAttributes($y[0]).">{$y[1]}" ;
392 $t[$k] .= $y ;
393 array_push ( $td , true ) ;
394 }
395 }
396 }
397
398 # Closing open td, tr && table
399 while ( count ( $td ) > 0 )
400 {
401 if ( array_pop ( $td ) ) $t[] = "</td>" ;
402 if ( array_pop ( $tr ) ) $t[] = "</tr>" ;
403 $t[] = "</table>" ;
404 }
405
406 $t = implode ( "\n" , $t ) ;
407 # $t = $this->removeHTMLtags( $t );
408 return $t ;
409 }
410
411 # Well, OK, it's actually about 14 passes. But since all the
412 # hard lifting is done inside PHP's regex code, it probably
413 # wouldn't speed things up much to add a real parser.
414 #
415 function doWikiPass2( $text, $linestart )
416 {
417 $fname = "Parser::doWikiPass2";
418 wfProfileIn( $fname );
419
420 $text = $this->removeHTMLtags( $text );
421 $text = $this->replaceVariables( $text );
422
423 # $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
424
425 $text = $this->doHeadings( $text );
426
427 if($this->mOptions->getUseDynamicDates()) {
428 global $wgDateFormatter;
429 $text = $wgDateFormatter->reformat( $this->mOptions->getDateFormat(), $text );
430 }
431
432 $text = $this->replaceExternalLinks( $text );
433 $text = $this->doTokenizedParser ( $text );
434
435 $text = $this->doTableStuff ( $text ) ;
436
437 $text = $this->formatHeadings( $text );
438
439 $sk =& $this->mOptions->getSkin();
440 $text = $sk->transformContent( $text );
441 $fixtags = array(
442 "/<hr *>/i" => '<hr/>',
443 "/<br *>/i" => '<br/>',
444 "/<center *>/i"=>'<span style="text-align:center;">',
445 "/<\\/center *>/i" => '</span>'
446 );
447 $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text );
448 // another round, but without regex
449 $fixtags = array(
450 '& ' => '&amp;',
451 '&<' => '&amp;<',
452 );
453 $text = str_replace( array_keys($fixtags), array_values($fixtags), $text );
454
455 $text .= $this->categoryMagic () ;
456
457 # needs to be called last
458 $text = $this->doBlockLevels( $text, $linestart );
459
460 wfProfileOut( $fname );
461 return $text;
462 }
463
464
465 /* private */ function doHeadings( $text )
466 {
467 for ( $i = 6; $i >= 1; --$i ) {
468 $h = substr( "======", 0, $i );
469 $text = preg_replace( "/^{$h}(.+){$h}(\\s|$)/m",
470 "<h{$i}>\\1</h{$i}>\\2", $text );
471 }
472 return $text;
473 }
474
475 # Note: we have to do external links before the internal ones,
476 # and otherwise take great care in the order of things here, so
477 # that we don't end up interpreting some URLs twice.
478
479 /* private */ function replaceExternalLinks( $text )
480 {
481 $fname = "Parser::replaceExternalLinks";
482 wfProfileIn( $fname );
483 $text = $this->subReplaceExternalLinks( $text, "http", true );
484 $text = $this->subReplaceExternalLinks( $text, "https", true );
485 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
486 $text = $this->subReplaceExternalLinks( $text, "irc", false );
487 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
488 $text = $this->subReplaceExternalLinks( $text, "news", false );
489 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
490 wfProfileOut( $fname );
491 return $text;
492 }
493
494 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
495 {
496 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
497 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
498
499 # this is the list of separators that should be ignored if they
500 # are the last character of an URL but that should be included
501 # if they occur within the URL, e.g. "go to www.foo.com, where .."
502 # in this case, the last comma should not become part of the URL,
503 # but in "www.foo.com/123,2342,32.htm" it should.
504 $sep = ",;\.:";
505 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
506 $images = "gif|png|jpg|jpeg";
507
508 # PLEASE NOTE: The curly braces { } are not part of the regex,
509 # they are interpreted as part of the string (used to tell PHP
510 # that the content of the string should be inserted there).
511 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
512 "((?i){$images})([^{$uc}]|$)/";
513
514 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
515 $sk =& $this->mOptions->getSkin();
516
517 if ( $autonumber and $this->mOptions->getAllowExternalImages() ) { # Use img tags only for HTTP urls
518 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
519 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
520 }
521 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
522 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
523 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
524 "</a>\\5", $s );
525 $s = str_replace( $unique, $protocol, $s );
526
527 $a = explode( "[{$protocol}:", " " . $s );
528 $s = array_shift( $a );
529 $s = substr( $s, 1 );
530
531 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
532 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
533
534 foreach ( $a as $line ) {
535 if ( preg_match( $e1, $line, $m ) ) {
536 $link = "{$protocol}:{$m[1]}";
537 $trail = $m[2];
538 if ( $autonumber ) { $text = "[" . ++$this->mAutonumber . "]"; }
539 else { $text = wfEscapeHTML( $link ); }
540 } else if ( preg_match( $e2, $line, $m ) ) {
541 $link = "{$protocol}:{$m[1]}";
542 $text = $m[2];
543 $trail = $m[3];
544 } else {
545 $s .= "[{$protocol}:" . $line;
546 continue;
547 }
548 if( $link == $text || preg_match( "!$protocol://" . preg_quote( $text, "/" ) . "/?$!", $link ) ) {
549 $paren = "";
550 } else {
551 # Expand the URL for printable version
552 $paren = "<span class='urlexpansion'> (<i>" . htmlspecialchars ( $link ) . "</i>)</span>";
553 }
554 $la = $sk->getExternalLinkAttributes( $link, $text );
555 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
556
557 }
558 return $s;
559 }
560
561 /* private */ function handle3Quotes( &$state, $token )
562 {
563 if ( $state["strong"] !== false ) {
564 if ( $state["em"] !== false && $state["em"] > $state["strong"] )
565 {
566 # ''' lala ''lala '''
567 $s = "</em></strong><em>";
568 } else {
569 $s = "</strong>";
570 }
571 $state["strong"] = FALSE;
572 } else {
573 $s = "<strong>";
574 $state["strong"] = $token["pos"];
575 }
576 return $s;
577 }
578
579 /* private */ function handle2Quotes( &$state, $token )
580 {
581 if ( $state["em"] !== false ) {
582 if ( $state["strong"] !== false && $state["strong"] > $state["em"] )
583 {
584 # ''lala'''lala'' ....'''
585 $s = "</strong></em><strong>";
586 } else {
587 $s = "</em>";
588 }
589 $state["em"] = FALSE;
590 } else {
591 $s = "<em>";
592 $state["em"] = $token["pos"];
593 }
594 return $s;
595 }
596
597 /* private */ function handle5Quotes( &$state, $token )
598 {
599 $s = "";
600 if ( $state["em"] !== false && $state["strong"] ) {
601 if ( $state["em"] < $state["strong"] ) {
602 $s .= "</strong></em>";
603 } else {
604 $s .= "</em></strong>";
605 }
606 $state["strong"] = $state["em"] = FALSE;
607 } elseif ( $state["em"] !== false ) {
608 $s .= "</em><strong>";
609 $state["em"] = FALSE;
610 $state["strong"] = $token["pos"];
611 } elseif ( $state["strong"] !== false ) {
612 $s .= "</strong><em>";
613 $state["strong"] = FALSE;
614 $state["em"] = $token["pos"];
615 } else { # not $em and not $strong
616 $s .= "<strong><em>";
617 $state["strong"] = $state["em"] = $token["pos"];
618 }
619 return $s;
620 }
621
622 /* private */ function doTokenizedParser( $str )
623 {
624 global $wgLang; # for language specific parser hook
625
626 $tokenizer=Tokenizer::newFromString( $str );
627 $tokenStack = array();
628
629 $s="";
630 $state["em"] = FALSE;
631 $state["strong"] = FALSE;
632 $tagIsOpen = FALSE;
633 $threeopen = false;
634
635 # The tokenizer splits the text into tokens and returns them one by one.
636 # Every call to the tokenizer returns a new token.
637 while ( $token = $tokenizer->nextToken() )
638 {
639 switch ( $token["type"] )
640 {
641 case "text":
642 # simple text with no further markup
643 $txt = $token["text"];
644 break;
645 case "[[[":
646 # remember the tag opened with 3 [
647 $threeopen = true;
648 case "[[":
649 # link opening tag.
650 # FIXME : Treat orphaned open tags (stack not empty when text is over)
651 $tagIsOpen = TRUE;
652 array_push( $tokenStack, $token );
653 $txt="";
654 break;
655
656 case "]]]":
657 case "]]":
658 # link close tag.
659 # get text from stack, glue it together, and call the code to handle a
660 # link
661
662 if ( count( $tokenStack ) == 0 )
663 {
664 # stack empty. Found a ]] without an opening [[
665 $txt = "]]";
666 } else {
667 $linkText = "";
668 $lastToken = array_pop( $tokenStack );
669 while ( !(($lastToken["type"] == "[[[") or ($lastToken["type"] == "[[")) )
670 {
671 if( !empty( $lastToken["text"] ) ) {
672 $linkText = $lastToken["text"] . $linkText;
673 }
674 $lastToken = array_pop( $tokenStack );
675 }
676
677 $txt = $linkText ."]]";
678
679 if( isset( $lastToken["text"] ) ) {
680 $prefix = $lastToken["text"];
681 } else {
682 $prefix = "";
683 }
684 $nextToken = $tokenizer->previewToken();
685 if ( $nextToken["type"] == "text" )
686 {
687 # Preview just looks at it. Now we have to fetch it.
688 $nextToken = $tokenizer->nextToken();
689 $txt .= $nextToken["text"];
690 }
691 $fakestate = $this->mStripState;
692 $txt = $this->handleInternalLink( $this->unstrip($txt,$fakestate), $prefix );
693
694 # did the tag start with 3 [ ?
695 if($threeopen) {
696 # show the first as text
697 $txt = "[".$txt;
698 $threeopen=false;
699 }
700
701 }
702 $tagIsOpen = (count( $tokenStack ) != 0);
703 break;
704 case "----":
705 $txt = "\n<hr />\n";
706 break;
707 case "'''":
708 # This and the three next ones handle quotes
709 $txt = $this->handle3Quotes( $state, $token );
710 break;
711 case "''":
712 $txt = $this->handle2Quotes( $state, $token );
713 break;
714 case "'''''":
715 $txt = $this->handle5Quotes( $state, $token );
716 break;
717 case "":
718 # empty token
719 $txt="";
720 break;
721 case "RFC ":
722 if ( $tagIsOpen ) {
723 $txt = "RFC ";
724 } else {
725 $txt = $this->doMagicRFC( $tokenizer );
726 }
727 break;
728 case "ISBN ":
729 if ( $tagIsOpen ) {
730 $txt = "ISBN ";
731 } else {
732 $txt = $this->doMagicISBN( $tokenizer );
733 }
734 break;
735 default:
736 # Call language specific Hook.
737 $txt = $wgLang->processToken( $token, $tokenStack );
738 if ( NULL == $txt ) {
739 # An unkown token. Highlight.
740 $txt = "<font color=\"#FF0000\"><b>".$token["type"]."</b></font>";
741 $txt .= "<font color=\"#FFFF00\"><b>".$token["text"]."</b></font>";
742 }
743 break;
744 }
745 # If we're parsing the interior of a link, don't append the interior to $s,
746 # but push it to the stack so it can be processed when a ]] token is found.
747 if ( $tagIsOpen && $txt != "" ) {
748 $token["type"] = "text";
749 $token["text"] = $txt;
750 array_push( $tokenStack, $token );
751 } else {
752 $s .= $txt;
753 }
754 } #end while
755 if ( count( $tokenStack ) != 0 )
756 {
757 # still objects on stack. opened [[ tag without closing ]] tag.
758 $txt = "";
759 while ( $lastToken = array_pop( $tokenStack ) )
760 {
761 if ( $lastToken["type"] == "text" )
762 {
763 $txt = $lastToken["text"] . $txt;
764 } else {
765 $txt = $lastToken["type"] . $txt;
766 }
767 }
768 $s .= $txt;
769 }
770 return $s;
771 }
772
773 /* private */ function handleInternalLink( $line, $prefix )
774 {
775 global $wgLang, $wgLinkCache;
776 global $wgNamespacesWithSubpages, $wgLanguageCode;
777 static $fname = "Parser::handleInternalLink" ;
778 wfProfileIn( $fname );
779
780 wfProfileIn( "$fname-setup" );
781 static $tc = FALSE;
782 if ( !$tc ) { $tc = Title::legalChars() . "#"; }
783 $sk =& $this->mOptions->getSkin();
784
785 # Match a link having the form [[namespace:link|alternate]]trail
786 static $e1 = FALSE;
787 if ( !$e1 ) { $e1 = "/^([{$tc}]+)(?:\\|([^]]+))?]](.*)\$/sD"; }
788 # Match the end of a line for a word that's not followed by whitespace,
789 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
790 #$e2 = "/^(.*)\\b(\\w+)\$/suD";
791 #$e2 = "/^(.*\\s)(\\S+)\$/suD";
792 static $e2 = '/^(.*\s)([a-zA-Z\x80-\xff]+)$/sD';
793
794
795 # Special and Media are pseudo-namespaces; no pages actually exist in them
796 static $image = FALSE;
797 static $special = FALSE;
798 static $media = FALSE;
799 static $category = FALSE;
800 if ( !$image ) { $image = Namespace::getImage(); }
801 if ( !$special ) { $special = Namespace::getSpecial(); }
802 if ( !$media ) { $media = Namespace::getMedia(); }
803 if ( !$category ) { $category = wfMsg ( "category" ) ; }
804
805 $nottalk = !Namespace::isTalk( $this->mTitle->getNamespace() );
806
807 wfProfileOut( "$fname-setup" );
808 $s = "";
809
810 if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
811 $text = $m[2];
812 $trail = $m[3];
813 } else { # Invalid form; output directly
814 $s .= $prefix . "[[" . $line ;
815 return $s;
816 }
817
818 /* Valid link forms:
819 Foobar -- normal
820 :Foobar -- override special treatment of prefix (images, language links)
821 /Foobar -- convert to CurrentPage/Foobar
822 /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
823 */
824 $c = substr($m[1],0,1);
825 $noforce = ($c != ":");
826 if( $c == "/" ) { # subpage
827 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
828 $m[1]=substr($m[1],1,strlen($m[1])-2);
829 $noslash=$m[1];
830 } else {
831 $noslash=substr($m[1],1);
832 }
833 if($wgNamespacesWithSubpages[$this->mTitle->getNamespace()]) { # subpages allowed here
834 $link = $this->mTitle->getPrefixedText(). "/" . trim($noslash);
835 if( "" == $text ) {
836 $text= $m[1];
837 } # this might be changed for ugliness reasons
838 } else {
839 $link = $noslash; # no subpage allowed, use standard link
840 }
841 } elseif( $noforce ) { # no subpage
842 $link = $m[1];
843 } else {
844 $link = substr( $m[1], 1 );
845 }
846 if( "" == $text )
847 $text = $link;
848
849 $nt = Title::newFromText( $link );
850 if( !$nt ) {
851 $s .= $prefix . "[[" . $line;
852 return $s;
853 }
854 $ns = $nt->getNamespace();
855 $iw = $nt->getInterWiki();
856 if( $noforce ) {
857 if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgLang->getLanguageName( $iw ) ) {
858 array_push( $this->mOutput->mLanguageLinks, $nt->getPrefixedText() );
859 return (trim($s) == '')? '': $s;
860 }
861 if( $ns == $image ) {
862 $s .= $prefix . $sk->makeImageLinkObj( $nt, $text ) . $trail;
863 $wgLinkCache->addImageLinkObj( $nt );
864 return $s;
865 }
866 }
867 if( ( $nt->getPrefixedText() == $this->mTitle->getPrefixedText() ) &&
868 ( strpos( $link, "#" ) == FALSE ) ) {
869 $s .= $prefix . "<strong>" . $text . "</strong>" . $trail;
870 return $s;
871 }
872
873 # Category feature
874 $catns = strtoupper ( $nt->getDBkey () ) ;
875 $catns = explode ( ":" , $catns ) ;
876 if ( count ( $catns ) > 1 ) $catns = array_shift ( $catns ) ;
877 else $catns = "" ;
878 if ( $catns == strtoupper($category) && $this->mOptions->getUseCategoryMagic() ) {
879 $t = explode ( ":" , $nt->getText() ) ;
880 array_shift ( $t ) ;
881 $t = implode ( ":" , $t ) ;
882 $t = $wgLang->ucFirst ( $t ) ;
883 $nnt = Title::newFromText ( $category.":".$t ) ;
884 $t = $sk->makeLinkObj( $nnt, $t, "", $trail , $prefix );
885 $this->mOutput->mCategoryLinks[] = $t ;
886 $s .= $prefix . $trail ;
887 return $s ;
888 }
889
890 if( $ns == $media ) {
891 $s .= $prefix . $sk->makeMediaLinkObj( $nt, $text ) . $trail;
892 $wgLinkCache->addImageLinkObj( $nt );
893 return $s;
894 } elseif( $ns == $special ) {
895 $s .= $prefix . $sk->makeKnownLinkObj( $nt, $text, "", $trail );
896 return $s;
897 }
898 $s .= $sk->makeLinkObj( $nt, $text, "", $trail , $prefix );
899
900 wfProfileOut( $fname );
901 return $s;
902 }
903
904 # Some functions here used by doBlockLevels()
905 #
906 /* private */ function closeParagraph()
907 {
908 $result = "";
909 if ( '' != $this->mLastSection ) {
910 $result = "</" . $this->mLastSection . ">\n";
911 }
912 $this->mLastSection = "";
913 return $result;
914 }
915 # getCommon() returns the length of the longest common substring
916 # of both arguments, starting at the beginning of both.
917 #
918 /* private */ function getCommon( $st1, $st2 )
919 {
920 $fl = strlen( $st1 );
921 $shorter = strlen( $st2 );
922 if ( $fl < $shorter ) { $shorter = $fl; }
923
924 for ( $i = 0; $i < $shorter; ++$i ) {
925 if ( $st1{$i} != $st2{$i} ) { break; }
926 }
927 return $i;
928 }
929 # These next three functions open, continue, and close the list
930 # element appropriate to the prefix character passed into them.
931 #
932 /* private */ function openList( $char )
933 {
934 $result = $this->closeParagraph();
935
936 if ( "*" == $char ) { $result .= "<ul><li>"; }
937 else if ( "#" == $char ) { $result .= "<ol><li>"; }
938 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
939 else if ( ";" == $char ) {
940 $result .= "<dl><dt>";
941 $this->mDTopen = true;
942 }
943 else { $result = "<!-- ERR 1 -->"; }
944
945 return $result;
946 }
947
948 /* private */ function nextItem( $char )
949 {
950 if ( "*" == $char || "#" == $char ) { return "</li><li>"; }
951 else if ( ":" == $char || ";" == $char ) {
952 $close = "</dd>";
953 if ( $this->mDTopen ) { $close = "</dt>"; }
954 if ( ";" == $char ) {
955 $this->mDTopen = true;
956 return $close . "<dt>";
957 } else {
958 $this->mDTopen = false;
959 return $close . "<dd>";
960 }
961 }
962 return "<!-- ERR 2 -->";
963 }
964
965 /* private */function closeList( $char )
966 {
967 if ( "*" == $char ) { $text = "</li></ul>"; }
968 else if ( "#" == $char ) { $text = "</li></ol>"; }
969 else if ( ":" == $char ) {
970 if ( $this->mDTopen ) {
971 $this->mDTopen = false;
972 $text = "</dt></dl>";
973 } else {
974 $text = "</dd></dl>";
975 }
976 }
977 else { return "<!-- ERR 3 -->"; }
978 return $text."\n";
979 }
980
981 /* private */ function doBlockLevels( $text, $linestart )
982 {
983 $fname = "Parser::doBlockLevels";
984 wfProfileIn( $fname );
985 # Parsing through the text line by line. The main thing
986 # happening here is handling of block-level elements p, pre,
987 # and making lists from lines starting with * # : etc.
988 #
989 $a = explode( "\n", $text );
990 $lastPref = $text = $lastLine = '';
991 $this->mDTopen = $inBlockElem = false;
992
993 if ( ! $linestart ) { $text .= array_shift( $a ); }
994 foreach ( $a as $t ) {
995 if ( "" != $text ) { $text .= "\n"; }
996
997 $oLine = $t;
998 $opl = strlen( $lastPref );
999 $npl = strspn( $t, "*#:;" );
1000 $pref = substr( $t, 0, $npl );
1001 $pref2 = str_replace( ";", ":", $pref );
1002 $t = substr( $t, $npl );
1003
1004 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1005 $text .= $this->nextItem( substr( $pref, -1 ) );
1006
1007 if ( ";" == substr( $pref, -1 ) ) {
1008 $cpos = strpos( $t, ":" );
1009 if ( ! ( false === $cpos ) ) {
1010 $term = substr( $t, 0, $cpos );
1011 $text .= $term . $this->nextItem( ":" );
1012 $t = substr( $t, $cpos + 1 );
1013 }
1014 }
1015 } else if (0 != $npl || 0 != $opl) {
1016 $cpl = $this->getCommon( $pref, $lastPref );
1017
1018 while ( $cpl < $opl ) {
1019 $text .= $this->closeList( $lastPref{$opl-1} );
1020 --$opl;
1021 }
1022 if ( $npl <= $cpl && $cpl > 0 ) {
1023 $text .= $this->nextItem( $pref{$cpl-1} );
1024 }
1025 while ( $npl > $cpl ) {
1026 $char = substr( $pref, $cpl, 1 );
1027 $text .= $this->openList( $char );
1028
1029 if ( ";" == $char ) {
1030 $cpos = strpos( $t, ":" );
1031 if ( ! ( false === $cpos ) ) {
1032 $term = substr( $t, 0, $cpos );
1033 $text .= $term . $this->nextItem( ":" );
1034 $t = substr( $t, $cpos + 1 );
1035 }
1036 }
1037 ++$cpl;
1038 }
1039 $lastPref = $pref2;
1040 }
1041 if ( 0 == $npl ) { # No prefix--go to paragraph mode
1042 $uniq_prefix = UNIQ_PREFIX;
1043 // XXX: use a stack for nestable elements like span, table and div
1044 $openmatch = preg_match("/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<div|<pre|<tr|<td)/i", $t );
1045 $closematch = preg_match(
1046 "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|".
1047 "<\\/div|<hr|<\\/td|<\\/pre|<\\/p|".$uniq_prefix."-pre)/i", $t );
1048 if ( $openmatch or $closematch ) {
1049 $text .= $this->closeParagraph();
1050 if ( $closematch ) {
1051 $inBlockElem = false;
1052 } else {
1053 $inBlockElem = true;
1054 }
1055 } else if ( !$inBlockElem ) {
1056 if ( " " == $t{0} ) {
1057 $newSection = "pre";
1058 $text .= $this->closeParagraph();
1059 $text .= "<" . $newSection . ">";
1060 $this->mLastSection = $newSection;
1061 } else {
1062 $newSection = "p";
1063 if ( '' == trim($t) ) {
1064 if ( '' == trim($lastLine) ) {
1065 $text .= $this->closeParagraph();
1066 $text .= "<" . $newSection . "><br/>";
1067 $this->mLastSection = $newSection;
1068 } else {
1069 $t = '';
1070 }
1071 } else {
1072 $text .= $this->closeParagraph();
1073 $text .= "<" . $newSection . ">";
1074 $this->mLastSection = $newSection;
1075 }
1076 }
1077
1078 }
1079 }
1080 $lastLine = $t;
1081 $text .= $t;
1082 }
1083 while ( $npl ) {
1084 $text .= $this->closeList( $pref2{$npl-1} );
1085 --$npl;
1086 }
1087 if ( "" != $this->mLastSection ) {
1088 $text .= "</" . $this->mLastSection . ">";
1089 $this->mLastSection = "";
1090 }
1091 wfProfileOut( $fname );
1092 return $text;
1093 }
1094
1095 function getVariableValue( $index ) {
1096 global $wgLang, $wgSitename, $wgServer;
1097
1098 switch ( $index ) {
1099 case MAG_CURRENTMONTH:
1100 return date( "m" );
1101 case MAG_CURRENTMONTHNAME:
1102 return $wgLang->getMonthName( date("n") );
1103 case MAG_CURRENTMONTHNAMEGEN:
1104 return $wgLang->getMonthNameGen( date("n") );
1105 case MAG_CURRENTDAY:
1106 return date("j");
1107 case MAG_CURRENTDAYNAME:
1108 return $wgLang->getWeekdayName( date("w")+1 );
1109 case MAG_CURRENTYEAR:
1110 return date( "Y" );
1111 case MAG_CURRENTTIME:
1112 return $wgLang->time( wfTimestampNow(), false );
1113 case MAG_NUMBEROFARTICLES:
1114 return wfNumberOfArticles();
1115 case MAG_SITENAME:
1116 return $wgSitename;
1117 case MAG_SERVER:
1118 return $wgServer;
1119 default:
1120 return NULL;
1121 }
1122 }
1123
1124 function initialiseVariables()
1125 {
1126 global $wgVariableIDs;
1127 $this->mVariables = array();
1128 foreach ( $wgVariableIDs as $id ) {
1129 $mw =& MagicWord::get( $id );
1130 $mw->addToArray( $this->mVariables, $this->getVariableValue( $id ) );
1131 }
1132 }
1133
1134 /* private */ function replaceVariables( $text )
1135 {
1136 global $wgLang, $wgCurParser;
1137 global $wgScript, $wgArticlePath;
1138
1139 $fname = "Parser::replaceVariables";
1140 wfProfileIn( $fname );
1141
1142 $bail = false;
1143 if ( !$this->mVariables ) {
1144 $this->initialiseVariables();
1145 }
1146 $titleChars = Title::legalChars();
1147 $regex = "/{{([$titleChars\\|]*?)}}/s";
1148
1149 # "Recursive" variable expansion: run it through a couple of passes
1150 for ( $i=0; $i<MAX_INCLUDE_REPEAT && !$bail; $i++ ) {
1151 $oldText = $text;
1152
1153 # It's impossible to rebind a global in PHP
1154 # Instead, we run the substitution on a copy, then merge the changed fields back in
1155 $wgCurParser = $this->fork();
1156
1157 $text = preg_replace_callback( $regex, "wfBraceSubstitution", $text );
1158 if ( $oldText == $text ) {
1159 $bail = true;
1160 }
1161 $this->merge( $wgCurParser );
1162 }
1163
1164 return $text;
1165 }
1166
1167 # Returns a copy of this object except with various variables cleared
1168 # This copy can be re-merged with the parent after operations on the copy
1169 function fork()
1170 {
1171 $copy = $this;
1172 $copy->mOutput = new ParserOutput;
1173 return $copy;
1174 }
1175
1176 # Merges a copy split off with fork()
1177 function merge( &$copy )
1178 {
1179 $this->mOutput->merge( $copy->mOutput );
1180
1181 # Merge include throttling arrays
1182 foreach( $copy->mIncludeCount as $dbk => $count ) {
1183 if ( array_key_exists( $dbk, $this->mIncludeCount ) ) {
1184 $this->mIncludeCount[$dbk] += $count;
1185 } else {
1186 $this->mIncludeCount[$dbk] = $count;
1187 }
1188 }
1189 }
1190
1191 function braceSubstitution( $matches )
1192 {
1193 global $wgLinkCache, $wgLang;
1194 $fname = "Parser::braceSubstitution";
1195 $found = false;
1196 $nowiki = false;
1197
1198 $text = $matches[1];
1199
1200 # SUBST
1201 $mwSubst =& MagicWord::get( MAG_SUBST );
1202 if ( $mwSubst->matchStartAndRemove( $text ) ) {
1203 if ( $this->mOutputType != OT_WIKI ) {
1204 # Invalid SUBST not replaced at PST time
1205 # Return without further processing
1206 $text = $matches[0];
1207 $found = true;
1208 }
1209 } elseif ( $this->mOutputType == OT_WIKI ) {
1210 # SUBST not found in PST pass, do nothing
1211 $text = $matches[0];
1212 $found = true;
1213 }
1214
1215 # MSG, MSGNW and INT
1216 if ( !$found ) {
1217 # Check for MSGNW:
1218 $mwMsgnw =& MagicWord::get( MAG_MSGNW );
1219 if ( $mwMsgnw->matchStartAndRemove( $text ) ) {
1220 $nowiki = true;
1221 } else {
1222 # Remove obsolete MSG:
1223 $mwMsg =& MagicWord::get( MAG_MSG );
1224 $mwMsg->matchStartAndRemove( $text );
1225 }
1226
1227 # Check if it is an internal message
1228 $mwInt =& MagicWord::get( MAG_INT );
1229 if ( $mwInt->matchStartAndRemove( $text ) ) {
1230 $text = wfMsg( $text );
1231 $found = true;
1232 }
1233 }
1234
1235 # NS
1236 if ( !$found ) {
1237 # Check for NS: (namespace expansion)
1238 $mwNs = MagicWord::get( MAG_NS );
1239 if ( $mwNs->matchStartAndRemove( $text ) ) {
1240 if ( intval( $text ) ) {
1241 $text = $wgLang->getNsText( intval( $text ) );
1242 $found = true;
1243 } else {
1244 $index = Namespace::getCanonicalIndex( strtolower( $text ) );
1245 if ( !is_null( $index ) ) {
1246 $text = $wgLang->getNsText( $index );
1247 $found = true;
1248 }
1249 }
1250 }
1251 }
1252
1253 # LOCALURL and LOCALURLE
1254 if ( !$found ) {
1255 $mwLocal = MagicWord::get( MAG_LOCALURL );
1256 $mwLocalE = MagicWord::get( MAG_LOCALURLE );
1257
1258 if ( $mwLocal->matchStartAndRemove( $text ) ) {
1259 $func = 'getLocalURL';
1260 } elseif ( $mwLocalE->matchStartAndRemove( $text ) ) {
1261 $func = 'escapeLocalURL';
1262 } else {
1263 $func = '';
1264 }
1265
1266 if ( $func !== '' ) {
1267 $args = explode( "|", $text );
1268 $n = count( $args );
1269 if ( $n > 0 ) {
1270 $title = Title::newFromText( $args[0] );
1271 if ( !is_null( $title ) ) {
1272 if ( $n > 1 ) {
1273 $text = $title->$func( $args[1] );
1274 } else {
1275 $text = $title->$func();
1276 }
1277 $found = true;
1278 }
1279 }
1280 }
1281 }
1282
1283 # Check for a match against internal variables
1284 if ( !$found && array_key_exists( $text, $this->mVariables ) ) {
1285 $text = $this->mVariables[$text];
1286 $found = true;
1287 $this->mOutput->mContainsOldMagic = true;
1288 }
1289
1290 # Load from database
1291 if ( !$found ) {
1292 $title = Title::newFromText( $text, NS_TEMPLATE );
1293 if ( is_object( $title ) && !$title->isExternal() ) {
1294 # Check for excessive inclusion
1295 $dbk = $title->getPrefixedDBkey();
1296 if ( !array_key_exists( $dbk, $this->mIncludeCount ) ) {
1297 $this->mIncludeCount[$dbk] = 0;
1298 }
1299 if ( ++$this->mIncludeCount[$dbk] <= MAX_INCLUDE_REPEAT ) {
1300 $article = new Article( $title );
1301 $articleContent = $article->getContentWithoutUsingSoManyDamnGlobals();
1302 if ( $articleContent !== false ) {
1303 $found = true;
1304 $text = $articleContent;
1305
1306 # Escaping and link table handling
1307 # Not required for preSaveTransform()
1308 if ( $this->mOutputType == OT_HTML ) {
1309 if ( $nowiki ) {
1310 $text = wfEscapeWikiText( $text );
1311 } else {
1312 $text = $this->removeHTMLtags( $text );
1313 }
1314 $wgLinkCache->suspend();
1315 $text = $this->doTokenizedParser( $text );
1316 $wgLinkCache->resume();
1317 $wgLinkCache->addLinkObj( $title );
1318
1319 }
1320 }
1321 }
1322
1323 # If the title is valid but undisplayable, make a link to it
1324 if ( $this->mOutputType == OT_HTML && !$found ) {
1325 $text = "[[" . $title->getPrefixedText() . "]]";
1326 $found = true;
1327 }
1328 }
1329 }
1330
1331 if ( !$found ) {
1332 return $matches[0];
1333 } else {
1334 return $text;
1335 }
1336 }
1337
1338 # Cleans up HTML, removes dangerous tags and attributes
1339 /* private */ function removeHTMLtags( $text )
1340 {
1341 $fname = "Parser::removeHTMLtags";
1342 wfProfileIn( $fname );
1343 $htmlpairs = array( # Tags that must be closed
1344 "b", "i", "u", "font", "big", "small", "sub", "sup", "h1",
1345 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1346 "strike", "strong", "tt", "var", "div", "center",
1347 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1348 "ruby", "rt" , "rb" , "rp", "p"
1349 );
1350 $htmlsingle = array(
1351 "br", "hr", "li", "dt", "dd"
1352 );
1353 $htmlnest = array( # Tags that can be nested--??
1354 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1355 "dl", "font", "big", "small", "sub", "sup"
1356 );
1357 $tabletags = array( # Can only appear inside table
1358 "td", "th", "tr"
1359 );
1360
1361 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1362 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1363
1364 $htmlattrs = $this->getHTMLattrs () ;
1365
1366 # Remove HTML comments
1367 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1368
1369 $bits = explode( "<", $text );
1370 $text = array_shift( $bits );
1371 $tagstack = array(); $tablestack = array();
1372
1373 foreach ( $bits as $x ) {
1374 $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
1375 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1376 $x, $regs );
1377 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1378 error_reporting( $prev );
1379
1380 $badtag = 0 ;
1381 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1382 # Check our stack
1383 if ( $slash ) {
1384 # Closing a tag...
1385 if ( ! in_array( $t, $htmlsingle ) &&
1386 ( $ot = array_pop( $tagstack ) ) != $t ) {
1387 array_push( $tagstack, $ot );
1388 $badtag = 1;
1389 } else {
1390 if ( $t == "table" ) {
1391 $tagstack = array_pop( $tablestack );
1392 }
1393 $newparams = "";
1394 }
1395 } else {
1396 # Keep track for later
1397 if ( in_array( $t, $tabletags ) &&
1398 ! in_array( "table", $tagstack ) ) {
1399 $badtag = 1;
1400 } else if ( in_array( $t, $tagstack ) &&
1401 ! in_array ( $t , $htmlnest ) ) {
1402 $badtag = 1 ;
1403 } else if ( ! in_array( $t, $htmlsingle ) ) {
1404 if ( $t == "table" ) {
1405 array_push( $tablestack, $tagstack );
1406 $tagstack = array();
1407 }
1408 array_push( $tagstack, $t );
1409 }
1410 # Strip non-approved attributes from the tag
1411 $newparams = $this->fixTagAttributes($params);
1412
1413 }
1414 if ( ! $badtag ) {
1415 $rest = str_replace( ">", "&gt;", $rest );
1416 $text .= "<$slash$t $newparams$brace$rest";
1417 continue;
1418 }
1419 }
1420 $text .= "&lt;" . str_replace( ">", "&gt;", $x);
1421 }
1422 # Close off any remaining tags
1423 while ( $t = array_pop( $tagstack ) ) {
1424 $text .= "</$t>\n";
1425 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1426 }
1427 wfProfileOut( $fname );
1428 return $text;
1429 }
1430
1431 /*
1432 *
1433 * This function accomplishes several tasks:
1434 * 1) Auto-number headings if that option is enabled
1435 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1436 * 3) Add a Table of contents on the top for users who have enabled the option
1437 * 4) Auto-anchor headings
1438 *
1439 * It loops through all headlines, collects the necessary data, then splits up the
1440 * string and re-inserts the newly formatted headlines.
1441 *
1442 */
1443
1444 /* private */ function formatHeadings( $text )
1445 {
1446 $doNumberHeadings = $this->mOptions->getNumberHeadings();
1447 $doShowToc = $this->mOptions->getShowToc();
1448 if( !$this->mTitle->userCanEdit() ) {
1449 $showEditLink = 0;
1450 $rightClickHack = 0;
1451 } else {
1452 $showEditLink = $this->mOptions->getEditSection();
1453 $rightClickHack = $this->mOptions->getEditSectionOnRightClick();
1454 }
1455
1456 # Inhibit editsection links if requested in the page
1457 $esw =& MagicWord::get( MAG_NOEDITSECTION );
1458 if( $esw->matchAndRemove( $text ) ) {
1459 $showEditLink = 0;
1460 }
1461 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
1462 # do not add TOC
1463 $mw =& MagicWord::get( MAG_NOTOC );
1464 if( $mw->matchAndRemove( $text ) ) {
1465 $doShowToc = 0;
1466 }
1467
1468 # never add the TOC to the Main Page. This is an entry page that should not
1469 # be more than 1-2 screens large anyway
1470 if( $this->mTitle->getPrefixedText() == wfMsg("mainpage") ) {
1471 $doShowToc = 0;
1472 }
1473
1474 # Get all headlines for numbering them and adding funky stuff like [edit]
1475 # links - this is for later, but we need the number of headlines right now
1476 $numMatches = preg_match_all( "/<H([1-6])(.*?" . ">)(.*?)<\/H[1-6]>/i", $text, $matches );
1477
1478 # if there are fewer than 4 headlines in the article, do not show TOC
1479 if( $numMatches < 4 ) {
1480 $doShowToc = 0;
1481 }
1482
1483 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
1484 # override above conditions and always show TOC
1485 $mw =& MagicWord::get( MAG_FORCETOC );
1486 if ($mw->matchAndRemove( $text ) ) {
1487 $doShowToc = 1;
1488 }
1489
1490
1491 # We need this to perform operations on the HTML
1492 $sk =& $this->mOptions->getSkin();
1493
1494 # headline counter
1495 $headlineCount = 0;
1496
1497 # Ugh .. the TOC should have neat indentation levels which can be
1498 # passed to the skin functions. These are determined here
1499 $toclevel = 0;
1500 $toc = "";
1501 $full = "";
1502 $head = array();
1503 $sublevelCount = array();
1504 $level = 0;
1505 $prevlevel = 0;
1506 foreach( $matches[3] as $headline ) {
1507 $numbering = "";
1508 if( $level ) {
1509 $prevlevel = $level;
1510 }
1511 $level = $matches[1][$headlineCount];
1512 if( ( $doNumberHeadings || $doShowToc ) && $prevlevel && $level > $prevlevel ) {
1513 # reset when we enter a new level
1514 $sublevelCount[$level] = 0;
1515 $toc .= $sk->tocIndent( $level - $prevlevel );
1516 $toclevel += $level - $prevlevel;
1517 }
1518 if( ( $doNumberHeadings || $doShowToc ) && $level < $prevlevel ) {
1519 # reset when we step back a level
1520 $sublevelCount[$level+1]=0;
1521 $toc .= $sk->tocUnindent( $prevlevel - $level );
1522 $toclevel -= $prevlevel - $level;
1523 }
1524 # count number of headlines for each level
1525 @$sublevelCount[$level]++;
1526 if( $doNumberHeadings || $doShowToc ) {
1527 $dot = 0;
1528 for( $i = 1; $i <= $level; $i++ ) {
1529 if( !empty( $sublevelCount[$i] ) ) {
1530 if( $dot ) {
1531 $numbering .= ".";
1532 }
1533 $numbering .= $sublevelCount[$i];
1534 $dot = 1;
1535 }
1536 }
1537 }
1538
1539 # The canonized header is a version of the header text safe to use for links
1540 # Avoid insertion of weird stuff like <math> by expanding the relevant sections
1541 $canonized_headline = Parser::unstrip( $headline, $this->mStripState );
1542
1543 # strip out HTML
1544 $canonized_headline = preg_replace( "/<.*?" . ">/","",$canonized_headline );
1545 $tocline = trim( $canonized_headline );
1546 $canonized_headline = preg_replace("/[ &\\/<>\\(\\)\\[\\]=,+']+/", '_', html_entity_decode( $tocline));
1547 $refer[$headlineCount] = $canonized_headline;
1548
1549 # count how many in assoc. array so we can track dupes in anchors
1550 @$refers[$canonized_headline]++;
1551 $refcount[$headlineCount]=$refers[$canonized_headline];
1552
1553 # Prepend the number to the heading text
1554
1555 if( $doNumberHeadings || $doShowToc ) {
1556 $tocline = $numbering . " " . $tocline;
1557
1558 # Don't number the heading if it is the only one (looks silly)
1559 if( $doNumberHeadings && count( $matches[3] ) > 1) {
1560 # the two are different if the line contains a link
1561 $headline=$numbering . " " . $headline;
1562 }
1563 }
1564
1565 # Create the anchor for linking from the TOC to the section
1566 $anchor = $canonized_headline;
1567 if($refcount[$headlineCount] > 1 ) {
1568 $anchor .= "_" . $refcount[$headlineCount];
1569 }
1570 if( $doShowToc ) {
1571 $toc .= $sk->tocLine($anchor,$tocline,$toclevel);
1572 }
1573 if( $showEditLink ) {
1574 if ( empty( $head[$headlineCount] ) ) {
1575 $head[$headlineCount] = "";
1576 }
1577 $head[$headlineCount] .= $sk->editSectionLink($headlineCount+1);
1578 }
1579
1580 # Add the edit section span
1581 if( $rightClickHack ) {
1582 $headline = $sk->editSectionScript($headlineCount+1,$headline);
1583 }
1584
1585 # give headline the correct <h#> tag
1586 @$head[$headlineCount] .= "<a name=\"$anchor\"></a><h".$level.$matches[2][$headlineCount] .$headline."</h".$level.">";
1587
1588 $headlineCount++;
1589 }
1590
1591 if( $doShowToc ) {
1592 $toclines = $headlineCount;
1593 $toc .= $sk->tocUnindent( $toclevel );
1594 $toc = $sk->tocTable( $toc );
1595 }
1596
1597 # split up and insert constructed headlines
1598
1599 $blocks = preg_split( "/<H[1-6].*?" . ">.*?<\/H[1-6]>/i", $text );
1600 $i = 0;
1601
1602 foreach( $blocks as $block ) {
1603 if( $showEditLink && $headlineCount > 0 && $i == 0 && $block != "\n" ) {
1604 # This is the [edit] link that appears for the top block of text when
1605 # section editing is enabled
1606 $full .= $sk->editSectionLink(0);
1607 }
1608 $full .= $block;
1609 if( $doShowToc && !$i) {
1610 # Top anchor now in skin
1611 $full = $full.$toc;
1612 }
1613
1614 if( !empty( $head[$i] ) ) {
1615 $full .= $head[$i];
1616 }
1617 $i++;
1618 }
1619
1620 return $full;
1621 }
1622
1623 /* private */ function doMagicISBN( &$tokenizer )
1624 {
1625 global $wgLang;
1626
1627 # Check whether next token is a text token
1628 # If yes, fetch it and convert the text into a
1629 # Special::BookSources link
1630 $token = $tokenizer->previewToken();
1631 while ( $token["type"] == "" )
1632 {
1633 $tokenizer->nextToken();
1634 $token = $tokenizer->previewToken();
1635 }
1636 if ( $token["type"] == "text" )
1637 {
1638 $token = $tokenizer->nextToken();
1639 $x = $token["text"];
1640 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1641
1642 $isbn = $blank = "" ;
1643 while ( " " == $x{0} ) {
1644 $blank .= " ";
1645 $x = substr( $x, 1 );
1646 }
1647 while ( strstr( $valid, $x{0} ) != false ) {
1648 $isbn .= $x{0};
1649 $x = substr( $x, 1 );
1650 }
1651 $num = str_replace( "-", "", $isbn );
1652 $num = str_replace( " ", "", $num );
1653
1654 if ( "" == $num ) {
1655 $text = "ISBN $blank$x";
1656 } else {
1657 $titleObj = Title::makeTitle( NS_SPECIAL, "Booksources" );
1658 $text = "<a href=\"" .
1659 $titleObj->escapeLocalUrl( "isbn={$num}" ) .
1660 "\" class=\"internal\">ISBN $isbn</a>";
1661 $text .= $x;
1662 }
1663 } else {
1664 $text = "ISBN ";
1665 }
1666 return $text;
1667 }
1668 /* private */ function doMagicRFC( &$tokenizer )
1669 {
1670 global $wgLang;
1671
1672 # Check whether next token is a text token
1673 # If yes, fetch it and convert the text into a
1674 # link to an RFC source
1675 $token = $tokenizer->previewToken();
1676 while ( $token["type"] == "" )
1677 {
1678 $tokenizer->nextToken();
1679 $token = $tokenizer->previewToken();
1680 }
1681 if ( $token["type"] == "text" )
1682 {
1683 $token = $tokenizer->nextToken();
1684 $x = $token["text"];
1685 $valid = "0123456789";
1686
1687 $rfc = $blank = "" ;
1688 while ( " " == $x{0} ) {
1689 $blank .= " ";
1690 $x = substr( $x, 1 );
1691 }
1692 while ( strstr( $valid, $x{0} ) != false ) {
1693 $rfc .= $x{0};
1694 $x = substr( $x, 1 );
1695 }
1696
1697 if ( "" == $rfc ) {
1698 $text .= "RFC $blank$x";
1699 } else {
1700 $url = wfmsg( "rfcurl" );
1701 $url = str_replace( "$1", $rfc, $url);
1702 $sk =& $this->mOptions->getSkin();
1703 $la = $sk->getExternalLinkAttributes( $url, "RFC {$rfc}" );
1704 $text = "<a href='{$url}'{$la}>RFC {$rfc}</a>{$x}";
1705 }
1706 } else {
1707 $text = "RFC ";
1708 }
1709 return $text;
1710 }
1711
1712 function preSaveTransform( $text, &$title, &$user, $options, $clearState = true )
1713 {
1714 $this->mOptions = $options;
1715 $this->mTitle =& $title;
1716 $this->mOutputType = OT_WIKI;
1717
1718 if ( $clearState ) {
1719 $this->clearState();
1720 }
1721
1722 $stripState = false;
1723 $text = str_replace("\r\n", "\n", $text);
1724 $text = $this->strip( $text, $stripState, false );
1725 $text = $this->pstPass2( $text, $user );
1726 $text = $this->unstrip( $text, $stripState );
1727 return $text;
1728 }
1729
1730 /* private */ function pstPass2( $text, &$user )
1731 {
1732 global $wgLang, $wgLocaltimezone, $wgCurParser;
1733
1734 # Variable replacement
1735 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
1736 $text = $this->replaceVariables( $text );
1737
1738 # Signatures
1739 #
1740 $n = $user->getName();
1741 $k = $user->getOption( "nickname" );
1742 if ( "" == $k ) { $k = $n; }
1743 if(isset($wgLocaltimezone)) {
1744 $oldtz = getenv("TZ"); putenv("TZ=$wgLocaltimezone");
1745 }
1746 /* Note: this is an ugly timezone hack for the European wikis */
1747 $d = $wgLang->timeanddate( date( "YmdHis" ), false ) .
1748 " (" . date( "T" ) . ")";
1749 if(isset($wgLocaltimezone)) putenv("TZ=$oldtz");
1750
1751 $text = preg_replace( "/~~~~~/", $d, $text );
1752 $text = preg_replace( "/~~~~/", "[[" . $wgLang->getNsText(
1753 Namespace::getUser() ) . ":$n|$k]] $d", $text );
1754 $text = preg_replace( "/~~~/", "[[" . $wgLang->getNsText(
1755 Namespace::getUser() ) . ":$n|$k]]", $text );
1756
1757 # Context links: [[|name]] and [[name (context)|]]
1758 #
1759 $tc = "[&;%\\-,.\\(\\)' _0-9A-Za-z\\/:\\x80-\\xff]";
1760 $np = "[&;%\\-,.' _0-9A-Za-z\\/:\\x80-\\xff]"; # No parens
1761 $namespacechar = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
1762 $conpat = "/^({$np}+) \\(({$tc}+)\\)$/";
1763
1764 $p1 = "/\[\[({$np}+) \\(({$np}+)\\)\\|]]/"; # [[page (context)|]]
1765 $p2 = "/\[\[\\|({$tc}+)]]/"; # [[|page]]
1766 $p3 = "/\[\[($namespacechar+):({$np}+)\\|]]/"; # [[namespace:page|]]
1767 $p4 = "/\[\[($namespacechar+):({$np}+) \\(({$np}+)\\)\\|]]/";
1768 # [[ns:page (cont)|]]
1769 $context = "";
1770 $t = $this->mTitle->getText();
1771 if ( preg_match( $conpat, $t, $m ) ) {
1772 $context = $m[2];
1773 }
1774 $text = preg_replace( $p4, "[[\\1:\\2 (\\3)|\\2]]", $text );
1775 $text = preg_replace( $p1, "[[\\1 (\\2)|\\1]]", $text );
1776 $text = preg_replace( $p3, "[[\\1:\\2|\\2]]", $text );
1777
1778 if ( "" == $context ) {
1779 $text = preg_replace( $p2, "[[\\1]]", $text );
1780 } else {
1781 $text = preg_replace( $p2, "[[\\1 ({$context})|\\1]]", $text );
1782 }
1783
1784 /*
1785 $mw =& MagicWord::get( MAG_SUBST );
1786 $wgCurParser = $this->fork();
1787 $text = $mw->substituteCallback( $text, "wfBraceSubstitution" );
1788 $this->merge( $wgCurParser );
1789 */
1790
1791 # Trim trailing whitespace
1792 # MAG_END (__END__) tag allows for trailing
1793 # whitespace to be deliberately included
1794 $text = rtrim( $text );
1795 $mw =& MagicWord::get( MAG_END );
1796 $mw->matchAndRemove( $text );
1797
1798 return $text;
1799 }
1800
1801 # Set up some variables which are usually set up in parse()
1802 # so that an external function can call some class members with confidence
1803 function startExternalParse( &$title, $options, $outputType, $clearState = true )
1804 {
1805 $this->mTitle =& $title;
1806 $this->mOptions = $options;
1807 $this->mOutputType = $outputType;
1808 if ( $clearState ) {
1809 $this->clearState();
1810 }
1811 }
1812
1813 function transformMsg( $text, $options ) {
1814 global $wgTitle;
1815 static $executing = false;
1816
1817 # Guard against infinite recursion
1818 if ( $executing ) {
1819 return $text;
1820 }
1821 $executing = true;
1822
1823 $this->mTitle = $wgTitle;
1824 $this->mOptions = $options;
1825 $this->mOutputType = OT_MSG;
1826 $this->clearState();
1827 $text = $this->replaceVariables( $text );
1828
1829 $executing = false;
1830 return $text;
1831 }
1832 }
1833
1834 class ParserOutput
1835 {
1836 var $mText, $mLanguageLinks, $mCategoryLinks, $mContainsOldMagic;
1837
1838 function ParserOutput( $text = "", $languageLinks = array(), $categoryLinks = array(),
1839 $containsOldMagic = false )
1840 {
1841 $this->mText = $text;
1842 $this->mLanguageLinks = $languageLinks;
1843 $this->mCategoryLinks = $categoryLinks;
1844 $this->mContainsOldMagic = $containsOldMagic;
1845 }
1846
1847 function getText() { return $this->mText; }
1848 function getLanguageLinks() { return $this->mLanguageLinks; }
1849 function getCategoryLinks() { return $this->mCategoryLinks; }
1850 function containsOldMagic() { return $this->mContainsOldMagic; }
1851 function setText( $text ) { return wfSetVar( $this->mText, $text ); }
1852 function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); }
1853 function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategoryLinks, $cl ); }
1854 function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
1855
1856 function merge( $other ) {
1857 $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $other->mLanguageLinks );
1858 $this->mCategoryLinks = array_merge( $this->mCategoryLinks, $this->mLanguageLinks );
1859 $this->mContainsOldMagic = $this->mContainsOldMagic || $other->mContainsOldMagic;
1860 }
1861
1862 }
1863
1864 class ParserOptions
1865 {
1866 # All variables are private
1867 var $mUseTeX; # Use texvc to expand <math> tags
1868 var $mUseCategoryMagic; # Treat [[Category:xxxx]] tags specially
1869 var $mUseDynamicDates; # Use $wgDateFormatter to format dates
1870 var $mInterwikiMagic; # Interlanguage links are removed and returned in an array
1871 var $mAllowExternalImages; # Allow external images inline
1872 var $mSkin; # Reference to the preferred skin
1873 var $mDateFormat; # Date format index
1874 var $mEditSection; # Create "edit section" links
1875 var $mEditSectionOnRightClick; # Generate JavaScript to edit section on right click
1876 var $mNumberHeadings; # Automatically number headings
1877 var $mShowToc; # Show table of contents
1878
1879 function getUseTeX() { return $this->mUseTeX; }
1880 function getUseCategoryMagic() { return $this->mUseCategoryMagic; }
1881 function getUseDynamicDates() { return $this->mUseDynamicDates; }
1882 function getInterwikiMagic() { return $this->mInterwikiMagic; }
1883 function getAllowExternalImages() { return $this->mAllowExternalImages; }
1884 function getSkin() { return $this->mSkin; }
1885 function getDateFormat() { return $this->mDateFormat; }
1886 function getEditSection() { return $this->mEditSection; }
1887 function getEditSectionOnRightClick() { return $this->mEditSectionOnRightClick; }
1888 function getNumberHeadings() { return $this->mNumberHeadings; }
1889 function getShowToc() { return $this->mShowToc; }
1890
1891 function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); }
1892 function setUseCategoryMagic( $x ) { return wfSetVar( $this->mUseCategoryMagic, $x ); }
1893 function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
1894 function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
1895 function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); }
1896 function setSkin( $x ) { return wfSetRef( $this->mSkin, $x ); }
1897 function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); }
1898 function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); }
1899 function setEditSectionOnRightClick( $x ) { return wfSetVar( $this->mEditSectionOnRightClick, $x ); }
1900 function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); }
1901 function setShowToc( $x ) { return wfSetVar( $this->mShowToc, $x ); }
1902
1903 /* static */ function newFromUser( &$user )
1904 {
1905 $popts = new ParserOptions;
1906 $popts->initialiseFromUser( &$user );
1907 return $popts;
1908 }
1909
1910 function initialiseFromUser( &$userInput )
1911 {
1912 global $wgUseTeX, $wgUseCategoryMagic, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
1913
1914 if ( !$userInput ) {
1915 $user = new User;
1916 $user->setLoaded( true );
1917 } else {
1918 $user =& $userInput;
1919 }
1920
1921 $this->mUseTeX = $wgUseTeX;
1922 $this->mUseCategoryMagic = $wgUseCategoryMagic;
1923 $this->mUseDynamicDates = $wgUseDynamicDates;
1924 $this->mInterwikiMagic = $wgInterwikiMagic;
1925 $this->mAllowExternalImages = $wgAllowExternalImages;
1926 $this->mSkin =& $user->getSkin();
1927 $this->mDateFormat = $user->getOption( "date" );
1928 $this->mEditSection = $user->getOption( "editsection" );
1929 $this->mEditSectionOnRightClick = $user->getOption( "editsectiononrightclick" );
1930 $this->mNumberHeadings = $user->getOption( "numberheadings" );
1931 $this->mShowToc = $user->getOption( "showtoc" );
1932 }
1933
1934
1935 }
1936
1937 # Regex callbacks, used in Parser::replaceVariables
1938 function wfBraceSubstitution( $matches )
1939 {
1940 global $wgCurParser;
1941 return $wgCurParser->braceSubstitution( $matches );
1942 }
1943
1944 ?>