quick hack to protect css/js subpages of userpages. Should be replaced by some more...
[mediawiki.git] / includes / Parser.php
blob942621c1eb22443348c47e7e8b848ae61b4993b3
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 # string parameter for extractTags which will cause it
48 # to strip HTML comments in addition to regular
49 # <XML>-style tags. This should not be anything we
50 # may want to use in wikisyntax
51 define( "STRIP_COMMENTS", "HTMLCommentStrip" );
53 # prefix for escaping, used in two functions at least
54 define( "UNIQ_PREFIX", "NaodW29");
56 class Parser
58 # Cleared with clearState():
59 var $mOutput, $mAutonumber, $mDTopen, $mStripState = array();
60 var $mVariables, $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
62 # Temporary:
63 var $mOptions, $mTitle, $mOutputType;
65 function Parser()
67 $this->clearState();
70 function clearState()
72 $this->mOutput = new ParserOutput;
73 $this->mAutonumber = 0;
74 $this->mLastSection = "";
75 $this->mDTopen = false;
76 $this->mVariables = false;
77 $this->mIncludeCount = array();
78 $this->mStripState = array();
79 $this->mArgStack = array();
82 # First pass--just handle <nowiki> sections, pass the rest off
83 # to internalParse() which does all the real work.
85 # Returns a ParserOutput
87 function parse( $text, &$title, $options, $linestart = true, $clearState = true )
89 $fname = "Parser::parse";
90 wfProfileIn( $fname );
92 if ( $clearState ) {
93 $this->clearState();
96 $this->mOptions = $options;
97 $this->mTitle =& $title;
98 $this->mOutputType = OT_HTML;
100 $stripState = NULL;
101 $text = $this->strip( $text, $this->mStripState );
102 $text = $this->internalParse( $text, $linestart );
103 $text = $this->unstrip( $text, $this->mStripState );
104 # Clean up special characters, only run once, next-to-last before doBlockLevels
105 $fixtags = array(
106 "/<hr *>/i" => '<hr/>',
107 "/<br *>/i" => '<br/>',
108 "/<center *>/i"=>'<div class="center">',
109 "/<\\/center *>/i" => '</div>',
110 # Clean up spare ampersands; note that we probably ought to be
111 # more careful about named entities.
112 '/&(?!:amp;|#[Xx][0-9A-fa-f]+;|#[0-9]+;|[a-zA-Z0-9]+;)/' => '&amp;'
114 $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text );
116 # only once and last
117 $text = $this->doBlockLevels( $text, $linestart );
119 $this->mOutput->setText( $text );
120 wfProfileOut( $fname );
121 return $this->mOutput;
124 /* static */ function getRandomString()
126 return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff));
129 # Replaces all occurrences of <$tag>content</$tag> in the text
130 # with a random marker and returns the new text. the output parameter
131 # $content will be an associative array filled with data on the form
132 # $unique_marker => content.
134 # If $content is already set, the additional entries will be appended
136 # If $tag is set to STRIP_COMMENTS, the function will extract
137 # <!-- HTML comments -->
139 /* static */ function extractTags($tag, $text, &$content, $uniq_prefix = ""){
140 $rnd = $uniq_prefix . '-' . $tag . Parser::getRandomString();
141 if ( !$content ) {
142 $content = array( );
144 $n = 1;
145 $stripped = "";
147 while ( "" != $text ) {
148 if($tag==STRIP_COMMENTS) {
149 $p = preg_split( "/<!--/i", $text, 2 );
150 } else {
151 $p = preg_split( "/<\\s*$tag\\s*>/i", $text, 2 );
153 $stripped .= $p[0];
154 if ( ( count( $p ) < 2 ) || ( "" == $p[1] ) ) {
155 $text = "";
156 } else {
157 if($tag==STRIP_COMMENTS) {
158 $q = preg_split( "/-->/i", $p[1], 2 );
159 } else {
160 $q = preg_split( "/<\\/\\s*$tag\\s*>/i", $p[1], 2 );
162 $marker = $rnd . sprintf("%08X", $n++);
163 $content[$marker] = $q[0];
164 $stripped .= $marker;
165 $text = $q[1];
168 return $stripped;
171 # Strips and renders <nowiki>, <pre>, <math>, <hiero>
172 # If $render is set, performs necessary rendering operations on plugins
173 # Returns the text, and fills an array with data needed in unstrip()
174 # If the $state is already a valid strip state, it adds to the state
176 # When $stripcomments is set, HTML comments <!-- like this -->
177 # will be stripped in addition to other tags. This is important
178 # for section editing, where these comments cause confusion when
179 # counting the sections in the wikisource
180 function strip( $text, &$state, $stripcomments = false )
182 $render = ($this->mOutputType == OT_HTML);
183 $nowiki_content = array();
184 $hiero_content = array();
185 $math_content = array();
186 $pre_content = array();
187 $comment_content = array();
189 # Replace any instances of the placeholders
190 $uniq_prefix = UNIQ_PREFIX;
191 #$text = str_replace( $uniq_prefix, wfHtmlEscapeFirst( $uniq_prefix ), $text );
193 $text = Parser::extractTags("nowiki", $text, $nowiki_content, $uniq_prefix);
194 foreach( $nowiki_content as $marker => $content ){
195 if( $render ){
196 $nowiki_content[$marker] = wfEscapeHTMLTagsOnly( $content );
197 } else {
198 $nowiki_content[$marker] = "<nowiki>$content</nowiki>";
202 $text = Parser::extractTags("hiero", $text, $hiero_content, $uniq_prefix);
203 foreach( $hiero_content as $marker => $content ){
204 if( $render && $GLOBALS['wgUseWikiHiero']){
205 $hiero_content[$marker] = WikiHiero( $content, WH_MODE_HTML);
206 } else {
207 $hiero_content[$marker] = "<hiero>$content</hiero>";
211 $text = Parser::extractTags("math", $text, $math_content, $uniq_prefix);
212 foreach( $math_content as $marker => $content ){
213 if( $render && $this->mOptions->getUseTeX() ){
214 $math_content[$marker] = renderMath( $content );
215 } else {
216 $math_content[$marker] = "<math>$content</math>";
220 $text = Parser::extractTags("pre", $text, $pre_content, $uniq_prefix);
221 foreach( $pre_content as $marker => $content ){
222 if( $render ){
223 $pre_content[$marker] = "<pre>" . wfEscapeHTMLTagsOnly( $content ) . "</pre>";
224 } else {
225 $pre_content[$marker] = "<pre>$content</pre>";
228 if($stripcomments) {
229 $text = Parser::extractTags(STRIP_COMMENTS, $text, $comment_content, $uniq_prefix);
230 foreach( $comment_content as $marker => $content ){
231 $comment_content[$marker] = "<!--$content-->";
235 # Merge state with the pre-existing state, if there is one
236 if ( $state ) {
237 $state['nowiki'] = $state['nowiki'] + $nowiki_content;
238 $state['hiero'] = $state['hiero'] + $hiero_content;
239 $state['math'] = $state['math'] + $math_content;
240 $state['pre'] = $state['pre'] + $pre_content;
241 $state['comment'] = $state['comment'] + $comment_content;
242 } else {
243 $state = array(
244 'nowiki' => $nowiki_content,
245 'hiero' => $hiero_content,
246 'math' => $math_content,
247 'pre' => $pre_content,
248 'comment' => $comment_content
251 return $text;
254 function unstrip( $text, &$state )
256 # Must expand in reverse order, otherwise nested tags will be corrupted
257 $contentDict = end( $state );
258 for ( $contentDict = end( $state ); $contentDict !== false; $contentDict = prev( $state ) ) {
259 for ( $content = end( $contentDict ); $content !== false; $content = prev( $contentDict ) ) {
260 $text = str_replace( key( $contentDict ), $content, $text );
264 return $text;
267 # Add an item to the strip state
268 # Returns the unique tag which must be inserted into the stripped text
269 # The tag will be replaced with the original text in unstrip()
271 function insertStripItem( $text, &$state )
273 $rnd = UNIQ_PREFIX . '-item' . Parser::getRandomString();
274 if ( !$state ) {
275 $state = array(
276 'nowiki' => array(),
277 'hiero' => array(),
278 'math' => array(),
279 'pre' => array()
282 $state['item'][$rnd] = $text;
283 return $rnd;
286 # This method generates the list of subcategories and pages for a category
287 function categoryMagic ()
289 global $wgLang , $wgUser ;
290 if ( !$this->mOptions->getUseCategoryMagic() ) return ; # Doesn't use categories at all
292 $cns = Namespace::getCategory() ;
293 if ( $this->mTitle->getNamespace() != $cns ) return "" ; # This ain't a category page
295 $r = "<br style=\"clear:both;\"/>\n";
298 $sk =& $wgUser->getSkin() ;
300 $articles = array() ;
301 $children = array() ;
302 $data = array () ;
303 $id = $this->mTitle->getArticleID() ;
305 # For existing categories
306 if( $id ) {
307 $sql = "SELECT DISTINCT cur_title,cur_namespace FROM cur,links WHERE l_to={$id} AND l_from=cur_id";
308 $res = wfQuery ( $sql, DB_READ ) ;
309 while ( $x = wfFetchObject ( $res ) ) $data[] = $x ;
310 } else {
311 # For non-existing categories
312 $t = wfStrencode( $this->mTitle->getPrefixedDBKey() );
313 $sql = "SELECT DISTINCT cur_title,cur_namespace FROM cur,brokenlinks WHERE bl_to='$t' AND bl_from=cur_id" ;
314 $res = wfQuery ( $sql, DB_READ ) ;
315 while ( $x = wfFetchObject ( $res ) ) $data[] = $x ;
318 # For all pages that link to this category
319 foreach ( $data AS $x )
321 $t = $wgLang->getNsText ( $x->cur_namespace ) ;
322 if ( $t != "" ) $t .= ":" ;
323 $t .= $x->cur_title ;
325 if ( $x->cur_namespace == $cns ) {
326 array_push ( $children , $sk->makeLink ( $t ) ) ; # Subcategory
327 } else {
328 array_push ( $articles , $sk->makeLink ( $t ) ) ; # Page in this category
331 wfFreeResult ( $res ) ;
333 # Showing subcategories
334 if ( count ( $children ) > 0 )
336 asort ( $children ) ;
337 $r .= "<h2>".wfMsg("subcategories")."</h2>\n" ;
338 $r .= implode ( ", " , $children ) ;
341 # Showing pages in this category
342 if ( count ( $articles ) > 0 )
344 $ti = $this->mTitle->getText() ;
345 asort ( $articles ) ;
346 $h = wfMsg( "category_header", $ti );
347 $r .= "<h2>{$h}</h2>\n" ;
348 $r .= implode ( ", " , $articles ) ;
352 return $r ;
355 function getHTMLattrs ()
357 $htmlattrs = array( # Allowed attributes--no scripting, etc.
358 "title", "align", "lang", "dir", "width", "height",
359 "bgcolor", "clear", /* BR */ "noshade", /* HR */
360 "cite", /* BLOCKQUOTE, Q */ "size", "face", "color",
361 /* FONT */ "type", "start", "value", "compact",
362 /* For various lists, mostly deprecated but safe */
363 "summary", "width", "border", "frame", "rules",
364 "cellspacing", "cellpadding", "valign", "char",
365 "charoff", "colgroup", "col", "span", "abbr", "axis",
366 "headers", "scope", "rowspan", "colspan", /* Tables */
367 "id", "class", "name", "style" /* For CSS */
369 return $htmlattrs ;
372 function fixTagAttributes ( $t )
374 if ( trim ( $t ) == "" ) return "" ; # Saves runtime ;-)
375 $htmlattrs = $this->getHTMLattrs() ;
377 # Strip non-approved attributes from the tag
378 $t = preg_replace(
379 "/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e",
380 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
381 $t);
382 # Strip javascript "expression" from stylesheets. Brute force approach:
383 # If anythin offensive is found, all attributes of the HTML tag are dropped
385 if( preg_match(
386 "/style\\s*=.*(expression|tps*:\/\/|url\\s*\().*/is",
387 wfMungeToUtf8( $t ) ) )
389 $t="";
392 return trim ( $t ) ;
395 function doTableStuff ( $t )
397 $t = explode ( "\n" , $t ) ;
398 $td = array () ; # Is currently a td tag open?
399 $ltd = array () ; # Was it TD or TH?
400 $tr = array () ; # Is currently a tr tag open?
401 $ltr = array () ; # tr attributes
402 foreach ( $t AS $k => $x )
404 $x = trim ( $x ) ;
405 $fc = substr ( $x , 0 , 1 ) ;
406 if ( "{|" == substr ( $x , 0 , 2 ) )
408 $t[$k] = "\n<table " . $this->fixTagAttributes ( substr ( $x , 3 ) ) . ">" ;
409 array_push ( $td , false ) ;
410 array_push ( $ltd , "" ) ;
411 array_push ( $tr , false ) ;
412 array_push ( $ltr , "" ) ;
414 else if ( count ( $td ) == 0 ) { } # Don't do any of the following
415 else if ( "|}" == substr ( $x , 0 , 2 ) )
417 $z = "</table>\n" ;
418 $l = array_pop ( $ltd ) ;
419 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
420 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
421 array_pop ( $ltr ) ;
422 $t[$k] = $z ;
424 /* else if ( "|_" == substr ( $x , 0 , 2 ) ) # Caption
426 $z = trim ( substr ( $x , 2 ) ) ;
427 $t[$k] = "<caption>{$z}</caption>\n" ;
429 else if ( "|-" == substr ( $x , 0 , 2 ) ) # Allows for |---------------
431 $x = substr ( $x , 1 ) ;
432 while ( $x != "" && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ;
433 $z = "" ;
434 $l = array_pop ( $ltd ) ;
435 if ( array_pop ( $tr ) ) $z = "</tr>" . $z ;
436 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
437 array_pop ( $ltr ) ;
438 $t[$k] = $z ;
439 array_push ( $tr , false ) ;
440 array_push ( $td , false ) ;
441 array_push ( $ltd , "" ) ;
442 array_push ( $ltr , $this->fixTagAttributes ( $x ) ) ;
444 else if ( "|" == $fc || "!" == $fc || "|+" == substr ( $x , 0 , 2 ) ) # Caption
446 if ( "|+" == substr ( $x , 0 , 2 ) )
448 $fc = "+" ;
449 $x = substr ( $x , 1 ) ;
451 $after = substr ( $x , 1 ) ;
452 if ( $fc == "!" ) $after = str_replace ( "!!" , "||" , $after ) ;
453 $after = explode ( "||" , $after ) ;
454 $t[$k] = "" ;
455 foreach ( $after AS $theline )
457 $z = "" ;
458 if ( $fc != "+" )
460 $tra = array_pop ( $ltr ) ;
461 if ( !array_pop ( $tr ) ) $z = "<tr {$tra}>\n" ;
462 array_push ( $tr , true ) ;
463 array_push ( $ltr , "" ) ;
466 $l = array_pop ( $ltd ) ;
467 if ( array_pop ( $td ) ) $z = "</{$l}>" . $z ;
468 if ( $fc == "|" ) $l = "td" ;
469 else if ( $fc == "!" ) $l = "th" ;
470 else if ( $fc == "+" ) $l = "caption" ;
471 else $l = "" ;
472 array_push ( $ltd , $l ) ;
473 $y = explode ( "|" , $theline , 2 ) ;
474 if ( count ( $y ) == 1 ) $y = "{$z}<{$l}>{$y[0]}" ;
475 else $y = $y = "{$z}<{$l} ".$this->fixTagAttributes($y[0]).">{$y[1]}" ;
476 $t[$k] .= $y ;
477 array_push ( $td , true ) ;
482 # Closing open td, tr && table
483 while ( count ( $td ) > 0 )
485 if ( array_pop ( $td ) ) $t[] = "</td>" ;
486 if ( array_pop ( $tr ) ) $t[] = "</tr>" ;
487 $t[] = "</table>" ;
490 $t = implode ( "\n" , $t ) ;
491 # $t = $this->removeHTMLtags( $t );
492 return $t ;
495 function internalParse( $text, $linestart, $args = array() )
497 $fname = "Parser::internalParse";
498 wfProfileIn( $fname );
500 $text = $this->removeHTMLtags( $text );
501 $text = $this->replaceVariables( $text, $args );
503 # $text = preg_replace( "/(^|\n)-----*/", "\\1<hr>", $text );
505 $text = $this->doHeadings( $text );
506 if($this->mOptions->getUseDynamicDates()) {
507 global $wgDateFormatter;
508 $text = $wgDateFormatter->reformat( $this->mOptions->getDateFormat(), $text );
510 $text = $this->replaceExternalLinks( $text );
511 $text = $this->doTokenizedParser ( $text );
512 $text = $this->doTableStuff ( $text ) ;
513 $text = $this->formatHeadings( $text );
514 $sk =& $this->mOptions->getSkin();
515 $text = $sk->transformContent( $text );
517 if ( !isset ( $this->categoryMagicDone ) ) {
518 $text .= $this->categoryMagic () ;
519 $this->categoryMagicDone = true ;
522 wfProfileOut( $fname );
523 return $text;
527 /* private */ function doHeadings( $text )
529 for ( $i = 6; $i >= 1; --$i ) {
530 $h = substr( "======", 0, $i );
531 $text = preg_replace( "/^{$h}(.+){$h}(\\s|$)/m",
532 "<h{$i}>\\1</h{$i}>\\2", $text );
534 return $text;
537 # Note: we have to do external links before the internal ones,
538 # and otherwise take great care in the order of things here, so
539 # that we don't end up interpreting some URLs twice.
541 /* private */ function replaceExternalLinks( $text )
543 $fname = "Parser::replaceExternalLinks";
544 wfProfileIn( $fname );
545 $text = $this->subReplaceExternalLinks( $text, "http", true );
546 $text = $this->subReplaceExternalLinks( $text, "https", true );
547 $text = $this->subReplaceExternalLinks( $text, "ftp", false );
548 $text = $this->subReplaceExternalLinks( $text, "irc", false );
549 $text = $this->subReplaceExternalLinks( $text, "gopher", false );
550 $text = $this->subReplaceExternalLinks( $text, "news", false );
551 $text = $this->subReplaceExternalLinks( $text, "mailto", false );
552 wfProfileOut( $fname );
553 return $text;
556 /* private */ function subReplaceExternalLinks( $s, $protocol, $autonumber )
558 $unique = "4jzAfzB8hNvf4sqyO9Edd8pSmk9rE2in0Tgw3";
559 $uc = "A-Za-z0-9_\\/~%\\-+&*#?!=()@\\x80-\\xFF";
561 # this is the list of separators that should be ignored if they
562 # are the last character of an URL but that should be included
563 # if they occur within the URL, e.g. "go to www.foo.com, where .."
564 # in this case, the last comma should not become part of the URL,
565 # but in "www.foo.com/123,2342,32.htm" it should.
566 $sep = ",;\.:";
567 $fnc = "A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF";
568 $images = "gif|png|jpg|jpeg";
570 # PLEASE NOTE: The curly braces { } are not part of the regex,
571 # they are interpreted as part of the string (used to tell PHP
572 # that the content of the string should be inserted there).
573 $e1 = "/(^|[^\\[])({$protocol}:)([{$uc}{$sep}]+)\\/([{$fnc}]+)\\." .
574 "((?i){$images})([^{$uc}]|$)/";
576 $e2 = "/(^|[^\\[])({$protocol}:)(([".$uc."]|[".$sep."][".$uc."])+)([^". $uc . $sep. "]|[".$sep."]|$)/";
577 $sk =& $this->mOptions->getSkin();
579 if ( $autonumber and $this->mOptions->getAllowExternalImages() ) { # Use img tags only for HTTP urls
580 $s = preg_replace( $e1, "\\1" . $sk->makeImage( "{$unique}:\\3" .
581 "/\\4.\\5", "\\4.\\5" ) . "\\6", $s );
583 $s = preg_replace( $e2, "\\1" . "<a href=\"{$unique}:\\3\"" .
584 $sk->getExternalLinkAttributes( "{$unique}:\\3", wfEscapeHTML(
585 "{$unique}:\\3" ) ) . ">" . wfEscapeHTML( "{$unique}:\\3" ) .
586 "</a>\\5", $s );
587 $s = str_replace( $unique, $protocol, $s );
589 $a = explode( "[{$protocol}:", " " . $s );
590 $s = array_shift( $a );
591 $s = substr( $s, 1 );
593 $e1 = "/^([{$uc}"."{$sep}]+)](.*)\$/sD";
594 $e2 = "/^([{$uc}"."{$sep}]+)\\s+([^\\]]+)](.*)\$/sD";
596 foreach ( $a as $line ) {
597 if ( preg_match( $e1, $line, $m ) ) {
598 $link = "{$protocol}:{$m[1]}";
599 $trail = $m[2];
600 if ( $autonumber ) { $text = "[" . ++$this->mAutonumber . "]"; }
601 else { $text = wfEscapeHTML( $link ); }
602 } else if ( preg_match( $e2, $line, $m ) ) {
603 $link = "{$protocol}:{$m[1]}";
604 $text = $m[2];
605 $trail = $m[3];
606 } else {
607 $s .= "[{$protocol}:" . $line;
608 continue;
610 if( $link == $text || preg_match( "!$protocol://" . preg_quote( $text, "/" ) . "/?$!", $link ) ) {
611 $paren = "";
612 } else {
613 # Expand the URL for printable version
614 $paren = "<span class='urlexpansion'> (<i>" . htmlspecialchars ( $link ) . "</i>)</span>";
616 $la = $sk->getExternalLinkAttributes( $link, $text );
617 $s .= "<a href='{$link}'{$la}>{$text}</a>{$paren}{$trail}";
620 return $s;
623 /* private */ function handle3Quotes( &$state, $token )
625 if ( $state["strong"] !== false ) {
626 if ( $state["em"] !== false && $state["em"] > $state["strong"] )
628 # ''' lala ''lala '''
629 $s = "</em></strong><em>";
630 } else {
631 $s = "</strong>";
633 $state["strong"] = FALSE;
634 } else {
635 $s = "<strong>";
636 $state["strong"] = isset($token["pos"]) ? $token["pos"] : true;
638 return $s;
641 /* private */ function handle2Quotes( &$state, $token )
643 if ( $state["em"] !== false ) {
644 if ( $state["strong"] !== false && $state["strong"] > $state["em"] )
646 # ''lala'''lala'' ....'''
647 $s = "</strong></em><strong>";
648 } else {
649 $s = "</em>";
651 $state["em"] = FALSE;
652 } else {
653 $s = "<em>";
654 $state["em"] = isset($token["pos"]) ? $token["pos"] : true;
657 return $s;
660 /* private */ function handle5Quotes( &$state, $token )
662 $s = "";
663 if ( $state["em"] !== false && $state["strong"] !== false ) {
664 if ( $state["em"] < $state["strong"] ) {
665 $s .= "</strong></em>";
666 } else {
667 $s .= "</em></strong>";
669 $state["strong"] = $state["em"] = FALSE;
670 } elseif ( $state["em"] !== false ) {
671 $s .= "</em><strong>";
672 $state["em"] = FALSE;
673 $state["strong"] = $token["pos"];
674 } elseif ( $state["strong"] !== false ) {
675 $s .= "</strong><em>";
676 $state["strong"] = FALSE;
677 $state["em"] = $token["pos"];
678 } else { # not $em and not $strong
679 $s .= "<strong><em>";
680 $state["strong"] = $state["em"] = isset($token["pos"]) ? $token["pos"] : true;
682 return $s;
685 /* private */ function doTokenizedParser( $str )
687 global $wgLang; # for language specific parser hook
688 global $wgUploadDirectory, $wgUseTimeline;
690 $tokenizer=Tokenizer::newFromString( $str );
691 $tokenStack = array();
693 $s="";
694 $state["em"] = FALSE;
695 $state["strong"] = FALSE;
696 $tagIsOpen = FALSE;
697 $threeopen = false;
699 # The tokenizer splits the text into tokens and returns them one by one.
700 # Every call to the tokenizer returns a new token.
701 while ( $token = $tokenizer->nextToken() )
703 switch ( $token["type"] )
705 case "text":
706 # simple text with no further markup
707 $txt = $token["text"];
708 break;
709 case "blank":
710 # Text that contains blanks that have to be converted to
711 # non-breakable spaces for French.
712 # U+202F NARROW NO-BREAK SPACE might be a better choice, but
713 # browser support for Unicode spacing is poor.
714 $txt = str_replace( " ", "&nbsp;", $token["text"] );
715 break;
716 case "[[[":
717 # remember the tag opened with 3 [
718 $threeopen = true;
719 case "[[":
720 # link opening tag.
721 # FIXME : Treat orphaned open tags (stack not empty when text is over)
722 $tagIsOpen = TRUE;
723 array_push( $tokenStack, $token );
724 $txt="";
725 break;
727 case "]]]":
728 case "]]":
729 # link close tag.
730 # get text from stack, glue it together, and call the code to handle a
731 # link
733 if ( count( $tokenStack ) == 0 )
735 # stack empty. Found a ]] without an opening [[
736 $txt = "]]";
737 } else {
738 $linkText = "";
739 $lastToken = array_pop( $tokenStack );
740 while ( !(($lastToken["type"] == "[[[") or ($lastToken["type"] == "[[")) )
742 if( !empty( $lastToken["text"] ) ) {
743 $linkText = $lastToken["text"] . $linkText;
745 $lastToken = array_pop( $tokenStack );
748 $txt = $linkText ."]]";
750 if( isset( $lastToken["text"] ) ) {
751 $prefix = $lastToken["text"];
752 } else {
753 $prefix = "";
755 $nextToken = $tokenizer->previewToken();
756 if ( $nextToken["type"] == "text" )
758 # Preview just looks at it. Now we have to fetch it.
759 $nextToken = $tokenizer->nextToken();
760 $txt .= $nextToken["text"];
762 $txt = $this->handleInternalLink( $this->unstrip($txt,$this->mStripState), $prefix );
764 # did the tag start with 3 [ ?
765 if($threeopen) {
766 # show the first as text
767 $txt = "[".$txt;
768 $threeopen=false;
772 $tagIsOpen = (count( $tokenStack ) != 0);
773 break;
774 case "----":
775 $txt = "\n<hr />\n";
776 break;
777 case "'''":
778 # This and the three next ones handle quotes
779 $txt = $this->handle3Quotes( $state, $token );
780 break;
781 case "''":
782 $txt = $this->handle2Quotes( $state, $token );
783 break;
784 case "'''''":
785 $txt = $this->handle5Quotes( $state, $token );
786 break;
787 case "":
788 # empty token
789 $txt="";
790 break;
791 case "RFC ":
792 if ( $tagIsOpen ) {
793 $txt = "RFC ";
794 } else {
795 $txt = $this->doMagicRFC( $tokenizer );
797 break;
798 case "ISBN ":
799 if ( $tagIsOpen ) {
800 $txt = "ISBN ";
801 } else {
802 $txt = $this->doMagicISBN( $tokenizer );
804 break;
805 case "<timeline>":
806 if ( $wgUseTimeline &&
807 "" != ( $timelinesrc = $tokenizer->readAllUntil("&lt;/timeline&gt;") ) )
809 $txt = renderTimeline( $timelinesrc );
810 } else {
811 $txt=$token["text"];
813 break;
814 default:
815 # Call language specific Hook.
816 $txt = $wgLang->processToken( $token, $tokenStack );
817 if ( NULL == $txt ) {
818 # An unkown token. Highlight.
819 $txt = "<font color=\"#FF0000\"><b>".$token["type"]."</b></font>";
820 $txt .= "<font color=\"#FFFF00\"><b>".$token["text"]."</b></font>";
822 break;
824 # If we're parsing the interior of a link, don't append the interior to $s,
825 # but push it to the stack so it can be processed when a ]] token is found.
826 if ( $tagIsOpen && $txt != "" ) {
827 $token["type"] = "text";
828 $token["text"] = $txt;
829 array_push( $tokenStack, $token );
830 } else {
831 $s .= $txt;
833 } #end while
834 if ( count( $tokenStack ) != 0 )
836 # still objects on stack. opened [[ tag without closing ]] tag.
837 $txt = "";
838 while ( $lastToken = array_pop( $tokenStack ) )
840 if ( $lastToken["type"] == "text" )
842 $txt = $lastToken["text"] . $txt;
843 } else {
844 $txt = $lastToken["type"] . $txt;
847 $s .= $txt;
849 return $s;
852 /* private */ function handleInternalLink( $line, $prefix )
854 global $wgLang, $wgLinkCache;
855 global $wgNamespacesWithSubpages, $wgLanguageCode;
856 static $fname = "Parser::handleInternalLink" ;
857 wfProfileIn( $fname );
859 wfProfileIn( "$fname-setup" );
860 static $tc = FALSE;
861 if ( !$tc ) { $tc = Title::legalChars() . "#"; }
862 $sk =& $this->mOptions->getSkin();
864 # Match a link having the form [[namespace:link|alternate]]trail
865 static $e1 = FALSE;
866 if ( !$e1 ) { $e1 = "/^([{$tc}]+)(?:\\|([^]]+))?]](.*)\$/sD"; }
867 # Match the end of a line for a word that's not followed by whitespace,
868 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
869 #$e2 = "/^(.*)\\b(\\w+)\$/suD";
870 #$e2 = "/^(.*\\s)(\\S+)\$/suD";
871 static $e2 = '/^(.*\s)([a-zA-Z\x80-\xff]+)$/sD';
874 # Special and Media are pseudo-namespaces; no pages actually exist in them
875 static $image = FALSE;
876 static $special = FALSE;
877 static $media = FALSE;
878 static $category = FALSE;
879 if ( !$image ) { $image = Namespace::getImage(); }
880 if ( !$special ) { $special = Namespace::getSpecial(); }
881 if ( !$media ) { $media = Namespace::getMedia(); }
882 if ( !$category ) { $category = Namespace::getCategory(); ; }
884 $nottalk = !Namespace::isTalk( $this->mTitle->getNamespace() );
886 wfProfileOut( "$fname-setup" );
887 $s = "";
889 if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
890 $text = $m[2];
891 $trail = $m[3];
892 } else { # Invalid form; output directly
893 $s .= $prefix . "[[" . $line ;
894 return $s;
897 /* Valid link forms:
898 Foobar -- normal
899 :Foobar -- override special treatment of prefix (images, language links)
900 /Foobar -- convert to CurrentPage/Foobar
901 /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
903 $c = substr($m[1],0,1);
904 $noforce = ($c != ":");
905 if( $c == "/" ) { # subpage
906 if(substr($m[1],-1,1)=="/") { # / at end means we don't want the slash to be shown
907 $m[1]=substr($m[1],1,strlen($m[1])-2);
908 $noslash=$m[1];
909 } else {
910 $noslash=substr($m[1],1);
912 if($wgNamespacesWithSubpages[$this->mTitle->getNamespace()]) { # subpages allowed here
913 $link = $this->mTitle->getPrefixedText(). "/" . trim($noslash);
914 if( "" == $text ) {
915 $text= $m[1];
916 } # this might be changed for ugliness reasons
917 } else {
918 $link = $noslash; # no subpage allowed, use standard link
920 } elseif( $noforce ) { # no subpage
921 $link = $m[1];
922 } else {
923 $link = substr( $m[1], 1 );
925 if( "" == $text )
926 $text = $link;
928 $nt = Title::newFromText( $link );
929 if( !$nt ) {
930 $s .= $prefix . "[[" . $line;
931 return $s;
933 $ns = $nt->getNamespace();
934 $iw = $nt->getInterWiki();
935 if( $noforce ) {
936 if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgLang->getLanguageName( $iw ) ) {
937 array_push( $this->mOutput->mLanguageLinks, $nt->getPrefixedText() );
938 return (trim($s) == '')? '': $s;
940 if( $ns == $image ) {
941 $s .= $prefix . $sk->makeImageLinkObj( $nt, $text ) . $trail;
942 $wgLinkCache->addImageLinkObj( $nt );
943 return $s;
945 if ( $ns == $category ) {
946 $t = $nt->getText() ;
947 $nnt = Title::newFromText ( Namespace::getCanonicalName($category).":".$t ) ;
948 $t = $sk->makeLinkObj( $nnt, $t, "", "" , $prefix );
949 $this->mOutput->mCategoryLinks[] = $t ;
950 $s .= $prefix . $trail ;
951 return $s ;
954 if( ( $nt->getPrefixedText() == $this->mTitle->getPrefixedText() ) &&
955 ( strpos( $link, "#" ) == FALSE ) ) {
956 # Self-links are handled specially; generally de-link and change to bold.
957 $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, "", $trail );
958 return $s;
961 if( $ns == $media ) {
962 $s .= $prefix . $sk->makeMediaLinkObj( $nt, $text ) . $trail;
963 $wgLinkCache->addImageLinkObj( $nt );
964 return $s;
965 } elseif( $ns == $special ) {
966 $s .= $prefix . $sk->makeKnownLinkObj( $nt, $text, "", $trail );
967 return $s;
969 $s .= $sk->makeLinkObj( $nt, $text, "", $trail , $prefix );
971 wfProfileOut( $fname );
972 return $s;
975 # Some functions here used by doBlockLevels()
977 /* private */ function closeParagraph()
979 $result = "";
980 if ( '' != $this->mLastSection ) {
981 $result = "</" . $this->mLastSection . ">\n";
983 $this->mInPre = false;
984 $this->mLastSection = "";
985 return $result;
987 # getCommon() returns the length of the longest common substring
988 # of both arguments, starting at the beginning of both.
990 /* private */ function getCommon( $st1, $st2 )
992 $fl = strlen( $st1 );
993 $shorter = strlen( $st2 );
994 if ( $fl < $shorter ) { $shorter = $fl; }
996 for ( $i = 0; $i < $shorter; ++$i ) {
997 if ( $st1{$i} != $st2{$i} ) { break; }
999 return $i;
1001 # These next three functions open, continue, and close the list
1002 # element appropriate to the prefix character passed into them.
1004 /* private */ function openList( $char )
1006 $result = $this->closeParagraph();
1008 if ( "*" == $char ) { $result .= "<ul><li>"; }
1009 else if ( "#" == $char ) { $result .= "<ol><li>"; }
1010 else if ( ":" == $char ) { $result .= "<dl><dd>"; }
1011 else if ( ";" == $char ) {
1012 $result .= "<dl><dt>";
1013 $this->mDTopen = true;
1015 else { $result = "<!-- ERR 1 -->"; }
1017 return $result;
1020 /* private */ function nextItem( $char )
1022 if ( "*" == $char || "#" == $char ) { return "</li><li>"; }
1023 else if ( ":" == $char || ";" == $char ) {
1024 $close = "</dd>";
1025 if ( $this->mDTopen ) { $close = "</dt>"; }
1026 if ( ";" == $char ) {
1027 $this->mDTopen = true;
1028 return $close . "<dt>";
1029 } else {
1030 $this->mDTopen = false;
1031 return $close . "<dd>";
1034 return "<!-- ERR 2 -->";
1037 /* private */function closeList( $char )
1039 if ( "*" == $char ) { $text = "</li></ul>"; }
1040 else if ( "#" == $char ) { $text = "</li></ol>"; }
1041 else if ( ":" == $char ) {
1042 if ( $this->mDTopen ) {
1043 $this->mDTopen = false;
1044 $text = "</dt></dl>";
1045 } else {
1046 $text = "</dd></dl>";
1049 else { return "<!-- ERR 3 -->"; }
1050 return $text."\n";
1053 /* private */ function doBlockLevels( $text, $linestart )
1055 $fname = "Parser::doBlockLevels";
1056 wfProfileIn( $fname );
1057 # Parsing through the text line by line. The main thing
1058 # happening here is handling of block-level elements p, pre,
1059 # and making lists from lines starting with * # : etc.
1061 $a = explode( "\n", $text );
1063 $lastPref = $text = $lastLine = '';
1064 $this->mDTopen = $inBlockElem = false;
1065 $npl = 0;
1066 $pstack = false;
1068 if ( ! $linestart ) { $text .= array_shift( $a ); }
1069 foreach ( $a as $t ) {
1070 $oLine = $t;
1071 $opl = strlen( $lastPref );
1072 $preCloseMatch = preg_match("/<\\/pre/i", $t );
1073 $preOpenMatch = preg_match("/<pre/i", $t );
1074 if (!$this->mInPre) {
1075 $this->mInPre = !empty($preOpenMatch);
1077 if ( !$this->mInPre ) {
1078 $npl = strspn( $t, "*#:;" );
1079 $pref = substr( $t, 0, $npl );
1080 $pref2 = str_replace( ";", ":", $pref );
1081 $t = substr( $t, $npl );
1082 } else {
1083 $npl = 0;
1084 $pref = $pref2 = '';
1087 // list generation
1088 if ( 0 != $npl && 0 == strcmp( $lastPref, $pref2 ) ) {
1089 $text .= $this->nextItem( substr( $pref, -1 ) );
1090 if ( $pstack ) { $pstack = false; }
1092 if ( ";" == substr( $pref, -1 ) ) {
1093 $cpos = strpos( $t, ":" );
1094 if ( false !== $cpos ) {
1095 $term = substr( $t, 0, $cpos );
1096 $text .= $term . $this->nextItem( ":" );
1097 $t = substr( $t, $cpos + 1 );
1100 } else if (0 != $npl || 0 != $opl) {
1101 $cpl = $this->getCommon( $pref, $lastPref );
1102 if ( $pstack ) { $pstack = false; }
1104 while ( $cpl < $opl ) {
1105 $text .= $this->closeList( $lastPref{$opl-1} );
1106 --$opl;
1108 if ( $npl <= $cpl && $cpl > 0 ) {
1109 $text .= $this->nextItem( $pref{$cpl-1} );
1111 while ( $npl > $cpl ) {
1112 $char = substr( $pref, $cpl, 1 );
1113 $text .= $this->openList( $char );
1115 if ( ";" == $char ) {
1116 $cpos = strpos( $t, ":" );
1117 if ( ! ( false === $cpos ) ) {
1118 $term = substr( $t, 0, $cpos );
1119 $text .= $term . $this->nextItem( ":" );
1120 $t = substr( $t, $cpos + 1 );
1123 ++$cpl;
1125 $lastPref = $pref2;
1127 if ( 0 == $npl ) { # No prefix (not in list)--go to paragraph mode
1128 $uniq_prefix = UNIQ_PREFIX;
1129 // XXX: use a stack for nestable elements like span, table and div
1130 $openmatch = preg_match("/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<div|<pre|<tr|<td|<p|<ul|<li)/i", $t );
1131 $closematch = preg_match(
1132 "/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|".
1133 "<\\/div|<hr|<\\/td|<\\/pre|<\\/p|".$uniq_prefix."-pre|<\\/li|<\\/ul)/i", $t );
1134 if ( $openmatch or $closematch ) {
1135 if ( $pstack ) { $pstack = false; }
1136 $text .= $this->closeParagraph();
1137 if($preOpenMatch and !$preCloseMatch) {
1138 $this->mInPre = true;
1140 if ( $closematch ) {
1141 $inBlockElem = false;
1142 } else {
1143 $inBlockElem = true;
1145 } else if ( !$inBlockElem ) {
1146 if ( " " == $t{0} ) {
1147 // pre
1148 if ($this->mLastSection != 'pre') {
1149 $pstack = false;
1150 $text .= $this->closeParagraph().'<pre>';
1151 $this->mLastSection = 'pre';
1153 } else {
1154 // paragraph
1155 if ( '' == trim($t) ) {
1156 if ( $pstack ) {
1157 $text .= $pstack.'<br/>';
1158 $pstack = false;
1159 $this->mLastSection = 'p';
1160 } else {
1161 if ($this->mLastSection != 'p' ) {
1162 $text .= $this->closeParagraph();
1163 $this->mLastSection = '';
1164 $pstack = "<p>";
1165 } else {
1166 $pstack = '</p><p>';
1169 } else {
1170 if ( $pstack ) {
1171 $text .= $pstack;
1172 $pstack = false;
1173 $this->mLastSection = 'p';
1174 } else if ($this->mLastSection != 'p') {
1175 $text .= $this->closeParagraph().'<p>';
1176 $this->mLastSection = 'p';
1182 if ($pstack === false) {
1183 $text .= $t."\n";
1186 while ( $npl ) {
1187 $text .= $this->closeList( $pref2{$npl-1} );
1188 --$npl;
1190 if ( "" != $this->mLastSection ) {
1191 $text .= "</" . $this->mLastSection . ">";
1192 $this->mLastSection = "";
1195 wfProfileOut( $fname );
1196 return $text;
1199 function getVariableValue( $index ) {
1200 global $wgLang, $wgSitename, $wgServer;
1202 switch ( $index ) {
1203 case MAG_CURRENTMONTH:
1204 return date( "m" );
1205 case MAG_CURRENTMONTHNAME:
1206 return $wgLang->getMonthName( date("n") );
1207 case MAG_CURRENTMONTHNAMEGEN:
1208 return $wgLang->getMonthNameGen( date("n") );
1209 case MAG_CURRENTDAY:
1210 return date("j");
1211 case MAG_PAGENAME:
1212 return $this->mTitle->getText();
1213 case MAG_NAMESPACE:
1214 # return Namespace::getCanonicalName($this->mTitle->getNamespace());
1215 return $wgLang->getNsText($this->mTitle->getNamespace()); // Patch by Dori
1216 case MAG_CURRENTDAYNAME:
1217 return $wgLang->getWeekdayName( date("w")+1 );
1218 case MAG_CURRENTYEAR:
1219 return date( "Y" );
1220 case MAG_CURRENTTIME:
1221 return $wgLang->time( wfTimestampNow(), false );
1222 case MAG_NUMBEROFARTICLES:
1223 return wfNumberOfArticles();
1224 case MAG_SITENAME:
1225 return $wgSitename;
1226 case MAG_SERVER:
1227 return $wgServer;
1228 default:
1229 return NULL;
1233 function initialiseVariables()
1235 global $wgVariableIDs;
1236 $this->mVariables = array();
1237 foreach ( $wgVariableIDs as $id ) {
1238 $mw =& MagicWord::get( $id );
1239 $mw->addToArray( $this->mVariables, $this->getVariableValue( $id ) );
1243 /* private */ function replaceVariables( $text, $args = array() )
1245 global $wgLang, $wgScript, $wgArticlePath;
1247 $fname = "Parser::replaceVariables";
1248 wfProfileIn( $fname );
1250 $bail = false;
1251 if ( !$this->mVariables ) {
1252 $this->initialiseVariables();
1254 $titleChars = Title::legalChars();
1255 $regex = "/(\\n?){{([$titleChars]*?)(\\|.*?|)}}/s";
1257 # This function is called recursively. To keep track of arguments we need a stack:
1258 array_push( $this->mArgStack, $args );
1260 # PHP global rebinding syntax is a bit weird, need to use the GLOBALS array
1261 $GLOBALS['wgCurParser'] =& $this;
1262 $text = preg_replace_callback( $regex, "wfBraceSubstitution", $text );
1264 array_pop( $this->mArgStack );
1266 return $text;
1269 function braceSubstitution( $matches )
1271 global $wgLinkCache, $wgLang;
1272 $fname = "Parser::braceSubstitution";
1273 $found = false;
1274 $nowiki = false;
1275 $title = NULL;
1277 # $newline is an optional newline character before the braces
1278 # $part1 is the bit before the first |, and must contain only title characters
1279 # $args is a list of arguments, starting from index 0, not including $part1
1281 $newline = $matches[1];
1282 $part1 = $matches[2];
1283 # If the third subpattern matched anything, it will start with |
1284 if ( $matches[3] !== "" ) {
1285 $args = explode( "|", substr( $matches[3], 1 ) );
1286 } else {
1287 $args = array();
1289 $argc = count( $args );
1291 # SUBST
1292 $mwSubst =& MagicWord::get( MAG_SUBST );
1293 if ( $mwSubst->matchStartAndRemove( $part1 ) ) {
1294 if ( $this->mOutputType != OT_WIKI ) {
1295 # Invalid SUBST not replaced at PST time
1296 # Return without further processing
1297 $text = $matches[0];
1298 $found = true;
1300 } elseif ( $this->mOutputType == OT_WIKI ) {
1301 # SUBST not found in PST pass, do nothing
1302 $text = $matches[0];
1303 $found = true;
1306 # MSG, MSGNW and INT
1307 if ( !$found ) {
1308 # Check for MSGNW:
1309 $mwMsgnw =& MagicWord::get( MAG_MSGNW );
1310 if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
1311 $nowiki = true;
1312 } else {
1313 # Remove obsolete MSG:
1314 $mwMsg =& MagicWord::get( MAG_MSG );
1315 $mwMsg->matchStartAndRemove( $part1 );
1318 # Check if it is an internal message
1319 $mwInt =& MagicWord::get( MAG_INT );
1320 if ( $mwInt->matchStartAndRemove( $part1 ) ) {
1321 if ( $this->incrementIncludeCount( "int:$part1" ) ) {
1322 $text = wfMsgReal( $part1, $args, true );
1323 $found = true;
1328 # NS
1329 if ( !$found ) {
1330 # Check for NS: (namespace expansion)
1331 $mwNs = MagicWord::get( MAG_NS );
1332 if ( $mwNs->matchStartAndRemove( $part1 ) ) {
1333 if ( intval( $part1 ) ) {
1334 $text = $wgLang->getNsText( intval( $part1 ) );
1335 $found = true;
1336 } else {
1337 $index = Namespace::getCanonicalIndex( strtolower( $part1 ) );
1338 if ( !is_null( $index ) ) {
1339 $text = $wgLang->getNsText( $index );
1340 $found = true;
1346 # LOCALURL and LOCALURLE
1347 if ( !$found ) {
1348 $mwLocal = MagicWord::get( MAG_LOCALURL );
1349 $mwLocalE = MagicWord::get( MAG_LOCALURLE );
1351 if ( $mwLocal->matchStartAndRemove( $part1 ) ) {
1352 $func = 'getLocalURL';
1353 } elseif ( $mwLocalE->matchStartAndRemove( $part1 ) ) {
1354 $func = 'escapeLocalURL';
1355 } else {
1356 $func = '';
1359 if ( $func !== '' ) {
1360 $title = Title::newFromText( $part1 );
1361 if ( !is_null( $title ) ) {
1362 if ( $argc > 0 ) {
1363 $text = $title->$func( $args[0] );
1364 } else {
1365 $text = $title->$func();
1367 $found = true;
1372 # Internal variables
1373 if ( !$found && array_key_exists( $part1, $this->mVariables ) ) {
1374 $text = $this->mVariables[$part1];
1375 $found = true;
1376 $this->mOutput->mContainsOldMagic = true;
1379 # Arguments input from the caller
1380 $inputArgs = end( $this->mArgStack );
1381 if ( !$found && array_key_exists( $part1, $inputArgs ) ) {
1382 $text = $inputArgs[$part1];
1383 $found = true;
1386 # Load from database
1387 if ( !$found ) {
1388 $title = Title::newFromText( $part1, NS_TEMPLATE );
1389 if ( !is_null( $title ) && !$title->isExternal() ) {
1390 # Check for excessive inclusion
1391 $dbk = $title->getPrefixedDBkey();
1392 if ( $this->incrementIncludeCount( $dbk ) ) {
1393 $article = new Article( $title );
1394 $articleContent = $article->getContentWithoutUsingSoManyDamnGlobals();
1395 if ( $articleContent !== false ) {
1396 $found = true;
1397 $text = $articleContent;
1402 # If the title is valid but undisplayable, make a link to it
1403 if ( $this->mOutputType == OT_HTML && !$found ) {
1404 $text = "[[" . $title->getPrefixedText() . "]]";
1405 $found = true;
1410 # Recursive parsing, escaping and link table handling
1411 # Only for HTML output
1412 if ( $nowiki && $found && $this->mOutputType == OT_HTML ) {
1413 $text = wfEscapeWikiText( $text );
1414 } elseif ( $this->mOutputType == OT_HTML && $found ) {
1415 # Clean up argument array
1416 $assocArgs = array();
1417 $index = 1;
1418 foreach( $args as $arg ) {
1419 $eqpos = strpos( $arg, "=" );
1420 if ( $eqpos === false ) {
1421 $assocArgs[$index++] = $arg;
1422 } else {
1423 $name = trim( substr( $arg, 0, $eqpos ) );
1424 $value = trim( substr( $arg, $eqpos+1 ) );
1425 if ( $value === false ) {
1426 $value = "";
1428 if ( $name !== false ) {
1429 $assocArgs[$name] = $value;
1434 # Do not enter included links in link table
1435 if ( !is_null( $title ) ) {
1436 $wgLinkCache->suspend();
1439 # Run full parser on the included text
1440 $text = $this->strip( $text, $this->mStripState );
1441 $text = $this->internalParse( $text, (bool)$newline, $assocArgs );
1443 # Add the result to the strip state for re-inclusion after
1444 # the rest of the processing
1445 $text = $this->insertStripItem( $text, $this->mStripState );
1447 # Resume the link cache and register the inclusion as a link
1448 if ( !is_null( $title ) ) {
1449 $wgLinkCache->resume();
1450 $wgLinkCache->addLinkObj( $title );
1454 if ( !$found ) {
1455 return $matches[0];
1456 } else {
1457 return $newline . $text;
1461 # Returns true if the function is allowed to include this entity
1462 function incrementIncludeCount( $dbk )
1464 if ( !array_key_exists( $dbk, $this->mIncludeCount ) ) {
1465 $this->mIncludeCount[$dbk] = 0;
1467 if ( ++$this->mIncludeCount[$dbk] <= MAX_INCLUDE_REPEAT ) {
1468 return true;
1469 } else {
1470 return false;
1475 # Cleans up HTML, removes dangerous tags and attributes
1476 /* private */ function removeHTMLtags( $text )
1478 $fname = "Parser::removeHTMLtags";
1479 wfProfileIn( $fname );
1480 $htmlpairs = array( # Tags that must be closed
1481 "b", "del", "i", "ins", "u", "font", "big", "small", "sub", "sup", "h1",
1482 "h2", "h3", "h4", "h5", "h6", "cite", "code", "em", "s",
1483 "strike", "strong", "tt", "var", "div", "center",
1484 "blockquote", "ol", "ul", "dl", "table", "caption", "pre",
1485 "ruby", "rt" , "rb" , "rp", "p"
1487 $htmlsingle = array(
1488 "br", "hr", "li", "dt", "dd"
1490 $htmlnest = array( # Tags that can be nested--??
1491 "table", "tr", "td", "th", "div", "blockquote", "ol", "ul",
1492 "dl", "font", "big", "small", "sub", "sup"
1494 $tabletags = array( # Can only appear inside table
1495 "td", "th", "tr"
1498 $htmlsingle = array_merge( $tabletags, $htmlsingle );
1499 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
1501 $htmlattrs = $this->getHTMLattrs () ;
1503 # Remove HTML comments
1504 $text = preg_replace( "/<!--.*-->/sU", "", $text );
1506 $bits = explode( "<", $text );
1507 $text = array_shift( $bits );
1508 $tagstack = array(); $tablestack = array();
1510 foreach ( $bits as $x ) {
1511 $prev = error_reporting( E_ALL & ~( E_NOTICE | E_WARNING ) );
1512 preg_match( "/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/",
1513 $x, $regs );
1514 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
1515 error_reporting( $prev );
1517 $badtag = 0 ;
1518 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
1519 # Check our stack
1520 if ( $slash ) {
1521 # Closing a tag...
1522 if ( ! in_array( $t, $htmlsingle ) &&
1523 ( $ot = array_pop( $tagstack ) ) != $t ) {
1524 array_push( $tagstack, $ot );
1525 $badtag = 1;
1526 } else {
1527 if ( $t == "table" ) {
1528 $tagstack = array_pop( $tablestack );
1530 $newparams = "";
1532 } else {
1533 # Keep track for later
1534 if ( in_array( $t, $tabletags ) &&
1535 ! in_array( "table", $tagstack ) ) {
1536 $badtag = 1;
1537 } else if ( in_array( $t, $tagstack ) &&
1538 ! in_array ( $t , $htmlnest ) ) {
1539 $badtag = 1 ;
1540 } else if ( ! in_array( $t, $htmlsingle ) ) {
1541 if ( $t == "table" ) {
1542 array_push( $tablestack, $tagstack );
1543 $tagstack = array();
1545 array_push( $tagstack, $t );
1547 # Strip non-approved attributes from the tag
1548 $newparams = $this->fixTagAttributes($params);
1551 if ( ! $badtag ) {
1552 $rest = str_replace( ">", "&gt;", $rest );
1553 $text .= "<$slash$t $newparams$brace$rest";
1554 continue;
1557 $text .= "&lt;" . str_replace( ">", "&gt;", $x);
1559 # Close off any remaining tags
1560 while ( $t = array_pop( $tagstack ) ) {
1561 $text .= "</$t>\n";
1562 if ( $t == "table" ) { $tagstack = array_pop( $tablestack ); }
1564 wfProfileOut( $fname );
1565 return $text;
1570 * This function accomplishes several tasks:
1571 * 1) Auto-number headings if that option is enabled
1572 * 2) Add an [edit] link to sections for logged in users who have enabled the option
1573 * 3) Add a Table of contents on the top for users who have enabled the option
1574 * 4) Auto-anchor headings
1576 * It loops through all headlines, collects the necessary data, then splits up the
1577 * string and re-inserts the newly formatted headlines.
1581 /* private */ function formatHeadings( $text )
1583 $doNumberHeadings = $this->mOptions->getNumberHeadings();
1584 $doShowToc = $this->mOptions->getShowToc();
1585 if( !$this->mTitle->userCanEdit() ) {
1586 $showEditLink = 0;
1587 $rightClickHack = 0;
1588 } else {
1589 $showEditLink = $this->mOptions->getEditSection();
1590 $rightClickHack = $this->mOptions->getEditSectionOnRightClick();
1593 # Inhibit editsection links if requested in the page
1594 $esw =& MagicWord::get( MAG_NOEDITSECTION );
1595 if( $esw->matchAndRemove( $text ) ) {
1596 $showEditLink = 0;
1598 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
1599 # do not add TOC
1600 $mw =& MagicWord::get( MAG_NOTOC );
1601 if( $mw->matchAndRemove( $text ) ) {
1602 $doShowToc = 0;
1605 # never add the TOC to the Main Page. This is an entry page that should not
1606 # be more than 1-2 screens large anyway
1607 if( $this->mTitle->getPrefixedText() == wfMsg("mainpage") ) {
1608 $doShowToc = 0;
1611 # Get all headlines for numbering them and adding funky stuff like [edit]
1612 # links - this is for later, but we need the number of headlines right now
1613 $numMatches = preg_match_all( "/<H([1-6])(.*?" . ">)(.*?)<\/H[1-6]>/i", $text, $matches );
1615 # if there are fewer than 4 headlines in the article, do not show TOC
1616 if( $numMatches < 4 ) {
1617 $doShowToc = 0;
1620 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
1621 # override above conditions and always show TOC
1622 $mw =& MagicWord::get( MAG_FORCETOC );
1623 if ($mw->matchAndRemove( $text ) ) {
1624 $doShowToc = 1;
1628 # We need this to perform operations on the HTML
1629 $sk =& $this->mOptions->getSkin();
1631 # headline counter
1632 $headlineCount = 0;
1634 # Ugh .. the TOC should have neat indentation levels which can be
1635 # passed to the skin functions. These are determined here
1636 $toclevel = 0;
1637 $toc = "";
1638 $full = "";
1639 $head = array();
1640 $sublevelCount = array();
1641 $level = 0;
1642 $prevlevel = 0;
1643 foreach( $matches[3] as $headline ) {
1644 $numbering = "";
1645 if( $level ) {
1646 $prevlevel = $level;
1648 $level = $matches[1][$headlineCount];
1649 if( ( $doNumberHeadings || $doShowToc ) && $prevlevel && $level > $prevlevel ) {
1650 # reset when we enter a new level
1651 $sublevelCount[$level] = 0;
1652 $toc .= $sk->tocIndent( $level - $prevlevel );
1653 $toclevel += $level - $prevlevel;
1655 if( ( $doNumberHeadings || $doShowToc ) && $level < $prevlevel ) {
1656 # reset when we step back a level
1657 $sublevelCount[$level+1]=0;
1658 $toc .= $sk->tocUnindent( $prevlevel - $level );
1659 $toclevel -= $prevlevel - $level;
1661 # count number of headlines for each level
1662 @$sublevelCount[$level]++;
1663 if( $doNumberHeadings || $doShowToc ) {
1664 $dot = 0;
1665 for( $i = 1; $i <= $level; $i++ ) {
1666 if( !empty( $sublevelCount[$i] ) ) {
1667 if( $dot ) {
1668 $numbering .= ".";
1670 $numbering .= $sublevelCount[$i];
1671 $dot = 1;
1676 # The canonized header is a version of the header text safe to use for links
1677 # Avoid insertion of weird stuff like <math> by expanding the relevant sections
1678 $canonized_headline = $this->unstrip( $headline, $this->mStripState );
1680 # strip out HTML
1681 $canonized_headline = preg_replace( "/<.*?" . ">/","",$canonized_headline );
1682 $tocline = trim( $canonized_headline );
1683 $canonized_headline = preg_replace("/[ \\?&\\/<>\\(\\)\\[\\]=,+']+/", '_', html_entity_decode( $tocline));
1684 $refer[$headlineCount] = $canonized_headline;
1686 # count how many in assoc. array so we can track dupes in anchors
1687 @$refers[$canonized_headline]++;
1688 $refcount[$headlineCount]=$refers[$canonized_headline];
1690 # Prepend the number to the heading text
1692 if( $doNumberHeadings || $doShowToc ) {
1693 $tocline = $numbering . " " . $tocline;
1695 # Don't number the heading if it is the only one (looks silly)
1696 if( $doNumberHeadings && count( $matches[3] ) > 1) {
1697 # the two are different if the line contains a link
1698 $headline=$numbering . " " . $headline;
1702 # Create the anchor for linking from the TOC to the section
1703 $anchor = $canonized_headline;
1704 if($refcount[$headlineCount] > 1 ) {
1705 $anchor .= "_" . $refcount[$headlineCount];
1707 if( $doShowToc ) {
1708 $toc .= $sk->tocLine($anchor,$tocline,$toclevel);
1710 if( $showEditLink ) {
1711 if ( empty( $head[$headlineCount] ) ) {
1712 $head[$headlineCount] = "";
1714 $head[$headlineCount] .= $sk->editSectionLink($headlineCount+1);
1717 # Add the edit section span
1718 if( $rightClickHack ) {
1719 $headline = $sk->editSectionScript($headlineCount+1,$headline);
1722 # give headline the correct <h#> tag
1723 @$head[$headlineCount] .= "<a name=\"$anchor\"></a><h".$level.$matches[2][$headlineCount] .$headline."</h".$level.">";
1725 $headlineCount++;
1728 if( $doShowToc ) {
1729 $toclines = $headlineCount;
1730 $toc .= $sk->tocUnindent( $toclevel );
1731 $toc = $sk->tocTable( $toc );
1734 # split up and insert constructed headlines
1736 $blocks = preg_split( "/<H[1-6].*?" . ">.*?<\/H[1-6]>/i", $text );
1737 $i = 0;
1739 foreach( $blocks as $block ) {
1740 if( $showEditLink && $headlineCount > 0 && $i == 0 && $block != "\n" ) {
1741 # This is the [edit] link that appears for the top block of text when
1742 # section editing is enabled
1744 # Disabled because it broke block formatting
1745 # For example, a bullet point in the top line
1746 # $full .= $sk->editSectionLink(0);
1748 $full .= $block;
1749 if( $doShowToc && !$i) {
1750 # Top anchor now in skin
1751 $full = $full.$toc;
1754 if( !empty( $head[$i] ) ) {
1755 $full .= $head[$i];
1757 $i++;
1760 return $full;
1763 /* private */ function doMagicISBN( &$tokenizer )
1765 global $wgLang;
1767 # Check whether next token is a text token
1768 # If yes, fetch it and convert the text into a
1769 # Special::BookSources link
1770 $token = $tokenizer->previewToken();
1771 while ( $token["type"] == "" )
1773 $tokenizer->nextToken();
1774 $token = $tokenizer->previewToken();
1776 if ( $token["type"] == "text" )
1778 $token = $tokenizer->nextToken();
1779 $x = $token["text"];
1780 $valid = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1782 $isbn = $blank = "" ;
1783 while ( " " == $x{0} ) {
1784 $blank .= " ";
1785 $x = substr( $x, 1 );
1787 while ( strstr( $valid, $x{0} ) != false ) {
1788 $isbn .= $x{0};
1789 $x = substr( $x, 1 );
1791 $num = str_replace( "-", "", $isbn );
1792 $num = str_replace( " ", "", $num );
1794 if ( "" == $num ) {
1795 $text = "ISBN $blank$x";
1796 } else {
1797 $titleObj = Title::makeTitle( NS_SPECIAL, "Booksources" );
1798 $text = "<a href=\"" .
1799 $titleObj->escapeLocalUrl( "isbn={$num}" ) .
1800 "\" class=\"internal\">ISBN $isbn</a>";
1801 $text .= $x;
1803 } else {
1804 $text = "ISBN ";
1806 return $text;
1808 /* private */ function doMagicRFC( &$tokenizer )
1810 global $wgLang;
1812 # Check whether next token is a text token
1813 # If yes, fetch it and convert the text into a
1814 # link to an RFC source
1815 $token = $tokenizer->previewToken();
1816 while ( $token["type"] == "" )
1818 $tokenizer->nextToken();
1819 $token = $tokenizer->previewToken();
1821 if ( $token["type"] == "text" )
1823 $token = $tokenizer->nextToken();
1824 $x = $token["text"];
1825 $valid = "0123456789";
1827 $rfc = $blank = "" ;
1828 while ( " " == $x{0} ) {
1829 $blank .= " ";
1830 $x = substr( $x, 1 );
1832 while ( strstr( $valid, $x{0} ) != false ) {
1833 $rfc .= $x{0};
1834 $x = substr( $x, 1 );
1837 if ( "" == $rfc ) {
1838 $text .= "RFC $blank$x";
1839 } else {
1840 $url = wfmsg( "rfcurl" );
1841 $url = str_replace( "$1", $rfc, $url);
1842 $sk =& $this->mOptions->getSkin();
1843 $la = $sk->getExternalLinkAttributes( $url, "RFC {$rfc}" );
1844 $text = "<a href='{$url}'{$la}>RFC {$rfc}</a>{$x}";
1846 } else {
1847 $text = "RFC ";
1849 return $text;
1852 function preSaveTransform( $text, &$title, &$user, $options, $clearState = true )
1854 $this->mOptions = $options;
1855 $this->mTitle =& $title;
1856 $this->mOutputType = OT_WIKI;
1858 if ( $clearState ) {
1859 $this->clearState();
1862 $stripState = false;
1863 $pairs = array(
1864 "\r\n" => "\n",
1866 $text = str_replace(array_keys($pairs), array_values($pairs), $text);
1867 // now with regexes
1868 $pairs = array(
1869 "/<br.+(clear|break)=[\"']?(all|both)[\"']?\\/?>/i" => '<br style="clear:both;"/>',
1870 "/<br *?>/i" => "<br/>",
1872 $text = preg_replace(array_keys($pairs), array_values($pairs), $text);
1873 $text = $this->strip( $text, $stripState, false );
1874 $text = $this->pstPass2( $text, $user );
1875 $text = $this->unstrip( $text, $stripState );
1876 return $text;
1879 /* private */ function pstPass2( $text, &$user )
1881 global $wgLang, $wgLocaltimezone, $wgCurParser;
1883 # Variable replacement
1884 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
1885 $text = $this->replaceVariables( $text );
1887 # Signatures
1889 $n = $user->getName();
1890 $k = $user->getOption( "nickname" );
1891 if ( "" == $k ) { $k = $n; }
1892 if(isset($wgLocaltimezone)) {
1893 $oldtz = getenv("TZ"); putenv("TZ=$wgLocaltimezone");
1895 /* Note: this is an ugly timezone hack for the European wikis */
1896 $d = $wgLang->timeanddate( date( "YmdHis" ), false ) .
1897 " (" . date( "T" ) . ")";
1898 if(isset($wgLocaltimezone)) putenv("TZ=$oldtz");
1900 $text = preg_replace( "/~~~~~/", $d, $text );
1901 $text = preg_replace( "/~~~~/", "[[" . $wgLang->getNsText(
1902 Namespace::getUser() ) . ":$n|$k]] $d", $text );
1903 $text = preg_replace( "/~~~/", "[[" . $wgLang->getNsText(
1904 Namespace::getUser() ) . ":$n|$k]]", $text );
1906 # Context links: [[|name]] and [[name (context)|]]
1908 $tc = "[&;%\\-,.\\(\\)' _0-9A-Za-z\\/:\\x80-\\xff]";
1909 $np = "[&;%\\-,.' _0-9A-Za-z\\/:\\x80-\\xff]"; # No parens
1910 $namespacechar = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
1911 $conpat = "/^({$np}+) \\(({$tc}+)\\)$/";
1913 $p1 = "/\[\[({$np}+) \\(({$np}+)\\)\\|]]/"; # [[page (context)|]]
1914 $p2 = "/\[\[\\|({$tc}+)]]/"; # [[|page]]
1915 $p3 = "/\[\[($namespacechar+):({$np}+)\\|]]/"; # [[namespace:page|]]
1916 $p4 = "/\[\[($namespacechar+):({$np}+) \\(({$np}+)\\)\\|]]/";
1917 # [[ns:page (cont)|]]
1918 $context = "";
1919 $t = $this->mTitle->getText();
1920 if ( preg_match( $conpat, $t, $m ) ) {
1921 $context = $m[2];
1923 $text = preg_replace( $p4, "[[\\1:\\2 (\\3)|\\2]]", $text );
1924 $text = preg_replace( $p1, "[[\\1 (\\2)|\\1]]", $text );
1925 $text = preg_replace( $p3, "[[\\1:\\2|\\2]]", $text );
1927 if ( "" == $context ) {
1928 $text = preg_replace( $p2, "[[\\1]]", $text );
1929 } else {
1930 $text = preg_replace( $p2, "[[\\1 ({$context})|\\1]]", $text );
1934 $mw =& MagicWord::get( MAG_SUBST );
1935 $wgCurParser = $this->fork();
1936 $text = $mw->substituteCallback( $text, "wfBraceSubstitution" );
1937 $this->merge( $wgCurParser );
1940 # Trim trailing whitespace
1941 # MAG_END (__END__) tag allows for trailing
1942 # whitespace to be deliberately included
1943 $text = rtrim( $text );
1944 $mw =& MagicWord::get( MAG_END );
1945 $mw->matchAndRemove( $text );
1947 return $text;
1950 # Set up some variables which are usually set up in parse()
1951 # so that an external function can call some class members with confidence
1952 function startExternalParse( &$title, $options, $outputType, $clearState = true )
1954 $this->mTitle =& $title;
1955 $this->mOptions = $options;
1956 $this->mOutputType = $outputType;
1957 if ( $clearState ) {
1958 $this->clearState();
1962 function transformMsg( $text, $options ) {
1963 global $wgTitle;
1964 static $executing = false;
1966 # Guard against infinite recursion
1967 if ( $executing ) {
1968 return $text;
1970 $executing = true;
1972 $this->mTitle = $wgTitle;
1973 $this->mOptions = $options;
1974 $this->mOutputType = OT_MSG;
1975 $this->clearState();
1976 $text = $this->replaceVariables( $text );
1978 $executing = false;
1979 return $text;
1983 class ParserOutput
1985 var $mText, $mLanguageLinks, $mCategoryLinks, $mContainsOldMagic;
1987 function ParserOutput( $text = "", $languageLinks = array(), $categoryLinks = array(),
1988 $containsOldMagic = false )
1990 $this->mText = $text;
1991 $this->mLanguageLinks = $languageLinks;
1992 $this->mCategoryLinks = $categoryLinks;
1993 $this->mContainsOldMagic = $containsOldMagic;
1996 function getText() { return $this->mText; }
1997 function getLanguageLinks() { return $this->mLanguageLinks; }
1998 function getCategoryLinks() { return $this->mCategoryLinks; }
1999 function containsOldMagic() { return $this->mContainsOldMagic; }
2000 function setText( $text ) { return wfSetVar( $this->mText, $text ); }
2001 function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks, $ll ); }
2002 function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategoryLinks, $cl ); }
2003 function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic, $com ); }
2005 function merge( $other ) {
2006 $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $other->mLanguageLinks );
2007 $this->mCategoryLinks = array_merge( $this->mCategoryLinks, $this->mLanguageLinks );
2008 $this->mContainsOldMagic = $this->mContainsOldMagic || $other->mContainsOldMagic;
2013 class ParserOptions
2015 # All variables are private
2016 var $mUseTeX; # Use texvc to expand <math> tags
2017 var $mUseCategoryMagic; # Treat [[Category:xxxx]] tags specially
2018 var $mUseDynamicDates; # Use $wgDateFormatter to format dates
2019 var $mInterwikiMagic; # Interlanguage links are removed and returned in an array
2020 var $mAllowExternalImages; # Allow external images inline
2021 var $mSkin; # Reference to the preferred skin
2022 var $mDateFormat; # Date format index
2023 var $mEditSection; # Create "edit section" links
2024 var $mEditSectionOnRightClick; # Generate JavaScript to edit section on right click
2025 var $mNumberHeadings; # Automatically number headings
2026 var $mShowToc; # Show table of contents
2028 function getUseTeX() { return $this->mUseTeX; }
2029 function getUseCategoryMagic() { return $this->mUseCategoryMagic; }
2030 function getUseDynamicDates() { return $this->mUseDynamicDates; }
2031 function getInterwikiMagic() { return $this->mInterwikiMagic; }
2032 function getAllowExternalImages() { return $this->mAllowExternalImages; }
2033 function getSkin() { return $this->mSkin; }
2034 function getDateFormat() { return $this->mDateFormat; }
2035 function getEditSection() { return $this->mEditSection; }
2036 function getEditSectionOnRightClick() { return $this->mEditSectionOnRightClick; }
2037 function getNumberHeadings() { return $this->mNumberHeadings; }
2038 function getShowToc() { return $this->mShowToc; }
2040 function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX, $x ); }
2041 function setUseCategoryMagic( $x ) { return wfSetVar( $this->mUseCategoryMagic, $x ); }
2042 function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
2043 function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
2044 function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); }
2045 function setSkin( $x ) { return wfSetRef( $this->mSkin, $x ); }
2046 function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); }
2047 function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); }
2048 function setEditSectionOnRightClick( $x ) { return wfSetVar( $this->mEditSectionOnRightClick, $x ); }
2049 function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); }
2050 function setShowToc( $x ) { return wfSetVar( $this->mShowToc, $x ); }
2052 /* static */ function newFromUser( &$user )
2054 $popts = new ParserOptions;
2055 $popts->initialiseFromUser( $user );
2056 return $popts;
2059 function initialiseFromUser( &$userInput )
2061 global $wgUseTeX, $wgUseCategoryMagic, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
2063 if ( !$userInput ) {
2064 $user = new User;
2065 $user->setLoaded( true );
2066 } else {
2067 $user =& $userInput;
2070 $this->mUseTeX = $wgUseTeX;
2071 $this->mUseCategoryMagic = $wgUseCategoryMagic;
2072 $this->mUseDynamicDates = $wgUseDynamicDates;
2073 $this->mInterwikiMagic = $wgInterwikiMagic;
2074 $this->mAllowExternalImages = $wgAllowExternalImages;
2075 $this->mSkin =& $user->getSkin();
2076 $this->mDateFormat = $user->getOption( "date" );
2077 $this->mEditSection = $user->getOption( "editsection" );
2078 $this->mEditSectionOnRightClick = $user->getOption( "editsectiononrightclick" );
2079 $this->mNumberHeadings = $user->getOption( "numberheadings" );
2080 $this->mShowToc = $user->getOption( "showtoc" );
2086 # Regex callbacks, used in Parser::replaceVariables
2087 function wfBraceSubstitution( $matches )
2089 global $wgCurParser;
2090 return $wgCurParser->braceSubstitution( $matches );