changed languages-directory to lower case, for Unix
[mediawiki.git] / includes / Parser.php
blob5f1043a00739ce7702e1d3078fa98457932e9b1e
1 <?php
3 include_once('Tokenizer.php');
5 if( $GLOBALS['wgUseWikiHiero'] ){
6 include_once('wikihiero.php');
8 if( $GLOBALS['wgUseTimeline'] ){
9 include_once('extensions/timeline/Timeline.php');
12 # PHP Parser
14 # Processes wiki markup
16 # There are two main entry points into the Parser class: parse() and preSaveTransform().
17 # The parse() function produces HTML output, preSaveTransform() produces altered wiki markup.
19 # Globals used:
20 # objects: $wgLang, $wgDateFormatter, $wgLinkCache, $wgCurParser
22 # NOT $wgArticle, $wgUser or $wgTitle. Keep them away!
24 # settings: $wgUseTex*, $wgUseCategoryMagic*, $wgUseDynamicDates*, $wgInterwikiMagic*,
25 # $wgNamespacesWithSubpages, $wgLanguageCode, $wgAllowExternalImages*,
26 # $wgLocaltimezone
28 # * only within ParserOptions
31 #----------------------------------------
32 # Variable substitution O(N^2) attack
33 #-----------------------------------------
34 # Without countermeasures, it would be possible to attack the parser by saving a page
35 # filled with a large number of inclusions of large pages. The size of the generated
36 # page would be proportional to the square of the input size. Hence, we limit the number
37 # of inclusions of any given page, thus bringing any attack back to O(N).
40 define( "MAX_INCLUDE_REPEAT", 5 );
42 # Allowed values for $mOutputType
43 define( "OT_HTML", 1 );
44 define( "OT_WIKI", 2 );
45 define( "OT_MSG", 3 );
47 # prefix for escaping, used in two functions at least
48 define( "UNIQ_PREFIX", "NaodW29");
50 class Parser
52 # Cleared with clearState():
53 var $mOutput, $mAutonumber, $mDTopen, $mStripState = array();
54 var $mVariables, $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
56 # Temporary:
57 var $mOptions, $mTitle, $mOutputType;
59 function Parser()
61 $this->clearState();
64 function clearState()
66 $this->mOutput = new ParserOutput;
67 $this->mAutonumber = 0;
68 $this->mLastSection = "";
69 $this->mDTopen = false;
70 $this->mVariables = false;
71 $this->mIncludeCount = array();
72 $this->mStripState = array();
73 $this->mArgStack = array();
76 # First pass--just handle <nowiki> sections, pass the rest off
77 # to internalParse() which does all the real work.
79 # Returns a ParserOutput
81 function parse( $text, &$title, $options, $linestart = true, $clearState = true )
83 $fname = "Parser::parse";
84 wfProfileIn( $fname );
86 if ( $clearState ) {
87 $this->clearState();
90 $this->mOptions = $options;
91 $this->mTitle =& $title;
92 $this->mOutputType = OT_HTML;
94 $stripState = NULL;
95 $text = $this->strip( $text, $this->mStripState );
96 $text = $this->internalParse( $text, $linestart );
97 $text = $this->unstrip( $text, $this->mStripState );
98 # Clean up special characters, only run once, next-to-last before doBlockLevels
99 $fixtags = array(
100 "/<hr *>/i" => '<hr/>',
101 "/<br *>/i" => '<br/>',
102 "/<center *>/i"=>'<div class="center">',
103 "/<\\/center *>/i" => '</div>',
104 # Clean up spare ampersands; note that we probably ought to be
105 # more careful about named entities.
106 '/&(?!:amp;|#[Xx][0-9A-fa-f]+;|#[0-9]+;|[a-zA-Z0-9]+;)/' => '&amp;'
108 $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text );
110 # only once and last
111 $text = $this->doBlockLevels( $text, $linestart );
113 $this->mOutput->setText( $text );
114 wfProfileOut( $fname );
115 return $this->mOutput;
118 /* static */ function getRandomString()
120 return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff));
123 # Replaces all occurrences of <$tag>content</$tag> in the text
124 # with a random marker and returns the new text. the output parameter
125 # $content will be an associative array filled with data on the form
126 # $unique_marker => content.
128 # If $content is already set, the additional entries will be appended
130 /* static */ function extractTags($tag, $text, &$content, $uniq_prefix = ""){
131 $rnd = $uniq_prefix . '-' . $tag . Parser::getRandomString();
132 if ( !$content ) {
133 $content = array( );
135 $n = 1;
136 $stripped = "";
138 while ( "" != $text ) {
139 $p = preg_split( "/<\\s*$tag\\s*>/i", $text, 2 );
140 $stripped .= $p[0];
141 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) {
142 $text = "";
143 } else {
144 $q = preg_split( "/<\\/\\s*$tag\\s*>/i", $p[1], 2 );
145 $marker = $rnd . sprintf("%08X", $n++);
146 $content[$marker] = $q[0];
147 $stripped .= $marker;
148 $text = $q[1];
151 return $stripped;
154 # Strips <nowiki>, <pre> and <math>
155 # Returns the text, and fills an array with data needed in unstrip()
156 # If the $state is already a valid strip state, it adds to the state
158 function strip( $text, &$state )
160 $render = ($this->mOutputType == OT_HTML);
161 $nowiki_content = array();
162 $hiero_content = array();
163 $math_content = array();
164 $pre_content = array();
165 $item_content = array();
167 # Replace any instances of the placeholders
168 $uniq_prefix = UNIQ_PREFIX;
169 #$text = str_replace( $uniq_prefix, wfHtmlEscapeFirst( $uniq_prefix ), $text );
171 $text = Parser::extractTags("nowiki", $text, $nowiki_content, $uniq_prefix);
172 foreach( $nowiki_content as $marker => $content ){
173 if( $render ){
174 $nowiki_content[$marker] = wfEscapeHTMLTagsOnly( $content );
175 } else {
176 $nowiki_content[$marker] = "<nowiki>$content</nowiki>";
180 if( $GLOBALS['wgUseWikiHiero'] ){
181 $text = Parser::extractTags("hiero", $text, $hiero_content, $uniq_prefix);
182 foreach( $hiero_content as $marker => $content ){
183 if( $render ){
184 $hiero_content[$marker] = WikiHiero( $content, WH_MODE_HTML);
185 } else {
186 $hiero_content[$marker] = "<hiero>$content</hiero>";
191 if( $this->mOptions->getUseTeX() ){
192 $text = Parser::extractTags("math", $text, $math_content, $uniq_prefix);
193 foreach( $math_content as $marker => $content ){
194 if( $render ){
195 $math_content[$marker] = renderMath( $content );
196 } else {
197 $math_content[$marker] = "<math>$content</math>";
202 $text = Parser::extractTags("pre", $text, $pre_content, $uniq_prefix);
203 foreach( $pre_content as $marker => $content ){
204 if( $render ){
205 $pre_content[$marker] = "<pre>" . wfEscapeHTMLTagsOnly( $content ) . "</pre>";
206 } else {
207 $pre_content[$marker] = "<pre>$content</pre>";
211 # Merge state with the pre-existing state, if there is one
212 if ( $state ) {
213 $state['nowiki'] = $state['nowiki'] + $nowiki_content;
214 $state['hiero'] = $state['hiero'] + $hiero_content;
215 $state['math'] = $state['math'] + $math_content;
216 $state['pre'] = $state['pre'] + $pre_content;
217 } else {
218 $state = array(
219 'nowiki' => $nowiki_content,
220 'hiero' => $hiero_content,
221 'math' => $math_content,
222 'pre' => $pre_content,
223 'item' => $item_content
226 return $text;
229 function unstrip( $text, &$state )
231 # Must expand in reverse order, otherwise nested tags will be corrupted
232 $contentDict = end( $state );
233 for ( $contentDict = end( $state ); $contentDict !== false; $contentDict = prev( $state ) ) {
234 for ( $content = end( $contentDict ); $content !== false; $content = prev( $contentDict ) ) {
235 $text = str_replace( key( $contentDict ), $content, $text );
239 return $text;
242 # Add an item to the strip state
243 # Returns the unique tag which must be inserted into the stripped text
244 # The tag will be replaced with the original text in unstrip()
246 function insertStripItem( $text, &$state )
248 $rnd = UNIQ_PREFIX . '-item' . Parser::getRandomString();
249 if ( !$state ) {
250 $state = array(
251 'nowiki' => array(),
252 'hiero' => array(),
253 'math' => array(),
254 'pre' => array(),
255 'item' => array()
258 $state['item'][$rnd] = $text;
259 return $rnd;
262 # This method generates the list of subcategories and pages for a category
263 function categoryMagic ()
265 global $wgLang , $wgUser ;
266 if ( !$this->mOptions->getUseCategoryMagic() ) return ; # Doesn't use categories at all
268 $cns = Namespace::getCategory() ;
269 if ( $this->mTitle->getNamespace() != $cns ) return "" ; # This ain't a category page
271 $r = "<br style=\"clear:both;\"/>\n";
274 $sk =& $wgUser->getSkin() ;
276 $articles = array() ;
277 $children = array() ;
278 $data = array () ;
279 $id = $this->mTitle->getArticleID() ;
281 # For existing categories
282 if( $id ) {
283 $sql = "SELECT DISTINCT cur_title,cur_namespace FROM cur,links WHERE l_to={$id} AND l_from=cur_id";
284 $res = wfQuery ( $sql, DB_READ ) ;
285 while ( $x = wfFetchObject ( $res ) ) $data[] = $x ;
286 } else {
287 # For non-existing categories
288 $t = wfStrencode( $this->mTitle->getPrefixedDBKey() );
289 $sql = "SELECT DISTINCT cur_title,cur_namespace FROM cur,brokenlinks WHERE bl_to='$t' AND bl_from=cur_id" ;
290 $res = wfQuery ( $sql, DB_READ ) ;
291 while ( $x = wfFetchObject ( $res ) ) $data[] = $x ;
294 # For all pages that link to this category
295 foreach ( $data AS $x )
297 $t = $wgLang->getNsText ( $x->cur_namespace ) ;
298 if ( $t != "" ) $t .= ":" ;
299 $t .= $x->cur_title ;
301 if ( $x->cur_namespace == $cns ) {
302 array_push ( $children , $sk->makeLink ( $t ) ) ; # Subcategory
303 } else {
304 array_push ( $articles , $sk->makeLink ( $t ) ) ; # Page in this category
307 wfFreeResult ( $res ) ;
309 # Showing subcategories
310 if ( count ( $children ) > 0 )
312 asort ( $children ) ;
313 $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
314 $r .= implode ( ", " , $children ) ;
317 # Showing pages in this category
318 if ( count ( $articles ) > 0 )
320 $ti = $this->mTitle->getText() ;
321 asort ( $articles ) ;
322 $h = wfMsg( "category_header", $ti );
323 $r .= "<h2>{$h}</h2>\n" ;
324 $r .= implode ( ", " , $articles ) ;
328 return $r ;
331 function getHTMLattrs ()
333 $htmlattrs = array( # Allowed attributes--no scripting, etc.
334 "title", "align", "lang", "dir", "width", "height",
335 "bgcolor", "clear", /* BR */ "noshade", /* HR */
336 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
337 /* FONT */ "type", "start", "value", "compact",
338 /* For various lists, mostly deprecated but safe */
339 "summary", "width", "border", "frame", "rules",
340 "cellspacing", "cellpadding", "valign", "char",
341 "charoff", "colgroup", "col", "span", "abbr", "axis",
342 "headers", "scope", "rowspan", "colspan", /* Tables */
343 "id", "class", "name", "style" /* For CSS */
345 return $htmlattrs ;
348 function fixTagAttributes ( $t )
350 if ( trim ( $t ) == "" ) return "" ; # Saves runtime ;-)
351 $htmlattrs = $this->getHTMLattrs() ;
353 # Strip non-approved attributes from the tag
354 $t = preg_replace(
355 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
356 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
357 $t);
358 # Strip javascript "expression" from stylesheets. Brute force approach:
359 # If anythin offensive is found, all attributes of the HTML tag are dropped
361 if( preg_match(
362 "/style\\s*=.*(expression|tps*:\/\/|url\\s*\().*/is",
363 wfMungeToUtf8( $t ) ) )
365 $t="";
368 return trim ( $t ) ;
371 function doTableStuff ( $t )
373 $t = explode ( "\n" , $t ) ;
374 $td = array () ; # Is currently a td tag open?
375 $ltd = array () ; # Was it TD or TH?
376 $tr = array () ; # Is currently a tr tag open?
377 $ltr = array () ; # tr attributes
378 foreach ( $t AS $k => $x )
380 $x = trim ( $x ) ;
381 $fc = substr ( $x , 0 , 1 ) ;
382 if ( "{|" == substr ( $x , 0 , 2 ) )
384 $t[$k] = "\n<table " . $this->fixTagAttributes ( substr ( $x , 3 ) ) . ">" ;
385 array_push ( $td , false ) ;
386 array_push ( $ltd , "" ) ;
387 array_push ( $tr , false ) ;
388 array_push ( $ltr , "" ) ;
390 else if ( count ( $td ) == 0 ) { } # Don't do any of the following
391 else if ( "|}" == substr ( $x , 0 , 2 ) )
393 $z = "</table>\n" ;
394 $l = array_pop ( $ltd ) ;
395 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
396 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
397 array_pop ( $ltr ) ;
398 $t[$k] = $z ;
400 /* else if ( "|_" == substr ( $x , 0 , 2 ) ) # Caption
402 $z = trim ( substr ( $x , 2 ) ) ;
403 $t[$k] = "<caption>{$z}</caption>\n" ;
405 else if ( "|-" == substr ( $x , 0 , 2 ) ) # Allows for |---------------
407 $x = substr ( $x , 1 ) ;
408 while ( $x != "" && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ;
409 $z = "" ;
410 $l = array_pop ( $ltd ) ;
411 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
412 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
413 array_pop ( $ltr ) ;
414 $t[$k] = $z ;
415 array_push ( $tr , false ) ;
416 array_push ( $td , false ) ;
417 array_push ( $ltd , "" ) ;
418 array_push ( $ltr , $this->fixTagAttributes ( $x ) ) ;
420 else if ( "|" == $fc || "!" == $fc || "|+" == substr ( $x , 0 , 2 ) ) # Caption
422 if ( "|+" == substr ( $x , 0 , 2 ) )
424 $fc = "+" ;
425 $x = substr ( $x , 1 ) ;
427 $after = substr ( $x , 1 ) ;
428 if ( $fc == "!" ) $after = str_replace ( "!!" , "||" , $after ) ;
429 $after = explode ( "||" , $after ) ;
430 $t[$k] = "" ;
431 foreach ( $after AS $theline )
433 $z = "" ;
434 if ( $fc != "+" )
436 $tra = array_pop ( $ltr ) ;
437 if ( !array_pop ( $tr ) ) $z = "<tr {$tra}>\n" ;
438 array_push ( $tr , true ) ;
439 array_push ( $ltr , "" ) ;
442 $l = array_pop ( $ltd ) ;
443 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
444 if ( $fc == "|" ) $l = "td" ;
445 else if ( $fc == "!" ) $l = "th" ;
446 else if ( $fc == "+" ) $l = "caption" ;
447 else $l = "" ;
448 array_push ( $ltd , $l ) ;
449 $y = explode ( "|" , $theline , 2 ) ;
450 if ( count ( $y ) == 1 ) $y = "{$z}<{$l}>{$y[0]}" ;
451 else $y = $y = "{$z}<{$l} ".$this->fixTagAttributes($y[0]).">{$y[1]}" ;
452 $t[$k] .= $y ;
453 array_push ( $td , true ) ;
458 # Closing open td, tr && table
459 while ( count ( $td ) > 0 )
461 if ( array_pop ( $td ) ) $t[] = "</td>" ;
462 if ( array_pop ( $tr ) ) $t[] = "</tr>" ;
463 $t[] = "</table>" ;
466 $t = implode ( "\n" , $t ) ;
467 # $t = $this->removeHTMLtags( $t );
468 return $t ;
471 function internalParse( $text, $linestart, $args = array() )
473 $fname = "Parser::internalParse";
474 wfProfileIn( $fname );
476 $text = $this->removeHTMLtags( $text );
477 $text = $this->replaceVariables( $text, $args );
479 # $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
481 $text = $this->doHeadings( $text );
482 if($this->mOptions->getUseDynamicDates()) {
483 global $wgDateFormatter;
484 $text = $wgDateFormatter->reformat( $this->mOptions->getDateFormat(), $text );
486 $text = $this->replaceExternalLinks( $text );
487 $text = $this->doTokenizedParser ( $text );
488 $text = $this->doTableStuff ( $text ) ;
489 $text = $this->formatHeadings( $text );
490 $sk =& $this->mOptions->getSkin();
491 $text = $sk->transformContent( $text );
493 if ( !isset ( $this->categoryMagicDone ) ) {
494 $text .= $this->categoryMagic () ;
495 $this->categoryMagicDone = true ;
498 wfProfileOut( $fname );
499 return $text;
503 /* private */ function doHeadings( $text )
505 for ( $i = 6; $i >= 1; --$i ) {
506 $h = substr( "======", 0, $i );
507 $text = preg_replace( "/^{$h}(.+){$h}(\\s|$)/m",
508 "<h{$i}>\\1</h{$i}>\\2", $text );
510 return $text;
513 # Note: we have to do external links before the internal ones,
514 # and otherwise take great care in the order of things here, so
515 # that we don't end up interpreting some URLs twice.
517 /* private */ function replaceExternalLinks( $text )
519 $fname = "Parser::replaceExternalLinks";
520 wfProfileIn( $fname );
521 $text = $this->subReplaceExternalLinks( $text, "http", true );
522 $text = $this->subReplaceExternalLinks( $text, "https", true );
523 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
524 $text = $this->subReplaceExternalLinks( $text, "irc", false );
525 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
526 $text = $this->subReplaceExternalLinks( $text, "news", false );
527 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
528 wfProfileOut( $fname );
529 return $text;
532 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
534 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
535 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
537 # this is the list of separators that should be ignored if they
538 # are the last character of an URL but that should be included
539 # if they occur within the URL, e.g. "go to www.foo.com, where .."
540 # in this case, the last comma should not become part of the URL,
541 # but in "www.foo.com/123,2342,32.htm" it should.
542 $sep = ",;\.:";
543 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
544 $images = "gif|png|jpg|jpeg";
546 # PLEASE NOTE: The curly braces { } are not part of the regex,
547 # they are interpreted as part of the string (used to tell PHP
548 # that the content of the string should be inserted there).
549 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
550 "((?i){$images})([^{$uc}]|$)/";
552 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
553 $sk =& $this->mOptions->getSkin();
555 if ( $autonumber and $this->mOptions->getAllowExternalImages() ) { # Use img tags only for HTTP urls
556 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
557 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
559 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
560 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
561 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
562 "</a>\\5", $s );
563 $s = str_replace( $unique, $protocol, $s );
565 $a = explode( "[{$protocol}:", " " . $s );
566 $s = array_shift( $a );
567 $s = substr( $s, 1 );
569 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
570 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
572 foreach ( $a as $line ) {
573 if ( preg_match( $e1, $line, $m ) ) {
574 $link = "{$protocol}:{$m[1]}";
575 $trail = $m[2];
576 if ( $autonumber ) { $text = "[" . ++$this->mAutonumber . "]"; }
577 else { $text = wfEscapeHTML( $link ); }
578 } else if ( preg_match( $e2, $line, $m ) ) {
579 $link = "{$protocol}:{$m[1]}";
580 $text = $m[2];
581 $trail = $m[3];
582 } else {
583 $s .= "[{$protocol}:" . $line;
584 continue;
586 if( $link == $text || preg_match( "!$protocol://" . preg_quote( $text, "/" ) . "/?$!", $link ) ) {
587 $paren = "";
588 } else {
589 # Expand the URL for printable version
590 $paren = "<span class='urlexpansion'> (<i>" . htmlspecialchars ( $link ) . "</i>)</span>";
592 $la = $sk->getExternalLinkAttributes( $link, $text );
593 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
596 return $s;
599 /* private */ function handle3Quotes( &$state, $token )
601 if ( $state["strong"] !== false ) {
602 if ( $state["em"] !== false && $state["em"] > $state["strong"] )
604 # ''' lala ''lala '''
605 $s = "</em></strong><em>";
606 } else {
607 $s = "</strong>";
609 $state["strong"] = FALSE;
610 } else {
611 $s = "<strong>";
612 $state["strong"] = isset($token["pos"]) ? $token["pos"] : true;
614 return $s;
617 /* private */ function handle2Quotes( &$state, $token )
619 if ( $state["em"] !== false ) {
620 if ( $state["strong"] !== false && $state["strong"] > $state["em"] )
622 # ''lala'''lala'' ....'''
623 $s = "</strong></em><strong>";
624 } else {
625 $s = "</em>";
627 $state["em"] = FALSE;
628 } else {
629 $s = "<em>";
630 $state["em"] = isset($token["pos"]) ? $token["pos"] : true;
633 return $s;
636 /* private */ function handle5Quotes( &$state, $token )
638 $s = "";
639 if ( $state["em"] !== false && $state["strong"] !== false ) {
640 if ( $state["em"] < $state["strong"] ) {
641 $s .= "</strong></em>";
642 } else {
643 $s .= "</em></strong>";
645 $state["strong"] = $state["em"] = FALSE;
646 } elseif ( $state["em"] !== false ) {
647 $s .= "</em><strong>";
648 $state["em"] = FALSE;
649 $state["strong"] = $token["pos"];
650 } elseif ( $state["strong"] !== false ) {
651 $s .= "</strong><em>";
652 $state["strong"] = FALSE;
653 $state["em"] = $token["pos"];
654 } else { # not $em and not $strong
655 $s .= "<strong><em>";
656 $state["strong"] = $state["em"] = isset($token["pos"]) ? $token["pos"] : true;
658 return $s;
661 /* private */ function doTokenizedParser( $str )
663 global $wgLang; # for language specific parser hook
664 global $wgUploadDirectory, $wgUseTimeline;
666 $tokenizer=Tokenizer::newFromString( $str );
667 $tokenStack = array();
669 $s="";
670 $state["em"] = FALSE;
671 $state["strong"] = FALSE;
672 $tagIsOpen = FALSE;
673 $threeopen = false;
675 # The tokenizer splits the text into tokens and returns them one by one.
676 # Every call to the tokenizer returns a new token.
677 while ( $token = $tokenizer->nextToken() )
679 switch ( $token["type"] )
681 case "text":
682 # simple text with no further markup
683 $txt = $token["text"];
684 break;
685 case "blank":
686 # Text that contains blanks that have to be converted to
687 # non-breakable spaces for French.
688 # U+202F NARROW NO-BREAK SPACE might be a better choice, but
689 # browser support for Unicode spacing is poor.
690 $txt = str_replace( " ", "&nbsp;", $token["text"] );
691 break;
692 case "[[[":
693 # remember the tag opened with 3 [
694 $threeopen = true;
695 case "[[":
696 # link opening tag.
697 # FIXME : Treat orphaned open tags (stack not empty when text is over)
698 $tagIsOpen = TRUE;
699 array_push( $tokenStack, $token );
700 $txt="";
701 break;
703 case "]]]":
704 case "]]":
705 # link close tag.
706 # get text from stack, glue it together, and call the code to handle a
707 # link
709 if ( count( $tokenStack ) == 0 )
711 # stack empty. Found a ]] without an opening [[
712 $txt = "]]";
713 } else {
714 $linkText = "";
715 $lastToken = array_pop( $tokenStack );
716 while ( !(($lastToken["type"] == "[[[") or ($lastToken["type"] == "[[")) )
718 if( !empty( $lastToken["text"] ) ) {
719 $linkText = $lastToken["text"] . $linkText;
721 $lastToken = array_pop( $tokenStack );
724 $txt = $linkText ."]]";
726 if( isset( $lastToken["text"] ) ) {
727 $prefix = $lastToken["text"];
728 } else {
729 $prefix = "";
731 $nextToken = $tokenizer->previewToken();
732 if ( $nextToken["type"] == "text" )
734 # Preview just looks at it. Now we have to fetch it.
735 $nextToken = $tokenizer->nextToken();
736 $txt .= $nextToken["text"];
738 $txt = $this->handleInternalLink( $this->unstrip($txt,$this->mStripState), $prefix );
740 # did the tag start with 3 [ ?
741 if($threeopen) {
742 # show the first as text
743 $txt = "[".$txt;
744 $threeopen=false;
748 $tagIsOpen = (count( $tokenStack ) != 0);
749 break;
750 case "----":
751 $txt = "\n<hr />\n";
752 break;
753 case "'''":
754 # This and the three next ones handle quotes
755 $txt = $this->handle3Quotes( $state, $token );
756 break;
757 case "''":
758 $txt = $this->handle2Quotes( $state, $token );
759 break;
760 case "'''''":
761 $txt = $this->handle5Quotes( $state, $token );
762 break;
763 case "":
764 # empty token
765 $txt="";
766 break;
767 case "RFC ":
768 if ( $tagIsOpen ) {
769 $txt = "RFC ";
770 } else {
771 $txt = $this->doMagicRFC( $tokenizer );
773 break;
774 case "ISBN ":
775 if ( $tagIsOpen ) {
776 $txt = "ISBN ";
777 } else {
778 $txt = $this->doMagicISBN( $tokenizer );
780 break;
781 case "<timeline>":
782 if ( $wgUseTimeline &&
783 "" != ( $timelinesrc = $tokenizer->readAllUntil("&lt;/timeline&gt;") ) )
785 $txt = renderTimeline( $timelinesrc );
786 } else {
787 $txt=$token["text"];
789 break;
790 default:
791 # Call language specific Hook.
792 $txt = $wgLang->processToken( $token, $tokenStack );
793 if ( NULL == $txt ) {
794 # An unkown token. Highlight.
795 $txt = "<font color=\"#FF0000\"><b>".$token["type"]."</b></font>";
796 $txt .= "<font color=\"#FFFF00\"><b>".$token["text"]."</b></font>";
798 break;
800 # If we're parsing the interior of a link, don't append the interior to $s,
801 # but push it to the stack so it can be processed when a ]] token is found.
802 if ( $tagIsOpen && $txt != "" ) {
803 $token["type"] = "text";
804 $token["text"] = $txt;
805 array_push( $tokenStack, $token );
806 } else {
807 $s .= $txt;
809 } #end while
810 if ( count( $tokenStack ) != 0 )
812 # still objects on stack. opened [[ tag without closing ]] tag.
813 $txt = "";
814 while ( $lastToken = array_pop( $tokenStack ) )
816 if ( $lastToken["type"] == "text" )
818 $txt = $lastToken["text"] . $txt;
819 } else {
820 $txt = $lastToken["type"] . $txt;
823 $s .= $txt;
825 return $s;
828 /* private */ function handleInternalLink( $line, $prefix )
830 global $wgLang, $wgLinkCache;
831 global $wgNamespacesWithSubpages, $wgLanguageCode;
832 static $fname = "Parser::handleInternalLink" ;
833 wfProfileIn( $fname );
835 wfProfileIn( "$fname-setup" );
836 static $tc = FALSE;
837 if ( !$tc ) { $tc = Title::legalChars() . "#"; }
838 $sk =& $this->mOptions->getSkin();
840 # Match a link having the form [[namespace:link|alternate]]trail
841 static $e1 = FALSE;
842 if ( !$e1 ) { $e1 = "/^([{$tc}]+)(?:\\|([^]]+))?]](.*)\$/sD"; }
843 # Match the end of a line for a word that's not followed by whitespace,
844 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
845 #$e2 = "/^(.*)\\b(\\w+)\$/suD";
846 #$e2 = "/^(.*\\s)(\\S+)\$/suD";
847 static $e2 = '/^(.*\s)([a-zA-Z\x80-\xff]+)$/sD';
850 # Special and Media are pseudo-namespaces; no pages actually exist in them
851 static $image = FALSE;
852 static $special = FALSE;
853 static $media = FALSE;
854 static $category = FALSE;
855 if ( !$image ) { $image = Namespace::getImage(); }
856 if ( !$special ) { $special = Namespace::getSpecial(); }
857 if ( !$media ) { $media = Namespace::getMedia(); }
858 if ( !$category ) { $category = Namespace::getCategory(); ; }
860 $nottalk = !Namespace::isTalk( $this->mTitle->getNamespace() );
862 wfProfileOut( "$fname-setup" );
863 $s = "";
865 if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
866 $text = $m[2];
867 $trail = $m[3];
868 } else { # Invalid form; output directly
869 $s .= $prefix . "[[" . $line ;
870 return $s;
873 /* Valid link forms:
874 Foobar -- normal
875 :Foobar -- override special treatment of prefix (images, language links)
876 /Foobar -- convert to CurrentPage/Foobar
877 /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
879 $c = substr($m[1],0,1);
880 $noforce = ($c != ":");
881 if( $c == "/" ) { # subpage
882 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
883 $m[1]=substr($m[1],1,strlen($m[1])-2);
884 $noslash=$m[1];
885 } else {
886 $noslash=substr($m[1],1);
888 if($wgNamespacesWithSubpages[$this->mTitle->getNamespace()]) { # subpages allowed here
889 $link = $this->mTitle->getPrefixedText(). "/" . trim($noslash);
890 if( "" == $text ) {
891 $text= $m[1];
892 } # this might be changed for ugliness reasons
893 } else {
894 $link = $noslash; # no subpage allowed, use standard link
896 } elseif( $noforce ) { # no subpage
897 $link = $m[1];
898 } else {
899 $link = substr( $m[1], 1 );
901 if( "" == $text )
902 $text = $link;
904 $nt = Title::newFromText( $link );
905 if( !$nt ) {
906 $s .= $prefix . "[[" . $line;
907 return $s;
909 $ns = $nt->getNamespace();
910 $iw = $nt->getInterWiki();
911 if( $noforce ) {
912 if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgLang->getLanguageName( $iw ) ) {
913 array_push( $this->mOutput->mLanguageLinks, $nt->getPrefixedText() );
914 return (trim($s) == '')? '': $s;
916 if( $ns == $image ) {
917 $s .= $prefix . $sk->makeImageLinkObj( $nt, $text ) . $trail;
918 $wgLinkCache->addImageLinkObj( $nt );
919 return $s;
921 if ( $ns == $category ) {
922 $t = $nt->getText() ;
923 $nnt = Title::newFromText ( Namespace::getCanonicalName($category).":".$t ) ;
924 $t = $sk->makeLinkObj( $nnt, $t, "", "" , $prefix );
925 $this->mOutput->mCategoryLinks[] = $t ;
926 $s .= $prefix . $trail ;
927 return $s ;
930 if( ( $nt->getPrefixedText() == $this->mTitle->getPrefixedText() ) &&
931 ( strpos( $link, "#" ) == FALSE ) ) {
932 # Self-links are handled specially; generally de-link and change to bold.
933 $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, "", $trail );
934 return $s;
937 if( $ns == $media ) {
938 $s .= $prefix . $sk->makeMediaLinkObj( $nt, $text ) . $trail;
939 $wgLinkCache->addImageLinkObj( $nt );
940 return $s;
941 } elseif( $ns == $special ) {
942 $s .= $prefix . $sk->makeKnownLinkObj( $nt, $text, "", $trail );
943 return $s;
945 $s .= $sk->makeLinkObj( $nt, $text, "", $trail , $prefix );
947 wfProfileOut( $fname );
948 return $s;
951 # Some functions here used by doBlockLevels()
953 /* private */ function closeParagraph()
955 $result = "";
956 if ( '' != $this->mLastSection ) {
957 $result = "</" . $this->mLastSection . ">\n";
959 $this->mInPre = false;
960 $this->mLastSection = "";
961 return $result;
963 # getCommon() returns the length of the longest common substring
964 # of both arguments, starting at the beginning of both.
966 /* private */ function getCommon( $st1, $st2 )
968 $fl = strlen( $st1 );
969 $shorter = strlen( $st2 );
970 if ( $fl < $shorter ) { $shorter = $fl; }
972 for ( $i = 0; $i < $shorter; ++$i ) {
973 if ( $st1{$i} != $st2{$i} ) { break; }
975 return $i;
977 # These next three functions open, continue, and close the list
978 # element appropriate to the prefix character passed into them.
980 /* private */ function openList( $char )
982 $result = $this->closeParagraph();
984 if ( "*" == $char ) { $result .= "<ul><li>"; }
985 else if ( "#" == $char ) { $result .= "<ol><li>"; }
986 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
987 else if ( ";" == $char ) {
988 $result .= "<dl><dt>";
989 $this->mDTopen = true;
991 else { $result = "<!-- ERR 1 -->"; }
993 return $result;
996 /* private */ function nextItem( $char )
998 if ( "*" == $char || "#" == $char ) { return "</li><li>"; }
999 else if ( ":" == $char || ";" == $char ) {
1000 $close = "</dd>";
1001 if ( $this->mDTopen ) { $close = "</dt>"; }
1002 if ( ";" == $char ) {
1003 $this->mDTopen = true;
1004 return $close . "<dt>";
1005 } else {
1006 $this->mDTopen = false;
1007 return $close . "<dd>";
1010 return "<!-- ERR 2 -->";
1013 /* private */function closeList( $char )
1015 if ( "*" == $char ) { $text = "</li></ul>"; }
1016 else if ( "#" == $char ) { $text = "</li></ol>"; }
1017 else if ( ":" == $char ) {
1018 if ( $this->mDTopen ) {
1019 $this->mDTopen = false;
1020 $text = "</dt></dl>";
1021 } else {
1022 $text = "</dd></dl>";
1025 else { return "<!-- ERR 3 -->"; }
1026 return $text."\n";
1029 /* private */ function doBlockLevels( $text, $linestart )
1031 $fname = "Parser::doBlockLevels";
1032 wfProfileIn( $fname );
1033 # Parsing through the text line by line. The main thing
1034 # happening here is handling of block-level elements p, pre,
1035 # and making lists from lines starting with * # : etc.
1037 $a = explode( "\n", $text );
1039 $lastPref = $text = $lastLine = '';
1040 $this->mDTopen = $inBlockElem = false;
1041 $npl = 0;
1042 $pstack = false;
1044 if ( ! $linestart ) { $text .= array_shift( $a ); }
1045 foreach ( $a as $t ) {
1046 $oLine = $t;
1047 $opl = strlen( $lastPref );
1048 $preCloseMatch = preg_match("/<\\/pre/i", $t );
1049 $preOpenMatch = preg_match("/<pre/i", $t );
1050 if (!$this->mInPre) {
1051 $this->mInPre = !empty($preOpenMatch);
1053 if ( !$this->mInPre ) {
1054 $npl = strspn( $t, "*#:;" );
1055 $pref = substr( $t, 0, $npl );
1056 $pref2 = str_replace( ";", ":", $pref );
1057 $t = substr( $t, $npl );
1058 } else {
1059 $npl = 0;
1060 $pref = $pref2 = '';
1063 // list generation
1064 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1065 $text .= $this->nextItem( substr( $pref, -1 ) );
1066 if ( $pstack ) { $pstack = false; }
1068 if ( ";" == substr( $pref, -1 ) ) {
1069 $cpos = strpos( $t, ":" );
1070 if ( false !== $cpos ) {
1071 $term = substr( $t, 0, $cpos );
1072 $text .= $term . $this->nextItem( ":" );
1073 $t = substr( $t, $cpos + 1 );
1076 } else if (0 != $npl || 0 != $opl) {
1077 $cpl = $this->getCommon( $pref, $lastPref );
1078 if ( $pstack ) { $pstack = false; }
1080 while ( $cpl < $opl ) {
1081 $text .= $this->closeList( $lastPref{$opl-1} );
1082 --$opl;
1084 if ( $npl <= $cpl && $cpl > 0 ) {
1085 $text .= $this->nextItem( $pref{$cpl-1} );
1087 while ( $npl > $cpl ) {
1088 $char = substr( $pref, $cpl, 1 );
1089 $text .= $this->openList( $char );
1091 if ( ";" == $char ) {
1092 $cpos = strpos( $t, ":" );
1093 if ( ! ( false === $cpos ) ) {
1094 $term = substr( $t, 0, $cpos );
1095 $text .= $term . $this->nextItem( ":" );
1096 $t = substr( $t, $cpos + 1 );
1099 ++$cpl;
1101 $lastPref = $pref2;
1103 if ( 0 == $npl ) { # No prefix (not in list)--go to paragraph mode
1104 $uniq_prefix = UNIQ_PREFIX;
1105 // XXX: use a stack for nestable elements like span, table and div
1106 $openmatch = preg_match("/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<div|<pre|<tr|<td|<p|<ul|<li)/i", $t );
1107 $closematch = preg_match(
1108 "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|".
1109 "<\\/div|<hr|<\\/td|<\\/pre|<\\/p|".$uniq_prefix."-pre|<\\/li|<\\/ul)/i", $t );
1110 if ( $openmatch or $closematch ) {
1111 if ( $pstack ) { $pstack = false; }
1112 $text .= $this->closeParagraph();
1113 if($preOpenMatch and !$preCloseMatch) {
1114 $this->mInPre = true;
1116 if ( $closematch ) {
1117 $inBlockElem = false;
1118 } else {
1119 $inBlockElem = true;
1121 } else if ( !$inBlockElem ) {
1122 if ( " " == $t{0} ) {
1123 // pre
1124 if ($this->mLastSection != 'pre') {
1125 $pstack = false;
1126 $text .= $this->closeParagraph().'<pre>';
1127 $this->mLastSection = 'pre';
1129 } else {
1130 // paragraph
1131 if ( '' == trim($t) ) {
1132 if ( $pstack ) {
1133 $text .= $pstack.'<br/>';
1134 $pstack = false;
1135 $this->mLastSection = 'p';
1136 } else {
1137 if ($this->mLastSection != 'p' ) {
1138 $text .= $this->closeParagraph();
1139 $this->mLastSection = '';
1140 $pstack = "<p>";
1141 } else {
1142 $pstack = '</p><p>';
1145 } else {
1146 if ( $pstack ) {
1147 $text .= $pstack;
1148 $pstack = false;
1149 $this->mLastSection = 'p';
1150 } else if ($this->mLastSection != 'p') {
1151 $text .= $this->closeParagraph().'<p>';
1152 $this->mLastSection = 'p';
1158 if ($pstack === false) {
1159 $text .= $t."\n";
1162 while ( $npl ) {
1163 $text .= $this->closeList( $pref2{$npl-1} );
1164 --$npl;
1166 if ( "" != $this->mLastSection ) {
1167 $text .= "</" . $this->mLastSection . ">";
1168 $this->mLastSection = "";
1171 wfProfileOut( $fname );
1172 return $text;
1175 function getVariableValue( $index ) {
1176 global $wgLang, $wgSitename, $wgServer;
1178 switch ( $index ) {
1179 case MAG_CURRENTMONTH:
1180 return date( "m" );
1181 case MAG_CURRENTMONTHNAME:
1182 return $wgLang->getMonthName( date("n") );
1183 case MAG_CURRENTMONTHNAMEGEN:
1184 return $wgLang->getMonthNameGen( date("n") );
1185 case MAG_CURRENTDAY:
1186 return date("j");
1187 case MAG_PAGENAME:
1188 return $this->mTitle->getText();
1189 case MAG_NAMESPACE:
1190 # return Namespace::getCanonicalName($this->mTitle->getNamespace());
1191 return $wgLang->getNsText($this->mTitle->getNamespace()); // Patch by Dori
1192 case MAG_CURRENTDAYNAME:
1193 return $wgLang->getWeekdayName( date("w")+1 );
1194 case MAG_CURRENTYEAR:
1195 return date( "Y" );
1196 case MAG_CURRENTTIME:
1197 return $wgLang->time( wfTimestampNow(), false );
1198 case MAG_NUMBEROFARTICLES:
1199 return wfNumberOfArticles();
1200 case MAG_SITENAME:
1201 return $wgSitename;
1202 case MAG_SERVER:
1203 return $wgServer;
1204 default:
1205 return NULL;
1209 function initialiseVariables()
1211 global $wgVariableIDs;
1212 $this->mVariables = array();
1213 foreach ( $wgVariableIDs as $id ) {
1214 $mw =& MagicWord::get( $id );
1215 $mw->addToArray( $this->mVariables, $this->getVariableValue( $id ) );
1219 /* private */ function replaceVariables( $text, $args = array() )
1221 global $wgLang, $wgScript, $wgArticlePath;
1223 $fname = "Parser::replaceVariables";
1224 wfProfileIn( $fname );
1226 $bail = false;
1227 if ( !$this->mVariables ) {
1228 $this->initialiseVariables();
1230 $titleChars = Title::legalChars();
1231 $regex = "/(\\n?){{([$titleChars]*?)(\\|.*?|)}}/s";
1233 # This function is called recursively. To keep track of arguments we need a stack:
1234 array_push( $this->mArgStack, $args );
1236 # PHP global rebinding syntax is a bit weird, need to use the GLOBALS array
1237 $GLOBALS['wgCurParser'] =& $this;
1238 $text = preg_replace_callback( $regex, "wfBraceSubstitution", $text );
1240 array_pop( $this->mArgStack );
1242 return $text;
1245 function braceSubstitution( $matches )
1247 global $wgLinkCache, $wgLang;
1248 $fname = "Parser::braceSubstitution";
1249 $found = false;
1250 $nowiki = false;
1251 $title = NULL;
1253 # $newline is an optional newline character before the braces
1254 # $part1 is the bit before the first |, and must contain only title characters
1255 # $args is a list of arguments, starting from index 0, not including $part1
1257 $newline = $matches[1];
1258 $part1 = $matches[2];
1259 # If the third subpattern matched anything, it will start with |
1260 if ( $matches[3] !== "" ) {
1261 $args = explode( "|", substr( $matches[3], 1 ) );
1262 } else {
1263 $args = array();
1265 $argc = count( $args );
1267 # SUBST
1268 $mwSubst =& MagicWord::get( MAG_SUBST );
1269 if ( $mwSubst->matchStartAndRemove( $part1 ) ) {
1270 if ( $this->mOutputType != OT_WIKI ) {
1271 # Invalid SUBST not replaced at PST time
1272 # Return without further processing
1273 $text = $matches[0];
1274 $found = true;
1276 } elseif ( $this->mOutputType == OT_WIKI ) {
1277 # SUBST not found in PST pass, do nothing
1278 $text = $matches[0];
1279 $found = true;
1282 # MSG, MSGNW and INT
1283 if ( !$found ) {
1284 # Check for MSGNW:
1285 $mwMsgnw =& MagicWord::get( MAG_MSGNW );
1286 if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
1287 $nowiki = true;
1288 } else {
1289 # Remove obsolete MSG:
1290 $mwMsg =& MagicWord::get( MAG_MSG );
1291 $mwMsg->matchStartAndRemove( $part1 );
1294 # Check if it is an internal message
1295 $mwInt =& MagicWord::get( MAG_INT );
1296 if ( $mwInt->matchStartAndRemove( $part1 ) ) {
1297 if ( $this->incrementIncludeCount( "int:$part1" ) ) {
1298 $text = wfMsgReal( $part1, $args, true );
1299 $found = true;
1304 # NS
1305 if ( !$found ) {
1306 # Check for NS: (namespace expansion)
1307 $mwNs = MagicWord::get( MAG_NS );
1308 if ( $mwNs->matchStartAndRemove( $part1 ) ) {
1309 if ( intval( $part1 ) ) {
1310 $text = $wgLang->getNsText( intval( $part1 ) );
1311 $found = true;
1312 } else {
1313 $index = Namespace::getCanonicalIndex( strtolower( $part1 ) );
1314 if ( !is_null( $index ) ) {
1315 $text = $wgLang->getNsText( $index );
1316 $found = true;
1322 # LOCALURL and LOCALURLE
1323 if ( !$found ) {
1324 $mwLocal = MagicWord::get( MAG_LOCALURL );
1325 $mwLocalE = MagicWord::get( MAG_LOCALURLE );
1327 if ( $mwLocal->matchStartAndRemove( $part1 ) ) {
1328 $func = 'getLocalURL';
1329 } elseif ( $mwLocalE->matchStartAndRemove( $part1 ) ) {
1330 $func = 'escapeLocalURL';
1331 } else {
1332 $func = '';
1335 if ( $func !== '' ) {
1336 $title = Title::newFromText( $part1 );
1337 if ( !is_null( $title ) ) {
1338 if ( $argc > 0 ) {
1339 $text = $title->$func( $args[0] );
1340 } else {
1341 $text = $title->$func();
1343 $found = true;
1348 # Internal variables
1349 if ( !$found && array_key_exists( $part1, $this->mVariables ) ) {
1350 $text = $this->mVariables[$part1];
1351 $found = true;
1352 $this->mOutput->mContainsOldMagic = true;
1355 # Arguments input from the caller
1356 $inputArgs = end( $this->mArgStack );
1357 if ( !$found && array_key_exists( $part1, $inputArgs ) ) {
1358 $text = $inputArgs[$part1];
1359 $found = true;
1362 # Load from database
1363 if ( !$found ) {
1364 $title = Title::newFromText( $part1, NS_TEMPLATE );
1365 if ( !is_null( $title ) && !$title->isExternal() ) {
1366 # Check for excessive inclusion
1367 $dbk = $title->getPrefixedDBkey();
1368 if ( $this->incrementIncludeCount( $dbk ) ) {
1369 $article = new Article( $title );
1370 $articleContent = $article->getContentWithoutUsingSoManyDamnGlobals();
1371 if ( $articleContent !== false ) {
1372 $found = true;
1373 $text = $articleContent;
1378 # If the title is valid but undisplayable, make a link to it
1379 if ( $this->mOutputType == OT_HTML && !$found ) {
1380 $text = "[[" . $title->getPrefixedText() . "]]";
1381 $found = true;
1386 # Recursive parsing, escaping and link table handling
1387 # Only for HTML output
1388 if ( $nowiki && $found && $this->mOutputType == OT_HTML ) {
1389 $text = wfEscapeWikiText( $text );
1390 } elseif ( $this->mOutputType == OT_HTML && $found ) {
1391 # Clean up argument array
1392 $assocArgs = array();
1393 $index = 1;
1394 foreach( $args as $arg ) {
1395 $eqpos = strpos( $arg, "=" );
1396 if ( $eqpos === false ) {
1397 $assocArgs[$index++] = $arg;
1398 } else {
1399 $name = trim( substr( $arg, 0, $eqpos ) );
1400 $value = trim( substr( $arg, $eqpos+1 ) );
1401 if ( $value === false ) {
1402 $value = "";
1404 if ( $name !== false ) {
1405 $assocArgs[$name] = $value;
1410 # Do not enter included links in link table
1411 if ( !is_null( $title ) ) {
1412 $wgLinkCache->suspend();
1415 # Run full parser on the included text
1416 $text = $this->strip( $text, $this->mStripState );
1417 $text = $this->internalParse( $text, (bool)$newline, $assocArgs );
1419 # Add the result to the strip state for re-inclusion after
1420 # the rest of the processing
1421 $text = $this->insertStripItem( $text, $this->mStripState );
1423 # Resume the link cache and register the inclusion as a link
1424 if ( !is_null( $title ) ) {
1425 $wgLinkCache->resume();
1426 $wgLinkCache->addLinkObj( $title );
1430 if ( !$found ) {
1431 return $matches[0];
1432 } else {
1433 return $newline . $text;
1437 # Returns true if the function is allowed to include this entity
1438 function incrementIncludeCount( $dbk )
1440 if ( !array_key_exists( $dbk, $this->mIncludeCount ) ) {
1441 $this->mIncludeCount[$dbk] = 0;
1443 if ( ++$this->mIncludeCount[$dbk] <= MAX_INCLUDE_REPEAT ) {
1444 return true;
1445 } else {
1446 return false;
1451 # Cleans up HTML, removes dangerous tags and attributes
1452 /* private */ function removeHTMLtags( $text )
1454 $fname = "Parser::removeHTMLtags";
1455 wfProfileIn( $fname );
1456 $htmlpairs = array( # Tags that must be closed
1457 "b", "del", "i", "ins", "u", "font", "big", "small", "sub", "sup", "h1",
1458 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1459 "strike", "strong", "tt", "var", "div", "center",
1460 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1461 "ruby", "rt" , "rb" , "rp", "p"
1463 $htmlsingle = array(
1464 "br", "hr", "li", "dt", "dd"
1466 $htmlnest = array( # Tags that can be nested--??
1467 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1468 "dl", "font", "big", "small", "sub", "sup"
1470 $tabletags = array( # Can only appear inside table
1471 "td", "th", "tr"
1474 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1475 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1477 $htmlattrs = $this->getHTMLattrs () ;
1479 # Remove HTML comments
1480 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1482 $bits = explode( "<", $text );
1483 $text = array_shift( $bits );
1484 $tagstack = array(); $tablestack = array();
1486 foreach ( $bits as $x ) {
1487 $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
1488 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1489 $x, $regs );
1490 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1491 error_reporting( $prev );
1493 $badtag = 0 ;
1494 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1495 # Check our stack
1496 if ( $slash ) {
1497 # Closing a tag...
1498 if ( ! in_array( $t, $htmlsingle ) &&
1499 ( $ot = array_pop( $tagstack ) ) != $t ) {
1500 array_push( $tagstack, $ot );
1501 $badtag = 1;
1502 } else {
1503 if ( $t == "table" ) {
1504 $tagstack = array_pop( $tablestack );
1506 $newparams = "";
1508 } else {
1509 # Keep track for later
1510 if ( in_array( $t, $tabletags ) &&
1511 ! in_array( "table", $tagstack ) ) {
1512 $badtag = 1;
1513 } else if ( in_array( $t, $tagstack ) &&
1514 ! in_array ( $t , $htmlnest ) ) {
1515 $badtag = 1 ;
1516 } else if ( ! in_array( $t, $htmlsingle ) ) {
1517 if ( $t == "table" ) {
1518 array_push( $tablestack, $tagstack );
1519 $tagstack = array();
1521 array_push( $tagstack, $t );
1523 # Strip non-approved attributes from the tag
1524 $newparams = $this->fixTagAttributes($params);
1527 if ( ! $badtag ) {
1528 $rest = str_replace( ">", "&gt;", $rest );
1529 $text .= "<$slash$t $newparams$brace$rest";
1530 continue;
1533 $text .= "&lt;" . str_replace( ">", "&gt;", $x);
1535 # Close off any remaining tags
1536 while ( $t = array_pop( $tagstack ) ) {
1537 $text .= "</$t>\n";
1538 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1540 wfProfileOut( $fname );
1541 return $text;
1546 * This function accomplishes several tasks:
1547 * 1) Auto-number headings if that option is enabled
1548 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1549 * 3) Add a Table of contents on the top for users who have enabled the option
1550 * 4) Auto-anchor headings
1552 * It loops through all headlines, collects the necessary data, then splits up the
1553 * string and re-inserts the newly formatted headlines.
1557 /* private */ function formatHeadings( $text )
1559 $doNumberHeadings = $this->mOptions->getNumberHeadings();
1560 $doShowToc = $this->mOptions->getShowToc();
1561 if( !$this->mTitle->userCanEdit() ) {
1562 $showEditLink = 0;
1563 $rightClickHack = 0;
1564 } else {
1565 $showEditLink = $this->mOptions->getEditSection();
1566 $rightClickHack = $this->mOptions->getEditSectionOnRightClick();
1569 # Inhibit editsection links if requested in the page
1570 $esw =& MagicWord::get( MAG_NOEDITSECTION );
1571 if( $esw->matchAndRemove( $text ) ) {
1572 $showEditLink = 0;
1574 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
1575 # do not add TOC
1576 $mw =& MagicWord::get( MAG_NOTOC );
1577 if( $mw->matchAndRemove( $text ) ) {
1578 $doShowToc = 0;
1581 # never add the TOC to the Main Page. This is an entry page that should not
1582 # be more than 1-2 screens large anyway
1583 if( $this->mTitle->getPrefixedText() == wfMsg("mainpage") ) {
1584 $doShowToc = 0;
1587 # Get all headlines for numbering them and adding funky stuff like [edit]
1588 # links - this is for later, but we need the number of headlines right now
1589 $numMatches = preg_match_all( "/<H([1-6])(.*?" . ">)(.*?)<\/H[1-6]>/i", $text, $matches );
1591 # if there are fewer than 4 headlines in the article, do not show TOC
1592 if( $numMatches < 4 ) {
1593 $doShowToc = 0;
1596 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
1597 # override above conditions and always show TOC
1598 $mw =& MagicWord::get( MAG_FORCETOC );
1599 if ($mw->matchAndRemove( $text ) ) {
1600 $doShowToc = 1;
1604 # We need this to perform operations on the HTML
1605 $sk =& $this->mOptions->getSkin();
1607 # headline counter
1608 $headlineCount = 0;
1610 # Ugh .. the TOC should have neat indentation levels which can be
1611 # passed to the skin functions. These are determined here
1612 $toclevel = 0;
1613 $toc = "";
1614 $full = "";
1615 $head = array();
1616 $sublevelCount = array();
1617 $level = 0;
1618 $prevlevel = 0;
1619 foreach( $matches[3] as $headline ) {
1620 $numbering = "";
1621 if( $level ) {
1622 $prevlevel = $level;
1624 $level = $matches[1][$headlineCount];
1625 if( ( $doNumberHeadings || $doShowToc ) && $prevlevel && $level > $prevlevel ) {
1626 # reset when we enter a new level
1627 $sublevelCount[$level] = 0;
1628 $toc .= $sk->tocIndent( $level - $prevlevel );
1629 $toclevel += $level - $prevlevel;
1631 if( ( $doNumberHeadings || $doShowToc ) && $level < $prevlevel ) {
1632 # reset when we step back a level
1633 $sublevelCount[$level+1]=0;
1634 $toc .= $sk->tocUnindent( $prevlevel - $level );
1635 $toclevel -= $prevlevel - $level;
1637 # count number of headlines for each level
1638 @$sublevelCount[$level]++;
1639 if( $doNumberHeadings || $doShowToc ) {
1640 $dot = 0;
1641 for( $i = 1; $i <= $level; $i++ ) {
1642 if( !empty( $sublevelCount[$i] ) ) {
1643 if( $dot ) {
1644 $numbering .= ".";
1646 $numbering .= $sublevelCount[$i];
1647 $dot = 1;
1652 # The canonized header is a version of the header text safe to use for links
1653 # Avoid insertion of weird stuff like <math> by expanding the relevant sections
1654 $canonized_headline = $this->unstrip( $headline, $this->mStripState );
1656 # strip out HTML
1657 $canonized_headline = preg_replace( "/<.*?" . ">/","",$canonized_headline );
1658 $tocline = trim( $canonized_headline );
1659 $canonized_headline = preg_replace("/[ \\?&\\/<>\\(\\)\\[\\]=,+']+/", '_', html_entity_decode( $tocline));
1660 $refer[$headlineCount] = $canonized_headline;
1662 # count how many in assoc. array so we can track dupes in anchors
1663 @$refers[$canonized_headline]++;
1664 $refcount[$headlineCount]=$refers[$canonized_headline];
1666 # Prepend the number to the heading text
1668 if( $doNumberHeadings || $doShowToc ) {
1669 $tocline = $numbering . " " . $tocline;
1671 # Don't number the heading if it is the only one (looks silly)
1672 if( $doNumberHeadings && count( $matches[3] ) > 1) {
1673 # the two are different if the line contains a link
1674 $headline=$numbering . " " . $headline;
1678 # Create the anchor for linking from the TOC to the section
1679 $anchor = $canonized_headline;
1680 if($refcount[$headlineCount] > 1 ) {
1681 $anchor .= "_" . $refcount[$headlineCount];
1683 if( $doShowToc ) {
1684 $toc .= $sk->tocLine($anchor,$tocline,$toclevel);
1686 if( $showEditLink ) {
1687 if ( empty( $head[$headlineCount] ) ) {
1688 $head[$headlineCount] = "";
1690 $head[$headlineCount] .= $sk->editSectionLink($headlineCount+1);
1693 # Add the edit section span
1694 if( $rightClickHack ) {
1695 $headline = $sk->editSectionScript($headlineCount+1,$headline);
1698 # give headline the correct <h#> tag
1699 @$head[$headlineCount] .= "<a name=\"$anchor\"></a><h".$level.$matches[2][$headlineCount] .$headline."</h".$level.">";
1701 $headlineCount++;
1704 if( $doShowToc ) {
1705 $toclines = $headlineCount;
1706 $toc .= $sk->tocUnindent( $toclevel );
1707 $toc = $sk->tocTable( $toc );
1710 # split up and insert constructed headlines
1712 $blocks = preg_split( "/<H[1-6].*?" . ">.*?<\/H[1-6]>/i", $text );
1713 $i = 0;
1715 foreach( $blocks as $block ) {
1716 if( $showEditLink && $headlineCount > 0 && $i == 0 && $block != "\n" ) {
1717 # This is the [edit] link that appears for the top block of text when
1718 # section editing is enabled
1720 # Disabled because it broke block formatting
1721 # For example, a bullet point in the top line
1722 # $full .= $sk->editSectionLink(0);
1724 $full .= $block;
1725 if( $doShowToc && !$i) {
1726 # Top anchor now in skin
1727 $full = $full.$toc;
1730 if( !empty( $head[$i] ) ) {
1731 $full .= $head[$i];
1733 $i++;
1736 return $full;
1739 /* private */ function doMagicISBN( &$tokenizer )
1741 global $wgLang;
1743 # Check whether next token is a text token
1744 # If yes, fetch it and convert the text into a
1745 # Special::BookSources link
1746 $token = $tokenizer->previewToken();
1747 while ( $token["type"] == "" )
1749 $tokenizer->nextToken();
1750 $token = $tokenizer->previewToken();
1752 if ( $token["type"] == "text" )
1754 $token = $tokenizer->nextToken();
1755 $x = $token["text"];
1756 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1758 $isbn = $blank = "" ;
1759 while ( " " == $x{0} ) {
1760 $blank .= " ";
1761 $x = substr( $x, 1 );
1763 while ( strstr( $valid, $x{0} ) != false ) {
1764 $isbn .= $x{0};
1765 $x = substr( $x, 1 );
1767 $num = str_replace( "-", "", $isbn );
1768 $num = str_replace( " ", "", $num );
1770 if ( "" == $num ) {
1771 $text = "ISBN $blank$x";
1772 } else {
1773 $titleObj = Title::makeTitle( NS_SPECIAL, "Booksources" );
1774 $text = "<a href=\"" .
1775 $titleObj->escapeLocalUrl( "isbn={$num}" ) .
1776 "\" class=\"internal\">ISBN $isbn</a>";
1777 $text .= $x;
1779 } else {
1780 $text = "ISBN ";
1782 return $text;
1784 /* private */ function doMagicRFC( &$tokenizer )
1786 global $wgLang;
1788 # Check whether next token is a text token
1789 # If yes, fetch it and convert the text into a
1790 # link to an RFC source
1791 $token = $tokenizer->previewToken();
1792 while ( $token["type"] == "" )
1794 $tokenizer->nextToken();
1795 $token = $tokenizer->previewToken();
1797 if ( $token["type"] == "text" )
1799 $token = $tokenizer->nextToken();
1800 $x = $token["text"];
1801 $valid = "0123456789";
1803 $rfc = $blank = "" ;
1804 while ( " " == $x{0} ) {
1805 $blank .= " ";
1806 $x = substr( $x, 1 );
1808 while ( strstr( $valid, $x{0} ) != false ) {
1809 $rfc .= $x{0};
1810 $x = substr( $x, 1 );
1813 if ( "" == $rfc ) {
1814 $text .= "RFC $blank$x";
1815 } else {
1816 $url = wfmsg( "rfcurl" );
1817 $url = str_replace( "$1", $rfc, $url);
1818 $sk =& $this->mOptions->getSkin();
1819 $la = $sk->getExternalLinkAttributes( $url, "RFC {$rfc}" );
1820 $text = "<a href='{$url}'{$la}>RFC {$rfc}</a>{$x}";
1822 } else {
1823 $text = "RFC ";
1825 return $text;
1828 function preSaveTransform( $text, &$title, &$user, $options, $clearState = true )
1830 $this->mOptions = $options;
1831 $this->mTitle =& $title;
1832 $this->mOutputType = OT_WIKI;
1834 if ( $clearState ) {
1835 $this->clearState();
1838 $stripState = false;
1839 $pairs = array(
1840 "\r\n" => "\n",
1842 $text = str_replace(array_keys($pairs), array_values($pairs), $text);
1843 // now with regexes
1844 $pairs = array(
1845 "/<br.+(clear|break)=[\"']?(all|both)[\"']?\\/?>/i" => '<br style="clear:both;"/>',
1846 "/<br *?>/i" => "<br/>",
1848 $text = preg_replace(array_keys($pairs), array_values($pairs), $text);
1849 $text = $this->strip( $text, $stripState, false );
1850 $text = $this->pstPass2( $text, $user );
1851 $text = $this->unstrip( $text, $stripState );
1852 return $text;
1855 /* private */ function pstPass2( $text, &$user )
1857 global $wgLang, $wgLocaltimezone, $wgCurParser;
1859 # Variable replacement
1860 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
1861 $text = $this->replaceVariables( $text );
1863 # Signatures
1865 $n = $user->getName();
1866 $k = $user->getOption( "nickname" );
1867 if ( "" == $k ) { $k = $n; }
1868 if(isset($wgLocaltimezone)) {
1869 $oldtz = getenv("TZ"); putenv("TZ=$wgLocaltimezone");
1871 /* Note: this is an ugly timezone hack for the European wikis */
1872 $d = $wgLang->timeanddate( date( "YmdHis" ), false ) .
1873 " (" . date( "T" ) . ")";
1874 if(isset($wgLocaltimezone)) putenv("TZ=$oldtz");
1876 $text = preg_replace( "/~~~~~/", $d, $text );
1877 $text = preg_replace( "/~~~~/", "[[" . $wgLang->getNsText(
1878 Namespace::getUser() ) . ":$n|$k]] $d", $text );
1879 $text = preg_replace( "/~~~/", "[[" . $wgLang->getNsText(
1880 Namespace::getUser() ) . ":$n|$k]]", $text );
1882 # Context links: [[|name]] and [[name (context)|]]
1884 $tc = "[&;%\\-,.\\(\\)' _0-9A-Za-z\\/:\\x80-\\xff]";
1885 $np = "[&;%\\-,.' _0-9A-Za-z\\/:\\x80-\\xff]"; # No parens
1886 $namespacechar = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
1887 $conpat = "/^({$np}+) \\(({$tc}+)\\)$/";
1889 $p1 = "/\[\[({$np}+) \\(({$np}+)\\)\\|]]/"; # [[page (context)|]]
1890 $p2 = "/\[\[\\|({$tc}+)]]/"; # [[|page]]
1891 $p3 = "/\[\[($namespacechar+):({$np}+)\\|]]/"; # [[namespace:page|]]
1892 $p4 = "/\[\[($namespacechar+):({$np}+) \\(({$np}+)\\)\\|]]/";
1893 # [[ns:page (cont)|]]
1894 $context = "";
1895 $t = $this->mTitle->getText();
1896 if ( preg_match( $conpat, $t, $m ) ) {
1897 $context = $m[2];
1899 $text = preg_replace( $p4, "[[\\1:\\2 (\\3)|\\2]]", $text );
1900 $text = preg_replace( $p1, "[[\\1 (\\2)|\\1]]", $text );
1901 $text = preg_replace( $p3, "[[\\1:\\2|\\2]]", $text );
1903 if ( "" == $context ) {
1904 $text = preg_replace( $p2, "[[\\1]]", $text );
1905 } else {
1906 $text = preg_replace( $p2, "[[\\1 ({$context})|\\1]]", $text );
1910 $mw =& MagicWord::get( MAG_SUBST );
1911 $wgCurParser = $this->fork();
1912 $text = $mw->substituteCallback( $text, "wfBraceSubstitution" );
1913 $this->merge( $wgCurParser );
1916 # Trim trailing whitespace
1917 # MAG_END (__END__) tag allows for trailing
1918 # whitespace to be deliberately included
1919 $text = rtrim( $text );
1920 $mw =& MagicWord::get( MAG_END );
1921 $mw->matchAndRemove( $text );
1923 return $text;
1926 # Set up some variables which are usually set up in parse()
1927 # so that an external function can call some class members with confidence
1928 function startExternalParse( &$title, $options, $outputType, $clearState = true )
1930 $this->mTitle =& $title;
1931 $this->mOptions = $options;
1932 $this->mOutputType = $outputType;
1933 if ( $clearState ) {
1934 $this->clearState();
1938 function transformMsg( $text, $options ) {
1939 global $wgTitle;
1940 static $executing = false;
1942 # Guard against infinite recursion
1943 if ( $executing ) {
1944 return $text;
1946 $executing = true;
1948 $this->mTitle = $wgTitle;
1949 $this->mOptions = $options;
1950 $this->mOutputType = OT_MSG;
1951 $this->clearState();
1952 $text = $this->replaceVariables( $text );
1954 $executing = false;
1955 return $text;
1959 class ParserOutput
1961 var $mText, $mLanguageLinks, $mCategoryLinks, $mContainsOldMagic;
1963 function ParserOutput( $text = "", $languageLinks = array(), $categoryLinks = array(),
1964 $containsOldMagic = false )
1966 $this->mText = $text;
1967 $this->mLanguageLinks = $languageLinks;
1968 $this->mCategoryLinks = $categoryLinks;
1969 $this->mContainsOldMagic = $containsOldMagic;
1972 function getText() { return $this->mText; }
1973 function getLanguageLinks() { return $this->mLanguageLinks; }
1974 function getCategoryLinks() { return $this->mCategoryLinks; }
1975 function containsOldMagic() { return $this->mContainsOldMagic; }
1976 function setText( $text ) { return wfSetVar( $this->mText, $text ); }
1977 function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); }
1978 function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategoryLinks, $cl ); }
1979 function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
1981 function merge( $other ) {
1982 $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $other->mLanguageLinks );
1983 $this->mCategoryLinks = array_merge( $this->mCategoryLinks, $this->mLanguageLinks );
1984 $this->mContainsOldMagic = $this->mContainsOldMagic || $other->mContainsOldMagic;
1989 class ParserOptions
1991 # All variables are private
1992 var $mUseTeX; # Use texvc to expand <math> tags
1993 var $mUseCategoryMagic; # Treat [[Category:xxxx]] tags specially
1994 var $mUseDynamicDates; # Use $wgDateFormatter to format dates
1995 var $mInterwikiMagic; # Interlanguage links are removed and returned in an array
1996 var $mAllowExternalImages; # Allow external images inline
1997 var $mSkin; # Reference to the preferred skin
1998 var $mDateFormat; # Date format index
1999 var $mEditSection; # Create "edit section" links
2000 var $mEditSectionOnRightClick; # Generate JavaScript to edit section on right click
2001 var $mNumberHeadings; # Automatically number headings
2002 var $mShowToc; # Show table of contents
2004 function getUseTeX() { return $this->mUseTeX; }
2005 function getUseCategoryMagic() { return $this->mUseCategoryMagic; }
2006 function getUseDynamicDates() { return $this->mUseDynamicDates; }
2007 function getInterwikiMagic() { return $this->mInterwikiMagic; }
2008 function getAllowExternalImages() { return $this->mAllowExternalImages; }
2009 function getSkin() { return $this->mSkin; }
2010 function getDateFormat() { return $this->mDateFormat; }
2011 function getEditSection() { return $this->mEditSection; }
2012 function getEditSectionOnRightClick() { return $this->mEditSectionOnRightClick; }
2013 function getNumberHeadings() { return $this->mNumberHeadings; }
2014 function getShowToc() { return $this->mShowToc; }
2016 function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); }
2017 function setUseCategoryMagic( $x ) { return wfSetVar( $this->mUseCategoryMagic, $x ); }
2018 function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
2019 function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
2020 function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); }
2021 function setSkin( $x ) { return wfSetRef( $this->mSkin, $x ); }
2022 function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); }
2023 function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); }
2024 function setEditSectionOnRightClick( $x ) { return wfSetVar( $this->mEditSectionOnRightClick, $x ); }
2025 function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); }
2026 function setShowToc( $x ) { return wfSetVar( $this->mShowToc, $x ); }
2028 /* static */ function newFromUser( &$user )
2030 $popts = new ParserOptions;
2031 $popts->initialiseFromUser( $user );
2032 return $popts;
2035 function initialiseFromUser( &$userInput )
2037 global $wgUseTeX, $wgUseCategoryMagic, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
2039 if ( !$userInput ) {
2040 $user = new User;
2041 $user->setLoaded( true );
2042 } else {
2043 $user =& $userInput;
2046 $this->mUseTeX = $wgUseTeX;
2047 $this->mUseCategoryMagic = $wgUseCategoryMagic;
2048 $this->mUseDynamicDates = $wgUseDynamicDates;
2049 $this->mInterwikiMagic = $wgInterwikiMagic;
2050 $this->mAllowExternalImages = $wgAllowExternalImages;
2051 $this->mSkin =& $user->getSkin();
2052 $this->mDateFormat = $user->getOption( "date" );
2053 $this->mEditSection = $user->getOption( "editsection" );
2054 $this->mEditSectionOnRightClick = $user->getOption( "editsectiononrightclick" );
2055 $this->mNumberHeadings = $user->getOption( "numberheadings" );
2056 $this->mShowToc = $user->getOption( "showtoc" );
2062 # Regex callbacks, used in Parser::replaceVariables
2063 function wfBraceSubstitution( $matches )
2065 global $wgCurParser;
2066 return $wgCurParser->braceSubstitution( $matches );