42c85acab5927cd8a178ed6c82a38c6075f3e742
4 * File for Parser and related classes
11 * Variable substitution O(N^2) attack
13 * Without countermeasures, it would be possible to attack the parser by saving
14 * a page filled with a large number of inclusions of large pages. The size of
15 * the generated page would be proportional to the square of the input size.
16 * Hence, we limit the number of inclusions of any given page, thus bringing any
17 * attack back to O(N).
20 define( 'MAX_INCLUDE_REPEAT', 100 );
21 define( 'MAX_INCLUDE_SIZE', 1000000 ); // 1 Million
23 define( 'RLH_FOR_UPDATE', 1 );
25 # Allowed values for $mOutputType
26 define( 'OT_HTML', 1 );
27 define( 'OT_WIKI', 2 );
28 define( 'OT_MSG' , 3 );
30 # string parameter for extractTags which will cause it
31 # to strip HTML comments in addition to regular
32 # <XML>-style tags. This should not be anything we
33 # may want to use in wikisyntax
34 define( 'STRIP_COMMENTS', 'HTMLCommentStrip' );
36 # prefix for escaping, used in two functions at least
37 define( 'UNIQ_PREFIX', 'NaodW29');
39 # Constants needed for external link processing
40 define( 'URL_PROTOCOLS', 'http|https|ftp|irc|gopher|news|mailto' );
41 define( 'HTTP_PROTOCOLS', 'http|https' );
42 # Everything except bracket, space, or control characters
43 define( 'EXT_LINK_URL_CLASS', '[^]<>"\\x00-\\x20\\x7F]' );
45 define( 'EXT_LINK_TEXT_CLASS', '[^\]\\x00-\\x1F\\x7F]' );
46 define( 'EXT_IMAGE_FNAME_CLASS', '[A-Za-z0-9_.,~%\\-+&;#*?!=()@\\x80-\\xFF]' );
47 define( 'EXT_IMAGE_EXTENSIONS', 'gif|png|jpg|jpeg' );
48 define( 'EXT_LINK_BRACKETED', '/\[(('.URL_PROTOCOLS
.'):'.EXT_LINK_URL_CLASS
.'+) *('.EXT_LINK_TEXT_CLASS
.'*?)\]/S' );
49 define( 'EXT_IMAGE_REGEX',
50 '/^('.HTTP_PROTOCOLS
.':)'. # Protocol
51 '('.EXT_LINK_URL_CLASS
.'+)\\/'. # Hostname and path
52 '('.EXT_IMAGE_FNAME_CLASS
.'+)\\.((?i)'.EXT_IMAGE_EXTENSIONS
.')$/S' # Filename
58 * Processes wiki markup
61 * There are three main entry points into the Parser class:
63 * produces HTML output
65 * produces altered wiki markup.
67 * performs brace substitution on MediaWiki messages
70 * objects: $wgLang, $wgDateFormatter, $wgLinkCache
72 * NOT $wgArticle, $wgUser or $wgTitle. Keep them away!
75 * $wgUseTex*, $wgUseDynamicDates*, $wgInterwikiMagic*,
76 * $wgNamespacesWithSubpages, $wgAllowExternalImages*,
79 * * only within ParserOptions
92 # Cleared with clearState():
93 var $mOutput, $mAutonumber, $mDTopen, $mStripState = array();
94 var $mVariables, $mIncludeCount, $mArgStack, $mLastSection, $mInPre;
97 var $mOptions, $mTitle, $mOutputType,
98 $mTemplates, // cache of already loaded templates, avoids
99 // multiple SQL queries for the same string
100 $mTemplatePath; // stores an unsorted hash of all the templates already loaded
101 // in this path. Used for loop detection.
111 $this->mTemplates
= array();
112 $this->mTemplatePath
= array();
113 $this->mTagHooks
= array();
122 function clearState() {
123 $this->mOutput
= new ParserOutput
;
124 $this->mAutonumber
= 0;
125 $this->mLastSection
= "";
126 $this->mDTopen
= false;
127 $this->mVariables
= false;
128 $this->mIncludeCount
= array();
129 $this->mStripState
= array();
130 $this->mArgStack
= array();
131 $this->mInPre
= false;
135 * First pass--just handle <nowiki> sections, pass the rest off
136 * to internalParse() which does all the real work.
139 * @return ParserOutput a ParserOutput
141 function parse( $text, &$title, $options, $linestart = true, $clearState = true ) {
142 global $wgUseTidy, $wgContLang;
143 $fname = 'Parser::parse';
144 wfProfileIn( $fname );
150 $this->mOptions
= $options;
151 $this->mTitle
=& $title;
152 $this->mOutputType
= OT_HTML
;
155 $text = $this->strip( $text, $this->mStripState
);
157 $text = $this->internalParse( $text, $linestart );
158 $text = $this->unstrip( $text, $this->mStripState
);
159 # Clean up special characters, only run once, next-to-last before doBlockLevels
162 # french spaces, last one Guillemet-left
163 # only if there is something before the space
164 '/(.) (?=\\?|:|;|!|\\302\\273)/i' => '\\1 \\2',
165 # french spaces, Guillemet-right
166 "/(\\302\\253) /i"=>"\\1 ",
167 '/<hr *>/i' => '<hr />',
168 '/<br *>/i' => '<br />',
169 '/<center *>/i' => '<div class="center">',
170 '/<\\/center *>/i' => '</div>',
171 # Clean up spare ampersands; note that we probably ought to be
172 # more careful about named entities.
173 '/&(?!:amp;|#[Xx][0-9A-fa-f]+;|#[0-9]+;|[a-zA-Z0-9]+;)/' => '&'
175 $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text );
178 # french spaces, last one Guillemet-left
179 '/ (\\?|:|;|!|\\302\\273)/i' => ' \\1',
180 # french spaces, Guillemet-right
181 '/(\\302\\253) /i' => '\\1 ',
182 '/<center *>/i' => '<div class="center">',
183 '/<\\/center *>/i' => '</div>'
185 $text = preg_replace( array_keys($fixtags), array_values($fixtags), $text );
188 $text = $this->doBlockLevels( $text, $linestart );
190 $this->replaceLinkHolders( $text );
191 $text = $wgContLang->convert($text);
193 $text = $this->unstripNoWiki( $text, $this->mStripState
);
196 $text = Parser
::tidy($text);
199 $this->mOutput
->setText( $text );
200 wfProfileOut( $fname );
201 return $this->mOutput
;
205 * Get a random string
210 function getRandomString() {
211 return dechex(mt_rand(0, 0x7fffffff)) . dechex(mt_rand(0, 0x7fffffff));
215 * Replaces all occurrences of <$tag>content</$tag> in the text
216 * with a random marker and returns the new text. the output parameter
217 * $content will be an associative array filled with data on the form
218 * $unique_marker => content.
220 * If $content is already set, the additional entries will be appended
221 * If $tag is set to STRIP_COMMENTS, the function will extract
222 * <!-- HTML comments -->
227 function extractTags($tag, $text, &$content, $uniq_prefix = ''){
228 $rnd = $uniq_prefix . '-' . $tag . Parser
::getRandomString();
235 while ( '' != $text ) {
236 if($tag==STRIP_COMMENTS
) {
237 $p = preg_split( '/<!--/i', $text, 2 );
239 $p = preg_split( "/<\\s*$tag\\s*>/i", $text, 2 );
242 if ( ( count( $p ) < 2 ) ||
( '' == $p[1] ) ) {
245 if($tag==STRIP_COMMENTS
) {
246 $q = preg_split( '/-->/i', $p[1], 2 );
248 $q = preg_split( "/<\\/\\s*$tag\\s*>/i", $p[1], 2 );
250 $marker = $rnd . sprintf('%08X', $n++
);
251 $content[$marker] = $q[0];
252 $stripped .= $marker;
260 * Strips and renders nowiki, pre, math, hiero
261 * If $render is set, performs necessary rendering operations on plugins
262 * Returns the text, and fills an array with data needed in unstrip()
263 * If the $state is already a valid strip state, it adds to the state
265 * @param bool $stripcomments when set, HTML comments <!-- like this -->
266 * will be stripped in addition to other tags. This is important
267 * for section editing, where these comments cause confusion when
268 * counting the sections in the wikisource
272 function strip( $text, &$state, $stripcomments = false ) {
273 $render = ($this->mOutputType
== OT_HTML
);
274 $html_content = array();
275 $nowiki_content = array();
276 $math_content = array();
277 $pre_content = array();
278 $comment_content = array();
279 $ext_content = array();
281 # Replace any instances of the placeholders
282 $uniq_prefix = UNIQ_PREFIX
;
283 #$text = str_replace( $uniq_prefix, wfHtmlEscapeFirst( $uniq_prefix ), $text );
286 global $wgRawHtml, $wgWhitelistEdit;
287 if( $wgRawHtml && $wgWhitelistEdit ) {
288 $text = Parser
::extractTags('html', $text, $html_content, $uniq_prefix);
289 foreach( $html_content as $marker => $content ) {
291 # Raw and unchecked for validity.
292 $html_content[$marker] = $content;
294 $html_content[$marker] = '<html>'.$content.'</html>';
300 $text = Parser
::extractTags('nowiki', $text, $nowiki_content, $uniq_prefix);
301 foreach( $nowiki_content as $marker => $content ) {
303 $nowiki_content[$marker] = wfEscapeHTMLTagsOnly( $content );
305 $nowiki_content[$marker] = '<nowiki>'.$content.'</nowiki>';
310 $text = Parser
::extractTags('math', $text, $math_content, $uniq_prefix);
311 foreach( $math_content as $marker => $content ){
313 if( $this->mOptions
->getUseTeX() ) {
314 $math_content[$marker] = renderMath( $content );
316 $math_content[$marker] = '<math>'.$content.'<math>';
319 $math_content[$marker] = '<math>'.$content.'</math>';
324 $text = Parser
::extractTags('pre', $text, $pre_content, $uniq_prefix);
325 foreach( $pre_content as $marker => $content ){
327 $pre_content[$marker] = '<pre>' . wfEscapeHTMLTagsOnly( $content ) . '</pre>';
329 $pre_content[$marker] = '<pre>'.$content.'</pre>';
335 $text = Parser
::extractTags(STRIP_COMMENTS
, $text, $comment_content, $uniq_prefix);
336 foreach( $comment_content as $marker => $content ){
337 $comment_content[$marker] = '<!--'.$content.'-->';
342 foreach ( $this->mTagHooks
as $tag => $callback ) {
343 $ext_contents[$tag] = array();
344 $text = Parser
::extractTags( $tag, $text, $ext_content[$tag], $uniq_prefix );
345 foreach( $ext_content[$tag] as $marker => $content ) {
347 $ext_content[$tag][$marker] = $callback( $content );
349 $ext_content[$tag][$marker] = "<$tag>$content</$tag>";
354 # Merge state with the pre-existing state, if there is one
356 $state['html'] = $state['html'] +
$html_content;
357 $state['nowiki'] = $state['nowiki'] +
$nowiki_content;
358 $state['math'] = $state['math'] +
$math_content;
359 $state['pre'] = $state['pre'] +
$pre_content;
360 $state['comment'] = $state['comment'] +
$comment_content;
362 foreach( $ext_content as $tag => $array ) {
363 if ( array_key_exists( $tag, $state ) ) {
364 $state[$tag] = $state[$tag] +
$array;
369 'html' => $html_content,
370 'nowiki' => $nowiki_content,
371 'math' => $math_content,
372 'pre' => $pre_content,
373 'comment' => $comment_content,
380 * restores pre, math, and hiero removed by strip()
382 * always call unstripNoWiki() after this one
385 function unstrip( $text, &$state ) {
386 # Must expand in reverse order, otherwise nested tags will be corrupted
387 $contentDict = end( $state );
388 for ( $contentDict = end( $state ); $contentDict !== false; $contentDict = prev( $state ) ) {
389 if( key($state) != 'nowiki' && key($state) != 'html') {
390 for ( $content = end( $contentDict ); $content !== false; $content = prev( $contentDict ) ) {
391 $text = str_replace( key( $contentDict ), $content, $text );
400 * always call this after unstrip() to preserve the order
404 function unstripNoWiki( $text, &$state ) {
405 # Must expand in reverse order, otherwise nested tags will be corrupted
406 for ( $content = end($state['nowiki']); $content !== false; $content = prev( $state['nowiki'] ) ) {
407 $text = str_replace( key( $state['nowiki'] ), $content, $text );
412 for ( $content = end($state['html']); $content !== false; $content = prev( $state['html'] ) ) {
413 $text = str_replace( key( $state['html'] ), $content, $text );
421 * Add an item to the strip state
422 * Returns the unique tag which must be inserted into the stripped text
423 * The tag will be replaced with the original text in unstrip()
427 function insertStripItem( $text, &$state ) {
428 $rnd = UNIQ_PREFIX
. '-item' . Parser
::getRandomString();
437 $state['item'][$rnd] = $text;
442 * Return allowed HTML attributes
446 function getHTMLattrs () {
447 $htmlattrs = array( # Allowed attributes--no scripting, etc.
448 'title', 'align', 'lang', 'dir', 'width', 'height',
449 'bgcolor', 'clear', /* BR */ 'noshade', /* HR */
450 'cite', /* BLOCKQUOTE, Q */ 'size', 'face', 'color',
451 /* FONT */ 'type', 'start', 'value', 'compact',
452 /* For various lists, mostly deprecated but safe */
453 'summary', 'width', 'border', 'frame', 'rules',
454 'cellspacing', 'cellpadding', 'valign', 'char',
455 'charoff', 'colgroup', 'col', 'span', 'abbr', 'axis',
456 'headers', 'scope', 'rowspan', 'colspan', /* Tables */
457 'id', 'class', 'name', 'style' /* For CSS */
463 * Remove non approved attributes and javascript in css
467 function fixTagAttributes ( $t ) {
468 if ( trim ( $t ) == '' ) return '' ; # Saves runtime ;-)
469 $htmlattrs = $this->getHTMLattrs() ;
471 # Strip non-approved attributes from the tag
473 '/(\\w+)(\\s*=\\s*([^\\s\">]+|\"[^\">]*\"))?/e',
474 "(in_array(strtolower(\"\$1\"),\$htmlattrs)?(\"\$1\".((\"x\$3\" != \"x\")?\"=\$3\":'')):'')",
477 $t = str_replace ( '<></>' , '' , $t ) ; # This should fix bug 980557
479 # Strip javascript "expression" from stylesheets. Brute force approach:
480 # If anythin offensive is found, all attributes of the HTML tag are dropped
483 '/style\\s*=.*(expression|tps*:\/\/|url\\s*\().*/is',
484 wfMungeToUtf8( $t ) ) )
493 * interface with html tidy, used if $wgUseTidy = true
498 function tidy ( $text ) {
499 global $wgTidyConf, $wgTidyBin, $wgTidyOpts;
500 global $wgInputEncoding, $wgOutputEncoding;
501 $fname = 'Parser::tidy';
502 wfProfileIn( $fname );
506 switch(strtoupper($wgOutputEncoding)) {
508 $opts .= ($wgInputEncoding == $wgOutputEncoding)?
' -latin1':' -raw';
511 $opts .= ($wgInputEncoding == $wgOutputEncoding)?
' -utf8':' -raw';
517 $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'.
518 ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>'.
519 '<head><title>test</title></head><body>'.$text.'</body></html>';
520 $descriptorspec = array(
521 0 => array('pipe', 'r'),
522 1 => array('pipe', 'w'),
523 2 => array('file', '/dev/null', 'a')
525 $process = proc_open("$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes);
526 if (is_resource($process)) {
527 fwrite($pipes[0], $wrappedtext);
529 while (!feof($pipes[1])) {
530 $cleansource .= fgets($pipes[1], 1024);
533 $return_value = proc_close($process);
536 wfProfileOut( $fname );
538 if( $cleansource == '' && $text != '') {
539 wfDebug( "Tidy error detected!\n" );
540 return $text . "\n<!-- Tidy found serious XHTML errors -->\n";
547 * parse the wiki syntax used to render tables
551 function doTableStuff ( $t ) {
552 $fname = 'Parser::doTableStuff';
553 wfProfileIn( $fname );
555 $t = explode ( "\n" , $t ) ;
556 $td = array () ; # Is currently a td tag open?
557 $ltd = array () ; # Was it TD or TH?
558 $tr = array () ; # Is currently a tr tag open?
559 $ltr = array () ; # tr attributes
560 $indent_level = 0; # indent level of the table
561 foreach ( $t AS $k => $x )
564 $fc = substr ( $x , 0 , 1 ) ;
565 if ( preg_match( '/^(:*)\{\|(.*)$/', $x, $matches ) ) {
566 $indent_level = strlen( $matches[1] );
568 str_repeat( '<dl><dd>', $indent_level ) .
569 '<table ' . $this->fixTagAttributes ( $matches[2] ) . '>' ;
570 array_push ( $td , false ) ;
571 array_push ( $ltd , '' ) ;
572 array_push ( $tr , false ) ;
573 array_push ( $ltr , '' ) ;
575 else if ( count ( $td ) == 0 ) { } # Don't do any of the following
576 else if ( '|}' == substr ( $x , 0 , 2 ) ) {
578 $l = array_pop ( $ltd ) ;
579 if ( array_pop ( $tr ) ) $z = '</tr>' . $z ;
580 if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ;
582 $t[$k] = $z . str_repeat( '</dd></dl>', $indent_level );
584 else if ( '|-' == substr ( $x , 0 , 2 ) ) { # Allows for |---------------
585 $x = substr ( $x , 1 ) ;
586 while ( $x != '' && substr ( $x , 0 , 1 ) == '-' ) $x = substr ( $x , 1 ) ;
588 $l = array_pop ( $ltd ) ;
589 if ( array_pop ( $tr ) ) $z = '</tr>' . $z ;
590 if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ;
593 array_push ( $tr , false ) ;
594 array_push ( $td , false ) ;
595 array_push ( $ltd , '' ) ;
596 array_push ( $ltr , $this->fixTagAttributes ( $x ) ) ;
598 else if ( '|' == $fc ||
'!' == $fc ||
'|+' == substr ( $x , 0 , 2 ) ) { # Caption
600 if ( '|+' == substr ( $x , 0 , 2 ) ) {
602 $x = substr ( $x , 1 ) ;
604 $after = substr ( $x , 1 ) ;
605 if ( $fc == '!' ) $after = str_replace ( '!!' , '||' , $after ) ;
606 $after = explode ( '||' , $after ) ;
609 # Loop through each table cell
610 foreach ( $after AS $theline )
615 $tra = array_pop ( $ltr ) ;
616 if ( !array_pop ( $tr ) ) $z = '<tr '.$tra.">\n" ;
617 array_push ( $tr , true ) ;
618 array_push ( $ltr , '' ) ;
621 $l = array_pop ( $ltd ) ;
622 if ( array_pop ( $td ) ) $z = '</'.$l.'>' . $z ;
623 if ( $fc == '|' ) $l = 'td' ;
624 else if ( $fc == '!' ) $l = 'th' ;
625 else if ( $fc == '+' ) $l = 'caption' ;
627 array_push ( $ltd , $l ) ;
630 $y = explode ( '|' , $theline , 2 ) ;
631 # Note that a '|' inside an invalid link should not
632 # be mistaken as delimiting cell parameters
633 if ( strpos( $y[0], '[[' ) !== false ) {
634 $y = array ($theline);
636 if ( count ( $y ) == 1 )
637 $y = "{$z}<{$l}>{$y[0]}" ;
638 else $y = $y = "{$z}<{$l} ".$this->fixTagAttributes($y[0]).">{$y[1]}" ;
640 array_push ( $td , true ) ;
645 # Closing open td, tr && table
646 while ( count ( $td ) > 0 )
648 if ( array_pop ( $td ) ) $t[] = '</td>' ;
649 if ( array_pop ( $tr ) ) $t[] = '</tr>' ;
653 $t = implode ( "\n" , $t ) ;
654 # $t = $this->removeHTMLtags( $t );
655 wfProfileOut( $fname );
660 * Helper function for parse() that transforms wiki markup into
661 * HTML. Only called for $mOutputType == OT_HTML.
665 function internalParse( $text, $linestart, $args = array(), $isMain=true ) {
668 $fname = 'Parser::internalParse';
669 wfProfileIn( $fname );
671 $text = $this->removeHTMLtags( $text );
672 $text = $this->replaceVariables( $text, $args );
674 $text = preg_replace( '/(^|\n)-----*/', '\\1<hr />', $text );
676 $text = $this->doHeadings( $text );
677 if($this->mOptions
->getUseDynamicDates()) {
678 global $wgDateFormatter;
679 $text = $wgDateFormatter->reformat( $this->mOptions
->getDateFormat(), $text );
681 $text = $this->doAllQuotes( $text );
682 $text = $this->replaceExternalLinks( $text );
683 $text = $this->replaceInternalLinks ( $text );
684 $text = $this->doMagicLinks( $text );
685 $text = $this->doTableStuff( $text );
686 $text = $this->formatHeadings( $text, $isMain );
687 $sk =& $this->mOptions
->getSkin();
688 $text = $sk->transformContent( $text );
690 wfProfileOut( $fname );
695 * Replace special strings like "ISBN xxx" and "RFC xxx" with
696 * magic external links.
700 function &doMagicLinks( &$text ) {
701 global $wgUseGeoMode;
702 $text = $this->magicISBN( $text );
703 if ( isset( $wgUseGeoMode ) && $wgUseGeoMode ) {
704 $text = $this->magicGEO( $text );
706 $text = $this->magicRFC( $text );
711 * Parse ^^ tokens and return html
715 function doExponent ( $text ) {
716 $fname = 'Parser::doExponent';
717 wfProfileIn( $fname);
718 $text = preg_replace('/\^\^(.*)\^\^/','<small><sup>\\1</sup></small>', $text);
719 wfProfileOut( $fname);
724 * Parse headers and return html
728 function doHeadings( $text ) {
729 $fname = 'Parser::doHeadings';
730 wfProfileIn( $fname );
731 for ( $i = 6; $i >= 1; --$i ) {
732 $h = substr( '======', 0, $i );
733 $text = preg_replace( "/^{$h}(.+){$h}(\\s|$)/m",
734 "<h{$i}>\\1</h{$i}>\\2", $text );
736 wfProfileOut( $fname );
741 * Replace single quotes with HTML markup
743 * @return string the altered text
745 function doAllQuotes( $text ) {
746 $fname = 'Parser::doAllQuotes';
747 wfProfileIn( $fname );
749 $lines = explode( "\n", $text );
750 foreach ( $lines as $line ) {
751 $outtext .= $this->doQuotes ( $line ) . "\n";
753 $outtext = substr($outtext, 0,-1);
754 wfProfileOut( $fname );
759 * Helper function for doAllQuotes()
762 function doQuotes( $text ) {
763 $arr = preg_split ("/(''+)/", $text, -1, PREG_SPLIT_DELIM_CAPTURE
);
764 if (count ($arr) == 1)
768 # First, do some preliminary work. This may shift some apostrophes from
769 # being mark-up to being text. It also counts the number of occurrences
770 # of bold and italics mark-ups.
778 # If there are ever four apostrophes, assume the first is supposed to
779 # be text, and the remaining three constitute mark-up for bold text.
780 if (strlen ($arr[$i]) == 4)
785 # If there are more than 5 apostrophes in a row, assume they're all
786 # text except for the last 5.
787 else if (strlen ($arr[$i]) > 5)
789 $arr[$i-1] .= str_repeat ("'", strlen ($arr[$i]) - 5);
792 # Count the number of occurrences of bold and italics mark-ups.
793 # We are not counting sequences of five apostrophes.
794 if (strlen ($arr[$i]) == 2) $numitalics++
; else
795 if (strlen ($arr[$i]) == 3) $numbold++
; else
796 if (strlen ($arr[$i]) == 5) { $numitalics++
; $numbold++
; }
801 # If there is an odd number of both bold and italics, it is likely
802 # that one of the bold ones was meant to be an apostrophe followed
803 # by italics. Which one we cannot know for certain, but it is more
804 # likely to be one that has a single-letter word before it.
805 if (($numbold %
2 == 1) && ($numitalics %
2 == 1))
808 $firstsingleletterword = -1;
809 $firstmultiletterword = -1;
813 if (($i %
2 == 1) and (strlen ($r) == 3))
815 $x1 = substr ($arr[$i-1], -1);
816 $x2 = substr ($arr[$i-1], -2, 1);
818 if ($firstspace == -1) $firstspace = $i;
819 } else if ($x2 == ' ') {
820 if ($firstsingleletterword == -1) $firstsingleletterword = $i;
822 if ($firstmultiletterword == -1) $firstmultiletterword = $i;
828 # If there is a single-letter word, use it!
829 if ($firstsingleletterword > -1)
831 $arr [ $firstsingleletterword ] = "''";
832 $arr [ $firstsingleletterword-1 ] .= "'";
834 # If not, but there's a multi-letter word, use that one.
835 else if ($firstmultiletterword > -1)
837 $arr [ $firstmultiletterword ] = "''";
838 $arr [ $firstmultiletterword-1 ] .= "'";
840 # ... otherwise use the first one that has neither.
841 # (notice that it is possible for all three to be -1 if, for example,
842 # there is only one pentuple-apostrophe in the line)
843 else if ($firstspace > -1)
845 $arr [ $firstspace ] = "''";
846 $arr [ $firstspace-1 ] .= "'";
850 # Now let's actually convert our apostrophic mush to HTML!
859 if ($state == 'both')
866 if (strlen ($r) == 2)
869 { $output .= '</i>'; $state = ''; }
870 else if ($state == 'bi')
871 { $output .= '</i>'; $state = 'b'; }
872 else if ($state == 'ib')
873 { $output .= '</b></i><b>'; $state = 'b'; }
874 else if ($state == 'both')
875 { $output .= '<b><i>'.$buffer.'</i>'; $state = 'b'; }
876 else # $state can be 'b' or ''
877 { $output .= '<i>'; $state .= 'i'; }
879 else if (strlen ($r) == 3)
882 { $output .= '</b>'; $state = ''; }
883 else if ($state == 'bi')
884 { $output .= '</i></b><i>'; $state = 'i'; }
885 else if ($state == 'ib')
886 { $output .= '</b>'; $state = 'i'; }
887 else if ($state == 'both')
888 { $output .= '<i><b>'.$buffer.'</b>'; $state = 'i'; }
889 else # $state can be 'i' or ''
890 { $output .= '<b>'; $state .= 'b'; }
892 else if (strlen ($r) == 5)
895 { $output .= '</b><i>'; $state = 'i'; }
896 else if ($state == 'i')
897 { $output .= '</i><b>'; $state = 'b'; }
898 else if ($state == 'bi')
899 { $output .= '</i></b>'; $state = ''; }
900 else if ($state == 'ib')
901 { $output .= '</b></i>'; $state = ''; }
902 else if ($state == 'both')
903 { $output .= '<i><b>'.$buffer.'</b></i>'; $state = ''; }
904 else # ($state == '')
905 { $buffer = ''; $state = 'both'; }
910 # Now close all remaining tags. Notice that the order is important.
911 if ($state == 'b' ||
$state == 'ib')
913 if ($state == 'i' ||
$state == 'bi' ||
$state == 'ib')
917 if ($state == 'both')
918 $output .= '<b><i>'.$buffer.'</i></b>';
924 * Replace external links
926 * Note: we have to do external links before the internal ones,
927 * and otherwise take great care in the order of things here, so
928 * that we don't end up interpreting some URLs twice.
932 function replaceExternalLinks( $text ) {
933 $fname = 'Parser::replaceExternalLinks';
934 wfProfileIn( $fname );
936 $sk =& $this->mOptions
->getSkin();
937 $linktrail = wfMsgForContent('linktrail');
938 $bits = preg_split( EXT_LINK_BRACKETED
, $text, -1, PREG_SPLIT_DELIM_CAPTURE
);
940 $s = $this->replaceFreeExternalLinks( array_shift( $bits ) );
943 while ( $i<count( $bits ) ) {
945 $protocol = $bits[$i++
];
947 $trail = $bits[$i++
];
949 # The characters '<' and '>' (which were escaped by
950 # removeHTMLtags()) should not be included in
951 # URLs, per RFC 2396.
952 if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE
)) {
953 $text = substr($url, $m2[0][1]) . ' ' . $text;
954 $url = substr($url, 0, $m2[0][1]);
957 # If the link text is an image URL, replace it with an <img> tag
958 # This happened by accident in the original parser, but some people used it extensively
959 $img = $this->maybeMakeImageLink( $text );
960 if ( $img !== false ) {
966 # No link text, e.g. [http://domain.tld/some.link]
968 # Autonumber if allowed
969 if ( strpos( HTTP_PROTOCOLS
, $protocol ) !== false ) {
970 $text = '[' . ++
$this->mAutonumber
. ']';
972 # Otherwise just use the URL
973 $text = htmlspecialchars( $url );
976 # Have link text, e.g. [http://domain.tld/some.link text]s
978 if ( preg_match( $linktrail, $trail, $m2 ) ) {
984 $encUrl = htmlspecialchars( $url );
985 # Bit in parentheses showing the URL for the printable version
986 if( $url == $text ||
preg_match( "!$protocol://" . preg_quote( $text, '/' ) . "/?$!", $url ) ) {
989 # Expand the URL for printable version
990 if ( ! $sk->suppressUrlExpansion() ) {
991 $paren = "<span class='urlexpansion'> (<i>" . htmlspecialchars ( $encUrl ) . "</i>)</span>";
997 # Process the trail (i.e. everything after this link up until start of the next link),
998 # replacing any non-bracketed links
999 $trail = $this->replaceFreeExternalLinks( $trail );
1001 # Use the encoded URL
1002 # This means that users can paste URLs directly into the text
1003 # Funny characters like ö aren't valid in URLs anyway
1004 # This was changed in August 2004
1005 $s .= $sk->makeExternalLink( $url, $text, false ) . $dtrail. $paren . $trail;
1008 wfProfileOut( $fname );
1013 * Replace anything that looks like a URL with a link
1016 function replaceFreeExternalLinks( $text ) {
1017 $bits = preg_split( '/((?:'.URL_PROTOCOLS
.'):)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE
);
1018 $s = array_shift( $bits );
1021 $sk =& $this->mOptions
->getSkin();
1023 while ( $i < count( $bits ) ){
1024 $protocol = $bits[$i++
];
1025 $remainder = $bits[$i++
];
1027 if ( preg_match( '/^('.EXT_LINK_URL_CLASS
.'+)(.*)$/s', $remainder, $m ) ) {
1028 # Found some characters after the protocol that look promising
1029 $url = $protocol . $m[1];
1032 # The characters '<' and '>' (which were escaped by
1033 # removeHTMLtags()) should not be included in
1034 # URLs, per RFC 2396.
1035 if (preg_match('/&(lt|gt);/', $url, $m2, PREG_OFFSET_CAPTURE
)) {
1036 $trail = substr($url, $m2[0][1]) . $trail;
1037 $url = substr($url, 0, $m2[0][1]);
1040 # Move trailing punctuation to $trail
1042 # If there is no left bracket, then consider right brackets fair game too
1043 if ( strpos( $url, '(' ) === false ) {
1047 $numSepChars = strspn( strrev( $url ), $sep );
1048 if ( $numSepChars ) {
1049 $trail = substr( $url, -$numSepChars ) . $trail;
1050 $url = substr( $url, 0, -$numSepChars );
1053 # Replace & from obsolete syntax with &.
1054 # All HTML entities will be escaped by makeExternalLink()
1055 # or maybeMakeImageLink()
1056 $url = str_replace( '&', '&', $url );
1058 # Is this an external image?
1059 $text = $this->maybeMakeImageLink( $url );
1060 if ( $text === false ) {
1061 # Not an image, make a link
1062 $text = $sk->makeExternalLink( $url, $url );
1064 $s .= $text . $trail;
1066 $s .= $protocol . $remainder;
1073 * make an image if it's allowed
1076 function maybeMakeImageLink( $url ) {
1077 $sk =& $this->mOptions
->getSkin();
1079 if ( $this->mOptions
->getAllowExternalImages() ) {
1080 if ( preg_match( EXT_IMAGE_REGEX
, $url ) ) {
1082 $text = $sk->makeImage( htmlspecialchars( $url ) );
1089 * Process [[ ]] wikilinks
1094 function replaceInternalLinks( $s ) {
1095 global $wgLang, $wgContLang, $wgLinkCache;
1096 global $wgDisableLangConversion;
1097 static $fname = 'Parser::replaceInternalLinks' ;
1099 wfProfileIn( $fname );
1101 wfProfileIn( $fname.'-setup' );
1103 # the % is needed to support urlencoded titles as well
1104 if ( !$tc ) { $tc = Title
::legalChars() . '#%'; }
1106 $sk =& $this->mOptions
->getSkin();
1107 global $wgUseOldExistenceCheck;
1108 # "Post-parse link colour check" works only on wiki text since it's now
1109 # in Parser. Enable it, then disable it when we're done.
1110 $saveParseColour = $sk->postParseLinkColour( !$wgUseOldExistenceCheck );
1112 $redirect = MagicWord
::get ( MAG_REDIRECT
) ;
1114 #split the entire text string on occurences of [[
1115 $a = explode( '[[', ' ' . $s );
1116 #get the first element (all text up to first [[), and remove the space we added
1117 $s = array_shift( $a );
1118 $s = substr( $s, 1 );
1120 # Match a link having the form [[namespace:link|alternate]]trail
1122 if ( !$e1 ) { $e1 = "/^([{$tc}]+)(?:\\|([^]]+))?]](.*)\$/sD"; }
1123 # Match cases where there is no "]]", which might still be images
1124 static $e1_img = FALSE;
1125 if ( !$e1_img ) { $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; }
1126 # Match the end of a line for a word that's not followed by whitespace,
1127 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
1128 static $e2 = '/^(.*?)([a-zA-Z\x80-\xff]+)$/sD';
1130 $useLinkPrefixExtension = $wgContLang->linkPrefixExtension();
1132 $nottalk = !Namespace::isTalk( $this->mTitle
->getNamespace() );
1134 if ( $useLinkPrefixExtension ) {
1135 if ( preg_match( $e2, $s, $m ) ) {
1136 $first_prefix = $m[2];
1139 $first_prefix = false;
1145 wfProfileOut( $fname.'-setup' );
1147 $checkVariantLink = sizeof($wgContLang->getVariants())>1;
1148 # Loop for each link
1149 for ($k = 0; isset( $a[$k] ); $k++
) {
1151 wfProfileIn( $fname.'-prefixhandling' );
1152 if ( $useLinkPrefixExtension ) {
1153 if ( preg_match( $e2, $s, $m ) ) {
1161 $prefix = $first_prefix;
1162 $first_prefix = false;
1165 wfProfileOut( $fname.'-prefixhandling' );
1167 $might_be_img = false;
1169 if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
1171 # fix up urlencoded title texts
1172 if(preg_match('/%/', $m[1] )) $m[1] = urldecode($m[1]);
1174 } elseif( preg_match($e1_img, $line, $m) ) { # Invalid, but might be an image with a link in its caption
1175 $might_be_img = true;
1177 if(preg_match('/%/', $m[1] )) $m[1] = urldecode($m[1]);
1179 } else { # Invalid form; output directly
1180 $s .= $prefix . '[[' . $line ;
1184 # Don't allow internal links to pages containing
1185 # PROTO: where PROTO is a valid URL protocol; these
1186 # should be external links.
1187 if (preg_match('/((?:'.URL_PROTOCOLS
.'):)/', $m[1])) {
1188 $s .= $prefix . '[[' . $line ;
1192 # Make subpage if necessary
1193 $link = $this->maybeDoSubpageLink( $m[1], $text );
1195 $noforce = (substr($m[1], 0, 1) != ':');
1197 # Strip off leading ':'
1198 $link = substr($link, 1);
1201 $nt = Title
::newFromText( $this->unstripNoWiki($link, $this->mStripState
) );
1203 $s .= $prefix . '[[' . $line;
1207 #check other language variants of the link
1208 #if the article does not exist
1209 if( $nt->getArticleID() == 0
1210 && $checkVariantLink ) {
1211 $wgContLang->findVariantLink($link, $nt);
1214 $ns = $nt->getNamespace();
1215 $iw = $nt->getInterWiki();
1217 if ($might_be_img) { # if this is actually an invalid link
1218 if ($ns == NS_IMAGE
&& $noforce) { #but might be an image
1220 while (isset ($a[$k+
1]) ) {
1221 #look at the next 'line' to see if we can close it there
1222 $next_line = array_shift(array_splice( $a, $k +
1, 1) );
1223 if( preg_match("/^(.*?]].*?)]](.*)$/sD", $next_line, $m) ) {
1224 # the first ]] closes the inner link, the second the image
1226 $text .= '[[' . $m[1];
1229 } elseif( preg_match("/^.*?]].*$/sD", $next_line, $m) ) {
1230 #if there's exactly one ]] that's fine, we'll keep looking
1231 $text .= '[[' . $m[0];
1233 #if $next_line is invalid too, we need look no further
1234 $text .= '[[' . $next_line;
1239 # we couldn't find the end of this imageLink, so output it raw
1240 #but don't ignore what might be perfectly normal links in the text we've examined
1241 $text = $this->replaceInternalLinks($text);
1242 $s .= $prefix . '[[' . $link . '|' . $text;
1243 # note: no $trail, because without an end, there *is* no trail
1246 } else { #it's not an image, so output it raw
1247 $s .= $prefix . '[[' . $link . '|' . $text;
1248 # note: no $trail, because without an end, there *is* no trail
1253 $wasblank = ( '' == $text );
1254 if( $wasblank ) $text = $link;
1257 # Link not escaped by : , create the various objects
1261 if( $iw && $this->mOptions
->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) {
1262 array_push( $this->mOutput
->mLanguageLinks
, $nt->getFullText() );
1263 $tmp = $prefix . $trail ;
1264 $s .= (trim($tmp) == '')?
'': $tmp;
1268 if ( $ns == NS_IMAGE
) {
1269 # recursively parse links inside the image caption
1270 # actually, this will parse them in any other parameters, too,
1271 # but it might be hard to fix that, and it doesn't matter ATM
1272 $text = $this->replaceExternalLinks($text);
1273 $text = $this->replaceInternalLinks($text);
1275 # replace the image with a link-holder so that replaceExternalLinks() can't mess with it
1276 $s .= $prefix . $this->insertStripItem( $sk->makeImageLinkObj( $nt, $text ), $this->mStripState
) . $trail;
1277 $wgLinkCache->addImageLinkObj( $nt );
1281 if ( $ns == NS_CATEGORY
) {
1282 $t = $nt->getText() ;
1284 $wgLinkCache->suspend(); # Don't save in links/brokenlinks
1285 $pPLC=$sk->postParseLinkColour();
1286 $sk->postParseLinkColour( false );
1287 $t = $sk->makeLinkObj( $nt, $t, '', '' , $prefix );
1288 $sk->postParseLinkColour( $pPLC );
1289 $wgLinkCache->resume();
1292 if ( $this->mTitle
->getNamespace() == NS_CATEGORY
) {
1293 $sortkey = $this->mTitle
->getText();
1295 $sortkey = $this->mTitle
->getPrefixedText();
1300 $wgLinkCache->addCategoryLinkObj( $nt, $sortkey );
1301 $this->mOutput
->mCategoryLinks
[] = $t ;
1302 $s .= $prefix . $trail ;
1307 if( ( $nt->getPrefixedText() === $this->mTitle
->getPrefixedText() ) &&
1308 ( $nt->getFragment() === '' ) ) {
1309 # Self-links are handled specially; generally de-link and change to bold.
1310 $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail );
1314 # Special and Media are pseudo-namespaces; no pages actually exist in them
1315 if( $ns == NS_MEDIA
) {
1316 $s .= $prefix . $sk->makeMediaLinkObj( $nt, $text ) . $trail;
1317 $wgLinkCache->addImageLinkObj( $nt );
1319 } elseif( $ns == NS_SPECIAL
) {
1320 $s .= $prefix . $sk->makeKnownLinkObj( $nt, $text, '', $trail );
1323 $s .= $sk->makeLinkObj( $nt, $text, '', $trail, $prefix );
1325 $sk->postParseLinkColour( $saveParseColour );
1326 wfProfileOut( $fname );
1331 * Handle link to subpage if necessary
1332 * @param string $target the source of the link
1333 * @param string &$text the link text, modified as necessary
1334 * @return string the full name of the link
1337 function maybeDoSubpageLink($target, &$text) {
1340 # :Foobar -- override special treatment of prefix (images, language links)
1341 # /Foobar -- convert to CurrentPage/Foobar
1342 # /Foobar/ -- convert to CurrentPage/Foobar, strip the initial / from text
1343 global $wgNamespacesWithSubpages;
1345 $fname = 'Parser::maybeDoSubpageLink';
1346 wfProfileIn( $fname );
1347 # Look at the first character
1348 if( $target{0} == '/' ) {
1349 # / at end means we don't want the slash to be shown
1350 if(substr($target,-1,1)=='/') {
1351 $target=substr($target,1,-1);
1354 $noslash=substr($target,1);
1357 # Some namespaces don't allow subpages
1358 if(!empty($wgNamespacesWithSubpages[$this->mTitle
->getNamespace()])) {
1359 # subpages allowed here
1360 $ret = $this->mTitle
->getPrefixedText(). '/' . trim($noslash);
1361 if( '' === $text ) {
1363 } # this might be changed for ugliness reasons
1365 # no subpage allowed, use standard link
1373 wfProfileOut( $fname );
1378 * Used by doBlockLevels()
1381 /* private */ function closeParagraph() {
1383 if ( '' != $this->mLastSection
) {
1384 $result = '</' . $this->mLastSection
. ">\n";
1386 $this->mInPre
= false;
1387 $this->mLastSection
= '';
1390 # getCommon() returns the length of the longest common substring
1391 # of both arguments, starting at the beginning of both.
1393 /* private */ function getCommon( $st1, $st2 ) {
1394 $fl = strlen( $st1 );
1395 $shorter = strlen( $st2 );
1396 if ( $fl < $shorter ) { $shorter = $fl; }
1398 for ( $i = 0; $i < $shorter; ++
$i ) {
1399 if ( $st1{$i} != $st2{$i} ) { break; }
1403 # These next three functions open, continue, and close the list
1404 # element appropriate to the prefix character passed into them.
1406 /* private */ function openList( $char ) {
1407 $result = $this->closeParagraph();
1409 if ( '*' == $char ) { $result .= '<ul><li>'; }
1410 else if ( '#' == $char ) { $result .= '<ol><li>'; }
1411 else if ( ':' == $char ) { $result .= '<dl><dd>'; }
1412 else if ( ';' == $char ) {
1413 $result .= '<dl><dt>';
1414 $this->mDTopen
= true;
1416 else { $result = '<!-- ERR 1 -->'; }
1421 /* private */ function nextItem( $char ) {
1422 if ( '*' == $char ||
'#' == $char ) { return '</li><li>'; }
1423 else if ( ':' == $char ||
';' == $char ) {
1425 if ( $this->mDTopen
) { $close = '</dt>'; }
1426 if ( ';' == $char ) {
1427 $this->mDTopen
= true;
1428 return $close . '<dt>';
1430 $this->mDTopen
= false;
1431 return $close . '<dd>';
1434 return '<!-- ERR 2 -->';
1437 /* private */ function closeList( $char ) {
1438 if ( '*' == $char ) { $text = '</li></ul>'; }
1439 else if ( '#' == $char ) { $text = '</li></ol>'; }
1440 else if ( ':' == $char ) {
1441 if ( $this->mDTopen
) {
1442 $this->mDTopen
= false;
1443 $text = '</dt></dl>';
1445 $text = '</dd></dl>';
1448 else { return '<!-- ERR 3 -->'; }
1454 * Make lists from lines starting with ':', '*', '#', etc.
1457 * @return string the lists rendered as HTML
1459 function doBlockLevels( $text, $linestart ) {
1460 $fname = 'Parser::doBlockLevels';
1461 wfProfileIn( $fname );
1463 # Parsing through the text line by line. The main thing
1464 # happening here is handling of block-level elements p, pre,
1465 # and making lists from lines starting with * # : etc.
1467 $textLines = explode( "\n", $text );
1469 $lastPrefix = $output = $lastLine = '';
1470 $this->mDTopen
= $inBlockElem = false;
1472 $paragraphStack = false;
1474 if ( !$linestart ) {
1475 $output .= array_shift( $textLines );
1477 foreach ( $textLines as $oLine ) {
1478 $lastPrefixLength = strlen( $lastPrefix );
1479 $preCloseMatch = preg_match('/<\\/pre/i', $oLine );
1480 $preOpenMatch = preg_match('/<pre/i', $oLine );
1481 if ( !$this->mInPre
) {
1482 # Multiple prefixes may abut each other for nested lists.
1483 $prefixLength = strspn( $oLine, '*#:;' );
1484 $pref = substr( $oLine, 0, $prefixLength );
1487 $pref2 = str_replace( ';', ':', $pref );
1488 $t = substr( $oLine, $prefixLength );
1489 $this->mInPre
= !empty($preOpenMatch);
1491 # Don't interpret any other prefixes in preformatted text
1493 $pref = $pref2 = '';
1498 if( $prefixLength && 0 == strcmp( $lastPrefix, $pref2 ) ) {
1499 # Same as the last item, so no need to deal with nesting or opening stuff
1500 $output .= $this->nextItem( substr( $pref, -1 ) );
1501 $paragraphStack = false;
1503 if ( substr( $pref, -1 ) == ';') {
1504 # The one nasty exception: definition lists work like this:
1505 # ; title : definition text
1506 # So we check for : in the remainder text to split up the
1507 # title and definition, without b0rking links.
1508 if ($this->findColonNoLinks($t, $term, $t2) !== false) {
1510 $output .= $term . $this->nextItem( ':' );
1513 } elseif( $prefixLength ||
$lastPrefixLength ) {
1514 # Either open or close a level...
1515 $commonPrefixLength = $this->getCommon( $pref, $lastPrefix );
1516 $paragraphStack = false;
1518 while( $commonPrefixLength < $lastPrefixLength ) {
1519 $output .= $this->closeList( $lastPrefix{$lastPrefixLength-1} );
1520 --$lastPrefixLength;
1522 if ( $prefixLength <= $commonPrefixLength && $commonPrefixLength > 0 ) {
1523 $output .= $this->nextItem( $pref{$commonPrefixLength-1} );
1525 while ( $prefixLength > $commonPrefixLength ) {
1526 $char = substr( $pref, $commonPrefixLength, 1 );
1527 $output .= $this->openList( $char );
1529 if ( ';' == $char ) {
1530 # FIXME: This is dupe of code above
1531 if ($this->findColonNoLinks($t, $term, $t2) !== false) {
1533 $output .= $term . $this->nextItem( ':' );
1536 ++
$commonPrefixLength;
1538 $lastPrefix = $pref2;
1540 if( 0 == $prefixLength ) {
1541 # No prefix (not in list)--go to paragraph mode
1542 $uniq_prefix = UNIQ_PREFIX
;
1543 // XXX: use a stack for nestable elements like span, table and div
1544 $openmatch = preg_match('/(<table|<blockquote|<h1|<h2|<h3|<h4|<h5|<h6|<pre|<tr|<p|<ul|<li|<\\/tr|<\\/td|<\\/th)/i', $t );
1545 $closematch = preg_match(
1546 '/(<\\/table|<\\/blockquote|<\\/h1|<\\/h2|<\\/h3|<\\/h4|<\\/h5|<\\/h6|'.
1547 '<td|<th|<div|<\\/div|<hr|<\\/pre|<\\/p|'.$uniq_prefix.'-pre|<\\/li|<\\/ul)/i', $t );
1548 if ( $openmatch or $closematch ) {
1549 $paragraphStack = false;
1550 $output .= $this->closeParagraph();
1551 if($preOpenMatch and !$preCloseMatch) {
1552 $this->mInPre
= true;
1554 if ( $closematch ) {
1555 $inBlockElem = false;
1557 $inBlockElem = true;
1559 } else if ( !$inBlockElem && !$this->mInPre
) {
1560 if ( ' ' == $t{0} and ( $this->mLastSection
== 'pre' or trim($t) != '' ) ) {
1562 if ($this->mLastSection
!= 'pre') {
1563 $paragraphStack = false;
1564 $output .= $this->closeParagraph().'<pre>';
1565 $this->mLastSection
= 'pre';
1567 $t = substr( $t, 1 );
1570 if ( '' == trim($t) ) {
1571 if ( $paragraphStack ) {
1572 $output .= $paragraphStack.'<br />';
1573 $paragraphStack = false;
1574 $this->mLastSection
= 'p';
1576 if ($this->mLastSection
!= 'p' ) {
1577 $output .= $this->closeParagraph();
1578 $this->mLastSection
= '';
1579 $paragraphStack = '<p>';
1581 $paragraphStack = '</p><p>';
1585 if ( $paragraphStack ) {
1586 $output .= $paragraphStack;
1587 $paragraphStack = false;
1588 $this->mLastSection
= 'p';
1589 } else if ($this->mLastSection
!= 'p') {
1590 $output .= $this->closeParagraph().'<p>';
1591 $this->mLastSection
= 'p';
1597 if ($paragraphStack === false) {
1601 while ( $prefixLength ) {
1602 $output .= $this->closeList( $pref2{$prefixLength-1} );
1605 if ( '' != $this->mLastSection
) {
1606 $output .= '</' . $this->mLastSection
. '>';
1607 $this->mLastSection
= '';
1610 wfProfileOut( $fname );
1615 * Split up a string on ':', ignoring any occurences inside
1616 * <a>..</a> or <span>...</span>
1617 * @param string $str the string to split
1618 * @param string &$before set to everything before the ':'
1619 * @param string &$after set to everything after the ':'
1620 * return string the position of the ':', or false if none found
1622 function findColonNoLinks($str, &$before, &$after) {
1623 # I wonder if we should make this count all tags, not just <a>
1624 # and <span>. That would prevent us from matching a ':' that
1625 # comes in the middle of italics other such formatting....
1627 $fname = 'Parser::findColonNoLinks';
1628 wfProfileIn( $fname );
1631 $colon = strpos($str, ':', $pos);
1633 if ($colon !== false) {
1634 $before = substr($str, 0, $colon);
1635 $after = substr($str, $colon +
1);
1637 # Skip any ':' within <a> or <span> pairs
1638 $a = substr_count($before, '<a');
1639 $s = substr_count($before, '<span');
1640 $ca = substr_count($before, '</a>');
1641 $cs = substr_count($before, '</span>');
1643 if ($a <= $ca and $s <= $cs) {
1644 # Tags are balanced before ':'; ok
1649 } while ($colon !== false);
1650 wfProfileOut( $fname );
1655 * Return value of a magic variable (like PAGENAME)
1659 function getVariableValue( $index ) {
1660 global $wgContLang, $wgSitename, $wgServer;
1663 case MAG_CURRENTMONTH
:
1664 return $wgContLang->formatNum( date( 'm' ) );
1665 case MAG_CURRENTMONTHNAME
:
1666 return $wgContLang->getMonthName( date('n') );
1667 case MAG_CURRENTMONTHNAMEGEN
:
1668 return $wgContLang->getMonthNameGen( date('n') );
1669 case MAG_CURRENTDAY
:
1670 return $wgContLang->formatNum( date('j') );
1672 return $this->mTitle
->getText();
1674 return $this->mTitle
->getPartialURL();
1676 # return Namespace::getCanonicalName($this->mTitle->getNamespace());
1677 return $wgContLang->getNsText($this->mTitle
->getNamespace()); # Patch by Dori
1678 case MAG_CURRENTDAYNAME
:
1679 return $wgContLang->getWeekdayName( date('w')+
1 );
1680 case MAG_CURRENTYEAR
:
1681 return $wgContLang->formatNum( date( 'Y' ) );
1682 case MAG_CURRENTTIME
:
1683 return $wgContLang->time( wfTimestampNow(), false );
1684 case MAG_NUMBEROFARTICLES
:
1685 return $wgContLang->formatNum( wfNumberOfArticles() );
1696 * initialise the magic variables (like CURRENTMONTHNAME)
1700 function initialiseVariables() {
1701 $fname = 'Parser::initialiseVariables';
1702 wfProfileIn( $fname );
1703 global $wgVariableIDs;
1704 $this->mVariables
= array();
1705 foreach ( $wgVariableIDs as $id ) {
1706 $mw =& MagicWord
::get( $id );
1707 $mw->addToArray( $this->mVariables
, $this->getVariableValue( $id ) );
1709 wfProfileOut( $fname );
1713 * Replace magic variables, templates, and template arguments
1714 * with the appropriate text. Templates are substituted recursively,
1715 * taking care to avoid infinite loops.
1717 * Note that the substitution depends on value of $mOutputType:
1718 * OT_WIKI: only {{subst:}} templates
1719 * OT_MSG: only magic variables
1720 * OT_HTML: all templates and magic variables
1722 * @param string $tex The text to transform
1723 * @param array $args Key-value pairs representing template parameters to substitute
1726 function replaceVariables( $text, $args = array() ) {
1727 global $wgLang, $wgScript, $wgArticlePath;
1729 # Prevent too big inclusions
1730 if(strlen($text)> MAX_INCLUDE_SIZE
)
1733 $fname = 'Parser::replaceVariables';
1734 wfProfileIn( $fname );
1736 $titleChars = Title
::legalChars();
1738 # This function is called recursively. To keep track of arguments we need a stack:
1739 array_push( $this->mArgStack
, $args );
1741 # Variable substitution
1742 $text = preg_replace_callback( "/{{([$titleChars]*?)}}/", array( &$this, 'variableSubstitution' ), $text );
1744 if ( $this->mOutputType
== OT_HTML ||
$this->mOutputType
== OT_WIKI
) {
1745 # Argument substitution
1746 $text = preg_replace_callback( "/{{{([$titleChars]*?)}}}/", array( &$this, 'argSubstitution' ), $text );
1748 # Template substitution
1749 $regex = '/(\\n|{)?{{(['.$titleChars.']*)(\\|.*?|)}}/s';
1750 $text = preg_replace_callback( $regex, array( &$this, 'braceSubstitution' ), $text );
1752 array_pop( $this->mArgStack
);
1754 wfProfileOut( $fname );
1759 * Replace magic variables
1762 function variableSubstitution( $matches ) {
1763 if ( !$this->mVariables
) {
1764 $this->initialiseVariables();
1767 if ( $this->mOutputType
== OT_WIKI
) {
1768 # Do only magic variables prefixed by SUBST
1769 $mwSubst =& MagicWord
::get( MAG_SUBST
);
1770 if (!$mwSubst->matchStartAndRemove( $matches[1] ))
1772 # Note that if we don't substitute the variable below,
1773 # we don't remove the {{subst:}} magic word, in case
1774 # it is a template rather than a magic variable.
1776 if ( !$skip && array_key_exists( $matches[1], $this->mVariables
) ) {
1777 $text = $this->mVariables
[$matches[1]];
1778 $this->mOutput
->mContainsOldMagic
= true;
1780 $text = $matches[0];
1785 # Split template arguments
1786 function getTemplateArgs( $argsString ) {
1787 if ( $argsString === '' ) {
1791 $args = explode( '|', substr( $argsString, 1 ) );
1793 # If any of the arguments contains a '[[' but no ']]', it needs to be
1794 # merged with the next arg because the '|' character between belongs
1795 # to the link syntax and not the template parameter syntax.
1796 $argc = count($args);
1798 for ( $i = 0; $i < $argc-1; $i++
) {
1799 if ( substr_count ( $args[$i], '[[' ) != substr_count ( $args[$i], ']]' ) ) {
1800 $args[$i] .= '|'.$args[$i+
1];
1801 array_splice($args, $i+
1, 1);
1811 * Return the text of a template, after recursively
1812 * replacing any variables or templates within the template.
1814 * @param array $matches The parts of the template
1815 * $matches[1]: the title, i.e. the part before the |
1816 * $matches[2]: the parameters (including a leading |), if any
1817 * @return string the text of the template
1820 function braceSubstitution( $matches ) {
1821 global $wgLinkCache, $wgContLang;
1822 $fname = 'Parser::braceSubstitution';
1829 # Need to know if the template comes at the start of a line,
1830 # to treat the beginning of the template like the beginning
1831 # of a line for tables and block-level elements.
1832 $linestart = $matches[1];
1834 # $part1 is the bit before the first |, and must contain only title characters
1835 # $args is a list of arguments, starting from index 0, not including $part1
1837 $part1 = $matches[2];
1838 # If the third subpattern matched anything, it will start with |
1840 $args = $this->getTemplateArgs($matches[3]);
1841 $argc = count( $args );
1843 # Don't parse {{{}}} because that's only for template arguments
1844 if ( $linestart === '{' ) {
1845 $text = $matches[0];
1852 $mwSubst =& MagicWord
::get( MAG_SUBST
);
1853 if ( $mwSubst->matchStartAndRemove( $part1 ) xor ($this->mOutputType
== OT_WIKI
) ) {
1854 # One of two possibilities is true:
1855 # 1) Found SUBST but not in the PST phase
1856 # 2) Didn't find SUBST and in the PST phase
1857 # In either case, return without further processing
1858 $text = $matches[0];
1864 # MSG, MSGNW and INT
1867 $mwMsgnw =& MagicWord
::get( MAG_MSGNW
);
1868 if ( $mwMsgnw->matchStartAndRemove( $part1 ) ) {
1871 # Remove obsolete MSG:
1872 $mwMsg =& MagicWord
::get( MAG_MSG
);
1873 $mwMsg->matchStartAndRemove( $part1 );
1876 # Check if it is an internal message
1877 $mwInt =& MagicWord
::get( MAG_INT
);
1878 if ( $mwInt->matchStartAndRemove( $part1 ) ) {
1879 if ( $this->incrementIncludeCount( 'int:'.$part1 ) ) {
1880 $text = $linestart . wfMsgReal( $part1, $args, true );
1888 # Check for NS: (namespace expansion)
1889 $mwNs = MagicWord
::get( MAG_NS
);
1890 if ( $mwNs->matchStartAndRemove( $part1 ) ) {
1891 if ( intval( $part1 ) ) {
1892 $text = $linestart . $wgContLang->getNsText( intval( $part1 ) );
1895 $index = Namespace::getCanonicalIndex( strtolower( $part1 ) );
1896 if ( !is_null( $index ) ) {
1897 $text = $linestart . $wgContLang->getNsText( $index );
1904 # LOCALURL and LOCALURLE
1906 $mwLocal = MagicWord
::get( MAG_LOCALURL
);
1907 $mwLocalE = MagicWord
::get( MAG_LOCALURLE
);
1909 if ( $mwLocal->matchStartAndRemove( $part1 ) ) {
1910 $func = 'getLocalURL';
1911 } elseif ( $mwLocalE->matchStartAndRemove( $part1 ) ) {
1912 $func = 'escapeLocalURL';
1917 if ( $func !== '' ) {
1918 $title = Title
::newFromText( $part1 );
1919 if ( !is_null( $title ) ) {
1921 $text = $linestart . $title->$func( $args[0] );
1923 $text = $linestart . $title->$func();
1931 if ( !$found && $argc == 1 ) {
1932 $mwGrammar =& MagicWord
::get( MAG_GRAMMAR
);
1933 if ( $mwGrammar->matchStartAndRemove( $part1 ) ) {
1934 $text = $linestart . $wgContLang->convertGrammar( $args[0], $part1 );
1939 # Template table test
1941 # Did we encounter this template already? If yes, it is in the cache
1942 # and we need to check for loops.
1943 if ( !$found && isset( $this->mTemplates
[$part1] ) ) {
1944 # set $text to cached message.
1945 $text = $linestart . $this->mTemplates
[$part1];
1948 # Infinite loop test
1949 if ( isset( $this->mTemplatePath
[$part1] ) ) {
1952 $text .= '<!-- WARNING: template loop detected -->';
1956 # Load from database
1957 $itcamefromthedatabase = false;
1960 $part1 = $this->maybeDoSubpageLink( $part1, $subpage='' );
1961 if ($subpage !== '') {
1962 $ns = $this->mTitle
->getNamespace();
1964 $title = Title
::newFromText( $part1, $ns );
1965 if ( !is_null( $title ) && !$title->isExternal() ) {
1966 # Check for excessive inclusion
1967 $dbk = $title->getPrefixedDBkey();
1968 if ( $this->incrementIncludeCount( $dbk ) ) {
1969 # This should never be reached.
1970 $article = new Article( $title );
1971 $articleContent = $article->getContentWithoutUsingSoManyDamnGlobals();
1972 if ( $articleContent !== false ) {
1974 $text = $linestart . $articleContent;
1975 $itcamefromthedatabase = true;
1979 # If the title is valid but undisplayable, make a link to it
1980 if ( $this->mOutputType
== OT_HTML
&& !$found ) {
1981 $text = $linestart . '[['.$title->getPrefixedText().']]';
1985 # Template cache array insertion
1986 $this->mTemplates
[$part1] = $text;
1990 # Recursive parsing, escaping and link table handling
1991 # Only for HTML output
1992 if ( $nowiki && $found && $this->mOutputType
== OT_HTML
) {
1993 $text = wfEscapeWikiText( $text );
1994 } elseif ( ($this->mOutputType
== OT_HTML ||
$this->mOutputType
== OT_WIKI
) && $found && !$noparse) {
1995 # Clean up argument array
1996 $assocArgs = array();
1998 foreach( $args as $arg ) {
1999 $eqpos = strpos( $arg, '=' );
2000 if ( $eqpos === false ) {
2001 $assocArgs[$index++
] = $arg;
2003 $name = trim( substr( $arg, 0, $eqpos ) );
2004 $value = trim( substr( $arg, $eqpos+
1 ) );
2005 if ( $value === false ) {
2008 if ( $name !== false ) {
2009 $assocArgs[$name] = $value;
2014 # Add a new element to the templace recursion path
2015 $this->mTemplatePath
[$part1] = 1;
2017 $text = $this->strip( $text, $this->mStripState
);
2018 $text = $this->removeHTMLtags( $text );
2019 $text = $this->replaceVariables( $text, $assocArgs );
2021 # Resume the link cache and register the inclusion as a link
2022 if ( $this->mOutputType
== OT_HTML
&& !is_null( $title ) ) {
2023 $wgLinkCache->addLinkObj( $title );
2026 # If the template begins with a table or block-level
2027 # element, it should be treated as beginning a new line.
2028 if ($linestart !== '\n' && preg_match('/^({\\||:|;|#|\*)/', $text)) {
2029 $text = "\n" . $text;
2033 # Empties the template path
2034 $this->mTemplatePath
= array();
2038 # replace ==section headers==
2039 # XXX this needs to go away once we have a better parser.
2040 if ( $this->mOutputType
!= OT_WIKI
&& $itcamefromthedatabase ) {
2041 if( !is_null( $title ) )
2042 $encodedname = base64_encode($title->getPrefixedDBkey());
2044 $encodedname = base64_encode("");
2045 $m = preg_split('/(^={1,6}.*?={1,6}\s*?$)/m', $text, -1,
2046 PREG_SPLIT_DELIM_CAPTURE
);
2049 for( $i = 0; $i < count($m); $i +
= 2 ) {
2051 if (!isset($m[$i +
1]) ||
$m[$i +
1] == "") continue;
2053 if( strstr($hl, "<!--MWTEMPLATESECTION") ) {
2057 preg_match('/^(={1,6})(.*?)(={1,6})\s*?$/m', $hl, $m2);
2058 $text .= $m2[1] . $m2[2] . "<!--MWTEMPLATESECTION="
2059 . $encodedname . "&" . base64_encode("$nsec") . "-->" . $m2[3];
2066 # Empties the template path
2067 $this->mTemplatePath
= array();
2076 * Triple brace replacement -- used for template arguments
2079 function argSubstitution( $matches ) {
2080 $arg = trim( $matches[1] );
2081 $text = $matches[0];
2082 $inputArgs = end( $this->mArgStack
);
2084 if ( array_key_exists( $arg, $inputArgs ) ) {
2085 $text = $inputArgs[$arg];
2092 * Returns true if the function is allowed to include this entity
2095 function incrementIncludeCount( $dbk ) {
2096 if ( !array_key_exists( $dbk, $this->mIncludeCount
) ) {
2097 $this->mIncludeCount
[$dbk] = 0;
2099 if ( ++
$this->mIncludeCount
[$dbk] <= MAX_INCLUDE_REPEAT
) {
2108 * Cleans up HTML, removes dangerous tags and attributes, and
2109 * removes HTML comments
2112 function removeHTMLtags( $text ) {
2113 global $wgUseTidy, $wgUserHtml;
2114 $fname = 'Parser::removeHTMLtags';
2115 wfProfileIn( $fname );
2118 $htmlpairs = array( # Tags that must be closed
2119 'b', 'del', 'i', 'ins', 'u', 'font', 'big', 'small', 'sub', 'sup', 'h1',
2120 'h2', 'h3', 'h4', 'h5', 'h6', 'cite', 'code', 'em', 's',
2121 'strike', 'strong', 'tt', 'var', 'div', 'center',
2122 'blockquote', 'ol', 'ul', 'dl', 'table', 'caption', 'pre',
2123 'ruby', 'rt' , 'rb' , 'rp', 'p'
2125 $htmlsingle = array(
2126 'br', 'hr', 'li', 'dt', 'dd'
2128 $htmlnest = array( # Tags that can be nested--??
2129 'table', 'tr', 'td', 'th', 'div', 'blockquote', 'ol', 'ul',
2130 'dl', 'font', 'big', 'small', 'sub', 'sup'
2132 $tabletags = array( # Can only appear inside table
2136 $htmlpairs = array();
2137 $htmlsingle = array();
2138 $htmlnest = array();
2139 $tabletags = array();
2142 $htmlsingle = array_merge( $tabletags, $htmlsingle );
2143 $htmlelements = array_merge( $htmlsingle, $htmlpairs );
2145 $htmlattrs = $this->getHTMLattrs () ;
2147 # Remove HTML comments
2148 $text = $this->removeHTMLcomments( $text );
2150 $bits = explode( '<', $text );
2151 $text = array_shift( $bits );
2153 $tagstack = array(); $tablestack = array();
2154 foreach ( $bits as $x ) {
2155 $prev = error_reporting( E_ALL
& ~
( E_NOTICE | E_WARNING
) );
2156 preg_match( '/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/',
2158 list( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
2159 error_reporting( $prev );
2162 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
2166 if ( ! in_array( $t, $htmlsingle ) &&
2167 ( $ot = @array_pop
( $tagstack ) ) != $t ) {
2168 @array_push
( $tagstack, $ot );
2171 if ( $t == 'table' ) {
2172 $tagstack = array_pop( $tablestack );
2177 # Keep track for later
2178 if ( in_array( $t, $tabletags ) &&
2179 ! in_array( 'table', $tagstack ) ) {
2181 } else if ( in_array( $t, $tagstack ) &&
2182 ! in_array ( $t , $htmlnest ) ) {
2184 } else if ( ! in_array( $t, $htmlsingle ) ) {
2185 if ( $t == 'table' ) {
2186 array_push( $tablestack, $tagstack );
2187 $tagstack = array();
2189 array_push( $tagstack, $t );
2191 # Strip non-approved attributes from the tag
2192 $newparams = $this->fixTagAttributes($params);
2196 $rest = str_replace( '>', '>', $rest );
2197 $text .= "<$slash$t $newparams$brace$rest";
2201 $text .= '<' . str_replace( '>', '>', $x);
2203 # Close off any remaining tags
2204 while ( is_array( $tagstack ) && ($t = array_pop( $tagstack )) ) {
2206 if ( $t == 'table' ) { $tagstack = array_pop( $tablestack ); }
2209 # this might be possible using tidy itself
2210 foreach ( $bits as $x ) {
2211 preg_match( '/^(\\/?)(\\w+)([^>]*)(\\/{0,1}>)([^<]*)$/',
2213 @list
( $qbar, $slash, $t, $params, $brace, $rest ) = $regs;
2214 if ( in_array( $t = strtolower( $t ), $htmlelements ) ) {
2215 $newparams = $this->fixTagAttributes($params);
2216 $rest = str_replace( '>', '>', $rest );
2217 $text .= "<$slash$t $newparams$brace$rest";
2219 $text .= '<' . str_replace( '>', '>', $x);
2223 wfProfileOut( $fname );
2228 * Remove '<!--', '-->', and everything between.
2229 * To avoid leaving blank lines, when a comment is both preceded
2230 * and followed by a newline (ignoring spaces), trim leading and
2231 * trailing spaces and one of the newlines.
2235 function removeHTMLcomments( $text ) {
2236 $fname='Parser::removeHTMLcomments';
2237 wfProfileIn( $fname );
2238 while (($start = strpos($text, '<!--')) !== false) {
2239 $end = strpos($text, '-->', $start +
4);
2240 if ($end === false) {
2241 # Unterminated comment; bail out
2247 # Trim space and newline if the comment is both
2248 # preceded and followed by a newline
2249 $spaceStart = max($start - 1, 0);
2250 $spaceLen = $end - $spaceStart;
2251 while (substr($text, $spaceStart, 1) === ' ' && $spaceStart > 0) {
2255 while (substr($text, $spaceStart +
$spaceLen, 1) === ' ')
2257 if (substr($text, $spaceStart, 1) === "\n" and substr($text, $spaceStart +
$spaceLen, 1) === "\n") {
2258 # Remove the comment, leading and trailing
2259 # spaces, and leave only one newline.
2260 $text = substr_replace($text, "\n", $spaceStart, $spaceLen +
1);
2263 # Remove just the comment.
2264 $text = substr_replace($text, '', $start, $end - $start);
2267 wfProfileOut( $fname );
2272 * This function accomplishes several tasks:
2273 * 1) Auto-number headings if that option is enabled
2274 * 2) Add an [edit] link to sections for logged in users who have enabled the option
2275 * 3) Add a Table of contents on the top for users who have enabled the option
2276 * 4) Auto-anchor headings
2278 * It loops through all headlines, collects the necessary data, then splits up the
2279 * string and re-inserts the newly formatted headlines.
2282 /* private */ function formatHeadings( $text, $isMain=true ) {
2283 global $wgInputEncoding, $wgMaxTocLevel, $wgContLang, $wgLinkHolders;
2285 $doNumberHeadings = $this->mOptions
->getNumberHeadings();
2286 $doShowToc = $this->mOptions
->getShowToc();
2287 $forceTocHere = false;
2288 if( !$this->mTitle
->userCanEdit() ) {
2290 $rightClickHack = 0;
2292 $showEditLink = $this->mOptions
->getEditSection();
2293 $rightClickHack = $this->mOptions
->getEditSectionOnRightClick();
2296 # Inhibit editsection links if requested in the page
2297 $esw =& MagicWord
::get( MAG_NOEDITSECTION
);
2298 if( $esw->matchAndRemove( $text ) ) {
2301 # if the string __NOTOC__ (not case-sensitive) occurs in the HTML,
2303 $mw =& MagicWord
::get( MAG_NOTOC
);
2304 if( $mw->matchAndRemove( $text ) ) {
2308 # never add the TOC to the Main Page. This is an entry page that should not
2309 # be more than 1-2 screens large anyway
2310 if( $this->mTitle
->getPrefixedText() == wfMsg('mainpage') ) {
2314 # Get all headlines for numbering them and adding funky stuff like [edit]
2315 # links - this is for later, but we need the number of headlines right now
2316 $numMatches = preg_match_all( '/<H([1-6])(.*?' . '>)(.*?)<\/H[1-6]>/i', $text, $matches );
2318 # if there are fewer than 4 headlines in the article, do not show TOC
2319 if( $numMatches < 4 ) {
2323 # if the string __TOC__ (not case-sensitive) occurs in the HTML,
2324 # override above conditions and always show TOC at that place
2325 $mw =& MagicWord
::get( MAG_TOC
);
2326 if ($mw->match( $text ) ) {
2328 $forceTocHere = true;
2330 # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML,
2331 # override above conditions and always show TOC above first header
2332 $mw =& MagicWord
::get( MAG_FORCETOC
);
2333 if ($mw->matchAndRemove( $text ) ) {
2340 # We need this to perform operations on the HTML
2341 $sk =& $this->mOptions
->getSkin();
2345 $sectionCount = 0; # headlineCount excluding template sections
2347 # Ugh .. the TOC should have neat indentation levels which can be
2348 # passed to the skin functions. These are determined here
2353 $sublevelCount = array();
2356 foreach( $matches[3] as $headline ) {
2358 $templatetitle = "";
2359 $templatesection = 0;
2361 if (preg_match("/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/", $headline, $mat)) {
2363 $templatetitle = base64_decode($mat[1]);
2364 $templatesection = 1 +
(int)base64_decode($mat[2]);
2365 $headline = preg_replace("/<!--MWTEMPLATESECTION=([^&]+)&([^_]+)-->/", "", $headline);
2370 $prevlevel = $level;
2372 $level = $matches[1][$headlineCount];
2373 if( ( $doNumberHeadings ||
$doShowToc ) && $prevlevel && $level > $prevlevel ) {
2374 # reset when we enter a new level
2375 $sublevelCount[$level] = 0;
2376 $toc .= $sk->tocIndent( $level - $prevlevel );
2377 $toclevel +
= $level - $prevlevel;
2379 if( ( $doNumberHeadings ||
$doShowToc ) && $level < $prevlevel ) {
2380 # reset when we step back a level
2381 $sublevelCount[$level+
1]=0;
2382 $toc .= $sk->tocUnindent( $prevlevel - $level );
2383 $toclevel -= $prevlevel - $level;
2385 # count number of headlines for each level
2386 @$sublevelCount[$level]++
;
2387 if( $doNumberHeadings ||
$doShowToc ) {
2389 for( $i = 1; $i <= $level; $i++
) {
2390 if( !empty( $sublevelCount[$i] ) ) {
2394 $numbering .= $wgContLang->formatNum( $sublevelCount[$i] );
2400 # The canonized header is a version of the header text safe to use for links
2401 # Avoid insertion of weird stuff like <math> by expanding the relevant sections
2402 $canonized_headline = $this->unstrip( $headline, $this->mStripState
);
2403 $canonized_headline = $this->unstripNoWiki( $headline, $this->mStripState
);
2405 # Remove link placeholders by the link text.
2406 # <!--LINK number-->
2408 # link text with suffix
2409 $canonized_headline = preg_replace( '/<!--LINK ([0-9]*)-->/e',
2410 "\$wgLinkHolders['texts'][\$1]",
2411 $canonized_headline );
2414 $canonized_headline = preg_replace( '/<.*?' . '>/','',$canonized_headline );
2415 $tocline = trim( $canonized_headline );
2416 $canonized_headline = urlencode( do_html_entity_decode( str_replace(' ', '_', $tocline), ENT_COMPAT
, $wgInputEncoding ) );
2417 $replacearray = array(
2421 $canonized_headline = str_replace(array_keys($replacearray),array_values($replacearray),$canonized_headline);
2422 $refer[$headlineCount] = $canonized_headline;
2424 # count how many in assoc. array so we can track dupes in anchors
2425 @$refers[$canonized_headline]++
;
2426 $refcount[$headlineCount]=$refers[$canonized_headline];
2428 # Prepend the number to the heading text
2430 if( $doNumberHeadings ||
$doShowToc ) {
2431 $tocline = $numbering . ' ' . $tocline;
2433 # Don't number the heading if it is the only one (looks silly)
2434 if( $doNumberHeadings && count( $matches[3] ) > 1) {
2435 # the two are different if the line contains a link
2436 $headline=$numbering . ' ' . $headline;
2440 # Create the anchor for linking from the TOC to the section
2441 $anchor = $canonized_headline;
2442 if($refcount[$headlineCount] > 1 ) {
2443 $anchor .= '_' . $refcount[$headlineCount];
2445 if( $doShowToc && ( !isset($wgMaxTocLevel) ||
$toclevel<$wgMaxTocLevel ) ) {
2446 $toc .= $sk->tocLine($anchor,$tocline,$toclevel);
2448 if( $showEditLink && ( !$istemplate ||
$templatetitle !== "" ) ) {
2449 if ( empty( $head[$headlineCount] ) ) {
2450 $head[$headlineCount] = '';
2453 $head[$headlineCount] .= $sk->editSectionLinkForOther($templatetitle, $templatesection);
2455 $head[$headlineCount] .= $sk->editSectionLink($this->mTitle
, $sectionCount+
1);
2458 # Add the edit section span
2459 if( $rightClickHack ) {
2461 $headline = $sk->editSectionScriptForOther($templatetitle, $templatesection, $headline);
2463 $headline = $sk->editSectionScript($this->title
, $sectionCount+
1,$headline);
2466 # give headline the correct <h#> tag
2467 @$head[$headlineCount] .= "<a name=\"$anchor\"></a><h".$level.$matches[2][$headlineCount] .$headline.'</h'.$level.'>';
2475 $toclines = $headlineCount;
2476 $toc .= $sk->tocUnindent( $toclevel );
2477 $toc = $sk->tocTable( $toc );
2480 # split up and insert constructed headlines
2482 $blocks = preg_split( '/<H[1-6].*?' . '>.*?<\/H[1-6]>/i', $text );
2485 foreach( $blocks as $block ) {
2486 if( $showEditLink && $headlineCount > 0 && $i == 0 && $block != "\n" ) {
2487 # This is the [edit] link that appears for the top block of text when
2488 # section editing is enabled
2490 # Disabled because it broke block formatting
2491 # For example, a bullet point in the top line
2492 # $full .= $sk->editSectionLink(0);
2495 if( $doShowToc && !$i && $isMain && !$forceTocHere) {
2496 # Top anchor now in skin
2500 if( !empty( $head[$i] ) ) {
2506 $mw =& MagicWord
::get( MAG_TOC
);
2507 return $mw->replace( $toc, $full );
2514 * Return an HTML link for the "ISBN 123456" text
2517 function magicISBN( $text ) {
2519 $fname = 'Parser::magicISBN';
2520 wfProfileIn( $fname );
2522 $a = split( 'ISBN ', ' '.$text );
2523 if ( count ( $a ) < 2 ) {
2524 wfProfileOut( $fname );
2527 $text = substr( array_shift( $a ), 1);
2528 $valid = '0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ';
2530 foreach ( $a as $x ) {
2531 $isbn = $blank = '' ;
2532 while ( ' ' == $x{0} ) {
2534 $x = substr( $x, 1 );
2536 if ( $x == '' ) { # blank isbn
2537 $text .= "ISBN $blank";
2540 while ( strstr( $valid, $x{0} ) != false ) {
2542 $x = substr( $x, 1 );
2544 $num = str_replace( '-', '', $isbn );
2545 $num = str_replace( ' ', '', $num );
2548 $text .= "ISBN $blank$x";
2550 $titleObj = Title
::makeTitle( NS_SPECIAL
, 'Booksources' );
2551 $text .= '<a href="' .
2552 $titleObj->escapeLocalUrl( 'isbn='.$num ) .
2553 "\" class=\"internal\">ISBN $isbn</a>";
2557 wfProfileOut( $fname );
2562 * Return an HTML link for the "GEO ..." text
2565 function magicGEO( $text ) {
2566 global $wgLang, $wgUseGeoMode;
2567 $fname = 'Parser::magicGEO';
2568 wfProfileIn( $fname );
2570 # These next five lines are only for the ~35000 U.S. Census Rambot pages...
2571 $directions = array ( 'N' => 'North' , 'S' => 'South' , 'E' => 'East' , 'W' => 'West' ) ;
2572 $text = preg_replace ( "/(\d+)°(\d+)'(\d+)\" {$directions['N']}, (\d+)°(\d+)'(\d+)\" {$directions['W']}/" , "(GEO +\$1.\$2.\$3:-\$4.\$5.\$6)" , $text ) ;
2573 $text = preg_replace ( "/(\d+)°(\d+)'(\d+)\" {$directions['N']}, (\d+)°(\d+)'(\d+)\" {$directions['E']}/" , "(GEO +\$1.\$2.\$3:+\$4.\$5.\$6)" , $text ) ;
2574 $text = preg_replace ( "/(\d+)°(\d+)'(\d+)\" {$directions['S']}, (\d+)°(\d+)'(\d+)\" {$directions['W']}/" , "(GEO +\$1.\$2.\$3:-\$4.\$5.\$6)" , $text ) ;
2575 $text = preg_replace ( "/(\d+)°(\d+)'(\d+)\" {$directions['S']}, (\d+)°(\d+)'(\d+)\" {$directions['E']}/" , "(GEO +\$1.\$2.\$3:+\$4.\$5.\$6)" , $text ) ;
2577 $a = split( 'GEO ', ' '.$text );
2578 if ( count ( $a ) < 2 ) {
2579 wfProfileOut( $fname );
2582 $text = substr( array_shift( $a ), 1);
2583 $valid = '0123456789.+-:';
2585 foreach ( $a as $x ) {
2586 $geo = $blank = '' ;
2587 while ( ' ' == $x{0} ) {
2589 $x = substr( $x, 1 );
2591 while ( strstr( $valid, $x{0} ) != false ) {
2593 $x = substr( $x, 1 );
2595 $num = str_replace( '+', '', $geo );
2596 $num = str_replace( ' ', '', $num );
2598 if ( '' == $num ||
count ( explode ( ':' , $num , 3 ) ) < 2 ) {
2599 $text .= "GEO $blank$x";
2601 $titleObj = Title
::makeTitle( NS_SPECIAL
, 'Geo' );
2602 $text .= '<a href="' .
2603 $titleObj->escapeLocalUrl( 'coordinates='.$num ) .
2604 "\" class=\"internal\">GEO $geo</a>";
2608 wfProfileOut( $fname );
2613 * Return an HTML link for the "RFC 1234" text
2615 * @param string $text text to be processed
2617 function magicRFC( $text ) {
2620 $valid = '0123456789';
2623 $a = split( 'RFC ', ' '.$text );
2624 if ( count ( $a ) < 2 ) return $text;
2625 $text = substr( array_shift( $a ), 1);
2627 /* Check if RFC keyword is preceed by [[.
2628 * This test is made here cause of the array_shift above
2629 * that prevent the test to be done in the foreach.
2631 if(substr($text, -2) == '[[') { $internal = true; }
2633 foreach ( $a as $x ) {
2634 /* token might be empty if we have RFC RFC 1234 */
2640 $rfc = $blank = '' ;
2642 /** remove and save whitespaces in $blank */
2643 while ( $x{0} == ' ' ) {
2645 $x = substr( $x, 1 );
2648 /** remove and save the rfc number in $rfc */
2649 while ( strstr( $valid, $x{0} ) != false ) {
2651 $x = substr( $x, 1 );
2655 /* call back stripped spaces*/
2656 $text .= "RFC $blank$x";
2657 } elseif( $internal) {
2659 $text .= "RFC $rfc$x";
2661 /* build the external link*/
2662 $url = wfmsg( 'rfcurl' );
2663 $url = str_replace( '$1', $rfc, $url);
2664 $sk =& $this->mOptions
->getSkin();
2665 $la = $sk->getExternalLinkAttributes( $url, 'RFC '.$rfc );
2666 $text .= "<a href='{$url}'{$la}>RFC {$rfc}</a>{$x}";
2669 /* Check if the next RFC keyword is preceed by [[ */
2670 $internal = (substr($x,-2) == '[[');
2676 * Transform wiki markup when saving a page by doing \r\n -> \n
2677 * conversion, substitting signatures, {{subst:}} templates, etc.
2679 * @param string $text the text to transform
2680 * @param Title &$title the Title object for the current article
2681 * @param User &$user the User object describing the current user
2682 * @param ParserOptions $options parsing options
2683 * @param bool $clearState whether to clear the parser state first
2684 * @return string the altered wiki markup
2687 function preSaveTransform( $text, &$title, &$user, $options, $clearState = true ) {
2688 $this->mOptions
= $options;
2689 $this->mTitle
=& $title;
2690 $this->mOutputType
= OT_WIKI
;
2692 if ( $clearState ) {
2693 $this->clearState();
2696 $stripState = false;
2700 $text = str_replace(array_keys($pairs), array_values($pairs), $text);
2704 "/<br.+(clear|break)=[\"']?(all|both)[\"']?\\/?>/i" => '<br style="clear:both;"/>',
2705 "/<br *?>/i" => "<br />",
2707 $text = preg_replace(array_keys($pairs), array_values($pairs), $text);
2709 $text = $this->strip( $text, $stripState, false );
2710 $text = $this->pstPass2( $text, $user );
2711 $text = $this->unstrip( $text, $stripState );
2712 $text = $this->unstripNoWiki( $text, $stripState );
2717 * Pre-save transform helper function
2720 function pstPass2( $text, &$user ) {
2721 global $wgLang, $wgContLang, $wgLocaltimezone;
2723 # Variable replacement
2724 # Because mOutputType is OT_WIKI, this will only process {{subst:xxx}} type tags
2725 $text = $this->replaceVariables( $text );
2729 $n = $user->getName();
2730 $k = $user->getOption( 'nickname' );
2731 if ( '' == $k ) { $k = $n; }
2732 if(isset($wgLocaltimezone)) {
2733 $oldtz = getenv('TZ'); putenv('TZ='.$wgLocaltimezone);
2735 /* Note: this is an ugly timezone hack for the European wikis */
2736 $d = $wgContLang->timeanddate( date( 'YmdHis' ), false ) .
2737 ' (' . date( 'T' ) . ')';
2738 if(isset($wgLocaltimezone)) putenv('TZ='.$oldtzs);
2740 $text = preg_replace( '/~~~~~/', $d, $text );
2741 $text = preg_replace( '/~~~~/', '[[' . $wgContLang->getNsText( NS_USER
) . ":$n|$k]] $d", $text );
2742 $text = preg_replace( '/~~~/', '[[' . $wgContLang->getNsText( NS_USER
) . ":$n|$k]]", $text );
2744 # Context links: [[|name]] and [[name (context)|]]
2746 $tc = "[&;%\\-,.\\(\\)' _0-9A-Za-z\\/:\\x80-\\xff]";
2747 $np = "[&;%\\-,.' _0-9A-Za-z\\/:\\x80-\\xff]"; # No parens
2748 $namespacechar = '[ _0-9A-Za-z\x80-\xff]'; # Namespaces can use non-ascii!
2749 $conpat = "/^({$np}+) \\(({$tc}+)\\)$/";
2751 $p1 = "/\[\[({$np}+) \\(({$np}+)\\)\\|]]/"; # [[page (context)|]]
2752 $p2 = "/\[\[\\|({$tc}+)]]/"; # [[|page]]
2753 $p3 = "/\[\[(:*$namespacechar+):({$np}+)\\|]]/"; # [[namespace:page|]] and [[:namespace:page|]]
2754 $p4 = "/\[\[(:*$namespacechar+):({$np}+) \\(({$np}+)\\)\\|]]/"; # [[ns:page (cont)|]] and [[:ns:page (cont)|]]
2756 $t = $this->mTitle
->getText();
2757 if ( preg_match( $conpat, $t, $m ) ) {
2760 $text = preg_replace( $p4, '[[\\1:\\2 (\\3)|\\2]]', $text );
2761 $text = preg_replace( $p1, '[[\\1 (\\2)|\\1]]', $text );
2762 $text = preg_replace( $p3, '[[\\1:\\2|\\2]]', $text );
2764 if ( '' == $context ) {
2765 $text = preg_replace( $p2, '[[\\1]]', $text );
2767 $text = preg_replace( $p2, "[[\\1 ({$context})|\\1]]", $text );
2770 # Trim trailing whitespace
2771 # MAG_END (__END__) tag allows for trailing
2772 # whitespace to be deliberately included
2773 $text = rtrim( $text );
2774 $mw =& MagicWord
::get( MAG_END
);
2775 $mw->matchAndRemove( $text );
2781 * Set up some variables which are usually set up in parse()
2782 * so that an external function can call some class members with confidence
2785 function startExternalParse( &$title, $options, $outputType, $clearState = true ) {
2786 $this->mTitle
=& $title;
2787 $this->mOptions
= $options;
2788 $this->mOutputType
= $outputType;
2789 if ( $clearState ) {
2790 $this->clearState();
2795 * Transform a MediaWiki message by replacing magic variables.
2797 * @param string $text the text to transform
2798 * @param ParserOptions $options options
2799 * @return string the text with variables substituted
2802 function transformMsg( $text, $options ) {
2804 static $executing = false;
2806 # Guard against infinite recursion
2812 $this->mTitle
= $wgTitle;
2813 $this->mOptions
= $options;
2814 $this->mOutputType
= OT_MSG
;
2815 $this->clearState();
2816 $text = $this->replaceVariables( $text );
2823 * Create an HTML-style tag, e.g. <yourtag>special text</yourtag>
2824 * Callback will be called with the text within
2825 * Transform and return the text within
2828 function setHook( $tag, $callback ) {
2829 $oldVal = @$this->mTagHooks
[$tag];
2830 $this->mTagHooks
[$tag] = $callback;
2835 * Replace <!--LINK--> link placeholders with actual links, in the buffer
2836 * Placeholders created in Skin::makeLinkObj()
2837 * Returns an array of links found, indexed by PDBK:
2841 * $options is a bit field, RLH_FOR_UPDATE to select for update
2843 function replaceLinkHolders( &$text, $options = 0 ) {
2844 global $wgUser, $wgLinkCache, $wgUseOldExistenceCheck, $wgLinkHolders;
2846 if ( $wgUseOldExistenceCheck ) {
2850 $fname = 'Parser::replaceLinkHolders';
2851 wfProfileIn( $fname );
2856 #if ( !empty( $tmpLinks[0] ) ) { #TODO
2857 if ( !empty( $wgLinkHolders['namespaces'] ) ) {
2858 wfProfileIn( $fname.'-check' );
2859 $dbr =& wfGetDB( DB_SLAVE
);
2860 $cur = $dbr->tableName( 'cur' );
2861 $sk = $wgUser->getSkin();
2862 $threshold = $wgUser->getOption('stubthreshold');
2865 asort( $wgLinkHolders['namespaces'] );
2869 foreach ( $wgLinkHolders['namespaces'] as $key => $val ) {
2871 $title = $wgLinkHolders['titles'][$key];
2873 # Skip invalid entries.
2874 # Result will be ugly, but prevents crash.
2875 if ( is_null( $title ) ) {
2878 $pdbk = $pdbks[$key] = $title->getPrefixedDBkey();
2880 # Check if it's in the link cache already
2881 if ( $wgLinkCache->getGoodLinkID( $pdbk ) ) {
2882 $colours[$pdbk] = 1;
2883 } elseif ( $wgLinkCache->isBadLink( $pdbk ) ) {
2884 $colours[$pdbk] = 0;
2886 # Not in the link cache, add it to the query
2887 if ( !isset( $current ) ) {
2889 $query = "SELECT cur_id, cur_namespace, cur_title";
2890 if ( $threshold > 0 ) {
2891 $query .= ", LENGTH(cur_text) AS cur_len, cur_is_redirect";
2893 $query .= " FROM $cur WHERE (cur_namespace=$val AND cur_title IN(";
2894 } elseif ( $current != $val ) {
2896 $query .= ")) OR (cur_namespace=$val AND cur_title IN(";
2901 $query .= $dbr->addQuotes( $wgLinkHolders['dbkeys'][$key] );
2906 if ( $options & RLH_FOR_UPDATE
) {
2907 $query .= ' FOR UPDATE';
2910 $res = $dbr->query( $query, $fname );
2912 # Fetch data and form into an associative array
2913 # non-existent = broken
2916 while ( $s = $dbr->fetchObject($res) ) {
2917 $title = Title
::makeTitle( $s->cur_namespace
, $s->cur_title
);
2918 $pdbk = $title->getPrefixedDBkey();
2919 $wgLinkCache->addGoodLink( $s->cur_id
, $pdbk );
2921 if ( $threshold > 0 ) {
2922 $size = $s->cur_len
;
2923 if ( $s->cur_is_redirect ||
$s->cur_namespace
!= 0 ||
$length < $threshold ) {
2924 $colours[$pdbk] = 1;
2926 $colours[$pdbk] = 2;
2929 $colours[$pdbk] = 1;
2933 wfProfileOut( $fname.'-check' );
2935 # Construct search and replace arrays
2936 wfProfileIn( $fname.'-construct' );
2937 global $outputReplace;
2938 $outputReplace = array();
2939 foreach ( $wgLinkHolders['namespaces'] as $key => $ns ) {
2940 $pdbk = $pdbks[$key];
2941 $searchkey = '<!--LINK '.$key.'-->';
2942 $title = $wgLinkHolders['titles'][$key];
2943 if ( empty( $colours[$pdbk] ) ) {
2944 $wgLinkCache->addBadLink( $pdbk );
2945 $colours[$pdbk] = 0;
2946 $outputReplace[$searchkey] = $sk->makeBrokenLinkObj( $title,
2947 $wgLinkHolders['texts'][$key],
2948 $wgLinkHolders['queries'][$key] );
2949 } elseif ( $colours[$pdbk] == 1 ) {
2950 $outputReplace[$searchkey] = $sk->makeKnownLinkObj( $title,
2951 $wgLinkHolders['texts'][$key],
2952 $wgLinkHolders['queries'][$key] );
2953 } elseif ( $colours[$pdbk] == 2 ) {
2954 $outputReplace[$searchkey] = $sk->makeStubLinkObj( $title,
2955 $wgLinkHolders['texts'][$key],
2956 $wgLinkHolders['queries'][$key] );
2959 wfProfileOut( $fname.'-construct' );
2962 wfProfileIn( $fname.'-replace' );
2964 $text = preg_replace_callback(
2965 '/(<!--LINK .*?-->)/',
2966 "outputReplaceMatches",
2968 wfProfileOut( $fname.'-replace' );
2970 wfProfileIn( $fname.'-interwiki' );
2971 global $wgInterwikiLinkHolders;
2972 $outputReplace = $wgInterwikiLinkHolders;
2973 $text = preg_replace_callback(
2974 '/<!--IWLINK (.*?)-->/',
2975 "outputReplaceMatches",
2977 wfProfileOut( $fname.'-interwiki' );
2980 wfProfileOut( $fname );
2987 * @package MediaWiki
2991 var $mText, $mLanguageLinks, $mCategoryLinks, $mContainsOldMagic;
2992 var $mCacheTime; # Used in ParserCache
2994 function ParserOutput( $text = '', $languageLinks = array(), $categoryLinks = array(),
2995 $containsOldMagic = false )
2997 $this->mText
= $text;
2998 $this->mLanguageLinks
= $languageLinks;
2999 $this->mCategoryLinks
= $categoryLinks;
3000 $this->mContainsOldMagic
= $containsOldMagic;
3001 $this->mCacheTime
= '';
3004 function getText() { return $this->mText
; }
3005 function getLanguageLinks() { return $this->mLanguageLinks
; }
3006 function getCategoryLinks() { return $this->mCategoryLinks
; }
3007 function getCacheTime() { return $this->mCacheTime
; }
3008 function containsOldMagic() { return $this->mContainsOldMagic
; }
3009 function setText( $text ) { return wfSetVar( $this->mText
, $text ); }
3010 function setLanguageLinks( $ll ) { return wfSetVar( $this->mLanguageLinks
, $ll ); }
3011 function setCategoryLinks( $cl ) { return wfSetVar( $this->mCategoryLinks
, $cl ); }
3012 function setContainsOldMagic( $com ) { return wfSetVar( $this->mContainsOldMagic
, $com ); }
3013 function setCacheTime( $t ) { return wfSetVar( $this->mCacheTime
, $t ); }
3015 function merge( $other ) {
3016 $this->mLanguageLinks
= array_merge( $this->mLanguageLinks
, $other->mLanguageLinks
);
3017 $this->mCategoryLinks
= array_merge( $this->mCategoryLinks
, $this->mLanguageLinks
);
3018 $this->mContainsOldMagic
= $this->mContainsOldMagic ||
$other->mContainsOldMagic
;
3024 * Set options of the Parser
3026 * @package MediaWiki
3030 # All variables are private
3031 var $mUseTeX; # Use texvc to expand <math> tags
3032 var $mUseDynamicDates; # Use $wgDateFormatter to format dates
3033 var $mInterwikiMagic; # Interlanguage links are removed and returned in an array
3034 var $mAllowExternalImages; # Allow external images inline
3035 var $mSkin; # Reference to the preferred skin
3036 var $mDateFormat; # Date format index
3037 var $mEditSection; # Create "edit section" links
3038 var $mEditSectionOnRightClick; # Generate JavaScript to edit section on right click
3039 var $mNumberHeadings; # Automatically number headings
3040 var $mShowToc; # Show table of contents
3042 function getUseTeX() { return $this->mUseTeX
; }
3043 function getUseDynamicDates() { return $this->mUseDynamicDates
; }
3044 function getInterwikiMagic() { return $this->mInterwikiMagic
; }
3045 function getAllowExternalImages() { return $this->mAllowExternalImages
; }
3046 function getSkin() { return $this->mSkin
; }
3047 function getDateFormat() { return $this->mDateFormat
; }
3048 function getEditSection() { return $this->mEditSection
; }
3049 function getEditSectionOnRightClick() { return $this->mEditSectionOnRightClick
; }
3050 function getNumberHeadings() { return $this->mNumberHeadings
; }
3051 function getShowToc() { return $this->mShowToc
; }
3053 function setUseTeX( $x ) { return wfSetVar( $this->mUseTeX
, $x ); }
3054 function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates
, $x ); }
3055 function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic
, $x ); }
3056 function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages
, $x ); }
3057 function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat
, $x ); }
3058 function setEditSection( $x ) { return wfSetVar( $this->mEditSection
, $x ); }
3059 function setEditSectionOnRightClick( $x ) { return wfSetVar( $this->mEditSectionOnRightClick
, $x ); }
3060 function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings
, $x ); }
3061 function setShowToc( $x ) { return wfSetVar( $this->mShowToc
, $x ); }
3063 function setSkin( &$x ) { $this->mSkin
=& $x; }
3065 # Get parser options
3066 /* static */ function newFromUser( &$user ) {
3067 $popts = new ParserOptions
;
3068 $popts->initialiseFromUser( $user );
3073 function initialiseFromUser( &$userInput ) {
3074 global $wgUseTeX, $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
3076 $fname = 'ParserOptions::initialiseFromUser';
3077 wfProfileIn( $fname );
3078 if ( !$userInput ) {
3080 $user->setLoaded( true );
3082 $user =& $userInput;
3085 $this->mUseTeX
= $wgUseTeX;
3086 $this->mUseDynamicDates
= $wgUseDynamicDates;
3087 $this->mInterwikiMagic
= $wgInterwikiMagic;
3088 $this->mAllowExternalImages
= $wgAllowExternalImages;
3089 wfProfileIn( $fname.'-skin' );
3090 $this->mSkin
=& $user->getSkin();
3091 wfProfileOut( $fname.'-skin' );
3092 $this->mDateFormat
= $user->getOption( 'date' );
3093 $this->mEditSection
= $user->getOption( 'editsection' );
3094 $this->mEditSectionOnRightClick
= $user->getOption( 'editsectiononrightclick' );
3095 $this->mNumberHeadings
= $user->getOption( 'numberheadings' );
3096 $this->mShowToc
= $user->getOption( 'showtoc' );
3097 wfProfileOut( $fname );
3104 * Callback function used by Parser::replaceLinkHolders()
3105 * to substitute link placeholders.
3107 function &outputReplaceMatches($matches) {
3108 global $outputReplace;
3109 return $outputReplace[$matches[1]];
3113 * Return the total number of articles
3115 function wfNumberOfArticles() {
3116 global $wgNumberOfArticles;
3119 return $wgNumberOfArticles;
3123 * Get various statistics from the database
3126 function wfLoadSiteStats() {
3127 global $wgNumberOfArticles, $wgTotalViews, $wgTotalEdits;
3128 $fname = 'wfLoadSiteStats';
3130 if ( -1 != $wgNumberOfArticles ) return;
3131 $dbr =& wfGetDB( DB_SLAVE
);
3132 $s = $dbr->selectRow( 'site_stats',
3133 array( 'ss_total_views', 'ss_total_edits', 'ss_good_articles' ),
3134 array( 'ss_row_id' => 1 ), $fname
3137 if ( $s === false ) {
3140 $wgTotalViews = $s->ss_total_views
;
3141 $wgTotalEdits = $s->ss_total_edits
;
3142 $wgNumberOfArticles = $s->ss_good_articles
;
3146 function wfEscapeHTMLTagsOnly( $in ) {
3148 array( '"', '>', '<' ),
3149 array( '"', '>', '<' ),